diff --git a/Documentation/Changes.txt b/Documentation/Changes.txt index d065d200a..b1b90037f 100644 --- a/Documentation/Changes.txt +++ b/Documentation/Changes.txt @@ -2,8 +2,9 @@ Version 1.0.9 ============= - Add TR3 Wall mounted blade. +- Add TR3 Claw mutant. - Add removable puzzles from puzzle holes and puzzle dones. - - employed by setting the trigger type as "Switch" for either puzzle hole or puzzle done. + - employed by setting the trigger type as "Switch" for either puzzle hole or puzzle done. - Can be mixed with puzzle done and puzzle holes of the same or different type. - Fix Cold bar triggered when room is dry (only trigger if and only if the room is water). - Fix spiky wall speed value. diff --git a/TombEngine/Objects/Effects/enemy_missile.cpp b/TombEngine/Objects/Effects/enemy_missile.cpp index 886121884..cff7d6b62 100644 --- a/TombEngine/Objects/Effects/enemy_missile.cpp +++ b/TombEngine/Objects/Effects/enemy_missile.cpp @@ -11,12 +11,14 @@ #include "Game/items.h" #include "Game/Lara/lara.h" #include "Math/Math.h" +#include "Objects/TR3/Entity/tr3_claw_mutant.h" #include "Objects/TR4/Entity/tr4_mutant.h" #include "Objects/TR4/Entity/tr4_demigod.h" #include "Renderer/Renderer11Enums.h" #include "Specific/level.h" using namespace TEN::Effects::Items; +using namespace TEN::Entities::Creatures::TR3; using namespace TEN::Entities::TR4; using namespace TEN::Math; @@ -58,9 +60,13 @@ namespace TEN::Entities::Effects flame.fxObj = fxNumber; if (fx.flag1 == 1) + { flame.scalar = 3; + } else + { flame.scalar = 2; + } flame.sSize = flame.size = (GetRandomControl() & 7) + 64; flame.dSize = flame.size / 32; @@ -92,9 +98,13 @@ namespace TEN::Entities::Effects flame.rotAng = GetRandomControl() & 0xFFF; if (GetRandomControl() & 1) + { flame.rotAdd = -32 - (GetRandomControl() & 0x1F); + } else + { flame.rotAdd = (GetRandomControl() & 0x1F) + 32; + } flame.gravity = 0; flame.maxYvel = 0; @@ -135,7 +145,7 @@ namespace TEN::Entities::Effects } else { - if (fx.flag1 == (int)MissileType::Mutant) + if (fx.flag1 == (int)MissileType::CrocgodMutant) { if (fx.counter) fx.counter--; @@ -152,15 +162,20 @@ namespace TEN::Entities::Effects if (fx.speed < maxVelocity) { - if (fx.flag1 == (int)MissileType::Mutant) + if (fx.flag1 == (int)MissileType::CrocgodMutant) + { fx.speed++; + } else + { fx.speed += 3; + } } if (fx.speed < maxVelocity && fx.flag1 != (int)MissileType::SophiaLeighNormal && - fx.flag1 != (int)MissileType::SophiaLeighLarge) + fx.flag1 != (int)MissileType::SophiaLeighLarge && + fx.flag1 != (int)MissileType::ClawMutantPlasma) { short dy = orient.y - fx.pos.Orientation.y; if (abs(dy) > abs(ANGLE(180.0f))) @@ -171,8 +186,6 @@ namespace TEN::Entities::Effects dx = -dx; dy >>= 3; - dx >>= 3; - if (dy < -maxRotation) { dy = -maxRotation; @@ -182,6 +195,7 @@ namespace TEN::Entities::Effects dy = maxRotation; } + dx >>= 3; if (dx < -maxRotation) { dx = -maxRotation; @@ -192,85 +206,86 @@ namespace TEN::Entities::Effects } fx.pos.Orientation.x += dx; - - if (fx.flag1 != (int)MissileType::Demigod3Radial && (fx.flag1 != (int)MissileType::Mutant || !fx.counter)) + if (fx.flag1 != (int)MissileType::Demigod3Radial && (fx.flag1 != (int)MissileType::CrocgodMutant || !fx.counter)) fx.pos.Orientation.y += dy; } fx.pos.Orientation.z += 16 * fx.speed; - if (fx.flag1 == (int)MissileType::Mutant) + if (fx.flag1 == (int)MissileType::CrocgodMutant) fx.pos.Orientation.z += 16 * fx.speed; auto prevPos = fx.pos.Position; int speed = (fx.speed * phd_cos(fx.pos.Orientation.x)); - fx.pos.Position.x += (speed * phd_sin(fx.pos.Orientation.y)); + fx.pos.Position.x += speed * phd_sin(fx.pos.Orientation.y); fx.pos.Position.y += -((fx.speed * phd_sin(fx.pos.Orientation.x))) + fx.fallspeed; - fx.pos.Position.z += (speed * phd_cos(fx.pos.Orientation.y)); + fx.pos.Position.z += speed * phd_cos(fx.pos.Orientation.y); - auto probe = GetCollision(fx.pos.Position.x, fx.pos.Position.y, fx.pos.Position.z, fx.roomNumber); + auto pointColl = GetCollision(fx.pos.Position.x, fx.pos.Position.y, fx.pos.Position.z, fx.roomNumber); - if (fx.pos.Position.y >= probe.Position.Floor || fx.pos.Position.y <= probe.Position.Ceiling) + if (fx.pos.Position.y >= pointColl.Position.Floor || fx.pos.Position.y <= pointColl.Position.Ceiling) { fx.pos.Position = prevPos; - if (fx.flag1 != (int)MissileType::Mutant && - fx.flag1 != (int)MissileType::SophiaLeighNormal && - fx.flag1 != (int)MissileType::SophiaLeighLarge) + switch ((MissileType)fx.flag1) + { + case MissileType::SethLarge: + TriggerShockwave(&fx.pos, 32, 160, 64, 64, 128, 00, 24, EulerAngles(fx.pos.Orientation.x - ANGLE(90.0f), 0, 0), 0, true, false, (int)ShockwaveStyle::Normal); + TriggerExplosionSparks(prevPos.x, prevPos.y, prevPos.z, 3, -2, 2, fx.roomNumber); BubblesShatterFunction(&fx, 0, -32); + break; - if (fx.flag1 == (int)MissileType::SethLarge) - { - TriggerShockwave(&fx.pos, 32, 160, 64, 64, 128, 00, 24, EulerAngles((((~g_Level.Rooms[fx.roomNumber].flags) / 16) & 2) * 65536, 0.0f, 0.0f), 0, true, false, (int)ShockwaveStyle::Normal); + case MissileType::SophiaLeighNormal: + TriggerShockwave(&fx.pos, 5, 32, 128, 0, 128, 128, 24, EulerAngles(fx.pos.Orientation.x - ANGLE(90.0f), 0, 0), 0, true, false, (int)ShockwaveStyle::Normal); TriggerExplosionSparks(prevPos.x, prevPos.y, prevPos.z, 3, -2, 2, fx.roomNumber); - } - else if (fx.flag1 == (int)MissileType::SophiaLeighNormal) - { - TriggerShockwave(&fx.pos, 5, 32, 128, 0, 128, 128, 24, EulerAngles(fx.pos.Orientation.y + ANGLE(180), 0.0f, 0.0f), 0, true, false, (int)ShockwaveStyle::Normal); - TriggerExplosionSparks(prevPos.x, prevPos.y, prevPos.z, 3, -2, 2, fx.roomNumber); - } - else if (fx.flag1 == (int)MissileType::SophiaLeighLarge) - { - TriggerShockwave(&fx.pos, 10, 64, 128, 0, 128, 128, 24, EulerAngles(fx.pos.Orientation.y + ANGLE(180), 0.0f, 0.0f), 0, true, false, (int)ShockwaveStyle::Normal); - TriggerExplosionSparks(prevPos.x, prevPos.y, prevPos.z, 3, -2, 2, fx.roomNumber); - } - else - { - if (fx.flag1) - { - if (fx.flag1 == (int)MissileType::Demigod3Single || fx.flag1 == (int)MissileType::Demigod3Radial) - { - TriggerShockwave(&fx.pos, 32, 160, 64, 0, 96, 128, 16, EulerAngles::Zero, 0, true, false, (int)ShockwaveStyle::Normal); - } - else if (fx.flag1 == (int)MissileType::Demigod2) - { - TriggerShockwave(&fx.pos, 32, 160, 64, 128, 64, 0, 16, EulerAngles::Zero, 0, true, false, (int)ShockwaveStyle::Normal); - } - else - { - if (fx.flag1 != (int)MissileType::Harpy) - { - if (fx.flag1 == (int)MissileType::Mutant) - { - TriggerExplosionSparks(prevPos.x, prevPos.y, prevPos.z, 3, -2, 0, fx.roomNumber); - TriggerShockwave(&fx.pos, 48, 240, 64, 128, 96, 0, 24, EulerAngles::Zero, 15, true, false, (int)ShockwaveStyle::Normal); - fx.pos.Position.y -= 128; - TriggerShockwave(&fx.pos, 48, 240, 48, 128, 112, 0, 16, EulerAngles::Zero, 15, true, false, (int)ShockwaveStyle::Normal); - fx.pos.Position.y += 256; - TriggerShockwave(&fx.pos, 48, 240, 48, 128, 112, 0, 16, EulerAngles::Zero, 15, true, false, (int)ShockwaveStyle::Normal); - } + break; - } - else - { - TriggerShockwave(&fx.pos, 32, 160, 64, 128, 128, 0, 16, EulerAngles::Zero, 0, true, false, (int)ShockwaveStyle::Normal); - } - } - } - else + case MissileType::SophiaLeighLarge: + TriggerShockwave(&fx.pos, 10, 64, 128, 0, 128, 128, 24, EulerAngles(fx.pos.Orientation.x - ANGLE(90.0f), 0, 0), 0, true, false, (int)ShockwaveStyle::Normal); + TriggerExplosionSparks(prevPos.x, prevPos.y, prevPos.z, 3, -2, 2, fx.roomNumber); + break; + + case MissileType::Demigod3Single: + case MissileType::Demigod3Radial: + TriggerShockwave(&fx.pos, 32, 160, 64, 0, 96, 128, 16, EulerAngles::Zero, 0, true, false, (int)ShockwaveStyle::Normal); + BubblesShatterFunction(&fx, 0, -32); + break; + + case MissileType::Demigod2: + TriggerShockwave(&fx.pos, 32, 160, 64, 128, 64, 0, 16, EulerAngles::Zero, 0, true, false, (int)ShockwaveStyle::Normal); + BubblesShatterFunction(&fx, 0, -32); + break; + + case MissileType::Harpy: + TriggerShockwave(&fx.pos, 32, 160, 64, 128, 128, 0, 16, EulerAngles::Zero, 0, true, false, (int)ShockwaveStyle::Normal); + BubblesShatterFunction(&fx, 0, -32); + break; + + case MissileType::CrocgodMutant: + TriggerExplosionSparks(prevPos.x, prevPos.y, prevPos.z, 3, -2, 0, fx.roomNumber); + TriggerShockwave(&fx.pos, 48, 240, 64, 128, 96, 0, 24, EulerAngles::Zero, 15, true, false, (int)ShockwaveStyle::Normal); + + fx.pos.Position.y -= 128; + TriggerShockwave(&fx.pos, 48, 240, 48, 128, 112, 0, 16, EulerAngles::Zero, 15, true, false, (int)ShockwaveStyle::Normal); + + fx.pos.Position.y += 256; + TriggerShockwave(&fx.pos, 48, 240, 48, 128, 112, 0, 16, EulerAngles::Zero, 15, true, false, (int)ShockwaveStyle::Normal); + break; + + case MissileType::ClawMutantPlasma: + for (int i = 0; i < 6; i++) { - TriggerShockwave(&fx.pos, 32, 160, 64, 0, 128, 64, 16, EulerAngles::Zero, 0, true, false, (int)ShockwaveStyle::Normal); + SpawnClawMutantPlasmaFlameBall(fxNumber, Vector3(20.0f, 32.0f, 20.0f), Vector3(Random::GenerateFloat(-115.0f, 185.0f), 0, Random::GenerateFloat(-115.0f, 185.0f)), 24); + SpawnClawMutantPlasmaFlameBall(fxNumber, Vector3(20.0f, 32.0f, 20.0f), Vector3(Random::GenerateFloat(-115.0f, 185.0f), 0, Random::GenerateFloat(-115.0f, 185.0f)), 24); + SpawnClawMutantPlasmaFlameBall(fxNumber, Vector3(20.0f, 32.0f, 20.0f), Vector3(Random::GenerateFloat(-115.0f, 185.0f), 0, Random::GenerateFloat(-115.0f, 185.0f)), 24); } + + break; + + default: + TriggerShockwave(&fx.pos, 32, 160, 64, 0, 128, 64, 16, EulerAngles::Zero, 0, true, false, (int)ShockwaveStyle::Normal); + BubblesShatterFunction(&fx, 0, -32); + break; } KillEffect(fxNumber); @@ -280,93 +295,110 @@ namespace TEN::Entities::Effects if (ItemNearLara(fx.pos.Position, 200)) { LaraItem->HitStatus = true; - if (fx.flag1 != (int)MissileType::Mutant && - fx.flag1 != (int)MissileType::SophiaLeighNormal && - fx.flag1 != (int)MissileType::SophiaLeighLarge) - BubblesShatterFunction(&fx, 0, -32); - - KillEffect(fxNumber); - - if (fx.flag1 == (int)MissileType::SethLarge) + switch ((MissileType)fx.flag1) { + case MissileType::SethLarge: TriggerShockwave(&fx.pos, 48, 240, 64, 0, 128, 64, 24, EulerAngles::Zero, 0, true, false, (int)ShockwaveStyle::Normal); TriggerExplosionSparks(prevPos.x, prevPos.y, prevPos.z, 3, -2, 2, fx.roomNumber); ItemCustomBurn(LaraItem, Vector3(0.0f, 0.8f, 0.1f), Vector3(0.0f, 0.9f, 0.8f)); - } - else if (fx.flag1 == (int)MissileType::SophiaLeighNormal) - { + break; + + case MissileType::SophiaLeighLarge: TriggerShockwave(&fx.pos, 5, 32, 128, 0, 128, 128, 24, EulerAngles(fx.pos.Orientation.y, 0.0f, 0.0f), fx.flag2, true, false, (int)ShockwaveStyle::Normal); TriggerExplosionSparks(prevPos.x, prevPos.y, prevPos.z, 3, -2, 2, fx.roomNumber); - } - else if (fx.flag1 == (int)MissileType::SophiaLeighLarge) - { + break; + + case MissileType::SophiaLeighNormal: TriggerShockwave(&fx.pos, 10, 64, 128, 0, 128, 128, 24, EulerAngles(fx.pos.Orientation.y, 0.0f, 0.0f), fx.flag2, true, false, (int)ShockwaveStyle::Normal); TriggerExplosionSparks(prevPos.x, prevPos.y, prevPos.z, 3, -2, 2, fx.roomNumber); - } - else if (fx.flag1) - { - switch (fx.flag1) + break; + + case MissileType::Demigod3Single: + case MissileType::Demigod3Radial: + TriggerShockwave(&fx.pos, 32, 160, 64, 0, 96, 128, 16, EulerAngles::Zero, 10, true, false, (int)ShockwaveStyle::Normal); + BubblesShatterFunction(&fx, 0, -32); + break; + + case MissileType::Demigod2: + TriggerShockwave(&fx.pos, 32, 160, 64, 128, 64, 0, 16, EulerAngles::Zero, 5, true, false, (int)ShockwaveStyle::Normal); + BubblesShatterFunction(&fx, 0, -32); + break; + + case MissileType::ClawMutantPlasma: + DoDamage(LaraItem, fx.flag2); + for (int i = 0; i < 3; i++) { - case (int)MissileType::Demigod3Single: - case (int)MissileType::Demigod3Radial: - TriggerShockwave(&fx.pos, 32, 160, 64, 0, 96, 128, 16, EulerAngles::Zero, 10, true, false, (int)ShockwaveStyle::Normal); - break; - - case (int)MissileType::Demigod2: - TriggerShockwave(&fx.pos, 32, 160, 64, 128, 64, 0, 16, EulerAngles::Zero, 5, true, false, (int)ShockwaveStyle::Normal); - break; - - case (int)MissileType::Harpy: - TriggerShockwave(&fx.pos, 32, 160, 64, 128, 128, 0, 16, EulerAngles::Zero, 3, true, false, (int)ShockwaveStyle::Normal); - break; - - case (int)MissileType::Mutant: - TriggerExplosionSparks(prevPos.x, prevPos.y, prevPos.z, 3, -2, 0, fx.roomNumber); - TriggerShockwave(&fx.pos, 48, 240, 64, 128, 96, 0, 24, EulerAngles::Zero, 0, true, false, (int)ShockwaveStyle::Normal); - fx.pos.Position.y -= 128; - TriggerShockwave(&fx.pos, 48, 240, 48, 128, 112, 0, 16, EulerAngles::Zero, 0, true, false, (int)ShockwaveStyle::Normal); - fx.pos.Position.y += 256; - TriggerShockwave(&fx.pos, 48, 240, 48, 128, 112, 0, 16, EulerAngles::Zero, 0, true, false, (int)ShockwaveStyle::Normal); - ItemBurn(LaraItem); - break; + SpawnClawMutantPlasmaFlameBall(fxNumber, Vector3(20.0f, 32.0f, 20.0f), Vector3(Random::GenerateFloat(-115.0f, 185.0f), 0, Random::GenerateFloat(-115.0f, 185.0f)), 24); + SpawnClawMutantPlasmaFlameBall(fxNumber, Vector3(20.0f, 32.0f, 20.0f), Vector3(Random::GenerateFloat(-115.0f, 185.0f), 0, Random::GenerateFloat(-115.0f, 185.0f)), 24); + SpawnClawMutantPlasmaFlameBall(fxNumber, Vector3(20.0f, 32.0f, 20.0f), Vector3(Random::GenerateFloat(-115.0f, 185.0f), 0, Random::GenerateFloat(-115.0f, 185.0f)), 24); } + + break; + + case MissileType::Harpy: + TriggerShockwave(&fx.pos, 32, 160, 64, 128, 128, 0, 16, EulerAngles::Zero, 3, true, false, (int)ShockwaveStyle::Normal); + BubblesShatterFunction(&fx, 0, -32); + break; + + case MissileType::CrocgodMutant: + TriggerExplosionSparks(prevPos.x, prevPos.y, prevPos.z, 3, -2, 0, fx.roomNumber); + TriggerShockwave(&fx.pos, 48, 240, 64, 128, 96, 0, 24, EulerAngles::Zero, 0, true, false, (int)ShockwaveStyle::Normal); + + fx.pos.Position.y -= 128; + TriggerShockwave(&fx.pos, 48, 240, 48, 128, 112, 0, 16, EulerAngles::Zero, 0, true, false, (int)ShockwaveStyle::Normal); + + fx.pos.Position.y += 256; + TriggerShockwave(&fx.pos, 48, 240, 48, 128, 112, 0, 16, EulerAngles::Zero, 0, true, false, (int)ShockwaveStyle::Normal); + ItemBurn(LaraItem); + break; + + default: + TriggerShockwave(&fx.pos, 24, 88, 48, 0, 128, 64, 16, EulerAngles::Zero, 1, true, false, (int)ShockwaveStyle::Normal); + break; } - else - { - TriggerShockwave(&fx.pos, 24, 88, 48, 0, 128, 64, 16, EulerAngles((((~g_Level.Rooms[fx.roomNumber].flags) / 16) & 2) * 65536, 0.0f, 0.0f), 1, true, false, (int)ShockwaveStyle::Normal); - } + + KillEffect(fxNumber); } else { - if (probe.RoomNumber != fx.roomNumber) - EffectNewRoom(fxNumber, probe.RoomNumber); + if (pointColl.RoomNumber != fx.roomNumber) + EffectNewRoom(fxNumber, pointColl.RoomNumber); auto deltaPos = prevPos - fx.pos.Position; if (Wibble & 4) { - switch (fx.flag1) + switch ((MissileType)fx.flag1) { default: - case (int)MissileType::SethLarge: - TriggerSethMissileFlame(fxNumber, 32 * deltaPos.x, 32 * deltaPos.y, 32 * deltaPos.z); + case MissileType::SethLarge: + TriggerSethMissileFlame(fxNumber, deltaPos.x * 32, deltaPos.y * 32, deltaPos.z * 32); break; - case (int)MissileType::Harpy: - TriggerHarpyFlameFlame(fxNumber, 16 * deltaPos.x, 16 * deltaPos.y, 16 * deltaPos.z); + case MissileType::Harpy: + TriggerHarpyFlameFlame(fxNumber, deltaPos.x * 16, deltaPos.y * 16, deltaPos.z * 16); break; - case (int)MissileType::Demigod3Single: - case (int)MissileType::Demigod3Radial: - case (int)MissileType::Demigod2: - TriggerDemigodMissileFlame(fxNumber, 16 * deltaPos.x, 16 * deltaPos.y, 16 * deltaPos.z); + case MissileType::Demigod3Single: + case MissileType::Demigod3Radial: + case MissileType::Demigod2: + TriggerDemigodMissileFlame(fxNumber, deltaPos.x * 16, deltaPos.y * 16, deltaPos.z * 16); break; - case (int)MissileType::Mutant: - TriggerCrocgodMissileFlame(fxNumber, 16 * deltaPos.x, 16 * deltaPos.y, 16 * deltaPos.z); + case MissileType::ClawMutantPlasma: + for (int i = 0; i < 3; i++) + SpawnClawMutantPlasmaFlameBall(fxNumber, Vector3(deltaPos.x, deltaPos.y * 16, deltaPos.z), Vector3::Zero, 10.0f); + + break; + + case MissileType::CrocgodMutant: + TriggerCrocgodMissileFlame(fxNumber, deltaPos.x * 16, deltaPos.y * 16, deltaPos.z * 16); break; } } } + + if (fx.flag1 == (int)MissileType::ClawMutantPlasma) + TriggerDynamicLight(fx.pos.Position.x, fx.pos.Position.y, fx.pos.Position.z, 8, 0, 64, 128); } } diff --git a/TombEngine/Objects/Effects/enemy_missile.h b/TombEngine/Objects/Effects/enemy_missile.h index 3c5a0dfb9..6bd225061 100644 --- a/TombEngine/Objects/Effects/enemy_missile.h +++ b/TombEngine/Objects/Effects/enemy_missile.h @@ -10,9 +10,10 @@ namespace TEN::Entities::Effects Demigod3Single = 3, Demigod3Radial = 4, Demigod2 = 5, - Mutant = 6, + CrocgodMutant = 6, SophiaLeighNormal = 7, - SophiaLeighLarge = 8 + SophiaLeighLarge = 8, + ClawMutantPlasma = 9 }; void ControlEnemyMissile(short fxNumber); diff --git a/TombEngine/Objects/TR3/Entity/tr3_claw_mutant.cpp b/TombEngine/Objects/TR3/Entity/tr3_claw_mutant.cpp new file mode 100644 index 000000000..b4b612098 --- /dev/null +++ b/TombEngine/Objects/TR3/Entity/tr3_claw_mutant.cpp @@ -0,0 +1,510 @@ +#include "framework.h" +#include "Objects/TR3/Entity/tr3_claw_mutant.h" + +#include "Game/animation.h" +#include "Game/control/box.h" +#include "Game/effects/effects.h" +#include "Game/Lara/lara_helpers.h" +#include "Math/Math.h" +#include "Objects/Effects/enemy_missile.h" +#include "Game/misc.h" +#include "Game/people.h" +#include "Specific/setup.h" + +using namespace TEN::Entities::Effects; +using namespace TEN::Math; + +// ItemFlags[5] flag enables damage left (0 = enabled, 1 = disabled). +// ItemFlags[6] flag enables damage right (0 = enabled, 1 = disabled). + +namespace TEN::Entities::Creatures::TR3 +{ + constexpr auto CLAW_MUTANT_CLAW_ATTACK_DAMAGE = 100; + constexpr auto CLAW_MUTANT_PLASMA_ATTACK_DAMAGE = 200; + + constexpr auto CLAW_MUTANT_WALK_CHANCE = 1 / 64.0f; + + constexpr auto CLAW_MUTANT_IDLE_CLAW_ATTACK_RANGE = SQUARE(BLOCK(1.5f)); + constexpr auto CLAW_MUTANT_IDLE_DUAL_CLAW_ATTACK_RANGE = SQUARE(BLOCK(1.5f)); + constexpr auto CLAW_MUTANT_WALK_CLAW_ATTACK_RANGE = SQUARE(BLOCK(1)); + constexpr auto CLAW_MUTANT_RUN_CLAW_ATTACK_RANGE = SQUARE(BLOCK(2)); + constexpr auto CLAW_MUTANT_PLASMA_ATTACK_RANGE = SQUARE(BLOCK(3)); + + constexpr auto CLAW_MUTANT_PLASMA_VELOCITY = 250; + + constexpr auto CLAW_MUTANT_WALK_TURN_RATE_MAX = ANGLE(3.0f); + constexpr auto CLAW_MUTANT_RUN_TURN_RATE_MAX = ANGLE(4.0f); + + const auto ClawMutantLeftBite = BiteInfo(Vector3(19.0f, -13.0f, 3.0f), 7); + const auto ClawMutantRightBite = BiteInfo(Vector3(19.0f, -13.0f, 3.0f), 4); + const auto ClawMutantTailBite = BiteInfo(Vector3(-32.0f, -16.0f, -119.0f), 13); + + enum ClawMutantState + { + CLAW_MUTANT_STATE_IDLE = 0, + CLAW_MUTANT_STATE_WALK = 1, + CLAW_MUTANT_STATE_RUN = 2, + CLAW_MUTANT_STATE_RUN_CLAW_ATTACK = 3, + CLAW_MUTANT_STATE_WALK_CLAW_ATTACK_LEFT = 4, + CLAW_MUTANT_STATE_WALK_CLAW_ATTACK_RIGHT = 5, + CLAW_MUTANT_STATE_IDLE_CLAW_ATTACK_LEFT = 6, + CLAW_MUTANT_STATE_IDLE_CLAW_ATTACK_RIGHT = 7, + CLAW_MUTANT_STATE_DEATH = 8, + CLAW_MUTANT_STATE_IDLE_DUAL_CLAW_ATTACK = 9, + CLAW_MUTANT_STATE_PLASMA_ATTACK = 10 + }; + + enum ClawMutantAnim + { + CLAW_MUTANT_ANIM_IDLE = 0, + CLAW_MUTANT_ANIM_IDLE_TO_WALK_LEFT = 1, + CLAW_MUTANT_ANIM_IDLE_TO_RUN_LEFT = 2, + CLAW_MUTANT_ANIM_WALK_TO_IDLE_RIGHT = 3, + CLAW_MUTANT_ANIM_WALK_TO_IDLE_LEFT = 4, + CLAW_MUTANT_ANIM_RUN_TO_IDLE_RIGHT = 5, + CLAW_MUTANT_ANIM_RUN_TO_IDLE_LEFT = 6, + CLAW_MUTANT_ANIM_WALK = 7, + CLAW_MUTANT_ANIM_WALK_TO_RUN = 8, + CLAW_MUTANT_ANIM_RUN = 9, + CLAW_MUTANT_ANIM_RUN_TO_WALK_LEFT = 10, + CLAW_MUTANT_ANIM_RUN_TO_WALK_RIGHT = 11, + CLAW_MUTANT_ANIM_RUN_CLAW_ATTACK = 12, + CLAW_MUTANT_ANIM_RUN_CLAW_ATTACK_CANCEL = 13, + CLAW_MUTANT_ANIM_IDLE_CLAW_ATTACK_LEFT = 14, + CLAW_MUTANT_ANIM_IDLE_CLAW_ATTACK_RIGHT = 15, + CLAW_MUTANT_ANIM_IDLE_DUAL_CLAW_ATTACK = 16, + CLAW_MUTANT_ANIM_WALK_CLAW_ATTACK_LEFT = 17, + CLAW_MUTANT_ANIM_WALK_CLAW_ATTACK_RIGHT = 18, + CLAW_MUTANT_ANIM_PLASMA_ATTACK = 19, + CLAW_MUTANT_ANIM_DEATH = 20 + }; + + static void SpawnClawMutantPlasma(int itemNumber) + { + auto& plasma = *GetFreeParticle(); + + plasma.on = true; + plasma.sB = 255; + plasma.sG = 48 + (GetRandomControl() & 31); + plasma.sR = 48; + + plasma.dB = 192 + (GetRandomControl() & 63); + plasma.dG = 128 + (GetRandomControl() & 63); + plasma.dR = 32; + + plasma.colFadeSpeed = 12 + (GetRandomControl() & 3); + plasma.fadeToBlack = 8; + plasma.sLife = + plasma.life = (GetRandomControl() & 7) + 24; + + plasma.blendMode = BLEND_MODES::BLENDMODE_ADDITIVE; + + plasma.extras = 0; + plasma.dynamic = -1; + + plasma.x = ((GetRandomControl() & 15) - 8); + plasma.y = 0; + plasma.z = ((GetRandomControl() & 15) - 8); + + plasma.xVel = ((GetRandomControl() & 31) - 16); + plasma.yVel = (GetRandomControl() & 15) + 16; + plasma.zVel = ((GetRandomControl() & 31) - 16); + plasma.friction = 3; + + if (Random::TestProbability(1 / 2.0f)) + { + plasma.flags = SP_SCALE | SP_DEF | SP_ROTATE | SP_EXPDEF | SP_ITEM | SP_NODEATTACH; + plasma.rotAng = GetRandomControl() & 4095; + + if (Random::TestProbability(1 / 2.0f)) + { + plasma.rotAdd = -(GetRandomControl() & 15) - 16; + } + else + { + plasma.rotAdd = (GetRandomControl() & 15) + 16; + } + } + else + { + plasma.flags = SP_SCALE | SP_DEF | SP_EXPDEF | SP_ITEM | SP_NODEATTACH; + } + + plasma.gravity = (GetRandomControl() & 31) + 16; + plasma.maxYvel = (GetRandomControl() & 7) + 16; + + plasma.fxObj = itemNumber; + plasma.nodeNumber = ParticleNodeOffsetIDs::NodeClawMutantPlasma; + + plasma.spriteIndex = Objects[ID_DEFAULT_SPRITES].meshIndex; + plasma.scalar = 1; + int size = (GetRandomControl() & 31) + 64; + plasma.size = + plasma.sSize = size; + plasma.dSize = size / 4; + } + + static void SpawnClawMutantPlasmaBall(ItemInfo& item) + { + const auto& creature = *GetCreatureInfo(&item); + + int plasmaBall = CreateNewEffect(item.RoomNumber); + if (plasmaBall == NO_ITEM) + return; + + auto enemyPos = creature.Enemy->Pose.Position; + if (creature.Enemy->IsLara() && GetLaraInfo(creature.Enemy)->Control.IsLow) + { + enemyPos.y -= CLICK(1); + } + else + { + enemyPos.y -= CLICK(2); + } + + auto& fx = EffectList[plasmaBall]; + + auto jointPos = GetJointPosition(item, ClawMutantTailBite.meshNum, ClawMutantTailBite.Position); + auto orient = Geometry::GetOrientToPoint(jointPos.ToVector3(), enemyPos.ToVector3()); + + fx.pos.Position = jointPos; + fx.pos.Orientation.x = orient.x; + fx.pos.Orientation.y = orient.y; + fx.objectNumber = ID_ENERGY_BUBBLES; + fx.color = Vector4::Zero; + fx.speed = CLAW_MUTANT_PLASMA_VELOCITY; + fx.flag2 = CLAW_MUTANT_PLASMA_ATTACK_DAMAGE; + fx.flag1 = (int)MissileType::ClawMutantPlasma; + fx.fallspeed = 0; + } + + static void SpawnMutantPlasmaLight(ItemInfo& item) + { + int bright = item.Animation.FrameNumber - GetAnimData(item).frameBase; + if (bright > 16) + { + bright = GetAnimData(item).frameBase + 28 + 16 - item.Animation.FrameNumber; + if (bright > 16) + bright = 16; + } + + if (bright > 0) + { + auto pos = GetJointPosition(item, 13, Vector3i(-32, -16, -192)); + int rnd = GetRandomControl(); + byte r, g, b; + + b = 31 - ((rnd / 16) & 3); + g = 24 - ((rnd / 64) & 3); + r = rnd & 7; + + r = (r * bright) / 16; + g = (g * bright) / 16; + b = (b * bright) / 16; + TriggerDynamicLight(pos.x, pos.y, pos.z, bright, r, g, b); + } + } + + static void DamageTargetWithClaw(ItemInfo& source, ItemInfo& target) + { + if (source.ItemFlags[5] == 0 && source.TouchBits.Test(ClawMutantLeftBite.meshNum)) + { + DoDamage(&target, CLAW_MUTANT_CLAW_ATTACK_DAMAGE / 2); + CreatureEffect2(&source, ClawMutantLeftBite, 10, source.Pose.Orientation.y, DoBloodSplat); + source.ItemFlags[5] = 1; + } + + if (source.ItemFlags[6] == 0 && source.TouchBits.Test(ClawMutantRightBite.meshNum)) + { + DoDamage(&target, CLAW_MUTANT_CLAW_ATTACK_DAMAGE / 2); + CreatureEffect2(&source, ClawMutantRightBite, 10, source.Pose.Orientation.y, DoBloodSplat); + source.ItemFlags[6] = 1; + } + } + + void SpawnClawMutantPlasmaFlameBall(int fxNumber, const Vector3& vel, const Vector3& offset, float life) + { + auto& plasma = *GetFreeParticle(); + + plasma.on = true; + plasma.sB = 255; + plasma.sG = 48 + (GetRandomControl() & 31); + plasma.sR = 48; + + plasma.dB = 192 + (GetRandomControl() & 63); + plasma.dG = 128 + (GetRandomControl() & 63); + plasma.dR = 32; + + plasma.colFadeSpeed = 12 + (GetRandomControl() & 3); + plasma.fadeToBlack = 8; + plasma.sLife = + plasma.life = (GetRandomControl() & 7) + life; + + plasma.blendMode = BLENDMODE_ADDITIVE; + + plasma.extras = 0; + plasma.dynamic = -1; + + plasma.x = offset.x + ((GetRandomControl() & 15) - 8); + plasma.y = 0; + plasma.z = offset.z + ((GetRandomControl() & 15) - 8); + + plasma.xVel = vel.x; + plasma.yVel = vel.y; + plasma.zVel = vel.z; + plasma.friction = 5; + + if (Random::TestProbability(1 / 2.0f)) + { + plasma.flags = SP_SCALE | SP_DEF | SP_ROTATE | SP_EXPDEF | SP_FX; + plasma.rotAng = GetRandomControl() & 4095; + + if (Random::TestProbability(1 / 2.0f)) + { + plasma.rotAdd = -(GetRandomControl() & 15) - 16; + } + else + { + plasma.rotAdd = (GetRandomControl() & 15) + 16; + } + } + else + { + plasma.flags = SP_SCALE | SP_DEF | SP_EXPDEF | SP_FX; + } + + plasma.fxObj = fxNumber; + plasma.spriteIndex = Objects[ID_DEFAULT_SPRITES].meshIndex; + plasma.scalar = 1; + plasma.gravity = + plasma.maxYvel = 0; + + int size = (GetRandomControl() & 31) + 64; + plasma.size = + plasma.sSize = size; + plasma.dSize /= 16; + + plasma.yVel = (GetRandomControl() & 511) - 256; + plasma.xVel *= 2; + plasma.zVel *= 2; + plasma.scalar = 2; + plasma.friction = 85; + plasma.gravity = 22; + } + + void ClawMutantControl(short itemNumber) + { + if (!CreatureActive(itemNumber)) + return; + + auto& item = g_Level.Items[itemNumber]; + auto& object = Objects[item.ObjectNumber]; + auto& creature = *GetCreatureInfo(&item); + + short headingAngle = 0; + auto extraHeadRot = EulerAngles::Zero; + auto extraTorsoRot = EulerAngles::Zero; + + if (item.HitPoints <= 0) + { + if (item.Animation.ActiveState != CLAW_MUTANT_STATE_DEATH) + SetAnimation(&item, CLAW_MUTANT_ANIM_DEATH); + + int frameEnd = GetAnimData(item, CLAW_MUTANT_ANIM_DEATH).frameEnd; + if (item.Animation.FrameNumber >= frameEnd) + CreatureDie(itemNumber, true); + } + else + { + if (item.AIBits) + GetAITarget(&creature); + + AI_INFO ai; + CreatureAIInfo(&item, &ai); + GetCreatureMood(&item, &ai, ai.zoneNumber == ai.enemyZone ? true : false); + CreatureMood(&item, &ai, ai.zoneNumber == ai.enemyZone ? true : false); + + headingAngle = CreatureTurn(&item, creature.MaxTurn); + bool canShoot = (Targetable(&item, &ai) && + ((ai.distance > CLAW_MUTANT_PLASMA_ATTACK_RANGE && !item.ItemFlags[0]) || ai.zoneNumber != ai.enemyZone)); + + switch (item.Animation.ActiveState) + { + case CLAW_MUTANT_STATE_IDLE: + item.ItemFlags[5] = 0; + item.ItemFlags[6] = 0; + creature.MaxTurn = 0; + + if (item.AIBits & GUARD) + { + extraHeadRot.y = AIGuard(&creature); + item.Animation.TargetState = CLAW_MUTANT_STATE_IDLE; + break; + } + else if (item.AIBits & PATROL1) + { + extraHeadRot.y = 0; + item.Animation.TargetState = CLAW_MUTANT_STATE_WALK; + } + else if (creature.Mood == MoodType::Escape) + { + item.Animation.TargetState = CLAW_MUTANT_STATE_RUN; + } + else if (ai.bite && ai.distance < CLAW_MUTANT_IDLE_CLAW_ATTACK_RANGE) + { + extraTorsoRot.x = ai.xAngle; + extraTorsoRot.y = ai.angle; + + if (ai.angle < 0) + { + item.Animation.TargetState = CLAW_MUTANT_STATE_IDLE_CLAW_ATTACK_LEFT; + } + else + { + item.Animation.TargetState = CLAW_MUTANT_STATE_IDLE_CLAW_ATTACK_RIGHT; + } + } + else if (ai.bite && ai.distance < CLAW_MUTANT_IDLE_DUAL_CLAW_ATTACK_RANGE) + { + extraTorsoRot.x = ai.xAngle; + extraTorsoRot.y = ai.angle; + item.Animation.TargetState = CLAW_MUTANT_STATE_IDLE_DUAL_CLAW_ATTACK; + } + else if (canShoot) + { + item.Animation.TargetState = CLAW_MUTANT_STATE_PLASMA_ATTACK; + } + else if (creature.Mood == MoodType::Bored) + { + if (Random::TestProbability(CLAW_MUTANT_WALK_CHANCE)) + item.Animation.TargetState = CLAW_MUTANT_STATE_WALK; + } + else if (item.Animation.RequiredState != NO_STATE) + { + item.Animation.TargetState = item.Animation.RequiredState; + } + else + { + item.Animation.TargetState = CLAW_MUTANT_STATE_RUN; + } + + break; + + case CLAW_MUTANT_STATE_WALK: + item.ItemFlags[5] = 0; + item.ItemFlags[6] = 0; + creature.MaxTurn = CLAW_MUTANT_WALK_TURN_RATE_MAX; + + if (ai.ahead) + extraHeadRot.y = ai.angle; + + if (item.AIBits & PATROL1) + { + item.Animation.TargetState = CLAW_MUTANT_STATE_WALK; + extraHeadRot.y = 0; + } + else if (ai.bite && ai.distance < CLAW_MUTANT_WALK_CLAW_ATTACK_RANGE) + { + if (ai.angle < 0) + { + item.Animation.TargetState = CLAW_MUTANT_STATE_WALK_CLAW_ATTACK_LEFT; + } + else + { + item.Animation.TargetState = CLAW_MUTANT_STATE_WALK_CLAW_ATTACK_RIGHT; + } + } + else if (canShoot) + { + item.Animation.TargetState = CLAW_MUTANT_STATE_IDLE; + } + else if (creature.Mood == MoodType::Escape || creature.Mood == MoodType::Attack) + { + item.Animation.TargetState = CLAW_MUTANT_STATE_RUN; + } + + break; + + case CLAW_MUTANT_STATE_RUN: + item.ItemFlags[5] = 0; + item.ItemFlags[6] = 0; + creature.MaxTurn = CLAW_MUTANT_RUN_TURN_RATE_MAX; + + if (ai.ahead) + extraHeadRot.y = ai.angle; + + if (item.AIBits & GUARD) + { + item.Animation.TargetState = CLAW_MUTANT_STATE_IDLE; + } + else if (creature.Mood == MoodType::Bored) + { + item.Animation.TargetState = CLAW_MUTANT_STATE_IDLE; + } + else if (creature.Flags != 0 && ai.ahead) + { + item.Animation.TargetState = CLAW_MUTANT_STATE_IDLE; + } + else if (ai.bite && ai.distance < CLAW_MUTANT_RUN_CLAW_ATTACK_RANGE) + { + if (creature.Enemy != nullptr && creature.Enemy->Animation.Velocity.z == 0.0f) + { + item.Animation.TargetState = CLAW_MUTANT_STATE_IDLE; + } + else + { + item.Animation.TargetState = CLAW_MUTANT_STATE_RUN_CLAW_ATTACK; + } + } + else if (canShoot) + { + item.Animation.TargetState = CLAW_MUTANT_STATE_IDLE; + } + + break; + + case CLAW_MUTANT_STATE_WALK_CLAW_ATTACK_LEFT: + case CLAW_MUTANT_STATE_WALK_CLAW_ATTACK_RIGHT: + case CLAW_MUTANT_STATE_RUN_CLAW_ATTACK: + case CLAW_MUTANT_STATE_IDLE_DUAL_CLAW_ATTACK: + case CLAW_MUTANT_STATE_IDLE_CLAW_ATTACK_LEFT: + case CLAW_MUTANT_STATE_IDLE_CLAW_ATTACK_RIGHT: + if (ai.ahead) + { + extraTorsoRot.x = ai.xAngle; + extraTorsoRot.y = ai.angle; + } + + DamageTargetWithClaw(item, *creature.Enemy); + break; + + case CLAW_MUTANT_STATE_PLASMA_ATTACK: + if (ai.ahead) + { + extraTorsoRot.x = ai.xAngle; + extraTorsoRot.y = ai.angle; + } + + if (item.Animation.FrameNumber == GetFrameIndex(&item, 0) && Random::TestProbability(1 / 4.0f) == 0) + item.ItemFlags[0] = 1; + + if (item.Animation.FrameNumber < GetFrameIndex(&item, 28)) + { + SpawnClawMutantPlasma(itemNumber); + } + else if (item.Animation.FrameNumber == GetFrameIndex(&item, 28)) + { + SpawnClawMutantPlasmaBall(item); + } + + SpawnMutantPlasmaLight(item); + break; + } + } + + CreatureJoint(&item, 0, extraTorsoRot.x); + CreatureJoint(&item, 1, extraTorsoRot.y); + CreatureJoint(&item, 2, extraHeadRot.y); + CreatureAnimation(itemNumber, headingAngle, 0); + } +} diff --git a/TombEngine/Objects/TR3/Entity/tr3_claw_mutant.h b/TombEngine/Objects/TR3/Entity/tr3_claw_mutant.h new file mode 100644 index 000000000..1e28bdcdf --- /dev/null +++ b/TombEngine/Objects/TR3/Entity/tr3_claw_mutant.h @@ -0,0 +1,7 @@ +#pragma once + +namespace TEN::Entities::Creatures::TR3 +{ + void ClawMutantControl(short itemNumber); + void SpawnClawMutantPlasmaFlameBall(int fxNumber, const Vector3& vel, const Vector3& offset, float life); +} diff --git a/TombEngine/Objects/TR3/tr3_objects.cpp b/TombEngine/Objects/TR3/tr3_objects.cpp index 5835225e0..e1b15fa84 100644 --- a/TombEngine/Objects/TR3/tr3_objects.cpp +++ b/TombEngine/Objects/TR3/tr3_objects.cpp @@ -17,11 +17,11 @@ #include "Objects/TR3/Entity/WaspMutant.h" // OK #include "Objects/TR3/Entity/tr3_tony.h" // OK #include "Objects/TR3/Entity/tr3_civvy.h" // OK +#include "Objects/TR3/Entity/tr3_claw_mutant.h" // OK #include "Objects/TR3/Entity/tr3_cobra.h" // OK #include "Objects/TR3/Entity/tr3_fish_emitter.h" // OK #include "Objects/TR3/Entity/tr3_flamethrower.h" // OK #include "Objects/TR3/Entity/tr3_monkey.h" // OK - #include "Objects/TR3/Entity/tr3_mp_gun.h" // OK #include "Objects/TR3/Entity/tr3_mp_stick.h" // OK #include "Objects/TR3/Entity/tr3_raptor.h" // OK @@ -381,6 +381,22 @@ static void StartEntity(ObjectInfo* obj) obj->SetBoneRotationFlags(1, ROT_X | ROT_Z); obj->SetupHitEffect(); } + + obj = &Objects[ID_CLAW_MUTANT]; + if (obj->loaded) + { + obj->initialise = InitialiseCreature; + obj->control = ClawMutantControl; + obj->collision = CreatureCollision; + obj->shadowType = ShadowMode::All; + obj->HitPoints = 130; + obj->intelligent = true; + obj->radius = 204; + obj->pivotLength = 0; + obj->SetBoneRotationFlags(0, ROT_X | ROT_Z); + obj->SetBoneRotationFlags(7, ROT_Y); + obj->SetupHitEffect(); + } } static void StartObject(ObjectInfo* obj) diff --git a/TombEngine/Objects/game_object_ids.h b/TombEngine/Objects/game_object_ids.h index 1d94d18fe..67f7b2b7d 100644 --- a/TombEngine/Objects/game_object_ids.h +++ b/TombEngine/Objects/game_object_ids.h @@ -228,8 +228,8 @@ enum GAME_OBJECT_ID : short ID_BOSS_SHIELD, ID_BOSS_EXPLOSION_SHOCKWAVE, ID_BOSS_EXPLOSION_RING, - - ID_WASP_MUTANT = 294, + ID_CLAW_MUTANT = 293, + ID_WASP_MUTANT, ID_SPRINGBOARD = 320, ID_ROLLING_SPINDLE, diff --git a/TombEngine/Renderer/Renderer11Frame.cpp b/TombEngine/Renderer/Renderer11Frame.cpp index 14600b779..5b0f677c7 100644 --- a/TombEngine/Renderer/Renderer11Frame.cpp +++ b/TombEngine/Renderer/Renderer11Frame.cpp @@ -756,8 +756,7 @@ namespace TEN::Renderer for (fxNum = r->fxNumber; fxNum != NO_ITEM; fxNum = EffectList[fxNum].nextFx) { FX_INFO *fx = &EffectList[fxNum]; - - if (fx->objectNumber < 0) + if (fx->objectNumber < 0 || fx->color.w <= 0) continue; ObjectInfo *obj = &Objects[fx->objectNumber]; diff --git a/TombEngine/Scripting/Internal/TEN/Objects/ObjectIDs.h b/TombEngine/Scripting/Internal/TEN/Objects/ObjectIDs.h index 060da0443..778342b8a 100644 --- a/TombEngine/Scripting/Internal/TEN/Objects/ObjectIDs.h +++ b/TombEngine/Scripting/Internal/TEN/Objects/ObjectIDs.h @@ -235,6 +235,7 @@ The following constants are inside ObjID. BOSS_SHIELD BOSS_EXPLOSION_SHOCKWAVE BOSS_EXPLOSION_RING + CLAW_MUTANT WASP_MUTANT SPRINGBOARD ROLLING_SPINDLE @@ -1406,6 +1407,7 @@ static const std::unordered_map kObjIDs { { "BOSS_SHIELD", ID_BOSS_SHIELD }, { "BOSS_EXPLOSION_SHOCKWAVE", ID_BOSS_EXPLOSION_SHOCKWAVE }, { "BOSS_EXPLOSION_RING", ID_BOSS_EXPLOSION_RING }, + { "CLAW_MUTANT", ID_CLAW_MUTANT }, { "WASP_MUTANT", ID_WASP_MUTANT }, { "SPRINGBOARD", ID_SPRINGBOARD }, { "ROLLING_SPINDLE", ID_ROLLING_SPINDLE }, diff --git a/TombEngine/TombEngine.vcxproj b/TombEngine/TombEngine.vcxproj index 53ed7b862..de9771721 100644 --- a/TombEngine/TombEngine.vcxproj +++ b/TombEngine/TombEngine.vcxproj @@ -321,6 +321,7 @@ CALL gen.bat + @@ -757,6 +758,7 @@ CALL gen.bat + diff --git a/TombEngine/TombEngine_vs2022.vcxproj b/TombEngine/TombEngine_vs2022.vcxproj index d58310b84..80ba40e10 100644 --- a/TombEngine/TombEngine_vs2022.vcxproj +++ b/TombEngine/TombEngine_vs2022.vcxproj @@ -329,6 +329,7 @@ CALL gen.bat + @@ -760,6 +761,7 @@ CALL gen.bat +