Damocles sword backport (#846)

* Port Damocles sword

* Simplify; play sound

* Simplify

* Add GenerateVector3InBox() function; update Damocles sword blood generation

* Rename local variable

* Organise include

* Fix translation?

* Use GetShortestAngle()

* Use bools

* Add comment

* Don't use item->Floor

* Fix wrong sign

* Rename function

* Fix damocles sword translation

* Update range check

* Cleanup

* Fix signs

* Fix translation

* Demagic impale depth; increase blood

* Shake camera upon impact

* Update comment

* Cleanup

* Use ItemFlags

* Cleanup

* Update comment

* Simplify

* Optimise

Co-authored-by: Sezz <sezzary@outlook.com>
This commit is contained in:
Kuba Bilinski 2022-11-15 10:12:24 +00:00 committed by GitHub
parent db6df48184
commit 62655f51a3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 203 additions and 33 deletions

View file

@ -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);

View file

@ -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

View file

@ -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);

View file

@ -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);
}

View file

@ -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);
}
}
}

View file

@ -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);
}

View file

@ -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)

View file

@ -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);
}

View file

@ -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);
}
}

View file

@ -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,

View file

@ -166,6 +166,7 @@ CALL gen.bat</Command>
<ClInclude Include="Game\debug\debug.h" />
<ClInclude Include="Game\effects\lightning.h" />
<ClInclude Include="Game\GuiObjects.h" />
<ClInclude Include="Objects\TR1\Trap\DamoclesSword.h" />
<ClInclude Include="Scripting\Internal\TEN\Objects\Lara\LaraObject.h" />
<ClInclude Include="Specific\Input\InputAction.h" />
<ClInclude Include="Game\itemdata\door_data.h" />
@ -628,6 +629,7 @@ CALL gen.bat</Command>
<ClCompile Include="Game\collision\floordata.cpp" />
<ClCompile Include="Game\effects\footprint.cpp" />
<ClCompile Include="Game\GuiObjects.cpp" />
<ClCompile Include="Objects\TR1\Trap\DamoclesSword.cpp" />
<ClCompile Include="Scripting\Internal\TEN\Objects\Lara\LaraObject.cpp" />
<ClCompile Include="Specific\Input\InputAction.cpp" />
<ClCompile Include="Game\itemdata\itemdata.cpp" />