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 +