TombEngine/TombEngine/Game/Lara/lara_flare.cpp
Lwmte f4763bd4a2
Customize global hardcoded parameters (#1495)
* Initial commit

* Update CHANGELOG.md

* Tint flare smoke

* Expose GetCustomizations

* Added lensflare and flicker customization options for flare

* Update LensFlare.cpp

* Remove unnecessary code

* Update lara_flare.cpp

* Massive refactor to merge animations, settings and customizations

* Add HUD customization options

* Customize weapons

* Fixed flare, renamed recoil to interval, fixed lensflare default

* Occlude flare lensflares

* Update Settings.cpp

* Use alternate damage for lasersight mode

* Added hair cust

* Fix comment

* Fix another comment

* Fix link

* Fix placeholder table names

* Reorganize types

* Add missing initializers for hair settings

* Added physics cust

* Clarify description

* Update settings.lua

* Update CHANGELOG.md

* Add gun smoke, gun shells and ammo pickup counts

* Fix naming ambiguity

* Remove missing features from documentation

* Fix comment

* Fix parameter name, change default settings file

* Fixed pitch black

* Rollback DoDistanceFogForVertex

* Add camera cust

* Change binocular/lasersight toggle to color

* Update lara_basic.cpp

* Add time and statistics classes and script API for it

* Fix comment

* Use DoDamage on Lara helpers to register with statistics

* Update Time.cpp

* Fix documentation

* Fix default flare timeout

* Update Settings.lua

* Add flare muzzle offset customization

* Remove young Lara limitations

* Fix lasersight color

* Push full settings.lua

* Update RendererCompatibility.cpp

* Allow to customize root meshes, decopypaste hair and joint init code

* Added sol Time operator overloads

* Some changes to docs, add meaningful error for unknown fields

* Use existing new index template, add gunflash color settings, add shotgun muzzle

* Remove excessive usage of GetSettings()

* Cleanups

* Update Settings.lua

* Clarify parameter name

* Fix InitializeWeaponInfo

* PR review code tidying

* Fix bad merge

* Update FlowHandler.cpp

* Remove tabs for LDoc comments

* Use different comment style to preserve formatting

* Update lara_fire.cpp

* Some cleanups

* Fixed GetTimeUnits

* Fix typo

* Update Time.cpp

---------

Co-authored-by: Sezz <sezzary@outlook.com>
2024-12-15 18:24:49 +02:00

518 lines
13 KiB
C++

#include "framework.h"
#include "Game/Lara/lara_flare.h"
#include "Game/animation.h"
#include "Game/camera.h"
#include "Game/collision/collide_item.h"
#include "Game/collision/Point.h"
#include "Game/control/control.h"
#include "Game/effects/effects.h"
#include "Game/effects/chaffFX.h"
#include "Game/items.h"
#include "Game/Lara/lara.h"
#include "Game/Lara/lara_fire.h"
#include "Game/Lara/lara_helpers.h"
#include "Game/Lara/lara_tests.h"
#include "Game/Setup.h"
#include "Math/Math.h"
#include "Objects/Effects/LensFlare.h"
#include "Scripting/Include/Flow/ScriptInterfaceFlowHandler.h"
#include "Sound/sound.h"
#include "Specific/clock.h"
#include "Specific/level.h"
using namespace TEN::Collision::Point;
using namespace TEN::Entities::Effects;
using namespace TEN::Math;
constexpr auto FLARE_DEATH_DELAY = 1.0f * FPS;
void FlareControl(short itemNumber)
{
auto& flareItem = g_Level.Items[itemNumber];
if (TestEnvironment(ENV_FLAG_SWAMP, &flareItem))
{
KillItem(itemNumber);
return;
}
if (flareItem.Animation.Velocity.y)
{
flareItem.Pose.Orientation.x -= ANGLE(5.0f);
flareItem.Pose.Orientation.z += ANGLE(5.0f);
}
else
{
flareItem.Pose.Orientation.x = 0;
flareItem.Pose.Orientation.z = 0;
}
auto vel = Vector3i(
flareItem.Animation.Velocity.z * phd_sin(flareItem.Pose.Orientation.y),
flareItem.Animation.Velocity.y,
flareItem.Animation.Velocity.z * phd_cos(flareItem.Pose.Orientation.y));
auto prevPos = flareItem.Pose.Position;
flareItem.Pose.Position += Vector3i(vel.x, 0, vel.z);
if (TestEnvironment(ENV_FLAG_WATER, &flareItem) ||
TestEnvironment(ENV_FLAG_SWAMP, &flareItem))
{
flareItem.Animation.Velocity.y += (5 - flareItem.Animation.Velocity.y) / 2;
flareItem.Animation.Velocity.z += (5 - flareItem.Animation.Velocity.z) / 2;
}
else
{
flareItem.Animation.Velocity.y += g_GameFlow->GetSettings()->Physics.Gravity;
}
flareItem.Pose.Position.y += flareItem.Animation.Velocity.y;
DoProjectileDynamics(itemNumber, prevPos.x, prevPos.y, prevPos.z, vel.x, vel.y, vel.z);
const auto& settings = g_GameFlow->GetSettings()->Flare;
int& life = flareItem.Data;
life &= 0x7FFF;
if (life >= (settings.Timeout * FPS))
{
if (flareItem.Animation.Velocity.y == 0.0f &&
flareItem.Animation.Velocity.z == 0.0f)
{
KillItem(itemNumber);
return;
}
}
else
{
life++;
}
auto offset = settings.Offset.ToVector3i() + Vector3i(-6, 6, 0);
auto lightPos = GetJointPosition(flareItem, 0, offset);
if (DoFlareLight(flareItem, lightPos, life))
{
TriggerChaffEffects(flareItem, life);
life |= 0x8000;
}
}
void ReadyFlare(ItemInfo& laraItem)
{
auto& player = *GetLaraInfo(&laraItem);
player.Control.HandStatus = HandStatus::Free;
player.LeftArm.Orientation =
player.RightArm.Orientation = EulerAngles::Identity;
player.LeftArm.Locked =
player.RightArm.Locked = false;
player.TargetEntity = nullptr;
}
void UndrawFlareMeshes(ItemInfo& laraItem)
{
laraItem.Model.MeshIndex[LM_LHAND] = laraItem.Model.BaseMesh + LM_LHAND;
}
void DrawFlareMeshes(ItemInfo& laraItem)
{
laraItem.Model.MeshIndex[LM_LHAND] = Objects[ID_FLARE_ANIM].meshIndex + LM_LHAND;
}
void UndrawFlare(ItemInfo& laraItem)
{
auto& player = *GetLaraInfo(&laraItem);
int flareFrame = player.Flare.Frame;
int armFrame = player.LeftArm.FrameNumber;
player.Flare.ControlLeft = true;
if (laraItem.Animation.TargetState == LS_IDLE &&
player.Context.Vehicle == NO_VALUE)
{
if (laraItem.Animation.AnimNumber == LA_STAND_IDLE)
{
laraItem.Animation.AnimNumber = LA_DISCARD_FLARE;
flareFrame = armFrame + GetAnimData(laraItem).frameBase;
laraItem.Animation.FrameNumber = flareFrame;
player.Flare.Frame = flareFrame;
}
if (laraItem.Animation.AnimNumber == LA_DISCARD_FLARE)
{
player.Flare.ControlLeft = false;
if (flareFrame >= (GetAnimData(laraItem).frameBase + 31)) // 31 = Last frame.
{
player.Control.Weapon.RequestGunType = player.Control.Weapon.LastGunType;
player.Control.Weapon.GunType = player.Control.Weapon.LastGunType;
player.Control.HandStatus = HandStatus::Free;
InitializeNewWeapon(laraItem);
player.TargetEntity = nullptr;
player.LeftArm.Locked =
player.RightArm.Locked = false;
SetAnimation(laraItem, LA_STAND_IDLE);
player.Flare.Frame = GetAnimData(laraItem).frameBase;
return;
}
player.Flare.Frame++;
}
}
else if (laraItem.Animation.AnimNumber == LA_DISCARD_FLARE)
{
SetAnimation(&laraItem, LA_STAND_IDLE);
}
if (armFrame >= 33 && armFrame < 72)
{
armFrame = 2;
DoFlareInHand(laraItem, player.Flare.Life);
}
else if (!armFrame)
{
armFrame = 1;
DoFlareInHand(laraItem, player.Flare.Life);
}
else if (armFrame >= 72 && armFrame < 95)
{
armFrame++;
if (armFrame == 94)
{
armFrame = 1;
DoFlareInHand(laraItem, player.Flare.Life);
}
}
else if (armFrame >= 1 && armFrame < 33)
{
armFrame++;
if (armFrame == 21)
{
CreateFlare(laraItem, ID_FLARE_ITEM, true);
UndrawFlareMeshes(laraItem);
player.Flare.Life = 0;
}
else if (armFrame == 33)
{
armFrame = 0;
player.Control.Weapon.RequestGunType = player.Control.Weapon.LastGunType;
player.Control.Weapon.GunType = player.Control.Weapon.LastGunType;
player.Control.HandStatus = HandStatus::Free;
InitializeNewWeapon(laraItem);
player.TargetEntity = nullptr;
player.LeftArm.Locked =
player.RightArm.Locked = false;
player.Flare.ControlLeft = false;
player.Flare.Frame = 0;
}
else if (armFrame < 21)
{
DoFlareInHand(laraItem, player.Flare.Life);
}
}
else if (armFrame >= 95 && armFrame < 110)
{
armFrame++;
if (armFrame == 110)
{
armFrame = 1;
DoFlareInHand(laraItem, player.Flare.Life);
}
}
player.LeftArm.FrameNumber = armFrame;
SetFlareArm(laraItem, player.LeftArm.FrameNumber);
}
void DrawFlare(ItemInfo& laraItem)
{
auto& player = *GetLaraInfo(&laraItem);
if (laraItem.Animation.ActiveState == LS_PICKUP_FLARE ||
laraItem.Animation.ActiveState == LS_PICKUP)
{
DoFlareInHand(laraItem, player.Flare.Life);
player.Flare.ControlLeft = false;
player.LeftArm.FrameNumber = 93;
SetFlareArm(laraItem, 93);
}
else
{
int armFrame = player.LeftArm.FrameNumber + 1;
player.Flare.ControlLeft = true;
if (armFrame < 33 || armFrame > 94)
{
armFrame = 33;
}
else if (armFrame == 46)
{
DrawFlareMeshes(laraItem);
}
else if (armFrame >= 72 && armFrame <= 93)
{
if (armFrame == 72)
{
player.Flare.Life = 1;
SoundEffect(
SFX_TR4_FLARE_IGNITE_DRY,
&laraItem.Pose,
TestEnvironment(ENV_FLAG_WATER, &laraItem) ? SoundEnvironment::Underwater : SoundEnvironment::Land);
}
DoFlareInHand(laraItem, player.Flare.Life);
}
else
{
if (armFrame == 94)
{
ReadyFlare(laraItem);
armFrame = 0;
DoFlareInHand(laraItem, player.Flare.Life);
}
}
player.LeftArm.FrameNumber = armFrame;
SetFlareArm(laraItem, armFrame);
}
}
void SetFlareArm(ItemInfo& laraItem, int armFrame)
{
auto& player = *GetLaraInfo(&laraItem);
int flareAnimNumber = Objects[ID_FLARE_ANIM].animIndex;
if (armFrame >= 95)
{
flareAnimNumber += 4;
}
else if (armFrame >= 72)
{
flareAnimNumber += 3;
}
else if (armFrame >= 33)
{
flareAnimNumber += 2;
}
else if (armFrame >= 1)
{
flareAnimNumber += 1;
}
player.LeftArm.AnimNumber = flareAnimNumber;
player.LeftArm.FrameBase = GetAnimData(flareAnimNumber).FramePtr;
}
void CreateFlare(ItemInfo& laraItem, GAME_OBJECT_ID objectID, bool isThrown)
{
const auto& lara = *GetLaraInfo(&laraItem);
auto itemNumber = CreateItem();
if (itemNumber == NO_VALUE)
return;
auto& flareItem = g_Level.Items[itemNumber];
flareItem.ObjectNumber = objectID;
flareItem.RoomNumber = laraItem.RoomNumber;
auto pos = GetJointPosition(&laraItem, LM_LHAND, Vector3i(-16, 32, 42));
if (objectID == ID_BURNING_TORCH_ITEM && laraItem.Animation.ActiveState == LS_CROUCH_IDLE)
pos.y -= CLICK(0.5f);
flareItem.Pose.Position = pos;
int floorHeight = GetPointCollision(pos, laraItem.RoomNumber).GetFloorHeight();
auto isCollided = !GetCollidedObjects(flareItem, true, true).IsEmpty();
bool hasLanded = false;
if (floorHeight < pos.y || isCollided)
{
hasLanded = true;
flareItem.Pose.Position.x = laraItem.Pose.Position.x + 320 * phd_sin(flareItem.Pose.Orientation.y);
flareItem.Pose.Position.z = laraItem.Pose.Position.z + 320 * phd_cos(flareItem.Pose.Orientation.y);
flareItem.Pose.Orientation.y = laraItem.Pose.Orientation.y + ANGLE(180.0f);
flareItem.RoomNumber = laraItem.RoomNumber;
}
else
{
if (isThrown)
{
flareItem.Pose.Orientation.y = laraItem.Pose.Orientation.y;
}
else
{
flareItem.Pose.Orientation.y = laraItem.Pose.Orientation.y - ANGLE(45.0f);
}
flareItem.RoomNumber = laraItem.RoomNumber;
}
InitializeItem(itemNumber);
flareItem.Pose.Orientation.x = 0;
flareItem.Pose.Orientation.z = 0;
flareItem.Model.Color = Vector4::One;
if (isThrown)
{
flareItem.Animation.Velocity.z = laraItem.Animation.Velocity.z + 50;
flareItem.Animation.Velocity.y = laraItem.Animation.Velocity.y - 50;
}
else
{
flareItem.Animation.Velocity.z = laraItem.Animation.Velocity.z + 10;
flareItem.Animation.Velocity.y = laraItem.Animation.Velocity.y + 50;
}
if (hasLanded)
flareItem.Animation.Velocity.z /= 2;
if (objectID == ID_FLARE_ITEM)
{
flareItem.Data = (int)0;
int& life = flareItem.Data;
if (DoFlareLight(flareItem, flareItem.Pose.Position, lara.Flare.Life))
{
life = lara.Flare.Life | 0x8000;
}
else
{
life = lara.Flare.Life & 0x7FFF;
}
}
else
{
flareItem.ItemFlags[3] = lara.Torch.IsLit;
}
AddActiveItem(itemNumber);
flareItem.Status = ITEM_ACTIVE;
}
void DoFlareInHand(ItemInfo& laraItem, int flareLife)
{
auto& player = *GetLaraInfo(&laraItem);
const auto& settings = g_GameFlow->GetSettings()->Flare;
auto offset = settings.Offset.ToVector3i() + Vector3i(11, 32, 0);
auto lightPos = GetJointPosition(&laraItem, LM_LHAND, offset);
if (DoFlareLight(laraItem, lightPos, flareLife))
TriggerChaffEffects(player.Control.Look.IsUsingBinoculars ? 0 : flareLife);
if (player.Flare.Life >= ((settings.Timeout * FPS) - (FLARE_DEATH_DELAY / 2)))
{
// Prevent player from intercepting reach/jump states with flare throws.
if (laraItem.Animation.IsAirborne ||
laraItem.Animation.TargetState == LS_JUMP_PREPARE ||
laraItem.Animation.TargetState == LS_JUMP_FORWARD)
{
return;
}
if (player.Control.HandStatus == HandStatus::Free)
player.Control.HandStatus = HandStatus::WeaponUndraw;
}
else if (player.Flare.Life != 0)
{
player.Flare.Life++;
}
}
bool DoFlareLight(ItemInfo& item, const Vector3i& pos, int flareLife)
{
constexpr auto START_DELAY = 0.25f * FPS;
constexpr auto END_DELAY = 3.0f * FPS;
constexpr auto INTENSITY_MAX = 1.0f;
constexpr auto INTENSITY_MIN = 0.9f;
constexpr auto CHAFF_SPAWN_CHANCE = 4 / 10.0f;
constexpr auto CHAFF_SPAWN_ENDING_CHANCE = CHAFF_SPAWN_CHANCE / 2;
constexpr auto CHAFF_SPAWN_DYING_CHANCE = CHAFF_SPAWN_CHANCE / 4;
constexpr auto LIGHT_SPHERE_RADIUS = BLOCK(1 / 16.0f);
constexpr auto LIGHT_POS_OFFSET = Vector3(0.0f, -BLOCK(1 / 8.0f), 0.0f);
auto& settings = g_GameFlow->GetSettings()->Flare;
float flareRange = settings.Range * BLOCK(0.25f);
auto flareColor = Vector3(settings.Color);
int flareTimeout = settings.Timeout * FPS;
if (flareLife >= flareTimeout || flareLife == 0)
return false;
// Determine flare progress.
bool isStarting = (flareLife <= START_DELAY);
bool isEnding = (flareLife > (flareTimeout - END_DELAY));
bool isDying = (flareLife > (flareTimeout - FLARE_DEATH_DELAY));
bool spawnChaff = false;
float mult = 1.0f;
// Define light multiplier and chaff spawn status.
if (isStarting)
{
mult -= 0.5f * (1.0f - ((float)flareLife / START_DELAY));
}
else if (isDying)
{
mult = (flareTimeout - (float)flareLife) / FLARE_DEATH_DELAY;
spawnChaff = Random::TestProbability(CHAFF_SPAWN_DYING_CHANCE);
}
else if (isEnding)
{
mult = Random::GenerateFloat(0.8f, 1.0f);
spawnChaff = Random::TestProbability(CHAFF_SPAWN_ENDING_CHANCE);
}
else
{
spawnChaff = Random::TestProbability(CHAFF_SPAWN_CHANCE);
}
// Determine light position.
auto lightPos = pos.ToVector3();
float intensity = 1.0f;
// Handle flicker effect if specified.
if (settings.Flicker)
{
auto sphere = BoundingSphere(pos.ToVector3() + LIGHT_POS_OFFSET, LIGHT_SPHERE_RADIUS);
lightPos = Random::GeneratePointInSphere(sphere);
intensity = Random::GenerateFloat(INTENSITY_MIN, INTENSITY_MAX);
}
// Calculate color.
float falloff = intensity * mult * flareRange;
auto color = (flareColor * intensity * std::clamp(mult, 0.0f, 1.0f));
// Spawn dynamic light.
TriggerDynamicPointLight(lightPos, Color(color), falloff, false);
// Spawn lensflare if brightness is not 0.
if (settings.LensflareBrightness > EPSILON)
{
if (item.ObjectNumber == GAME_OBJECT_ID::ID_FLARE_ITEM)
{
float currentIntensity = (float)item.ItemFlags[0] / LENSFLARE_ITEMFLAG_BRIGHTNESS_SCALE;
SetupLensFlare(pos.ToVector3(), item.RoomNumber, Color(color) * settings.LensflareBrightness, &currentIntensity, 0);
item.ItemFlags[0] = short(currentIntensity * LENSFLARE_ITEMFLAG_BRIGHTNESS_SCALE);
}
else
{
SetupLensFlare(pos.ToVector3(), item.RoomNumber, Color(color) * settings.LensflareBrightness, nullptr, 0);
}
}
// Return chaff spawn status.
return ((isDying || isEnding) ? spawnChaff : true);
}