mirror of
https://github.com/TombEngine/TombEngine.git
synced 2025-05-03 10:17:59 +03:00
Merge branch 'master' into fix-TR5-Twogun
This commit is contained in:
commit
c844fe7569
50 changed files with 2020 additions and 569 deletions
|
@ -2,6 +2,7 @@ Version 1.0.6
|
||||||
=============
|
=============
|
||||||
|
|
||||||
* Fix major pathfinding bug which could have caused lots of issues with enemy behaviour.
|
* Fix major pathfinding bug which could have caused lots of issues with enemy behaviour.
|
||||||
|
* Fix potential random crashes due to incorrect rendering behaviour.
|
||||||
* Fix savegame crash for disabled enemies with partially set activation mask.
|
* Fix savegame crash for disabled enemies with partially set activation mask.
|
||||||
* Fix certain enemies not damaging Lara if binoculars or lasersight mode is active.
|
* Fix certain enemies not damaging Lara if binoculars or lasersight mode is active.
|
||||||
* Fix invisible Lara after starting a new game from title flyby with hidden Lara.
|
* Fix invisible Lara after starting a new game from title flyby with hidden Lara.
|
||||||
|
@ -10,13 +11,19 @@ Version 1.0.6
|
||||||
* Fix backholster weapons not updating their sound position together with player.
|
* Fix backholster weapons not updating their sound position together with player.
|
||||||
* Fix black screen bug when there was an obstacle between the fixed camera and the target.
|
* Fix black screen bug when there was an obstacle between the fixed camera and the target.
|
||||||
* Fix underwater caustics not appearing without visiting options menu beforehand.
|
* Fix underwater caustics not appearing without visiting options menu beforehand.
|
||||||
* Fix TR1 BIG_RAT which crashed the game when it was killed.
|
|
||||||
* Fix TR4 SKELETON spawn when used with OCB 3
|
|
||||||
* Fix TR4 SAS teleporting over the blocks he walks by.
|
|
||||||
* Fix enemy projectile effect colours.
|
* Fix enemy projectile effect colours.
|
||||||
|
* Fix TR1 rat which crashed the game when it was killed.
|
||||||
|
* Fix TR1 ape climbing.
|
||||||
|
* Fix TR2 small spider climbing and pathfinding.
|
||||||
|
* Fix TR4 skeleton spawn when used with OCB 3.
|
||||||
|
* Fix TR4 SAS teleporting over the blocks he walks by.
|
||||||
|
* Fix TR3 Shiva and TR4 baddy 2 not blocking bullets.
|
||||||
* Restore original volumetric explosion effects.
|
* Restore original volumetric explosion effects.
|
||||||
|
* Add TR3 lizard and Puna.
|
||||||
|
* Add TR3 boss effects in ID_BOSS_SHIELD and ID_BOSS_SHOCKWAVE_EXPLOSION slots.
|
||||||
* Add undead flag to TR4 sphinx to prevent it from becoming bugged after receiving a lot of damage.
|
* Add undead flag to TR4 sphinx to prevent it from becoming bugged after receiving a lot of damage.
|
||||||
* Add an option to activate Lua or node events from legacy triggers.
|
* Add an option to activate Lua or node events from legacy triggers.
|
||||||
|
* Add more warnings in logs to enemies which animation or required slot is missing.
|
||||||
* Antitriggering an enemy will now cause it to vanish and pause.
|
* Antitriggering an enemy will now cause it to vanish and pause.
|
||||||
* Re-triggering an enemy will cause it to reappear and unpause.
|
* Re-triggering an enemy will cause it to reappear and unpause.
|
||||||
* Lua Moveable functions Enable and Disable now correctly trigger and antitrigger the moveable.
|
* Lua Moveable functions Enable and Disable now correctly trigger and antitrigger the moveable.
|
||||||
|
|
|
@ -1049,29 +1049,31 @@ void LaraTargetInfo(ItemInfo* laraItem, const WeaponInfo& weaponInfo)
|
||||||
|
|
||||||
void HitTarget(ItemInfo* laraItem, ItemInfo* targetEntity, GameVector* hitPos, int damage, bool isExplosive)
|
void HitTarget(ItemInfo* laraItem, ItemInfo* targetEntity, GameVector* hitPos, int damage, bool isExplosive)
|
||||||
{
|
{
|
||||||
const auto& lara = *GetLaraInfo(laraItem);
|
|
||||||
const auto& object = Objects[targetEntity->ObjectNumber];
|
const auto& object = Objects[targetEntity->ObjectNumber];
|
||||||
|
|
||||||
targetEntity->HitStatus = true;
|
targetEntity->HitStatus = true;
|
||||||
|
|
||||||
if (targetEntity->IsCreature())
|
if (targetEntity->IsCreature())
|
||||||
GetCreatureInfo(targetEntity)->HurtByLara = true;
|
GetCreatureInfo(targetEntity)->HurtByLara = true;
|
||||||
|
|
||||||
if (hitPos != nullptr)
|
if (hitPos != nullptr)
|
||||||
{
|
{
|
||||||
int foundJointID = -1;
|
hitPos->RoomNumber = targetEntity->RoomNumber;
|
||||||
for (int jointID = 0; jointID < object.nmeshes; jointID++)
|
|
||||||
|
int foundJointIndex = -1;
|
||||||
|
for (int jointIndex = 0; jointIndex < object.nmeshes; jointIndex++)
|
||||||
{
|
{
|
||||||
auto pos = GetJointPosition(targetEntity, jointID);
|
const auto& mesh = g_Level.Meshes[object.meshIndex + jointIndex];
|
||||||
float distance = targetEntity->ItemFlags[7] != 0 ? (float)targetEntity->ItemFlags[7] : BLOCK(1 / 16.0f);
|
auto jointPos = GetJointPosition(targetEntity, jointIndex);
|
||||||
if (Vector3i::Distance(hitPos->ToVector3i(), pos) < distance)
|
|
||||||
|
float distance = Vector3::Distance(hitPos->ToVector3(), jointPos.ToVector3());
|
||||||
|
if (distance < mesh.sphere.Radius)
|
||||||
{
|
{
|
||||||
foundJointID = jointID;
|
foundJointIndex = jointIndex;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object.HitRoutine(*targetEntity, *laraItem, *hitPos, damage, isExplosive, foundJointID);
|
object.HitRoutine(*targetEntity, *laraItem, *hitPos, damage, isExplosive, foundJointIndex);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
|
@ -319,6 +319,11 @@ bool HasStateDispatch(ItemInfo* item, int targetState)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool TestAnimNumber(const ItemInfo& item, int animNumber)
|
||||||
|
{
|
||||||
|
return (item.Animation.AnimNumber == (Objects[item.ObjectNumber].animIndex + animNumber));
|
||||||
|
}
|
||||||
|
|
||||||
bool TestLastFrame(ItemInfo* item, int animNumber)
|
bool TestLastFrame(ItemInfo* item, int animNumber)
|
||||||
{
|
{
|
||||||
if (animNumber == NO_ANIM)
|
if (animNumber == NO_ANIM)
|
||||||
|
@ -328,10 +333,22 @@ bool TestLastFrame(ItemInfo* item, int animNumber)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
const auto& anim = g_Level.Anims[animNumber];
|
const auto& anim = g_Level.Anims[animNumber];
|
||||||
|
|
||||||
return (item->Animation.FrameNumber >= anim.frameEnd);
|
return (item->Animation.FrameNumber >= anim.frameEnd);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool TestAnimFrame(const ItemInfo& item, int frameStart)
|
||||||
|
{
|
||||||
|
const auto& anim = g_Level.Anims[item.Animation.AnimNumber];
|
||||||
|
return (item.Animation.FrameNumber == (anim.frameBase + frameStart));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TestAnimFrameRange(const ItemInfo& item, int frameStart, int frameEnd)
|
||||||
|
{
|
||||||
|
const auto& anim = g_Level.Anims[item.Animation.AnimNumber];
|
||||||
|
return (item.Animation.FrameNumber >= (anim.frameBase + frameStart) &&
|
||||||
|
item.Animation.FrameNumber <= (anim.frameBase + frameEnd));
|
||||||
|
}
|
||||||
|
|
||||||
void TranslateItem(ItemInfo* item, short headingAngle, float forward, float down, float right)
|
void TranslateItem(ItemInfo* item, short headingAngle, float forward, float down, float right)
|
||||||
{
|
{
|
||||||
item->Pose.Translate(headingAngle, forward, down, right);
|
item->Pose.Translate(headingAngle, forward, down, right);
|
||||||
|
|
|
@ -81,7 +81,10 @@ void AnimateLara(ItemInfo* item);
|
||||||
void AnimateItem(ItemInfo* item);
|
void AnimateItem(ItemInfo* item);
|
||||||
|
|
||||||
bool HasStateDispatch(ItemInfo* item, int targetState = NO_STATE);
|
bool HasStateDispatch(ItemInfo* item, int targetState = NO_STATE);
|
||||||
|
bool TestAnimNumber(const ItemInfo& item, int animNumber);
|
||||||
bool TestLastFrame(ItemInfo* item, int animNumber = NO_ANIM);
|
bool TestLastFrame(ItemInfo* item, int animNumber = NO_ANIM);
|
||||||
|
bool TestAnimFrame(const ItemInfo& item, int frameStart);
|
||||||
|
bool TestAnimFrameRange(const ItemInfo& item, int frameStart, int frameEnd);
|
||||||
|
|
||||||
void TranslateItem(ItemInfo* item, short headingAngle, float forward, float down = 0.0f, float right = 0.0f);
|
void TranslateItem(ItemInfo* item, short headingAngle, float forward, float down = 0.0f, float right = 0.0f);
|
||||||
void TranslateItem(ItemInfo* item, const EulerAngles& orient, float distance);
|
void TranslateItem(ItemInfo* item, const EulerAngles& orient, float distance);
|
||||||
|
|
|
@ -26,7 +26,7 @@ constexpr auto REACHED_GOAL_RADIUS = 640;
|
||||||
constexpr auto ATTACK_RANGE = SQUARE(SECTOR(3));
|
constexpr auto ATTACK_RANGE = SQUARE(SECTOR(3));
|
||||||
constexpr auto ESCAPE_CHANCE = 0x800;
|
constexpr auto ESCAPE_CHANCE = 0x800;
|
||||||
constexpr auto RECOVER_CHANCE = 0x100;
|
constexpr auto RECOVER_CHANCE = 0x100;
|
||||||
constexpr auto BIFF_AVOID_TURN = 1536;
|
constexpr auto BIFF_AVOID_TURN = ANGLE(11.25f);
|
||||||
constexpr auto FEELER_DISTANCE = CLICK(2);
|
constexpr auto FEELER_DISTANCE = CLICK(2);
|
||||||
constexpr auto FEELER_ANGLE = ANGLE(45.0f);
|
constexpr auto FEELER_ANGLE = ANGLE(45.0f);
|
||||||
constexpr auto CREATURE_AI_ROTATION_MAX = ANGLE(90.0f);
|
constexpr auto CREATURE_AI_ROTATION_MAX = ANGLE(90.0f);
|
||||||
|
@ -41,34 +41,6 @@ constexpr auto FRAME_PRIO_BASE = 4;
|
||||||
constexpr auto FRAME_PRIO_EXP = 1.5;
|
constexpr auto FRAME_PRIO_EXP = 1.5;
|
||||||
#endif // CREATURE_AI_PRIORITY_OPTIMIZATION
|
#endif // CREATURE_AI_PRIORITY_OPTIMIZATION
|
||||||
|
|
||||||
// TODO: Do it via Lua instead. -- TokyoSU 22.12.21
|
|
||||||
bool IsCreatureVaultAvailable(ItemInfo* item, int stepCount)
|
|
||||||
{
|
|
||||||
switch (stepCount)
|
|
||||||
{
|
|
||||||
case -4:
|
|
||||||
return (item->ObjectNumber != ID_SMALL_SPIDER);
|
|
||||||
|
|
||||||
case -3:
|
|
||||||
return (item->ObjectNumber != ID_CIVVY &&
|
|
||||||
item->ObjectNumber != ID_MP_WITH_STICK &&
|
|
||||||
item->ObjectNumber != ID_YETI &&
|
|
||||||
item->ObjectNumber != ID_APE &&
|
|
||||||
item->ObjectNumber != ID_SMALL_SPIDER);
|
|
||||||
|
|
||||||
case -2:
|
|
||||||
return (item->ObjectNumber != ID_BADDY1 &&
|
|
||||||
item->ObjectNumber != ID_BADDY2 &&
|
|
||||||
item->ObjectNumber != ID_CIVVY &&
|
|
||||||
item->ObjectNumber != ID_MP_WITH_STICK &&
|
|
||||||
item->ObjectNumber != ID_YETI &&
|
|
||||||
item->ObjectNumber != ID_APE &&
|
|
||||||
item->ObjectNumber != ID_SMALL_SPIDER);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void DrawBox(int boxIndex, Vector3 color)
|
void DrawBox(int boxIndex, Vector3 color)
|
||||||
{
|
{
|
||||||
if (boxIndex == NO_BOX)
|
if (boxIndex == NO_BOX)
|
||||||
|
@ -719,7 +691,7 @@ void CreatureFloat(short itemNumber)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void CreatureJoint(ItemInfo* item, short joint, short required)
|
void CreatureJoint(ItemInfo* item, short joint, short required, short maxAngle)
|
||||||
{
|
{
|
||||||
if (!item->IsCreature())
|
if (!item->IsCreature())
|
||||||
return;
|
return;
|
||||||
|
@ -733,10 +705,10 @@ void CreatureJoint(ItemInfo* item, short joint, short required)
|
||||||
change = ANGLE(-3.0f);
|
change = ANGLE(-3.0f);
|
||||||
|
|
||||||
creature->JointRotation[joint] += change;
|
creature->JointRotation[joint] += change;
|
||||||
if (creature->JointRotation[joint] > ANGLE(70.0f))
|
if (creature->JointRotation[joint] > maxAngle)
|
||||||
creature->JointRotation[joint] = ANGLE(70.0f);
|
creature->JointRotation[joint] = maxAngle;
|
||||||
else if (creature->JointRotation[joint] < -ANGLE(70.0f))
|
else if (creature->JointRotation[joint] < -maxAngle)
|
||||||
creature->JointRotation[joint] = -ANGLE(70.0f);
|
creature->JointRotation[joint] = -maxAngle;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CreatureTilt(ItemInfo* item, short angle)
|
void CreatureTilt(ItemInfo* item, short angle)
|
||||||
|
@ -925,21 +897,20 @@ bool ValidBox(ItemInfo* item, short zoneNumber, short boxNumber)
|
||||||
if (boxNumber == NO_BOX)
|
if (boxNumber == NO_BOX)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
auto* object = &Objects[item->ObjectNumber];
|
const auto& creature = *GetCreatureInfo(item);
|
||||||
auto* creature = GetCreatureInfo(item);
|
const auto& zone = g_Level.Zones[(int)creature.LOT.Zone][FlipStatus].data();
|
||||||
auto* zone = g_Level.Zones[(int)creature->LOT.Zone][FlipStatus].data();
|
|
||||||
|
|
||||||
if (creature->LOT.Fly == NO_FLYING && zone[boxNumber] != zoneNumber)
|
if (creature.LOT.Fly == NO_FLYING && zone[boxNumber] != zoneNumber)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
auto* box = &g_Level.Boxes[boxNumber];
|
const auto& box = g_Level.Boxes[boxNumber];
|
||||||
if (creature->LOT.BlockMask & box->flags)
|
if (creature.LOT.BlockMask & box.flags)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (item->Pose.Position.z > (box->left * SECTOR(1)) &&
|
if (item->Pose.Position.z > (box.left * BLOCK(1)) &&
|
||||||
item->Pose.Position.z < (box->right * SECTOR(1)) &&
|
item->Pose.Position.z < (box.right * BLOCK(1)) &&
|
||||||
item->Pose.Position.x > (box->top * SECTOR(1)) &&
|
item->Pose.Position.x > (box.top * BLOCK(1)) &&
|
||||||
item->Pose.Position.x < (box->bottom * SECTOR(1)))
|
item->Pose.Position.x < (box.bottom * BLOCK(1)))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -951,9 +922,10 @@ bool EscapeBox(ItemInfo* item, ItemInfo* enemy, int boxNumber)
|
||||||
{
|
{
|
||||||
if (boxNumber == NO_BOX)
|
if (boxNumber == NO_BOX)
|
||||||
return false;
|
return false;
|
||||||
auto* box = &g_Level.Boxes[boxNumber];
|
|
||||||
int x = (box->top + box->bottom) * SECTOR(1) / 2 - enemy->Pose.Position.x;
|
const auto& box = g_Level.Boxes[boxNumber];
|
||||||
int z = (box->left + box->right) * SECTOR(1) / 2 - enemy->Pose.Position.z;
|
int x = ((box.top + box.bottom) * BLOCK(0.5f)) - enemy->Pose.Position.x;
|
||||||
|
int z = ((box.left + box.right) * BLOCK(0.5f)) - enemy->Pose.Position.z;
|
||||||
|
|
||||||
if (x > -ESCAPE_DIST && x < ESCAPE_DIST &&
|
if (x > -ESCAPE_DIST && x < ESCAPE_DIST &&
|
||||||
z > -ESCAPE_DIST && z < ESCAPE_DIST)
|
z > -ESCAPE_DIST && z < ESCAPE_DIST)
|
||||||
|
@ -1114,8 +1086,8 @@ bool CreatureActive(short itemNumber)
|
||||||
if (!Objects[item->ObjectNumber].intelligent)
|
if (!Objects[item->ObjectNumber].intelligent)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// Object is already dead or body cleared.
|
// Object is already dead.
|
||||||
if (item->Flags & IFLAG_KILLED || item->Flags & IFLAG_CLEAR_BODY)
|
if (item->Flags & IFLAG_KILLED)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (item->Status == ITEM_INVISIBLE || !item->IsCreature())
|
if (item->Status == ITEM_INVISIBLE || !item->IsCreature())
|
||||||
|
@ -1180,6 +1152,36 @@ bool StalkBox(ItemInfo* item, ItemInfo* enemy, int boxNumber)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Do it via Lua instead. -- TokyoSU 22.12.21
|
||||||
|
bool IsCreatureVaultAvailable(ItemInfo* item, int stepCount)
|
||||||
|
{
|
||||||
|
switch (stepCount)
|
||||||
|
{
|
||||||
|
case -4:
|
||||||
|
return (item->ObjectNumber != ID_SMALL_SPIDER);
|
||||||
|
|
||||||
|
case -3:
|
||||||
|
return (item->ObjectNumber != ID_CIVVY &&
|
||||||
|
item->ObjectNumber != ID_MP_WITH_STICK &&
|
||||||
|
item->ObjectNumber != ID_YETI &&
|
||||||
|
item->ObjectNumber != ID_LIZARD &&
|
||||||
|
item->ObjectNumber != ID_APE &&
|
||||||
|
item->ObjectNumber != ID_SMALL_SPIDER);
|
||||||
|
|
||||||
|
case -2:
|
||||||
|
return (item->ObjectNumber != ID_BADDY1 &&
|
||||||
|
item->ObjectNumber != ID_BADDY2 &&
|
||||||
|
item->ObjectNumber != ID_CIVVY &&
|
||||||
|
item->ObjectNumber != ID_MP_WITH_STICK &&
|
||||||
|
item->ObjectNumber != ID_YETI &&
|
||||||
|
item->ObjectNumber != ID_LIZARD &&
|
||||||
|
item->ObjectNumber != ID_APE &&
|
||||||
|
item->ObjectNumber != ID_SMALL_SPIDER);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
int CreatureVault(short itemNumber, short angle, int vault, int shift)
|
int CreatureVault(short itemNumber, short angle, int vault, int shift)
|
||||||
{
|
{
|
||||||
auto* item = &g_Level.Items[itemNumber];
|
auto* item = &g_Level.Items[itemNumber];
|
||||||
|
@ -1192,8 +1194,7 @@ int CreatureVault(short itemNumber, short angle, int vault, int shift)
|
||||||
|
|
||||||
CreatureAnimation(itemNumber, angle, 0);
|
CreatureAnimation(itemNumber, angle, 0);
|
||||||
|
|
||||||
// FIXME: Add climb down animations for Von Croy and baddies?
|
if (item->Floor > (y + CLICK(4.5f)))
|
||||||
if (item->Floor > y + CLICK(4.5f))
|
|
||||||
{
|
{
|
||||||
vault = 0;
|
vault = 0;
|
||||||
}
|
}
|
||||||
|
@ -1439,9 +1440,7 @@ void FindAITargetObject(CreatureInfo* creature, short objectNumber)
|
||||||
|
|
||||||
aiItem->ObjectNumber = foundObject->objectNumber;
|
aiItem->ObjectNumber = foundObject->objectNumber;
|
||||||
aiItem->RoomNumber = foundObject->roomNumber;
|
aiItem->RoomNumber = foundObject->roomNumber;
|
||||||
aiItem->Pose.Position.x = foundObject->pos.Position.x;
|
aiItem->Pose.Position = foundObject->pos.Position;
|
||||||
aiItem->Pose.Position.y = foundObject->pos.Position.y;
|
|
||||||
aiItem->Pose.Position.z = foundObject->pos.Position.z;
|
|
||||||
aiItem->Pose.Orientation.y = foundObject->pos.Orientation.y;
|
aiItem->Pose.Orientation.y = foundObject->pos.Orientation.y;
|
||||||
aiItem->Flags = foundObject->flags;
|
aiItem->Flags = foundObject->flags;
|
||||||
aiItem->TriggerFlags = foundObject->triggerFlags;
|
aiItem->TriggerFlags = foundObject->triggerFlags;
|
||||||
|
@ -1462,7 +1461,7 @@ int TargetReachable(ItemInfo* item, ItemInfo* enemy)
|
||||||
auto& room = g_Level.Rooms[enemy->RoomNumber];
|
auto& room = g_Level.Rooms[enemy->RoomNumber];
|
||||||
auto* floor = GetSector(&room, enemy->Pose.Position.x - room.x, enemy->Pose.Position.z - room.z);
|
auto* floor = GetSector(&room, enemy->Pose.Position.x - room.x, enemy->Pose.Position.z - room.z);
|
||||||
|
|
||||||
// NEW: Only update enemy box number if it is actually reachable by enemy.
|
// NEW: Only update enemy box number if it is actually reachable by the enemy.
|
||||||
// This prevents enemies from running to the player and attacking nothing when they are hanging or shimmying. -- Lwmte, 27.06.22
|
// This prevents enemies from running to the player and attacking nothing when they are hanging or shimmying. -- Lwmte, 27.06.22
|
||||||
|
|
||||||
bool isReachable = false;
|
bool isReachable = false;
|
||||||
|
@ -1476,7 +1475,7 @@ int TargetReachable(ItemInfo* item, ItemInfo* enemy)
|
||||||
{
|
{
|
||||||
auto pointColl = GetCollision(floor, enemy->Pose.Position.x, enemy->Pose.Position.y, enemy->Pose.Position.z);
|
auto pointColl = GetCollision(floor, enemy->Pose.Position.x, enemy->Pose.Position.y, enemy->Pose.Position.z);
|
||||||
auto bounds = GameBoundingBox(item);
|
auto bounds = GameBoundingBox(item);
|
||||||
isReachable = ((abs(enemy->Pose.Position.y - pointColl.Position.Floor)) < bounds.GetHeight());
|
isReachable = abs(enemy->Pose.Position.y - pointColl.Position.Floor) < bounds.GetHeight();
|
||||||
}
|
}
|
||||||
|
|
||||||
return (isReachable ? floor->Box : item->BoxNumber);
|
return (isReachable ? floor->Box : item->BoxNumber);
|
||||||
|
|
|
@ -170,7 +170,7 @@ short CreatureEffect2(ItemInfo* item, BiteInfo bite, short velocity, short angle
|
||||||
short CreatureEffect(ItemInfo* item, BiteInfo bite, std::function<CreatureEffectFunction> func);
|
short CreatureEffect(ItemInfo* item, BiteInfo bite, std::function<CreatureEffectFunction> func);
|
||||||
void CreatureUnderwater(ItemInfo* item, int depth);
|
void CreatureUnderwater(ItemInfo* item, int depth);
|
||||||
void CreatureFloat(short itemNumber);
|
void CreatureFloat(short itemNumber);
|
||||||
void CreatureJoint(ItemInfo* item, short joint, short required);
|
void CreatureJoint(ItemInfo* item, short joint, short required, short maxAngle = ANGLE(70.0f));
|
||||||
void CreatureTilt(ItemInfo* item, short angle);
|
void CreatureTilt(ItemInfo* item, short angle);
|
||||||
short CreatureTurn(ItemInfo* item, short maxTurn);
|
short CreatureTurn(ItemInfo* item, short maxTurn);
|
||||||
void CreatureDie(short itemNumber, bool explode);
|
void CreatureDie(short itemNumber, bool explode);
|
||||||
|
|
|
@ -244,18 +244,18 @@ void UpdateSparks()
|
||||||
if (spark->sLife - spark->life == spark->extras >> 3 &&
|
if (spark->sLife - spark->life == spark->extras >> 3 &&
|
||||||
spark->extras & 7)
|
spark->extras & 7)
|
||||||
{
|
{
|
||||||
int unk;
|
int explosionType;
|
||||||
if (spark->flags & SP_UNDERWEXP)
|
if (spark->flags & SP_UNDERWEXP)
|
||||||
{
|
{
|
||||||
unk = 1;
|
explosionType = 1;
|
||||||
}
|
}
|
||||||
else if (spark->flags & SP_PLASMAEXP)
|
else if (spark->flags & SP_PLASMAEXP)
|
||||||
{
|
{
|
||||||
unk = 2;
|
explosionType = 2;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
unk = 0;
|
explosionType = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int j = 0; j < (spark->extras & 7); j++)
|
for (int j = 0; j < (spark->extras & 7); j++)
|
||||||
|
@ -265,13 +265,13 @@ void UpdateSparks()
|
||||||
spark->z,
|
spark->z,
|
||||||
(spark->extras & 7) - 1,
|
(spark->extras & 7) - 1,
|
||||||
spark->dynamic,
|
spark->dynamic,
|
||||||
unk,
|
explosionType,
|
||||||
(spark->extras & 7));
|
spark->roomNumber);
|
||||||
|
|
||||||
spark->dynamic = -1;
|
spark->dynamic = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (unk == 1)
|
if (explosionType == 1)
|
||||||
{
|
{
|
||||||
TriggerExplosionBubble(
|
TriggerExplosionBubble(
|
||||||
spark->x,
|
spark->x,
|
||||||
|
|
|
@ -14,6 +14,7 @@ using namespace TEN::Math::Random;
|
||||||
namespace TEN::Effects::Spark
|
namespace TEN::Effects::Spark
|
||||||
{
|
{
|
||||||
std::array<SparkParticle, 128> SparkParticles;
|
std::array<SparkParticle, 128> SparkParticles;
|
||||||
|
|
||||||
void UpdateSparkParticles()
|
void UpdateSparkParticles()
|
||||||
{
|
{
|
||||||
for (int i = 0; i < SparkParticles.size(); i++)
|
for (int i = 0; i < SparkParticles.size(); i++)
|
||||||
|
@ -68,7 +69,7 @@ namespace TEN::Effects::Spark
|
||||||
s.active = true;
|
s.active = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void TriggerRicochetSpark(const GameVector& pos, short angle, int count)
|
void TriggerRicochetSpark(const GameVector& pos, short angle, int count, const Vector4& colorStart)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < count; i++)
|
for (int i = 0; i < count; i++)
|
||||||
{
|
{
|
||||||
|
@ -87,7 +88,7 @@ namespace TEN::Effects::Spark
|
||||||
v += Vector3(GenerateFloat(-64, 64), GenerateFloat(-64, 64), GenerateFloat(-64, 64));
|
v += Vector3(GenerateFloat(-64, 64), GenerateFloat(-64, 64), GenerateFloat(-64, 64));
|
||||||
v.Normalize(v);
|
v.Normalize(v);
|
||||||
s.velocity = v * GenerateFloat(17, 24);
|
s.velocity = v * GenerateFloat(17, 24);
|
||||||
s.sourceColor = Vector4(1, 0.8f, 0.2f, 1) * 3;
|
s.sourceColor = colorStart;
|
||||||
s.destinationColor = Vector4(0, 0, 0, 0);
|
s.destinationColor = Vector4(0, 0, 0, 0);
|
||||||
s.active = true;
|
s.active = true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,8 @@
|
||||||
|
|
||||||
namespace TEN::Effects::Spark
|
namespace TEN::Effects::Spark
|
||||||
{
|
{
|
||||||
|
constexpr auto SPARK_RICOCHET_COLOR_DEFAULT = Vector4(1.0f, 0.8f, 0.2f, 1.0f);
|
||||||
|
|
||||||
struct SparkParticle
|
struct SparkParticle
|
||||||
{
|
{
|
||||||
Vector3 pos;
|
Vector3 pos;
|
||||||
|
@ -26,7 +28,7 @@ namespace TEN::Effects::Spark
|
||||||
void UpdateSparkParticles();
|
void UpdateSparkParticles();
|
||||||
SparkParticle& GetFreeSparkParticle();
|
SparkParticle& GetFreeSparkParticle();
|
||||||
void TriggerFlareSparkParticles(const Vector3i& pos, const Vector3i& vel, const ColorData& color, int roomNumber);
|
void TriggerFlareSparkParticles(const Vector3i& pos, const Vector3i& vel, const ColorData& color, int roomNumber);
|
||||||
void TriggerRicochetSpark(const GameVector& pos, short angle, int num);
|
void TriggerRicochetSpark(const GameVector& pos, short angle, int num, const Vector4& colorStart = SPARK_RICOCHET_COLOR_DEFAULT);
|
||||||
void TriggerFrictionSpark(const GameVector& pos, const EulerAngles& angle, float length, int count);
|
void TriggerFrictionSpark(const GameVector& pos, const EulerAngles& angle, float length, int count);
|
||||||
void TriggerElectricSpark(const GameVector& pos, const EulerAngles& angle, int count);
|
void TriggerElectricSpark(const GameVector& pos, const EulerAngles& angle, int count);
|
||||||
void TriggerAttackSpark(const Vector3& basePos, const Vector3& color);
|
void TriggerAttackSpark(const Vector3& basePos, const Vector3& color);
|
||||||
|
|
|
@ -25,7 +25,7 @@ using namespace TEN::Control::Volumes;
|
||||||
|
|
||||||
constexpr int ITEM_DEATH_TIMEOUT = 4 * FPS;
|
constexpr int ITEM_DEATH_TIMEOUT = 4 * FPS;
|
||||||
|
|
||||||
bool ItemInfo::TestOcb(short ocbFlags)
|
bool ItemInfo::TestOcb(short ocbFlags) const
|
||||||
{
|
{
|
||||||
return ((TriggerFlags & ocbFlags) == ocbFlags);
|
return ((TriggerFlags & ocbFlags) == ocbFlags);
|
||||||
}
|
}
|
||||||
|
@ -40,29 +40,53 @@ void ItemInfo::ClearAllOcb()
|
||||||
TriggerFlags = 0;
|
TriggerFlags = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ItemInfo::TestFlags(short id, short value)
|
bool ItemInfo::TestFlags(int id, short flags) const
|
||||||
{
|
{
|
||||||
if (id < 0 || id > 7)
|
if (id < 0 || id > 7)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
return (ItemFlags[id] == value);
|
return (ItemFlags[id] & flags) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ItemInfo::SetFlags(short id, short value)
|
bool ItemInfo::TestFlagField(int id, short flags) const
|
||||||
|
{
|
||||||
|
if (id < 0 || id > 7)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return (ItemFlags[id] == flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
short ItemInfo::GetFlagField(int id) const
|
||||||
|
{
|
||||||
|
if (id < 0 || id > 7)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
return ItemFlags[id];
|
||||||
|
}
|
||||||
|
|
||||||
|
void ItemInfo::SetFlagField(int id, short flags)
|
||||||
{
|
{
|
||||||
if (id < 0 || id > 7)
|
if (id < 0 || id > 7)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
ItemFlags[id] = value;
|
this->ItemFlags[id] = flags;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ItemInfo::ClearFlags(int id, short flags)
|
||||||
|
{
|
||||||
|
if (id < 0 || id > 7)
|
||||||
|
return;
|
||||||
|
|
||||||
|
this->ItemFlags[id] &= ~flags;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ItemInfo::TestMeshSwapFlags(unsigned int flags)
|
bool ItemInfo::TestMeshSwapFlags(unsigned int flags)
|
||||||
{
|
{
|
||||||
for (size_t i = 0; i < Model.MeshIndex.size(); i++)
|
for (int i = 0; i < Model.MeshIndex.size(); i++)
|
||||||
{
|
{
|
||||||
if (flags & (1 << i))
|
if (flags & (1 << i))
|
||||||
{
|
{
|
||||||
if (Model.MeshIndex[i] == Model.BaseMesh + i)
|
if (Model.MeshIndex[i] == (Model.BaseMesh + i))
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -242,8 +266,7 @@ void RemoveAllItemsInRoom(short roomNumber, short objectNumber)
|
||||||
void AddActiveItem(short itemNumber)
|
void AddActiveItem(short itemNumber)
|
||||||
{
|
{
|
||||||
auto* item = &g_Level.Items[itemNumber];
|
auto* item = &g_Level.Items[itemNumber];
|
||||||
|
item->Flags |= IFLAG_TRIGGERED;
|
||||||
item->Flags |= 0x20;
|
|
||||||
|
|
||||||
if (Objects[item->ObjectNumber].control == NULL)
|
if (Objects[item->ObjectNumber].control == NULL)
|
||||||
{
|
{
|
||||||
|
@ -470,10 +493,8 @@ void InitialiseItem(short itemNumber)
|
||||||
item->Animation.Velocity.y = 0;
|
item->Animation.Velocity.y = 0;
|
||||||
item->Animation.Velocity.z = 0;
|
item->Animation.Velocity.z = 0;
|
||||||
|
|
||||||
item->ItemFlags[3] = 0;
|
for (int i = 0; i < NUM_ITEM_FLAGS; i++)
|
||||||
item->ItemFlags[2] = 0;
|
item->ItemFlags[i] = 0;
|
||||||
item->ItemFlags[1] = 0;
|
|
||||||
item->ItemFlags[0] = 0;
|
|
||||||
|
|
||||||
item->Active = false;
|
item->Active = false;
|
||||||
item->Status = ITEM_NOT_ACTIVE;
|
item->Status = ITEM_NOT_ACTIVE;
|
||||||
|
@ -481,9 +502,7 @@ void InitialiseItem(short itemNumber)
|
||||||
item->HitStatus = false;
|
item->HitStatus = false;
|
||||||
item->Collidable = true;
|
item->Collidable = true;
|
||||||
item->LookedAt = false;
|
item->LookedAt = false;
|
||||||
|
|
||||||
item->Timer = 0;
|
item->Timer = 0;
|
||||||
|
|
||||||
item->HitPoints = Objects[item->ObjectNumber].HitPoints;
|
item->HitPoints = Objects[item->ObjectNumber].HitPoints;
|
||||||
|
|
||||||
if (item->ObjectNumber == ID_HK_ITEM ||
|
if (item->ObjectNumber == ID_HK_ITEM ||
|
||||||
|
@ -516,7 +535,6 @@ void InitialiseItem(short itemNumber)
|
||||||
}
|
}
|
||||||
|
|
||||||
auto* room = &g_Level.Rooms[item->RoomNumber];
|
auto* room = &g_Level.Rooms[item->RoomNumber];
|
||||||
|
|
||||||
item->NextItem = room->itemNumber;
|
item->NextItem = room->itemNumber;
|
||||||
room->itemNumber = itemNumber;
|
room->itemNumber = itemNumber;
|
||||||
|
|
||||||
|
@ -545,11 +563,10 @@ void InitialiseItem(short itemNumber)
|
||||||
|
|
||||||
short CreateItem()
|
short CreateItem()
|
||||||
{
|
{
|
||||||
short itemNumber = 0;
|
if (NextItemFree == NO_ITEM)
|
||||||
|
return NO_ITEM;
|
||||||
|
|
||||||
if (NextItemFree == -1) return NO_ITEM;
|
short itemNumber = NextItemFree;
|
||||||
|
|
||||||
itemNumber = NextItemFree;
|
|
||||||
g_Level.Items[NextItemFree].Flags = 0;
|
g_Level.Items[NextItemFree].Flags = 0;
|
||||||
NextItemFree = g_Level.Items[NextItemFree].NextItem;
|
NextItemFree = g_Level.Items[NextItemFree].NextItem;
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,9 @@ enum GAME_OBJECT_ID : short;
|
||||||
|
|
||||||
constexpr auto NO_ITEM = -1;
|
constexpr auto NO_ITEM = -1;
|
||||||
constexpr auto NOT_TARGETABLE = -16384;
|
constexpr auto NOT_TARGETABLE = -16384;
|
||||||
|
|
||||||
constexpr auto NUM_ITEMS = 1024;
|
constexpr auto NUM_ITEMS = 1024;
|
||||||
|
constexpr auto NUM_ITEM_FLAGS = 8;
|
||||||
|
|
||||||
constexpr unsigned int ALL_JOINT_BITS = UINT_MAX;
|
constexpr unsigned int ALL_JOINT_BITS = UINT_MAX;
|
||||||
constexpr unsigned int NO_JOINT_BITS = 0;
|
constexpr unsigned int NO_JOINT_BITS = 0;
|
||||||
|
@ -43,11 +45,12 @@ enum ItemStatus
|
||||||
|
|
||||||
enum ItemFlags
|
enum ItemFlags
|
||||||
{
|
{
|
||||||
|
IFLAG_TRIGGERED = (1 << 5),
|
||||||
IFLAG_CLEAR_BODY = (1 << 7),
|
IFLAG_CLEAR_BODY = (1 << 7),
|
||||||
IFLAG_INVISIBLE = (1 << 8),
|
IFLAG_INVISIBLE = (1 << 8),
|
||||||
IFLAG_REVERSE = (1 << 14),
|
IFLAG_REVERSE = (1 << 14),
|
||||||
IFLAG_KILLED = (1 << 15),
|
IFLAG_KILLED = (1 << 15),
|
||||||
IFLAG_ACTIVATION_MASK = 0x3E00 // bits 9-13
|
IFLAG_ACTIVATION_MASK = 0x3E00 // Bits 9-13 (IFLAG_CODEBITS)
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class EffectType
|
enum class EffectType
|
||||||
|
@ -100,7 +103,7 @@ struct EntityEffectData
|
||||||
int Count = -1;
|
int Count = -1;
|
||||||
};
|
};
|
||||||
|
|
||||||
//todo we need to find good "default states" for a lot of these - squidshire 25/05/2022
|
// TODO: We need to find good "default states" for a lot of these/ -- squidshire 25/05/2022
|
||||||
struct ItemInfo
|
struct ItemInfo
|
||||||
{
|
{
|
||||||
GAME_OBJECT_ID ObjectNumber;
|
GAME_OBJECT_ID ObjectNumber;
|
||||||
|
@ -138,7 +141,7 @@ struct ItemInfo
|
||||||
BitField MeshBits = BitField();
|
BitField MeshBits = BitField();
|
||||||
|
|
||||||
unsigned short Flags; // ItemFlags enum
|
unsigned short Flags; // ItemFlags enum
|
||||||
short ItemFlags[8];
|
short ItemFlags[NUM_ITEM_FLAGS];
|
||||||
short TriggerFlags;
|
short TriggerFlags;
|
||||||
|
|
||||||
// TODO: Move to CreatureInfo?
|
// TODO: Move to CreatureInfo?
|
||||||
|
@ -146,12 +149,15 @@ struct ItemInfo
|
||||||
short AfterDeath;
|
short AfterDeath;
|
||||||
short CarriedItem;
|
short CarriedItem;
|
||||||
|
|
||||||
bool TestOcb(short ocbFlags);
|
bool TestOcb(short ocbFlags) const;
|
||||||
void RemoveOcb(short ocbFlags);
|
void RemoveOcb(short ocbFlags);
|
||||||
void ClearAllOcb();
|
void ClearAllOcb();
|
||||||
|
|
||||||
bool TestFlags(short id, short value);
|
bool TestFlags(int id, short flags) const; // ItemFlags[id] & flags
|
||||||
void SetFlags(short id, short value);
|
bool TestFlagField(int id, short flags) const; // ItemFlags[id] == flags
|
||||||
|
short GetFlagField(int id) const; // ItemFlags[id]
|
||||||
|
void SetFlagField(int id, short flags); // ItemFlags[id] = flags
|
||||||
|
void ClearFlags(int id, short flags); // ItemFlags[id] &= ~flags
|
||||||
|
|
||||||
bool TestMeshSwapFlags(unsigned int flags);
|
bool TestMeshSwapFlags(unsigned int flags);
|
||||||
bool TestMeshSwapFlags(const std::vector<unsigned int>& flags);
|
bool TestMeshSwapFlags(const std::vector<unsigned int>& flags);
|
||||||
|
|
|
@ -17,7 +17,8 @@ CreatureInfo* GetCreatureInfo(ItemInfo* item)
|
||||||
|
|
||||||
void TargetNearestEntity(ItemInfo* item, CreatureInfo* creature)
|
void TargetNearestEntity(ItemInfo* item, CreatureInfo* creature)
|
||||||
{
|
{
|
||||||
float bestDistance = FLT_MAX;
|
float nearestDistance = INFINITY;
|
||||||
|
|
||||||
for (int i = 0; i < g_Level.NumItems; i++)
|
for (int i = 0; i < g_Level.NumItems; i++)
|
||||||
{
|
{
|
||||||
auto* targetEntity = &g_Level.Items[i];
|
auto* targetEntity = &g_Level.Items[i];
|
||||||
|
@ -30,10 +31,10 @@ void TargetNearestEntity(ItemInfo* item, CreatureInfo* creature)
|
||||||
targetEntity->Status != ITEM_INVISIBLE)
|
targetEntity->Status != ITEM_INVISIBLE)
|
||||||
{
|
{
|
||||||
float distance = Vector3i::Distance(item->Pose.Position, targetEntity->Pose.Position);
|
float distance = Vector3i::Distance(item->Pose.Position, targetEntity->Pose.Position);
|
||||||
if (distance < bestDistance)
|
if (distance < nearestDistance)
|
||||||
{
|
{
|
||||||
creature->Enemy = targetEntity;
|
creature->Enemy = targetEntity;
|
||||||
bestDistance = distance;
|
nearestDistance = distance;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,8 +62,8 @@ void DoFlipMap(short group)
|
||||||
|
|
||||||
FlipStatus = FlipStats[group] = !FlipStats[group];
|
FlipStatus = FlipStats[group] = !FlipStats[group];
|
||||||
|
|
||||||
for (int i = 0; i < ActiveCreatures.size(); i++)
|
for (auto& currentCreature : ActiveCreatures)
|
||||||
ActiveCreatures[i]->LOT.TargetBox = NO_BOX;
|
currentCreature->LOT.TargetBox = NO_BOX;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AddRoomFlipItems(ROOM_INFO* room)
|
void AddRoomFlipItems(ROOM_INFO* room)
|
||||||
|
|
|
@ -14,6 +14,16 @@ namespace TEN::Math::Geometry
|
||||||
return Vector3i(TranslatePoint(point.ToVector3(), headingAngle, forward, down, right));
|
return Vector3i(TranslatePoint(point.ToVector3(), headingAngle, forward, down, right));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Vector3i TranslatePoint(const Vector3i& point, const EulerAngles& orient, float distance)
|
||||||
|
{
|
||||||
|
return Vector3i(TranslatePoint(point.ToVector3(), orient, distance));
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector3i TranslatePoint(const Vector3i& point, const Vector3& direction, float distance)
|
||||||
|
{
|
||||||
|
return Vector3i(TranslatePoint(point.ToVector3(), direction, distance));
|
||||||
|
}
|
||||||
|
|
||||||
Vector3 TranslatePoint(const Vector3& point, short headingAngle, float forward, float down, float right)
|
Vector3 TranslatePoint(const Vector3& point, short headingAngle, float forward, float down, float right)
|
||||||
{
|
{
|
||||||
if (forward == 0.0f && down == 0.0f && right == 0.0f)
|
if (forward == 0.0f && down == 0.0f && right == 0.0f)
|
||||||
|
@ -28,11 +38,6 @@ namespace TEN::Math::Geometry
|
||||||
point.z + ((forward * cosHeading) - (right * sinHeading)));
|
point.z + ((forward * cosHeading) - (right * sinHeading)));
|
||||||
}
|
}
|
||||||
|
|
||||||
Vector3i TranslatePoint(const Vector3i& point, const EulerAngles& orient, float distance)
|
|
||||||
{
|
|
||||||
return Vector3i(TranslatePoint(point.ToVector3(), orient, distance));
|
|
||||||
}
|
|
||||||
|
|
||||||
Vector3 TranslatePoint(const Vector3& point, const EulerAngles& orient, float distance)
|
Vector3 TranslatePoint(const Vector3& point, const EulerAngles& orient, float distance)
|
||||||
{
|
{
|
||||||
if (distance == 0.0f)
|
if (distance == 0.0f)
|
||||||
|
@ -49,11 +54,6 @@ namespace TEN::Math::Geometry
|
||||||
point.z + (distance * (cosX * cosY)));
|
point.z + (distance * (cosX * cosY)));
|
||||||
}
|
}
|
||||||
|
|
||||||
Vector3i TranslatePoint(const Vector3i& point, const Vector3& direction, float distance)
|
|
||||||
{
|
|
||||||
return Vector3i(TranslatePoint(point.ToVector3(), direction, distance));
|
|
||||||
}
|
|
||||||
|
|
||||||
Vector3 TranslatePoint(const Vector3& point, const Vector3& direction, float distance)
|
Vector3 TranslatePoint(const Vector3& point, const Vector3& direction, float distance)
|
||||||
{
|
{
|
||||||
if (distance == 0.0f)
|
if (distance == 0.0f)
|
||||||
|
@ -102,11 +102,26 @@ namespace TEN::Math::Geometry
|
||||||
float distanceAlpha = direction.Dot(origin - linePoint0) / direction.Dot(direction);
|
float distanceAlpha = direction.Dot(origin - linePoint0) / direction.Dot(direction);
|
||||||
|
|
||||||
if (distanceAlpha < 0.0f)
|
if (distanceAlpha < 0.0f)
|
||||||
|
{
|
||||||
return linePoint0;
|
return linePoint0;
|
||||||
|
}
|
||||||
else if (distanceAlpha > 1.0f)
|
else if (distanceAlpha > 1.0f)
|
||||||
|
{
|
||||||
|
return linePoint1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return TranslatePoint(linePoint0, direction, distanceAlpha);
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector3 GetTruncatedLineEndpoint(const Vector3& linePoint0, const Vector3& linePoint1, float maxDistance)
|
||||||
|
{
|
||||||
|
auto distance = Vector3::Distance(linePoint0, linePoint1);
|
||||||
|
if (distance <= maxDistance)
|
||||||
return linePoint1;
|
return linePoint1;
|
||||||
|
|
||||||
return (linePoint0 + (direction * distanceAlpha));
|
auto direction = linePoint1 - linePoint0;
|
||||||
|
direction.Normalize();
|
||||||
|
return TranslatePoint(linePoint0, direction, maxDistance);
|
||||||
}
|
}
|
||||||
|
|
||||||
EulerAngles GetOrientToPoint(const Vector3& origin, const Vector3& target)
|
EulerAngles GetOrientToPoint(const Vector3& origin, const Vector3& target)
|
||||||
|
|
|
@ -6,14 +6,14 @@ class Vector3i;
|
||||||
|
|
||||||
namespace TEN::Math::Geometry
|
namespace TEN::Math::Geometry
|
||||||
{
|
{
|
||||||
// Since Y is assumed as the vertical axis, only the Y Euler component needs to be considered and
|
// Since Y is assumed as the vertical axis, 2D operations are simply done in the XZ plane.
|
||||||
// 2D vector operations can be done in the XZ plane. Maybe revise geometry functions to each take an "up" vector argument someday.
|
// Maybe revise geometry functions to each take a "force" vector argument someday.
|
||||||
|
|
||||||
Vector3i TranslatePoint(const Vector3i& point, short headingAngle, float forward, float down = 0.0f, float right = 0.0f);
|
Vector3i TranslatePoint(const Vector3i& point, short headingAngle, float forward, float down = 0.0f, float right = 0.0f);
|
||||||
Vector3 TranslatePoint(const Vector3& point, short headingAngle, float forward, float down = 0.0f, float right = 0.0f);
|
|
||||||
Vector3i TranslatePoint(const Vector3i& point, const EulerAngles& orient, float distance);
|
Vector3i TranslatePoint(const Vector3i& point, const EulerAngles& orient, float distance);
|
||||||
Vector3 TranslatePoint(const Vector3& point, const EulerAngles& orient, float distance);
|
|
||||||
Vector3i TranslatePoint(const Vector3i& point, const Vector3& direction, float distance);
|
Vector3i TranslatePoint(const Vector3i& point, const Vector3& direction, float distance);
|
||||||
|
Vector3 TranslatePoint(const Vector3& point, short headingAngle, float forward, float down = 0.0f, float right = 0.0f);
|
||||||
|
Vector3 TranslatePoint(const Vector3& point, const EulerAngles& orient, float distance);
|
||||||
Vector3 TranslatePoint(const Vector3& point, const Vector3& direction, float distance);
|
Vector3 TranslatePoint(const Vector3& point, const Vector3& direction, float distance);
|
||||||
|
|
||||||
short GetShortestAngle(short fromAngle, short toAngle);
|
short GetShortestAngle(short fromAngle, short toAngle);
|
||||||
|
@ -22,6 +22,7 @@ namespace TEN::Math::Geometry
|
||||||
|
|
||||||
float GetDistanceToLine(const Vector3& origin, const Vector3& linePoint0, const Vector3& linePoint1);
|
float GetDistanceToLine(const Vector3& origin, const Vector3& linePoint0, const Vector3& linePoint1);
|
||||||
Vector3 GetClosestPointOnLine(const Vector3& origin, const Vector3& linePoint0, const Vector3& linePoint1);
|
Vector3 GetClosestPointOnLine(const Vector3& origin, const Vector3& linePoint0, const Vector3& linePoint1);
|
||||||
|
Vector3 GetTruncatedLineEndpoint(const Vector3& linePoint0, const Vector3& linePoint1, float maxDistance);
|
||||||
EulerAngles GetOrientToPoint(const Vector3& origin, const Vector3& target);
|
EulerAngles GetOrientToPoint(const Vector3& origin, const Vector3& target);
|
||||||
|
|
||||||
bool IsPointInFront(const Pose& pose, const Vector3& target);
|
bool IsPointInFront(const Pose& pose, const Vector3& target);
|
||||||
|
|
280
TombEngine/Objects/Effects/Boss.cpp
Normal file
280
TombEngine/Objects/Effects/Boss.cpp
Normal file
|
@ -0,0 +1,280 @@
|
||||||
|
#include "framework.h"
|
||||||
|
#include "Objects/Effects/Boss.h"
|
||||||
|
|
||||||
|
#include "Game/collision/collide_room.h"
|
||||||
|
#include "Game/effects/effects.h"
|
||||||
|
#include "Game/effects/spark.h"
|
||||||
|
#include "Game/effects/tomb4fx.h"
|
||||||
|
#include "Game/items.h"
|
||||||
|
#include "Game/misc.h"
|
||||||
|
#include "Objects/TR3/Entity/PunaBoss.h"
|
||||||
|
#include "Specific/setup.h"
|
||||||
|
|
||||||
|
using namespace TEN::Effects::Spark;
|
||||||
|
using namespace TEN::Entities::Creatures::TR3;
|
||||||
|
|
||||||
|
namespace TEN::Effects::Boss
|
||||||
|
{
|
||||||
|
void SpawnShield(const ItemInfo& item, const Vector4& color)
|
||||||
|
{
|
||||||
|
if (!item.TestFlags((int)BossItemFlags::Object, (short)BossFlagValue::Shield))
|
||||||
|
return;
|
||||||
|
|
||||||
|
int itemNumber = CreateItem();
|
||||||
|
if (itemNumber == NO_ITEM)
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto& shieldItem = g_Level.Items[itemNumber];
|
||||||
|
|
||||||
|
shieldItem.ObjectNumber = ID_BOSS_SHIELD;
|
||||||
|
shieldItem.RoomNumber = item.RoomNumber;
|
||||||
|
shieldItem.Pose.Position = item.Pose.Position;
|
||||||
|
shieldItem.Pose.Position.y -= CLICK(3);
|
||||||
|
shieldItem.Pose.Orientation = item.Pose.Orientation;
|
||||||
|
shieldItem.Flags |= IFLAG_ACTIVATION_MASK;
|
||||||
|
|
||||||
|
InitialiseItem(itemNumber);
|
||||||
|
|
||||||
|
shieldItem.Model.Color = color;
|
||||||
|
shieldItem.Collidable = true;
|
||||||
|
shieldItem.ItemFlags[0] = 2;
|
||||||
|
shieldItem.Model.Mutator[0].Scale = Vector3(2.0f); // Scale model by factor of 2.
|
||||||
|
shieldItem.Status = ITEM_ACTIVE;
|
||||||
|
|
||||||
|
AddActiveItem(itemNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SpawnShockwaveExplosion(const ItemInfo& item, const Vector4& color)
|
||||||
|
{
|
||||||
|
if (!item.TestFlags((int)BossItemFlags::Object, (short)BossFlagValue::ShockwaveExplosion))
|
||||||
|
return;
|
||||||
|
|
||||||
|
int itemNumber = CreateItem();
|
||||||
|
if (itemNumber == NO_ITEM)
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto& shockwaveItem = g_Level.Items[itemNumber];
|
||||||
|
|
||||||
|
shockwaveItem.Pose.Position = item.Pose.Position;
|
||||||
|
shockwaveItem.Pose.Position.y -= CLICK(2);
|
||||||
|
|
||||||
|
shockwaveItem.Pose.Orientation = EulerAngles(
|
||||||
|
Random::GenerateAngle(ANGLE(-20.0f), ANGLE(20.0f)),
|
||||||
|
Random::GenerateAngle(ANGLE(-20.0f), ANGLE(20.0f)),
|
||||||
|
Random::GenerateAngle(ANGLE(-20.0f), ANGLE(20.0f)));
|
||||||
|
|
||||||
|
shockwaveItem.ObjectNumber = ID_BOSS_EXPLOSION_SHOCKWAVE;
|
||||||
|
shockwaveItem.RoomNumber = item.RoomNumber;
|
||||||
|
shockwaveItem.Flags |= IFLAG_ACTIVATION_MASK;
|
||||||
|
|
||||||
|
InitialiseItem(itemNumber);
|
||||||
|
|
||||||
|
auto result = color;
|
||||||
|
result.w = 1.0f;
|
||||||
|
shockwaveItem.Model.Color = result;
|
||||||
|
shockwaveItem.Collidable = false; // No collision for this entity.
|
||||||
|
shockwaveItem.ItemFlags[0] = 70; // Timer before clearing; will fade out, then get destroyed.
|
||||||
|
shockwaveItem.Model.Mutator[0].Scale = Vector3::Zero; // Start without scale.
|
||||||
|
shockwaveItem.Status = ITEM_ACTIVE;
|
||||||
|
|
||||||
|
AddActiveItem(itemNumber);
|
||||||
|
SoundEffect(SFX_TR3_BLAST_CIRCLE, &shockwaveItem.Pose);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShieldControl(int itemNumber)
|
||||||
|
{
|
||||||
|
auto& item = g_Level.Items[itemNumber];
|
||||||
|
|
||||||
|
// Entity life.
|
||||||
|
if (item.ItemFlags[0] > 0)
|
||||||
|
item.ItemFlags[0]--;
|
||||||
|
|
||||||
|
if (item.ItemFlags[0] <= 0)
|
||||||
|
{
|
||||||
|
RemoveActiveItem(itemNumber);
|
||||||
|
KillItem(itemNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto colorEnd = Vector4(item.Model.Color.x - 0.1f, item.Model.Color.y - 0.1f, item.Model.Color.z - 0.1f, item.Model.Color.w);
|
||||||
|
item.Model.Color = Vector4::Lerp(item.Model.Color, colorEnd, 0.35f);
|
||||||
|
item.Pose.Orientation.y += Random::GenerateAngle();
|
||||||
|
UpdateItemRoom(itemNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: Ring and explosion wave rotate and scale by default.
|
||||||
|
void ShockwaveRingControl(int itemNumber)
|
||||||
|
{
|
||||||
|
auto& item = g_Level.Items[itemNumber];
|
||||||
|
|
||||||
|
// Entity life.
|
||||||
|
if (item.ItemFlags[0] > 0)
|
||||||
|
item.ItemFlags[0]--;
|
||||||
|
|
||||||
|
if (item.ItemFlags[0] <= 0)
|
||||||
|
{
|
||||||
|
auto colorEnd = Vector4(item.Model.Color.x - 0.1f, item.Model.Color.y - 0.1f, item.Model.Color.z - 0.1f, item.Model.Color.w);
|
||||||
|
item.Model.Color = Vector4::Lerp(item.Model.Color, colorEnd, 0.35f);
|
||||||
|
|
||||||
|
if (item.Model.Color.x <= 0.0f &&
|
||||||
|
item.Model.Color.y <= 0.0f &&
|
||||||
|
item.Model.Color.z <= 0.0f)
|
||||||
|
{
|
||||||
|
RemoveActiveItem(itemNumber);
|
||||||
|
KillItem(itemNumber);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
item.Model.Mutator[0].Scale += Vector3::One;
|
||||||
|
UpdateItemRoom(itemNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShockwaveExplosionControl(int itemNumber)
|
||||||
|
{
|
||||||
|
auto& item = g_Level.Items[itemNumber];
|
||||||
|
|
||||||
|
// Entity life.
|
||||||
|
if (item.ItemFlags[0] > 0)
|
||||||
|
item.ItemFlags[0]--;
|
||||||
|
|
||||||
|
if (item.ItemFlags[0] <= 0)
|
||||||
|
{
|
||||||
|
auto colorEnd = Vector4(item.Model.Color.x - 0.1f, item.Model.Color.y - 0.1f, item.Model.Color.z - 0.1f, item.Model.Color.w);
|
||||||
|
item.Model.Color = Vector4::Lerp(item.Model.Color, colorEnd, 0.35f);
|
||||||
|
|
||||||
|
if (item.Model.Color.x <= 0.0f &&
|
||||||
|
item.Model.Color.y <= 0.0f &&
|
||||||
|
item.Model.Color.z <= 0.0f)
|
||||||
|
{
|
||||||
|
RemoveActiveItem(itemNumber);
|
||||||
|
KillItem(itemNumber);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
item.Pose.Orientation.y += ANGLE(5.0f);
|
||||||
|
item.Model.Mutator[0].Scale += Vector3(0.5f);
|
||||||
|
UpdateItemRoom(itemNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SpawnExplosionSmoke(const Vector3& pos)
|
||||||
|
{
|
||||||
|
auto& smoke = *GetFreeParticle();
|
||||||
|
|
||||||
|
auto sphere = BoundingSphere(pos, BLOCK(1 / 64.0f));
|
||||||
|
auto effectPos = Random::GeneratePointInSphere(sphere);
|
||||||
|
|
||||||
|
smoke.on = true;
|
||||||
|
smoke.blendMode = BLEND_MODES::BLENDMODE_ADDITIVE;
|
||||||
|
|
||||||
|
smoke.x = effectPos.x;
|
||||||
|
smoke.y = effectPos.y;
|
||||||
|
smoke.z = effectPos.z;
|
||||||
|
smoke.xVel = Random::GenerateInt(BLOCK(0.5f), BLOCK(0.5f));
|
||||||
|
smoke.yVel = GetRandomControl() - 128;
|
||||||
|
smoke.zVel = Random::GenerateInt(BLOCK(0.5f), BLOCK(0.5f));
|
||||||
|
smoke.sR = 75;
|
||||||
|
smoke.sG = 125;
|
||||||
|
smoke.sB = 175;
|
||||||
|
smoke.dR = 25;
|
||||||
|
smoke.dG = 80;
|
||||||
|
smoke.dB = 100;
|
||||||
|
smoke.colFadeSpeed = 8;
|
||||||
|
smoke.fadeToBlack = 64;
|
||||||
|
smoke.life =
|
||||||
|
smoke.sLife = Random::GenerateInt(96, 128);
|
||||||
|
smoke.friction = 6;
|
||||||
|
smoke.rotAng = Random::GenerateAngle();
|
||||||
|
smoke.rotAdd = Random::GenerateAngle(ANGLE(-0.2f), ANGLE(0.2f));
|
||||||
|
|
||||||
|
smoke.scalar = 3;
|
||||||
|
smoke.gravity = Random::GenerateInt(-8, -4);
|
||||||
|
smoke.maxYvel = Random::GenerateInt(-12, -8);
|
||||||
|
smoke.dSize = Random::GenerateInt(256, 288);
|
||||||
|
smoke.sSize =
|
||||||
|
smoke.size = smoke.dSize / 2;
|
||||||
|
smoke.flags = SP_SCALE | SP_DEF | SP_ROTATE | SP_EXPDEF;
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: Actual death occurs when countUntilDeath >= 60.
|
||||||
|
void ExplodeBoss(int itemNumber, ItemInfo& item, int countUntilDeath, const Vector4& color)
|
||||||
|
{
|
||||||
|
// Disable shield.
|
||||||
|
item.SetFlagField((int)BossItemFlags::ShieldIsEnabled, 0);
|
||||||
|
item.HitPoints = NOT_TARGETABLE;
|
||||||
|
|
||||||
|
// Start explosion (entity will keep duration count).
|
||||||
|
int counter = item.ItemFlags[(int)BossItemFlags::ExplodeCount];
|
||||||
|
if (counter == 1)
|
||||||
|
{
|
||||||
|
SpawnShockwaveExplosion(item, color);
|
||||||
|
|
||||||
|
auto sphere = BoundingSphere(item.Pose.Position.ToVector3() + Vector3(0.0f, -CLICK(3), 0.0f), BLOCK(0.5f));
|
||||||
|
for (int i = 0; i < 3; i++)
|
||||||
|
{
|
||||||
|
auto pos = Random::GeneratePointInSphere(sphere);
|
||||||
|
SpawnExplosionSmoke(pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (counter > 0 && !(counter % 10))
|
||||||
|
{
|
||||||
|
auto sphere = BoundingSphere(item.Pose.Position.ToVector3() + Vector3(0.0f, -CLICK(3), 0.0f), BLOCK(0.5f));
|
||||||
|
for (int i = 0; i < 3; i++)
|
||||||
|
{
|
||||||
|
auto pos = Random::GeneratePointInSphere(sphere);
|
||||||
|
SpawnExplosionSmoke(pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
sphere = BoundingSphere(item.Pose.Position.ToVector3() + Vector3(0.0f, -CLICK(2), 0.0f), BLOCK(1 / 16.0f));
|
||||||
|
auto shockwavePos = Pose(Random::GeneratePointInSphere(sphere), item.Pose.Orientation);
|
||||||
|
|
||||||
|
int speed = Random::GenerateInt(BLOCK(0.5f), BLOCK(1.6f));
|
||||||
|
auto orient2D = Random::GenerateAngle();
|
||||||
|
|
||||||
|
TriggerShockwave(
|
||||||
|
&shockwavePos, 300, BLOCK(0.5f), speed,
|
||||||
|
color.x * UCHAR_MAX, color.y * UCHAR_MAX, color.z * UCHAR_MAX,
|
||||||
|
36, orient2D, 0);
|
||||||
|
SoundEffect(SFX_TR3_BLAST_CIRCLE, &shockwavePos);
|
||||||
|
}
|
||||||
|
|
||||||
|
TriggerDynamicLight(
|
||||||
|
item.Pose.Position.x,
|
||||||
|
item.Pose.Position.y - CLICK(2),
|
||||||
|
item.Pose.Position.z,
|
||||||
|
counter / 2,
|
||||||
|
color.x * UCHAR_MAX, color.y * UCHAR_MAX, color.z * UCHAR_MAX);
|
||||||
|
|
||||||
|
if (counter >= countUntilDeath)
|
||||||
|
CreatureDie(itemNumber, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CheckForRequiredObjects(ItemInfo& item)
|
||||||
|
{
|
||||||
|
short flags = 0;
|
||||||
|
|
||||||
|
if (item.ObjectNumber == ID_PUNA_BOSS && Objects[ID_LIZARD].loaded)
|
||||||
|
flags |= (short)BossFlagValue::Lizard;
|
||||||
|
|
||||||
|
// The following are only for aesthetics.
|
||||||
|
|
||||||
|
if (Objects[ID_BOSS_EXPLOSION_SHOCKWAVE].loaded)
|
||||||
|
flags |= (short)BossFlagValue::ShockwaveExplosion;
|
||||||
|
|
||||||
|
if (Objects[ID_BOSS_SHIELD].loaded)
|
||||||
|
flags |= (short)BossFlagValue::Shield;
|
||||||
|
|
||||||
|
item.SetFlagField((int)BossItemFlags::Object, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: Used by player's FireWeapon() and only if ID_BOSS_SHIELD is loaded.
|
||||||
|
void SpawnShieldAndRichochetSparks(const ItemInfo& item, const Vector3& pos, const Vector4& color)
|
||||||
|
{
|
||||||
|
SpawnShield(item, color);
|
||||||
|
|
||||||
|
auto target = GameVector(pos, item.RoomNumber);
|
||||||
|
auto yOrient = Geometry::GetOrientToPoint(item.Pose.Position.ToVector3(), target.ToVector3()).y;
|
||||||
|
auto sparkColor = color;
|
||||||
|
sparkColor.w = 1.0f;
|
||||||
|
TriggerRicochetSpark(target, yOrient, 13, sparkColor);
|
||||||
|
}
|
||||||
|
}
|
36
TombEngine/Objects/Effects/Boss.h
Normal file
36
TombEngine/Objects/Effects/Boss.h
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
#pragma once
|
||||||
|
#include "Game/items.h"
|
||||||
|
|
||||||
|
namespace TEN::Effects::Boss
|
||||||
|
{
|
||||||
|
enum class BossItemFlags
|
||||||
|
{
|
||||||
|
Object = 0, // BossFlagValue enum.
|
||||||
|
Rotation = 1, // Store rotation for use (e.g. Puna when summoning).
|
||||||
|
ShieldIsEnabled = 2,
|
||||||
|
AttackType = 3,
|
||||||
|
AttackCount = 4, // Change behaviour after some attack.
|
||||||
|
DeathCount = 5,
|
||||||
|
ItemNumber = 6, // Check if summon is dead.
|
||||||
|
ExplodeCount = 7
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class BossFlagValue
|
||||||
|
{
|
||||||
|
ShockwaveExplosion = (1 << 0),
|
||||||
|
Shield = (1 << 1),
|
||||||
|
Lizard = (1 << 2)
|
||||||
|
};
|
||||||
|
|
||||||
|
void ShieldControl(int itemNumber);
|
||||||
|
void ShockwaveRingControl(int itemNumber);
|
||||||
|
void ShockwaveExplosionControl(int itemNumber);
|
||||||
|
|
||||||
|
void ExplodeBoss(int itemNumber, ItemInfo& item, int deathCountToDie, const Vector4& color);
|
||||||
|
void CheckForRequiredObjects(ItemInfo& item);
|
||||||
|
|
||||||
|
void SpawnShield(const ItemInfo& item, const Vector4& color);
|
||||||
|
void SpawnShockwaveExplosion(const ItemInfo& item, const Vector4& color);
|
||||||
|
|
||||||
|
void SpawnShieldAndRichochetSparks(const ItemInfo& item, const Vector3& pos, const Vector4& color);
|
||||||
|
}
|
|
@ -22,9 +22,20 @@ using namespace TEN::Math;
|
||||||
|
|
||||||
namespace TEN::Entities::Effects
|
namespace TEN::Entities::Effects
|
||||||
{
|
{
|
||||||
void TriggerSethMissileFlame(short fxNum, short xVel, short yVel, short zVel)
|
enum class MissileType
|
||||||
{
|
{
|
||||||
auto* fx = &EffectList[fxNum];
|
SethNormal = 0,
|
||||||
|
SethLarge = 1,
|
||||||
|
Harpy = 2,
|
||||||
|
Demigod3Single = 3,
|
||||||
|
Demigod3Radial = 4,
|
||||||
|
Demigod2 = 5,
|
||||||
|
Mutant = 6
|
||||||
|
};
|
||||||
|
|
||||||
|
void TriggerSethMissileFlame(short fxNumber, short xVel, short yVel, short zVel)
|
||||||
|
{
|
||||||
|
auto* fx = &EffectList[fxNumber];
|
||||||
|
|
||||||
int dx = LaraItem->Pose.Position.x - fx->pos.Position.x;
|
int dx = LaraItem->Pose.Position.x - fx->pos.Position.x;
|
||||||
int dz = LaraItem->Pose.Position.z - fx->pos.Position.z;
|
int dz = LaraItem->Pose.Position.z - fx->pos.Position.z;
|
||||||
|
@ -62,7 +73,7 @@ namespace TEN::Entities::Effects
|
||||||
|
|
||||||
spark->gravity = 0;
|
spark->gravity = 0;
|
||||||
spark->maxYvel = 0;
|
spark->maxYvel = 0;
|
||||||
spark->fxObj = fxNum;
|
spark->fxObj = fxNumber;
|
||||||
|
|
||||||
if (fx->flag1 == 1)
|
if (fx->flag1 == 1)
|
||||||
spark->scalar = 3;
|
spark->scalar = 3;
|
||||||
|
@ -144,14 +155,14 @@ namespace TEN::Entities::Effects
|
||||||
int maxRotation = 0;
|
int maxRotation = 0;
|
||||||
int maxVelocity = 0;
|
int maxVelocity = 0;
|
||||||
|
|
||||||
if (fx->flag1 == 1)
|
if (fx->flag1 == (int)MissileType::SethLarge)
|
||||||
{
|
{
|
||||||
maxRotation = ANGLE(2.8f);
|
maxRotation = ANGLE(2.8f);
|
||||||
maxVelocity = CLICK(1);
|
maxVelocity = CLICK(1);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (fx->flag1 == 6)
|
if (fx->flag1 == (int)MissileType::Mutant)
|
||||||
{
|
{
|
||||||
if (fx->counter)
|
if (fx->counter)
|
||||||
fx->counter--;
|
fx->counter--;
|
||||||
|
@ -168,7 +179,7 @@ namespace TEN::Entities::Effects
|
||||||
|
|
||||||
if (fx->speed < maxVelocity)
|
if (fx->speed < maxVelocity)
|
||||||
{
|
{
|
||||||
if (fx->flag1 == 6)
|
if (fx->flag1 == (int)MissileType::Mutant)
|
||||||
fx->speed++;
|
fx->speed++;
|
||||||
else
|
else
|
||||||
fx->speed += 3;
|
fx->speed += 3;
|
||||||
|
@ -204,12 +215,12 @@ namespace TEN::Entities::Effects
|
||||||
|
|
||||||
fx->pos.Orientation.x += dx;
|
fx->pos.Orientation.x += dx;
|
||||||
|
|
||||||
if (fx->flag1 != 4 && (fx->flag1 != 6 || !fx->counter))
|
if (fx->flag1 != (int)MissileType::Demigod3Radial && (fx->flag1 != (int)MissileType::Mutant || !fx->counter))
|
||||||
fx->pos.Orientation.y += dy;
|
fx->pos.Orientation.y += dy;
|
||||||
}
|
}
|
||||||
|
|
||||||
fx->pos.Orientation.z += 16 * fx->speed;
|
fx->pos.Orientation.z += 16 * fx->speed;
|
||||||
if (fx->flag1 == 6)
|
if (fx->flag1 == (int)MissileType::Mutant)
|
||||||
fx->pos.Orientation.z += 16 * fx->speed;
|
fx->pos.Orientation.z += 16 * fx->speed;
|
||||||
|
|
||||||
int oldX = fx->pos.Position.x;
|
int oldX = fx->pos.Position.x;
|
||||||
|
@ -229,10 +240,10 @@ namespace TEN::Entities::Effects
|
||||||
fx->pos.Position.y = oldY;
|
fx->pos.Position.y = oldY;
|
||||||
fx->pos.Position.z = oldZ;
|
fx->pos.Position.z = oldZ;
|
||||||
|
|
||||||
if (fx->flag1 != 6)
|
if (fx->flag1 != (int)MissileType::Mutant)
|
||||||
BubblesShatterFunction(fx, 0, -32);
|
BubblesShatterFunction(fx, 0, -32);
|
||||||
|
|
||||||
if (fx->flag1 == 1)
|
if (fx->flag1 == (int)MissileType::SethLarge)
|
||||||
{
|
{
|
||||||
TriggerShockwave(&fx->pos, 32, 160, 64, 64, 128, 00, 24, (((~g_Level.Rooms[fx->roomNumber].flags) / 16) & 2) * 65536, 0);
|
TriggerShockwave(&fx->pos, 32, 160, 64, 64, 128, 00, 24, (((~g_Level.Rooms[fx->roomNumber].flags) / 16) & 2) * 65536, 0);
|
||||||
TriggerExplosionSparks(oldX, oldY, oldZ, 3, -2, 2, fx->roomNumber);
|
TriggerExplosionSparks(oldX, oldY, oldZ, 3, -2, 2, fx->roomNumber);
|
||||||
|
@ -241,26 +252,26 @@ namespace TEN::Entities::Effects
|
||||||
{
|
{
|
||||||
if (fx->flag1)
|
if (fx->flag1)
|
||||||
{
|
{
|
||||||
if (fx->flag1 == 3 || fx->flag1 == 4)
|
if (fx->flag1 == (int)MissileType::Demigod3Single || fx->flag1 == (int)MissileType::Demigod3Radial)
|
||||||
{
|
{
|
||||||
TriggerShockwave(&fx->pos, 32, 160, 64, 0, 96, 128, 16, 0, 0);
|
TriggerShockwave(&fx->pos, 32, 160, 64, 0, 96, 128, 16, 0, 0);
|
||||||
}
|
}
|
||||||
else if (fx->flag1 == 5)
|
else if (fx->flag1 == (int)MissileType::Demigod2)
|
||||||
{
|
{
|
||||||
TriggerShockwave(&fx->pos, 32, 160, 64, 128, 64, 0, 16, 0, 0);
|
TriggerShockwave(&fx->pos, 32, 160, 64, 128, 64, 0, 16, 0, 0);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (fx->flag1 != 2)
|
if (fx->flag1 != (int)MissileType::Harpy)
|
||||||
{
|
{
|
||||||
if (fx->flag1 == 6)
|
if (fx->flag1 == (int)MissileType::Mutant)
|
||||||
{
|
{
|
||||||
TriggerExplosionSparks(oldX, oldY, oldZ, 3, -2, 0, fx->roomNumber);
|
TriggerExplosionSparks(oldX, oldY, oldZ, 3, -2, 0, fx->roomNumber);
|
||||||
TriggerShockwave(&fx->pos, 48, 240, 64, 0, 96, 128, 24, 0, 15);
|
TriggerShockwave(&fx->pos, 48, 240, 64, 128, 96, 0, 24, 0, 15);
|
||||||
fx->pos.Position.y -= 128;
|
fx->pos.Position.y -= 128;
|
||||||
TriggerShockwave(&fx->pos, 48, 240, 48, 0, 112, 128, 16, 0, 15);
|
TriggerShockwave(&fx->pos, 48, 240, 48, 128, 112, 0, 16, 0, 15);
|
||||||
fx->pos.Position.y += 256;
|
fx->pos.Position.y += 256;
|
||||||
TriggerShockwave(&fx->pos, 48, 240, 48, 0, 112, 128, 16, 0, 15);
|
TriggerShockwave(&fx->pos, 48, 240, 48, 128, 112, 0, 16, 0, 15);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -283,12 +294,12 @@ namespace TEN::Entities::Effects
|
||||||
if (ItemNearLara(fx->pos.Position, 200))
|
if (ItemNearLara(fx->pos.Position, 200))
|
||||||
{
|
{
|
||||||
LaraItem->HitStatus = true;
|
LaraItem->HitStatus = true;
|
||||||
if (fx->flag1 != 6)
|
if (fx->flag1 != (int)MissileType::Mutant)
|
||||||
BubblesShatterFunction(fx, 0, -32);
|
BubblesShatterFunction(fx, 0, -32);
|
||||||
|
|
||||||
KillEffect(fxNum);
|
KillEffect(fxNum);
|
||||||
|
|
||||||
if (fx->flag1 == 1)
|
if (fx->flag1 == (int)MissileType::SethLarge)
|
||||||
{
|
{
|
||||||
TriggerShockwave(&fx->pos, 48, 240, 64, 0, 128, 64, 24, 0, 0);
|
TriggerShockwave(&fx->pos, 48, 240, 64, 0, 128, 64, 24, 0, 0);
|
||||||
TriggerExplosionSparks(oldX, oldY, oldZ, 3, -2, 2, fx->roomNumber);
|
TriggerExplosionSparks(oldX, oldY, oldZ, 3, -2, 2, fx->roomNumber);
|
||||||
|
@ -298,26 +309,26 @@ namespace TEN::Entities::Effects
|
||||||
{
|
{
|
||||||
switch (fx->flag1)
|
switch (fx->flag1)
|
||||||
{
|
{
|
||||||
case 3:
|
case (int)MissileType::Demigod3Single:
|
||||||
case 4:
|
case (int)MissileType::Demigod3Radial:
|
||||||
TriggerShockwave(&fx->pos, 32, 160, 64, 0, 96, 128, 16, 0, 10);
|
TriggerShockwave(&fx->pos, 32, 160, 64, 0, 96, 128, 16, 0, 10);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 5:
|
case (int)MissileType::Demigod2:
|
||||||
TriggerShockwave(&fx->pos, 32, 160, 64, 128, 64, 0, 16, 0, 5);
|
TriggerShockwave(&fx->pos, 32, 160, 64, 128, 64, 0, 16, 0, 5);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 2:
|
case (int)MissileType::Harpy:
|
||||||
TriggerShockwave(&fx->pos, 32, 160, 64, 128, 128, 0, 16, 0, 3);//H
|
TriggerShockwave(&fx->pos, 32, 160, 64, 128, 128, 0, 16, 0, 3);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 6:
|
case (int)MissileType::Mutant:
|
||||||
TriggerExplosionSparks(oldX, oldY, oldZ, 3, -2, 0, fx->roomNumber);
|
TriggerExplosionSparks(oldX, oldY, oldZ, 3, -2, 0, fx->roomNumber);
|
||||||
TriggerShockwave(&fx->pos, 48, 240, 64, 0, 96, 128, 24, 0, 0);
|
TriggerShockwave(&fx->pos, 48, 240, 64, 128, 96, 0, 24, 0, 0);
|
||||||
fx->pos.Position.y -= 128;
|
fx->pos.Position.y -= 128;
|
||||||
TriggerShockwave(&fx->pos, 48, 240, 48, 0, 112, 128, 16, 0, 0);
|
TriggerShockwave(&fx->pos, 48, 240, 48, 128, 112, 0, 16, 0, 0);
|
||||||
fx->pos.Position.y += 256;
|
fx->pos.Position.y += 256;
|
||||||
TriggerShockwave(&fx->pos, 48, 240, 48, 0, 112, 128, 16, 0, 0);
|
TriggerShockwave(&fx->pos, 48, 240, 48, 128, 112, 0, 16, 0, 0);
|
||||||
ItemBurn(LaraItem);
|
ItemBurn(LaraItem);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -341,21 +352,21 @@ namespace TEN::Entities::Effects
|
||||||
switch (fx->flag1)
|
switch (fx->flag1)
|
||||||
{
|
{
|
||||||
default:
|
default:
|
||||||
case 1:
|
case (int)MissileType::SethLarge:
|
||||||
TriggerSethMissileFlame(fxNum, 32 * dx, 32 * dy, 32 * dz);
|
TriggerSethMissileFlame(fxNum, 32 * dx, 32 * dy, 32 * dz);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 2:
|
case (int)MissileType::Harpy:
|
||||||
TriggerHarpyFlameFlame(fxNum, 16 * dx, 16 * dy, 16 * dz);
|
TriggerHarpyFlameFlame(fxNum, 16 * dx, 16 * dy, 16 * dz);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 3:
|
case (int)MissileType::Demigod3Single:
|
||||||
case 4:
|
case (int)MissileType::Demigod3Radial:
|
||||||
case 5:
|
case (int)MissileType::Demigod2:
|
||||||
TriggerDemigodMissileFlame(fxNum, 16 * dx, 16 * dy, 16 * dz);
|
TriggerDemigodMissileFlame(fxNum, 16 * dx, 16 * dy, 16 * dz);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 6:
|
case (int)MissileType::Mutant:
|
||||||
TriggerCrocgodMissileFlame(fxNum, 16 * dx, 16 * dy, 16 * dz);
|
TriggerCrocgodMissileFlame(fxNum, 16 * dx, 16 * dy, 16 * dz);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -401,11 +401,8 @@ namespace TEN::Entities::Doors
|
||||||
if (boxIndex != NO_BOX)
|
if (boxIndex != NO_BOX)
|
||||||
{
|
{
|
||||||
g_Level.Boxes[boxIndex].flags &= ~BLOCKED;
|
g_Level.Boxes[boxIndex].flags &= ~BLOCKED;
|
||||||
|
for (auto& currentCreature : ActiveCreatures)
|
||||||
for (int i = 0; i < ActiveCreatures.size(); i++)
|
currentCreature->LOT.TargetBox = NO_BOX;
|
||||||
{
|
|
||||||
ActiveCreatures[i]->LOT.TargetBox = NO_BOX;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -437,8 +434,8 @@ namespace TEN::Entities::Doors
|
||||||
{
|
{
|
||||||
g_Level.Boxes[boxIndex].flags |= BLOCKED;
|
g_Level.Boxes[boxIndex].flags |= BLOCKED;
|
||||||
|
|
||||||
for (int i = 0; i < ActiveCreatures.size(); i++)
|
for (auto& currentCreature : ActiveCreatures)
|
||||||
ActiveCreatures[i]->LOT.TargetBox = NO_BOX;
|
currentCreature->LOT.TargetBox = NO_BOX;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -150,12 +150,12 @@ namespace TEN::Entities::Creatures::TR1
|
||||||
if (Targetable(item, AI) && (AI->zoneNumber != AI->enemyZone || AI->distance > WINGED_MUTANT_ATTACK_RANGE))
|
if (Targetable(item, AI) && (AI->zoneNumber != AI->enemyZone || AI->distance > WINGED_MUTANT_ATTACK_RANGE))
|
||||||
{
|
{
|
||||||
if ((AI->angle > 0 && AI->angle < ANGLE(45.0f)) &&
|
if ((AI->angle > 0 && AI->angle < ANGLE(45.0f)) &&
|
||||||
item->TestFlags(WMUTANT_OCB_DISABLE_DART_WEAPON, false))
|
item->TestFlagField(WMUTANT_OCB_DISABLE_DART_WEAPON, false))
|
||||||
{
|
{
|
||||||
return WMUTANT_PROJ_DART;
|
return WMUTANT_PROJ_DART;
|
||||||
}
|
}
|
||||||
else if ((AI->angle < 0 && AI->angle > -ANGLE(45.0f)) &&
|
else if ((AI->angle < 0 && AI->angle > -ANGLE(45.0f)) &&
|
||||||
item->TestFlags(WMUTANT_OCB_DISABLE_BOMB_WEAPON, false))
|
item->TestFlagField(WMUTANT_OCB_DISABLE_BOMB_WEAPON, false))
|
||||||
{
|
{
|
||||||
return WMUTANT_PROJ_BOMB;
|
return WMUTANT_PROJ_BOMB;
|
||||||
}
|
}
|
||||||
|
@ -171,19 +171,19 @@ namespace TEN::Entities::Creatures::TR1
|
||||||
{
|
{
|
||||||
SwitchPathfinding(creature, WMUTANT_PATH_AERIAL);
|
SwitchPathfinding(creature, WMUTANT_PATH_AERIAL);
|
||||||
SetAnimation(item, WMUTANT_ANIM_FLY);
|
SetAnimation(item, WMUTANT_ANIM_FLY);
|
||||||
item->SetFlags(WMUTANT_CONF_PATHFINDING_MODE, WMUTANT_PATH_AERIAL);
|
item->SetFlagField(WMUTANT_CONF_PATHFINDING_MODE, WMUTANT_PATH_AERIAL);
|
||||||
}
|
}
|
||||||
else if (item->TestOcb(WMUTANT_OCB_START_INACTIVE))
|
else if (item->TestOcb(WMUTANT_OCB_START_INACTIVE))
|
||||||
{
|
{
|
||||||
SwitchPathfinding(creature, WMUTANT_PATH_GROUND);
|
SwitchPathfinding(creature, WMUTANT_PATH_GROUND);
|
||||||
SetAnimation(item, WMUTANT_ANIM_INACTIVE);
|
SetAnimation(item, WMUTANT_ANIM_INACTIVE);
|
||||||
item->SetFlags(WMUTANT_CONF_PATHFINDING_MODE, WMUTANT_PATH_GROUND);
|
item->SetFlagField(WMUTANT_CONF_PATHFINDING_MODE, WMUTANT_PATH_GROUND);
|
||||||
}
|
}
|
||||||
else if (item->TestOcb(WMUTANT_OCB_START_POSE))
|
else if (item->TestOcb(WMUTANT_OCB_START_POSE))
|
||||||
{
|
{
|
||||||
SwitchPathfinding(creature, WMUTANT_PATH_GROUND);
|
SwitchPathfinding(creature, WMUTANT_PATH_GROUND);
|
||||||
SetAnimation(item, WMUTANT_ANIM_INACTIVE);
|
SetAnimation(item, WMUTANT_ANIM_INACTIVE);
|
||||||
item->SetFlags(WMUTANT_CONF_PATHFINDING_MODE, WMUTANT_PATH_GROUND);
|
item->SetFlagField(WMUTANT_CONF_PATHFINDING_MODE, WMUTANT_PATH_GROUND);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove OCBs since we don't need them anymore.
|
// Remove OCBs since we don't need them anymore.
|
||||||
|
@ -201,26 +201,28 @@ namespace TEN::Entities::Creatures::TR1
|
||||||
auto* item = &g_Level.Items[itemNumber];
|
auto* item = &g_Level.Items[itemNumber];
|
||||||
|
|
||||||
InitialiseCreature(itemNumber);
|
InitialiseCreature(itemNumber);
|
||||||
item->SetFlags(WMUTANT_CONF_PATHFINDING_MODE, WMUTANT_PATH_GROUND);
|
item->SetFlagField(WMUTANT_CONF_PATHFINDING_MODE, WMUTANT_PATH_GROUND);
|
||||||
item->SetFlags(WMUTANT_CONF_PROJECTILE_MODE, WMUTANT_PROJ_NONE);
|
item->SetFlagField(WMUTANT_CONF_PROJECTILE_MODE, WMUTANT_PROJ_NONE);
|
||||||
|
|
||||||
if (item->TestOcb(WMUTANT_OCB_NO_WINGS))
|
if (item->TestOcb(WMUTANT_OCB_NO_WINGS))
|
||||||
{
|
{
|
||||||
item->SetFlags(WMUTANT_CONF_CAN_FLY, false);
|
item->SetFlagField(WMUTANT_CONF_CAN_FLY, false);
|
||||||
item->MeshBits = 0xFFE07FFF;
|
item->MeshBits = 0xFFE07FFF;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
item->SetFlags(WMUTANT_CONF_CAN_FLY, true);
|
item->SetFlagField(WMUTANT_CONF_CAN_FLY, true);
|
||||||
|
|
||||||
if (item->TestOcb(WMUTANT_OCB_DISABLE_BOMB_WEAPON))
|
if (item->TestOcb(WMUTANT_OCB_DISABLE_BOMB_WEAPON))
|
||||||
item->SetFlags(WMUTANT_CONF_DISABLE_BOMB_WEAPON, true);
|
item->SetFlagField(WMUTANT_CONF_DISABLE_BOMB_WEAPON, true);
|
||||||
if (item->TestOcb(WMUTANT_OCB_DISABLE_DART_WEAPON))
|
if (item->TestOcb(WMUTANT_OCB_DISABLE_DART_WEAPON))
|
||||||
item->SetFlags(WMUTANT_CONF_DISABLE_DART_WEAPON, true);
|
item->SetFlagField(WMUTANT_CONF_DISABLE_DART_WEAPON, true);
|
||||||
|
|
||||||
if (item->TestOcb(WMUTANT_OCB_DISABLE_BOMB_WEAPON))
|
if (item->TestOcb(WMUTANT_OCB_DISABLE_BOMB_WEAPON))
|
||||||
item->RemoveOcb(WMUTANT_OCB_DISABLE_BOMB_WEAPON);
|
item->RemoveOcb(WMUTANT_OCB_DISABLE_BOMB_WEAPON);
|
||||||
|
|
||||||
if (item->TestOcb(WMUTANT_OCB_DISABLE_DART_WEAPON))
|
if (item->TestOcb(WMUTANT_OCB_DISABLE_DART_WEAPON))
|
||||||
item->RemoveOcb(WMUTANT_OCB_DISABLE_DART_WEAPON);
|
item->RemoveOcb(WMUTANT_OCB_DISABLE_DART_WEAPON);
|
||||||
|
|
||||||
if (item->TestOcb(WMUTANT_OCB_NO_WINGS))
|
if (item->TestOcb(WMUTANT_OCB_NO_WINGS))
|
||||||
item->RemoveOcb(WMUTANT_OCB_NO_WINGS);
|
item->RemoveOcb(WMUTANT_OCB_NO_WINGS);
|
||||||
}
|
}
|
||||||
|
@ -237,8 +239,8 @@ namespace TEN::Entities::Creatures::TR1
|
||||||
short head = 0;
|
short head = 0;
|
||||||
short torso = 0; // Only when shooting.
|
short torso = 0; // Only when shooting.
|
||||||
|
|
||||||
bool flyEnabled = item->TestFlags(WMUTANT_CONF_CAN_FLY, true);
|
bool flyEnabled = item->TestFlagField(WMUTANT_CONF_CAN_FLY, true);
|
||||||
bool flyStatus = item->TestFlags(WMUTANT_CONF_PATHFINDING_MODE, WMUTANT_PATH_AERIAL);
|
bool flyStatus = item->TestFlagField(WMUTANT_CONF_PATHFINDING_MODE, WMUTANT_PATH_AERIAL);
|
||||||
|
|
||||||
WingedInitOCB(item, creature);
|
WingedInitOCB(item, creature);
|
||||||
|
|
||||||
|
@ -274,7 +276,7 @@ namespace TEN::Entities::Creatures::TR1
|
||||||
if (flyStatus && creature->Mood != MoodType::Escape &&
|
if (flyStatus && creature->Mood != MoodType::Escape &&
|
||||||
AI.zoneNumber == AI.enemyZone)
|
AI.zoneNumber == AI.enemyZone)
|
||||||
{
|
{
|
||||||
item->SetFlags(WMUTANT_CONF_PATHFINDING_MODE, WMUTANT_PATH_GROUND);
|
item->SetFlagField(WMUTANT_CONF_PATHFINDING_MODE, WMUTANT_PATH_GROUND);
|
||||||
}
|
}
|
||||||
|
|
||||||
SwitchPathfinding(creature, WMUTANT_PATH_AERIAL);
|
SwitchPathfinding(creature, WMUTANT_PATH_AERIAL);
|
||||||
|
@ -285,7 +287,7 @@ namespace TEN::Entities::Creatures::TR1
|
||||||
(!AI.ahead || creature->Mood == MoodType::Bored)) ||
|
(!AI.ahead || creature->Mood == MoodType::Bored)) ||
|
||||||
creature->Mood == MoodType::Escape)
|
creature->Mood == MoodType::Escape)
|
||||||
{
|
{
|
||||||
item->SetFlags(WMUTANT_CONF_PATHFINDING_MODE, WMUTANT_PATH_AERIAL);
|
item->SetFlagField(WMUTANT_CONF_PATHFINDING_MODE, WMUTANT_PATH_AERIAL);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -302,7 +304,7 @@ namespace TEN::Entities::Creatures::TR1
|
||||||
case WMUTANT_STATE_IDLE:
|
case WMUTANT_STATE_IDLE:
|
||||||
torso = 0;
|
torso = 0;
|
||||||
creature->MaxTurn = 0;
|
creature->MaxTurn = 0;
|
||||||
item->SetFlags(WMUTANT_CONF_PATHFINDING_MODE, WMUTANT_PROJ_NONE);
|
item->SetFlagField(WMUTANT_CONF_PATHFINDING_MODE, WMUTANT_PROJ_NONE);
|
||||||
|
|
||||||
if (flyStatus && flyEnabled)
|
if (flyStatus && flyEnabled)
|
||||||
item->Animation.TargetState = WMUTANT_STATE_FLY;
|
item->Animation.TargetState = WMUTANT_STATE_FLY;
|
||||||
|
@ -433,7 +435,7 @@ namespace TEN::Entities::Creatures::TR1
|
||||||
case WMUTANT_STATE_AIM_DART:
|
case WMUTANT_STATE_AIM_DART:
|
||||||
torso = AI.angle / 2;
|
torso = AI.angle / 2;
|
||||||
creature->MaxTurn = 0;
|
creature->MaxTurn = 0;
|
||||||
item->SetFlags(WMUTANT_CONF_PROJECTILE_MODE, WMUTANT_PROJ_DART);
|
item->SetFlagField(WMUTANT_CONF_PROJECTILE_MODE, WMUTANT_PROJ_DART);
|
||||||
|
|
||||||
if (shootType == WMUTANT_PROJ_DART)
|
if (shootType == WMUTANT_PROJ_DART)
|
||||||
item->Animation.TargetState = WMUTANT_STATE_SHOOT;
|
item->Animation.TargetState = WMUTANT_STATE_SHOOT;
|
||||||
|
@ -445,7 +447,7 @@ namespace TEN::Entities::Creatures::TR1
|
||||||
case WMUTANT_STATE_AIM_BOMB:
|
case WMUTANT_STATE_AIM_BOMB:
|
||||||
torso = AI.angle / 2;
|
torso = AI.angle / 2;
|
||||||
creature->MaxTurn = 0;
|
creature->MaxTurn = 0;
|
||||||
item->SetFlags(WMUTANT_CONF_PROJECTILE_MODE, WMUTANT_PROJ_BOMB);
|
item->SetFlagField(WMUTANT_CONF_PROJECTILE_MODE, WMUTANT_PROJ_BOMB);
|
||||||
|
|
||||||
if (shootType == WMUTANT_PROJ_BOMB)
|
if (shootType == WMUTANT_PROJ_BOMB)
|
||||||
item->Animation.TargetState = WMUTANT_STATE_SHOOT;
|
item->Animation.TargetState = WMUTANT_STATE_SHOOT;
|
||||||
|
@ -459,15 +461,15 @@ namespace TEN::Entities::Creatures::TR1
|
||||||
torso = AI.angle / 2;
|
torso = AI.angle / 2;
|
||||||
creature->MaxTurn = 0;
|
creature->MaxTurn = 0;
|
||||||
|
|
||||||
bool isDart = item->TestFlags(WMUTANT_CONF_PROJECTILE_MODE, WMUTANT_PROJ_DART);
|
bool isDart = item->TestFlagField(WMUTANT_CONF_PROJECTILE_MODE, WMUTANT_PROJ_DART);
|
||||||
bool isBomb = item->TestFlags(WMUTANT_CONF_PROJECTILE_MODE, WMUTANT_PROJ_BOMB);
|
bool isBomb = item->TestFlagField(WMUTANT_CONF_PROJECTILE_MODE, WMUTANT_PROJ_BOMB);
|
||||||
|
|
||||||
if (isDart)
|
if (isDart)
|
||||||
CreatureEffect2(item, WingedMutantShardBite, WINGED_MUTANT_SHARD_VELOCITY, torso, ShardGun);
|
CreatureEffect2(item, WingedMutantShardBite, WINGED_MUTANT_SHARD_VELOCITY, torso, ShardGun);
|
||||||
else if (isBomb)
|
else if (isBomb)
|
||||||
CreatureEffect2(item, WingedMutantRocketBite, WINGED_MUTANT_BOMB_VELOCITY, torso, BombGun);
|
CreatureEffect2(item, WingedMutantRocketBite, WINGED_MUTANT_BOMB_VELOCITY, torso, BombGun);
|
||||||
|
|
||||||
item->SetFlags(WMUTANT_CONF_PROJECTILE_MODE, WMUTANT_PROJ_NONE);
|
item->SetFlagField(WMUTANT_CONF_PROJECTILE_MODE, WMUTANT_PROJ_NONE);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,14 +16,18 @@ namespace TEN::Entities::Creatures::TR2
|
||||||
const auto EagleBite = BiteInfo(Vector3(15.0f, 46.0f, 21.0f), 6);
|
const auto EagleBite = BiteInfo(Vector3(15.0f, 46.0f, 21.0f), 6);
|
||||||
const auto CrowBite = BiteInfo(Vector3(2.0f, 10.0f, 60.0f), 14);
|
const auto CrowBite = BiteInfo(Vector3(2.0f, 10.0f, 60.0f), 14);
|
||||||
|
|
||||||
// TODO
|
enum EagleOrCrowState
|
||||||
enum EagleState
|
|
||||||
{
|
{
|
||||||
|
// No state 0.
|
||||||
|
EAGLE_CROW_STATE_FLY = 1,
|
||||||
|
EAGLE_CROW_STATE_IDLE = 2,
|
||||||
|
EAGLE_CROW_STATE_PLANE = 3,
|
||||||
|
EAGLE_CROW_STATE_DEATH_START = 4,
|
||||||
|
EAGLE_CROW_STATE_DEATH_END = 5,
|
||||||
|
EAGLE_CROW_STATE_ATTACK = 6
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO
|
enum EagleOrCrowAnim
|
||||||
enum EagleAnim
|
|
||||||
{
|
{
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
#include "Game/collision/collide_item.h"
|
#include "Game/collision/collide_item.h"
|
||||||
#include "Game/control/box.h"
|
#include "Game/control/box.h"
|
||||||
#include "Game/itemdata/creature_info.h"
|
#include "Game/itemdata/creature_info.h"
|
||||||
|
#include "Objects/Utils/object_helper.h"
|
||||||
#include "Specific/level.h"
|
#include "Specific/level.h"
|
||||||
#include "Specific/setup.h"
|
#include "Specific/setup.h"
|
||||||
|
|
||||||
|
@ -156,16 +157,7 @@ static void StartEntity(ObjectInfo* obj)
|
||||||
obj = &Objects[ID_GOON_SILENCER2];
|
obj = &Objects[ID_GOON_SILENCER2];
|
||||||
if (obj->loaded)
|
if (obj->loaded)
|
||||||
{
|
{
|
||||||
if (Objects[ID_GOON_SILENCER1].loaded)
|
AssignObjectAnimations(*obj, ID_GOON_SILENCER1, "ID_GOON_SILENCER2", "ID_GOON_SILENCER1");
|
||||||
{
|
|
||||||
obj->animIndex = Objects[ID_GOON_SILENCER1].animIndex;
|
|
||||||
obj->frameBase = Objects[ID_GOON_SILENCER1].frameBase;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
TENLog("ID_GOON_SILENCER1 not found.", LogLevel::Warning);
|
|
||||||
}
|
|
||||||
|
|
||||||
obj->initialise = InitialiseCreature;
|
obj->initialise = InitialiseCreature;
|
||||||
obj->collision = CreatureCollision;
|
obj->collision = CreatureCollision;
|
||||||
obj->control = SilencerControl;
|
obj->control = SilencerControl;
|
||||||
|
@ -183,16 +175,7 @@ static void StartEntity(ObjectInfo* obj)
|
||||||
obj = &Objects[ID_GOON_SILENCER3];
|
obj = &Objects[ID_GOON_SILENCER3];
|
||||||
if (obj->loaded)
|
if (obj->loaded)
|
||||||
{
|
{
|
||||||
if (Objects[ID_GOON_SILENCER1].loaded)
|
AssignObjectAnimations(*obj, ID_GOON_SILENCER1, "ID_GOON_SILENCER3", "ID_GOON_SILENCER1");
|
||||||
{
|
|
||||||
obj->animIndex = Objects[ID_GOON_SILENCER1].animIndex;
|
|
||||||
obj->frameBase = Objects[ID_GOON_SILENCER1].frameBase;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
TENLog("ID_GOON_SILENCER1 not found.", LogLevel::Warning);
|
|
||||||
}
|
|
||||||
|
|
||||||
obj->initialise = InitialiseCreature;
|
obj->initialise = InitialiseCreature;
|
||||||
obj->collision = CreatureCollision;
|
obj->collision = CreatureCollision;
|
||||||
obj->control = SilencerControl;
|
obj->control = SilencerControl;
|
||||||
|
@ -371,16 +354,7 @@ static void StartEntity(ObjectInfo* obj)
|
||||||
obj = &Objects[ID_MERCENARY_AUTOPISTOLS2];
|
obj = &Objects[ID_MERCENARY_AUTOPISTOLS2];
|
||||||
if (obj->loaded)
|
if (obj->loaded)
|
||||||
{
|
{
|
||||||
if (Objects[ID_MERCENARY_AUTOPISTOLS1].loaded)
|
AssignObjectAnimations(*obj, ID_MERCENARY_AUTOPISTOLS1, "ID_MERCENARY_AUTOPISTOLS2", "ID_MERCENARY_AUTOPISTOLS1");
|
||||||
{
|
|
||||||
obj->animIndex = Objects[ID_MERCENARY_AUTOPISTOLS1].animIndex;
|
|
||||||
obj->frameBase = Objects[ID_MERCENARY_AUTOPISTOLS1].frameBase;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
TENLog("ID_MERCENARY_AUTOPISTOLS1 not found.", LogLevel::Warning);
|
|
||||||
}
|
|
||||||
|
|
||||||
obj->initialise = InitialiseCreature;
|
obj->initialise = InitialiseCreature;
|
||||||
obj->collision = CreatureCollision;
|
obj->collision = CreatureCollision;
|
||||||
obj->control = MercenaryAutoPistolControl;
|
obj->control = MercenaryAutoPistolControl;
|
||||||
|
@ -427,6 +401,7 @@ static void StartEntity(ObjectInfo* obj)
|
||||||
obj = &Objects[ID_SWORD_GUARDIAN];
|
obj = &Objects[ID_SWORD_GUARDIAN];
|
||||||
if (obj->loaded)
|
if (obj->loaded)
|
||||||
{
|
{
|
||||||
|
CheckIfSlotExists(ID_SWORD_GUARDIAN_STATUE, "ID_SWORD_GUARDIAN", "ID_SWORD_GUARDIAN_STATUE");
|
||||||
obj->initialise = InitialiseCreature;
|
obj->initialise = InitialiseCreature;
|
||||||
obj->collision = CreatureCollision;
|
obj->collision = CreatureCollision;
|
||||||
obj->control = SwordGuardianControl;
|
obj->control = SwordGuardianControl;
|
||||||
|
@ -443,6 +418,7 @@ static void StartEntity(ObjectInfo* obj)
|
||||||
obj = &Objects[ID_SPEAR_GUARDIAN];
|
obj = &Objects[ID_SPEAR_GUARDIAN];
|
||||||
if (obj->loaded)
|
if (obj->loaded)
|
||||||
{
|
{
|
||||||
|
CheckIfSlotExists(ID_SPEAR_GUARDIAN_STATUE, "ID_SPEAR_GUARDIAN", "ID_SPEAR_GUARDIAN_STATUE");
|
||||||
obj->initialise = InitialiseSpearGuardian;
|
obj->initialise = InitialiseSpearGuardian;
|
||||||
obj->collision = CreatureCollision;
|
obj->collision = CreatureCollision;
|
||||||
obj->control = SpearGuardianControl;
|
obj->control = SpearGuardianControl;
|
||||||
|
@ -459,9 +435,7 @@ static void StartEntity(ObjectInfo* obj)
|
||||||
obj = &Objects[ID_DRAGON_FRONT];
|
obj = &Objects[ID_DRAGON_FRONT];
|
||||||
if (obj->loaded)
|
if (obj->loaded)
|
||||||
{
|
{
|
||||||
if (!Objects[ID_DRAGON_BACK].loaded)
|
CheckIfSlotExists(ID_DRAGON_BACK, "ID_DRAGON_FRONT", "ID_DRAGON_BACK");
|
||||||
TENLog("ID_DRAGON_FRONT needs ID_DRAGON_BACK.", LogLevel::Warning);
|
|
||||||
|
|
||||||
obj->initialise = InitialiseCreature;
|
obj->initialise = InitialiseCreature;
|
||||||
obj->collision = DragonCollision;
|
obj->collision = DragonCollision;
|
||||||
obj->control = DragonControl;
|
obj->control = DragonControl;
|
||||||
|
@ -476,19 +450,18 @@ static void StartEntity(ObjectInfo* obj)
|
||||||
obj = &Objects[ID_DRAGON_BACK];
|
obj = &Objects[ID_DRAGON_BACK];
|
||||||
if (obj->loaded)
|
if (obj->loaded)
|
||||||
{
|
{
|
||||||
if (!Objects[ID_MARCO_BARTOLI].loaded)
|
CheckIfSlotExists(ID_MARCO_BARTOLI, "ID_DRAGON_BACK", "ID_MARCO_BARTOLI");
|
||||||
TENLog("ID_DRAGON_BACK needs ID_MARCO_BARTOLI.", LogLevel::Warning);
|
|
||||||
|
|
||||||
obj->initialise = InitialiseCreature;
|
obj->initialise = InitialiseCreature;
|
||||||
obj->collision = DragonCollision;
|
obj->collision = DragonCollision;
|
||||||
obj->control = DragonControl;
|
obj->control = DragonControl;
|
||||||
obj->radius = 256;
|
obj->radius = 256;
|
||||||
obj->SetupHitEffect(true);
|
obj->SetupHitEffect();
|
||||||
}
|
}
|
||||||
|
|
||||||
obj = &Objects[ID_MARCO_BARTOLI];
|
obj = &Objects[ID_MARCO_BARTOLI];
|
||||||
if (obj->loaded)
|
if (obj->loaded)
|
||||||
{
|
{
|
||||||
|
CheckIfSlotExists(ID_DRAGON_BACK, "ID_MARCO_BARTOLI", "ID_DRAGON_BACK");
|
||||||
obj->initialise = InitialiseBartoli;
|
obj->initialise = InitialiseBartoli;
|
||||||
obj->control = BartoliControl;
|
obj->control = BartoliControl;
|
||||||
}
|
}
|
||||||
|
|
397
TombEngine/Objects/TR3/Entity/Lizard.cpp
Normal file
397
TombEngine/Objects/TR3/Entity/Lizard.cpp
Normal file
|
@ -0,0 +1,397 @@
|
||||||
|
#include "framework.h"
|
||||||
|
#include "Objects/TR3/Entity/Lizard.h"
|
||||||
|
|
||||||
|
#include "Game/control/box.h"
|
||||||
|
#include "Game/effects/tomb4fx.h"
|
||||||
|
#include "Game/Lara/lara_helpers.h"
|
||||||
|
#include "Game/misc.h"
|
||||||
|
#include "Game/people.h"
|
||||||
|
#include "Specific/level.h"
|
||||||
|
|
||||||
|
namespace TEN::Entities::Creatures::TR3
|
||||||
|
{
|
||||||
|
constexpr auto LIZARD_ATTACK_1_DAMAGE = 120;
|
||||||
|
constexpr auto LIZARD_ATTACK_2_DAMAGE = 100;
|
||||||
|
|
||||||
|
constexpr auto LIZARD_ATTACK_0_RANGE = SQUARE(BLOCK(2.5f));
|
||||||
|
constexpr auto LIZARD_ATTACK_1_RANGE = SQUARE(BLOCK(0.75f));
|
||||||
|
constexpr auto LIZARD_ATTACK_2_RANGE = SQUARE(BLOCK(1.5f));
|
||||||
|
constexpr auto LIZARD_WALK_RANGE = SQUARE(BLOCK(2));
|
||||||
|
|
||||||
|
constexpr auto LIZARD_WALK_CHANCE = 1 / 256.0f;
|
||||||
|
constexpr auto LIZARD_BORED_WALK_CHANCE = 1 / 512.0f;
|
||||||
|
constexpr auto LIZARD_WAIT_CHANCE = 1 / 256.0f;
|
||||||
|
|
||||||
|
constexpr auto LIZARD_WALK_TURN_RATE_MAX = ANGLE(10.0f);
|
||||||
|
constexpr auto LIZARD_RUN_TURN_RATE_MAX = ANGLE(4.0f);
|
||||||
|
|
||||||
|
constexpr auto LIZARD_VAULT_SHIFT = 260;
|
||||||
|
|
||||||
|
const auto LizardBiteAttackBite = BiteInfo(Vector3(0.0f, -120.0f, 120.0f), 10);
|
||||||
|
const auto LizardSwipeAttackBite = BiteInfo(Vector3::Zero, 5);
|
||||||
|
const auto LizardGasBite = BiteInfo(Vector3(0.0f, -64.0f, 56.0f), 9);
|
||||||
|
const auto LizardSwipeAttackJoints = std::vector<unsigned int>{ 5 };
|
||||||
|
const auto LizardBiteAttackJoints = std::vector<unsigned int>{ 10 };
|
||||||
|
|
||||||
|
enum LizardState
|
||||||
|
{
|
||||||
|
// No state 0.
|
||||||
|
LIZARD_STATE_IDLE = 1,
|
||||||
|
LIZARD_STATE_WALK_FORWARD = 2,
|
||||||
|
LIZARD_STATE_PUNCH_2 = 3,
|
||||||
|
LIZARD_STATE_AIM_2 = 4,
|
||||||
|
LIZARD_STATE_WAIT = 5,
|
||||||
|
LIZARD_STATE_AIM_1 = 6,
|
||||||
|
LIZARD_STATE_AIM_0 = 7,
|
||||||
|
LIZARD_STATE_PUNCH_1 = 8,
|
||||||
|
LIZARD_STATE_PUNCH_0 = 9,
|
||||||
|
LIZARD_STATE_RUN_FORWARD = 10,
|
||||||
|
LIZARD_STATE_DEATH = 11,
|
||||||
|
LIZARD_STATE_VAULT_3_STEPS_UP = 12,
|
||||||
|
LIZARD_STATE_VAULT_1_STEP_UP = 13,
|
||||||
|
LIZARD_STATE_VAULT_2_STEPS_UP = 14,
|
||||||
|
LIZARD_STATE_VAULT_4_STEPS_DOWN = 15
|
||||||
|
};
|
||||||
|
|
||||||
|
enum LizardAnim
|
||||||
|
{
|
||||||
|
LIZARD_ANIM_SLIDE_1 = 23,
|
||||||
|
LIZARD_ANIM_DEATH = 26,
|
||||||
|
LIZARD_ANIM_VAULT_4_STEPS_UP = 27,
|
||||||
|
LIZARD_ANIM_VAULT_2_STEPS_UP = 28,
|
||||||
|
LIZARD_ANIM_VAULT_3_STEPS_UP = 29,
|
||||||
|
LIZARD_ANIM_VAULT_4_STEPS_DOWN = 30,
|
||||||
|
LIZARD_ANIM_SLIDE_2 = 31
|
||||||
|
};
|
||||||
|
|
||||||
|
static bool IsLizardTargetBlocked(ItemInfo& item)
|
||||||
|
{
|
||||||
|
auto& creature = *GetCreatureInfo(&item);
|
||||||
|
|
||||||
|
return (creature.Enemy && creature.Enemy->BoxNumber != NO_BOX &&
|
||||||
|
(g_Level.Boxes[creature.Enemy->BoxNumber].flags & BLOCKABLE));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void SpawnLizardGas(int itemNumber, const BiteInfo& bite, int speed)
|
||||||
|
{
|
||||||
|
static constexpr auto numPoisonThrows = 2;
|
||||||
|
|
||||||
|
for (int i = 0; i < numPoisonThrows; i++)
|
||||||
|
ThrowPoison(itemNumber, bite.meshNum, Vector3i(bite.Position), Vector3i(0.0f, -100.0f, speed << 2), Vector3(0.0f, 1.0f, 0.0f));
|
||||||
|
|
||||||
|
ThrowPoison(itemNumber, bite.meshNum, Vector3i(bite.Position), Vector3i(0.0f, -100.0f, speed << 1), Vector3(0.0f, 1.0f, 0.0f));
|
||||||
|
}
|
||||||
|
|
||||||
|
void LizardControl(short itemNumber)
|
||||||
|
{
|
||||||
|
if (!CreatureActive(itemNumber))
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto& item = g_Level.Items[itemNumber];
|
||||||
|
auto& creature = *GetCreatureInfo(&item);
|
||||||
|
|
||||||
|
short headingAngle = 0;
|
||||||
|
short tiltAngle = 0;
|
||||||
|
auto headOrient = EulerAngles::Zero;
|
||||||
|
|
||||||
|
if (item.HitPoints <= 0)
|
||||||
|
{
|
||||||
|
// Avoid doing the death animation if summoned.
|
||||||
|
if (item.Animation.ActiveState != LIZARD_STATE_DEATH)
|
||||||
|
SetAnimation(&item, LIZARD_ANIM_DEATH);
|
||||||
|
|
||||||
|
// Explode if summoned.
|
||||||
|
if (item.TestFlagField(0, 1) && item.Animation.FrameNumber == GetFrameNumber(&item, 50))
|
||||||
|
CreatureDie(itemNumber, true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AI_INFO ai;
|
||||||
|
CreatureAIInfo(&item, &ai);
|
||||||
|
GetCreatureMood(&item, &ai, true);
|
||||||
|
CreatureMood(&item, &ai, true);
|
||||||
|
|
||||||
|
if (IsLizardTargetBlocked(item))
|
||||||
|
creature.Mood = MoodType::Attack;
|
||||||
|
|
||||||
|
headingAngle = CreatureTurn(&item, creature.MaxTurn);
|
||||||
|
|
||||||
|
// Turn head. Avoid while climbing or falling.
|
||||||
|
if (ai.ahead &&
|
||||||
|
item.Animation.ActiveState < LIZARD_STATE_DEATH)
|
||||||
|
{
|
||||||
|
headOrient.x = ai.xAngle;
|
||||||
|
headOrient.y = ai.angle;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isPlayerPoisonedOrTargetBlocked =
|
||||||
|
(creature.Enemy != nullptr && GetLaraInfo(creature.Enemy)->PoisonPotency < 256) ||
|
||||||
|
IsLizardTargetBlocked(item);
|
||||||
|
|
||||||
|
switch (item.Animation.ActiveState)
|
||||||
|
{
|
||||||
|
case LIZARD_STATE_IDLE:
|
||||||
|
creature.MaxTurn = 0;
|
||||||
|
creature.Flags = 0;
|
||||||
|
|
||||||
|
if (creature.Mood == MoodType::Escape)
|
||||||
|
{
|
||||||
|
item.Animation.TargetState = LIZARD_STATE_RUN_FORWARD;
|
||||||
|
}
|
||||||
|
else if (creature.Mood == MoodType::Bored)
|
||||||
|
{
|
||||||
|
if (item.Animation.RequiredState)
|
||||||
|
{
|
||||||
|
item.Animation.TargetState = item.Animation.RequiredState;
|
||||||
|
}
|
||||||
|
else if (Random::TestProbability(LIZARD_BORED_WALK_CHANCE))
|
||||||
|
{
|
||||||
|
item.Animation.TargetState = LIZARD_STATE_WALK_FORWARD;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
item.Animation.TargetState = LIZARD_STATE_WAIT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (ai.bite && ai.distance < LIZARD_ATTACK_1_RANGE)
|
||||||
|
{
|
||||||
|
item.Animation.TargetState = LIZARD_STATE_AIM_1;
|
||||||
|
}
|
||||||
|
else if (Targetable(&item, &ai) && ai.bite &&
|
||||||
|
ai.distance < LIZARD_ATTACK_0_RANGE && isPlayerPoisonedOrTargetBlocked)
|
||||||
|
{
|
||||||
|
item.Animation.TargetState = LIZARD_STATE_AIM_0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
item.Animation.TargetState = LIZARD_STATE_RUN_FORWARD;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case LIZARD_STATE_WAIT:
|
||||||
|
creature.MaxTurn = 0;
|
||||||
|
|
||||||
|
if (creature.Mood != MoodType::Bored)
|
||||||
|
{
|
||||||
|
item.Animation.TargetState = LIZARD_STATE_IDLE;
|
||||||
|
}
|
||||||
|
else if (Random::TestProbability(LIZARD_WALK_CHANCE))
|
||||||
|
{
|
||||||
|
item.Animation.RequiredState = LIZARD_STATE_WALK_FORWARD;
|
||||||
|
item.Animation.TargetState = LIZARD_STATE_IDLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case LIZARD_STATE_WALK_FORWARD:
|
||||||
|
if (TestAnimNumber(item, LIZARD_ANIM_SLIDE_1) || TestAnimNumber(item, LIZARD_ANIM_SLIDE_2))
|
||||||
|
creature.MaxTurn = 0;
|
||||||
|
else
|
||||||
|
creature.MaxTurn = LIZARD_WALK_TURN_RATE_MAX;
|
||||||
|
|
||||||
|
if (creature.Mood == MoodType::Escape)
|
||||||
|
{
|
||||||
|
item.Animation.TargetState = LIZARD_STATE_RUN_FORWARD;
|
||||||
|
}
|
||||||
|
else if (creature.Mood == MoodType::Bored)
|
||||||
|
{
|
||||||
|
if (Random::TestProbability(LIZARD_WAIT_CHANCE))
|
||||||
|
{
|
||||||
|
item.Animation.RequiredState = LIZARD_STATE_WAIT;
|
||||||
|
item.Animation.TargetState = LIZARD_STATE_IDLE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (ai.bite && ai.distance < LIZARD_ATTACK_1_RANGE)
|
||||||
|
{
|
||||||
|
item.Animation.TargetState = LIZARD_STATE_IDLE;
|
||||||
|
}
|
||||||
|
else if (ai.bite && ai.distance < LIZARD_ATTACK_2_RANGE)
|
||||||
|
{
|
||||||
|
item.Animation.TargetState = LIZARD_STATE_AIM_2;
|
||||||
|
}
|
||||||
|
else if (Targetable(&item, &ai) && ai.distance < LIZARD_ATTACK_0_RANGE &&
|
||||||
|
isPlayerPoisonedOrTargetBlocked)
|
||||||
|
{
|
||||||
|
item.Animation.TargetState = LIZARD_STATE_IDLE;
|
||||||
|
}
|
||||||
|
else if (ai.distance > LIZARD_WALK_RANGE)
|
||||||
|
{
|
||||||
|
item.Animation.TargetState = LIZARD_STATE_RUN_FORWARD;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case LIZARD_STATE_RUN_FORWARD:
|
||||||
|
creature.MaxTurn = LIZARD_RUN_TURN_RATE_MAX;
|
||||||
|
tiltAngle = headingAngle / 2;
|
||||||
|
|
||||||
|
if (creature.Mood == MoodType::Escape)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if (creature.Mood == MoodType::Bored)
|
||||||
|
{
|
||||||
|
item.Animation.TargetState = LIZARD_STATE_WALK_FORWARD;
|
||||||
|
}
|
||||||
|
else if (ai.bite && ai.distance < LIZARD_ATTACK_1_RANGE)
|
||||||
|
{
|
||||||
|
item.Animation.TargetState = LIZARD_STATE_IDLE;
|
||||||
|
}
|
||||||
|
else if (Targetable(&item, &ai) && ai.distance < LIZARD_ATTACK_0_RANGE &&
|
||||||
|
isPlayerPoisonedOrTargetBlocked)
|
||||||
|
{
|
||||||
|
item.Animation.TargetState = LIZARD_STATE_IDLE;
|
||||||
|
}
|
||||||
|
else if (ai.ahead && ai.distance < LIZARD_WALK_RANGE)
|
||||||
|
{
|
||||||
|
item.Animation.TargetState = LIZARD_STATE_WALK_FORWARD;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case LIZARD_STATE_AIM_0:
|
||||||
|
creature.MaxTurn = 0;
|
||||||
|
creature.Flags = 0;
|
||||||
|
|
||||||
|
if (abs(ai.angle) < LIZARD_RUN_TURN_RATE_MAX)
|
||||||
|
{
|
||||||
|
item.Pose.Orientation.y += ai.angle;
|
||||||
|
}
|
||||||
|
else if (ai.angle < 0)
|
||||||
|
{
|
||||||
|
item.Pose.Orientation.y -= LIZARD_RUN_TURN_RATE_MAX;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
item.Pose.Orientation.y += LIZARD_RUN_TURN_RATE_MAX;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Maybe we should add targetable as well? -- TokyoSU
|
||||||
|
if (ai.bite && ai.distance < LIZARD_ATTACK_0_RANGE && isPlayerPoisonedOrTargetBlocked)
|
||||||
|
item.Animation.TargetState = LIZARD_STATE_PUNCH_0;
|
||||||
|
else
|
||||||
|
item.Animation.TargetState = LIZARD_STATE_IDLE;
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case LIZARD_STATE_AIM_1:
|
||||||
|
creature.MaxTurn = LIZARD_WALK_TURN_RATE_MAX;
|
||||||
|
creature.Flags = 0;
|
||||||
|
|
||||||
|
if (ai.ahead && ai.distance < LIZARD_ATTACK_1_RANGE)
|
||||||
|
item.Animation.TargetState = LIZARD_STATE_PUNCH_1;
|
||||||
|
else
|
||||||
|
item.Animation.TargetState = LIZARD_STATE_IDLE;
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case LIZARD_STATE_AIM_2:
|
||||||
|
creature.MaxTurn = LIZARD_WALK_TURN_RATE_MAX;
|
||||||
|
creature.Flags = 0;
|
||||||
|
|
||||||
|
if (ai.ahead && ai.distance < LIZARD_ATTACK_2_RANGE)
|
||||||
|
item.Animation.TargetState = LIZARD_STATE_PUNCH_2;
|
||||||
|
else
|
||||||
|
item.Animation.TargetState = LIZARD_STATE_IDLE;
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case LIZARD_STATE_PUNCH_0:
|
||||||
|
creature.MaxTurn = 0;
|
||||||
|
|
||||||
|
if (abs(ai.angle) < LIZARD_RUN_TURN_RATE_MAX)
|
||||||
|
{
|
||||||
|
item.Pose.Orientation.y += ai.angle;
|
||||||
|
}
|
||||||
|
else if (ai.angle < 0)
|
||||||
|
{
|
||||||
|
item.Pose.Orientation.y -= LIZARD_RUN_TURN_RATE_MAX;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
item.Pose.Orientation.y += LIZARD_RUN_TURN_RATE_MAX;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TestAnimFrameRange(item, 7, 28))
|
||||||
|
{
|
||||||
|
if (creature.Flags < 24)
|
||||||
|
creature.Flags += 2;
|
||||||
|
|
||||||
|
if (creature.Flags < 24)
|
||||||
|
SpawnLizardGas(itemNumber, LizardGasBite, creature.Flags);
|
||||||
|
else
|
||||||
|
SpawnLizardGas(itemNumber, LizardGasBite, (GetRandomControl() & 15) + 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TestAnimFrame(item, 28))
|
||||||
|
creature.Flags = 0;
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case LIZARD_STATE_PUNCH_1:
|
||||||
|
creature.MaxTurn = 0;
|
||||||
|
|
||||||
|
if (!creature.Flags && item.TouchBits.Test(LizardSwipeAttackJoints))
|
||||||
|
{
|
||||||
|
DoDamage(creature.Enemy, LIZARD_ATTACK_1_DAMAGE);
|
||||||
|
CreatureEffect(&item, LizardSwipeAttackBite, DoBloodSplat);
|
||||||
|
SoundEffect(SFX_TR4_LARA_THUD, &item.Pose);
|
||||||
|
creature.Flags = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ai.distance < LIZARD_ATTACK_2_RANGE)
|
||||||
|
item.Animation.TargetState = LIZARD_STATE_PUNCH_2;
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case LIZARD_STATE_PUNCH_2:
|
||||||
|
creature.MaxTurn = 0;
|
||||||
|
|
||||||
|
if (creature.Flags != 2 && item.TouchBits.Test(LizardBiteAttackJoints))
|
||||||
|
{
|
||||||
|
DoDamage(creature.Enemy, LIZARD_ATTACK_2_DAMAGE);
|
||||||
|
CreatureEffect(&item, LizardSwipeAttackBite, DoBloodSplat);
|
||||||
|
SoundEffect(SFX_TR4_LARA_THUD, &item.Pose);
|
||||||
|
creature.Flags = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CreatureTilt(&item, tiltAngle);
|
||||||
|
CreatureJoint(&item, 0, headOrient.x);
|
||||||
|
CreatureJoint(&item, 1, headOrient.y);
|
||||||
|
|
||||||
|
if (item.Animation.ActiveState < LIZARD_STATE_DEATH)
|
||||||
|
{
|
||||||
|
switch (CreatureVault(itemNumber, headingAngle, 2, LIZARD_VAULT_SHIFT))
|
||||||
|
{
|
||||||
|
case 2:
|
||||||
|
SetAnimation(&item, LIZARD_ANIM_VAULT_2_STEPS_UP);
|
||||||
|
creature.MaxTurn = 0;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 3:
|
||||||
|
SetAnimation(&item, LIZARD_ANIM_VAULT_3_STEPS_UP);
|
||||||
|
creature.MaxTurn = 0;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 4:
|
||||||
|
SetAnimation(&item, LIZARD_ANIM_VAULT_4_STEPS_UP);
|
||||||
|
creature.MaxTurn = 0;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case -4:
|
||||||
|
SetAnimation(&item, LIZARD_ANIM_VAULT_4_STEPS_DOWN);
|
||||||
|
creature.MaxTurn = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
CreatureAnimation(itemNumber, headingAngle, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
9
TombEngine/Objects/TR3/Entity/Lizard.h
Normal file
9
TombEngine/Objects/TR3/Entity/Lizard.h
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
struct BiteInfo;
|
||||||
|
struct ItemInfo;
|
||||||
|
|
||||||
|
namespace TEN::Entities::Creatures::TR3
|
||||||
|
{
|
||||||
|
void LizardControl(short itemNumber);
|
||||||
|
}
|
529
TombEngine/Objects/TR3/Entity/PunaBoss.cpp
Normal file
529
TombEngine/Objects/TR3/Entity/PunaBoss.cpp
Normal file
|
@ -0,0 +1,529 @@
|
||||||
|
#include "framework.h"
|
||||||
|
#include "Objects/TR3/Entity/PunaBoss.h"
|
||||||
|
|
||||||
|
#include "Game/control/box.h"
|
||||||
|
#include "Game/control/los.h"
|
||||||
|
#include "Game/effects/effects.h"
|
||||||
|
#include "Game/effects/item_fx.h"
|
||||||
|
#include "Game/effects/lightning.h"
|
||||||
|
#include "Game/Lara/lara_helpers.h"
|
||||||
|
#include "Game/misc.h"
|
||||||
|
#include "Math/Math.h"
|
||||||
|
#include "Objects/Effects/Boss.h"
|
||||||
|
#include "Specific/level.h"
|
||||||
|
#include "Specific/setup.h"
|
||||||
|
|
||||||
|
using namespace TEN::Effects::Boss;
|
||||||
|
using namespace TEN::Effects::Items;
|
||||||
|
using namespace TEN::Effects::Lightning;
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
const auto PunaBossHeadBite = BiteInfo(Vector3::Zero, 8);
|
||||||
|
const auto PunaBossHandBite = BiteInfo(Vector3::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,
|
||||||
|
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;
|
||||||
|
|
||||||
|
auto pos = GetJointPosition(&item, PunaBossHeadBite.meshNum).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, 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();
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void SpawnPunaLightning(ItemInfo& item, const Vector3& pos, const BiteInfo& bite, bool isSummon)
|
||||||
|
{
|
||||||
|
const auto& creature = *GetCreatureInfo(&item);
|
||||||
|
|
||||||
|
auto origin = GameVector(GetJointPosition(&item, bite.meshNum, bite.Position), item.RoomNumber);
|
||||||
|
|
||||||
|
if (isSummon)
|
||||||
|
{
|
||||||
|
auto target = GameVector(pos, item.RoomNumber);
|
||||||
|
|
||||||
|
TriggerLightning(&origin.ToVector3i(), &target.ToVector3i(), 1, 0, 255, 180, 30, LI_THININ | LI_SPLINE | LI_MOVEEND, 8, 12);
|
||||||
|
TriggerLightning(&origin.ToVector3i(), &target.ToVector3i(), 1, 180, 255, 0, 30, LI_THININ | LI_SPLINE | LI_MOVEEND, 3, 12);
|
||||||
|
TriggerLightning(&origin.ToVector3i(), &target.ToVector3i(), Random::GenerateInt(25, 50), 100, 200, 200, 30, LI_THININ | LI_THINOUT, 4, 12);
|
||||||
|
TriggerLightning(&origin.ToVector3i(), &target.ToVector3i(), Random::GenerateInt(25, 50), 100, 250, 255, 30, LI_THININ | LI_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);
|
||||||
|
|
||||||
|
TriggerLightning(&origin.ToVector3i(), &target2.ToVector3i(), Random::GenerateInt(15, 40), 20, 160, 160, 20, LI_THINOUT | LI_THININ, 4, 6);
|
||||||
|
TriggerLightning(&origin.ToVector3i(), &target2.ToVector3i(), Random::GenerateInt(25, 35), 20, 160, 160, 20, LI_THINOUT | LI_THININ, 2, 7);
|
||||||
|
|
||||||
|
TriggerLightning(&target2.ToVector3i(), &origin1.ToVector3i(), Random::GenerateInt(15, 40), 20, 160, 160, 20, LI_THINOUT | LI_THININ, 4, 6);
|
||||||
|
TriggerLightning(&target2.ToVector3i(), &origin1.ToVector3i(), Random::GenerateInt(25, 35), 20, 160, 160, 20, LI_THINOUT | LI_THININ, 2, 7);
|
||||||
|
|
||||||
|
TriggerLightning(&origin1.ToVector3i(), &target3.ToVector3i(), Random::GenerateInt(15, 40), 20, 160, 160, 20, LI_THINOUT | LI_THININ, 4, 9);
|
||||||
|
TriggerLightning(&origin1.ToVector3i(), &target3.ToVector3i(), Random::GenerateInt(25, 35), 20, 160, 160, 20, LI_THINOUT | LI_THININ, 2, 10);
|
||||||
|
|
||||||
|
TriggerLightning(&origin2.ToVector3i(), &target3.ToVector3i(), Random::GenerateInt(15, 40), 20, 160, 160, 16, LI_THINOUT | LI_THININ, 4, 7);
|
||||||
|
TriggerLightning(&origin2.ToVector3i(), &target3.ToVector3i(), Random::GenerateInt(25, 35), 20, 160, 160, 16, LI_THINOUT | LI_THININ, 2, 8);
|
||||||
|
|
||||||
|
TriggerLightning(&origin.ToVector3i(), &target.ToVector3i(), 1, 20, 160, 160, 30, LI_THININ | LI_SPLINE | LI_MOVEEND, 12, 12);
|
||||||
|
TriggerLightning(&origin.ToVector3i(), &target.ToVector3i(), 1, 80, 160, 160, 30, LI_THININ | LI_SPLINE | LI_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 InitialisePuna(short itemNumber)
|
||||||
|
{
|
||||||
|
auto& item = g_Level.Items[itemNumber];
|
||||||
|
|
||||||
|
InitialiseCreature(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.
|
||||||
|
item.SetFlagField((int)BossItemFlags::Rotation, item.Pose.Orientation.y + ANGLE(180.0f));
|
||||||
|
item.SetFlagField((int)BossItemFlags::AttackType, (int)PunaAttackType::AwaitPlayer); // Normal behaviour at start.
|
||||||
|
item.SetFlagField((int)BossItemFlags::ShieldIsEnabled, 1); // Activated at start.
|
||||||
|
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))
|
||||||
|
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;
|
||||||
|
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);
|
||||||
|
item.ItemFlags[(int)BossItemFlags::DeathCount] = 1;
|
||||||
|
creature.MaxTurn = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int frameEnd = g_Level.Anims[object.animIndex + PUNA_ANIM_DEATH].frameEnd;
|
||||||
|
if (item.Animation.FrameNumber >= frameEnd)
|
||||||
|
{
|
||||||
|
// Avoid having the object stop working.
|
||||||
|
item.Animation.FrameNumber = frameEnd;
|
||||||
|
item.MeshBits.ClearAll();
|
||||||
|
|
||||||
|
if (item.GetFlagField((int)BossItemFlags::ExplodeCount) < PUNA_EXPLOSION_NUM_MAX)
|
||||||
|
item.ItemFlags[(int)BossItemFlags::ExplodeCount]++;
|
||||||
|
|
||||||
|
// Do explosion effect.
|
||||||
|
ExplodeBoss(itemNumber, item, PUNA_EXPLOSION_NUM_MAX, PUNA_EFFECT_COLOR);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
auto deathCount = item.GetFlagField((int)BossItemFlags::DeathCount);
|
||||||
|
item.Pose.Orientation.z = (Random::GenerateInt() % deathCount) - (item.ItemFlags[(int)BossItemFlags::DeathCount] >> 1);
|
||||||
|
|
||||||
|
if (deathCount < 2048)
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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))
|
||||||
|
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.
|
||||||
|
if (item.TestFlagField((int)BossItemFlags::AttackType, (int)PunaAttackType::DeathLightning))
|
||||||
|
{
|
||||||
|
creature.Target = creature.Enemy->Pose.Position;
|
||||||
|
}
|
||||||
|
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.
|
||||||
|
item.SetFlagField((int)BossItemFlags::ItemNumber, (short)GetLizardItemNumber(item));
|
||||||
|
creature.Target = GetLizardTargetPosition(item);
|
||||||
|
}
|
||||||
|
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.
|
||||||
|
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.
|
||||||
|
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.
|
||||||
|
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;
|
||||||
|
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 &&
|
||||||
|
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);
|
||||||
|
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;
|
||||||
|
|
||||||
|
if (item.TestFlags((int)BossItemFlags::Object, (short)BossFlagValue::Lizard) && isLizardActiveNearby)
|
||||||
|
item.ItemFlags[(int)BossItemFlags::AttackCount]++;
|
||||||
|
}
|
||||||
|
else if (item.ItemFlags[(int)BossItemFlags::AttackCount] >= PUNA_HEAD_ATTACK_NUM_MAX &&
|
||||||
|
creature.Enemy->HitPoints > 0 &&
|
||||||
|
item.ItemFlags[(int)BossItemFlags::AttackType] != (int)PunaAttackType::Wait)
|
||||||
|
{
|
||||||
|
item.SetFlagField((int)BossItemFlags::AttackType, (int)PunaAttackType::SummonLightning);
|
||||||
|
|
||||||
|
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:
|
||||||
|
item.SetFlagField((int)BossItemFlags::ShieldIsEnabled, 0);
|
||||||
|
creature.MaxTurn = 0;
|
||||||
|
|
||||||
|
if (item.Animation.FrameNumber == GetFrameNumber(&item, 14))
|
||||||
|
SpawnPunaLightning(item, targetPos.ToVector3(), PunaBossHeadBite, false);
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PUNA_STATE_HAND_ATTACK:
|
||||||
|
item.SetFlagField((int)BossItemFlags::ShieldIsEnabled, 0);
|
||||||
|
creature.MaxTurn = 0;
|
||||||
|
|
||||||
|
if (item.Animation.FrameNumber == GetFrameNumber(&item, 30))
|
||||||
|
{
|
||||||
|
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 (pos.has_value())
|
||||||
|
{
|
||||||
|
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));
|
||||||
|
|
||||||
|
SpawnShieldAndRichochetSparks(target, pos->ToVector3(), color);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (target.HitStatus)
|
||||||
|
SoundEffect(SFX_TR3_PUNA_BOSS_TAKE_HIT, &target.Pose);
|
||||||
|
|
||||||
|
DoBloodSplat(pos->x, pos->y, pos->z, 5, source.Pose.Orientation.y, pos->RoomNumber);
|
||||||
|
DoItemHit(&target, damage, isExplosive);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
11
TombEngine/Objects/TR3/Entity/PunaBoss.h
Normal file
11
TombEngine/Objects/TR3/Entity/PunaBoss.h
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
class GameVector;
|
||||||
|
struct ItemInfo;
|
||||||
|
|
||||||
|
namespace TEN::Entities::Creatures::TR3
|
||||||
|
{
|
||||||
|
void InitialisePuna(short itemNumber);
|
||||||
|
void PunaControl(short itemNumber);
|
||||||
|
void PunaHit(ItemInfo& target, ItemInfo& source, std::optional<GameVector> pos, int damage, bool isExplosive, int jointIndex);
|
||||||
|
}
|
|
@ -122,9 +122,8 @@ namespace TEN::Entities::Creatures::TR3
|
||||||
|
|
||||||
laraAI.distance = pow(dx, 2) + pow(dx, 2);
|
laraAI.distance = pow(dx, 2) + pow(dx, 2);
|
||||||
|
|
||||||
for (int slot = 0; slot < ActiveCreatures.size(); slot++)
|
for (auto& currentCreature : ActiveCreatures)
|
||||||
{
|
{
|
||||||
auto* currentCreature = ActiveCreatures[slot];
|
|
||||||
if (currentCreature->ItemNumber == NO_ITEM || currentCreature->ItemNumber == itemNumber)
|
if (currentCreature->ItemNumber == NO_ITEM || currentCreature->ItemNumber == itemNumber)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
|
|
@ -105,9 +105,8 @@ namespace TEN::Entities::Creatures::TR3
|
||||||
laraAI.distance = pow(dx, 2) + pow(dx, 2);
|
laraAI.distance = pow(dx, 2) + pow(dx, 2);
|
||||||
|
|
||||||
int bestDistance = INT_MAX;
|
int bestDistance = INT_MAX;
|
||||||
for (int slot = 0; slot < ActiveCreatures.size(); slot++)
|
for (auto& currentCreature : ActiveCreatures)
|
||||||
{
|
{
|
||||||
auto* currentCreature = ActiveCreatures[slot];
|
|
||||||
if (currentCreature->ItemNumber == NO_ITEM || currentCreature->ItemNumber == itemNumber)
|
if (currentCreature->ItemNumber == NO_ITEM || currentCreature->ItemNumber == itemNumber)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
|
|
@ -98,15 +98,12 @@ namespace TEN::Entities::Creatures::TR3
|
||||||
ItemInfo* nearestItem = nullptr;
|
ItemInfo* nearestItem = nullptr;
|
||||||
float minDistance = FLT_MAX;
|
float minDistance = FLT_MAX;
|
||||||
|
|
||||||
for (auto* activeCreature : ActiveCreatures)
|
for (auto& currentCreature : ActiveCreatures)
|
||||||
{
|
{
|
||||||
if (activeCreature->ItemNumber == NO_ITEM || activeCreature->ItemNumber == itemNumber)
|
if (currentCreature->ItemNumber == NO_ITEM || currentCreature->ItemNumber == itemNumber)
|
||||||
{
|
|
||||||
activeCreature++;
|
|
||||||
continue;
|
continue;
|
||||||
}
|
|
||||||
|
|
||||||
auto* targetItem = &g_Level.Items[activeCreature->ItemNumber];
|
auto* targetItem = &g_Level.Items[currentCreature->ItemNumber];
|
||||||
|
|
||||||
int distance = Vector3i::Distance(item->Pose.Position, targetItem->Pose.Position);
|
int distance = Vector3i::Distance(item->Pose.Position, targetItem->Pose.Position);
|
||||||
if (distance < minDistance && item->HitPoints > 0)
|
if (distance < minDistance && item->HitPoints > 0)
|
||||||
|
@ -114,8 +111,6 @@ namespace TEN::Entities::Creatures::TR3
|
||||||
nearestItem = targetItem;
|
nearestItem = targetItem;
|
||||||
minDistance = distance;
|
minDistance = distance;
|
||||||
}
|
}
|
||||||
|
|
||||||
activeCreature++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nearestItem != nullptr &&
|
if (nearestItem != nullptr &&
|
||||||
|
|
|
@ -82,17 +82,7 @@ namespace TEN::Entities::Creatures::TR3
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
void SwapShivaMeshToStone(ItemInfo& item, int jointIndex)
|
static void ShivaDamage(ItemInfo* item, CreatureInfo* creature, int damage)
|
||||||
{
|
|
||||||
item.Model.MeshIndex[jointIndex] = Objects[ID_SHIVA_STATUE].meshIndex + jointIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
void SwapShivaMeshToNormal(ItemInfo& item, int jointIndex)
|
|
||||||
{
|
|
||||||
item.Model.MeshIndex[jointIndex] = Objects[ID_SHIVA].meshIndex + jointIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ShivaDamage(ItemInfo* item, CreatureInfo* creature, int damage)
|
|
||||||
{
|
{
|
||||||
if (!creature->Flags && item->TouchBits.Test(ShivaAttackRightJoints))
|
if (!creature->Flags && item->TouchBits.Test(ShivaAttackRightJoints))
|
||||||
{
|
{
|
||||||
|
@ -111,7 +101,17 @@ namespace TEN::Entities::Creatures::TR3
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DoShivaSwapMesh(ItemInfo& item, bool isDead)
|
static void SwapShivaMeshToStone(ItemInfo& item, int jointIndex)
|
||||||
|
{
|
||||||
|
item.Model.MeshIndex[jointIndex] = Objects[ID_SHIVA_STATUE].meshIndex + jointIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void SwapShivaMeshToNormal(ItemInfo& item, int jointIndex)
|
||||||
|
{
|
||||||
|
item.Model.MeshIndex[jointIndex] = Objects[ID_SHIVA].meshIndex + jointIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool DoShivaSwapMesh(ItemInfo& item, bool isDead)
|
||||||
{
|
{
|
||||||
const auto& object = Objects[item.ObjectNumber];
|
const auto& object = Objects[item.ObjectNumber];
|
||||||
auto& creature = *GetCreatureInfo(&item);
|
auto& creature = *GetCreatureInfo(&item);
|
||||||
|
@ -122,11 +122,11 @@ namespace TEN::Entities::Creatures::TR3
|
||||||
|
|
||||||
if (isDead && item.ItemFlags[0] < 0)
|
if (isDead && item.ItemFlags[0] < 0)
|
||||||
{
|
{
|
||||||
item.SetFlags(2, 0);
|
item.SetFlagField(2, 0);
|
||||||
}
|
}
|
||||||
else if (!isDead && item.ItemFlags[0] >= object.nmeshes)
|
else if (!isDead && item.ItemFlags[0] >= object.nmeshes)
|
||||||
{
|
{
|
||||||
item.SetFlags(2, 0);
|
item.SetFlagField(2, 0);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -154,19 +154,102 @@ namespace TEN::Entities::Creatures::TR3
|
||||||
{
|
{
|
||||||
item.Animation.TargetState = SHIVA_STATE_IDLE;
|
item.Animation.TargetState = SHIVA_STATE_IDLE;
|
||||||
creature.Flags = -45;
|
creature.Flags = -45;
|
||||||
item.SetFlags(1, 0);
|
item.SetFlagField(1, 0);
|
||||||
item.SetFlags(1, 1); // Is alive (for savegame).
|
item.SetFlagField(1, 1); // Is alive (for savegame).
|
||||||
}
|
}
|
||||||
else if (item.TestFlags(2, 0) && isDead)
|
else if (item.TestFlags(2, 0) && isDead)
|
||||||
{
|
{
|
||||||
item.SetFlags(1, 0);
|
item.SetFlagField(1, 0);
|
||||||
item.SetFlags(1, 2); // Is dead.
|
item.SetFlagField(1, 2); // Is dead.
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void SpawnShivaSmoke(const Vector3& pos, int roomNumber)
|
||||||
|
{
|
||||||
|
auto& smoke = *GetFreeParticle();
|
||||||
|
|
||||||
|
bool isUnderwater = TestEnvironment(ENV_FLAG_WATER, Vector3i(pos), roomNumber);
|
||||||
|
auto sphere = BoundingSphere(pos, 16);
|
||||||
|
auto effectPos = Random::GeneratePointInSphere(sphere);
|
||||||
|
|
||||||
|
smoke.on = true;
|
||||||
|
smoke.blendMode = BLEND_MODES::BLENDMODE_ADDITIVE;
|
||||||
|
|
||||||
|
smoke.x = effectPos.x;
|
||||||
|
smoke.y = effectPos.y;
|
||||||
|
smoke.z = effectPos.z;
|
||||||
|
smoke.xVel = Random::GenerateInt(-BLOCK(0.5f), BLOCK(0.5f));
|
||||||
|
smoke.yVel = Random::GenerateInt(-BLOCK(1 / 8.0f), BLOCK(1 / 8.0f));
|
||||||
|
smoke.zVel = Random::GenerateInt(-BLOCK(0.5f), BLOCK(0.5f));
|
||||||
|
|
||||||
|
if (isUnderwater)
|
||||||
|
{
|
||||||
|
smoke.sR = 144;
|
||||||
|
smoke.sG = 144;
|
||||||
|
smoke.sB = 144;
|
||||||
|
smoke.dR = 64;
|
||||||
|
smoke.dG = 64;
|
||||||
|
smoke.dB = 64;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
smoke.sR = 0;
|
||||||
|
smoke.sG = 0;
|
||||||
|
smoke.sB = 0;
|
||||||
|
smoke.dR = 192;
|
||||||
|
smoke.dG = 192;
|
||||||
|
smoke.dB = 208;
|
||||||
|
}
|
||||||
|
|
||||||
|
smoke.colFadeSpeed = 8;
|
||||||
|
smoke.fadeToBlack = 64;
|
||||||
|
smoke.sLife =
|
||||||
|
smoke.life = Random::GenerateInt(96, 128);
|
||||||
|
smoke.extras = 0;
|
||||||
|
smoke.dynamic = -1;
|
||||||
|
|
||||||
|
if (isUnderwater)
|
||||||
|
{
|
||||||
|
smoke.yVel /= 16;
|
||||||
|
smoke.y += 32;
|
||||||
|
smoke.friction = 4 | 16;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
smoke.friction = 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
smoke.rotAng = Random::GenerateAngle();
|
||||||
|
smoke.rotAdd = Random::GenerateAngle(ANGLE(-0.2f), ANGLE(0.2f));
|
||||||
|
|
||||||
|
smoke.scalar = 3;
|
||||||
|
|
||||||
|
if (isUnderwater)
|
||||||
|
{
|
||||||
|
smoke.gravity = 0;
|
||||||
|
smoke.maxYvel = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
smoke.gravity = Random::GenerateInt(-8, -4);
|
||||||
|
smoke.maxYvel = Random::GenerateInt(-8, -4);
|
||||||
|
}
|
||||||
|
|
||||||
|
int scale = Random::GenerateInt(128, 160);
|
||||||
|
smoke.size =
|
||||||
|
smoke.sSize = scale / 4;
|
||||||
|
smoke.dSize = scale;
|
||||||
|
|
||||||
|
scale += Random::GenerateInt(32, 64);
|
||||||
|
smoke.size =
|
||||||
|
smoke.sSize = scale / 8;
|
||||||
|
smoke.dSize = scale;
|
||||||
|
smoke.flags = SP_SCALE | SP_DEF | SP_ROTATE | SP_EXPDEF;
|
||||||
|
}
|
||||||
|
|
||||||
void InitialiseShiva(short itemNumber)
|
void InitialiseShiva(short itemNumber)
|
||||||
{
|
{
|
||||||
auto& item = g_Level.Items[itemNumber];
|
auto& item = g_Level.Items[itemNumber];
|
||||||
|
@ -177,7 +260,7 @@ namespace TEN::Entities::Creatures::TR3
|
||||||
item.Status &= ~ITEM_INVISIBLE;
|
item.Status &= ~ITEM_INVISIBLE;
|
||||||
|
|
||||||
// Joint index used for swapping mesh.
|
// Joint index used for swapping mesh.
|
||||||
item.SetFlags(0, 0);
|
item.SetFlagField(0, 0);
|
||||||
|
|
||||||
if (item.TestFlags(1, 0))
|
if (item.TestFlags(1, 0))
|
||||||
{
|
{
|
||||||
|
@ -185,7 +268,7 @@ namespace TEN::Entities::Creatures::TR3
|
||||||
SwapShivaMeshToStone(item, jointIndex);
|
SwapShivaMeshToStone(item, jointIndex);
|
||||||
|
|
||||||
// Continue transition until finished.
|
// Continue transition until finished.
|
||||||
item.SetFlags(2, 1);
|
item.SetFlagField(2, 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -208,10 +291,10 @@ namespace TEN::Entities::Creatures::TR3
|
||||||
if (item->Animation.ActiveState != SHIVA_STATE_DEATH)
|
if (item->Animation.ActiveState != SHIVA_STATE_DEATH)
|
||||||
{
|
{
|
||||||
SetAnimation(item, SHIVA_ANIM_DEATH);
|
SetAnimation(item, SHIVA_ANIM_DEATH);
|
||||||
item->ItemFlags[0] = object.nmeshes - 1;
|
item->SetFlagField(0, object.nmeshes - 1);
|
||||||
|
|
||||||
// Redo mesh swap to stone.
|
// Redo mesh swap to stone.
|
||||||
item->SetFlags(2, 2);
|
item->SetFlagField(2, 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
int frameEnd = g_Level.Anims[object.animIndex + SHIVA_ANIM_DEATH].frameEnd - 1;
|
int frameEnd = g_Level.Anims[object.animIndex + SHIVA_ANIM_DEATH].frameEnd - 1;
|
||||||
|
@ -232,6 +315,7 @@ namespace TEN::Entities::Creatures::TR3
|
||||||
GetCreatureMood(item, &ai, true);
|
GetCreatureMood(item, &ai, true);
|
||||||
CreatureMood(item, &ai, true);
|
CreatureMood(item, &ai, true);
|
||||||
|
|
||||||
|
// Shiva don't resent fear.
|
||||||
if (creature->Mood == MoodType::Escape)
|
if (creature->Mood == MoodType::Escape)
|
||||||
{
|
{
|
||||||
creature->Target.x = creature->Enemy->Pose.Position.x;
|
creature->Target.x = creature->Enemy->Pose.Position.x;
|
||||||
|
@ -463,107 +547,24 @@ namespace TEN::Entities::Creatures::TR3
|
||||||
|
|
||||||
void ShivaHit(ItemInfo& target, ItemInfo& source, std::optional<GameVector> pos, int damage, bool isExplosive, int jointIndex)
|
void ShivaHit(ItemInfo& target, ItemInfo& source, std::optional<GameVector> pos, int damage, bool isExplosive, int jointIndex)
|
||||||
{
|
{
|
||||||
|
if (!pos.has_value())
|
||||||
|
return;
|
||||||
|
|
||||||
const auto& player = *GetLaraInfo(&source);
|
const auto& player = *GetLaraInfo(&source);
|
||||||
const auto& object = Objects[target.ObjectNumber];
|
const auto& object = Objects[target.ObjectNumber];
|
||||||
|
|
||||||
// If guarded, ricochet without damage.
|
// If guarded, ricochet without damage.
|
||||||
if ((target.Animation.ActiveState == SHIVA_STATE_WALK_FORWARD_GUARDING ||
|
if ((target.Animation.ActiveState == SHIVA_STATE_WALK_FORWARD_GUARDING ||
|
||||||
target.Animation.ActiveState == SHIVA_STATE_GUARD_IDLE) && pos.has_value())
|
target.Animation.ActiveState == SHIVA_STATE_GUARD_IDLE))
|
||||||
{
|
{
|
||||||
SoundEffect(SFX_TR4_BADDY_SWORD_RICOCHET, &target.Pose);
|
SoundEffect(SFX_TR4_BADDY_SWORD_RICOCHET, &target.Pose);
|
||||||
TriggerRicochetSpark(*pos, source.Pose.Orientation.y, 3, 0);
|
TriggerRicochetSpark(*pos, source.Pose.Orientation.y, 3, 0);
|
||||||
}
|
}
|
||||||
// Do basic hit effect.
|
// Do basic hit effect.
|
||||||
else if (object.hitEffect == HitEffect::Blood && pos.has_value())
|
else if (object.hitEffect == HitEffect::Blood)
|
||||||
{
|
{
|
||||||
DoBloodSplat(pos->x, pos->y, pos->z, 10, source.Pose.Orientation.y, pos->RoomNumber);
|
DoBloodSplat(pos->x, pos->y, pos->z, Random::GenerateInt(4, 8), source.Pose.Orientation.y, pos->RoomNumber);
|
||||||
DoItemHit(&target, damage, isExplosive);
|
DoItemHit(&target, damage, isExplosive);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void SpawnShivaSmoke(const Vector3& pos, int roomNumber)
|
|
||||||
{
|
|
||||||
auto& smoke = *GetFreeParticle();
|
|
||||||
|
|
||||||
bool isUnderwater = TestEnvironment(ENV_FLAG_WATER, Vector3i(pos), roomNumber);
|
|
||||||
auto sphere = BoundingSphere(pos, 16);
|
|
||||||
auto randPos = Random::GeneratePointInSphere(sphere);
|
|
||||||
|
|
||||||
if (isUnderwater)
|
|
||||||
{
|
|
||||||
smoke.sR = 144;
|
|
||||||
smoke.sG = 144;
|
|
||||||
smoke.sB = 144;
|
|
||||||
smoke.dR = 64;
|
|
||||||
smoke.dG = 64;
|
|
||||||
smoke.dB = 64;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
smoke.sR = 0;
|
|
||||||
smoke.sG = 0;
|
|
||||||
smoke.sB = 0;
|
|
||||||
smoke.dR = 192;
|
|
||||||
smoke.dG = 192;
|
|
||||||
smoke.dB = 208;
|
|
||||||
}
|
|
||||||
|
|
||||||
smoke.colFadeSpeed = 8;
|
|
||||||
smoke.fadeToBlack = 64;
|
|
||||||
smoke.sLife =
|
|
||||||
smoke.life = Random::GenerateInt(96, 128);
|
|
||||||
smoke.blendMode = BLEND_MODES::BLENDMODE_ADDITIVE;
|
|
||||||
smoke.extras = 0;
|
|
||||||
smoke.dynamic = -1;
|
|
||||||
|
|
||||||
smoke.x = randPos.x;
|
|
||||||
smoke.y = randPos.y;
|
|
||||||
smoke.z = randPos.z;
|
|
||||||
smoke.xVel = ((GetRandomControl() & 4095) - 2048) / 4;
|
|
||||||
smoke.yVel = (GetRandomControl() & 255) - 128;
|
|
||||||
smoke.zVel = ((GetRandomControl() & 4095) - 2048) / 4;
|
|
||||||
|
|
||||||
if (isUnderwater)
|
|
||||||
{
|
|
||||||
smoke.yVel /= 16;
|
|
||||||
smoke.y += 32;
|
|
||||||
smoke.friction = 4 | (16);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
smoke.friction = 6;
|
|
||||||
}
|
|
||||||
|
|
||||||
smoke.flags = SP_SCALE | SP_DEF | SP_ROTATE | SP_EXPDEF;
|
|
||||||
smoke.rotAng = GetRandomControl() & 4095;
|
|
||||||
|
|
||||||
if (Random::TestProbability(1 / 2.0f))
|
|
||||||
smoke.rotAdd = -(GetRandomControl() & 15) - 16;
|
|
||||||
else
|
|
||||||
smoke.rotAdd = (GetRandomControl() & 15) + 16;
|
|
||||||
|
|
||||||
smoke.scalar = 3;
|
|
||||||
|
|
||||||
if (isUnderwater)
|
|
||||||
{
|
|
||||||
smoke.gravity =
|
|
||||||
smoke.maxYvel = 0;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
smoke.gravity = -(GetRandomControl() & 3) - 3;
|
|
||||||
smoke.maxYvel = -(GetRandomControl() & 3) - 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
int size = (GetRandomControl() & 31) + 128;
|
|
||||||
smoke.size =
|
|
||||||
smoke.sSize = size / 4;
|
|
||||||
smoke.dSize = size;
|
|
||||||
|
|
||||||
size += (GetRandomControl() & 31) + 32;
|
|
||||||
smoke.size =
|
|
||||||
smoke.sSize = size / 8;
|
|
||||||
smoke.dSize = size;
|
|
||||||
smoke.on = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,5 @@ namespace TEN::Entities::Creatures::TR3
|
||||||
{
|
{
|
||||||
void InitialiseShiva(short itemNumber);
|
void InitialiseShiva(short itemNumber);
|
||||||
void ShivaControl(short itemNumber);
|
void ShivaControl(short itemNumber);
|
||||||
|
|
||||||
void ShivaHit(ItemInfo& target, ItemInfo& source, std::optional<GameVector> pos, int damage, bool isExplosive, int jointIndex);
|
void ShivaHit(ItemInfo& target, ItemInfo& source, std::optional<GameVector> pos, int damage, bool isExplosive, int jointIndex);
|
||||||
void SpawnShivaSmoke(const Vector3& pos, int roomNumber);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1067,7 +1067,8 @@ namespace TEN::Entities::Vehicles
|
||||||
if (spark->sLife < 9)
|
if (spark->sLife < 9)
|
||||||
spark->sLife = spark->life = 9;
|
spark->sLife = spark->life = 9;
|
||||||
|
|
||||||
spark->blendMode = BLEND_MODES::BLENDMODE_SCREEN;
|
// TODO: Switch back to screen blend mode once rendering for it is refactored. -- Sezz 2023.01.14
|
||||||
|
spark->blendMode = BLEND_MODES::BLENDMODE_ADDITIVE;
|
||||||
spark->colFadeSpeed = 4;
|
spark->colFadeSpeed = 4;
|
||||||
spark->fadeToBlack = 4;
|
spark->fadeToBlack = 4;
|
||||||
spark->extras = 0;
|
spark->extras = 0;
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#include "framework.h"
|
#include "framework.h"
|
||||||
#include "Objects/TR3/tr3_objects.h"
|
#include "Objects/TR3/tr3_objects.h"
|
||||||
|
|
||||||
|
#include "Objects/TR5/Object/tr5_missile.h"
|
||||||
#include "Game/collision/collide_item.h"
|
#include "Game/collision/collide_item.h"
|
||||||
#include "Game/control/box.h"
|
#include "Game/control/box.h"
|
||||||
#include "Game/itemdata/creature_info.h"
|
#include "Game/itemdata/creature_info.h"
|
||||||
|
@ -23,6 +24,11 @@
|
||||||
#include "Objects/TR3/Entity/tr3_tony.h" // OK
|
#include "Objects/TR3/Entity/tr3_tony.h" // OK
|
||||||
#include "Objects/TR3/Entity/tr3_trex.h" // OK
|
#include "Objects/TR3/Entity/tr3_trex.h" // OK
|
||||||
#include "Objects/TR3/Entity/tr3_tribesman.h" // OK
|
#include "Objects/TR3/Entity/tr3_tribesman.h" // OK
|
||||||
|
#include "Objects/TR3/Entity/Lizard.h" // OK
|
||||||
|
#include "Objects/TR3/Entity/PunaBoss.h" // OK
|
||||||
|
|
||||||
|
// Effects
|
||||||
|
#include "Objects/Effects/Boss.h"
|
||||||
|
|
||||||
// Traps
|
// Traps
|
||||||
#include "Objects/TR3/Trap/train.h"
|
#include "Objects/TR3/Trap/train.h"
|
||||||
|
@ -34,8 +40,10 @@
|
||||||
#include "Objects/TR3/Vehicles/quad_bike.h"
|
#include "Objects/TR3/Vehicles/quad_bike.h"
|
||||||
#include "Objects/TR3/Vehicles/upv.h"
|
#include "Objects/TR3/Vehicles/upv.h"
|
||||||
#include "Objects/TR3/Vehicles/rubber_boat.h"
|
#include "Objects/TR3/Vehicles/rubber_boat.h"
|
||||||
|
#include "Objects/Utils/object_helper.h"
|
||||||
|
|
||||||
using namespace TEN::Entities::Creatures::TR3;
|
using namespace TEN::Entities::Creatures::TR3;
|
||||||
|
using namespace TEN::Effects::Boss;
|
||||||
|
|
||||||
static void StartEntity(ObjectInfo* obj)
|
static void StartEntity(ObjectInfo* obj)
|
||||||
{
|
{
|
||||||
|
@ -198,6 +206,8 @@ static void StartEntity(ObjectInfo* obj)
|
||||||
obj = &Objects[ID_MONKEY];
|
obj = &Objects[ID_MONKEY];
|
||||||
if (obj->loaded)
|
if (obj->loaded)
|
||||||
{
|
{
|
||||||
|
CheckIfSlotExists(ID_MESHSWAP_MONKEY_KEY, "ID_MONKEY", "ID_MESHSWAP_MONKEY_KEY");
|
||||||
|
CheckIfSlotExists(ID_MESHSWAP_MONKEY_MEDIPACK, "ID_MONKEY", "ID_MESHSWAP_MONKEY_MEDIPACK");
|
||||||
obj->initialise = InitialiseMonkey;
|
obj->initialise = InitialiseMonkey;
|
||||||
obj->control = MonkeyControl;
|
obj->control = MonkeyControl;
|
||||||
obj->collision = CreatureCollision;
|
obj->collision = CreatureCollision;
|
||||||
|
@ -248,6 +258,7 @@ static void StartEntity(ObjectInfo* obj)
|
||||||
obj = &Objects[ID_SHIVA];
|
obj = &Objects[ID_SHIVA];
|
||||||
if (obj->loaded)
|
if (obj->loaded)
|
||||||
{
|
{
|
||||||
|
CheckIfSlotExists(ID_SHIVA_STATUE, "ID_SHIVA", "ID_SHIVA_STATUE");
|
||||||
obj->initialise = InitialiseShiva;
|
obj->initialise = InitialiseShiva;
|
||||||
obj->collision = CreatureCollision;
|
obj->collision = CreatureCollision;
|
||||||
obj->control = ShivaControl;
|
obj->control = ShivaControl;
|
||||||
|
@ -297,11 +308,70 @@ static void StartEntity(ObjectInfo* obj)
|
||||||
obj->SetBoneRotationFlags(13, ROT_Y);
|
obj->SetBoneRotationFlags(13, ROT_Y);
|
||||||
obj->SetupHitEffect();
|
obj->SetupHitEffect();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
obj = &Objects[ID_LIZARD];
|
||||||
|
if (obj->loaded)
|
||||||
|
{
|
||||||
|
obj->initialise = InitialiseCreature;
|
||||||
|
obj->control = LizardControl;
|
||||||
|
obj->collision = CreatureCollision;
|
||||||
|
obj->shadowType = ShadowMode::All;
|
||||||
|
obj->HitPoints = 36;
|
||||||
|
obj->intelligent = true;
|
||||||
|
obj->pivotLength = 0;
|
||||||
|
obj->radius = 204;
|
||||||
|
obj->LotType = LotType::Human;
|
||||||
|
obj->SetBoneRotationFlags(9, ROT_X | ROT_Z);
|
||||||
|
obj->SetupHitEffect();
|
||||||
|
}
|
||||||
|
|
||||||
|
obj = &Objects[ID_PUNA_BOSS];
|
||||||
|
if (obj->loaded)
|
||||||
|
{
|
||||||
|
obj->initialise = InitialisePuna;
|
||||||
|
obj->control = PunaControl;
|
||||||
|
obj->collision = CreatureCollision;
|
||||||
|
obj->HitRoutine = PunaHit;
|
||||||
|
obj->shadowType = ShadowMode::All;
|
||||||
|
obj->HitPoints = 200;
|
||||||
|
obj->intelligent = true;
|
||||||
|
obj->nonLot = true;
|
||||||
|
obj->radius = 102;
|
||||||
|
obj->pivotLength = 50;
|
||||||
|
obj->SetBoneRotationFlags(4, ROT_Y); // Puna quest object.
|
||||||
|
obj->SetBoneRotationFlags(7, ROT_X | ROT_Y); // Head.
|
||||||
|
obj->SetupHitEffect();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void StartObject(ObjectInfo* obj)
|
static void StartObject(ObjectInfo* obj)
|
||||||
{
|
{
|
||||||
|
obj = &Objects[ID_BOSS_SHIELD];
|
||||||
|
if (obj->loaded)
|
||||||
|
{
|
||||||
|
obj->initialise = nullptr;
|
||||||
|
obj->collision = ObjectCollision;
|
||||||
|
obj->control = ShieldControl;
|
||||||
|
obj->shadowType = ShadowMode::None;
|
||||||
|
}
|
||||||
|
|
||||||
|
obj = &Objects[ID_BOSS_EXPLOSION_RING];
|
||||||
|
if (obj->loaded)
|
||||||
|
{
|
||||||
|
obj->initialise = nullptr;
|
||||||
|
obj->collision = nullptr;
|
||||||
|
obj->control = ShockwaveRingControl;
|
||||||
|
obj->shadowType = ShadowMode::None;
|
||||||
|
}
|
||||||
|
|
||||||
|
obj = &Objects[ID_BOSS_EXPLOSION_SHOCKWAVE];
|
||||||
|
if (obj->loaded)
|
||||||
|
{
|
||||||
|
obj->initialise = nullptr;
|
||||||
|
obj->collision = nullptr;
|
||||||
|
obj->control = ShockwaveExplosionControl;
|
||||||
|
obj->shadowType = ShadowMode::None;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void StartTrap(ObjectInfo* obj)
|
static void StartTrap(ObjectInfo* obj)
|
||||||
|
|
|
@ -1267,37 +1267,27 @@ namespace TEN::Entities::TR4
|
||||||
switch (vault)
|
switch (vault)
|
||||||
{
|
{
|
||||||
case 2:
|
case 2:
|
||||||
item->Animation.AnimNumber = Objects[objectNumber].animIndex + BADDY_ANIM_CLIMB_2_STEPS;
|
SetAnimation(item, BADDY_ANIM_CLIMB_2_STEPS);
|
||||||
item->Animation.FrameNumber = g_Level.Anims[item->Animation.AnimNumber].frameBase;
|
|
||||||
item->Animation.ActiveState = BADDY_STATE_CLIMB_2_STEPS;
|
|
||||||
creature->MaxTurn = 0;
|
creature->MaxTurn = 0;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 3:
|
case 3:
|
||||||
item->Animation.AnimNumber = Objects[objectNumber].animIndex + BADDY_ANIM_CLIMB_3_STEPS;
|
SetAnimation(item, BADDY_ANIM_CLIMB_3_STEPS);
|
||||||
item->Animation.FrameNumber = g_Level.Anims[item->Animation.AnimNumber].frameBase;
|
|
||||||
item->Animation.ActiveState = BADDY_STATE_CLIMB_3_STEPS;
|
|
||||||
creature->MaxTurn = 0;
|
creature->MaxTurn = 0;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 4:
|
case 4:
|
||||||
item->Animation.AnimNumber = Objects[objectNumber].animIndex + BADDY_ANIM_CLIMB_4_STEPS;
|
SetAnimation(item, BADDY_ANIM_CLIMB_4_STEPS);
|
||||||
item->Animation.ActiveState = BADDY_STATE_CLIMB_4_STEPS;
|
|
||||||
item->Animation.FrameNumber = g_Level.Anims[item->Animation.AnimNumber].frameBase;
|
|
||||||
creature->MaxTurn = 0;
|
creature->MaxTurn = 0;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case -3:
|
case -3:
|
||||||
item->Animation.AnimNumber = Objects[objectNumber].animIndex + BADDY_ANIM_JUMP_OFF_3_STEPS;
|
SetAnimation(item, BADDY_ANIM_JUMP_OFF_3_STEPS);
|
||||||
item->Animation.FrameNumber = g_Level.Anims[item->Animation.AnimNumber].frameBase;
|
|
||||||
item->Animation.ActiveState = BADDY_STATE_JUMP_OFF_3_STEPS;
|
|
||||||
creature->MaxTurn = 0;
|
creature->MaxTurn = 0;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case -4:
|
case -4:
|
||||||
item->Animation.AnimNumber = Objects[objectNumber].animIndex + BADDY_ANIM_JUMP_OFF_4_STEPS;
|
SetAnimation(item, BADDY_ANIM_JUMP_OFF_4_STEPS);
|
||||||
item->Animation.FrameNumber = g_Level.Anims[item->Animation.AnimNumber].frameBase;
|
|
||||||
item->Animation.ActiveState = BADDY_STATE_JUMP_OFF_4_STEPS;
|
|
||||||
creature->MaxTurn = 0;
|
creature->MaxTurn = 0;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
@ -1307,9 +1297,7 @@ namespace TEN::Entities::TR4
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
item->Animation.AnimNumber = Objects[objectNumber].animIndex + BADDY_ANIM_BLIND;
|
SetAnimation(item, BADDY_ANIM_BLIND, Random::GenerateInt(0, 8));
|
||||||
item->Animation.FrameNumber = g_Level.Anims[item->Animation.AnimNumber].frameBase + (GetRandomControl() & 7);
|
|
||||||
item->Animation.ActiveState = BADDY_STATE_BLIND;
|
|
||||||
creature->MaxTurn = 0;
|
creature->MaxTurn = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1321,21 +1309,21 @@ namespace TEN::Entities::TR4
|
||||||
|
|
||||||
if (pos.has_value())
|
if (pos.has_value())
|
||||||
{
|
{
|
||||||
if ((target.Animation.ActiveState == BADDY_STATE_DODGE || Random::TestProbability(1 / 2.0f)) &&
|
if (target.Animation.ActiveState == BADDY_STATE_DODGE &&
|
||||||
(player.Control.Weapon.GunType == LaraWeaponType::Pistol ||
|
(player.Control.Weapon.GunType == LaraWeaponType::Pistol ||
|
||||||
player.Control.Weapon.GunType == LaraWeaponType::Shotgun ||
|
player.Control.Weapon.GunType == LaraWeaponType::Shotgun ||
|
||||||
player.Control.Weapon.GunType == LaraWeaponType::Uzi ||
|
player.Control.Weapon.GunType == LaraWeaponType::Uzi ||
|
||||||
player.Control.Weapon.GunType == LaraWeaponType::HK ||
|
player.Control.Weapon.GunType == LaraWeaponType::HK ||
|
||||||
player.Control.Weapon.GunType == LaraWeaponType::Revolver))
|
player.Control.Weapon.GunType == LaraWeaponType::Revolver))
|
||||||
{
|
{
|
||||||
// Baddy2 sword deflecting bullet.
|
// Baddy2 bullet deflection with sword.
|
||||||
SoundEffect(SFX_TR4_BADDY_SWORD_RICOCHET, &target.Pose);
|
SoundEffect(SFX_TR4_BADDY_SWORD_RICOCHET, &target.Pose);
|
||||||
TriggerRicochetSpark(*pos, source.Pose.Orientation.y, 3, 0);
|
TriggerRicochetSpark(*pos, source.Pose.Orientation.y, 3, 0);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else if (object.hitEffect == HitEffect::Blood)
|
else if (object.hitEffect == HitEffect::Blood)
|
||||||
{
|
{
|
||||||
DoBloodSplat(pos->x, pos->y, pos->z, 10, source.Pose.Orientation.y, pos->RoomNumber);
|
DoBloodSplat(pos->x, pos->y, pos->z, Random::GenerateInt(4, 8), source.Pose.Orientation.y, pos->RoomNumber);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -136,11 +136,11 @@ namespace TEN::Entities::TR4
|
||||||
creature->Enemy = nullptr;
|
creature->Enemy = nullptr;
|
||||||
float minDistance = FLT_MAX;
|
float minDistance = FLT_MAX;
|
||||||
|
|
||||||
for (auto* activeCreature : ActiveCreatures)
|
for (auto& currentCreature : ActiveCreatures)
|
||||||
{
|
{
|
||||||
if (activeCreature->ItemNumber != NO_ITEM && activeCreature->ItemNumber != itemNumber)
|
if (currentCreature->ItemNumber != NO_ITEM && currentCreature->ItemNumber != itemNumber)
|
||||||
{
|
{
|
||||||
auto* currentItem = &g_Level.Items[activeCreature->ItemNumber];
|
auto* currentItem = &g_Level.Items[currentCreature->ItemNumber];
|
||||||
if (currentItem->ObjectNumber != ID_LARA)
|
if (currentItem->ObjectNumber != ID_LARA)
|
||||||
{
|
{
|
||||||
if (currentItem->ObjectNumber != ID_BIG_SCORPION &&
|
if (currentItem->ObjectNumber != ID_BIG_SCORPION &&
|
||||||
|
|
|
@ -23,6 +23,12 @@ using namespace TEN::Math::Random;
|
||||||
|
|
||||||
namespace TEN::Entities::TR4
|
namespace TEN::Entities::TR4
|
||||||
{
|
{
|
||||||
|
constexpr auto DEMIGOD_IDLE_RANGE = SQUARE(BLOCK(2));
|
||||||
|
constexpr auto DEMIGOD_WALK_RANGE = SQUARE(BLOCK(3));
|
||||||
|
constexpr auto DEMIGOD1_WALK_RANGE = SQUARE(BLOCK(3));
|
||||||
|
constexpr auto DEMIGOD2_RADIAL_PROJECTILE_ATTACK_RANGE = SQUARE(BLOCK(5));
|
||||||
|
constexpr auto DEMIGOD3_RADIAL_PROJECTILE_ATTACK_RANGE = SQUARE(BLOCK(5));
|
||||||
|
|
||||||
enum DemigodState
|
enum DemigodState
|
||||||
{
|
{
|
||||||
DEMIGOD_STATE_IDLE = 0,
|
DEMIGOD_STATE_IDLE = 0,
|
||||||
|
@ -122,8 +128,8 @@ namespace TEN::Entities::TR4
|
||||||
int dx = LaraItem->Pose.Position.x - fx->pos.Position.x;
|
int dx = LaraItem->Pose.Position.x - fx->pos.Position.x;
|
||||||
int dz = LaraItem->Pose.Position.z - fx->pos.Position.z;
|
int dz = LaraItem->Pose.Position.z - fx->pos.Position.z;
|
||||||
|
|
||||||
if (dx >= -SECTOR(16) && dx <= SECTOR(16) &&
|
if (dx >= -BLOCK(16) && dx <= BLOCK(16) &&
|
||||||
dz >= -SECTOR(16) && dz <= SECTOR(16))
|
dz >= -BLOCK(16) && dz <= BLOCK(16))
|
||||||
{
|
{
|
||||||
auto* spark = GetFreeParticle();
|
auto* spark = GetFreeParticle();
|
||||||
|
|
||||||
|
@ -200,7 +206,7 @@ namespace TEN::Entities::TR4
|
||||||
fx->flag1 = flags;
|
fx->flag1 = flags;
|
||||||
fx->speed = (GetRandomControl() & 0x1F) + 96;
|
fx->speed = (GetRandomControl() & 0x1F) + 96;
|
||||||
fx->objectNumber = ID_ENERGY_BUBBLES;
|
fx->objectNumber = ID_ENERGY_BUBBLES;
|
||||||
fx->frameNumber = Objects[ID_ENERGY_BUBBLES].meshIndex + (flags >= 4, flags - 1, flags);
|
fx->frameNumber = Objects[ID_ENERGY_BUBBLES].meshIndex + ((flags >= 4) ? flags - 1 : flags);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -436,7 +442,7 @@ namespace TEN::Entities::TR4
|
||||||
|
|
||||||
if (item->ObjectNumber == ID_DEMIGOD1)
|
if (item->ObjectNumber == ID_DEMIGOD1)
|
||||||
{
|
{
|
||||||
if (AI.distance >= pow(SECTOR(3), 2))
|
if (AI.distance >= DEMIGOD1_WALK_RANGE)
|
||||||
{
|
{
|
||||||
item->Animation.TargetState = DEMIGOD_STATE_WALK_FORWARD;
|
item->Animation.TargetState = DEMIGOD_STATE_WALK_FORWARD;
|
||||||
break;
|
break;
|
||||||
|
@ -451,7 +457,7 @@ namespace TEN::Entities::TR4
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (AI.distance <= pow(SECTOR(3), 2))
|
if (AI.distance <= DEMIGOD1_WALK_RANGE)
|
||||||
{
|
{
|
||||||
item->Animation.TargetState = DEMIGOD_STATE_WALK_FORWARD;
|
item->Animation.TargetState = DEMIGOD_STATE_WALK_FORWARD;
|
||||||
break;
|
break;
|
||||||
|
@ -471,15 +477,15 @@ namespace TEN::Entities::TR4
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (AI.distance <= pow(SECTOR(2), 2) ||
|
if (item->ObjectNumber == ID_DEMIGOD3)
|
||||||
AI.distance >= pow(SECTOR(5), 2))
|
{
|
||||||
|
if (AI.distance <= DEMIGOD_IDLE_RANGE ||
|
||||||
|
AI.distance >= DEMIGOD3_RADIAL_PROJECTILE_ATTACK_RANGE)
|
||||||
{
|
{
|
||||||
item->Animation.TargetState = DEMIGOD_STATE_WALK_FORWARD;
|
item->Animation.TargetState = DEMIGOD_STATE_WALK_FORWARD;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (item->ObjectNumber == ID_DEMIGOD3)
|
|
||||||
{
|
|
||||||
if (TestProbability(0.25f))
|
if (TestProbability(0.25f))
|
||||||
{
|
{
|
||||||
item->Animation.TargetState = DEMIGOD3_STATE_RADIAL_AIM;
|
item->Animation.TargetState = DEMIGOD3_STATE_RADIAL_AIM;
|
||||||
|
@ -488,13 +494,17 @@ namespace TEN::Entities::TR4
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
item->Animation.TargetState = DEMIGOD2_STATE_RADIAL_PROJECTILE_ATTACK;
|
if (AI.distance > DEMIGOD_WALK_RANGE && item->ObjectNumber == ID_DEMIGOD2)
|
||||||
|
item->Animation.TargetState = DEMIGOD2_STATE_RADIAL_AIM;
|
||||||
|
else
|
||||||
|
item->Animation.TargetState = DEMIGOD_STATE_WALK_FORWARD;
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case DEMIGOD_STATE_WALK_FORWARD:
|
case DEMIGOD_STATE_WALK_FORWARD:
|
||||||
creature->MaxTurn = ANGLE(7.0f);
|
creature->MaxTurn = ANGLE(7.0f);
|
||||||
|
|
||||||
if (AI.distance < pow(SECTOR(2), 2))
|
if (AI.distance < DEMIGOD_IDLE_RANGE)
|
||||||
{
|
{
|
||||||
item->Animation.TargetState = DEMIGOD_STATE_IDLE;
|
item->Animation.TargetState = DEMIGOD_STATE_IDLE;
|
||||||
break;
|
break;
|
||||||
|
@ -502,7 +512,7 @@ namespace TEN::Entities::TR4
|
||||||
|
|
||||||
if (item->ObjectNumber == ID_DEMIGOD1)
|
if (item->ObjectNumber == ID_DEMIGOD1)
|
||||||
{
|
{
|
||||||
if (AI.distance < pow(SECTOR(3), 2))
|
if (AI.distance < DEMIGOD1_WALK_RANGE)
|
||||||
{
|
{
|
||||||
item->Animation.TargetState = DEMIGOD_STATE_IDLE;
|
item->Animation.TargetState = DEMIGOD_STATE_IDLE;
|
||||||
break;
|
break;
|
||||||
|
@ -517,7 +527,7 @@ namespace TEN::Entities::TR4
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (AI.distance > pow(SECTOR(3), 2))
|
if (AI.distance > DEMIGOD_WALK_RANGE)
|
||||||
{
|
{
|
||||||
if (item->ObjectNumber == ID_DEMIGOD2)
|
if (item->ObjectNumber == ID_DEMIGOD2)
|
||||||
item->Animation.TargetState = DEMIGOD2_STATE_RADIAL_PROJECTILE_ATTACK;
|
item->Animation.TargetState = DEMIGOD2_STATE_RADIAL_PROJECTILE_ATTACK;
|
||||||
|
@ -530,7 +540,7 @@ namespace TEN::Entities::TR4
|
||||||
case DEMIGOD_STATE_RUN_FORWARD:
|
case DEMIGOD_STATE_RUN_FORWARD:
|
||||||
creature->MaxTurn = ANGLE(7.0f);
|
creature->MaxTurn = ANGLE(7.0f);
|
||||||
|
|
||||||
if (AI.distance < pow(SECTOR(2), 2))
|
if (AI.distance < DEMIGOD_IDLE_RANGE)
|
||||||
{
|
{
|
||||||
item->Animation.TargetState = DEMIGOD_STATE_IDLE;
|
item->Animation.TargetState = DEMIGOD_STATE_IDLE;
|
||||||
break;
|
break;
|
||||||
|
@ -538,7 +548,7 @@ namespace TEN::Entities::TR4
|
||||||
|
|
||||||
if (item->ObjectNumber == ID_DEMIGOD1)
|
if (item->ObjectNumber == ID_DEMIGOD1)
|
||||||
{
|
{
|
||||||
if (AI.distance < pow(SECTOR(3), 2))
|
if (AI.distance < DEMIGOD1_WALK_RANGE)
|
||||||
{
|
{
|
||||||
item->Animation.TargetState = DEMIGOD_STATE_IDLE;
|
item->Animation.TargetState = DEMIGOD_STATE_IDLE;
|
||||||
break;
|
break;
|
||||||
|
@ -546,13 +556,13 @@ namespace TEN::Entities::TR4
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (Targetable(item, &AI) || item->ObjectNumber == ID_DEMIGOD3 && AI.distance > pow(SECTOR(2), 2))
|
if (Targetable(item, &AI) || item->ObjectNumber == ID_DEMIGOD3 && AI.distance > DEMIGOD_IDLE_RANGE)
|
||||||
{
|
{
|
||||||
item->Animation.TargetState = DEMIGOD_STATE_IDLE;
|
item->Animation.TargetState = DEMIGOD_STATE_IDLE;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (AI.distance < pow(SECTOR(3), 2))
|
if (AI.distance < DEMIGOD_WALK_RANGE)
|
||||||
item->Animation.TargetState = DEMIGOD_STATE_WALK_FORWARD;
|
item->Animation.TargetState = DEMIGOD_STATE_WALK_FORWARD;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -602,17 +612,25 @@ namespace TEN::Entities::TR4
|
||||||
|
|
||||||
case DEMIGOD3_STATE_RADIAL_AIM:
|
case DEMIGOD3_STATE_RADIAL_AIM:
|
||||||
creature->MaxTurn = ANGLE(7.0f);
|
creature->MaxTurn = ANGLE(7.0f);
|
||||||
if (!Targetable(item, &AI) && AI.distance < pow(SECTOR(5), 2))
|
|
||||||
|
if (!Targetable(item, &AI) && AI.distance < DEMIGOD3_RADIAL_PROJECTILE_ATTACK_RANGE)
|
||||||
item->Animation.TargetState = DEMIGOD3_STATE_RADIAL_PROJECTILE_ATTACK;
|
item->Animation.TargetState = DEMIGOD3_STATE_RADIAL_PROJECTILE_ATTACK;
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case DEMIGOD2_STATE_RADIAL_AIM:
|
||||||
|
creature->MaxTurn = ANGLE(7.0f);
|
||||||
|
if (!Targetable(item, &AI) && AI.distance < DEMIGOD2_RADIAL_PROJECTILE_ATTACK_RANGE)
|
||||||
|
item->Animation.TargetState = DEMIGOD2_STATE_RADIAL_PROJECTILE_ATTACK;
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
case DEMIGOD3_STATE_RADIAL_PROJECTILE_ATTACK:
|
case DEMIGOD3_STATE_RADIAL_PROJECTILE_ATTACK:
|
||||||
creature->MaxTurn = ANGLE(7.0f);
|
creature->MaxTurn = ANGLE(7.0f);
|
||||||
|
|
||||||
DoDemigodEffects(itemNumber);
|
DoDemigodEffects(itemNumber);
|
||||||
|
|
||||||
if (!Targetable(item, &AI) || AI.distance < pow(SECTOR(5), 2) || !GetRandomControl())
|
if (!Targetable(item, &AI) || AI.distance < DEMIGOD3_RADIAL_PROJECTILE_ATTACK_RANGE || !GetRandomControl())
|
||||||
{
|
{
|
||||||
item->Animation.TargetState = DEMIGOD_STATE_IDLE;
|
item->Animation.TargetState = DEMIGOD_STATE_IDLE;
|
||||||
break;
|
break;
|
||||||
|
@ -663,7 +681,7 @@ namespace TEN::Entities::TR4
|
||||||
else
|
else
|
||||||
item->Pose.Orientation.y += AI.angle;
|
item->Pose.Orientation.y += AI.angle;
|
||||||
|
|
||||||
if (AI.distance >= pow(SECTOR(3), 2) ||
|
if (AI.distance >= DEMIGOD1_WALK_RANGE ||
|
||||||
!AI.bite &&
|
!AI.bite &&
|
||||||
(LaraItem->Animation.ActiveState < LS_LADDER_IDLE ||
|
(LaraItem->Animation.ActiveState < LS_LADDER_IDLE ||
|
||||||
LaraItem->Animation.ActiveState > LS_LADDER_DOWN ||
|
LaraItem->Animation.ActiveState > LS_LADDER_DOWN ||
|
||||||
|
|
|
@ -191,18 +191,15 @@ namespace TEN::Entities::TR4
|
||||||
{
|
{
|
||||||
int minDistance = INT_MAX;
|
int minDistance = INT_MAX;
|
||||||
|
|
||||||
for (int i = 0; i < ActiveCreatures.size(); i++)
|
for (auto& currentCreature : ActiveCreatures)
|
||||||
{
|
{
|
||||||
auto* currentCreatureInfo = ActiveCreatures[i];
|
if (currentCreature->ItemNumber == NO_ITEM ||
|
||||||
|
currentCreature->ItemNumber == itemNumber)
|
||||||
if (currentCreatureInfo->ItemNumber == NO_ITEM ||
|
|
||||||
currentCreatureInfo->ItemNumber == itemNumber)
|
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto* currentItem = &g_Level.Items[currentCreatureInfo->ItemNumber];
|
auto* currentItem = &g_Level.Items[currentCreature->ItemNumber];
|
||||||
|
|
||||||
if (currentItem->ObjectNumber != ID_GUIDE &&
|
if (currentItem->ObjectNumber != ID_GUIDE &&
|
||||||
abs(currentItem->Pose.Position.y - item->Pose.Position.y) <= 512)
|
abs(currentItem->Pose.Position.y - item->Pose.Position.y) <= 512)
|
||||||
{
|
{
|
||||||
|
|
|
@ -148,12 +148,11 @@ namespace TEN::Entities::TR4
|
||||||
|
|
||||||
float minDistance = FLT_MAX;
|
float minDistance = FLT_MAX;
|
||||||
|
|
||||||
for (auto* activeCreature : ActiveCreatures)
|
for (auto& currentCreature : ActiveCreatures)
|
||||||
{
|
{
|
||||||
if (activeCreature->ItemNumber != NO_ITEM && activeCreature->ItemNumber != itemNumber)
|
if (currentCreature->ItemNumber != NO_ITEM && currentCreature->ItemNumber != itemNumber)
|
||||||
{
|
{
|
||||||
auto* currentItem = &g_Level.Items[activeCreature->ItemNumber];
|
auto* currentItem = &g_Level.Items[currentCreature->ItemNumber];
|
||||||
|
|
||||||
if (currentItem->ObjectNumber != ID_LARA)
|
if (currentItem->ObjectNumber != ID_LARA)
|
||||||
{
|
{
|
||||||
if (currentItem->ObjectNumber != ID_TROOPS &&
|
if (currentItem->ObjectNumber != ID_TROOPS &&
|
||||||
|
|
|
@ -230,10 +230,9 @@ namespace TEN::Entities::TR4
|
||||||
int distance;
|
int distance;
|
||||||
auto* targetCreature = ActiveCreatures[0];
|
auto* targetCreature = ActiveCreatures[0];
|
||||||
|
|
||||||
for (int i = 0; i < ActiveCreatures.size(); i++)
|
for (auto& currentCreature : ActiveCreatures)
|
||||||
{
|
{
|
||||||
targetCreature = ActiveCreatures[i];
|
targetCreature = currentCreature;
|
||||||
|
|
||||||
if (targetCreature->ItemNumber == NO_ITEM ||
|
if (targetCreature->ItemNumber == NO_ITEM ||
|
||||||
targetCreature->ItemNumber == itemNumber ||
|
targetCreature->ItemNumber == itemNumber ||
|
||||||
g_Level.Items[targetCreature->ItemNumber].ObjectNumber == ID_VON_CROY ||
|
g_Level.Items[targetCreature->ItemNumber].ObjectNumber == ID_VON_CROY ||
|
||||||
|
|
|
@ -84,10 +84,9 @@ namespace TEN::Entities::TR4
|
||||||
|
|
||||||
int minDistance = INT_MAX;
|
int minDistance = INT_MAX;
|
||||||
|
|
||||||
for (int i = 0; i < ActiveCreatures.size(); i++)
|
for (auto& currentCreature : ActiveCreatures)
|
||||||
{
|
{
|
||||||
auto* currentItem = ActiveCreatures[i];
|
auto* currentItem = currentCreature;
|
||||||
|
|
||||||
if (currentItem->ItemNumber == NO_ITEM || currentItem->ItemNumber == itemNumber)
|
if (currentItem->ItemNumber == NO_ITEM || currentItem->ItemNumber == itemNumber)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
|
|
@ -196,9 +196,7 @@ namespace TEN::Entities
|
||||||
obj = &Objects[ID_BADDY1];
|
obj = &Objects[ID_BADDY1];
|
||||||
if (obj->loaded)
|
if (obj->loaded)
|
||||||
{
|
{
|
||||||
if (!Objects[ID_MESHSWAP_BADDY1].loaded)
|
AssignObjectMeshSwap(*obj, ID_MESHSWAP_BADDY1, "ID_BADDY1", "ID_MESHSWAP_BADDY1");
|
||||||
TENLog("ID_MESHSWAP_BADDY1 missing; ID_BADDY1 will not use its sword and uzi meshes.", LogLevel::Warning);
|
|
||||||
|
|
||||||
obj->initialise = InitialiseBaddy;
|
obj->initialise = InitialiseBaddy;
|
||||||
obj->control = BaddyControl;
|
obj->control = BaddyControl;
|
||||||
obj->collision = CreatureCollision;
|
obj->collision = CreatureCollision;
|
||||||
|
@ -218,9 +216,7 @@ namespace TEN::Entities
|
||||||
obj = &Objects[ID_BADDY2];
|
obj = &Objects[ID_BADDY2];
|
||||||
if (obj->loaded)
|
if (obj->loaded)
|
||||||
{
|
{
|
||||||
if (!Objects[ID_MESHSWAP_BADDY2].loaded)
|
AssignObjectMeshSwap(*obj, ID_MESHSWAP_BADDY2, "ID_BADDY2", "ID_MESHSWAP_BADDY2");
|
||||||
TENLog("ID_MESHSWAP_BADDY2 missing; ID_BADDY1 will not use its sword and uzi meshes.", LogLevel::Warning);
|
|
||||||
|
|
||||||
obj->initialise = InitialiseBaddy;
|
obj->initialise = InitialiseBaddy;
|
||||||
obj->control = BaddyControl;
|
obj->control = BaddyControl;
|
||||||
obj->collision = CreatureCollision;
|
obj->collision = CreatureCollision;
|
||||||
|
@ -539,8 +535,7 @@ namespace TEN::Entities
|
||||||
obj = &Objects[ID_BABOON_INV];
|
obj = &Objects[ID_BABOON_INV];
|
||||||
if (obj->loaded)
|
if (obj->loaded)
|
||||||
{
|
{
|
||||||
if (Objects[ID_BABOON_NORMAL].loaded)
|
AssignObjectAnimations(*obj, ID_BABOON_NORMAL, "ID_BABOON_INV", "ID_BABOON_NORMAL");
|
||||||
obj->animIndex = Objects[ID_BABOON_NORMAL].animIndex;
|
|
||||||
obj->initialise = InitialiseBaboon;
|
obj->initialise = InitialiseBaboon;
|
||||||
obj->control = BaboonControl;
|
obj->control = BaboonControl;
|
||||||
obj->collision = CreatureCollision;
|
obj->collision = CreatureCollision;
|
||||||
|
@ -555,8 +550,7 @@ namespace TEN::Entities
|
||||||
obj = &Objects[ID_BABOON_SILENT];
|
obj = &Objects[ID_BABOON_SILENT];
|
||||||
if (obj->loaded)
|
if (obj->loaded)
|
||||||
{
|
{
|
||||||
if (Objects[ID_BABOON_NORMAL].loaded)
|
AssignObjectAnimations(*obj, ID_BABOON_NORMAL, "ID_BABOON_SILENT", "ID_BABOON_NORMAL");
|
||||||
obj->animIndex = Objects[ID_BABOON_NORMAL].animIndex;
|
|
||||||
obj->initialise = InitialiseBaboon;
|
obj->initialise = InitialiseBaboon;
|
||||||
obj->control = BaboonControl;
|
obj->control = BaboonControl;
|
||||||
obj->collision = CreatureCollision;
|
obj->collision = CreatureCollision;
|
||||||
|
|
|
@ -124,10 +124,7 @@ static void StartEntity(ObjectInfo *obj)
|
||||||
obj = &Objects[ID_GUARD1];
|
obj = &Objects[ID_GUARD1];
|
||||||
if (obj->loaded)
|
if (obj->loaded)
|
||||||
{
|
{
|
||||||
// Object required.
|
AssignObjectAnimations(*obj, ID_SWAT, "ID_GUARD1", "ID_SWAT");
|
||||||
if (Objects[ID_SWAT].loaded)
|
|
||||||
obj->animIndex = Objects[ID_SWAT].animIndex;
|
|
||||||
|
|
||||||
obj->initialise = InitialiseGuard;
|
obj->initialise = InitialiseGuard;
|
||||||
obj->collision = CreatureCollision;
|
obj->collision = CreatureCollision;
|
||||||
obj->control = GuardControl;
|
obj->control = GuardControl;
|
||||||
|
@ -147,10 +144,8 @@ static void StartEntity(ObjectInfo *obj)
|
||||||
obj = &Objects[ID_SWAT_PLUS];
|
obj = &Objects[ID_SWAT_PLUS];
|
||||||
if (obj->loaded)
|
if (obj->loaded)
|
||||||
{
|
{
|
||||||
if (!Objects[ID_SWAT].loaded)
|
if (!AssignObjectAnimations(*obj, ID_SWAT, "ID_SWAT_PLUS", "ID_SWAT"))
|
||||||
obj->animIndex = Objects[ID_GUARD1].animIndex;
|
AssignObjectAnimations(*obj, ID_GUARD1, "ID_SWAT_PLUS", "ID_GUARD1");
|
||||||
else
|
|
||||||
obj->animIndex = Objects[ID_SWAT].animIndex;
|
|
||||||
|
|
||||||
obj->initialise = InitialiseGuard;
|
obj->initialise = InitialiseGuard;
|
||||||
obj->collision = CreatureCollision;
|
obj->collision = CreatureCollision;
|
||||||
|
@ -170,10 +165,8 @@ static void StartEntity(ObjectInfo *obj)
|
||||||
obj = &Objects[ID_MAFIA];
|
obj = &Objects[ID_MAFIA];
|
||||||
if (obj->loaded)
|
if (obj->loaded)
|
||||||
{
|
{
|
||||||
if (!Objects[ID_SWAT].loaded)
|
if (!AssignObjectAnimations(*obj, ID_SWAT, "ID_MAFIA", "ID_SWAT"))
|
||||||
obj->animIndex = Objects[ID_GUARD1].animIndex;
|
AssignObjectAnimations(*obj, ID_GUARD1, "ID_MAFIA", "ID_GUARD1");
|
||||||
else
|
|
||||||
obj->animIndex = Objects[ID_SWAT].animIndex;
|
|
||||||
|
|
||||||
obj->initialise = InitialiseGuard;
|
obj->initialise = InitialiseGuard;
|
||||||
obj->collision = CreatureCollision;
|
obj->collision = CreatureCollision;
|
||||||
|
@ -194,10 +187,8 @@ static void StartEntity(ObjectInfo *obj)
|
||||||
obj = &Objects[ID_SCIENTIST];
|
obj = &Objects[ID_SCIENTIST];
|
||||||
if (obj->loaded)
|
if (obj->loaded)
|
||||||
{
|
{
|
||||||
if (!Objects[ID_SWAT].loaded)
|
if (!AssignObjectAnimations(*obj, ID_SWAT, "ID_SCIENTIST", "ID_SWAT"))
|
||||||
obj->animIndex = Objects[ID_GUARD1].animIndex;
|
AssignObjectAnimations(*obj, ID_GUARD1, "ID_SCIENTIST", "ID_GUARD1");
|
||||||
else
|
|
||||||
obj->animIndex = Objects[ID_SWAT].animIndex;
|
|
||||||
|
|
||||||
obj->initialise = InitialiseGuard;
|
obj->initialise = InitialiseGuard;
|
||||||
obj->control = GuardControl;
|
obj->control = GuardControl;
|
||||||
|
@ -216,10 +207,8 @@ static void StartEntity(ObjectInfo *obj)
|
||||||
obj = &Objects[ID_GUARD2];
|
obj = &Objects[ID_GUARD2];
|
||||||
if (obj->loaded)
|
if (obj->loaded)
|
||||||
{
|
{
|
||||||
if (!Objects[ID_SWAT].loaded)
|
if (!AssignObjectAnimations(*obj, ID_SWAT, "ID_GUARD2", "ID_SWAT"))
|
||||||
obj->animIndex = Objects[ID_GUARD1].animIndex;
|
AssignObjectAnimations(*obj, ID_GUARD1, "ID_GUARD2", "ID_GUARD1");
|
||||||
else
|
|
||||||
obj->animIndex = Objects[ID_SWAT].animIndex;
|
|
||||||
|
|
||||||
obj->initialise = InitialiseGuard;
|
obj->initialise = InitialiseGuard;
|
||||||
obj->control = GuardControl;
|
obj->control = GuardControl;
|
||||||
|
@ -240,10 +229,8 @@ static void StartEntity(ObjectInfo *obj)
|
||||||
obj = &Objects[ID_GUARD3];
|
obj = &Objects[ID_GUARD3];
|
||||||
if (obj->loaded)
|
if (obj->loaded)
|
||||||
{
|
{
|
||||||
if (!Objects[ID_SWAT].loaded)
|
if (!AssignObjectAnimations(*obj, ID_SWAT, "ID_GUARD3", "ID_SWAT"))
|
||||||
obj->animIndex = Objects[ID_GUARD1].animIndex;
|
AssignObjectAnimations(*obj, ID_GUARD1, "ID_GUARD3", "ID_GUARD1");
|
||||||
else
|
|
||||||
obj->animIndex = Objects[ID_SWAT].animIndex;
|
|
||||||
|
|
||||||
obj->initialise = InitialiseGuard;
|
obj->initialise = InitialiseGuard;
|
||||||
obj->control = GuardControl;
|
obj->control = GuardControl;
|
||||||
|
@ -923,7 +910,7 @@ static void StartTrap(ObjectInfo *obj)
|
||||||
obj->control = AnimatingControl;
|
obj->control = AnimatingControl;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Why commented? -- TokyoSU, 2022.12.24
|
// TODO: Seem not decompiled. -- TokyoSU, 2023.01.12
|
||||||
obj = &Objects[ID_GEN_SLOT4];
|
obj = &Objects[ID_GEN_SLOT4];
|
||||||
if (obj->loaded)
|
if (obj->loaded)
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,146 +1,191 @@
|
||||||
#include "framework.h"
|
#include "framework.h"
|
||||||
|
#include "Objects/Utils/object_helper.h"
|
||||||
|
|
||||||
#include "Game/collision/collide_item.h"
|
#include "Game/collision/collide_item.h"
|
||||||
#include "Game/Lara/lara_flare.h"
|
#include "Game/Lara/lara_flare.h"
|
||||||
#include "Game/pickup/pickup.h"
|
#include "Game/pickup/pickup.h"
|
||||||
#include "Objects/Utils/object_helper.h"
|
|
||||||
#include "Objects/Generic/Object/objects.h"
|
#include "Objects/Generic/Object/objects.h"
|
||||||
#include "Objects/Generic/puzzles_keys.h"
|
#include "Objects/Generic/puzzles_keys.h"
|
||||||
#include "Objects/TR5/Object/tr5_pushableblock.h"
|
#include "Objects/TR5/Object/tr5_pushableblock.h"
|
||||||
#include "Specific/level.h"
|
#include "Specific/level.h"
|
||||||
|
|
||||||
void InitSmashObject(ObjectInfo* obj, int objectNumber)
|
void AssignObjectMeshSwap(ObjectInfo& object, int requiredMeshSwap, const std::string& baseName, const std::string& requiredName)
|
||||||
{
|
{
|
||||||
obj = &Objects[objectNumber];
|
if (Objects[requiredMeshSwap].loaded)
|
||||||
if (obj->loaded)
|
object.meshSwapSlot = requiredMeshSwap;
|
||||||
|
else
|
||||||
|
TENLog("Slot " + requiredName + " not loaded. Meshswap issues with " + baseName + " may result in incorrect behaviour.", LogLevel::Warning);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AssignObjectAnimations(ObjectInfo& object, int requiredObject, const std::string& baseName, const std::string& requiredName)
|
||||||
{
|
{
|
||||||
obj->initialise = InitialiseSmashObject;
|
// Check if the object has at least 1 animation with more than 1 frame.
|
||||||
obj->collision = ObjectCollision;
|
const auto& anim = g_Level.Anims[object.animIndex];
|
||||||
obj->control = SmashObjectControl;
|
if ((anim.frameEnd - anim.frameBase) > 1)
|
||||||
obj->SetupHitEffect(true);
|
return true;
|
||||||
|
|
||||||
|
// Use slot if loaded.
|
||||||
|
if (Objects[requiredObject].loaded)
|
||||||
|
{
|
||||||
|
// Check if the required object has at least 1 animation with more than 1 frame.
|
||||||
|
const auto& anim = g_Level.Anims[Objects[requiredObject].animIndex];
|
||||||
|
if ((anim.frameEnd - anim.frameBase) > 1)
|
||||||
|
{
|
||||||
|
object.animIndex = Objects[requiredObject].animIndex;
|
||||||
|
object.frameBase = Objects[requiredObject].frameBase;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
TENLog("Slot " + requiredName + " has no animation data. " + baseName + " will have no animations.", LogLevel::Warning);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
TENLog("Slot " + requiredName + " not loaded. " + baseName + " will have no animations.", LogLevel::Warning);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CheckIfSlotExists(int requiredObject, const std::string& baseName, const std::string& requiredName)
|
||||||
|
{
|
||||||
|
if (!Objects[requiredObject].loaded)
|
||||||
|
TENLog("Slot " + requiredName + " not loaded. " + baseName + " may work incorrectly or crash.", LogLevel::Warning);
|
||||||
|
}
|
||||||
|
|
||||||
|
void InitSmashObject(ObjectInfo* object, int objectNumber)
|
||||||
|
{
|
||||||
|
object = &Objects[objectNumber];
|
||||||
|
if (object->loaded)
|
||||||
|
{
|
||||||
|
object->initialise = InitialiseSmashObject;
|
||||||
|
object->collision = ObjectCollision;
|
||||||
|
object->control = SmashObjectControl;
|
||||||
|
object->SetupHitEffect(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void InitKeyHole(ObjectInfo* obj, int objectNumber)
|
void InitKeyHole(ObjectInfo* object, int objectNumber)
|
||||||
{
|
{
|
||||||
obj = &Objects[objectNumber];
|
object = &Objects[objectNumber];
|
||||||
if (obj->loaded)
|
if (object->loaded)
|
||||||
{
|
{
|
||||||
obj->collision = KeyHoleCollision;
|
object->collision = KeyHoleCollision;
|
||||||
obj->SetupHitEffect(true);
|
object->SetupHitEffect(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void InitPuzzleHole(ObjectInfo* obj, int objectNumber)
|
void InitPuzzleHole(ObjectInfo* object, int objectNumber)
|
||||||
{
|
{
|
||||||
obj = &Objects[objectNumber];
|
object = &Objects[objectNumber];
|
||||||
if (obj->loaded)
|
if (object->loaded)
|
||||||
{
|
{
|
||||||
obj->collision = PuzzleHoleCollision;
|
object->collision = PuzzleHoleCollision;
|
||||||
obj->control = AnimatingControl;
|
object->control = AnimatingControl;
|
||||||
obj->isPuzzleHole = true;
|
object->isPuzzleHole = true;
|
||||||
obj->SetupHitEffect(true);
|
object->SetupHitEffect(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void InitPuzzleDone(ObjectInfo* obj, int objectNumber)
|
void InitPuzzleDone(ObjectInfo* object, int objectNumber)
|
||||||
{
|
{
|
||||||
obj = &Objects[objectNumber];
|
object = &Objects[objectNumber];
|
||||||
if (obj->loaded)
|
if (object->loaded)
|
||||||
{
|
{
|
||||||
obj->collision = PuzzleDoneCollision;
|
object->collision = PuzzleDoneCollision;
|
||||||
obj->control = AnimatingControl;
|
object->control = AnimatingControl;
|
||||||
obj->SetupHitEffect(true);
|
object->SetupHitEffect(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void InitAnimating(ObjectInfo* obj, int objectNumber)
|
void InitAnimating(ObjectInfo* object, int objectNumber)
|
||||||
{
|
{
|
||||||
obj = &Objects[objectNumber];
|
object = &Objects[objectNumber];
|
||||||
if (obj->loaded)
|
if (object->loaded)
|
||||||
{
|
{
|
||||||
obj->initialise = InitialiseAnimating;
|
object->initialise = InitialiseAnimating;
|
||||||
obj->control = AnimatingControl;
|
object->control = AnimatingControl;
|
||||||
obj->collision = ObjectCollision;
|
object->collision = ObjectCollision;
|
||||||
obj->SetupHitEffect(true);
|
object->SetupHitEffect(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void InitPickup(ObjectInfo* obj, int objectNumber)
|
void InitPickup(ObjectInfo* object, int objectNumber)
|
||||||
{
|
{
|
||||||
obj = &Objects[objectNumber];
|
object = &Objects[objectNumber];
|
||||||
if (obj->loaded)
|
if (object->loaded)
|
||||||
{
|
{
|
||||||
obj->initialise = InitialisePickup;
|
object->initialise = InitialisePickup;
|
||||||
obj->collision = PickupCollision;
|
object->collision = PickupCollision;
|
||||||
obj->control = PickupControl;
|
object->control = PickupControl;
|
||||||
obj->isPickup = true;
|
object->isPickup = true;
|
||||||
obj->SetupHitEffect(true);
|
object->SetupHitEffect(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void InitPickup(ObjectInfo* obj, int objectNumber, std::function<ControlFunction> func)
|
void InitPickup(ObjectInfo* object, int objectNumber, std::function<ControlFunction> func)
|
||||||
{
|
{
|
||||||
obj = &Objects[objectNumber];
|
object = &Objects[objectNumber];
|
||||||
if (obj->loaded)
|
if (object->loaded)
|
||||||
{
|
{
|
||||||
obj->initialise = InitialisePickup;
|
object->initialise = InitialisePickup;
|
||||||
|
|
||||||
obj->collision = PickupCollision;
|
object->collision = PickupCollision;
|
||||||
obj->control = (func != nullptr) ? func : PickupControl;
|
object->control = (func != nullptr) ? func : PickupControl;
|
||||||
obj->isPickup = true;
|
object->isPickup = true;
|
||||||
obj->SetupHitEffect(true);
|
object->SetupHitEffect(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void InitFlare(ObjectInfo* obj, int objectNumber)
|
void InitFlare(ObjectInfo* object, int objectNumber)
|
||||||
{
|
{
|
||||||
obj = &Objects[objectNumber];
|
object = &Objects[objectNumber];
|
||||||
if (obj->loaded)
|
if (object->loaded)
|
||||||
{
|
{
|
||||||
obj->collision = PickupCollision;
|
object->collision = PickupCollision;
|
||||||
obj->control = FlareControl;
|
object->control = FlareControl;
|
||||||
obj->pivotLength = 256;
|
object->pivotLength = 256;
|
||||||
obj->HitPoints = 256; // Time.
|
object->HitPoints = 256; // Time.
|
||||||
obj->usingDrawAnimatingItem = false;
|
object->usingDrawAnimatingItem = false;
|
||||||
obj->isPickup = true;
|
object->isPickup = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void InitProjectile(ObjectInfo* obj, std::function<InitFunction> func, int objectNumber, bool noLoad)
|
void InitProjectile(ObjectInfo* object, std::function<InitFunction> func, int objectNumber, bool noLoad)
|
||||||
{
|
{
|
||||||
obj = &Objects[objectNumber];
|
object = &Objects[objectNumber];
|
||||||
if (obj->loaded || noLoad)
|
if (object->loaded || noLoad)
|
||||||
{
|
{
|
||||||
obj->initialise = nullptr;
|
object->initialise = nullptr;
|
||||||
obj->collision = nullptr;
|
object->collision = nullptr;
|
||||||
obj->control = func;
|
object->control = func;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void InitSearchObject(ObjectInfo* obj, int objectNumber)
|
void InitSearchObject(ObjectInfo* object, int objectNumber)
|
||||||
{
|
{
|
||||||
obj = &Objects[objectNumber];
|
object = &Objects[objectNumber];
|
||||||
if (obj->loaded)
|
if (object->loaded)
|
||||||
{
|
{
|
||||||
obj->initialise = InitialiseSearchObject;
|
object->initialise = InitialiseSearchObject;
|
||||||
obj->collision = SearchObjectCollision;
|
object->collision = SearchObjectCollision;
|
||||||
obj->control = SearchObjectControl;
|
object->control = SearchObjectControl;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void InitPushableObject(ObjectInfo* obj, int objectNumber)
|
void InitPushableObject(ObjectInfo* object, int objectNumber)
|
||||||
{
|
{
|
||||||
obj = &Objects[objectNumber];
|
object = &Objects[objectNumber];
|
||||||
if (obj->loaded)
|
if (object->loaded)
|
||||||
{
|
{
|
||||||
obj->initialise = InitialisePushableBlock;
|
object->initialise = InitialisePushableBlock;
|
||||||
obj->control = PushableBlockControl;
|
object->control = PushableBlockControl;
|
||||||
obj->collision = PushableBlockCollision;
|
object->collision = PushableBlockCollision;
|
||||||
obj->floor = PushableBlockFloor;
|
object->floor = PushableBlockFloor;
|
||||||
obj->ceiling = PushableBlockCeiling;
|
object->ceiling = PushableBlockCeiling;
|
||||||
obj->floorBorder = PushableBlockFloorBorder;
|
object->floorBorder = PushableBlockFloorBorder;
|
||||||
obj->ceilingBorder = PushableBlockCeilingBorder;
|
object->ceilingBorder = PushableBlockCeilingBorder;
|
||||||
obj->SetupHitEffect(true);
|
object->SetupHitEffect(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,14 +4,18 @@
|
||||||
#define InitFunction void(short itemNumber)
|
#define InitFunction void(short itemNumber)
|
||||||
#define ControlFunction void(short itemNumber)
|
#define ControlFunction void(short itemNumber)
|
||||||
|
|
||||||
void InitSmashObject(ObjectInfo* obj, int objectNumber);
|
void AssignObjectMeshSwap(ObjectInfo& object, int requiredMeshSwap, const std::string& baseName, const std::string& requiredName);
|
||||||
void InitKeyHole(ObjectInfo* obj, int objectNumber);
|
bool AssignObjectAnimations(ObjectInfo& object, int requiredObject, const std::string& baseName = "NOT_SET", const std::string& requiredName = "NOT_SET");
|
||||||
void InitPuzzleHole(ObjectInfo* obj, int objectNumber);
|
void CheckIfSlotExists(int requiredObj, const std::string& baseName, const std::string& requiredName);
|
||||||
void InitPuzzleDone(ObjectInfo* obj, int objectNumber);
|
|
||||||
void InitAnimating(ObjectInfo* obj, int objectNumber);
|
void InitSmashObject(ObjectInfo* object, int objectNumber);
|
||||||
void InitPickup(ObjectInfo* obj, int objectNumber);
|
void InitKeyHole(ObjectInfo* object, int objectNumber);
|
||||||
void InitPickup(ObjectInfo* obj, int objectNumber, std::function<ControlFunction> func);
|
void InitPuzzleHole(ObjectInfo* object, int objectNumber);
|
||||||
void InitFlare(ObjectInfo* obj, int objectNumber);
|
void InitPuzzleDone(ObjectInfo* object, int objectNumber);
|
||||||
void InitProjectile(ObjectInfo* obj, std::function<InitFunction> func, int objectNumber, bool noLoad = false);
|
void InitAnimating(ObjectInfo* object, int objectNumber);
|
||||||
void InitSearchObject(ObjectInfo* obj, int objectNumber);
|
void InitPickup(ObjectInfo* object, int objectNumber);
|
||||||
void InitPushableObject(ObjectInfo* obj, int objectNumber);
|
void InitPickup(ObjectInfo* object, int objectNumber, std::function<ControlFunction> func);
|
||||||
|
void InitFlare(ObjectInfo* object, int objectNumber);
|
||||||
|
void InitProjectile(ObjectInfo* object, std::function<InitFunction> func, int objectNumber, bool noLoad = false);
|
||||||
|
void InitSearchObject(ObjectInfo* object, int objectNumber);
|
||||||
|
void InitPushableObject(ObjectInfo* object, int objectNumber);
|
||||||
|
|
|
@ -182,7 +182,7 @@ enum GAME_OBJECT_ID : short
|
||||||
ID_SWORD_GUARDIAN_STATUE,
|
ID_SWORD_GUARDIAN_STATUE,
|
||||||
ID_SHIVA,
|
ID_SHIVA,
|
||||||
ID_SHIVA_STATUE,
|
ID_SHIVA_STATUE,
|
||||||
ID_TRIBEBOSS,
|
ID_WILLARD,
|
||||||
ID_CIVVY,
|
ID_CIVVY,
|
||||||
ID_MUTANT2,
|
ID_MUTANT2,
|
||||||
ID_LIZARD,
|
ID_LIZARD,
|
||||||
|
|
|
@ -29,6 +29,26 @@ namespace TEN::Renderer
|
||||||
|
|
||||||
m_meshes.clear();
|
m_meshes.clear();
|
||||||
|
|
||||||
|
TENLog("Allocated renderer object memory.", LogLevel::Info);
|
||||||
|
|
||||||
|
m_animatedTextures.resize(g_Level.AnimatedTextures.size());
|
||||||
|
for (int i = 0; i < g_Level.AnimatedTextures.size(); i++)
|
||||||
|
{
|
||||||
|
TEXTURE* texture = &g_Level.AnimatedTextures[i];
|
||||||
|
Texture2D normal;
|
||||||
|
if (texture->normalMapData.size() < 1) {
|
||||||
|
normal = CreateDefaultNormalTexture();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
normal = Texture2D(m_device.Get(), texture->normalMapData.data(), texture->normalMapData.size());
|
||||||
|
}
|
||||||
|
TexturePair tex = std::make_tuple(Texture2D(m_device.Get(), texture->colorMapData.data(), texture->colorMapData.size()), normal);
|
||||||
|
m_animatedTextures[i] = tex;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_animatedTextures.size() > 0)
|
||||||
|
TENLog("Generated " + std::to_string(m_animatedTextures.size()) + " animated textures.", LogLevel::Info);
|
||||||
|
|
||||||
std::transform(g_Level.AnimatedTexturesSequences.begin(), g_Level.AnimatedTexturesSequences.end(), std::back_inserter(m_animatedTextureSets), [](ANIMATED_TEXTURES_SEQUENCE& sequence) {
|
std::transform(g_Level.AnimatedTexturesSequences.begin(), g_Level.AnimatedTexturesSequences.end(), std::back_inserter(m_animatedTextureSets), [](ANIMATED_TEXTURES_SEQUENCE& sequence) {
|
||||||
RendererAnimatedTextureSet set{};
|
RendererAnimatedTextureSet set{};
|
||||||
set.NumTextures = sequence.numFrames;
|
set.NumTextures = sequence.numFrames;
|
||||||
|
@ -48,6 +68,9 @@ namespace TEN::Renderer
|
||||||
return set;
|
return set;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (m_animatedTextureSets.size() > 0)
|
||||||
|
TENLog("Generated " + std::to_string(m_animatedTextureSets.size()) + " animated texture sets.", LogLevel::Info);
|
||||||
|
|
||||||
m_roomTextures.resize(g_Level.RoomTextures.size());
|
m_roomTextures.resize(g_Level.RoomTextures.size());
|
||||||
for (int i = 0; i < g_Level.RoomTextures.size(); i++)
|
for (int i = 0; i < g_Level.RoomTextures.size(); i++)
|
||||||
{
|
{
|
||||||
|
@ -70,20 +93,8 @@ namespace TEN::Renderer
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
m_animatedTextures.resize(g_Level.AnimatedTextures.size());
|
if (m_roomTextures.size() > 0)
|
||||||
for (int i = 0; i < g_Level.AnimatedTextures.size(); i++)
|
TENLog("Generated " + std::to_string(m_roomTextures.size()) + " room texture atlases.", LogLevel::Info);
|
||||||
{
|
|
||||||
TEXTURE *texture = &g_Level.AnimatedTextures[i];
|
|
||||||
Texture2D normal;
|
|
||||||
if (texture->normalMapData.size() < 1) {
|
|
||||||
normal = CreateDefaultNormalTexture();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
normal = Texture2D(m_device.Get(), texture->normalMapData.data(), texture->normalMapData.size());
|
|
||||||
}
|
|
||||||
TexturePair tex = std::make_tuple(Texture2D(m_device.Get(), texture->colorMapData.data(), texture->colorMapData.size()), normal);
|
|
||||||
m_animatedTextures[i] = tex;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_moveablesTextures.resize(g_Level.MoveablesTextures.size());
|
m_moveablesTextures.resize(g_Level.MoveablesTextures.size());
|
||||||
for (int i = 0; i < g_Level.MoveablesTextures.size(); i++)
|
for (int i = 0; i < g_Level.MoveablesTextures.size(); i++)
|
||||||
|
@ -107,6 +118,9 @@ namespace TEN::Renderer
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (m_moveablesTextures.size() > 0)
|
||||||
|
TENLog("Generated " + std::to_string(m_moveablesTextures.size()) + " moveable texture atlases.", LogLevel::Info);
|
||||||
|
|
||||||
m_staticsTextures.resize(g_Level.StaticsTextures.size());
|
m_staticsTextures.resize(g_Level.StaticsTextures.size());
|
||||||
for (int i = 0; i < g_Level.StaticsTextures.size(); i++)
|
for (int i = 0; i < g_Level.StaticsTextures.size(); i++)
|
||||||
{
|
{
|
||||||
|
@ -129,6 +143,9 @@ namespace TEN::Renderer
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (m_staticsTextures.size() > 0)
|
||||||
|
TENLog("Generated " + std::to_string(m_staticsTextures.size()) + " static mesh texture atlases.", LogLevel::Info);
|
||||||
|
|
||||||
m_spritesTextures.resize(g_Level.SpritesTextures.size());
|
m_spritesTextures.resize(g_Level.SpritesTextures.size());
|
||||||
for (int i = 0; i < g_Level.SpritesTextures.size(); i++)
|
for (int i = 0; i < g_Level.SpritesTextures.size(); i++)
|
||||||
{
|
{
|
||||||
|
@ -136,8 +153,13 @@ namespace TEN::Renderer
|
||||||
m_spritesTextures[i] = Texture2D(m_device.Get(), texture->colorMapData.data(), texture->colorMapData.size());
|
m_spritesTextures[i] = Texture2D(m_device.Get(), texture->colorMapData.data(), texture->colorMapData.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (m_spritesTextures.size() > 0)
|
||||||
|
TENLog("Generated " + std::to_string(m_spritesTextures.size()) + " sprite atlases.", LogLevel::Info);
|
||||||
|
|
||||||
m_skyTexture = Texture2D(m_device.Get(), g_Level.SkyTexture.colorMapData.data(), g_Level.SkyTexture.colorMapData.size());
|
m_skyTexture = Texture2D(m_device.Get(), g_Level.SkyTexture.colorMapData.data(), g_Level.SkyTexture.colorMapData.size());
|
||||||
|
|
||||||
|
TENLog("Loaded sky texture.", LogLevel::Info);
|
||||||
|
|
||||||
int totalVertices = 0;
|
int totalVertices = 0;
|
||||||
int totalIndices = 0;
|
int totalIndices = 0;
|
||||||
for (auto& room : g_Level.Rooms)
|
for (auto& room : g_Level.Rooms)
|
||||||
|
@ -153,9 +175,13 @@ namespace TEN::Renderer
|
||||||
roomsVertices.resize(totalVertices);
|
roomsVertices.resize(totalVertices);
|
||||||
roomsIndices.resize(totalIndices);
|
roomsIndices.resize(totalIndices);
|
||||||
|
|
||||||
|
TENLog("Loaded total " + std::to_string(totalVertices) + " room vertices.", LogLevel::Info);
|
||||||
|
|
||||||
int lastVertex = 0;
|
int lastVertex = 0;
|
||||||
int lastIndex = 0;
|
int lastIndex = 0;
|
||||||
|
|
||||||
|
TENLog("Preparing room data...", LogLevel::Info);
|
||||||
|
|
||||||
for (int i = 0; i < g_Level.Rooms.size(); i++)
|
for (int i = 0; i < g_Level.Rooms.size(); i++)
|
||||||
{
|
{
|
||||||
ROOM_INFO& room = g_Level.Rooms[i];
|
ROOM_INFO& room = g_Level.Rooms[i];
|
||||||
|
@ -404,6 +430,8 @@ namespace TEN::Renderer
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
TENLog("Preparing object data...", LogLevel::Info);
|
||||||
|
|
||||||
bool skinPresent = false;
|
bool skinPresent = false;
|
||||||
bool hairsPresent = false;
|
bool hairsPresent = false;
|
||||||
|
|
||||||
|
@ -707,6 +735,8 @@ namespace TEN::Renderer
|
||||||
m_moveablesVertexBuffer = VertexBuffer(m_device.Get(), moveablesVertices.size(), moveablesVertices.data());
|
m_moveablesVertexBuffer = VertexBuffer(m_device.Get(), moveablesVertices.size(), moveablesVertices.data());
|
||||||
m_moveablesIndexBuffer = IndexBuffer(m_device.Get(), moveablesIndices.size(), moveablesIndices.data());
|
m_moveablesIndexBuffer = IndexBuffer(m_device.Get(), moveablesIndices.size(), moveablesIndices.data());
|
||||||
|
|
||||||
|
TENLog("Preparing static mesh data...", LogLevel::Info);
|
||||||
|
|
||||||
totalVertices = 0;
|
totalVertices = 0;
|
||||||
totalIndices = 0;
|
totalIndices = 0;
|
||||||
for (int i = 0; i < StaticObjectsIds.size(); i++)
|
for (int i = 0; i < StaticObjectsIds.size(); i++)
|
||||||
|
@ -753,6 +783,8 @@ namespace TEN::Renderer
|
||||||
m_staticsIndexBuffer = IndexBuffer(m_device.Get(), 1);
|
m_staticsIndexBuffer = IndexBuffer(m_device.Get(), 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TENLog("Preparing sprite data...", LogLevel::Info);
|
||||||
|
|
||||||
// Step 5: prepare sprites
|
// Step 5: prepare sprites
|
||||||
m_sprites.resize(g_Level.Sprites.size());
|
m_sprites.resize(g_Level.Sprites.size());
|
||||||
|
|
||||||
|
|
|
@ -1368,7 +1368,7 @@ namespace TEN::Renderer
|
||||||
if (!smoke.active)
|
if (!smoke.active)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// TODO: Switch back to alpha blend mode once rendering for it is refactored. -- Sezz 21.01.2023
|
// TODO: Switch back to alpha blend mode once rendering for it is refactored. -- Sezz 2023.01.14
|
||||||
AddSpriteBillboard(
|
AddSpriteBillboard(
|
||||||
&m_sprites[Objects[ID_SMOKE_SPRITES].meshIndex + smoke.sprite],
|
&m_sprites[Objects[ID_SMOKE_SPRITES].meshIndex + smoke.sprite],
|
||||||
smoke.position,
|
smoke.position,
|
||||||
|
|
|
@ -1356,7 +1356,7 @@ static const std::unordered_map<std::string, GAME_OBJECT_ID> kObjIDs {
|
||||||
{ "SWORD_GUARDIAN_STATUE", ID_SWORD_GUARDIAN_STATUE },
|
{ "SWORD_GUARDIAN_STATUE", ID_SWORD_GUARDIAN_STATUE },
|
||||||
{ "SHIVA", ID_SHIVA },
|
{ "SHIVA", ID_SHIVA },
|
||||||
{ "SHIVA_STATUE", ID_SHIVA_STATUE },
|
{ "SHIVA_STATUE", ID_SHIVA_STATUE },
|
||||||
{ "TRIBEBOSS", ID_TRIBEBOSS },
|
{ "WILLARD", ID_WILLARD },
|
||||||
{ "CIVVY", ID_CIVVY },
|
{ "CIVVY", ID_CIVVY },
|
||||||
{ "MUTANT2", ID_MUTANT2 },
|
{ "MUTANT2", ID_MUTANT2 },
|
||||||
{ "LIZARD", ID_LIZARD },
|
{ "LIZARD", ID_LIZARD },
|
||||||
|
|
|
@ -97,7 +97,7 @@ struct ObjectInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Use this to set up a hit rffect for the slot based on its value.
|
/// Use this to set up a hit effect for the slot based on its value.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="isAlive">Use this if the object is alive but not intelligent to set up blood effects.</param>
|
/// <param name="isAlive">Use this if the object is alive but not intelligent to set up blood effects.</param>
|
||||||
void SetupHitEffect(bool isSolid = false, bool isAlive = false)
|
void SetupHitEffect(bool isSolid = false, bool isAlive = false)
|
||||||
|
|
|
@ -167,6 +167,9 @@ CALL gen.bat</Command>
|
||||||
<ClInclude Include="Game\GuiObjects.h" />
|
<ClInclude Include="Game\GuiObjects.h" />
|
||||||
<ClInclude Include="Objects\TR1\Trap\DamoclesSword.h" />
|
<ClInclude Include="Objects\TR1\Trap\DamoclesSword.h" />
|
||||||
<ClInclude Include="Objects\TR5\Entity\HeavyGuard.h" />
|
<ClInclude Include="Objects\TR5\Entity\HeavyGuard.h" />
|
||||||
|
<ClInclude Include="Objects\TR3\Entity\Lizard.h" />
|
||||||
|
<ClInclude Include="Objects\TR3\Entity\PunaBoss.h" />
|
||||||
|
<ClInclude Include="Objects\Effects\Boss.h" />
|
||||||
<ClInclude Include="Scripting\Internal\TEN\Effects\EffectIDs.h" />
|
<ClInclude Include="Scripting\Internal\TEN\Effects\EffectIDs.h" />
|
||||||
<ClInclude Include="Scripting\Internal\TEN\Misc\ActionIDs.h" />
|
<ClInclude Include="Scripting\Internal\TEN\Misc\ActionIDs.h" />
|
||||||
<ClInclude Include="Scripting\Internal\TEN\Misc\CameraTypes.h" />
|
<ClInclude Include="Scripting\Internal\TEN\Misc\CameraTypes.h" />
|
||||||
|
@ -639,6 +642,9 @@ CALL gen.bat</Command>
|
||||||
<ClCompile Include="Game\GuiObjects.cpp" />
|
<ClCompile Include="Game\GuiObjects.cpp" />
|
||||||
<ClCompile Include="Objects\TR1\Trap\DamoclesSword.cpp" />
|
<ClCompile Include="Objects\TR1\Trap\DamoclesSword.cpp" />
|
||||||
<ClCompile Include="Objects\TR5\Entity\HeavyGuard.cpp" />
|
<ClCompile Include="Objects\TR5\Entity\HeavyGuard.cpp" />
|
||||||
|
<ClCompile Include="Objects\TR3\Entity\Lizard.cpp" />
|
||||||
|
<ClCompile Include="Objects\TR3\Entity\PunaBoss.cpp" />
|
||||||
|
<ClCompile Include="Objects\Effects\Boss.cpp" />
|
||||||
<ClCompile Include="Scripting\Internal\TEN\Objects\Lara\LaraObject.cpp" />
|
<ClCompile Include="Scripting\Internal\TEN\Objects\Lara\LaraObject.cpp" />
|
||||||
<ClCompile Include="Scripting\Internal\TEN\Objects\Room\RoomObject.cpp" />
|
<ClCompile Include="Scripting\Internal\TEN\Objects\Room\RoomObject.cpp" />
|
||||||
<ClCompile Include="Scripting\Internal\TEN\Objects\Volume\VolumeObject.cpp" />
|
<ClCompile Include="Scripting\Internal\TEN\Objects\Volume\VolumeObject.cpp" />
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue