Merge branch 'develop' into DisplayString_Scale

This commit is contained in:
Sezz 2023-10-30 23:48:22 +11:00
commit fad4cb0255
159 changed files with 6017 additions and 3337 deletions

View file

@ -1,4 +1,4 @@
Version 1.1.1 Version 1.2
============== ==============
* Fix burning torch not working properly if there are more than 256 objects in a level. * Fix burning torch not working properly if there are more than 256 objects in a level.
@ -13,8 +13,15 @@ Version 1.1.1
* Fix TR2 doberman crashing the game when killed by explosive weapons. * Fix TR2 doberman crashing the game when killed by explosive weapons.
* Fix volume change in settings not affecting voice track. * Fix volume change in settings not affecting voice track.
* Fix several lighting bugs. * Fix several lighting bugs.
* Fix double drawing additive faces.
* Fix savegame count not properly increasing. * Fix savegame count not properly increasing.
* Fix regeneration of non-ammo pickups with OCB 128. * Fix regeneration of non-ammo pickups with OCB 128.
* Overhaul Pushable blocks:
- Separate climbable and normal pushable blocks slots.
- Add new pushable OCB to manipulate pushable block properties.
- Add new Lara animations for pushing over the block off the edge (both TR1-3 and TR4-5 versions).
- Fix pushables not working with raising block.
- Fix miscellaneous pushable bugs.
* Overhaul look-around feature: * Overhaul look-around feature:
- Allow for more consistent and wider viewing angles while crawling, crouching, and hanging. - Allow for more consistent and wider viewing angles while crawling, crouching, and hanging.
- Improve look camera movement and control. - Improve look camera movement and control.
@ -28,18 +35,23 @@ Version 1.1.1
- OCB > 0: When the player steps on the platform, it will crumble after the number of frames set in the OCB. - OCB > 0: When the player steps on the platform, it will crumble after the number of frames set in the OCB.
- A positive value results in activation via player collision. - A positive value results in activation via player collision.
- A negative value requires a trigger to activate. - A negative value requires a trigger to activate.
* Add basic mouse input handling. Allows for binding of mouse inputs in control settings.
* Add settings for Mouse Sensitivity and Mouse Smoothing (not used in-game yet).
Lua API changes: Lua API changes:
* Add DisplaySprite object. * Add DisplaySprite object.
* Add Flow::EnableLoadSave() function to disable savegames. * Add Flow:EnableLoadSave() function to disable savegames.
* Add Flow::EnablePointFilter() function to disable bilinear filtering. * Add Flow:EnablePointFilter() function to disable bilinear filtering.
* Add Lara::GetAmmoType() function to read the ammo that Lara is using. * Add InputGetCursorDisplayPosition() function to get the position of the cursor.
* Add Lara::GetControlLock() and Lara::SetControlLock() functions to handle controls locking. * Add Lara:GetAmmoType() function to read the ammo that Lara is using.
* Add Lara:GetControlLock() and Lara:SetControlLock() functions to handle controls locking.
* Add Logic.HandleEvent() function to call node events. * Add Logic.HandleEvent() function to call node events.
* Add functions to load, save, delete and check existence of savegames. * Add functions to load, save, delete and check existence of savegames.
* Add DisplayStringOption.RIGHT and DisplayStringOption.BLINK flags for DisplayString. * Add DisplayStringOption.RIGHT and DisplayStringOption.BLINK flags for DisplayString.
* Make Vec2 object float-based instead of integer-based. * Make Vec2 and Vec3 objects float-based instead of integer-based.
* Add Vec2 arithmetic for division with a number and multiplication with another Vec2. * Split and organise functions in `Misc` namespace to appropriate new namespaces.
* Add Vec2/Vec3 arithmetic for division with a number and multiplication with another Vec2/Vec3.
* Add various Vec2/Vec3 operations such as Normalize() or Lerp().
* Add log messages warnings to functions AddCallback and RemoveCallback. * Add log messages warnings to functions AddCallback and RemoveCallback.
* Fix InventoryItem constructor, now it will accept compound values in Item Action parameter. * Fix InventoryItem constructor, now it will accept compound values in Item Action parameter.
* Fix Moveable constructor forcing initial animation to 0 and hit points to 10, even if not specified. * Fix Moveable constructor forcing initial animation to 0 and hit points to 10, even if not specified.

View file

@ -16,7 +16,7 @@ Flow.SetTitleScreenImagePath("Screens\\main.jpg")
Flow.SetTotalSecretCount(5) Flow.SetTotalSecretCount(5)
-- Enable/Disable bilinear filtering. -- Enable/Disable Point Filter (Square, unsmoothed pixels).
Flow.EnablePointFilter(false) Flow.EnablePointFilter(false)

View file

@ -91,7 +91,7 @@ namespace TEN::Hud
float life = round(DisplayPickup::LIFE_MAX * FPS); float life = round(DisplayPickup::LIFE_MAX * FPS);
// Increment count of existing display pickup if it exists. // Increment count of existing display pickup if it exists.
for (auto& pickup : DisplayPickups) for (auto& pickup : _displayPickups)
{ {
// Ignore already disappearing display pickups. // Ignore already disappearing display pickups.
if (pickup.Life <= 0.0f) if (pickup.Life <= 0.0f)
@ -132,17 +132,17 @@ namespace TEN::Hud
void PickupSummaryController::Update() void PickupSummaryController::Update()
{ {
if (DisplayPickups.empty()) if (_displayPickups.empty())
return; return;
// Get and apply stack positions as targets. // Get and apply stack positions as targets.
auto stackPositions = GetStackPositions(); auto stackPositions = GetStackPositions();
for (int i = 0; i < stackPositions.size(); i++) for (int i = 0; i < stackPositions.size(); i++)
DisplayPickups[i].Target = std::move(stackPositions[i]); _displayPickups[i].Target = std::move(stackPositions[i]);
// Update display pickups. // Update display pickups.
bool isHead = true; bool isHead = true;
for (auto& pickup : DisplayPickups) for (auto& pickup : _displayPickups)
{ {
pickup.Update(isHead); pickup.Update(isHead);
isHead = false; isHead = false;
@ -155,11 +155,11 @@ namespace TEN::Hud
{ {
//DrawDebug(); //DrawDebug();
if (DisplayPickups.empty()) if (_displayPickups.empty())
return; return;
// Draw display pickups. // Draw display pickups.
for (const auto& pickup : DisplayPickups) for (const auto& pickup : _displayPickups)
{ {
if (pickup.IsOffscreen()) if (pickup.IsOffscreen())
continue; continue;
@ -170,7 +170,7 @@ namespace TEN::Hud
void PickupSummaryController::Clear() void PickupSummaryController::Clear()
{ {
DisplayPickups.clear(); _displayPickups.clear();
} }
std::vector<Vector2> PickupSummaryController::GetStackPositions() const std::vector<Vector2> PickupSummaryController::GetStackPositions() const
@ -183,8 +183,8 @@ namespace TEN::Hud
// Calculate stack positions. // Calculate stack positions.
auto stackPositions = std::vector<Vector2>{}; auto stackPositions = std::vector<Vector2>{};
stackPositions.resize(DisplayPickups.size()); stackPositions.resize(_displayPickups.size());
for (int i = 0; i < DisplayPickups.size(); i++) for (int i = 0; i < _displayPickups.size(); i++)
{ {
auto relPos = (i < STACK_HEIGHT_MAX) ? (Vector2(0.0f, i) * SCREEN_SCALE) : Vector2(0.0f, SCREEN_SPACE_RES.y); auto relPos = (i < STACK_HEIGHT_MAX) ? (Vector2(0.0f, i) * SCREEN_SCALE) : Vector2(0.0f, SCREEN_SPACE_RES.y);
auto pos = (SCREEN_SPACE_RES - relPos) - SCREEN_OFFSET; auto pos = (SCREEN_SPACE_RES - relPos) - SCREEN_OFFSET;
@ -196,29 +196,29 @@ namespace TEN::Hud
DisplayPickup& PickupSummaryController::GetNewDisplayPickup() DisplayPickup& PickupSummaryController::GetNewDisplayPickup()
{ {
assertion(DisplayPickups.size() <= DISPLAY_PICKUP_COUNT_MAX, "Display pickup overflow."); assertion(_displayPickups.size() <= DISPLAY_PICKUP_COUNT_MAX, "Display pickup overflow.");
// Add and return new display pickup. // Add and return new display pickup.
if (DisplayPickups.size() < DISPLAY_PICKUP_COUNT_MAX) if (_displayPickups.size() < DISPLAY_PICKUP_COUNT_MAX)
return DisplayPickups.emplace_back(); return _displayPickups.emplace_back();
// Clear and return most recent display pickup. // Clear and return most recent display pickup.
auto& pickup = DisplayPickups.back(); auto& pickup = _displayPickups.back();
pickup = {}; pickup = {};
return pickup; return pickup;
} }
void PickupSummaryController::ClearInactiveDisplayPickups() void PickupSummaryController::ClearInactiveDisplayPickups()
{ {
DisplayPickups.erase( _displayPickups.erase(
std::remove_if( std::remove_if(
DisplayPickups.begin(), DisplayPickups.end(), _displayPickups.begin(), _displayPickups.end(),
[](const DisplayPickup& pickup) { return ((pickup.Life <= 0.0f) && pickup.IsOffscreen()); }), [](const DisplayPickup& pickup) { return ((pickup.Life <= 0.0f) && pickup.IsOffscreen()); }),
DisplayPickups.end()); _displayPickups.end());
} }
void PickupSummaryController::DrawDebug() const void PickupSummaryController::DrawDebug() const
{ {
g_Renderer.PrintDebugMessage("Display pickups in summary: %d", DisplayPickups.size()); g_Renderer.PrintDebugMessage("Display pickups in summary: %d", _displayPickups.size());
} }
} }

View file

@ -37,7 +37,7 @@ namespace TEN::Hud
static constexpr auto DISPLAY_PICKUP_COUNT_ARG_DEFAULT = 1; static constexpr auto DISPLAY_PICKUP_COUNT_ARG_DEFAULT = 1;
// Members // Members
std::vector<DisplayPickup> DisplayPickups = {}; std::vector<DisplayPickup> _displayPickups = {};
public: public:
// Utilities // Utilities

View file

@ -52,10 +52,10 @@ namespace TEN::Hud
const auto& player = GetLaraInfo(item); const auto& player = GetLaraInfo(item);
// Initialize bar values. // Initialize bar values.
AirBar.Initialize(player.Status.Air / LARA_AIR_MAX); _airBar.Initialize(player.Status.Air / LARA_AIR_MAX);
ExposureBar.Initialize(player.Status.Exposure / LARA_EXPOSURE_MAX); _exposureBar.Initialize(player.Status.Exposure / LARA_EXPOSURE_MAX);
HealthBar.Initialize(item.HitPoints / LARA_HEALTH_MAX); _healthBar.Initialize(item.HitPoints / LARA_HEALTH_MAX);
StaminaBar.Initialize(player.Status.Stamina / LARA_STAMINA_MAX); _staminaBar.Initialize(player.Status.Stamina / LARA_STAMINA_MAX);
} }
void StatusBarsController::Update(const ItemInfo& item) void StatusBarsController::Update(const ItemInfo& item)
@ -64,7 +64,7 @@ namespace TEN::Hud
// Update flash. // Update flash.
if ((GameTimer % (int)round(FLASH_INTERVAL * FPS)) == 0) if ((GameTimer % (int)round(FLASH_INTERVAL * FPS)) == 0)
DoFlash = !DoFlash; _doFlash = !_doFlash;
// Update bars. // Update bars.
UpdateAirBar(item); UpdateAirBar(item);
@ -99,13 +99,13 @@ namespace TEN::Hud
const auto& player = GetLaraInfo(item); const auto& player = GetLaraInfo(item);
// Update generic data. // Update generic data.
AirBar.Update(player.Status.Air / LARA_AIR_MAX); _airBar.Update(player.Status.Air / LARA_AIR_MAX);
// Update life. // Update life.
if (AirBar.Value != AirBar.TargetValue || if (_airBar.Value != _airBar.TargetValue ||
player.Control.WaterStatus == WaterStatus::Underwater) player.Control.WaterStatus == WaterStatus::Underwater)
{ {
AirBar.Life = round(StatusBar::LIFE_MAX * FPS); _airBar.Life = round(StatusBar::LIFE_MAX * FPS);
} }
// HACK: Special case for UPV as it sets player.Control.WaterStatus to WaterStatus::Dry. // HACK: Special case for UPV as it sets player.Control.WaterStatus to WaterStatus::Dry.
@ -113,7 +113,7 @@ namespace TEN::Hud
{ {
const auto& vehicleItem = g_Level.Items[player.Context.Vehicle]; const auto& vehicleItem = g_Level.Items[player.Context.Vehicle];
if (vehicleItem.ObjectNumber == ID_UPV) if (vehicleItem.ObjectNumber == ID_UPV)
AirBar.Life = round(StatusBar::LIFE_MAX * FPS); _airBar.Life = round(StatusBar::LIFE_MAX * FPS);
} }
} }
@ -122,13 +122,13 @@ namespace TEN::Hud
const auto& player = GetLaraInfo(item); const auto& player = GetLaraInfo(item);
// Update generic data. // Update generic data.
ExposureBar.Update(player.Status.Exposure / LARA_EXPOSURE_MAX); _exposureBar.Update(player.Status.Exposure / LARA_EXPOSURE_MAX);
// Update life. // Update life.
if (ExposureBar.Value != ExposureBar.TargetValue || if (_exposureBar.Value != _exposureBar.TargetValue ||
(TestEnvironment(ENV_FLAG_WATER, item.RoomNumber) && TestEnvironment(ENV_FLAG_COLD, item.RoomNumber))) (TestEnvironment(ENV_FLAG_WATER, item.RoomNumber) && TestEnvironment(ENV_FLAG_COLD, item.RoomNumber)))
{ {
ExposureBar.Life = round(StatusBar::LIFE_MAX * FPS); _exposureBar.Life = round(StatusBar::LIFE_MAX * FPS);
} }
} }
@ -137,25 +137,25 @@ namespace TEN::Hud
const auto& player = GetLaraInfo(item); const auto& player = GetLaraInfo(item);
// Update generic data. // Update generic data.
HealthBar.Update(item.HitPoints / LARA_HEALTH_MAX); _healthBar.Update(item.HitPoints / LARA_HEALTH_MAX);
// Update life. // Update life.
if (HealthBar.Value != HealthBar.TargetValue || if (_healthBar.Value != _healthBar.TargetValue ||
item.HitPoints <= LARA_HEALTH_CRITICAL || player.Status.Poison != 0 || item.HitPoints <= LARA_HEALTH_CRITICAL || player.Status.Poison != 0 ||
(player.Control.HandStatus == HandStatus::WeaponDraw && (player.Control.HandStatus == HandStatus::WeaponDraw &&
player.Control.Weapon.GunType != LaraWeaponType::Flare) || // HACK: Exclude flare. player.Control.Weapon.GunType != LaraWeaponType::Flare) || // HACK: Exclude flare.
(player.Control.HandStatus == HandStatus::WeaponReady && (player.Control.HandStatus == HandStatus::WeaponReady &&
player.Control.Weapon.GunType != LaraWeaponType::Torch)) // HACK: Exclude torch. player.Control.Weapon.GunType != LaraWeaponType::Torch)) // HACK: Exclude torch.
{ {
HealthBar.Life = round(StatusBar::LIFE_MAX * FPS); _healthBar.Life = round(StatusBar::LIFE_MAX * FPS);
} }
// Special case for weapon undraw. // Special case for weapon undraw.
if (HealthBar.Value == HealthBar.TargetValue && if (_healthBar.Value == _healthBar.TargetValue &&
item.HitPoints > LARA_HEALTH_CRITICAL && player.Status.Poison == 0 && item.HitPoints > LARA_HEALTH_CRITICAL && player.Status.Poison == 0 &&
player.Control.HandStatus == HandStatus::WeaponUndraw) player.Control.HandStatus == HandStatus::WeaponUndraw)
{ {
HealthBar.Life = 0.0f; _healthBar.Life = 0.0f;
} }
} }
@ -164,20 +164,20 @@ namespace TEN::Hud
const auto& player = GetLaraInfo(item); const auto& player = GetLaraInfo(item);
// Update generic data. // Update generic data.
StaminaBar.Update(player.Status.Stamina / LARA_STAMINA_MAX); _staminaBar.Update(player.Status.Stamina / LARA_STAMINA_MAX);
// Update life. // Update life.
if (StaminaBar.Value != StaminaBar.TargetValue || if (_staminaBar.Value != _staminaBar.TargetValue ||
StaminaBar.Value != 1.0f) _staminaBar.Value != 1.0f)
{ {
StaminaBar.Life = round(StatusBar::LIFE_MAX * FPS); _staminaBar.Life = round(StatusBar::LIFE_MAX * FPS);
} }
} }
void StatusBarsController::DrawStatusBar(float value, float criticalValue, const RendererHudBar& rHudBar, GAME_OBJECT_ID textureID, int frame, bool isPoisoned) const void StatusBarsController::DrawStatusBar(float value, float criticalValue, const RendererHudBar& rHudBar, GAME_OBJECT_ID textureID, int frame, bool isPoisoned) const
{ {
if (value <= criticalValue) if (value <= criticalValue)
value = DoFlash ? value : 0.0f; value = _doFlash ? value : 0.0f;
g_Renderer.DrawBar(value, rHudBar, textureID, frame, isPoisoned); g_Renderer.DrawBar(value, rHudBar, textureID, frame, isPoisoned);
} }
@ -187,10 +187,10 @@ namespace TEN::Hud
constexpr auto TEXTURE_ID = ID_AIR_BAR_TEXTURE; constexpr auto TEXTURE_ID = ID_AIR_BAR_TEXTURE;
constexpr auto CRITICAL_VALUE = LARA_AIR_CRITICAL / LARA_AIR_MAX; constexpr auto CRITICAL_VALUE = LARA_AIR_CRITICAL / LARA_AIR_MAX;
if (AirBar.Life <= 0.0f) if (_airBar.Life <= 0.0f)
return; return;
DrawStatusBar(AirBar.Value, CRITICAL_VALUE, *g_AirBar, TEXTURE_ID, 0, false); DrawStatusBar(_airBar.Value, CRITICAL_VALUE, *g_AirBar, TEXTURE_ID, 0, false);
} }
void StatusBarsController::DrawExposureBar() const void StatusBarsController::DrawExposureBar() const
@ -198,10 +198,10 @@ namespace TEN::Hud
constexpr auto TEXTURE_ID = ID_SFX_BAR_TEXTURE; constexpr auto TEXTURE_ID = ID_SFX_BAR_TEXTURE;
constexpr auto CRITICAL_VALUE = LARA_EXPOSURE_CRITICAL / LARA_EXPOSURE_MAX; constexpr auto CRITICAL_VALUE = LARA_EXPOSURE_CRITICAL / LARA_EXPOSURE_MAX;
if (ExposureBar.Life <= 0.0f) if (_exposureBar.Life <= 0.0f)
return; return;
DrawStatusBar(ExposureBar.Value, CRITICAL_VALUE, *g_ExposureBar, TEXTURE_ID, 0, false); DrawStatusBar(_exposureBar.Value, CRITICAL_VALUE, *g_ExposureBar, TEXTURE_ID, 0, false);
} }
void StatusBarsController::DrawHealthBar(bool isPoisoned) const void StatusBarsController::DrawHealthBar(bool isPoisoned) const
@ -209,10 +209,10 @@ namespace TEN::Hud
constexpr auto TEXTURE_ID = ID_HEALTH_BAR_TEXTURE; constexpr auto TEXTURE_ID = ID_HEALTH_BAR_TEXTURE;
constexpr auto CRITICAL_VALUE = LARA_HEALTH_CRITICAL / LARA_HEALTH_MAX; constexpr auto CRITICAL_VALUE = LARA_HEALTH_CRITICAL / LARA_HEALTH_MAX;
if (HealthBar.Life <= 0.0f) if (_healthBar.Life <= 0.0f)
return; return;
DrawStatusBar(HealthBar.Value, CRITICAL_VALUE, *g_HealthBar, TEXTURE_ID, GlobalCounter, isPoisoned); DrawStatusBar(_healthBar.Value, CRITICAL_VALUE, *g_HealthBar, TEXTURE_ID, GlobalCounter, isPoisoned);
} }
void StatusBarsController::DrawStaminaBar() const void StatusBarsController::DrawStaminaBar() const
@ -220,9 +220,9 @@ namespace TEN::Hud
constexpr auto TEXTURE_ID = ID_DASH_BAR_TEXTURE; constexpr auto TEXTURE_ID = ID_DASH_BAR_TEXTURE;
constexpr auto CRITICAL_VALUE = 0; constexpr auto CRITICAL_VALUE = 0;
if (StaminaBar.Life <= 0.0f) if (_staminaBar.Life <= 0.0f)
return; return;
DrawStatusBar(StaminaBar.Value, CRITICAL_VALUE, *g_StaminaBar, TEXTURE_ID, 0, false); DrawStatusBar(_staminaBar.Value, CRITICAL_VALUE, *g_StaminaBar, TEXTURE_ID, 0, false);
} }
} }

View file

@ -25,12 +25,12 @@ namespace TEN::Hud
{ {
private: private:
// Members // Members
StatusBar AirBar = {}; StatusBar _airBar = {};
StatusBar ExposureBar = {}; StatusBar _exposureBar = {};
StatusBar HealthBar = {}; StatusBar _healthBar = {};
StatusBar StaminaBar = {}; StatusBar _staminaBar = {};
bool DoFlash = false; bool _doFlash = false;
public: public:
// Utilities // Utilities

View file

@ -96,7 +96,7 @@ namespace TEN::Hud
// Update orientation. // Update orientation.
if (IsPrimary) if (IsPrimary)
{ {
Orientation += ROT; Orientation += IsActive ? ROT : (ROT * 2);
} }
else else
{ {
@ -114,7 +114,7 @@ namespace TEN::Hud
} }
else else
{ {
Scale = Lerp(Scale, 0.0f, MORPH_LERP_ALPHA / 2); Scale = Lerp(Scale, 0.0f, MORPH_LERP_ALPHA / 4);
} }
// Update color. // Update color.
@ -178,8 +178,8 @@ namespace TEN::Hud
// Check if target highlighter is enabled. // Check if target highlighter is enabled.
if (!g_Configuration.EnableTargetHighlighter) if (!g_Configuration.EnableTargetHighlighter)
{ {
if (!Crosshairs.empty()) if (!_crosshairs.empty())
Crosshairs.clear(); _crosshairs.clear();
return; return;
} }
@ -197,8 +197,8 @@ namespace TEN::Hud
itemNumbers.push_back(itemPtr->Index); itemNumbers.push_back(itemPtr->Index);
// Find crosshair at item number key. // Find crosshair at item number key.
auto it = Crosshairs.find(itemPtr->Index); auto it = _crosshairs.find(itemPtr->Index);
if (it == Crosshairs.end()) if (it == _crosshairs.end())
continue; continue;
// Set crosshair as primary or peripheral. // Set crosshair as primary or peripheral.
@ -222,10 +222,10 @@ namespace TEN::Hud
{ {
//DrawDebug(); //DrawDebug();
if (Crosshairs.empty()) if (_crosshairs.empty())
return; return;
for (const auto& [itemNumber, crosshair] : Crosshairs) for (const auto& [itemNumber, crosshair] : _crosshairs)
crosshair.Draw(); crosshair.Draw();
} }
@ -239,7 +239,7 @@ namespace TEN::Hud
constexpr auto TARGET_BONE_ID = 0; constexpr auto TARGET_BONE_ID = 0;
// No crosshairs to update; return early. // No crosshairs to update; return early.
if (Crosshairs.empty() && itemNumbers.empty()) if (_crosshairs.empty() && itemNumbers.empty())
return; return;
// Update active crosshairs. // Update active crosshairs.
@ -249,8 +249,8 @@ namespace TEN::Hud
auto targetPos = GetJointPosition(item, TARGET_BONE_ID).ToVector3(); auto targetPos = GetJointPosition(item, TARGET_BONE_ID).ToVector3();
// Update existing active crosshair. // Update existing active crosshair.
auto it = Crosshairs.find(itemNumber); auto it = _crosshairs.find(itemNumber);
if (it != Crosshairs.end()) if (it != _crosshairs.end())
{ {
auto& crosshair = it->second; auto& crosshair = it->second;
if (crosshair.IsActive) if (crosshair.IsActive)
@ -265,7 +265,7 @@ namespace TEN::Hud
} }
// Update inactive crosshairs. // Update inactive crosshairs.
for (auto& [itemNumber, crosshair] : Crosshairs) for (auto& [itemNumber, crosshair] : _crosshairs)
{ {
// Find crosshairs at absent item number keys. // Find crosshairs at absent item number keys.
if (Contains(itemNumbers, itemNumber)) if (Contains(itemNumbers, itemNumber))
@ -286,12 +286,12 @@ namespace TEN::Hud
constexpr auto CROSSHAIR_COUNT_MAX = 16; constexpr auto CROSSHAIR_COUNT_MAX = 16;
// Map is full; clear smallest crosshair. // Map is full; clear smallest crosshair.
if (Crosshairs.size() >= CROSSHAIR_COUNT_MAX) if (_crosshairs.size() >= CROSSHAIR_COUNT_MAX)
{ {
int key = 0; int key = 0;
float smallestScale = INFINITY; float smallestScale = INFINITY;
for (auto& [itemNumber, crosshair] : Crosshairs) for (auto& [itemNumber, crosshair] : _crosshairs)
{ {
if (crosshair.Scale < smallestScale) if (crosshair.Scale < smallestScale)
{ {
@ -300,12 +300,12 @@ namespace TEN::Hud
} }
} }
Crosshairs.erase(key); _crosshairs.erase(key);
} }
// Return new crosshair. // Return new crosshair.
Crosshairs.insert({ itemNumber, {} }); _crosshairs.insert({ itemNumber, {} });
auto& crosshair = Crosshairs.at(itemNumber); auto& crosshair = _crosshairs.at(itemNumber);
crosshair = {}; crosshair = {};
return crosshair; return crosshair;
} }
@ -348,11 +348,11 @@ namespace TEN::Hud
void TargetHighlighterController::ClearInactiveCrosshairs() void TargetHighlighterController::ClearInactiveCrosshairs()
{ {
for (auto it = Crosshairs.begin(); it != Crosshairs.end();) for (auto it = _crosshairs.begin(); it != _crosshairs.end();)
{ {
const auto& crosshair = it->second; const auto& crosshair = it->second;
(!crosshair.IsActive && crosshair.Scale <= EPSILON) ? (!crosshair.IsActive && crosshair.Scale <= EPSILON) ?
(it = Crosshairs.erase(it)) : ++it; (it = _crosshairs.erase(it)) : ++it;
} }
} }
@ -362,7 +362,7 @@ namespace TEN::Hud
unsigned int primaryCount = 0; unsigned int primaryCount = 0;
unsigned int peripheralCount = 0; unsigned int peripheralCount = 0;
for (const auto& [itemNumber, crosshair] : Crosshairs) for (const auto& [itemNumber, crosshair] : _crosshairs)
{ {
crosshair.IsPrimary ? primaryCount++ : peripheralCount++; crosshair.IsPrimary ? primaryCount++ : peripheralCount++;

View file

@ -55,7 +55,7 @@ namespace TEN::Hud
{ {
private: private:
// Members // Members
std::unordered_map<int, CrosshairData> Crosshairs = {}; // Key = item number. std::unordered_map<int, CrosshairData> _crosshairs = {}; // Key = item number.
public: public:
// Utilities // Utilities

View file

@ -38,22 +38,25 @@
#include "Scripting/Include/Flow/ScriptInterfaceFlowHandler.h" #include "Scripting/Include/Flow/ScriptInterfaceFlowHandler.h"
#include "Scripting/Include/ScriptInterfaceLevel.h" #include "Scripting/Include/ScriptInterfaceLevel.h"
#include "Sound/sound.h" #include "Sound/sound.h"
#include "Specific/Input/Input.h"
#include "Specific/winmain.h" #include "Specific/winmain.h"
using namespace TEN::Collision::Floordata;
using namespace TEN::Control::Volumes; using namespace TEN::Control::Volumes;
using namespace TEN::Effects::Hair; using namespace TEN::Effects::Hair;
using namespace TEN::Effects::Items; using namespace TEN::Effects::Items;
using namespace TEN::Collision::Floordata;
using namespace TEN::Input; using namespace TEN::Input;
using namespace TEN::Math; using namespace TEN::Math;
using TEN::Renderer::g_Renderer; using TEN::Renderer::g_Renderer;
using PlayerStateRoutine = std::function<void(ItemInfo* item, CollisionInfo* coll)>;
LaraInfo Lara = {}; LaraInfo Lara = {};
ItemInfo* LaraItem; ItemInfo* LaraItem;
CollisionInfo LaraCollision = {}; CollisionInfo LaraCollision = {};
std::function<LaraRoutineFunction> lara_control_routines[NUM_LARA_STATES + 1] = auto PlayerStateControlRoutines = std::array<PlayerStateRoutine, NUM_LARA_STATES + 1>
{ {
lara_as_walk_forward, lara_as_walk_forward,
lara_as_run_forward, lara_as_run_forward,
@ -245,11 +248,11 @@ std::function<LaraRoutineFunction> lara_control_routines[NUM_LARA_STATES + 1] =
lara_as_null, lara_as_null,
lara_as_null, lara_as_null,
lara_as_use_puzzle,//189 lara_as_use_puzzle,//189
lara_as_null,//190 lara_as_pushable_edge_slip,//190
lara_as_sprint_slide,//191 lara_as_sprint_slide,//191
}; };
std::function<LaraRoutineFunction> lara_collision_routines[NUM_LARA_STATES + 1] = auto PlayerStateCollisionRoutines = std::array<PlayerStateRoutine, NUM_LARA_STATES + 1>
{ {
lara_col_walk_forward, lara_col_walk_forward,
lara_col_run_forward, lara_col_run_forward,
@ -287,8 +290,8 @@ std::function<LaraRoutineFunction> lara_collision_routines[NUM_LARA_STATES + 1]
lara_col_surface_idle,//33 lara_col_surface_idle,//33
lara_col_surface_swim_forward,//34 lara_col_surface_swim_forward,//34
lara_col_surface_dive,//35 lara_col_surface_dive,//35
lara_default_col, lara_void_func,//36
lara_default_col, lara_void_func,//37
lara_default_col, lara_default_col,
lara_default_col, lara_default_col,
lara_default_col, lara_default_col,
@ -916,7 +919,7 @@ void LaraAboveWater(ItemInfo* item, CollisionInfo* coll)
return; return;
// Handle player state. // Handle player state.
lara_control_routines[item->Animation.ActiveState](item, coll); PlayerStateControlRoutines[item->Animation.ActiveState](item, coll);
HandleLaraMovementParameters(item, coll); HandleLaraMovementParameters(item, coll);
AnimateItem(item); AnimateItem(item);
@ -928,7 +931,7 @@ void LaraAboveWater(ItemInfo* item, CollisionInfo* coll)
// Handle player state collision. // Handle player state collision.
if (lara->Context.Vehicle == NO_ITEM) if (lara->Context.Vehicle == NO_ITEM)
lara_collision_routines[item->Animation.ActiveState](item, coll); PlayerStateCollisionRoutines[item->Animation.ActiveState](item, coll);
} }
// Handle weapons. // Handle weapons.
@ -964,7 +967,7 @@ void LaraWaterSurface(ItemInfo* item, CollisionInfo* coll)
coll->Setup.BlockCeilingSlope = false; coll->Setup.BlockCeilingSlope = false;
coll->Setup.BlockDeathFloorDown = false; coll->Setup.BlockDeathFloorDown = false;
coll->Setup.BlockMonkeySwingEdge = false; coll->Setup.BlockMonkeySwingEdge = false;
coll->Setup.EnableObjectPush = false; coll->Setup.EnableObjectPush = true;
coll->Setup.EnableSpasm = false; coll->Setup.EnableSpasm = false;
coll->Setup.PrevPosition = item->Pose.Position; coll->Setup.PrevPosition = item->Pose.Position;
@ -979,7 +982,7 @@ void LaraWaterSurface(ItemInfo* item, CollisionInfo* coll)
lara->Control.Count.Pose = 0; lara->Control.Count.Pose = 0;
lara_control_routines[item->Animation.ActiveState](item, coll); PlayerStateControlRoutines[item->Animation.ActiveState](item, coll);
auto* level = g_GameFlow->GetLevel(CurrentLevel); auto* level = g_GameFlow->GetLevel(CurrentLevel);
@ -1005,7 +1008,7 @@ void LaraWaterSurface(ItemInfo* item, CollisionInfo* coll)
DoObjectCollision(item, coll); DoObjectCollision(item, coll);
if (lara->Context.Vehicle == NO_ITEM) if (lara->Context.Vehicle == NO_ITEM)
lara_collision_routines[item->Animation.ActiveState](item, coll); PlayerStateCollisionRoutines[item->Animation.ActiveState](item, coll);
UpdateLaraRoom(item, LARA_RADIUS); UpdateLaraRoom(item, LARA_RADIUS);
@ -1050,7 +1053,7 @@ void LaraUnderwater(ItemInfo* item, CollisionInfo* coll)
lara->Control.Count.Pose = 0; lara->Control.Count.Pose = 0;
lara_control_routines[item->Animation.ActiveState](item, coll); PlayerStateControlRoutines[item->Animation.ActiveState](item, coll);
auto* level = g_GameFlow->GetLevel(CurrentLevel); auto* level = g_GameFlow->GetLevel(CurrentLevel);
@ -1095,7 +1098,7 @@ void LaraUnderwater(ItemInfo* item, CollisionInfo* coll)
DoObjectCollision(item, coll); DoObjectCollision(item, coll);
if (/*lara->ExtraAnim == -1 &&*/ lara->Context.Vehicle == NO_ITEM) if (/*lara->ExtraAnim == -1 &&*/ lara->Context.Vehicle == NO_ITEM)
lara_collision_routines[item->Animation.ActiveState](item, coll); PlayerStateCollisionRoutines[item->Animation.ActiveState](item, coll);
UpdateLaraRoom(item, 0); UpdateLaraRoom(item, 0);
@ -1186,8 +1189,8 @@ bool UpdateLaraRoom(ItemInfo* item, int height, int xOffset, int zOffset)
auto point = Geometry::TranslatePoint(item->Pose.Position, item->Pose.Orientation.y, zOffset, height, xOffset); auto point = Geometry::TranslatePoint(item->Pose.Position, item->Pose.Orientation.y, zOffset, height, xOffset);
// Hacky L-shaped Location traversal. // Hacky L-shaped Location traversal.
item->Location = GetRoom(item->Location, point.x, point.y, point.z); item->Location = GetRoom(item->Location, point);
item->Location = GetRoom(item->Location, item->Pose.Position.x, point.y, item->Pose.Position.z); item->Location = GetRoom(item->Location, Vector3i(item->Pose.Position.x, point.y, item->Pose.Position.z));
item->Floor = GetFloorHeight(item->Location, item->Pose.Position.x, item->Pose.Position.z).value_or(NO_HEIGHT); item->Floor = GetFloorHeight(item->Location, item->Pose.Position.x, item->Pose.Position.z).value_or(NO_HEIGHT);
if (item->RoomNumber != item->Location.roomNumber) if (item->RoomNumber != item->Location.roomNumber)

View file

@ -78,15 +78,15 @@ constexpr auto LARA_HEALTH_MAX = 1000.0f;
constexpr auto LARA_HEALTH_CRITICAL = LARA_HEALTH_MAX / 4; constexpr auto LARA_HEALTH_CRITICAL = LARA_HEALTH_MAX / 4;
constexpr auto LARA_POISON_MAX = 128.0f; constexpr auto LARA_POISON_MAX = 128.0f;
constexpr auto LARA_STAMINA_MAX = 120.0f; constexpr auto LARA_STAMINA_MAX = 120.0f;
constexpr auto LARA_STAMINA_CRITICAL = LARA_STAMINA_MAX / 2;
constexpr auto LARA_STAMINA_MIN = LARA_STAMINA_MAX / 10; constexpr auto LARA_STAMINA_MIN = LARA_STAMINA_MAX / 10;
constexpr auto LARA_STAMINA_CRITICAL = LARA_STAMINA_MAX / 2;
constexpr auto PLAYER_DRIP_NODE_MAX = 64.0f; constexpr auto PLAYER_DRIP_NODE_MAX = 64.0f;
constexpr auto PLAYER_BUBBLE_NODE_MAX = 12.0f; constexpr auto PLAYER_BUBBLE_NODE_MAX = 12.0f;
constexpr auto STEPUP_HEIGHT = (int)CLICK(3.0f / 2); constexpr auto STEPUP_HEIGHT = (int)CLICK(3 / 2.0f);
constexpr auto BAD_JUMP_CEILING = (int)CLICK(6.0f / 8); constexpr auto BAD_JUMP_CEILING = (int)CLICK(6 / 8.0f);
constexpr auto SHALLOW_WATER_DEPTH = (int)CLICK(1.0f / 2); constexpr auto SHALLOW_WATER_DEPTH = (int)CLICK(1 / 2.0f);
constexpr auto WADE_DEPTH = STEPUP_HEIGHT; constexpr auto WADE_DEPTH = STEPUP_HEIGHT;
constexpr auto SWIM_DEPTH = CLICK(3) - 38; constexpr auto SWIM_DEPTH = CLICK(3) - 38;
constexpr auto SLOPE_DIFFERENCE = 60; constexpr auto SLOPE_DIFFERENCE = 60;
@ -95,14 +95,6 @@ extern LaraInfo Lara;
extern ItemInfo* LaraItem; extern ItemInfo* LaraItem;
extern CollisionInfo LaraCollision; extern CollisionInfo LaraCollision;
#define LARA_MESHES(slot, mesh) Lara.meshPtrs[mesh] = MESHES(slot, mesh)
#define CHECK_LARA_MESHES(slot, mesh) Lara.meshPtrs[mesh] == MESHES(slot, mesh)
#define INIT_LARA_MESHES(mesh, to, from) Lara.meshPtrs[mesh] = LARA_MESHES(to, mesh) = LARA_MESHES(from, mesh)
#define LaraRoutineFunction void(ItemInfo* item, CollisionInfo* coll)
extern std::function<LaraRoutineFunction> lara_control_routines[NUM_LARA_STATES + 1];
extern std::function<LaraRoutineFunction> lara_collision_routines[NUM_LARA_STATES + 1];
void LaraControl(ItemInfo* item, CollisionInfo* coll); void LaraControl(ItemInfo* item, CollisionInfo* coll);
void LaraAboveWater(ItemInfo* item, CollisionInfo* coll); void LaraAboveWater(ItemInfo* item, CollisionInfo* coll);
void LaraWaterSurface(ItemInfo* item, CollisionInfo* coll); void LaraWaterSurface(ItemInfo* item, CollisionInfo* coll);
@ -110,4 +102,4 @@ void LaraUnderwater(ItemInfo* item, CollisionInfo* coll);
void LaraCheat(ItemInfo* item, CollisionInfo* coll); void LaraCheat(ItemInfo* item, CollisionInfo* coll);
void AnimateItem(ItemInfo* item); void AnimateItem(ItemInfo* item);
void UpdateLara(ItemInfo* item, bool isTitle); void UpdateLara(ItemInfo* item, bool isTitle);
bool UpdateLaraRoom(ItemInfo* item, int height, int xOffset = 0, int zOffset = 0); bool UpdateLaraRoom(ItemInfo* item, int height, int xOffset = 0, int zOffset = 0);

View file

@ -433,17 +433,15 @@ void LaraSnapToHeight(ItemInfo* item, CollisionInfo* coll)
void GetLaraDeadlyBounds() void GetLaraDeadlyBounds()
{ {
auto bounds = GameBoundingBox(LaraItem); auto bounds = GameBoundingBox(LaraItem);
auto tBounds = GameBoundingBox::Zero; bounds.Rotate(LaraItem->Pose.Orientation);
tBounds.RotateNoPersp(LaraItem->Pose.Orientation, bounds);
DeadlyBounds = GameBoundingBox( DeadlyBounds = GameBoundingBox(
LaraItem->Pose.Position.x + tBounds.X1, LaraItem->Pose.Position.x + bounds.X1,
LaraItem->Pose.Position.x + tBounds.X2, LaraItem->Pose.Position.x + bounds.X2,
LaraItem->Pose.Position.y + tBounds.Y1, LaraItem->Pose.Position.y + bounds.Y1,
LaraItem->Pose.Position.y + tBounds.Y2, LaraItem->Pose.Position.y + bounds.Y2,
LaraItem->Pose.Position.z + tBounds.Z1, LaraItem->Pose.Position.z + bounds.Z1,
LaraItem->Pose.Position.z + tBounds.Z2 LaraItem->Pose.Position.z + bounds.Z2);
);
} }
void LaraJumpCollision(ItemInfo* item, CollisionInfo* coll, short moveAngle) void LaraJumpCollision(ItemInfo* item, CollisionInfo* coll, short moveAngle)

View file

@ -357,6 +357,17 @@ static void SetPlayerOptics(ItemInfo* item)
AlterFOV(LastFOV); AlterFOV(LastFOV);
} }
static short NormalizeLookAroundTurnRate(short turnRate, short opticRange)
{
constexpr auto ZOOM_LEVEL_MAX = ANGLE(10.0f);
constexpr auto ZOOM_LEVEL_REF = ANGLE(17.0f);
if (opticRange == 0)
return turnRate;
return short(turnRate * (ZOOM_LEVEL_MAX - opticRange) / ZOOM_LEVEL_REF);
};
void HandlePlayerLookAround(ItemInfo& item, bool invertXAxis) void HandlePlayerLookAround(ItemInfo& item, bool invertXAxis)
{ {
constexpr auto OPTIC_RANGE_MAX = ANGLE(8.5f); constexpr auto OPTIC_RANGE_MAX = ANGLE(8.5f);
@ -365,17 +376,6 @@ void HandlePlayerLookAround(ItemInfo& item, bool invertXAxis)
constexpr auto TURN_RATE_MAX = ANGLE(4.0f); constexpr auto TURN_RATE_MAX = ANGLE(4.0f);
constexpr auto TURN_RATE_ACCEL = ANGLE(0.75f); constexpr auto TURN_RATE_ACCEL = ANGLE(0.75f);
auto normalizeTurnRate = [](short turnRate, short opticRange)
{
constexpr auto ZOOM_LEVEL_MAX = ANGLE(10.0f);
constexpr auto ZOOM_LEVEL_REF = ANGLE(17.0f);
if (opticRange == 0)
return turnRate;
return short(turnRate * (ZOOM_LEVEL_MAX - opticRange) / ZOOM_LEVEL_REF);
};
auto& player = GetLaraInfo(item); auto& player = GetLaraInfo(item);
if (!CanPlayerLookAround(item)) if (!CanPlayerLookAround(item))
@ -427,14 +427,14 @@ void HandlePlayerLookAround(ItemInfo& item, bool invertXAxis)
if ((IsHeld(In::Forward) || IsHeld(In::Back)) && if ((IsHeld(In::Forward) || IsHeld(In::Back)) &&
(player.Control.Look.Mode == LookMode::Free || player.Control.Look.Mode == LookMode::Vertical)) (player.Control.Look.Mode == LookMode::Free || player.Control.Look.Mode == LookMode::Vertical))
{ {
axisCoeff.x = AxisMap[InputAxis::MoveVertical]; axisCoeff.x = AxisMap[(int)InputAxis::Move].y;
} }
// Determine Y axis coefficient. // Determine Y axis coefficient.
if ((IsHeld(In::Left) || IsHeld(In::Right)) && if ((IsHeld(In::Left) || IsHeld(In::Right)) &&
(player.Control.Look.Mode == LookMode::Free || player.Control.Look.Mode == LookMode::Horizontal)) (player.Control.Look.Mode == LookMode::Free || player.Control.Look.Mode == LookMode::Horizontal))
{ {
axisCoeff.y = AxisMap[InputAxis::MoveHorizontal]; axisCoeff.y = AxisMap[(int)InputAxis::Move].x;
} }
// Determine turn rate base values. // Determine turn rate base values.
@ -442,8 +442,8 @@ void HandlePlayerLookAround(ItemInfo& item, bool invertXAxis)
short turnRateAccel = isSlow ? (TURN_RATE_ACCEL / 2) : TURN_RATE_ACCEL; short turnRateAccel = isSlow ? (TURN_RATE_ACCEL / 2) : TURN_RATE_ACCEL;
// Normalize turn rate base values. // Normalize turn rate base values.
turnRateMax = normalizeTurnRate(turnRateMax, player.Control.Look.OpticRange); turnRateMax = NormalizeLookAroundTurnRate(turnRateMax, player.Control.Look.OpticRange);
turnRateAccel = normalizeTurnRate(turnRateAccel, player.Control.Look.OpticRange); turnRateAccel = NormalizeLookAroundTurnRate(turnRateAccel, player.Control.Look.OpticRange);
// Modulate turn rates. // Modulate turn rates.
player.Control.Look.TurnRate = EulerAngles( player.Control.Look.TurnRate = EulerAngles(
@ -539,8 +539,8 @@ void HandlePlayerWetnessDrips(ItemInfo& item)
int jointIndex = 0; int jointIndex = 0;
for (auto& node : player.Effect.DripNodes) for (auto& node : player.Effect.DripNodes)
{ {
auto pos = GetJointPosition(&item, jointIndex).ToVector3(); auto pos = GetJointPosition(&item, jointIndex);
int roomNumber = GetRoom(item.Location, pos.x, pos.y, pos.z).roomNumber; int roomNumber = GetRoom(item.Location, pos).roomNumber;
jointIndex++; jointIndex++;
// Node underwater; set max wetness value. // Node underwater; set max wetness value.
@ -558,7 +558,7 @@ void HandlePlayerWetnessDrips(ItemInfo& item)
float chance = (node / PLAYER_DRIP_NODE_MAX) / 2; float chance = (node / PLAYER_DRIP_NODE_MAX) / 2;
if (Random::TestProbability(chance)) if (Random::TestProbability(chance))
{ {
SpawnWetnessDrip(pos, item.RoomNumber); SpawnWetnessDrip(pos.ToVector3(), item.RoomNumber);
node -= 1.0f; node -= 1.0f;
if (node <= 0.0f) if (node <= 0.0f)
@ -576,8 +576,8 @@ void HandlePlayerDiveBubbles(ItemInfo& item)
int jointIndex = 0; int jointIndex = 0;
for (auto& node : player.Effect.BubbleNodes) for (auto& node : player.Effect.BubbleNodes)
{ {
auto pos = GetJointPosition(&item, jointIndex).ToVector3(); auto pos = GetJointPosition(&item, jointIndex);
int roomNumber = GetRoom(item.Location, pos.x, pos.y, pos.z).roomNumber; int roomNumber = GetRoom(item.Location, pos).roomNumber;
jointIndex++; jointIndex++;
// Node inactive; continue. // Node inactive; continue.
@ -593,7 +593,7 @@ void HandlePlayerDiveBubbles(ItemInfo& item)
if (Random::TestProbability(chance)) if (Random::TestProbability(chance))
{ {
unsigned int count = (int)round(node * BUBBLE_COUNT_MULT); unsigned int count = (int)round(node * BUBBLE_COUNT_MULT);
SpawnDiveBubbles(pos, roomNumber, count); SpawnDiveBubbles(pos.ToVector3(), roomNumber, count);
node -= 1.0f; node -= 1.0f;
if (node <= 0.0f) if (node <= 0.0f)
@ -862,14 +862,14 @@ void ModulateLaraTurnRateX(ItemInfo* item, short accelRate, short minTurnRate, s
{ {
auto* lara = GetLaraInfo(item); auto* lara = GetLaraInfo(item);
//lara->Control.TurnRate.x = ModulateLaraTurnRate(lara->Control.TurnRate.x, accelRate, minTurnRate, maxTurnRate, AxisMap[InputAxis::MoveVertical], invert); //lara->Control.TurnRate.x = ModulateLaraTurnRate(lara->Control.TurnRate.x, accelRate, minTurnRate, maxTurnRate, AxisMap[InputAxis::Move].y, invert);
} }
void ModulateLaraTurnRateY(ItemInfo* item, short accelRate, short minTurnRate, short maxTurnRate, bool invert) void ModulateLaraTurnRateY(ItemInfo* item, short accelRate, short minTurnRate, short maxTurnRate, bool invert)
{ {
auto* lara = GetLaraInfo(item); auto* lara = GetLaraInfo(item);
float axisCoeff = AxisMap[InputAxis::MoveHorizontal]; float axisCoeff = AxisMap[(int)InputAxis::Move].x;
if (item->Animation.IsAirborne) if (item->Animation.IsAirborne)
{ {
int sign = std::copysign(1, axisCoeff); int sign = std::copysign(1, axisCoeff);
@ -1000,7 +1000,7 @@ void ModulateLaraLean(ItemInfo* item, CollisionInfo* coll, short baseRate, short
if (!item->Animation.Velocity.z) if (!item->Animation.Velocity.z)
return; return;
float axisCoeff = AxisMap[InputAxis::MoveHorizontal]; float axisCoeff = AxisMap[(int)InputAxis::Move].x;
int sign = copysign(1, axisCoeff); int sign = copysign(1, axisCoeff);
short maxAngleNormalized = maxAngle * axisCoeff; short maxAngleNormalized = maxAngle * axisCoeff;
@ -1017,7 +1017,7 @@ void ModulateLaraCrawlFlex(ItemInfo* item, short baseRate, short maxAngle)
if (!item->Animation.Velocity.z) if (!item->Animation.Velocity.z)
return; return;
float axisCoeff = AxisMap[InputAxis::MoveHorizontal]; float axisCoeff = AxisMap[(int)InputAxis::Move].x;
int sign = copysign(1, axisCoeff); int sign = copysign(1, axisCoeff);
short maxAngleNormalized = maxAngle * axisCoeff; short maxAngleNormalized = maxAngle * axisCoeff;

View file

@ -157,36 +157,36 @@ void lara_as_use_puzzle(ItemInfo* item, CollisionInfo* coll)
// PUSHABLE // PUSHABLE
// -------- // --------
// State: LS_PUSHABLE_PUSH (36) // State: LS_PUSHABLE_PUSH (36)
// Collision: lara_default_col() // Collision: lara_void_func()
void lara_as_pushable_push(ItemInfo* item, CollisionInfo* coll) void lara_as_pushable_push(ItemInfo* item, CollisionInfo* coll)
{ {
auto* lara = GetLaraInfo(item); auto& player = GetLaraInfo(*item);
lara->Control.Look.Mode = LookMode::None; player.Control.Look.Mode = LookMode::None;
coll->Setup.EnableObjectPush = false; coll->Setup.EnableObjectPush = false;
coll->Setup.EnableSpasm = false; coll->Setup.EnableSpasm = false;
Camera.targetAngle = ANGLE(35.0f); Camera.targetAngle = ANGLE(35.0f);
Camera.targetElevation = -ANGLE(25.0f); Camera.targetElevation = ANGLE(-25.0f);
Camera.flags = CF_FOLLOW_CENTER; Camera.flags = CF_FOLLOW_CENTER;
} }
// State: LS_PUSHABLE_PULL (37) // State: LS_PUSHABLE_PULL (37)
// Collision: lara_default_col() // Collision: lara_void_func()
void lara_as_pushable_pull(ItemInfo* item, CollisionInfo* coll) void lara_as_pushable_pull(ItemInfo* item, CollisionInfo* coll)
{ {
auto* lara = GetLaraInfo(item); auto& player = GetLaraInfo(*item);
lara->Control.Look.Mode = LookMode::None; player.Control.Look.Mode = LookMode::None;
coll->Setup.EnableObjectPush = false; coll->Setup.EnableObjectPush = false;
coll->Setup.EnableSpasm = false; coll->Setup.EnableSpasm = false;
Camera.targetAngle = ANGLE(35.0f); Camera.targetAngle = ANGLE(35.0f);
Camera.targetElevation = -ANGLE(25.0f); Camera.targetElevation = ANGLE(-25.0f);
Camera.flags = CF_FOLLOW_CENTER; Camera.flags = CF_FOLLOW_CENTER;
} }
// State: LS_PUSHABLE_GRAB (38) // State: LS_PUSHABLE_GRAB (38)
// Collision: lara_default_col() // Collision: lara_default_col()
void lara_as_pushable_grab(ItemInfo* item, CollisionInfo* coll) void lara_as_pushable_grab(ItemInfo* item, CollisionInfo* coll)
{ {
coll->Setup.EnableObjectPush = false; coll->Setup.EnableObjectPush = false;
@ -197,6 +197,20 @@ void lara_as_pushable_grab(ItemInfo* item, CollisionInfo* coll)
item->Animation.TargetState = LS_IDLE; item->Animation.TargetState = LS_IDLE;
} }
// State: LS_PUSHABLE_PUSH_EDGE_SLIP (190)
// Collision: lara_void_func()
void lara_as_pushable_edge_slip(ItemInfo* item, CollisionInfo* coll)
{
auto& player = GetLaraInfo(*item);
player.Control.Look.Mode = LookMode::None;
coll->Setup.EnableObjectPush = false;
coll->Setup.EnableSpasm = false;
Camera.targetAngle = ANGLE(35.0f);
Camera.targetElevation = ANGLE(-25.0f);
Camera.flags = CF_FOLLOW_CENTER;
}
// ------ // ------
// PULLEY // PULLEY
// ------ // ------

View file

@ -37,6 +37,7 @@ void lara_as_use_puzzle(ItemInfo* item, CollisionInfo* coll);
void lara_as_pushable_push(ItemInfo* item, CollisionInfo* coll); void lara_as_pushable_push(ItemInfo* item, CollisionInfo* coll);
void lara_as_pushable_pull(ItemInfo* item, CollisionInfo* coll); void lara_as_pushable_pull(ItemInfo* item, CollisionInfo* coll);
void lara_as_pushable_grab(ItemInfo* item, CollisionInfo* coll); void lara_as_pushable_grab(ItemInfo* item, CollisionInfo* coll);
void lara_as_pushable_edge_slip(ItemInfo* item, CollisionInfo* coll);
// ------ // ------
// PULLEY // PULLEY

View file

@ -1569,7 +1569,7 @@ void HandleProjectile(ItemInfo& projectile, ItemInfo& emitter, const Vector3i& p
hasHit = hasHitNotByEmitter = doShatter = true; hasHit = hasHitNotByEmitter = doShatter = true;
doExplosion = isExplosive; doExplosion = isExplosive;
if (StaticObjects[meshPtr->staticNumber].shatterType == SHT_NONE) if (StaticObjects[meshPtr->staticNumber].shatterType == ShatterType::None)
continue; continue;
meshPtr->HitPoints -= damage; meshPtr->HitPoints -= damage;
@ -1625,7 +1625,7 @@ void HandleProjectile(ItemInfo& projectile, ItemInfo& emitter, const Vector3i& p
if (type != ProjectileType::FlashGrenade && currentObject.damageType != DamageMode::None) if (type != ProjectileType::FlashGrenade && currentObject.damageType != DamageMode::None)
DoExplosiveDamage(emitter, *itemPtr, projectile, damage); DoExplosiveDamage(emitter, *itemPtr, projectile, damage);
} }
else if (currentObject.damageType == DamageMode::AnyWeapon) else if (currentObject.damageType == DamageMode::Any)
{ {
if (type == ProjectileType::Poison) if (type == ProjectileType::Poison)
{ {

View file

@ -208,10 +208,10 @@ enum LaraState
LS_CRAWL_TURN_180 = 172, LS_CRAWL_TURN_180 = 172,
LS_TURN_180 = 173, LS_TURN_180 = 173,
// 174-188 reserved for "true" ladders. -- Sezz 2023.04.16 // 174-188 reserved for "true" ladders. DO NOT add anything here. -- Sezz 2023.04.16
LS_REMOVE_PUZZLE = 189, LS_REMOVE_PUZZLE = 189,
LS_RESERVED_PUSHABLE_STATE = 190, LS_PUSHABLE_EDGE_SLIP = 190,
LS_SPRINT_SLIDE = 191, LS_SPRINT_SLIDE = 191,
NUM_LARA_STATES NUM_LARA_STATES
@ -346,8 +346,8 @@ enum LaraAnim
LA_ONWATER_DIVE = 119, // Tread water > underwater LA_ONWATER_DIVE = 119, // Tread water > underwater
LA_PUSHABLE_GRAB = 120, // Grab pushable object (looped) LA_PUSHABLE_GRAB = 120, // Grab pushable object (looped)
LA_PUSHABLE_RELEASE = 121, // Release pushable object LA_PUSHABLE_RELEASE = 121, // Release pushable object
LA_PUSHABLE_PULL = 122, // Pull pushable object (looped) LA_PUSHABLE_OBJECT_PULL = 122, // Pull pushable object (looped)
LA_PUSHABLE_PUSH = 123, // Push pushable object (looped) LA_PUSHABLE_OBJECT_PUSH = 123, // Push pushable object (looped)
LA_UNDERWATER_DEATH = 124, // Drowning death LA_UNDERWATER_DEATH = 124, // Drowning death
LA_STAND_HIT_FRONT = 125, // Jerk back standing from damage LA_STAND_HIT_FRONT = 125, // Jerk back standing from damage
LA_STAND_HIT_BACK = 126, // Jerk forward standing from damage LA_STAND_HIT_BACK = 126, // Jerk forward standing from damage
@ -578,9 +578,9 @@ enum LaraAnim
LA_PULLEY_PULL = 340, // Pull pulley LA_PULLEY_PULL = 340, // Pull pulley
LA_PULLEY_RELEASE = 341, // Pull pulley > stand LA_PULLEY_RELEASE = 341, // Pull pulley > stand
LA_POLE_TO_STAND = 342, // Pole > stand LA_POLE_TO_STAND = 342, // Pole > stand
LA_POLE_TURN_CLOCKWISE_CONTINUE_UNUSED = 343, // TODO: remove. LA_PUSHABLE_BLOCK_PULL = 343, // Pull pushable block
LA_POLE_TURN_CLOCKWISE_END = 344, // Rotate clockwise on pole (2/2) LA_POLE_TURN_CLOCKWISE_END = 344, // Rotate clockwise on pole (2/2)
LA_POLE_TURN_COUNTER_CLOCKWISE_CONTINUE_UNUSED = 345, // TODO: remove. LA_PUSHABLE_BLOCK_PUSH = 345, // Push pushable block
LA_POLE_TURN_COUNTER_CLOCKWISE_END = 346, // Rotate counter-clockwise on pole (2/2) LA_POLE_TURN_COUNTER_CLOCKWISE_END = 346, // Rotate counter-clockwise on pole (2/2)
LA_TURNSWITCH_PUSH_CLOCKWISE_START = 347, // Push turnswitch clockwise (1/3) LA_TURNSWITCH_PUSH_CLOCKWISE_START = 347, // Push turnswitch clockwise (1/3)
LA_TURNSWITCH_PUSH_CLOCKWISE_CONTINUE = 348, // Push turnswitch clockwise (2/3) LA_TURNSWITCH_PUSH_CLOCKWISE_CONTINUE = 348, // Push turnswitch clockwise (2/3)
@ -598,14 +598,14 @@ enum LaraAnim
LA_SHIMMY_LEFT_CORNER_INNER_45 = 360, // Shimmy around inner left corner (45) LA_SHIMMY_LEFT_CORNER_INNER_45 = 360, // Shimmy around inner left corner (45)
LA_SHIMMY_RIGHT_CORNER_INNER_90 = 361, // Shimmy around inner right corner (90) LA_SHIMMY_RIGHT_CORNER_INNER_90 = 361, // Shimmy around inner right corner (90)
LA_SHIMMY_RIGHT_CORNER_INNER_45 = 362, // Shimmy around inner right corner (45) LA_SHIMMY_RIGHT_CORNER_INNER_45 = 362, // Shimmy around inner right corner (45)
LA_LADDER_LEFT_CORNER_OUTER_START = 363, // Ladder around outer left corner (1/2) LA_LADDER_LEFT_CORNER_OUTER_START = 363, // Ladder around outer left corner
LA_LADDER_LEFT_CORNER_OUTER_END = 364, // Ladder around outer left corner (2/2) LA_PUSHABLE_OBJECT_PUSH_EDGE_SLIP = 364,
LA_LADDER_RIGHT_CORNER_OUTER_START = 365, // Ladder around outer right corner (1/2) LA_LADDER_RIGHT_CORNER_OUTER_START = 365, // Ladder around outer right corner
LA_LADDER_RIGHT_CORNER_OUTER_END = 366, // Ladder around outer right corner (2/2) LA_PUSHABLE_BLOCK_PUSH_EDGE_SLIP = 366,
LA_LADDER_LEFT_CORNER_INNER_START = 367, // Ladder around inner left corner (1/2) LA_LADDER_LEFT_CORNER_INNER_START = 367, // Ladder around inner left corner
LA_LADDER_LEFT_CORNER_INNER_END = 368, // Ladder around inner left corner (2/2) LA_LADDER_LEFT_CORNER_INNER_END = 368, // TODO: Remove.
LA_LADDER_RIGHT_CORNER_INNER_START = 369, // Ladder around inner right corner (1/2) LA_LADDER_RIGHT_CORNER_INNER_START = 369, // Ladder around inner right corner
LA_LADDER_RIGHT_CORNER_INNER_END = 370, // Ladder around inner right corner (2/2) LA_LADDER_RIGHT_CORNER_INNER_END = 370, // TODO: Remove.
LA_JUMP_UP_TO_ROPE_START = 371, // Jump up > rope idle (1/2) LA_JUMP_UP_TO_ROPE_START = 371, // Jump up > rope idle (1/2)
LA_TRAIN_OVERBOARD_DEATH = 372, // Train overboard death LA_TRAIN_OVERBOARD_DEATH = 372, // Train overboard death
LA_JUMP_UP_TO_ROPE_END = 373, // Jump up > rope idle (2/2) LA_JUMP_UP_TO_ROPE_END = 373, // Jump up > rope idle (2/2)
@ -654,9 +654,8 @@ enum LaraAnim
LA_JUMPSWITCH_PULL = 414, // Pull jumpswitch LA_JUMPSWITCH_PULL = 414, // Pull jumpswitch
LA_UNDERWATER_CEILING_SWITCH_PULL = 415, // Pull underwater ceiling switch LA_UNDERWATER_CEILING_SWITCH_PULL = 415, // Pull underwater ceiling switch
LA_UNDERWATER_DOOR_OPEN = 416, // Open underwater_door LA_UNDERWATER_DOOR_OPEN = 416, // Open underwater_door
LA_PUSHABLE_PUSH_TO_STAND = 417, // Push pushable object (not looped) > stand LA_PUSHABLE_OBJECT_PUSH_TO_STAND = 417, // Push pushable object (not looped) > stand
LA_PUSHABLE_PULL_TO_STAND = 418, // Pull pushable object (not looped) > stand LA_PUSHABLE_OBJECT_PULL_TO_STAND = 418, // Pull pushable object (not looped) > stand
// TODO: add TR1-3 push/pull anims.
LA_CROWBAR_PRY_WALL_FAST = 419, // Pry item off wall quickly LA_CROWBAR_PRY_WALL_FAST = 419, // Pry item off wall quickly
LA_CROWBAR_USE_ON_FLOOR = 420, // Use crowbar to activate floor lever LA_CROWBAR_USE_ON_FLOOR = 420, // Use crowbar to activate floor lever
LA_CRAWL_JUMP_FLIP_DOWN = 421, // Roll jump down from crawl LA_CRAWL_JUMP_FLIP_DOWN = 421, // Roll jump down from crawl
@ -817,14 +816,13 @@ enum LaraAnim
LA_LEDGE_JUMP_BACK_START = 567, LA_LEDGE_JUMP_BACK_START = 567,
LA_LEDGE_JUMP_BACK_END = 568, LA_LEDGE_JUMP_BACK_END = 568,
// 569-598 reserved for "true" ladders. -- Sezz 2023.04.16 // 569-598 reserved for "true" ladders. DO NOT add anything here. -- Sezz 2023.04.16
NUM_LARA_ANIMS NUM_LARA_ANIMS
// TRASHED ANIMS (please reuse slots before going any higher and remove entries from this list as you go): // TRASHED ANIMS (reuse slots before going any higher and remove entries from this list when you do):
// 280, // 280,
// 343, 345, // 368, 370,
// 364, 366, 368, 370,
}; };
enum LaraExtraAnim enum LaraExtraAnim

View file

@ -43,8 +43,8 @@ bool TestValidLedge(ItemInfo* item, CollisionInfo* coll, bool ignoreHeadroom, bo
int y = item->Pose.Position.y - coll->Setup.Height; int y = item->Pose.Position.y - coll->Setup.Height;
// Get frontal collision data // Get frontal collision data
auto frontLeft = GetCollision(item->Pose.Position.x + xl, y, item->Pose.Position.z + zl, GetRoom(item->Location, item->Pose.Position.x, y, item->Pose.Position.z).roomNumber); auto frontLeft = GetCollision(item->Pose.Position.x + xl, y, item->Pose.Position.z + zl, GetRoom(item->Location, Vector3i(item->Pose.Position.x, y, item->Pose.Position.z)).roomNumber);
auto frontRight = GetCollision(item->Pose.Position.x + xr, y, item->Pose.Position.z + zr, GetRoom(item->Location, item->Pose.Position.x, y, item->Pose.Position.z).roomNumber); auto frontRight = GetCollision(item->Pose.Position.x + xr, y, item->Pose.Position.z + zr, GetRoom(item->Location, Vector3i(item->Pose.Position.x, y, item->Pose.Position.z)).roomNumber);
// If any of the frontal collision results intersects item bounds, return false, because there is material intersection. // If any of the frontal collision results intersects item bounds, return false, because there is material intersection.
// This check helps to filter out cases when Lara is formally facing corner but ledge check returns true because probe distance is fixed. // This check helps to filter out cases when Lara is formally facing corner but ledge check returns true because probe distance is fixed.
@ -63,8 +63,8 @@ bool TestValidLedge(ItemInfo* item, CollisionInfo* coll, bool ignoreHeadroom, bo
int zf = phd_cos(coll->NearestLedgeAngle) * (coll->Setup.Radius * 1.2f); int zf = phd_cos(coll->NearestLedgeAngle) * (coll->Setup.Radius * 1.2f);
// Get floor heights at both points // Get floor heights at both points
auto left = GetCollision(item->Pose.Position.x + xf + xl, y, item->Pose.Position.z + zf + zl, GetRoom(item->Location, item->Pose.Position.x, y, item->Pose.Position.z).roomNumber).Position.Floor; auto left = GetCollision(item->Pose.Position.x + xf + xl, y, item->Pose.Position.z + zf + zl, GetRoom(item->Location, Vector3i(item->Pose.Position.x, y, item->Pose.Position.z)).roomNumber).Position.Floor;
auto right = GetCollision(item->Pose.Position.x + xf + xr, y, item->Pose.Position.z + zf + zr, GetRoom(item->Location, item->Pose.Position.x, y, item->Pose.Position.z).roomNumber).Position.Floor; auto right = GetCollision(item->Pose.Position.x + xf + xr, y, item->Pose.Position.z + zf + zr, GetRoom(item->Location, Vector3i(item->Pose.Position.x, y, item->Pose.Position.z)).roomNumber).Position.Floor;
// If specified, limit vertical search zone only to nearest height // If specified, limit vertical search zone only to nearest height
if (heightLimit && (abs(left - y) > CLICK(0.5f) || abs(right - y) > CLICK(0.5f))) if (heightLimit && (abs(left - y) > CLICK(0.5f) || abs(right - y) > CLICK(0.5f)))
@ -926,12 +926,25 @@ bool TestLaraWaterClimbOut(ItemInfo* item, CollisionInfo* coll)
return false; return false;
int frontFloor = coll->Front.Floor + LARA_HEIGHT_TREAD; int frontFloor = coll->Front.Floor + LARA_HEIGHT_TREAD;
if (frontFloor <= -CLICK(2) || if (coll->Front.Bridge == NO_ITEM &&
frontFloor > CLICK(1.25f) - 4) (frontFloor <= -CLICK(2) ||
frontFloor > CLICK(1.25f) - 4))
{ {
return false; return false;
} }
//Do extra check if it's a bridge collider:
if (coll->Front.Bridge != NO_ITEM)
{
auto distancePointLara = GetBridgeBorder(coll->Front.Bridge, false) - LaraItem->Pose.Position.y;
frontFloor = distancePointLara - 128;
if (frontFloor <= -CLICK(2) ||
frontFloor > CLICK(1.25f) - 4)
{
return false;
}
}
if (!TestValidLedge(item, coll)) if (!TestValidLedge(item, coll))
return false; return false;
@ -980,7 +993,11 @@ bool TestLaraWaterClimbOut(ItemInfo* item, CollisionInfo* coll)
SetAnimation(item, LA_ONWATER_TO_STAND_0_STEP); SetAnimation(item, LA_ONWATER_TO_STAND_0_STEP);
} }
UpdateLaraRoom(item, -LARA_HEIGHT / 2); if (coll->Front.Bridge == NO_ITEM)
UpdateLaraRoom(item, -LARA_HEIGHT / 2);
else
UpdateLaraRoom(item, -LARA_HEIGHT);
SnapItemToLedge(item, coll, 1.7f); SnapItemToLedge(item, coll, 1.7f);
item->Pose.Position.y += frontFloor - 5; item->Pose.Position.y += frontFloor - 5;

View file

@ -34,6 +34,87 @@ using namespace TEN::Entities::Switches;
ObjectHandler Objects; ObjectHandler Objects;
StaticInfo StaticObjects[MAX_STATICS]; StaticInfo StaticObjects[MAX_STATICS];
void ObjectHandler::Initialize()
{
std::memset(_objects, 0, sizeof(ObjectInfo) * GAME_OBJECT_ID::ID_NUMBER_OBJECTS);
}
bool ObjectHandler::CheckID(GAME_OBJECT_ID objectID, bool isSilent)
{
if (objectID == GAME_OBJECT_ID::ID_NO_OBJECT || objectID >= GAME_OBJECT_ID::ID_NUMBER_OBJECTS)
{
if (!isSilent)
{
TENLog(
"Attempted to access unavailable slot ID (" + std::to_string(objectID) + "). " +
"Check if last accessed item exists in level.", LogLevel::Warning, LogConfig::Debug);
}
return false;
}
return true;
}
ObjectInfo& ObjectHandler::operator [](int objectID)
{
if (CheckID((GAME_OBJECT_ID)objectID))
return _objects[objectID];
return GetFirstAvailableObject();
}
ObjectInfo& ObjectHandler::GetFirstAvailableObject()
{
for (int i = 0; i < ID_NUMBER_OBJECTS; i++)
{
if (_objects[i].loaded)
return _objects[i];
}
return _objects[0];
}
// NOTE: JointRotationFlags allows bones to be rotated with CreatureJoint().
void ObjectInfo::SetBoneRotationFlags(int boneID, int flags)
{
g_Level.Bones[boneIndex + (boneID * 4)] |= flags;
}
// NOTE: Use if object is alive, but not intelligent, to set up blood effects.
void ObjectInfo::SetHitEffect(bool isSolid, bool isAlive)
{
// Avoid some objects such as ID_SAS_DYING having None.
if (isAlive)
{
hitEffect = HitEffect::Blood;
return;
}
if (intelligent)
{
if (isSolid && HitPoints > 0)
{
hitEffect = HitEffect::Richochet;
}
else if ((damageType != DamageMode::Any && HitPoints > 0) || HitPoints == NOT_TARGETABLE)
{
hitEffect = HitEffect::Smoke;
}
else if (damageType == DamageMode::Any && HitPoints > 0)
{
hitEffect = HitEffect::Blood;
}
}
else if (isSolid && HitPoints <= 0)
{
hitEffect = HitEffect::Richochet;
}
else
{
hitEffect = HitEffect::None;
}
}
void InitializeGameFlags() void InitializeGameFlags()
{ {
@ -101,7 +182,7 @@ void InitializeObjects()
obj->waterCreature = false; obj->waterCreature = false;
obj->nonLot = false; obj->nonLot = false;
obj->usingDrawAnimatingItem = true; obj->usingDrawAnimatingItem = true;
obj->damageType = DamageMode::AnyWeapon; obj->damageType = DamageMode::Any;
obj->LotType = LotType::Basic; obj->LotType = LotType::Basic;
obj->meshSwapSlot = NO_ITEM; obj->meshSwapSlot = NO_ITEM;
obj->isPickup = false; obj->isPickup = false;

View file

@ -9,6 +9,24 @@ struct CollisionInfo;
struct ItemInfo; struct ItemInfo;
constexpr auto DEFAULT_RADIUS = 10; constexpr auto DEFAULT_RADIUS = 10;
constexpr auto GRAVITY = 6.0f;
constexpr auto SWAMP_GRAVITY = GRAVITY / 3.0f;
constexpr auto MAX_STATICS = 1000;
enum JointRotationFlags
{
ROT_X = (1 << 2),
ROT_Y = (1 << 3),
ROT_Z = (1 << 4)
};
// Unused.
enum ShatterFlags
{
NoCollision = (1 << 0),
Shatterable = (1 << 1)
};
// Custom LOT definition for Creature. Used in InitializeSlot() in lot.cpp. // Custom LOT definition for Creature. Used in InitializeSlot() in lot.cpp.
enum class LotType enum class LotType
@ -27,35 +45,27 @@ enum class LotType
SnowmobileGun // Only 1 block vault allowed and 4 block drop max. SnowmobileGun // Only 1 block vault allowed and 4 block drop max.
}; };
enum JointRotationFlags
{
ROT_X = (1 << 2),
ROT_Y = (1 << 3),
ROT_Z = (1 << 4)
};
enum class HitEffect enum class HitEffect
{ {
None, None,
Blood, Blood,
Smoke, Smoke,
Richochet, Richochet,
Special, Special
Max
}; };
enum class DamageMode enum class DamageMode
{ {
AnyWeapon, None,
ExplosivesOnly, Any,
None Explosion
}; };
enum ShatterType enum class ShatterType
{ {
SHT_NONE, None,
SHT_FRAGMENT, Fragment,
SHT_EXPLODE Explode
}; };
struct ObjectInfo struct ObjectInfo
@ -99,98 +109,23 @@ struct ObjectInfo
std::function<int(short itemNumber)> floorBorder; std::function<int(short itemNumber)> floorBorder;
std::function<int(short itemNumber)> ceilingBorder; std::function<int(short itemNumber)> ceilingBorder;
// NOTE: ROT_X/Y/Z allows bones to be rotated with CreatureJoint(). void SetBoneRotationFlags(int boneID, int flags);
void SetBoneRotationFlags(int boneNumber, int flags) void SetHitEffect(bool isSolid = false, bool isAlive = false);
{
g_Level.Bones[boneIndex + (boneNumber * 4)] |= flags;
}
// Set up hit effect for object based on its value.
// Use if object is alive but not intelligent to set up blood effects.
void SetupHitEffect(bool isSolid = false, bool isAlive = false)
{
// Avoid some objects such as ID_SAS_DYING having None.
if (isAlive)
{
hitEffect = HitEffect::Blood;
return;
}
if (intelligent)
{
if (isSolid && HitPoints > 0)
{
hitEffect = HitEffect::Richochet;
}
else if ((damageType != DamageMode::AnyWeapon && HitPoints > 0) || HitPoints == NOT_TARGETABLE)
{
hitEffect = HitEffect::Smoke;
}
else if (damageType == DamageMode::AnyWeapon && HitPoints > 0)
{
hitEffect = HitEffect::Blood;
}
}
else if (isSolid && HitPoints <= 0)
{
hitEffect = HitEffect::Richochet;
}
else
{
hitEffect = HitEffect::None;
}
}
}; };
class ObjectHandler class ObjectHandler
{ {
private: private:
ObjectInfo Objects[ID_NUMBER_OBJECTS]; ObjectInfo _objects[ID_NUMBER_OBJECTS];
ObjectInfo& GetFirstAvailableObject() public:
{ void Initialize();
for (int i = 0; i < ID_NUMBER_OBJECTS; i++) bool CheckID(GAME_OBJECT_ID objectID, bool isSilent = false);
{
if (Objects[i].loaded)
return Objects[i];
}
return Objects[0]; ObjectInfo& operator [](int objectID);
}
public: private:
void Initialize() ObjectInfo& GetFirstAvailableObject();
{
std::memset(Objects, 0, sizeof(ObjectInfo) * GAME_OBJECT_ID::ID_NUMBER_OBJECTS);
}
bool CheckID(int index, bool isSilent = false)
{
if (index == GAME_OBJECT_ID::ID_NO_OBJECT || index >= GAME_OBJECT_ID::ID_NUMBER_OBJECTS)
{
if (!isSilent)
{
TENLog("Attempted to access unavailable slot ID (" + std::to_string(index) + "). " +
"Check if last accessed item exists in level.", LogLevel::Warning, LogConfig::Debug);
}
return false;
}
return true;
}
ObjectInfo& operator[](int index)
{
if (CheckID(index))
{
return Objects[index];
}
else
{
return GetFirstAvailableObject();
}
}
}; };
struct StaticInfo struct StaticInfo
@ -199,16 +134,10 @@ struct StaticInfo
int flags; int flags;
GameBoundingBox visibilityBox; GameBoundingBox visibilityBox;
GameBoundingBox collisionBox; GameBoundingBox collisionBox;
int shatterType; ShatterType shatterType;
int shatterSound; int shatterSound;
}; };
constexpr auto MAX_STATICS = 1000;
constexpr auto SF_NO_COLLISION = 0x01;
constexpr auto SF_SHATTERABLE = 0x02;
constexpr auto GRAVITY = 6.0f;
constexpr auto SWAMP_GRAVITY = GRAVITY / 3.0f;
extern ObjectHandler Objects; extern ObjectHandler Objects;
extern StaticInfo StaticObjects[MAX_STATICS]; extern StaticInfo StaticObjects[MAX_STATICS];

View file

@ -91,9 +91,7 @@ void DoThumbstickCamera()
if (Camera.laraNode == -1 && Camera.target.ToVector3i() == OldCam.target) if (Camera.laraNode == -1 && Camera.target.ToVector3i() == OldCam.target)
{ {
auto axisCoeff = Vector2( const auto& axisCoeff = AxisMap[(int)InputAxis::Camera];
AxisMap[InputAxis::CameraHorizontal],
AxisMap[InputAxis::CameraVertical]);
if (abs(axisCoeff.x) > EPSILON && abs(Camera.targetAngle) == 0) if (abs(axisCoeff.x) > EPSILON && abs(Camera.targetAngle) == 0)
Camera.targetAngle = ANGLE(VERTICAL_CONSTRAINT_ANGLE * axisCoeff.x); Camera.targetAngle = ANGLE(VERTICAL_CONSTRAINT_ANGLE * axisCoeff.x);

View file

@ -1877,7 +1877,9 @@ void DoObjectCollision(ItemInfo* laraItem, CollisionInfo* coll)
} }
} }
else if (coll->Setup.EnableObjectPush) else if (coll->Setup.EnableObjectPush)
{
ItemPushItem(item, laraItem, coll, false, 1); ItemPushItem(item, laraItem, coll, false, 1);
}
} }
} }
@ -1904,15 +1906,19 @@ void DoObjectCollision(ItemInfo* laraItem, CollisionInfo* coll)
// HACK: Shatter statics only by non-harmless vehicles. // HACK: Shatter statics only by non-harmless vehicles.
if (!doPlayerCollision && if (!doPlayerCollision &&
!harmless && abs(laraItem->Animation.Velocity.z) > VEHICLE_COLLISION_TERMINAL_VELOCITY && !harmless && abs(laraItem->Animation.Velocity.z) > VEHICLE_COLLISION_TERMINAL_VELOCITY &&
StaticObjects[mesh.staticNumber].shatterType != SHT_NONE) StaticObjects[mesh.staticNumber].shatterType != ShatterType::None)
{ {
SoundEffect(GetShatterSound(mesh.staticNumber), &mesh.pos); SoundEffect(GetShatterSound(mesh.staticNumber), &mesh.pos);
ShatterObject(nullptr, &mesh, -128, laraItem->RoomNumber, 0); ShatterObject(nullptr, &mesh, -128, laraItem->RoomNumber, 0);
} }
else if (coll->Setup.EnableObjectPush) else if (coll->Setup.EnableObjectPush)
{
ItemPushStatic(laraItem, mesh, coll); ItemPushStatic(laraItem, mesh, coll);
}
else else
{
continue; continue;
}
} }
} }

View file

@ -78,9 +78,13 @@ int FindGridShift(int x, int z)
return 0; return 0;
if ((z / BLOCK(1)) <= (x / BLOCK(1))) if ((z / BLOCK(1)) <= (x / BLOCK(1)))
{
return (-1 - (x & WALL_MASK)); return (-1 - (x & WALL_MASK));
}
else else
{
return ((BLOCK(1) + 1) - (x & WALL_MASK)); return ((BLOCK(1) + 1) - (x & WALL_MASK));
}
} }
// Test if the axis-aligned bounding box collides with geometry at all. // Test if the axis-aligned bounding box collides with geometry at all.
@ -142,7 +146,7 @@ CollisionResult GetCollision(ItemInfo* item, short headingAngle, float forward,
ROOM_VECTOR{ GetFloor(item->Pose.Position.x, item->Pose.Position.y, item->Pose.Position.z, &tempRoomNumber)->Room, item->Pose.Position.y }; ROOM_VECTOR{ GetFloor(item->Pose.Position.x, item->Pose.Position.y, item->Pose.Position.z, &tempRoomNumber)->Room, item->Pose.Position.y };
auto point = Geometry::TranslatePoint(item->Pose.Position, headingAngle, forward, down, right); auto point = Geometry::TranslatePoint(item->Pose.Position, headingAngle, forward, down, right);
int adjacentRoomNumber = GetRoom(location, item->Pose.Position.x, point.y, item->Pose.Position.z).roomNumber; int adjacentRoomNumber = GetRoom(location, Vector3i(item->Pose.Position.x, point.y, item->Pose.Position.z)).roomNumber;
return GetCollision(point.x, point.y, point.z, adjacentRoomNumber); return GetCollision(point.x, point.y, point.z, adjacentRoomNumber);
} }
@ -153,7 +157,7 @@ CollisionResult GetCollision(const Vector3i& pos, int roomNumber, short headingA
auto location = ROOM_VECTOR{ GetFloor(pos.x, pos.y, pos.z, &tempRoomNumber)->Room, pos.y }; auto location = ROOM_VECTOR{ GetFloor(pos.x, pos.y, pos.z, &tempRoomNumber)->Room, pos.y };
auto point = Geometry::TranslatePoint(pos, headingAngle, forward, down, right); auto point = Geometry::TranslatePoint(pos, headingAngle, forward, down, right);
int adjacentRoomNumber = GetRoom(location, pos.x, point.y, pos.z).roomNumber; int adjacentRoomNumber = GetRoom(location, Vector3i(pos.x, point.y, pos.z)).roomNumber;
return GetCollision(point.x, point.y, point.z, adjacentRoomNumber); return GetCollision(point.x, point.y, point.z, adjacentRoomNumber);
} }
@ -163,7 +167,7 @@ CollisionResult GetCollision(const Vector3i& pos, int roomNumber, const EulerAng
short tempRoomNumber = roomNumber; short tempRoomNumber = roomNumber;
auto location = ROOM_VECTOR{ GetFloor(pos.x, pos.y, pos.z, &tempRoomNumber)->Room, pos.y }; auto location = ROOM_VECTOR{ GetFloor(pos.x, pos.y, pos.z, &tempRoomNumber)->Room, pos.y };
int adjacentRoomNumber = GetRoom(location, pos.x, point.y, pos.z).roomNumber; int adjacentRoomNumber = GetRoom(location, Vector3i(pos.x, point.y, pos.z)).roomNumber;
return GetCollision(point.x, point.y, point.z, adjacentRoomNumber); return GetCollision(point.x, point.y, point.z, adjacentRoomNumber);
} }
@ -213,9 +217,9 @@ CollisionResult GetCollision(FloorInfo* floor, int x, int y, int z)
result.Position.Ceiling = GetCeilingHeight(ROOM_VECTOR{ floor->Room, y }, x, z).value_or(NO_HEIGHT); result.Position.Ceiling = GetCeilingHeight(ROOM_VECTOR{ floor->Room, y }, x, z).value_or(NO_HEIGHT);
// Probe bottom collision block through portals. // Probe bottom collision block through portals.
while (floor->GetRoomNumberBelow(x, y, z).value_or(NO_ROOM) != NO_ROOM) while (floor->GetRoomNumberBelow(Vector3i(x, y, z)).value_or(NO_ROOM) != NO_ROOM)
{ {
auto* room = &g_Level.Rooms[floor->GetRoomNumberBelow(x, y, z).value_or(floor->Room)]; auto* room = &g_Level.Rooms[floor->GetRoomNumberBelow(Vector3i(x, y, z)).value_or(floor->Room)];
floor = GetSector(room, x - room->x, z - room->z); floor = GetSector(room, x - room->x, z - room->z);
} }
@ -229,7 +233,7 @@ CollisionResult GetCollision(FloorInfo* floor, int x, int y, int z)
// Split, bridge and slope data. // Split, bridge and slope data.
result.Position.DiagonalStep = floor->IsSurfaceDiagonalStep(true); result.Position.DiagonalStep = floor->IsSurfaceDiagonalStep(true);
result.Position.SplitAngle = floor->FloorCollision.SplitAngle; result.Position.SplitAngle = floor->FloorCollision.SplitAngle;
result.Position.Bridge = result.BottomBlock->GetInsideBridgeItemNumber(x, result.Position.Floor, z, true, false); result.Position.Bridge = result.BottomBlock->GetInsideBridgeItemNumber(Vector3i(x, result.Position.Floor, z), true, false);
result.Position.FloorSlope = result.Position.Bridge < 0 && (abs(result.FloorTilt.x) >= 3 || (abs(result.FloorTilt.y) >= 3)); result.Position.FloorSlope = result.Position.Bridge < 0 && (abs(result.FloorTilt.x) >= 3 || (abs(result.FloorTilt.y) >= 3));
result.Position.CeilingSlope = abs(result.CeilingTilt.x) >= 4 || abs(result.CeilingTilt.y) >= 4; // TODO: Fix on bridges placed beneath ceiling slopes. @Sezz 2022.01.29 result.Position.CeilingSlope = abs(result.CeilingTilt.x) >= 4 || abs(result.CeilingTilt.y) >= 4; // TODO: Fix on bridges placed beneath ceiling slopes. @Sezz 2022.01.29
@ -279,12 +283,29 @@ static void SetSectorAttribs(CollisionPosition& sectorAttribs, const CollisionSe
} }
else if (collSetup.BlockMonkeySwingEdge) else if (collSetup.BlockMonkeySwingEdge)
{ {
auto monkeyPointColl = GetCollision(probePos.x, probePos.y + collSetup.Height, probePos.z, realRoomNumber); auto pointColl = GetCollision(probePos.x, probePos.y + collSetup.Height, probePos.z, realRoomNumber);
if (!monkeyPointColl.BottomBlock->Flags.Monkeyswing) if (!pointColl.BottomBlock->Flags.Monkeyswing)
sectorAttribs.Floor = MAX_HEIGHT; sectorAttribs.Floor = MAX_HEIGHT;
} }
} }
static void HandleDiagonalShift(ItemInfo& item, CollisionInfo& coll, const Vector3i& pos, CardinalDirection quadrant, bool isRightShift)
{
// Calculate angles.
int sign = isRightShift ? 1 : -1;
short perpSplitAngle = (ANGLE(90.0f) * quadrant) + (ANGLE(45.0f) * sign);
short deltaAngle = abs(Geometry::GetShortestAngle(coll.Setup.ForwardAngle, perpSplitAngle));
// HACK: Force slight push left to avoid getting stuck.
float alpha = 1.0f - ((float)deltaAngle / (float)ANGLE(90.0f));
if (alpha >= 0.5f)
TranslateItem(&item, perpSplitAngle, item.Animation.Velocity.z * alpha);
// Set shift.
coll.Shift.Position.x += coll.Setup.PrevPosition.x - pos.x;
coll.Shift.Position.z += coll.Setup.PrevPosition.z - pos.z;
};
void GetCollisionInfo(CollisionInfo* coll, ItemInfo* item, const Vector3i& offset, bool resetRoom) void GetCollisionInfo(CollisionInfo* coll, ItemInfo* item, const Vector3i& offset, bool resetRoom)
{ {
// Player collision has several more precise checks for bridge collisions. // Player collision has several more precise checks for bridge collisions.
@ -311,7 +332,7 @@ void GetCollisionInfo(CollisionInfo* coll, ItemInfo* item, const Vector3i& offse
// If unconstrained mode is specified, don't use quadrant. // If unconstrained mode is specified, don't use quadrant.
switch ((coll->Setup.Mode == CollisionProbeMode::Quadrants) ? quadrant : -1) switch ((coll->Setup.Mode == CollisionProbeMode::Quadrants) ? quadrant : -1)
{ {
case 0: case NORTH:
xFront = phd_sin(coll->Setup.ForwardAngle) * coll->Setup.Radius; xFront = phd_sin(coll->Setup.ForwardAngle) * coll->Setup.Radius;
zFront = coll->Setup.Radius; zFront = coll->Setup.Radius;
xLeft = -coll->Setup.Radius; xLeft = -coll->Setup.Radius;
@ -320,7 +341,7 @@ void GetCollisionInfo(CollisionInfo* coll, ItemInfo* item, const Vector3i& offse
zRight = coll->Setup.Radius; zRight = coll->Setup.Radius;
break; break;
case 1: case EAST:
xFront = coll->Setup.Radius; xFront = coll->Setup.Radius;
zFront = phd_cos(coll->Setup.ForwardAngle) * coll->Setup.Radius; zFront = phd_cos(coll->Setup.ForwardAngle) * coll->Setup.Radius;
xLeft = coll->Setup.Radius; xLeft = coll->Setup.Radius;
@ -329,7 +350,7 @@ void GetCollisionInfo(CollisionInfo* coll, ItemInfo* item, const Vector3i& offse
zRight = -coll->Setup.Radius; zRight = -coll->Setup.Radius;
break; break;
case 2: case SOUTH:
xFront = phd_sin(coll->Setup.ForwardAngle) * coll->Setup.Radius; xFront = phd_sin(coll->Setup.ForwardAngle) * coll->Setup.Radius;
zFront = -coll->Setup.Radius; zFront = -coll->Setup.Radius;
xLeft = coll->Setup.Radius; xLeft = coll->Setup.Radius;
@ -338,7 +359,7 @@ void GetCollisionInfo(CollisionInfo* coll, ItemInfo* item, const Vector3i& offse
zRight = -coll->Setup.Radius; zRight = -coll->Setup.Radius;
break; break;
case 3: case WEST:
xFront = -coll->Setup.Radius; xFront = -coll->Setup.Radius;
zFront = phd_cos(coll->Setup.ForwardAngle) * coll->Setup.Radius; zFront = phd_cos(coll->Setup.ForwardAngle) * coll->Setup.Radius;
xLeft = -coll->Setup.Radius; xLeft = -coll->Setup.Radius;
@ -347,7 +368,7 @@ void GetCollisionInfo(CollisionInfo* coll, ItemInfo* item, const Vector3i& offse
zRight = coll->Setup.Radius; zRight = coll->Setup.Radius;
break; break;
// No valid quadrant; return true probe offsets from object rotation. // No valid quadrant; get true probe offsets from object rotation.
default: default:
xFront = phd_sin(coll->Setup.ForwardAngle) * coll->Setup.Radius; xFront = phd_sin(coll->Setup.ForwardAngle) * coll->Setup.Radius;
zFront = phd_cos(coll->Setup.ForwardAngle) * coll->Setup.Radius; zFront = phd_cos(coll->Setup.ForwardAngle) * coll->Setup.Radius;
@ -392,10 +413,10 @@ void GetCollisionInfo(CollisionInfo* coll, ItemInfo* item, const Vector3i& offse
if (doPlayerCollision) if (doPlayerCollision)
{ {
tfLocation = GetRoom(item->Location, probePos.x, probePos.y, probePos.z); tfLocation = GetRoom(item->Location, probePos);
height = GetFloorHeight(tfLocation, probePos.x, probePos.z).value_or(NO_HEIGHT); height = GetFloorHeight(tfLocation, probePos.x, probePos.z).value_or(NO_HEIGHT);
tcLocation = GetRoom(item->Location, probePos.x, probePos.y - item->Animation.Velocity.y, probePos.z); tcLocation = GetRoom(item->Location, Vector3i(probePos.x, probePos.y - item->Animation.Velocity.y, probePos.z));
ceiling = GetCeilingHeight(tcLocation, probePos.x, probePos.z).value_or(NO_HEIGHT); ceiling = GetCeilingHeight(tcLocation, probePos.x, probePos.z).value_or(NO_HEIGHT);
} }
else else
@ -435,10 +456,10 @@ void GetCollisionInfo(CollisionInfo* coll, ItemInfo* item, const Vector3i& offse
topRoomNumber = realRoomNumber; topRoomNumber = realRoomNumber;
} }
tfLocation = GetRoom(tfLocation, probePos.x, probePos.y, probePos.z); tfLocation = GetRoom(tfLocation, probePos);
height = GetFloorHeight(tfLocation, probePos.x, probePos.z).value_or(NO_HEIGHT); height = GetFloorHeight(tfLocation, probePos.x, probePos.z).value_or(NO_HEIGHT);
tcLocation = GetRoom(tcLocation, probePos.x, probePos.y - item->Animation.Velocity.y, probePos.z); tcLocation = GetRoom(tcLocation, Vector3i(probePos.x, probePos.y - item->Animation.Velocity.y, probePos.z));
ceiling = GetCeilingHeight(tcLocation, probePos.x, probePos.z).value_or(NO_HEIGHT); ceiling = GetCeilingHeight(tcLocation, probePos.x, probePos.z).value_or(NO_HEIGHT);
} }
else else
@ -459,7 +480,7 @@ void GetCollisionInfo(CollisionInfo* coll, ItemInfo* item, const Vector3i& offse
if (doPlayerCollision) if (doPlayerCollision)
{ {
tfLocation = GetRoom(tfLocation, probePos.x + xFront, probePos.y, probePos.z + zFront); tfLocation = GetRoom(tfLocation, Vector3i(probePos.x + xFront, probePos.y, probePos.z + zFront));
height = GetFloorHeight(tfLocation, probePos.x + xFront, probePos.z + zFront).value_or(NO_HEIGHT); height = GetFloorHeight(tfLocation, probePos.x + xFront, probePos.z + zFront).value_or(NO_HEIGHT);
} }
else else
@ -483,10 +504,10 @@ void GetCollisionInfo(CollisionInfo* coll, ItemInfo* item, const Vector3i& offse
if (doPlayerCollision) if (doPlayerCollision)
{ {
lrfLocation = GetRoom(item->Location, probePos.x, probePos.y, probePos.z); lrfLocation = GetRoom(item->Location, probePos);
height = GetFloorHeight(lrfLocation, probePos.x, probePos.z).value_or(NO_HEIGHT); height = GetFloorHeight(lrfLocation, probePos.x, probePos.z).value_or(NO_HEIGHT);
lrcLocation = GetRoom(item->Location, probePos.x, probePos.y - item->Animation.Velocity.y, probePos.z); lrcLocation = GetRoom(item->Location, Vector3i(probePos.x, probePos.y - item->Animation.Velocity.y, probePos.z));
ceiling = GetCeilingHeight(lrcLocation, probePos.x, probePos.z).value_or(NO_HEIGHT); ceiling = GetCeilingHeight(lrcLocation, probePos.x, probePos.z).value_or(NO_HEIGHT);
} }
else else
@ -513,10 +534,10 @@ void GetCollisionInfo(CollisionInfo* coll, ItemInfo* item, const Vector3i& offse
if (doPlayerCollision) if (doPlayerCollision)
{ {
tfLocation = GetRoom(tfLocation, probePos.x, probePos.y, probePos.z); tfLocation = GetRoom(tfLocation, probePos);
height = GetFloorHeight(tfLocation, probePos.x, probePos.z).value_or(NO_HEIGHT); height = GetFloorHeight(tfLocation, probePos.x, probePos.z).value_or(NO_HEIGHT);
tcLocation = GetRoom(tcLocation, probePos.x, probePos.y - item->Animation.Velocity.y, probePos.z); tcLocation = GetRoom(tcLocation, Vector3i(probePos.x, probePos.y - item->Animation.Velocity.y, probePos.z));
ceiling = GetCeilingHeight(tcLocation, probePos.x, probePos.z).value_or(NO_HEIGHT); ceiling = GetCeilingHeight(tcLocation, probePos.x, probePos.z).value_or(NO_HEIGHT);
} }
else else
@ -548,10 +569,10 @@ void GetCollisionInfo(CollisionInfo* coll, ItemInfo* item, const Vector3i& offse
if (doPlayerCollision) if (doPlayerCollision)
{ {
lrfLocation = GetRoom(item->Location, probePos.x, probePos.y, probePos.z); lrfLocation = GetRoom(item->Location, probePos);
height = GetFloorHeight(lrfLocation, probePos.x, probePos.z).value_or(NO_HEIGHT); height = GetFloorHeight(lrfLocation, probePos.x, probePos.z).value_or(NO_HEIGHT);
lrcLocation = GetRoom(item->Location, probePos.x, probePos.y - item->Animation.Velocity.y, probePos.z); lrcLocation = GetRoom(item->Location, Vector3i(probePos.x, probePos.y - item->Animation.Velocity.y, probePos.z));
ceiling = GetCeilingHeight(lrcLocation, probePos.x, probePos.z).value_or(NO_HEIGHT); ceiling = GetCeilingHeight(lrcLocation, probePos.x, probePos.z).value_or(NO_HEIGHT);
} }
else else
@ -578,10 +599,10 @@ void GetCollisionInfo(CollisionInfo* coll, ItemInfo* item, const Vector3i& offse
if (doPlayerCollision) if (doPlayerCollision)
{ {
tfLocation = GetRoom(tfLocation, probePos.x, probePos.y, probePos.z); tfLocation = GetRoom(tfLocation, probePos);
height = GetFloorHeight(tfLocation, probePos.x, probePos.z).value_or(NO_HEIGHT); height = GetFloorHeight(tfLocation, probePos.x, probePos.z).value_or(NO_HEIGHT);
tcLocation = GetRoom(tcLocation, probePos.x, probePos.y - item->Animation.Velocity.y, probePos.z); tcLocation = GetRoom(tcLocation, Vector3i(probePos.x, probePos.y - item->Animation.Velocity.y, probePos.z));
ceiling = GetCeilingHeight(tcLocation, probePos.x, probePos.z).value_or(NO_HEIGHT); ceiling = GetCeilingHeight(tcLocation, probePos.x, probePos.z).value_or(NO_HEIGHT);
} }
else else
@ -629,6 +650,7 @@ void GetCollisionInfo(CollisionInfo* coll, ItemInfo* item, const Vector3i& offse
coll->CollisionType = CT_TOP; coll->CollisionType = CT_TOP;
} }
// Shift away from front wall.
if (coll->Front.Floor > coll->Setup.LowerFloorBound || if (coll->Front.Floor > coll->Setup.LowerFloorBound ||
coll->Front.Floor < coll->Setup.UpperFloorBound || coll->Front.Floor < coll->Setup.UpperFloorBound ||
coll->Front.Ceiling > coll->Setup.LowerCeilingBound || coll->Front.Ceiling > coll->Setup.LowerCeilingBound ||
@ -644,20 +666,18 @@ void GetCollisionInfo(CollisionInfo* coll, ItemInfo* item, const Vector3i& offse
{ {
switch (quadrant) switch (quadrant)
{ {
case 0: case NORTH:
case 2: case SOUTH:
coll->Shift.Position.x += coll->Setup.PrevPosition.x - entityPos.x;
coll->Shift.Position.z += FindGridShift(entityPos.z + zFront, entityPos.z); coll->Shift.Position.z += FindGridShift(entityPos.z + zFront, entityPos.z);
break; break;
case 1: case EAST:
case 3: case WEST:
coll->Shift.Position.x += FindGridShift(entityPos.x + xFront, entityPos.x); coll->Shift.Position.x += FindGridShift(entityPos.x + xFront, entityPos.x);
coll->Shift.Position.z += coll->Setup.PrevPosition.z - entityPos.z;
break; break;
} }
} }
coll->CollisionType = (coll->CollisionType == CT_TOP) ? CT_TOP_FRONT : CT_FRONT; coll->CollisionType = (coll->CollisionType == CT_TOP) ? CT_TOP_FRONT : CT_FRONT;
return; return;
} }
@ -670,6 +690,7 @@ void GetCollisionInfo(CollisionInfo* coll, ItemInfo* item, const Vector3i& offse
return; return;
} }
// Shift away from left wall.
if (coll->MiddleLeft.Floor > coll->Setup.LowerFloorBound || if (coll->MiddleLeft.Floor > coll->Setup.LowerFloorBound ||
coll->MiddleLeft.Floor < coll->Setup.UpperFloorBound || coll->MiddleLeft.Floor < coll->Setup.UpperFloorBound ||
coll->MiddleLeft.Ceiling > coll->Setup.LowerCeilingBound || coll->MiddleLeft.Ceiling > coll->Setup.LowerCeilingBound ||
@ -678,23 +699,19 @@ void GetCollisionInfo(CollisionInfo* coll, ItemInfo* item, const Vector3i& offse
{ {
if (coll->TriangleAtLeft() && !coll->MiddleLeft.FloorSlope) if (coll->TriangleAtLeft() && !coll->MiddleLeft.FloorSlope)
{ {
// HACK: Force slight push-out to the left side to avoid stucking HandleDiagonalShift(*item, *coll, entityPos, (CardinalDirection)quadrant, true);
TranslateItem(item, coll->Setup.ForwardAngle + ANGLE(8.0f), item->Animation.Velocity.z);
coll->Shift.Position.x += coll->Setup.PrevPosition.x - entityPos.x;
coll->Shift.Position.z += coll->Setup.PrevPosition.z - entityPos.z;
} }
else else
{ {
switch (quadrant) switch (quadrant)
{ {
case 0: case NORTH:
case 2: case SOUTH:
coll->Shift.Position.x += FindGridShift(entityPos.x + xLeft, entityPos.x + xFront); coll->Shift.Position.x += FindGridShift(entityPos.x + xLeft, entityPos.x + xFront);
break; break;
case 1: case EAST:
case 3: case WEST:
coll->Shift.Position.z += FindGridShift(entityPos.z + zLeft, entityPos.z + zFront); coll->Shift.Position.z += FindGridShift(entityPos.z + zLeft, entityPos.z + zFront);
break; break;
} }
@ -702,7 +719,8 @@ void GetCollisionInfo(CollisionInfo* coll, ItemInfo* item, const Vector3i& offse
if (coll->DiagonalStepAtLeft()) if (coll->DiagonalStepAtLeft())
{ {
int quarter = (unsigned short)(coll->Setup.ForwardAngle) / ANGLE(90.0f); // NOTE: Different from quadrant! // NOTE: Different from quadrant.
int quarter = (unsigned short)coll->Setup.ForwardAngle / ANGLE(90.0f);
quarter %= 2; quarter %= 2;
if (coll->MiddleLeft.HasFlippedDiagonalSplit()) if (coll->MiddleLeft.HasFlippedDiagonalSplit())
@ -724,6 +742,7 @@ void GetCollisionInfo(CollisionInfo* coll, ItemInfo* item, const Vector3i& offse
return; return;
} }
// Shift away from right wall.
if (coll->MiddleRight.Floor > coll->Setup.LowerFloorBound || if (coll->MiddleRight.Floor > coll->Setup.LowerFloorBound ||
coll->MiddleRight.Floor < coll->Setup.UpperFloorBound || coll->MiddleRight.Floor < coll->Setup.UpperFloorBound ||
coll->MiddleRight.Ceiling > coll->Setup.LowerCeilingBound || coll->MiddleRight.Ceiling > coll->Setup.LowerCeilingBound ||
@ -732,23 +751,19 @@ void GetCollisionInfo(CollisionInfo* coll, ItemInfo* item, const Vector3i& offse
{ {
if (coll->TriangleAtRight() && !coll->MiddleRight.FloorSlope) if (coll->TriangleAtRight() && !coll->MiddleRight.FloorSlope)
{ {
// HACK: Force slight push out to the right side to avoid getting stuck. HandleDiagonalShift(*item, *coll, entityPos, (CardinalDirection)quadrant, false);
TranslateItem(item, coll->Setup.ForwardAngle - ANGLE(8.0f), item->Animation.Velocity.z);
coll->Shift.Position.x += coll->Setup.PrevPosition.x - entityPos.x;
coll->Shift.Position.z += coll->Setup.PrevPosition.z - entityPos.z;
} }
else else
{ {
switch (quadrant) switch (quadrant)
{ {
case 0: case NORTH:
case 2: case SOUTH:
coll->Shift.Position.x += FindGridShift(entityPos.x + xRight, entityPos.x + xFront); coll->Shift.Position.x += FindGridShift(entityPos.x + xRight, entityPos.x + xFront);
break; break;
case 1: case EAST:
case 3: case WEST:
coll->Shift.Position.z += FindGridShift(entityPos.z + zRight, entityPos.z + zFront); coll->Shift.Position.z += FindGridShift(entityPos.z + zRight, entityPos.z + zFront);
break; break;
} }
@ -903,7 +918,7 @@ short GetNearestLedgeAngle(ItemInfo* item, CollisionInfo* coll, float& distance)
float maxZ = ceil(ffpZ / BLOCK(1)) * BLOCK(1) + 1.0f; float maxZ = ceil(ffpZ / BLOCK(1)) * BLOCK(1) + 1.0f;
// Get front floor block // Get front floor block
auto room = GetRoom(item->Location, ffpX, y, ffpZ).roomNumber; auto room = GetRoom(item->Location, Vector3i(ffpX, y, ffpZ)).roomNumber;
auto block = GetCollision(ffpX, y, ffpZ, room).Block; auto block = GetCollision(ffpX, y, ffpZ, room).Block;
// Get front floor surface heights // Get front floor surface heights
@ -921,7 +936,7 @@ short GetNearestLedgeAngle(ItemInfo* item, CollisionInfo* coll, float& distance)
int height = useCeilingLedge ? ceilingHeight : floorHeight; int height = useCeilingLedge ? ceilingHeight : floorHeight;
// Determine if there is a bridge in front. // Determine if there is a bridge in front.
auto bridge = block->GetInsideBridgeItemNumber(ffpX, height, ffpZ, true, y == height); auto bridge = block->GetInsideBridgeItemNumber(Vector3i(ffpX, height, ffpZ), true, y == height);
// Determine floor probe offset. // Determine floor probe offset.
// This must be slightly in front of own coll radius so no bridge misfires occur. // This must be slightly in front of own coll radius so no bridge misfires occur.
@ -933,7 +948,7 @@ short GetNearestLedgeAngle(ItemInfo* item, CollisionInfo* coll, float& distance)
// g_Renderer.AddDebugSphere(Vector3(fpX, y, fpZ), 16, Vector4(0, 1, 0, 1), RendererDebugPage::CollisionStats); // g_Renderer.AddDebugSphere(Vector3(fpX, y, fpZ), 16, Vector4(0, 1, 0, 1), RendererDebugPage::CollisionStats);
// Get true room number and block, based on derived height // Get true room number and block, based on derived height
room = GetRoom(item->Location, fpX, height, fpZ).roomNumber; room = GetRoom(item->Location, Vector3i(fpX, height, fpZ)).roomNumber;
block = GetCollision(fpX, height, fpZ, room).Block; block = GetCollision(fpX, height, fpZ, room).Block;
// We don't need actual corner heights to build planes, so just use normalized value here. // We don't need actual corner heights to build planes, so just use normalized value here.
@ -1132,7 +1147,7 @@ short GetNearestLedgeAngle(ItemInfo* item, CollisionInfo* coll, float& distance)
FloorInfo* GetFloor(int x, int y, int z, short* roomNumber) FloorInfo* GetFloor(int x, int y, int z, short* roomNumber)
{ {
const auto location = GetRoom(ROOM_VECTOR{ *roomNumber, y }, x, y, z); const auto location = GetRoom(ROOM_VECTOR{ *roomNumber, y }, Vector3i(x, y, z));
*roomNumber = location.roomNumber; *roomNumber = location.roomNumber;
return &GetFloor(*roomNumber, x, z); return &GetFloor(*roomNumber, x, z);
} }
@ -1164,33 +1179,6 @@ int GetDistanceToFloor(int itemNumber, bool precise)
return (minHeight + item->Pose.Position.y - height); return (minHeight + item->Pose.Position.y - height);
} }
void AlterFloorHeight(ItemInfo* item, int height)
{
if (abs(height))
{
if (height >= 0)
height++;
else
height--;
}
short roomNumber = item->RoomNumber;
FloorInfo* floor = GetFloor(item->Pose.Position.x, item->Pose.Position.y, item->Pose.Position.z, &roomNumber);
FloorInfo* ceiling = GetFloor(item->Pose.Position.x, height + item->Pose.Position.y - BLOCK(1), item->Pose.Position.z, &roomNumber);
floor->FloorCollision.Planes[0].z += height;
floor->FloorCollision.Planes[1].z += height;
auto* box = &g_Level.Boxes[floor->Box];
if (box->flags & BLOCKABLE)
{
if (height >= 0)
box->flags &= ~BLOCKED;
else
box->flags |= BLOCKED;
}
}
int GetWaterSurface(int x, int y, int z, short roomNumber) int GetWaterSurface(int x, int y, int z, short roomNumber)
{ {
auto* room = &g_Level.Rooms[roomNumber]; auto* room = &g_Level.Rooms[roomNumber];
@ -1198,9 +1186,9 @@ int GetWaterSurface(int x, int y, int z, short roomNumber)
if (TestEnvironment(ENV_FLAG_WATER, room)) if (TestEnvironment(ENV_FLAG_WATER, room))
{ {
while (floor->GetRoomNumberAbove(x, y, z).value_or(NO_ROOM) != NO_ROOM) while (floor->GetRoomNumberAbove(Vector3i(x, y, z)).value_or(NO_ROOM) != NO_ROOM)
{ {
room = &g_Level.Rooms[floor->GetRoomNumberAbove(x, y, z).value_or(floor->Room)]; room = &g_Level.Rooms[floor->GetRoomNumberAbove(Vector3i(x, y, z)).value_or(floor->Room)];
if (!TestEnvironment(ENV_FLAG_WATER, room)) if (!TestEnvironment(ENV_FLAG_WATER, room))
return (floor->GetSurfaceHeight(x, z, false)); return (floor->GetSurfaceHeight(x, z, false));
@ -1211,9 +1199,9 @@ int GetWaterSurface(int x, int y, int z, short roomNumber)
} }
else else
{ {
while (floor->GetRoomNumberBelow(x, y, z).value_or(NO_ROOM) != NO_ROOM) while (floor->GetRoomNumberBelow(Vector3i(x, y, z)).value_or(NO_ROOM) != NO_ROOM)
{ {
room = &g_Level.Rooms[floor->GetRoomNumberBelow(x, y, z).value_or(floor->Room)]; room = &g_Level.Rooms[floor->GetRoomNumberBelow(Vector3i(x, y, z)).value_or(floor->Room)];
if (TestEnvironment(ENV_FLAG_WATER, room)) if (TestEnvironment(ENV_FLAG_WATER, room))
return (floor->GetSurfaceHeight(x, z, true)); return (floor->GetSurfaceHeight(x, z, true));
@ -1274,9 +1262,9 @@ int GetWaterDepth(int x, int y, int z, short roomNumber)
if (TestEnvironment(ENV_FLAG_WATER, room) || if (TestEnvironment(ENV_FLAG_WATER, room) ||
TestEnvironment(ENV_FLAG_SWAMP, room)) TestEnvironment(ENV_FLAG_SWAMP, room))
{ {
while (floor->GetRoomNumberAbove(x, y, z).value_or(NO_ROOM) != NO_ROOM) while (floor->GetRoomNumberAbove(Vector3i(x, y, z)).value_or(NO_ROOM) != NO_ROOM)
{ {
room = &g_Level.Rooms[floor->GetRoomNumberAbove(x, y, z).value_or(floor->Room)]; room = &g_Level.Rooms[floor->GetRoomNumberAbove(Vector3i(x, y, z)).value_or(floor->Room)];
if (!TestEnvironment(ENV_FLAG_WATER, room) && if (!TestEnvironment(ENV_FLAG_WATER, room) &&
!TestEnvironment(ENV_FLAG_SWAMP, room)) !TestEnvironment(ENV_FLAG_SWAMP, room))
@ -1293,9 +1281,9 @@ int GetWaterDepth(int x, int y, int z, short roomNumber)
} }
else else
{ {
while (floor->GetRoomNumberBelow(x, y, z).value_or(NO_ROOM) != NO_ROOM) while (floor->GetRoomNumberBelow(Vector3i(x, y, z)).value_or(NO_ROOM) != NO_ROOM)
{ {
room = &g_Level.Rooms[floor->GetRoomNumberBelow(x, y, z).value_or(floor->Room)]; room = &g_Level.Rooms[floor->GetRoomNumberBelow(Vector3i(x, y, z)).value_or(floor->Room)];
if (TestEnvironment(ENV_FLAG_WATER, room) || if (TestEnvironment(ENV_FLAG_WATER, room) ||
TestEnvironment(ENV_FLAG_SWAMP, room)) TestEnvironment(ENV_FLAG_SWAMP, room))
@ -1365,9 +1353,9 @@ int GetWaterHeight(int x, int y, int z, short roomNumber)
if (TestEnvironment(ENV_FLAG_WATER, room) || if (TestEnvironment(ENV_FLAG_WATER, room) ||
TestEnvironment(ENV_FLAG_SWAMP, room)) TestEnvironment(ENV_FLAG_SWAMP, room))
{ {
while (floor->GetRoomNumberAbove(x, y, z).value_or(NO_ROOM) != NO_ROOM) while (floor->GetRoomNumberAbove(Vector3i(x, y, z)).value_or(NO_ROOM) != NO_ROOM)
{ {
auto* room = &g_Level.Rooms[floor->GetRoomNumberAbove(x, y, z).value_or(floor->Room)]; auto* room = &g_Level.Rooms[floor->GetRoomNumberAbove(Vector3i(x, y, z)).value_or(floor->Room)];
if (!TestEnvironment(ENV_FLAG_WATER, room) && if (!TestEnvironment(ENV_FLAG_WATER, room) &&
!TestEnvironment(ENV_FLAG_SWAMP, room)) !TestEnvironment(ENV_FLAG_SWAMP, room))
@ -1376,13 +1364,13 @@ int GetWaterHeight(int x, int y, int z, short roomNumber)
floor = GetSector(room, x - room->x, z - room->z); floor = GetSector(room, x - room->x, z - room->z);
} }
return GetCollision(floor, x, y, z).Block->GetSurfaceHeight(x, y, z, false); return GetCollision(floor, x, y, z).Block->GetSurfaceHeight(Vector3i(x, y, z), false);
} }
else if (floor->GetRoomNumberBelow(x, y, z).value_or(NO_ROOM) != NO_ROOM) else if (floor->GetRoomNumberBelow(Vector3i(x, y, z)).value_or(NO_ROOM) != NO_ROOM)
{ {
while (floor->GetRoomNumberBelow(x, y, z).value_or(NO_ROOM) != NO_ROOM) while (floor->GetRoomNumberBelow(Vector3i(x, y, z)).value_or(NO_ROOM) != NO_ROOM)
{ {
auto* room2 = &g_Level.Rooms[floor->GetRoomNumberBelow(x, y, z).value_or(floor->Room)]; auto* room2 = &g_Level.Rooms[floor->GetRoomNumberBelow(Vector3i(x, y, z)).value_or(floor->Room)];
if (TestEnvironment(ENV_FLAG_WATER, room2) || if (TestEnvironment(ENV_FLAG_WATER, room2) ||
TestEnvironment(ENV_FLAG_SWAMP, room2)) TestEnvironment(ENV_FLAG_SWAMP, room2))
@ -1391,7 +1379,7 @@ int GetWaterHeight(int x, int y, int z, short roomNumber)
floor = GetSector(room2, x - room2->x, z - room2->z); floor = GetSector(room2, x - room2->x, z - room2->z);
} }
return GetCollision(floor, x, y, z).Block->GetSurfaceHeight(x, y, z, true); return GetCollision(floor, x, y, z).Block->GetSurfaceHeight(Vector3i(x, y, z), true);
} }
return NO_HEIGHT; return NO_HEIGHT;

View file

@ -145,7 +145,6 @@ FloorInfo* GetFloor(int x, int y, int z, short* roomNumber);
int GetFloorHeight(FloorInfo* floor, int x, int y, int z); int GetFloorHeight(FloorInfo* floor, int x, int y, int z);
int GetCeiling(FloorInfo* floor, int x, int y, int z); int GetCeiling(FloorInfo* floor, int x, int y, int z);
int GetDistanceToFloor(int itemNumber, bool precise = true); int GetDistanceToFloor(int itemNumber, bool precise = true);
void AlterFloorHeight(ItemInfo* item, int height);
int GetWaterSurface(int x, int y, int z, short roomNumber); int GetWaterSurface(int x, int y, int z, short roomNumber);
int GetWaterSurface(ItemInfo* item); int GetWaterSurface(ItemInfo* item);

View file

@ -8,9 +8,11 @@
#include "Math/Math.h" #include "Math/Math.h"
#include "Renderer/Renderer11.h" #include "Renderer/Renderer11.h"
#include "Specific/level.h" #include "Specific/level.h"
#include "Specific/trutils.h"
using namespace TEN::Collision::Floordata; using namespace TEN::Collision::Floordata;
using namespace TEN::Math; using namespace TEN::Math;
using namespace TEN::Utils;
int FloorInfo::GetSurfacePlaneIndex(int x, int z, bool isFloor) const int FloorInfo::GetSurfacePlaneIndex(int x, int z, bool isFloor) const
{ {
@ -86,25 +88,25 @@ std::optional<int> FloorInfo::GetRoomNumberBelow(int x, int z) const
return GetRoomNumberBelow(planeIndex); return GetRoomNumberBelow(planeIndex);
} }
std::optional<int> FloorInfo::GetRoomNumberBelow(int x, int y, int z) const std::optional<int> FloorInfo::GetRoomNumberBelow(const Vector3i& pos) const
{ {
// Get surface heights. // Get surface heights.
int floorHeight = GetSurfaceHeight(x, z, true); int floorHeight = GetSurfaceHeight(pos.x, pos.z, true);
int ceilingHeight = GetSurfaceHeight(x, z, false); int ceilingHeight = GetSurfaceHeight(pos.x, pos.z, false);
// Loop through bridges. // Loop through bridges.
for (const int& i : BridgeItemNumbers) for (int i : BridgeItemNumbers)
{ {
const auto& bridgeItem = g_Level.Items[i]; const auto& bridgeItem = g_Level.Items[i];
const auto& bridgeObject = Objects[bridgeItem.ObjectNumber]; const auto& bridgeObject = Objects[bridgeItem.ObjectNumber];
// Get bridge floor height. // Get bridge floor height.
auto bridgeFloorHeight = bridgeObject.floor(i, x, y, z); auto bridgeFloorHeight = bridgeObject.floor(i, pos.x, pos.y, pos.z);
if (!bridgeFloorHeight.has_value()) if (!bridgeFloorHeight.has_value())
continue; continue;
// Assess relation of bridge to collision block. // Assess relation of bridge to collision block.
if (*bridgeFloorHeight >= y && // Below input height bound. if (*bridgeFloorHeight >= pos.y && // Below input height bound.
*bridgeFloorHeight <= floorHeight && // Within floor bound. *bridgeFloorHeight <= floorHeight && // Within floor bound.
*bridgeFloorHeight >= ceilingHeight) // Within ceiling bound. *bridgeFloorHeight >= ceilingHeight) // Within ceiling bound.
{ {
@ -112,7 +114,7 @@ std::optional<int> FloorInfo::GetRoomNumberBelow(int x, int y, int z) const
} }
} }
return GetRoomNumberBelow(x, z); return GetRoomNumberBelow(pos.x, pos.z);
} }
std::optional<int> FloorInfo::GetRoomNumberAbove(int planeIndex) const std::optional<int> FloorInfo::GetRoomNumberAbove(int planeIndex) const
@ -127,25 +129,25 @@ std::optional<int> FloorInfo::GetRoomNumberAbove(int x, int z) const
return GetRoomNumberAbove(planeIndex); return GetRoomNumberAbove(planeIndex);
} }
std::optional<int> FloorInfo::GetRoomNumberAbove(int x, int y, int z) const std::optional<int> FloorInfo::GetRoomNumberAbove(const Vector3i& pos) const
{ {
// Get surface heights. // Get surface heights.
int floorHeight = GetSurfaceHeight(x, z, true); int floorHeight = GetSurfaceHeight(pos.x, pos.z, true);
int ceilingHeight = GetSurfaceHeight(x, z, false); int ceilingHeight = GetSurfaceHeight(pos.x, pos.z, false);
// Loop through bridges. // Loop through bridges.
for (const int& i : BridgeItemNumbers) for (int i : BridgeItemNumbers)
{ {
const auto& bridgeItem = g_Level.Items[i]; const auto& bridgeItem = g_Level.Items[i];
const auto& bridgeObject = Objects[bridgeItem.ObjectNumber]; const auto& bridgeObject = Objects[bridgeItem.ObjectNumber];
// Get bridge ceiling height. // Get bridge ceiling height.
auto bridgeCeilingHeight = bridgeObject.ceiling(i, x, y, z); auto bridgeCeilingHeight = bridgeObject.ceiling(i, pos.x, pos.y, pos.z);
if (!bridgeCeilingHeight.has_value()) if (!bridgeCeilingHeight.has_value())
continue; continue;
// Assess relation of bridge to collision block. // Assess relation of bridge to collision block.
if (*bridgeCeilingHeight <= y && // Above input height bound. if (*bridgeCeilingHeight <= pos.y && // Above input height bound.
*bridgeCeilingHeight <= floorHeight && // Within floor bound. *bridgeCeilingHeight <= floorHeight && // Within floor bound.
*bridgeCeilingHeight >= ceilingHeight) // Within ceiling bound. *bridgeCeilingHeight >= ceilingHeight) // Within ceiling bound.
{ {
@ -153,7 +155,7 @@ std::optional<int> FloorInfo::GetRoomNumberAbove(int x, int y, int z) const
} }
} }
return GetRoomNumberAbove(x, z); return GetRoomNumberAbove(pos.x, pos.z);
} }
std::optional<int> FloorInfo::GetRoomNumberAtSide() const std::optional<int> FloorInfo::GetRoomNumberAtSide() const
@ -177,27 +179,27 @@ int FloorInfo::GetSurfaceHeight(int x, int z, bool isFloor) const
plane.z); plane.z);
} }
int FloorInfo::GetSurfaceHeight(int x, int y, int z, bool isFloor) const int FloorInfo::GetSurfaceHeight(const Vector3i& pos, bool isFloor) const
{ {
// Get surface heights. // Get surface heights.
int floorHeight = GetSurfaceHeight(x, z, true); int floorHeight = GetSurfaceHeight(pos.x, pos.z, true);
int ceilingHeight = GetSurfaceHeight(x, z, false); int ceilingHeight = GetSurfaceHeight(pos.x, pos.z, false);
// Loop through bridges. // Loop through bridges.
for (const int& i : BridgeItemNumbers) for (int i : BridgeItemNumbers)
{ {
const auto& bridgeItem = g_Level.Items[i]; const auto& bridgeItem = g_Level.Items[i];
const auto& bridgeObject = Objects[bridgeItem.ObjectNumber]; const auto& bridgeObject = Objects[bridgeItem.ObjectNumber];
// Get bridge surface height. // Get bridge surface height.
auto bridgeSurfaceHeight = isFloor ? bridgeObject.floor(i, x, y, z) : bridgeObject.ceiling(i, x, y, z); auto bridgeSurfaceHeight = isFloor ? bridgeObject.floor(i, pos.x, pos.y, pos.z) : bridgeObject.ceiling(i, pos.x, pos.y, pos.z);
if (!bridgeSurfaceHeight.has_value()) if (!bridgeSurfaceHeight.has_value())
continue; continue;
// Assess relation of bridge to collision block. // Assess relation of bridge to collision block.
if (isFloor) if (isFloor)
{ {
if (*bridgeSurfaceHeight >= y && // Below input height bound. if (*bridgeSurfaceHeight >= pos.y && // Below input height bound.
*bridgeSurfaceHeight < floorHeight && // Within floor bound. *bridgeSurfaceHeight < floorHeight && // Within floor bound.
*bridgeSurfaceHeight >= ceilingHeight) // Within ceiling bound. *bridgeSurfaceHeight >= ceilingHeight) // Within ceiling bound.
{ {
@ -206,7 +208,7 @@ int FloorInfo::GetSurfaceHeight(int x, int y, int z, bool isFloor) const
} }
else else
{ {
if (*bridgeSurfaceHeight <= y && // Above input height bound. if (*bridgeSurfaceHeight <= pos.y && // Above input height bound.
*bridgeSurfaceHeight <= floorHeight && // Within floor bound. *bridgeSurfaceHeight <= floorHeight && // Within floor bound.
*bridgeSurfaceHeight > ceilingHeight) // Within ceiling bound. *bridgeSurfaceHeight > ceilingHeight) // Within ceiling bound.
{ {
@ -219,33 +221,33 @@ int FloorInfo::GetSurfaceHeight(int x, int y, int z, bool isFloor) const
return (isFloor ? floorHeight : ceilingHeight); return (isFloor ? floorHeight : ceilingHeight);
} }
int FloorInfo::GetBridgeSurfaceHeight(int x, int y, int z, bool isFloor) const int FloorInfo::GetBridgeSurfaceHeight(const Vector3i& pos, bool isFloor) const
{ {
// Loop through bridges. // Loop through bridges.
for (const int& i : BridgeItemNumbers) for (int i : BridgeItemNumbers)
{ {
const auto& bridgeItem = g_Level.Items[i]; const auto& bridgeItem = g_Level.Items[i];
const auto& bridgeObject = Objects[bridgeItem.ObjectNumber]; const auto& bridgeObject = Objects[bridgeItem.ObjectNumber];
// Get surface heights. // Get surface heights.
auto floorHeight = bridgeObject.floor(i, x, y, z); auto floorHeight = bridgeObject.floor(i, pos.x, pos.y, pos.z);
auto ceilingHeight = bridgeObject.ceiling(i, x, y, z); auto ceilingHeight = bridgeObject.ceiling(i, pos.x, pos.y, pos.z);
if (!floorHeight.has_value() || !ceilingHeight.has_value()) if (!floorHeight.has_value() || !ceilingHeight.has_value())
continue; continue;
// Assess relation of bridge to collision block. // Assess relation of bridge to collision block.
if (isFloor) if (isFloor)
{ {
if (y > *floorHeight && if (pos.y > *floorHeight &&
y <= *ceilingHeight) pos.y <= *ceilingHeight)
{ {
return *floorHeight; return *floorHeight;
} }
} }
else else
{ {
if (y >= *floorHeight && if (pos.y >= *floorHeight &&
y < *ceilingHeight) pos.y < *ceilingHeight)
{ {
return *ceilingHeight; return *ceilingHeight;
} }
@ -253,7 +255,7 @@ int FloorInfo::GetBridgeSurfaceHeight(int x, int y, int z, bool isFloor) const
} }
// Return bridge surface height. // Return bridge surface height.
return GetSurfaceHeight(x, y, z, isFloor); return GetSurfaceHeight(pos, isFloor);
} }
Vector2 FloorInfo::GetSurfaceSlope(int planeIndex, bool isFloor) const Vector2 FloorInfo::GetSurfaceSlope(int planeIndex, bool isFloor) const
@ -281,7 +283,7 @@ bool FloorInfo::IsWall(int x, int z) const
return IsWall(planeIndex); return IsWall(planeIndex);
} }
int FloorInfo::GetInsideBridgeItemNumber(int x, int y, int z, bool testFloorBorder, bool testCeilingBorder) const int FloorInfo::GetInsideBridgeItemNumber(const Vector3i& pos, bool testFloorBorder, bool testCeilingBorder) const
{ {
for (int itemNumber : BridgeItemNumbers) for (int itemNumber : BridgeItemNumbers)
{ {
@ -289,14 +291,19 @@ int FloorInfo::GetInsideBridgeItemNumber(int x, int y, int z, bool testFloorBord
const auto& bridgeObject = Objects[bridgeItem.ObjectNumber]; const auto& bridgeObject = Objects[bridgeItem.ObjectNumber];
// Get surface heights. // Get surface heights.
auto floorHeight = bridgeObject.floor(itemNumber, x, y, z); auto floorHeight = bridgeObject.floor(itemNumber, pos.x, pos.y, pos.z);
auto ceilingHeight = bridgeObject.ceiling(itemNumber, x, y, z); auto ceilingHeight = bridgeObject.ceiling(itemNumber, pos.x, pos.y, pos.z);
if (!floorHeight.has_value() || !ceilingHeight.has_value()) if (!floorHeight.has_value() || !ceilingHeight.has_value())
continue; continue;
if ((y > *floorHeight && y < *ceilingHeight) || if (pos.y > *floorHeight &&
(testFloorBorder && y == *floorHeight) || pos.y < *ceilingHeight)
(testCeilingBorder && y == *ceilingHeight)) {
return itemNumber;
}
if ((testFloorBorder && pos.y == *floorHeight) ||
(testCeilingBorder && pos.y == *ceilingHeight))
{ {
return itemNumber; return itemNumber;
} }
@ -361,10 +368,10 @@ namespace TEN::Collision::Floordata
return pos; return pos;
} }
FloorInfo& GetFloor(int roomNumber, const Vector2i& pos) FloorInfo& GetFloor(int roomNumber, const Vector2i& roomPos)
{ {
auto& room = g_Level.Rooms[roomNumber]; auto& room = g_Level.Rooms[roomNumber];
return room.floor[room.zSize * pos.x + pos.y]; return room.floor[(room.zSize * roomPos.x) + roomPos.y];
} }
FloorInfo& GetFloor(int roomNumber, int x, int z) FloorInfo& GetFloor(int roomNumber, int x, int z)
@ -433,7 +440,7 @@ namespace TEN::Collision::Floordata
auto* sectorPtr = &startSector; auto* sectorPtr = &startSector;
do do
{ {
pos.y = sectorPtr->GetBridgeSurfaceHeight(pos.x, pos.y, pos.z, true); pos.y = sectorPtr->GetBridgeSurfaceHeight(pos, true);
while (pos.y <= sectorPtr->GetSurfaceHeight(pos.x, pos.z, false)) while (pos.y <= sectorPtr->GetSurfaceHeight(pos.x, pos.z, false))
{ {
auto roomNumberAbove = sectorPtr->GetRoomNumberAbove(pos.x, pos.z); auto roomNumberAbove = sectorPtr->GetRoomNumberAbove(pos.x, pos.z);
@ -443,7 +450,7 @@ namespace TEN::Collision::Floordata
sectorPtr = &GetFloorSide(*roomNumberAbove, pos.x, pos.z, &roomNumber); sectorPtr = &GetFloorSide(*roomNumberAbove, pos.x, pos.z, &roomNumber);
} }
} }
while (sectorPtr->GetInsideBridgeItemNumber(pos.x, pos.y, pos.z, false, true) >= 0); while (sectorPtr->GetInsideBridgeItemNumber(pos, false, true) >= 0);
if (topRoomNumberPtr != nullptr) if (topRoomNumberPtr != nullptr)
*topRoomNumberPtr = roomNumber; *topRoomNumberPtr = roomNumber;
@ -463,7 +470,7 @@ namespace TEN::Collision::Floordata
auto* sectorPtr = &startSector; auto* sectorPtr = &startSector;
do do
{ {
pos.y = sectorPtr->GetBridgeSurfaceHeight(pos.x, pos.y, pos.z, false); pos.y = sectorPtr->GetBridgeSurfaceHeight(pos, false);
while (pos.y >= sectorPtr->GetSurfaceHeight(pos.x, pos.z, true)) while (pos.y >= sectorPtr->GetSurfaceHeight(pos.x, pos.z, true))
{ {
auto roomNumberBelow = sectorPtr->GetRoomNumberBelow(pos.x, pos.z); auto roomNumberBelow = sectorPtr->GetRoomNumberBelow(pos.x, pos.z);
@ -473,7 +480,7 @@ namespace TEN::Collision::Floordata
sectorPtr = &GetFloorSide(*roomNumberBelow, pos.x, pos.z, &roomNumber); sectorPtr = &GetFloorSide(*roomNumberBelow, pos.x, pos.z, &roomNumber);
} }
} }
while (sectorPtr->GetInsideBridgeItemNumber(pos.x, pos.y, pos.z, true, false) >= 0); while (sectorPtr->GetInsideBridgeItemNumber(pos, true, false) >= 0);
if (bottomRoomNumberPtr) if (bottomRoomNumberPtr)
*bottomRoomNumberPtr = roomNumber; *bottomRoomNumberPtr = roomNumber;
@ -509,16 +516,18 @@ namespace TEN::Collision::Floordata
direction = 1; direction = 1;
} }
else else
{
return std::nullopt; return std::nullopt;
}
} }
} }
const auto floorHeight = floor->GetSurfaceHeight(x, y, z, true); const auto floorHeight = floor->GetSurfaceHeight(Vector3i(x, y, z), true);
const auto ceilingHeight = floor->GetSurfaceHeight(x, y, z, false); const auto ceilingHeight = floor->GetSurfaceHeight(Vector3i(x, y, z), false);
y = std::clamp(y, std::min(ceilingHeight, floorHeight), std::max(ceilingHeight, floorHeight)); y = std::clamp(y, std::min(ceilingHeight, floorHeight), std::max(ceilingHeight, floorHeight));
if (floor->GetInsideBridgeItemNumber(x, y, z, y == ceilingHeight, y == floorHeight) >= 0) if (floor->GetInsideBridgeItemNumber(Vector3i(x, y, z), y == ceilingHeight, y == floorHeight) >= 0)
{ {
if (direction <= 0) if (direction <= 0)
{ {
@ -539,15 +548,15 @@ namespace TEN::Collision::Floordata
if (direction >= 0) if (direction >= 0)
{ {
auto roomBelow = floor->GetRoomNumberBelow(x, y, z); auto roomBelow = floor->GetRoomNumberBelow(Vector3i(x, y, z));
while (roomBelow) while (roomBelow)
{ {
floor = &GetFloorSide(*roomBelow, x, z); floor = &GetFloorSide(*roomBelow, x, z);
roomBelow = floor->GetRoomNumberBelow(x, y, z); roomBelow = floor->GetRoomNumberBelow(Vector3i(x, y, z));
} }
} }
return std::optional{floor->GetSurfaceHeight(x, y, z, true)}; return floor->GetSurfaceHeight(Vector3i(x, y, z), true);
} }
std::optional<int> GetCeilingHeight(const ROOM_VECTOR& location, int x, int z) std::optional<int> GetCeilingHeight(const ROOM_VECTOR& location, int x, int z)
@ -575,16 +584,18 @@ namespace TEN::Collision::Floordata
direction = -1; direction = -1;
} }
else else
{
return std::nullopt; return std::nullopt;
}
} }
} }
const auto floorHeight = floor->GetSurfaceHeight(x, y, z, true); const auto floorHeight = floor->GetSurfaceHeight(Vector3i(x, y, z), true);
const auto ceilingHeight = floor->GetSurfaceHeight(x, y, z, false); const auto ceilingHeight = floor->GetSurfaceHeight(Vector3i(x, y, z), false);
y = std::clamp(y, std::min(ceilingHeight, floorHeight), std::max(ceilingHeight, floorHeight)); y = std::clamp(y, std::min(ceilingHeight, floorHeight), std::max(ceilingHeight, floorHeight));
if (floor->GetInsideBridgeItemNumber(x, y, z, y == ceilingHeight, y == floorHeight) >= 0) if (floor->GetInsideBridgeItemNumber(Vector3i(x, y, z), y == ceilingHeight, y == floorHeight) >= 0)
{ {
if (direction >= 0) if (direction >= 0)
{ {
@ -605,148 +616,152 @@ namespace TEN::Collision::Floordata
if (direction <= 0) if (direction <= 0)
{ {
auto roomAbove = floor->GetRoomNumberAbove(x, y, z); auto roomAbove = floor->GetRoomNumberAbove(Vector3i(x, y, z));
while (roomAbove) while (roomAbove)
{ {
floor = &GetFloorSide(*roomAbove, x, z); floor = &GetFloorSide(*roomAbove, x, z);
roomAbove = floor->GetRoomNumberAbove(x, y, z); roomAbove = floor->GetRoomNumberAbove(Vector3i(x, y, z));
} }
} }
return std::optional{floor->GetSurfaceHeight(x, y, z, false)}; return floor->GetSurfaceHeight(Vector3i(x, y, z), false);
} }
std::optional<ROOM_VECTOR> GetBottomRoom(ROOM_VECTOR location, int x, int y, int z) std::optional<ROOM_VECTOR> GetBottomRoom(ROOM_VECTOR location, const Vector3i& pos)
{ {
auto floor = &GetFloorSide(location.roomNumber, x, z, &location.roomNumber); auto floor = &GetFloorSide(location.roomNumber, pos.x, pos.z, &location.roomNumber);
if (floor->IsWall(x, z)) if (floor->IsWall(pos.x, pos.z))
{ {
floor = &GetBottomFloor(location.roomNumber, x, z, &location.roomNumber); floor = &GetBottomFloor(location.roomNumber, pos.x, pos.z, &location.roomNumber);
if (floor->IsWall(x, z)) if (floor->IsWall(pos.x, pos.z))
return std::nullopt; return std::nullopt;
location.yNumber = floor->GetSurfaceHeight(x, z, false); location.yNumber = floor->GetSurfaceHeight(pos.x, pos.z, false);
} }
auto floorHeight = floor->GetSurfaceHeight(x, location.yNumber, z, true); auto floorHeight = floor->GetSurfaceHeight(Vector3i(pos.x, location.yNumber, pos.z), true);
auto ceilingHeight = floor->GetSurfaceHeight(x, location.yNumber, z, false); auto ceilingHeight = floor->GetSurfaceHeight(Vector3i(pos.x, location.yNumber, pos.z), false);
location.yNumber = std::clamp(location.yNumber, std::min(ceilingHeight, floorHeight), std::max(ceilingHeight, floorHeight)); location.yNumber = std::clamp(location.yNumber, std::min(ceilingHeight, floorHeight), std::max(ceilingHeight, floorHeight));
if (floor->GetInsideBridgeItemNumber(x, location.yNumber, z, location.yNumber == ceilingHeight, location.yNumber == floorHeight) >= 0) if (floor->GetInsideBridgeItemNumber(Vector3i(pos.x, location.yNumber, pos.z), location.yNumber == ceilingHeight, location.yNumber == floorHeight) >= 0)
{ {
const auto height = GetBottomHeight(*floor, Vector3i(x, location.yNumber, z), &location.roomNumber, &floor); const auto height = GetBottomHeight(*floor, Vector3i(pos.x, location.yNumber, pos.z), &location.roomNumber, &floor);
if (!height) if (!height)
return std::nullopt; return std::nullopt;
location.yNumber = *height; location.yNumber = *height;
} }
floorHeight = floor->GetSurfaceHeight(x, location.yNumber, z, true); floorHeight = floor->GetSurfaceHeight(Vector3i(pos.x, location.yNumber, pos.z), true);
ceilingHeight = floor->GetSurfaceHeight(x, location.yNumber, z, false); ceilingHeight = floor->GetSurfaceHeight(Vector3i(pos.x, location.yNumber, pos.z), false);
if (y < ceilingHeight && floor->GetRoomNumberAbove(x, location.yNumber, z)) if (pos.y < ceilingHeight && floor->GetRoomNumberAbove(Vector3i(pos.x, location.yNumber, pos.z)))
return std::nullopt; return std::nullopt;
if (y <= floorHeight)
if (pos.y <= floorHeight)
{ {
location.yNumber = std::max(y, ceilingHeight); location.yNumber = std::max(pos.y, ceilingHeight);
return std::optional{location}; return location;
} }
auto roomBelow = floor->GetRoomNumberBelow(x, location.yNumber, z); auto roomBelow = floor->GetRoomNumberBelow(Vector3i(pos.x, location.yNumber, pos.z));
while (roomBelow) while (roomBelow)
{ {
floor = &GetFloorSide(*roomBelow, x, z, &location.roomNumber); floor = &GetFloorSide(*roomBelow, pos.x, pos.z, &location.roomNumber);
location.yNumber = floor->GetSurfaceHeight(x, z, false); location.yNumber = floor->GetSurfaceHeight(pos.x, pos.z, false);
floorHeight = floor->GetSurfaceHeight(x, location.yNumber, z, true); floorHeight = floor->GetSurfaceHeight(Vector3i(pos.x, location.yNumber, pos.z), true);
ceilingHeight = floor->GetSurfaceHeight(x, location.yNumber, z, false); ceilingHeight = floor->GetSurfaceHeight(Vector3i(pos.x, location.yNumber, pos.z), false);
if (y < ceilingHeight && floor->GetRoomNumberAbove(x, location.yNumber, z)) if (pos.y < ceilingHeight && floor->GetRoomNumberAbove(Vector3i(pos.x, location.yNumber, pos.z)))
return std::nullopt; return std::nullopt;
if (y <= floorHeight)
if (pos.y <= floorHeight)
{ {
location.yNumber = std::max(y, ceilingHeight); location.yNumber = std::max(pos.y, ceilingHeight);
return std::optional{location}; return location;
} }
roomBelow = floor->GetRoomNumberBelow(x, location.yNumber, z); roomBelow = floor->GetRoomNumberBelow(Vector3i(pos.x, location.yNumber, pos.z));
} }
return std::nullopt; return std::nullopt;
} }
std::optional<ROOM_VECTOR> GetTopRoom(ROOM_VECTOR location, int x, int y, int z) std::optional<ROOM_VECTOR> GetTopRoom(ROOM_VECTOR location, const Vector3i& pos)
{ {
auto floor = &GetFloorSide(location.roomNumber, x, z, &location.roomNumber); auto floor = &GetFloorSide(location.roomNumber, pos.x, pos.z, &location.roomNumber);
if (floor->IsWall(x, z)) if (floor->IsWall(pos.x, pos.z))
{ {
floor = &GetTopFloor(location.roomNumber, x, z, &location.roomNumber); floor = &GetTopFloor(location.roomNumber, pos.x, pos.z, &location.roomNumber);
if (floor->IsWall(x, z)) if (floor->IsWall(pos.x, pos.z))
return std::nullopt; return std::nullopt;
location.yNumber = floor->GetSurfaceHeight(x, z, true); location.yNumber = floor->GetSurfaceHeight(pos.x, pos.z, true);
} }
auto floorHeight = floor->GetSurfaceHeight(x, location.yNumber, z, true); auto floorHeight = floor->GetSurfaceHeight(Vector3i(pos.x, location.yNumber, pos.z), true);
auto ceilingHeight = floor->GetSurfaceHeight(x, location.yNumber, z, false); auto ceilingHeight = floor->GetSurfaceHeight(Vector3i(pos.x, location.yNumber, pos.z), false);
location.yNumber = std::clamp(location.yNumber, std::min(ceilingHeight, floorHeight), std::max(ceilingHeight, floorHeight)); location.yNumber = std::clamp(location.yNumber, std::min(ceilingHeight, floorHeight), std::max(ceilingHeight, floorHeight));
if (floor->GetInsideBridgeItemNumber(x, location.yNumber, z, location.yNumber == ceilingHeight, location.yNumber == floorHeight) >= 0) if (floor->GetInsideBridgeItemNumber(Vector3i(pos.x, location.yNumber, pos.z), location.yNumber == ceilingHeight, location.yNumber == floorHeight) >= 0)
{ {
const auto height = GetTopHeight(*floor, Vector3i(x, location.yNumber, z), &location.roomNumber, &floor); const auto height = GetTopHeight(*floor, Vector3i(pos.x, location.yNumber, pos.z), &location.roomNumber, &floor);
if (!height) if (!height)
return std::nullopt; return std::nullopt;
location.yNumber = *height; location.yNumber = *height;
} }
floorHeight = floor->GetSurfaceHeight(x, location.yNumber, z, true); floorHeight = floor->GetSurfaceHeight(Vector3i(pos.x, location.yNumber, pos.z), true);
ceilingHeight = floor->GetSurfaceHeight(x, location.yNumber, z, false); ceilingHeight = floor->GetSurfaceHeight(Vector3i(pos.x, location.yNumber, pos.z), false);
if (y > floorHeight && floor->GetRoomNumberBelow(x, location.yNumber, z)) if (pos.y > floorHeight && floor->GetRoomNumberBelow(Vector3i(pos.x, location.yNumber, pos.z)))
return std::nullopt; return std::nullopt;
if (y >= ceilingHeight)
if (pos.y >= ceilingHeight)
{ {
location.yNumber = std::min(y, floorHeight); location.yNumber = std::min(pos.y, floorHeight);
return std::optional{location}; return location;
} }
auto roomAbove = floor->GetRoomNumberAbove(x, location.yNumber, z); auto roomAbove = floor->GetRoomNumberAbove(Vector3i(pos.x, location.yNumber, pos.z));
while (roomAbove) while (roomAbove)
{ {
floor = &GetFloorSide(*roomAbove, x, z, &location.roomNumber); floor = &GetFloorSide(*roomAbove, pos.x, pos.z, &location.roomNumber);
location.yNumber = floor->GetSurfaceHeight(x, z, true); location.yNumber = floor->GetSurfaceHeight(pos.x, pos.z, true);
floorHeight = floor->GetSurfaceHeight(x, location.yNumber, z, true); floorHeight = floor->GetSurfaceHeight(Vector3i(pos.x, location.yNumber, pos.z), true);
ceilingHeight = floor->GetSurfaceHeight(x, location.yNumber, z, false); ceilingHeight = floor->GetSurfaceHeight(Vector3i(pos.x, location.yNumber, pos.z), false);
if (y > floorHeight && floor->GetRoomNumberBelow(x, location.yNumber, z)) if (pos.y > floorHeight && floor->GetRoomNumberBelow(Vector3i(pos.x, location.yNumber, pos.z)))
return std::nullopt; return std::nullopt;
if (y >= ceilingHeight)
if (pos.y >= ceilingHeight)
{ {
location.yNumber = std::min(y, floorHeight); location.yNumber = std::min(pos.y, floorHeight);
return std::optional{location}; return location;
} }
roomAbove = floor->GetRoomNumberAbove(x, location.yNumber, z); roomAbove = floor->GetRoomNumberAbove(Vector3i(pos.x, location.yNumber, pos.z));
} }
return std::nullopt; return std::nullopt;
} }
ROOM_VECTOR GetRoom(ROOM_VECTOR location, int x, int y, int z) ROOM_VECTOR GetRoom(ROOM_VECTOR location, const Vector3i& pos)
{ {
const auto locationBelow = GetBottomRoom(location, x, y, z); const auto locationBelow = GetBottomRoom(location, pos);
if (locationBelow) if (locationBelow)
return *locationBelow; return *locationBelow;
const auto locationAbove = GetTopRoom(location, x, y, z); const auto locationAbove = GetTopRoom(location, pos);
if (locationAbove) if (locationAbove)
return *locationAbove; return *locationAbove;
@ -841,7 +856,7 @@ namespace TEN::Collision::Floordata
// Animated objects are also supported, although horizontal collision shifting is unstable. // Animated objects are also supported, although horizontal collision shifting is unstable.
// Method: get accurate bounds in world transform by converting to OBB, then do a ray test // Method: get accurate bounds in world transform by converting to OBB, then do a ray test
// on top or bottom (depending on test side) to determine if box is present at a particular point. // on top or bottom (depending on test side) to determine if box is present at a particular point.
std::optional<int> GetBridgeItemIntersect(int itemNumber, int x, int y, int z, bool useBottomHeight) std::optional<int> GetBridgeItemIntersect(const Vector3i& pos, int itemNumber, bool useBottomHeight)
{ {
auto& item = g_Level.Items[itemNumber]; auto& item = g_Level.Items[itemNumber];
@ -849,10 +864,11 @@ namespace TEN::Collision::Floordata
auto dxBounds = bounds.ToBoundingOrientedBox(item.Pose); auto dxBounds = bounds.ToBoundingOrientedBox(item.Pose);
// Introduce slight vertical margin just in case. // Introduce slight vertical margin just in case.
auto pos = Vector3(x, y + (useBottomHeight ? 4 : -4), z); auto origin = Vector3(pos.x, pos.y + (useBottomHeight ? 4 : -4), pos.z);
auto dir = useBottomHeight ? -Vector3::UnitY : Vector3::UnitY;
float dist = 0.0f; float dist = 0.0f;
if (dxBounds.Intersects(pos, (useBottomHeight ? -Vector3::UnitY : Vector3::UnitY), dist)) if (dxBounds.Intersects(origin, dir, dist))
return (item.Pose.Position.y + (useBottomHeight ? bounds.Y2 : bounds.Y1)); return (item.Pose.Position.y + (useBottomHeight ? bounds.Y2 : bounds.Y1));
return std::nullopt; return std::nullopt;
@ -928,15 +944,9 @@ namespace TEN::Collision::Floordata
} }
} }
bool TestMaterial(MaterialType refMaterial, const std::vector<MaterialType>& materialList) bool TestMaterial(MaterialType refMaterial, const std::vector<MaterialType>& materials)
{ {
for (const auto& material : materialList) return Contains(materials, refMaterial);
{
if (material == refMaterial)
return true;
}
return false;
} }
static void DrawSectorFlagLabel(const Vector3& pos, const std::string& string, const Vector4& color, float verticalOffset) static void DrawSectorFlagLabel(const Vector3& pos, const std::string& string, const Vector4& color, float verticalOffset)
@ -968,7 +978,7 @@ namespace TEN::Collision::Floordata
constexpr auto ACTIVATOR_MINECART_LEFT_COLOR = Vector4(1.0f, 0.4f, 1.0f, 1.0f); constexpr auto ACTIVATOR_MINECART_LEFT_COLOR = Vector4(1.0f, 0.4f, 1.0f, 1.0f);
constexpr auto MINECART_STOP_COLOR = Vector4(0.4f, 1.0f, 1.0f, 1.0f); constexpr auto MINECART_STOP_COLOR = Vector4(0.4f, 1.0f, 1.0f, 1.0f);
// Only check sectors in player vicinity. // Only check sectors in player's vicinity.
const auto& room = g_Level.Rooms[item.RoomNumber]; const auto& room = g_Level.Rooms[item.RoomNumber];
int minX = std::max(item.Pose.Position.x - DRAW_RANGE, room.x) / BLOCK(1); int minX = std::max(item.Pose.Position.x - DRAW_RANGE, room.x) / BLOCK(1);
int maxX = std::min(item.Pose.Position.x + DRAW_RANGE, room.x + (room.xSize * BLOCK(1))) / BLOCK(1); int maxX = std::min(item.Pose.Position.x + DRAW_RANGE, room.x + (room.xSize * BLOCK(1))) / BLOCK(1);

View file

@ -4,20 +4,24 @@
using namespace TEN::Math; using namespace TEN::Math;
// GLOSSARY OF TERMS: // Ceiling: Upper surface of a sector.
// Block: Collision unit describing a room division within the grid. // Floor: Lower surface of a sector.
// Ceiling: Upper surface of a collision block. // Floordata: Name of the engine's level geometry collision system composed of rooms with sectors.
// Floor: Lower surface of a collision block. // Plane: Mathematical representation of one of two surface triangles.
// Floordata: Name of the engine's level geometry collision system consisting of rooms and their divisions within a grid. // Portal: Link from one room to another allowing traversal between them.
// Plane: One of two surface triangles. // Room number: Unique ID of a room.
// Portal: Link from one room to another allowing traversal. // Room position: Relative grid coordinates of room sectors.
// Room number: Unique numeric index of a room. // Sector/block: Collision data describing a single grid division within a room.
// Surface: Floor or ceiling. // Surface: Floor or ceiling consisting of two triangles.
// Wall: Inferred from a floor or ceiling with max height. Note that true "walls" do not exist. // Triangle: Surface subdivision.
// Wall: Inferred from a floor or ceiling with max height. Note that true "walls" don't exist in floordata.
// Planes are non-standard. Instead of 4 components storing a normal and a distance, floordata uses a Vector3: //
// x and y store the 2D direction vector in the xz plane (not normalized). // The way floordata "planes" are stored is non-standard.
// z stores the distance. // Instead of a Plane object with a normal + distance,
// they use a Vector3 object with data laid out as follows:
// x: X tilt grade (0.25f = 1/4 block).
// y: Z tilt grade (0.25f = 1/4 block).
// z: Plane's absolute height at the sector's center (i.e. distance in regular plane terms).
constexpr auto WALL_PLANE = Vector3(0, 0, -CLICK(127)); constexpr auto WALL_PLANE = Vector3(0, 0, -CLICK(127));
@ -132,15 +136,15 @@ class FloorInfo
std::optional<int> GetRoomNumberAbove(int planeIndex) const; std::optional<int> GetRoomNumberAbove(int planeIndex) const;
std::optional<int> GetRoomNumberAbove(int x, int z) const; std::optional<int> GetRoomNumberAbove(int x, int z) const;
std::optional<int> GetRoomNumberAbove(int x, int y, int z) const; std::optional<int> GetRoomNumberAbove(const Vector3i& pos) const;
std::optional<int> GetRoomNumberBelow(int planeIndex) const; std::optional<int> GetRoomNumberBelow(int planeIndex) const;
std::optional<int> GetRoomNumberBelow(int x, int z) const; std::optional<int> GetRoomNumberBelow(int x, int z) const;
std::optional<int> GetRoomNumberBelow(int x, int y, int z) const; std::optional<int> GetRoomNumberBelow(const Vector3i& pos) const;
std::optional<int> GetRoomNumberAtSide() const; // Through wall? std::optional<int> GetRoomNumberAtSide() const;
int GetSurfaceHeight(int x, int z, bool isFloor) const; int GetSurfaceHeight(int x, int z, bool isFloor) const;
int GetSurfaceHeight(int x, int y, int z, bool isFloor) const; int GetSurfaceHeight(const Vector3i& pos, bool isFloor) const;
int GetBridgeSurfaceHeight(int x, int y, int z, bool isFloor) const; int GetBridgeSurfaceHeight(const Vector3i& pos, bool isFloor) const;
Vector2 GetSurfaceSlope(int planeIndex, bool isFloor) const; Vector2 GetSurfaceSlope(int planeIndex, bool isFloor) const;
Vector2 GetSurfaceSlope(int x, int z, bool isFloor) const; Vector2 GetSurfaceSlope(int x, int z, bool isFloor) const;
@ -153,7 +157,7 @@ class FloorInfo
bool IsWall(int x, int z) const; bool IsWall(int x, int z) const;
// Bridge methods // Bridge methods
int GetInsideBridgeItemNumber(int x, int y, int z, bool floorBorder, bool ceilingBorder) const; int GetInsideBridgeItemNumber(const Vector3i& pos, bool floorBorder, bool ceilingBorder) const;
void AddBridge(int itemNumber); void AddBridge(int itemNumber);
void RemoveBridge(int itemNumber); void RemoveBridge(int itemNumber);
}; };
@ -166,7 +170,7 @@ namespace TEN::Collision::Floordata
Vector2i GetSectorPoint(int x, int z); Vector2i GetSectorPoint(int x, int z);
Vector2i GetRoomPosition(int roomNumber, int x, int z); Vector2i GetRoomPosition(int roomNumber, int x, int z);
FloorInfo& GetFloor(int roomNumber, const Vector2i& pos); FloorInfo& GetFloor(int roomNumber, const Vector2i& roomPos);
FloorInfo& GetFloor(int roomNumber, int x, int z); FloorInfo& GetFloor(int roomNumber, int x, int z);
FloorInfo& GetFloorSide(int roomNumber, int x, int z, int* sideRoomNumber = nullptr); FloorInfo& GetFloorSide(int roomNumber, int x, int z, int* sideRoomNumber = nullptr);
FloorInfo& GetBottomFloor(int roomNumber, int x, int z, int* bottomRoomNumber = nullptr); FloorInfo& GetBottomFloor(int roomNumber, int x, int z, int* bottomRoomNumber = nullptr);
@ -177,18 +181,18 @@ namespace TEN::Collision::Floordata
std::optional<int> GetFloorHeight(const ROOM_VECTOR& location, int x, int z); std::optional<int> GetFloorHeight(const ROOM_VECTOR& location, int x, int z);
std::optional<int> GetCeilingHeight(const ROOM_VECTOR& location, int x, int z); std::optional<int> GetCeilingHeight(const ROOM_VECTOR& location, int x, int z);
std::optional<ROOM_VECTOR> GetBottomRoom(ROOM_VECTOR location, int x, int y, int z); std::optional<ROOM_VECTOR> GetBottomRoom(ROOM_VECTOR location, const Vector3i& pos);
std::optional<ROOM_VECTOR> GetTopRoom(ROOM_VECTOR location, int x, int y, int z); std::optional<ROOM_VECTOR> GetTopRoom(ROOM_VECTOR location, const Vector3i& pos);
ROOM_VECTOR GetRoom(ROOM_VECTOR location, int x, int y, int z); ROOM_VECTOR GetRoom(ROOM_VECTOR location, const Vector3i& pos);
void AddBridge(int itemNumber, int x = 0, int z = 0); void AddBridge(int itemNumber, int x = 0, int z = 0);
void RemoveBridge(int itemNumber, int x = 0, int z = 0); void RemoveBridge(int itemNumber, int x = 0, int z = 0);
std::optional<int> GetBridgeItemIntersect(int itemNumber, int x, int y, int z, bool useBottomHeight); std::optional<int> GetBridgeItemIntersect(const Vector3i& pos, int itemNumber, bool useBottomHeight);
int GetBridgeBorder(int itemNumber, bool bottom); int GetBridgeBorder(int itemNumber, bool bottom);
void UpdateBridgeItem(int itemNumber, bool forceRemoval = false); void UpdateBridgeItem(int itemNumber, bool forceRemoval = false);
bool TestMaterial(MaterialType refMaterial, const std::vector<MaterialType>& materialList); bool TestMaterial(MaterialType refMaterial, const std::vector<MaterialType>& materials);
void DrawNearbySectorFlags(const ItemInfo& item); void DrawNearbySectorFlags(const ItemInfo& item);
} }

View file

@ -19,7 +19,7 @@
#include "Game/Setup.h" #include "Game/Setup.h"
#include "Math/Math.h" #include "Math/Math.h"
#include "Objects/objectslist.h" #include "Objects/objectslist.h"
#include "Objects/TR5/Object/tr5_pushableblock.h" #include "Objects/Generic/Object/Pushable/PushableObject.h"
#include "Renderer/Renderer11.h" #include "Renderer/Renderer11.h"
using namespace TEN::Effects::Smoke; using namespace TEN::Effects::Smoke;
@ -2134,9 +2134,6 @@ void InitializeItemBoxData()
for (int i = 0; i < g_Level.Items.size(); i++) for (int i = 0; i < g_Level.Items.size(); i++)
{ {
auto* currentItem = &g_Level.Items[i]; auto* currentItem = &g_Level.Items[i];
if (currentItem->Active && currentItem->Data.is<PushableInfo>())
ClearMovableBlockSplitters(currentItem->Pose.Position, currentItem->RoomNumber);
} }
for (auto& room : g_Level.Rooms) for (auto& room : g_Level.Rooms)

View file

@ -21,7 +21,6 @@
#include "Objects/TR4/Entity/tr4_beetle_swarm.h" #include "Objects/TR4/Entity/tr4_beetle_swarm.h"
#include "Objects/TR5/Emitter/tr5_spider_emitter.h" #include "Objects/TR5/Emitter/tr5_spider_emitter.h"
#include "Objects/TR5/Emitter/tr5_rats_emitter.h" #include "Objects/TR5/Emitter/tr5_rats_emitter.h"
#include "Objects/TR5/Object/tr5_pushableblock.h"
#include "Objects/Effects/tr4_locusts.h" #include "Objects/Effects/tr4_locusts.h"
using namespace TEN::Effects::Environment; using namespace TEN::Effects::Environment;

View file

@ -116,7 +116,7 @@ bool GetTargetOnLOS(GameVector* origin, GameVector* target, bool drawTarget, boo
{ {
if (itemNumber < 0) if (itemNumber < 0)
{ {
if (StaticObjects[mesh->staticNumber].shatterType != SHT_NONE) if (StaticObjects[mesh->staticNumber].shatterType != ShatterType::None)
{ {
ShatterImpactData.impactDirection = directionNorm; ShatterImpactData.impactDirection = directionNorm;
ShatterImpactData.impactLocation = Vector3(mesh->pos.Position.x, mesh->pos.Position.y, mesh->pos.Position.z); ShatterImpactData.impactLocation = Vector3(mesh->pos.Position.x, mesh->pos.Position.y, mesh->pos.Position.z);

View file

@ -41,22 +41,22 @@ namespace TEN::Effects::Streamer
// Apply expansion. // Apply expansion.
if (scaleRate != 0.0f) if (scaleRate != 0.0f)
{ {
auto direction = Orientation.ToDirection(); auto dir = Orientation.ToDirection();
Vertices[0] = Geometry::TranslatePoint(Vertices[0], -direction, scaleRate); Vertices[0] = Geometry::TranslatePoint(Vertices[0], -dir, scaleRate);
Vertices[1] = Geometry::TranslatePoint(Vertices[1], direction, scaleRate); Vertices[1] = Geometry::TranslatePoint(Vertices[1], dir, scaleRate);
} }
// Apply directional velocity. // Apply directional velocity.
if (vel != 0.0f) if (vel != 0.0f)
{ {
auto direction = Orientation.GetAxis(); auto dir = Orientation.GetAxis();
Vertices[0] = Geometry::TranslatePoint(Vertices[0], direction, vel); Vertices[0] = Geometry::TranslatePoint(Vertices[0], dir, vel);
Vertices[1] = Geometry::TranslatePoint(Vertices[1], direction, vel); Vertices[1] = Geometry::TranslatePoint(Vertices[1], dir, vel);
} }
} }
void Streamer::AddSegment(const Vector3& pos, const Vector3& direction, short orient2D, const Vector4& color, void Streamer::AddSegment(const Vector3& pos, const Vector3& dir, short orient, const Vector4& color,
float width, float life, float vel, float scaleRate, short rot2D, int flags, unsigned int segmentCount) float width, float life, float vel, float scaleRate, short rot, int flags, unsigned int segmentCount)
{ {
constexpr auto FADE_IN_COEFF = 3.0f; constexpr auto FADE_IN_COEFF = 3.0f;
@ -68,14 +68,14 @@ namespace TEN::Effects::Streamer
float alpha = (segmentCount / lifeMax) * FADE_IN_COEFF; float alpha = (segmentCount / lifeMax) * FADE_IN_COEFF;
float opacityMax = InterpolateCos(0.0f, color.w, alpha); float opacityMax = InterpolateCos(0.0f, color.w, alpha);
segment.Orientation = AxisAngle(direction, orient2D); segment.Orientation = AxisAngle(dir, orient);
segment.Color = Vector4(color.x, color.y, color.z, opacityMax); segment.Color = Vector4(color.x, color.y, color.z, opacityMax);
segment.Life = segment.Life =
segment.LifeMax = lifeMax; segment.LifeMax = lifeMax;
segment.OpacityMax = opacityMax; segment.OpacityMax = opacityMax;
segment.Velocity = vel; segment.Velocity = vel;
segment.ScaleRate = scaleRate; segment.ScaleRate = scaleRate;
segment.Rotation = rot2D; segment.Rotation = rot;
segment.Flags = flags; segment.Flags = flags;
segment.InitializeVertices(pos, width); segment.InitializeVertices(pos, width);
} }
@ -109,8 +109,8 @@ namespace TEN::Effects::Streamer
return Segments.emplace_back(); return Segments.emplace_back();
} }
void StreamerModule::AddStreamer(int tag, const Vector3& pos, const Vector3& direction, short orient2D, const Vector4& color, void StreamerModule::AddStreamer(int tag, const Vector3& pos, const Vector3& dir, short orient, const Vector4& color,
float width, float life, float vel, float scaleRate, short rot2D, int flags) float width, float life, float vel, float scaleRate, short rot, int flags)
{ {
assertion(Pools.size() <= POOL_COUNT_MAX, "Streamer pool count overflow."); assertion(Pools.size() <= POOL_COUNT_MAX, "Streamer pool count overflow.");
@ -120,7 +120,7 @@ namespace TEN::Effects::Streamer
// Get and extend streamer with new segment. // Get and extend streamer with new segment.
auto& streamer = GetStreamer(tag); auto& streamer = GetStreamer(tag);
streamer.AddSegment(pos, direction, orient2D, color, width, life, vel, scaleRate, rot2D, flags, (unsigned int)streamer.Segments.size()); streamer.AddSegment(pos, dir, orient, color, width, life, vel, scaleRate, rot, flags, (unsigned int)streamer.Segments.size());
} }
void StreamerModule::Update() void StreamerModule::Update()
@ -194,18 +194,18 @@ namespace TEN::Effects::Streamer
pool.end()); pool.end());
} }
void StreamerEffectController::Spawn(int entityNumber, int tag, const Vector3& pos, const Vector3& direction, short orient2D, const Vector4& color, void StreamerEffectController::Spawn(int itemNumber, int tag, const Vector3& pos, const Vector3& direction, short orient, const Vector4& color,
float width, float life, float vel, float scaleRate, short rot2D, int flags) float width, float life, float vel, float scaleRate, short rot, int flags)
{ {
assertion(Modules.size() <= MODULE_COUNT_MAX, "Streamer module count overflow."); assertion(Modules.size() <= MODULE_COUNT_MAX, "Streamer module count overflow.");
// Return early if module map is full and element with entityNumber key doesn't already exist. // Return early if module map is full and element with itemNumber key doesn't already exist.
if (Modules.size() == MODULE_COUNT_MAX && !Modules.count(entityNumber)) if (Modules.size() == MODULE_COUNT_MAX && !Modules.count(itemNumber))
return; return;
// Get module and extend streamer within pool. // Get module and extend streamer within pool.
auto& module = GetModule(entityNumber); auto& module = GetModule(itemNumber);
module.AddStreamer(tag, pos, direction, orient2D, color, width, life, vel, scaleRate, rot2D, flags); module.AddStreamer(tag, pos, direction, orient, color, width, life, vel, scaleRate, rot, flags);
} }
void StreamerEffectController::Update() void StreamerEffectController::Update()
@ -213,7 +213,7 @@ namespace TEN::Effects::Streamer
if (Modules.empty()) if (Modules.empty())
return; return;
for (auto& [entityNumber, module] : Modules) for (auto& [itemNumber, module] : Modules)
module.Update(); module.Update();
ClearInactiveModules(); ClearInactiveModules();
@ -224,11 +224,11 @@ namespace TEN::Effects::Streamer
*this = {}; *this = {};
} }
StreamerModule& StreamerEffectController::GetModule(int entityNumber) StreamerModule& StreamerEffectController::GetModule(int itemNumber)
{ {
// Get module at entityNumber key. // Get module at itemNumber key.
Modules.insert({ entityNumber, {} }); Modules.insert({ itemNumber, {} });
auto& module = Modules.at(entityNumber); auto& module = Modules.at(itemNumber);
return module; return module;
} }

View file

@ -51,8 +51,8 @@ namespace TEN::Effects::Streamer
std::vector<StreamerSegment> Segments = {}; std::vector<StreamerSegment> Segments = {};
// Utilities // Utilities
void AddSegment(const Vector3& pos, const Vector3& direction, short orient2D, const Vector4& color, void AddSegment(const Vector3& pos, const Vector3& dir, short orient, const Vector4& color,
float width, float life, float vel, float scaleRate, short rot2D, int flags, unsigned int segmentCount); float width, float life, float vel, float scaleRate, short rot, int flags, unsigned int segmentCount);
void Update(); void Update();
private: private:
@ -72,8 +72,8 @@ namespace TEN::Effects::Streamer
std::unordered_map<int, std::vector<Streamer>> Pools = {}; // Key = tag. std::unordered_map<int, std::vector<Streamer>> Pools = {}; // Key = tag.
// Utilities // Utilities
void AddStreamer(int tag, const Vector3& pos, const Vector3& direction, short orient2D, const Vector4& color, void AddStreamer(int tag, const Vector3& pos, const Vector3& dir, short orient, const Vector4& color,
float width, float life, float vel, float scaleRate, short rot2D, int flags); float width, float life, float vel, float scaleRate, short rot, int flags);
void Update(); void Update();
private: private:
@ -92,17 +92,17 @@ namespace TEN::Effects::Streamer
public: public:
// Members // Members
std::unordered_map<int, StreamerModule> Modules = {}; // Key = entity number. std::unordered_map<int, StreamerModule> Modules = {}; // Key = item number.
// Utilities // Utilities
void Spawn(int entityID, int tag, const Vector3& pos, const Vector3& direction, short orient2D, const Vector4& color, void Spawn(int itemNumber, int tag, const Vector3& pos, const Vector3& dir, short orient, const Vector4& color,
float width, float life, float vel, float scaleRate, short rot2D, int flags = 0); float width, float life, float vel, float scaleRate, short rot, int flags = 0);
void Update(); void Update();
void Clear(); void Clear();
private: private:
// Helpers // Helpers
StreamerModule& GetModule(int entityNumber); StreamerModule& GetModule(int itemNumber);
void ClearInactiveModules(); void ClearInactiveModules();
}; };

View file

@ -149,7 +149,7 @@ namespace TEN::Effects::Bubble
// Update room number. TODO: Should use GetCollision(), but calling it for each bubble is very inefficient. // Update room number. TODO: Should use GetCollision(), but calling it for each bubble is very inefficient.
auto roomVector = ROOM_VECTOR{ bubble.RoomNumber, int(bubble.Position.y - bubble.Gravity) }; auto roomVector = ROOM_VECTOR{ bubble.RoomNumber, int(bubble.Position.y - bubble.Gravity) };
int roomNumber = GetRoom(roomVector, bubble.Position.x, bubble.Position.y - bubble.Gravity, bubble.Position.z).roomNumber; int roomNumber = GetRoom(roomVector, Vector3i(bubble.Position.x, bubble.Position.y - bubble.Gravity, bubble.Position.z)).roomNumber;
int prevRoomNumber = bubble.RoomNumber; int prevRoomNumber = bubble.RoomNumber;
bubble.RoomNumber = roomNumber; bubble.RoomNumber = roomNumber;

View file

@ -231,14 +231,14 @@ void UpdateDebris()
if (deb.worldPosition.y < floor->GetSurfaceHeight(deb.worldPosition.x, deb.worldPosition.z, false)) if (deb.worldPosition.y < floor->GetSurfaceHeight(deb.worldPosition.x, deb.worldPosition.z, false))
{ {
auto roomNumber = floor->GetRoomNumberAbove(deb.worldPosition.x, deb.worldPosition.y, deb.worldPosition.z).value_or(NO_ROOM); auto roomNumber = floor->GetRoomNumberAbove(Vector3i(deb.worldPosition)).value_or(NO_ROOM);
if (roomNumber != NO_ROOM) if (roomNumber != NO_ROOM)
deb.roomNumber = roomNumber; deb.roomNumber = roomNumber;
} }
if (deb.worldPosition.y > floor->GetSurfaceHeight(deb.worldPosition.x, deb.worldPosition.z, true)) if (deb.worldPosition.y > floor->GetSurfaceHeight(deb.worldPosition.x, deb.worldPosition.z, true))
{ {
auto roomNumber = floor->GetRoomNumberBelow(deb.worldPosition.x, deb.worldPosition.y, deb.worldPosition.z).value_or(NO_ROOM); auto roomNumber = floor->GetRoomNumberBelow(Vector3i(deb.worldPosition)).value_or(NO_ROOM);
if (roomNumber != NO_ROOM) if (roomNumber != NO_ROOM)
{ {
deb.roomNumber = roomNumber; deb.roomNumber = roomNumber;

View file

@ -480,7 +480,7 @@ namespace TEN::Effects::Environment
auto coll = GetCollision(xPos, yPos, zPos, outsideRoom); auto coll = GetCollision(xPos, yPos, zPos, outsideRoom);
if (!(coll.Position.Ceiling < yPos || coll.Block->GetRoomNumberAbove(xPos, yPos, zPos).value_or(NO_ROOM) != NO_ROOM)) if (!(coll.Position.Ceiling < yPos || coll.Block->GetRoomNumberAbove(Vector3i(xPos, yPos, zPos)).value_or(NO_ROOM) != NO_ROOM))
continue; continue;
auto part = WeatherParticle(); auto part = WeatherParticle();

View file

@ -32,13 +32,17 @@ using namespace TEN::Utils;
namespace TEN::Gui namespace TEN::Gui
{ {
constexpr int LINE_HEIGHT = 25; constexpr int LINE_HEIGHT = 25;
constexpr int PHD_CENTER_X = SCREEN_SPACE_RES.x / 2; constexpr int PHD_CENTER_X = SCREEN_SPACE_RES.x / 2;
constexpr int PHD_CENTER_Y = SCREEN_SPACE_RES.y / 2; constexpr int PHD_CENTER_Y = SCREEN_SPACE_RES.y / 2;
constexpr int OBJLIST_SPACING = PHD_CENTER_X / 2; constexpr int OBJLIST_SPACING = PHD_CENTER_X / 2;
constexpr auto VOLUME_MAX = 100; constexpr auto VOLUME_MAX = 100;
constexpr auto VOLUME_STEP = VOLUME_MAX / 20; constexpr auto VOLUME_STEP = VOLUME_MAX / 20;
constexpr auto MOUSE_SENSITIVITY_MAX = 35;
constexpr auto MOUSE_SENSITIVITY_MIN = 1;
constexpr auto MOUSE_SMOOTHING_MAX = 5;
constexpr auto MOUSE_SMOOTHING_MIN = 0;
GuiController g_Gui; GuiController g_Gui;
@ -875,12 +879,14 @@ namespace TEN::Gui
TargetHighlighter, TargetHighlighter,
ToggleRumble, ToggleRumble,
ThumbstickCameraControl, ThumbstickCameraControl,
MouseSensitivity,
MouseSmoothing,
Apply, Apply,
Cancel Cancel
}; };
static const int numOtherSettingsOptions = 9; static const auto numOtherSettingsOptions = 11;
OptionCount = numOtherSettingsOptions; OptionCount = numOtherSettingsOptions;
@ -961,6 +967,30 @@ namespace TEN::Gui
isVolumeAdjusted = true; isVolumeAdjusted = true;
} }
break;
case OtherSettingsOption::MouseSensitivity:
if (CurrentSettings.Configuration.MouseSensitivity > MOUSE_SENSITIVITY_MIN)
{
CurrentSettings.Configuration.MouseSensitivity -= 1;
if (CurrentSettings.Configuration.MouseSensitivity < MOUSE_SENSITIVITY_MIN)
CurrentSettings.Configuration.MouseSensitivity = MOUSE_SENSITIVITY_MIN;
SoundEffect(SFX_TR4_MENU_CHOOSE, nullptr, SoundEnvironment::Always);
}
break;
case OtherSettingsOption::MouseSmoothing:
if (CurrentSettings.Configuration.MouseSmoothing > MOUSE_SMOOTHING_MIN)
{
CurrentSettings.Configuration.MouseSmoothing -= 1;
if (CurrentSettings.Configuration.MouseSmoothing < MOUSE_SMOOTHING_MIN)
CurrentSettings.Configuration.MouseSmoothing = MOUSE_SMOOTHING_MIN;
SoundEffect(SFX_TR4_MENU_CHOOSE, nullptr, SoundEnvironment::Always);
}
break; break;
} }
@ -1000,6 +1030,30 @@ namespace TEN::Gui
isVolumeAdjusted = true; isVolumeAdjusted = true;
} }
break;
case OtherSettingsOption::MouseSensitivity:
if (CurrentSettings.Configuration.MouseSensitivity < MOUSE_SENSITIVITY_MAX)
{
CurrentSettings.Configuration.MouseSensitivity += 1;
if (CurrentSettings.Configuration.MouseSensitivity > MOUSE_SENSITIVITY_MAX)
CurrentSettings.Configuration.MouseSensitivity = MOUSE_SENSITIVITY_MAX;
SoundEffect(SFX_TR4_MENU_CHOOSE, nullptr, SoundEnvironment::Always);
}
break;
case OtherSettingsOption::MouseSmoothing:
if (CurrentSettings.Configuration.MouseSmoothing < MOUSE_SMOOTHING_MAX)
{
CurrentSettings.Configuration.MouseSmoothing += 1;
if (CurrentSettings.Configuration.MouseSmoothing > MOUSE_SMOOTHING_MAX)
CurrentSettings.Configuration.MouseSmoothing = MOUSE_SMOOTHING_MAX;
SoundEffect(SFX_TR4_MENU_CHOOSE, nullptr, SoundEnvironment::Always);
}
break; break;
} }

View file

@ -9,6 +9,7 @@
#include "Game/itemdata/door_data.h" #include "Game/itemdata/door_data.h"
#include "Game/Lara/lara_struct.h" #include "Game/Lara/lara_struct.h"
#include "Math/Math.h" #include "Math/Math.h"
#include "Objects/Generic/Object/Pushable/PushableInfo.h"
#include "Objects/TR2/Vehicles/skidoo_info.h" #include "Objects/TR2/Vehicles/skidoo_info.h"
#include "Objects/TR2/Vehicles/speedboat_info.h" #include "Objects/TR2/Vehicles/speedboat_info.h"
#include "Objects/TR3/Vehicles/big_gun_info.h" #include "Objects/TR3/Vehicles/big_gun_info.h"
@ -22,7 +23,6 @@
#include "Objects/TR4/Vehicles/motorbike_info.h" #include "Objects/TR4/Vehicles/motorbike_info.h"
#include "Objects/TR5/Entity/tr5_laserhead_info.h" #include "Objects/TR5/Entity/tr5_laserhead_info.h"
#include "Objects/TR5/Light/tr5_light_info.h" #include "Objects/TR5/Light/tr5_light_info.h"
#include "Objects/TR5/Object/tr5_pushableblock_info.h"
template<class... Ts> struct visitor : Ts... { using Ts::operator()...; }; template<class... Ts> struct visitor : Ts... { using Ts::operator()...; };
template<class... Ts> visitor(Ts...)->visitor<Ts...>; // TODO: Line not needed in C++20. template<class... Ts> visitor(Ts...)->visitor<Ts...>; // TODO: Line not needed in C++20.

View file

@ -827,8 +827,8 @@ void DoItemHit(ItemInfo* target, int damage, bool isExplosive, bool allowBurn)
{ {
const auto& object = Objects[target->ObjectNumber]; const auto& object = Objects[target->ObjectNumber];
if ((object.damageType == DamageMode::AnyWeapon) || if ((object.damageType == DamageMode::Any) ||
(object.damageType == DamageMode::ExplosivesOnly && isExplosive)) (object.damageType == DamageMode::Explosion && isExplosive))
{ {
if (target->HitPoints > 0) if (target->HitPoints > 0)
{ {
@ -871,3 +871,17 @@ void DefaultItemHit(ItemInfo& target, ItemInfo& source, std::optional<GameVector
DoItemHit(&target, damage, isExplosive); DoItemHit(&target, damage, isExplosive);
} }
Vector3i GetNearestSectorCenter(const Vector3i& pos)
{
constexpr int SECTOR_SIZE = 1024;
// Calculate the sector-aligned coordinates.
int x = (pos.x / SECTOR_SIZE) * SECTOR_SIZE + SECTOR_SIZE / 2;
int z = (pos.z / SECTOR_SIZE) * SECTOR_SIZE + SECTOR_SIZE / 2;
// Keep the y-coordinate unchanged.
int y = pos.y;
return Vector3i(x, y, z);
}

View file

@ -197,3 +197,5 @@ void DoDamage(ItemInfo* item, int damage);
void DoItemHit(ItemInfo* target, int damage, bool isExplosive, bool allowBurn = true); void DoItemHit(ItemInfo* target, int damage, bool isExplosive, bool allowBurn = true);
void DefaultItemHit(ItemInfo& target, ItemInfo& source, std::optional<GameVector> pos, int damage, bool isExplosive, int jointIndex); void DefaultItemHit(ItemInfo& target, ItemInfo& source, std::optional<GameVector> pos, int damage, bool isExplosive, int jointIndex);
short SpawnItem(ItemInfo* item, GAME_OBJECT_ID objectNumber); short SpawnItem(ItemInfo* item, GAME_OBJECT_ID objectNumber);
Vector3i GetNearestSectorCenter(const Vector3i& pos);

View file

@ -1090,9 +1090,7 @@ void InitializePickup(short itemNumber)
{ {
// Automatically align pickups to the floor surface. // Automatically align pickups to the floor surface.
auto pointColl = GetCollision(item); auto pointColl = GetCollision(item);
int bridgeItemNumber = pointColl.Block->GetInsideBridgeItemNumber( int bridgeItemNumber = pointColl.Block->GetInsideBridgeItemNumber(item->Pose.Position, true, true);
item->Pose.Position.x, item->Pose.Position.y, item->Pose.Position.z,
true, true);
if (bridgeItemNumber != NO_ITEM) if (bridgeItemNumber != NO_ITEM)
{ {

View file

@ -585,6 +585,7 @@ bool SaveGame::Save(int slot)
flatbuffers::Offset<Save::Minecart> mineOffset; flatbuffers::Offset<Save::Minecart> mineOffset;
flatbuffers::Offset<Save::UPV> upvOffset; flatbuffers::Offset<Save::UPV> upvOffset;
flatbuffers::Offset<Save::Kayak> kayakOffset; flatbuffers::Offset<Save::Kayak> kayakOffset;
flatbuffers::Offset<Save::Pushable> pushableOffset;
flatbuffers::Offset<Save::Short> shortOffset; flatbuffers::Offset<Save::Short> shortOffset;
flatbuffers::Offset<Save::Int> intOffset; flatbuffers::Offset<Save::Int> intOffset;
@ -705,6 +706,44 @@ bool SaveGame::Save(int slot)
kayakBuilder.add_water_height(kayak->WaterHeight); kayakBuilder.add_water_height(kayak->WaterHeight);
kayakOffset = kayakBuilder.Finish(); kayakOffset = kayakBuilder.Finish();
} }
else if (itemToSerialize.Data.is<PushableInfo>())
{
auto pushable = (PushableInfo*)itemToSerialize.Data;
Save::PushableBuilder pushableBuilder{ fbb };
pushableBuilder.add_pushable_behaviour_state((int)pushable->BehaviorState);
pushableBuilder.add_pushable_gravity(pushable->Gravity);
pushableBuilder.add_pushable_water_force(pushable->Oscillation);
pushableBuilder.add_pushable_stack_limit(pushable->Stack.Limit);
pushableBuilder.add_pushable_stack_upper(pushable->Stack.ItemNumberAbove);
pushableBuilder.add_pushable_stack_lower(pushable->Stack.ItemNumberBelow);
pushableBuilder.add_pushable_start_x(pushable->StartPos.x);
pushableBuilder.add_pushable_start_z(pushable->StartPos.z);
pushableBuilder.add_pushable_room_number(pushable->StartPos.RoomNumber);
pushableBuilder.add_pushable_collider_flag(pushable->UseBridgeCollision);
pushableBuilder.add_pushable_north_pullable(pushable->EdgeAttribs[0].IsPullable);
pushableBuilder.add_pushable_north_pushable(pushable->EdgeAttribs[0].IsPushable);
pushableBuilder.add_pushable_north_climbable(pushable->EdgeAttribs[0].IsClimbable);
pushableBuilder.add_pushable_east_pullable(pushable->EdgeAttribs[1].IsPullable);
pushableBuilder.add_pushable_east_pushable(pushable->EdgeAttribs[1].IsPushable);
pushableBuilder.add_pushable_east_climbable(pushable->EdgeAttribs[1].IsClimbable);
pushableBuilder.add_pushable_south_pullable(pushable->EdgeAttribs[2].IsPullable);
pushableBuilder.add_pushable_south_pushable(pushable->EdgeAttribs[2].IsPushable);
pushableBuilder.add_pushable_south_climbable(pushable->EdgeAttribs[2].IsClimbable);
pushableBuilder.add_pushable_west_pullable(pushable->EdgeAttribs[3].IsPullable);
pushableBuilder.add_pushable_west_pushable(pushable->EdgeAttribs[3].IsPushable);
pushableBuilder.add_pushable_west_climbable(pushable->EdgeAttribs[3].IsClimbable);
pushableOffset = pushableBuilder.Finish();
}
else if (itemToSerialize.Data.is<short>()) else if (itemToSerialize.Data.is<short>())
{ {
Save::ShortBuilder sb{ fbb }; Save::ShortBuilder sb{ fbb };
@ -787,6 +826,11 @@ bool SaveGame::Save(int slot)
serializedItem.add_data_type(Save::ItemData::Kayak); serializedItem.add_data_type(Save::ItemData::Kayak);
serializedItem.add_data(kayakOffset.Union()); serializedItem.add_data(kayakOffset.Union());
} }
else if (itemToSerialize.Data.is<PushableInfo>())
{
serializedItem.add_data_type(Save::ItemData::Pushable);
serializedItem.add_data(pushableOffset.Union());
}
else if (itemToSerialize.Data.is<short>()) else if (itemToSerialize.Data.is<short>())
{ {
serializedItem.add_data_type(Save::ItemData::Short); serializedItem.add_data_type(Save::ItemData::Short);
@ -1245,7 +1289,7 @@ bool SaveGame::Save(int slot)
case SavedVarType::Vec3: case SavedVarType::Vec3:
{ {
SaveVec(SavedVarType::Vec3, s, Save::vec3TableBuilder, Save::VarUnion::vec3, Save::Vector3, FromVector3i); SaveVec(SavedVarType::Vec3, s, Save::vec3TableBuilder, Save::VarUnion::vec3, Save::Vector3, FromVector3);
break; break;
} }
@ -1782,6 +1826,41 @@ bool SaveGame::Load(int slot)
kayak->Velocity = savedKayak->velocity(); kayak->Velocity = savedKayak->velocity();
kayak->WaterHeight = savedKayak->water_height(); kayak->WaterHeight = savedKayak->water_height();
} }
else if (item->Data.is <PushableInfo>())
{
auto* pushable = (PushableInfo*)item->Data;
auto* savedPushable = (Save::Pushable*)savedItem->data();
pushable->BehaviorState = (PushableBehaviourState)savedPushable->pushable_behaviour_state();
pushable->Gravity = savedPushable->pushable_gravity();
pushable->Oscillation = savedPushable->pushable_water_force();
pushable->Stack.Limit = savedPushable->pushable_stack_limit();
pushable->Stack.ItemNumberAbove = savedPushable->pushable_stack_upper();
pushable->Stack.ItemNumberBelow = savedPushable->pushable_stack_lower();
pushable->StartPos.x = savedPushable->pushable_start_x();
pushable->StartPos.z = savedPushable->pushable_start_z();
pushable->StartPos.RoomNumber = savedPushable->pushable_room_number();
pushable->UseBridgeCollision = savedPushable->pushable_collider_flag();
pushable->EdgeAttribs[0].IsPullable = savedPushable->pushable_north_pullable();
pushable->EdgeAttribs[0].IsPushable = savedPushable->pushable_north_pushable();
pushable->EdgeAttribs[0].IsClimbable = savedPushable->pushable_north_climbable();
pushable->EdgeAttribs[1].IsPullable = savedPushable->pushable_east_pullable();
pushable->EdgeAttribs[1].IsPushable = savedPushable->pushable_east_pushable();
pushable->EdgeAttribs[1].IsClimbable = savedPushable->pushable_east_climbable();
pushable->EdgeAttribs[2].IsPullable = savedPushable->pushable_south_pullable();
pushable->EdgeAttribs[2].IsPushable = savedPushable->pushable_south_pushable();
pushable->EdgeAttribs[2].IsClimbable = savedPushable->pushable_south_climbable();
pushable->EdgeAttribs[3].IsPullable = savedPushable->pushable_west_pullable();
pushable->EdgeAttribs[3].IsPushable = savedPushable->pushable_west_pushable();
pushable->EdgeAttribs[3].IsClimbable = savedPushable->pushable_west_climbable();
}
else if (savedItem->data_type() == Save::ItemData::Short) else if (savedItem->data_type() == Save::ItemData::Short)
{ {
auto* data = savedItem->data(); auto* data = savedItem->data();
@ -2176,7 +2255,7 @@ bool SaveGame::Load(int slot)
{ {
auto stored = var->u_as_vec3()->vec(); auto stored = var->u_as_vec3()->vec();
SavedVar var; SavedVar var;
var.emplace<(int)SavedVarType::Vec3>(ToVector3i(stored)); var.emplace<(int)SavedVarType::Vec3>(ToVector3(stored));
loadedVars.push_back(var); loadedVars.push_back(var);
break; break;
} }

View file

@ -24,7 +24,6 @@
constexpr auto BLOCK = [](auto x) { return (BLOCK_UNIT * x); }; constexpr auto BLOCK = [](auto x) { return (BLOCK_UNIT * x); };
constexpr auto CLICK = [](auto x) { return ((BLOCK(1) / 4) * x); }; constexpr auto CLICK = [](auto x) { return ((BLOCK(1) / 4) * x); };
constexpr auto STEP_SIZE = CLICK(1);
constexpr auto WALL_MASK = BLOCK(1) - 1; constexpr auto WALL_MASK = BLOCK(1) - 1;
constexpr auto GRID_SNAP_SIZE = (int)BLOCK(1 / 8.0f); constexpr auto GRID_SNAP_SIZE = (int)BLOCK(1 / 8.0f);
//} //}

View file

@ -25,19 +25,19 @@ namespace TEN::Math::Geometry
return Vector3i(TranslatePoint(point.ToVector3(), orient, relOffset.ToVector3())); return Vector3i(TranslatePoint(point.ToVector3(), orient, relOffset.ToVector3()));
} }
Vector3i TranslatePoint(const Vector3i& point, const EulerAngles& orient, float distance) Vector3i TranslatePoint(const Vector3i& point, const EulerAngles& orient, float dist)
{ {
return Vector3i(TranslatePoint(point.ToVector3(), orient, distance)); return Vector3i(TranslatePoint(point.ToVector3(), orient, dist));
} }
Vector3i TranslatePoint(const Vector3i& point, const AxisAngle& orient, float distance) Vector3i TranslatePoint(const Vector3i& point, const AxisAngle& orient, float dist)
{ {
return Vector3i(TranslatePoint(point.ToVector3(), orient, distance)); return Vector3i(TranslatePoint(point.ToVector3(), orient, dist));
} }
Vector3i TranslatePoint(const Vector3i& point, const Vector3& direction, float distance) Vector3i TranslatePoint(const Vector3i& point, const Vector3& dir, float dist)
{ {
return Vector3i(TranslatePoint(point.ToVector3(), direction, distance)); return Vector3i(TranslatePoint(point.ToVector3(), dir, dist));
} }
Vector3 TranslatePoint(const Vector3& point, short headingAngle, float forward, float down, float right) Vector3 TranslatePoint(const Vector3& point, short headingAngle, float forward, float down, float right)
@ -63,29 +63,29 @@ namespace TEN::Math::Geometry
} }
// NOTE: Roll (Z axis) of EulerAngles orientation is disregarded. // NOTE: Roll (Z axis) of EulerAngles orientation is disregarded.
Vector3 TranslatePoint(const Vector3& point, const EulerAngles& orient, float distance) Vector3 TranslatePoint(const Vector3& point, const EulerAngles& orient, float dist)
{ {
if (distance == 0.0f) if (dist == 0.0f)
return point; return point;
auto direction = orient.ToDirection(); auto dir = orient.ToDirection();
return TranslatePoint(point, direction, distance); return TranslatePoint(point, dir, dist);
} }
Vector3 TranslatePoint(const Vector3& point, const AxisAngle& orient, float distance) Vector3 TranslatePoint(const Vector3& point, const AxisAngle& orient, float dist)
{ {
auto direction = orient.ToDirection(); auto dir = orient.ToDirection();
return TranslatePoint(point, direction, distance); return TranslatePoint(point, dir, dist);
} }
Vector3 TranslatePoint(const Vector3& point, const Vector3& direction, float distance) Vector3 TranslatePoint(const Vector3& point, const Vector3& dir, float dist)
{ {
if (distance == 0.0f) if (dist == 0.0f)
return point; return point;
auto directionNorm = direction; auto normalizedDir = dir;
directionNorm.Normalize(); normalizedDir.Normalize();
return (point + (directionNorm * distance)); return (point + (normalizedDir * dist));
} }
Vector3 RotatePoint(const Vector3& point, const EulerAngles& rot) Vector3 RotatePoint(const Vector3& point, const EulerAngles& rot)
@ -136,19 +136,19 @@ namespace TEN::Math::Geometry
if (linePoint0 == linePoint1) if (linePoint0 == linePoint1)
return linePoint0; return linePoint0;
auto direction = linePoint1 - linePoint0; auto dir = linePoint1 - linePoint0;
float distanceAlpha = direction.Dot(origin - linePoint0) / direction.Dot(direction); float distAlpha = dir.Dot(origin - linePoint0) / dir.Dot(dir);
if (distanceAlpha < 0.0f) if (distAlpha < 0.0f)
{ {
return linePoint0; return linePoint0;
} }
else if (distanceAlpha > 1.0f) else if (distAlpha > 1.0f)
{ {
return linePoint1; return linePoint1;
} }
return (linePoint0 + (direction * distanceAlpha)); return (linePoint0 + (dir * distAlpha));
} }
EulerAngles GetOrientToPoint(const Vector3& origin, const Vector3& target) EulerAngles GetOrientToPoint(const Vector3& origin, const Vector3& target)
@ -159,7 +159,7 @@ namespace TEN::Math::Geometry
return EulerAngles(target - origin); return EulerAngles(target - origin);
} }
EulerAngles GetRelOrientToNormal(short orient2D, const Vector3& normal, const Vector3& gravity) EulerAngles GetRelOrientToNormal(short orient, const Vector3& normal, const Vector3& gravity)
{ {
// TODO: Consider gravity direction. // TODO: Consider gravity direction.
@ -167,49 +167,50 @@ namespace TEN::Math::Geometry
short aspectAngle = Geometry::GetSurfaceAspectAngle(normal); short aspectAngle = Geometry::GetSurfaceAspectAngle(normal);
short slopeAngle = Geometry::GetSurfaceSlopeAngle(normal); short slopeAngle = Geometry::GetSurfaceSlopeAngle(normal);
short deltaAngle = Geometry::GetShortestAngle(orient2D, aspectAngle); short deltaAngle = Geometry::GetShortestAngle(orient, aspectAngle);
float sinDeltaAngle = phd_sin(deltaAngle); float sinDeltaAngle = phd_sin(deltaAngle);
float cosDeltaAngle = phd_cos(deltaAngle); float cosDeltaAngle = phd_cos(deltaAngle);
// Calculate relative orientation adhering to normal. // Calculate relative orientation adhering to normal.
return EulerAngles( return EulerAngles(
-slopeAngle * cosDeltaAngle, -slopeAngle * cosDeltaAngle,
orient2D, orient,
slopeAngle * sinDeltaAngle); slopeAngle * sinDeltaAngle);
} }
Quaternion ConvertDirectionToQuat(const Vector3& direction) Quaternion ConvertDirectionToQuat(const Vector3& dir)
{ {
constexpr auto SINGULARITY_THRESHOLD = 1.0f - EPSILON; constexpr auto SINGULARITY_THRESHOLD = 1.0f - EPSILON;
static const auto REF_DIRECTION = Vector3::UnitZ; static const auto REF_DIR = Vector3::UnitZ;
// If vectors are nearly opposite, return orientation 180 degrees around arbitrary axis. // Vectors are nearly opposite; return orientation 180 degrees around arbitrary axis.
float dot = REF_DIRECTION.Dot(direction); float dot = REF_DIR.Dot(dir);
if (dot < -SINGULARITY_THRESHOLD) if (dot < -SINGULARITY_THRESHOLD)
{ {
auto axis = Vector3::UnitX.Cross(REF_DIRECTION); auto axis = Vector3::UnitX.Cross(REF_DIR);
if (axis.LengthSquared() < EPSILON) if (axis.LengthSquared() < EPSILON)
axis = Vector3::UnitY.Cross(REF_DIRECTION); axis = Vector3::UnitY.Cross(REF_DIR);
axis.Normalize(); axis.Normalize();
auto axisAngle = AxisAngle(axis, FROM_RAD(PI)); auto axisAngle = AxisAngle(axis, FROM_RAD(PI));
return axisAngle.ToQuaternion(); return axisAngle.ToQuaternion();
} }
// If vectors are nearly identical, return identity quaternion. // Vectors are nearly identical; return identity quaternion.
if (dot > SINGULARITY_THRESHOLD) if (dot > SINGULARITY_THRESHOLD)
return Quaternion::Identity; return Quaternion::Identity;
// Calculate axis-angle and return converted quaternion. // Calculate axis-angle and return converted quaternion.
auto axisAngle = AxisAngle(REF_DIRECTION.Cross(direction), FROM_RAD(acos(dot))); auto axisAngle = AxisAngle(REF_DIR.Cross(dir), FROM_RAD(acos(dot)));
return axisAngle.ToQuaternion(); return axisAngle.ToQuaternion();
} }
Vector3 ConvertQuatToDirection(const Quaternion& quat) Vector3 ConvertQuatToDirection(const Quaternion& quat)
{ {
static const auto refDirection = Vector3::UnitZ; static const auto REF_DIR = Vector3::UnitZ;
return Vector3::Transform(refDirection, quat);
return Vector3::Transform(REF_DIR, quat);
} }
bool IsPointInFront(const Pose& pose, const Vector3& target) bool IsPointInFront(const Pose& pose, const Vector3& target)
@ -225,11 +226,11 @@ namespace TEN::Math::Geometry
float sinY = phd_sin(orient.y); float sinY = phd_sin(orient.y);
float cosY = phd_cos(orient.y); float cosY = phd_cos(orient.y);
// The 2D heading direction vector: X = +sinY, Y = 0, Z = +cosY // 2D heading direction vector: X = +sinY, Y = 0, Z = +cosY
auto headingDirection = Vector3(sinY, 0.0f, cosY); auto headingDir = Vector3(sinY, 0.0f, cosY);
auto targetDirection = target - origin; auto targetDir = target - origin;
float dot = headingDirection.Dot(targetDirection); float dot = headingDir.Dot(targetDir);
if (dot > 0.0f) if (dot > 0.0f)
return true; return true;
@ -241,13 +242,13 @@ namespace TEN::Math::Geometry
if (origin == target) if (origin == target)
return false; return false;
auto refDirection = refPoint - origin; auto refDir = refPoint - origin;
// The 2D heading direction vector to the 3D reference direction vector: X = +refDirection.x, Y = 0, Z = +refDirection.z // 2D heading direction vector to 3D reference direction vector: X = +refDirection.x, Y = 0, Z = +refDirection.z
auto headingDirection = Vector3(refDirection.x, 0.0f, refDirection.z); auto headingDir = Vector3(refDir.x, 0.0f, refDir.z);
auto targetDirection = target - origin; auto targetDir = target - origin;
float dot = headingDirection.Dot(targetDirection); float dot = headingDir.Dot(targetDir);
if (dot > 0.0f) if (dot > 0.0f)
return true; return true;
@ -267,11 +268,11 @@ namespace TEN::Math::Geometry
float sinY = phd_sin(orient.y); float sinY = phd_sin(orient.y);
float cosY = phd_cos(orient.y); float cosY = phd_cos(orient.y);
// The 2D normal vector to the 2D heading direction vector: X = +cosY, Y = 0, Z = -sinY // 2D normal vector to 2D heading direction vector: X = +cosY, Y = 0, Z = -sinY
auto headingNormal = Vector3(cosY, 0.0f, -sinY); auto headingNormal = Vector3(cosY, 0.0f, -sinY);
auto targetDirection = target - origin; auto targetDir = target - origin;
float dot = headingNormal.Dot(targetDirection); float dot = headingNormal.Dot(targetDir);
if (dot > 0.0f) if (dot > 0.0f)
return true; return true;
@ -283,13 +284,13 @@ namespace TEN::Math::Geometry
if (origin == target) if (origin == target)
return false; return false;
auto refDirection = refPoint - origin; auto refDir = refPoint - origin;
// The 2D normal vector to the 3D reference direction vector: X = +refDirection.z, Y = 0, Z = -refDirection.x // 2D normal vector to 3D reference direction vector: X = +refDirection.z, Y = 0, Z = -refDirection.x
auto headingNormal = Vector3(refDirection.z, 0.0f, -refDirection.x); auto headingNormal = Vector3(refDir.z, 0.0f, -refDir.x);
auto targetDirection = target - origin; auto targetDir = target - origin;
float dot = headingNormal.Dot(targetDirection); float dot = headingNormal.Dot(targetDir);
if (dot > 0.0f) if (dot > 0.0f)
return true; return true;

View file

@ -14,17 +14,17 @@ namespace TEN::Math::Geometry
Vector3i TranslatePoint(const Vector3i& point, short headingAngle, float forward, float down = 0.0f, float right = 0.0f); Vector3i TranslatePoint(const Vector3i& point, short headingAngle, float forward, float down = 0.0f, float right = 0.0f);
Vector3i TranslatePoint(const Vector3i& point, short headingAngle, const Vector3i& relOffset); Vector3i TranslatePoint(const Vector3i& point, short headingAngle, const Vector3i& relOffset);
Vector3i TranslatePoint(const Vector3i& point, const EulerAngles& orient, const Vector3i& relOffset); Vector3i TranslatePoint(const Vector3i& point, const EulerAngles& orient, const Vector3i& relOffset);
Vector3i TranslatePoint(const Vector3i& point, const EulerAngles& orient, float distance); Vector3i TranslatePoint(const Vector3i& point, const EulerAngles& orient, float dist);
Vector3i TranslatePoint(const Vector3i& point, const AxisAngle& orient, float distance); Vector3i TranslatePoint(const Vector3i& point, const AxisAngle& orient, float dist);
Vector3i TranslatePoint(const Vector3i& point, const Vector3& direction, float distance); Vector3i TranslatePoint(const Vector3i& point, const Vector3& dir, float dist);
// Float-based point translation // Float-based point translation
Vector3 TranslatePoint(const Vector3& point, short headingAngle, float forward, float down = 0.0f, float right = 0.0f); Vector3 TranslatePoint(const Vector3& point, short headingAngle, float forward, float down = 0.0f, float right = 0.0f);
Vector3 TranslatePoint(const Vector3& point, short headingAngle, const Vector3& relOffset); Vector3 TranslatePoint(const Vector3& point, short headingAngle, const Vector3& relOffset);
Vector3 TranslatePoint(const Vector3& point, const EulerAngles& orient, const Vector3& relOffset); Vector3 TranslatePoint(const Vector3& point, const EulerAngles& orient, const Vector3& relOffset);
Vector3 TranslatePoint(const Vector3& point, const EulerAngles& orient, float distance); Vector3 TranslatePoint(const Vector3& point, const EulerAngles& orient, float dist);
Vector3 TranslatePoint(const Vector3& point, const AxisAngle& orient, float distance); Vector3 TranslatePoint(const Vector3& point, const AxisAngle& orient, float dist);
Vector3 TranslatePoint(const Vector3& point, const Vector3& direction, float distance); Vector3 TranslatePoint(const Vector3& point, const Vector3& dir, float dist);
// Rotation // Rotation
Vector3 RotatePoint(const Vector3& point, const EulerAngles& rot); Vector3 RotatePoint(const Vector3& point, const EulerAngles& rot);
@ -39,10 +39,10 @@ namespace TEN::Math::Geometry
float GetDistanceToLine(const Vector3& origin, const Vector3& linePoint0, const Vector3& linePoint1); float GetDistanceToLine(const Vector3& origin, const Vector3& linePoint0, const Vector3& linePoint1);
Vector3 GetClosestPointOnLine(const Vector3& origin, const Vector3& linePoint0, const Vector3& linePoint1); Vector3 GetClosestPointOnLine(const Vector3& origin, const Vector3& linePoint0, const Vector3& linePoint1);
EulerAngles GetOrientToPoint(const Vector3& origin, const Vector3& target); EulerAngles GetOrientToPoint(const Vector3& origin, const Vector3& target);
EulerAngles GetRelOrientToNormal(short orient2D, const Vector3& normal, const Vector3& gravity = Vector3::UnitY); EulerAngles GetRelOrientToNormal(short orient, const Vector3& normal, const Vector3& gravity = Vector3::UnitY);
// Converters // Converters
Quaternion ConvertDirectionToQuat(const Vector3& direction); Quaternion ConvertDirectionToQuat(const Vector3& dir);
Vector3 ConvertQuatToDirection(const Quaternion& quat); Vector3 ConvertQuatToDirection(const Quaternion& quat);
// Point relation inquirers // Point relation inquirers

View file

@ -39,6 +39,16 @@ constexpr float TO_RAD(short shortAngle)
return ((shortAngle * DEGREES_TO_ONE_SHORT) * RADIAN); return ((shortAngle * DEGREES_TO_ONE_SHORT) * RADIAN);
} }
constexpr float RAD_TO_DEG(float radians)
{
return ((radians * 180.0f) / PI);
}
constexpr float DEG_TO_RAD(float degrees)
{
return ((degrees * PI) / 180.0f);
}
float phd_sin(short x); float phd_sin(short x);
float phd_cos(short x); float phd_cos(short x);
int phd_atan(int y, int x); int phd_atan(int y, int x);

View file

@ -14,10 +14,10 @@ using namespace TEN::Math;
AxisAngle::AxisAngle(const Vector3& axis, short angle) AxisAngle::AxisAngle(const Vector3& axis, short angle)
{ {
auto axisNorm = axis; auto normalizedAxis = axis;
axisNorm.Normalize(); normalizedAxis.Normalize();
Axis = axisNorm; Axis = normalizedAxis;
Angle = angle; Angle = angle;
} }
@ -75,10 +75,10 @@ using namespace TEN::Math;
void AxisAngle::SetAxis(const Vector3& axis) void AxisAngle::SetAxis(const Vector3& axis)
{ {
auto axisNorm = axis; auto normalizedAxis = axis;
axisNorm.Normalize(); normalizedAxis.Normalize();
Axis = axisNorm; Axis = normalizedAxis;
} }
void AxisAngle::SetAngle(short angle) void AxisAngle::SetAngle(short angle)
@ -118,8 +118,8 @@ using namespace TEN::Math;
Vector3 AxisAngle::ToDirection() const Vector3 AxisAngle::ToDirection() const
{ {
// TODO: Works, but need to find a way without EulerAngles. -- Sezz 2023.03.08 // TODO: Works, but need to find a way without EulerAngles. -- Sezz 2023.03.08
auto refDirection = Geometry::RotatePoint(Vector3::Right, EulerAngles(Axis)); auto refDir = Geometry::RotatePoint(Vector3::Right, EulerAngles(Axis));
return Geometry::RotatePoint(refDirection, *this); return Geometry::RotatePoint(refDir, *this);
} }
EulerAngles AxisAngle::ToEulerAngles() const EulerAngles AxisAngle::ToEulerAngles() const

View file

@ -12,13 +12,13 @@ using namespace TEN::Math;
//{ //{
const EulerAngles EulerAngles::Zero = EulerAngles(0, 0, 0); const EulerAngles EulerAngles::Zero = EulerAngles(0, 0, 0);
EulerAngles::EulerAngles(const Vector3& direction) EulerAngles::EulerAngles(const Vector3& dir)
{ {
auto directionNorm = direction; auto normalizedDir = dir;
directionNorm.Normalize(); normalizedDir.Normalize();
x = FROM_RAD(-asin(directionNorm.y)); x = FROM_RAD(-asin(normalizedDir.y));
y = FROM_RAD(atan2(directionNorm.x, directionNorm.z)); y = FROM_RAD(atan2(normalizedDir.x, normalizedDir.z));
z = 0; z = 0;
} }
@ -191,19 +191,19 @@ using namespace TEN::Math;
return *this; return *this;
} }
EulerAngles& EulerAngles::operator *=(float scale) EulerAngles& EulerAngles::operator *=(float scalar)
{ {
x *= scale; x *= scalar;
y *= scale; y *= scalar;
z *= scale; z *= scalar;
return *this; return *this;
} }
EulerAngles& EulerAngles::operator /=(float scale) EulerAngles& EulerAngles::operator /=(float scalar)
{ {
x /= scale; x /= scalar;
y /= scale; y /= scalar;
z /= scale; z /= scalar;
return *this; return *this;
} }
@ -222,14 +222,14 @@ using namespace TEN::Math;
return EulerAngles(x * eulers.x, y * eulers.y, z * eulers.z); return EulerAngles(x * eulers.x, y * eulers.y, z * eulers.z);
} }
EulerAngles EulerAngles::operator *(float scale) const EulerAngles EulerAngles::operator *(float scalar) const
{ {
return EulerAngles((short)round(x * scale), (short)round(y * scale), (short)round(z * scale)); return EulerAngles((short)round(x * scalar), (short)round(y * scalar), (short)round(z * scalar));
} }
EulerAngles EulerAngles::operator /(float scale) const EulerAngles EulerAngles::operator /(float scalar) const
{ {
return EulerAngles((short)round(x / scale), (short)round(y / scale), (short)round(z / scale)); return EulerAngles((short)round(x / scalar), (short)round(y / scalar), (short)round(z / scalar));
} }
float EulerAngles::ClampAlpha(float alpha) float EulerAngles::ClampAlpha(float alpha)

View file

@ -17,7 +17,7 @@
// Constructors // Constructors
constexpr EulerAngles() {}; constexpr EulerAngles() {};
constexpr EulerAngles(short x, short y, short z) { this->x = x; this->y = y; this->z = z; }; constexpr EulerAngles(short x, short y, short z) { this->x = x; this->y = y; this->z = z; };
EulerAngles(const Vector3& direction); EulerAngles(const Vector3& dir);
EulerAngles(const AxisAngle& axisAngle); EulerAngles(const AxisAngle& axisAngle);
EulerAngles(const Quaternion& quat); EulerAngles(const Quaternion& quat);
EulerAngles(const Matrix& rotMatrix); EulerAngles(const Matrix& rotMatrix);
@ -44,13 +44,13 @@
EulerAngles& operator +=(const EulerAngles& eulers); EulerAngles& operator +=(const EulerAngles& eulers);
EulerAngles& operator -=(const EulerAngles& eulers); EulerAngles& operator -=(const EulerAngles& eulers);
EulerAngles& operator *=(const EulerAngles& eulers); EulerAngles& operator *=(const EulerAngles& eulers);
EulerAngles& operator *=(float scale); EulerAngles& operator *=(float scalar);
EulerAngles& operator /=(float scale); EulerAngles& operator /=(float scalar);
EulerAngles operator +(const EulerAngles& eulers) const; EulerAngles operator +(const EulerAngles& eulers) const;
EulerAngles operator -(const EulerAngles& eulers) const; EulerAngles operator -(const EulerAngles& eulers) const;
EulerAngles operator *(const EulerAngles& eulers) const; EulerAngles operator *(const EulerAngles& eulers) const;
EulerAngles operator *(float scale) const; EulerAngles operator *(float scalar) const;
EulerAngles operator /(float scale) const; EulerAngles operator /(float scalar) const;
private: private:
// Temporary. Will be integrated into eventual Angle class. // Temporary. Will be integrated into eventual Angle class.

View file

@ -66,14 +66,16 @@
return ((Vector3(X2, Y2, Z2) - Vector3(X1, Y1, Z1)) / 2); return ((Vector3(X2, Y2, Z2) - Vector3(X1, Y1, Z1)) / 2);
} }
void GameBoundingBox::RotateNoPersp(const EulerAngles& orient, const GameBoundingBox& bounds) void GameBoundingBox::Rotate(const EulerAngles& rot)
{ {
auto rotMatrix = orient.ToRotationMatrix(); // Get box min and max values.
auto boxMin = Vector3(bounds.X1, bounds.Y1, bounds.Z1); auto boxMax = Vector3(X2, Y2, Z2);
auto boxMax = Vector3(bounds.X2, bounds.Y2, bounds.Z2); auto boxMin = Vector3(X1, Y1, Z1);
boxMin = Vector3::Transform(boxMin, rotMatrix); // Rotate min and max values.
auto rotMatrix = rot.ToRotationMatrix();
boxMax = Vector3::Transform(boxMax, rotMatrix); boxMax = Vector3::Transform(boxMax, rotMatrix);
boxMin = Vector3::Transform(boxMin, rotMatrix);
X1 = (int)round(boxMin.x); X1 = (int)round(boxMin.x);
X2 = (int)round(boxMax.x); X2 = (int)round(boxMax.x);
@ -90,7 +92,7 @@
BoundingOrientedBox GameBoundingBox::ToBoundingOrientedBox(const Vector3& pos, const Quaternion& orient) const BoundingOrientedBox GameBoundingBox::ToBoundingOrientedBox(const Vector3& pos, const Quaternion& orient) const
{ {
BoundingOrientedBox box; auto box = BoundingOrientedBox();
BoundingOrientedBox(GetCenter(), GetExtents(), Vector4::UnitY).Transform(box, 1.0f, orient, pos); BoundingOrientedBox(GetCenter(), GetExtents(), Vector4::UnitY).Transform(box, 1.0f, orient, pos);
return box; return box;
} }
@ -127,19 +129,19 @@
Z1 - pose.Position.z, Z2 - pose.Position.z); Z1 - pose.Position.z, Z2 - pose.Position.z);
} }
GameBoundingBox GameBoundingBox::operator *(float scale) const GameBoundingBox GameBoundingBox::operator *(float scalar) const
{ {
return GameBoundingBox( return GameBoundingBox(
X1 * scale, X2 * scale, X1 * scalar, X2 * scalar,
Y1 * scale, Y2 * scale, Y1 * scalar, Y2 * scalar,
Z1 * scale, Z2 * scale); Z1 * scalar, Z2 * scalar);
} }
GameBoundingBox GameBoundingBox::operator /(float scale) const GameBoundingBox GameBoundingBox::operator /(float scalar) const
{ {
return GameBoundingBox( return GameBoundingBox(
X1 / scale, X2 / scale, X1 / scalar, X2 / scalar,
Y1 / scale, Y2 / scale, Y1 / scalar, Y2 / scalar,
Z1 / scale, Z2 / scale); Z1 / scalar, Z2 / scalar);
} }
//} //}

View file

@ -36,7 +36,7 @@ struct ObjectInfo;
Vector3 GetExtents() const; Vector3 GetExtents() const;
// Utilities // Utilities
void RotateNoPersp(const EulerAngles& orient, const GameBoundingBox& bounds); void Rotate(const EulerAngles& rot);
// Converters // Converters
BoundingOrientedBox ToBoundingOrientedBox(const Pose& pose) const; BoundingOrientedBox ToBoundingOrientedBox(const Pose& pose) const;
@ -47,7 +47,7 @@ struct ObjectInfo;
GameBoundingBox operator +(const Pose& pose) const; GameBoundingBox operator +(const Pose& pose) const;
GameBoundingBox operator -(const GameBoundingBox& bounds) const; GameBoundingBox operator -(const GameBoundingBox& bounds) const;
GameBoundingBox operator -(const Pose& pose) const; GameBoundingBox operator -(const Pose& pose) const;
GameBoundingBox operator *(float scale) const; GameBoundingBox operator *(float scalar) const;
GameBoundingBox operator /(float scale) const; GameBoundingBox operator /(float scalar) const;
}; };
//} //}

View file

@ -94,19 +94,19 @@
return *this; return *this;
} }
GameVector& GameVector::operator *=(float scale) GameVector& GameVector::operator *=(float scalar)
{ {
x *= scale; x *= scalar;
y *= scale; y *= scalar;
z *= scale; z *= scalar;
return *this; return *this;
} }
GameVector& GameVector::operator /=(float scale) GameVector& GameVector::operator /=(float scalar)
{ {
x /= scale; x /= scalar;
y /= scale; y /= scalar;
z /= scale; z /= scalar;
return *this; return *this;
} }
@ -125,13 +125,13 @@
return GameVector(x * vector.x, y * vector.y, z * vector.z, RoomNumber); return GameVector(x * vector.x, y * vector.y, z * vector.z, RoomNumber);
} }
GameVector GameVector::operator *(float scale) const GameVector GameVector::operator *(float scalar) const
{ {
return GameVector((int)round(x * scale), (int)round(y * scale), (int)round(z * scale), RoomNumber); return GameVector((int)round(x * scalar), (int)round(y * scalar), (int)round(z * scalar), RoomNumber);
} }
GameVector GameVector::operator /(float scale) const GameVector GameVector::operator /(float scalar) const
{ {
return GameVector((int)round(x / scale), (int)round(y / scale), (int)round(z / scale), RoomNumber); return GameVector((int)round(x / scalar), (int)round(y / scalar), (int)round(z / scalar), RoomNumber);
} }
//} //}

View file

@ -35,12 +35,12 @@ class Vector3i;
GameVector& operator +=(const GameVector& vector); GameVector& operator +=(const GameVector& vector);
GameVector& operator -=(const GameVector& vector); GameVector& operator -=(const GameVector& vector);
GameVector& operator *=(const GameVector& vector); GameVector& operator *=(const GameVector& vector);
GameVector& operator *=(float scale); GameVector& operator *=(float scalar);
GameVector& operator /=(float scale); GameVector& operator /=(float scalar);
GameVector operator +(const GameVector& vector) const; GameVector operator +(const GameVector& vector) const;
GameVector operator -(const GameVector& vector) const; GameVector operator -(const GameVector& vector) const;
GameVector operator *(const GameVector& vector) const; GameVector operator *(const GameVector& vector) const;
GameVector operator *(float scale) const; GameVector operator *(float scalar) const;
GameVector operator /(float scale) const; GameVector operator /(float scalar) const;
}; };
//} //}

View file

@ -64,20 +64,21 @@ using namespace TEN::Math;
Position = Geometry::TranslatePoint(Position, headingAngle, forward, down, right); Position = Geometry::TranslatePoint(Position, headingAngle, forward, down, right);
} }
void Pose::Translate(const EulerAngles& orient, float distance) void Pose::Translate(const EulerAngles& orient, float dist)
{ {
Position = Geometry::TranslatePoint(Position, orient, distance); Position = Geometry::TranslatePoint(Position, orient, dist);
} }
void Pose::Translate(const Vector3& direction, float distance) void Pose::Translate(const Vector3& dir, float dist)
{ {
Position = Geometry::TranslatePoint(Position, direction, distance); Position = Geometry::TranslatePoint(Position, dir, dist);
} }
bool Pose::operator ==(const Pose& pose) const bool Pose::operator ==(const Pose& pose) const
{ {
return ((Position == pose.Position) && (Orientation == pose.Orientation)); return ((Position == pose.Position) && (Orientation == pose.Orientation));
} }
bool Pose::operator !=(const Pose& pose) const bool Pose::operator !=(const Pose& pose) const
{ {
return !(*this == pose); return !(*this == pose);

View file

@ -27,8 +27,8 @@
// Utilities // Utilities
void Translate(short headingAngle, float forward, float down = 0.0f, float right = 0.0f); void Translate(short headingAngle, float forward, float down = 0.0f, float right = 0.0f);
void Translate(const EulerAngles& orient, float distance); void Translate(const EulerAngles& orient, float dist);
void Translate(const Vector3& direction, float distance); void Translate(const Vector3& dir, float dist);
// Operators // Operators
bool operator ==(const Pose& pose) const; bool operator ==(const Pose& pose) const;

View file

@ -64,17 +64,17 @@ namespace TEN::Math
return *this; return *this;
} }
Vector2i& Vector2i::operator *=(float scale) Vector2i& Vector2i::operator *=(float scalar)
{ {
x *= scale; x *= scalar;
y *= scale; y *= scalar;
return *this; return *this;
} }
Vector2i& Vector2i::operator /=(float scale) Vector2i& Vector2i::operator /=(float scalar)
{ {
x /= scale; x /= scalar;
y /= scale; y /= scalar;
return *this; return *this;
} }
@ -93,13 +93,13 @@ namespace TEN::Math
return Vector2i(x * vector.x, y * vector.y); return Vector2i(x * vector.x, y * vector.y);
} }
Vector2i Vector2i::operator *(float scale) const Vector2i Vector2i::operator *(float scalar) const
{ {
return Vector2i((int)round(x * scale), (int)round(y * scale)); return Vector2i((int)round(x * scalar), (int)round(y * scalar));
} }
Vector2i Vector2i::operator /(float scale) const Vector2i Vector2i::operator /(float scalar) const
{ {
return Vector2i((int)round(x / scale), (int)round(y / scale)); return Vector2i((int)round(x / scalar), (int)round(y / scalar));
} }
} }

View file

@ -31,12 +31,12 @@ namespace TEN::Math
Vector2i& operator +=(const Vector2i& vector); Vector2i& operator +=(const Vector2i& vector);
Vector2i& operator -=(const Vector2i& vector); Vector2i& operator -=(const Vector2i& vector);
Vector2i& operator *=(const Vector2i& vector); Vector2i& operator *=(const Vector2i& vector);
Vector2i& operator *=(float scale); Vector2i& operator *=(float scalar);
Vector2i& operator /=(float scale); Vector2i& operator /=(float scalar);
Vector2i operator +(const Vector2i& vector) const; Vector2i operator +(const Vector2i& vector) const;
Vector2i operator -(const Vector2i& vector) const; Vector2i operator -(const Vector2i& vector) const;
Vector2i operator *(const Vector2i& vector) const; Vector2i operator *(const Vector2i& vector) const;
Vector2i operator *(float scale) const; Vector2i operator *(float scalar) const;
Vector2i operator /(float scale) const; Vector2i operator /(float scalar) const;
}; };
} }

View file

@ -69,19 +69,19 @@
return *this; return *this;
} }
Vector3i& Vector3i::operator *=(float scale) Vector3i& Vector3i::operator *=(float scalar)
{ {
x *= scale; x *= scalar;
y *= scale; y *= scalar;
z *= scale; z *= scalar;
return *this; return *this;
} }
Vector3i& Vector3i::operator /=(float scale) Vector3i& Vector3i::operator /=(float scalar)
{ {
x /= scale; x /= scalar;
y /= scale; y /= scalar;
z /= scale; z /= scalar;
return *this; return *this;
} }
@ -100,13 +100,13 @@
return Vector3i(x * vector.x, y * vector.y, z * vector.z); return Vector3i(x * vector.x, y * vector.y, z * vector.z);
} }
Vector3i Vector3i::operator *(float scale) const Vector3i Vector3i::operator *(float scalar) const
{ {
return Vector3i((int)round(x * scale), (int)round(y * scale), (int)round(z * scale)); return Vector3i((int)round(x * scalar), (int)round(y * scalar), (int)round(z * scalar));
} }
Vector3i Vector3i::operator /(float scale) const Vector3i Vector3i::operator /(float scalar) const
{ {
return Vector3i((int)round(x / scale), (int)round(y / scale), (int)round(z / scale)); return Vector3i((int)round(x / scalar), (int)round(y / scalar), (int)round(z / scalar));
} }
//} //}

View file

@ -32,12 +32,12 @@
Vector3i& operator +=(const Vector3i& vector); Vector3i& operator +=(const Vector3i& vector);
Vector3i& operator -=(const Vector3i& vector); Vector3i& operator -=(const Vector3i& vector);
Vector3i& operator *=(const Vector3i& vector); Vector3i& operator *=(const Vector3i& vector);
Vector3i& operator *=(float scale); Vector3i& operator *=(float scalar);
Vector3i& operator /=(float scale); Vector3i& operator /=(float scalar);
Vector3i operator +(const Vector3i& vector) const; Vector3i operator +(const Vector3i& vector) const;
Vector3i operator -(const Vector3i& vector) const; Vector3i operator -(const Vector3i& vector) const;
Vector3i operator *(const Vector3i& vector) const; Vector3i operator *(const Vector3i& vector) const;
Vector3i operator *(float scale) const; Vector3i operator *(float scalar) const;
Vector3i operator /(float scale) const; Vector3i operator /(float scalar) const;
}; };
//} //}

View file

@ -30,19 +30,19 @@ namespace TEN::Math::Random
return Vector2(cos(angle), sin(angle)); return Vector2(cos(angle), sin(angle));
} }
Vector2 GeneratePoint2DInSquare(const Vector2& pos2D, short orient2D, float apothem) Vector2 GeneratePoint2DInSquare(const Vector2& pos, short orient, float apothem)
{ {
auto rotMatrix = Matrix::CreateRotationZ(orient2D); auto rotMatrix = Matrix::CreateRotationZ(orient);
auto relPoint = Vector2( auto relPoint = Vector2(
GenerateFloat(-apothem, apothem), GenerateFloat(-apothem, apothem),
GenerateFloat(-apothem, apothem)); GenerateFloat(-apothem, apothem));
return (pos2D + Vector2::Transform(relPoint, rotMatrix)); return (pos + Vector2::Transform(relPoint, rotMatrix));
} }
Vector2 GeneratePoint2DInCircle(const Vector2& pos2D, float radius) Vector2 GeneratePoint2DInCircle(const Vector2& pos, float radius)
{ {
// Use rejection sampling. // Use rejection sampling method.
auto relPoint = Vector2::Zero; auto relPoint = Vector2::Zero;
do do
{ {
@ -51,7 +51,7 @@ namespace TEN::Math::Random
GenerateFloat(-1.0f, 1.0f)); GenerateFloat(-1.0f, 1.0f));
} while (relPoint.LengthSquared() > 1.0f); } while (relPoint.LengthSquared() > 1.0f);
return (pos2D + (relPoint * radius)); return (pos + (relPoint * radius));
} }
Vector3 GenerateDirection() Vector3 GenerateDirection()
@ -59,24 +59,24 @@ namespace TEN::Math::Random
float theta = GenerateFloat(0.0f, PI_MUL_2); // Generate angle in full circle. float theta = GenerateFloat(0.0f, PI_MUL_2); // Generate angle in full circle.
float phi = GenerateFloat(0.0f, PI); // Generate angle in sphere's upper half. float phi = GenerateFloat(0.0f, PI); // Generate angle in sphere's upper half.
auto direction = Vector3( auto dir = Vector3(
sin(phi) * cos(theta), sin(phi) * cos(theta),
sin(phi) * sin(theta), sin(phi) * sin(theta),
cos(phi)); cos(phi));
direction.Normalize(); dir.Normalize();
return direction; return dir;
} }
Vector3 GenerateDirectionInCone(const Vector3& direction, float semiangleInDeg) Vector3 GenerateDirectionInCone(const Vector3& dir, float semiangleInDeg)
{ {
float x = GenerateFloat(-semiangleInDeg, semiangleInDeg) * RADIAN; float x = GenerateFloat(-semiangleInDeg, semiangleInDeg) * RADIAN;
float y = GenerateFloat(-semiangleInDeg, semiangleInDeg) * RADIAN; float y = GenerateFloat(-semiangleInDeg, semiangleInDeg) * RADIAN;
float z = GenerateFloat(-semiangleInDeg, semiangleInDeg) * RADIAN; float z = GenerateFloat(-semiangleInDeg, semiangleInDeg) * RADIAN;
auto rotMatrix = Matrix::CreateRotationX(x) * Matrix::CreateRotationY(y) * Matrix::CreateRotationZ(z); auto rotMatrix = Matrix::CreateRotationX(x) * Matrix::CreateRotationY(y) * Matrix::CreateRotationZ(z);
auto directionInCone = Vector3::TransformNormal(direction, rotMatrix); auto dirInCone = Vector3::TransformNormal(dir, rotMatrix);
directionInCone.Normalize(); dirInCone.Normalize();
return directionInCone; return dirInCone;
} }
Vector3 GeneratePointInBox(const BoundingOrientedBox& box) Vector3 GeneratePointInBox(const BoundingOrientedBox& box)
@ -92,7 +92,7 @@ namespace TEN::Math::Random
Vector3 GeneratePointInSphere(const BoundingSphere& sphere) Vector3 GeneratePointInSphere(const BoundingSphere& sphere)
{ {
// Use rejection sampling. // Use rejection sampling method.
auto relPoint = Vector3::Zero; auto relPoint = Vector3::Zero;
do do
{ {
@ -120,19 +120,19 @@ namespace TEN::Math::Random
return (sphere.Center + (relPoint * sphere.Radius)); return (sphere.Center + (relPoint * sphere.Radius));
} }
bool TestProbability(float probability) bool TestProbability(float prob)
{ {
probability = std::clamp(probability, 0.0f, 1.0f); prob = std::clamp(prob, 0.0f, 1.0f);
if (probability == 0.0f) if (prob == 0.0f)
{ {
return false; return false;
} }
else if (probability == 1.0f) else if (prob == 1.0f)
{ {
return true; return true;
} }
return (GenerateFloat(0.0f, 1.0f) < probability); return (GenerateFloat(0.0f, 1.0f) < prob);
} }
} }

View file

@ -9,15 +9,15 @@ namespace TEN::Math::Random
// 2D geometric generation // 2D geometric generation
Vector2 GenerateDirection2D(); Vector2 GenerateDirection2D();
Vector2 GeneratePoint2DInSquare(const Vector2& pos2D, short orient2D, float apothem); Vector2 GeneratePoint2DInSquare(const Vector2& pos, short orient, float apothem);
Vector2 GeneratePoint2DInCircle(const Vector2& pos2D, float radius); Vector2 GeneratePoint2DInCircle(const Vector2& pos, float radius);
// 3D geometric generation // 3D geometric generation
Vector3 GenerateDirection(); Vector3 GenerateDirection();
Vector3 GenerateDirectionInCone(const Vector3& direction, float semiangleInDeg); Vector3 GenerateDirectionInCone(const Vector3& dir, float semiangleInDeg);
Vector3 GeneratePointInBox(const BoundingOrientedBox& box); Vector3 GeneratePointInBox(const BoundingOrientedBox& box);
Vector3 GeneratePointInSphere(const BoundingSphere& sphere); Vector3 GeneratePointInSphere(const BoundingSphere& sphere);
Vector3 GeneratePointOnSphere(const BoundingSphere& sphere); Vector3 GeneratePointOnSphere(const BoundingSphere& sphere);
bool TestProbability(float probability); bool TestProbability(float probab);
} }

View file

@ -5,95 +5,108 @@
namespace TEN::Math::Solvers namespace TEN::Math::Solvers
{ {
std::pair<float, float> SolveQuadratic(float a, float b, float c) std::optional<QuadraticSolution> SolveQuadratic(float a, float b, float c)
{ {
auto result = std::pair(INFINITY, INFINITY); if (abs(a) < EPSILON)
if (abs(a) < FLT_EPSILON)
{ {
if (abs(b) < FLT_EPSILON) // 0 solutions.
return result; // Zero solutions. if (abs(b) < EPSILON)
return std::nullopt;
result.first = -c / b; // 1 solution.
result.second = result.first; float root = -c / b;
return result; // One solution. return QuadraticSolution{ root, root };
} }
// 0 solutions.
float discriminant = SQUARE(b) - (4.0f * a * c); float discriminant = SQUARE(b) - (4.0f * a * c);
if (discriminant < 0.0f) if (discriminant < 0.0f)
return result; // Zero solutions. return std::nullopt;
float inv2a = 1.0f / (2.0f * a); float inv2a = 1.0f / (2.0f * a);
if (discriminant < FLT_EPSILON) // 1 solution.
if (discriminant < EPSILON)
{ {
result.first = -b * inv2a; float root = -b * inv2a;
result.second = result.first; return QuadraticSolution{ root, root };
return result; // One solution.
} }
// 2 solutions.
discriminant = sqrt(discriminant); discriminant = sqrt(discriminant);
result.first = (-b - discriminant) * inv2a; float root0 = (-b - discriminant) * inv2a;
result.second = (-b + discriminant) * inv2a; float root1 = (-b + discriminant) * inv2a;
return result; // Two solutions. return QuadraticSolution{ root0, root1 };
} }
bool SolveIK2D(const Vector2& target, float length0, float length1, Vector2& middle) IK2DSolution SolveIK2D(const Vector2& origin, const Vector2& target, float length0, float length1)
{ {
float length = target.Length(); auto scaledTarget = target;
if (length > (length0 + length1)) auto lengthMax = length0 + length1;
return false; auto dir = target - origin;
dir.Normalize();
bool flipXY = (target.x < target.y); // Check if target is within reach.
float a = flipXY ? target.y : target.x; float dist = Vector2::Distance(origin, target);
float b = flipXY ? target.x : target.y; if (dist > lengthMax)
assert(abs(a) > FLT_EPSILON); scaledTarget = origin + (dir * lengthMax);
// Ensure line slope is well defined.
bool flipXY = (scaledTarget.x < scaledTarget.y);
float a = flipXY ? (scaledTarget.y - origin.y) : (scaledTarget.x - origin.x);
float b = flipXY ? (scaledTarget.x - origin.x) : (scaledTarget.y - origin.y);
assertion(abs(a) >= EPSILON, "SolveIK2D() failed.");
float m = ((SQUARE(length0) - SQUARE(length1)) + (SQUARE(a) + SQUARE(b))) / (2.0f * a); float m = ((SQUARE(length0) - SQUARE(length1)) + (SQUARE(a) + SQUARE(b))) / (2.0f * a);
float n = b / a; float n = b / a;
auto quadratic = SolveQuadratic(1.0f + SQUARE(n), -2.0f * (m * n), SQUARE(m) - SQUARE(length0));
if (quadratic.first != INFINITY && quadratic.second != INFINITY) auto quadraticSol = SolveQuadratic(1.0f + SQUARE(n), -2.0f * (m * n), SQUARE(m) - SQUARE(length0));
auto middle = Vector2::Zero;
// Solution is valid; define middle accurately.
if (quadraticSol.has_value())
{ {
middle.x = flipXY ? quadratic.second : (m - (n * quadratic.second)); middle = origin + (flipXY ?
middle.y = flipXY ? (m - (n * quadratic.second)) : quadratic.second; Vector2(quadraticSol->Root1, (m - (n * quadraticSol->Root1))) :
return true; Vector2(quadraticSol->Root0, (m - (n * quadraticSol->Root0))));
}
// Solution is invalid: define middle as point on line segment between origin and target.
else
{
middle = origin + (dir * length0);
} }
middle = target * (length0 / length); return IK2DSolution{ origin, middle, scaledTarget };
return false;
} }
bool SolveIK3D(const Vector3& origin, const Vector3& target, const Vector3& pole, float length0, float length1, Vector3& middle) IK3DSolution SolveIK3D(const Vector3& origin, const Vector3& target, const Vector3& pole, float length0, float length1)
{ {
auto directionNorm = target - origin; auto dir = (target - origin);
directionNorm.Normalize(); dir.Normalize();
auto normal = directionNorm.Cross(pole - origin); auto normal = dir.Cross(pole - origin);
normal.Normalize(); normal.Normalize();
// TODO: Check what this means. // Construct transform matrix.
Matrix matrix; auto tMatrix = Matrix(
auto normalCrossDirectionNorm = normal.Cross(directionNorm); Vector4(normal.Cross(dir)),
matrix._11 = normalCrossDirectionNorm.x; Vector4(dir),
matrix._12 = normalCrossDirectionNorm.y; Vector4(normal),
matrix._13 = normalCrossDirectionNorm.z; Vector4(origin.x, origin.y, origin.z, 1.0f));
matrix._21 = directionNorm.x;
matrix._22 = directionNorm.y;
matrix._23 = directionNorm.z;
matrix._31 = normal.x;
matrix._32 = normal.y;
matrix._33 = normal.z;
matrix._41 = origin.x;
matrix._42 = origin.y;
matrix._43 = origin.z;
matrix._44 = 1.0f;
auto middle2D = Vector2(middle); // Get relative 2D IK solution.
bool result = SolveIK2D(Vector2(Vector3::Transform(target, matrix.Invert())), length0, length1, middle2D); auto inverseMatrix = tMatrix.Invert();
auto relTarget = Vector3::Transform(target, inverseMatrix);
auto ikSolution2D = SolveIK2D(Vector2::Zero, Vector2(relTarget), length0, length1);
middle = Vector3(middle2D); // Calculate absolute middle position.
middle = Vector3::Transform(middle, matrix); auto relMiddle = Vector3(ikSolution2D.Middle.x, ikSolution2D.Middle.y, 0.0f);
auto middle = Vector3::Transform(relMiddle, tMatrix);
return result; // Calculate absolute end position.
auto localEnd = Vector3(ikSolution2D.End.x, ikSolution2D.End.y, 0.0f);
auto end = Vector3::Transform(localEnd, tMatrix);
return IK3DSolution{ origin, middle, end };
} }
} }

View file

@ -2,8 +2,27 @@
namespace TEN::Math::Solvers namespace TEN::Math::Solvers
{ {
std::pair<float, float> SolveQuadratic(float a, float b, float c); struct QuadraticSolution
{
float Root0 = 0.0f;
float Root1 = 0.0f;
};
bool SolveIK2D(const Vector2& target, float length0, float length1, Vector2& middle); struct IK2DSolution
bool SolveIK3D(const Vector3& origin, const Vector3& target, const Vector3& pole, float length0, float length1, Vector3& middle); {
Vector2 Base = Vector2::Zero;
Vector2 Middle = Vector2::Zero;
Vector2 End = Vector2::Zero;
};
struct IK3DSolution
{
Vector3 Base = Vector3::Zero;
Vector3 Middle = Vector3::Zero;
Vector3 End = Vector3::Zero;
};
std::optional<QuadraticSolution> SolveQuadratic(float a, float b, float c);
IK2DSolution SolveIK2D(const Vector2& origin, const Vector2& target, float length0, float length1);
IK3DSolution SolveIK3D(const Vector3& origin, const Vector3& target, const Vector3& pole, float length0, float length1);
} }

View file

@ -207,7 +207,7 @@ void ElectricityWiresControl(short itemNumber)
if ((GetRandomControl() & 127) < 16) if ((GetRandomControl() & 127) < 16)
{ {
SpawnRipple( SpawnRipple(
Vector3(pos.x, floor->GetSurfaceHeight(pos.x, pos.y, pos.z, true), pos.z), Vector3(pos.x, floor->GetSurfaceHeight(pos, true), pos.z),
roomNumber, roomNumber,
Random::GenerateFloat(32.0f, 40.0f), Random::GenerateFloat(32.0f, 40.0f),
(int)RippleFlags::LowOpacity); (int)RippleFlags::LowOpacity);

View file

@ -275,7 +275,7 @@ namespace TEN::Entities::Doors
doorItem->ItemFlags[0]--; doorItem->ItemFlags[0]--;
doorItem->Pose.Position.y -= TEN::Entities::Switches::COG_DOOR_SPEED; doorItem->Pose.Position.y -= TEN::Entities::Switches::COG_DOOR_SPEED;
int y = bounds.Y1 + doorItem->ItemFlags[2] - STEP_SIZE; int y = bounds.Y1 + doorItem->ItemFlags[2] - CLICK(1);
if (doorItem->Pose.Position.y < y) if (doorItem->Pose.Position.y < y)
{ {
doorItem->Pose.Position.y = y; doorItem->Pose.Position.y = y;
@ -362,7 +362,7 @@ namespace TEN::Entities::Doors
{ {
if (!item->itemFlags[0]) if (!item->itemFlags[0])
SoundEffect(SFX_TR5_LIFT_DOORS, &item->pos); SoundEffect(SFX_TR5_LIFT_DOORS, &item->pos);
item->itemFlags[0] += STEP_SIZE; item->itemFlags[0] += CLICK(1);
} }
} }
else else
@ -371,7 +371,7 @@ namespace TEN::Entities::Doors
{ {
if (item->itemFlags[0] == BLOCK(4)) if (item->itemFlags[0] == BLOCK(4))
SoundEffect(SFX_TR5_LIFT_DOORS, &item->pos); SoundEffect(SFX_TR5_LIFT_DOORS, &item->pos);
item->itemFlags[0] -= STEP_SIZE; item->itemFlags[0] -= CLICK(1);
} }
if (!door->opened) if (!door->opened)
{ {

View file

@ -0,0 +1,112 @@
#include "framework.h"
#include "Objects/Generic/Object/Pushable/PushableBridge.h"
#include "Game/collision/floordata.h"
#include "Objects/Generic/Object/Pushable/PushableObject.h"
#include "Specific/level.h"
using namespace TEN::Collision::Floordata;
namespace TEN::Entities::Generic
{
std::optional<int> PushableBridgeFloor(int itemNumber, int x, int y, int z)
{
auto& pushableItem = g_Level.Items[itemNumber];
const auto& pushable = GetPushableInfo(pushableItem);
auto boxHeight = GetBridgeItemIntersect(Vector3i(x, y, z), itemNumber, false);
if (pushableItem.Active &&pushableItem.Status != ITEM_INVISIBLE &&
pushable.UseRoomCollision &&
boxHeight.has_value())
{
int height = pushableItem.Pose.Position.y - GetPushableHeight(pushableItem);
return height;
}
return std::nullopt;
}
std::optional<int> PushableBridgeCeiling(int itemNumber, int x, int y, int z)
{
auto& pushableItem = g_Level.Items[itemNumber];
const auto& pushable = GetPushableInfo(pushableItem);
auto boxHeight = GetBridgeItemIntersect(Vector3i(x, y, z), itemNumber, true);
if (pushableItem.Active && pushableItem.Status != ITEM_INVISIBLE &&
pushable.UseRoomCollision &&
boxHeight.has_value())
{
return pushableItem.Pose.Position.y;
}
return std::nullopt;
}
int PushableBridgeFloorBorder(int itemNumber)
{
auto& pushableItem = g_Level.Items[itemNumber];
return (pushableItem.Pose.Position.y - GetPushableHeight(pushableItem));
}
int PushableBridgeCeilingBorder(int itemNumber)
{
auto& pushableItem = g_Level.Items[itemNumber];
return pushableItem.Pose.Position.y;
}
void AddPushableBridge(ItemInfo& pushableItem)
{
auto& pushable = GetPushableInfo(pushableItem);
if (pushable.UseRoomCollision)
{
AddBridge(pushableItem.Index);
pushable.UseBridgeCollision = true;
}
}
void AddPushableStackBridge(ItemInfo& pushableItem, bool addBridge)
{
auto* pushableItemPtr = &g_Level.Items[pushableItem.Index];
const auto* pushablePtr = &GetPushableInfo(*pushableItemPtr);
// NOTE: Can't have stacked items on bridge.
if (!pushablePtr->UseRoomCollision)
return;
if (pushablePtr->UseBridgeCollision)
addBridge ? AddBridge(pushableItem.Index) : RemoveBridge(pushableItem.Index);
while (pushablePtr->Stack.ItemNumberAbove != NO_ITEM)
{
pushableItemPtr = &g_Level.Items[pushablePtr->Stack.ItemNumberAbove];
pushablePtr = &GetPushableInfo(*pushableItemPtr);
if (pushablePtr->UseBridgeCollision)
addBridge ? AddBridge(pushableItemPtr->Index) : RemoveBridge(pushableItemPtr->Index);
}
}
void RemovePushableBridge(ItemInfo& pushableItem)
{
auto& pushable = GetPushableInfo(pushableItem);
if (pushable.UseRoomCollision)
{
RemoveBridge(pushableItem.Index);
pushable.UseBridgeCollision = false;
}
}
void UpdatePushableBridge(const ItemInfo& pushableItem)
{
const auto& pushable = GetPushableInfo(pushableItem);
if (pushable.UseRoomCollision)
UpdateBridgeItem(pushableItem.Index);
}
}

View file

@ -0,0 +1,16 @@
#pragma once
struct ItemInfo;
namespace TEN::Entities::Generic
{
std::optional<int> PushableBridgeFloor(int itemNumber, int x, int y, int z);
std::optional<int> PushableBridgeCeiling(int itemNumber, int x, int y, int z);
int PushableBridgeFloorBorder(int itemNumber);
int PushableBridgeCeilingBorder(int itemNumber);
void AddPushableBridge(ItemInfo& pushableItem);
void AddPushableStackBridge(ItemInfo& pushableItem, bool addBridge);
void RemovePushableBridge(ItemInfo& pushableItem);
void UpdatePushableBridge(const ItemInfo& pushableItem);
}

View file

@ -0,0 +1,382 @@
#include "framework.h"
#include "Objects/Generic/Object/Pushable/PushableCollision.h"
#include "Game/collision/collide_item.h"
#include "Game/collision/floordata.h"
#include "Game/Lara/lara.h"
#include "Game/Setup.h"
#include "Objects/Generic/Object/Pushable/PushableBridge.h"
#include "Objects/Generic/Object/Pushable/PushableObject.h"
#include "Objects/Generic/Object/Pushable/PushableStack.h"
#include "Specific/Input/Input.h"
#include "Specific/level.h"
using namespace TEN::Collision::Floordata;
using namespace TEN::Input;
namespace TEN::Entities::Generic
{
constexpr auto PUSHABLE_FLOOR_HEIGHT_TOLERANCE = 32;
bool IsPushableValid(ItemInfo& pushableItem)
{
auto& pushable = GetPushableInfo(pushableItem);
// NOTE: Requires positive OCB value for interaction.
if (pushableItem.Status == ITEM_INVISIBLE || pushableItem.TriggerFlags < 0)
return false;
auto pointColl = CollisionResult{};
if (pushable.UseRoomCollision)
{
RemovePushableBridge(pushableItem);
pointColl = GetCollision(&pushableItem);
AddPushableBridge(pushableItem);
}
else
{
pointColl = GetCollision(&pushableItem);
}
// 1) Check for wall.
if (pointColl.Block->IsWall(pushableItem.Pose.Position.x, pushableItem.Pose.Position.z))
return false;
// 2) Test height from floor.
int heightFromFloor = abs(pointColl.Position.Floor - pushableItem.Pose.Position.y);
if (heightFromFloor >= PUSHABLE_FLOOR_HEIGHT_TOLERANCE)
return false;
// 3) Test height from player. Prevents player from grabbing pushable at wrong elevation in stack.
int heightFromPlayer = abs(LaraItem->Pose.Position.y - pushableItem.Pose.Position.y);
if (heightFromPlayer >= PUSHABLE_FLOOR_HEIGHT_TOLERANCE)
return false;
return true;
}
bool IsPushableObjectMoveAllowed(ItemInfo& pushableItem, const Vector3i& targetPos, int targetRoomNumber)
{
auto& pushable = GetPushableInfo(pushableItem);
pushable.IsOnEdge = false;
auto pointColl = GetCollision(targetPos, targetRoomNumber);
// 1) Check for wall.
if (pointColl.Block->IsWall(targetPos.x, targetPos.z))
return false;
// 2) Check for gaps or steps.
int heightFromFloor = abs(pointColl.Position.Floor - pushableItem.Pose.Position.y);
if (heightFromFloor >= PUSHABLE_FLOOR_HEIGHT_TOLERANCE)
{
// Step.
if (pointColl.Position.Floor < pushableItem.Pose.Position.y)
{
return false;
}
// Gap.
else
{
pushable.IsOnEdge = true;
if (!pushable.CanFall || pushable.Stack.ItemNumberAbove != NO_ITEM)
return false;
}
}
// 3) Check for slippery floor slope.
if (pointColl.Position.FloorSlope)
return false;
// 4) Check for diagonal floor step.
if (pointColl.Position.DiagonalStep)
return false;
// 5) Test floor slope. TODO: Check slope angles of normals directly.
if ((pointColl.Block->GetSurfaceSlope(0, true) != Vector2::Zero) || (pointColl.Block->GetSurfaceSlope(1, true) != Vector2::Zero))
return false;
// Check for stopper flag.
/*if (collisionResult.Block->Stopper)
{
if (collisionResult.Position.Floor <= pushableItem.Pose.Position.y)
return false;
}*/
// Is ceiling (square or diagonal) high enough?
int distFromCeil = abs(pointColl.Position.Ceiling - pointColl.Position.Floor);
int blockHeight = GetStackHeight(pushableItem.Index) - PUSHABLE_FLOOR_HEIGHT_TOLERANCE;
if (distFromCeil < blockHeight)
return false;
// TODO: Don't modify item.
// Test object collision.
auto prevPos = pushableItem.Pose.Position;
pushableItem.Pose.Position = targetPos;
GetCollidedObjects(&pushableItem, BLOCK(0.25f), true, &CollidedItems[0], &CollidedMeshes[0], true);
pushableItem.Pose.Position = prevPos;
if (CollidedMeshes[0])
return false;
for (int i = 0; i < MAX_COLLIDED_OBJECTS; i++)
{
if (!CollidedItems[i])
break;
if (Objects[CollidedItems[i]->ObjectNumber].isPickup)
continue;
if (Objects[CollidedItems[i]->ObjectNumber].floor == nullptr) //??
return false;
auto& object = Objects[CollidedItems[i]->ObjectNumber];
int collidedIndex = CollidedItems[i] - g_Level.Items.data(); // Index of CollidedItems[i].
auto colPos = CollidedItems[i]->Pose.Position;
// Check if floor function returns nullopt.
if (object.floor(collidedIndex, colPos.x, colPos.y, colPos.z) == std::nullopt)
return false;
}
return true;
}
bool IsValidForPlayer(const ItemInfo& pushableItem)
{
auto playerOffset = Vector3i::Zero;
int quadrant = GetQuadrant(LaraItem->Pose.Orientation.y);
switch (quadrant)
{
case NORTH:
playerOffset.z = GetBestFrame(*LaraItem).Offset.z - BLOCK(1);
break;
case EAST:
playerOffset.x = GetBestFrame(*LaraItem).Offset.z - BLOCK(1);
break;
case SOUTH:
playerOffset.z = -GetBestFrame(*LaraItem).Offset.z + BLOCK(1);
break;
case WEST:
playerOffset.x = -GetBestFrame(*LaraItem).Offset.z + BLOCK(1);
break;
default:
break;
}
auto laraDetectionPos = LaraItem->Pose.Position + playerOffset;
auto pointColl = GetCollision(laraDetectionPos.x, laraDetectionPos.y, laraDetectionPos.z, LaraItem->RoomNumber);
//This collisionResult is the point where Lara would be at the end of the pushable pull.
// If is a wall
if (pointColl.Block->IsWall(laraDetectionPos.x, laraDetectionPos.z))
return false;
// If floor is not flat
int floorDifference = abs(pointColl.Position.Floor - LaraItem->Pose.Position.y);
if (floorDifference >= PUSHABLE_FLOOR_HEIGHT_TOLERANCE)
return false;
// Is ceiling (square or diagonal) high enough?
int distanceToCeiling = abs(pointColl.Position.Ceiling - pointColl.Position.Floor);
if (distanceToCeiling < LARA_HEIGHT)
return false;
// Is there any enemy or object?
auto prevPos = LaraItem->Pose.Position;
LaraItem->Pose.Position = laraDetectionPos;
GetCollidedObjects(LaraItem, LARA_RADIUS, true, &CollidedItems[0], &CollidedMeshes[0], true);
LaraItem->Pose.Position = prevPos;
if (CollidedMeshes[0])
return false;
for (int i = 0; i < MAX_COLLIDED_OBJECTS; i++)
{
if (!CollidedItems[i])
break;
if (CollidedItems[i] == &pushableItem)
continue;
if (Objects[CollidedItems[i]->ObjectNumber].isPickup)
continue;
if (Objects[CollidedItems[i]->ObjectNumber].floor == nullptr)
{
return false;
}
else
{
const auto& object = Objects[CollidedItems[i]->ObjectNumber];
int collidedItemNumber = CollidedItems[i] - g_Level.Items.data();
auto pos = CollidedItems[i]->Pose.Position;
if (object.floor(collidedItemNumber, pos.x, pos.y, pos.z) == std::nullopt)
return false;
}
}
return true;
}
bool PushableIdleConditions(ItemInfo& pushableItem)
{
// If player is grabbing, check the push pull actions.
if (LaraItem->Animation.ActiveState != LS_PUSHABLE_GRAB ||
!TestLastFrame(LaraItem, LA_PUSHABLE_GRAB))
{
return false;
}
bool hasPushAction = IsHeld(In::Forward);
bool hasPullAction = IsHeld(In::Back);
//Cond 1: Is pressing Forward or Back?
if (!hasPushAction && !hasPullAction)
return false;
//Cond 2: Can do the interaction with that side of the pushable?
auto& pushable = GetPushableInfo(pushableItem);
int quadrant = GetQuadrant(LaraItem->Pose.Orientation.y);
auto& pushableSidesAttributes = pushable.EdgeAttribs[quadrant]; //0 North, 1 East, 2 South or 3 West.
if ((hasPushAction && !pushableSidesAttributes.IsPushable) ||
(hasPullAction && !pushableSidesAttributes.IsPullable))
{
return false;
}
//Cond 3: Is its stacked pushables under the limit?
if (!IsWithinStackLimit(pushableItem.Index))
return false;
//Cond 4: Does it comply with the room collision conditions?.
if (!PushableMovementConditions(pushableItem, hasPushAction, hasPullAction))
return false;
return true;
}
bool PushableMovementConditions(ItemInfo& pushableItem, bool hasPushAction, bool hasPullAction)
{
const auto& pushable = GetPushableInfo(pushableItem);
int quadrant = GetQuadrant(LaraItem->Pose.Orientation.y);
auto targetPos = pushableItem.Pose.Position;
int dir = (hasPushAction) ? 1 : -1;
switch (quadrant)
{
case NORTH:
targetPos.z = targetPos.z + dir * BLOCK(1);
break;
case EAST:
targetPos.x = targetPos.x + dir * BLOCK(1);
break;
case SOUTH:
targetPos.z = targetPos.z - dir * BLOCK(1);
break;
case WEST:
targetPos.x = targetPos.x - dir * BLOCK(1);
break;
}
auto targetRoom = pushableItem.RoomNumber;
if (!IsPushableObjectMoveAllowed(pushableItem, targetPos, targetRoom))
return false;
if (hasPullAction && !IsValidForPlayer(pushableItem))
return false;
SetPushableStopperFlag(true, targetPos, targetRoom);
return true;
}
PushableCollisionData GetPushableCollision(ItemInfo& item)
{
auto& pushable = GetPushableInfo(item);
auto pointColl = CollisionResult{};
int waterHeight = NO_HEIGHT;
if (pushable.UseBridgeCollision)
{
RemovePushableBridge(item);
pointColl = GetCollision(item);
waterHeight = GetWaterSurface(item.Pose.Position.x, item.Pose.Position.y, item.Pose.Position.z, item.RoomNumber);
AddPushableBridge(item);
}
else
{
pointColl = GetCollision(item);
waterHeight = GetWaterSurface(item.Pose.Position.x, item.Pose.Position.y, item.Pose.Position.z, item.RoomNumber);
}
auto pushableColl = PushableCollisionData{};
pushableColl.FloorHeight = pointColl.Position.Floor;
pushableColl.CeilingHeight = pointColl.Position.Ceiling;
// Above water.
if (TestEnvironment(ENV_FLAG_WATER, item.RoomNumber))
{
pushable.WaterSurfaceHeight = waterHeight;
if (pushableColl.FloorHeight > (item.Pose.Position.y + item.Animation.Velocity.y) &&
abs(item.Pose.Position.y - pushableColl.FloorHeight) >= PUSHABLE_FLOOR_HEIGHT_TOLERANCE)
{
pushableColl.EnvType = PushableEnvironmentType::Water;
}
else
{
pushableColl.EnvType = PushableEnvironmentType::WaterFloor;
}
}
// Above dry ground.
else
{
pushable.WaterSurfaceHeight = NO_HEIGHT;
// Airborne.
if (pushableColl.FloorHeight > (item.Pose.Position.y + item.Animation.Velocity.y) &&
abs(item.Pose.Position.y - pushableColl.FloorHeight) >= PUSHABLE_FLOOR_HEIGHT_TOLERANCE)
{
pushableColl.EnvType = PushableEnvironmentType::Air;
}
// Grounded.
else
{
auto floorNormal = GetSurfaceNormal(pointColl.FloorTilt, true);
auto floorSlopeAngle = Geometry::GetSurfaceSlopeAngle(floorNormal);
if (floorSlopeAngle == 0)
{
pushableColl.EnvType = PushableEnvironmentType::FlatFloor;
}
else
{
pushableColl.EnvType = PushableEnvironmentType::SlopedFloor;
}
}
}
return pushableColl;
}
}

View file

@ -0,0 +1,33 @@
#pragma once
class Vector3i;
struct ItemInfo;
namespace TEN::Entities::Generic
{
enum class PushableEnvironmentType
{
Air,
FlatFloor,
SlopedFloor,
Water,
WaterFloor
};
struct PushableCollisionData
{
PushableEnvironmentType EnvType = PushableEnvironmentType::Air;
int FloorHeight = 0;
int CeilingHeight = 0;
};
bool IsPushableValid(ItemInfo& pushableItem);
bool IsPushableObjectMoveAllowed(ItemInfo& pushableItem, const Vector3i& targetPos, int targetRoomNumber);
bool IsValidForPlayer(const ItemInfo& pushableItem);
bool PushableIdleConditions(ItemInfo& pushableItem);
bool PushableMovementConditions(ItemInfo& pushableItem, bool hasPushAction, bool hasPullAction);
PushableCollisionData GetPushableCollision(ItemInfo& item);
}

View file

@ -0,0 +1,148 @@
#include "framework.h"
#include "Objects/Generic/Object/Pushable/PushableEffects.h"
#include "Game/effects/Bubble.h"
#include "Game/effects/effects.h"
#include "Game/effects/Ripple.h"
#include "Game/Setup.h"
#include "Objects/Generic/Object/Pushable/PushableObject.h"
using namespace TEN::Effects::Bubble;
using namespace TEN::Effects::Ripple;
namespace TEN::Entities::Generic
{
void HandlePushableRippleEffect(ItemInfo& pushableItem)
{
constexpr auto FRAMES_BETWEEN_RIPPLES = 8;
constexpr auto FRAMES_BETWEEN_RIPPLES_SOUNDS = 30;
auto& pushable = GetPushableInfo(pushableItem);
// TODO: cleanup.
// TODO: Enhace the effect to make the ripples increase their size through the time.
if (pushable.WaterSurfaceHeight != NO_HEIGHT)
{
if (fmod(GameTimer, FRAMES_BETWEEN_RIPPLES) <= 0.0f)
SpawnRipple(
Vector3(pushableItem.Pose.Position.x, pushable.WaterSurfaceHeight, pushableItem.Pose.Position.z),
pushableItem.RoomNumber,
GameBoundingBox(&pushableItem).GetWidth() + (GetRandomControl() & 15),
(int)RippleFlags::SlowFade | (int)RippleFlags::LowOpacity);
if (fmod(GameTimer, FRAMES_BETWEEN_RIPPLES_SOUNDS) <= 0.0f)
pushable.SoundState = PushableSoundState::Wade;
}
}
void SpawnPushableSplash(ItemInfo& pushableItem)
{
auto& pushable = GetPushableInfo(pushableItem);
SplashSetup.y = pushable.WaterSurfaceHeight - 1;
SplashSetup.x = pushableItem.Pose.Position.x;
SplashSetup.z = pushableItem.Pose.Position.z;
SplashSetup.splashPower = pushableItem.Animation.Velocity.y * 2;
SplashSetup.innerRadius = 250;
SetupSplash(&SplashSetup, pushableItem.RoomNumber);
}
void SpawnPushableBubbles(const ItemInfo& pushableItem)
{
constexpr auto FRAMES_BETWEEN_BUBBLES = 8.0f;
if (fmod(GameTimer, FRAMES_BETWEEN_BUBBLES) <= 0.0f)
{
for (int i = 0; i < 32; i++)
{
auto pos = Vector3(
(GetRandomControl() & 0x1FF) + pushableItem.Pose.Position.x - 256,
(GetRandomControl() & 0x7F) + pushableItem.Pose.Position.y - 64,
(GetRandomControl() & 0x1FF) + pushableItem.Pose.Position.z - 256);
SpawnBubble(pos, pushableItem.RoomNumber, (int)BubbleFlags::HighAmplitude | (int)BubbleFlags::LargeScale);
}
}
}
void HandlePushableOscillation(ItemInfo& pushableItem)
{
constexpr auto BOX_VOLUME_MIN = BLOCK(0.5f);
const auto& pushable = GetPushableInfo(pushableItem);
auto time = GameTimer + pushableItem.Animation.Velocity.y;
// Calculate bounding box volume scaling factor.
auto bounds = GameBoundingBox(&pushableItem);
float boxVolume = bounds.GetWidth() * bounds.GetDepth() * bounds.GetHeight();
float boxScale = std::sqrt(std::min(BOX_VOLUME_MIN, boxVolume)) / 32.0f;
boxScale *= pushable.Oscillation;
float xOsc = (std::sin(time * 0.05f) * 0.5f) * boxScale;
float zOsc = (std::sin(time * 0.1f) * 0.75f) * boxScale;
short xAngle = ANGLE(xOsc * 20.0f);
short zAngle = ANGLE(zOsc * 20.0f);
pushableItem.Pose.Orientation = EulerAngles(xAngle, pushableItem.Pose.Orientation.y, zAngle);
}
void HandlePushableBridgeOscillation(ItemInfo& pushableItem)
{
constexpr auto BOX_VOLUME_MIN = BLOCK(0.5f);
const auto& pushable = GetPushableInfo(pushableItem);
auto time = GameTimer + pushableItem.Animation.Velocity.y;
// Calculate bounding box volume scaling factor.
auto bounds = GameBoundingBox(&pushableItem);
float boxVolume = bounds.GetWidth() * bounds.GetDepth() * bounds.GetHeight();
float boxScale = std::sqrt(std::min(BOX_VOLUME_MIN, boxVolume)) / 32.0f;
boxScale *= pushable.Oscillation;
// Vertical oscillation.
float verticalOsc = (std::sin(time * 0.2f) * 0.5f) * boxScale * 32;
short verticalTranslation = (short)verticalOsc;
pushableItem.Pose.Position.y += verticalTranslation;
}
void HandlePushableFallRotation(ItemInfo& item)
{
auto& pushableItem = item;
// Check if orientation is outside threeshold.
short orientThreshold = 1;
float correctionStep = 40.0f / 30; // 40 deg / 30 frames to do the correction in 1 sec approx.
if (abs(pushableItem.Pose.Orientation.x) >= orientThreshold ||
abs(pushableItem.Pose.Orientation.y) >= orientThreshold)
{
if (pushableItem.Pose.Orientation.x > 0)
{
pushableItem.Pose.Orientation.x -= correctionStep;
if (pushableItem.Pose.Orientation.x < 0)
pushableItem.Pose.Orientation.x = 0;
}
else
{
pushableItem.Pose.Orientation.x += correctionStep;
if (pushableItem.Pose.Orientation.x > 0)
pushableItem.Pose.Orientation.x = 0;
}
if (pushableItem.Pose.Orientation.z > 0)
{
pushableItem.Pose.Orientation.z -= correctionStep;
if (pushableItem.Pose.Orientation.z < 0)
pushableItem.Pose.Orientation.z = 0;
}
else
{
pushableItem.Pose.Orientation.z += correctionStep;
if (pushableItem.Pose.Orientation.z > 0)
pushableItem.Pose.Orientation.z = 0;
}
}
}
}

View file

@ -0,0 +1,15 @@
#pragma once
struct ItemInfo;
namespace TEN::Entities::Generic
{
void HandlePushableRippleEffect(ItemInfo& pushableItem);
void SpawnPushableSplash(ItemInfo& pushableItem);
void SpawnPushableBubbles(const ItemInfo& pushableItem);
// TODO: Move. Not effects.
void HandlePushableOscillation(ItemInfo& pushableItem);
void HandlePushableBridgeOscillation(ItemInfo& pushableItem);
void HandlePushableFallRotation(ItemInfo& pushableItem);
}

View file

@ -0,0 +1,60 @@
#include "framework.h"
#include "Objects/Generic/Object/Pushable/PushableInfo.h"
#include "Game/control/control.h"
#include "Math/Math.h"
using namespace TEN::Math;
namespace TEN::Entities::Generic
{
const auto PUSHABLE_EDGE_ATTRIBS_DEFAULT = PushableEdgeAttribs(true, true, false);
PushableEdgeAttribs::PushableEdgeAttribs()
{
*this = PUSHABLE_EDGE_ATTRIBS_DEFAULT;
}
PushableEdgeAttribs::PushableEdgeAttribs(bool isPullable, bool isPushable, bool isClimbable)
{
IsPullable = isPullable;
IsPushable = isPushable;
IsClimbable = isClimbable;
}
PushableInfo::PushableInfo()
{
constexpr auto DEFAULT_HEIGHT = BLOCK(1);
constexpr auto DEFAULT_GRAVITY = 8.0f;
constexpr auto DEFAULT_OSC = 0.75f;
constexpr auto DEFAULT_STACK_LIMIT = 3;
BehaviorState = PushableBehaviourState::Idle;
SoundState = PushableSoundState::None;
AnimSetID = 0;
Height = DEFAULT_HEIGHT;
WaterSurfaceHeight = NO_HEIGHT;
IsOnEdge = false;
Gravity = DEFAULT_GRAVITY;
Oscillation = DEFAULT_OSC;
Stack.Limit = DEFAULT_STACK_LIMIT;
Stack.ItemNumberAbove = NO_ITEM;
Stack.ItemNumberBelow = NO_ITEM;
CanFall = false;
DoCenterAlign = true;
IsBuoyant = false;
UseRoomCollision = false;
UseBridgeCollision = false;
EdgeAttribs =
{
{ CardinalDirection::NORTH, PushableEdgeAttribs() },
{ CardinalDirection::EAST, PushableEdgeAttribs() },
{ CardinalDirection::SOUTH, PushableEdgeAttribs() },
{ CardinalDirection::WEST, PushableEdgeAttribs() }
};
}
}

View file

@ -0,0 +1,59 @@
#pragma once
#include "Math/Math.h"
#include "Objects/Generic/Object/Pushable/PushableStates.h"
using namespace TEN::Math;
namespace TEN::Entities::Generic
{
enum class PushableSoundState
{
None,
Move,
Stop,
Fall,
Wade
};
struct PushableEdgeAttribs
{
bool IsPullable = false;
bool IsPushable = false;
bool IsClimbable = false;
PushableEdgeAttribs();
PushableEdgeAttribs(bool isPullable, bool isPushable, bool isClimbable);
};
struct PushableStackData
{
int Limit = 0; // Max number of pushables in stack that can be pushed.
int ItemNumberAbove = 0;
int ItemNumberBelow = 0;
};
struct PushableInfo
{
PushableBehaviourState BehaviorState = PushableBehaviourState::Idle;
PushableSoundState SoundState = PushableSoundState::None;
int AnimSetID = 0;
GameVector StartPos = GameVector::Zero; // ?? XZ used by movement code. Y used to store water height level.
PushableStackData Stack = {};
int Height = 0;
int WaterSurfaceHeight = 0; // Used for spawning effects.
bool IsOnEdge = false;
float Gravity = 0.0f;
float Oscillation = 0.0f; // Used when floating on water surface. Recomended range: (0.0f, 2.0f].
bool CanFall = false; // OCB 0.
bool DoCenterAlign = false; // OCB 1.
bool IsBuoyant = false; // OCB 2.
bool UseRoomCollision = false;
bool UseBridgeCollision = false;
std::map<int, PushableEdgeAttribs> EdgeAttribs = {};
PushableInfo();
};
}

View file

@ -0,0 +1,233 @@
#include "framework.h"
#include "Objects/Generic/Object/Pushable/PushableObject.h"
#include "Game/animation.h"
#include "Game/collision/collide_item.h"
#include "Game/collision/collide_room.h"
#include "Game/collision/floordata.h"
#include "Game/control/box.h"
#include "Game/control/flipeffect.h"
#include "Game/items.h"
#include "Game/Lara/lara.h"
#include "Game/Lara/lara_helpers.h"
#include "Game/Setup.h"
#include "Objects/Generic/Object/Pushable/PushableBridge.h"
#include "Objects/Generic/Object/Pushable/PushableCollision.h"
#include "Objects/Generic/Object/Pushable/PushableInfo.h"
#include "Objects/Generic/Object/Pushable/PushableSound.h"
#include "Objects/Generic/Object/Pushable/PushableStack.h"
#include "Objects/Generic/Object/Pushable/PushableStates.h"
#include "Sound/sound.h"
#include "Specific/Input/Input.h"
#include "Specific/level.h"
using namespace TEN::Collision::Floordata;
using namespace TEN::Input;
namespace TEN::Entities::Generic
{
auto PushableBlockPos = Vector3i::Zero;
auto PushableBlockBounds = ObjectCollisionBounds
{
GameBoundingBox(
0, 0,
-CLICK(0.25f), 0,
0, 0),
std::pair(
EulerAngles(ANGLE(-10.0f), -LARA_GRAB_THRESHOLD, ANGLE(-10.0f)),
EulerAngles(ANGLE(10.0f), LARA_GRAB_THRESHOLD, ANGLE(10.0f)))
};
std::vector<PushableAnimSet> PushableAnimSets =
{
// Pushable object (TR4-5).
{ LA_PUSHABLE_OBJECT_PULL, LA_PUSHABLE_OBJECT_PUSH, LA_PUSHABLE_OBJECT_PUSH_EDGE_SLIP, true },
// Pushable block (TR1-3).
{ LA_PUSHABLE_BLOCK_PULL, LA_PUSHABLE_BLOCK_PUSH, LA_PUSHABLE_BLOCK_PUSH_EDGE_SLIP, false }
};
PushableInfo& GetPushableInfo(const ItemInfo& item)
{
return (PushableInfo&)item.Data;
}
void InitializePushableBlock(int itemNumber)
{
auto& pushableItem = g_Level.Items[itemNumber];
if (pushableItem.Data == NULL) // First pushableItem in initialize.
InitializePushableStacks();
auto& pushable = GetPushableInfo(pushableItem);
pushable.StartPos = GameVector(pushableItem.Pose.Position, pushableItem.RoomNumber);
pushable.Height = GetPushableHeight(pushableItem);
if (pushableItem.ObjectNumber >= ID_PUSHABLE_OBJECT_CLIMBABLE1 &&
pushableItem.ObjectNumber <= ID_PUSHABLE_OBJECT_CLIMBABLE10)
{
pushable.UseRoomCollision = true;
AddPushableBridge(pushableItem);
}
else
{
pushable.UseRoomCollision = false;
}
SetPushableStopperFlag(true, pushableItem.Pose.Position, pushableItem.RoomNumber);
// Read OCB flags.
int ocb = pushableItem.TriggerFlags;
pushable.CanFall = (ocb & (1 << 0)) != 0; // Bit 0.
pushable.DoCenterAlign = (ocb & (1 << 1)) != 1; // Bit 1.
pushable.IsBuoyant = (ocb & (1 << 2)) != 0; // Bit 2.
pushable.AnimSetID = ((ocb & (1 << 3)) != 0) ? 1 : 0; // Bit 3.
pushableItem.Status = ITEM_ACTIVE;
AddActiveItem(itemNumber);
}
void PushableBlockControl(int itemNumber)
{
auto& pushableItem = g_Level.Items[itemNumber];
auto& pushable = GetPushableInfo(pushableItem);
if (Lara.Context.InteractedItem == itemNumber && Lara.Control.IsMoving)
return;
// Handle behaviour and sound state.
HandlePushableBehaviorState(pushableItem);
HandlePushableSoundState(pushableItem);
// Update bridge.
AddPushableStackBridge(pushableItem, false);
int probeRoomNumber = GetCollision(&pushableItem).RoomNumber;
AddPushableStackBridge(pushableItem, true);
// Update room number.
if (pushableItem.RoomNumber != probeRoomNumber)
{
ItemNewRoom(itemNumber, probeRoomNumber);
pushable.StartPos.RoomNumber = pushableItem.RoomNumber;
}
}
// If player is holding Action, initiates object interaction.
// Otherwise, activates normal object collision.
void PushableBlockCollision(int itemNumber, ItemInfo* playerItem, CollisionInfo* coll)
{
auto& pushableItem = g_Level.Items[itemNumber];
auto& pushable = GetPushableInfo(pushableItem);
auto& player = *GetLaraInfo(playerItem);
int quadrant = GetQuadrant(LaraItem->Pose.Orientation.y);
auto& pushableSidesAttributes = pushable.EdgeAttribs[quadrant]; // NOTE: 0 = north, 1 = east, 2 = south, 3 = west.
// Align player to pushable.
if ((IsHeld(In::Action) &&
!IsHeld(In::Forward) &&
playerItem->Animation.ActiveState == LS_IDLE &&
playerItem->Animation.AnimNumber == LA_STAND_IDLE &&
!playerItem->Animation.IsAirborne &&
player.Control.HandStatus == HandStatus::Free &&
IsPushableValid(pushableItem)) &&
pushable.BehaviorState == PushableBehaviourState::Idle &&
(pushableSidesAttributes.IsPushable || pushableSidesAttributes.IsPullable) || // Can interact with this side.
(player.Control.IsMoving && player.Context.InteractedItem == itemNumber)) // Already interacting.
{
// Set pushable bounds.
auto bounds = GameBoundingBox(&pushableItem);
PushableBlockBounds.BoundingBox.X1 = (bounds.X1 / 2) - 100;
PushableBlockBounds.BoundingBox.X2 = (bounds.X2 / 2) + 100;
PushableBlockBounds.BoundingBox.Z1 = bounds.Z1 - 200;
PushableBlockBounds.BoundingBox.Z2 = 0;
short yOrient = pushableItem.Pose.Orientation.y;
pushableItem.Pose.Orientation.y = GetQuadrant(playerItem->Pose.Orientation.y) * ANGLE(90.0f);
// Within interaction range, calculate target position for alignment.
if (TestLaraPosition(PushableBlockBounds, &pushableItem, playerItem))
{
int quadrant = GetQuadrant(pushableItem.Pose.Orientation.y);
switch (quadrant)
{
case NORTH:
PushableBlockPos.z = bounds.Z1 - CLICK(0.4f);
PushableBlockPos.x = pushable.DoCenterAlign ? 0 : LaraItem->Pose.Position.x - pushableItem.Pose.Position.x;
break;
case SOUTH:
PushableBlockPos.z = (bounds.Z2 + CLICK(0.4f)) * (-1);
PushableBlockPos.x = pushable.DoCenterAlign ? 0 : pushableItem.Pose.Position.x - LaraItem->Pose.Position.x;
break;
case EAST:
PushableBlockPos.z = bounds.X1 - CLICK(0.4f);
PushableBlockPos.x = pushable.DoCenterAlign ? 0 : pushableItem.Pose.Position.z - LaraItem->Pose.Position.z;
break;
case WEST:
PushableBlockPos.z = (bounds.X2 + CLICK(0.4f)) * (- 1);
PushableBlockPos.x = pushable.DoCenterAlign ? 0 : LaraItem->Pose.Position.z - pushableItem.Pose.Position.z;
break;
default:
break;
}
// Align player.
if (MoveLaraPosition(PushableBlockPos, &pushableItem, playerItem))
{
SetAnimation(playerItem, LA_PUSHABLE_GRAB);
playerItem->Pose.Orientation = pushableItem.Pose.Orientation;
player.Control.IsMoving = false;
player.Control.HandStatus = HandStatus::Busy;
player.Context.NextCornerPos.Position.x = itemNumber; // TODO: Do this differently.
}
player.Context.InteractedItem = itemNumber;
}
else
{
if (player.Control.IsMoving && player.Context.InteractedItem == itemNumber)
{
player.Control.IsMoving = false;
player.Control.HandStatus = HandStatus::Free;
}
}
pushableItem.Pose.Orientation.y = yOrient;
}
else
{
// Not holding Action; do normal collision routine.
if (playerItem->Animation.ActiveState != LS_PUSHABLE_GRAB ||
!TestLastFrame(playerItem, LA_PUSHABLE_GRAB) ||
player.Context.NextCornerPos.Position.x != itemNumber)
{
// NOTE: If using room collision, use bridge collision.
if (!pushable.UseRoomCollision)
ObjectCollision(itemNumber, playerItem, coll);
return;
}
}
}
int GetPushableHeight(ItemInfo& item)
{
int heightBoundingBox = -GameBoundingBox(&item).Y1;
int heightWorldAligned = (heightBoundingBox / CLICK(0.5)) * CLICK(0.5);
return heightWorldAligned;
}
void SetPushableStopperFlag(bool isStopper, const Vector3i& pos, int roomNumber)
{
auto pointColl = GetCollision(pos, roomNumber);
pointColl.Block->Stopper = isStopper;
// TODO: There is a problem, it also has to set/reset the flag in the flipped room.
// Because when flipmaps happens, it forgets about the old flag.
}
}

View file

@ -0,0 +1,40 @@
#pragma once
#include "Game/Lara/lara_struct.h"
// TODO: Merge with Info.h.
struct CollisionInfo;
struct ItemInfo;
namespace TEN::Entities::Generic
{
struct PushableInfo;
struct PushableAnimSet
{
int PullAnimNumber = 0;
int PushAnimNumber = 0;
int EdgeAnimNumber = 0;
bool EnableAnimLoop = 0;
PushableAnimSet(int pullAnimNumber, int pushAnimNumber, int edgeAnimNumber, bool enableAnimLoop)
{
PullAnimNumber = pullAnimNumber;
PushAnimNumber = pushAnimNumber;
EdgeAnimNumber = edgeAnimNumber;
EnableAnimLoop = enableAnimLoop;
}
};
extern std::vector<PushableAnimSet> PushableAnimSets;
PushableInfo& GetPushableInfo(const ItemInfo& item);
void InitializePushableBlock(int itemNumber);
void PushableBlockControl(int itemNumber);
void PushableBlockCollision(int itemNumber, ItemInfo* playerItem, CollisionInfo* coll);
int GetPushableHeight(ItemInfo& item);
void SetPushableStopperFlag(bool isStopper, const Vector3i& pos, int roomNumber);
}

View file

@ -0,0 +1,124 @@
#include "framework.h"
#include "Objects/Generic/Object/Pushable/PushableSound.h"
#include "Objects/Generic/Object/Pushable/PushableObject.h"
#include "Sound/sound.h"
#include "Specific/level.h"
namespace TEN::Entities::Generic
{
enum PushableSoundType
{
Loop,
Stop,
Fall
};
struct PushableSoundData
{
int LoopSoundID = 0;
int StopSoundID = 0;
int FallImpactSoundID = 0;
};
static PushableSoundData GetPushableSoundData(MaterialType material)
{
static const auto DEFAULT_SOUND_DATA = PushableSoundData
{
SFX_TR4_PUSHABLE_SOUND, SFX_TR4_PUSH_BLOCK_END, SFX_TR4_BOULDER_FALL
};
static const auto SOUND_DATA_MAP = std::unordered_map<MaterialType, PushableSoundData>
{
{ MaterialType::Mud, PushableSoundData{ SFX_TEN_PUSHABLES_MOVE_MUD, SFX_TEN_PUSHABLES_STOP_MUD, SFX_TEN_PUSHABLES_COLLIDE_MUD } },
{ MaterialType::Snow, PushableSoundData{ SFX_TEN_PUSHABLES_MOVE_SNOW, SFX_TEN_PUSHABLES_STOP_SNOW, SFX_TEN_PUSHABLES_COLLIDE_SNOW } },
{ MaterialType::Sand, PushableSoundData{ SFX_TEN_PUSHABLES_MOVE_SAND, SFX_TEN_PUSHABLES_STOP_SAND, SFX_TEN_PUSHABLES_COLLIDE_SAND } },
{ MaterialType::Gravel, PushableSoundData{ SFX_TEN_PUSHABLES_MOVE_GRAVEL, SFX_TEN_PUSHABLES_STOP_GRAVEL, SFX_TEN_PUSHABLES_COLLIDE_GRAVEL } },
{ MaterialType::Ice, PushableSoundData{ SFX_TEN_PUSHABLES_MOVE_ICE, SFX_TEN_PUSHABLES_STOP_ICE, SFX_TEN_PUSHABLES_COLLIDE_ICE } },
{ MaterialType::Water, PushableSoundData{ SFX_TEN_PUSHABLES_MOVE_WATER, SFX_TEN_PUSHABLES_STOP_WATER, SFX_TEN_PUSHABLES_COLLIDE_WATER } },
{ MaterialType::Stone, PushableSoundData{ SFX_TEN_PUSHABLES_STOP_MOVE_STONE, SFX_TEN_PUSHABLES_STOP_STONE, SFX_TEN_PUSHABLES_COLLIDE_STONE } },
{ MaterialType::Wood, PushableSoundData{ SFX_TEN_PUSHABLES_MOVE_WOOD, SFX_TEN_PUSHABLES_STOP_WOOD, SFX_TEN_PUSHABLES_COLLIDE_WOOD } },
{ MaterialType::Metal, PushableSoundData{ SFX_TEN_PUSHABLES_MOVE_METAL, SFX_TEN_PUSHABLES_STOP_METAL, SFX_TEN_PUSHABLES_COLLIDE_METAL } },
{ MaterialType::Marble, PushableSoundData{ SFX_TEN_PUSHABLES_MOVE_MARBLE, SFX_TEN_PUSHABLES_STOP_MARBLE, SFX_TEN_PUSHABLES_COLLIDE_MARBLE } },
{ MaterialType::Grass, PushableSoundData{ SFX_TEN_PUSHABLES_MOVE_GRASS, SFX_TEN_PUSHABLES_STOP_GRASS, SFX_TEN_PUSHABLES_COLLIDE_GRASS } },
{ MaterialType::Concrete, PushableSoundData{ SFX_TEN_PUSHABLES_MOVE_CONCRETE, SFX_TEN_PUSHABLES_STOP_CONCRETE, SFX_TEN_PUSHABLES_COLLIDE_CONCRETE } },
{ MaterialType::OldWood, PushableSoundData{ SFX_TR4_PUSHABLE_SOUND, SFX_TR4_PUSH_BLOCK_END, SFX_TR4_BOULDER_FALL } },
{ MaterialType::OldMetal, PushableSoundData{ SFX_TR4_PUSHABLE_SOUND, SFX_TR4_PUSH_BLOCK_END, SFX_TR4_BOULDER_FALL } },
{ MaterialType::Custom1, PushableSoundData{ SFX_TR4_PUSHABLE_SOUND, SFX_TR4_PUSH_BLOCK_END, SFX_TR4_BOULDER_FALL } },
{ MaterialType::Custom2, PushableSoundData{ SFX_TR4_PUSHABLE_SOUND, SFX_TR4_PUSH_BLOCK_END, SFX_TR4_BOULDER_FALL } },
{ MaterialType::Custom3, PushableSoundData{ SFX_TR4_PUSHABLE_SOUND, SFX_TR4_PUSH_BLOCK_END, SFX_TR4_BOULDER_FALL } },
{ MaterialType::Custom4, PushableSoundData{ SFX_TR4_PUSHABLE_SOUND, SFX_TR4_PUSH_BLOCK_END, SFX_TR4_BOULDER_FALL } },
{ MaterialType::Custom5, PushableSoundData{ SFX_TR4_PUSHABLE_SOUND, SFX_TR4_PUSH_BLOCK_END, SFX_TR4_BOULDER_FALL } },
{ MaterialType::Custom6, PushableSoundData{ SFX_TR4_PUSHABLE_SOUND, SFX_TR4_PUSH_BLOCK_END, SFX_TR4_BOULDER_FALL } },
{ MaterialType::Custom7, PushableSoundData{ SFX_TR4_PUSHABLE_SOUND, SFX_TR4_PUSH_BLOCK_END, SFX_TR4_BOULDER_FALL } },
{ MaterialType::Custom8, PushableSoundData{ SFX_TR4_PUSHABLE_SOUND, SFX_TR4_PUSH_BLOCK_END, SFX_TR4_BOULDER_FALL } }
};
// TODO: Uncomment when we get actual sounds for them.
//auto it = SOUND_DATA_MAP.find(material);
//return ((it != SOUND_DATA_MAP.end()) ? it->second : SOUND_DATA_DEFAULT);
return DEFAULT_SOUND_DATA;
}
static std::optional<int> GetPushableSoundID(const ItemInfo& pushableItem, PushableSoundType soundType)
{
// Get floor material.
auto pointColl = GetCollision(pushableItem);
auto material = pointColl.BottomBlock->Material;
switch (soundType)
{
case PushableSoundType::Loop:
return GetPushableSoundData(material).LoopSoundID;
case PushableSoundType::Stop:
return GetPushableSoundData(material).StopSoundID;
case PushableSoundType::Fall:
return GetPushableSoundData(material).FallImpactSoundID;
default:
TENLog("Missing pushable sound.", LogLevel::Error);
}
return std::nullopt;
}
void HandlePushableSoundState(ItemInfo& pushableItem)
{
auto& pushable = GetPushableInfo(pushableItem);
auto soundID = std::optional<int>(std::nullopt);
switch (pushable.SoundState)
{
default:
case PushableSoundState::None:
break;
case PushableSoundState::Move:
soundID = GetPushableSoundID(pushableItem, PushableSoundType::Loop);
break;
case PushableSoundState::Stop:
soundID = GetPushableSoundID(pushableItem, PushableSoundType::Stop);
pushable.SoundState = PushableSoundState::None;
break;
case PushableSoundState::Fall:
soundID = GetPushableSoundID(pushableItem, PushableSoundType::Fall);
pushable.SoundState = PushableSoundState::None;
break;
case PushableSoundState::Wade:
soundID = SFX_TR4_LARA_WADE;
pushable.SoundState = PushableSoundState::None;
break;
}
if (!soundID.has_value())
return;
SoundEffect(*soundID, &pushableItem.Pose, SoundEnvironment::Always);
}
}

View file

@ -0,0 +1,8 @@
#pragma once
struct ItemInfo;
namespace TEN::Entities::Generic
{
void HandlePushableSoundState(ItemInfo& pushableItem);
}

View file

@ -0,0 +1,324 @@
#include "framework.h"
#include "Objects/Generic/Object/Pushable/PushableStack.h"
#include "Game/collision/floordata.h"
#include "Game/Setup.h"
#include "Objects/Generic/Object/Pushable/PushableBridge.h"
#include "Objects/Generic/Object/Pushable/PushableObject.h"
#include "Specific/level.h"
using namespace TEN::Collision::Floordata;
namespace TEN::Entities::Generic
{
// NOTE: Required to use Vector3i in std::unordered_map.
struct Vector3iHasher
{
std::size_t operator()(const Vector3i& vector) const
{
std::size_t h1 = std::hash<int>()(vector.x);
std::size_t h2 = std::hash<int>()(vector.y);
std::size_t h3 = std::hash<int>()(vector.z);
return (h1 ^ (h2 << 1) ^ (h3 << 2));
}
};
void InitializePushableStacks()
{
// 1) Collect all pushables in level.
auto& pushableItemNumbers = FindAllPushables(g_Level.Items);
if (pushableItemNumbers.empty())
return;
// 2) Prepare data to create several lists per each stack group (one for each XZ and ground floor height).
// PROBLEM: Missing hash function in Vector3i, creating custom version Vector3iHasher at the top of this source code.
std::unordered_map < Vector3i, std::vector<int>, Vector3iHasher> stackGroups; // stack Position - pushable itemNumber
// 3) Iterate through the pushables list, to put them in their different stack groups. According to their XZ and ground floor height).
//Extra, I moved also the .Data initialization here, to can store data in all the pushables objects (even the ones not initialized yet).
for (int itemNumber : pushableItemNumbers)
{
auto& pushableItem = g_Level.Items[itemNumber];
pushableItem.Data = PushableInfo();
int x = pushableItem.Pose.Position.x;
int z = pushableItem.Pose.Position.z;
auto pointColl = GetCollision(&pushableItem);
int y = pointColl.Position.Floor;
stackGroups.emplace(Vector3i(x, y, z), std::vector<int>()).first->second.push_back(itemNumber);
}
// 4) Iterate through stack groups lists, sort each by vertical position, and iterate to make stack links.
for (auto& group : stackGroups)
{
auto& pushablesInGroup = group.second;
// If only 1 pushable in that position, no stack check required.
if (pushablesInGroup.size() <= 1)
continue;
// If 2 or more pushables in group, sort from bottom to top (highest Y to Lowest Y).
std::sort(pushablesInGroup.begin(), pushablesInGroup.end(), [](int pushableItemNumber0, int pushableItemNumber1)
{
const auto& pushable0 = g_Level.Items[pushableItemNumber0];
const auto& pushable1 = g_Level.Items[pushableItemNumber1];
return (pushable0.Pose.Position.y > pushable1.Pose.Position.y);
});
// Iterate through each group to set the stack links).
for (int i = 0; i < (pushablesInGroup.size() - 1); ++i)
{
// Set stackUpperItem and stackLowerItem variables accordingly.
int lowerItemNumber = pushablesInGroup[i];
auto& lowerPushableItem = g_Level.Items[lowerItemNumber];
auto& lowerPushable = GetPushableInfo(lowerPushableItem);
int upperItemNumber = pushablesInGroup[i + 1];
auto& upperPushableItem = g_Level.Items[upperItemNumber];
auto& upperPushable = GetPushableInfo(upperPushableItem);
lowerPushable.Stack.ItemNumberAbove = upperItemNumber;
upperPushable.Stack.ItemNumberBelow = lowerItemNumber;
}
}
}
std::vector<int> FindAllPushables(const std::vector<ItemInfo>& items)
{
auto pushableItemNumbers = std::vector<int>{};
for (int i = 0; i < items.size(); i++)
{
auto& item = items[i];
if ((item.ObjectNumber >= ID_PUSHABLE_OBJECT1 && item.ObjectNumber <= ID_PUSHABLE_OBJECT10) ||
(item.ObjectNumber >= ID_PUSHABLE_OBJECT_CLIMBABLE1 && item.ObjectNumber <= ID_PUSHABLE_OBJECT_CLIMBABLE10))
{
pushableItemNumbers.push_back(i);
}
}
return pushableItemNumbers;
}
void StackPushable(int itemNumber, int targetItemNumber)
{
if (targetItemNumber == NO_ITEM)
return;
auto& pushableItem = g_Level.Items[itemNumber];
auto& pushable = GetPushableInfo(pushableItem);
pushable.Stack.ItemNumberBelow = targetItemNumber;
auto& lowerPushableItem = g_Level.Items[targetItemNumber];
auto& lowerPushable = GetPushableInfo(lowerPushableItem);
lowerPushable.Stack.ItemNumberAbove = itemNumber;
}
void UnstackPushable(int itemNumber)
{
auto& pushableItem = g_Level.Items[itemNumber];
auto& pushable = GetPushableInfo(pushableItem);
if (pushable.Stack.ItemNumberBelow == NO_ITEM)
return;
auto& lowerPushableItem = g_Level.Items[pushable.Stack.ItemNumberBelow];
auto& lowerPushable = GetPushableInfo(lowerPushableItem);
pushable.Stack.ItemNumberBelow = NO_ITEM;
lowerPushable.Stack.ItemNumberAbove = NO_ITEM;
}
int SearchNearPushablesStack(int itemNumber)
{
auto& pushableItem = g_Level.Items[itemNumber];
int pushabelStackFound = FindPushableStackInRoom(itemNumber, pushableItem.RoomNumber);
if (pushabelStackFound != NO_ITEM)
return pushabelStackFound;
// Otherwise, check room below.
//auto collisionResult = GetCollision(pushableItem.Pose.Position.x, pushableItem.Pose.Position.y, pushableItem.Pose.Position.z, pushableItem.RoomNumber);
//auto roomNumberBelow = collisionResult.Block->GetRoomNumberBelow(pushableItem.Pose.Position.x, pushableItem.Pose.Position.y, pushableItem.Pose.Position.z).value();
//pushabelStackFound = FindPushableStackInRoom(itemNumber, roomNumberBelow);
return NO_ITEM;
}
int FindPushableStackInRoom(int itemNumber, int roomNumber)
{
auto& pushableItem = g_Level.Items[itemNumber];
auto& pushable = GetPushableInfo(pushableItem);
if (roomNumber != NO_ROOM)
{
short currentItemNumber = g_Level.Rooms[roomNumber].itemNumber;
while (currentItemNumber != NO_ITEM)
{
auto& currentItem = g_Level.Items[currentItemNumber];
// If climbable pushable, is in the same XZ position and is at a lower height.
if ((currentItem.ObjectNumber >= ID_PUSHABLE_OBJECT_CLIMBABLE1 && currentItem.ObjectNumber <= ID_PUSHABLE_OBJECT_CLIMBABLE10) &&
(currentItem.Pose.Position.x == pushableItem.Pose.Position.x) && (currentItem.Pose.Position.z == pushableItem.Pose.Position.z) &&
(currentItem.Pose.Position.y > pushableItem.Pose.Position.y))
{
// Find top item.
if (pushable.Stack.ItemNumberAbove == NO_ITEM)
{
return currentItemNumber;
}
else
{
int topItemNumber = pushable.Stack.ItemNumberAbove;
while (topItemNumber != NO_ITEM)
{
auto& topItem = g_Level.Items[topItemNumber];
auto& topPushable = GetPushableInfo(topItem);
if (topPushable.Stack.ItemNumberAbove == NO_ITEM)
{
return topItemNumber;
}
else
{
topItemNumber = topPushable.Stack.ItemNumberAbove;
}
}
}
}
else
{
currentItemNumber = currentItem.NextItem;
}
}
}
return NO_ITEM;
}
int GetPushableCountInStack(int itemNumber)
{
auto pushableItemCopy = g_Level.Items[itemNumber];
auto& pushableCopy = GetPushableInfo(pushableItemCopy);
int count = 1;
while (pushableCopy.Stack.ItemNumberAbove != NO_ITEM)
{
// Filter out current pushable item.
if (pushableCopy.Stack.ItemNumberAbove == itemNumber)
break;
pushableItemCopy = g_Level.Items[pushableCopy.Stack.ItemNumberAbove];
pushableCopy = GetPushableInfo(pushableItemCopy);
count++;
}
return count;
}
bool IsWithinStackLimit(int itemNumber)
{
auto& pushableItem = g_Level.Items[itemNumber];
auto& pushable = GetPushableInfo(pushableItem);
auto count = GetPushableCountInStack(itemNumber);
return (count <= pushable.Stack.Limit);
}
int GetStackHeight(int itemNumber)
{
auto pushableItemCopy = g_Level.Items[itemNumber];
auto& pushableCopy = GetPushableInfo(pushableItemCopy);
int totalHeight = pushableCopy.Height;
while (pushableCopy.Stack.ItemNumberAbove != NO_ITEM)
{
pushableItemCopy = g_Level.Items[pushableCopy.Stack.ItemNumberAbove];
pushableCopy = GetPushableInfo(pushableItemCopy);
totalHeight = totalHeight + pushableCopy.Height;
}
return totalHeight;
}
void StartMovePushableStack(int itemNumber)
{
auto& pushableItem = g_Level.Items[itemNumber];
auto& pushable = GetPushableInfo(pushableItem);
int currentItemNumber = pushable.Stack.ItemNumberAbove;
while (currentItemNumber != NO_ITEM)
{
auto& currentPushableItem = g_Level.Items[currentItemNumber];
auto& currentPushable = GetPushableInfo(currentPushableItem);
// Deactivate collision.
if (currentPushable.UseRoomCollision)
RemovePushableBridge(currentPushableItem);
currentPushable.BehaviorState = PushableBehaviourState::MoveStackHorizontal;
currentItemNumber = currentPushable.Stack.ItemNumberAbove;
}
}
void StopMovePushableStack(int itemNumber)
{
auto& pushableItem = g_Level.Items[itemNumber];
auto& pushable = GetPushableInfo(pushableItem);
int currentItemNumber = pushable.Stack.ItemNumberAbove;
while (currentItemNumber != NO_ITEM)
{
auto& currentPushableItem = g_Level.Items[currentItemNumber];
auto& currentPushable = GetPushableInfo(currentPushableItem);
currentPushableItem.Pose.Position = GetNearestSectorCenter(currentPushableItem.Pose.Position);
// Activate collision.
if (currentPushable.UseRoomCollision)
AddPushableBridge(currentPushableItem);
currentPushable.BehaviorState = PushableBehaviourState::Idle;
currentItemNumber = currentPushable.Stack.ItemNumberAbove;
}
}
void StartFallPushableStack(int itemNumber)
{
}
void StopFallPushableStack(int itemNumber)
{
}
// TODO: Problems with bridge collision.
void SetPushableVerticalPos(const ItemInfo& pushableItem, int relHeight)
{
auto* pushableItemPtr = &g_Level.Items[pushableItem.Index];
const auto* pushablePtr = &GetPushableInfo(*pushableItemPtr);
while (pushablePtr->Stack.ItemNumberAbove != NO_ITEM)
{
if (pushablePtr->Stack.ItemNumberAbove == pushableItem.Index)
break;
pushableItemPtr->Pose.Position.y += relHeight;
pushableItemPtr = &g_Level.Items[pushablePtr->Stack.ItemNumberAbove];
pushablePtr = &GetPushableInfo(*pushableItemPtr);
}
}
}

View file

@ -0,0 +1,28 @@
#pragma once
struct ItemInfo;
namespace TEN::Entities::Generic
{
void InitializePushableStacks();
std::vector<int> FindAllPushables(const std::vector<ItemInfo>& items);
void StackPushable(int itemNumber, int targetItemNumber);
void UnstackPushable(int itemNumber);
int SearchNearPushablesStack(int itemNumber);
int FindPushableStackInRoom(int itemNumber, int roomNumber);
int GetPushableCountInStack(int itemNumber);
bool IsWithinStackLimit(int itemNumber);
int GetStackHeight(int itemNumber);
void StartMovePushableStack(int itemNumber);
void StopMovePushableStack(int itemNumber);
void StartFallPushableStack(int itemNumber);
void StopFallPushableStack(int itemNumber);
void SetPushableVerticalPos(const ItemInfo& pushableItem, int deltaY);
}

View file

@ -0,0 +1,901 @@
#include "framework.h"
#include "Objects/Generic/Object/Pushable/PushableStates.h"
#include "Game/animation.h"
#include "Game/control/flipeffect.h"
#include "Game/Lara/lara.h"
#include "Game/Lara/lara_helpers.h"
#include "Objects/Generic/Object/Pushable/PushableBridge.h"
#include "Objects/Generic/Object/Pushable/PushableCollision.h"
#include "Objects/Generic/Object/Pushable/PushableEffects.h"
#include "Objects/Generic/Object/Pushable/PushableObject.h"
#include "Objects/Generic/Object/Pushable/PushableStack.h"
#include "Specific/Input/Input.h"
#include "Specific/level.h"
using namespace TEN::Input;
namespace TEN::Entities::Generic
{
constexpr auto PUSHABLE_FALL_VELOCITY_MAX = BLOCK(1 / 8.0f);
constexpr auto PUSHABLE_WATER_VELOCITY_MAX = BLOCK(1 / 16.0f);
constexpr auto PUSHABLE_RUMBLE_FALL_VELOCITY = 96.0f;
constexpr auto PUSHABLE_EDGE_SLIP_VELOCITY = 0.8f;
constexpr auto PUSHABLE_GRAVITY_AIR = 8.0f;
constexpr auto PUSHABLE_GRAVITY_WATER = 4.0f;
constexpr auto PUSHABLE_GRAVITY_ACCEL = 0.5f;
constexpr auto PUSHABLE_WATER_SURFACE_DISTANCE = CLICK(0.5f);
static void HandleIdleState(ItemInfo& pushableItem)
{
auto& pushable = GetPushableInfo(pushableItem);
auto& playerItem = *LaraItem;
auto& player = GetLaraInfo(playerItem);
// 1) Check if player is interacting.
if (player.Context.InteractedItem == pushableItem.Index)
{
if (PushableIdleConditions(pushableItem))
{
// Pushing.
if (IsHeld(In::Forward))
{
int pushAnimNumber = (pushable.IsOnEdge) ?
PushableAnimSets[pushable.AnimSetID].EdgeAnimNumber :
PushableAnimSets[pushable.AnimSetID].PushAnimNumber;
SetAnimation(LaraItem, pushAnimNumber);
}
// Pulling.
else if (IsHeld(In::Back))
{
int pullAnimNumber = PushableAnimSets[pushable.AnimSetID].PullAnimNumber;
SetAnimation(LaraItem, pullAnimNumber);
}
pushable.StartPos = pushableItem.Pose.Position;
pushable.StartPos.RoomNumber = pushableItem.RoomNumber;
pushable.BehaviorState = PushableBehaviourState::Move;
// Unstack lower pushables.
UnstackPushable(pushableItem.Index);
// Prepare upper pushables in stack for movement.
StartMovePushableStack(pushableItem.Index);
ResetPlayerFlex(LaraItem);
RemovePushableBridge(pushableItem);
}
else if (playerItem.Animation.ActiveState != LS_PUSHABLE_GRAB &&
playerItem.Animation.ActiveState != LS_PUSHABLE_PULL &&
playerItem.Animation.ActiveState != LS_PUSHABLE_PUSH &&
playerItem.Animation.ActiveState != LS_PUSHABLE_EDGE_SLIP)
{
player.Context.InteractedItem = NO_ITEM;
}
}
// Get pushable collision.
auto pushableColl = GetPushableCollision(pushableItem);
switch (pushableColl.EnvType)
{
case PushableEnvironmentType::FlatFloor:
if (pushableColl.FloorHeight != pushableItem.Pose.Position.y)
{
pushableItem.Pose.Position.y = pushableColl.FloorHeight;
int relHeight = pushableColl.FloorHeight - pushableItem.Pose.Position.y;
SetPushableVerticalPos(pushableItem, relHeight);
}
break;
case PushableEnvironmentType::WaterFloor:
{
if (pushableColl.FloorHeight != pushableItem.Pose.Position.y)
{
pushableItem.Pose.Position.y = pushableColl.FloorHeight;
int relHeight = pushableColl.FloorHeight - pushableItem.Pose.Position.y;
SetPushableVerticalPos(pushableItem, relHeight);
}
int waterheight = abs(pushableColl.FloorHeight - pushable.WaterSurfaceHeight);
if (waterheight > GetPushableHeight(pushableItem))
{
if (pushable.IsBuoyant && pushable.Stack.ItemNumberAbove == NO_ITEM)
{
pushable.BehaviorState = PushableBehaviourState::Float;
pushable.Gravity = 0.0f;
}
}
HandlePushableRippleEffect(pushableItem);
}
break;
case PushableEnvironmentType::Air:
// Pass to fall state if distance to floor is beyond threshold.
if (abs(pushableColl.FloorHeight - pushableItem.Pose.Position.y) > CLICK(0.75f))
{
pushable.BehaviorState = PushableBehaviourState::Fall;
SetPushableStopperFlag(false, pushableItem.Pose.Position, pushableItem.RoomNumber);
RemovePushableBridge(pushableItem);
}
else
{
pushableItem.Pose.Position.y = pushableColl.FloorHeight;
int relHeight = pushableColl.FloorHeight - pushableItem.Pose.Position.y;
SetPushableVerticalPos(pushableItem, relHeight);
}
break;
case PushableEnvironmentType::Water:
RemovePushableBridge(pushableItem);
SetPushableStopperFlag(false, pushableItem.Pose.Position, pushableItem.RoomNumber);
if (pushable.IsBuoyant && pushable.Stack.ItemNumberAbove == NO_ITEM)
{
pushable.BehaviorState = PushableBehaviourState::Float;
pushable.Gravity = 0.0f;
}
else
{
pushable.BehaviorState = PushableBehaviourState::Sink;
pushable.Gravity = PUSHABLE_GRAVITY_WATER;
}
break;
default:
TENLog("Error handling pushable collision in idle state for pushable item " + std::to_string(pushableItem.Index), LogLevel::Warning, LogConfig::All);
break;
}
}
static void HandleMoveState(ItemInfo& pushableItem)
{
auto& pushable = GetPushableInfo(pushableItem);
auto& playerItem = *LaraItem;
auto& player = GetLaraInfo(playerItem);
bool isPlayerPulling = playerItem.Animation.ActiveState == LS_PUSHABLE_PULL;
int quadrant = GetQuadrant(playerItem.Pose.Orientation.y);
int newPosX = pushable.StartPos.x;
int newPosZ = pushable.StartPos.z;
int displaceDepth = 0;
int displaceBox = GameBoundingBox(LaraItem).Z2;
if (pushable.SoundState == PushableSoundState::Move)
pushable.SoundState = PushableSoundState::Stop;
displaceDepth = GetLastFrame(GAME_OBJECT_ID::ID_LARA, playerItem.Animation.AnimNumber)->BoundingBox.Z2;
if (isPlayerPulling)
{
displaceBox -= (displaceDepth + BLOCK(1));
}
else
{
displaceBox -= displaceDepth - (pushable.IsOnEdge ? BLOCK(0.5f) : BLOCK(1));
}
// Player is pushing or pulling.
if (playerItem.Animation.FrameNumber != (g_Level.Anims[playerItem.Animation.AnimNumber].frameEnd - 1))
{
// 1) Determine displacement.
switch (quadrant)
{
case NORTH:
newPosZ += displaceBox;
break;
case EAST:
newPosX += displaceBox;
break;
case SOUTH:
newPosZ -= displaceBox;
break;
case WEST:
newPosX -= displaceBox;
break;
default:
break;
}
// Distance too far; return early. NOTE: May happen as animation bounds change.
if (abs(pushableItem.Pose.Position.z - newPosZ) > BLOCK(0.75f))
return;
if (abs(pushableItem.Pose.Position.x - newPosX) > BLOCK(0.75f))
return;
int travelledDist = Vector3i::Distance(pushableItem.Pose.Position, pushable.StartPos.ToVector3i());
if (pushable.IsOnEdge && travelledDist >= BLOCK(0.5f))
{
pushable.BehaviorState = PushableBehaviourState::EdgeSlip;
return;
}
// 2) Move pushable.
// Move only if the movement direction aligns with push/pull action.
// Z axis.
if (isPlayerPulling)
{
if ((quadrant == NORTH && pushableItem.Pose.Position.z > newPosZ) ||
(quadrant == SOUTH && pushableItem.Pose.Position.z < newPosZ))
{
pushableItem.Pose.Position.z = newPosZ;
pushable.SoundState = PushableSoundState::Move;
}
}
else
{
if ((quadrant == NORTH && pushableItem.Pose.Position.z < newPosZ) ||
(quadrant == SOUTH && pushableItem.Pose.Position.z > newPosZ))
{
pushableItem.Pose.Position.z = newPosZ;
pushable.SoundState = PushableSoundState::Move;
}
}
// X axis.
if (isPlayerPulling)
{
if ((quadrant == EAST && pushableItem.Pose.Position.x > newPosX) ||
(quadrant == WEST && pushableItem.Pose.Position.x < newPosX))
{
pushableItem.Pose.Position.x = newPosX;
pushable.SoundState = PushableSoundState::Move;
}
}
else
{
if ((quadrant == EAST && pushableItem.Pose.Position.x < newPosX) ||
(quadrant == WEST && pushableItem.Pose.Position.x > newPosX))
{
pushableItem.Pose.Position.x = newPosX;
pushable.SoundState = PushableSoundState::Move;
}
}
if (pushable.WaterSurfaceHeight != NO_HEIGHT)
HandlePushableRippleEffect(pushableItem);
}
// Push/pull animation finished.
else
{
// 1) Realign with sector center.
pushableItem.Pose.Position = GetNearestSectorCenter(pushableItem.Pose.Position);
// 2) Connect to stack if applicable.
int foundStack = SearchNearPushablesStack(pushableItem.Index);
StackPushable(pushableItem.Index, foundStack);
SetPushableStopperFlag(false, pushable.StartPos.ToVector3i(), pushable.StartPos.RoomNumber);
pushable.StartPos = pushableItem.Pose.Position;
pushable.StartPos.RoomNumber = pushableItem.RoomNumber;
// Get pushable collision.
auto pushableColl = GetPushableCollision(pushableItem);
switch (pushableColl.EnvType)
{
case PushableEnvironmentType::FlatFloor:
case PushableEnvironmentType::WaterFloor:
// Activate trigger.
TestTriggers(&pushableItem, true, pushableItem.Flags & IFLAG_ACTIVATION_MASK);
// Check if push/pull must stop.
if (!PushableAnimSets[pushable.AnimSetID].EnableAnimLoop ||
!IsHeld(In::Action) ||
!PushableMovementConditions(pushableItem, !isPlayerPulling, isPlayerPulling) ||
!IsPushableValid(pushableItem))
{
playerItem.Animation.TargetState = LS_IDLE;
pushable.BehaviorState = PushableBehaviourState::Idle;
// Set upper pushables back to normal(?).
StopMovePushableStack(pushableItem.Index);
AddPushableBridge(pushableItem);
// Connect to another stack.
int foundStack = SearchNearPushablesStack(pushableItem.Index);
StackPushable(pushableItem.Index, foundStack);
pushable.SoundState = PushableSoundState::Stop;
}
else if (playerItem.Animation.ActiveState == LS_PUSHABLE_PUSH && pushable.IsOnEdge)
{
playerItem.Animation.TargetState = LS_PUSHABLE_EDGE_SLIP;
auto movementDir = pushableItem.Pose.Position.ToVector3() - playerItem.Pose.Position.ToVector3();
movementDir.Normalize();
playerItem.Pose.Position = playerItem.Pose.Position + movementDir * BLOCK(1);
}
break;
case PushableEnvironmentType::Air:
pushable.BehaviorState = PushableBehaviourState::Fall;
pushable.SoundState = PushableSoundState::None;
playerItem.Animation.TargetState = LS_IDLE;
player.Context.InteractedItem = NO_ITEM;
return;
break;
case PushableEnvironmentType::SlopedFloor:
// TODO: If slippery slope, link to slide state.
break;
case PushableEnvironmentType::Water:
pushable.BehaviorState = PushableBehaviourState::Sink;
pushable.SoundState = PushableSoundState::None;
playerItem.Animation.TargetState = LS_IDLE;
player.Context.InteractedItem = NO_ITEM;
break;
default:
TENLog("Error handling pushable collision state in move state for pushable item number " + std::to_string(pushableItem.Index), LogLevel::Warning, LogConfig::All, false);
break;
}
}
}
static void HandleEdgeSlipState(ItemInfo& pushableItem)
{
constexpr auto LEAN_ANGLE_MAX = ANGLE(40.0f);
auto& pushable = GetPushableInfo(pushableItem);
// Get pushable collision.
auto pushableColl = GetPushableCollision(pushableItem);
// Calculate movement direction.
auto moveDir = (pushableItem.Pose.Position - pushable.StartPos.ToVector3i()).ToVector3();
moveDir.y = 0.0f;
moveDir.Normalize();
// Define origin and target.
auto origin = Geometry::TranslatePoint(pushable.StartPos.ToVector3(), moveDir, BLOCK(0.5f));
auto target = Geometry::TranslatePoint(pushable.StartPos.ToVector3(), moveDir, BLOCK(1));
target.y = pushable.StartPos.y + BLOCK(1);
// Calculate current position based on interpolation.
auto currentPos = pushableItem.Pose.Position.ToVector3();
float& elapsedTime = pushableItem.Animation.Velocity.y;
float alpha = std::clamp(elapsedTime / PUSHABLE_EDGE_SLIP_VELOCITY, 0.0f, 1.0f);
// TODO: Try Lerp() instead.
currentPos = Vector3(
InterpolateCubic(origin.x, origin.x, target.x, target.x, alpha),
InterpolateCubic(origin.y, origin.y, target.y - 700, target.y, alpha),
InterpolateCubic(origin.z, origin.z, target.z, target.z, alpha));
// Calculate lean angle based on movement direction.
float leanAngle = LEAN_ANGLE_MAX * alpha;
if (currentPos.y > pushableColl.FloorHeight)
{
currentPos.y = pushableColl.FloorHeight;
pushableItem.Pose.Orientation = EulerAngles(0, pushableItem.Pose.Orientation.y, 0);
}
else
{
// Handle slip lean.
int pushableQuadrant = GetQuadrant(pushableItem.Pose.Orientation.y);
int movementQuadrant = GetQuadrant(FROM_RAD(atan2(moveDir.z, moveDir.x)));
movementQuadrant = (movementQuadrant + pushableQuadrant) % 4;
switch (movementQuadrant)
{
// TODO: Use CardinalDirection enum.
case 0: //EAST
pushableItem.Pose.Orientation = EulerAngles(0, pushableItem.Pose.Orientation.y, leanAngle);
break;
case 1: //NORTH
pushableItem.Pose.Orientation = EulerAngles(-leanAngle, pushableItem.Pose.Orientation.y, 0);
break;
case 2: //WEST
pushableItem.Pose.Orientation = EulerAngles(0, pushableItem.Pose.Orientation.y, -leanAngle);
break;
case 3: //SOUTH
pushableItem.Pose.Orientation = EulerAngles(leanAngle, pushableItem.Pose.Orientation.y, 0);
break;
}
}
pushableItem.Pose.Position = currentPos;
elapsedTime += DELTA_TIME;
// Handle sounds.
if (alpha <= 0.5f)
{
pushable.SoundState = PushableSoundState::Move;
}
else
{
if (pushable.SoundState == PushableSoundState::Move)
pushable.SoundState = PushableSoundState::Stop;
}
// Check if movement is completed.
if (alpha >= 1.0f)
{
currentPos = GetNearestSectorCenter(pushableItem.Pose.Position).ToVector3();
switch (pushableColl.EnvType)
{
case PushableEnvironmentType::Air:
pushable.BehaviorState = PushableBehaviourState::Fall;
pushableItem.Animation.Velocity.y = PUSHABLE_FALL_VELOCITY_MAX / 2;
break;
case PushableEnvironmentType::FlatFloor:
case PushableEnvironmentType::WaterFloor:
pushableItem.Animation.Velocity.y = 0.0f;
pushableItem.Pose.Position.y = pushableColl.FloorHeight;
pushableItem.Pose.Orientation = EulerAngles(0, pushableItem.Pose.Orientation.y, 0);
pushable.BehaviorState = PushableBehaviourState::Idle;
break;
case PushableEnvironmentType::Water:
pushableItem.Animation.Velocity.y = PUSHABLE_WATER_VELOCITY_MAX / 2;
pushable.BehaviorState = PushableBehaviourState::Sink;
SpawnPushableSplash(pushableItem);
break;
case PushableEnvironmentType::SlopedFloor:
pushableItem.Animation.Velocity.y = 0.0f;
pushable.BehaviorState = PushableBehaviourState::Idle;
break;
default:
TENLog("Error handling pushable collision in edge slip state for pushable item number " + std::to_string(pushableItem.Index), LogLevel::Warning, LogConfig::All);
break;
}
}
}
static void HandleFallState(ItemInfo& pushableItem)
{
auto& pushable = GetPushableInfo(pushableItem);
// Get pushable collision.
auto pushableColl = GetPushableCollision(pushableItem);
int foundStack = NO_ITEM;
switch (pushableColl.EnvType)
{
case PushableEnvironmentType::Air:
pushableItem.Pose.Position.y += pushableItem.Animation.Velocity.y;
pushableItem.Animation.Velocity.y = std::min(pushableItem.Animation.Velocity.y + pushable.Gravity, PUSHABLE_FALL_VELOCITY_MAX);
HandlePushableFallRotation(pushableItem);
break;
case PushableEnvironmentType::FlatFloor:
case PushableEnvironmentType::WaterFloor:
// Connect to another stack.
foundStack = SearchNearPushablesStack(pushableItem.Index);
StackPushable(pushableItem.Index, foundStack);
pushableItem.Pose.Orientation = EulerAngles(0, pushableItem.Pose.Orientation.y, 0);
// Set stopper flag.
if (pushable.Stack.ItemNumberBelow == NO_ITEM)
SetPushableStopperFlag(true, pushableItem.Pose.Position, pushableItem.RoomNumber);
// Activate trigger.
TestTriggers(&pushableItem, true, pushableItem.Flags & IFLAG_ACTIVATION_MASK);
// Shake floor if pushable landed at high enough velocity.
if (pushableItem.Animation.Velocity.y >= PUSHABLE_RUMBLE_FALL_VELOCITY)
{
FloorShake(&pushableItem);
pushable.SoundState = PushableSoundState::Fall;
}
// Place on floor.
pushableItem.Animation.Velocity.y = 0.0f;
pushableItem.Pose.Position.y = pushableColl.FloorHeight;
pushable.BehaviorState = PushableBehaviourState::Idle;
AddPushableBridge(pushableItem);
break;
case PushableEnvironmentType::SlopedFloor:
pushableItem.Animation.Velocity.y = 0.0f;
pushableItem.Pose.Position.y = pushableColl.FloorHeight;
pushable.BehaviorState = PushableBehaviourState::Slide;
break;
case PushableEnvironmentType::Water:
pushable.BehaviorState = PushableBehaviourState::Sink;
SpawnPushableSplash(pushableItem);
break;
default:
TENLog("Error handling pushable collision in fall state for pushable item " + std::to_string(pushableItem.Index), LogLevel::Warning, LogConfig::All, false);
break;
}
}
static void HandleSinkState(ItemInfo& pushableItem)
{
auto& pushable = GetPushableInfo(pushableItem);
// Get pushable collision.
auto pushableColl = GetPushableCollision(pushableItem);
int foundStack = NO_ITEM;
switch (pushableColl.EnvType)
{
case PushableEnvironmentType::Air:
case PushableEnvironmentType::FlatFloor:
case PushableEnvironmentType::SlopedFloor:
// Set stopper flag.
if (pushable.Stack.ItemNumberBelow == NO_ITEM)
SetPushableStopperFlag(true, pushableItem.Pose.Position, pushableItem.RoomNumber);
pushable.BehaviorState = PushableBehaviourState::Fall;
pushable.Gravity = PUSHABLE_GRAVITY_AIR;
break;
case PushableEnvironmentType::Water:
{
// Manage influence of gravity.
if (pushable.IsBuoyant && pushable.Stack.ItemNumberAbove == NO_ITEM)
{
pushable.Gravity = pushable.Gravity - PUSHABLE_GRAVITY_ACCEL;
if (pushable.Gravity <= 0.0f)
{
pushable.BehaviorState = PushableBehaviourState::Float;
return;
}
}
else
{
pushable.Gravity = std::max(pushable.Gravity - PUSHABLE_GRAVITY_ACCEL, PUSHABLE_GRAVITY_WATER);
}
// Move object.
pushableItem.Pose.Position.y += pushableItem.Animation.Velocity.y;
pushableItem.Animation.Velocity.y = std::min(pushableItem.Animation.Velocity.y + pushable.Gravity, PUSHABLE_WATER_VELOCITY_MAX);
HandlePushableFallRotation(pushableItem);
// Shallow water.
int waterheight = abs(pushableColl.FloorHeight - pushable.WaterSurfaceHeight);
if (waterheight > GetPushableHeight(pushableItem))
{
HandlePushableRippleEffect(pushableItem);
}
// Deep water.
else
{
SpawnPushableBubbles(pushableItem);
}
}
break;
case PushableEnvironmentType::WaterFloor:
if (pushable.IsBuoyant && pushable.Stack.ItemNumberAbove == NO_ITEM)
{
pushableItem.Pose.Position.y = pushableColl.FloorHeight;
pushable.BehaviorState = PushableBehaviourState::Float;
pushable.Gravity = 0.0f;
}
else
{
pushableItem.Pose.Position.y = pushableColl.FloorHeight;
pushableItem.Animation.Velocity.y = 0.0f;
pushable.BehaviorState = PushableBehaviourState::UnderwaterIdle;
pushable.Gravity = PUSHABLE_GRAVITY_WATER;
AddPushableBridge(pushableItem);
pushableItem.Pose.Orientation = EulerAngles(0, pushableItem.Pose.Orientation.y, 0);
// Activate trigger.
TestTriggers(&pushableItem, true, pushableItem.Flags & IFLAG_ACTIVATION_MASK);
}
break;
default:
TENLog("Error handling pushable collision in sink state for pushable item " + std::to_string(pushableItem.Index), LogLevel::Warning, LogConfig::All, false);
break;
}
}
static void HandleFloatState(ItemInfo& pushableItem)
{
auto& pushable = GetPushableInfo(pushableItem);
// Get pushable collision.
auto pushableColl = GetPushableCollision(pushableItem);
int targetHeight = 0;
switch (pushableColl.EnvType)
{
case PushableEnvironmentType::Air:
case PushableEnvironmentType::FlatFloor:
case PushableEnvironmentType::SlopedFloor:
// Set stopper flag.
if (pushable.Stack.ItemNumberBelow == NO_ITEM)
SetPushableStopperFlag(true, pushableItem.Pose.Position, pushableItem.RoomNumber);
pushable.BehaviorState = PushableBehaviourState::Fall;
pushable.Gravity = PUSHABLE_GRAVITY_AIR;
break;
case PushableEnvironmentType::Water:
case PushableEnvironmentType::WaterFloor:
// Determine target height.
if (pushable.WaterSurfaceHeight == NO_HEIGHT)
{
if (abs(pushableColl.CeilingHeight - pushableColl.FloorHeight) >= (GetPushableHeight(pushableItem) + PUSHABLE_WATER_SURFACE_DISTANCE))
{
targetHeight = (pushableColl.CeilingHeight + pushable.Height) + PUSHABLE_WATER_SURFACE_DISTANCE;
}
else
{
targetHeight = pushableItem.Pose.Position.y;
}
}
// No ceiling; rise above water surface.
else
{
targetHeight = pushable.WaterSurfaceHeight - PUSHABLE_WATER_SURFACE_DISTANCE + pushable.Height;
}
// Manage influence of gravity.
pushable.Gravity = std::max(pushable.Gravity - PUSHABLE_GRAVITY_ACCEL, -PUSHABLE_GRAVITY_WATER);
// Move up.
if (targetHeight < pushableItem.Pose.Position.y)
{
pushableItem.Pose.Position.y += pushableItem.Animation.Velocity.y;
pushableItem.Animation.Velocity.y = std::min(pushableItem.Animation.Velocity.y + pushable.Gravity, PUSHABLE_WATER_VELOCITY_MAX);
HandlePushableFallRotation(pushableItem);
}
// Reached target height.
else
{
pushableItem.Animation.Velocity.y = 0.0f;
pushableItem.Pose.Position.y = targetHeight;
pushableItem.Pose.Orientation = EulerAngles(0, pushableItem.Pose.Orientation.y, 0);
pushable.BehaviorState = PushableBehaviourState::WaterSurfaceIdle;
pushable.Gravity = PUSHABLE_GRAVITY_WATER;
AddPushableBridge(pushableItem);
}
break;
default:
TENLog("Error handling pushable collision in float state for pushable item " + std::to_string(pushableItem.Index), LogLevel::Warning, LogConfig::All, false);
break;
}
}
static void HandleUnderwaterState(ItemInfo& pushableItem)
{
auto& pushable = GetPushableInfo(pushableItem);
// Get pushable collision.
auto pushableColl = GetPushableCollision(pushableItem);
switch (pushableColl.EnvType)
{
case PushableEnvironmentType::FlatFloor:
case PushableEnvironmentType::SlopedFloor:
case PushableEnvironmentType::Air:
// Set stopper flag.
if (pushable.Stack.ItemNumberBelow == NO_ITEM)
SetPushableStopperFlag(true, pushableItem.Pose.Position, pushableItem.RoomNumber);
pushableItem.Animation.Velocity.y = 0.0f;
pushableItem.Pose.Position.y = pushableColl.FloorHeight;
pushable.BehaviorState = PushableBehaviourState::Idle;
pushable.Gravity = PUSHABLE_GRAVITY_AIR;
break;
case PushableEnvironmentType::WaterFloor:
{
// Reached water floor; change to idle.
if (pushable.WaterSurfaceHeight != NO_HEIGHT)
{
int waterheight = abs(pushableColl.FloorHeight - pushable.WaterSurfaceHeight);
if (waterheight < GetPushableHeight(pushableItem))
{
pushableItem.Animation.Velocity.y = 0.0f;
pushableItem.Pose.Position.y = pushableColl.FloorHeight;
pushable.BehaviorState = PushableBehaviourState::Idle;
pushable.Gravity = PUSHABLE_GRAVITY_AIR;
}
}
else if (pushable.IsBuoyant && pushable.Stack.ItemNumberAbove == NO_ITEM)
{
pushableItem.Animation.Velocity.y = 0.0f;
pushable.BehaviorState = PushableBehaviourState::Float;
pushable.Gravity = 0.0f;
}
// Remain on floor.
pushableItem.Pose.Position.y = pushableColl.FloorHeight;
int relHeight = pushableColl.FloorHeight - pushableItem.Pose.Position.y;
SetPushableVerticalPos(pushableItem, relHeight);
}
break;
case PushableEnvironmentType::Water:
if (pushable.IsBuoyant && pushable.Stack.ItemNumberAbove == NO_ITEM)
{
pushableItem.Animation.Velocity.y = 0.0f;
pushable.BehaviorState = PushableBehaviourState::Float;
pushable.Gravity = 0.0f;
return;
}
//Only pass to sinking if distance to noticeable. If is small, just stuck it to the ground.
if (abs(pushableColl.FloorHeight - pushableItem.Pose.Position.y) > CLICK(0.75f))
{
//Reset Stopper Flag
if (pushable.Stack.ItemNumberBelow == NO_ITEM)
SetPushableStopperFlag(false, pushableItem.Pose.Position, pushableItem.RoomNumber);
pushableItem.Animation.Velocity.y = 0.0f;
pushable.BehaviorState = PushableBehaviourState::Sink;
pushable.Gravity = 0.0f;
}
else
{
pushableItem.Pose.Position.y = pushableColl.FloorHeight;
}
break;
default:
TENLog("Error handling pushable collision in underwater state for pushable item " + std::to_string(pushableItem.Index), LogLevel::Warning, LogConfig::All, false);
break;
}
}
static void HandleWaterSurfaceState(ItemInfo& pushableItem)
{
auto& pushable = GetPushableInfo(pushableItem);
// Get pushable collision.
auto pushableColl = GetPushableCollision(pushableItem);
switch (pushableColl.EnvType)
{
case PushableEnvironmentType::FlatFloor:
case PushableEnvironmentType::SlopedFloor:
case PushableEnvironmentType::Air:
pushableItem.Animation.Velocity.y = 0.0f;
pushableItem.Pose.Orientation = EulerAngles(0, pushableItem.Pose.Orientation.y, 0);
pushable.BehaviorState = PushableBehaviourState::Fall;
pushable.Gravity = PUSHABLE_GRAVITY_AIR;
RemovePushableBridge(pushableItem);
break;
case PushableEnvironmentType::Water:
pushable.UseRoomCollision ? HandlePushableBridgeOscillation(pushableItem) : HandlePushableOscillation(pushableItem);
HandlePushableRippleEffect(pushableItem);
break;
case PushableEnvironmentType::WaterFloor:
{
// If shallow water, change to idle state.
int waterheight = abs(pushableColl.FloorHeight - pushable.WaterSurfaceHeight);
if (waterheight < GetPushableHeight(pushableItem))
{
pushable.BehaviorState = PushableBehaviourState::Idle;
pushable.Gravity = PUSHABLE_GRAVITY_AIR;
}
else
{
pushable.BehaviorState = PushableBehaviourState::UnderwaterIdle;
pushable.Gravity = PUSHABLE_GRAVITY_WATER;
}
pushableItem.Animation.Velocity.y = 0.0f;
pushableItem.Pose.Position.y = pushableColl.FloorHeight;
pushableItem.Pose.Orientation = EulerAngles(0, pushableItem.Pose.Orientation.y, 0);
}
break;
default:
TENLog("Error handling pushable collision in water surface state for pushable item " + std::to_string(pushableItem.Index), LogLevel::Warning, LogConfig::All, false);
break;
}
}
static void HandleSlideState(ItemInfo& pushableItem)
{
auto& pushable = GetPushableInfo(pushableItem);
// TODO:
// 1. Get floor surface's slope and aspect angles.
// 2. Calculate speed.
// 3. Move object.
// 4. Assess oncoming sector.
// Is slope -> keep sliding.
// Is flat floor -> stop sliding.
// Is ledge -> fall.
// Is forbidden sector -> freeze.
// 5. Incorporate effects.
}
static void HandleMoveStackHorizontalState(ItemInfo& pushableItem)
{
auto& pushable = GetPushableInfo(pushableItem);
const auto& playerItem = *LaraItem;
const auto& player = GetLaraInfo(playerItem);
auto& movingPushableItem = g_Level.Items[player.Context.InteractedItem];
pushableItem.Pose.Position.x = movingPushableItem.Pose.Position.x;
pushableItem.Pose.Position.z = movingPushableItem.Pose.Position.z;
}
// TODO
/*void HandleStackFallState(ItemInfo& pushableItem)
{
auto& pushableItem = g_Level.Items[pushableItem.Index];
auto& pushable = GetPushableInfo(pushableItem);
const auto& playerItem = *LaraItem;
const auto& player = GetLaraInfo(playerItem);
auto& movingPushableItem = g_Level.Items[player.Context.InteractedItem];
pushableItem.Pose.Position.y = movingPushableItem.Pose.Position.y;
}*/
void HandlePushableBehaviorState(ItemInfo& pushableItem)
{
static const auto BEHAVIOR_STATE_MAP = std::unordered_map<PushableBehaviourState, std::function<void(ItemInfo& pushableItem)>>
{
{ PushableBehaviourState::Idle, &HandleIdleState },
{ PushableBehaviourState::Move, &HandleMoveState },
{ PushableBehaviourState::EdgeSlip, &HandleEdgeSlipState },
{ PushableBehaviourState::Fall, &HandleFallState },
{ PushableBehaviourState::Sink, &HandleSinkState },
{ PushableBehaviourState::Float, &HandleFloatState },
{ PushableBehaviourState::UnderwaterIdle, &HandleUnderwaterState },
{ PushableBehaviourState::WaterSurfaceIdle, &HandleWaterSurfaceState },
{ PushableBehaviourState::Slide, &HandleSlideState },
{ PushableBehaviourState::MoveStackHorizontal, &HandleMoveStackHorizontalState }
};
auto& pushable = GetPushableInfo(pushableItem);
auto it = BEHAVIOR_STATE_MAP.find(pushable.BehaviorState);
if (it == BEHAVIOR_STATE_MAP.end())
{
TENLog("Attempted to handle missing pushable state " + std::to_string((int)pushable.BehaviorState), LogLevel::Error, LogConfig::All);
return;
}
it->second(pushableItem);
}
}

View file

@ -0,0 +1,22 @@
#pragma once
struct ItemInfo;
namespace TEN::Entities::Generic
{
enum class PushableBehaviourState
{
Idle,
Move,
EdgeSlip,
Fall,
Sink,
Float,
UnderwaterIdle,
WaterSurfaceIdle,
Slide,
MoveStackHorizontal
};
void HandlePushableBehaviorState(ItemInfo& pushableItem);
};

View file

@ -14,7 +14,7 @@ std::optional<int> BridgeFloor(short itemNumber, int x, int y, int z)
{ {
const auto& item = g_Level.Items[itemNumber]; const auto& item = g_Level.Items[itemNumber];
auto boxHeight = GetBridgeItemIntersect(itemNumber, x, y, z, false); auto boxHeight = GetBridgeItemIntersect(Vector3i(x, y, z), itemNumber, false);
if (boxHeight.has_value() && tiltGrade != 0) if (boxHeight.has_value() && tiltGrade != 0)
{ {
int height = item.Pose.Position.y + (tiltGrade * ((GetOffset(item.Pose.Orientation.y, x, z) / 4) + (BLOCK(1) / 8))); int height = item.Pose.Position.y + (tiltGrade * ((GetOffset(item.Pose.Orientation.y, x, z) / 4) + (BLOCK(1) / 8)));
@ -29,7 +29,7 @@ std::optional<int> BridgeCeiling(short itemNumber, int x, int y, int z)
{ {
const auto& item = g_Level.Items[itemNumber]; const auto& item = g_Level.Items[itemNumber];
auto boxHeight = GetBridgeItemIntersect(itemNumber, x, y, z, true); auto boxHeight = GetBridgeItemIntersect(Vector3i(x, y, z), itemNumber, true);
if (boxHeight.has_value() && tiltGrade != 0) if (boxHeight.has_value() && tiltGrade != 0)
{ {
int height = item.Pose.Position.y + (tiltGrade * ((GetOffset(item.Pose.Orientation.y, x, z) / 4) + (BLOCK(1) / 8))); int height = item.Pose.Position.y + (tiltGrade * ((GetOffset(item.Pose.Orientation.y, x, z) / 4) + (BLOCK(1) / 8)));

View file

@ -232,7 +232,7 @@ std::optional<int> TrapDoorFloor(short itemNumber, int x, int y, int z)
if (!trapDoorItem->MeshBits.TestAny() || trapDoorItem->ItemFlags[2] == 0) if (!trapDoorItem->MeshBits.TestAny() || trapDoorItem->ItemFlags[2] == 0)
return std::nullopt; return std::nullopt;
return GetBridgeItemIntersect(itemNumber, x, y, z, false); return GetBridgeItemIntersect(Vector3i(x, y, z), itemNumber, false);
} }
std::optional<int> TrapDoorCeiling(short itemNumber, int x, int y, int z) std::optional<int> TrapDoorCeiling(short itemNumber, int x, int y, int z)
@ -242,5 +242,5 @@ std::optional<int> TrapDoorCeiling(short itemNumber, int x, int y, int z)
if (!trapDoorItem->MeshBits.TestAny() || trapDoorItem->ItemFlags[2] == 0) if (!trapDoorItem->MeshBits.TestAny() || trapDoorItem->ItemFlags[2] == 0)
return std::nullopt; return std::nullopt;
return GetBridgeItemIntersect(itemNumber, x, y, z, true); return GetBridgeItemIntersect(Vector3i(x, y, z), itemNumber, true);
} }

View file

@ -77,78 +77,82 @@ namespace TEN::Entities::Traps
switch (item.Animation.ActiveState) switch (item.Animation.ActiveState)
{ {
case CRUMBLING_PLATFORM_STATE_SHAKE: case CRUMBLING_PLATFORM_STATE_IDLE:
{
if (item.ItemFlags[0] > 0)
{
item.ItemFlags[0]--;
}
else
{
item.Animation.TargetState = CRUMBLING_PLATFORM_STATE_FALL;
item.ItemFlags[1] = CRUMBLING_PLATFORM_VELOCITY_MIN;
auto pointColl = GetCollision(item);
pointColl.Block->RemoveBridge(itemNumber);
}
}
break; break;
case CRUMBLING_PLATFORM_STATE_FALL: case CRUMBLING_PLATFORM_STATE_SHAKE:
{
if (item.ItemFlags[0] > 0)
{ {
short& fallVel = item.ItemFlags[1]; item.ItemFlags[0]--;
}
else
{
item.Animation.TargetState = CRUMBLING_PLATFORM_STATE_FALL;
item.ItemFlags[1] = CRUMBLING_PLATFORM_VELOCITY_MIN;
// Get point collision.
auto box = GameBoundingBox(&item);
auto pointColl = GetCollision(item); auto pointColl = GetCollision(item);
int relFloorHeight = (item.Pose.Position.y - pointColl.Position.Floor) - box.Y1 ; pointColl.Block->RemoveBridge(itemNumber);
// Airborne.
if (relFloorHeight <= fallVel)
{
fallVel += CRUMBLING_PLATFORM_VELOCITY_ACCEL;
if (fallVel > CRUMBLING_PLATFORM_VELOCITY_MAX)
fallVel = CRUMBLING_PLATFORM_VELOCITY_MAX;
item.Pose.Position.y += fallVel;
}
// Grounded.
else
{
item.Animation.TargetState = CRUMBLING_PLATFORM_STATE_LAND;
item.Pose.Position.y = pointColl.Position.Floor;
}
// Update room number.
int probedRoomNumber = pointColl.RoomNumber;
if (item.RoomNumber != probedRoomNumber)
ItemNewRoom(itemNumber, probedRoomNumber);
} }
}
break; break;
case CRUMBLING_PLATFORM_STATE_LAND: case CRUMBLING_PLATFORM_STATE_FALL:
{
short& fallVel = item.ItemFlags[1];
// Get point collision.
auto box = GameBoundingBox(&item);
auto pointColl = GetCollision(item);
int relFloorHeight = (item.Pose.Position.y - pointColl.Position.Floor) - box.Y1 ;
// Airborne.
if (relFloorHeight <= fallVel)
{ {
// Align to surface. fallVel += CRUMBLING_PLATFORM_VELOCITY_ACCEL;
auto radius = Vector2(Objects[item.ObjectNumber].radius); if (fallVel > CRUMBLING_PLATFORM_VELOCITY_MAX)
AlignEntityToSurface(&item, radius); fallVel = CRUMBLING_PLATFORM_VELOCITY_MAX;
// Deactivate. item.Pose.Position.y += fallVel;
if (TestLastFrame(&item)) }
{ // Grounded.
RemoveActiveItem(itemNumber); else
item.Status = ITEM_NOT_ACTIVE; {
} item.Animation.TargetState = CRUMBLING_PLATFORM_STATE_LAND;
item.Pose.Position.y = pointColl.Position.Floor;
} }
break; // Update room number.
int probedRoomNumber = pointColl.RoomNumber;
if (item.RoomNumber != probedRoomNumber)
ItemNewRoom(itemNumber, probedRoomNumber);
}
default: break;
TENLog(
"Error with crumbling platform with entity ID" + std::to_string(itemNumber) + ". animation state not recognized.", case CRUMBLING_PLATFORM_STATE_LAND:
LogLevel::Error, LogConfig::All); {
break; // Align to surface.
auto radius = Vector2(Objects[item.ObjectNumber].radius);
AlignEntityToSurface(&item, radius);
// Deactivate.
if (TestLastFrame(&item))
{
RemoveActiveItem(itemNumber);
item.Status = ITEM_NOT_ACTIVE;
}
}
break;
default:
TENLog(
"Error with crumbling platform item " + std::to_string(itemNumber) +
": attempted to handle missing state " + std::to_string(item.Animation.ActiveState),
LogLevel::Error, LogConfig::All);
break;
} }
AnimateItem(&item); AnimateItem(&item);
@ -181,7 +185,7 @@ namespace TEN::Entities::Traps
if (item.Animation.ActiveState == CRUMBLING_PLATFORM_STATE_IDLE || if (item.Animation.ActiveState == CRUMBLING_PLATFORM_STATE_IDLE ||
item.Animation.ActiveState == CRUMBLING_PLATFORM_STATE_SHAKE) item.Animation.ActiveState == CRUMBLING_PLATFORM_STATE_SHAKE)
{ {
auto boxHeight = GetBridgeItemIntersect(itemNumber, x, y, z, false); auto boxHeight = GetBridgeItemIntersect(Vector3i(x, y, z), itemNumber, false);
if (boxHeight.has_value()) if (boxHeight.has_value())
return *boxHeight; return *boxHeight;
} }
@ -196,7 +200,7 @@ namespace TEN::Entities::Traps
if (item.Animation.ActiveState == CRUMBLING_PLATFORM_STATE_IDLE || if (item.Animation.ActiveState == CRUMBLING_PLATFORM_STATE_IDLE ||
item.Animation.ActiveState == CRUMBLING_PLATFORM_STATE_SHAKE) item.Animation.ActiveState == CRUMBLING_PLATFORM_STATE_SHAKE)
{ {
auto boxHeight = GetBridgeItemIntersect(itemNumber, x, y, z, true); auto boxHeight = GetBridgeItemIntersect(Vector3i(x, y, z), itemNumber, true);
if (boxHeight.has_value()) if (boxHeight.has_value())
return *boxHeight; return *boxHeight;
} }

View file

@ -145,7 +145,7 @@ std::optional<int> FallingBlockFloor(short itemNumber, int x, int y, int z)
if (!item->MeshBits.TestAny() || item->ItemFlags[0] >= FALLINGBLOCK_DELAY) if (!item->MeshBits.TestAny() || item->ItemFlags[0] >= FALLINGBLOCK_DELAY)
return std::nullopt; return std::nullopt;
return GetBridgeItemIntersect(itemNumber, x, y, z, false); return GetBridgeItemIntersect(Vector3i(x, y, z), itemNumber, false);
} }
std::optional<int> FallingBlockCeiling(short itemNumber, int x, int y, int z) std::optional<int> FallingBlockCeiling(short itemNumber, int x, int y, int z)
@ -155,7 +155,7 @@ std::optional<int> FallingBlockCeiling(short itemNumber, int x, int y, int z)
if (!item->MeshBits.TestAny() || item->ItemFlags[0] >= FALLINGBLOCK_DELAY) if (!item->MeshBits.TestAny() || item->ItemFlags[0] >= FALLINGBLOCK_DELAY)
return std::nullopt; return std::nullopt;
return GetBridgeItemIntersect(itemNumber, x, y, z, true); return GetBridgeItemIntersect(Vector3i(x, y, z), itemNumber, true);
} }
int FallingBlockFloorBorder(short itemNumber) int FallingBlockFloorBorder(short itemNumber)

View file

@ -14,6 +14,7 @@
#include "Objects/Generic/Object/generic_bridge.h" #include "Objects/Generic/Object/generic_bridge.h"
#include "Objects/Generic/Object/burning_torch.h" #include "Objects/Generic/Object/burning_torch.h"
#include "Objects/Generic/Object/polerope.h" #include "Objects/Generic/Object/polerope.h"
#include "Objects/Generic/Object/Pushable/PushableObject.h"
#include "Objects/Generic/Object/rope.h" #include "Objects/Generic/Object/rope.h"
// Switches // Switches
@ -61,7 +62,7 @@ static void StartObject(ObjectInfo* object)
object->ceilingBorder = TrapDoorCeilingBorder; object->ceilingBorder = TrapDoorCeilingBorder;
object->floor = TrapDoorFloor; object->floor = TrapDoorFloor;
object->ceiling = TrapDoorCeiling; object->ceiling = TrapDoorCeiling;
object->SetupHitEffect(true); object->SetHitEffect(true);
} }
} }
@ -77,7 +78,7 @@ static void StartObject(ObjectInfo* object)
object->ceilingBorder = TrapDoorCeilingBorder; object->ceilingBorder = TrapDoorCeilingBorder;
object->floor = TrapDoorFloor; object->floor = TrapDoorFloor;
object->ceiling = TrapDoorCeiling; object->ceiling = TrapDoorCeiling;
object->SetupHitEffect(true); object->SetHitEffect(true);
} }
} }
@ -93,10 +94,16 @@ static void StartObject(ObjectInfo* object)
object->ceilingBorder = TrapDoorCeilingBorder; object->ceilingBorder = TrapDoorCeilingBorder;
object->floor = TrapDoorFloor; object->floor = TrapDoorFloor;
object->ceiling = TrapDoorCeiling; object->ceiling = TrapDoorCeiling;
object->SetupHitEffect(true); object->SetHitEffect(true);
} }
} }
for (int objectNumber = ID_PUSHABLE_OBJECT1; objectNumber <= ID_PUSHABLE_OBJECT10; objectNumber++)
InitPushableObject(object, objectNumber);
for (int objectNumber = ID_PUSHABLE_OBJECT_CLIMBABLE1; objectNumber <= ID_PUSHABLE_OBJECT_CLIMBABLE10; objectNumber++)
InitPushableObject(object, objectNumber);
object = &Objects[ID_BRIDGE_FLAT]; object = &Objects[ID_BRIDGE_FLAT];
if (object->loaded) if (object->loaded)
{ {
@ -155,7 +162,7 @@ void StartSwitches(ObjectInfo* object)
{ {
object->collision = CogSwitchCollision; object->collision = CogSwitchCollision;
object->control = CogSwitchControl; object->control = CogSwitchControl;
object->SetupHitEffect(true); object->SetHitEffect(true);
} }
object = &Objects[ID_LEVER_SWITCH]; object = &Objects[ID_LEVER_SWITCH];
@ -163,7 +170,7 @@ void StartSwitches(ObjectInfo* object)
{ {
object->collision = RailSwitchCollision; object->collision = RailSwitchCollision;
object->control = SwitchControl; object->control = SwitchControl;
object->SetupHitEffect(true); object->SetHitEffect(true);
} }
object = &Objects[ID_JUMP_SWITCH]; object = &Objects[ID_JUMP_SWITCH];
@ -171,7 +178,7 @@ void StartSwitches(ObjectInfo* object)
{ {
object->collision = JumpSwitchCollision; object->collision = JumpSwitchCollision;
object->control = SwitchControl; object->control = SwitchControl;
object->SetupHitEffect(true); object->SetHitEffect(true);
} }
for (int objectNumber = ID_SWITCH_TYPE1; objectNumber <= ID_SWITCH_TYPE16; objectNumber++) for (int objectNumber = ID_SWITCH_TYPE1; objectNumber <= ID_SWITCH_TYPE16; objectNumber++)
@ -181,7 +188,7 @@ void StartSwitches(ObjectInfo* object)
{ {
object->collision = SwitchCollision; object->collision = SwitchCollision;
object->control = SwitchControl; object->control = SwitchControl;
object->SetupHitEffect(true); object->SetHitEffect(true);
} }
} }
@ -190,7 +197,7 @@ void StartSwitches(ObjectInfo* object)
{ {
object->collision = AirlockSwitchCollision; object->collision = AirlockSwitchCollision;
object->control = SwitchControl; object->control = SwitchControl;
object->SetupHitEffect(true); object->SetHitEffect(true);
} }
object = &Objects[ID_CROWBAR_SWITCH]; object = &Objects[ID_CROWBAR_SWITCH];
@ -198,7 +205,7 @@ void StartSwitches(ObjectInfo* object)
{ {
object->collision = CrowbarSwitchCollision; object->collision = CrowbarSwitchCollision;
object->control = SwitchControl; object->control = SwitchControl;
object->SetupHitEffect(true); object->SetHitEffect(true);
} }
object = &Objects[ID_MINECART_SWITCH]; object = &Objects[ID_MINECART_SWITCH];
@ -207,7 +214,7 @@ void StartSwitches(ObjectInfo* object)
object->Initialize = InitializeAnimating; object->Initialize = InitializeAnimating;
object->control = AnimatingControl; object->control = AnimatingControl;
object->collision = ObjectCollision; object->collision = ObjectCollision;
object->SetupHitEffect(true); object->SetHitEffect(true);
object->shadowType = ShadowMode::All; object->shadowType = ShadowMode::All;
} }
@ -234,7 +241,7 @@ void StartSwitches(ObjectInfo* object)
{ {
object->control = TurnSwitchControl; object->control = TurnSwitchControl;
object->collision = TurnSwitchCollision; object->collision = TurnSwitchCollision;
object->SetupHitEffect(true); object->SetHitEffect(true);
} }
object = &Objects[ID_SEQUENCE_SWITCH1]; object = &Objects[ID_SEQUENCE_SWITCH1];
@ -242,7 +249,7 @@ void StartSwitches(ObjectInfo* object)
{ {
object->collision = FullBlockSwitchCollision; object->collision = FullBlockSwitchCollision;
object->control = FullBlockSwitchControl; object->control = FullBlockSwitchControl;
object->SetupHitEffect(true); object->SetHitEffect(true);
} }
object = &Objects[ID_SEQUENCE_SWITCH2]; object = &Objects[ID_SEQUENCE_SWITCH2];
@ -250,7 +257,7 @@ void StartSwitches(ObjectInfo* object)
{ {
object->collision = FullBlockSwitchCollision; object->collision = FullBlockSwitchCollision;
object->control = FullBlockSwitchControl; object->control = FullBlockSwitchControl;
object->SetupHitEffect(true); object->SetHitEffect(true);
} }
object = &Objects[ID_SEQUENCE_SWITCH3]; object = &Objects[ID_SEQUENCE_SWITCH3];
@ -258,7 +265,7 @@ void StartSwitches(ObjectInfo* object)
{ {
object->collision = FullBlockSwitchCollision; object->collision = FullBlockSwitchCollision;
object->control = FullBlockSwitchControl; object->control = FullBlockSwitchControl;
object->SetupHitEffect(true); object->SetHitEffect(true);
} }
for (int objectID = ID_SHOOT_SWITCH1; objectID <= ID_SHOOT_SWITCH4; objectID++) for (int objectID = ID_SHOOT_SWITCH1; objectID <= ID_SHOOT_SWITCH4; objectID++)
@ -292,7 +299,7 @@ void StartDoors(ObjectInfo* object)
object->Initialize = InitializeDoor; object->Initialize = InitializeDoor;
object->control = DoorControl; object->control = DoorControl;
object->collision = DoorCollision; object->collision = DoorCollision;
object->SetupHitEffect(true); object->SetHitEffect(true);
} }
} }
@ -301,7 +308,7 @@ void StartDoors(ObjectInfo* object)
{ {
object->Initialize = InitializeDoor; object->Initialize = InitializeDoor;
object->control = DoorControl; object->control = DoorControl;
object->SetupHitEffect(true); object->SetHitEffect(true);
} }
object = &Objects[ID_LIFT_DOORS2]; object = &Objects[ID_LIFT_DOORS2];
@ -309,7 +316,7 @@ void StartDoors(ObjectInfo* object)
{ {
object->Initialize = InitializeDoor; object->Initialize = InitializeDoor;
object->control = DoorControl; object->control = DoorControl;
object->SetupHitEffect(true); object->SetHitEffect(true);
} }
object = &Objects[ID_SEQUENCE_DOOR1]; object = &Objects[ID_SEQUENCE_DOOR1];
@ -318,7 +325,7 @@ void StartDoors(ObjectInfo* object)
object->Initialize = InitializeDoor; object->Initialize = InitializeDoor;
object->collision = DoorCollision; object->collision = DoorCollision;
object->control = SequenceDoorControl; object->control = SequenceDoorControl;
object->SetupHitEffect(true); object->SetHitEffect(true);
} }
for (int i = ID_DOUBLE_DOORS1; i <= ID_DOUBLE_DOORS4; i++) for (int i = ID_DOUBLE_DOORS1; i <= ID_DOUBLE_DOORS4; i++)
@ -329,7 +336,7 @@ void StartDoors(ObjectInfo* object)
object->Initialize = InitializeDoor; object->Initialize = InitializeDoor;
object->collision = DoubleDoorCollision; object->collision = DoubleDoorCollision;
object->control = PushPullKickDoorControl; object->control = PushPullKickDoorControl;
object->SetupHitEffect(true); object->SetHitEffect(true);
} }
} }
@ -341,7 +348,7 @@ void StartDoors(ObjectInfo* object)
object->Initialize = InitializeDoor; object->Initialize = InitializeDoor;
object->collision = UnderwaterDoorCollision; object->collision = UnderwaterDoorCollision;
object->control = PushPullKickDoorControl; object->control = PushPullKickDoorControl;
object->SetupHitEffect(true); object->SetHitEffect(true);
} }
} }
@ -353,7 +360,7 @@ void StartDoors(ObjectInfo* object)
object->Initialize = InitializeDoor; object->Initialize = InitializeDoor;
object->collision = PushPullKickDoorCollision; object->collision = PushPullKickDoorCollision;
object->control = PushPullKickDoorControl; object->control = PushPullKickDoorControl;
object->SetupHitEffect(true); object->SetHitEffect(true);
} }
} }
@ -362,7 +369,7 @@ void StartDoors(ObjectInfo* object)
{ {
object->Initialize = InitializeSteelDoor; object->Initialize = InitializeSteelDoor;
object->collision = SteelDoorCollision; object->collision = SteelDoorCollision;
object->SetupHitEffect(true); object->SetHitEffect(true);
} }
} }
@ -406,7 +413,7 @@ void StartTraps(ObjectInfo* object)
if (object->loaded) if (object->loaded)
{ {
object->collision = PoleCollision; object->collision = PoleCollision;
object->SetupHitEffect(true); object->SetHitEffect(true);
} }
object = &Objects[ID_BURNING_TORCH_ITEM]; object = &Objects[ID_BURNING_TORCH_ITEM];

View file

@ -46,7 +46,7 @@ static void StartEntity(ObjectInfo* obj)
obj->radius = 340; obj->radius = 340;
obj->intelligent = true; obj->intelligent = true;
obj->SetBoneRotationFlags(2, ROT_Y); obj->SetBoneRotationFlags(2, ROT_Y);
obj->SetupHitEffect(); obj->SetHitEffect();
} }
obj = &Objects[ID_BEAR]; obj = &Objects[ID_BEAR];
@ -61,7 +61,7 @@ static void StartEntity(ObjectInfo* obj)
obj->radius = 340; obj->radius = 340;
obj->intelligent = true; obj->intelligent = true;
obj->SetBoneRotationFlags(13, ROT_Y); obj->SetBoneRotationFlags(13, ROT_Y);
obj->SetupHitEffect(); obj->SetHitEffect();
} }
obj = &Objects[ID_APE]; obj = &Objects[ID_APE];
@ -76,7 +76,7 @@ static void StartEntity(ObjectInfo* obj)
obj->radius = 340; obj->radius = 340;
obj->intelligent = true; obj->intelligent = true;
obj->LotType = LotType::Ape; obj->LotType = LotType::Ape;
obj->SetupHitEffect(); obj->SetHitEffect();
} }
obj = &Objects[ID_BIG_RAT]; obj = &Objects[ID_BIG_RAT];
@ -93,7 +93,7 @@ static void StartEntity(ObjectInfo* obj)
obj->waterCreature = true; obj->waterCreature = true;
obj->LotType = LotType::Water; obj->LotType = LotType::Water;
obj->SetBoneRotationFlags(1, ROT_Y); obj->SetBoneRotationFlags(1, ROT_Y);
obj->SetupHitEffect(); obj->SetHitEffect();
} }
obj = &Objects[ID_NATLA]; obj = &Objects[ID_NATLA];
@ -109,7 +109,7 @@ static void StartEntity(ObjectInfo* obj)
obj->LotType = LotType::Flyer; obj->LotType = LotType::Flyer;
obj->SetBoneRotationFlags(0, ROT_X | ROT_Y); // Torso obj->SetBoneRotationFlags(0, ROT_X | ROT_Y); // Torso
obj->SetBoneRotationFlags(1, ROT_X | ROT_Y); // Head obj->SetBoneRotationFlags(1, ROT_X | ROT_Y); // Head
obj->SetupHitEffect(); obj->SetHitEffect();
} }
obj = &Objects[ID_GIANT_MUTANT]; obj = &Objects[ID_GIANT_MUTANT];
@ -124,7 +124,7 @@ static void StartEntity(ObjectInfo* obj)
obj->intelligent = true; obj->intelligent = true;
obj->LotType = LotType::Blockable; obj->LotType = LotType::Blockable;
obj->SetBoneRotationFlags(1, ROT_Y); obj->SetBoneRotationFlags(1, ROT_Y);
obj->SetupHitEffect(); obj->SetHitEffect();
} }
obj = &Objects[ID_DOPPELGANGER]; obj = &Objects[ID_DOPPELGANGER];
@ -142,7 +142,7 @@ static void StartEntity(ObjectInfo* obj)
obj->radius = 102; obj->radius = 102;
obj->intelligent = true; obj->intelligent = true;
obj->nonLot = true; obj->nonLot = true;
obj->SetupHitEffect(); obj->SetHitEffect();
} }
obj = &Objects[ID_CENTAUR_MUTANT]; obj = &Objects[ID_CENTAUR_MUTANT];
@ -158,7 +158,7 @@ static void StartEntity(ObjectInfo* obj)
obj->intelligent = true; obj->intelligent = true;
obj->LotType = LotType::Blockable; obj->LotType = LotType::Blockable;
obj->SetBoneRotationFlags(10, ROT_X | ROT_Y); obj->SetBoneRotationFlags(10, ROT_X | ROT_Y);
obj->SetupHitEffect(); obj->SetHitEffect();
} }
obj = &Objects[ID_WINGED_MUMMY]; obj = &Objects[ID_WINGED_MUMMY];
@ -175,7 +175,7 @@ static void StartEntity(ObjectInfo* obj)
obj->LotType = LotType::Flyer; obj->LotType = LotType::Flyer;
obj->SetBoneRotationFlags(1, ROT_Y); obj->SetBoneRotationFlags(1, ROT_Y);
obj->SetBoneRotationFlags(2, ROT_Y); obj->SetBoneRotationFlags(2, ROT_Y);
obj->SetupHitEffect(); obj->SetHitEffect();
} }
obj = &Objects[ID_COWBOY]; obj = &Objects[ID_COWBOY];
@ -191,7 +191,7 @@ static void StartEntity(ObjectInfo* obj)
obj->intelligent = true; obj->intelligent = true;
obj->SetBoneRotationFlags(0, ROT_X | ROT_Y); obj->SetBoneRotationFlags(0, ROT_X | ROT_Y);
obj->SetBoneRotationFlags(1, ROT_X | ROT_Y); obj->SetBoneRotationFlags(1, ROT_X | ROT_Y);
obj->SetupHitEffect(); obj->SetHitEffect();
} }
obj = &Objects[ID_KOLD]; obj = &Objects[ID_KOLD];
@ -207,7 +207,7 @@ static void StartEntity(ObjectInfo* obj)
obj->intelligent = true; obj->intelligent = true;
obj->SetBoneRotationFlags(1, ROT_Y); obj->SetBoneRotationFlags(1, ROT_Y);
obj->SetBoneRotationFlags(0, ROT_X | ROT_Y); obj->SetBoneRotationFlags(0, ROT_X | ROT_Y);
obj->SetupHitEffect(); obj->SetHitEffect();
} }
obj = &Objects[ID_SKATEBOARD_KID]; obj = &Objects[ID_SKATEBOARD_KID];
@ -223,7 +223,7 @@ static void StartEntity(ObjectInfo* obj)
obj->intelligent = true; obj->intelligent = true;
obj->SetBoneRotationFlags(7, ROT_Y); // Head. obj->SetBoneRotationFlags(7, ROT_Y); // Head.
obj->SetBoneRotationFlags(0, ROT_Y | ROT_X); // Torso. obj->SetBoneRotationFlags(0, ROT_Y | ROT_X); // Torso.
obj->SetupHitEffect(); obj->SetHitEffect();
} }
} }
@ -246,7 +246,7 @@ static void StartTrap(ObjectInfo* obj)
obj->control = ControlDamoclesSword; obj->control = ControlDamoclesSword;
obj->collision = CollideDamoclesSword; obj->collision = CollideDamoclesSword;
obj->shadowType = ShadowMode::All; obj->shadowType = ShadowMode::All;
obj->SetupHitEffect(true); obj->SetHitEffect(true);
} }
obj = &Objects[ID_SLAMMING_DOORS]; obj = &Objects[ID_SLAMMING_DOORS];
@ -256,7 +256,7 @@ static void StartTrap(ObjectInfo* obj)
obj->control = ControlSlammingDoors; obj->control = ControlSlammingDoors;
obj->collision = GenericSphereBoxCollision; obj->collision = GenericSphereBoxCollision;
obj->shadowType = ShadowMode::All; obj->shadowType = ShadowMode::All;
obj->SetupHitEffect(true); obj->SetHitEffect(true);
} }
obj = &Objects[ID_SWINGING_BLADE]; obj = &Objects[ID_SWINGING_BLADE];
@ -266,7 +266,7 @@ static void StartTrap(ObjectInfo* obj)
obj->control = ControlSwingingBlade; obj->control = ControlSwingingBlade;
obj->collision = GenericSphereBoxCollision; obj->collision = GenericSphereBoxCollision;
obj->shadowType = ShadowMode::All; obj->shadowType = ShadowMode::All;
obj->SetupHitEffect(true); obj->SetHitEffect(true);
} }
} }

View file

@ -57,7 +57,7 @@ static void StartEntity(ObjectInfo* obj)
obj->waterCreature = true; obj->waterCreature = true;
obj->LotType = LotType::Water; obj->LotType = LotType::Water;
obj->SetBoneRotationFlags(9, ROT_Y); obj->SetBoneRotationFlags(9, ROT_Y);
obj->SetupHitEffect(); obj->SetHitEffect();
} }
obj = &Objects[ID_BARRACUDA]; obj = &Objects[ID_BARRACUDA];
@ -74,7 +74,7 @@ static void StartEntity(ObjectInfo* obj)
obj->waterCreature = true; obj->waterCreature = true;
obj->LotType = LotType::Water; obj->LotType = LotType::Water;
obj->SetBoneRotationFlags(6, ROT_Y); obj->SetBoneRotationFlags(6, ROT_Y);
obj->SetupHitEffect(); obj->SetHitEffect();
} }
obj = &Objects[ID_EAGLE]; obj = &Objects[ID_EAGLE];
@ -89,7 +89,7 @@ static void StartEntity(ObjectInfo* obj)
obj->intelligent = true; obj->intelligent = true;
obj->pivotLength = 0; obj->pivotLength = 0;
obj->LotType = LotType::Flyer; obj->LotType = LotType::Flyer;
obj->SetupHitEffect(); obj->SetHitEffect();
} }
obj = &Objects[ID_CROW]; obj = &Objects[ID_CROW];
@ -104,7 +104,7 @@ static void StartEntity(ObjectInfo* obj)
obj->intelligent = true; obj->intelligent = true;
obj->pivotLength = 0; obj->pivotLength = 0;
obj->LotType = LotType::Flyer; obj->LotType = LotType::Flyer;
obj->SetupHitEffect(); obj->SetHitEffect();
} }
obj = &Objects[ID_RAT]; obj = &Objects[ID_RAT];
@ -118,7 +118,7 @@ static void StartEntity(ObjectInfo* obj)
obj->pivotLength = 50; obj->pivotLength = 50;
obj->radius = 204; obj->radius = 204;
obj->intelligent = true; obj->intelligent = true;
obj->SetupHitEffect(); obj->SetHitEffect();
} }
obj = &Objects[ID_YETI]; obj = &Objects[ID_YETI];
@ -135,7 +135,7 @@ static void StartEntity(ObjectInfo* obj)
obj->LotType = LotType::Human; obj->LotType = LotType::Human;
obj->SetBoneRotationFlags(6, ROT_Y); obj->SetBoneRotationFlags(6, ROT_Y);
obj->SetBoneRotationFlags(14, ROT_Y); obj->SetBoneRotationFlags(14, ROT_Y);
obj->SetupHitEffect(); obj->SetHitEffect();
} }
obj = &Objects[ID_GOON_SILENCER1]; obj = &Objects[ID_GOON_SILENCER1];
@ -151,7 +151,7 @@ static void StartEntity(ObjectInfo* obj)
obj->intelligent = true; obj->intelligent = true;
obj->SetBoneRotationFlags(0, ROT_X | ROT_Y); obj->SetBoneRotationFlags(0, ROT_X | ROT_Y);
obj->SetBoneRotationFlags(1, ROT_X | ROT_Y); obj->SetBoneRotationFlags(1, ROT_X | ROT_Y);
obj->SetupHitEffect(); obj->SetHitEffect();
} }
obj = &Objects[ID_GOON_SILENCER2]; obj = &Objects[ID_GOON_SILENCER2];
@ -168,7 +168,7 @@ static void StartEntity(ObjectInfo* obj)
obj->intelligent = true; obj->intelligent = true;
obj->SetBoneRotationFlags(0, ROT_X | ROT_Y); obj->SetBoneRotationFlags(0, ROT_X | ROT_Y);
obj->SetBoneRotationFlags(1, ROT_X | ROT_Y); obj->SetBoneRotationFlags(1, ROT_X | ROT_Y);
obj->SetupHitEffect(); obj->SetHitEffect();
} }
obj = &Objects[ID_GOON_SILENCER3]; obj = &Objects[ID_GOON_SILENCER3];
@ -185,7 +185,7 @@ static void StartEntity(ObjectInfo* obj)
obj->intelligent = true; obj->intelligent = true;
obj->SetBoneRotationFlags(0, ROT_X | ROT_Y); obj->SetBoneRotationFlags(0, ROT_X | ROT_Y);
obj->SetBoneRotationFlags(1, ROT_X | ROT_Y); obj->SetBoneRotationFlags(1, ROT_X | ROT_Y);
obj->SetupHitEffect(); obj->SetHitEffect();
} }
obj = &Objects[ID_WORKER_SHOTGUN]; obj = &Objects[ID_WORKER_SHOTGUN];
@ -201,7 +201,7 @@ static void StartEntity(ObjectInfo* obj)
obj->intelligent = true; obj->intelligent = true;
obj->SetBoneRotationFlags(4, ROT_X | ROT_Y); obj->SetBoneRotationFlags(4, ROT_X | ROT_Y);
obj->SetBoneRotationFlags(13, ROT_X | ROT_Y); obj->SetBoneRotationFlags(13, ROT_X | ROT_Y);
obj->SetupHitEffect(); obj->SetHitEffect();
} }
obj = &Objects[ID_WORKER_MACHINEGUN]; obj = &Objects[ID_WORKER_MACHINEGUN];
@ -217,7 +217,7 @@ static void StartEntity(ObjectInfo* obj)
obj->intelligent = true; obj->intelligent = true;
obj->SetBoneRotationFlags(4, ROT_X | ROT_Y); obj->SetBoneRotationFlags(4, ROT_X | ROT_Y);
obj->SetBoneRotationFlags(13, ROT_X | ROT_Y); obj->SetBoneRotationFlags(13, ROT_X | ROT_Y);
obj->SetupHitEffect(); obj->SetHitEffect();
} }
obj = &Objects[ID_SMALL_SPIDER]; obj = &Objects[ID_SMALL_SPIDER];
@ -232,7 +232,7 @@ static void StartEntity(ObjectInfo* obj)
obj->radius = 102; obj->radius = 102;
obj->intelligent = true; obj->intelligent = true;
obj->LotType = LotType::Spider; obj->LotType = LotType::Spider;
obj->SetupHitEffect(); obj->SetHitEffect();
} }
obj = &Objects[ID_BIG_SPIDER]; obj = &Objects[ID_BIG_SPIDER];
@ -246,7 +246,7 @@ static void StartEntity(ObjectInfo* obj)
obj->pivotLength = 0; obj->pivotLength = 0;
obj->radius = 102; obj->radius = 102;
obj->intelligent = true; obj->intelligent = true;
obj->SetupHitEffect(); obj->SetHitEffect();
} }
obj = &Objects[ID_WORKER_DUAL_REVOLVER]; obj = &Objects[ID_WORKER_DUAL_REVOLVER];
@ -262,7 +262,7 @@ static void StartEntity(ObjectInfo* obj)
obj->intelligent = true; obj->intelligent = true;
obj->SetBoneRotationFlags(0, ROT_X | ROT_Y); obj->SetBoneRotationFlags(0, ROT_X | ROT_Y);
obj->SetBoneRotationFlags(11, ROT_X | ROT_Y); obj->SetBoneRotationFlags(11, ROT_X | ROT_Y);
obj->SetupHitEffect(); obj->SetHitEffect();
} }
obj = &Objects[ID_BIRDMONSTER]; obj = &Objects[ID_BIRDMONSTER];
@ -277,7 +277,7 @@ static void StartEntity(ObjectInfo* obj)
obj->radius = 341; obj->radius = 341;
obj->intelligent = true; obj->intelligent = true;
obj->SetBoneRotationFlags(14, ROT_X | ROT_Y); obj->SetBoneRotationFlags(14, ROT_X | ROT_Y);
obj->SetupHitEffect(); obj->SetHitEffect();
} }
obj = &Objects[ID_WORKER_FLAMETHROWER]; obj = &Objects[ID_WORKER_FLAMETHROWER];
@ -293,7 +293,7 @@ static void StartEntity(ObjectInfo* obj)
obj->intelligent = true; obj->intelligent = true;
obj->SetBoneRotationFlags(4, ROT_X | ROT_Y); obj->SetBoneRotationFlags(4, ROT_X | ROT_Y);
obj->SetBoneRotationFlags(14, ROT_X | ROT_Y); obj->SetBoneRotationFlags(14, ROT_X | ROT_Y);
obj->SetupHitEffect(); obj->SetHitEffect();
} }
obj = &Objects[ID_KNIFETHROWER]; obj = &Objects[ID_KNIFETHROWER];
@ -309,7 +309,7 @@ static void StartEntity(ObjectInfo* obj)
obj->intelligent = true; obj->intelligent = true;
obj->SetBoneRotationFlags(6, ROT_X | ROT_Y); obj->SetBoneRotationFlags(6, ROT_X | ROT_Y);
obj->SetBoneRotationFlags(8, ROT_X | ROT_Y); obj->SetBoneRotationFlags(8, ROT_X | ROT_Y);
obj->SetupHitEffect(); obj->SetHitEffect();
} }
obj = &Objects[ID_MERCENARY_UZI]; obj = &Objects[ID_MERCENARY_UZI];
@ -325,7 +325,7 @@ static void StartEntity(ObjectInfo* obj)
obj->intelligent = true; obj->intelligent = true;
obj->SetBoneRotationFlags(6, ROT_X | ROT_Y); obj->SetBoneRotationFlags(6, ROT_X | ROT_Y);
obj->SetBoneRotationFlags(8, ROT_X | ROT_Y); obj->SetBoneRotationFlags(8, ROT_X | ROT_Y);
obj->SetupHitEffect(); obj->SetHitEffect();
} }
obj = &Objects[ID_MERCENARY_AUTOPISTOLS1]; obj = &Objects[ID_MERCENARY_AUTOPISTOLS1];
@ -341,7 +341,7 @@ static void StartEntity(ObjectInfo* obj)
obj->intelligent = true; obj->intelligent = true;
obj->SetBoneRotationFlags(6, ROT_X | ROT_Y); obj->SetBoneRotationFlags(6, ROT_X | ROT_Y);
obj->SetBoneRotationFlags(8, ROT_X | ROT_Y); obj->SetBoneRotationFlags(8, ROT_X | ROT_Y);
obj->SetupHitEffect(); obj->SetHitEffect();
} }
obj = &Objects[ID_MERCENARY_AUTOPISTOLS2]; obj = &Objects[ID_MERCENARY_AUTOPISTOLS2];
@ -358,7 +358,7 @@ static void StartEntity(ObjectInfo* obj)
obj->intelligent = true; obj->intelligent = true;
obj->SetBoneRotationFlags(6, ROT_X | ROT_Y); obj->SetBoneRotationFlags(6, ROT_X | ROT_Y);
obj->SetBoneRotationFlags(8, ROT_X | ROT_Y); obj->SetBoneRotationFlags(8, ROT_X | ROT_Y);
obj->SetupHitEffect(); obj->SetHitEffect();
} }
obj = &Objects[ID_MONK1]; obj = &Objects[ID_MONK1];
@ -373,7 +373,7 @@ static void StartEntity(ObjectInfo* obj)
obj->radius = 204; obj->radius = 204;
obj->intelligent = true; obj->intelligent = true;
obj->SetBoneRotationFlags(6, ROT_X | ROT_Y); obj->SetBoneRotationFlags(6, ROT_X | ROT_Y);
obj->SetupHitEffect(); obj->SetHitEffect();
} }
obj = &Objects[ID_MONK2]; obj = &Objects[ID_MONK2];
@ -388,7 +388,7 @@ static void StartEntity(ObjectInfo* obj)
obj->radius = 204; obj->radius = 204;
obj->intelligent = true; obj->intelligent = true;
obj->SetBoneRotationFlags(6, ROT_X | ROT_Y); obj->SetBoneRotationFlags(6, ROT_X | ROT_Y);
obj->SetupHitEffect(); obj->SetHitEffect();
} }
obj = &Objects[ID_SWORD_GUARDIAN]; obj = &Objects[ID_SWORD_GUARDIAN];
@ -406,7 +406,7 @@ static void StartEntity(ObjectInfo* obj)
obj->intelligent = true; obj->intelligent = true;
obj->SetBoneRotationFlags(6, ROT_X | ROT_Y); obj->SetBoneRotationFlags(6, ROT_X | ROT_Y);
obj->SetBoneRotationFlags(17, ROT_X | ROT_Y); obj->SetBoneRotationFlags(17, ROT_X | ROT_Y);
obj->SetupHitEffect(); obj->SetHitEffect();
} }
obj = &Objects[ID_SPEAR_GUARDIAN]; obj = &Objects[ID_SPEAR_GUARDIAN];
@ -424,7 +424,7 @@ static void StartEntity(ObjectInfo* obj)
obj->intelligent = true; obj->intelligent = true;
obj->SetBoneRotationFlags(6, ROT_X | ROT_Y); obj->SetBoneRotationFlags(6, ROT_X | ROT_Y);
obj->SetBoneRotationFlags(12, ROT_X | ROT_Y); obj->SetBoneRotationFlags(12, ROT_X | ROT_Y);
obj->SetupHitEffect(); obj->SetHitEffect();
} }
obj = &Objects[ID_DRAGON_FRONT]; obj = &Objects[ID_DRAGON_FRONT];
@ -439,7 +439,7 @@ static void StartEntity(ObjectInfo* obj)
obj->radius = 256; obj->radius = 256;
obj->intelligent = true; obj->intelligent = true;
obj->SetBoneRotationFlags(10, ROT_Z); obj->SetBoneRotationFlags(10, ROT_Z);
obj->SetupHitEffect(); obj->SetHitEffect();
} }
obj = &Objects[ID_DRAGON_BACK]; obj = &Objects[ID_DRAGON_BACK];
@ -450,7 +450,7 @@ static void StartEntity(ObjectInfo* obj)
obj->collision = DragonCollision; obj->collision = DragonCollision;
obj->control = DragonControl; obj->control = DragonControl;
obj->radius = 256; obj->radius = 256;
obj->SetupHitEffect(); obj->SetHitEffect();
} }
obj = &Objects[ID_MARCO_BARTOLI]; obj = &Objects[ID_MARCO_BARTOLI];
@ -472,7 +472,7 @@ static void StartEntity(ObjectInfo* obj)
obj->radius = 256; obj->radius = 256;
obj->intelligent = true; obj->intelligent = true;
obj->LotType = LotType::SnowmobileGun; obj->LotType = LotType::SnowmobileGun;
obj->SetupHitEffect(); obj->SetHitEffect();
} }
obj = &Objects[ID_SNOWMOBILE_DRIVER]; obj = &Objects[ID_SNOWMOBILE_DRIVER];
@ -481,7 +481,7 @@ static void StartEntity(ObjectInfo* obj)
CheckIfSlotExists(ID_SNOWMOBILE_GUN, GetObjectName(ID_SNOWMOBILE_DRIVER)); CheckIfSlotExists(ID_SNOWMOBILE_GUN, GetObjectName(ID_SNOWMOBILE_DRIVER));
obj->Initialize = InitializeSkidooMan; obj->Initialize = InitializeSkidooMan;
obj->control = SkidooManControl; obj->control = SkidooManControl;
obj->SetupHitEffect(true); obj->SetHitEffect(true);
} }
} }
@ -512,7 +512,7 @@ static void StartTrap(ObjectInfo* obj)
obj->Initialize = InitializeKillerStatue; obj->Initialize = InitializeKillerStatue;
obj->control = KillerStatueControl; obj->control = KillerStatueControl;
obj->collision = ObjectCollision; obj->collision = ObjectCollision;
obj->SetupHitEffect(true); obj->SetHitEffect(true);
} }
} }
@ -527,7 +527,7 @@ static void StartVehicles(ObjectInfo* obj)
obj->collision = SpeedboatPlayerCollision; obj->collision = SpeedboatPlayerCollision;
obj->control = SpeedboatControl; obj->control = SpeedboatControl;
obj->shadowType = ShadowMode::Lara; obj->shadowType = ShadowMode::Lara;
obj->SetupHitEffect(true); obj->SetHitEffect(true);
} }
// TODO: Create a new renderer for the skidoo with animated track. // TODO: Create a new renderer for the skidoo with animated track.
@ -537,7 +537,7 @@ static void StartVehicles(ObjectInfo* obj)
obj->Initialize = InitializeSkidoo; obj->Initialize = InitializeSkidoo;
obj->collision = SkidooPlayerCollision; obj->collision = SkidooPlayerCollision;
obj->shadowType = ShadowMode::Lara; obj->shadowType = ShadowMode::Lara;
obj->SetupHitEffect(true); obj->SetHitEffect(true);
} }
} }

View file

@ -1027,7 +1027,7 @@ namespace TEN::Entities::Vehicles
{ {
if (TestBoundsCollide(item, kayakItem, KAYAK_TO_ENTITY_RADIUS)) if (TestBoundsCollide(item, kayakItem, KAYAK_TO_ENTITY_RADIUS))
{ {
DoLotsOfBlood(laraItem->Pose.Position.x, laraItem->Pose.Position.y - STEP_SIZE, laraItem->Pose.Position.z, kayakItem->Animation.Velocity.z, kayakItem->Pose.Orientation.y, laraItem->RoomNumber, 3); DoLotsOfBlood(laraItem->Pose.Position.x, laraItem->Pose.Position.y - CLICK(1), laraItem->Pose.Position.z, kayakItem->Animation.Velocity.z, kayakItem->Pose.Orientation.y, laraItem->RoomNumber, 3);
DoDamage(laraItem, 5); DoDamage(laraItem, 5);
} }
} }

View file

@ -70,7 +70,7 @@ static void StartEntity(ObjectInfo* obj)
obj->nonLot = true; // NOTE: Doesn't move to reach the player, only throws projectiles. obj->nonLot = true; // NOTE: Doesn't move to reach the player, only throws projectiles.
obj->SetBoneRotationFlags(6, ROT_X | ROT_Y); obj->SetBoneRotationFlags(6, ROT_X | ROT_Y);
obj->SetBoneRotationFlags(13, ROT_Y); obj->SetBoneRotationFlags(13, ROT_Y);
obj->SetupHitEffect(); obj->SetHitEffect();
} }
obj = &Objects[ID_TIGER]; obj = &Objects[ID_TIGER];
@ -85,7 +85,7 @@ static void StartEntity(ObjectInfo* obj)
obj->radius = 340; obj->radius = 340;
obj->intelligent = true; obj->intelligent = true;
obj->SetBoneRotationFlags(21, ROT_Y); obj->SetBoneRotationFlags(21, ROT_Y);
obj->SetupHitEffect(); obj->SetHitEffect();
} }
obj = &Objects[ID_COBRA]; obj = &Objects[ID_COBRA];
@ -101,7 +101,7 @@ static void StartEntity(ObjectInfo* obj)
obj->nonLot = true; obj->nonLot = true;
obj->SetBoneRotationFlags(0, ROT_Y); obj->SetBoneRotationFlags(0, ROT_Y);
obj->SetBoneRotationFlags(6, ROT_Y); obj->SetBoneRotationFlags(6, ROT_Y);
obj->SetupHitEffect(); obj->SetHitEffect();
} }
obj = &Objects[ID_RAPTOR]; obj = &Objects[ID_RAPTOR];
@ -119,7 +119,7 @@ static void StartEntity(ObjectInfo* obj)
obj->SetBoneRotationFlags(21, ROT_Y); obj->SetBoneRotationFlags(21, ROT_Y);
obj->SetBoneRotationFlags(23, ROT_Y); obj->SetBoneRotationFlags(23, ROT_Y);
obj->SetBoneRotationFlags(25, ROT_Y); obj->SetBoneRotationFlags(25, ROT_Y);
obj->SetupHitEffect(); obj->SetHitEffect();
} }
obj = &Objects[ID_TRIBESMAN_WITH_AX]; obj = &Objects[ID_TRIBESMAN_WITH_AX];
@ -135,7 +135,7 @@ static void StartEntity(ObjectInfo* obj)
obj->pivotLength = 0; obj->pivotLength = 0;
obj->SetBoneRotationFlags(6, ROT_Y); obj->SetBoneRotationFlags(6, ROT_Y);
obj->SetBoneRotationFlags(13, ROT_Y); obj->SetBoneRotationFlags(13, ROT_Y);
obj->SetupHitEffect(); obj->SetHitEffect();
} }
obj = &Objects[ID_TRIBESMAN_WITH_DARTS]; obj = &Objects[ID_TRIBESMAN_WITH_DARTS];
@ -151,7 +151,7 @@ static void StartEntity(ObjectInfo* obj)
obj->pivotLength = 0; obj->pivotLength = 0;
obj->SetBoneRotationFlags(6, ROT_Y); obj->SetBoneRotationFlags(6, ROT_Y);
obj->SetBoneRotationFlags(13, ROT_Y); obj->SetBoneRotationFlags(13, ROT_Y);
obj->SetupHitEffect(); obj->SetHitEffect();
} }
obj = &Objects[ID_TYRANNOSAUR]; obj = &Objects[ID_TYRANNOSAUR];
@ -168,7 +168,7 @@ static void StartEntity(ObjectInfo* obj)
obj->LotType = LotType::Blockable; obj->LotType = LotType::Blockable;
obj->SetBoneRotationFlags(10, ROT_Y); obj->SetBoneRotationFlags(10, ROT_Y);
obj->SetBoneRotationFlags(11, ROT_Y); obj->SetBoneRotationFlags(11, ROT_Y);
obj->SetupHitEffect(); obj->SetHitEffect();
} }
obj = &Objects[ID_SCUBA_DIVER]; obj = &Objects[ID_SCUBA_DIVER];
@ -186,7 +186,7 @@ static void StartEntity(ObjectInfo* obj)
obj->LotType = LotType::Water; obj->LotType = LotType::Water;
obj->SetBoneRotationFlags(10, ROT_X | ROT_Y); obj->SetBoneRotationFlags(10, ROT_X | ROT_Y);
obj->SetBoneRotationFlags(14, ROT_Y); obj->SetBoneRotationFlags(14, ROT_Y);
obj->SetupHitEffect(); obj->SetHitEffect();
} }
obj = &Objects[ID_SCUBA_HARPOON]; obj = &Objects[ID_SCUBA_HARPOON];
@ -209,7 +209,7 @@ static void StartEntity(ObjectInfo* obj)
obj->pivotLength = 0; obj->pivotLength = 0;
obj->SetBoneRotationFlags(0, ROT_X | ROT_Y); obj->SetBoneRotationFlags(0, ROT_X | ROT_Y);
obj->SetBoneRotationFlags(7, ROT_Y); obj->SetBoneRotationFlags(7, ROT_Y);
obj->SetupHitEffect(); obj->SetHitEffect();
} }
obj = &Objects[ID_MONKEY]; obj = &Objects[ID_MONKEY];
@ -227,7 +227,7 @@ static void StartEntity(ObjectInfo* obj)
obj->pivotLength = 0; obj->pivotLength = 0;
obj->SetBoneRotationFlags(0, ROT_X | ROT_Y); obj->SetBoneRotationFlags(0, ROT_X | ROT_Y);
obj->SetBoneRotationFlags(7, ROT_Y); obj->SetBoneRotationFlags(7, ROT_Y);
obj->SetupHitEffect(); obj->SetHitEffect();
} }
obj = &Objects[ID_MP_WITH_GUN]; obj = &Objects[ID_MP_WITH_GUN];
@ -243,7 +243,7 @@ static void StartEntity(ObjectInfo* obj)
obj->pivotLength = 0; obj->pivotLength = 0;
obj->SetBoneRotationFlags(6, ROT_X | ROT_Y); obj->SetBoneRotationFlags(6, ROT_X | ROT_Y);
obj->SetBoneRotationFlags(13, ROT_Y); obj->SetBoneRotationFlags(13, ROT_Y);
obj->SetupHitEffect(); obj->SetHitEffect();
} }
obj = &Objects[ID_MP_WITH_STICK]; obj = &Objects[ID_MP_WITH_STICK];
@ -260,7 +260,7 @@ static void StartEntity(ObjectInfo* obj)
obj->LotType = LotType::Human; obj->LotType = LotType::Human;
obj->SetBoneRotationFlags(6, ROT_X | ROT_Y); obj->SetBoneRotationFlags(6, ROT_X | ROT_Y);
obj->SetBoneRotationFlags(13, ROT_Y); obj->SetBoneRotationFlags(13, ROT_Y);
obj->SetupHitEffect(); obj->SetHitEffect();
} }
obj = &Objects[ID_SHIVA]; obj = &Objects[ID_SHIVA];
@ -279,7 +279,7 @@ static void StartEntity(ObjectInfo* obj)
obj->LotType = LotType::Blockable; obj->LotType = LotType::Blockable;
obj->SetBoneRotationFlags(6, ROT_X | ROT_Y); obj->SetBoneRotationFlags(6, ROT_X | ROT_Y);
obj->SetBoneRotationFlags(25, ROT_X | ROT_Y); obj->SetBoneRotationFlags(25, ROT_X | ROT_Y);
obj->SetupHitEffect(); obj->SetHitEffect();
} }
obj = &Objects[ID_SOPHIA_LEIGH_BOSS]; obj = &Objects[ID_SOPHIA_LEIGH_BOSS];
@ -296,7 +296,7 @@ static void StartEntity(ObjectInfo* obj)
obj->LotType = LotType::Human; obj->LotType = LotType::Human;
obj->SetBoneRotationFlags(6, ROT_X | ROT_Y); obj->SetBoneRotationFlags(6, ROT_X | ROT_Y);
obj->SetBoneRotationFlags(13, ROT_Y); obj->SetBoneRotationFlags(13, ROT_Y);
obj->SetupHitEffect(); obj->SetHitEffect();
} }
obj = &Objects[ID_CIVVY]; obj = &Objects[ID_CIVVY];
@ -313,7 +313,7 @@ static void StartEntity(ObjectInfo* obj)
obj->LotType = LotType::Human; obj->LotType = LotType::Human;
obj->SetBoneRotationFlags(6, ROT_X | ROT_Y); obj->SetBoneRotationFlags(6, ROT_X | ROT_Y);
obj->SetBoneRotationFlags(13, ROT_Y); obj->SetBoneRotationFlags(13, ROT_Y);
obj->SetupHitEffect(); obj->SetHitEffect();
} }
obj = &Objects[ID_LIZARD]; obj = &Objects[ID_LIZARD];
@ -329,7 +329,7 @@ static void StartEntity(ObjectInfo* obj)
obj->radius = 204; obj->radius = 204;
obj->LotType = LotType::Human; obj->LotType = LotType::Human;
obj->SetBoneRotationFlags(9, ROT_X | ROT_Z); obj->SetBoneRotationFlags(9, ROT_X | ROT_Z);
obj->SetupHitEffect(); obj->SetHitEffect();
} }
obj = &Objects[ID_PUNA_BOSS]; obj = &Objects[ID_PUNA_BOSS];
@ -347,7 +347,7 @@ static void StartEntity(ObjectInfo* obj)
obj->pivotLength = 50; obj->pivotLength = 50;
obj->SetBoneRotationFlags(4, ROT_Y); // Puna quest object. obj->SetBoneRotationFlags(4, ROT_Y); // Puna quest object.
obj->SetBoneRotationFlags(7, ROT_X | ROT_Y); // Head. obj->SetBoneRotationFlags(7, ROT_X | ROT_Y); // Head.
obj->SetupHitEffect(); obj->SetHitEffect();
} }
obj = &Objects[ID_WASP_MUTANT]; obj = &Objects[ID_WASP_MUTANT];
@ -362,7 +362,7 @@ static void StartEntity(ObjectInfo* obj)
obj->radius = 102; obj->radius = 102;
obj->pivotLength = 0; obj->pivotLength = 0;
obj->LotType = LotType::Flyer; obj->LotType = LotType::Flyer;
obj->SetupHitEffect(); obj->SetHitEffect();
} }
obj = &Objects[ID_COMPSOGNATHUS]; obj = &Objects[ID_COMPSOGNATHUS];
@ -377,7 +377,7 @@ static void StartEntity(ObjectInfo* obj)
obj->radius = 204; obj->radius = 204;
obj->pivotLength = 0; obj->pivotLength = 0;
obj->SetBoneRotationFlags(1, ROT_X | ROT_Z); obj->SetBoneRotationFlags(1, ROT_X | ROT_Z);
obj->SetupHitEffect(); obj->SetHitEffect();
} }
obj = &Objects[ID_CLAW_MUTANT]; obj = &Objects[ID_CLAW_MUTANT];
@ -393,7 +393,7 @@ static void StartEntity(ObjectInfo* obj)
obj->pivotLength = 0; obj->pivotLength = 0;
obj->SetBoneRotationFlags(0, ROT_X | ROT_Z); obj->SetBoneRotationFlags(0, ROT_X | ROT_Z);
obj->SetBoneRotationFlags(7, ROT_Y); obj->SetBoneRotationFlags(7, ROT_Y);
obj->SetupHitEffect(); obj->SetHitEffect();
} }
} }
@ -436,7 +436,7 @@ static void StartObject(ObjectInfo* obj)
obj->HitRoutine = CorpseHit; obj->HitRoutine = CorpseHit;
obj->HitPoints = NOT_TARGETABLE; obj->HitPoints = NOT_TARGETABLE;
obj->shadowType = ShadowMode::None; obj->shadowType = ShadowMode::None;
obj->SetupHitEffect(); obj->SetHitEffect();
} }
} }
@ -447,7 +447,7 @@ static void StartTrap(ObjectInfo* obj)
{ {
obj->control = TrainControl; obj->control = TrainControl;
obj->collision = TrainCollision; obj->collision = TrainCollision;
obj->SetupHitEffect(true); obj->SetHitEffect(true);
} }
obj = &Objects[ID_ELECTRIC_CLEANER]; obj = &Objects[ID_ELECTRIC_CLEANER];
@ -479,7 +479,7 @@ static void StartVehicles(ObjectInfo* obj)
obj->Initialize = InitializeQuadBike; obj->Initialize = InitializeQuadBike;
obj->collision = QuadBikePlayerCollision; obj->collision = QuadBikePlayerCollision;
obj->shadowType = ShadowMode::Lara; obj->shadowType = ShadowMode::Lara;
obj->SetupHitEffect(true); obj->SetHitEffect(true);
} }
obj = &Objects[ID_RUBBER_BOAT]; obj = &Objects[ID_RUBBER_BOAT];
@ -490,7 +490,7 @@ static void StartVehicles(ObjectInfo* obj)
obj->collision = RubberBoatPlayerCollision; obj->collision = RubberBoatPlayerCollision;
obj->drawRoutine = DrawRubberBoat; obj->drawRoutine = DrawRubberBoat;
obj->shadowType = ShadowMode::Lara; obj->shadowType = ShadowMode::Lara;
obj->SetupHitEffect(true); obj->SetHitEffect(true);
} }
@ -500,7 +500,7 @@ static void StartVehicles(ObjectInfo* obj)
obj->Initialize = InitializeKayak; obj->Initialize = InitializeKayak;
obj->collision = KayakPlayerCollision; obj->collision = KayakPlayerCollision;
obj->shadowType = ShadowMode::Lara; obj->shadowType = ShadowMode::Lara;
obj->SetupHitEffect(true); obj->SetHitEffect(true);
} }
@ -510,7 +510,7 @@ static void StartVehicles(ObjectInfo* obj)
obj->Initialize = InitializeMinecart; obj->Initialize = InitializeMinecart;
obj->collision = MinecartPlayerCollision; obj->collision = MinecartPlayerCollision;
obj->shadowType = ShadowMode::Lara; obj->shadowType = ShadowMode::Lara;
obj->SetupHitEffect(true); obj->SetHitEffect(true);
} }
@ -520,7 +520,7 @@ static void StartVehicles(ObjectInfo* obj)
obj->Initialize = BigGunInitialize; obj->Initialize = BigGunInitialize;
obj->collision = BigGunCollision; obj->collision = BigGunCollision;
obj->shadowType = ShadowMode::Lara; obj->shadowType = ShadowMode::Lara;
obj->SetupHitEffect(true); obj->SetHitEffect(true);
} }
obj = &Objects[ID_UPV]; obj = &Objects[ID_UPV];
@ -530,7 +530,7 @@ static void StartVehicles(ObjectInfo* obj)
obj->control = UPVEffects; obj->control = UPVEffects;
obj->collision = UPVPlayerCollision; obj->collision = UPVPlayerCollision;
obj->shadowType = ShadowMode::Lara; obj->shadowType = ShadowMode::Lara;
obj->SetupHitEffect(true); obj->SetHitEffect(true);
} }
} }

View file

@ -194,7 +194,7 @@ namespace TEN::Entities::TR4
{ {
if (abs(pos.x - mesh.pos.Position.x) < BLOCK(1) && if (abs(pos.x - mesh.pos.Position.x) < BLOCK(1) &&
abs(pos.z - mesh.pos.Position.z) < BLOCK(1) && abs(pos.z - mesh.pos.Position.z) < BLOCK(1) &&
StaticObjects[mesh.staticNumber].shatterType == SHT_NONE) StaticObjects[mesh.staticNumber].shatterType == ShatterType::None)
{ {
ShatterObject(nullptr, &mesh, -64, LaraItem->RoomNumber, 0); ShatterObject(nullptr, &mesh, -64, LaraItem->RoomNumber, 0);
SoundEffect(SFX_TR4_SMASH_ROCK, &item->Pose); SoundEffect(SFX_TR4_SMASH_ROCK, &item->Pose);

View file

@ -650,7 +650,7 @@ namespace TEN::Entities::TR4
if (abs(pos.x - staticMesh->pos.Position.x) < BLOCK(1) && if (abs(pos.x - staticMesh->pos.Position.x) < BLOCK(1) &&
abs(pos.z - staticMesh->pos.Position.z) < BLOCK(1) && abs(pos.z - staticMesh->pos.Position.z) < BLOCK(1) &&
StaticObjects[staticMesh->staticNumber].shatterType != SHT_NONE) StaticObjects[staticMesh->staticNumber].shatterType != ShatterType::None)
{ {
ShatterObject(0, staticMesh, -128, LaraItem->RoomNumber, 0); ShatterObject(0, staticMesh, -128, LaraItem->RoomNumber, 0);
SoundEffect(SFX_TR4_SMASH_ROCK, &item->Pose); SoundEffect(SFX_TR4_SMASH_ROCK, &item->Pose);

View file

@ -92,7 +92,7 @@ namespace TEN::Entities::TR4
if (((mesh->pos.Position.z / BLOCK(1)) == (z / BLOCK(1))) && if (((mesh->pos.Position.z / BLOCK(1)) == (z / BLOCK(1))) &&
((mesh->pos.Position.x / BLOCK(1)) == (x / BLOCK(1))) && ((mesh->pos.Position.x / BLOCK(1)) == (x / BLOCK(1))) &&
StaticObjects[mesh->staticNumber].shatterType != SHT_NONE) StaticObjects[mesh->staticNumber].shatterType != ShatterType::None)
{ {
ShatterObject(nullptr, mesh, -64, item->RoomNumber, 0); ShatterObject(nullptr, mesh, -64, item->RoomNumber, 0);
SoundEffect(SFX_TR4_SMASH_ROCK, &item->Pose); SoundEffect(SFX_TR4_SMASH_ROCK, &item->Pose);

View file

@ -133,7 +133,8 @@ namespace TEN::Entities::TR4
{ {
target = &g_Level.Items[targetItem]; target = &g_Level.Items[targetItem];
if (target->ObjectNumber >= ID_PUSHABLE_OBJECT1 && target->ObjectNumber <= ID_PUSHABLE_OBJECT10) if ( (target->ObjectNumber >= ID_PUSHABLE_OBJECT1 && target->ObjectNumber <= ID_PUSHABLE_OBJECT10) ||
(target->ObjectNumber >= ID_PUSHABLE_OBJECT_CLIMBABLE1 && target->ObjectNumber <= ID_PUSHABLE_OBJECT_CLIMBABLE10))
{ {
if (item->Pose.Position.x == target->Pose.Position.x && if (item->Pose.Position.x == target->Pose.Position.x &&
item->Pose.Position.z == target->Pose.Position.z) item->Pose.Position.z == target->Pose.Position.z)

View file

@ -266,7 +266,7 @@ namespace TEN::Entities::Vehicles
FloorInfo* floor = GetFloor(old->x, pos->y, pos->z, &roomNumber); FloorInfo* floor = GetFloor(old->x, pos->y, pos->z, &roomNumber);
int height = GetFloorHeight(floor, old->x, pos->y, pos->z); int height = GetFloorHeight(floor, old->x, pos->y, pos->z);
if (height < old->y - STEP_SIZE) if (height < old->y - CLICK(1))
{ {
if (pos->z > old->z) if (pos->z > old->z)
z = -1 - shiftZ; z = -1 - shiftZ;
@ -278,7 +278,7 @@ namespace TEN::Entities::Vehicles
floor = GetFloor(pos->x, pos->y, old->z, &roomNumber); floor = GetFloor(pos->x, pos->y, old->z, &roomNumber);
height = GetFloorHeight(floor, pos->x, pos->y, old->z); height = GetFloorHeight(floor, pos->x, pos->y, old->z);
if (height < old->y - STEP_SIZE) if (height < old->y - CLICK(1))
{ {
if (pos->x > old->x) if (pos->x > old->x)
x = -1 - shiftX; x = -1 - shiftX;
@ -642,11 +642,11 @@ namespace TEN::Entities::Vehicles
int rot2 = 0; int rot2 = 0;
int hf = GetVehicleHeight(jeepItem, JEEP_FRONT, -JEEP_SIDE, false, &f); int hf = GetVehicleHeight(jeepItem, JEEP_FRONT, -JEEP_SIDE, false, &f);
if (hf < f_old.y - STEP_SIZE) if (hf < f_old.y - CLICK(1))
rot1 = abs(4 * DoJeepShift(jeepItem, &f, &f_old)); rot1 = abs(4 * DoJeepShift(jeepItem, &f, &f_old));
int hmm = GetVehicleHeight(jeepItem, -(JEEP_FRONT + 50), -JEEP_SIDE, false, &mm); int hmm = GetVehicleHeight(jeepItem, -(JEEP_FRONT + 50), -JEEP_SIDE, false, &mm);
if (hmm < mm_old.y - STEP_SIZE) if (hmm < mm_old.y - CLICK(1))
{ {
if (rot) if (rot)
rot1 += abs(4 * DoJeepShift(jeepItem, &mm, &mm_old)); rot1 += abs(4 * DoJeepShift(jeepItem, &mm, &mm_old));
@ -655,15 +655,15 @@ namespace TEN::Entities::Vehicles
} }
int hb = GetVehicleHeight(jeepItem, JEEP_FRONT, JEEP_SIDE, false, &b); int hb = GetVehicleHeight(jeepItem, JEEP_FRONT, JEEP_SIDE, false, &b);
if (hb < b_old.y - STEP_SIZE) if (hb < b_old.y - CLICK(1))
rot2 = -abs(4 * DoJeepShift(jeepItem, &b, &b_old)); rot2 = -abs(4 * DoJeepShift(jeepItem, &b, &b_old));
int hmb = GetVehicleHeight(jeepItem, -(JEEP_FRONT + 50), 0, false, &mb); int hmb = GetVehicleHeight(jeepItem, -(JEEP_FRONT + 50), 0, false, &mb);
if (hmb < mb_old.y - STEP_SIZE) if (hmb < mb_old.y - CLICK(1))
DoJeepShift(jeepItem, &mb, &mb_old); DoJeepShift(jeepItem, &mb, &mb_old);
int hmt = GetVehicleHeight(jeepItem, -(JEEP_FRONT + 50), JEEP_SIDE, false, &mt); int hmt = GetVehicleHeight(jeepItem, -(JEEP_FRONT + 50), JEEP_SIDE, false, &mt);
if (hmt < mt_old.y - STEP_SIZE) if (hmt < mt_old.y - CLICK(1))
{ {
if (rot2) if (rot2)
rot2 -= abs(4 * DoJeepShift(jeepItem, &mt, &mt_old)); rot2 -= abs(4 * DoJeepShift(jeepItem, &mt, &mt_old));
@ -676,7 +676,7 @@ namespace TEN::Entities::Vehicles
roomNumber = jeepItem->RoomNumber; roomNumber = jeepItem->RoomNumber;
floor = GetFloor(jeepItem->Pose.Position.x, jeepItem->Pose.Position.y, jeepItem->Pose.Position.z, &roomNumber); floor = GetFloor(jeepItem->Pose.Position.x, jeepItem->Pose.Position.y, jeepItem->Pose.Position.z, &roomNumber);
if (GetFloorHeight(floor, jeepItem->Pose.Position.x, jeepItem->Pose.Position.y, jeepItem->Pose.Position.z) < jeepItem->Pose.Position.y - STEP_SIZE) if (GetFloorHeight(floor, jeepItem->Pose.Position.x, jeepItem->Pose.Position.y, jeepItem->Pose.Position.z) < jeepItem->Pose.Position.y - CLICK(1))
DoJeepShift(jeepItem, (Vector3i*)&jeepItem->Pose, &oldPos); DoJeepShift(jeepItem, (Vector3i*)&jeepItem->Pose, &oldPos);
if (!jeep->Velocity) if (!jeep->Velocity)
@ -735,7 +735,7 @@ namespace TEN::Entities::Vehicles
int rot1 = 0; int rot1 = 0;
int rot2 = 0; int rot2 = 0;
if (jeepItem->Pose.Position.y >= height - STEP_SIZE) if (jeepItem->Pose.Position.y >= height - CLICK(1))
{ {
lara->Control.Look.Mode = (jeepItem->Animation.Velocity.z == 0.0f) ? LookMode::Horizontal : LookMode::Free; lara->Control.Look.Mode = (jeepItem->Animation.Velocity.z == 0.0f) ? LookMode::Horizontal : LookMode::Free;

View file

@ -100,7 +100,7 @@ namespace TEN::Entities
obj->pivotLength = 20; obj->pivotLength = 20;
obj->radius = 128; obj->radius = 128;
obj->intelligent = true; obj->intelligent = true;
obj->SetupHitEffect(); obj->SetHitEffect();
} }
obj = &Objects[ID_BIG_SCORPION]; obj = &Objects[ID_BIG_SCORPION];
@ -114,7 +114,7 @@ namespace TEN::Entities
obj->pivotLength = 50; obj->pivotLength = 50;
obj->radius = 512; obj->radius = 512;
obj->intelligent = true; obj->intelligent = true;
obj->SetupHitEffect(); obj->SetHitEffect();
} }
obj = &Objects[ID_HAMMERHEAD]; obj = &Objects[ID_HAMMERHEAD];
@ -130,7 +130,7 @@ namespace TEN::Entities
obj->intelligent = true; obj->intelligent = true;
obj->waterCreature = true; obj->waterCreature = true;
obj->LotType = LotType::Water; obj->LotType = LotType::Water;
obj->SetupHitEffect(); obj->SetHitEffect();
} }
obj = &Objects[ID_WILD_BOAR]; obj = &Objects[ID_WILD_BOAR];
@ -146,7 +146,7 @@ namespace TEN::Entities
obj->intelligent = true; obj->intelligent = true;
obj->SetBoneRotationFlags(12, ROT_Y | ROT_Z); obj->SetBoneRotationFlags(12, ROT_Y | ROT_Z);
obj->SetBoneRotationFlags(13, ROT_Y | ROT_Z); obj->SetBoneRotationFlags(13, ROT_Y | ROT_Z);
obj->SetupHitEffect(); obj->SetHitEffect();
} }
obj = &Objects[ID_DOG]; obj = &Objects[ID_DOG];
@ -162,7 +162,7 @@ namespace TEN::Entities
obj->intelligent = true; obj->intelligent = true;
obj->SetBoneRotationFlags(0, ROT_Y); obj->SetBoneRotationFlags(0, ROT_Y);
obj->SetBoneRotationFlags(2, ROT_X | ROT_Y); obj->SetBoneRotationFlags(2, ROT_X | ROT_Y);
obj->SetupHitEffect(); obj->SetHitEffect();
} }
obj = &Objects[ID_BAT]; obj = &Objects[ID_BAT];
@ -177,7 +177,7 @@ namespace TEN::Entities
obj->radius = 102; obj->radius = 102;
obj->intelligent = true; obj->intelligent = true;
obj->LotType = LotType::Flyer; obj->LotType = LotType::Flyer;
obj->SetupHitEffect(); obj->SetHitEffect();
} }
obj = &Objects[ID_AHMET]; obj = &Objects[ID_AHMET];
@ -192,7 +192,7 @@ namespace TEN::Entities
obj->radius = 341; obj->radius = 341;
obj->intelligent = true; obj->intelligent = true;
obj->SetBoneRotationFlags(9, ROT_Y); obj->SetBoneRotationFlags(9, ROT_Y);
obj->SetupHitEffect(); obj->SetHitEffect();
} }
obj = &Objects[ID_BADDY1]; obj = &Objects[ID_BADDY1];
@ -211,7 +211,7 @@ namespace TEN::Entities
obj->LotType = LotType::HumanPlusJumpAndMonkey; obj->LotType = LotType::HumanPlusJumpAndMonkey;
obj->SetBoneRotationFlags(7, ROT_X | ROT_Y); obj->SetBoneRotationFlags(7, ROT_X | ROT_Y);
obj->SetBoneRotationFlags(22, ROT_X | ROT_Y); obj->SetBoneRotationFlags(22, ROT_X | ROT_Y);
obj->SetupHitEffect(); obj->SetHitEffect();
} }
obj = &Objects[ID_BADDY2]; obj = &Objects[ID_BADDY2];
@ -231,7 +231,7 @@ namespace TEN::Entities
obj->LotType = LotType::HumanPlusJumpAndMonkey; obj->LotType = LotType::HumanPlusJumpAndMonkey;
obj->SetBoneRotationFlags(7, ROT_X | ROT_Y); obj->SetBoneRotationFlags(7, ROT_X | ROT_Y);
obj->SetBoneRotationFlags(22, ROT_X | ROT_Y); obj->SetBoneRotationFlags(22, ROT_X | ROT_Y);
obj->SetupHitEffect(); obj->SetHitEffect();
} }
obj = &Objects[ID_SAS_CAIRO]; obj = &Objects[ID_SAS_CAIRO];
@ -247,7 +247,7 @@ namespace TEN::Entities
obj->intelligent = true; obj->intelligent = true;
obj->SetBoneRotationFlags(0, ROT_X | ROT_Y); obj->SetBoneRotationFlags(0, ROT_X | ROT_Y);
obj->SetBoneRotationFlags(7, ROT_X | ROT_Y); obj->SetBoneRotationFlags(7, ROT_X | ROT_Y);
obj->SetupHitEffect(); obj->SetHitEffect();
} }
obj = &Objects[ID_MUMMY]; obj = &Objects[ID_MUMMY];
@ -260,9 +260,9 @@ namespace TEN::Entities
obj->HitPoints = 15; obj->HitPoints = 15;
obj->radius = 170; obj->radius = 170;
obj->intelligent = true; obj->intelligent = true;
obj->damageType = DamageMode::ExplosivesOnly; obj->damageType = DamageMode::Explosion;
obj->SetBoneRotationFlags(7, ROT_X | ROT_Y); obj->SetBoneRotationFlags(7, ROT_X | ROT_Y);
obj->SetupHitEffect(); obj->SetHitEffect();
} }
obj = &Objects[ID_SKELETON]; obj = &Objects[ID_SKELETON];
@ -277,9 +277,9 @@ namespace TEN::Entities
obj->radius = 128; obj->radius = 128;
obj->explodableMeshbits = 0xA00; obj->explodableMeshbits = 0xA00;
obj->intelligent = true; obj->intelligent = true;
obj->damageType = DamageMode::ExplosivesOnly; obj->damageType = DamageMode::Explosion;
obj->LotType = LotType::Skeleton; obj->LotType = LotType::Skeleton;
obj->SetupHitEffect(); obj->SetHitEffect();
} }
obj = &Objects[ID_KNIGHT_TEMPLAR]; obj = &Objects[ID_KNIGHT_TEMPLAR];
@ -296,7 +296,7 @@ namespace TEN::Entities
obj->damageType = DamageMode::None; obj->damageType = DamageMode::None;
obj->SetBoneRotationFlags(6, ROT_X | ROT_Y); obj->SetBoneRotationFlags(6, ROT_X | ROT_Y);
obj->SetBoneRotationFlags(7, ROT_Y); obj->SetBoneRotationFlags(7, ROT_Y);
obj->SetupHitEffect(); obj->SetHitEffect();
} }
obj = &Objects[ID_BIG_BEETLE]; obj = &Objects[ID_BIG_BEETLE];
@ -311,7 +311,7 @@ namespace TEN::Entities
obj->radius = 204; obj->radius = 204;
obj->intelligent = true; obj->intelligent = true;
obj->LotType = LotType::Flyer; obj->LotType = LotType::Flyer;
obj->SetupHitEffect(); obj->SetHitEffect();
} }
obj = &Objects[ID_SETHA]; obj = &Objects[ID_SETHA];
@ -327,7 +327,7 @@ namespace TEN::Entities
obj->intelligent = true; obj->intelligent = true;
obj->damageType = DamageMode::None; obj->damageType = DamageMode::None;
obj->LotType = LotType::Skeleton; obj->LotType = LotType::Skeleton;
obj->SetupHitEffect(true); obj->SetHitEffect(true);
} }
obj = &Objects[ID_DEMIGOD1]; obj = &Objects[ID_DEMIGOD1];
@ -344,7 +344,7 @@ namespace TEN::Entities
obj->damageType = DamageMode::None; obj->damageType = DamageMode::None;
obj->SetBoneRotationFlags(8, ROT_X | ROT_Y | ROT_Z); obj->SetBoneRotationFlags(8, ROT_X | ROT_Y | ROT_Z);
obj->SetBoneRotationFlags(20, ROT_Y); obj->SetBoneRotationFlags(20, ROT_Y);
obj->SetupHitEffect(true); obj->SetHitEffect(true);
} }
obj = &Objects[ID_DEMIGOD2]; obj = &Objects[ID_DEMIGOD2];
@ -360,7 +360,7 @@ namespace TEN::Entities
obj->intelligent = true; obj->intelligent = true;
obj->SetBoneRotationFlags(8, ROT_X | ROT_Y | ROT_Z); obj->SetBoneRotationFlags(8, ROT_X | ROT_Y | ROT_Z);
obj->SetBoneRotationFlags(20, ROT_Y); obj->SetBoneRotationFlags(20, ROT_Y);
obj->SetupHitEffect(true); obj->SetHitEffect(true);
} }
obj = &Objects[ID_DEMIGOD3]; obj = &Objects[ID_DEMIGOD3];
@ -376,7 +376,7 @@ namespace TEN::Entities
obj->intelligent = true; obj->intelligent = true;
obj->SetBoneRotationFlags(8, ROT_X | ROT_Y | ROT_Z); obj->SetBoneRotationFlags(8, ROT_X | ROT_Y | ROT_Z);
obj->SetBoneRotationFlags(20, ROT_Y); obj->SetBoneRotationFlags(20, ROT_Y);
obj->SetupHitEffect(true); obj->SetHitEffect(true);
} }
obj = &Objects[ID_JEAN_YVES]; obj = &Objects[ID_JEAN_YVES];
@ -386,7 +386,7 @@ namespace TEN::Entities
obj->control = JeanYvesControl; obj->control = JeanYvesControl;
obj->collision = ObjectCollision; obj->collision = ObjectCollision;
obj->nonLot = true; obj->nonLot = true;
obj->SetupHitEffect(true); obj->SetHitEffect(true);
} }
obj = &Objects[ID_TROOPS]; obj = &Objects[ID_TROOPS];
@ -402,7 +402,7 @@ namespace TEN::Entities
obj->intelligent = true; obj->intelligent = true;
obj->SetBoneRotationFlags(0, ROT_X | ROT_Y); obj->SetBoneRotationFlags(0, ROT_X | ROT_Y);
obj->SetBoneRotationFlags(7, ROT_X | ROT_Y); obj->SetBoneRotationFlags(7, ROT_X | ROT_Y);
obj->SetupHitEffect(); obj->SetHitEffect();
} }
obj = &Objects[ID_SENTRY_GUN]; obj = &Objects[ID_SENTRY_GUN];
@ -422,7 +422,7 @@ namespace TEN::Entities
obj->SetBoneRotationFlags(1, ROT_X | ROT_X); obj->SetBoneRotationFlags(1, ROT_X | ROT_X);
obj->SetBoneRotationFlags(2, ROT_X | ROT_Z); obj->SetBoneRotationFlags(2, ROT_X | ROT_Z);
obj->SetBoneRotationFlags(3, ROT_X | ROT_Z); obj->SetBoneRotationFlags(3, ROT_X | ROT_Z);
obj->SetupHitEffect(true); obj->SetHitEffect(true);
} }
obj = &Objects[ID_HARPY]; obj = &Objects[ID_HARPY];
@ -437,7 +437,7 @@ namespace TEN::Entities
obj->radius = 409; obj->radius = 409;
obj->intelligent = true; obj->intelligent = true;
obj->LotType = LotType::Flyer; obj->LotType = LotType::Flyer;
obj->SetupHitEffect(); obj->SetHitEffect();
} }
obj = &Objects[ID_GUIDE]; obj = &Objects[ID_GUIDE];
@ -454,7 +454,7 @@ namespace TEN::Entities
obj->meshSwapSlot = ID_MESHSWAP2; obj->meshSwapSlot = ID_MESHSWAP2;
obj->SetBoneRotationFlags(6, ROT_X | ROT_Y); obj->SetBoneRotationFlags(6, ROT_X | ROT_Y);
obj->SetBoneRotationFlags(20, ROT_X | ROT_Y); obj->SetBoneRotationFlags(20, ROT_X | ROT_Y);
obj->SetupHitEffect(); obj->SetHitEffect();
} }
obj = &Objects[ID_CROCODILE]; obj = &Objects[ID_CROCODILE];
@ -474,7 +474,7 @@ namespace TEN::Entities
obj->SetBoneRotationFlags(7, ROT_Y); obj->SetBoneRotationFlags(7, ROT_Y);
obj->SetBoneRotationFlags(9, ROT_Y); obj->SetBoneRotationFlags(9, ROT_Y);
obj->SetBoneRotationFlags(10, ROT_Y); obj->SetBoneRotationFlags(10, ROT_Y);
obj->SetupHitEffect(); obj->SetHitEffect();
} }
obj = &Objects[ID_SPHINX]; obj = &Objects[ID_SPHINX];
@ -489,7 +489,7 @@ namespace TEN::Entities
obj->radius = 512; obj->radius = 512;
obj->intelligent = true; obj->intelligent = true;
obj->damageType = DamageMode::None; obj->damageType = DamageMode::None;
obj->SetupHitEffect(); obj->SetHitEffect();
} }
obj = &Objects[ID_HORSE]; obj = &Objects[ID_HORSE];
@ -498,7 +498,7 @@ namespace TEN::Entities
obj->Initialize = InitializeHorse; obj->Initialize = InitializeHorse;
obj->collision = ObjectCollision; obj->collision = ObjectCollision;
obj->control = nullptr; obj->control = nullptr;
obj->SetupHitEffect(true); obj->SetHitEffect(true);
} }
obj = &Objects[ID_HORSEMAN]; obj = &Objects[ID_HORSEMAN];
@ -512,7 +512,7 @@ namespace TEN::Entities
obj->pivotLength = 500; obj->pivotLength = 500;
obj->radius = 409; obj->radius = 409;
obj->intelligent = true; obj->intelligent = true;
obj->SetupHitEffect(true); obj->SetHitEffect(true);
} }
obj = &Objects[ID_BABOON_NORMAL]; obj = &Objects[ID_BABOON_NORMAL];
@ -526,7 +526,7 @@ namespace TEN::Entities
obj->pivotLength = 200; obj->pivotLength = 200;
obj->radius = 256; obj->radius = 256;
obj->intelligent = true; obj->intelligent = true;
obj->SetupHitEffect(); obj->SetHitEffect();
} }
obj = &Objects[ID_BABOON_INV]; obj = &Objects[ID_BABOON_INV];
@ -541,7 +541,7 @@ namespace TEN::Entities
obj->pivotLength = 200; obj->pivotLength = 200;
obj->radius = 256; obj->radius = 256;
obj->intelligent = true; obj->intelligent = true;
obj->SetupHitEffect(); obj->SetHitEffect();
} }
obj = &Objects[ID_BABOON_SILENT]; obj = &Objects[ID_BABOON_SILENT];
@ -556,7 +556,7 @@ namespace TEN::Entities
obj->pivotLength = 200; obj->pivotLength = 200;
obj->radius = 256; obj->radius = 256;
obj->intelligent = true; obj->intelligent = true;
obj->SetupHitEffect(); obj->SetHitEffect();
} }
obj = &Objects[ID_CROCODILE_GOD]; obj = &Objects[ID_CROCODILE_GOD];
@ -573,7 +573,7 @@ namespace TEN::Entities
obj->damageType = DamageMode::None; obj->damageType = DamageMode::None;
obj->SetBoneRotationFlags(6, ROT_X | ROT_Y); obj->SetBoneRotationFlags(6, ROT_X | ROT_Y);
obj->SetBoneRotationFlags(7, ROT_X | ROT_Y); obj->SetBoneRotationFlags(7, ROT_X | ROT_Y);
obj->SetupHitEffect(); obj->SetHitEffect();
} }
obj = &Objects[ID_LOCUSTS_EMITTER]; obj = &Objects[ID_LOCUSTS_EMITTER];
@ -619,7 +619,7 @@ namespace TEN::Entities
obj->Initialize = InitializeInjuredSas; obj->Initialize = InitializeInjuredSas;
obj->control = InjuredSasControl; obj->control = InjuredSasControl;
obj->collision = ObjectCollision; obj->collision = ObjectCollision;
obj->SetupHitEffect(false, true); obj->SetHitEffect(false, true);
} }
obj = &Objects[ID_ENEMY_JEEP]; obj = &Objects[ID_ENEMY_JEEP];
@ -639,7 +639,7 @@ namespace TEN::Entities
obj->SetBoneRotationFlags(9, ROT_X); obj->SetBoneRotationFlags(9, ROT_X);
obj->SetBoneRotationFlags(11, ROT_X); obj->SetBoneRotationFlags(11, ROT_X);
obj->SetBoneRotationFlags(12, ROT_X); obj->SetBoneRotationFlags(12, ROT_X);
obj->SetupHitEffect(true); obj->SetHitEffect(true);
} }
obj = &Objects[ID_VON_CROY]; obj = &Objects[ID_VON_CROY];
@ -666,7 +666,7 @@ namespace TEN::Entities
{ {
obj->control = AnimatingControl; obj->control = AnimatingControl;
obj->collision = SasDragBlokeCollision; obj->collision = SasDragBlokeCollision;
obj->SetupHitEffect(false, true); obj->SetHitEffect(false, true);
} }
obj = &Objects[ID_SARCOPHAGUS]; obj = &Objects[ID_SARCOPHAGUS];
@ -674,7 +674,7 @@ namespace TEN::Entities
{ {
obj->control = AnimatingControl; obj->control = AnimatingControl;
obj->collision = SarcophagusCollision; obj->collision = SarcophagusCollision;
obj->SetupHitEffect(true); obj->SetHitEffect(true);
} }
obj = &Objects[ID_MAPPER]; obj = &Objects[ID_MAPPER];
@ -683,7 +683,7 @@ namespace TEN::Entities
obj->Initialize = InitializeMapper; obj->Initialize = InitializeMapper;
obj->control = MapperControl; obj->control = MapperControl;
obj->drawRoutine = nullptr; obj->drawRoutine = nullptr;
obj->SetupHitEffect(true); obj->SetHitEffect(true);
} }
obj = &Objects[ID_ELEMENT_PUZZLE]; obj = &Objects[ID_ELEMENT_PUZZLE];
@ -692,7 +692,7 @@ namespace TEN::Entities
obj->Initialize = InitializeElementPuzzle; obj->Initialize = InitializeElementPuzzle;
obj->control = ElementPuzzleControl; obj->control = ElementPuzzleControl;
obj->collision = ElementPuzzleCollision; obj->collision = ElementPuzzleCollision;
obj->SetupHitEffect(true); obj->SetHitEffect(true);
} }
obj = &Objects[ID_WHEEL_OF_FORTUNE]; obj = &Objects[ID_WHEEL_OF_FORTUNE];
@ -706,14 +706,14 @@ namespace TEN::Entities
obj->SetBoneRotationFlags(1, ROT_Z); obj->SetBoneRotationFlags(1, ROT_Z);
obj->SetBoneRotationFlags(2, ROT_Z); obj->SetBoneRotationFlags(2, ROT_Z);
obj->SetBoneRotationFlags(3, ROT_Z); obj->SetBoneRotationFlags(3, ROT_Z);
obj->SetupHitEffect(true); obj->SetHitEffect(true);
} }
obj = &Objects[ID_ENEMY_PIECE]; obj = &Objects[ID_ENEMY_PIECE];
if (obj->loaded) if (obj->loaded)
{ {
obj->collision = ObjectCollision; obj->collision = ObjectCollision;
obj->SetupHitEffect(true); obj->SetHitEffect(true);
} }
obj = &Objects[ID_GOD_HEAD]; obj = &Objects[ID_GOD_HEAD];
@ -728,7 +728,7 @@ namespace TEN::Entities
obj->Initialize = InitializeGamePiece; obj->Initialize = InitializeGamePiece;
obj->control = SenetControl; obj->control = SenetControl;
obj->collision = ObjectCollision; obj->collision = ObjectCollision;
obj->SetupHitEffect(true); obj->SetHitEffect(true);
} }
} }
@ -742,7 +742,7 @@ namespace TEN::Entities
obj->Initialize = InitializeObelisk; obj->Initialize = InitializeObelisk;
obj->control = ObeliskControl; obj->control = ObeliskControl;
obj->collision = ObjectCollision; obj->collision = ObjectCollision;
obj->SetupHitEffect(true); obj->SetHitEffect(true);
} }
obj = &Objects[ID_WRAITH_TRAP]; obj = &Objects[ID_WRAITH_TRAP];
@ -751,7 +751,7 @@ namespace TEN::Entities
obj->Initialize = InitializeWraithTrap; obj->Initialize = InitializeWraithTrap;
obj->control = WraithTrapControl; obj->control = WraithTrapControl;
obj->collision = ObjectCollision; obj->collision = ObjectCollision;
obj->SetupHitEffect(true); obj->SetHitEffect(true);
} }
for (int objectID = ID_WATERFALL1; objectID <= ID_WATERFALL6; objectID++) for (int objectID = ID_WATERFALL1; objectID <= ID_WATERFALL6; objectID++)
@ -769,7 +769,7 @@ namespace TEN::Entities
{ {
obj->control = ChainControl; obj->control = ChainControl;
obj->collision = GenericSphereBoxCollision; obj->collision = GenericSphereBoxCollision;
obj->SetupHitEffect(true); obj->SetHitEffect(true);
} }
obj = &Objects[ID_PLOUGH]; obj = &Objects[ID_PLOUGH];
@ -777,7 +777,7 @@ namespace TEN::Entities
{ {
obj->control = PloughControl; obj->control = PloughControl;
obj->collision = GenericSphereBoxCollision; obj->collision = GenericSphereBoxCollision;
obj->SetupHitEffect(true); obj->SetHitEffect(true);
} }
obj = &Objects[ID_CATWALK_BLADE]; obj = &Objects[ID_CATWALK_BLADE];
@ -785,7 +785,7 @@ namespace TEN::Entities
{ {
obj->control = CatwalkBladeControl; obj->control = CatwalkBladeControl;
obj->collision = BladeCollision; obj->collision = BladeCollision;
obj->SetupHitEffect(true); obj->SetHitEffect(true);
} }
obj = &Objects[ID_SETH_BLADE]; obj = &Objects[ID_SETH_BLADE];
@ -794,7 +794,7 @@ namespace TEN::Entities
obj->Initialize = InitializeSethBlade; obj->Initialize = InitializeSethBlade;
obj->control = SethBladeControl; obj->control = SethBladeControl;
obj->collision = GenericSphereBoxCollision; obj->collision = GenericSphereBoxCollision;
obj->SetupHitEffect(true); obj->SetHitEffect(true);
} }
obj = &Objects[ID_PLINTH_BLADE]; obj = &Objects[ID_PLINTH_BLADE];
@ -802,7 +802,7 @@ namespace TEN::Entities
{ {
obj->control = PlinthBladeControl; obj->control = PlinthBladeControl;
obj->collision = BladeCollision; obj->collision = BladeCollision;
obj->SetupHitEffect(true); obj->SetHitEffect(true);
} }
obj = &Objects[ID_BIRD_BLADE]; obj = &Objects[ID_BIRD_BLADE];
@ -810,7 +810,7 @@ namespace TEN::Entities
{ {
obj->control = BirdBladeControl; obj->control = BirdBladeControl;
obj->collision = GenericSphereBoxCollision; obj->collision = GenericSphereBoxCollision;
obj->SetupHitEffect(true); obj->SetHitEffect(true);
} }
obj = &Objects[ID_JOBY_SPIKES]; obj = &Objects[ID_JOBY_SPIKES];
@ -819,7 +819,7 @@ namespace TEN::Entities
obj->Initialize = InitializeJobySpikes; obj->Initialize = InitializeJobySpikes;
obj->control = JobySpikesControl; obj->control = JobySpikesControl;
obj->collision = GenericSphereBoxCollision; obj->collision = GenericSphereBoxCollision;
obj->SetupHitEffect(true); obj->SetHitEffect(true);
} }
obj = &Objects[ID_MOVING_BLADE]; obj = &Objects[ID_MOVING_BLADE];
@ -827,7 +827,7 @@ namespace TEN::Entities
{ {
obj->control = MovingBladeControl; obj->control = MovingBladeControl;
obj->collision = BladeCollision; obj->collision = BladeCollision;
obj->SetupHitEffect(true); obj->SetHitEffect(true);
} }
obj = &Objects[ID_SPIKEBALL]; obj = &Objects[ID_SPIKEBALL];
@ -835,7 +835,7 @@ namespace TEN::Entities
{ {
obj->control = SpikeballControl; obj->control = SpikeballControl;
obj->collision = GenericSphereBoxCollision; obj->collision = GenericSphereBoxCollision;
obj->SetupHitEffect(true); obj->SetHitEffect(true);
} }
obj = &Objects[ID_CHAIN]; obj = &Objects[ID_CHAIN];
@ -843,7 +843,7 @@ namespace TEN::Entities
{ {
obj->control = ChainControl; obj->control = ChainControl;
obj->collision = GenericSphereBoxCollision; obj->collision = GenericSphereBoxCollision;
obj->SetupHitEffect(true); obj->SetHitEffect(true);
} }
obj = &Objects[ID_PLOUGH]; obj = &Objects[ID_PLOUGH];
@ -851,7 +851,7 @@ namespace TEN::Entities
{ {
obj->control = PloughControl; obj->control = PloughControl;
obj->collision = GenericSphereBoxCollision; obj->collision = GenericSphereBoxCollision;
obj->SetupHitEffect(true); obj->SetHitEffect(true);
} }
obj = &Objects[ID_FLOOR_4BLADES]; obj = &Objects[ID_FLOOR_4BLADES];
@ -859,7 +859,7 @@ namespace TEN::Entities
{ {
obj->control = FourBladesControl; obj->control = FourBladesControl;
obj->collision = GenericSphereBoxCollision; obj->collision = GenericSphereBoxCollision;
obj->SetupHitEffect(true); obj->SetHitEffect(true);
} }
obj = &Objects[ID_CEILING_4BLADES]; obj = &Objects[ID_CEILING_4BLADES];
@ -867,7 +867,7 @@ namespace TEN::Entities
{ {
obj->control = FourBladesControl; obj->control = FourBladesControl;
obj->collision = GenericSphereBoxCollision; obj->collision = GenericSphereBoxCollision;
obj->SetupHitEffect(true); obj->SetHitEffect(true);
} }
obj = &Objects[ID_STARGATE]; obj = &Objects[ID_STARGATE];
@ -875,7 +875,7 @@ namespace TEN::Entities
{ {
obj->control = StargateControl; obj->control = StargateControl;
obj->collision = StargateCollision; obj->collision = StargateCollision;
obj->SetupHitEffect(true); obj->SetHitEffect(true);
} }
obj = &Objects[ID_SLICER_DICER]; obj = &Objects[ID_SLICER_DICER];
@ -884,7 +884,7 @@ namespace TEN::Entities
obj->Initialize = InitializeSlicerDicer; obj->Initialize = InitializeSlicerDicer;
obj->control = SlicerDicerControl; obj->control = SlicerDicerControl;
obj->collision = BladeCollision; obj->collision = BladeCollision;
obj->SetupHitEffect(true); obj->SetHitEffect(true);
} }
obj = &Objects[ID_MINE]; obj = &Objects[ID_MINE];
@ -901,7 +901,7 @@ namespace TEN::Entities
obj->Initialize = InitializeSpikyWall; obj->Initialize = InitializeSpikyWall;
obj->control = ControlSpikyWall; obj->control = ControlSpikyWall;
obj->collision = CollideSpikyWall; obj->collision = CollideSpikyWall;
obj->SetupHitEffect(true); obj->SetHitEffect(true);
} }
obj = &Objects[ID_SPIKY_CEILING]; obj = &Objects[ID_SPIKY_CEILING];
@ -910,7 +910,7 @@ namespace TEN::Entities
obj->Initialize = InitializeSpikyCeiling; obj->Initialize = InitializeSpikyCeiling;
obj->control = ControlSpikyCeiling; obj->control = ControlSpikyCeiling;
obj->collision = CollideSpikyCeiling; obj->collision = CollideSpikyCeiling;
obj->SetupHitEffect(true); obj->SetHitEffect(true);
} }
obj = &Objects[ID_COG]; obj = &Objects[ID_COG];
@ -918,7 +918,7 @@ namespace TEN::Entities
{ {
obj->control = CogControl; obj->control = CogControl;
obj->collision = CogCollision; obj->collision = CogCollision;
obj->SetupHitEffect(true); obj->SetHitEffect(true);
} }
obj = &Objects[ID_LARA_DOUBLE]; obj = &Objects[ID_LARA_DOUBLE];
@ -932,7 +932,7 @@ namespace TEN::Entities
obj->pivotLength = 50; obj->pivotLength = 50;
obj->radius = 128; obj->radius = 128;
obj->intelligent = true; obj->intelligent = true;
obj->SetupHitEffect(true); obj->SetHitEffect(true);
} }
obj = &Objects[ID_TEETH_SPIKES]; obj = &Objects[ID_TEETH_SPIKES];
@ -947,7 +947,7 @@ namespace TEN::Entities
{ {
obj->control = HammerControl; obj->control = HammerControl;
obj->collision = GenericSphereBoxCollision; obj->collision = GenericSphereBoxCollision;
obj->SetupHitEffect(true); obj->SetHitEffect(true);
} }
} }
@ -959,7 +959,7 @@ namespace TEN::Entities
obj->Initialize = InitializeJeep; obj->Initialize = InitializeJeep;
obj->collision = JeepPlayerCollision; obj->collision = JeepPlayerCollision;
obj->shadowType = ShadowMode::Lara; obj->shadowType = ShadowMode::Lara;
obj->SetupHitEffect(true); obj->SetHitEffect(true);
} }
obj = &Objects[ID_MOTORBIKE]; obj = &Objects[ID_MOTORBIKE];
@ -968,7 +968,7 @@ namespace TEN::Entities
obj->Initialize = InitializeMotorbike; obj->Initialize = InitializeMotorbike;
obj->collision = MotorbikePlayerCollision; obj->collision = MotorbikePlayerCollision;
obj->shadowType = ShadowMode::Lara; obj->shadowType = ShadowMode::Lara;
obj->SetupHitEffect(true); obj->SetHitEffect(true);
} }
} }

View file

@ -347,7 +347,7 @@ namespace TEN::Entities::Creatures::TR5
{ {
if (!((pos.x ^ mesh->pos.Position.x) & 0xFFFFFC00)) if (!((pos.x ^ mesh->pos.Position.x) & 0xFFFFFC00))
{ {
if (StaticObjects[mesh->staticNumber].shatterType != SHT_NONE) if (StaticObjects[mesh->staticNumber].shatterType != ShatterType::None)
{ {
ShatterObject(0, mesh, -64, LaraItem->RoomNumber, 0); ShatterObject(0, mesh, -64, LaraItem->RoomNumber, 0);
//SoundEffect(ShatterSounds[gfCurrentLevel - 5][*(v28 + 18)], v28); //SoundEffect(ShatterSounds[gfCurrentLevel - 5][*(v28 + 18)], v28);

View file

@ -110,7 +110,7 @@ namespace TEN::Entities::Creatures::TR5
if (objOnLos < 0 && GetRandomControl() & 1) if (objOnLos < 0 && GetRandomControl() & 1)
{ {
if (StaticObjects[hitMesh->staticNumber].shatterType != SHT_NONE) if (StaticObjects[hitMesh->staticNumber].shatterType != ShatterType::None)
{ {
ShatterObject(0, hitMesh, 64, target.RoomNumber, 0); ShatterObject(0, hitMesh, 64, target.RoomNumber, 0);
TestTriggers(hitMesh->pos.Position.x, hitMesh->pos.Position.y, hitMesh->pos.Position.z, target.RoomNumber, true); TestTriggers(hitMesh->pos.Position.x, hitMesh->pos.Position.y, hitMesh->pos.Position.z, target.RoomNumber, true);

View file

@ -553,7 +553,7 @@ namespace TEN::Entities::Creatures::TR5
if (!((mesh->pos.Position.z ^ pos.z) & 0xFFFFFC00) && !((mesh->pos.Position.x ^ pos.x) & 0xFFFFFC00)) if (!((mesh->pos.Position.z ^ pos.z) & 0xFFFFFC00) && !((mesh->pos.Position.x ^ pos.x) & 0xFFFFFC00))
{ {
if (StaticObjects[mesh->staticNumber].shatterType != SHT_NONE) if (StaticObjects[mesh->staticNumber].shatterType != ShatterType::None)
{ {
ShatterObject(0, mesh, -64, LaraItem->RoomNumber, 0); ShatterObject(0, mesh, -64, LaraItem->RoomNumber, 0);
SoundEffect(GetShatterSound(mesh->staticNumber), (Pose*)mesh); SoundEffect(GetShatterSound(mesh->staticNumber), (Pose*)mesh);

View file

@ -76,7 +76,7 @@ bool IsOnExpandingPlatform(int itemNumber, int x, int z)
return false; return false;
} }
return GetBridgeItemIntersect(itemNumber, x, item->Pose.Position.y, z, false).has_value(); return GetBridgeItemIntersect(Vector3i(x, item->Pose.Position.y, z), itemNumber, false).has_value();
} }
bool IsInFrontOfExpandingPlatform(int itemNumber, int x, int y, int z, int margin) bool IsInFrontOfExpandingPlatform(int itemNumber, int x, int y, int z, int margin)
@ -129,7 +129,7 @@ bool IsInFrontOfExpandingPlatform(int itemNumber, int x, int y, int z, int margi
return false; return false;
} }
return GetBridgeItemIntersect(itemNumber, x, item->Pose.Position.y, z, false).has_value(); return GetBridgeItemIntersect(Vector3i(x, item->Pose.Position.y, z), itemNumber, false).has_value();
} }
void ShiftLaraOnPlatform(short itemNumber, bool isExpanding) void ShiftLaraOnPlatform(short itemNumber, bool isExpanding)
@ -254,7 +254,7 @@ std::optional<int> ExpandingPlatformFloor(short itemNumber, int x, int y, int z)
if (!IsOnExpandingPlatform(itemNumber, x, z)) if (!IsOnExpandingPlatform(itemNumber, x, z))
return std::nullopt; return std::nullopt;
return GetBridgeItemIntersect(itemNumber, x, y, z, false); return GetBridgeItemIntersect(Vector3i(x, y, z), itemNumber, false);
} }
std::optional<int> ExpandingPlatformCeiling(short itemNumber, int x, int y, int z) std::optional<int> ExpandingPlatformCeiling(short itemNumber, int x, int y, int z)
@ -262,7 +262,7 @@ std::optional<int> ExpandingPlatformCeiling(short itemNumber, int x, int y, int
if (!IsOnExpandingPlatform(itemNumber, x, z)) if (!IsOnExpandingPlatform(itemNumber, x, z))
return std::nullopt; return std::nullopt;
return GetBridgeItemIntersect(itemNumber, x, y, z, true); return GetBridgeItemIntersect(Vector3i(x, y, z), itemNumber, true);
} }
int ExpandingPlatformFloorBorder(short itemNumber) int ExpandingPlatformFloorBorder(short itemNumber)

File diff suppressed because it is too large Load diff

View file

@ -1,38 +0,0 @@
#pragma once
class Vector3i;
struct CollisionInfo;
struct ItemInfo;
namespace TEN::Entities::Generic
{
struct PushableInfo;
PushableInfo* GetPushableInfo(ItemInfo* item);
void InitializePushableBlock(short itemNumber);
void ClearMovableBlockSplitters(const Vector3i& pos, short roomNumber);
void PushableBlockControl(short itemNumber);
void PushableBlockCollision(short itemNumber, ItemInfo* laraItem, CollisionInfo* coll);
bool TestBlockMovable(ItemInfo* item, int blockHeight);
bool TestBlockPush(ItemInfo* item, int blockHeight, int quadrant);
bool TestBlockPull(ItemInfo* item, int blockHeight, int quadrant);
// Pushable stack utilities
void MoveStackXZ(short itemNumber);
void MoveStackY(short itemNumber, int y);
void RemoveBridgeStack(short itemNumber);
void AddBridgeStack(short itemNumber);
void RemoveFromStack(short itemNumber);
int FindStack(short itemNumber);
int GetStackHeight(ItemInfo* item);
bool CheckStackLimit(ItemInfo* item);
void PushLoop(ItemInfo* item);
void PushEnd(ItemInfo* item);
std::optional<int> PushableBlockFloor(short itemNumber, int x, int y, int z);
std::optional<int> PushableBlockCeiling(short itemNumber, int x, int y, int z);
int PushableBlockFloorBorder(short itemNumber);
int PushableBlockCeilingBorder(short itemNumber);
}

View file

@ -1,36 +0,0 @@
#pragma once
namespace TEN::Entities::Generic
{
enum class PushableMovementState
{
None,
Moving,
Stopping
};
struct PushableInfo
{
PushableMovementState MovementState = PushableMovementState::None;
int height; // height for collision, also in floor procedure
int weight;
int stackLimit;
int moveX; // used for pushable movement code
int moveZ; // used for pushable movement code
int linkedIndex; // using itemFlags[1] for now
int gravity; // fall acceleration
int loopSound; // looped sound index for movement
int stopSound; // ending sound index
int fallSound; // sound on hitting floor (if dropped)
int climb; // not used for now
bool canFall; // OCB 32
bool hasFloorCeiling; // has floor and ceiling procedures (OCB 64)
bool disablePull; // OCB 128
bool disablePush; // OCB 256
bool disableW; // OCB 512 (W+E)
bool disableE; // OCB 512 (W+E)
bool disableN; // OCB 1024 (N+S)
bool disableS; // OCB 1024 (N+S)
};
}

Some files were not shown because too many files have changed in this diff Show more