From 158cdff069e4d40dc46a620963c12822adb1f5ba Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Mon, 16 Oct 2023 00:24:17 +0200 Subject: [PATCH] work on aicdud.cpp --- source/CMakeLists.txt | 2 +- source/core/maptypes.h | 1 + source/games/blood/all.cpp | 2 +- source/games/blood/src/actor.cpp | 1 - source/games/blood/src/aicdud.cpp | 1451 ++++++++++++++ source/games/blood/src/aiunicult.cpp | 2662 -------------------------- source/games/blood/src/aiunicult.h | 34 - source/games/blood/src/blood.cpp | 8 - source/games/blood/src/blood.h | 46 +- source/games/blood/src/bloodactor.h | 4 +- source/games/blood/src/gameutil.cpp | 24 + source/games/blood/src/gameutil.h | 1 + source/games/blood/src/loadsave.cpp | 9 +- source/games/blood/src/nnextcdud.h | 53 +- source/games/blood/src/nnexts.cpp | 52 +- source/games/blood/src/nnextsif.cpp | 7 + 16 files changed, 1557 insertions(+), 2800 deletions(-) create mode 100644 source/games/blood/src/aicdud.cpp delete mode 100644 source/games/blood/src/aiunicult.cpp diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index 48cab3cc0..f831aaf6e 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -656,7 +656,7 @@ set( NOT_COMPILED_SOURCE_FILES games/blood/src/airat.cpp games/blood/src/aispid.cpp games/blood/src/aitchern.cpp - games/blood/src/aiunicult.cpp + games/blood/src/aicdud.cpp games/blood/src/aizomba.cpp games/blood/src/aizombf.cpp games/blood/src/animatesprite.cpp diff --git a/source/core/maptypes.h b/source/core/maptypes.h index 7572bf93e..983dfd790 100644 --- a/source/core/maptypes.h +++ b/source/core/maptypes.h @@ -39,6 +39,7 @@ void MarkVerticesForSector(int sector); static constexpr double zmaptoworld = (1 / 256.); // this for necessary conversions to convert map data to floating point representation. static constexpr double maptoworld = (1 / 16.); // this for necessary conversions to convert map data to floating point representation. static constexpr double REPEAT_SCALE = (1 / 64.); // map's 'repeat' values use 2.6 fixed point. +static constexpr double CLIPDIST_SCALE = 0.25; static constexpr double INV_REPEAT_SCALE = 64; // These are refactoring markers that should be eliminated. diff --git a/source/games/blood/all.cpp b/source/games/blood/all.cpp index 0a353b32a..29914651e 100644 --- a/source/games/blood/all.cpp +++ b/source/games/blood/all.cpp @@ -29,7 +29,7 @@ #include "src/airat.cpp" #include "src/aispid.cpp" #include "src/aitchern.cpp" -#include "src/aiunicult.cpp" +#include "src/aicdud.cpp" #include "src/aizomba.cpp" #include "src/aizombf.cpp" #include "src/animatesprite.cpp" diff --git a/source/games/blood/src/actor.cpp b/source/games/blood/src/actor.cpp index 19bc0c736..c0cf5cb83 100644 --- a/source/games/blood/src/actor.cpp +++ b/source/games/blood/src/actor.cpp @@ -32,7 +32,6 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. #include "blood.h" #include "texids.h" #ifdef NOONE_EXTENSIONS -#include "aicdud.h" #include "nnexts.h" #include "nnextcdud.h" #endif diff --git a/source/games/blood/src/aicdud.cpp b/source/games/blood/src/aicdud.cpp new file mode 100644 index 000000000..e67e8cf32 --- /dev/null +++ b/source/games/blood/src/aicdud.cpp @@ -0,0 +1,1451 @@ +//------------------------------------------------------------------------- +/* +Copyright (C) 2010-2019 EDuke32 developers and contributors +Copyright (C) 2019 Nuke.YKT +Copyright (C) NoOne + +***************************************************************** +NoOne: AI code for Custom Dude system. +***************************************************************** + +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. +*/ +//------------------------------------------------------------------------- +#ifdef NOONE_EXTENSIONS +#include "ai.h" +#include "nnexts.h" +#include "globals.h" +#include "player.h" +#include "endgame.h" +#include "view.h" +//#include "aicdud.h" + +BEGIN_BLD_NS + +#define SEQOFFS(x) (kCdudeDefaultSeq + x) + +#pragma pack(push, 1) +struct TARGET_INFO +{ + DBloodActor* pSpr; + unsigned int nDist; + DAngle nAng; + DAngle nDang; + int nCode; +}; +#pragma pack(pop) + +void resetTarget(DBloodActor* pXSpr) { pSpr->xspr.xspr.target = nullptr; } +void moveStop(DBloodActor* pSpr) { pSpr->vel.XY().Zero(); } +static char THINK_CLOCK(int nSpr, int nClock = 3) { return ((gFrameCount & nClock) == (nSpr & nClock)); } +static int qsSortTargets(TARGET_INFO* ref1, TARGET_INFO* ref2) { return ref1->nDist > ref2->nDist? 1 : ref1->nDist < ref2->nDist? -1 : 0; } + +// This set of functions needs to be exported for scripting later to allow extension of this list. +static DBloodActor* weaponShotDummy(CUSTOMDUDE*, CUSTOMDUDE_WEAPON*, DVector3& offs, DVector3& vel) { return nullptr; } +static DBloodActor* weaponShotHitscan(CUSTOMDUDE* pDude, CUSTOMDUDE_WEAPON* pWeap, DVector3& offs, DVector3& vel); +static DBloodActor* weaponShotMissile(CUSTOMDUDE* pDude, CUSTOMDUDE_WEAPON* pWeap, DVector3& offs, DVector3& vel); +static DBloodActor* weaponShotThing(CUSTOMDUDE* pDude, CUSTOMDUDE_WEAPON* pWeap, DVector3& offs, DVector3& vel); +static DBloodActor* weaponShotSummon(CUSTOMDUDE* pDude, CUSTOMDUDE_WEAPON* pWeap, DVector3& offs, DVector3& vel); +static DBloodActor* weaponShotKamikaze(CUSTOMDUDE* pDude, CUSTOMDUDE_WEAPON* pWeap, DVector3& offs, DVector3& vel); +static DBloodActor* weaponShotSpecialBeastStomp(CUSTOMDUDE* pDude, CUSTOMDUDE_WEAPON*, DVector3& offs, DVector3& vel); + +DBloodActor* (*gWeaponShotFunc[])(CUSTOMDUDE* pDude, CUSTOMDUDE_WEAPON* pWeap, DVector3& offs, DVector3& vel) = +{ + weaponShotDummy, // none + weaponShotHitscan, + weaponShotMissile, + weaponShotThing, + weaponShotSummon, // vanilla dude + weaponShotSummon, // custom dude + weaponShotKamikaze, + weaponShotSpecialBeastStomp, +}; + +static AISTATE gCdudeStateDeath = { kAiStateOther, -1, nullptr, 0, &AF(enterDeath), NULL, NULL, NULL }; // just turns dude to a gib + +// Land, Crouch, Swim (proper order matters!) +AISTATE gCdudeStateTemplate[kCdudeStateNormalMax][kCdudePostureMax] = +{ + // idle (don't change pos or patrol gets broken!) + { + { kAiStateIdle, SEQOFFS(0), nullptr, 0, &AF(resetTarget), NULL, &AF(thinkTarget), NULL }, + { kAiStateIdle, SEQOFFS(17), nullptr, 0, &AF(resetTarget), NULL, &AF(thinkTarget), NULL }, + { kAiStateIdle, SEQOFFS(13), nullptr, 0, &AF(resetTarget), NULL, &AF(thinkTarget), NULL }, + }, + + // search (don't change pos or patrol gets broken!) + { + { kAiStateSearch, SEQOFFS(9), nullptr, 800, NULL, &AF(moveForward), &AF(thinkSearch), &gCdudeStateTemplate[kCdudeStateIdle][kCdudePostureL] }, + { kAiStateSearch, SEQOFFS(14), nullptr, 800, NULL, &AF(moveForward), &AF(thinkSearch), &gCdudeStateTemplate[kCdudeStateIdle][kCdudePostureC] }, + { kAiStateSearch, SEQOFFS(13), nullptr, 800, NULL, &AF(moveForward), &AF(thinkSearch), &gCdudeStateTemplate[kCdudeStateIdle][kCdudePostureW] }, + }, + + // dodge + { + { kAiStateMove, SEQOFFS(9), nullptr, 90, NULL, &AF(moveDodge), NULL, &gCdudeStateTemplate[kCdudeStateChase][kCdudePostureL] }, + { kAiStateMove, SEQOFFS(14), nullptr, 90, NULL, &AF(moveDodge), NULL, &gCdudeStateTemplate[kCdudeStateChase][kCdudePostureC] }, + { kAiStateMove, SEQOFFS(13), nullptr, 90, NULL, &AF(moveDodge), NULL, &gCdudeStateTemplate[kCdudeStateChase][kCdudePostureW] }, + }, + + // chase + { + { kAiStateChase, SEQOFFS(9), nullptr, 30, NULL, &AF(moveForward), &AF(thinkChase), NULL }, + { kAiStateChase, SEQOFFS(14), nullptr, 30, NULL, &AF(moveForward), &AF(thinkChase), NULL }, + { kAiStateChase, SEQOFFS(13), nullptr, 30, NULL, &AF(moveForward), &AF(thinkChase), NULL }, + }, + + // flee + { + { kAiStateMove, SEQOFFS(9), nullptr, 256, NULL, &AF(moveForward), &AF(thinkFlee), &gCdudeStateTemplate[kCdudeStateSearch][kCdudePostureL] }, + { kAiStateMove, SEQOFFS(14), nullptr, 256, NULL, &AF(moveForward), &AF(thinkFlee), &gCdudeStateTemplate[kCdudeStateSearch][kCdudePostureC] }, + { kAiStateMove, SEQOFFS(13), nullptr, 256, NULL, &AF(moveForward), &AF(thinkFlee), &gCdudeStateTemplate[kCdudeStateSearch][kCdudePostureW] }, + }, + + // recoil normal + { + { kAiStateRecoil, SEQOFFS(5), nullptr, 0, NULL, NULL, NULL, &gCdudeStateTemplate[kCdudeStateChase][kCdudePostureL] }, + { kAiStateRecoil, SEQOFFS(5), nullptr, 0, NULL, NULL, NULL, &gCdudeStateTemplate[kCdudeStateChase][kCdudePostureC] }, + { kAiStateRecoil, SEQOFFS(5), nullptr, 0, NULL, NULL, NULL, &gCdudeStateTemplate[kCdudeStateChase][kCdudePostureW] }, + }, + + // recoil tesla + { + { kAiStateRecoil, SEQOFFS(4), nullptr, 0, NULL, NULL, NULL, &gCdudeStateTemplate[kCdudeStateChase][kCdudePostureL] }, + { kAiStateRecoil, SEQOFFS(4), nullptr, 0, NULL, NULL, NULL, &gCdudeStateTemplate[kCdudeStateChase][kCdudePostureC] }, + { kAiStateRecoil, SEQOFFS(4), nullptr, 0, NULL, NULL, NULL, &gCdudeStateTemplate[kCdudeStateChase][kCdudePostureW] }, + }, + + // burn search + { + { kAiStateSearch, SEQOFFS(3), nullptr, 3600, &AF(enterBurnSearchWater), &AF(aiMoveForward), &AF(maybeThinkSearch), &gCdudeStateTemplate[kCdudeBurnStateSearch][kCdudePostureL]}, + { kAiStateSearch, SEQOFFS(3), nullptr, 3600, &AF(enterBurnSearchWater), &AF(aiMoveForward), &AF(maybeThinkSearch), &gCdudeStateTemplate[kCdudeBurnStateSearch][kCdudePostureC] }, + { kAiStateSearch, SEQOFFS(3), nullptr, 3600, &AF(enterBurnSearchWater), &AF(aiMoveForward), &AF(maybeThinkSearch), &gCdudeStateTemplate[kCdudeBurnStateSearch][kCdudePostureW] }, + }, + + // morph (put thinkFunc in moveFunc because it supposed to work fast) + { + { kAiStateOther, SEQOFFS(18), nullptr, 0, &AF(enterMorph), &AF(thinkMorph), NULL, NULL }, + { kAiStateOther, SEQOFFS(18), nullptr, 0, &AF(enterMorph), &AF(thinkMorph), NULL, NULL }, + { kAiStateOther, SEQOFFS(18), nullptr, 0, &AF(enterMorph), &AF(thinkMorph), NULL, NULL }, + }, + + // knock enter + { + { kAiStateKnockout, SEQOFFS(0), nullptr, 0, NULL, NULL, NULL, &gCdudeStateTemplate[kCdudeStateKnock][kCdudePostureL] }, + { kAiStateKnockout, SEQOFFS(0), nullptr, 0, NULL, NULL, NULL, &gCdudeStateTemplate[kCdudeStateKnock][kCdudePostureC] }, + { kAiStateKnockout, SEQOFFS(0), nullptr, 0, NULL, &AF(moveKnockout), NULL, &gCdudeStateTemplate[kCdudeStateKnock][kCdudePostureW] }, + }, + + // knock + { + { kAiStateKnockout, SEQOFFS(0), nullptr, 0, NULL, NULL, NULL, &gCdudeStateTemplate[kCdudeStateKnockExit][kCdudePostureL] }, + { kAiStateKnockout, SEQOFFS(0), nullptr, 0, NULL, NULL, NULL, &gCdudeStateTemplate[kCdudeStateKnockExit][kCdudePostureC] }, + { kAiStateKnockout, SEQOFFS(0), nullptr, 0, NULL, &AF(moveKnockout), NULL, &gCdudeStateTemplate[kCdudeStateKnockExit][kCdudePostureW] }, + }, + + // knock exit + { + { kAiStateKnockout, SEQOFFS(0), nullptr, 0, NULL, &AF(turnToTarget), NULL, &gCdudeStateTemplate[kCdudeStateSearch][kCdudePostureL] }, + { kAiStateKnockout, SEQOFFS(0), nullptr, 0, NULL, &AF(turnToTarget), NULL, &gCdudeStateTemplate[kCdudeStateSearch][kCdudePostureC] }, + { kAiStateKnockout, SEQOFFS(0), nullptr, 0, NULL, &AF(turnToTarget), NULL, &gCdudeStateTemplate[kCdudeStateSearch][kCdudePostureW] }, + }, + + // sleep + { + { kAiStateIdle, SEQOFFS(0), nullptr, 0, &AF(enterSleep), NULL, &AF(thinkTarget), NULL }, + { kAiStateIdle, SEQOFFS(0), nullptr, 0, &AF(enterSleep), NULL, &AF(thinkTarget), NULL }, + { kAiStateIdle, SEQOFFS(0), nullptr, 0, &AF(enterSleep), NULL, &AF(thinkTarget), NULL }, + }, + + // wake + { + { kAiStateIdle, SEQOFFS(0), nullptr, 0, &AF(enterWake), &AF(turnToTarget), NULL, &gCdudeStateTemplate[kCdudeStateSearch][kCdudePostureL] }, + { kAiStateIdle, SEQOFFS(0), nullptr, 0, &AF(enterWake), &AF(turnToTarget), NULL, &gCdudeStateTemplate[kCdudeStateSearch][kCdudePostureC] }, + { kAiStateIdle, SEQOFFS(0), nullptr, 0, &AF(enterWake), &AF(turnToTarget), NULL, &gCdudeStateTemplate[kCdudeStateSearch][kCdudePostureW] }, + }, + + // generic idle (ai fight compat.) + { + { kAiStateGenIdle, SEQOFFS(0), nullptr, 0, &AF(resetTarget), NULL, NULL, NULL }, + { kAiStateGenIdle, SEQOFFS(17), nullptr, 0, &AF(resetTarget), NULL, NULL, NULL }, + { kAiStateGenIdle, SEQOFFS(13), nullptr, 0, &AF(resetTarget), NULL, NULL, NULL }, + }, +}; + +// Land, Crouch, Swim +AISTATE gCdudeStateAttackTemplate[kCdudePostureMax] = +{ + // attack (put thinkFunc in moveFunc because it supposed to work fast) + { kAiStateAttack, SEQOFFS(6), &AF(weaponShot), 0, &AF(moveStop), &AF(thinkChase), NULL, &gCdudeStateAttackTemplate[kCdudePostureL] }, + { kAiStateAttack, SEQOFFS(8), &AF(weaponShot), 0, &AF(moveStop), &AF(thinkChase), NULL, &gCdudeStateAttackTemplate[kCdudePostureC] }, + { kAiStateAttack, SEQOFFS(8), &AF(weaponShot), 0, &AF(moveStop), &AF(thinkChase), NULL, &gCdudeStateAttackTemplate[kCdudePostureW] }, +}; + +// Random pick +AISTATE gCdudeStateDyingTemplate[kCdudePostureMax] = +{ + // dying + { kAiStateOther, SEQOFFS(1), nullptr, 0, &AF(enterDying), NULL, &AF(thinkDying), &gCdudeStateDeath }, + { kAiStateOther, SEQOFFS(1), nullptr, 0, &AF(enterDying), NULL, &AF(thinkDying), &gCdudeStateDeath }, + { kAiStateOther, SEQOFFS(1), nullptr, 0, &AF(enterDying), NULL, &AF(thinkDying), &gCdudeStateDeath }, +}; + +// for kModernThingThrowableRock +static const short gCdudeDebrisPics[6] = +{ + 2406, 2280, 2185, 2155, 2620, 3135 +}; + +static DBloodActor* weaponShotHitscan(CUSTOMDUDE* pDude, CUSTOMDUDE_WEAPON* pWeap, DVector3& pOffs, DVector3& vel) +{ + const VECTORDATA* pVect = &gVectorData[pWeap->id]; + auto pSpr = pDude->pSpr; + + // ugly hack to make it fire at required distance was removed because we have a better solution! :P + actFireVector(pSpr, pOffs.X, pOffs.Y, vel, (VECTOR_TYPE)pWeap->id, pWeap->GetDistanceF()); + + return nullptr; +} + +static DBloodActor* weaponShotMissile(CUSTOMDUDE* pDude, CUSTOMDUDE_WEAPON* pWeap, DVector3& pOffs, DVector3& vel) +{ + DBloodActor* pSpr = pDude->pSpr, *pShot; + + pShot = nnExtFireMissile(pSpr, pOffs.X, pOffs.Y, vel, pWeap->id); + if (pShot) + { + nnExtOffsetSprite(pShot, DVector3(0, pOffs.Y, 0)); + + if (pWeap->shot.clipdist) + pShot->clipdist = pWeap->shot.clipdist; + + if (pWeap->HaveVelocity()) + { + pShot->xspr.target = nullptr; // have to erase, so vanilla won't set velocity back + nnExtScaleVelocity(pShot, pWeap->shot._velocity, vel); + } + + pWeap->shot.appearance.Set(pShot); + + if (pWeap->shot.targetFollow != nullAngle) + { + pShot->xspr.goalAng = pWeap->shot.targetFollow; + gFlwSpritesList.Push(MakeObjPtr(pShot)); + pShot->prevmarker = pSpr->xspr.target; + pShot->xspr.target = nullptr; // have own target follow code + } + + + return pShot; + } + + return nullptr; +} + +static DBloodActor* weaponShotThing(CUSTOMDUDE* pDude, CUSTOMDUDE_WEAPON* pWeap, DVector3& pOffs, DVector3& vel) +{ + DBloodActor* pSpr = pDude->pSpr; + DBloodActor* pLeech = pDude->pLeech, *pShot, *pTarget = pSpr->xspr.target; + + if (!pTarget) + return nullptr; + + auto dv = pTarget->spr.pos - pSpr->spr.pos; + auto nDist = dv.Length(); + int nDiv = 540, nSlope = 12000; + bool impact = true; + int nHealth = 0; + + switch (pWeap->id) + { + case kModernThingEnemyLifeLeech: + case kThingDroppedLifeLeech: + if (!pDude->IsLeechBroken()) + { + if (pLeech) + { + if (xsprIsFine(pLeech)) + nHealth = pLeech->xspr.health; + + pDude->LeechPickup(); // pickup it before throw + } + } + break; + } + + nSlope = (pWeap->HaveSlope()) ? pWeap->shot.slope : (int(vel.Z * 2) - nSlope); + + // fixed point math sucks + //nVel = divscale23(nDist / nDiv, 120); + double nVel = (nDist * worldtoint * 128 / nDiv) / 120.; + + pShot = actFireThing(pSpr, -pOffs.X, pOffs.Z, FixedToFloat(nSlope), pWeap->id, nVel); + if (pShot) + { + nnExtOffsetSprite(pShot, DVector3(0, pOffs.Y, 0)); + + pShot->ownerActor = pSpr; + + switch (pWeap->id) + { + case kModernThingTNTProx: + case kThingArmedProxBomb: + case kModernThingThrowableRock: + case kModernThingEnemyLifeLeech: + case kThingDroppedLifeLeech: + case kThingBloodBits: + case kThingBloodChunks: + switch (pWeap->id) + { + case kModernThingThrowableRock: + pShot->spr.setspritetexture(tileGetTextureID(gCdudeDebrisPics[Random(countof(gCdudeDebrisPics))])); + pShot->spr.scale.X = pShot->spr.scale.Y = (24 + Random(42)) * REPEAT_SCALE; + pShot->spr.cstat |= CSTAT_SPRITE_BLOCK; + pShot->spr.pal = 5; + + if (Chance(0x5000)) pShot->spr.cstat |= CSTAT_SPRITE_XFLIP; + if (Chance(0x5000)) pShot->spr.cstat |= CSTAT_SPRITE_YFLIP; + + if (pShot->spr.scale.X > 60 * REPEAT_SCALE) pShot->xspr.data1 = 43; + else if (pShot->spr.scale.X > 40 * REPEAT_SCALE) pShot->xspr.data1 = 33; + else if (pShot->spr.scale.X > 30 * REPEAT_SCALE) pShot->xspr.data1 = 23; + else pShot->xspr.data1 = 12; + break; + case kThingArmedProxBomb: + case kModernThingTNTProx: + pShot->xspr.state = 0; + pShot->xspr.Proximity = true; + break; + case kThingBloodBits: + case kThingBloodChunks: + DudeToGibCallback1(pShot); + break; + default: + if (pLeech) + { + pShot->xspr.health = nHealth; + } + else + { + pShot->xspr.health = ((pShot->IntVar("defhealth") << 4) * ClipLow(gGameOptions.nDifficulty, 1)) >> 1; + } + + pShot->spr.cstat &= ~CSTAT_SPRITE_BLOCK; + pShot->spr.pal = 6; + pShot->spr.clipdist = 0; + pShot->xspr.data3 = 512 / (gGameOptions.nDifficulty + 1); + pShot->xspr.target = pTarget; + pShot->xspr.Proximity = true; + pShot->xspr.stateTimer = 1; + + evPostActor(pShot, 80, AF(LeechStateTimer)); + pDude->pLeech = pShot; + break; + } + impact = false; + break; + case kThingNapalmBall: + pShot->spr.scale.X = pShot->spr.scale.Y = 24 * REPEAT_SCALE; + pShot->xspr.data4 = 3 + Random2(2); + pShot->xspr.Impact = true; + break; + } + + if (pWeap->shot.clipdist) pShot->clipdist = pWeap->shot.clipdist * CLIPDIST_SCALE; + if (pWeap->HaveVelocity()) nnExtScaleVelocity(pShot, pWeap->shot._velocity * 8, vel, 0x01); + + pWeap->shot.appearance.Set(pShot); + + if (pWeap->shot.targetFollow != nullAngle) + { + pShot->xspr.goalAng = pWeap->shot.targetFollow; + pShot->prevmarker = pSpr->xspr.target; + gFlwSpritesList.Push(MakeObjPtr(pShot)); + } + + if (pWeap->shot.impact > 1) + { + if (impact) + pShot->xspr.Impact = (pShot->xspr.Impact != 0 && nDist <= 7680); + } + else + { + pShot->xspr.Impact = pWeap->shot.impact; + } + + if (!pShot->xspr.Impact) + evPostActor(pShot, 120 * Random(2) + 120, kCmdOn, pSpr); + + return pShot; + } + + return nullptr; +} + +static bool posObstructed(DVector3& pos, double nRadius) +{ + int i; + for (i = sector.SSize() - 1; i >= 0; i--) + { + if (inside(pos.X, pos.Y, §or[i])) + break; + } + if (i < 0) + return true; + + BloodSpriteIterator it; + while (auto pSpr = it.Next()) + { + if ((pSpr->spr.flags & kHitagFree) || (pSpr->spr.flags & kHitagRespawn)) continue; + if ((pSpr->spr.cstat & CSTAT_SPRITE_ALIGNMENT_MASK) != CSTAT_SPRITE_ALIGNMENT_FACING) + continue; + + if (!(pSpr->spr.cstat & CSTAT_SPRITE_BLOCK)) + { + if (!pSpr->IsDudeActor() || !dudeIsAlive(pSpr)) + continue; + } + else + { + auto tex = TexMan.GetGameTexture(pSpr->spr.spritetexture()); + if (!tex) continue; + float w = tex->GetDisplayWidth(); + float h = tex->GetDisplayHeight(); + + if (w <= 0 || h <= 0) + continue; + } + + if (CheckProximityPoint(pSpr->spr.pos.X, pSpr->spr.pos.Y, pSpr->spr.pos.Z, pos.X, pos.Y, pos.Z, nRadius)) + return true; + } + + return false; +} + + + +static DBloodActor* weaponShotSummon(CUSTOMDUDE* pDude, CUSTOMDUDE_WEAPON* pWeap, DVector3& offs, DVector3& vel) +{ + DBloodActor* pShot, *pSpr = pDude->pSpr; + + DVector3 pos = pSpr->spr.pos; + DAngle a = nullAngle; + + int nDude = pWeap->id; + if (pWeap->type == kCdudeWeaponSummonCdude) + nDude = kDudeModernCustom; + + DVector3 pOffs(offs.X, max(offs.Y, 800.), offs.Z); + nnExtOffsetPos(pOffs, pSpr->spr.Angles.Yaw, pos); + + while (a < DAngle180) + { + if (!posObstructed(pos, 2.)) + { + if ((pShot = nnExtSpawnDude(pSpr, nDude, pos)) != NULL) + { + if (nDude == kDudeModernCustom) + pShot->xspr.data1 = pWeap->id; + + if (pWeap->shot.clipdist) + pShot->clipdist = pWeap->shot.clipdist * CLIPDIST_SCALE; + + if (pWeap->HaveVelocity()) + nnExtScaleVelocity(pShot, pWeap->shot._velocity, vel); + + pWeap->shot.appearance.Set(pShot); + + aiInitSprite(pShot); + + pShot->xspr.TargetPos = pSpr->xspr.TargetPos; + pShot->xspr.target = pSpr->xspr.target; + pShot->spr.Angles = pSpr->spr.Angles; + + aiActivateDude(pShot); + + pDude->pSlaves.Push(MakeObjPtr(pShot)); + if (AllowedKillType(pShot)) + Level.addKillCount(); + + return pShot; + } + } + else + { + pos.XY() = rotatepoint(pSpr->spr.pos.XY(), pos.XY(), a); + a += DAngle15; + continue; + } + + break; + } + + return nullptr; +} + +static DBloodActor* weaponShotKamikaze(CUSTOMDUDE* pDude, CUSTOMDUDE_WEAPON* pWeap, DVector3& pOffs, DVector3& vel) +{ + DBloodActor* pSpr = pDude->pSpr; + DBloodActor* pShot = actSpawnSprite(pSpr->sector(), pSpr->spr.pos, kStatExplosion, true); + + if (pShot) + { + int nType = pWeap->id - kTrapExploder; + const EXPLOSION* pExpl = &explodeInfo[nType]; + const EXPLOSION_EXTRA* pExtra = &gExplodeExtra[nType]; + + pShot->spr.lotag = nType; // this may not call ChangeType! + pShot->spr.cstat |= CSTAT_SPRITE_INVISIBLE; + pShot->ownerActor = pSpr; + pShot->spr.shade = -127; + pShot->spr.scale.X = pShot->spr.scale.Y = pExpl->repeat * REPEAT_SCALE; + pShot->spr.Angles.Yaw = pSpr->spr.Angles.Yaw; + + pShot->xspr.data1 = pExpl->ticks; + pShot->xspr.data2 = pExpl->quakeEffect; + pShot->xspr.data3 = pExpl->flashEffect; + pShot->xspr.data4 = ClipLow(pWeap->GetDistance() >> 4, pExpl->radius); + + seqSpawn(pExtra->seq, pShot); + + if (pExtra->ground) + pShot->spr.pos.Z = getflorzofslopeptr(pShot->sector(), pShot->spr.pos.X, pShot->spr.pos.Y); + + pWeap->shot.appearance.Set(pShot); + + clampSprite(pShot); + nnExtOffsetSprite(pShot, pOffs); // offset after default sprite placement + } + + if (pSpr->xspr.health) + { + pSpr->xspr.health = 0; // it supposed to attack once + pDude->Kill(pSpr, kDamageExplode, 0x10000); + } + + return pShot; +} + +static DBloodActor* weaponShotSpecialBeastStomp(CUSTOMDUDE* pDude, CUSTOMDUDE_WEAPON* pWeapon, DVector3& pOffs, DVector3& vel) +{ + DBloodActor* pSpr = pDude->pSpr; + + int vc = 400; + int v1c = 7 * gGameOptions.nDifficulty; + int v10 = 55 * gGameOptions.nDifficulty; + + for (auto stat : { kStatDude, kStatThing }) + { + BloodStatIterator it(stat); + while (auto pSpr2 = it.Next()) + { + if (pSpr2 == pSpr || !xsprIsFine(pSpr2) || pSpr2->ownerActor == pSpr) + continue; + + if (CheckProximity(pSpr2, pSpr->spr.pos, pSpr->sector(), pWeapon->GetDistance())) + { + double nDist2 = (pSpr->spr.pos.XY() - pSpr2->spr.pos.XY()).LengthSquared(); + if (nDist2 <= vc * vc) + { + int nDamage; + if (!nDist2) + nDamage = v1c + v10; + else + nDamage = v1c + ((vc - nDist2) * v10) / vc; + + if (pSpr2->IsPlayerActor()) + { + auto pPlayer = getPlayer(pSpr2); + pPlayer->quakeEffect = ClipHigh(pPlayer->quakeEffect + (nDamage << 2), 1280); + } + + actDamageSprite(pSpr, pSpr2, kDamageFall, nDamage << 4); + } + } + } + } + + return nullptr; +} + + + +void weaponShot(DBloodActor* pSpr) +{ + // most of the fixed point math in here has been kept for reasons of simplicity. + if (!pSpr->hasX()) + return; + + CUSTOMDUDE* pDude = cdudeGet(pSpr); + CUSTOMDUDE_WEAPON *pCurWeap = pDude->pWeapon, *pWeap; + DBloodActor *pShot; + POINT3D *pStyleOffs; + DVector3 shotoffs; + + int nShots, nTime; + int dz1; + int dx2, dy2, dz2; + int dx3, dy3, dz3; + int i, j; + + int txof; char hxof; + int sang; int hsht; + int tang; char styled; + + + const int dx1 = int(pSpr->spr.Angles.Yaw.Cos() * 16384); + const int dy1 = int(pSpr->spr.Angles.Yaw.Sin() * 16384); + + if (pCurWeap) + { + for (i = 0; i < pDude->numWeapons; i++) + { + pWeap = &pDude->weapons[i]; + if (pWeap->available) + { + if (pCurWeap != pWeap) + { + // check if this weapon could be used in conjunction with current + if (!pCurWeap->sharedId || pCurWeap->sharedId != pWeap->sharedId) + continue; + } + + nShots = pWeap->GetNumshots(); pWeap->ammo.Dec(nShots); + styled = (nShots > 1 && pWeap->style.available); + shotoffs = pWeap->shot.offset; + + if (styled) + { + pStyleOffs = &pWeap->style.offset; hsht = nShots >> 1; + sang = pWeap->style.angle / nShots; + hxof = 0; + tang = 0; + } + + dz1 = (pWeap->shot.slope == INT32_MAX) ? + pDude->AdjustSlope(pSpr->xspr.target, pWeap->shot.offset.Z) : pWeap->shot.slope; + + for (j = nShots; j > 0; j--) + { + if (!styled || j == nShots) + { + dx3 = Random3(pWeap->dispersion[0]); + dy3 = Random3(pWeap->dispersion[0]); + dz3 = Random3(pWeap->dispersion[1]); + + dx2 = dx1 + dx3; + dy2 = dy1 + dy3; + dz2 = dz1 + dz3; + } + + DVector3 dv(dx2 * inttoworld, dy2 * inttoworld, dz2 * zinttoworld); + pShot = gWeaponShotFunc[pWeap->type](pDude, pWeap, shotoffs, dv); + if (pShot) + { + // override removal timer + if ((nTime = pWeap->shot.remTime) >= 0) + { + evKillActor(pShot, AF(RemoveActor)); + if (nTime) + evPostActor(pShot, nTime, AF(RemoveActor)); + } + + // setup style + if (styled) + { + if (pStyleOffs->X) + { + txof = pStyleOffs->X; + if (j <= hsht) + { + if (!hxof) + { + shotoffs.X = pWeap->shot.offset.X; + hxof = 1; + } + + txof = -txof; + } + + shotoffs.X += txof * inttoworld; + } + + shotoffs.Y += pStyleOffs->Y * inttoworld; + shotoffs.Z += pStyleOffs->Z * zinttoworld; + + if (pWeap->style.angle) + { + // for sprites + if (pShot) + { + if (j <= hsht && sang > 0) + { + sang = -sang; + tang = 0; + } + + tang += sang; + pShot->vel.XY() = rotatepoint(pShot->vel.XY(), pSpr->spr.pos.XY(), DAngle::fromBuild(tang)); // formula looks broken + //pShot->vel.XY() = rotatepoint(pShot->vel.XY(), DVector2(0, 0), DAngle::fromBuild(tang)); // what it probably should be! + pShot->spr.Angles.Yaw = pShot->vel.Angle(); + } + // for hitscan + else + { + if (j <= hsht && sang > 0) + { + dx2 = dx1 + dx3; dy2 = dy1 + dy3; + sang = -sang; + } + + auto dv = rotatepoint(DVector2(dx2 * inttoworld, dy2 * inttoworld), pSpr->spr.pos.XY(), DAngle::fromBuild(sang)); // formula looks broken + //auto dv = rotatepoint(DVector2(dx2 * inttoworld, dy2 * inttoworld), DVector2(0, 0), DAngle::fromBuild(sang)); // what it probably should be! + dx2 = int(dv.X * worldtoint); + dy2 = int(dv.Y * worldtoint); + } + } + } + } + } + + pWeap->sound.Play(pSpr); + if (pWeap->cooldown.Check()) + pWeap->available = 0; + } + } + } +} + +static int checkTarget(CUSTOMDUDE* pDude, DBloodActor* pTarget, TARGET_INFO* pOut) +{ + DBloodActor* pSpr = pDude->pSpr; + if (!xspriRangeIsFine(pTarget->extra)) + return -1; + + XSPRITE* pXTarget = &xsprite[pTarget->extra]; + if (pSpr->owner == pTarget->index || pXTarget->health <= 0) + return -2; + + if (IsPlayerSprite(pTarget)) + { + PLAYER* pPlayer = &gPlayer[pTarget->type - kDudePlayer1]; + if (powerupCheck(pPlayer, kPwUpShadowCloak) > 0) + return -3; + } + + int x = pTarget->x; + int y = pTarget->y; + int z = pTarget->z; + int nSector = pTarget->sectnum; + int dx = x - pSpr->x; + int dy = y - pSpr->y; + int nDist = approxDist(dx, dy); + char s = (nDist < pDude->seeDist); + char h = (nDist < pDude->hearDist); + + if (!s && !h) + return -4; + + DUDEINFO* pInfo = pDude->pInfo; + if (!cansee(x, y, z, nSector, pSpr->x, pSpr->y, pSpr->z - ((pInfo->eyeHeight * pSpr->yrepeat) << 2), pSpr->sectnum)) + return -5; + + int nAng = getangle(dx, dy); + if (s) + { + int nDang = klabs(((nAng + kAng180 - pSpr->ang) & kAngMask) - kAng180); + if (nDang <= pDude->periphery) + { + pOut->pSpr = pTarget; + pOut->nDist = nDist; + pOut->nDang = nDang; + pOut->nAng = nAng; + pOut->nCode = 1; + return 1; + } + } + + if (h) + { + pOut->pSpr = pTarget; + pOut->nDist = nDist; + pOut->nDang = 0; + pOut->nAng = nAng; + pOut->nCode = 2; + return 2; + } + + return -255; +} + +void thinkTarget(DBloodActor* pSpr) +{ + int i; + spritetype* pTarget; + TARGET_INFO targets[kMaxPlayers], *pInfo = targets; + CUSTOMDUDE* pDude = cdudeGet(pSpr); + int numTargets = 0; + + if (Chance(pDude->pInfo->alertChance)) + { + for (i = connecthead; i >= 0; i = connectpoint2[i]) + { + PLAYER* pPlayer = &gPlayer[i]; + if (checkTarget(pDude, pPlayer->pSprite, &targets[numTargets]) > 0) + numTargets++; + } + + if (numTargets) + { + if (numTargets > 1) // closest first + qsort(targets, numTargets, sizeof(targets[0]), (int(*)(const void*, const void*))qsSortTargets); + + pTarget = pInfo->pSpr; + if (pDude->pExtra->stats.active) + { + if (pSpr->xspr.target != pTarget->index || Chance(0x0400)) + pDude->PlaySound(kCdudeSndTargetSpot); + } + + pSpr->xspr.goalAng = pInfo->nAng & kAngMask; + if (pInfo->nCode == 1) aiSetTarget(pXSpr, pTarget->index); + else aiSetTarget(pXSpr, pTarget->x, pTarget->y, pTarget->z); + aiActivateDude(pSpr); + } + } +} + +void thinkFlee(DBloodActor* pSpr) +{ + int nAng = getangle(pSpr->x - pSpr->xspr.targetX, pSpr->y - pSpr->xspr.targetY); + int nDang = klabs(((nAng + kAng180 - pSpr->ang) & kAngMask) - kAng180); + if (nDang > kAng45) + pSpr->xspr.goalAng = (nAng + (kAng15 * Random2(2))) & kAngMask; + + aiChooseDirection(pSpr, pXSpr, pSpr->xspr.goalAng); + +} + +void thinkSearch(DBloodActor* pSpr) +{ + aiChooseDirection(pSpr, pSpr->xspr.goalAng); + thinkTarget(pSpr); +} + +void maybeThinkSearch(DBloodActor* pSpr) +{ + // this originally edited the state's callback, but that's inherently non-serializable so another way had to be chosen. + if (pSpr->chasehackflag) + { + aiChooseDirection(pSpr, pSpr->xspr.goalAng); + thinkTarget(pSpr); + } +} + +void thinkChase(DBloodActor* pSpr) +{ + CUSTOMDUDE* pDude = cdudeGet(pSpr); HITINFO* pHit = &gHitInfo; DUDEINFO* pInfo = pDude->pInfo; + int nDist, nHeigh, dx, dy, nDAng, nSlope = 0; + char thinkTime = THINK_CLOCK(pSpr->index); + char turn2target = 0, interrupt = 0; + char inAttack = pDude->IsAttacking(); + char changePos = 0; + + if (!spriRangeIsFine(pSpr->xspr.target)) + { + pDude->NewState(kCdudeStateSearch); + return; + } + + spritetype* pTarget = &sprite[pSpr->xspr.target]; + if (pTarget->owner == pSpr->index || !IsDudeSprite(pTarget) || !xsprIsFine(pTarget)) // target lost + { + pDude->NewState(kCdudeStateSearch); + return; + } + + XSPRITE* pXTarget = &xsprite[pTarget->extra]; + if (pXTarget->health <= 0) // target is dead + { + PLAYER* pPlayer = NULL; + if ((!IsPlayerSprite(pTarget)) || ((pPlayer = getPlayerById(pTarget->type)) != NULL && pPlayer->fraggerId == pSpr->index)) + pDude->PlaySound(kCdudeSndTargetDead); + + if (inAttack) pDude->NextState(kCdudeStateSearch); + else pDude->NewState(kCdudeStateSearch); + return; + } + + if (IsPlayerSprite(pTarget)) + { + PLAYER* pPlayer = &gPlayer[pTarget->type - kDudePlayer1]; + if (powerupCheck(pPlayer, kPwUpShadowCloak) > 0) + { + pDude->NewState(kCdudeStateSearch); + return; + } + } + + // check target + dx = pTarget->x - pSpr->x; + dy = pTarget->y - pSpr->y; + nDist = approxDist(dx, dy); + + nDAng = klabs(((getangle(dx, dy) + kAng180 - pSpr->ang) & kAngMask) - kAng180); + nHeigh = (pInfo->eyeHeight * pSpr->yrepeat) << 2; + + if (thinkTime && !inAttack) + aiChooseDirection(pSpr, pXSpr, getangle(dx, dy)); + + // is the target visible? + if (nDist > pInfo->seeDist || !cansee(pTarget->x, pTarget->y, pTarget->z, pTarget->sectnum, pSpr->x, pSpr->y, pSpr->z - nHeigh, pSpr->sectnum)) + { + if (inAttack) pDude->NextState(kCdudeStateSearch); + else pDude->NewState(kCdudeStateSearch); + return; + } + else if (nDAng > pInfo->periphery) + { + if (inAttack) pDude->NextState(kCdudeStateChase); + else pDude->NewState(kCdudeStateChase); + return; + } + + ARG_PICK_WEAPON* pPickArg = new ARG_PICK_WEAPON(pSpr, pXSpr, pTarget, pXTarget, nDist, nDAng); + CUSTOMDUDE_WEAPON* pWeapon = pDude->pWeapon; + if (pWeapon) + { + nSlope = pDude->AdjustSlope(pSpr->xspr.target, pWeapon->shot.offset.z); + turn2target = pWeapon->turnToTarget; + interrupt = pWeapon->interruptable; + } + + if (thinkTime && Chance(0x2000)) + pDude->PlaySound(kCdudeSndTargetChase); + + // in attack + if (inAttack) + { + if (turn2target) + { + pSpr->xspr.goalAng = getTargetAng(pSpr); + moveTurn(pSpr); + } + + if (pSpr->xspr.aiState->stateTicks) // attack timer set + { + if (!pSpr->xspr.stateTimer) + { + pWeapon = pDude->PickWeapon(pPickArg); + if (pWeapon && pWeapon == pDude->pWeapon) + { + pDude->pWeapon = pWeapon; + pDude->NewState(pWeapon->stateID); + } + else + pDude->NewState(kCdudeStateChase); + } + else if (interrupt) + { + pDude->PickWeapon(pPickArg); + if (!pWeapon->available) + pDude->NewState(kCdudeStateChase); + } + + return; + } + + if (!pDude->SeqPlaying()) // final frame + { + pWeapon = pDude->PickWeapon(pPickArg); + if (!pWeapon) + { + pDude->NewState(kCdudeStateChase); + return; + } + else + { + pDude->pWeapon = pWeapon; + } + } + else // playing previous animation + { + if (!interrupt) + { + if (!pWeapon) + { + pDude->NextState(kCdudeStateChase); + } + + return; + } + else + { + pDude->PickWeapon(pPickArg); + if (!pWeapon->available) + { + pDude->NewState(kCdudeStateChase); + return; + } + } + } + } + else + { + // enter attack + pWeapon = pDude->PickWeapon(pPickArg); + if (pWeapon) + pDude->pWeapon = pWeapon; + } + + if (pWeapon) + { + switch (pWeapon->type) + { + case kCdudeWeaponNone: + if (pDude->CanMove()) pDude->NextState(kCdudeStateFlee); + else pDude->NextState(kCdudeStateSearch); + return; + case kCdudeWeaponHitscan: + case kCdudeWeaponMissile: + case kCdudeWeaponThrow: + if (pDude->CanMove()) + { + HitScan(pSpr, pSpr->z, dx, dy, nSlope, pWeapon->clipMask, nDist); + if (pHit->hitsprite != pSpr->xspr.target && !pDude->AdjustSlope(nDist, &nSlope)) + { + changePos = 1; + if (spriRangeIsFine(pHit->hitsprite)) + { + spritetype* pHitSpr = &sprite[pHit->hitsprite]; + XSPRITE* pXHitSpr = NULL; + if (xsprIsFine(pHitSpr)) + pXHitSpr = &xsprite[pHitSpr->extra]; + + if (IsDudeSprite(pHitSpr)) + { + if (pXHitSpr) + { + if (pXHitSpr->target == pSpr->index) + return; + + if (pXHitSpr->dodgeDir > 0) + pSpr->xspr.dodgeDir = -pXHitSpr->dodgeDir; + } + } + else if (pHitSpr->owner == pSpr->index) // projectiles, things, fx etc... + { + if (!pXHitSpr || !pXHitSpr->health) + changePos = 0; + } + + if (changePos) + { + // prefer dodge + if (pDude->dodge.onAimMiss.Allow()) + { + pDude->NewState(kCdudeStateDodge, 30 * (Random(2) + 1)); + return; + } + } + } + + if (changePos) + { + // prefer chase + pDude->NewState(kCdudeStateChase); + return; + } + } + } + fallthrough__; + default: + pDude->NewState(pWeapon->stateID); + pDude->NextState(pWeapon->nextStateID); + return; + } + } + + if (!pDude->CanMove()) + pDude->NextState(kCdudeStateSearch); +} + +int getTargetAng(DBloodActor* pSpr) +{ + int x, y; + if (spriRangeIsFine(pSpr->xspr.target)) + { + spritetype* pTarg = &sprite[pSpr->xspr.target]; + x = pTarg->x; + y = pTarg->y; + } + else + { + x = pSpr->xspr.targetX; + y = pSpr->xspr.targetY; + } + + return getangle(x - pSpr->x, y - pSpr->y); +} + +void turnToTarget(DBloodActor* pSpr) +{ + pSpr->ang = getTargetAng(pSpr); + pSpr->xspr.goalAng = pSpr->ang; +} + +void moveTurn(DBloodActor* pSpr) +{ + CUSTOMDUDE* pDude = cdudeGet(pSpr); + int nVelTurn = pDude->GetVelocity(kParVelocityTurn); + int nAng = ((kAng180 + pSpr->xspr.goalAng - pSpr->ang) & kAngMask) - kAng180; + pSpr->ang = ((pSpr->ang + ClipRange(nAng, -nVelTurn, nVelTurn)) & kAngMask); +} + +void moveDodge(DBloodActor* pSpr) +{ + CUSTOMDUDE* pDude = cdudeGet(pSpr); + moveTurn(pSpr); + + if (pSpr->xspr.dodgeDir && pDude->CanMove()) + { + int nVelDodge = pDude->GetVelocity(kParVelocityDodge); + int nCos = Cos(pSpr->ang); int nSin = Sin(pSpr->ang); + int dX = xvel[pSpr->index]; int dY = yvel[pSpr->index]; + int t1 = dmulscale30(dX, nCos, dY, nSin); int t2 = dmulscale30(dX, nSin, -dY, nCos); + + if (pSpr->xspr.dodgeDir > 0) + { + t2 += nVelDodge; + } + else + { + t2 -= nVelDodge; + } + + xvel[pSpr->index] = dmulscale30(t1, nCos, t2, nSin); + yvel[pSpr->index] = dmulscale30(t1, nSin, -t2, nCos); + } +} + +void moveKnockout(DBloodActor* pSpr) +{ + int zv = pSpr->vel.Z; + pSpr->vel.Z = ClipRange(zv + mulscale16(zv, 0x3000), 0x1000, 0x40000); +} + +void moveForward(DBloodActor* pSpr) +{ + CUSTOMDUDE* pDude = cdudeGet(pSpr); + int nVelTurn = pDude->GetVelocity(kParVelocityTurn); + int nVelForward = pDude->GetVelocity(kParVelocityForward); + int nAng = ((kAng180 + pSpr->xspr.goalAng - pSpr->ang) & kAngMask) - kAng180; + pSpr->ang = ((pSpr->ang + ClipRange(nAng, -nVelTurn, nVelTurn)) & kAngMask); + int z = 0; + + if (pDude->CanMove()) + { + if (pDude->IsUnderwater()) + { + if (spriRangeIsFine(pSpr->xspr.target)) + { + spritetype* pTarget = &sprite[pSpr->xspr.target]; + if (spriteIsUnderwater(pTarget, true)) + z = (pTarget->z - pSpr->z) + (10 << Random(12)); + } + else + { + z = (pSpr->xspr.targetZ - pSpr->z); + } + + if (Chance(0x0500)) + z <<= 1; + + pSpr->vel.Z += z; + } + + // don't move forward if trying to turn around + if (klabs(nAng) <= kAng60) + { + xvel[pSpr->index] += mulscale30(Cos(pSpr->ang), nVelForward); + yvel[pSpr->index] += mulscale30(Sin(pSpr->ang), nVelForward); + } + } +} + +void enterSleep(DBloodActor* pSpr) +{ + CUSTOMDUDE* pDude = cdudeGet(pSpr); + pDude->StatusSet(kCdudeStatusSleep); + resetTarget(pSpr); + moveStop(pSpr); + + // reduce distances while sleeping + pDude->seeDist = kCdudeMinSeeDist; + pDude->hearDist = kCdudeMinHearDist; + pDude->periphery = kAng360; +} + +void enterWake(DBloodActor* pSpr) +{ + CUSTOMDUDE* pDude = cdudeGet(pSpr); + if (pDude->StatusTest(kCdudeStatusSleep)) + { + pDude->StatusRem(kCdudeStatusSleep); + + // restore distances when awaked + pDude->seeDist = pDude->pInfo->seeDist; + pDude->hearDist = pDude->pInfo->hearDist; + pDude->periphery = pDude->pInfo->periphery; + } + + pDude->PlaySound(kCdudeSndWake); +} + + +void enterDying(DBloodActor* pSpr) +{ + CUSTOMDUDE* pDude = cdudeGet(pSpr); + if (pDude->mass > 48) + pDude->mass = ClipLow(pDude->mass >> 2, 48); +} + +void thinkDying(DBloodActor* pSpr) +{ + SPRITEHIT* pHit = &pSpr->hit; + if (pHit->florhit.type == kHitNone && spriteIsUnderwater(pSpr, true)) + pSpr->vel.Z = max(pSpr->vel.Z, 1024.); +} + +void enterDeath(DBloodActor* pSpr) +{ + // don't let the data fields gets overwritten! + if (!(pSpr->spr.flags & kHitagRespawn)) + DudeToGibCallback1(pSpr); + + pSpr->ChangeType(kThingBloodChunks); + actPostSprite(pSpr, kStatThing); +} + +void enterMorph(DBloodActor* pSpr) +{ + CUSTOMDUDE* pDude = cdudeGet(pSpr); + if (!pDude->IsMorphing()) + { + pDude->PlaySound(kCdudeSndTransforming); + pDude->StatusSet(kCdudeStatusMorph); // set morph status + pSpr->xspr.locked = 1; // lock it while morphing + + pSpr->spr.flags &= ~kPhysMove; + moveStop(pSpr); + if (pSpr->xspr.aiState->seqId <= 0) + seqKill(pSpr); + } +} + +void thinkMorph(DBloodActor* pSpr) +{ + int nTarget; char triggerOn, triggerOff; + CUSTOMDUDE* pDude = cdudeGet(pSpr); + + if (pDude->SeqPlaying()) + { + moveStop(pSpr); + return; + } + + pDude->ClearEffectCallbacks(); + pDude->StatusRem(kCdudeStatusMorph); // clear morph status + pSpr->xspr.burnSource = -1; + pSpr->xspr.burnTime = 0; + pSpr->xspr.locked = 0; + pSpr->xspr.scale = 0; + + if (pDude->NextDude) + { + // classic morphing to already inserted sprite by TX ID + DBloodActor* pNext = pDude->NextDude; + + pSpr->xspr.key = pSpr->xspr.dropMsg = 0; + + // save incarnation's going on and off options + triggerOn = pNext->xspr.triggerOn, triggerOff = pNext->xspr.triggerOff; + + // then remove it from incarnation so it won't send the commands + pNext->xspr.triggerOn = pNext->xspr.triggerOff = 0; + + // trigger dude death before morphing + trTriggerSprite(pSpr, kCmdOff, pSpr); + + pSpr->ChangeType(pSpr->spr.inittype = pNext->GetType()); + pSpr->spr.flags = pNext->spr.flags; + pSpr->spr.pal = pNext->spr.pal; + pSpr->spr.shade = pNext->spr.shade; + pSpr->clipdist = pNext->clipdist; + pSpr->spr.scale = pNext->spr.scale; + + pSpr->xspr.txID = pNext->xspr.txID; + pSpr->xspr.command = pNext->xspr.command; + pSpr->xspr.triggerOn = triggerOn; + pSpr->xspr.triggerOff = triggerOff; + pSpr->xspr.busyTime = pNext->xspr.busyTime; + pSpr->xspr.waitTime = pNext->xspr.waitTime; + + // inherit respawn properties + pSpr->xspr.respawn = pNext->xspr.respawn; + pSpr->xspr.respawnPending = pNext->xspr.respawnPending; + + pSpr->xspr.data1 = pNext->xspr.data1; // for v1 this is weapon id, v2 - descriptor id + pSpr->xspr.data2 = pNext->xspr.data2; // for v1 this is seqBase id + pSpr->xspr.data3 = pSpr->xspr.sysData1 = pNext->xspr.sysData1; // for v1 this is soundBase id + pSpr->xspr.data4 = pSpr->xspr.sysData2 = pNext->xspr.sysData2; // start hp + + // inherit dude flags + pSpr->xspr.dudeGuard = pNext->xspr.dudeGuard; + pSpr->xspr.dudeDeaf = pNext->xspr.dudeDeaf; + pSpr->xspr.dudeAmbush = pNext->xspr.dudeAmbush; + pSpr->xspr.dudeFlag4 = pNext->xspr.dudeFlag4; + pSpr->xspr.modernFlags = pNext->xspr.modernFlags; + + pSpr->xspr.dropMsg = pNext->xspr.dropMsg; + pSpr->xspr.key = pNext->xspr.key; + + pSpr->xspr.Decoupled = pNext->xspr.Decoupled; + pSpr->xspr.locked = pNext->xspr.locked; + + // set health + pSpr->xspr.health = nnExtDudeStartHealth(pSpr, pSpr->xspr.data4); + + // restore values for incarnation + pNext->xspr.triggerOn = triggerOn; + pNext->xspr.triggerOff = triggerOff; + } + else + { + int nNextDudeType = pDude->NextDudeType; + + // v2 morphing + if (nNextDudeType > 0) + { + // morph to another custom dude + pSpr->xspr.data1 = nNextDudeType - 1; + } + else if (nNextDudeType < 0) + { + // morph to some vanilla dude + pSpr->ChangeType(-nNextDudeType - 1); + pSpr->clipdist = getDudeInfo(pSpr)->clipdist * CLIPDIST_SCALE; + pSpr->xspr.data1 = 0; + } + + pSpr->spr.inittype = pSpr->GetType(); + pSpr->xspr.health = nnExtDudeStartHealth(pSpr, 0); + pSpr->xspr.data4 = pSpr->xspr.sysData2 = 0; + pSpr->xspr.data2 = 0; + pSpr->xspr.data3 = 0; + } + + // clear init status + pDude->initialized = 0; + + DBloodActor* pTarget = pSpr->xspr.target; // save target + aiInitSprite(pSpr); // re-init sprite with all new settings + + switch (pSpr->GetType()) + { + case kDudePodMother: // fake dude + case kDudeTentacleMother: // fake dude + break; + default: + if (pSpr->xspr.dudeFlag4) break; + else if (nTarget) aiSetTarget(pSpr, pTarget); // try to restore target + else aiSetTarget(pSpr, pSpr->spr.pos); + aiActivateDude(pSpr); // finally activate it + break; + } +} + +// get closest visible underwater sector it can fall in +void enterBurnSearchWater(DBloodActor* pSpr) +{ + double nClosest = FLT_MAX; + int nDist, s, e; + + auto p1 = pSpr->spr.pos.XY(); + double z1, z2; + double x2, y2; + + // this originally edited the state function, which is unsafe. + pSpr->chasehackflag = false; + if (!Chance(0x8000)) + { + pSpr->chasehackflag = true; // try follow to the target + return; + } + + GetActorExtents(pSpr, &z1, &z2); + + for(int i = sector.SSize() - 1; i >= 0; i--) + { + auto sect = §or[i]; + if (sect->upperLink == nullptr) + continue; + + DBloodActor* pUp = barrier_cast(sect->upperLink); + DBloodActor* pLow = pUp->ownerActor; + if (pLow && IsUnderwaterSector(pLow->sector())) + { + for(auto& wal : sect->walls) + { + + if (!cansee(DVector3(p1, z1), pSpr->sector(), DVector3(wal.center(), z1), sect)) + continue; + + double sqDist = SquareDistToWall(p1.X, p1.Y, &wal); + if (sqDist < nClosest) + { + pSpr->xspr.goalAng = (wal.center() - p1).Angle(); + nClosest = sqDist; + } + } + } + } + + if (Chance(0xB000) && pSpr->xspr.target) + { + DBloodActor* pTarget = pSpr->xspr.target; + auto dv = (p1 - pTarget->spr.pos.XY()); + if (dv.LengthSquared() < nClosest) // water sector is not closer than target + { + pSpr->xspr.goalAng = dv.Angle(); + pSpr->chasehackflag = true; + return; + } + } +} + +void cdudeDoExplosion(CUSTOMDUDE* pDude) +{ + static DVector3 nulvec; + CUSTOMDUDE_WEAPON* pWeap = pDude->pWeapon; + if (pWeap && pWeap->type == kCdudeWeaponKamikaze) + weaponShotKamikaze(pDude, pWeap, pWeap->shot.offset, nulvec); +} + +END_BLD_NS +#endif diff --git a/source/games/blood/src/aiunicult.cpp b/source/games/blood/src/aiunicult.cpp deleted file mode 100644 index 29216f3b5..000000000 --- a/source/games/blood/src/aiunicult.cpp +++ /dev/null @@ -1,2662 +0,0 @@ -//------------------------------------------------------------------------- -/* -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! - - -#include "raze_sound.h" - -#include "blood.h" - -#ifdef NOONE_EXTENSIONS -#if 0 - - -BEGIN_BLD_NS -static void ThrowThing(DBloodActor*, bool); -static void unicultThinkSearch(DBloodActor*); -static void unicultThinkGoto(DBloodActor*); -static void unicultThinkChase(DBloodActor*); -static void forcePunch(DBloodActor*); - -AISTATE genDudeIdleL = { kAiStateIdle, 0, nullptr, 0, NULL, NULL, &AF(aiThinkTarget), NULL }; -AISTATE genDudeIdleW = { kAiStateIdle, 13, nullptr, 0, NULL, NULL, &AF(aiThinkTarget), NULL }; -// --------------------- -AISTATE genDudeSearchL = { kAiStateSearch, 9, nullptr, 600, NULL, aiGenDudeMoveForward, unicultThinkSearch, &genDudeIdleL }; -AISTATE genDudeSearchW = { kAiStateSearch, 13, nullptr, 600, NULL, aiGenDudeMoveForward, unicultThinkSearch, &genDudeIdleW }; -// --------------------- -AISTATE genDudeSearchShortL = { kAiStateSearch, 9, nullptr, 200, NULL, aiGenDudeMoveForward, unicultThinkSearch, &genDudeIdleL }; -AISTATE genDudeSearchShortW = { kAiStateSearch, 13, nullptr, 200, NULL, aiGenDudeMoveForward, unicultThinkSearch, &genDudeIdleW }; -// --------------------- -AISTATE genDudeSearchNoWalkL = { kAiStateSearch, 0, nullptr, 600, NULL, &AF(aiMoveTurn), unicultThinkSearch, &genDudeIdleL }; -AISTATE genDudeSearchNoWalkW = { kAiStateSearch, 13, nullptr, 600, NULL, &AF(aiMoveTurn), unicultThinkSearch, &genDudeIdleW }; -// --------------------- -AISTATE genDudeGotoL = { kAiStateMove, 9, nullptr, 600, NULL, aiGenDudeMoveForward, unicultThinkGoto, &genDudeIdleL }; -AISTATE genDudeGotoW = { kAiStateMove, 13, nullptr, 600, NULL, aiGenDudeMoveForward, unicultThinkGoto, &genDudeIdleW }; -// --------------------- -AISTATE genDudeDodgeL = { kAiStateMove, 9, nullptr, 90, NULL, aiMoveDodge, NULL, &genDudeChaseL }; -AISTATE genDudeDodgeD = { kAiStateMove, 14, nullptr, 90, NULL, &AF(aiMoveDodge), NULL, &genDudeChaseD }; -AISTATE genDudeDodgeW = { kAiStateMove, 13, nullptr, 90, NULL, &AF(aiMoveDodge), NULL, &genDudeChaseW }; -// --------------------- -AISTATE genDudeDodgeShortL = { kAiStateMove, 9, nullptr, 60, NULL, aiMoveDodge, NULL, &genDudeChaseL }; -AISTATE genDudeDodgeShortD = { kAiStateMove, 14, nullptr, 60, NULL, &AF(aiMoveDodge), NULL, &genDudeChaseD }; -AISTATE genDudeDodgeShortW = { kAiStateMove, 13, nullptr, 60, NULL, &AF(aiMoveDodge), NULL, &genDudeChaseW }; -// --------------------- -AISTATE genDudeDodgeShorterL = { kAiStateMove, 9, nullptr, 20, NULL, aiMoveDodge, NULL, &genDudeChaseL }; -AISTATE genDudeDodgeShorterD = { kAiStateMove, 14, nullptr, 20, NULL, &AF(aiMoveDodge), NULL, &genDudeChaseD }; -AISTATE genDudeDodgeShorterW = { kAiStateMove, 13, nullptr, 20, NULL, &AF(aiMoveDodge), NULL, &genDudeChaseW }; -// --------------------- -AISTATE genDudeChaseL = { kAiStateChase, 9, nullptr, 0, NULL, aiGenDudeMoveForward, unicultThinkChase, NULL }; -AISTATE genDudeChaseD = { kAiStateChase, 14, nullptr, 0, NULL, aiGenDudeMoveForward, unicultThinkChase, NULL }; -AISTATE genDudeChaseW = { kAiStateChase, 13, nullptr, 0, NULL, aiGenDudeMoveForward, unicultThinkChase, NULL }; -// --------------------- -AISTATE genDudeChaseNoWalkL = { kAiStateChase, 0, nullptr, 0, NULL, aiMoveTurn, unicultThinkChase, NULL }; -AISTATE genDudeChaseNoWalkD = { kAiStateChase, 14, nullptr, 0, NULL, aiMoveTurn, unicultThinkChase, NULL }; -AISTATE genDudeChaseNoWalkW = { kAiStateChase, 13, nullptr, 0, NULL, aiMoveTurn, unicultThinkChase, NULL }; -// --------------------- -AISTATE genDudeFireL = { kAiStateChase, 6, &AF(genDudeAttack1), 0, NULL, &AF(aiMoveTurn), unicultThinkChase, &genDudeFireL }; -AISTATE genDudeFireD = { kAiStateChase, 8, &AF(genDudeAttack1), 0, NULL, &AF(aiMoveTurn), unicultThinkChase, &genDudeFireD }; -AISTATE genDudeFireW = { kAiStateChase, 8, &AF(genDudeAttack1), 0, NULL, &AF(aiMoveTurn), unicultThinkChase, &genDudeFireW }; -// ---------------------z -AISTATE genDudeRecoilL = { kAiStateRecoil, 5, nullptr, 0, NULL, NULL, NULL, &genDudeChaseL }; -AISTATE genDudeRecoilD = { kAiStateRecoil, 5, nullptr, 0, NULL, NULL, NULL, &genDudeChaseD }; -AISTATE genDudeRecoilW = { kAiStateRecoil, 5, nullptr, 0, NULL, NULL, NULL, &genDudeChaseW }; -AISTATE genDudeRecoilTesla = { kAiStateRecoil, 4, nullptr, 0, NULL, NULL, NULL, &genDudeDodgeShortL }; -// --------------------- -AISTATE genDudeThrow = { kAiStateChase, 7, &AF(ThrowCallback1), 0, NULL, NULL, NULL, &genDudeChaseL }; -AISTATE genDudeThrow2 = { kAiStateChase, 7, &AF(ThrowCallback2), 0, NULL, NULL, NULL, &genDudeChaseL }; -// --------------------- -AISTATE genDudePunch = { kAiStateChase,10, &AF(punchCallback), 0, NULL, NULL, forcePunch, &genDudeChaseL }; -// --------------------- - -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 -}; - - -//--------------------------------------------------------------------------- -// -// -// -//--------------------------------------------------------------------------- - -static void forcePunch(DBloodActor* actor) -{ - if (actor->genDudeExtra.forcePunch && seqGetStatus(actor) == -1) - punchCallback(actor); -} - -//--------------------------------------------------------------------------- -// -// -// -//--------------------------------------------------------------------------- - -static bool genDudeAdjustSlope(DBloodActor* actor, double dist, int weaponType, int by = 64) -{ - if (actor->GetTarget() != nullptr) - { - double fStart = 0; - double fEnd = 0; - GENDUDEEXTRA* pExtra = &actor->genDudeExtra; - unsigned int clipMask = (weaponType == kGenDudeWeaponMissile) ? CLIPMASK0 : CLIPMASK1; - - for (int i = -8191; i < 8192; i += by) - { - double ii = i / 16384.; - HitScan(actor, actor->spr.pos.Z, DVector3(actor->spr.Angles.Yaw.ToVector(), ii), clipMask, dist * 16); // this originally passed a badly scaled 'dist'. - if (!fStart && actor->GetTarget() == gHitInfo.actor()) fStart = ii; - else if (fStart && actor->GetTarget() != gHitInfo.actor()) - { - fEnd = ii; - break; - } - } - - if (fStart != fEnd) - { - if (weaponType == kGenDudeWeaponHitscan) - { - actor->dudeSlope = fStart - ((fStart - fEnd) * 0.25); - } - else if (weaponType == kGenDudeWeaponMissile) - { - auto type = GetSpawnType(pExtra->curWeapon); - auto clipdist = GetDefaultByType(type)->FloatVar("defclipdist"); - actor->dudeSlope = (fStart - ((fStart - fEnd) * 0.25)) - clipdist / 2048; - } - return true; - } - } - return false; - -} - -//--------------------------------------------------------------------------- -// -// -// -//--------------------------------------------------------------------------- - -void genDudeUpdate(DBloodActor* actor) -{ - GENDUDEEXTRA* pExtra = &actor->genDudeExtra; - for (int i = 0; i < kGenDudePropertyMax; i++) { - if (pExtra->updReq[i]) genDudePrepare(actor, i); - } -} - -//--------------------------------------------------------------------------- -// -// -// -//--------------------------------------------------------------------------- - -void punchCallback(DBloodActor* actor) -{ - auto const target = actor->GetTarget(); - if (target != nullptr) - { - double nZOffset1 = getDudeInfo(actor)->eyeHeight * actor->spr.scale.Y; - double nZOffset2 = 0; - - - if (target->IsDudeActor()) - nZOffset2 = getDudeInfo(target)->eyeHeight * target->spr.scale.Y; - - if (!playGenDudeSound(actor, kGenDudeSndAttackMelee)) - sfxPlay3DSound(actor, 530, 1, 0); - - actFireVector(actor, 0, 0, DVector3(actor->spr.Angles.Yaw.ToVector() * 64, nZOffset1 - nZOffset2), kVectorGenDudePunch); - } -} - -//--------------------------------------------------------------------------- -// -// -// -//--------------------------------------------------------------------------- - -void genDudeAttack1(DBloodActor* actor) -{ - if (actor->GetTarget() == nullptr) return; - - DVector3 dv; - actor->vel.XY().Zero(); - - GENDUDEEXTRA* pExtra = &actor->genDudeExtra; - int dispersion = pExtra->baseDispersion; - if (inDuck(actor->xspr.aiState)) - dispersion = ClipLow(dispersion >> 1, kGenDudeMinDispesion); - - if (pExtra->weaponType == kGenDudeWeaponHitscan) - { - dv = DVector3(actor->spr.Angles.Yaw.ToVector(), actor->dudeSlope); - // dispersal modifiers here in case if non-melee enemy - if (!dudeIsMelee(actor)) - { - dv.X += Random3F(dispersion, 14); - dv.Y += Random3F(dispersion, 14); - dv.Z += Random3F(dispersion, 14); - } - - actFireVector(actor, 0, 0, dv, (VECTOR_TYPE)pExtra->curWeapon); - if (!playGenDudeSound(actor, kGenDudeSndAttackNormal)) - sfxPlayVectorSound(actor, pExtra->curWeapon); - } - else if (pExtra->weaponType == kGenDudeWeaponSummon) - { - DBloodActor* spawned = nullptr; - double dist = actor->clipdist * 4; - if (pExtra->slaveCount <= gGameOptions.nDifficulty) - { - if ((spawned = actSpawnDude(actor, pExtra->curWeapon, dist + RandomD(dist, 4))) != NULL) - { - spawned->SetOwner(actor); - - if (spawned->hasX()) - { - spawned->SetTarget(actor->GetTarget()); - if (spawned->GetTarget() != nullptr) - aiActivateDude(spawned); - } - - if (AllowedKillType(spawned)) Level.addKillCount(); - pExtra->slave[pExtra->slaveCount++] = spawned; - if (!playGenDudeSound(actor, kGenDudeSndAttackNormal)) - sfxPlay3DSoundVolume(actor, 379, 1, 0, 0x10000 - Random3(0x3000)); - } - } - } - else if (pExtra->weaponType == kGenDudeWeaponMissile) - { - dv = DVector3(actor->spr.Angles.Yaw.ToVector(), actor->dudeSlope); - - // dispersal modifiers here - dv.X += Random3F(dispersion, 14); - dv.Y += Random3F(dispersion, 14); - dv.Z += Random3F(dispersion >> 1, 14); - - actFireMissile(actor, 0, 0, dv, pExtra->curWeapon); - if (!playGenDudeSound(actor, kGenDudeSndAttackNormal)) - sfxPlayMissileSound(actor, pExtra->curWeapon); - } -} - -//--------------------------------------------------------------------------- -// -// -// -//--------------------------------------------------------------------------- - -void ThrowCallback1(DBloodActor* actor) -{ - ThrowThing(actor, true); -} - -void ThrowCallback2(DBloodActor* actor) -{ - ThrowThing(actor, false); -} - -static void ThrowThing(DBloodActor* actor, bool impact) -{ - auto target = actor->GetTarget(); - - if (target == nullptr) - return; - - if (!(target->GetType() >= kDudeBase && target->GetType() < kDudeMax)) - return; - - int curWeapon = actor->genDudeExtra.curWeapon; - int weaponType = actor->genDudeExtra.weaponType; - if (weaponType != kGenDudeWeaponThrow) return; - - if (!gThingInfoExtra[curWeapon - kThingBase].allowThrow) return; - else if (!playGenDudeSound(actor, kGenDudeSndAttackThrow)) - sfxPlay3DSound(actor, 455, -1, 0); - - double zThrow = 14500 / 65536.; - auto dv = target->spr.pos - actor->spr.pos; - double dist = dv.Length(); - - auto actLeech = leechIsDropped(actor); - - switch (curWeapon) { - case kModernThingEnemyLifeLeech: - case kThingDroppedLifeLeech: - zThrow = 5000 / 65536; - // pickup life leech before throw it again - if (actLeech != NULL) removeLeech(actLeech); - break; - } - - DBloodActor* spawned = nullptr; - if ((spawned = actFireThing(actor, 0., 0., (dv.Z / 32768.) - zThrow, curWeapon, dist * (2048. / 64800))) == nullptr) return; - - spawned->SetOwner(actor); - - switch (curWeapon) { - case kThingNapalmBall: - spawned->spr.scale = DVector2(0.375, 0.375); - spawned->xspr.data4 = 3 + gGameOptions.nDifficulty; - impact = true; - break; - case kModernThingThrowableRock: - { - double s = 0.375 + Random(42) * REPEAT_SCALE; - spawned->spr.setspritetexture(aTexIds[kTexROCKDEBRIS1 + Random(5)]); - spawned->spr.scale = DVector2(s, s); - 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.scale.X > 0.9375 ) spawned->xspr.data1 = 43; - else if (spawned->spr.scale.X > 0.625) spawned->xspr.data1 = 33; - else if (spawned->spr.scale.X > 0.46875) spawned->xspr.data1 = 23; - else spawned->xspr.data1 = 12; - return; - } - case kThingTNTBarrel: - case kThingArmedProxBomb: - case kThingArmedSpray: - impact = false; - break; - case kModernThingTNTProx: - spawned->xspr.state = 0; - spawned->xspr.Proximity = true; - return; - case kModernThingEnemyLifeLeech: - if (actLeech != nullptr) spawned->xspr.health = actLeech->xspr.health; - else spawned->xspr.health = ((spawned->IntVar("health") << 4) * gGameOptions.nDifficulty) >> 1; - - sfxPlay3DSound(actor, 490, -1, 0); - - spawned->xspr.data3 = 512 / (gGameOptions.nDifficulty + 1); - spawned->spr.cstat &= ~CSTAT_SPRITE_BLOCK; - spawned->spr.pal = 6; - spawned->clipdist = 0; - spawned->SetTarget(actor->GetTarget()); - spawned->xspr.Proximity = true; - spawned->xspr.stateTimer = 1; - - actor->genDudeExtra.pLifeLeech = spawned; - evPostActor(spawned, 80, AF(LeechStateTimer)); - return; - } - - if (impact == true && dist <= 7680) spawned->xspr.Impact = true; - else { - spawned->xspr.Impact = false; - evPostActor(spawned, 120 * Random(2) + 120, kCmdOn, actor); - } -} - -//--------------------------------------------------------------------------- -// -// -// -//--------------------------------------------------------------------------- - -static void unicultThinkSearch(DBloodActor* actor) -{ - // TO DO: if can't see the target, but in fireDist range - stop moving and look around - - aiChooseDirection(actor, actor->xspr.goalAng); - aiLookForTarget(actor); -} - -//--------------------------------------------------------------------------- -// -// -// -//--------------------------------------------------------------------------- - -static void unicultThinkGoto(DBloodActor* actor) -{ - if (!(actor->IsDudeActor())) - { - Printf(PRINT_HIGH, "actor->IsDudeActor()"); - return; - } - - auto dvec = actor->xspr.TargetPos.XY() - actor->spr.pos.XY(); - DAngle nAngle = dvec.Angle(); - double nDist = dvec.Length(); - aiChooseDirection(actor, nAngle); - - // if reached target, change to search mode - if (nDist < 320 && absangle(actor->spr.Angles.Yaw, nAngle) < getDudeInfo(actor)->Periphery()) - { - if (spriteIsUnderwater(actor, false)) aiGenDudeNewState(actor, &genDudeSearchW); - else aiGenDudeNewState(actor, &genDudeSearchL); - } - aiThinkTarget(actor); -} - -//--------------------------------------------------------------------------- -// -// -// -//--------------------------------------------------------------------------- - -static void unicultThinkChase(DBloodActor* actor) -{ - if (!actor->IsDudeActor()) return; - - auto const target = actor->GetTarget(); - if (target == nullptr) - { - if (spriteIsUnderwater(actor, false)) aiGenDudeNewState(actor, &genDudeGotoW); - else aiGenDudeNewState(actor, &genDudeGotoL); - return; - } - else - { - genDudeUpdate(actor); - } - - if (!target || !target->IsDudeActor() || !target->hasX()) // target lost - { - if (spriteIsUnderwater(actor, false)) aiGenDudeNewState(actor, &genDudeSearchShortW); - else aiGenDudeNewState(actor, &genDudeSearchShortL); - actor->SetTarget(nullptr); - return; - } - - if (target->xspr.health <= 0) // target is dead - { - DBloodPlayer* pPlayer = NULL; - if ((!target->IsPlayerActor()) || ((pPlayer = getPlayerById(target->GetType())) != NULL && pPlayer->fragger == actor)) - { - playGenDudeSound(actor, kGenDudeSndTargetDead); - if (spriteIsUnderwater(actor, false)) aiGenDudeNewState(actor, &genDudeSearchShortW); - else aiGenDudeNewState(actor, &genDudeSearchShortL); - } - else if (spriteIsUnderwater(actor, false)) aiGenDudeNewState(actor, &genDudeGotoW); - else aiGenDudeNewState(actor, &genDudeGotoL); - actor->SetTarget(nullptr); - return; - } - - // check target - auto dv = target->spr.pos.XY() - actor->spr.pos.XY(); - double dist = max(dv.Length(), 1 / 256.); - DAngle nAngle = dv.Angle(); - - // 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. - auto velocity = actor->vel; - if (inAttack(actor->xspr.aiState)) - velocity.X = velocity.Y = max(actor->clipdist, 0.5) / 32768; - - aiGenDudeChooseDirection(actor, nAngle, velocity.XY()); - - GENDUDEEXTRA* pExtra = &actor->genDudeExtra; - if (!pExtra->canAttack) - { - if (pExtra->canWalk) aiSetTarget(actor, actor); // targeting self??? - if (spriteIsUnderwater(actor, false)) aiGenDudeNewState(actor, &genDudeGotoW); - else aiGenDudeNewState(actor, &genDudeGotoL); - return; - } - else if (target->IsPlayerActor()) - { - auto pPlayer = getPlayer(target); - if (powerupCheck(pPlayer, kPwUpShadowCloak) > 0) - { - if (spriteIsUnderwater(actor, false)) aiGenDudeNewState(actor, &genDudeSearchShortW); - else aiGenDudeNewState(actor, &genDudeSearchShortL); - actor->SetTarget(nullptr); - return; - } - } - - DUDEINFO* pDudeInfo = getDudeInfo(actor); - DAngle losAngle = absangle(actor->spr.Angles.Yaw, nAngle); - double height = (pDudeInfo->eyeHeight * actor->spr.scale.Y); - - if (dist > pDudeInfo->SeeDist() || !cansee(target->spr.pos, target->sector(), - actor->spr.pos.plusZ(-height), actor->sector())) - { - if (spriteIsUnderwater(actor, false)) aiGenDudeNewState(actor, &genDudeSearchW); - else aiGenDudeNewState(actor, &genDudeSearchL); - actor->SetTarget(nullptr); - return; - } - - // is the target visible? - if (dist < pDudeInfo->SeeDist() && losAngle <= pDudeInfo->Periphery()) - { - if ((PlayClock & 64) == 0 && Chance(0x3000) && !spriteIsUnderwater(actor, false)) - playGenDudeSound(actor, kGenDudeSndChasing); - - actor->dudeSlope = dist == 0 ? 0 : target->spr.pos.Z - actor->spr.pos.Z / dist; - - int curWeapon = actor->genDudeExtra.curWeapon; - int weaponType = actor->genDudeExtra.weaponType; - - auto actLeech = leechIsDropped(actor); - - const VECTORDATA* meleeVector = &gVectorData[22]; - if (weaponType == kGenDudeWeaponThrow) - { - if (losAngle < DAngle15) - { - if (!gThingInfoExtra[curWeapon - kThingBase].allowThrow) - { - if (spriteIsUnderwater(actor)) aiGenDudeNewState(actor, &genDudeChaseW); - else aiGenDudeNewState(actor, &genDudeChaseL); - return; - - } - else if (dist < 766.5 && dist > 480 && !spriteIsUnderwater(actor, false) && curWeapon != kModernThingEnemyLifeLeech) - { - int pHit = HitScan(actor, actor->spr.pos.Z, DVector3(dv, 0), 16777280, 0); - switch (pHit) { - case 0: - case 4: - return; - default: - aiGenDudeNewState(actor, &genDudeThrow); - return; - } - - } - else if (dist > 254.5 && dist <= 692 && !spriteIsUnderwater(actor, false) && !actor->GetSpecialOwner()) - { - switch (curWeapon) - { - case kModernThingEnemyLifeLeech: - { - if (actLeech == nullptr) - { - aiGenDudeNewState(actor, &genDudeThrow2); - genDudeThrow2.nextState = &genDudeDodgeShortL; - return; - } - - int ldist = aiFightGetTargetDist(target, pDudeInfo, actLeech); - if (ldist > 3 || !cansee(target->spr.pos, target->sector(), - actLeech->spr.pos, actLeech->sector()) || actLeech->GetTarget() == nullptr) - { - aiGenDudeNewState(actor, &genDudeThrow2); - genDudeThrow2.nextState = &genDudeDodgeShortL; - } - else - { - genDudeThrow2.nextState = &genDudeChaseL; - if (dist > 317 && Chance(0x5000)) - { - if (!canDuck(actor) || Chance(0x4000)) aiGenDudeNewState(actor, &genDudeDodgeShortL); - else aiGenDudeNewState(actor, &genDudeDodgeShortD); - } - else - { - aiGenDudeNewState(actor, &genDudeChaseL); - } - - } - } - return; - case kModernThingThrowableRock: - if (Chance(0x4000)) aiGenDudeNewState(actor, &genDudeThrow2); - else playGenDudeSound(actor, kGenDudeSndTargetSpot); - return; - default: - aiGenDudeNewState(actor, &genDudeThrow2); - return; - } - - } - else if (dist <= meleeVector->fMaxDist()) - { - - if (spriteIsUnderwater(actor, false)) - { - if (Chance(0x9000)) aiGenDudeNewState(actor, &genDudePunch); - else aiGenDudeNewState(actor, &genDudeDodgeW); - } - else if (Chance(0x9000)) aiGenDudeNewState(actor, &genDudePunch); - else aiGenDudeNewState(actor, &genDudeDodgeL); - return; - - } - else - { - int state = checkAttackState(actor); - if (state == 1) aiGenDudeNewState(actor, &genDudeChaseW); - else if (state == 2) - { - if (Chance(0x5000)) aiGenDudeNewState(actor, &genDudeChaseD); - else aiGenDudeNewState(actor, &genDudeChaseL); - } - else aiGenDudeNewState(actor, &genDudeChaseL); - return; - } - } - } - else - { - double vdist; - double mdist; - double defDist; - - vdist = mdist = defDist = actor->genDudeExtra.fireDist; - - if (weaponType == kGenDudeWeaponHitscan) - { - if ((vdist = gVectorData[curWeapon].fMaxDist()) <= 0) - vdist = mdist; - - } - else if (weaponType == kGenDudeWeaponSummon) - { - // don't attack slaves - if (actor->GetTarget() != nullptr && actor->GetTarget()->GetOwner() == actor) - { - aiSetTarget(actor, actor->spr.pos); - return; - } - else if (actor->genDudeExtra.slaveCount > gGameOptions.nDifficulty || dist < meleeVector->fMaxDist()) - { - if (dist <= meleeVector->fMaxDist()) - { - aiGenDudeNewState(actor, &genDudePunch); - return; - } - else - { - int state = checkAttackState(actor); - if (state == 1) aiGenDudeNewState(actor, &genDudeChaseW); - else if (state == 2) aiGenDudeNewState(actor, &genDudeChaseD); - else aiGenDudeNewState(actor, &genDudeChaseL); - return; - } - } - - } - else if (weaponType == kGenDudeWeaponMissile) - { - // special handling for flame, explosive and life leech missiles - int state = checkAttackState(actor); - switch (curWeapon) - { - case kMissileLifeLeechRegular: - // pickup life leech if it was thrown previously - if (actLeech != NULL) removeLeech(actLeech); - mdist = 1500/16.; - break; - - case kMissileFlareAlt: - mdist = 2500/16.; - [[fallthrough]]; - case kMissileFireball: - case kMissileFireballNapalm: - case kMissileFireballCerberus: - case kMissileFireballTchernobog: - if (mdist == defDist) mdist = 3000/16.; - if (dist > mdist || actor->xspr.locked == 1) break; - else if (dist <= meleeVector->fMaxDist() && Chance(0x9000)) - aiGenDudeNewState(actor, &genDudePunch); - else if (state == 1) aiGenDudeNewState(actor, &genDudeChaseW); - else if (state == 2) aiGenDudeNewState(actor, &genDudeChaseD); - else aiGenDudeNewState(actor, &genDudeChaseL); - return; - - case kMissileFlameSpray: - case kMissileFlameHound: - //viewSetSystemMessage("%d", target->xspr.burnTime); - if (spriteIsUnderwater(actor, false)) - { - if (dist > meleeVector->fMaxDist()) aiGenDudeNewState(actor, &genDudeChaseW); - else if (Chance(0x8000)) aiGenDudeNewState(actor, &genDudePunch); - else aiGenDudeNewState(actor, &genDudeDodgeShortW); - return; - } - else if (dist <= 250 && target->xspr.burnTime >= 2000 && target->GetBurnSource() == actor) - { - if (dist > meleeVector->fMaxDist()) aiGenDudeNewState(actor, &genDudeChaseL); - else aiGenDudeNewState(actor, &genDudePunch); - return; - } - vdist = 3500/16. + (gGameOptions.nDifficulty * 25); - break; - } - } - else if (weaponType == kGenDudeWeaponKamikaze) - { - int nType = curWeapon - kTrapExploder; - const EXPLOSION* pExpl = &explodeInfo[nType]; - if (CheckProximity(actor, target->spr.pos, target->sector(), pExpl->radius >> 1)) - { - actor->vel.Zero(); - if (doExplosion(actor, nType) && actor->xspr.health > 0) - actDamageSprite(actor, actor, kDamageExplode, 65535); - } - return; - } - - int state = checkAttackState(actor); - DAngle kAngle = (dudeIsMelee(actor) || dist <= 256/* kGenDudeMaxMeleeDist */) ? pDudeInfo->Periphery() : DAngle1 * 10; - - if (dist < vdist && losAngle < kAngle) - { - if (pExtra->canWalk) - { - double objDist = -1; - double targetDist = -1; - int hit = -1; - if (weaponType == kGenDudeWeaponHitscan) - hit = HitScan(actor, actor->spr.pos.Z, DVector3(actor->spr.Angles.Yaw.ToVector(), actor->dudeSlope), CLIPMASK1, dist); - else if (weaponType == kGenDudeWeaponMissile) - hit = HitScan(actor, actor->spr.pos.Z, DVector3(actor->spr.Angles.Yaw.ToVector(), actor->dudeSlope), CLIPMASK0, dist); - - if (hit >= 0) - { - targetDist = dist - (target->clipdist); - objDist = (gHitInfo.hitpos.XY() - actor->spr.pos.XY()).Length(); - } - - if (actor != gHitInfo.actor() && targetDist > objDist) - { - DBloodActor* hitactor = nullptr; - walltype* pHWall = NULL; - XWALL* pXHWall = NULL; - bool hscn = false; - bool blck = false; - bool failed = false; - - switch (hit) - { - case 3: - hitactor = gHitInfo.actor(); - if (hitactor) - { - hscn = (hitactor->spr.cstat & CSTAT_SPRITE_BLOCK_HITSCAN); blck = (hitactor->spr.cstat & CSTAT_SPRITE_BLOCK); - } - break; - case 0: - case 4: - pHWall = gHitInfo.hitWall; - if (pHWall->hasX()) pXHWall = &pHWall->xw(); - hscn = (pHWall->cstat & CSTAT_WALL_BLOCK_HITSCAN); blck = (pHWall->cstat & CSTAT_WALL_BLOCK); - break; - } - - switch (hit) { - case 0: - case 1: - case 2: - if (weaponType != kGenDudeWeaponMissile && genDudeAdjustSlope(actor, dist, weaponType) - && dist < (375 + RandomF(2000, 4)) && pExtra->baseDispersion < kGenDudeMaxDispersion >> 1) break; - - else if (spriteIsUnderwater(actor)) aiGenDudeNewState(actor, &genDudeChaseW); - else aiGenDudeNewState(actor, &genDudeChaseL); - return; - case 3: - if (hitactor->spr.statnum == kStatFX || hitactor->spr.statnum == kStatProjectile || hitactor->spr.statnum == kStatDebris) - break; - if (hitactor->IsDudeActor() && (weaponType != kGenDudeWeaponHitscan || hscn)) - { - // dodge a bit in sides - if (hitactor->xspr.health > 0 && hitactor->GetTarget() != actor) - { - if (pExtra->baseDispersion < 1024 && weaponType != kGenDudeWeaponMissile) - { - if (spriteIsUnderwater(actor)) aiGenDudeNewState(actor, &genDudeDodgeShorterW); - else if (inDuck(actor->xspr.aiState)) aiGenDudeNewState(actor, &genDudeDodgeShorterD); - else aiGenDudeNewState(actor, &genDudeDodgeShorterL); - } - else if (spriteIsUnderwater(actor)) aiGenDudeNewState(actor, &genDudeDodgeShortW); - else if (inDuck(actor->xspr.aiState)) aiGenDudeNewState(actor, &genDudeDodgeShortD); - else aiGenDudeNewState(actor, &genDudeDodgeShortL); - - switch (hitactor->GetType()) - { - case kDudeModernCustom: // and make dude which could be hit to dodge too - if (!dudeIsMelee(hitactor) && Chance(int(dist * 256))) - { - if (!inAttack(hitactor->xspr.aiState)) - { - if (spriteIsUnderwater(hitactor)) aiGenDudeNewState(hitactor, &genDudeDodgeShorterW); - else if (inDuck(actor->xspr.aiState)) aiGenDudeNewState(hitactor, &genDudeDodgeShorterD); - else aiGenDudeNewState(hitactor, &genDudeDodgeShorterL); - - // preferable in opposite sides - if (Chance(0x8000)) - { - if (actor->xspr.dodgeDir == 1) hitactor->xspr.dodgeDir = -1; - else if (actor->xspr.dodgeDir == -1) hitactor->xspr.dodgeDir = 1; - } - break; - } - if (actor->spr.pos.X < hitactor->spr.pos.X) - { - if (Chance(0x9000) && target->spr.pos.X > hitactor->spr.pos.X) actor->xspr.dodgeDir = -1; - else actor->xspr.dodgeDir = 1; - } - else - { - if (Chance(0x9000) && target->spr.pos.X > hitactor->spr.pos.X) actor->xspr.dodgeDir = 1; - else actor->xspr.dodgeDir = -1; - } - } - break; - default: - if (actor->spr.pos.X < hitactor->spr.pos.X) - { - if (Chance(0x9000) && target->spr.pos.X > hitactor->spr.pos.X) actor->xspr.dodgeDir = -1; - else actor->xspr.dodgeDir = 1; - } - else - { - if (Chance(0x9000) && target->spr.pos.X > hitactor->spr.pos.X) actor->xspr.dodgeDir = 1; - else actor->xspr.dodgeDir = -1; - } - break; - } - return; - } - break; - } - else if (weaponType == kGenDudeWeaponHitscan && hscn) - { - if (genDudeAdjustSlope(actor, dist, weaponType)) break; - VectorScan(actor, 0, 0, DVector3(actor->spr.Angles.Yaw.ToVector(), actor->dudeSlope), dist, 1); - if (actor == gHitInfo.actor()) break; - - bool immune = nnExtIsImmune(hitactor, gVectorData[curWeapon].dmgType); - if (!(hitactor->hasX() && (!immune || (immune && hitactor->spr.statnum == kStatThing && hitactor->xspr.Vector)) && !hitactor->xspr.locked)) - { - auto hdist = (gHitInfo.hitpos.XY() - actor->spr.pos.XY()).Length(); - if ((hdist <= 93.75 && !blck) - || (dist <= (pExtra->fireDist / max(Random(4), 1u)))) - { - //viewSetSystemMessage("GO CHASE"); - if (spriteIsUnderwater(actor)) aiGenDudeNewState(actor, &genDudeChaseW); - else aiGenDudeNewState(actor, &genDudeChaseL); - return; - - } - auto tex1 = TexMan.GetGameTexture(hitactor->spr.spritetexture()); - auto tex2 = TexMan.GetGameTexture(actor->spr.spritetexture()); - - double wd1 = tex1->GetDisplayWidth() * hitactor->spr.scale.X; - double wd2 = tex2->GetDisplayWidth() * actor->spr.scale.X; - if (wd1 < (wd2 * 8)) - { - //viewSetSystemMessage("OBJ SIZE: %d DUDE SIZE: %d", wd1, wd2); - if (spriteIsUnderwater(actor)) aiGenDudeNewState(actor, &genDudeDodgeShorterW); - else if (inDuck(actor->xspr.aiState)) aiGenDudeNewState(actor, &genDudeDodgeShorterD); - else aiGenDudeNewState(actor, &genDudeDodgeShorterL); - - if (actor->spr.pos.X < hitactor->spr.pos.X) - { - if (Chance(0x3000) && target->spr.pos.X > hitactor->spr.pos.X) actor->xspr.dodgeDir = -1; - else actor->xspr.dodgeDir = 1; - } - else - { - if (Chance(0x3000) && target->spr.pos.X > hitactor->spr.pos.X) actor->xspr.dodgeDir = 1; - else actor->xspr.dodgeDir = -1; - } - - if (actor->hit.hit.type == kHitWall || actor->hit.hit.type == kHitSprite) - { - if (spriteIsUnderwater(actor)) aiGenDudeNewState(actor, &genDudeChaseW); - else aiGenDudeNewState(actor, &genDudeChaseL); - actor->xspr.goalAng = RandomAngle(); - //viewSetSystemMessage("WALL OR SPRITE TOUCH"); - } - - } - else - { - if (spriteIsUnderwater(actor)) aiGenDudeNewState(actor, &genDudeChaseW); - else aiGenDudeNewState(actor, &genDudeChaseL); - //viewSetSystemMessage("TOO BIG OBJECT TO DODGE!!!!!!!!"); - } - return; - } - break; - } - [[fallthrough]]; - case 4: - if (hit == 4 && weaponType == kGenDudeWeaponHitscan && hscn) - { - bool masked = (pHWall->cstat & CSTAT_WALL_MASKED); - if (masked) VectorScan(actor, 0, 0, DVector3(actor->spr.Angles.Yaw.ToVector(), actor->dudeSlope), dist, 1); - - if ((actor != gHitInfo.actor()) && (pHWall->type != kWallGib || !masked || pXHWall == NULL || !pXHWall->triggerVector || pXHWall->locked)) - { - if (spriteIsUnderwater(actor)) aiGenDudeNewState(actor, &genDudeChaseW); - else aiGenDudeNewState(actor, &genDudeChaseL); - return; - } - } - else if (hit >= 3 && weaponType == kGenDudeWeaponMissile && blck) - { - switch (curWeapon) { - case kMissileLifeLeechRegular: - case kMissileTeslaAlt: - case kMissileFlareAlt: - case kMissileFireball: - case kMissileFireballNapalm: - case kMissileFireballCerberus: - case kMissileFireballTchernobog: - { - // allow attack if dude is far from object, but target is close to it - double dudeDist = (gHitInfo.hitpos.XY() - actor->spr.pos.XY()).Length(); - double targetDist1 = (gHitInfo.hitpos.XY() - target->spr.pos.XY()).Length(); - if (dudeDist < mdist) - { - //viewSetSystemMessage("DUDE CLOSE TO OBJ: %d, MDIST: %d", dudeDist, mdist); - if (spriteIsUnderwater(actor)) aiGenDudeNewState(actor, &genDudeChaseW); - else aiGenDudeNewState(actor, &genDudeChaseL); - return; - } - else if (targetDist1 <= mdist * 0.5) - { - //viewSetSystemMessage("TARGET CLOSE TO OBJ: %d, MDIST: %d", targetDist, mdist >> 1); - break; - } - [[fallthrough]]; - } - default: - //viewSetSystemMessage("DEF HIT: %d, MDIST: %d", hit, mdist); - if (hit == 4) failed = (pHWall->type != kWallGib || pXHWall == NULL || !pXHWall->triggerVector || pXHWall->locked); - else if (hit == 3 && (failed = (hitactor->spr.statnum != kStatThing || !hitactor->hasX() || hitactor->xspr.locked)) == false) - { - // check also for damage resistance (all possible damages missile can use) - for (int i = 0; i < kDmgMax; i++) - { - if (gMissileInfoExtra[curWeapon - kMissileBase].dmgType[i] && (failed = nnExtIsImmune(hitactor, i)) == false) - break; - } - } - - if (failed) - { - if (spriteIsUnderwater(actor)) aiGenDudeNewState(actor, &genDudeSearchW); - else aiGenDudeNewState(actor, &genDudeSearchL); - return; - } - break; - } - } - break; - } - } - } - - aiSetTarget(actor, actor->GetTarget()); - switch (state) - { - case 1: - aiGenDudeNewState(actor, &genDudeFireW); - actor->xspr.aiState->nextState = &genDudeFireW; - break; - case 2: - aiGenDudeNewState(actor, &genDudeFireD); - actor->xspr.aiState->nextState = &genDudeFireD; - break; - default: - aiGenDudeNewState(actor, &genDudeFireL); - actor->xspr.aiState->nextState = &genDudeFireL; - break; - } - } - else - { - if (seqGetID(actor) == actor->xspr.data2 + ((state < 3) ? 8 : 6)) - { - if (state == 1) actor->xspr.aiState->nextState = &genDudeChaseW; - else if (state == 2) actor->xspr.aiState->nextState = &genDudeChaseD; - else actor->xspr.aiState->nextState = &genDudeChaseL; - - } - else if (state == 1 && actor->xspr.aiState != &genDudeChaseW && actor->xspr.aiState != &genDudeFireW) - { - aiGenDudeNewState(actor, &genDudeChaseW); - actor->xspr.aiState->nextState = &genDudeFireW; - - } - else if (state == 2 && actor->xspr.aiState != &genDudeChaseD && actor->xspr.aiState != &genDudeFireD) - { - aiGenDudeNewState(actor, &genDudeChaseD); - actor->xspr.aiState->nextState = &genDudeFireD; - - } - else if (actor->xspr.aiState != &genDudeChaseL && actor->xspr.aiState != &genDudeFireL) - { - aiGenDudeNewState(actor, &genDudeChaseL); - actor->xspr.aiState->nextState = &genDudeFireL; - } - } - } - } -} - -//--------------------------------------------------------------------------- -// -// -// -//--------------------------------------------------------------------------- - -int checkAttackState(DBloodActor* actor) -{ - if (dudeIsPlayingSeq(actor, 14) || spriteIsUnderwater(actor, false)) - { - if (!dudeIsPlayingSeq(actor, 14) || spriteIsUnderwater(actor, false)) - { - if (spriteIsUnderwater(actor, false)) - { - return 1; //water - } - } - else - { - return 2; //duck - } - } - else - { - return 3; //land - } - return 0; -} - -//--------------------------------------------------------------------------- -// -// -// -//--------------------------------------------------------------------------- - -static int getGenDudeMoveSpeed(DBloodActor* actor, int which, bool mul, bool shift) -{ - DUDEINFO* pDudeInfo = getDudeInfo(actor); - int speed = -1; int step = 2500; int maxSpeed = 146603; - switch (which) - { - case 0: - speed = pDudeInfo->frontSpeed; - break; - case 1: - speed = pDudeInfo->sideSpeed; - break; - case 2: - speed = pDudeInfo->backSpeed; - break; - case 3: - speed = pDudeInfo->angSpeed; - break; - default: - return -1; - } - if (actor->xspr.busyTime > 0) speed /= 3; - if (speed > 0 && mul) - { - if (actor->xspr.busyTime > 0) - speed += (step * actor->xspr.busyTime); - } - - if (shift) speed *= 4 >> 4; - if (speed > maxSpeed) speed = maxSpeed; - - return speed; -} - -//--------------------------------------------------------------------------- -// -// -// -//--------------------------------------------------------------------------- - -void aiGenDudeMoveForward(DBloodActor* actor) -{ - DUDEINFO* pDudeInfo = getDudeInfo(actor); - GENDUDEEXTRA* pExtra = &actor->genDudeExtra; - - if (pExtra->canFly) - { - auto nAng = deltaangle(actor->spr.Angles.Yaw, actor->xspr.goalAng); - auto nTurnRange = pDudeInfo->TurnRange(); - actor->spr.Angles.Yaw += clamp(nAng, -nTurnRange, nTurnRange); - double nAccel = pDudeInfo->FrontSpeed() * 4; - if (abs(nAng) > DAngle60) - return; - if (actor->GetTarget() == nullptr) - actor->spr.Angles.Yaw += DAngle45; - auto dvec = actor->xspr.TargetPos.XY() - actor->spr.pos.XY(); - double nDist = dvec.Length(); - if ((unsigned int)Random(64) < 32 && nDist <= 0x40) - return; - AdjustVelocity(actor, ADJUSTER{ - if (actor->GetTarget() == nullptr) - t1 += nAccel; - else - t1 += nAccel * 0.5; - }); - - } - else - { - DAngle maxTurn = mapangle(pDudeInfo->angSpeed * 4 >> 4); - - DAngle dang = actor->xspr.goalAng - actor->spr.Angles.Yaw; - actor->spr.Angles.Yaw += clamp(dang, -maxTurn, maxTurn); - - // don't move forward if trying to turn around - if (abs(dang) > DAngle180 / 3) - return; - - double frontSpeed = FixedToFloat(actor->genDudeExtra.moveSpeed); - actor->vel += actor->spr.Angles.Yaw.ToVector() * frontSpeed; - } -} - -//--------------------------------------------------------------------------- -// -// -// -//--------------------------------------------------------------------------- - -void aiGenDudeChooseDirection(DBloodActor* actor, DAngle direction, const DVector2& vel) -{ - if (!(actor->IsDudeActor())) - { - Printf(PRINT_HIGH, "actor->IsDudeActor()"); - return; - } - - // TO-DO: Take in account if sprite is flip-x, so enemy select correct angle - - DAngle vc = deltaangle(actor->spr.Angles.Yaw, direction); - double range = vel.dot(actor->spr.Angles.Yaw.ToVector()) * 120; - DAngle v8 = vc > nullAngle ? DAngle180 / 3 : -DAngle180 / 3; - - if (CanMove(actor, actor->GetTarget(), actor->spr.Angles.Yaw + vc, range)) - actor->xspr.goalAng = actor->spr.Angles.Yaw + vc; - else if (CanMove(actor, actor->GetTarget(), actor->spr.Angles.Yaw + vc / 2, range)) - actor->xspr.goalAng = actor->spr.Angles.Yaw + vc / 2; - else if (CanMove(actor, actor->GetTarget(), actor->spr.Angles.Yaw - vc / 2, range)) - actor->xspr.goalAng = actor->spr.Angles.Yaw - vc / 2; - else if (CanMove(actor, actor->GetTarget(), actor->spr.Angles.Yaw + v8, range)) - actor->xspr.goalAng = actor->spr.Angles.Yaw + v8; - else if (CanMove(actor, actor->GetTarget(), actor->spr.Angles.Yaw, range)) - actor->xspr.goalAng = actor->spr.Angles.Yaw; - else if (CanMove(actor, actor->GetTarget(), actor->spr.Angles.Yaw - v8, range)) - actor->xspr.goalAng = actor->spr.Angles.Yaw - v8; - else - actor->xspr.goalAng = actor->spr.Angles.Yaw + DAngle180 / 3; - - actor->xspr.goalAng = actor->xspr.goalAng.Normalized360(); - actor->xspr.dodgeDir = (Chance(0x8000)) ? 1 : -1; - - if (!CanMove(actor, actor->GetTarget(), actor->spr.Angles.Yaw + DAngle90 * actor->xspr.dodgeDir, 512)) - { - actor->xspr.dodgeDir = -actor->xspr.dodgeDir; - if (!CanMove(actor, actor->GetTarget(), actor->spr.Angles.Yaw + DAngle90 * actor->xspr.dodgeDir, 512)) - actor->xspr.dodgeDir = 0; - } -} - -//--------------------------------------------------------------------------- -// -// -// -//--------------------------------------------------------------------------- - -void aiGenDudeNewState(DBloodActor* actor, AISTATE* pAIState) -{ - if (!actor->hasX()) - { - return; - } - - if (actor->xspr.health <= 0 || actor->xspr.sysData1 == kGenDudeTransformStatus) - return; - - - // redirect dudes which cannot walk to non-walk states - if (!actor->genDudeExtra.canWalk) - { - - if (pAIState == &genDudeDodgeL || pAIState == &genDudeDodgeShortL || pAIState == &genDudeDodgeShorterL) - pAIState = &genDudeRecoilL; - - else if (pAIState == &genDudeDodgeD || pAIState == &genDudeDodgeShortD || pAIState == &genDudeDodgeShorterD) - pAIState = &genDudeRecoilD; - - else if (pAIState == &genDudeDodgeW || pAIState == &genDudeDodgeShortW || pAIState == &genDudeDodgeShorterW) - pAIState = &genDudeRecoilW; - - else if (pAIState == &genDudeSearchL || pAIState == &genDudeSearchShortL) - pAIState = &genDudeSearchNoWalkL; - - else if (pAIState == &genDudeSearchW || pAIState == &genDudeSearchShortW) - pAIState = &genDudeSearchNoWalkW; - - else if (pAIState == &genDudeGotoL) pAIState = &genDudeIdleL; - else if (pAIState == &genDudeGotoW) pAIState = &genDudeIdleW; - else if (pAIState == &genDudeChaseL) pAIState = &genDudeChaseNoWalkL; - else if (pAIState == &genDudeChaseD) pAIState = &genDudeChaseNoWalkD; - else if (pAIState == &genDudeChaseW) pAIState = &genDudeChaseNoWalkW; - else if (pAIState == &genDudeRecoilTesla) { - - if (spriteIsUnderwater(actor, false)) pAIState = &genDudeRecoilW; - else pAIState = &genDudeRecoilL; - - } - - } - - if (!actor->genDudeExtra.canRecoil) - { - if (pAIState == &genDudeRecoilL || pAIState == &genDudeRecoilD) pAIState = &genDudeIdleL; - else if (pAIState == &genDudeRecoilW) pAIState = &genDudeIdleW; - } - - actor->xspr.stateTimer = pAIState->stateTicks; actor->xspr.aiState = pAIState; - - int stateSeq = actor->xspr.data2 + pAIState->seqId; - if (pAIState->seqId >= 0 && getSequence(stateSeq)) - { - seqSpawn(stateSeq, actor, *pAIState->funcId); - } - - if (pAIState->enterFunc) - pAIState->enterFunc(actor); -} - -//--------------------------------------------------------------------------- -// -// -// -//--------------------------------------------------------------------------- - -bool playGenDudeSound(DBloodActor* actor, int mode) -{ - if (mode < kGenDudeSndTargetSpot || mode >= kGenDudeSndMax || !actor->hasX()) return false; - const GENDUDESND* sndInfo = &gCustomDudeSnd[mode]; - int sndStartId = actor->xspr.sysData1; - int rand = sndInfo->randomRange; - int sndId = (sndStartId <= 0) ? sndInfo->defaultSndId : sndStartId + sndInfo->sndIdOffset; - GENDUDEEXTRA* pExtra = &actor->genDudeExtra; - - if (sndId < 0) return false; - else if (sndStartId <= 0) sndId += Random(rand); - else - { - // Let's try to get random snd - int maxRetries = 5; - while (maxRetries-- > 0) { - int random = Random(rand); - if (!soundEngine->FindSoundByResID(sndId + random).isvalid()) continue; - sndId = sndId + random; - break; - } - - // If no success in getting random snd, get first existing one - if (maxRetries <= 0) - { - int maxSndId = sndId + rand; - while (sndId < maxSndId && !soundEngine->FindSoundByResID(sndId++).isvalid()); - } - - // let's check if there same sounds already played by other dudes - // so we won't get a lot of annoying screams in the same time and - // ensure sound played in it's full length (if not interruptable) - if (pExtra->sndPlaying && !sndInfo->interruptable) - { - if (soundEngine->GetSoundPlayingInfo(SOURCE_Any, nullptr, soundEngine->FindSoundByResID(sndId))) - { - return true; - } - - pExtra->sndPlaying = false; - } - } - - if (sndInfo->aiPlaySound) aiPlay3DSound(actor, sndId, AI_SFX_PRIORITY_2, -1); - else sfxPlay3DSound(actor, sndId, -1, 0); - - pExtra->sndPlaying = true; - return true; -} - -//--------------------------------------------------------------------------- -// -// -// -//--------------------------------------------------------------------------- - -bool spriteIsUnderwater(DBloodActor* actor, bool oldWay) -{ - return (IsUnderwaterSector(actor->sector()) - || (oldWay && (actor->xspr.medium == kMediumWater || actor->xspr.medium == kMediumGoo))); -} - -DBloodActor* leechIsDropped(DBloodActor* actor) -{ - return actor->genDudeExtra.pLifeLeech; -} - -//--------------------------------------------------------------------------- -// -// -// -//--------------------------------------------------------------------------- - -void removeDudeStuff(DBloodActor* actor) -{ - BloodStatIterator it(kStatThing); - while (auto actor2 = it.Next()) - { - if (actor2->GetOwner() != actor) continue; - switch (actor2->GetType()) { - case kThingArmedProxBomb: - case kThingArmedRemoteBomb: - case kModernThingTNTProx: - actor2->ChangeType(kSpriteDecoration); - actPostSprite(actor2, kStatFree); - break; - case kModernThingEnemyLifeLeech: - killDudeLeech(actor2); - break; - } - } - - it.Reset(kStatDude); - while (auto actor2 = it.Next()) - { - if (actor2->GetOwner() != actor) continue; - actDamageSprite(actor2->GetOwner(), actor2, kDamageFall, 65535); - } -} - -//--------------------------------------------------------------------------- -// -// -// -//--------------------------------------------------------------------------- - -void removeLeech(DBloodActor* actLeech, bool delSprite) -{ - if (actLeech != nullptr) - { - auto effectactor = gFX.fxSpawnActor((FX_ID)52, actLeech->sector(), actLeech->spr.pos, actLeech->spr.Angles.Yaw); - if (effectactor != nullptr) - { - effectactor->spr.cstat = CSTAT_SPRITE_ALIGNMENT_FACING; - effectactor->spr.pal = 6; - double repeat = 1 + Random(50) * REPEAT_SCALE; - effectactor->spr.scale = DVector2(repeat, repeat); - } - - sfxPlay3DSoundVolume(actLeech, 490, -1, 0, 60000); - - if (actLeech->GetOwner()) - actLeech->GetOwner()->genDudeExtra.pLifeLeech = nullptr; - - if (delSprite) - { - actLeech->ChangeType(kSpriteDecoration); - actPostSprite(actLeech, kStatFree); - } - - - } -} - - -void killDudeLeech(DBloodActor* actLeech) -{ - if (actLeech != NULL) - { - actDamageSprite(actLeech->GetOwner(), actLeech, kDamageExplode, 65535); - sfxPlay3DSoundVolume(actLeech, 522, -1, 0, 60000); - - if (actLeech->GetOwner() != nullptr) - actLeech->GetOwner()->genDudeExtra.pLifeLeech = nullptr; - } -} - -//--------------------------------------------------------------------------- -// -// -// -//--------------------------------------------------------------------------- - -DBloodActor* getNextIncarnation(DBloodActor* actor) -{ - for (int i = bucketHead[actor->xspr.txID]; i < bucketHead[actor->xspr.txID + 1]; i++) - { - if (!rxBucket[i].isActor()) continue; - auto rxactor = rxBucket[i].actor(); - if (actor != rxactor && rxactor->spr.statnum == kStatInactive) return rxactor; - } - return nullptr; -} - -bool dudeIsMelee(DBloodActor* actor) -{ - return actor->genDudeExtra.isMelee; -} - -//--------------------------------------------------------------------------- -// -// -// -//--------------------------------------------------------------------------- - -static void scaleDamage(DBloodActor* actor) -{ - int curWeapon = actor->genDudeExtra.curWeapon; - int weaponType = actor->genDudeExtra.weaponType; - signed short* curScale = actor->dmgControl; - for (int i = 0; i < kDmgMax; i++) - curScale[i] = getDudeInfo(kDudeModernCustom)->startDamage[i]; - - switch (weaponType) { - // just copy damage resistance of dude that should be summoned - case kGenDudeWeaponSummon: - for (int i = 0; i < kDmgMax; i++) - curScale[i] = getDudeInfo(curWeapon)->startDamage[i]; - break; - // these does not like the explosions and burning - case kGenDudeWeaponKamikaze: - curScale[kDmgBurn] = curScale[kDmgExplode] = curScale[kDmgElectric] = 1024; - break; - case kGenDudeWeaponMissile: - case kGenDudeWeaponThrow: - switch (curWeapon) - { - case kMissileButcherKnife: - curScale[kDmgBullet] = 100; - [[fallthrough]]; - case kMissileEctoSkull: - curScale[kDmgSpirit] = 32; - break; - case kMissileLifeLeechAltNormal: - case kMissileLifeLeechAltSmall: - case kMissileArcGargoyle: - curScale[kDmgSpirit] -= 32; - curScale[kDmgElectric] = 52; - break; - case kMissileFlareRegular: - case kMissileFlareAlt: - case kMissileFlameSpray: - case kMissileFlameHound: - case kThingArmedSpray: - case kThingPodFireBall: - case kThingNapalmBall: - curScale[kDmgBurn] = 32; - break; - case kMissileLifeLeechRegular: - curScale[kDmgBurn] = 60 + Random(4); - [[fallthrough]]; - case kThingDroppedLifeLeech: - case kModernThingEnemyLifeLeech: - curScale[kDmgSpirit] = 32 + Random(18); - break; - case kMissileFireball: - case kMissileFireballNapalm: - case kMissileFireballCerberus: - case kMissileFireballTchernobog: - curScale[kDmgBurn] = 50; - curScale[kDmgExplode] -= 32; - curScale[kDmgFall] = 65 + Random(15); - break; - case kThingTNTBarrel: - case kThingArmedProxBomb: - case kThingArmedRemoteBomb: - case kThingArmedTNTBundle: - case kThingArmedTNTStick: - case kModernThingTNTProx: - curScale[kDmgBurn] -= 32; - curScale[kDmgExplode] -= 32; - curScale[kDmgFall] = 65 + Random(15); - break; - case kMissileTeslaAlt: - case kMissileTeslaRegular: - curScale[kDmgElectric] = 32 + Random(8); - break; - } - break; - - } - - // add resistance if have an armor item to drop - if (actor->xspr.dropMsg >= kItemArmorAsbest && actor->xspr.dropMsg <= kItemArmorSuper) - { - switch (actor->xspr.dropMsg) - { - case kItemArmorAsbest: - curScale[kDmgBurn] = 0; - curScale[kDmgExplode] -= 30; - break; - case kItemArmorBasic: - curScale[kDmgBurn] -= 15; - curScale[kDmgExplode] -= 15; - curScale[kDmgBullet] -= 15; - curScale[kDmgSpirit] -= 15; - break; - case kItemArmorBody: - curScale[kDmgBullet] -= 30; - break; - case kItemArmorFire: - curScale[kDmgBurn] -= 30; - curScale[kDmgExplode] -= 30; - break; - case kItemArmorSpirit: - curScale[kDmgSpirit] -= 30; - break; - case kItemArmorSuper: - curScale[kDmgBurn] -= 60; - curScale[kDmgExplode] -= 60; - curScale[kDmgBullet] -= 60; - curScale[kDmgSpirit] -= 60; - break; - } - } - - // take in account scale of sprite - int yscale = int(actor->spr.scale.Y * 64); - if (yscale < 64) - { - for (int i = 0; i < kDmgMax; i++) curScale[i] += (64 - yscale); - } - else if (yscale > 64) - { - for (int i = 0; i < kDmgMax; i++) curScale[i] -= ((yscale - 64) >> 2); - } - - // take surface type into account - int surfType = GetExtInfo(actor->spr.spritetexture()).surftype; - switch (surfType) - { - case 1: // stone - curScale[kDmgFall] = 0; - curScale[kDmgBullet] -= 200; - curScale[kDmgBurn] -= 100; - curScale[kDmgExplode] -= 80; - curScale[kDmgChoke] += 30; - curScale[kDmgElectric] += 20; - break; - case 2: // metal - curScale[kDmgFall] = 16; - curScale[kDmgBullet] -= 128; - curScale[kDmgBurn] -= 90; - curScale[kDmgExplode] -= 55; - curScale[kDmgChoke] += 20; - curScale[kDmgElectric] += 30; - break; - case 3: // wood - curScale[kDmgBullet] -= 10; - curScale[kDmgBurn] += 50; - curScale[kDmgExplode] += 40; - curScale[kDmgChoke] += 10; - curScale[kDmgElectric] -= 60; - break; - case 5: // water - case 6: // dirt - case 7: // clay - case 13: // goo - curScale[kDmgFall] = 8; - curScale[kDmgBullet] -= 20; - curScale[kDmgBurn] -= 200; - curScale[kDmgExplode] -= 60; - curScale[kDmgChoke] = 0; - curScale[kDmgElectric] += 40; - break; - case 8: // snow - case 9: // ice - curScale[kDmgFall] = 8; - curScale[kDmgBullet] -= 20; - curScale[kDmgBurn] -= 100; - curScale[kDmgExplode] -= 50; - curScale[kDmgChoke] = 0; - curScale[kDmgElectric] += 40; - break; - case 10: // leaves - case 12: // plant - curScale[kDmgFall] = 0; - curScale[kDmgBullet] -= 10; - curScale[kDmgBurn] += 70; - curScale[kDmgExplode] += 50; - break; - case 11: // cloth - curScale[kDmgFall] = 8; - curScale[kDmgBullet] -= 10; - curScale[kDmgBurn] += 30; - curScale[kDmgExplode] += 20; - break; - case 14: // lava - curScale[kDmgBurn] = 0; - curScale[kDmgExplode] = 0; - curScale[kDmgChoke] += 30; - break; - } - - // finally, scale dmg for difficulty - for (int i = 0; i < kDmgMax; i++) - curScale[i] = MulScale(DudeDifficulty[gGameOptions.nDifficulty], ClipLow(curScale[i], 1), 8); - - //short* dc = curScale; - //if (actor->xspr.rxID == 788) - //viewSetSystemMessage("0: %d, 1: %d, 2: %d, 3: %d, 4: %d, 5: %d, 6: %d", dc[0], dc[1], dc[2], dc[3], dc[4], dc[5], dc[6]); -} - -//--------------------------------------------------------------------------- -// -// -// -//--------------------------------------------------------------------------- - -static int getDispersionModifier(DBloodActor* actor, int minDisp, int maxDisp) -{ - // the faster fire rate, the less frames = more dispersion - Seq* pSeq = getSequence(actor->xspr.data2 + 6); - int disp = 1; - if (pSeq != nullptr) - { - int nFrames = pSeq->nFrames; int ticks = pSeq->ticksPerFrame; int shots = 0; - for (int i = 0; i <= pSeq->nFrames; i++) { - if (pSeq->frames[i].trigger) shots++; - } - - disp = (((shots * 1000) / nFrames) / ticks) * 20; - if (gGameOptions.nDifficulty > 0) - disp /= gGameOptions.nDifficulty; - - //viewSetSystemMessage("DISP: %d FRAMES: %d SHOTS: %d TICKS %d", disp, nFrames, shots, ticks); - - } - - return ClipRange(disp, minDisp, maxDisp); -} - -//--------------------------------------------------------------------------- -// -// the distance counts from sprite size -// -//--------------------------------------------------------------------------- - -static double getRangeAttackDist(DBloodActor* actor, double minDist, double maxDist) -{ - int yscale = int(actor->spr.scale.Y * 64); - int dist = 0; - int seqId = actor->xspr.data2; - int mul = 550; - auto texid = actor->spr.spritetexture(); - - if (yscale > 0) - { - if (seqId >= 0) - { - Seq* pSeq = getSequence(seqId); - if (pSeq) - { - texid = seqGetTexture(&pSeq->frames[0]); - } - } - auto tex = TexMan.GetGameTexture(texid); - dist = int(tex->GetDisplayHeight() * 256); - if (yscale < 64) dist -= (64 - yscale) * mul; - else if (yscale > 64) dist += (yscale - 64) * (mul / 3); - } - return clamp(dist / 16., minDist, maxDist); -} - -//--------------------------------------------------------------------------- -// -// -// -//--------------------------------------------------------------------------- - -int getBaseChanceModifier(int baseChance) -{ - return ((gGameOptions.nDifficulty > 0) ? baseChance - (0x0500 * gGameOptions.nDifficulty) : baseChance); -} - -int getRecoilChance(DBloodActor* actor) -{ - int mass = getSpriteMassBySize(actor); - int baseChance = (!dudeIsMelee(actor) ? 0x8000 : 0x4000); - baseChance = getBaseChanceModifier(baseChance) + actor->xspr.data3; - - int chance = ((baseChance / mass) << 7); - return chance; -} - -int getDodgeChance(DBloodActor* actor) -{ - int mass = getSpriteMassBySize(actor); - int baseChance = (!dudeIsMelee(actor) ? 0x6000 : 0x1000); - baseChance = getBaseChanceModifier(baseChance) + actor->xspr.data3; - - int chance = ((baseChance / mass) << 7); - return chance; -} - -//--------------------------------------------------------------------------- -// -// -// -//--------------------------------------------------------------------------- - -void dudeLeechOperate(DBloodActor* actor, const EVENT& event) -{ - if (event.cmd == kCmdOff) - { - actPostSprite(actor, kStatFree); - return; - } - - auto actTarget = actor->GetTarget(); - if (actTarget != nullptr && actTarget != actor->GetOwner()) - { - if (actTarget->spr.statnum == kStatDude && !(actTarget->spr.flags & 32) && actTarget->hasX() && !actor->xspr.stateTimer) - { - if (actTarget->IsPlayerActor()) - { - auto pPlayer = getPlayer(actTarget); - if (powerupCheck(pPlayer, kPwUpShadowCloak) > 0) return; - } - double top, bottom; - GetActorExtents(actor, &top, &bottom); - int nType = actTarget->GetType() - kDudeBase; - DUDEINFO* pDudeInfo = &dudeInfo[nType]; - double z1 = (top - actor->spr.pos.Z) - 1; - auto atpos = actTarget->spr.pos; - - double nDist = (atpos.XY() - actor->spr.pos.XY()).Length(); - - if (nDist != 0 && cansee(DVector3(actor->spr.pos.XY(), top), actor->sector(), atpos, actTarget->sector())) - { - atpos.XY() += actTarget->vel.XY() * nDist * 0.0375; - - auto angBak = actor->spr.Angles.Yaw; - actor->spr.Angles.Yaw = (atpos - actor->spr.pos.XY()).Angle(); - DVector3 dv; - dv.XY() = actor->spr.Angles.Yaw.ToVector() * 64; - double tz = actTarget->spr.pos.Z - (actTarget->spr.scale.Y * pDudeInfo->aimHeight); - double dz = (tz - top - 1) / nDist * 4; - int nMissileType = kMissileLifeLeechAltNormal + (actor->xspr.data3 ? 1 : 0); - int t2; - - if (!actor->xspr.data3) t2 = 120 / 10; - else t2 = (3 * 120) / 10; - - auto missile = actFireMissile(actor, 0, z1, dv, nMissileType); - if (missile) - { - missile->SetOwner(actor); - actor->xspr.stateTimer = 1; - evPostActor(actor, t2, AF(LeechStateTimer)); - actor->xspr.data3 = ClipLow(actor->xspr.data3 - 1, 0); - } - actor->spr.Angles.Yaw = angBak; - } - } - - } -} - -//--------------------------------------------------------------------------- -// -// -// -//--------------------------------------------------------------------------- - -bool doExplosion(DBloodActor* actor, int nType) -{ - auto cls = GetSpawnType(nType); - auto actExplosion = actSpawnSprite(actor->sector(), actor->spr.pos, kStatExplosion, true, cls, nType); - - int nSeq = 4; int nSnd = 304; const EXPLOSION* pExpl = &explodeInfo[nType]; - - actExplosion->ChangeType(nType); - actExplosion->spr.cstat |= CSTAT_SPRITE_INVISIBLE; - actExplosion->SetOwner(actor); - actExplosion->spr.shade = -127; - - actExplosion->spr.scale = DVector2(pExpl->repeat * REPEAT_SCALE, pExpl->repeat * REPEAT_SCALE); - - actExplosion->xspr.data1 = pExpl->ticks; - actExplosion->xspr.data2 = pExpl->quakeEffect; - actExplosion->xspr.data3 = pExpl->flashEffect; - - if (nType == 0) { nSeq = 3; nSnd = 303; } - else if (nType == 2) { nSeq = 4; nSnd = 305; } - else if (nType == 3) { nSeq = 9; nSnd = 307; } - else if (nType == 4) { nSeq = 5; nSnd = 307; } - else if (nType <= 6) { nSeq = 4; nSnd = 303; } - else if (nType == 7) { nSeq = 4; nSnd = 303; } - - seqSpawn(nSeq, actExplosion); - sfxPlay3DSound(actExplosion, nSnd, -1, 0); - - return true; -} - -//--------------------------------------------------------------------------- -// -// 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, double nDist) -{ - auto cls = GetSpawnType(kDudeModernCustom); - auto spawned = actSpawnSprite(actor, kStatDude, cls, kDudeModernCustom); - - auto pos = actor->spr.pos; - if (nDist > 0) - { - pos.XY() += actor->spr.Angles.Yaw.ToVector() * nDist; - } - - spawned->spr.Angles.Yaw = actor->spr.Angles.Yaw; - SetActor(spawned, pos); - spawned->spr.cstat |= CSTAT_SPRITE_BLOCK_ALL | CSTAT_SPRITE_BLOOD_BIT1; - auto pDudeInfo = getDudeInfo(spawned); - spawned->clipdist = pDudeInfo->fClipdist(); - - // inherit weapon, seq and sound settings. - spawned->xspr.data1 = source->xspr.data1; - spawned->xspr.data2 = source->xspr.data2; - spawned->xspr.sysData1 = source->xspr.data3; // move sndStartId from data3 to sysData1 - spawned->xspr.data3 = 0; - - // spawn seq - seqSpawn(genDudeSeqStartId(spawned), spawned); - - // inherit movement speed. - spawned->xspr.busyTime = source->xspr.busyTime; - - // inherit clipdist? - if (source->clipdist > 0) - spawned->copy_clipdist(source); - - // inherit custom hp settings - if (source->xspr.data4 <= 0) spawned->xspr.health = pDudeInfo->startHealth << 4; - else spawned->xspr.health = ClipRange(source->xspr.data4 << 4, 1, 65535); - - - if (source->spr.flags & kModernTypeFlag1) - { - switch (source->GetType()) { - case kModernCustomDudeSpawn: - //inherit pal? - if (spawned->spr.pal <= 0) spawned->spr.pal = source->spr.pal; - - // inherit spawn sprite trigger settings, so designer can count monsters. - spawned->xspr.txID = source->xspr.txID; - spawned->xspr.command = source->xspr.command; - spawned->xspr.triggerOn = source->xspr.triggerOn; - spawned->xspr.triggerOff = source->xspr.triggerOff; - - // inherit drop items - spawned->xspr.dropMsg = source->xspr.dropMsg; - - // inherit required key so it can be dropped - spawned->xspr.key = source->xspr.key; - - // inherit dude flags - spawned->xspr.dudeDeaf = source->xspr.dudeDeaf; - spawned->xspr.dudeGuard = source->xspr.dudeGuard; - spawned->xspr.dudeAmbush = source->xspr.dudeAmbush; - spawned->xspr.dudeFlag4 = source->xspr.dudeFlag4; - spawned->xspr.modernFlags = source->xspr.modernFlags; - break; - } - } - - // inherit sprite size (useful for seqs with zero repeats) - if (source->spr.flags & kModernTypeFlag2) - { - spawned->spr.scale = source->spr.scale; - } - - if (AllowedKillType(spawned)) Level.addKillCount(); - aiInitSprite(spawned); - return spawned; -} - -//--------------------------------------------------------------------------- -// -// -// -//--------------------------------------------------------------------------- - -void genDudeTransform(DBloodActor* actor) -{ - if (!actor->hasX()) return; - - auto actIncarnation = getNextIncarnation(actor); - if (actIncarnation == NULL) - { - if (actor->xspr.sysData1 == kGenDudeTransformStatus) actor->xspr.sysData1 = 0; - trTriggerSprite(actor, kCmdOff, actor); - return; - } - - actor->xspr.key = actor->xspr.dropMsg = actor->xspr.locked = 0; - - // save incarnation's going on and off options - bool triggerOn = actIncarnation->xspr.triggerOn; - bool triggerOff = actIncarnation->xspr.triggerOff; - - // then remove it from incarnation so it will not send the commands - actIncarnation->xspr.triggerOn = false; - actIncarnation->xspr.triggerOff = false; - - // trigger dude death before transform - trTriggerSprite(actor, kCmdOff, actor); - - actor->spr.inittype = actIncarnation->GetType(); - actor->ChangeType(actor->spr.inittype); - actor->spr.flags = actIncarnation->spr.flags; - actor->spr.pal = actIncarnation->spr.pal; - actor->spr.shade = actIncarnation->spr.shade; - actor->copy_clipdist(actIncarnation); - actor->spr.scale = actIncarnation->spr.scale; - - actor->xspr.txID = actIncarnation->xspr.txID; - actor->xspr.command = actIncarnation->xspr.command; - actor->xspr.triggerOn = triggerOn; - actor->xspr.triggerOff = triggerOff; - actor->xspr.busyTime = actIncarnation->xspr.busyTime; - actor->xspr.waitTime = actIncarnation->xspr.waitTime; - - // inherit respawn properties - actor->xspr.respawn = actIncarnation->xspr.respawn; - actor->xspr.respawnPending = actIncarnation->xspr.respawnPending; - - actor->xspr.burnTime = 0; - actor->SetBurnSource(nullptr); - - actor->xspr.data1 = actIncarnation->xspr.data1; - actor->xspr.data2 = actIncarnation->xspr.data2; - - actor->xspr.sysData1 = actIncarnation->xspr.data3; // soundBase id - actor->xspr.sysData2 = actIncarnation->xspr.data4; // start hp - - actor->xspr.dudeGuard = actIncarnation->xspr.dudeGuard; - actor->xspr.dudeDeaf = actIncarnation->xspr.dudeDeaf; - actor->xspr.dudeAmbush = actIncarnation->xspr.dudeAmbush; - actor->xspr.dudeFlag4 = actIncarnation->xspr.dudeFlag4; - actor->xspr.modernFlags = actIncarnation->xspr.modernFlags; - - actor->xspr.dropMsg = actIncarnation->xspr.dropMsg; - actor->xspr.key = actIncarnation->xspr.key; - - actor->xspr.locked = actIncarnation->xspr.locked; - actor->xspr.Decoupled = actIncarnation->xspr.Decoupled; - - // clear drop items of the incarnation - actIncarnation->xspr.key = actIncarnation->xspr.dropMsg = 0; - - // set hp - if (actor->xspr.sysData2 <= 0) actor->xspr.health = dudeInfo[actor->GetType() - kDudeBase].startHealth << 4; - else actor->xspr.health = ClipRange(actor->xspr.sysData2 << 4, 1, 65535); - - int seqId = dudeInfo[actor->GetType() - kDudeBase].seqStartID; - switch (actor->GetType()) { - case kDudePodMother: // fake dude - case kDudeTentacleMother: // fake dude - break; - case kDudeModernCustom: - case kDudeModernCustomBurning: - seqId = genDudeSeqStartId(actor); - genDudePrepare(actor, kGenDudePropertyMass); - [[fallthrough]]; // go below - default: - seqSpawn(seqId, actor); - - // save target - auto target = actor->GetTarget(); - - // re-init sprite - aiInitSprite(actor); - - // try to restore target - if (target == nullptr) aiSetTarget(actor, actor->spr.pos); - else aiSetTarget(actor, target); - - // finally activate it - aiActivateDude(actor); - - break; - } - actIncarnation->xspr.triggerOn = triggerOn; - actIncarnation->xspr.triggerOff = triggerOff; - - /*// remove the incarnation in case if non-locked - if (actIncarnation->xspr.locked == 0) { - actIncarnation->xspr.txID = actIncarnation->ChangeType(0); - actPostSprite(pIncarnation, kStatFree); - // or restore triggerOn and off options - } else { - actIncarnation->xspr.triggerOn = triggerOn; - actIncarnation->xspr.triggerOff = triggerOff; - }*/ -} - -//--------------------------------------------------------------------------- -// -// -// -//--------------------------------------------------------------------------- - -void updateTargetOfLeech(DBloodActor* actor) -{ - if (!actor->hasX()) return; - - auto actLeech = leechIsDropped(actor); - if (actLeech == NULL || !actLeech->hasX()) actor->genDudeExtra.pLifeLeech = nullptr; - else - { - if (actor->GetTarget() != actLeech->GetTarget()) - { - if (actor->GetTarget() == nullptr && actLeech->GetTarget() != nullptr) - { - aiSetTarget(actor, actLeech->GetTarget()); - if (inIdle(actor->xspr.aiState)) - aiActivateDude(actor); - } - else - { - actLeech->SetTarget(actor->GetTarget()); - } - } - } -} - -//--------------------------------------------------------------------------- -// -// -// -//--------------------------------------------------------------------------- - -void updateTargetOfSlaves(DBloodActor* actor) -{ - if (!actor->hasX()) return; - - GENDUDEEXTRA* pExtra = &actor->genDudeExtra; - auto slave = pExtra->slave; - auto actTarget = actor->GetTarget(); - if (!actTarget || !actTarget->IsDudeActor() || !actTarget->hasX() || actTarget->xspr.health <= 0) actTarget = nullptr; - - for (int i = 0; i <= gGameOptions.nDifficulty; i++) - { - if (slave[i] != nullptr) - { - if (!slave[i]->IsDudeActor() || !slave[i]->hasX() || slave[i]->xspr.health <= 0) - { - slave[i]->SetOwner(nullptr); - slave[i] = nullptr; - continue; - } - - if (actTarget != nullptr) - { - if (actTarget != slave[i]->GetTarget()) aiSetTarget(slave[i], actTarget); - // check if slave have proper target - if (slave[i]->GetTarget() == nullptr || slave[i]->GetTarget()->GetOwner() == actor) - aiSetTarget(slave[i], actor->spr.pos); - } - else - { - aiSetTarget(slave[i], actor->spr.pos); // 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; -} - -//--------------------------------------------------------------------------- -// -// -// -//--------------------------------------------------------------------------- - -int inDodge(AISTATE* aiState) -{ - if (aiState == &genDudeDodgeL) return 1; - else if (aiState == &genDudeDodgeD) return 2; - else if (aiState == &genDudeDodgeW) return 3; - else if (aiState == &genDudeDodgeShortL) return 4; - else if (aiState == &genDudeDodgeShortD) return 5; - else if (aiState == &genDudeDodgeShortW) return 6; - else if (aiState == &genDudeDodgeShorterL) return 7; - else if (aiState == &genDudeDodgeShorterD) return 8; - else if (aiState == &genDudeDodgeShorterW) return 9; - return 0; - -} - -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; -} - - -//--------------------------------------------------------------------------- -// -// -// -//--------------------------------------------------------------------------- - -bool canSwim(DBloodActor* actor) -{ - return actor->genDudeExtra.canSwim; -} - -bool canDuck(DBloodActor* actor) -{ - return actor->genDudeExtra.canDuck; -} - -bool canWalk(DBloodActor* actor) -{ - return actor->genDudeExtra.canWalk; -} - -//--------------------------------------------------------------------------- -// -// -// -//--------------------------------------------------------------------------- - -int genDudeSeqStartId(DBloodActor* actor) -{ - if (genDudePrepare(actor, kGenDudePropertyStates)) return actor->xspr.data2; - else return kGenDudeDefaultSeq; -} - -bool genDudePrepare(DBloodActor* actor, int propId) -{ - if (!actor || !actor->hasX()) return false; - - if (actor->GetType() != kDudeModernCustom) { - Printf(PRINT_HIGH, "actor->GetType() != kDudeModernCustom"); - return false; - } - else if (propId < kGenDudePropertyAll || propId >= kGenDudePropertyMax) { - viewSetSystemMessage("Unknown custom dude #%d property (%d)", actor->GetIndex(), propId); - return false; - } - - GENDUDEEXTRA* pExtra = &actor->genDudeExtra; - pExtra->updReq[propId] = false; - - switch (propId) { - case kGenDudePropertyAll: - case kGenDudePropertyInitVals: - pExtra->moveSpeed = getGenDudeMoveSpeed(actor, 0, true, false); - pExtra->clipdist = actor->spr.clipdist; - if (propId) break; - [[fallthrough]]; - - case kGenDudePropertyWeapon: { - pExtra->curWeapon = actor->xspr.data1; - switch (actor->xspr.data1) { - case VECTOR_TYPE_19: pExtra->curWeapon = kVectorBullet; break; - case kMissileUnused: pExtra->curWeapon = kMissileArcGargoyle; break; - case kThingDroppedLifeLeech: pExtra->curWeapon = kModernThingEnemyLifeLeech; break; - } - - pExtra->canAttack = false; - if (pExtra->curWeapon > 0 && getSequence(actor->xspr.data2 + kGenDudeSeqAttackNormalL)) - pExtra->canAttack = true; - - pExtra->weaponType = kGenDudeWeaponNone; - if (pExtra->curWeapon > 0 && pExtra->curWeapon < kVectorMax) pExtra->weaponType = kGenDudeWeaponHitscan; - else if (pExtra->curWeapon >= kDudeBase && pExtra->curWeapon < kDudeMax) pExtra->weaponType = kGenDudeWeaponSummon; - else if (pExtra->curWeapon >= kMissileBase && pExtra->curWeapon < kMissileMax) pExtra->weaponType = kGenDudeWeaponMissile; - else if (pExtra->curWeapon >= kThingBase && pExtra->curWeapon < kThingMax) pExtra->weaponType = kGenDudeWeaponThrow; - else if (pExtra->curWeapon >= kTrapExploder && pExtra->curWeapon < (kTrapExploder + kExplodeMax) - 1) - pExtra->weaponType = kGenDudeWeaponKamikaze; - - pExtra->isMelee = false; - if (pExtra->weaponType == kGenDudeWeaponKamikaze) pExtra->isMelee = true; - else if (pExtra->weaponType == kGenDudeWeaponHitscan) { - if (gVectorData[pExtra->curWeapon].fMaxDist() > 0 && gVectorData[pExtra->curWeapon].fMaxDist() <= kGenDudeMaxMeleeDistf) - pExtra->isMelee = true; - } - - if (propId) break; - [[fallthrough]]; - - } - case kGenDudePropertyDmgScale: - scaleDamage(actor); - if (propId) break; - [[fallthrough]]; - - case kGenDudePropertyMass: { - // to ensure mass gets updated, let's clear all cache - SPRITEMASS* pMass = &actor->spriteMass; - pMass->seqId = 0; - pMass->texid = FNullTextureID(); - pMass->scale.Zero(); - pMass->clipDist = 0; - pMass->mass = pMass->airVel = pMass->fraction = 0; - getSpriteMassBySize(actor); - if (propId) break; - [[fallthrough]]; - } - case kGenDudePropertyAttack: - pExtra->fireDist = getRangeAttackDist(actor, 187.5, 2812.5); - pExtra->throwDist = pExtra->fireDist; // temp - pExtra->baseDispersion = getDispersionModifier(actor, 200, 3500); - if (propId) break; - [[fallthrough]]; - - case kGenDudePropertyStates: { - - pExtra->canFly = false; - - // check the animation - int seqStartId = -1; - if (actor->xspr.data2 <= 0) seqStartId = actor->xspr.data2 = getDudeInfo(actor)->seqStartID; - else seqStartId = actor->xspr.data2; - - for (int i = seqStartId; i < seqStartId + kGenDudeSeqMax; i++) { - switch (i - seqStartId) { - case kGenDudeSeqIdleL: - case kGenDudeSeqDeathDefault: - case kGenDudeSeqAttackNormalL: - case kGenDudeSeqAttackThrow: - case kGenDudeSeqAttackPunch: - { - Seq* pSeq = getSequence(i); - if (!pSeq) - { - actor->xspr.data2 = getDudeInfo(actor)->seqStartID; - viewSetSystemMessage("No SEQ animation id %d found for custom dude #%d!", i, actor->GetIndex()); - viewSetSystemMessage("SEQ base id: %d", seqStartId); - } - else if ((i - seqStartId) == kGenDudeSeqAttackPunch) - { - pExtra->forcePunch = true; // required for those who don't have fire trigger in punch seq and for default animation - for (int ii = 0; ii < pSeq->nFrames; ii++) { - if (!pSeq->frames[ii].trigger) continue; - pExtra->forcePunch = false; - break; - } - } - break; - } - case kGenDudeSeqDeathExplode: - pExtra->availDeaths[kDmgExplode] = !!getSequence(i); - break; - case kGenDudeSeqBurning: - pExtra->canBurn = !!getSequence(i); - break; - case kGenDudeSeqElectocuted: - pExtra->canElectrocute = !!getSequence(i); - break; - case kGenDudeSeqRecoil: - pExtra->canRecoil = !!getSequence(i); - break; - case kGenDudeSeqMoveL: { - bool oldStatus = pExtra->canWalk; - pExtra->canWalk = !!getSequence(i); - if (oldStatus != pExtra->canWalk) { - if (actor->GetTarget() == nullptr) - { - if (spriteIsUnderwater(actor, false)) aiGenDudeNewState(actor, &genDudeIdleW); - else aiGenDudeNewState(actor, &genDudeIdleL); - } - else if (pExtra->canWalk) - { - if (spriteIsUnderwater(actor, false)) aiGenDudeNewState(actor, &genDudeChaseW); - else if (inDuck(actor->xspr.aiState)) aiGenDudeNewState(actor, &genDudeChaseD); - else aiGenDudeNewState(actor, &genDudeChaseL); - } - else - { - if (spriteIsUnderwater(actor, false)) aiGenDudeNewState(actor, &genDudeChaseNoWalkW); - else if (inDuck(actor->xspr.aiState)) aiGenDudeNewState(actor, &genDudeChaseNoWalkD); - else aiGenDudeNewState(actor, &genDudeChaseNoWalkL); - } - } - break; - } - case kGenDudeSeqAttackNormalDW: - pExtra->canDuck = (getSequence(i) && getSequence(seqStartId + 14)); - pExtra->canSwim = (getSequence(i) && getSequence(seqStartId + 13) - && getSequence(seqStartId + 17)); - break; - case kGenDudeSeqDeathBurn1: { - bool seq15 = getSequence(i); - bool seq16 = getSequence(seqStartId + 16); - if (seq15 && seq16) pExtra->availDeaths[kDmgBurn] = 3; - else if (seq16) pExtra->availDeaths[kDmgBurn] = 2; - else if (seq15) pExtra->availDeaths[kDmgBurn] = 1; - else pExtra->availDeaths[kDmgBurn] = 0; - break; - } - case kGenDudeSeqMoveW: - case kGenDudeSeqMoveD: - case kGenDudeSeqDeathBurn2: - case kGenDudeSeqIdleW: - break; - case kGenDudeSeqReserved3: - case kGenDudeSeqReserved4: - case kGenDudeSeqReserved5: - case kGenDudeSeqReserved6: - case kGenDudeSeqReserved7: - case kGenDudeSeqReserved8: - /*if (getSequence(i)) { - viewSetSystemMessage("Found reserved SEQ animation (%d) for custom dude #%d!", i, actor->GetIndex()); - viewSetSystemMessage("Using reserved animation is not recommended."); - viewSetSystemMessage("SEQ base id: %d", seqStartId); - }*/ - break; - } - } - if (propId) break; - [[fallthrough]]; - } - case kGenDudePropertyLeech: - pExtra->pLifeLeech = nullptr; - if (!actor->GetSpecialOwner()) - { - BloodStatIterator it(kStatThing); - while (auto actor2 = it.Next()) - { - if (actor2->GetOwner() == actor && actor2->GetType() == kModernThingEnemyLifeLeech) { - pExtra->pLifeLeech = actor2; - break; - } - } - } - if (propId) break; - [[fallthrough]]; - - case kGenDudePropertySlaves: - { - pExtra->slaveCount = 0; - for (auto i = 0; i < kGenDudeMaxSlaves; i++) - { - pExtra->slave[i] = nullptr; - } - - BloodStatIterator it(kStatDude); - while (auto actor2 = it.Next()) - { - if (actor2->GetOwner() != actor) continue; - else if (!actor2->IsDudeActor() || !actor2->hasX() || actor2->xspr.health <= 0) { - actor2->SetOwner(nullptr); - continue; - } - - pExtra->slave[pExtra->slaveCount++] = actor2; - if (pExtra->slaveCount > gGameOptions.nDifficulty) - break; - } - if (propId) break; - [[fallthrough]]; - } - case kGenDudePropertySpriteSize: { - if (seqGetStatus(actor) == -1) - seqSpawn(actor->xspr.data2 + actor->xspr.aiState->seqId, actor); - - // make sure dudes aren't in the floor or ceiling - double zTop, zBot; - GetActorExtents(actor, &zTop, &zBot); - if (!(actor->sector()->ceilingstat & CSTAT_SECTOR_SKY)) - actor->spr.pos.Z += max(actor->sector()->ceilingz - zTop, 0.); - if (!(actor->sector()->floorstat & CSTAT_SECTOR_SKY)) - actor->spr.pos.Z += min(actor->sector()->floorz - zBot, 0.); - - actor->clipdist = clamp((actor->spr.scale.X + actor->spr.scale.Y) * 8, 1., 30.); - if (propId) break; - } - } - - return true; -} - -//--------------------------------------------------------------------------- -// -// -// -//--------------------------------------------------------------------------- - -void genDudePostDeath(DBloodActor* actor, DAMAGE_TYPE damageType, int damage) -{ - if (damageType == kDamageExplode) - { - DUDEINFO* pDudeInfo = getDudeInfo(actor); - for (int i = 0; i < 3; i++) - if (pDudeInfo->nGibType[i] > -1) - GibSprite(actor, (GIBTYPE)pDudeInfo->nGibType[i], NULL, NULL); - - for (int i = 0; i < 4; i++) - fxSpawnBlood(actor, damage); - } - - AddKill(actor, actor); - - actor->ChangeType(kThingBloodChunks); - actPostSprite(actor, kStatThing); -} - -//--------------------------------------------------------------------------- -// -// -// -//--------------------------------------------------------------------------- - -void aiGenDudeInitSprite(DBloodActor* actor) -{ - switch (actor->GetType()) - { - case kDudeModernCustom: - { - - actor->dudeExtra.active = actor->dudeExtra.thinkTime = 0; - aiGenDudeNewState(actor, &genDudeIdleL); - break; - } - case kDudeModernCustomBurning: - aiGenDudeNewState(actor, &genDudeBurnGoto); - actor->xspr.burnTime = 1200; - break; - } - - actor->spr.flags = 15; - return; -} - -//--------------------------------------------------------------------------- -// -// -// -//--------------------------------------------------------------------------- - -bool actKillModernDude(DBloodActor* actor, DAMAGE_TYPE damageType) -{ - GENDUDEEXTRA* pExtra = &actor->genDudeExtra; - removeDudeStuff(actor); - if (actor->xspr.txID <= 0 || getNextIncarnation(actor) == nullptr) - { - if (pExtra->weaponType == kGenDudeWeaponKamikaze && Chance(0x4000) && damageType != kDamageSpirit && damageType != kDamageDrown) - { - doExplosion(actor, actor->xspr.data1 - kTrapExploder); - if (Chance(0x9000)) damageType = kDamageExplode; - } - - if (damageType == kDamageBurn) - { - if (pExtra->availDeaths[kDamageBurn] && !spriteIsUnderwater(actor)) - { - if (pExtra->canBurn) - { - actor->ChangeType(kDudeModernCustomBurning); - if (actor->xspr.data2 == kGenDudeDefaultSeq) // don't inherit palette for burning if using default animation - actor->spr.pal = 0; - - aiGenDudeNewState(actor, &genDudeBurnGoto); - actHealDude(actor, dudeInfo[55].startHealth, dudeInfo[55].startHealth); - if (actor->xspr.burnTime <= 0) actor->xspr.burnTime = 1200; - actor->dudeExtra.time = PlayClock + 360; - return true; - } - - } - else - { - actor->xspr.burnTime = 0; - actor->SetBurnSource(nullptr); - damageType = kDamageFall; - } - } - } - else - { - actor->xspr.locked = 1; // lock while transforming - - aiSetGenIdleState(actor); // set idle state - - if (actor->xspr.key > 0) // drop keys - actDropObject(actor, kItemKeyBase + actor->xspr.key - 1); - - if (actor->xspr.dropMsg > 0) // drop items - actDropObject(actor, actor->xspr.dropMsg); - - actor->spr.flags &= ~kPhysMove; - actor->vel.XY().Zero(); - - playGenDudeSound(actor, kGenDudeSndTransforming); - int seqId = actor->xspr.data2 + kGenDudeSeqTransform; - if (getSequence(seqId)) seqSpawn(seqId, actor); - else - { - seqKill(actor); - DBloodActor* pEffect = gFX.fxSpawnActor((FX_ID)52, actor->sector(), actor->spr.pos, actor->spr.Angles.Yaw); - if (pEffect != nullptr) - { - pEffect->spr.cstat = CSTAT_SPRITE_ALIGNMENT_FACING; - pEffect->spr.pal = 6; - pEffect->spr.scale = actor->spr.scale; - } - - GIBTYPE nGibType; - for (int i = 0; i < 3; i++) - { - if (Chance(0x3000)) nGibType = GIBTYPE_6; - else if (Chance(0x2000)) nGibType = GIBTYPE_5; - else nGibType = GIBTYPE_17; - - double top, bottom; - GetActorExtents(actor, &top, &bottom); - DVector3 gibPos(actor->spr.pos.XY(), top); - DVector3 gibVel(actor->vel.XY() * 0.5, -FixedToFloat(0xccccc)); - GibSprite(actor, nGibType, &gibPos, &gibVel); - } - } - - actor->xspr.sysData1 = kGenDudeTransformStatus; // in transform - return true; - } - return false; -} - -//--------------------------------------------------------------------------- -// -// -// -//--------------------------------------------------------------------------- - -void modernCustomDudeDeath(DBloodActor* actor, int nSeq, int damageType) -{ - playGenDudeSound(actor, kGenDudeSndDeathNormal); - auto dudeToGib = (actCheckRespawn(actor)) ? nullptr : ((nSeq == 3) ? AF(DudeToGibCallback2) : AF(DudeToGibCallback1)); - if (nSeq == 3) - { - GENDUDEEXTRA* pExtra = &actor->genDudeExtra; - if (pExtra->availDeaths[kDmgBurn] == 3) seqSpawn((15 + Random(2)) + actor->xspr.data2, actor, dudeToGib); - else if (pExtra->availDeaths[kDmgBurn] == 2) seqSpawn(16 + actor->xspr.data2, actor, dudeToGib); - else if (pExtra->availDeaths[kDmgBurn] == 1) seqSpawn(15 + actor->xspr.data2, actor, dudeToGib); - else if (getSequence(actor->xspr.data2 + nSeq))seqSpawn(nSeq + actor->xspr.data2, actor, dudeToGib); - else seqSpawn(1 + actor->xspr.data2, actor, dudeToGib); - - } - else - { - seqSpawn(nSeq + actor->xspr.data2, actor, dudeToGib); - } -} - -//--------------------------------------------------------------------------- -// -// -// -//--------------------------------------------------------------------------- - -void modernCustomDudeBurningDeath(DBloodActor* actor, int nSeq) -{ - playGenDudeSound(actor, kGenDudeSndDeathExplode); - auto dudeToGib = (actCheckRespawn(actor)) ? nullptr : AF(DudeToGibCallback1); - - if (Chance(0x4000)) spawnGibs(actor, GIBTYPE_27, -0xccccc); - - GENDUDEEXTRA* pExtra = &actor->genDudeExtra; - int seqofs = actor->xspr.data2; - if (pExtra->availDeaths[kDmgBurn] == 3) seqSpawn((15 + Random(2)) + seqofs, actor, dudeToGib); - else if (pExtra->availDeaths[kDmgBurn] == 2) seqSpawn(16 + seqofs, actor, dudeToGib); - else if (pExtra->availDeaths[kDmgBurn] == 1) seqSpawn(15 + seqofs, actor, dudeToGib); - else seqSpawn(1 + seqofs, actor, dudeToGib); -} - - -END_BLD_NS -#endif -#endif diff --git a/source/games/blood/src/aiunicult.h b/source/games/blood/src/aiunicult.h index 168e51bdd..ab3b19fa2 100644 --- a/source/games/blood/src/aiunicult.h +++ b/source/games/blood/src/aiunicult.h @@ -158,40 +158,6 @@ struct GENDUDESND extern const GENDUDESND gCustomDudeSnd[]; -// temporary, until normal DUDEEXTRA gets refactored -struct GENDUDEEXTRA -{ - double clipdist; - uint16_t availDeaths[kDamageMax]; // list of seqs with deaths for each damage type - uint32_t moveSpeed; - double fireDist; // counts from sprite size - double throwDist; // counts from sprite size - uint16_t curWeapon; // data1 duplicate to avoid potential problems when changing data dynamically - uint16_t weaponType; - uint16_t baseDispersion; - uint16_t slaveCount; // how many dudes is summoned - TObjPtr pLifeLeech; // spritenum of dropped dude's leech - TObjPtr slave[kGenDudeMaxSlaves]; // index of the ones dude is summon - bool updReq[kGenDudePropertyMax]; // update requests - union - { - struct - { - bool sndPlaying : 1; // indicate if sound of AISTATE currently playing - bool forcePunch : 1; // indicate if there is no fire trigger in punch state seq - bool isMelee : 1; - bool canBurn : 1; // can turn in Burning dude or not - bool canElectrocute : 1; - bool canAttack : 1; - bool canRecoil : 1; - bool canWalk : 1; - bool canDuck : 1; - bool canSwim : 1; - bool canFly : 1; - }; - int flags; - }; -}; DBloodActor* getNextIncarnation(DBloodActor* actor); void killDudeLeech(DBloodActor* pLeech); diff --git a/source/games/blood/src/blood.cpp b/source/games/blood/src/blood.cpp index 177cf8517..34e976c92 100644 --- a/source/games/blood/src/blood.cpp +++ b/source/games/blood/src/blood.cpp @@ -60,14 +60,6 @@ IMPLEMENT_POINTERS_START(DBloodActor) #ifdef NOONE_EXTENSIONS IMPLEMENT_POINTER(prevmarker) IMPLEMENT_POINTER(ownerActor) -IMPLEMENT_POINTER(genDudeExtra.pLifeLeech) -IMPLEMENT_POINTER(genDudeExtra.slave[0]) -IMPLEMENT_POINTER(genDudeExtra.slave[1]) -IMPLEMENT_POINTER(genDudeExtra.slave[2]) -IMPLEMENT_POINTER(genDudeExtra.slave[3]) -IMPLEMENT_POINTER(genDudeExtra.slave[4]) -IMPLEMENT_POINTER(genDudeExtra.slave[5]) -IMPLEMENT_POINTER(genDudeExtra.slave[6]) #endif IMPLEMENT_POINTER(xspr.burnSource) IMPLEMENT_POINTER(xspr.target) diff --git a/source/games/blood/src/blood.h b/source/games/blood/src/blood.h index 708beeab1..2e3627bb1 100644 --- a/source/games/blood/src/blood.h +++ b/source/games/blood/src/blood.h @@ -60,6 +60,9 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. BEGIN_BLD_NS +// Define VM entry points for all callback functions so that we can +// a) later seamlessly call scripted functions where callbacks are used and +// b) get a means to serialize callback pointers. // ai callbacks DEF_ANIMATOR(aiMoveDodge) DEF_ANIMATOR(aiMoveForward) @@ -180,11 +183,37 @@ DEF_ANIMATOR(zombaThinkSearch) DEF_ANIMATOR(zombfThinkChase) DEF_ANIMATOR(zombfThinkGoto) DEF_ANIMATOR(zombfThinkSearch) + +#ifdef NOONE_EXTENSIONS +// patrol DEF_ANIMATOR(aiPatrolMove) DEF_ANIMATOR(aiPatrolThink) DEF_ANIMATOR(aiPatrolRandGoalAng) DEF_ANIMATOR(aiPatrolTurn) +// custom dude +DEF_ANIMATOR(resetTarget) +DEF_ANIMATOR(moveStop) +DEF_ANIMATOR(thinkSearch) +DEF_ANIMATOR(maybeThinkSearch) +DEF_ANIMATOR(thinkChase) +DEF_ANIMATOR(thinkFlee) +DEF_ANIMATOR(thinkTarget) +DEF_ANIMATOR(thinkMorph) +DEF_ANIMATOR(thinkDying) +DEF_ANIMATOR(enterBurnSearchWater) +DEF_ANIMATOR(enterMorph) +DEF_ANIMATOR(enterDying) +DEF_ANIMATOR(enterDeath) +DEF_ANIMATOR(enterSleep) +DEF_ANIMATOR(enterWake) +DEF_ANIMATOR(turnToTarget) +DEF_ANIMATOR(moveTurn) +DEF_ANIMATOR(moveDodge) +DEF_ANIMATOR(moveForward) +DEF_ANIMATOR(moveKnockout) +#endif + // seq callbacks DEF_ANIMATOR(FireballSeqCallback) DEF_ANIMATOR(Fx33Callback) @@ -240,6 +269,9 @@ DEF_ANIMATOR(PlayerKneelsOver) DEF_ANIMATOR(FireballTrapSeqCallback) DEF_ANIMATOR(MGunFireSeqCallback) DEF_ANIMATOR(MGunOpenSeqCallback) +#ifdef NOONE_EXTENSIONS +DEF_ANIMATOR(weaponShot) +#endif // event callbacks @@ -269,20 +301,6 @@ DEF_ANIMATOR(callbackMakeMissileBlocking) // 23 DEF_ANIMATOR(fxPodGreenBloodSpray) // 24 -#ifdef NOONE_EXTENSIONS -DEF_ANIMATOR(forcePunch) -DEF_ANIMATOR(aiGenDudeMoveForward) -DEF_ANIMATOR(unicultThinkChase) -DEF_ANIMATOR(unicultThinkGoto) -DEF_ANIMATOR(unicultThinkSearch) -DEF_ANIMATOR(genDudeAttack1) -DEF_ANIMATOR(punchCallback) -DEF_ANIMATOR(ThrowCallback1) -DEF_ANIMATOR(ThrowCallback2) -DEF_ANIMATOR(callbackGenDudeUpdate) -#endif - - enum EFeatureFlags { kFeatureCustomAmmoCount = 1, diff --git a/source/games/blood/src/bloodactor.h b/source/games/blood/src/bloodactor.h index fd2281371..71e9ed268 100644 --- a/source/games/blood/src/bloodactor.h +++ b/source/games/blood/src/bloodactor.h @@ -23,6 +23,7 @@ public: double dudeSlope; // Q18.14 format bool hasx; bool explosionhackflag; // this originally hijacked the target field which is not safe when working with pointers. + bool chasehackflag; // replaces hackery with non-serializable state data. XSPRITE xspr; SPRITEHIT hit; DUDEEXTRA dudeExtra; @@ -31,12 +32,11 @@ public: // nnext stuff. For now not exported to scripting. #ifdef NOONE_EXTENSIONS SPRITEMASS spriteMass; - GENDUDEEXTRA genDudeExtra; EventObject condition[4]; TObjPtr prevmarker; // needed by the nnext marker code. This originally hijacked targetX in XSPRITE #endif DVector3 basePoint; - int16_t dmgControl[kDamageMax]; // combination of the ones in DUDEINFO, THINGINFO and GENDUDEEXTRA, needs to be modifiable + int16_t dmgControl[kDamageMax]; // combination of the ones in DUDEINFO, THINGINFO, needs to be modifiable // transient data (not written to savegame) int cumulDamage; diff --git a/source/games/blood/src/gameutil.cpp b/source/games/blood/src/gameutil.cpp index a2606ee1f..f1b1927e8 100644 --- a/source/games/blood/src/gameutil.cpp +++ b/source/games/blood/src/gameutil.cpp @@ -60,6 +60,30 @@ bool CheckProximity(DBloodActor* actor, const DVector3& pos, sectortype* pSector return 0; } +//========================================================================== +// +// +// +//========================================================================== + +bool CheckProximityPoint(double nX1, double nY1, double nZ1, double nX2, double nY2, double nZ2, double nDist) +{ + double oX = abs(nX2 - nX1); + if (oX >= nDist) + return 0; + double oY = abs(nY2 - nY1); + if (oY >= nDist) + return 0; + if (nZ2 != nZ1) + { + double oZ = abs(nZ2 - nZ1); + if (oZ >= nDist) + return 0; + } + if (oX * oX + oY * oY > nDist * nDist) return 0; + return 1; +} + //--------------------------------------------------------------------------- // // Note: This function features some very bad math. diff --git a/source/games/blood/src/gameutil.h b/source/games/blood/src/gameutil.h index 00dea3f0b..ad3d3ca9c 100644 --- a/source/games/blood/src/gameutil.h +++ b/source/games/blood/src/gameutil.h @@ -33,6 +33,7 @@ enum { bool CheckProximity(DBloodActor* pSprite, const DVector3& pos, sectortype* pSector, int nDist); +bool CheckProximityPoint(double nX1, double nY1, double nZ1, double nX2, double nY2, double nZ2, double nDist); int HitScan(DBloodActor* pSprite, double z, const DVector3& pos, unsigned int nMask, double range = 0); int VectorScan(DBloodActor* pSprite, double nOffset, double nZOffset, const DVector3& vel, double nRange, int ac); diff --git a/source/games/blood/src/loadsave.cpp b/source/games/blood/src/loadsave.cpp index ad15e78e9..06fd30cd5 100644 --- a/source/games/blood/src/loadsave.cpp +++ b/source/games/blood/src/loadsave.cpp @@ -487,6 +487,7 @@ void DBloodActor::Serialize(FSerializer& arc) ("dudeslope", dudeSlope) ("dudeextra", dudeExtra) ("explosionflag", explosionhackflag) + ("chasehackflag", chasehackflag) ("spritehit", hit) ("owneractor", ownerActor) .Array("dmgcontrol", dmgControl, kDamageMax); @@ -498,14 +499,8 @@ void DBloodActor::Serialize(FSerializer& arc) { arc//("spritemass", spriteMass) // should always be cached and not written out. ("prevmarker", prevmarker) - .Array("conditions", condition, 2); + .Array("conditions", condition, 4); - - // GenDudeExtra only contains valid info for kDudeModernCustom and kDudeModernCustomBurning so only save when needed as these are not small. - if (GetType() == kDudeModernCustom) - { - arc("gendudeextra", genDudeExtra); - } } #endif } diff --git a/source/games/blood/src/nnextcdud.h b/source/games/blood/src/nnextcdud.h index 50c2fa264..be2c33eba 100644 --- a/source/games/blood/src/nnextcdud.h +++ b/source/games/blood/src/nnextcdud.h @@ -62,11 +62,12 @@ kCdudeFXEffectBase = 0, kCudeFXEffectCallbackBase = 512, kCdudeGIBEffectBase = 1024, -kCdudeMinSeeDist = 3000, -kCdudeMinHearDist = (kCdudeMinSeeDist >> 1), kCdudeBurningHealth = (25 << 4), }; +constexpr double kCdudeMinSeeDist = 3000 * inttoworld; +constexpr double kCdudeMinHearDist = (kCdudeMinSeeDist * 0.5); + class CUSTOMDUDE; extern VMFunction* nCdudeAppearanceCallback; extern VMFunction* gCdudeCustomCallback[]; @@ -380,13 +381,13 @@ class ARG_PICK_WEAPON { public: DAngle angle; - double distance; + double _distance; uint8_t dudeHealth; uint8_t targHealth; ARG_PICK_WEAPON(DBloodActor* actor, DBloodActor* target) { DVector2 distv = target->spr.pos.XY() - actor->spr.pos.XY(); - distance = distv.Length(); + _distance = distv.Length(); angle = absangle(distv.Angle(), actor->spr.Angles.Yaw); dudeHealth = CountHealthPerc(actor); targHealth = CountHealthPerc(target); @@ -394,7 +395,7 @@ class ARG_PICK_WEAPON ARG_PICK_WEAPON(DBloodActor* actor, DBloodActor* target, double nDist, DAngle nAng) { - distance = nDist; + _distance = nDist; angle = nAng; dudeHealth = CountHealthPerc(actor); targHealth = CountHealthPerc(target); @@ -647,14 +648,14 @@ class CUSTOMDUDE_WEAPON CUSTOMDUDE_SOUND sound; struct SHOT { - unsigned int velocity; + double _velocity; signed int slope; - uint16_t targetFollow : 12; - uint16_t clipdist : 8; - uint16_t impact : 2; - int16_t remTime : 14; + DAngle targetFollow; + uint8_t clipdist; + uint8_t impact; + int16_t remTime; APPEARANCE appearance; - POINT3D offset; + DVector3 offset; } shot; struct AMMO @@ -723,15 +724,16 @@ class CUSTOMDUDE_WEAPON targHpRange[1] = 255; shot.remTime = -1; - shot.velocity = INT32_MAX; + shot._velocity = FLT_MAX; shot.slope = INT32_MAX; } char HaveAmmmo(void) { return (!ammo.total || ammo.cur); } int GetDistance(void) { return ClipLow(distRange[1] - distRange[0], 0); } + int GetDistanceF(void) { return maptoworld * ClipLow(distRange[1] - distRange[0], 0); } int GetNumshots(void) { return (ammo.total) ? ClipHigh(ammo.cur, numshots) : numshots; } char IsTimeout(void) { return ((unsigned int)PlayClock < cooldown.clock); } char HaveSlope(void) { return (shot.slope != INT32_MAX); } - char HaveVelocity(void) { return (shot.velocity != INT32_MAX); } + char HaveVelocity(void) { return (shot._velocity != FLT_MAX); } }; @@ -818,8 +820,8 @@ class CUSTOMDUDE_EFFECT unsigned short id[kCdudeMaxEffects]; unsigned int clock; signed int liveTime; - double velocity; - double velocitySlope; + double _velocity; + double _velocitySlope; DAngle angle; uint8_t posture; uint8_t medium; @@ -837,7 +839,7 @@ class CUSTOMDUDE_EFFECT { memset(this, 0, sizeof(CUSTOMDUDE_EFFECT)); angle = DAngle360; - velocity = -1; + _velocity = -1; chance = 0x10000; srcVelocity = 1; @@ -890,9 +892,9 @@ class CUSTOMDUDE_EFFECT void Setup(DBloodActor* pSrc, DBloodActor* pEff, bool relVel) { - DVector3 dvel(0, 0, velocitySlope); + DVector3 dvel(0, 0, _velocitySlope); DAngle nAng = ((angle != DAngle360) ? (pSrc->spr.Angles.Yaw + angle) : RandomAngle()).Normalized360(); - double nVel = velocity; + double nVel = _velocity; int rp = Random(15); pEff->ownerActor = pSrc; @@ -1184,7 +1186,7 @@ class CUSTOMDUDE uint8_t numWeapons; DUDEEXTRA* pExtra; DUDEINFO* pInfo; - DBloodActor* pSpr; + DBloodActor* pSpr; // this is our owner so no TObjPtr is needed TObjPtr pLeech; CUSTOMDUDE_WEAPON weapons[kCdudeMaxWeapons]; // the weapons it may have CUSTOMDUDE_WEAPON* pWeapon; // pointer to current weapon @@ -1203,11 +1205,14 @@ class CUSTOMDUDE unsigned int mass ; // mass in KG FTextureID largestPic ; // in all states to compare on crouching sectortype* prevSector ; // the recent sector dude was in - double seeDist ; // dudeInfo duplicate for sleeping - double hearDist ; // dudeInfo duplicate for sleeping + double _seeDist ; // dudeInfo duplicate for sleeping + double _hearDist ; // dudeInfo duplicate for sleeping DAngle periphery ; // dudeInfo duplicate for sleeping unsigned int fallHeight ; // in pixels - signed int nextDude ; // -1: none, <-1: vdude, >=0: ins, >=kMaxSprites: cdude + TObjPtr NextDude; + int NextDudeType; + + //signed int nextDude ; // -1: none, <-1: vdude, >=0: ins, >=kMaxSprites: cdude //---------------------------------------------------------------------------------------------------- void PlaySound(int nState) { return (sound[nState].Play(pSpr)); } int GetStateSeq(int nState, int nPosture) { return states[nState][nPosture].seqId; } @@ -1244,7 +1249,7 @@ class CUSTOMDUDE char IsTooTight(void); //---------------------------------------------------------------------------------------------------- CUSTOMDUDE_WEAPON* PickWeapon(ARG_PICK_WEAPON* pArg); - int AdjustSlope(int nTarget, int zOffs); + int AdjustSlope(DBloodActor* nTarget, int zOffs); char AdjustSlope(int nDist, int* nSlope); //---------------------------------------------------------------------------------------------------- void InitSprite(void); @@ -1255,7 +1260,7 @@ class CUSTOMDUDE int Damage(DBloodActor* nFrom, int nDmgType, int nDmg); void Kill(DBloodActor* nFrom, int nDmgType, int nDmg); //---------------------------------------------------------------------------------------------------- - char CanMove(sectortype* pXSect, char Crusher, char Water, char Uwater, char Depth, int bottom, int floorZ); + char CanMove(sectortype* pXSect, bool Crusher, bool Water, bool Uwater, bool Depth, double bottom, double floorZ); char FindState(AISTATE* pState, int* nStateType, int* nPosture); void NewState(int nStateType, int nTimeOverride = -1); char NewState(AISTATE* pState); diff --git a/source/games/blood/src/nnexts.cpp b/source/games/blood/src/nnexts.cpp index 49cdc8ccc..5e6b2f2b9 100644 --- a/source/games/blood/src/nnexts.cpp +++ b/source/games/blood/src/nnexts.cpp @@ -817,7 +817,7 @@ bool nnExtIsImmune(DBloodActor* actor, int dmgType, int minScale) else if (actor->IsDudeActor()) { if (actor->IsPlayerActor()) return (getPlayer(actor)->damageControl[dmgType]); - else if (actor->GetType() == kDudeModernCustom) return (cdudeGet(actor)->GetDamage(-1, dmgType) <= minScale); + else if (actor->GetType() == kDudeModernCustom) return (cdudeGet(actor)->GetDamage(nullptr, dmgType) <= minScale); return (actor->dmgControl[dmgType] <= minScale); } } @@ -1667,8 +1667,6 @@ int getSpriteMassBySize(DBloodActor* actor) switch (actor->GetType()) { case kDudeModernCustom: - seqId = actor->xspr.data2; - clipDist = actor->genDudeExtra.clipdist; break; default: seqId = getDudeInfo(actor)->seqStartID; @@ -2643,19 +2641,6 @@ void useObjResizer(DBloodActor* sourceactor, int targType, sectortype* targSect, } } -#if 0 - if (fit && (targetactor->GetType() == kDudeModernCustom) - { - // request properties update for custom dude - - targetactor->genDudeExtra.updReq[kGenDudePropertySpriteSize] = true; - targetactor->genDudeExtra.updReq[kGenDudePropertyAttack] = true; - targetactor->genDudeExtra.updReq[kGenDudePropertyMass] = true; - targetactor->genDudeExtra.updReq[kGenDudePropertyDmgScale] = true; - evPostActor(targetactor, kGenDudeUpdTimeRate, AF(callbackGenDudeUpdate)); - } -#endif - if (valueIsBetween(sourceactor->xspr.data3, -1, 32767)) targetactor->spr.xoffset = ClipRange(sourceactor->xspr.data3, 0, 255); @@ -8747,7 +8732,7 @@ char nnExtOffsetSprite(DBloodActor* pSpr, const DVector3& opos) // //--------------------------------------------------------------------------- -void nnExtScaleVelocity(DBloodActor* pSpr, int nVel, const DVector3& vel, int which) +void nnExtScaleVelocity(DBloodActor* pSpr, double nVel, const DVector3& vel, int which) { if (which & 0x01) { @@ -8767,7 +8752,7 @@ void nnExtScaleVelocity(DBloodActor* pSpr, int nVel, const DVector3& vel, int wh // //--------------------------------------------------------------------------- -void nnExtScaleVelocityRel(DBloodActor* pSpr, int nVel, const DVector3& vel, int which) +void nnExtScaleVelocityRel(DBloodActor* pSpr, double nVel, const DVector3& vel, int which) { if (which & 0x01) { @@ -8919,34 +8904,6 @@ int nnExtDudeStartHealth(DBloodActor* pSpr, int nHealth) return getDudeInfo(pSpr->GetType())->startHealth << 4; } -//--------------------------------------------------------------------------- -// -// -// -//--------------------------------------------------------------------------- - -FSerializer& Serialize(FSerializer& arc, const char* keyname, GENDUDEEXTRA& w, GENDUDEEXTRA* def) -{ - if (arc.BeginObject(keyname)) - { - arc ("clipdist", w.clipdist) - .Array("availdeaths", w.availDeaths, kDamageMax) - ("movespeed", w.moveSpeed) - ("firedist", w.fireDist) - ("throwdist", w.throwDist) - ("curweapon", w.curWeapon) - ("weapontype", w.weaponType) - ("basedispersion", w.baseDispersion) - ("slavecount", w.slaveCount) - ("lifeleech", w.pLifeLeech) - .Array("slaves", w.slave, w.slaveCount) - .Array("updreq", w.updReq, kGenDudePropertyMax) - ("flags", w.flags) - .EndObject(); - } - return arc; -} - #if 0 // no need to save if we ensure it never gets accessed directly without validating the content FSerializer& Serialize(FSerializer& arc, const char* keyname, SPRITEMASS& w, SPRITEMASS* def) { @@ -8967,6 +8924,8 @@ FSerializer& Serialize(FSerializer& arc, const char* keyname, SPRITEMASS& w, SPR } #endif +void SerializeConditions(FSerializer& arc); + void SerializeNNExts(FSerializer& arc) { if (arc.BeginObject("nnexts")) @@ -8979,6 +8938,7 @@ void SerializeNNExts(FSerializer& arc) ("eventredirects", gEventRedirectsUsed); gSprNSect.Serialize(arc); + SerializeConditions(arc); arc.EndObject(); } } diff --git a/source/games/blood/src/nnextsif.cpp b/source/games/blood/src/nnextsif.cpp index 4440626c1..e7e7f82e8 100644 --- a/source/games/blood/src/nnextsif.cpp +++ b/source/games/blood/src/nnextsif.cpp @@ -1849,6 +1849,13 @@ FSerializer& Serialize(FSerializer& arc, const char* keyname, TRACKING_CONDITION return arc; } +void SerializeConditions(FSerializer& arc) +{ + // if this gets run before loading a map it needs to run the one-time init code before deserializing the data. + if (arc.isReading() && gConditions.Size() == 0) conditionInitData(); + arc("conditions", gTrackingConditionsList); +} + static void TriggerObject(EventObject nSerial, DBloodActor* pCond) {