TombEngine/TombEngine/Objects/TR3/Entity/PunaBoss.cpp

541 lines
19 KiB
C++
Raw Normal View History

2022-12-27 11:12:37 +01:00
#include "framework.h"
2023-01-10 16:45:16 +11:00
#include "Objects/TR3/Entity/PunaBoss.h"
#include "Game/control/box.h"
#include "Game/control/los.h"
2022-12-28 12:19:41 +01:00
#include "Game/effects/effects.h"
2023-01-29 00:03:55 +11:00
#include "Game/effects/Electricity.h"
#include "Game/effects/item_fx.h"
#include "Game/Lara/lara_helpers.h"
#include "Game/misc.h"
#include "Game/Setup.h"
#include "Math/Math.h"
2023-01-10 18:06:26 +11:00
#include "Objects/Effects/Boss.h"
#include "Specific/level.h"
2022-12-28 12:19:41 +01:00
using namespace TEN::Effects::Boss;
2023-01-29 00:03:55 +11:00
using namespace TEN::Effects::Electricity;
using namespace TEN::Effects::Items;
2022-12-27 11:12:37 +01:00
namespace TEN::Entities::Creatures::TR3
{
constexpr auto PUNA_LIGHTNING_DAMAGE = 350;
constexpr auto PUNA_ATTACK_RANGE = BLOCK(20);
constexpr auto PUNA_ALERT_RANGE = BLOCK(2.5f);
constexpr auto PUNA_TURN_RATE_MAX = ANGLE(3.0f);
constexpr auto PUNA_CHAIR_TURN_RATE_MAX = ANGLE(7.0f);
constexpr auto PUNA_HEAD_X_ANGLE_MAX = ANGLE(20.0f);
constexpr auto PUNA_ADJUST_LIGHTNING_X_ANGLE = ANGLE(3.0f);
constexpr auto PUNA_EXPLOSION_NUM_MAX = 60;
constexpr auto PUNA_HEAD_ATTACK_NUM_MAX = 4;
constexpr auto PUNA_EFFECT_COLOR = Vector4(0.0f, 0.4f, 0.5f, 0.5f);
constexpr auto PUNA_EXPLOSION_MAIN_COLOR = Vector4(0.0f, 0.7f, 0.3f, 0.5f);
constexpr auto PUNA_EXPLOSION_SECOND_COLOR = Vector4(0.1f, 0.3f, 0.7f, 0.5f);
Enemy firing system refactor (#1069) * Refactored gunflash. - Fixed rendering of gunflash. - Improved gen.bat with some comment. - Now entity can shoot with 2 weapon (left and right) like lara. * Fix compiling error * Move muzzleFlash to creature struct - Removed BiteInfo and remplaced it by CreatureBiteInfo. - Fixed Mafia2 and Cybord muzzleflash pos. - Added dynamic light and smoke when entity shoot. (only for weapon) * Able to switch GUN_FLASH mesh. * Improve CreatureEffect/Effect2 Pass bite directly instead. * Fixed TR5 and TR4 biteInfo * Finished implementing gunflash for creatures. - Fix snowmobile gun crash. - Fix knifethrower not throwing knife. * Removed traps cpp/h. * Update Changes.txt * Update Changes.txt * Update effects.cpp * Fixed build error * Update setup.cpp - Removed old ObjectObjects() and TrapObjects() and move the code to TR5 objects. * Fix mpgun gunflash not playing when dead. * Fixed snowmobile gun not appearing correctly - Fixed crash if ID_GUN_FLASH2 not exist and creature like snomobile driver shoot. - Fix the snowmobile driver AI that where commented (was disabled to test the gunflash). * Improved AssignObjectAnimations * Added new lot for snowmobile gun - Allow the snowmobile to drop from 4 block ! * Fixed savegame crash * Change what @Lwmte reviewed. * Added todo for snowmobile gun. - reverted NO_JOINT in CreatureBiteInfo causing error when compiling. * Fix compile error caused by the develop merge * Minor formatting * Update people.cpp --------- Co-authored-by: Sezz <sezzary@outlook.com> Co-authored-by: Kubsy <80340234+Kubsy@users.noreply.github.com>
2023-04-28 17:52:35 +02:00
const auto PunaBossHeadBite = CreatureBiteInfo(Vector3i::Zero, 8);
const auto PunaBossHandBite = CreatureBiteInfo(Vector3i::Zero, 14);
enum PunaState
{
PUNA_STATE_IDLE = 0,
PUNA_STATE_HEAD_ATTACK = 1,
PUNA_STATE_HAND_ATTACK = 2,
PUNA_STATE_DEATH = 3
};
enum PunaAnim
{
PUNA_ANIM_IDLE = 0,
PUNA_ANIM_HEAD_ATTACK = 1,
PUNA_ANIM_HAND_ATTACK = 2,
PUNA_ANIM_DEATH = 3
};
enum class PunaAttackType
{
AwaitPlayer,
2023-01-10 23:03:35 +11:00
DeathLightning,
SummonLightning,
Wait // Used while an active lizard is nearby.
};
static short GetPunaHeadOrientToTarget(ItemInfo& item, const Vector3& target)
{
if (!item.TestFlags((int)BossItemFlags::Object, (short)BossFlagValue::Lizard))
return NO_ITEM;
Enemy firing system refactor (#1069) * Refactored gunflash. - Fixed rendering of gunflash. - Improved gen.bat with some comment. - Now entity can shoot with 2 weapon (left and right) like lara. * Fix compiling error * Move muzzleFlash to creature struct - Removed BiteInfo and remplaced it by CreatureBiteInfo. - Fixed Mafia2 and Cybord muzzleflash pos. - Added dynamic light and smoke when entity shoot. (only for weapon) * Able to switch GUN_FLASH mesh. * Improve CreatureEffect/Effect2 Pass bite directly instead. * Fixed TR5 and TR4 biteInfo * Finished implementing gunflash for creatures. - Fix snowmobile gun crash. - Fix knifethrower not throwing knife. * Removed traps cpp/h. * Update Changes.txt * Update Changes.txt * Update effects.cpp * Fixed build error * Update setup.cpp - Removed old ObjectObjects() and TrapObjects() and move the code to TR5 objects. * Fix mpgun gunflash not playing when dead. * Fixed snowmobile gun not appearing correctly - Fixed crash if ID_GUN_FLASH2 not exist and creature like snomobile driver shoot. - Fix the snowmobile driver AI that where commented (was disabled to test the gunflash). * Improved AssignObjectAnimations * Added new lot for snowmobile gun - Allow the snowmobile to drop from 4 block ! * Fixed savegame crash * Change what @Lwmte reviewed. * Added todo for snowmobile gun. - reverted NO_JOINT in CreatureBiteInfo causing error when compiling. * Fix compile error caused by the develop merge * Minor formatting * Update people.cpp --------- Co-authored-by: Sezz <sezzary@outlook.com> Co-authored-by: Kubsy <80340234+Kubsy@users.noreply.github.com>
2023-04-28 17:52:35 +02:00
auto pos = GetJointPosition(&item, PunaBossHeadBite).ToVector3();
auto orient = Geometry::GetOrientToPoint(pos, target);
return (orient.y - item.Pose.Orientation.y);
}
static std::vector<int> GetLizardEntityList(const ItemInfo& item)
{
auto entityList = std::vector<int>{};
for (auto& currentEntity : g_Level.Items)
{
if (currentEntity.ObjectNumber == ID_LIZARD &&
currentEntity.RoomNumber == item.RoomNumber &&
currentEntity.HitPoints > 0 &&
currentEntity.Status == ITEM_INVISIBLE &&
!(currentEntity.Flags & IFLAG_KILLED))
{
entityList.push_back(currentEntity.Index);
}
}
return entityList;
}
static Vector3 GetLizardTargetPosition(ItemInfo& item)
{
if (!item.TestFlagField((int)BossItemFlags::ItemNumber, NO_ITEM))
{
const auto& targetEntity = g_Level.Items[item.GetFlagField((int)BossItemFlags::ItemNumber)];
return targetEntity.Pose.Position.ToVector3();
}
// Failsafe.
const auto& creature = *GetCreatureInfo(&item);
return creature.Target.ToVector3();
}
static int GetLizardItemNumber(const ItemInfo& item)
{
if (!item.TestFlags((int)BossItemFlags::Object, (short)BossFlagValue::Lizard))
return NO_ITEM;
auto lizardList = GetLizardEntityList(item);
if (lizardList.empty())
return NO_ITEM;
if (lizardList.size() == 1)
return lizardList[0];
else
return lizardList[Random::GenerateInt(0, (int)lizardList.size() - 1)];
}
static bool IsLizardActiveNearby(const ItemInfo& item, bool isInitializing = false)
{
for (auto& currentEntity : g_Level.Items)
{
// Check if the entity is a lizard.
if (currentEntity.ObjectNumber != ID_LIZARD)
continue;
// Check if the entity is in the same room as Puna.
if (currentEntity.RoomNumber != item.RoomNumber)
continue;
// If the entity is currently initializing, return early.
if (isInitializing)
return true;
// Check status of entity.
if (currentEntity.HitPoints > 0 &&
currentEntity.Status == ITEM_INVISIBLE &&
!(currentEntity.Flags & IFLAG_KILLED))
{
return true;
}
}
return false;
}
static void SpawnSummonSmoke(const Vector3& pos)
{
auto& smoke = *GetFreeParticle();
int scale = Random::GenerateInt(256, 384);
smoke.on = true;
smoke.spriteIndex = Objects[ID_DEFAULT_SPRITES].meshIndex;
smoke.blendMode = BLEND_MODES::BLENDMODE_ADDITIVE;
smoke.x = pos.x + Random::GenerateInt(-64, 64);
smoke.y = pos.y - Random::GenerateInt(0, 32);
smoke.z = pos.z + Random::GenerateInt(-64, 64);
smoke.xVel = Random::GenerateInt(-128, 128);
smoke.yVel = Random::GenerateInt(-32, -16);
smoke.zVel = Random::GenerateInt(-128, 128);
smoke.sR = 16;
smoke.sG = 64;
smoke.sB = 0;
smoke.dR = 8;
smoke.dG = 32;
smoke.dB = 0;
smoke.colFadeSpeed = Random::GenerateInt(16, 24);
smoke.fadeToBlack = 64;
smoke.sLife =
smoke.life = Random::GenerateInt(96, 112);
smoke.extras = 0;
smoke.dynamic = -1;
smoke.friction = 0;
if (Random::TestProbability(1 / 2.0f))
{
smoke.rotAng = Random::GenerateAngle();
2023-01-22 14:49:15 +11:00
smoke.rotAdd = Random::GenerateAngle(ANGLE(-0.06f), ANGLE(0.06f));
smoke.flags = SP_SCALE | SP_DEF | SP_ROTATE | SP_EXPDEF | SP_WIND;
}
else
{
smoke.flags = SP_SCALE | SP_DEF | SP_EXPDEF | SP_WIND;
}
smoke.scalar = 3;
smoke.gravity = Random::GenerateInt(-16, -8);
smoke.maxYvel = Random::GenerateInt(-12, -8);
smoke.size =
smoke.sSize = scale / 2;
smoke.dSize = scale;
}
static void SpawnLizard(ItemInfo& item)
{
if (!item.TestFlagField((int)BossItemFlags::ItemNumber, NO_ITEM))
{
auto itemNumber = item.GetFlagField((int)BossItemFlags::ItemNumber);
auto& currentItem = g_Level.Items[itemNumber];
for (int i = 0; i < 20; i++)
SpawnSummonSmoke(currentItem.Pose.Position.ToVector3());
AddActiveItem(itemNumber);
currentItem.ItemFlags[0] = 1; // Flag 1 = spawned lizard.
item.SetFlagField((int)BossItemFlags::AttackType, (int)PunaAttackType::Wait);
}
}
Enemy firing system refactor (#1069) * Refactored gunflash. - Fixed rendering of gunflash. - Improved gen.bat with some comment. - Now entity can shoot with 2 weapon (left and right) like lara. * Fix compiling error * Move muzzleFlash to creature struct - Removed BiteInfo and remplaced it by CreatureBiteInfo. - Fixed Mafia2 and Cybord muzzleflash pos. - Added dynamic light and smoke when entity shoot. (only for weapon) * Able to switch GUN_FLASH mesh. * Improve CreatureEffect/Effect2 Pass bite directly instead. * Fixed TR5 and TR4 biteInfo * Finished implementing gunflash for creatures. - Fix snowmobile gun crash. - Fix knifethrower not throwing knife. * Removed traps cpp/h. * Update Changes.txt * Update Changes.txt * Update effects.cpp * Fixed build error * Update setup.cpp - Removed old ObjectObjects() and TrapObjects() and move the code to TR5 objects. * Fix mpgun gunflash not playing when dead. * Fixed snowmobile gun not appearing correctly - Fixed crash if ID_GUN_FLASH2 not exist and creature like snomobile driver shoot. - Fix the snowmobile driver AI that where commented (was disabled to test the gunflash). * Improved AssignObjectAnimations * Added new lot for snowmobile gun - Allow the snowmobile to drop from 4 block ! * Fixed savegame crash * Change what @Lwmte reviewed. * Added todo for snowmobile gun. - reverted NO_JOINT in CreatureBiteInfo causing error when compiling. * Fix compile error caused by the develop merge * Minor formatting * Update people.cpp --------- Co-authored-by: Sezz <sezzary@outlook.com> Co-authored-by: Kubsy <80340234+Kubsy@users.noreply.github.com>
2023-04-28 17:52:35 +02:00
static void SpawnPunaLightning(ItemInfo& item, const Vector3& pos, const CreatureBiteInfo& bite, bool isSummon)
{
const auto& creature = *GetCreatureInfo(&item);
Enemy firing system refactor (#1069) * Refactored gunflash. - Fixed rendering of gunflash. - Improved gen.bat with some comment. - Now entity can shoot with 2 weapon (left and right) like lara. * Fix compiling error * Move muzzleFlash to creature struct - Removed BiteInfo and remplaced it by CreatureBiteInfo. - Fixed Mafia2 and Cybord muzzleflash pos. - Added dynamic light and smoke when entity shoot. (only for weapon) * Able to switch GUN_FLASH mesh. * Improve CreatureEffect/Effect2 Pass bite directly instead. * Fixed TR5 and TR4 biteInfo * Finished implementing gunflash for creatures. - Fix snowmobile gun crash. - Fix knifethrower not throwing knife. * Removed traps cpp/h. * Update Changes.txt * Update Changes.txt * Update effects.cpp * Fixed build error * Update setup.cpp - Removed old ObjectObjects() and TrapObjects() and move the code to TR5 objects. * Fix mpgun gunflash not playing when dead. * Fixed snowmobile gun not appearing correctly - Fixed crash if ID_GUN_FLASH2 not exist and creature like snomobile driver shoot. - Fix the snowmobile driver AI that where commented (was disabled to test the gunflash). * Improved AssignObjectAnimations * Added new lot for snowmobile gun - Allow the snowmobile to drop from 4 block ! * Fixed savegame crash * Change what @Lwmte reviewed. * Added todo for snowmobile gun. - reverted NO_JOINT in CreatureBiteInfo causing error when compiling. * Fix compile error caused by the develop merge * Minor formatting * Update people.cpp --------- Co-authored-by: Sezz <sezzary@outlook.com> Co-authored-by: Kubsy <80340234+Kubsy@users.noreply.github.com>
2023-04-28 17:52:35 +02:00
auto origin = GameVector(GetJointPosition(&item, bite), item.RoomNumber);
if (isSummon)
{
auto target = GameVector(pos, item.RoomNumber);
2023-01-29 00:21:31 +11:00
SpawnElectricity(origin.ToVector3(), target.ToVector3(), 1, 0, 255, 180, 30, (int)(int)(int)ElectricityFlags::ThinIn | (int)ElectricityFlags::Spline | (int)ElectricityFlags::MoveEnd, 8, 12);
SpawnElectricity(origin.ToVector3(), target.ToVector3(), 1, 180, 255, 0, 30, (int)(int)(int)ElectricityFlags::ThinIn | (int)ElectricityFlags::Spline | (int)ElectricityFlags::MoveEnd, 3, 12);
SpawnElectricity(origin.ToVector3(), target.ToVector3(), Random::GenerateInt(25, 50), 100, 200, 200, 30, (int)(int)(int)ElectricityFlags::ThinIn | (int)(int)ElectricityFlags::ThinOut, 4, 12);
SpawnElectricity(origin.ToVector3(), target.ToVector3(), Random::GenerateInt(25, 50), 100, 250, 255, 30, (int)(int)(int)ElectricityFlags::ThinIn | (int)(int)ElectricityFlags::ThinOut, 2, 12);
TriggerDynamicLight(origin.x, origin.y, origin.z, 20, 0, 255, 0);
SpawnLizard(item);
}
else
{
auto target = GameVector(Geometry::TranslatePoint(origin.ToVector3(), pos - origin.ToVector3(), PUNA_ATTACK_RANGE), creature.Enemy->RoomNumber);
auto origin1 = GameVector(Geometry::TranslatePoint(origin.ToVector3(), pos - origin.ToVector3(), PUNA_ATTACK_RANGE / 4), creature.Enemy->RoomNumber);
auto origin2 = GameVector(Geometry::TranslatePoint(origin1.ToVector3(), pos - origin1.ToVector3(), PUNA_ATTACK_RANGE / 4), creature.Enemy->RoomNumber);
auto target2 = GameVector(Geometry::TranslatePoint(origin.ToVector3(), pos - origin.ToVector3(), PUNA_ATTACK_RANGE / 6), creature.Enemy->RoomNumber);
auto target3 = GameVector(Geometry::TranslatePoint(origin1.ToVector3(), pos - origin1.ToVector3(), PUNA_ATTACK_RANGE / 10), creature.Enemy->RoomNumber);
2023-01-29 00:21:31 +11:00
SpawnElectricity(origin.ToVector3(), target2.ToVector3(), Random::GenerateInt(15, 40), 20, 160, 160, 20, (int)(int)ElectricityFlags::ThinOut | (int)(int)(int)ElectricityFlags::ThinIn, 4, 6);
SpawnElectricity(origin.ToVector3(), target2.ToVector3(), Random::GenerateInt(25, 35), 20, 160, 160, 20, (int)(int)ElectricityFlags::ThinOut | (int)(int)(int)ElectricityFlags::ThinIn, 2, 7);
2023-01-29 00:21:31 +11:00
SpawnElectricity(target2.ToVector3(), origin1.ToVector3(), Random::GenerateInt(15, 40), 20, 160, 160, 20, (int)(int)ElectricityFlags::ThinOut | (int)(int)(int)ElectricityFlags::ThinIn, 4, 6);
SpawnElectricity(target2.ToVector3(), origin1.ToVector3(), Random::GenerateInt(25, 35), 20, 160, 160, 20, (int)(int)ElectricityFlags::ThinOut | (int)(int)(int)ElectricityFlags::ThinIn, 2, 7);
2023-01-29 00:21:31 +11:00
SpawnElectricity(origin1.ToVector3(), target3.ToVector3(), Random::GenerateInt(15, 40), 20, 160, 160, 20, (int)(int)ElectricityFlags::ThinOut | (int)(int)(int)ElectricityFlags::ThinIn, 4, 9);
SpawnElectricity(origin1.ToVector3(), target3.ToVector3(), Random::GenerateInt(25, 35), 20, 160, 160, 20, (int)(int)ElectricityFlags::ThinOut | (int)(int)(int)ElectricityFlags::ThinIn, 2, 10);
2023-01-29 00:21:31 +11:00
SpawnElectricity(origin2.ToVector3(), target3.ToVector3(), Random::GenerateInt(15, 40), 20, 160, 160, 16, (int)(int)ElectricityFlags::ThinOut | (int)(int)(int)ElectricityFlags::ThinIn, 4, 7);
SpawnElectricity(origin2.ToVector3(), target3.ToVector3(), Random::GenerateInt(25, 35), 20, 160, 160, 16, (int)(int)ElectricityFlags::ThinOut | (int)(int)(int)ElectricityFlags::ThinIn, 2, 8);
2023-01-29 00:21:31 +11:00
SpawnElectricity(origin.ToVector3(), target.ToVector3(), 1, 20, 160, 160, 30, (int)(int)(int)ElectricityFlags::ThinIn | (int)ElectricityFlags::Spline | (int)ElectricityFlags::MoveEnd, 12, 12);
SpawnElectricity(origin.ToVector3(), target.ToVector3(), 1, 80, 160, 160, 30, (int)(int)(int)ElectricityFlags::ThinIn | (int)ElectricityFlags::Spline | (int)ElectricityFlags::MoveEnd, 5, 12);
TriggerDynamicLight(origin.x, origin.y, origin.z, 20, 0, 255, 255);
auto hitPos = Vector3i::Zero;
MESH_INFO* mesh = nullptr;
if (ObjectOnLOS2(&origin, &target, &hitPos, &mesh, ID_LARA) == GetLaraInfo(creature.Enemy)->ItemNumber)
{
if (creature.Enemy->HitPoints <= PUNA_LIGHTNING_DAMAGE)
{
ItemElectricBurn(creature.Enemy);
DoDamage(creature.Enemy, PUNA_LIGHTNING_DAMAGE);
}
else
{
DoDamage(creature.Enemy, PUNA_LIGHTNING_DAMAGE);
}
}
}
}
void InitializePuna(short itemNumber)
{
auto& item = g_Level.Items[itemNumber];
InitializeCreature(itemNumber);
SetAnimation(&item, PUNA_ANIM_IDLE);
CheckForRequiredObjects(item);
// Save Puna's default angle. It will be used while waiting (i.e. an active lizard is nearby).
// NOTE: Since Puna is oriented to face away from the player, add 180 degrees.
2023-01-13 08:57:36 +01:00
item.SetFlagField((int)BossItemFlags::Rotation, item.Pose.Orientation.y + ANGLE(180.0f));
2023-01-13 21:05:46 +11:00
item.SetFlagField((int)BossItemFlags::AttackType, (int)PunaAttackType::AwaitPlayer); // Normal behaviour at start.
item.SetFlagField((int)BossItemFlags::ShieldIsEnabled, 1); // Activated at start.
2023-01-13 08:57:36 +01:00
item.SetFlagField((int)BossItemFlags::AttackCount, 0);
item.SetFlagField((int)BossItemFlags::DeathCount, 0);
item.SetFlagField((int)BossItemFlags::ItemNumber, NO_ITEM);
item.SetFlagField((int)BossItemFlags::ExplodeCount, 0);
// If there is no lizard nearby, remove the lizard flag.
if (!IsLizardActiveNearby(item, true))
2023-01-13 08:57:36 +01:00
item.ClearFlags((int)BossItemFlags::Object, (short)BossFlagValue::Lizard);
}
void PunaControl(short itemNumber)
{
if (!CreatureActive(itemNumber))
return;
auto& item = g_Level.Items[itemNumber];
auto& object = Objects[item.ObjectNumber];
auto& creature = *GetCreatureInfo(&item);
static auto targetPos = Vector3i::Zero;
auto headOrient = EulerAngles::Zero;
short headingAngle = 0;
short prevYOrient = 0;
bool hasTurned = false;
2023-01-10 17:29:33 +11:00
bool isLizardActiveNearby = IsLizardActiveNearby(item);
if (item.HitPoints <= 0)
{
if (item.Animation.ActiveState != PUNA_STATE_DEATH)
{
SetAnimation(&item, PUNA_ANIM_DEATH);
SoundEffect(SFX_TR3_PUNA_BOSS_DEATH, &item.Pose);
2023-01-13 08:57:36 +01:00
item.ItemFlags[(int)BossItemFlags::DeathCount] = 1;
creature.MaxTurn = 0;
}
int frameEnd = GetAnimData(object, PUNA_ANIM_DEATH).frameEnd;
if (item.Animation.FrameNumber >= frameEnd)
{
// Avoid having the object stop working.
item.Animation.FrameNumber = frameEnd;
item.MeshBits.ClearAll();
2023-01-13 08:57:36 +01:00
if (item.GetFlagField((int)BossItemFlags::ExplodeCount) < PUNA_EXPLOSION_NUM_MAX)
item.ItemFlags[(int)BossItemFlags::ExplodeCount]++;
2023-01-22 20:19:42 +11:00
// Do explosion effect.
ExplodeBoss(itemNumber, item, PUNA_EXPLOSION_NUM_MAX, PUNA_EFFECT_COLOR, PUNA_EXPLOSION_MAIN_COLOR, PUNA_EXPLOSION_SECOND_COLOR);
return;
}
else
{
2023-01-13 08:57:36 +01:00
auto deathCount = item.GetFlagField((int)BossItemFlags::DeathCount);
item.Pose.Orientation.z = (Random::GenerateInt() % deathCount) - (item.ItemFlags[(int)BossItemFlags::DeathCount] >> 1);
if (deathCount < 2048)
2023-01-13 08:57:36 +01:00
item.ItemFlags[(int)BossItemFlags::DeathCount] += 32;
}
}
else
{
prevYOrient = item.Pose.Orientation.y;
AI_INFO ai;
CreatureAIInfo(&item, &ai);
if (ai.ahead)
{
headOrient.x = ai.xAngle;
headOrient.y = ai.angle;
}
2023-01-19 16:43:17 +01:00
if (item.TestFlagField((int)BossItemFlags::AttackType, (int)PunaAttackType::AwaitPlayer) && creature.Enemy != nullptr)
{
float distance = Vector3i::Distance(creature.Enemy->Pose.Position, item.Pose.Position);
if (distance <= BLOCK(2.5f))
2023-01-19 16:43:17 +01:00
item.SetFlagField((int)BossItemFlags::AttackType, (int)PunaAttackType::DeathLightning);
// Rotate the object on puna boss chair.
creature.JointRotation[0] += PUNA_CHAIR_TURN_RATE_MAX;
return;
}
// Get target.
2023-01-19 16:43:17 +01:00
if (item.TestFlagField((int)BossItemFlags::AttackType, (int)PunaAttackType::DeathLightning))
{
creature.Target = creature.Enemy->Pose.Position;
}
2023-01-13 08:57:36 +01:00
else if (item.TestFlags((int)BossItemFlags::Object, (short)BossFlagValue::Lizard) &&
item.TestFlagField((int)BossItemFlags::AttackType, (int)PunaAttackType::SummonLightning) &&
item.TestFlagField((int)BossItemFlags::ItemNumber, NO_ITEM) &&
!item.TestFlagField((int)BossItemFlags::AttackType, (int)PunaAttackType::Wait) && isLizardActiveNearby)
{
// Get random lizard item number.
2023-01-13 08:57:36 +01:00
item.SetFlagField((int)BossItemFlags::ItemNumber, (short)GetLizardItemNumber(item));
creature.Target = GetLizardTargetPosition(item);
}
2023-01-13 08:57:36 +01:00
else if (item.TestFlags((int)BossItemFlags::Object, (short)BossFlagValue::Lizard) &&
item.TestFlagField((int)BossItemFlags::AttackType, (int)PunaAttackType::Wait) &&
!item.TestFlagField((int)BossItemFlags::ItemNumber, NO_ITEM))
{
// Rotate to idle position while player fights lizard.
2023-01-13 08:57:36 +01:00
auto targetOrient = EulerAngles(item.Pose.Orientation.x, item.GetFlagField((int)BossItemFlags::Rotation), item.Pose.Orientation.z);
item.Pose.Orientation.InterpolateConstant(targetOrient, ANGLE(3.0f));
// Check if target is dead.
2023-01-13 08:57:36 +01:00
auto& summonItem = g_Level.Items[item.GetFlagField((int)BossItemFlags::ItemNumber)];
if (summonItem.HitPoints <= 0)
{
// Reset the attack type, attack count, itemNumber, and restart the sequence.
2023-01-13 08:57:36 +01:00
item.SetFlagField((int)BossItemFlags::AttackType, (int)PunaAttackType::DeathLightning);
item.SetFlagField((int)BossItemFlags::AttackCount, 0);
item.SetFlagField((int)BossItemFlags::ItemNumber, NO_ITEM);
}
}
short headYOrient = GetPunaHeadOrientToTarget(item, creature.Target.ToVector3());
headingAngle = CreatureTurn(&item, creature.MaxTurn);
switch (item.Animation.ActiveState)
{
case PUNA_STATE_IDLE:
creature.MaxTurn = PUNA_TURN_RATE_MAX;
2023-01-13 08:57:36 +01:00
item.SetFlagField((int)BossItemFlags::ShieldIsEnabled, 1);
if ((item.Animation.TargetState != PUNA_STATE_HAND_ATTACK && item.Animation.TargetState != PUNA_STATE_HEAD_ATTACK) &&
ai.angle > ANGLE(-1.0f) && ai.angle < ANGLE(1.0f) &&
creature.Enemy->HitPoints > 0 &&
2023-01-13 08:57:36 +01:00
item.GetFlagField((int)BossItemFlags::AttackCount) < PUNA_HEAD_ATTACK_NUM_MAX &&
!item.TestFlagField((int)BossItemFlags::AttackType, (int)PunaAttackType::SummonLightning) && !item.TestFlagField((int)BossItemFlags::AttackType, (int)PunaAttackType::Wait))
{
creature.MaxTurn = 0;
targetPos = creature.Target;
targetPos.y -= CLICK(2);
2023-01-13 08:57:36 +01:00
item.SetFlagField((int)BossItemFlags::AttackType, (int)PunaAttackType::DeathLightning);
if (Random::TestProbability(1 / 3.0f))
item.Animation.TargetState = PUNA_STATE_HEAD_ATTACK;
else
item.Animation.TargetState = PUNA_STATE_HAND_ATTACK;
2023-01-13 08:57:36 +01:00
if (item.TestFlags((int)BossItemFlags::Object, (short)BossFlagValue::Lizard) && isLizardActiveNearby)
item.ItemFlags[(int)BossItemFlags::AttackCount]++;
}
2023-01-13 08:57:36 +01:00
else if (item.ItemFlags[(int)BossItemFlags::AttackCount] >= PUNA_HEAD_ATTACK_NUM_MAX &&
creature.Enemy->HitPoints > 0 &&
2023-01-13 08:57:36 +01:00
item.ItemFlags[(int)BossItemFlags::AttackType] != (int)PunaAttackType::Wait)
{
2023-01-13 08:57:36 +01:00
item.SetFlagField((int)BossItemFlags::AttackType, (int)PunaAttackType::SummonLightning);
2023-01-13 08:57:36 +01:00
if (!item.TestFlagField((int)BossItemFlags::ItemNumber, NO_ITEM))
{
if (headYOrient > ANGLE(-1.0f) && headYOrient < ANGLE(1.0f))
{
targetPos = creature.Target;
targetPos.y -= CLICK(2);
item.Animation.TargetState = PUNA_STATE_HAND_ATTACK;
}
}
}
break;
case PUNA_STATE_HEAD_ATTACK:
2023-01-13 08:57:36 +01:00
item.SetFlagField((int)BossItemFlags::ShieldIsEnabled, 0);
creature.MaxTurn = 0;
if (item.Animation.FrameNumber == GetFrameIndex(&item, 14))
SpawnPunaLightning(item, targetPos.ToVector3(), PunaBossHeadBite, false);
break;
case PUNA_STATE_HAND_ATTACK:
2023-01-13 08:57:36 +01:00
item.SetFlagField((int)BossItemFlags::ShieldIsEnabled, 0);
creature.MaxTurn = 0;
if (item.Animation.FrameNumber == GetFrameIndex(&item, 30))
{
2023-01-13 08:57:36 +01:00
if (item.TestFlags((int)BossItemFlags::Object, (short)BossFlagValue::Lizard) &&
item.TestFlagField((int)BossItemFlags::AttackType, (int)PunaAttackType::SummonLightning) &&
!item.TestFlagField((int)BossItemFlags::ItemNumber, NO_ITEM) && isLizardActiveNearby)
{
SpawnPunaLightning(item, targetPos.ToVector3(), PunaBossHandBite, true);
}
else
{
SpawnPunaLightning(item, targetPos.ToVector3(), PunaBossHandBite, false);
}
}
break;
}
}
// Rotate chair.
creature.JointRotation[0] += PUNA_CHAIR_TURN_RATE_MAX;
CreatureJoint(&item, 1, headOrient.y);
CreatureJoint(&item, 2, headOrient.x, PUNA_HEAD_X_ANGLE_MAX);
CreatureAnimation(itemNumber, headingAngle, 0);
// Emit sound while chair is rotating.
if (prevYOrient != item.Pose.Orientation.y && !hasTurned)
{
hasTurned = true;
SoundEffect(SFX_TR3_PUNA_BOSS_TURN_CHAIR, &item.Pose);
}
else if (prevYOrient == item.Pose.Orientation.y)
{
hasTurned = false;
StopSoundEffect(SFX_TR3_PUNA_BOSS_CHAIR_2);
StopSoundEffect(SFX_TR3_PUNA_BOSS_TURN_CHAIR);
}
}
void PunaHit(ItemInfo& target, ItemInfo& source, std::optional<GameVector> pos, int damage, bool isExplosive, int jointIndex)
{
if (target.TestFlags((int)BossItemFlags::Object, (short)BossFlagValue::Shield) &&
target.TestFlagField((int)BossItemFlags::ShieldIsEnabled, 1))
{
auto color = Vector4(
0.0f,
Random::GenerateFloat(0.0f, 0.5f),
Random::GenerateFloat(0.0f, 0.5f),
Random::GenerateFloat(0.5f, 0.8f));
if (pos.has_value() && !isExplosive)
{
SpawnShieldAndRichochetSparks(target, pos->ToVector3(), color);
}
else if (isExplosive)
{
SpawnShield(target, color);
}
}
else
{
if (target.HitStatus)
SoundEffect(SFX_TR3_PUNA_BOSS_TAKE_HIT, &target.Pose);
if (pos.has_value())
DoBloodSplat(pos->x, pos->y, pos->z, 5, source.Pose.Orientation.y, pos->RoomNumber);
DoItemHit(&target, damage, isExplosive, false);
}
}
2022-12-27 11:12:37 +01:00
}