diff --git a/TombEngine/Game/effects/effects.cpp b/TombEngine/Game/effects/effects.cpp
index bc6a66759..45d26eff9 100644
--- a/TombEngine/Game/effects/effects.cpp
+++ b/TombEngine/Game/effects/effects.cpp
@@ -1033,7 +1033,8 @@ void DoLotsOfBlood(int x, int y, int z, int speed, short direction, short roomNu
{
for (int i = 0; i < count; i++)
{
- DoBloodSplat(x + 256 - (GetRandomControl() * 512 / 0x8000),
+ DoBloodSplat(
+ x + 256 - (GetRandomControl() * 512 / 0x8000),
y + 256 - (GetRandomControl() * 512 / 0x8000),
z + 256 - (GetRandomControl() * 512 / 0x8000),
speed, direction, roomNumber);
diff --git a/TombEngine/Math/Containers/GameBoundingBox.cpp b/TombEngine/Math/Containers/GameBoundingBox.cpp
index 0b1fe99ac..d66aae665 100644
--- a/TombEngine/Math/Containers/GameBoundingBox.cpp
+++ b/TombEngine/Math/Containers/GameBoundingBox.cpp
@@ -54,19 +54,19 @@
// NOTE: Previously phd_RotBoundingBoxNoPersp().
void GameBoundingBox::RotateNoPersp(const EulerAngles& orient, const GameBoundingBox& bounds)
{
- auto world = orient.ToRotationMatrix();
- auto bMin = Vector3(bounds.X1, bounds.Y1, bounds.Z1);
- auto bMax = Vector3(bounds.X2, bounds.Y2, bounds.Z2);
+ auto rotMatrix = orient.ToRotationMatrix();
+ auto boxMin = Vector3(bounds.X1, bounds.Y1, bounds.Z1);
+ auto boxMax = Vector3(bounds.X2, bounds.Y2, bounds.Z2);
- bMin = Vector3::Transform(bMin, world);
- bMax = Vector3::Transform(bMax, world);
+ boxMin = Vector3::Transform(boxMin, rotMatrix);
+ boxMax = Vector3::Transform(boxMax, rotMatrix);
- this->X1 = (int)round(bMin.x);
- this->X2 = (int)round(bMax.x);
- this->Y1 = (int)round(bMin.y);
- this->Y2 = (int)round(bMax.y);
- this->Z1 = (int)round(bMin.z);
- this->Z2 = (int)round(bMax.z);
+ this->X1 = (int)round(boxMin.x);
+ this->X2 = (int)round(boxMax.x);
+ this->Y1 = (int)round(boxMin.y);
+ this->Y2 = (int)round(boxMax.y);
+ this->Z1 = (int)round(boxMin.z);
+ this->Z2 = (int)round(boxMax.z);
}
BoundingOrientedBox GameBoundingBox::ToBoundingOrientedBox(const Pose& pose) const
diff --git a/TombEngine/Math/Random.cpp b/TombEngine/Math/Random.cpp
index 18b170310..55937c57e 100644
--- a/TombEngine/Math/Random.cpp
+++ b/TombEngine/Math/Random.cpp
@@ -50,6 +50,18 @@ namespace TEN::Math::Random
return (vector * length);
}
+ Vector3 GenerateVector3InBox(const BoundingOrientedBox& box)
+ {
+ auto rotMatrix = Matrix::CreateFromQuaternion(box.Orientation);
+ auto vector = Vector3(
+ GenerateFloat(-box.Extents.x, box.Extents.x),
+ GenerateFloat(-box.Extents.y, box.Extents.y),
+ GenerateFloat(-box.Extents.z, box.Extents.z)
+ );
+
+ return Vector3::Transform(vector, rotMatrix);
+ }
+
bool TestProbability(float probability)
{
probability = std::clamp(probability, 0.0f, 1.0f);
diff --git a/TombEngine/Math/Random.h b/TombEngine/Math/Random.h
index 285dd1705..e831009b2 100644
--- a/TombEngine/Math/Random.h
+++ b/TombEngine/Math/Random.h
@@ -9,6 +9,7 @@ namespace TEN::Math::Random
Vector2 GenerateVector2(float length = 1.0f);
Vector3 GenerateVector3(float length = 1.0f);
Vector3 GenerateVector3InCone(const Vector3& direction, float semiangleInDeg, float length = 1.0f);
+ Vector3 GenerateVector3InBox(const BoundingOrientedBox& box);
bool TestProbability(float probability);
}
diff --git a/TombEngine/Objects/TR1/Trap/DamoclesSword.cpp b/TombEngine/Objects/TR1/Trap/DamoclesSword.cpp
new file mode 100644
index 000000000..d9f4b87ff
--- /dev/null
+++ b/TombEngine/Objects/TR1/Trap/DamoclesSword.cpp
@@ -0,0 +1,138 @@
+#include "framework.h"
+#include "Objects/TR1/Trap/DamoclesSword.h"
+
+#include "Game/camera.h"
+#include "Game/collision/collide_item.h"
+#include "Game/collision/collide_room.h"
+#include "Game/effects/effects.h"
+#include "Game/Lara/lara.h"
+#include "Math/Math.h"
+#include "Specific/level.h"
+#include "Specific/setup.h"
+
+using namespace TEN::Math;
+
+namespace TEN::Entities::Traps::TR1
+{
+ constexpr auto DAMOCLES_SWORD_DAMAGE = 100;
+
+ constexpr auto DAMOCLES_SWORD_VELOCITY_MIN = BLOCK(1.0f / 20);
+ constexpr auto DAMOCLES_SWORD_VELOCITY_MAX = BLOCK(1.0f / 8);
+
+ constexpr auto DAMOCLES_SWORD_IMPALE_DEPTH = -BLOCK(1.0f / 8);
+ constexpr auto DAMOCLES_SWORD_ACTIVATE_RANGE_2D = BLOCK(3.0f / 2);
+ constexpr auto DAMOCLES_SWORD_ACTIVATE_RANGE_VERTICAL = BLOCK(3);
+
+ const auto DAMOCLES_SWORD_TURN_RATE_MAX = ANGLE(5.0f);
+
+ void SetupDamoclesSword(ObjectInfo* object)
+ {
+ object->initialise = InitialiseDamoclesSword;
+ object->control = ControlDamoclesSword;
+ object->collision = CollideDamoclesSword;
+ //object->shadowSize = UNIT_SHADOW;
+ object->savePosition = true;
+ object->saveAnim = true;
+ object->saveFlags = true;
+ }
+
+ void InitialiseDamoclesSword(short itemNumber)
+ {
+ auto& item = g_Level.Items[itemNumber];
+
+ item.Pose.Orientation.y = Random::GenerateAngle();
+ item.Animation.Velocity.y = DAMOCLES_SWORD_VELOCITY_MIN;
+ item.ItemFlags[0] = Random::GenerateAngle(-DAMOCLES_SWORD_TURN_RATE_MAX, DAMOCLES_SWORD_TURN_RATE_MAX); // NOTE: ItemFlags[0] stores random turn rate.
+ }
+
+ void ControlDamoclesSword(short itemNumber)
+ {
+ auto& item = g_Level.Items[itemNumber];
+ const auto& laraItem = *LaraItem;
+
+ // Fall toward player.
+ if (item.Animation.IsAirborne)
+ {
+ item.Pose.Orientation.y += item.ItemFlags[0]; // NOTE: ItemFlags[0] stores random turn rate.
+
+ // Calculate vertical velocity.
+ item.Animation.Velocity.y += (item.Animation.Velocity.y < DAMOCLES_SWORD_VELOCITY_MAX) ? GRAVITY : 1.0f;
+
+ // Translate sword.
+ short headingAngle = Geometry::GetOrientToPoint(item.Pose.Position.ToVector3(), laraItem.Pose.Position.ToVector3()).y;
+ TranslateItem(&item, headingAngle, item.ItemFlags[1], item.Animation.Velocity.y); // NOTE: ItemFlags[1] stores calculated forward velocity.
+
+ int vPos = item.Pose.Position.y;
+ auto pointColl = GetCollision(&item);
+
+ // Impale floor.
+ if ((pointColl.Position.Floor - vPos) <= DAMOCLES_SWORD_IMPALE_DEPTH)
+ {
+ SoundEffect(SFX_TR1_DAMOCLES_ROOM_SWORD, &item.Pose);
+ float distance = Vector3::Distance(item.Pose.Position.ToVector3(), Camera.pos.ToVector3());
+ Camera.bounce = -((BLOCK(7.0f / 2) - distance) * abs(item.Animation.Velocity.y)) / BLOCK(7.0f / 2);
+
+ item.Animation.IsAirborne = false;
+ item.Status = ItemStatus::ITEM_DEACTIVATED;
+ item.ItemFlags[0] = 0; // NOTE: ItemFlags[0] stores random turn rate.
+
+ RemoveActiveItem(itemNumber);
+ }
+
+ return;
+ }
+
+ // Scan for player.
+ if (item.Pose.Position.y < GetCollision(&item).Position.Floor)
+ {
+ item.Pose.Orientation.y += item.ItemFlags[0]; // NOTE: ItemFlags[0] stores random turn rate.
+
+ // Check vertical position to player.
+ if (item.Pose.Position.y >= laraItem.Pose.Position.y)
+ return;
+
+ // Check vertical distance.
+ float distanceV = laraItem.Pose.Position.y - item.Pose.Position.y;
+ if (distanceV > DAMOCLES_SWORD_ACTIVATE_RANGE_VERTICAL)
+ return;
+
+ // Check 2D distance.
+ float distance2D = Vector2i::Distance(
+ Vector2i(item.Pose.Position.x, item.Pose.Position.z),
+ Vector2i(laraItem.Pose.Position.x, laraItem.Pose.Position.z));
+ if (distance2D > DAMOCLES_SWORD_ACTIVATE_RANGE_2D)
+ return;
+
+ // Drop sword.
+ // TODO: Have 2D velocity also take vertical distance into account.
+ item.Animation.IsAirborne = true;
+ item.ItemFlags[1] = distance2D / 32; // NOTE: ItemFlags[1] stores calculated forward velocity.
+ return;
+ }
+ }
+
+ void CollideDamoclesSword(short itemNumber, ItemInfo* laraItem, CollisionInfo* coll)
+ {
+ auto& item = g_Level.Items[itemNumber];
+
+ if (!TestBoundsCollide(&item, laraItem, coll->Setup.Radius))
+ return;
+
+ if (coll->Setup.EnableObjectPush)
+ ItemPushItem(&item, laraItem, coll, false, true);
+
+ if (item.Animation.IsAirborne)
+ {
+ DoDamage(laraItem, DAMOCLES_SWORD_DAMAGE);
+
+ auto bloodBounds = GameBoundingBox(laraItem).ToBoundingOrientedBox(laraItem->Pose);
+ auto bloodPos = Vector3i(Random::GenerateVector3InBox(bloodBounds) + bloodBounds.Center);
+
+ auto orientToSword = Geometry::GetOrientToPoint(laraItem->Pose.Position.ToVector3(), item.Pose.Position.ToVector3());
+ short randAngleOffset = Random::GenerateAngle(ANGLE(-11.25f), ANGLE(11.25f));
+ short bloodHeadingAngle = orientToSword.y + randAngleOffset;
+
+ DoLotsOfBlood(bloodPos.x, bloodPos.y, bloodPos.z, laraItem->Animation.Velocity.z, bloodHeadingAngle, laraItem->RoomNumber, 20);
+ }
+ }
+}
diff --git a/TombEngine/Objects/TR1/Trap/DamoclesSword.h b/TombEngine/Objects/TR1/Trap/DamoclesSword.h
new file mode 100644
index 000000000..caa922952
--- /dev/null
+++ b/TombEngine/Objects/TR1/Trap/DamoclesSword.h
@@ -0,0 +1,14 @@
+#pragma once
+
+struct CollisionInfo;
+struct ItemInfo;
+struct ObjectInfo;
+
+namespace TEN::Entities::Traps::TR1
+{
+ void SetupDamoclesSword(ObjectInfo* object);
+ void InitialiseDamoclesSword(short itemNumber);
+
+ void ControlDamoclesSword(short itemNumber);
+ void CollideDamoclesSword(short itemNumber, ItemInfo* laraItem, CollisionInfo* coll);
+}
diff --git a/TombEngine/Objects/TR1/tr1_objects.cpp b/TombEngine/Objects/TR1/tr1_objects.cpp
index 179c038bd..1ccf0db15 100644
--- a/TombEngine/Objects/TR1/tr1_objects.cpp
+++ b/TombEngine/Objects/TR1/tr1_objects.cpp
@@ -20,7 +20,11 @@
#include "Objects/TR1/Entity/tr1_winged_mutant.h"
#include "Objects/Utils/object_helper.h"
+// Traps
+#include "Objects/TR1/Trap/DamoclesSword.h"
+
using namespace TEN::Entities::Creatures::TR1;
+using namespace TEN::Entities::Traps::TR1;
static void StartEntity(ObjectInfo* obj)
{
@@ -197,7 +201,6 @@ static void StartEntity(ObjectInfo* obj)
obj->SetBoneRotation(1, ROT_Y); // torso
obj->SetBoneRotation(2, ROT_Y); // head
}
-
}
static void StartObject(ObjectInfo* obj)
@@ -213,7 +216,9 @@ static void StartObject(ObjectInfo* obj)
static void StartTrap(ObjectInfo* obj)
{
-
+ obj = &Objects[ID_DAMOCLES_SWORD];
+ if (obj->loaded)
+ SetupDamoclesSword(obj);
}
static void StartProjectiles(ObjectInfo* obj)
diff --git a/TombEngine/Objects/TR2/Trap/tr2_springboard.cpp b/TombEngine/Objects/TR2/Trap/tr2_springboard.cpp
index 20110e407..6543b402f 100644
--- a/TombEngine/Objects/TR2/Trap/tr2_springboard.cpp
+++ b/TombEngine/Objects/TR2/Trap/tr2_springboard.cpp
@@ -8,27 +8,27 @@
void SpringBoardControl(short itemNumber)
{
- auto* item = &g_Level.Items[itemNumber];
+ auto& item = g_Level.Items[itemNumber];
- if (item->Animation.ActiveState == 0 && LaraItem->Pose.Position.y == item->Pose.Position.y &&
- LaraItem->Pose.Position.x / SECTOR(1) == item->Pose.Position.x / SECTOR(1) &&
- LaraItem->Pose.Position.z / SECTOR(1) == item->Pose.Position.z / SECTOR(1))
+ if (item.Animation.ActiveState == 0 && LaraItem->Pose.Position.y == item.Pose.Position.y &&
+ (LaraItem->Pose.Position.x / BLOCK(1)) == (item.Pose.Position.x / BLOCK(1)) &&
+ (LaraItem->Pose.Position.z / BLOCK(1)) == (item.Pose.Position.z / BLOCK(1)))
{
if (LaraItem->HitPoints <= 0)
return;
- if (LaraItem->Animation.ActiveState == LS_WALK_BACK || LaraItem->Animation.ActiveState == LS_RUN_BACK)
- LaraItem->Animation.Velocity.z = -LaraItem->Animation.Velocity.z;
+ if (LaraItem->Animation.ActiveState == LS_WALK_BACK ||
+ LaraItem->Animation.ActiveState == LS_RUN_BACK)
+ {
+ LaraItem->Animation.Velocity.z *= -1;
+ }
- LaraItem->Animation.AnimNumber = LA_FALL_START;
- LaraItem->Animation.FrameNumber = g_Level.Anims[LaraItem->Animation.AnimNumber].frameBase;
- LaraItem->Animation.ActiveState = LS_JUMP_FORWARD;
- LaraItem->Animation.TargetState = LS_JUMP_FORWARD;
+ SetAnimation(LaraItem, LA_FALL_START);
LaraItem->Animation.IsAirborne = true;
LaraItem->Animation.Velocity.y = -240.0f;
- item->Animation.TargetState = 1;
+ item.Animation.TargetState = 1;
}
- AnimateItem(item);
+ AnimateItem(&item);
}
diff --git a/TombEngine/Objects/TR5/Object/tr5_rollingball.cpp b/TombEngine/Objects/TR5/Object/tr5_rollingball.cpp
index 21eba0a31..f115ded19 100644
--- a/TombEngine/Objects/TR5/Object/tr5_rollingball.cpp
+++ b/TombEngine/Objects/TR5/Object/tr5_rollingball.cpp
@@ -64,18 +64,14 @@ void RollingBallControl(short itemNumber)
if (item->Pose.Position.y > dh)
{
- if (abs(item->Animation.Velocity.y) > 16)
+ if (abs(item->Animation.Velocity.y) > 16.0f)
{
- int distance = sqrt(
- pow(Camera.pos.x - item->Pose.Position.x, 2) +
- pow(Camera.pos.y - item->Pose.Position.y, 2) +
- pow(Camera.pos.z - item->Pose.Position.z, 2));
-
+ float distance = Vector3::Distance(item->Pose.Position.ToVector3(), Camera.pos.ToVector3());
if (distance < 16384)
{
if ((item->TriggerFlags & 1) != 1) // Flag 1 = silent.
{
- Camera.bounce = -(((16384 - distance) * abs(item->Animation.Velocity.y)) / 16384);
+ Camera.bounce = -((BLOCK(16) - distance) * abs(item->Animation.Velocity.y)) / BLOCK(16);
SoundEffect(SFX_TR4_BOULDER_FALL, &item->Pose);
}
}
diff --git a/TombEngine/Objects/game_object_ids.h b/TombEngine/Objects/game_object_ids.h
index cf69ddf54..b6f7e4673 100644
--- a/TombEngine/Objects/game_object_ids.h
+++ b/TombEngine/Objects/game_object_ids.h
@@ -357,6 +357,7 @@ enum GAME_OBJECT_ID : short
ID_PUSHABLE_OBJECT_CLIMBABLE10,
ID_TRAIN,
ID_EXPLOSION,
+ ID_DAMOCLES_SWORD,
ID_PUZZLE_ITEM1 = 500,
ID_PUZZLE_ITEM2,
diff --git a/TombEngine/TombEngine.vcxproj b/TombEngine/TombEngine.vcxproj
index bf7ebb046..981b7e7fb 100644
--- a/TombEngine/TombEngine.vcxproj
+++ b/TombEngine/TombEngine.vcxproj
@@ -166,6 +166,7 @@ CALL gen.bat
+
@@ -628,6 +629,7 @@ CALL gen.bat
+