mirror of
https://github.com/TombEngine/TombEngine.git
synced 2025-05-02 01:37:59 +03:00
Electric Cleaner with vectors adngel (#1005)
* Add cleaner code; * Do smooth turns * Revert acceleration (not working properly yet) * Update cleaner.cpp * Fix AdjustStopperFlag function, modify turn speed * Move electric cleaner into namespace * Cleaning cleaner first pass * Demagic turn tolerance, use bools * Update ElectricCleaner.cpp * Revert smooth turns * Format code according to conventions, rename function * cleaner update * Updated * Updated OCB to flag data. * Formula simplified * restored original radius I had reduced while I was working on it, but that's not needed anymore. * Update comment * Temporary Fix: Method to detect pushables. * Feedback applied * Revert primitive variable from auto to float --------- Co-authored-by: Troye <woopstombraider@gmail.com> Co-authored-by: Lwmte <3331699+Lwmte@users.noreply.github.com> Co-authored-by: Sezz <sezzary@outlook.com> Co-authored-by: Kubsy <80340234+Kubsy@users.noreply.github.com>
This commit is contained in:
parent
2a4404effe
commit
302eb9c93b
11 changed files with 475 additions and 8 deletions
|
@ -168,6 +168,11 @@ CollisionResult GetCollision(int x, int y, int z, short roomNumber)
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CollisionResult GetCollision(const GameVector& point)
|
||||||
|
{
|
||||||
|
return GetCollision(point.x, point.y, point.z, point.RoomNumber);
|
||||||
|
}
|
||||||
|
|
||||||
// A reworked legacy GetFloorHeight() function which writes data
|
// A reworked legacy GetFloorHeight() function which writes data
|
||||||
// into a special CollisionResult struct instead of global variables.
|
// into a special CollisionResult struct instead of global variables.
|
||||||
// It writes for both floor and ceiling heights at the same coordinates, meaning it should be used
|
// It writes for both floor and ceiling heights at the same coordinates, meaning it should be used
|
||||||
|
|
|
@ -126,6 +126,7 @@ CollisionResult GetCollision(ItemInfo* item);
|
||||||
CollisionResult GetCollision(ItemInfo* item, short headingAngle, float forward, float down = 0.0f, float right = 0.0f);
|
CollisionResult GetCollision(ItemInfo* item, short headingAngle, float forward, float down = 0.0f, float right = 0.0f);
|
||||||
CollisionResult GetCollision(Vector3i pos, int roomNumber, short headingAngle, float forward, float down = 0.0f, float right = 0.0f);
|
CollisionResult GetCollision(Vector3i pos, int roomNumber, short headingAngle, float forward, float down = 0.0f, float right = 0.0f);
|
||||||
CollisionResult GetCollision(int x, int y, int z, short roomNumber);
|
CollisionResult GetCollision(int x, int y, int z, short roomNumber);
|
||||||
|
CollisionResult GetCollision(const GameVector& point);
|
||||||
CollisionResult GetCollision(FloorInfo* floor, int x, int y, int z);
|
CollisionResult GetCollision(FloorInfo* floor, int x, int y, int z);
|
||||||
|
|
||||||
void GetCollisionInfo(CollisionInfo* coll, ItemInfo* item, const Vector3i& offset, bool resetRoom = false);
|
void GetCollisionInfo(CollisionInfo* coll, ItemInfo* item, const Vector3i& offset, bool resetRoom = false);
|
||||||
|
|
|
@ -2081,21 +2081,21 @@ TARGET_TYPE CalculateTarget(Vector3i* target, ItemInfo* item, LOTInfo* LOT)
|
||||||
return TARGET_TYPE::NO_TARGET;
|
return TARGET_TYPE::NO_TARGET;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AdjustStopperFlag(ItemInfo* item, int direction, bool set)
|
void AdjustStopperFlag(ItemInfo* item, int direction)
|
||||||
{
|
{
|
||||||
int x = item->Pose.Position.x;
|
int x = item->Pose.Position.x;
|
||||||
int z = item->Pose.Position.z;
|
int z = item->Pose.Position.z;
|
||||||
|
|
||||||
auto* room = &g_Level.Rooms[item->RoomNumber];
|
auto* room = &g_Level.Rooms[item->RoomNumber];
|
||||||
auto* floor = GetSector(room, x - room->x, z - room->z);
|
auto* floor = GetSector(room, x - room->x, z - room->z);
|
||||||
floor->Stopper = set;
|
floor->Stopper = !floor->Stopper;
|
||||||
|
|
||||||
x = item->Pose.Position.x + SECTOR(1) * phd_sin(direction);
|
x = item->Pose.Position.x + SECTOR(1) * phd_sin(direction);
|
||||||
z = item->Pose.Position.z + SECTOR(1) * phd_cos(direction);
|
z = item->Pose.Position.z + SECTOR(1) * phd_cos(direction);
|
||||||
room = &g_Level.Rooms[GetCollision(x, item->Pose.Position.y, z, item->RoomNumber).RoomNumber];
|
room = &g_Level.Rooms[GetCollision(x, item->Pose.Position.y, z, item->RoomNumber).RoomNumber];
|
||||||
|
|
||||||
floor = GetSector(room, x - room->x, z - room->z);
|
floor = GetSector(room, x - room->x, z - room->z);
|
||||||
floor->Stopper = set;
|
floor->Stopper = !floor->Stopper;
|
||||||
}
|
}
|
||||||
|
|
||||||
void InitialiseItemBoxData()
|
void InitialiseItemBoxData()
|
||||||
|
|
|
@ -187,7 +187,7 @@ void CreatureAIInfo(ItemInfo* item, AI_INFO* AI);
|
||||||
TARGET_TYPE CalculateTarget(Vector3i* target, ItemInfo* item, LOTInfo* LOT);
|
TARGET_TYPE CalculateTarget(Vector3i* target, ItemInfo* item, LOTInfo* LOT);
|
||||||
bool CreatureAnimation(short itemNumber, short angle, short tilt);
|
bool CreatureAnimation(short itemNumber, short angle, short tilt);
|
||||||
void CreatureHealth(ItemInfo* item);
|
void CreatureHealth(ItemInfo* item);
|
||||||
void AdjustStopperFlag(ItemInfo* item, int direction, bool set);
|
void AdjustStopperFlag(ItemInfo* item, int direction);
|
||||||
void InitialiseItemBoxData();
|
void InitialiseItemBoxData();
|
||||||
|
|
||||||
bool CanCreatureJump(ItemInfo& item, JumpDistance jumpDistType);
|
bool CanCreatureJump(ItemInfo& item, JumpDistance jumpDistType);
|
||||||
|
|
422
TombEngine/Objects/TR3/Trap/ElectricCleaner.cpp
Normal file
422
TombEngine/Objects/TR3/Trap/ElectricCleaner.cpp
Normal file
|
@ -0,0 +1,422 @@
|
||||||
|
#include "framework.h"
|
||||||
|
#include "Objects/TR3/Trap/ElectricCleaner.h"
|
||||||
|
|
||||||
|
#include "Game/collision/collide_item.h"
|
||||||
|
#include "Game/control/box.h"
|
||||||
|
#include "Game/effects/item_fx.h"
|
||||||
|
#include "Game/effects/spark.h"
|
||||||
|
#include "Game/effects/tomb4fx.h"
|
||||||
|
#include "Game/Lara/lara_helpers.h"
|
||||||
|
#include "Specific/setup.h"
|
||||||
|
|
||||||
|
using namespace TEN::Effects::Items;
|
||||||
|
using namespace TEN::Effects::Spark;
|
||||||
|
|
||||||
|
// ItemFlags[0]: Rotation speed and heading angle.
|
||||||
|
// ItemFlags[1]: Flags, each bit is used to check the status of a flag
|
||||||
|
// b0: flagDoDetection
|
||||||
|
// b1: flagTurnRight
|
||||||
|
// b2: flagPriorityForward
|
||||||
|
// b3: flagAntiClockWiseOrder
|
||||||
|
// b4: flagStopAfterKill - If true the cleaner will stop when kills Lara.
|
||||||
|
// ItemFlags[2]: Movement velocity.
|
||||||
|
// ItemFlags[3, 4, 5]: Counters for dynamic lights and sparks.
|
||||||
|
// ItemFlags[6]: Goal direction angle.
|
||||||
|
|
||||||
|
// OCB:
|
||||||
|
// 0: Stop after killing the player.
|
||||||
|
// Anything else: Don't stop after killing the player.
|
||||||
|
|
||||||
|
namespace TEN::Entities::Traps
|
||||||
|
{
|
||||||
|
constexpr auto ELECTRIC_CLEANER_VELOCITY = BLOCK(1 / 16.0f);
|
||||||
|
constexpr auto ELECTRIC_CLEANER_TURN_RATE = 1024;
|
||||||
|
|
||||||
|
const auto ElectricCleanerHarmJoints = std::vector<unsigned int>{ 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13 };
|
||||||
|
|
||||||
|
std::vector <ItemInfo*> MyPushablesList = {};
|
||||||
|
|
||||||
|
void InitialiseElectricCleaner(short itemNumber)
|
||||||
|
{
|
||||||
|
auto& item = g_Level.Items[itemNumber];
|
||||||
|
|
||||||
|
// Align to the middle of the block.
|
||||||
|
item.Pose.Position.x = (item.Pose.Position.x & ~WALL_MASK) | (int)BLOCK(0.5f);
|
||||||
|
item.Pose.Position.z = (item.Pose.Position.z & ~WALL_MASK) | (int)BLOCK(0.5f);
|
||||||
|
|
||||||
|
// Init flags.
|
||||||
|
item.ItemFlags[0] = ELECTRIC_CLEANER_TURN_RATE;
|
||||||
|
item.ItemFlags[1] = 0;
|
||||||
|
item.ItemFlags[2] = ELECTRIC_CLEANER_VELOCITY;
|
||||||
|
item.ItemFlags[6] = item.Pose.Orientation.y;
|
||||||
|
item.Collidable = true;
|
||||||
|
|
||||||
|
if (item.TriggerFlags)
|
||||||
|
item.ItemFlags[1] &= ~(1 << 4); // Turn off 1st bit for flagStopAfterKill.
|
||||||
|
else
|
||||||
|
item.ItemFlags[1] |= (1 << 4); // Turn on 1st bit for flagStopAfterKill.
|
||||||
|
|
||||||
|
CollectLevelPushables(MyPushablesList);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ElectricCleanerControl(short itemNumber)
|
||||||
|
{
|
||||||
|
auto& item = g_Level.Items[itemNumber];
|
||||||
|
auto& object = Objects[item.ObjectNumber];
|
||||||
|
|
||||||
|
auto& rotationVel = item.ItemFlags[0];
|
||||||
|
auto& moveVel = item.ItemFlags[2];
|
||||||
|
auto& goalAngle = item.ItemFlags[6];
|
||||||
|
|
||||||
|
if (!TriggerActive(&item))
|
||||||
|
{
|
||||||
|
if (moveVel > 0)
|
||||||
|
{
|
||||||
|
moveVel = 0;
|
||||||
|
SoundEffect(SFX_TR3_CLEANER_FUSEBOX, &item.Pose);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (moveVel <= 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto angleDifference = abs(TO_RAD(goalAngle) - TO_RAD(item.Pose.Orientation.y));
|
||||||
|
|
||||||
|
bool flagDoDetection = ((item.ItemFlags[1] & (1 << 0)) != 0);
|
||||||
|
bool flagTurnRight = ((item.ItemFlags[1] & (1 << 1)) != 0);
|
||||||
|
bool flagPriorityForward = ((item.ItemFlags[1] & (1 << 2)) != 0);
|
||||||
|
bool flagAntiClockWiseOrder = ((item.ItemFlags[1] & (1 << 3)) != 0);
|
||||||
|
|
||||||
|
auto col = GetCollision(item.Pose.Position.x, item.Pose.Position.y, item.Pose.Position.z, item.RoomNumber);
|
||||||
|
|
||||||
|
float yaw = TO_RAD(item.Pose.Orientation.y);
|
||||||
|
|
||||||
|
auto forwardDirection = Vector3(sin(yaw), 0, cos(yaw));
|
||||||
|
forwardDirection.Normalize();
|
||||||
|
|
||||||
|
auto rightDirection = Vector3(cos(yaw), 0, -sin(yaw));
|
||||||
|
rightDirection.Normalize();
|
||||||
|
|
||||||
|
if (angleDifference > TO_RAD(rotationVel))
|
||||||
|
{
|
||||||
|
if (flagTurnRight)
|
||||||
|
item.Pose.Orientation.y -= rotationVel;
|
||||||
|
else
|
||||||
|
item.Pose.Orientation.y += rotationVel;
|
||||||
|
|
||||||
|
// Recalculate new difference to check if we should force align with axis for safety check.
|
||||||
|
angleDifference = abs(TO_RAD(goalAngle) - TO_RAD(item.Pose.Orientation.y));
|
||||||
|
if (angleDifference <= TO_RAD(rotationVel))
|
||||||
|
item.Pose.Orientation.y = goalAngle;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (flagDoDetection &&
|
||||||
|
(item.Pose.Position.x & WALL_MASK) == BLOCK(0.5f) &&
|
||||||
|
(item.Pose.Position.z & WALL_MASK) == BLOCK(0.5f))
|
||||||
|
{
|
||||||
|
//Do triggers
|
||||||
|
TestTriggers(&item, true);
|
||||||
|
|
||||||
|
//Search for next direction
|
||||||
|
Vector3 NewDirection;
|
||||||
|
|
||||||
|
if (flagPriorityForward)
|
||||||
|
{
|
||||||
|
if (flagAntiClockWiseOrder) //Forward Right Left
|
||||||
|
NewDirection = ElectricCleanerSearchDirections(item, forwardDirection, rightDirection, -rightDirection);
|
||||||
|
else //Forward Left Right
|
||||||
|
NewDirection = ElectricCleanerSearchDirections(item, forwardDirection, -rightDirection, rightDirection);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (flagAntiClockWiseOrder) //Right Forward Left
|
||||||
|
NewDirection = ElectricCleanerSearchDirections(item, rightDirection, forwardDirection, -rightDirection);
|
||||||
|
else //Left Forward Right
|
||||||
|
NewDirection = ElectricCleanerSearchDirections(item, -rightDirection, forwardDirection, rightDirection);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (NewDirection == Vector3::Zero) //Return back. (We already know is a valid one because it came from there).
|
||||||
|
NewDirection = -forwardDirection;
|
||||||
|
|
||||||
|
//Will turn left or right?
|
||||||
|
auto crossProductResult = NewDirection.Cross(forwardDirection);
|
||||||
|
if (crossProductResult.y > 0)
|
||||||
|
item.ItemFlags[1] |= (1 << 1); // Turn on 1st bit for flagTurnRight.
|
||||||
|
else if (crossProductResult.y < 0)
|
||||||
|
item.ItemFlags[1] &= ~(1 << 1); // Turn off 1st bit for flagTurnRight. (So it'll turn to the left)
|
||||||
|
|
||||||
|
//Store goal angle to control the rotation.
|
||||||
|
item.ItemFlags[6] = FROM_RAD(atan2(NewDirection.x, NewDirection.z));
|
||||||
|
|
||||||
|
if (item.Pose.Orientation.y - item.ItemFlags[6] == 0)
|
||||||
|
//If it doesn't have to rotate, do forward movement to keep smooth movement.
|
||||||
|
item.Pose.Position = item.Pose.Position + forwardDirection * moveVel;
|
||||||
|
else
|
||||||
|
//If it has to rotate, stop detection so it doesn't calculate collisions again while rotating in the same sector.
|
||||||
|
item.ItemFlags[1] &= ~(1 << 0); // Turn off 1st bit for flagDoDetection.
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
item.Pose.Position.y = col.Position.Floor;
|
||||||
|
|
||||||
|
//Is not in the center of a tile, keep moving forward.
|
||||||
|
item.Pose.Position = item.Pose.Position + forwardDirection * moveVel;
|
||||||
|
|
||||||
|
auto slope = col.Block->FloorSlope(0);
|
||||||
|
|
||||||
|
if (slope.LengthSquared() > 0) //If it's a slope, don't do turns.
|
||||||
|
item.ItemFlags[1] &= ~(1 << 0); // Turn off 1st bit for flagDoDetection.
|
||||||
|
else
|
||||||
|
item.ItemFlags[1] |= (1 << 0); // Turn on 1st bit for flagDoDetection.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AnimateItem(&item);
|
||||||
|
|
||||||
|
int probedRoomNumber = GetCollision(&item).RoomNumber;
|
||||||
|
if (item.RoomNumber != probedRoomNumber)
|
||||||
|
ItemNewRoom(itemNumber, probedRoomNumber);
|
||||||
|
|
||||||
|
auto radius = Vector2(object.radius, object.radius);
|
||||||
|
AlignEntityToSurface(&item, radius);
|
||||||
|
|
||||||
|
SpawnElectricCleanerSparks(item);
|
||||||
|
ElectricCleanerToItemCollision(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsNextSectorValid(ItemInfo& item, const Vector3& dir)
|
||||||
|
{
|
||||||
|
GameVector detectionPoint = item.Pose.Position + dir * BLOCK(1);
|
||||||
|
detectionPoint.RoomNumber = item.RoomNumber;
|
||||||
|
|
||||||
|
auto col = GetCollision(detectionPoint);
|
||||||
|
|
||||||
|
//Is a wall
|
||||||
|
if (col.Block->IsWall(detectionPoint.x, detectionPoint.z))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
//Is it a sliding slope?
|
||||||
|
if (col.Position.FloorSlope)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (abs(col.FloorTilt.x) == 0 && abs(col.FloorTilt.y) == 0) //Is a flat tile
|
||||||
|
{
|
||||||
|
//Is a 1 click step (higher or lower).
|
||||||
|
int distanceToFloor = abs(col.Position.Floor - item.Pose.Position.y);
|
||||||
|
if (distanceToFloor >= CLICK(1))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else //Is a slope tile
|
||||||
|
{
|
||||||
|
//Is a 2 click step (higher or lower).
|
||||||
|
int distanceToFloor = abs(col.Position.Floor - item.Pose.Position.y);
|
||||||
|
if (distanceToFloor > CLICK(2))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
short slopeAngle = ANGLE(0.0f);
|
||||||
|
|
||||||
|
if (col.FloorTilt.x > 0)
|
||||||
|
slopeAngle = -ANGLE(90.0f);
|
||||||
|
else if (col.FloorTilt.x < 0)
|
||||||
|
slopeAngle = ANGLE(90.0f);
|
||||||
|
|
||||||
|
if (col.FloorTilt.y > 0 && col.FloorTilt.y > abs(col.FloorTilt.x))
|
||||||
|
slopeAngle = ANGLE(180.0f);
|
||||||
|
else if (col.FloorTilt.y < 0 && -col.FloorTilt.y > abs(col.FloorTilt.x))
|
||||||
|
slopeAngle = ANGLE(0.0f);
|
||||||
|
|
||||||
|
auto angleDir = FROM_RAD(atan2(dir.x, dir.z));
|
||||||
|
auto alignment = slopeAngle - angleDir;
|
||||||
|
|
||||||
|
//Is slope not aligned with the direction?
|
||||||
|
if ((alignment != 32768) && (alignment != 0) && (alignment != -32768))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Is diagonal floor?
|
||||||
|
if (col.Position.DiagonalStep)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
//Is ceiling (square or diagonal) high enough?
|
||||||
|
int distanceToCeiling = abs(col.Position.Ceiling - col.Position.Floor);
|
||||||
|
int cleanerHeight = BLOCK(1); //TODO change it for the collision bounding box height.
|
||||||
|
if (distanceToCeiling < cleanerHeight)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
//Is a non walkable tile? (So there is not any box)
|
||||||
|
if (col.Block->Box == NO_BOX)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
//Is a blocked grey box (So it's an Isolated box)
|
||||||
|
if (g_Level.Boxes[col.Block->Box].flags & BLOCKABLE)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
//Is a stopper tile? (There is still a shatter object).
|
||||||
|
if (col.Block->Stopper)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
//Is there a pushable block?
|
||||||
|
if (CheckPushableList(MyPushablesList, detectionPoint.ToVector3()))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
//If nothing of that happened, then it must be a valid sector.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector3 ElectricCleanerSearchDirections(ItemInfo& item, const Vector3& dir1, const Vector3& dir2, const Vector3& dir3)
|
||||||
|
{
|
||||||
|
if (IsNextSectorValid(item, dir1))
|
||||||
|
return dir1;
|
||||||
|
if (IsNextSectorValid(item, dir2))
|
||||||
|
return dir2;
|
||||||
|
if (IsNextSectorValid(item, dir3))
|
||||||
|
return dir3;
|
||||||
|
|
||||||
|
return Vector3::Zero;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ElectricCleanerCollision(short itemNumber, ItemInfo* laraItem, CollisionInfo* coll)
|
||||||
|
{
|
||||||
|
auto& item = g_Level.Items[itemNumber];
|
||||||
|
|
||||||
|
ObjectCollision(itemNumber, laraItem, coll);
|
||||||
|
|
||||||
|
if (item.TouchBits.Test(ElectricCleanerHarmJoints) && item.ItemFlags[2])
|
||||||
|
{
|
||||||
|
ItemElectricBurn(laraItem, -1);
|
||||||
|
laraItem->HitPoints = 0;
|
||||||
|
|
||||||
|
bool flagStopAfterKill = ((item.ItemFlags[1] & (1 << 4)) != 0);
|
||||||
|
if (flagStopAfterKill)
|
||||||
|
item.ItemFlags[2] = 0;
|
||||||
|
|
||||||
|
SoundEffect(SFX_TR3_CLEANER_FUSEBOX, &item.Pose);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ElectricCleanerToItemCollision(ItemInfo& item)
|
||||||
|
{
|
||||||
|
auto backupPos = item.Pose.Position;
|
||||||
|
|
||||||
|
switch (item.Pose.Orientation.y)
|
||||||
|
{
|
||||||
|
case ANGLE(0.0f):
|
||||||
|
item.Pose.Position.z += BLOCK(0.5f);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ANGLE(90.0f):
|
||||||
|
item.Pose.Position.x += BLOCK(0.5f);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ANGLE(-180.0f):
|
||||||
|
item.Pose.Position.z -= BLOCK(0.5f);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ANGLE(-90.0f):
|
||||||
|
item.Pose.Position.x -= BLOCK(0.5f);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (GetCollidedObjects(&item, CLICK(1), true, CollidedItems, CollidedMeshes, true))
|
||||||
|
{
|
||||||
|
int lp = 0;
|
||||||
|
while (CollidedItems[lp] != nullptr)
|
||||||
|
{
|
||||||
|
if (Objects[CollidedItems[lp]->ObjectNumber].intelligent)
|
||||||
|
{
|
||||||
|
CollidedItems[lp]->HitPoints = 0;
|
||||||
|
ItemElectricBurn(CollidedItems[lp], 120);
|
||||||
|
}
|
||||||
|
|
||||||
|
lp++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
item.Pose.Position = backupPos;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SpawnElectricCleanerSparks(ItemInfo& item)
|
||||||
|
{
|
||||||
|
static auto wireEndJoints = std::array<int, 3>{ 5, 9, 13 };
|
||||||
|
|
||||||
|
SoundEffect(SFX_TR3_CLEANER_LOOP, &item.Pose);
|
||||||
|
|
||||||
|
auto vel = Vector3i(
|
||||||
|
(Random::GenerateInt(0, 255) * 4) - 512,
|
||||||
|
Random::GenerateInt(0, 7) - 4,
|
||||||
|
(Random::GenerateInt(0, 255) * 4) - 512);
|
||||||
|
|
||||||
|
for (int i = 0; i < 3; i++)
|
||||||
|
{
|
||||||
|
if ((!(GetRandomControl() & 7) && !item.ItemFlags[3 + i]) || item.ItemFlags[3 + i])
|
||||||
|
{
|
||||||
|
if (!item.ItemFlags[3 + i])
|
||||||
|
item.ItemFlags[3 + i] = Random::GenerateInt(0, 12) + 8;
|
||||||
|
else
|
||||||
|
item.ItemFlags[3 + i]--;
|
||||||
|
|
||||||
|
int joint = wireEndJoints[i];
|
||||||
|
auto pos = GetJointPosition(&item, joint, Vector3i(-160, -8, 16));
|
||||||
|
|
||||||
|
byte c = Random::GenerateInt(0, 64) + 128;
|
||||||
|
TriggerDynamicLight(pos.x, pos.y, pos.z, 10, c >> 2, c >> 1, c);
|
||||||
|
|
||||||
|
auto& spark = GetFreeSparkParticle();
|
||||||
|
|
||||||
|
spark = {};
|
||||||
|
spark.active = true;
|
||||||
|
spark.age = 0;
|
||||||
|
float color = (192.0F + Random::GenerateFloat(0, 63.0F)) / 255.0F;
|
||||||
|
spark.sourceColor = Vector4(color / 4, color / 2, color, 1.0F);
|
||||||
|
color = (192.0F + Random::GenerateFloat(0, 63.0F)) / 255.0F;
|
||||||
|
spark.destinationColor = Vector4(color / 4, color / 2, color, 1.0F);
|
||||||
|
spark.life = Random::GenerateFloat(20, 27);
|
||||||
|
spark.friction = 1.2f;
|
||||||
|
spark.gravity = 1.5f;
|
||||||
|
spark.width = 8.0f;
|
||||||
|
spark.height = 96.0f;
|
||||||
|
auto v = vel.ToVector3();
|
||||||
|
v.Normalize(v);
|
||||||
|
spark.velocity = v;
|
||||||
|
spark.pos = pos.ToVector3();
|
||||||
|
spark.room = item.RoomNumber;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO method to detect pushables while Pushable_Object get refactored.
|
||||||
|
|
||||||
|
void CollectLevelPushables(std::vector <ItemInfo* >& PushablesList)
|
||||||
|
{
|
||||||
|
for (int index = 0; index < g_Level.Items.size(); index++)
|
||||||
|
{
|
||||||
|
ItemInfo* currentItem = &g_Level.Items[index];
|
||||||
|
if (currentItem->ObjectNumber >= (ID_PUSHABLE_OBJECT1) &&
|
||||||
|
currentItem->ObjectNumber <= (ID_PUSHABLE_OBJECT10))
|
||||||
|
PushablesList.push_back(currentItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CheckPushableList(std::vector <ItemInfo* >& PushablesList, Vector3& refPoint)
|
||||||
|
{
|
||||||
|
auto pushableDistance = INFINITE;
|
||||||
|
for (int index = 0; index < PushablesList.size(); index++)
|
||||||
|
{
|
||||||
|
ItemInfo* currentObj = PushablesList[index];
|
||||||
|
|
||||||
|
if (currentObj == nullptr)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
auto PushablePos = currentObj->Pose.Position.ToVector3();
|
||||||
|
auto currentDistance = Vector3::Distance(PushablePos, refPoint);
|
||||||
|
|
||||||
|
if (currentDistance < 1024)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
21
TombEngine/Objects/TR3/Trap/ElectricCleaner.h
Normal file
21
TombEngine/Objects/TR3/Trap/ElectricCleaner.h
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
struct CollisionInfo;
|
||||||
|
struct ItemInfo;
|
||||||
|
|
||||||
|
namespace TEN::Entities::Traps
|
||||||
|
{
|
||||||
|
void InitialiseElectricCleaner(short itemNumber);
|
||||||
|
void ElectricCleanerControl(short itemNumber);
|
||||||
|
void ElectricCleanerCollision(short itemNumber, ItemInfo* laraItem, CollisionInfo* coll);
|
||||||
|
|
||||||
|
bool IsNextSectorValid(ItemInfo& item, const Vector3& dir);
|
||||||
|
Vector3 ElectricCleanerSearchDirections(ItemInfo& item, const Vector3& dir1, const Vector3& dir2, const Vector3& dir3);
|
||||||
|
void ElectricCleanerToItemCollision(ItemInfo& item);
|
||||||
|
void SpawnElectricCleanerSparks(ItemInfo& item);
|
||||||
|
|
||||||
|
//TODO method to detect pushables while Pushable_Object get refactored.
|
||||||
|
void CollectLevelPushables (std::vector <ItemInfo*>& PushablesList);
|
||||||
|
bool CheckPushableList (std::vector <ItemInfo* >& PushablesList, Vector3& refPoint);
|
||||||
|
|
||||||
|
}
|
|
@ -32,6 +32,7 @@
|
||||||
#include "Objects/Effects/Boss.h"
|
#include "Objects/Effects/Boss.h"
|
||||||
|
|
||||||
// Traps
|
// Traps
|
||||||
|
#include "Objects/TR3/Trap/ElectricCleaner.h"
|
||||||
#include "Objects/TR3/Trap/train.h"
|
#include "Objects/TR3/Trap/train.h"
|
||||||
|
|
||||||
// Vehicles
|
// Vehicles
|
||||||
|
@ -44,6 +45,7 @@
|
||||||
#include "Objects/Utils/object_helper.h"
|
#include "Objects/Utils/object_helper.h"
|
||||||
|
|
||||||
using namespace TEN::Entities::Creatures::TR3;
|
using namespace TEN::Entities::Creatures::TR3;
|
||||||
|
using namespace TEN::Entities::Traps;
|
||||||
using namespace TEN::Effects::Boss;
|
using namespace TEN::Effects::Boss;
|
||||||
|
|
||||||
static void StartEntity(ObjectInfo* obj)
|
static void StartEntity(ObjectInfo* obj)
|
||||||
|
@ -398,6 +400,18 @@ static void StartTrap(ObjectInfo* obj)
|
||||||
obj->collision = TrainCollision;
|
obj->collision = TrainCollision;
|
||||||
obj->SetupHitEffect(true);
|
obj->SetupHitEffect(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
obj = &Objects[ID_ELECTRIC_CLEANER];
|
||||||
|
if (obj->loaded)
|
||||||
|
{
|
||||||
|
obj->initialise = InitialiseElectricCleaner;
|
||||||
|
obj->control = ElectricCleanerControl;
|
||||||
|
obj->collision = ElectricCleanerCollision;
|
||||||
|
obj->shadowType = ShadowMode::All;
|
||||||
|
obj->HitPoints = NOT_TARGETABLE;
|
||||||
|
obj->nonLot = 1;
|
||||||
|
obj->radius = 512;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void StartVehicles(ObjectInfo* obj)
|
static void StartVehicles(ObjectInfo* obj)
|
||||||
|
|
|
@ -192,7 +192,7 @@ namespace TEN::Entities::Generic
|
||||||
if (pushable->hasFloorCeiling)
|
if (pushable->hasFloorCeiling)
|
||||||
{
|
{
|
||||||
//AlterFloorHeight(item, -((item->triggerFlags - 64) * 256));
|
//AlterFloorHeight(item, -((item->triggerFlags - 64) * 256));
|
||||||
AdjustStopperFlag(item, item->ItemFlags[0] + 0x8000, false);
|
AdjustStopperFlag(item, item->ItemFlags[0] + ANGLE(180));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -390,7 +390,7 @@ namespace TEN::Entities::Generic
|
||||||
if (pushable->hasFloorCeiling)
|
if (pushable->hasFloorCeiling)
|
||||||
{
|
{
|
||||||
//AlterFloorHeight(item, -((item->triggerFlags - 64) * 256));
|
//AlterFloorHeight(item, -((item->triggerFlags - 64) * 256));
|
||||||
AdjustStopperFlag(item, item->ItemFlags[0] + 0x8000, false);
|
AdjustStopperFlag(item, item->ItemFlags[0] + ANGLE(180));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -498,8 +498,7 @@ namespace TEN::Entities::Generic
|
||||||
|
|
||||||
if (pushable->hasFloorCeiling)
|
if (pushable->hasFloorCeiling)
|
||||||
{
|
{
|
||||||
//AlterFloorHeight(item, ((item->triggerFlags - 64) * 256));
|
AdjustStopperFlag(pushableItem, pushableItem->ItemFlags[0]);
|
||||||
AdjustStopperFlag(pushableItem, pushableItem->ItemFlags[0], false);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
|
@ -357,6 +357,7 @@ enum GAME_OBJECT_ID : short
|
||||||
ID_TRAIN,
|
ID_TRAIN,
|
||||||
ID_EXPLOSION,
|
ID_EXPLOSION,
|
||||||
ID_DAMOCLES_SWORD,
|
ID_DAMOCLES_SWORD,
|
||||||
|
ID_ELECTRIC_CLEANER,
|
||||||
|
|
||||||
ID_PUZZLE_ITEM1 = 500,
|
ID_PUZZLE_ITEM1 = 500,
|
||||||
ID_PUZZLE_ITEM2,
|
ID_PUZZLE_ITEM2,
|
||||||
|
|
|
@ -364,6 +364,7 @@ The following constants are inside ObjID.
|
||||||
TRAIN
|
TRAIN
|
||||||
EXPLOSION
|
EXPLOSION
|
||||||
DAMOCLES_SWORD
|
DAMOCLES_SWORD
|
||||||
|
ELECTRIC_CLEANER
|
||||||
PUZZLE_ITEM1
|
PUZZLE_ITEM1
|
||||||
PUZZLE_ITEM2
|
PUZZLE_ITEM2
|
||||||
PUZZLE_ITEM3
|
PUZZLE_ITEM3
|
||||||
|
@ -1531,6 +1532,7 @@ static const std::unordered_map<std::string, GAME_OBJECT_ID> kObjIDs {
|
||||||
{ "TRAIN", ID_TRAIN },
|
{ "TRAIN", ID_TRAIN },
|
||||||
{ "EXPLOSION", ID_EXPLOSION },
|
{ "EXPLOSION", ID_EXPLOSION },
|
||||||
{ "DAMOCLES_SWORD", ID_DAMOCLES_SWORD },
|
{ "DAMOCLES_SWORD", ID_DAMOCLES_SWORD },
|
||||||
|
{ "ELECTRIC_CLEANER", ID_ELECTRIC_CLEANER },
|
||||||
{ "PUZZLE_ITEM1", ID_PUZZLE_ITEM1 },
|
{ "PUZZLE_ITEM1", ID_PUZZLE_ITEM1 },
|
||||||
{ "PUZZLE_ITEM2", ID_PUZZLE_ITEM2 },
|
{ "PUZZLE_ITEM2", ID_PUZZLE_ITEM2 },
|
||||||
{ "PUZZLE_ITEM3", ID_PUZZLE_ITEM3 },
|
{ "PUZZLE_ITEM3", ID_PUZZLE_ITEM3 },
|
||||||
|
|
|
@ -169,6 +169,7 @@ CALL gen.bat</Command>
|
||||||
<ClInclude Include="Game\Hud\PickupSummary.h" />
|
<ClInclude Include="Game\Hud\PickupSummary.h" />
|
||||||
<ClInclude Include="Math\Objects\AxisAngle.h" />
|
<ClInclude Include="Math\Objects\AxisAngle.h" />
|
||||||
<ClInclude Include="Objects\TR1\Trap\DamoclesSword.h" />
|
<ClInclude Include="Objects\TR1\Trap\DamoclesSword.h" />
|
||||||
|
<ClInclude Include="Objects\TR3\Trap\ElectricCleaner.h" />
|
||||||
<ClInclude Include="Objects\TR3\Entity\Compsognathus.h" />
|
<ClInclude Include="Objects\TR3\Entity\Compsognathus.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\Lizard.h" />
|
||||||
|
@ -648,6 +649,7 @@ CALL gen.bat</Command>
|
||||||
<ClCompile Include="Game\Hud\PickupSummary.cpp" />
|
<ClCompile Include="Game\Hud\PickupSummary.cpp" />
|
||||||
<ClCompile Include="Math\Objects\AxisAngle.cpp" />
|
<ClCompile Include="Math\Objects\AxisAngle.cpp" />
|
||||||
<ClCompile Include="Objects\TR1\Trap\DamoclesSword.cpp" />
|
<ClCompile Include="Objects\TR1\Trap\DamoclesSword.cpp" />
|
||||||
|
<ClCompile Include="Objects\TR3\Trap\ElectricCleaner.cpp" />
|
||||||
<ClCompile Include="Objects\TR3\Entity\Compsognathus.cpp" />
|
<ClCompile Include="Objects\TR3\Entity\Compsognathus.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\Lizard.cpp" />
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue