diff --git a/CHANGELOG.md b/CHANGELOG.md index 43521bea0..ee74d0abb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,12 +1,94 @@ # Changelog -Here you will find the full changelog of TEN's releases from Version 1.0 and up +The dates are in European standard format where date is presented as **YYYY-MM-DD**. +TombEngine releases are located in this repository (alongside with Tomb Editor): https://github.com/TombEngine/TombEditorReleases -The dates are in European standard format where date is presented as **YYYY-MM-DD** +## Version 1.5 - xxxx-xx-xx + +### Bug fixes + +* Fixed original issue with classic switch off trigger incorrectly activating some trigger actions. +* Fixed moveable status after antitriggering. +* Fixed leveljump vehicle transfer. +* Fixed weapons not properly hitting enemies. +* Fixed falling through dynamic bridges that are moving upwards. +* Fixed Laserhead teleporting Lara and making her invisible on death. +* Fixed pick-ups from Sarcophagus objects. +* Fixed issue with Lara not rotating together with bridges while picking up items. +* Fixed ghost collision with objects with zero bounds. +* Fixed several binocular bugs. +* Fixed faulty death sectors. +* Fixed shimmy softlocks around static meshes with soft collision. +* Fixed incorrect swing ledge grabs with steep grab angles. +* Fixed incorrect climbing out of water on bridge objects and in front of static meshes. +* Fixed incorrect diving animation when swandiving from a high place. +* Fixed room clipping when flyby path goes out of room bounds. +* Fixed camera rotating with the player's hips when climbing out of water. +* Fixed camera behaviour on sloped surfaces after player's death. +* Fixed camera position after loading a savegame. +* Fixed broken ropes after loading a savegame. +* Fixed AI for TR2 Skidoo driver and Worker with shotgun. +* Fixed Ember Emitter crashing when ocb is between -1 and -10. +* Fixed Electric Cleaner and Squishy Block not detecting collision with certain block heights. +* Fixed Squishy Blocks crashing the level. +* Fixed Larson and Pierre pathfinding. +* Fixed Dart Emitters failing with antitrigger. +* Fixed Homing Dart Emitter spawning darts continously when player is on its trigger. +* Fixed Four Blade Trap floor and ceiling collision. +* Fixed Joby Spikes collision and deformation. +* Fixed Sentry Gun joint rotation. +* Fixed Teeth Spikes not triggering the player impale animation. +* Fixed TR4 Mine crash with OCB 1 when triggered. +* Fixed cases where Atlantean Mutant's bombs cause the game to crash. +* Fixed torch flame delay when the player throws or drops a torch. +* Fixed display sprites and display strings rendering in the inventory background. +* Fixed young Lara hair drawing. https://tombengine.com/docs/level-settings/#young_lara + +### Features/Amendments + +* Added high framerate mode (also known as 60 FPS mode). +* Added a customisable global lensflare effect. https://tombengine.com/docs/level-settings/#lensflare +* Added a customisable starry sky and meteor effect. https://tombengine.com/docs/level-settings/#stars +* Added the ability to display "Lara's Home" entry in the main menu. +* Added the ability to change pickup item count by modifying item hit points. +* Added F12 as alternative to PrtSc for screenshots. +* Added ability to invoke load game dialog after death by pressing any key. +* Added visible mouse pointer in windowed mode. +* Added portal debug mode. +* Added new sound conditions: Quicksand and Underwater. + - Quicksand - sound effect plays when a moveable is in quicksand. + - Underwater - sound plays when the camera is submerged. +* Added TR3 Seal Mutant. https://tombengine.com/docs/ocb-and-setup-instructions/#sealmutant + - You must use this version: https://github.com/TombEngine/Resources/raw/main/Wad2%20Objects/Enemies/TEN_Seal_Mutant.wad2 +* Added TR4 Enemy Jeep. https://tombengine.com/docs/ocb-and-setup-instructions/#enemy_jeep + - You must use this version: https://github.com/TombEngine/Resources/raw/main/Wad2%20Objects/Enemies/TR4_Enemy_Jeep.wad2 +* Changed TR5 Rome Hammer to not hurt player whilst deactivated. +* Changed TR2 Statue with blade damage from 20 to 200. +* Changed sound effect that is triggered when using the `level.rumble` feature in a level (ID 359 in the soundmap). +* Changed hardcoded sound for Raising Block 1/2 back to the ID used in TRLE (ID 149). +* Enhanced TR2 Rolling Spindle detection to avoid them going down through pits. +* Enhanced Sentry Guns, with a new ItemFlags[3], to contain the ID of the inventory item that deactivates the sentry guns (Puzzle Item 5 by default). +* Enhanced Dart Emitter, with a new ItemFlags[0], to contain the number of frames between shots (by default 32 in Dart Emitter, and 24 in Homing-Dart Emitter). +* Enhanced Raptor behaviour and handling. https://tombengine.com/docs/ocb-and-setup-instructions/#raptor + - You must use this version: https://github.com/TombEngine/Resources/raw/main/Wad2%20Objects/Enemies/TEN_Raptor.wad2 +* Removed original limit of 32 active Flame Emitters. + +### Lua API changes + +* Added Flow.EnableHomeLevel() function. +* Added Flow.IsStringPresent() function. +* Added Flow.LensFlare() and Flow.Starfield() classes. +* Added Inventory.GetUsedItem(), Inventory.SetUsedItem() and Inventory.ClearUsedItem() functions. +* Added Input.KeyClearAll() function. +* Added Moveable.GetJointRotation() and optional 'offset' parameter for Moveable.GetJointPosition(). +* Added Moveable.GetTargetState() function. +* Added Room:GetRoomNumber() function. +* Removed anims.monkeyAutoJump. It is now a player menu configuration. +* Fixed Volume:GetActive() method. ## [Version 1.4](https://github.com/TombEngine/TombEditorReleases/releases/tag/v1.7.1) - 2024-04-21 -### Bug Fixes +### Bug fixes * Fixed drawing of display sprites in title level. * Fixed drawing of smoke sprites and various other sprites. * Fixed drawing of transparent surfaces when debris are present in scene. @@ -49,7 +131,7 @@ The dates are in European standard format where date is presented as **YYYY-MM-D ## [Version 1.3](https://github.com/TombEngine/TombEditorReleases/releases/tag/v1.7) - 2024-01-06 -### Bug Fixes +### Bug fixes * Fixed crash if title logo is removed from Textures folder. * Fixed crash if unknown player state ID is encountered. * Fixed bug with OCB 2 on pushables, and some other pushable bugs. @@ -105,7 +187,7 @@ The dates are in European standard format where date is presented as **YYYY-MM-D ## [Version 1.2](https://github.com/TombEngine/TombEditorReleases/releases/tag/v1.6.9) - 2023-11-11 -### Bug Fixes +### Bug fixes * Fix burning torch not working properly if there are more than 256 objects in a level. * Fix grenade and rocket projectiles smoke offset in certain directions. * Fix projectiles flying through animating objects. @@ -133,24 +215,24 @@ The dates are in European standard format where date is presented as **YYYY-MM-D ### Features/Amendments * Improve head-on wall collision. * Overhaul pushables: - - Separate climbable and non-climbable pushable object slots. - - Add new pushable OCB to manipulate pushable properties. - - Add new animations for pushing pushables off edgees (TR1-3 and TR4-5 versions). - - Fix pushables not working with raising blocks. - - Fix miscellaneous pushable bugs. + - Separate climbable and non-climbable pushable object slots. + - Add new pushable OCB to manipulate pushable properties. + - Add new animations for pushing pushables off edgees (TR1-3 and TR4-5 versions). + - Fix pushables not working with raising blocks. + - Fix miscellaneous pushable bugs. * Overhaul look-around feature: - - Allow for more consistent and wider viewing angles while crawling, crouching, and hanging. - - Improve look camera movement and control. - - Re-enable looking while performing up jump, backward jump, or backward crawl. - - Add functionality to rotate slowly when holding Walk while using binoculars or lasersight. + - Allow for more consistent and wider viewing angles while crawling, crouching, and hanging. + - Improve look camera movement and control. + - Re-enable looking while performing up jump, backward jump, or backward crawl. + - Add functionality to rotate slowly when holding Walk while using binoculars or lasersight. * Add target highlighter system with toggle in Sound and Gameplay settings. * Add sprint slide state 191. * Add swinging blade. * Add crumbling platform and add new OCBs for behaviour: - - OCB 0: Default behaviour. When the player steps on the platform, it will shake and crumble after 1.2 seconds. - - 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 negative value requires a trigger to activate. + - OCB 0: Default behaviour. When the player steps on the platform, it will shake and crumble after 1.2 seconds. + - 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 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). @@ -182,7 +264,7 @@ The dates are in European standard format where date is presented as **YYYY-MM-D ## [Version 1.1.0](https://github.com/TombEngine/TombEditorReleases/releases/tag/v1.6.8) - 2023-07-29 -### Bug Fixes +### Bug fixes * Fix enemies shooting Lara through static meshes and moveables. * Fix skeletons and mummies not being affected by explosive weapons. * Fix crash on loading if static meshes with IDs above maximum are present. @@ -393,7 +475,7 @@ The dates are in European standard format where date is presented as **YYYY-MM-D ## [Version 1.0.6](https://github.com/TombEngine/TombEditorReleases/releases/tag/v1.6.4) - 2023-01-29 -### Bug FIxes +### Bug fixes * Fix major pathfinding bug which could have caused lots of issues with enemy behaviour. * Fix potential random crashes due to incorrect rendering behaviour. * Fix savegame crash for disabled enemies with partially set activation mask. diff --git a/Documentation/config.ld b/Documentation/config.ld index 6220159cc..bd40d633c 100644 --- a/Documentation/config.ld +++ b/Documentation/config.ld @@ -12,7 +12,7 @@ new_type("luautil", "5 Lua utility modules", true) not_luadoc = true -local version = "1.4" +local version = "1.5" project = "TombEngine" title = "TombEngine " .. version .. " Lua API" description = "TombEngine " .. version .. " scripting interface" diff --git a/Documentation/doc/1 modules/Effects.html b/Documentation/doc/1 modules/Effects.html index c9aaf46b4..ad3ca0b7d 100644 --- a/Documentation/doc/1 modules/Effects.html +++ b/Documentation/doc/1 modules/Effects.html @@ -3,7 +3,7 @@ - TombEngine 1.4 Lua API + TombEngine 1.5 Lua API @@ -49,10 +49,12 @@
  • Flow.Animations
  • Flow.Fog
  • Flow.InventoryItem
  • +
  • Flow.LensFlare
  • Flow.Level
  • Flow.Mirror
  • Flow.Settings
  • Flow.SkyLayer
  • +
  • Flow.Starfield
  • Objects.AIObject
  • Objects.Camera
  • Objects.LaraObject
  • diff --git a/Documentation/doc/1 modules/Flow.html b/Documentation/doc/1 modules/Flow.html index 46af015e4..4b028009d 100644 --- a/Documentation/doc/1 modules/Flow.html +++ b/Documentation/doc/1 modules/Flow.html @@ -3,7 +3,7 @@ - TombEngine 1.4 Lua API + TombEngine 1.5 Lua API @@ -49,10 +49,12 @@
  • Flow.Animations
  • Flow.Fog
  • Flow.InventoryItem
  • +
  • Flow.LensFlare
  • Flow.Level
  • Flow.Mirror
  • Flow.Settings
  • Flow.SkyLayer
  • +
  • Flow.Starfield
  • Objects.AIObject
  • Objects.Camera
  • Objects.LaraObject
  • @@ -129,6 +131,10 @@ scripts too.

    Enable or disable level selection in title flyby. + EnableHomeLevel(enabled) + Enable or disable Home Level entry in the main menu. + + EnableLoadSave(enabled) Enable or disable saving and loading of savegames. @@ -137,7 +143,7 @@ scripts too.

    - + @@ -230,6 +236,10 @@ scripts too.

    + + + + @@ -363,11 +373,11 @@ Must be true or false
    - - EnableLoadSave(enabled) + + EnableHomeLevel(enabled)
    - Enable or disable saving and loading of savegames. + Enable or disable Home Level entry in the main menu. () @@ -375,7 +385,29 @@ Must be true or false + + + + + +
    +
    + + EnableLoadSave(enabled) +
    +
    + Enable or disable saving and loading of savegames. () + + + +

    Parameters:

    + @@ -393,8 +425,7 @@ Must be true or false EnableFlyCheat(enabled)
    - Enable or disable DOZY mode (fly cheat). -Must be true or false + Enable or disable the fly cheat. () @@ -402,7 +433,7 @@ Must be true or false @@ -902,6 +933,28 @@ You will not need to call them manually. +
    +
    + + IsStringPresent(string) +
    +
    + Check if translated string is present. + + + +

    Parameters:

    + + + + + +
    diff --git a/Documentation/doc/1 modules/Input.html b/Documentation/doc/1 modules/Input.html index 61d495f1c..bf7b07fec 100644 --- a/Documentation/doc/1 modules/Input.html +++ b/Documentation/doc/1 modules/Input.html @@ -3,7 +3,7 @@ - TombEngine 1.4 Lua API + TombEngine 1.5 Lua API @@ -49,10 +49,12 @@
  • Flow.Animations
  • Flow.Fog
  • Flow.InventoryItem
  • +
  • Flow.LensFlare
  • Flow.Level
  • Flow.Mirror
  • Flow.Settings
  • Flow.SkyLayer
  • +
  • Flow.Starfield
  • Objects.AIObject
  • Objects.Camera
  • Objects.LaraObject
  • @@ -129,6 +131,10 @@
    + + + + @@ -254,6 +260,21 @@ + +
    + + KeyClearAll() +
    +
    + Clear all action keys. + + + + + + + +
    diff --git a/Documentation/doc/1 modules/Inventory.html b/Documentation/doc/1 modules/Inventory.html index e5fd825ec..32be88d47 100644 --- a/Documentation/doc/1 modules/Inventory.html +++ b/Documentation/doc/1 modules/Inventory.html @@ -3,7 +3,7 @@ - TombEngine 1.4 Lua API + TombEngine 1.5 Lua API @@ -49,10 +49,12 @@
  • Flow.Animations
  • Flow.Fog
  • Flow.InventoryItem
  • +
  • Flow.LensFlare
  • Flow.Level
  • Flow.Mirror
  • Flow.Settings
  • Flow.SkyLayer
  • +
  • Flow.Starfield
  • Objects.AIObject
  • Objects.Camera
  • Objects.LaraObject
  • @@ -124,6 +126,18 @@
    + + + + + + + + + + + +
    EnableFlyCheat(enabled)Enable or disable DOZY mode (fly cheat).Enable or disable the fly cheat.
    EnablePointFilter(enabled) Get translated string.
    IsStringPresent(string)Check if translated string is present.
    SetLanguageNames(table) Set language names for translations.
    Clear an action key.
    KeyClearAll()Clear all action keys.
    GetMouseDisplayPosition() Get the display position of the cursor in percent.
    SetItemCount(objectID, count) Set the amount of an item in the player's inventory.
    GetUsedItem()Get last item used in the player's inventory.
    SetUsedItem(objectID)Set last item used in the player's inventory.
    ClearUsedItem()Clear last item used in the player's inventory.

    @@ -245,6 +259,70 @@ + +
    + + GetUsedItem() +
    +
    + Get last item used in the player's inventory. + This value will be valid only for a single frame after exiting inventory, after which Lara says "No". + Therefore, this function must be preferably used either in OnLoop or OnUseItem events. + + + + +

    Returns:

    +
      + + ObjID + Last item used in the inventory. +
    + + + + +
    +
    + + SetUsedItem(objectID) +
    +
    + Set last item used in the player's inventory. + You will be able to specify only objects which already exist in the inventory. + Will only be valid for the next frame. If not processed by the game, Lara will say "No". + + + +

    Parameters:

    + + + + + + +
    +
    + + ClearUsedItem() +
    +
    + Clear last item used in the player's inventory. + When this function is used in OnUseItem level function, it allows to override existing item functionality. + For items without existing functionality, this function is needed to avoid Lara saying "No" after using it. + + + + + + + +
    diff --git a/Documentation/doc/1 modules/Logic.html b/Documentation/doc/1 modules/Logic.html index e190f3c99..500e3d67e 100644 --- a/Documentation/doc/1 modules/Logic.html +++ b/Documentation/doc/1 modules/Logic.html @@ -3,7 +3,7 @@ - TombEngine 1.4 Lua API + TombEngine 1.5 Lua API @@ -49,10 +49,12 @@
  • Flow.Animations
  • Flow.Fog
  • Flow.InventoryItem
  • +
  • Flow.LensFlare
  • Flow.Level
  • Flow.Mirror
  • Flow.Settings
  • Flow.SkyLayer
  • +
  • Flow.Starfield
  • Objects.AIObject
  • Objects.Camera
  • Objects.LaraObject
  • @@ -263,7 +265,8 @@ LOAD SAVE START END -LOOP +LOOP +USEITEM

    Parameters:

    diff --git a/Documentation/doc/1 modules/Objects.html b/Documentation/doc/1 modules/Objects.html index 2cb40e8e6..e616c0ac0 100644 --- a/Documentation/doc/1 modules/Objects.html +++ b/Documentation/doc/1 modules/Objects.html @@ -3,7 +3,7 @@ - TombEngine 1.4 Lua API + TombEngine 1.5 Lua API @@ -49,10 +49,12 @@
  • Flow.Animations
  • Flow.Fog
  • Flow.InventoryItem
  • +
  • Flow.LensFlare
  • Flow.Level
  • Flow.Mirror
  • Flow.Settings
  • Flow.SkyLayer
  • +
  • Flow.Starfield
  • Objects.AIObject
  • Objects.Camera
  • Objects.LaraObject
  • diff --git a/Documentation/doc/1 modules/Sound.html b/Documentation/doc/1 modules/Sound.html index 109ac6e89..cc291c4eb 100644 --- a/Documentation/doc/1 modules/Sound.html +++ b/Documentation/doc/1 modules/Sound.html @@ -3,7 +3,7 @@ - TombEngine 1.4 Lua API + TombEngine 1.5 Lua API @@ -49,10 +49,12 @@
  • Flow.Animations
  • Flow.Fog
  • Flow.InventoryItem
  • +
  • Flow.LensFlare
  • Flow.Level
  • Flow.Mirror
  • Flow.Settings
  • Flow.SkyLayer
  • +
  • Flow.Starfield
  • Objects.AIObject
  • Objects.Camera
  • Objects.LaraObject
  • diff --git a/Documentation/doc/1 modules/Strings.html b/Documentation/doc/1 modules/Strings.html index 63f90605b..e570b6358 100644 --- a/Documentation/doc/1 modules/Strings.html +++ b/Documentation/doc/1 modules/Strings.html @@ -3,7 +3,7 @@ - TombEngine 1.4 Lua API + TombEngine 1.5 Lua API @@ -49,10 +49,12 @@
  • Flow.Animations
  • Flow.Fog
  • Flow.InventoryItem
  • +
  • Flow.LensFlare
  • Flow.Level
  • Flow.Mirror
  • Flow.Settings
  • Flow.SkyLayer
  • +
  • Flow.Starfield
  • Objects.AIObject
  • Objects.Camera
  • Objects.LaraObject
  • @@ -109,7 +111,7 @@

    Functions

    - + @@ -131,7 +133,7 @@
    - ShowString(str, time) + ShowString(str, time, autoDelete)
    Show some text on-screen. @@ -151,6 +153,13 @@ If not given, the string will have an "infinite" life, and will show until HideString is called or until the level is finished. Default: nil (i.e. infinite) +
  • autoDelete + bool + should be string automatically deleted after timeout is reached. +If not given, the string will remain allocated even after timeout is reached, and can be +shown again without re-initialization. +Default: false +
  • diff --git a/Documentation/doc/1 modules/Util.html b/Documentation/doc/1 modules/Util.html index d9f5352d1..319360268 100644 --- a/Documentation/doc/1 modules/Util.html +++ b/Documentation/doc/1 modules/Util.html @@ -3,7 +3,7 @@ - TombEngine 1.4 Lua API + TombEngine 1.5 Lua API @@ -49,10 +49,12 @@
  • Flow.Animations
  • Flow.Fog
  • Flow.InventoryItem
  • +
  • Flow.LensFlare
  • Flow.Level
  • Flow.Mirror
  • Flow.Settings
  • Flow.SkyLayer
  • +
  • Flow.Starfield
  • Objects.AIObject
  • Objects.Camera
  • Objects.LaraObject
  • diff --git a/Documentation/doc/1 modules/View.html b/Documentation/doc/1 modules/View.html index 34b89cba7..b201316aa 100644 --- a/Documentation/doc/1 modules/View.html +++ b/Documentation/doc/1 modules/View.html @@ -3,7 +3,7 @@ - TombEngine 1.4 Lua API + TombEngine 1.5 Lua API @@ -49,10 +49,12 @@
  • Flow.Animations
  • Flow.Fog
  • Flow.InventoryItem
  • +
  • Flow.LensFlare
  • Flow.Level
  • Flow.Mirror
  • Flow.Settings
  • Flow.SkyLayer
  • +
  • Flow.Starfield
  • Objects.AIObject
  • Objects.Camera
  • Objects.LaraObject
  • diff --git a/Documentation/doc/2 classes/Flow.Animations.html b/Documentation/doc/2 classes/Flow.Animations.html index fa6b9a829..b48bf5fc2 100644 --- a/Documentation/doc/2 classes/Flow.Animations.html +++ b/Documentation/doc/2 classes/Flow.Animations.html @@ -3,7 +3,7 @@ - TombEngine 1.4 Lua API + TombEngine 1.5 Lua API @@ -49,10 +49,12 @@
  • Flow.Animations
  • Flow.Fog
  • Flow.InventoryItem
  • +
  • Flow.LensFlare
  • Flow.Level
  • Flow.Mirror
  • Flow.Settings
  • Flow.SkyLayer
  • +
  • Flow.Starfield
  • Objects.AIObject
  • Objects.Camera
  • Objects.LaraObject
  • diff --git a/Documentation/doc/2 classes/Flow.Fog.html b/Documentation/doc/2 classes/Flow.Fog.html index a55a11544..11a25b76a 100644 --- a/Documentation/doc/2 classes/Flow.Fog.html +++ b/Documentation/doc/2 classes/Flow.Fog.html @@ -3,7 +3,7 @@ - TombEngine 1.4 Lua API + TombEngine 1.5 Lua API @@ -49,10 +49,12 @@
  • Flow.Animations
  • Flow.Fog
  • Flow.InventoryItem
  • +
  • Flow.LensFlare
  • Flow.Level
  • Flow.Mirror
  • Flow.Settings
  • Flow.SkyLayer
  • +
  • Flow.Starfield
  • Objects.AIObject
  • Objects.Camera
  • Objects.LaraObject
  • diff --git a/Documentation/doc/2 classes/Flow.InventoryItem.html b/Documentation/doc/2 classes/Flow.InventoryItem.html index ccfe68f21..a6677c716 100644 --- a/Documentation/doc/2 classes/Flow.InventoryItem.html +++ b/Documentation/doc/2 classes/Flow.InventoryItem.html @@ -3,7 +3,7 @@ - TombEngine 1.4 Lua API + TombEngine 1.5 Lua API @@ -49,10 +49,12 @@
  • Flow.Animations
  • Flow.Fog
  • Flow.InventoryItem
  • +
  • Flow.LensFlare
  • Flow.Level
  • Flow.Mirror
  • Flow.Settings
  • Flow.SkyLayer
  • +
  • Flow.Starfield
  • Objects.AIObject
  • Objects.Camera
  • Objects.LaraObject
  • diff --git a/Documentation/doc/2 classes/Flow.LensFlare.html b/Documentation/doc/2 classes/Flow.LensFlare.html new file mode 100644 index 000000000..0af9dd01b --- /dev/null +++ b/Documentation/doc/2 classes/Flow.LensFlare.html @@ -0,0 +1,397 @@ + + + + + TombEngine 1.5 Lua API + + + + +
    ShowString(str, time)ShowString(str, time, autoDelete) Show some text on-screen.
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    LensFlare(Pitch, Yaw, Color.)Create a LensFlare object.
    LensFlare:GetEnabled()Get the lens flare's enabled status.
    LensFlare:GetSunSpriteID()Get the sun's sprite ID.
    LensFlare:GetPitch()Get the lens flare's pitch angle in degrees.
    LensFlare:GetYaw()Get the lens flare's yaw angle in degrees.
    LensFlare:SetColor()Get the lens flare's color.
    LensFlare:SetSunSpriteID(New)Set the lens flare's sun sprite ID.
    LensFlare:SetPitch(New)Set the lens flare's pitch angle.
    LensFlare:SetYaw(New)Set the lens flare's yaw angle.
    LensFlare:SetColor(New)Set the lens flare's color.
    + +
    +
    + + +

    Functions

    + +
    +
    + + LensFlare(Pitch, Yaw, Color.) +
    +
    + Create a LensFlare object. () + + + +

    Parameters:

    + + +

    Returns:

    +
      + + LensFlare + A new LensFlare object. +
    + + + + +
    +
    + + LensFlare:GetEnabled() +
    +
    + Get the lens flare's enabled status. () + + + + +

    Returns:

    +
      + + bool + Lens flare's enabled status. +
    + + + + +
    +
    + + LensFlare:GetSunSpriteID() +
    +
    + Get the sun's sprite ID. () + + + + +

    Returns:

    +
      + + int + Sprite ID. +
    + + + + +
    +
    + + LensFlare:GetPitch() +
    +
    + Get the lens flare's pitch angle in degrees. () + + + + +

    Returns:

    +
      + + float + Pitch angle in degrees. +
    + + + + +
    +
    + + LensFlare:GetYaw() +
    +
    + Get the lens flare's yaw angle in degrees. () + + + + +

    Returns:

    +
      + + float + Yaw angle in degrees. +
    + + + + +
    +
    + + LensFlare:SetColor() +
    +
    + Get the lens flare's color. () + + + + + + + + +
    +
    + + LensFlare:SetSunSpriteID(New) +
    +
    + Set the lens flare's sun sprite ID. () + + + +

    Parameters:

    + + + + + + +
    +
    + + LensFlare:SetPitch(New) +
    +
    + Set the lens flare's pitch angle. (float) + + + +

    Parameters:

    + + + + + + +
    +
    + + LensFlare:SetYaw(New) +
    +
    + Set the lens flare's yaw angle. (float) + + + +

    Parameters:

    + + + + + + +
    +
    + + LensFlare:SetColor(New) +
    +
    + Set the lens flare's color. (Color) + + + +

    Parameters:

    + + + + + + +
    +
    + + + + +
    +generated by TEN-LDoc (a fork of LDoc 1.4.6) +
    + + + diff --git a/Documentation/doc/2 classes/Flow.Level.html b/Documentation/doc/2 classes/Flow.Level.html index 6c505e3e8..eaffdc6fb 100644 --- a/Documentation/doc/2 classes/Flow.Level.html +++ b/Documentation/doc/2 classes/Flow.Level.html @@ -3,7 +3,7 @@ - TombEngine 1.4 Lua API + TombEngine 1.5 Lua API @@ -49,10 +49,12 @@
  • Flow.Animations
  • Flow.Fog
  • Flow.InventoryItem
  • +
  • Flow.LensFlare
  • Flow.Level
  • Flow.Mirror
  • Flow.Settings
  • Flow.SkyLayer
  • +
  • Flow.Starfield
  • Objects.AIObject
  • Objects.Camera
  • Objects.LaraObject
  • @@ -106,10 +108,6 @@

    Members

    - - - - @@ -135,6 +133,14 @@ + + + + + + + + @@ -198,22 +204,6 @@

    Members

    -
    - - nameKey -
    -
    - (string) string key for the level's (localised) name. - Corresponds to an entry in strings.lua. - - - - - - - - -
    scriptFile @@ -307,6 +297,36 @@ + +
    + + starfield +
    +
    + (Flow.Starfield) Starfield. + + + + + + + + +
    +
    + + lensFlare +
    +
    + (Flow.LensFlare) Global lens flare . + + + + + + + +
    diff --git a/Documentation/doc/2 classes/Flow.Mirror.html b/Documentation/doc/2 classes/Flow.Mirror.html index a391b693d..b100c6f4b 100644 --- a/Documentation/doc/2 classes/Flow.Mirror.html +++ b/Documentation/doc/2 classes/Flow.Mirror.html @@ -3,7 +3,7 @@ - TombEngine 1.4 Lua API + TombEngine 1.5 Lua API @@ -49,10 +49,12 @@
  • Flow.Animations
  • Flow.Fog
  • Flow.InventoryItem
  • +
  • Flow.LensFlare
  • Flow.Level
  • Flow.Mirror
  • Flow.Settings
  • Flow.SkyLayer
  • +
  • Flow.Starfield
  • Objects.AIObject
  • Objects.Camera
  • Objects.LaraObject
  • diff --git a/Documentation/doc/2 classes/Flow.Settings.html b/Documentation/doc/2 classes/Flow.Settings.html index a5f64f145..007e40a45 100644 --- a/Documentation/doc/2 classes/Flow.Settings.html +++ b/Documentation/doc/2 classes/Flow.Settings.html @@ -3,7 +3,7 @@ - TombEngine 1.4 Lua API + TombEngine 1.5 Lua API @@ -49,10 +49,12 @@
  • Flow.Animations
  • Flow.Fog
  • Flow.InventoryItem
  • +
  • Flow.LensFlare
  • Flow.Level
  • Flow.Mirror
  • Flow.Settings
  • Flow.SkyLayer
  • +
  • Flow.Starfield
  • Objects.AIObject
  • Objects.Camera
  • Objects.LaraObject
  • diff --git a/Documentation/doc/2 classes/Flow.SkyLayer.html b/Documentation/doc/2 classes/Flow.SkyLayer.html index 6ec6adcf9..69565445a 100644 --- a/Documentation/doc/2 classes/Flow.SkyLayer.html +++ b/Documentation/doc/2 classes/Flow.SkyLayer.html @@ -3,7 +3,7 @@ - TombEngine 1.4 Lua API + TombEngine 1.5 Lua API @@ -49,10 +49,12 @@
  • Flow.Animations
  • Flow.Fog
  • Flow.InventoryItem
  • +
  • Flow.LensFlare
  • Flow.Level
  • Flow.Mirror
  • Flow.Settings
  • Flow.SkyLayer
  • +
  • Flow.Starfield
  • Objects.AIObject
  • Objects.Camera
  • Objects.LaraObject
  • @@ -154,13 +156,14 @@
    (int) cloud speed.

    -

    Values can be between [-32768, 32767], with positive numbers resulting in a sky that scrolls from -west to east, and negative numbers resulting in one that travels east to west.

    - -

    Please note that speeds outside of the range of about [-1000, 1000] will cause the -sky to scroll so fast that it will no longer appear as a coherent stream of clouds. -Less is more. City of The Dead, for example, uses a speed value of 16. +

        Values can be between [-32768, 32767], with positive numbers resulting in a sky that scrolls from
    +    west to east, and negative numbers resulting in one that travels east to west.
     
    +    Please note that speeds outside of the range of about [-1000, 1000] will cause the
    +    sky to scroll so fast that it will no longer appear as a coherent stream of clouds.
    +    Less is more. City of The Dead, for example, uses a speed value of 16.
    +
    +

    diff --git a/Documentation/doc/2 classes/Flow.Starfield.html b/Documentation/doc/2 classes/Flow.Starfield.html new file mode 100644 index 000000000..199aa62ed --- /dev/null +++ b/Documentation/doc/2 classes/Flow.Starfield.html @@ -0,0 +1,454 @@ + + + + + TombEngine 1.5 Lua API + + + + +

    nameKey(string) string key for the level's (localised) name.
    scriptFile (string) Level-specific Lua script file.(Flow.SkyLayer) Secondary sky layer
    starfield(Flow.Starfield) Starfield.
    lensFlare(Flow.LensFlare) Global lens flare .
    fog (Flow.Fog) omni fog RGB color and distance.
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Starfield(starCount)Create a starfield object with only stars.
    Starfield(starCount, meteorCount)Create a starfield object with stars and meteors.
    Starfield:GetStarsEnabled()Get the starfield's enabled status of stars.
    Starfield:GetMeteorsEnabled()Get the starfield's enabled status of meteors.
    Starfield:GetStarCount()Get the starfield's number of stars.
    Starfield:GetMeteorCount()Get the starfield's number of meteors.
    Starfield:GetMeteorSpawnDensity()Get the starfield's meteor spawn density.
    Starfield:GetMeteorVelocity()Get the starfield's meteor velocity.
    Starfield:SetStarCount(New)Set the starfield's number of stars (6000 max).
    Starfield:SetMeteorCount(New)Set the starfield's number of meteors (100 max).
    Starfield:SetMeteorSpawnDensity(New)Set the starfield's meteor spawn density.
    Starfield:SetMeteorVelocity(New)Set the starfield's meteor velocity.
    + +
    +
    + + +

    Functions

    + +
    +
    + + Starfield(starCount) +
    +
    + Create a starfield object with only stars. () + + + +

    Parameters:

    + + +

    Returns:

    +
      + + Starfield + A new Starfield object. +
    + + + + +
    +
    + + Starfield(starCount, meteorCount) +
    +
    + Create a starfield object with stars and meteors. () + + + +

    Parameters:

    + + +

    Returns:

    +
      + + Starfield + A new Starfield object. +
    + + + + +
    +
    + + Starfield:GetStarsEnabled() +
    +
    + Get the starfield's enabled status of stars. () + + + + +

    Returns:

    +
      + + bool + Stars enabled status. +
    + + + + +
    +
    + + Starfield:GetMeteorsEnabled() +
    +
    + Get the starfield's enabled status of meteors. () + + + + +

    Returns:

    +
      + + bool + Meteors enabled status. +
    + + + + +
    +
    + + Starfield:GetStarCount() +
    +
    + Get the starfield's number of stars. () + + + + +

    Returns:

    +
      + + int + Count. +
    + + + + +
    +
    + + Starfield:GetMeteorCount() +
    +
    + Get the starfield's number of meteors. () + + + + +

    Returns:

    +
      + + int + Count. +
    + + + + +
    +
    + + Starfield:GetMeteorSpawnDensity() +
    +
    + Get the starfield's meteor spawn density. () + + + + +

    Returns:

    +
      + + int + Spawn density. +
    + + + + +
    +
    + + Starfield:GetMeteorVelocity() +
    +
    + Get the starfield's meteor velocity. () + + + + +

    Returns:

    +
      + + float + Velocity. +
    + + + + +
    +
    + + Starfield:SetStarCount(New) +
    +
    + Set the starfield's number of stars (6000 max). (int) + + + +

    Parameters:

    + + + + + + +
    +
    + + Starfield:SetMeteorCount(New) +
    +
    + Set the starfield's number of meteors (100 max). (int) + + + +

    Parameters:

    + + + + + + +
    +
    + + Starfield:SetMeteorSpawnDensity(New) +
    +
    + Set the starfield's meteor spawn density. (int) + + + +

    Parameters:

    + + + + + + +
    +
    + + Starfield:SetMeteorVelocity(New) +
    +
    + Set the starfield's meteor velocity. (float) + + + +

    Parameters:

    + + + + + + +
    +
    + + + + +
    +generated by TEN-LDoc (a fork of LDoc 1.4.6) +
    + + + diff --git a/Documentation/doc/2 classes/Objects.AIObject.html b/Documentation/doc/2 classes/Objects.AIObject.html index a8816eb21..308b5c37a 100644 --- a/Documentation/doc/2 classes/Objects.AIObject.html +++ b/Documentation/doc/2 classes/Objects.AIObject.html @@ -3,7 +3,7 @@ - TombEngine 1.4 Lua API + TombEngine 1.5 Lua API @@ -49,10 +49,12 @@
  • Flow.Animations
  • Flow.Fog
  • Flow.InventoryItem
  • +
  • Flow.LensFlare
  • Flow.Level
  • Flow.Mirror
  • Flow.Settings
  • Flow.SkyLayer
  • +
  • Flow.Starfield
  • Objects.AIObject
  • Objects.Camera
  • Objects.LaraObject
  • diff --git a/Documentation/doc/2 classes/Objects.Camera.html b/Documentation/doc/2 classes/Objects.Camera.html index 297e7cb13..a7ec64c6b 100644 --- a/Documentation/doc/2 classes/Objects.Camera.html +++ b/Documentation/doc/2 classes/Objects.Camera.html @@ -3,7 +3,7 @@ - TombEngine 1.4 Lua API + TombEngine 1.5 Lua API @@ -49,10 +49,12 @@
  • Flow.Animations
  • Flow.Fog
  • Flow.InventoryItem
  • +
  • Flow.LensFlare
  • Flow.Level
  • Flow.Mirror
  • Flow.Settings
  • Flow.SkyLayer
  • +
  • Flow.Starfield
  • Objects.AIObject
  • Objects.Camera
  • Objects.LaraObject
  • diff --git a/Documentation/doc/2 classes/Objects.LaraObject.html b/Documentation/doc/2 classes/Objects.LaraObject.html index 56ae5ea19..f153396da 100644 --- a/Documentation/doc/2 classes/Objects.LaraObject.html +++ b/Documentation/doc/2 classes/Objects.LaraObject.html @@ -3,7 +3,7 @@ - TombEngine 1.4 Lua API + TombEngine 1.5 Lua API @@ -49,10 +49,12 @@
  • Flow.Animations
  • Flow.Fog
  • Flow.InventoryItem
  • +
  • Flow.LensFlare
  • Flow.Level
  • Flow.Mirror
  • Flow.Settings
  • Flow.SkyLayer
  • +
  • Flow.Starfield
  • Objects.AIObject
  • Objects.Camera
  • Objects.LaraObject
  • diff --git a/Documentation/doc/2 classes/Objects.Moveable.html b/Documentation/doc/2 classes/Objects.Moveable.html index 00671976d..fdd8a2559 100644 --- a/Documentation/doc/2 classes/Objects.Moveable.html +++ b/Documentation/doc/2 classes/Objects.Moveable.html @@ -3,7 +3,7 @@ - TombEngine 1.4 Lua API + TombEngine 1.5 Lua API @@ -49,10 +49,12 @@
  • Flow.Animations
  • Flow.Fog
  • Flow.InventoryItem
  • +
  • Flow.LensFlare
  • Flow.Level
  • Flow.Mirror
  • Flow.Settings
  • Flow.SkyLayer
  • +
  • Flow.Starfield
  • Objects.AIObject
  • Objects.Camera
  • Objects.LaraObject
  • @@ -108,7 +110,7 @@ pickups, and Lara herself (see also Functions - + @@ -120,11 +122,11 @@ pickups, and Lara herself (see also Shatter item. - + - + @@ -152,6 +154,10 @@ pickups, and Lara herself (see also Retrieve the index of the current state. + + + + @@ -225,10 +231,14 @@ pickups, and Lara herself (see also Determine whether the moveable is active or not - + + + + + @@ -390,7 +400,7 @@ pickups, and Lara herself (see also
    - Moveable(object, name, position, rotation, roomID, animNumber, frameNumber, hp, OCB, AIBits) + Moveable(object, name, position, rotation, roomNumber, animNumber, frameNumber, hp, OCB, AIBits)
    Used to generate a new moveable dynamically at runtime.
    @@ -418,9 +428,9 @@ most can just be ignored (see usage). Rotation rotation rotation about x, y, and z axes (default Rotation(0, 0, 0)) -
  • roomID +
  • roomNumber int - room ID item is in (default: calculated automatically) + the room number the moveable is in (default: calculated automatically).
  • animNumber int @@ -494,7 +504,7 @@ most can just be ignored (see usage).
  • - Moveable:SetEffect(effect, timeout) + Moveable:SetEffect(effect[, timeout])
    Set effect to moveable @@ -509,7 +519,8 @@ most can just be ignored (see usage).
  • timeout float - time (in seconds) after which effect turns off (optional). + time (in seconds) after which effect turns off. + (optional)
  • @@ -520,7 +531,7 @@ most can just be ignored (see usage).
    - Moveable:SetCustomEffect(Color1, Color2, timeout) + Moveable:SetCustomEffect(Color1, Color2[, timeout])
    Set custom colored burn effect to moveable @@ -539,7 +550,8 @@ most can just be ignored (see usage).
  • timeout float - time (in seconds) after which effect turns off (optional). + time (in seconds) after which effect turns off. + (optional)
  • @@ -681,6 +693,28 @@ shiva:SetObjectID(TEN.Objects.ObjID.BIGMEDI_ITEM) +
    +
    + + Moveable:GetTargetState() +
    +
    + Retrieve the index of the target state. + This corresponds to the state the object is trying to get into, which is sometimes different from the active state. + + + + +

    Returns:

    +
      + + int + the index of the target state +
    + + + +
    @@ -1096,7 +1130,7 @@ shiva:SetObjectID(TEN.Objects.ObjID.BIGMEDI_ITEM)
    - Moveable:GetJointPosition(index) + Moveable:GetJointPosition(index[, offset])
    Get the object's joint position @@ -1109,13 +1143,46 @@ shiva:SetObjectID(TEN.Objects.ObjID.BIGMEDI_ITEM) int of a joint to get position +
  • offset + Vec3 + a pre-rotation offset to the joint + (optional) +
  • Returns:

      Vec3 - a copy of the moveable's position + a copy of the moveable's joint position +
    + + + + +
    +
    + + Moveable:GetJointRotation(index) +
    +
    + Get the object's joint rotation + + + +

    Parameters:

    + + +

    Returns:

    +
      + + Rotation + a calculated copy of the moveable's joint rotation
    @@ -1359,7 +1426,10 @@ shiva:SetObjectID(TEN.Objects.ObjID.BIGMEDI_ITEM)

    Usage:

    Moveable(object, name, position, rotation, roomID, animNumber, frameNumber, hp, OCB, AIBits)Moveable(object, name, position, rotation, roomNumber, animNumber, frameNumber, hp, OCB, AIBits) Used to generate a new moveable dynamically at runtime.
    Moveable:SetEffect(effect, timeout)Moveable:SetEffect(effect[, timeout]) Set effect to moveable
    Moveable:SetCustomEffect(Color1, Color2, timeout)Moveable:SetCustomEffect(Color1, Color2[, timeout]) Set custom colored burn effect to moveable
    Moveable:GetTargetState()Retrieve the index of the target state.
    Moveable:SetState(index) Set the object's state to the one specified by the given index.
    Moveable:GetJointPosition(index)Moveable:GetJointPosition(index[, offset]) Get the object's joint position
    Moveable:GetJointRotation(index)Get the object's joint rotation
    Moveable:GetRotation() Get the moveable's rotation
    - - + + + + + + @@ -121,28 +127,28 @@ - + + + + + - - - - - - + + - - + + - - + +
    Room:GetActive()Determine whether the room is active or notRoom:GetRoomNumber()Get the room's number.
    Room:GetName()Get the room's unique string identifier.
    Room:GetColor()Get the room's reverb type.
    Room:SetReverbType(new)Room:SetName(name)Set the room's unique string identifier.
    Room:SetReverbType(Reverb) Set the room's reverb type.
    Room:GetName()Get the room's unique string identifier.
    Room:SetName(name)Set the room's name (its unique string identifier).Room:SetFlag(flagID, Boolean)Set the room's specified flag.
    Room:GetFlag(flagID) Get the room's specified flag value (true or false).
    Room:SetFlag(flagID, the)Set the room's specified flag value.Room:IsTagPresent(tag)Check if the specified tag is set for the room.
    Room:IsTagPresent(tag)Checks if specified tag is set for this room.Room:GetActive()Check if the room is active.
    @@ -154,11 +160,11 @@
    - - Room:GetActive() + + Room:GetRoomNumber()
    - Determine whether the room is active or not + Get the room's number. () @@ -166,8 +172,29 @@

    Returns:

      - bool - true if the room is active + int + Room number. +
    + + + + +
    +
    + + Room:GetName() +
    +
    + Get the room's unique string identifier. () + + + + +

    Returns:

    +
      + + string + Room name.
    @@ -179,7 +206,7 @@ Room:GetColor()
    - Get the room's ambient light color. + Get the room's ambient light color. () @@ -188,7 +215,7 @@
      Color - ambient light color of the room + Ambient light color.
    @@ -200,7 +227,7 @@ Room:GetReverbType()
    - Get the room's reverb type. + Get the room's reverb type. () @@ -209,50 +236,7 @@
      RoomReverb - room's reverb type -
    - - - - -
    -
    - - Room:SetReverbType(new) -
    -
    - Set the room's reverb type. - - - -

    Parameters:

    - - - - - - -
    -
    - - Room:GetName() -
    -
    - Get the room's unique string identifier. - - - - -

    Returns:

    -
      - - string - the room's name + Reverb type.
    @@ -264,7 +248,7 @@ Room:SetName(name)
    - Set the room's name (its unique string identifier). + Set the room's unique string identifier. () @@ -272,7 +256,55 @@ + + + + + +
    +
    + + Room:SetReverbType(Reverb) +
    +
    + Set the room's reverb type. () + + + +

    Parameters:

    + + + + + + +
    +
    + + Room:SetFlag(flagID, Boolean) +
    +
    + Set the room's specified flag. () + + + +

    Parameters:

    + @@ -286,7 +318,7 @@ Room:GetFlag(flagID)
    - Get the room's specified flag value (true or false). + Get the room's specified flag value (true or false). () @@ -294,39 +326,7 @@ - -

    Returns:

    -
      - - bool - the room's specified flag value -
    - - - - -
    -
    - - Room:SetFlag(flagID, the) -
    -
    - Set the room's specified flag value. - - - -

    Parameters:

    - @@ -340,7 +340,7 @@ Room:IsTagPresent(tag)
    - Checks if specified tag is set for this room. + Check if the specified tag is set for the room. () @@ -348,7 +348,7 @@ @@ -356,7 +356,28 @@
      bool - true if tag is present, false if not + Boolean of the tag's presence. +
    + + + + +
    +
    + + Room:GetActive() +
    +
    + Check if the room is active. () + + + + +

    Returns:

    +
      + + bool + Boolean of the room's active status.
    diff --git a/Documentation/doc/2 classes/Objects.Sink.html b/Documentation/doc/2 classes/Objects.Sink.html index ca807b417..ceb1904e5 100644 --- a/Documentation/doc/2 classes/Objects.Sink.html +++ b/Documentation/doc/2 classes/Objects.Sink.html @@ -3,7 +3,7 @@ - TombEngine 1.4 Lua API + TombEngine 1.5 Lua API @@ -49,10 +49,12 @@
  • Flow.Animations
  • Flow.Fog
  • Flow.InventoryItem
  • +
  • Flow.LensFlare
  • Flow.Level
  • Flow.Mirror
  • Flow.Settings
  • Flow.SkyLayer
  • +
  • Flow.Starfield
  • Objects.AIObject
  • Objects.Camera
  • Objects.LaraObject
  • diff --git a/Documentation/doc/2 classes/Objects.SoundSource.html b/Documentation/doc/2 classes/Objects.SoundSource.html index 24e4f3b7d..78ca5f16c 100644 --- a/Documentation/doc/2 classes/Objects.SoundSource.html +++ b/Documentation/doc/2 classes/Objects.SoundSource.html @@ -3,7 +3,7 @@ - TombEngine 1.4 Lua API + TombEngine 1.5 Lua API @@ -49,10 +49,12 @@
  • Flow.Animations
  • Flow.Fog
  • Flow.InventoryItem
  • +
  • Flow.LensFlare
  • Flow.Level
  • Flow.Mirror
  • Flow.Settings
  • Flow.SkyLayer
  • +
  • Flow.Starfield
  • Objects.AIObject
  • Objects.Camera
  • Objects.LaraObject
  • diff --git a/Documentation/doc/2 classes/Objects.Static.html b/Documentation/doc/2 classes/Objects.Static.html index c91209d0e..362584c3a 100644 --- a/Documentation/doc/2 classes/Objects.Static.html +++ b/Documentation/doc/2 classes/Objects.Static.html @@ -3,7 +3,7 @@ - TombEngine 1.4 Lua API + TombEngine 1.5 Lua API @@ -49,10 +49,12 @@
  • Flow.Animations
  • Flow.Fog
  • Flow.InventoryItem
  • +
  • Flow.LensFlare
  • Flow.Level
  • Flow.Mirror
  • Flow.Settings
  • Flow.SkyLayer
  • +
  • Flow.Starfield
  • Objects.AIObject
  • Objects.Camera
  • Objects.LaraObject
  • diff --git a/Documentation/doc/2 classes/Objects.Volume.html b/Documentation/doc/2 classes/Objects.Volume.html index a23618fc7..3970c11bf 100644 --- a/Documentation/doc/2 classes/Objects.Volume.html +++ b/Documentation/doc/2 classes/Objects.Volume.html @@ -3,7 +3,7 @@ - TombEngine 1.4 Lua API + TombEngine 1.5 Lua API @@ -49,10 +49,12 @@
  • Flow.Animations
  • Flow.Fog
  • Flow.InventoryItem
  • +
  • Flow.LensFlare
  • Flow.Level
  • Flow.Mirror
  • Flow.Settings
  • Flow.SkyLayer
  • +
  • Flow.Starfield
  • Objects.AIObject
  • Objects.Camera
  • Objects.LaraObject
  • diff --git a/Documentation/doc/2 classes/Strings.DisplayString.html b/Documentation/doc/2 classes/Strings.DisplayString.html index eff4085fd..c32acc85d 100644 --- a/Documentation/doc/2 classes/Strings.DisplayString.html +++ b/Documentation/doc/2 classes/Strings.DisplayString.html @@ -3,7 +3,7 @@ - TombEngine 1.4 Lua API + TombEngine 1.5 Lua API @@ -49,10 +49,12 @@
  • Flow.Animations
  • Flow.Fog
  • Flow.InventoryItem
  • +
  • Flow.LensFlare
  • Flow.Level
  • Flow.Mirror
  • Flow.Settings
  • Flow.SkyLayer
  • +
  • Flow.Starfield
  • Objects.AIObject
  • Objects.Camera
  • Objects.LaraObject
  • diff --git a/Documentation/doc/2 classes/View.DisplaySprite.html b/Documentation/doc/2 classes/View.DisplaySprite.html index 4edcad83e..97256f8a6 100644 --- a/Documentation/doc/2 classes/View.DisplaySprite.html +++ b/Documentation/doc/2 classes/View.DisplaySprite.html @@ -3,7 +3,7 @@ - TombEngine 1.4 Lua API + TombEngine 1.5 Lua API @@ -49,10 +49,12 @@
  • Flow.Animations
  • Flow.Fog
  • Flow.InventoryItem
  • +
  • Flow.LensFlare
  • Flow.Level
  • Flow.Mirror
  • Flow.Settings
  • Flow.SkyLayer
  • +
  • Flow.Starfield
  • Objects.AIObject
  • Objects.Camera
  • Objects.LaraObject
  • @@ -178,7 +180,7 @@ DisplaySprite(ID, int, pos, rot, scale[, color])
    - Create a DisplaySprite object. + Create a DisplaySprite object. () diff --git a/Documentation/doc/3 primitive classes/Color.html b/Documentation/doc/3 primitive classes/Color.html index 58efaffe6..7021129ac 100644 --- a/Documentation/doc/3 primitive classes/Color.html +++ b/Documentation/doc/3 primitive classes/Color.html @@ -3,7 +3,7 @@ - TombEngine 1.4 Lua API + TombEngine 1.5 Lua API @@ -49,10 +49,12 @@
  • Flow.Animations
  • Flow.Fog
  • Flow.InventoryItem
  • +
  • Flow.LensFlare
  • Flow.Level
  • Flow.Mirror
  • Flow.Settings
  • Flow.SkyLayer
  • +
  • Flow.Starfield
  • Objects.AIObject
  • Objects.Camera
  • Objects.LaraObject
  • @@ -100,27 +102,27 @@

    Primitive Class Color

    -

    An RGBA or RGB color.

    -

    Components are specified in bytes; all values are clamped to [0, 255].

    +

    Represents an RGBA or RGB color.

    +

    Components are specified in bytes. All values are clamped to the range [0, 255].

    Members

    - + - + - + - +
    r(int) red component(int) Red component.
    g(int) green component(int) Green component.
    b(int) blue component(int) Blue component.
    a(int) alpha component (255 = opaque, 0 = invisible)(int) Alpha component (0 = invisible, 255 = opaque).

    Functions

    @@ -129,12 +131,6 @@ Color(R, G, B) - - - - Color(R, G, B, A) - - @@ -157,7 +153,7 @@ r
    - (int) red component + (int) Red component. @@ -172,7 +168,7 @@ g
    - (int) green component + (int) Green component. @@ -187,7 +183,7 @@ b
    - (int) blue component + (int) Blue component. @@ -202,7 +198,7 @@ a
    - (int) alpha component (255 = opaque, 0 = invisible) + (int) Alpha component (0 = invisible, 255 = opaque). @@ -253,48 +249,6 @@ -
    -
    - - Color(R, G, B, A) -
    -
    - - - - - - -

    Parameters:

    - - -

    Returns:

    -
      - - Color - A new Color object. -
    - - - -
    @@ -311,7 +265,7 @@ @@ -319,7 +273,7 @@
      string - A string showing the r, g, b, and a values of the color + A string representing the r, g, b, and a values of the color.
    diff --git a/Documentation/doc/3 primitive classes/Rotation.html b/Documentation/doc/3 primitive classes/Rotation.html index d3f49a53a..79ee6746c 100644 --- a/Documentation/doc/3 primitive classes/Rotation.html +++ b/Documentation/doc/3 primitive classes/Rotation.html @@ -3,7 +3,7 @@ - TombEngine 1.4 Lua API + TombEngine 1.5 Lua API @@ -49,10 +49,12 @@
  • Flow.Animations
  • Flow.Fog
  • Flow.InventoryItem
  • +
  • Flow.LensFlare
  • Flow.Level
  • Flow.Mirror
  • Flow.Settings
  • Flow.SkyLayer
  • +
  • Flow.Starfield
  • Objects.AIObject
  • Objects.Camera
  • Objects.LaraObject
  • @@ -101,7 +103,7 @@

    Primitive Class Rotation

    Represents a degree-based 3D rotation.

    -

    All values are clamped to the range [0.0, 360.0].

    +

    All values are clamped to the range [0.0, 360.0].

    Members

    diff --git a/Documentation/doc/3 primitive classes/Vec2.html b/Documentation/doc/3 primitive classes/Vec2.html index 30cb5546b..e42f03304 100644 --- a/Documentation/doc/3 primitive classes/Vec2.html +++ b/Documentation/doc/3 primitive classes/Vec2.html @@ -3,7 +3,7 @@ - TombEngine 1.4 Lua API + TombEngine 1.5 Lua API @@ -49,10 +49,12 @@
  • Flow.Animations
  • Flow.Fog
  • Flow.InventoryItem
  • +
  • Flow.LensFlare
  • Flow.Level
  • Flow.Mirror
  • Flow.Settings
  • Flow.SkyLayer
  • +
  • Flow.Starfield
  • Objects.AIObject
  • Objects.Camera
  • Objects.LaraObject
  • diff --git a/Documentation/doc/3 primitive classes/Vec3.html b/Documentation/doc/3 primitive classes/Vec3.html index d5c81d100..9aeb59a86 100644 --- a/Documentation/doc/3 primitive classes/Vec3.html +++ b/Documentation/doc/3 primitive classes/Vec3.html @@ -3,7 +3,7 @@ - TombEngine 1.4 Lua API + TombEngine 1.5 Lua API @@ -49,10 +49,12 @@
  • Flow.Animations
  • Flow.Fog
  • Flow.InventoryItem
  • +
  • Flow.LensFlare
  • Flow.Level
  • Flow.Mirror
  • Flow.Settings
  • Flow.SkyLayer
  • +
  • Flow.Starfield
  • Objects.AIObject
  • Objects.Camera
  • Objects.LaraObject
  • diff --git a/Documentation/doc/4 enums/Effects.BlendID.html b/Documentation/doc/4 enums/Effects.BlendID.html index cbc48754f..d3f8f0c97 100644 --- a/Documentation/doc/4 enums/Effects.BlendID.html +++ b/Documentation/doc/4 enums/Effects.BlendID.html @@ -3,7 +3,7 @@ - TombEngine 1.4 Lua API + TombEngine 1.5 Lua API @@ -49,10 +49,12 @@
  • Flow.Animations
  • Flow.Fog
  • Flow.InventoryItem
  • +
  • Flow.LensFlare
  • Flow.Level
  • Flow.Mirror
  • Flow.Settings
  • Flow.SkyLayer
  • +
  • Flow.Starfield
  • Objects.AIObject
  • Objects.Camera
  • Objects.LaraObject
  • diff --git a/Documentation/doc/4 enums/Effects.EffectID.html b/Documentation/doc/4 enums/Effects.EffectID.html index 585cd7cfa..9ad52e5b6 100644 --- a/Documentation/doc/4 enums/Effects.EffectID.html +++ b/Documentation/doc/4 enums/Effects.EffectID.html @@ -3,7 +3,7 @@ - TombEngine 1.4 Lua API + TombEngine 1.5 Lua API @@ -49,10 +49,12 @@
  • Flow.Animations
  • Flow.Fog
  • Flow.InventoryItem
  • +
  • Flow.LensFlare
  • Flow.Level
  • Flow.Mirror
  • Flow.Settings
  • Flow.SkyLayer
  • +
  • Flow.Starfield
  • Objects.AIObject
  • Objects.Camera
  • Objects.LaraObject
  • diff --git a/Documentation/doc/4 enums/Flow.GameStatus.html b/Documentation/doc/4 enums/Flow.GameStatus.html index d7b9af20a..1575981e4 100644 --- a/Documentation/doc/4 enums/Flow.GameStatus.html +++ b/Documentation/doc/4 enums/Flow.GameStatus.html @@ -3,7 +3,7 @@ - TombEngine 1.4 Lua API + TombEngine 1.5 Lua API @@ -49,10 +49,12 @@
  • Flow.Animations
  • Flow.Fog
  • Flow.InventoryItem
  • +
  • Flow.LensFlare
  • Flow.Level
  • Flow.Mirror
  • Flow.Settings
  • Flow.SkyLayer
  • +
  • Flow.Starfield
  • Objects.AIObject
  • Objects.Camera
  • Objects.LaraObject
  • diff --git a/Documentation/doc/4 enums/Input.ActionID.html b/Documentation/doc/4 enums/Input.ActionID.html index 960659276..1838d08c1 100644 --- a/Documentation/doc/4 enums/Input.ActionID.html +++ b/Documentation/doc/4 enums/Input.ActionID.html @@ -3,7 +3,7 @@ - TombEngine 1.4 Lua API + TombEngine 1.5 Lua API @@ -49,10 +49,12 @@
  • Flow.Animations
  • Flow.Fog
  • Flow.InventoryItem
  • +
  • Flow.LensFlare
  • Flow.Level
  • Flow.Mirror
  • Flow.Settings
  • Flow.SkyLayer
  • +
  • Flow.Starfield
  • Objects.AIObject
  • Objects.Camera
  • Objects.LaraObject
  • diff --git a/Documentation/doc/4 enums/Objects.AmmoType.html b/Documentation/doc/4 enums/Objects.AmmoType.html index c21a2221d..ab6edf13e 100644 --- a/Documentation/doc/4 enums/Objects.AmmoType.html +++ b/Documentation/doc/4 enums/Objects.AmmoType.html @@ -3,7 +3,7 @@ - TombEngine 1.4 Lua API + TombEngine 1.5 Lua API @@ -49,10 +49,12 @@
  • Flow.Animations
  • Flow.Fog
  • Flow.InventoryItem
  • +
  • Flow.LensFlare
  • Flow.Level
  • Flow.Mirror
  • Flow.Settings
  • Flow.SkyLayer
  • +
  • Flow.Starfield
  • Objects.AIObject
  • Objects.Camera
  • Objects.LaraObject
  • diff --git a/Documentation/doc/4 enums/Objects.MoveableStatus.html b/Documentation/doc/4 enums/Objects.MoveableStatus.html index 2407fc1f4..51fef38ca 100644 --- a/Documentation/doc/4 enums/Objects.MoveableStatus.html +++ b/Documentation/doc/4 enums/Objects.MoveableStatus.html @@ -3,7 +3,7 @@ - TombEngine 1.4 Lua API + TombEngine 1.5 Lua API @@ -49,10 +49,12 @@
  • Flow.Animations
  • Flow.Fog
  • Flow.InventoryItem
  • +
  • Flow.LensFlare
  • Flow.Level
  • Flow.Mirror
  • Flow.Settings
  • Flow.SkyLayer
  • +
  • Flow.Starfield
  • Objects.AIObject
  • Objects.Camera
  • Objects.LaraObject
  • diff --git a/Documentation/doc/4 enums/Objects.ObjID.html b/Documentation/doc/4 enums/Objects.ObjID.html index 9372f1014..eef909dc7 100644 --- a/Documentation/doc/4 enums/Objects.ObjID.html +++ b/Documentation/doc/4 enums/Objects.ObjID.html @@ -3,7 +3,7 @@ - TombEngine 1.4 Lua API + TombEngine 1.5 Lua API @@ -49,10 +49,12 @@
  • Flow.Animations
  • Flow.Fog
  • Flow.InventoryItem
  • +
  • Flow.LensFlare
  • Flow.Level
  • Flow.Mirror
  • Flow.Settings
  • Flow.SkyLayer
  • +
  • Flow.Starfield
  • Objects.AIObject
  • Objects.Camera
  • Objects.LaraObject
  • @@ -171,7 +173,8 @@ LARA_PETROL_MESH LARA_DIRT_MESH LARA_CROWBAR_ANIM LARA_TORCH_ANIM -HAIR +HAIR_PRIMARY +HAIR_SECONDARY SNOWMOBILE_LARA_ANIMS SNOWMOBILE QUAD_LARA_ANIMS @@ -357,7 +360,7 @@ WASP_MUTANT SKATEBOARD SKATEBOARD_KID WINSTON -ARMY_WINSTON +SEAL_MUTANT SPRINGBOARD ROLLING_SPINDLE DISK_SHOOTER @@ -943,6 +946,7 @@ MESHSWAP_ROMAN_GOD1 MESHSWAP_ROMAN_GOD2 MESHSWAP_MONKEY_MEDIPACK MESHSWAP_MONKEY_KEY +MESHSWAP_WINSTON_ARMY_OUTFIT ANIMATING1 ANIMATING2 ANIMATING3 @@ -1104,6 +1108,10 @@ HEALTH_BAR_TEXTURE AIR_BAR_TEXTURE DASH_BAR_TEXTURE SFX_BAR_TEXTURE +SPEEDOMETER +CUSTOM_BAR_GRAPHIC +CUSTOM_AMMO_GRAPHIC + PANEL_BORDER PANEL_MIDDLE PANEL_CORNER diff --git a/Documentation/doc/4 enums/Objects.RoomFlagID.html b/Documentation/doc/4 enums/Objects.RoomFlagID.html index bdac34926..f5aec2102 100644 --- a/Documentation/doc/4 enums/Objects.RoomFlagID.html +++ b/Documentation/doc/4 enums/Objects.RoomFlagID.html @@ -3,7 +3,7 @@ - TombEngine 1.4 Lua API + TombEngine 1.5 Lua API @@ -49,10 +49,12 @@
  • Flow.Animations
  • Flow.Fog
  • Flow.InventoryItem
  • +
  • Flow.LensFlare
  • Flow.Level
  • Flow.Mirror
  • Flow.Settings
  • Flow.SkyLayer
  • +
  • Flow.Starfield
  • Objects.AIObject
  • Objects.Camera
  • Objects.LaraObject
  • diff --git a/Documentation/doc/4 enums/Objects.RoomReverb.html b/Documentation/doc/4 enums/Objects.RoomReverb.html index 839bad41c..8af2f63c1 100644 --- a/Documentation/doc/4 enums/Objects.RoomReverb.html +++ b/Documentation/doc/4 enums/Objects.RoomReverb.html @@ -3,7 +3,7 @@ - TombEngine 1.4 Lua API + TombEngine 1.5 Lua API @@ -49,10 +49,12 @@
  • Flow.Animations
  • Flow.Fog
  • Flow.InventoryItem
  • +
  • Flow.LensFlare
  • Flow.Level
  • Flow.Mirror
  • Flow.Settings
  • Flow.SkyLayer
  • +
  • Flow.Starfield
  • Objects.AIObject
  • Objects.Camera
  • Objects.LaraObject
  • diff --git a/Documentation/doc/4 enums/Sound.SoundTrackType.html b/Documentation/doc/4 enums/Sound.SoundTrackType.html index 64c9e8ba1..71391280e 100644 --- a/Documentation/doc/4 enums/Sound.SoundTrackType.html +++ b/Documentation/doc/4 enums/Sound.SoundTrackType.html @@ -3,7 +3,7 @@ - TombEngine 1.4 Lua API + TombEngine 1.5 Lua API @@ -49,10 +49,12 @@
  • Flow.Animations
  • Flow.Fog
  • Flow.InventoryItem
  • +
  • Flow.LensFlare
  • Flow.Level
  • Flow.Mirror
  • Flow.Settings
  • Flow.SkyLayer
  • +
  • Flow.Starfield
  • Objects.AIObject
  • Objects.Camera
  • Objects.LaraObject
  • diff --git a/Documentation/doc/4 enums/Util.LogLevel.html b/Documentation/doc/4 enums/Util.LogLevel.html index 78844e1a5..9a558d2f2 100644 --- a/Documentation/doc/4 enums/Util.LogLevel.html +++ b/Documentation/doc/4 enums/Util.LogLevel.html @@ -3,7 +3,7 @@ - TombEngine 1.4 Lua API + TombEngine 1.5 Lua API @@ -49,10 +49,12 @@
  • Flow.Animations
  • Flow.Fog
  • Flow.InventoryItem
  • +
  • Flow.LensFlare
  • Flow.Level
  • Flow.Mirror
  • Flow.Settings
  • Flow.SkyLayer
  • +
  • Flow.Starfield
  • Objects.AIObject
  • Objects.Camera
  • Objects.LaraObject
  • diff --git a/Documentation/doc/4 enums/View.AlignMode.html b/Documentation/doc/4 enums/View.AlignMode.html index 92542e66b..067a9b16a 100644 --- a/Documentation/doc/4 enums/View.AlignMode.html +++ b/Documentation/doc/4 enums/View.AlignMode.html @@ -3,7 +3,7 @@ - TombEngine 1.4 Lua API + TombEngine 1.5 Lua API @@ -49,10 +49,12 @@
  • Flow.Animations
  • Flow.Fog
  • Flow.InventoryItem
  • +
  • Flow.LensFlare
  • Flow.Level
  • Flow.Mirror
  • Flow.Settings
  • Flow.SkyLayer
  • +
  • Flow.Starfield
  • Objects.AIObject
  • Objects.Camera
  • Objects.LaraObject
  • diff --git a/Documentation/doc/4 enums/View.CameraType.html b/Documentation/doc/4 enums/View.CameraType.html index d2b629311..f46afe46e 100644 --- a/Documentation/doc/4 enums/View.CameraType.html +++ b/Documentation/doc/4 enums/View.CameraType.html @@ -3,7 +3,7 @@ - TombEngine 1.4 Lua API + TombEngine 1.5 Lua API @@ -49,10 +49,12 @@
  • Flow.Animations
  • Flow.Fog
  • Flow.InventoryItem
  • +
  • Flow.LensFlare
  • Flow.Level
  • Flow.Mirror
  • Flow.Settings
  • Flow.SkyLayer
  • +
  • Flow.Starfield
  • Objects.AIObject
  • Objects.Camera
  • Objects.LaraObject
  • diff --git a/Documentation/doc/4 enums/View.PostProcessMode.html b/Documentation/doc/4 enums/View.PostProcessMode.html index 9caf7b0be..ae2a247f1 100644 --- a/Documentation/doc/4 enums/View.PostProcessMode.html +++ b/Documentation/doc/4 enums/View.PostProcessMode.html @@ -3,7 +3,7 @@ - TombEngine 1.4 Lua API + TombEngine 1.5 Lua API @@ -49,10 +49,12 @@
  • Flow.Animations
  • Flow.Fog
  • Flow.InventoryItem
  • +
  • Flow.LensFlare
  • Flow.Level
  • Flow.Mirror
  • Flow.Settings
  • Flow.SkyLayer
  • +
  • Flow.Starfield
  • Objects.AIObject
  • Objects.Camera
  • Objects.LaraObject
  • diff --git a/Documentation/doc/4 enums/View.ScaleMode.html b/Documentation/doc/4 enums/View.ScaleMode.html index 192589739..e51125c98 100644 --- a/Documentation/doc/4 enums/View.ScaleMode.html +++ b/Documentation/doc/4 enums/View.ScaleMode.html @@ -3,7 +3,7 @@ - TombEngine 1.4 Lua API + TombEngine 1.5 Lua API @@ -49,10 +49,12 @@
  • Flow.Animations
  • Flow.Fog
  • Flow.InventoryItem
  • +
  • Flow.LensFlare
  • Flow.Level
  • Flow.Mirror
  • Flow.Settings
  • Flow.SkyLayer
  • +
  • Flow.Starfield
  • Objects.AIObject
  • Objects.Camera
  • Objects.LaraObject
  • diff --git a/Documentation/doc/5 lua utility modules/EventSequence.html b/Documentation/doc/5 lua utility modules/EventSequence.html index 93464f9ff..81b60a585 100644 --- a/Documentation/doc/5 lua utility modules/EventSequence.html +++ b/Documentation/doc/5 lua utility modules/EventSequence.html @@ -3,7 +3,7 @@ - TombEngine 1.4 Lua API + TombEngine 1.5 Lua API @@ -49,10 +49,12 @@
  • Flow.Animations
  • Flow.Fog
  • Flow.InventoryItem
  • +
  • Flow.LensFlare
  • Flow.Level
  • Flow.Mirror
  • Flow.Settings
  • Flow.SkyLayer
  • +
  • Flow.Starfield
  • Objects.AIObject
  • Objects.Camera
  • Objects.LaraObject
  • diff --git a/Documentation/doc/5 lua utility modules/Timer.html b/Documentation/doc/5 lua utility modules/Timer.html index 1a3645b43..482ccacce 100644 --- a/Documentation/doc/5 lua utility modules/Timer.html +++ b/Documentation/doc/5 lua utility modules/Timer.html @@ -3,7 +3,7 @@ - TombEngine 1.4 Lua API + TombEngine 1.5 Lua API @@ -49,10 +49,12 @@
  • Flow.Animations
  • Flow.Fog
  • Flow.InventoryItem
  • +
  • Flow.LensFlare
  • Flow.Level
  • Flow.Mirror
  • Flow.Settings
  • Flow.SkyLayer
  • +
  • Flow.Starfield
  • Objects.AIObject
  • Objects.Camera
  • Objects.LaraObject
  • diff --git a/Documentation/doc/index.html b/Documentation/doc/index.html index 77f425919..814a66e4a 100644 --- a/Documentation/doc/index.html +++ b/Documentation/doc/index.html @@ -3,7 +3,7 @@ - TombEngine 1.4 Lua API + TombEngine 1.5 Lua API @@ -49,10 +49,12 @@
  • Flow.Animations
  • Flow.Fog
  • Flow.InventoryItem
  • +
  • Flow.LensFlare
  • Flow.Level
  • Flow.Mirror
  • Flow.Settings
  • Flow.SkyLayer
  • +
  • Flow.Starfield
  • Objects.AIObject
  • Objects.Camera
  • Objects.LaraObject
  • @@ -100,7 +102,7 @@
    -

    TombEngine 1.4 scripting interface

    +

    TombEngine 1.5 scripting interface

    Welcome to the TombEngine scripting API. This is a work in progress and some information might be wrong or outdated. Please also note that this is primarily a reference document, not a tutorial, so expect descriptions to be fairly sparse.

    At the time of writing, there is a tutorial describing the basics of Lua, as well as a number of example scripts, on the TombEngine website.

    @@ -183,6 +185,10 @@ local door = GetMoveableByName("door_type4_14") Flow.InventoryItem Represents the properties of an object as it appears in the inventory. + + Flow.LensFlare + Represents a lens flare. + Flow.Level Stores level metadata. @@ -199,6 +205,10 @@ local door = GetMoveableByName("door_type4_14") Flow.SkyLayer Describes a layer of moving clouds. + + Flow.Starfield + Represents a starfield. + Objects.AIObject AI object @@ -217,7 +227,7 @@ local door = GetMoveableByName("door_type4_14") Objects.Room - Rooms + Room object. Objects.Sink @@ -244,7 +254,7 @@ local door = GetMoveableByName("door_type4_14") - + diff --git a/README.md b/README.md index fd3834ff5..52dab0c1e 100644 --- a/README.md +++ b/README.md @@ -2,13 +2,12 @@ ![Logo](https://github.com/MontyTRC89/TombEngine/assets/80340234/f22c9ca9-7159-467f-b8ad-7bb32274a278) -In the year 2000, Core Design granted us a great gift: their TR4-based Level Editor, which allowed people to create custom levels. It was, unfortunately, quite limited, hence why over the decades it was upgraded massively with fan patcher projects such as Tomb Raider Engine Patcher (TREP) and Tomb Raider Next Generation (TRNG). - -TombEngine (TEN) is a new, open-source engine which aims to abolish all limits, fix bugs from the original games, introduce new features while refining old ones, and provide for a refined, user-friendly level creation process. Current support includes: +TombEngine (TEN) is an open-source, custom level engine which aims to abolish limits and fix bugs of the classic Tomb Raider games, introduce new features while refining old ones, and provide user-friendly level creation process. Current support includes: - Lua (as the native scripting language) -- All objects from the classic series (1-5) -- Many more exciting gameplay functionalities such as diagonal shimmying and expanded crawlspace flexibility -- An enlarged 2D map, allowing for the creation of massive levels (imagine one big level may previously have been split into five!) +- Many objects from the original series (1-5) +- Support for high framerate, antialiasing, mipmapping and SSAO +- Full diagonal geometry support +- Uncapped map size - A streamlined player control scheme. If you would like to participate in TEN discussion with other TEN devs whether it is contributing, bugs or general discussion, then join this discord server: https://discord.gg/h5tUYFmres @@ -22,19 +21,21 @@ To compile TEN, ensure you have installed: Steps: 1) Clone the repository to your GitHub Desktop -2) Launch TombEngine.sln and compile -3) Once compiled, create a separate folder to serve as your main TEN directory -4) Copy everything inside the Build folder to the main TEN directory -5) Copy the Scripts folder to your main TEN directory -6) Ensure you have the necessary level data and texture files as well -7) In the case Windows warns about missing DLLs, (bass.dll, etc.) copy the missing DLL files found inside the Libs folder to your main TEN directory. +2) Open TombEngine.sln +4) Compile the solution +5) Once compiled, create a separate folder to serve as your main TEN directory (or create test TEN project using TombIDE) +6) Copy everything inside the Build folder to the main TEN directory +7) Ensure you have the necessary level data and texture files as well +8) In the case Windows warns about missing DLLs, (bass.dll, etc.) copy the missing DLL files found inside the Libs folder to your main TEN directory. Visual Studio may also warn about NuGet packages. To fix: 1) Delete the Packages folder 2) Go back to Microsoft Visual Studio 3) Right-click on the TombEngine solution in the Solution Explorer tab and select "Restore NuGet Packages" -4) Compile again and once done, you should be able to compile a level with TombEditor and run it in TEN. +4) If it doesn't help, manually install `directxtk_desktop_2019` and `Microsoft.XAudio2.Redist` packages via NuGet Package Manager + +Once done, you should be able to build a level with TombEditor and run it in TEN. # Disclaimer -We do not and have never worked for Core Design, Eidos Interactive, or Square Enix. This is a hobby project. Tomb Raider is a registered trademark of Square Enix; TombEngine is not be sold. The code is open-source to encourage contributions and to be used for study purposes. We are not responsible for illegal uses of this source code. This source code is released as-is and continues to be maintained by non-paid contributors in their free time. +This is a community project which is not affiliated with Core Design, Eidos Interactive, or Square Enix. Tomb Raider is a registered trademark of Square Enix; TombEngine is not be sold. The code is open-source to encourage contributions and to be used for study purposes. We are not responsible for illegal uses of this source code. This source code is released as-is and continues to be maintained by non-paid contributors in their free time. diff --git a/Scripts/Settings.lua b/Scripts/Settings.lua index 4b7a4c226..f50976766 100644 --- a/Scripts/Settings.lua +++ b/Scripts/Settings.lua @@ -11,7 +11,6 @@ local anims = Flow.Animations.new() anims.crawlExtended = true anims.crouchRoll = true anims.crawlspaceSwandive = true -anims.monkeyAutoJump = false anims.overhangClimb = false anims.slideExtended = false anims.sprintJump = false diff --git a/Scripts/Strings.lua b/Scripts/Strings.lua index a7f6a7678..64b06d34e 100644 --- a/Scripts/Strings.lua +++ b/Scripts/Strings.lua @@ -58,7 +58,7 @@ local strings = -- Level name strings - lara_home = { "Lara's Home" }, + home_level = { "Home Level" }, test_level = { "Test Level" }, title = { "Title" }, } diff --git a/Scripts/SystemStrings.lua b/Scripts/SystemStrings.lua index 2bb0d2e47..fdd9821c2 100644 --- a/Scripts/SystemStrings.lua +++ b/Scripts/SystemStrings.lua @@ -51,7 +51,8 @@ local strings = ammo_used = { "Ammo Used" }, antialiasing = { "Antialiasing" }, apply = { "Apply" }, - automatic_targeting = { "Automatic Targeting" }, + auto_monkey_swing_jump = { "Auto Monkey Jump" }, + auto_targeting = { "Auto Targeting" }, back = { "Back" }, cancel = { "Cancel" }, caustics = { "Underwater Caustics" }, @@ -74,13 +75,17 @@ local strings = exit_to_title = { "Exit to Title" }, general_actions = { "General Actions" }, high = { "High" }, + high_framerate = { "High Framerate" }, level_secrets_found = { "Secrets Found in Level" }, load_game = { "Load Game" }, low = { "Low" }, medium = { "Medium" }, menu_actions = { "Menu Actions" }, + menu_option_looping = { "Menu Option Looping" }, + menu_option_looping_all_menus = { "All Menus" }, + menu_option_looping_disabled = { "Disabled" }, + menu_option_looping_save_load_only = { "Save/Load Only" }, mouse_sensitivity = { "Mouse Sensitivity" }, - mouse_smoothing = { "Mouse Smoothing" }, music_volume = { "Music Volume" }, new_game = { "New Game" }, none = { "None" }, diff --git a/TombEngine/Game/GuiObjects.cpp b/TombEngine/Game/GuiObjects.cpp index 62cccff81..0967e3a00 100644 --- a/TombEngine/Game/GuiObjects.cpp +++ b/TombEngine/Game/GuiObjects.cpp @@ -331,21 +331,21 @@ namespace TEN::Gui void CombineRevolverLasersight(ItemInfo* item, bool flag) { - auto* lara = GetLaraInfo(item); + auto& player = GetLaraInfo(*item); if (flag) { - lara->Inventory.HasLasersight = true; - lara->Weapons[(int)LaraWeaponType::Revolver].HasLasersight = false; + player.Inventory.HasLasersight = true; + player.Weapons[(int)LaraWeaponType::Revolver].HasLasersight = false; } else { - lara->Inventory.HasLasersight = false; - lara->Weapons[(int)LaraWeaponType::Revolver].HasLasersight = true; + player.Inventory.HasLasersight = false; + player.Weapons[(int)LaraWeaponType::Revolver].HasLasersight = true; } - if (lara->Control.HandStatus != HandStatus::Free && - lara->Control.Weapon.GunType == LaraWeaponType::Revolver) + if (player.Control.HandStatus != HandStatus::Free && + player.Control.Weapon.GunType == LaraWeaponType::Revolver) { UndrawPistolMesh(*item, LaraWeaponType::Revolver, true); DrawPistolMeshes(*item, LaraWeaponType::Revolver); @@ -354,21 +354,21 @@ namespace TEN::Gui void CombineCrossbowLasersight(ItemInfo* item, bool flag) { - auto* lara = GetLaraInfo(item); + auto& player = GetLaraInfo(*item); if (flag) { - lara->Inventory.HasLasersight = true; - lara->Weapons[(int)LaraWeaponType::Crossbow].HasLasersight = false; + player.Inventory.HasLasersight = true; + player.Weapons[(int)LaraWeaponType::Crossbow].HasLasersight = false; } else { - lara->Inventory.HasLasersight = false; - lara->Weapons[(int)LaraWeaponType::Crossbow].HasLasersight = true; + player.Inventory.HasLasersight = false; + player.Weapons[(int)LaraWeaponType::Crossbow].HasLasersight = true; } - if (lara->Control.HandStatus != HandStatus::Free && - lara->Control.Weapon.GunType == LaraWeaponType::Crossbow) + if (player.Control.HandStatus != HandStatus::Free && + player.Control.Weapon.GunType == LaraWeaponType::Crossbow) { UndrawShotgunMeshes(*item, LaraWeaponType::Crossbow); DrawShotgunMeshes(*item, LaraWeaponType::Crossbow); @@ -377,21 +377,21 @@ namespace TEN::Gui void CombineHKLasersight(ItemInfo* item, bool flag) { - auto* lara = GetLaraInfo(item); + auto& player = GetLaraInfo(*item); if (flag) { - lara->Inventory.HasLasersight = true; - lara->Weapons[(int)LaraWeaponType::HK].HasLasersight = false; + player.Inventory.HasLasersight = true; + player.Weapons[(int)LaraWeaponType::HK].HasLasersight = false; } else { - lara->Inventory.HasLasersight = false; - lara->Weapons[(int)LaraWeaponType::HK].HasLasersight = true; + player.Inventory.HasLasersight = false; + player.Weapons[(int)LaraWeaponType::HK].HasLasersight = true; } - if (lara->Control.HandStatus != HandStatus::Free && - lara->Control.Weapon.GunType == LaraWeaponType::HK) + if (player.Control.HandStatus != HandStatus::Free && + player.Control.Weapon.GunType == LaraWeaponType::HK) { UndrawShotgunMeshes(*item, LaraWeaponType::HK); DrawShotgunMeshes(*item, LaraWeaponType::HK); @@ -400,514 +400,514 @@ namespace TEN::Gui void CombinePuzzleItem1(ItemInfo* item, bool flag) { - auto* lara = GetLaraInfo(item); + auto& player = GetLaraInfo(*item); - lara->Inventory.PuzzlesCombo[0] = false; - lara->Inventory.PuzzlesCombo[1] = false; - lara->Inventory.Puzzles[0] = true; + player.Inventory.PuzzlesCombo[0] = false; + player.Inventory.PuzzlesCombo[1] = false; + player.Inventory.Puzzles[0] = true; } void CombinePuzzleItem2(ItemInfo* item, bool flag) { - auto* lara = GetLaraInfo(item); + auto& player = GetLaraInfo(*item); - lara->Inventory.PuzzlesCombo[2] = false; - lara->Inventory.PuzzlesCombo[3] = false; - lara->Inventory.Puzzles[1] = true; + player.Inventory.PuzzlesCombo[2] = false; + player.Inventory.PuzzlesCombo[3] = false; + player.Inventory.Puzzles[1] = true; } void CombinePuzzleItem3(ItemInfo* item, bool flag) { - auto* lara = GetLaraInfo(item); + auto& player = GetLaraInfo(*item); - lara->Inventory.PuzzlesCombo[4] = false; - lara->Inventory.PuzzlesCombo[5] = false; - lara->Inventory.Puzzles[2] = true; + player.Inventory.PuzzlesCombo[4] = false; + player.Inventory.PuzzlesCombo[5] = false; + player.Inventory.Puzzles[2] = true; } void CombinePuzzleItem4(ItemInfo* item, bool flag) { - auto* lara = GetLaraInfo(item); + auto& player = GetLaraInfo(*item); - lara->Inventory.PuzzlesCombo[6] = false; - lara->Inventory.PuzzlesCombo[7] = false; - lara->Inventory.Puzzles[3] = true; + player.Inventory.PuzzlesCombo[6] = false; + player.Inventory.PuzzlesCombo[7] = false; + player.Inventory.Puzzles[3] = true; } void CombinePuzzleItem5(ItemInfo* item, bool flag) { - auto* lara = GetLaraInfo(item); + auto& player = GetLaraInfo(*item); - lara->Inventory.PuzzlesCombo[8] = false; - lara->Inventory.PuzzlesCombo[9] = false; - lara->Inventory.Puzzles[4] = true; + player.Inventory.PuzzlesCombo[8] = false; + player.Inventory.PuzzlesCombo[9] = false; + player.Inventory.Puzzles[4] = true; } void CombinePuzzleItem6(ItemInfo* item, bool flag) { - auto* lara = GetLaraInfo(item); + auto& player = GetLaraInfo(*item); - lara->Inventory.PuzzlesCombo[10] = false; - lara->Inventory.PuzzlesCombo[11] = false; - lara->Inventory.Puzzles[5] = true; + player.Inventory.PuzzlesCombo[10] = false; + player.Inventory.PuzzlesCombo[11] = false; + player.Inventory.Puzzles[5] = true; } void CombinePuzzleItem7(ItemInfo* item, bool flag) { - auto* lara = GetLaraInfo(item); + auto& player = GetLaraInfo(*item); - lara->Inventory.PuzzlesCombo[12] = false; - lara->Inventory.PuzzlesCombo[13] = false; - lara->Inventory.Puzzles[6] = true; + player.Inventory.PuzzlesCombo[12] = false; + player.Inventory.PuzzlesCombo[13] = false; + player.Inventory.Puzzles[6] = true; } void CombinePuzzleItem8(ItemInfo* item, bool flag) { - auto* lara = GetLaraInfo(item); + auto& player = GetLaraInfo(*item); - lara->Inventory.PuzzlesCombo[14] = false; - lara->Inventory.PuzzlesCombo[15] = false; - lara->Inventory.Puzzles[7] = true; + player.Inventory.PuzzlesCombo[14] = false; + player.Inventory.PuzzlesCombo[15] = false; + player.Inventory.Puzzles[7] = true; } void CombinePuzzleItem9(ItemInfo* item, bool flag) { - auto* lara = GetLaraInfo(item); + auto& player = GetLaraInfo(*item); - lara->Inventory.PuzzlesCombo[16] = false; - lara->Inventory.PuzzlesCombo[17] = false; - lara->Inventory.Puzzles[8] = true; + player.Inventory.PuzzlesCombo[16] = false; + player.Inventory.PuzzlesCombo[17] = false; + player.Inventory.Puzzles[8] = true; } void CombinePuzzleItem10(ItemInfo* item, bool flag) { - auto* lara = GetLaraInfo(item); + auto& player = GetLaraInfo(*item); - lara->Inventory.PuzzlesCombo[18] = false; - lara->Inventory.PuzzlesCombo[19] = false; - lara->Inventory.Puzzles[9] = true; + player.Inventory.PuzzlesCombo[18] = false; + player.Inventory.PuzzlesCombo[19] = false; + player.Inventory.Puzzles[9] = true; } void CombinePuzzleItem11(ItemInfo* item, bool flag) { - auto* lara = GetLaraInfo(item); + auto& player = GetLaraInfo(*item); - lara->Inventory.PuzzlesCombo[20] = false; - lara->Inventory.PuzzlesCombo[21] = false; - lara->Inventory.Puzzles[10] = true; + player.Inventory.PuzzlesCombo[20] = false; + player.Inventory.PuzzlesCombo[21] = false; + player.Inventory.Puzzles[10] = true; } void CombinePuzzleItem12(ItemInfo* item, bool flag) { - auto* lara = GetLaraInfo(item); + auto& player = GetLaraInfo(*item); - lara->Inventory.PuzzlesCombo[22] = false; - lara->Inventory.PuzzlesCombo[23] = false; - lara->Inventory.Puzzles[11] = true; + player.Inventory.PuzzlesCombo[22] = false; + player.Inventory.PuzzlesCombo[23] = false; + player.Inventory.Puzzles[11] = true; } void CombinePuzzleItem13(ItemInfo* item, bool flag) { - auto* lara = GetLaraInfo(item); + auto& player = GetLaraInfo(*item); - lara->Inventory.PuzzlesCombo[24] = false; - lara->Inventory.PuzzlesCombo[25] = false; - lara->Inventory.Puzzles[12] = true; + player.Inventory.PuzzlesCombo[24] = false; + player.Inventory.PuzzlesCombo[25] = false; + player.Inventory.Puzzles[12] = true; } void CombinePuzzleItem14(ItemInfo* item, bool flag) { - auto* lara = GetLaraInfo(item); + auto& player = GetLaraInfo(*item); - lara->Inventory.PuzzlesCombo[26] = false; - lara->Inventory.PuzzlesCombo[27] = false; - lara->Inventory.Puzzles[13] = true; + player.Inventory.PuzzlesCombo[26] = false; + player.Inventory.PuzzlesCombo[27] = false; + player.Inventory.Puzzles[13] = true; } void CombinePuzzleItem15(ItemInfo* item, bool flag) { - auto* lara = GetLaraInfo(item); + auto& player = GetLaraInfo(*item); - lara->Inventory.PuzzlesCombo[28] = false; - lara->Inventory.PuzzlesCombo[29] = false; - lara->Inventory.Puzzles[14] = true; + player.Inventory.PuzzlesCombo[28] = false; + player.Inventory.PuzzlesCombo[29] = false; + player.Inventory.Puzzles[14] = true; } void CombinePuzzleItem16(ItemInfo* item, bool flag) { - auto* lara = GetLaraInfo(item); + auto& player = GetLaraInfo(*item); - lara->Inventory.PuzzlesCombo[30] = false; - lara->Inventory.PuzzlesCombo[31] = false; - lara->Inventory.Puzzles[15] = true; + player.Inventory.PuzzlesCombo[30] = false; + player.Inventory.PuzzlesCombo[31] = false; + player.Inventory.Puzzles[15] = true; } void CombineKeyItem1(ItemInfo* item, bool flag) { - auto* lara = GetLaraInfo(item); + auto& player = GetLaraInfo(*item); - lara->Inventory.Keys[0] = true; - lara->Inventory.KeysCombo[0] = false; - lara->Inventory.KeysCombo[1] = false; + player.Inventory.Keys[0] = true; + player.Inventory.KeysCombo[0] = false; + player.Inventory.KeysCombo[1] = false; } void CombineKeyItem2(ItemInfo* item, bool flag) { - auto* lara = GetLaraInfo(item); + auto& player = GetLaraInfo(*item); - lara->Inventory.Keys[1] = true; - lara->Inventory.KeysCombo[2] = false; - lara->Inventory.KeysCombo[3] = false; + player.Inventory.Keys[1] = true; + player.Inventory.KeysCombo[2] = false; + player.Inventory.KeysCombo[3] = false; } void CombineKeyItem3(ItemInfo* item, bool flag) { - auto* lara = GetLaraInfo(item); + auto& player = GetLaraInfo(*item); - lara->Inventory.Keys[2] = true; - lara->Inventory.KeysCombo[4] = false; - lara->Inventory.KeysCombo[5] = false; + player.Inventory.Keys[2] = true; + player.Inventory.KeysCombo[4] = false; + player.Inventory.KeysCombo[5] = false; } void CombineKeyItem4(ItemInfo* item, bool flag) { - auto* lara = GetLaraInfo(item); + auto& player = GetLaraInfo(*item); - lara->Inventory.Keys[3] = true; - lara->Inventory.KeysCombo[6] = false; - lara->Inventory.KeysCombo[7] = false; + player.Inventory.Keys[3] = true; + player.Inventory.KeysCombo[6] = false; + player.Inventory.KeysCombo[7] = false; } void CombineKeyItem5(ItemInfo* item, bool flag) { - auto* lara = GetLaraInfo(item); + auto& player = GetLaraInfo(*item); - lara->Inventory.Keys[4] = true; - lara->Inventory.KeysCombo[8] = false; - lara->Inventory.KeysCombo[9] = false; + player.Inventory.Keys[4] = true; + player.Inventory.KeysCombo[8] = false; + player.Inventory.KeysCombo[9] = false; } void CombineKeyItem6(ItemInfo* item, bool flag) { - auto* lara = GetLaraInfo(item); + auto& player = GetLaraInfo(*item); - lara->Inventory.Keys[5] = true; - lara->Inventory.KeysCombo[10] = false; - lara->Inventory.KeysCombo[11] = false; + player.Inventory.Keys[5] = true; + player.Inventory.KeysCombo[10] = false; + player.Inventory.KeysCombo[11] = false; } void CombineKeyItem7(ItemInfo* item, bool flag) { - auto* lara = GetLaraInfo(item); + auto& player = GetLaraInfo(*item); - lara->Inventory.Keys[6] = true; - lara->Inventory.KeysCombo[12] = false; - lara->Inventory.KeysCombo[13] = false; + player.Inventory.Keys[6] = true; + player.Inventory.KeysCombo[12] = false; + player.Inventory.KeysCombo[13] = false; } void CombineKeyItem8(ItemInfo* item, bool flag) { - auto* lara = GetLaraInfo(item); + auto& player = GetLaraInfo(*item); - lara->Inventory.Keys[7] = true; - lara->Inventory.KeysCombo[14] = false; - lara->Inventory.KeysCombo[15] = false; + player.Inventory.Keys[7] = true; + player.Inventory.KeysCombo[14] = false; + player.Inventory.KeysCombo[15] = false; } void CombineKeyItem9(ItemInfo* item, bool flag) { - auto* lara = GetLaraInfo(item); + auto& player = GetLaraInfo(*item); - lara->Inventory.Keys[8] = true; - lara->Inventory.KeysCombo[16] = false; - lara->Inventory.KeysCombo[17] = false; + player.Inventory.Keys[8] = true; + player.Inventory.KeysCombo[16] = false; + player.Inventory.KeysCombo[17] = false; } void CombineKeyItem10(ItemInfo* item, bool flag) { - auto* lara = GetLaraInfo(item); + auto& player = GetLaraInfo(*item); - lara->Inventory.Keys[9] = true; - lara->Inventory.KeysCombo[18] = false; - lara->Inventory.KeysCombo[19] = false; + player.Inventory.Keys[9] = true; + player.Inventory.KeysCombo[18] = false; + player.Inventory.KeysCombo[19] = false; } void CombineKeyItem11(ItemInfo* item, bool flag) { - auto* lara = GetLaraInfo(item); + auto& player = GetLaraInfo(*item); - lara->Inventory.Keys[10] = true; - lara->Inventory.KeysCombo[20] = false; - lara->Inventory.KeysCombo[21] = false; + player.Inventory.Keys[10] = true; + player.Inventory.KeysCombo[20] = false; + player.Inventory.KeysCombo[21] = false; } void CombineKeyItem12(ItemInfo* item, bool flag) { - auto* lara = GetLaraInfo(item); + auto& player = GetLaraInfo(*item); - lara->Inventory.Keys[11] = true; - lara->Inventory.KeysCombo[22] = false; - lara->Inventory.KeysCombo[23] = false; + player.Inventory.Keys[11] = true; + player.Inventory.KeysCombo[22] = false; + player.Inventory.KeysCombo[23] = false; } void CombineKeyItem13(ItemInfo* item, bool flag) { - auto* lara = GetLaraInfo(item); + auto& player = GetLaraInfo(*item); - lara->Inventory.Keys[12] = true; - lara->Inventory.KeysCombo[24] = false; - lara->Inventory.KeysCombo[25] = false; + player.Inventory.Keys[12] = true; + player.Inventory.KeysCombo[24] = false; + player.Inventory.KeysCombo[25] = false; } void CombineKeyItem14(ItemInfo* item, bool flag) { - auto* lara = GetLaraInfo(item); + auto& player = GetLaraInfo(*item); - lara->Inventory.Keys[13] = true; - lara->Inventory.KeysCombo[26] = false; - lara->Inventory.KeysCombo[27] = false; + player.Inventory.Keys[13] = true; + player.Inventory.KeysCombo[26] = false; + player.Inventory.KeysCombo[27] = false; } void CombineKeyItem15(ItemInfo* item, bool flag) { - auto* lara = GetLaraInfo(item); + auto& player = GetLaraInfo(*item); - lara->Inventory.Keys[14] = true; - lara->Inventory.KeysCombo[28] = false; - lara->Inventory.KeysCombo[29] = false; + player.Inventory.Keys[14] = true; + player.Inventory.KeysCombo[28] = false; + player.Inventory.KeysCombo[29] = false; } void CombineKeyItem16(ItemInfo* item, bool flag) { - auto* lara = GetLaraInfo(item); + auto& player = GetLaraInfo(*item); - lara->Inventory.Keys[15] = true; - lara->Inventory.KeysCombo[30] = false; - lara->Inventory.KeysCombo[31] = false; + player.Inventory.Keys[15] = true; + player.Inventory.KeysCombo[30] = false; + player.Inventory.KeysCombo[31] = false; } void CombinePickupItem1(ItemInfo* item, bool flag) { - auto* lara = GetLaraInfo(item); + auto& player = GetLaraInfo(*item); - lara->Inventory.Pickups[0] = true; - lara->Inventory.PickupsCombo[0] = false; - lara->Inventory.PickupsCombo[1] = false; + player.Inventory.Pickups[0] = true; + player.Inventory.PickupsCombo[0] = false; + player.Inventory.PickupsCombo[1] = false; } void CombinePickupItem2(ItemInfo* item, bool flag) { - auto* lara = GetLaraInfo(item); + auto& player = GetLaraInfo(*item); - lara->Inventory.Pickups[1] = true; - lara->Inventory.PickupsCombo[2] = false; - lara->Inventory.PickupsCombo[3] = false; + player.Inventory.Pickups[1] = true; + player.Inventory.PickupsCombo[2] = false; + player.Inventory.PickupsCombo[3] = false; } void CombinePickupItem3(ItemInfo* item, bool flag) { - auto* lara = GetLaraInfo(item); + auto& player = GetLaraInfo(*item); - lara->Inventory.Pickups[2] = true; - lara->Inventory.PickupsCombo[4] = false; - lara->Inventory.PickupsCombo[5] = false; + player.Inventory.Pickups[2] = true; + player.Inventory.PickupsCombo[4] = false; + player.Inventory.PickupsCombo[5] = false; } void CombinePickupItem4(ItemInfo* item, bool flag) { - auto* lara = GetLaraInfo(item); + auto& player = GetLaraInfo(*item); - lara->Inventory.Pickups[3] = true; - lara->Inventory.PickupsCombo[6] = false; - lara->Inventory.PickupsCombo[7] = false; + player.Inventory.Pickups[3] = true; + player.Inventory.PickupsCombo[6] = false; + player.Inventory.PickupsCombo[7] = false; } void CombinePickupItem5(ItemInfo* item, bool flag) { - auto* lara = GetLaraInfo(item); + auto& player = GetLaraInfo(*item); - lara->Inventory.Pickups[4] = true; - lara->Inventory.PickupsCombo[8] = false; - lara->Inventory.PickupsCombo[9] = false; + player.Inventory.Pickups[4] = true; + player.Inventory.PickupsCombo[8] = false; + player.Inventory.PickupsCombo[9] = false; } void CombinePickupItem6(ItemInfo* item, bool flag) { - auto* lara = GetLaraInfo(item); + auto& player = GetLaraInfo(*item); - lara->Inventory.Pickups[5] = true; - lara->Inventory.PickupsCombo[10] = false; - lara->Inventory.PickupsCombo[11] = false; + player.Inventory.Pickups[5] = true; + player.Inventory.PickupsCombo[10] = false; + player.Inventory.PickupsCombo[11] = false; } void CombinePickupItem7(ItemInfo* item, bool flag) { - auto* lara = GetLaraInfo(item); + auto& player = GetLaraInfo(*item); - lara->Inventory.Pickups[6] = true; - lara->Inventory.PickupsCombo[12] = false; - lara->Inventory.PickupsCombo[13] = false; + player.Inventory.Pickups[6] = true; + player.Inventory.PickupsCombo[12] = false; + player.Inventory.PickupsCombo[13] = false; } void CombinePickupItem8(ItemInfo* item, bool flag) { - auto* lara = GetLaraInfo(item); + auto& player = GetLaraInfo(*item); - lara->Inventory.Pickups[7] = true; - lara->Inventory.PickupsCombo[14] = false; - lara->Inventory.PickupsCombo[15] = false; + player.Inventory.Pickups[7] = true; + player.Inventory.PickupsCombo[14] = false; + player.Inventory.PickupsCombo[15] = false; } void CombinePickupItem9(ItemInfo* item, bool flag) { - auto* lara = GetLaraInfo(item); + auto& player = GetLaraInfo(*item); - lara->Inventory.Pickups[8] = true; - lara->Inventory.PickupsCombo[16] = false; - lara->Inventory.PickupsCombo[17] = false; + player.Inventory.Pickups[8] = true; + player.Inventory.PickupsCombo[16] = false; + player.Inventory.PickupsCombo[17] = false; } void CombinePickupItem10(ItemInfo* item, bool flag) { - auto* lara = GetLaraInfo(item); + auto& player = GetLaraInfo(*item); - lara->Inventory.Pickups[9] = true; - lara->Inventory.PickupsCombo[18] = false; - lara->Inventory.PickupsCombo[19] = false; + player.Inventory.Pickups[9] = true; + player.Inventory.PickupsCombo[18] = false; + player.Inventory.PickupsCombo[19] = false; } void CombinePickupItem11(ItemInfo* item, bool flag) { - auto* lara = GetLaraInfo(item); + auto& player = GetLaraInfo(*item); - lara->Inventory.Pickups[10] = true; - lara->Inventory.PickupsCombo[20] = false; - lara->Inventory.PickupsCombo[21] = false; + player.Inventory.Pickups[10] = true; + player.Inventory.PickupsCombo[20] = false; + player.Inventory.PickupsCombo[21] = false; } void CombinePickupItem12(ItemInfo* item, bool flag) { - auto* lara = GetLaraInfo(item); + auto& player = GetLaraInfo(*item); - lara->Inventory.Pickups[11] = true; - lara->Inventory.PickupsCombo[22] = false; - lara->Inventory.PickupsCombo[23] = false; + player.Inventory.Pickups[11] = true; + player.Inventory.PickupsCombo[22] = false; + player.Inventory.PickupsCombo[23] = false; } void CombinePickupItem13(ItemInfo* item, bool flag) { - auto* lara = GetLaraInfo(item); + auto& player = GetLaraInfo(*item); - lara->Inventory.Pickups[12] = true; - lara->Inventory.PickupsCombo[24] = false; - lara->Inventory.PickupsCombo[25] = false; + player.Inventory.Pickups[12] = true; + player.Inventory.PickupsCombo[24] = false; + player.Inventory.PickupsCombo[25] = false; } void CombinePickupItem14(ItemInfo* item, bool flag) { - auto* lara = GetLaraInfo(item); + auto& player = GetLaraInfo(*item); - lara->Inventory.Pickups[13] = true; - lara->Inventory.PickupsCombo[26] = false; - lara->Inventory.PickupsCombo[27] = false; + player.Inventory.Pickups[13] = true; + player.Inventory.PickupsCombo[26] = false; + player.Inventory.PickupsCombo[27] = false; } void CombinePickupItem15(ItemInfo* item, bool flag) { - auto* lara = GetLaraInfo(item); + auto& player = GetLaraInfo(*item); - lara->Inventory.Pickups[14] = true; - lara->Inventory.PickupsCombo[28] = false; - lara->Inventory.PickupsCombo[29] = false; + player.Inventory.Pickups[14] = true; + player.Inventory.PickupsCombo[28] = false; + player.Inventory.PickupsCombo[29] = false; } void CombinePickupItem16(ItemInfo* item, bool flag) { - auto* lara = GetLaraInfo(item); + auto& player = GetLaraInfo(*item); - lara->Inventory.Pickups[15] = true; - lara->Inventory.PickupsCombo[30] = false; - lara->Inventory.PickupsCombo[31] = false; + player.Inventory.Pickups[15] = true; + player.Inventory.PickupsCombo[30] = false; + player.Inventory.PickupsCombo[31] = false; } void CombineExamine1(ItemInfo* item, bool flag) { - auto* lara = GetLaraInfo(item); + auto& player = GetLaraInfo(*item); - lara->Inventory.Examines[0] = true; - lara->Inventory.ExaminesCombo[0] = false; - lara->Inventory.ExaminesCombo[1] = false; + player.Inventory.Examines[0] = true; + player.Inventory.ExaminesCombo[0] = false; + player.Inventory.ExaminesCombo[1] = false; } void CombineExamine2(ItemInfo* item, bool flag) { - auto* lara = GetLaraInfo(item); + auto& player = GetLaraInfo(*item); - lara->Inventory.Examines[1] = true; - lara->Inventory.ExaminesCombo[2] = false; - lara->Inventory.ExaminesCombo[3] = false; + player.Inventory.Examines[1] = true; + player.Inventory.ExaminesCombo[2] = false; + player.Inventory.ExaminesCombo[3] = false; } void CombineExamine3(ItemInfo* item, bool flag) { - auto* lara = GetLaraInfo(item); + auto& player = GetLaraInfo(*item); - lara->Inventory.Examines[2] = true; - lara->Inventory.ExaminesCombo[4] = false; - lara->Inventory.ExaminesCombo[5] = false; + player.Inventory.Examines[2] = true; + player.Inventory.ExaminesCombo[4] = false; + player.Inventory.ExaminesCombo[5] = false; } void CombineExamine4(ItemInfo* item, bool flag) { - auto* lara = GetLaraInfo(item); + auto& player = GetLaraInfo(*item); - lara->Inventory.Examines[3] = true; - lara->Inventory.ExaminesCombo[6] = false; - lara->Inventory.ExaminesCombo[7] = false; + player.Inventory.Examines[3] = true; + player.Inventory.ExaminesCombo[6] = false; + player.Inventory.ExaminesCombo[7] = false; } void CombineExamine5(ItemInfo* item, bool flag) { - auto* lara = GetLaraInfo(item); + auto& player = GetLaraInfo(*item); - lara->Inventory.Examines[4] = true; - lara->Inventory.ExaminesCombo[8] = false; - lara->Inventory.ExaminesCombo[9] = false; + player.Inventory.Examines[4] = true; + player.Inventory.ExaminesCombo[8] = false; + player.Inventory.ExaminesCombo[9] = false; } void CombineExamine6(ItemInfo* item, bool flag) { - auto* lara = GetLaraInfo(item); + auto& player = GetLaraInfo(*item); - lara->Inventory.Examines[5] = true; - lara->Inventory.ExaminesCombo[10] = false; - lara->Inventory.ExaminesCombo[11] = false; + player.Inventory.Examines[5] = true; + player.Inventory.ExaminesCombo[10] = false; + player.Inventory.ExaminesCombo[11] = false; } void CombineExamine7(ItemInfo* item, bool flag) { - auto* lara = GetLaraInfo(item); + auto& player = GetLaraInfo(*item); - lara->Inventory.Examines[6] = true; - lara->Inventory.ExaminesCombo[12] = false; - lara->Inventory.ExaminesCombo[13] = false; + player.Inventory.Examines[6] = true; + player.Inventory.ExaminesCombo[12] = false; + player.Inventory.ExaminesCombo[13] = false; } void CombineExamine8(ItemInfo* item, bool flag) { - auto* lara = GetLaraInfo(item); + auto& player = GetLaraInfo(*item); - lara->Inventory.Examines[7] = true; - lara->Inventory.ExaminesCombo[14] = false; - lara->Inventory.ExaminesCombo[15] = false; + player.Inventory.Examines[7] = true; + player.Inventory.ExaminesCombo[14] = false; + player.Inventory.ExaminesCombo[15] = false; } void CombineClockWorkBeetle(ItemInfo* item, bool flag) { - auto* lara = GetLaraInfo(item); + auto& player = GetLaraInfo(*item); - lara->Inventory.BeetleComponents |= BEETLECOMP_FLAG_BEETLE; // Get beetle. - lara->Inventory.BeetleComponents &= BEETLECOMP_FLAG_COMBO_1; // Remove combo 1. - lara->Inventory.BeetleComponents &= BEETLECOMP_FLAG_COMBO_2; // Remove combo 2. + player.Inventory.BeetleComponents |= BEETLECOMP_FLAG_BEETLE; // Get beetle. + player.Inventory.BeetleComponents &= BEETLECOMP_FLAG_COMBO_1; // Remove combo 1. + player.Inventory.BeetleComponents &= BEETLECOMP_FLAG_COMBO_2; // Remove combo 2. } } diff --git a/TombEngine/Game/Hud/PickupSummary.cpp b/TombEngine/Game/Hud/PickupSummary.cpp index bc816aafc..4376cf80b 100644 --- a/TombEngine/Game/Hud/PickupSummary.cpp +++ b/TombEngine/Game/Hud/PickupSummary.cpp @@ -2,6 +2,8 @@ #include "Game/Hud/PickupSummary.h" #include "Game/pickup/pickup.h" +#include "Game/pickup/pickup_ammo.h" +#include "Game/pickup/pickup_consumable.h" #include "Math/Math.h" #include "Renderer/Renderer.h" #include "Specific/clock.h" @@ -39,6 +41,8 @@ namespace TEN::Hud constexpr auto ROT_RATE = ANGLE(360.0f / (LIFE_MAX * FPS)); constexpr auto ROT = EulerAngles(0, ROT_RATE, 0); + StoreInterpolationData(); + // Move offscreen. if (Life <= 0.0f && isHead) { @@ -122,6 +126,21 @@ namespace TEN::Hud pickup.StringScalar = 0.0f; } + void PickupSummaryController::AddDisplayPickup(const ItemInfo& item) + { + // NOTE: Ammo and consumables are a special case, as internal amount differs from pickup amount. + int ammoCount = GetDefaultAmmoCount(item.ObjectNumber); + int consumableCount = GetDefaultConsumableCount(item.ObjectNumber); + + int count = DISPLAY_PICKUP_COUNT_ARG_DEFAULT; + if (ammoCount != NO_VALUE) + count = ammoCount; + if (consumableCount != NO_VALUE) + count = consumableCount; + + AddDisplayPickup(item.ObjectNumber, item.Pose.Position.ToVector3(), (item.HitPoints > 0) ? item.HitPoints : count); + } + void PickupSummaryController::AddDisplayPickup(GAME_OBJECT_ID objectID, const Vector3& pos, unsigned int count) { // Project 3D position to 2D origin. @@ -196,7 +215,7 @@ namespace TEN::Hud DisplayPickup& PickupSummaryController::GetNewDisplayPickup() { - assertion(_displayPickups.size() <= DISPLAY_PICKUP_COUNT_MAX, "Display pickup overflow."); + TENAssert(_displayPickups.size() <= DISPLAY_PICKUP_COUNT_MAX, "Display pickup overflow."); // Add and return new display pickup. if (_displayPickups.size() < DISPLAY_PICKUP_COUNT_MAX) @@ -213,12 +232,15 @@ namespace TEN::Hud _displayPickups.erase( std::remove_if( _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()); } void PickupSummaryController::DrawDebug() const { - g_Renderer.PrintDebugMessage("Display pickups in summary: %d", _displayPickups.size()); + PrintDebugMessage("Display pickups in summary: %d", _displayPickups.size()); } } diff --git a/TombEngine/Game/Hud/PickupSummary.h b/TombEngine/Game/Hud/PickupSummary.h index a1c0b012f..b79898a8d 100644 --- a/TombEngine/Game/Hud/PickupSummary.h +++ b/TombEngine/Game/Hud/PickupSummary.h @@ -25,24 +25,41 @@ namespace TEN::Hud float StringScale = 0.0f; float StringScalar = 0.0f; + Vector2 PrevPosition = Vector2::Zero; + EulerAngles PrevOrientation = EulerAngles::Identity; + float PrevScale = 0.0f; + float PrevOpacity = 0.0f; + bool IsOffscreen() const; void Update(bool isHead); + + void StoreInterpolationData() + { + PrevPosition = Position; + PrevOrientation = Orientation; + PrevScale = Scale; + PrevOpacity = Opacity; + } }; class PickupSummaryController { private: // Constants + static constexpr auto DISPLAY_PICKUP_COUNT_MAX = 64; static constexpr auto DISPLAY_PICKUP_COUNT_ARG_DEFAULT = 1; // Members + std::vector _displayPickups = {}; public: // Utilities + void AddDisplayPickup(GAME_OBJECT_ID objectID, const Vector2& origin, unsigned int count = DISPLAY_PICKUP_COUNT_ARG_DEFAULT); void AddDisplayPickup(GAME_OBJECT_ID objectID, const Vector3& pos, unsigned int count = DISPLAY_PICKUP_COUNT_ARG_DEFAULT); + void AddDisplayPickup(const ItemInfo& item); void Update(); void Draw() const; @@ -50,6 +67,7 @@ namespace TEN::Hud private: // Helpers + std::vector GetStackPositions() const; DisplayPickup& GetNewDisplayPickup(); void ClearInactiveDisplayPickups(); diff --git a/TombEngine/Game/Hud/Speedometer.cpp b/TombEngine/Game/Hud/Speedometer.cpp index 5768c69cf..58e25c801 100644 --- a/TombEngine/Game/Hud/Speedometer.cpp +++ b/TombEngine/Game/Hud/Speedometer.cpp @@ -3,11 +3,12 @@ #include "Game/effects/DisplaySprite.h" #include "Math/Math.h" -#include "Renderer/Renderer.h" #include "Specific/clock.h" +#include "Renderer/Renderer.h" using namespace TEN::Effects::DisplaySprite; using namespace TEN::Math; + using TEN::Renderer::g_Renderer; namespace TEN::Hud @@ -30,6 +31,8 @@ namespace TEN::Hud return; } + StoreInterpolationData(); + // Update life and updated value status. _life = std::clamp(_life + (_hasValueUpdated ? 1.0f : -1.0f), 0.0f, LIFE_MAX * FPS); _hasValueUpdated = false; @@ -46,7 +49,7 @@ namespace TEN::Hud void SpeedometerController::Draw() const { constexpr auto POS = Vector2(DISPLAY_SPACE_RES.x - (DISPLAY_SPACE_RES.x / 6), DISPLAY_SPACE_RES.y - (DISPLAY_SPACE_RES.y / 10)); - constexpr auto ORIENT_OFFSET = ANGLE(90.0f); + constexpr auto POINTER_ANGLE_OFFSET = ANGLE(90.0f); constexpr auto SCALE = Vector2(0.35f); constexpr auto DIAL_ELEMENT_SPRITE_ID = 0; constexpr auto POINTER_ELEMENT_SPRITE_ID = 1; @@ -58,19 +61,22 @@ namespace TEN::Hud if (_life <= 0.0f) return; - auto color = Color(1.0f, 1.0f, 1.0f, _opacity); + short pointerAngle = (short)Lerp(_prevPointerAngle, _pointerAngle, g_Renderer.GetInterpolationFactor()); + auto color = Color(1.0f, 1.0f, 1.0f, Lerp(_prevOpacity, _opacity, g_Renderer.GetInterpolationFactor())); // Draw dial. AddDisplaySprite( ID_SPEEDOMETER, DIAL_ELEMENT_SPRITE_ID, POS, 0, SCALE, color, - DIAL_PRIORITY, DisplaySpriteAlignMode::Center, DisplaySpriteScaleMode::Fit, BlendMode::AlphaBlend); + DIAL_PRIORITY, DisplaySpriteAlignMode::Center, DisplaySpriteScaleMode::Fit, BlendMode::AlphaBlend, + DisplaySpritePhase::Draw); // Draw pointer. AddDisplaySprite( ID_SPEEDOMETER, POINTER_ELEMENT_SPRITE_ID, - POS, _pointerAngle + ORIENT_OFFSET, SCALE, color, - POINTER_PRIORITY, DisplaySpriteAlignMode::Center, DisplaySpriteScaleMode::Fit, BlendMode::AlphaBlend); + POS, pointerAngle + POINTER_ANGLE_OFFSET, SCALE, color, + POINTER_PRIORITY, DisplaySpriteAlignMode::Center, DisplaySpriteScaleMode::Fit, BlendMode::AlphaBlend, + DisplaySpritePhase::Draw); } void SpeedometerController::Clear() @@ -80,10 +86,10 @@ namespace TEN::Hud void SpeedometerController::DrawDebug() const { - g_Renderer.PrintDebugMessage("SPEEDOMETER DEBUG"); - g_Renderer.PrintDebugMessage("Value: %.3f", _value); - g_Renderer.PrintDebugMessage("Pointer angle: %.3f", _pointerAngle); - g_Renderer.PrintDebugMessage("Opacity: %.3f", _opacity); - g_Renderer.PrintDebugMessage("Life: %.3f", _life / FPS); + PrintDebugMessage("SPEEDOMETER DEBUG"); + PrintDebugMessage("Value: %.3f", _value); + PrintDebugMessage("Pointer angle: %.3f", _pointerAngle); + PrintDebugMessage("Opacity: %.3f", _opacity); + PrintDebugMessage("Life: %.3f", _life / FPS); } } diff --git a/TombEngine/Game/Hud/Speedometer.h b/TombEngine/Game/Hud/Speedometer.h index 164d652a2..c87e47a6c 100644 --- a/TombEngine/Game/Hud/Speedometer.h +++ b/TombEngine/Game/Hud/Speedometer.h @@ -6,9 +6,11 @@ namespace TEN::Hud { private: // Constants + static constexpr auto LIFE_MAX = 0.75f; // Members + bool _hasValueUpdated = false; float _value = 0.0f; @@ -16,8 +18,18 @@ namespace TEN::Hud float _opacity = 0.0f; float _life = 0.0f; + short _prevPointerAngle = 0; + float _prevOpacity = 0.0f; + + void StoreInterpolationData() + { + _prevPointerAngle = _pointerAngle; + _prevOpacity = _opacity; + } + public: // Utilities + void UpdateValue(float value); void Update(); diff --git a/TombEngine/Game/Hud/StatusBars.h b/TombEngine/Game/Hud/StatusBars.h index 8ba931cb2..783e4648f 100644 --- a/TombEngine/Game/Hud/StatusBars.h +++ b/TombEngine/Game/Hud/StatusBars.h @@ -26,6 +26,7 @@ namespace TEN::Hud { private: // Members + StatusBar _airBar = {}; StatusBar _exposureBar = {}; StatusBar _healthBar = {}; @@ -35,6 +36,7 @@ namespace TEN::Hud public: // Utilities + void Initialize(const ItemInfo& item); void Update(const ItemInfo& item); void Draw(const ItemInfo& item) const; @@ -42,12 +44,14 @@ namespace TEN::Hud private: // Update helpers + void UpdateAirBar(const ItemInfo& item); void UpdateExposureBar(const ItemInfo& item); void UpdateHealthBar(const ItemInfo& item); void UpdateStaminaBar(const ItemInfo& item); // Draw helpers + void DrawStatusBar(float value, float criticalValue, const RendererHudBar& rHudBar, GAME_OBJECT_ID textureID, int frame, bool isPoisoned) const; void DrawAirBar() const; void DrawExposureBar() const; diff --git a/TombEngine/Game/Hud/TargetHighlighter.cpp b/TombEngine/Game/Hud/TargetHighlighter.cpp index 34638f991..dcbf0c4aa 100644 --- a/TombEngine/Game/Hud/TargetHighlighter.cpp +++ b/TombEngine/Game/Hud/TargetHighlighter.cpp @@ -74,6 +74,9 @@ namespace TEN::Hud constexpr auto ORIENT_LERP_ALPHA = 0.1f; constexpr auto RADIUS_LERP_ALPHA = 0.2f; + if (Position.has_value()) + StoreInterpolationData(); + // Update active status. IsActive = isActive; @@ -133,30 +136,36 @@ namespace TEN::Hud constexpr auto STATIC_ELEMENT_SPRITE_ID = 0; constexpr auto SEGMENT_ELEMENT_SPRITE_ID = 1; constexpr auto PRIORITY = 0; // TODO: Check later. May interfere with Lua display sprites. -- Sezz 2023.10.06 - constexpr auto ALIGN_MODE = DisplaySpriteAlignMode::Center; - constexpr auto SCALE_MODE = DisplaySpriteScaleMode::Fill; - constexpr auto BLEND_MODE = BlendMode::Additive; if (!Position.has_value()) return; + auto pos0 = Vector2::Lerp(PrevPosition, *Position, g_Renderer.GetInterpolationFactor()); + short orient0 = PrevOrientation + Geometry::GetShortestAngle(PrevOrientation, Orientation) * g_Renderer.GetInterpolationFactor(); + float scale = Lerp(PrevScale, Scale, g_Renderer.GetInterpolationFactor()); + auto color = Color::Lerp(PrevColor, Color, g_Renderer.GetInterpolationFactor()); + // Draw main static element. AddDisplaySprite( SPRITE_SEQUENCE_OBJECT_ID, STATIC_ELEMENT_SPRITE_ID, - *Position, Orientation, Vector2(Scale), Color, - PRIORITY, ALIGN_MODE, SCALE_MODE, BLEND_MODE); + pos0, orient0, Vector2(scale), color, + PRIORITY, DisplaySpriteAlignMode::Center, DisplaySpriteScaleMode::Fill, + BlendMode::Additive, DisplaySpritePhase::Draw); // Draw animated outer segment elements. - for (const auto& segment : Segments) + for (int i = 0; i < Segments.size(); i++) { - auto pos = *Position + segment.PosOffset; - short orient = Orientation + segment.OrientOffset; - auto scale = Vector2(Scale / 2); + const auto& segment = Segments[i]; + const auto& prevSegment = PrevSegments[i]; + + auto pos1 = pos0 + Vector2::Lerp(prevSegment.PosOffset, segment.PosOffset, g_Renderer.GetInterpolationFactor()); + short orient1 = orient0 + (prevSegment.OrientOffset + (Geometry::GetShortestAngle(prevSegment.OrientOffset, segment.OrientOffset) * g_Renderer.GetInterpolationFactor())); AddDisplaySprite( SPRITE_SEQUENCE_OBJECT_ID, SEGMENT_ELEMENT_SPRITE_ID, - pos, orient, scale, Color, - PRIORITY, ALIGN_MODE, SCALE_MODE, BLEND_MODE); + pos1, orient1, Vector2(scale / 2), color, + PRIORITY, DisplaySpriteAlignMode::Center, DisplaySpriteScaleMode::Fill, + BlendMode::Additive, DisplaySpritePhase::Draw); } } @@ -175,23 +184,23 @@ namespace TEN::Hud // Loop over player targets. auto itemNumbers = std::vector{}; - for (const auto* itemPtr : player.TargetList) + for (const auto* item : player.TargetList) { - if (itemPtr == nullptr) + if (item == nullptr) continue; // Collect item number. - if (itemPtr->HitPoints != NOT_TARGETABLE) - itemNumbers.push_back(itemPtr->Index); + if (item->HitPoints != NOT_TARGETABLE) + itemNumbers.push_back(item->Index); // Find crosshair at item number key. - auto it = _crosshairs.find(itemPtr->Index); + auto it = _crosshairs.find(item->Index); if (it == _crosshairs.end()) continue; // Set crosshair as primary or peripheral. auto& crosshair = it->second; - if (player.TargetEntity != nullptr && itemPtr->Index == player.TargetEntity->Index) + if (player.TargetEntity != nullptr && item->Index == player.TargetEntity->Index) { crosshair.SetPrimary(); } @@ -350,9 +359,9 @@ namespace TEN::Hud for (const auto& [itemNumber, crosshair] : _crosshairs) crosshair.IsPrimary ? primaryCount++ : peripheralCount++; - g_Renderer.PrintDebugMessage("TARGET HIGHLIGHTER DEBUG"); - g_Renderer.PrintDebugMessage(g_Configuration.EnableTargetHighlighter ? "Enabled" : "Disabled"); - g_Renderer.PrintDebugMessage("Primary crosshairs: %d", primaryCount); - g_Renderer.PrintDebugMessage("Peripheral crosshairs: %d", peripheralCount); + PrintDebugMessage("TARGET HIGHLIGHTER DEBUG"); + PrintDebugMessage(g_Configuration.EnableTargetHighlighter ? "Enabled" : "Disabled"); + PrintDebugMessage("Primary crosshairs: %d", primaryCount); + PrintDebugMessage("Peripheral crosshairs: %d", peripheralCount); } } diff --git a/TombEngine/Game/Hud/TargetHighlighter.h b/TombEngine/Game/Hud/TargetHighlighter.h index b62c5b3df..93cc5815d 100644 --- a/TombEngine/Game/Hud/TargetHighlighter.h +++ b/TombEngine/Game/Hud/TargetHighlighter.h @@ -15,11 +15,13 @@ namespace TEN::Hud public: // Constants + static constexpr auto COLOR_RED = Color(1.0f, 0.2f, 0.2f); static constexpr auto COLOR_GRAY = Color(0.7f, 0.7f, 0.7f, 0.7f); static constexpr auto SEGMENT_COUNT = 4; // Members + bool IsActive = false; bool IsPrimary = false; @@ -34,37 +36,59 @@ namespace TEN::Hud std::array Segments = {}; + Vector2 PrevPosition = Vector2::Zero; + short PrevOrientation = 0; + float PrevScale = 0.0f; + Vector4 PrevColor = Vector4::Zero; + std::array PrevSegments = {}; + // Getters + float GetScale(float cameraDist) const; float GetRadius() const; Vector2 GetPositionOffset(short orientOffset) const; // Setters + void SetPrimary(); void SetPeripheral(); // Utilities + void Update(const Vector3& targetPos, bool isActive, bool doPulse); void Draw() const; + + void StoreInterpolationData() + { + PrevPosition = *Position; + PrevOrientation = Orientation; + PrevScale = Scale; + PrevColor = Color; + PrevSegments = Segments; + } }; class TargetHighlighterController { private: // Members + std::unordered_map _crosshairs = {}; // Key = item number. public: // Utilities + void Update(const ItemInfo& playerItem); void Draw() const; void Clear(); private: // Update helpers + void Update(const std::vector& itemNumbers); // Object helpers + CrosshairData& GetNewCrosshair(int itemNumber); void AddCrosshair(int itemNumber, const Vector3& targetPos); void ClearInactiveCrosshairs(); diff --git a/TombEngine/Game/Lara/PlayerContext.cpp b/TombEngine/Game/Lara/PlayerContext.cpp index b9944df22..99ac6a5b9 100644 --- a/TombEngine/Game/Lara/PlayerContext.cpp +++ b/TombEngine/Game/Lara/PlayerContext.cpp @@ -3,6 +3,7 @@ #include "Game/collision/collide_item.h" #include "Game/collision/collide_room.h" +#include "Game/collision/Point.h" #include "Game/collision/floordata.h" #include "Game/control/los.h" #include "Game/items.h" @@ -14,6 +15,7 @@ #include "Specific/Input/Input.h" using namespace TEN::Collision::Floordata; +using namespace TEN::Collision::Point; using namespace TEN::Input; namespace TEN::Entities::Player @@ -35,8 +37,8 @@ namespace TEN::Entities::Player const auto& player = GetLaraInfo(item); // Get point collision. - auto pointColl = GetCollision(&item, 0, 0, -coll.Setup.Height / 2); // NOTE: Height offset required for correct bridge collision. - int relFloorHeight = pointColl.Position.Floor - item.Pose.Position.y; + auto pointColl = GetPointCollision(item, 0, 0, -coll.Setup.Height / 2); // NOTE: Height offset required for correct bridge collision. + int relFloorHeight = pointColl.GetFloorHeight() - item.Pose.Position.y; // 1) Test if player is already aligned with floor. if (relFloorHeight == 0) @@ -61,8 +63,8 @@ namespace TEN::Entities::Player constexpr auto UPPER_FLOOR_BOUND_DOWN = CLICK(0.75f); // Get point collision. - auto pointColl = GetCollision(&item, 0, 0, -coll.Setup.Height / 2); // NOTE: Height offset required for correct bridge collision. - int relFloorHeight = pointColl.Position.Floor - item.Pose.Position.y; + auto pointColl = GetPointCollision(item, 0, 0, -coll.Setup.Height / 2); // NOTE: Height offset required for correct bridge collision. + int relFloorHeight = pointColl.GetFloorHeight() - item.Pose.Position.y; // Determine appropriate floor bounds. int lowerFloorBound = isGoingUp ? LOWER_FLOOR_BOUND_UP : LOWER_FLOOR_BOUND_DOWN; @@ -167,30 +169,30 @@ namespace TEN::Entities::Player int playerHeight = isCrawling ? LARA_HEIGHT_CRAWL : coll.Setup.Height; // Get point collision. - auto pointColl = GetCollision(&item, setup.HeadingAngle, OFFSET_RADIUS(playerRadius), -playerHeight); + auto pointColl = GetPointCollision(item, setup.HeadingAngle, OFFSET_RADIUS(playerRadius), -playerHeight); int vPos = item.Pose.Position.y; int vPosTop = vPos - playerHeight; // Calculate slope aspect delta angle. - short aspectAngle = Geometry::GetSurfaceAspectAngle(pointColl.FloorNormal); + short aspectAngle = Geometry::GetSurfaceAspectAngle(pointColl.GetFloorNormal()); short aspectAngleDelta = Geometry::GetShortestAngle(setup.HeadingAngle, aspectAngle); - // 1) Check for slippery slope below floor (if applicable). - if (setup.TestSlipperySlopeBelow && - (pointColl.Position.FloorSlope && abs(aspectAngleDelta) <= SLOPE_ASPECT_ANGLE_DELTA_MAX)) + // 1) Check for illegal slope below floor (if applicable). + if (setup.TestSteepFloorBelow && + (pointColl.IsSteepFloor() && abs(aspectAngleDelta) <= SLOPE_ASPECT_ANGLE_DELTA_MAX)) { return false; } - // 1) Check for slippery slope above floor (if applicable). - if (setup.TestSlipperySlopeAbove && - (pointColl.Position.FloorSlope && abs(aspectAngleDelta) >= SLOPE_ASPECT_ANGLE_DELTA_MAX)) + // 1) Check for illegal slope above floor (if applicable). + if (setup.TestSteepFloorAbove && + (pointColl.IsSteepFloor() && abs(aspectAngleDelta) >= SLOPE_ASPECT_ANGLE_DELTA_MAX)) { return false; } // 3) Check for death floor (if applicable). - if (setup.TestDeathFloor && pointColl.Block->Flags.Death) + if (setup.TestDeathFloor && pointColl.GetSector().Flags.Death && pointColl.GetFloorBridgeItemNumber() == NO_VALUE) return false; // LOS setup at upper floor bound. @@ -200,9 +202,9 @@ namespace TEN::Entities::Player item.Pose.Position.z, item.RoomNumber); auto target0 = GameVector( - pointColl.Coordinates.x, + pointColl.GetPosition().x, (vPos + setup.UpperFloorBound) - 1, - pointColl.Coordinates.z, + pointColl.GetPosition().z, item.RoomNumber); // LOS setup at lowest ceiling bound (player height). @@ -212,9 +214,9 @@ namespace TEN::Entities::Player item.Pose.Position.z, item.RoomNumber); auto target1 = GameVector( - pointColl.Coordinates.x, + pointColl.GetPosition().x, vPosTop + 1, - pointColl.Coordinates.z, + pointColl.GetPosition().z, item.RoomNumber); // Calculate LOS direction. @@ -232,9 +234,9 @@ namespace TEN::Entities::Player if (!LOS(&origin0, &target0) || !LOS(&origin1, &target1)) return false; - int relFloorHeight = pointColl.Position.Floor - vPos; - int relCeilHeight = pointColl.Position.Ceiling - vPos; - int floorToCeilHeight = abs(pointColl.Position.Ceiling - pointColl.Position.Floor); + int relFloorHeight = pointColl.GetFloorHeight() - vPos; + int relCeilHeight = pointColl.GetCeilingHeight() - vPos; + int floorToCeilHeight = abs(pointColl.GetCeilingHeight() - pointColl.GetFloorHeight()); // 6) Assess point collision. if (relFloorHeight <= setup.LowerFloorBound && // Floor height is above lower floor bound. @@ -400,12 +402,12 @@ namespace TEN::Entities::Player return false; // Get point collision. - auto pointColl = GetCollision(&item, 0, 0, -coll.Setup.Height / 2); // NOTE: Offset required for correct bridge collision. - int relFloorHeight = pointColl.Position.Floor - item.Pose.Position.y; + auto pointColl = GetPointCollision(item, 0, 0, -coll.Setup.Height / 2); // NOTE: Offset required for correct bridge collision. + int relFloorHeight = pointColl.GetFloorHeight() - item.Pose.Position.y; // 2) Assess point collision. if (abs(relFloorHeight) <= ABS_FLOOR_BOUND && // Floor height is within upper/lower floor bounds. - pointColl.Position.FloorSlope) // Floor is a slippery slope. + pointColl.IsSteepFloor()) // Floor is a slippery slope. { return true; } @@ -432,8 +434,8 @@ namespace TEN::Entities::Player float radius = TestState(item.Animation.ActiveState, CROUCH_STATES) ? LARA_RADIUS_CRAWL : LARA_RADIUS; // Get center point collision. - auto pointCollCenter = GetCollision(&item, 0, 0.0f, -LARA_HEIGHT / 2); - int floorToCeilHeightCenter = abs(pointCollCenter.Position.Ceiling - pointCollCenter.Position.Floor); + auto pointCollCenter = GetPointCollision(item, 0, 0.0f, -LARA_HEIGHT / 2); + int floorToCeilHeightCenter = abs(pointCollCenter.GetCeilingHeight() - pointCollCenter.GetFloorHeight()); // Assess center point collision. if (floorToCeilHeightCenter < LARA_HEIGHT || // Floor-to-ceiling height isn't too wide. @@ -445,9 +447,9 @@ namespace TEN::Entities::Player // TODO: Check whether < or <= and > or >=. // Get front point collision. - auto pointCollFront = GetCollision(&item, item.Pose.Orientation.y, radius, -coll.Setup.Height); - int floorToCeilHeightFront = abs(pointCollFront.Position.Ceiling - pointCollFront.Position.Floor); - int relFloorHeightFront = abs(pointCollFront.Position.Floor - pointCollCenter.Position.Floor); + auto pointCollFront = GetPointCollision(item, item.Pose.Orientation.y, radius, -coll.Setup.Height); + int floorToCeilHeightFront = abs(pointCollFront.GetCeilingHeight() - pointCollFront.GetFloorHeight()); + int relFloorHeightFront = abs(pointCollFront.GetFloorHeight() - pointCollCenter.GetFloorHeight()); // Assess front point collision. if (relFloorHeightFront <= CRAWL_STEPUP_HEIGHT && // Floor is within upper/lower floor bounds. @@ -458,9 +460,9 @@ namespace TEN::Entities::Player } // Get back point collision. - auto pointCollBack = GetCollision(&item, item.Pose.Orientation.y, -radius, -coll.Setup.Height); - int floorToCeilHeightBack = abs(pointCollBack.Position.Ceiling - pointCollBack.Position.Floor); - int relFloorHeightBack = abs(pointCollBack.Position.Floor - pointCollCenter.Position.Floor); + auto pointCollBack = GetPointCollision(item, item.Pose.Orientation.y, -radius, -coll.Setup.Height); + int floorToCeilHeightBack = abs(pointCollBack.GetCeilingHeight() - pointCollBack.GetFloorHeight()); + int relFloorHeightBack = abs(pointCollBack.GetFloorHeight() - pointCollCenter.GetFloorHeight()); // Assess back point collision. if (relFloorHeightBack <= CRAWL_STEPUP_HEIGHT && // Floor is within upper/lower floor bounds. @@ -526,22 +528,22 @@ namespace TEN::Entities::Player // TODO: Extend point collision struct to also find water depths. float dist = 0.0f; - auto pointColl0 = GetCollision(&item); + auto pointColl0 = GetPointCollision(item); // 3) Test continuity of path. while (dist < PROBE_DIST_MAX) { // Get point collision. dist += STEP_DIST; - auto pointColl1 = GetCollision(&item, item.Pose.Orientation.y, dist, -LARA_HEIGHT_CRAWL); + auto pointColl1 = GetPointCollision(item, item.Pose.Orientation.y, dist, -LARA_HEIGHT_CRAWL); - int floorHeightDelta = abs(pointColl0.Position.Floor - pointColl1.Position.Floor); - int floorToCeilHeight = abs(pointColl1.Position.Ceiling - pointColl1.Position.Floor); + int floorHeightDelta = abs(pointColl0.GetFloorHeight() - pointColl1.GetFloorHeight()); + int floorToCeilHeight = abs(pointColl1.GetCeilingHeight() - pointColl1.GetFloorHeight()); // Assess point collision. if (floorHeightDelta > FLOOR_BOUND || // Avoid floor height delta beyond crawl stepup threshold. floorToCeilHeight <= FLOOR_TO_CEIL_HEIGHT_MAX || // Avoid narrow spaces. - pointColl1.Position.FloorSlope) // Avoid slippery floor slopes. + pointColl1.IsSteepFloor()) // Avoid slippery floor slopes. { return false; } @@ -580,8 +582,8 @@ namespace TEN::Entities::Player constexpr auto UPPER_CEIL_BOUND = -MONKEY_STEPUP_HEIGHT; // Get point collision. - auto pointColl = GetCollision(&item); - int relCeilHeight = pointColl.Position.Ceiling - (item.Pose.Position.y - LARA_HEIGHT_MONKEY); + auto pointColl = GetPointCollision(item); + int relCeilHeight = pointColl.GetCeilingHeight() - (item.Pose.Position.y - LARA_HEIGHT_MONKEY); // Assess point collision. if (relCeilHeight <= LOWER_CEIL_BOUND && // Ceiling height is above lower ceiling bound. @@ -604,14 +606,14 @@ namespace TEN::Entities::Player return true; // Get point collision. - auto pointColl = GetCollision(&item); + auto pointColl = GetPointCollision(item); // 2) Test for slippery ceiling slope and check if overhang climb is disabled. - if (pointColl.Position.CeilingSlope && !g_GameFlow->HasOverhangClimb()) + if (pointColl.IsSteepCeiling() && !g_GameFlow->HasOverhangClimb()) return true; // 3) Assess point collision. - int relCeilHeight = pointColl.Position.Ceiling - (item.Pose.Position.y - LARA_HEIGHT_MONKEY); + int relCeilHeight = pointColl.GetCeilingHeight() - (item.Pose.Position.y - LARA_HEIGHT_MONKEY); if (abs(relCeilHeight) > ABS_CEIL_BOUND) // Ceiling height is within lower/upper ceiling bound. return true; @@ -630,9 +632,9 @@ namespace TEN::Entities::Player return false; // Get point collision. - auto pointColl = GetCollision(&item); - int relCeilHeight = pointColl.Position.Ceiling - (item.Pose.Position.y - LARA_HEIGHT_MONKEY); - int floorToCeilHeight = abs(pointColl.Position.Ceiling - pointColl.Position.Floor); + auto pointColl = GetPointCollision(item); + int relCeilHeight = pointColl.GetCeilingHeight() - (item.Pose.Position.y - LARA_HEIGHT_MONKEY); + int floorToCeilHeight = abs(pointColl.GetCeilingHeight() - pointColl.GetFloorHeight()); // 2) Assess collision with ceiling. if (relCeilHeight < 0 && @@ -658,14 +660,14 @@ namespace TEN::Entities::Player constexpr auto PLAYER_HEIGHT = LARA_HEIGHT_MONKEY; // Get point collision. - auto pointColl = GetCollision(&item, setup.HeadingAngle, OFFSET_RADIUS(coll.Setup.Radius)); + auto pointColl = GetPointCollision(item, setup.HeadingAngle, OFFSET_RADIUS(coll.Setup.Radius)); // 1) Test if ceiling is monkey swing. - if (!pointColl.BottomBlock->Flags.Monkeyswing) + if (!pointColl.GetBottomSector().Flags.Monkeyswing) return false; - // 2) Test for ceiling slippery slope. - if (pointColl.Position.CeilingSlope) + // 2) Test for illegal ceiling. + if (pointColl.IsSteepCeiling()) return false; int vPos = item.Pose.Position.y; @@ -678,9 +680,9 @@ namespace TEN::Entities::Player item.Pose.Position.z, item.RoomNumber); auto target0 = GameVector( - pointColl.Coordinates.x, + pointColl.GetPosition().x, vPos - 1, - pointColl.Coordinates.z, + pointColl.GetPosition().z, item.RoomNumber); // Raycast setup at lower ceiling bound. @@ -690,9 +692,9 @@ namespace TEN::Entities::Player item.Pose.Position.z, item.RoomNumber); auto target1 = GameVector( - pointColl.Coordinates.x, + pointColl.GetPosition().x, (vPosTop + setup.LowerCeilingBound) + 1, - pointColl.Coordinates.z, + pointColl.GetPosition().z, item.RoomNumber); // Prepare data for static object LOS. @@ -712,9 +714,9 @@ namespace TEN::Entities::Player // TODO: Assess static object geometry ray collision. - int relFloorHeight = pointColl.Position.Floor - vPos; - int relCeilHeight = pointColl.Position.Ceiling - vPosTop; - int floorToCeilHeight = abs(pointColl.Position.Ceiling - pointColl.Position.Floor); + int relFloorHeight = pointColl.GetFloorHeight() - vPos; + int relCeilHeight = pointColl.GetCeilingHeight() - vPosTop; + int floorToCeilHeight = abs(pointColl.GetCeilingHeight() - pointColl.GetFloorHeight()); // 4) Assess point collision. if (relFloorHeight > 0 && // Floor is within highest floor bound (player base). @@ -782,8 +784,8 @@ namespace TEN::Entities::Player return false; // Get point collision. - auto pointColl = GetCollision(&item, 0, 0, -coll.Setup.Height / 2); - int relFloorHeight = pointColl.Position.Floor - item.Pose.Position.y; + auto pointColl = GetPointCollision(item, 0, 0, -coll.Setup.Height / 2); + int relFloorHeight = pointColl.GetFloorHeight() - item.Pose.Position.y; // 2) Assess point collision. if (relFloorHeight > UPPER_FLOOR_BOUND) // Floor height is below upper floor bound. @@ -805,11 +807,11 @@ namespace TEN::Entities::Player return true; // Get point collision. - auto pointColl = GetCollision(&item); + auto pointColl = GetPointCollision(item); int vPos = item.Pose.Position.y; // 3) Assess point collision. - if ((pointColl.Position.Floor - vPos) <= projVerticalVel) // Floor height is above projected vertical position. + if ((pointColl.GetFloorHeight() - vPos) <= projVerticalVel) // Floor height is above projected vertical position. return true; return false; @@ -848,9 +850,9 @@ namespace TEN::Entities::Player return false;*/ // Get point collision. - auto pointColl = GetCollision(&item, setup.HeadingAngle, setup.Distance, -coll.Setup.Height); - int relFloorHeight = pointColl.Position.Floor - item.Pose.Position.y; - int relCeilHeight = pointColl.Position.Ceiling - item.Pose.Position.y; + auto pointColl = GetPointCollision(item, setup.HeadingAngle, setup.Distance, -coll.Setup.Height); + int relFloorHeight = pointColl.GetFloorHeight() - item.Pose.Position.y; + int relCeilHeight = pointColl.GetCeilingHeight() - item.Pose.Position.y; // 4) Assess point collision. if (relFloorHeight >= -STEPUP_HEIGHT && // Floor is within highest floor bound. @@ -923,11 +925,11 @@ namespace TEN::Entities::Player return IsRunJumpQueueableState(item.Animation.TargetState); // Get point collision. - auto pointColl = GetCollision(&item, item.Pose.Orientation.y, BLOCK(1), -coll.Setup.Height); + auto pointColl = GetPointCollision(item, item.Pose.Orientation.y, BLOCK(1), -coll.Setup.Height); int lowerCeilingBound = (LOWER_CEIL_BOUND_BASE - coll.Setup.Height); - int relFloorHeight = pointColl.Position.Floor - item.Pose.Position.y; - int relCeilHeight = pointColl.Position.Ceiling - item.Pose.Position.y; + int relFloorHeight = pointColl.GetFloorHeight() - item.Pose.Position.y; + int relCeilHeight = pointColl.GetCeilingHeight() - item.Pose.Position.y; // 2) Assess point collision for possible running jump ahead. if (relCeilHeight < lowerCeilingBound || // Ceiling height is above lower ceiling bound. @@ -993,7 +995,7 @@ namespace TEN::Entities::Player // TODO: Broken on diagonal slides? - auto pointColl = GetCollision(&item); + auto pointColl = GetPointCollision(item); //short aspectAngle = GetLaraSlideHeadingAngle(item, coll); //short slopeAngle = Geometry::GetSurfaceSlopeAngle(GetSurfaceNormal(pointColl.FloorTilt, true)); @@ -1002,8 +1004,8 @@ namespace TEN::Entities::Player bool CanCrawlspaceDive(const ItemInfo& item, const CollisionInfo& coll) { - auto pointColl = GetCollision(&item, coll.Setup.ForwardAngle, coll.Setup.Radius, -coll.Setup.Height); - return (abs(pointColl.Position.Ceiling - pointColl.Position.Floor) < LARA_HEIGHT || IsInLowSpace(item, coll)); + auto pointColl = GetPointCollision(item, coll.Setup.ForwardAngle, coll.Setup.Radius, -coll.Setup.Height); + return (abs(pointColl.GetCeilingHeight() - pointColl.GetFloorHeight()) < LARA_HEIGHT || IsInLowSpace(item, coll)); } bool CanPerformLedgeJump(const ItemInfo& item, const CollisionInfo& coll) @@ -1031,8 +1033,8 @@ namespace TEN::Entities::Player // TODO: Assess static object geometry ray collision. // Get point collision. - auto pointColl = GetCollision(&item); - int relCeilHeight = pointColl.Position.Ceiling - (item.Pose.Position.y - LARA_HEIGHT_STRETCH); + auto pointColl = GetPointCollision(item); + int relCeilHeight = pointColl.GetCeilingHeight() - (item.Pose.Position.y - LARA_HEIGHT_STRETCH); // 3) Assess point collision. if (relCeilHeight >= -coll.Setup.Height) // Ceiling height is below upper ceiling bound. @@ -1045,10 +1047,10 @@ namespace TEN::Entities::Player { const auto& player = GetLaraInfo(item); - auto pointColl = GetCollision(&item); + auto pointColl = GetPointCollision(item); if (player.Control.Tightrope.CanDismount && // Dismount is allowed. - pointColl.Position.Floor == item.Pose.Position.y) // Floor is level with player. + pointColl.GetFloorHeight() == item.Pose.Position.y) // Floor is level with player. { return true; } diff --git a/TombEngine/Game/Lara/PlayerContextData.h b/TombEngine/Game/Lara/PlayerContextData.h index c53dcbd8b..c5fca5052 100644 --- a/TombEngine/Game/Lara/PlayerContextData.h +++ b/TombEngine/Game/Lara/PlayerContextData.h @@ -8,9 +8,9 @@ namespace TEN::Entities::Player int LowerFloorBound = 0; int UpperFloorBound = 0; - bool TestSlipperySlopeBelow = true; - bool TestSlipperySlopeAbove = true; - bool TestDeathFloor = true; + bool TestSteepFloorBelow = true; + bool TestSteepFloorAbove = true; + bool TestDeathFloor = true; }; struct MonkeySwingMovementSetupData diff --git a/TombEngine/Game/Lara/PlayerStateMachine.cpp b/TombEngine/Game/Lara/PlayerStateMachine.cpp index feab50981..84cc003f9 100644 --- a/TombEngine/Game/Lara/PlayerStateMachine.cpp +++ b/TombEngine/Game/Lara/PlayerStateMachine.cpp @@ -71,7 +71,7 @@ namespace TEN::Entities::Player PlayerBehaviorStateRoutines[LS_PUSHABLE_PUSH] = std::pair(lara_as_pushable_push, lara_default_col); PlayerBehaviorStateRoutines[LS_PUSHABLE_PULL] = std::pair(lara_as_pushable_pull, lara_default_col); PlayerBehaviorStateRoutines[LS_PUSHABLE_GRAB] = std::pair(lara_as_pushable_grab, lara_default_col); - PlayerBehaviorStateRoutines[LS_PICKUP] = std::pair(lara_as_pickup, lara_default_col); + PlayerBehaviorStateRoutines[LS_PICKUP] = std::pair(lara_as_pickup, lara_col_pickup); PlayerBehaviorStateRoutines[LS_SWITCH_DOWN] = std::pair(lara_as_switch_on, lara_default_col); PlayerBehaviorStateRoutines[LS_SWITCH_UP] = std::pair(lara_as_switch_off, lara_default_col); PlayerBehaviorStateRoutines[LS_INSERT_KEY] = std::pair(lara_as_use_key, lara_default_col); @@ -99,7 +99,7 @@ namespace TEN::Entities::Player PlayerBehaviorStateRoutines[LS_TEST_3] = std::pair(lara_void_func, lara_void_func); PlayerBehaviorStateRoutines[LS_WADE_FORWARD] = std::pair(lara_as_wade_forward, lara_col_wade_forward); PlayerBehaviorStateRoutines[LS_UNDERWATER_ROLL] = std::pair(lara_as_underwater_roll_180, lara_col_underwater_roll_180); - PlayerBehaviorStateRoutines[LS_PICKUP_FLARE] = std::pair(lara_as_pickup_flare, lara_default_col); + PlayerBehaviorStateRoutines[LS_PICKUP_FLARE] = std::pair(lara_as_pickup_flare, lara_col_pickup); PlayerBehaviorStateRoutines[LS_JUMP_ROLL_180] = std::pair(lara_void_func, lara_void_func); PlayerBehaviorStateRoutines[LS_KICK] = std::pair(lara_void_func, lara_void_func); PlayerBehaviorStateRoutines[LS_ZIP_LINE] = std::pair(lara_as_zip_line, lara_void_func); diff --git a/TombEngine/Game/Lara/lara.cpp b/TombEngine/Game/Lara/lara.cpp index 447f10052..46b34aec8 100644 --- a/TombEngine/Game/Lara/lara.cpp +++ b/TombEngine/Game/Lara/lara.cpp @@ -24,6 +24,7 @@ #include "Game/camera.h" #include "Game/collision/collide_item.h" #include "Game/collision/floordata.h" +#include "Game/collision/Point.h" #include "Game/control/flipeffect.h" #include "Game/control/volume.h" #include "Game/effects/Hair.h" @@ -47,6 +48,7 @@ #include "Specific/winmain.h" using namespace TEN::Collision::Floordata; +using namespace TEN::Collision::Point; using namespace TEN::Control::Volumes; using namespace TEN::Effects::Hair; using namespace TEN::Effects::Items; @@ -57,8 +59,8 @@ using namespace TEN::Gui; using TEN::Renderer::g_Renderer; -LaraInfo Lara = {}; -ItemInfo* LaraItem; +LaraInfo Lara = {}; +ItemInfo* LaraItem = nullptr; CollisionInfo LaraCollision = {}; void LaraControl(ItemInfo* item, CollisionInfo* coll) @@ -152,7 +154,7 @@ void LaraControl(ItemInfo* item, CollisionInfo* coll) } else if (item->Animation.ActiveState == LS_FREEFALL_DIVE) { - SetAnimation(item, LA_SWANDIVE_DIVE); + SetAnimation(item, LA_SWANDIVE_FREEFALL_DIVE); item->Animation.Velocity.y /= 2; item->Pose.Orientation.x = ANGLE(-85.0f); player.Control.HandStatus = HandStatus::Free; @@ -207,7 +209,7 @@ void LaraControl(ItemInfo* item, CollisionInfo* coll) // pre-TR5 bug where player would keep submerged until root mesh was above water level. isWaterOnHeadspace = TestEnvironment( ENV_FLAG_WATER, item->Pose.Position.x, item->Pose.Position.y - CLICK(1), item->Pose.Position.z, - GetCollision(item->Pose.Position.x, item->Pose.Position.y - CLICK(1), item->Pose.Position.z, item->RoomNumber).RoomNumber); + GetPointCollision(*item, 0, 0, -CLICK(1)).GetRoomNumber()); if (water.WaterDepth == NO_HEIGHT || abs(water.HeightFromWater) >= CLICK(1) || isWaterOnHeadspace || item->Animation.AnimNumber == LA_UNDERWATER_RESURFACE || item->Animation.AnimNumber == LA_ONWATER_DIVE) @@ -333,7 +335,7 @@ void LaraControl(ItemInfo* item, CollisionInfo* coll) if (DebugMode) { - DrawNearbyPathfinding(GetCollision(item).BottomBlock->Box); + DrawNearbyPathfinding(GetPointCollision(*item).GetBottomSector().PathfindingBoxID); DrawNearbySectorFlags(*item); } } @@ -354,14 +356,13 @@ void LaraAboveWater(ItemInfo* item, CollisionInfo* coll) coll->Setup.BlockMonkeySwingEdge = false; coll->Setup.EnableObjectPush = true; coll->Setup.EnableSpasm = true; + coll->Setup.ForceSolidStatics = false; coll->Setup.PrevPosition = item->Pose.Position; coll->Setup.PrevAnimObjectID = item->Animation.AnimObjectID; coll->Setup.PrevAnimNumber = item->Animation.AnimNumber; coll->Setup.PrevFrameNumber = item->Animation.FrameNumber; coll->Setup.PrevState = item->Animation.ActiveState; - UpdateLaraRoom(item, -LARA_HEIGHT / 2); - // Handle look-around. if (((IsHeld(In::Look) && CanPlayerLookAround(*item)) || (player.Control.Look.IsUsingBinoculars || player.Control.Look.IsUsingLasersight)) && @@ -376,6 +377,8 @@ void LaraAboveWater(ItemInfo* item, CollisionInfo* coll) } player.Control.Look.Mode = LookMode::None; + UpdateLaraRoom(item, -LARA_HEIGHT / 2); + // Process vehicles. if (HandleLaraVehicle(item, coll)) return; @@ -424,6 +427,7 @@ void LaraWaterSurface(ItemInfo* item, CollisionInfo* coll) coll->Setup.BlockMonkeySwingEdge = false; coll->Setup.EnableObjectPush = true; coll->Setup.EnableSpasm = false; + coll->Setup.ForceSolidStatics = false; coll->Setup.PrevPosition = item->Pose.Position; // Handle look-around. @@ -496,6 +500,7 @@ void LaraUnderwater(ItemInfo* item, CollisionInfo* coll) coll->Setup.BlockMonkeySwingEdge = false; coll->Setup.EnableObjectPush = true; coll->Setup.EnableSpasm = false; + coll->Setup.ForceSolidStatics = false; coll->Setup.PrevPosition = item->Pose.Position; // Handle look-around. @@ -626,6 +631,7 @@ void UpdateLara(ItemInfo* item, bool isTitle) // Control player. InItemControlLoop = true; + LaraControl(item, &LaraCollision); HandlePlayerFlyCheat(*item); InItemControlLoop = false; @@ -634,17 +640,11 @@ void UpdateLara(ItemInfo* item, bool isTitle) if (isTitle) ActionMap = actionMap; - if (g_Gui.GetInventoryItemChosen() != NO_VALUE) - { - g_Gui.SetInventoryItemChosen(NO_VALUE); - SayNo(); - } - // Update player animations. g_Renderer.UpdateLaraAnimations(true); // Update player effects. - HairEffect.Update(*item, g_GameFlow->GetLevel(CurrentLevel)->GetLaraType() == LaraType::Young); + HairEffect.Update(*item); HandlePlayerWetnessDrips(*item); HandlePlayerDiveBubbles(*item); ProcessEffects(item); diff --git a/TombEngine/Game/Lara/lara.h b/TombEngine/Game/Lara/lara.h index 184a82cb3..5e35047bf 100644 --- a/TombEngine/Game/Lara/lara.h +++ b/TombEngine/Game/Lara/lara.h @@ -93,8 +93,8 @@ constexpr auto WADE_WATER_DEPTH = STEPUP_HEIGHT; constexpr auto SWIM_WATER_DEPTH = CLICK(2.75f); constexpr auto SLOPE_DIFFERENCE = 60; -extern LaraInfo Lara; -extern ItemInfo* LaraItem; +extern LaraInfo Lara; +extern ItemInfo* LaraItem; extern CollisionInfo LaraCollision; void LaraControl(ItemInfo* item, CollisionInfo* coll); diff --git a/TombEngine/Game/Lara/lara_basic.cpp b/TombEngine/Game/Lara/lara_basic.cpp index 8fbf4ec2f..5e65451a7 100644 --- a/TombEngine/Game/Lara/lara_basic.cpp +++ b/TombEngine/Game/Lara/lara_basic.cpp @@ -39,17 +39,7 @@ void lara_void_func(ItemInfo* item, CollisionInfo* coll) void lara_default_col(ItemInfo* item, CollisionInfo* coll) { - auto& player = GetLaraInfo(*item); - - player.Control.MoveAngle = item->Pose.Orientation.y; - coll->Setup.LowerFloorBound = STEPUP_HEIGHT; - coll->Setup.UpperFloorBound = -STEPUP_HEIGHT; - coll->Setup.LowerCeilingBound = 0; - coll->Setup.BlockFloorSlopeDown = true; - coll->Setup.BlockFloorSlopeUp = true; - coll->Setup.ForwardAngle = player.Control.MoveAngle; - GetCollisionInfo(coll, item); - LaraResetGravityStatus(item, coll); + LaraDefaultCollision(item, coll); } // Boulder death. diff --git a/TombEngine/Game/Lara/lara_climb.cpp b/TombEngine/Game/Lara/lara_climb.cpp index 483ab6b69..44aaa2360 100644 --- a/TombEngine/Game/Lara/lara_climb.cpp +++ b/TombEngine/Game/Lara/lara_climb.cpp @@ -3,7 +3,7 @@ #include "Game/animation.h" #include "Game/camera.h" -#include "Game/collision/sphere.h" +#include "Game/collision/Point.h" #include "Game/control/control.h" #include "Game/items.h" #include "Game/Lara/lara.h" @@ -14,6 +14,7 @@ #include "Specific/Input/Input.h" #include "Specific/level.h" +using namespace TEN::Collision::Point; using namespace TEN::Input; constexpr auto LADDER_TEST_MARGIN = 8; @@ -327,8 +328,8 @@ void lara_col_climb_idle(ItemInfo* item, CollisionInfo* coll) // HACK: Prevent climbing inside sloped ceilings. Breaks overhang even more, but that shouldn't matter since we'll be doing it over. -- Sezz 2022.05.13 int y = item->Pose.Position.y - (coll->Setup.Height + CLICK(0.5f)); - auto probe = GetCollision(item, 0, 0, -(coll->Setup.Height + CLICK(0.5f))); - if ((probe.Position.Ceiling - y) < 0) + auto probe = GetPointCollision(*item, 0, 0, -(coll->Setup.Height + CLICK(0.5f))); + if ((probe.GetCeilingHeight() - y) < 0) { item->Animation.TargetState = LS_LADDER_UP; item->Pose.Position.y += yShift; @@ -416,7 +417,7 @@ void lara_as_climb_stepoff_right(ItemInfo* item, CollisionInfo* coll) short GetClimbFlags(int x, int y, int z, short roomNumber) { - return GetClimbFlags(GetCollision(x, y, z, roomNumber).BottomBlock); + return GetClimbFlags(&GetPointCollision(Vector3i(x, y, z), roomNumber).GetBottomSector()); } short GetClimbFlags(FloorInfo* floor) @@ -787,13 +788,13 @@ int LaraTestClimb(ItemInfo* item, int xOffset, int yOffset, int zOffset, int xFr int y = item->Pose.Position.y + yOffset; int z = item->Pose.Position.z + zOffset; - auto probeUp = GetCollision(x, y - CLICK(0.5f), z, item->RoomNumber); - auto probeDown = GetCollision(x, y, z, item->RoomNumber); + auto probeUp = GetPointCollision(Vector3i(x, y - CLICK(0.5f), z), item->RoomNumber); + auto probeDown = GetPointCollision(Vector3i(x, y, z), item->RoomNumber); - if (!lara->Control.CanClimbLadder && !TestLaraNearClimbableWall(item, probeDown.BottomBlock)) + if (!lara->Control.CanClimbLadder && !TestLaraNearClimbableWall(item, &probeDown.GetBottomSector())) return 0; - int height = probeUp.Position.Floor; + int height = probeUp.GetFloorHeight(); if (height == NO_HEIGHT) return 0; @@ -805,7 +806,7 @@ int LaraTestClimb(ItemInfo* item, int xOffset, int yOffset, int zOffset, int xFr if (height < 0) *shift = height; - int ceiling = probeDown.Position.Ceiling - y; + int ceiling = probeDown.GetCeilingHeight() - y; if (ceiling > LADDER_CLIMB_SHIFT) return 0; @@ -822,8 +823,8 @@ int LaraTestClimb(ItemInfo* item, int xOffset, int yOffset, int zOffset, int xFr int dz = zFront + z; int dx = xFront + x; - auto probeFront = GetCollision(dx, y, dz, item->RoomNumber); - height = probeFront.Position.Floor; + auto probeFront = GetPointCollision(Vector3i(dx, y, dz), item->RoomNumber); + height = probeFront.GetFloorHeight(); if (height != NO_HEIGHT) height -= y; @@ -839,9 +840,9 @@ int LaraTestClimb(ItemInfo* item, int xOffset, int yOffset, int zOffset, int xFr *shift = height; } - auto probeTop = GetCollision(x, y + itemHeight, z, item->RoomNumber); - auto probeTopFront = GetCollision(dx, y + itemHeight, dz, probeTop.RoomNumber); - ceiling = probeTopFront.Position.Ceiling; + auto probeTop = GetPointCollision(Vector3i(x, y + itemHeight, z), item->RoomNumber); + auto probeTopFront = GetPointCollision(Vector3i(dx, y + itemHeight, dz), probeTop.GetRoomNumber()); + ceiling = probeTopFront.GetCeilingHeight(); if (ceiling == NO_HEIGHT) return 1; @@ -862,7 +863,7 @@ int LaraTestClimb(ItemInfo* item, int xOffset, int yOffset, int zOffset, int xFr return 1; } - ceiling = probeFront.Position.Ceiling - y; + ceiling = probeFront.GetCeilingHeight() - y; if (ceiling >= CLICK(2)) return 1; @@ -895,16 +896,16 @@ int LaraTestClimbUpPos(ItemInfo* item, int front, int right, int* shift, int* le *shift = 0; // Test center. - auto pointColl = GetCollision(item); + auto pointColl = GetPointCollision(*item); int vPos = item->Pose.Position.y - CLICK(4); - if ((pointColl.Position.Ceiling - vPos) > LADDER_CLIMB_SHIFT) + if ((pointColl.GetCeilingHeight() - vPos) > LADDER_CLIMB_SHIFT) return 0; - pointColl = GetCollision(probePos.x, probePos.y, probePos.z, item->RoomNumber); - int ceiling = (CLICK(1) - probePos.y) + pointColl.Position.Ceiling; + pointColl = GetPointCollision(probePos, item->RoomNumber); + int ceiling = (CLICK(1) - probePos.y) + pointColl.GetCeilingHeight(); - pointColl = GetCollision(probePos.x + probeOffset.x, probePos.y, probePos.z + probeOffset.z, pointColl.RoomNumber); - int height = pointColl.Position.Floor; + pointColl = GetPointCollision(Vector3i(probePos.x + probeOffset.x, probePos.y, probePos.z + probeOffset.z), pointColl.GetRoomNumber()); + int height = pointColl.GetFloorHeight(); if (height == NO_HEIGHT) { @@ -933,10 +934,10 @@ int LaraTestClimbUpPos(ItemInfo* item, int front, int right, int* shift, int* le if (height > 0 && height > *shift) *shift = height; - pointColl = GetCollision(probePos.x, probePos.y + CLICK(2), probePos.z, item->RoomNumber); - pointColl = GetCollision(probePos.x + probeOffset.x, probePos.y + CLICK(2), probePos.z + probeOffset.z, pointColl.RoomNumber); + pointColl = GetPointCollision(Vector3i(probePos.x, probePos.y + CLICK(2), probePos.z), item->RoomNumber); + pointColl = GetPointCollision(Vector3i(probePos.x + probeOffset.x, probePos.y + CLICK(2), probePos.z + probeOffset.z), pointColl.GetRoomNumber()); - ceiling = pointColl.Position.Ceiling - probePos.y; + ceiling = pointColl.GetCeilingHeight() - probePos.y; if (ceiling <= height) return 1; @@ -947,7 +948,7 @@ int LaraTestClimbUpPos(ItemInfo* item, int front, int right, int* shift, int* le } else { - ceiling = GetCollision(probePos.x + probeOffset.x, probePos.y, probePos.z + probeOffset.z, pointColl.RoomNumber).Position.Ceiling - probePos.y; + ceiling = GetPointCollision(Vector3i(probePos.x + probeOffset.x, probePos.y, probePos.z + probeOffset.z), pointColl.GetRoomNumber()).GetCeilingHeight() - probePos.y; if (ceiling < CLICK(2)) { if ((height - ceiling) <= LARA_HEIGHT) diff --git a/TombEngine/Game/Lara/lara_collide.cpp b/TombEngine/Game/Lara/lara_collide.cpp index 7ef49478b..e87e37f00 100644 --- a/TombEngine/Game/Lara/lara_collide.cpp +++ b/TombEngine/Game/Lara/lara_collide.cpp @@ -4,6 +4,7 @@ #include "Game/animation.h" #include "Game/collision/collide_room.h" #include "Game/collision/collide_item.h" +#include "Game/collision/Point.h" #include "Game/control/control.h" #include "Game/effects/effects.h" #include "Game/items.h" @@ -19,9 +20,15 @@ #include "Scripting/Include/Flow/ScriptInterfaceFlowHandler.h" #include "Scripting/Include/ScriptInterfaceLevel.h" +using namespace TEN::Collision::Point; using namespace TEN::Entities::Player; using namespace TEN::Input; +constexpr auto DEFLECT_STRAIGHT_ANGLE = ANGLE(5.0f); +constexpr auto DEFLECT_DIAGONAL_ANGLE = ANGLE(12.0f); +constexpr auto DEFLECT_STRAIGHT_ANGLE_CRAWL = ANGLE(2.0f); +constexpr auto DEFLECT_DIAGONAL_ANGLE_CRAWL = ANGLE(5.0f); + // ----------------------------- // COLLISION TEST FUNCTIONS // For State Control & Collision @@ -43,12 +50,12 @@ bool LaraDeflectEdge(ItemInfo* item, CollisionInfo* coll) if (coll->CollisionType == CollisionType::Left) { ShiftItem(item, coll); - item->Pose.Orientation.y += ANGLE(coll->DiagonalStepAtLeft() ? DEFLECT_DIAGONAL_ANGLE : DEFLECT_STRAIGHT_ANGLE); + item->Pose.Orientation.y += coll->DiagonalStepAtLeft() ? DEFLECT_DIAGONAL_ANGLE : DEFLECT_STRAIGHT_ANGLE; } else if (coll->CollisionType == CollisionType::Right) { ShiftItem(item, coll); - item->Pose.Orientation.y -= ANGLE(coll->DiagonalStepAtRight() ? DEFLECT_DIAGONAL_ANGLE : DEFLECT_STRAIGHT_ANGLE); + item->Pose.Orientation.y -= coll->DiagonalStepAtRight() ? DEFLECT_DIAGONAL_ANGLE : DEFLECT_STRAIGHT_ANGLE; } else if (coll->LastBridgeItemNumber != NO_VALUE) { @@ -118,11 +125,11 @@ bool LaraDeflectEdgeJump(ItemInfo* item, CollisionInfo* coll) switch (coll->CollisionType) { case CollisionType::Left: - item->Pose.Orientation.y += ANGLE(DEFLECT_STRAIGHT_ANGLE); + item->Pose.Orientation.y += DEFLECT_STRAIGHT_ANGLE; break; case CollisionType::Right: - item->Pose.Orientation.y -= ANGLE(DEFLECT_STRAIGHT_ANGLE); + item->Pose.Orientation.y -= DEFLECT_STRAIGHT_ANGLE; break; case CollisionType::Top: @@ -154,11 +161,11 @@ void LaraSlideEdgeJump(ItemInfo* item, CollisionInfo* coll) switch (coll->CollisionType) { case CollisionType::Left: - item->Pose.Orientation.y += ANGLE(DEFLECT_STRAIGHT_ANGLE); + item->Pose.Orientation.y += DEFLECT_STRAIGHT_ANGLE; break; case CollisionType::Right: - item->Pose.Orientation.y -= ANGLE(DEFLECT_STRAIGHT_ANGLE); + item->Pose.Orientation.y -= DEFLECT_STRAIGHT_ANGLE; break; case CollisionType::Top: @@ -196,12 +203,12 @@ bool LaraDeflectEdgeCrawl(ItemInfo* item, CollisionInfo* coll) if (coll->CollisionType == CollisionType::Left) { ShiftItem(item, coll); - item->Pose.Orientation.y += ANGLE(coll->DiagonalStepAtLeft() ? DEFLECT_DIAGONAL_ANGLE_CRAWL : DEFLECT_STRAIGHT_ANGLE_CRAWL); + item->Pose.Orientation.y += coll->DiagonalStepAtLeft() ? DEFLECT_DIAGONAL_ANGLE_CRAWL : DEFLECT_STRAIGHT_ANGLE_CRAWL; } else if (coll->CollisionType == CollisionType::Right) { ShiftItem(item, coll); - item->Pose.Orientation.y -= ANGLE(coll->DiagonalStepAtRight() ? DEFLECT_DIAGONAL_ANGLE_CRAWL : DEFLECT_STRAIGHT_ANGLE_CRAWL); + item->Pose.Orientation.y -= coll->DiagonalStepAtRight() ? DEFLECT_DIAGONAL_ANGLE_CRAWL : DEFLECT_STRAIGHT_ANGLE_CRAWL; } return false; @@ -227,12 +234,12 @@ bool LaraDeflectEdgeMonkey(ItemInfo* item, CollisionInfo* coll) if (coll->CollisionType == CollisionType::Left) { ShiftItem(item, coll); - item->Pose.Orientation.y += ANGLE(coll->DiagonalStepAtLeft() ? DEFLECT_DIAGONAL_ANGLE : DEFLECT_STRAIGHT_ANGLE); + item->Pose.Orientation.y += coll->DiagonalStepAtLeft() ? DEFLECT_DIAGONAL_ANGLE : DEFLECT_STRAIGHT_ANGLE; } else if (coll->CollisionType == CollisionType::Right) { ShiftItem(item, coll); - item->Pose.Orientation.y -= ANGLE(coll->DiagonalStepAtRight() ? DEFLECT_DIAGONAL_ANGLE : DEFLECT_STRAIGHT_ANGLE); + item->Pose.Orientation.y -= coll->DiagonalStepAtRight() ? DEFLECT_DIAGONAL_ANGLE : DEFLECT_STRAIGHT_ANGLE; } return false; @@ -489,19 +496,32 @@ void LaraSurfaceCollision(ItemInfo* item, CollisionInfo* coll) item->Pose.Orientation.y -= ANGLE(5.0f); } - auto pointColl = GetCollision(item); - int waterHeight = GetWaterHeight(item->Pose.Position.x, item->Pose.Position.y, item->Pose.Position.z, item->RoomNumber); - - if ((pointColl.Position.Floor - item->Pose.Position.y) < SWIM_WATER_DEPTH) + auto pointColl = GetPointCollision(*item); + if ((pointColl.GetFloorHeight() - item->Pose.Position.y) < SWIM_WATER_DEPTH) { TestPlayerWaterStepOut(item, coll); } - else if ((waterHeight - item->Pose.Position.y) <= -LARA_HEADROOM) + else if ((pointColl.GetWaterTopHeight() - item->Pose.Position.y) <= -LARA_HEADROOM) { SetLaraSwimDiveAnimation(item); } } +void LaraDefaultCollision(ItemInfo* item, CollisionInfo* coll) +{ + auto& player = GetLaraInfo(*item); + + player.Control.MoveAngle = item->Pose.Orientation.y; + coll->Setup.LowerFloorBound = STEPUP_HEIGHT; + coll->Setup.UpperFloorBound = -STEPUP_HEIGHT; + coll->Setup.LowerCeilingBound = 0; + coll->Setup.BlockFloorSlopeDown = true; + coll->Setup.BlockFloorSlopeUp = true; + coll->Setup.ForwardAngle = player.Control.MoveAngle; + GetCollisionInfo(coll, item); + LaraResetGravityStatus(item, coll); +} + void LaraSwimCollision(ItemInfo* item, CollisionInfo* coll) { auto* lara = GetLaraInfo(item); diff --git a/TombEngine/Game/Lara/lara_collide.h b/TombEngine/Game/Lara/lara_collide.h index 79a25d9ad..705cb373c 100644 --- a/TombEngine/Game/Lara/lara_collide.h +++ b/TombEngine/Game/Lara/lara_collide.h @@ -3,11 +3,6 @@ struct ItemInfo; struct CollisionInfo; -constexpr auto DEFLECT_STRAIGHT_ANGLE = 5.0f; -constexpr auto DEFLECT_DIAGONAL_ANGLE = 12.0f; -constexpr auto DEFLECT_STRAIGHT_ANGLE_CRAWL = 2.0f; -constexpr auto DEFLECT_DIAGONAL_ANGLE_CRAWL = 5.0f; - // ----------------------------- // COLLISION TEST FUNCTIONS // For State Control & Collision @@ -30,6 +25,7 @@ void GetLaraDeadlyBounds(); void LaraJumpCollision(ItemInfo* item, CollisionInfo* coll, short moveAngle); void LaraSurfaceCollision(ItemInfo* item, CollisionInfo* coll); void LaraSwimCollision(ItemInfo* item, CollisionInfo* coll); +void LaraDefaultCollision(ItemInfo* item, CollisionInfo* coll); void LaraWaterCurrent(ItemInfo* item, CollisionInfo* coll); diff --git a/TombEngine/Game/Lara/lara_fire.cpp b/TombEngine/Game/Lara/lara_fire.cpp index 5bac95a04..08a97f190 100644 --- a/TombEngine/Game/Lara/lara_fire.cpp +++ b/TombEngine/Game/Lara/lara_fire.cpp @@ -4,7 +4,7 @@ #include "Scripting/Include/Flow/ScriptInterfaceFlowHandler.h" #include "Game/animation.h" #include "Game/camera.h" -#include "Game/collision/sphere.h" +#include "Game/collision/Sphere.h" #include "Game/control/los.h" #include "Game/control/lot.h" #include "Game/effects/effects.h" @@ -31,6 +31,7 @@ #include "Specific/level.h" #include "Specific/trutils.h" +using namespace TEN::Collision::Sphere; using namespace TEN::Entities::Generic; using namespace TEN::Input; using namespace TEN::Math; @@ -842,32 +843,38 @@ FireWeaponType FireWeapon(LaraWeaponType weaponType, ItemInfo& targetEntity, Ite auto target = origin + (directionNorm * weapon.TargetDist); auto ray = Ray(origin, directionNorm); - int num = GetSpheres(&targetEntity, CreatureSpheres, SPHERES_SPACE_WORLD, Matrix::Identity); - int bestJointIndex = NO_VALUE; - float bestDistance = INFINITY; - for (int i = 0; i < num; i++) - { - auto sphere = BoundingSphere(Vector3(CreatureSpheres[i].x, CreatureSpheres[i].y, CreatureSpheres[i].z), CreatureSpheres[i].r); - float distance = 0.0f; - if (ray.Intersects(sphere, distance)) - { - if (distance < bestDistance) - { - bestDistance = distance; - bestJointIndex = i; - } - } - } - player.Control.Weapon.HasFired = true; player.Control.Weapon.Fired = true; - + auto vOrigin = GameVector(pos); short roomNumber = laraItem.RoomNumber; GetFloor(pos.x, pos.y, pos.z, &roomNumber); vOrigin.RoomNumber = roomNumber; - if (bestJointIndex < 0) + if (&targetEntity == nullptr) + { + auto vTarget = GameVector(target); + GetTargetOnLOS(&vOrigin, &vTarget, false, true); + return FireWeaponType::Miss; + } + + auto spheres = targetEntity.GetSpheres(); + int closestJointIndex = NO_VALUE; + float closestDist = INFINITY; + for (int i = 0; i < spheres.size(); i++) + { + float dist = 0.0f; + if (ray.Intersects(spheres[i], dist)) + { + if (dist < closestDist) + { + closestDist = dist; + closestJointIndex = i; + } + } + } + + if (closestJointIndex < 0) { auto vTarget = GameVector(target); GetTargetOnLOS(&vOrigin, &vTarget, false, true); @@ -876,13 +883,13 @@ FireWeaponType FireWeapon(LaraWeaponType weaponType, ItemInfo& targetEntity, Ite else { SaveGame::Statistics.Game.AmmoHits++; - target = origin + (directionNorm * bestDistance); + target = origin + (directionNorm * closestDist); auto vTarget = GameVector(target); // NOTE: It seems that entities hit by the player in the normal way must have GetTargetOnLOS return false. // It's strange, but this replicates original behaviour until we fully understand what is happening. if (!GetTargetOnLOS(&vOrigin, &vTarget, false, true)) - HitTarget(&laraItem, &targetEntity, &vTarget, weapon.Damage, false, bestJointIndex); + HitTarget(&laraItem, &targetEntity, &vTarget, weapon.Damage, false, closestJointIndex); return FireWeaponType::PossibleHit; } diff --git a/TombEngine/Game/Lara/lara_flare.cpp b/TombEngine/Game/Lara/lara_flare.cpp index 1e17c2e21..0cae11806 100644 --- a/TombEngine/Game/Lara/lara_flare.cpp +++ b/TombEngine/Game/Lara/lara_flare.cpp @@ -4,6 +4,7 @@ #include "Game/animation.h" #include "Game/camera.h" #include "Game/collision/collide_item.h" +#include "Game/collision/Point.h" #include "Game/control/control.h" #include "Game/effects/effects.h" #include "Game/effects/chaffFX.h" @@ -18,6 +19,7 @@ #include "Specific/clock.h" #include "Specific/level.h" +using namespace TEN::Collision::Point; using namespace TEN::Math; constexpr auto FLARE_LIFE_MAX = 60.0f * FPS; @@ -258,7 +260,7 @@ void DrawFlare(ItemInfo& laraItem) SoundEffect( SFX_TR4_FLARE_IGNITE_DRY, &laraItem.Pose, - TestEnvironment(ENV_FLAG_WATER, &laraItem) ? SoundEnvironment::Water : SoundEnvironment::Land); + TestEnvironment(ENV_FLAG_WATER, &laraItem) ? SoundEnvironment::ShallowWater : SoundEnvironment::Land); } DoFlareInHand(laraItem, player.Flare.Life); @@ -323,7 +325,7 @@ void CreateFlare(ItemInfo& laraItem, GAME_OBJECT_ID objectID, bool isThrown) flareItem.Pose.Position = pos; - int floorHeight = GetCollision(pos.x, pos.y, pos.z, laraItem.RoomNumber).Position.Floor; + int floorHeight = GetPointCollision(pos, laraItem.RoomNumber).GetFloorHeight(); auto isCollided = !GetCollidedObjects(flareItem, true, true).IsEmpty(); bool hasLanded = false; diff --git a/TombEngine/Game/Lara/lara_hang.cpp b/TombEngine/Game/Lara/lara_hang.cpp index f9dc87d44..432b1e1ba 100644 --- a/TombEngine/Game/Lara/lara_hang.cpp +++ b/TombEngine/Game/Lara/lara_hang.cpp @@ -146,10 +146,10 @@ void lara_col_hang(ItemInfo* item, CollisionInfo* coll) if (item->Animation.AnimNumber == LA_REACH_TO_HANG || item->Animation.AnimNumber == LA_HANG_IDLE) { - TestForObjectOnLedge(item, coll); - if (IsHeld(In::Forward)) { + TestForObjectOnLedge(item, coll); + if (coll->Front.Floor > -(CLICK(3.5f) - 46) && TestValidLedge(item, coll) && !coll->HitStatic) { diff --git a/TombEngine/Game/Lara/lara_helpers.cpp b/TombEngine/Game/Lara/lara_helpers.cpp index 80ea111fc..b307919d2 100644 --- a/TombEngine/Game/Lara/lara_helpers.cpp +++ b/TombEngine/Game/Lara/lara_helpers.cpp @@ -7,6 +7,7 @@ #include "Game/camera.h" #include "Game/collision/collide_room.h" #include "Game/collision/floordata.h" +#include "Game/collision/Point.h" #include "Game/control/control.h" #include "Game/control/volume.h" #include "Game/items.h" @@ -21,7 +22,6 @@ #include "Game/savegame.h" #include "Game/Setup.h" #include "Math/Math.h" -#include "Renderer/Renderer.h" #include "Scripting/Include/ScriptInterfaceLevel.h" #include "Sound/sound.h" #include "Specific/Input/Input.h" @@ -37,6 +37,7 @@ #include "Objects/TR4/Vehicles/motorbike.h" using namespace TEN::Collision::Floordata; +using namespace TEN::Collision::Point; using namespace TEN::Control::Volumes; using namespace TEN::Effects::Bubble; using namespace TEN::Effects::Drip; @@ -44,7 +45,6 @@ using namespace TEN::Entities::Player; using namespace TEN::Gui; using namespace TEN::Input; using namespace TEN::Math; -using namespace TEN::Renderer; // ----------------------------- // HELPER FUNCTIONS @@ -160,9 +160,9 @@ void HandlePlayerStatusEffects(ItemInfo& item, WaterStatus waterStatus, PlayerWa const auto& vehicleItem = g_Level.Items[player.Context.Vehicle]; if (vehicleItem.ObjectNumber == ID_UPV) { - auto pointColl = GetCollision(&item, 0, 0, CLICK(1)); + auto pointColl = GetPointCollision(item, 0, 0, CLICK(1)); - water.IsCold = (water.IsCold || TestEnvironment(ENV_FLAG_COLD, pointColl.RoomNumber)); + water.IsCold = (water.IsCold || TestEnvironment(ENV_FLAG_COLD, pointColl.GetRoomNumber())); if (water.IsCold) { player.Status.Exposure--; @@ -276,50 +276,7 @@ void HandlePlayerStatusEffects(ItemInfo& item, WaterStatus waterStatus, PlayerWa } } -static void UsePlayerMedipack(ItemInfo& item) -{ - auto& player = GetLaraInfo(item); - - // Can't use medipack; return early. - if (item.HitPoints <= 0 || - (item.HitPoints >= LARA_HEALTH_MAX && player.Status.Poison == 0)) - { - return; - } - - bool hasUsedMedipack = false; - - if (IsClicked(In::SmallMedipack) && - player.Inventory.TotalSmallMedipacks != 0) - { - hasUsedMedipack = true; - - item.HitPoints += LARA_HEALTH_MAX / 2; - if (item.HitPoints > LARA_HEALTH_MAX) - item.HitPoints = LARA_HEALTH_MAX; - - if (player.Inventory.TotalSmallMedipacks != -1) - player.Inventory.TotalSmallMedipacks--; - } - else if (IsClicked(In::LargeMedipack) && - player.Inventory.TotalLargeMedipacks != 0) - { - hasUsedMedipack = true; - item.HitPoints = LARA_HEALTH_MAX; - - if (player.Inventory.TotalLargeMedipacks != -1) - player.Inventory.TotalLargeMedipacks--; - } - - if (hasUsedMedipack) - { - player.Status.Poison = 0; - SaveGame::Statistics.Game.HealthUsed++; - SoundEffect(SFX_TR4_MENU_MEDI, nullptr, SoundEnvironment::Always); - } -} - -static std::optional GetPlayerScrolledWeaponType(const ItemInfo& item, LaraWeaponType currentWeaponType, bool getPrev) +static LaraWeaponType GetPlayerScrolledWeaponType(const ItemInfo& item, LaraWeaponType currentWeaponType, bool getPrev) { static const auto SCROLL_WEAPON_TYPES = std::vector { @@ -334,15 +291,15 @@ static std::optional GetPlayerScrolledWeaponType(const ItemInfo& LaraWeaponType::RocketLauncher }; - auto getNextIndex = [getPrev](unsigned int index) + auto getNextIndex = [getPrev](int index) { - return (index + (getPrev ? ((unsigned int)SCROLL_WEAPON_TYPES.size() - 1) : 1)) % (unsigned int)SCROLL_WEAPON_TYPES.size(); + return (index + (getPrev ? ((int)SCROLL_WEAPON_TYPES.size() - 1) : 1)) % (int)SCROLL_WEAPON_TYPES.size(); }; auto& player = GetLaraInfo(item); // Get vector index for current weapon type. - auto currentIndex = std::optional(std::nullopt); + auto currentIndex = NO_VALUE; for (int i = 0; i < SCROLL_WEAPON_TYPES.size(); i++) { if (SCROLL_WEAPON_TYPES[i] == currentWeaponType) @@ -352,13 +309,13 @@ static std::optional GetPlayerScrolledWeaponType(const ItemInfo& } } - // Invalid current weapon type; return nullopt. - if (!currentIndex.has_value()) - return std::nullopt; + // Invalid current weapon type; return None type. + if (currentIndex == NO_VALUE) + return LaraWeaponType::None; // Get next valid weapon type in sequence. - unsigned int nextIndex = getNextIndex(*currentIndex); - while (nextIndex != *currentIndex) + int nextIndex = getNextIndex(currentIndex); + while (nextIndex != currentIndex) { auto nextWeaponType = SCROLL_WEAPON_TYPES[nextIndex]; if (player.Weapons[(int)nextWeaponType].Present) @@ -367,8 +324,8 @@ static std::optional GetPlayerScrolledWeaponType(const ItemInfo& nextIndex = getNextIndex(nextIndex); } - // No valid weapon type; return nullopt. - return std::nullopt; + // No valid weapon type; return None type. + return LaraWeaponType::None; } void HandlePlayerQuickActions(ItemInfo& item) @@ -376,17 +333,21 @@ void HandlePlayerQuickActions(ItemInfo& item) auto& player = GetLaraInfo(item); // Handle medipacks. - if (IsClicked(In::SmallMedipack) || IsClicked(In::LargeMedipack)) - UsePlayerMedipack(item); + if (IsClicked(In::SmallMedipack)) + { + g_Gui.UseItem(item, GAME_OBJECT_ID::ID_SMALLMEDI_ITEM); + } + else if (IsClicked(In::LargeMedipack)) + { + g_Gui.UseItem(item, GAME_OBJECT_ID::ID_BIGMEDI_ITEM); + } // Handle weapon scroll request. if (IsClicked(In::PreviousWeapon) || IsClicked(In::NextWeapon)) { - bool getPrev = IsClicked(In::PreviousWeapon); - auto weaponType = GetPlayerScrolledWeaponType(item, player.Control.Weapon.GunType, getPrev); - - if (weaponType.has_value()) - player.Control.Weapon.RequestGunType = *weaponType; + auto weaponType = GetPlayerScrolledWeaponType(item, player.Control.Weapon.GunType, IsClicked(In::PreviousWeapon)); + if (weaponType != LaraWeaponType::None) + player.Control.Weapon.RequestGunType = weaponType; } // Handle weapon requests. @@ -419,7 +380,7 @@ void HandlePlayerQuickActions(ItemInfo& item) // TODO: 10th possible weapon, probably grapple gun. /*if (IsClicked(In::Weapon10) && player.Weapons[(int)LaraWeaponType::].Present) - player.Control.Weapon.RequestGunType = LaraWeaponType::;*/ + player.Control.Weapon.RequestGunType = LaraWeaponType::;*/ } bool CanPlayerLookAround(const ItemInfo& item) @@ -1029,7 +990,7 @@ void HandlePlayerAirBubbles(ItemInfo* item) { constexpr auto BUBBLE_COUNT_MAX = 3; - SoundEffect(SFX_TR4_LARA_BUBBLES, &item->Pose, SoundEnvironment::Water); + SoundEffect(SFX_TR4_LARA_BUBBLES, &item->Pose, SoundEnvironment::ShallowWater); const auto& level = *g_GameFlow->GetLevel(CurrentLevel); @@ -1083,6 +1044,8 @@ void HandlePlayerElevationChange(ItemInfo* item, CollisionInfo* coll) if (CanStepUp(*item, *coll)) { item->Animation.TargetState = LS_STEP_UP; + item->DisableInterpolation = true; + if (GetStateDispatch(item, GetAnimData(*item))) { item->Pose.Position.y += coll->Middle.Floor; @@ -1250,20 +1213,14 @@ LaraInfo*& GetLaraInfo(ItemInfo* item) PlayerWaterData GetPlayerWaterData(ItemInfo& item) { - bool isWater = TestEnvironment(ENV_FLAG_WATER, &item); - bool isSwamp = TestEnvironment(ENV_FLAG_SWAMP, &item); - bool isCold = TestEnvironment(ENV_FLAG_COLD, &item); - - int waterDepth = GetWaterDepth(&item); - int waterHeight = GetWaterHeight(&item); - - auto pointColl = GetCollision(item); - int heightFromWater = (waterHeight == NO_HEIGHT) ? NO_HEIGHT : (std::min(item.Pose.Position.y, pointColl.Position.Floor) - waterHeight); + auto pointColl = GetPointCollision(item); + int heightFromWater = (pointColl.GetWaterTopHeight() == NO_HEIGHT) ? + NO_HEIGHT : (std::min(item.Pose.Position.y, pointColl.GetFloorHeight()) - pointColl.GetWaterTopHeight()); return PlayerWaterData { - isWater, isSwamp, isCold, - waterDepth, waterHeight, heightFromWater + TestEnvironment(ENV_FLAG_WATER, &item), TestEnvironment(ENV_FLAG_SWAMP, &item), TestEnvironment(ENV_FLAG_COLD, &item), + pointColl.GetWaterBottomHeight(), pointColl.GetWaterTopHeight(), heightFromWater }; } @@ -1322,20 +1279,20 @@ static short GetLegacySlideHeadingAngle(const Vector3& floorNormal) short GetPlayerSlideHeadingAngle(ItemInfo* item, CollisionInfo* coll) { short headingAngle = coll->Setup.ForwardAngle; - auto pointColl = GetCollision(item); + auto pointColl = GetPointCollision(*item); // Ground is flat. - if (pointColl.FloorTilt == Vector2::Zero) + if (pointColl.GetFloorNormal() == -Vector3::UnitY) return coll->Setup.ForwardAngle; // Return slide heading angle. if (g_GameFlow->HasSlideExtended()) { - return Geometry::GetSurfaceAspectAngle(pointColl.FloorNormal); + return Geometry::GetSurfaceAspectAngle(pointColl.GetFloorNormal()); } else { - return GetLegacySlideHeadingAngle(pointColl.FloorNormal); + return GetLegacySlideHeadingAngle(pointColl.GetFloorNormal()); } } @@ -1510,7 +1467,7 @@ void UpdateLaraSubsuitAngles(ItemInfo* item) auto mul1 = (float)abs(lara->Control.Subsuit.Velocity[0]) / BLOCK(8); auto mul2 = (float)abs(lara->Control.Subsuit.Velocity[1]) / BLOCK(8); auto vol = ((mul1 + mul2) * 5.0f) + 0.5f; - SoundEffect(SFX_TR5_VEHICLE_DIVESUIT_ENGINE, &item->Pose, SoundEnvironment::Water, 1.0f + (mul1 + mul2), vol); + SoundEffect(SFX_TR5_VEHICLE_DIVESUIT_ENGINE, &item->Pose, SoundEnvironment::ShallowWater, 1.0f + (mul1 + mul2), vol); } } @@ -1524,7 +1481,7 @@ void ModulateLaraSlideVelocity(ItemInfo* item, CollisionInfo* coll) if (g_GameFlow->HasSlideExtended()) { - auto probe = GetCollision(item); + auto probe = GetPointCollision(*item); short minSlideAngle = ANGLE(33.75f); //short steepness = Geometry::GetSurfaceSlopeAngle(probe.FloorTilt); //short direction = Geometry::GetSurfaceAspectAngle(probe.FloorTilt); @@ -1533,7 +1490,7 @@ void ModulateLaraSlideVelocity(ItemInfo* item, CollisionInfo* coll) //int slideVelocity = std::min(minVelocity + 10 * (steepness * velocityMultiplier), LARA_TERMINAL_VELOCITY); //short deltaAngle = abs((short)(direction - item->Pose.Orientation.y)); - //g_Renderer.PrintDebugMessage("%d", slideVelocity); + //PrintDebugMessage("%d", slideVelocity); //lara->ExtraVelocity.x += slideVelocity; //lara->ExtraVelocity.y += slideVelocity * phd_sin(steepness); @@ -1545,7 +1502,7 @@ void ModulateLaraSlideVelocity(ItemInfo* item, CollisionInfo* coll) void AlignLaraToSurface(ItemInfo* item, float alpha) { // Determine relative orientation adhering to floor normal. - auto floorNormal = GetCollision(item).FloorNormal; + auto floorNormal = GetPointCollision(*item).GetFloorNormal(); auto orient = Geometry::GetRelOrientToNormal(item->Pose.Orientation.y, floorNormal); // Apply extra rotation according to alpha. diff --git a/TombEngine/Game/Lara/lara_initialise.cpp b/TombEngine/Game/Lara/lara_initialise.cpp index b2775ed81..507491f1e 100644 --- a/TombEngine/Game/Lara/lara_initialise.cpp +++ b/TombEngine/Game/Lara/lara_initialise.cpp @@ -1,6 +1,7 @@ #include "framework.h" #include "Game/Lara/lara_initialise.h" +#include "Game/collision/Point.h" #include "Game/Hud/Hud.h" #include "Game/items.h" #include "Game/Lara/lara.h" @@ -12,12 +13,17 @@ #include "Game/Lara/PlayerStateMachine.h" #include "Game/Setup.h" #include "Objects/TR2/Vehicles/skidoo.h" +#include "Objects/TR2/Vehicles/speedboat.h" #include "Objects/TR3/Vehicles/kayak.h" +#include "Objects/TR3/Vehicles/minecart.h" #include "Objects/TR3/Vehicles/quad_bike.h" +#include "Objects/TR3/Vehicles/rubber_boat.h" +#include "Objects/TR3/Vehicles/upv.h" #include "Objects/TR4/Vehicles/jeep.h" #include "Objects/TR4/Vehicles/motorbike.h" #include "Specific/level.h" +using namespace TEN::Collision::Point; using namespace TEN::Entities::Player; using namespace TEN::Hud; @@ -120,6 +126,9 @@ void InitializeLaraAnims(ItemInfo* item) player.LeftArm.Locked = false; player.RightArm.Locked = false; + if (PlayerVehicleObjectID != GAME_OBJECT_ID::ID_NO_OBJECT) + return; + if (TestEnvironment(ENV_FLAG_WATER, item)) { SetAnimation(item, LA_UNDERWATER_IDLE); @@ -131,8 +140,8 @@ void InitializeLaraAnims(ItemInfo* item) player.Control.WaterStatus = WaterStatus::Dry; // Allow player to start in crawl idle anim if start position is too low. - auto pointColl = GetCollision(item); - if (abs(pointColl.Position.Ceiling - pointColl.Position.Floor) < LARA_HEIGHT) + auto pointColl = GetPointCollision(*item); + if (abs(pointColl.GetCeilingHeight() - pointColl.GetFloorHeight()) < LARA_HEIGHT) { SetAnimation(item, LA_CRAWL_IDLE); player.Control.IsLow = @@ -173,7 +182,7 @@ void InitializeLaraStartPosition(ItemInfo& playerItem) playerItem.Location.Height = playerItem.Pose.Position.y; } -static void InitializePlayerVehicle(ItemInfo& playerItem) +void InitializePlayerVehicle(ItemInfo& playerItem) { if (PlayerVehicleObjectID == GAME_OBJECT_ID::ID_NO_OBJECT) return; @@ -185,7 +194,6 @@ static void InitializePlayerVehicle(ItemInfo& playerItem) // Restore vehicle. TENLog("Transferring vehicle " + GetObjectName(PlayerVehicleObjectID) + " from the previous level."); vehicle->Pose = playerItem.Pose; - ItemNewRoom(vehicle->Index, playerItem.RoomNumber); SetLaraVehicle(&playerItem, vehicle); playerItem.Animation = PlayerAnim; @@ -195,6 +203,7 @@ static void InitializePlayerVehicle(ItemInfo& playerItem) { case GAME_OBJECT_ID::ID_KAYAK: InitializeKayak(vehicle->Index); + KayakPaddleTake(GetKayakInfo(&g_Level.Items[vehicle->Index]), &playerItem); break; case GAME_OBJECT_ID::ID_MOTORBIKE: @@ -213,9 +222,38 @@ static void InitializePlayerVehicle(ItemInfo& playerItem) InitializeSkidoo(vehicle->Index); break; + case GAME_OBJECT_ID::ID_MINECART: + MinecartWrenchTake(GetMinecartInfo(&g_Level.Items[vehicle->Index]), &playerItem); + break; + + case GAME_OBJECT_ID::ID_SPEEDBOAT: + InitializeSpeedboat(vehicle->Index); + DoSpeedboatMount(&g_Level.Items[vehicle->Index], &playerItem, VehicleMountType::LevelStart); + break; + + case GAME_OBJECT_ID::ID_RUBBER_BOAT: + InitializeRubberBoat(vehicle->Index); + DoRubberBoatMount(&g_Level.Items[vehicle->Index], &playerItem, VehicleMountType::LevelStart); + break; + + case GAME_OBJECT_ID::ID_UPV: + DoUPVMount(&g_Level.Items[vehicle->Index], &playerItem, VehicleMountType::LevelStart); + GetUPVInfo(&g_Level.Items[vehicle->Index])->Flags = UPVFlags::UPV_FLAG_CONTROL; + break; + default: break; } + + // HACK: Reset activity status because boats need to be on active item linked list. + + if (vehicle->ObjectNumber == GAME_OBJECT_ID::ID_RUBBER_BOAT || + vehicle->ObjectNumber == GAME_OBJECT_ID::ID_SPEEDBOAT) + { + RemoveActiveItem(vehicle->Index, false); + AddActiveItem(vehicle->Index); + g_Level.Items[vehicle->Index].Status = ITEM_ACTIVE; + } } void InitializeLaraLevelJump(ItemInfo* item, LaraInfo* playerBackup) @@ -258,9 +296,6 @@ void InitializeLaraLevelJump(ItemInfo* item, LaraInfo* playerBackup) // Restore hit points. item->HitPoints = PlayerHitPoints; - - // Restore vehicle. - InitializePlayerVehicle(*item); } void InitializeLaraDefaultInventory(ItemInfo& item) diff --git a/TombEngine/Game/Lara/lara_initialise.h b/TombEngine/Game/Lara/lara_initialise.h index a218a7ccf..86ecd4829 100644 --- a/TombEngine/Game/Lara/lara_initialise.h +++ b/TombEngine/Game/Lara/lara_initialise.h @@ -9,3 +9,4 @@ void InitializeLaraAnims(ItemInfo* item); void InitializeLaraStartPosition(ItemInfo& playerItem); void InitializeLaraLevelJump(ItemInfo* item, LaraInfo* playerBackup); void InitializeLaraDefaultInventory(ItemInfo& item); +void InitializePlayerVehicle(ItemInfo& playerItem); \ No newline at end of file diff --git a/TombEngine/Game/Lara/lara_jump.cpp b/TombEngine/Game/Lara/lara_jump.cpp index b84506aac..5b940035f 100644 --- a/TombEngine/Game/Lara/lara_jump.cpp +++ b/TombEngine/Game/Lara/lara_jump.cpp @@ -64,7 +64,7 @@ void lara_as_jump_forward(ItemInfo* item, CollisionInfo* coll) { DoLaraFallDamage(item); - if (item->HitPoints <= 0) USE_FEATURE_IF_CPP20([[unlikely]]) + if (item->HitPoints <= 0) item->Animation.TargetState = LS_DEATH; else if (IsHeld(In::Forward) && !IsHeld(In::Walk) && player.Control.WaterStatus != WaterStatus::Wade) @@ -146,7 +146,7 @@ void lara_as_freefall(ItemInfo* item, CollisionInfo* coll) if (item->HitPoints <= 0) item->Animation.TargetState = LS_DEATH; - else USE_FEATURE_IF_CPP20([[likely]]) + else item->Animation.TargetState = LS_IDLE; SetLaraLand(item, coll); @@ -202,7 +202,7 @@ void lara_as_reach(ItemInfo* item, CollisionInfo* coll) if (item->HitPoints <= 0) item->Animation.TargetState = LS_DEATH; - else USE_FEATURE_IF_CPP20([[likely]]) + else item->Animation.TargetState = LS_IDLE; SetLaraLand(item, coll); @@ -414,7 +414,7 @@ void lara_as_jump_back(ItemInfo* item, CollisionInfo* coll) if (item->HitPoints <= 0) item->Animation.TargetState = LS_DEATH; - else USE_FEATURE_IF_CPP20([[likely]]) + else item->Animation.TargetState = LS_IDLE; SetLaraLand(item, coll); @@ -468,7 +468,7 @@ void lara_as_jump_right(ItemInfo* item, CollisionInfo* coll) if (item->HitPoints <= 0) item->Animation.TargetState = LS_DEATH; - else USE_FEATURE_IF_CPP20([[likely]]) + else item->Animation.TargetState = LS_IDLE; SetLaraLand(item, coll); @@ -523,7 +523,7 @@ void lara_as_jump_left(ItemInfo* item, CollisionInfo* coll) if (item->HitPoints <= 0) item->Animation.TargetState = LS_DEATH; - else USE_FEATURE_IF_CPP20([[likely]]) + else item->Animation.TargetState = LS_IDLE; SetLaraLand(item, coll); @@ -576,7 +576,7 @@ void lara_as_jump_up(ItemInfo* item, CollisionInfo* coll) { if (item->HitPoints <= 0) item->Animation.TargetState = LS_DEATH; - else USE_FEATURE_IF_CPP20([[likely]]) + else item->Animation.TargetState = LS_IDLE; SetLaraLand(item, coll); @@ -673,7 +673,7 @@ void lara_as_fall_back(ItemInfo* item, CollisionInfo* coll) if (item->HitPoints <= 0) item->Animation.TargetState = LS_DEATH; - else USE_FEATURE_IF_CPP20([[likely]]) + else item->Animation.TargetState = LS_IDLE; SetLaraLand(item, coll); @@ -749,7 +749,7 @@ void lara_as_swan_dive(ItemInfo* item, CollisionInfo* coll) item->Animation.TargetState = LS_CROUCH_IDLE; TranslateItem(item, coll->Setup.ForwardAngle, CLICK(0.5f)); // HACK: Move forward to avoid standing up or falling out on an edge. } - else USE_FEATURE_IF_CPP20([[likely]]) + else item->Animation.TargetState = LS_IDLE; SetLaraLand(item, coll); @@ -823,7 +823,7 @@ void lara_as_freefall_dive(ItemInfo* item, CollisionInfo* coll) item->Animation.TargetState = LS_DEATH; Rumble(0.5f, 0.2f); } - else USE_FEATURE_IF_CPP20([[likely]]) + else item->Animation.TargetState = LS_IDLE; SetLaraLand(item, coll); diff --git a/TombEngine/Game/Lara/lara_objects.cpp b/TombEngine/Game/Lara/lara_objects.cpp index bee098072..3f4989534 100644 --- a/TombEngine/Game/Lara/lara_objects.cpp +++ b/TombEngine/Game/Lara/lara_objects.cpp @@ -8,6 +8,7 @@ #include "Game/items.h" #include "Game/Lara/PlayerContext.h" #include "Game/Lara/lara.h" +#include "Game/Lara/lara_collide.h" #include "Game/Lara/lara_helpers.h" #include "Game/Lara/lara_tests.h" #include "Objects/Generic/Object/rope.h" @@ -45,6 +46,12 @@ void lara_as_pickup(ItemInfo* item, CollisionInfo* coll) item->Animation.TargetState = GetNextAnimState(item); } +void lara_col_pickup(ItemInfo* item, CollisionInfo* coll) +{ + LaraDefaultCollision(item, coll); + ShiftItem(item, coll); +} + // State: LS_PICKUP_FLARE (67) // Collision: lara_default_col() void lara_as_pickup_flare(ItemInfo* item, CollisionInfo* coll) diff --git a/TombEngine/Game/Lara/lara_objects.h b/TombEngine/Game/Lara/lara_objects.h index 18636c583..c53ffd84d 100644 --- a/TombEngine/Game/Lara/lara_objects.h +++ b/TombEngine/Game/Lara/lara_objects.h @@ -14,6 +14,7 @@ struct CollisionInfo; void lara_as_pickup(ItemInfo* item, CollisionInfo* coll); void lara_as_pickup_flare(ItemInfo* item, CollisionInfo* coll); +void lara_col_pickup(ItemInfo* item, CollisionInfo* coll); // ------ // SWITCH diff --git a/TombEngine/Game/Lara/lara_one_gun.cpp b/TombEngine/Game/Lara/lara_one_gun.cpp index 053de051c..4270b6c9b 100644 --- a/TombEngine/Game/Lara/lara_one_gun.cpp +++ b/TombEngine/Game/Lara/lara_one_gun.cpp @@ -4,6 +4,7 @@ #include "Game/animation.h" #include "Game/camera.h" #include "Game/collision/collide_item.h" +#include "Game/collision/Point.h" #include "Game/control/box.h" #include "Game/control/control.h" #include "Game/control/los.h" @@ -31,6 +32,7 @@ #include "Specific/Input/Input.h" #include "Specific/level.h" +using namespace TEN::Collision::Point; using namespace TEN::Effects::Bubble; using namespace TEN::Effects::Drip; using namespace TEN::Effects::Environment; @@ -426,7 +428,7 @@ void FireShotgun(ItemInfo& laraItem) player.RightArm.GunFlash = Weapons[(int)LaraWeaponType::Shotgun].FlashTime; - SoundEffect(SFX_TR4_EXPLOSION1, &laraItem.Pose, TestEnvironment(ENV_FLAG_WATER, &laraItem) ? SoundEnvironment::Water : SoundEnvironment::Land); + SoundEffect(SFX_TR4_EXPLOSION1, &laraItem.Pose, TestEnvironment(ENV_FLAG_WATER, &laraItem) ? SoundEnvironment::ShallowWater : SoundEnvironment::Land); SoundEffect(Weapons[(int)LaraWeaponType::Shotgun].SampleNum, &laraItem.Pose); Rumble(0.5f, 0.2f); @@ -600,7 +602,7 @@ bool FireHarpoon(ItemInfo& laraItem, const std::optional& pose) auto jointPos = GetJointPosition(&laraItem, LM_RHAND, Vector3i(-2, 373, 77)); harpoonItem.RoomNumber = laraItem.RoomNumber; - int floorHeight = GetCollision(jointPos.x, jointPos.y, jointPos.z, harpoonItem.RoomNumber).Position.Floor; + int floorHeight = GetPointCollision(jointPos, harpoonItem.RoomNumber).GetFloorHeight(); if (floorHeight >= jointPos.y) { harpoonItem.Pose.Position = jointPos; @@ -693,7 +695,7 @@ void FireGrenade(ItemInfo& laraItem) grenadeItem.Pose.Position = jointPos; auto smokePos = jointPos; - int floorHeight = GetCollision(jointPos.x, jointPos.y, jointPos.z, grenadeItem.RoomNumber).Position.Floor; + int floorHeight = GetPointCollision(jointPos, grenadeItem.RoomNumber).GetFloorHeight(); if (floorHeight < jointPos.y) { grenadeItem.Pose.Position.x = laraItem.Pose.Position.x; @@ -1027,9 +1029,11 @@ void FireCrossbow(ItemInfo& laraItem, const std::optional& pose) boltItem.RoomNumber = laraItem.RoomNumber; - int floorHeight = GetCollision(jointPos.x, jointPos.y, jointPos.z, boltItem.RoomNumber).Position.Floor; + int floorHeight = GetPointCollision(jointPos, boltItem.RoomNumber).GetFloorHeight(); if (floorHeight >= jointPos.y) + { boltItem.Pose.Position = jointPos; + } else { boltItem.Pose.Position = Vector3i(laraItem.Pose.Position.x, jointPos.y, laraItem.Pose.Position.z); @@ -1418,20 +1422,20 @@ bool EmitFromProjectile(ItemInfo& projectile, ProjectileType type) return true; } -bool TestProjectileNewRoom(ItemInfo& item, const CollisionResult& coll) +bool TestProjectileNewRoom(ItemInfo& item, PointCollisionData& pointColl) { // Check if projectile changed room. - if (item.RoomNumber == coll.RoomNumber) + if (item.RoomNumber == pointColl.GetRoomNumber()) return false; // If currently in water and previously on land, spawn ripple. - if (TestEnvironment(ENV_FLAG_WATER, item.RoomNumber) != TestEnvironment(ENV_FLAG_WATER, coll.RoomNumber)) + if (TestEnvironment(ENV_FLAG_WATER, item.RoomNumber) != TestEnvironment(ENV_FLAG_WATER, pointColl.GetRoomNumber())) { const auto& player = GetLaraInfo(item); - int floorDiff = abs(coll.Position.Floor - item.Pose.Position.y); - int ceilingDiff = abs(coll.Position.Ceiling - item.Pose.Position.y); - int yPoint = (floorDiff > ceilingDiff) ? coll.Position.Ceiling : coll.Position.Floor; + int floorDiff = abs(pointColl.GetFloorHeight() - item.Pose.Position.y); + int ceilingDiff = abs(pointColl.GetCeilingHeight() - item.Pose.Position.y); + int yPoint = (floorDiff > ceilingDiff) ? pointColl.GetCeilingHeight() : pointColl.GetFloorHeight(); if (player.Control.Weapon.GunType != LaraWeaponType::GrenadeLauncher && player.Control.Weapon.GunType != LaraWeaponType::RocketLauncher) { @@ -1447,7 +1451,7 @@ bool TestProjectileNewRoom(ItemInfo& item, const CollisionResult& coll) } } - ItemNewRoom(item.Index, coll.RoomNumber); + ItemNewRoom(item.Index, pointColl.GetRoomNumber()); return true; } @@ -1475,7 +1479,7 @@ void ExplodeProjectile(ItemInfo& item, const Vector3i& prevPos) void HandleProjectile(ItemInfo& projectile, ItemInfo& emitter, const Vector3i& prevPos, ProjectileType type, int damage) { - auto pointColl = GetCollision(&projectile); + auto pointColl = GetPointCollision(projectile); bool hasHit = false; bool hasHitNotByEmitter = false; @@ -1485,8 +1489,8 @@ void HandleProjectile(ItemInfo& projectile, ItemInfo& emitter, const Vector3i& p // For non-grenade projectiles, check for room collision. if (type < ProjectileType::Grenade) { - if (pointColl.Position.Floor < projectile.Pose.Position.y || - pointColl.Position.Ceiling > projectile.Pose.Position.y) + if (pointColl.GetFloorHeight() < projectile.Pose.Position.y || + pointColl.GetCeilingHeight() > projectile.Pose.Position.y) { hasHit = hasHitNotByEmitter = true; } @@ -1559,7 +1563,7 @@ void HandleProjectile(ItemInfo& projectile, ItemInfo& emitter, const Vector3i& p break; // Run through statics. - for (auto* staticPtr : collObjects.StaticPtrs) + for (auto* staticPtr : collObjects.Statics) { hasHit = hasHitNotByEmitter = doShatter = true; doExplosion = isExplosive; @@ -1580,7 +1584,7 @@ void HandleProjectile(ItemInfo& projectile, ItemInfo& emitter, const Vector3i& p } // Run through items. - for (auto* itemPtr : collObjects.ItemPtrs) + for (auto* itemPtr : collObjects.Items) { // Object was already affected by collision, skip it. if (std::find(affectedObjects.begin(), affectedObjects.end(), itemPtr->Index) != affectedObjects.end()) diff --git a/TombEngine/Game/Lara/lara_one_gun.h b/TombEngine/Game/Lara/lara_one_gun.h index 382d76567..b583b7eba 100644 --- a/TombEngine/Game/Lara/lara_one_gun.h +++ b/TombEngine/Game/Lara/lara_one_gun.h @@ -39,6 +39,7 @@ void HarpoonBoltControl(short itemNumber); void FireGrenade(ItemInfo& laraItem); void GrenadeControl(short itemNumber); void FireRocket(ItemInfo& laraItem); +void FireRocket(ItemInfo& laraItem); void RocketControl(short itemNumber); void FireCrossbow(ItemInfo& laraItem, const std::optional& pose = std::nullopt); void FireCrossBowFromLaserSight(ItemInfo& laraItem, GameVector* origin, GameVector* target); diff --git a/TombEngine/Game/Lara/lara_overhang.cpp b/TombEngine/Game/Lara/lara_overhang.cpp index 814d371ff..12f04361e 100644 --- a/TombEngine/Game/Lara/lara_overhang.cpp +++ b/TombEngine/Game/Lara/lara_overhang.cpp @@ -2,8 +2,9 @@ #include "Game/Lara/lara_overhang.h" #include "Game/camera.h" -#include "Game/collision/floordata.h" #include "Game/collision/collide_room.h" +#include "Game/collision/floordata.h" +#include "Game/collision/Point.h" #include "Game/control/control.h" #include "Game/Lara/lara.h" #include "Game/Lara/lara_climb.h" @@ -16,6 +17,8 @@ #include "Specific/Input/Input.h" #include "Specific/level.h" +using namespace TEN::Collision::Floordata; +using namespace TEN::Collision::Point; using namespace TEN::Entities::Generic; using namespace TEN::Input; @@ -324,14 +327,14 @@ void lara_col_slopeclimb(ItemInfo* item, CollisionInfo* coll) auto up = Vector3i(item->Pose.Position.x - slopeData.Offset.x, item->Pose.Position.y - CLICK(1), item->Pose.Position.z - slopeData.Offset.z); auto down = Vector3i(item->Pose.Position.x + slopeData.Offset.x, item->Pose.Position.y + CLICK(1), item->Pose.Position.z + slopeData.Offset.z); - auto probeNow = GetCollision(now.x, now.y, now.z, item->RoomNumber); - auto probeUp = GetCollision(up.x, up.y, up.z, item->RoomNumber); - auto probeDown = GetCollision(down.x, down.y, down.z, item->RoomNumber); + auto probeNow = GetPointCollision(now, item->RoomNumber); + auto probeUp = GetPointCollision(up, item->RoomNumber); + auto probeDown = GetPointCollision(down, item->RoomNumber); if (item->Animation.AnimNumber == LA_OVERHANG_LADDER_SLOPE_CONCAVE) return; - item->Pose.Position.y = probeNow.Position.Ceiling + HEIGHT_ADJUST; + item->Pose.Position.y = probeNow.GetCeilingHeight() + HEIGHT_ADJUST; // Drop down if action not pressed. if (!IsHeld(In::Action)) @@ -351,13 +354,13 @@ void lara_col_slopeclimb(ItemInfo* item, CollisionInfo* coll) if (IsHeld(In::Forward)) { // Test for ledge over slope. - short tempRoom = probeUp.Block->GetNextRoomNumber(up.x, up.z, false).value_or(NO_VALUE); + short tempRoom = probeUp.GetSector().GetNextRoomNumber(up.x, up.z, false).value_or(NO_VALUE); if (tempRoom != NO_VALUE) { - auto probeLedge = GetCollision(now.x, now.y - CLICK(3), now.z, tempRoom); + auto probeLedge = GetPointCollision(Vector3i(now.x, now.y - CLICK(3), now.z), tempRoom); - if ((probeLedge.Position.Floor - probeLedge.Position.Ceiling) >= CLICK(3) && - abs((item->Pose.Position.y - (CLICK(2.5f) + 48)) - probeLedge.Position.Floor) < 64) + if ((probeLedge.GetFloorHeight() - probeLedge.GetCeilingHeight()) >= CLICK(3) && + abs((item->Pose.Position.y - (CLICK(2.5f) + 48)) - probeLedge.GetFloorHeight()) < 64) { AlignToEdge(item, FORWARD_ALIGNMENT); SetAnimation(item, LA_OVERHANG_LEDGE_VAULT_START); // Ledge climb-up from slope. @@ -365,32 +368,32 @@ void lara_col_slopeclimb(ItemInfo* item, CollisionInfo* coll) } // Test for slope to overhead ladder transition (convex). - if (GetClimbFlags(probeUp.BottomBlock) & slopeData.ClimbOrient && + if (GetClimbFlags(&probeUp.GetBottomSector()) & slopeData.ClimbOrient && InStrip(item->Pose.Position.x, item->Pose.Position.z, item->Pose.Orientation.y, CLICK(3), CLICK(4))) { - if (GetCollision(probeUp.Block, up.x, up.y, up.z).Position.Ceiling - item->Pose.Position.y <= (BLOCK(1.5f) - 80)) // Check if a wall is actually there. - { - AlignToEdge(item, FORWARD_ALIGNMENT); - SetAnimation(item, LA_OVERHANG_SLOPE_LADDER_CONVEX_START); - } + //if (GetPointCollision(probeUp.Block, up.x, up.y, up.z).GetCeilingHeight() - item->Pose.Position.y <= (BLOCK(1.5f) - 80)) // Check if a wall is actually there. + //{ + // AlignToEdge(item, FORWARD_ALIGNMENT); + // SetAnimation(item, LA_OVERHANG_SLOPE_LADDER_CONVEX_START); + //} } // Test for monkey at next position. - if (probeUp.BottomBlock->Flags.Monkeyswing) + if (probeUp.GetBottomSector().Flags.Monkeyswing) { - int yDelta = probeUp.Position.Ceiling - probeNow.Position.Ceiling; + int yDelta = probeUp.GetCeilingHeight() - probeNow.GetCeilingHeight(); int height; // Height variable for bridge ceiling functions. // Test for upwards slope to climb. short bridge = FindBridge(4, item->Pose.Orientation.y, up, &height, -CLICK(2.5f), -CLICK(1.5f)); - if (yDelta >= -CLICK(1.25f) && yDelta <= -CLICK(0.75f) && (SlopeCheck(probeUp.CeilingTilt, slopeData.Goal) || bridge >= 0)) + if (yDelta >= -CLICK(1.25f) && yDelta <= -CLICK(0.75f) && (SlopeCheck(GetSurfaceTilt(probeUp.GetCeilingNormal(), false).ToVector2(), slopeData.Goal) || bridge >= 0)) { // Do one more check for wall/ceiling step 2 * offX / Z further to avoid lara sinking her head in wall/step. - auto probeWall = GetCollision((up.x - slopeData.Offset.x), (up.y - CLICK(1)), (up.z - slopeData.Offset.z), item->RoomNumber); + auto probeWall = GetPointCollision(Vector3i((up.x - slopeData.Offset.x), (up.y - CLICK(1)), (up.z - slopeData.Offset.z)), item->RoomNumber); - if (!probeWall.Block->IsWall((up.x - slopeData.Offset.x), (up.z - slopeData.Offset.z)) && - (probeNow.Position.Ceiling - probeWall.Position.Ceiling) > CLICK(0.5f)) // No wall or downward ceiling step. + if (!probeWall.GetSector().IsWall((up.x - slopeData.Offset.x), (up.z - slopeData.Offset.z)) && + (probeNow.GetCeilingHeight() - probeWall.GetCeilingHeight()) > CLICK(0.5f)) // No wall or downward ceiling step. { TranslateItem(item, 0, -CLICK(1), -CLICK(1)); SetAnimation(item, item->Animation.AnimNumber == LA_OVERHANG_IDLE_LEFT ? LA_OVERHANG_CLIMB_UP_LEFT : LA_OVERHANG_CLIMB_UP_RIGHT); @@ -405,7 +408,7 @@ void lara_col_slopeclimb(ItemInfo* item, CollisionInfo* coll) // HACK: because of the different calculations of bridge height in TR4 and TEN, we need to lower yDiff tolerance to 0.9f. if (yDelta > -CLICK(0.9f) && yDelta <= -CLICK(0.5f) && - ((abs(probeUp.CeilingTilt.x) <= 2 && abs(probeUp.CeilingTilt.y) <= 2) || bridge >= 0)) + ((abs(GetSurfaceTilt(probeUp.GetCeilingNormal(), false).ToVector2().x) <= 2 && abs(GetSurfaceTilt(probeUp.GetCeilingNormal(), false).ToVector2().y) <= 2) || bridge >= 0)) { SetAnimation(item, LA_OVERHANG_SLOPE_MONKEY_CONCAVE); // Slope to overhead monkey transition (concave). } @@ -413,30 +416,30 @@ void lara_col_slopeclimb(ItemInfo* item, CollisionInfo* coll) } else if (IsHeld(In::Back)) { - if ((GetClimbFlags(GetCollision(probeNow.Block, item->Pose.Position.x, item->Pose.Position.y, item->Pose.Position.z).BottomBlock) & slopeData.ClimbOrient) && - InStrip(item->Pose.Position.x, item->Pose.Position.z, item->Pose.Orientation.y, 0, CLICK(1))) - { - AlignToEdge(item, BACKWARD_ALIGNMENT); - SetAnimation(item, LA_OVERHANG_SLOPE_LADDER_CONCAVE); // Slope to underlying ladder transition (concave). - return; - } + //if ((GetClimbFlags(GetPointCollision(probeNow.Block, item->Pose.Position.x, item->Pose.Position.y, item->Pose.Position.z).BottomBlock) & slopeData.ClimbOrient) && + // InStrip(item->Pose.Position.x, item->Pose.Position.z, item->Pose.Orientation.y, 0, CLICK(1))) + //{ + // AlignToEdge(item, BACKWARD_ALIGNMENT); + // SetAnimation(item, LA_OVERHANG_SLOPE_LADDER_CONCAVE); // Slope to underlying ladder transition (concave). + // return; + //} - if (probeDown.BottomBlock->Flags.Monkeyswing) + if (probeDown.GetBottomSector().Flags.Monkeyswing) { int height; - int yDiff = probeDown.Position.Ceiling - probeNow.Position.Ceiling; + int yDiff = probeDown.GetCeilingHeight() - probeNow.GetCeilingHeight(); // Test for flat monkey (abs(slope) < 2). short bridge = FindBridge(0, slopeData.GoalOrient, down, &height, -CLICK(3), -CLICK(2)); if (bridge < 0) bridge = FindBridge(1, slopeData.GoalOrient, down, &height, -CLICK(3), -CLICK(2)); - if ((abs(yDiff) < CLICK(1) && abs(probeDown.CeilingTilt.x) <= 2 && abs(probeDown.CeilingTilt.y) <= 2) || bridge >= 0) + if ((abs(yDiff) < CLICK(1) && abs(GetSurfaceTilt(probeDown.GetCeilingNormal(), false).ToVector2().x) <= 2 && abs(GetSurfaceTilt(probeDown.GetCeilingNormal(), false).ToVector2().y) <= 2) || bridge >= 0) SetAnimation(item, LA_OVERHANG_SLOPE_MONKEY_CONVEX); // Force slope to underlying monkey transition (convex) // Test for downward slope to climb. bridge = FindBridge(4, slopeData.GoalOrient, down, &height, -CLICK(2.5f), -CLICK(1.5f)); - if (yDiff >= CLICK(0.75f) && yDiff <= CLICK(1.25f) && (SlopeCheck(probeDown.CeilingTilt, slopeData.Goal) || bridge >= 0)) + if (yDiff >= CLICK(0.75f) && yDiff <= CLICK(1.25f) && (SlopeCheck(GetSurfaceTilt(probeDown.GetCeilingNormal(), false).ToVector2(), slopeData.Goal) || bridge >= 0)) { SetAnimation(item, item->Animation.AnimNumber == LA_OVERHANG_IDLE_LEFT ? LA_OVERHANG_CLIMB_DOWN_LEFT : LA_OVERHANG_CLIMB_DOWN_RIGHT); return; @@ -491,9 +494,9 @@ void lara_col_slopehang(ItemInfo* item, CollisionInfo* coll) auto now = item->Pose.Position; - auto probeNow = GetCollision(now.x, now.y, now.z, item->RoomNumber); + auto probeNow = GetPointCollision(now, item->RoomNumber); - item->Pose.Position.y = probeNow.Position.Ceiling + HEIGHT_ADJUST; + item->Pose.Position.y = probeNow.GetCeilingHeight() + HEIGHT_ADJUST; // Drop down if action not pressed. if (!IsHeld(In::Action)) @@ -528,16 +531,16 @@ void lara_col_slopehang(ItemInfo* item, CollisionInfo* coll) direction = ANGLE(90.0f); } - auto probeShimmy = GetCollision(shimmy.x, shimmy.y, shimmy.z, item->RoomNumber); + auto probeShimmy = GetPointCollision(shimmy, item->RoomNumber); - if (probeShimmy.BottomBlock->Flags.Monkeyswing) + if (probeShimmy.GetBottomSector().Flags.Monkeyswing) { - int yDiff = probeShimmy.Position.Ceiling - probeNow.Position.Ceiling; + int yDiff = probeShimmy.GetCeilingHeight() - probeNow.GetCeilingHeight(); int height; short bridge = FindBridge(4, slopeData.GoalOrient, shimmy, &height, -CLICK(2.5f), -CLICK(1.5f)); - if ((SlopeCheck(probeShimmy.CeilingTilt, slopeData.Goal) && abs(yDiff) < 64) || bridge >= 0) + if ((SlopeCheck(GetSurfaceTilt(probeShimmy.GetCeilingNormal(), false).ToVector2(), slopeData.Goal) && abs(yDiff) < 64) || bridge >= 0) SetAnimation(item, direction < 0 ? LA_OVERHANG_SHIMMY_LEFT : LA_OVERHANG_SHIMMY_RIGHT); } } @@ -567,9 +570,9 @@ void lara_col_slopeshimmy(ItemInfo* item, CollisionInfo* coll) auto now = item->Pose.Position; - auto probeNow = GetCollision(now.x, now.y, now.z, item->RoomNumber); + auto probeNow = GetPointCollision(now, item->RoomNumber); - item->Pose.Position.y = probeNow.Position.Ceiling + HEIGHT_ADJUST; + item->Pose.Position.y = probeNow.GetCeilingHeight() + HEIGHT_ADJUST; auto shimmy = item->Pose.Position; if (item->Animation.AnimNumber == LA_OVERHANG_SHIMMY_LEFT) @@ -583,17 +586,17 @@ void lara_col_slopeshimmy(ItemInfo* item, CollisionInfo* coll) shimmy.z -= slopeData.Offset.x / 2; } - auto probeShimmy = GetCollision(shimmy.x, shimmy.y, shimmy.z, item->RoomNumber); + auto probeShimmy = GetPointCollision(shimmy, item->RoomNumber); bool cancelShimmy = true; - if (probeShimmy.BottomBlock->Flags.Monkeyswing) + if (probeShimmy.GetBottomSector().Flags.Monkeyswing) { - int yDiff = probeShimmy.Position.Ceiling - probeNow.Position.Ceiling; + int yDiff = probeShimmy.GetCeilingHeight() - probeNow.GetCeilingHeight(); int height; short bridge = FindBridge(4, slopeData.GoalOrient, shimmy, &height, -CLICK(2.5f), -CLICK(1.5f)); - if ((SlopeCheck(probeShimmy.CeilingTilt, slopeData.Goal) && abs(yDiff) < 64) || bridge >= 0) + if ((SlopeCheck(GetSurfaceTilt(probeShimmy.GetCeilingNormal(), false).ToVector2(), slopeData.Goal) && abs(yDiff) < 64) || bridge >= 0) cancelShimmy = false; } @@ -836,16 +839,16 @@ void SlopeHangExtra(ItemInfo* item, CollisionInfo* coll) auto down = Vector3i(item->Pose.Position.x + slopeData.Offset.x, item->Pose.Position.y + CLICK(1), item->Pose.Position.z + slopeData.Offset.z); - auto probeDown = GetCollision(down.x, down.y, down.z, item->RoomNumber); + auto probeDown = GetPointCollision(down, item->RoomNumber); - int ceilDist = item->Pose.Position.y - probeDown.Position.Ceiling; + int ceilDist = item->Pose.Position.y - probeDown.GetCeilingHeight(); if (item->Animation.TargetState == LS_LADDER_IDLE) // Prevent going from hang to climb mode if slope is under ladder. { if (ceilDist >= CLICK(1) && ceilDist < CLICK(2)) { - if ((probeDown.CeilingTilt.x / 3) == (slopeData.Goal.x / 3) || - (probeDown.CeilingTilt.y / 3) == (slopeData.Goal.y / 3)) + if ((GetSurfaceTilt(probeDown.GetCeilingNormal(), false).ToVector2().x / 3) == (slopeData.Goal.x / 3) || + (GetSurfaceTilt(probeDown.GetCeilingNormal(), false).ToVector2().y / 3) == (slopeData.Goal.y / 3)) { item->Animation.TargetState = LS_HANG; if (IsHeld(In::Forward)) @@ -861,8 +864,8 @@ void SlopeHangExtra(ItemInfo* item, CollisionInfo* coll) { if (ceilDist < CLICK(1)) { - if ((probeDown.CeilingTilt.x / 3) == (goal.x / 3) || - (probeDown.CeilingTilt.z / 3) == (goal.y / 3)) + if ((GetSurfaceTilt(probeDown.GetCeilingNormal(), false).ToVector2().x / 3) == (goal.x / 3) || + (GetSurfaceTilt(probeDown.GetCeilingNormal(), false).ToVector2().z / 3) == (goal.y / 3)) { SetAnimation(item, LA_REACH_TO_HANG, 21); } @@ -881,19 +884,19 @@ void SlopeReachExtra(ItemInfo* item, CollisionInfo* coll) auto now = item->Pose.Position; - auto probeNow = GetCollision(now.x, now.y, now.z, item->RoomNumber); + auto probeNow = GetPointCollision(now, item->RoomNumber); - int ceilDist = item->Pose.Position.y - probeNow.Position.Ceiling; + int ceilDist = item->Pose.Position.y - probeNow.GetCeilingHeight(); - if (probeNow.BottomBlock->Flags.Monkeyswing && ceilDist <= CLICK(3.5f)) + if (probeNow.GetBottomSector().Flags.Monkeyswing && ceilDist <= CLICK(3.5f)) { int height; short bridge = FindBridge(4, slopeData.GoalOrient, now, &height, -CLICK(4), -CLICK(2.5f)); - if (abs(probeNow.CeilingTilt.x) > 2 || abs(probeNow.CeilingTilt.y) > 2 || bridge >= 0) + if (abs(GetSurfaceTilt(probeNow.GetCeilingNormal(), false).ToVector2().x) > 2 || abs(GetSurfaceTilt(probeNow.GetCeilingNormal(), false).ToVector2().y) > 2 || bridge >= 0) { bool disableGrab = true; - if (SlopeCheck(probeNow.CeilingTilt, slopeData.Goal) || bridge >= 0) + if (SlopeCheck(GetSurfaceTilt(probeNow.GetCeilingNormal(), false).ToVector2(), slopeData.Goal) || bridge >= 0) { if (abs(OrientDelta(item->Pose.Orientation.y, slopeData.GoalOrient)) < ANGLE(33.75f)) disableGrab = false; @@ -916,17 +919,17 @@ void SlopeClimbExtra(ItemInfo* item, CollisionInfo* coll) auto now = item->Pose.Position; auto down = Vector3i(item->Pose.Position.x + slopeData.Offset.x, item->Pose.Position.y + CLICK(1), item->Pose.Position.z + slopeData.Offset.z); - auto probeNow = GetCollision(now.x, now.y, now.z, item->RoomNumber); - auto probeDown = GetCollision(down.x, down.y, down.z, item->RoomNumber); + auto probeNow = GetPointCollision(now, item->RoomNumber); + auto probeDown = GetPointCollision(down, item->RoomNumber); // Block for ladder to overhead slope transition. if (item->Animation.AnimNumber == LA_LADDER_IDLE) { if (IsHeld(In::Forward)) { - int ceilDist = probeNow.Position.Ceiling - item->Pose.Position.y; + int ceilDist = probeNow.GetCeilingHeight() - item->Pose.Position.y; - if (probeNow.BottomBlock->Flags.Monkeyswing && ceilDist >= -CLICK(4) && ceilDist <= -CLICK(3)) + if (probeNow.GetBottomSector().Flags.Monkeyswing && ceilDist >= -CLICK(4) && ceilDist <= -CLICK(3)) { short facing = item->Pose.Orientation.y + ANGLE(45.0f); facing &= ANGLE(270.0f); @@ -934,9 +937,9 @@ void SlopeClimbExtra(ItemInfo* item, CollisionInfo* coll) int height; short bridge = FindBridge(4, facing, now, &height, -CLICK(4), -CLICK(3)); - if (SlopeCheck(probeNow.CeilingTilt, slopeData.Goal) || bridge >= 0) + if (SlopeCheck(GetSurfaceTilt(probeNow.GetCeilingNormal(), false).ToVector2(), slopeData.Goal) || bridge >= 0) { - item->Pose.Position.y = probeNow.Position.Ceiling + 900; + item->Pose.Position.y = probeNow.GetCeilingHeight() + 900; SetAnimation(item, LA_OVERHANG_LADDER_SLOPE_CONCAVE); // Ladder to overhead slope transition (concave). } } @@ -944,9 +947,9 @@ void SlopeClimbExtra(ItemInfo* item, CollisionInfo* coll) if (IsHeld(In::Back)) { - int ceilDist = probeDown.Position.Ceiling - item->Pose.Position.y; + int ceilDist = probeDown.GetCeilingHeight() - item->Pose.Position.y; - if (probeDown.BottomBlock->Flags.Monkeyswing && ceilDist >= 0 && ceilDist <= CLICK(1)) + if (probeDown.GetBottomSector().Flags.Monkeyswing && ceilDist >= 0 && ceilDist <= CLICK(1)) { short facing = item->Pose.Orientation.y + ANGLE(45.0f); facing &= ANGLE(270.0f); @@ -954,9 +957,9 @@ void SlopeClimbExtra(ItemInfo* item, CollisionInfo* coll) int height; short bridge = FindBridge(4, facing, down, &height, -CLICK(0.5f), -CLICK(0.25f)); - if (SlopeCheck(probeDown.CeilingTilt, slopeData.Goal) || bridge >= 0) + if (SlopeCheck(GetSurfaceTilt(probeDown.GetCeilingNormal(), false).ToVector2(), slopeData.Goal) || bridge >= 0) { - item->Pose.Position.y = probeDown.Position.Ceiling - 156; + item->Pose.Position.y = probeDown.GetCeilingHeight() - 156; SetAnimation(item, LA_OVERHANG_LADDER_SLOPE_CONVEX); // Ladder to underlying slope transition (convex). } } @@ -967,12 +970,12 @@ void SlopeClimbExtra(ItemInfo* item, CollisionInfo* coll) // Extends LS_LADDER_IDLE (56) bool LadderMonkeyExtra(ItemInfo* item, CollisionInfo* coll) { - auto probe = GetCollision(item); + auto probe = GetPointCollision(*item); - if (probe.Position.CeilingSlope) + if (probe.IsSteepCeiling()) return false; - if (probe.BottomBlock->Flags.Monkeyswing && (item->Pose.Position.y - coll->Setup.Height - CLICK(0.5f) <= probe.Position.Ceiling)) + if (probe.GetBottomSector().Flags.Monkeyswing && (item->Pose.Position.y - coll->Setup.Height - CLICK(0.5f) <= probe.GetCeilingHeight())) { item->Animation.TargetState = LS_MONKEY_IDLE; return true; @@ -991,15 +994,15 @@ void SlopeClimbDownExtra(ItemInfo* item, CollisionInfo* coll) auto down = Vector3i(item->Pose.Position.x + slopeData.Offset.x, item->Pose.Position.y + CLICK(1), item->Pose.Position.z + slopeData.Offset.z); - auto probeDown = GetCollision(down.x, down.y, down.z, item->RoomNumber); + auto probeDown = GetPointCollision(down, item->RoomNumber); if (item->Animation.AnimNumber == LA_LADDER_DOWN) // Make Lara stop before underlying slope ceiling at correct height. { if (IsHeld(In::Back)) { - int ceilDist = probeDown.Position.Ceiling - item->Pose.Position.y; + int ceilDist = probeDown.GetCeilingHeight() - item->Pose.Position.y; - if (probeDown.BottomBlock->Flags.Monkeyswing && ceilDist >= 0 && ceilDist <= CLICK(1)) + if (probeDown.GetBottomSector().Flags.Monkeyswing && ceilDist >= 0 && ceilDist <= CLICK(1)) { short facing = item->Pose.Orientation.y + ANGLE(45.0f); facing &= ANGLE(270.0f); @@ -1007,9 +1010,9 @@ void SlopeClimbDownExtra(ItemInfo* item, CollisionInfo* coll) int height; short bridge = FindBridge(4, facing, down, &height, -CLICK(0.5f), -CLICK(0.25f)); - if (SlopeCheck(probeDown.CeilingTilt, slopeData.Goal) || bridge >= 0) + if (SlopeCheck(GetSurfaceTilt(probeDown.GetCeilingNormal(), false).ToVector2(), slopeData.Goal) || bridge >= 0) { - item->Pose.Position.y = probeDown.Position.Ceiling - 156; + item->Pose.Position.y = probeDown.GetCeilingHeight() - 156; item->Animation.TargetState = LS_LADDER_IDLE; } } @@ -1054,14 +1057,14 @@ void SlopeMonkeyExtra(ItemInfo* item, CollisionInfo* coll) auto now = item->Pose.Position; auto down = Vector3i(item->Pose.Position.x + slopeData.Offset.x, item->Pose.Position.y + CLICK(1), item->Pose.Position.z + slopeData.Offset.z); - auto probeNow = GetCollision(now.x, now.y, now.z, item->RoomNumber); - auto probeDown = GetCollision(down.x, down.y, down.z, item->RoomNumber); + auto probeNow = GetPointCollision(now, item->RoomNumber); + auto probeDown = GetPointCollision(down, item->RoomNumber); if (item->Animation.AnimNumber == LA_REACH_TO_MONKEY && !GetFrameIndex(item, 0)) // Manage proper grabbing of monkey slope on forward jump. { - int ceilDist = item->Pose.Position.y - probeNow.Position.Ceiling; + int ceilDist = item->Pose.Position.y - probeNow.GetCeilingHeight(); - if (probeNow.BottomBlock->Flags.Monkeyswing && ceilDist <= CLICK(3.5f)) + if (probeNow.GetBottomSector().Flags.Monkeyswing && ceilDist <= CLICK(3.5f)) { short facing = item->Pose.Orientation.y + ANGLE(45.0f); facing &= 0xC000; @@ -1069,12 +1072,12 @@ void SlopeMonkeyExtra(ItemInfo* item, CollisionInfo* coll) int height; short bridge = FindBridge(4, facing, now, &height, -CLICK(3.5f), -CLICK(2.5f)); - if (SlopeCheck(probeNow.CeilingTilt, slopeData.Goal) || bridge >= 0) + if (SlopeCheck(GetSurfaceTilt(probeNow.GetCeilingNormal(), false).ToVector2(), slopeData.Goal) || bridge >= 0) { lara->Context.NextCornerPos.Orientation.z = AlignToGrab(item); - int ceiling = GetCollision(probeNow.Block, item->Pose.Position.x, item->Pose.Position.y, item->Pose.Position.z).Position.Ceiling; - item->Pose.Position.y = ceiling + HEIGHT_ADJUST; + /*int ceiling = GetPointCollision(probeNow.Block, item->Pose.Position.x, item->Pose.Position.y, item->Pose.Position.z).GetCeilingHeight(); + item->Pose.Position.y = ceiling + HEIGHT_ADJUST;*/ SetAnimation(item, LA_OVERHANG_HANG_SWING); } @@ -1083,16 +1086,16 @@ void SlopeMonkeyExtra(ItemInfo* item, CollisionInfo* coll) if (IsHeld(In::Forward)) // Monkey to slope transitions. { - if (probeNow.BottomBlock->Flags.Monkeyswing && + if (probeNow.GetBottomSector().Flags.Monkeyswing && ((item->Animation.AnimNumber == LA_REACH_TO_MONKEY && GetFrameIndex(item, 0) >= 54) || item->Animation.AnimNumber == LA_MONKEY_IDLE)) { if (abs(OrientDelta(slopeData.GoalOrient, item->Pose.Orientation.y)) <= ANGLE(30.0f) && InStrip(item->Pose.Position.x, item->Pose.Position.z, item->Pose.Orientation.y, 0, CLICK(0.5f))) { - if (probeDown.BottomBlock->Flags.Monkeyswing) + /*if (probeDown.BottomBlock->Flags.Monkeyswing) { - int ceiling = GetCollision(probeDown.Block, down.x, now.y, down.z).Position.Ceiling; - int yDiff = ceiling - probeNow.Position.Ceiling; + int ceiling = GetPointCollision(probeDown.Block, down.x, now.y, down.z).GetCeilingHeight(); + int yDiff = ceiling - probeNow.GetCeilingHeight(); int height; short bridge = FindBridge(4, slopeData.GoalOrient, down, &height, -CLICK(7) >> 1, -CLICK(5) >> 1); @@ -1113,7 +1116,7 @@ void SlopeMonkeyExtra(ItemInfo* item, CollisionInfo* coll) return; //item->Pose.Position.y = ceiling + 914; } - } + }*/ } } @@ -1122,19 +1125,19 @@ void SlopeMonkeyExtra(ItemInfo* item, CollisionInfo* coll) // Additional overhang ladder tests. int y = item->Pose.Position.y - coll->Setup.Height; - auto probe = GetCollision(down.x, item->Pose.Position.y - coll->Setup.Height, down.z, item->RoomNumber); + auto probe = GetPointCollision(Vector3i(down.x, item->Pose.Position.y - coll->Setup.Height, down.z), item->RoomNumber); - if (probe.BottomBlock->Flags.IsWallClimbable(GetClimbDirectionFlags(item->Pose.Orientation.y + ANGLE(180.0f))) && - probe.Position.Floor >= (item->Pose.Position.y - CLICK(1)) && - probe.Position.Ceiling <= (y - CLICK(1))) + if (probe.GetBottomSector().Flags.IsWallClimbable(GetClimbDirectionFlags(item->Pose.Orientation.y + ANGLE(180.0f))) && + probe.GetFloorHeight() >= (item->Pose.Position.y - CLICK(1)) && + probe.GetCeilingHeight() <= (y - CLICK(1))) { // Primary checks succeeded, now do C-shaped secondary probing. - probe = GetCollision(down.x, y, down.z, probe.RoomNumber); - probe = GetCollision(down.x, y - CLICK(2), down.z, probe.RoomNumber); - probe = GetCollision(now.x, y - CLICK(2), now.z, probe.RoomNumber); + probe = GetPointCollision(Vector3i(down.x, y, down.z), probe.GetRoomNumber()); + probe = GetPointCollision(Vector3i(down.x, y - CLICK(2), down.z), probe.GetRoomNumber()); + probe = GetPointCollision(Vector3i(now.x, y - CLICK(2), now.z), probe.GetRoomNumber()); - if (probe.Position.Floor <= (y - CLICK(1)) || - probe.Position.Ceiling >= (y - CLICK(1))) + if (probe.GetFloorHeight() <= (y - CLICK(1)) || + probe.GetCeilingHeight() >= (y - CLICK(1))) { if (item->Animation.TargetState != LS_LADDER_IDLE) { diff --git a/TombEngine/Game/Lara/lara_struct.h b/TombEngine/Game/Lara/lara_struct.h index fa21dd987..1f1758096 100644 --- a/TombEngine/Game/Lara/lara_struct.h +++ b/TombEngine/Game/Lara/lara_struct.h @@ -1025,7 +1025,7 @@ public: Ammo& operator --() { - assertion(Count > 0, "Ammo count is already 0."); + TENAssert(Count > 0, "Ammo count is already 0."); --Count; return *this; } diff --git a/TombEngine/Game/Lara/lara_surface.cpp b/TombEngine/Game/Lara/lara_surface.cpp index c79e89cd3..9ec72f52f 100644 --- a/TombEngine/Game/Lara/lara_surface.cpp +++ b/TombEngine/Game/Lara/lara_surface.cpp @@ -273,6 +273,5 @@ void lara_as_surface_climb_out(ItemInfo* item, CollisionInfo* coll) player.Control.Look.Mode = LookMode::None; coll->Setup.EnableObjectPush = false; coll->Setup.EnableSpasm = false; - Camera.flags = CF_FOLLOW_CENTER; - Camera.laraNode = LM_HIPS; // Forces the camera to follow Lara instead of snapping. + Camera.flags = CF_FOLLOW_CENTER; // Forces the camera to follow Lara instead of snapping. } diff --git a/TombEngine/Game/Lara/lara_swim.cpp b/TombEngine/Game/Lara/lara_swim.cpp index 713a56e2a..c814159aa 100644 --- a/TombEngine/Game/Lara/lara_swim.cpp +++ b/TombEngine/Game/Lara/lara_swim.cpp @@ -3,6 +3,7 @@ #include "Game/animation.h" #include "Game/camera.h" +#include "Game/collision/Point.h" #include "Game/control/control.h" #include "Game/items.h" #include "Game/Lara/lara_collide.h" @@ -14,6 +15,7 @@ #include "Specific/level.h" #include "Specific/Input/Input.h" +using namespace TEN::Collision::Point; using namespace TEN::Input; // ----------------------------- @@ -187,7 +189,7 @@ void lara_col_underwater_death(ItemInfo* item, CollisionInfo* coll) item->HitPoints = -1; lara->Control.HandStatus = HandStatus::Busy; - int waterHeight = GetWaterHeight(item); + int waterHeight = GetPointCollision(*item).GetWaterTopHeight(); if (waterHeight < (item->Pose.Position.y - (CLICK(0.4f) - 2)) && waterHeight != NO_HEIGHT) { diff --git a/TombEngine/Game/Lara/lara_tests.cpp b/TombEngine/Game/Lara/lara_tests.cpp index 33beda7d6..5748ca575 100644 --- a/TombEngine/Game/Lara/lara_tests.cpp +++ b/TombEngine/Game/Lara/lara_tests.cpp @@ -5,6 +5,7 @@ #include "Game/animation.h" #include "Game/collision/collide_item.h" #include "Game/collision/collide_room.h" +#include "Game/collision/Point.h" #include "Game/control/control.h" #include "Game/control/los.h" #include "Game/items.h" @@ -16,16 +17,16 @@ #include "Game/Lara/lara_helpers.h" #include "Game/Lara/lara_monkey.h" #include "Math/Math.h" -#include "Renderer/Renderer.h" +#include "Specific/configuration.h" #include "Specific/Input/Input.h" #include "Specific/level.h" #include "Specific/trutils.h" using namespace TEN::Collision::Floordata; +using namespace TEN::Collision::Point; using namespace TEN::Entities::Player; using namespace TEN::Input; using namespace TEN::Math; -using namespace TEN::Renderer; using namespace TEN::Utils; // ----------------------------- @@ -46,18 +47,18 @@ bool TestValidLedge(ItemInfo* item, CollisionInfo* coll, bool ignoreHeadroom, bo int y = item->Pose.Position.y - coll->Setup.Height; // Get frontal collision data - auto frontLeft = GetCollision(item->Pose.Position.x + xl, y, item->Pose.Position.z + zl, GetRoomVector(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, GetRoomVector(item->Location, Vector3i(item->Pose.Position.x, y, item->Pose.Position.z)).RoomNumber); + auto frontLeft = GetPointCollision(Vector3i(item->Pose.Position.x + xl, y, item->Pose.Position.z + zl), GetRoomVector(item->Location, Vector3i(item->Pose.Position.x, y, item->Pose.Position.z)).RoomNumber); + auto frontRight = GetPointCollision(Vector3i(item->Pose.Position.x + xr, y, item->Pose.Position.z + zr), GetRoomVector(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. // This check helps to filter out cases when Lara is formally facing corner but ledge check returns true because probe distance is fixed. - if (frontLeft.Position.Floor < (item->Pose.Position.y - CLICK(0.5f)) || frontRight.Position.Floor < (item->Pose.Position.y - CLICK(0.5f))) + if (frontLeft.GetFloorHeight() < (item->Pose.Position.y - CLICK(0.5f)) || frontRight.GetFloorHeight() < (item->Pose.Position.y - CLICK(0.5f))) return false; - if (frontLeft.Position.Ceiling > (item->Pose.Position.y - coll->Setup.Height) || frontRight.Position.Ceiling > (item->Pose.Position.y - coll->Setup.Height)) + if (frontLeft.GetCeilingHeight() >(item->Pose.Position.y - coll->Setup.Height) || frontRight.GetCeilingHeight() > (item->Pose.Position.y - coll->Setup.Height)) return false; - //g_Renderer.AddDebugSphere(Vector3(item->pos.Position.x + xl, left, item->pos.Position.z + zl), 64, Vector4::One, RendererDebugPage::CollisionStats); - //g_Renderer.AddDebugSphere(Vector3(item->pos.Position.x + xr, right, item->pos.Position.z + zr), 64, Vector4::One, RendererDebugPage::CollisionStats); + //DrawDebugSphere(Vector3(item->pos.Position.x + xl, left, item->pos.Position.z + zl), 64, Vector4::One, RendererDebugPage::CollisionStats); + //DrawDebugSphere(Vector3(item->pos.Position.x + xr, right, item->pos.Position.z + zr), 64, Vector4::One, RendererDebugPage::CollisionStats); // Determine ledge probe embed offset. // We use 0.2f radius extents here for two purposes. First - we can't guarantee that shifts weren't already applied @@ -66,8 +67,8 @@ bool TestValidLedge(ItemInfo* item, CollisionInfo* coll, bool ignoreHeadroom, bo int zf = phd_cos(coll->NearestLedgeAngle) * (coll->Setup.Radius * 1.2f); // Get floor heights at both points - auto left = GetCollision(item->Pose.Position.x + xf + xl, y, item->Pose.Position.z + zf + zl, GetRoomVector(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, GetRoomVector(item->Location, Vector3i(item->Pose.Position.x, y, item->Pose.Position.z)).RoomNumber).Position.Floor; + auto left = GetPointCollision(Vector3i(item->Pose.Position.x + xf + xl, y, item->Pose.Position.z + zf + zl), GetRoomVector(item->Location, Vector3i(item->Pose.Position.x, y, item->Pose.Position.z)).RoomNumber).GetFloorHeight(); + auto right = GetPointCollision(Vector3i(item->Pose.Position.x + xf + xr, y, item->Pose.Position.z + zf + zr), GetRoomVector(item->Location, Vector3i(item->Pose.Position.x, y, item->Pose.Position.z)).RoomNumber).GetFloorHeight(); // If specified, limit vertical search zone only to nearest height if (heightLimit && (abs(left - y) > CLICK(0.5f) || abs(right - y) > CLICK(0.5f))) @@ -140,6 +141,7 @@ bool TestLaraHang(ItemInfo* item, CollisionInfo* coll) coll->Setup.UpperFloorBound = -STEPUP_HEIGHT; coll->Setup.LowerCeilingBound = 0; coll->Setup.ForwardAngle = lara->Control.MoveAngle; + coll->Setup.ForceSolidStatics = true; // When Lara is about to move, use larger embed offset for stabilizing diagonal shimmying) int embedOffset = 4; @@ -214,7 +216,7 @@ bool TestLaraHang(ItemInfo* item, CollisionInfo* coll) z += testShift.y; } - if (TestLaraNearClimbableWall(item, GetCollision(x, item->Pose.Position.y, z, item->RoomNumber).BottomBlock)) + if (TestLaraNearClimbableWall(item, &GetPointCollision(Vector3i(x, item->Pose.Position.y, z), item->RoomNumber).GetBottomSector())) { if (!TestLaraHangOnClimbableWall(item, coll)) verticalShift = 0; // Ignore vertical shift if ladder is encountered next block @@ -450,7 +452,7 @@ bool TestLaraClimbIdle(ItemInfo* item, CollisionInfo* coll) bool TestLaraNearClimbableWall(ItemInfo* item, FloorInfo* floor) { if (floor == nullptr) - floor = GetCollision(item).BottomBlock; + floor = &GetPointCollision(*item).GetBottomSector(); return ((256 << (GetQuadrant(item->Pose.Orientation.y))) & GetClimbFlags(floor)); } @@ -523,7 +525,7 @@ bool TestLaraValidHangPosition(ItemInfo* item, CollisionInfo* coll) // Get incoming ledge height and own Lara's upper bound. // First one will be negative while first one is positive. // Difference between two indicates difference in height between ledges. - auto frontFloor = GetCollision(item, lara->Control.MoveAngle, coll->Setup.Radius + CLICK(0.5f), -LARA_HEIGHT).Position.Floor; + auto frontFloor = GetPointCollision(*item, lara->Control.MoveAngle, coll->Setup.Radius + CLICK(0.5f), -LARA_HEIGHT).GetFloorHeight(); auto laraUpperBound = item->Pose.Position.y - coll->Setup.Height; // If difference is above 1/2 click, return false (ledge is out of reach). @@ -582,7 +584,7 @@ CornerType TestLaraHangCorner(ItemInfo* item, CollisionInfo* coll, float testAng item->Pose = cornerResult.RealPositionResult; lara->Context.NextCornerPos.Position = Vector3i( item->Pose.Position.x, - GetCollision(item, item->Pose.Orientation.y, coll->Setup.Radius + 16, -(coll->Setup.Height + CLICK(0.5f))).Position.Floor + abs(bounds.Y1), + GetPointCollision(*item, item->Pose.Orientation.y, coll->Setup.Radius + 16, -(coll->Setup.Height + CLICK(0.5f))).GetFloorHeight() + abs(bounds.Y1), item->Pose.Position.z ); lara->Context.NextCornerPos.Orientation.y = item->Pose.Orientation.y; @@ -640,7 +642,7 @@ CornerType TestLaraHangCorner(ItemInfo* item, CollisionInfo* coll, float testAng // Store next position item->Pose = cornerResult.RealPositionResult; lara->Context.NextCornerPos.Position.x = item->Pose.Position.x; - lara->Context.NextCornerPos.Position.y = GetCollision(item, item->Pose.Orientation.y, coll->Setup.Radius * 1.25f, -(abs(bounds.Y1) + LARA_HEADROOM)).Position.Floor + abs(bounds.Y1); + lara->Context.NextCornerPos.Position.y = GetPointCollision(*item, item->Pose.Orientation.y, coll->Setup.Radius * 1.25f, -(abs(bounds.Y1) + LARA_HEADROOM)).GetFloorHeight() + abs(bounds.Y1); lara->Context.NextCornerPos.Position.z = item->Pose.Position.z; lara->Context.NextCornerPos.Orientation.y = item->Pose.Orientation.y; lara->Control.MoveAngle = item->Pose.Orientation.y; @@ -742,14 +744,16 @@ CornerTestResult TestItemAtNextCornerPosition(ItemInfo* item, CollisionInfo* col bool TestHangSwingIn(ItemInfo* item, CollisionInfo* coll) { - auto* lara = GetLaraInfo(item); + int vPos = item->Pose.Position.y; + auto pointColl = GetPointCollision(*item, item->Pose.Orientation.y, OFFSET_RADIUS(coll->Setup.Radius) + item->Animation.Velocity.z); - int y = item->Pose.Position.y; - auto probe = GetCollision(item, item->Pose.Orientation.y, OFFSET_RADIUS(coll->Setup.Radius)); + // 1) Test for wall. + if (pointColl.GetFloorHeight() == NO_HEIGHT) + return false; - if ((probe.Position.Floor - y) > 0 && - (probe.Position.Ceiling - y) < -CLICK(1.6f) && - probe.Position.Floor != NO_HEIGHT) + // 2) Test leg space. + if ((pointColl.GetFloorHeight() - vPos) > 0 && + (pointColl.GetCeilingHeight() - vPos) < -CLICK(1.6f)) { return true; } @@ -852,32 +856,22 @@ bool LaraPositionOnLOS(ItemInfo* item, short angle, int distance) int LaraFloorFront(ItemInfo* item, short angle, int distance) { - return LaraCollisionFront(item, angle, distance).Position.Floor; + auto pointColl = GetPointCollision(*item, angle, distance, -LARA_HEIGHT); + + if (pointColl.GetFloorHeight() == NO_HEIGHT) + return pointColl.GetFloorHeight(); + + return (pointColl.GetFloorHeight() - item->Pose.Position.y); } int LaraCeilingFront(ItemInfo* item, short angle, int distance, int height) { - return LaraCeilingCollisionFront(item, angle, distance, height).Position.Ceiling; -} + auto pointColl = GetPointCollision(*item, angle, distance, -height); -CollisionResult LaraCollisionFront(ItemInfo* item, short angle, int distance) -{ - auto probe = GetCollision(item, angle, distance, -LARA_HEIGHT); + if (pointColl.GetCeilingHeight() == NO_HEIGHT) + return pointColl.GetCeilingHeight(); - if (probe.Position.Floor != NO_HEIGHT) - probe.Position.Floor -= item->Pose.Position.y; - - return probe; -} - -CollisionResult LaraCeilingCollisionFront(ItemInfo* item, short angle, int distance, int height) -{ - auto probe = GetCollision(item, angle, distance, -height); - - if (probe.Position.Ceiling != NO_HEIGHT) - probe.Position.Ceiling += height - item->Pose.Position.y; - - return probe; + return ((pointColl.GetCeilingHeight() + height) - item->Pose.Position.y); } bool TestPlayerWaterStepOut(ItemInfo* item, CollisionInfo* coll) @@ -885,17 +879,17 @@ bool TestPlayerWaterStepOut(ItemInfo* item, CollisionInfo* coll) auto& player = GetLaraInfo(*item); // Get point collision. - auto pointColl = GetCollision(item); + auto pointColl = GetPointCollision(*item); int vPos = item->Pose.Position.y; if (coll->CollisionType == CollisionType::Front || - pointColl.Position.FloorSlope || - (pointColl.Position.Floor - vPos) <= 0) + pointColl.IsSteepFloor() || + (pointColl.GetFloorHeight() - vPos) <= 0) { return false; } - if ((pointColl.Position.Floor - vPos) >= -CLICK(0.5f)) + if ((pointColl.GetFloorHeight() - vPos) >= -CLICK(0.5f)) { SetAnimation(item, LA_STAND_IDLE); } @@ -905,7 +899,7 @@ bool TestPlayerWaterStepOut(ItemInfo* item, CollisionInfo* coll) item->Animation.TargetState = LS_IDLE; } - item->Pose.Position.y = pointColl.Position.Floor; + item->Pose.Position.y = pointColl.GetFloorHeight(); UpdateLaraRoom(item, -(STEPUP_HEIGHT - 3)); ResetPlayerLean(item); @@ -933,22 +927,39 @@ bool TestLaraWaterClimbOut(ItemInfo* item, CollisionInfo* coll) if (coll->Middle.Ceiling > -STEPUP_HEIGHT) return false; - int frontFloor = coll->Front.Floor + LARA_HEIGHT_TREAD; - if (coll->Front.Bridge == NO_VALUE && - (frontFloor <= -CLICK(2) || - frontFloor > CLICK(1.25f) - 4)) + // HACK: Probe at incremetal height steps to account for room stacks. -- Sezz 2024.10.28 + int frontFloor = NO_HEIGHT; + if (coll->Front.Bridge != NO_VALUE) { - return false; + auto pointColl = GetPointCollision(*item, item->Pose.Orientation.y, BLOCK(0.2f), -BLOCK(0.5f)); + frontFloor = (pointColl.GetFloorHeight() - item->Pose.Position.y); + } + else + { + int yOffset = CLICK(1.25f); + while (yOffset > -CLICK(2)) + { + auto pointColl = GetPointCollision(*item, item->Pose.Orientation.y, BLOCK(0.2f), yOffset); + + frontFloor = pointColl.GetFloorHeight() - item->Pose.Position.y; + if (frontFloor > -CLICK(2) && + frontFloor <= (CLICK(1.25f) - 4)) + { + break; + } + + yOffset -= CLICK(0.5f); + } } // Extra bridge check. if (coll->Front.Bridge != NO_VALUE) { - int bridgeBorder = GetBridgeBorder(g_Level.Items[coll->Front.Bridge], false) - item->Pose.Position.y; + frontFloor = GetBridgeBorder(g_Level.Items[coll->Front.Bridge], false) - item->Pose.Position.y; - frontFloor = bridgeBorder - CLICK(0.5f); - if (frontFloor <= -CLICK(2) || - frontFloor > CLICK(1.25f) - 4) + int bridgeBorder = frontFloor - CLICK(0.5f); + if (bridgeBorder <= -CLICK(2) || + bridgeBorder > CLICK(1.25f) - 4) { return false; } @@ -961,8 +972,8 @@ bool TestLaraWaterClimbOut(ItemInfo* item, CollisionInfo* coll) if (coll->HitStatic) return false; - auto probe = GetCollision(item, coll->Setup.ForwardAngle, CLICK(2), -CLICK(1)); - int headroom = probe.Position.Floor - probe.Position.Ceiling; + auto probe = GetPointCollision(*item, coll->Setup.ForwardAngle, CLICK(2), -CLICK(1)); + int headroom = probe.GetFloorHeight() - probe.GetCeilingHeight(); if (frontFloor <= -CLICK(1)) { @@ -1095,21 +1106,19 @@ void TestLaraWaterDepth(ItemInfo* item, CollisionInfo* coll) { auto& player = GetLaraInfo(*item); - auto pointColl = GetCollision(item); - int waterDepth = GetWaterDepth(item->Pose.Position.x, item->Pose.Position.y, item->Pose.Position.z, pointColl.RoomNumber); + auto pointColl = GetPointCollision(*item); - if (waterDepth == NO_HEIGHT) + if (pointColl.GetWaterBottomHeight() == NO_HEIGHT) { item->Animation.Velocity.y = 0.0f; item->Pose.Position = coll->Setup.PrevPosition; } - - else if (waterDepth <= (LARA_HEIGHT - (LARA_HEADROOM / 2))) + else if (pointColl.GetWaterBottomHeight() <= (LARA_HEIGHT - (LARA_HEADROOM / 2))) { SetAnimation(item, LA_UNDERWATER_TO_STAND); ResetPlayerLean(item); item->Animation.TargetState = LS_IDLE; - item->Pose.Position.y = pointColl.Position.Floor; + item->Pose.Position.y = pointColl.GetFloorHeight(); item->Animation.IsAirborne = false; item->Animation.Velocity.y = 0.0f; item->Animation.Velocity.z = 0.0f; @@ -1202,12 +1211,12 @@ std::optional TestLaraVaultTolerance(ItemInfo* item, CollisionI auto* lara = GetLaraInfo(item); int distance = OFFSET_RADIUS(coll->Setup.Radius); - auto probeFront = GetCollision(item, coll->NearestLedgeAngle, distance, -coll->Setup.Height); - auto probeMiddle = GetCollision(item); + auto probeFront = GetPointCollision(*item, coll->NearestLedgeAngle, distance, -coll->Setup.Height); + auto probeMiddle = GetPointCollision(*item); bool isSwamp = TestEnvironment(ENV_FLAG_SWAMP, item); bool swampTooDeep = testSetup.CheckSwampDepth ? (isSwamp && lara->Context.WaterSurfaceDist < -CLICK(3)) : isSwamp; - int y = isSwamp ? item->Pose.Position.y : probeMiddle.Position.Floor; // HACK: Avoid cheese when in the midst of performing a step. Can be done better. @Sezz 2022.04.08 + int y = isSwamp ? item->Pose.Position.y : probeMiddle.GetFloorHeight(); // HACK: Avoid cheese when in the midst of performing a step. Can be done better. @Sezz 2022.04.08 // Check swamp depth (if applicable). if (swampTooDeep) @@ -1220,27 +1229,27 @@ std::optional TestLaraVaultTolerance(ItemInfo* item, CollisionI // Raise y position of point/room probe by increments of CLICK(0.5f) to find potential vault ledge. int yOffset = testSetup.LowerFloorBound; - while (((probeFront.Position.Ceiling - y) > -coll->Setup.Height || // Ceiling is below Lara's height... - abs(probeFront.Position.Ceiling - probeFront.Position.Floor) <= testSetup.ClampMin || // OR clamp is too small - abs(probeFront.Position.Ceiling - probeFront.Position.Floor) > testSetup.ClampMax) && // OR clamp is too large (future-proofing; not possible right now). + while (((probeFront.GetCeilingHeight() - y) > -coll->Setup.Height || // Ceiling is below Lara's height... + abs(probeFront.GetCeilingHeight() - probeFront.GetFloorHeight()) <= testSetup.ClampMin || // OR clamp is too small + abs(probeFront.GetCeilingHeight() - probeFront.GetFloorHeight()) > testSetup.ClampMax) && // OR clamp is too large (future-proofing; not possible right now). yOffset > (testSetup.UpperFloorBound - coll->Setup.Height)) // Offset is not too high. { - probeFront = GetCollision(item, coll->NearestLedgeAngle, distance, yOffset); + probeFront = GetPointCollision(*item, coll->NearestLedgeAngle, distance, yOffset); yOffset -= std::max(CLICK(0.5f), testSetup.ClampMin); } // Discard walls. - if (probeFront.Position.Floor == NO_HEIGHT) + if (probeFront.GetFloorHeight() == NO_HEIGHT) return std::nullopt; // Assess point/room collision. - if ((probeFront.Position.Floor - y) < testSetup.LowerFloorBound && // Within lower floor bound. - (probeFront.Position.Floor - y) >= testSetup.UpperFloorBound && // Within upper floor bound. - abs(probeFront.Position.Ceiling - probeFront.Position.Floor) > testSetup.ClampMin && // Within clamp min. - abs(probeFront.Position.Ceiling - probeFront.Position.Floor) <= testSetup.ClampMax && // Within clamp max. - abs(probeMiddle.Position.Ceiling - probeFront.Position.Floor) >= testSetup.GapMin) // Gap is optically permissive. + if ((probeFront.GetFloorHeight() - y) < testSetup.LowerFloorBound && // Within lower floor bound. + (probeFront.GetFloorHeight() - y) >= testSetup.UpperFloorBound && // Within upper floor bound. + abs(probeFront.GetCeilingHeight() - probeFront.GetFloorHeight()) > testSetup.ClampMin && // Within clamp min. + abs(probeFront.GetCeilingHeight() - probeFront.GetFloorHeight()) <= testSetup.ClampMax && // Within clamp max. + abs(probeMiddle.GetCeilingHeight() - probeFront.GetFloorHeight()) >= testSetup.GapMin) // Gap is optically permissive. { - return VaultTestResult{ probeFront.Position.Floor }; + return VaultTestResult{ probeFront.GetFloorHeight() }; } return std::nullopt; @@ -1390,20 +1399,20 @@ std::optional TestLaraLadderAutoJump(ItemInfo* item, CollisionI int y = item->Pose.Position.y; int distance = OFFSET_RADIUS(coll->Setup.Radius); - auto probeFront = GetCollision(item, coll->NearestLedgeAngle, distance, -coll->Setup.Height); - auto probeMiddle = GetCollision(item); + auto probeFront = GetPointCollision(*item, coll->NearestLedgeAngle, distance, -coll->Setup.Height); + auto probeMiddle = GetPointCollision(*item); // Check ledge angle. if (!TestValidLedgeAngle(item, coll)) return std::nullopt; if (lara->Control.CanClimbLadder && // Ladder sector flag set. - (probeMiddle.Position.Ceiling - y) <= -CLICK(6.5f) && // Within lowest middle ceiling bound. (Synced with TestLaraLadderMount()) - ((probeFront.Position.Floor - y) <= -CLICK(6.5f) || // Floor height is appropriate, OR - (probeFront.Position.Ceiling - y) > -CLICK(6.5f)) && // Ceiling height is appropriate. (Synced with TestLaraLadderMount()) + (probeMiddle.GetCeilingHeight() - y) <= -CLICK(6.5f) && // Within lowest middle ceiling bound. (Synced with TestLaraLadderMount()) + ((probeFront.GetFloorHeight() - y) <= -CLICK(6.5f) || // Floor height is appropriate, OR + (probeFront.GetCeilingHeight() - y) > -CLICK(6.5f)) && // Ceiling height is appropriate. (Synced with TestLaraLadderMount()) coll->NearestLedgeDistance <= coll->Setup.Radius) // Appropriate distance from wall. { - return VaultTestResult{ probeMiddle.Position.Ceiling, false, true, true }; + return VaultTestResult{ probeMiddle.GetCeilingHeight(), false, true, true }; } return std::nullopt; @@ -1415,18 +1424,18 @@ std::optional TestLaraLadderMount(ItemInfo* item, CollisionInfo int y = item->Pose.Position.y; int distance = OFFSET_RADIUS(coll->Setup.Radius); - auto probeFront = GetCollision(item, coll->NearestLedgeAngle, distance, -coll->Setup.Height); - auto probeMiddle = GetCollision(item); + auto probeFront = GetPointCollision(*item, coll->NearestLedgeAngle, distance, -coll->Setup.Height); + auto probeMiddle = GetPointCollision(*item); // Check ledge angle. if (!TestValidLedgeAngle(item, coll)) return std::nullopt; if (lara->Control.CanClimbLadder && // Ladder sector flag set. - (probeMiddle.Position.Ceiling - y) <= -CLICK(4.5f) && // Within lower middle ceiling bound. - (probeMiddle.Position.Ceiling - y) > -CLICK(6.5f) && // Within upper middle ceiling bound. - (probeMiddle.Position.Floor - y) > -CLICK(6.5f) && // Within upper middle floor bound. (Synced with TestLaraAutoJump()) - (probeFront.Position.Ceiling - y) <= -CLICK(4.5f) && // Within lowest front ceiling bound. + (probeMiddle.GetCeilingHeight() - y) <= -CLICK(4.5f) && // Within lower middle ceiling bound. + (probeMiddle.GetCeilingHeight() - y) > -CLICK(6.5f) && // Within upper middle ceiling bound. + (probeMiddle.GetFloorHeight() - y) > -CLICK(6.5f) && // Within upper middle floor bound. (Synced with TestLaraAutoJump()) + (probeFront.GetCeilingHeight() - y) <= -CLICK(4.5f) && // Within lowest front ceiling bound. coll->NearestLedgeDistance <= coll->Setup.Radius) // Appropriate distance from wall. { return VaultTestResult{ NO_HEIGHT, true, true, false }; @@ -1435,18 +1444,18 @@ std::optional TestLaraLadderMount(ItemInfo* item, CollisionInfo return std::nullopt; } -std::optional TestLaraMonkeyAutoJump(ItemInfo* item, CollisionInfo* coll) +std::optional TestLaraAutoMonkeySwingJump(ItemInfo* item, CollisionInfo* coll) { auto* lara = GetLaraInfo(item); int y = item->Pose.Position.y; - auto probe = GetCollision(item); + auto probe = GetPointCollision(*item); if (lara->Control.CanMonkeySwing && // Monkey swing sector flag set. - (probe.Position.Ceiling - y) < -LARA_HEIGHT_MONKEY && // Within lower ceiling bound. - (probe.Position.Ceiling - y) >= -CLICK(7)) // Within upper ceiling bound. + (probe.GetCeilingHeight() - y) < -LARA_HEIGHT_MONKEY && // Within lower ceiling bound. + (probe.GetCeilingHeight() - y) >= -CLICK(7)) // Within upper ceiling bound. { - return VaultTestResult{ probe.Position.Ceiling, false, false, true }; + return VaultTestResult{ probe.GetCeilingHeight(), false, false, true }; } return std::nullopt; @@ -1538,8 +1547,8 @@ std::optional TestLaraVault(ItemInfo* item, CollisionInfo* coll // In this case, they fail due to a reliance on ShiftItem(). @Sezz 2021.02.05 // Auto jump to monkey swing. - vaultResult = TestLaraMonkeyAutoJump(item, coll); - if (vaultResult.has_value() && g_GameFlow->HasMonkeyAutoJump()) + vaultResult = TestLaraAutoMonkeySwingJump(item, coll); + if (vaultResult.has_value() && g_Configuration.EnableAutoMonkeySwingJump) { vaultResult->TargetState = LS_AUTO_JUMP; if (!HasStateDispatch(item, vaultResult->TargetState)) @@ -1606,15 +1615,15 @@ bool TestAndDoLaraLadderClimb(ItemInfo* item, CollisionInfo* coll) CrawlVaultTestResult TestLaraCrawlVaultTolerance(ItemInfo* item, CollisionInfo* coll, CrawlVaultTestSetup testSetup) { int y = item->Pose.Position.y; - auto probeA = GetCollision(item, item->Pose.Orientation.y, testSetup.CrossDist, -LARA_HEIGHT_CRAWL); // Crossing. - auto probeB = GetCollision(item, item->Pose.Orientation.y, testSetup.DestDist, -LARA_HEIGHT_CRAWL); // Approximate destination. - auto probeMiddle = GetCollision(item); + auto probeA = GetPointCollision(*item, item->Pose.Orientation.y, testSetup.CrossDist, -LARA_HEIGHT_CRAWL); // Crossing. + auto probeB = GetPointCollision(*item, item->Pose.Orientation.y, testSetup.DestDist, -LARA_HEIGHT_CRAWL); // Approximate destination. + auto probeMiddle = GetPointCollision(*item); - bool isSlope = testSetup.CheckSlope ? probeB.Position.FloorSlope : false; - bool isDeath = testSetup.CheckDeath ? probeB.Block->Flags.Death : false; + bool isSlope = testSetup.CheckSlope ? probeB.IsSteepFloor() : false; + bool isDeath = testSetup.CheckDeath ? probeB.GetSector().Flags.Death : false; // Discard walls. - if (probeA.Position.Floor == NO_HEIGHT || probeB.Position.Floor == NO_HEIGHT) + if (probeA.GetFloorHeight() == NO_HEIGHT || probeB.GetFloorHeight() == NO_HEIGHT) return CrawlVaultTestResult{ false }; // Check for slope or death sector (if applicable). @@ -1622,14 +1631,14 @@ CrawlVaultTestResult TestLaraCrawlVaultTolerance(ItemInfo* item, CollisionInfo* return CrawlVaultTestResult{ false }; // Assess point/room collision. - if ((probeA.Position.Floor - y) <= testSetup.LowerFloorBound && // Within lower floor bound. - (probeA.Position.Floor - y) >= testSetup.UpperFloorBound && // Within upper floor bound. - abs(probeA.Position.Ceiling - probeA.Position.Floor) > testSetup.ClampMin && // Crossing clamp limit. - abs(probeB.Position.Ceiling - probeB.Position.Floor) > testSetup.ClampMin && // Destination clamp limit. - abs(probeMiddle.Position.Ceiling - probeA.Position.Floor) >= testSetup.GapMin && // Gap is optically permissive (going up). - abs(probeA.Position.Ceiling - probeMiddle.Position.Floor) >= testSetup.GapMin && // Gap is optically permissive (going down). - abs(probeA.Position.Floor - probeB.Position.Floor) <= testSetup.FloorBound && // Crossing/destination floor height difference suggests continuous crawl surface. - (probeA.Position.Ceiling - y) < -testSetup.GapMin) // Ceiling height is permissive. + if ((probeA.GetFloorHeight() - y) <= testSetup.LowerFloorBound && // Within lower floor bound. + (probeA.GetFloorHeight() - y) >= testSetup.UpperFloorBound && // Within upper floor bound. + abs(probeA.GetCeilingHeight() - probeA.GetFloorHeight()) > testSetup.ClampMin && // Crossing clamp limit. + abs(probeB.GetCeilingHeight() - probeB.GetFloorHeight()) > testSetup.ClampMin && // Destination clamp limit. + abs(probeMiddle.GetCeilingHeight() - probeA.GetFloorHeight()) >= testSetup.GapMin && // Gap is optically permissive (going up). + abs(probeA.GetCeilingHeight() - probeMiddle.GetFloorHeight()) >= testSetup.GapMin && // Gap is optically permissive (going down). + abs(probeA.GetFloorHeight() - probeB.GetFloorHeight()) <= testSetup.FloorBound && // Crossing/destination floor height difference suggests continuous crawl surface. + (probeA.GetCeilingHeight() - y) < -testSetup.GapMin) // Ceiling height is permissive. { return CrawlVaultTestResult{ true }; } @@ -1718,7 +1727,7 @@ CrawlVaultTestResult TestLaraCrawlVault(ItemInfo* item, CollisionInfo* coll) { if (IsHeld(In::Crouch) && TestLaraCrawlDownStep(item, coll).Success) crawlVaultResult.TargetState = LS_CRAWL_STEP_DOWN; - else USE_FEATURE_IF_CPP20([[likely]]) + else crawlVaultResult.TargetState = LS_CRAWL_EXIT_STEP_DOWN; crawlVaultResult.Success = HasStateDispatch(item, crawlVaultResult.TargetState); @@ -1731,7 +1740,7 @@ CrawlVaultTestResult TestLaraCrawlVault(ItemInfo* item, CollisionInfo* coll) { if (IsHeld(In::Walk)) crawlVaultResult.TargetState = LS_CRAWL_EXIT_FLIP; - else USE_FEATURE_IF_CPP20([[likely]]) + else crawlVaultResult.TargetState = LS_CRAWL_EXIT_JUMP; crawlVaultResult.Success = HasStateDispatch(item, crawlVaultResult.TargetState); @@ -1763,14 +1772,14 @@ bool TestLaraCrawlToHang(ItemInfo* item, CollisionInfo* coll) { int y = item->Pose.Position.y; int distance = CLICK(1.2f); - auto probe = GetCollision(item, item->Pose.Orientation.y + ANGLE(180.0f), distance, -LARA_HEIGHT_CRAWL); + auto probe = GetPointCollision(*item, item->Pose.Orientation.y + ANGLE(180.0f), distance, -LARA_HEIGHT_CRAWL); bool objectCollided = TestLaraObjectCollision(item, item->Pose.Orientation.y + ANGLE(180.0f), CLICK(1.2f), -LARA_HEIGHT_CRAWL); if (!objectCollided && // No obstruction. - (probe.Position.Floor - y) >= LARA_HEIGHT_STRETCH && // Highest floor bound. - (probe.Position.Ceiling - y) <= -CLICK(0.75f) && // Gap is optically permissive. - probe.Position.Floor != NO_HEIGHT) + (probe.GetFloorHeight() - y) >= LARA_HEIGHT_STRETCH && // Highest floor bound. + (probe.GetCeilingHeight() - y) <= -CLICK(0.75f) && // Gap is optically permissive. + probe.GetFloorHeight() != NO_HEIGHT) { return true; } @@ -1801,9 +1810,9 @@ bool TestLaraPoleCollision(ItemInfo* item, CollisionInfo* coll, bool goingUp, fl auto sphere = BoundingSphere(spherePos, poleProbeCollRadius); auto offsetSphere = BoundingSphere(spherePos + sphereOffset2D, poleProbeCollRadius); - //g_Renderer.AddDebugSphere(sphere.Center, 16.0f, Vector4(1, 0, 0, 1), RendererDebugPage::CollisionStats); + //DrawDebugSphere(sphere.Center, 16.0f, Vector4(1, 0, 0, 1), RendererDebugPage::CollisionStats); - for (const auto* itemPtr : collObjects.ItemPtrs) + for (const auto* itemPtr : collObjects.Items) { if (itemPtr->ObjectNumber != ID_POLEROPE) continue; @@ -1811,7 +1820,7 @@ bool TestLaraPoleCollision(ItemInfo* item, CollisionInfo* coll, bool goingUp, fl auto poleBox = GameBoundingBox(itemPtr).ToBoundingOrientedBox(itemPtr->Pose); poleBox.Extents = poleBox.Extents + Vector3(coll->Setup.Radius, 0.0f, coll->Setup.Radius); - //g_Renderer.AddDebugBox(poleBox, Vector4(0, 0, 1, 1), RendererDebugPage::CollisionStats); + //DrawDebugBox(poleBox, Vector4(0, 0, 1, 1), RendererDebugPage::CollisionStats); if (poleBox.Intersects(sphere) || poleBox.Intersects(offsetSphere)) { diff --git a/TombEngine/Game/Lara/lara_tests.h b/TombEngine/Game/Lara/lara_tests.h index 4ddebea69..3777783a0 100644 --- a/TombEngine/Game/Lara/lara_tests.h +++ b/TombEngine/Game/Lara/lara_tests.h @@ -34,8 +34,6 @@ bool TestLaraFacingCorner(const ItemInfo* item, short headingAngle, float dist); bool LaraPositionOnLOS(ItemInfo* item, short angle, int distance); int LaraFloorFront(ItemInfo* item, short angle, int distance); int LaraCeilingFront(ItemInfo* item, short angle, int distance, int height); -CollisionResult LaraCollisionFront(ItemInfo* item, short angle, int distance); -CollisionResult LaraCeilingCollisionFront(ItemInfo* item, short angle, int distance, int height); bool TestPlayerWaterStepOut(ItemInfo* item, CollisionInfo* coll); bool TestLaraWaterClimbOut(ItemInfo* item, CollisionInfo* coll); @@ -58,7 +56,7 @@ std::optional TestLaraVault3StepsToCrouch(ItemInfo* item, Colli std::optional TestLaraLedgeAutoJump(ItemInfo* item, CollisionInfo* coll); std::optional TestLaraLadderAutoJump(ItemInfo* item, CollisionInfo* coll); std::optional TestLaraLadderMount(ItemInfo* item, CollisionInfo* coll); -std::optional TestLaraMonkeyAutoJump(ItemInfo* item, CollisionInfo* coll); +std::optional TestLaraAutoMonkeySwingJump(ItemInfo* item, CollisionInfo* coll); std::optional TestLaraVault(ItemInfo* item, CollisionInfo* coll); bool TestAndDoLaraLadderClimb(ItemInfo* item, CollisionInfo* coll); diff --git a/TombEngine/Game/Setup.cpp b/TombEngine/Game/Setup.cpp index a427dfe71..0f5b82874 100644 --- a/TombEngine/Game/Setup.cpp +++ b/TombEngine/Game/Setup.cpp @@ -79,7 +79,14 @@ ObjectInfo& ObjectHandler::GetFirstAvailableObject() // NOTE: JointRotationFlags allows bones to be rotated with CreatureJoint(). void ObjectInfo::SetBoneRotationFlags(int boneID, int flags) { - g_Level.Bones[boneIndex + (boneID * 4)] |= flags; + int index = boneIndex + (boneID * 4); + if (index < 0 || index >= g_Level.Bones.size()) + { + TENLog("Failed to set rotation flag for bone ID " + std::to_string(boneID), LogLevel::Warning); + return; + } + + g_Level.Bones[index] |= flags; } void ObjectInfo::SetHitEffect(HitEffect hitEffect) @@ -127,7 +134,7 @@ void InitializeGameFlags() ZeroMemory(FlipMap, MAX_FLIPMAP * sizeof(int)); ZeroMemory(FlipStats, MAX_FLIPMAP * sizeof(bool)); - FlipEffect = -1; + FlipEffect = NO_VALUE; FlipStatus = false; Camera.underwater = false; } diff --git a/TombEngine/Game/Setup.h b/TombEngine/Game/Setup.h index 7873eb168..824426e10 100644 --- a/TombEngine/Game/Setup.h +++ b/TombEngine/Game/Setup.h @@ -16,16 +16,16 @@ constexpr auto MAX_STATICS = 1000; enum JointRotationFlags { - ROT_X = (1 << 2), - ROT_Y = (1 << 3), - ROT_Z = (1 << 4) + ROT_X = 1 << 2, + ROT_Y = 1 << 3, + ROT_Z = 1 << 4 }; // Unused. enum ShatterFlags { - NoCollision = (1 << 0), - Shatterable = (1 << 1) + NoCollision = 1 << 0, + Shatterable = 1 << 1 }; // Custom LOT definition for Creature. Used in InitializeSlot() in lot.cpp. @@ -39,10 +39,11 @@ enum class LotType HumanPlusJump, HumanPlusJumpAndMonkey, Flyer, - Blockable, // For large creatures such as trex and shiva. - Spider, // Only 2 block vault allowed. - Ape, // Only 2 block vault allowed. - SnowmobileGun // Only 1 block vault allowed and 4 block drop max. + Blockable, // For large creatures such as trex and shiva. + Spider, // Only 2 block vault allowed. + Ape, // Only 2 block vault allowed. + SnowmobileGun, // Only 1 block vault allowed and 4 block drop max. + EnemyJeep }; enum class HitEffect diff --git a/TombEngine/Game/animation.cpp b/TombEngine/Game/animation.cpp index 3003f6292..f29ee8590 100644 --- a/TombEngine/Game/animation.cpp +++ b/TombEngine/Game/animation.cpp @@ -3,6 +3,7 @@ #include "Game/camera.h" #include "Game/collision/collide_room.h" +#include "Game/collision/Point.h" #include "Game/control/box.h" #include "Game/control/flipeffect.h" #include "Game/items.h" @@ -15,6 +16,7 @@ #include "Sound/sound.h" #include "Specific/level.h" +using namespace TEN::Collision::Point; using namespace TEN::Entities::Generic; using namespace TEN::Math; using TEN::Renderer::g_Renderer; @@ -60,6 +62,8 @@ static void PerformAnimCommands(ItemInfo& item, bool isFrameBased) { UpdateItemRoom(item.Index); } + + item.DisableInterpolation = true; } commandDataPtr += 3; @@ -110,49 +114,95 @@ static void PerformAnimCommands(ItemInfo& item, bool isFrameBased) break; case AnimCommandType::SoundEffect: - if (isFrameBased && item.Animation.FrameNumber == commandDataPtr[0]) + { + int frameNumber = commandDataPtr[0]; + if (isFrameBased && item.Animation.FrameNumber == frameNumber) { - if (!Objects[item.ObjectNumber].waterCreature) - { - bool playInWater = (commandDataPtr[1] & 0x8000) != 0; - bool playOnLand = (commandDataPtr[1] & 0x4000) != 0; - bool playAlways = (playInWater && playOnLand) || (!playInWater && !playOnLand); + // Get sound ID and sound environment flag from packed data. + int soundID = commandDataPtr[1] & 0xFFF; // Exclude last 4 bits for sound ID. + int soundEnvFlag = commandDataPtr[1] & 0xF000; // Keep only last 4 bits for sound environment flag. - if (item.IsLara()) - { - auto& player = GetLaraInfo(item); - - if (playAlways || - (playOnLand && (player.Context.WaterSurfaceDist >= -SHALLOW_WATER_DEPTH || player.Context.WaterSurfaceDist == NO_HEIGHT)) || - (playInWater && player.Context.WaterSurfaceDist < -SHALLOW_WATER_DEPTH && player.Context.WaterSurfaceDist != NO_HEIGHT && !TestEnvironment(ENV_FLAG_SWAMP, &item))) - { - SoundEffect(commandDataPtr[1] & 0x3FFF, &item.Pose, SoundEnvironment::Always); - } - } - else - { - if (item.RoomNumber == NO_VALUE) - { - SoundEffect(commandDataPtr[1] & 0x3FFF, &item.Pose, SoundEnvironment::Always); - } - else if (TestEnvironment(ENV_FLAG_WATER, &item)) - { - if (playAlways || (playInWater && TestEnvironment(ENV_FLAG_WATER, Camera.pos.RoomNumber))) - SoundEffect(commandDataPtr[1] & 0x3FFF, &item.Pose, SoundEnvironment::Always); - } - else if (playAlways || (playOnLand && !TestEnvironment(ENV_FLAG_WATER, Camera.pos.RoomNumber) && !TestEnvironment(ENV_FLAG_SWAMP, Camera.pos.RoomNumber))) - { - SoundEffect(commandDataPtr[1] & 0x3FFF, &item.Pose, SoundEnvironment::Always); - } - } - } - else + // FAILSAFE. + if (item.RoomNumber == NO_VALUE) { - SoundEffect(commandDataPtr[1] & 0x3FFF, &item.Pose, TestEnvironment(ENV_FLAG_WATER, &item) ? SoundEnvironment::Water : SoundEnvironment::Land); + SoundEffect(soundID, &item.Pose, SoundEnvironment::Always); + commandDataPtr += 2; + break; } + + // Get required sound environment from flag. + auto requiredSoundEnv = SoundEnvironment::Always; + switch (soundEnvFlag) + { + default: + case 0: + requiredSoundEnv = SoundEnvironment::Always; + break; + + case (1 << 14): + requiredSoundEnv = SoundEnvironment::Land; + break; + + case (1 << 15): + requiredSoundEnv = SoundEnvironment::ShallowWater; + break; + + case (1 << 12): + requiredSoundEnv = SoundEnvironment::Swamp; + break; + + case (1 << 13): + requiredSoundEnv = SoundEnvironment::Underwater; + break; + } + + int roomNumberAtPos = GetPointCollision(item).GetRoomNumber(); + bool isWater = TestEnvironment(ENV_FLAG_WATER, roomNumberAtPos); + bool isSwamp = TestEnvironment(ENV_FLAG_SWAMP, roomNumberAtPos); + + // Get sound environment for sound effect. + auto soundEnv = std::optional(); + switch (requiredSoundEnv) + { + case SoundEnvironment::Always: + soundEnv = SoundEnvironment::Always; + break; + + case SoundEnvironment::Land: + if (!isWater && !isSwamp) + soundEnv = SoundEnvironment::Land; + + break; + + case SoundEnvironment::ShallowWater: + if (isWater) + { + // HACK: Must update assets before removing this exception for water creatures. + const auto& object = Objects[item.ObjectNumber]; + soundEnv = object.waterCreature ? SoundEnvironment::Underwater : SoundEnvironment::ShallowWater; + } + + break; + + case SoundEnvironment::Swamp: + if (isSwamp) + soundEnv = SoundEnvironment::Swamp; + + break; + + case SoundEnvironment::Underwater: + if (isWater || isSwamp) + soundEnv = SoundEnvironment::Underwater; + + break; + } + + if (soundEnv.has_value()) + SoundEffect(soundID, &item.Pose, *soundEnv); } commandDataPtr += 2; + } break; case AnimCommandType::Flipeffect: @@ -162,6 +212,13 @@ static void PerformAnimCommands(ItemInfo& item, bool isFrameBased) commandDataPtr += 2; break; + case AnimCommandType::DisableInterpolation: + if (isFrameBased && item.Animation.FrameNumber == commandDataPtr[0]) + item.DisableInterpolation = true; + + commandDataPtr += 1; + break; + default: break; } @@ -506,7 +563,7 @@ const AnimFrame* GetFrame(GAME_OBJECT_ID objectID, int animNumber, int frameNumb const auto& object = Objects[objectID]; int animIndex = object.animIndex + animNumber; - assertion(animIndex < g_Level.Anims.size(), "GetFrame() attempted to access missing animation."); + TENAssert(animIndex < g_Level.Anims.size(), "GetFrame() attempted to access missing animation."); const auto& anim = GetAnimData(object, animNumber); @@ -663,7 +720,7 @@ void ClampRotation(Pose& outPose, short angle, short rotation) Vector3i GetJointPosition(const ItemInfo& item, int jointIndex, const Vector3i& relOffset) { // Use matrices done in renderer to transform relative offset. - return Vector3i(g_Renderer.GetAbsEntityBonePosition(item.Index, jointIndex, relOffset.ToVector3())); + return Vector3i(g_Renderer.GetMoveableBonePosition(item.Index, jointIndex, relOffset.ToVector3())); } Vector3i GetJointPosition(ItemInfo* item, int jointIndex, const Vector3i& relOffset) @@ -689,16 +746,9 @@ Vector3 GetJointOffset(GAME_OBJECT_ID objectID, int jointIndex) return Vector3(*(bonePtr + 1), *(bonePtr + 2), *(bonePtr + 3)); } -Quaternion GetBoneOrientation(const ItemInfo& item, int boneIndex) +Quaternion GetBoneOrientation(const ItemInfo& item, int boneID) { - static const auto REF_DIRECTION = Vector3::UnitZ; - - auto origin = g_Renderer.GetAbsEntityBonePosition(item.Index, boneIndex); - auto target = g_Renderer.GetAbsEntityBonePosition(item.Index, boneIndex, REF_DIRECTION); - - auto direction = target - origin; - direction.Normalize(); - return Geometry::ConvertDirectionToQuat(direction); + return g_Renderer.GetMoveableBoneOrientation(item.Index, boneID); } // NOTE: Will not work for bones at ends of hierarchies. diff --git a/TombEngine/Game/animation.h b/TombEngine/Game/animation.h index 7cf2fc000..dc93532a0 100644 --- a/TombEngine/Game/animation.h +++ b/TombEngine/Game/animation.h @@ -24,7 +24,8 @@ enum class AnimCommandType AttackReady, Deactivate, SoundEffect, - Flipeffect + Flipeffect, + DisableInterpolation }; struct AnimFrame diff --git a/TombEngine/Game/camera.cpp b/TombEngine/Game/camera.cpp index 1fd9628d4..49414d5ff 100644 --- a/TombEngine/Game/camera.cpp +++ b/TombEngine/Game/camera.cpp @@ -3,6 +3,7 @@ #include "Game/animation.h" #include "Game/collision/collide_room.h" +#include "Game/collision/Point.h" #include "Game/control/los.h" #include "Game/effects/debris.h" #include "Game/effects/effects.h" @@ -20,13 +21,14 @@ #include "Sound/sound.h" #include "Specific/Input/Input.h" #include "Specific/level.h" +#include "Specific/winmain.h" -using TEN::Renderer::g_Renderer; - +using namespace TEN::Collision::Point; using namespace TEN::Effects::Environment; using namespace TEN::Entities::Generic; using namespace TEN::Input; using namespace TEN::Math; +using TEN::Renderer::g_Renderer; constexpr auto PARTICLE_FADE_THRESHOLD = BLOCK(14); constexpr auto COLL_CHECK_THRESHOLD = BLOCK(4); @@ -132,8 +134,8 @@ static int GetLookCameraVerticalOffset(const ItemInfo& item, const CollisionInfo } // Get floor-to-ceiling height. - auto pointColl = GetCollision(item); - int floorToCeilHeight = abs(pointColl.Position.Ceiling - pointColl.Position.Floor); + auto pointColl = GetPointCollision(item); + int floorToCeilHeight = abs(pointColl.GetCeilingHeight() - pointColl.GetFloorHeight()); // Return appropriate vertical offset. return -((verticalOffset < floorToCeilHeight) ? verticalOffset : floorToCeilHeight); @@ -164,7 +166,7 @@ void LookCamera(ItemInfo& item, const CollisionInfo& coll) bool isInSwamp = TestEnvironment(ENV_FLAG_SWAMP, item.RoomNumber); auto basePos = Vector3i( item.Pose.Position.x, - isInSwamp ? g_Level.Rooms[item.RoomNumber].maxceiling : item.Pose.Position.y, + isInSwamp ? g_Level.Rooms[item.RoomNumber].TopHeight : item.Pose.Position.y, item.Pose.Position.z); // Define landmarks. @@ -173,8 +175,8 @@ void LookCamera(ItemInfo& item, const CollisionInfo& coll) auto lookAtPos = Geometry::TranslatePoint(pivotPos, orient, LOOK_AT_DIST); // Determine best position. - auto origin = GameVector(pivotPos, GetCollision(&item, item.Pose.Orientation.y, pivotOffset.z, pivotOffset.y).RoomNumber); - auto target = GameVector(idealPos, GetCollision(origin.ToVector3i(), origin.RoomNumber, orient, idealDist).RoomNumber); + auto origin = GameVector(pivotPos, GetPointCollision(item, item.Pose.Orientation.y, pivotOffset.z, pivotOffset.y).GetRoomNumber()); + auto target = GameVector(idealPos, GetPointCollision(origin.ToVector3i(), origin.RoomNumber, orient.ToDirection(), idealDist).GetRoomNumber()); // Handle room and object collisions. LOSAndReturnTarget(&origin, &target, 0); @@ -192,15 +194,8 @@ void LookCamera(ItemInfo& item, const CollisionInfo& coll) void LookAt(CAMERA_INFO* cam, short roll) { - auto pos = cam->pos.ToVector3(); - auto target = cam->target.ToVector3(); - auto up = Vector3::Down; - float fov = TO_RAD(CurrentFOV / 1.333333f); - float r = TO_RAD(roll); - - float levelFarView = g_GameFlow->GetLevel(CurrentLevel)->GetFarView() * float(BLOCK(1)); - - g_Renderer.UpdateCameraMatrices(cam, r, fov, levelFarView); + cam->Fov = TO_RAD(CurrentFOV / 1.333333f); + cam->Roll = TO_RAD(roll); } void AlterFOV(short value, bool store) @@ -246,13 +241,13 @@ void InitializeCamera() Camera.targetDistance = BLOCK(1.5f); Camera.item = nullptr; - Camera.numberFrames = 1; Camera.type = CameraType::Chase; Camera.speed = 1; Camera.flags = CF_NONE; Camera.bounce = 0; Camera.number = -1; Camera.fixedCamera = false; + Camera.DisableInterpolation = true; AlterFOV(ANGLE(DEFAULT_FOV)); @@ -341,11 +336,11 @@ void MoveCamera(GameVector* ideal, int speed) int y = Camera.pos.y; if (TestEnvironment(ENV_FLAG_SWAMP, Camera.pos.RoomNumber)) - y = g_Level.Rooms[Camera.pos.RoomNumber].y - CLICK(1); + y = g_Level.Rooms[Camera.pos.RoomNumber].Position.y - CLICK(1); - auto probe = GetCollision(Camera.pos.x, y, Camera.pos.z, Camera.pos.RoomNumber); - if (y < probe.Position.Ceiling || - y > probe.Position.Floor) + auto pointColl = GetPointCollision(Vector3i(Camera.pos.x, y, Camera.pos.z), Camera.pos.RoomNumber); + if (y < pointColl.GetCeilingHeight() || + y > pointColl.GetFloorHeight()) { LOSAndReturnTarget(&Camera.target, &Camera.pos, 0); @@ -364,41 +359,41 @@ void MoveCamera(GameVector* ideal, int speed) } } - probe = GetCollision(Camera.pos.x, Camera.pos.y, Camera.pos.z, Camera.pos.RoomNumber); + pointColl = GetPointCollision(Camera.pos.ToVector3i(), Camera.pos.RoomNumber); int buffer = CLICK(1) - 1; - if ((Camera.pos.y - buffer) < probe.Position.Ceiling && - (Camera.pos.y + buffer) > probe.Position.Floor && - probe.Position.Ceiling < probe.Position.Floor && - probe.Position.Ceiling != NO_HEIGHT && - probe.Position.Floor != NO_HEIGHT) + if ((Camera.pos.y - buffer) < pointColl.GetCeilingHeight() && + (Camera.pos.y + buffer) > pointColl.GetFloorHeight() && + pointColl.GetCeilingHeight() < pointColl.GetFloorHeight() && + pointColl.GetCeilingHeight() != NO_HEIGHT && + pointColl.GetFloorHeight() != NO_HEIGHT) { - Camera.pos.y = (probe.Position.Floor + probe.Position.Ceiling) / 2; + Camera.pos.y = (pointColl.GetFloorHeight() + pointColl.GetCeilingHeight()) / 2; } - else if ((Camera.pos.y + buffer) > probe.Position.Floor && - probe.Position.Ceiling < probe.Position.Floor && - probe.Position.Ceiling != NO_HEIGHT && - probe.Position.Floor != NO_HEIGHT) + else if ((Camera.pos.y + buffer) > pointColl.GetFloorHeight() && + pointColl.GetCeilingHeight() < pointColl.GetFloorHeight() && + pointColl.GetCeilingHeight() != NO_HEIGHT && + pointColl.GetFloorHeight() != NO_HEIGHT) { - Camera.pos.y = probe.Position.Floor - buffer; + Camera.pos.y = pointColl.GetFloorHeight() - buffer; } - else if ((Camera.pos.y - buffer) < probe.Position.Ceiling && - probe.Position.Ceiling < probe.Position.Floor && - probe.Position.Ceiling != NO_HEIGHT && - probe.Position.Floor != NO_HEIGHT) + else if ((Camera.pos.y - buffer) < pointColl.GetCeilingHeight() && + pointColl.GetCeilingHeight() < pointColl.GetFloorHeight() && + pointColl.GetCeilingHeight() != NO_HEIGHT && + pointColl.GetFloorHeight() != NO_HEIGHT) { - Camera.pos.y = probe.Position.Ceiling + buffer; + Camera.pos.y = pointColl.GetCeilingHeight() + buffer; } - else if (probe.Position.Ceiling >= probe.Position.Floor || - probe.Position.Floor == NO_HEIGHT || - probe.Position.Ceiling == NO_HEIGHT) + else if (pointColl.GetCeilingHeight() >= pointColl.GetFloorHeight() || + pointColl.GetFloorHeight() == NO_HEIGHT || + pointColl.GetCeilingHeight() == NO_HEIGHT) { Camera.pos = *ideal; } ItemsCollideCamera(); - Camera.pos.RoomNumber = GetCollision(Camera.pos.x, Camera.pos.y, Camera.pos.z, Camera.pos.RoomNumber).RoomNumber; + Camera.pos.RoomNumber = GetPointCollision(Camera.pos.ToVector3i(), Camera.pos.RoomNumber).GetRoomNumber(); LookAt(&Camera, 0); UpdateMikePos(*LaraItem); Camera.oldType = Camera.type; @@ -465,7 +460,7 @@ void MoveObjCamera(GameVector* ideal, ItemInfo* camSlotId, int camMeshId, ItemIn } Camera.pos += (ideal->ToVector3i() - Camera.pos.ToVector3i()) / speed; - Camera.pos.RoomNumber = GetCollision(Camera.pos.x, Camera.pos.y, Camera.pos.z, Camera.pos.RoomNumber).RoomNumber; + Camera.pos.RoomNumber = GetPointCollision(Camera.pos.ToVector3i(), Camera.pos.RoomNumber).GetRoomNumber(); LookAt(&Camera, 0); auto angle = Camera.target.ToVector3i() - Camera.pos.ToVector3i(); @@ -529,21 +524,23 @@ void ChaseCamera(ItemInfo* item) int distance = Camera.targetDistance * phd_cos(Camera.actualElevation); - auto probe = GetCollision(Camera.target.x, Camera.target.y + CLICK(1), Camera.target.z, Camera.target.RoomNumber); + auto pointColl = GetPointCollision(Vector3i(Camera.target.x, Camera.target.y + CLICK(1), Camera.target.z), Camera.target.RoomNumber); - if (TestEnvironment(ENV_FLAG_SWAMP, probe.RoomNumber)) - Camera.target.y = g_Level.Rooms[probe.RoomNumber].maxceiling - CLICK(1); + if (TestEnvironment(ENV_FLAG_SWAMP, pointColl.GetRoomNumber())) + Camera.target.y = g_Level.Rooms[pointColl.GetRoomNumber()].TopHeight - CLICK(1); int y = Camera.target.y; - probe = GetCollision(Camera.target.x, y, Camera.target.z, Camera.target.RoomNumber); - if (((y < probe.Position.Ceiling || probe.Position.Floor < y) || probe.Position.Floor <= probe.Position.Ceiling) || - (probe.Position.Floor == NO_HEIGHT || probe.Position.Ceiling == NO_HEIGHT)) + pointColl = GetPointCollision(Vector3i(Camera.target.x, y, Camera.target.z), Camera.target.RoomNumber); + if (((y < pointColl.GetCeilingHeight() || pointColl.GetFloorHeight() < y) || pointColl.GetFloorHeight() <= pointColl.GetCeilingHeight()) || + (pointColl.GetFloorHeight() == NO_HEIGHT || pointColl.GetCeilingHeight() == NO_HEIGHT)) { TargetSnaps++; Camera.target = LastTarget; } else + { TargetSnaps = 0; + } for (int i = 0; i < maxSwivelSteps; i++) Ideals[i].y = Camera.target.y + (Camera.targetDistance * phd_sin(Camera.actualElevation)); @@ -646,43 +643,43 @@ void CombatCamera(ItemInfo* item) Camera.targetElevation = player.ExtraHeadRot.x + player.ExtraTorsoRot.x + item->Pose.Orientation.x - ANGLE(15.0f); } - auto probe = GetCollision(Camera.target.x, Camera.target.y + CLICK(1), Camera.target.z, Camera.target.RoomNumber); - if (TestEnvironment(ENV_FLAG_SWAMP, probe.RoomNumber)) - Camera.target.y = g_Level.Rooms[probe.RoomNumber].y - CLICK(1); + auto pointColl = GetPointCollision(Vector3i(Camera.target.x, Camera.target.y + CLICK(1), Camera.target.z), Camera.target.RoomNumber); + if (TestEnvironment(ENV_FLAG_SWAMP, pointColl.GetRoomNumber())) + Camera.target.y = g_Level.Rooms[pointColl.GetRoomNumber()].Position.y - CLICK(1); - probe = GetCollision(Camera.target.x, Camera.target.y, Camera.target.z, Camera.target.RoomNumber); - Camera.target.RoomNumber = probe.RoomNumber; + pointColl = GetPointCollision(Camera.target.ToVector3i(), Camera.target.RoomNumber); + Camera.target.RoomNumber = pointColl.GetRoomNumber(); int buffer = CLICK(0.25f); - if ((probe.Position.Ceiling + buffer) > (probe.Position.Floor - buffer) && - probe.Position.Floor != NO_HEIGHT && - probe.Position.Ceiling != NO_HEIGHT) + if ((pointColl.GetCeilingHeight() + buffer) > (pointColl.GetFloorHeight() - buffer) && + pointColl.GetFloorHeight() != NO_HEIGHT && + pointColl.GetCeilingHeight() != NO_HEIGHT) { - Camera.target.y = (probe.Position.Ceiling + probe.Position.Floor) / 2; + Camera.target.y = (pointColl.GetCeilingHeight() + pointColl.GetFloorHeight()) / 2; Camera.targetElevation = 0; } - else if (Camera.target.y > (probe.Position.Floor - buffer) && - probe.Position.Floor != NO_HEIGHT) + else if (Camera.target.y > (pointColl.GetFloorHeight() - buffer) && + pointColl.GetFloorHeight() != NO_HEIGHT) { - Camera.target.y = probe.Position.Floor - buffer; + Camera.target.y = pointColl.GetFloorHeight() - buffer; Camera.targetElevation = 0; } - else if (Camera.target.y < (probe.Position.Ceiling + buffer) && - probe.Position.Ceiling != NO_HEIGHT) + else if (Camera.target.y < (pointColl.GetCeilingHeight() + buffer) && + pointColl.GetCeilingHeight() != NO_HEIGHT) { - Camera.target.y = probe.Position.Ceiling + buffer; + Camera.target.y = pointColl.GetCeilingHeight() + buffer; Camera.targetElevation = 0; } int y = Camera.target.y; - probe = GetCollision(Camera.target.x, y, Camera.target.z, Camera.target.RoomNumber); - Camera.target.RoomNumber = probe.RoomNumber; + pointColl = GetPointCollision(Vector3i(Camera.target.x, y, Camera.target.z), Camera.target.RoomNumber); + Camera.target.RoomNumber = pointColl.GetRoomNumber(); - if (y < probe.Position.Ceiling || - y > probe.Position.Floor || - probe.Position.Ceiling >= probe.Position.Floor || - probe.Position.Floor == NO_HEIGHT || - probe.Position.Ceiling == NO_HEIGHT) + if (y < pointColl.GetCeilingHeight() || + y > pointColl.GetFloorHeight() || + pointColl.GetCeilingHeight() >= pointColl.GetFloorHeight() || + pointColl.GetFloorHeight() == NO_HEIGHT || + pointColl.GetCeilingHeight() == NO_HEIGHT) { TargetSnaps++; Camera.target = LastTarget; @@ -768,116 +765,114 @@ bool CameraCollisionBounds(GameVector* ideal, int push, bool yFirst) int y = ideal->y; int z = ideal->z; - CollisionResult probe = {}; + auto pointColl = GetPointCollision(Vector3i(x, y, z), ideal->RoomNumber); if (yFirst) { - probe = GetCollision(x, y, z, ideal->RoomNumber); - int buffer = CLICK(1) - 1; - if ((y - buffer) < probe.Position.Ceiling && - (y + buffer) > probe.Position.Floor && - probe.Position.Ceiling < probe.Position.Floor && - probe.Position.Ceiling != NO_HEIGHT && - probe.Position.Floor != NO_HEIGHT) + if ((y - buffer) < pointColl.GetCeilingHeight() && + (y + buffer) > pointColl.GetFloorHeight() && + pointColl.GetCeilingHeight() < pointColl.GetFloorHeight() && + pointColl.GetCeilingHeight() != NO_HEIGHT && + pointColl.GetFloorHeight() != NO_HEIGHT) { - y = (probe.Position.Floor + probe.Position.Ceiling) / 2; + y = (pointColl.GetFloorHeight() + pointColl.GetCeilingHeight()) / 2; } - else if ((y + buffer) > probe.Position.Floor && - probe.Position.Ceiling < probe.Position.Floor && - probe.Position.Ceiling != NO_HEIGHT && - probe.Position.Floor != NO_HEIGHT) + else if ((y + buffer) > pointColl.GetFloorHeight() && + pointColl.GetCeilingHeight() < pointColl.GetFloorHeight() && + pointColl.GetCeilingHeight() != NO_HEIGHT && + pointColl.GetFloorHeight() != NO_HEIGHT) { - y = probe.Position.Floor - buffer; + y = pointColl.GetFloorHeight() - buffer; } - else if ((y - buffer) < probe.Position.Ceiling && - probe.Position.Ceiling < probe.Position.Floor && - probe.Position.Ceiling != NO_HEIGHT && - probe.Position.Floor != NO_HEIGHT) + else if ((y - buffer) < pointColl.GetCeilingHeight() && + pointColl.GetCeilingHeight() < pointColl.GetFloorHeight() && + pointColl.GetCeilingHeight() != NO_HEIGHT && + pointColl.GetFloorHeight() != NO_HEIGHT) { - y = probe.Position.Ceiling + buffer; + y = pointColl.GetCeilingHeight() + buffer; } } - probe = GetCollision(x - push, y, z, ideal->RoomNumber); - if (y > probe.Position.Floor || - probe.Position.Floor == NO_HEIGHT || - probe.Position.Ceiling == NO_HEIGHT || - probe.Position.Ceiling >= probe.Position.Floor || - y < probe.Position.Ceiling) + pointColl = GetPointCollision(Vector3i(x - push, y, z), ideal->RoomNumber); + if (y > pointColl.GetFloorHeight() || + pointColl.GetFloorHeight() == NO_HEIGHT || + pointColl.GetCeilingHeight() == NO_HEIGHT || + pointColl.GetCeilingHeight() >= pointColl.GetFloorHeight() || + y < pointColl.GetCeilingHeight()) { x = (x & (~1023)) + push; } - probe = GetCollision(x, y, z - push, ideal->RoomNumber); - if (y > probe.Position.Floor || - probe.Position.Floor == NO_HEIGHT || - probe.Position.Ceiling == NO_HEIGHT || - probe.Position.Ceiling >= probe.Position.Floor || - y < probe.Position.Ceiling) + pointColl = GetPointCollision(Vector3i(x, y, z - push), ideal->RoomNumber); + if (y > pointColl.GetFloorHeight() || + pointColl.GetFloorHeight() == NO_HEIGHT || + pointColl.GetCeilingHeight() == NO_HEIGHT || + pointColl.GetCeilingHeight() >= pointColl.GetFloorHeight() || + y < pointColl.GetCeilingHeight()) { z = (z & (~1023)) + push; } - probe = GetCollision(x + push, y, z, ideal->RoomNumber); - if (y > probe.Position.Floor || - probe.Position.Floor == NO_HEIGHT || - probe.Position.Ceiling == NO_HEIGHT || - probe.Position.Ceiling >= probe.Position.Floor || - y < probe.Position.Ceiling) + pointColl = GetPointCollision(Vector3i(x + push, y, z), ideal->RoomNumber); + if (y > pointColl.GetFloorHeight() || + pointColl.GetFloorHeight() == NO_HEIGHT || + pointColl.GetCeilingHeight() == NO_HEIGHT || + pointColl.GetCeilingHeight() >= pointColl.GetFloorHeight() || + y < pointColl.GetCeilingHeight()) { x = (x | 1023) - push; } - probe = GetCollision(x, y, z + push, ideal->RoomNumber); - if (y > probe.Position.Floor || - probe.Position.Floor == NO_HEIGHT || - probe.Position.Ceiling == NO_HEIGHT || - probe.Position.Ceiling >= probe.Position.Floor || - y < probe.Position.Ceiling) + pointColl = GetPointCollision(Vector3i(x, y, z + push), ideal->RoomNumber); + if (y > pointColl.GetFloorHeight() || + pointColl.GetFloorHeight() == NO_HEIGHT || + pointColl.GetCeilingHeight() == NO_HEIGHT || + pointColl.GetCeilingHeight() >= pointColl.GetFloorHeight() || + y < pointColl.GetCeilingHeight()) { z = (z | 1023) - push; } if (!yFirst) { - probe = GetCollision(x, y, z, ideal->RoomNumber); + pointColl = GetPointCollision(Vector3i(x, y, z), ideal->RoomNumber); int buffer = CLICK(1) - 1; - if ((y - buffer) < probe.Position.Ceiling && - (y + buffer) > probe.Position.Floor && - probe.Position.Ceiling < probe.Position.Floor && - probe.Position.Ceiling != NO_HEIGHT && - probe.Position.Floor != NO_HEIGHT) + if ((y - buffer) < pointColl.GetCeilingHeight() && + (y + buffer) > pointColl.GetFloorHeight() && + pointColl.GetCeilingHeight() < pointColl.GetFloorHeight() && + pointColl.GetCeilingHeight() != NO_HEIGHT && + pointColl.GetFloorHeight() != NO_HEIGHT) { - y = (probe.Position.Floor + probe.Position.Ceiling) / 2; + y = (pointColl.GetFloorHeight() + pointColl.GetCeilingHeight()) / 2; } - else if ((y + buffer) > probe.Position.Floor && - probe.Position.Ceiling < probe.Position.Floor && - probe.Position.Ceiling != NO_HEIGHT && - probe.Position.Floor != NO_HEIGHT) + else if ((y + buffer) > pointColl.GetFloorHeight() && + pointColl.GetCeilingHeight() < pointColl.GetFloorHeight() && + pointColl.GetCeilingHeight() != NO_HEIGHT && + pointColl.GetFloorHeight() != NO_HEIGHT) { - y = probe.Position.Floor - buffer; + y = pointColl.GetFloorHeight() - buffer; } - else if ((y - buffer) < probe.Position.Ceiling && - probe.Position.Ceiling < probe.Position.Floor && - probe.Position.Ceiling != NO_HEIGHT && - probe.Position.Floor != NO_HEIGHT) + else if ((y - buffer) < pointColl.GetCeilingHeight() && + pointColl.GetCeilingHeight() < pointColl.GetFloorHeight() && + pointColl.GetCeilingHeight() != NO_HEIGHT && + pointColl.GetFloorHeight() != NO_HEIGHT) { - y = probe.Position.Ceiling + buffer; + y = pointColl.GetCeilingHeight() + buffer; } } - probe = GetCollision(x, y, z, ideal->RoomNumber); - if (y > probe.Position.Floor || - y < probe.Position.Ceiling || - probe.Position.Floor == NO_HEIGHT || - probe.Position.Ceiling == NO_HEIGHT || - probe.Position.Ceiling >= probe.Position.Floor) + pointColl = GetPointCollision(Vector3i(x, y, z), ideal->RoomNumber); + if (y > pointColl.GetFloorHeight() || + y < pointColl.GetCeilingHeight() || + pointColl.GetFloorHeight() == NO_HEIGHT || + pointColl.GetCeilingHeight() == NO_HEIGHT || + pointColl.GetCeilingHeight() >= pointColl.GetFloorHeight()) { return true; } - ideal->RoomNumber = probe.RoomNumber; + ideal->RoomNumber = pointColl.GetRoomNumber(); ideal->x = x; ideal->y = y; ideal->z = z; @@ -950,9 +945,19 @@ void BinocularCamera(ItemInfo* item) player.Inventory.IsBusy = false; Camera.type = BinocularOldCamera; + Camera.target = LastTarget; AlterFOV(LastFOV); return; } + + if (IsHeld(In::Action)) + { + ClearAction(In::Action); + + auto origin = Camera.pos.ToVector3i(); + auto target = Camera.target.ToVector3i(); + LaraTorch(&origin, &target, player.ExtraHeadRot.y, 192); + } } AlterFOV(7 * (ANGLE(11.5f) - player.Control.Look.OpticRange), false); @@ -961,20 +966,20 @@ void BinocularCamera(ItemInfo* item) int y = item->Pose.Position.y - CLICK(2); int z = item->Pose.Position.z; - auto probe = GetCollision(x, y, z, item->RoomNumber); - if (probe.Position.Ceiling <= (y - CLICK(1))) + auto pointColl = GetPointCollision(Vector3i(x, y, z), item->RoomNumber); + if (pointColl.GetCeilingHeight() <= (y - CLICK(1))) { y -= CLICK(1); } else { - y = probe.Position.Ceiling + CLICK(0.25f); + y = pointColl.GetCeilingHeight() + CLICK(0.25f); } Camera.pos.x = x; Camera.pos.y = y; Camera.pos.z = z; - Camera.pos.RoomNumber = probe.RoomNumber; + Camera.pos.RoomNumber = pointColl.GetRoomNumber(); float l = BLOCK(20.25f) * phd_cos(player.Control.Look.Orientation.x); float tx = x + l * phd_sin(item->Pose.Orientation.y + player.Control.Look.Orientation.y); @@ -1014,19 +1019,12 @@ void BinocularCamera(ItemInfo* item) } } - Camera.target.RoomNumber = GetCollision(Camera.pos.x, Camera.pos.y, Camera.pos.z, Camera.target.RoomNumber).RoomNumber; + Camera.target.RoomNumber = GetPointCollision(Camera.pos.ToVector3i(), Camera.target.RoomNumber).GetRoomNumber(); LookAt(&Camera, 0); UpdateMikePos(*item); Camera.oldType = Camera.type; GetTargetOnLOS(&Camera.pos, &Camera.target, false, false); - - if (IsHeld(In::Action)) - { - auto origin = Camera.pos.ToVector3i(); - auto target = Camera.target.ToVector3i(); - LaraTorch(&origin, &target, player.ExtraHeadRot.y, 192); - } } void ConfirmCameraTargetPos() @@ -1050,12 +1048,12 @@ void ConfirmCameraTargetPos() } int y = Camera.target.y; - auto probe = GetCollision(Camera.target.x, y, Camera.target.z, Camera.target.RoomNumber); - if (y < probe.Position.Ceiling || - probe.Position.Floor < y || - probe.Position.Floor <= probe.Position.Ceiling || - probe.Position.Floor == NO_HEIGHT || - probe.Position.Ceiling == NO_HEIGHT) + auto pointColl = GetPointCollision(Vector3i(Camera.target.x, y, Camera.target.z), Camera.target.RoomNumber); + if (y < pointColl.GetCeilingHeight() || + pointColl.GetFloorHeight() < y || + pointColl.GetFloorHeight() <= pointColl.GetCeilingHeight() || + pointColl.GetFloorHeight() == NO_HEIGHT || + pointColl.GetCeilingHeight() == NO_HEIGHT) { Camera.target.x = pos.x; Camera.target.y = pos.y; @@ -1166,7 +1164,6 @@ void CalculateCamera(const CollisionInfo& coll) Lara.ExtraTorsoRot.x = Lara.ExtraHeadRot.x; Lara.Control.Look.Orientation = lookOrient; - Camera.type = CameraType::Look; Camera.item->LookedAt = true; } @@ -1266,7 +1263,7 @@ void CalculateCamera(const CollisionInfo& coll) Camera.speed = 1; } - Camera.target.RoomNumber = GetCollision(x, y, z, Camera.target.RoomNumber).RoomNumber; + Camera.target.RoomNumber = GetPointCollision(Vector3i(x, y, z), Camera.target.RoomNumber).GetRoomNumber(); if (abs(LastTarget.x - Camera.target.x) < 4 && abs(LastTarget.y - Camera.target.y) < 4 && @@ -1289,9 +1286,13 @@ void CalculateCamera(const CollisionInfo& coll) Camera.fixedCamera = isFixedCamera; Camera.last = Camera.number; + Camera.DisableInterpolation = (Camera.DisableInterpolation || Camera.lastType != Camera.type); + Camera.lastType = Camera.type; - if ((Camera.type != CameraType::Heavy || Camera.timer == -1) && - LaraItem->HitPoints > 0) + if (CalculateDeathCamera()) + return; + + if (Camera.type != CameraType::Heavy || Camera.timer == -1) { Camera.type = CameraType::Chase; Camera.speed = 10; @@ -1306,6 +1307,27 @@ void CalculateCamera(const CollisionInfo& coll) } } +bool CalculateDeathCamera() +{ + // If player is alive, it's not a death camera. + if (LaraItem->HitPoints > 0) + return false; + + // If Lara is in a special death animation (from extra_anims) triggered by enemies. + if (LaraItem->Animation.AnimObjectID == ID_LARA_EXTRA_ANIMS) + return true; + + // Special death animations + if (LaraItem->Animation.AnimNumber == LA_SPIKE_DEATH || + LaraItem->Animation.AnimNumber == LA_BOULDER_DEATH || + LaraItem->Animation.AnimNumber == LA_TRAIN_OVERBOARD_DEATH) + { + return true; + } + + return false; +} + bool TestBoundsCollideCamera(const GameBoundingBox& bounds, const Pose& pose, short radius) { auto sphere = BoundingSphere(Camera.pos.ToVector3(), radius); @@ -1359,9 +1381,9 @@ void ItemPushCamera(GameBoundingBox* bounds, Pose* pos, short radius) Camera.pos.x = pos->Position.x + ((x * cosY) + (z * sinY)); Camera.pos.z = pos->Position.z + ((z * cosY) - (x * sinY)); - auto pointColl = GetCollision(Camera.pos.x, Camera.pos.y, Camera.pos.z, Camera.pos.RoomNumber); - if (pointColl.Position.Floor == NO_HEIGHT || Camera.pos.y > pointColl.Position.Floor || Camera.pos.y < pointColl.Position.Ceiling) - Camera.pos = GameVector(CamOldPos, pointColl.RoomNumber); + auto pointColl = GetPointCollision(Camera.pos.ToVector3i(), Camera.pos.RoomNumber); + if (pointColl.GetFloorHeight() == NO_HEIGHT || Camera.pos.y > pointColl.GetFloorHeight() || Camera.pos.y < pointColl.GetCeilingHeight()) + Camera.pos = GameVector(CamOldPos, pointColl.GetRoomNumber()); } bool CheckItemCollideCamera(ItemInfo* item) @@ -1391,7 +1413,7 @@ bool CheckItemCollideCamera(ItemInfo* item) static std::vector FillCollideableItemList() { auto itemList = std::vector{}; - auto& roomList = g_Level.Rooms[Camera.pos.RoomNumber].neighbors; + auto& roomList = g_Level.Rooms[Camera.pos.RoomNumber].NeighborRoomNumbers; for (short i = 0; i < g_Level.NumItems; i++) { @@ -1441,7 +1463,7 @@ bool CheckStaticCollideCamera(MESH_INFO* mesh) std::vector FillCollideableStaticsList() { std::vector staticList; - auto& roomList = g_Level.Rooms[Camera.pos.RoomNumber].neighbors; + auto& roomList = g_Level.Rooms[Camera.pos.RoomNumber].NeighborRoomNumbers; for (int i : roomList) { @@ -1484,7 +1506,7 @@ void ItemsCollideCamera() if (TestBoundsCollideCamera(bounds, item->Pose, CAMERA_RADIUS)) ItemPushCamera(&bounds, &item->Pose, RADIUS); - TEN::Renderer::g_Renderer.AddDebugBox( + DrawDebugBox( bounds.ToBoundingOrientedBox(item->Pose), Vector4(1.0f, 0.0f, 0.0f, 1.0f), RendererDebugPage::CollisionStats); } @@ -1507,7 +1529,7 @@ void ItemsCollideCamera() if (TestBoundsCollideCamera(bounds, mesh->pos, CAMERA_RADIUS)) ItemPushCamera(&bounds, &mesh->pos, RADIUS); - TEN::Renderer::g_Renderer.AddDebugBox( + DrawDebugBox( bounds.ToBoundingOrientedBox(mesh->pos), Vector4(1.0f, 0.0f, 0.0f, 1.0f), RendererDebugPage::CollisionStats); } @@ -1516,6 +1538,76 @@ void ItemsCollideCamera() staticList.clear(); } +void PrepareCamera() +{ + if (TrackCameraInit) + { + UseSpotCam = false; + AlterFOV(LastFOV); + } +} + +static void DrawPortals() +{ + constexpr auto EXT_COLOR = Color(1.0f, 1.0f, 0.0f, 0.15f); + constexpr auto INT_COLOR = Color(1.0f, 0.0f, 0.0f, 0.15f); + + if (!DebugMode) + return; + + auto neighborRoomNumbers = GetNeighborRoomNumbers(Camera.pos.RoomNumber, 1); + for (auto& roomNumber : neighborRoomNumbers) + { + const auto& room = g_Level.Rooms[roomNumber]; + + auto pos = room.Position.ToVector3(); + auto color = (roomNumber == Camera.pos.RoomNumber) ? INT_COLOR : EXT_COLOR; + + for (const auto& door : room.doors) + { + DrawDebugTriangle(door.vertices[0] + pos, door.vertices[1] + pos, door.vertices[2] + pos, color, RendererDebugPage::PortalDebug); + DrawDebugTriangle(door.vertices[2] + pos, door.vertices[3] + pos, door.vertices[0] + pos, color, RendererDebugPage::PortalDebug); + + DrawDebugLine(door.vertices[0] + pos, door.vertices[2] + pos, color, RendererDebugPage::PortalDebug); + DrawDebugLine(door.vertices[1] + pos, door.vertices[3] + pos, color, RendererDebugPage::PortalDebug); + + auto center = pos + ((door.vertices[0] + door.vertices[1] + door.vertices[2] + door.vertices[3]) / 4); + auto target = Geometry::TranslatePoint(center, door.normal, CLICK(1)); + + DrawDebugLine(center, target, color, RendererDebugPage::PortalDebug); + } + } +} + +void UpdateCamera() +{ + // HACK: Disable interpolation when switching to/from flyby camera. + // When camera structs are converted to a class, this should go to getter/setter. -- Lwmte, 29.10.2024 + static bool spotcamSwitched = false; + if (UseSpotCam != spotcamSwitched) + { + Camera.DisableInterpolation = true; + spotcamSwitched = UseSpotCam; + } + + if (UseSpotCam) + { + // Draw flyby cameras. + CalculateSpotCameras(); + } + else + { + // Do the standard camera. + TrackCameraInit = false; + CalculateCamera(LaraCollision); + } + + // Update cameras matrices there, after having done all the possible camera logic. + g_Renderer.UpdateCameraMatrices(&Camera, BLOCK(g_GameFlow->GetLevel(CurrentLevel)->GetFarView())); + + DrawPortals(); +} + void UpdateMikePos(const ItemInfo& item) { if (Camera.mikeAtLara) @@ -1543,7 +1635,8 @@ void UpdateMikePos(const ItemInfo& item) void RumbleScreen() { if (!(GlobalCounter & 0x1FF)) - SoundEffect(SFX_TR5_KLAXON, nullptr, SoundEnvironment::Land, 0.25f); + // SFX Enum Changed from TR5 and pitch shift removed. User can set this in their sound XML. Stranger1992 31st August 2024 + SoundEffect(SFX_TR4_ENVIORONMENT_RUMBLE, nullptr, SoundEnvironment::Land); if (RumbleTimer >= 0) RumbleTimer++; diff --git a/TombEngine/Game/camera.h b/TombEngine/Game/camera.h index 504a59ed4..5bf9429cb 100644 --- a/TombEngine/Game/camera.h +++ b/TombEngine/Game/camera.h @@ -24,11 +24,11 @@ struct CAMERA_INFO GameVector target; CameraType type; CameraType oldType; + CameraType lastType; int shift; int flags; bool fixedCamera; bool underwater; - int numberFrames; int bounce; int targetDistance; short targetAngle; @@ -46,6 +46,11 @@ struct CAMERA_INFO ItemInfo* lastItem; int mikeAtLara; Vector3i mikePos; + + float Roll = 0.0f; + float Fov = 0.0f; + + bool DisableInterpolation = false; }; struct ObjectCameraInfo @@ -100,19 +105,24 @@ void BinocularCamera(ItemInfo* item); void ConfirmCameraTargetPos(); void CalculateCamera(const CollisionInfo& coll); void RumbleScreen(); +bool CalculateDeathCamera(); bool TestBoundsCollideCamera(const GameBoundingBox& bounds, const Pose& pose, short radius); void ItemPushCamera(GameBoundingBox* bounds, Pose* pos, short radius); void ItemsCollideCamera(); +void RefreshFixedCamera(short camNumber); + void ObjCamera(ItemInfo* camSlotId, int camMeshID, ItemInfo* targetItem, int targetMeshID, bool cond); void MoveObjCamera(GameVector* ideal, ItemInfo* camSlotId, int camMeshID, ItemInfo* targetItem, int targetMeshID); -void RefreshFixedCamera(short camNumber); +void ClearObjCamera(); void SetScreenFadeOut(float speed, bool force = false); void SetScreenFadeIn(float speed, bool force = false); void SetCinematicBars(float height, float speed); void ClearCinematicBars(); +void PrepareCamera(); +void UpdateCamera(); +void DrawPortals(); void UpdateFadeScreenAndCinematicBars(); void UpdateMikePos(const ItemInfo& item); -void ClearObjCamera(); float GetParticleDistanceFade(const Vector3i& pos); diff --git a/TombEngine/Game/collision/Point.cpp b/TombEngine/Game/collision/Point.cpp new file mode 100644 index 000000000..652421c8c --- /dev/null +++ b/TombEngine/Game/collision/Point.cpp @@ -0,0 +1,593 @@ +#include "framework.h" +#include "Game/collision/Point.h" + +#include "Game/collision/collide_room.h" +#include "Game/collision/floordata.h" +#include "Game/items.h" +#include "Game/room.h" +#include "Game/Setup.h" +#include "Math/Math.h" +#include "Objects/game_object_ids.h" +#include "Specific/level.h" + +using namespace TEN::Collision::Floordata; +using namespace TEN::Collision::Room; +using namespace TEN::Math; + +namespace TEN::Collision::Point +{ + PointCollisionData::PointCollisionData(const Vector3i& pos, int roomNumber) + { + _position = pos; + _roomNumber = roomNumber; + } + + Vector3i PointCollisionData::GetPosition() const + { + return _position; + } + + int PointCollisionData::GetRoomNumber() const + { + return _roomNumber; + } + + FloorInfo& PointCollisionData::GetSector() + { + if (_sector != nullptr) + return *_sector; + + // Set current sector. + short probeRoomNumber = _roomNumber; + _sector = GetFloor(_position.x, _position.y, _position.z, &probeRoomNumber); + + return *_sector; + } + + FloorInfo& PointCollisionData::GetBottomSector() + { + if (_bottomSector != nullptr) + return *_bottomSector; + + // Set bottom sector. + auto* bottomSector = &GetSector(); + auto roomNumberBelow = bottomSector->GetNextRoomNumber(_position, true); + while (roomNumberBelow.has_value()) + { + int roomNumber = roomNumberBelow.value_or(bottomSector->RoomNumber); + auto& room = g_Level.Rooms[roomNumber]; + + bottomSector = Room::GetSector(&room, _position.x - room.Position.x, _position.z - room.Position.z); + roomNumberBelow = bottomSector->GetNextRoomNumber(_position, true); + } + _bottomSector = bottomSector; + + return *_bottomSector; + } + + FloorInfo& PointCollisionData::GetTopSector() + { + if (_topSector != nullptr) + return *_topSector; + + // Set top sector. + auto* topSector = &GetSector(); + auto roomNumberAbove = topSector->GetNextRoomNumber(_position, false); + while (roomNumberAbove.has_value()) + { + int roomNumber = roomNumberAbove.value_or(topSector->RoomNumber); + auto& room = g_Level.Rooms[roomNumber]; + + topSector = Room::GetSector(&room, _position.x - room.Position.x, _position.z - room.Position.z); + roomNumberAbove = topSector->GetNextRoomNumber(_position, false); + } + _topSector = topSector; + + return *_topSector; + } + + int PointCollisionData::GetFloorHeight() + { + if (_floorHeight.has_value()) + return *_floorHeight; + + // Set floor height. + auto location = RoomVector(GetSector().RoomNumber, _position.y); + _floorHeight = Floordata::GetSurfaceHeight(location, _position.x, _position.z, true).value_or(NO_HEIGHT); + + return *_floorHeight; + } + + int PointCollisionData::GetCeilingHeight() + { + if (_ceilingHeight.has_value()) + return *_ceilingHeight; + + // Set ceiling height. + auto location = RoomVector(GetSector().RoomNumber, _position.y); + _ceilingHeight = Floordata::GetSurfaceHeight(location, _position.x, _position.z, false).value_or(NO_HEIGHT); + + return *_ceilingHeight; + } + + Vector3 PointCollisionData::GetFloorNormal() + { + if (_floorNormal.has_value()) + return *_floorNormal; + + // Set floor normal. + if (GetFloorBridgeItemNumber() != NO_VALUE) + { + _floorNormal = GetBridgeNormal(true); + } + else + { + _floorNormal = GetBottomSector().GetSurfaceNormal(_position.x, _position.z, true); + } + + return *_floorNormal; + } + + Vector3 PointCollisionData::GetCeilingNormal() + { + if (_ceilingNormal.has_value()) + return *_ceilingNormal; + + // Set ceiling normal. + if (GetCeilingBridgeItemNumber() != NO_VALUE) + { + _ceilingNormal = GetBridgeNormal(false); + } + else + { + _ceilingNormal = GetTopSector().GetSurfaceNormal(_position.x, _position.z, false); + } + + return *_ceilingNormal; + } + + int PointCollisionData::GetFloorBridgeItemNumber() + { + if (_floorBridgeItemNumber.has_value()) + return *_floorBridgeItemNumber; + + // Set floor bridge item number. + int floorHeight = GetFloorHeight(); + auto pos = Vector3i(_position.x, floorHeight, _position.z); + _floorBridgeItemNumber = GetBottomSector().GetInsideBridgeItemNumber(pos, true, false); + + return *_floorBridgeItemNumber; + } + + int PointCollisionData::GetCeilingBridgeItemNumber() + { + if (_ceilingBridgeItemNumber.has_value()) + return *_ceilingBridgeItemNumber; + + // Set ceiling bridge item number. + int ceilingHeight = GetCeilingHeight(); + auto pos = Vector3i(_position.x, ceilingHeight, _position.z); + _ceilingBridgeItemNumber = GetTopSector().GetInsideBridgeItemNumber(pos, false, true); + + return *_ceilingBridgeItemNumber; + } + + int PointCollisionData::GetWaterSurfaceHeight() + { + if (_waterSurfaceHeight.has_value()) + return *_waterSurfaceHeight; + + auto* room = &g_Level.Rooms[_roomNumber]; + const auto* sector = Room::GetSector(room, _position.x - room->Position.x, _position.z - room->Position.z); + + // Set water surface height. + bool isBelow = !TestEnvironment(ENV_FLAG_WATER, room); + while (sector->GetNextRoomNumber(_position, isBelow).has_value()) + { + room = &g_Level.Rooms[sector->GetNextRoomNumber(_position, isBelow).value_or(sector->RoomNumber)]; + if (isBelow == TestEnvironment(ENV_FLAG_WATER, room)) + { + _waterSurfaceHeight = sector->GetSurfaceHeight(_position.x, _position.z, isBelow); + return *_waterSurfaceHeight; + } + + sector = Room::GetSector(room, _position.x - room->Position.x, _position.z - room->Position.z); + } + + _waterSurfaceHeight = NO_HEIGHT; + return *_waterSurfaceHeight; + } + + int PointCollisionData::GetWaterBottomHeight() + { + if (_waterBottomHeight.has_value()) + return *_waterBottomHeight; + + FloorInfo* sector = nullptr; + auto* room = &g_Level.Rooms[_roomNumber]; + short roomNumber = _roomNumber; + + int adjoiningRoomNumber = NO_VALUE; + do + { + int x = (_position.x - room->Position.x) / BLOCK(1); + int z = (_position.z - room->Position.z) / BLOCK(1); + + if (z <= 0) + { + z = 0; + if (x < 1) + { + x = 1; + } + else if (x > (room->XSize - 2)) + { + x = room->XSize - 2; + } + } + else if (z >= (room->ZSize - 1)) + { + z = room->ZSize - 1; + if (x < 1) + { + x = 1; + } + else if (x > (room->XSize - 2)) + { + x = room->XSize - 2; + } + } + else if (x < 0) + { + x = 0; + } + else if (x >= room->XSize) + { + x = room->XSize - 1; + } + + sector = &room->Sectors[z + (x * room->ZSize)]; + adjoiningRoomNumber = sector->SidePortalRoomNumber; + if (adjoiningRoomNumber != NO_VALUE) + { + roomNumber = adjoiningRoomNumber; + room = &g_Level.Rooms[adjoiningRoomNumber]; + } + } + while (adjoiningRoomNumber != NO_VALUE); + + // Set water bottom height. + if (TestEnvironment(ENV_FLAG_WATER, room) || TestEnvironment(ENV_FLAG_SWAMP, room)) + { + while (sector->GetNextRoomNumber(_position, false).value_or(NO_VALUE) != NO_VALUE) + { + room = &g_Level.Rooms[sector->GetNextRoomNumber(_position, false).value_or(sector->RoomNumber)]; + if (!TestEnvironment(ENV_FLAG_WATER, room) && !TestEnvironment(ENV_FLAG_SWAMP, room)) + { + int waterHeight = sector->GetSurfaceHeight(_position.x, _position.z, false); + int floorHeight = GetPointCollision(_position, sector->RoomNumber).GetBottomSector().GetSurfaceHeight(_position.x, _position.z, true); + + _waterBottomHeight = floorHeight - waterHeight; + return *_waterBottomHeight; + } + + sector = Room::GetSector(room, _position.x - room->Position.x, _position.z - room->Position.z); + } + + _waterBottomHeight = DEEP_WATER; + return *_waterBottomHeight; + } + else + { + while (sector->GetNextRoomNumber(_position, true).value_or(NO_VALUE) != NO_VALUE) + { + room = &g_Level.Rooms[sector->GetNextRoomNumber(_position, true).value_or(sector->RoomNumber)]; + if (TestEnvironment(ENV_FLAG_WATER, room) || TestEnvironment(ENV_FLAG_SWAMP, room)) + { + int waterHeight = sector->GetSurfaceHeight(_position.x, _position.z, true); + sector = GetFloor(_position.x, _position.y, _position.z, &roomNumber); + + _waterBottomHeight = GetPointCollision(_position, sector->RoomNumber).GetFloorHeight() - waterHeight; + return *_waterBottomHeight; + } + + sector = Room::GetSector(room, _position.x - room->Position.x, _position.z - room->Position.z); + } + + _waterBottomHeight = NO_HEIGHT; + return *_waterBottomHeight; + } + } + + int PointCollisionData::GetWaterTopHeight() + { + if (_waterTopHeight.has_value()) + return *_waterTopHeight; + + FloorInfo* sector = nullptr; + auto* room = &g_Level.Rooms[_roomNumber]; + int roomNumber = _roomNumber; + + int adjoiningRoomNumber = NO_VALUE; + do + { + int x = (_position.x - room->Position.x) / BLOCK(1); + int z = (_position.z - room->Position.z) / BLOCK(1); + + if (z <= 0) + { + z = 0; + if (x < 1) + { + x = 1; + } + else if (x > (room->XSize - 2)) + { + x = room->XSize - 2; + } + } + else if (z >= (room->ZSize - 1)) + { + z = room->ZSize - 1; + if (x < 1) + { + x = 1; + } + else if (x > (room->XSize - 2)) + { + x = room->XSize - 2; + } + } + else if (x < 0) + { + x = 0; + } + else if (x >= room->XSize) + { + x = room->XSize - 1; + } + + sector = &room->Sectors[z + (x * room->ZSize)]; + adjoiningRoomNumber = sector->SidePortalRoomNumber; + + if (adjoiningRoomNumber != NO_VALUE) + { + roomNumber = adjoiningRoomNumber; + room = &g_Level.Rooms[adjoiningRoomNumber]; + } + } + while (adjoiningRoomNumber != NO_VALUE); + + if (sector->IsWall(_position.x, _position.z)) + { + _waterTopHeight = NO_HEIGHT; + return *_waterTopHeight; + } + + if (TestEnvironment(ENV_FLAG_WATER, room) || TestEnvironment(ENV_FLAG_SWAMP, room)) + { + while (sector->GetNextRoomNumber(_position, false).has_value()) + { + room = &g_Level.Rooms[sector->GetNextRoomNumber(_position, false).value_or(sector->RoomNumber)]; + if (!TestEnvironment(ENV_FLAG_WATER, room) && !TestEnvironment(ENV_FLAG_SWAMP, room)) + break; + + sector = Room::GetSector(room, _position.x - room->Position.x, _position.z - room->Position.z); + } + + _waterTopHeight = sector->GetSurfaceHeight(_position, false); + return *_waterTopHeight; + } + else if (sector->GetNextRoomNumber(_position, true).has_value()) + { + while (sector->GetNextRoomNumber(_position, true).has_value()) + { + room = &g_Level.Rooms[sector->GetNextRoomNumber(_position, true).value_or(sector->RoomNumber)]; + if (TestEnvironment(ENV_FLAG_WATER, room) || TestEnvironment(ENV_FLAG_SWAMP, room)) + break; + + sector = Room::GetSector(room, _position.x - room->Position.x, _position.z - room->Position.z); + } + + _waterTopHeight = sector->GetSurfaceHeight(_position, true); + return *_waterTopHeight; + } + + _waterTopHeight = NO_HEIGHT; + return *_waterTopHeight; + } + + bool PointCollisionData::IsWall() + { + return (GetFloorHeight() == NO_HEIGHT || GetCeilingHeight() == NO_HEIGHT || + GetFloorHeight() <= GetCeilingHeight()); + } + + bool PointCollisionData::IsSteepFloor() + { + short slopeAngle = Geometry::GetSurfaceSlopeAngle(GetFloorNormal()); + short steepSlopeAngle = (GetFloorBridgeItemNumber() != NO_VALUE) ? + DEFAULT_STEEP_FLOOR_SLOPE_ANGLE : + GetBottomSector().GetSurfaceIllegalSlopeAngle(_position.x, _position.z, true); + + return (abs(slopeAngle) >= steepSlopeAngle); + } + + bool PointCollisionData::IsSteepCeiling() + { + short slopeAngle = Geometry::GetSurfaceSlopeAngle(GetCeilingNormal(), -Vector3::UnitY); + short steepSlopeAngle = (GetCeilingBridgeItemNumber() != NO_VALUE) ? + DEFAULT_STEEP_CEILING_SLOPE_ANGLE : + GetTopSector().GetSurfaceIllegalSlopeAngle(_position.x, _position.z, false); + + return (abs(slopeAngle) >= steepSlopeAngle); + } + + bool PointCollisionData::IsDiagonalFloorStep() + { + return GetBottomSector().IsSurfaceDiagonalStep(true); + } + + bool PointCollisionData::IsDiagonalCeilingStep() + { + return GetTopSector().IsSurfaceDiagonalStep(false); + } + + bool PointCollisionData::IsDiagonalFloorSplit() + { + float splitAngle = GetBottomSector().FloorSurface.SplitAngle; + return (splitAngle == SectorSurfaceData::SPLIT_ANGLE_0 || splitAngle == SectorSurfaceData::SPLIT_ANGLE_1); + } + + bool PointCollisionData::IsDiagonalCeilingSplit() + { + float splitAngle = GetTopSector().CeilingSurface.SplitAngle; + return (splitAngle == SectorSurfaceData::SPLIT_ANGLE_0 || splitAngle == SectorSurfaceData::SPLIT_ANGLE_1); + } + + bool PointCollisionData::IsFlippedDiagonalFloorSplit() + { + float splitAngle = GetBottomSector().FloorSurface.SplitAngle; + return (IsDiagonalFloorStep() && splitAngle == SectorSurfaceData::SPLIT_ANGLE_1); + } + + bool PointCollisionData::IsFlippedDiagonalCeilingSplit() + { + float splitAngle = GetTopSector().CeilingSurface.SplitAngle; + return (IsDiagonalCeilingStep() && splitAngle == SectorSurfaceData::SPLIT_ANGLE_1); + } + + bool PointCollisionData::TestEnvironmentFlag(RoomEnvFlags envFlag) + { + const auto& room = g_Level.Rooms[_roomNumber]; + return ((room.flags & envFlag) == envFlag); + } + + // HACK. + Vector3 PointCollisionData::GetBridgeNormal(bool isFloor) + { + constexpr auto ANGLE_STEP = ANGLE(45.0f / 4); + + int bridgeItemNumber = isFloor ? GetFloorBridgeItemNumber() : GetCeilingBridgeItemNumber(); + const auto& bridgeItem = g_Level.Items[bridgeItemNumber]; + + auto orient = bridgeItem.Pose.Orientation; + switch (bridgeItem.ObjectNumber) + { + default: + case ID_BRIDGE_FLAT: + break; + + case ID_BRIDGE_TILT1: + orient.z -= ANGLE_STEP; + break; + + case ID_BRIDGE_TILT2: + orient.z -= ANGLE_STEP * 2; + break; + + case ID_BRIDGE_TILT3: + orient.z -= ANGLE_STEP * 3; + break; + + case ID_BRIDGE_TILT4: + orient.z -= ANGLE_STEP * 4; + break; + } + + int sign = isFloor ? -1 : 1; + return Vector3::Transform(Vector3::UnitY * sign, orient.ToRotationMatrix()); + } + + static int GetProbeRoomNumber(const Vector3i& pos, const RoomVector& location, const Vector3i& probePos) + { + // Conduct L-shaped room traversal. + short probeRoomNumber = GetRoomVector(location, Vector3i(pos.x, probePos.y, pos.z)).RoomNumber; + GetFloor(probePos.x, probePos.y, probePos.z, &probeRoomNumber); + + return probeRoomNumber; + } + + static RoomVector GetLocation(const Vector3i& pos, int roomNumber) + { + short tempRoomNumber = roomNumber; + const auto& sector = *GetFloor(pos.x, pos.y, pos.z, &tempRoomNumber); + + return RoomVector(sector.RoomNumber, pos.y); + } + + static RoomVector GetLocation(const ItemInfo& item) + { + // TODO: Find cleaner solution. Manually constructing a player "location" can result in stumbles when climbing onto thin platforms. + // May have to do with player's room number being updated at half-height? -- Sezz 2022.06.14 + if (item.IsLara()) + return item.Location; + + short tempRoomNumber = item.RoomNumber; + const auto& sector = *GetFloor(item.Pose.Position.x, item.Pose.Position.y, item.Pose.Position.z, &tempRoomNumber); + + return RoomVector(sector.RoomNumber, item.Pose.Position.y); + } + + PointCollisionData GetPointCollision(const Vector3i& pos, int roomNumber) + { + // HACK: Ensure room number is correct if position extends to another room. + // Accounts for some calls to this function which directly pass offset position instead of using dedicated probe overloads. + GetFloor(pos.x, pos.y, pos.z, (short*)&roomNumber); + + return PointCollisionData(pos, roomNumber); + } + + PointCollisionData GetPointCollision(const Vector3i& pos, int roomNumber, const Vector3& dir, float dist) + { + // Get "location". + auto location = GetLocation(pos, roomNumber); + + // Calculate probe position. + auto probePos = Geometry::TranslatePoint(pos, dir, dist); + short probeRoomNumber = GetProbeRoomNumber(pos, location, probePos); + + return PointCollisionData(probePos, probeRoomNumber); + } + + PointCollisionData GetPointCollision(const Vector3i& pos, int roomNumber, short headingAngle, float forward, float down, float right, const Vector3& axis) + { + // Get "location". + auto location = GetLocation(pos, roomNumber); + + // Calculate probe position. + auto probePos = Geometry::TranslatePoint(pos, headingAngle, forward, down, right, axis); + short probeRoomNumber = GetProbeRoomNumber(pos, location, probePos); + + return PointCollisionData(probePos, probeRoomNumber); + } + + PointCollisionData GetPointCollision(const ItemInfo& item) + { + return GetPointCollision(item.Pose.Position, item.RoomNumber); + } + + PointCollisionData GetPointCollision(const ItemInfo& item, const Vector3& dir, float dist) + { + // Get "location". + auto location = GetLocation(item); + + // Calculate probe position. + auto probePos = Geometry::TranslatePoint(item.Pose.Position, dir, dist); + short probeRoomNumber = GetProbeRoomNumber(item.Pose.Position, location, probePos); + + return PointCollisionData(probePos, probeRoomNumber); + } + + PointCollisionData GetPointCollision(const ItemInfo& item, short headingAngle, float forward, float down, float right, const Vector3& axis) + { + // Get "location". + auto location = GetLocation(item); + + // Calculate probe position. + auto probePos = Geometry::TranslatePoint(item.Pose.Position, headingAngle, forward, down, right, axis); + short probeRoomNumber = GetProbeRoomNumber(item.Pose.Position, location, probePos); + + return PointCollisionData(probePos, probeRoomNumber); + } +} diff --git a/TombEngine/Game/collision/Point.h b/TombEngine/Game/collision/Point.h new file mode 100644 index 000000000..9dbc6fe32 --- /dev/null +++ b/TombEngine/Game/collision/Point.h @@ -0,0 +1,90 @@ +#pragma once +#include "Game/collision/collide_room.h" +#include "Math/Math.h" + +enum RoomEnvFlags; +class FloorInfo; +struct ItemInfo; + +using namespace TEN::Math; + +namespace TEN::Collision::Point +{ + class PointCollisionData + { + private: + // Members + + Vector3i _position = Vector3i::Zero; + int _roomNumber = 0; + + FloorInfo* _sector = nullptr; + FloorInfo* _bottomSector = nullptr; + FloorInfo* _topSector = nullptr; + + std::optional _floorHeight = std::nullopt; + std::optional _ceilingHeight = std::nullopt; + std::optional _floorNormal = std::nullopt; + std::optional _ceilingNormal = std::nullopt; + std::optional _floorBridgeItemNumber = std::nullopt; + std::optional _ceilingBridgeItemNumber = std::nullopt; + + std::optional _waterSurfaceHeight = std::nullopt; + std::optional _waterBottomHeight = std::nullopt; + std::optional _waterTopHeight = std::nullopt; + + public: + // Constructors + + PointCollisionData(const Vector3i& pos, int roomNumber); + + // Getters + + Vector3i GetPosition() const; + int GetRoomNumber() const; + + FloorInfo& GetSector(); + FloorInfo& GetBottomSector(); + FloorInfo& GetTopSector(); + + int GetFloorHeight(); + int GetCeilingHeight(); + Vector3 GetFloorNormal(); + Vector3 GetCeilingNormal(); + int GetFloorBridgeItemNumber(); + int GetCeilingBridgeItemNumber(); + + int GetWaterSurfaceHeight(); + int GetWaterBottomHeight(); + int GetWaterTopHeight(); + + // Inquirers + + bool IsWall(); + bool IsSteepFloor(); + bool IsSteepCeiling(); + bool IsDiagonalFloorStep(); + bool IsDiagonalCeilingStep(); + bool IsDiagonalFloorSplit(); + bool IsDiagonalCeilingSplit(); + bool IsFlippedDiagonalFloorSplit(); + bool IsFlippedDiagonalCeilingSplit(); + + bool TestEnvironmentFlag(RoomEnvFlags envFlag); + + private: + // Helpers + + Vector3 GetBridgeNormal(bool isFloor); + }; + + PointCollisionData GetPointCollision(const Vector3i& pos, int roomNumber); + PointCollisionData GetPointCollision(const Vector3i& pos, int roomNumber, const Vector3& dir, float dist); + PointCollisionData GetPointCollision(const Vector3i& pos, int roomNumber, short headingAngle, float forward, float down = 0.0f, float right = 0.0f, + const Vector3& axis = Vector3::UnitY); + + PointCollisionData GetPointCollision(const ItemInfo& item); + PointCollisionData GetPointCollision(const ItemInfo& item, const Vector3& dir, float dist); + PointCollisionData GetPointCollision(const ItemInfo& item, short headingAngle, float forward, float down = 0.0f, float right = 0.0f, + const Vector3& axis = Vector3::UnitY); +} diff --git a/TombEngine/Game/collision/collide_item.cpp b/TombEngine/Game/collision/collide_item.cpp index 27e580458..eaaa9263b 100644 --- a/TombEngine/Game/collision/collide_item.cpp +++ b/TombEngine/Game/collision/collide_item.cpp @@ -4,7 +4,9 @@ #include "Game/animation.h" #include "Game/control/los.h" #include "Game/collision/collide_room.h" -#include "Game/collision/sphere.h" +#include "Game/collision/floordata.h" +#include "Game/collision/Point.h" +#include "Game/collision/Sphere.h" #include "Game/effects/debris.h" #include "Game/effects/effects.h" #include "Game/effects/simple_particle.h" @@ -16,86 +18,78 @@ #include "Game/room.h" #include "Game/Setup.h" #include "Math/Math.h" -#include "Renderer/Renderer.h" #include "Scripting/Include/ScriptInterfaceGame.h" #include "Sound/sound.h" +using namespace TEN::Collision::Floordata; +using namespace TEN::Collision::Point; +using namespace TEN::Collision::Sphere; using namespace TEN::Math; -using namespace TEN::Renderer; constexpr auto ANIMATED_ALIGNMENT_FRAME_COUNT_THRESHOLD = 6; // Globals + GameBoundingBox GlobalCollisionBounds; -void GenericSphereBoxCollision(short itemNumber, ItemInfo* laraItem, CollisionInfo* coll) +void GenericSphereBoxCollision(short itemNumber, ItemInfo* playerItem, CollisionInfo* coll) { - auto* item = &g_Level.Items[itemNumber]; + auto& item = g_Level.Items[itemNumber]; + if (item.Status == ITEM_INVISIBLE) + return; - if (item->Status != ITEM_INVISIBLE) + if (!TestBoundsCollide(&item, playerItem, coll->Setup.Radius)) + return; + + HandleItemSphereCollision(item, *playerItem); + if (!item.TouchBits.TestAny()) + return; + + short prevYOrient = item.Pose.Orientation.y; + item.Pose.Orientation.y = 0; + auto spheres = item.GetSpheres(); + item.Pose.Orientation.y = prevYOrient; + + int harmBits = *(int*)&item.ItemFlags[0]; // NOTE: Value spread across ItemFlags[0] and ItemFlags[1]. + + auto collidedBits = item.TouchBits; + if (item.ItemFlags[2] != 0) + collidedBits.Clear(0); + + coll->Setup.EnableObjectPush = (item.ItemFlags[4] == 0); + + // Handle push and damage. + for (int i = 0; i < spheres.size(); i++) { - if (TestBoundsCollide(item, laraItem, coll->Setup.Radius)) + if (collidedBits.Test(i)) { - int collidedBits = TestCollision(item, laraItem); - if (collidedBits) + const auto& sphere = spheres[i]; + + GlobalCollisionBounds.X1 = sphere.Center.x - sphere.Radius - item.Pose.Position.x; + GlobalCollisionBounds.X2 = sphere.Center.x + sphere.Radius - item.Pose.Position.x; + GlobalCollisionBounds.Y1 = sphere.Center.y - sphere.Radius - item.Pose.Position.y; + GlobalCollisionBounds.Y2 = sphere.Center.y + sphere.Radius - item.Pose.Position.y; + GlobalCollisionBounds.Z1 = sphere.Center.z - sphere.Radius - item.Pose.Position.z; + GlobalCollisionBounds.Z2 = sphere.Center.z + sphere.Radius - item.Pose.Position.z; + + auto pos = playerItem->Pose.Position; + if (ItemPushItem(&item, playerItem, coll, harmBits & 1, 3) && (harmBits & 1)) { - short prevYOrient = item->Pose.Orientation.y; + DoDamage(playerItem, item.ItemFlags[3]); - item->Pose.Orientation.y = 0; - GetSpheres(item, CreatureSpheres, SPHERES_SPACE_WORLD, Matrix::Identity); - item->Pose.Orientation.y = prevYOrient; - - int deadlyBits = *((int*)&item->ItemFlags[0]); - auto* sphere = &CreatureSpheres[0]; - - if (item->ItemFlags[2] != 0) - collidedBits &= ~1; - coll->Setup.EnableObjectPush = item->ItemFlags[4] == 0; - - while (collidedBits) + auto deltaPos = pos - playerItem->Pose.Position; + if (deltaPos != Vector3i::Zero) { - if (collidedBits & 1) - { - GlobalCollisionBounds.X1 = sphere->x - sphere->r - item->Pose.Position.x; - GlobalCollisionBounds.X2 = sphere->x + sphere->r - item->Pose.Position.x; - GlobalCollisionBounds.Y1 = sphere->y - sphere->r - item->Pose.Position.y; - GlobalCollisionBounds.Y2 = sphere->y + sphere->r - item->Pose.Position.y; - GlobalCollisionBounds.Z1 = sphere->z - sphere->r - item->Pose.Position.z; - GlobalCollisionBounds.Z2 = sphere->z + sphere->r - item->Pose.Position.z; - - int x = laraItem->Pose.Position.x; - int y = laraItem->Pose.Position.y; - int z = laraItem->Pose.Position.z; - - if (ItemPushItem(item, laraItem, coll, deadlyBits & 1, 3) && (deadlyBits & 1)) - { - DoDamage(laraItem, item->ItemFlags[3]); - - int dx = x - laraItem->Pose.Position.x; - int dy = y - laraItem->Pose.Position.y; - int dz = z - laraItem->Pose.Position.z; - - if (dx || dy || dz) - { - if (TriggerActive(item)) - TriggerLaraBlood(); - } - - if (!coll->Setup.EnableObjectPush) - { - laraItem->Pose.Position.x += dx; - laraItem->Pose.Position.y += dy; - laraItem->Pose.Position.z += dz; - } - } - } - - collidedBits >>= 1; - deadlyBits >>= 1; - sphere++; + if (TriggerActive(&item)) + TriggerLaraBlood(); } + + if (!coll->Setup.EnableObjectPush) + playerItem->Pose.Position += deltaPos; } } + + harmBits >>= 1; } } @@ -115,13 +109,20 @@ CollidedObjectData GetCollidedObjects(ItemInfo& collidingItem, bool onlyVisible, auto collidingSphere = BoundingSphere(collidingBounds.GetCenter() + collidingItem.Pose.Position.ToVector3(), collidingExtents.Length()); auto collidingCircle = Vector3(collidingSphere.Center.x, collidingSphere.Center.z, (customRadius > 0.0f) ? customRadius : std::hypot(collidingExtents.x, collidingExtents.z)); + // Convert bounding box to DX bounds. + auto convertedBounds = collidingBounds.ToBoundingOrientedBox(collidingItem.Pose); + + // Override extents if specified. + if (customRadius > 0.0f) + convertedBounds.Extents = Vector3(customRadius); + // Quickly discard collision if colliding item bounds are below tolerance threshold. if (collidingSphere.Radius <= EXTENTS_LENGTH_MIN) return collObjects; // Run through neighboring rooms. const auto& room = g_Level.Rooms[collidingItem.RoomNumber]; - for (int roomNumber : room.neighbors) + for (int roomNumber : room.NeighborRoomNumbers) { auto& neighborRoom = g_Level.Rooms[roomNumber]; if (!neighborRoom.Active()) @@ -191,16 +192,11 @@ CollidedObjectData GetCollidedObjects(ItemInfo& collidingItem, bool onlyVisible, if (!Geometry::CircleIntersects(circle, collidingCircle)) continue; - auto box0 = bounds.ToBoundingOrientedBox(item.Pose); - auto box1 = collidingBounds.ToBoundingOrientedBox(collidingItem.Pose); - - // Override extents if specified. - if (customRadius > 0.0f) - box1.Extents = Vector3(customRadius); + auto box = bounds.ToBoundingOrientedBox(item.Pose); // Test accurate box intersection. - if (box0.Intersects(box1)) - collObjects.ItemPtrs.push_back(&item); + if (box.Intersects(convertedBounds)) + collObjects.Items.push_back(&item); } while (itemNumber != NO_VALUE); } @@ -240,16 +236,18 @@ CollidedObjectData GetCollidedObjects(ItemInfo& collidingItem, bool onlyVisible, if (!Geometry::CircleIntersects(circle, collidingCircle)) continue; - auto box0 = bounds.ToBoundingOrientedBox(staticObj.pos.Position); - auto box1 = collidingBounds.ToBoundingOrientedBox(collidingItem.Pose); + // Skip if either bounding box has any zero extent (not a collidable volume). + if (bounds.GetExtents().Length() <= EXTENTS_LENGTH_MIN) + continue; - // Override extents if specified. - if (customRadius > 0.0f) - box1.Extents = Vector3(customRadius); + if (collidingBounds.GetExtents().Length() <= EXTENTS_LENGTH_MIN) + continue; + + auto box = bounds.ToBoundingOrientedBox(staticObj.pos.Position); // Test accurate box intersection. - if (box0.Intersects(box1)) - collObjects.StaticPtrs.push_back(&staticObj); + if (box.Intersects(convertedBounds)) + collObjects.Statics.push_back(&staticObj); } } } @@ -289,7 +287,8 @@ bool TestWithGlobalCollisionBounds(ItemInfo* item, ItemInfo* laraItem, Collision void TestForObjectOnLedge(ItemInfo* item, CollisionInfo* coll) { - int height = GameBoundingBox(item).GetHeight(); + auto bbox = GameBoundingBox(item).ToBoundingOrientedBox(item->Pose); + auto height = (bbox.Center - bbox.Extents).y - CLICK(1); for (int i = 0; i < 3; i++) { @@ -298,14 +297,15 @@ void TestForObjectOnLedge(ItemInfo* item, CollisionInfo* coll) auto origin = Vector3( item->Pose.Position.x + (sinHeading * (coll->Setup.Radius)), - item->Pose.Position.y - (height + CLICK(1)), + height, item->Pose.Position.z + (cosHeading * (coll->Setup.Radius))); + auto mxR = Matrix::CreateFromYawPitchRoll(TO_RAD(coll->Setup.ForwardAngle), 0.0f, 0.0f); auto direction = (Matrix::CreateTranslation(Vector3::UnitZ) * mxR).Translation(); - // g_Renderer.AddDebugSphere(origin, 16, Vector4::One, RendererDebugPage::CollisionStats); + // DrawDebugSphere(origin, 16, Vector4::One, RendererDebugPage::CollisionStats); - for (auto i : g_Level.Rooms[item->RoomNumber].neighbors) + for (auto i : g_Level.Rooms[item->RoomNumber].NeighborRoomNumbers) { if (!g_Level.Rooms[i].Active()) continue; @@ -400,7 +400,7 @@ bool AlignLaraPosition(const Vector3i& offset, ItemInfo* item, ItemInfo* laraIte auto pos = Vector3::Transform(offset.ToVector3(), rotMatrix); auto target = item->Pose.Position.ToVector3() + pos; - int height = GetCollision(target.x, target.y, target.z, laraItem->RoomNumber).Position.Floor; + int height = GetPointCollision(target, laraItem->RoomNumber).GetFloorHeight(); if ((laraItem->Pose.Position.y - height) <= CLICK(2)) { laraItem->Pose.Position = Vector3i(target); @@ -429,7 +429,7 @@ bool MoveLaraPosition(const Vector3i& offset, ItemInfo* item, ItemInfo* laraItem else { // Prevent picking up items which can result in so called "flare pickup bug" - int height = GetCollision(target.Position.x, target.Position.y, target.Position.z, laraItem->RoomNumber).Position.Floor; + int height = GetPointCollision(target.Position, laraItem->RoomNumber).GetFloorHeight(); if (abs(height - laraItem->Pose.Position.y) <= CLICK(2)) return Move3DPosTo3DPos(laraItem, laraItem->Pose, target, LARA_ALIGN_VELOCITY, ANGLE(2.0f)); } @@ -572,6 +572,9 @@ bool TestBoundsCollide(ItemInfo* item, ItemInfo* laraItem, int radius) const auto& bounds = GetBestFrame(*item).BoundingBox; const auto& playerBounds = GetBestFrame(*laraItem).BoundingBox; + if (bounds.GetExtents() == Vector3::Zero || playerBounds.GetExtents() == Vector3::Zero) + return false; + if ((item->Pose.Position.y + bounds.Y2) <= (laraItem->Pose.Position.y + playerBounds.Y1)) return false; @@ -702,7 +705,7 @@ bool ItemPushItem(ItemInfo* item0, ItemInfo* item1, CollisionInfo* coll, bool en item1->Pose.Position.Lerp(item0->Pose.Position + newDeltaPos, SOFT_PUSH_LERP_ALPHA); } // Snap to new position. - else + else if (coll->Setup.EnableObjectPush) { item1->Pose.Position = item0->Pose.Position + newDeltaPos; } @@ -885,22 +888,19 @@ void ItemPushBridge(ItemInfo& item, CollisionInfo& coll) ShiftItem(&item, &coll); } -void CollideBridgeItems(ItemInfo& item, CollisionInfo& coll, const CollisionResult& collResult) +void CollideBridgeItems(ItemInfo& item, CollisionInfo& coll, PointCollisionData& pointColl) { - // Store an offset for a bridge item into shifts, if exists. - if (coll.LastBridgeItemNumber == collResult.Position.Bridge && coll.LastBridgeItemNumber != NO_VALUE) + // Store offset for bridge item into shifts if it exists. + if (coll.LastBridgeItemNumber == pointColl.GetFloorBridgeItemNumber() && coll.LastBridgeItemNumber != NO_VALUE) { - auto& bridgeItem = g_Level.Items[collResult.Position.Bridge]; + auto& bridgeItem = g_Level.Items[pointColl.GetFloorBridgeItemNumber()]; auto deltaPos = bridgeItem.Pose.Position - coll.LastBridgeItemPose.Position; auto deltaOrient = bridgeItem.Pose.Orientation - coll.LastBridgeItemPose.Orientation; auto deltaPose = Pose(deltaPos, deltaOrient); - int absDeltaHeight = item.Pose.Position.y - collResult.Position.Floor; - int relDeltaHeight = absDeltaHeight + GameBoundingBox(&item).Y2; - - if (deltaPose != Pose::Zero && - (abs(absDeltaHeight) <= CLICK(1 / 8.0f) || abs(relDeltaHeight) <= CLICK(1 / 8.0f))) + // Item is grounded and bridge position changed; set shift. + if (deltaPose != Pose::Zero && !item.Animation.IsAirborne) { const auto& bridgePos = bridgeItem.Pose.Position; @@ -910,15 +910,19 @@ void CollideBridgeItems(ItemInfo& item, CollisionInfo& coll, const CollisionResu auto offset = bridgePos.ToVector3() + Vector3::Transform(relOffset, rotMatrix); deltaPose.Position -= item.Pose.Position - Vector3i(offset); - - // Don't update shifts if difference is too big (possibly bridge was teleported or just entered bridge). - if (deltaPose.Position.ToVector3().Length() <= coll.Setup.Radius * 2) + + // Don't update shifts if difference is too big (bridge was possibly teleported or just entered). + if (Vector2(deltaPose.Position.x, deltaPose.Position.z).Length() <= (coll.Setup.Radius * 2)) + { + deltaPose.Orientation = EulerAngles(0, deltaPose.Orientation.y, 0); coll.Shift = deltaPose; + } } - else if (deltaPos.ToVector3().Length() <= coll.Setup.Radius && relDeltaHeight > 0 && - (deltaPos != Vector3i::Zero || deltaOrient != EulerAngles::Identity)) + // Push item. + else if (TestBoundsCollide(&bridgeItem, &item, coll.Setup.Radius) && + Vector2(deltaPose.Position.x, deltaPose.Position.z).Length() <= coll.Setup.Radius && + (deltaPos != Vector3i::Zero || deltaOrient != EulerAngles::Identity)) { - // Push item away if not directly above bridge, and bridge position was changed. ItemPushItem(&bridgeItem, &item); } @@ -930,22 +934,27 @@ void CollideBridgeItems(ItemInfo& item, CollisionInfo& coll, const CollisionResu coll.LastBridgeItemNumber = NO_VALUE; } - coll.LastBridgeItemNumber = collResult.Position.Bridge; + coll.LastBridgeItemNumber = pointColl.GetFloorBridgeItemNumber(); } void CollideSolidStatics(ItemInfo* item, CollisionInfo* coll) { + coll->HitStatic = false; coll->HitTallObject = false; - for (auto i : g_Level.Rooms[item->RoomNumber].neighbors) + for (auto i : g_Level.Rooms[item->RoomNumber].NeighborRoomNumbers) { if (!g_Level.Rooms[i].Active()) continue; for (auto& mesh : g_Level.Rooms[i].mesh) { - // Only process meshes which are visible and solid. - if (!(mesh.flags & StaticMeshFlags::SM_VISIBLE) || !(mesh.flags & StaticMeshFlags::SM_SOLID)) + // Only process meshes which are visible. + if (!(mesh.flags & StaticMeshFlags::SM_VISIBLE)) + continue; + + // Only process meshes which are solid, or if solid mode is set by the setup. + if (!coll->Setup.ForceSolidStatics && !(mesh.flags & StaticMeshFlags::SM_SOLID)) continue; float distance = Vector3i::Distance(item->Pose.Position, mesh.pos.Position); @@ -984,7 +993,7 @@ bool CollideSolidBounds(ItemInfo* item, const GameBoundingBox& box, const Pose& itemBounds.Extents = itemBounds.Extents - Vector3(BLOCK(1)); // Draw static bounds. - g_Renderer.AddDebugBox(staticBounds, Vector4(1, 0.3f, 0, 1), RendererDebugPage::CollisionStats); + DrawDebugBox(staticBounds, Vector4(1, 0.3f, 0, 1), RendererDebugPage::CollisionStats); // Calculate horizontal item collision bounds according to radius. GameBoundingBox collBox; @@ -1011,7 +1020,7 @@ bool CollideSolidBounds(ItemInfo* item, const GameBoundingBox& box, const Pose& bool intersects = staticBounds.Intersects(collBounds); // Draw item coll bounds. - g_Renderer.AddDebugBox(collBounds, intersects ? Vector4(1, 0, 0, 1) : Vector4(0, 1, 0, 1), RendererDebugPage::CollisionStats); + DrawDebugBox(collBounds, intersects ? Vector4(1, 0, 0, 1) : Vector4(0, 1, 0, 1), RendererDebugPage::CollisionStats); // Decompose static bounds into top/bottom plane vertices. Vector3 corners[8]; @@ -1272,39 +1281,42 @@ void DoProjectileDynamics(short itemNumber, int x, int y, int z, int xv, int yv, { auto* item = &g_Level.Items[itemNumber]; - auto prevPointProbe = GetCollision(x, y, z, item->RoomNumber); - auto pointProbe = GetCollision(item); + auto prevPointColl = GetPointCollision(Vector3i(x, y, z), item->RoomNumber); + auto pointColl = GetPointCollision(*item); + + // TODO: Use floor normal directly. + auto floorTilt = GetSurfaceTilt(pointColl.GetFloorNormal(), true); auto bounds = GameBoundingBox(item); int radius = bounds.GetHeight(); item->Pose.Position.y += radius; - if (item->Pose.Position.y >= pointProbe.Position.Floor) + if (item->Pose.Position.y >= pointColl.GetFloorHeight()) { int bs = 0; - if (pointProbe.Position.FloorSlope && prevPointProbe.Position.Floor < pointProbe.Position.Floor) + if (pointColl.IsSteepFloor() && prevPointColl.GetFloorHeight() < pointColl.GetFloorHeight()) { int yAngle = (long)((unsigned short)item->Pose.Orientation.y); - if (pointProbe.FloorTilt.x < 0) + if (floorTilt.x < 0) { if (yAngle >= ANGLE(180.0f)) bs = 1; } - else if (pointProbe.FloorTilt.x > 0) + else if (floorTilt.x > 0) { if (yAngle <= ANGLE(180.0f)) bs = 1; } - if (pointProbe.FloorTilt.y < 0) + if (floorTilt.y < 0) { if (yAngle >= ANGLE(90.0f) && yAngle <= ANGLE(270.0f)) bs = 1; } - else if (pointProbe.FloorTilt.y > 0) + else if (floorTilt.y > 0) { if (yAngle <= ANGLE(90.0f) || yAngle >= ANGLE(270.0f)) bs = 1; @@ -1313,7 +1325,7 @@ void DoProjectileDynamics(short itemNumber, int x, int y, int z, int xv, int yv, // If last position of item was also below this floor height, we've hit a wall, else we've hit a floor. - if (y > (pointProbe.Position.Floor + 32) && bs == 0 && + if (y > (pointColl.GetFloorHeight() + 32) && bs == 0 && (((x / BLOCK(1)) != (item->Pose.Position.x / BLOCK(1))) || ((z / BLOCK(1)) != (item->Pose.Position.z / BLOCK(1))))) { @@ -1353,14 +1365,14 @@ void DoProjectileDynamics(short itemNumber, int x, int y, int z, int xv, int yv, item->Pose.Position.z = z; } // Hit a steep slope? - else if (pointProbe.Position.FloorSlope) + else if (pointColl.IsSteepFloor()) { // Need to know which direction the slope is. item->Animation.Velocity.z -= (item->Animation.Velocity.z / 4); // Hit angle = ANGLE(90.0f) - if (pointProbe.FloorTilt.x < 0 && ((abs(pointProbe.FloorTilt.x)) - (abs(pointProbe.FloorTilt.y)) >= 2)) + if (floorTilt.x < 0 && ((abs(floorTilt.x)) - (abs(floorTilt.y)) >= 2)) { if (((unsigned short)item->Pose.Orientation.y) > ANGLE(180.0f)) { @@ -1372,7 +1384,7 @@ void DoProjectileDynamics(short itemNumber, int x, int y, int z, int xv, int yv, { if (item->Animation.Velocity.z < 32) { - item->Animation.Velocity.z -= pointProbe.FloorTilt.x * 2; + item->Animation.Velocity.z -= floorTilt.x * 2; if ((unsigned short)item->Pose.Orientation.y > ANGLE(90.0f) && (unsigned short)item->Pose.Orientation.y < ANGLE(270.0f)) { item->Pose.Orientation.y -= ANGLE(22.5f); @@ -1394,7 +1406,7 @@ void DoProjectileDynamics(short itemNumber, int x, int y, int z, int xv, int yv, } } // Hit angle = ANGLE(270.0f) - else if (pointProbe.FloorTilt.x > 0 && ((abs(pointProbe.FloorTilt.x)) - (abs(pointProbe.FloorTilt.y)) >= 2)) + else if (floorTilt.x > 0 && ((abs(floorTilt.x)) - (abs(floorTilt.y)) >= 2)) { if (((unsigned short)item->Pose.Orientation.y) < ANGLE(180.0f)) { @@ -1406,7 +1418,7 @@ void DoProjectileDynamics(short itemNumber, int x, int y, int z, int xv, int yv, { if (item->Animation.Velocity.z < 32) { - item->Animation.Velocity.z += pointProbe.FloorTilt.x * 2; + item->Animation.Velocity.z += floorTilt.x * 2; if ((unsigned short)item->Pose.Orientation.y > ANGLE(270.0f) || (unsigned short)item->Pose.Orientation.y < ANGLE(90.0f)) { item->Pose.Orientation.y -= ANGLE(22.5f); @@ -1428,7 +1440,7 @@ void DoProjectileDynamics(short itemNumber, int x, int y, int z, int xv, int yv, } } // Hit angle = 0 - else if (pointProbe.FloorTilt.y < 0 && ((abs(pointProbe.FloorTilt.y)) - (abs(pointProbe.FloorTilt.x)) >= 2)) + else if (floorTilt.y < 0 && ((abs(floorTilt.y)) - (abs(floorTilt.x)) >= 2)) { if (((unsigned short)item->Pose.Orientation.y) > ANGLE(90.0f) && ((unsigned short)item->Pose.Orientation.y) < ANGLE(270.0f)) { @@ -1440,7 +1452,7 @@ void DoProjectileDynamics(short itemNumber, int x, int y, int z, int xv, int yv, { if (item->Animation.Velocity.z < 32) { - item->Animation.Velocity.z -= pointProbe.FloorTilt.y * 2; + item->Animation.Velocity.z -= floorTilt.y * 2; if ((unsigned short)item->Pose.Orientation.y < ANGLE(180.0f)) { @@ -1463,7 +1475,7 @@ void DoProjectileDynamics(short itemNumber, int x, int y, int z, int xv, int yv, } } // Hit angle = ANGLE(180.0f) - else if (pointProbe.FloorTilt.y > 0 && ((abs(pointProbe.FloorTilt.y)) - (abs(pointProbe.FloorTilt.x)) >= 2)) + else if (floorTilt.y > 0 && ((abs(floorTilt.y)) - (abs(floorTilt.x)) >= 2)) { if (((unsigned short)item->Pose.Orientation.y) > ANGLE(270.0f) || ((unsigned short)item->Pose.Orientation.y) < ANGLE(90.0f)) { @@ -1475,7 +1487,7 @@ void DoProjectileDynamics(short itemNumber, int x, int y, int z, int xv, int yv, { if (item->Animation.Velocity.z < 32) { - item->Animation.Velocity.z += pointProbe.FloorTilt.y * 2; + item->Animation.Velocity.z += floorTilt.y * 2; if ((unsigned short)item->Pose.Orientation.y > ANGLE(180.0f)) { @@ -1497,7 +1509,7 @@ void DoProjectileDynamics(short itemNumber, int x, int y, int z, int xv, int yv, item->Animation.Velocity.y = 0; } } - else if (pointProbe.FloorTilt.x < 0 && pointProbe.FloorTilt.y < 0) // Hit angle = 0x2000 + else if (floorTilt.x < 0 && floorTilt.y < 0) // Hit angle = 0x2000 { if (((unsigned short)item->Pose.Orientation.y) > ANGLE(135.0f) && ((unsigned short)item->Pose.Orientation.y) < ANGLE(315.0f)) { @@ -1509,7 +1521,7 @@ void DoProjectileDynamics(short itemNumber, int x, int y, int z, int xv, int yv, { if (item->Animation.Velocity.z < 32) { - item->Animation.Velocity.z += -pointProbe.FloorTilt.x + -pointProbe.FloorTilt.y; + item->Animation.Velocity.z += -floorTilt.x + -floorTilt.y; if ((unsigned short)item->Pose.Orientation.y > ANGLE(45.0f) && (unsigned short)item->Pose.Orientation.y < ANGLE(225.0f)) { item->Pose.Orientation.y -= ANGLE(22.5f); @@ -1531,7 +1543,7 @@ void DoProjectileDynamics(short itemNumber, int x, int y, int z, int xv, int yv, } } // Hit angle = ANGLE(135.0f) - else if (pointProbe.FloorTilt.x < 0 && pointProbe.FloorTilt.y > 0) + else if (floorTilt.x < 0 && floorTilt.y > 0) { if (((unsigned short)item->Pose.Orientation.y) > ANGLE(225.0f) || ((unsigned short)item->Pose.Orientation.y) < ANGLE(45.0f)) { @@ -1543,7 +1555,7 @@ void DoProjectileDynamics(short itemNumber, int x, int y, int z, int xv, int yv, { if (item->Animation.Velocity.z < 32) { - item->Animation.Velocity.z += (-pointProbe.FloorTilt.x) + pointProbe.FloorTilt.y; + item->Animation.Velocity.z += (-floorTilt.x) + floorTilt.y; if ((unsigned short)item->Pose.Orientation.y < ANGLE(315.0f) && (unsigned short)item->Pose.Orientation.y > ANGLE(135.0f)) { item->Pose.Orientation.y -= ANGLE(22.5f); @@ -1565,7 +1577,7 @@ void DoProjectileDynamics(short itemNumber, int x, int y, int z, int xv, int yv, } } // Hit angle = ANGLE(225.5f) - else if (pointProbe.FloorTilt.x > 0 && pointProbe.FloorTilt.y > 0) + else if (floorTilt.x > 0 && floorTilt.y > 0) { if (((unsigned short)item->Pose.Orientation.y) > ANGLE(315.0f) || ((unsigned short)item->Pose.Orientation.y) < ANGLE(135.0f)) { @@ -1577,7 +1589,7 @@ void DoProjectileDynamics(short itemNumber, int x, int y, int z, int xv, int yv, { if (item->Animation.Velocity.z < 32) { - item->Animation.Velocity.z += pointProbe.FloorTilt.x + pointProbe.FloorTilt.y; + item->Animation.Velocity.z += floorTilt.x + floorTilt.y; if ((unsigned short)item->Pose.Orientation.y < ANGLE(45.0f) || (unsigned short)item->Pose.Orientation.y > ANGLE(225.5f)) { item->Pose.Orientation.y -= ANGLE(22.5f); @@ -1599,7 +1611,7 @@ void DoProjectileDynamics(short itemNumber, int x, int y, int z, int xv, int yv, } } // Hit angle = ANGLE(315.0f) - else if (pointProbe.FloorTilt.x > 0 && pointProbe.FloorTilt.y < 0) + else if (floorTilt.x > 0 && floorTilt.y < 0) { if (((unsigned short)item->Pose.Orientation.y) > ANGLE(45.0f) && ((unsigned short)item->Pose.Orientation.y) < ANGLE(225.5f)) { @@ -1611,7 +1623,7 @@ void DoProjectileDynamics(short itemNumber, int x, int y, int z, int xv, int yv, { if (item->Animation.Velocity.z < 32) { - item->Animation.Velocity.z += pointProbe.FloorTilt.x + (-pointProbe.FloorTilt.y); + item->Animation.Velocity.z += floorTilt.x + (-floorTilt.y); if ((unsigned short)item->Pose.Orientation.y < ANGLE(135.0f) || (unsigned short)item->Pose.Orientation.y > ANGLE(315.0f)) { item->Pose.Orientation.y -= ANGLE(22.5f); @@ -1672,7 +1684,7 @@ void DoProjectileDynamics(short itemNumber, int x, int y, int z, int xv, int yv, } } - item->Pose.Position.y = pointProbe.Position.Floor; + item->Pose.Position.y = pointColl.GetFloorHeight(); } } // Check for on top of object. @@ -1680,8 +1692,8 @@ void DoProjectileDynamics(short itemNumber, int x, int y, int z, int xv, int yv, { if (yv >= 0) { - prevPointProbe = GetCollision(item->Pose.Position.x, y, item->Pose.Position.z, item->RoomNumber); - pointProbe = GetCollision(item); + prevPointColl = GetPointCollision(Vector3i(item->Pose.Position.x, y, item->Pose.Position.z), item->RoomNumber); + pointColl = GetPointCollision(*item); // Bounce off floor. @@ -1689,7 +1701,7 @@ void DoProjectileDynamics(short itemNumber, int x, int y, int z, int xv, int yv, // was always set to 0 by GetHeight() function which was called before the check. // Possibly a mistake or unfinished feature by Core? -- Lwmte, 27.08.21 - if (item->Pose.Position.y >= prevPointProbe.Position.Floor) + if (item->Pose.Position.y >= prevPointColl.GetFloorHeight()) { // Hit the floor; bounce and slow down. if (item->Animation.Velocity.y > 0) @@ -1723,17 +1735,17 @@ void DoProjectileDynamics(short itemNumber, int x, int y, int z, int xv, int yv, } } - item->Pose.Position.y = prevPointProbe.Position.Floor; + item->Pose.Position.y = prevPointColl.GetFloorHeight(); } } // else { // Bounce off ceiling. - pointProbe = GetCollision(item); + pointColl = GetPointCollision(*item); - if (item->Pose.Position.y < pointProbe.Position.Ceiling) + if (item->Pose.Position.y < pointColl.GetCeilingHeight()) { - if (y < pointProbe.Position.Ceiling && + if (y < pointColl.GetCeilingHeight() && (((x / BLOCK(1)) != (item->Pose.Position.x / BLOCK(1))) || ((z / BLOCK(1)) != (item->Pose.Position.z / BLOCK(1))))) { @@ -1762,7 +1774,7 @@ void DoProjectileDynamics(short itemNumber, int x, int y, int z, int xv, int yv, item->Pose.Position.z = z; } else - item->Pose.Position.y = pointProbe.Position.Ceiling; + item->Pose.Position.y = pointColl.GetCeilingHeight(); if (item->Animation.Velocity.y < 0) item->Animation.Velocity.y = -item->Animation.Velocity.y; @@ -1770,14 +1782,14 @@ void DoProjectileDynamics(short itemNumber, int x, int y, int z, int xv, int yv, } } - pointProbe = GetCollision(item->Pose.Position.x, item->Pose.Position.y, item->Pose.Position.z, item->RoomNumber); + pointColl = GetPointCollision(*item); - if (pointProbe.RoomNumber != item->RoomNumber) + if (pointColl.GetRoomNumber() != item->RoomNumber) { - if (item->ObjectNumber == ID_GRENADE && TestEnvironment(RoomEnvFlags::ENV_FLAG_WATER, pointProbe.RoomNumber)) + if (item->ObjectNumber == ID_GRENADE && TestEnvironment(RoomEnvFlags::ENV_FLAG_WATER, pointColl.GetRoomNumber())) Splash(item); - ItemNewRoom(itemNumber, pointProbe.RoomNumber); + ItemNewRoom(itemNumber, pointColl.GetRoomNumber()); } item->Pose.Position.y -= radius; @@ -1803,7 +1815,7 @@ void DoObjectCollision(ItemInfo* item, CollisionInfo* coll) return; const auto& room = g_Level.Rooms[item->RoomNumber]; - for (int neighborRoomNumber : room.neighbors) + for (int neighborRoomNumber : room.NeighborRoomNumbers) { auto& neighborRoom = g_Level.Rooms[neighborRoomNumber]; if (!neighborRoom.Active()) @@ -1862,9 +1874,10 @@ void DoObjectCollision(ItemInfo* item, CollisionInfo* coll) if (linkItem.HitPoints <= 0 || linkItem.HitPoints == NOT_TARGETABLE) continue; - if (isHarmless || abs(item->Animation.Velocity.z) < VEHICLE_COLLISION_TERMINAL_VELOCITY) + if (isHarmless || abs(item->Animation.Velocity.z) < VEHICLE_COLLISION_TERMINAL_VELOCITY || + object.damageType == DamageMode::None) { - // If vehicle is harmless or speed is too low, just push enemy. + // If vehicle is harmless, enemy is non-damageable, or speed is too low, push enemy. ItemPushItem(&linkItem, item, coll, false, 0); continue; } @@ -1952,7 +1965,7 @@ void ObjectCollision(const short itemNumber, ItemInfo* laraItem, CollisionInfo* if (TestBoundsCollide(item, laraItem, coll->Setup.Radius)) { - if (TestCollision(item, laraItem)) + if (HandleItemSphereCollision(*item, *laraItem)) { if (coll->Setup.EnableObjectPush) ItemPushItem(item, laraItem, coll, false, 1); @@ -1967,7 +1980,7 @@ void CreatureCollision(short itemNumber, ItemInfo* laraItem, CollisionInfo* coll if (!TestBoundsCollide(item, laraItem, coll->Setup.Radius)) return; - if (!TestCollision(item, laraItem)) + if (!HandleItemSphereCollision(*item, *laraItem)) return; bool doPlayerCollision = laraItem->IsLara(); @@ -2002,7 +2015,7 @@ void TrapCollision(short itemNumber, ItemInfo* playerItem, CollisionInfo* coll) if (!TestBoundsCollide(&item, playerItem, coll->Setup.Radius)) return; - TestCollision(&item, playerItem); + HandleItemSphereCollision(item, *playerItem); } else if (item.Status != ITEM_INVISIBLE) { diff --git a/TombEngine/Game/collision/collide_item.h b/TombEngine/Game/collision/collide_item.h index 3ca5b5ecb..5c0a4f908 100644 --- a/TombEngine/Game/collision/collide_item.h +++ b/TombEngine/Game/collision/collide_item.h @@ -1,6 +1,9 @@ #pragma once +#include "Game/collision/Point.h" #include "Math/Math.h" +using namespace TEN::Collision::Point; + class FloorInfo; struct CollisionInfo; struct CollisionResult; @@ -27,13 +30,13 @@ struct ObjectCollisionBounds struct CollidedObjectData { - std::vector ItemPtrs = {}; - std::vector StaticPtrs = {}; + std::vector Items = {}; + std::vector Statics = {}; - bool IsEmpty() const { return (ItemPtrs.empty() && StaticPtrs.empty()); }; + bool IsEmpty() const { return (Items.empty() && Statics.empty()); }; }; -void GenericSphereBoxCollision(short itemNumber, ItemInfo* laraItem, CollisionInfo* coll); +void GenericSphereBoxCollision(short itemNumber, ItemInfo* playerItem, CollisionInfo* coll); CollidedObjectData GetCollidedObjects(ItemInfo& collidingItem, bool onlyVisible, bool ignorePlayer, float customRadius = 0.0f, ObjectCollectionMode mode = ObjectCollectionMode::All); bool TestWithGlobalCollisionBounds(ItemInfo* item, ItemInfo* laraItem, CollisionInfo* coll); void TestForObjectOnLedge(ItemInfo* item, CollisionInfo* coll); @@ -56,7 +59,7 @@ void ItemPushBridge(ItemInfo& item, CollisionInfo& coll); bool CollideSolidBounds(ItemInfo* item, const GameBoundingBox& box, const Pose& pose, CollisionInfo* coll); void CollideSolidStatics(ItemInfo* item, CollisionInfo* coll); -void CollideBridgeItems(ItemInfo& item, CollisionInfo& coll, const CollisionResult& collResult); +void CollideBridgeItems(ItemInfo& item, CollisionInfo& coll, PointCollisionData& pointColl); void AIPickupCollision(short itemNumber, ItemInfo* laraItem, CollisionInfo* coll); void ObjectCollision(short itemNumber, ItemInfo* laraItem, CollisionInfo* coll); diff --git a/TombEngine/Game/collision/collide_room.cpp b/TombEngine/Game/collision/collide_room.cpp index 903b6938e..a083624d0 100644 --- a/TombEngine/Game/collision/collide_room.cpp +++ b/TombEngine/Game/collision/collide_room.cpp @@ -4,17 +4,18 @@ #include "Game/control/box.h" #include "Game/control/los.h" #include "Game/collision/collide_item.h" +#include "Game/collision/Point.h" #include "Game/animation.h" #include "Game/Lara/lara.h" #include "Game/items.h" #include "Game/room.h" #include "Math/Math.h" #include "Sound/sound.h" -#include "Renderer/Renderer.h" using namespace TEN::Collision::Floordata; +using namespace TEN::Collision::Point; +using namespace TEN::Collision::Room; using namespace TEN::Math; -using namespace TEN::Renderer; void ShiftItem(ItemInfo* item, CollisionInfo* coll) { @@ -97,12 +98,12 @@ bool TestItemRoomCollisionAABB(ItemInfo* item) auto test = [item](short x, short y, short z, bool floor) { - auto collPos = GetCollision(x, y, z, item->RoomNumber).Position; + auto pointColl = GetPointCollision(Vector3i(x, y, z), item->RoomNumber); if (floor) - return (y > collPos.Floor); + return (y > pointColl.GetFloorHeight()); else - return (y < collPos.Ceiling); + return (y < pointColl.GetCeilingHeight()); }; bool collided = @@ -118,155 +119,26 @@ bool TestItemRoomCollisionAABB(ItemInfo* item) return collided; } -// Overload used to quickly get point collision parameters at a given item's position. -CollisionResult GetCollision(const ItemInfo& item) +static CollisionPositionData GetCollisionPosition(PointCollisionData& pointColl) { - short newRoomNumber = item.RoomNumber; - auto floor = GetFloor(item.Pose.Position.x, item.Pose.Position.y, item.Pose.Position.z, &newRoomNumber); - auto probe = GetCollision(floor, item.Pose.Position.x, item.Pose.Position.y, item.Pose.Position.z); + auto collPos = CollisionPositionData{}; + collPos.Floor = pointColl.GetFloorHeight(); + collPos.Ceiling = pointColl.GetCeilingHeight(); + collPos.Bridge = pointColl.GetFloorBridgeItemNumber(); + collPos.SplitAngle = pointColl.GetBottomSector().FloorSurface.SplitAngle; + collPos.FloorSlope = pointColl.IsSteepFloor(); + collPos.CeilingSlope = pointColl.IsSteepCeiling(); + collPos.DiagonalStep = pointColl.IsDiagonalFloorStep(); - probe.RoomNumber = newRoomNumber; - return probe; + return collPos; } -// Deprecated. -CollisionResult GetCollision(const ItemInfo* item) -{ - return GetCollision(*item); -} - -// Overload used to probe point collision parameters from a given item's position. -CollisionResult GetCollision(const ItemInfo* item, short headingAngle, float forward, float down, float right) -{ - short tempRoomNumber = item->RoomNumber; - - // TODO: Find cleaner solution. Constructing a Location for Lara on the spot can result in a stumble when climbing onto thin platforms. -- Sezz 2022.06.14 - auto location = item->IsLara() ? - item->Location : - RoomVector(GetFloor(item->Pose.Position.x, item->Pose.Position.y, item->Pose.Position.z, &tempRoomNumber)->RoomNumber, item->Pose.Position.y); - - auto point = Geometry::TranslatePoint(item->Pose.Position, headingAngle, forward, down, right); - int adjacentRoomNumber = GetRoomVector(location, Vector3i(item->Pose.Position.x, point.y, item->Pose.Position.z)).RoomNumber; - return GetCollision(point.x, point.y, point.z, adjacentRoomNumber); -} - -// Overload used to probe point collision parameters from a given position. -CollisionResult GetCollision(const Vector3i& pos, int roomNumber, short headingAngle, float forward, float down, float right) -{ - short tempRoomNumber = roomNumber; - auto location = RoomVector(GetFloor(pos.x, pos.y, pos.z, &tempRoomNumber)->RoomNumber, pos.y); - - auto point = Geometry::TranslatePoint(pos, headingAngle, forward, down, right); - int adjacentRoomNumber = GetRoomVector(location, Vector3i(pos.x, point.y, pos.z)).RoomNumber; - return GetCollision(point.x, point.y, point.z, adjacentRoomNumber); -} - -CollisionResult GetCollision(const Vector3i& pos, int roomNumber, const EulerAngles& orient, float dist) -{ - auto point = Geometry::TranslatePoint(pos, orient, dist); - - short tempRoomNumber = roomNumber; - auto location = RoomVector(GetFloor(pos.x, pos.y, pos.z, &tempRoomNumber)->RoomNumber, pos.y); - int adjacentRoomNumber = GetRoomVector(location, Vector3i(pos.x, point.y, pos.z)).RoomNumber; - return GetCollision(point.x, point.y, point.z, adjacentRoomNumber); -} - -CollisionResult GetCollision(const Vector3i& pos, int roomNumber, const Vector3& dir, float dist) -{ - auto point = Geometry::TranslatePoint(pos, dir, dist); - - short tempRoomNumber = roomNumber; - auto location = RoomVector(GetFloor(pos.x, pos.y, pos.z, &tempRoomNumber)->RoomNumber, pos.y); - int adjacentRoomNumber = GetRoomVector(location, Vector3i(pos.x, point.y, pos.z)).RoomNumber; - return GetCollision(point.x, point.y, point.z, adjacentRoomNumber); -} - -// Overload used as universal wrapper across collisional code replacing -// triads of roomNumber-GetFloor()-GetFloorHeight() calls. -// Advantage is that it does NOT modify incoming roomNumber argument, -// instead storing one modified by GetFloor() within a returned CollisionResult struct. -// This way, no external variables are modified as output arguments. -CollisionResult GetCollision(const Vector3i& pos, int roomNumber) -{ - return GetCollision(pos.x, pos.y, pos.z, roomNumber); -} - -// Deprecated. -CollisionResult GetCollision(int x, int y, int z, short roomNumber) -{ - auto room = roomNumber; - auto floor = GetFloor(x, y, z, &room); - auto result = GetCollision(floor, x, y, z); - - result.RoomNumber = room; - return result; -} - -// NOTE: To be used only when absolutely necessary. -CollisionResult GetCollision(const GameVector& pos) -{ - return GetCollision(pos.x, pos.y, pos.z, pos.RoomNumber); -} - -// A reworked legacy GetFloorHeight() function which writes data -// into a special CollisionResult struct instead of global variables. -// It writes for both floor and ceiling heights at the same coordinates, meaning it should be used -// in place of successive GetFloorHeight() and GetCeilingHeight() calls to increase readability. -CollisionResult GetCollision(FloorInfo* floor, int x, int y, int z) -{ - auto result = CollisionResult{}; - - // Record coordinates. - result.Coordinates = Vector3i(x, y, z); - - // Return provided collision block into result as itself. - result.Block = floor; - - // Floor and ceiling heights are borrowed directly from floordata. - result.Position.Floor = GetSurfaceHeight(RoomVector(floor->RoomNumber, y), x, z, true).value_or(NO_HEIGHT); - result.Position.Ceiling = GetSurfaceHeight(RoomVector(floor->RoomNumber, y), x, z, false).value_or(NO_HEIGHT); - - // Probe bottom collision block through portals. - while (floor->GetNextRoomNumber(Vector3i(x, y, z), true).has_value()) - { - auto* room = &g_Level.Rooms[floor->GetNextRoomNumber(Vector3i(x, y, z), true).value_or(floor->RoomNumber)]; - floor = GetSector(room, x - room->x, z - room->z); - } - - // Return probed bottom collision block into result. - result.BottomBlock = floor; - - // Get surface noramls. - result.FloorNormal = floor->GetSurfaceNormal(x, z, true); - result.CeilingNormal = floor->GetSurfaceNormal(x, z, false); - - // Backport surface normals to tilts. - result.FloorTilt = GetSurfaceTilt(result.FloorNormal, true).ToVector2(); - result.CeilingTilt = GetSurfaceTilt(result.CeilingNormal, false).ToVector2(); - - // Split, bridge and slope data. - result.Position.DiagonalStep = floor->IsSurfaceDiagonalStep(true); - result.Position.SplitAngle = TO_RAD(floor->FloorSurface.SplitAngle); - result.Position.Bridge = result.BottomBlock->GetInsideBridgeItemNumber(Vector3i(x, result.Position.Floor, z), true, false); - result.Position.FloorSlope = result.Position.Bridge < 0 && Geometry::GetSurfaceSlopeAngle(result.FloorNormal) >= - result.BottomBlock->GetSurfaceIllegalSlopeAngle(x, z, true); - result.Position.CeilingSlope = Geometry::GetSurfaceSlopeAngle(result.CeilingNormal, -Vector3::UnitY) >= - result.BottomBlock->GetSurfaceIllegalSlopeAngle(x, z, false); // TODO: Fix on bridges placed beneath ceiling slopes. @Sezz 2022.01.29 - - return result; -} - -void GetCollisionInfo(CollisionInfo* coll, ItemInfo* item, bool resetRoom) -{ - GetCollisionInfo(coll, item, Vector3i::Zero, resetRoom); -} - -static void SetSectorAttribs(CollisionPosition& sectorAttribs, const CollisionSetup& collSetup, const CollisionResult& pointColl, +static void SetSectorAttribs(CollisionPositionData& sectorAttribs, const CollisionSetupData& collSetup, PointCollisionData& pointColl, const Vector3i& probePos, int realRoomNumber) { constexpr auto ASPECT_ANGLE_DELTA_MAX = ANGLE(90.0f); - auto floorNormal = pointColl.FloorNormal; + auto floorNormal = pointColl.GetFloorNormal(); short aspectAngle = Geometry::GetSurfaceAspectAngle(floorNormal); short aspectAngleDelta = Geometry::GetShortestAngle(collSetup.ForwardAngle, aspectAngle); @@ -293,14 +165,14 @@ static void SetSectorAttribs(CollisionPosition& sectorAttribs, const CollisionSe } else if (collSetup.BlockDeathFloorDown && sectorAttribs.Floor >= CLICK(0.5f) && - pointColl.BottomBlock->Flags.Death) + pointColl.GetBottomSector().Flags.Death) { sectorAttribs.Floor = MAX_HEIGHT; } else if (collSetup.BlockMonkeySwingEdge) { - auto pointColl = GetCollision(probePos.x, probePos.y + collSetup.Height, probePos.z, realRoomNumber); - if (!pointColl.BottomBlock->Flags.Monkeyswing) + auto pointColl = GetPointCollision(probePos, realRoomNumber, Vector3::UnitY, collSetup.Height); + if (!pointColl.GetBottomSector().Flags.Monkeyswing) sectorAttribs.Floor = MAX_HEIGHT; } } @@ -407,8 +279,8 @@ void GetCollisionInfo(CollisionInfo* coll, ItemInfo* item, const Vector3i& offse // HACK: when using SetPosition animcommand, item->RoomNumber does not immediately // update, but only at the end of control loop. This may cause bugs when Lara is - // climbing or vaulting ledges under slopes. Using Location->roomNumber solves - // these bugs, as it is updated immediately. But since Location->roomNumber is ONLY + // climbing or vaulting ledges under slopes. Using Location->RoomNumber solves + // these bugs, as it is updated immediately. But since Location->RoomNumber is ONLY // updated for Lara, we can't use it for all objects for now. In future, we should // either update Location field for all objects or use this value as it is now. @@ -416,22 +288,22 @@ void GetCollisionInfo(CollisionInfo* coll, ItemInfo* item, const Vector3i& offse // TEST 1: TILT AND NEAREST LEDGE CALCULATION - auto collResult = GetCollision(probePos.x, item->Pose.Position.y, probePos.z, realRoomNumber); + auto pointColl = GetPointCollision(Vector3i(probePos.x, item->Pose.Position.y, probePos.z), realRoomNumber); - coll->FloorNormal = collResult.FloorNormal; - coll->CeilingNormal = collResult.CeilingNormal; - coll->FloorTilt = collResult.FloorTilt; - coll->CeilingTilt = collResult.CeilingTilt; + coll->FloorNormal = pointColl.GetFloorNormal(); + coll->CeilingNormal = pointColl.GetCeilingNormal(); + coll->FloorTilt = GetSurfaceTilt(pointColl.GetFloorNormal(), true).ToVector2(); + coll->CeilingTilt = GetSurfaceTilt(pointColl.GetCeilingNormal(), false).ToVector2(); coll->NearestLedgeAngle = GetNearestLedgeAngle(item, coll, coll->NearestLedgeDistance); // Debug angle and distance - // g_Renderer.PrintDebugMessage("Nearest angle: %d", coll->NearestLedgeAngle); - // g_Renderer.PrintDebugMessage("Nearest dist: %f", coll->NearestLedgeDistance); + // PrintDebugMessage("Nearest angle: %d", coll->NearestLedgeAngle); + // PrintDebugMessage("Nearest dist: %f", coll->NearestLedgeDistance); // TEST 2: CENTERPOINT PROBE - collResult = GetCollision(probePos.x, probePos.y, probePos.z, realRoomNumber); - int topRoomNumber = collResult.RoomNumber; // Keep top room number as we need it to re-probe from origin room. + pointColl = GetPointCollision(probePos, realRoomNumber); + int topRoomNumber = pointColl.GetRoomNumber(); // Keep top room number as we need it to re-probe from origin room. if (doPlayerCollision) { @@ -443,8 +315,8 @@ void GetCollisionInfo(CollisionInfo* coll, ItemInfo* item, const Vector3i& offse } else { - height = collResult.Position.Floor; - ceiling = GetCeiling(collResult.Block, probePos.x, probePos.y - item->Animation.Velocity.y, probePos.z); + height = pointColl.GetFloorHeight(); + ceiling = GetCeiling(&pointColl.GetSector(), probePos.x, probePos.y - item->Animation.Velocity.y, probePos.z); } if (height != NO_HEIGHT) @@ -453,21 +325,21 @@ void GetCollisionInfo(CollisionInfo* coll, ItemInfo* item, const Vector3i& offse if (ceiling != NO_HEIGHT) ceiling -= probePos.y; - coll->Middle = collResult.Position; + coll->Middle = GetCollisionPosition(pointColl); coll->Middle.Floor = height; coll->Middle.Ceiling = ceiling; - // Additionally calculate bridge shifts, if present. - CollideBridgeItems(*item, *coll, collResult); + // Additionally calculate bridge shifts if present. + CollideBridgeItems(*item, *coll, pointColl); // TEST 3: FRONTAL PROBE probePos.x = entityPos.x + xFront; probePos.z = entityPos.z + zFront; - g_Renderer.AddDebugSphere(probePos.ToVector3(), 32, Vector4(1, 0, 0, 1), RendererDebugPage::CollisionStats); + DrawDebugSphere(probePos.ToVector3(), 32, Vector4(1, 0, 0, 1), RendererDebugPage::CollisionStats); - collResult = GetCollision(probePos.x, probePos.y, probePos.z, topRoomNumber); + pointColl = GetPointCollision(probePos, topRoomNumber); if (doPlayerCollision) { @@ -486,8 +358,8 @@ void GetCollisionInfo(CollisionInfo* coll, ItemInfo* item, const Vector3i& offse } else { - height = collResult.Position.Floor; - ceiling = GetCeiling(collResult.Block, probePos.x, probePos.y - item->Animation.Velocity.y, probePos.z); + height = pointColl.GetFloorHeight(); + ceiling = GetCeiling(&pointColl.GetSector(), probePos.x, probePos.y - item->Animation.Velocity.y, probePos.z); } if (height != NO_HEIGHT) @@ -496,7 +368,7 @@ void GetCollisionInfo(CollisionInfo* coll, ItemInfo* item, const Vector3i& offse if (ceiling != NO_HEIGHT) ceiling -= probePos.y; - coll->Front = collResult.Position; + coll->Front = GetCollisionPosition(pointColl); coll->Front.Floor = height; coll->Front.Ceiling = ceiling; @@ -507,22 +379,22 @@ void GetCollisionInfo(CollisionInfo* coll, ItemInfo* item, const Vector3i& offse } else { - height = GetCollision(probePos.x + xFront, probePos.y, probePos.z + zFront, topRoomNumber).Position.Floor; + height = GetPointCollision(Vector3i(probePos.x + xFront, probePos.y, probePos.z + zFront), topRoomNumber).GetFloorHeight(); } if (height != NO_HEIGHT) height -= (doPlayerCollision ? entityPos.y : probePos.y); - SetSectorAttribs(coll->Front, coll->Setup, collResult, probePos, realRoomNumber); + SetSectorAttribs(coll->Front, coll->Setup, pointColl, probePos, realRoomNumber); // TEST 4: MIDDLE-LEFT PROBE probePos.x = entityPos.x + xLeft; probePos.z = entityPos.z + zLeft; - g_Renderer.AddDebugSphere(probePos.ToVector3(), 32, Vector4(0, 0, 1, 1), RendererDebugPage::CollisionStats); + DrawDebugSphere(probePos.ToVector3(), 32, Vector4(0, 0, 1, 1), RendererDebugPage::CollisionStats); - collResult = GetCollision(probePos.x, probePos.y, probePos.z, item->RoomNumber); + pointColl = GetPointCollision(probePos, item->RoomNumber); if (doPlayerCollision) { @@ -534,8 +406,8 @@ void GetCollisionInfo(CollisionInfo* coll, ItemInfo* item, const Vector3i& offse } else { - height = collResult.Position.Floor; - ceiling = GetCeiling(collResult.Block, probePos.x, probePos.y - item->Animation.Velocity.y, probePos.z); + height = pointColl.GetFloorHeight(); + ceiling = GetCeiling(&pointColl.GetSector(), probePos.x, probePos.y - item->Animation.Velocity.y, probePos.z); } if (height != NO_HEIGHT) @@ -544,15 +416,16 @@ void GetCollisionInfo(CollisionInfo* coll, ItemInfo* item, const Vector3i& offse if (ceiling != NO_HEIGHT) ceiling -= probePos.y; - coll->MiddleLeft = collResult.Position; + coll->MiddleLeft = GetCollisionPosition(pointColl); coll->MiddleLeft.Floor = height; coll->MiddleLeft.Ceiling = ceiling; - SetSectorAttribs(coll->MiddleLeft, coll->Setup, collResult, probePos, realRoomNumber); + SetSectorAttribs(coll->MiddleLeft, coll->Setup, pointColl, probePos, realRoomNumber); // TEST 5: FRONT-LEFT PROBE - collResult = GetCollision(probePos.x, probePos.y, probePos.z, topRoomNumber); // Use plain X/Z values here as proposed by Choco. + // Use plain X/Z values. + pointColl = GetPointCollision(probePos, topRoomNumber); if (doPlayerCollision) { @@ -564,8 +437,8 @@ void GetCollisionInfo(CollisionInfo* coll, ItemInfo* item, const Vector3i& offse } else { - height = collResult.Position.Floor; - ceiling = GetCeiling(collResult.Block, probePos.x, probePos.y - item->Animation.Velocity.y, probePos.z); + height = pointColl.GetFloorHeight(); + ceiling = GetCeiling(&pointColl.GetSector(), probePos.x, probePos.y - item->Animation.Velocity.y, probePos.z); } if (height != NO_HEIGHT) @@ -574,20 +447,20 @@ void GetCollisionInfo(CollisionInfo* coll, ItemInfo* item, const Vector3i& offse if (ceiling != NO_HEIGHT) ceiling -= probePos.y; - coll->FrontLeft = collResult.Position; + coll->FrontLeft = GetCollisionPosition(pointColl); coll->FrontLeft.Floor = height; coll->FrontLeft.Ceiling = ceiling; - SetSectorAttribs(coll->FrontLeft, coll->Setup, collResult, probePos, realRoomNumber); + SetSectorAttribs(coll->FrontLeft, coll->Setup, pointColl, probePos, realRoomNumber); // TEST 6: MIDDLE-RIGHT PROBE probePos.x = entityPos.x + xRight; probePos.z = entityPos.z + zRight; - g_Renderer.AddDebugSphere(probePos.ToVector3(), 32, Vector4(0, 1, 0, 1), RendererDebugPage::CollisionStats); + DrawDebugSphere(probePos.ToVector3(), 32, Vector4(0, 1, 0, 1), RendererDebugPage::CollisionStats); - collResult = GetCollision(probePos.x, probePos.y, probePos.z, item->RoomNumber); + pointColl = GetPointCollision(probePos, item->RoomNumber); if (doPlayerCollision) { @@ -599,8 +472,8 @@ void GetCollisionInfo(CollisionInfo* coll, ItemInfo* item, const Vector3i& offse } else { - height = collResult.Position.Floor; - ceiling = GetCeiling(collResult.Block, probePos.x, probePos.y - item->Animation.Velocity.y, probePos.z); + height = pointColl.GetFloorHeight(); + ceiling = GetCeiling(&pointColl.GetSector(), probePos.x, probePos.y - item->Animation.Velocity.y, probePos.z); } if (height != NO_HEIGHT) @@ -609,15 +482,15 @@ void GetCollisionInfo(CollisionInfo* coll, ItemInfo* item, const Vector3i& offse if (ceiling != NO_HEIGHT) ceiling -= probePos.y; - coll->MiddleRight = collResult.Position; + coll->MiddleRight = GetCollisionPosition(pointColl); coll->MiddleRight.Floor = height; coll->MiddleRight.Ceiling = ceiling; - SetSectorAttribs(coll->MiddleRight, coll->Setup, collResult, probePos, realRoomNumber); + SetSectorAttribs(coll->MiddleRight, coll->Setup, pointColl, probePos, realRoomNumber); // TEST 7: FRONT-RIGHT PROBE - collResult = GetCollision(probePos.x, probePos.y, probePos.z, topRoomNumber); + pointColl = GetPointCollision(probePos, topRoomNumber); if (doPlayerCollision) { @@ -629,8 +502,8 @@ void GetCollisionInfo(CollisionInfo* coll, ItemInfo* item, const Vector3i& offse } else { - height = collResult.Position.Floor; - ceiling = GetCeiling(collResult.Block, probePos.x, probePos.y - item->Animation.Velocity.y, probePos.z); + height = pointColl.GetFloorHeight(); + ceiling = GetCeiling(&pointColl.GetSector(), probePos.x, probePos.y - item->Animation.Velocity.y, probePos.z); } if (height != NO_HEIGHT) @@ -639,11 +512,11 @@ void GetCollisionInfo(CollisionInfo* coll, ItemInfo* item, const Vector3i& offse if (ceiling != NO_HEIGHT) ceiling -= probePos.y; - coll->FrontRight = collResult.Position; + coll->FrontRight = GetCollisionPosition(pointColl); coll->FrontRight.Floor = height; coll->FrontRight.Ceiling = ceiling; - SetSectorAttribs(coll->FrontRight, coll->Setup, collResult, probePos, realRoomNumber); + SetSectorAttribs(coll->FrontRight, coll->Setup, pointColl, probePos, realRoomNumber); // TEST 8: SOLID STATIC MESHES @@ -818,16 +691,21 @@ void GetCollisionInfo(CollisionInfo* coll, ItemInfo* item, const Vector3i& offse } } +void GetCollisionInfo(CollisionInfo* coll, ItemInfo* item, bool resetRoom) +{ + GetCollisionInfo(coll, item, Vector3i::Zero, resetRoom); +} + void AlignEntityToSurface(ItemInfo* item, const Vector2& ellipse, float alpha, short constraintAngle) { // Reduce ellipse axis lengths for stability. auto reducedEllipse = ellipse * 0.75f; // Probe heights at points around entity. - int frontHeight = GetCollision(item, item->Pose.Orientation.y, reducedEllipse.y).Position.Floor; - int backHeight = GetCollision(item, item->Pose.Orientation.y + ANGLE(180.0f), reducedEllipse.y).Position.Floor; - int leftHeight = GetCollision(item, item->Pose.Orientation.y - ANGLE(90.0f), reducedEllipse.x).Position.Floor; - int rightHeight = GetCollision(item, item->Pose.Orientation.y + ANGLE(90.0f), reducedEllipse.x).Position.Floor; + int frontHeight = GetPointCollision(*item, item->Pose.Orientation.y, reducedEllipse.y).GetFloorHeight(); + int backHeight = GetPointCollision(*item, item->Pose.Orientation.y + ANGLE(180.0f), reducedEllipse.y).GetFloorHeight(); + int leftHeight = GetPointCollision(*item, item->Pose.Orientation.y - ANGLE(90.0f), reducedEllipse.x).GetFloorHeight(); + int rightHeight = GetPointCollision(*item, item->Pose.Orientation.y + ANGLE(90.0f), reducedEllipse.x).GetFloorHeight(); // Calculate height deltas. int forwardHeightDelta = backHeight - frontHeight; @@ -927,7 +805,7 @@ short GetNearestLedgeAngle(ItemInfo* item, CollisionInfo* coll, float& distance) } // Debug probe point - // g_Renderer.AddDebugSphere(Vector3(eX, y, eZ), 16, Vector4(1, 1, 0, 1), RendererDebugPage::CollisionStats); + // DrawDebugSphere(Vector3(eX, y, eZ), 16, Vector4(1, 1, 0, 1), RendererDebugPage::CollisionStats); // Determine front floor probe offset. // It is needed to identify if there is bridge or ceiling split in front. @@ -943,11 +821,11 @@ short GetNearestLedgeAngle(ItemInfo* item, CollisionInfo* coll, float& distance) // Get front floor block auto room = GetRoomVector(item->Location, Vector3i(ffpX, y, ffpZ)).RoomNumber; - auto block = GetCollision(ffpX, y, ffpZ, room).Block; + auto sector = &GetPointCollision(Vector3i(ffpX, y, ffpZ), room).GetSector(); // Get front floor surface heights - auto floorHeight = GetSurfaceHeight(RoomVector(block->RoomNumber, y), ffpX, ffpZ, true).value_or(NO_HEIGHT); - auto ceilingHeight = GetSurfaceHeight(RoomVector(block->RoomNumber, y), ffpX, ffpZ, false).value_or(NO_HEIGHT); + auto floorHeight = GetSurfaceHeight(RoomVector(sector->RoomNumber, y), ffpX, ffpZ, true).value_or(NO_HEIGHT); + auto ceilingHeight = GetSurfaceHeight(RoomVector(sector->RoomNumber, y), ffpX, ffpZ, false).value_or(NO_HEIGHT); // If probe landed inside wall (i.e. both floor/ceiling heights are NO_HEIGHT), make a fake // ledge for algorithm to further succeed. @@ -960,7 +838,7 @@ short GetNearestLedgeAngle(ItemInfo* item, CollisionInfo* coll, float& distance) int height = useCeilingLedge ? ceilingHeight : floorHeight; // Determine if there is a bridge in front. - auto bridge = block->GetInsideBridgeItemNumber(Vector3i(ffpX, height, ffpZ), true, y == height); + auto bridge = sector->GetInsideBridgeItemNumber(Vector3i(ffpX, height, ffpZ), true, y == height); // Determine floor probe offset. // This must be slightly in front of own coll radius so no bridge misfires occur. @@ -969,11 +847,11 @@ short GetNearestLedgeAngle(ItemInfo* item, CollisionInfo* coll, float& distance) auto fpZ = eZ + floorProbeOffset * cosForwardAngle; // Debug probe point. - // g_Renderer.AddDebugSphere(Vector3(fpX, y, fpZ), 16, Vector4(0, 1, 0, 1), RendererDebugPage::CollisionStats); + // DrawDebugSphere(Vector3(fpX, y, fpZ), 16, Vector4(0, 1, 0, 1), RendererDebugPage::CollisionStats); // Get true room number and block, based on derived height room = GetRoomVector(item->Location, Vector3i(fpX, height, fpZ)).RoomNumber; - block = GetCollision(fpX, height, fpZ, room).Block; + sector = &GetPointCollision(Vector3i(fpX, height, fpZ), room).GetSector(); // We don't need actual corner heights to build planes, so just use normalized value here. auto fY = height - 1; @@ -985,7 +863,7 @@ short GetNearestLedgeAngle(ItemInfo* item, CollisionInfo* coll, float& distance) auto ray = Ray(Vector3(eX, cY, eZ), direction); // Debug ray direction. - // g_Renderer.AddDebugLine(Vector3(eX, y, eZ), Vector3(eX, y, eZ) + direction * 256, Vector4(1, 0, 0, 1)); + // DrawDebugLine(Vector3(eX, y, eZ), Vector3(eX, y, eZ) + direction * 256, Vector4(1, 0, 0, 1)); // Keep origin ray to calculate true centerpoint distance to ledge later. if (p == 0) @@ -1031,7 +909,7 @@ short GetNearestLedgeAngle(ItemInfo* item, CollisionInfo* coll, float& distance) else { // Determine if we should use floor or ceiling split angle based on early tests. - auto splitAngle = (useCeilingLedge ? TO_RAD(block->CeilingSurface.SplitAngle) : TO_RAD(block->FloorSurface.SplitAngle)); + auto splitAngle = (useCeilingLedge ? TO_RAD(sector->CeilingSurface.SplitAngle) : TO_RAD(sector->FloorSurface.SplitAngle)); // Get horizontal block corner coordinates. auto fX = floor(eX / BLOCK(1)) * BLOCK(1) - 1; @@ -1040,7 +918,7 @@ short GetNearestLedgeAngle(ItemInfo* item, CollisionInfo* coll, float& distance) auto cZ = fZ + BLOCK(1) + 1; // Debug used block - // g_Renderer.AddDebugSphere(Vector3(round(eX / BLOCK(1)) * BLOCK(1) + BLOCK(0.5f), y, round(eZ / BLOCK(1)) * BLOCK(1) + BLOCK(0.5f)), 16, Vector4::One, RendererDebugPage::CollisionStats); + // DrawDebugSphere(Vector3(round(eX / BLOCK(1)) * BLOCK(1) + BLOCK(0.5f), y, round(eZ / BLOCK(1)) * BLOCK(1) + BLOCK(0.5f)), 16, Vector4::One, RendererDebugPage::CollisionStats); // Get split angle coordinates. auto sX = fX + 1 + BLOCK(0.5f); @@ -1059,7 +937,7 @@ short GetNearestLedgeAngle(ItemInfo* item, CollisionInfo* coll, float& distance) }; // If split angle exists, take split plane into account too. - auto useSplitAngle = (useCeilingLedge ? block->IsSurfaceSplit(false) : block->IsSurfaceSplit(true)); + auto useSplitAngle = (useCeilingLedge ? sector->IsSurfaceSplit(false) : sector->IsSurfaceSplit(true)); // Find closest block edge plane. for (int i = 0; i < (useSplitAngle ? 5 : 4); i++) @@ -1086,7 +964,7 @@ short GetNearestLedgeAngle(ItemInfo* item, CollisionInfo* coll, float& distance) if (i == 4) { - auto usedSectorPlane = useCeilingLedge ? block->GetSurfaceTriangleID(eX, eZ, false) : block->GetSurfaceTriangleID(eX, eZ, true); + auto usedSectorPlane = useCeilingLedge ? sector->GetSurfaceTriangleID(eX, eZ, false) : sector->GetSurfaceTriangleID(eX, eZ, true); result[p] = FROM_RAD(splitAngle) + ANGLE(usedSectorPlane * 180.0f) + ANGLE(90.0f); } else @@ -1171,7 +1049,7 @@ short GetNearestLedgeAngle(ItemInfo* item, CollisionInfo* coll, float& distance) FloorInfo* GetFloor(int x, int y, int z, short* roomNumber) { - const auto location = GetRoomVector(RoomVector(*roomNumber, y), Vector3i(x, y, z)); + auto location = GetRoomVector(RoomVector(*roomNumber, y), Vector3i(x, y, z)); *roomNumber = location.RoomNumber; return &GetFloor(*roomNumber, x, z); } @@ -1188,240 +1066,29 @@ int GetCeiling(FloorInfo* floor, int x, int y, int z) int GetDistanceToFloor(int itemNumber, bool precise) { - auto* item = &g_Level.Items[itemNumber]; + const auto& item = g_Level.Items[itemNumber]; - auto pointColl = GetCollision(item); + auto pointColl = GetPointCollision(item); // HACK: Remove item from bridge objects temporarily. - pointColl.Block->RemoveBridge(itemNumber); - int height = GetFloorHeight(pointColl.Block, item->Pose.Position.x, item->Pose.Position.y, item->Pose.Position.z); - pointColl.Block->AddBridge(itemNumber); + pointColl.GetSector().RemoveBridge(itemNumber); + int height = GetFloorHeight(&pointColl.GetSector(), item.Pose.Position.x, item.Pose.Position.y, item.Pose.Position.z); + pointColl.GetSector().AddBridge(itemNumber); - auto bounds = GameBoundingBox(item); + auto bounds = GameBoundingBox(&item); int minHeight = precise ? bounds.Y2 : 0; - return (minHeight + item->Pose.Position.y - height); -} - -int GetWaterSurface(int x, int y, int z, short roomNumber) -{ - auto* room = &g_Level.Rooms[roomNumber]; - FloorInfo* floor = GetSector(room, x - room->x, z - room->z); - - if (TestEnvironment(ENV_FLAG_WATER, room)) - { - while (floor->GetNextRoomNumber(Vector3i(x, y, z), false).has_value()) - { - room = &g_Level.Rooms[floor->GetNextRoomNumber(Vector3i(x, y, z), false).value_or(floor->RoomNumber)]; - if (!TestEnvironment(ENV_FLAG_WATER, room)) - return (floor->GetSurfaceHeight(x, z, false)); - - floor = GetSector(room, x - room->x, z - room->z); - } - - return NO_HEIGHT; - } - else - { - while (floor->GetNextRoomNumber(Vector3i(x, y, z), true).has_value()) - { - room = &g_Level.Rooms[floor->GetNextRoomNumber(Vector3i(x, y, z), true).value_or(floor->RoomNumber)]; - if (TestEnvironment(ENV_FLAG_WATER, room)) - return (floor->GetSurfaceHeight(x, z, true)); - - floor = GetSector(room, x - room->x, z - room->z); - } - } - - return NO_HEIGHT; -} - -int GetWaterSurface(ItemInfo* item) -{ - return GetWaterSurface(item->Pose.Position.x, item->Pose.Position.y, item->Pose.Position.z, item->RoomNumber); -} - -int GetWaterDepth(int x, int y, int z, short roomNumber) -{ - FloorInfo* floor; - auto* room = &g_Level.Rooms[roomNumber]; - - short roomIndex = NO_VALUE; - do - { - int zFloor = (z - room->z) / BLOCK(1); - int xFloor = (x - room->x) / BLOCK(1); - - if (zFloor <= 0) - { - zFloor = 0; - if (xFloor < 1) - xFloor = 1; - else if (xFloor > room->xSize - 2) - xFloor = room->xSize - 2; - } - else if (zFloor >= room->zSize - 1) - { - zFloor = room->zSize - 1; - if (xFloor < 1) - xFloor = 1; - else if (xFloor > room->xSize - 2) - xFloor = room->xSize - 2; - } - else if (xFloor < 0) - xFloor = 0; - else if (xFloor >= room->xSize) - xFloor = room->xSize - 1; - - floor = &room->floor[zFloor + xFloor * room->zSize]; - roomIndex = floor->SidePortalRoomNumber; - if (roomIndex != NO_VALUE) - { - roomNumber = roomIndex; - room = &g_Level.Rooms[roomIndex]; - } - } - while (roomIndex != NO_VALUE); - - if (TestEnvironment(ENV_FLAG_WATER, room) || - TestEnvironment(ENV_FLAG_SWAMP, room)) - { - while (floor->GetNextRoomNumber(Vector3i(x, y, z), false).has_value()) - { - room = &g_Level.Rooms[floor->GetNextRoomNumber(Vector3i(x, y, z), false).value_or(floor->RoomNumber)]; - - if (!TestEnvironment(ENV_FLAG_WATER, room) && - !TestEnvironment(ENV_FLAG_SWAMP, room)) - { - int waterHeight = floor->GetSurfaceHeight(x, z, false); - int floorHeight = GetCollision(floor, x, y, z).BottomBlock->GetSurfaceHeight(x, z, true); - return (floorHeight - waterHeight); - } - - floor = GetSector(room, x - room->x, z - room->z); - } - - return DEEP_WATER; - } - else - { - while (floor->GetNextRoomNumber(Vector3i(x, y, z), true).has_value()) - { - room = &g_Level.Rooms[floor->GetNextRoomNumber(Vector3i(x, y, z), true).value_or(floor->RoomNumber)]; - - if (TestEnvironment(ENV_FLAG_WATER, room) || - TestEnvironment(ENV_FLAG_SWAMP, room)) - { - int waterHeight = floor->GetSurfaceHeight(x, z, true); - floor = GetFloor(x, y, z, &roomNumber); - return (GetFloorHeight(floor, x, y, z) - waterHeight); - } - - floor = GetSector(room, x - room->x, z - room->z); - } - - return NO_HEIGHT; - } -} - -int GetWaterDepth(ItemInfo* item) -{ - return GetWaterDepth(item->Pose.Position.x, item->Pose.Position.y, item->Pose.Position.z, item->RoomNumber); -} - -int GetWaterHeight(int x, int y, int z, short roomNumber) -{ - auto* room = &g_Level.Rooms[roomNumber]; - FloorInfo* floor; - - short adjoiningRoom = NO_VALUE; - do - { - int xBlock = (x - room->x) / BLOCK(1); - int zBlock = (z - room->z) / BLOCK(1); - - if (zBlock <= 0) - { - zBlock = 0; - if (xBlock < 1) - xBlock = 1; - else if (xBlock > room->xSize - 2) - xBlock = room->xSize - 2; - } - else if (zBlock >= room->zSize - 1) - { - zBlock = room->zSize - 1; - if (xBlock < 1) - xBlock = 1; - else if (xBlock > room->xSize - 2) - xBlock = room->xSize - 2; - } - else if (xBlock < 0) - xBlock = 0; - else if (xBlock >= room->xSize) - xBlock = room->xSize - 1; - - floor = &room->floor[zBlock + xBlock * room->zSize]; - adjoiningRoom = floor->SidePortalRoomNumber; - - if (adjoiningRoom != NO_VALUE) - { - roomNumber = adjoiningRoom; - room = &g_Level.Rooms[adjoiningRoom]; - } - } while (adjoiningRoom != NO_VALUE); - - if (floor->IsWall(x, z)) - return NO_HEIGHT; - - if (TestEnvironment(ENV_FLAG_WATER, room) || - TestEnvironment(ENV_FLAG_SWAMP, room)) - { - while (floor->GetNextRoomNumber(Vector3i(x, y, z), false).has_value()) - { - auto* room = &g_Level.Rooms[floor->GetNextRoomNumber(Vector3i(x, y, z), false).value_or(floor->RoomNumber)]; - - if (!TestEnvironment(ENV_FLAG_WATER, room) && - !TestEnvironment(ENV_FLAG_SWAMP, room)) - break; - - floor = GetSector(room, x - room->x, z - room->z); - } - - return GetCollision(floor, x, y, z).Block->GetSurfaceHeight(Vector3i(x, y, z), false); - } - else if (floor->GetNextRoomNumber(Vector3i(x, y, z), true).has_value()) - { - while (floor->GetNextRoomNumber(Vector3i(x, y, z), true).has_value()) - { - auto* room2 = &g_Level.Rooms[floor->GetNextRoomNumber(Vector3i(x, y, z), true).value_or(floor->RoomNumber)]; - - if (TestEnvironment(ENV_FLAG_WATER, room2) || - TestEnvironment(ENV_FLAG_SWAMP, room2)) - break; - - floor = GetSector(room2, x - room2->x, z - room2->z); - } - - return GetCollision(floor, x, y, z).Block->GetSurfaceHeight(Vector3i(x, y, z), true); - } - - return NO_HEIGHT; -} - -int GetWaterHeight(ItemInfo* item) -{ - return GetWaterHeight(item->Pose.Position.x, item->Pose.Position.y, item->Pose.Position.z, item->RoomNumber); + return (minHeight + item.Pose.Position.y - height); } bool TestEnvironment(RoomEnvFlags environmentType, int x, int y, int z, int roomNumber) { - return TestEnvironment(environmentType, GetCollision(x, y, z, roomNumber).RoomNumber); + return TestEnvironment(environmentType, GetPointCollision(Vector3i(x, y, z), roomNumber).GetRoomNumber()); } -bool TestEnvironment(RoomEnvFlags environmentType, Vector3i pos, int roomNumber) +bool TestEnvironment(RoomEnvFlags environmentType, const Vector3i& pos, int roomNumber) { - return TestEnvironment(environmentType, GetCollision(pos.x, pos.y, pos.z, roomNumber).RoomNumber); + return TestEnvironment(environmentType, GetPointCollision(pos, roomNumber).GetRoomNumber()); } bool TestEnvironment(RoomEnvFlags environmentType, const ItemInfo* item) @@ -1434,7 +1101,7 @@ bool TestEnvironment(RoomEnvFlags environmentType, int roomNumber) return TestEnvironment(environmentType, &g_Level.Rooms[roomNumber]); } -bool TestEnvironment(RoomEnvFlags environmentType, ROOM_INFO* room) +bool TestEnvironment(RoomEnvFlags environmentType, const ROOM_INFO* room) { return TestEnvironmentFlags(environmentType, room->flags); } diff --git a/TombEngine/Game/collision/collide_room.h b/TombEngine/Game/collision/collide_room.h index 46023b3e9..16ae61831 100644 --- a/TombEngine/Game/collision/collide_room.h +++ b/TombEngine/Game/collision/collide_room.h @@ -1,18 +1,24 @@ #pragma once - +#include "Game/collision/floordata.h" #include "Math/Math.h" #include "Objects/game_object_ids.h" -struct ItemInfo; -class FloorInfo; -struct ROOM_INFO; -struct MESH_INFO; enum RoomEnvFlags; +class FloorInfo; +struct ItemInfo; +struct MESH_INFO; +struct ROOM_INFO; + +using namespace TEN::Collision::Floordata; +using namespace TEN::Math; constexpr auto NO_LOWER_BOUND = -NO_HEIGHT; // Used by coll->Setup.LowerFloorBound. constexpr auto NO_UPPER_BOUND = NO_HEIGHT; // Used by coll->Setup.UpperFloorBound. constexpr auto COLLISION_CHECK_DISTANCE = BLOCK(8); +constexpr auto DEFAULT_STEEP_FLOOR_SLOPE_ANGLE = ANGLE(36.0f); +constexpr auto DEFAULT_STEEP_CEILING_SLOPE_ANGLE = ANGLE(45.0f); + enum class CollisionType { None, @@ -38,58 +44,47 @@ enum class CornerType Outer }; -struct CollisionPosition +struct CollisionPositionData { - int Floor; - int Ceiling; - int Bridge; - float SplitAngle; - bool FloorSlope; - bool CeilingSlope; - bool DiagonalStep; + int Floor = 0; + int Ceiling = 0; + int Bridge = 0; + short SplitAngle = 0; + bool FloorSlope = false; + bool CeilingSlope = false; + bool DiagonalStep = false; - bool HasDiagonalSplit() { return ((SplitAngle == (45.0f * RADIAN)) || (SplitAngle == (135.0f * RADIAN))); } - bool HasFlippedDiagonalSplit() { return (HasDiagonalSplit() && (SplitAngle != (45.0f * RADIAN))); } + bool HasDiagonalSplit() { return ((SplitAngle == SectorSurfaceData::SPLIT_ANGLE_0) || (SplitAngle == SectorSurfaceData::SPLIT_ANGLE_1)); } + bool HasFlippedDiagonalSplit() { return (HasDiagonalSplit() && (SplitAngle != SectorSurfaceData::SPLIT_ANGLE_0)); } }; -struct CollisionResult +struct CollisionSetupData { - Vector3i Coordinates; - int RoomNumber; + // Collider parameters + CollisionProbeMode Mode = CollisionProbeMode::Quadrants; + int Radius = 0; + int Height = 0; + short ForwardAngle = 0; - FloorInfo* Block; - FloorInfo* BottomBlock; + // Borderline step heights + int LowerFloorBound = 0; + int UpperFloorBound = 0; + int LowerCeilingBound = 0; + int UpperCeilingBound = 0; - CollisionPosition Position; - Vector2 FloorTilt; // x = x, y = z - Vector2 CeilingTilt; // x = x, y = z - - Vector3 FloorNormal; - Vector3 CeilingNormal; -}; - -struct CollisionSetup -{ - CollisionProbeMode Mode; // Probe rotation mode - int Radius; // Collision bounds horizontal size - int Height; // Collision bounds vertical size - short ForwardAngle; // Forward angle direction - - int LowerFloorBound; // Borderline floor step-up height - int UpperFloorBound; // Borderline floor step-down height - int LowerCeilingBound; // Borderline ceiling step-up height - int UpperCeilingBound; // Borderline ceiling step-down height - - bool BlockFloorSlopeUp; // Treat steep slopes as walls - bool BlockFloorSlopeDown; // Treat steep slopes as pits - bool BlockCeilingSlope; // Treat steep slopes on ceilings as walls - bool BlockDeathFloorDown; // Treat death sectors as pits - bool BlockMonkeySwingEdge; // Treat non-monkey sectors as walls + // Blocker flags + bool BlockFloorSlopeUp = false; + bool BlockFloorSlopeDown = false; + bool BlockCeilingSlope = false; + bool BlockDeathFloorDown = false; + bool BlockMonkeySwingEdge = false; - bool EnableObjectPush; // Can be pushed by objects - bool EnableSpasm; // Convulse when pushed + // Inquirers + bool EnableObjectPush = false; + bool EnableSpasm = false; + bool ForceSolidStatics = false; - // Preserve previous parameters to restore later. + // Previous parameters Vector3i PrevPosition = Vector3i::Zero; GAME_OBJECT_ID PrevAnimObjectID = ID_NO_OBJECT; int PrevAnimNumber = 0; @@ -99,51 +94,42 @@ struct CollisionSetup struct CollisionInfo { - CollisionSetup Setup; // In parameters + CollisionSetupData Setup = {}; - CollisionPosition Middle; - CollisionPosition MiddleLeft; - CollisionPosition MiddleRight; - CollisionPosition Front; - CollisionPosition FrontLeft; - CollisionPosition FrontRight; + CollisionPositionData Middle = {}; + CollisionPositionData MiddleLeft = {}; + CollisionPositionData MiddleRight = {}; + CollisionPositionData Front = {}; + CollisionPositionData FrontLeft = {}; + CollisionPositionData FrontRight = {}; - Pose Shift = Pose::Zero; - CollisionType CollisionType; - Vector3 FloorNormal; - Vector3 CeilingNormal; - Vector2 FloorTilt; // x = x, y = z - Vector2 CeilingTilt; // x = x, y = z - short NearestLedgeAngle; - float NearestLedgeDistance; + CollisionType CollisionType = CollisionType::None; + Pose Shift = Pose::Zero; - int LastBridgeItemNumber; - Pose LastBridgeItemPose; + Vector3 FloorNormal = Vector3::Zero; + Vector3 CeilingNormal = Vector3::Zero; + Vector2 FloorTilt = Vector2::Zero; // NOTE: Deprecated. + Vector2 CeilingTilt = Vector2::Zero; // NOTE: Deprecated. + short NearestLedgeAngle = 0; + float NearestLedgeDistance = 0.0f; - bool HitStatic; - bool HitTallObject; + int LastBridgeItemNumber = 0; + Pose LastBridgeItemPose = Pose::Zero; - bool TriangleAtRight() { return ((MiddleRight.SplitAngle != 0.0f) && (MiddleRight.SplitAngle == Middle.SplitAngle)); } - bool TriangleAtLeft() { return ((MiddleLeft.SplitAngle != 0.0f) && (MiddleLeft.SplitAngle == Middle.SplitAngle)); } + bool HitStatic = false; + bool HitTallObject = false; + + // Inquirers. + bool TriangleAtRight() { return ((MiddleRight.SplitAngle != 0) && (MiddleRight.SplitAngle == Middle.SplitAngle)); } + bool TriangleAtLeft() { return ((MiddleLeft.SplitAngle != 0) && (MiddleLeft.SplitAngle == Middle.SplitAngle)); } bool DiagonalStepAtRight() { return (MiddleRight.DiagonalStep && TriangleAtRight() && (NearestLedgeAngle % ANGLE(90.0f))); } bool DiagonalStepAtLeft() { return (MiddleLeft.DiagonalStep && TriangleAtLeft() && (NearestLedgeAngle % ANGLE(90.0f))); } }; [[nodiscard]] bool TestItemRoomCollisionAABB(ItemInfo* item); -CollisionResult GetCollision(const ItemInfo& item); -CollisionResult GetCollision(const ItemInfo* item); -CollisionResult GetCollision(const ItemInfo* item, short headingAngle, float forward, float down = 0.0f, float right = 0.0f); -CollisionResult GetCollision(const Vector3i& pos, int roomNumber, short headingAngle, float forward, float down = 0.0f, float right = 0.0f); -CollisionResult GetCollision(const Vector3i& pos, int roomNumber, const EulerAngles& orient, float dist); -CollisionResult GetCollision(const Vector3i& pos, int roomNumber, const Vector3& dir, float dist); -CollisionResult GetCollision(const Vector3i& pos, int roomNumber); -CollisionResult GetCollision(int x, int y, int z, short roomNumber); -CollisionResult GetCollision(const GameVector& pos); -CollisionResult GetCollision(FloorInfo* floor, int x, int y, int z); - -void GetCollisionInfo(CollisionInfo* coll, ItemInfo* item, const Vector3i& offset, bool resetRoom = false); void GetCollisionInfo(CollisionInfo* coll, ItemInfo* item, bool resetRoom = false); +void GetCollisionInfo(CollisionInfo* coll, ItemInfo* item, const Vector3i& offset, bool resetRoom = false); int GetQuadrant(short angle); short GetNearestLedgeAngle(ItemInfo* item, CollisionInfo* coll, float& distance); @@ -152,13 +138,6 @@ int GetFloorHeight(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 GetWaterSurface(int x, int y, int z, short roomNumber); -int GetWaterSurface(ItemInfo* item); -int GetWaterDepth(int x, int y, int z, short roomNumber); -int GetWaterDepth(ItemInfo* item); -int GetWaterHeight(int x, int y, int z, short roomNumber); -int GetWaterHeight(ItemInfo* item); - int FindGridShift(int x, int z); void ShiftItem(ItemInfo* item, CollisionInfo* coll); void SnapItemToLedge(ItemInfo* item, CollisionInfo* coll, float offsetMultiplier = 0.0f, bool snapToAngle = true); @@ -167,10 +146,10 @@ void SnapItemToGrid(ItemInfo* item, CollisionInfo* coll); void AlignEntityToSurface(ItemInfo* item, const Vector2& ellipse, float alpha = 0.75f, short constraintAngle = ANGLE(70.0f)); +// TODO: Deprecated. bool TestEnvironment(RoomEnvFlags environmentType, int x, int y, int z, int roomNumber); -bool TestEnvironment(RoomEnvFlags environmentType, Vector3i pos, int roomNumber); +bool TestEnvironment(RoomEnvFlags environmentType, const Vector3i& pos, int roomNumber); bool TestEnvironment(RoomEnvFlags environmentType, const ItemInfo* item); bool TestEnvironment(RoomEnvFlags environmentType, int roomNumber); -bool TestEnvironment(RoomEnvFlags environmentType, ROOM_INFO* room); +bool TestEnvironment(RoomEnvFlags environmentType, const ROOM_INFO* room); bool TestEnvironmentFlags(RoomEnvFlags environmentType, int flags); - diff --git a/TombEngine/Game/collision/floordata.cpp b/TombEngine/Game/collision/floordata.cpp index 05915f846..e59d89ae3 100644 --- a/TombEngine/Game/collision/floordata.cpp +++ b/TombEngine/Game/collision/floordata.cpp @@ -1,6 +1,8 @@ #include "framework.h" #include "Game/collision/floordata.h" + #include "Game/collision/collide_room.h" +#include "Game/collision/Point.h" #include "Game/items.h" #include "Game/room.h" #include "Game/Setup.h" @@ -11,6 +13,7 @@ #include "Specific/trutils.h" using namespace TEN::Collision::Floordata; +using namespace TEN::Collision::Point; using namespace TEN::Entities::Generic; using namespace TEN::Math; using namespace TEN::Utils; @@ -59,7 +62,7 @@ Vector3 FloorInfo::GetSurfaceNormal(int x, int z, bool isFloor) const short FloorInfo::GetSurfaceIllegalSlopeAngle(int x, int z, bool isFloor) const { const auto& tri = GetSurfaceTriangle(x, z, isFloor); - return tri.IllegalSlopeAngle; + return tri.SteepSlopeAngle; } MaterialType FloorInfo::GetSurfaceMaterial(int x, int z, bool isFloor) const @@ -179,6 +182,10 @@ int FloorInfo::GetSurfaceHeight(int x, int z, bool isFloor) const auto normal = tri.Plane.Normal(); float relPlaneHeight = -((normal.x * sectorPoint.x) + (normal.z * sectorPoint.y)) / normal.y; + // Due to precision loss, we can't recover NO_HEIGHT constant from the plane, and must return original integer constant. + if (tri.Plane.D() == (float)NO_HEIGHT) + return NO_HEIGHT; + // Return sector floor or ceiling height. NOTE: Bridges ignored. return (tri.Plane.D() + relPlaneHeight); } @@ -195,18 +202,27 @@ int FloorInfo::GetSurfaceHeight(const Vector3i& pos, bool isFloor) const const auto& bridgeItem = g_Level.Items[itemNumber]; const auto& bridge = GetBridgeObject(bridgeItem); + auto bridgeFloorHeight = bridge.GetFloorHeight(bridgeItem, pos); + auto bridgeCeilingHeight = bridge.GetCeilingHeight(bridgeItem, pos); + // 2.1) Get bridge surface height. auto bridgeSurfaceHeight = isFloor ? bridge.GetFloorHeight(bridgeItem, pos) : bridge.GetCeilingHeight(bridgeItem, pos); + if (!bridgeSurfaceHeight.has_value()) continue; + // Use bridge midpoint to decide whether to return bridge height or room height, in case probe point + // is located within the bridge. Without it, dynamic bridges may fail while Lara is standing on them. + int thickness = bridge.GetCeilingBorder(bridgeItem) - bridge.GetFloorBorder(bridgeItem); + int midpoint = bridgeItem.Pose.Position.y + thickness / 2; + // 2.2) Track closest floor or ceiling height. if (isFloor) { // Test if bridge floor height is closer. - if (*bridgeSurfaceHeight >= pos.y && // Bridge floor height is below position. - *bridgeSurfaceHeight < floorHeight && // Bridge floor height is above current closest floor height. - *bridgeSurfaceHeight >= ceilingHeight) // Bridge ceiling height is below sector ceiling height. + if (midpoint >= pos.y && // Bridge midpoint is below position. + *bridgeSurfaceHeight < floorHeight && // Bridge floor height is above current closest floor height. + *bridgeSurfaceHeight >= ceilingHeight) // Bridge ceiling height is below sector ceiling height. { floorHeight = *bridgeSurfaceHeight; } @@ -214,7 +230,7 @@ int FloorInfo::GetSurfaceHeight(const Vector3i& pos, bool isFloor) const else { // Test if bridge ceiling height is closer. - if (*bridgeSurfaceHeight <= pos.y && // Bridge ceiling height is above position. + if (midpoint <= pos.y && // Bridge midpoint is above position. *bridgeSurfaceHeight > ceilingHeight && // Bridge ceiling height is below current closest ceiling height. *bridgeSurfaceHeight <= floorHeight) // Bridge floor height is above sector floor height. { @@ -365,17 +381,17 @@ namespace TEN::Collision::Floordata const auto& room = g_Level.Rooms[roomNumber]; // Calculate room grid coord. - auto roomGridCoord = Vector2i((x - room.x) / BLOCK(1), (z - room.z) / BLOCK(1)); - if (x < room.x) + auto roomGridCoord = Vector2i((x - room.Position.x) / BLOCK(1), (z - room.Position.z) / BLOCK(1)); + if (x < room.Position.x) roomGridCoord.x -= 1; - if (z < room.z) + if (z < room.Position.z) roomGridCoord.y -= 1; // Clamp room grid coord to room bounds (if applicable). if (clampToBounds) { - roomGridCoord.x = std::clamp(roomGridCoord.x, 0, room.xSize - 1); - roomGridCoord.y = std::clamp(roomGridCoord.y, 0, room.zSize - 1); + roomGridCoord.x = std::clamp(roomGridCoord.x, 0, room.XSize - 1); + roomGridCoord.y = std::clamp(roomGridCoord.y, 0, room.ZSize - 1); } return roomGridCoord; @@ -394,8 +410,8 @@ namespace TEN::Collision::Floordata const auto& room = g_Level.Rooms[roomNumber]; // Search area out of range; return empty vector. - if (xMax <= 0 || xMin >= (room.xSize - 1) || - xMax <= 0 || xMin >= (room.xSize - 1)) + if (xMax <= 0 || xMin >= (room.XSize - 1) || + xMax <= 0 || xMin >= (room.XSize - 1)) { return {}; } @@ -405,13 +421,13 @@ namespace TEN::Collision::Floordata for (int x = xMin; x <= xMax; x++) { // Test if out of room X range. - if (x <= 0 || x >= (room.xSize - 1)) + if (x <= 0 || x >= (room.XSize - 1)) continue; for (int z = zMin; z <= zMax; z++) { // Test if out of room Z range. - if (z <= 0 || z >= (room.zSize - 1)) + if (z <= 0 || z >= (room.ZSize - 1)) continue; roomGridCoords.push_back(Vector2i(x, z)); @@ -421,30 +437,30 @@ namespace TEN::Collision::Floordata return roomGridCoords; } - std::vector GetNeighborSectorPtrs(const Vector3i& pos, int roomNumber, unsigned int searchDepth) + std::vector GetNeighborSectors(const Vector3i& pos, int roomNumber, unsigned int searchDepth) { - auto sectorPtrs = std::vector{}; + auto sectors = std::vector{}; // Run through neighbor rooms. auto& room = g_Level.Rooms[roomNumber]; - for (int neighborRoomNumber : room.neighbors) + for (int neighborRoomNumber : room.NeighborRoomNumbers) { - // Collect neighbor sector pointers. + // Collect neighbor sectors. auto roomGridCoords = GetNeighborRoomGridCoords(pos, neighborRoomNumber, searchDepth); for (const auto& roomGridCoord : roomGridCoords) - sectorPtrs.push_back(&GetFloor(neighborRoomNumber, roomGridCoord)); + sectors.push_back(&GetFloor(neighborRoomNumber, roomGridCoord)); } - // Return neighbor sector pointers. - return sectorPtrs; + // Return neighbor sectors. + return sectors; } FloorInfo& GetFloor(int roomNumber, const Vector2i& roomGridCoord) { auto& room = g_Level.Rooms[roomNumber]; - int sectorID = (room.zSize * roomGridCoord.x) + roomGridCoord.y; - return room.floor[sectorID]; + int sectorID = (room.ZSize * roomGridCoord.x) + roomGridCoord.y; + return room.Sectors[sectorID]; } FloorInfo& GetFloor(int roomNumber, int x, int z) @@ -455,89 +471,96 @@ namespace TEN::Collision::Floordata FloorInfo& GetFarthestSector(int roomNumber, int x, int z, bool isBottom) { - auto* sectorPtr = &GetSideSector(roomNumber, x, z); + auto* sector = &GetSideSector(roomNumber, x, z); // Find bottom or top sector. - bool isWall = sectorPtr->IsWall(x, z); + bool isWall = sector->IsWall(x, z); while (isWall) { - auto nextRoomNumber = sectorPtr->GetNextRoomNumber(x, z, isBottom); + auto nextRoomNumber = sector->GetNextRoomNumber(x, z, isBottom); if (!nextRoomNumber.has_value()) break; // TODO: Check. - sectorPtr = &GetSideSector(*nextRoomNumber, x, z); - isWall = sectorPtr->IsWall(x, z); + sector = &GetSideSector(*nextRoomNumber, x, z); + isWall = sector->IsWall(x, z); } - return *sectorPtr; + return *sector; } FloorInfo& GetSideSector(int roomNumber, int x, int z) { - auto* sectorPtr = &GetFloor(roomNumber, x, z); + auto* sector = &GetFloor(roomNumber, x, z); // Find side sector. - auto sideRoomNumber = sectorPtr->GetSideRoomNumber(); + auto sideRoomNumber = sector->GetSideRoomNumber(); while (sideRoomNumber.has_value()) { - sectorPtr = &GetFloor(*sideRoomNumber, x, z); - sideRoomNumber = sectorPtr->GetSideRoomNumber(); + sector = &GetFloor(*sideRoomNumber, x, z); + sideRoomNumber = sector->GetSideRoomNumber(); } - return *sectorPtr; + return *sector; } static std::optional GetFarthestHeightData(FloorInfo& currentSector, Vector3i pos, bool isBottom) { // Find bottom or top height while bridge exists(?). - auto* sectorPtr = ¤tSector; + auto* sector = ¤tSector; do { // Set vertical position to lowest bridge ceiling height or highest bridge floor height. - pos.y = sectorPtr->GetBridgeSurfaceHeight(pos, !isBottom); + pos.y = sector->GetBridgeSurfaceHeight(pos, !isBottom); // Find sector at lowest bridge floor height or highest bridge ceiling height. while (isBottom ? - (pos.y >= sectorPtr->GetSurfaceHeight(pos.x, pos.z, true)) : - (pos.y <= sectorPtr->GetSurfaceHeight(pos.x, pos.z, false))) + (pos.y >= sector->GetSurfaceHeight(pos.x, pos.z, true)) : + (pos.y <= sector->GetSurfaceHeight(pos.x, pos.z, false))) { - auto nextRoomNumber = sectorPtr->GetNextRoomNumber(pos.x, pos.z, isBottom); + auto nextRoomNumber = sector->GetNextRoomNumber(pos.x, pos.z, isBottom); if (!nextRoomNumber.has_value()) return std::nullopt; - sectorPtr = &GetSideSector(*nextRoomNumber, pos.x, pos.z); + sector = &GetSideSector(*nextRoomNumber, pos.x, pos.z); } } - while (sectorPtr->GetInsideBridgeItemNumber(pos, isBottom, !isBottom) != NO_VALUE); + while (sector->GetInsideBridgeItemNumber(pos, isBottom, !isBottom) != NO_VALUE); - return FarthestHeightData{ *sectorPtr, pos.y }; + return FarthestHeightData{ *sector, pos.y }; } std::optional GetSurfaceHeight(const RoomVector& location, int x, int z, bool isFloor) { - auto* sectorPtr = &GetSideSector(location.RoomNumber, x, z); + enum class Polarity + { + None, + Floor, + Ceiling + }; + + auto* sector = &GetSideSector(location.RoomNumber, x, z); auto pos = Vector3i(x, location.Height, z); - int polarity = 0; + auto polarity = Polarity::None; - if (sectorPtr->IsWall(x, z)) + if (sector->IsWall(x, z)) { - sectorPtr = &GetFarthestSector(location.RoomNumber, x, z, !isFloor); + sector = &GetFarthestSector(location.RoomNumber, x, z, !isFloor); - if (!sectorPtr->IsWall(x, z)) + if (!sector->IsWall(x, z)) { - pos.y = sectorPtr->GetSurfaceHeight(x, z, isFloor); - polarity = isFloor ? -1 : 1; + pos.y = sector->GetSurfaceHeight(x, z, isFloor); + polarity = isFloor ? Polarity::Floor : Polarity::Ceiling; } else { - sectorPtr = &GetFarthestSector(location.RoomNumber, x, z, isFloor); + sector = &GetFarthestSector(location.RoomNumber, x, z, isFloor); - if (!sectorPtr->IsWall(x, z)) + if (!sector->IsWall(x, z)) { - pos.y = sectorPtr->GetSurfaceHeight(x, z, !isFloor); - polarity = isFloor ? 1 : -1; + pos.y = sector->GetSurfaceHeight(x, z, !isFloor); + polarity = isFloor ? Polarity::Ceiling : Polarity::Floor; } else { @@ -546,81 +569,81 @@ namespace TEN::Collision::Floordata } } - int floorHeight = sectorPtr->GetSurfaceHeight(pos, true); - int ceilingHeight = sectorPtr->GetSurfaceHeight(pos, false); + int floorHeight = sector->GetSurfaceHeight(pos, true); + int ceilingHeight = sector->GetSurfaceHeight(pos, false); pos.y = std::clamp(pos.y, std::min(floorHeight, ceilingHeight), std::max(floorHeight, ceilingHeight)); bool testFloorBorder = (pos.y == ceilingHeight); bool testCeilBorder = (pos.y == floorHeight); - int insideBridgeItemNumber = sectorPtr->GetInsideBridgeItemNumber(pos, testFloorBorder, testCeilBorder); + int insideBridgeItemNumber = sector->GetInsideBridgeItemNumber(pos, testFloorBorder, testCeilBorder); if (insideBridgeItemNumber != NO_VALUE) { - if (isFloor ? (polarity <= 0) : (polarity >= 0)) + if (polarity == Polarity::None || (isFloor ? (polarity == Polarity::Floor) : (polarity == Polarity::Ceiling))) { - auto heightData = GetFarthestHeightData(*sectorPtr, pos, !isFloor); + auto heightData = GetFarthestHeightData(*sector, pos, !isFloor); if (heightData.has_value()) return heightData->Height; } - if (isFloor ? (polarity >= 0) : (polarity <= 0)) + if (polarity == Polarity::None || (isFloor ? (polarity == Polarity::Ceiling) : (polarity == Polarity::Floor))) { - auto heightData = GetFarthestHeightData(*sectorPtr, pos, isFloor); + auto heightData = GetFarthestHeightData(*sector, pos, isFloor); if (!heightData.has_value()) return std::nullopt; - sectorPtr = &heightData->Sector; + sector = &heightData->Sector; pos.y = heightData->Height; } } - if (isFloor ? (polarity >= 0) : (polarity <= 0)) + if (polarity == Polarity::None || (isFloor ? (polarity == Polarity::Ceiling) : (polarity == Polarity::Floor))) { - auto nextRoomNumber = sectorPtr->GetNextRoomNumber(pos, isFloor); + auto nextRoomNumber = sector->GetNextRoomNumber(pos, isFloor); while (nextRoomNumber.has_value()) { - sectorPtr = &GetSideSector(*nextRoomNumber, x, z); - nextRoomNumber = sectorPtr->GetNextRoomNumber(pos, isFloor); + sector = &GetSideSector(*nextRoomNumber, x, z); + nextRoomNumber = sector->GetNextRoomNumber(pos, isFloor); } } - return sectorPtr->GetSurfaceHeight(pos, isFloor); + return sector->GetSurfaceHeight(pos, isFloor); } static std::optional GetFarthestRoomVector(RoomVector location, const Vector3i& pos, bool isBottom) { - auto* sectorPtr = &GetSideSector(location.RoomNumber, pos.x, pos.z); - location.RoomNumber = sectorPtr->RoomNumber; + auto* sector = &GetSideSector(location.RoomNumber, pos.x, pos.z); + location.RoomNumber = sector->RoomNumber; - if (sectorPtr->IsWall(pos.x, pos.z)) + if (sector->IsWall(pos.x, pos.z)) { - sectorPtr = &GetFarthestSector(location.RoomNumber, pos.x, pos.z, isBottom); - location.RoomNumber = sectorPtr->RoomNumber; + sector = &GetFarthestSector(location.RoomNumber, pos.x, pos.z, isBottom); + location.RoomNumber = sector->RoomNumber; - if (sectorPtr->IsWall(pos.x, pos.z)) + if (sector->IsWall(pos.x, pos.z)) return std::nullopt; - location.Height = sectorPtr->GetSurfaceHeight(pos.x, pos.z, !isBottom); + location.Height = sector->GetSurfaceHeight(pos.x, pos.z, !isBottom); } - int floorHeight = sectorPtr->GetSurfaceHeight(Vector3i(pos.x, location.Height, pos.z), true); - int ceilingHeight = sectorPtr->GetSurfaceHeight(Vector3i(pos.x, location.Height, pos.z), false); + int floorHeight = sector->GetSurfaceHeight(Vector3i(pos.x, location.Height, pos.z), true); + int ceilingHeight = sector->GetSurfaceHeight(Vector3i(pos.x, location.Height, pos.z), false); location.Height = std::clamp(location.Height, std::min(ceilingHeight, floorHeight), std::max(ceilingHeight, floorHeight)); bool testFloorBorder = (location.Height == ceilingHeight); bool testCeilBorder = (location.Height == floorHeight); - int insideBridgeItemNumber = sectorPtr->GetInsideBridgeItemNumber(Vector3i(pos.x, location.Height, pos.z), testFloorBorder, testCeilBorder); + int insideBridgeItemNumber = sector->GetInsideBridgeItemNumber(Vector3i(pos.x, location.Height, pos.z), testFloorBorder, testCeilBorder); if (insideBridgeItemNumber != NO_VALUE) { - auto heightData = GetFarthestHeightData(*sectorPtr, Vector3i(pos.x, location.Height, pos.z), isBottom); + auto heightData = GetFarthestHeightData(*sector, Vector3i(pos.x, location.Height, pos.z), isBottom); if (!heightData.has_value()) return std::nullopt; - sectorPtr = &heightData->Sector; - location.RoomNumber = sectorPtr->RoomNumber; + sector = &heightData->Sector; + location.RoomNumber = sector->RoomNumber; location.Height = heightData->Height; } @@ -630,19 +653,19 @@ namespace TEN::Collision::Floordata { if (!isFirstSector) { - sectorPtr = &GetSideSector(*nextRoomNumber, pos.x, pos.z); - location.RoomNumber = sectorPtr->RoomNumber; - location.Height = sectorPtr->GetSurfaceHeight(pos.x, pos.z, !isBottom); + sector = &GetSideSector(*nextRoomNumber, pos.x, pos.z); + location.RoomNumber = sector->RoomNumber; + location.Height = sector->GetSurfaceHeight(pos.x, pos.z, !isBottom); } isFirstSector = false; if (isBottom) { - ceilingHeight = sectorPtr->GetSurfaceHeight(Vector3i(pos.x, location.Height, pos.z), false); - if (pos.y < ceilingHeight && sectorPtr->GetNextRoomNumber(Vector3i(pos.x, location.Height, pos.z), false)) + ceilingHeight = sector->GetSurfaceHeight(Vector3i(pos.x, location.Height, pos.z), false); + if (pos.y < ceilingHeight && sector->GetNextRoomNumber(Vector3i(pos.x, location.Height, pos.z), false)) return std::nullopt; - floorHeight = sectorPtr->GetSurfaceHeight(Vector3i(pos.x, location.Height, pos.z), true); + floorHeight = sector->GetSurfaceHeight(Vector3i(pos.x, location.Height, pos.z), true); if (pos.y <= floorHeight) { location.Height = std::max(pos.y, ceilingHeight); @@ -651,11 +674,11 @@ namespace TEN::Collision::Floordata } else { - floorHeight = sectorPtr->GetSurfaceHeight(Vector3i(pos.x, location.Height, pos.z), true); - if (pos.y > floorHeight && sectorPtr->GetNextRoomNumber(Vector3i(pos.x, location.Height, pos.z), true)) + floorHeight = sector->GetSurfaceHeight(Vector3i(pos.x, location.Height, pos.z), true); + if (pos.y > floorHeight && sector->GetNextRoomNumber(Vector3i(pos.x, location.Height, pos.z), true)) return std::nullopt; - ceilingHeight = sectorPtr->GetSurfaceHeight(Vector3i(pos.x, location.Height, pos.z), false); + ceilingHeight = sector->GetSurfaceHeight(Vector3i(pos.x, location.Height, pos.z), false); if (pos.y >= ceilingHeight) { location.Height = std::min(pos.y, floorHeight); @@ -663,7 +686,7 @@ namespace TEN::Collision::Floordata } } - nextRoomNumber = sectorPtr->GetNextRoomNumber(Vector3i(pos.x, location.Height, pos.z), isBottom); + nextRoomNumber = sector->GetNextRoomNumber(Vector3i(pos.x, location.Height, pos.z), isBottom); } return std::nullopt; @@ -693,34 +716,34 @@ namespace TEN::Collision::Floordata x += bridgeItem.Pose.Position.x; z += bridgeItem.Pose.Position.z; - auto* sectorPtr = &GetSideSector(bridgeItem.RoomNumber, x, z); - sectorPtr->AddBridge(itemNumber); + auto* sector = &GetSideSector(bridgeItem.RoomNumber, x, z); + sector->AddBridge(itemNumber); if (bridge.GetFloorBorder != nullptr) { int floorBorder = bridge.GetFloorBorder(bridgeItem); - while (floorBorder <= sectorPtr->GetSurfaceHeight(x, z, false)) + while (floorBorder <= sector->GetSurfaceHeight(x, z, false)) { - auto roomNumberAbove = sectorPtr->GetNextRoomNumber(x, z, false); + auto roomNumberAbove = sector->GetNextRoomNumber(x, z, false); if (!roomNumberAbove.has_value()) break; - sectorPtr = &GetSideSector(*roomNumberAbove, x, z); - sectorPtr->AddBridge(itemNumber); + sector = &GetSideSector(*roomNumberAbove, x, z); + sector->AddBridge(itemNumber); } } if (bridge.GetCeilingBorder != nullptr) { int ceilingBorder = bridge.GetCeilingBorder(bridgeItem); - while (ceilingBorder >= sectorPtr->GetSurfaceHeight(x, z, true)) + while (ceilingBorder >= sector->GetSurfaceHeight(x, z, true)) { - auto roomNumberBelow = sectorPtr->GetNextRoomNumber(x, z, true); + auto roomNumberBelow = sector->GetNextRoomNumber(x, z, true); if (!roomNumberBelow.has_value()) break; - sectorPtr = &GetSideSector(*roomNumberBelow, x, z); - sectorPtr->AddBridge(itemNumber); + sector = &GetSideSector(*roomNumberBelow, x, z); + sector->AddBridge(itemNumber); } } } @@ -736,34 +759,34 @@ namespace TEN::Collision::Floordata x += bridgeItem.Pose.Position.x; z += bridgeItem.Pose.Position.z; - auto* sectorPtr = &GetSideSector(bridgeItem.RoomNumber, x, z); - sectorPtr->RemoveBridge(itemNumber); + auto* sector = &GetSideSector(bridgeItem.RoomNumber, x, z); + sector->RemoveBridge(itemNumber); if (bridge.GetFloorBorder != nullptr) { int floorBorder = bridge.GetFloorBorder(bridgeItem); - while (floorBorder <= sectorPtr->GetSurfaceHeight(x, z, false)) + while (floorBorder <= sector->GetSurfaceHeight(x, z, false)) { - auto roomNumberAbove = sectorPtr->GetNextRoomNumber(x, z, false); + auto roomNumberAbove = sector->GetNextRoomNumber(x, z, false); if (!roomNumberAbove.has_value()) break; - sectorPtr = &GetSideSector(*roomNumberAbove, x, z); - sectorPtr->RemoveBridge(itemNumber); + sector = &GetSideSector(*roomNumberAbove, x, z); + sector->RemoveBridge(itemNumber); } } if (bridge.GetCeilingBorder != nullptr) { int ceilingBorder = bridge.GetCeilingBorder(bridgeItem); - while (ceilingBorder >= sectorPtr->GetSurfaceHeight(x, z, true)) + while (ceilingBorder >= sector->GetSurfaceHeight(x, z, true)) { - auto roomNumberBelow = sectorPtr->GetNextRoomNumber(x, z, true); + auto roomNumberBelow = sector->GetNextRoomNumber(x, z, true); if (!roomNumberBelow.has_value()) break; - sectorPtr = &GetSideSector(*roomNumberBelow, x, z); - sectorPtr->RemoveBridge(itemNumber); + sector = &GetSideSector(*roomNumberBelow, x, z); + sector->RemoveBridge(itemNumber); } } } @@ -776,8 +799,7 @@ namespace TEN::Collision::Floordata { constexpr auto VERTICAL_MARGIN = 4; - auto bounds = GameBoundingBox(&item); - auto box = bounds.ToBoundingOrientedBox(item.Pose); + auto box = GameBoundingBox(&item).ToBoundingOrientedBox(item.Pose); auto origin = Vector3(pos.x, pos.y + (useBottomHeight ? VERTICAL_MARGIN : -VERTICAL_MARGIN), pos.z); auto dir = useBottomHeight ? -Vector3::UnitY : Vector3::UnitY; @@ -785,7 +807,7 @@ namespace TEN::Collision::Floordata // Ray intersects box; return bridge box height. float dist = 0.0f; if (box.Intersects(origin, dir, dist)) - return (item.Pose.Position.y + (useBottomHeight ? bounds.Y2 : bounds.Y1)); + return Geometry::TranslatePoint(origin, dir, dist).y; return std::nullopt; } @@ -822,18 +844,18 @@ namespace TEN::Collision::Floordata const auto& room = g_Level.Rooms[item.RoomNumber]; // Get projected AABB min and max of bridge OBB. - float xMin = floor((std::min(std::min(std::min(corners[0].x, corners[1].x), corners[4].x), corners[5].x) - room.x) / BLOCK(1)); - float zMin = floor((std::min(std::min(std::min(corners[0].z, corners[1].z), corners[4].z), corners[5].z) - room.z) / BLOCK(1)); - float xMax = ceil((std::max(std::max(std::max(corners[0].x, corners[1].x), corners[4].x), corners[5].x) - room.x) / BLOCK(1)); - float zMax = ceil((std::max(std::max(std::max(corners[0].z, corners[1].z), corners[4].z), corners[5].z) - room.z) / BLOCK(1)); + float xMin = floor((std::min(std::min(std::min(corners[0].x, corners[1].x), corners[4].x), corners[5].x) - room.Position.x) / BLOCK(1)); + float zMin = floor((std::min(std::min(std::min(corners[0].z, corners[1].z), corners[4].z), corners[5].z) - room.Position.z) / BLOCK(1)); + float xMax = ceil((std::max(std::max(std::max(corners[0].x, corners[1].x), corners[4].x), corners[5].x) - room.Position.x) / BLOCK(1)); + float zMax = ceil((std::max(std::max(std::max(corners[0].z, corners[1].z), corners[4].z), corners[5].z) - room.Position.z) / BLOCK(1)); // Run through sectors enclosed in projected bridge AABB. - for (int x = 0; x < room.xSize; x++) + for (int x = 0; x < room.XSize; x++) { - for (int z = 0; z < room.zSize; z++) + for (int z = 0; z < room.ZSize; z++) { - float pX = (room.x + BLOCK(x)) + BLOCK(0.5f); - float pZ = (room.z + BLOCK(z)) + BLOCK(0.5f); + float pX = (room.Position.x + BLOCK(x)) + BLOCK(0.5f); + float pZ = (room.Position.z + BLOCK(z)) + BLOCK(0.5f); float offX = pX - item.Pose.Position.x; float offZ = pZ - item.Pose.Position.z; @@ -880,7 +902,7 @@ namespace TEN::Collision::Floordata if (labelPos2D.has_value()) { *labelPos2D += Vector2(0.0f, verticalOffset); - g_Renderer.AddDebugString(string, *labelPos2D, color, LABEL_SCALE, 0, RendererDebugPage::CollisionStats); + DrawDebugString(string, *labelPos2D, color, LABEL_SCALE, RendererDebugPage::CollisionStats); } } @@ -897,12 +919,12 @@ namespace TEN::Collision::Floordata constexpr auto MINECART_STOP_COLOR = Vector4(0.4f, 1.0f, 1.0f, 1.0f); // Get point collision. - auto pointColl = GetCollision(item); + auto pointColl = GetPointCollision(item); auto pos = item.Pose.Position.ToVector3(); // Run through neighboring rooms. const auto& room = g_Level.Rooms[item.RoomNumber]; - for (int neighborRoomNumber : room.neighbors) + for (int neighborRoomNumber : room.NeighborRoomNumbers) { const auto& neighborRoom = g_Level.Rooms[neighborRoomNumber]; @@ -910,53 +932,53 @@ namespace TEN::Collision::Floordata auto roomGridCoords = GetNeighborRoomGridCoords(item.Pose.Position, neighborRoomNumber, SECTOR_SEARCH_DEPTH); for (const auto& roomGridCoord : roomGridCoords) { - pos.x = BLOCK(roomGridCoord.x) + neighborRoom.x; - pos.z = BLOCK(roomGridCoord.y) + neighborRoom.z; + pos.x = BLOCK(roomGridCoord.x) + neighborRoom.Position.x; + pos.z = BLOCK(roomGridCoord.y) + neighborRoom.Position.z; - pointColl = GetCollision(pos, neighborRoomNumber); - pos.y = pointColl.Position.Floor; + pointColl = GetPointCollision(pos, neighborRoomNumber); + pos.y = pointColl.GetFloorHeight(); float verticalOffset = STRING_SPACING; // Stopper - if (pointColl.Block->Stopper) + if (pointColl.GetSector().Stopper) { DrawSectorFlagLabel(pos, "Stopper", STOPPER_COLOR, verticalOffset); verticalOffset += STRING_SPACING; } // Death - if (pointColl.Block->Flags.Death) + if (pointColl.GetSector().Flags.Death) { DrawSectorFlagLabel(pos, "Death", DEATH_COLOR, verticalOffset); verticalOffset += STRING_SPACING; } // Monkey Swing - if (pointColl.Block->Flags.Monkeyswing) + if (pointColl.GetSector().Flags.Monkeyswing) { DrawSectorFlagLabel(pos, "Monkey Swing", MONKEY_SWING_COLOR, verticalOffset); verticalOffset += STRING_SPACING; } // Beetle / Minecart Right - if (pointColl.Block->Flags.MarkBeetle) + if (pointColl.GetSector().Flags.MarkBeetle) { - auto labelString = std::string("Beetle") + (!pointColl.Block->Flags.MinecartStop() ? " / Minecart Right" : ""); + auto labelString = std::string("Beetle") + (!pointColl.GetSector().Flags.MinecartStop() ? " / Minecart Right" : ""); DrawSectorFlagLabel(pos, labelString, BEETLE_MINECART_RIGHT_COLOR, verticalOffset); verticalOffset += STRING_SPACING; } // Activator / Minecart Left - if (pointColl.Block->Flags.MarkTriggerer) + if (pointColl.GetSector().Flags.MarkTriggerer) { - auto labelString = std::string("Activator") + (!pointColl.Block->Flags.MinecartStop() ? " / Minecart Left" : ""); + auto labelString = std::string("Activator") + (!pointColl.GetSector().Flags.MinecartStop() ? " / Minecart Left" : ""); DrawSectorFlagLabel(pos, labelString, ACTIVATOR_MINECART_LEFT_COLOR, verticalOffset); verticalOffset += STRING_SPACING; } // Minecart Stop - if (pointColl.Block->Flags.MinecartStop()) + if (pointColl.GetSector().Flags.MinecartStop()) { DrawSectorFlagLabel(pos, "Minecart Stop", MINECART_STOP_COLOR, verticalOffset); verticalOffset += STRING_SPACING; diff --git a/TombEngine/Game/collision/floordata.h b/TombEngine/Game/collision/floordata.h index 16662eeca..56dff4dec 100644 --- a/TombEngine/Game/collision/floordata.h +++ b/TombEngine/Game/collision/floordata.h @@ -49,10 +49,10 @@ enum class MaterialType enum class ClimbDirectionFlags { - North = (1 << 8), - East = (1 << 9), - South = (1 << 10), - West = (1 << 11) + North = 1 << 8, + East = 1 << 9, + South = 1 << 10, + West = 1 << 11 }; // NOTE: Describes vertical room location. @@ -74,10 +74,10 @@ public: struct SectorSurfaceTriangleData { - Plane Plane = {}; - int PortalRoomNumber = 0; - short IllegalSlopeAngle = 0; - MaterialType Material = MaterialType::Stone; + Plane Plane = {}; + int PortalRoomNumber = 0; + short SteepSlopeAngle = 0; + MaterialType Material = MaterialType::Stone; }; struct SectorSurfaceData @@ -135,17 +135,19 @@ struct SectorFlagData class FloorInfo { public: - // Components - int RoomNumber = 0; - int SidePortalRoomNumber = 0; - SectorSurfaceData FloorSurface = {}; - SectorSurfaceData CeilingSurface = {}; - std::set BridgeItemNumbers = {}; - SectorFlagData Flags = {}; + // Members + Vector2i Position = Vector2i::Zero; + int RoomNumber = 0; + SectorSurfaceData FloorSurface = {}; + SectorSurfaceData CeilingSurface = {}; + SectorFlagData Flags = {}; - int Box = 0; - int TriggerIndex = 0; - bool Stopper = true; + std::set BridgeItemNumbers = {}; + int SidePortalRoomNumber = 0; + + int PathfindingBoxID = 0; + int TriggerIndex = 0; + bool Stopper = true; // Getters int GetSurfaceTriangleID(int x, int z, bool isFloor) const; @@ -184,7 +186,7 @@ namespace TEN::Collision::Floordata Vector2i GetSectorPoint(int x, int z); Vector2i GetRoomGridCoord(int roomNumber, int x, int z, bool clampToBounds = true); std::vector GetNeighborRoomGridCoords(const Vector3i& pos, int roomNumber, unsigned int searchDepth); - std::vector GetNeighborSectorPtrs(const Vector3i& pos, int roomNumber, unsigned int searchDepth); + std::vector GetNeighborSectors(const Vector3i& pos, int roomNumber, unsigned int searchDepth); FloorInfo& GetFloor(int roomNumber, const Vector2i& roomGridCoord); FloorInfo& GetFloor(int roomNumber, int x, int z); diff --git a/TombEngine/Game/collision/sphere.cpp b/TombEngine/Game/collision/sphere.cpp index 22ca14f86..fd20d15f8 100644 --- a/TombEngine/Game/collision/sphere.cpp +++ b/TombEngine/Game/collision/sphere.cpp @@ -1,5 +1,5 @@ #include "framework.h" -#include "Game/collision/sphere.h" +#include "Game/collision/Sphere.h" #include "Game/Lara/lara.h" #include "Game/items.h" @@ -8,85 +8,54 @@ #include "Math/Math.h" #include "Renderer/Renderer.h" +using namespace TEN::Math; using namespace TEN::Renderer; -SPHERE LaraSpheres[MAX_SPHERES]; -SPHERE CreatureSpheres[MAX_SPHERES]; - -int GetSpheres(ItemInfo* item, SPHERE* ptr, int worldSpace, Matrix local) +namespace TEN::Collision::Sphere { - if (item == nullptr) - return 0; - - BoundingSphere spheres[MAX_SPHERES]; - int num = g_Renderer.GetSpheres(item->Index, spheres, worldSpace, local); - - for (int i = 0; i < MAX_SPHERES; i++) + bool HandleItemSphereCollision(ItemInfo& item0, ItemInfo& item1) { - ptr[i].x = spheres[i].Center.x; - ptr[i].y = spheres[i].Center.y; - ptr[i].z = spheres[i].Center.z; - ptr[i].r = spheres[i].Radius; - } + auto spheres0 = item0.GetSpheres(); + auto spheres1 = item1.GetSpheres(); - return num; -} + item1.TouchBits.ClearAll(); -int TestCollision(ItemInfo* item, ItemInfo* laraItem) -{ - int flags = 0; - - int creatureSphereCount = GetSpheres(item, CreatureSpheres, SPHERES_SPACE_WORLD, Matrix::Identity); - int laraSphereCount = GetSpheres(laraItem, LaraSpheres, SPHERES_SPACE_WORLD, Matrix::Identity); - - laraItem->TouchBits = NO_JOINT_BITS; - - if (creatureSphereCount <= 0) - { - item->TouchBits = NO_JOINT_BITS; - return 0; - } - else - { - for (int i = 0; i < creatureSphereCount; i++) + if (spheres0.empty()) { - auto* ptr1 = &CreatureSpheres[i]; - - int x1 = item->Pose.Position.x + ptr1->x; - int y1 = item->Pose.Position.y + ptr1->y; - int z1 = item->Pose.Position.z + ptr1->z; - int r1 = ptr1->r; + item0.TouchBits.ClearAll(); + return false; + } - if (r1 > 0) + // Run through item 0 spheres. + bool isCollided = false; + for (int i = 0; i < spheres0.size(); i++) + { + // Get sphere 0. + const auto& sphere0 = spheres0[i]; + if (sphere0.Radius <= 0.0f) + continue; + + // Run through item 1 spheres. + for (int j = 0; j < spheres1.size(); j++) { - for (int j = 0; j < laraSphereCount; j++) - { - auto* ptr2 = &LaraSpheres[j]; + // Get sphere 1. + const auto& sphere1 = spheres1[j]; + if (sphere1.Radius <= 0.0f) + continue; - int x2 = item->Pose.Position.x + ptr2->x; - int y2 = item->Pose.Position.y + ptr2->y; - int z2 = item->Pose.Position.z + ptr2->z; - int r2 = ptr2->r; + // Test sphere collision. + if (!sphere0.Intersects(sphere1)) + continue; - if (r2 > 0) - { - int dx = x1 - x2; - int dy = y1 - y2; - int dz = z1 - z2; - int r = r1 + r2; + // Set touch bits. + item0.TouchBits.Set(i); + item1.TouchBits.Set(j); - if ((SQUARE(dx) + SQUARE(dy) + SQUARE(dz)) < SQUARE(r)) - { - item->TouchBits.Set(i); - laraItem->TouchBits.Set(j); - flags |= 1 << i; - break; - } - } - } + isCollided = true; + break; } } - return flags; + return isCollided; } } diff --git a/TombEngine/Game/collision/sphere.h b/TombEngine/Game/collision/sphere.h index d02c36198..30883d9be 100644 --- a/TombEngine/Game/collision/sphere.h +++ b/TombEngine/Game/collision/sphere.h @@ -1,31 +1,8 @@ #pragma once -#include "Game/items.h" -#define SPHERES_SPACE_LOCAL 0 -#define SPHERES_SPACE_WORLD 1 -#define SPHERES_SPACE_BONE_ORIGIN 2 -#define MAX_SPHERES 34 +struct ItemInfo; -struct SPHERE +namespace TEN::Collision::Sphere { - int x = 0; - int y = 0; - int z = 0; - int r = 0; - - SPHERE() {}; - - SPHERE(const Vector3& pos, float radius) - { - this->x = (int)round(pos.x); - this->y = (int)round(pos.y); - this->z = (int)round(pos.z); - this->r = (int)round(radius); - } -}; - -extern SPHERE LaraSpheres[MAX_SPHERES]; -extern SPHERE CreatureSpheres[MAX_SPHERES]; - -int TestCollision(ItemInfo* item, ItemInfo* laraItem); -int GetSpheres(ItemInfo* item, SPHERE* ptr, int worldSpace, Matrix local); + bool HandleItemSphereCollision(ItemInfo& item0, ItemInfo& item1); +} diff --git a/TombEngine/Game/control/box.cpp b/TombEngine/Game/control/box.cpp index c1701b9a6..998794061 100644 --- a/TombEngine/Game/control/box.cpp +++ b/TombEngine/Game/control/box.cpp @@ -3,8 +3,8 @@ #include "Game/animation.h" #include "Game/camera.h" -#include "Game/collision/sphere.h" #include "Game/collision/collide_room.h" +#include "Game/collision/Point.h" #include "Game/control/control.h" #include "Game/control/lot.h" #include "Game/effects/smoke.h" @@ -20,19 +20,18 @@ #include "Math/Math.h" #include "Objects/objectslist.h" #include "Objects/Generic/Object/Pushable/PushableObject.h" -#include "Renderer/Renderer.h" +using namespace TEN::Collision::Point; +using namespace TEN::Collision::Room; using namespace TEN::Effects::Smoke; constexpr auto ESCAPE_DIST = BLOCK(5); constexpr auto STALK_DIST = BLOCK(3); -constexpr auto REACHED_GOAL_RADIUS = 640; +constexpr auto REACHED_GOAL_RADIUS = BLOCK(0.625); constexpr auto ATTACK_RANGE = SQUARE(BLOCK(3)); constexpr auto ESCAPE_CHANCE = 0x800; constexpr auto RECOVER_CHANCE = 0x100; constexpr auto BIFF_AVOID_TURN = ANGLE(11.25f); -constexpr auto FEELER_DISTANCE = CLICK(2); -constexpr auto FEELER_ANGLE = ANGLE(45.0f); constexpr auto CREATURE_AI_ROTATION_MAX = ANGLE(90.0f); constexpr auto CREATURE_JOINT_ROTATION_MAX = ANGLE(70.0f); @@ -52,7 +51,7 @@ void DrawBox(int boxIndex, Vector3 color) if (boxIndex == NO_VALUE) return; - auto& currBox = g_Level.Boxes[boxIndex]; + auto& currBox = g_Level.PathfindingBoxes[boxIndex]; float x = ((float)currBox.left + (float)(currBox.right - currBox.left) / 2.0f) * 1024.0f; auto y = currBox.height - CLICK(1); @@ -66,7 +65,7 @@ void DrawBox(int boxIndex, Vector3 color) for (int i = 0; i <= 10; i++) { dBox.Extents = extents + Vector3(i); - TEN::Renderer::g_Renderer.AddDebugBox(dBox, Vector4(color.x, color.y, color.z, 1), RendererDebugPage::PathfindingStats); + DrawDebugBox(dBox, Vector4(color.x, color.y, color.z, 1), RendererDebugPage::PathfindingStats); } } @@ -75,7 +74,7 @@ void DrawNearbyPathfinding(int boxIndex) if (boxIndex == NO_VALUE) return; - auto& currBox = g_Level.Boxes[boxIndex]; + auto& currBox = g_Level.PathfindingBoxes[boxIndex]; auto index = currBox.overlapIndex; // Grey flag box. @@ -152,13 +151,13 @@ bool SameZone(CreatureInfo* creature, ItemInfo* target) auto* zone = g_Level.Zones[(int)creature->LOT.Zone][(int)FlipStatus].data(); auto& roomSource = g_Level.Rooms[item.RoomNumber]; - auto& boxSource = GetSector(&roomSource, item.Pose.Position.x - roomSource.x, item.Pose.Position.z - roomSource.z)->Box; + auto& boxSource = GetSector(&roomSource, item.Pose.Position.x - roomSource.Position.x, item.Pose.Position.z - roomSource.Position.z)->PathfindingBoxID; if (boxSource == NO_VALUE) return false; item.BoxNumber = boxSource; auto& roomTarget = g_Level.Rooms[target->RoomNumber]; - auto& boxTarget = GetSector(&roomTarget, target->Pose.Position.x - roomTarget.x, target->Pose.Position.z - roomTarget.z)->Box; + auto& boxTarget = GetSector(&roomTarget, target->Pose.Position.x - roomTarget.Position.x, target->Pose.Position.z - roomTarget.Position.z)->PathfindingBoxID; if (boxTarget == NO_VALUE) return false; target->BoxNumber = boxTarget; @@ -254,7 +253,7 @@ bool CreaturePathfind(ItemInfo* item, Vector3i prevPos, short angle, short tilt) int boxHeight; if (item->BoxNumber != NO_VALUE) - boxHeight = g_Level.Boxes[item->BoxNumber].height; + boxHeight = g_Level.PathfindingBoxes[item->BoxNumber].height; else boxHeight = item->Floor; @@ -264,31 +263,31 @@ bool CreaturePathfind(ItemInfo* item, Vector3i prevPos, short angle, short tilt) GetFloor(prevPos.x, y, prevPos.z, &roomNumber); auto* floor = GetFloor(item->Pose.Position.x, y, item->Pose.Position.z, &roomNumber); - if (floor->Box == NO_VALUE) + if (floor->PathfindingBoxID == NO_VALUE) return false; - int height = g_Level.Boxes[floor->Box].height; + int height = g_Level.PathfindingBoxes[floor->PathfindingBoxID].height; int nextHeight = 0; int nextBox; if (!Objects[item->ObjectNumber].nonLot) { - nextBox = LOT->Node[floor->Box].exitBox; + nextBox = LOT->Node[floor->PathfindingBoxID].exitBox; } else { floor = GetFloor(item->Pose.Position.x, item->Pose.Position.y, item->Pose.Position.z, &roomNumber); - height = g_Level.Boxes[floor->Box].height; - nextBox = floor->Box; + height = g_Level.PathfindingBoxes[floor->PathfindingBoxID].height; + nextBox = floor->PathfindingBoxID; } if (nextBox == NO_VALUE) nextHeight = height; else - nextHeight = g_Level.Boxes[nextBox].height; + nextHeight = g_Level.PathfindingBoxes[nextBox].height; - if (floor->Box == NO_VALUE || !LOT->IsJumping && - (LOT->Fly == NO_FLYING && item->BoxNumber != NO_VALUE && zone[item->BoxNumber] != zone[floor->Box] || + if (floor->PathfindingBoxID == NO_VALUE || !LOT->IsJumping && + (LOT->Fly == NO_FLYING && item->BoxNumber != NO_VALUE && zone[item->BoxNumber] != zone[floor->PathfindingBoxID] || boxHeight - height > LOT->Step || boxHeight - height < LOT->Drop)) { @@ -308,22 +307,22 @@ bool CreaturePathfind(ItemInfo* item, Vector3i prevPos, short angle, short tilt) item->Pose.Position.z = prevPos.z | WALL_MASK; floor = GetFloor(item->Pose.Position.x, y, item->Pose.Position.z, &roomNumber); - height = g_Level.Boxes[floor->Box].height; + height = g_Level.PathfindingBoxes[floor->PathfindingBoxID].height; if (!Objects[item->ObjectNumber].nonLot) { - nextBox = LOT->Node[floor->Box].exitBox; + nextBox = LOT->Node[floor->PathfindingBoxID].exitBox; } else { floor = GetFloor(item->Pose.Position.x, item->Pose.Position.y, item->Pose.Position.z, &roomNumber); - height = g_Level.Boxes[floor->Box].height; - nextBox = floor->Box; + height = g_Level.PathfindingBoxes[floor->PathfindingBoxID].height; + nextBox = floor->PathfindingBoxID; } if (nextBox == NO_VALUE) nextHeight = height; else - nextHeight = g_Level.Boxes[nextBox].height; + nextHeight = g_Level.PathfindingBoxes[nextBox].height; } int x = item->Pose.Position.x; @@ -610,22 +609,28 @@ void CreatureUnderwater(ItemInfo* item, int depth) waterLevel = 0; } else - waterHeight = GetWaterHeight(item); + { + waterHeight = GetPointCollision(*item).GetWaterTopHeight(); + } int y = waterHeight + waterLevel; if (item->Pose.Position.y < y) { - int height = GetCollision(item).Position.Floor; + int height = GetPointCollision(*item).GetFloorHeight(); item->Pose.Position.y = y; if (y > height) item->Pose.Position.y = height; if (item->Pose.Orientation.x > ANGLE(2.0f)) + { item->Pose.Orientation.x -= ANGLE(2.0f); + } else if (item->Pose.Orientation.x > 0) + { item->Pose.Orientation.x = 0; + } } } @@ -633,13 +638,13 @@ void CreatureFloat(short itemNumber) { auto* item = &g_Level.Items[itemNumber]; - auto pointColl = GetCollision(item); + auto pointColl = GetPointCollision(*item); item->HitPoints = NOT_TARGETABLE; item->Pose.Orientation.x = 0; int y = item->Pose.Position.y; - int waterLevel = GetWaterHeight(item); + int waterLevel = GetPointCollision(*item).GetWaterTopHeight(); if (waterLevel == NO_HEIGHT) return; @@ -651,9 +656,9 @@ void CreatureFloat(short itemNumber) AnimateItem(item); - item->Floor = pointColl.Position.Floor; - if (pointColl.RoomNumber != item->RoomNumber) - ItemNewRoom(itemNumber, pointColl.RoomNumber); + item->Floor = pointColl.GetFloorHeight(); + if (pointColl.GetRoomNumber() != item->RoomNumber) + ItemNewRoom(itemNumber, pointColl.GetRoomNumber()); if (item->Pose.Position.y <= waterLevel) { @@ -786,7 +791,7 @@ void CreatureHealth(ItemInfo* item) TestEnvironment(RoomEnvFlags::ENV_FLAG_WATER, &g_Level.Rooms[item->RoomNumber])) { auto bounds = GameBoundingBox(item); - auto height = item->Pose.Position.y - GetWaterHeight(item); + auto height = item->Pose.Position.y - GetPointCollision(*item).GetWaterTopHeight(); if (abs(bounds.Y1 + bounds.Y2) < height) DoDamage(item, INT_MAX); @@ -848,13 +853,13 @@ void CreatureDie(int itemNumber, bool doExplosion, int flags) bool BadFloor(int x, int y, int z, int boxHeight, int nextHeight, short roomNumber, LOTInfo* LOT) { auto* floor = GetFloor(x, y, z, &roomNumber); - if (floor->Box == NO_VALUE) + if (floor->PathfindingBoxID == NO_VALUE) return true; if (LOT->IsJumping) return false; - auto* box = &g_Level.Boxes[floor->Box]; + auto* box = &g_Level.PathfindingBoxes[floor->PathfindingBoxID]; if (box->flags & LOT->BlockMask) return true; @@ -888,7 +893,7 @@ int CreatureCreature(short itemNumber) { auto* linked = &g_Level.Items[link]; - if (link != itemNumber && linked != LaraItem && linked->Status == ITEM_ACTIVE && linked->HitPoints > 0) // TODO: deal with LaraItem global. + if (link != itemNumber && linked != LaraItem && linked->IsCreature() && linked->Status == ITEM_ACTIVE && linked->HitPoints > 0) // TODO: deal with LaraItem global. { int xDistance = abs(linked->Pose.Position.x - x); int zDistance = abs(linked->Pose.Position.z - z); @@ -919,7 +924,7 @@ bool ValidBox(ItemInfo* item, short zoneNumber, short boxNumber) if (creature.LOT.Fly == NO_FLYING && zone[boxNumber] != zoneNumber) return false; - const auto& box = g_Level.Boxes[boxNumber]; + const auto& box = g_Level.PathfindingBoxes[boxNumber]; if (creature.LOT.BlockMask & box.flags) return false; @@ -939,7 +944,7 @@ bool EscapeBox(ItemInfo* item, ItemInfo* enemy, int boxNumber) if (boxNumber == NO_VALUE) return false; - const auto& box = g_Level.Boxes[boxNumber]; + const auto& box = g_Level.PathfindingBoxes[boxNumber]; int x = ((box.top + box.bottom) * BLOCK(0.5f)) - enemy->Pose.Position.x; int z = ((box.left + box.right) * BLOCK(0.5f)) - enemy->Pose.Position.z; @@ -957,7 +962,7 @@ void TargetBox(LOTInfo* LOT, int boxNumber) { if (boxNumber == NO_VALUE) return; - auto* box = &g_Level.Boxes[boxNumber]; + auto* box = &g_Level.PathfindingBoxes[boxNumber]; // Maximize target precision. DO NOT change bracket precedence! LOT->Target.x = (int)((box->top * BLOCK(1)) + (float)GetRandomControl() * (((float)(box->bottom - box->top) - 1.0f) / 32.0f) + CLICK(2.0f)); @@ -1007,7 +1012,7 @@ bool SearchLOT(LOTInfo* LOT, int depth) return false; } - auto* box = &g_Level.Boxes[LOT->Head]; + auto* box = &g_Level.PathfindingBoxes[LOT->Head]; auto* node = &LOT->Node[LOT->Head]; int index = box->overlapIndex; @@ -1025,7 +1030,7 @@ bool SearchLOT(LOTInfo* LOT, int depth) if (LOT->Fly == NO_FLYING && searchZone != zone[boxNumber]) continue; - int delta = g_Level.Boxes[boxNumber].height - box->height; + int delta = g_Level.PathfindingBoxes[boxNumber].height - box->height; if ((delta > LOT->Step || delta < LOT->Drop) && (!(flags & BOX_MONKEY) || !LOT->CanMonkey)) continue; @@ -1036,7 +1041,7 @@ bool SearchLOT(LOTInfo* LOT, int depth) if ((node->searchNumber & SEARCH_NUMBER) < (expand->searchNumber & SEARCH_NUMBER)) continue; - if (node->searchNumber & BLOCKED_SEARCH) + if (node->searchNumber & SEARCH_BLOCKED) { if ((node->searchNumber & SEARCH_NUMBER) == (expand->searchNumber & SEARCH_NUMBER)) continue; @@ -1045,12 +1050,12 @@ bool SearchLOT(LOTInfo* LOT, int depth) } else { - if ((node->searchNumber & SEARCH_NUMBER) == (expand->searchNumber & SEARCH_NUMBER) && !(expand->searchNumber & BLOCKED_SEARCH)) + if ((node->searchNumber & SEARCH_NUMBER) == (expand->searchNumber & SEARCH_NUMBER) && !(expand->searchNumber & SEARCH_BLOCKED)) continue; - if (g_Level.Boxes[boxNumber].flags & LOT->BlockMask) + if (g_Level.PathfindingBoxes[boxNumber].flags & LOT->BlockMask) { - expand->searchNumber = node->searchNumber | BLOCKED_SEARCH; + expand->searchNumber = node->searchNumber | SEARCH_BLOCKED; } else { @@ -1136,7 +1141,7 @@ bool StalkBox(ItemInfo* item, ItemInfo* enemy, int boxNumber) { if (enemy == nullptr || boxNumber == NO_VALUE) return false; - auto* box = &g_Level.Boxes[boxNumber]; + auto* box = &g_Level.PathfindingBoxes[boxNumber]; int xRange = STALK_DIST + ((box->bottom - box->top) * BLOCK(1)); int zRange = STALK_DIST + ((box->right - box->left) * BLOCK(1)); @@ -1292,11 +1297,15 @@ void GetAITarget(CreatureInfo* creature) { auto* enemy = creature->Enemy; - short enemyObjectNumber; + int enemyObjectID = 0; if (enemy) - enemyObjectNumber = enemy->ObjectNumber; + { + enemyObjectID = enemy->ObjectNumber; + } else - enemyObjectNumber = NO_VALUE; + { + enemyObjectID = NO_VALUE; + } auto* item = &g_Level.Items[creature->ItemNumber]; @@ -1324,12 +1333,12 @@ void GetAITarget(CreatureInfo* creature) } else if (!creature->Patrol) { - if (enemyObjectNumber != ID_AI_PATROL1) - FindAITargetObject(creature, ID_AI_PATROL1); + if (enemyObjectID != ID_AI_PATROL1) + FindAITargetObject(*item, ID_AI_PATROL1); } - else if (enemyObjectNumber != ID_AI_PATROL2) + else if (enemyObjectID != ID_AI_PATROL2) { - FindAITargetObject(creature, ID_AI_PATROL2); + FindAITargetObject(*item, ID_AI_PATROL2); } else if (abs(enemy->Pose.Position.x - item->Pose.Position.x) < REACHED_GOAL_RADIUS && abs(enemy->Pose.Position.y - item->Pose.Position.y) < REACHED_GOAL_RADIUS && @@ -1345,8 +1354,8 @@ void GetAITarget(CreatureInfo* creature) // First if was removed probably after TR3 and was it used by monkeys? /*if (!(item->aiBits & MODIFY) && !creature->hurtByLara) creature->enemy = LaraItem; - else*/ if (enemyObjectNumber != ID_AI_AMBUSH) - FindAITargetObject(creature, ID_AI_AMBUSH); + else*/ if (enemyObjectID != ID_AI_AMBUSH) + FindAITargetObject(*item, ID_AI_AMBUSH); /*else if (item->objectNumber == ID_MONKEY) return;*/ else if (abs(enemy->Pose.Position.x - item->Pose.Position.x) < REACHED_GOAL_RADIUS && @@ -1357,6 +1366,7 @@ void GetAITarget(CreatureInfo* creature) creature->ReachedGoal = true; creature->Enemy = LaraItem; item->AIBits &= ~(AMBUSH /* | MODIFY*/); + if (item->AIBits != MODIFY) { item->AIBits |= GUARD; @@ -1376,9 +1386,9 @@ void GetAITarget(CreatureInfo* creature) { item->AIBits &= ~FOLLOW; } - else if (enemyObjectNumber != ID_AI_FOLLOW) + else if (enemyObjectID != ID_AI_FOLLOW) { - FindAITargetObject(creature, ID_AI_FOLLOW); + FindAITargetObject(*item, ID_AI_FOLLOW); } else if (abs(enemy->Pose.Position.x - item->Pose.Position.x) < REACHED_GOAL_RADIUS && abs(enemy->Pose.Position.y - item->Pose.Position.y) < REACHED_GOAL_RADIUS && @@ -1388,17 +1398,18 @@ void GetAITarget(CreatureInfo* creature) item->AIBits &= ~FOLLOW; } } - /*else if (item->objectNumber == ID_MONKEY && item->carriedItem == NO_VALUE) + + /*else if (item->ObjectNumber == ID_MONKEY && item->CarriedItem == NO_VALUE) { - if (item->aiBits != MODIFY) + if (item->AIBits != MODIFY) { - if (enemyObjectNumber != ID_SMALLMEDI_ITEM) - FindAITargetObject(creature, ID_SMALLMEDI_ITEM); + if (enemyObjectID != ID_SMALLMEDI_ITEM) + FindAITargetObject(*item, ID_SMALLMEDI_ITEM); } else { - if (enemyObjectNumber != ID_KEY_ITEM4) - FindAITargetObject(creature, ID_KEY_ITEM4); + if (enemyObjectID != ID_KEY_ITEM4) + FindAITargetObject(*item, ID_KEY_ITEM4); } }*/ } @@ -1427,76 +1438,108 @@ void FindAITarget(CreatureInfo* creature, short objectNumber) } } -void FindAITargetObject(CreatureInfo* creature, int objectNumber) +void FindAITargetObject(ItemInfo& item, GAME_OBJECT_ID objectID, std::optional ocb, std::optional checkSameZone) { - const auto& item = g_Level.Items[creature->ItemNumber]; + auto& creature = *GetCreatureInfo(&item); - FindAITargetObject(creature, objectNumber, item.ItemFlags[3], true); + auto data = AITargetData{}; + data.CheckDistance = false; + data.CheckOcb = ocb.has_value(); + data.ObjectID = objectID; + data.Ocb = ocb.value_or(item.ItemFlags[3]); + data.CheckSameZone = checkSameZone.value_or(true); + + if (FindAITargetObject(item, data)) + { + *creature.AITarget = data.FoundItem; + creature.Enemy = creature.AITarget; + } } -void FindAITargetObject(CreatureInfo* creature, int objectNumber, int ocb, bool checkSameZone) +bool FindAITargetObject(ItemInfo& item, AITargetData& data) { - auto& item = g_Level.Items[creature->ItemNumber]; - if (g_Level.AIObjects.empty()) - return; + return false; - AI_OBJECT* foundObject = nullptr; + auto& creature = *GetCreatureInfo(&item); - for (auto& aiObject : g_Level.AIObjects) + const AI_OBJECT* foundAIObject = nullptr; + + for (const auto& aiObject : g_Level.AIObjects) { - if (aiObject.objectNumber == objectNumber && - aiObject.triggerFlags == ocb && - aiObject.roomNumber != NO_VALUE) + // Check if object IDs match. + if (aiObject.objectNumber != data.ObjectID) + continue; + + // Check if room is valid. + if (aiObject.roomNumber == NO_VALUE) + continue; + + // Check if distance is valid. + if (data.CheckDistance) { - int* zone = g_Level.Zones[(int)creature->LOT.Zone][(int)FlipStatus].data(); + if (Vector3i::Distance(item.Pose.Position, aiObject.pos.Position) > data.DistanceMax) + continue; + } + + // Check if OCBs match (useful for pathfinding). + if (data.CheckOcb) + { + if (aiObject.triggerFlags != data.Ocb) + continue; + } + + // Check if zone IDs match. + if (data.CheckSameZone) + { + int* zone = g_Level.Zones[(int)creature.LOT.Zone][(int)FlipStatus].data(); auto* room = &g_Level.Rooms[item.RoomNumber]; - item.BoxNumber = GetSector(room, item.Pose.Position.x - room->x, item.Pose.Position.z - room->z)->Box; + // NOTE: Avoid changing box ID of item or AI item so a local variable isn't required when searching for AI object near it. + int boxID = GetSector(room, item.Pose.Position.x - room->Position.x, item.Pose.Position.z - room->Position.z)->PathfindingBoxID; room = &g_Level.Rooms[aiObject.roomNumber]; - aiObject.boxNumber = GetSector(room, aiObject.pos.Position.x - room->x, aiObject.pos.Position.z - room->z)->Box; + int aiBoxID = GetSector(room, aiObject.pos.Position.x - room->Position.x, aiObject.pos.Position.z - room->Position.z)->PathfindingBoxID; - if (item.BoxNumber == NO_VALUE || aiObject.boxNumber == NO_VALUE) - return; + // Box is invalid or zones don't match; continue. + if (boxID == NO_VALUE || aiBoxID == NO_VALUE) + continue; - if (checkSameZone && (zone[item.BoxNumber] != zone[aiObject.boxNumber])) - return; - - // Don't check for same zone. Needed for Sophia Leigh. - foundObject = &aiObject; + // Zone is invalid; continue. + if (zone[boxID] != zone[aiBoxID]) + continue; } + + // HACK: Don't check for matching zone. Needed for Sophia Leigh. + foundAIObject = &aiObject; } - if (foundObject == nullptr) - return; + if (foundAIObject == nullptr) + return false; - auto& aiItem = *creature->AITarget; + auto aiItem = ItemInfo{}; + aiItem.ObjectNumber = foundAIObject->objectNumber; + aiItem.RoomNumber = foundAIObject->roomNumber; + aiItem.Pose.Position = foundAIObject->pos.Position; + aiItem.Pose.Orientation.y = foundAIObject->pos.Orientation.y; + aiItem.Flags = foundAIObject->flags; + aiItem.TriggerFlags = foundAIObject->triggerFlags; + aiItem.BoxNumber = foundAIObject->boxNumber; - creature->Enemy = &aiItem; - - aiItem.ObjectNumber = foundObject->objectNumber; - aiItem.RoomNumber = foundObject->roomNumber; - aiItem.Pose.Position = foundObject->pos.Position; - aiItem.Pose.Orientation.y = foundObject->pos.Orientation.y; - aiItem.Flags = foundObject->flags; - aiItem.TriggerFlags = foundObject->triggerFlags; - aiItem.BoxNumber = foundObject->boxNumber; - - if (!(creature->AITarget->Flags & ItemFlags::IFLAG_TRIGGERED)) + if (!(aiItem.Flags & IFLAG_TRIGGERED)) { - float sinY = phd_sin(creature->AITarget->Pose.Orientation.y); - float cosY = phd_cos(creature->AITarget->Pose.Orientation.y); - - creature->AITarget->Pose.Position.x += CLICK(1) * sinY; - creature->AITarget->Pose.Position.z += CLICK(1) * cosY; + aiItem.Pose.Position.x += CLICK(1) * phd_sin(aiItem.Pose.Orientation.y); + aiItem.Pose.Position.z += CLICK(1) * phd_cos(aiItem.Pose.Orientation.y); } + + data.FoundItem = aiItem; + return true; } int TargetReachable(ItemInfo* item, ItemInfo* enemy) { const auto& creature = *GetCreatureInfo(item); auto& room = g_Level.Rooms[enemy->RoomNumber]; - auto* floor = GetSector(&room, enemy->Pose.Position.x - room.x, enemy->Pose.Position.z - room.z); + auto* floor = GetSector(&room, enemy->Pose.Position.x - room.Position.x, enemy->Pose.Position.z - room.Position.z); // NEW: Only update enemy box number if it is actually reachable by the enemy. // This prevents enemies from running to the player and attacking nothing when they are hanging or shimmying. -- Lwmte, 27.06.22 @@ -1510,12 +1553,12 @@ int TargetReachable(ItemInfo* item, ItemInfo* enemy) } else { - auto pointColl = GetCollision(floor, enemy->Pose.Position.x, enemy->Pose.Position.y, enemy->Pose.Position.z); + auto pointColl = GetPointCollision(enemy->Pose.Position, floor->RoomNumber); auto bounds = GameBoundingBox(item); - isReachable = abs(enemy->Pose.Position.y - pointColl.Position.Floor) < bounds.GetHeight(); + isReachable = abs(enemy->Pose.Position.y - pointColl.GetFloorHeight()) < bounds.GetHeight(); } - return (isReachable ? floor->Box : NO_VALUE); + return (isReachable ? floor->PathfindingBoxID : NO_VALUE); } void CreatureAIInfo(ItemInfo* item, AI_INFO* AI) @@ -1537,7 +1580,7 @@ void CreatureAIInfo(ItemInfo* item, AI_INFO* AI) auto* zone = g_Level.Zones[(int)creature->LOT.Zone][(int)FlipStatus].data(); auto* room = &g_Level.Rooms[item->RoomNumber]; - item->BoxNumber = GetSector(room, item->Pose.Position.x - room->x, item->Pose.Position.z - room->z)->Box; + item->BoxNumber = GetSector(room, item->Pose.Position.x - room->Position.x, item->Pose.Position.z - room->Position.z)->PathfindingBoxID; AI->zoneNumber = zone[item->BoxNumber]; enemy->BoxNumber = TargetReachable(item, enemy); @@ -1545,12 +1588,12 @@ void CreatureAIInfo(ItemInfo* item, AI_INFO* AI) if (!object->nonLot) { - if (enemy->BoxNumber != NO_VALUE && g_Level.Boxes[enemy->BoxNumber].flags & creature->LOT.BlockMask) + if (enemy->BoxNumber != NO_VALUE && g_Level.PathfindingBoxes[enemy->BoxNumber].flags & creature->LOT.BlockMask) { AI->enemyZone |= BLOCKED; } else if (item->BoxNumber != NO_VALUE && - creature->LOT.Node[item->BoxNumber].searchNumber == (creature->LOT.SearchNumber | BLOCKED_SEARCH)) + creature->LOT.Node[item->BoxNumber].searchNumber == (creature->LOT.SearchNumber | SEARCH_BLOCKED)) { AI->enemyZone |= BLOCKED; } @@ -1745,7 +1788,7 @@ void CreatureMood(ItemInfo* item, AI_INFO* AI, bool isViolent) int endBox = LOT->Node[item->BoxNumber].exitBox; if (endBox != NO_VALUE) { - int overlapIndex = g_Level.Boxes[item->BoxNumber].overlapIndex; + int overlapIndex = g_Level.PathfindingBoxes[item->BoxNumber].overlapIndex; int nextBox = 0; int flags = 0; @@ -1779,7 +1822,7 @@ void GetCreatureMood(ItemInfo* item, AI_INFO* AI, bool isViolent) auto* enemy = creature->Enemy; auto* LOT = &creature->LOT; - if (item->BoxNumber == NO_VALUE || creature->LOT.Node[item->BoxNumber].searchNumber == (creature->LOT.SearchNumber | BLOCKED_SEARCH)) + if (item->BoxNumber == NO_VALUE || creature->LOT.Node[item->BoxNumber].searchNumber == (creature->LOT.SearchNumber | SEARCH_BLOCKED)) creature->LOT.RequiredBox = NO_VALUE; if (creature->Mood != MoodType::Attack && creature->LOT.RequiredBox != NO_VALUE && !ValidBox(item, AI->zoneNumber, creature->LOT.TargetBox)) @@ -1892,7 +1935,7 @@ TARGET_TYPE CalculateTarget(Vector3i* target, ItemInfo* item, LOTInfo* LOT) if (boxNumber == NO_VALUE) return TARGET_TYPE::NO_TARGET; - auto* box = &g_Level.Boxes[boxNumber]; + auto* box = &g_Level.PathfindingBoxes[boxNumber]; int boxLeft = ((int)box->left * BLOCK(1)); int boxRight = ((int)box->right * BLOCK(1)) - 1; @@ -1902,11 +1945,11 @@ TARGET_TYPE CalculateTarget(Vector3i* target, ItemInfo* item, LOTInfo* LOT) int right = boxRight; int top = boxTop; int bottom = boxBottom; - int direction = ALL_CLIP; + int direction = CLIP_ALL; do { - box = &g_Level.Boxes[boxNumber]; + box = &g_Level.PathfindingBoxes[boxNumber]; if (LOT->Fly != NO_FLYING) { @@ -1942,7 +1985,7 @@ TARGET_TYPE CalculateTarget(Vector3i* target, ItemInfo* item, LOTInfo* LOT) if (target->z < (boxLeft + CLICK(2))) target->z = boxLeft + CLICK(2); - if (direction & SECONDARY_CLIP) + if (direction & CLIP_SECONDARY) return TARGET_TYPE::SECONDARY_TARGET; if (boxTop > top) @@ -1957,10 +2000,10 @@ TARGET_TYPE CalculateTarget(Vector3i* target, ItemInfo* item, LOTInfo* LOT) { target->z = (right - CLICK(2)); - if (direction != ALL_CLIP) + if (direction != CLIP_ALL) return TARGET_TYPE::SECONDARY_TARGET; - direction |= (ALL_CLIP | SECONDARY_CLIP); + direction |= (CLIP_ALL | CLIP_SECONDARY); } } else if (item->Pose.Position.z > boxRight && direction != CLIP_LEFT) @@ -1972,7 +2015,7 @@ TARGET_TYPE CalculateTarget(Vector3i* target, ItemInfo* item, LOTInfo* LOT) if (target->z > boxRight - CLICK(2)) target->z = boxRight - CLICK(2); - if (direction & SECONDARY_CLIP) + if (direction & CLIP_SECONDARY) return TARGET_TYPE::SECONDARY_TARGET; if (boxTop > top) @@ -1987,10 +2030,10 @@ TARGET_TYPE CalculateTarget(Vector3i* target, ItemInfo* item, LOTInfo* LOT) { target->z = left + CLICK(2); - if (direction != ALL_CLIP) + if (direction != CLIP_ALL) return TARGET_TYPE::SECONDARY_TARGET; - direction |= (ALL_CLIP | SECONDARY_CLIP); + direction |= (CLIP_ALL | CLIP_SECONDARY); } } @@ -2003,7 +2046,7 @@ TARGET_TYPE CalculateTarget(Vector3i* target, ItemInfo* item, LOTInfo* LOT) if (target->x < boxTop + CLICK(2)) target->x = boxTop + CLICK(2); - if (direction & SECONDARY_CLIP) + if (direction & CLIP_SECONDARY) return TARGET_TYPE::SECONDARY_TARGET; if (boxLeft > left) @@ -2018,10 +2061,10 @@ TARGET_TYPE CalculateTarget(Vector3i* target, ItemInfo* item, LOTInfo* LOT) { target->x = bottom - CLICK(2); - if (direction != ALL_CLIP) + if (direction != CLIP_ALL) return TARGET_TYPE::SECONDARY_TARGET; - direction |= (ALL_CLIP | SECONDARY_CLIP); + direction |= (CLIP_ALL | CLIP_SECONDARY); } } else if (item->Pose.Position.x > boxBottom && direction != CLIP_TOP) @@ -2033,7 +2076,7 @@ TARGET_TYPE CalculateTarget(Vector3i* target, ItemInfo* item, LOTInfo* LOT) if (target->x > (boxBottom - CLICK(2))) target->x = (boxBottom - CLICK(2)); - if (direction & SECONDARY_CLIP) + if (direction & CLIP_SECONDARY) return TARGET_TYPE::SECONDARY_TARGET; if (boxLeft > left) @@ -2048,10 +2091,10 @@ TARGET_TYPE CalculateTarget(Vector3i* target, ItemInfo* item, LOTInfo* LOT) { target->x = top + CLICK(2); - if (direction != ALL_CLIP) + if (direction != CLIP_ALL) return TARGET_TYPE::SECONDARY_TARGET; - direction |= (ALL_CLIP | SECONDARY_CLIP); + direction |= (CLIP_ALL | CLIP_SECONDARY); } } } @@ -2062,7 +2105,7 @@ TARGET_TYPE CalculateTarget(Vector3i* target, ItemInfo* item, LOTInfo* LOT) { target->z = LOT->Target.z; } - else if (!(direction & SECONDARY_CLIP)) + else if (!(direction & CLIP_SECONDARY)) { if (target->z < (boxLeft + CLICK(2))) target->z = boxLeft + CLICK(2); @@ -2074,7 +2117,7 @@ TARGET_TYPE CalculateTarget(Vector3i* target, ItemInfo* item, LOTInfo* LOT) { target->x = LOT->Target.x; } - else if (!(direction & SECONDARY_CLIP)) + else if (!(direction & CLIP_SECONDARY)) { if (target->x < (boxTop + CLICK(2))) target->x = boxTop + CLICK(2); @@ -2087,11 +2130,11 @@ TARGET_TYPE CalculateTarget(Vector3i* target, ItemInfo* item, LOTInfo* LOT) } boxNumber = LOT->Node[boxNumber].exitBox; - if (boxNumber != NO_VALUE && (g_Level.Boxes[boxNumber].flags & LOT->BlockMask)) + if (boxNumber != NO_VALUE && (g_Level.PathfindingBoxes[boxNumber].flags & LOT->BlockMask)) break; } while (boxNumber != NO_VALUE); - if (!(direction & SECONDARY_CLIP)) + if (!(direction & CLIP_SECONDARY)) { if (target->z < (boxLeft + CLICK(2))) target->z = boxLeft + CLICK(2); @@ -2099,7 +2142,7 @@ TARGET_TYPE CalculateTarget(Vector3i* target, ItemInfo* item, LOTInfo* LOT) target->z = boxRight - CLICK(2); } - if (!(direction & SECONDARY_CLIP)) + if (!(direction & CLIP_SECONDARY)) { if (target->x < (boxTop + CLICK(2))) target->x = boxTop + CLICK(2); @@ -2121,14 +2164,14 @@ void AdjustStopperFlag(ItemInfo* item, int direction) int z = item->Pose.Position.z; auto* room = &g_Level.Rooms[item->RoomNumber]; - auto* floor = GetSector(room, x - room->x, z - room->z); + auto* floor = GetSector(room, x - room->Position.x, z - room->Position.z); floor->Stopper = !floor->Stopper; x = item->Pose.Position.x + BLOCK(1) * phd_sin(direction); z = item->Pose.Position.z + BLOCK(1) * phd_cos(direction); - room = &g_Level.Rooms[GetCollision(x, item->Pose.Position.y, z, item->RoomNumber).RoomNumber]; + room = &g_Level.Rooms[GetPointCollision(Vector3i(x, item->Pose.Position.y, z), item->RoomNumber).GetRoomNumber()]; - floor = GetSector(room, x - room->x, z - room->z); + floor = GetSector(room, x - room->Position.x, z - room->Position.z); floor->Stopper = !floor->Stopper; } @@ -2143,15 +2186,15 @@ void InitializeItemBoxData() { for (const auto& mesh : room.mesh) { - long index = ((mesh.pos.Position.z - room.z) / BLOCK(1)) + room.zSize * ((mesh.pos.Position.x - room.x) / BLOCK(1)); - if (index > room.floor.size()) + long index = ((mesh.pos.Position.z - room.Position.z) / BLOCK(1)) + room.ZSize * ((mesh.pos.Position.x - room.Position.x) / BLOCK(1)); + if (index > room.Sectors.size()) continue; - auto* floor = &room.floor[index]; - if (floor->Box == NO_VALUE) + auto* floor = &room.Sectors[index]; + if (floor->PathfindingBoxID == NO_VALUE) continue; - if (!(g_Level.Boxes[floor->Box].flags & BLOCKED)) + if (!(g_Level.PathfindingBoxes[floor->PathfindingBoxID].flags & BLOCKED)) { int floorHeight = floor->GetSurfaceHeight(mesh.pos.Position.x, mesh.pos.Position.z, true); const auto& bBox = GetBoundsAccurate(mesh, false); @@ -2176,32 +2219,23 @@ bool CanCreatureJump(ItemInfo& item, JumpDistance jumpDistType) if (creature.Enemy == nullptr) return false; - float stepDist = 0.0f; - switch (jumpDistType) - { - default: - case JumpDistance::Block1: - stepDist = BLOCK(0.51f); - break; - - case JumpDistance::Block2: - stepDist = BLOCK(0.76f); - break; - } + float stepDist = BLOCK(0.92f); int vPos = item.Pose.Position.y; - auto pointCollA = GetCollision(&item, item.Pose.Orientation.y, stepDist); - auto pointCollB = GetCollision(&item, item.Pose.Orientation.y, stepDist * 2); - auto pointCollC = GetCollision(&item, item.Pose.Orientation.y, stepDist * 3); + auto pointCollA = GetPointCollision(item, item.Pose.Orientation.y, stepDist); + auto pointCollB = GetPointCollision(item, item.Pose.Orientation.y, stepDist * 2); + auto pointCollC = GetPointCollision(item, item.Pose.Orientation.y, stepDist * 3); switch (jumpDistType) { default: case JumpDistance::Block1: if (item.BoxNumber == creature.Enemy->BoxNumber || - vPos >= (pointCollA.Position.Floor - STEPUP_HEIGHT) || - vPos >= (pointCollB.Position.Floor + CLICK(1)) || - vPos <= (pointCollB.Position.Floor - CLICK(1))) + vPos >= (pointCollA.GetFloorHeight() - STEPUP_HEIGHT) || + vPos >= (pointCollB.GetFloorHeight() + CLICK(1)) || + vPos <= (pointCollB.GetFloorHeight() - CLICK(1)) || + pointCollA.GetSector().PathfindingBoxID == NO_VALUE || + pointCollB.GetSector().PathfindingBoxID == NO_VALUE) { return false; } @@ -2210,10 +2244,13 @@ bool CanCreatureJump(ItemInfo& item, JumpDistance jumpDistType) case JumpDistance::Block2: if (item.BoxNumber == creature.Enemy->BoxNumber || - vPos >= (pointCollA.Position.Floor - STEPUP_HEIGHT) || - vPos >= (pointCollB.Position.Floor - STEPUP_HEIGHT) || - vPos >= (pointCollC.Position.Floor + CLICK(1)) || - vPos <= (pointCollC.Position.Floor - CLICK(1))) + vPos >= (pointCollA.GetFloorHeight() - STEPUP_HEIGHT) || + vPos >= (pointCollB.GetFloorHeight() - STEPUP_HEIGHT) || + vPos >= (pointCollC.GetFloorHeight() + CLICK(1)) || + vPos <= (pointCollC.GetFloorHeight() - CLICK(1)) || + pointCollA.GetSector().PathfindingBoxID == NO_VALUE || + pointCollB.GetSector().PathfindingBoxID == NO_VALUE || + pointCollC.GetSector().PathfindingBoxID == NO_VALUE) { return false; } diff --git a/TombEngine/Game/control/box.h b/TombEngine/Game/control/box.h index 50ceb5142..1152ec78b 100644 --- a/TombEngine/Game/control/box.h +++ b/TombEngine/Game/control/box.h @@ -1,4 +1,5 @@ #pragma once + #include "Specific/level.h" #include "Math/Math.h" @@ -33,12 +34,14 @@ struct AI_INFO short enemyFacing; }; +// TODO: Use DX BoundingBox class to store AABB. struct BOX_INFO { unsigned int left; unsigned int right; unsigned int top; unsigned int bottom; + int height; int overlapIndex; int flags; @@ -52,34 +55,51 @@ struct OVERLAP #define CreatureEffectFunction short(int x, int y, int z, short speed, short yRot, short roomNumber) -constexpr auto BOX_BLOCKED = (1 << 14); // unpassable for other enemies, always set for movable blocks & closed doors -constexpr auto BOX_LAST = (1 << 15); // unpassable by large enemies (T-Rex, Centaur, etc), always set behind doors +// TODO: Following constants can be moved to new flag enums for improved clarity. -constexpr auto REVERSE = 0x4000; constexpr auto BLOCKABLE = 0x8000; -constexpr auto BLOCKED = 0x4000; -constexpr auto SEARCH_NUMBER = 0x7FFF; -constexpr auto BLOCKED_SEARCH = 0x8000; -constexpr auto BOX_JUMP = 0x800; -constexpr auto BOX_MONKEY = 0x2000; +constexpr auto BLOCKED = 0x4000; + +constexpr auto SEARCH_NUMBER = INT_MAX; +constexpr auto SEARCH_BLOCKED = (1 << 31); + +constexpr auto BOX_JUMP = 0x800; +constexpr auto BOX_MONKEY = 0x2000; constexpr auto BOX_END_BIT = 0x8000; -constexpr auto EXPAND_LEFT = 0x1; -constexpr auto EXPAND_RIGHT = 0x2; -constexpr auto EXPAND_TOP = 0x4; + +constexpr auto EXPAND_LEFT = 0x1; +constexpr auto EXPAND_RIGHT = 0x2; +constexpr auto EXPAND_TOP = 0x4; constexpr auto EXPAND_BOTTOM = 0x8; + constexpr auto NO_FLYING = 0; -constexpr auto FLY_ZONE = 0x2000; -constexpr auto CLIP_LEFT = 0x1; -constexpr auto CLIP_RIGHT = 0x2; -constexpr auto CLIP_TOP = 0x4; +constexpr auto FLY_ZONE = 0x2000; + +constexpr auto CLIP_LEFT = 0x1; +constexpr auto CLIP_RIGHT = 0x2; +constexpr auto CLIP_TOP = 0x4; constexpr auto CLIP_BOTTOM = 0x8; -constexpr auto SECONDARY_CLIP = 0x10; -constexpr auto ALL_CLIP = (CLIP_LEFT | CLIP_RIGHT | CLIP_TOP | CLIP_BOTTOM); +constexpr auto CLIP_ALL = (CLIP_LEFT | CLIP_RIGHT | CLIP_TOP | CLIP_BOTTOM); + +constexpr auto CLIP_SECONDARY = 0x10; + +struct AITargetData +{ + ItemInfo FoundItem = {}; + GAME_OBJECT_ID ObjectID = GAME_OBJECT_ID::ID_NO_OBJECT; + float DistanceMax = 0.0f; + int Ocb = NO_VALUE; + + bool CheckDistance = false; + bool CheckSameZone = true; + bool CheckOcb = false; +}; void GetCreatureMood(ItemInfo* item, AI_INFO* AI, bool isViolent); void CreatureMood(ItemInfo* item, AI_INFO* AI, bool isViolent); -void FindAITargetObject(CreatureInfo* creature, int objectNumber); -void FindAITargetObject(CreatureInfo* creature, int objectNumber, int ocb, bool checkSameZone = true); +void FindAITargetObject(ItemInfo& item, GAME_OBJECT_ID objectID, std::optional ocb = std::nullopt, std::optional checkSameZone = std::nullopt); +bool FindAITargetObject(ItemInfo& item, AITargetData& data); + void GetAITarget(CreatureInfo* creature); int CreatureVault(short itemNumber, short angle, int vault, int shift); bool MoveCreature3DPos(Pose* fromPose, Pose* toPose, int velocity, short angleDif, int angleAdd); diff --git a/TombEngine/Game/control/control.cpp b/TombEngine/Game/control/control.cpp index 4b71e240b..43c3bd150 100644 --- a/TombEngine/Game/control/control.cpp +++ b/TombEngine/Game/control/control.cpp @@ -1,12 +1,11 @@ #include "framework.h" #include "Game/control/control.h" -#include #include +#include #include "Game/camera.h" #include "Game/collision/collide_room.h" -#include "Game/collision/sphere.h" #include "Game/control/flipeffect.h" #include "Game/control/lot.h" #include "Game/control/volume.h" @@ -40,6 +39,7 @@ #include "Game/Setup.h" #include "Game/spotcam.h" #include "Math/Math.h" +#include "Objects/Effects/LensFlare.h" #include "Objects/Effects/tr4_locusts.h" #include "Objects/Generic/Object/objects.h" #include "Objects/Generic/Object/rope.h" @@ -76,8 +76,10 @@ using namespace TEN::Effects::Ripple; using namespace TEN::Effects::Smoke; using namespace TEN::Effects::Spark; using namespace TEN::Effects::Streamer; +using namespace TEN::Entities::Creatures::TR3; using namespace TEN::Entities::Generic; using namespace TEN::Entities::Switches; +using namespace TEN::Entities::Traps; using namespace TEN::Entities::TR4; using namespace TEN::Collision::Floordata; using namespace TEN::Control::Volumes; @@ -85,12 +87,14 @@ using namespace TEN::Hud; using namespace TEN::Input; using namespace TEN::Math; using namespace TEN::Renderer; -using namespace TEN::Traps::TR5; using namespace TEN::Entities::Creatures::TR3; +using namespace TEN::Entities::Effects; + +constexpr auto DEATH_NO_INPUT_TIMEOUT = 10 * FPS; +constexpr auto DEATH_INPUT_TIMEOUT = 3 * FPS; int GameTimer = 0; int GlobalCounter = 0; -int Wibble = 0; bool InitializeGame; bool DoTheGame; @@ -113,61 +117,116 @@ short NextFxFree; int ControlPhaseTime; -int DrawPhase(bool isTitle) +void DrawPhase(bool isTitle, float interpolationFactor) { if (isTitle) { - g_Renderer.RenderTitle(); + g_Renderer.RenderTitle(interpolationFactor); } else { - g_Renderer.Render(); + g_Renderer.Render(interpolationFactor); } - // Clear display sprites. - ClearDisplaySprites(); - - Camera.numberFrames = g_Renderer.Synchronize(); - return Camera.numberFrames; + g_Renderer.Lock(); } -GameStatus ControlPhase(int numFrames) +GameStatus ControlPhase(bool insideMenu) { auto time1 = std::chrono::high_resolution_clock::now(); - bool isTitle = (CurrentLevel == 0); + g_Renderer.PrepareScene(); + g_Renderer.SaveOldState(); + + ClearFires(); + ClearLensFlares(); + ClearAllDisplaySprites(); + + SetupInterpolation(); + PrepareCamera(); + RegeneratePickups(); - numFrames = std::clamp(numFrames, 0, 10); - - if (TrackCameraInit) - { - UseSpotCam = false; - AlterFOV(LastFOV); - } - g_GameStringsHandler->ProcessDisplayStrings(DELTA_TIME); - - bool isFirstTime = true; - static int framesCount = 0; - for (framesCount += numFrames; framesCount > 0; framesCount -= LOOP_FRAME_COUNT) + // Controls are polled before OnLoop to allow input data to be overwritten by script API methods. + HandleControls(isTitle); + + // Pre-loop script and event handling. + g_GameScript->OnLoop(DELTA_TIME, false); // TODO: Don't use DELTA_TIME constant with high framerate. + HandleAllGlobalEvents(EventType::Loop, (Activator)LaraItem->Index); + + // Control lock is processed after handling scripts because builder may want to process input externally while locking player from input. + if (!isTitle && Lara.Control.IsLocked) + ClearAllActions(); + + // Item update should happen before camera update, so potential flyby/track camera triggers are processed correctly. + UpdateAllItems(); + UpdateAllEffects(); + UpdateLara(LaraItem, isTitle); + g_GameScriptEntities->TestCollidingObjects(); + + // Smash shatters and clear stopper flags under them. + UpdateShatters(); + + // Clear last selected item in inventory (must be after on loop event handling, so they can detect that). + g_Gui.CancelInventorySelection(); + + // Control lock is processed after handling scripts because builder may want to + // process input externally while locking player from input. + if (!isTitle && Lara.Control.IsLocked) + ClearAllActions(); + + // Update weather. + Weather.Update(); + + // Update effects. + StreamerEffect.Update(); + UpdateWibble(); + UpdateSparks(); + UpdateFireSparks(); + UpdateSmoke(); + UpdateBlood(); + UpdateBubbles(); + UpdateDebris(); + UpdateGunShells(); + UpdateFootprints(); + UpdateSplashes(); + UpdateElectricityArcs(); + UpdateHelicalLasers(); + UpdateDrips(); + UpdateRats(); + UpdateRipples(); + UpdateBats(); + UpdateSpiders(); + UpdateSparkParticles(); + UpdateSmokeParticles(); + UpdateSimpleParticles(); + UpdateExplosionParticles(); + UpdateShockwaves(); + UpdateBeetleSwarm(); + UpdateLocusts(); + UpdateUnderwaterBloodParticles(); + UpdateFishSwarm(); + UpdateGlobalLensFlare(); + + // Update HUD. + g_Hud.Update(*LaraItem); + UpdateFadeScreenAndCinematicBars(); + + // Rumble screen (like in submarine level of TRC). + if (g_GameFlow->GetLevel(CurrentLevel)->Rumble) + RumbleScreen(); + + DoFlipEffect(FlipEffect, LaraItem); + + PlaySoundSources(); + Sound_UpdateScene(); + + // Handle inventory, pause, load, save screens. + if (!insideMenu) { - // Controls are polled before OnLoop, so input data could be - // overwritten by script API methods. - HandleControls(isTitle); - - // Pre-loop script and event handling. - g_GameScript->OnLoop(DELTA_TIME, false); // TODO: Don't use DELTA_TIME constant with variable framerate - HandleAllGlobalEvents(EventType::Loop, (Activator)LaraItem->Index); - - // Control lock is processed after handling scripts, because builder may want to - // process input externally, while still locking Lara from input. - if (!isTitle && Lara.Control.IsLocked) - ClearAllActions(); - - // Handle inventory / pause / load / save screens. auto result = HandleMenuCalls(isTitle); if (result != GameStatus::Normal) return result; @@ -176,124 +235,48 @@ GameStatus ControlPhase(int numFrames) result = HandleGlobalInputEvents(isTitle); if (result != GameStatus::Normal) return result; - - // Queued input actions are read again and cleared after UI - // interrupts are processed, so first frame after exiting UI - // will still register it. - ApplyActionQueue(); - ClearActionQueue(); - - UpdateAllItems(); - UpdateAllEffects(); - UpdateLara(LaraItem, isTitle); - - g_GameScriptEntities->TestCollidingObjects(); - - if (UseSpotCam) - { - // Draw flyby cameras. - CalculateSpotCameras(); - } - else - { - // Do the standard camera. - TrackCameraInit = false; - CalculateCamera(LaraCollision); - } - - // Update oscillator seed. - Wibble = (Wibble + WIBBLE_SPEED) & WIBBLE_MAX; - - // Smash shatters and clear stopper flags under them. - UpdateShatters(); - - // Update weather. - Weather.Update(); - - // Update effects. - StreamerEffect.Update(); - UpdateSparks(); - UpdateFireSparks(); - UpdateSmoke(); - UpdateBlood(); - UpdateBubbles(); - UpdateDebris(); - UpdateGunShells(); - UpdateFootprints(); - UpdateSplashes(); - UpdateElectricityArcs(); - UpdateHelicalLasers(); - UpdateDrips(); - UpdateRats(); - UpdateRipples(); - UpdateBats(); - UpdateSpiders(); - UpdateSparkParticles(); - UpdateSmokeParticles(); - UpdateSimpleParticles(); - UpdateExplosionParticles(); - UpdateShockwaves(); - UpdateBeetleSwarm(); - UpdateFishSwarm(); - UpdateLocusts(); - UpdateUnderwaterBloodParticles(); - - // Update HUD. - g_Hud.Update(*LaraItem); - UpdateFadeScreenAndCinematicBars(); - - // Rumble screen (like in submarine level of TRC). - if (g_GameFlow->GetLevel(CurrentLevel)->Rumble) - RumbleScreen(); - - PlaySoundSources(); - DoFlipEffect(FlipEffect, LaraItem); - - // Post-loop script and event handling. - g_GameScript->OnLoop(DELTA_TIME, true); - - // Clear savegame loaded flag. - JustLoaded = false; - - // Update timers. - GameTimer++; - GlobalCounter++; - - // Add renderer objects on the first processed frame. - if (isFirstTime) - { - g_Renderer.Lock(); - isFirstTime = false; - } } - using ns = std::chrono::nanoseconds; - using get_time = std::chrono::steady_clock; + UpdateCamera(); + + // Post-loop script and event handling. + g_GameScript->OnLoop(DELTA_TIME, true); + + // Clear savegame loaded flag. + JustLoaded = false; + + // Update timers. + GameTimer++; + GlobalCounter++; auto time2 = std::chrono::high_resolution_clock::now(); - ControlPhaseTime = (std::chrono::duration_cast(time2 - time1)).count() / 1000000; + ControlPhaseTime = (std::chrono::duration_cast(time2 - time1)).count() / 1000000; return GameStatus::Normal; } unsigned CALLBACK GameMain(void *) { - TENLog("Starting GameMain...", LogLevel::Info); + TENLog("Starting GameMain()...", LogLevel::Info); TimeInit(); - // Do a fixed time title image. + // Do fixed-time title image. if (g_GameFlow->IntroImagePath.empty()) - TENLog("Intro image path is not set.", LogLevel::Warning); + { + TENLog("Intro image path not set.", LogLevel::Warning); + } else + { g_Renderer.RenderTitleImage(); + } - // Execute the Lua gameflow and play the game. + // Execute Lua gameflow and play game. g_GameFlow->DoFlow(); DoTheGame = false; - // Finish the thread. + // Finish thread. PostMessage(WindowsHandle, WM_CLOSE, NULL, NULL); EndThread(); @@ -309,7 +292,7 @@ GameStatus DoLevel(int levelIndex, bool loadGame) // Load level. Fall back to title if unsuccessful. if (!LoadLevelFile(levelIndex)) - return isTitle ? GameStatus::ExitGame : GameStatus::ExitToTitle; + return (isTitle ? GameStatus::ExitGame : GameStatus::ExitToTitle); // Initialize items, effects, lots, and cameras. HairEffect.Initialize(); @@ -366,11 +349,15 @@ void KillMoveItems() { for (int i = 0; i < ItemNewRoomNo; i++) { - short itemNumber = ItemNewRooms[2 * i]; + int itemNumber = ItemNewRooms[i * 2]; if (itemNumber >= 0) - ItemNewRoom(itemNumber, ItemNewRooms[2 * i + 1]); + { + ItemNewRoom(itemNumber, ItemNewRooms[(i * 2) + 1]); + } else + { KillItem(itemNumber & 0x7FFF); + } } } @@ -383,17 +370,22 @@ void KillMoveEffects() { for (int i = 0; i < ItemNewRoomNo; i++) { - short itemNumber = ItemNewRooms[2 * i]; + int itemNumber = ItemNewRooms[i * 2]; if (itemNumber >= 0) - EffectNewRoom(itemNumber, ItemNewRooms[2 * i + 1]); + { + EffectNewRoom(itemNumber, ItemNewRooms[(i * 2) + 1]); + } else + { KillEffect(itemNumber & 0x7FFF); + } } } ItemNewRoomNo = 0; } +// NOTE: No one should use this ever again. int GetRandomControl() { return Random::GenerateInt(); @@ -409,13 +401,13 @@ void CleanUp() // Reset oscillator seed. Wibble = 0; - // Needs to be cleared, otherwise controls will lock if user exits to title while playing flyby with locked controls. + // Clear player lock, otherwise controls will lock if user exits to title while playing flyby with locked controls. Lara.Control.IsLocked = false; // Resets lightning and wind parameters to avoid holding over previous weather to new level. Weather.Clear(); - // Needs to be cleared, otherwise a list of active creatures from previous level will spill into new level. + // Clear creatures, otherwise list of active creatures from previous level will spill into new level. ActiveCreatures.clear(); // Clear ropes. @@ -429,7 +421,7 @@ void CleanUp() StreamerEffect.Clear(); ClearUnderwaterBloodParticles(); ClearBubbles(); - ClearDisplaySprites(); + ClearAllDisplaySprites(); ClearFootprints(); ClearDrips(); ClearRipples(); @@ -465,26 +457,36 @@ void InitializeScripting(int levelIndex, LevelLoadType type) g_GameStringsHandler->ClearDisplayStrings(); g_GameScript->ResetScripts(!levelIndex || type != LevelLoadType::New); - auto* level = g_GameFlow->GetLevel(levelIndex); + const auto& level = *g_GameFlow->GetLevel(levelIndex); // Run level script if it exists. - if (!level->ScriptFileName.empty()) + if (!level.ScriptFileName.empty()) { - g_GameScript->ExecuteScriptFile(g_GameFlow->GetGameDir() + level->ScriptFileName); + auto levelScriptName = g_GameFlow->GetGameDir() + level.ScriptFileName; + if (std::filesystem::is_regular_file(levelScriptName)) + { + g_GameScript->ExecuteScriptFile(levelScriptName); + } + else + { + TENLog("Level script not found: " + levelScriptName, LogLevel::Warning); + } + g_GameScript->InitCallbacks(); g_GameStringsHandler->SetCallbackDrawString([](const std::string& key, D3DCOLOR color, const Vec2& pos, float scale, int flags) { g_Renderer.AddString( key, - Vector2(((float)pos.x / (float)g_Configuration.ScreenWidth * DISPLAY_SPACE_RES.x), - ((float)pos.y / (float)g_Configuration.ScreenHeight * DISPLAY_SPACE_RES.y)), + Vector2( + (pos.x / g_Configuration.ScreenWidth) * DISPLAY_SPACE_RES.x, + (pos.y / g_Configuration.ScreenHeight) * DISPLAY_SPACE_RES.y), Color(color), scale, flags); }); } // Play default background music. if (type != LevelLoadType::Load) - PlaySoundTrack(level->GetAmbientTrack(), SoundTrackType::BGM); + PlaySoundTrack(level.GetAmbientTrack(), SoundTrackType::BGM); } void DeInitializeScripting(int levelIndex, GameStatus reason) @@ -495,7 +497,7 @@ void DeInitializeScripting(int levelIndex, GameStatus reason) g_GameScript->FreeLevelScripts(); g_GameScriptEntities->FreeEntities(); - if (!levelIndex) + if (levelIndex == 0) g_GameScript->ResetScripts(true); } @@ -504,19 +506,11 @@ void InitializeOrLoadGame(bool loadGame) g_Gui.SetInventoryItemChosen(NO_VALUE); g_Gui.SetEnterInventory(NO_VALUE); - // Restore the game? + // Restore game? if (loadGame) { SaveGame::Load(g_GameFlow->SelectedSaveGame); - Camera.pos.x = LaraItem->Pose.Position.x + 256; - Camera.pos.y = LaraItem->Pose.Position.y + 256; - Camera.pos.z = LaraItem->Pose.Position.z + 256; - - Camera.target.x = LaraItem->Pose.Position.x; - Camera.target.y = LaraItem->Pose.Position.y; - Camera.target.z = LaraItem->Pose.Position.z; - InitializeGame = false; g_GameFlow->SelectedSaveGame = 0; @@ -525,7 +519,7 @@ void InitializeOrLoadGame(bool loadGame) } else { - // If not loading a savegame, clear all info. + // If not loading savegame, clear all info. SaveGame::Statistics.Level = {}; if (InitializeGame) @@ -551,58 +545,47 @@ void InitializeOrLoadGame(bool loadGame) GameStatus DoGameLoop(int levelIndex) { - int numFrames = LOOP_FRAME_COUNT; + int frameCount = LOOP_FRAME_COUNT; auto& status = g_GameFlow->LastGameStatus; - // Before entering actual game loop, ControlPhase must be + // Before entering actual game loop, ControlPhase() must be // called once to sort out various runtime shenanigangs (e.g. hair). - status = ControlPhase(numFrames); + status = ControlPhase(false); + + g_Synchronizer.Init(); + bool legacy30FpsDoneDraw = false; while (DoTheGame) { - status = ControlPhase(numFrames); + g_Synchronizer.Sync(); - if (!levelIndex) + while (g_Synchronizer.Synced()) { - UpdateInputActions(LaraItem); + status = ControlPhase(false); + g_Synchronizer.Step(); - auto invStatus = g_Gui.TitleOptions(LaraItem); + legacy30FpsDoneDraw = false; + } - switch (invStatus) + if (status != GameStatus::Normal) + break; + + if (!g_Configuration.EnableHighFramerate) + { + if (!legacy30FpsDoneDraw) { - case InventoryResult::NewGame: - case InventoryResult::NewGameSelectedLevel: - status = GameStatus::NewGame; - break; - - case InventoryResult::LoadGame: - status = GameStatus::LoadGame; - break; - - case InventoryResult::ExitGame: - status = GameStatus::ExitGame; - break; + DrawPhase(!levelIndex, 0.0f); + legacy30FpsDoneDraw = true; } - - if (invStatus != InventoryResult::None) - break; } else { - if (status == GameStatus::ExitToTitle || - status == GameStatus::LaraDead || - status == GameStatus::LoadGame || - status == GameStatus::LevelComplete) - { - break; - } + DrawPhase(!levelIndex, g_Synchronizer.GetInterpolationFactor()); } - - numFrames = DrawPhase(!levelIndex); - Sound_UpdateScene(); } EndGameLoop(levelIndex, status); + return status; } @@ -616,73 +599,95 @@ void EndGameLoop(int levelIndex, GameStatus reason) StopRumble(); } +void SetupInterpolation() +{ + for (auto& item : g_Level.Items) + item.DisableInterpolation = false; +} + void HandleControls(bool isTitle) { // Poll input devices and update input variables. - if (!isTitle) - { - // TODO: To allow cutscene skipping later, don't clear Deselect action. - UpdateInputActions(LaraItem, true); - } - else - { + // TODO: To allow cutscene skipping later, don't clear Deselect action. + UpdateInputActions(LaraItem, true); + + if (isTitle) ClearAction(In::Look); - } } GameStatus HandleMenuCalls(bool isTitle) { - auto result = GameStatus::Normal; + auto gameStatus = GameStatus::Normal; - if (isTitle || ScreenFading) - return result; + if (ScreenFading) + return gameStatus; - // Does the player want to enter inventory? - if (IsClicked(In::Save) && LaraItem->HitPoints > 0 && - g_Gui.GetInventoryMode() != InventoryMode::Save && - g_GameFlow->IsLoadSaveEnabled()) + if (isTitle) + { + auto invStatus = g_Gui.TitleOptions(LaraItem); + + switch (invStatus) + { + case InventoryResult::NewGame: + case InventoryResult::NewGameSelectedLevel: + return GameStatus::NewGame; + + case InventoryResult::LoadGame: + return GameStatus::LoadGame; + + case InventoryResult::ExitGame: + return GameStatus::ExitGame; + } + + return gameStatus; + } + + bool playerAlive = LaraItem->HitPoints > 0; + + bool doLoad = IsClicked(In::Load) || + (!IsClicked(In::Inventory) && !NoAction() && SaveGame::IsLoadGamePossible() && Lara.Control.Count.Death > DEATH_INPUT_TIMEOUT); + bool doSave = IsClicked(In::Save) && playerAlive; + bool doPause = IsClicked(In::Pause) && playerAlive; + bool doInventory = (IsClicked(In::Inventory) || g_Gui.GetEnterInventory() != NO_VALUE) && playerAlive; + + // Handle inventory. + if (doSave && g_GameFlow->IsLoadSaveEnabled() && g_Gui.GetInventoryMode() != InventoryMode::Save) { SaveGame::LoadHeaders(); g_Gui.SetInventoryMode(InventoryMode::Save); g_Gui.CallInventory(LaraItem, false); } - else if (IsClicked(In::Load) && - g_Gui.GetInventoryMode() != InventoryMode::Load && - g_GameFlow->IsLoadSaveEnabled()) + else if (doLoad && g_GameFlow->IsLoadSaveEnabled() && g_Gui.GetInventoryMode() != InventoryMode::Load) { SaveGame::LoadHeaders(); + g_Gui.SetInventoryMode(InventoryMode::Load); if (g_Gui.CallInventory(LaraItem, false)) - result = GameStatus::LoadGame; + gameStatus = GameStatus::LoadGame; } - else if (IsClicked(In::Pause) && LaraItem->HitPoints > 0 && - g_Gui.GetInventoryMode() != InventoryMode::Pause) + else if (doPause && g_Gui.GetInventoryMode() != InventoryMode::Pause) { if (g_Gui.CallPause()) - result = GameStatus::ExitToTitle; + gameStatus = GameStatus::ExitToTitle; } - else if ((IsClicked(In::Inventory) || g_Gui.GetEnterInventory() != NO_VALUE) && - LaraItem->HitPoints > 0 && !Lara.Control.Look.IsUsingBinoculars) + else if (doInventory && LaraItem->HitPoints > 0 && !Lara.Control.Look.IsUsingBinoculars) { if (g_Gui.CallInventory(LaraItem, true)) - result = GameStatus::LoadGame; + gameStatus = GameStatus::LoadGame; } - if (result != GameStatus::Normal) + if (gameStatus != GameStatus::Normal) { StopAllSounds(); StopRumble(); } - return result; + return gameStatus; } GameStatus HandleGlobalInputEvents(bool isTitle) { - constexpr auto DEATH_NO_INPUT_TIMEOUT = 10 * FPS; - constexpr auto DEATH_INPUT_TIMEOUT = 3 * FPS; - if (isTitle) return GameStatus::Normal; diff --git a/TombEngine/Game/control/control.h b/TombEngine/Game/control/control.h index 45cab275e..646808f27 100644 --- a/TombEngine/Game/control/control.h +++ b/TombEngine/Game/control/control.h @@ -17,6 +17,7 @@ enum class GameStatus { Normal, NewGame, + HomeLevel, LoadGame, SaveGame, ExitToTitle, @@ -49,15 +50,11 @@ enum FadeStatus constexpr int MAX_ROOMS = 1024; -constexpr int WIBBLE_SPEED = 4; -constexpr int WIBBLE_MAX = UCHAR_MAX - WIBBLE_SPEED + 1; - -constexpr int LOOP_FRAME_COUNT = 2; +constexpr auto LOOP_FRAME_COUNT = 2; extern int GameTimer; extern int RumbleTimer; extern int GlobalCounter; -extern int Wibble; extern bool InitializeGame; extern bool DoTheGame; @@ -81,9 +78,9 @@ extern int ControlPhaseTime; extern std::vector OutsideRoomTable[OUTSIDE_SIZE][OUTSIDE_SIZE]; -int DrawPhase(bool isTitle); +void DrawPhase(bool isTitle, float interpolationFactor); -GameStatus ControlPhase(int numFrames); +GameStatus ControlPhase(bool insideMenu); GameStatus DoLevel(int levelIndex, bool loadGame = false); GameStatus DoGameLoop(int levelIndex); void EndGameLoop(int levelIndex, GameStatus reason); @@ -105,4 +102,6 @@ void InitializeOrLoadGame(bool loadGame); void InitializeScripting(int levelIndex, LevelLoadType type); void DeInitializeScripting(int levelIndex); +void SetupInterpolation(); + unsigned CALLBACK GameMain(void*); diff --git a/TombEngine/Game/control/event.h b/TombEngine/Game/control/event.h index 771b60112..2b712efec 100644 --- a/TombEngine/Game/control/event.h +++ b/TombEngine/Game/control/event.h @@ -41,6 +41,7 @@ namespace TEN::Control::Volumes Save, Start, End, + UseItem, Count }; diff --git a/TombEngine/Game/control/flipeffect.cpp b/TombEngine/Game/control/flipeffect.cpp index b128351a8..a47059395 100644 --- a/TombEngine/Game/control/flipeffect.cpp +++ b/TombEngine/Game/control/flipeffect.cpp @@ -94,21 +94,17 @@ void ClearSwarmEnemies(ItemInfo* item) void FlashOrange(ItemInfo* item) { - FlipEffect = -1; + FlipEffect = NO_VALUE; Weather.Flash(255, 128, 0, 0.03f); } void MeshSwapToPour(ItemInfo* item) { - auto* lara = GetLaraInfo(item); - item->Model.MeshIndex[LM_LHAND] = Objects[item->ItemFlags[2]].meshIndex + LM_LHAND; } void MeshSwapFromPour(ItemInfo* item) { - auto* lara = GetLaraInfo(item); - item->Model.MeshIndex[LM_LHAND] = item->Model.BaseMesh + LM_LHAND; } @@ -149,60 +145,60 @@ void InvisibilityOn(ItemInfo* item) void SetFog(ItemInfo* item) { - FlipEffect = -1; + FlipEffect = NO_VALUE; } void DrawLeftPistol(ItemInfo* item) { - auto* lara = GetLaraInfo(item); + auto& player = GetLaraInfo(*item); if (item->Model.MeshIndex[LM_LHAND] == item->Model.BaseMesh + LM_LHAND) { item->Model.MeshIndex[LM_LHAND] = Objects[GetWeaponObjectMeshID(*item, LaraWeaponType::Pistol)].meshIndex + LM_LHAND; - lara->Control.Weapon.HolsterInfo.LeftHolster = HolsterSlot::Empty; + player.Control.Weapon.HolsterInfo.LeftHolster = HolsterSlot::Empty; } else { item->Model.MeshIndex[LM_LHAND] = item->Model.BaseMesh + LM_LHAND; - lara->Control.Weapon.HolsterInfo.LeftHolster = GetWeaponHolsterSlot(LaraWeaponType::Pistol); + player.Control.Weapon.HolsterInfo.LeftHolster = GetWeaponHolsterSlot(LaraWeaponType::Pistol); } } void DrawRightPistol(ItemInfo* item) { - auto* lara = GetLaraInfo(item); + auto& player = GetLaraInfo(*item); if (item->Model.MeshIndex[LM_RHAND] == item->Model.BaseMesh + LM_RHAND) { item->Model.MeshIndex[LM_RHAND] = Objects[GetWeaponObjectMeshID(*item, LaraWeaponType::Pistol)].meshIndex + LM_RHAND; - lara->Control.Weapon.HolsterInfo.RightHolster = HolsterSlot::Empty; + player.Control.Weapon.HolsterInfo.RightHolster = HolsterSlot::Empty; } else { item->Model.MeshIndex[LM_RHAND] = item->Model.BaseMesh + LM_RHAND; - lara->Control.Weapon.HolsterInfo.RightHolster = GetWeaponHolsterSlot(LaraWeaponType::Pistol); + player.Control.Weapon.HolsterInfo.RightHolster = GetWeaponHolsterSlot(LaraWeaponType::Pistol); } } void ShootLeftGun(ItemInfo* item) { - auto* lara = GetLaraInfo(item); + auto& player = GetLaraInfo(*item); - lara->LeftArm.GunFlash = 3; + player.LeftArm.GunFlash = 3; } void ShootRightGun(ItemInfo* item) { - auto* lara = GetLaraInfo(item); + auto& player = GetLaraInfo(*item); - lara->RightArm.GunFlash = 3; + player.RightArm.GunFlash = 3; } void LaraHandsFree(ItemInfo* item) { - auto* lara = GetLaraInfo(item); + auto& player = GetLaraInfo(*item); - lara->Control.HandStatus = HandStatus::Free; + player.Control.HandStatus = HandStatus::Free; } void KillActiveBaddys(ItemInfo* item) @@ -231,14 +227,14 @@ void KillActiveBaddys(ItemInfo* item) } while (itemNumber != NO_VALUE); } - FlipEffect = -1; + FlipEffect = NO_VALUE; } void LaraLocationPad(ItemInfo* item) { auto* lara = GetLaraInfo(item); - FlipEffect = -1; + FlipEffect = NO_VALUE; lara->Location = TriggerTimer; lara->LocationPad = TriggerTimer; @@ -248,7 +244,7 @@ void LaraLocation(ItemInfo* item) { auto* lara = GetLaraInfo(item); - FlipEffect = -1; + FlipEffect = NO_VALUE; lara->Location = TriggerTimer; if (lara->HighestLocation < TriggerTimer) @@ -259,17 +255,19 @@ void ExplosionFX(ItemInfo* item) { SoundEffect(SFX_TR4_EXPLOSION1, nullptr); Camera.bounce = -75; - FlipEffect = -1; + FlipEffect = NO_VALUE; } void SwapCrowbar(ItemInfo* item) { - auto* lara = GetLaraInfo(item); - if (item->Model.MeshIndex[LM_RHAND] == item->Model.BaseMesh + LM_RHAND) + { item->Model.MeshIndex[LM_RHAND] = Objects[ID_LARA_CROWBAR_ANIM].meshIndex + LM_RHAND; + } else + { item->Model.MeshIndex[LM_RHAND] = item->Model.BaseMesh + LM_RHAND; + } } void ActivateKey(ItemInfo* item) @@ -285,7 +283,7 @@ void ActivateCamera(ItemInfo* item) void PoseidonSFX(ItemInfo* item) { SoundEffect(SFX_TR4_WATER_FLUSHES, nullptr); - FlipEffect = -1; + FlipEffect = NO_VALUE; } void RubbleFX(ItemInfo* item) @@ -303,13 +301,13 @@ void RubbleFX(ItemInfo* item) else Camera.bounce = -150; - FlipEffect = -1; + FlipEffect = NO_VALUE; } void PlaySoundEffect(ItemInfo* item) { SoundEffect(TriggerTimer, nullptr); - FlipEffect = -1; + FlipEffect = NO_VALUE; } void FloorShake(ItemInfo* item) @@ -331,6 +329,8 @@ void Turn180(ItemInfo* item) item->Pose.Orientation.x = -item->Pose.Orientation.x; item->Pose.Orientation.y += ANGLE(180.0f); item->Pose.Orientation.z = -item->Pose.Orientation.z; + + item->DisableInterpolation = true; } void FinishLevel(ItemInfo* item) diff --git a/TombEngine/Game/control/flipeffect.h b/TombEngine/Game/control/flipeffect.h index 3c55622da..7e6b981a0 100644 --- a/TombEngine/Game/control/flipeffect.h +++ b/TombEngine/Game/control/flipeffect.h @@ -1,6 +1,7 @@ #pragma once -#include "Game/items.h" + #include "Game/control/control.h" +#include "Game/items.h" #define EffectFunction void(ItemInfo* item) @@ -10,35 +11,35 @@ extern int FlipEffect; extern std::function effect_routines[]; -void AddLeftFootprint(ItemInfo* item); -void AddRightFootprint(ItemInfo* item); +void AddLeftFootprint(ItemInfo* item); // TODO: To anim command. +void AddRightFootprint(ItemInfo* item); // TODO: To anim command. void VoidEffect(ItemInfo* item); void FinishLevel(ItemInfo* item); -void Turn180(ItemInfo* item); +void Turn180(ItemInfo* item); // TODO: To anim command. void FloorShake(ItemInfo* item); void PlaySoundEffect(ItemInfo* item); void RubbleFX(ItemInfo* item); void PoseidonSFX(ItemInfo* item); void ActivateCamera(ItemInfo* item); void ActivateKey(ItemInfo* item); -void SwapCrowbar(ItemInfo* item); +void SwapCrowbar(ItemInfo* item); // TODO: To anim command. void ExplosionFX(ItemInfo* item); void LaraLocation(ItemInfo* item); void LaraLocationPad(ItemInfo* item); void KillActiveBaddys(ItemInfo* item); -void LaraHandsFree(ItemInfo* item); -void ShootRightGun(ItemInfo* item); -void ShootLeftGun(ItemInfo* item); +void LaraHandsFree(ItemInfo* item); // TODO: To anim command. +void ShootRightGun(ItemInfo* item); // TODO: To anim command. +void ShootLeftGun(ItemInfo* item); // TODO: To anim command. void SetFog(ItemInfo* item); void InvisibilityOn(ItemInfo* item); void InvisibilityOff(ItemInfo* item); void ResetHair(ItemInfo* item); -void Pickup(ItemInfo* item); -void Puzzle(ItemInfo* item); -void DrawRightPistol(ItemInfo* item); -void DrawLeftPistol(ItemInfo* item); -void MeshSwapToPour(ItemInfo* item); -void MeshSwapFromPour(ItemInfo* item); +void Pickup(ItemInfo* item); // TODO: To anim command. +void Puzzle(ItemInfo* item); // TODO: To anim command. +void DrawRightPistol(ItemInfo* item); // TODO: To anim command. +void DrawLeftPistol(ItemInfo* item); // TODO: To anim command. +void MeshSwapToPour(ItemInfo* item); // TODO: To anim command. +void MeshSwapFromPour(ItemInfo* item); // TODO: To anim command. void FlashOrange(ItemInfo* item); void ClearSwarmEnemies(ItemInfo* item); diff --git a/TombEngine/Game/control/los.cpp b/TombEngine/Game/control/los.cpp index 2c0ea4ec6..003d04b30 100644 --- a/TombEngine/Game/control/los.cpp +++ b/TombEngine/Game/control/los.cpp @@ -3,6 +3,7 @@ #include "Game/animation.h" #include "Game/collision/collide_room.h" +#include "Game/collision/Sphere.h" #include "Game/effects/tomb4fx.h" #include "Game/effects/debris.h" #include "Game/items.h" @@ -18,6 +19,7 @@ #include "Sound/sound.h" #include "Specific/Input/Input.h" +using namespace TEN::Collision::Sphere; using namespace TEN::Math; using TEN::Renderer::g_Renderer; @@ -308,9 +310,9 @@ bool GetTargetOnLOS(GameVector* origin, GameVector* target, bool drawTarget, boo { item->MeshBits &= ~ShatterItem.bit; ShatterImpactData.impactDirection = dir; - ShatterImpactData.impactLocation = Vector3(ShatterItem.sphere.x, ShatterItem.sphere.y, ShatterItem.sphere.z); + ShatterImpactData.impactLocation = ShatterItem.sphere.Center; ShatterObject(&ShatterItem, 0, 128, target2.RoomNumber, 0); - TriggerRicochetSpark(target2, LaraItem->Pose.Orientation.y, 3, 0); + TriggerRicochetSpark(target2, LaraItem->Pose.Orientation.y, 3, 0); } else { @@ -323,20 +325,19 @@ bool GetTargetOnLOS(GameVector* origin, GameVector* target, bool drawTarget, boo { const auto& weapon = Weapons[(int)Lara.Control.Weapon.GunType]; - int num = GetSpheres(item, CreatureSpheres, SPHERES_SPACE_WORLD, Matrix::Identity); + auto spheres = item->GetSpheres(); auto ray = Ray(origin->ToVector3(), dir); float bestDistance = INFINITY; int bestJointIndex = NO_VALUE; - for (int i = 0; i < num; i++) + for (int i = 0; i < spheres.size(); i++) { - auto sphere = BoundingSphere(Vector3(CreatureSpheres[i].x, CreatureSpheres[i].y, CreatureSpheres[i].z), CreatureSpheres[i].r); - float distance = 0.0f; - if (ray.Intersects(sphere, distance)) + float dist = 0.0f; + if (ray.Intersects(spheres[i], dist)) { - if (distance < bestDistance) + if (dist < bestDistance) { - bestDistance = distance; + bestDistance = dist; bestJointIndex = i; } } @@ -500,106 +501,31 @@ static bool DoRayBox(const GameVector& origin, const GameVector& target, const G auto* item = &g_Level.Items[closestItemNumber]; auto* object = &Objects[item->ObjectNumber]; - // Get transformed mesh sphere. - GetSpheres(item, CreatureSpheres, SPHERES_SPACE_WORLD, Matrix::Identity); - SPHERE spheres[34]; - memcpy(spheres, CreatureSpheres, sizeof(SPHERE) * 34); - if (object->nmeshes <= 0) return false; meshIndex = object->meshIndex; + auto spheres = item->GetSpheres(); for (int i = 0; i < object->nmeshes; i++) { // If mesh is visible. if (item->MeshBits & (1 << i)) { - auto* sphere = &CreatureSpheres[i]; + float distance; + const auto& sphere = spheres[i]; - // TODO: this approach is the correct one but, again, Core's math is a mystery and this test was meant - // to fail deliberately in some way. I've so added again Core's legacy test for allowing the current game logic - // but after more testing we should trash it in the future and restore the new way. - -#if 0 - // Create the bounding sphere and test it against the ray - BoundingSphere sph = BoundingSphere(Vector3(sphere->x, sphere->y, sphere->z), sphere->r); - float newDist; - if (sph.Intersects(rayStart, rayDirNormalized, newDist)) + if (sphere.Intersects(rayOrigin, rayDir, distance)) { - // HACK: Core seems to take in account for distance not the real hit point but the centre of the sphere. - // This can work well for example for GUARDIAN because the head sphere is so big that would always be hit - // and eyes would not be destroyed. - newDist = sqrt(SQUARE(sphere->x - start->x) + SQUARE(sphere->y - start->y) + SQUARE(sphere->z - start->z)); - - // Test for min distance - if (newDist < minDistance) + // Test for minimum distance. + if (distance < minDist) { - minDistance = newDist; - meshPtr = &g_Level.Meshes[obj->meshIndex + i]; + minDist = distance; + meshIndex = object->meshIndex + i; bit = 1 << i; sp = i; } } -#endif - - Vector3i p[4]; - - p[1].x = origin.x; - p[1].y = origin.y; - p[1].z = origin.z; - p[2].x = target.x; - p[2].y = target.y; - p[2].z = target.z; - p[3].x = sphere->x; - p[3].y = sphere->y; - p[3].z = sphere->z; - - int r0 = (p[3].x - p[1].x) * (p[2].x - p[1].x) + - (p[3].y - p[1].y) * (p[2].y - p[1].y) + - (p[3].z - p[1].z) * (p[2].z - p[1].z); - - int r1 = SQUARE(p[2].x - p[1].x) + - SQUARE(p[2].y - p[1].y) + - SQUARE(p[2].z - p[1].z); - - if (((r0 < 0 && r1 < 0) || - (r1 > 0 && r0 > 0)) && - (abs(r0) <= abs(r1))) - { - r1 >>= 16; - if (r1) - r0 /= r1; - else - r0 = 0; - - p[0].x = p[1].x + ((r0 * (p[2].x - p[1].x)) >> 16); - p[0].y = p[1].y + ((r0 * (p[2].y - p[1].y)) >> 16); - p[0].z = p[1].z + ((r0 * (p[2].z - p[1].z)) >> 16); - - int dx = SQUARE(p[0].x - p[3].x); - int dy = SQUARE(p[0].y - p[3].y); - int dz = SQUARE(p[0].z - p[3].z); - - int distance = dx + dy + dz; - - if (distance < SQUARE(sphere->r)) - { - dx = SQUARE(sphere->x - origin.x); - dy = SQUARE(sphere->y - origin.y); - dz = SQUARE(sphere->z - origin.z); - - distance = dx + dy + dz; - - if (distance < minDist) - { - minDist = distance; - meshIndex = object->meshIndex + i; - bit = 1 << i; - sp = i; - } - } - } } } @@ -622,14 +548,12 @@ static bool DoRayBox(const GameVector& origin, const GameVector& target, const G { auto* item = &g_Level.Items[closestItemNumber]; - GetSpheres(item, CreatureSpheres, SPHERES_SPACE_WORLD | SPHERES_SPACE_BONE_ORIGIN, Matrix::Identity); + auto spheres = item->GetSpheres(); ShatterItem.yRot = item->Pose.Orientation.y; ShatterItem.meshIndex = meshIndex; ShatterItem.color = item->Model.Color; - ShatterItem.sphere.x = CreatureSpheres[sp].x; - ShatterItem.sphere.y = CreatureSpheres[sp].y; - ShatterItem.sphere.z = CreatureSpheres[sp].z; + ShatterItem.sphere.Center = spheres[sp].Center; ShatterItem.bit = bit; ShatterItem.flags = 0; } @@ -784,7 +708,7 @@ std::optional GetStaticObjectLos(const Vector3& origin, int roomNumber, { // Run through neighbor rooms. const auto& room = g_Level.Rooms[roomNumber]; - for (int neighborRoomNumber : room.neighbors) + for (int neighborRoomNumber : room.NeighborRoomNumbers) { // Get neighbor room. const auto& neighborRoom = g_Level.Rooms[neighborRoomNumber]; diff --git a/TombEngine/Game/control/lot.cpp b/TombEngine/Game/control/lot.cpp index 22218b376..b1168618d 100644 --- a/TombEngine/Game/control/lot.cpp +++ b/TombEngine/Game/control/lot.cpp @@ -9,6 +9,9 @@ #include "Game/Lara/lara.h" #include "Game/Setup.h" #include "Specific/level.h" +#include "Specific/trutils.h" + +using namespace TEN::Collision::Room; #define DEFAULT_FLY_UPDOWN_SPEED 16 #define DEFAULT_SWIM_UPDOWN_SPEED 32 @@ -23,7 +26,7 @@ void InitializeLOTarray(int itemNumber) if (!creature->LOT.Initialized) { - creature->LOT.Node = std::vector(g_Level.Boxes.size(), BoxNode{}); + creature->LOT.Node = std::vector(g_Level.PathfindingBoxes.size(), BoxNode{}); creature->LOT.Initialized = true; } } @@ -194,6 +197,13 @@ void InitializeSlot(short itemNumber, bool makeTarget) creature->LOT.Drop = -BLOCK(1); creature->LOT.Zone = ZoneType::Human; break; + + case LotType::EnemyJeep: + creature->LOT.Step = BLOCK(4); + creature->LOT.Drop = -BLOCK(4); + creature->LOT.CanJump = true; + creature->LOT.Zone = ZoneType::Human; + break; } ClearLOT(&creature->LOT); @@ -203,6 +213,41 @@ void InitializeSlot(short itemNumber, bool makeTarget) SlotsUsed++; } +void TargetNearestEntity(ItemInfo& item, const std::vector& keyObjectIds, bool ignoreKeyObjectIds) +{ + auto& creature = *GetCreatureInfo(&item); + + float closestDistSqr = INFINITY; + for (auto& target : ActiveCreatures) + { + auto& targetItem = g_Level.Items[target->ItemNumber]; + if (targetItem.Index == item.Index) + continue; + + // Ignore or specifically target key object IDs. + if (!keyObjectIds.empty() && (ignoreKeyObjectIds ? Contains(keyObjectIds, targetItem.ObjectNumber) : !Contains(keyObjectIds, targetItem.ObjectNumber))) + continue; + + if (&targetItem != &item && targetItem.HitPoints > 0 && targetItem.Status != ITEM_INVISIBLE) + { + float distSqr = Vector3i::DistanceSquared(item.Pose.Position, targetItem.Pose.Position); + if (distSqr < closestDistSqr) + { + creature.Enemy = &targetItem; + closestDistSqr = distSqr; + } + } + } + + // Handle player as special case. + if (!keyObjectIds.empty() && (ignoreKeyObjectIds ? Contains(keyObjectIds, ID_LARA) : !Contains(keyObjectIds, ID_LARA))) + return; + + float distToPlayerSqr = Vector3i::DistanceSquared(item.Pose.Position, LaraItem->Pose.Position); + if (distToPlayerSqr < closestDistSqr) + creature.Enemy = LaraItem; +} + void SetEntityTarget(short itemNum, short target) { auto* item = &g_Level.Items[itemNum]; @@ -238,14 +283,14 @@ void CreateZone(ItemInfo* item) auto* creature = GetCreatureInfo(item); auto* room = &g_Level.Rooms[item->RoomNumber]; - item->BoxNumber = GetSector(room, item->Pose.Position.x - room->x, item->Pose.Position.z - room->z)->Box; + item->BoxNumber = GetSector(room, item->Pose.Position.x - room->Position.x, item->Pose.Position.z - room->Position.z)->PathfindingBoxID; if (creature->LOT.Fly) { auto* node = creature->LOT.Node.data(); creature->LOT.ZoneCount = 0; - for (int i = 0; i < g_Level.Boxes.size(); i++) + for (int i = 0; i < g_Level.PathfindingBoxes.size(); i++) { node->boxNumber = i; node++; @@ -263,7 +308,7 @@ void CreateZone(ItemInfo* item) auto* node = creature->LOT.Node.data(); creature->LOT.ZoneCount = 0; - for (int i = 0; i < g_Level.Boxes.size(); i++) + for (int i = 0; i < g_Level.PathfindingBoxes.size(); i++) { if (*zone == zoneNumber || *flippedZone == flippedZoneNumber) { diff --git a/TombEngine/Game/control/lot.h b/TombEngine/Game/control/lot.h index 273b3bfc8..251adea17 100644 --- a/TombEngine/Game/control/lot.h +++ b/TombEngine/Game/control/lot.h @@ -7,6 +7,7 @@ void InitializeLOTarray(int allocMem); bool EnableEntityAI(short itemNum, bool always, bool makeTarget = true); void InitializeSlot(short itemNum, bool makeTarget); void SetEntityTarget(short itemNum, short target); +void TargetNearestEntity(ItemInfo& item, const std::vector& keyObjectIds = {}, bool ignoreKeyObjectIds = true); void DisableEntityAI(short itemNumber); void ClearLOT(LOTInfo* LOT); void CreateZone(ItemInfo* item); diff --git a/TombEngine/Game/control/trigger.cpp b/TombEngine/Game/control/trigger.cpp index 5b03b8f2a..adbe0b74c 100644 --- a/TombEngine/Game/control/trigger.cpp +++ b/TombEngine/Game/control/trigger.cpp @@ -3,6 +3,7 @@ #include "Game/camera.h" #include "Game/collision/floordata.h" +#include "Game/collision/Point.h" #include "Game/control/flipeffect.h" #include "Game/control/box.h" #include "Game/control/lot.h" @@ -24,6 +25,7 @@ #include "Sound/sound.h" #include "Specific/clock.h" +using namespace TEN::Collision::Point; using namespace TEN::Effects::Items; using namespace TEN::Entities::Switches; @@ -114,7 +116,7 @@ int GetSwitchTrigger(ItemInfo* item, short* itemNumbersPtr, int attatchedToSwitc return k; } -int SwitchTrigger(short itemNumber, short timer) +bool SwitchTrigger(short itemNumber, short timer) { auto& item = g_Level.Items[itemNumber]; const auto& player = Lara; @@ -127,7 +129,7 @@ int SwitchTrigger(short itemNumber, short timer) item.Status = ITEM_ACTIVE; item.ItemFlags[1] = false; - return 1; + return true; } if (item.ObjectNumber >= ID_PUZZLE_HOLE1 && item.ObjectNumber <= ID_PUZZLE_HOLE16 && @@ -137,13 +139,13 @@ int SwitchTrigger(short itemNumber, short timer) item.Status = ITEM_DEACTIVATED; item.ItemFlags[1] = false; - return 1; + return true; } if ((item.ObjectNumber >= ID_PUZZLE_DONE1 && item.ObjectNumber <= ID_PUZZLE_DONE16) || (item.ObjectNumber >= ID_PUZZLE_HOLE1 && item.ObjectNumber <= ID_PUZZLE_HOLE16)) { - return 0; + return false; } // Handle reusable receptacles. @@ -156,7 +158,7 @@ int SwitchTrigger(short itemNumber, short timer) item.Status = ITEM_ACTIVE; item.ItemFlags[5] = (int)ReusableReceptacleState::Done; item.ItemFlags[1] = false; - return 1; + return true; } if (item.ObjectNumber >= ID_KEY_HOLE1 && item.ObjectNumber <= ID_KEY_HOLE16 && @@ -167,11 +169,11 @@ int SwitchTrigger(short itemNumber, short timer) item.Status = ITEM_DEACTIVATED; item.ItemFlags[5] = (int)ReusableReceptacleState::Empty; item.ItemFlags[1] = false; - return 1; + return true; } if (item.ObjectNumber >= ID_KEY_HOLE1 && item.ObjectNumber <= ID_KEY_HOLE16) - return 0; + return false; // Handle switches. if (item.Status == ITEM_DEACTIVATED) @@ -186,7 +188,7 @@ int SwitchTrigger(short itemNumber, short timer) if (timer != 1) item.Timer = FPS * timer; - return 1; + return true; } if (item.TriggerFlags >= 0 || item.Animation.ActiveState != SWITCH_OFF) @@ -197,12 +199,12 @@ int SwitchTrigger(short itemNumber, short timer) if (!item.ItemFlags[0] == 0) item.Flags |= ONESHOT; - return 1; + return true; } else { item.Status = ITEM_ACTIVE; - return 1; + return true; } } else if (item.Status != ITEM_NOT_ACTIVE) @@ -211,13 +213,14 @@ int SwitchTrigger(short itemNumber, short timer) item.Animation.AnimNumber == GetAnimIndex(item, 2) && item.Animation.FrameNumber == GetFrameIndex(&item, 0)) { - return 1; + return true; } - return ((item.Flags & ONESHOT) >> 8); + if (item.Flags & ONESHOT) + return true; } - return 0; + return false; } int KeyTrigger(short itemNumber) @@ -312,12 +315,12 @@ void RefreshCamera(short type, short* data) short* GetTriggerIndex(FloorInfo* floor, int x, int y, int z) { - auto bottomBlock = GetCollision(x, y, z, floor->RoomNumber).BottomBlock; + const auto& bottomSector = GetPointCollision(Vector3i(x, y, z), floor->RoomNumber).GetBottomSector(); - if (bottomBlock->TriggerIndex == NO_VALUE) + if (bottomSector.TriggerIndex == NO_VALUE) return nullptr; - return &g_Level.FloorData[bottomBlock->TriggerIndex]; + return &g_Level.FloorData[bottomSector.TriggerIndex]; } short* GetTriggerIndex(ItemInfo* item) @@ -330,35 +333,50 @@ short* GetTriggerIndex(ItemInfo* item) void Antitrigger(short const value, short const flags) { ItemInfo* item = &g_Level.Items[value]; - if (item->ObjectNumber == ID_EARTHQUAKE) + + if (item->Flags & IFLAG_KILLED) + return; + + if (item->ObjectNumber == ID_EARTHQUAKE) // HACK: move to earthquake control function! { item->ItemFlags[0] = 0; item->ItemFlags[1] = 100; } - item->Flags &= ~(CODE_BITS | REVERSE); + item->Flags &= ~(CODE_BITS | IFLAG_REVERSE); if (flags & ONESHOT) item->Flags |= ATONESHOT; - if (item->Active && Objects[item->ObjectNumber].intelligent) + if (Objects[item->ObjectNumber].intelligent) { - DisableEntityAI(value); - RemoveActiveItem(value, false); - item->Active = false; - item->Status = ITEM_INVISIBLE; + if (item->Active) + { + DisableEntityAI(value); + RemoveActiveItem(value, false); + item->Status = ITEM_INVISIBLE; + } + } + else + { + item->Status = ITEM_DEACTIVATED; } } void Trigger(short const value, short const flags) { ItemInfo* item = &g_Level.Items[value]; + + if (item->Flags & IFLAG_KILLED) + return; + + item->TouchBits = NO_JOINT_BITS; item->Flags |= TRIGGERED; if (flags & ONESHOT) item->Flags |= ONESHOT; - if (!(item->Active) && !(item->Flags & IFLAG_KILLED)) + if (!item->Active) { if (Objects[item->ObjectNumber].intelligent) { @@ -366,47 +384,41 @@ void Trigger(short const value, short const flags) { if (item->Status == ITEM_INVISIBLE) { - item->TouchBits = NO_JOINT_BITS; if (EnableEntityAI(value, false)) { - item->Status = ITEM_ACTIVE; AddActiveItem(value); } else { item->Status = ITEM_INVISIBLE; AddActiveItem(value); + return; } } } else { - item->TouchBits = NO_JOINT_BITS; - item->Status = ITEM_ACTIVE; AddActiveItem(value); EnableEntityAI(value, true); } } else { - item->TouchBits = NO_JOINT_BITS; AddActiveItem(value); - item->Status = ITEM_ACTIVE; } + + item->Status = ITEM_ACTIVE; + item->DisableInterpolation = true; } } void TestTriggers(int x, int y, int z, FloorInfo* floor, Activator activator, bool heavy, int heavyFlags) { - int flip = -1; - int flipAvailable = 0; - int newEffect = -1; - int switchOff = 0; - //int switchFlag = 0; - short objectNumber = 0; + bool switchOff = false; + bool flipAvailable = false; + int flip = NO_VALUE; + int newEffect = NO_VALUE; int keyResult = 0; - short cameraFlags = 0; - short cameraTimer = 0; int spotCamIndex = 0; auto data = GetTriggerIndex(floor, x, y, z); @@ -466,13 +478,7 @@ void TestTriggers(int x, int y, int z, FloorInfo* floor, Activator activator, bo if (!SwitchTrigger(value, timer)) return; - objectNumber = g_Level.Items[value].ObjectNumber; - //This disables the antitrigger of the Valve switch (ocb 5). I don't know the purpose of this in TR4. - //if (objectNumber >= ID_SWITCH_TYPE1 && objectNumber <= ID_SWITCH_TYPE6 && g_Level.Items[value].TriggerFlags == 5) - //switchFlag = 1; - - switchOff = (g_Level.Items[value].Animation.ActiveState == 1); - + switchOff = (triggerType == TRIGGER_TYPES::SWITCH && timer && g_Level.Items[value].Animation.ActiveState == 1); break; case TRIGGER_TYPES::MONKEY: @@ -517,8 +523,8 @@ void TestTriggers(int x, int y, int z, FloorInfo* floor, Activator activator, bo case TRIGGER_TYPES::PAD: case TRIGGER_TYPES::ANTIPAD: { - auto pointColl = GetCollision(floor, x, y, z); - if (pointColl.Position.Floor == y && pointColl.Position.Bridge == NO_VALUE) + auto pointColl = GetPointCollision(Vector3i(x, y, z), floor->RoomNumber); + if (pointColl.GetFloorHeight() == y && pointColl.GetFloorBridgeItemNumber() == NO_VALUE) break; } return; @@ -571,8 +577,8 @@ void TestTriggers(int x, int y, int z, FloorInfo* floor, Activator activator, bo if (keyResult >= 2 || (triggerType == TRIGGER_TYPES::ANTIPAD || - triggerType == TRIGGER_TYPES::ANTITRIGGER || - triggerType == TRIGGER_TYPES::HEAVYANTITRIGGER) && + triggerType == TRIGGER_TYPES::ANTITRIGGER || + triggerType == TRIGGER_TYPES::HEAVYANTITRIGGER) && item->Flags & ATONESHOT) break; @@ -656,7 +662,7 @@ void TestTriggers(int x, int y, int z, FloorInfo* floor, Activator activator, bo if (triggerType == TRIGGER_TYPES::COMBAT) break; - if (triggerType == TRIGGER_TYPES::SWITCH && timer && switchOff) + if (switchOff) break; if (Camera.number != Camera.last || triggerType == TRIGGER_TYPES::SWITCH) @@ -674,6 +680,9 @@ void TestTriggers(int x, int y, int z, FloorInfo* floor, Activator activator, bo if (keyResult == 1) break; + if (switchOff) + break; + if (triggerType == TRIGGER_TYPES::ANTIPAD || triggerType == TRIGGER_TYPES::ANTITRIGGER || triggerType == TRIGGER_TYPES::HEAVYANTITRIGGER) @@ -748,17 +757,26 @@ void TestTriggers(int x, int y, int z, FloorInfo* floor, Activator activator, bo break; case TO_FLIPEFFECT: + if (switchOff) + break; + TriggerTimer = timer; newEffect = value; break; case TO_FINISH: + if (switchOff) + break; + NextLevel = value ? value : (CurrentLevel + 1); RequiredStartPos = timer; break; case TO_CD: - PlaySoundTrack(value, flags); + if (switchOff) + break; + + PlaySoundTrack(value, flags & CODE_BITS); break; case TO_CUTSCENE: @@ -766,6 +784,9 @@ void TestTriggers(int x, int y, int z, FloorInfo* floor, Activator activator, bo break; case TO_SECRET: + if (switchOff) + break; + if (!(SaveGame::Statistics.Level.Secrets & (1 << value))) { PlaySecretTrack(); @@ -777,6 +798,8 @@ void TestTriggers(int x, int y, int z, FloorInfo* floor, Activator activator, bo case TO_VOLUMEEVENT: case TO_GLOBALEVENT: trigger = *(data++); + + if (!switchOff) { auto& list = targetType == TO_VOLUMEEVENT ? g_Level.VolumeEventSets : g_Level.GlobalEventSets; @@ -815,10 +838,10 @@ void TestTriggers(int x, int y, int z, FloorInfo* floor, Activator activator, bo if (cameraItem && (Camera.type == CameraType::Fixed || Camera.type == CameraType::Heavy)) Camera.item = cameraItem; - if (flip != -1) + if (flip != NO_VALUE) DoFlipMap(flip); - if (newEffect != -1 && (flip || !flipAvailable)) + if (newEffect != NO_VALUE && (flip || !flipAvailable)) FlipEffect = newEffect; } @@ -844,11 +867,12 @@ void TestTriggers(int x, int y, int z, short roomNumber, bool heavy, int heavyFl void ProcessSectorFlags(ItemInfo* item) { - auto pointColl = GetCollision(item); - auto& sector = *GetCollision(item).BottomBlock; - bool isPlayer = item->IsLara(); + // HACK: because of L-shaped portal configurations, we need to fetch room number from Location struct for player. + auto pointColl = isPlayer ? GetPointCollision(item->Pose.Position, item->Location.RoomNumber) : GetPointCollision(*item); + auto& sector = pointColl.GetBottomSector(); + // Set monkeyswing and wall climb statuses for player. if (isPlayer) { @@ -869,7 +893,7 @@ void ProcessSectorFlags(ItemInfo* item) } // Burn or drown item. - if (sector.Flags.Death && item->Pose.Position.y == item->Floor && pointColl.Position.Bridge == NO_VALUE) + if (sector.Flags.Death && item->Pose.Position.y == item->Floor && pointColl.GetFloorBridgeItemNumber() == NO_VALUE) { if (isPlayer) { @@ -879,7 +903,7 @@ void ProcessSectorFlags(ItemInfo* item) player.Control.WaterStatus != WaterStatus::Dry) { // Check floor material. - auto material = sector.GetSurfaceMaterial(pointColl.Coordinates.x, pointColl.Coordinates.z, true); + auto material = sector.GetSurfaceMaterial(pointColl.GetPosition().x, pointColl.GetPosition().z, true); if (material == MaterialType::Water && Objects[ID_KAYAK_LARA_ANIMS].loaded) // HACK: Allow both lava and rapids in same level. { KayakLaraRapidsDrown(item); @@ -892,7 +916,7 @@ void ProcessSectorFlags(ItemInfo* item) } else if (Objects[item->ObjectNumber].intelligent && item->HitPoints != NOT_TARGETABLE) { - auto material = sector.GetSurfaceMaterial(pointColl.Coordinates.x, pointColl.Coordinates.z, true); + auto material = sector.GetSurfaceMaterial(pointColl.GetPosition().x, pointColl.GetPosition().z, true); if (material == MaterialType::Water || TestEnvironment(RoomEnvFlags::ENV_FLAG_WATER, sector.RoomNumber)) { // TODO: Implement correct rapids behaviour for other objects. diff --git a/TombEngine/Game/control/trigger.h b/TombEngine/Game/control/trigger.h index fb60974fa..f1b3e2668 100644 --- a/TombEngine/Game/control/trigger.h +++ b/TombEngine/Game/control/trigger.h @@ -63,7 +63,7 @@ extern int KeyTriggerActive; bool GetKeyTrigger(ItemInfo* item); int GetSwitchTrigger(ItemInfo* item, short* itemNumbersPtr, int attatchedToSwitch); -int SwitchTrigger(short itemNumber, short timer); +bool SwitchTrigger(short itemNumber, short timer); int KeyTrigger(short itemNumber); bool PickupTrigger(short itemNumber); void RefreshCamera(short type, short* data); diff --git a/TombEngine/Game/control/volume.cpp b/TombEngine/Game/control/volume.cpp index a15f4ad2a..5968dd206 100644 --- a/TombEngine/Game/control/volume.cpp +++ b/TombEngine/Game/control/volume.cpp @@ -10,12 +10,8 @@ #include "Game/room.h" #include "Game/savegame.h" #include "Game/Setup.h" -#include "Renderer/Renderer.h" -#include "Renderer/RendererEnums.h" #include "Scripting/Include/ScriptInterfaceGame.h" -using TEN::Renderer::g_Renderer; - namespace TEN::Control::Volumes { constexpr auto CAM_SIZE = 32; @@ -30,7 +26,7 @@ namespace TEN::Control::Volumes case VolumeType::Box: if (roomNumber == Camera.pos.RoomNumber) { - g_Renderer.AddDebugBox(volume.Box, + DrawDebugBox(volume.Box, Vector4(color, 0.0f, color, 1.0f), RendererDebugPage::CollisionStats); } return volume.Box.Intersects(box); @@ -38,7 +34,7 @@ namespace TEN::Control::Volumes case VolumeType::Sphere: if (roomNumber == Camera.pos.RoomNumber) { - g_Renderer.AddDebugSphere(volume.Sphere.Center, volume.Sphere.Radius, + DrawDebugSphere(volume.Sphere.Center, volume.Sphere.Radius, Vector4(color, 0.0f, color, 1.0f), RendererDebugPage::CollisionStats); } return volume.Sphere.Intersects(box); @@ -49,7 +45,7 @@ namespace TEN::Control::Volumes } } - BoundingOrientedBox ConstructRoughBox(ItemInfo& item, const CollisionSetup& coll) + BoundingOrientedBox ConstructRoughBox(ItemInfo& item, const CollisionSetupData& coll) { auto pBounds = GameBoundingBox(&item).ToBoundingOrientedBox(item.Pose); auto pos = Vector3(item.Pose.Position.x, pBounds.Center.y, item.Pose.Position.z); @@ -89,14 +85,16 @@ namespace TEN::Control::Volumes return nullptr; } - void HandleEvent(Event& event, Activator& activator) + bool HandleEvent(Event& event, Activator& activator) { if (event.Function.empty() || event.CallCounter == 0 || event.CallCounter < NO_CALL_COUNTER) - return; + return false; g_GameScript->ExecuteFunction(event.Function, activator, event.Data); if (event.CallCounter != NO_CALL_COUNTER) event.CallCounter--; + + return true; } bool HandleEvent(const std::string& name, EventType eventType, Activator activator) @@ -141,14 +139,14 @@ namespace TEN::Control::Volumes if (roomNumber == NO_VALUE) return; - for (int currentRoomIndex : g_Level.Rooms[roomNumber].neighbors) + for (int currentRoomIndex : g_Level.Rooms[roomNumber].NeighborRoomNumbers) { auto& room = g_Level.Rooms[currentRoomIndex]; if (!room.Active()) continue; - for (auto& volume : room.triggerVolumes) + for (auto& volume : room.TriggerVolumes) { if (!volume.Enabled) continue; @@ -244,13 +242,13 @@ namespace TEN::Control::Volumes TestVolumes(roomNumber, box, ActivatorFlags::Static, mesh); } - void TestVolumes(short itemNumber, const CollisionSetup* coll) + void TestVolumes(short itemNumber, const CollisionSetupData* coll) { auto& item = g_Level.Items[itemNumber]; auto box = (coll != nullptr) ? ConstructRoughBox(item, *coll) : GameBoundingBox(&item).ToBoundingOrientedBox(item.Pose); - g_Renderer.AddDebugBox(box, Vector4(1.0f, 1.0f, 0.0f, 1.0f), RendererDebugPage::CollisionStats); + DrawDebugBox(box, Vector4(1.0f, 1.0f, 0.0f, 1.0f), RendererDebugPage::CollisionStats); if (item.IsLara() || item.Index == Lara.Context.Vehicle) { diff --git a/TombEngine/Game/control/volume.h b/TombEngine/Game/control/volume.h index 9d9fc6a84..5d514ae8d 100644 --- a/TombEngine/Game/control/volume.h +++ b/TombEngine/Game/control/volume.h @@ -4,7 +4,7 @@ #include "Game/Setup.h" #include "Renderer/Renderer.h" -struct CollisionSetup; +struct CollisionSetupData; namespace TEN::Control::Volumes { @@ -39,11 +39,11 @@ namespace TEN::Control::Volumes }; void TestVolumes(short roomNumber, const BoundingOrientedBox& box, ActivatorFlags activatorFlag, Activator activator); - void TestVolumes(short itemNumber, const CollisionSetup* coll = nullptr); + void TestVolumes(short itemNumber, const CollisionSetupData* coll = nullptr); void TestVolumes(short roomNumber, MESH_INFO* mesh); void TestVolumes(CAMERA_INFO* camera); - void HandleEvent(Event& event, Activator& activator); + bool HandleEvent(Event& event, Activator& activator); bool HandleEvent(const std::string& name, EventType eventType, Activator activator); void HandleAllGlobalEvents(EventType type, Activator& activator); bool SetEventState(const std::string& name, EventType eventType, bool enabled); diff --git a/TombEngine/Game/debug/debug.cpp b/TombEngine/Game/debug/debug.cpp index a5102b454..233632785 100644 --- a/TombEngine/Game/debug/debug.cpp +++ b/TombEngine/Game/debug/debug.cpp @@ -1,61 +1,159 @@ #include "framework.h" -#include "Game/debug/debug.h" +#include "Game/Debug/Debug.h" +#include #include #include #include +#include -void InitTENLog(const std::string& logDirContainingDir) +#include "Renderer/Renderer.h" + +using TEN::Renderer::g_Renderer; + +namespace TEN::Debug { - // "true" means create new log file each time game is run. - auto logPath = logDirContainingDir + "Logs/TENLog.txt"; - auto fileSink = std::make_shared(logPath, true); + static auto StartTime = std::chrono::high_resolution_clock::time_point{}; - std::shared_ptr logger; - - // Set file and console log targets. - auto consoleSink = std::make_shared(); - logger = std::make_shared(std::string{ "multi_sink" }, spdlog::sinks_init_list{ fileSink, consoleSink }); - - spdlog::initialize_logger(logger); - logger->set_level(spdlog::level::info); - logger->flush_on(spdlog::level::info); - logger->set_pattern("[%Y-%b-%d %T] [%^%l%$] %v"); -} - -void TENLog(std::string_view str, LogLevel level, LogConfig config, bool allowSpam) -{ - static std::string lastString = {}; - - if (lastString == str && !allowSpam) - return; - - if constexpr (!DebugBuild) + void InitTENLog(const std::string& logDirContainingDir) { - if (LogConfig::Debug == config) + // "true" means create new log file each time game is run. + auto logPath = logDirContainingDir + "Logs/TENLog.txt"; + auto fileSink = std::make_shared(logPath, true); + + auto logger = std::shared_ptr(); + + // Set file and console log targets. + auto consoleSink = std::make_shared(); + logger = std::make_shared(std::string("multi_sink"), spdlog::sinks_init_list{ fileSink, consoleSink }); + + spdlog::initialize_logger(logger); + logger->set_level(spdlog::level::info); + logger->flush_on(spdlog::level::info); + logger->set_pattern("[%Y-%b-%d %T] [%^%l%$] %v"); + } + + void ShutdownTENLog() + { + spdlog::shutdown(); + } + + void TENLog(const std::string_view& msg, LogLevel level, LogConfig config, bool allowSpam) + { + static auto prevString = std::string(); + if (prevString == msg && !allowSpam) return; + + if constexpr (!DebugBuild) + { + if (config == LogConfig::Debug) + return; + } + + auto logger = spdlog::get("multi_sink"); + switch (level) + { + case LogLevel::Error: + logger->error(msg); + break; + + case LogLevel::Warning: + logger->warn(msg); + break; + + case LogLevel::Info: + logger->info(msg); + break; + } + + logger->flush(); + + prevString = std::string(msg); } - auto logger = spdlog::get("multi_sink"); - switch (level) + void StartDebugTimer() { - case LogLevel::Error: - logger->error(str); - break; - case LogLevel::Warning: - logger->warn(str); - break; - case LogLevel::Info: - logger->info(str); - break; + StartTime = std::chrono::high_resolution_clock::now(); } - logger->flush(); + void EndDebugTimer() + { + auto endTime = std::chrono::high_resolution_clock::now(); + auto duration = std::chrono::duration_cast(endTime - StartTime); + + PrintDebugMessage("Execution (microseconds): %d", duration); + } - lastString = std::string(str); -} + void PrintDebugMessage(LPCSTR msg, ...) + { + auto args = va_list{}; + va_start(args, msg); + g_Renderer.PrintDebugMessage(msg, args); + va_end(args); + } + + void DrawDebugString(const std::string& string, const Vector2& pos, const Color& color, float scale, RendererDebugPage page) + { + g_Renderer.AddDebugString(string, pos, color, scale, page); + } -void ShutdownTENLog() -{ - spdlog::shutdown(); + void DrawDebug2DLine(const Vector2& origin, const Vector2& target, const Color& color, RendererDebugPage page) + { + g_Renderer.AddLine2D(origin, target, color, page); + } + + void DrawDebugLine(const Vector3& origin, const Vector3& target, const Color& color, RendererDebugPage page) + { + g_Renderer.AddDebugLine(origin, target, color, page); + } + + void DrawDebugTriangle(const Vector3& vertex0, const Vector3& vertex1, const Vector3& vertex2, const Color& color, RendererDebugPage page) + { + g_Renderer.AddDebugTriangle(vertex0, vertex1, vertex2, color, page); + } + + void DrawDebugTarget(const Vector3& center, const Quaternion& orient, float radius, const Color& color, RendererDebugPage page) + { + g_Renderer.AddDebugTarget(center, orient, radius, color, page); + } + + void DrawDebugBox(const std::array& corners, const Color& color, RendererDebugPage page, bool isWireframe) + { + g_Renderer.AddDebugBox(corners, color, page, isWireframe); + } + + void DrawDebugBox(const Vector3& min, const Vector3& max, const Color& color, RendererDebugPage page, bool isWireframe) + { + g_Renderer.AddDebugBox(min, max, color, page, isWireframe); + } + + void DrawDebugBox(const BoundingOrientedBox& box, const Color& color, RendererDebugPage page, bool isWireframe) + { + g_Renderer.AddDebugBox(box, color, page, isWireframe); + } + + void DrawDebugBox(const BoundingBox& box, const Color& color, RendererDebugPage page, bool isWireframe) + { + g_Renderer.AddDebugBox(box, color, page, isWireframe); + } + + void DrawDebugCone(const Vector3& center, const Quaternion& orient, float radius, float length, const Vector4& color, RendererDebugPage page, bool isWireframe) + { + g_Renderer.AddDebugCone(center, orient, radius, length, color, page, isWireframe); + } + + void DrawDebugCylinder(const Vector3& center, const Quaternion& orient, float radius, float length, const Color& color, RendererDebugPage page, bool isWireframe) + { + g_Renderer.AddDebugCylinder(center, orient, radius, length, color, page, isWireframe); + } + + void DrawDebugSphere(const Vector3& center, float radius, const Color& color, RendererDebugPage page, bool isWireframe) + { + g_Renderer.AddDebugSphere(center, radius, color, page, isWireframe); + } + + void DrawDebugSphere(const BoundingSphere& sphere, const Color& color, RendererDebugPage page, bool isWireframe) + { + g_Renderer.AddDebugSphere(sphere, color, page, isWireframe); + } } diff --git a/TombEngine/Game/debug/debug.h b/TombEngine/Game/debug/debug.h index c668d40e2..edcbbf47a 100644 --- a/TombEngine/Game/debug/debug.h +++ b/TombEngine/Game/debug/debug.h @@ -1,45 +1,75 @@ #pragma once -#if _DEBUG -constexpr bool DebugBuild = true; -#else -constexpr bool DebugBuild = false; -#endif +#include #include #include -#include -enum class LogLevel +#include "Renderer/RendererEnums.h" + +namespace TEN::Debug { - Error, - Warning, - Info -}; +#if _DEBUG + constexpr bool DebugBuild = true; +#else + constexpr bool DebugBuild = false; +#endif -enum class LogConfig -{ - Debug, - All -}; - -void TENLog(std::string_view str, LogLevel level = LogLevel::Info, LogConfig config = LogConfig::All, bool allowSpam = false); -void ShutdownTENLog(); -void InitTENLog(const std::string& logDirContainingDir); - -class TENScriptException : public std::runtime_error -{ -public: - using std::runtime_error::runtime_error; -}; - -inline void assertion(const bool& expr, const char* msg) -{ - if constexpr (DebugBuild) + enum class LogLevel { - if (!expr) + Error, + Warning, + Info + }; + + enum class LogConfig + { + Debug, + All + }; + + class TENScriptException : public std::runtime_error + { + public: + using std::runtime_error::runtime_error; + }; + + // Logs + + void InitTENLog(const std::string& logDirContainingDir); + void ShutdownTENLog(); + void TENLog(const std::string_view& msg, LogLevel level = LogLevel::Info, LogConfig config = LogConfig::All, bool allowSpam = false); + + inline void TENAssert(const bool& cond, const std::string& msg) + { + if constexpr (DebugBuild) { - TENLog(msg, LogLevel::Error); - throw std::runtime_error(msg); + if (!cond) + { + TENLog(msg, LogLevel::Error); + throw std::runtime_error(msg); + } } - } -}; + }; + + // Timers + + void StartDebugTimer(); + void EndDebugTimer(); + + // Objects + + void PrintDebugMessage(LPCSTR msg, ...); + void DrawDebugString(const std::string& string, const Vector2& pos, const Color& color, float scale, RendererDebugPage page = RendererDebugPage::None); + void DrawDebug2DLine(const Vector2& origin, const Vector2& target, const Color& color, RendererDebugPage page = RendererDebugPage::None); + void DrawDebugLine(const Vector3& origin, const Vector3& target, const Color& color, RendererDebugPage page = RendererDebugPage::None); + void DrawDebugTriangle(const Vector3& vertex0, const Vector3& vertex1, const Vector3& vertex2, const Color& color, RendererDebugPage page = RendererDebugPage::None); + void DrawDebugTarget(const Vector3& center, const Quaternion& orient, float radius, const Color& color, RendererDebugPage page = RendererDebugPage::None); + void DrawDebugBox(const std::array& corners, const Color& color, RendererDebugPage page = RendererDebugPage::None, bool isWireframe = true); + void DrawDebugBox(const Vector3& min, const Vector3& max, const Color& color, RendererDebugPage page = RendererDebugPage::None, bool isWireframe = true); + void DrawDebugBox(const BoundingOrientedBox& box, const Color& color, RendererDebugPage page = RendererDebugPage::None, bool isWireframe = true); + void DrawDebugBox(const BoundingBox& box, const Color& color, RendererDebugPage page = RendererDebugPage::None, bool isWireframe = true); + void DrawDebugCone(const Vector3& center, const Quaternion& orient, float radius, float length, const Vector4& color, RendererDebugPage page, bool isWireframe = true); + void DrawDebugCylinder(const Vector3& center, const Quaternion& orient, float radius, float length, const Color& color, RendererDebugPage page = RendererDebugPage::None, bool isWireframe = true); + void DrawDebugSphere(const Vector3& center, float radius, const Color& color, RendererDebugPage page = RendererDebugPage::None, bool isWireframe = true); + void DrawDebugSphere(const BoundingSphere& sphere, const Color& color, RendererDebugPage page = RendererDebugPage::None, bool isWireframe = true); +} diff --git a/TombEngine/Game/effects/Blood.cpp b/TombEngine/Game/effects/Blood.cpp index 9c46f2653..1d215db83 100644 --- a/TombEngine/Game/effects/Blood.cpp +++ b/TombEngine/Game/effects/Blood.cpp @@ -58,6 +58,8 @@ namespace TEN::Effects::Blood if (uwBlood.Life <= 0.0f) continue; + uwBlood.StoreInterpolationData(); + // Update size. if (uwBlood.Size < UW_BLOOD_SIZE_MAX) uwBlood.Size += 4.0f; diff --git a/TombEngine/Game/effects/Blood.h b/TombEngine/Game/effects/Blood.h index 8b1e41650..b30e60761 100644 --- a/TombEngine/Game/effects/Blood.h +++ b/TombEngine/Game/effects/Blood.h @@ -14,6 +14,21 @@ namespace TEN::Effects::Blood float Init = 0.0f; float Size = 0.0f; float Opacity = 0.0f; + + Vector3 PrevPosition = Vector3::Zero; + Vector4 PrevColor = Vector4::Zero; + float PrevLife = 0.0f; + float PrevSize = 0.0f; + float PrevOpacity = 0.0f; + + void StoreInterpolationData() + { + PrevPosition = Position; + PrevColor = Color; + PrevLife = Life; + PrevSize = Size; + PrevOpacity = Opacity; + } }; extern std::vector UnderwaterBloodParticles; diff --git a/TombEngine/Game/effects/DisplaySprite.cpp b/TombEngine/Game/effects/DisplaySprite.cpp index 9a6b410c3..dd3713d6b 100644 --- a/TombEngine/Game/effects/DisplaySprite.cpp +++ b/TombEngine/Game/effects/DisplaySprite.cpp @@ -14,7 +14,8 @@ namespace TEN::Effects::DisplaySprite std::vector DisplaySprites = {}; void AddDisplaySprite(GAME_OBJECT_ID objectID, int spriteID, const Vector2& pos, short orient, const Vector2& scale, const Vector4& color, - int priority, DisplaySpriteAlignMode alignMode, DisplaySpriteScaleMode scaleMode, BlendMode blendMode) + int priority, DisplaySpriteAlignMode alignMode, DisplaySpriteScaleMode scaleMode, + BlendMode blendMode, DisplaySpritePhase source) { auto displaySprite = DisplaySprite{}; displaySprite.ObjectID = objectID; @@ -27,12 +28,25 @@ namespace TEN::Effects::DisplaySprite displaySprite.AlignMode = alignMode; displaySprite.ScaleMode = scaleMode; displaySprite.BlendMode = blendMode; + displaySprite.Source = source; DisplaySprites.push_back(displaySprite); } - void ClearDisplaySprites() + void ClearAllDisplaySprites() { DisplaySprites.clear(); } + + void ClearDrawPhaseDisplaySprites() + { + DisplaySprites.erase( + std::remove_if( + DisplaySprites.begin(), DisplaySprites.end(), + [](const DisplaySprite& displaySprite) + { + return (displaySprite.Source == DisplaySpritePhase::Draw); + }), + DisplaySprites.end()); + } } diff --git a/TombEngine/Game/effects/DisplaySprite.h b/TombEngine/Game/effects/DisplaySprite.h index 2f9eb4564..f8928f10a 100644 --- a/TombEngine/Game/effects/DisplaySprite.h +++ b/TombEngine/Game/effects/DisplaySprite.h @@ -23,6 +23,12 @@ namespace TEN::Effects::DisplaySprite Fill, Stretch }; + + enum class DisplaySpritePhase + { + Control, + Draw + }; struct DisplaySprite { @@ -38,11 +44,15 @@ namespace TEN::Effects::DisplaySprite DisplaySpriteAlignMode AlignMode = DisplaySpriteAlignMode::Center; DisplaySpriteScaleMode ScaleMode = DisplaySpriteScaleMode::Fit; BlendMode BlendMode = BlendMode::AlphaBlend; + + DisplaySpritePhase Source = DisplaySpritePhase::Control; }; extern std::vector DisplaySprites; void AddDisplaySprite(GAME_OBJECT_ID objectID, int spriteID, const Vector2& pos, short orient, const Vector2& scale, const Vector4& color, - int priority, DisplaySpriteAlignMode alignMode, DisplaySpriteScaleMode scaleMode, BlendMode blendMode); - void ClearDisplaySprites(); + int priority, DisplaySpriteAlignMode alignMode, DisplaySpriteScaleMode scaleMode, + BlendMode blendMode, DisplaySpritePhase source); + void ClearAllDisplaySprites(); + void ClearDrawPhaseDisplaySprites(); } diff --git a/TombEngine/Game/effects/Electricity.cpp b/TombEngine/Game/effects/Electricity.cpp index 2c169717c..7e64d4cb4 100644 --- a/TombEngine/Game/effects/Electricity.cpp +++ b/TombEngine/Game/effects/Electricity.cpp @@ -175,6 +175,8 @@ namespace TEN::Effects::Electricity for (auto& laser : HelicalLasers) { + laser.StoreInterpolationData(); + // Set to despawn. laser.Life -= 1.0f; if (laser.Life <= 0.0f) @@ -213,6 +215,8 @@ namespace TEN::Effects::Electricity if (arc.life <= 0.0f) continue; + arc.StoreInterpolationData(); + // If/when this behaviour is changed, modify AddLightningArc accordingly. arc.life -= 2.0f; if (arc.life > 0.0f) diff --git a/TombEngine/Game/effects/Electricity.h b/TombEngine/Game/effects/Electricity.h index 45ea1ecbd..98be1f93d 100644 --- a/TombEngine/Game/effects/Electricity.h +++ b/TombEngine/Game/effects/Electricity.h @@ -39,6 +39,27 @@ namespace TEN::Effects::Electricity int rotation; int type; int flags; + + Vector3 PrevPos1 = Vector3::Zero; + Vector3 PrevPos2 = Vector3::Zero; + Vector3 PrevPos3 = Vector3::Zero; + Vector3 PrevPos4 = Vector3::Zero; + byte PrevR = 0; + byte PrevG = 0; + byte PrevB = 0; + float PrevLife = 0.0f; + + void StoreInterpolationData() + { + PrevPos1 = pos1; + PrevPos2 = pos2; + PrevPos3 = pos3; + PrevPos4 = pos4; + PrevR = r; + PrevG = g; + PrevB = b; + PrevLife = life; + } }; struct HelicalLaser @@ -57,6 +78,27 @@ namespace TEN::Effects::Electricity float LengthEnd = 0.0f; float Opacity = 0.0f; short Rotation = 0; + + Vector3 PrevOrigin = Vector3::Zero; + Vector3 PrevTarget = Vector3::Zero; + short PrevOrientation2D = 0; + Vector4 PrevColor = Vector4::Zero; + float PrevLife = 0.0f; + float PrevRadius = 0.0f; + float PrevLength = 0.0f; + float PrevOpacity = 0.0f; + + void StoreInterpolationData() + { + PrevOrigin = Origin; + PrevTarget = Target; + PrevOrientation2D = Orientation2D; + PrevColor = Color; + PrevLife = Life; + PrevRadius = Radius; + PrevLength = Length; + PrevOpacity = Opacity; + } }; extern std::vector ElectricityArcs; diff --git a/TombEngine/Game/effects/Ripple.cpp b/TombEngine/Game/effects/Ripple.cpp index 982c15177..e266ef127 100644 --- a/TombEngine/Game/effects/Ripple.cpp +++ b/TombEngine/Game/effects/Ripple.cpp @@ -59,6 +59,8 @@ namespace TEN::Effects::Ripple if (ripple.Life <= 0.0f) continue; + ripple.StoreInterpolationData(); + // Update size. if (ripple.Size < RIPPLE_SIZE_MAX) ripple.Size += (ripple.Flags & ((int)RippleFlags::SlowFade | (int)RippleFlags::OnGround)) ? SIZE_STEP_SMALL : SIZE_STEP_LARGE; diff --git a/TombEngine/Game/effects/Ripple.h b/TombEngine/Game/effects/Ripple.h index ad1688c2b..261b647d3 100644 --- a/TombEngine/Game/effects/Ripple.h +++ b/TombEngine/Game/effects/Ripple.h @@ -23,6 +23,17 @@ namespace TEN::Effects::Ripple float Size = 0.0f; float FadeDuration = 0.0f; int Flags = 0; + + Vector3 PrevPosition = Vector3::Zero; + Vector4 PrevColor = Vector4::Zero; + float PrevSize = 0.0f; + + void StoreInterpolationData() + { + PrevPosition = Position; + PrevColor = Color; + PrevSize = Size; + } }; extern std::vector Ripples; diff --git a/TombEngine/Game/effects/Streamer.cpp b/TombEngine/Game/effects/Streamer.cpp index ec7bad158..a7266498f 100644 --- a/TombEngine/Game/effects/Streamer.cpp +++ b/TombEngine/Game/effects/Streamer.cpp @@ -21,9 +21,11 @@ namespace TEN::Effects::Streamer void Streamer::StreamerSegment::Update() { + StoreInterpolationData(); + // Update opacity. if (Color.w > 0.0f) - Color.w = InterpolateCos(0.0f, OpacityMax, Life / LifeMax); + Color.w = EaseInOutSine(0.0f, OpacityMax, Life / LifeMax); // TODO: Not working. // Update orientation. @@ -66,7 +68,7 @@ namespace TEN::Effects::Streamer float lifeMax = std::min(round(life * FPS), (float)SEGMENT_COUNT_MAX); float alpha = (segmentCount / lifeMax) * FADE_IN_COEFF; - float opacityMax = InterpolateCos(0.0f, color.w, alpha); + float opacityMax = EaseInOutSine(0.0f, color.w, alpha); segment.Orientation = AxisAngle(dir, orient); segment.Color = Vector4(color.x, color.y, color.z, opacityMax); @@ -99,7 +101,7 @@ namespace TEN::Effects::Streamer Streamer::StreamerSegment& Streamer::GetNewSegment() { - assertion(Segments.size() <= SEGMENT_COUNT_MAX, "Streamer segment count overflow."); + TENAssert(Segments.size() <= SEGMENT_COUNT_MAX, "Streamer segment count overflow."); // Clear oldest segment if vector is full. if (Segments.size() == SEGMENT_COUNT_MAX) @@ -112,7 +114,7 @@ namespace TEN::Effects::Streamer 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 rot, int flags) { - assertion(Pools.size() <= POOL_COUNT_MAX, "Streamer pool count overflow."); + TENAssert(Pools.size() <= POOL_COUNT_MAX, "Streamer pool count overflow."); // Return early if pool map is full and element with tag key doesn't already exist. if (Pools.size() == POOL_COUNT_MAX && !Pools.count(tag)) @@ -150,7 +152,7 @@ namespace TEN::Effects::Streamer Streamer& StreamerModule::GetStreamer(int tag) { auto& pool = GetPool(tag); - assertion(pool.size() <= STREAMER_COUNT_MAX, "Streamer pool size overflow."); + TENAssert(pool.size() <= STREAMER_COUNT_MAX, "Streamer pool size overflow."); // Return most recent streamer iteration if it exists and is unbroken. if (!pool.empty()) @@ -190,14 +192,17 @@ namespace TEN::Effects::Streamer pool.erase( std::remove_if( pool.begin(), pool.end(), - [](const auto& streamer) { return streamer.Segments.empty(); }), + [](const auto& streamer) + { + return streamer.Segments.empty(); + }), pool.end()); } 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 rot, int flags) { - assertion(Modules.size() <= MODULE_COUNT_MAX, "Streamer module count overflow."); + TENAssert(Modules.size() <= MODULE_COUNT_MAX, "Streamer module count overflow."); // Return early if module map is full and element with itemNumber key doesn't already exist. if (Modules.size() == MODULE_COUNT_MAX && !Modules.count(itemNumber)) diff --git a/TombEngine/Game/effects/Streamer.h b/TombEngine/Game/effects/Streamer.h index ea712a44a..57df932d2 100644 --- a/TombEngine/Game/effects/Streamer.h +++ b/TombEngine/Game/effects/Streamer.h @@ -9,10 +9,9 @@ namespace TEN::Effects::Streamer { enum class StreamerFlags { - FadeLeft = (1 << 0), - FadeRight = (1 << 1), - - BlendModeAdditive = (1 << 2) + FadeLeft = 1 << 0, + FadeRight = 1 << 1, + BlendModeAdditive = 1 << 2 }; class Streamer @@ -39,11 +38,21 @@ namespace TEN::Effects::Streamer std::array Vertices = {}; + Vector4 PrevColor = Vector4::Zero; + std::array PrevVertices = {}; + void InitializeVertices(const Vector3& pos, float width); void Update(); private: void TransformVertices(float vel, float scaleRate); + + void StoreInterpolationData() + { + PrevColor = Color; + PrevVertices[0] = Vertices[0]; + PrevVertices[1] = Vertices[1]; + } }; // Members diff --git a/TombEngine/Game/effects/bubble.cpp b/TombEngine/Game/effects/bubble.cpp index 58f10ccf4..7dc9f676f 100644 --- a/TombEngine/Game/effects/bubble.cpp +++ b/TombEngine/Game/effects/bubble.cpp @@ -147,7 +147,9 @@ namespace TEN::Effects::Bubble if (bubble.Life <= 0.0f) continue; - // Update room number. TODO: Should use GetCollision(), but calling it for each bubble is very inefficient. + bubble.StoreInterpolationData(); + + // Update room number. TODO: Should use GetPointCollision(), but calling it for each bubble is very inefficient. auto roomVector = RoomVector(bubble.RoomNumber, int(bubble.Position.y - bubble.Gravity)); int roomNumber = GetRoomVector(roomVector, Vector3i(bubble.Position.x, bubble.Position.y - bubble.Gravity, bubble.Position.z)).RoomNumber; int prevRoomNumber = bubble.RoomNumber; @@ -158,7 +160,7 @@ namespace TEN::Effects::Bubble { // Hit water surface; spawn ripple. SpawnRipple( - Vector3(bubble.Position.x, g_Level.Rooms[prevRoomNumber].maxceiling, bubble.Position.z), + Vector3(bubble.Position.x, g_Level.Rooms[prevRoomNumber].TopHeight, bubble.Position.z), roomNumber, ((bubble.SizeMax.x + bubble.SizeMax.y) / 2) * 0.5f, (int)RippleFlags::SlowFade); @@ -168,7 +170,7 @@ namespace TEN::Effects::Bubble } // Hit ceiling. NOTE: This is a hacky check. New collision fetching should provide fast info on a need-to-know basis. else if (bubble.RoomNumber == prevRoomNumber && - bubble.Position.y <= g_Level.Rooms[prevRoomNumber].maxceiling) + bubble.Position.y <= g_Level.Rooms[prevRoomNumber].TopHeight) { bubble.Life = 0.0f; continue; diff --git a/TombEngine/Game/effects/bubble.h b/TombEngine/Game/effects/bubble.h index 4825b89cf..dff0c523a 100644 --- a/TombEngine/Game/effects/bubble.h +++ b/TombEngine/Game/effects/bubble.h @@ -30,6 +30,19 @@ namespace TEN::Effects::Bubble float Gravity = 0.0f; float OscillationPeriod = 0.0f; float OscillationVelocity = 0.0f; + + Vector3 PrevPosition = Vector3::Zero; + Vector4 PrevColor = Vector4::Zero; + Vector2 PrevSize = Vector2::Zero; + float PrevLife = 0.0f; + + void StoreInterpolationData() + { + PrevPosition = Position; + PrevColor = Color; + PrevSize = Size; + PrevLife = Life; + } }; extern std::vector Bubbles; diff --git a/TombEngine/Game/effects/chaffFX.cpp b/TombEngine/Game/effects/chaffFX.cpp index ed2468ce9..f3e511a89 100644 --- a/TombEngine/Game/effects/chaffFX.cpp +++ b/TombEngine/Game/effects/chaffFX.cpp @@ -133,12 +133,12 @@ void TriggerChaffSmoke(const Vector3i& pos, const Vector3i& vel, int speed, bool smoke->blendMode = BlendMode::Additive; - smoke->x = pos.x + (GetRandomControl() & 7) - 3; - smoke->y = pos.y + (GetRandomControl() & 7) - 3; - smoke->z = pos.z + (GetRandomControl() & 7) - 3; - smoke->xVel = vel.x + ((GetRandomDraw() & 63) - 32); - smoke->yVel = vel.y; - smoke->zVel = vel.z + ((GetRandomDraw() & 63) - 32); + smoke->position.x = pos.x + (GetRandomControl() & 7) - 3; + smoke->position.y = pos.y + (GetRandomControl() & 7) - 3; + smoke->position.z = pos.z + (GetRandomControl() & 7) - 3; + smoke->velocity.x = vel.x + ((GetRandomDraw() & 63) - 32); + smoke->velocity.y = vel.y; + smoke->velocity.z = vel.z + ((GetRandomDraw() & 63) - 32); smoke->friction = 4; if (GetRandomControl() & 1) diff --git a/TombEngine/Game/effects/debris.cpp b/TombEngine/Game/effects/debris.cpp index 9d98a0ada..560bc9000 100644 --- a/TombEngine/Game/effects/debris.cpp +++ b/TombEngine/Game/effects/debris.cpp @@ -2,14 +2,15 @@ #include "Game/effects/debris.h" #include "Game/collision/collide_room.h" +#include "Game/collision/Sphere.h" #include "Game/effects/tomb4fx.h" #include "Game/Setup.h" #include "Specific/level.h" -#include "Math/Random.h" #include "Math/Math.h" +using namespace TEN::Collision::Sphere; +using namespace TEN::Math; using namespace TEN::Renderer; -using namespace TEN::Math::Random; ShatterImpactInfo ShatterImpactData; SHATTER_ITEM ShatterItem; @@ -26,17 +27,15 @@ bool ExplodeItemNode(ItemInfo* item, int node, int noXZVel, int bits) if (number == BODY_DO_EXPLOSION) number = -64; - GetSpheres(item, CreatureSpheres, SPHERES_SPACE_WORLD | SPHERES_SPACE_BONE_ORIGIN, Matrix::Identity); + auto spheres = item->GetSpheres(); ShatterItem.yRot = item->Pose.Orientation.y; ShatterItem.bit = 1 << node; ShatterItem.meshIndex = Objects[item->ObjectNumber].meshIndex + node; - ShatterItem.sphere.x = CreatureSpheres[node].x; - ShatterItem.sphere.y = CreatureSpheres[node].y; - ShatterItem.sphere.z = CreatureSpheres[node].z; + ShatterItem.sphere.Center = spheres[node].Center; ShatterItem.color = item->Model.Color; ShatterItem.flags = item->ObjectNumber == ID_CROSSBOW_BOLT ? 0x400 : 0; ShatterImpactData.impactDirection = Vector3(0, -1, 0); - ShatterImpactData.impactLocation = { (float)ShatterItem.sphere.x, (float)ShatterItem.sphere.y, (float)ShatterItem.sphere.z }; + ShatterImpactData.impactLocation = ShatterItem.sphere.Center; ShatterObject(&ShatterItem, NULL, number, item->RoomNumber, noXZVel); item->MeshBits &= ~ShatterItem.bit; @@ -88,7 +87,7 @@ void ShatterObject(SHATTER_ITEM* item, MESH_INFO* mesh, int num, short roomNumbe isStatic = false; meshIndex = item->meshIndex; yRot = item->yRot; - pos = Vector3(item->sphere.x, item->sphere.y, item->sphere.z); + pos = item->sphere.Center; scale = 1.0f; } @@ -177,8 +176,8 @@ void ShatterObject(SHATTER_ITEM* item, MESH_INFO* mesh, int num, short roomNumbe fragment->restitution = 0.6f; fragment->friction = 0.6f; fragment->linearDrag = .99f; - fragment->angularVelocity = Vector3(GenerateFloat(-1, 1) * 0.39, GenerateFloat(-1, 1) * 0.39, GenerateFloat(-1, 1) * 0.39); - fragment->angularDrag = GenerateFloat(0.9f, 0.999f); + fragment->angularVelocity = Vector3(Random::GenerateFloat(-1, 1) * 0.39, Random::GenerateFloat(-1, 1) * 0.39, Random::GenerateFloat(-1, 1) * 0.39); + fragment->angularDrag = Random::GenerateFloat(0.9f, 0.999f); fragment->velocity = CalculateFragmentImpactVelocity(fragment->worldPosition, ShatterImpactData.impactDirection, ShatterImpactData.impactLocation); fragment->roomNumber = roomNumber; fragment->numBounces = 0; @@ -196,10 +195,10 @@ Vector3 CalculateFragmentImpactVelocity(const Vector3& fragmentWorldPosition, co radiusNormVec.Normalize(); float radiusStrenght = 1-((fragmentWorldPosition - impactLocation).Length() / 1024); radiusStrenght = fmax(radiusStrenght, 0); - Vector3 radiusRandomVector = Vector3(GenerateFloat(-0.2f, 0.2f), GenerateFloat(-0.2f, 0.2f), GenerateFloat(-0.2f, 0.2f)) + radiusNormVec; + Vector3 radiusRandomVector = Vector3(Random::GenerateFloat(-0.2f, 0.2f), Random::GenerateFloat(-0.2f, 0.2f), Random::GenerateFloat(-0.2f, 0.2f)) + radiusNormVec; radiusRandomVector.Normalize(); Vector3 radiusVelocity = radiusRandomVector * radiusStrenght*40; - Vector3 impactDirectionVelocity = (impactDirection + Vector3(GenerateFloat(-0.2f, 0.2f), GenerateFloat(-0.2f, 0.2f), GenerateFloat(-0.2f, 0.2f))) * 80 ; + Vector3 impactDirectionVelocity = (impactDirection + Vector3(Random::GenerateFloat(-0.2f, 0.2f), Random::GenerateFloat(-0.2f, 0.2f), Random::GenerateFloat(-0.2f, 0.2f))) * 80 ; return radiusVelocity + impactDirectionVelocity; } @@ -217,6 +216,8 @@ void UpdateDebris() { if (deb.active) { + deb.StoreInterpolationData(); + FloorInfo* floor; short roomNumber; diff --git a/TombEngine/Game/effects/debris.h b/TombEngine/Game/effects/debris.h index 1c2df5372..f06ce1c8b 100644 --- a/TombEngine/Game/effects/debris.h +++ b/TombEngine/Game/effects/debris.h @@ -1,9 +1,8 @@ #pragma once -#include "Game/collision/sphere.h" -#include "Specific/newtypes.h" -#include "Specific/level.h" -#include "Renderer/Renderer.h" #include "Renderer/Graphics/Vertices/Vertex.h" +#include "Renderer/Renderer.h" +#include "Specific/level.h" +#include "Specific/newtypes.h" constexpr int MAX_DEBRIS = 2048; @@ -26,7 +25,7 @@ struct ITEM_LIGHT struct SHATTER_ITEM { - SPHERE sphere; + BoundingSphere sphere; ITEM_LIGHT* il; int meshIndex; Vector4 color; @@ -71,6 +70,13 @@ struct DebrisFragment bool active; bool isStatic; Matrix Transform; + + Matrix PrevTransform = Matrix::Identity; + + void StoreInterpolationData() + { + PrevTransform = Transform; + } }; extern SHATTER_ITEM ShatterItem; diff --git a/TombEngine/Game/effects/drip.cpp b/TombEngine/Game/effects/drip.cpp index 01609b5f0..f21b06c6c 100644 --- a/TombEngine/Game/effects/drip.cpp +++ b/TombEngine/Game/effects/drip.cpp @@ -3,6 +3,7 @@ #include "Game/collision/collide_room.h" #include "Game/collision/floordata.h" +#include "Game/collision/Point.h" #include "Game/effects/effects.h" #include "Game/effects/Ripple.h" #include "Game/effects/weather.h" @@ -12,6 +13,7 @@ #include "Specific/clock.h" #include "Specific/level.h" +using namespace TEN::Collision::Point; using namespace TEN::Effects::Environment; using namespace TEN::Effects::Ripple; using namespace TEN::Collision::Floordata; @@ -105,17 +107,19 @@ namespace TEN::Effects::Drip if (drip.Life <= 0.0f) continue; + drip.StoreInterpolationData(); + // Update velocity. drip.Velocity.y += drip.Gravity; if (TestEnvironment(ENV_FLAG_WIND, drip.RoomNumber)) drip.Velocity += Weather.Wind(); int prevRoomNumber = drip.RoomNumber; - auto pointColl = GetCollision(drip.Position.x, drip.Position.y, drip.Position.z, drip.RoomNumber); + auto pointColl = GetPointCollision(drip.Position, drip.RoomNumber); // Update position. drip.Position += drip.Velocity; - drip.RoomNumber = pointColl.RoomNumber; + drip.RoomNumber = pointColl.GetRoomNumber(); // Update size and color. float alpha = 1.0f - (drip.Life / drip.LifeMax); @@ -128,10 +132,9 @@ namespace TEN::Effects::Drip // Spawn ripple on surface only. if (!TestEnvironment(ENV_FLAG_WATER, prevRoomNumber)) { - float waterHeight = GetWaterHeight(drip.Position.x, drip.Position.y, drip.Position.z, drip.RoomNumber); SpawnRipple( - Vector3(drip.Position.x, waterHeight - RIPPLE_HEIGHT_OFFSET, drip.Position.z), - pointColl.RoomNumber, + Vector3(drip.Position.x, pointColl.GetWaterTopHeight() - RIPPLE_HEIGHT_OFFSET, drip.Position.z), + pointColl.GetRoomNumber(), Random::GenerateFloat(RIPPLE_SIZE_WATER_MIN, RIPPLE_SIZE_WATER_MAX), (int)RippleFlags::SlowFade | (int)RippleFlags::LowOpacity); } @@ -140,20 +143,20 @@ namespace TEN::Effects::Drip continue; } // Hit floor; spawn ripple. - else if (drip.Position.y >= pointColl.Position.Floor) + else if (drip.Position.y >= pointColl.GetFloorHeight()) { SpawnRipple( - Vector3(drip.Position.x, pointColl.Position.Floor - RIPPLE_HEIGHT_OFFSET, drip.Position.z), - pointColl.RoomNumber, + Vector3(drip.Position.x, pointColl.GetFloorHeight() - RIPPLE_HEIGHT_OFFSET, drip.Position.z), + pointColl.GetRoomNumber(), Random::GenerateFloat(RIPPLE_SIZE_GROUND_MIN, RIPPLE_SIZE_GROUND_MAX), (int)RippleFlags::SlowFade | (int)RippleFlags::LowOpacity | (int)RippleFlags::OnGround, - pointColl.FloorNormal); + pointColl.GetFloorNormal()); drip.Life = 0.0f; continue; } // Hit ceiling; deactivate. - else if (drip.Position.y <= pointColl.Position.Ceiling) + else if (drip.Position.y <= pointColl.GetCeilingHeight()) { drip.Life = 0.0f; continue; diff --git a/TombEngine/Game/effects/drip.h b/TombEngine/Game/effects/drip.h index 52f13d39c..31c428a74 100644 --- a/TombEngine/Game/effects/drip.h +++ b/TombEngine/Game/effects/drip.h @@ -15,6 +15,21 @@ namespace TEN::Effects::Drip float Life = 0.0f; float LifeMax = 0.0f; float Gravity = 0.0f; + + Vector3 PrevPosition = Vector3::Zero; + Vector3 PrevVelocity = Vector3::Zero; + Vector2 PrevSize = Vector2::Zero; + Vector4 PrevColor = Vector4::Zero; + float PrevLife = 0.0f; + + void StoreInterpolationData() + { + PrevPosition = Position; + PrevColor = Color; + PrevSize = Size; + PrevLife = Life; + PrevVelocity = Velocity; + } }; extern std::vector Drips; diff --git a/TombEngine/Game/effects/effects.cpp b/TombEngine/Game/effects/effects.cpp index 4cdc79ac4..c64da1969 100644 --- a/TombEngine/Game/effects/effects.cpp +++ b/TombEngine/Game/effects/effects.cpp @@ -4,6 +4,7 @@ #include "Scripting/Include/Flow/ScriptInterfaceFlowHandler.h" #include "Game/animation.h" #include "Game/collision/collide_room.h" +#include "Game/collision/Point.h" #include "Game/effects/Blood.h" #include "Game/effects/Bubble.h" #include "Game/effects/Drip.h" @@ -25,6 +26,7 @@ #include "Specific/clock.h" #include "Specific/level.h" +using namespace TEN::Collision::Point; using namespace TEN::Effects::Blood; using namespace TEN::Effects::Bubble; using namespace TEN::Effects::Drip; @@ -38,6 +40,9 @@ using namespace TEN::Math::Random; using TEN::Renderer::g_Renderer; +constexpr int WIBBLE_SPEED = 4; +constexpr int WIBBLE_MAX = UCHAR_MAX - WIBBLE_SPEED + 1; + // New particle class Particle Particles[MAX_PARTICLES]; ParticleDynamic ParticleDynamics[MAX_PARTICLE_DYNAMICS]; @@ -47,7 +52,9 @@ FX_INFO EffectList[NUM_EFFECTS]; GameBoundingBox DeadlyBounds; SPLASH_SETUP SplashSetup; SPLASH_STRUCT Splashes[MAX_SPLASHES]; + int SplashCount = 0; +int Wibble = 0; Vector3i NodeVectors[ParticleNodeOffsetIDs::NodeMax]; NODEOFFSET_INFO NodeOffsets[ParticleNodeOffsetIDs::NodeMax] = @@ -119,46 +126,42 @@ void DetatchSpark(int number, SpriteEnumFlag type) Particle* GetFreeParticle() { - int result = -1; - - // Get first free available spark + int partID = NO_VALUE; + // Get first free available particle. for (int i = 0; i < MAX_PARTICLES; i++) { - auto* particle = &Particles[i]; - - if (!particle->on) + const auto& part = Particles[i]; + if (!part.on) { - result = i; + partID = i; break; } } - // No free sparks left, hijack existing one with less possible life - - int life = INT_MAX; - if (result == -1) + // No free particles; get particle with shortest life. + float shortestLife = INFINITY; + if (partID == NO_VALUE) { for (int i = 0; i < MAX_PARTICLES; i++) { - auto* particle = &Particles[i]; + const auto& part = Particles[i]; - if (particle->life < life && particle->dynamic == -1 && !(particle->flags & SP_EXPLOSION)) + if (part.life < shortestLife && part.dynamic == NO_VALUE && !(part.flags & SP_EXPLOSION)) { - result = i; - life = particle->life; + partID = i; + shortestLife = part.life; } } } - auto* spark = &Particles[result]; + auto& part = Particles[partID]; + part.spriteIndex = Objects[ID_DEFAULT_SPRITES].meshIndex; + part.blendMode = BlendMode::Additive; + part.extras = 0; + part.dynamic = NO_VALUE; - spark->extras = 0; - spark->dynamic = -1; - spark->spriteIndex = Objects[ID_DEFAULT_SPRITES].meshIndex; - spark->blendMode = BlendMode::Additive; - - return spark; + return ∂ } void SetSpriteSequence(Particle& particle, GAME_OBJECT_ID objectID) @@ -178,6 +181,13 @@ void SetSpriteSequence(Particle& particle, GAME_OBJECT_ID objectID) particle.spriteIndex = Objects[objectID].meshIndex + (int)round(Lerp(0.0f, numSprites, normalizedAge)); } +void UpdateWibble() +{ + // Update oscillator seed. + Wibble = (Wibble + WIBBLE_SPEED) & WIBBLE_MAX; + +} + void UpdateSparks() { auto bounds = GameBoundingBox(LaraItem); @@ -195,6 +205,8 @@ void UpdateSparks() if (spark->on) { + spark->StoreInterpolationData(); + spark->life--; if (!spark->life) @@ -937,7 +949,7 @@ void TriggerSuperJetFlame(ItemInfo* item, int yvel, int deadly) sptr->xVel = (GetRandomControl() & 0xFF) - 128; sptr->zVel = (GetRandomControl() & 0xFF) - 128; - float xAngle = item->Pose.Orientation.x; + float xAngle = item->Pose.Orientation.x + ANGLE(180); // Nullmesh is rotated 180 degrees in editor float yAngle = item->Pose.Orientation.y; Vector3 dir; @@ -1043,6 +1055,8 @@ void UpdateSplashes() if (splash.isActive) { + splash.StoreInterpolationData(); + splash.life--; if (splash.life <= 0) splash.isActive = false; @@ -1070,11 +1084,15 @@ void UpdateSplashes() short DoBloodSplat(int x, int y, int z, short speed, short direction, short roomNumber) { - short probedRoomNumber = GetCollision(x, y, z, roomNumber).RoomNumber; + short probedRoomNumber = GetPointCollision(Vector3i(x, y, z), roomNumber).GetRoomNumber(); if (TestEnvironment(ENV_FLAG_WATER, probedRoomNumber)) + { SpawnUnderwaterBlood(Vector3(x, y, z), probedRoomNumber, speed); + } else + { TriggerBlood(x, y, z, direction >> 4, speed); + } return 0; } @@ -1228,7 +1246,7 @@ void KillAllCurrentItems(short itemNumber) // TODO: Reimplement this functionality. } -// TODO: Rname to SpawnDynamicLight(). +// TODO: Rename to SpawnDynamicLight(). void TriggerDynamicLight(const Vector3& pos, const Color& color, float falloff) { g_Renderer.AddDynamicLight( @@ -1252,15 +1270,15 @@ void SpawnPlayerWaterSurfaceEffects(const ItemInfo& item, int waterHeight, int w return; // Get point collision. - auto pointColl0 = GetCollision(&item, 0, 0, -(LARA_HEIGHT / 2)); - auto pointColl1 = GetCollision(&item, 0, 0, item.Animation.Velocity.y); + auto pointColl0 = GetPointCollision(item, 0, 0, -(LARA_HEIGHT / 2)); + auto pointColl1 = GetPointCollision(item, 0, 0, item.Animation.Velocity.y); // In swamp; return early. - if (TestEnvironment(ENV_FLAG_SWAMP, pointColl1.RoomNumber)) + if (TestEnvironment(ENV_FLAG_SWAMP, pointColl1.GetRoomNumber())) return; - bool isWater0 = TestEnvironment(ENV_FLAG_WATER, pointColl0.RoomNumber); - bool isWater1 = TestEnvironment(ENV_FLAG_WATER, pointColl1.RoomNumber); + bool isWater0 = TestEnvironment(ENV_FLAG_WATER, pointColl0.GetRoomNumber()); + bool isWater1 = TestEnvironment(ENV_FLAG_WATER, pointColl1.GetRoomNumber()); // Spawn splash. if (!isWater0 && isWater1 && @@ -1273,7 +1291,7 @@ void SpawnPlayerWaterSurfaceEffects(const ItemInfo& item, int waterHeight, int w SplashSetup.innerRadius = 16; SplashSetup.splashPower = item.Animation.Velocity.z; - SetupSplash(&SplashSetup, pointColl0.RoomNumber); + SetupSplash(&SplashSetup, pointColl0.GetRoomNumber()); SplashCount = 16; } // Spawn ripple. @@ -1298,11 +1316,11 @@ void SpawnPlayerWaterSurfaceEffects(const ItemInfo& item, int waterHeight, int w void Splash(ItemInfo* item) { - int probedRoomNumber = GetCollision(item).RoomNumber; + int probedRoomNumber = GetPointCollision(*item).GetRoomNumber(); if (!TestEnvironment(ENV_FLAG_WATER, probedRoomNumber)) return; - int waterHeight = GetWaterHeight(item); + int waterHeight = GetPointCollision(*item).GetWaterTopHeight(); SplashSetup.x = item->Pose.Position.x; SplashSetup.y = waterHeight - 1; @@ -1452,21 +1470,21 @@ void TriggerFlashSmoke(int x, int y, int z, short roomNumber) spark->fadeToBlack = 16; spark->blendMode = BlendMode::Additive; spark->life = spark->sLife = (GetRandomControl() & 0xF) + 64; - spark->x = (GetRandomControl() & 0x1F) + x - 16; - spark->y = (GetRandomControl() & 0x1F) + y - 16; - spark->z = (GetRandomControl() & 0x1F) + z - 16; + spark->position.x = (GetRandomControl() & 0x1F) + x - 16; + spark->position.y = (GetRandomControl() & 0x1F) + y - 16; + spark->position.z = (GetRandomControl() & 0x1F) + z - 16; if (water) { - spark->xVel = spark->yVel = GetRandomControl() & 0x3FF - 512; - spark->zVel = (GetRandomControl() & 0x3FF) - 512; + spark->velocity.x = spark->velocity.y = GetRandomControl() & 0x3FF - 512; + spark->velocity.z = (GetRandomControl() & 0x3FF) - 512; spark->friction = 68; } else { - spark->xVel = 2 * (GetRandomControl() & 0x3FF) - 1024; - spark->yVel = -512 - (GetRandomControl() & 0x3FF); - spark->zVel = 2 * (GetRandomControl() & 0x3FF) - 1024; + spark->velocity.x = 2 * (GetRandomControl() & 0x3FF) - 1024; + spark->velocity.y = -512 - (GetRandomControl() & 0x3FF); + spark->velocity.z = 2 * (GetRandomControl() & 0x3FF) - 1024; spark->friction = 85; } @@ -1934,7 +1952,7 @@ void ProcessEffects(ItemInfo* item) if (item->Effect.Type != EffectType::Sparks && item->Effect.Type != EffectType::Smoke) { const auto& bounds = GameBoundingBox(item); - int waterHeight = GetWaterHeight(item); + int waterHeight = GetPointCollision(*item).GetWaterTopHeight(); int itemLevel = item->Pose.Position.y + bounds.Y2 - (bounds.GetHeight() / 3); if (waterHeight != NO_HEIGHT && itemLevel > waterHeight) diff --git a/TombEngine/Game/effects/effects.h b/TombEngine/Game/effects/effects.h index 2b77640a0..aaa5031a9 100644 --- a/TombEngine/Game/effects/effects.h +++ b/TombEngine/Game/effects/effects.h @@ -18,6 +18,8 @@ constexpr auto NUM_EFFECTS = 256; constexpr auto MAX_PARTICLES = 1024; constexpr auto MAX_PARTICLE_DYNAMICS = 8; +extern int Wibble; + enum SpriteEnumFlag { SP_NONE = 0, @@ -89,6 +91,8 @@ struct FX_INFO Vector4 color; short flag1; short flag2; + + bool DisableInterpolation; }; struct NODEOFFSET_INFO @@ -110,17 +114,6 @@ struct SPLASH_SETUP int room; }; -struct RIPPLE_STRUCT -{ - int x; - int y; - int z; - char flags; - unsigned char life; - unsigned char size; - unsigned char init; -}; - struct Particle { int x; @@ -160,6 +153,27 @@ struct Particle int fxObj; int roomNumber; unsigned char nodeNumber; // ParticleNodeOffsetIDs enum. + + int PrevX; + int PrevY; + int PrevZ; + short PrevRotAng; + byte PrevR; + byte PrevG; + byte PrevB; + byte PrevScalar; + + void StoreInterpolationData() + { + PrevX = x; + PrevY = y; + PrevZ = z; + PrevRotAng = rotAng; + PrevR = r; + PrevG = g; + PrevB = b; + PrevScalar = scalar; + } }; struct SPLASH_STRUCT @@ -181,6 +195,25 @@ struct SPLASH_STRUCT unsigned short life; bool isRipple; bool isActive; + + Vector3 PrevPosition = Vector3::Zero; + float PrevInnerRad = 0.0f; + float PrevOuterRad = 0.0f; + float PrevHeight = 0.0f; + float PrevHeightSpeed = 0.0f; + float PrevAnimPhase = 0.0f; + unsigned short PrevLife = 0; + + void StoreInterpolationData() + { + PrevPosition = Vector3(x, y, z); + PrevInnerRad = innerRad; + PrevOuterRad = outerRad; + PrevHeight = height; + PrevHeightSpeed = heightSpeed; + PrevAnimPhase = animationPhase; + PrevLife = life; + } }; struct ParticleDynamic @@ -211,7 +244,7 @@ extern FX_INFO EffectList[NUM_EFFECTS]; template TEffect& GetNewEffect(std::vector& effects, unsigned int countMax) { - assertion(effects.size() <= countMax, "Too many particle effects."); + TENAssert(effects.size() <= countMax, "Too many particle effects."); // Add and return new effect. if (effects.size() < countMax) @@ -279,5 +312,6 @@ void TriggerRocketFire(int x, int y, int z); void TriggerExplosionBubbles(int x, int y, int z, short roomNumber); void Ricochet(Pose& pos); void ProcessEffects(ItemInfo* item); +void UpdateWibble(); void TriggerDynamicLight(const Vector3& pos, const Color& color, float falloff); diff --git a/TombEngine/Game/effects/explosion.cpp b/TombEngine/Game/effects/explosion.cpp index 592e21580..d64dd8e42 100644 --- a/TombEngine/Game/effects/explosion.cpp +++ b/TombEngine/Game/effects/explosion.cpp @@ -49,6 +49,8 @@ namespace TEN::Effects::Explosion if (!e.active) continue; + e.StoreInterpolationData(); + e.age++; if (e.age > e.life) { diff --git a/TombEngine/Game/effects/explosion.h b/TombEngine/Game/effects/explosion.h index 4c6ff9348..bc822573f 100644 --- a/TombEngine/Game/effects/explosion.h +++ b/TombEngine/Game/effects/explosion.h @@ -1,28 +1,41 @@ -#pragma once -#include -#include -#include - -namespace TEN::Effects::Explosion -{ - struct ExplosionParticle - { - Vector3 pos; - Vector3 vel; - Vector4 tint; - float size; - float rotation; - float angularVel; - float age; - float life; - int room; - int sprite; - bool active; - }; - extern std::array explosionParticles; - - void TriggerExplosion(const Vector3& pos, float size, bool triggerSparks, bool triggerSmoke, bool triggerShockwave, int room); - void UpdateExplosionParticles(); - ExplosionParticle& getFreeExplosionParticle(); - void SpawnExplosionParticle(const Vector3& pos); -} +#pragma once +#include +#include +#include + +namespace TEN::Effects::Explosion +{ + struct ExplosionParticle + { + Vector3 pos; + Vector3 vel; + Vector4 tint; + float size; + float rotation; + float angularVel; + float age; + float life; + int room; + int sprite; + bool active; + + Vector3 oldPos; + Vector4 oldTint; + float oldSize; + float oldRotation; + + void StoreInterpolationData() + { + oldPos = pos; + oldTint = tint; + oldSize = size; + oldRotation = rotation; + } + }; + extern std::array explosionParticles; + + void TriggerExplosion(const Vector3& pos, float size, bool triggerSparks, bool triggerSmoke, bool triggerShockwave, int room); + void UpdateExplosionParticles(); + ExplosionParticle& getFreeExplosionParticle(); + void SpawnExplosionParticle(const Vector3& pos); +} diff --git a/TombEngine/Game/effects/footprint.cpp b/TombEngine/Game/effects/footprint.cpp index 56d8d197c..74b7758e5 100644 --- a/TombEngine/Game/effects/footprint.cpp +++ b/TombEngine/Game/effects/footprint.cpp @@ -4,6 +4,7 @@ #include "Game/animation.h" #include "Game/collision/collide_room.h" #include "Game/collision/floordata.h" +#include "Game/collision/Point.h" #include "Game/control/control.h" #include "Game/effects/effects.h" #include "Game/items.h" @@ -16,6 +17,7 @@ #include "Specific/level.h" using namespace TEN::Collision::Floordata; +using namespace TEN::Collision::Point; using namespace TEN::Math; namespace TEN::Effects::Footprint @@ -58,14 +60,14 @@ namespace TEN::Effects::Footprint { MaterialType::Concrete, SOUND_EFFECTS::SFX_TR4_LARA_FOOTSTEPS }, { MaterialType::OldWood, SOUND_EFFECTS::SFX_TR4_LARA_FOOTSTEPS_WOOD }, { MaterialType::OldMetal, SOUND_EFFECTS::SFX_TR4_LARA_FOOTSTEPS_METAL }, - { MaterialType::Custom1, SOUND_EFFECTS::SFX_CUSTOM_FOOTSTEP_1 }, - { MaterialType::Custom2, SOUND_EFFECTS::SFX_CUSTOM_FOOTSTEP_2 }, - { MaterialType::Custom3, SOUND_EFFECTS::SFX_CUSTOM_FOOTSTEP_3 }, - { MaterialType::Custom4, SOUND_EFFECTS::SFX_CUSTOM_FOOTSTEP_4 }, - { MaterialType::Custom5, SOUND_EFFECTS::SFX_CUSTOM_FOOTSTEP_5 }, - { MaterialType::Custom6, SOUND_EFFECTS::SFX_CUSTOM_FOOTSTEP_6 }, - { MaterialType::Custom7, SOUND_EFFECTS::SFX_CUSTOM_FOOTSTEP_7 }, - { MaterialType::Custom8, SOUND_EFFECTS::SFX_CUSTOM_FOOTSTEP_8 } + { MaterialType::Custom1, SOUND_EFFECTS::SFX_TEN_CUSTOM_FOOTSTEPS_1 }, + { MaterialType::Custom2, SOUND_EFFECTS::SFX_TEN_CUSTOM_FOOTSTEPS_2 }, + { MaterialType::Custom3, SOUND_EFFECTS::SFX_TEN_CUSTOM_FOOTSTEPS_3 }, + { MaterialType::Custom4, SOUND_EFFECTS::SFX_TEN_CUSTOM_FOOTSTEPS_4 }, + { MaterialType::Custom5, SOUND_EFFECTS::SFX_TEN_CUSTOM_FOOTSTEPS_5 }, + { MaterialType::Custom6, SOUND_EFFECTS::SFX_TEN_CUSTOM_FOOTSTEPS_6 }, + { MaterialType::Custom7, SOUND_EFFECTS::SFX_TEN_CUSTOM_FOOTSTEPS_7 }, + { MaterialType::Custom8, SOUND_EFFECTS::SFX_TEN_CUSTOM_FOOTSTEPS_8 } }; auto it = SOUND_MAP.find(material); @@ -100,7 +102,7 @@ namespace TEN::Effects::Footprint constexpr auto FOOT_OFFSET = Vector3i(0, HEIGHT_OFFSET, 0); auto footPos = GetJointPosition(item, jointIndex, FOOT_OFFSET); - int floorHeight = GetCollision(footPos.x, footPos.y - CLICK(1), footPos.z, item.RoomNumber).Position.Floor; + int floorHeight = GetPointCollision(footPos, item.RoomNumber, -Vector3::UnitY, CLICK(1)).GetFloorHeight(); bool canSpawn = (abs(footPos.y - floorHeight) < ABS_FLOOR_BOUND); auto pos = Vector3(footPos.x, floorHeight - SURFACE_OFFSET, footPos.z); @@ -112,16 +114,16 @@ namespace TEN::Effects::Footprint constexpr auto ABS_FLOOR_BOUND = CLICK(0.5f); // Get point collision at every vertex point. - auto pointColl0 = GetCollision(vertexPoints[0].x, pos.y - CLICK(1), vertexPoints[0].z, item.RoomNumber); - auto pointColl1 = GetCollision(vertexPoints[1].x, pos.y - CLICK(1), vertexPoints[1].z, item.RoomNumber); - auto pointColl2 = GetCollision(vertexPoints[2].x, pos.y - CLICK(1), vertexPoints[2].z, item.RoomNumber); - auto pointColl3 = GetCollision(vertexPoints[3].x, pos.y - CLICK(1), vertexPoints[3].z, item.RoomNumber); + auto pointColl0 = GetPointCollision(Vector3i(vertexPoints[0].x, pos.y - CLICK(1), vertexPoints[0].z), item.RoomNumber); + auto pointColl1 = GetPointCollision(Vector3i(vertexPoints[1].x, pos.y - CLICK(1), vertexPoints[1].z), item.RoomNumber); + auto pointColl2 = GetPointCollision(Vector3i(vertexPoints[2].x, pos.y - CLICK(1), vertexPoints[2].z), item.RoomNumber); + auto pointColl3 = GetPointCollision(Vector3i(vertexPoints[3].x, pos.y - CLICK(1), vertexPoints[3].z), item.RoomNumber); // Don't spawn footprint if floor heights at vertex points are outside upper/lower floor height bound. - if ((abs(pointColl0.Position.Floor - pointColl1.Position.Floor) > ABS_FLOOR_BOUND) || - (abs(pointColl1.Position.Floor - pointColl2.Position.Floor) > ABS_FLOOR_BOUND) || - (abs(pointColl2.Position.Floor - pointColl3.Position.Floor) > ABS_FLOOR_BOUND) || - (abs(pointColl3.Position.Floor - pointColl0.Position.Floor) > ABS_FLOOR_BOUND)) + if ((abs(pointColl0.GetFloorHeight() - pointColl1.GetFloorHeight()) > ABS_FLOOR_BOUND) || + (abs(pointColl1.GetFloorHeight() - pointColl2.GetFloorHeight()) > ABS_FLOOR_BOUND) || + (abs(pointColl2.GetFloorHeight() - pointColl3.GetFloorHeight()) > ABS_FLOOR_BOUND) || + (abs(pointColl3.GetFloorHeight() - pointColl0.GetFloorHeight()) > ABS_FLOOR_BOUND)) { return false; } @@ -163,15 +165,15 @@ namespace TEN::Effects::Footprint // Slightly randomize 2D position. posData.Position += Vector3(Random::GenerateFloat(-5.0f, 5.0f), 0.0f, Random::GenerateFloat(-5.0f, 5.0f)); - auto pointColl = GetCollision(posData.Position.x, posData.Position.y - CLICK(1), posData.Position.z, item.RoomNumber); + auto pointColl = GetPointCollision(posData.Position, item.RoomNumber, -Vector3::UnitY, CLICK(1)); // Don't process material if foot hit bridge object. // TODO: Handle bridges once bridge collision is less stupid. - if (pointColl.Position.Bridge != NO_VALUE) + if (pointColl.GetFloorBridgeItemNumber() != NO_VALUE) return; // Get and emit footstep sound for floor material. - auto sfx = GetFootprintSfx(pointColl.BottomBlock->GetSurfaceMaterial(pointColl.Coordinates.x, pointColl.Coordinates.z, true)); + auto sfx = GetFootprintSfx(pointColl.GetBottomSector().GetSurfaceMaterial(pointColl.GetPosition().x, pointColl.GetPosition().z, true)); if (sfx != SOUND_EFFECTS::SFX_TR4_LARA_FOOTSTEPS) // HACK: Must be here until reference WAD2 is revised. { auto pose = item.Pose; @@ -179,10 +181,10 @@ namespace TEN::Effects::Footprint } // Check floor material. - if (!TestMaterial(pointColl.BottomBlock->GetSurfaceMaterial(pointColl.Coordinates.x, pointColl.Coordinates.z, true), FootprintMaterials)) + if (!TestMaterial(pointColl.GetBottomSector().GetSurfaceMaterial(pointColl.GetPosition().x, pointColl.GetPosition().z, true), FootprintMaterials)) return; - auto vertexPoints = GetFootprintVertexPoints(item, posData.Position, pointColl.FloorNormal); + auto vertexPoints = GetFootprintVertexPoints(item, posData.Position, pointColl.GetFloorNormal()); // Test floor continuity. if (!TestFootprintFloor(item, posData.Position, vertexPoints)) diff --git a/TombEngine/Game/effects/hair.cpp b/TombEngine/Game/effects/hair.cpp index 4a27ba83a..32ae15b55 100644 --- a/TombEngine/Game/effects/hair.cpp +++ b/TombEngine/Game/effects/hair.cpp @@ -3,7 +3,7 @@ #include "Game/animation.h" #include "Game/collision/collide_room.h" -#include "Game/collision/sphere.h" +#include "Game/collision/Point.h" #include "Game/control/control.h" #include "Game/effects/weather.h" #include "Game/items.h" @@ -14,6 +14,7 @@ #include "Scripting/Include/Flow/ScriptInterfaceFlowHandler.h" #include "Specific/level.h" +using namespace TEN::Collision::Point; using namespace TEN::Effects::Environment; using TEN::Renderer::g_Renderer; @@ -21,8 +22,11 @@ namespace TEN::Effects::Hair { HairEffectController HairEffect = {}; - void HairUnit::Update(const ItemInfo& item, int hairUnitIndex) + void HairUnit::Update(const ItemInfo& item, int hairUnitID) { + for (auto& segment : Segments) + segment.StoreInterpolationData(); + const auto& player = GetLaraInfo(item); bool isYoung = (g_GameFlow->GetLevel(CurrentLevel)->GetLaraType() == LaraType::Young); @@ -32,15 +36,14 @@ namespace TEN::Effects::Hair g_Renderer.GetBoneMatrix(item.Index, LM_HEAD, &worldMatrix); // Apply base offset to world matrix. - auto relOffset = GetRelBaseOffset(hairUnitIndex, isYoung); + auto relOffset = GetRelBaseOffset(hairUnitID, isYoung); worldMatrix = Matrix::CreateTranslation(relOffset) * worldMatrix; - + // Use player's head bone orientation as base. - auto baseOrient = Geometry::ConvertDirectionToQuat(-Geometry::ConvertQuatToDirection(GetBoneOrientation(item, LM_HEAD))); + auto baseOrient = Geometry::ConvertDirectionToQuat(-Geometry::ConvertQuatToDirection(GetBoneOrientation(item, LM_HEAD))) * item.Pose.Orientation.ToQuaternion(); // Set position of base segment. - auto basePos = worldMatrix.Translation(); - Segments[0].Position = basePos; + Segments[0].Position = worldMatrix.Translation(); if (!IsInitialized) { @@ -51,7 +54,7 @@ namespace TEN::Effects::Hair auto& nextSegment = Segments[i + 1]; // NOTE: Joint offset determines segment length. - auto jointOffset = GetJointOffset(ID_HAIR, i); + auto jointOffset = GetJointOffset(ObjectID, i); worldMatrix = Matrix::CreateTranslation(segment.Position); worldMatrix = Matrix::CreateFromQuaternion(segment.Orientation) * worldMatrix; @@ -67,7 +70,7 @@ namespace TEN::Effects::Hair // Get water height. auto pos = item.Pose.Position + Vector3i(GetWaterProbeOffset(item)); int roomNumber = item.RoomNumber; - int waterHeight = GetWaterHeight(pos.x, pos.y, pos.z, roomNumber); + int waterHeight = GetPointCollision(pos, roomNumber).GetWaterTopHeight(); // Get collision spheres. auto spheres = GetSpheres(item, isYoung); @@ -81,12 +84,10 @@ namespace TEN::Effects::Hair // TR3 UPV uses a hack which forces player water status to dry. // Therefore, cannot directly use water status value to determine enrironment. bool isOnLand = (player.Control.WaterStatus == WaterStatus::Dry && - (player.Context.Vehicle == -1 || g_Level.Items[player.Context.Vehicle].ObjectNumber != ID_UPV)); + (player.Context.Vehicle == NO_VALUE || g_Level.Items[player.Context.Vehicle].ObjectNumber != ID_UPV)); - // Handle segment room collision. + // Handle segment collision. CollideSegmentWithRoom(segment, waterHeight, roomNumber, isOnLand); - - // Handle segment sphere collision. CollideSegmentWithSpheres(segment, spheres); // Calculate orientation. @@ -97,8 +98,8 @@ namespace TEN::Effects::Hair worldMatrix = Matrix::CreateFromQuaternion(prevSegment.Orientation) * worldMatrix; auto jointOffset = (i == (Segments.size() - 1)) ? - GetJointOffset(ID_HAIR, (i - 1) - 1) : - GetJointOffset(ID_HAIR, (i - 1)); + GetJointOffset(ObjectID, (i - 1) - 1) : + GetJointOffset(ObjectID, (i - 1)); worldMatrix = Matrix::CreateTranslation(jointOffset) * worldMatrix; segment.Position = worldMatrix.Translation(); @@ -107,21 +108,21 @@ namespace TEN::Effects::Hair } } - Vector3 HairUnit::GetRelBaseOffset(int hairUnitIndex, bool isYoung) + Vector3 HairUnit::GetRelBaseOffset(int hairUnitID, bool isYoung) { auto relOffset = Vector3::Zero; if (isYoung) { - switch (hairUnitIndex) + switch (hairUnitID) { // Left pigtail offset. case 0: - relOffset = Vector3(-52.0f, -48.0f, -50.0f); + relOffset = Vector3(-48.0f, -48.0f, -50.0f); break; // Right pigtail offset. case 1: - relOffset = Vector3(44.0f, -48.0f, -50.0f); + relOffset = Vector3(48.0f, -48.0f, -50.0f); break; } } @@ -173,13 +174,13 @@ namespace TEN::Effects::Hair Quaternion HairUnit::GetSegmentOrientation(const Vector3& origin, const Vector3& target, const Quaternion& baseOrient) { // Calculate absolute orientation. - auto absDirection = target - origin; - absDirection.Normalize(); - auto absOrient = Geometry::ConvertDirectionToQuat(absDirection); + auto absDir = target - origin; + absDir.Normalize(); + auto absOrient = Geometry::ConvertDirectionToQuat(absDir); // Calculate relative twist rotation. // TODO: Find accurate twist angle based on relation between absOrient and baseOrient. - auto twistAxisAngle = AxisAngle(absDirection, EulerAngles(baseOrient).y); + auto twistAxisAngle = AxisAngle(absDir, EulerAngles(baseOrient).y); auto twistRot = twistAxisAngle.ToQuaternion(); // Return ideal orientation. @@ -196,21 +197,21 @@ namespace TEN::Effects::Hair spheres.reserve(SPHERE_COUNT); // Hips sphere. - auto* meshPtr = &g_Level.Meshes[item.Model.MeshIndex[LM_HIPS]]; - auto pos = GetJointPosition(item, LM_HIPS, Vector3i(meshPtr->sphere.Center)).ToVector3(); - spheres.push_back(BoundingSphere(pos, meshPtr->sphere.Radius)); + const auto* mesh = &g_Level.Meshes[item.Model.MeshIndex[LM_HIPS]]; + auto pos = GetJointPosition(item, LM_HIPS, Vector3i(mesh->sphere.Center)).ToVector3(); + spheres.push_back(BoundingSphere(pos, mesh->sphere.Radius)); // Torso sphere. - meshPtr = &g_Level.Meshes[item.Model.MeshIndex[LM_TORSO]]; - pos = GetJointPosition(item, LM_TORSO, Vector3i(meshPtr->sphere.Center) + TORSO_SPHERE_OFFSET).ToVector3(); - spheres.push_back(BoundingSphere(pos, meshPtr->sphere.Radius)); + mesh = &g_Level.Meshes[item.Model.MeshIndex[LM_TORSO]]; + pos = GetJointPosition(item, LM_TORSO, Vector3i(mesh->sphere.Center) + TORSO_SPHERE_OFFSET).ToVector3(); + spheres.push_back(BoundingSphere(pos, mesh->sphere.Radius)); if (isYoung) spheres.back().Radius = spheres.back().Radius - ((spheres.back().Radius / 4) + (spheres.back().Radius / 8)); // Head sphere. - meshPtr = &g_Level.Meshes[item.Model.MeshIndex[LM_HEAD]]; - pos = GetJointPosition(item, LM_HEAD, Vector3i(meshPtr->sphere.Center) + HEAD_SPHERE_OFFSET).ToVector3(); - spheres.push_back(BoundingSphere(pos, meshPtr->sphere.Radius)); + mesh = &g_Level.Meshes[item.Model.MeshIndex[LM_HEAD]]; + pos = GetJointPosition(item, LM_HEAD, Vector3i(mesh->sphere.Center) + HEAD_SPHERE_OFFSET).ToVector3(); + spheres.push_back(BoundingSphere(pos, mesh->sphere.Radius)); // Neck sphere. spheres.push_back(BoundingSphere( @@ -218,30 +219,30 @@ namespace TEN::Effects::Hair isYoung ? 0.0f : (spheres[2].Radius * 0.75f))); // Left arm sphere. - meshPtr = &g_Level.Meshes[item.Model.MeshIndex[LM_LINARM]]; - pos = GetJointPosition(item, LM_LINARM, Vector3i(meshPtr->sphere.Center)).ToVector3(); - spheres.push_back(BoundingSphere(pos, (meshPtr->sphere.Radius / 3) * 4)); + mesh = &g_Level.Meshes[item.Model.MeshIndex[LM_LINARM]]; + pos = GetJointPosition(item, LM_LINARM, Vector3i(mesh->sphere.Center)).ToVector3(); + spheres.push_back(BoundingSphere(pos, (mesh->sphere.Radius / 3) * 4)); // Right arm sphere. - meshPtr = &g_Level.Meshes[item.Model.MeshIndex[LM_RINARM]]; - pos = GetJointPosition(item, LM_RINARM, Vector3i(meshPtr->sphere.Center)).ToVector3(); - spheres.push_back(BoundingSphere(pos, (meshPtr->sphere.Radius / 3) * 4)); + mesh = &g_Level.Meshes[item.Model.MeshIndex[LM_RINARM]]; + pos = GetJointPosition(item, LM_RINARM, Vector3i(mesh->sphere.Center)).ToVector3(); + spheres.push_back(BoundingSphere(pos, (mesh->sphere.Radius / 3) * 4)); // Left holster sphere. - meshPtr = &g_Level.Meshes[item.Model.MeshIndex[LM_LTHIGH]]; - pos = GetJointPosition(item, LM_LTHIGH, Vector3i(meshPtr->sphere.Center)).ToVector3(); + mesh = &g_Level.Meshes[item.Model.MeshIndex[LM_LTHIGH]]; + pos = GetJointPosition(item, LM_LTHIGH, Vector3i(mesh->sphere.Center)).ToVector3(); spheres.push_back( BoundingSphere( pos + ((spheres[0].Center - pos) / 2), - meshPtr->sphere.Radius)); + mesh->sphere.Radius)); // Right holster sphere. - meshPtr = &g_Level.Meshes[item.Model.MeshIndex[LM_RTHIGH]]; - pos = GetJointPosition(item, LM_RTHIGH, Vector3i(meshPtr->sphere.Center)).ToVector3(); + mesh = &g_Level.Meshes[item.Model.MeshIndex[LM_RTHIGH]]; + pos = GetJointPosition(item, LM_RTHIGH, Vector3i(mesh->sphere.Center)).ToVector3(); spheres.push_back( BoundingSphere( pos + ((spheres[0].Center - pos) / 2), - meshPtr->sphere.Radius)); + mesh->sphere.Radius)); if (isYoung) spheres[1].Center = (spheres[1].Center + spheres[2].Center) / 2; @@ -251,19 +252,18 @@ namespace TEN::Effects::Hair void HairUnit::CollideSegmentWithRoom(HairSegment& segment, int waterHeight, int roomNumber, bool isOnLand) { - constexpr auto VELOCITY_COEFF = 0.75f; + constexpr auto VEL_COEFF = 0.75f; - auto pointColl = GetCollision(segment.Position.x, segment.Position.y, segment.Position.z, roomNumber); - int floorHeight = pointColl.Position.Floor; + auto pointColl = GetPointCollision(segment.Position, roomNumber); Segments[0].Velocity = segment.Position; - segment.Position += segment.Velocity * VELOCITY_COEFF; + segment.Position += segment.Velocity * VEL_COEFF; // Land collision. if (isOnLand) { // Let wind affect position. - if (TestEnvironment(ENV_FLAG_WIND, pointColl.RoomNumber)) + if (TestEnvironment(ENV_FLAG_WIND, pointColl.GetRoomNumber())) segment.Position += Weather.Wind() * 2; // Apply gravity. @@ -275,7 +275,7 @@ namespace TEN::Effects::Hair segment.Position.y = waterHeight; } // Avoid clipping through floor. - else if (floorHeight > Segments[0].Position.y && segment.Position.y > floorHeight) + else if (pointColl.GetFloorHeight() > Segments[0].Position.y && segment.Position.y > pointColl.GetFloorHeight()) { segment.Position = Segments[0].Velocity; } @@ -287,9 +287,9 @@ namespace TEN::Effects::Hair { segment.Position.y = waterHeight; } - else if (segment.Position.y > floorHeight) + else if (segment.Position.y > pointColl.GetFloorHeight()) { - segment.Position.y = floorHeight; + segment.Position.y = pointColl.GetFloorHeight(); } } } @@ -298,57 +298,59 @@ namespace TEN::Effects::Hair { for (const auto& sphere : spheres) { - auto direction = segment.Position - sphere.Center; + auto dir = segment.Position - sphere.Center; - float distance = Vector3::Distance(segment.Position, sphere.Center); - if (distance < sphere.Radius) + float dist = Vector3::Distance(segment.Position, sphere.Center); + if (dist < sphere.Radius) { // Avoid division by zero. - if (distance == 0.0f) - distance = 1.0f; + if (dist == 0.0f) + dist = 1.0f; // Push segment away from sphere. - segment.Position = sphere.Center + (direction * (sphere.Radius / distance)); + segment.Position = sphere.Center + (dir * (sphere.Radius / dist)); } } } void HairEffectController::Initialize() { - constexpr auto ORIENT_DEFAULT = EulerAngles(ANGLE(-90.0f), 0, 0); + constexpr auto DEFAULT_ORIENT = EulerAngles(ANGLE(-90.0f), 0, 0); bool isYoung = (g_GameFlow->GetLevel(CurrentLevel)->GetLaraType() == LaraType::Young); // Initialize hair units. - bool isHead = true; - for (auto& unit : Units) + for (int i = 0; i < Units.size(); i++) { - unit.IsEnabled = (!isHead || isYoung); + auto& unit = Units[i]; + + auto objectID = (i == 0) ? ID_HAIR_PRIMARY : ID_HAIR_SECONDARY; + const auto& object = Objects[objectID]; + + unit.IsEnabled = (object.loaded && (i == 0 || (i == 1 && isYoung))); unit.IsInitialized = false; - - unsigned int segmentCount = Objects[ID_HAIR].nmeshes + 1; - unit.Segments.resize(segmentCount); + unit.ObjectID = objectID; + unit.Segments.resize(object.nmeshes + 1); // Initialize segments. for (auto& segment : unit.Segments) { - segment.Position = GetJointOffset(ID_HAIR, 0); + segment.Position = GetJointOffset(objectID, 0); segment.Velocity = Vector3::Zero; - segment.Orientation = ORIENT_DEFAULT.ToQuaternion(); + segment.Orientation = DEFAULT_ORIENT.ToQuaternion(); } - - isHead = false; } } - void HairEffectController::Update(ItemInfo& item, bool isYoung) + void HairEffectController::Update(ItemInfo& item) { for (int i = 0; i < Units.size(); i++) { - Units[i].Update(item, i); + auto& unit = Units[i]; + if (!unit.IsEnabled) + continue; - if (isYoung && i == 1) - Units[i].Update(item, i); + unit.Update(item, i); } } } diff --git a/TombEngine/Game/effects/hair.h b/TombEngine/Game/effects/hair.h index 8e0a842b6..5abe6acf8 100644 --- a/TombEngine/Game/effects/hair.h +++ b/TombEngine/Game/effects/hair.h @@ -1,5 +1,7 @@ #pragma once +#include "Objects/game_object_ids.h" + struct ItemInfo; namespace TEN::Effects::Hair @@ -8,6 +10,7 @@ namespace TEN::Effects::Hair { private: // Constants + static constexpr auto HAIR_GRAVITY = 10.0f; struct HairSegment @@ -15,20 +18,34 @@ namespace TEN::Effects::Hair Vector3 Position = Vector3::Zero; Vector3 Velocity = Vector3::Zero; Quaternion Orientation = Quaternion::Identity; + + Vector3 PrevPosition = Vector3::Zero; + Quaternion PrevOrientation = Quaternion::Identity; + + void StoreInterpolationData() + { + PrevPosition = Position; + PrevOrientation = Orientation; + } }; public: // Members + bool IsEnabled = false; bool IsInitialized = false; + + GAME_OBJECT_ID ObjectID = GAME_OBJECT_ID::ID_NO_OBJECT; std::vector Segments = {}; // Utilities - void Update(const ItemInfo& item, int hairUnitIndex); + + void Update(const ItemInfo& item, int hairUnitID); private: // Helpers - Vector3 GetRelBaseOffset(int hairUnitIndex, bool isYoung); + + Vector3 GetRelBaseOffset(int hairUnitID, bool isYoung); Vector3 GetWaterProbeOffset(const ItemInfo& item); Quaternion GetSegmentOrientation(const Vector3& origin, const Vector3& target, const Quaternion& baseOrient); std::vector GetSpheres(const ItemInfo& item, bool isYoung); @@ -41,15 +58,18 @@ namespace TEN::Effects::Hair { private: // Constants + static constexpr auto UNIT_COUNT_MAX = 2; public: // Members + std::array Units = {}; // Utilities + void Initialize(); - void Update(ItemInfo& item, bool isYoung); + void Update(ItemInfo& item); }; extern HairEffectController HairEffect; diff --git a/TombEngine/Game/effects/item_fx.cpp b/TombEngine/Game/effects/item_fx.cpp index b3a203a0c..5261e6734 100644 --- a/TombEngine/Game/effects/item_fx.cpp +++ b/TombEngine/Game/effects/item_fx.cpp @@ -3,6 +3,7 @@ #include "Game/collision/collide_room.h" #include "Game/collision/floordata.h" +#include "Game/collision/Point.h" #include "Game/control/control.h" #include "Game/effects/effects.h" #include "Game/effects/smoke.h" @@ -13,6 +14,7 @@ #include "Specific/clock.h" #include "Specific/level.h" +using namespace TEN::Collision::Point; using namespace TEN::Effects::Smoke; namespace TEN::Effects::Items @@ -70,7 +72,7 @@ namespace TEN::Effects::Items if (item->HitPoints < 0) return; - auto height = GetCollision(item->Pose.Position.x, 32000, item->Pose.Position.z, item->RoomNumber).Position.Floor; + auto height = GetPointCollision(Vector3i(item->Pose.Position.x, 32000, item->Pose.Position.z), item->RoomNumber).GetFloorHeight(); if (item->Floor == height) { item->HitPoints = -1; diff --git a/TombEngine/Game/effects/simple_particle.cpp b/TombEngine/Game/effects/simple_particle.cpp index 0165512ac..850c1b98b 100644 --- a/TombEngine/Game/effects/simple_particle.cpp +++ b/TombEngine/Game/effects/simple_particle.cpp @@ -13,9 +13,11 @@ namespace TEN::Effects SimpleParticle& GetFreeSimpleParticle() { - for (auto& p : simpleParticles) - if (!p.active) - return p; + for (auto& part : simpleParticles) + { + if (!part.active) + return part; + } return simpleParticles[0]; } @@ -28,17 +30,18 @@ namespace TEN::Effects float z = std::cos(angle + angleVariation); x = x* -500 + snowMobile->Pose.Position.x; z = z* -500 + snowMobile->Pose.Position.z; - SimpleParticle& p = GetFreeSimpleParticle(); - p = {}; - p.active = true; - p.life = Random::GenerateFloat(8, 14); - p.room = snowMobile->RoomNumber; - p.ageRate = Random::GenerateFloat(0.9f, 1.3f); + + SimpleParticle& part = GetFreeSimpleParticle(); + part = {}; + part.active = true; + part.life = Random::GenerateFloat(8, 14); + part.room = snowMobile->RoomNumber; + part.ageRate = Random::GenerateFloat(0.9f, 1.3f); float size = Random::GenerateFloat(96, 128); - p.worldPosition = {x, float(snowMobile->Pose.Position.y) - size / 2 , z}; - p.sequence = ID_SKIDOO_SNOW_TRAIL_SPRITES; - p.size = Random::GenerateFloat(256, 512); - p.blendMode = BlendMode::AlphaBlend; + part.worldPosition = {x, float(snowMobile->Pose.Position.y) - size / 2 , z}; + part.sequence = ID_SKIDOO_SNOW_TRAIL_SPRITES; + part.size = Random::GenerateFloat(256, 512); + part.blendMode = BlendMode::AlphaBlend; } void TriggerSpeedboatFoam(ItemInfo* boat, Vector3 offset) @@ -47,39 +50,42 @@ namespace TEN::Effects { float size = Random::GenerateFloat(96, 128); float angle = TO_RAD(boat->Pose.Orientation.y); - float angleVariation = i*2*10 * RADIAN; + float angleVariation = i * 2 * 10 * RADIAN; float y = float(boat->Pose.Position.y) - size / 2 + offset.y; float x = std::sin(angle + angleVariation); float z = std::cos(angle + angleVariation); x = x * offset.z + z * offset.x + boat->Pose.Position.x; z = z * offset.z + x * offset.x + boat->Pose.Position.z; - SimpleParticle& p = GetFreeSimpleParticle(); - p = {}; - p.active = true; - p.life = Random::GenerateFloat(5, 9); - p.room = boat->RoomNumber; - p.ageRate = Random::GenerateFloat(0.9f, 1.3f); - p.worldPosition = { x, y, z }; - p.sequence = ID_MOTORBOAT_FOAM_SPRITES; - p.size = Random::GenerateFloat(256, 512); - p.blendMode = BlendMode::Additive; + + auto& part = GetFreeSimpleParticle(); + part = {}; + part.active = true; + part.life = Random::GenerateFloat(5, 9); + part.room = boat->RoomNumber; + part.ageRate = Random::GenerateFloat(0.9f, 1.3f); + part.worldPosition = { x, y, z }; + part.sequence = ID_MOTORBOAT_FOAM_SPRITES; + part.size = Random::GenerateFloat(256, 512); + part.blendMode = BlendMode::Additive; } } void UpdateSimpleParticles() { - for (auto& p : simpleParticles) + for (auto& part : simpleParticles) { - if (!p.active) + if (!part.active) continue; - p.age+= p.ageRate; - if (p.life < p.age) - p.active = false; + part.StoreInterpolationData(); - int numSprites = -Objects[p.sequence].nmeshes - 1; - float normalizedAge = p.age / p.life; - p.sprite = Lerp(0.0f, numSprites, normalizedAge); + part.age+= part.ageRate; + if (part.life < part.age) + part.active = false; + + int spriteCount = -Objects[part.sequence].nmeshes - 1; + float normalizedAge = part.age / part.life; + part.sprite = Lerp(0.0f, spriteCount, normalizedAge); } } } diff --git a/TombEngine/Game/effects/simple_particle.h b/TombEngine/Game/effects/simple_particle.h index cd32afa42..42811cf23 100644 --- a/TombEngine/Game/effects/simple_particle.h +++ b/TombEngine/Game/effects/simple_particle.h @@ -1,14 +1,17 @@ #pragma once #include "Objects\objectslist.h" +#include enum class BlendMode; struct ItemInfo; namespace TEN::Effects { + using namespace DirectX::SimpleMath; + struct SimpleParticle { - DirectX::SimpleMath::Vector3 worldPosition; + Vector3 worldPosition; float size; float age; float ageRate; @@ -18,6 +21,15 @@ namespace TEN::Effects GAME_OBJECT_ID sequence; bool active; BlendMode blendMode; + + Vector3 PrevWorldPosition = Vector3::Zero; + float PrevSize = 0.0f; + + void StoreInterpolationData() + { + PrevWorldPosition = worldPosition; + PrevSize = size; + } }; extern std::array simpleParticles; diff --git a/TombEngine/Game/effects/smoke.cpp b/TombEngine/Game/effects/smoke.cpp index d9f6727a2..ed5c78535 100644 --- a/TombEngine/Game/effects/smoke.cpp +++ b/TombEngine/Game/effects/smoke.cpp @@ -45,6 +45,8 @@ namespace TEN::Effects::Smoke if (!s.active) continue; + s.StoreInterpolationData(); + s.age += 1; if (s.age > s.life) { diff --git a/TombEngine/Game/effects/smoke.h b/TombEngine/Game/effects/smoke.h index 33e146fd8..5386fe12e 100644 --- a/TombEngine/Game/effects/smoke.h +++ b/TombEngine/Game/effects/smoke.h @@ -27,6 +27,19 @@ namespace TEN::Effects::Smoke float terminalVelocity; bool affectedByWind; bool active; + + Vector3 PrevPosition = Vector3::Zero; + float PrevRotation = 0.0f; + Vector4 PrevColor = Vector4::Zero; + float PrevSize = 0.0f; + + void StoreInterpolationData() + { + PrevPosition = position; + PrevRotation = rotation; + PrevColor = color; + PrevSize = size; + } }; extern std::array SmokeParticles; diff --git a/TombEngine/Game/effects/spark.cpp b/TombEngine/Game/effects/spark.cpp index 3cf7fcb9f..521906e6d 100644 --- a/TombEngine/Game/effects/spark.cpp +++ b/TombEngine/Game/effects/spark.cpp @@ -23,6 +23,8 @@ namespace TEN::Effects::Spark if (!s.active) continue; + + s.StoreInterpolationData(); s.age += 1; if (s.age > s.life) diff --git a/TombEngine/Game/effects/spark.h b/TombEngine/Game/effects/spark.h index 308d43f41..d925ae26e 100644 --- a/TombEngine/Game/effects/spark.h +++ b/TombEngine/Game/effects/spark.h @@ -22,6 +22,15 @@ namespace TEN::Effects::Spark float width; float height; bool active; + + Vector3 PrevPosition = Vector3::Zero; + Vector3 PrevVelocity = Vector3::Zero; + + void StoreInterpolationData() + { + PrevPosition = pos; + PrevVelocity = velocity; + } }; extern std::array SparkParticles; diff --git a/TombEngine/Game/effects/tomb4fx.cpp b/TombEngine/Game/effects/tomb4fx.cpp index 2d128dc87..cf83fe7bf 100644 --- a/TombEngine/Game/effects/tomb4fx.cpp +++ b/TombEngine/Game/effects/tomb4fx.cpp @@ -5,6 +5,7 @@ #include "Game/animation.h" #include "Game/collision/collide_room.h" #include "Game/collision/floordata.h" +#include "Game/collision/Point.h" #include "Game/effects/effects.h" #include "Game/effects/Bubble.h" #include "Game/effects/debris.h" @@ -27,6 +28,7 @@ using namespace TEN::Effects::Environment; using namespace TEN::Effects::Ripple; using namespace TEN::Effects::Smoke; using namespace TEN::Collision::Floordata; +using namespace TEN::Collision::Point; using namespace TEN::Math; using TEN::Renderer::g_Renderer; @@ -51,7 +53,7 @@ SMOKE_SPARKS SmokeSparks[MAX_SPARKS_SMOKE]; GUNSHELL_STRUCT Gunshells[MAX_GUNSHELL]; BLOOD_STRUCT Blood[MAX_SPARKS_BLOOD]; SHOCKWAVE_STRUCT ShockWaves[MAX_SHOCKWAVE]; -FIRE_LIST Fires[MAX_FIRE_LIST]; +std::vector Fires; int GetFreeFireSpark() { @@ -109,15 +111,15 @@ void TriggerGlobalStaticFlame() spark->fadeToBlack = 0; spark->life = 8; spark->sLife = 8; - spark->y = 0; - spark->x = (GetRandomControl() & 7) - 4; + spark->position = Vector3i( + (GetRandomControl() & 7) - 4, + 0, + (GetRandomControl() & 7) - 4 + ); spark->maxYvel = 0; spark->gravity = 0; - spark->z = (GetRandomControl() & 7) - 4; spark->friction = 0; - spark->xVel = 0; - spark->yVel = 0; - spark->zVel = 0; + spark->velocity = Vector3i::Zero; spark->flags = SP_NONE; spark->dSize = spark->sSize = spark->size = (GetRandomControl() & 0x1F) + -128; } @@ -136,12 +138,16 @@ void TriggerGlobalFireSmoke() spark->fadeToBlack = 16; spark->colFadeSpeed = (GetRandomControl() & 7) + 32; spark->life = spark->sLife = (GetRandomControl() & 0xF) + 57; - spark->x = (GetRandomControl() & 0xF) - 8; - spark->y = -256 - (GetRandomControl() & 0x7F); - spark->z = (GetRandomControl() & 0xF) - 8; - spark->xVel = (GetRandomControl() & 0xFF) - 128; - spark->yVel = -16 - (GetRandomControl() & 0xF); - spark->zVel = (GetRandomControl() & 0xFF) - 128; + spark->position = Vector3i( + (GetRandomControl() & 0xF) - 8, + -256 - (GetRandomControl() & 0x7F), + (GetRandomControl() & 0xF) - 8 + ); + spark->velocity = Vector3i( + (GetRandomControl() & 0xFF) - 128, + -16 - (GetRandomControl() & 0xF), + (GetRandomControl() & 0xFF) - 128 + ); spark->friction = 4; if (GetRandomControl() & 1) @@ -177,12 +183,16 @@ void TriggerGlobalFireFlame() spark->fadeToBlack = 8; spark->colFadeSpeed = (GetRandomControl() & 3) + 8; spark->life = spark->sLife = (GetRandomControl() & 7) + 32; - spark->y = 0; - spark->x = 4 * (GetRandomControl() & 0x1F) - 64; - spark->z = 4 * (GetRandomControl() & 0x1F) - 64; - spark->xVel = 2 * (GetRandomControl() & 0xFF) - 256; - spark->yVel = -16 - (GetRandomControl() & 0xF); - spark->zVel = 2 * (GetRandomControl() & 0xFF) - 256; + spark->position = Vector3i( + 4 * (GetRandomControl() & 0x1F) - 64, + 0, + 4 * (GetRandomControl() & 0x1F) - 64 + ); + spark->velocity = Vector3i( + 2 * (GetRandomControl() & 0xFF) - 256, + -16 - (GetRandomControl() & 0xF), + 2 * (GetRandomControl() & 0xFF) - 256 + ); spark->friction = 5; spark->gravity = -32 - (GetRandomControl() & 0x1F); spark->maxYvel = -16 - (GetRandomControl() & 7); @@ -256,30 +266,23 @@ void TriggerPilotFlame(int itemNumber, int nodeIndex) spark->dSize = size; } -Particle* SetupPoisonSpark(Vector3 color) +static Particle& SetupPoisonParticle(const Color& colorStart, const Color& colorEnd) { - auto* spark = GetFreeParticle(); + auto& part = *GetFreeParticle(); + part.sR = std::clamp(colorStart.x * UCHAR_MAX, 0, UCHAR_MAX); + part.sG = std::clamp(colorStart.y * UCHAR_MAX, 0, UCHAR_MAX); + part.sB = std::clamp(colorStart.z * UCHAR_MAX, 0, UCHAR_MAX); + part.dR = std::clamp(colorEnd.x * UCHAR_MAX, 0, UCHAR_MAX); + part.dG = std::clamp(colorEnd.y * UCHAR_MAX, 0, UCHAR_MAX); + part.dB = std::clamp(colorEnd.z * UCHAR_MAX, 0, UCHAR_MAX); + part.colFadeSpeed = 14; + part.fadeToBlack = 8; + part.blendMode = BlendMode::Screen; - bool rMax = color.x > color.y && color.x > color.z; - bool gMax = color.y > color.x && color.y > color.z; - bool bMax = color.z > color.x && color.z > color.y; - - char seed = (GetRandomControl() & 0x1F) + 220; - - spark->sR = (rMax ? seed : 255) * (color.x * 0.4); - spark->sG = (gMax ? seed : 255) * (color.y * 0.4); - spark->sB = (bMax ? seed : 255) * (color.z * 0.4); - spark->dR = 255 * color.x; - spark->dG = 255 * color.y; - spark->dB = 255 * color.z; - spark->colFadeSpeed = 14; - spark->fadeToBlack = 8; - spark->blendMode = BlendMode::Screen; - - return spark; + return part; } -Particle* SetupFireSpark() +static Particle* SetupFireSpark() { auto* spark = GetFreeParticle(); @@ -296,19 +299,20 @@ Particle* SetupFireSpark() return spark; } -void AttachAndCreateSpark(Particle* spark, ItemInfo* item, int meshIndex, Vector3i offset, Vector3i vel) +static void AttachAndCreateSpark(Particle* spark, const ItemInfo* item, int meshID, Vector3i offset, Vector3i vel, int spriteID = 0) { - auto pos1 = GetJointPosition(item, meshIndex, Vector3i(-4, -30, -4) + offset); + auto pos1 = GetJointPosition(*item, meshID, Vector3i(-4, -30, -4) + offset); spark->x = (GetRandomControl() & 0x1F) + pos1.x - 16; spark->y = (GetRandomControl() & 0x1F) + pos1.y - 16; spark->z = (GetRandomControl() & 0x1F) + pos1.z - 16; - auto pos2 = GetJointPosition(item, meshIndex, Vector3i(-4, -30, -4) + offset + vel); + auto pos2 = GetJointPosition(*item, meshID, Vector3i(-4, -30, -4) + offset + vel); int v = (GetRandomControl() & 0x3F) + 192; spark->life = spark->sLife = v / 6; + spark->spriteIndex = Objects[ID_DEFAULT_SPRITES].meshIndex + spriteID; spark->xVel = v * (pos2.x - pos1.x) / 10; spark->yVel = v * (pos2.y - pos1.y) / 10; @@ -327,39 +331,39 @@ void AttachAndCreateSpark(Particle* spark, ItemInfo* item, int meshIndex, Vector spark->on = 1; } -void ThrowFire(int itemNumber, int meshIndex, const Vector3i& offset, const Vector3i& vel) +void ThrowFire(int itemNumber, int meshID, const Vector3i& offset, const Vector3i& vel, int spriteID) { auto& item = g_Level.Items[itemNumber]; for (int i = 0; i < 3; i++) { - auto& spark = *SetupFireSpark(); - AttachAndCreateSpark(&spark, &item, meshIndex, offset, vel); + auto& part = *SetupFireSpark(); + AttachAndCreateSpark(&part, &item, meshID, offset, vel, spriteID); - spark.flags = SP_FIRE | SP_SCALE | SP_DEF | SP_ROTATE | SP_EXPDEF; + part.flags = SP_FIRE | SP_SCALE | SP_DEF | SP_ROTATE | SP_EXPDEF; } } -void ThrowFire(int itemNumber, const CreatureBiteInfo& bite, const Vector3i& vel) +void ThrowFire(int itemNumber, const CreatureBiteInfo& bite, const Vector3i& vel, int spriteID) { - ThrowFire(itemNumber, bite.BoneID, bite.Position, vel); + ThrowFire(itemNumber, bite.BoneID, bite.Position, vel, spriteID); } -void ThrowPoison(int itemNumber, int meshIndex, const Vector3i& offset, const Vector3i& vel, const Vector3& color) +void ThrowPoison(const ItemInfo& item, int boneID, const Vector3& offset, const Vector3& vel, const Color& colorStart, const Color& colorEnd, int spriteID) { - auto* item = &g_Level.Items[itemNumber]; + constexpr auto COUNT = 2; - for (int i = 0; i < 2; i++) + for (int i = 0; i < COUNT; i++) { - auto* spark = SetupPoisonSpark(color); - AttachAndCreateSpark(spark, item, meshIndex, offset, vel); - spark->flags = SP_POISON | SP_SCALE | SP_DEF | SP_ROTATE | SP_EXPDEF; + auto& part = SetupPoisonParticle(colorStart, colorEnd); + AttachAndCreateSpark(&part, &item, boneID, offset, vel, spriteID); + part.flags = SP_POISON | SP_SCALE | SP_DEF | SP_ROTATE | SP_EXPDEF; } } -void ThrowPoison(int itemNumber, const CreatureBiteInfo& bite, const Vector3i& vel, const Vector3& color) +void ThrowPoison(const ItemInfo& item, const CreatureBiteInfo& bite, const Vector3& vel, const Color& colorStart, const Color& colorEnd, int spriteID) { - ThrowPoison(itemNumber, bite.BoneID, bite.Position, vel, color); + ThrowPoison(item, bite.BoneID, bite.Position, vel, colorStart, colorEnd, spriteID); } void UpdateFireProgress() @@ -375,31 +379,20 @@ void UpdateFireProgress() void AddFire(int x, int y, int z, short roomNum, float size, short fade) { - FIRE_LIST* fptr = &Fires[0]; - int i = 0; - while (fptr->on) - { - fptr++; - if (++i >= MAX_FIRE_LIST) - return; - } + FIRE_LIST newFire; - if (fade) - fptr->on = fade; - else - fptr->on = 1; - - fptr->x = x; - fptr->y = y; - fptr->z = z; - fptr->roomNumber = roomNum; - fptr->size = size; + newFire.fade = (fade == 0 ? 1 : (unsigned char)fade); + newFire.position = Vector3i(x, y, z); + newFire.roomNumber = roomNum; + newFire.size = size; + newFire.StoreInterpolationData(); + + Fires.push_back(newFire); } void ClearFires() { - for (int i = 0; i < MAX_FIRE_LIST; i++) - Fires[i].on = false; + Fires.clear(); } void UpdateFireSparks() @@ -420,29 +413,33 @@ void UpdateFireSparks() continue; } + spark->StoreInterpolationData(); + if (spark->sLife - spark->life < spark->colFadeSpeed) { int dl = ((spark->sLife - spark->life) << 16) / spark->colFadeSpeed; - spark->r = spark->sR + (dl * (spark->dR - spark->sR) >> 16); - spark->g = spark->sG + (dl * (spark->dG - spark->sG) >> 16); - spark->b = spark->sB + (dl * (spark->dB - spark->sB) >> 16); + spark->color = Vector3i( + spark->sR + (dl * (spark->dR - spark->sR) >> 16), + spark->sG + (dl * (spark->dG - spark->sG) >> 16), + spark->sB + (dl * (spark->dB - spark->sB) >> 16) + ); } else if (spark->life >= spark->fadeToBlack) { - spark->r = spark->dR; - spark->g = spark->dG; - spark->b = spark->dB; + spark->color = Vector3i(spark->dR, spark->dG, spark->dB); } else { int dl = ((spark->life - spark->fadeToBlack) << 16) / spark->fadeToBlack + 0x10000; - spark->r = dl * spark->dR >> 16; - spark->g = dl * spark->dG >> 16; - spark->b = dl * spark->dB >> 16; + spark->color = Vector3i( + dl * spark->dR >> 16, + dl * spark->dG >> 16, + dl * spark->dB >> 16 + ); - if (spark->r < 8 && spark->g < 8 && spark->b < 8) + if (spark->color.x < 8 && spark->color.y < 8 && spark->color.z < 8) { spark->on = false; continue; @@ -457,23 +454,21 @@ void UpdateFireSparks() spark->def = sprite; int dl = ((spark->sLife - spark->life) << 16) / spark->sLife; - spark->yVel += spark->gravity; + spark->velocity.y += spark->gravity; if (spark->maxYvel) { - if ((spark->yVel < 0 && spark->yVel < (spark->maxYvel << 5)) || - (spark->yVel > 0 && spark->yVel > (spark->maxYvel << 5))) - spark->yVel = spark->maxYvel << 5; + if ((spark->velocity.y < 0 && spark->velocity.y < (spark->maxYvel << 5)) || + (spark->velocity.y > 0 && spark->velocity.y > (spark->maxYvel << 5))) + spark->velocity.y = spark->maxYvel << 5; } if (spark->friction) { - spark->xVel -= spark->xVel >> spark->friction; - spark->zVel -= spark->zVel >> spark->friction; + spark->velocity.x -= spark->velocity.x >> spark->friction; + spark->velocity.z -= spark->velocity.z >> spark->friction; } - spark->x += spark->xVel / 48; - spark->y += spark->yVel / 48; - spark->z += spark->zVel / 48; + spark->position += spark->velocity / 48; spark->size = spark->sSize + ((dl * (spark->dSize - spark->sSize)) / 65536); } @@ -534,6 +529,8 @@ void UpdateSmoke() continue; } + spark->StoreInterpolationData(); + if (spark->sLife - spark->life >= spark->colFadeSpeed) { if (spark->life >= spark->fadeToBlack) @@ -572,45 +569,45 @@ void UpdateSmoke() int dl = ((spark->sLife - spark->life) << 16) / spark->sLife; - spark->yVel += spark->gravity; + spark->velocity.y += spark->gravity; if (spark->maxYvel != 0) { - if (spark->yVel < 0) + if (spark->velocity.y < 0) { - if (spark->yVel < spark->maxYvel) + if (spark->velocity.y < spark->maxYvel) { - spark->yVel = spark->maxYvel; + spark->velocity.y = spark->maxYvel; } } else { - if (spark->yVel > spark->maxYvel) + if (spark->velocity.y > spark->maxYvel) { - spark->yVel = spark->maxYvel; + spark->velocity.y = spark->maxYvel; } } } if (spark->friction & 0xF) { - spark->xVel -= spark->xVel >> (spark->friction & 0xF); - spark->zVel -= spark->zVel >> (spark->friction & 0xF); + spark->velocity.x -= spark->velocity.x >> (spark->friction & 0xF); + spark->velocity.z -= spark->velocity.z >> (spark->friction & 0xF); } if (spark->friction & 0xF0) { - spark->yVel -= spark->yVel >> (spark->friction >> 4); + spark->velocity.y -= spark->velocity.y >> (spark->friction >> 4); } - spark->x += spark->xVel >> 5; - spark->y += spark->yVel >> 5; - spark->z += spark->zVel >> 5; + spark->position.x += spark->velocity.x >> 5; + spark->position.y += spark->velocity.y >> 5; + spark->position.z += spark->velocity.z >> 5; if (spark->flags & SP_WIND) { - spark->x += Weather.Wind().x; - spark->z += Weather.Wind().z; + spark->position.x += Weather.Wind().x; + spark->position.z += Weather.Wind().z; } spark->size = spark->sSize + (dl * (spark->dSize - spark->sSize) >> 16); @@ -649,12 +646,12 @@ void TriggerShatterSmoke(int x, int y, int z) spark->fadeToBlack = 24 - (GetRandomControl() & 7); spark->blendMode = BlendMode::Additive; spark->life = spark->sLife = (GetRandomControl() & 7) + 48; - spark->x = (GetRandomControl() & 0x1F) + x - 16; - spark->y = (GetRandomControl() & 0x1F) + y - 16; - spark->z = (GetRandomControl() & 0x1F) + z - 16; - spark->xVel = 2 * (GetRandomControl() & 0x1FF) - 512; - spark->yVel = 2 * (GetRandomControl() & 0x1FF) - 512; - spark->zVel = 2 * (GetRandomControl() & 0x1FF) - 512; + spark->position.x = (GetRandomControl() & 0x1F) + x - 16; + spark->position.y = (GetRandomControl() & 0x1F) + y - 16; + spark->position.z = (GetRandomControl() & 0x1F) + z - 16; + spark->velocity.x = 2 * (GetRandomControl() & 0x1FF) - 512; + spark->velocity.y = 2 * (GetRandomControl() & 0x1FF) - 512; + spark->velocity.z = 2 * (GetRandomControl() & 0x1FF) - 512; spark->friction = 7; if (GetRandomControl() & 1) @@ -748,6 +745,13 @@ void TriggerBlood(int x, int y, int z, int unk, int num) int size = (GetRandomControl() & 7) + 8; blood->sSize = blood->size = size; blood->dSize = size >> 2; + + blood->oldX = blood->x; + blood->oldY = blood->y; + blood->oldZ = blood->z; + blood->oldRotAng = blood->rotAng; + blood->oldSize = blood->size; + blood->oldShade = blood->shade; } } @@ -767,6 +771,8 @@ void UpdateBlood() continue; } + blood->StoreInterpolationData(); + if (blood->sLife - blood->life >= blood->colFadeSpeed) { if (blood->life >= blood->fadeToBlack) @@ -957,6 +963,8 @@ void UpdateGunShells() if (gunshell->counter) { + gunshell->StoreInterpolationData(); + auto prevPos = gunshell->pos.Position; gunshell->counter--; @@ -993,10 +1001,10 @@ void UpdateGunShells() !TestEnvironment(ENV_FLAG_WATER, prevRoomNumber)) { - SpawnSplashDrips(Vector3(gunshell->pos.Position.x, g_Level.Rooms[gunshell->roomNumber].maxceiling, gunshell->pos.Position.z), gunshell->roomNumber, 3, true); + SpawnSplashDrips(Vector3(gunshell->pos.Position.x, g_Level.Rooms[gunshell->roomNumber].TopHeight, gunshell->pos.Position.z), gunshell->roomNumber, 3, true); //AddWaterSparks(gs->pos.Position.x, g_Level.Rooms[gs->roomNumber].maxceiling, gs->pos.Position.z, 8); SpawnRipple( - Vector3(gunshell->pos.Position.x, g_Level.Rooms[gunshell->roomNumber].maxceiling, gunshell->pos.Position.z), + Vector3(gunshell->pos.Position.x, g_Level.Rooms[gunshell->roomNumber].TopHeight, gunshell->pos.Position.z), gunshell->roomNumber, Random::GenerateFloat(8.0f, 12.0f), (int)RippleFlags::SlowFade); @@ -1123,7 +1131,7 @@ void TriggerUnderwaterExplosion(ItemInfo* item, int flag) TriggerExplosionBubbles(x, y, z, item->RoomNumber); TriggerExplosionSparks(x, y, z, 2, -1, 1, item->RoomNumber); - int waterHeight = GetWaterHeight(x, y, z, item->RoomNumber); + int waterHeight = GetPointCollision(Vector3i(x, y, z), item->RoomNumber).GetWaterTopHeight(); if (waterHeight != NO_HEIGHT) SomeSparkEffect(x, waterHeight, z, 8); } @@ -1135,7 +1143,7 @@ void TriggerUnderwaterExplosion(ItemInfo* item, int flag) for (int i = 0; i < 3; i++) TriggerExplosionSparks(item->Pose.Position.x, item->Pose.Position.y, item->Pose.Position.z, 2, -1, 1, item->RoomNumber); - int waterHeight = GetWaterHeight(item->Pose.Position.x, item->Pose.Position.y, item->Pose.Position.z, item->RoomNumber); + int waterHeight = GetPointCollision(*item).GetWaterTopHeight(); if (waterHeight != NO_HEIGHT) { int dy = item->Pose.Position.y - waterHeight; @@ -1366,6 +1374,8 @@ void UpdateShockwaves() if (shockwave.life <= 0) continue; + shockwave.StoreInterpolationData(); + shockwave.life--; // Spawn light. diff --git a/TombEngine/Game/effects/tomb4fx.h b/TombEngine/Game/effects/tomb4fx.h index ae58bd055..8c0d23c28 100644 --- a/TombEngine/Game/effects/tomb4fx.h +++ b/TombEngine/Game/effects/tomb4fx.h @@ -27,12 +27,8 @@ enum BodyPartFlags struct SMOKE_SPARKS { - int x; - int y; - int z; - int xVel; - int yVel; - int zVel; + Vector3i position; + Vector3i velocity; int gravity; short rotAng; short flags; @@ -56,6 +52,21 @@ struct SMOKE_SPARKS byte fxObj; byte nodeNumber; byte mirror; + + Vector3i oldPosition; + byte oldShade; + byte oldSize; + byte oldScalar; + short oldRotAng; + + void StoreInterpolationData() + { + oldPosition = position; + oldShade = shade; + oldSize = size; + oldScalar = scalar; + oldRotAng = rotAng; + } }; struct SHOCKWAVE_STRUCT @@ -84,6 +95,21 @@ struct SHOCKWAVE_STRUCT bool fadeIn = false; bool HasLight = false; + + short oldInnerRad; + short oldOuterRad; + byte oldR; + byte oldG; + byte oldB; + + void StoreInterpolationData() + { + oldInnerRad = innerRad; + oldOuterRad = outerRad; + oldR = r; + oldG = g; + oldB = b; + } }; struct GUNSHELL_STRUCT @@ -95,6 +121,13 @@ struct GUNSHELL_STRUCT short counter; short dirXrot; short objectNumber; + + Pose oldPos; + + void StoreInterpolationData() + { + oldPos = pos; + } }; struct DRIP_STRUCT @@ -112,26 +145,49 @@ struct DRIP_STRUCT short roomNumber; byte outside; byte pad; + + int oldX; + int oldY; + int oldZ; + byte oldR; + byte oldG; + byte oldB; + + void StoreInterpolationData() + { + oldX = x; + oldY = y; + oldZ = z; + oldR = r; + oldG = g; + oldB = b; + } }; struct FIRE_LIST { - int x; - int y; - int z; - byte on; + Vector3i position; + unsigned char fade; float size; short roomNumber; + + Vector3i oldPosition; + float oldSize; + byte oldFade; + + void StoreInterpolationData() + { + oldPosition = position; + oldSize = size; + oldFade = fade; + } }; struct FIRE_SPARKS { - short x; - short y; - short z; - short xVel; - short yVel; - short zVel; + Vector3i position; + Vector3i velocity; + Vector3i color; short gravity; short rotAng; short flags; @@ -150,13 +206,25 @@ struct FIRE_SPARKS unsigned char dR; unsigned char dG; unsigned char dB; - unsigned char r; - unsigned char g; - unsigned char b; unsigned char colFadeSpeed; unsigned char fadeToBlack; unsigned char sLife; unsigned char life; + + Vector3i oldPosition; + Vector3i oldColor; + unsigned char oldScalar; + unsigned char oldSize; + short oldRotAng; + + void StoreInterpolationData() + { + oldPosition = position; + oldColor = color; + oldScalar = scalar; + oldSize = size; + oldRotAng = rotAng; + } }; struct BLOOD_STRUCT @@ -183,6 +251,23 @@ struct BLOOD_STRUCT byte sLife; byte life; byte pad; + + int oldX; + int oldY; + int oldZ; + short oldRotAng; + byte oldShade; + byte oldSize; + + void StoreInterpolationData() + { + oldX = x; + oldY = y; + oldZ = z; + oldRotAng = rotAng; + oldShade = shade; + oldSize = size; + } }; enum class ShockwaveStyle @@ -209,7 +294,6 @@ extern int NextSpider; extern int NextGunShell; constexpr auto MAX_SPARKS_FIRE = 20; -constexpr auto MAX_FIRE_LIST = 32; constexpr auto MAX_SPARKS_SMOKE = 32; constexpr auto MAX_SPARKS_BLOOD = 32; constexpr auto MAX_GUNFLASH = 4; @@ -221,7 +305,7 @@ extern SMOKE_SPARKS SmokeSparks[MAX_SPARKS_SMOKE]; extern GUNSHELL_STRUCT Gunshells[MAX_GUNSHELL]; extern BLOOD_STRUCT Blood[MAX_SPARKS_BLOOD]; extern SHOCKWAVE_STRUCT ShockWaves[MAX_SHOCKWAVE]; -extern FIRE_LIST Fires[MAX_FIRE_LIST]; +extern std::vector Fires; void TriggerBlood(int x, int y, int z, int unk, int num); void TriggerExplosionBubble(int x, int y, int z, short roomNumber); @@ -230,13 +314,13 @@ void TriggerGlobalStaticFlame(); void TriggerGlobalFireSmoke(); void TriggerGlobalFireFlame(); void TriggerPilotFlame(int itemNumber, int nodeIndex); -void ThrowFire(int itemNumber, int meshIndex, const Vector3i& offset, const Vector3i& vel); -void ThrowFire(int itemNumber, const CreatureBiteInfo& bite, const Vector3i& vel); -void ThrowPoison(int itemNumber, int meshIndex, const Vector3i& offset, const Vector3i& vel, const Vector3& color); -void ThrowPoison(int itemNumber, const CreatureBiteInfo& bite, const Vector3i& vel, const Vector3& color); +void ThrowFire(int itemNumber, int meshID, const Vector3i& offset, const Vector3i& vel, int spriteID = 0); +void ThrowFire(int itemNumber, const CreatureBiteInfo& bite, const Vector3i& vel, int spriteID = 0); +void ThrowPoison(const ItemInfo& item, int boneID, const Vector3& offset, const Vector3& vel, const Color& colorStart, const Color& colorEnd, int spriteID = 0); +void ThrowPoison(const ItemInfo& item, const CreatureBiteInfo& bite, const Vector3& vel, const Color& colorStart, const Color& colorEnd, int spriteID = 0); void UpdateFireProgress(); void ClearFires(); -void AddFire(int x, int y, int z, short roomNum, float size, short fade); +void AddFire(int x, int y, int z, short roomNum, float size, short fade = 1); void UpdateFireSparks(); int GetFreeSmokeSpark(); void UpdateSmoke(); diff --git a/TombEngine/Game/effects/weather.cpp b/TombEngine/Game/effects/weather.cpp index c5c378d71..e4e2c95f6 100644 --- a/TombEngine/Game/effects/weather.cpp +++ b/TombEngine/Game/effects/weather.cpp @@ -3,48 +3,30 @@ #include "Game/camera.h" #include "Game/collision/collide_room.h" +#include "Game/collision/Point.h" #include "Game/effects/effects.h" #include "Game/effects/Ripple.h" #include "Game/effects/tomb4fx.h" #include "Game/savegame.h" #include "Game/Setup.h" -#include "Math/Random.h" +#include "Math.h" +#include "Objects/game_object_ids.h" #include "Sound/sound.h" -#include "Specific/level.h" #include "Scripting/Include/ScriptInterfaceLevel.h" +#include "Specific/level.h" +using namespace TEN::Collision::Point; using namespace TEN::Effects::Ripple; -using namespace TEN::Math::Random; +using namespace TEN::Math; namespace TEN::Effects::Environment { - constexpr auto WEATHER_PARTICLES_SPAWN_DENSITY = 32; - constexpr auto WEATHER_PARTICLES_MAX_COUNT = 2048; - constexpr auto WEATHER_PARTICLES_MAX_COLL_CHECK_DELAY = 5.0f; - - constexpr auto MAX_DUST_SIZE = 25.0f; - constexpr auto MAX_SNOW_SIZE = 32.0f; - constexpr auto MAX_RAIN_SIZE = 128.0f; - - constexpr auto WEATHER_PARTICLE_HORIZONTAL_SPEED = 8.0f; - constexpr auto MAX_SNOW_SPEED = 128.0f; - constexpr auto MAX_RAIN_SPEED = 256.0f; - constexpr auto MAX_DUST_SPEED = 1.0f; - - constexpr auto WEATHER_PARTICLES_TRANSPARENCY = 0.8f; - constexpr auto WEATHER_PARTICLES_NEAR_DEATH_LIFE_VALUE = 20.0f; - constexpr auto WEATHER_PARTICLES_NEAR_DEATH_MELT_FACTOR = 1.0f - (1.0f / (WEATHER_PARTICLES_NEAR_DEATH_LIFE_VALUE * 2)); - - constexpr auto DUST_SPAWN_DENSITY = 300; - constexpr auto DUST_LIFE = 40; - constexpr auto DUST_SPAWN_RADIUS = (10 * 1024); - EnvironmentController Weather; float WeatherParticle::Transparency() const { - float result = WEATHER_PARTICLES_TRANSPARENCY; - float fade = WEATHER_PARTICLES_NEAR_DEATH_LIFE_VALUE; + float result = WEATHER_PARTICLE_OPACITY; + float fade = WEATHER_PARTICLE_NEAR_DEATH_LIFE; if (Life <= fade) result *= Life / fade; @@ -60,39 +42,49 @@ namespace TEN::Effects::Environment EnvironmentController::EnvironmentController() { - Particles.reserve(WEATHER_PARTICLES_MAX_COUNT); + Particles.reserve(WEATHER_PARTICLE_COUNT_MAX); } void EnvironmentController::Update() { - ScriptInterfaceLevel* level = g_GameFlow->GetLevel(CurrentLevel); + const auto& level = *g_GameFlow->GetLevel(CurrentLevel); UpdateSky(level); UpdateStorm(level); UpdateWind(level); UpdateFlash(level); UpdateWeather(level); + UpdateStarfield(level); + SpawnWeatherParticles(level); SpawnDustParticles(level); + SpawnMeteorParticles(level); } void EnvironmentController::Clear() { - // Clear storm vars + // Clear storm variables. StormTimer = 0; StormSkyColor = 1; StormSkyColor2 = 1; - // Clear wind vars - WindCurrent = WindX = WindZ = 0; - WindAngle = WindDAngle = 2048; + // Clear wind variables. + WindCurrent = + WindX = + WindZ = 0; + WindAngle = + WindDAngle = 2048; - // Clear flash vars + // Clear flash variables. FlashProgress = 0.0f; FlashColorBase = Vector3::Zero; - // Clear weather + // Clear weather. Particles.clear(); + + // Clear starfield. + ResetStarField = true; + Meteors.clear(); } void EnvironmentController::Flash(int r, int g, int b, float speed) @@ -104,27 +96,31 @@ namespace TEN::Effects::Environment std::clamp(b, 0, UCHAR_MAX) / (float)UCHAR_MAX); } - void EnvironmentController::UpdateSky(ScriptInterfaceLevel* level) + void EnvironmentController::UpdateSky(const ScriptInterfaceLevel& level) { for (int i = 0; i < 2; i++) { - if (!level->GetSkyLayerEnabled(i)) + if (!level.GetSkyLayerEnabled(i)) continue; - SkyCurrentPosition[i] += level->GetSkyLayerSpeed(i); - if (SkyCurrentPosition[i] <= SKY_SIZE) + auto& skyPos = SkyCurrentPosition[i]; + + skyPos += level.GetSkyLayerSpeed(i); + if (skyPos <= SKY_SIZE) { - if (SkyCurrentPosition[i] < 0) - SkyCurrentPosition[i] += SKY_SIZE; + if (skyPos < 0) + skyPos += SKY_SIZE; } else - SkyCurrentPosition[i] -= SKY_SIZE; + { + skyPos -= SKY_SIZE; + } } } - void EnvironmentController::UpdateStorm(ScriptInterfaceLevel* level) + void EnvironmentController::UpdateStorm(const ScriptInterfaceLevel& level) { - if (level->GetStormEnabled()) + if (level.GetStormEnabled()) { if (StormCount || StormRand) { @@ -132,7 +128,7 @@ namespace TEN::Effects::Environment if (StormTimer > -1) StormTimer--; if (!StormTimer) - SoundEffect(SFX_TR4_THUNDER_RUMBLE, NULL); + SoundEffect(SFX_TR4_THUNDER_RUMBLE, nullptr); } else if (!(rand() & 0x7F)) { @@ -143,11 +139,13 @@ namespace TEN::Effects::Environment for (int i = 0; i < 2; i++) { - auto color = Vector4(level->GetSkyLayerColor(i).GetR() / 255.0f, - level->GetSkyLayerColor(i).GetG() / 255.0f, - level->GetSkyLayerColor(i).GetB() / 255.0f, 1.0f); + auto color = Color( + level.GetSkyLayerColor(i).GetR() / 255.0f, + level.GetSkyLayerColor(i).GetG() / 255.0f, + level.GetSkyLayerColor(i).GetB() / 255.0f, + 1.0f); - if (level->GetStormEnabled()) + if (level.GetStormEnabled()) { auto flashBrightness = StormSkyColor / 255.0f; auto r = std::clamp(color.x + flashBrightness, 0.0f, 1.0f); @@ -157,7 +155,9 @@ namespace TEN::Effects::Environment SkyCurrentColor[i] = Vector4(r, g, b, color.w); } else + { SkyCurrentColor[i] = color; + } } } @@ -172,7 +172,7 @@ namespace TEN::Effects::Environment } else if (StormCount < 5 && StormSkyColor < 50) { - auto newColor = StormSkyColor - StormCount * 2; + auto newColor = StormSkyColor - (StormCount * 2); if (newColor < 0) newColor = 0; StormSkyColor = newColor; @@ -187,20 +187,28 @@ namespace TEN::Effects::Environment } } - void EnvironmentController::UpdateWind(ScriptInterfaceLevel* level) + void EnvironmentController::UpdateWind(const ScriptInterfaceLevel& level) { WindCurrent += (GetRandomControl() & 7) - 3; if (WindCurrent <= -2) + { WindCurrent++; + } else if (WindCurrent >= 9) + { WindCurrent--; + } WindDAngle = (WindDAngle + 2 * (GetRandomControl() & 63) - 64) & 0x1FFE; if (WindDAngle < 1024) + { WindDAngle = 2048 - WindDAngle; + } else if (WindDAngle > 3072) + { WindDAngle += 6144 - 2 * WindDAngle; + } WindAngle = (WindAngle + ((WindDAngle - WindAngle) >> 3)) & 0x1FFE; @@ -208,7 +216,7 @@ namespace TEN::Effects::Environment WindZ = WindCurrent * phd_cos(WindAngle << 3); } - void EnvironmentController::UpdateFlash(ScriptInterfaceLevel* level) + void EnvironmentController::UpdateFlash(const ScriptInterfaceLevel& level) { if (FlashProgress > 0.0f) { @@ -221,184 +229,274 @@ namespace TEN::Effects::Environment FlashColorBase = Vector3::Zero; } - void EnvironmentController::UpdateWeather(ScriptInterfaceLevel* level) + void EnvironmentController::UpdateStarfield(const ScriptInterfaceLevel& level) { - for (auto& p : Particles) + if (!level.GetStarfieldStarsEnabled()) + return; + + if (ResetStarField) { - p.Life -= 2; + int starCount = level.GetStarfieldStarCount(); - // Disable particle if it is dead. It will be cleaned on next call of - // SpawnWeatherParticles(). + Stars.clear(); + Stars.reserve(starCount); - if (p.Life <= 0) + for (int i = 0; i < starCount; i++) { - p.Enabled = false; + auto starDir = Random::GenerateDirectionInCone(-Vector3::UnitY, 70.0f); + starDir.Normalize(); + + auto star = StarParticle{}; + star.Direction = starDir; + star.Color = Vector3( + Random::GenerateFloat(0.6f, 1.0f), + Random::GenerateFloat(0.6f, 1.0f), + Random::GenerateFloat(0.6f, 1.0f)); + star.Scale = Random::GenerateFloat(0.5f, 1.5f); + + float cosine = Vector3::UnitY.Dot(starDir); + float maxCosine = cos(DEG_TO_RAD(50.0f)); + float minCosine = cos(DEG_TO_RAD(70.0f)); + + if (cosine >= minCosine && cosine <= maxCosine) + { + star.Extinction = (cosine - minCosine) / (maxCosine - minCosine); + } + else + { + star.Extinction = 1.0f; + } + + Stars.push_back(star); + } + + ResetStarField = false; + } + + for (auto& star : Stars) + star.Blinking = Random::GenerateFloat(0.5f, 1.0f); + + if (level.GetStarfieldMeteorsEnabled()) + { + for (auto& meteor : Meteors) + { + meteor.Life--; + + if (meteor.Life <= 0) + { + meteor.Active = false; + continue; + } + + meteor.StoreInterpolationData(); + + if (meteor.Life <= METEOR_PARTICLE_FADE_TIME) + { + meteor.Fade = meteor.Life / METEOR_PARTICLE_FADE_TIME; + } + else if (meteor.Life >= METEOR_PARTICLE_LIFE_MAX - METEOR_PARTICLE_FADE_TIME) + { + meteor.Fade = (METEOR_PARTICLE_LIFE_MAX - meteor.Life) / METEOR_PARTICLE_FADE_TIME; + } + else + { + meteor.Fade = 1.0f; + } + + meteor.Position += meteor.Direction * level.GetStarfieldMeteorVelocity(); + } + } + } + + void EnvironmentController::UpdateWeather(const ScriptInterfaceLevel& level) + { + for (auto& part : Particles) + { + part.StoreInterpolationData(); + + part.Life -= 2; + + // Disable particle if dead. Will be cleaned on next call of SpawnWeatherParticles(). + if (part.Life <= 0) + { + part.Enabled = false; continue; } - // Check if particle got out of collision check radius, and fade it out if it did. - - if (abs(Camera.pos.x - p.Position.x) > COLLISION_CHECK_DISTANCE || - abs(Camera.pos.z - p.Position.z) > COLLISION_CHECK_DISTANCE) + // Check if particle got out of collision check radius and fade out if it did. + if (abs(Camera.pos.x - part.Position.x) > COLLISION_CHECK_DISTANCE || + abs(Camera.pos.z - part.Position.z) > COLLISION_CHECK_DISTANCE) { - p.Life = std::clamp(p.Life, 0.0f, WEATHER_PARTICLES_NEAR_DEATH_LIFE_VALUE); + part.Life = std::clamp(part.Life, 0.0f, WEATHER_PARTICLE_NEAR_DEATH_LIFE); } // If particle was locked (after landing or stucking in substance such as water or swamp), - // fade it out and bypass any collision checks and movement updates. - - if (p.Stopped) + // fade out and bypass collision checks and movement updates. + if (part.Stopped) { - if (p.Type == WeatherType::Snow) - p.Size *= WEATHER_PARTICLES_NEAR_DEATH_MELT_FACTOR; + if (part.Type == WeatherType::Snow) + part.Size *= WEATHER_PARTICLE_NEAR_DEATH_MELT_FACTOR; continue; } - // Backup old position and progress new position according to velocity. + // Backup previous position and progress new position according to velocity. + auto prevPos = part.Position; + part.Position.x += part.Velocity.x; + part.Position.z += part.Velocity.z; - auto oldPos = p.Position; - p.Position.x += p.Velocity.x; - p.Position.z += p.Velocity.z; - - switch (p.Type) + switch (part.Type) { case WeatherType::None: - p.Position.y += p.Velocity.y; + part.Position.y += part.Velocity.y; break; case WeatherType::Rain: case WeatherType::Snow: - p.Position.y += (p.Velocity.y / 2.0f); + part.Position.y += (part.Velocity.y / 2.0f); break; } - // Particle is inert, don't check collisions. - - if (p.Type == WeatherType::None) + // Particle is inert; don't check collisions. + if (part.Type == WeatherType::None) continue; - CollisionResult coll; + auto pointColl = GetPointCollision(part.Position, part.RoomNumber); bool collisionCalculated = false; - if (p.CollisionCheckDelay <= 0) + if (part.CollisionCheckDelay <= 0) { - coll = GetCollision(p.Position.x, p.Position.y, p.Position.z, p.Room); + pointColl = GetPointCollision(part.Position, part.RoomNumber); // Determine collision checking frequency based on nearest floor/ceiling surface position. - // If floor and ceiling is too far, don't do precise collision checks, instead doing it + // If floor and ceiling are too far, don't do precise collision checks, instead doing it // every 5th frame. If particle approaches floor or ceiling, make checks more frequent. - // This allows to avoid unnecessary thousands of calls to GetCollisionResult for every particle. + // This avoids calls to GetPointCollision() for every particle. - auto coeff = std::min(std::max(0.0f, (coll.Position.Floor - p.Position.y)), std::max(0.0f, (p.Position.y - coll.Position.Ceiling))); - p.CollisionCheckDelay = std::min(floor(coeff / std::max(std::numeric_limits::denorm_min(), p.Velocity.y)), WEATHER_PARTICLES_MAX_COLL_CHECK_DELAY); + auto coeff = std::min(std::max(0.0f, (pointColl.GetFloorHeight() - part.Position.y)), std::max(0.0f, (part.Position.y - pointColl.GetCeilingHeight()))); + part.CollisionCheckDelay = std::min(floor(coeff / std::max(std::numeric_limits::denorm_min(), part.Velocity.y)), WEATHER_PARTICLE_COLL_CHECK_DELAY_MAX); collisionCalculated = true; } else { - p.CollisionCheckDelay--; + part.CollisionCheckDelay--; } - // Check if particle got out of room bounds - - if (!IsPointInRoom(p.Position, p.Room)) + // Check if particle is beyond room bounds. + if (!IsPointInRoom(part.Position, part.RoomNumber)) { if (!collisionCalculated) { - coll = GetCollision(p.Position.x, p.Position.y, p.Position.z, p.Room); + pointColl = GetPointCollision(part.Position, part.RoomNumber); collisionCalculated = true; } - if (coll.RoomNumber == p.Room) + if (pointColl.GetRoomNumber() == part.RoomNumber) { - p.Enabled = false; // Not landed on door, so out of room bounds - delete + // Not landed on door, so out of room bounds - delete. + part.Enabled = false; continue; } else - p.Room = coll.RoomNumber; + { + part.RoomNumber = pointColl.GetRoomNumber(); + } } // If collision was updated, process with position checks. - if (collisionCalculated) { // If particle is inside water or swamp, count it as "inSubstance". // If particle got below floor or above ceiling, count it as "landed". - - bool inSubstance = g_Level.Rooms[coll.RoomNumber].flags & (ENV_FLAG_WATER | ENV_FLAG_SWAMP); - bool landed = (coll.Position.Floor <= p.Position.y) || (coll.Position.Ceiling >= p.Position.y); + bool inSubstance = g_Level.Rooms[pointColl.GetRoomNumber()].flags & (ENV_FLAG_WATER | ENV_FLAG_SWAMP); + bool landed = (pointColl.GetFloorHeight() <= part.Position.y) || (pointColl.GetCeilingHeight() >= part.Position.y); if (inSubstance || landed) { - p.Stopped = true; - p.Position = oldPos; - p.Life = std::clamp(p.Life, 0.0f, WEATHER_PARTICLES_NEAR_DEATH_LIFE_VALUE); + part.Stopped = true; + part.Position = prevPos; + part.Life = std::clamp(part.Life, 0.0f, WEATHER_PARTICLE_NEAR_DEATH_LIFE); // Produce ripples if particle got into substance (water or swamp). if (inSubstance) - SpawnRipple(p.Position, p.Room, Random::GenerateFloat(16.0f, 24.0f), (int)RippleFlags::SlowFade | (int)RippleFlags::LowOpacity); + SpawnRipple(part.Position, part.RoomNumber, Random::GenerateFloat(16.0f, 24.0f), (int)RippleFlags::SlowFade | (int)RippleFlags::LowOpacity); // Immediately disable rain particle because it doesn't need fading out. - if (p.Type == WeatherType::Rain) + if (part.Type == WeatherType::Rain) { - p.Enabled = false; - AddWaterSparks(oldPos.x, oldPos.y, oldPos.z, 6); + part.Enabled = false; + AddWaterSparks(prevPos.x, prevPos.y, prevPos.z, 6); } } } // Update velocities for every particle type. - - switch (p.Type) + switch (part.Type) { case WeatherType::Snow: - if (p.Velocity.x < (WindX << 2)) - p.Velocity.x += GenerateFloat(0.5f, 2.5f); - else if (p.Velocity.x > (WindX << 2)) - p.Velocity.x -= GenerateFloat(0.5f, 2.5f); + if (part.Velocity.x < (WindX << 2)) + { + part.Velocity.x += Random::GenerateFloat(0.5f, 2.5f); + } + else if (part.Velocity.x > (WindX << 2)) + { + part.Velocity.x -= Random::GenerateFloat(0.5f, 2.5f); + } - if (p.Velocity.z < (WindZ << 2)) - p.Velocity.z += GenerateFloat(0.5f, 2.5f); - else if (p.Velocity.z > (WindZ << 2)) - p.Velocity.z -= GenerateFloat(0.5f, 2.5f); + if (part.Velocity.z < (WindZ << 2)) + { + part.Velocity.z += Random::GenerateFloat(0.5f, 2.5f); + } + else if (part.Velocity.z > (WindZ << 2)) + { + part.Velocity.z -= Random::GenerateFloat(0.5f, 2.5f); + } - if (p.Velocity.y < p.Size / 2) - p.Velocity.y += p.Size / 5.0f; + if (part.Velocity.y < part.Size / 2) + part.Velocity.y += part.Size / 5.0f; break; case WeatherType::Rain: - auto random = GenerateInt(); + auto random = Random::GenerateInt(); if ((random & 3) != 3) { - p.Velocity.x += (float)((random & 3) - 1); - if (p.Velocity.x < -4) - p.Velocity.x = -4; - else if (p.Velocity.x > 4) - p.Velocity.x = 4; + part.Velocity.x += (float)((random & 3) - 1); + if (part.Velocity.x < -4) + { + part.Velocity.x = -4; + } + else if (part.Velocity.x > 4) + { + part.Velocity.x = 4; + } } random = (random >> 2) & 3; if (random != 3) { - p.Velocity.z += random - 1; - if (p.Velocity.z < -4) - p.Velocity.z = -4; - else if (p.Velocity.z > 4) - p.Velocity.z = 4; + part.Velocity.z += random - 1; + if (part.Velocity.z < -4) + { + part.Velocity.z = -4; + } + else if (part.Velocity.z > 4) + { + part.Velocity.z = 4; + } } - if (p.Velocity.y < p.Size * 2 * std::clamp(level->GetWeatherStrength(), 0.6f, 1.0f)) - p.Velocity.y += p.Size / 5.0f; + if (part.Velocity.y < part.Size * 2 * std::clamp(level.GetWeatherStrength(), 0.6f, 1.0f)) + part.Velocity.y += part.Size / 5.0f; break; } } } - void EnvironmentController::SpawnDustParticles(ScriptInterfaceLevel* level) + void EnvironmentController::SpawnDustParticles(const ScriptInterfaceLevel& level) { for (int i = 0; i < DUST_SPAWN_DENSITY; i++) { @@ -406,7 +504,7 @@ namespace TEN::Effects::Environment int yPos = Camera.pos.y + rand() % DUST_SPAWN_RADIUS - DUST_SPAWN_RADIUS / 2.0f; int zPos = Camera.pos.z + rand() % DUST_SPAWN_RADIUS - DUST_SPAWN_RADIUS / 2.0f; - // Use fast GetFloor instead of GetCollision as we spawn a lot of dust. + // Use more memory-efficient GetFloor() instead of GetPointCollision() as a lot of dust may spawn at a time. short roomNumber = Camera.pos.RoomNumber; auto* floor = GetFloor(xPos, yPos, zPos, &roomNumber); @@ -419,13 +517,13 @@ namespace TEN::Effects::Environment auto part = WeatherParticle(); - part.Velocity = Random::GenerateDirection() * MAX_DUST_SPEED; + part.Velocity = Random::GenerateDirection() * DUST_VELOCITY_MAX; - part.Size = GenerateFloat(MAX_DUST_SIZE / 2, MAX_DUST_SIZE); + part.Size = Random::GenerateFloat(DUST_SIZE_MAX / 2, DUST_SIZE_MAX); part.Type = WeatherType::None; - part.Life = DUST_LIFE + GenerateInt(-10, 10); - part.Room = roomNumber; + part.Life = DUST_LIFE + Random::GenerateInt(-10, 10); + part.RoomNumber = roomNumber; part.Position.x = xPos; part.Position.y = yPos; part.Position.z = zPos; @@ -437,38 +535,47 @@ namespace TEN::Effects::Environment } } - void EnvironmentController::SpawnWeatherParticles(ScriptInterfaceLevel* level) + void EnvironmentController::SpawnWeatherParticles(const ScriptInterfaceLevel& level) { - // Clean up dead particles + // Clean up dead particles. if (Particles.size() > 0) - Particles.erase(std::remove_if(Particles.begin(), Particles.end(), [](const WeatherParticle& part) { return !part.Enabled; }), Particles.end()); + { + Particles.erase( + std::remove_if( + Particles.begin(), Particles.end(), + [](const WeatherParticle& part) + { + return !part.Enabled; + }), + Particles.end()); + } - if (level->GetWeatherType() == WeatherType::None || level->GetWeatherStrength() == 0.0f) + if (level.GetWeatherType() == WeatherType::None || level.GetWeatherStrength() == 0.0f) return; int newParticlesCount = 0; - int density = WEATHER_PARTICLES_SPAWN_DENSITY * level->GetWeatherStrength(); + int density = WEATHER_PARTICLE_SPAWN_DENSITY * level.GetWeatherStrength(); - // Snow is falling twice as fast, and must be spawned accordingly fast - if (level->GetWeatherType() == WeatherType::Snow) + // Snow is falling twice as fast and must be spawned accordingly fast. + if (level.GetWeatherType() == WeatherType::Snow) density *= 2; - if (density > 0.0f && level->GetWeatherType() != WeatherType::None) + if (density > 0.0f && level.GetWeatherType() != WeatherType::None) { - while (Particles.size() < WEATHER_PARTICLES_MAX_COUNT) + while (Particles.size() < WEATHER_PARTICLE_COUNT_MAX) { if (newParticlesCount > density) break; newParticlesCount++; - auto distance = level->GetWeatherType() == WeatherType::Snow ? COLLISION_CHECK_DISTANCE : COLLISION_CHECK_DISTANCE / 2; - auto radius = GenerateInt(0, distance); - short angle = GenerateInt(ANGLE(0), ANGLE(180)); + float dist = (level.GetWeatherType() == WeatherType::Snow) ? COLLISION_CHECK_DISTANCE : (COLLISION_CHECK_DISTANCE / 2); + float radius = Random::GenerateInt(0, dist); + short angle = Random::GenerateInt(ANGLE(0), ANGLE(180)); auto xPos = Camera.pos.x + ((int)(phd_cos(angle) * radius)); auto zPos = Camera.pos.z + ((int)(phd_sin(angle) * radius)); - auto yPos = Camera.pos.y - (BLOCK(4) + GenerateInt() & (BLOCK(4) - 1)); + auto yPos = Camera.pos.y - (BLOCK(4) + Random::GenerateInt() & (BLOCK(4) - 1)); auto outsideRoom = IsRoomOutside(xPos, yPos, zPos); @@ -478,33 +585,33 @@ namespace TEN::Effects::Environment if (g_Level.Rooms[outsideRoom].flags & (ENV_FLAG_WATER | ENV_FLAG_SWAMP)) continue; - auto coll = GetCollision(xPos, yPos, zPos, outsideRoom); + auto pointColl = GetPointCollision(Vector3i(xPos, yPos, zPos), outsideRoom); - if (!(coll.Position.Ceiling < yPos || coll.Block->GetNextRoomNumber(Vector3i(xPos, yPos, zPos), false).has_value())) + if (!(pointColl.GetCeilingHeight() < yPos || pointColl.GetSector().GetNextRoomNumber(Vector3i(xPos, yPos, zPos), false).has_value())) continue; auto part = WeatherParticle(); - switch (level->GetWeatherType()) + switch (level.GetWeatherType()) { case WeatherType::Snow: - part.Size = GenerateFloat(MAX_SNOW_SIZE / 3, MAX_SNOW_SIZE); - part.Velocity.y = GenerateFloat(MAX_SNOW_SPEED / 4, MAX_SNOW_SPEED) * (part.Size / MAX_SNOW_SIZE); - part.Life = (MAX_SNOW_SPEED / 3) + ((MAX_SNOW_SPEED / 2) - ((int)part.Velocity.y >> 2)); + part.Size = Random::GenerateFloat(SNOW_SIZE_MAX / 3, SNOW_SIZE_MAX); + part.Velocity.y = Random::GenerateFloat(SNOW_VELOCITY_MAX / 4, SNOW_VELOCITY_MAX) * (part.Size / SNOW_SIZE_MAX); + part.Life = (SNOW_VELOCITY_MAX / 3) + ((SNOW_VELOCITY_MAX / 2) - ((int)part.Velocity.y >> 2)); break; case WeatherType::Rain: - part.Size = GenerateFloat(MAX_RAIN_SIZE / 2, MAX_RAIN_SIZE); - part.Velocity.y = GenerateFloat(MAX_RAIN_SPEED / 2, MAX_RAIN_SPEED) * (part.Size / MAX_RAIN_SIZE) * std::clamp(level->GetWeatherStrength(), 0.6f, 1.0f); - part.Life = (MAX_RAIN_SPEED * 2) - part.Velocity.y; + part.Size = Random::GenerateFloat(RAIN_SIZE_MAX / 2, RAIN_SIZE_MAX); + part.Velocity.y = Random::GenerateFloat(RAIN_VELOCITY_MAX / 2, RAIN_VELOCITY_MAX) * (part.Size / RAIN_SIZE_MAX) * std::clamp(level.GetWeatherStrength(), 0.6f, 1.0f); + part.Life = (RAIN_VELOCITY_MAX * 2) - part.Velocity.y; break; } - part.Velocity.x = GenerateFloat(WEATHER_PARTICLE_HORIZONTAL_SPEED / 2, WEATHER_PARTICLE_HORIZONTAL_SPEED); - part.Velocity.z = GenerateFloat(WEATHER_PARTICLE_HORIZONTAL_SPEED / 2, WEATHER_PARTICLE_HORIZONTAL_SPEED); + part.Velocity.x = Random::GenerateFloat(WEATHER_PARTICLE_HORIZONTAL_VELOCITY / 2, WEATHER_PARTICLE_HORIZONTAL_VELOCITY); + part.Velocity.z = Random::GenerateFloat(WEATHER_PARTICLE_HORIZONTAL_VELOCITY / 2, WEATHER_PARTICLE_HORIZONTAL_VELOCITY); - part.Type = level->GetWeatherType(); - part.Room = outsideRoom; + part.Type = level.GetWeatherType(); + part.RoomNumber = outsideRoom; part.Position.x = xPos; part.Position.y = yPos; part.Position.z = zPos; @@ -517,4 +624,57 @@ namespace TEN::Effects::Environment } } } + + void EnvironmentController::SpawnMeteorParticles(const ScriptInterfaceLevel& level) + { + // Clean up dead particles. + if (!Meteors.empty()) + { + Meteors.erase( + std::remove_if( + Meteors.begin(), Meteors.end(), + [](const MeteorParticle& part) + { + return !part.Active; + }), + Meteors.end()); + } + + if (!level.GetStarfieldMeteorsEnabled()) + return; + + int density = level.GetStarfieldMeteorSpawnDensity(); + if (density > 0) + { + int newParticlesCount = 0; + + while (Meteors.size() < level.GetStarfieldMeteorCount()) + { + if (newParticlesCount > density) + break; + + auto horizontalDir = Random::GenerateDirection2D(); + + auto part = MeteorParticle(); + + part.Active = true; + part.Life = METEOR_PARTICLE_LIFE_MAX; + part.StartPosition = + part.Position = Random::GenerateDirectionInCone(-Vector3::UnitY, 40.0f) * BLOCK(1.5f); + part.Fade = 0.0f; + part.Color = Vector3( + Random::GenerateFloat(0.6f, 1.0f), + Random::GenerateFloat(0.6f, 1.0f), + Random::GenerateFloat(0.6f, 1.0f)); + + part.Direction = Random::GenerateDirectionInCone(Vector3(horizontalDir.x, 0, horizontalDir.y), 10.0f); + if (part.Direction.y < 0.0f) + part.Direction.y = -part.Direction.y; + + Meteors.push_back(part); + + newParticlesCount++; + } + } + } } diff --git a/TombEngine/Game/effects/weather.h b/TombEngine/Game/effects/weather.h index e96f27d76..ad06bbc9b 100644 --- a/TombEngine/Game/effects/weather.h +++ b/TombEngine/Game/effects/weather.h @@ -1,27 +1,101 @@ #pragma once -#include -#include "Scripting/Include/ScriptInterfaceLevel.h" #include "Math/Math.h" +#include "Objects/Effects/LensFlare.h" +#include "Scripting/Include/ScriptInterfaceLevel.h" + +using namespace TEN::Entities::Effects; namespace TEN::Effects::Environment { + constexpr auto WEATHER_PARTICLE_SPAWN_DENSITY = 32; + constexpr auto WEATHER_PARTICLE_COUNT_MAX = 2048; + constexpr auto WEATHER_PARTICLE_COLL_CHECK_DELAY_MAX = 5.0f; + + constexpr auto DUST_SIZE_MAX = 25.0f; + constexpr auto SNOW_SIZE_MAX = 32.0f; + constexpr auto RAIN_SIZE_MAX = 128.0f; + + constexpr auto WEATHER_PARTICLE_HORIZONTAL_VELOCITY = 8.0f; + constexpr auto SNOW_VELOCITY_MAX = 128.0f; + constexpr auto RAIN_VELOCITY_MAX = 256.0f; + constexpr auto DUST_VELOCITY_MAX = 1.0f; + + constexpr auto WEATHER_PARTICLE_OPACITY = 0.8f; + constexpr auto WEATHER_PARTICLE_NEAR_DEATH_LIFE = 20.0f; + constexpr auto WEATHER_PARTICLE_NEAR_DEATH_MELT_FACTOR = 1.0f - (1.0f / (WEATHER_PARTICLE_NEAR_DEATH_LIFE * 2)); + + constexpr auto DUST_SPAWN_DENSITY = 300; + constexpr auto DUST_LIFE = 40; + constexpr auto DUST_SPAWN_RADIUS = BLOCK(10); + + constexpr auto METEOR_PARTICLE_COUNT_MAX = 10; + constexpr auto METEOR_PARTICLE_LIFE_MAX = 150; + constexpr auto METEOR_PARTICLE_VELOCITY = 32.0f; + constexpr auto METEOR_PARTICLE_SPAWN_DENSITY = 4; + constexpr auto METEOR_PARTICLE_FADE_TIME = 30.0f; + + struct StarParticle + { + Vector3 Position = Vector3::Zero; + Vector3 Direction = Vector3::Zero; + Vector3 Color = Vector3::Zero; + + float Extinction = 1.0f; + float Scale = 1.0f; + float Blinking = 1.0f; + }; + + struct MeteorParticle + { + Vector3 Position = Vector3::Zero; + Vector3 Direction = Vector3::Zero; + Vector3 StartPosition = Vector3::Zero; + Vector3 Color = Vector3::Zero; + + bool Active = false; + float Life = 0.0f; + float Fade = 0.0f; + + Vector3 PrevPosition = Vector3::Zero; + float PrevFade = 0.0f; + + void StoreInterpolationData() + { + PrevPosition = Position; + PrevFade = Fade; + } + }; + struct WeatherParticle { WeatherType Type = WeatherType::None; - int Room = -1; - Vector3 Position = Vector3::Zero; - Vector3 Velocity = Vector3::Zero; + Vector3 Position = Vector3::Zero; + int RoomNumber = NO_VALUE; + Vector3 Velocity = Vector3::Zero; - float StartLife = 0.0f; - float Life = 0.0f; + float StartLife = 0.0f; + float Life = 0.0f; float CollisionCheckDelay = 0.0f; - float Size = 0.0f; + float Size = 0.0f; bool Enabled = false; bool Stopped = false; float Transparency() const; + + Vector3 PrevPosition = Vector3::Zero; + Vector3 PrevVelocity = Vector3::Zero; + float PrevSize = 0.0f; + float PrevLife = 0.0f; + + void StoreInterpolationData() + { + PrevPosition = Position; + PrevVelocity = Velocity; + PrevSize = Size; + PrevLife = Life; + } }; class EnvironmentController @@ -30,7 +104,7 @@ namespace TEN::Effects::Environment EnvironmentController(); Vector3 Wind() { return Vector3(WindX / 2.0f, 0, WindZ / 2.0f); } - Vector3 FlashColor() { return FlashColorBase * sin(FlashProgress * PI / 2.0f); } + Vector3 FlashColor() { return FlashColorBase * sin((FlashProgress * PI) / 2.0f); } Vector4 SkyColor(int index) { return SkyCurrentColor[std::clamp(index, 0, 1)]; } short SkyPosition(int index) { return SkyCurrentPosition[std::clamp(index, 0, 1)]; } @@ -39,14 +113,16 @@ namespace TEN::Effects::Environment void Clear(); const std::vector& GetParticles() const { return Particles; } + const std::vector& GetStars() const { return Stars; } + const std::vector& GetMeteors() const { return Meteors; } private: // Weather - std::vector Particles; + std::vector Particles = {}; // Sky - Vector4 SkyCurrentColor[2] = {}; - short SkyCurrentPosition[2] = {}; + Vector4 SkyCurrentColor[2] = {}; + short SkyCurrentPosition[2] = {}; // Wind int WindX = 0; @@ -57,25 +133,35 @@ namespace TEN::Effects::Environment // Flash fader Vector3 FlashColorBase = Vector3::Zero; - float FlashSpeed = 1.0f; - float FlashProgress = 0.0f; + float FlashSpeed = 1.0f; + float FlashProgress = 0.0f; // Lightning - int StormCount = 0; - int StormRand = 0; - int StormTimer = 0; - byte StormSkyColor = 1; + int StormCount = 0; + int StormRand = 0; + int StormTimer = 0; + byte StormSkyColor = 1; byte StormSkyColor2 = 1; - void UpdateSky(ScriptInterfaceLevel* level); - void UpdateStorm(ScriptInterfaceLevel* level); - void UpdateWind(ScriptInterfaceLevel* level); - void UpdateFlash(ScriptInterfaceLevel* level); - void UpdateWeather(ScriptInterfaceLevel* level); + // Starfield + std::vector Stars = {}; + std::vector Meteors = {}; + bool ResetStarField = true; + + // Lens flare + LensFlare GlobalLensFlare = {}; + + void UpdateStarfield(const ScriptInterfaceLevel& level); + void UpdateSky(const ScriptInterfaceLevel& level); + void UpdateStorm(const ScriptInterfaceLevel& level); + void UpdateWind(const ScriptInterfaceLevel& level); + void UpdateFlash(const ScriptInterfaceLevel& level); + void UpdateWeather(const ScriptInterfaceLevel& level); void UpdateLightning(); - void SpawnDustParticles(ScriptInterfaceLevel* level); - void SpawnWeatherParticles(ScriptInterfaceLevel* level); + void SpawnDustParticles(const ScriptInterfaceLevel& level); + void SpawnWeatherParticles(const ScriptInterfaceLevel& level); + void SpawnMeteorParticles(const ScriptInterfaceLevel& level); }; extern EnvironmentController Weather; diff --git a/TombEngine/Game/gui.cpp b/TombEngine/Game/gui.cpp index 06635e097..949634a88 100644 --- a/TombEngine/Game/gui.cpp +++ b/TombEngine/Game/gui.cpp @@ -6,6 +6,8 @@ #include "Game/animation.h" #include "Game/camera.h" #include "Game/control/control.h" +#include "Game/control/volume.h" +#include "Game/effects/DisplaySprite.h" #include "Game/items.h" #include "Game/Lara/lara.h" #include "Game/Lara/lara_fire.h" @@ -25,7 +27,9 @@ #include "Specific/configuration.h" #include "Specific/level.h" #include "Specific/trutils.h" +#include "Specific/winmain.h" +using namespace TEN::Effects::DisplaySprite; using namespace TEN::Input; using namespace TEN::Renderer; using namespace TEN::Utils; @@ -172,7 +176,7 @@ namespace TEN::Gui return ((IsReleased(In::Select) || IsReleased(In::Action)) && CanSelect()); } } - + bool GuiController::GuiIsDeselected() const { return ((IsClicked(In::Deselect) || IsClicked(In::Draw)) && CanDeselect()); @@ -273,6 +277,7 @@ namespace TEN::Gui void GuiController::DrawInventory() { g_Renderer.RenderInventory(); + g_Renderer.Lock(); // TODO: When inventory is converted to 60 FPS, move this lock call outside of render loop. } InventoryResult GuiController::TitleOptions(ItemInfo* item) @@ -280,14 +285,17 @@ namespace TEN::Gui enum TitleOption { NewGame, + HomeLevel, LoadGame, Options, - ExitGame + ExitGame, + + Count }; - static const int numTitleOptions = 3; - static const int numLoadGameOptions = SAVEGAME_MAX - 1; - static const int numOptionsOptions = 2; + constexpr auto TITLE_OPTION_COUNT = TitleOption::Count - 1; + constexpr auto LOAD_GAME_OPTION_COUNT = SAVEGAME_MAX - 1; + constexpr auto OPTION_OPTION_COUNT = 2; static int selectedOptionBackup; auto inventoryResult = InventoryResult::None; @@ -299,20 +307,31 @@ namespace TEN::Gui switch (MenuToDisplay) { case Menu::Title: - OptionCount = g_GameFlow->IsLoadSaveEnabled() ? numTitleOptions : (numTitleOptions - 1); + OptionCount = TITLE_OPTION_COUNT; + + if (!g_GameFlow->IsHomeLevelEnabled()) + OptionCount--; + + if (!g_GameFlow->IsLoadSaveEnabled()) + OptionCount--; + break; case Menu::SelectLevel: inventoryResult = InventoryResult::None; OptionCount = g_GameFlow->GetNumLevels() - 2; + + if (g_GameFlow->IsHomeLevelEnabled()) + OptionCount--; + break; case Menu::LoadGame: - OptionCount = numLoadGameOptions; + OptionCount = LOAD_GAME_OPTION_COUNT; break; case Menu::Options: - OptionCount = numOptionsOptions; + OptionCount = OPTION_OPTION_COUNT; break; case Menu::Display: @@ -353,21 +372,7 @@ namespace TEN::Gui MenuToDisplay == Menu::SelectLevel || MenuToDisplay == Menu::Options) { - if (GuiIsPulsed(In::Forward)) - { - SelectedOption = (SelectedOption <= 0) ? OptionCount : (SelectedOption - 1); - SoundEffect(SFX_TR4_MENU_CHOOSE, nullptr, SoundEnvironment::Always); - } - - if (GuiIsPulsed(In::Back)) - { - if (SelectedOption < OptionCount) - SelectedOption++; - else - SelectedOption -= OptionCount; - - SoundEffect(SFX_TR4_MENU_CHOOSE, nullptr, SoundEnvironment::Always); - } + SelectedOption = GetLoopedSelectedOption(SelectedOption, OptionCount, g_Configuration.MenuOptionLoopingMode == MenuOptionLoopingMode::AllMenus); if (GuiIsDeselected() && MenuToDisplay != Menu::Title) { @@ -382,9 +387,14 @@ namespace TEN::Gui if (MenuToDisplay == Menu::Title) { - // Skip load game entry if loading and saving is disabled. int realSelectedOption = SelectedOption; - if (!g_GameFlow->IsLoadSaveEnabled() && SelectedOption > TitleOption::NewGame) + + // Skip Home Level entry if home level is disabled. + if (!g_GameFlow->IsHomeLevelEnabled() && realSelectedOption > TitleOption::NewGame) + realSelectedOption++; + + // Skip Load Game entry if loading and saving is disabled. + if (!g_GameFlow->IsLoadSaveEnabled() && realSelectedOption > TitleOption::HomeLevel) realSelectedOption++; switch (realSelectedOption) @@ -403,6 +413,10 @@ namespace TEN::Gui break; + case TitleOption::HomeLevel: + inventoryResult = InventoryResult::HomeLevel; + break; + case TitleOption::LoadGame: selectedOptionBackup = SelectedOption; SelectedOption = 0; @@ -423,8 +437,13 @@ namespace TEN::Gui } else if (MenuToDisplay == Menu::SelectLevel) { - // Level 0 is the title level, so increment the option by 1 to offset it. + // Level 0 is Title Level; increment option to offset it. g_GameFlow->SelectedLevelForNewGame = SelectedOption + 1; + + // Level 1 reserved for Home Level; increment option if enabled to offset it. + if (g_GameFlow->IsHomeLevelEnabled()) + g_GameFlow->SelectedLevelForNewGame++; + MenuToDisplay = Menu::Title; SelectedOption = 0; inventoryResult = InventoryResult::NewGameSelectedLevel; @@ -466,11 +485,12 @@ namespace TEN::Gui Caustics, Antialiasing, AmbientOcclusion, + HighFramerate, Save, Cancel }; - static const int numDisplaySettingsOptions = 7; + static const int numDisplaySettingsOptions = 8; OptionCount = numDisplaySettingsOptions; @@ -527,6 +547,12 @@ namespace TEN::Gui SoundEffect(SFX_TR4_MENU_CHOOSE, nullptr, SoundEnvironment::Always); CurrentSettings.Configuration.EnableAmbientOcclusion = !CurrentSettings.Configuration.EnableAmbientOcclusion; break; + + case DisplaySettingsOption::HighFramerate: + SoundEffect(SFX_TR4_MENU_CHOOSE, nullptr, SoundEnvironment::Always); + CurrentSettings.Configuration.EnableHighFramerate = !CurrentSettings.Configuration.EnableHighFramerate; + break; + } } @@ -573,28 +599,15 @@ namespace TEN::Gui SoundEffect(SFX_TR4_MENU_CHOOSE, nullptr, SoundEnvironment::Always); CurrentSettings.Configuration.EnableAmbientOcclusion = !CurrentSettings.Configuration.EnableAmbientOcclusion; break; + + case DisplaySettingsOption::HighFramerate: + SoundEffect(SFX_TR4_MENU_CHOOSE, nullptr, SoundEnvironment::Always); + CurrentSettings.Configuration.EnableHighFramerate = !CurrentSettings.Configuration.EnableHighFramerate; + break; } } - if (GuiIsPulsed(In::Forward)) - { - if (SelectedOption <= 0) - SelectedOption += OptionCount; - else - SelectedOption--; - - SoundEffect(SFX_TR4_MENU_CHOOSE, nullptr, SoundEnvironment::Always); - } - - if (GuiIsPulsed(In::Back)) - { - if (SelectedOption < OptionCount) - SelectedOption++; - else - SelectedOption -= OptionCount; - - SoundEffect(SFX_TR4_MENU_CHOOSE, nullptr, SoundEnvironment::Always); - } + SelectedOption = GetLoopedSelectedOption(SelectedOption, OptionCount, g_Configuration.MenuOptionLoopingMode == MenuOptionLoopingMode::AllMenus); if (GuiIsSelected()) { @@ -611,9 +624,11 @@ namespace TEN::Gui SaveConfiguration(); // Reset screen and go back. - g_Renderer.ChangeScreenResolution(CurrentSettings.Configuration.ScreenWidth, CurrentSettings.Configuration.ScreenHeight, + g_Renderer.ChangeScreenResolution(CurrentSettings.Configuration.ScreenWidth, CurrentSettings.Configuration.ScreenHeight, CurrentSettings.Configuration.EnableWindowedMode); + g_Renderer.SetGraphicsSettingsChanged(); + MenuToDisplay = fromPauseMenu ? Menu::Pause : Menu::Options; SelectedOption = fromPauseMenu ? 1 : 0; } @@ -671,96 +686,120 @@ namespace TEN::Gui { ClearAllActions(); + g_Synchronizer.Init(); + + bool legacy30FpsDoneDraw = false; + bool decreaseCounter = false; + while (CurrentSettings.NewKeyWaitTimer > 0.0f) { - CurrentSettings.NewKeyWaitTimer -= 1.0f; - if (CurrentSettings.NewKeyWaitTimer <= 0.0f) - CurrentSettings.NewKeyWaitTimer = 0.0f; + g_Synchronizer.Sync(); - UpdateInputActions(item); + while (g_Synchronizer.Synced()) + { + CurrentSettings.NewKeyWaitTimer -= 1.0f; + if (CurrentSettings.NewKeyWaitTimer <= 0.0f) + CurrentSettings.NewKeyWaitTimer = 0.0f; - if (CurrentSettings.IgnoreInput) - { - if (NoAction()) - CurrentSettings.IgnoreInput = false; - } - else - { - int selectedKey = 0; - for (selectedKey = 0; selectedKey < MAX_INPUT_SLOTS; selectedKey++) + if (!fromPauseMenu) { - if (KeyMap[selectedKey]) - break; + ControlPhase(true); + } + else + { + // Just for updating blink time + g_Renderer.PrepareScene(); } - if (selectedKey == MAX_INPUT_SLOTS) - selectedKey = 0; + UpdateInputActions(item); - if (selectedKey && !g_KeyNames[selectedKey].empty()) + if (CurrentSettings.IgnoreInput) { - unsigned int baseIndex = 0; - switch (MenuToDisplay) + if (NoAction()) + CurrentSettings.IgnoreInput = false; + } + else + { + int selectedKey = 0; + for (selectedKey = 0; selectedKey < MAX_INPUT_SLOTS; selectedKey++) { - case Menu::VehicleActions: - baseIndex = (unsigned int)GeneralActionStrings.size(); - break; - - case Menu::QuickActions: - baseIndex = unsigned int(GeneralActionStrings.size() + VehicleActionStrings.size()); - break; - - case Menu::MenuActions: - baseIndex = unsigned int(GeneralActionStrings.size() + VehicleActionStrings.size() + QuickActionStrings.size()); - break; - - default: - break; + if (KeyMap[selectedKey]) + break; } - Bindings[1][baseIndex + SelectedOption] = selectedKey; - DefaultConflict(); + if (selectedKey == MAX_INPUT_SLOTS) + selectedKey = 0; - CurrentSettings.NewKeyWaitTimer = 0.0f; - CurrentSettings.IgnoreInput = true; - return; + if (selectedKey && !g_KeyNames[selectedKey].empty()) + { + unsigned int baseIndex = 0; + switch (MenuToDisplay) + { + case Menu::VehicleActions: + baseIndex = (unsigned int)GeneralActionStrings.size(); + break; + + case Menu::QuickActions: + baseIndex = unsigned int(GeneralActionStrings.size() + VehicleActionStrings.size()); + break; + + case Menu::MenuActions: + baseIndex = unsigned int(GeneralActionStrings.size() + VehicleActionStrings.size() + QuickActionStrings.size()); + break; + + default: + break; + } + + Bindings[1][baseIndex + SelectedOption] = selectedKey; + DefaultConflict(); + + CurrentSettings.NewKeyWaitTimer = 0.0f; + CurrentSettings.IgnoreInput = true; + return; + } } + + g_Synchronizer.Step(); + + legacy30FpsDoneDraw = false; } - if (fromPauseMenu) + if (!g_Configuration.EnableHighFramerate) { - g_Renderer.RenderInventory(); - Camera.numberFrames = g_Renderer.Synchronize(); + if (!legacy30FpsDoneDraw) + { + if (fromPauseMenu) + { + g_Renderer.RenderInventory(); + } + else + { + g_Renderer.RenderTitle(0); + } + g_Renderer.Lock(); + legacy30FpsDoneDraw = true; + } } else { - g_Renderer.RenderTitle(); - Camera.numberFrames = g_Renderer.Synchronize(); - int numFrames = Camera.numberFrames; - ControlPhase(numFrames); + //g_Renderer.PrepareScene(); + + if (fromPauseMenu) + { + g_Renderer.RenderInventory(); + } + else + { + g_Renderer.RenderTitle(0); + } + g_Renderer.Lock(); } } } else { - if (GuiIsPulsed(In::Forward)) - { - if (SelectedOption <= 0) - SelectedOption += OptionCount; - else - SelectedOption--; - - SoundEffect(SFX_TR4_MENU_CHOOSE, nullptr, SoundEnvironment::Always); - } - - if (GuiIsPulsed(In::Back)) - { - if (SelectedOption < OptionCount) - SelectedOption++; - else - SelectedOption -= OptionCount; - - SoundEffect(SFX_TR4_MENU_CHOOSE, nullptr, SoundEnvironment::Always); - } + SelectedOption = GetLoopedSelectedOption(SelectedOption, OptionCount, g_Configuration.MenuOptionLoopingMode == MenuOptionLoopingMode::AllMenus); // HACK: Menu screen scroll. if (GuiIsPulsed(In::Left) || GuiIsPulsed(In::Right)) @@ -885,21 +924,23 @@ namespace TEN::Gui Reverb, MusicVolume, SfxVolume, + Subtitles, + AutoMonkeySwingJump, AutoTargeting, TargetHighlighter, ToggleRumble, ThumbstickCameraControl, + MouseSensitivity, - MouseSmoothing, Apply, - Cancel + Cancel, + + Count }; - static const auto numOtherSettingsOptions = 11; - - OptionCount = numOtherSettingsOptions; + OptionCount = (int)OtherSettingsOption::Count - 1; if (GuiIsDeselected()) { @@ -922,6 +963,11 @@ namespace TEN::Gui CurrentSettings.Configuration.EnableReverb = !CurrentSettings.Configuration.EnableReverb; break; + case OtherSettingsOption::AutoMonkeySwingJump: + SoundEffect(SFX_TR4_MENU_CHOOSE, nullptr, SoundEnvironment::Always); + CurrentSettings.Configuration.EnableAutoMonkeySwingJump = !CurrentSettings.Configuration.EnableAutoMonkeySwingJump; + break; + case OtherSettingsOption::Subtitles: SoundEffect(SFX_TR4_MENU_CHOOSE, nullptr, SoundEnvironment::Always); CurrentSettings.Configuration.EnableSubtitles = !CurrentSettings.Configuration.EnableSubtitles; @@ -936,7 +982,7 @@ namespace TEN::Gui SoundEffect(SFX_TR4_MENU_CHOOSE, nullptr, SoundEnvironment::Always); CurrentSettings.Configuration.EnableTargetHighlighter = !CurrentSettings.Configuration.EnableTargetHighlighter; break; - + case OtherSettingsOption::ToggleRumble: SoundEffect(SFX_TR4_MENU_CHOOSE, nullptr, SoundEnvironment::Always); CurrentSettings.Configuration.EnableRumble = !CurrentSettings.Configuration.EnableRumble; @@ -990,18 +1036,6 @@ namespace TEN::Gui 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; } @@ -1053,18 +1087,6 @@ namespace TEN::Gui 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; } @@ -1075,33 +1097,7 @@ namespace TEN::Gui } } - if (GuiIsPulsed(In::Forward)) - { - if (SelectedOption <= 0) - { - SelectedOption += OptionCount; - } - else - { - SelectedOption--; - } - - SoundEffect(SFX_TR4_MENU_CHOOSE, nullptr, SoundEnvironment::Always); - } - - if (GuiIsPulsed(In::Back)) - { - if (SelectedOption < OptionCount) - { - SelectedOption++; - } - else - { - SelectedOption -= OptionCount; - } - - SoundEffect(SFX_TR4_MENU_CHOOSE, nullptr, SoundEnvironment::Always); - } + SelectedOption = GetLoopedSelectedOption(SelectedOption, OptionCount, g_Configuration.MenuOptionLoopingMode == MenuOptionLoopingMode::AllMenus); if (GuiIsSelected()) { @@ -1183,25 +1179,7 @@ namespace TEN::Gui if (MenuToDisplay == Menu::Pause || MenuToDisplay == Menu::Options) { - if (GuiIsPulsed(In::Forward)) - { - if (SelectedOption <= 0) - SelectedOption += OptionCount; - else - SelectedOption--; - - SoundEffect(SFX_TR4_MENU_CHOOSE, nullptr, SoundEnvironment::Always); - } - - if (GuiIsPulsed(In::Back)) - { - if (SelectedOption < OptionCount) - SelectedOption++; - else - SelectedOption -= OptionCount; - - SoundEffect(SFX_TR4_MENU_CHOOSE, nullptr, SoundEnvironment::Always); - } + SelectedOption = GetLoopedSelectedOption(SelectedOption, OptionCount, g_Configuration.MenuOptionLoopingMode == MenuOptionLoopingMode::AllMenus); } if (GuiIsDeselected() || IsClicked(In::Pause)) @@ -1242,6 +1220,7 @@ namespace TEN::Gui case PauseMenuOption::ExitToTitle: SetInventoryMode(InventoryMode::None); + App.ResetClock = true; return InventoryResult::ExitToTitle; break; } @@ -1401,7 +1380,7 @@ namespace TEN::Gui if (Rings[(int)RingTypes::Ammo].RingActive) return; - + AmmoObjectList[0].Orientation = EulerAngles::Identity; AmmoObjectList[1].Orientation = EulerAngles::Identity; AmmoObjectList[2].Orientation = EulerAngles::Identity; @@ -1542,7 +1521,7 @@ namespace TEN::Gui void GuiController::ConstructObjectList(ItemInfo* item) { - auto* lara = GetLaraInfo(item); + auto& player = GetLaraInfo(*item); Rings[(int)RingTypes::Inventory].NumObjectsInList = 0; @@ -1558,19 +1537,19 @@ namespace TEN::Gui if (g_GameFlow->GetLevel(CurrentLevel)->GetLaraType() != LaraType::Young) { - if (lara->Weapons[(int)LaraWeaponType::Pistol].Present) + if (player.Weapons[(int)LaraWeaponType::Pistol].Present) InsertObjectIntoList(INV_OBJECT_PISTOLS); else if (Ammo.AmountPistolsAmmo) InsertObjectIntoList(INV_OBJECT_PISTOLS_AMMO); - if (lara->Weapons[(int)LaraWeaponType::Uzi].Present) + if (player.Weapons[(int)LaraWeaponType::Uzi].Present) InsertObjectIntoList(INV_OBJECT_UZIS); else if (Ammo.AmountUziAmmo) InsertObjectIntoList(INV_OBJECT_UZI_AMMO); - if (lara->Weapons[(int)LaraWeaponType::Revolver].Present) + if (player.Weapons[(int)LaraWeaponType::Revolver].Present) { - if (lara->Weapons[(int)LaraWeaponType::Revolver].HasLasersight) + if (player.Weapons[(int)LaraWeaponType::Revolver].HasLasersight) InsertObjectIntoList(INV_OBJECT_REVOLVER_LASER); else InsertObjectIntoList(INV_OBJECT_REVOLVER); @@ -1578,11 +1557,11 @@ namespace TEN::Gui else if (Ammo.AmountRevolverAmmo) InsertObjectIntoList(INV_OBJECT_REVOLVER_AMMO); - if (lara->Weapons[(int)LaraWeaponType::Shotgun].Present) + if (player.Weapons[(int)LaraWeaponType::Shotgun].Present) { InsertObjectIntoList(INV_OBJECT_SHOTGUN); - if (lara->Weapons[(int)LaraWeaponType::Shotgun].SelectedAmmo == WeaponAmmoType::Ammo2) + if (player.Weapons[(int)LaraWeaponType::Shotgun].SelectedAmmo == WeaponAmmoType::Ammo2) Ammo.CurrentShotGunAmmoType = 1; } else @@ -1594,33 +1573,33 @@ namespace TEN::Gui InsertObjectIntoList(INV_OBJECT_SHOTGUN_AMMO_2); } - if (lara->Weapons[(int)LaraWeaponType::HK].Present) + if (player.Weapons[(int)LaraWeaponType::HK].Present) { - if (lara->Weapons[(int)LaraWeaponType::HK].HasLasersight) + if (player.Weapons[(int)LaraWeaponType::HK].HasLasersight) InsertObjectIntoList(INV_OBJECT_HK_LASERSIGHT); else InsertObjectIntoList(INV_OBJECT_HK); - if (lara->Weapons[(int)LaraWeaponType::HK].WeaponMode == LaraWeaponTypeCarried::WTYPE_AMMO_2) + if (player.Weapons[(int)LaraWeaponType::HK].WeaponMode == LaraWeaponTypeCarried::WTYPE_AMMO_2) Ammo.CurrentHKAmmoType = 1; - if (lara->Weapons[(int)LaraWeaponType::HK].WeaponMode == LaraWeaponTypeCarried::WTYPE_AMMO_3) + if (player.Weapons[(int)LaraWeaponType::HK].WeaponMode == LaraWeaponTypeCarried::WTYPE_AMMO_3) Ammo.CurrentHKAmmoType = 2; } else if (Ammo.AmountHKAmmo1) InsertObjectIntoList(INV_OBJECT_HK_AMMO); - if (lara->Weapons[(int)LaraWeaponType::Crossbow].Present) + if (player.Weapons[(int)LaraWeaponType::Crossbow].Present) { - if (lara->Weapons[(int)LaraWeaponType::Crossbow].HasLasersight) + if (player.Weapons[(int)LaraWeaponType::Crossbow].HasLasersight) InsertObjectIntoList(INV_OBJECT_CROSSBOW_LASER); else InsertObjectIntoList(INV_OBJECT_CROSSBOW); - if (lara->Weapons[(int)LaraWeaponType::Crossbow].SelectedAmmo == WeaponAmmoType::Ammo2) + if (player.Weapons[(int)LaraWeaponType::Crossbow].SelectedAmmo == WeaponAmmoType::Ammo2) Ammo.CurrentCrossBowAmmoType = 1; - if (lara->Weapons[(int)LaraWeaponType::Crossbow].SelectedAmmo == WeaponAmmoType::Ammo3) + if (player.Weapons[(int)LaraWeaponType::Crossbow].SelectedAmmo == WeaponAmmoType::Ammo3) Ammo.CurrentCrossBowAmmoType = 2; } else @@ -1635,14 +1614,14 @@ namespace TEN::Gui InsertObjectIntoList(INV_OBJECT_CROSSBOW_AMMO_3); } - if (lara->Weapons[(int)LaraWeaponType::GrenadeLauncher].Present) + if (player.Weapons[(int)LaraWeaponType::GrenadeLauncher].Present) { InsertObjectIntoList(INV_OBJECT_GRENADE_LAUNCHER); - if (lara->Weapons[(int)LaraWeaponType::GrenadeLauncher].SelectedAmmo == WeaponAmmoType::Ammo2) + if (player.Weapons[(int)LaraWeaponType::GrenadeLauncher].SelectedAmmo == WeaponAmmoType::Ammo2) Ammo.CurrentGrenadeGunAmmoType = 1; - if (lara->Weapons[(int)LaraWeaponType::GrenadeLauncher].SelectedAmmo == WeaponAmmoType::Ammo3) + if (player.Weapons[(int)LaraWeaponType::GrenadeLauncher].SelectedAmmo == WeaponAmmoType::Ammo3) Ammo.CurrentGrenadeGunAmmoType = 2; } else @@ -1657,107 +1636,107 @@ namespace TEN::Gui InsertObjectIntoList(INV_OBJECT_GRENADE_AMMO_3); } - if (lara->Weapons[(int)LaraWeaponType::RocketLauncher].Present) + if (player.Weapons[(int)LaraWeaponType::RocketLauncher].Present) InsertObjectIntoList(INV_OBJECT_ROCKET_LAUNCHER); else if (Ammo.AmountRocketsAmmo) InsertObjectIntoList(INV_OBJECT_ROCKET_AMMO); - if (lara->Weapons[(int)LaraWeaponType::HarpoonGun].Present) + if (player.Weapons[(int)LaraWeaponType::HarpoonGun].Present) InsertObjectIntoList(INV_OBJECT_HARPOON_GUN); else if (Ammo.AmountHarpoonAmmo) InsertObjectIntoList(INV_OBJECT_HARPOON_AMMO); - if (lara->Inventory.HasLasersight) + if (player.Inventory.HasLasersight) InsertObjectIntoList(INV_OBJECT_LASERSIGHT); - if (lara->Inventory.HasSilencer) + if (player.Inventory.HasSilencer) InsertObjectIntoList(INV_OBJECT_SILENCER); - if (lara->Inventory.HasBinoculars) + if (player.Inventory.HasBinoculars) InsertObjectIntoList(INV_OBJECT_BINOCULARS); - if (lara->Inventory.TotalFlares) + if (player.Inventory.TotalFlares) InsertObjectIntoList(INV_OBJECT_FLARES); } InsertObjectIntoList(INV_OBJECT_TIMEX);//every level has the timex? what's a good way to check?! - if (lara->Inventory.TotalSmallMedipacks) + if (player.Inventory.TotalSmallMedipacks) InsertObjectIntoList(INV_OBJECT_SMALL_MEDIPACK); - if (lara->Inventory.TotalLargeMedipacks) + if (player.Inventory.TotalLargeMedipacks) InsertObjectIntoList(INV_OBJECT_LARGE_MEDIPACK); - if (lara->Inventory.HasCrowbar) + if (player.Inventory.HasCrowbar) InsertObjectIntoList(INV_OBJECT_CROWBAR); - if (lara->Inventory.BeetleComponents) + if (player.Inventory.BeetleComponents) { - if (lara->Inventory.BeetleComponents & BEETLECOMP_FLAG_BEETLE) + if (player.Inventory.BeetleComponents & BEETLECOMP_FLAG_BEETLE) InsertObjectIntoList(INV_OBJECT_BEETLE); - if (lara->Inventory.BeetleComponents & BEETLECOMP_FLAG_COMBO_1) + if (player.Inventory.BeetleComponents & BEETLECOMP_FLAG_COMBO_1) InsertObjectIntoList(INV_OBJECT_BEETLE_PART1); - if (lara->Inventory.BeetleComponents & BEETLECOMP_FLAG_COMBO_2) + if (player.Inventory.BeetleComponents & BEETLECOMP_FLAG_COMBO_2) InsertObjectIntoList(INV_OBJECT_BEETLE_PART2); } - if (lara->Inventory.SmallWaterskin) - InsertObjectIntoList((lara->Inventory.SmallWaterskin - 1) + INV_OBJECT_SMALL_WATERSKIN_EMPTY); + if (player.Inventory.SmallWaterskin) + InsertObjectIntoList((player.Inventory.SmallWaterskin - 1) + INV_OBJECT_SMALL_WATERSKIN_EMPTY); - if (lara->Inventory.BigWaterskin) - InsertObjectIntoList((lara->Inventory.BigWaterskin - 1) + INV_OBJECT_BIG_WATERSKIN_EMPTY); + if (player.Inventory.BigWaterskin) + InsertObjectIntoList((player.Inventory.BigWaterskin - 1) + INV_OBJECT_BIG_WATERSKIN_EMPTY); for (int i = 0; i < NUM_PUZZLES; i++) { - if (lara->Inventory.Puzzles[i]) + if (player.Inventory.Puzzles[i]) InsertObjectIntoList(INV_OBJECT_PUZZLE1 + i); } for (int i = 0; i < NUM_PUZZLE_PIECES; i++) { - if (lara->Inventory.PuzzlesCombo[i]) + if (player.Inventory.PuzzlesCombo[i]) InsertObjectIntoList(INV_OBJECT_PUZZLE1_COMBO1 + i); } for (int i = 0; i < NUM_KEYS; i++) { - if (lara->Inventory.Keys[i]) + if (player.Inventory.Keys[i]) InsertObjectIntoList(INV_OBJECT_KEY1 + i); } for (int i = 0; i < NUM_KEY_PIECES; i++) { - if (lara->Inventory.KeysCombo[i]) + if (player.Inventory.KeysCombo[i]) InsertObjectIntoList(INV_OBJECT_KEY1_COMBO1 + i); } for (int i = 0; i < NUM_PICKUPS; i++) { - if (lara->Inventory.Pickups[i]) + if (player.Inventory.Pickups[i]) InsertObjectIntoList(INV_OBJECT_PICKUP1 + i); } for (int i = 0; i < NUM_PICKUPS_PIECES; i++) { - if (lara->Inventory.PickupsCombo[i]) + if (player.Inventory.PickupsCombo[i]) InsertObjectIntoList(INV_OBJECT_PICKUP1_COMBO1 + i); } for (int i = 0; i < NUM_EXAMINES; i++) { - if (lara->Inventory.Examines[i]) + if (player.Inventory.Examines[i]) InsertObjectIntoList(INV_OBJECT_EXAMINE1 + i); } for (int i = 0; i < NUM_EXAMINES_PIECES; i++) { - if (lara->Inventory.ExaminesCombo[i]) + if (player.Inventory.ExaminesCombo[i]) InsertObjectIntoList(INV_OBJECT_EXAMINE1_COMBO1 + i); } - if (lara->Inventory.Diary.Present) + if (player.Inventory.Diary.Present) InsertObjectIntoList(INV_OBJECT_DIARY); if (g_GameFlow->IsLoadSaveEnabled()) @@ -1778,7 +1757,7 @@ namespace TEN::Gui void GuiController::ConstructCombineObjectList(ItemInfo* item) { - auto* lara = GetLaraInfo(item); + auto& player = GetLaraInfo(*item); Rings[(int)RingTypes::Ammo].NumObjectsInList = 0; @@ -1787,73 +1766,73 @@ namespace TEN::Gui if (!(g_GameFlow->GetLevel(CurrentLevel)->GetLaraType() == LaraType::Young)) { - if (lara->Weapons[(int)LaraWeaponType::Revolver].Present) + if (player.Weapons[(int)LaraWeaponType::Revolver].Present) { - if (lara->Weapons[(int)LaraWeaponType::Revolver].HasLasersight) + if (player.Weapons[(int)LaraWeaponType::Revolver].HasLasersight) InsertObjectIntoList_v2(INV_OBJECT_REVOLVER_LASER); else InsertObjectIntoList_v2(INV_OBJECT_REVOLVER); } - if (lara->Weapons[(int)LaraWeaponType::HK].Present) + if (player.Weapons[(int)LaraWeaponType::HK].Present) { - if (lara->Weapons[(int)LaraWeaponType::HK].HasLasersight) + if (player.Weapons[(int)LaraWeaponType::HK].HasLasersight) InsertObjectIntoList_v2(INV_OBJECT_HK_LASERSIGHT); else InsertObjectIntoList_v2(INV_OBJECT_HK); } - if (lara->Weapons[(int)LaraWeaponType::Crossbow].Present) + if (player.Weapons[(int)LaraWeaponType::Crossbow].Present) { - if (lara->Weapons[(int)LaraWeaponType::Crossbow].HasLasersight) + if (player.Weapons[(int)LaraWeaponType::Crossbow].HasLasersight) InsertObjectIntoList_v2(INV_OBJECT_CROSSBOW_LASER); else InsertObjectIntoList_v2(INV_OBJECT_CROSSBOW); } - if (lara->Inventory.HasLasersight) + if (player.Inventory.HasLasersight) InsertObjectIntoList_v2(INV_OBJECT_LASERSIGHT); - if (lara->Inventory.HasSilencer) + if (player.Inventory.HasSilencer) InsertObjectIntoList_v2(INV_OBJECT_SILENCER); } - if (lara->Inventory.BeetleComponents) + if (player.Inventory.BeetleComponents) { - if (lara->Inventory.BeetleComponents & 2) + if (player.Inventory.BeetleComponents & 2) InsertObjectIntoList_v2(INV_OBJECT_BEETLE_PART1); - if (lara->Inventory.BeetleComponents & 4) + if (player.Inventory.BeetleComponents & 4) InsertObjectIntoList_v2(INV_OBJECT_BEETLE_PART2); } - if (lara->Inventory.SmallWaterskin) - InsertObjectIntoList_v2(lara->Inventory.SmallWaterskin - 1 + INV_OBJECT_SMALL_WATERSKIN_EMPTY); + if (player.Inventory.SmallWaterskin) + InsertObjectIntoList_v2(player.Inventory.SmallWaterskin - 1 + INV_OBJECT_SMALL_WATERSKIN_EMPTY); - if (lara->Inventory.BigWaterskin) - InsertObjectIntoList_v2(lara->Inventory.BigWaterskin - 1 + INV_OBJECT_BIG_WATERSKIN_EMPTY); + if (player.Inventory.BigWaterskin) + InsertObjectIntoList_v2(player.Inventory.BigWaterskin - 1 + INV_OBJECT_BIG_WATERSKIN_EMPTY); for (int i = 0; i < NUM_PUZZLE_PIECES; i++) { - if (lara->Inventory.PuzzlesCombo[i]) + if (player.Inventory.PuzzlesCombo[i]) InsertObjectIntoList_v2(INV_OBJECT_PUZZLE1_COMBO1 + i); } for (int i = 0; i < NUM_KEY_PIECES; i++) { - if (lara->Inventory.KeysCombo[i]) + if (player.Inventory.KeysCombo[i]) InsertObjectIntoList_v2(INV_OBJECT_KEY1_COMBO1 + i); } for (int i = 0; i < NUM_PICKUPS_PIECES; i++) { - if (lara->Inventory.PickupsCombo[i]) + if (player.Inventory.PickupsCombo[i]) InsertObjectIntoList_v2(INV_OBJECT_PICKUP1_COMBO1 + i); } for (int i = 0; i < NUM_EXAMINES_PIECES; i++) { - if (lara->Inventory.ExaminesCombo[i]) + if (player.Inventory.ExaminesCombo[i]) InsertObjectIntoList_v2(INV_OBJECT_EXAMINE1_COMBO1 + i); } @@ -1871,45 +1850,45 @@ namespace TEN::Gui void GuiController::InitializeInventory(ItemInfo* item) { - auto* lara = GetLaraInfo(item); + auto& player = GetLaraInfo(*item); AlterFOV(ANGLE(DEFAULT_FOV), false); - lara->Inventory.IsBusy = false; + player.Inventory.IsBusy = false; InventoryItemChosen = NO_VALUE; - UseItem = false; + ItemUsed = false; - if (lara->Weapons[(int)LaraWeaponType::Shotgun].Ammo[0].HasInfinite()) + if (player.Weapons[(int)LaraWeaponType::Shotgun].Ammo[0].HasInfinite()) { Ammo.AmountShotGunAmmo1 = -1; } else { - Ammo.AmountShotGunAmmo1 = lara->Weapons[(int)LaraWeaponType::Shotgun].Ammo[0].GetCount() / 6; + Ammo.AmountShotGunAmmo1 = player.Weapons[(int)LaraWeaponType::Shotgun].Ammo[0].GetCount() / 6; } - if (lara->Weapons[(int)LaraWeaponType::Shotgun].Ammo[1].HasInfinite()) + if (player.Weapons[(int)LaraWeaponType::Shotgun].Ammo[1].HasInfinite()) { Ammo.AmountShotGunAmmo2 = -1; } else { - Ammo.AmountShotGunAmmo2 = lara->Weapons[(int)LaraWeaponType::Shotgun].Ammo[1].GetCount() / 6; + Ammo.AmountShotGunAmmo2 = player.Weapons[(int)LaraWeaponType::Shotgun].Ammo[1].GetCount() / 6; } - - Ammo.AmountShotGunAmmo1 = lara->Weapons[(int)LaraWeaponType::Shotgun].Ammo[(int)WeaponAmmoType::Ammo1].HasInfinite() ? -1 : lara->Weapons[(int)LaraWeaponType::Shotgun].Ammo[(int)WeaponAmmoType::Ammo1].GetCount(); - Ammo.AmountShotGunAmmo2 = lara->Weapons[(int)LaraWeaponType::Shotgun].Ammo[(int)WeaponAmmoType::Ammo2].HasInfinite() ? -1 : lara->Weapons[(int)LaraWeaponType::Shotgun].Ammo[(int)WeaponAmmoType::Ammo2].GetCount(); - Ammo.AmountHKAmmo1 = lara->Weapons[(int)LaraWeaponType::HK].Ammo[(int)WeaponAmmoType::Ammo1].HasInfinite() ? -1 : lara->Weapons[(int)LaraWeaponType::HK].Ammo[(int)WeaponAmmoType::Ammo1].GetCount(); - Ammo.AmountCrossBowAmmo1 = lara->Weapons[(int)LaraWeaponType::Crossbow].Ammo[(int)WeaponAmmoType::Ammo1].HasInfinite() ? -1 : lara->Weapons[(int)LaraWeaponType::Crossbow].Ammo[(int)WeaponAmmoType::Ammo1].GetCount(); - Ammo.AmountCrossBowAmmo2 = lara->Weapons[(int)LaraWeaponType::Crossbow].Ammo[(int)WeaponAmmoType::Ammo2].HasInfinite() ? -1 : lara->Weapons[(int)LaraWeaponType::Crossbow].Ammo[(int)WeaponAmmoType::Ammo2].GetCount(); - Ammo.AmountCrossBowAmmo3 = lara->Weapons[(int)LaraWeaponType::Crossbow].Ammo[(int)WeaponAmmoType::Ammo3].HasInfinite() ? -1 : lara->Weapons[(int)LaraWeaponType::Crossbow].Ammo[(int)WeaponAmmoType::Ammo3].GetCount(); - Ammo.AmountUziAmmo = lara->Weapons[(int)LaraWeaponType::Uzi].Ammo[(int)WeaponAmmoType::Ammo1].HasInfinite() ? -1 : lara->Weapons[(int)LaraWeaponType::Uzi].Ammo[(int)WeaponAmmoType::Ammo1].GetCount(); - Ammo.AmountRevolverAmmo = lara->Weapons[(int)LaraWeaponType::Revolver].Ammo[(int)WeaponAmmoType::Ammo1].HasInfinite() ? -1 : lara->Weapons[(int)LaraWeaponType::Revolver].Ammo[(int)WeaponAmmoType::Ammo1].GetCount(); - Ammo.AmountPistolsAmmo = lara->Weapons[(int)LaraWeaponType::Pistol].Ammo[(int)WeaponAmmoType::Ammo1].HasInfinite() ? -1 : lara->Weapons[(int)LaraWeaponType::Pistol].Ammo[(int)WeaponAmmoType::Ammo1].GetCount(); - Ammo.AmountRocketsAmmo = lara->Weapons[(int)LaraWeaponType::RocketLauncher].Ammo[(int)WeaponAmmoType::Ammo1].HasInfinite() ? -1 : lara->Weapons[(int)LaraWeaponType::RocketLauncher].Ammo[(int)WeaponAmmoType::Ammo1].GetCount(); - Ammo.AmountHarpoonAmmo = lara->Weapons[(int)LaraWeaponType::HarpoonGun].Ammo[(int)WeaponAmmoType::Ammo1].HasInfinite()? -1 : lara->Weapons[(int)LaraWeaponType::HarpoonGun].Ammo[(int)WeaponAmmoType::Ammo1].GetCount(); - Ammo.AmountGrenadeAmmo1 = lara->Weapons[(int)LaraWeaponType::GrenadeLauncher].Ammo[(int)WeaponAmmoType::Ammo1].HasInfinite() ? -1 : lara->Weapons[(int)LaraWeaponType::GrenadeLauncher].Ammo[(int)WeaponAmmoType::Ammo1].GetCount(); - Ammo.AmountGrenadeAmmo2 = lara->Weapons[(int)LaraWeaponType::GrenadeLauncher].Ammo[(int)WeaponAmmoType::Ammo2].HasInfinite() ? -1 : lara->Weapons[(int)LaraWeaponType::GrenadeLauncher].Ammo[(int)WeaponAmmoType::Ammo2].GetCount(); - Ammo.AmountGrenadeAmmo3 = lara->Weapons[(int)LaraWeaponType::GrenadeLauncher].Ammo[(int)WeaponAmmoType::Ammo3].HasInfinite() ? -1 : lara->Weapons[(int)LaraWeaponType::GrenadeLauncher].Ammo[(int)WeaponAmmoType::Ammo3].GetCount(); + + Ammo.AmountShotGunAmmo1 = player.Weapons[(int)LaraWeaponType::Shotgun].Ammo[(int)WeaponAmmoType::Ammo1].HasInfinite() ? -1 : player.Weapons[(int)LaraWeaponType::Shotgun].Ammo[(int)WeaponAmmoType::Ammo1].GetCount(); + Ammo.AmountShotGunAmmo2 = player.Weapons[(int)LaraWeaponType::Shotgun].Ammo[(int)WeaponAmmoType::Ammo2].HasInfinite() ? -1 : player.Weapons[(int)LaraWeaponType::Shotgun].Ammo[(int)WeaponAmmoType::Ammo2].GetCount(); + Ammo.AmountHKAmmo1 = player.Weapons[(int)LaraWeaponType::HK].Ammo[(int)WeaponAmmoType::Ammo1].HasInfinite() ? -1 : player.Weapons[(int)LaraWeaponType::HK].Ammo[(int)WeaponAmmoType::Ammo1].GetCount(); + Ammo.AmountCrossBowAmmo1 = player.Weapons[(int)LaraWeaponType::Crossbow].Ammo[(int)WeaponAmmoType::Ammo1].HasInfinite() ? -1 : player.Weapons[(int)LaraWeaponType::Crossbow].Ammo[(int)WeaponAmmoType::Ammo1].GetCount(); + Ammo.AmountCrossBowAmmo2 = player.Weapons[(int)LaraWeaponType::Crossbow].Ammo[(int)WeaponAmmoType::Ammo2].HasInfinite() ? -1 : player.Weapons[(int)LaraWeaponType::Crossbow].Ammo[(int)WeaponAmmoType::Ammo2].GetCount(); + Ammo.AmountCrossBowAmmo3 = player.Weapons[(int)LaraWeaponType::Crossbow].Ammo[(int)WeaponAmmoType::Ammo3].HasInfinite() ? -1 : player.Weapons[(int)LaraWeaponType::Crossbow].Ammo[(int)WeaponAmmoType::Ammo3].GetCount(); + Ammo.AmountUziAmmo = player.Weapons[(int)LaraWeaponType::Uzi].Ammo[(int)WeaponAmmoType::Ammo1].HasInfinite() ? -1 : player.Weapons[(int)LaraWeaponType::Uzi].Ammo[(int)WeaponAmmoType::Ammo1].GetCount(); + Ammo.AmountRevolverAmmo = player.Weapons[(int)LaraWeaponType::Revolver].Ammo[(int)WeaponAmmoType::Ammo1].HasInfinite() ? -1 : player.Weapons[(int)LaraWeaponType::Revolver].Ammo[(int)WeaponAmmoType::Ammo1].GetCount(); + Ammo.AmountPistolsAmmo = player.Weapons[(int)LaraWeaponType::Pistol].Ammo[(int)WeaponAmmoType::Ammo1].HasInfinite() ? -1 : player.Weapons[(int)LaraWeaponType::Pistol].Ammo[(int)WeaponAmmoType::Ammo1].GetCount(); + Ammo.AmountRocketsAmmo = player.Weapons[(int)LaraWeaponType::RocketLauncher].Ammo[(int)WeaponAmmoType::Ammo1].HasInfinite() ? -1 : player.Weapons[(int)LaraWeaponType::RocketLauncher].Ammo[(int)WeaponAmmoType::Ammo1].GetCount(); + Ammo.AmountHarpoonAmmo = player.Weapons[(int)LaraWeaponType::HarpoonGun].Ammo[(int)WeaponAmmoType::Ammo1].HasInfinite()? -1 : player.Weapons[(int)LaraWeaponType::HarpoonGun].Ammo[(int)WeaponAmmoType::Ammo1].GetCount(); + Ammo.AmountGrenadeAmmo1 = player.Weapons[(int)LaraWeaponType::GrenadeLauncher].Ammo[(int)WeaponAmmoType::Ammo1].HasInfinite() ? -1 : player.Weapons[(int)LaraWeaponType::GrenadeLauncher].Ammo[(int)WeaponAmmoType::Ammo1].GetCount(); + Ammo.AmountGrenadeAmmo2 = player.Weapons[(int)LaraWeaponType::GrenadeLauncher].Ammo[(int)WeaponAmmoType::Ammo2].HasInfinite() ? -1 : player.Weapons[(int)LaraWeaponType::GrenadeLauncher].Ammo[(int)WeaponAmmoType::Ammo2].GetCount(); + Ammo.AmountGrenadeAmmo3 = player.Weapons[(int)LaraWeaponType::GrenadeLauncher].Ammo[(int)WeaponAmmoType::Ammo3].HasInfinite() ? -1 : player.Weapons[(int)LaraWeaponType::GrenadeLauncher].Ammo[(int)WeaponAmmoType::Ammo3].GetCount(); ConstructObjectList(item); if (EnterInventory == NO_VALUE) @@ -2009,7 +1988,7 @@ namespace TEN::Gui else if (AmmoSelectorFadeDir == 1) { if (AmmoSelectorFadeVal < 128) - AmmoSelectorFadeVal += 32; + AmmoSelectorFadeVal += 32 / g_Renderer.GetFramerateMultiplier(); if (AmmoSelectorFadeVal > 128) { @@ -2020,7 +1999,7 @@ namespace TEN::Gui else if (AmmoSelectorFadeDir == 2) { if (AmmoSelectorFadeVal > 0) - AmmoSelectorFadeVal -= 32; + AmmoSelectorFadeVal -= 32 / g_Renderer.GetFramerateMultiplier(); if (AmmoSelectorFadeVal < 0) { @@ -2030,16 +2009,16 @@ namespace TEN::Gui } } - void GuiController::UseCurrentItem(ItemInfo* item) + void GuiController::UseItem(ItemInfo& item, int objectNumber) { - const std::vector CrouchStates = + const auto CROUCH_STATES = std::vector { LS_CROUCH_IDLE, LS_CROUCH_TURN_LEFT, LS_CROUCH_TURN_RIGHT, LS_CROUCH_TURN_180 }; - const std::vector CrawlStates = + const auto CRAWL_STATES = std::vector { LS_CRAWL_IDLE, LS_CRAWL_FORWARD, @@ -2050,272 +2029,239 @@ namespace TEN::Gui LS_CRAWL_TO_HANG }; - auto* lara = GetLaraInfo(item); + auto& player = GetLaraInfo(item); - int prevOpticRange = lara->Control.Look.OpticRange; - short inventoryObject = Rings[(int)RingTypes::Inventory].CurrentObjectList[Rings[(int)RingTypes::Inventory].CurrentObjectInList].InventoryItem; - short gameObject = InventoryObjectTable[inventoryObject].ObjectNumber; + short prevOpticRange = player.Control.Look.OpticRange; + player.Control.Look.OpticRange = 0; + player.Inventory.OldBusy = false; + item.MeshBits = ALL_JOINT_BITS; - item->MeshBits = ALL_JOINT_BITS; - lara->Control.Look.OpticRange = 0; - lara->Inventory.OldBusy = false; + InventoryItemChosen = objectNumber; - if (lara->Control.WaterStatus == WaterStatus::Dry || - lara->Control.WaterStatus == WaterStatus::Wade) + // Use item event handling. + g_GameScript->OnUseItem((GAME_OBJECT_ID)InventoryItemChosen); + HandleAllGlobalEvents(EventType::UseItem, (Activator)item.Index); + + // Quickly discard further processing if chosen item was reset in script. + if (InventoryItemChosen == NO_VALUE) + return; + + if (InventoryItemChosen == ID_PISTOLS_ITEM || + InventoryItemChosen == ID_UZI_ITEM || + InventoryItemChosen == ID_REVOLVER_ITEM) { - if (gameObject == ID_PISTOLS_ITEM) + if (player.Control.WaterStatus != WaterStatus::Dry && + player.Control.WaterStatus != WaterStatus::Wade) { - lara->Control.Weapon.RequestGunType = LaraWeaponType::Pistol; - - if (lara->Control.HandStatus != HandStatus::Free) - return; - - if (lara->Control.Weapon.GunType == LaraWeaponType::Pistol) - lara->Control.HandStatus = HandStatus::WeaponDraw; - return; } - if (gameObject == ID_UZI_ITEM) + switch (InventoryItemChosen) { - lara->Control.Weapon.RequestGunType = LaraWeaponType::Uzi; + case ID_PISTOLS_ITEM: + player.Control.Weapon.RequestGunType = LaraWeaponType::Pistol; + break; - if (lara->Control.HandStatus != HandStatus::Free) + case ID_UZI_ITEM: + player.Control.Weapon.RequestGunType = LaraWeaponType::Uzi; + break; + + case ID_REVOLVER_ITEM: + player.Control.Weapon.RequestGunType = LaraWeaponType::Revolver; + break; + + default: return; - - if (lara->Control.Weapon.GunType == LaraWeaponType::Uzi) - lara->Control.HandStatus = HandStatus::WeaponDraw; - - return; } + + if (player.Control.HandStatus == HandStatus::Free && + player.Control.Weapon.GunType == player.Control.Weapon.RequestGunType) + { + player.Control.HandStatus = HandStatus::WeaponDraw; + } + + InventoryItemChosen = NO_VALUE; + return; } - if (gameObject != ID_SHOTGUN_ITEM && - gameObject != ID_REVOLVER_ITEM && - gameObject != ID_HK_ITEM && - gameObject != ID_CROSSBOW_ITEM && - gameObject != ID_GRENADE_GUN_ITEM && - gameObject != ID_ROCKET_LAUNCHER_ITEM && - gameObject != ID_HARPOON_ITEM) + if (InventoryItemChosen == ID_SHOTGUN_ITEM || + InventoryItemChosen == ID_HK_ITEM || + InventoryItemChosen == ID_CROSSBOW_ITEM || + InventoryItemChosen == ID_GRENADE_GUN_ITEM || + InventoryItemChosen == ID_ROCKET_LAUNCHER_ITEM || + InventoryItemChosen == ID_HARPOON_ITEM) { - if (gameObject == ID_FLARE_INV_ITEM) + if (InventoryItemChosen != ID_HARPOON_ITEM && + player.Control.WaterStatus != WaterStatus::Dry && + player.Control.WaterStatus != WaterStatus::Wade) { - if (lara->Control.HandStatus == HandStatus::Free) - { - if (!TestState(item->Animation.ActiveState, CrawlStates)) - { - if (lara->Control.Weapon.GunType != LaraWeaponType::Flare) - { - // HACK. - ClearAllActions(); - ActionMap[(int)In::Flare].Update(1.0f); - - HandleWeapon(*item); - ClearAllActions(); - } - - return; - } - } - - SayNo(); return; } - switch (inventoryObject) + if (TestState(item.Animation.ActiveState, CROUCH_STATES) || + TestState(item.Animation.ActiveState, CRAWL_STATES)) { - case INV_OBJECT_BINOCULARS: - if (((item->Animation.ActiveState == LS_IDLE && item->Animation.AnimNumber == LA_STAND_IDLE) || - (lara->Control.IsLow && !IsHeld(In::Crouch))) && - !UseSpotCam && !TrackCameraInit) - { - lara->Control.Look.OpticRange = 128; - lara->Control.Look.IsUsingBinoculars = true; - lara->Inventory.OldBusy = true; - - // TODO: To prevent Lara from crouching or performing other actions, the inherent state of - // LA_BINOCULARS_IDLE must be changed to LS_IDLE. @Sezz 2022.05.19 - //SetAnimation(item, LA_BINOCULARS_IDLE); - - if (lara->Control.HandStatus != HandStatus::Free) - lara->Control.HandStatus = HandStatus::WeaponUndraw; - } - - if (prevOpticRange) - { - lara->Control.Look.OpticRange = prevOpticRange; - } - else - { - BinocularOldCamera = Camera.oldType; - } - - return; - - case INV_OBJECT_SMALL_MEDIPACK: - - if ((item->HitPoints <= 0 || item->HitPoints >= LARA_HEALTH_MAX) && - lara->Status.Poison == 0) - { - SayNo(); - return; - } - - if (lara->Inventory.TotalSmallMedipacks != 0) - { - if (lara->Inventory.TotalSmallMedipacks != -1) - lara->Inventory.TotalSmallMedipacks--; - - lara->Status.Poison = 0; - item->HitPoints += LARA_HEALTH_MAX / 2; - - if (item->HitPoints > LARA_HEALTH_MAX) - item->HitPoints = LARA_HEALTH_MAX; - - SoundEffect(SFX_TR4_MENU_MEDI, nullptr, SoundEnvironment::Always); - SaveGame::Statistics.Game.HealthUsed++; - } - else - SayNo(); - - return; - - case INV_OBJECT_LARGE_MEDIPACK: - - if ((item->HitPoints <= 0 || item->HitPoints >= LARA_HEALTH_MAX) && - lara->Status.Poison == 0) - { - SayNo(); - return; - } - - if (lara->Inventory.TotalLargeMedipacks != 0) - { - if (lara->Inventory.TotalLargeMedipacks != -1) - lara->Inventory.TotalLargeMedipacks--; - - lara->Status.Poison = 0; - item->HitPoints = LARA_HEALTH_MAX; - - SoundEffect(SFX_TR4_MENU_MEDI, nullptr, SoundEnvironment::Always); - SaveGame::Statistics.Game.HealthUsed++; - } - else - SayNo(); - - return; - - default: - InventoryItemChosen = gameObject; return; } + switch (InventoryItemChosen) + { + case ID_SHOTGUN_ITEM: + player.Control.Weapon.RequestGunType = LaraWeaponType::Shotgun; + break; + + case ID_REVOLVER_ITEM: + player.Control.Weapon.RequestGunType = LaraWeaponType::Revolver; + break; + + case ID_HK_ITEM: + player.Control.Weapon.RequestGunType = LaraWeaponType::HK; + break; + + case ID_CROSSBOW_ITEM: + player.Control.Weapon.RequestGunType = LaraWeaponType::Crossbow; + break; + + case ID_GRENADE_GUN_ITEM: + player.Control.Weapon.RequestGunType = LaraWeaponType::GrenadeLauncher; + break; + + case ID_HARPOON_ITEM: + player.Control.Weapon.RequestGunType = LaraWeaponType::HarpoonGun; + break; + + case ID_ROCKET_LAUNCHER_ITEM: + player.Control.Weapon.RequestGunType = LaraWeaponType::RocketLauncher; + break; + + default: + return; + } + + if (player.Control.HandStatus == HandStatus::Free && + player.Control.Weapon.GunType == player.Control.Weapon.RequestGunType) + { + player.Control.HandStatus = HandStatus::WeaponDraw; + } + + InventoryItemChosen = NO_VALUE; return; } - if (lara->Control.HandStatus == HandStatus::Busy) + switch (InventoryItemChosen) { - SayNo(); - return; - } - - if (TestState(item->Animation.ActiveState, CrouchStates) || - TestState(item->Animation.ActiveState, CrawlStates)) - { - SayNo(); - return; - } - - if (gameObject == ID_SHOTGUN_ITEM) - { - lara->Control.Weapon.RequestGunType = LaraWeaponType::Shotgun; - - if (lara->Control.HandStatus != HandStatus::Free) + case ID_FLARE_INV_ITEM: + if (player.Control.HandStatus != HandStatus::Free) return; - if (lara->Control.Weapon.GunType == LaraWeaponType::Shotgun) - lara->Control.HandStatus = HandStatus::WeaponDraw; + if (!TestState(item.Animation.ActiveState, CRAWL_STATES)) + { + if (player.Control.Weapon.GunType != LaraWeaponType::Flare) + { + // HACK. + ClearAllActions(); + ActionMap[(int)In::Flare].Update(1.0f); + HandleWeapon(item); + ClearAllActions(); + } + } + + InventoryItemChosen = NO_VALUE; return; - } - if (gameObject == ID_REVOLVER_ITEM) - { - lara->Control.Weapon.RequestGunType = LaraWeaponType::Revolver; + case ID_BINOCULARS_ITEM: + if (((item.Animation.ActiveState == LS_IDLE && item.Animation.AnimNumber == LA_STAND_IDLE) || + (player.Control.IsLow && !IsHeld(In::Crouch))) && + !UseSpotCam && !TrackCameraInit) + { + player.Control.Look.OpticRange = ANGLE(0.7f); + player.Control.Look.IsUsingBinoculars = true; + player.Inventory.OldBusy = true; - if (lara->Control.HandStatus != HandStatus::Free) - return; + // TODO: To prevent Lara from crouching or performing other actions, the inherent state of + // LA_BINOCULARS_IDLE must be changed to LS_IDLE. @Sezz 2022.05.19 + //SetAnimation(item, LA_BINOCULARS_IDLE); - if (lara->Control.Weapon.GunType == LaraWeaponType::Revolver) - lara->Control.HandStatus = HandStatus::WeaponDraw; + if (player.Control.HandStatus != HandStatus::Free) + player.Control.HandStatus = HandStatus::WeaponUndraw; + } + if (prevOpticRange != ANGLE(0.0f)) + { + player.Control.Look.OpticRange = prevOpticRange; + } + else + { + BinocularOldCamera = Camera.oldType; + } + + InventoryItemChosen = NO_VALUE; return; - } - else if (gameObject == ID_HK_ITEM) - { - lara->Control.Weapon.RequestGunType = LaraWeaponType::HK; - if (lara->Control.HandStatus != HandStatus::Free) + case ID_SMALLMEDI_ITEM: + if ((item.HitPoints <= 0 || item.HitPoints >= LARA_HEALTH_MAX) && + player.Status.Poison == 0) + { return; + } - if (lara->Control.Weapon.GunType == LaraWeaponType::HK) - lara->Control.HandStatus = HandStatus::WeaponDraw; + if (player.Inventory.TotalSmallMedipacks != 0) + { + if (player.Inventory.TotalSmallMedipacks != NO_VALUE) + player.Inventory.TotalSmallMedipacks--; + player.Status.Poison = 0; + item.HitPoints += LARA_HEALTH_MAX / 2; + + if (item.HitPoints > LARA_HEALTH_MAX) + item.HitPoints = LARA_HEALTH_MAX; + + SoundEffect(SFX_TR4_MENU_MEDI, nullptr, SoundEnvironment::Always); + SaveGame::Statistics.Game.HealthUsed++; + } + else + { + return; + } + + InventoryItemChosen = NO_VALUE; return; - } - else if (gameObject == ID_CROSSBOW_ITEM) - { - lara->Control.Weapon.RequestGunType = LaraWeaponType::Crossbow; - if (lara->Control.HandStatus != HandStatus::Free) + case ID_BIGMEDI_ITEM: + if ((item.HitPoints <= 0 || item.HitPoints >= LARA_HEALTH_MAX) && + player.Status.Poison == 0) + { return; + } - if (lara->Control.Weapon.GunType == LaraWeaponType::Crossbow) - lara->Control.HandStatus = HandStatus::WeaponDraw; + if (player.Inventory.TotalLargeMedipacks != 0) + { + if (player.Inventory.TotalLargeMedipacks != NO_VALUE) + player.Inventory.TotalLargeMedipacks--; + player.Status.Poison = 0; + item.HitPoints = LARA_HEALTH_MAX; + + SoundEffect(SFX_TR4_MENU_MEDI, nullptr, SoundEnvironment::Always); + SaveGame::Statistics.Game.HealthUsed++; + } + else + { + return; + } + + InventoryItemChosen = NO_VALUE; return; - } - else if (gameObject == ID_GRENADE_GUN_ITEM) - { - lara->Control.Weapon.RequestGunType = LaraWeaponType::GrenadeLauncher; - - if (lara->Control.HandStatus != HandStatus::Free) - return; - - if (lara->Control.Weapon.GunType == LaraWeaponType::GrenadeLauncher) - lara->Control.HandStatus = HandStatus::WeaponDraw; - - return; - } - else if (gameObject == ID_HARPOON_ITEM) - { - lara->Control.Weapon.RequestGunType = LaraWeaponType::HarpoonGun; - - if (lara->Control.HandStatus != HandStatus::Free) - return; - - if (lara->Control.Weapon.GunType == LaraWeaponType::HarpoonGun) - lara->Control.HandStatus = HandStatus::WeaponDraw; - - return; - } - else if (gameObject == ID_ROCKET_LAUNCHER_ITEM) - { - lara->Control.Weapon.RequestGunType = LaraWeaponType::RocketLauncher; - - if (lara->Control.HandStatus != HandStatus::Free) - return; - - if (lara->Control.Weapon.GunType == LaraWeaponType::RocketLauncher) - lara->Control.HandStatus = HandStatus::WeaponDraw; + default: return; } } void GuiController::DoInventory(ItemInfo* item) { - auto* lara = GetLaraInfo(item); + auto& player = GetLaraInfo(*item); auto& invRing = Rings[(int)RingTypes::Inventory]; auto& ammoRing = Rings[(int)RingTypes::Ammo]; @@ -2540,51 +2486,10 @@ namespace TEN::Gui !invRing.ObjectListMovement && !ammoRing.ObjectListMovement) { + CurrentSelectedOption = GetLoopedSelectedOption(CurrentSelectedOption, n - 1, g_Configuration.MenuOptionLoopingMode == MenuOptionLoopingMode::AllMenus); + if (AmmoActive) - { - if (GuiIsPulsed(In::Forward)) - { - if (CurrentSelectedOption <= 0) - CurrentSelectedOption = n - 1; - else - CurrentSelectedOption--; - - SoundEffect(SFX_TR4_MENU_SELECT, nullptr, SoundEnvironment::Always); - } - - if (GuiIsPulsed(In::Back)) - { - if (CurrentSelectedOption >= (n - 1)) - CurrentSelectedOption = 0; - else - CurrentSelectedOption++; - - SoundEffect(SFX_TR4_MENU_SELECT, nullptr, SoundEnvironment::Always); - } - *CurrentAmmoType = CurrentSelectedOption; - } - else - { - if (GuiIsPulsed(In::Forward)) - { - if (CurrentSelectedOption <= 0) - CurrentSelectedOption = n - 1; - else - CurrentSelectedOption--; - - SoundEffect(SFX_TR4_MENU_SELECT, nullptr, SoundEnvironment::Always); - } - else if (GuiIsPulsed(In::Back)) - { - if (CurrentSelectedOption >= (n - 1)) - CurrentSelectedOption = 0; - else - CurrentSelectedOption++; - - SoundEffect(SFX_TR4_MENU_SELECT, nullptr, SoundEnvironment::Always); - } - } if (GuiIsSelected(false)) { @@ -2654,12 +2559,12 @@ namespace TEN::Gui case MenuType::Equip: case MenuType::Use: MenuActive = false; - UseItem = true; + ItemUsed = true; break; case MenuType::Diary: SetInventoryMode(InventoryMode::Diary); - lara->Inventory.Diary.CurrentPage = 1; + player.Inventory.Diary.CurrentPage = 1; break; } } @@ -2687,64 +2592,64 @@ namespace TEN::Gui // Update the selected ammo of weapons that require it, and only these. void GuiController::UpdateWeaponStatus(ItemInfo* item) { - auto* lara = GetLaraInfo(item); + auto& player = GetLaraInfo(*item); - if (lara->Weapons[(int)LaraWeaponType::Shotgun].Present) + if (player.Weapons[(int)LaraWeaponType::Shotgun].Present) { if (Ammo.CurrentShotGunAmmoType) - lara->Weapons[(int)LaraWeaponType::Shotgun].SelectedAmmo = WeaponAmmoType::Ammo2; + player.Weapons[(int)LaraWeaponType::Shotgun].SelectedAmmo = WeaponAmmoType::Ammo2; else - lara->Weapons[(int)LaraWeaponType::Shotgun].SelectedAmmo = WeaponAmmoType::Ammo1; + player.Weapons[(int)LaraWeaponType::Shotgun].SelectedAmmo = WeaponAmmoType::Ammo1; } - if (lara->Weapons[(int)LaraWeaponType::Crossbow].Present) + if (player.Weapons[(int)LaraWeaponType::Crossbow].Present) { - lara->Weapons[(int)LaraWeaponType::Crossbow].SelectedAmmo = WeaponAmmoType::Ammo1; + player.Weapons[(int)LaraWeaponType::Crossbow].SelectedAmmo = WeaponAmmoType::Ammo1; if (Ammo.CurrentCrossBowAmmoType == 1) - lara->Weapons[(int)LaraWeaponType::Crossbow].SelectedAmmo = WeaponAmmoType::Ammo2; + player.Weapons[(int)LaraWeaponType::Crossbow].SelectedAmmo = WeaponAmmoType::Ammo2; else if (Ammo.CurrentCrossBowAmmoType == 2) - lara->Weapons[(int)LaraWeaponType::Crossbow].SelectedAmmo = WeaponAmmoType::Ammo3; + player.Weapons[(int)LaraWeaponType::Crossbow].SelectedAmmo = WeaponAmmoType::Ammo3; } - if (lara->Weapons[(int)LaraWeaponType::HK].Present) + if (player.Weapons[(int)LaraWeaponType::HK].Present) { - lara->Weapons[(int)LaraWeaponType::HK].WeaponMode = LaraWeaponTypeCarried::WTYPE_AMMO_1; - lara->Weapons[(int)LaraWeaponType::HK].SelectedAmmo = WeaponAmmoType::Ammo1; + player.Weapons[(int)LaraWeaponType::HK].WeaponMode = LaraWeaponTypeCarried::WTYPE_AMMO_1; + player.Weapons[(int)LaraWeaponType::HK].SelectedAmmo = WeaponAmmoType::Ammo1; if (Ammo.CurrentHKAmmoType == 1) { - lara->Weapons[(int)LaraWeaponType::HK].WeaponMode = LaraWeaponTypeCarried::WTYPE_AMMO_2; - lara->Weapons[(int)LaraWeaponType::HK].SelectedAmmo = WeaponAmmoType::Ammo1; + player.Weapons[(int)LaraWeaponType::HK].WeaponMode = LaraWeaponTypeCarried::WTYPE_AMMO_2; + player.Weapons[(int)LaraWeaponType::HK].SelectedAmmo = WeaponAmmoType::Ammo1; } else if (Ammo.CurrentHKAmmoType == 2) { - lara->Weapons[(int)LaraWeaponType::HK].WeaponMode = LaraWeaponTypeCarried::WTYPE_AMMO_3; - lara->Weapons[(int)LaraWeaponType::HK].SelectedAmmo = WeaponAmmoType::Ammo1; + player.Weapons[(int)LaraWeaponType::HK].WeaponMode = LaraWeaponTypeCarried::WTYPE_AMMO_3; + player.Weapons[(int)LaraWeaponType::HK].SelectedAmmo = WeaponAmmoType::Ammo1; } } - if (lara->Weapons[(int)LaraWeaponType::GrenadeLauncher].Present) + if (player.Weapons[(int)LaraWeaponType::GrenadeLauncher].Present) { - lara->Weapons[(int)LaraWeaponType::GrenadeLauncher].SelectedAmmo = WeaponAmmoType::Ammo1; + player.Weapons[(int)LaraWeaponType::GrenadeLauncher].SelectedAmmo = WeaponAmmoType::Ammo1; if (Ammo.CurrentGrenadeGunAmmoType == 1) - lara->Weapons[(int)LaraWeaponType::GrenadeLauncher].SelectedAmmo = WeaponAmmoType::Ammo2; + player.Weapons[(int)LaraWeaponType::GrenadeLauncher].SelectedAmmo = WeaponAmmoType::Ammo2; else if (Ammo.CurrentGrenadeGunAmmoType == 2) - lara->Weapons[(int)LaraWeaponType::GrenadeLauncher].SelectedAmmo = WeaponAmmoType::Ammo3; + player.Weapons[(int)LaraWeaponType::GrenadeLauncher].SelectedAmmo = WeaponAmmoType::Ammo3; } } void GuiController::SpinBack(EulerAngles& orient) { - orient.Lerp(EulerAngles::Identity, 1.0f / 8); + orient.Lerp(EulerAngles::Identity, 1.0f / (8.0f * g_Renderer.GetFramerateMultiplier())); } void GuiController::DrawAmmoSelector() { if (!AmmoSelectorFlag) return; - + int xPos = (2 * PHD_CENTER_X - OBJLIST_SPACING) / 2; if (NumAmmoSlots == 2) xPos -= OBJLIST_SPACING / 2; @@ -2760,13 +2665,13 @@ namespace TEN::Gui if (n == *CurrentAmmoType) { if (invObject->RotFlags & INV_ROT_X) - AmmoObjectList[n].Orientation.x += ANGLE(5.0f); + AmmoObjectList[n].Orientation.x += ANGLE(5.0f / g_Renderer.GetFramerateMultiplier()); if (invObject->RotFlags & INV_ROT_Y) - AmmoObjectList[n].Orientation.y += ANGLE(5.0f); + AmmoObjectList[n].Orientation.y += ANGLE(5.0f / g_Renderer.GetFramerateMultiplier()); if (invObject->RotFlags & INV_ROT_Z) - AmmoObjectList[n].Orientation.z += ANGLE(5.0f); + AmmoObjectList[n].Orientation.z += ANGLE(5.0f / g_Renderer.GetFramerateMultiplier()); } else SpinBack(AmmoObjectList[n].Orientation); @@ -2792,7 +2697,7 @@ namespace TEN::Gui // CHECK: AmmoSelectorFadeVal is never true and therefore the string is never printed. //if (AmmoSelectorFadeVal) g_Renderer.AddString(PHD_CENTER_X, 380, &invTextBuffer[0], PRINTSTRING_COLOR_YELLOW, (int)PrintStringFlags::Center | (int)PrintStringFlags::Outline); - + if (n == *CurrentAmmoType) g_Renderer.DrawObjectIn2DSpace(objectNumber, Vector2(x, y), AmmoObjectList[n].Orientation, scaler); else @@ -3138,13 +3043,13 @@ namespace TEN::Gui if (!i && !ring.ObjectListMovement) { if (invObject.RotFlags & INV_ROT_X) - listObject.Orientation.x += ANGLE(5.0f); + listObject.Orientation.x += ANGLE(5.0f / g_Renderer.GetFramerateMultiplier()); if (invObject.RotFlags & INV_ROT_Y) - listObject.Orientation.y += ANGLE(5.0f); + listObject.Orientation.y += ANGLE(5.0f / g_Renderer.GetFramerateMultiplier()); if (invObject.RotFlags & INV_ROT_Z) - listObject.Orientation.z += ANGLE(5.0f); + listObject.Orientation.z += ANGLE(5.0f / g_Renderer.GetFramerateMultiplier()); } else { @@ -3200,17 +3105,17 @@ namespace TEN::Gui if (ring.NumObjectsInList != 1 && (ringType != RingTypes::Ammo || CombineRingFadeVal == 128)) { if (ring.ObjectListMovement > 0) - ring.ObjectListMovement += ANGLE(45.0f); + ring.ObjectListMovement += ANGLE(45.0f / g_Renderer.GetFramerateMultiplier()); if (ring.ObjectListMovement < 0) - ring.ObjectListMovement -= ANGLE(45.0f); + ring.ObjectListMovement -= ANGLE(45.0f / g_Renderer.GetFramerateMultiplier()); if (IsHeld(In::Left)) { if (!ring.ObjectListMovement) { SoundEffect(SFX_TR4_MENU_ROTATE, nullptr, SoundEnvironment::Always); - ring.ObjectListMovement += ANGLE(45.0f); + ring.ObjectListMovement += ANGLE(45.0f / g_Renderer.GetFramerateMultiplier()); if (AmmoSelectorFlag) AmmoSelectorFadeDir = 2; @@ -3222,7 +3127,7 @@ namespace TEN::Gui if (!ring.ObjectListMovement) { SoundEffect(SFX_TR4_MENU_ROTATE, nullptr, SoundEnvironment::Always); - ring.ObjectListMovement -= ANGLE(45.0f); + ring.ObjectListMovement -= ANGLE(45.0f / g_Renderer.GetFramerateMultiplier()); if (AmmoSelectorFlag) AmmoSelectorFadeDir = 2; @@ -3263,6 +3168,8 @@ namespace TEN::Gui bool GuiController::CallPause() { + ClearAllDisplaySprites(); + g_Renderer.PrepareScene(); g_Renderer.DumpGameScene(); PauseAllSounds(SoundPauseMode::Pause); SoundEffect(SFX_TR4_MENU_SELECT, nullptr, SoundEnvironment::Always); @@ -3273,34 +3180,79 @@ namespace TEN::Gui bool doExitToTitle = false; + g_Synchronizer.Init(); + + bool legacy30FpsDoneDraw = false; + while (g_Gui.GetInventoryMode() == InventoryMode::Pause) { - g_Gui.DrawInventory(); - g_Renderer.Synchronize(); - - if (g_Gui.DoPauseMenu(LaraItem) == InventoryResult::ExitToTitle) + if (ThreadEnded) { - doExitToTitle = true; + App.ResetClock = true; + return false; + } + + g_Synchronizer.Sync(); + + while (g_Synchronizer.Synced()) + { + g_Renderer.PrepareScene(); + + if (g_Gui.DoPauseMenu(LaraItem) == InventoryResult::ExitToTitle) + { + doExitToTitle = true; + break; + } + + g_Synchronizer.Step(); + + legacy30FpsDoneDraw = false; + } + + if (doExitToTitle) break; + + if (!g_Configuration.EnableHighFramerate) + { + if (!legacy30FpsDoneDraw) + { + g_Renderer.RenderInventory(); + g_Renderer.Lock(); + g_Renderer.Synchronize(); + legacy30FpsDoneDraw = true; + } + } + else + { + g_Renderer.RenderInventory(); + g_Renderer.Lock(); } } if (doExitToTitle) + { StopAllSounds(); + } else + { ResumeAllSounds(SoundPauseMode::Pause); + } + + App.ResetClock = true; return doExitToTitle; } bool GuiController::CallInventory(ItemInfo* item, bool resetMode) { - auto* lara = GetLaraInfo(item); + auto& player = GetLaraInfo(*item); bool doLoad = false; - lara->Inventory.OldBusy = lara->Inventory.IsBusy; + player.Inventory.OldBusy = player.Inventory.IsBusy; + ClearAllDisplaySprites(); + g_Renderer.PrepareScene(); g_Renderer.DumpGameScene(); PauseAllSounds(SoundPauseMode::Inventory); SoundEffect(SFX_TR4_MENU_SELECT, nullptr, SoundEnvironment::Always); @@ -3309,99 +3261,129 @@ namespace TEN::Gui SetInventoryMode(InventoryMode::InGame); InitializeInventory(item); - Camera.numberFrames = 2; + g_Synchronizer.Init(); + + bool legacy30FpsDoneDraw = false; bool exitLoop = false; + while (!exitLoop) { if (ThreadEnded) + { + App.ResetClock = true; return false; - - TimeInMenu++; - GameTimer++; - - UpdateInputActions(item); - - if (GuiIsDeselected() || IsClicked(In::Inventory)) - { - SoundEffect(SFX_TR4_MENU_SELECT, nullptr, SoundEnvironment::Always); - exitLoop = true; } - DrawInventory(); - DrawCompass(item); + g_Synchronizer.Sync(); - switch (InvMode) + while (g_Synchronizer.Synced()) { - case InventoryMode::InGame: - DoInventory(item); - break; + TimeInMenu++; + GameTimer++; - case InventoryMode::Statistics: - DoStatisticsMode(); - break; + UpdateInputActions(item); - case InventoryMode::Examine: - DoExamineMode(); - break; - - case InventoryMode::Diary: - DoDiary(item); - break; - - case InventoryMode::Load: - switch (DoLoad()) + if (GuiIsDeselected() || IsClicked(In::Inventory)) { - case LoadResult::Load: - doLoad = true; + SoundEffect(SFX_TR4_MENU_SELECT, nullptr, SoundEnvironment::Always); exitLoop = true; - break; - - case LoadResult::Cancel: - exitLoop = !resetMode; - - if (resetMode) - SetInventoryMode(InventoryMode::InGame); - - break; - - case LoadResult::None: - break; } - break; + g_Renderer.PrepareScene(); - case InventoryMode::Save: - if (DoSave()) + switch (InvMode) { - exitLoop = !resetMode; - if (resetMode) - SetInventoryMode(InventoryMode::InGame); + case InventoryMode::InGame: + DoInventory(item); + break; + + case InventoryMode::Statistics: + DoStatisticsMode(); + break; + + case InventoryMode::Examine: + DoExamineMode(); + break; + + case InventoryMode::Diary: + DoDiary(item); + break; + + case InventoryMode::Load: + switch (DoLoad()) + { + case LoadResult::Load: + doLoad = true; + exitLoop = true; + break; + + case LoadResult::Cancel: + exitLoop = !resetMode; + + if (resetMode) + SetInventoryMode(InventoryMode::InGame); + + break; + + case LoadResult::None: + break; + } + + break; + + case InventoryMode::Save: + if (DoSave()) + { + exitLoop = !resetMode; + if (resetMode) + SetInventoryMode(InventoryMode::InGame); + } + + break; } - break; + if (ItemUsed && NoAction()) + exitLoop = true; + + SetEnterInventory(NO_VALUE); + g_Synchronizer.Step(); + + legacy30FpsDoneDraw = false; } - if (UseItem && NoAction()) - exitLoop = true; - - SetEnterInventory(NO_VALUE); - - Camera.numberFrames = g_Renderer.Synchronize(); + if (!g_Configuration.EnableHighFramerate) + { + if (!legacy30FpsDoneDraw) + { + g_Renderer.RenderInventory(); + g_Renderer.Lock(); + g_Renderer.Synchronize(); + legacy30FpsDoneDraw = true; + } + } + else + { + g_Renderer.RenderInventory(); + g_Renderer.Lock(); + } } LastInvItem = Rings[(int)RingTypes::Inventory].CurrentObjectList[Rings[(int)RingTypes::Inventory].CurrentObjectInList].InventoryItem; UpdateWeaponStatus(item); - if (UseItem) - UseCurrentItem(item); + if (ItemUsed) + UseItem(*item, InventoryObjectTable[LastInvItem].ObjectNumber); AlterFOV(LastFOV); + g_Renderer.PrepareScene(); ResumeAllSounds(SoundPauseMode::Inventory); - lara->Inventory.IsBusy = lara->Inventory.OldBusy; + player.Inventory.IsBusy = player.Inventory.OldBusy; SetInventoryMode(InventoryMode::None); + App.ResetClock = true; + return doLoad; } @@ -3427,6 +3409,15 @@ namespace TEN::Gui } } + void GuiController::CancelInventorySelection() + { + if (GetInventoryItemChosen() != NO_VALUE) + { + SetInventoryItemChosen(NO_VALUE); + SayNo(); + } + } + void GuiController::DrawCompass(ItemInfo* item) { constexpr auto POS_2D = Vector2(130.0f, 450.0f); @@ -3435,8 +3426,8 @@ namespace TEN::Gui auto needleOrient = EulerAngles(0, CompassNeedleAngle, 0); needleOrient.Lerp(EulerAngles(0, item->Pose.Orientation.y, 0), LERP_ALPHA); - float wibble = std::sin(((float)(GameTimer & 0x3F) / (float)0x3F) * PI_MUL_2); - this->CompassNeedleAngle = needleOrient.y + ANGLE(wibble); + float wibble = std::sin((float(GameTimer & 0x3F) / (float)0x3F) * PI_MUL_2); + CompassNeedleAngle = needleOrient.y + ANGLE(wibble / 2); // HACK: Needle is rotated in the draw function. const auto& invObject = InventoryObjectTable[INV_OBJECT_COMPASS]; @@ -3445,21 +3436,21 @@ namespace TEN::Gui void GuiController::DoDiary(ItemInfo* item) { - auto* lara = GetLaraInfo(item); + auto& player = GetLaraInfo(*item); SetInventoryMode(InventoryMode::Diary); if (GuiIsPulsed(In::Right) && - lara->Inventory.Diary.CurrentPage < lara->Inventory.Diary.NumPages) + player.Inventory.Diary.CurrentPage < player.Inventory.Diary.NumPages) { - lara->Inventory.Diary.CurrentPage++; + player.Inventory.Diary.CurrentPage++; SoundEffect(SFX_TR4_MENU_CHOOSE, nullptr, SoundEnvironment::Always); } if (GuiIsPulsed(In::Left) && - lara->Inventory.Diary.CurrentPage > 1) + player.Inventory.Diary.CurrentPage > 1) { - lara->Inventory.Diary.CurrentPage--; + player.Inventory.Diary.CurrentPage--; SoundEffect(SFX_TR4_MENU_CHOOSE, nullptr, SoundEnvironment::Always); } @@ -3475,27 +3466,56 @@ namespace TEN::Gui return SelectedSaveSlot; } - LoadResult GuiController::DoLoad() + int GuiController::GetLoopedSelectedOption(int selectedOption, int optionCount, bool canLoop) { - if (GuiIsPulsed(In::Back)) - { - SoundEffect(SFX_TR4_MENU_CHOOSE, nullptr, SoundEnvironment::Always); - - if (SelectedSaveSlot == (SAVEGAME_MAX - 1)) - SelectedSaveSlot -= SAVEGAME_MAX - 1; - else - SelectedSaveSlot++; - } - if (GuiIsPulsed(In::Forward)) { - SoundEffect(SFX_TR4_MENU_CHOOSE, nullptr, SoundEnvironment::Always); - - if (SelectedSaveSlot == 0) - SelectedSaveSlot += SAVEGAME_MAX - 1; + if (selectedOption <= 0) + { + if (IsClicked(In::Forward) && canLoop) + { + SoundEffect(SFX_TR4_MENU_CHOOSE, nullptr, SoundEnvironment::Always); + return optionCount; + } + } else - SelectedSaveSlot--; + { + SoundEffect(SFX_TR4_MENU_CHOOSE, nullptr, SoundEnvironment::Always); + return (selectedOption - 1); + } } + else if (GuiIsPulsed(In::Back)) + { + if (selectedOption >= optionCount) + { + if (IsClicked(In::Back) && canLoop) + { + SoundEffect(SFX_TR4_MENU_CHOOSE, nullptr, SoundEnvironment::Always); + return 0; + } + } + else + { + SoundEffect(SFX_TR4_MENU_CHOOSE, nullptr, SoundEnvironment::Always); + return (selectedOption + 1); + } + } + + return selectedOption; + } + + LoadResult GuiController::DoLoad() + { + constexpr auto DEATH_NO_INPUT_LOAD_TIMEOUT = 1 * FPS; + + bool canLoop = g_Configuration.MenuOptionLoopingMode == MenuOptionLoopingMode::SaveLoadOnly || + g_Configuration.MenuOptionLoopingMode == MenuOptionLoopingMode::AllMenus; + + // If load menu is accessed after death, delay input polling to allow player to stop spamming input. + if (GetLaraInfo(*LaraItem).Control.Count.Death > 0 && TimeInMenu < DEATH_NO_INPUT_LOAD_TIMEOUT) + return LoadResult::None; + + SelectedSaveSlot = GetLoopedSelectedOption(SelectedSaveSlot, SAVEGAME_MAX - 1, canLoop); if (GuiIsSelected()) { @@ -3517,25 +3537,9 @@ namespace TEN::Gui bool GuiController::DoSave() { - if (GuiIsPulsed(In::Back)) - { - SoundEffect(SFX_TR4_MENU_CHOOSE, nullptr, SoundEnvironment::Always); - - if (SelectedSaveSlot == (SAVEGAME_MAX - 1)) - SelectedSaveSlot -= SAVEGAME_MAX - 1; - else - SelectedSaveSlot++; - } - - if (GuiIsPulsed(In::Forward)) - { - SoundEffect(SFX_TR4_MENU_CHOOSE, nullptr, SoundEnvironment::Always); - - if (SelectedSaveSlot == 0) - SelectedSaveSlot += SAVEGAME_MAX - 1; - else - SelectedSaveSlot--; - } + bool canLoop = g_Configuration.MenuOptionLoopingMode == MenuOptionLoopingMode::SaveLoadOnly || + g_Configuration.MenuOptionLoopingMode == MenuOptionLoopingMode::AllMenus; + SelectedSaveSlot = GetLoopedSelectedOption(SelectedSaveSlot, SAVEGAME_MAX - 1, canLoop); if (GuiIsSelected()) { @@ -3552,10 +3556,10 @@ namespace TEN::Gui bool GuiController::PerformWaterskinCombine(ItemInfo* item, bool flag) { - auto* lara = GetLaraInfo(item); + auto& player = GetLaraInfo(*item); - int smallLiters = lara->Inventory.SmallWaterskin - 1; // How many liters in the small one? - int bigLiters = lara->Inventory.BigWaterskin - 1; // How many liters in the big one? + int smallLiters = player.Inventory.SmallWaterskin - 1; // How many liters in the small one? + int bigLiters = player.Inventory.BigWaterskin - 1; // How many liters in the big one? int smallCapacity = 3 - smallLiters; // How many more liters can fit in the small one? int bigCapacity = 5 - bigLiters; // How many more liters can fit in the big one? @@ -3563,7 +3567,7 @@ namespace TEN::Gui if (flag) { // Big one isn't empty and the small one isn't full. - if (lara->Inventory.BigWaterskin != 1 && smallCapacity) + if (player.Inventory.BigWaterskin != 1 && smallCapacity) { i = bigLiters; @@ -3579,18 +3583,18 @@ namespace TEN::Gui i--; } while (i); - lara->Inventory.SmallWaterskin = smallLiters + 1; - lara->Inventory.BigWaterskin = bigLiters + 1; + player.Inventory.SmallWaterskin = smallLiters + 1; + player.Inventory.BigWaterskin = bigLiters + 1; CombineObject1 = (smallLiters + 1) + (INV_OBJECT_SMALL_WATERSKIN_EMPTY - 1); return true; } } - else + else { // Small one isn't empty and the big one isn't full. - if (lara->Inventory.SmallWaterskin != 1 && bigCapacity) + if (player.Inventory.SmallWaterskin != 1 && bigCapacity) { - i = lara->Inventory.SmallWaterskin - 1; + i = player.Inventory.SmallWaterskin - 1; do { @@ -3604,8 +3608,8 @@ namespace TEN::Gui i--; } while (i); - lara->Inventory.SmallWaterskin = smallLiters + 1; - lara->Inventory.BigWaterskin = bigLiters + 1; + player.Inventory.SmallWaterskin = smallLiters + 1; + player.Inventory.BigWaterskin = bigLiters + 1; CombineObject1 = (bigLiters + 1) + (INV_OBJECT_BIG_WATERSKIN_EMPTY - 1); return true; } diff --git a/TombEngine/Game/gui.h b/TombEngine/Game/gui.h index 2bdf145bb..3dda2f61c 100644 --- a/TombEngine/Game/gui.h +++ b/TombEngine/Game/gui.h @@ -30,6 +30,7 @@ namespace TEN::Gui None, UseItem, NewGame, + HomeLevel, LoadGame, SaveGame, ExitGame, @@ -139,7 +140,7 @@ namespace TEN::Gui // Inventory variables short CombineObject1; short CombineObject2; - bool UseItem; + bool ItemUsed; char SeperateTypeFlag; char CombineTypeFlag; InventoryRing Rings[2]; @@ -180,8 +181,11 @@ namespace TEN::Gui void DrawAmmoSelector(); bool PerformWaterskinCombine(ItemInfo* item, bool flag); void DrawCompass(ItemInfo* item); + void CancelInventorySelection(); + void UseItem(ItemInfo& item, int objectNumber); // Getters + const InventoryRing& GetRing(RingTypes ringType); int GetSelectedOption(); Menu GetMenuToDisplay(); @@ -191,8 +195,10 @@ namespace TEN::Gui int GetLastInventoryItem(); SettingsData& GetCurrentSettings(); int GetLoadSaveSelection(); + int GetLoopedSelectedOption(int selectedOption, int optionCount, bool canLoop); // Setters + void SetSelectedOption(int menu); void SetMenuToDisplay(Menu menu); void SetInventoryMode(InventoryMode mode); @@ -220,7 +226,6 @@ namespace TEN::Gui void SeparateObject(ItemInfo* item, int objectNumber); void InsertObjectIntoList(int objectNumber); void InsertObjectIntoList_v2(int objectNumber); - void UseCurrentItem(ItemInfo* item); void SpinBack(EulerAngles& orient); void UpdateWeaponStatus(ItemInfo* item); void DoStatisticsMode(); diff --git a/TombEngine/Game/itemdata/creature_info.h b/TombEngine/Game/itemdata/creature_info.h index 206e3ded2..f2ba55346 100644 --- a/TombEngine/Game/itemdata/creature_info.h +++ b/TombEngine/Game/itemdata/creature_info.h @@ -83,12 +83,6 @@ struct CreatureBiteInfo Position = pos; BoneID = boneID; } - - CreatureBiteInfo(float x, float y, float z, int boneID) - { - Position = Vector3(x, y, z); - BoneID = boneID; - } }; struct CreatureMuzzleFlashInfo diff --git a/TombEngine/Game/items.cpp b/TombEngine/Game/items.cpp index 29a4cf9bc..5f30fac06 100644 --- a/TombEngine/Game/items.cpp +++ b/TombEngine/Game/items.cpp @@ -3,6 +3,7 @@ #include "Game/collision/floordata.h" #include "Game/collision/collide_room.h" +#include "Game/collision/Point.h" #include "Game/control/control.h" #include "Game/control/volume.h" #include "Game/effects/effects.h" @@ -16,6 +17,7 @@ #include "Objects/Generic/Object/BridgeObject.h" #include "Objects/Generic/Object/Pushable/PushableInfo.h" #include "Objects/Generic/Object/Pushable/PushableObject.h" +#include "Renderer/Renderer.h" #include "Scripting/Include/Objects/ScriptInterfaceObjectsHandler.h" #include "Scripting/Include/ScriptInterfaceGame.h" #include "Scripting/Internal/TEN/Objects/ObjectIDs.h" @@ -26,12 +28,15 @@ #include "Specific/trutils.h" using namespace TEN::Collision::Floordata; +using namespace TEN::Collision::Point; +using namespace TEN::Collision::Room; using namespace TEN::Control::Volumes; using namespace TEN::Effects::Items; using namespace TEN::Entities::Generic; using namespace TEN::Input; using namespace TEN::Math; using namespace TEN::Utils; +using TEN::Renderer::g_Renderer; constexpr auto ITEM_DEATH_TIMEOUT = 4 * FPS; @@ -179,6 +184,11 @@ bool ItemInfo::IsBridge() const return Contains(BRIDGE_OBJECT_IDS, ObjectNumber); } +std::vector ItemInfo::GetSpheres() const +{ + return g_Renderer.GetSpheres(Index); +} + bool TestState(int refState, const std::vector& stateList) { for (const auto& state : stateList) @@ -581,9 +591,9 @@ void InitializeItem(short itemNumber) item->NextItem = room->itemNumber; room->itemNumber = itemNumber; - FloorInfo* floor = GetSector(room, item->Pose.Position.x - room->x, item->Pose.Position.z - room->z); + FloorInfo* floor = GetSector(room, item->Pose.Position.x - room->Position.x, item->Pose.Position.z - room->Position.z); item->Floor = floor->GetSurfaceHeight(item->Pose.Position.x, item->Pose.Position.z, true); - item->BoxNumber = floor->Box; + item->BoxNumber = floor->PathfindingBoxID; item->ResetModelToDefault(); @@ -600,6 +610,8 @@ short CreateItem() g_Level.Items[NextItemFree].Flags = 0; NextItemFree = g_Level.Items[NextItemFree].NextItem; + g_Level.Items[itemNumber].DisableInterpolation = true; + return itemNumber; } @@ -802,10 +814,9 @@ bool UpdateItemRoom(short itemNumber) { auto* item = &g_Level.Items[itemNumber]; - auto roomNumber = GetCollision(item->Pose.Position.x, - item->Pose.Position.y - CLICK(2), - item->Pose.Position.z, - item->RoomNumber).RoomNumber; + auto roomNumber = GetPointCollision( + Vector3i(item->Pose.Position.x, item->Pose.Position.y - CLICK(2), item->Pose.Position.z), + item->RoomNumber).GetRoomNumber(); if (roomNumber != item->RoomNumber) { diff --git a/TombEngine/Game/items.h b/TombEngine/Game/items.h index cda9aafee..2d1d7e438 100644 --- a/TombEngine/Game/items.h +++ b/TombEngine/Game/items.h @@ -109,7 +109,7 @@ struct ItemInfo int Index = 0; // ItemNumber // TODO: Make int. GAME_OBJECT_ID ObjectNumber = ID_NO_OBJECT; // ObjectID - /*ItemStatus*/int Status = ITEM_NOT_ACTIVE; + ItemStatus Status = ITEM_NOT_ACTIVE; bool Active = false; // TODO: Refactor linked list. @@ -128,11 +128,12 @@ struct ItemInfo short RoomNumber = 0; // TODO: Make int. int Floor = 0; - int HitPoints = 0; - bool HitStatus = false; - bool LookedAt = false; - bool Collidable = false; - bool InDrawRoom = false; + int HitPoints = 0; + bool HitStatus = false; + bool LookedAt = false; + bool Collidable = false; + bool InDrawRoom = false; + bool DisableInterpolation = false; int BoxNumber = 0; int Timer = 0; @@ -150,11 +151,13 @@ struct ItemInfo short CarriedItem = 0; // OCB utilities + bool TestOcb(short ocbFlags) const; void RemoveOcb(short ocbFlags); void ClearAllOcb(); // ItemFlags utilities + bool TestFlags(int id, short flags) const; // ItemFlags[id] & flags bool TestFlagField(int id, short flags) const; // ItemFlags[id] == flags short GetFlagField(int id) const; // ItemFlags[id] @@ -162,6 +165,7 @@ struct ItemInfo void ClearFlags(int id, short flags); // ItemFlags[id] &= ~flags // Model utilities + bool TestMeshSwapFlags(unsigned int flags); bool TestMeshSwapFlags(const std::vector& flags); void SetMeshSwapFlags(unsigned int flags, bool clear = false); @@ -169,9 +173,14 @@ struct ItemInfo void ResetModelToDefault(); // Inquirers + bool IsLara() const; bool IsCreature() const; bool IsBridge() const; + + // Getters + + std::vector GetSpheres() const; }; bool TestState(int refState, const std::vector& stateList); diff --git a/TombEngine/Game/misc.cpp b/TombEngine/Game/misc.cpp index 878281da8..7fe379a31 100644 --- a/TombEngine/Game/misc.cpp +++ b/TombEngine/Game/misc.cpp @@ -2,89 +2,81 @@ #include "Game/misc.h" #include "Game/animation.h" +#include "Game/collision/Point.h" #include "Game/Lara/lara.h" #include "Game/itemdata/creature_info.h" #include "Game/items.h" #include "Game/Setup.h" #include "Specific/level.h" -using std::vector; +using namespace TEN::Collision::Point; +using namespace TEN::Utils; CreatureInfo* GetCreatureInfo(ItemInfo* item) { return (CreatureInfo*)item->Data; } -void TargetNearestEntity(ItemInfo* item, CreatureInfo* creature) -{ - float nearestDistance = INFINITY; - - for (int i = 0; i < g_Level.NumItems; i++) - { - auto* targetEntity = &g_Level.Items[i]; - - if (targetEntity == nullptr) - continue; - - if (targetEntity != item && - targetEntity->HitPoints > 0 && - targetEntity->Status != ITEM_INVISIBLE) - { - float distance = Vector3i::Distance(item->Pose.Position, targetEntity->Pose.Position); - if (distance < nearestDistance) - { - creature->Enemy = targetEntity; - nearestDistance = distance; - } - } - } -} - -bool IsNextSectorValid(const ItemInfo& item, const Vector3& dir, float dist) +bool IsNextSectorValid(const ItemInfo& item, const Vector3& dir, float dist, bool canFloat) { auto projectedPos = Geometry::TranslatePoint(item.Pose.Position, dir, dist); - auto pointColl = GetCollision(item.Pose.Position, item.RoomNumber, dir, dist); + auto pointColl = GetPointCollision(item.Pose.Position, item.RoomNumber, dir, dist); int height = GameBoundingBox(&item).GetHeight(); + // TODO: Use floor normal directly. + auto floorTilt = GetSurfaceTilt(pointColl.GetFloorNormal(), true); + // Test for wall. - if (pointColl.Block->IsWall(projectedPos.x, projectedPos.z)) + if (pointColl.GetSector().IsWall(projectedPos.x, projectedPos.z)) return false; // Test for slippery slope. - if (pointColl.Position.FloorSlope) + if (pointColl.IsSteepFloor()) return false; // Flat floor. - if ((abs(pointColl.FloorTilt.x) == 0 && abs(pointColl.FloorTilt.y) == 0)) + if ((abs(floorTilt.x) == 0 && abs(floorTilt.y) == 0)) { // Test for step. - int relFloorHeight = abs(pointColl.Position.Floor - item.Pose.Position.y); - if (relFloorHeight >= CLICK(1) && item.Pose.Position.y >= pointColl.Position.Floor) + int relFloorHeight = abs(pointColl.GetFloorHeight() - item.Pose.Position.y); + if (relFloorHeight >= CLICK(1) && item.Pose.Position.y >= pointColl.GetFloorHeight() && canFloat) + { return false; + } + else if (relFloorHeight >= CLICK(1) && !canFloat) + { + return false; + } } // Sloped floor. else { // Half block. - int relFloorHeight = abs(pointColl.Position.Floor - item.Pose.Position.y); - if (relFloorHeight > CLICK(1)) + int relFloorHeight = abs(pointColl.GetFloorHeight() - item.Pose.Position.y); + if (relFloorHeight > CLICK(1) && canFloat) + { return false; + } + else if (relFloorHeight > CLICK(2) && !canFloat) + { + return false; + } short slopeAngle = ANGLE(0.0f); - if (pointColl.FloorTilt.x > 0) + if (floorTilt.x > 0) { slopeAngle = ANGLE(-90.0f); } - else if (pointColl.FloorTilt.x < 0) + else if (floorTilt.x < 0) { slopeAngle = ANGLE(90.0f); } - if (pointColl.FloorTilt.y > 0 && pointColl.FloorTilt.y > abs(pointColl.FloorTilt.x)) + if (floorTilt.y > 0 && floorTilt.y > abs(floorTilt.x)) { slopeAngle = ANGLE(180.0f); } - else if (pointColl.FloorTilt.y < 0 && -pointColl.FloorTilt.y > abs(pointColl.FloorTilt.x)) + else if (floorTilt.y < 0 && -floorTilt.y > abs(floorTilt.x)) { slopeAngle = ANGLE(0.0f); } @@ -98,29 +90,37 @@ bool IsNextSectorValid(const ItemInfo& item, const Vector3& dir, float dist) } // Check for diagonal split. - if (pointColl.Position.DiagonalStep) + if (pointColl.IsDiagonalFloorStep()) return false; // Test ceiling height. - int relCeilHeight = abs(pointColl.Position.Ceiling - pointColl.Position.Floor); + int relCeilHeight = abs(pointColl.GetCeilingHeight() - pointColl.GetFloorHeight()); - if (relCeilHeight <= height) - return false; - - // Check for blocked grey box. - if (g_Level.Boxes[pointColl.Block->Box].flags & BLOCKABLE) + if (canFloat) { - if (g_Level.Boxes[pointColl.Block->Box].flags & BLOCKED) + if (relCeilHeight <= height) + return false; + } + else + { + if (relCeilHeight < BLOCK(1)) return false; } // Check for inaccessible sector. - if (pointColl.Block->Box == NO_VALUE) + if (pointColl.GetSector().PathfindingBoxID == NO_VALUE) return false; + // Check for blocked grey box. + if (g_Level.PathfindingBoxes[pointColl.GetSector().PathfindingBoxID].flags & BLOCKABLE) + { + if (g_Level.PathfindingBoxes[pointColl.GetSector().PathfindingBoxID].flags & BLOCKED) + return false; + } + // Check for stopper flag. - if (pointColl.Block->Stopper) + if (pointColl.GetSector().Stopper) return false; return true; -} \ No newline at end of file +} diff --git a/TombEngine/Game/misc.h b/TombEngine/Game/misc.h index ef00bf1fd..3981f8fc6 100644 --- a/TombEngine/Game/misc.h +++ b/TombEngine/Game/misc.h @@ -18,5 +18,4 @@ enum LaraMeshMask }; CreatureInfo* GetCreatureInfo(ItemInfo* item); -void TargetNearestEntity(ItemInfo* item, CreatureInfo* creature); -bool IsNextSectorValid(const ItemInfo& item, const Vector3& dir, float dist); +bool IsNextSectorValid(const ItemInfo& item, const Vector3& dir, float dist, bool canFloat); diff --git a/TombEngine/Game/missile.cpp b/TombEngine/Game/missile.cpp index 19b253ad9..f77cc2a4e 100644 --- a/TombEngine/Game/missile.cpp +++ b/TombEngine/Game/missile.cpp @@ -4,6 +4,7 @@ #include "Game/animation.h" #include "Game/collision/collide_item.h" #include "Game/collision/collide_room.h" +#include "Game/collision/Point.h" #include "Game/effects/tomb4fx.h" #include "Game/effects/effects.h" #include "Game/effects/explosion.h" @@ -15,6 +16,7 @@ #include "Sound/sound.h" #include "Specific/level.h" +using namespace TEN::Collision::Point;; using namespace TEN::Effects::Bubble; using namespace TEN::Effects::Explosion; using namespace TEN::Math; @@ -48,7 +50,7 @@ void ControlMissile(short fxNumber) auto& fx = EffectList[fxNumber]; auto isUnderwater = TestEnvironment(ENV_FLAG_WATER, fx.roomNumber); - auto soundFXType = isUnderwater ? SoundEnvironment::Water : SoundEnvironment::Land; + auto soundFXType = isUnderwater ? SoundEnvironment::ShallowWater : SoundEnvironment::Land; if (fx.objectNumber == ID_SCUBA_HARPOON && isUnderwater && fx.pos.Orientation.x > ANGLE(-67.5f)) @@ -58,12 +60,13 @@ void ControlMissile(short fxNumber) fx.pos.Translate(fx.pos.Orientation, fx.speed); - auto pointColl = GetCollision(fx.pos.Position.x, fx.pos.Position.y, fx.pos.Position.z, fx.roomNumber); + auto pointColl = GetPointCollision(fx.pos.Position, fx.roomNumber); auto hasHitPlayer = ItemNearLara(fx.pos.Position, hitRadius); // Check whether something was hit. - if (fx.pos.Position.y >= pointColl.Position.Floor || - fx.pos.Position.y <= pointColl.Position.Ceiling || + if (fx.pos.Position.y >= pointColl.GetFloorHeight() || + fx.pos.Position.y <= pointColl.GetCeilingHeight() || + pointColl.IsWall() || hasHitPlayer) { if (fx.objectNumber == ID_KNIFETHROWER_KNIFE || @@ -109,10 +112,11 @@ void ControlMissile(short fxNumber) } KillEffect(fxNumber); + return; } - if (pointColl.RoomNumber != fx.roomNumber) - EffectNewRoom(fxNumber, pointColl.RoomNumber); + if (pointColl.GetRoomNumber() != fx.roomNumber) + EffectNewRoom(fxNumber, pointColl.GetRoomNumber()); if (fx.objectNumber == ID_KNIFETHROWER_KNIFE) fx.pos.Orientation.z += ANGLE(30.0f); // Update knife rotation over time. @@ -142,23 +146,23 @@ void ControlNatlaGun(short fxNumber) // If first frame, start another explosion at next position. if (fx.frameNumber == -1) { - auto pointColl = GetCollision(fx.pos.Position, fx.roomNumber, fx.pos.Orientation.y, fx.speed); + auto pointColl = GetPointCollision(fx.pos.Position, fx.roomNumber, fx.pos.Orientation.y, fx.speed); // Don't create one if hit a wall. - if (pointColl.Coordinates.y >= pointColl.Position.Floor || - pointColl.Coordinates.y <= pointColl.Position.Ceiling) + if (pointColl.GetPosition().y >= pointColl.GetFloorHeight() || + pointColl.GetPosition().y <= pointColl.GetCeilingHeight()) { return; } - fxNumber = CreateNewEffect(pointColl.RoomNumber); + fxNumber = CreateNewEffect(pointColl.GetRoomNumber()); if (fxNumber != NO_VALUE) { auto& fxNew = EffectList[fxNumber]; - fxNew.pos.Position = pointColl.Coordinates; + fxNew.pos.Position = pointColl.GetPosition(); fxNew.pos.Orientation.y = fx.pos.Orientation.y; - fxNew.roomNumber = pointColl.RoomNumber; + fxNew.roomNumber = pointColl.GetRoomNumber(); fxNew.speed = fx.speed; fxNew.frameNumber = 0; fxNew.objectNumber = ID_PROJ_BOMB; diff --git a/TombEngine/Game/people.cpp b/TombEngine/Game/people.cpp index acbf086f1..5b9b1e943 100644 --- a/TombEngine/Game/people.cpp +++ b/TombEngine/Game/people.cpp @@ -3,7 +3,6 @@ #include "Game/animation.h" #include "Game/control/los.h" -#include "Game/collision/sphere.h" #include "Game/effects/effects.h" #include "Game/effects/debris.h" #include "Game/itemdata/creature_info.h" @@ -169,8 +168,8 @@ bool TargetVisible(ItemInfo* item, AI_INFO* ai, float maxAngleInDegrees) auto target = GameVector( enemy->Pose.Position.x, enemy->Pose.Position.y + ((((bounds.Y1 * 2) + bounds.Y1) + bounds.Y2) / 4), - enemy->Pose.Position.z, - enemy->RoomNumber); // TODO: Check why this line didn't exist before. -- TokyoSU, 10/8/2022 + enemy->Pose.Position.z); + return LOS(&origin, &target); } diff --git a/TombEngine/Game/pickup/pickup.cpp b/TombEngine/Game/pickup/pickup.cpp index bc53726a2..3b8f2fd83 100644 --- a/TombEngine/Game/pickup/pickup.cpp +++ b/TombEngine/Game/pickup/pickup.cpp @@ -5,6 +5,7 @@ #include "Game/animation.h" #include "Game/camera.h" #include "Game/collision/collide_item.h" +#include "Game/collision/Point.h" #include "Game/effects/debris.h" #include "Game/Gui.h" #include "Game/Hud/Hud.h" @@ -30,6 +31,7 @@ #include "Specific/level.h" #include "Scripting/Include/Flow/ScriptInterfaceFlowHandler.h" +using namespace TEN::Collision::Point; using namespace TEN::Entities::Generic; using namespace TEN::Hud; using namespace TEN::Input; @@ -158,6 +160,11 @@ void PickedUpObject(GAME_OBJECT_ID objectID, std::optional count) } } +void PickedUpObject(ItemInfo& item) +{ + PickedUpObject(item.ObjectNumber, item.HitPoints > 0 ? std::optional(item.HitPoints) : std::nullopt); +} + int GetInventoryCount(GAME_OBJECT_ID objectID) { auto boolResult = HasWeapon(Lara, objectID); @@ -196,18 +203,18 @@ void RemoveObjectFromInventory(GAME_OBJECT_ID objectID, std::optional count } } -void CollectCarriedItems(ItemInfo* item) +void CollectCarriedItems(ItemInfo* item) { short pickupNumber = item->CarriedItem; while (pickupNumber != NO_VALUE) { - auto* pickupItem = &g_Level.Items[pickupNumber]; + auto& pickupItem = g_Level.Items[pickupNumber]; - PickedUpObject(pickupItem->ObjectNumber); - g_Hud.PickupSummary.AddDisplayPickup(pickupItem->ObjectNumber, pickupItem->Pose.Position.ToVector3()); + PickedUpObject(pickupItem); + g_Hud.PickupSummary.AddDisplayPickup(pickupItem); KillItem(pickupNumber); - pickupNumber = pickupItem->CarriedItem; + pickupNumber = pickupItem.CarriedItem; } item->CarriedItem = NO_VALUE; @@ -232,8 +239,8 @@ void CollectMultiplePickups(int itemNumber) auto& firstItem = g_Level.Items[itemNumber]; auto collObjects = GetCollidedObjects(firstItem, true, true, LARA_RADIUS, ObjectCollectionMode::Items); - collObjects.ItemPtrs.push_back(&firstItem); - for (auto* itemPtr : collObjects.ItemPtrs) + collObjects.Items.push_back(&firstItem); + for (auto* itemPtr : collObjects.Items) { if (!Objects[itemPtr->ObjectNumber].isPickup) continue; @@ -245,8 +252,8 @@ void CollectMultiplePickups(int itemNumber) continue; } - PickedUpObject(itemPtr->ObjectNumber); - g_Hud.PickupSummary.AddDisplayPickup(itemPtr->ObjectNumber, itemPtr->Pose.Position.ToVector3()); + PickedUpObject(*itemPtr); + g_Hud.PickupSummary.AddDisplayPickup(*itemPtr); if (itemPtr->TriggerFlags & (1 << 8)) { @@ -316,8 +323,8 @@ void DoPickup(ItemInfo* laraItem) return; } - PickedUpObject(pickupItem->ObjectNumber); - g_Hud.PickupSummary.AddDisplayPickup(pickupItem->ObjectNumber, pickupItem->Pose.Position.ToVector3()); + PickedUpObject(*pickupItem); + g_Hud.PickupSummary.AddDisplayPickup(*pickupItem); HideOrDisablePickup(*pickupItem); pickupItem->Pose.Orientation = prevOrient; @@ -345,8 +352,8 @@ void DoPickup(ItemInfo* laraItem) return; } - PickedUpObject(pickupItem->ObjectNumber); - g_Hud.PickupSummary.AddDisplayPickup(pickupItem->ObjectNumber, pickupItem->Pose.Position.ToVector3()); + PickedUpObject(*pickupItem); + g_Hud.PickupSummary.AddDisplayPickup(*pickupItem); if (pickupItem->TriggerFlags & (1 << 8)) { @@ -835,13 +842,13 @@ void DropPickups(ItemInfo* item) auto bounds = GameBoundingBox(item); auto extents = bounds.GetExtents(); auto origin = Geometry::TranslatePoint(item->Pose.Position.ToVector3(), item->Pose.Orientation, bounds.GetCenter()); - auto yPos = GetCollision(item).Position.Floor; + auto yPos = GetPointCollision(*item).GetFloorHeight(); origin.y = yPos; // Initialize drop origin Y point as floor height at centerpoint, in case all corner tests fail. auto collObjects = GetCollidedObjects(*item, true, true); - short startAngle = ANGLE(Random::GenerateInt(0, 3) * 90); // Randomize start corner. + short startAngle = ANGLE(Random::GenerateInt(0, 3) * 90.0f); // Randomize start corner. // Iterate through 4 corners and find best-fitting position, which is not inside a wall, not on a slope // and also does not significantly differ in height to an object centerpoint height. @@ -850,31 +857,31 @@ void DropPickups(ItemInfo* item) for (int corner = 0; corner < 4; corner++) { auto angle = item->Pose.Orientation; - angle.y += startAngle + corner * ANGLE(90); + angle.y += startAngle + corner * ANGLE(90.0f); // At first, do an inside-wall test at an extended extent point to make sure player can correctly align. auto candidatePos = Geometry::TranslatePoint(origin, angle, extents * 1.2f); candidatePos.y = yPos; - auto collPoint = GetCollision(candidatePos.x, candidatePos.y, candidatePos.z, item->RoomNumber); + auto collPoint = GetPointCollision(candidatePos, item->RoomNumber); // If position is inside a wall or on a slope, don't use it. - if (collPoint.Position.Floor == NO_HEIGHT || collPoint.Position.FloorSlope) + if (collPoint.GetFloorHeight() == NO_HEIGHT || collPoint.IsSteepFloor()) continue; // Remember floor position for a tested point. - int candidateYPos = collPoint.Position.Floor; + int candidateYPos = collPoint.GetFloorHeight(); // Now repeat the same test for original extent point to make sure it's also valid. candidatePos = Geometry::TranslatePoint(origin, angle, extents); candidatePos.y = yPos; - collPoint = GetCollision(candidatePos.x, candidatePos.y, candidatePos.z, item->RoomNumber); + collPoint = GetPointCollision(candidatePos, item->RoomNumber); // If position is inside a wall or on a slope, don't use it. - if (collPoint.Position.Floor == NO_HEIGHT || collPoint.Position.FloorSlope) + if (collPoint.GetFloorHeight() == NO_HEIGHT || collPoint.IsSteepFloor()) continue; // If position is not in the same room, don't use it. - if (collPoint.RoomNumber != item->RoomNumber) + if (collPoint.GetRoomNumber() != item->RoomNumber) continue; // Setup a dummy sphere with 1-click diameter for item and static mesh collision tests. @@ -884,7 +891,7 @@ void DropPickups(ItemInfo* item) // Iterate through all found items and statics around, and determine if dummy sphere // intersects any of those. If so, try other corner. - for (const auto* itemPtr : collObjects.ItemPtrs) + for (const auto* itemPtr : collObjects.Items) { auto box = GameBoundingBox(itemPtr).ToBoundingOrientedBox(itemPtr->Pose); if (box.Intersects(sphere)) @@ -894,7 +901,7 @@ void DropPickups(ItemInfo* item) } } - for (auto* staticPtr : collObjects.StaticPtrs) + for (auto* staticPtr : collObjects.Statics) { auto& object = StaticObjects[staticPtr->staticNumber]; @@ -911,16 +918,16 @@ void DropPickups(ItemInfo* item) // Finally, do height difference tests. If difference is more than one and a half click, // most likely it's hanging in the air or submerged, so bypass the corner. - if (abs(collPoint.Position.Floor - yPos) > CLICK(1.5f)) + if (abs(collPoint.GetFloorHeight() - yPos) > CLICK(1.5f)) continue; // If height difference between extent points is more than one click, it means it landed // on a step, so let's search for other position. - if (abs(collPoint.Position.Floor - candidateYPos) >= CLICK(1.0f)) + if (abs(collPoint.GetFloorHeight() - candidateYPos) >= CLICK(1.0f)) continue; origin = candidatePos; - origin.y = collPoint.Position.Floor; + origin.y = collPoint.GetFloorHeight(); break; } @@ -1074,19 +1081,23 @@ void InitializePickup(short itemNumber) if (triggerFlags == 0) { // Automatically align pickups to the floor surface. - auto pointColl = GetCollision(item); - int bridgeItemNumber = pointColl.Block->GetInsideBridgeItemNumber(item->Pose.Position, true, true); + auto pointColl = GetPointCollision(*item); + int bridgeItemNumber = pointColl.GetSector().GetInsideBridgeItemNumber(item->Pose.Position, true, true); if (bridgeItemNumber != NO_VALUE) { // If pickup is within bridge item, most likely it means it is // below pushable or raising block, so ignore its collision. - pointColl.Block->RemoveBridge(bridgeItemNumber); - pointColl = GetCollision(item); - pointColl.Block->AddBridge(bridgeItemNumber); + pointColl.GetSector().RemoveBridge(bridgeItemNumber); + pointColl = GetPointCollision(*item); + item->Pose.Position.y = pointColl.GetFloorHeight() - bounds.Y2; + pointColl.GetSector().AddBridge(bridgeItemNumber); + } + else + { + item->Pose.Position.y = pointColl.GetFloorHeight() - bounds.Y2; } - item->Pose.Position.y = pointColl.Position.Floor - bounds.Y2; AlignEntityToSurface(item, Vector2(Objects[item->ObjectNumber].radius)); } } @@ -1257,8 +1268,8 @@ void SearchObjectControl(short itemNumber) if (Objects[item2->ObjectNumber].isPickup) { - PickedUpObject(item2->ObjectNumber); - g_Hud.PickupSummary.AddDisplayPickup(item2->ObjectNumber, item2->Pose.Position.ToVector3()); + PickedUpObject(*item2); + g_Hud.PickupSummary.AddDisplayPickup(*item2); KillItem(item->ItemFlags[1]); } else diff --git a/TombEngine/Game/pickup/pickup.h b/TombEngine/Game/pickup/pickup.h index 99ce4c71c..0e154da66 100644 --- a/TombEngine/Game/pickup/pickup.h +++ b/TombEngine/Game/pickup/pickup.h @@ -13,6 +13,7 @@ extern Vector3i OldPickupPos; void InitializePickup(short itemNumber); bool SetInventoryCount(GAME_OBJECT_ID objectID, int count); void PickedUpObject(GAME_OBJECT_ID objectID, std::optional count = std::nullopt); +void PickedUpObject(ItemInfo& item); void RemoveObjectFromInventory(GAME_OBJECT_ID objectID, std::optional count = std::nullopt); int GetInventoryCount(GAME_OBJECT_ID objectID); void CollectCarriedItems(ItemInfo* item); diff --git a/TombEngine/Game/pickup/pickup_ammo.cpp b/TombEngine/Game/pickup/pickup_ammo.cpp index 5df02fc06..bd6d957da 100644 --- a/TombEngine/Game/pickup/pickup_ammo.cpp +++ b/TombEngine/Game/pickup/pickup_ammo.cpp @@ -38,7 +38,7 @@ static constexpr std::array kAmmo bool TryModifyingAmmo(LaraInfo& lara, GAME_OBJECT_ID objectID, std::optional amount, ModificationType modType) { int arrayPos = GetArraySlot(kAmmo, objectID); - if (-1 == arrayPos) + if (arrayPos == NO_VALUE) return false; auto ammoPickup = kAmmo[arrayPos]; @@ -50,7 +50,7 @@ bool TryModifyingAmmo(LaraInfo& lara, GAME_OBJECT_ID objectID, std::optional std::optional GetAmmoCount(LaraInfo& lara, GAME_OBJECT_ID objectID) { int arrayPos = GetArraySlot(kAmmo, objectID); - if (-1 == arrayPos) + if (arrayPos == NO_VALUE) return std::nullopt; AmmoPickupInfo info = kAmmo[arrayPos]; @@ -91,6 +91,15 @@ std::optional GetAmmoCount(LaraInfo& lara, GAME_OBJECT_ID objectID) if (!lara.Weapons[(int)info.LaraWeaponType].Ammo[(int)info.AmmoType].HasInfinite()) return lara.Weapons[(int)info.LaraWeaponType].Ammo[(int)info.AmmoType].GetCount(); - // -1 signifies infinite ammo. - return -1; + // NO_VALUE signifies infinite ammo. + return NO_VALUE; +} + +int GetDefaultAmmoCount(GAME_OBJECT_ID objectID) +{ + int arrayPos = GetArraySlot(kAmmo, objectID); + if (arrayPos == NO_VALUE) + return NO_VALUE; + + return kAmmo[arrayPos].Amount; } diff --git a/TombEngine/Game/pickup/pickup_ammo.h b/TombEngine/Game/pickup/pickup_ammo.h index 4a05e92b2..3d7565c7f 100644 --- a/TombEngine/Game/pickup/pickup_ammo.h +++ b/TombEngine/Game/pickup/pickup_ammo.h @@ -8,3 +8,4 @@ bool TryAddingAmmo(LaraInfo&, GAME_OBJECT_ID objectID, std::optional amount bool TryRemovingAmmo(LaraInfo&, GAME_OBJECT_ID objectID, std::optional amount = std::nullopt); bool TryModifyingAmmo(LaraInfo& lara, GAME_OBJECT_ID objectID, std::optional amount, ModificationType modType); std::optional GetAmmoCount(LaraInfo&, GAME_OBJECT_ID objectID); +int GetDefaultAmmoCount(GAME_OBJECT_ID objectID); diff --git a/TombEngine/Game/pickup/pickup_consumable.cpp b/TombEngine/Game/pickup/pickup_consumable.cpp index 582652293..0bdeccc35 100644 --- a/TombEngine/Game/pickup/pickup_consumable.cpp +++ b/TombEngine/Game/pickup/pickup_consumable.cpp @@ -30,7 +30,7 @@ static constexpr std::array kConsumables = bool TryModifyingConsumable(LaraInfo& lara, GAME_OBJECT_ID objectID, std::optional amount, ModificationType modType) { int arrayPos = GetArraySlot(kConsumables, objectID); - if (-1 == arrayPos) + if (arrayPos == NO_VALUE) return false; ConsumablePickupInfo info = kConsumables[arrayPos]; @@ -42,7 +42,7 @@ bool TryModifyingConsumable(LaraInfo& lara, GAME_OBJECT_ID objectID, std::option break; default: - if (currentAmt != -1) + if (currentAmt != NO_VALUE) { int defaultModify = ModificationType::Add == modType ? info.Amount : -info.Amount; int newVal = currentAmt + (amount.has_value() ? amount.value() : defaultModify); @@ -70,10 +70,19 @@ bool TryRemovingConsumable(LaraInfo& lara, GAME_OBJECT_ID objectID, std::optiona std::optional GetConsumableCount(LaraInfo& lara, GAME_OBJECT_ID objectID) { int arrayPos = GetArraySlot(kConsumables, objectID); - if (-1 == arrayPos) + if (arrayPos == NO_VALUE) return std::nullopt; ConsumablePickupInfo info = kConsumables[arrayPos]; return lara.Inventory.*(info.Count); } + +int GetDefaultConsumableCount(GAME_OBJECT_ID objectID) +{ + int arrayPos = GetArraySlot(kConsumables, objectID); + if (arrayPos == NO_VALUE) + return NO_VALUE; + + return kConsumables[arrayPos].Amount; +} diff --git a/TombEngine/Game/pickup/pickup_consumable.h b/TombEngine/Game/pickup/pickup_consumable.h index fcf3acf5b..97bf83434 100644 --- a/TombEngine/Game/pickup/pickup_consumable.h +++ b/TombEngine/Game/pickup/pickup_consumable.h @@ -8,3 +8,4 @@ bool TryAddingConsumable(LaraInfo&, GAME_OBJECT_ID objectID, std::optional bool TryRemovingConsumable(LaraInfo&, GAME_OBJECT_ID objectID, std::optional amount = 0); bool TryModifyingConsumable(LaraInfo& lara, GAME_OBJECT_ID objectID, std::optional amount, ModificationType modType); std::optional GetConsumableCount(LaraInfo&, GAME_OBJECT_ID objectID); +int GetDefaultConsumableCount(GAME_OBJECT_ID objectID); diff --git a/TombEngine/Game/pickup/pickuputil.h b/TombEngine/Game/pickup/pickuputil.h index 9f1217459..58b4063cb 100644 --- a/TombEngine/Game/pickup/pickuputil.h +++ b/TombEngine/Game/pickup/pickuputil.h @@ -19,5 +19,5 @@ template int GetArraySlot(std::array const& arr, GA return i; } - return -1; + return NO_VALUE; } diff --git a/TombEngine/Game/room.cpp b/TombEngine/Game/room.cpp index fb4378614..ddeb7a57b 100644 --- a/TombEngine/Game/room.cpp +++ b/TombEngine/Game/room.cpp @@ -2,6 +2,7 @@ #include "Game/room.h" #include "Game/collision/collide_room.h" +#include "Game/collision/Point.h" #include "Game/control/control.h" #include "Game/control/lot.h" #include "Game/control/volume.h" @@ -13,6 +14,7 @@ using namespace TEN::Math; using namespace TEN::Collision::Floordata; +using namespace TEN::Collision::Point; using namespace TEN::Renderer; using namespace TEN::Utils; @@ -30,8 +32,8 @@ bool ROOM_INFO::Active() const // Since engine swaps whole room memory block but substitutes flippedRoom, // must check both original room number and flippedRoom equality, // as well as NO_VALUE if checking non-flipped rooms. - return (!FlipStats[flipNumber] && flippedRoom != index && flippedRoom != NO_VALUE) || - ( FlipStats[flipNumber] && flippedRoom == index); + return (!FlipStats[flipNumber] && flippedRoom != RoomNumber && flippedRoom != NO_VALUE) || + ( FlipStats[flipNumber] && flippedRoom == RoomNumber); } static void AddRoomFlipItems(const ROOM_INFO& room) @@ -103,11 +105,11 @@ void DoFlipMap(int group) g_Renderer.FlipRooms(roomNumber, room.flippedRoom); // Update active room sectors. - for (auto& sector : room.floor) + for (auto& sector : room.Sectors) sector.RoomNumber = roomNumber; // Update flipped room sectors. - for (auto& sector : flippedRoom.floor) + for (auto& sector : flippedRoom.Sectors) sector.RoomNumber = room.flippedRoom; } } @@ -156,22 +158,22 @@ int IsRoomOutside(int x, int y, int z) int roomNumber = OutsideRoomTable[xTable][zTable][i]; const auto& room = g_Level.Rooms[roomNumber]; - if ((x > (room.x + BLOCK(1)) && x < (room.x + (room.xSize - 1) * BLOCK(1))) && - (y > room.maxceiling && y < room.minfloor) && - (z > (room.z + BLOCK(1)) && z < (room.z + (room.zSize - 1) * BLOCK(1)))) + if ((x > (room.Position.x + BLOCK(1)) && x < (room.Position.x + (room.XSize - 1) * BLOCK(1))) && + (y > room.TopHeight && y < room.BottomHeight) && + (z > (room.Position.z + BLOCK(1)) && z < (room.Position.z + (room.ZSize - 1) * BLOCK(1)))) { - auto pointColl = GetCollision(x, y, z, roomNumber); + auto pointColl = GetPointCollision(Vector3i(x, y, z), roomNumber); - if (pointColl.Position.Floor == NO_HEIGHT || y > pointColl.Position.Floor) + if (pointColl.GetFloorHeight() == NO_HEIGHT || y > pointColl.GetFloorHeight()) return NO_VALUE; - if (y < pointColl.Position.Ceiling) + if (y < pointColl.GetCeilingHeight()) return NO_VALUE; if (TestEnvironmentFlags(ENV_FLAG_WATER, room.flags) || TestEnvironmentFlags(ENV_FLAG_WIND, room.flags)) { - return pointColl.RoomNumber; + return pointColl.GetRoomNumber(); } return NO_VALUE; @@ -181,17 +183,20 @@ int IsRoomOutside(int x, int y, int z) return NO_VALUE; } -// TODO: Can use floordata's GetRoomGridCoord()? -FloorInfo* GetSector(ROOM_INFO* room, int x, int z) +namespace TEN::Collision::Room { - int sectorX = std::clamp(x / BLOCK(1), 0, room->xSize - 1); - int sectorZ = std::clamp(z / BLOCK(1), 0, room->zSize - 1); + // TODO: Can use floordata's GetRoomGridCoord()? + FloorInfo* GetSector(ROOM_INFO* room, int x, int z) + { + int sectorX = std::clamp(x / BLOCK(1), 0, room->XSize - 1); + int sectorZ = std::clamp(z / BLOCK(1), 0, room->ZSize - 1); - int sectorID = sectorZ + (sectorX * room->zSize); - if (sectorID > room->floor.size()) - return nullptr; - - return &room->floor[sectorID]; + int sectorID = sectorZ + (sectorX * room->ZSize); + if (sectorID > room->Sectors.size()) + return nullptr; + + return &room->Sectors[sectorID]; + } } GameBoundingBox& GetBoundsAccurate(const MESH_INFO& mesh, bool getVisibilityBox) @@ -214,9 +219,9 @@ bool IsPointInRoom(const Vector3i& pos, int roomNumber) { const auto& room = g_Level.Rooms[roomNumber]; - if (pos.z >= (room.z + BLOCK(1)) && pos.z <= (room.z + BLOCK(room.zSize - 1)) && - pos.y <= room.minfloor && pos.y > room.maxceiling && - pos.x >= (room.x + BLOCK(1)) && pos.x <= (room.x + BLOCK(room.xSize - 1))) + if (pos.z >= (room.Position.z + BLOCK(1)) && pos.z <= (room.Position.z + BLOCK(room.ZSize - 1)) && + pos.y <= room.BottomHeight && pos.y > room.TopHeight && + pos.x >= (room.Position.x + BLOCK(1)) && pos.x <= (room.Position.x + BLOCK(room.XSize - 1))) { return true; } @@ -229,7 +234,7 @@ int FindRoomNumber(const Vector3i& pos, int startRoomNumber) if (startRoomNumber != NO_VALUE && startRoomNumber < g_Level.Rooms.size()) { const auto& room = g_Level.Rooms[startRoomNumber]; - for (int neighborRoomNumber : room.neighbors) + for (int neighborRoomNumber : room.NeighborRoomNumbers) { const auto& neighborRoom = g_Level.Rooms[neighborRoomNumber]; if (neighborRoomNumber != startRoomNumber && neighborRoom.Active() && @@ -253,18 +258,18 @@ Vector3i GetRoomCenter(int roomNumber) { const auto& room = g_Level.Rooms[roomNumber]; - int halfLength = BLOCK(room.xSize) / 2; - int halfDepth = BLOCK(room.zSize) / 2; - int halfHeight = (room.maxceiling - room.minfloor) / 2; + int halfLength = BLOCK(room.XSize) / 2; + int halfDepth = BLOCK(room.ZSize) / 2; + int halfHeight = (room.TopHeight - room.BottomHeight) / 2; // Calculate and return center. return Vector3i( - room.x + halfLength, - room.minfloor + halfHeight, - room.z + halfDepth); + room.Position.x + halfLength, + room.BottomHeight + halfHeight, + room.Position.z + halfDepth); } -static std::vector GetNeighborRoomNumbers(int roomNumber, unsigned int searchDepth, std::vector& visitedRoomNumbers = std::vector{}) +std::vector GetNeighborRoomNumbers(int roomNumber, unsigned int searchDepth, std::vector& visitedRoomNumbers) { // Invalid room; return empty vector. if (g_Level.Rooms.size() <= roomNumber) @@ -311,7 +316,7 @@ void InitializeNeighborRoomList() for (int roomNumber = 0; roomNumber < g_Level.Rooms.size(); roomNumber++) { auto& room = g_Level.Rooms[roomNumber]; - room.neighbors = GetNeighborRoomNumbers(roomNumber, NEIGHBOR_ROOM_SEARCH_DEPTH); + room.NeighborRoomNumbers = GetNeighborRoomNumbers(roomNumber, NEIGHBOR_ROOM_SEARCH_DEPTH); } // Add flipped variations of itself. @@ -321,11 +326,11 @@ void InitializeNeighborRoomList() if (room.flippedRoom == NO_VALUE) continue; - if (!Contains(room.neighbors, room.flippedRoom)) - room.neighbors.push_back(room.flippedRoom); + if (!Contains(room.NeighborRoomNumbers, room.flippedRoom)) + room.NeighborRoomNumbers.push_back(room.flippedRoom); auto& flippedRoom = g_Level.Rooms[room.flippedRoom]; - if (!Contains(flippedRoom.neighbors, roomNumber)) - flippedRoom.neighbors.push_back(roomNumber); + if (!Contains(flippedRoom.NeighborRoomNumbers, roomNumber)) + flippedRoom.NeighborRoomNumbers.push_back(roomNumber); } } diff --git a/TombEngine/Game/room.h b/TombEngine/Game/room.h index 0c150e722..8bf665efe 100644 --- a/TombEngine/Game/room.h +++ b/TombEngine/Game/room.h @@ -87,31 +87,32 @@ struct MESH_INFO struct ROOM_INFO { - int index; - int x; - int y; - int z; - int minfloor; - int maxceiling; - int xSize; - int zSize; + int RoomNumber = 0; + std::string Name = {}; + std::vector Tags = {}; + + Vector3i Position = Vector3i::Zero; + int BottomHeight = 0; + int TopHeight = 0; + int XSize = 0; + int ZSize = 0; + Vector3 ambient; - int flippedRoom; int flags; int meshEffect; ReverbType reverbType; + int flippedRoom; int flipNumber; short itemNumber; short fxNumber; bool boundActive; - std::string name = {}; - std::vector tags = {}; + std::vector NeighborRoomNumbers = {}; - std::vector floor = {}; + std::vector Sectors = {}; std::vector lights = {}; - std::vector mesh = {}; - std::vector triggerVolumes = {}; + std::vector mesh = {}; // Statics + std::vector TriggerVolumes = {}; std::vector positions = {}; std::vector normals = {}; @@ -120,8 +121,6 @@ struct ROOM_INFO std::vector buckets = {}; std::vector doors = {}; - std::vector neighbors = {}; - bool Active() const; }; @@ -134,4 +133,9 @@ int IsRoomOutside(int x, int y, int z); void InitializeNeighborRoomList(); GameBoundingBox& GetBoundsAccurate(const MESH_INFO& mesh, bool getVisibilityBox); -FloorInfo* GetSector(ROOM_INFO* room, int x, int z); +std::vector GetNeighborRoomNumbers(int roomNumber, unsigned int searchDepth, std::vector& visitedRoomNumbers = std::vector{}); + +namespace TEN::Collision::Room +{ + FloorInfo* GetSector(ROOM_INFO* room, int x, int z); +} diff --git a/TombEngine/Game/savegame.cpp b/TombEngine/Game/savegame.cpp index 98100796f..42a877e0e 100644 --- a/TombEngine/Game/savegame.cpp +++ b/TombEngine/Game/savegame.cpp @@ -15,6 +15,7 @@ #include "Game/items.h" #include "Game/itemdata/creature_info.h" #include "Game/Lara/lara.h" +#include "Game/Lara/lara_initialise.h" #include "Game/misc.h" #include "Game/spotcam.h" #include "Game/room.h" @@ -129,6 +130,11 @@ Save::Vector4 FromVector4(const Vector4& vec) return Save::Vector4(vec.x, vec.y, vec.z, vec.w); } +Save::GameVector FromGameVector(const GameVector& vec) +{ + return Save::GameVector(vec.x, vec.y, vec.z, (int)vec.RoomNumber); +} + Pose ToPose(const Save::Pose& pose) { return Pose( @@ -171,6 +177,11 @@ Vector4 ToVector4(const Save::Vector4* vec) return Vector4(vec->x(), vec->y(), vec->z(), vec->w()); } +GameVector ToGameVector(const Save::GameVector* vec) +{ + return GameVector(vec->x(), vec->y(), vec->z(), (short)vec->room_number()); +} + bool SaveGame::IsSaveGameSlotValid(int slot) { if (slot < 0 || slot > SAVEGAME_MAX_SLOT) @@ -195,6 +206,17 @@ bool SaveGame::DoesSaveGameExist(int slot, bool silent) return true; } +bool SaveGame::IsLoadGamePossible() +{ + for (int i = 0; i < SAVEGAME_MAX; i++) + { + if (Infos[i].Present) + return true; + } + + return false; +} + std::string SaveGame::GetSavegameFilename(int slot) { return (FullSaveDirectory + SAVEGAME_FILE_MASK + std::to_string(slot)); @@ -489,6 +511,17 @@ const std::vector SaveGame::Build() status.add_stamina(Lara.Status.Stamina); auto statusOffset = status.Finish(); + Save::CollisionInfoDataBuilder collision{ fbb }; + collision.add_last_bridge_item_number(LaraCollision.LastBridgeItemNumber); + collision.add_last_bridge_item_pose(&FromPose(LaraCollision.LastBridgeItemPose)); + auto collisionOffset = collision.Finish(); + + Save::CameraBuilder camera{ fbb }; + camera.add_position(&FromGameVector(Camera.pos)); + camera.add_target(&FromGameVector(Camera.target)); + auto cameraOffset = camera.Finish(); + + std::vector> carriedWeapons; for (int i = 0; i < (int)LaraWeaponType::NumWeapons; i++) { @@ -535,6 +568,7 @@ const std::vector SaveGame::Build() lara.add_location_pad(Lara.LocationPad); lara.add_right_arm(rightArmOffset); lara.add_status(statusOffset); + lara.add_collision(collisionOffset); lara.add_target_arm_orient(&FromEulerAngles(Lara.TargetArmOrient)); lara.add_target_entity_number(Lara.TargetEntity == nullptr ? -1 : Lara.TargetEntity->Index); lara.add_torch(torchOffset); @@ -544,11 +578,11 @@ const std::vector SaveGame::Build() std::vector> rooms; for (auto& room : g_Level.Rooms) { - auto nameOffset = fbb.CreateString(room.name); + auto nameOffset = fbb.CreateString(room.Name); Save::RoomBuilder serializedInfo{ fbb }; serializedInfo.add_name(nameOffset); - serializedInfo.add_index(room.index); + serializedInfo.add_index(room.RoomNumber); serializedInfo.add_reverb_type((int)room.reverbType); serializedInfo.add_flags(room.flags); auto serializedInfoOffset = serializedInfo.Finish(); @@ -991,14 +1025,14 @@ const std::vector SaveGame::Build() staticMesh.add_flags(room->mesh[j].flags); staticMesh.add_hit_points(room->mesh[j].HitPoints); - staticMesh.add_room_number(room->index); + staticMesh.add_room_number(room->RoomNumber); staticMesh.add_number(j); staticMeshes.push_back(staticMesh.Finish()); } - for (int j = 0; j < room->triggerVolumes.size(); j++) + for (int j = 0; j < room->TriggerVolumes.size(); j++) { - auto& currVolume = room->triggerVolumes[j]; + auto& currVolume = room->TriggerVolumes[j]; std::vector> queue; for (int k = 0; k < currVolume.StateQueue.size(); k++) @@ -1022,7 +1056,7 @@ const std::vector SaveGame::Build() auto nameOffset = fbb.CreateString(currVolume.Name); Save::VolumeBuilder volume{ fbb }; - volume.add_room_number(room->index); + volume.add_room_number(room->RoomNumber); volume.add_number(j); volume.add_name(nameOffset); volume.add_enabled(currVolume.Enabled); @@ -1203,38 +1237,24 @@ const std::vector SaveGame::Build() { ROPE_STRUCT* rope = &Ropes[Lara.Control.Rope.Ptr]; - std::vector segments; + std::vector> segments; for (int i = 0; i < ROPE_SEGMENTS; i++) - segments.push_back(&FromVector3i(rope->segment[i])); - auto segmentsOffset = fbb.CreateVector(segments); + { + Save::RopeSegmentBuilder segment{ fbb }; - std::vector velocities; - for (int i = 0; i < ROPE_SEGMENTS; i++) - velocities.push_back(&FromVector3i(rope->velocity[i])); - auto velocitiesOffset = fbb.CreateVector(velocities); + segment.add_segment(&FromVector3i(rope->segment[i])); + segment.add_velocity(&FromVector3i(rope->velocity[i])); + segment.add_normalised_segment(&FromVector3i(rope->normalisedSegment[i])); + segment.add_mesh_segment(&FromVector3i(rope->meshSegment[i])); + segment.add_coord(&FromVector3i(rope->coords[i])); - std::vector normalisedSegments; - for (int i = 0; i < ROPE_SEGMENTS; i++) - normalisedSegments.push_back(&FromVector3i(rope->normalisedSegment[i])); - auto normalisedSegmentsOffset = fbb.CreateVector(normalisedSegments); - - std::vector meshSegments; - for (int i = 0; i < ROPE_SEGMENTS; i++) - meshSegments.push_back(&FromVector3i(rope->meshSegment[i])); - auto meshSegmentsOffset = fbb.CreateVector(meshSegments); - - std::vector coords; - for (int i = 0; i < ROPE_SEGMENTS; i++) - coords.push_back(&FromVector3i(rope->coords[i])); - auto coordsOffset = fbb.CreateVector(coords); + segments.push_back(segment.Finish()); + } + auto ropeSegmentsOffset = fbb.CreateVector(segments); Save::RopeBuilder ropeInfo{ fbb }; - ropeInfo.add_segments(segmentsOffset); - ropeInfo.add_velocities(velocitiesOffset); - ropeInfo.add_mesh_segments(meshSegmentsOffset); - ropeInfo.add_normalised_segments(normalisedSegmentsOffset); - ropeInfo.add_coords(coordsOffset); + ropeInfo.add_segments(ropeSegmentsOffset); ropeInfo.add_coiled(rope->coiled); ropeInfo.add_position(&FromVector3i(rope->position)); ropeInfo.add_segment_length(rope->segmentLength); @@ -1403,6 +1423,7 @@ const std::vector SaveGame::Build() sgb.add_header(headerOffset); sgb.add_level(levelStatisticsOffset); sgb.add_game(gameStatisticsOffset); + sgb.add_camera(cameraOffset); sgb.add_lara(laraOffset); sgb.add_rooms(roomOffset); sgb.add_next_item_free(NextItemFree); @@ -1492,6 +1513,9 @@ void SaveGame::LoadHub(int index) // Load hub data. TENLog("Loading hub data for level #" + std::to_string(index), LogLevel::Info); Parse(Hub[index], true); + + // Restore vehicle. + InitializePlayerVehicle(*LaraItem); } bool SaveGame::IsOnHub(int index) @@ -1957,13 +1981,14 @@ static void ParsePlayer(const Save::SaveGame* s) { auto* rope = &Ropes[Lara.Control.Rope.Ptr]; - for (int i = 0; i < ROPE_SEGMENTS; i++) + for (int i = 0; i < s->rope()->segments()->size(); i++) { - rope->segment[i] = ToVector3i(s->rope()->segments()->Get(i)); - rope->normalisedSegment[i] = ToVector3i(s->rope()->normalised_segments()->Get(i)); - rope->meshSegment[i] = ToVector3i(s->rope()->mesh_segments()->Get(i)); - rope->coords[i] = ToVector3i(s->rope()->coords()->Get(i)); - rope->velocity[i] = ToVector3i(s->rope()->velocities()->Get(i)); + auto ropeSegment = s->rope()->segments()->Get(i); + rope->segment[i] = ToVector3i(ropeSegment->segment()); + rope->normalisedSegment[i] = ToVector3i(ropeSegment->normalised_segment()); + rope->meshSegment[i] = ToVector3i(ropeSegment->mesh_segment()); + rope->coords[i] = ToVector3i(ropeSegment->coord()); + rope->velocity[i] = ToVector3i(ropeSegment->velocity()); } rope->coiled = s->rope()->coiled(); @@ -1983,6 +2008,14 @@ static void ParsePlayer(const Save::SaveGame* s) AlternatePendulum.rope = rope; } + // Collision + LaraCollision.LastBridgeItemNumber = s->lara()->collision()->last_bridge_item_number(); + LaraCollision.LastBridgeItemPose = ToPose(*s->lara()->collision()->last_bridge_item_pose()); + + // Camera + Camera.pos = ToGameVector(s->camera()->position()); + Camera.target = ToGameVector(s->camera()->target()); + for (auto& item : g_Level.Items) { if (item.ObjectNumber != ID_LARA || item.Index >= g_Level.NumItems) @@ -2010,7 +2043,7 @@ static void ParseEffects(const Save::SaveGame* s) // Restore soundtracks. for (int i = 0; i < s->soundtracks()->size(); i++) { - assertion(i < (int)SoundTrackType::Count, "Soundtrack type count was changed"); + TENAssert(i < (int)SoundTrackType::Count, "Soundtrack type count was changed"); auto track = s->soundtracks()->Get(i); PlaySoundTrack(track->name()->str(), (SoundTrackType)i, track->position()); @@ -2155,7 +2188,7 @@ static void ParseLevel(const Save::SaveGame* s, bool hubMode) for (int i = 0; i < s->rooms()->size(); i++) { auto room = s->rooms()->Get(i); - g_Level.Rooms[room->index()].name = room->name()->str(); + g_Level.Rooms[room->index()].Name = room->name()->str(); g_Level.Rooms[room->index()].flags = room->flags(); g_Level.Rooms[room->index()].reverbType = (ReverbType)room->reverb_type(); } @@ -2191,18 +2224,18 @@ static void ParseLevel(const Save::SaveGame* s, bool hubMode) auto room = &g_Level.Rooms[volume->room_number()]; int number = volume->number(); - room->triggerVolumes[number].Enabled = volume->enabled(); - room->triggerVolumes[number].Name = volume->name()->str(); - room->triggerVolumes[number].Box.Center = - room->triggerVolumes[number].Sphere.Center = ToVector3(volume->position()); - room->triggerVolumes[number].Box.Orientation = ToVector4(volume->rotation()); - room->triggerVolumes[number].Box.Extents = ToVector3(volume->scale()); - room->triggerVolumes[number].Sphere.Radius = room->triggerVolumes[number].Box.Extents.x; + room->TriggerVolumes[number].Enabled = volume->enabled(); + room->TriggerVolumes[number].Name = volume->name()->str(); + room->TriggerVolumes[number].Box.Center = + room->TriggerVolumes[number].Sphere.Center = ToVector3(volume->position()); + room->TriggerVolumes[number].Box.Orientation = ToVector4(volume->rotation()); + room->TriggerVolumes[number].Box.Extents = ToVector3(volume->scale()); + room->TriggerVolumes[number].Sphere.Radius = room->TriggerVolumes[number].Box.Extents.x; for (int j = 0; j < volume->queue()->size(); j++) { auto state = volume->queue()->Get(j); - room->triggerVolumes[number].StateQueue.push_back( + room->TriggerVolumes[number].StateQueue.push_back( VolumeState { (VolumeStateStatus)state->status(), @@ -2228,7 +2261,7 @@ static void ParseLevel(const Save::SaveGame* s, bool hubMode) // Restore action queue. for (int i = 0; i < s->action_queue()->size(); i++) { - assertion(i < ActionQueue.size(), "Action queue size was changed"); + TENAssert(i < ActionQueue.size(), "Action queue size was changed"); ActionQueue[i] = (QueueState)s->action_queue()->Get(i); } @@ -2299,10 +2332,21 @@ static void ParseLevel(const Save::SaveGame* s, bool hubMode) // Don't load player data in hub mode. if (item->ObjectNumber == ID_LARA && hubMode) + { + item->RoomNumber = savedItem->room_number(); + item->Floor = savedItem->floor(); + item->BoxNumber = savedItem->box_number(); continue; + } if (item->Index == Lara.Context.Vehicle && hubMode) + { + //item->Pose = ToPose(*savedItem->pose()); + item->RoomNumber = savedItem->room_number(); + item->Floor = savedItem->floor(); + item->BoxNumber = savedItem->box_number(); continue; + } // Position item->Pose = ToPose(*savedItem->pose()); diff --git a/TombEngine/Game/savegame.h b/TombEngine/Game/savegame.h index 0d31db314..61a8d922e 100644 --- a/TombEngine/Game/savegame.h +++ b/TombEngine/Game/savegame.h @@ -64,6 +64,7 @@ public: static void Delete(int slot); static bool DoesSaveGameExist(int slot, bool silent = false); + static bool IsLoadGamePossible(); static void SaveHub(int index); static void LoadHub(int index); diff --git a/TombEngine/Game/spotcam.cpp b/TombEngine/Game/spotcam.cpp index 3b006c2d0..bc8a91065 100644 --- a/TombEngine/Game/spotcam.cpp +++ b/TombEngine/Game/spotcam.cpp @@ -5,6 +5,7 @@ #include "Game/camera.h" #include "Game/control/control.h" #include "Game/control/volume.h" +#include "Game/collision/Point.h" #include "Game/effects/tomb4fx.h" #include "Game/items.h" #include "Game/Lara/lara.h" @@ -14,6 +15,7 @@ using namespace TEN::Input; using namespace TEN::Renderer; using namespace TEN::Control::Volumes; +using namespace TEN::Collision::Point; constexpr auto MAX_CAMERA = 18; @@ -235,6 +237,8 @@ void InitializeSpotCam(short Sequence) CameraFOV[1] = SpotCam[CurrentSplineCamera].fov; CameraSpeed[1] = SpotCam[CurrentSplineCamera].speed; + Camera.DisableInterpolation = true; + SplineFromCamera = 0; int cn = CurrentSplineCamera; @@ -466,6 +470,14 @@ void CalculateSpotCameras() if ((s->flags & SCF_DISABLE_BREAKOUT) || !lookPressed) { + // Disable interpolation if camera traveled too far. + auto p1 = Vector3(Camera.pos.x, Camera.pos.y, Camera.pos.z); + auto p2 = Vector3(cpx, cpy, cpz); + auto dist = Vector3::Distance(p1, p2); + + if (dist > CLICK(1)) + Camera.DisableInterpolation = true; + Camera.pos.x = cpx; Camera.pos.y = cpy; Camera.pos.z = cpz; @@ -486,11 +498,22 @@ void CalculateSpotCameras() auto outsideRoom = IsRoomOutside(cpx, cpy, cpz); if (outsideRoom == NO_VALUE) { - Camera.pos.RoomNumber = SpotCam[CurrentSplineCamera].roomNumber; - GetFloor(Camera.pos.x, Camera.pos.y, Camera.pos.z, &Camera.pos.RoomNumber); + // HACK: Sometimes actual camera room number desyncs from room number derived using floordata functions. + // If such case is identified, we do a brute-force search for coherrent room number. + // This issue is only present in sub-click floor height setups after TE 1.7.0. -- Lwmte, 02.11.2024 + + auto pos = Vector3i(Camera.pos.x, Camera.pos.y, Camera.pos.z); + int collRoomNumber = GetPointCollision(pos, SpotCam[CurrentSplineCamera].roomNumber).GetRoomNumber(); + + if (collRoomNumber != Camera.pos.RoomNumber) + collRoomNumber = FindRoomNumber(pos, SpotCam[CurrentSplineCamera].roomNumber); + + Camera.pos.RoomNumber = collRoomNumber; } else + { Camera.pos.RoomNumber = outsideRoom; + } AlterFOV(cfov, false); @@ -573,6 +596,8 @@ void CalculateSpotCameras() { cn = FirstCamera + SpotCam[CurrentSplineCamera].timer; + Camera.DisableInterpolation = true; + CameraXposition[1] = SpotCam[cn].x; CameraYposition[1] = SpotCam[cn].y; CameraZposition[1] = SpotCam[cn].z; @@ -633,7 +658,10 @@ void CalculateSpotCameras() SpotcamPaused = 0; if (LastCamera >= CurrentSplineCamera) + { + Camera.DisableInterpolation = true; return; + } if (s->flags & SCF_LOOP_SEQUENCE) { @@ -664,6 +692,7 @@ void CalculateSpotCameras() SetCinematicBars(0.0f, SPOTCAM_CINEMATIC_BARS_SPEED); + Camera.DisableInterpolation = true; UseSpotCam = false; Lara.Control.IsLocked = false; CheckTrigger = false; @@ -676,10 +705,10 @@ void CalculateSpotCameras() Camera.pos.x = InitialCameraPosition.x; Camera.pos.y = InitialCameraPosition.y; Camera.pos.z = InitialCameraPosition.z; + Camera.pos.RoomNumber = InitialCameraRoom; Camera.target.x = InitialCameraTarget.x; Camera.target.y = InitialCameraTarget.y; Camera.target.z = InitialCameraTarget.z; - Camera.pos.RoomNumber = InitialCameraRoom; } SpotcamOverlay = false; diff --git a/TombEngine/Math/Constants.h b/TombEngine/Math/Constants.h index 2e2a1e26d..a43ba00a8 100644 --- a/TombEngine/Math/Constants.h +++ b/TombEngine/Math/Constants.h @@ -4,6 +4,7 @@ //namespace TEN::Math //{ // Math constants + constexpr auto PI = 3.14159265358979323846264338327950288419716939937510f; constexpr auto PI_MUL_2 = PI * 2; constexpr auto PI_DIV_2 = PI / 2; @@ -15,7 +16,14 @@ constexpr auto SQUARE = [](auto x) { return (x * x); }; constexpr auto CUBE = [](auto x) { return (x * x * x); }; + // Geometry constants + + constexpr auto BOX_VERTEX_COUNT = 8; + constexpr auto BOX_EDGE_COUNT = 12; + constexpr auto BOX_FACE_COUNT = 6; + // World constants + constexpr auto BLOCK_UNIT = 1024; constexpr auto NO_HEIGHT = INT_MIN + UCHAR_MAX; constexpr auto MAX_HEIGHT = INT_MIN + 1; // NOTE: +1 prevents issues with sign change. diff --git a/TombEngine/Math/Geometry.h b/TombEngine/Math/Geometry.h index 25a00ed9a..f47482a01 100644 --- a/TombEngine/Math/Geometry.h +++ b/TombEngine/Math/Geometry.h @@ -8,6 +8,7 @@ class Vector3i; namespace TEN::Math::Geometry { // Integer-based point translation + Vector3i TranslatePoint(const Vector3i& point, short headingAngle, float forward, float down = 0.0f, float right = 0.0f, const Vector3& axis = Vector3::UnitY); Vector3i TranslatePoint(const Vector3i& point, short headingAngle, const Vector3i& relOffset, const Vector3& axis = Vector3::UnitY); Vector3i TranslatePoint(const Vector3i& point, const EulerAngles& orient, const Vector3i& relOffset); @@ -16,6 +17,7 @@ namespace TEN::Math::Geometry Vector3i TranslatePoint(const Vector3i& point, const Vector3& dir, float dist); // Float-based point translation + Vector3 TranslatePoint(const Vector3& point, short headingAngle, float forward, float down = 0.0f, float right = 0.0f, const Vector3& axis = Vector3::UnitY); Vector3 TranslatePoint(const Vector3& point, short headingAngle, const Vector3& relOffset, const Vector3& axis = Vector3::UnitY); Vector3 TranslatePoint(const Vector3& point, const EulerAngles& orient, const Vector3& relOffset); @@ -24,15 +26,18 @@ namespace TEN::Math::Geometry Vector3 TranslatePoint(const Vector3& point, const Vector3& dir, float dist); // Rotation + Vector3 RotatePoint(const Vector3& point, const EulerAngles& rot); Vector3 RotatePoint(const Vector3& point, const AxisAngle& rot); // Angle getters + short GetShortestAngle(short fromAngle, short toAngle); short GetSurfaceSlopeAngle(const Vector3& normal, const Vector3& axis = Vector3::UnitY); short GetSurfaceAspectAngle(const Vector3& normal, const Vector3& axis = Vector3::UnitY); // Misc. getters + float GetDistanceToLine(const Vector3& origin, const Vector3& linePoint0, const Vector3& linePoint1); Vector3 GetClosestPointOnLine(const Vector3& origin, const Vector3& linePoint0, const Vector3& linePoint1); Vector3 GetClosestPointOnLinePerp(const Vector3& origin, const Vector3& linePoint0, const Vector3& linePoint1, const Vector3& axis = Vector3::UnitY); @@ -41,10 +46,12 @@ namespace TEN::Math::Geometry BoundingBox GetBoundingBox(const std::vector& points); // Converters + Quaternion ConvertDirectionToQuat(const Vector3& dir); Vector3 ConvertQuatToDirection(const Quaternion& quat); // Point relation inquirers + bool IsPointInFront(const Pose& pose, const Vector3& target); bool IsPointInFront(const Vector3& origin, const Vector3& target, const EulerAngles& orient); bool IsPointInFront(const Vector3& origin, const Vector3& target, const Vector3& refPoint); @@ -56,5 +63,6 @@ namespace TEN::Math::Geometry bool IsPointInSphere(const Vector3& point, const BoundingSphere& sphere); // Intersection inquirers + bool CircleIntersects(const Vector3& circle0, const Vector3& circle1); } diff --git a/TombEngine/Math/Interpolation.cpp b/TombEngine/Math/Interpolation.cpp deleted file mode 100644 index 12e8cfb4f..000000000 --- a/TombEngine/Math/Interpolation.cpp +++ /dev/null @@ -1,58 +0,0 @@ -#include "framework.h" -#include "Math/Interpolation.h" - -#include "Math/Constants.h" - -namespace TEN::Math -{ - float Lerp(float value0, float value1, float alpha) - { - alpha = std::clamp(alpha, 0.0f, 1.0f); - return (((1.0f - alpha) * value0) + (alpha * value1)); - } - - float InterpolateCos(float value0, float value1, float alpha) - { - alpha = std::clamp(alpha, 0.0f, 1.0f); - return Lerp(value0, value1, (1 - cos(alpha * PI)) * 0.5f); - } - - float InterpolateCubic(float value0, float value1, float value2, float value3, float alpha) - { - alpha = std::clamp(alpha, 0.0f, 1.0f); - - float p = (value3 - value2) - (value0 - value1); - float q = (value0 - value1) - p; - float r = value2 - value0; - float s = value1; - float x = alpha; - float xSquared = SQUARE(x); - return ((p * xSquared * x) + (q * xSquared) + (r * x) + s); - } - - float Smoothstep(float alpha) - { - return Smoothstep(0.0f, 1.0f, alpha); - } - - float Smoothstep(float value0, float value1, float alpha) - { - alpha = std::clamp(alpha, value0, value1); - - // Don't process if input value is same as one of the values. - if (alpha == value0) - { - return value0; - } - else if (alpha == value1) - { - return value1; - } - - // Scale, bias, and saturate alpha to [0, 1] range. - alpha = std::clamp((alpha - value0) / (value1 - value0), 0.0f, 1.0f); - - // Evaluate polynomial. - return (CUBE(alpha) * (alpha * (alpha * 6 - 15) + 10)); - } -} diff --git a/TombEngine/Math/Interpolation.h b/TombEngine/Math/Interpolation.h deleted file mode 100644 index 83bb060e2..000000000 --- a/TombEngine/Math/Interpolation.h +++ /dev/null @@ -1,10 +0,0 @@ -#pragma once - -namespace TEN::Math -{ - float Lerp(float value0, float value1, float alpha); - float InterpolateCos(float value0, float value1, float alpha); - float InterpolateCubic(float value0, float value1, float value2, float value3, float alpha); - float Smoothstep(float alpha); - float Smoothstep(float value0, float value1, float alpha); -} diff --git a/TombEngine/Math/Legacy.h b/TombEngine/Math/Legacy.h index 333968f9e..ad8a857fd 100644 --- a/TombEngine/Math/Legacy.h +++ b/TombEngine/Math/Legacy.h @@ -1,4 +1,5 @@ #pragma once + #include "Math/Constants.h" constexpr auto FP_SHIFT = 16; @@ -8,14 +9,19 @@ constexpr auto PREDICTIVE_SCALE_FACTOR = 14; constexpr auto SHORTS_TO_1_DEGREE = 65536.0f / 360.0f; constexpr auto DEGREES_TO_1_SHORT = 360.0f / 65536.0f; +constexpr float ROUND(float value) +{ + return ((value > 0.0f) ? int(value + 0.5f) : int(value - 0.5f)); +} + constexpr short ANGLE(float degrees) { - return short(degrees * SHORTS_TO_1_DEGREE); + return (short)ROUND(degrees * SHORTS_TO_1_DEGREE); } constexpr short FROM_RAD(float radians) { - return short((radians / RADIAN) * SHORTS_TO_1_DEGREE); + return (short)ROUND((radians / RADIAN) * SHORTS_TO_1_DEGREE); } constexpr float TO_DEGREES(short shortAngle) diff --git a/TombEngine/Math/Math.cpp b/TombEngine/Math/Math.cpp deleted file mode 100644 index 3ede4ffd8..000000000 --- a/TombEngine/Math/Math.cpp +++ /dev/null @@ -1,35 +0,0 @@ -#include "framework.h" - -#include -#include "Math/Math.h" - -namespace TEN::Math -{ - float Luma(const Vector3& color) - { - constexpr auto RED_COEFF = 0.2126f; - constexpr auto GREEN_COEFF = 0.7152f; - constexpr auto BLUE_COEFF = 0.0722f; - - // Use Rec.709 trichromat formula to get perceptive luma value. - return float((color.x * RED_COEFF) + (color.y * GREEN_COEFF) + (color.z * BLUE_COEFF)); - } - - Vector3 Screen(const Vector3& ambient, const Vector3& tint) - { - float luma = Luma(tint); - auto multiplicative = ambient * tint; - auto additive = ambient + tint; - - return Vector3( - Lerp(multiplicative.x, additive.x, luma), - Lerp(multiplicative.y, additive.y, luma), - Lerp(multiplicative.z, additive.z, luma)); - } - - Vector4 Screen(const Vector4& ambient, const Vector4& tint) - { - auto result = Screen(Vector3(ambient), Vector3(tint)); - return Vector4(result.x, result.y, result.z, ambient.w * tint.w); - } -} diff --git a/TombEngine/Math/Math.h b/TombEngine/Math/Math.h index e95d96fe2..cb625c648 100644 --- a/TombEngine/Math/Math.h +++ b/TombEngine/Math/Math.h @@ -1,7 +1,6 @@ #pragma once #include "Math/Constants.h" #include "Math/Geometry.h" -#include "Math/Interpolation.h" #include "Math/Legacy.h" #include "Math/Objects/EulerAngles.h" #include "Math/Objects/GameBoundingBox.h" @@ -11,13 +10,4 @@ #include "Math/Objects/Vector3i.h" #include "Math/Random.h" #include "Math/Solvers.h" - -namespace TEN::Math -{ - constexpr inline auto OFFSET_RADIUS = [](auto x) { return ((x * SQRT_2) + 4); }; - constexpr inline auto MESH_BITS = [](auto x) { return (1 << x); }; - - float Luma(const Vector3& color); - Vector3 Screen(const Vector3& ambient, const Vector3& tint); - Vector4 Screen(const Vector4& ambient, const Vector4& tint); -} +#include "Math/Utils.h" diff --git a/TombEngine/Math/Objects/AxisAngle.h b/TombEngine/Math/Objects/AxisAngle.h index 7ccc2cd90..ddcc72e31 100644 --- a/TombEngine/Math/Objects/AxisAngle.h +++ b/TombEngine/Math/Objects/AxisAngle.h @@ -8,14 +8,17 @@ class EulerAngles; { private: // Members + Vector3 _axis = Vector3::Backward; short _angle = 0; public: // Constants + static const AxisAngle Identity; // Constructors + AxisAngle() {}; AxisAngle(const Vector3& axis, short angle); AxisAngle(const EulerAngles& eulers); @@ -23,24 +26,29 @@ class EulerAngles; AxisAngle(const Matrix& rotMatrix); // Getters + Vector3 GetAxis() const; short GetAngle() const; // Setters + void SetAxis(const Vector3& axis); void SetAngle(short angle); // Utilities + void Slerp(const AxisAngle& axisAngleTo, float alpha); static AxisAngle Slerp(const AxisAngle& axisAngleFrom, const AxisAngle& axisAngleTo, float alpha); // Converters + Vector3 ToDirection() const; EulerAngles ToEulerAngles() const; Quaternion ToQuaternion() const; Matrix ToRotationMatrix() const; // Operators + bool operator ==(const AxisAngle& axisAngle) const; bool operator !=(const AxisAngle& axisAngle) const; AxisAngle& operator =(const AxisAngle& axisAngle); diff --git a/TombEngine/Math/Objects/EulerAngles.h b/TombEngine/Math/Objects/EulerAngles.h index e08973cfa..f25f69d1a 100644 --- a/TombEngine/Math/Objects/EulerAngles.h +++ b/TombEngine/Math/Objects/EulerAngles.h @@ -7,14 +7,17 @@ { public: // Members (CONVENTION: X = Pitch, Y = Yaw, Z = Roll) + short x = 0; short y = 0; short z = 0; // Constants + static const EulerAngles Identity; // Constructors + constexpr EulerAngles() {}; constexpr EulerAngles(short x, short y, short z) { this->x = x; this->y = y; this->z = z; }; EulerAngles(const Vector3& dir); @@ -23,6 +26,7 @@ EulerAngles(const Matrix& rotMatrix); // Utilities + static bool Compare(const EulerAngles& eulers0, const EulerAngles& eulers1, short epsilon = 3); void Lerp(const EulerAngles& eulersTo, float alpha, short epsilon = 3); static EulerAngles Lerp(const EulerAngles& eulersFrom, const EulerAngles& eulersTo, float alpha, short epsilon = 3); @@ -32,12 +36,14 @@ static EulerAngles InterpolateConstant(const EulerAngles& eulersFrom, const EulerAngles& eulerTo, short angularVel); // Converters + Vector3 ToDirection() const; AxisAngle ToAxisAngle() const; Quaternion ToQuaternion() const; Matrix ToRotationMatrix() const; // Operators + bool operator ==(const EulerAngles& eulers) const; bool operator !=(const EulerAngles& eulers) const; EulerAngles& operator =(const EulerAngles& eulers); @@ -54,6 +60,7 @@ private: // Temporary. Will be integrated into eventual Angle class. + static float ClampAlpha(float alpha); static bool Compare(short angle0, short angle1, short epsilon = 3); static short Lerp(short angleFrom, short angleTo, float alpha, short epsilon = 3); diff --git a/TombEngine/Math/Objects/GameBoundingBox.h b/TombEngine/Math/Objects/GameBoundingBox.h index 705688145..17569391d 100644 --- a/TombEngine/Math/Objects/GameBoundingBox.h +++ b/TombEngine/Math/Objects/GameBoundingBox.h @@ -12,6 +12,7 @@ struct ObjectInfo; { public: // Members + int X1 = 0; int X2 = 0; int Y1 = 0; @@ -20,15 +21,18 @@ struct ObjectInfo; int Z2 = 0; // Constants + static const GameBoundingBox Zero; // Constructors + GameBoundingBox() {}; GameBoundingBox(float x1, float x2, float y1, float y2, float z1, float z2); GameBoundingBox(GAME_OBJECT_ID objectID, int animNumber = 0, int frameNumber = 0); GameBoundingBox(const ItemInfo* item); // Getters + int GetWidth() const; int GetHeight() const; int GetDepth() const; @@ -36,13 +40,16 @@ struct ObjectInfo; Vector3 GetExtents() const; // Utilities + void Rotate(const EulerAngles& rot); // Converters + BoundingOrientedBox ToBoundingOrientedBox(const Pose& pose) const; BoundingOrientedBox ToBoundingOrientedBox(const Vector3& pos, const Quaternion& orient) const; // Operators + GameBoundingBox operator +(const GameBoundingBox& bounds) const; GameBoundingBox operator +(const Pose& pose) const; GameBoundingBox operator -(const GameBoundingBox& bounds) const; diff --git a/TombEngine/Math/Objects/GameVector.h b/TombEngine/Math/Objects/GameVector.h index f75cc748c..ac43cdd2e 100644 --- a/TombEngine/Math/Objects/GameVector.h +++ b/TombEngine/Math/Objects/GameVector.h @@ -8,6 +8,7 @@ class Vector3i; { public: // Members + int x = 0; int y = 0; int z = 0; @@ -15,9 +16,11 @@ class Vector3i; int BoxNumber = 0; // Unused. // Constants + static const GameVector Zero; // Constructors + GameVector(); GameVector(const Vector3i& pos); GameVector(const Vector3i& pos, short roomNumber); @@ -25,10 +28,12 @@ class Vector3i; GameVector(int xPos, int yPos, int zPos, short roomNumber); // Converters + Vector3 ToVector3() const; Vector3i ToVector3i() const; // Operators + bool operator ==(const GameVector& vector) const; bool operator !=(const GameVector& vector) const; GameVector& operator =(const GameVector& vector); diff --git a/TombEngine/Math/Objects/Pose.h b/TombEngine/Math/Objects/Pose.h index 1a4342262..e60a7bc73 100644 --- a/TombEngine/Math/Objects/Pose.h +++ b/TombEngine/Math/Objects/Pose.h @@ -8,13 +8,16 @@ { public: // Members + Vector3i Position = Vector3i::Zero; EulerAngles Orientation = EulerAngles::Identity; // Constants + static const Pose Zero; // Constructors + Pose(); Pose(const Vector3i& pos); Pose(int xPos, int yPos, int zPos); @@ -26,11 +29,13 @@ Pose(int xPos, int yPos, int zPos, short xOrient, short yOrient, short zOrient); // Utilities + void Translate(short headingAngle, float forward, float down = 0.0f, float right = 0.0f); void Translate(const EulerAngles& orient, float dist); void Translate(const Vector3& dir, float dist); // Operators + bool operator ==(const Pose& pose) const; bool operator !=(const Pose& pose) const; }; diff --git a/TombEngine/Math/Objects/Vector2i.h b/TombEngine/Math/Objects/Vector2i.h index bf0e7d44b..38fef4d0e 100644 --- a/TombEngine/Math/Objects/Vector2i.h +++ b/TombEngine/Math/Objects/Vector2i.h @@ -6,25 +6,31 @@ namespace TEN::Math { public: // Members + int x = 0; int y = 0; // Constants + static const Vector2i Zero; // Constructors + constexpr Vector2i() {}; constexpr Vector2i(int x, int y) { this->x = x; this->y = y; }; Vector2i(const Vector2& vector); // Utilities + static float Distance(const Vector2i& origin, const Vector2i& target); static float DistanceSquared(const Vector2i& origin, const Vector2i& target); // Converters + Vector2 ToVector2() const; // Operators + bool operator ==(const Vector2i& vector) const; bool operator !=(const Vector2i& vector) const; Vector2i& operator =(const Vector2i& vector); diff --git a/TombEngine/Math/Objects/Vector3i.h b/TombEngine/Math/Objects/Vector3i.h index 9a8c75bdf..efa9ec3bb 100644 --- a/TombEngine/Math/Objects/Vector3i.h +++ b/TombEngine/Math/Objects/Vector3i.h @@ -6,28 +6,34 @@ { public: // Members + int x = 0; int y = 0; int z = 0; // Constants + static const Vector3i Zero; // Constructors + constexpr Vector3i() {}; constexpr Vector3i(int x, int y, int z) { this->x = x; this->y = y; this->z = z; }; Vector3i(const Vector3& vector); // Utilities + static float Distance(const Vector3i& origin, const Vector3i& target); static float DistanceSquared(const Vector3i& origin, const Vector3i& target); void Lerp(const Vector3i& target, float alpha); static Vector3i Lerp(const Vector3i& origin, const Vector3i& target, float alpha); // Converters + Vector3 ToVector3() const; // Operators + bool operator ==(const Vector3i& vector) const; bool operator !=(const Vector3i& vector) const; Vector3i& operator =(const Vector3i& vector); diff --git a/TombEngine/Math/Random.h b/TombEngine/Math/Random.h index fd7508d35..97b783993 100644 --- a/TombEngine/Math/Random.h +++ b/TombEngine/Math/Random.h @@ -5,16 +5,19 @@ class EulerAngles; namespace TEN::Math::Random { // Value generation + int GenerateInt(int low = 0, int high = SHRT_MAX); float GenerateFloat(float low = 0.0f, float high = 1.0f); short GenerateAngle(short low = SHRT_MIN, short high = SHRT_MAX); // 2D geometric generation + Vector2 GenerateDirection2D(); Vector2 GeneratePoint2DInSquare(const Vector2& pos, short orient, float apothem); Vector2 GeneratePoint2DInCircle(const Vector2& pos, float radius); // 3D geometric generation + Vector3 GenerateDirection(); Vector3 GenerateDirectionInCone(const Vector3& dir, float semiangleInDeg); Vector3 GeneratePointInBox(const BoundingOrientedBox& box); @@ -22,5 +25,7 @@ namespace TEN::Math::Random Vector3 GeneratePointOnSphere(const BoundingSphere& sphere); Vector3 GeneratePointInSpheroid(const Vector3& center, const EulerAngles& orient, const Vector3& semiMajorAxis); + // Probability + bool TestProbability(float prob); } diff --git a/TombEngine/Math/Solvers.cpp b/TombEngine/Math/Solvers.cpp index 017b70d15..0b6b3da43 100644 --- a/TombEngine/Math/Solvers.cpp +++ b/TombEngine/Math/Solvers.cpp @@ -56,7 +56,7 @@ namespace TEN::Math::Solvers 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."); + TENAssert(abs(a) >= EPSILON, "SolveIK2D() failed."); float m = ((SQUARE(length0) - SQUARE(length1)) + (SQUARE(a) + SQUARE(b))) / (2.0f * a); float n = b / a; diff --git a/TombEngine/Math/Utils.cpp b/TombEngine/Math/Utils.cpp new file mode 100644 index 000000000..4387dc1af --- /dev/null +++ b/TombEngine/Math/Utils.cpp @@ -0,0 +1,111 @@ +#include "framework.h" +#include "Math/Utils.h" + +#include "Math/Constants.h" + +namespace TEN::Math +{ + float FloorToStep(float value, float step) + { + return (floor(value / step) * step); + } + + float CeilToStep(float value, float step) + { + return (ceil(value / step) * step); + } + + float RoundToStep(float value, float step) + { + return (round(value / step) * step); + } + + float Remap(float value, float min0, float max0, float min1, float max1) + { + float alpha = (value - min0) / (max0 - min0); + return Lerp(min1, max1, alpha); + } + + float Lerp(float value0, float value1, float alpha) + { + alpha = std::clamp(alpha, 0.0f, 1.0f); + return (((1.0f - alpha) * value0) + (alpha * value1)); + } + + float Smoothstep(float value0, float value1, float alpha) + { + alpha = std::clamp(alpha, value0, value1); + + // Scale, bias, and saturate alpha to [0, 1] range. + alpha = std::clamp((alpha - value0) / (value1 - value0), 0.0f, 1.0f); + + // Evaluate polynomial. + return (CUBE(alpha) * (alpha * ((alpha * 6) - 15.0f) + 10.0f)); + } + + float Smoothstep(float alpha) + { + return Smoothstep(0.0f, 1.0f, alpha); + } + + float EaseInSine(float value0, float value1, float alpha) + { + alpha = std::clamp(alpha, 0.0f, 1.0f); + return Lerp(value0, value1, 1.0f - cos((alpha * PI) / 2)); + } + + float EaseInSine(float alpha) + { + return EaseInSine(0.0f, 1.0f, alpha); + } + + float EaseOutSine(float value0, float value1, float alpha) + { + alpha = std::clamp(alpha, 0.0f, 1.0f); + return Lerp(value0, value1, sin((alpha * PI) / 2)); + } + + float EaseOutSine(float alpha) + { + return EaseOutSine(0.0f, 1.0f, alpha); + } + + float EaseInOutSine(float value0, float value1, float alpha) + { + alpha = std::clamp(alpha, 0.0f, 1.0f); + return Lerp(value0, value1, (1.0f - cos(alpha * PI)) / 2); + } + + float EaseInOutSine(float alpha) + { + return EaseInOutSine(0.0f, 1.0f, alpha); + } + + float Luma(const Vector3& color) + { + constexpr auto RED_COEFF = 0.2126f; + constexpr auto GREEN_COEFF = 0.7152f; + constexpr auto BLUE_COEFF = 0.0722f; + + // Use Rec.709 trichromat formula to get perceptive luma value. + return float((color.x * RED_COEFF) + (color.y * GREEN_COEFF) + (color.z * BLUE_COEFF)); + } + + Vector3 Screen(const Vector3& ambient, const Vector3& tint) + { + float luma = Luma(tint); + auto multiplicative = ambient * tint; + auto additive = ambient + tint; + + return Vector3( + Lerp(multiplicative.x, additive.x, luma), + Lerp(multiplicative.y, additive.y, luma), + Lerp(multiplicative.z, additive.z, luma)); + } + + Vector4 Screen(const Vector4& ambient, const Vector4& tint) + { + auto result = Screen(Vector3(ambient), Vector3(tint)); + return Vector4(result.x, result.y, result.z, ambient.w * tint.w); + } +} diff --git a/TombEngine/Math/Utils.h b/TombEngine/Math/Utils.h new file mode 100644 index 000000000..5066188e5 --- /dev/null +++ b/TombEngine/Math/Utils.h @@ -0,0 +1,32 @@ +#pragma once + +namespace TEN::Math +{ + constexpr inline auto OFFSET_RADIUS = [](auto x) { return ((x * SQRT_2) + 4); }; + constexpr inline auto MESH_BITS = [](auto x) { return (1 << x); }; + + // Value manipulation + + float FloorToStep(float value, float step); + float CeilToStep(float value, float step); + float RoundToStep(float value, float step); + float Remap(float value, float min0, float max0, float min1, float max1); + + // Interpolation + + float Lerp(float value0, float value1, float alpha); + float Smoothstep(float value0, float value1, float alpha); + float Smoothstep(float alpha); + float EaseInSine(float value0, float value1, float alpha); + float EaseInSine(float alpha); + float EaseOutSine(float value0, float value1, float alpha); + float EaseOutSine(float alpha); + float EaseInOutSine(float value0, float value1, float alpha); + float EaseInOutSine(float alpha); + + // Color + + float Luma(const Vector3& color); + Vector3 Screen(const Vector3& ambient, const Vector3& tint); + Vector4 Screen(const Vector4& ambient, const Vector4& tint); +} diff --git a/TombEngine/Objects/Effects/EmberEmitter.cpp b/TombEngine/Objects/Effects/EmberEmitter.cpp index 096ade6e1..4aef9f6de 100644 --- a/TombEngine/Objects/Effects/EmberEmitter.cpp +++ b/TombEngine/Objects/Effects/EmberEmitter.cpp @@ -27,10 +27,13 @@ namespace TEN::Effects::EmberEmitter if (item.TriggerFlags < 0) { + if (item.TriggerFlags > -11) + item.TriggerFlags = -11; + if (!item.ItemFlags[2]) { - int div = abs(item.TriggerFlags) % 10 << 10; - int mod = abs(item.TriggerFlags) / 10 << 10; + int div = (abs(item.TriggerFlags) % 10) << 10; + int mod = (abs(item.TriggerFlags) / 10) << 10; // TODO: Use Random::GenerateInt() item.ItemFlags[0] = GetRandomControl() % div; diff --git a/TombEngine/Objects/Effects/LensFlare.cpp b/TombEngine/Objects/Effects/LensFlare.cpp new file mode 100644 index 000000000..788b64c75 --- /dev/null +++ b/TombEngine/Objects/Effects/LensFlare.cpp @@ -0,0 +1,110 @@ +#include "framework.h" +#include "Objects/Effects/LensFlare.h" + +#include "Game/camera.h" +#include "Game/control/los.h" +#include "Math/Math.h" +#include "Scripting/Include/ScriptInterfaceLevel.h" +#include "Scripting/Include/Flow/ScriptInterfaceFlowHandler.h" +#include "Specific/level.h" + +using namespace TEN::Math; + +namespace TEN::Entities::Effects +{ + std::vector LensFlares; + + static void SetupLensFlare(const Vector3& pos, int roomNumber, const Color& color, bool isGlobal, int spriteID) + { + auto lensFlarePos = Vector3::Zero; + if (isGlobal) + { + if (TestEnvironment(ENV_FLAG_NO_LENSFLARE, Camera.pos.RoomNumber)) + return; + + lensFlarePos = pos; + auto delta = (lensFlarePos - Camera.pos.ToVector3()) / 16.0f; + while (abs(delta.x) > BLOCK(200) || abs(delta.y) > BLOCK(200) || abs(delta.z) > BLOCK(200)) + lensFlarePos -= delta; + + delta = (lensFlarePos - Camera.pos.ToVector3()) / 16.0f; + while (abs(delta.x) > BLOCK(32) || abs(delta.y) > BLOCK(32) || abs(delta.z) > BLOCK(32)) + lensFlarePos -= delta; + + delta = (lensFlarePos - Camera.pos.ToVector3()) / 16.0f; + for (int i = 0; i < 16; i++) + { + int foundRoomNumber = IsRoomOutside(lensFlarePos.x, lensFlarePos.y, lensFlarePos.z); + if (foundRoomNumber != NO_VALUE) + { + roomNumber = foundRoomNumber; + break; + } + + lensFlarePos -= delta; + } + } + else + { + float dist = Vector3::Distance(pos, Camera.pos.ToVector3()); + if (dist > BLOCK(32)) + return; + + lensFlarePos = pos; + } + + bool isVisible = false; + if (roomNumber != NO_VALUE) + { + if (TestEnvironment(ENV_FLAG_NOT_NEAR_OUTSIDE, roomNumber) || !isGlobal) + { + auto origin = Camera.pos; + auto target = GameVector(lensFlarePos, roomNumber); + isVisible = LOS(&origin, &target); + } + } + + if (!isVisible && !isGlobal) + return; + + auto lensFlare = LensFlare{}; + lensFlare.Position = pos; + lensFlare.RoomNumber = roomNumber; + lensFlare.IsGlobal = isGlobal; + lensFlare.Color = color; + lensFlare.SpriteID = spriteID; + + LensFlares.push_back(lensFlare); + } + + void UpdateGlobalLensFlare() + { + constexpr auto BASE_POS = Vector3(0.0f, 0.0f, BLOCK(256)); + + if (!g_GameFlow->GetLevel(CurrentLevel)->GetLensFlareEnabled()) + return; + + auto orient = EulerAngles(g_GameFlow->GetLevel(CurrentLevel)->GetLensFlarePitch(), g_GameFlow->GetLevel(CurrentLevel)->GetLensFlareYaw(), 0); + auto color = g_GameFlow->GetLevel(CurrentLevel)->GetLensFlareColor(); + auto spriteID = g_GameFlow->GetLevel(CurrentLevel)->GetLensFlareSunSpriteID(); + + auto pos = Camera.pos.ToVector3(); + auto rotMatrix = orient.ToRotationMatrix(); + + pos += Vector3::Transform(BASE_POS, rotMatrix); + SetupLensFlare(pos, NO_VALUE, color, true, spriteID); + } + + void ControlLensFlare(int itemNumber) + { + auto& item = g_Level.Items[itemNumber]; + + if (TriggerActive(&item)) + SetupLensFlare(item.Pose.Position.ToVector3(), item.RoomNumber, Color(), false, SPRITE_TYPES::SPR_LENS_FLARE_3); + } + + void ClearLensFlares() + { + LensFlares.clear(); + } +} diff --git a/TombEngine/Objects/Effects/LensFlare.h b/TombEngine/Objects/Effects/LensFlare.h new file mode 100644 index 000000000..0f384591f --- /dev/null +++ b/TombEngine/Objects/Effects/LensFlare.h @@ -0,0 +1,23 @@ +#pragma once + +class EulerAngles; + +namespace TEN::Entities::Effects +{ + struct LensFlare + { + int SpriteID = 0; + + Vector3 Position = Vector3::Zero; + int RoomNumber = 0; + Color Color = {}; + + bool IsGlobal = false; + }; + + extern std::vector LensFlares; + + void ControlLensFlare(int itemNumber); + void ClearLensFlares(); + void UpdateGlobalLensFlare(); +} diff --git a/TombEngine/Objects/Effects/effect_objects.cpp b/TombEngine/Objects/Effects/effect_objects.cpp index 6014c7c5b..0b6b1f9ec 100644 --- a/TombEngine/Objects/Effects/effect_objects.cpp +++ b/TombEngine/Objects/Effects/effect_objects.cpp @@ -2,8 +2,9 @@ #include "Objects/Effects/effect_objects.h" #include "Game/Setup.h" -#include "Objects/Effects/flame_emitters.h" #include "Objects/Effects/enemy_missile.h" +#include "Objects/Effects/flame_emitters.h" +#include "Objects/Effects/LensFlare.h" using namespace TEN::Entities::Effects; @@ -54,4 +55,8 @@ void InitializeEffectsObjects() obj->collision = nullptr; obj->control = ControlEnemyMissile; } -} \ No newline at end of file + + obj = &Objects[ID_LENS_FLARE]; + if (obj->loaded) + obj->control = ControlLensFlare; +} diff --git a/TombEngine/Objects/Effects/enemy_missile.cpp b/TombEngine/Objects/Effects/enemy_missile.cpp index 0d4c47484..2ab48ec11 100644 --- a/TombEngine/Objects/Effects/enemy_missile.cpp +++ b/TombEngine/Objects/Effects/enemy_missile.cpp @@ -3,6 +3,7 @@ #include "Game/collision/collide_item.h" #include "Game/collision/collide_room.h" +#include "Game/collision/Point.h" #include "Game/control/control.h" #include "Game/effects/debris.h" #include "Game/effects/effects.h" @@ -17,6 +18,7 @@ #include "Renderer/RendererEnums.h" #include "Specific/level.h" +using namespace TEN::Collision::Point; using namespace TEN::Effects::Items; using namespace TEN::Entities::Creatures::TR3; using namespace TEN::Entities::TR4; @@ -119,9 +121,7 @@ namespace TEN::Entities::Effects ShatterItem.yRot = fx->pos.Orientation.y; ShatterItem.meshIndex = fx->frameNumber; ShatterItem.color = Vector4::One; - ShatterItem.sphere.x = fx->pos.Position.x; - ShatterItem.sphere.y = fx->pos.Position.y; - ShatterItem.sphere.z = fx->pos.Position.z; + ShatterItem.sphere.Center = fx->pos.Position.ToVector3(); ShatterItem.bit = 0; ShatterItem.flags = fx->flag2 & 0x400; ShatterObject(&ShatterItem, 0, param2, fx->roomNumber, param1); @@ -221,9 +221,9 @@ namespace TEN::Entities::Effects fx.pos.Position.y += -((fx.speed * phd_sin(fx.pos.Orientation.x))) + fx.fallspeed; fx.pos.Position.z += speed * phd_cos(fx.pos.Orientation.y); - auto pointColl = GetCollision(fx.pos.Position.x, fx.pos.Position.y, fx.pos.Position.z, fx.roomNumber); + auto pointColl = GetPointCollision(fx.pos.Position, fx.roomNumber); - if (fx.pos.Position.y >= pointColl.Position.Floor || fx.pos.Position.y <= pointColl.Position.Ceiling) + if (fx.pos.Position.y >= pointColl.GetFloorHeight() || fx.pos.Position.y <= pointColl.GetCeilingHeight()) { fx.pos.Position = prevPos; @@ -361,8 +361,8 @@ namespace TEN::Entities::Effects } else { - if (pointColl.RoomNumber != fx.roomNumber) - EffectNewRoom(fxNumber, pointColl.RoomNumber); + if (pointColl.GetRoomNumber() != fx.roomNumber) + EffectNewRoom(fxNumber, pointColl.GetRoomNumber()); auto deltaPos = prevPos - fx.pos.Position; diff --git a/TombEngine/Objects/Effects/flame_emitters.cpp b/TombEngine/Objects/Effects/flame_emitters.cpp index 173ba6fc7..12319e2d1 100644 --- a/TombEngine/Objects/Effects/flame_emitters.cpp +++ b/TombEngine/Objects/Effects/flame_emitters.cpp @@ -4,7 +4,7 @@ #include "Game/animation.h" #include "Game/collision/collide_item.h" #include "Game/collision/collide_room.h" -#include "Game/collision/sphere.h" +#include "Game/collision/Point.h" #include "Game/effects/effects.h" #include "Game/effects/Electricity.h" #include "Game/effects/item_fx.h" @@ -20,6 +20,7 @@ #include "Specific/Input/Input.h" #include "Specific/level.h" +using namespace TEN::Collision::Point; using namespace TEN::Effects::Electricity; using namespace TEN::Effects::Environment; using namespace TEN::Effects::Items; @@ -63,7 +64,7 @@ namespace TEN::Entities::Effects void BurnNearbyItems(ItemInfo* item, int radius) { auto collObjects = GetCollidedObjects(*item, true, false, radius, ObjectCollectionMode::Items); - for (auto* itemPtr : collObjects.ItemPtrs) + for (auto* itemPtr : collObjects.Items) { if (TestEnvironment(ENV_FLAG_WATER, itemPtr->RoomNumber)) continue; @@ -181,7 +182,7 @@ namespace TEN::Entities::Effects else { // Normal flames. - AddFire(item->Pose.Position.x, item->Pose.Position.y, item->Pose.Position.z, item->RoomNumber, 2.0f, 0); + AddFire(item->Pose.Position.x, item->Pose.Position.y, item->Pose.Position.z, item->RoomNumber, 2.0f); TriggerDynamicLight(item->Pose.Position.x, item->Pose.Position.y, item->Pose.Position.z, 16 - (GetRandomControl() & 1), @@ -261,21 +262,21 @@ namespace TEN::Entities::Effects item->Pose.Position.x += phd_sin(item->Pose.Orientation.y - ANGLE(180)) * (CLICK(1) / FPS); item->Pose.Position.z += phd_cos(item->Pose.Orientation.y - ANGLE(180)) * (CLICK(1) / FPS); - auto pointColl = GetCollision(item); + auto pointColl = GetPointCollision(*item); - if (TestEnvironment(ENV_FLAG_WATER, pointColl.RoomNumber) || - pointColl.Position.Floor - item->Pose.Position.y > CLICK(2) || - pointColl.Position.Floor == NO_HEIGHT) + if (TestEnvironment(ENV_FLAG_WATER, pointColl.GetRoomNumber()) || + pointColl.GetFloorHeight() - item->Pose.Position.y > CLICK(2) || + pointColl.GetFloorHeight() == NO_HEIGHT) { Weather.Flash(255, 128, 0, 0.03f); KillItem(itemNumber); return; } - if (item->RoomNumber != pointColl.RoomNumber) - ItemNewRoom(itemNumber, pointColl.RoomNumber); + if (item->RoomNumber != pointColl.GetRoomNumber()) + ItemNewRoom(itemNumber, pointColl.GetRoomNumber()); - item->Pose.Position.y = pointColl.Position.Floor; + item->Pose.Position.y = pointColl.GetFloorHeight(); if (Wibble & 7) TriggerFireFlame(item->Pose.Position.x, item->Pose.Position.y - 32, item->Pose.Position.z, FlameType::Medium); diff --git a/TombEngine/Objects/Effects/tr4_locusts.cpp b/TombEngine/Objects/Effects/tr4_locusts.cpp index e828ce78b..25b4aca2e 100644 --- a/TombEngine/Objects/Effects/tr4_locusts.cpp +++ b/TombEngine/Objects/Effects/tr4_locusts.cpp @@ -3,7 +3,6 @@ #include "Game/animation.h" #include "Game/collision/collide_item.h" -#include "Game/collision/sphere.h" #include "Game/effects/tomb4fx.h" #include "Game/items.h" #include "Game/Lara/lara.h" @@ -111,6 +110,8 @@ namespace TEN::Entities::TR4 auto* locust = &Locusts[i]; if (locust->on) { + locust->StoreInterpolationData(); + // NOTE: not present in original TR4 code //if (locust->target == nullptr) // locust->target = LaraItem; diff --git a/TombEngine/Objects/Effects/tr4_locusts.h b/TombEngine/Objects/Effects/tr4_locusts.h index 2f2495a2b..6fd6530ea 100644 --- a/TombEngine/Objects/Effects/tr4_locusts.h +++ b/TombEngine/Objects/Effects/tr4_locusts.h @@ -14,7 +14,13 @@ struct LOCUST_INFO short escapeZrot; BYTE counter; - Matrix Transform; + Matrix Transform = Matrix::Identity; + Matrix PrevTransform = Matrix::Identity; + + void StoreInterpolationData() + { + PrevTransform = Transform; + } }; namespace TEN::Entities::TR4 diff --git a/TombEngine/Objects/Effects/tr5_electricity.cpp b/TombEngine/Objects/Effects/tr5_electricity.cpp index 0afbb7f0f..81e6db94a 100644 --- a/TombEngine/Objects/Effects/tr5_electricity.cpp +++ b/TombEngine/Objects/Effects/tr5_electricity.cpp @@ -4,7 +4,7 @@ #include "Game/animation.h" #include "Game/collision/collide_item.h" #include "Game/collision/collide_room.h" -#include "Game/collision/sphere.h" +#include "Game/collision/Point.h" #include "Game/control/control.h" #include "Game/effects/effects.h" #include "Game/effects/item_fx.h" @@ -17,6 +17,7 @@ #include "Specific/clock.h" #include "Specific/level.h" +using namespace TEN::Collision::Point; using namespace TEN::Effects::Items; using namespace TEN::Effects::Ripple; @@ -73,7 +74,7 @@ void TriggerElectricityWireSparks(int x, int z, byte objNum, byte node, bool glo if (glow) { spark->scalar = 1; - spark->spriteIndex = Objects[ID_DEFAULT_SPRITES].meshIndex + SPR_LENSFLARE_LIGHT; + spark->spriteIndex = Objects[ID_DEFAULT_SPRITES].meshIndex + SPR_LENS_FLARE_LIGHT; spark->size = spark->sSize = (GetRandomControl() & 0x1F) + 160; } else @@ -178,7 +179,7 @@ void ElectricityWiresControl(short itemNumber) return; auto collObjects = GetCollidedObjects(*item, true, false, BLOCK(2), ObjectCollectionMode::Items); - for (auto* itemPtr : collObjects.ItemPtrs) + for (auto* itemPtr : collObjects.Items) { const auto& object = Objects[itemPtr->ObjectNumber]; @@ -215,10 +216,9 @@ void ElectricityWiresControl(short itemNumber) for (int j = 0; j < object.nmeshes; j++) { auto collPos = GetJointPosition(itemPtr, j); + auto pointCollJointRoom = GetPointCollision(collPos, itemPtr->RoomNumber).GetRoomNumber(); - auto collJointRoom = GetCollision(collPos.x, collPos.y, collPos.z, itemPtr->RoomNumber).RoomNumber; - - if (!isWaterNearby && isTouchingWater && roomNumber == collJointRoom) + if (!isWaterNearby && isTouchingWater && roomNumber == pointCollJointRoom) isWaterNearby = true; } diff --git a/TombEngine/Objects/Generic/Doors/double_doors.cpp b/TombEngine/Objects/Generic/Doors/double_doors.cpp index 9e605f320..8a20011c6 100644 --- a/TombEngine/Objects/Generic/Doors/double_doors.cpp +++ b/TombEngine/Objects/Generic/Doors/double_doors.cpp @@ -10,7 +10,6 @@ #include "Game/pickup/pickup.h" #include "Sound/sound.h" #include "Game/animation.h" -#include "Game/collision/sphere.h" #include "Game/Lara/lara.h" #include "Game/Lara/lara_helpers.h" #include "Game/Lara/lara_struct.h" diff --git a/TombEngine/Objects/Generic/Doors/generic_doors.cpp b/TombEngine/Objects/Generic/Doors/generic_doors.cpp index b42c57f26..756dfd0c2 100644 --- a/TombEngine/Objects/Generic/Doors/generic_doors.cpp +++ b/TombEngine/Objects/Generic/Doors/generic_doors.cpp @@ -10,7 +10,7 @@ #include "Game/pickup/pickup.h" #include "Sound/sound.h" #include "Game/animation.h" -#include "Game/collision/sphere.h" +#include "Game/collision/Sphere.h" #include "Objects/Generic/Switches/cog_switch.h" #include "Objects/objectslist.h" #include "Game/Lara/lara.h" @@ -23,6 +23,8 @@ #include "Game/collision/collide_item.h" #include "Game/itemdata/itemdata.h" +using namespace TEN::Collision::Room; +using namespace TEN::Collision::Sphere; using namespace TEN::Gui; using namespace TEN::Input; @@ -76,35 +78,35 @@ namespace TEN::Entities::Doors xOffset = BLOCK(1); auto* r = &g_Level.Rooms[doorItem->RoomNumber]; - doorData->d1.floor = GetSector(r, doorItem->Pose.Position.x - r->x + xOffset, doorItem->Pose.Position.z - r->z + zOffset); + doorData->d1.floor = GetSector(r, doorItem->Pose.Position.x - r->Position.x + xOffset, doorItem->Pose.Position.z - r->Position.z + zOffset); auto roomNumber = doorData->d1.floor->SidePortalRoomNumber; if (roomNumber == NO_VALUE) - boxNumber = doorData->d1.floor->Box; + boxNumber = doorData->d1.floor->PathfindingBoxID; else { auto* b = &g_Level.Rooms[roomNumber]; - boxNumber = GetSector(b, doorItem->Pose.Position.x - b->x + xOffset, doorItem->Pose.Position.z - b->z + zOffset)->Box; + boxNumber = GetSector(b, doorItem->Pose.Position.x - b->Position.x + xOffset, doorItem->Pose.Position.z - b->Position.z + zOffset)->PathfindingBoxID; } - doorData->d1.block = (boxNumber != NO_VALUE && g_Level.Boxes[boxNumber].flags & BLOCKABLE) ? boxNumber : NO_VALUE; + doorData->d1.block = (boxNumber != NO_VALUE && g_Level.PathfindingBoxes[boxNumber].flags & BLOCKABLE) ? boxNumber : NO_VALUE; doorData->d1.data = *doorData->d1.floor; if (r->flippedRoom != -1) { r = &g_Level.Rooms[r->flippedRoom]; - doorData->d1flip.floor = GetSector(r, doorItem->Pose.Position.x - r->x + xOffset, doorItem->Pose.Position.z - r->z + zOffset); + doorData->d1flip.floor = GetSector(r, doorItem->Pose.Position.x - r->Position.x + xOffset, doorItem->Pose.Position.z - r->Position.z + zOffset); roomNumber = doorData->d1flip.floor->SidePortalRoomNumber; if (roomNumber == NO_VALUE) - boxNumber = doorData->d1flip.floor->Box; + boxNumber = doorData->d1flip.floor->PathfindingBoxID; else { auto* b = &g_Level.Rooms[roomNumber]; - boxNumber = GetSector(b, doorItem->Pose.Position.x - b->x + xOffset, doorItem->Pose.Position.z - b->z + zOffset)->Box; + boxNumber = GetSector(b, doorItem->Pose.Position.x - b->Position.x + xOffset, doorItem->Pose.Position.z - b->Position.z + zOffset)->PathfindingBoxID; } - doorData->d1flip.block = (boxNumber != NO_VALUE && g_Level.Boxes[boxNumber].flags & BLOCKABLE) ? boxNumber : NO_VALUE; + doorData->d1flip.block = (boxNumber != NO_VALUE && g_Level.PathfindingBoxes[boxNumber].flags & BLOCKABLE) ? boxNumber : NO_VALUE; doorData->d1flip.data = *doorData->d1flip.floor; } else @@ -123,35 +125,35 @@ namespace TEN::Entities::Doors else { r = &g_Level.Rooms[twoRoom]; - doorData->d2.floor = GetSector(r, doorItem->Pose.Position.x - r->x, doorItem->Pose.Position.z - r->z); + doorData->d2.floor = GetSector(r, doorItem->Pose.Position.x - r->Position.x, doorItem->Pose.Position.z - r->Position.z); roomNumber = doorData->d2.floor->SidePortalRoomNumber; if (roomNumber == NO_VALUE) - boxNumber = doorData->d2.floor->Box; + boxNumber = doorData->d2.floor->PathfindingBoxID; else { auto* b = &g_Level.Rooms[roomNumber]; - boxNumber = GetSector(b, doorItem->Pose.Position.x - b->x, doorItem->Pose.Position.z - b->z)->Box; + boxNumber = GetSector(b, doorItem->Pose.Position.x - b->Position.x, doorItem->Pose.Position.z - b->Position.z)->PathfindingBoxID; } - doorData->d2.block = (boxNumber != NO_VALUE && g_Level.Boxes[boxNumber].flags & BLOCKABLE) ? boxNumber : NO_VALUE; + doorData->d2.block = (boxNumber != NO_VALUE && g_Level.PathfindingBoxes[boxNumber].flags & BLOCKABLE) ? boxNumber : NO_VALUE; doorData->d2.data = *doorData->d2.floor; if (r->flippedRoom != -1) { r = &g_Level.Rooms[r->flippedRoom]; - doorData->d2flip.floor = GetSector(r, doorItem->Pose.Position.x - r->x, doorItem->Pose.Position.z - r->z); + doorData->d2flip.floor = GetSector(r, doorItem->Pose.Position.x - r->Position.x, doorItem->Pose.Position.z - r->Position.z); roomNumber = doorData->d2flip.floor->SidePortalRoomNumber; if (roomNumber == NO_VALUE) - boxNumber = doorData->d2flip.floor->Box; + boxNumber = doorData->d2flip.floor->PathfindingBoxID; else { auto* b = &g_Level.Rooms[roomNumber]; - boxNumber = GetSector(b, doorItem->Pose.Position.x - b->x, doorItem->Pose.Position.z - b->z)->Box; + boxNumber = GetSector(b, doorItem->Pose.Position.x - b->Position.x, doorItem->Pose.Position.z - b->Position.z)->PathfindingBoxID; } - doorData->d2flip.block = (boxNumber != NO_VALUE && g_Level.Boxes[boxNumber].flags & BLOCKABLE) ? boxNumber : NO_VALUE; + doorData->d2flip.block = (boxNumber != NO_VALUE && g_Level.PathfindingBoxes[boxNumber].flags & BLOCKABLE) ? boxNumber : NO_VALUE; doorData->d2flip.data = *doorData->d2flip.floor; } else @@ -246,7 +248,7 @@ namespace TEN::Entities::Doors if (TestBoundsCollide(doorItem, laraItem, coll->Setup.Radius)) { - if (TestCollision(doorItem, laraItem)) + if (HandleItemSphereCollision(*doorItem, *laraItem)) { if (coll->Setup.EnableObjectPush) { @@ -400,7 +402,7 @@ namespace TEN::Entities::Doors short boxIndex = doorPos->block; if (boxIndex != NO_VALUE) { - g_Level.Boxes[boxIndex].flags &= ~BLOCKED; + g_Level.PathfindingBoxes[boxIndex].flags &= ~BLOCKED; for (auto& currentCreature : ActiveCreatures) currentCreature->LOT.TargetBox = NO_VALUE; } @@ -409,13 +411,13 @@ namespace TEN::Entities::Doors void ShutThatDoor(DOORPOS_DATA* doorPos, DOOR_DATA* dd) { - static const auto WALL_PLANE = Plane(-Vector3::UnitY, -CLICK(127)); + static const auto WALL_PLANE = Plane(-Vector3::UnitY, (float)NO_HEIGHT); FloorInfo* floor = doorPos->floor; if (floor) { - floor->Box = NO_VALUE; + floor->PathfindingBoxID = NO_VALUE; floor->TriggerIndex = 0; // FIXME: HACK!!!!!!! @@ -434,7 +436,7 @@ namespace TEN::Entities::Doors short boxIndex = doorPos->block; if (boxIndex != NO_VALUE) { - g_Level.Boxes[boxIndex].flags |= BLOCKED; + g_Level.PathfindingBoxes[boxIndex].flags |= BLOCKED; for (auto& currentCreature : ActiveCreatures) currentCreature->LOT.TargetBox = NO_VALUE; diff --git a/TombEngine/Objects/Generic/Doors/pushpull_kick_door.cpp b/TombEngine/Objects/Generic/Doors/pushpull_kick_door.cpp index be3992325..5f13849ab 100644 --- a/TombEngine/Objects/Generic/Doors/pushpull_kick_door.cpp +++ b/TombEngine/Objects/Generic/Doors/pushpull_kick_door.cpp @@ -10,7 +10,6 @@ #include "Game/pickup/pickup.h" #include "Sound/sound.h" #include "Game/animation.h" -#include "Game/collision/sphere.h" #include "Game/Lara/lara_helpers.h" #include "Game/Lara/lara_struct.h" #include "Game/Lara/lara.h" diff --git a/TombEngine/Objects/Generic/Doors/sequence_door.cpp b/TombEngine/Objects/Generic/Doors/sequence_door.cpp index a83a24eb9..27133e958 100644 --- a/TombEngine/Objects/Generic/Doors/sequence_door.cpp +++ b/TombEngine/Objects/Generic/Doors/sequence_door.cpp @@ -10,7 +10,6 @@ #include "Game/pickup/pickup.h" #include "Sound/sound.h" #include "Game/animation.h" -#include "Game/collision/sphere.h" #include "Game/Lara/lara_struct.h" #include "Game/Lara/lara.h" #include "Math/Math.h" diff --git a/TombEngine/Objects/Generic/Doors/steel_door.cpp b/TombEngine/Objects/Generic/Doors/steel_door.cpp index 42077147b..e0024d854 100644 --- a/TombEngine/Objects/Generic/Doors/steel_door.cpp +++ b/TombEngine/Objects/Generic/Doors/steel_door.cpp @@ -10,7 +10,7 @@ #include "Game/pickup/pickup.h" #include "Sound/sound.h" #include "Game/animation.h" -#include "Game/collision/sphere.h" +#include "Game/collision/Sphere.h" #include "Game/Lara/lara_struct.h" #include "Game/Lara/lara.h" #include "Math/Math.h" @@ -19,6 +19,8 @@ #include "Game/collision/collide_room.h" #include "Game/collision/collide_item.h" +using namespace TEN::Collision::Sphere; + namespace TEN::Entities::Doors { void InitializeSteelDoor(short itemNumber) @@ -37,7 +39,7 @@ namespace TEN::Entities::Doors { if (TestBoundsCollide(doorItem, laraItem, coll->Setup.Radius)) { - if (TestCollision(doorItem, laraItem)) + if (HandleItemSphereCollision(*doorItem, *laraItem)) { if (coll->Setup.EnableObjectPush) ItemPushItem(doorItem, laraItem, coll, 0, 1); diff --git a/TombEngine/Objects/Generic/Doors/underwater_door.cpp b/TombEngine/Objects/Generic/Doors/underwater_door.cpp index e93b308bd..c6a7aac4f 100644 --- a/TombEngine/Objects/Generic/Doors/underwater_door.cpp +++ b/TombEngine/Objects/Generic/Doors/underwater_door.cpp @@ -3,7 +3,6 @@ #include "Game/animation.h" #include "Game/collision/collide_item.h" -#include "Game/collision/sphere.h" #include "Game/control/box.h" #include "Game/control/control.h" #include "Game/control/lot.h" diff --git a/TombEngine/Objects/Generic/Object/Pushable/PushableCollision.cpp b/TombEngine/Objects/Generic/Object/Pushable/PushableCollision.cpp index bb390a04e..9af3e76a4 100644 --- a/TombEngine/Objects/Generic/Object/Pushable/PushableCollision.cpp +++ b/TombEngine/Objects/Generic/Object/Pushable/PushableCollision.cpp @@ -3,6 +3,7 @@ #include "Game/collision/collide_item.h" #include "Game/collision/floordata.h" +#include "Game/collision/Point.h" #include "Game/Lara/lara.h" #include "Game/Setup.h" #include "Objects/Generic/Object/BridgeObject.h" @@ -13,6 +14,7 @@ #include "Specific/level.h" using namespace TEN::Collision::Floordata; +using namespace TEN::Collision::Point; using namespace TEN::Entities::Generic; using namespace TEN::Input; @@ -28,24 +30,23 @@ namespace TEN::Entities::Generic if (pushableItem.Status == ITEM_INVISIBLE || pushableItem.TriggerFlags < 0) return false; - auto pointColl = CollisionResult{}; + auto pointColl = GetPointCollision(pushableItem); if (pushable.UseRoomCollision) { RemovePushableBridge(pushableItem); - pointColl = GetCollision(&pushableItem); + + pointColl = GetPointCollision(pushableItem); + pointColl.GetFloorHeight(); + AddPushableBridge(pushableItem); } - else - { - pointColl = GetCollision(&pushableItem); - } // 1) Check for wall. - if (pointColl.Block->IsWall(pushableItem.Pose.Position.x, pushableItem.Pose.Position.z)) + if (pointColl.GetSector().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); + int heightFromFloor = abs(pointColl.GetFloorHeight() - pushableItem.Pose.Position.y); if (heightFromFloor >= PUSHABLE_FLOOR_HEIGHT_TOLERANCE) return false; @@ -63,18 +64,18 @@ namespace TEN::Entities::Generic pushable.IsOnEdge = false; - auto pointColl = GetCollision(targetPos, targetRoomNumber); + auto pointColl = GetPointCollision(targetPos, targetRoomNumber); // 1) Check for wall. - if (pointColl.Block->IsWall(targetPos.x, targetPos.z)) + if (pointColl.GetSector().IsWall(targetPos.x, targetPos.z)) return false; // 2) Check for gaps or steps. - int heightFromFloor = abs(pointColl.Position.Floor - pushableItem.Pose.Position.y); + int heightFromFloor = abs(pointColl.GetFloorHeight() - pushableItem.Pose.Position.y); if (heightFromFloor >= PUSHABLE_FLOOR_HEIGHT_TOLERANCE) { // Step. - if (pointColl.Position.Floor < pushableItem.Pose.Position.y) + if (pointColl.GetFloorHeight() < pushableItem.Pose.Position.y) { return false; } @@ -88,26 +89,26 @@ namespace TEN::Entities::Generic } // 3) Check for slippery floor slope. - if (pointColl.Position.FloorSlope) + if (pointColl.IsSteepFloor()) return false; // 4) Check for diagonal floor step. - if (pointColl.Position.DiagonalStep) + if (pointColl.IsDiagonalFloorStep()) return false; // 5) Test floor slope. TODO: Check slope angles of normals directly. - if ((pointColl.Block->GetSurfaceNormal(0, true) != -Vector3::UnitY) || (pointColl.Block->GetSurfaceNormal(1, true) != -Vector3::UnitY)) + if ((pointColl.GetSector().GetSurfaceNormal(0, true) != -Vector3::UnitY) || (pointColl.GetSector().GetSurfaceNormal(1, true) != -Vector3::UnitY)) return false; // Check for stopper flag. /*if (collisionResult.Block->Stopper) { - if (collisionResult.Position.Floor <= pushableItem.Pose.Position.y) + if (collisionResult.GetFloorHeight() <= pushableItem.Pose.Position.y) return false; }*/ // Is ceiling (square or diagonal) high enough? - int distFromCeil = abs(pointColl.Position.Ceiling - pointColl.Position.Floor); + int distFromCeil = abs(pointColl.GetCeilingHeight() - pointColl.GetFloorHeight()); int blockHeight = GetStackHeight(pushableItem.Index) - PUSHABLE_FLOOR_HEIGHT_TOLERANCE; if (distFromCeil < blockHeight) return false; @@ -119,10 +120,10 @@ namespace TEN::Entities::Generic auto collObjects = GetCollidedObjects(pushableItem, true, true); pushableItem.Pose.Position = prevPos; - if (!collObjects.StaticPtrs.empty()) + if (!collObjects.Statics.empty()) return false; - for (const auto* itemPtr : collObjects.ItemPtrs) + for (const auto* itemPtr : collObjects.Items) { const auto& object = Objects[itemPtr->ObjectNumber]; @@ -168,32 +169,32 @@ namespace TEN::Entities::Generic } // This collisionResult is the point where Lara would be at the end of the pushable pull. - auto pointColl = GetCollision(LaraItem->Pose.Position + playerOffset, LaraItem->RoomNumber); + auto pointColl = GetPointCollision(LaraItem->Pose.Position + playerOffset, LaraItem->RoomNumber); // Test for wall. - if (pointColl.Block->IsWall(pointColl.Coordinates.x, pointColl.Coordinates.z)) + if (pointColl.GetSector().IsWall(pointColl.GetPosition().x, pointColl.GetPosition().z)) return false; // Test for flat floor. - int floorHeightDelta = abs(pointColl.Position.Floor - LaraItem->Pose.Position.y); + int floorHeightDelta = abs(pointColl.GetFloorHeight() - LaraItem->Pose.Position.y); if (floorHeightDelta >= PUSHABLE_FLOOR_HEIGHT_TOLERANCE) return false; // Test floor-to-ceiling height. - int floorToCeilHeight = abs(pointColl.Position.Ceiling - pointColl.Position.Floor); + int floorToCeilHeight = abs(pointColl.GetCeilingHeight() - pointColl.GetFloorHeight()); if (floorToCeilHeight < LARA_HEIGHT) return false; // Collide with objects. auto prevPos = LaraItem->Pose.Position; - LaraItem->Pose.Position = pointColl.Coordinates; + LaraItem->Pose.Position = pointColl.GetPosition(); auto collObjects = GetCollidedObjects(*LaraItem, true, true); LaraItem->Pose.Position = prevPos; - if (!collObjects.StaticPtrs.empty()) + if (!collObjects.Statics.empty()) return false; - for (const auto* itemPtr : collObjects.ItemPtrs) + for (const auto* itemPtr : collObjects.Items) { const auto& object = Objects[itemPtr->ObjectNumber]; @@ -301,33 +302,38 @@ namespace TEN::Entities::Generic { auto& pushable = GetPushableInfo(item); - auto pointColl = CollisionResult{}; + auto pointColl = GetPointCollision(item); int waterHeight = NO_HEIGHT; + auto pushableColl = PushableCollisionData{}; + if (pushable.UseBridgeCollision) { RemovePushableBridge(item); - pointColl = GetCollision(item); - waterHeight = GetWaterSurface(item.Pose.Position.x, item.Pose.Position.y, item.Pose.Position.z, item.RoomNumber); + pointColl = GetPointCollision(item); + pointColl.GetFloorHeight(); + + waterHeight = pointColl.GetWaterSurfaceHeight(); if (waterHeight == NO_HEIGHT && TestEnvironment(ENV_FLAG_SWAMP, item.RoomNumber)) - waterHeight = g_Level.Rooms[item.RoomNumber].maxceiling; + waterHeight = g_Level.Rooms[item.RoomNumber].TopHeight; + + pushableColl.FloorHeight = pointColl.GetFloorHeight(); + pushableColl.CeilingHeight = pointColl.GetCeilingHeight(); AddPushableBridge(item); } else { - pointColl = GetCollision(item); - waterHeight = GetWaterSurface(item.Pose.Position.x, item.Pose.Position.y, item.Pose.Position.z, item.RoomNumber); - + waterHeight = pointColl.GetWaterSurfaceHeight(); + if (waterHeight == NO_HEIGHT && TestEnvironment(ENV_FLAG_SWAMP, item.RoomNumber)) - waterHeight = g_Level.Rooms[item.RoomNumber].maxceiling; - } + waterHeight = g_Level.Rooms[item.RoomNumber].TopHeight; - auto pushableColl = PushableCollisionData{}; - pushableColl.FloorHeight = pointColl.Position.Floor; - pushableColl.CeilingHeight = pointColl.Position.Ceiling; + pushableColl.FloorHeight = pointColl.GetFloorHeight(); + pushableColl.CeilingHeight = pointColl.GetCeilingHeight(); + } // Above water. if (TestEnvironment(ENV_FLAG_WATER, item.RoomNumber) || @@ -359,7 +365,7 @@ namespace TEN::Entities::Generic // Grounded. else { - auto floorSlopeAngle = Geometry::GetSurfaceSlopeAngle(pointColl.FloorNormal); + auto floorSlopeAngle = Geometry::GetSurfaceSlopeAngle(pointColl.GetFloorNormal()); if (floorSlopeAngle == 0) { diff --git a/TombEngine/Objects/Generic/Object/Pushable/PushableObject.cpp b/TombEngine/Objects/Generic/Object/Pushable/PushableObject.cpp index 530c29c6b..4ef806982 100644 --- a/TombEngine/Objects/Generic/Object/Pushable/PushableObject.cpp +++ b/TombEngine/Objects/Generic/Object/Pushable/PushableObject.cpp @@ -4,6 +4,7 @@ #include "Game/animation.h" #include "Game/collision/collide_item.h" #include "Game/collision/collide_room.h" +#include "Game/collision/Point.h" #include "Game/collision/floordata.h" #include "Game/control/box.h" #include "Game/control/flipeffect.h" @@ -22,6 +23,7 @@ #include "Specific/level.h" using namespace TEN::Collision::Floordata; +using namespace TEN::Collision::Point; using namespace TEN::Input; namespace TEN::Entities::Generic @@ -102,7 +104,7 @@ namespace TEN::Entities::Generic // Update bridge. AddPushableStackBridge(pushableItem, false); - int probeRoomNumber = GetCollision(&pushableItem).RoomNumber; + int probeRoomNumber = GetPointCollision(pushableItem).GetRoomNumber(); AddPushableStackBridge(pushableItem, true); // Update room number. @@ -224,8 +226,8 @@ namespace TEN::Entities::Generic void SetPushableStopperFlag(bool isStopper, const Vector3i& pos, int roomNumber) { - auto pointColl = GetCollision(pos, roomNumber); - pointColl.Block->Stopper = isStopper; + auto pointColl = GetPointCollision(pos, roomNumber); + pointColl.GetSector().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. diff --git a/TombEngine/Objects/Generic/Object/Pushable/PushableSound.cpp b/TombEngine/Objects/Generic/Object/Pushable/PushableSound.cpp index 4509c346f..bd11e2fd3 100644 --- a/TombEngine/Objects/Generic/Object/Pushable/PushableSound.cpp +++ b/TombEngine/Objects/Generic/Object/Pushable/PushableSound.cpp @@ -1,10 +1,13 @@ #include "framework.h" #include "Objects/Generic/Object/Pushable/PushableSound.h" +#include "Game/collision/Point.h" #include "Objects/Generic/Object/Pushable/PushableObject.h" #include "Sound/sound.h" #include "Specific/level.h" +using namespace TEN::Collision::Point; + namespace TEN::Entities::Generic { enum PushableSoundType @@ -36,7 +39,7 @@ namespace TEN::Entities::Generic { 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::Stone, PushableSoundData{ SFX_TEN_PUSHABLES_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 } }, @@ -64,8 +67,8 @@ namespace TEN::Entities::Generic static std::optional GetPushableSoundID(const ItemInfo& pushableItem, PushableSoundType soundType) { // Get floor material. - auto pointColl = GetCollision(pushableItem); - auto material = pointColl.BottomBlock->GetSurfaceMaterial(pointColl.Coordinates.x, pointColl.Coordinates.z, true); + auto pointColl = GetPointCollision(pushableItem); + auto material = pointColl.GetBottomSector().GetSurfaceMaterial(pointColl.GetPosition().x, pointColl.GetPosition().z, true); switch (soundType) { diff --git a/TombEngine/Objects/Generic/Object/Pushable/PushableStack.cpp b/TombEngine/Objects/Generic/Object/Pushable/PushableStack.cpp index 8fa88319d..a99438936 100644 --- a/TombEngine/Objects/Generic/Object/Pushable/PushableStack.cpp +++ b/TombEngine/Objects/Generic/Object/Pushable/PushableStack.cpp @@ -2,12 +2,14 @@ #include "Objects/Generic/Object/Pushable/PushableStack.h" #include "Game/collision/floordata.h" +#include "Game/collision/Point.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; +using namespace TEN::Collision::Point; namespace TEN::Entities::Generic { @@ -44,8 +46,8 @@ namespace TEN::Entities::Generic int x = pushableItem.Pose.Position.x; int z = pushableItem.Pose.Position.z; - auto pointColl = GetCollision(&pushableItem); - int y = pointColl.Position.Floor; + auto pointColl = GetPointCollision(pushableItem); + int y = pointColl.GetFloorHeight(); stackGroups.emplace(Vector3i(x, y, z), std::vector()).first->second.push_back(itemNumber); } @@ -144,7 +146,7 @@ namespace TEN::Entities::Generic return pushabelStackFound; // Otherwise, check room below. - //auto collisionResult = GetCollision(pushableItem.Pose.Position.x, pushableItem.Pose.Position.y, pushableItem.Pose.Position.z, pushableItem.RoomNumber); + //auto collisionResult = GetPointCollision(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); diff --git a/TombEngine/Objects/Generic/Object/Pushable/PushableStates.cpp b/TombEngine/Objects/Generic/Object/Pushable/PushableStates.cpp index 3fe03da99..14afb8e0a 100644 --- a/TombEngine/Objects/Generic/Object/Pushable/PushableStates.cpp +++ b/TombEngine/Objects/Generic/Object/Pushable/PushableStates.cpp @@ -367,6 +367,20 @@ namespace TEN::Entities::Generic } } + // TODO: Remove. + static float InterpolateCubic(float value0, float value1, float value2, float value3, float alpha) + { + alpha = std::clamp(alpha, 0.0f, 1.0f); + + float p = (value3 - value2) - (value0 - value1); + float q = (value0 - value1) - p; + float r = value2 - value0; + float s = value1; + float x = alpha; + float xSquared = SQUARE(x); + return ((p * xSquared * x) + (q * xSquared) + (r * x) + s); + } + static void HandleEdgeSlipState(ItemInfo& pushableItem) { constexpr auto LEAN_ANGLE_MAX = ANGLE(40.0f); diff --git a/TombEngine/Objects/Generic/Object/burning_torch.cpp b/TombEngine/Objects/Generic/Object/burning_torch.cpp index 7d271b08d..4c1da0297 100644 --- a/TombEngine/Objects/Generic/Object/burning_torch.cpp +++ b/TombEngine/Objects/Generic/Object/burning_torch.cpp @@ -127,7 +127,6 @@ namespace TEN::Entities::Generic lara->LeftArm.FrameNumber++; if (lara->LeftArm.FrameNumber == 27) { - lara->Torch.IsLit = false; lara->Flare.ControlLeft = false; lara->LeftArm.Locked = false; lara->Torch.State = TorchState::Holding; @@ -139,6 +138,7 @@ namespace TEN::Entities::Generic { laraItem->Model.MeshIndex[LM_LHAND] = laraItem->Model.BaseMesh + LM_LHAND; CreateFlare(*laraItem, ID_BURNING_TORCH_ITEM, true); + lara->Torch.IsLit = false; } } } @@ -147,7 +147,6 @@ namespace TEN::Entities::Generic lara->LeftArm.FrameNumber++; if (lara->LeftArm.FrameNumber == 41) { - lara->Torch.IsLit = false; lara->Flare.ControlLeft = false; lara->LeftArm.Locked = false; lara->Torch.State = TorchState::Holding; @@ -159,6 +158,7 @@ namespace TEN::Entities::Generic { laraItem->Model.MeshIndex[LM_LHAND] = laraItem->Model.BaseMesh + LM_LHAND; CreateFlare(*laraItem, ID_BURNING_TORCH_ITEM, false); + lara->Torch.IsLit = false; } } else if (lara->Torch.State == TorchState::JustLit) @@ -261,19 +261,19 @@ namespace TEN::Entities::Generic if (!collObjects.IsEmpty()) { LaraCollision.Setup.EnableObjectPush = true; - if (!collObjects.ItemPtrs.empty()) + if (!collObjects.Items.empty()) { - const auto& object = Objects[collObjects.ItemPtrs.front()->ObjectNumber]; + const auto& object = Objects[collObjects.Items.front()->ObjectNumber]; if (!object.intelligent && - !collObjects.ItemPtrs.front()->IsLara()) + !collObjects.Items.front()->IsLara()) { - ObjectCollision(collObjects.ItemPtrs.front()->Index, item, &LaraCollision); + ObjectCollision(collObjects.Items.front()->Index, item, &LaraCollision); } } - else if (!collObjects.StaticPtrs.empty()) + else if (!collObjects.Statics.empty()) { - ItemPushStatic(item, *collObjects.StaticPtrs.front(), &LaraCollision); + ItemPushStatic(item, *collObjects.Statics.front(), &LaraCollision); } item->Animation.Velocity.z = -int(item->Animation.Velocity.z / 1.5f); diff --git a/TombEngine/Objects/Generic/Object/generic_bridge.cpp b/TombEngine/Objects/Generic/Object/generic_bridge.cpp index 05826f823..81abfa392 100644 --- a/TombEngine/Objects/Generic/Object/generic_bridge.cpp +++ b/TombEngine/Objects/Generic/Object/generic_bridge.cpp @@ -10,15 +10,28 @@ using namespace TEN::Collision::Floordata; namespace TEN::Entities::Generic { + static int GetTiltOffset(const ItemInfo& item, const Vector3i& pos, int tiltGrade, bool isFloor) + { + // Calculate delta position. + auto deltaPos = (item.Pose.Position - pos).ToVector3(); + + // Calculate tilt normal. + int sign = isFloor ? -1 : 1; + auto rotMatrix = EulerAngles(0, item.Pose.Orientation.y, 0).ToRotationMatrix(); + auto normal = Vector3::Transform(Vector3(-tiltGrade, 1.0f, 0.0f) * sign, rotMatrix); + normal.Normalize(); + + // Calculate and return tilt offset. + float relPlaneHeight = -((normal.x * deltaPos.x) + (normal.z * deltaPos.z)) / normal.y; + return ((relPlaneHeight / 4) + CLICK(tiltGrade * 0.5f)); + } + template static std::optional GetBridgeFloorHeight(const ItemInfo& item, const Vector3i& pos) { auto boxHeight = GetBridgeItemIntersect(item, pos, false); if (boxHeight.has_value() && tiltGrade != 0) - { - int height = item.Pose.Position.y + (tiltGrade * ((GetOffset(item.Pose.Orientation.y, pos.x, pos.z) / 4) + (BLOCK(1 / 8.0f)))); - return height; - } + return (*boxHeight + GetTiltOffset(item, pos, tiltGrade, true)); return boxHeight; } @@ -28,10 +41,7 @@ namespace TEN::Entities::Generic { auto boxHeight = GetBridgeItemIntersect(item, pos, true); if (boxHeight.has_value() && tiltGrade != 0) - { - int height = item.Pose.Position.y + (tiltGrade * ((GetOffset(item.Pose.Orientation.y, pos.x, pos.z) / 4) + (BLOCK(1 / 8.0f)))); - return (height + CLICK(1)); - } + return ((item.Pose.Position.y + GetTiltOffset(item, pos, tiltGrade, false)) + CLICK(1)); return boxHeight; } @@ -96,15 +106,4 @@ namespace TEN::Entities::Generic UpdateBridgeItem(bridgeItem); } - - int GetOffset(short angle, int x, int z) - { - // Get rotated sector point. - auto sectorPoint = GetSectorPoint(x, z).ToVector2(); - auto rotMatrix = Matrix::CreateRotationZ(TO_RAD(angle)); - Vector2::Transform(sectorPoint, rotMatrix, sectorPoint); - - // Return offset. - return -sectorPoint.x; - } } diff --git a/TombEngine/Objects/Generic/Object/generic_bridge.h b/TombEngine/Objects/Generic/Object/generic_bridge.h index 3830ecd23..12dfee101 100644 --- a/TombEngine/Objects/Generic/Object/generic_bridge.h +++ b/TombEngine/Objects/Generic/Object/generic_bridge.h @@ -3,5 +3,4 @@ namespace TEN::Entities::Generic { void InitializeBridge(short itemNumber); - int GetOffset(short angle, int x, int z); } diff --git a/TombEngine/Objects/Generic/Object/generic_trapdoor.cpp b/TombEngine/Objects/Generic/Object/generic_trapdoor.cpp index 940720209..f9fa318ab 100644 --- a/TombEngine/Objects/Generic/Object/generic_trapdoor.cpp +++ b/TombEngine/Objects/Generic/Object/generic_trapdoor.cpp @@ -187,8 +187,8 @@ namespace TEN::Entities::Generic ForcedFixedCamera.x = trapDoorItem->Pose.Position.x - phd_sin(trapDoorItem->Pose.Orientation.y) * 2048; ForcedFixedCamera.y = trapDoorItem->Pose.Position.y - 2048; - if (ForcedFixedCamera.y < g_Level.Rooms[trapDoorItem->RoomNumber].maxceiling) - ForcedFixedCamera.y = g_Level.Rooms[trapDoorItem->RoomNumber].maxceiling; + if (ForcedFixedCamera.y < g_Level.Rooms[trapDoorItem->RoomNumber].TopHeight) + ForcedFixedCamera.y = g_Level.Rooms[trapDoorItem->RoomNumber].TopHeight; ForcedFixedCamera.z = trapDoorItem->Pose.Position.z - phd_cos(trapDoorItem->Pose.Orientation.y) * 2048; ForcedFixedCamera.RoomNumber = trapDoorItem->RoomNumber; diff --git a/TombEngine/Objects/Generic/Object/objects.cpp b/TombEngine/Objects/Generic/Object/objects.cpp index 184674ae9..9cd125291 100644 --- a/TombEngine/Objects/Generic/Object/objects.cpp +++ b/TombEngine/Objects/Generic/Object/objects.cpp @@ -3,7 +3,6 @@ #include "Game/animation.h" #include "Game/collision/collide_item.h" -#include "Game/collision/sphere.h" #include "Game/control/control.h" #include "Game/effects/effects.h" #include "Game/items.h" @@ -236,13 +235,31 @@ void InitializeAnimating(short itemNumber) void AnimatingControl(short itemNumber) { - auto* item = &g_Level.Items[itemNumber]; + auto& item = g_Level.Items[itemNumber]; - if (!TriggerActive(item)) - return; + if (TriggerActive(&item)) + { + item.Status = ITEM_ACTIVE; + AnimateItem(&item); - item->Status = ITEM_ACTIVE; - AnimateItem(item); + if (item.TriggerFlags == 666) //OCB used for the helicopter animating in the Train level. + { + auto pos = GetJointPosition(item, 0); + SoundEffect(SFX_TR4_HELICOPTER_LOOP, (Pose*)&pos); + + if (item.Animation.FrameNumber == GetAnimData(item).frameEnd) + { + item.Flags &= 0xC1; + RemoveActiveItem(itemNumber); + item.Status = ITEM_NOT_ACTIVE; + } + } + } + else if (item.TriggerFlags == 2) //Make the animating dissapear when anti-triggered. + { + RemoveActiveItem(itemNumber); + item.Status = ITEM_INVISIBLE; + } // TODO: ID_SHOOT_SWITCH2 is probably the bell in Trajan Markets, use Lua for that. /*if (item->frameNumber >= g_Level.Anims[item->animNumber].frameEnd) diff --git a/TombEngine/Objects/Generic/Object/polerope.cpp b/TombEngine/Objects/Generic/Object/polerope.cpp index e8f5faf11..ff5826d1b 100644 --- a/TombEngine/Objects/Generic/Object/polerope.cpp +++ b/TombEngine/Objects/Generic/Object/polerope.cpp @@ -2,7 +2,7 @@ #include "Objects/Generic/Object/polerope.h" #include "Game/collision/collide_item.h" -#include "Game/collision/sphere.h" +#include "Game/collision/Sphere.h" #include "Game/control/box.h" #include "Game/control/control.h" #include "Game/control/lot.h" @@ -15,6 +15,7 @@ #include "Specific/Input/Input.h" #include "Specific/level.h" +using namespace TEN::Collision::Sphere; using namespace TEN::Input; using namespace TEN::Math; @@ -119,7 +120,7 @@ namespace TEN::Entities::Generic // Test bounds collision. if (TestBoundsCollide(&poleItem, laraItem, LARA_RADIUS + (int)round(abs(laraItem->Animation.Velocity.z)))) { - if (TestCollision(&poleItem, laraItem)) + if (HandleItemSphereCollision(poleItem, *laraItem)) { // Temporarily reorient pole. short yOrient = poleItem.Pose.Orientation.y; diff --git a/TombEngine/Objects/Generic/Object/rope.cpp b/TombEngine/Objects/Generic/Object/rope.cpp index ce4b64671..63cd732b6 100644 --- a/TombEngine/Objects/Generic/Object/rope.cpp +++ b/TombEngine/Objects/Generic/Object/rope.cpp @@ -10,7 +10,6 @@ #include "Game/Lara/lara.h" #include "Math/Math.h" #include "Game/collision/collide_room.h" -#include "Game/collision/sphere.h" #include "Objects/Generic/Object/rope.h" #include "Sound/sound.h" #include "Game/camera.h" @@ -155,6 +154,8 @@ namespace TEN::Entities::Generic if (TriggerActive(item)) { rope->active = 1; + rope->StoreInterpolationData(); + RopeDynamics(rope); } else @@ -277,9 +278,9 @@ namespace TEN::Entities::Generic for (int i = pendulumPointer->node; i >= 0; --i) { - rope->segment[i].x = rope->meshSegment[i - 1].x + ((int64_t)rope->segmentLength * vec.x >> FP_SHIFT); - rope->segment[i].y = rope->meshSegment[i - 1].y + ((int64_t)rope->segmentLength * vec.y >> FP_SHIFT); - rope->segment[i].z = rope->meshSegment[i - 1].z + ((int64_t)rope->segmentLength * vec.z >> FP_SHIFT); + rope->segment[i].x = rope->meshSegment[std::clamp(i - 1, 0, ROPE_SEGMENTS)].x + ((int64_t)rope->segmentLength * vec.x >> FP_SHIFT); + rope->segment[i].y = rope->meshSegment[std::clamp(i - 1, 0, ROPE_SEGMENTS)].y + ((int64_t)rope->segmentLength * vec.y >> FP_SHIFT); + rope->segment[i].z = rope->meshSegment[std::clamp(i - 1, 0, ROPE_SEGMENTS)].z + ((int64_t)rope->segmentLength * vec.z >> FP_SHIFT); rope->velocity[i].x = 0; rope->velocity[i].y = 0; diff --git a/TombEngine/Objects/Generic/Object/rope.h b/TombEngine/Objects/Generic/Object/rope.h index cc6865516..18333a59b 100644 --- a/TombEngine/Objects/Generic/Object/rope.h +++ b/TombEngine/Objects/Generic/Object/rope.h @@ -21,15 +21,22 @@ namespace TEN::Entities::Generic int room; Vector3i position; - Vector3i segment[ROPE_SEGMENTS]; - Vector3i velocity[ROPE_SEGMENTS]; - Vector3i normalisedSegment[ROPE_SEGMENTS]; - Vector3i meshSegment[ROPE_SEGMENTS]; - Vector3i coords[ROPE_SEGMENTS]; + std::array segment = {}; + std::array velocity = {}; + std::array normalisedSegment = {}; + std::array meshSegment = {}; + std::array coords = {}; int segmentLength; short active; short coiled; + + std::array PrevMeshSegments = {}; + + void StoreInterpolationData() + { + PrevMeshSegments = meshSegment; + } }; struct PENDULUM diff --git a/TombEngine/Objects/Generic/Switches/switch.cpp b/TombEngine/Objects/Generic/Switches/switch.cpp index 19b2dc1c1..4a2ca8962 100644 --- a/TombEngine/Objects/Generic/Switches/switch.cpp +++ b/TombEngine/Objects/Generic/Switches/switch.cpp @@ -3,7 +3,6 @@ #include "Game/animation.h" #include "Game/camera.h" -#include "Game/collision/sphere.h" #include "Game/control/lot.h" #include "Game/effects/debris.h" #include "Game/items.h" diff --git a/TombEngine/Objects/Generic/Switches/turn_switch.cpp b/TombEngine/Objects/Generic/Switches/turn_switch.cpp index 662058a76..050168886 100644 --- a/TombEngine/Objects/Generic/Switches/turn_switch.cpp +++ b/TombEngine/Objects/Generic/Switches/turn_switch.cpp @@ -128,6 +128,7 @@ namespace TEN::Entities::Switches UseForcedFixedCamera = true; ForcedFixedCamera.y = switchItem->Pose.Position.y - 2048; ForcedFixedCamera.RoomNumber = switchItem->RoomNumber; + Camera.DisableInterpolation = true; AddActiveItem(itemNumber); @@ -176,6 +177,8 @@ namespace TEN::Entities::Switches if (switchItem->Animation.AnimNumber == Objects[switchItem->ObjectNumber].animIndex + 2) { switchItem->Pose.Orientation.y += ANGLE(90.0f); + switchItem->DisableInterpolation = true; + if (IsHeld(In::Action)) { laraItem->Animation.AnimNumber = LA_TURNSWITCH_PUSH_CLOCKWISE_START; @@ -187,9 +190,12 @@ namespace TEN::Entities::Switches } if (laraItem->Animation.AnimNumber == LA_TURNSWITCH_PUSH_CLOCKWISE_END && - laraItem->Animation.FrameNumber == GetAnimData(laraItem).frameEnd && + laraItem->Animation.FrameNumber == GetAnimData(laraItem).frameEnd && !switchItem->ItemFlags[1]) + { switchItem->ItemFlags[1] = 1; + switchItem->DisableInterpolation = true; + } if ((laraItem->Animation.FrameNumber >= GetAnimData(*laraItem, LA_TURNSWITCH_PUSH_CLOCKWISE_START).frameBase && laraItem->Animation.FrameNumber <= GetAnimData(*laraItem, LA_TURNSWITCH_PUSH_CLOCKWISE_START).frameBase + 43) || @@ -204,6 +210,8 @@ namespace TEN::Entities::Switches if (switchItem->Animation.AnimNumber == Objects[ID_TURN_SWITCH].animIndex + 6) { switchItem->Pose.Orientation.y -= ANGLE(90.0f); + switchItem->DisableInterpolation = true; + if (IsHeld(In::Action)) { SetAnimation(*laraItem, LA_TURNSWITCH_PUSH_COUNTER_CLOCKWISE_START); @@ -216,6 +224,7 @@ namespace TEN::Entities::Switches !switchItem->ItemFlags[1]) { switchItem->ItemFlags[1] = 1; + switchItem->DisableInterpolation = true; } if ((laraItem->Animation.FrameNumber >= GetAnimData(*laraItem, LA_TURNSWITCH_PUSH_COUNTER_CLOCKWISE_START).frameBase && @@ -242,6 +251,7 @@ namespace TEN::Entities::Switches Lara.Control.HandStatus = HandStatus::Free; UseForcedFixedCamera = 0; + Camera.DisableInterpolation = true; switchItem->ItemFlags[1] = 2; } } diff --git a/TombEngine/Objects/Generic/Traps/crumblingPlatform.cpp b/TombEngine/Objects/Generic/Traps/crumblingPlatform.cpp index 4af04c235..9a1a87e89 100644 --- a/TombEngine/Objects/Generic/Traps/crumblingPlatform.cpp +++ b/TombEngine/Objects/Generic/Traps/crumblingPlatform.cpp @@ -3,6 +3,7 @@ #include "Game/collision/collide_item.h" #include "Game/collision/floordata.h" +#include "Game/collision/Point.h" #include "Game/Lara/lara.h" #include "Game/Lara/lara_helpers.h" #include "Game/setup.h" @@ -11,6 +12,7 @@ #include "Specific/level.h" using namespace TEN::Collision::Floordata; +using namespace TEN::Collision::Point; using namespace TEN::Entities::Generic; // NOTES: @@ -139,8 +141,8 @@ namespace TEN::Entities::Traps item.Animation.TargetState = CRUMBLING_PLATFORM_STATE_FALL; item.ItemFlags[1] = CRUMBLING_PLATFORM_VELOCITY_MIN; - auto pointColl = GetCollision(item); - pointColl.Block->RemoveBridge(itemNumber); + auto pointColl = GetPointCollision(item); + pointColl.GetSector().RemoveBridge(itemNumber); } } @@ -152,8 +154,8 @@ namespace TEN::Entities::Traps // Get point collision. auto box = GameBoundingBox(&item); - auto pointColl = GetCollision(item); - int relFloorHeight = (item.Pose.Position.y - pointColl.Position.Floor) - box.Y1 ; + auto pointColl = GetPointCollision(item); + int relFloorHeight = (item.Pose.Position.y - pointColl.GetFloorHeight()) - box.Y1 ; // Airborne. if (relFloorHeight <= fallVel) @@ -168,11 +170,11 @@ namespace TEN::Entities::Traps else { item.Animation.TargetState = CRUMBLING_PLATFORM_STATE_LAND; - item.Pose.Position.y = pointColl.Position.Floor; + item.Pose.Position.y = pointColl.GetFloorHeight(); } // Update room number. - int probedRoomNumber = pointColl.RoomNumber; + int probedRoomNumber = pointColl.GetRoomNumber(); if (item.RoomNumber != probedRoomNumber) ItemNewRoom(itemNumber, probedRoomNumber); } diff --git a/TombEngine/Objects/Generic/Traps/dart_emitter.cpp b/TombEngine/Objects/Generic/Traps/dart_emitter.cpp index eb7fa126e..f786b659c 100644 --- a/TombEngine/Objects/Generic/Traps/dart_emitter.cpp +++ b/TombEngine/Objects/Generic/Traps/dart_emitter.cpp @@ -9,188 +9,225 @@ #include "Sound/sound.h" #include "Specific/level.h" +// NOTES: +// ItemFlags[0]: Delay between darts in frame time. +// ItemFlags[1]: Timer in frame time. + namespace TEN::Entities::Traps { - constexpr auto DART_DEFAULT_DAMAGE = 25; + constexpr auto DART_DEFAULT_HARM_DAMAGE = 25; + constexpr auto DART_DEFAULT_VELOCITY = BLOCK(0.25f); + constexpr auto DART_DEFAULT_DELAY = 32; + constexpr auto DART_DEFAULT_HOMING_DELAY = 24; - void DartControl(short itemNumber) + void InitializeDartEmitter(short itemNumber) { - auto* item = &g_Level.Items[itemNumber]; + auto& item = g_Level.Items[itemNumber]; - if (item->TouchBits.TestAny()) + auto& delay = item.ItemFlags[0]; + + if (item.ObjectNumber == ID_HOMING_DART_EMITTER) { - if (item->TriggerFlags < 0) + if (delay == 0) + delay = DART_DEFAULT_HOMING_DELAY; + } + else + { + if (delay == 0) + delay = DART_DEFAULT_DELAY; + } + } + + void ControlDart(short itemNumber) + { + auto& item = g_Level.Items[itemNumber]; + + if (item.TouchBits.TestAny()) + { + if (item.TriggerFlags < 0) Lara.Status.Poison += 1; - DoDamage(LaraItem, item->TriggerFlags ? abs(item->TriggerFlags) : DART_DEFAULT_DAMAGE); - DoBloodSplat(item->Pose.Position.x, item->Pose.Position.y, item->Pose.Position.z, (GetRandomControl() & 3) + 4, LaraItem->Pose.Orientation.y, LaraItem->RoomNumber); + DoDamage(LaraItem, item.TriggerFlags ? abs(item.TriggerFlags) : DART_DEFAULT_HARM_DAMAGE); + DoBloodSplat(item.Pose.Position.x, item.Pose.Position.y, item.Pose.Position.z, (GetRandomControl() & 3) + 4, LaraItem->Pose.Orientation.y, LaraItem->RoomNumber); KillItem(itemNumber); } else { - int oldX = item->Pose.Position.x; - int oldZ = item->Pose.Position.z; + auto prevPos = item.Pose.Position; - int velocity = item->Animation.Velocity.z * phd_cos(item->Pose.Orientation.x); + float vel = item.Animation.Velocity.z * phd_cos(item.Pose.Orientation.x); - item->Pose.Position.x += velocity * phd_sin(item->Pose.Orientation.y); - item->Pose.Position.y -= item->Animation.Velocity.z * phd_sin(item->Pose.Orientation.x); - item->Pose.Position.z += velocity * phd_cos(item->Pose.Orientation.y); + item.Pose.Position.x += vel * phd_sin(item.Pose.Orientation.y); + item.Pose.Position.y -= item.Animation.Velocity.z * phd_sin(item.Pose.Orientation.x); + item.Pose.Position.z += vel * phd_cos(item.Pose.Orientation.y); - short roomNumber = item->RoomNumber; - FloorInfo* floor = GetFloor(item->Pose.Position.x, item->Pose.Position.y, item->Pose.Position.z, &roomNumber); + short roomNumber = item.RoomNumber; + auto* floor = GetFloor(item.Pose.Position.x, item.Pose.Position.y, item.Pose.Position.z, &roomNumber); - if (item->RoomNumber != roomNumber) + if (item.RoomNumber != roomNumber) ItemNewRoom(itemNumber, roomNumber); - int height = GetFloorHeight(floor, item->Pose.Position.x, item->Pose.Position.y, item->Pose.Position.z); - item->Floor = height; + int height = GetFloorHeight(floor, item.Pose.Position.x, item.Pose.Position.y, item.Pose.Position.z); + item.Floor = height; - if (item->Pose.Position.y >= height) + if (item.Pose.Position.y >= height) { for (int i = 0; i < 4; i++) - TriggerDartSmoke(oldX, item->Pose.Position.y, oldZ, 0, 0, true); + SpawnDartSmoke(Vector3(prevPos.x, item.Pose.Position.y, prevPos.z), Vector3::Zero, true); KillItem(itemNumber); } } } - void DartEmitterControl(short itemNumber) + void ControlDartEmitter(short itemNumber) { - ItemInfo* item = &g_Level.Items[itemNumber]; + auto& item = g_Level.Items[itemNumber]; - if (item->Active) + if (TriggerActive(&item)) { - if (item->Timer > 0) + if (item.Active) { - item->Timer--; + auto& delay = item.ItemFlags[0]; + auto& timer = item.ItemFlags[1]; + + if (timer > 0) + { + timer--; + return; + } + else + { + timer = delay; + } + } + + int dartItemNumber = CreateItem(); + if (dartItemNumber == NO_VALUE) return; - } - else - { - item->Timer = 24; - } - } - int dartItemNumber = CreateItem(); - if (dartItemNumber == NO_VALUE) - return; + auto& dartItem = g_Level.Items[dartItemNumber]; + dartItem.ObjectNumber = ID_DARTS; + dartItem.Pose.Position = item.Pose.Position + Vector3i(0, -CLICK(0.9f), 0); + dartItem.Pose.Orientation = item.Pose.Orientation + EulerAngles(0, ANGLE(180.0f), 0); + dartItem.RoomNumber = item.RoomNumber; - ItemInfo* dartItem = &g_Level.Items[dartItemNumber]; + InitializeItem(dartItemNumber); - dartItem->ObjectNumber = ID_DARTS; - dartItem->RoomNumber = item->RoomNumber; + dartItem.Animation.Velocity.z = DART_DEFAULT_VELOCITY; + dartItem.TriggerFlags = item.TriggerFlags; + dartItem.Model.Color = item.Model.Color; - dartItem->Pose.Position.x = item->Pose.Position.x; - dartItem->Pose.Position.y = item->Pose.Position.y - CLICK(0.9); - dartItem->Pose.Position.z = item->Pose.Position.z; + for (int i = 0; i < 4; i++) + SpawnDartSmoke(dartItem.Pose.Position.ToVector3(), Vector3::Zero, false); - InitializeItem(dartItemNumber); + AddActiveItem(dartItemNumber); + dartItem.Status = ITEM_ACTIVE; - dartItem->Pose.Orientation.x = item->Pose.Orientation.x - ANGLE(180.0f); - dartItem->Pose.Orientation.y = item->Pose.Orientation.y; - dartItem->Pose.Orientation.z = item->Pose.Orientation.z; - dartItem->Animation.Velocity.z = BLOCK(0.25f); - dartItem->TriggerFlags = item->TriggerFlags; - dartItem->Model.Color = item->Model.Color; - - for (int i = 0; i < 4; i++) - TriggerDartSmoke(dartItem->Pose.Position.x, dartItem->Pose.Position.y, dartItem->Pose.Position.z, 0, 0, false); - - AddActiveItem(dartItemNumber); - dartItem->Status = ITEM_ACTIVE; - - SoundEffect(SFX_TR4_DART_SPIT, &dartItem->Pose); - } - - void TriggerDartSmoke(int x, int y, int z, int xv, int zv, bool hit) - { - int dx = LaraItem->Pose.Position.x - x; - int dz = LaraItem->Pose.Position.z - z; - - if (dx < -16384 || dx > 16384 || dz < -16384 || dz > 16384) - return; - - auto* spark = GetFreeParticle(); - - spark->on = true; - - spark->sR = 16; - spark->sG = 8; - spark->sB = 4; - - spark->dR = 64; - spark->dG = 48; - spark->dB = 32; - - spark->colFadeSpeed = 8; - spark->fadeToBlack = 4; - - spark->blendMode = BlendMode::Additive; - - spark->life = spark->sLife = (GetRandomControl() & 3) + 32; - - spark->x = x + ((GetRandomControl() & 31) - 16); - spark->y = y + ((GetRandomControl() & 31) - 16); - spark->z = z + ((GetRandomControl() & 31) - 16); - - if (hit) - { - spark->xVel = -xv + ((GetRandomControl() & 255) - 128); - spark->yVel = -(GetRandomControl() & 3) - 4; - spark->zVel = -zv + ((GetRandomControl() & 255) - 128); - spark->friction = 3; + SoundEffect(SFX_TR4_DART_SPIT, &dartItem.Pose); } else { - if (xv) - spark->xVel = -xv; - else - spark->xVel = ((GetRandomControl() & 255) - 128); + item.Status = ITEM_NOT_ACTIVE; + RemoveActiveItem(itemNumber, false); + item.Active = false; + } + } - spark->yVel = -(GetRandomControl() & 3) - 4; - if (zv) - spark->zVel = -zv; - else - spark->zVel = ((GetRandomControl() & 255) - 128); + void SpawnDartSmoke(const Vector3& pos, const Vector3& vel, bool isHit) + { + auto& part = *GetFreeParticle(); - spark->friction = 3; + part.on = true; + + part.sR = 16; + part.sG = 8; + part.sB = 4; + + part.dR = 64; + part.dG = 48; + part.dB = 32; + + part.colFadeSpeed = 8; + part.fadeToBlack = 4; + + part.blendMode = BlendMode::Additive; + + part.life = part.sLife = (GetRandomControl() & 3) + 32; + + part.x = pos.x + ((GetRandomControl() & 31) - 16); + part.y = pos.y + ((GetRandomControl() & 31) - 16); + part.z = pos.z + ((GetRandomControl() & 31) - 16); + + if (isHit) + { + part.xVel = -vel.x + ((GetRandomControl() & 255) - 128); + part.yVel = -(GetRandomControl() & 3) - 4; + part.zVel = -vel.z + ((GetRandomControl() & 255) - 128); + part.friction = 3; + } + else + { + if (vel.x != 0.0f) + { + part.xVel = -vel.x; + } + else + { + part.xVel = ((GetRandomControl() & 255) - 128); + } + + part.yVel = -(GetRandomControl() & 3) - 4; + if (vel.z != 0.0f) + { + part.zVel = -vel.z; + } + else + { + part.zVel = ((GetRandomControl() & 255) - 128); + } + + part.friction = 3; } - spark->friction = 3; + part.friction = 3; if (GetRandomControl() & 1) { - spark->flags = SP_EXPDEF | SP_ROTATE | SP_DEF | SP_SCALE; + part.flags = SP_EXPDEF | SP_ROTATE | SP_DEF | SP_SCALE; - spark->rotAng = GetRandomControl() & 0xFFF; + part.rotAng = GetRandomControl() & 0xFFF; if (GetRandomControl() & 1) - spark->rotAdd = -16 - (GetRandomControl() & 0xF); + { + part.rotAdd = -16 - (GetRandomControl() & 0xF); + } else - spark->rotAdd = (GetRandomControl() & 0xF) + 16; + { + part.rotAdd = (GetRandomControl() & 0xF) + 16; + } } else { - spark->flags = SP_EXPDEF | SP_DEF | SP_SCALE; + part.flags = SP_EXPDEF | SP_DEF | SP_SCALE; } - spark->scalar = 1; + part.scalar = 1; int size = (GetRandomControl() & 63) + 72; - if (hit) + if (isHit) { - size >>= 1; - spark->size = spark->sSize = size >> 2; - spark->gravity = spark->maxYvel = 0; + size /= 2; + part.size = + part.sSize = size *= 4; + part.gravity = part.maxYvel = 0; } else { - spark->size = spark->sSize = size >> 4; - spark->gravity = -(GetRandomControl() & 3) - 4; - spark->maxYvel = -(GetRandomControl() & 3) - 4; + part.size = part.sSize = size >> 4; + part.gravity = -(GetRandomControl() & 3) - 4; + part.maxYvel = -(GetRandomControl() & 3) - 4; } - spark->dSize = size; + part.dSize = size; } } diff --git a/TombEngine/Objects/Generic/Traps/dart_emitter.h b/TombEngine/Objects/Generic/Traps/dart_emitter.h index f7bc3244c..4a91cc7bb 100644 --- a/TombEngine/Objects/Generic/Traps/dart_emitter.h +++ b/TombEngine/Objects/Generic/Traps/dart_emitter.h @@ -2,7 +2,9 @@ namespace TEN::Entities::Traps { - void DartControl(short itemNumber); - void DartEmitterControl(short itemNumber); - void TriggerDartSmoke(int x, int y, int z, int xv, int zv, bool hit); + void InitializeDartEmitter(short itemNumber); + void ControlDart(short itemNumber); + void ControlDartEmitter(short itemNumber); + + void SpawnDartSmoke(const Vector3& pos, const Vector3& vel, bool isHit); } diff --git a/TombEngine/Objects/Generic/Traps/falling_block.cpp b/TombEngine/Objects/Generic/Traps/falling_block.cpp index ce6994747..61ed241e1 100644 --- a/TombEngine/Objects/Generic/Traps/falling_block.cpp +++ b/TombEngine/Objects/Generic/Traps/falling_block.cpp @@ -152,12 +152,11 @@ namespace TEN::Entities::Generic ShatterItem.yRot = item->Pose.Orientation.y; ShatterItem.meshIndex = Objects[item->ObjectNumber].meshIndex; ShatterItem.color = item->Model.Color; - ShatterItem.sphere.x = item->Pose.Position.x; - ShatterItem.sphere.y = item->Pose.Position.y - CLICK(1); // So debris won't spawn below floor - ShatterItem.sphere.z = item->Pose.Position.z; + ShatterItem.sphere.Center = item->Pose.Position.ToVector3(); + ShatterItem.sphere.Center.y -= CLICK(1); // Prevent debris from spawning below floor. ShatterItem.bit = 0; ShatterImpactData.impactDirection = Vector3(0, -(float)item->ItemFlags[1] / (float)FALLINGBLOCK_MAX_SPEED, 0); - ShatterImpactData.impactLocation = { (float)ShatterItem.sphere.x, (float)ShatterItem.sphere.y, (float)ShatterItem.sphere.z }; + ShatterImpactData.impactLocation = ShatterItem.sphere.Center; ShatterObject(&ShatterItem, nullptr, 0, item->RoomNumber, false); SoundEffect(SFX_TR4_ROCK_FALL_LAND, &item->Pose); diff --git a/TombEngine/Objects/Generic/generic_objects.cpp b/TombEngine/Objects/Generic/generic_objects.cpp index 7d57948c5..5e09d44ba 100644 --- a/TombEngine/Objects/Generic/generic_objects.cpp +++ b/TombEngine/Objects/Generic/generic_objects.cpp @@ -347,14 +347,15 @@ void StartTraps(ObjectInfo* object) if (object->loaded) { object->collision = ObjectCollision; - object->control = DartControl; + object->control = ControlDart; object->shadowType = ShadowMode::All; } object = &Objects[ID_DART_EMITTER]; if (object->loaded) { - object->control = DartEmitterControl; + object->Initialize = InitializeDartEmitter; + object->control = ControlDartEmitter; object->drawRoutine = nullptr; object->usingDrawAnimatingItem = false; } @@ -362,7 +363,8 @@ void StartTraps(ObjectInfo* object) object = &Objects[ID_HOMING_DART_EMITTER]; if (object->loaded) { - object->control = DartEmitterControl; + object->Initialize = InitializeDartEmitter; + object->control = ControlDartEmitter; object->drawRoutine = nullptr; object->usingDrawAnimatingItem = false; } diff --git a/TombEngine/Objects/Generic/puzzles_keys.cpp b/TombEngine/Objects/Generic/puzzles_keys.cpp index 811896ad1..31733b631 100644 --- a/TombEngine/Objects/Generic/puzzles_keys.cpp +++ b/TombEngine/Objects/Generic/puzzles_keys.cpp @@ -339,6 +339,7 @@ void PuzzleDone(ItemInfo* item, short itemNumber) item->ObjectNumber += GAME_OBJECT_ID{ ID_PUZZLE_DONE1 - ID_PUZZLE_HOLE1 }; item->ItemFlags[5] = (int)ReusableReceptacleState::Done; SetAnimation(item, 0); + item->DisableInterpolation = true; item->ResetModelToDefault(); } else @@ -349,6 +350,7 @@ void PuzzleDone(ItemInfo* item, short itemNumber) item->Animation.ActiveState = GetAnimData(item).ActiveState; item->Animation.TargetState = GetAnimData(item).ActiveState; item->Animation.RequiredState = NO_VALUE; + item->DisableInterpolation = true; item->ResetModelToDefault(); AddActiveItem(itemNumber); diff --git a/TombEngine/Objects/TR1/Entity/Centaur.cpp b/TombEngine/Objects/TR1/Entity/Centaur.cpp new file mode 100644 index 000000000..9d4e99c67 --- /dev/null +++ b/TombEngine/Objects/TR1/Entity/Centaur.cpp @@ -0,0 +1,182 @@ +#include "framework.h" +#include "Objects/TR1/Entity/Centaur.h" + +#include "Game/animation.h" +#include "Game/control/box.h" +#include "Game/collision/collide_item.h" +#include "Game/collision/collide_room.h" +#include "Game/effects/effects.h" +#include "Game/effects/tomb4fx.h" +#include "Game/items.h" +#include "Game/Lara/lara.h" +#include "Game/Lara/lara_one_gun.h" +#include "Game/misc.h" +#include "Game/missile.h" +#include "Game/people.h" +#include "Game/Setup.h" +#include "Math/Math.h" +#include "Sound/sound.h" +#include "Specific/level.h" + +using namespace TEN::Math; + +namespace TEN::Entities::Creatures::TR1 +{ + constexpr auto CENTAUR_REAR_DAMAGE = 200; + constexpr auto CENTAUR_REAR_RANGE = SQUARE(BLOCK(3 / 2.0f)); + constexpr auto CENTAUR_REAR_CHANCE = 1 / 340.0f; + constexpr auto CENTAUR_BOMB_VELOCITY = CLICK(1); + + constexpr auto CENTAUR_TURN_RATE_MAX = ANGLE(4.0f); + + const auto CentaurRocketBite = CreatureBiteInfo(Vector3(11, 415, 41), 13); + const auto CentaurRearBite = CreatureBiteInfo(Vector3(50, 30, 0), 5); + const auto CentaurAttackJoints = std::vector{ 0, 3, 4, 7, 8, 16, 17 }; + + enum CentaurState + { + // No state 0. + CENTAUR_STATE_IDLE = 1, + CENTAUR_PROJECTILE_ATTACK = 2, + CENTAUR_STATE_RUN_FORWARD = 3, + CENTAUR_STATE_AIM = 4, + CENTAUR_STATE_DEATH = 5, + CENTAUR_STATE_WARNING = 6 + }; + + // TODO + enum CentaurAnim + { + CENTAUR_ANIM_DEATH = 8, + }; + + void ControlCentaur(short itemNumber) + { + if (!CreatureActive(itemNumber)) + return; + + auto& item = g_Level.Items[itemNumber]; + auto& creature = *GetCreatureInfo(&item); + + short headingAngle = 0; + auto headOrient = EulerAngles::Identity; + auto torsoOrient = EulerAngles::Identity; + + if (item.HitPoints <= 0) + { + if (item.Animation.ActiveState != CENTAUR_STATE_DEATH) + SetAnimation(item, CENTAUR_ANIM_DEATH); + } + else + { + AI_INFO ai; + CreatureAIInfo(&item, &ai); + + if (ai.ahead) + { + headOrient.x = ai.xAngle; + headOrient.y = ai.angle; + torsoOrient.x = ai.xAngle / 2; + torsoOrient.y = ai.angle / 2; + } + + GetCreatureMood(&item, &ai, true); + CreatureMood(&item, &ai, true); + headingAngle = CreatureTurn(&item, CENTAUR_TURN_RATE_MAX); + + switch (item.Animation.ActiveState) + { + case CENTAUR_STATE_IDLE: + if (item.Animation.RequiredState != NO_VALUE) + { + item.Animation.TargetState = item.Animation.RequiredState; + } + else if (ai.bite && ai.distance < CENTAUR_REAR_RANGE) + { + item.Animation.TargetState = CENTAUR_STATE_RUN_FORWARD; + } + else if (Targetable(&item, &ai)) + { + item.Animation.TargetState = CENTAUR_STATE_AIM; + } + else + { + item.Animation.TargetState = CENTAUR_STATE_RUN_FORWARD; + } + + break; + + case CENTAUR_STATE_RUN_FORWARD: + torsoOrient = EulerAngles::Identity; + + if (ai.bite && ai.distance < CENTAUR_REAR_RANGE) + { + item.Animation.TargetState = CENTAUR_STATE_IDLE; + item.Animation.RequiredState = CENTAUR_STATE_WARNING; + } + else if (Targetable(&item, &ai)) + { + item.Animation.TargetState = CENTAUR_STATE_IDLE; + item.Animation.RequiredState = CENTAUR_STATE_AIM; + } + else if (Random::TestProbability(CENTAUR_REAR_CHANCE)) + { + item.Animation.TargetState = CENTAUR_STATE_IDLE; + item.Animation.RequiredState = CENTAUR_STATE_WARNING; + } + + break; + + case CENTAUR_STATE_AIM: + if (item.Animation.RequiredState != NO_VALUE) + { + item.Animation.TargetState = item.Animation.RequiredState; + } + else if (Targetable(&item, &ai)) + { + item.Animation.TargetState = CENTAUR_PROJECTILE_ATTACK; + } + else + { + item.Animation.TargetState = CENTAUR_STATE_IDLE; + } + + break; + + case CENTAUR_PROJECTILE_ATTACK: + if (item.Animation.RequiredState == NO_VALUE) + { + item.Animation.RequiredState = CENTAUR_STATE_AIM; + CreatureEffect2(&item, CentaurRocketBite, CENTAUR_BOMB_VELOCITY, headOrient.y, BombGun); + } + + break; + + case CENTAUR_STATE_WARNING: + if (item.Animation.RequiredState == NO_VALUE && + item.TouchBits.Test(CentaurAttackJoints)) + { + DoDamage(creature.Enemy, CENTAUR_REAR_DAMAGE); + CreatureEffect(&item, CentaurRearBite, DoBloodSplat); + item.Animation.RequiredState = CENTAUR_STATE_IDLE; + } + + break; + } + } + + CreatureJoint(&item, 0, headOrient.y); + CreatureJoint(&item, 1, -headOrient.x); + CreatureJoint(&item, 2, torsoOrient.y); + CreatureJoint(&item, 3, -torsoOrient.x); + CreatureAnimation(itemNumber, headingAngle, 0); + + if (item.Status == ITEM_DEACTIVATED) + { + SoundEffect(SFX_TR1_ATLANTEAN_DEATH, &item.Pose); + ExplodingDeath(itemNumber, BODY_DO_EXPLOSION); + KillItem(itemNumber); + item.Status = ITEM_DEACTIVATED; + } + } +} diff --git a/TombEngine/Objects/TR1/Entity/tr1_centaur.h b/TombEngine/Objects/TR1/Entity/Centaur.h similarity index 55% rename from TombEngine/Objects/TR1/Entity/tr1_centaur.h rename to TombEngine/Objects/TR1/Entity/Centaur.h index cf34267d2..1aa3e4f3a 100644 --- a/TombEngine/Objects/TR1/Entity/tr1_centaur.h +++ b/TombEngine/Objects/TR1/Entity/Centaur.h @@ -3,7 +3,7 @@ namespace TEN::Entities::Creatures::TR1 { constexpr auto SHARD_VELOCITY = 250; - constexpr auto BOMB_VELOCITY = 220; + constexpr auto BOMB_VELOCITY = 220; - void CentaurControl(short itemNumber); + void ControlCentaur(short itemNumber); } diff --git a/TombEngine/Objects/TR1/Entity/SkateboardKid.cpp b/TombEngine/Objects/TR1/Entity/SkateboardKid.cpp index 7e9160975..038db2a70 100644 --- a/TombEngine/Objects/TR1/Entity/SkateboardKid.cpp +++ b/TombEngine/Objects/TR1/Entity/SkateboardKid.cpp @@ -77,7 +77,7 @@ namespace TEN::Entities::Creatures::TR1 AddActiveItem(skateItemNumber); skate.Active = false; - skate.Status |= ITEM_INVISIBLE; + skate.Status = ITEM_INVISIBLE; item.ItemFlags[0] = skateItemNumber; } diff --git a/TombEngine/Objects/TR1/Entity/WingedMutant.cpp b/TombEngine/Objects/TR1/Entity/WingedMutant.cpp new file mode 100644 index 000000000..d55729dd9 --- /dev/null +++ b/TombEngine/Objects/TR1/Entity/WingedMutant.cpp @@ -0,0 +1,573 @@ +#include "framework.h" +#include "Objects/TR1/Entity/WingedMutant.h" + +#include "Game/collision/collide_room.h" +#include "Game/control/box.h" +#include "Game/control/lot.h" +#include "Game/effects/effects.h" +#include "Game/effects/tomb4fx.h" +#include "Game/itemdata/creature_info.h" +#include "Game/items.h" +#include "Game/misc.h" +#include "Game/missile.h" +#include "Game/people.h" +#include "Math/Math.h" +#include "Sound/sound.h" +#include "Specific/level.h" + +using namespace TEN::Math; + +namespace TEN::Entities::Creatures::TR1 +{ + constexpr auto WINGED_MUTANT_IDLE_JUMP_ATTACK_DAMAGE = 150; + constexpr auto WINGED_MUTANT_RUN_JUMP_ATTACK_DAMAGE = 100; + constexpr auto WINGED_MUTANT_SWIPE_ATTACK_DAMAGE = 200; + + constexpr auto WINGED_MUTANT_WALK_RANGE = SQUARE(BLOCK(4.5f)); + constexpr auto WINGED_MUTANT_SWIPE_ATTACK_RANGE = SQUARE(BLOCK(0.3f)); + constexpr auto WINGED_MUTANT_RUN_JUMP_ATTACK_RANGE = SQUARE(BLOCK(0.65f)); + constexpr auto WINGED_MUTANT_IDLE_JUMP_ATTACK_RANGE = SQUARE(BLOCK(2.5f)); + constexpr auto WINGED_MUTANT_PROJECTILE_ATTACK_RANGE = SQUARE(BLOCK(3.0f)); + constexpr auto WINGED_MUTANT_POSE_RANGE = SQUARE(BLOCK(4.5f)); + + constexpr auto WINGED_MUTANT_POSE_CHANCE = 1 / 400.0f; + constexpr auto WINGED_MUTANT_UNPOSE_CHANCE = 1 / 164.0f; + + constexpr auto WINGED_MUTANT_FLY_VELOCITY = BLOCK(1 / 32.0f); + constexpr auto WINGED_MUTANT_SHARD_VELOCITY = 250; + constexpr auto WINGED_MUTANT_BOMB_VELOCITY = 220; + + constexpr auto WINGED_MUTANT_WALK_TURN_RATE_MAX = ANGLE(2.0f); + constexpr auto WINGED_MUTANT_RUN_TURN_RATE_MAX = ANGLE(6.0f); + + const auto WingedMutantBiteLeftHand = CreatureBiteInfo(Vector3(0, 0, 0), 7); + const auto WingedMutantBiteRightHand = CreatureBiteInfo(Vector3(0, 0, 0), 10); + const auto WingedMutantRocketBite = CreatureBiteInfo(Vector3(0, 200, 20), 6); + const auto WingedMutantShardBite = CreatureBiteInfo(Vector3(0, 200, 20), 9); + const auto WingedMutantHeadJoints = std::vector{ 3 }; + const auto WingedMutantHandsJoints = std::vector{ 7, 10 }; + const auto WingedMutantWingsJoints = std::vector{ 15, 16, 17, 18, 19, 20 }; + + enum WingedMutantState + { + // No state 0. + WMUTANT_STATE_IDLE = 1, + WMUTANT_STATE_WALK_FORWARD = 2, + WMUTANT_STATE_RUN_FORWARD = 3, + WMUTANT_STATE_IDLE_JUMP_ATTACK = 4, + WMUTANT_STATE_DEATH = 5, + WMUTANT_STATE_POSE = 6, + WMUTANT_STATE_RUN_JUMP_ATTACK = 7, + WMUTANT_STATE_SWIPE_ATTACK = 8, + WMUTANT_STATE_AIM_DART = 9, + WMUTANT_STATE_AIM_BOMB = 10, + WMUTANT_STATE_SHOOT = 11, + WMUTANT_STATE_INACTIVE = 12, + WMUTANT_STATE_FLY = 13, + }; + + enum WingedMutantAnim + { + WMUTANT_ANIM_INACTIVE = 0, + WMUTANT_ANIM_INACTIVE_TO_IDLE = 1, + WMUTANT_ANIM_IDLE = 2, + WMUTANT_ANIM_IDLE_TO_RUN = 3, + WMUTANT_ANIM_RUN_FORWARD = 4, + WMUTANT_ANIM_IDLE_JUMP_ATTACK_START = 5, + WMUTANT_ANIM_IDLE_JUMP_ATTACK_END = 6, + WMUTANT_ANIM_IDLE_TO_POSE = 7, + WMUTANT_ANIM_POSE = 8, + WMUTANT_ANIM_POSE_TO_IDLE = 9, + WMUTANT_ANIM_POSE_TO_WALK_FORWARD = 10, + WMUTANT_ANIM_WALK_FORWARD = 11, + WMUTANT_ANIM_WALK_FORWARD_TO_IDLE = 12, + WMUTANT_ANIM_WALK_FORWARD_TO_POSE = 13, + WMUTANT_ANIM_RUN_JUMP_ATTACK = 14, + WMUTANT_ANIM_IDLE_TO_AIM_1 = 15, + WMUTANT_ANIM_AIM_DART = 16, + WMUTANT_ANIM_SHOOT_DART = 17, + WMUTANT_ANIM_AIM_DART_TO_IDLE = 18, + WMUTANT_ANIM_IDLE_TO_AIM_BOMB = 19, + WMUTANT_ANIM_SHOOT_BOMB = 20, + WMUTANT_ANIM_RUN_FORWARD_TO_IDLE = 21, + WMUTANT_ANIM_AIM_BOMB_TO_IDLE = 22, + WMUTANT_ANIM_IDLE_TO_FLY = 23, + WMUTANT_ANIM_FLY = 24, + WMUTANT_ANIM_FLY_TO_IDLE = 25, + WMUTANT_ANIM_SWIPE_ATTACK = 26 + }; + + enum WingedMutantPathFinding + { + WMUTANT_PATH_GROUND = 1, + WMUTANT_PATH_AERIAL = 2 + }; + + // NOTE: Originally, winged mutants did not have OCBs. -- TokyoSU 5/8/2022 + enum WingedMutantOcb + { + WMUTANT_OCB_START_AERIAL = (1 << 0), + WMUTANT_OCB_START_INACTIVE = (1 << 1), + WMUTANT_OCB_START_POSE = (1 << 2), + WMUTANT_OCB_NO_WINGS = (1 << 3), + WMUTANT_OCB_DISABLE_DART_WEAPON = (1 << 4), + WMUTANT_OCB_DISABLE_BOMB_WEAPON = (1 << 5) + }; + + enum WingedMutantProjectileType + { + WMUTANT_PROJ_NONE, + WMUTANT_PROJ_DART, + WMUTANT_PROJ_BOMB + }; + + enum WingedMutantConfig + { + WMUTANT_CONF_CAN_FLY, + WMUTANT_CONF_PATHFINDING_MODE, + WMUTANT_CONF_PROJECTILE_MODE, + WMUTANT_CONF_NO_WINGS, + WMUTANT_CONF_DISABLE_DART_WEAPON, + WMUTANT_CONF_DISABLE_BOMB_WEAPON + }; + + static void SwitchPathfinding(ItemInfo& item, WingedMutantPathFinding path) + { + auto& creature = *GetCreatureInfo(&item); + + switch (path) + { + case WMUTANT_PATH_GROUND: + creature.LOT.Step = CLICK(1); + creature.LOT.Drop = -CLICK(1); + creature.LOT.Fly = NO_FLYING; + creature.LOT.Zone = ZoneType::Basic; + break; + + case WMUTANT_PATH_AERIAL: + creature.LOT.Step = BLOCK(20); + creature.LOT.Drop = -BLOCK(20); + creature.LOT.Fly = WINGED_MUTANT_FLY_VELOCITY; + creature.LOT.Zone = ZoneType::Flyer; + break; + } + } + + static WingedMutantProjectileType CanTargetPlayer(ItemInfo& item, AI_INFO& ai) + { + if (Targetable(&item, &ai) && + (ai.zoneNumber != ai.enemyZone || ai.distance > WINGED_MUTANT_PROJECTILE_ATTACK_RANGE)) + { + if ((ai.angle > 0 && ai.angle < ANGLE(45.0f)) && + item.TestFlagField(WMUTANT_CONF_DISABLE_DART_WEAPON, false)) + { + return WMUTANT_PROJ_DART; + } + else if ((ai.angle < 0 && ai.angle > ANGLE(-45.0f)) && + item.TestFlagField(WMUTANT_CONF_DISABLE_BOMB_WEAPON, false)) + { + return WMUTANT_PROJ_BOMB; + } + } + + // Cannot be targeted. + return WMUTANT_PROJ_NONE; + } + + static void InitializeWingedMutantOCB(ItemInfo& item) + { + if (item.TestOcb(WMUTANT_OCB_START_AERIAL)) + { + SwitchPathfinding(item, WMUTANT_PATH_AERIAL); + SetAnimation(item, WMUTANT_ANIM_FLY); + item.SetFlagField(WMUTANT_CONF_PATHFINDING_MODE, WMUTANT_PATH_AERIAL); + } + else if (item.TestOcb(WMUTANT_OCB_START_INACTIVE)) + { + SwitchPathfinding(item, WMUTANT_PATH_GROUND); + SetAnimation(item, WMUTANT_ANIM_INACTIVE); + item.SetFlagField(WMUTANT_CONF_PATHFINDING_MODE, WMUTANT_PATH_GROUND); + } + else if (item.TestOcb(WMUTANT_OCB_START_POSE)) + { + SwitchPathfinding(item, WMUTANT_PATH_GROUND); + SetAnimation(item, WMUTANT_ANIM_INACTIVE); + item.SetFlagField(WMUTANT_CONF_PATHFINDING_MODE, WMUTANT_PATH_GROUND); + } + + // Remove unnecessary OCBs. + if (item.TestOcb(WMUTANT_OCB_START_AERIAL)) + item.RemoveOcb(WMUTANT_OCB_START_AERIAL); + + if (item.TestOcb(WMUTANT_OCB_START_INACTIVE)) + item.RemoveOcb(WMUTANT_OCB_START_INACTIVE); + + if (item.TestOcb(WMUTANT_OCB_START_POSE)) + item.RemoveOcb(WMUTANT_OCB_START_POSE); + } + + void InitializeWingedMutant(short itemNumber) + { + auto& item = g_Level.Items[itemNumber]; + + InitializeCreature(itemNumber); + item.SetFlagField(WMUTANT_CONF_PATHFINDING_MODE, WMUTANT_PATH_GROUND); + item.SetFlagField(WMUTANT_CONF_PROJECTILE_MODE, WMUTANT_PROJ_NONE); + + if (item.TestOcb(WMUTANT_OCB_NO_WINGS)) + { + item.SetFlagField(WMUTANT_CONF_CAN_FLY, false); + item.MeshBits.Clear(WingedMutantWingsJoints); + } + else + { + item.SetFlagField(WMUTANT_CONF_CAN_FLY, true); + } + + if (item.TestOcb(WMUTANT_OCB_DISABLE_BOMB_WEAPON)) + item.SetFlagField(WMUTANT_CONF_DISABLE_BOMB_WEAPON, true); + + if (item.TestOcb(WMUTANT_OCB_DISABLE_DART_WEAPON)) + item.SetFlagField(WMUTANT_CONF_DISABLE_DART_WEAPON, true); + + if (item.TestOcb(WMUTANT_OCB_DISABLE_BOMB_WEAPON)) + item.RemoveOcb(WMUTANT_OCB_DISABLE_BOMB_WEAPON); + + if (item.TestOcb(WMUTANT_OCB_DISABLE_DART_WEAPON)) + item.RemoveOcb(WMUTANT_OCB_DISABLE_DART_WEAPON); + + if (item.TestOcb(WMUTANT_OCB_NO_WINGS)) + item.RemoveOcb(WMUTANT_OCB_NO_WINGS); + } + + void ControlWingedMutant(short itemNumber) + { + if (!CreatureActive(itemNumber)) + return; + + auto& item = g_Level.Items[itemNumber]; + auto& creature = *GetCreatureInfo(&item); + + short headingAngle = 0; + short headYOrient = 0; + short torsoYOrient = 0; // Only when shooting. + + bool enableFlying = item.TestFlagField(WMUTANT_CONF_CAN_FLY, true); + bool isFlying = item.TestFlagField(WMUTANT_CONF_PATHFINDING_MODE, WMUTANT_PATH_AERIAL); + + InitializeWingedMutantOCB(item); + + if (item.HitPoints <= 0) + { + CreatureDie(itemNumber, true, BODY_DO_EXPLOSION | BODY_PART_EXPLODE | BODY_NO_SMOKE | BODY_NO_SHATTER_EFFECT); + + auto pos = item.Pose; + pos.Position.y -= CLICK(3); + TriggerExplosionSparks(pos.Position.x, pos.Position.y, pos.Position.z, 3, -2, 0, item.RoomNumber); + TriggerExplosionSparks(pos.Position.x, pos.Position.y, pos.Position.z, 3, -1, 0, item.RoomNumber); + TriggerShockwave(&pos, 48, 304, (GetRandomControl() & 0x1F) + 112, 128, 32, 32, 32, EulerAngles(2048, 0.0f, 0.0f), 0, true, false, false, (int)ShockwaveStyle::Normal); + + SoundEffect(SFX_TR1_ATLANTEAN_EXPLODE, &item.Pose); + return; + } + else + { + AI_INFO ai; + SwitchPathfinding(item, WMUTANT_PATH_GROUND); + CreatureAIInfo(&item, &ai); + + bool isSameZoneInGroundMode = (ai.zoneNumber == ai.enemyZone); + auto projectileType = CanTargetPlayer(item, ai); + + if (enableFlying && item.Animation.ActiveState == WMUTANT_STATE_FLY) + { + SwitchPathfinding(item, WMUTANT_PATH_AERIAL); + CreatureAIInfo(&item, &ai); + } + + if (ai.ahead) + { + headYOrient = ai.angle; + } + else + { + headYOrient = 0; + torsoYOrient = 0; + } + + GetCreatureMood(&item, &ai, isFlying); + CreatureMood(&item, &ai, isFlying); + headingAngle = CreatureTurn(&item, creature.MaxTurn); + + switch (item.Animation.ActiveState) + { + case WMUTANT_STATE_INACTIVE: + creature.MaxTurn = 0; + creature.Flags = 0; + + if (TargetVisible(&item, &ai) || creature.HurtByLara) + item.Animation.TargetState = WMUTANT_STATE_IDLE; + + break; + + case WMUTANT_STATE_IDLE: + item.SetFlagField(WMUTANT_CONF_PATHFINDING_MODE, WMUTANT_PROJ_NONE); + creature.MaxTurn = 0; + creature.Flags = 0; + torsoYOrient = 0; + + if (enableFlying && !isSameZoneInGroundMode) + { + item.Animation.TargetState = WMUTANT_STATE_FLY; + item.SetFlagField(WMUTANT_CONF_PATHFINDING_MODE, WMUTANT_PATH_AERIAL); + } + else if (item.TouchBits.Test(WingedMutantHeadJoints)) + { + item.Animation.TargetState = WMUTANT_STATE_SWIPE_ATTACK; + } + else if (ai.bite && ai.distance < WINGED_MUTANT_IDLE_JUMP_ATTACK_RANGE) + { + item.Animation.TargetState = WMUTANT_STATE_IDLE_JUMP_ATTACK; + } + else if (ai.bite && ai.distance < WINGED_MUTANT_SWIPE_ATTACK_RANGE) + { + item.Animation.TargetState = WMUTANT_STATE_SWIPE_ATTACK; + } + else if (projectileType == WMUTANT_PROJ_DART) + { + item.Animation.TargetState = WMUTANT_STATE_AIM_DART; + } + else if (projectileType == WMUTANT_PROJ_BOMB) + { + item.Animation.TargetState = WMUTANT_STATE_AIM_BOMB; + } + else if (creature.Mood == MoodType::Bored || + (creature.Mood == MoodType::Stalk && ai.distance < WINGED_MUTANT_POSE_RANGE)) + { + item.Animation.TargetState = WMUTANT_STATE_POSE; + } + else + { + item.Animation.TargetState = WMUTANT_STATE_RUN_FORWARD; + } + + break; + + case WMUTANT_STATE_POSE: + creature.Flags = 0; + creature.MaxTurn = 0; + headYOrient = 0; // NOTE: Pose has animation for head. + + if (projectileType != WMUTANT_PROJ_NONE || (isFlying && enableFlying)) + { + item.Animation.TargetState = WMUTANT_STATE_IDLE; + } + else if (creature.Mood == MoodType::Stalk) + { + if (ai.distance < WINGED_MUTANT_WALK_RANGE) + { + if (isSameZoneInGroundMode || + Random::TestProbability(WINGED_MUTANT_UNPOSE_CHANCE)) + { + item.Animation.TargetState = WMUTANT_STATE_WALK_FORWARD; + } + } + else + { + item.Animation.TargetState = WMUTANT_STATE_IDLE; + } + } + else if (creature.Mood == MoodType::Bored && Random::TestProbability(WINGED_MUTANT_UNPOSE_CHANCE)) + { + item.Animation.TargetState = WMUTANT_STATE_WALK_FORWARD; + } + else if (creature.Mood == MoodType::Attack || + creature.Mood == MoodType::Escape) + { + item.Animation.TargetState = WMUTANT_STATE_IDLE; + } + + break; + + case WMUTANT_STATE_WALK_FORWARD: + creature.MaxTurn = WINGED_MUTANT_WALK_TURN_RATE_MAX; + creature.Flags = 0; + + if (projectileType != WMUTANT_PROJ_NONE || (isFlying && enableFlying)) + { + item.Animation.TargetState = WMUTANT_STATE_IDLE; + } + else if (creature.Mood == MoodType::Attack || creature.Mood == MoodType::Escape) + { + item.Animation.TargetState = WMUTANT_STATE_IDLE; + } + else if (creature.Mood == MoodType::Bored || + (creature.Mood == MoodType::Stalk && !isSameZoneInGroundMode)) + { + if (Random::TestProbability(WINGED_MUTANT_POSE_CHANCE)) + item.Animation.TargetState = WMUTANT_STATE_POSE; + } + else if (creature.Mood == MoodType::Stalk && + ai.distance > WINGED_MUTANT_WALK_RANGE) + { + item.Animation.TargetState = WMUTANT_STATE_IDLE; + } + + break; + + case WMUTANT_STATE_RUN_FORWARD: + creature.MaxTurn = WINGED_MUTANT_RUN_TURN_RATE_MAX; + creature.Flags = 0; + + if (enableFlying && !isSameZoneInGroundMode) + { + item.Animation.TargetState = WMUTANT_STATE_IDLE; + } + else if (projectileType != WMUTANT_PROJ_NONE) + { + item.Animation.TargetState = WMUTANT_STATE_IDLE; + } + else if (item.TouchBits.Test(WingedMutantHeadJoints)) + { + item.Animation.TargetState = WMUTANT_STATE_IDLE; + } + else if (ai.bite && ai.distance < WINGED_MUTANT_RUN_JUMP_ATTACK_RANGE) + { + item.Animation.TargetState = WMUTANT_STATE_RUN_JUMP_ATTACK; + } + else if (ai.bite && ai.distance < WINGED_MUTANT_SWIPE_ATTACK_RANGE) + { + item.Animation.TargetState = WMUTANT_STATE_SWIPE_ATTACK; + } + else if (ai.ahead && ai.distance < WINGED_MUTANT_SWIPE_ATTACK_RANGE) + { + item.Animation.TargetState = WMUTANT_STATE_SWIPE_ATTACK; + } + else if (creature.Mood == MoodType::Bored || + (creature.Mood == MoodType::Stalk && ai.distance < WINGED_MUTANT_POSE_RANGE)) + { + item.Animation.TargetState = WMUTANT_STATE_IDLE; + } + + break; + + case WMUTANT_STATE_IDLE_JUMP_ATTACK: + if (item.Animation.RequiredState == NO_VALUE && + (item.TouchBits.Test(WingedMutantHandsJoints) || item.TouchBits.Test(WingedMutantHeadJoints)) && creature.Flags == 0) + { + DoDamage(creature.Enemy, WINGED_MUTANT_IDLE_JUMP_ATTACK_DAMAGE / 2); + CreatureEffect(&item, WingedMutantBiteLeftHand, DoBloodSplat); + + DoDamage(creature.Enemy, WINGED_MUTANT_IDLE_JUMP_ATTACK_DAMAGE / 2); + CreatureEffect(&item, WingedMutantBiteRightHand, DoBloodSplat); + + item.Animation.TargetState = WMUTANT_STATE_IDLE; + creature.Flags = 1; + } + + break; + + case WMUTANT_STATE_RUN_JUMP_ATTACK: + if (item.Animation.RequiredState == NO_VALUE && + (item.TouchBits.Test(WingedMutantHandsJoints) || item.TouchBits.Test(WingedMutantHeadJoints)) && creature.Flags == 0) + { + DoDamage(creature.Enemy, WINGED_MUTANT_RUN_JUMP_ATTACK_DAMAGE / 2); + CreatureEffect(&item, WingedMutantBiteLeftHand, DoBloodSplat); + + DoDamage(creature.Enemy, WINGED_MUTANT_RUN_JUMP_ATTACK_DAMAGE / 2); + CreatureEffect(&item, WingedMutantBiteRightHand, DoBloodSplat); + + item.Animation.TargetState = WMUTANT_STATE_RUN_FORWARD; + creature.Flags = 1; + } + + break; + + case WMUTANT_STATE_SWIPE_ATTACK: + if (item.Animation.RequiredState == NO_VALUE && + item.TouchBits.Test(WingedMutantHandsJoints) && creature.Flags == 0) + { + DoDamage(creature.Enemy, WINGED_MUTANT_SWIPE_ATTACK_DAMAGE / 2); + CreatureEffect(&item, WingedMutantBiteLeftHand, DoBloodSplat); + + DoDamage(creature.Enemy, WINGED_MUTANT_SWIPE_ATTACK_DAMAGE / 2); + CreatureEffect(&item, WingedMutantBiteRightHand, DoBloodSplat); + + item.Animation.TargetState = WMUTANT_STATE_IDLE; + creature.Flags = 1; + } + + break; + + case WMUTANT_STATE_AIM_DART: + item.SetFlagField(WMUTANT_CONF_PROJECTILE_MODE, WMUTANT_PROJ_DART); + creature.MaxTurn = 0; + creature.Flags = 0; + torsoYOrient = ai.angle / 2; + + if (projectileType == WMUTANT_PROJ_DART) + { + item.Animation.TargetState = WMUTANT_STATE_SHOOT; + } + else + { + item.Animation.TargetState = WMUTANT_STATE_IDLE; + } + + break; + + case WMUTANT_STATE_AIM_BOMB: + item.SetFlagField(WMUTANT_CONF_PROJECTILE_MODE, WMUTANT_PROJ_BOMB); + creature.MaxTurn = 0; + creature.Flags = 0; + torsoYOrient = ai.angle / 2; + + if (projectileType == WMUTANT_PROJ_BOMB) + { + item.Animation.TargetState = WMUTANT_STATE_SHOOT; + } + else + { + item.Animation.TargetState = WMUTANT_STATE_IDLE; + } + + break; + + case WMUTANT_STATE_SHOOT: + creature.MaxTurn = 0; + torsoYOrient = ai.angle / 2; + + if (creature.Flags == 0) + { + if (projectileType == WMUTANT_PROJ_DART) + { + CreatureEffect2(&item, WingedMutantShardBite, WINGED_MUTANT_SHARD_VELOCITY, torsoYOrient, ShardGun); + } + else if (projectileType == WMUTANT_PROJ_BOMB) + { + CreatureEffect2(&item, WingedMutantRocketBite, WINGED_MUTANT_BOMB_VELOCITY, torsoYOrient, BombGun); + } + + creature.Flags = 1; + } + + item.SetFlagField(WMUTANT_CONF_PROJECTILE_MODE, WMUTANT_PROJ_NONE); + break; + + case WMUTANT_STATE_FLY: + if (creature.Mood != MoodType::Escape && isSameZoneInGroundMode) + { + item.Animation.TargetState = WMUTANT_STATE_IDLE; // Switch to ground mode. + item.Pose.Position.y = item.Floor; + item.SetFlagField(WMUTANT_CONF_PATHFINDING_MODE, WMUTANT_PATH_GROUND); + } + + break; + } + } + + CreatureJoint(&item, 0, torsoYOrient); + CreatureJoint(&item, 1, headYOrient); + CreatureAnimation(itemNumber, headingAngle, 0); + } +} diff --git a/TombEngine/Objects/TR1/Entity/tr1_winged_mutant.h b/TombEngine/Objects/TR1/Entity/WingedMutant.h similarity index 69% rename from TombEngine/Objects/TR1/Entity/tr1_winged_mutant.h rename to TombEngine/Objects/TR1/Entity/WingedMutant.h index 96eefbf9d..9ed5600f7 100644 --- a/TombEngine/Objects/TR1/Entity/tr1_winged_mutant.h +++ b/TombEngine/Objects/TR1/Entity/WingedMutant.h @@ -3,6 +3,5 @@ namespace TEN::Entities::Creatures::TR1 { void InitializeWingedMutant(short itemNumber); - void WingedMutantControl(short itemNumber); + void ControlWingedMutant(short itemNumber); } - diff --git a/TombEngine/Objects/TR1/Entity/tr1_bear.cpp b/TombEngine/Objects/TR1/Entity/tr1_bear.cpp index 241a8102a..6ce1bc0c7 100644 --- a/TombEngine/Objects/TR1/Entity/tr1_bear.cpp +++ b/TombEngine/Objects/TR1/Entity/tr1_bear.cpp @@ -2,6 +2,7 @@ #include "Objects/TR1/Entity/tr1_bear.h" #include "Game/collision/collide_room.h" +#include "Game/collision/Point.h" #include "Game/control/box.h" #include "Game/control/control.h" #include "Game/effects/effects.h" @@ -13,6 +14,7 @@ #include "Math/Math.h" #include "Specific/level.h" +using namespace TEN::Collision::Point; using namespace TEN::Math; namespace TEN::Entities::Creatures::TR1 @@ -146,8 +148,8 @@ namespace TEN::Entities::Creatures::TR1 bool isPlayerDead = LaraItem->HitPoints <= 0; - auto pointColl = GetCollision(item); - int floorToCeilHeight = abs(pointColl.Position.Ceiling - pointColl.Position.Floor); + auto pointColl = GetPointCollision(item); + int floorToCeilHeight = abs(pointColl.GetCeilingHeight() - pointColl.GetFloorHeight()); switch (item.Animation.ActiveState) { diff --git a/TombEngine/Objects/TR1/Entity/tr1_big_rat.cpp b/TombEngine/Objects/TR1/Entity/tr1_big_rat.cpp index cfd63dc11..97f53fb50 100644 --- a/TombEngine/Objects/TR1/Entity/tr1_big_rat.cpp +++ b/TombEngine/Objects/TR1/Entity/tr1_big_rat.cpp @@ -2,6 +2,7 @@ #include "Objects/TR1/Entity/tr1_big_rat.h" #include "Game/collision/collide_room.h" +#include "Game/collision/Point.h" #include "Game/control/box.h" #include "Game/control/control.h" #include "Game/effects/effects.h" @@ -13,6 +14,7 @@ #include "Math/Math.h" #include "Specific/level.h" +using namespace TEN::Collision::Point; using namespace TEN::Math; namespace TEN::Entities::Creatures::TR1 @@ -83,8 +85,7 @@ namespace TEN::Entities::Creatures::TR1 bool RatOnWater(ItemInfo* item) { - int waterDepth = GetWaterSurface(item->Pose.Position.x, item->Pose.Position.y, item->Pose.Position.z, item->RoomNumber); - + int waterDepth = GetPointCollision(*item).GetWaterSurfaceHeight(); if (item->IsCreature()) { auto& creature = *GetCreatureInfo(item); @@ -240,7 +241,7 @@ namespace TEN::Entities::Creatures::TR1 if (RatOnWater(item)) { CreatureUnderwater(item, 0); - item->Pose.Position.y = GetWaterHeight(item) - BIG_RAT_WATER_SURFACE_OFFSET; + item->Pose.Position.y = GetPointCollision(*item).GetWaterTopHeight() - BIG_RAT_WATER_SURFACE_OFFSET; } else { diff --git a/TombEngine/Objects/TR1/Entity/tr1_centaur.cpp b/TombEngine/Objects/TR1/Entity/tr1_centaur.cpp deleted file mode 100644 index 4e0904cdb..000000000 --- a/TombEngine/Objects/TR1/Entity/tr1_centaur.cpp +++ /dev/null @@ -1,158 +0,0 @@ -#include "framework.h" -#include "Objects/TR1/Entity/tr1_centaur.h" - -#include "Game/animation.h" -#include "Game/control/box.h" -#include "Game/collision/collide_item.h" -#include "Game/collision/collide_room.h" -#include "Game/effects/effects.h" -#include "Game/effects/tomb4fx.h" -#include "Game/items.h" -#include "Game/Lara/lara.h" -#include "Game/Lara/lara_one_gun.h" -#include "Game/misc.h" -#include "Game/missile.h" -#include "Game/people.h" -#include "Game/Setup.h" -#include "Math/Math.h" -#include "Sound/sound.h" -#include "Specific/level.h" - -using namespace TEN::Math; - -namespace TEN::Entities::Creatures::TR1 -{ - constexpr auto CENTAUR_REAR_DAMAGE = 200; - constexpr auto CENTAUR_REAR_RANGE = BLOCK(3 / 2.0f); - constexpr auto CENTAUR_REAR_CHANCE = 1 / 340.0f; - constexpr auto CENTAUR_BOMB_VELOCITY = CLICK(1); - - constexpr auto CENTAUR_TURN_RATE_MAX = ANGLE(4.0f); - - const auto CentaurRocketBite = CreatureBiteInfo(Vector3(11, 415, 41), 13); - const auto CentaurRearBite = CreatureBiteInfo(Vector3(50, 30, 0), 5); - const auto CentaurAttackJoints = std::vector{ 0, 3, 4, 7, 8, 16, 17 }; - - enum CentaurState - { - // No state 0. - CENTAUR_STATE_IDLE = 1, - CENTAUR_PROJECTILE_ATTACK = 2, - CENTAUR_STATE_RUN_FORWARD = 3, - CENTAUR_STATE_AIM = 4, - CENTAUR_STATE_DEATH = 5, - CENTAUR_STATE_WARNING = 6 - }; - - // TODO - enum CentaurAnim - { - CENTAUR_ANIM_DEATH = 8, - }; - - void CentaurControl(short itemNumber) - { - if (!CreatureActive(itemNumber)) - return; - - auto* item = &g_Level.Items[itemNumber]; - auto* creature = GetCreatureInfo(item); - - short angle = 0; - short head = 0; - - if (item->HitPoints <= 0) - { - if (item->Animation.ActiveState != CENTAUR_STATE_DEATH) - SetAnimation(item, CENTAUR_ANIM_DEATH); - } - else - { - AI_INFO AI; - CreatureAIInfo(item, &AI); - - if (AI.ahead) - head = AI.angle; - - CreatureMood(item, &AI, true); - - angle = CreatureTurn(item, CENTAUR_TURN_RATE_MAX); - - switch (item->Animation.ActiveState) - { - case CENTAUR_STATE_IDLE: - CreatureJoint(item, 17, 0); - if (item->Animation.RequiredState != NO_VALUE) - item->Animation.TargetState = item->Animation.RequiredState; - else if (AI.bite && AI.distance < pow(CENTAUR_REAR_RANGE, 2)) - item->Animation.TargetState = CENTAUR_STATE_RUN_FORWARD; - else if (Targetable(item, &AI)) - item->Animation.TargetState = CENTAUR_STATE_AIM; - else - item->Animation.TargetState = CENTAUR_STATE_RUN_FORWARD; - - break; - - case CENTAUR_STATE_RUN_FORWARD: - if (AI.bite && AI.distance < pow(CENTAUR_REAR_RANGE, 2)) - { - item->Animation.TargetState = CENTAUR_STATE_IDLE; - item->Animation.RequiredState = CENTAUR_STATE_WARNING; - } - else if (Targetable(item, &AI)) - { - item->Animation.TargetState = CENTAUR_STATE_IDLE; - item->Animation.RequiredState = CENTAUR_STATE_AIM; - } - else if (Random::TestProbability(CENTAUR_REAR_CHANCE)) - { - item->Animation.TargetState = CENTAUR_STATE_IDLE; - item->Animation.RequiredState = CENTAUR_STATE_WARNING; - } - - break; - - case CENTAUR_STATE_AIM: - if (item->Animation.RequiredState != NO_VALUE) - item->Animation.TargetState = item->Animation.RequiredState; - else if (Targetable(item, &AI)) - item->Animation.TargetState = CENTAUR_PROJECTILE_ATTACK; - else - item->Animation.TargetState = CENTAUR_STATE_IDLE; - - break; - - case CENTAUR_PROJECTILE_ATTACK: - if (item->Animation.RequiredState == NO_VALUE) - { - item->Animation.RequiredState = CENTAUR_STATE_AIM; - CreatureEffect2(item, CentaurRocketBite, CENTAUR_BOMB_VELOCITY, head, BombGun); - } - - break; - - case CENTAUR_STATE_WARNING: - if (item->Animation.RequiredState == NO_VALUE && - item->TouchBits.Test(CentaurAttackJoints)) - { - DoDamage(creature->Enemy, CENTAUR_REAR_DAMAGE); - CreatureEffect(item, CentaurRearBite, DoBloodSplat); - item->Animation.RequiredState = CENTAUR_STATE_IDLE; - } - - break; - } - } - - CreatureJoint(item, 0, head); - CreatureAnimation(itemNumber, angle, 0); - - if (item->Status == ITEM_DEACTIVATED) - { - SoundEffect(SFX_TR1_ATLANTEAN_DEATH, &item->Pose); - ExplodingDeath(itemNumber, BODY_DO_EXPLOSION); - KillItem(itemNumber); - item->Status = ITEM_DEACTIVATED; - } - } -} diff --git a/TombEngine/Objects/TR1/Entity/tr1_doppelganger.cpp b/TombEngine/Objects/TR1/Entity/tr1_doppelganger.cpp index 26cffbd02..d9e981c69 100644 --- a/TombEngine/Objects/TR1/Entity/tr1_doppelganger.cpp +++ b/TombEngine/Objects/TR1/Entity/tr1_doppelganger.cpp @@ -3,6 +3,7 @@ #include "Game/animation.h" #include "Game/collision/collide_room.h" +#include "Game/collision/Point.h" #include "Game/control/control.h" #include "Game/control/lot.h" #include "Game/items.h" @@ -11,6 +12,8 @@ #include "Game/misc.h" #include "Specific/level.h" +using namespace TEN::Collision::Point; + namespace TEN::Entities::Creatures::TR1 { ItemInfo* FindDoppelgangerReference(const ItemInfo& item, int objectNumber) @@ -53,14 +56,14 @@ namespace TEN::Entities::Creatures::TR1 { case 0: { - int laraFloorHeight = GetCollision(LaraItem).Position.Floor; + int laraFloorHeight = GetPointCollision(*LaraItem).GetFloorHeight(); // Get floor heights for comparison. auto pos = Vector3i( (referencePtr->Pose.Position.x * 2) - LaraItem->Pose.Position.x, LaraItem->Pose.Position.y, (referencePtr->Pose.Position.z * 2) - LaraItem->Pose.Position.z); - item.Floor = GetCollision(pos.x, pos.y, pos.z, item.RoomNumber).Position.Floor; + item.Floor = GetPointCollision(pos, item.RoomNumber).GetFloorHeight(); // Animate doppelganger, mirroring player's position. item.Animation.AnimNumber = LaraItem->Animation.AnimNumber; @@ -110,7 +113,7 @@ namespace TEN::Entities::Creatures::TR1 } TestTriggers(&item, true); - item.Floor = GetCollision(&item).Position.Floor; + item.Floor = GetPointCollision(item).GetFloorHeight(); if (item.Pose.Position.y >= item.Floor) { @@ -134,7 +137,7 @@ namespace TEN::Entities::Creatures::TR1 break; } - ItemNewRoom(itemNumber, GetCollision(&item).RoomNumber); + ItemNewRoom(itemNumber, GetPointCollision(item).GetRoomNumber()); AnimateItem(&item); } } diff --git a/TombEngine/Objects/TR1/Entity/tr1_winged_mutant.cpp b/TombEngine/Objects/TR1/Entity/tr1_winged_mutant.cpp deleted file mode 100644 index d0f36cd62..000000000 --- a/TombEngine/Objects/TR1/Entity/tr1_winged_mutant.cpp +++ /dev/null @@ -1,571 +0,0 @@ -#include "framework.h" -#include "Objects/TR1/Entity/tr1_winged_mutant.h" - -#include "Game/collision/collide_room.h" -#include "Game/control/box.h" -#include "Game/control/lot.h" -#include "Game/effects/effects.h" -#include "Game/effects/tomb4fx.h" -#include "Game/itemdata/creature_info.h" -#include "Game/items.h" -#include "Game/misc.h" -#include "Game/missile.h" -#include "Game/people.h" -#include "Math/Math.h" -#include "Sound/sound.h" -#include "Specific/level.h" - -using namespace TEN::Math; - -namespace TEN::Entities::Creatures::TR1 -{ - constexpr auto WINGED_MUTANT_IDLE_JUMP_ATTACK_DAMAGE = 150; - constexpr auto WINGED_MUTANT_RUN_JUMP_ATTACK_DAMAGE = 100; - constexpr auto WINGED_MUTANT_SWIPE_ATTACK_DAMAGE = 200; - - constexpr auto WINGED_MUTANT_WALK_RANGE = SQUARE(BLOCK(4.5f)); - constexpr auto WINGED_MUTANT_SWIPE_ATTACK_RANGE = SQUARE(BLOCK(0.3f)); - constexpr auto WINGED_MUTANT_RUN_JUMP_ATTACK_RANGE = SQUARE(BLOCK(0.65f)); - constexpr auto WINGED_MUTANT_IDLE_JUMP_ATTACK_RANGE = SQUARE(BLOCK(2.5f)); - constexpr auto WINGED_MUTANT_PROJECTILE_ATTACK_RANGE = SQUARE(BLOCK(3.0f)); - constexpr auto WINGED_MUTANT_POSE_RANGE = SQUARE(BLOCK(4.5f)); - - constexpr auto WINGED_MUTANT_POSE_CHANCE = 1 / 400.0f; - constexpr auto WINGED_MUTANT_UNPOSE_CHANCE = 1 / 164.0f; - - constexpr auto WINGED_MUTANT_FLY_VELOCITY = BLOCK(1 / 32.0f); - constexpr auto WINGED_MUTANT_SHARD_VELOCITY = 250; - constexpr auto WINGED_MUTANT_BOMB_VELOCITY = 220; - - constexpr auto WINGED_MUTANT_WALK_TURN_RATE_MAX = ANGLE(2.0f); - constexpr auto WINGED_MUTANT_RUN_TURN_RATE_MAX = ANGLE(6.0f); - - const auto WingedMutantBiteLeftHand = CreatureBiteInfo(Vector3(0, 0, 0), 7); - const auto WingedMutantBiteRightHand = CreatureBiteInfo(Vector3(0, 0, 0), 10); - const auto WingedMutantRocketBite = CreatureBiteInfo(Vector3(0, 200, 20), 6); - const auto WingedMutantShardBite = CreatureBiteInfo(Vector3(0, 200, 20), 9); - const auto WingedMutantHeadJoints = std::vector{ 3 }; - const auto WingedMutantHandsJoints = std::vector{ 7, 10 }; - const auto WingedMutantWingsJoints = std::vector{ 15, 16, 17, 18, 19, 20 }; - - enum WingedMutantState - { - // No state 0. - WMUTANT_STATE_IDLE = 1, - WMUTANT_STATE_WALK_FORWARD = 2, - WMUTANT_STATE_RUN_FORWARD = 3, - WMUTANT_STATE_IDLE_JUMP_ATTACK = 4, - WMUTANT_STATE_DEATH = 5, - WMUTANT_STATE_POSE = 6, - WMUTANT_STATE_RUN_JUMP_ATTACK = 7, - WMUTANT_STATE_SWIPE_ATTACK = 8, - WMUTANT_STATE_AIM_DART = 9, - WMUTANT_STATE_AIM_BOMB = 10, - WMUTANT_STATE_SHOOT = 11, - WMUTANT_STATE_INACTIVE = 12, - WMUTANT_STATE_FLY = 13, - }; - - enum WingedMutantAnim - { - WMUTANT_ANIM_INACTIVE = 0, - WMUTANT_ANIM_INACTIVE_TO_IDLE = 1, - WMUTANT_ANIM_IDLE = 2, - WMUTANT_ANIM_IDLE_TO_RUN = 3, - WMUTANT_ANIM_RUN_FORWARD = 4, - WMUTANT_ANIM_IDLE_JUMP_ATTACK_START = 5, - WMUTANT_ANIM_IDLE_JUMP_ATTACK_END = 6, - WMUTANT_ANIM_IDLE_TO_POSE = 7, - WMUTANT_ANIM_POSE = 8, - WMUTANT_ANIM_POSE_TO_IDLE = 9, - WMUTANT_ANIM_POSE_TO_WALK_FORWARD = 10, - WMUTANT_ANIM_WALK_FORWARD = 11, - WMUTANT_ANIM_WALK_FORWARD_TO_IDLE = 12, - WMUTANT_ANIM_WALK_FORWARD_TO_POSE = 13, - WMUTANT_ANIM_RUN_JUMP_ATTACK = 14, - WMUTANT_ANIM_IDLE_TO_AIM_1 = 15, - WMUTANT_ANIM_AIM_DART = 16, - WMUTANT_ANIM_SHOOT_DART = 17, - WMUTANT_ANIM_AIM_DART_TO_IDLE = 18, - WMUTANT_ANIM_IDLE_TO_AIM_BOMB = 19, - WMUTANT_ANIM_SHOOT_BOMB = 20, - WMUTANT_ANIM_RUN_FORWARD_TO_IDLE = 21, - WMUTANT_ANIM_AIM_BOMB_TO_IDLE = 22, - WMUTANT_ANIM_IDLE_TO_FLY = 23, - WMUTANT_ANIM_FLY = 24, - WMUTANT_ANIM_FLY_TO_IDLE = 25, - WMUTANT_ANIM_SWIPE_ATTACK = 26 - }; - - enum WingedMutantPathFinding - { - WMUTANT_PATH_GROUND = 1, - WMUTANT_PATH_AERIAL = 2 - }; - - // NOTE: Originally, winged mutants did not have OCBs. -- TokyoSU 5/8/2022 - enum WingedMutantOcb - { - WMUTANT_OCB_START_AERIAL = (1 << 0), - WMUTANT_OCB_START_INACTIVE = (1 << 1), - WMUTANT_OCB_START_POSE = (1 << 2), - WMUTANT_OCB_NO_WINGS = (1 << 3), - WMUTANT_OCB_DISABLE_DART_WEAPON = (1 << 4), - WMUTANT_OCB_DISABLE_BOMB_WEAPON = (1 << 5) - }; - - enum WingedMutantProjectileType - { - WMUTANT_PROJ_NONE, - WMUTANT_PROJ_DART, - WMUTANT_PROJ_BOMB - }; - - enum WingedMutantConfig - { - WMUTANT_CONF_CAN_FLY, - WMUTANT_CONF_PATHFINDING_MODE, - WMUTANT_CONF_PROJECTILE_MODE, - WMUTANT_CONF_NO_WINGS, - WMUTANT_CONF_DISABLE_DART_WEAPON, - WMUTANT_CONF_DISABLE_BOMB_WEAPON - }; - - static void SwitchPathfinding(CreatureInfo* creature, WingedMutantPathFinding path) - { - switch (path) - { - case WMUTANT_PATH_GROUND: - creature->LOT.Step = CLICK(1); - creature->LOT.Drop = -CLICK(1); - creature->LOT.Fly = NO_FLYING; - creature->LOT.Zone = ZoneType::Basic; - break; - - case WMUTANT_PATH_AERIAL: - creature->LOT.Step = BLOCK(20); - creature->LOT.Drop = -BLOCK(20); - creature->LOT.Fly = WINGED_MUTANT_FLY_VELOCITY; - creature->LOT.Zone = ZoneType::Flyer; - break; - } - } - - static WingedMutantProjectileType CanTargetLara(ItemInfo* item, CreatureInfo* creature, AI_INFO* AI) - { - if (Targetable(item, AI) && - (AI->zoneNumber != AI->enemyZone || AI->distance > WINGED_MUTANT_PROJECTILE_ATTACK_RANGE)) - { - if ((AI->angle > 0 && AI->angle < ANGLE(45.0f)) && - item->TestFlagField(WMUTANT_CONF_DISABLE_DART_WEAPON, false)) - { - return WMUTANT_PROJ_DART; - } - else if ((AI->angle < 0 && AI->angle > -ANGLE(45.0f)) && - item->TestFlagField(WMUTANT_CONF_DISABLE_BOMB_WEAPON, false)) - { - return WMUTANT_PROJ_BOMB; - } - } - - // Cannot be targeted. - return WMUTANT_PROJ_NONE; - } - - static void WingedInitOCB(ItemInfo* item, CreatureInfo* creature) - { - if (item->TestOcb(WMUTANT_OCB_START_AERIAL)) - { - SwitchPathfinding(creature, WMUTANT_PATH_AERIAL); - SetAnimation(item, WMUTANT_ANIM_FLY); - item->SetFlagField(WMUTANT_CONF_PATHFINDING_MODE, WMUTANT_PATH_AERIAL); - } - else if (item->TestOcb(WMUTANT_OCB_START_INACTIVE)) - { - SwitchPathfinding(creature, WMUTANT_PATH_GROUND); - SetAnimation(item, WMUTANT_ANIM_INACTIVE); - item->SetFlagField(WMUTANT_CONF_PATHFINDING_MODE, WMUTANT_PATH_GROUND); - } - else if (item->TestOcb(WMUTANT_OCB_START_POSE)) - { - SwitchPathfinding(creature, WMUTANT_PATH_GROUND); - SetAnimation(item, WMUTANT_ANIM_INACTIVE); - item->SetFlagField(WMUTANT_CONF_PATHFINDING_MODE, WMUTANT_PATH_GROUND); - } - - // Remove unnecessary OCBs. - if (item->TestOcb(WMUTANT_OCB_START_AERIAL)) - item->RemoveOcb(WMUTANT_OCB_START_AERIAL); - - if (item->TestOcb(WMUTANT_OCB_START_INACTIVE)) - item->RemoveOcb(WMUTANT_OCB_START_INACTIVE); - - if (item->TestOcb(WMUTANT_OCB_START_POSE)) - item->RemoveOcb(WMUTANT_OCB_START_POSE); - } - - void InitializeWingedMutant(short itemNumber) - { - auto* item = &g_Level.Items[itemNumber]; - - InitializeCreature(itemNumber); - item->SetFlagField(WMUTANT_CONF_PATHFINDING_MODE, WMUTANT_PATH_GROUND); - item->SetFlagField(WMUTANT_CONF_PROJECTILE_MODE, WMUTANT_PROJ_NONE); - - if (item->TestOcb(WMUTANT_OCB_NO_WINGS)) - { - item->SetFlagField(WMUTANT_CONF_CAN_FLY, false); - item->MeshBits.Clear(WingedMutantWingsJoints); - } - else - { - item->SetFlagField(WMUTANT_CONF_CAN_FLY, true); - } - - if (item->TestOcb(WMUTANT_OCB_DISABLE_BOMB_WEAPON)) - item->SetFlagField(WMUTANT_CONF_DISABLE_BOMB_WEAPON, true); - - if (item->TestOcb(WMUTANT_OCB_DISABLE_DART_WEAPON)) - item->SetFlagField(WMUTANT_CONF_DISABLE_DART_WEAPON, true); - - if (item->TestOcb(WMUTANT_OCB_DISABLE_BOMB_WEAPON)) - item->RemoveOcb(WMUTANT_OCB_DISABLE_BOMB_WEAPON); - - if (item->TestOcb(WMUTANT_OCB_DISABLE_DART_WEAPON)) - item->RemoveOcb(WMUTANT_OCB_DISABLE_DART_WEAPON); - - if (item->TestOcb(WMUTANT_OCB_NO_WINGS)) - item->RemoveOcb(WMUTANT_OCB_NO_WINGS); - } - - void WingedMutantControl(short itemNumber) - { - if (!CreatureActive(itemNumber)) - return; - - auto* item = &g_Level.Items[itemNumber]; - auto* creature = GetCreatureInfo(item); - - short angle = 0; - short head = 0; - short torso = 0; // Only when shooting. - - bool flyEnabled = item->TestFlagField(WMUTANT_CONF_CAN_FLY, true); - bool flyStatus = item->TestFlagField(WMUTANT_CONF_PATHFINDING_MODE, WMUTANT_PATH_AERIAL); - - WingedInitOCB(item, creature); - - if (item->HitPoints <= 0) - { - CreatureDie(itemNumber, true, BODY_DO_EXPLOSION | BODY_PART_EXPLODE | BODY_NO_SMOKE | BODY_NO_SHATTER_EFFECT); - - auto pos = item->Pose; - pos.Position.y -= CLICK(3); - TriggerExplosionSparks(pos.Position.x, pos.Position.y, pos.Position.z, 3, -2, 0, item->RoomNumber); - TriggerExplosionSparks(pos.Position.x, pos.Position.y, pos.Position.z, 3, -1, 0, item->RoomNumber); - TriggerShockwave(&pos, 48, 304, (GetRandomControl() & 0x1F) + 112, 128, 32, 32, 32, EulerAngles(2048, 0.0f, 0.0f), 0, true, false, false, (int)ShockwaveStyle::Normal); - - SoundEffect(SFX_TR1_ATLANTEAN_EXPLODE, &item->Pose); - return; - } - else - { - AI_INFO ai; - SwitchPathfinding(creature, WMUTANT_PATH_GROUND); - CreatureAIInfo(item, &ai); - - bool isSameZoneInGroundMode = (ai.zoneNumber == ai.enemyZone); - auto projectileType = CanTargetLara(item, creature, &ai); - - if (flyEnabled && item->Animation.ActiveState == WMUTANT_STATE_FLY) - { - SwitchPathfinding(creature, WMUTANT_PATH_AERIAL); - CreatureAIInfo(item, &ai); - } - - if (ai.ahead) - { - head = ai.angle; - } - else - { - head = 0; - torso = 0; - } - - GetCreatureMood(item, &ai, flyStatus); - CreatureMood(item, &ai, flyStatus); - angle = CreatureTurn(item, creature->MaxTurn); - - switch (item->Animation.ActiveState) - { - case WMUTANT_STATE_INACTIVE: - creature->MaxTurn = 0; - creature->Flags = 0; - - if (TargetVisible(item, &ai) || creature->HurtByLara) - item->Animation.TargetState = WMUTANT_STATE_IDLE; - - break; - - case WMUTANT_STATE_IDLE: - item->SetFlagField(WMUTANT_CONF_PATHFINDING_MODE, WMUTANT_PROJ_NONE); - creature->MaxTurn = 0; - creature->Flags = 0; - torso = 0; - - if (flyEnabled && !isSameZoneInGroundMode) - { - item->Animation.TargetState = WMUTANT_STATE_FLY; - item->SetFlagField(WMUTANT_CONF_PATHFINDING_MODE, WMUTANT_PATH_AERIAL); - } - else if (item->TouchBits.Test(WingedMutantHeadJoints)) - { - item->Animation.TargetState = WMUTANT_STATE_SWIPE_ATTACK; - } - else if (ai.bite && ai.distance < WINGED_MUTANT_IDLE_JUMP_ATTACK_RANGE) - { - item->Animation.TargetState = WMUTANT_STATE_IDLE_JUMP_ATTACK; - } - else if (ai.bite && ai.distance < WINGED_MUTANT_SWIPE_ATTACK_RANGE) - { - item->Animation.TargetState = WMUTANT_STATE_SWIPE_ATTACK; - } - else if (projectileType == WMUTANT_PROJ_DART) - { - item->Animation.TargetState = WMUTANT_STATE_AIM_DART; - } - else if (projectileType == WMUTANT_PROJ_BOMB) - { - item->Animation.TargetState = WMUTANT_STATE_AIM_BOMB; - } - else if (creature->Mood == MoodType::Bored || - (creature->Mood == MoodType::Stalk && ai.distance < WINGED_MUTANT_POSE_RANGE)) - { - item->Animation.TargetState = WMUTANT_STATE_POSE; - } - else - { - item->Animation.TargetState = WMUTANT_STATE_RUN_FORWARD; - } - - break; - - case WMUTANT_STATE_POSE: - creature->Flags = 0; - creature->MaxTurn = 0; - head = 0; // NOTE: Pose has animation for head. - - if (projectileType != WMUTANT_PROJ_NONE || (flyStatus && flyEnabled)) - { - item->Animation.TargetState = WMUTANT_STATE_IDLE; - } - else if (creature->Mood == MoodType::Stalk) - { - if (ai.distance < WINGED_MUTANT_WALK_RANGE) - { - if (isSameZoneInGroundMode || - Random::TestProbability(WINGED_MUTANT_UNPOSE_CHANCE)) - { - item->Animation.TargetState = WMUTANT_STATE_WALK_FORWARD; - } - } - else - { - item->Animation.TargetState = WMUTANT_STATE_IDLE; - } - } - else if (creature->Mood == MoodType::Bored && Random::TestProbability(WINGED_MUTANT_UNPOSE_CHANCE)) - { - item->Animation.TargetState = WMUTANT_STATE_WALK_FORWARD; - } - else if (creature->Mood == MoodType::Attack || - creature->Mood == MoodType::Escape) - { - item->Animation.TargetState = WMUTANT_STATE_IDLE; - } - - break; - - case WMUTANT_STATE_WALK_FORWARD: - creature->MaxTurn = WINGED_MUTANT_WALK_TURN_RATE_MAX; - creature->Flags = 0; - - if (projectileType != WMUTANT_PROJ_NONE || (flyStatus && flyEnabled)) - { - item->Animation.TargetState = WMUTANT_STATE_IDLE; - } - else if (creature->Mood == MoodType::Attack || creature->Mood == MoodType::Escape) - { - item->Animation.TargetState = WMUTANT_STATE_IDLE; - } - else if (creature->Mood == MoodType::Bored || - (creature->Mood == MoodType::Stalk && !isSameZoneInGroundMode)) - { - if (Random::TestProbability(WINGED_MUTANT_POSE_CHANCE)) - item->Animation.TargetState = WMUTANT_STATE_POSE; - } - else if (creature->Mood == MoodType::Stalk && - ai.distance > WINGED_MUTANT_WALK_RANGE) - { - item->Animation.TargetState = WMUTANT_STATE_IDLE; - } - - break; - - case WMUTANT_STATE_RUN_FORWARD: - creature->MaxTurn = WINGED_MUTANT_RUN_TURN_RATE_MAX; - creature->Flags = 0; - - if (flyEnabled && !isSameZoneInGroundMode) - { - item->Animation.TargetState = WMUTANT_STATE_IDLE; - } - else if (projectileType != WMUTANT_PROJ_NONE) - { - item->Animation.TargetState = WMUTANT_STATE_IDLE; - } - else if (item->TouchBits.Test(WingedMutantHeadJoints)) - { - item->Animation.TargetState = WMUTANT_STATE_IDLE; - } - else if (ai.bite && ai.distance < WINGED_MUTANT_RUN_JUMP_ATTACK_RANGE) - { - item->Animation.TargetState = WMUTANT_STATE_RUN_JUMP_ATTACK; - } - else if (ai.bite && ai.distance < WINGED_MUTANT_SWIPE_ATTACK_RANGE) - { - item->Animation.TargetState = WMUTANT_STATE_SWIPE_ATTACK; - } - else if (ai.ahead && ai.distance < WINGED_MUTANT_SWIPE_ATTACK_RANGE) - { - item->Animation.TargetState = WMUTANT_STATE_SWIPE_ATTACK; - } - else if (creature->Mood == MoodType::Bored || - (creature->Mood == MoodType::Stalk && ai.distance < WINGED_MUTANT_POSE_RANGE)) - { - item->Animation.TargetState = WMUTANT_STATE_IDLE; - } - - break; - - case WMUTANT_STATE_IDLE_JUMP_ATTACK: - if (item->Animation.RequiredState == NO_VALUE && - (item->TouchBits.Test(WingedMutantHandsJoints) || item->TouchBits.Test(WingedMutantHeadJoints)) && creature->Flags == 0) - { - DoDamage(creature->Enemy, WINGED_MUTANT_IDLE_JUMP_ATTACK_DAMAGE / 2); - CreatureEffect(item, WingedMutantBiteLeftHand, DoBloodSplat); - - DoDamage(creature->Enemy, WINGED_MUTANT_IDLE_JUMP_ATTACK_DAMAGE / 2); - CreatureEffect(item, WingedMutantBiteRightHand, DoBloodSplat); - - item->Animation.TargetState = WMUTANT_STATE_IDLE; - creature->Flags = 1; - } - - break; - - case WMUTANT_STATE_RUN_JUMP_ATTACK: - if (item->Animation.RequiredState == NO_VALUE && - (item->TouchBits.Test(WingedMutantHandsJoints) || item->TouchBits.Test(WingedMutantHeadJoints)) && creature->Flags == 0) - { - DoDamage(creature->Enemy, WINGED_MUTANT_RUN_JUMP_ATTACK_DAMAGE / 2); - CreatureEffect(item, WingedMutantBiteLeftHand, DoBloodSplat); - - DoDamage(creature->Enemy, WINGED_MUTANT_RUN_JUMP_ATTACK_DAMAGE / 2); - CreatureEffect(item, WingedMutantBiteRightHand, DoBloodSplat); - - item->Animation.TargetState = WMUTANT_STATE_RUN_FORWARD; - creature->Flags = 1; - } - - break; - - case WMUTANT_STATE_SWIPE_ATTACK: - if (item->Animation.RequiredState == NO_VALUE && - item->TouchBits.Test(WingedMutantHandsJoints) && creature->Flags == 0) - { - DoDamage(creature->Enemy, WINGED_MUTANT_SWIPE_ATTACK_DAMAGE / 2); - CreatureEffect(item, WingedMutantBiteLeftHand, DoBloodSplat); - - DoDamage(creature->Enemy, WINGED_MUTANT_SWIPE_ATTACK_DAMAGE / 2); - CreatureEffect(item, WingedMutantBiteRightHand, DoBloodSplat); - - item->Animation.TargetState = WMUTANT_STATE_IDLE; - creature->Flags = 1; - } - - break; - - case WMUTANT_STATE_AIM_DART: - item->SetFlagField(WMUTANT_CONF_PROJECTILE_MODE, WMUTANT_PROJ_DART); - creature->MaxTurn = 0; - creature->Flags = 0; - torso = ai.angle / 2; - - if (projectileType == WMUTANT_PROJ_DART) - { - item->Animation.TargetState = WMUTANT_STATE_SHOOT; - } - else - { - item->Animation.TargetState = WMUTANT_STATE_IDLE; - } - - break; - - case WMUTANT_STATE_AIM_BOMB: - item->SetFlagField(WMUTANT_CONF_PROJECTILE_MODE, WMUTANT_PROJ_BOMB); - creature->MaxTurn = 0; - creature->Flags = 0; - torso = ai.angle / 2; - - if (projectileType == WMUTANT_PROJ_BOMB) - { - item->Animation.TargetState = WMUTANT_STATE_SHOOT; - } - else - { - item->Animation.TargetState = WMUTANT_STATE_IDLE; - } - - break; - - case WMUTANT_STATE_SHOOT: - creature->MaxTurn = 0; - torso = ai.angle / 2; - - if (creature->Flags == 0) - { - if (projectileType == WMUTANT_PROJ_DART) - { - CreatureEffect2(item, WingedMutantShardBite, WINGED_MUTANT_SHARD_VELOCITY, torso, ShardGun); - } - else if (projectileType == WMUTANT_PROJ_BOMB) - { - CreatureEffect2(item, WingedMutantRocketBite, WINGED_MUTANT_BOMB_VELOCITY, torso, BombGun); - } - - creature->Flags = 1; - } - - item->SetFlagField(WMUTANT_CONF_PROJECTILE_MODE, WMUTANT_PROJ_NONE); - break; - - case WMUTANT_STATE_FLY: - if (creature->Mood != MoodType::Escape && isSameZoneInGroundMode) - { - item->Animation.TargetState = WMUTANT_STATE_IDLE; // Switch to ground mode. - item->Pose.Position.y = item->Floor; - item->SetFlagField(WMUTANT_CONF_PATHFINDING_MODE, WMUTANT_PATH_GROUND); - } - - break; - } - } - - CreatureJoint(item, 0, torso); - CreatureJoint(item, 1, head); - CreatureAnimation(itemNumber, angle, 0); - } -} diff --git a/TombEngine/Objects/TR1/Trap/DamoclesSword.cpp b/TombEngine/Objects/TR1/Trap/DamoclesSword.cpp index 25290c7c3..1a9d9998b 100644 --- a/TombEngine/Objects/TR1/Trap/DamoclesSword.cpp +++ b/TombEngine/Objects/TR1/Trap/DamoclesSword.cpp @@ -4,15 +4,17 @@ #include "Game/camera.h" #include "Game/collision/collide_item.h" #include "Game/collision/collide_room.h" +#include "Game/collision/Point.h" #include "Game/effects/effects.h" #include "Game/Lara/lara.h" #include "Game/Setup.h" #include "Math/Math.h" #include "Specific/level.h" +using namespace TEN::Collision::Point; using namespace TEN::Math; -namespace TEN::Entities::Traps::TR1 +namespace TEN::Entities::Traps { // NOTES: // ItemFlags[0] = random turn rate when active. @@ -59,10 +61,10 @@ namespace TEN::Entities::Traps::TR1 TranslateItem(&item, headingAngle, item.ItemFlags[1], item.Animation.Velocity.y); int vPos = item.Pose.Position.y; - auto pointColl = GetCollision(&item); + auto pointColl = GetPointCollision(item); // Impale floor. - if ((pointColl.Position.Floor - vPos) <= DAMOCLES_SWORD_IMPALE_DEPTH) + if ((pointColl.GetFloorHeight() - vPos) <= DAMOCLES_SWORD_IMPALE_DEPTH) { SoundEffect(SFX_TR1_DAMOCLES_ROOM_SWORD, &item.Pose); float distance = Vector3::Distance(item.Pose.Position.ToVector3(), Camera.pos.ToVector3()); @@ -79,7 +81,7 @@ namespace TEN::Entities::Traps::TR1 } // Scan for player. - if (item.Pose.Position.y < GetCollision(&item).Position.Floor) + if (item.Pose.Position.y < GetPointCollision(item).GetFloorHeight()) { item.Pose.Orientation.y += item.ItemFlags[0]; diff --git a/TombEngine/Objects/TR1/Trap/DamoclesSword.h b/TombEngine/Objects/TR1/Trap/DamoclesSword.h index 5e76ec675..c5cc457f5 100644 --- a/TombEngine/Objects/TR1/Trap/DamoclesSword.h +++ b/TombEngine/Objects/TR1/Trap/DamoclesSword.h @@ -4,7 +4,7 @@ struct CollisionInfo; struct ItemInfo; struct ObjectInfo; -namespace TEN::Entities::Traps::TR1 +namespace TEN::Entities::Traps { void InitializeDamoclesSword(short itemNumber); diff --git a/TombEngine/Objects/TR1/Trap/SlammingDoors.cpp b/TombEngine/Objects/TR1/Trap/SlammingDoors.cpp index cb1974298..9c41a6a64 100644 --- a/TombEngine/Objects/TR1/Trap/SlammingDoors.cpp +++ b/TombEngine/Objects/TR1/Trap/SlammingDoors.cpp @@ -9,7 +9,7 @@ using namespace TEN::Math; -namespace TEN::Entities::Traps::TR1 +namespace TEN::Entities::Traps { constexpr auto SLAMMING_DOORS_HARM_DAMAGE = 400; diff --git a/TombEngine/Objects/TR1/Trap/SlammingDoors.h b/TombEngine/Objects/TR1/Trap/SlammingDoors.h index 700de7700..92e111334 100644 --- a/TombEngine/Objects/TR1/Trap/SlammingDoors.h +++ b/TombEngine/Objects/TR1/Trap/SlammingDoors.h @@ -3,7 +3,7 @@ struct BiteInfo; struct ItemInfo; -namespace TEN::Entities::Traps::TR1 +namespace TEN::Entities::Traps { void InitializeSlammingDoors(short itemNumber); void ControlSlammingDoors(short itemNumber); diff --git a/TombEngine/Objects/TR1/Trap/SwingingBlade.cpp b/TombEngine/Objects/TR1/Trap/SwingingBlade.cpp index 05420db62..1475d5662 100644 --- a/TombEngine/Objects/TR1/Trap/SwingingBlade.cpp +++ b/TombEngine/Objects/TR1/Trap/SwingingBlade.cpp @@ -3,7 +3,7 @@ #include "Game/Setup.h" -namespace TEN::Entities::Traps::TR1 +namespace TEN::Entities::Traps { constexpr auto SWINGING_BLADE_HARM_DAMAGE = 100; @@ -57,6 +57,17 @@ namespace TEN::Entities::Traps::TR1 // Unset harm joints. item.ItemFlags[0] = 0; } + else + { + if (item.Animation.AnimNumber == GetAnimIndex(item, SWINGING_BLADE_ANIM_DISABLED) && + item.Animation.FrameNumber == GetAnimData(item).frameEnd) + { + item.Flags &= 0xC1; + RemoveActiveItem(itemNumber, false); + item.Active = false; + item.Status = ITEM_NOT_ACTIVE; + } + } } AnimateItem(&item); diff --git a/TombEngine/Objects/TR1/Trap/SwingingBlade.h b/TombEngine/Objects/TR1/Trap/SwingingBlade.h index d85880131..ac88336e6 100644 --- a/TombEngine/Objects/TR1/Trap/SwingingBlade.h +++ b/TombEngine/Objects/TR1/Trap/SwingingBlade.h @@ -1,6 +1,6 @@ #pragma once -namespace TEN::Entities::Traps::TR1 +namespace TEN::Entities::Traps { void InitializeSwingingBlade(short itemNumber); void ControlSwingingBlade(short itemNumber); diff --git a/TombEngine/Objects/TR1/tr1_objects.cpp b/TombEngine/Objects/TR1/tr1_objects.cpp index 244deb99f..2f2eb4c49 100644 --- a/TombEngine/Objects/TR1/tr1_objects.cpp +++ b/TombEngine/Objects/TR1/tr1_objects.cpp @@ -10,18 +10,18 @@ #include "Specific/level.h" // Creatures -#include "Objects/TR1/Entity/Cowboy.h" // OK -#include "Objects/TR1/Entity/Kold.h" // OK -#include "Objects/TR1/Entity/tr1_ape.h" // OK -#include "Objects/TR1/Entity/tr1_bear.h" // OK -#include "Objects/TR1/Entity/tr1_doppelganger.h" // OK -#include "Objects/TR1/Entity/tr1_natla.h" // OK -#include "Objects/TR1/Entity/tr1_giant_mutant.h" // OK -#include "Objects/TR1/Entity/tr1_wolf.h" // OK -#include "Objects/TR1/Entity/tr1_big_rat.h" // OK -#include "Objects/TR1/Entity/tr1_centaur.h" // OK -#include "Objects/TR1/Entity/tr1_winged_mutant.h" // OK -#include "Objects/TR1/Entity/SkateboardKid.h" // OK +#include "Objects/TR1/Entity/Centaur.h" +#include "Objects/TR1/Entity/Cowboy.h" +#include "Objects/TR1/Entity/Kold.h" +#include "Objects/TR1/Entity/SkateboardKid.h" +#include "Objects/TR1/Entity/WingedMutant.h" +#include "Objects/TR1/Entity/tr1_ape.h" +#include "Objects/TR1/Entity/tr1_bear.h" +#include "Objects/TR1/Entity/tr1_doppelganger.h" +#include "Objects/TR1/Entity/tr1_natla.h" +#include "Objects/TR1/Entity/tr1_giant_mutant.h" +#include "Objects/TR1/Entity/tr1_wolf.h" +#include "Objects/TR1/Entity/tr1_big_rat.h" #include "Objects/Utils/object_helper.h" // Traps @@ -30,7 +30,7 @@ #include "Objects/TR1/Trap/SwingingBlade.h" using namespace TEN::Entities::Creatures::TR1; -using namespace TEN::Entities::Traps::TR1; +using namespace TEN::Entities::Traps; static void StartEntity(ObjectInfo* obj) { @@ -149,7 +149,7 @@ static void StartEntity(ObjectInfo* obj) if (obj->loaded) { obj->Initialize = InitializeCreature; - obj->control = CentaurControl; + obj->control = ControlCentaur; obj->collision = CreatureCollision; obj->shadowType = ShadowMode::All; obj->HitPoints = 120; @@ -157,7 +157,8 @@ static void StartEntity(ObjectInfo* obj) obj->radius = BLOCK(1 / 3.0f); obj->intelligent = true; obj->LotType = LotType::Blockable; - obj->SetBoneRotationFlags(10, ROT_X | ROT_Y); + obj->SetBoneRotationFlags(10, ROT_X | ROT_Y); // Torso + obj->SetBoneRotationFlags(17, ROT_X | ROT_Y); // Head obj->SetHitEffect(); } @@ -165,7 +166,7 @@ static void StartEntity(ObjectInfo* obj) if (obj->loaded) { obj->Initialize = InitializeWingedMutant; - obj->control = WingedMutantControl; + obj->control = ControlWingedMutant; obj->collision = CreatureCollision; obj->shadowType = ShadowMode::All; obj->pivotLength = 150; @@ -274,7 +275,6 @@ static void StartProjectiles(ObjectInfo* obj) { InitProjectile(obj, ControlMissile, ID_PROJ_SHARD); InitProjectile(obj, ControlMissile, ID_PROJ_BOMB); - InitProjectile(obj, ControlMissile, ID_PROJ_BOMB); } void InitializeTR1Objects() diff --git a/TombEngine/Objects/TR2/Entity/Dragon.cpp b/TombEngine/Objects/TR2/Entity/Dragon.cpp index 9f8c8f4aa..90e90a51c 100644 --- a/TombEngine/Objects/TR2/Entity/Dragon.cpp +++ b/TombEngine/Objects/TR2/Entity/Dragon.cpp @@ -3,7 +3,7 @@ #include "Game/camera.h" #include "Game/collision/collide_item.h" -#include "Game/collision/sphere.h" +#include "Game/collision/Point.h" #include "Game/control/lot.h" #include "Game/effects/effects.h" #include "Game/effects/tomb4fx.h" @@ -16,6 +16,7 @@ #include "Specific/clock.h" #include "Specific/Input/Input.h" +using namespace TEN::Collision::Point; using namespace TEN::Input; using namespace TEN::Math; @@ -333,15 +334,15 @@ namespace TEN::Entities::Creatures::TR2 { if (GetFrameNumber(item) == GetFrameCount(item.Animation.AnimNumber)) { - auto pointColl = GetCollision(pos, item.RoomNumber); + auto pointColl = GetPointCollision(pos, item.RoomNumber); - if (pointColl.Position.Floor == NO_HEIGHT) + if (pointColl.GetFloorHeight() == NO_HEIGHT) { pos.y -= CLICK(0.5f); } else { - pos.y = pointColl.Position.Floor - CLICK(0.5f); + pos.y = pointColl.GetFloorHeight() - CLICK(0.5f); } auto pose = Pose(pos, EulerAngles::Identity); diff --git a/TombEngine/Objects/TR2/Entity/tr2_skidman.cpp b/TombEngine/Objects/TR2/Entity/tr2_skidman.cpp index 4b573f7b5..94fb34145 100644 --- a/TombEngine/Objects/TR2/Entity/tr2_skidman.cpp +++ b/TombEngine/Objects/TR2/Entity/tr2_skidman.cpp @@ -4,7 +4,7 @@ #include "Game/animation.h" #include "Game/items.h" #include "Game/collision/collide_item.h" -#include "Game/collision/sphere.h" +#include "Game/collision/Sphere.h" #include "Game/control/box.h" #include "Game/control/lot.h" #include "Game/effects/smoke.h" @@ -19,6 +19,7 @@ #include "Sound/sound.h" #include "Specific/level.h" +using namespace TEN::Collision::Sphere; using namespace TEN::Effects::Smoke; namespace TEN::Entities::Creatures::TR2 @@ -81,7 +82,7 @@ namespace TEN::Entities::Creatures::TR2 if (item.Flags & IFLAG_REVERSE) { - item.Status &= ~ITEM_INVISIBLE; + item.Status = ITEM_NOT_ACTIVE; } else { @@ -96,7 +97,7 @@ namespace TEN::Entities::Creatures::TR2 if (!TestBoundsCollide(&item, laraItem, coll->Setup.Radius)) return; - if (!TestCollision(&item, laraItem)) + if (!HandleItemSphereCollision(item, *laraItem)) return; if (coll->Setup.EnableObjectPush) @@ -131,6 +132,9 @@ namespace TEN::Entities::Creatures::TR2 int skidooItemNumber = (short)riderItem.ItemFlags[0]; auto* skidooItem = &g_Level.Items[skidooItemNumber]; + if (!CreatureActive(skidooItemNumber)) + return; + if (!skidooItem->Data) { EnableEntityAI(skidooItemNumber, true); diff --git a/TombEngine/Objects/TR2/Entity/tr2_spear_guardian.cpp b/TombEngine/Objects/TR2/Entity/tr2_spear_guardian.cpp index cabdd0ee0..c8d31ee1d 100644 --- a/TombEngine/Objects/TR2/Entity/tr2_spear_guardian.cpp +++ b/TombEngine/Objects/TR2/Entity/tr2_spear_guardian.cpp @@ -195,7 +195,7 @@ namespace TEN::Entities::Creatures::TR2 InitializeCreature(itemNumber); SetAnimation(&item, SPEAR_GUARDIAN_ANIM_AWAKE); - item.Status &= ~ITEM_INVISIBLE; + item.Status = ITEM_NOT_ACTIVE; item.ItemFlags[0] = 0; // Joint index for mesh swap. item.ItemFlags[1] = 1; // Immune state (bool). diff --git a/TombEngine/Objects/TR2/Entity/tr2_sword_guardian.cpp b/TombEngine/Objects/TR2/Entity/tr2_sword_guardian.cpp index d620b5daf..e3fb2e420 100644 --- a/TombEngine/Objects/TR2/Entity/tr2_sword_guardian.cpp +++ b/TombEngine/Objects/TR2/Entity/tr2_sword_guardian.cpp @@ -174,7 +174,7 @@ namespace TEN::Entities::Creatures::TR2 InitializeCreature(itemNumber); SetAnimation(&item, SWORD_GUARDIAN_ANIM_AWAKE); - item.Status &= ~ITEM_INVISIBLE; + item.Status = ITEM_NOT_ACTIVE; item.ItemFlags[0] = 0; // Joint index for mesh swap. item.ItemFlags[1] = 1; // Immune state (bool). diff --git a/TombEngine/Objects/TR2/Entity/tr2_worker_shotgun.cpp b/TombEngine/Objects/TR2/Entity/tr2_worker_shotgun.cpp index 0d365e113..7694f559e 100644 --- a/TombEngine/Objects/TR2/Entity/tr2_worker_shotgun.cpp +++ b/TombEngine/Objects/TR2/Entity/tr2_worker_shotgun.cpp @@ -5,29 +5,61 @@ #include "Game/control/box.h" #include "Game/control/control.h" #include "Game/effects/effects.h" -#include "Game/items.h" #include "Game/itemdata/creature_info.h" +#include "Game/items.h" #include "Game/misc.h" #include "Game/people.h" #include "Game/Setup.h" +#include "Math/Math.h" #include "Specific/level.h" +using namespace TEN::Math; + namespace TEN::Entities::Creatures::TR2 { constexpr auto WORKER_SHOTGUN_NUM_SHOTS = 6; const auto WorkerShotgunBite = CreatureBiteInfo(Vector3(0, 350, 40), 9); - // TODO enum ShotgunWorkerState { - + // No state 0. + WORKER_SHOTGUN_STATE_WALK = 1, + WORKER_SHOTGUN_STATE_IDLE = 2, + WORKER_SHOTGUN_STATE_REST = 3, + WORKER_SHOTGUN_STATE_STANDING_ATTACK = 4, + WORKER_SHOTGUN_STATE_RUN = 5, + WORKER_SHOTGUN_STATE_WALKING_ATTACK = 6, + WORKER_SHOTGUN_STATE_DEATH = 7, + WORKER_SHOTGUN_STATE_STANDING_ATTACK_AIM = 8, + WORKER_SHOTGUN_STATE_KNEEL_ATTACK_AIM = 9, + WORKER_SHOTGUN_STATE_KNEEL_ATTACK = 10 }; - // TODO enum ShotgunWorkerAnim { - + WORKER_SHOTGUN_ANIM_WALK = 0, + WORKER_SHOTGUN_ANIM_STANDING_ATTACK_AIM = 1, + WORKER_SHOTGUN_ANIM_STANDING_ATTACK_SHOOT = 2, + WORKER_SHOTGUN_ANIM_STANDING_ATTACK_STOP = 3, + WORKER_SHOTGUN_ANIM_WALK_TO_IDLE = 4, + WORKER_SHOTGUN_ANIM_IDLE = 5, + WORKER_SHOTGUN_ANIM_IDLE_TO_WALK = 6, + WORKER_SHOTGUN_ANIM_WALKING_ATTACK_AIM = 7, + WORKER_SHOTGUN_ANIM_WALKING_ATTACK_SHOOT = 8, + WORKER_SHOTGUN_ANIM_REST_TO_IDLE = 9, + WORKER_SHOTGUN_ANIM_REST = 10, + WORKER_SHOTGUN_ANIM_REST_TO_STANDING_ATTACK = 11, + WORKER_SHOTGUN_ANIM_IDLE_TO_REST = 12, + WORKER_SHOTGUN_ANIM_STANDING_ATTACK_TO_IDLE = 13, + WORKER_SHOTGUN_ANIM_WALK_TO_RUN = 14, + WORKER_SHOTGUN_ANIM_RUN_TO_WALK = 15, + WORKER_SHOTGUN_ANIM_IDLE_TO_RUN = 16, + WORKER_SHOTGUN_ANIM_RUN = 17, + WORKER_SHOTGUN_ANIM_DEATH = 18, + WORKER_SHOTGUN_ANIM_KNEEL_ATTACK_AIM = 19, + WORKER_SHOTGUN_ANIM_KNEEL_ATTACK_SHOOT = 20, + WORKER_SHOTGUN_ANIM_KNEEL_ATTACK_STOP = 21 }; static void ShootWorkerShotgun(ItemInfo& item, AI_INFO& ai, const CreatureBiteInfo& bite, short headingAngle, int damage) @@ -62,8 +94,8 @@ namespace TEN::Entities::Creatures::TR2 if (item->HitPoints <= 0) { - if (item->Animation.ActiveState != 7) - SetAnimation(item, 18); + if (item->Animation.ActiveState != WORKER_SHOTGUN_STATE_DEATH) + SetAnimation(item, WORKER_SHOTGUN_ANIM_DEATH); } else { @@ -77,7 +109,7 @@ namespace TEN::Entities::Creatures::TR2 switch (item->Animation.ActiveState) { - case 2: + case WORKER_SHOTGUN_STATE_IDLE: creature->MaxTurn = 0; creature->Flags = 0; @@ -89,38 +121,38 @@ namespace TEN::Entities::Creatures::TR2 if (creature->Mood == MoodType::Escape) { - item->Animation.TargetState = 5; + item->Animation.TargetState = WORKER_SHOTGUN_STATE_RUN; } else if (Targetable(item, &ai)) { if (ai.distance < SQUARE(BLOCK(3)) || ai.zoneNumber != ai.enemyZone) { - item->Animation.TargetState = (GetRandomControl() >= 0x4000) ? 9 : 8; + item->Animation.TargetState = Random::TestProbability(1 / 2.0f) ? WORKER_SHOTGUN_STATE_KNEEL_ATTACK_AIM : WORKER_SHOTGUN_STATE_STANDING_ATTACK_AIM; } else { - item->Animation.TargetState = 1; + item->Animation.TargetState = WORKER_SHOTGUN_STATE_WALK; } } else if (creature->Mood == MoodType::Attack || !ai.ahead) { if (ai.distance <= SQUARE(BLOCK(2))) { - item->Animation.TargetState = 1; + item->Animation.TargetState = WORKER_SHOTGUN_STATE_WALK; } else { - item->Animation.TargetState = 5; + item->Animation.TargetState = WORKER_SHOTGUN_STATE_RUN; } } else { - item->Animation.TargetState = 3; + item->Animation.TargetState = WORKER_SHOTGUN_STATE_REST; } break; - case 3: + case WORKER_SHOTGUN_STATE_REST: if (ai.ahead) { extraHeadRot.x = ai.xAngle; @@ -129,16 +161,16 @@ namespace TEN::Entities::Creatures::TR2 if (Targetable(item, &ai)) { - item->Animation.TargetState = 4; + item->Animation.TargetState = WORKER_SHOTGUN_STATE_STANDING_ATTACK; } else if (creature->Mood == MoodType::Attack || !ai.ahead) { - item->Animation.TargetState = 2; + item->Animation.TargetState = WORKER_SHOTGUN_STATE_IDLE; } break; - case 1: + case WORKER_SHOTGUN_STATE_WALK: creature->MaxTurn = ANGLE(3.0f); if (ai.ahead) @@ -149,32 +181,33 @@ namespace TEN::Entities::Creatures::TR2 if (creature->Mood == MoodType::Escape) { - item->Animation.TargetState = 5; + item->Animation.TargetState = WORKER_SHOTGUN_STATE_RUN; } else if (Targetable(item, &ai)) { if (ai.distance < SQUARE(BLOCK(3)) || ai.zoneNumber != ai.enemyZone) { - item->Animation.TargetState = 2; + item->Animation.TargetState = WORKER_SHOTGUN_STATE_IDLE; } else { - item->Animation.TargetState = 6; + item->Animation.TargetState = WORKER_SHOTGUN_STATE_WALKING_ATTACK; + creature->Flags = 0; } } else if (creature->Mood == MoodType::Attack || !ai.ahead) { if (ai.distance > SQUARE(BLOCK(2))) - item->Animation.TargetState = 5; + item->Animation.TargetState = WORKER_SHOTGUN_STATE_RUN; } else { - item->Animation.TargetState = 2; + item->Animation.TargetState = WORKER_SHOTGUN_STATE_IDLE; } break; - case 5: + case WORKER_SHOTGUN_STATE_RUN: creature->MaxTurn = ANGLE(5.0f); tiltAngle = headingAngle / 2; @@ -188,18 +221,18 @@ namespace TEN::Entities::Creatures::TR2 { if (Targetable(item, &ai)) { - item->Animation.TargetState = 1; + item->Animation.TargetState = WORKER_SHOTGUN_STATE_WALK; } else if (creature->Mood == MoodType::Bored || creature->Mood == MoodType::Stalk) { - item->Animation.TargetState = 1; + item->Animation.TargetState = WORKER_SHOTGUN_STATE_WALK; } } break; - case 8: - case 9: + case WORKER_SHOTGUN_STATE_STANDING_ATTACK_AIM: + case WORKER_SHOTGUN_STATE_KNEEL_ATTACK_AIM: creature->Flags = 0; if (ai.ahead) @@ -210,20 +243,20 @@ namespace TEN::Entities::Creatures::TR2 if (Targetable(item, &ai)) { - if (item->Animation.ActiveState == 8) + if (item->Animation.ActiveState == WORKER_SHOTGUN_STATE_STANDING_ATTACK_AIM) { - item->Animation.TargetState = 4; + item->Animation.TargetState = WORKER_SHOTGUN_STATE_STANDING_ATTACK; } else { - item->Animation.TargetState = 10; + item->Animation.TargetState = WORKER_SHOTGUN_STATE_KNEEL_ATTACK; } } break; - case 4: - case 10: + case WORKER_SHOTGUN_STATE_STANDING_ATTACK: + case WORKER_SHOTGUN_STATE_KNEEL_ATTACK: if (ai.ahead) { extraTorsoRot.x = ai.xAngle; @@ -238,15 +271,15 @@ namespace TEN::Entities::Creatures::TR2 creature->Flags = 1; } - if (item->Animation.ActiveState == 4 && item->Animation.TargetState != 2 && + if (item->Animation.ActiveState == WORKER_SHOTGUN_STATE_STANDING_ATTACK && item->Animation.TargetState != WORKER_SHOTGUN_STATE_IDLE && (creature->Mood == MoodType::Escape || ai.distance > SQUARE(BLOCK(3)) || !Targetable(item, &ai))) { - item->Animation.TargetState = 2; + item->Animation.TargetState = WORKER_SHOTGUN_STATE_IDLE; } break; - case 6: + case WORKER_SHOTGUN_STATE_WALKING_ATTACK: if (ai.ahead) { extraTorsoRot.x = ai.xAngle; diff --git a/TombEngine/Objects/TR2/Trap/tr2_killerstatue.cpp b/TombEngine/Objects/TR2/Trap/tr2_killerstatue.cpp index 9bad3ef5b..f351788ae 100644 --- a/TombEngine/Objects/TR2/Trap/tr2_killerstatue.cpp +++ b/TombEngine/Objects/TR2/Trap/tr2_killerstatue.cpp @@ -9,35 +9,44 @@ #include "Game/Setup.h" #include "Specific/level.h" -void InitializeKillerStatue(short itemNumber) +namespace TEN::Entities::Traps { - auto* item = &g_Level.Items[itemNumber]; + constexpr auto KILLER_STATUE_HARM_DAMAGE = 200; - item->Animation.AnimNumber = Objects[item->ObjectNumber].animIndex + 3; - item->Animation.FrameNumber = GetAnimData(item).frameBase; - item->Animation.ActiveState = 1; -} - -void KillerStatueControl(short itemNumber) -{ - auto* item = &g_Level.Items[itemNumber]; - - if (TriggerActive(item) && item->Animation.ActiveState == 1) - item->Animation.TargetState = 2; - else - item->Animation.TargetState = 1; - - if (item->TouchBits & 0x80 && item->Animation.ActiveState == 2) + void InitializeKillerStatue(short itemNumber) { - DoDamage(LaraItem, 20); + auto& item = g_Level.Items[itemNumber]; - int x = LaraItem->Pose.Position.x + (GetRandomControl() - BLOCK(16)) / CLICK(1); - int z = LaraItem->Pose.Position.z + (GetRandomControl() - BLOCK(16)) / CLICK(1); - int y = LaraItem->Pose.Position.y - GetRandomControl() / 44; - int d = (GetRandomControl() - BLOCK(16)) / 8 + LaraItem->Pose.Orientation.y; - - DoBloodSplat(x, y, z, LaraItem->Animation.Velocity.z, d, LaraItem->RoomNumber); + item.Animation.AnimNumber = Objects[item.ObjectNumber].animIndex + 3; + item.Animation.FrameNumber = GetAnimData(item).frameBase; + item.Animation.ActiveState = 1; } - AnimateItem(item); + void ControlKillerStatue(short itemNumber) + { + auto& item = g_Level.Items[itemNumber]; + + if (TriggerActive(&item) && item.Animation.ActiveState == 1) + { + item.Animation.TargetState = 2; + } + else + { + item.Animation.TargetState = 1; + } + + if (item.TouchBits & 0x80 && item.Animation.ActiveState == 2) + { + DoDamage(LaraItem, KILLER_STATUE_HARM_DAMAGE); + + int x = LaraItem->Pose.Position.x + (GetRandomControl() - BLOCK(16)) / CLICK(1); + int z = LaraItem->Pose.Position.z + (GetRandomControl() - BLOCK(16)) / CLICK(1); + int y = LaraItem->Pose.Position.y - GetRandomControl() / 44; + int d = (GetRandomControl() - BLOCK(16)) / 8 + LaraItem->Pose.Orientation.y; + + DoBloodSplat(x, y, z, LaraItem->Animation.Velocity.z, d, LaraItem->RoomNumber); + } + + AnimateItem(&item); + } } diff --git a/TombEngine/Objects/TR2/Trap/tr2_killerstatue.h b/TombEngine/Objects/TR2/Trap/tr2_killerstatue.h index 91878614c..fac277086 100644 --- a/TombEngine/Objects/TR2/Trap/tr2_killerstatue.h +++ b/TombEngine/Objects/TR2/Trap/tr2_killerstatue.h @@ -1,4 +1,7 @@ #pragma once -void InitializeKillerStatue(short itemNumber); -void KillerStatueControl(short itemNumber); +namespace TEN::Entities::Traps +{ + void InitializeKillerStatue(short itemNumber); + void ControlKillerStatue(short itemNumber); +} diff --git a/TombEngine/Objects/TR2/Trap/tr2_spinningblade.cpp b/TombEngine/Objects/TR2/Trap/tr2_spinningblade.cpp index 9c16dfea7..f3107871b 100644 --- a/TombEngine/Objects/TR2/Trap/tr2_spinningblade.cpp +++ b/TombEngine/Objects/TR2/Trap/tr2_spinningblade.cpp @@ -3,6 +3,7 @@ #include "Game/animation.h" #include "Game/collision/collide_room.h" +#include "Game/collision/Point.h" #include "Game/control/control.h" #include "Game/effects/effects.h" #include "Game/items.h" @@ -11,58 +12,73 @@ #include "Sound/sound.h" #include "Specific/level.h" -void InitializeSpinningBlade(short itemNumber) +using namespace TEN::Collision::Point; + +namespace TEN::Entities::Traps { - auto* item = &g_Level.Items[itemNumber]; - SetAnimation(item, 3); -} + constexpr auto SPINNING_BLADE_DAMAGE = 100; -void SpinningBladeControl(short itemNumber) -{ - auto* item = &g_Level.Items[itemNumber]; - - bool isSpinning = false; - - if (item->Animation.ActiveState == 2) + void InitializeSpinningBlade(short itemNumber) { - if (item->Animation.TargetState != 1) - { - int x = item->Pose.Position.x + BLOCK(3) * phd_sin(item->Pose.Orientation.y) / 2; - int z = item->Pose.Position.z + BLOCK(3) * phd_cos(item->Pose.Orientation.y) / 2; - - int floorHeight = GetCollision(x, item->Pose.Position.y, z, item->RoomNumber).Position.Floor; - if (floorHeight == NO_HEIGHT) - item->Animation.TargetState = 1; - } - - isSpinning = true; - - if (item->TouchBits.TestAny()) - { - DoDamage(LaraItem, 100); - DoLotsOfBlood(LaraItem->Pose.Position.x, LaraItem->Pose.Position.y - CLICK(2), LaraItem->Pose.Position.z, (short)(item->Animation.Velocity.z * 2), LaraItem->Pose.Orientation.y, LaraItem->RoomNumber, 2); - } - - SoundEffect(SFX_TR2_ROLLING_BLADE, &item->Pose); - } - else - { - if (TriggerActive(item)) - item->Animation.TargetState = 2; - - isSpinning = false; + auto* item = &g_Level.Items[itemNumber]; + SetAnimation(item, 3); } - AnimateItem(item); + void ControlSpinningBlade(short itemNumber) + { + auto& item = g_Level.Items[itemNumber]; - auto probe = GetCollision(item); + bool isSpinning = false; - item->Floor = probe.Position.Floor; - item->Pose.Position.y = probe.Position.Floor; + if (item.Animation.ActiveState == 2) + { + if (item.Animation.TargetState != 1) + { + int x = item.Pose.Position.x + BLOCK(3) * phd_sin(item.Pose.Orientation.y) / 2; + int z = item.Pose.Position.z + BLOCK(3) * phd_cos(item.Pose.Orientation.y) / 2; - if (probe.RoomNumber != item->RoomNumber) - ItemNewRoom(itemNumber, probe.RoomNumber); + auto aheadPointColl = GetPointCollision(Vector3i(x, item.Pose.Position.y, z), item.RoomNumber); + int floorHeight = aheadPointColl.GetFloorHeight(); + int relFloorHeight = abs(floorHeight - item.Pose.Position.y); + int relCeilHeight = abs(aheadPointColl.GetCeilingHeight() - floorHeight); - if (isSpinning && item->Animation.ActiveState == 1) - item->Pose.Orientation.y += -ANGLE(180.0f); + if (floorHeight == NO_HEIGHT || // Is wall. + aheadPointColl.GetSector().Stopper || // Sector has stopper flag. + relCeilHeight < BLOCK(1) || // Ceiling is lower than 1 block. + relFloorHeight >= CLICK(2.5)) // Is step. + + item.Animation.TargetState = 1; + } + + isSpinning = true; + + if (item.TouchBits.TestAny()) + { + DoDamage(LaraItem, SPINNING_BLADE_DAMAGE); + DoLotsOfBlood(LaraItem->Pose.Position.x, LaraItem->Pose.Position.y - CLICK(2), LaraItem->Pose.Position.z, (short)(item.Animation.Velocity.z * 2), LaraItem->Pose.Orientation.y, LaraItem->RoomNumber, 2); + } + + SoundEffect(SFX_TR2_ROLLING_BLADE, &item.Pose); + } + else + { + if (TriggerActive(&item)) + item.Animation.TargetState = 2; + + isSpinning = false; + } + + AnimateItem(&item); + + auto pointColl = GetPointCollision(item); + + item.Floor = pointColl.GetFloorHeight(); + item.Pose.Position.y = pointColl.GetFloorHeight(); + + if (pointColl.GetRoomNumber() != item.RoomNumber) + ItemNewRoom(itemNumber, pointColl.GetRoomNumber()); + + if (isSpinning && item.Animation.ActiveState == 1) + item.Pose.Orientation.y += -ANGLE(180.0f); + } } diff --git a/TombEngine/Objects/TR2/Trap/tr2_spinningblade.h b/TombEngine/Objects/TR2/Trap/tr2_spinningblade.h index f1d321b8c..dc612b9cf 100644 --- a/TombEngine/Objects/TR2/Trap/tr2_spinningblade.h +++ b/TombEngine/Objects/TR2/Trap/tr2_spinningblade.h @@ -1,4 +1,7 @@ #pragma once -void InitializeSpinningBlade(short itemNumber); -void SpinningBladeControl(short itemNumber); +namespace TEN::Entities::Traps +{ + void InitializeSpinningBlade(short itemNumber); + void ControlSpinningBlade(short itemNumber); +} diff --git a/TombEngine/Objects/TR2/Vehicles/skidoo.cpp b/TombEngine/Objects/TR2/Vehicles/skidoo.cpp index b65904283..d13a21f71 100644 --- a/TombEngine/Objects/TR2/Vehicles/skidoo.cpp +++ b/TombEngine/Objects/TR2/Vehicles/skidoo.cpp @@ -4,7 +4,7 @@ #include "Game/animation.h" #include "Game/camera.h" #include "Game/collision/collide_item.h" -#include "Game/collision/sphere.h" +#include "Game/collision/Point.h" #include "Game/effects/effects.h" #include "Game/effects/simple_particle.h" #include "Game/effects/tomb4fx.h" @@ -21,6 +21,7 @@ #include "Math/Math.h" #include "Sound/sound.h" +using namespace TEN::Collision::Point; using namespace TEN::Input; using namespace TEN::Math; @@ -185,12 +186,12 @@ namespace TEN::Entities::Vehicles else angle = skidooItem->Pose.Orientation.y - ANGLE(90.0f); - auto probe = GetCollision(skidooItem, angle, -SKIDOO_DISMOUNT_DISTANCE); + auto probe = GetPointCollision(*skidooItem, angle, -SKIDOO_DISMOUNT_DISTANCE); - if ((probe.Position.FloorSlope || probe.Position.Floor == NO_HEIGHT) || - abs(probe.Position.Floor - skidooItem->Pose.Position.y) > CLICK(2) || - ((probe.Position.Ceiling - skidooItem->Pose.Position.y) > -LARA_HEIGHT || - (probe.Position.Floor - probe.Position.Ceiling) < LARA_HEIGHT)) + if ((probe.IsSteepFloor() || probe.GetFloorHeight() == NO_HEIGHT) || + abs(probe.GetFloorHeight() - skidooItem->Pose.Position.y) > CLICK(2) || + ((probe.GetCeilingHeight() - skidooItem->Pose.Position.y) > -LARA_HEIGHT || + (probe.GetFloorHeight() - probe.GetCeilingHeight()) < LARA_HEIGHT)) { return false; } @@ -278,7 +279,7 @@ namespace TEN::Entities::Vehicles auto heightFrontLeft = GetVehicleHeight(skidooItem, SKIDOO_FRONT, -SKIDOO_SIDE, true, &frontLeft); auto heightFrontRight = GetVehicleHeight(skidooItem, SKIDOO_FRONT, SKIDOO_SIDE, true, &frontRight); - auto probe = GetCollision(skidooItem); + auto probe = GetPointCollision(*skidooItem); TestTriggers(skidooItem, true); TestTriggers(skidooItem, false); @@ -300,7 +301,7 @@ namespace TEN::Entities::Vehicles collide = 0; } - int height = probe.Position.Floor; + int height = probe.GetFloorHeight(); int pitch = 0; if (skidooItem->Flags & IFLAG_INVISIBLE) @@ -361,10 +362,10 @@ namespace TEN::Entities::Vehicles if (skidooItem->Flags & IFLAG_INVISIBLE) { - if (probe.RoomNumber != skidooItem->RoomNumber) + if (probe.GetRoomNumber() != skidooItem->RoomNumber) { - ItemNewRoom(lara->Context.Vehicle, probe.RoomNumber); - ItemNewRoom(laraItem->Index, probe.RoomNumber); + ItemNewRoom(lara->Context.Vehicle, probe.GetRoomNumber()); + ItemNewRoom(laraItem->Index, probe.GetRoomNumber()); } AnimateItem(laraItem); @@ -377,10 +378,10 @@ namespace TEN::Entities::Vehicles SkidooAnimation(skidooItem, laraItem, collide, dead); - if (probe.RoomNumber != skidooItem->RoomNumber) + if (probe.GetRoomNumber() != skidooItem->RoomNumber) { - ItemNewRoom(lara->Context.Vehicle, probe.RoomNumber); - ItemNewRoom(laraItem->Index, probe.RoomNumber); + ItemNewRoom(lara->Context.Vehicle, probe.GetRoomNumber()); + ItemNewRoom(laraItem->Index, probe.GetRoomNumber()); } if (laraItem->Animation.ActiveState != SKIDOO_STATE_FALLOFF) @@ -690,8 +691,8 @@ namespace TEN::Entities::Vehicles void DoSnowEffect(ItemInfo* skidooItem) { - auto pointColl = GetCollision(skidooItem); - auto material = pointColl.BottomBlock->GetSurfaceMaterial(pointColl.Coordinates.x, pointColl.Coordinates.z, true); + auto pointColl = GetPointCollision(*skidooItem); + auto material = pointColl.GetBottomSector().GetSurfaceMaterial(pointColl.GetPosition().x, pointColl.GetPosition().z, true); if (material != MaterialType::Ice && material != MaterialType::Snow) return; @@ -773,8 +774,8 @@ namespace TEN::Entities::Vehicles x = 0; z = 0; - auto probe = GetCollision(old->x, pos->y, pos->z, skidooItem->RoomNumber); - if (probe.Position.Floor < (old->y - CLICK(1))) + auto probe = GetPointCollision(Vector3i(old->x, pos->y, pos->z), skidooItem->RoomNumber); + if (probe.GetFloorHeight() < (old->y - CLICK(1))) { if (pos->z > old->z) z = -shiftZ - 1; @@ -782,8 +783,8 @@ namespace TEN::Entities::Vehicles z = BLOCK(1) - shiftZ; } - probe = GetCollision(pos->x, pos->y, old->z, skidooItem->RoomNumber); - if (probe.Position.Floor < (old->y - CLICK(1))) + probe = GetPointCollision(Vector3i(pos->x, pos->y, old->z), skidooItem->RoomNumber); + if (probe.GetFloorHeight() < (old->y - CLICK(1))) { if (pos->x > old->x) x = -shiftX - 1; @@ -920,8 +921,8 @@ namespace TEN::Entities::Vehicles if (heightFrontRight < (frontRightOld.y - CLICK(1))) rotation += DoSkidooShift(skidooItem, &frontRight, &frontRightOld); - auto probe = GetCollision(skidooItem); - if (probe.Position.Floor < (skidooItem->Pose.Position.y - CLICK(1))) + auto probe = GetPointCollision(*skidooItem); + if (probe.GetFloorHeight() < (skidooItem->Pose.Position.y - CLICK(1))) DoSkidooShift(skidooItem, (Vector3i*)&skidooItem->Pose, &oldPos); skidoo->ExtraRotation = rotation; diff --git a/TombEngine/Objects/TR2/Vehicles/speedboat.cpp b/TombEngine/Objects/TR2/Vehicles/speedboat.cpp index 8f6ced39e..7b7310172 100644 --- a/TombEngine/Objects/TR2/Vehicles/speedboat.cpp +++ b/TombEngine/Objects/TR2/Vehicles/speedboat.cpp @@ -4,7 +4,7 @@ #include "Game/animation.h" #include "Game/camera.h" #include "Game/collision/collide_item.h" -#include "Game/collision/sphere.h" +#include "Game/collision/Point.h" #include "Game/effects/effects.h" #include "Game/effects/simple_particle.h" #include "Game/items.h" @@ -17,6 +17,7 @@ #include "Specific/Input/Input.h" #include "Specific/level.h" +using namespace TEN::Collision::Point; using namespace TEN::Input; namespace TEN::Entities::Vehicles @@ -237,19 +238,19 @@ namespace TEN::Entities::Vehicles int x = speedboatItem->Pose.Position.x + SPEEDBOAT_DISMOUNT_DISTANCE * phd_sin(angle); int y = speedboatItem->Pose.Position.y; int z = speedboatItem->Pose.Position.z + SPEEDBOAT_DISMOUNT_DISTANCE * phd_cos(angle); - auto probe = GetCollision(x, y, z, speedboatItem->RoomNumber); + auto probe = GetPointCollision(Vector3i(x, y, z), speedboatItem->RoomNumber); - if ((probe.Position.Floor - speedboatItem->Pose.Position.y) < -CLICK(2)) + if ((probe.GetFloorHeight() - speedboatItem->Pose.Position.y) < -CLICK(2)) return false; - if (probe.Position.FloorSlope || - probe.Position.Floor == NO_HEIGHT) + if (probe.IsSteepFloor() || + probe.GetFloorHeight() == NO_HEIGHT) { return false; } - if ((probe.Position.Floor - probe.Position.Ceiling) < LARA_HEIGHT || - (probe.Position.Ceiling - speedboatItem->Pose.Position.y) > -LARA_HEIGHT) + if ((probe.GetFloorHeight() - probe.GetCeilingHeight()) < LARA_HEIGHT || + (probe.GetCeilingHeight() - speedboatItem->Pose.Position.y) > -LARA_HEIGHT) { return false; } @@ -281,15 +282,15 @@ namespace TEN::Entities::Vehicles int x = laraItem->Pose.Position.x + 360 * phd_sin(laraItem->Pose.Orientation.y); int y = laraItem->Pose.Position.y - 90; int z = laraItem->Pose.Position.z + 360 * phd_cos(laraItem->Pose.Orientation.y); - auto probe = GetCollision(x, y, z, laraItem->RoomNumber); + auto probe = GetPointCollision(Vector3i(x, y, z), laraItem->RoomNumber); - if (probe.Position.Floor >= (y - CLICK(1))) + if (probe.GetFloorHeight() >= (y - CLICK(1))) { laraItem->Pose.Position.x = x; laraItem->Pose.Position.z = z; - if (probe.RoomNumber != laraItem->RoomNumber) - ItemNewRoom(laraItem->Index, probe.RoomNumber); + if (probe.GetRoomNumber() != laraItem->RoomNumber) + ItemNewRoom(laraItem->Index, probe.GetRoomNumber()); } laraItem->Pose.Position.y = y; @@ -373,8 +374,8 @@ namespace TEN::Entities::Vehicles x = 0; z = 0; - auto probe = GetCollision(old->x, pos->y, pos->z, speedboatItem->RoomNumber); - if (probe.Position.Floor < (old->y - CLICK(1))) + auto probe = GetPointCollision(Vector3i(old->x, pos->y, pos->z), speedboatItem->RoomNumber); + if (probe.GetFloorHeight() < (old->y - CLICK(1))) { if (pos->z > old->z) z = -shiftZ - 1; @@ -382,8 +383,8 @@ namespace TEN::Entities::Vehicles z = BLOCK(1) - shiftZ; } - probe = GetCollision(pos->x, pos->y, old->z, speedboatItem->RoomNumber); - if (probe.Position.Floor < (old->y - CLICK(1))) + probe = GetPointCollision(Vector3i(pos->x, pos->y, old->z), speedboatItem->RoomNumber); + if (probe.GetFloorHeight() < (old->y - CLICK(1))) { if (pos->x > old->x) x = -shiftX - 1; @@ -547,11 +548,11 @@ namespace TEN::Entities::Vehicles SpeedboatDoShift(speedboatItem, &f, &frontOld); } - auto probe = GetCollision(speedboatItem); - auto height = GetWaterHeight(speedboatItem->Pose.Position.x, speedboatItem->Pose.Position.y - 5, speedboatItem->Pose.Position.z, probe.RoomNumber); + auto pointColl = GetPointCollision(*speedboatItem); + auto height = GetPointCollision(Vector3i(speedboatItem->Pose.Position.x, speedboatItem->Pose.Position.y - 5, speedboatItem->Pose.Position.z), pointColl.GetRoomNumber()).GetWaterTopHeight(); if (height == NO_HEIGHT) - height = GetFloorHeight(probe.Block, speedboatItem->Pose.Position.x, speedboatItem->Pose.Position.y - 5, speedboatItem->Pose.Position.z); + height = GetFloorHeight(&pointColl.GetSector(), speedboatItem->Pose.Position.x, speedboatItem->Pose.Position.y - 5, speedboatItem->Pose.Position.z); if (height < (speedboatItem->Pose.Position.y - CLICK(0.5f))) SpeedboatDoShift(speedboatItem, (Vector3i*)&speedboatItem->Pose, &old); @@ -815,7 +816,7 @@ namespace TEN::Entities::Vehicles int heightFrontLeft = GetVehicleWaterHeight(speedboatItem, SPEEDBOAT_FRONT, -SPEEDBOAT_SIDE, true, &frontLeft); int heightFrontRight = GetVehicleWaterHeight(speedboatItem, SPEEDBOAT_FRONT, SPEEDBOAT_SIDE, true, &frontRight); - auto probe = GetCollision(speedboatItem); + auto probe = GetPointCollision(*speedboatItem); if (lara->Context.Vehicle == itemNumber) { @@ -823,7 +824,7 @@ namespace TEN::Entities::Vehicles TestTriggers(speedboatItem, false); } - auto water = GetWaterHeight(speedboatItem->Pose.Position.x, speedboatItem->Pose.Position.y, speedboatItem->Pose.Position.z, probe.RoomNumber); + auto water = GetPointCollision(Vector3i(speedboatItem->Pose.Position.x, speedboatItem->Pose.Position.y, speedboatItem->Pose.Position.z), probe.GetRoomNumber()).GetWaterTopHeight(); speedboat->Water = water; bool noTurn = true; @@ -864,9 +865,9 @@ namespace TEN::Entities::Vehicles speedboat->TurnRate = 0; } - speedboatItem->Floor = probe.Position.Floor - 5; + speedboatItem->Floor = probe.GetFloorHeight() - 5; if (speedboat->Water == NO_HEIGHT) - speedboat->Water = probe.Position.Floor; + speedboat->Water = probe.GetFloorHeight(); else speedboat->Water -= 5; @@ -878,14 +879,18 @@ namespace TEN::Entities::Vehicles if (ofs - speedboatItem->Animation.Velocity.y > 32 && speedboatItem->Animation.Velocity.y == 0 && water != NO_HEIGHT) SpeedboatSplash(speedboatItem, ofs - speedboatItem->Animation.Velocity.y, water); - probe.Position.Floor = (frontLeft.y + frontRight.y); - if (probe.Position.Floor < 0) - probe.Position.Floor = -(abs(probe.Position.Floor) / 2); + int floorHeight = (frontLeft.y + frontRight.y); + if (floorHeight < 0) + { + floorHeight = -(abs(floorHeight) / 2); + } else - probe.Position.Floor /= 2; + { + floorHeight /= 2; + } - short xRot = phd_atan(SPEEDBOAT_FRONT, speedboatItem->Pose.Position.y - probe.Position.Floor); - short zRot = phd_atan(SPEEDBOAT_SIDE, probe.Position.Floor - frontLeft.y); + short xRot = phd_atan(SPEEDBOAT_FRONT, speedboatItem->Pose.Position.y - floorHeight); + short zRot = phd_atan(SPEEDBOAT_SIDE, floorHeight - frontLeft.y); speedboatItem->Pose.Orientation.x += ((xRot - speedboatItem->Pose.Orientation.x) / 2); speedboatItem->Pose.Orientation.z += ((zRot - speedboatItem->Pose.Orientation.z) / 2); @@ -899,10 +904,10 @@ namespace TEN::Entities::Vehicles { SpeedboatAnimation(speedboatItem, laraItem, collide); - if (probe.RoomNumber != speedboatItem->RoomNumber) + if (probe.GetRoomNumber() != speedboatItem->RoomNumber) { - ItemNewRoom(lara->Context.Vehicle, probe.RoomNumber); - ItemNewRoom(laraItem->Index, probe.RoomNumber); + ItemNewRoom(lara->Context.Vehicle, probe.GetRoomNumber()); + ItemNewRoom(laraItem->Index, probe.GetRoomNumber()); } laraItem->Pose = speedboatItem->Pose; @@ -930,8 +935,8 @@ namespace TEN::Entities::Vehicles } else { - if (probe.RoomNumber != speedboatItem->RoomNumber) - ItemNewRoom(itemNumber, probe.RoomNumber); + if (probe.GetRoomNumber() != speedboatItem->RoomNumber) + ItemNewRoom(itemNumber, probe.GetRoomNumber()); speedboatItem->Pose.Orientation.z += speedboat->LeanAngle; } @@ -941,7 +946,7 @@ namespace TEN::Entities::Vehicles if (speedboatItem->Animation.Velocity.z && (water - 5) == speedboatItem->Pose.Position.y) { - auto roomNumber = probe.Block->GetNextRoomNumber(speedboatItem->Pose.Position.x, speedboatItem->Pose.Position.z, true); + auto roomNumber = probe.GetSector().GetNextRoomNumber(speedboatItem->Pose.Position.x, speedboatItem->Pose.Position.z, true); if (roomNumber.has_value() && (TestEnvironment(RoomEnvFlags::ENV_FLAG_WATER, *roomNumber) || TestEnvironment(RoomEnvFlags::ENV_FLAG_SWAMP, *roomNumber))) { @@ -962,7 +967,7 @@ namespace TEN::Entities::Vehicles TEN::Effects::TriggerSpeedboatFoam(speedboatItem, Vector3(0.0f, 0.0f, SPEEDBOAT_BACK)); } - int waterHeight = GetWaterHeight(speedboatItem); + int waterHeight = GetPointCollision(*speedboatItem).GetWaterTopHeight(); SpawnVehicleWake(*speedboatItem, SPEEDBOAT_WAKE_OFFSET, waterHeight); } } diff --git a/TombEngine/Objects/TR2/tr2_objects.cpp b/TombEngine/Objects/TR2/tr2_objects.cpp index 98462ec63..c69e12e3f 100644 --- a/TombEngine/Objects/TR2/tr2_objects.cpp +++ b/TombEngine/Objects/TR2/tr2_objects.cpp @@ -41,6 +41,7 @@ #include "Objects/TR2/Vehicles/skidoo.h" using namespace TEN::Entities::Creatures::TR2; +using namespace TEN::Entities::Traps; static void StartEntity(ObjectInfo* obj) { @@ -534,7 +535,7 @@ static void StartTrap(ObjectInfo* obj) if (obj->loaded) { obj->Initialize = InitializeSpinningBlade; - obj->control = SpinningBladeControl; + obj->control = ControlSpinningBlade; obj->collision = ObjectCollision; } @@ -548,7 +549,7 @@ static void StartTrap(ObjectInfo* obj) if (obj->loaded) { obj->Initialize = InitializeKillerStatue; - obj->control = KillerStatueControl; + obj->control = ControlKillerStatue; obj->collision = ObjectCollision; obj->SetHitEffect(true); } diff --git a/TombEngine/Objects/TR3/Entity/FishSwarm.cpp b/TombEngine/Objects/TR3/Entity/FishSwarm.cpp index 30808cdc8..ae67854f0 100644 --- a/TombEngine/Objects/TR3/Entity/FishSwarm.cpp +++ b/TombEngine/Objects/TR3/Entity/FishSwarm.cpp @@ -3,6 +3,7 @@ #include "Game/collision/collide_item.h" #include "Game/collision/collide_room.h" +#include "Game/collision/Point.h" #include "Game/control/box.h" #include "Game/control/flipeffect.h" #include "Game/effects/effects.h" @@ -18,6 +19,7 @@ #include "Specific/clock.h" #include "Specific/level.h" +using namespace TEN::Collision::Point; using namespace TEN::Entities::TR3; using namespace TEN::Math; using namespace TEN::Renderer; @@ -182,7 +184,7 @@ namespace TEN::Entities::Creatures::TR3 // Follow path. if (item.AIBits && !item.ItemFlags[4]) { - FindAITargetObject(&creature, ID_AI_FOLLOW, item.ItemFlags[3] + item.ItemFlags[2], false); + FindAITargetObject(item, ID_AI_FOLLOW, item.ItemFlags[3] + item.ItemFlags[2], false); if (creature.AITarget->TriggerFlags == (item.ItemFlags[3] + item.ItemFlags[2]) && creature.AITarget->ObjectNumber == ID_AI_FOLLOW) @@ -218,18 +220,17 @@ namespace TEN::Entities::Creatures::TR3 auto pos = Random::GeneratePointInSpheroid(item.StartPose.Position.ToVector3(), EulerAngles::Identity, SPHEROID_SEMI_MAJOR_AXIS); // Get point collision. - auto pointColl = GetCollision(pos, item.RoomNumber); - int waterHeight = GetWaterHeight(pointColl.Coordinates.x, pointColl.Coordinates.y, pointColl.Coordinates.z, pointColl.RoomNumber); + auto pointColl = GetPointCollision(pos, item.RoomNumber); // 1) Test for water room. - if (!TestEnvironment(ENV_FLAG_WATER, pointColl.RoomNumber)) + if (!TestEnvironment(ENV_FLAG_WATER, pointColl.GetRoomNumber())) return Vector3::Zero; // 2) Assess point collision. - if (pos.y >= (pointColl.Position.Floor - BUFFER) || - pos.y <= (waterHeight + BUFFER) || - pointColl.Block->IsWall(item.Pose.Position.x + BUFFER, item.Pose.Position.z + BUFFER) || - pointColl.Block->IsWall(item.Pose.Position.x - BUFFER, item.Pose.Position.z - BUFFER)) + if (pos.y >= (pointColl.GetFloorHeight() - BUFFER) || + pos.y <= (pointColl.GetWaterTopHeight() + BUFFER) || + pointColl.GetSector().IsWall(item.Pose.Position.x + BUFFER, item.Pose.Position.z + BUFFER) || + pointColl.GetSector().IsWall(item.Pose.Position.x - BUFFER, item.Pose.Position.z - BUFFER)) { return Vector3::Zero; } @@ -260,6 +261,8 @@ namespace TEN::Entities::Creatures::TR3 if (fish.Life <= 0.0f) continue; + fish.StoreInterpolationData(); + // Increase separation distance for each fish. float separationDist = FISH_BASE_SEPARATION_DISTANCE + (fishID * 3); fishID += 1; @@ -366,19 +369,19 @@ namespace TEN::Entities::Creatures::TR3 } } - auto pointColl = GetCollision(fish.Position, fish.RoomNumber); + auto pointColl = GetPointCollision(fish.Position, fish.RoomNumber); const auto& room = g_Level.Rooms[fish.RoomNumber]; // Update fish room number. - if (pointColl.RoomNumber != fish.RoomNumber && - pointColl.RoomNumber != NO_VALUE && - TestEnvironment(ENV_FLAG_WATER, pointColl.RoomNumber)) + if (pointColl.GetRoomNumber() != fish.RoomNumber && + pointColl.GetRoomNumber() != NO_VALUE && + TestEnvironment(ENV_FLAG_WATER, pointColl.GetRoomNumber())) { - fish.RoomNumber = pointColl.RoomNumber; + fish.RoomNumber = pointColl.GetRoomNumber(); } // Clamp position to slightly below water surface. - int waterHeight = GetWaterHeight(fish.Position.x, fish.Position.y, fish.Position.z, fish.RoomNumber); + int waterHeight = pointColl.GetWaterTopHeight(); if (fish.Position.y < (waterHeight + WATER_SURFACE_OFFSET)) fish.Position.y = waterHeight + WATER_SURFACE_OFFSET; @@ -415,6 +418,8 @@ namespace TEN::Entities::Creatures::TR3 fish.Undulation += std::clamp(movementValue / 2, 0.3f, 1.0f); if (fish.Undulation > PI_MUL_2) fish.Undulation -= PI_MUL_2; + + fish.Transform = fish.Orientation.ToRotationMatrix() * Matrix::CreateTranslation(fish.Position); } } diff --git a/TombEngine/Objects/TR3/Entity/FishSwarm.h b/TombEngine/Objects/TR3/Entity/FishSwarm.h index 1a1dfe471..6aa4c6962 100644 --- a/TombEngine/Objects/TR3/Entity/FishSwarm.h +++ b/TombEngine/Objects/TR3/Entity/FishSwarm.h @@ -25,6 +25,14 @@ namespace TEN::Entities::Creatures::TR3 ItemInfo* TargetItemPtr = nullptr; ItemInfo* LeaderItemPtr = nullptr; + + Matrix Transform = Matrix::Identity; + Matrix PrevTransform = Matrix::Identity; + + void StoreInterpolationData() + { + PrevTransform = Transform; + } }; extern std::vector FishSwarm; diff --git a/TombEngine/Objects/TR3/Entity/Lizard.cpp b/TombEngine/Objects/TR3/Entity/Lizard.cpp index d5a10e72b..ee5ba0347 100644 --- a/TombEngine/Objects/TR3/Entity/Lizard.cpp +++ b/TombEngine/Objects/TR3/Entity/Lizard.cpp @@ -69,17 +69,20 @@ namespace TEN::Entities::Creatures::TR3 auto& creature = *GetCreatureInfo(&item); return (creature.Enemy && creature.Enemy->BoxNumber != NO_VALUE && - (g_Level.Boxes[creature.Enemy->BoxNumber].flags & BLOCKABLE)); + (g_Level.PathfindingBoxes[creature.Enemy->BoxNumber].flags & BLOCKABLE)); } - static void SpawnLizardGas(int itemNumber, const CreatureBiteInfo& bite, int speed) + static void SpawnLizardGas(const ItemInfo& item, const CreatureBiteInfo& bite, float vel) { - constexpr auto THROW_COUNT = 2; + constexpr auto THROW_COUNT = 3; + auto velVector = Vector3(0.0f, -100.0f, vel * 4); for (int i = 0; i < THROW_COUNT; i++) - ThrowPoison(itemNumber, bite, Vector3i(0.0f, -100.0f, speed << 2), Vector3(0.0f, 1.0f, 0.0f)); - - ThrowPoison(itemNumber, bite, Vector3i(0.0f, -100.0f, speed << 1), Vector3(0.0f, 1.0f, 0.0f)); + { + auto colorStart = Color(0.0f, Random::GenerateFloat(0.25f, 0.5f), 0.1f); + auto colorEnd = Color(0.0f, Random::GenerateFloat(0.1f, 0.2f), 0.1f); + ThrowPoison(item, bite, velVector, colorStart, colorEnd); + } } void LizardControl(short itemNumber) @@ -319,9 +322,13 @@ namespace TEN::Entities::Creatures::TR3 creature.Flags += 2; if (creature.Flags < 24) - SpawnLizardGas(itemNumber, LizardGasBite, creature.Flags); + { + SpawnLizardGas(item, LizardGasBite, creature.Flags); + } else - SpawnLizardGas(itemNumber, LizardGasBite, (GetRandomControl() & 15) + 8); + { + SpawnLizardGas(item, LizardGasBite, (GetRandomControl() & 15) + 8); + } } if (TestAnimFrame(item, 28)) diff --git a/TombEngine/Objects/TR3/Entity/Raptor.cpp b/TombEngine/Objects/TR3/Entity/Raptor.cpp new file mode 100644 index 000000000..b74994521 --- /dev/null +++ b/TombEngine/Objects/TR3/Entity/Raptor.cpp @@ -0,0 +1,450 @@ +#include "framework.h" +#include "Objects/TR3/Entity/Raptor.h" + +#include "Game/collision/Point.h" +#include "Game/control/box.h" +#include "Game/control/control.h" +#include "Game/control/lot.h" +#include "Game/effects/effects.h" +#include "Game/items.h" +#include "Game/itemdata/creature_info.h" +#include "Game/Lara/lara.h" +#include "Game/misc.h" +#include "Game/Setup.h" +#include "Math/Math.h" +#include "Specific/level.h" + +using namespace TEN::Collision::Point; +using namespace TEN::Math; + +namespace TEN::Entities::Creatures::TR3 +{ + constexpr auto RAPTOR_ATTACK_DAMAGE = 100; + + constexpr auto RAPTOR_BITE_ATTACK_RANGE = SQUARE(BLOCK(0.6f)); + constexpr auto RAPTOR_JUMP_ATTACK_RANGE = SQUARE(BLOCK(1.5f)); + constexpr auto RAPTOR_RUN_ATTACK_RANGE = SQUARE(BLOCK(1.5f)); + + constexpr auto RAPTOR_ROAR_CHANCE = 1.0f / 256; + constexpr auto RAPTOR_SWITCH_TARGET_CHANCE = 1.0f / 128; + + constexpr auto RAPTOR_WALK_TURN_RATE_MAX = ANGLE(2.0f); + constexpr auto RAPTOR_RUN_TURN_RATE_MAX = ANGLE(4.0f); + constexpr auto RAPTOR_ATTACK_TURN_RATE_MAX = ANGLE(2.0f); + + const auto RaptorBite = CreatureBiteInfo(Vector3(0, 66, 318), 22); + const auto RaptorAttackJoints = std::vector{ 10, 11, 12, 13, 14, 16, 17, 18, 19, 20, 21, 22, 23 }; + + enum RaptorState + { + RAPTOR_STATE_DEATH = 0, + RAPTOR_STATE_IDLE = 1, + RAPTOR_STATE_WALK_FORWARD = 2, + RAPTOR_STATE_RUN_FORWARD = 3, + RAPTOR_STATE_JUMP_ATTACK = 4, + RAPTOR_STATE_NONE = 5, + RAPTOR_STATE_ROAR = 6, + RAPTOR_STATE_RUN_BITE_ATTACK = 7, + RAPTOR_STATE_BITE_ATTACK = 8, + RAPTOR_STATE_JUMP_START = 9, + RAPTOR_STATE_JUMP_2_BLOCKS = 10, + RAPTOR_STATE_JUMP_1_BLOCK = 11, + RAPTOR_STATE_CLIMB = 12 + }; + + enum RaptorAnim + { + RAPTOR_ANIM_IDLE = 0, + RAPTOR_ANIM_RUN_FORWARD = 1, + RAPTOR_ANIM_RUN_FORWARD_TO_IDLE = 2, + RAPTOR_ANIM_IDLE_TO_RUN_FORWARD = 3, + RAPTOR_ANIM_ROAR = 4, + RAPTOR_ANIM_WALK_FORWARD = 5, + RAPTOR_ANIM_WALK_FORWARD_TO_IDLE = 6, + RAPTOR_ANIM_IDLE_TO_WALK_FORWARD = 7, + RAPTOR_ANIM_RUN_BITE_ATTACK = 8, + RAPTOR_ANIM_DEATH_1 = 9, + RAPTOR_ANIM_DEATH_2 = 10, + RAPTOR_ANIM_JUMP_ATTACK_START = 11, + RAPTOR_ANIM_JUMP_ATTACK_END = 12, + RAPTOR_ANIM_BITE_ATTACK = 13, + RAPTOR_ANIM_JUMP_START = 14, + RAPTOR_ANIM_JUMP_2_BLOCKS = 15, + RAPTOR_ANIM_JUMP_END = 16, + RAPTOR_ANIM_JUMP_1_BLOCK = 17, + RAPTOR_ANIM_VAULT_2_STEPS = 18, + RAPTOR_ANIM_VAULT_3_STEPS = 19, + RAPTOR_ANIM_VAULT_4_STEPS = 20, + RAPTOR_ANIM_VAULT_DROP_2_STEPS = 21, + RAPTOR_ANIM_VAULT_DROP_3_STEPS = 22, + RAPTOR_ANIM_VAULT_DROP_4_STEPS = 23 + }; + + enum RaptorFlags + { + OCB_NORMAL_BEHAVIOUR = 0, + OCB_ENABLE_JUMP = 1 + }; + + const std::array RaptorDeathAnims = { RAPTOR_ANIM_DEATH_1, RAPTOR_ANIM_DEATH_2, }; + const std::vector RaptorIgnoredObjectIds = { ID_RAPTOR, ID_COMPSOGNATHUS }; + + void RaptorControl(short itemNumber) + { + if (!CreatureActive(itemNumber)) + return; + + auto& item = g_Level.Items[itemNumber]; + auto& creature = *GetCreatureInfo(&item); + + short headingAngle = 0; + short tiltAngle = 0; + short headYOrient = 0; + short neckYOrient = 0; + + bool canJump = item.TestOcb(OCB_ENABLE_JUMP); + if (!canJump) + { + creature.LOT.Step = CLICK(1); + creature.LOT.Drop = -CLICK(2); + creature.LOT.Zone = ZoneType::Basic; + creature.LOT.CanJump = false; + } + + bool canJump1block = (canJump && CanCreatureJump(item, JumpDistance::Block1)); + bool canJump2blocks = (canJump && !canJump1block && CanCreatureJump(item, JumpDistance::Block2)); + + // Require Idle state. + if (item.HitPoints <= 0 && item.Animation.ActiveState == RAPTOR_STATE_IDLE) + { + if (item.Animation.ActiveState != RAPTOR_STATE_DEATH) + SetAnimation(item, RaptorDeathAnims[Random::GenerateInt(0, (int)RaptorDeathAnims.size() - 1)]); + } + else + { + // NOTE: Ignores other small dinosaurs. + TargetNearestEntity(item, RaptorIgnoredObjectIds); + + AI_INFO ai; + if (item.AIBits) + GetAITarget(&creature); + CreatureAIInfo(&item, &ai); + + GetCreatureMood(&item, &ai, true); + CreatureMood(&item, &ai, true); + if (creature.Mood == MoodType::Bored) + creature.MaxTurn /= 2; + + headingAngle = CreatureTurn(&item, creature.MaxTurn); + if (ai.ahead) + { + headYOrient = ai.angle; + neckYOrient = -headingAngle * 6; + } + + switch (item.Animation.ActiveState) + { + case RAPTOR_STATE_IDLE: + creature.MaxTurn = 0; + creature.LOT.IsJumping = false; + creature.Flags &= ~1; + + if (canJump1block || canJump2blocks) + { + creature.MaxTurn = 0; + creature.LOT.IsJumping = true; + SetAnimation(item, RAPTOR_ANIM_JUMP_START); + + if (canJump1block) + { + item.Animation.TargetState = RAPTOR_STATE_JUMP_1_BLOCK; + } + else + { + item.Animation.TargetState = RAPTOR_STATE_JUMP_2_BLOCKS; + } + } + else if (item.Animation.RequiredState != NO_VALUE) + { + item.Animation.TargetState = item.Animation.RequiredState; + } + else if (creature.Flags & 2) + { + creature.Flags &= ~2; + item.Animation.TargetState = RAPTOR_STATE_ROAR; + } + else if (item.TouchBits.Test(RaptorAttackJoints) || + (ai.distance < RAPTOR_BITE_ATTACK_RANGE && ai.bite)) + { + item.Animation.TargetState = RAPTOR_STATE_BITE_ATTACK; + } + else if (ai.bite && ai.distance < RAPTOR_JUMP_ATTACK_RANGE) + { + item.Animation.TargetState = RAPTOR_STATE_JUMP_ATTACK; + } + else if (creature.Mood == MoodType::Escape && + Lara.TargetEntity != &item && ai.ahead && !item.HitStatus) + { + item.Animation.TargetState = RAPTOR_STATE_IDLE; + } + else if (creature.Mood == MoodType::Bored) + { + item.Animation.TargetState = RAPTOR_STATE_WALK_FORWARD; + } + else + { + item.Animation.TargetState = RAPTOR_STATE_RUN_FORWARD; + } + + break; + + case RAPTOR_STATE_WALK_FORWARD: + creature.MaxTurn = RAPTOR_WALK_TURN_RATE_MAX; + creature.LOT.IsJumping = false; + creature.Flags &= ~1; + + if (item.HitPoints <= 0) + { + item.Animation.TargetState = RAPTOR_STATE_IDLE; + } + else if (canJump1block || canJump2blocks) + { + creature.MaxTurn = 0; + creature.LOT.IsJumping = true; + SetAnimation(item, RAPTOR_ANIM_JUMP_START); + + if (canJump1block) + { + item.Animation.TargetState = RAPTOR_STATE_JUMP_1_BLOCK; + } + else + { + item.Animation.TargetState = RAPTOR_STATE_JUMP_2_BLOCKS; + } + } + else if (creature.Mood != MoodType::Bored) + { + item.Animation.TargetState = RAPTOR_STATE_IDLE; + } + else if (ai.ahead && Random::TestProbability(RAPTOR_ROAR_CHANCE)) + { + item.Animation.TargetState = RAPTOR_STATE_IDLE; + item.Animation.RequiredState = RAPTOR_STATE_ROAR; + creature.Flags &= ~2; + } + + break; + + case RAPTOR_STATE_RUN_FORWARD: + creature.MaxTurn = RAPTOR_RUN_TURN_RATE_MAX; + creature.LOT.IsJumping = false; + creature.Flags &= ~1; + tiltAngle = headingAngle; + + if (item.HitPoints <= 0) + { + item.Animation.TargetState = RAPTOR_STATE_IDLE; + } + else if (canJump1block || canJump2blocks) + { + creature.MaxTurn = 0; + creature.LOT.IsJumping = true; + SetAnimation(item, RAPTOR_ANIM_JUMP_START); + + if (canJump1block) + { + item.Animation.TargetState = RAPTOR_STATE_JUMP_1_BLOCK; + } + else + { + item.Animation.TargetState = RAPTOR_STATE_JUMP_2_BLOCKS; + } + } + else if (item.TouchBits.Test(RaptorAttackJoints)) + { + item.Animation.TargetState = RAPTOR_STATE_IDLE; + } + else if (creature.Flags & 2) + { + item.Animation.TargetState = RAPTOR_STATE_IDLE; + item.Animation.RequiredState = RAPTOR_STATE_ROAR; + creature.Flags &= ~2; + } + else if (ai.bite && ai.distance < RAPTOR_RUN_ATTACK_RANGE) + { + if (Random::TestProbability(1 / 4.0f)) + { + item.Animation.TargetState = RAPTOR_STATE_IDLE; + } + else + { + item.Animation.TargetState = RAPTOR_STATE_RUN_BITE_ATTACK; + } + } + else if (ai.ahead && creature.Mood != MoodType::Escape && + Random::TestProbability(RAPTOR_ROAR_CHANCE)) + { + item.Animation.TargetState = RAPTOR_STATE_IDLE; + item.Animation.RequiredState = RAPTOR_STATE_ROAR; + } + else if (creature.Mood == MoodType::Bored || + (creature.Mood == MoodType::Escape && Lara.TargetEntity != &item && ai.ahead)) + { + item.Animation.TargetState = RAPTOR_STATE_IDLE; + } + + break; + + case RAPTOR_STATE_JUMP_ATTACK: + creature.MaxTurn = RAPTOR_ATTACK_TURN_RATE_MAX; + tiltAngle = headingAngle; + + if (creature.Enemy != nullptr) + { + if (creature.Enemy->IsLara() && !(creature.Flags & 1) && + item.TouchBits.Test(RaptorAttackJoints)) + { + DoDamage(creature.Enemy, RAPTOR_ATTACK_DAMAGE); + CreatureEffect(&item, RaptorBite, DoBloodSplat); + creature.Flags |= 1; + + if (LaraItem->HitPoints <= 0) + creature.Flags |= 2; + + item.Animation.RequiredState = RAPTOR_STATE_IDLE; + } + else if (!(creature.Flags & 1)) + { + if (Vector3i::Distance(item.Pose.Position, creature.Enemy->Pose.Position) <= BLOCK(0.5f)) + { + if (creature.Enemy->HitPoints <= 0) + creature.Flags |= 2; + + DoDamage(creature.Enemy, 25); + CreatureEffect(&item, RaptorBite, DoBloodSplat); + creature.Flags |= 1; + } + } + } + + break; + + case RAPTOR_STATE_BITE_ATTACK: + creature.MaxTurn = RAPTOR_ATTACK_TURN_RATE_MAX; + tiltAngle = headingAngle; + + if (creature.Enemy != nullptr) + { + if (creature.Enemy->IsLara() && !(creature.Flags & 1) && + item.TouchBits.Test(RaptorAttackJoints)) + { + DoDamage(creature.Enemy, RAPTOR_ATTACK_DAMAGE); + CreatureEffect(&item, RaptorBite, DoBloodSplat); + creature.Flags |= 1; + + if (LaraItem->HitPoints <= 0) + creature.Flags |= 2; + + item.Animation.RequiredState = RAPTOR_STATE_IDLE; + } + else if (!(creature.Flags & 1)) + { + if (Vector3i::Distance(item.Pose.Position, creature.Enemy->Pose.Position) <= BLOCK(0.5f)) + { + if (creature.Enemy->HitPoints <= 0) + creature.Flags |= 2; + + DoDamage(creature.Enemy, 25); + CreatureEffect(&item, RaptorBite, DoBloodSplat); + creature.Flags |= 1; + } + } + } + + break; + + case RAPTOR_STATE_RUN_BITE_ATTACK: + creature.MaxTurn = RAPTOR_ATTACK_TURN_RATE_MAX; + tiltAngle = headingAngle; + + if (creature.Enemy != nullptr) + { + if (creature.Enemy->IsLara() && !(creature.Flags & 1) && + item.TouchBits.Test(RaptorAttackJoints)) + { + DoDamage(creature.Enemy, RAPTOR_ATTACK_DAMAGE); + CreatureEffect(&item, RaptorBite, DoBloodSplat); + item.Animation.RequiredState = RAPTOR_STATE_RUN_FORWARD; + creature.Flags |= 1; + + if (LaraItem->HitPoints <= 0) + creature.Flags |= 2; + } + else if (!(creature.Flags & 1)) + { + if (Vector3i::Distance(item.Pose.Position, creature.Enemy->Pose.Position) <= BLOCK(0.5f)) + { + if (creature.Enemy->HitPoints <= 0) + creature.Flags |= 2; + + DoDamage(creature.Enemy, 25); + CreatureEffect(&item, RaptorBite, DoBloodSplat); + creature.Flags |= 1; + } + } + } + + break; + } + } + + CreatureTilt(&item, tiltAngle); + CreatureJoint(&item, 0, headYOrient / 2); + CreatureJoint(&item, 1, headYOrient / 2); + CreatureJoint(&item, 2, neckYOrient); + CreatureJoint(&item, 3, neckYOrient); + + if (item.Animation.ActiveState != RAPTOR_STATE_JUMP_2_BLOCKS && + item.Animation.ActiveState != RAPTOR_STATE_JUMP_1_BLOCK && + item.Animation.ActiveState != RAPTOR_STATE_CLIMB && + item.Animation.ActiveState != RAPTOR_STATE_JUMP_START) + { + switch (CreatureVault(itemNumber, headingAngle, 2, CLICK(2.5f))) + { + case 2: + SetAnimation(item, RAPTOR_ANIM_VAULT_2_STEPS); + creature.MaxTurn = 0; + break; + + case 3: + SetAnimation(item, RAPTOR_ANIM_VAULT_3_STEPS); + creature.MaxTurn = 0; + break; + + case 4: + SetAnimation(item, RAPTOR_ANIM_VAULT_4_STEPS); + creature.MaxTurn = 0; + break; + + case -2: + SetAnimation(item, RAPTOR_ANIM_VAULT_DROP_2_STEPS); + creature.MaxTurn = 0; + break; + + case -3: + SetAnimation(item, RAPTOR_ANIM_VAULT_DROP_3_STEPS); + creature.MaxTurn = 0; + break; + + case -4: + SetAnimation(item, RAPTOR_ANIM_VAULT_DROP_4_STEPS); + creature.MaxTurn = 0; + break; + } + } + else + { + CreatureAnimation(itemNumber, headingAngle, tiltAngle); + } + } +} diff --git a/TombEngine/Objects/TR3/Entity/tr3_raptor.h b/TombEngine/Objects/TR3/Entity/Raptor.h similarity index 100% rename from TombEngine/Objects/TR3/Entity/tr3_raptor.h rename to TombEngine/Objects/TR3/Entity/Raptor.h diff --git a/TombEngine/Objects/TR3/Entity/SealMutant.cpp b/TombEngine/Objects/TR3/Entity/SealMutant.cpp new file mode 100644 index 000000000..0050fa143 --- /dev/null +++ b/TombEngine/Objects/TR3/Entity/SealMutant.cpp @@ -0,0 +1,333 @@ +#include "framework.h" +#include "Objects/TR3/Entity/SealMutant.h" + +#include "Game/animation.h" +#include "Game/control/box.h" +#include "Game/control/lot.h" +#include "Game/effects/effects.h" +#include "Game/effects/tomb4fx.h" +#include "Game/Lara/lara.h" +#include "Game/Lara/lara_helpers.h" +#include "Game/misc.h" +#include "Game/people.h" +#include "Game/Setup.h" +#include "Math/Math.h" +#include "Objects/Effects/enemy_missile.h" + +using namespace TEN::Math; + +// NOTES: +// ItemFlags[0]: Sprite ID for poison effect. + +namespace TEN::Entities::Creatures::TR3 +{ + constexpr auto SEAL_MUTANT_ATTACK_DAMAGE = 1; + + constexpr auto SEAL_MUTANT_ALERT_RANGE = SQUARE(BLOCK(1)); + constexpr auto SEAL_MUTANT_ATTACK_RANGE = SQUARE(BLOCK(1.5f)); + + constexpr auto SEAL_MUTANT_WALK_TURN_RATE = ANGLE(3.0f); + + constexpr auto SEAL_MUTANT_FLAME_LIGHT_Y_OFFSET = CLICK(2); + constexpr auto SEAL_MUTANT_BURN_END_TIME = 16; + + const auto SealMutantGasBite = CreatureBiteInfo(Vector3(0.0f, 48.0f, 140.0f), 10); + const auto SealMutantAttackTargetObjectIds = { ID_LARA, ID_FLAMETHROWER_BADDY, ID_WORKER_FLAMETHROWER }; + + enum SealMutantState + { + SEAL_MUTANT_STATE_IDLE = 0, + SEAL_MUTANT_STATE_WALK = 1, + SEAL_MUTANT_STATE_ATTACK = 2, + SEAL_MUTANT_STATE_DEATH = 3, + SEAL_MUTANT_STATE_TRAP = 4 + }; + + enum SealMutantAnim + { + SEAL_MUTANT_ANIM_IDLE = 0, + SEAL_MUTANT_ANIM_IDLE_TO_WALK = 1, + SEAL_MUTANT_ANIM_WALK = 2, + SEAL_MUTANT_ANIM_WALK_TO_IDLE = 3, + SEAL_MUTANT_ANIM_ATTACK = 4, + SEAL_MUTANT_ANIM_DEATH = 5, + SEAL_MUTANT_ANIM_TRAP = 6 + }; + + enum SealMutantItemFlags + { + IF_SEAL_MUTANT_FLAME_TIMER = 0 + }; + + enum SealMutantOcb + { + OCB_NORMAL_BEHAVIOUR = 0, + OCB_TRAP = 1 + }; + + static void SpawnSealMutantPoisonGas(ItemInfo& item, float vel) + { + constexpr auto GAS_COUNT = 2; + constexpr auto VEL_MULT = 5.0f; + constexpr auto PLAYER_CROUCH_GRAVITY = 32.0f; + + auto& creature = *GetCreatureInfo(&item); + + // HACK. + float gravity = 0.0f; + if (creature.Enemy != nullptr) + { + if (creature.Enemy->IsLara()) + { + const auto& player = GetLaraInfo(*creature.Enemy); + if (player.Control.IsLow) + gravity = PLAYER_CROUCH_GRAVITY; + } + else + { + DoDamage(creature.Enemy, SEAL_MUTANT_ATTACK_DAMAGE); + } + } + + auto velVector = Vector3(0.0f, gravity, vel * VEL_MULT); + auto colorStart = Color(Random::GenerateFloat(0.25f, 0.5f), Random::GenerateFloat(0.25f, 0.5f), 0.1f); + auto colorEnd = Color(Random::GenerateFloat(0.05f, 0.1f), Random::GenerateFloat(0.05f, 0.1f), 0.0f); + + for (int i = 0; i < GAS_COUNT; i++) + ThrowPoison(item, SealMutantGasBite, velVector, colorStart, colorEnd, item.ItemFlags[0]); + } + + void InitializeSealMutant(short itemNumber) + { + auto& item = g_Level.Items[itemNumber]; + + item.ItemFlags[0] = 0; + InitializeCreature(itemNumber); + } + + void ControlSealMutant(short itemNumber) + { + if (!CreatureActive(itemNumber)) + return; + + auto& item = g_Level.Items[itemNumber]; + auto& creature = *GetCreatureInfo(&item); + + short headingAngle = 0; + auto headOrient = EulerAngles::Identity; + auto torsoOrient = EulerAngles::Identity; + + float gasVel = 0.0f; + + if (item.TestOcb(OCB_TRAP)) + { + if (item.Animation.ActiveState != SEAL_MUTANT_STATE_TRAP) + { + SetAnimation(item, SEAL_MUTANT_ANIM_TRAP); + } + else if (TestAnimFrameRange(item, 1, 124)) + { + const auto& anim = GetAnimData(item.Animation.AnimNumber); + + gasVel = item.Animation.FrameNumber - (anim.frameBase + 1); + if (gasVel > 24.0f) + { + gasVel = item.Animation.FrameNumber - (anim.frameEnd - 8); + if (gasVel <= 0.0f) + gasVel = 1.0f; + + if (gasVel > 24.0f) + gasVel = Random::GenerateFloat(8.0f, 24.0f); + + SpawnSealMutantPoisonGas(item, gasVel); + } + } + + CreatureAnimation(itemNumber, 0, 0); + return; + } + + if (item.GetFlagField(IF_SEAL_MUTANT_FLAME_TIMER) > 80) + item.HitPoints = 0; + + if (item.HitPoints <= 0) + { + const auto& anim = GetAnimData(item.Animation.AnimNumber); + + if (item.Animation.ActiveState != SEAL_MUTANT_STATE_DEATH) + { + SetAnimation(item, SEAL_MUTANT_ANIM_DEATH); + } + else if (item.GetFlagField(IF_SEAL_MUTANT_FLAME_TIMER) > 80) + { + for (int boneID = 9; boneID < 17; boneID++) + { + auto pos = GetJointPosition(item, boneID); + TriggerFireFlame(pos.x, pos.y, pos.z, FlameType::Medium); + } + + int burnTimer = item.Animation.FrameNumber - anim.frameBase; + if (burnTimer > SEAL_MUTANT_BURN_END_TIME) + { + burnTimer = item.Animation.FrameNumber - anim.frameEnd; + if (burnTimer > SEAL_MUTANT_BURN_END_TIME) + burnTimer = SEAL_MUTANT_BURN_END_TIME; + } + + if (burnTimer != SEAL_MUTANT_BURN_END_TIME) + { + auto pos = GetJointPosition(item, SealMutantGasBite.BoneID, Vector3(0.0f, -SEAL_MUTANT_FLAME_LIGHT_Y_OFFSET, 0.0f)); + auto color = Color(Random::GenerateFloat(0.75f, 1.0f), Random::GenerateFloat(0.4f, 0.5f), Random::GenerateFloat(0.0f, 0.25f)); + float falloff = Random::GenerateFloat(0.03f, 0.04f); + TriggerDynamicLight(pos.ToVector3(), color, falloff); + } + } + else if (TestAnimFrameRange(item, 1, 124)) + { + gasVel = item.Animation.FrameNumber - (anim.frameBase + 1); + if (gasVel > 24.0f) + { + gasVel = item.Animation.FrameNumber - (anim.frameEnd - 8); + if (gasVel <= 0.0f) + gasVel = 1.0f; + + if (gasVel > 24.0f) + gasVel = Random::GenerateFloat(16.0f, 24.0f); + + SpawnSealMutantPoisonGas(item, gasVel); + } + } + } + else + { + if (item.AIBits) + { + GetAITarget(&creature); + } + else + { + TargetNearestEntity(item, SealMutantAttackTargetObjectIds, false); + } + + AI_INFO ai; + CreatureAIInfo(&item, &ai); + + GetCreatureMood(&item, &ai, ai.zoneNumber == ai.enemyZone); + if (creature.Enemy != nullptr && creature.Enemy->IsLara()) + { + const auto& player = GetLaraInfo(*creature.Enemy); + if (player.Status.Poison >= LARA_POISON_MAX) + creature.Mood = MoodType::Escape; + } + + CreatureMood(&item, &ai, ai.zoneNumber == ai.enemyZone); + headingAngle = CreatureTurn(&item, creature.MaxTurn); + + auto* target = creature.Enemy; + creature.Enemy = LaraItem; + if (ai.distance < SEAL_MUTANT_ALERT_RANGE || item.HitStatus || TargetVisible(&item, &ai)) + AlertAllGuards(itemNumber); + + creature.Enemy = target; + + switch (item.Animation.ActiveState) + { + case SEAL_MUTANT_STATE_IDLE: + creature.MaxTurn = 0; + creature.Flags = 0; + headOrient.x = -ai.xAngle; + headOrient.y = ai.angle; + torsoOrient.x = 0; + torsoOrient.z = 0; + + if (item.AIBits & GUARD) + { + item.Animation.TargetState = SEAL_MUTANT_STATE_IDLE; + headOrient.x = 0; + headOrient.y = AIGuard(&creature); + } + else if (item.AIBits & PATROL1) + { + item.Animation.TargetState = SEAL_MUTANT_STATE_WALK; + headOrient.x = 0; + headOrient.y = 0; + } + else if (creature.Mood == MoodType::Escape) + { + item.Animation.TargetState = SEAL_MUTANT_STATE_WALK; + } + else if (Targetable(&item, &ai) && ai.distance < SEAL_MUTANT_ATTACK_RANGE) + { + item.Animation.TargetState = SEAL_MUTANT_STATE_ATTACK; + } + else if (item.Animation.RequiredState != NO_VALUE) + { + item.Animation.TargetState = item.Animation.RequiredState; + } + else + { + item.Animation.TargetState = SEAL_MUTANT_STATE_WALK; + } + + break; + + case SEAL_MUTANT_STATE_WALK: + creature.MaxTurn = SEAL_MUTANT_WALK_TURN_RATE; + + if (ai.ahead) + { + headOrient.x = -ai.xAngle; + headOrient.y = ai.angle; + } + + if (item.AIBits & PATROL1) + { + item.Animation.TargetState = SEAL_MUTANT_STATE_WALK; + headOrient.y = 0; + } + else if (Targetable(&item, &ai) && ai.distance < SEAL_MUTANT_ATTACK_RANGE) + { + item.Animation.TargetState = SEAL_MUTANT_STATE_IDLE; + } + + break; + + case SEAL_MUTANT_STATE_ATTACK: + if (ai.ahead) + { + headOrient.x = -ai.xAngle; + headOrient.y = ai.angle; + torsoOrient.x = -ai.xAngle / 2; + torsoOrient.z = ai.angle / 2; + } + + if (TestAnimFrameRange(item, 35, 58)) + { + if (creature.Flags < 24) + creature.Flags += 3; + + gasVel = 0.0f; + if (creature.Flags < 24.0f) + { + gasVel = creature.Flags; + } + else + { + gasVel = Random::GenerateFloat(16.0f, 24.0f); + } + + SpawnSealMutantPoisonGas(item, gasVel); + if (creature.Enemy != nullptr && !creature.Enemy->IsLara()) + creature.Enemy->HitStatus = true; + } + + break; + } + } + + CreatureJoint(&item, 0, torsoOrient.z); + CreatureJoint(&item, 1, torsoOrient.x); + CreatureJoint(&item, 2, headOrient.y); + CreatureAnimation(itemNumber, headingAngle, 0); + } +} diff --git a/TombEngine/Objects/TR3/Entity/SealMutant.h b/TombEngine/Objects/TR3/Entity/SealMutant.h new file mode 100644 index 000000000..7dc7c3fa1 --- /dev/null +++ b/TombEngine/Objects/TR3/Entity/SealMutant.h @@ -0,0 +1,7 @@ +#pragma once + +namespace TEN::Entities::Creatures::TR3 +{ + void InitializeSealMutant(short itemNumber); + void ControlSealMutant(short itemNumber); +} diff --git a/TombEngine/Objects/TR3/Entity/Shiva.cpp b/TombEngine/Objects/TR3/Entity/Shiva.cpp index d7a655235..e93810050 100644 --- a/TombEngine/Objects/TR3/Entity/Shiva.cpp +++ b/TombEngine/Objects/TR3/Entity/Shiva.cpp @@ -4,6 +4,7 @@ #include "Game/animation.h" #include "Game/camera.h" #include "Game/collision/collide_room.h" +#include "Game/collision/Point.h" #include "Game/control/box.h" #include "Game/effects/effects.h" #include "Game/itemdata/creature_info.h" @@ -17,6 +18,7 @@ #include "Sound/sound.h" #include "Specific/level.h" +using namespace TEN::Collision::Point; using namespace TEN::Math; namespace TEN::Entities::Creatures::TR3 @@ -252,7 +254,7 @@ namespace TEN::Entities::Creatures::TR3 void InitializeShiva(short itemNumber) { auto& item = g_Level.Items[itemNumber]; - item.Status &= ~ITEM_INVISIBLE; // Draw the statue from the start. + item.Status = ITEM_NOT_ACTIVE; // Draw the statue from the start. InitializeCreature(itemNumber); SetAnimation(&item, SHIVA_ANIM_INACTIVE); @@ -339,9 +341,9 @@ namespace TEN::Entities::Creatures::TR3 { int x = item->Pose.Position.x + BLOCK(1) * phd_sin(item->Pose.Orientation.y + ANGLE(180.0f)); int z = item->Pose.Position.z + BLOCK(1) * phd_cos(item->Pose.Orientation.y + ANGLE(180.0f)); - auto box = GetCollision(x, item->Pose.Position.y, z, item->RoomNumber).BottomBlock->Box; + auto box = GetPointCollision(Vector3i(x, item->Pose.Position.y, z), item->RoomNumber).GetBottomSector().PathfindingBoxID; - if (box != NO_VALUE && !(g_Level.Boxes[box].flags & BLOCKABLE) && !creature.Flags) + if (box != NO_VALUE && !(g_Level.PathfindingBoxes[box].flags & BLOCKABLE) && !creature.Flags) item->Animation.TargetState = SHIVA_STATE_WALK_BACK; else item->Animation.TargetState = SHIVA_STATE_GUARD_IDLE; diff --git a/TombEngine/Objects/TR3/Entity/SophiaLeigh.cpp b/TombEngine/Objects/TR3/Entity/SophiaLeigh.cpp index ea752dae8..d4ae15027 100644 --- a/TombEngine/Objects/TR3/Entity/SophiaLeigh.cpp +++ b/TombEngine/Objects/TR3/Entity/SophiaLeigh.cpp @@ -289,7 +289,7 @@ namespace TEN::Entities::Creatures::TR3 // Check the previous and next position of AI object to // allow Sophia to go up or down based on enemy's vertical position. - FindAITargetObject(creature, ID_AI_X1, creature->LocationAI, false); + FindAITargetObject(item, ID_AI_X1, creature->LocationAI, false); if (Vector3i::Distance(item.Pose.Position, creature->Enemy->Pose.Position) < SOPHIALEIGH_REACHED_GOAL_RANGE) { diff --git a/TombEngine/Objects/TR3/Entity/TwinAutoGun.cpp b/TombEngine/Objects/TR3/Entity/TwinAutoGun.cpp index 67cd6c0f5..e944f5062 100644 --- a/TombEngine/Objects/TR3/Entity/TwinAutoGun.cpp +++ b/TombEngine/Objects/TR3/Entity/TwinAutoGun.cpp @@ -21,8 +21,8 @@ namespace TEN::Entities::Creatures::TR3 constexpr auto TWIN_AUTO_GUN_SHOT_DAMAGE = 5; constexpr auto TWIN_AUTO_GUN_HIT_POINTS_MAX = 28; - const auto TwinAutoGunLeftBite = CreatureBiteInfo(110, -30, 530, 2); - const auto TwinAutoGunRightBite = CreatureBiteInfo(-110, -30, 530, 2); + const auto TwinAutoGunLeftBite = CreatureBiteInfo(Vector3(110.0f, -30.0f, 530.0f), 2); + const auto TwinAutoGunRightBite = CreatureBiteInfo(Vector3(-110.0f, -30.0f, 530.0f), 2); enum TwinAutoGunAnim { diff --git a/TombEngine/Objects/TR3/Entity/tr3_civvy.cpp b/TombEngine/Objects/TR3/Entity/tr3_civvy.cpp index 7a86c5c01..0a98b223c 100644 --- a/TombEngine/Objects/TR3/Entity/tr3_civvy.cpp +++ b/TombEngine/Objects/TR3/Entity/tr3_civvy.cpp @@ -182,7 +182,7 @@ namespace TEN::Entities::Creatures::TR3 auto jointHeadRot = EulerAngles::Identity; auto jointTorsoRot = EulerAngles::Identity; - if (item.BoxNumber != NO_VALUE && (g_Level.Boxes[item.BoxNumber].flags & BLOCKED) && item.HitPoints > 0) + if (item.BoxNumber != NO_VALUE && (g_Level.PathfindingBoxes[item.BoxNumber].flags & BLOCKED) && item.HitPoints > 0) { DoDamage(&item, INT_MAX); DoLotsOfBlood(item.Pose.Position.x, item.Pose.Position.y - (GetRandomControl() & 255) - 32, item.Pose.Position.z, (GetRandomControl() & 127) + 128, GetRandomControl() << 1, item.RoomNumber, 3); diff --git a/TombEngine/Objects/TR3/Entity/tr3_flamethrower.cpp b/TombEngine/Objects/TR3/Entity/tr3_flamethrower.cpp index 57d37d488..4c1c60021 100644 --- a/TombEngine/Objects/TR3/Entity/tr3_flamethrower.cpp +++ b/TombEngine/Objects/TR3/Entity/tr3_flamethrower.cpp @@ -28,6 +28,7 @@ namespace TEN::Entities::Creatures::TR3 constexpr auto FLAMETHROWER_WALK_TURN_RATE_MAX = ANGLE(5.0f); const auto FlamethrowerBite = CreatureBiteInfo(Vector3(0, 340, 64), 7); + const auto FlamethrowerTargetIds = { ID_LARA, ID_SEAL_MUTANT }; // TODO enum FlamethrowerState @@ -94,30 +95,7 @@ namespace TEN::Entities::Creatures::TR3 } else { - creature->Enemy = nullptr; - - ItemInfo* target = nullptr; - int minDistance = INT_MAX; - - for (auto& currentCreature : ActiveCreatures) - { - if (currentCreature->ItemNumber == NO_VALUE || currentCreature->ItemNumber == itemNumber) - continue; - - target = &g_Level.Items[currentCreature->ItemNumber]; - if (target->ObjectNumber == ID_LARA || target->HitPoints <= 0 || target->ObjectNumber == ID_FLAMETHROWER_BADDY) - continue; - - int x = target->Pose.Position.x - item->Pose.Position.x; - int z = target->Pose.Position.z - item->Pose.Position.z; - - int distance = SQUARE(z) + SQUARE(x); - if (distance < minDistance) - { - creature->Enemy = target; - minDistance = distance; - } - } + TargetNearestEntity(*item, FlamethrowerTargetIds, false); } AI_INFO AI; @@ -311,10 +289,8 @@ namespace TEN::Entities::Creatures::TR3 else { ThrowFire(itemNumber, FlamethrowerBite, Vector3i(0, (Random::GenerateInt() & 63) + 12, 0)); - if (realEnemy) - { - /*code*/ - } + if (realEnemy && realEnemy->ObjectNumber == ID_SEAL_MUTANT) + realEnemy->ItemFlags[0]++; } SoundEffect(SFX_TR4_FLAME_EMITTER, &item->Pose); @@ -345,10 +321,8 @@ namespace TEN::Entities::Creatures::TR3 else { ThrowFire(itemNumber, FlamethrowerBite, Vector3i(0, (GetRandomControl() & 63) + 12, 0)); - if (realEnemy) - { - /*code*/ - } + if (realEnemy && realEnemy->ObjectNumber == ID_SEAL_MUTANT) + realEnemy->ItemFlags[0]++; } SoundEffect(SFX_TR4_FLAME_EMITTER, &item->Pose); diff --git a/TombEngine/Objects/TR3/Entity/tr3_mp_gun.cpp b/TombEngine/Objects/TR3/Entity/tr3_mp_gun.cpp index b12f201ab..00e6e74e5 100644 --- a/TombEngine/Objects/TR3/Entity/tr3_mp_gun.cpp +++ b/TombEngine/Objects/TR3/Entity/tr3_mp_gun.cpp @@ -3,9 +3,9 @@ #include "Game/animation.h" #include "Game/collision/collide_room.h" +#include "Game/collision/Point.h" #include "Game/control/box.h" #include "Game/control/lot.h" -#include "Game/collision/sphere.h" #include "Game/effects/effects.h" #include "Game/itemdata/creature_info.h" #include "Game/items.h" @@ -17,6 +17,7 @@ #include "Sound/sound.h" #include "Specific/level.h" +using namespace TEN::Collision::Point; using namespace TEN::Math; namespace TEN::Entities::Creatures::TR3 @@ -70,7 +71,7 @@ namespace TEN::Entities::Creatures::TR3 if (creature->MuzzleFlash[0].Delay != 0) creature->MuzzleFlash[0].Delay--; - if (item->BoxNumber != NO_VALUE && (g_Level.Boxes[item->BoxNumber].flags & BLOCKED)) + if (item->BoxNumber != NO_VALUE && (g_Level.PathfindingBoxes[item->BoxNumber].flags & BLOCKED)) { DoDamage(item, 20); DoLotsOfBlood(item->Pose.Position.x, item->Pose.Position.y - (GetRandomControl() & 255) - 32, item->Pose.Position.z, (GetRandomControl() & 127) + 128, GetRandomControl() * 2, item->RoomNumber, 3); @@ -163,7 +164,7 @@ namespace TEN::Entities::Creatures::TR3 int y = item->Pose.Position.y; int z = item->Pose.Position.z + BLOCK(1) * phd_cos(item->Pose.Orientation.y + laraAI.angle); - int height = GetCollision(x, y, z, item->RoomNumber).Position.Floor; + int height = GetPointCollision(Vector3i(x, y, z), item->RoomNumber).GetFloorHeight(); bool cover = (item->Pose.Position.y > (height + CLICK(3)) && item->Pose.Position.y < (height + CLICK(4.5f)) && laraAI.distance > pow(BLOCK(1), 2)); auto* enemy = creature->Enemy; diff --git a/TombEngine/Objects/TR3/Entity/tr3_mp_stick.cpp b/TombEngine/Objects/TR3/Entity/tr3_mp_stick.cpp index 643f4d6c6..7b1bf2bab 100644 --- a/TombEngine/Objects/TR3/Entity/tr3_mp_stick.cpp +++ b/TombEngine/Objects/TR3/Entity/tr3_mp_stick.cpp @@ -77,7 +77,7 @@ namespace TEN::Entities::Creatures::TR3 short head = 0; auto extraTorsoRot = EulerAngles::Identity; - if (item->BoxNumber != NO_VALUE && (g_Level.Boxes[item->BoxNumber].flags & BLOCKED)) + if (item->BoxNumber != NO_VALUE && (g_Level.PathfindingBoxes[item->BoxNumber].flags & BLOCKED)) { DoDamage(item, 20); DoLotsOfBlood(item->Pose.Position.x, item->Pose.Position.y - (GetRandomControl() & 255) - 32, item->Pose.Position.z, (GetRandomControl() & 127) + 128, GetRandomControl() * 2, item->RoomNumber, 3); diff --git a/TombEngine/Objects/TR3/Entity/tr3_raptor.cpp b/TombEngine/Objects/TR3/Entity/tr3_raptor.cpp deleted file mode 100644 index 77d4c0558..000000000 --- a/TombEngine/Objects/TR3/Entity/tr3_raptor.cpp +++ /dev/null @@ -1,348 +0,0 @@ -#include "framework.h" -#include "Objects/TR3/Entity/tr3_raptor.h" - -#include "Game/control/box.h" -#include "Game/control/control.h" -#include "Game/control/lot.h" -#include "Game/effects/effects.h" -#include "Game/items.h" -#include "Game/itemdata/creature_info.h" -#include "Game/Lara/lara.h" -#include "Game/misc.h" -#include "Game/Setup.h" -#include "Math/Math.h" -#include "Specific/level.h" - -using namespace TEN::Math; - -namespace TEN::Entities::Creatures::TR3 -{ - constexpr auto RAPTOR_ATTACK_DAMAGE = 100; - - constexpr auto RAPTOR_BITE_ATTACK_RANGE = SQUARE(585); - constexpr auto RAPTOR_JUMP_ATTACK_RANGE = SQUARE(BLOCK(1.5f)); - constexpr auto RAPTOR_RUN_ATTACK_RANGE = SQUARE(BLOCK(1.5f)); - - constexpr auto RAPTOR_ROAR_CHANCE = 1.0f / 256; - constexpr auto RAPTOR_SWITCH_TARGET_CHANCE = 1.0f / 128; - - constexpr auto RAPTOR_WALK_TURN_RATE_MAX = ANGLE(2.0f); - constexpr auto RAPTOR_RUN_TURN_RATE_MAX = ANGLE(2.0f); - constexpr auto RAPTOR_ATTACK_TURN_RATE_MAX = ANGLE(2.0f); - - const auto RaptorBite = CreatureBiteInfo(Vector3(0, 66, 318), 22); - const auto RaptorAttackJoints = std::vector{ 10, 11, 12, 13, 14, 16, 17, 18, 19, 20, 21, 22, 23 }; - - enum RaptorState - { - RAPTOR_STATE_DEATH = 0, - RAPTOR_STATE_IDLE = 1, - RAPTOR_STATE_WALK_FORWARD = 2, - RAPTOR_STATE_RUN_FORWARD = 3, - RAPTOR_STATE_JUMP_ATTACK = 4, - RAPTOR_STATE_NONE = 5, - RAPTOR_STATE_ROAR = 6, - RAPTOR_STATE_RUN_BITE_ATTACK = 7, - RAPTOR_STATE_BITE_ATTACK = 8 - }; - - enum RaptorAnim - { - RAPTOR_ANIM_IDLE = 0, - RAPTOR_ANIM_RUN_FORWARD = 1, - RAPTOR_ANIM_RUN_FORWARD_TO_IDLE = 2, - RAPTOR_ANIM_IDLE_TO_RUN_FORWARD = 3, - RAPTOR_ANIM_ROAR = 4, - RAPTOR_ANIM_WALK_FORWARD = 5, - RAPTOR_ANIM_WALK_FORWARD_TO_IDLE = 6, - RAPTOR_ANIM_IDLE_TO_WALK_FORWARD = 7, - RAPTOR_ANIM_RUN_BITE_ATTACK = 8, - RAPTOR_ANIM_DEATH_1 = 9, - RAPTOR_ANIM_DEATH_2 = 10, - RAPTOR_ANIM_JUMP_ATTACK_START = 11, - RAPTOR_ANIM_JUMP_ATTACK_END = 12, - RAPTOR_ANIM_BITE_ATTACK = 13 - }; - - // TODO - enum RaptorFlags - { - - }; - - const std::array RaptorDeathAnims = { RAPTOR_ANIM_DEATH_1, RAPTOR_ANIM_DEATH_2, }; - - void RaptorControl(short itemNumber) - { - if (!CreatureActive(itemNumber)) - return; - - auto* item = &g_Level.Items[itemNumber]; - auto* creature = GetCreatureInfo(item); - - short angle = 0; - short tilt = 0; - short head = 0; - short neck = 0; - - if (item->HitPoints <= 0) - { - if (item->Animation.ActiveState != RAPTOR_STATE_DEATH) - SetAnimation(item, RaptorDeathAnims[Random::GenerateInt(0, (int)RaptorDeathAnims.size() - 1)]); - } - else - { - if (creature->Enemy == nullptr || Random::TestProbability(RAPTOR_SWITCH_TARGET_CHANCE)) - { - ItemInfo* nearestItem = nullptr; - float minDistance = INFINITY; - - for (auto& currentCreature : ActiveCreatures) - { - if (currentCreature->ItemNumber == NO_VALUE || currentCreature->ItemNumber == itemNumber) - continue; - - auto* targetItem = &g_Level.Items[currentCreature->ItemNumber]; - - int distance = Vector3i::Distance(item->Pose.Position, targetItem->Pose.Position); - if (distance < minDistance && item->HitPoints > 0) - { - nearestItem = targetItem; - minDistance = distance; - } - } - - if (nearestItem != nullptr && - (nearestItem->ObjectNumber != ID_RAPTOR || - (Random::TestProbability(1 / 30.0f) && minDistance < SQUARE(BLOCK(2))))) - { - creature->Enemy = nearestItem; - } - - int distance = Vector3i::Distance(item->Pose.Position, LaraItem->Pose.Position); - if (distance <= minDistance) - creature->Enemy = LaraItem; - } - - if (item->AIBits) - GetAITarget(creature); - - AI_INFO AI; - CreatureAIInfo(item, &AI); - - if (AI.ahead) - head = AI.angle; - - GetCreatureMood(item, &AI, true); - CreatureMood(item, &AI, true); - - if (creature->Mood == MoodType::Bored) - creature->MaxTurn /= 2; - - angle = CreatureTurn(item, creature->MaxTurn); - neck = -angle * 6; - - switch (item->Animation.ActiveState) - { - case RAPTOR_STATE_IDLE: - creature->MaxTurn = 0; - creature->Flags &= ~1; - - if (item->Animation.RequiredState != NO_VALUE) - item->Animation.TargetState = item->Animation.RequiredState; - else if (creature->Flags & 2) - { - creature->Flags &= ~2; - item->Animation.TargetState = RAPTOR_STATE_ROAR; - } - else if (item->TouchBits.Test(RaptorAttackJoints) || - (AI.distance < RAPTOR_BITE_ATTACK_RANGE && AI.bite)) - { - item->Animation.TargetState = RAPTOR_STATE_BITE_ATTACK; - } - else if (AI.bite && AI.distance < RAPTOR_JUMP_ATTACK_RANGE) - item->Animation.TargetState = RAPTOR_STATE_JUMP_ATTACK; - else if (creature->Mood == MoodType::Escape && - Lara.TargetEntity != item && AI.ahead && !item->HitStatus) - { - item->Animation.TargetState = RAPTOR_STATE_IDLE; - } - else if (creature->Mood == MoodType::Bored) - item->Animation.TargetState = RAPTOR_STATE_WALK_FORWARD; - else - item->Animation.TargetState = RAPTOR_STATE_RUN_FORWARD; - - break; - - case RAPTOR_STATE_WALK_FORWARD: - creature->MaxTurn = RAPTOR_WALK_TURN_RATE_MAX; - creature->Flags &= ~1; - - if (creature->Mood != MoodType::Bored) - item->Animation.TargetState = RAPTOR_STATE_IDLE; - else if (AI.ahead && Random::TestProbability(RAPTOR_ROAR_CHANCE)) - { - item->Animation.TargetState = RAPTOR_STATE_IDLE; - item->Animation.RequiredState = RAPTOR_STATE_ROAR; - creature->Flags &= ~2; - } - - break; - - case RAPTOR_STATE_RUN_FORWARD: - creature->MaxTurn = RAPTOR_RUN_TURN_RATE_MAX; - creature->Flags &= ~1; - tilt = angle; - - if (item->TouchBits.Test(RaptorAttackJoints)) - item->Animation.TargetState = RAPTOR_STATE_IDLE; - else if (creature->Flags & 2) - { - item->Animation.TargetState = RAPTOR_STATE_IDLE; - item->Animation.RequiredState = RAPTOR_STATE_ROAR; - creature->Flags &= ~2; - } - else if (AI.bite && AI.distance < RAPTOR_RUN_ATTACK_RANGE) - { - if (item->Animation.TargetState == RAPTOR_STATE_RUN_FORWARD) - { - if (Random::TestProbability(0.25f)) - item->Animation.TargetState = RAPTOR_STATE_IDLE; - else - item->Animation.TargetState = RAPTOR_STATE_RUN_BITE_ATTACK; - } - } - else if (AI.ahead && creature->Mood != MoodType::Escape && - Random::TestProbability(RAPTOR_ROAR_CHANCE)) - { - item->Animation.TargetState = RAPTOR_STATE_IDLE; - item->Animation.RequiredState = RAPTOR_STATE_ROAR; - } - else if (creature->Mood == MoodType::Bored || - (creature->Mood == MoodType::Escape && Lara.TargetEntity != item && AI.ahead)) - { - item->Animation.TargetState = RAPTOR_STATE_IDLE; - } - - break; - - case RAPTOR_STATE_JUMP_ATTACK: - creature->MaxTurn = RAPTOR_ATTACK_TURN_RATE_MAX; - tilt = angle; - - if (creature->Enemy->IsLara()) - { - if (!(creature->Flags & 1) && - item->TouchBits.Test(RaptorAttackJoints)) - { - DoDamage(creature->Enemy, RAPTOR_ATTACK_DAMAGE); - CreatureEffect(item, RaptorBite, DoBloodSplat); - creature->Flags |= 1; - - if (LaraItem->HitPoints <= 0) - creature->Flags |= 2; - - item->Animation.RequiredState = RAPTOR_STATE_IDLE; - } - } - else - { - if (!(creature->Flags & 1) && creature->Enemy != nullptr) - { - if (Vector3i::Distance(item->Pose.Position, creature->Enemy->Pose.Position) <= BLOCK(0.5f)) - { - if (creature->Enemy->HitPoints <= 0) - creature->Flags |= 2; - - DoDamage(creature->Enemy, 25); - CreatureEffect(item, RaptorBite, DoBloodSplat); - creature->Flags |= 1; - } - } - } - - break; - - case RAPTOR_STATE_BITE_ATTACK: - creature->MaxTurn = RAPTOR_ATTACK_TURN_RATE_MAX; - tilt = angle; - - if (creature->Enemy->IsLara()) - { - if (!(creature->Flags & 1) && - item->TouchBits.Test(RaptorAttackJoints)) - { - DoDamage(creature->Enemy, RAPTOR_ATTACK_DAMAGE); - CreatureEffect(item, RaptorBite, DoBloodSplat); - creature->Flags |= 1; - - if (LaraItem->HitPoints <= 0) - creature->Flags |= 2; - - item->Animation.RequiredState = RAPTOR_STATE_IDLE; - } - } - else - { - if (!(creature->Flags & 1) && creature->Enemy != nullptr) - { - if (Vector3i::Distance(item->Pose.Position, creature->Enemy->Pose.Position) <= BLOCK(0.5f)) - { - if (creature->Enemy->HitPoints <= 0) - creature->Flags |= 2; - - DoDamage(creature->Enemy, 25); - CreatureEffect(item, RaptorBite, DoBloodSplat); - creature->Flags |= 1; - } - } - } - - break; - - case RAPTOR_STATE_RUN_BITE_ATTACK: - creature->MaxTurn = RAPTOR_ATTACK_TURN_RATE_MAX; - tilt = angle; - - if (creature->Enemy->IsLara()) - { - if (!(creature->Flags & 1) && - item->TouchBits.Test(RaptorAttackJoints)) - { - DoDamage(creature->Enemy, RAPTOR_ATTACK_DAMAGE); - CreatureEffect(item, RaptorBite, DoBloodSplat); - item->Animation.RequiredState = RAPTOR_STATE_RUN_FORWARD; - creature->Flags |= 1; - - if (LaraItem->HitPoints <= 0) - creature->Flags |= 2; - } - } - else - { - if (!(creature->Flags & 1) && creature->Enemy != nullptr) - { - if (Vector3i::Distance(item->Pose.Position, creature->Enemy->Pose.Position) <= BLOCK(0.5f)) - { - if (creature->Enemy->HitPoints <= 0) - creature->Flags |= 2; - - DoDamage(creature->Enemy, 25); - CreatureEffect(item, RaptorBite, DoBloodSplat); - creature->Flags |= 1; - } - } - } - - break; - } - } - - CreatureTilt(item, tilt); - CreatureJoint(item, 0, head / 2); - CreatureJoint(item, 1, head / 2); - CreatureJoint(item, 2, neck); - CreatureJoint(item, 3, neck); - CreatureAnimation(itemNumber, angle, tilt); - } -} diff --git a/TombEngine/Objects/TR3/Entity/tr3_scuba_diver.cpp b/TombEngine/Objects/TR3/Entity/tr3_scuba_diver.cpp index b8dab0ddc..9f4baad45 100644 --- a/TombEngine/Objects/TR3/Entity/tr3_scuba_diver.cpp +++ b/TombEngine/Objects/TR3/Entity/tr3_scuba_diver.cpp @@ -2,6 +2,7 @@ #include "Objects/TR3/Entity/tr3_scuba_diver.h" #include "Game/collision/collide_room.h" +#include "Game/collision/Point.h" #include "Game/control/box.h" #include "Game/control/control.h" #include "Game/control/los.h" @@ -13,6 +14,8 @@ #include "Game/Setup.h" #include "Specific/level.h" +using namespace TEN::Collision::Point; + namespace TEN::Entities::Creatures::TR3 { constexpr auto SCUBA_DIVER_ATTACK_DAMAGE = 50; @@ -92,12 +95,12 @@ namespace TEN::Entities::Creatures::TR3 { TranslateItem(item, item->Pose.Orientation, item->Animation.Velocity.z); - auto probe = GetCollision(item); + auto probe = GetPointCollision(*item); - if (item->RoomNumber != probe.RoomNumber) - ItemNewRoom(itemNumber, probe.RoomNumber); + if (item->RoomNumber != probe.GetRoomNumber()) + ItemNewRoom(itemNumber, probe.GetRoomNumber()); - item->Floor = GetCollision(item).Position.Floor; + item->Floor = GetPointCollision(*item).GetFloorHeight(); if (item->Pose.Position.y >= item->Floor) KillItem(itemNumber); } @@ -127,11 +130,11 @@ namespace TEN::Entities::Creatures::TR3 } else { - AI_INFO AI; - CreatureAIInfo(item, &AI); + AI_INFO ai; + CreatureAIInfo(item, &ai); - GetCreatureMood(item, &AI, false); - CreatureMood(item, &AI, false); + GetCreatureMood(item, &ai, false); + CreatureMood(item, &ai, false); bool shoot = false; if (Lara.Control.WaterStatus == WaterStatus::Dry) @@ -140,23 +143,21 @@ namespace TEN::Entities::Creatures::TR3 item->Pose.Position.x, item->Pose.Position.y - CLICK(1), item->Pose.Position.z, - item->RoomNumber - ); + item->RoomNumber); auto target = GameVector( LaraItem->Pose.Position.x, LaraItem->Pose.Position.y - (LARA_HEIGHT - 150), - LaraItem->Pose.Position.z - ); + LaraItem->Pose.Position.z); shoot = LOS(&origin, &target); if (shoot) creature->Target = LaraItem->Pose.Position; - if (AI.angle < -ANGLE(45.0f) || AI.angle > ANGLE(45.0f)) + if (ai.angle < -ANGLE(45.0f) || ai.angle > ANGLE(45.0f)) shoot = false; } - else if (AI.angle > -ANGLE(45.0f) && AI.angle < ANGLE(45.0f)) + else if (ai.angle > -ANGLE(45.0f) && ai.angle < ANGLE(45.0f)) { auto origin = GameVector(item->Pose.Position, item->RoomNumber); auto target = GameVector(LaraItem->Pose.Position); @@ -165,7 +166,7 @@ namespace TEN::Entities::Creatures::TR3 } angle = CreatureTurn(item, creature->MaxTurn); - waterHeight = GetWaterSurface(item->Pose.Position.x, item->Pose.Position.y, item->Pose.Position.z, item->RoomNumber) + BLOCK(0.5f); + waterHeight = GetPointCollision(*item).GetWaterSurfaceHeight() + BLOCK(0.5f); switch (item->Animation.ActiveState) { @@ -173,7 +174,7 @@ namespace TEN::Entities::Creatures::TR3 creature->MaxTurn = SCUBA_DIVER_SWIM_TURN_RATE_MAX; if (shoot) - neck = -AI.angle; + neck = -ai.angle; if (creature->Target.y < waterHeight && item->Pose.Position.y < (waterHeight + creature->LOT.Fly)) @@ -191,7 +192,7 @@ namespace TEN::Entities::Creatures::TR3 creature->Flags = 0; if (shoot) - neck = -AI.angle; + neck = -ai.angle; if (!shoot || creature->Mood == MoodType::Escape || (creature->Target.y < waterHeight && @@ -206,7 +207,7 @@ namespace TEN::Entities::Creatures::TR3 case SDIVER_STATE_SWIM_SHOOT: if (shoot) - neck = -AI.angle; + neck = -ai.angle; if (!creature->Flags) { @@ -221,7 +222,7 @@ namespace TEN::Entities::Creatures::TR3 creature->MaxTurn = SCUBA_DIVER_SWIM_TURN_RATE_MAX; if (shoot) - head = AI.angle; + head = ai.angle; if (creature->Target.y > waterHeight) item->Animation.TargetState = SDIVER_STATE_SWIM; @@ -236,7 +237,7 @@ namespace TEN::Entities::Creatures::TR3 creature->Flags = 0; if (shoot) - head = AI.angle; + head = ai.angle; if (!shoot || creature->Mood == MoodType::Escape || creature->Target.y > waterHeight) item->Animation.TargetState = SDIVER_STATE_TREAD_WATER_IDLE; @@ -247,7 +248,7 @@ namespace TEN::Entities::Creatures::TR3 case SDIVER_STATE_TREAD_WATER_SHOOT: if (shoot) - head = AI.angle; + head = ai.angle; if (!creature->Flags) { diff --git a/TombEngine/Objects/TR3/Entity/tr3_tony.cpp b/TombEngine/Objects/TR3/Entity/tr3_tony.cpp index ff2ffb407..9a4ed5ac5 100644 --- a/TombEngine/Objects/TR3/Entity/tr3_tony.cpp +++ b/TombEngine/Objects/TR3/Entity/tr3_tony.cpp @@ -4,6 +4,7 @@ #include "Game/animation.h" #include "Game/collision/collide_item.h" #include "Game/collision/collide_room.h" +#include "Game/collision/Point.h" #include "Game/control/box.h" #include "Game/control/lot.h" #include "Game/effects/effects.h" @@ -18,6 +19,7 @@ #include "Sound/sound.h" #include "Specific/level.h" +using namespace TEN::Collision::Point; using namespace TEN::Effects::Items; using namespace TEN::Effects::Boss; @@ -412,10 +414,10 @@ namespace TEN::Entities::Creatures::TR3 break; } - auto probe = GetCollision(fx.pos.Position.x, fx.pos.Position.y, fx.pos.Position.z, fx.roomNumber); + auto pointColl = GetPointCollision(fx.pos.Position, fx.roomNumber); - if (fx.pos.Position.y >= probe.Position.Floor || - fx.pos.Position.y < probe.Position.Ceiling) + if (fx.pos.Position.y >= pointColl.GetFloorHeight() || + fx.pos.Position.y < pointColl.GetCeilingHeight()) { Vector3i pos; int debrisCount = type == TonyFlameType::InFront ? 7 : 3; @@ -427,13 +429,13 @@ namespace TEN::Entities::Creatures::TR3 for (int x = 0; x < 2; x++) TriggerExplosionSparks(prevPos.x, prevPos.y, prevPos.z, 3, -1, 0, fx.roomNumber); - probe = GetCollision(LaraItem); // TODO: Deal with LaraItem global. - pos.y = probe.Position.Ceiling + CLICK(1); + pointColl = GetPointCollision(*LaraItem); // TODO: Deal with LaraItem global. + pos.y = pointColl.GetCeilingHeight() + CLICK(1); pos.x = LaraItem->Pose.Position.x + (GetRandomControl() & 1023) - CLICK(2); pos.z = LaraItem->Pose.Position.z + (GetRandomControl() & 1023) - CLICK(2); - TriggerExplosionSparks(pos.x, pos.y, pos.z, 3, -2, 0, probe.RoomNumber); - TriggerFireBall(nullptr, TonyFlameType::ShowerFromCeiling, &pos, probe.RoomNumber, 0, 0); // Fallthrough is intended. + TriggerExplosionSparks(pos.x, pos.y, pos.z, 3, -2, 0, pointColl.GetRoomNumber()); + TriggerFireBall(nullptr, TonyFlameType::ShowerFromCeiling, &pos, pointColl.GetRoomNumber(), 0, 0); // Fallthrough is intended. case TonyFlameType::InFront: case TonyFlameType::ShowerFromCeiling: @@ -450,7 +452,7 @@ namespace TEN::Entities::Creatures::TR3 return; } - if (TestEnvironment(ENV_FLAG_WATER, probe.RoomNumber)) + if (TestEnvironment(ENV_FLAG_WATER, pointColl.GetRoomNumber())) { KillEffect(fxNumber); return; @@ -468,7 +470,7 @@ namespace TEN::Entities::Creatures::TR3 } } - if (probe.RoomNumber != fx.roomNumber) + if (pointColl.GetRoomNumber() != fx.roomNumber) EffectNewRoom(fxNumber, LaraItem->RoomNumber); if (LightIntensityTable[fx.flag1]) diff --git a/TombEngine/Objects/TR3/Entity/tr3_tribesman.cpp b/TombEngine/Objects/TR3/Entity/tr3_tribesman.cpp index f97c28937..06bf326d6 100644 --- a/TombEngine/Objects/TR3/Entity/tr3_tribesman.cpp +++ b/TombEngine/Objects/TR3/Entity/tr3_tribesman.cpp @@ -2,7 +2,6 @@ #include "Objects/TR3/Entity/tr3_tribesman.h" #include "Game/animation.h" -#include "Game/collision/sphere.h" #include "Game/control/box.h" #include "Game/effects/effects.h" #include "Game/itemdata/creature_info.h" @@ -391,8 +390,7 @@ namespace TEN::Entities::Creatures::TR3 pos1.z += 96; pos1 = GetJointPosition(item, TribesmanDartBite2.BoneID, pos1); - TriggerDartSmoke(pos1.x, pos1.y, pos1.z, 0, 0, true); - TriggerDartSmoke(pos1.x, pos1.y, pos1.z, 0, 0, true); + SpawnDartSmoke(pos1.ToVector3(), Vector3::Zero, true); } void TribemanDartsControl(short itemNumber) diff --git a/TombEngine/Objects/TR3/Object/corpse.cpp b/TombEngine/Objects/TR3/Object/corpse.cpp index 0ceed1b43..cba3931ea 100644 --- a/TombEngine/Objects/TR3/Object/corpse.cpp +++ b/TombEngine/Objects/TR3/Object/corpse.cpp @@ -7,8 +7,8 @@ #include "Game/control/control.h" #include "Game/collision/collide_item.h" #include "Game/collision/collide_room.h" +#include "Game/collision/Point.h" #include "Game/collision/floordata.h" -#include "Game/collision/sphere.h" #include "Game/effects/effects.h" #include "Game/effects/Ripple.h" #include "Game/items.h" @@ -19,6 +19,7 @@ #include "Sound/sound.h" #include "Specific/level.h" +using namespace TEN::Collision::Point; using namespace TEN::Effects::Ripple; using namespace TEN::Math; @@ -72,30 +73,30 @@ namespace TEN::Entities::TR3 bool isWater = TestEnvironment(RoomEnvFlags::ENV_FLAG_WATER, item.RoomNumber); float verticalVelCoeff = isWater ? 81.0f : 1.0f; - int roomNumber = GetCollision(&item).RoomNumber; - if (item.RoomNumber != roomNumber) + auto pointColl = GetPointCollision(item); + if (item.RoomNumber != pointColl.GetRoomNumber()) { - if (TestEnvironment(RoomEnvFlags::ENV_FLAG_WATER, roomNumber) && + if (TestEnvironment(RoomEnvFlags::ENV_FLAG_WATER, pointColl.GetRoomNumber()) && !TestEnvironment(RoomEnvFlags::ENV_FLAG_WATER, item.RoomNumber)) { - int waterHeight = GetWaterHeight(item.Pose.Position.x, item.Pose.Position.y, item.Pose.Position.z, roomNumber); + int waterHeight = pointColl.GetWaterTopHeight(); SplashSetup.y = waterHeight - 1; SplashSetup.x = item.Pose.Position.x; SplashSetup.z = item.Pose.Position.z; SplashSetup.splashPower = item.Animation.Velocity.y * 4; SplashSetup.innerRadius = 160.0f; - SetupSplash(&SplashSetup, roomNumber); + SetupSplash(&SplashSetup, pointColl.GetRoomNumber()); item.Animation.Velocity.y = 0.0f; } - ItemNewRoom(itemNumber, roomNumber); + ItemNewRoom(itemNumber, pointColl.GetRoomNumber()); } - auto pointColl = GetCollision(&item); + pointColl = GetPointCollision(item); item.Animation.IsAirborne = true; - if (pointColl.Position.Floor < item.Pose.Position.y) + if (pointColl.GetFloorHeight() < item.Pose.Position.y) { if (!isWater) { diff --git a/TombEngine/Objects/TR3/Trap/ElectricCleaner.cpp b/TombEngine/Objects/TR3/Trap/ElectricCleaner.cpp index 84343ccc5..436ade346 100644 --- a/TombEngine/Objects/TR3/Trap/ElectricCleaner.cpp +++ b/TombEngine/Objects/TR3/Trap/ElectricCleaner.cpp @@ -2,6 +2,8 @@ #include "Objects/TR3/Trap/ElectricCleaner.h" #include "Game/collision/collide_item.h" +#include "Game/collision/floordata.h" +#include "Game/collision/Point.h" #include "Game/control/box.h" #include "Game/effects/item_fx.h" #include "Game/effects/spark.h" @@ -10,6 +12,8 @@ #include "Game/misc.h" #include "Game/Setup.h" +using namespace TEN::Collision::Point; +using namespace TEN::Collision::Floordata; using namespace TEN::Effects::Items; using namespace TEN::Effects::Spark; @@ -72,13 +76,13 @@ namespace TEN::Entities::Traps static Vector3 GetElectricCleanerMovementDirection(const ItemInfo& item, const Vector3& dir0, const Vector3& dir1, const Vector3& dir2) { - if (IsNextSectorValid(item, dir0, BLOCK(1))) + if (IsNextSectorValid(item, dir0, BLOCK(1), false)) return dir0; - if (IsNextSectorValid(item, dir1, BLOCK(1))) + if (IsNextSectorValid(item, dir1, BLOCK(1), false)) return dir1; - if (IsNextSectorValid(item, dir2, BLOCK(1))) + if (IsNextSectorValid(item, dir2, BLOCK(1), false)) return dir2; return Vector3::Zero; @@ -110,7 +114,7 @@ namespace TEN::Entities::Traps auto collObjects = GetCollidedObjects(item, true, true); if (!collObjects.IsEmpty()) { - for (auto* itemPtr : collObjects.ItemPtrs) + for (auto* itemPtr : collObjects.Items) { const auto& object = Objects[itemPtr->ObjectNumber]; @@ -226,8 +230,8 @@ namespace TEN::Entities::Traps case ElectricCleanerState::MOVE: { - auto pointColl = GetCollision(item.Pose.Position.x, item.Pose.Position.y, item.Pose.Position.z, item.RoomNumber); - item.Pose.Position.y = pointColl.Position.Floor; + auto pointColl = GetPointCollision(item); + item.Pose.Position.y = pointColl.GetFloorHeight(); auto forwardDir = EulerAngles(0, item.Pose.Orientation.y, 0).ToDirection(); @@ -238,7 +242,7 @@ namespace TEN::Entities::Traps (item.Pose.Position.z & WALL_MASK) == BLOCK(0.5f)) { // Only turn on flat floor. - if (abs(pointColl.FloorTilt.x) == 0 && abs(pointColl.FloorTilt.y) == 0) + if (pointColl.GetFloorNormal() == -Vector3::UnitY) activeState = ElectricCleanerState::CHOOSE_PATH; } } @@ -323,7 +327,7 @@ namespace TEN::Entities::Traps AnimateItem(&item); - int probedRoomNumber = GetCollision(&item).RoomNumber; + int probedRoomNumber = GetPointCollision(item).GetRoomNumber(); if (item.RoomNumber != probedRoomNumber) ItemNewRoom(itemNumber, probedRoomNumber); diff --git a/TombEngine/Objects/TR3/Trap/train.cpp b/TombEngine/Objects/TR3/Trap/train.cpp index 84c2b521a..94d29c5ce 100644 --- a/TombEngine/Objects/TR3/Trap/train.cpp +++ b/TombEngine/Objects/TR3/Trap/train.cpp @@ -6,8 +6,9 @@ #include "Game/control/control.h" #include "Game/collision/collide_room.h" #include "Game/collision/collide_item.h" +#include "Game/collision/Point.h" #include "Game/collision/floordata.h" -#include "Game/collision/sphere.h" +#include "Game/collision/Sphere.h" #include "Game/effects/effects.h" #include "Game/items.h" #include "Game/Lara/lara.h" @@ -16,122 +17,129 @@ #include "Sound/sound.h" #include "Specific/level.h" -constexpr auto TRAIN_VEL = 260; +using namespace TEN::Collision::Point; +using namespace TEN::Collision::Sphere; -long TrainTestHeight(ItemInfo* item, long x, long z, short* roomNumber) +namespace TEN::Entities::Traps { - float sinX = phd_sin(item->Pose.Orientation.x); - float sinY = phd_sin(item->Pose.Orientation.y); - float cosY = phd_cos(item->Pose.Orientation.y); - float sinZ = phd_sin(item->Pose.Orientation.z); + constexpr auto TRAIN_VEL = 260; - auto pos = Vector3i( - round(item->Pose.Position.x + ((z * sinY) + (x * cosY))), - round(item->Pose.Position.y - ((z * sinX) + (x * sinZ))), - round(item->Pose.Position.z + ((z * cosY) - (x * sinY))) - ); - auto probe = GetCollision(pos.x, pos.y, pos.z, item->RoomNumber); - - *roomNumber = probe.RoomNumber; - return probe.Position.Floor; -} - -void TrainControl(short itemNumber) -{ - auto* item = &g_Level.Items[itemNumber]; - - if (!TriggerActive(item)) - return; - - if (item->ItemFlags[0] == 0) - item->ItemFlags[0] = item->ItemFlags[1] = TRAIN_VEL; - - float sinY = phd_sin(item->Pose.Orientation.y); - float cosY = phd_cos(item->Pose.Orientation.y); - - item->Pose.Position.x += item->ItemFlags[1] * sinY; - item->Pose.Position.z += item->ItemFlags[1] * cosY; - - short roomNumber; - long rh = TrainTestHeight(item, 0, BLOCK(5), &roomNumber); - long floorHeight = TrainTestHeight(item, 0, 0, &roomNumber); - item->Pose.Position.y = floorHeight; - - if (floorHeight == NO_HEIGHT) + long TrainTestHeight(ItemInfo* item, long x, long z, short* roomNumber) { - KillItem(itemNumber); - return; + float sinX = phd_sin(item->Pose.Orientation.x); + float sinY = phd_sin(item->Pose.Orientation.y); + float cosY = phd_cos(item->Pose.Orientation.y); + float sinZ = phd_sin(item->Pose.Orientation.z); + + auto pos = Vector3i( + round(item->Pose.Position.x + ((z * sinY) + (x * cosY))), + round(item->Pose.Position.y - ((z * sinX) + (x * sinZ))), + round(item->Pose.Position.z + ((z * cosY) - (x * sinY)))); + auto pointColl = GetPointCollision(pos, item->RoomNumber); + + *roomNumber = pointColl.GetRoomNumber(); + return pointColl.GetFloorHeight(); } - item->Pose.Position.y -= 32;// ? - - short probedRoomNumber = GetCollision(item).RoomNumber; - if (probedRoomNumber != item->RoomNumber) - ItemNewRoom(itemNumber, probedRoomNumber); - - item->Pose.Orientation.x = -(rh - floorHeight) * 2; - - TriggerDynamicLight(item->Pose.Position.x + BLOCK(3) * sinY, item->Pose.Position.y, item->Pose.Position.z + BLOCK(3) * cosY, 16, 31, 31, 31); - - if (item->ItemFlags[1] != TRAIN_VEL) + void ControlTrain(short itemNumber) { - item->ItemFlags[1] -= 48; - if (item->ItemFlags[1] < 0) - item->ItemFlags[1] = 0; + auto& item = g_Level.Items[itemNumber]; - if (!UseForcedFixedCamera) + if (!TriggerActive(&item)) + return; + + if (item.ItemFlags[0] == 0) + item.ItemFlags[0] = item.ItemFlags[1] = TRAIN_VEL; + + float sinY = phd_sin(item.Pose.Orientation.y); + float cosY = phd_cos(item.Pose.Orientation.y); + + item.Pose.Position.x += item.ItemFlags[1] * sinY; + item.Pose.Position.z += item.ItemFlags[1] * cosY; + + short roomNumber; + long rh = TrainTestHeight(&item, 0, BLOCK(5), &roomNumber); + long floorHeight = TrainTestHeight(&item, 0, 0, &roomNumber); + item.Pose.Position.y = floorHeight; + + if (floorHeight == NO_HEIGHT) { - ForcedFixedCamera.x = item->Pose.Position.x + BLOCK(8) * sinY; - ForcedFixedCamera.z = item->Pose.Position.z + BLOCK(8) * cosY; + KillItem(itemNumber); + return; + } - ForcedFixedCamera.y = GetCollision(ForcedFixedCamera.x, item->Pose.Position.y - CLICK(2), ForcedFixedCamera.z, item->RoomNumber).Position.Floor; + item.Pose.Position.y -= 32;// ? - ForcedFixedCamera.RoomNumber = roomNumber; - UseForcedFixedCamera = 1; + int probedRoomNumber = GetPointCollision(item).GetRoomNumber(); + if (probedRoomNumber != item.RoomNumber) + ItemNewRoom(itemNumber, probedRoomNumber); + + item.Pose.Orientation.x = -(rh - floorHeight) * 2; + + TriggerDynamicLight(item.Pose.Position.x + BLOCK(3) * sinY, item.Pose.Position.y, item.Pose.Position.z + BLOCK(3) * cosY, 16, 31, 31, 31); + + if (item.ItemFlags[1] != TRAIN_VEL) + { + item.ItemFlags[1] -= 48; + if (item.ItemFlags[1] < 0) + item.ItemFlags[1] = 0; + + if (!UseForcedFixedCamera) + { + ForcedFixedCamera.x = item.Pose.Position.x + BLOCK(8) * sinY; + ForcedFixedCamera.z = item.Pose.Position.z + BLOCK(8) * cosY; + + ForcedFixedCamera.y = GetPointCollision(Vector3i(ForcedFixedCamera.x, item.Pose.Position.y - CLICK(2), ForcedFixedCamera.z), item.RoomNumber).GetFloorHeight(); + + ForcedFixedCamera.RoomNumber = roomNumber; + UseForcedFixedCamera = 1; + } + } + else + { + SoundEffect(SFX_TR3_TUBE_LOOP, &item.Pose, SoundEnvironment::Always); } } - else - SoundEffect(SFX_TR3_TUBE_LOOP, &item->Pose, SoundEnvironment::Always); -} -void TrainCollision(short itemNumber, ItemInfo* laraItem, CollisionInfo* coll) -{ - auto* item = &g_Level.Items[itemNumber]; - auto* laraInfo = GetLaraInfo(laraItem); + void CollideTrain(short itemNumber, ItemInfo* playerItem, CollisionInfo* coll) + { + auto& item = g_Level.Items[itemNumber]; + auto& player = GetLaraInfo(*playerItem); - if (!TestBoundsCollide(item, laraItem, coll->Setup.Radius)) + if (!TestBoundsCollide(&item, playerItem, coll->Setup.Radius)) + return; + + if (!HandleItemSphereCollision(item, *playerItem)) return; - if (!TestCollision(item, laraItem)) - return; + SoundEffect(SFX_TR4_LARA_GENERAL_DEATH, &playerItem->Pose, SoundEnvironment::Always); + SoundEffect(SFX_TR4_LARA_HIGH_FALL_DEATH, &playerItem->Pose, SoundEnvironment::Always); + StopSoundEffect(SFX_TR3_TUBE_LOOP); - SoundEffect(SFX_TR4_LARA_GENERAL_DEATH, &laraItem->Pose, SoundEnvironment::Always); - SoundEffect(SFX_TR4_LARA_HIGH_FALL_DEATH, &laraItem->Pose, SoundEnvironment::Always); - StopSoundEffect(SFX_TR3_TUBE_LOOP); + SetAnimation(*playerItem, ID_LARA_EXTRA_ANIMS, LEA_TRAIN_DEATH_START); + playerItem->Animation.IsAirborne = false; + playerItem->Animation.Velocity.y = 0.0f; + playerItem->Animation.Velocity.z = 0.0f; + playerItem->Pose.Orientation.y = item.Pose.Orientation.y; - SetAnimation(*laraItem, ID_LARA_EXTRA_ANIMS, LEA_TRAIN_DEATH_START); - laraItem->Animation.IsAirborne = false; - laraItem->Animation.Velocity.y = 0.0f; - laraItem->Animation.Velocity.z = 0.0f; - laraItem->Pose.Orientation.y = item->Pose.Orientation.y; + DoDamage(playerItem, INT_MAX); - DoDamage(laraItem, INT_MAX); + AnimateItem(playerItem); - AnimateItem(laraItem); + player.ExtraAnim = 1; + player.Control.HandStatus = HandStatus::Busy; + player.Control.Weapon.GunType = LaraWeaponType::None; + player.HitDirection = NO_VALUE; + player.Status.Air = NO_VALUE; - laraInfo->ExtraAnim = 1; - laraInfo->Control.HandStatus = HandStatus::Busy; - laraInfo->Control.Weapon.GunType = LaraWeaponType::None; - laraInfo->HitDirection = -1; - laraInfo->Status.Air = -1; + item.ItemFlags[1] = 160; - item->ItemFlags[1] = 160; + float sinY = phd_sin(item.Pose.Orientation.y); + float cosY = phd_cos(item.Pose.Orientation.y); - float sinY = phd_sin(item->Pose.Orientation.y); - float cosY = phd_cos(item->Pose.Orientation.y); + long x = playerItem->Pose.Position.x + CLICK(1) * sinY; + long z = playerItem->Pose.Position.z + CLICK(1) * cosY; - long x = laraItem->Pose.Position.x + CLICK(1) * sinY; - long z = laraItem->Pose.Position.z + CLICK(1) * cosY; - - DoLotsOfBlood(x, laraItem->Pose.Position.y - CLICK(2), z, BLOCK(1), item->Pose.Orientation.y, laraItem->RoomNumber, 15); + DoLotsOfBlood(x, playerItem->Pose.Position.y - CLICK(2), z, BLOCK(1), item.Pose.Orientation.y, playerItem->RoomNumber, 15); + } } diff --git a/TombEngine/Objects/TR3/Trap/train.h b/TombEngine/Objects/TR3/Trap/train.h index 4d58d54d7..c8780d0ed 100644 --- a/TombEngine/Objects/TR3/Trap/train.h +++ b/TombEngine/Objects/TR3/Trap/train.h @@ -1,7 +1,10 @@ #pragma once -#include "Game/items.h" -#include "Game/collision/collide_room.h" +struct CollisionInfo; +struct ItemInfo; -void TrainControl(short itemNumber); -void TrainCollision(short itemNumber, ItemInfo* laraItem, CollisionInfo* coll); +namespace TEN::Entities::Traps +{ + void ControlTrain(short itemNumber); + void CollideTrain(short itemNumber, ItemInfo* playerItem, CollisionInfo* coll); +} diff --git a/TombEngine/Objects/TR3/Vehicles/big_gun.cpp b/TombEngine/Objects/TR3/Vehicles/big_gun.cpp index 52e7032fa..3c94992a0 100644 --- a/TombEngine/Objects/TR3/Vehicles/big_gun.cpp +++ b/TombEngine/Objects/TR3/Vehicles/big_gun.cpp @@ -4,8 +4,9 @@ #include "Game/animation.h" #include "Game/camera.h" #include "Game/control/box.h" -#include "Game/collision/collide_room.h" #include "Game/collision/collide_item.h" +#include "Game/collision/collide_room.h" +#include "Game/collision/Point.h" #include "Game/effects/effects.h" #include "Game/effects/tomb4fx.h" #include "Game/items.h" @@ -20,6 +21,7 @@ #include "Specific/Input/Input.h" #include "Specific/level.h" +using namespace TEN::Collision::Point; using namespace TEN::Input; namespace TEN::Entities::Vehicles @@ -126,8 +128,8 @@ namespace TEN::Entities::Vehicles auto* projectileItem = &g_Level.Items[itemNumber]; projectileItem->ObjectNumber = ID_ROCKET; auto pos = GetJointPosition(bigGunItem, BigGunBite); - auto probe = GetCollision(pos.x, pos.y, pos.z, bigGunItem->RoomNumber); - projectileItem->RoomNumber = probe.RoomNumber; + auto pointColl = GetPointCollision(pos, bigGunItem->RoomNumber); + projectileItem->RoomNumber = pointColl.GetRoomNumber(); projectileItem->Pose.Position = pos; projectileItem->Pose.Orientation = EulerAngles( -((bigGun->XOrientFrame - 32) * ANGLE(1.0f)), diff --git a/TombEngine/Objects/TR3/Vehicles/kayak.cpp b/TombEngine/Objects/TR3/Vehicles/kayak.cpp index e757275be..e66e1c11d 100644 --- a/TombEngine/Objects/TR3/Vehicles/kayak.cpp +++ b/TombEngine/Objects/TR3/Vehicles/kayak.cpp @@ -5,6 +5,7 @@ #include "Game/camera.h" #include "Game/collision/collide_item.h" #include "Game/collision/collide_room.h" +#include "Game/collision/Point.h" #include "Game/control/control.h" #include "Game/effects/effects.h" #include "Game/items.h" @@ -18,6 +19,7 @@ #include "Specific/level.h" #include "Specific/Input/Input.h" +using namespace TEN::Collision::Point; using namespace TEN::Input; namespace TEN::Entities::Vehicles @@ -31,9 +33,9 @@ namespace TEN::Entities::Vehicles constexpr int KAYAK_VELOCITY_LR_ACCEL = 16 * VEHICLE_VELOCITY_SCALE; constexpr int KAYAK_VELOCITY_HOLD_TURN_DECEL = 0.5f * VEHICLE_VELOCITY_SCALE; constexpr int KAYAK_VELOCITY_FRICTION_DECEL = 0.5f * VEHICLE_VELOCITY_SCALE; - constexpr int KAYAK_VELOCITY_MAX = 56 * VEHICLE_VELOCITY_SCALE; + constexpr auto KAYAK_FLAG_PADDLE_MESH = 0x80; constexpr auto KAYAK_WAKE_OFFSET = Vector3(BLOCK(0.1f), 0.0f, BLOCK(0.25f)); // TODO: Very confusing. @@ -212,13 +214,27 @@ namespace TEN::Entities::Vehicles int x = kayakItem->Pose.Position.x + (zOffset * sinY) + (xOffset * cosY); int z = kayakItem->Pose.Position.z + (zOffset * cosY) - (xOffset * sinY); - int probedRoomNumber = GetCollision(x, kayakItem->Pose.Position.y, z, kayakItem->RoomNumber).RoomNumber; - int waterHeight = GetWaterHeight(x, kayakItem->Pose.Position.y, z, probedRoomNumber); + int probedRoomNumber = GetPointCollision(Vector3i(x, kayakItem->Pose.Position.y, z), kayakItem->RoomNumber).GetRoomNumber(); + int waterHeight = GetPointCollision(Vector3i(x, kayakItem->Pose.Position.y, z), probedRoomNumber).GetWaterTopHeight(); //if (waterHeight != NO_HEIGHT) // SetupRipple(x, kayakItem->Pose.Position.y, z, -2 - (GetRandomControl() & 1), 0, Objects[ID_KAYAK_PADDLE_TRAIL_SPRITE].meshIndex,TO_RAD(kayakItem->Pose.Orientation.y)); } + void KayakPaddleTake(KayakInfo* kayak, ItemInfo* laraItem) + { + kayak->Flags |= KAYAK_FLAG_PADDLE_MESH; + laraItem->Model.MeshIndex[LM_RHAND] = Objects[ID_KAYAK_LARA_ANIMS].meshIndex + LM_RHAND; + laraItem->MeshBits.Clear(KayakLaraLegJoints); + } + + void KayakPaddlePut(KayakInfo* kayak, ItemInfo* laraItem) + { + kayak->Flags &= ~KAYAK_FLAG_PADDLE_MESH; + laraItem->Model.MeshIndex[LM_RHAND] = laraItem->Model.BaseMesh + LM_RHAND; + laraItem->MeshBits.Set(KayakLaraLegJoints); + } + int KayakGetCollisionAnim(ItemInfo* kayakItem, int xDiff, int zDiff) { xDiff = kayakItem->Pose.Position.x - xDiff; @@ -405,8 +421,8 @@ namespace TEN::Entities::Vehicles x = 0; z = 0; - auto probe = GetCollision(old->x, pos->y, pos->z, kayakItem->RoomNumber); - if (probe.Position.Floor < (old->y - CLICK(1))) + auto probe = GetPointCollision(Vector3i(old->x, pos->y, pos->z), kayakItem->RoomNumber); + if (probe.GetFloorHeight() < (old->y - CLICK(1))) { if (pos->z > old->z) z = -zShift - 1; @@ -414,8 +430,8 @@ namespace TEN::Entities::Vehicles z = BLOCK(1) - zShift; } - probe = GetCollision(pos->x, pos->y, old->z, kayakItem->RoomNumber); - if (probe.Position.Floor < (old->y - CLICK(1))) + probe = GetPointCollision(Vector3i(pos->x, pos->y, old->z), kayakItem->RoomNumber); + if (probe.GetFloorHeight() < (old->y - CLICK(1))) { if (pos->x > old->x) x = -xShift - 1; @@ -533,22 +549,22 @@ namespace TEN::Entities::Vehicles kayakItem->Pose.Orientation.y += rot; - auto probe = GetCollision(kayakItem); - int probedRoomNum = probe.RoomNumber; + auto probe = GetPointCollision(*kayakItem); + int probedRoomNum = probe.GetRoomNumber(); - height2 = GetWaterHeight(kayakItem->Pose.Position.x, kayakItem->Pose.Position.y, kayakItem->Pose.Position.z, probedRoomNum); + height2 = GetPointCollision(kayakItem->Pose.Position, probedRoomNum).GetWaterTopHeight(); if (height2 == NO_HEIGHT) - height2 = probe.Position.Floor; + height2 = probe.GetFloorHeight(); if (height2 < (kayakItem->Pose.Position.y - KAYAK_COLLIDE)) KayakDoShift(kayakItem, (Vector3i*)&kayakItem->Pose, &oldPos[8]); - probe = GetCollision(kayakItem); - probedRoomNum = probe.RoomNumber; + probe = GetPointCollision(*kayakItem); + probedRoomNum = probe.GetRoomNumber(); - height2 = GetWaterHeight(kayakItem->Pose.Position.x, kayakItem->Pose.Position.y, kayakItem->Pose.Position.z, probedRoomNum); + height2 = GetPointCollision(kayakItem->Pose.Position, probedRoomNum).GetWaterTopHeight(); if (height2 == NO_HEIGHT) - height2 = probe.Position.Floor; + height2 = probe.GetFloorHeight(); if (height2 == NO_HEIGHT) { @@ -895,28 +911,14 @@ namespace TEN::Entities::Vehicles break; case KAYAK_STATE_MOUNT_LEFT: - if (TestAnimNumber(*laraItem, KAYAK_ANIM_GET_PADDLE) && - frame == 24 && - !(kayak->Flags & 0x80)) - { - kayak->Flags |= 0x80; - laraItem->Model.MeshIndex[LM_RHAND] = Objects[ID_KAYAK_LARA_ANIMS].meshIndex + LM_RHAND; - laraItem->MeshBits.Clear(KayakLaraLegJoints); - } - + if (TestAnimNumber(*laraItem, KAYAK_ANIM_GET_PADDLE) && frame == 24 && !(kayak->Flags & KAYAK_FLAG_PADDLE_MESH)) + KayakPaddleTake(kayak, laraItem); break; case KAYAK_STATE_DISMOUNT: - if (TestAnimNumber(*laraItem, KAYAK_ANIM_DISMOUNT_START) && - frame == 27 && - kayak->Flags & 0x80) - { - kayak->Flags &= ~0x80; - laraItem->Model.MeshIndex[LM_RHAND] = laraItem->Model.BaseMesh + LM_RHAND; - laraItem->MeshBits.Set(KayakLaraLegJoints); - } - laraItem->Animation.TargetState = laraItem->Animation.RequiredState; + if (TestAnimNumber(*laraItem, KAYAK_ANIM_DISMOUNT_START) && frame == 27 && kayak->Flags & KAYAK_FLAG_PADDLE_MESH) + KayakPaddlePut(kayak, laraItem); break; case KAYAK_STATE_DISMOUNT_LEFT: @@ -996,7 +998,7 @@ namespace TEN::Entities::Vehicles void KayakToItemCollision(ItemInfo* kayakItem, ItemInfo* laraItem) { - for (auto i : g_Level.Rooms[kayakItem->RoomNumber].neighbors) + for (auto i : g_Level.Rooms[kayakItem->RoomNumber].NeighborRoomNumbers) { if (!g_Level.Rooms[i].Active()) continue; @@ -1081,13 +1083,13 @@ namespace TEN::Entities::Vehicles KayakToBackground(kayakItem, laraItem); TestTriggers(kayakItem, false); - auto probe = GetCollision(kayakItem); - int water = GetWaterHeight(kayakItem->Pose.Position.x, kayakItem->Pose.Position.y, kayakItem->Pose.Position.z, probe.RoomNumber); + auto probe = GetPointCollision(*kayakItem); + int water = GetPointCollision(kayakItem->Pose.Position, probe.GetRoomNumber()).GetWaterTopHeight(); kayak->WaterHeight = water; if (kayak->WaterHeight == NO_HEIGHT) { - water = probe.Position.Floor; + water = probe.GetFloorHeight(); kayak->WaterHeight = water; kayak->TrueWater = false; } @@ -1108,10 +1110,10 @@ namespace TEN::Entities::Vehicles if (lara->Context.Vehicle != NO_VALUE) { - if (kayakItem->RoomNumber != probe.RoomNumber) + if (kayakItem->RoomNumber != probe.GetRoomNumber()) { - ItemNewRoom(lara->Context.Vehicle, probe.RoomNumber); - ItemNewRoom(laraItem->Index, probe.RoomNumber); + ItemNewRoom(lara->Context.Vehicle, probe.GetRoomNumber()); + ItemNewRoom(laraItem->Index, probe.GetRoomNumber()); } laraItem->Pose.Position = kayakItem->Pose.Position; @@ -1129,7 +1131,7 @@ namespace TEN::Entities::Vehicles if (kayak->TrueWater && (kayakItem->Animation.Velocity.z != 0.0f || lara->Context.WaterCurrentPull != Vector3i::Zero)) { - int waterHeight = GetWaterHeight(kayakItem); + int waterHeight = GetPointCollision(*kayakItem).GetWaterTopHeight(); SpawnVehicleWake(*kayakItem, KAYAK_WAKE_OFFSET, waterHeight); } diff --git a/TombEngine/Objects/TR3/Vehicles/kayak.h b/TombEngine/Objects/TR3/Vehicles/kayak.h index 8eb409dea..db7aec2b6 100644 --- a/TombEngine/Objects/TR3/Vehicles/kayak.h +++ b/TombEngine/Objects/TR3/Vehicles/kayak.h @@ -6,24 +6,27 @@ struct ItemInfo; namespace TEN::Entities::Vehicles { + KayakInfo* GetKayakInfo(ItemInfo* kayakItem); void InitializeKayak(short itemNumber); void KayakPlayerCollision(short itemNumber, ItemInfo* laraItem, CollisionInfo* coll); void DoKayakMount(ItemInfo* kayakItem, ItemInfo* laraItem, VehicleMountType mountType); + void KayakPaddleTake(KayakInfo* kayak, ItemInfo* laraItem); + void KayakPaddlePut(KayakInfo* kayak, ItemInfo* laraItem); + void KayakDraw(ItemInfo* kayakItem); void KayakDoRipple(ItemInfo* kayakItem, int xOffset, int zOffset); - int KayakGetCollisionAnim(ItemInfo* kayakItem, int xDiff, int zDiff); - int KayakDoDynamics(int height, int verticalVelocity, int* y); + int KayakGetCollisionAnim(ItemInfo* kayakItem, int xDiff, int zDiff); + int KayakDoDynamics(int height, int verticalVelocity, int* y); void KayakDoCurrent(ItemInfo* kayakItem, ItemInfo* laraItem); bool KayakCanGetOut(ItemInfo* kayakItem, int dir); - int KayakDoShift(ItemInfo* kayakItem, Vector3i* pos, Vector3i* old); + int KayakDoShift(ItemInfo* kayakItem, Vector3i* pos, Vector3i* old); void KayakToBackground(ItemInfo* kayakItem, ItemInfo* laraItem); void KayakUserInput(ItemInfo* kayakItem, ItemInfo* laraItem); void KayakToItemCollision(ItemInfo* kayakItem, ItemInfo* laraItem); void KayakLaraRapidsDrown(ItemInfo* laraItem); - void PreDrawWakeFx(ItemInfo* kayakItem); bool KayakControl(ItemInfo* laraItem); } diff --git a/TombEngine/Objects/TR3/Vehicles/minecart.cpp b/TombEngine/Objects/TR3/Vehicles/minecart.cpp index 38cb99643..5fc83591b 100644 --- a/TombEngine/Objects/TR3/Vehicles/minecart.cpp +++ b/TombEngine/Objects/TR3/Vehicles/minecart.cpp @@ -3,8 +3,9 @@ #include "Game/animation.h" #include "Game/camera.h" -#include "Game/collision/sphere.h" #include "Game/collision/collide_item.h" +#include "Game/collision/Point.h" +#include "Game/collision/Sphere.h" #include "Game/effects/effects.h" #include "Game/effects/spark.h" #include "Game/effects/tomb4fx.h" @@ -20,6 +21,8 @@ #include "Specific/Input/Input.h" #include "Specific/level.h" +using namespace TEN::Collision::Point; +using namespace TEN::Collision::Sphere; using namespace TEN::Effects::Spark; using namespace TEN::Input; using namespace TEN::Math; @@ -180,7 +183,7 @@ namespace TEN::Entities::Vehicles // This allows creation of minecarts which will get to a point and stop forever. auto mountType = GetVehicleMountType(minecartItem, laraItem, coll, MinecartMountTypes, MINECART_MOUNT_DISTANCE); if (mountType == VehicleMountType::None || - GetCollision(minecartItem, minecartItem->Pose.Orientation.y, BLOCK(1)).BottomBlock->Flags.MinecartStop()) + GetPointCollision(*minecartItem, minecartItem->Pose.Orientation.y, BLOCK(1)).GetBottomSector().Flags.MinecartStop()) { ObjectCollision(itemNumber, laraItem, coll); } @@ -238,14 +241,27 @@ namespace TEN::Entities::Vehicles } } + void MinecartWrenchTake(MinecartInfo* minecart, ItemInfo* laraItem) + { + laraItem->Model.MeshIndex[LM_RHAND] = Objects[ID_MINECART_LARA_ANIMS].meshIndex + LM_RHAND; + minecart->Flags |= MINECART_FLAG_WRENCH_MESH; + } + + void MinecartWrenchPut(MinecartInfo* minecart, ItemInfo* laraItem) + { + laraItem->Model.MeshIndex[LM_RHAND] = laraItem->Model.BaseMesh + LM_RHAND; + minecart->Flags &= ~MINECART_FLAG_WRENCH_MESH; + } + static int GetMinecartCollision(ItemInfo* minecartItem, short angle, int distance) { - auto probe = GetCollision(minecartItem, angle, distance, -LARA_HEIGHT); + auto probe = GetPointCollision(*minecartItem, angle, distance, -LARA_HEIGHT); - if (probe.Position.Floor != NO_HEIGHT) - probe.Position.Floor -= minecartItem->Pose.Position.y; + if (probe.GetFloorHeight() == NO_HEIGHT) + return probe.GetFloorHeight(); + + return (probe.GetFloorHeight() - minecartItem->Pose.Position.y); - return probe.Position.Floor; } static bool TestMinecartDismount(ItemInfo* laraItem, int direction) @@ -259,16 +275,16 @@ namespace TEN::Entities::Vehicles else angle = minecartItem->Pose.Orientation.y - ANGLE(90.0f); - auto probe = GetCollision(minecartItem, angle, -MINECART_DISMOUNT_DISTANCE); + auto probe = GetPointCollision(*minecartItem, angle, -MINECART_DISMOUNT_DISTANCE); - if (probe.Position.FloorSlope || probe.Position.Floor == NO_HEIGHT) + if (probe.IsSteepFloor() || probe.GetFloorHeight() == NO_HEIGHT) return false; - if (abs(probe.Position.Floor - minecartItem->Pose.Position.y) > CLICK(2)) + if (abs(probe.GetFloorHeight() - minecartItem->Pose.Position.y) > CLICK(2)) return false; - if ((probe.Position.Ceiling - minecartItem->Pose.Position.y) > -LARA_HEIGHT || - (probe.Position.Floor - probe.Position.Ceiling) < LARA_HEIGHT) + if ((probe.GetCeilingHeight() - minecartItem->Pose.Position.y) > -LARA_HEIGHT || + (probe.GetFloorHeight() - probe.GetCeilingHeight()) < LARA_HEIGHT) { return false; } @@ -278,7 +294,7 @@ namespace TEN::Entities::Vehicles static void MinecartToEntityCollision(ItemInfo* minecartItem, ItemInfo* laraItem) { - for (auto i : g_Level.Rooms[minecartItem->RoomNumber].neighbors) + for (auto i : g_Level.Rooms[minecartItem->RoomNumber].NeighborRoomNumbers) { if (!g_Level.Rooms[i].Active()) continue; @@ -351,7 +367,7 @@ namespace TEN::Entities::Vehicles auto* minecart = GetMinecartInfo(minecartItem); auto* lara = GetLaraInfo(laraItem); - auto flags = GetCollision(minecartItem).BottomBlock->Flags; + auto flags = GetPointCollision(*minecartItem).GetBottomSector().Flags; if (minecart->StopDelayTime) minecart->StopDelayTime--; @@ -751,8 +767,7 @@ namespace TEN::Entities::Vehicles if (laraItem->Animation.FrameNumber == GetFrameIndex(minecartItem, MINECART_WRENCH_MESH_TOGGLE_FRAME) && minecart->Flags & MINECART_FLAG_WRENCH_MESH) { - laraItem->Model.MeshIndex[LM_RHAND] = laraItem->Model.BaseMesh + LM_RHAND; - minecart->Flags &= ~MINECART_FLAG_WRENCH_MESH; + MinecartWrenchPut(minecart, laraItem); } if (minecart->Flags & MINECART_FLAG_DISMOUNT_RIGHT) @@ -800,8 +815,7 @@ namespace TEN::Entities::Vehicles if (!(minecart->Flags & MINECART_FLAG_WRENCH_MESH) && laraItem->Animation.FrameNumber == GetFrameIndex(minecartItem, MINECART_WRENCH_MESH_TOGGLE_FRAME)) { - laraItem->Model.MeshIndex[LM_RHAND] = Objects[ID_MINECART_LARA_ANIMS].meshIndex + LM_RHAND; - minecart->Flags |= MINECART_FLAG_WRENCH_MESH; + MinecartWrenchTake(minecart, laraItem); } } @@ -968,7 +982,7 @@ namespace TEN::Entities::Vehicles if (lara->Context.Vehicle != NO_VALUE) laraItem->Pose = minecartItem->Pose; - short probedRoomNumber = GetCollision(minecartItem).RoomNumber; + short probedRoomNumber = GetPointCollision(*minecartItem).GetRoomNumber(); if (probedRoomNumber != minecartItem->RoomNumber) { ItemNewRoom(lara->Context.Vehicle, probedRoomNumber); diff --git a/TombEngine/Objects/TR3/Vehicles/minecart.h b/TombEngine/Objects/TR3/Vehicles/minecart.h index e5677b172..4fcfea6f6 100644 --- a/TombEngine/Objects/TR3/Vehicles/minecart.h +++ b/TombEngine/Objects/TR3/Vehicles/minecart.h @@ -7,9 +7,13 @@ struct ItemInfo; namespace TEN::Entities::Vehicles { void InitializeMinecart(short itemNumber); + MinecartInfo* GetMinecartInfo(ItemInfo* minecartItem); void MinecartPlayerCollision(short itemNumber, ItemInfo* laraItem, CollisionInfo* coll); void DoMinecartMount(ItemInfo* minecartItem, ItemInfo* laraItem, VehicleMountType mountType); bool MinecartControl(ItemInfo* laraItem); + + void MinecartWrenchTake(MinecartInfo* minecart, ItemInfo* laraItem); + void MinecartWrenchPut(MinecartInfo* minecart, ItemInfo* laraItem); } diff --git a/TombEngine/Objects/TR3/Vehicles/quad_bike.cpp b/TombEngine/Objects/TR3/Vehicles/quad_bike.cpp index 18af8d3aa..c8ae399b1 100644 --- a/TombEngine/Objects/TR3/Vehicles/quad_bike.cpp +++ b/TombEngine/Objects/TR3/Vehicles/quad_bike.cpp @@ -3,8 +3,9 @@ #include "Game/animation.h" #include "Game/camera.h" -#include "Game/collision/collide_room.h" #include "Game/collision/collide_item.h" +#include "Game/collision/collide_room.h" +#include "Game/collision/Point.h" #include "Game/effects/effects.h" #include "Game/effects/simple_particle.h" #include "Game/effects/tomb4fx.h" @@ -22,6 +23,7 @@ #include "Specific/level.h" #include "Specific/Input/Input.h" +using namespace TEN::Collision::Point; using namespace TEN::Input; using namespace TEN::Math; @@ -236,19 +238,19 @@ namespace TEN::Entities::Vehicles int y = quadBikeItem->Pose.Position.y; int z = quadBikeItem->Pose.Position.z + CLICK(2) * phd_cos(angle); - auto collResult = GetCollision(x, y, z, quadBikeItem->RoomNumber); + auto pointColl = GetPointCollision(Vector3i(x, y, z), quadBikeItem->RoomNumber); - if (collResult.Position.FloorSlope || - collResult.Position.Floor == NO_HEIGHT) + if (pointColl.IsSteepFloor() || + pointColl.GetFloorHeight() == NO_HEIGHT) { return false; } - if (abs(collResult.Position.Floor - quadBikeItem->Pose.Position.y) > CLICK(2)) + if (abs(pointColl.GetFloorHeight() - quadBikeItem->Pose.Position.y) > CLICK(2)) return false; - if ((collResult.Position.Ceiling - quadBikeItem->Pose.Position.y) > -LARA_HEIGHT || - (collResult.Position.Floor - collResult.Position.Ceiling) < LARA_HEIGHT) + if ((pointColl.GetCeilingHeight() - quadBikeItem->Pose.Position.y) > -LARA_HEIGHT || + (pointColl.GetFloorHeight() - pointColl.GetCeilingHeight()) < LARA_HEIGHT) { return false; } @@ -343,7 +345,6 @@ namespace TEN::Entities::Vehicles static int DoQuadShift(ItemInfo* quadBikeItem, Vector3i* pos, Vector3i* old) { - CollisionResult probe; int x = pos->x / BLOCK(1); int z = pos->z / BLOCK(1); int oldX = old->x / BLOCK(1); @@ -387,8 +388,8 @@ namespace TEN::Entities::Vehicles x = 0; z = 0; - probe = GetCollision(old->x, pos->y, pos->z, quadBikeItem->RoomNumber); - if (probe.Position.Floor < (old->y - CLICK(1))) + auto pointColl = GetPointCollision(Vector3i(old->x, pos->y, pos->z), quadBikeItem->RoomNumber); + if (pointColl.GetFloorHeight() < (old->y - CLICK(1))) { if (pos->z > old->z) z = -shiftZ - 1; @@ -396,8 +397,8 @@ namespace TEN::Entities::Vehicles z = BLOCK(1) - shiftZ; } - probe = GetCollision(pos->x, pos->y, old->z, quadBikeItem->RoomNumber); - if (probe.Position.Floor < (old->y - CLICK(1))) + pointColl = GetPointCollision(Vector3i(pos->x, pos->y, old->z), quadBikeItem->RoomNumber); + if (pointColl.GetFloorHeight() < (old->y - CLICK(1))) { if (pos->x > old->x) x = -shiftX - 1; @@ -536,9 +537,9 @@ namespace TEN::Entities::Vehicles else quadBikeItem->Pose.Orientation.y += quadBike->TurnRate + quadBike->ExtraRotation; - auto probe = GetCollision(quadBikeItem); + auto probe = GetPointCollision(*quadBikeItem); int speed = 0; - if (quadBikeItem->Pose.Position.y >= probe.Position.Floor) + if (quadBikeItem->Pose.Position.y >= probe.GetFloorHeight()) speed = quadBikeItem->Animation.Velocity.z * phd_cos(quadBikeItem->Pose.Orientation.x); else speed = quadBikeItem->Animation.Velocity.z; @@ -635,8 +636,8 @@ namespace TEN::Entities::Vehicles rot += rotAdd; } - probe = GetCollision(quadBikeItem); - if (probe.Position.Floor < quadBikeItem->Pose.Position.y - CLICK(1)) + probe = GetPointCollision(*quadBikeItem); + if (probe.GetFloorHeight() < quadBikeItem->Pose.Position.y - CLICK(1)) DoQuadShift(quadBikeItem, (Vector3i*)&quadBikeItem->Pose, &old); quadBike->ExtraRotation = rot; @@ -1094,7 +1095,7 @@ namespace TEN::Entities::Vehicles bool collide = QuadDynamics(quadBikeItem, laraItem); - auto probe = GetCollision(quadBikeItem); + auto probe = GetPointCollision(*quadBikeItem); Vector3i frontLeft, frontRight; auto floorHeightLeft = GetVehicleHeight(quadBikeItem, QBIKE_FRONT, -QBIKE_SIDE, false, &frontLeft); @@ -1129,7 +1130,7 @@ namespace TEN::Entities::Vehicles break; default: - drive = QuadUserControl(quadBikeItem, probe.Position.Floor, &pitch); + drive = QuadUserControl(quadBikeItem, probe.GetFloorHeight(), &pitch); HandleVehicleSpeedometer(quadBikeItem->Animation.Velocity.z, MAX_VELOCITY / (float)VEHICLE_VELOCITY_SCALE); break; } @@ -1153,7 +1154,7 @@ namespace TEN::Entities::Vehicles quadBike->Pitch = 0; } - quadBikeItem->Floor = probe.Position.Floor; + quadBikeItem->Floor = probe.GetFloorHeight(); short rotAdd = quadBike->Velocity / 4; quadBike->RearRot -= rotAdd; @@ -1162,22 +1163,22 @@ namespace TEN::Entities::Vehicles quadBike->LeftVerticalVelocity = DoQuadDynamics(floorHeightLeft, quadBike->LeftVerticalVelocity, (int*)&frontLeft.y); quadBike->RightVerticalVelocity = DoQuadDynamics(floorHeightRight, quadBike->RightVerticalVelocity, (int*)&frontRight.y); - quadBikeItem->Animation.Velocity.y = DoQuadDynamics(probe.Position.Floor, quadBikeItem->Animation.Velocity.y, (int*)&quadBikeItem->Pose.Position.y); + quadBikeItem->Animation.Velocity.y = DoQuadDynamics(probe.GetFloorHeight(), quadBikeItem->Animation.Velocity.y, (int*)&quadBikeItem->Pose.Position.y); quadBike->Velocity = DoVehicleWaterMovement(quadBikeItem, laraItem, quadBike->Velocity, QBIKE_RADIUS, &quadBike->TurnRate, QBIKE_WAKE_OFFSET); - probe.Position.Floor = (frontLeft.y + frontRight.y) / 2; - short xRot = phd_atan(QBIKE_FRONT, quadBikeItem->Pose.Position.y - probe.Position.Floor); - short zRot = phd_atan(QBIKE_SIDE, probe.Position.Floor - frontLeft.y); + int floorHeight = (frontLeft.y + frontRight.y) / 2; + short xRot = phd_atan(QBIKE_FRONT, quadBikeItem->Pose.Position.y - floorHeight); + short zRot = phd_atan(QBIKE_SIDE, floorHeight - frontLeft.y); quadBikeItem->Pose.Orientation.x += ((xRot - quadBikeItem->Pose.Orientation.x) / 2); quadBikeItem->Pose.Orientation.z += ((zRot - quadBikeItem->Pose.Orientation.z) / 2); if (!(quadBike->Flags & QBIKE_FLAG_DEAD)) { - if (probe.RoomNumber != quadBikeItem->RoomNumber) + if (probe.GetRoomNumber() != quadBikeItem->RoomNumber) { - ItemNewRoom(lara->Context.Vehicle, probe.RoomNumber); - ItemNewRoom(laraItem->Index, probe.RoomNumber); + ItemNewRoom(lara->Context.Vehicle, probe.GetRoomNumber()); + ItemNewRoom(laraItem->Index, probe.GetRoomNumber()); } laraItem->Pose = quadBikeItem->Pose; diff --git a/TombEngine/Objects/TR3/Vehicles/rubber_boat.cpp b/TombEngine/Objects/TR3/Vehicles/rubber_boat.cpp index e6ca7dabe..1b52158e4 100644 --- a/TombEngine/Objects/TR3/Vehicles/rubber_boat.cpp +++ b/TombEngine/Objects/TR3/Vehicles/rubber_boat.cpp @@ -4,7 +4,7 @@ #include "Game/animation.h" #include "Game/camera.h" #include "Game/collision/collide_item.h" -#include "Game/collision/sphere.h" +#include "Game/collision/Point.h" #include "Game/effects/Bubble.h" #include "Game/effects/effects.h" #include "Game/items.h" @@ -19,6 +19,7 @@ #include "Specific/Input/Input.h" #include "Specific/level.h" +using namespace TEN::Collision::Point; using namespace TEN::Effects::Bubble; using namespace TEN::Input; @@ -262,7 +263,7 @@ namespace TEN::Entities::Vehicles x = 0; z = 0; - int height = GetCollision(old->x, pos->y, pos->z, rBoatItem->RoomNumber).Position.Floor; + int height = GetPointCollision(Vector3i(old->x, pos->y, pos->z), rBoatItem->RoomNumber).GetFloorHeight(); if (height < (old->y - CLICK(1))) { if (pos->z > old->z) @@ -271,7 +272,7 @@ namespace TEN::Entities::Vehicles z = BLOCK(1) - zShift; } - height = GetCollision(pos->x, pos->y, old->z, rBoatItem->RoomNumber).Position.Floor; + height = GetPointCollision(Vector3i(pos->x, pos->y, old->z), rBoatItem->RoomNumber).GetFloorHeight(); if (height < (old->y - CLICK(1))) { if (pos->x > old->x) @@ -421,7 +422,7 @@ namespace TEN::Entities::Vehicles short roomNumber = rBoatItem->RoomNumber; auto floor = GetFloor(rBoatItem->Pose.Position.x, rBoatItem->Pose.Position.y, rBoatItem->Pose.Position.z, &roomNumber); - int height = GetWaterHeight(rBoatItem->Pose.Position.x, rBoatItem->Pose.Position.y, rBoatItem->Pose.Position.z, roomNumber); + int height = GetPointCollision(rBoatItem->Pose.Position, roomNumber).GetWaterTopHeight(); if (height == NO_HEIGHT) height = GetFloorHeight(floor, rBoatItem->Pose.Position.x, rBoatItem->Pose.Position.y, rBoatItem->Pose.Position.z); @@ -604,16 +605,16 @@ namespace TEN::Entities::Vehicles int y = sBoatItem->Pose.Position.y; int z = sBoatItem->Pose.Position.z + BLOCK(1) * phd_cos(angle); - auto collResult = GetCollision(x, y, z, sBoatItem->RoomNumber); + auto pointColl = GetPointCollision(Vector3i(x, y, z), sBoatItem->RoomNumber); - if ((collResult.Position.Floor - sBoatItem->Pose.Position.y) < -512) + if ((pointColl.GetFloorHeight() - sBoatItem->Pose.Position.y) < -512) return false; - if (collResult.Position.FloorSlope || collResult.Position.Floor == NO_HEIGHT) + if (pointColl.IsSteepFloor() || pointColl.GetFloorHeight() == NO_HEIGHT) return false; - if ((collResult.Position.Ceiling - sBoatItem->Pose.Position.y) > -LARA_HEIGHT || - (collResult.Position.Floor - collResult.Position.Ceiling) < LARA_HEIGHT) + if ((pointColl.GetCeilingHeight() - sBoatItem->Pose.Position.y) > -LARA_HEIGHT || + (pointColl.GetFloorHeight() - pointColl.GetCeilingHeight()) < LARA_HEIGHT) { return false; } @@ -786,14 +787,14 @@ namespace TEN::Entities::Vehicles int y = laraItem->Pose.Position.y - 90; int z = laraItem->Pose.Position.z + 360 * phd_cos(laraItem->Pose.Orientation.y); - auto probe = GetCollision(x, y, z, laraItem->RoomNumber); - if (probe.Position.Floor >= (y - CLICK(1))) + auto probe = GetPointCollision(Vector3i(x, y, z), laraItem->RoomNumber); + if (probe.GetFloorHeight() >= (y - CLICK(1))) { laraItem->Pose.Position.x = x; laraItem->Pose.Position.z = z; - if (probe.RoomNumber != laraItem->RoomNumber) - ItemNewRoom(laraItem->Index, probe.RoomNumber); + if (probe.GetRoomNumber() != laraItem->RoomNumber) + ItemNewRoom(laraItem->Index, probe.GetRoomNumber()); } laraItem->Pose.Position.y = y; @@ -824,8 +825,8 @@ namespace TEN::Entities::Vehicles TestTriggers(rBoatItem, true); } - auto probe = GetCollision(rBoatItem); - int water = GetWaterHeight(rBoatItem->Pose.Position.x, rBoatItem->Pose.Position.y, rBoatItem->Pose.Position.z, probe.RoomNumber); + auto probe = GetPointCollision(*rBoatItem); + int water = GetPointCollision(rBoatItem->Pose.Position, probe.GetRoomNumber()).GetWaterTopHeight(); rBoat->Water = water; if (lara->Context.Vehicle == itemNumber && laraItem->HitPoints > 0) @@ -861,7 +862,7 @@ namespace TEN::Entities::Vehicles rBoat->TurnRate = 0; } - height = probe.Position.Floor; + height = probe.GetFloorHeight(); rBoatItem->Floor = height - 5; if (rBoat->Water == NO_HEIGHT) @@ -895,10 +896,10 @@ namespace TEN::Entities::Vehicles { RubberBoatAnimation(rBoatItem, laraItem, collide); - if (probe.RoomNumber != rBoatItem->RoomNumber) + if (probe.GetRoomNumber() != rBoatItem->RoomNumber) { - ItemNewRoom(itemNumber, probe.RoomNumber); - ItemNewRoom(laraItem->Index, probe.RoomNumber); + ItemNewRoom(itemNumber, probe.GetRoomNumber()); + ItemNewRoom(laraItem->Index, probe.GetRoomNumber()); } rBoatItem->Pose.Orientation.z += rBoat->LeanAngle; @@ -914,8 +915,8 @@ namespace TEN::Entities::Vehicles } else { - if (probe.RoomNumber != rBoatItem->RoomNumber) - ItemNewRoom(itemNumber, probe.RoomNumber); + if (probe.GetRoomNumber() != rBoatItem->RoomNumber) + ItemNewRoom(itemNumber, probe.GetRoomNumber()); rBoatItem->Pose.Orientation.z += rBoat->LeanAngle; } @@ -933,15 +934,15 @@ namespace TEN::Entities::Vehicles DoRubberBoatDismount(rBoatItem, laraItem); - short probedRoomNumber = GetCollision(rBoatItem->Pose.Position.x, rBoatItem->Pose.Position.y + 128, rBoatItem->Pose.Position.z, rBoatItem->RoomNumber).RoomNumber; - height = GetWaterHeight(rBoatItem->Pose.Position.x, rBoatItem->Pose.Position.y + 128, rBoatItem->Pose.Position.z, probedRoomNumber); + short probedRoomNumber = GetPointCollision(Vector3i(rBoatItem->Pose.Position.x, rBoatItem->Pose.Position.y + 128, rBoatItem->Pose.Position.z), rBoatItem->RoomNumber).GetRoomNumber(); + height = GetPointCollision(Vector3i(rBoatItem->Pose.Position.x, rBoatItem->Pose.Position.y + 128, rBoatItem->Pose.Position.z), probedRoomNumber).GetWaterTopHeight(); if (height > rBoatItem->Pose.Position.y + 32 || height == NO_HEIGHT) height = 0; else height = 1; auto prop = GetJointPosition(rBoatItem, 2, Vector3i(0, 0, -80)); - probedRoomNumber = GetCollision(prop.x, prop.y, prop.z, rBoatItem->RoomNumber).RoomNumber; + probedRoomNumber = GetPointCollision(prop, rBoatItem->RoomNumber).GetRoomNumber(); if (rBoatItem->Animation.Velocity.z && height < prop.y && @@ -949,7 +950,7 @@ namespace TEN::Entities::Vehicles { TriggerRubberBoatMist(prop.x, prop.y, prop.z, abs(rBoatItem->Animation.Velocity.z), rBoatItem->Pose.Orientation.y + ANGLE(180.0f), 0); - int waterHeight = GetWaterHeight(rBoatItem); + int waterHeight = GetPointCollision(*rBoatItem).GetWaterTopHeight(); SpawnVehicleWake(*rBoatItem, RBOAT_WAKE_OFFSET, waterHeight); if ((GetRandomControl() & 1) == 0) @@ -968,7 +969,7 @@ namespace TEN::Entities::Vehicles } else { - height = GetCollision(prop.x, prop.y, prop.z, rBoatItem->RoomNumber).Position.Floor; + height = GetPointCollision(prop, rBoatItem->RoomNumber).GetFloorHeight(); if (prop.y > height && !TestEnvironment(ENV_FLAG_WATER, probedRoomNumber)) { diff --git a/TombEngine/Objects/TR3/Vehicles/upv.cpp b/TombEngine/Objects/TR3/Vehicles/upv.cpp index 4b4bf1573..60ea75f8a 100644 --- a/TombEngine/Objects/TR3/Vehicles/upv.cpp +++ b/TombEngine/Objects/TR3/Vehicles/upv.cpp @@ -3,9 +3,10 @@ #include "Game/animation.h" #include "Game/camera.h" -#include "Game/collision/sphere.h" #include "Game/collision/collide_item.h" #include "Game/collision/collide_room.h" +#include "Game/collision/Point.h" +#include "Game/collision/Sphere.h" #include "Game/control/box.h" #include "Game/control/los.h" #include "Game/effects/Bubble.h" @@ -26,6 +27,8 @@ #include "Specific/level.h" #include "Specific/Input/Input.h" +using namespace TEN::Collision::Point; +using namespace TEN::Collision::Sphere; using namespace TEN::Effects::Bubble; using namespace TEN::Effects::Streamer; using namespace TEN::Input; @@ -148,21 +151,13 @@ namespace TEN::Entities::Vehicles UPV_BITE_RIGHT_RUDDER_RIGHT = 4, UPV_BITE_RIGHT_RUDDER_LEFT = 5 // Unused. }; - enum UPVFlags - { - UPV_FLAG_CONTROL = (1 << 0), - UPV_FLAG_SURFACE = (1 << 1), - UPV_FLAG_DIVE = (1 << 2), - UPV_FLAG_DEAD = (1 << 3) - }; - UPVInfo* GetUPVInfo(ItemInfo* UPVItem) { return (UPVInfo*)UPVItem->Data; } - void UPVInitialize(short itemNumber) + void InitializeUPV(short itemNumber) { auto* UPVItem = &g_Level.Items[itemNumber]; UPVItem->Data = UPVInfo(); @@ -183,7 +178,7 @@ namespace TEN::Entities::Vehicles if (mountType == VehicleMountType::None) { // HACK: Collision in water behaves differently? @Sezz 2022.06.28 - if (TestBoundsCollide(UPVItem, laraItem, coll->Setup.Radius) && TestCollision(UPVItem, laraItem)) + if (TestBoundsCollide(UPVItem, laraItem, coll->Setup.Radius) && HandleItemSphereCollision(*UPVItem, *laraItem)) ItemPushItem(UPVItem, laraItem, coll, false, 0); } else @@ -311,7 +306,7 @@ namespace TEN::Entities::Vehicles if (Random::TestProbability(1 / 2.0f)) { auto bubblePos = Random::GeneratePointInSphere(sphere); - int probedRoomNumber = GetCollision(bubblePos.x, bubblePos.y, bubblePos.z, UPVItem->RoomNumber).RoomNumber; + int probedRoomNumber = GetPointCollision(bubblePos, UPVItem->RoomNumber).GetRoomNumber(); for (int i = 0; i < 3; i++) SpawnBubble(bubblePos, probedRoomNumber, (int)BubbleFlags::HighAmplitude); @@ -358,12 +353,12 @@ namespace TEN::Entities::Vehicles int z = UPVItem->Pose.Position.z + velocity * phd_cos(moveAngle); int y = UPVItem->Pose.Position.y - UPV_DISMOUNT_DISTANCE * phd_sin(-UPVItem->Pose.Orientation.x); - auto probe = GetCollision(x, y, z, UPVItem->RoomNumber); - if ((probe.Position.Floor - probe.Position.Ceiling) < CLICK(1) || - probe.Position.Floor < y || - probe.Position.Ceiling > y || - probe.Position.Floor == NO_HEIGHT || - probe.Position.Ceiling == NO_HEIGHT) + auto probe = GetPointCollision(Vector3i(x, y, z), UPVItem->RoomNumber); + if ((probe.GetFloorHeight() - probe.GetCeilingHeight()) < CLICK(1) || + probe.GetFloorHeight() < y || + probe.GetCeilingHeight() > y || + probe.GetFloorHeight() == NO_HEIGHT || + probe.GetCeilingHeight() == NO_HEIGHT) { return false; } @@ -729,8 +724,9 @@ namespace TEN::Entities::Vehicles UPV->Flags &= ~UPV_FLAG_CONTROL; int waterDepth, waterHeight, heightFromWater; - waterDepth = GetWaterSurface(laraItem); - waterHeight = GetWaterHeight(laraItem); + auto pointColl = GetPointCollision(*laraItem); + waterDepth = pointColl.GetWaterSurfaceHeight(); + waterHeight = pointColl.GetWaterTopHeight(); if (waterHeight != NO_HEIGHT) heightFromWater = laraItem->Pose.Position.y - waterHeight; @@ -849,7 +845,7 @@ namespace TEN::Entities::Vehicles auto* UPV = GetUPVInfo(UPVItem); auto oldPos = UPVItem->Pose; - auto probe = GetCollision(UPVItem); + auto probe = GetPointCollision(*UPVItem); if (!(UPV->Flags & UPV_FLAG_DEAD)) { @@ -869,8 +865,9 @@ namespace TEN::Entities::Vehicles TranslateItem(UPVItem, UPVItem->Pose.Orientation, UPVItem->Animation.Velocity.z); } - int newHeight = GetCollision(UPVItem).Position.Floor; - int waterHeight = GetWaterHeight(UPVItem); + auto pointColl = GetPointCollision(*UPVItem); + int newHeight = pointColl.GetFloorHeight(); + int waterHeight = pointColl.GetWaterTopHeight(); if ((newHeight - waterHeight) < UPV_HEIGHT || (newHeight < UPVItem->Pose.Position.y - UPV_HEIGHT / 2) || (newHeight == NO_HEIGHT) || (waterHeight == NO_HEIGHT)) @@ -879,7 +876,7 @@ namespace TEN::Entities::Vehicles UPVItem->Animation.Velocity.z = 0; } - UPVItem->Floor = probe.Position.Floor; + UPVItem->Floor = probe.GetFloorHeight(); if (UPV->Flags & UPV_FLAG_CONTROL && !(UPV->Flags & UPV_FLAG_DEAD)) { @@ -898,7 +895,7 @@ namespace TEN::Entities::Vehicles UPV->Flags |= UPV_FLAG_SURFACE; } else if ((waterHeight - UPVItem->Pose.Position.y) >= -UPV_WATER_SURFACE_DISTANCE && waterHeight != NO_HEIGHT && - (laraItem->Pose.Position.y - probe.Position.Ceiling) >= CLICK(1)) + (laraItem->Pose.Position.y - probe.GetCeilingHeight()) >= CLICK(1)) { UPVItem->Pose.Position.y = waterHeight + UPV_WATER_SURFACE_DISTANCE; @@ -941,7 +938,7 @@ namespace TEN::Entities::Vehicles if (UPV->Velocity || IsDirectionalActionHeld()) { - waterHeight = GetWaterHeight(UPVItem); + waterHeight = GetPointCollision(*UPVItem).GetWaterTopHeight(); SpawnVehicleWake(*UPVItem, UPV_WAKE_OFFSET, waterHeight, true); } @@ -963,10 +960,10 @@ namespace TEN::Entities::Vehicles } } - if (probe.RoomNumber != UPVItem->RoomNumber) + if (probe.GetRoomNumber() != UPVItem->RoomNumber) { - ItemNewRoom(lara->Context.Vehicle, probe.RoomNumber); - ItemNewRoom(laraItem->Index, probe.RoomNumber); + ItemNewRoom(lara->Context.Vehicle, probe.GetRoomNumber()); + ItemNewRoom(laraItem->Index, probe.GetRoomNumber()); } laraItem->Pose = UPVItem->Pose; @@ -991,8 +988,8 @@ namespace TEN::Entities::Vehicles { AnimateItem(laraItem); - if (probe.RoomNumber != UPVItem->RoomNumber) - ItemNewRoom(lara->Context.Vehicle, probe.RoomNumber); + if (probe.GetRoomNumber() != UPVItem->RoomNumber) + ItemNewRoom(lara->Context.Vehicle, probe.GetRoomNumber()); BackgroundCollision(UPVItem, laraItem); diff --git a/TombEngine/Objects/TR3/Vehicles/upv.h b/TombEngine/Objects/TR3/Vehicles/upv.h index dec8f642a..897acd302 100644 --- a/TombEngine/Objects/TR3/Vehicles/upv.h +++ b/TombEngine/Objects/TR3/Vehicles/upv.h @@ -6,7 +6,16 @@ struct ItemInfo; namespace TEN::Entities::Vehicles { - void UPVInitialize(short itemNumber); + enum UPVFlags + { + UPV_FLAG_CONTROL = (1 << 0), + UPV_FLAG_SURFACE = (1 << 1), + UPV_FLAG_DIVE = (1 << 2), + UPV_FLAG_DEAD = (1 << 3) + }; + + void InitializeUPV(short itemNumber); + UPVInfo* GetUPVInfo(ItemInfo* UPVItem); void UPVPlayerCollision(short itemNumber, ItemInfo* laraItem, CollisionInfo* coll); void DoUPVMount(ItemInfo* UPVItem, ItemInfo* laraItem, VehicleMountType mountType); diff --git a/TombEngine/Objects/TR3/tr3_objects.cpp b/TombEngine/Objects/TR3/tr3_objects.cpp index 2b607a6ca..950c4fb7d 100644 --- a/TombEngine/Objects/TR3/tr3_objects.cpp +++ b/TombEngine/Objects/TR3/tr3_objects.cpp @@ -8,28 +8,29 @@ #include "Specific/level.h" // Creatures -#include "Objects/TR3/Entity/Compsognathus.h" // OK -#include "Objects/TR3/Entity/Lizard.h" // OK -#include "Objects/TR3/Entity/PunaBoss.h" // OK -#include "Objects/TR3/Entity/Shiva.h" // OK -#include "Objects/TR3/Entity/SophiaLeigh.h" // OK +#include "Objects/TR3/Entity/Compsognathus.h" +#include "Objects/TR3/Entity/Lizard.h" +#include "Objects/TR3/Entity/PunaBoss.h" +#include "Objects/TR3/Entity/SealMutant.h" +#include "Objects/TR3/Entity/Shiva.h" +#include "Objects/TR3/Entity/SophiaLeigh.h" +#include "Objects/TR3/Entity/Raptor.h" #include "Objects/TR3/Entity/TwinAutoGun.h" -#include "Objects/TR3/Entity/WaspMutant.h" // OK -#include "Objects/TR3/Entity/Winston.h" // OK -#include "Objects/TR3/Entity/tr3_tony.h" // OK -#include "Objects/TR3/Entity/tr3_civvy.h" // OK -#include "Objects/TR3/Entity/tr3_claw_mutant.h" // OK -#include "Objects/TR3/Entity/tr3_cobra.h" // OK -#include "Objects/TR3/Entity/FishSwarm.h" // OK -#include "Objects/TR3/Entity/tr3_flamethrower.h" // OK -#include "Objects/TR3/Entity/tr3_monkey.h" // OK -#include "Objects/TR3/Entity/tr3_mp_gun.h" // OK -#include "Objects/TR3/Entity/tr3_mp_stick.h" // OK -#include "Objects/TR3/Entity/tr3_raptor.h" // OK -#include "Objects/TR3/Entity/tr3_scuba_diver.h" // OK -#include "Objects/TR3/Entity/tr3_tiger.h" // OK -#include "Objects/TR3/Entity/tr3_trex.h" // OK -#include "Objects/TR3/Entity/tr3_tribesman.h" // OK +#include "Objects/TR3/Entity/WaspMutant.h" +#include "Objects/TR3/Entity/Winston.h" +#include "Objects/TR3/Entity/tr3_tony.h" +#include "Objects/TR3/Entity/tr3_civvy.h" +#include "Objects/TR3/Entity/tr3_claw_mutant.h" +#include "Objects/TR3/Entity/tr3_cobra.h" +#include "Objects/TR3/Entity/FishSwarm.h" +#include "Objects/TR3/Entity/tr3_flamethrower.h" +#include "Objects/TR3/Entity/tr3_monkey.h" +#include "Objects/TR3/Entity/tr3_mp_gun.h" +#include "Objects/TR3/Entity/tr3_mp_stick.h" +#include "Objects/TR3/Entity/tr3_scuba_diver.h" +#include "Objects/TR3/Entity/tr3_tiger.h" +#include "Objects/TR3/Entity/tr3_trex.h" +#include "Objects/TR3/Entity/tr3_tribesman.h" // Effects #include "Objects/Effects/Boss.h" @@ -117,6 +118,7 @@ static void StartEntity(ObjectInfo* obj) obj->radius = 341; obj->pivotLength = 600; obj->intelligent = true; + obj->LotType = LotType::HumanPlusJump; obj->SetBoneRotationFlags(20, ROT_Y); obj->SetBoneRotationFlags(21, ROT_Y); obj->SetBoneRotationFlags(23, ROT_Y); @@ -365,7 +367,7 @@ static void StartEntity(ObjectInfo* obj) obj->SetBoneRotationFlags(7, ROT_X | ROT_Y); // Head. obj->SetHitEffect(); } - + obj = &Objects[ID_WASP_MUTANT]; if (obj->loaded) { @@ -380,7 +382,7 @@ static void StartEntity(ObjectInfo* obj) obj->LotType = LotType::Flyer; obj->SetHitEffect(); } - + obj = &Objects[ID_COMPSOGNATHUS]; if (obj->loaded) { @@ -412,6 +414,22 @@ static void StartEntity(ObjectInfo* obj) obj->SetHitEffect(); } + obj = &Objects[ID_SEAL_MUTANT]; + if (obj->loaded) + { + obj->Initialize = InitializeSealMutant; + obj->collision = CreatureCollision; + obj->control = ControlSealMutant; + obj->shadowType = ShadowMode::All; + obj->HitPoints = 50; + obj->radius = 204; + obj->pivotLength = 0; + obj->intelligent = true; + obj->SetBoneRotationFlags(8, ROT_X | ROT_Z); // Torso X/Z + obj->SetBoneRotationFlags(9, ROT_Y); // Head + obj->SetHitEffect(); + } + obj = &Objects[ID_WINSTON]; if (obj->loaded) { @@ -484,8 +502,8 @@ static void StartTrap(ObjectInfo* obj) obj = &Objects[ID_TRAIN]; if (obj->loaded) { - obj->control = TrainControl; - obj->collision = TrainCollision; + obj->control = ControlTrain; + obj->collision = CollideTrain; obj->SetHitEffect(true); } @@ -565,7 +583,7 @@ static void StartVehicles(ObjectInfo* obj) obj = &Objects[ID_UPV]; if (obj->loaded) { - obj->Initialize = UPVInitialize; + obj->Initialize = InitializeUPV; obj->control = UPVEffects; obj->collision = UPVPlayerCollision; obj->shadowType = ShadowMode::Lara; diff --git a/TombEngine/Objects/TR4/Entity/Wraith.cpp b/TombEngine/Objects/TR4/Entity/Wraith.cpp index 4546d6938..9eccd1b87 100644 --- a/TombEngine/Objects/TR4/Entity/Wraith.cpp +++ b/TombEngine/Objects/TR4/Entity/Wraith.cpp @@ -2,6 +2,7 @@ #include "Objects/TR4/Entity/Wraith.h" #include "Game/collision/collide_room.h" +#include "Game/collision/Point.h" #include "Game/control/flipeffect.h" #include "Game/effects/effects.h" #include "Game/effects/Electricity.h" @@ -18,6 +19,7 @@ #include "Sound/sound.h" #include "Specific/level.h" +using namespace TEN::Collision::Point; using namespace TEN::Effects::Items; using namespace TEN::Effects::Electricity; using namespace TEN::Effects::Streamer; @@ -227,13 +229,13 @@ namespace TEN::Entities::TR4 { const auto& room = g_Level.Rooms[LaraItem->RoomNumber]; - x = room.x + room.xSize * BLOCK(1) / 2 - item.Pose.Position.x; - z = room.z + room.zSize * BLOCK(1) / 2 - item.Pose.Position.z; + x = room.Position.x + room.XSize * BLOCK(1) / 2 - item.Pose.Position.x; + z = room.Position.z + room.ZSize * BLOCK(1) / 2 - item.Pose.Position.z; distance = SQUARE(x) + SQUARE(z); dy = abs((distance / MAX_VISIBILITY_DISTANCE) - CLICK(1)); //Prevent Wraiths to go below floor level - y = room.y + ((room.maxceiling - room.minfloor) / 4); + y = room.Position.y + ((room.TopHeight - room.BottomHeight) / 4); } dy = y - item.Pose.Position.y - dy - CLICK(0.5f); @@ -303,11 +305,11 @@ namespace TEN::Entities::TR4 item.Pose.Orientation.x += angleV; } - auto pointColl = GetCollision(&item); + auto pointColl = GetPointCollision(item); bool hasHitWall = false; - if (pointColl.Position.Floor < item.Pose.Position.y || - pointColl.Position.Ceiling > item.Pose.Position.y) + if (pointColl.GetFloorHeight() < item.Pose.Position.y || + pointColl.GetCeilingHeight() > item.Pose.Position.y) { hasHitWall = true; } @@ -317,8 +319,8 @@ namespace TEN::Entities::TR4 item.Pose.Position.y += item.Animation.Velocity.z * phd_sin(item.Pose.Orientation.x); item.Pose.Position.z += item.Animation.Velocity.z * phd_cos(item.Pose.Orientation.y); - if (pointColl.RoomNumber != item.RoomNumber) - ItemNewRoom(itemNumber, pointColl.RoomNumber); + if (pointColl.GetRoomNumber() != item.RoomNumber) + ItemNewRoom(itemNumber, pointColl.GetRoomNumber()); for (int linkItemNumber = g_Level.Rooms[item.RoomNumber].itemNumber; linkItemNumber != NO_VALUE; linkItemNumber = g_Level.Items[linkItemNumber].NextItem) { @@ -550,10 +552,10 @@ namespace TEN::Entities::TR4 } // Check if WRAITH is below floor or above ceiling and spawn wall effect - pointColl = GetCollision(&item); + pointColl = GetPointCollision(item); - if (pointColl.Position.Floor < item.Pose.Position.y || - pointColl.Position.Ceiling > item.Pose.Position.y) + if (pointColl.GetFloorHeight() < item.Pose.Position.y || + pointColl.GetCeilingHeight() > item.Pose.Position.y) { if (!hasHitWall) WraithWallEffect(prevPos, item.Pose.Orientation.y - ANGLE(180.0f), item.ObjectNumber); @@ -710,7 +712,7 @@ namespace TEN::Entities::TR4 if (item2->NextActive == NO_VALUE) { - FlipEffect = -1; + FlipEffect = NO_VALUE; return; } } @@ -718,6 +720,6 @@ namespace TEN::Entities::TR4 item2->HitPoints = item->Index; } - FlipEffect = -1; + FlipEffect = NO_VALUE; } } diff --git a/TombEngine/Objects/TR4/Entity/tr4_ahmet.cpp b/TombEngine/Objects/TR4/Entity/tr4_ahmet.cpp index a2754fb84..71284ef93 100644 --- a/TombEngine/Objects/TR4/Entity/tr4_ahmet.cpp +++ b/TombEngine/Objects/TR4/Entity/tr4_ahmet.cpp @@ -1,7 +1,7 @@ #include "framework.h" #include "Objects/TR4/Entity/tr4_ahmet.h" -#include "Game/collision/sphere.h" +#include "Game/collision/Sphere.h" #include "Game/control/box.h" #include "Game/control/control.h" #include "Game/control/lot.h" @@ -17,6 +17,7 @@ #include "Sound/sound.h" #include "Specific/level.h" +using namespace TEN::Collision::Sphere; using namespace TEN::Effects::Environment; using namespace TEN::Math; @@ -80,26 +81,21 @@ namespace TEN::Entities::TR4 static void TriggerAhmetDeathEffect(ItemInfo* item) { - // HACK: Using CreatureSpheres here in release mode results in total mess-up - // of LaraSpheres, which in-game appears as a ghostly Lara fire silhouette. - // Later, both CreatureSpheres and LaraSpheres globals should be eradicated. - - static SPHERE spheres[MAX_SPHERES] = {}; - if (!(Wibble & 7)) { - int meshCount = GetSpheres(item, spheres, SPHERES_SPACE_WORLD, Matrix::Identity); - auto sphere = &spheres[(Wibble / 8) & 1]; - for (int i = meshCount; i > 0; i--, sphere += 2) - TriggerFireFlame(sphere->x, sphere->y, sphere->z, FlameType::Medium); + auto spheres = item->GetSpheres(); + const auto* spherePtr = &spheres[(Wibble / 8) & 1]; + + // TODO + for (int i = (int)spheres.size(); i > 0; i--, spherePtr += 2) + TriggerFireFlame(spherePtr->Center.x, spherePtr->Center.y, spherePtr->Center.z, FlameType::Medium); } TriggerDynamicLight( item->Pose.Position.x, item->Pose.Position.y - CLICK(1), item->Pose.Position.z, - 13, (GetRandomControl() & 0x3F) - 64, (GetRandomControl() & 0x1F) + 96, 0 - ); + 13, (GetRandomControl() & 0x3F) - 64, (GetRandomControl() & 0x1F) + 96, 0); SoundEffect(SFX_TR4_LOOP_FOR_SMALL_FIRES, &item->Pose); } diff --git a/TombEngine/Objects/TR4/Entity/tr4_baboon.cpp b/TombEngine/Objects/TR4/Entity/tr4_baboon.cpp index c3436320c..69d7404bc 100644 --- a/TombEngine/Objects/TR4/Entity/tr4_baboon.cpp +++ b/TombEngine/Objects/TR4/Entity/tr4_baboon.cpp @@ -2,6 +2,7 @@ #include "Objects/TR4/Entity/tr4_baboon.h" #include "Game/collision/collide_room.h" +#include "Game/collision/Point.h" #include "Game/control/box.h" #include "Game/control/lot.h" #include "Game/control/control.h" @@ -15,6 +16,7 @@ #include "Game/Setup.h" #include "Math/Math.h" +using namespace TEN::Collision::Point; using namespace TEN::Effects::Environment; using namespace TEN::Math; @@ -512,9 +514,9 @@ namespace TEN::Entities::TR4 pos.y = item->Pose.Position.y; - auto probe = GetCollision(pos.x, pos.y, pos.z, item->RoomNumber); - item->Floor = probe.Position.Floor; - TestTriggers(pos.x, pos.y, pos.z, probe.RoomNumber, true); + auto probe = GetPointCollision(pos, item->RoomNumber); + item->Floor = probe.GetFloorHeight(); + TestTriggers(pos.x, pos.y, pos.z, probe.GetRoomNumber(), true); item->TriggerFlags = 1; } diff --git a/TombEngine/Objects/TR4/Entity/tr4_baddy.cpp b/TombEngine/Objects/TR4/Entity/tr4_baddy.cpp index c2f45016a..80c1036ef 100644 --- a/TombEngine/Objects/TR4/Entity/tr4_baddy.cpp +++ b/TombEngine/Objects/TR4/Entity/tr4_baddy.cpp @@ -3,6 +3,7 @@ #include "Game/animation.h" #include "Game/collision/collide_room.h" +#include "Game/collision/Point.h" #include "Game/control/box.h" #include "Game/control/control.h" #include "Game/control/lot.h" @@ -18,6 +19,7 @@ #include "Math/Math.h" #include "Specific/level.h" +using namespace TEN::Collision::Point; using namespace TEN::Math; /* @@ -343,7 +345,7 @@ namespace TEN::Entities::TR4 if (item->TriggerFlags % 1000 > 100) { item->ItemFlags[0] = -80; - FindAITargetObject(creature, ID_AI_X1); + FindAITargetObject(*item, ID_AI_X1); } item->TriggerFlags = 1000 * (item->TriggerFlags / 1000); @@ -359,15 +361,15 @@ namespace TEN::Entities::TR4 x += dx; z += dz; - int height1 = GetCollision(x, y, z, item->RoomNumber).Position.Floor; + int height1 = GetPointCollision(Vector3i(x, y, z), item->RoomNumber).GetFloorHeight(); x += dx; z += dz; - int height2 = GetCollision(x, y, z, item->RoomNumber).Position.Floor; + int height2 = GetPointCollision(Vector3i(x, y, z), item->RoomNumber).GetFloorHeight(); x += dx; z += dz; - int height3 = GetCollision(x, y, z, item->RoomNumber).Position.Floor; + int height3 = GetPointCollision(Vector3i(x, y, z), item->RoomNumber).GetFloorHeight(); int height = 0; bool canJump1Sector = true; @@ -421,11 +423,11 @@ namespace TEN::Entities::TR4 item->ItemFlags[1] = item->RoomNumber; - CollisionResult probe; + auto probe = GetPointCollision(*item); if (item->HitPoints <= 0) { - item->Floor = GetCollision(item).Position.Floor; + item->Floor = GetPointCollision(*item).GetFloorHeight(); currentCreature->LOT.IsMonkeying = false; switch (item->Animation.ActiveState) @@ -575,7 +577,7 @@ namespace TEN::Entities::TR4 x = item->Pose.Position.x + dx; y = item->Pose.Position.y; z = item->Pose.Position.z + dz; - int height4 = GetCollision(x, y, z, item->RoomNumber).Position.Floor; + int height4 = GetPointCollision(Vector3i(x, y, z), item->RoomNumber).GetFloorHeight(); dx = 942 * phd_sin(item->Pose.Orientation.y + ANGLE(78.75f)); dz = 942 * phd_cos(item->Pose.Orientation.y + ANGLE(78.75f)); @@ -583,7 +585,7 @@ namespace TEN::Entities::TR4 x = item->Pose.Position.x + dx; y = item->Pose.Position.y; z = item->Pose.Position.z + dz; - int height5 = GetCollision(x, y, z, item->RoomNumber).Position.Floor; + int height5 = GetPointCollision(Vector3i(x, y, z), item->RoomNumber).GetFloorHeight(); if (abs(height5 - item->Pose.Position.y) > CLICK(1)) jump = false; @@ -600,7 +602,7 @@ namespace TEN::Entities::TR4 x = item->Pose.Position.x + dx; y = item->Pose.Position.y; z = item->Pose.Position.z + dz; - int height6 = GetCollision(x, y, z, item->RoomNumber).Position.Floor; + int height6 = GetPointCollision(Vector3i(x, y, z), item->RoomNumber).GetFloorHeight(); dx = 942 * phd_sin(item->Pose.Orientation.y - ANGLE(78.75f)); dz = 942 * phd_cos(item->Pose.Orientation.y - ANGLE(78.75f)); @@ -608,7 +610,7 @@ namespace TEN::Entities::TR4 x = item->Pose.Position.x + dx; y = item->Pose.Position.y; z = item->Pose.Position.z + dz; - int height7 = GetCollision(x, y, z, item->RoomNumber).Position.Floor; + int height7 = GetPointCollision(Vector3i(x, y, z), item->RoomNumber).GetFloorHeight(); if (abs(height7 - item->Pose.Position.y) > CLICK(1) || (height6 + CLICK(2)) >= item->Pose.Position.y) @@ -715,8 +717,8 @@ namespace TEN::Entities::TR4 if (currentCreature->MonkeySwingAhead) { - probe = GetCollision(item); - if (probe.Position.Ceiling == probe.Position.Floor - CLICK(6)) + probe = GetPointCollision(*item); + if (probe.GetCeilingHeight() == probe.GetFloorHeight() - CLICK(6)) { if (item->TestMeshSwapFlags(MESHSWAPFLAGS_BADDY_EMPTY)) { @@ -950,7 +952,7 @@ namespace TEN::Entities::TR4 joint1 = 0; joint2 = 0; - probe = GetCollision(item); + probe = GetPointCollision(*item); if (laraAI.ahead && laraAI.distance < pow(682, 2) && (LaraItem->Animation.ActiveState == LS_MONKEY_IDLE || @@ -965,7 +967,7 @@ namespace TEN::Entities::TR4 } else if (item->BoxNumber != currentCreature->LOT.TargetBox && currentCreature->MonkeySwingAhead || - probe.Position.Ceiling != (probe.Position.Floor - CLICK(6))) + probe.GetCeilingHeight() != (probe.GetFloorHeight() - CLICK(6))) { item->Animation.TargetState = BADDY_STATE_MONKEY_FORWARD; } @@ -989,9 +991,9 @@ namespace TEN::Entities::TR4 if (item->BoxNumber == currentCreature->LOT.TargetBox || !currentCreature->MonkeySwingAhead) { - probe = GetCollision(item); + probe = GetPointCollision(*item); - if (probe.Position.Ceiling == probe.Position.Floor - CLICK(6)) + if (probe.GetCeilingHeight() == probe.GetFloorHeight() - CLICK(6)) item->Animation.TargetState = BADDY_STATE_MONKEY_IDLE; } diff --git a/TombEngine/Objects/TR4/Entity/tr4_beetle_swarm.cpp b/TombEngine/Objects/TR4/Entity/tr4_beetle_swarm.cpp index 9951cd441..2d901cff1 100644 --- a/TombEngine/Objects/TR4/Entity/tr4_beetle_swarm.cpp +++ b/TombEngine/Objects/TR4/Entity/tr4_beetle_swarm.cpp @@ -3,6 +3,7 @@ #include "Game/collision/collide_room.h" #include "Game/control/flipeffect.h" +#include "Game/effects/effects.h" #include "Game/items.h" #include "Game/Lara/lara.h" #include "Game/Setup.h" @@ -104,7 +105,7 @@ namespace TEN::Entities::TR4 { ZeroMemory(BeetleSwarm, NUM_BEETLES * sizeof(BeetleData)); NextBeetle = 0; - FlipEffect = -1; + FlipEffect = NO_VALUE; } } @@ -143,6 +144,8 @@ namespace TEN::Entities::TR4 if (beetle->On) { + beetle->StoreInterpolationData(); + auto oldPos = beetle->Pose.Position; beetle->Pose.Position.x += beetle->Velocity * phd_sin(beetle->Pose.Orientation.y); diff --git a/TombEngine/Objects/TR4/Entity/tr4_beetle_swarm.h b/TombEngine/Objects/TR4/Entity/tr4_beetle_swarm.h index 99f5cb88c..b4bd983e0 100644 --- a/TombEngine/Objects/TR4/Entity/tr4_beetle_swarm.h +++ b/TombEngine/Objects/TR4/Entity/tr4_beetle_swarm.h @@ -13,7 +13,13 @@ namespace TEN::Entities::TR4 byte Flags; - Matrix Transform; + Matrix Transform = Matrix::Identity; + Matrix PrevTransform = Matrix::Identity; + + void StoreInterpolationData() + { + PrevTransform = Transform; + } }; constexpr auto NUM_BEETLES = 256; diff --git a/TombEngine/Objects/TR4/Entity/tr4_crocodile.cpp b/TombEngine/Objects/TR4/Entity/tr4_crocodile.cpp index 22b35629a..8f3013e92 100644 --- a/TombEngine/Objects/TR4/Entity/tr4_crocodile.cpp +++ b/TombEngine/Objects/TR4/Entity/tr4_crocodile.cpp @@ -3,6 +3,7 @@ #include "Game/animation.h" #include "Game/collision/collide_room.h" +#include "Game/collision/Point.h" #include "Game/control/box.h" #include "Game/control/control.h" #include "Game/effects/effects.h" @@ -15,6 +16,7 @@ #include "Math/Math.h" #include "Specific/level.h" +using namespace TEN::Collision::Point; using namespace TEN::Math; namespace TEN::Entities::TR4 @@ -90,7 +92,7 @@ namespace TEN::Entities::TR4 { auto* creature = GetCreatureInfo(item); - int waterDepth = GetWaterSurface(item->Pose.Position.x, item->Pose.Position.y, item->Pose.Position.z, item->RoomNumber); + int waterDepth = GetPointCollision(*item).GetWaterSurfaceHeight(); if (waterDepth != NO_HEIGHT) { creature->LOT.Step = BLOCK(20); diff --git a/TombEngine/Objects/TR4/Entity/tr4_demigod.cpp b/TombEngine/Objects/TR4/Entity/tr4_demigod.cpp index 17c14ea39..4b4e1fe91 100644 --- a/TombEngine/Objects/TR4/Entity/tr4_demigod.cpp +++ b/TombEngine/Objects/TR4/Entity/tr4_demigod.cpp @@ -290,12 +290,12 @@ namespace TEN::Entities::TR4 spark->fadeToBlack = 24 - (GetRandomControl() & 7); spark->blendMode = BlendMode::Additive; spark->life = spark->sLife = (GetRandomControl() & 7) + 48; - spark->x = (GetRandomControl() & 0x1F) + x - 16; - spark->y = (GetRandomControl() & 0x1F) + y - 16; - spark->z = (GetRandomControl() & 0x1F) + z - 16; - spark->xVel = (byte)(GetRandomControl() + 256) * phd_sin(angle); - spark->yVel = -32 - (GetRandomControl() & 0x3F); - spark->zVel = (byte)(GetRandomControl() + 256) * phd_cos(angle); + spark->position.x = (GetRandomControl() & 0x1F) + x - 16; + spark->position.y = (GetRandomControl() & 0x1F) + y - 16; + spark->position.z = (GetRandomControl() & 0x1F) + z - 16; + spark->velocity.x = (byte)(GetRandomControl() + 256) * phd_sin(angle); + spark->velocity.y = -32 - (GetRandomControl() & 0x3F); + spark->velocity.z = (byte)(GetRandomControl() + 256) * phd_cos(angle); spark->friction = 9; if (Random::TestProbability(1 / 2.0f)) diff --git a/TombEngine/Objects/TR4/Entity/tr4_dog.cpp b/TombEngine/Objects/TR4/Entity/tr4_dog.cpp index 734f4b192..fa2c37e7f 100644 --- a/TombEngine/Objects/TR4/Entity/tr4_dog.cpp +++ b/TombEngine/Objects/TR4/Entity/tr4_dog.cpp @@ -81,7 +81,7 @@ namespace TEN::Entities::TR4 if (item->TriggerFlags) { SetAnimation(item, DOG_ANIM_AWAKEN); - item->Status -= ITEM_INVISIBLE; + item->Status = ITEM_NOT_ACTIVE; } else SetAnimation(item, DOG_ANIM_IDLE); diff --git a/TombEngine/Objects/TR4/Entity/tr4_enemy_jeep.cpp b/TombEngine/Objects/TR4/Entity/tr4_enemy_jeep.cpp index 9aba5f5cb..3930f6872 100644 --- a/TombEngine/Objects/TR4/Entity/tr4_enemy_jeep.cpp +++ b/TombEngine/Objects/TR4/Entity/tr4_enemy_jeep.cpp @@ -3,376 +3,446 @@ #include "Game/animation.h" #include "Game/collision/collide_room.h" +#include "Game/collision/Point.h" #include "Game/control/box.h" #include "Game/control/lot.h" #include "Game/control/trigger.h" #include "Game/effects/effects.h" +#include "Game/effects/smoke.h" #include "Game/effects/tomb4fx.h" #include "Game/itemdata/creature_info.h" #include "Game/items.h" #include "Game/Lara/lara.h" +#include "Game/Lara/lara_one_gun.h" #include "Game/misc.h" +#include "Game/people.h" #include "Game/Setup.h" #include "Math/Math.h" +#include "Renderer/Renderer.h" #include "Sound/sound.h" #include "Specific/level.h" +using namespace TEN::Collision::Point; +using namespace TEN::Effects::Smoke; using namespace TEN::Math; +using namespace TEN::Renderer; + +/// item.ItemFlags[1] = AI_X2 behaviour +/// item.ItemFlags[2] = Wheel rotation +/// item.ItemFlags[3] = Grenade cooldown +/// item.ItemFlags[4] = Behaviour when idle +/// item.ItemFlags[5] = Wait radius namespace TEN::Entities::TR4 { - void EnemyJeepLaunchGrenade(ItemInfo* item) + enum EnemyJeepAnim { - short grenadeItemNumber = CreateItem(); + ENEMY_JEEP_ANIM_MOVE_START = 0, + ENEMY_JEEP_ANIM_MOVE_STOP = 1, + ENEMY_JEEP_ANIM_MOVE = 2, + ENEMY_JEEP_ANIM_TURN_LEFT_START = 3, + ENEMY_JEEP_ANIM_TURN_LEFT = 4, + ENEMY_JEEP_ANIM_TURN_LEFT_END = 5, + ENEMY_JEEP_ANIM_FALL_END = 6, + ENEMY_JEEP_ANIM_FALL_2_STEPS = 7, + ENEMY_JEEP_ANIM_JUMP_2_STEP_PIT = 8, + ENEMY_JEEP_ANIM_IDLE = 9, + ENEMY_JEEP_ANIM_TURN_RIGHT_START = 10, + ENEMY_JEEP_ANIM_TURN_RIGHT = 11, + ENEMY_JEEP_ANIM_TURN_RIGHT_END = 12 + }; - if (grenadeItemNumber != NO_VALUE) + enum EnemyJeepState + { + ENEMY_JEEP_STATE_IDLE = 0, + ENEMY_JEEP_STATE_MOVE = 1, + ENEMY_JEEP_STATE_STOP = 2, + ENEMY_JEEP_STATE_TURN_LEFT = 3, + ENEMY_JEEP_STATE_TURN_RIGHT = 4, + ENEMY_JEEP_STATE_DROP_LAND = 5, + + // States to allow customization. + + ENEMY_JEEP_STATE_DROP = 6, + ENEMY_JEEP_STATE_JUMP_PIT = 7, + ENEMY_JEEP_STATE_CUSTOM_DROP = 8, + ENEMY_JEEP_STATE_CUSTOM_JUMP_PIT = 9 + }; + + enum EnemyJeepOcb + { + EJ_NO_PLAYER_VEHICLE_REQUIRED = 1 // Starts immediately instead of waiting for player to enter vehicle. + }; + + enum EnemyJeepX2Ocb + { + X2_DROP_GRENADE = 1, + X2_DROP = 2, + X2_JUMP_PIT = 3, + // Need ocb 4 + block distance + // Example: 4 + 1024 = 4 block distance + wait behaviour. + X2_WAIT_UNTIL_PLAYER_NEAR = 4, + X2_DISAPPEAR = 5, + X2_ACTIVATE_HEAVY_TRIGGER = 6, + X2_CUSTOM_DROP = 7, // Another drop step for customization. + X2_CUSTOM_JUMP_PIT = 8, // Another jump steps for customization. + }; + + constexpr auto ENEMY_JEEP_GRENADE_VELOCITY = 32.0f; + constexpr auto ENEMY_JEEP_GRENADE_TIMER = 150; + + constexpr auto ENEMY_JEEP_RIGHT_LIGHT_MESHBITS = 15; + constexpr auto ENEMY_JEEP_LEFT_LIGHT_MESHBITS = 17; + constexpr auto ENEMY_JEEP_GRENADE_COOLDOWN_TIME = 15; + constexpr auto ENEMY_JEEP_PLAYER_IS_NEAR = BLOCK(6.0f); + constexpr auto ENEMY_JEEP_NEAR_X1_NODE_DISTANCE = BLOCK(1); + constexpr auto ENEMY_JEEP_NEAR_X2_NODE_DISTANCE = BLOCK(0.3f); + constexpr auto ENEMY_JEEP_PITCH_MAX = 120.0f; + constexpr auto ENEMY_JEEP_PITCH_WHEEL_SPEED_MULTIPLIER = 12.0f; + + constexpr auto ENEMY_JEEP_WHEEL_LEFTRIGHT_TURN_MINIMUM = ANGLE(12.0f); + + constexpr auto ENEMY_JEEP_CENTER_MESH = 11; + + const auto EnemyJeepGrenadeBite = CreatureBiteInfo(Vector3(0.0f, -640.0f, -768.0f), ENEMY_JEEP_CENTER_MESH); + const auto EnemyJeepRightLightBite = CreatureBiteInfo(Vector3(200.0f, -144.0f, -768.0f), ENEMY_JEEP_CENTER_MESH); + const auto EnemyJeepLeftLightBite = CreatureBiteInfo(Vector3(-200.0f, -144.0f, -768.0f), ENEMY_JEEP_CENTER_MESH); + + static void DrawEnemyJeepLightMesh(ItemInfo& item, bool isBraking) + { + if (isBraking) { - auto* grenadeItem = &g_Level.Items[grenadeItemNumber]; + item.MeshBits.Set(ENEMY_JEEP_RIGHT_LIGHT_MESHBITS); + item.MeshBits.Set(ENEMY_JEEP_LEFT_LIGHT_MESHBITS); + } + else + { + item.MeshBits.Clear(ENEMY_JEEP_RIGHT_LIGHT_MESHBITS); + item.MeshBits.Clear(ENEMY_JEEP_LEFT_LIGHT_MESHBITS); + } + } - grenadeItem->Model.Color = Vector4(0.5f, 0.5f, 0.5f, 1.0f); - grenadeItem->ObjectNumber = ID_GRENADE; - grenadeItem->RoomNumber = item->RoomNumber; + static void SpawnEnemyJeepBrakeLights(const ItemInfo& item) + { + constexpr auto COLOR = Color(0.25f, 0.0f, 0.0f); + constexpr auto FALLOFF = 0.04f; - InitializeItem(grenadeItemNumber); + auto leftPos = GetJointPosition(item, EnemyJeepLeftLightBite).ToVector3(); + TriggerDynamicLight(leftPos, COLOR, FALLOFF); - grenadeItem->Pose.Orientation.x = item->Pose.Orientation.x; - grenadeItem->Pose.Orientation.y = item->Pose.Orientation.y - ANGLE(180.0f); - grenadeItem->Pose.Orientation.z = 0; + auto rightPos = GetJointPosition(item, EnemyJeepRightLightBite).ToVector3(); + TriggerDynamicLight(rightPos, COLOR, FALLOFF); + } - grenadeItem->Pose.Position.x = item->Pose.Position.x + BLOCK(1) * phd_sin(grenadeItem->Pose.Orientation.y); - grenadeItem->Pose.Position.y = item->Pose.Position.y - CLICK(3); - grenadeItem->Pose.Position.z = item->Pose.Position.x + BLOCK(1) * phd_cos(grenadeItem->Pose.Orientation.y); + static void SpawnEnemyJeepGrenade(ItemInfo& item) + { + int grenadeItemNumber = CreateItem(); + if (grenadeItemNumber == NO_VALUE || item.ItemFlags[3] > 0) + return; - for (int i = 0; i < 5; i++) - TriggerGunSmoke(item->Pose.Position.x, item->Pose.Position.y, item->Pose.Position.z, 0, 0, 0, 1, LaraWeaponType::GrenadeLauncher, 32); + auto& grenadeItem = g_Level.Items[grenadeItemNumber]; - if (Random::TestProbability(0.75f)) - grenadeItem->ItemFlags[0] = 1; - else - grenadeItem->ItemFlags[0] = 2; + grenadeItem.ObjectNumber = ID_GRENADE; + grenadeItem.RoomNumber = item.RoomNumber; + grenadeItem.Model.Color = Color(0.5f, 0.5f, 0.5f, 1.0f); - grenadeItem->Animation.ActiveState = grenadeItem->Pose.Orientation.x; - grenadeItem->Animation.TargetState = grenadeItem->Pose.Orientation.y; - grenadeItem->Animation.RequiredState = NO_VALUE; - grenadeItem->Animation.Velocity.z = 32; - grenadeItem->Animation.Velocity.y = -32 * phd_sin(grenadeItem->Pose.Orientation.x); - grenadeItem->HitPoints = 120; + auto grenadePos = GetJointPosition(item, EnemyJeepGrenadeBite); + auto grenadeposF = Vector3(grenadePos.x, grenadePos.y, grenadePos.z); - AddActiveItem(grenadeItemNumber); + grenadeItem.Pose.Orientation = EulerAngles(item.Pose.Orientation.x, item.Pose.Orientation.y + ANGLE(180.0f), 0); + grenadeItem.Pose.Position = grenadePos + Vector3i(BLOCK(0.1f) * phd_sin(grenadeItem.Pose.Orientation.y), 0, BLOCK(0.1f) * phd_cos(grenadeItem.Pose.Orientation.y)); + + InitializeItem(grenadeItemNumber); + + for (int i = 0; i < 9; i++) + SpawnGunSmokeParticles(grenadeposF, Vector3(0, 0, 1), item.RoomNumber, 1, LaraWeaponType::RocketLauncher, 32); + + if (GetRandomControl() & 3) + { + grenadeItem.ItemFlags[0] = (int)ProjectileType::Grenade; + } + else + { + grenadeItem.ItemFlags[0] = (int)ProjectileType::FragGrenade; + } + + grenadeItem.Animation.Velocity.z = ENEMY_JEEP_GRENADE_VELOCITY; + grenadeItem.Animation.Velocity.y = CLICK(1) * phd_sin(grenadeItem.Pose.Orientation.x); + grenadeItem.Animation.ActiveState = grenadeItem.Pose.Orientation.x; + grenadeItem.Animation.TargetState = grenadeItem.Pose.Orientation.y; + grenadeItem.Animation.RequiredState = NO_VALUE; + grenadeItem.HitPoints = ENEMY_JEEP_GRENADE_TIMER; + + item.ItemFlags[3] = ENEMY_JEEP_GRENADE_COOLDOWN_TIME; + + AddActiveItem(grenadeItemNumber); + SoundEffect(SFX_TR4_GRENADEGUN_FIRE, &item.Pose); +; } + + static void RotateTowardTarget(ItemInfo& item, const short angle, short turnRate) + { + if (abs(angle) < turnRate) + { + item.Pose.Orientation.y += angle; + } + else if (angle < 0) + { + item.Pose.Orientation.y -= turnRate; + } + else + { + item.Pose.Orientation.y += turnRate; + } + } + + // Check for X1 and X2 AI object and do a path based on X1 ocb, check behaviour with X2 like throw grenade or stop and wait for X sec... + static void DoNodePath(ItemInfo& item) + { + auto& creature = *GetCreatureInfo(&item); + + // Use it to setup the path + FindAITargetObject(item, ID_AI_X1, creature.LocationAI, false); + if (Vector3i::Distance(item.Pose.Position, creature.Enemy->Pose.Position) <= ENEMY_JEEP_NEAR_X1_NODE_DISTANCE) + creature.LocationAI++; + + // Use it to get behaviour if you arrive on X2 ai without modifing the creature.Enemy. + auto data = AITargetData{}; + data.CheckDistance = true; + data.CheckOcb = false; + data.ObjectID = ID_AI_X2; + data.Ocb = NO_VALUE; + data.CheckSameZone = false; + data.DistanceMax = ENEMY_JEEP_NEAR_X2_NODE_DISTANCE; + + if (FindAITargetObject(item, data)) + { + DrawDebugSphere(data.FoundItem.Pose.Position.ToVector3(), 128.0f, Color(1, 1, 0), RendererDebugPage::WireframeMode, true); + item.ItemFlags[1] = data.FoundItem.TriggerFlags; + } + } + + // Process the AI_X2 ocb and do any required query, like drop grenade or jump pit. + static void ProcessBehaviour(ItemInfo& item) + { + switch (item.ItemFlags[1]) + { + // Drop grenade. + case X2_DROP_GRENADE: + SpawnEnemyJeepGrenade(item); + break; + + // Drop 2 step or more. + case X2_DROP: + item.Animation.TargetState = ENEMY_JEEP_STATE_DROP; + break; + + // Jump 2 step pit. + case X2_JUMP_PIT: + item.Animation.TargetState = ENEMY_JEEP_STATE_JUMP_PIT; + break; + + // Make the entity disappear/kill itself. + case X2_DISAPPEAR: + item.Status = ITEM_INVISIBLE; + item.Flags |= IFLAG_INVISIBLE; + RemoveActiveItem(item.Index); + DisableEntityAI(item.Index); + break; + + // Make the entity start heavy trigger below it. + case X2_ACTIVATE_HEAVY_TRIGGER: + TestTriggers(item.Pose.Position.x, item.Pose.Position.y, item.Pose.Position.z, item.RoomNumber, true, 0); + break; + + case X2_CUSTOM_DROP: + item.Animation.TargetState = ENEMY_JEEP_STATE_CUSTOM_DROP; + break; + + case X2_CUSTOM_JUMP_PIT: + item.Animation.TargetState = ENEMY_JEEP_STATE_CUSTOM_JUMP_PIT; + break; + + default: + bool waitBehaviour = (item.ItemFlags[1] & X2_WAIT_UNTIL_PLAYER_NEAR) != 0; + if (waitBehaviour) + { + item.Animation.TargetState = ENEMY_JEEP_STATE_STOP; + item.ItemFlags[4] = 1; + item.ItemFlags[5] = item.ItemFlags[1] - X2_WAIT_UNTIL_PLAYER_NEAR; + } + + break; + } + + item.ItemFlags[1] = 0; // Reset X2 flags to avoid behaviour loop. + } + + static bool IsJeepIdle(int activeState) + { + return (activeState == ENEMY_JEEP_STATE_IDLE || + activeState == ENEMY_JEEP_STATE_STOP); + } + + static bool IsJeepMoving(int activeState) + { + return (activeState == ENEMY_JEEP_STATE_MOVE || + activeState == ENEMY_JEEP_STATE_TURN_LEFT || + activeState == ENEMY_JEEP_STATE_TURN_RIGHT); + } + + static bool IsJeepJumpingOrDropping(int activeState, bool onlyJump = false) + { + if (onlyJump) + { + return (activeState == ENEMY_JEEP_STATE_JUMP_PIT || + activeState == ENEMY_JEEP_STATE_CUSTOM_JUMP_PIT); + } + else + { + return (activeState == ENEMY_JEEP_STATE_JUMP_PIT || + activeState == ENEMY_JEEP_STATE_DROP || + activeState == ENEMY_JEEP_STATE_CUSTOM_DROP || + activeState == ENEMY_JEEP_STATE_CUSTOM_JUMP_PIT); } } void InitializeEnemyJeep(short itemNumber) { - auto* item = &g_Level.Items[itemNumber]; + auto& item = g_Level.Items[itemNumber]; + item.ItemFlags[2] = 0; + item.ItemFlags[3] = 0; + item.ItemFlags[4] = NO_VALUE; + item.ItemFlags[5] = NO_VALUE; - item->ItemFlags[0] = -80; - - if (g_Level.NumItems > 0) - { - for (int i = 0; i < g_Level.NumItems; i++) - { - auto* other = &g_Level.Items[i]; - - if (other == item || other->TriggerFlags != item->TriggerFlags) - continue; - - item->ItemFlags[1] = i; - other->ItemFlags[0] = -80; - other->Pose.Position.y = item->Pose.Position.y - BLOCK(1); - } - } + InitializeCreature(itemNumber); + SetAnimation(item, ENEMY_JEEP_ANIM_IDLE); + DrawEnemyJeepLightMesh(item, true); } - void EnemyJeepControl(short itemNumber) + void ControlEnemyJeep(short itemNumber) { - if (CreatureActive(itemNumber)) + if (!CreatureActive(itemNumber)) + return; + + auto& item = g_Level.Items[itemNumber]; + auto& creature = *GetCreatureInfo(&item); + auto& object = Objects[item.ObjectNumber]; + + AI_INFO ai = {}; + + if (item.ItemFlags[3] > 0) + item.ItemFlags[3]--; + item.ItemFlags[3] = std::clamp(item.ItemFlags[3], 0, ENEMY_JEEP_GRENADE_COOLDOWN_TIME); + + if (item.HitPoints <= 0) { - auto* item = &g_Level.Items[itemNumber]; - auto* creature = GetCreatureInfo(item); - int x = item->Pose.Position.x; - int y = item->Pose.Position.y; - int z = item->Pose.Position.z; + } + else + { + CreatureAIInfo(&item, &ai); - int dx = 682 * phd_sin(item->Pose.Orientation.y); - int dz = 682 * phd_cos(item->Pose.Orientation.y); - - int height1 = GetCollision(x - dz, y, z - dx, item->RoomNumber).Position.Floor; - if (abs(item->Pose.Position.y - height1) > CLICK(3)) + // Manage light mesh and dynamic light based on state. + if (IsJeepIdle(item.Animation.ActiveState)) { - item->Pose.Position.x += dz / 64; - item->Pose.Position.z += dx / 64; - item->Pose.Orientation.y += ANGLE(2.0f); - height1 = y; - } - - int height2 = GetCollision(x + dz, y, z - dx, item->RoomNumber).Position.Floor; - if (abs(item->Pose.Position.y - height2) > CLICK(3)) - { - item->Pose.Orientation.y -= ANGLE(2.0f); - item->Pose.Position.x -= dz / 64; - item->Pose.Position.z += dx / 64; - height2 = y; - } - - short zRot = phd_atan(1364, height2 - height1); - - int height3 = GetCollision(x + dx, y, z + dz, item->RoomNumber).Position.Floor; - if (abs(y - height3) > CLICK(3)) - height3 = y; - - int height4 = GetCollision(x - dx, y, z - dz, item->RoomNumber).Position.Floor; - if (abs(y - height4) > CLICK(3)) - height4 = y; - - short xRot = phd_atan(1364, height4 - height3); - - AI_INFO AI; - CreatureAIInfo(item, &AI); - - creature->Enemy = creature->AITarget; - - auto* target = creature->AITarget; - - dx = LaraItem->Pose.Position.x - item->Pose.Position.x; - dz = LaraItem->Pose.Position.z - item->Pose.Position.z; - short angle = phd_atan(dz, dx) - item->Pose.Orientation.y; - - int distance; - if (dx > BLOCK(31.25f) || dx < -BLOCK(31.25f) || - dz > BLOCK(31.25f) || dz < -BLOCK(31.25f)) - { - distance = INT_MAX; - } - else - distance = pow(dx, 2) + pow(dz, 2); - - auto pos = Vector3i::Zero; - switch (item->Animation.ActiveState) - { - case 0: - case 2: - item->ItemFlags[0] -= 128; - item->MeshBits = -98305; - - pos = GetJointPosition(item, 11, Vector3i(0, -144, -1024)); - TriggerDynamicLight(pos.x, pos.y, pos.z, 10, 64, 0, 0); - - if (item->ItemFlags[0] < 0) - item->ItemFlags[0] = 0; - - if (item->Animation.RequiredState != NO_VALUE) - item->Animation.TargetState = item->Animation.RequiredState; - else if (AI.distance > pow(BLOCK(1), 2) || Lara.Location >= item->ItemFlags[3]) - item->Animation.TargetState = 1; - - break; - - case 1: - item->ItemFlags[0] += 37; - item->MeshBits = 0xFFFDBFFF; - creature->MaxTurn = item->ItemFlags[0] / 16; - - if (item->ItemFlags[0] > 8704) - item->ItemFlags[0] = 8704; - - if (AI.angle <= ANGLE(1.4f)) - { - if (AI.angle < -ANGLE(1.4f)) - item->Animation.TargetState = 3; - } - else - item->Animation.TargetState = 4; - - break; - - case 3: - case 4: - item->Animation.TargetState = 1; - item->ItemFlags[0] += 18; - - if (item->ItemFlags[0] > 8704) - item->ItemFlags[0] = 8704; - - break; - - case 5: - if (item->ItemFlags[0] < 1184) - item->ItemFlags[0] = 1184; - - break; - - default: - break; - } - - if (height3 <= (item->Floor + CLICK(2))) - { - if (height4 > (item->Floor + CLICK(2)) && item->Animation.ActiveState != 5) - { - item->Animation.AnimNumber = Objects[item->ObjectNumber].animIndex + 8; - item->Animation.FrameNumber = GetAnimData(item).frameBase; - item->Animation.ActiveState = 5; - item->Animation.TargetState = 1; - item->ItemFlags[1] = 0; - } + DrawEnemyJeepLightMesh(item, true); + SpawnEnemyJeepBrakeLights(item); } else { - creature->LOT.RequiredBox |= 8; - - if (item->ItemFlags[1] > 0) - { - item->ItemFlags[1] -= 8; - item->Pose.Position.y += item->ItemFlags[1] / 64; - - if (item->ItemFlags[1] < 0) - creature->LOT.RequiredBox &= ~8; - } - else - { - item->ItemFlags[1] = 2 * xRot; - creature->LOT.RequiredBox |= 8u; - } - - if (creature->LOT.RequiredBox & 8) - { - item->Animation.TargetState = 1; - creature->MaxTurn = 0; - } + DrawEnemyJeepLightMesh(item, false); } - if (AI.distance < pow(BLOCK(1.5f), 2) || item->ItemFlags[3] == -2) - creature->ReachedGoal = true; - - if (creature->ReachedGoal) + // This jump/drop check need to be there or else the jeep could miss a AI_X1 + // and potentially could break, anyway it's still weird + // to see it going back to valid the missed AI. + if (IsJeepMoving(item.Animation.ActiveState) || + IsJeepJumpingOrDropping(item.Animation.ActiveState)) { - TestTriggers(target, true); + DoNodePath(item); + } + + if (IsJeepMoving(item.Animation.ActiveState)) + RotateTowardTarget(item, ai.angle, ANGLE(5.0f)); - if (Lara.Location < item->ItemFlags[3] && item->Animation.ActiveState != 2 && item->Animation.TargetState != 2) + switch (item.Animation.ActiveState) + { + case ENEMY_JEEP_STATE_IDLE: + switch (item.ItemFlags[4]) { - item->Animation.AnimNumber = Objects[item->ObjectNumber].animIndex + 1; - item->Animation.FrameNumber = GetAnimData(item).frameBase; - item->Animation.TargetState = 2; - item->Animation.ActiveState = 2; + // Wait for player to enter a vehicle. + default: + case 0: + if ((item.TriggerFlags & EJ_NO_PLAYER_VEHICLE_REQUIRED) != 0 || + Lara.Context.Vehicle != NO_VALUE) + item.Animation.TargetState = ENEMY_JEEP_STATE_MOVE; + break; - if (target->Flags & 4) + // Wait until player is near. + case 1: + if (item.ItemFlags[5] != NO_VALUE && + Vector3i::Distance(LaraItem->Pose.Position, item.Pose.Position) <= item.ItemFlags[5]) { - item->Pose = target->Pose; - - if (item->RoomNumber != target->RoomNumber) - ItemNewRoom(itemNumber, target->RoomNumber); - } - } - - if (distance > pow(BLOCK(2), 2) && - distance < pow(BLOCK(10), 2) && - !item->ItemFlags[2] && - (angle < -ANGLE(112.5f) || angle > ANGLE(112.5f))) - { - EnemyJeepLaunchGrenade(item); - item->ItemFlags[2] = 150; - } - - if (target->Flags == 62) - { - item->Status = ITEM_INVISIBLE; - RemoveActiveItem(itemNumber); - DisableEntityAI(itemNumber); - } - - if (Lara.Location >= item->ItemFlags[3] || !(target->Flags & 4)) - { - creature->ReachedGoal = false; - item->ItemFlags[3]++; - - creature->Enemy = nullptr; - AI_OBJECT* aiObject = nullptr; - - for (int i = 0; i < g_Level.AIObjects.size(); i++) - { - aiObject = &g_Level.AIObjects[i]; - - if (g_Level.AIObjects[i].triggerFlags == item->ItemFlags[3] && g_Level.AIObjects[i].roomNumber != NO_VALUE) - { - aiObject = &g_Level.AIObjects[i]; - break; - } + item.Animation.TargetState = ENEMY_JEEP_STATE_MOVE; + item.ItemFlags[4] = NO_VALUE; // Remove state. + item.ItemFlags[5] = NO_VALUE; // Remove radius. } - if (aiObject != nullptr) - { - creature->Enemy = nullptr; - target->ObjectNumber = aiObject->objectNumber; - target->RoomNumber = aiObject->roomNumber; - target->Pose.Position = aiObject->pos.Position; - target->Pose.Orientation.y = aiObject->pos.Orientation.y; - target->Flags = aiObject->flags; - target->TriggerFlags = aiObject->triggerFlags; - target->BoxNumber = aiObject->boxNumber; - - if (!(aiObject->flags & 0x20)) - { - target->Pose.Position.x += CLICK(1) * phd_sin(target->Pose.Orientation.y); - target->Pose.Position.z += CLICK(1) * phd_cos(target->Pose.Orientation.y); - } - } + break; } + + break; + + case ENEMY_JEEP_STATE_MOVE: + if (ai.angle < -ENEMY_JEEP_WHEEL_LEFTRIGHT_TURN_MINIMUM) + { + item.Animation.TargetState = ENEMY_JEEP_STATE_TURN_LEFT; + } + else if (ai.angle > ENEMY_JEEP_WHEEL_LEFTRIGHT_TURN_MINIMUM) + { + item.Animation.TargetState = ENEMY_JEEP_STATE_TURN_RIGHT; + } + + break; + + case ENEMY_JEEP_STATE_TURN_LEFT: + case ENEMY_JEEP_STATE_TURN_RIGHT: + if (abs(ai.angle) <= ENEMY_JEEP_WHEEL_LEFTRIGHT_TURN_MINIMUM) + item.Animation.TargetState = ENEMY_JEEP_STATE_MOVE; + + break; } + } - item->ItemFlags[2]--; - if (item->ItemFlags[2] < 0) - item->ItemFlags[2] = 0; + ProcessBehaviour(item); + CreatureAnimation(itemNumber, 0, 0); - if (abs(xRot - item->Pose.Orientation.x) < ANGLE(1.4f)) - item->Pose.Orientation.x = xRot; - else if (xRot < item->Pose.Orientation.x) - item->Pose.Orientation.x -= ANGLE(1.4f); - else - item->Pose.Orientation.x += ANGLE(1.4f); - - if (abs(zRot - item->Pose.Orientation.z) < ANGLE(1.4f)) - item->Pose.Orientation.z = zRot; - else if (zRot < item->Pose.Orientation.z) - item->Pose.Orientation.z -= ANGLE(1.4f); - else - item->Pose.Orientation.z += ANGLE(1.4f); - - item->ItemFlags[0] += -2 - xRot / 512; - if (item->ItemFlags[0] < 0) - item->ItemFlags[0] = 0; - - dx = item->ItemFlags[0] * phd_sin(-2 - xRot / 512); - dz = item->ItemFlags[0] * phd_cos(-2 - xRot / 512); - - item->Pose.Position.x += dx / 64; - item->Pose.Position.z += dz / 64; + if (IsJeepJumpingOrDropping(item.Animation.ActiveState, true)) + { + // Required, else the entity will go back to previous position (before the jump) + creature.LOT.IsJumping = true; + } + else + { + creature.LOT.IsJumping = false; + AlignEntityToSurface(&item, Vector2(object.radius / 3, (object.radius / 3) * 1.33f), 0.8f); + } + // Didn't use Move there because we need the move sound when jump/drop and rotating left/right. + if (!IsJeepIdle(item.Animation.ActiveState)) + { + float pitch = std::clamp(0.4f + (float)abs(item.Animation.Velocity.z) / (float)ENEMY_JEEP_PITCH_MAX, 0.6f, 1.4f); for (int i = 0; i < 4; i++) - creature->JointRotation[i] -= item->ItemFlags[0]; + creature.JointRotation[i] -= ANGLE(pitch * ENEMY_JEEP_PITCH_WHEEL_SPEED_MULTIPLIER); - if (!creature->ReachedGoal) - ClampRotation(item->Pose, AI.angle, item->ItemFlags[0] / 16); + SoundEffect(SFX_TR4_VEHICLE_JEEP_MOVING, &item.Pose, SoundEnvironment::Land, pitch, 1.5f); + } + else + { + for (int i = 0; i < 4; i++) + creature.JointRotation[i] = 0; - creature->MaxTurn = 0; - AnimateItem(item); - - auto probe = GetCollision(item); - item->Floor = probe.Position.Floor; - if (item->RoomNumber != probe.RoomNumber) - ItemNewRoom(itemNumber, probe.RoomNumber); - - if (item->Pose.Position.y < item->Floor) - item->Animation.IsAirborne = true; - else - { - item->Pose.Position.y = item->Floor; - item->Animation.IsAirborne = false; - item->Animation.Velocity.y = 0; - } - - SoundEffect(SFX_TR4_VEHICLE_JEEP_MOVING, &item->Pose, SoundEnvironment::Land, 1.0f + (float)item->ItemFlags[0] / BLOCK(8)); // TODO: Check actual sound! + SoundEffect(SFX_TR4_VEHICLE_JEEP_IDLE, &item.Pose); } } } diff --git a/TombEngine/Objects/TR4/Entity/tr4_enemy_jeep.h b/TombEngine/Objects/TR4/Entity/tr4_enemy_jeep.h index c2a2d0706..53e7ad1ae 100644 --- a/TombEngine/Objects/TR4/Entity/tr4_enemy_jeep.h +++ b/TombEngine/Objects/TR4/Entity/tr4_enemy_jeep.h @@ -1,8 +1,9 @@ #pragma once + #include "Game/items.h" namespace TEN::Entities::TR4 { void InitializeEnemyJeep(short itemNumber); - void EnemyJeepControl(short itemNumber); + void ControlEnemyJeep(short itemNumber); } diff --git a/TombEngine/Objects/TR4/Entity/tr4_horseman.cpp b/TombEngine/Objects/TR4/Entity/tr4_horseman.cpp index 2c30ba0cc..b0815a528 100644 --- a/TombEngine/Objects/TR4/Entity/tr4_horseman.cpp +++ b/TombEngine/Objects/TR4/Entity/tr4_horseman.cpp @@ -3,7 +3,7 @@ #include "Game/animation.h" #include "Game/collision/collide_room.h" -#include "Game/collision/sphere.h" +#include "Game/collision/Point.h" #include "Game/control/box.h" #include "Game/control/control.h" #include "Game/effects/debris.h" @@ -16,6 +16,7 @@ #include "Sound/sound.h" #include "Specific/level.h" +using namespace TEN::Collision::Point; using namespace TEN::Math; namespace TEN::Entities::TR4 @@ -251,14 +252,14 @@ namespace TEN::Entities::TR4 int y = horseItem->Pose.Position.y; int z = horseItem->Pose.Position.z + 341 * phd_cos(horseItem->Pose.Orientation.y); - auto probe = GetCollision(x, y, z, item->RoomNumber); - int height1 = probe.Position.Floor; + auto probe = GetPointCollision(Vector3i(x, y, z), item->RoomNumber); + int height1 = probe.GetFloorHeight(); x = horseItem->Pose.Position.x - 341 * phd_sin(horseItem->Pose.Orientation.y); y = horseItem->Pose.Position.y; z = horseItem->Pose.Position.z - 341 * phd_cos(horseItem->Pose.Orientation.y); - int height2 = GetCollision(x, y, z, probe.RoomNumber).Position.Floor; + int height2 = GetPointCollision(Vector3i(x, y, z), probe.GetRoomNumber()).GetFloorHeight(); xRot = phd_atan(682, height2 - height1); } @@ -352,7 +353,7 @@ namespace TEN::Entities::TR4 SoundEffect(SFX_TR4_HORSEMAN_TAKEHIT, &item->Pose); SoundEffect(SFX_TR4_HORSE_RICOCHET, &item->Pose); - auto pos = GetJointPosition(item, SPHERES_SPACE_WORLD, Vector3i(0, -128, 80)); + auto pos = GetJointPosition(item, 0, Vector3i(0, -128, 80)); HorsemanSparks(&pos, item->Pose.Orientation.y, 7); } else if (Random::TestProbability(1 / 8.0f)) diff --git a/TombEngine/Objects/TR4/Entity/tr4_knight_templar.cpp b/TombEngine/Objects/TR4/Entity/tr4_knight_templar.cpp index 85fa39ed0..3ed121e7b 100644 --- a/TombEngine/Objects/TR4/Entity/tr4_knight_templar.cpp +++ b/TombEngine/Objects/TR4/Entity/tr4_knight_templar.cpp @@ -186,7 +186,7 @@ namespace TEN::Entities::TR4 auto pos = GetJointPosition(item, LM_LINARM); auto& room = g_Level.Rooms[item->RoomNumber]; - auto& currentFloor = room.floor[(pos.z - room.z) / BLOCK(1) + (pos.x - room.x) / BLOCK(1) * room.zSize]; + auto& currentFloor = room.Sectors[(pos.z - room.Position.z) / BLOCK(1) + (pos.x - room.Position.x) / BLOCK(1) * room.ZSize]; if (currentFloor.Stopper) { diff --git a/TombEngine/Objects/TR4/Entity/tr4_mummy.cpp b/TombEngine/Objects/TR4/Entity/tr4_mummy.cpp index 84ffb9c7e..2a2d54033 100644 --- a/TombEngine/Objects/TR4/Entity/tr4_mummy.cpp +++ b/TombEngine/Objects/TR4/Entity/tr4_mummy.cpp @@ -81,7 +81,7 @@ namespace TEN::Entities::TR4 if (item->TriggerFlags == 2) { SetAnimation(item, MUMMY_ANIM_COLLAPSE_END); - item->Status -= ITEM_INVISIBLE; + item->Status = ITEM_NOT_ACTIVE; } else { diff --git a/TombEngine/Objects/TR4/Entity/tr4_mutant.cpp b/TombEngine/Objects/TR4/Entity/tr4_mutant.cpp index 41cc2809d..60d8cd8d8 100644 --- a/TombEngine/Objects/TR4/Entity/tr4_mutant.cpp +++ b/TombEngine/Objects/TR4/Entity/tr4_mutant.cpp @@ -2,8 +2,8 @@ #include "Objects/TR4/Entity/tr4_mutant.h" #include "Game/animation.h" -#include "Game/collision/sphere.h" #include "Game/control/control.h" +#include "Game/control/lot.h" #include "Game/effects/effects.h" #include "Game/itemdata/creature_info.h" #include "Game/items.h" @@ -287,7 +287,7 @@ namespace TEN::Entities::TR4 } else { - TargetNearestEntity(item, creature); + TargetNearestEntity(*item); } AI_INFO ai; diff --git a/TombEngine/Objects/TR4/Entity/tr4_sas.cpp b/TombEngine/Objects/TR4/Entity/tr4_sas.cpp index 97f1049ef..d8be29188 100644 --- a/TombEngine/Objects/TR4/Entity/tr4_sas.cpp +++ b/TombEngine/Objects/TR4/Entity/tr4_sas.cpp @@ -4,6 +4,7 @@ #include "Game/animation.h" #include "Game/collision/collide_item.h" #include "Game/collision/collide_room.h" +#include "Game/collision/Point.h" #include "Game/control/box.h" #include "Game/control/control.h" #include "Game/control/volume.h" @@ -23,6 +24,7 @@ #include "Specific/Input/Input.h" #include "Specific/level.h" +using namespace TEN::Collision::Point; using namespace TEN::Control::Volumes; using namespace TEN::Input; using namespace TEN::Math; @@ -655,7 +657,7 @@ namespace TEN::Entities::TR4 auto pos = GetJointPosition(&item, SasGunBite); grenadeItem->Pose.Position = pos; - auto floorHeight = GetCollision(pos.x, pos.y, pos.z, grenadeItem->RoomNumber).Position.Floor; + auto floorHeight = GetPointCollision(pos, grenadeItem->RoomNumber).GetFloorHeight(); if (floorHeight < pos.y) { grenadeItem->Pose.Position = Vector3i(item.Pose.Position.x, pos.y, item.Pose.Position.z); diff --git a/TombEngine/Objects/TR4/Entity/tr4_sentry_gun.cpp b/TombEngine/Objects/TR4/Entity/tr4_sentry_gun.cpp index 83900153e..6a54d69eb 100644 --- a/TombEngine/Objects/TR4/Entity/tr4_sentry_gun.cpp +++ b/TombEngine/Objects/TR4/Entity/tr4_sentry_gun.cpp @@ -18,124 +18,159 @@ using namespace TEN::Gui; +// NOTES: +// item.ItemFlags[0] = Gun flash duration in frame time. +// item.ItemFlags[1] = Radar angle. +// item.ItemFlags[2] = Barrel angle. +// item.ItemFlags[3] = Object ID of inventory item which deactivates sentry gun if present. + namespace TEN::Entities::TR4 { + constexpr auto SENTRYGUN_SHOT_DAMAGE = 5; + + const auto SentryGunRadarJoints = std::vector{ 3 }; + const auto SentryGunBarrelJoints = std::vector{ 4 }; + const auto SentryGunFuelCanJoints = std::vector{ 6 }; + const auto SentryGunFlashJoints = std::vector{ 8 }; + const auto SentryGunBodyJoints = std::vector{ 7, 9 }; + const auto SentryGunFlameOffset = Vector3i(-140, 0, 0); const auto SentryGunBite = CreatureBiteInfo(Vector3::Zero, 8); void InitializeSentryGun(short itemNumber) { - auto* item = &g_Level.Items[itemNumber]; + auto& item = g_Level.Items[itemNumber]; InitializeCreature(itemNumber); - item->ItemFlags[0] = 0; - item->ItemFlags[1] = 768; - item->ItemFlags[2] = 0; + + auto& flashDuration = item.ItemFlags[0]; + auto& radarAngle = item.ItemFlags[1]; + auto& barrelAngle = item.ItemFlags[2]; + auto& deactivatorItemObjctID = item.ItemFlags[3]; + + flashDuration = 0; + radarAngle = 768; + barrelAngle = 0; + + if (deactivatorItemObjctID == 0) + deactivatorItemObjctID = ID_PUZZLE_ITEM5; } - void SentryGunControl(short itemNumber) + void ControlSentryGun(short itemNumber) { if (!CreatureActive(itemNumber)) return; - auto* item = &g_Level.Items[itemNumber]; - auto* creature = GetCreatureInfo(item); + auto& item = g_Level.Items[itemNumber]; + auto* creature = GetCreatureInfo(&item); - int c = 0; + auto& flashDuration = item.ItemFlags[0]; + auto& radarAngle = item.ItemFlags[1]; + auto& barrelAngle = item.ItemFlags[2]; + auto& deactivatorItemObjectID = item.ItemFlags[3]; - if (!creature) + int headingAngle = 0; + + // TODO: Why this check? + if (creature == nullptr) return; // Was fuel can exploded? - if (item->MeshBits.Test(6)) + if (item.MeshBits.Test(SentryGunFuelCanJoints)) { - if (item->ItemFlags[0]) + if (flashDuration) { auto pos = GetJointPosition(item, SentryGunBite); - TriggerDynamicLight(pos.x, pos.y, pos.z, 4 * item->ItemFlags[0] + 12, 24, 16, 4); - item->ItemFlags[0]--; + TriggerDynamicLight(pos.x, pos.y, pos.z, 4 * flashDuration + 12, 24, 16, 4); + flashDuration--; } - if (item->ItemFlags[0] & 1) - item->MeshBits.Set(8); - else - item->MeshBits.Clear(8); - - if (item->TriggerFlags == 0) + if (flashDuration & 1) { - item->Pose.Position.y -= CLICK(2); + item.MeshBits.Set(SentryGunFlashJoints); + } + else + { + item.MeshBits.Clear(SentryGunFlashJoints); + } - AI_INFO AI; - CreatureAIInfo(item, &AI); + if (item.TriggerFlags == 0) + { + item.Pose.Position.y -= CLICK(2); - item->Pose.Position.y += CLICK(2); + AI_INFO ai; + CreatureAIInfo(&item, &ai); - int deltaAngle = AI.angle - creature->JointRotation[0]; + item.Pose.Position.y += CLICK(2); - AI.ahead = true; - if (deltaAngle <= -ANGLE(90.0f) || deltaAngle >= ANGLE(90.0f)) - AI.ahead = false; + int deltaAngle = ai.angle - creature->JointRotation[0]; - if (Targetable(item, &AI)) + ai.ahead = true; + if (deltaAngle <= ANGLE(-90.0f) || deltaAngle >= ANGLE(90.0f)) + ai.ahead = false; + + if (Targetable(&item, &ai)) { - if (AI.distance < pow(BLOCK(9), 2)) + if (ai.distance < SQUARE(BLOCK(9))) { - if (!g_Gui.IsObjectInInventory(ID_PUZZLE_ITEM5) && !item->ItemFlags[0]) + if (!g_Gui.IsObjectInInventory(deactivatorItemObjectID) && !flashDuration) { - if (AI.distance <= pow(BLOCK(2), 2)) + if (ai.distance <= SQUARE(BLOCK(2))) { - // Throw fire + // Throw fire. ThrowFire(itemNumber, 7, SentryGunFlameOffset, SentryGunFlameOffset); - c = phd_sin((GlobalCounter & 0x1F) * 2048) * 4096; + headingAngle = phd_sin((GlobalCounter & 0x1F) * 2048) * 4096; // TODO: Demagic. } else { - // Shot to Lara with bullets - c = 0; - item->ItemFlags[0] = 2; + // Shoot player. + headingAngle = 0; + flashDuration = 2; - ShotLara(item, &AI, SentryGunBite, creature->JointRotation[0], 5); - SoundEffect(SFX_TR4_AUTOGUNS, &item->Pose); + ShotLara(&item, &ai, SentryGunBite, creature->JointRotation[0], SENTRYGUN_SHOT_DAMAGE); + SoundEffect(SFX_TR4_AUTOGUNS, &item.Pose); - item->ItemFlags[2] += 256; - if (item->ItemFlags[2] > 6144) - item->ItemFlags[2] = 6144; + barrelAngle += ANGLE(1.5f); + if (barrelAngle > ANGLE(45.0f)) + barrelAngle = ANGLE(45.0f); } } - deltaAngle = c + AI.angle - creature->JointRotation[0]; + deltaAngle = (headingAngle + ai.angle) - creature->JointRotation[0]; if (deltaAngle <= ANGLE(10.0f)) { - if (deltaAngle < -ANGLE(10.0f)) - deltaAngle = -ANGLE(10.0f); + if (deltaAngle < ANGLE(-10.0f)) + deltaAngle = ANGLE(-10.0f); } else + { deltaAngle = ANGLE(10.0f); + } creature->JointRotation[0] += deltaAngle; - CreatureJoint(item, 1, -AI.xAngle); + CreatureJoint(&item, 1, -ai.xAngle); } } - // Rotating gunbarrel - item->ItemFlags[2] -= 32; - if (item->ItemFlags[2] < 0) - item->ItemFlags[2] = 0; + // Rotate barrel. + barrelAngle -= 32; + if (barrelAngle < 0) + barrelAngle = 0; - creature->JointRotation[3] += item->ItemFlags[2]; + creature->JointRotation[3] += barrelAngle; - // Rotating radar - creature->JointRotation[2] += item->ItemFlags[1]; - if (creature->JointRotation[2] > ANGLE(90.0f) || creature->JointRotation[2] < -ANGLE(90.0f)) - item->ItemFlags[1] = -item->ItemFlags[1]; + // Rotate radar. + creature->JointRotation[2] += radarAngle; + if (creature->JointRotation[2] > ANGLE(90.0f) || creature->JointRotation[2] < ANGLE(-90.0f)) + radarAngle = -radarAngle; } else { - // Stuck sentry gun - CreatureJoint(item, 0, (GetRandomControl() & 0x7FF) - 1024); - CreatureJoint(item, 1, ANGLE(45.0f)); - CreatureJoint(item, 2, (GetRandomControl() & 0x3FFF) - ANGLE(45.0f)); + // Stuck sentry gun. + CreatureJoint(&item, 0, (GetRandomControl() & 0x7FF) - 1024); + CreatureJoint(&item, 1, ANGLE(45.0f)); + CreatureJoint(&item, 2, (GetRandomControl() & 0x3FFF) - ANGLE(45.0f)); } } else @@ -144,17 +179,17 @@ namespace TEN::Entities::TR4 DisableEntityAI(itemNumber); KillItem(itemNumber); - item->Flags |= 1u; - item->Status = ITEM_DEACTIVATED; + item.Flags |= 1; + item.Status = ITEM_DEACTIVATED; - RemoveAllItemsInRoom(item->RoomNumber, ID_SMOKE_EMITTER_BLACK); + RemoveAllItemsInRoom(item.RoomNumber, ID_SMOKE_EMITTER_BLACK); - TriggerExplosionSparks(item->Pose.Position.x, item->Pose.Position.y - CLICK(3), item->Pose.Position.z, 3, -2, 0, item->RoomNumber); + TriggerExplosionSparks(item.Pose.Position.x, item.Pose.Position.y - CLICK(3), item.Pose.Position.z, 3, -2, 0, item.RoomNumber); for (int i = 0; i < 2; i++) - TriggerExplosionSparks(item->Pose.Position.x, item->Pose.Position.y - CLICK(3), item->Pose.Position.z, 3, -1, 0, item->RoomNumber); + TriggerExplosionSparks(item.Pose.Position.x, item.Pose.Position.y - CLICK(3), item.Pose.Position.z, 3, -1, 0, item.RoomNumber); - SoundEffect(SFX_TR4_EXPLOSION1, &item->Pose, SoundEnvironment::Land, 1.5f); - SoundEffect(SFX_TR4_EXPLOSION2, &item->Pose); + SoundEffect(SFX_TR4_EXPLOSION1, &item.Pose, SoundEnvironment::Land, 1.5f); + SoundEffect(SFX_TR4_EXPLOSION2, &item.Pose); } } } diff --git a/TombEngine/Objects/TR4/Entity/tr4_sentry_gun.h b/TombEngine/Objects/TR4/Entity/tr4_sentry_gun.h index 195f268eb..937553872 100644 --- a/TombEngine/Objects/TR4/Entity/tr4_sentry_gun.h +++ b/TombEngine/Objects/TR4/Entity/tr4_sentry_gun.h @@ -3,5 +3,5 @@ namespace TEN::Entities::TR4 { void InitializeSentryGun(short itemNumber); - void SentryGunControl(short itemNumber); + void ControlSentryGun(short itemNumber); } diff --git a/TombEngine/Objects/TR4/Entity/tr4_setha.cpp b/TombEngine/Objects/TR4/Entity/tr4_setha.cpp index 1a0bc3c99..45af4e5f1 100644 --- a/TombEngine/Objects/TR4/Entity/tr4_setha.cpp +++ b/TombEngine/Objects/TR4/Entity/tr4_setha.cpp @@ -5,6 +5,7 @@ #include "Game/camera.h" #include "Game/collision/collide_item.h" #include "Game/collision/collide_room.h" +#include "Game/collision/Point.h" #include "Game/control/control.h" #include "Game/effects/effects.h" #include "Game/effects/spark.h" @@ -20,6 +21,7 @@ #include "Specific/clock.h" #include "Specific/level.h" +using namespace TEN::Collision::Point; using namespace TEN::Effects::Items; using namespace TEN::Effects::Spark; using namespace TEN::Math; @@ -129,19 +131,19 @@ namespace TEN::Entities::TR4 int dx = 870 * phd_sin(item->Pose.Orientation.y); int dz = 870 * phd_cos(item->Pose.Orientation.y); - int ceiling = GetCollision(x, y, z, item->RoomNumber).Position.Ceiling; + int ceiling = GetPointCollision(Vector3i(x, y, z), item->RoomNumber).GetCeilingHeight(); x += dx; z += dz; - int height1 = GetCollision(x, y, z, item->RoomNumber).Position.Floor; + int height1 = GetPointCollision(Vector3i(x, y, z), item->RoomNumber).GetFloorHeight(); x += dx; z += dz; - int height2 = GetCollision(x, y, z, item->RoomNumber).Position.Floor; + int height2 = GetPointCollision(Vector3i(x, y, z), item->RoomNumber).GetFloorHeight(); x += dx; z += dz; - int height3 = GetCollision(x, y, z, item->RoomNumber).Position.Floor; + int height3 = GetPointCollision(Vector3i(x, y, z), item->RoomNumber).GetFloorHeight(); bool canJump = false; if ((y < (height1 - CLICK(1.5f)) || y < (height2 - CLICK(1.5f))) && @@ -152,7 +154,7 @@ namespace TEN::Entities::TR4 x = item->Pose.Position.x - dx; z = item->Pose.Position.z - dz; - int height4 = GetCollision(x, y, z, item->RoomNumber).Position.Floor; + int height4 = GetPointCollision(Vector3i(x, y, z), item->RoomNumber).GetFloorHeight(); AI_INFO AI; short angle = 0; diff --git a/TombEngine/Objects/TR4/Entity/tr4_skeleton.cpp b/TombEngine/Objects/TR4/Entity/tr4_skeleton.cpp index 0b5919070..df8eaf7d8 100644 --- a/TombEngine/Objects/TR4/Entity/tr4_skeleton.cpp +++ b/TombEngine/Objects/TR4/Entity/tr4_skeleton.cpp @@ -3,6 +3,7 @@ #include "Game/animation.h" #include "Game/collision/collide_room.h" +#include "Game/collision/Point.h" #include "Game/collision/floordata.h" #include "Game/control/box.h" #include "Game/control/lot.h" @@ -20,6 +21,7 @@ #include "Math/Math.h" #include "Specific/level.h" +using namespace TEN::Collision::Point; using namespace TEN::Math; namespace TEN::Entities::TR4 @@ -134,7 +136,7 @@ namespace TEN::Entities::TR4 case 3: SetAnimation(item, SKELETON_ANIM_STANDING_UP); - item->Status -= ITEM_INVISIBLE; + item->Status = ITEM_NOT_ACTIVE; break; } } @@ -148,7 +150,7 @@ namespace TEN::Entities::TR4 auto* fx = &EffectList[fxNumber]; fx->pos.Position.x = (byte)GetRandomControl() + item->Pose.Position.x - 128; - fx->pos.Position.y = GetCollision(item).Position.Floor; + fx->pos.Position.y = GetPointCollision(*item).GetFloorHeight(); fx->pos.Position.z = (byte)GetRandomControl() + item->Pose.Position.z - 128; fx->roomNumber = item->RoomNumber; fx->pos.Orientation.y = 2 * GetRandomControl(); @@ -223,15 +225,15 @@ namespace TEN::Entities::TR4 x += dx; z += dz; - int height1 = GetCollision(x, y, z, item->RoomNumber).Position.Floor; + int height1 = GetPointCollision(Vector3i(x, y, z), item->RoomNumber).GetFloorHeight(); x += dx; z += dz; - int height2 = GetCollision(x, y, z, item->RoomNumber).Position.Floor; + int height2 = GetPointCollision(Vector3i(x, y, z), item->RoomNumber).GetFloorHeight(); x += dx; z += dz; - int height3 = GetCollision(x, y, z, item->RoomNumber).Position.Floor; + int height3 = GetPointCollision(Vector3i(x, y, z), item->RoomNumber).GetFloorHeight(); int height = 0; bool canJump1Block = true; @@ -338,7 +340,7 @@ namespace TEN::Entities::TR4 x = item->Pose.Position.x + dx; y = item->Pose.Position.y; z = item->Pose.Position.z + dz; - int height4 = GetCollision(x, y, z, item->RoomNumber).Position.Floor; + int height4 = GetPointCollision(Vector3i(x, y, z), item->RoomNumber).GetFloorHeight(); dx = 870 * phd_sin(item->Pose.Orientation.y + ANGLE(78.75f)); dz = 870 * phd_cos(item->Pose.Orientation.y + ANGLE(78.75f)); @@ -346,7 +348,7 @@ namespace TEN::Entities::TR4 x = item->Pose.Position.x + dx; y = item->Pose.Position.y; z = item->Pose.Position.z + dz; - int height5 = GetCollision(x, y, z, item->RoomNumber).Position.Floor; + int height5 = GetPointCollision(Vector3i(x, y, z), item->RoomNumber).GetFloorHeight(); if (abs(height5 - item->Pose.Position.y) > CLICK(1)) jumpRight = false; @@ -364,7 +366,7 @@ namespace TEN::Entities::TR4 x = item->Pose.Position.x + dx; y = item->Pose.Position.y; z = item->Pose.Position.z + dz; - int height6 = GetCollision(x, y, z, item->RoomNumber).Position.Floor; + int height6 = GetPointCollision(Vector3i(x, y, z), item->RoomNumber).GetFloorHeight(); dx = 870 * phd_sin(item->Pose.Orientation.y - ANGLE(78.75f)); dz = 870 * phd_cos(item->Pose.Orientation.y - ANGLE(78.75f)); @@ -372,7 +374,7 @@ namespace TEN::Entities::TR4 x = item->Pose.Position.x + dx; y = item->Pose.Position.y; z = item->Pose.Position.z + dz; - int height7 = GetCollision(x, y, z, item->RoomNumber).Position.Floor; + int height7 = GetPointCollision(Vector3i(x, y, z), item->RoomNumber).GetFloorHeight(); if (abs(height7 - item->Pose.Position.y) > CLICK(1) || height6 + CLICK(2) >= item->Pose.Position.y) jumpLeft = false; @@ -557,7 +559,7 @@ namespace TEN::Entities::TR4 creature->LOT.IsJumping = true; - if (GetCollision(item).Position.Floor > item->Pose.Position.y + BLOCK(1)) + if (GetPointCollision(*item).GetFloorHeight() > item->Pose.Position.y + BLOCK(1)) { item->Animation.AnimNumber = Objects[ID_SKELETON].animIndex + 44; item->Animation.FrameNumber = GetAnimData(item).frameBase; @@ -641,8 +643,8 @@ namespace TEN::Entities::TR4 auto pos = GetJointPosition(item, 16); - auto floor = GetCollision(x, y, z, item->RoomNumber).Block; - if (floor->Stopper) + auto& sector = GetPointCollision(Vector3i(x, y, z), item->RoomNumber).GetSector(); + if (sector.Stopper) { for (int i = 0; i < room->mesh.size(); i++) { @@ -654,7 +656,7 @@ namespace TEN::Entities::TR4 { ShatterObject(0, staticMesh, -128, LaraItem->RoomNumber, 0); SoundEffect(SFX_TR4_SMASH_ROCK, &item->Pose); - floor->Stopper = false; + sector.Stopper = false; TestTriggers(item, true); break; } @@ -702,7 +704,7 @@ namespace TEN::Entities::TR4 case SKELETON_STATE_JUMP_FORWARD_1_BLOCK: if (item->Animation.AnimNumber == Objects[item->ObjectNumber].animIndex + 43) { - if (GetCollision(item).Position.Floor > (item->Pose.Position.y + CLICK(5))) + if (GetPointCollision(*item).GetFloorHeight() > (item->Pose.Position.y + CLICK(5))) { item->Animation.AnimNumber = Objects[item->ObjectNumber].animIndex + 44; item->Animation.FrameNumber = GetAnimData(item).frameBase; @@ -717,7 +719,7 @@ namespace TEN::Entities::TR4 case SKELETON_STATE_JUMP_CONTINUE: case SKELETON_STATE_JUMP_START: - if (GetCollision(item).Position.Floor <= item->Pose.Position.y) + if (GetPointCollision(*item).GetFloorHeight() <= item->Pose.Position.y) { if (item->Active) { @@ -750,7 +752,7 @@ namespace TEN::Entities::TR4 creature->LOT.IsJumping = false; - if (GetCollision(item).Position.Floor <= (item->Pose.Position.y + BLOCK(1))) + if (GetPointCollision(*item).GetFloorHeight() <= (item->Pose.Position.y + BLOCK(1))) { if (Random::TestProbability(1 / 32.0f)) item->Animation.TargetState = 14; diff --git a/TombEngine/Objects/TR4/Entity/tr4_sphinx.cpp b/TombEngine/Objects/TR4/Entity/tr4_sphinx.cpp index b50c351fa..e6d194981 100644 --- a/TombEngine/Objects/TR4/Entity/tr4_sphinx.cpp +++ b/TombEngine/Objects/TR4/Entity/tr4_sphinx.cpp @@ -2,6 +2,7 @@ #include "Objects/TR4/Entity/tr4_sphinx.h" #include "Game/collision/collide_room.h" +#include "Game/collision/Point.h" #include "Game/control/box.h" #include "Game/effects/debris.h" #include "Game/effects/effects.h" @@ -13,6 +14,8 @@ #include "Sound/sound.h" #include "Specific/level.h" +using namespace TEN::Collision::Point; + namespace TEN::Entities::TR4 { constexpr auto SPHINX_ATTACK_DAMAGE = 200; @@ -78,11 +81,11 @@ namespace TEN::Entities::TR4 int y = item->Pose.Position.y; int z = item->Pose.Position.z + 614 * phd_cos(item->Pose.Orientation.y); - auto probe = GetCollision(x, y, z, item->RoomNumber); + auto pointColl = GetPointCollision(Vector3i(x, y, z), item->RoomNumber); - int height1 = probe.Position.Floor; + int height1 = pointColl.GetFloorHeight(); - if (item->Animation.ActiveState == SPHINX_STATE_RUN_FORWARD && probe.Block->Stopper) + if (item->Animation.ActiveState == SPHINX_STATE_RUN_FORWARD && pointColl.GetSector().Stopper) { auto* room = &g_Level.Rooms[item->RoomNumber]; @@ -97,8 +100,6 @@ namespace TEN::Entities::TR4 ShatterObject(nullptr, mesh, -64, item->RoomNumber, 0); SoundEffect(SFX_TR4_SMASH_ROCK, &item->Pose); - probe.Block = false; - TestTriggers(x, y, z, item->RoomNumber, true); } } @@ -108,7 +109,7 @@ namespace TEN::Entities::TR4 y = item->Pose.Position.y; z = item->Pose.Position.z - 614 * phd_cos(item->Pose.Orientation.y); - int height2 = GetCollision(x, y, z, item->RoomNumber).Position.Floor; + int height2 = GetPointCollision(Vector3i(x, y, z), item->RoomNumber).GetFloorHeight(); phd_atan(1228, height2 - height1); diff --git a/TombEngine/Objects/TR4/Entity/tr4_von_croy.cpp b/TombEngine/Objects/TR4/Entity/tr4_von_croy.cpp index 41aac500f..4f7f7b93e 100644 --- a/TombEngine/Objects/TR4/Entity/tr4_von_croy.cpp +++ b/TombEngine/Objects/TR4/Entity/tr4_von_croy.cpp @@ -3,6 +3,7 @@ #include "Game/animation.h" #include "Game/collision/collide_room.h" +#include "Game/collision/Point.h" #include "Game/control/box.h" #include "Game/control/lot.h" #include "Game/effects/effects.h" @@ -16,6 +17,7 @@ #include "Sound/sound.h" #include "Specific/level.h" +using namespace TEN::Collision::Point; using namespace TEN::Math; namespace TEN::Entities::TR4 @@ -170,20 +172,20 @@ namespace TEN::Entities::TR4 x += dx; z += dz; - int height1 = GetCollision(x, item->Pose.Position.y, z, item->RoomNumber).Position.Floor; + int height1 = GetPointCollision(Vector3i(x, item->Pose.Position.y, z), item->RoomNumber).GetFloorHeight(); x += dx; z += dz; - int height2 = GetCollision(x, item->Pose.Position.y, z, item->RoomNumber).Position.Floor; + int height2 = GetPointCollision(Vector3i(x, item->Pose.Position.y, z), item->RoomNumber).GetFloorHeight(); x += dx; z += dz; - int height3 = GetCollision(x, item->Pose.Position.y, z, item->RoomNumber).Position.Floor; + int height3 = GetPointCollision(Vector3i(x, item->Pose.Position.y, z), item->RoomNumber).GetFloorHeight(); x += dx; z += dz; - auto probe = GetCollision(x, item->Pose.Position.y, z, item->RoomNumber); - int height4 = probe.Position.Floor; + auto probe = GetPointCollision(Vector3i(x, item->Pose.Position.y, z), item->RoomNumber); + int height4 = probe.GetFloorHeight(); bool canJump1block = true; if (item->BoxNumber == LaraItem->BoxNumber || @@ -419,13 +421,17 @@ namespace TEN::Entities::TR4 if (creature->MonkeySwingAhead) { - probe = GetCollision(item->Pose.Position.x, item->Pose.Position.y, item->Pose.Position.z, probe.RoomNumber); - if (probe.Position.Ceiling == (probe.Position.Floor - 1536)) + probe = GetPointCollision(item->Pose.Position, probe.GetRoomNumber()); + if (probe.GetCeilingHeight() == (probe.GetFloorHeight() - 1536)) { if (item->TestMeshSwapFlags(VonCroyKnifeSwapJoints)) + { item->Animation.TargetState = VON_CROY_STATE_TOGGLE_KNIFE; + } else + { item->Animation.TargetState = VON_CROY_STATE_START_MONKEY; + } break; } @@ -598,8 +604,8 @@ namespace TEN::Entities::TR4 item->Animation.TargetState = VON_CROY_STATE_MONKEY; else { - probe = GetCollision(item->Pose.Position.x, item->Pose.Position.y, item->Pose.Position.z, probe.RoomNumber); - if (probe.Position.Ceiling == probe.Position.Floor - 1536) + probe = GetPointCollision(item->Pose.Position, probe.GetRoomNumber()); + if (probe.GetCeilingHeight() == probe.GetFloorHeight() - 1536) item->Animation.TargetState = VON_CROY_STATE_IDLE; } @@ -612,8 +618,8 @@ namespace TEN::Entities::TR4 if (item->BoxNumber == creature->LOT.TargetBox || !creature->MonkeySwingAhead) { - probe = GetCollision(item->Pose.Position.x, item->Pose.Position.y, item->Pose.Position.z, probe.RoomNumber); - if (probe.Position.Ceiling == (probe.Position.Floor - BLOCK(1.5f))) + probe = GetPointCollision(item->Pose.Position, probe.GetRoomNumber()); + if (probe.GetCeilingHeight() == (probe.GetFloorHeight() - BLOCK(1.5f))) item->Animation.TargetState = VON_CROY_STATE_START_MONKEY; } diff --git a/TombEngine/Objects/TR4/Object/tr4_clockwork_beetle.cpp b/TombEngine/Objects/TR4/Object/tr4_clockwork_beetle.cpp index 937f41e75..3fa9113f3 100644 --- a/TombEngine/Objects/TR4/Object/tr4_clockwork_beetle.cpp +++ b/TombEngine/Objects/TR4/Object/tr4_clockwork_beetle.cpp @@ -6,8 +6,11 @@ #include "Game/animation.h" #include "Sound/sound.h" #include "Game/collision/collide_room.h" +#include "Game/collision/Point.h" #include "Game/effects/debris.h" +using namespace TEN::Collision::Point; + void ClockworkBeetleControl(short itemNumber) { auto* beetle = &g_Level.Items[itemNumber]; @@ -323,7 +326,7 @@ void UseClockworkBeetle(short flag) item->Pose.Orientation.z = 0; if (Lara.Inventory.BeetleLife) - item->ItemFlags[0] = GetCollision(item).Block->Flags.MarkBeetle; + item->ItemFlags[0] = GetPointCollision(*item).GetSector().Flags.MarkBeetle; else item->ItemFlags[0] = 0; diff --git a/TombEngine/Objects/TR4/Object/tr4_element_puzzle.cpp b/TombEngine/Objects/TR4/Object/tr4_element_puzzle.cpp index 5bb0117ff..4ef807d66 100644 --- a/TombEngine/Objects/TR4/Object/tr4_element_puzzle.cpp +++ b/TombEngine/Objects/TR4/Object/tr4_element_puzzle.cpp @@ -1,12 +1,13 @@ #include "framework.h" #include "tr4_element_puzzle.h" + #include "Specific/level.h" +#include "Game/Collision/Sphere.h" #include "Game/control/control.h" #include "Sound/sound.h" #include "Game/animation.h" #include "Game/Lara/lara.h" #include "Game/Lara/lara_helpers.h" -#include "Game/collision/sphere.h" #include "Game/effects/effects.h" #include "Game/effects/tomb4fx.h" #include "Specific/Input/Input.h" @@ -15,6 +16,7 @@ #include "Game/collision/collide_item.h" #include "Game/items.h" +using namespace TEN::Collision::Sphere; using namespace TEN::Input; using namespace TEN::Entities::Switches; @@ -149,7 +151,7 @@ namespace TEN::Entities::TR4 if (TestBoundsCollide(item, laraItem, coll->Setup.Radius)) { - if (TestCollision(item, laraItem)) + if (HandleItemSphereCollision(*item, *laraItem)) { if (coll->Setup.EnableObjectPush) ItemPushItem(item, laraItem, coll, false, 0); diff --git a/TombEngine/Objects/TR4/Object/tr4_mapper.cpp b/TombEngine/Objects/TR4/Object/tr4_mapper.cpp index 5238ef5ad..27d8f26fa 100644 --- a/TombEngine/Objects/TR4/Object/tr4_mapper.cpp +++ b/TombEngine/Objects/TR4/Object/tr4_mapper.cpp @@ -2,15 +2,17 @@ #include "Objects/TR4/Object/tr4_mapper.h" #include "Specific/level.h" #include "Game/collision/collide_room.h" +#include "Game/collision/Point.h" #include "Game/control/control.h" #include "Sound/sound.h" #include "Game/animation.h" #include "Game/Lara/lara.h" -#include "Game/collision/sphere.h" #include "Game/effects/effects.h" #include "Game/items.h" #include "Renderer/RendererEnums.h" +using namespace TEN::Collision::Point; + namespace TEN::Entities::TR4 { void InitializeMapper(short itemNumber) @@ -31,11 +33,11 @@ namespace TEN::Entities::TR4 item->MeshBits |= 2; - auto pos = GetJointPosition(item, SPHERES_SPACE_WORLD); + auto pos = GetJointPosition(item, 0); byte color = (GetRandomControl() & 0x1F) + 192; TriggerDynamicLight(pos.x, pos.y, pos.z, (GetRandomControl() & 3) + 16, color, color, 0); - int height = GetCollision(item).Position.Floor; + int height = GetPointCollision(*item).GetFloorHeight(); for (int i = 0; i < 2; i++) { diff --git a/TombEngine/Objects/TR4/Object/tr4_sarcophagus.cpp b/TombEngine/Objects/TR4/Object/tr4_sarcophagus.cpp index ba8071532..d9553cfe5 100644 --- a/TombEngine/Objects/TR4/Object/tr4_sarcophagus.cpp +++ b/TombEngine/Objects/TR4/Object/tr4_sarcophagus.cpp @@ -73,6 +73,6 @@ void SarcophagusCollision(short itemNumber, ItemInfo* laraItem, CollisionInfo* c } else { - CollectMultiplePickups(sarcItem->Index); + CollectCarriedItems(sarcItem); } } diff --git a/TombEngine/Objects/TR4/Object/tr4_senet.cpp b/TombEngine/Objects/TR4/Object/tr4_senet.cpp index dd489a715..d8dde9c1f 100644 --- a/TombEngine/Objects/TR4/Object/tr4_senet.cpp +++ b/TombEngine/Objects/TR4/Object/tr4_senet.cpp @@ -12,7 +12,9 @@ #include "Specific/level.h" #include "Game/collision/collide_item.h" #include "Game/collision/collide_room.h" +#include "Game/collision/Point.h" +using namespace TEN::Collision::Point; using namespace TEN::Input; int SenetPiecesNumber[6]; @@ -123,7 +125,7 @@ void GameSticksControl(short itemNumber) else item2->Pose.Position.z -= 128; - probedRoomNumber = GetCollision(item2->Pose.Position.x, item2->Pose.Position.y - 32, item2->Pose.Position.z, item2->RoomNumber).RoomNumber; + probedRoomNumber = GetPointCollision(Vector3i(item2->Pose.Position.x, item2->Pose.Position.y - 32, item2->Pose.Position.z), item2->RoomNumber).GetRoomNumber(); if (item2->RoomNumber != probedRoomNumber) ItemNewRoom(SenetPiecesNumber[ActivePiece], probedRoomNumber); @@ -179,7 +181,7 @@ void GameSticksControl(short itemNumber) item2->Pose.Position.x = SenetTargetX - BLOCK(4 * number) + BLOCK(7); item2->Pose.Position.z = SenetTargetZ + BLOCK(i % 3); - probedRoomNumber = GetCollision(item2->Pose.Position.x, item2->Pose.Position.y - 32, item2->Pose.Position.z, item2->RoomNumber).RoomNumber; + probedRoomNumber = GetPointCollision(Vector3i(item2->Pose.Position.x, item2->Pose.Position.y - 32, item2->Pose.Position.z), item2->RoomNumber).GetRoomNumber(); if (item2->RoomNumber != probedRoomNumber) ItemNewRoom(SenetPiecesNumber[i], probedRoomNumber); diff --git a/TombEngine/Objects/TR4/Trap/SpikyCeiling.cpp b/TombEngine/Objects/TR4/Trap/SpikyCeiling.cpp index e685b46bb..6db8d0843 100644 --- a/TombEngine/Objects/TR4/Trap/SpikyCeiling.cpp +++ b/TombEngine/Objects/TR4/Trap/SpikyCeiling.cpp @@ -4,7 +4,8 @@ #include "Game/animation.h" #include "Game/collision/collide_item.h" #include "Game/collision/collide_room.h" -#include "Game/collision/sphere.h" +#include "Game/collision/Point.h" +#include "Game/collision/Sphere.h" #include "Game/control/control.h" #include "Game/effects/effects.h" #include "Game/items.h" @@ -12,6 +13,8 @@ #include "Sound/sound.h" #include "Specific/level.h" +using namespace TEN::Collision::Sphere; + namespace TEN::Entities::Traps { // NOTES: @@ -39,9 +42,9 @@ namespace TEN::Entities::Traps int lowerCeilBound = bounds.Y1; // Get point collision. - auto pointColl = GetCollision(&item); - int relFloorHeight = pointColl.Position.Floor - item.Pose.Position.y; - int relCeilHeight = pointColl.Position.Ceiling - item.Pose.Position.y; + auto pointColl = GetPointCollision(item); + int relFloorHeight = pointColl.GetFloorHeight() - item.Pose.Position.y; + int relCeilHeight = pointColl.GetCeilingHeight() - item.Pose.Position.y; int verticalVel = item.ItemFlags[0]; @@ -57,8 +60,8 @@ namespace TEN::Entities::Traps { item.Pose.Position.y += verticalVel; - if (pointColl.RoomNumber != item.RoomNumber) - ItemNewRoom(itemNumber, pointColl.RoomNumber); + if (pointColl.GetRoomNumber() != item.RoomNumber) + ItemNewRoom(itemNumber, pointColl.GetRoomNumber()); SoundEffect(SFX_TR4_ROLLING_BALL, &item.Pose); } @@ -77,7 +80,7 @@ namespace TEN::Entities::Traps if (!TestBoundsCollide(&item, playerItem, coll->Setup.Radius)) return; - TestCollision(&item, playerItem); + HandleItemSphereCollision(item, *playerItem); } else if (item.Status != ITEM_INVISIBLE) { diff --git a/TombEngine/Objects/TR4/Trap/SpikyWall.cpp b/TombEngine/Objects/TR4/Trap/SpikyWall.cpp index 25977321d..225b5efe1 100644 --- a/TombEngine/Objects/TR4/Trap/SpikyWall.cpp +++ b/TombEngine/Objects/TR4/Trap/SpikyWall.cpp @@ -3,7 +3,8 @@ #include "Game/collision/collide_item.h" #include "Game/collision/collide_room.h" -#include "Game/collision/sphere.h" +#include "Game/collision/Point.h" +#include "Game/collision/Sphere.h" #include "Game/control/control.h" #include "Game/effects/debris.h" #include "Game/effects/effects.h" @@ -14,6 +15,8 @@ #include "Sound/sound.h" #include "Specific/level.h" +using namespace TEN::Collision::Point; +using namespace TEN::Collision::Sphere; using namespace TEN::Math; // NOTES: @@ -47,13 +50,13 @@ namespace TEN::Entities::Traps int forwardVel = item.ItemFlags[0]; auto bounds = GameBoundingBox(&item); - auto pointColl0 = GetCollision(&item, item.Pose.Orientation.y, (forwardVel >= 0) ? bounds.Z2 : bounds.Z1, bounds.Y2); - auto pointColl1 = GetCollision(&item, item.Pose.Orientation.y, (forwardVel >= 0) ? bounds.Z2 : bounds.Z1, bounds.Y2, (bounds.X2 - bounds.X1) / 2); + auto pointColl0 = GetPointCollision(item, item.Pose.Orientation.y, (forwardVel >= 0) ? bounds.Z2 : bounds.Z1, bounds.Y2); + auto pointColl1 = GetPointCollision(item, item.Pose.Orientation.y, (forwardVel >= 0) ? bounds.Z2 : bounds.Z1, bounds.Y2, (bounds.X2 - bounds.X1) / 2); auto collObjects = GetCollidedObjects(item, true, true); if (!collObjects.IsEmpty()) { - for (auto* itemPtr : collObjects.ItemPtrs) + for (auto* itemPtr : collObjects.Items) { const auto& object = Objects[itemPtr->ObjectNumber]; @@ -76,18 +79,18 @@ namespace TEN::Entities::Traps // Stop moving. if (!item.TriggerFlags || - pointColl0.Block->IsWall(item.Pose.Position.x, item.Pose.Position.z) || - pointColl0.Block->Stopper || - pointColl1.Block->IsWall(item.Pose.Position.x, item.Pose.Position.z) || - pointColl1.Block->Stopper) + pointColl0.GetSector().IsWall(item.Pose.Position.x, item.Pose.Position.z) || + pointColl0.GetSector().Stopper || + pointColl1.GetSector().IsWall(item.Pose.Position.x, item.Pose.Position.z) || + pointColl1.GetSector().Stopper) { auto& room = g_Level.Rooms[item.RoomNumber]; for (auto& mesh : room.mesh) { - if ((abs(pointColl0.Coordinates.x - mesh.pos.Position.x) < BLOCK(1) && - abs(pointColl0.Coordinates.z - mesh.pos.Position.z) < BLOCK(1)) || - abs(pointColl1.Coordinates.x - mesh.pos.Position.x) < BLOCK(1) && - abs(pointColl1.Coordinates.z - mesh.pos.Position.z) < BLOCK(1) && + if ((abs(pointColl0.GetPosition().x - mesh.pos.Position.x) < BLOCK(1) && + abs(pointColl0.GetPosition().z - mesh.pos.Position.z) < BLOCK(1)) || + abs(pointColl1.GetPosition().x - mesh.pos.Position.x) < BLOCK(1) && + abs(pointColl1.GetPosition().z - mesh.pos.Position.z) < BLOCK(1) && StaticObjects[mesh.staticNumber].shatterType != ShatterType::None) { if (mesh.HitPoints != 0) @@ -109,8 +112,8 @@ namespace TEN::Entities::Traps item.Pose.Position = Geometry::TranslatePoint(item.Pose.Position, item.Pose.Orientation.y, forwardVel); item.Status = ITEM_ACTIVE; - if (pointColl0.RoomNumber != item.RoomNumber) - ItemNewRoom(itemNumber, pointColl0.RoomNumber); + if (pointColl0.GetRoomNumber() != item.RoomNumber) + ItemNewRoom(itemNumber, pointColl0.GetRoomNumber()); SoundEffect(SFX_TR4_ROLLING_BALL, &item.Pose); } @@ -129,7 +132,7 @@ namespace TEN::Entities::Traps if (!TestBoundsCollide(&item, playerItem, coll->Setup.Radius)) return; - TestCollision(&item, playerItem); + HandleItemSphereCollision(item, *playerItem); } else if (item.Status != ITEM_INVISIBLE) { diff --git a/TombEngine/Objects/TR4/Trap/SquishyBlock.cpp b/TombEngine/Objects/TR4/Trap/SquishyBlock.cpp index 3423d3890..0a025cd1a 100644 --- a/TombEngine/Objects/TR4/Trap/SquishyBlock.cpp +++ b/TombEngine/Objects/TR4/Trap/SquishyBlock.cpp @@ -5,7 +5,8 @@ #include "Game/camera.h" #include "Game/collision/collide_item.h" #include "Game/collision/collide_room.h" -#include "Game/collision/sphere.h" +#include "Game/collision/Point.h" +#include "Game/collision/Sphere.h" #include "Game/control/control.h" #include "Game/effects/effects.h" #include "Game/items.h" @@ -15,6 +16,9 @@ #include "Sound/sound.h" #include "Specific/level.h" +using namespace TEN::Collision::Point; +using namespace TEN::Collision::Sphere; + // NOTES: // item.ItemFlags[0]: use dynamic motion. // item.ItemFlags[1]: ?? @@ -94,12 +98,12 @@ namespace TEN::Entities::Traps { auto forwardDir = EulerAngles(0, item.Pose.Orientation.y + headingAngle, 0).ToDirection(); - auto pointColl = GetCollision(item.Pose.Position, item.RoomNumber, forwardDir, BLOCK(0.5f)); + auto pointColl = GetPointCollision(item.Pose.Position, item.RoomNumber, forwardDir, BLOCK(0.5f)); - if (pointColl.RoomNumber != item.RoomNumber) - ItemNewRoom(itemNumber, pointColl.RoomNumber); + if (pointColl.GetRoomNumber() != item.RoomNumber) + ItemNewRoom(itemNumber, pointColl.GetRoomNumber()); - if (!IsNextSectorValid(item, forwardDir, BLOCK(0.5f))) + if (!IsNextSectorValid(item, forwardDir, BLOCK(0.5f), true)) { switch (headingAngle) { @@ -172,7 +176,7 @@ namespace TEN::Entities::Traps if (!TestBoundsCollide(&item, playerItem, coll->Setup.Radius)) return; - if (!TestCollision(&item, playerItem)) + if (!HandleItemSphereCollision(item, *playerItem)) return; if (!ItemPushItem(&item, playerItem, coll, false, 1)) @@ -198,7 +202,7 @@ namespace TEN::Entities::Traps if (!TestBoundsCollide(&item, playerItem, coll->Setup.Radius)) return; - if (!TestCollision(&item, playerItem)) + if (!HandleItemSphereCollision(item, *playerItem)) return; if ((item.Animation.FrameNumber - GetAnimData(item).frameBase) <= FALLING_BLOCK_IMPACT_FRAME) diff --git a/TombEngine/Objects/TR4/Trap/tr4_birdblade.cpp b/TombEngine/Objects/TR4/Trap/tr4_birdblade.cpp index cf9626f90..e897279ce 100644 --- a/TombEngine/Objects/TR4/Trap/tr4_birdblade.cpp +++ b/TombEngine/Objects/TR4/Trap/tr4_birdblade.cpp @@ -1,33 +1,45 @@ #include "framework.h" -#include "tr4_birdblade.h" -#include "Specific/level.h" -#include "Game/control/control.h" +#include "Objects/TR4/Trap/tr4_birdblade.h" + #include "Game/animation.h" +#include "Game/control/control.h" #include "Game/items.h" +#include "Specific/level.h" -namespace TEN::Entities::TR4 +namespace TEN::Entities::Traps { - void BirdBladeControl(short itemNumber) + void InitializeBirdBlade(short itemNumber) { - auto* item = &g_Level.Items[itemNumber]; + auto& item = g_Level.Items[itemNumber]; - item->ItemFlags[3] = 100; + item.ItemFlags[4] = 1; + } - if (!TriggerActive(item)) + void ControlBirdBlade(short itemNumber) + { + auto& item = g_Level.Items[itemNumber]; + + item.ItemFlags[3] = 100; + + if (!TriggerActive(&item)) { - item->Animation.FrameNumber = GetAnimData(item).frameBase; - *((int*)&item->ItemFlags[0]) = 0; + item.Animation.FrameNumber = GetAnimData(item).frameBase; + *((int*)&item.ItemFlags[0]) = 0; } else { - int frameNumber = item->Animation.FrameNumber - GetAnimData(item).frameBase; + int frameNumber = item.Animation.FrameNumber - GetAnimData(item).frameBase; if (frameNumber <= 14 || frameNumber >= 31) - *((int*)&item->ItemFlags[0]) = 0; + { + *((int*)&item.ItemFlags[0]) = 0; + } else - *((int*)&item->ItemFlags[0]) = 6; + { + *((int*)&item.ItemFlags[0]) = 6; + } - AnimateItem(item); + AnimateItem(&item); } } } diff --git a/TombEngine/Objects/TR4/Trap/tr4_birdblade.h b/TombEngine/Objects/TR4/Trap/tr4_birdblade.h index 31ec3dce3..397834fdb 100644 --- a/TombEngine/Objects/TR4/Trap/tr4_birdblade.h +++ b/TombEngine/Objects/TR4/Trap/tr4_birdblade.h @@ -1,6 +1,7 @@ #pragma once -namespace TEN::Entities::TR4 +namespace TEN::Entities::Traps { - void BirdBladeControl(short itemNumber); + void InitializeBirdBlade(short itemNumber); + void ControlBirdBlade(short itemNumber); } diff --git a/TombEngine/Objects/TR4/Trap/tr4_blade.cpp b/TombEngine/Objects/TR4/Trap/tr4_blade.cpp index cb6369593..ed011bb4d 100644 --- a/TombEngine/Objects/TR4/Trap/tr4_blade.cpp +++ b/TombEngine/Objects/TR4/Trap/tr4_blade.cpp @@ -1,62 +1,49 @@ #include "framework.h" -#include "tr4_blade.h" -#include "Specific/level.h" -#include "Game/collision/collide_room.h" +#include "Objects/TR4/Trap/tr4_blade.h" + #include "Game/collision/collide_item.h" -#include "Game/Lara/lara.h" +#include "Game/collision/collide_room.h" #include "Game/control/control.h" #include "Game/effects/effects.h" #include "Game/items.h" +#include "Game/Lara/lara.h" +#include "Specific/level.h" -namespace TEN::Entities::TR4 +namespace TEN::Entities::Traps { - void BladeCollision(short itemNumber, ItemInfo* laraItem, CollisionInfo* coll) + void CollideBlade(short itemNumber, ItemInfo* playerItem, CollisionInfo* coll) { - auto* bladeItem = &g_Level.Items[itemNumber]; + auto& bladeItem = g_Level.Items[itemNumber]; - if (bladeItem->Status == ITEM_INVISIBLE) + if (bladeItem.Status == ITEM_INVISIBLE) return; - if (bladeItem->ItemFlags[3]) + if (!bladeItem.ItemFlags[3]) + return; + + if (!TestBoundsCollide(&bladeItem, playerItem, coll->Setup.Radius)) + return; + + auto prevPos = playerItem->Pose.Position; + + if (!ItemPushItem(&bladeItem, playerItem, coll, true, 1)) + return; + + DoDamage(playerItem, bladeItem.ItemFlags[3]); + + auto deltaPos = prevPos - playerItem->Pose.Position; + if (deltaPos != Vector3i::Zero && TriggerActive(&bladeItem)) { - if (TestBoundsCollide(bladeItem, laraItem, coll->Setup.Radius)) - { - int oldX = laraItem->Pose.Position.x; - int oldY = laraItem->Pose.Position.y; - int oldZ = laraItem->Pose.Position.z; - - int dx = 0; - int dy = 0; - int dz = 0; - - if (ItemPushItem(bladeItem, laraItem, coll, true, 1)) - { - DoDamage(laraItem, bladeItem->ItemFlags[3]); - - dx = oldX - laraItem->Pose.Position.x; - dy = oldY - laraItem->Pose.Position.y; - dz = oldZ - laraItem->Pose.Position.z; - - if ((dx || dy || dz) && TriggerActive(bladeItem)) - { - DoBloodSplat( - (GetRandomControl() & 0x3F) + laraItem->Pose.Position.x - 32, - laraItem->Pose.Position.y - (GetRandomControl() & 0x1FF) - 256, - (GetRandomControl() & 0x3F) + laraItem->Pose.Position.z - 32, - (GetRandomControl() & 3) + (bladeItem->ItemFlags[3] / 32) + 2, - 2 * GetRandomControl(), - laraItem->RoomNumber - ); - } - - if (!coll->Setup.EnableObjectPush) - { - laraItem->Pose.Position.x += dx; - laraItem->Pose.Position.y += dy; - laraItem->Pose.Position.z += dz; - } - } - } + DoBloodSplat( + (GetRandomControl() & 0x3F) + playerItem->Pose.Position.x - 32, + playerItem->Pose.Position.y - (GetRandomControl() & 0x1FF) - 256, + (GetRandomControl() & 0x3F) + playerItem->Pose.Position.z - 32, + (GetRandomControl() & 3) + (bladeItem.ItemFlags[3] / 32) + 2, + Random::GenerateAngle(), + playerItem->RoomNumber); } + + if (!coll->Setup.EnableObjectPush) + playerItem->Pose.Position += deltaPos; } } diff --git a/TombEngine/Objects/TR4/Trap/tr4_blade.h b/TombEngine/Objects/TR4/Trap/tr4_blade.h index 799b20831..616ea4514 100644 --- a/TombEngine/Objects/TR4/Trap/tr4_blade.h +++ b/TombEngine/Objects/TR4/Trap/tr4_blade.h @@ -3,7 +3,7 @@ struct ItemInfo; struct CollisionInfo; -namespace TEN::Entities::TR4 +namespace TEN::Entities::Traps { - void BladeCollision(short itemNumber, ItemInfo* laraItem, CollisionInfo* coll); + void CollideBlade(short itemNumber, ItemInfo* playerItem, CollisionInfo* coll); } diff --git a/TombEngine/Objects/TR4/Trap/tr4_catwalkblade.cpp b/TombEngine/Objects/TR4/Trap/tr4_catwalkblade.cpp index 887ccb796..32772265c 100644 --- a/TombEngine/Objects/TR4/Trap/tr4_catwalkblade.cpp +++ b/TombEngine/Objects/TR4/Trap/tr4_catwalkblade.cpp @@ -1,28 +1,35 @@ #include "framework.h" -#include "tr4_catwalkblade.h" -#include "Specific/level.h" -#include "Game/control/control.h" +#include "Objects/TR4/Trap/tr4_catwalkblade.h" + #include "Game/animation.h" +#include "Game/control/control.h" #include "Game/items.h" +#include "Specific/level.h" -namespace TEN::Entities::TR4 +namespace TEN::Entities::Traps { - void CatwalkBladeControl(short itemNumber) + void ControlCatwalkBlade(short itemNumber) { - auto* item = &g_Level.Items[itemNumber]; + auto& item = g_Level.Items[itemNumber]; - if (!TriggerActive(item)) - item->Animation.FrameNumber = GetAnimData(item).frameBase; + if (!TriggerActive(&item)) + { + item.Animation.FrameNumber = GetAnimData(item).frameBase; + } else { - int frameNumber = item->Animation.FrameNumber - GetAnimData(item).frameBase; + int frameNumber = item.Animation.FrameNumber - GetAnimData(item).frameBase; - if (item->Animation.FrameNumber == GetAnimData(item).frameEnd || frameNumber < 38) - item->ItemFlags[3] = 0; + if (item.Animation.FrameNumber == GetAnimData(item).frameEnd || frameNumber < 38) + { + item.ItemFlags[3] = 0; + } else - item->ItemFlags[3] = 100; + { + item.ItemFlags[3] = 100; + } - AnimateItem(item); + AnimateItem(&item); } } } diff --git a/TombEngine/Objects/TR4/Trap/tr4_catwalkblade.h b/TombEngine/Objects/TR4/Trap/tr4_catwalkblade.h index c95f05ff8..b470b7005 100644 --- a/TombEngine/Objects/TR4/Trap/tr4_catwalkblade.h +++ b/TombEngine/Objects/TR4/Trap/tr4_catwalkblade.h @@ -1,6 +1,6 @@ #pragma once -namespace TEN::Entities::TR4 +namespace TEN::Entities::Traps { - void CatwalkBladeControl(short itemNumber); + void ControlCatwalkBlade(short itemNumber); } diff --git a/TombEngine/Objects/TR4/Trap/tr4_chain.cpp b/TombEngine/Objects/TR4/Trap/tr4_chain.cpp index c81b77f49..e58a2c0a3 100644 --- a/TombEngine/Objects/TR4/Trap/tr4_chain.cpp +++ b/TombEngine/Objects/TR4/Trap/tr4_chain.cpp @@ -1,40 +1,41 @@ #include "framework.h" -#include "tr4_chain.h" -#include "Specific/level.h" -#include "Game/control/control.h" +#include "Objects/TR4/Trap/tr4_chain.h" + #include "Game/animation.h" +#include "Game/control/control.h" #include "Game/items.h" +#include "Specific/level.h" -namespace TEN::Entities::TR4 +namespace TEN::Entities::Traps { - void ChainControl(short itemNumber) + void ControlChain(short itemNumber) { - auto* item = &g_Level.Items[itemNumber]; + auto& item = g_Level.Items[itemNumber]; - if (item->TriggerFlags) + if (item.TriggerFlags) { - item->ItemFlags[2] = 1; - item->ItemFlags[3] = 75; + item.ItemFlags[2] = 1; + item.ItemFlags[3] = 75; - if (TriggerActive(item)) + if (TriggerActive(&item)) { - *((int*)&item->ItemFlags[0]) = 0x787E; - AnimateItem(item); + *(int*)&item.ItemFlags[0] = 0x787E; + AnimateItem(&item); return; } } else { - item->ItemFlags[3] = 25; + item.ItemFlags[3] = 25; - if (TriggerActive(item)) + if (TriggerActive(&item)) { - *((int*)&item->ItemFlags[0]) = 0x780; - AnimateItem(item); + *(int*)&item.ItemFlags[0] = 0x780; + AnimateItem(&item); return; } } - *((int*)&item->ItemFlags[0]) = 0; + *(int*)&item.ItemFlags[0] = 0; } } diff --git a/TombEngine/Objects/TR4/Trap/tr4_chain.h b/TombEngine/Objects/TR4/Trap/tr4_chain.h index de97e60bd..6a09dd1a6 100644 --- a/TombEngine/Objects/TR4/Trap/tr4_chain.h +++ b/TombEngine/Objects/TR4/Trap/tr4_chain.h @@ -1,9 +1,6 @@ #pragma once -struct ItemInfo; -struct CollisionInfo; - -namespace TEN::Entities::TR4 +namespace TEN::Entities::Traps { - void ChainControl(short itemNumber); + void ControlChain(short itemNumber); } diff --git a/TombEngine/Objects/TR4/Trap/tr4_cog.cpp b/TombEngine/Objects/TR4/Trap/tr4_cog.cpp index e334eb854..7a38d104a 100644 --- a/TombEngine/Objects/TR4/Trap/tr4_cog.cpp +++ b/TombEngine/Objects/TR4/Trap/tr4_cog.cpp @@ -1,64 +1,58 @@ #include "framework.h" -#include "tr4_cog.h" -#include "Specific/level.h" -#include "Game/control/control.h" -#include "Sound/sound.h" -#include "Game/collision/collide_room.h" -#include "Game/collision/collide_item.h" -#include "Game/effects/effects.h" -#include "Game/Lara/lara.h" +#include "Objects/TR4/Trap/tr4_cog.h" + #include "Game/animation.h" +#include "Game/collision/collide_item.h" +#include "Game/collision/collide_room.h" +#include "Game/control/control.h" +#include "Game/effects/effects.h" #include "Game/items.h" +#include "Game/Lara/lara.h" +#include "Sound/sound.h" +#include "Specific/level.h" -namespace TEN::Entities::TR4 +namespace TEN::Entities::Traps { - void CogControl(short itemNumber) - { - auto* item = &g_Level.Items[itemNumber]; + void ControlCog(short itemNumber) + { + auto& item = g_Level.Items[itemNumber]; - if (TriggerActive(item)) - { - item->Status = ITEM_ACTIVE; - AnimateItem(item); + if (TriggerActive(&item)) + { + item.Status = ITEM_ACTIVE; + AnimateItem(&item); + } + else if (item.TriggerFlags == 2) + { + item.Status = ITEM_INVISIBLE; + } + } - if (item->TriggerFlags == 666) - { - auto pos = GetJointPosition(item, 0); - SoundEffect(SFX_TR4_LIBRARY_COG_LOOP, (Pose*)&pos); - - //Shouldnt this be TR4_LIBRARY_COG_LOOP? Changed. Rollback if incorrect. Stranger1992 06/06/22 + void CollideCog(short itemNumber, ItemInfo* playerItem, CollisionInfo* coll) + { + auto& cogItem = g_Level.Items[itemNumber]; + + if (cogItem.Status == ITEM_INVISIBLE) + return; - if (item->Animation.FrameNumber == GetAnimData(item).frameEnd) - item->Flags &= 0xC1; - } - } - else if (item->TriggerFlags == 2) - item->Status |= ITEM_INVISIBLE; - } + if (!TestBoundsCollide(&cogItem, playerItem, coll->Setup.Radius)) + return; - void CogCollision(short itemNumber, ItemInfo* laraItem, CollisionInfo* coll) - { - auto* cogItem = &g_Level.Items[itemNumber]; - - if (cogItem->Status != ITEM_INVISIBLE) - { - if (TestBoundsCollide(cogItem, laraItem, coll->Setup.Radius)) - { - if (TriggerActive(cogItem)) - { - DoBloodSplat( - (GetRandomControl() & 0x3F) + laraItem->Pose.Position.x - 32, - (GetRandomControl() & 0x1F) + cogItem->Pose.Position.y - 16, - (GetRandomControl() & 0x3F) + laraItem->Pose.Position.z - 32, - (GetRandomControl() & 3) + 2, - 2 * GetRandomControl(), - laraItem->RoomNumber); + if (TriggerActive(&cogItem)) + { + DoBloodSplat( + (GetRandomControl() & 0x3F) + playerItem->Pose.Position.x - 32, + (GetRandomControl() & 0x1F) + cogItem.Pose.Position.y - 16, + (GetRandomControl() & 0x3F) + playerItem->Pose.Position.z - 32, + (GetRandomControl() & 3) + 2, + Random::GenerateAngle(), + playerItem->RoomNumber); - DoDamage(laraItem, 10); - } - else if (coll->Setup.EnableObjectPush) - ItemPushItem(cogItem, laraItem, coll, false, 0); - } - } - } + DoDamage(playerItem, 10); + } + else if (coll->Setup.EnableObjectPush) + { + ItemPushItem(&cogItem, playerItem, coll, false, 0); + } + } } diff --git a/TombEngine/Objects/TR4/Trap/tr4_cog.h b/TombEngine/Objects/TR4/Trap/tr4_cog.h index db7829b22..22a4c8bb9 100644 --- a/TombEngine/Objects/TR4/Trap/tr4_cog.h +++ b/TombEngine/Objects/TR4/Trap/tr4_cog.h @@ -3,8 +3,8 @@ struct ItemInfo; struct CollisionInfo; -namespace TEN::Entities::TR4 +namespace TEN::Entities::Traps { - void CogControl(short itemNumber); - void CogCollision(short itemNumber, ItemInfo* laraItem, CollisionInfo* coll); + void ControlCog(short itemNumber); + void CollideCog(short itemNumber, ItemInfo* playerItem, CollisionInfo* coll); } diff --git a/TombEngine/Objects/TR4/Trap/tr4_fourblades.cpp b/TombEngine/Objects/TR4/Trap/tr4_fourblades.cpp index bae0f4d45..0c5d0d0d0 100644 --- a/TombEngine/Objects/TR4/Trap/tr4_fourblades.cpp +++ b/TombEngine/Objects/TR4/Trap/tr4_fourblades.cpp @@ -1,45 +1,64 @@ #include "framework.h" -#include "tr4_fourblades.h" -#include "Specific/level.h" -#include "Game/control/control.h" +#include "Objects/TR4/Trap/tr4_fourblades.h" + #include "Game/animation.h" +#include "Game/control/control.h" #include "Game/items.h" +#include "Specific/level.h" -namespace TEN::Entities::TR4 +// NOTES: +// item.ItemFlags[0] = Damage joints. +// item.ItemFlags[3] = Damage. +// item.ItemFlags[4] = Push player (bool). + +namespace TEN::Entities::Traps { - void FourBladesControl(short itemNumber) - { - auto* item = &g_Level.Items[itemNumber]; + constexpr auto FOUR_BLADES_EMERGE_HARM_DAMAGE = 20; + constexpr auto FOUR_BLADES_IDLE_HARM_DAMAGE = 200; - if (!TriggerActive(item)) + constexpr auto FOUR_BLADES_HARM_JOINTS = MESH_BITS(1) | MESH_BITS(2) | MESH_BITS(3) | MESH_BITS(4); + + void InitializeFourBlades(short itemNumber) + { + auto& item = g_Level.Items[itemNumber]; + + item.ItemFlags[4] = 1; + } + + void ControlFourBlades(short itemNumber) + { + auto& item = g_Level.Items[itemNumber]; + + if (!TriggerActive(&item)) { - item->Animation.FrameNumber = GetAnimData(item).frameBase; - *((int*)&item->ItemFlags[0]) = 0; + item.Animation.FrameNumber = GetAnimData(item).frameBase; + item.ItemFlags[0] = 0; } else { - int frameNumber = item->Animation.FrameNumber - GetAnimData(item).frameBase; + int frameNumber = item.Animation.FrameNumber - GetAnimData(item).frameBase; if (frameNumber <= 5 || frameNumber >= 58 || frameNumber >= 8 && frameNumber <= 54) { - *((int*)&item->ItemFlags[0]) = 0; + item.ItemFlags[0] = 0; + item.ItemFlags[3] = 0; } else { + item.ItemFlags[0] = FOUR_BLADES_HARM_JOINTS; + if (frameNumber >= 6 && frameNumber <= 7) { - item->ItemFlags[3] = 20; - *((int*)&item->ItemFlags[0]) = 30; + item.ItemFlags[3] = FOUR_BLADES_EMERGE_HARM_DAMAGE; } else if (frameNumber >= 55 && frameNumber <= 57) { - item->ItemFlags[3] = 200; - *((int*)&item->ItemFlags[0]) = 30; + item.ItemFlags[3] = FOUR_BLADES_IDLE_HARM_DAMAGE; } } - AnimateItem(item); + AnimateItem(&item); } } } diff --git a/TombEngine/Objects/TR4/Trap/tr4_fourblades.h b/TombEngine/Objects/TR4/Trap/tr4_fourblades.h index dc5dd0f9b..7c89e948a 100644 --- a/TombEngine/Objects/TR4/Trap/tr4_fourblades.h +++ b/TombEngine/Objects/TR4/Trap/tr4_fourblades.h @@ -1,6 +1,7 @@ #pragma once -namespace TEN::Entities::TR4 +namespace TEN::Entities::Traps { - void FourBladesControl(short itemNumber); + void InitializeFourBlades(short itemNumber); + void ControlFourBlades(short itemNumber); } diff --git a/TombEngine/Objects/TR4/Trap/tr4_hammer.cpp b/TombEngine/Objects/TR4/Trap/tr4_hammer.cpp index 8dcd9c655..526dfab17 100644 --- a/TombEngine/Objects/TR4/Trap/tr4_hammer.cpp +++ b/TombEngine/Objects/TR4/Trap/tr4_hammer.cpp @@ -2,7 +2,6 @@ #include "Objects/TR4/Trap/tr4_hammer.h" #include "Game/animation.h" -#include "Game/collision/sphere.h" #include "Game/control/control.h" #include "Game/effects/debris.h" #include "Game/effects/effects.h" @@ -14,171 +13,185 @@ #include "Specific/Input/Input.h" #include "Specific/level.h" -constexpr auto RIGHT_HAMMER_BITS = ((1 << 5) | (1 << 6) | (1 << 7)); -constexpr auto LEFT_HAMMER_BITS = ((1 << 8) | (1 << 9) | (1 << 10)); -constexpr auto HAMMER_HIT_DAMAGE = 150; -constexpr auto HAMMER_OCB4_INTERVAL = 60; -constexpr auto HAMMER_HIT_FRAME = 8; //frame the hammer explodes pushables -constexpr auto HAMMER_CLOSED_FRAME = 52; //frame the hammer is fully closed +// NOTES: +// ItemFlags[0] | ItemFlags[1] = Harm joints. +// ItemFlags[2] = Timer in frame time before next activation. +// ItemFlags[3] = Damage. -namespace TEN::Entities::TR4 +namespace TEN::Entities::Traps { + constexpr auto HAMMER_HIT_DAMAGE = 150; + constexpr auto HAMMER_OCB4_INTERVAL = 60; + constexpr auto HAMMER_HIT_FRAME = 8; // Frame pushables can be destroyed. + constexpr auto HAMMER_CLOSED_FRAME = 52; // Frame on whcih hammer is fully closed. - enum HammerState - { - HAMMER_STATE_NONE = 0, - HAMMER_STATE_IDLE = 1, - HAMMER_STATE_ACTIVE = 2, - }; + constexpr auto RIGHT_HAMMER_BITS = (1 << 5) | (1 << 6) | (1 << 7); + constexpr auto LEFT_HAMMER_BITS = (1 << 8) | (1 << 9) | (1 << 10); - enum HammerAnim - { - HAMMER_ANIM_NOT_ACTIVATED = 0, - HAMMER_ANIM_ACTIVATED = 1 - }; + enum HammerState + { + HAMMER_STATE_NONE = 0, + HAMMER_STATE_IDLE = 1, + HAMMER_STATE_ACTIVE = 2, + }; - //ItemFlags[0] | ItemFlags[1] = hurtful bits. - //ItemFlags[2] = Timer. (X amount of frames before triggering again) - //ItemFlags[3] = Famage dealt to Lara at touch, per frame. + enum HammerAnim + { + HAMMER_ANIM_INACTIVE = 0, + HAMMER_ANIM_ACTIVE = 1 + }; - void HammerControl(short itemNumber) - { - auto* item = &g_Level.Items[itemNumber]; - int frameNumber = item->Animation.FrameNumber - GetAnimData(item).frameBase; - item->ItemFlags[3] = HAMMER_HIT_DAMAGE; + void ControlHammer(short itemNumber) + { + auto& item = g_Level.Items[itemNumber]; - if (!TriggerActive(item)) - { - *(long*)&item->ItemFlags[0] = 0; - return; - } + int frameNumber = item.Animation.FrameNumber - GetAnimData(item).frameBase; + item.ItemFlags[3] = HAMMER_HIT_DAMAGE; - int hammerTouched = 0; + if (!TriggerActive(&item)) + { + *(long*)&item.ItemFlags[0] = 0; + return; + } - if (!item->TriggerFlags) - { - if (frameNumber < HAMMER_CLOSED_FRAME) - *(long*)&item->ItemFlags[0] = RIGHT_HAMMER_BITS; - else - *(long*)&item->ItemFlags[0] = 0; - } - else if (item->Animation.ActiveState == HAMMER_STATE_IDLE && item->Animation.TargetState == HAMMER_STATE_IDLE) - { - if (item->ItemFlags[2]) - { - if (item->TriggerFlags == 3) - { - item->Flags &= ~CODE_BITS; - item->ItemFlags[2] = 0; - } - else if (item->TriggerFlags == 4) - item->ItemFlags[2]--; - else - item->ItemFlags[2] = 0; - } - else - { - item->Animation.AnimNumber = Objects[item->ObjectNumber].animIndex + HAMMER_ANIM_ACTIVATED; - item->Animation.FrameNumber = GetAnimData(item).frameBase; - item->Animation.ActiveState = HAMMER_STATE_ACTIVE; - item->Animation.TargetState = HAMMER_STATE_ACTIVE; - item->ItemFlags[2] = HAMMER_OCB4_INTERVAL; - } - } - else - { - item->Animation.TargetState = HAMMER_STATE_IDLE; + bool isHammerTouched = false; - if (frameNumber < HAMMER_CLOSED_FRAME) - *(long*)&item->ItemFlags[0] = RIGHT_HAMMER_BITS | LEFT_HAMMER_BITS; - else - *(long*)&item->ItemFlags[0] = 0; + if (!item.TriggerFlags) + { + if (frameNumber < HAMMER_CLOSED_FRAME) + { + *(long*)&item.ItemFlags[0] = RIGHT_HAMMER_BITS; + } + else + { + *(long*)&item.ItemFlags[0] = 0; + } + } + else if (item.Animation.ActiveState == HAMMER_STATE_IDLE && item.Animation.TargetState == HAMMER_STATE_IDLE) + { + if (item.ItemFlags[2]) + { + if (item.TriggerFlags == 3) + { + item.Flags &= ~CODE_BITS; + item.ItemFlags[2] = 0; + } + else if (item.TriggerFlags == 4) + { + item.ItemFlags[2]--; + } + else + { + item.ItemFlags[2] = 0; + } + } + else + { + item.Animation.AnimNumber = Objects[item.ObjectNumber].animIndex + HAMMER_ANIM_ACTIVE; + item.Animation.FrameNumber = GetAnimData(item).frameBase; + item.Animation.ActiveState = HAMMER_STATE_ACTIVE; + item.Animation.TargetState = HAMMER_STATE_ACTIVE; + item.ItemFlags[2] = HAMMER_OCB4_INTERVAL; + } + } + else + { + item.Animation.TargetState = HAMMER_STATE_IDLE; - if (frameNumber == HAMMER_HIT_FRAME) - { - if (item->TriggerFlags == 2) - { - short targetItem = g_Level.Rooms[item->RoomNumber].itemNumber; + if (frameNumber < HAMMER_CLOSED_FRAME) + { + *(long*)&item.ItemFlags[0] = RIGHT_HAMMER_BITS | LEFT_HAMMER_BITS; + } + else + { + *(long*)&item.ItemFlags[0] = 0; + } - if (targetItem != NO_VALUE) - { - auto* target = &g_Level.Items[targetItem]; - for (; targetItem != NO_VALUE; targetItem = target->NextItem) - { - target = &g_Level.Items[targetItem]; + if (frameNumber == HAMMER_HIT_FRAME) + { + if (item.TriggerFlags == 2) + { + short targetItem = g_Level.Rooms[item.RoomNumber].itemNumber; - if (target->ObjectNumber == ID_OBELISK && target->Pose.Orientation.y == -ANGLE(270) && - g_Level.Items[target->ItemFlags[0]].Pose.Orientation.y == ANGLE(90) && - g_Level.Items[target->ItemFlags[1]].Pose.Orientation.y == 0) - { - target->Flags |= CODE_BITS; - g_Level.Items[target->ItemFlags[0]].Flags |= CODE_BITS; - g_Level.Items[target->ItemFlags[1]].Flags |= CODE_BITS; - break; - } - } - } + if (targetItem != NO_VALUE) + { + auto* target = &g_Level.Items[targetItem]; + for (; targetItem != NO_VALUE; targetItem = target->NextItem) + { + target = &g_Level.Items[targetItem]; - SoundEffect(SFX_TR4_GENERIC_HEAVY_THUD, &item->Pose); - SoundEffect(SFX_TR4_EXPLOSION2, &item->Pose); - } - else - { - short targetItem = g_Level.Rooms[item->RoomNumber].itemNumber; + if (target->ObjectNumber == ID_OBELISK && target->Pose.Orientation.y == -ANGLE(270) && + g_Level.Items[target->ItemFlags[0]].Pose.Orientation.y == ANGLE(90) && + g_Level.Items[target->ItemFlags[1]].Pose.Orientation.y == 0) + { + target->Flags |= CODE_BITS; + g_Level.Items[target->ItemFlags[0]].Flags |= CODE_BITS; + g_Level.Items[target->ItemFlags[1]].Flags |= CODE_BITS; + break; + } + } + } - if (targetItem != NO_VALUE) - { - auto* target = &g_Level.Items[targetItem]; - for (; targetItem != NO_VALUE; targetItem = target->NextItem) - { - target = &g_Level.Items[targetItem]; + SoundEffect(SFX_TR4_GENERIC_HEAVY_THUD, &item.Pose); + SoundEffect(SFX_TR4_EXPLOSION2, &item.Pose); + } + else + { + int targetItemNumber = g_Level.Rooms[item.RoomNumber].itemNumber; + if (targetItemNumber != NO_VALUE) + { + // TODO: What is this syntax? + auto* targetItem = &g_Level.Items[targetItemNumber]; + for (; targetItemNumber != NO_VALUE; targetItemNumber = targetItem->NextItem) + { + targetItem = &g_Level.Items[targetItemNumber]; - 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 && - item->Pose.Position.z == target->Pose.Position.z) - { - ExplodeItemNode(target, 0, 0, 128); - KillItem(targetItem); - hammerTouched = 1; - } - } - } - } + if ((targetItem->ObjectNumber >= ID_PUSHABLE_OBJECT1 && targetItem->ObjectNumber <= ID_PUSHABLE_OBJECT10) || + (targetItem->ObjectNumber >= ID_PUSHABLE_OBJECT_CLIMBABLE1 && targetItem->ObjectNumber <= ID_PUSHABLE_OBJECT_CLIMBABLE10)) + { + if (item.Pose.Position.x == targetItem->Pose.Position.x && + item.Pose.Position.z == targetItem->Pose.Position.z) + { + ExplodeItemNode(targetItem, 0, 0, 128); + KillItem(targetItemNumber); + isHammerTouched = true; + } + } + } + } - if (hammerTouched) - { - targetItem = g_Level.Rooms[item->RoomNumber].itemNumber; + if (isHammerTouched) + { + targetItemNumber = g_Level.Rooms[item.RoomNumber].itemNumber; - if (targetItem != NO_VALUE) - { - auto* target = &g_Level.Items[targetItem]; - for (; targetItem != NO_VALUE; targetItem = target->NextItem) - { - target = &g_Level.Items[targetItem]; + if (targetItemNumber != NO_VALUE) + { + auto* target = &g_Level.Items[targetItemNumber]; + for (; targetItemNumber != NO_VALUE; targetItemNumber = target->NextItem) + { + target = &g_Level.Items[targetItemNumber]; - //changed to take all puzzle items, keys and their combos. Original is hardcoded to a few slots. -Troye - if ((target->ObjectNumber >= ID_PUZZLE_ITEM1 && target->ObjectNumber <= ID_PUZZLE_ITEM16) || - (target->ObjectNumber >= ID_PUZZLE_ITEM1_COMBO1 && target->ObjectNumber <= ID_PUZZLE_ITEM16_COMBO2) || - (target->ObjectNumber >= ID_KEY_ITEM1 && target->ObjectNumber <= ID_KEY_ITEM16) || - (target->ObjectNumber >= ID_KEY_ITEM1_COMBO1 && target->ObjectNumber <= ID_KEY_ITEM16_COMBO2)) - { - if (item->Pose.Position.x == target->Pose.Position.x && - item->Pose.Position.z == target->Pose.Position.z) - { - target->Status = ITEM_NOT_ACTIVE; - } - } - } - } - } - } - } - else if (frameNumber > HAMMER_CLOSED_FRAME && item->TriggerFlags == 2) - item->Flags &= ~CODE_BITS; - } + // Take all puzzle items, keys, and their combos. + if ((target->ObjectNumber >= ID_PUZZLE_ITEM1 && target->ObjectNumber <= ID_PUZZLE_ITEM16) || + (target->ObjectNumber >= ID_PUZZLE_ITEM1_COMBO1 && target->ObjectNumber <= ID_PUZZLE_ITEM16_COMBO2) || + (target->ObjectNumber >= ID_KEY_ITEM1 && target->ObjectNumber <= ID_KEY_ITEM16) || + (target->ObjectNumber >= ID_KEY_ITEM1_COMBO1 && target->ObjectNumber <= ID_KEY_ITEM16_COMBO2)) + { + if (item.Pose.Position.x == target->Pose.Position.x && + item.Pose.Position.z == target->Pose.Position.z) + { + target->Status = ITEM_NOT_ACTIVE; + } + } + } + } + } + } + } + else if (frameNumber > HAMMER_CLOSED_FRAME && item.TriggerFlags == 2) + item.Flags &= ~CODE_BITS; + } - AnimateItem(item); - } + AnimateItem(&item); + } } diff --git a/TombEngine/Objects/TR4/Trap/tr4_hammer.h b/TombEngine/Objects/TR4/Trap/tr4_hammer.h index 649d2f079..5b8998e22 100644 --- a/TombEngine/Objects/TR4/Trap/tr4_hammer.h +++ b/TombEngine/Objects/TR4/Trap/tr4_hammer.h @@ -1,6 +1,6 @@ #pragma once -namespace TEN::Entities::TR4 +namespace TEN::Entities::Traps { - void HammerControl(short itemNumber); + void ControlHammer(short itemNumber); } diff --git a/TombEngine/Objects/TR4/Trap/tr4_joby_spikes.cpp b/TombEngine/Objects/TR4/Trap/tr4_joby_spikes.cpp index 9512dc756..1caf7e87c 100644 --- a/TombEngine/Objects/TR4/Trap/tr4_joby_spikes.cpp +++ b/TombEngine/Objects/TR4/Trap/tr4_joby_spikes.cpp @@ -1,86 +1,109 @@ #include "framework.h" -#include "tr4_joby_spikes.h" -#include "Specific/level.h" -#include "Game/collision/collide_room.h" -#include "Game/control/control.h" +#include "Objects/TR4/Trap/tr4_joby_spikes.h" + #include "Game/animation.h" -#include "Sound/sound.h" -#include "Game/Lara/lara.h" +#include "Game/collision/collide_room.h" +#include "Game/collision/Point.h" +#include "Game/control/control.h" #include "Game/effects/effects.h" #include "Game/items.h" +#include "Game/Lara/lara.h" +#include "Sound/sound.h" +#include "Specific/level.h" -namespace TEN::Entities::TR4 +using namespace TEN::Collision::Point; + +// TODO: Need to test and adapt formula for scaling to other heights. + +namespace TEN::Entities::Traps { + constexpr auto JOBY_SPIKES_HARM_DAMAGE = 8; + constexpr auto JOBY_SPIKES_EXTRA_ROTATION_SPEED = 2; + constexpr auto JOBY_SPIKES_SCALE_INCREMENT = 3; + constexpr auto JOBY_SPIKES_MAX_SCALE = BLOCK(3.25f); + void InitializeJobySpikes(short itemNumber) { - auto* item = &g_Level.Items[itemNumber]; + auto& item = g_Level.Items[itemNumber]; + + auto& angleRotationSpeed = item.ItemFlags[0]; + auto& spikeLength = item.ItemFlags[1]; + auto& clockworkDirection = item.ItemFlags[2]; // ?? + auto& maxExtensionLength = item.ItemFlags[3]; // Set bone mutators to EulerAngles identity by default. - for (auto& mutator : item->Model.Mutators) + for (auto& mutator : item.Model.Mutators) mutator.Scale.y = 0.0f; - item->Pose.Orientation.y = GetRandomControl() * 1024; - item->ItemFlags[2] = GetRandomControl() & 1; + item.Pose.Orientation.y = Random::GenerateInt(0, INT16_MAX) * ANGLE(5.5f); + clockworkDirection = Random::GenerateInt(0, 1); - auto probe = GetCollision(item); - - // TODO: Check this optimized division. - //v6 = 1321528399i64 * ((probe.Position.Floor - probe.Position.Ceiling) << 12); - //item->itemFlags[3] = (HIDWORD(v6) >> 31) + (SHIDWORD(v6) >> 10); - - item->ItemFlags[3] = (short)((probe.Position.Floor - probe.Position.Ceiling) * 1024 * 12 / 13); + auto pointColl = GetPointCollision(item); + maxExtensionLength = (short)(4096 * (pointColl.GetFloorHeight() - pointColl.GetCeilingHeight()) / JOBY_SPIKES_MAX_SCALE); } - void JobySpikesControl(short itemNumber) + void ControlJobySpikes(short itemNumber) { - auto* item = &g_Level.Items[itemNumber]; + auto& item = g_Level.Items[itemNumber]; - if (!TriggerActive(item)) + if (!TriggerActive(&item)) return; - SoundEffect(SFX_TR4_METAL_SCRAPE_LOOP1, &item->Pose); - auto frameData = GetFrameInterpData(*LaraItem); + auto& angleRotationSpeed = item.ItemFlags[0]; + auto& spikeLength = item.ItemFlags[1]; + auto& clockworkDirection = item.ItemFlags[2]; + auto& maxExtensionLength = item.ItemFlags[3]; - int dy = LaraItem->Pose.Position.y + frameData.FramePtr0->BoundingBox.Y1; - int dl = 3328 * item->ItemFlags[1] / 4096; + SoundEffect(SFX_TR4_METAL_SCRAPE_LOOP1, &item.Pose); + auto frameData = GetFrameInterpData(*LaraItem); + + // Damage player. + int playerHeight = LaraItem->Pose.Position.y + frameData.FramePtr0->BoundingBox.Y1; + int spikeHeight = JOBY_SPIKES_MAX_SCALE * spikeLength / 4096; if (LaraItem->HitPoints > 0) { - if (item->Pose.Position.y + dl > dy) + if (item.Pose.Position.y + spikeHeight > playerHeight) { - if (abs(item->Pose.Position.x - LaraItem->Pose.Position.x) < CLICK(2)) + if (abs(item.Pose.Position.x - LaraItem->Pose.Position.x) < BLOCK(0.5)) { - if (abs(item->Pose.Position.z - LaraItem->Pose.Position.z) < CLICK(2)) + if (abs(item.Pose.Position.z - LaraItem->Pose.Position.z) < BLOCK(0.5)) { - int x = (GetRandomControl() & 0x7F) + LaraItem->Pose.Position.x - 64; - int y = dy + GetRandomControl() % (item->Pose.Position.y - dy + dl); - int z = (GetRandomControl() & 0x7F) + LaraItem->Pose.Position.z - 64; + DoDamage(LaraItem, JOBY_SPIKES_HARM_DAMAGE); - DoBloodSplat(x, y, z, (GetRandomControl() & 3) + 2, 2 * GetRandomControl(), item->RoomNumber); - DoDamage(LaraItem, 8); + int bloodPosX = LaraItem->Pose.Position.x + Random::GenerateInt(-64, 64); + int bloodPosY = playerHeight + Random::GenerateInt( 0, item.Pose.Position.y - playerHeight + spikeHeight); + int bloodPosZ = LaraItem->Pose.Position.z + Random::GenerateInt(-64, 64); + + DoBloodSplat(bloodPosX, bloodPosY, bloodPosZ, Random::GenerateInt (2,5), 2 * GetRandomControl(), item.RoomNumber); + } } } } - if (item->ItemFlags[2]) + // Rotate. + if (clockworkDirection == 1) { - if (item->ItemFlags[0] > -4096) - item->ItemFlags[0] = item->ItemFlags[1] + (item->ItemFlags[1] / 64) - 2; + if (angleRotationSpeed < 4096) + angleRotationSpeed = (spikeLength / 64) + spikeLength + JOBY_SPIKES_EXTRA_ROTATION_SPEED; + } + else + { + if (angleRotationSpeed > -4096) + angleRotationSpeed = spikeLength + (spikeLength / 64) - JOBY_SPIKES_EXTRA_ROTATION_SPEED; } - else if (item->ItemFlags[0] < 4096) - item->ItemFlags[0] = (item->ItemFlags[1] / 64) + item->ItemFlags[1] + 2; - if (item->ItemFlags[1] < item->ItemFlags[3]) - item->ItemFlags[1] += 3; + if (spikeLength < maxExtensionLength) + spikeLength += JOBY_SPIKES_SCALE_INCREMENT; - item->Pose.Orientation.y += item->ItemFlags[0]; + item.Pose.Orientation.y += angleRotationSpeed; - // Update bone mutators. - if (item->ItemFlags[1]) + // Scale. + if (spikeLength) { - for (auto& mutator : item->Model.Mutators) - mutator.Scale = Vector3(1.0f, item->ItemFlags[1] / 4096.0f, 1.0f); + for (auto& mutator : item.Model.Mutators) + mutator.Scale = Vector3(1.0f, spikeLength / 4096.0f, 1.0f); } } } diff --git a/TombEngine/Objects/TR4/Trap/tr4_joby_spikes.h b/TombEngine/Objects/TR4/Trap/tr4_joby_spikes.h index 9a27c4fed..ff4e2a37f 100644 --- a/TombEngine/Objects/TR4/Trap/tr4_joby_spikes.h +++ b/TombEngine/Objects/TR4/Trap/tr4_joby_spikes.h @@ -1,7 +1,7 @@ #pragma once -namespace TEN::Entities::TR4 +namespace TEN::Entities::Traps { void InitializeJobySpikes(short itemNumber); - void JobySpikesControl(short itemNumber); + void ControlJobySpikes(short itemNumber); } diff --git a/TombEngine/Objects/TR4/Trap/tr4_mine.cpp b/TombEngine/Objects/TR4/Trap/tr4_mine.cpp index 41c38608d..63266999b 100644 --- a/TombEngine/Objects/TR4/Trap/tr4_mine.cpp +++ b/TombEngine/Objects/TR4/Trap/tr4_mine.cpp @@ -1,62 +1,65 @@ #include "framework.h" -#include "tr4_mine.h" -#include "Specific/level.h" -#include "Game/collision/sphere.h" -#include "Sound/sound.h" +#include "Objects/TR4/Trap/tr4_mine.h" + +#include "Game/collision/collide_item.h" +#include "Game/collision/Sphere.h" #include "Game/effects/debris.h" #include "Game/effects/effects.h" #include "Game/effects/tomb4fx.h" #include "Game/effects/weather.h" #include "Game/items.h" -#include "Game/collision/collide_item.h" #include "Objects/objectslist.h" +#include "Sound/sound.h" +#include "Specific/level.h" +using namespace TEN::Collision::Sphere; using namespace TEN::Effects::Environment; -namespace TEN::Entities::TR4 +namespace TEN::Entities::Traps { void InitializeMine(short itemNumber) { - auto* item = &g_Level.Items[itemNumber]; + auto& item = g_Level.Items[itemNumber]; - if (item->TriggerFlags) - item->MeshBits = 0; + if (item.TriggerFlags) + item.MeshBits = 0; } - void MineControl(short itemNumber) + void ControlMine(short itemNumber) { - auto* item = &g_Level.Items[itemNumber]; + auto& item = g_Level.Items[itemNumber]; - int num = GetSpheres(item, CreatureSpheres, SPHERES_SPACE_WORLD, Matrix::Identity); - if (item->ItemFlags[0] >= 150) + auto spheres = item.GetSpheres(); + if (item.ItemFlags[0] >= 150) { - SoundEffect(SFX_TR4_EXPLOSION1, &item->Pose); - SoundEffect(SFX_TR4_EXPLOSION2, &item->Pose); - SoundEffect(SFX_TR4_EXPLOSION1, &item->Pose, SoundEnvironment::Land, 0.7f, 0.5f); + SoundEffect(SFX_TR4_EXPLOSION1, &item.Pose); + SoundEffect(SFX_TR4_EXPLOSION2, &item.Pose); + SoundEffect(SFX_TR4_EXPLOSION1, &item.Pose, SoundEnvironment::Land, 0.7f, 0.5f); - if (num > 0) + if (!spheres.empty()) { - for (int i = 0; i < num; i++) + for (int i = 0; i < spheres.size(); i++) { + // TODO: Hardcoding. if (i >= 7 && i != 9) { - auto* sphere = &CreatureSpheres[i]; + const auto& sphere = spheres[i]; - TriggerExplosionSparks(sphere->x, sphere->y, sphere->z, 3, -2, 0, -item->RoomNumber); - TriggerExplosionSparks(sphere->x, sphere->y, sphere->z, 3, -1, 0, -item->RoomNumber); - TriggerShockwave((Pose*)sphere, 48, 304, (GetRandomControl() & 0x1F) + 112, 0, 96, 128, 32, EulerAngles(2048, 0.0f, 0.0f), 0, true, false, false, (int)ShockwaveStyle::Normal); + TriggerExplosionSparks(sphere.Center.x, sphere.Center.y, sphere.Center.z, 3, -2, 0, -item.RoomNumber); + TriggerExplosionSparks(sphere.Center.x, sphere.Center.y, sphere.Center.z, 3, -1, 0, -item.RoomNumber); + TriggerShockwave(&Pose(Vector3i(sphere.Center)), 48, 304, (GetRandomControl() & 0x1F) + 112, 0, 96, 128, 32, EulerAngles(2048, 0.0f, 0.0f), 0, true, false, false, (int)ShockwaveStyle::Normal); } } - for (int i = 0; i < num; i++) - ExplodeItemNode(item, i, 0, -128); + for (int i = 0; i < spheres.size(); i++) + ExplodeItemNode(&item, i, 0, -128); } Weather.Flash(255, 192, 64, 0.03f); - short currentItemNumber = g_Level.Rooms[item->RoomNumber].itemNumber; + int currentItemNumber = g_Level.Rooms[item.RoomNumber].itemNumber; - // Make the sentry gun explode? + // Make sentry gun explode? while (currentItemNumber != NO_VALUE) { auto* currentItem = &g_Level.Items[currentItemNumber]; @@ -71,93 +74,93 @@ namespace TEN::Entities::TR4 } else { - item->ItemFlags[0]++; + item.ItemFlags[0]++; - int fade = 4 * item->ItemFlags[0]; + int fade = 4 * item.ItemFlags[0]; if (fade > 255) fade = 0; - for (int i = 0; i < num; i++) + for (int i = 0; i < spheres.size(); i++) { if (i == 0 || i > 5) { - auto* sphere = &CreatureSpheres[i]; - AddFire(sphere->x, sphere->y, sphere->z, item->RoomNumber, 0.25f, fade); + const auto& sphere = spheres[i]; + AddFire(sphere.Center.x, sphere.Center.y, sphere.Center.z, item.RoomNumber, 0.25f, fade); } } - SoundEffect(SFX_TR4_LOOP_FOR_SMALL_FIRES, &item->Pose); + SoundEffect(SFX_TR4_LOOP_FOR_SMALL_FIRES, &item.Pose); } } - void MineCollision(short itemNumber, ItemInfo* laraItem, CollisionInfo* coll) + void CollideMine(short itemNumber, ItemInfo* playerItem, CollisionInfo* coll) { - auto* mineItem = &g_Level.Items[itemNumber]; + auto& mineItem = g_Level.Items[itemNumber]; - if (mineItem->TriggerFlags && !mineItem->ItemFlags[3]) + if (!mineItem.TriggerFlags || mineItem.ItemFlags[3]) + return; + + if (playerItem->Animation.AnimNumber != LA_DETONATOR_USE || + playerItem->Animation.FrameNumber < (GetAnimData(playerItem).frameBase + 57)) { - if (laraItem->Animation.AnimNumber != LA_DETONATOR_USE || - laraItem->Animation.FrameNumber < GetAnimData(laraItem).frameBase + 57) + if (TestBoundsCollide(&mineItem, playerItem, BLOCK(0.5f))) { - if (TestBoundsCollide(mineItem, laraItem, 512)) - { - TriggerExplosionSparks(mineItem->Pose.Position.x, mineItem->Pose.Position.y, mineItem->Pose.Position.z, 3, -2, 0, mineItem->RoomNumber); - for (int i = 0; i < 2; i++) - TriggerExplosionSparks(mineItem->Pose.Position.x, mineItem->Pose.Position.y, mineItem->Pose.Position.z, 3, -1, 0, mineItem->RoomNumber); + TriggerExplosionSparks(mineItem.Pose.Position.x, mineItem.Pose.Position.y, mineItem.Pose.Position.z, 3, -2, 0, mineItem.RoomNumber); + for (int i = 0; i < 2; i++) + TriggerExplosionSparks(mineItem.Pose.Position.x, mineItem.Pose.Position.y, mineItem.Pose.Position.z, 3, -1, 0, mineItem.RoomNumber); - mineItem->MeshBits = 1; + mineItem.MeshBits = 1; - ExplodeItemNode(mineItem, 0, 0, 128); - KillItem(itemNumber); + ExplodeItemNode(&mineItem, 0, 0, 128); + KillItem(itemNumber); - laraItem->Animation.AnimNumber = LA_MINE_DEATH; - laraItem->Animation.FrameNumber = GetAnimData(*mineItem).frameBase; - laraItem->Animation.ActiveState = LS_DEATH; - laraItem->Animation.Velocity.z = 0; + playerItem->Animation.AnimNumber = LA_MINE_DEATH; + playerItem->Animation.FrameNumber = GetAnimData(*playerItem).frameBase; + playerItem->Animation.ActiveState = LS_DEATH; + playerItem->Animation.Velocity.z = 0; - SoundEffect(SFX_TR4_MINE_EXPLOSION_OVERLAY, &mineItem->Pose); - } + SoundEffect(SFX_TR4_MINE_EXPLOSION_OVERLAY, &mineItem.Pose); } - else + } + else + { + for (int i = 0; i < g_Level.NumItems; i++) { - for (int i = 0; i < g_Level.NumItems; i++) - { - auto* currentItem = &g_Level.Items[i]; + auto* currentItem = &g_Level.Items[i]; - // Explode other mines - if (currentItem->ObjectNumber == ID_MINE && - currentItem->Status != ITEM_INVISIBLE && - currentItem->TriggerFlags == 0) - { + // Explode other mines. + if (currentItem->ObjectNumber == ID_MINE && + currentItem->Status != ITEM_INVISIBLE && + currentItem->TriggerFlags == 0) + { + TriggerExplosionSparks( + currentItem->Pose.Position.x, + currentItem->Pose.Position.y, + currentItem->Pose.Position.z, + 3, + -2, + 0, + currentItem->RoomNumber); + + for (int j = 0; j < 2; j++) TriggerExplosionSparks( currentItem->Pose.Position.x, currentItem->Pose.Position.y, currentItem->Pose.Position.z, 3, - -2, + -1, 0, currentItem->RoomNumber); - for (int j = 0; j < 2; j++) - TriggerExplosionSparks( - currentItem->Pose.Position.x, - currentItem->Pose.Position.y, - currentItem->Pose.Position.z, - 3, - -1, - 0, - currentItem->RoomNumber); + currentItem->MeshBits = 1; - currentItem->MeshBits = 1; + ExplodeItemNode(currentItem, 0, 0, -32); + KillItem(i); - ExplodeItemNode(currentItem, 0, 0, -32); - KillItem(i); + if (!(GetRandomControl() & 3)) + SoundEffect(SFX_TR4_MINE_EXPLOSION_OVERLAY, ¤tItem->Pose); - if (!(GetRandomControl() & 3)) - SoundEffect(SFX_TR4_MINE_EXPLOSION_OVERLAY, ¤tItem->Pose); - - currentItem->Status = ITEM_INVISIBLE; - } + currentItem->Status = ITEM_INVISIBLE; } } } diff --git a/TombEngine/Objects/TR4/Trap/tr4_mine.h b/TombEngine/Objects/TR4/Trap/tr4_mine.h index ba19b8010..fb080e401 100644 --- a/TombEngine/Objects/TR4/Trap/tr4_mine.h +++ b/TombEngine/Objects/TR4/Trap/tr4_mine.h @@ -3,9 +3,9 @@ struct ItemInfo; struct CollisionInfo; -namespace TEN::Entities::TR4 +namespace TEN::Entities::Traps { void InitializeMine(short itemNumber); - void MineControl(short itemNumber); - void MineCollision(short itemNumber, ItemInfo* laraItem, CollisionInfo* coll); + void ControlMine(short itemNumber); + void CollideMine(short itemNumber, ItemInfo* playerItem, CollisionInfo* coll); } diff --git a/TombEngine/Objects/TR4/Trap/tr4_moving_blade.cpp b/TombEngine/Objects/TR4/Trap/tr4_moving_blade.cpp index e54965255..652f10943 100644 --- a/TombEngine/Objects/TR4/Trap/tr4_moving_blade.cpp +++ b/TombEngine/Objects/TR4/Trap/tr4_moving_blade.cpp @@ -1,26 +1,28 @@ #include "framework.h" -#include "tr4_moving_blade.h" -#include "Specific/level.h" -#include "Game/control/control.h" -#include "Sound/sound.h" +#include "Objects/TR4/Trap/tr4_moving_blade.h"\ + #include "Game/animation.h" -#include "Game/Lara/lara.h" -#include "Game/collision/sphere.h" +#include "Game/control/control.h" #include "Game/effects/effects.h" #include "Game/items.h" +#include "Game/Lara/lara.h" +#include "Sound/sound.h" +#include "Specific/level.h" -namespace TEN::Entities::TR4 +namespace TEN::Entities::Traps { - void MovingBladeControl(short itemNumber) + void ControlMovingBlade(short itemNumber) { - auto* item = &g_Level.Items[itemNumber]; + auto& item = g_Level.Items[itemNumber]; - if (TriggerActive(item)) + if (TriggerActive(&item)) { - item->ItemFlags[3] = 50; - AnimateItem(item); + item.ItemFlags[3] = 50; + AnimateItem(&item); } else - item->Animation.FrameNumber = GetAnimData(item).frameBase; + { + item.Animation.FrameNumber = GetAnimData(item).frameBase; + } } } diff --git a/TombEngine/Objects/TR4/Trap/tr4_moving_blade.h b/TombEngine/Objects/TR4/Trap/tr4_moving_blade.h index 84233080e..0deb9ee30 100644 --- a/TombEngine/Objects/TR4/Trap/tr4_moving_blade.h +++ b/TombEngine/Objects/TR4/Trap/tr4_moving_blade.h @@ -1,6 +1,6 @@ #pragma once -namespace TEN::Entities::TR4 +namespace TEN::Entities::Traps { - void MovingBladeControl(short itemNumber); + void ControlMovingBlade(short itemNumber); } diff --git a/TombEngine/Objects/TR4/Trap/tr4_plinthblade.cpp b/TombEngine/Objects/TR4/Trap/tr4_plinthblade.cpp index e9727ca2b..e5288a731 100644 --- a/TombEngine/Objects/TR4/Trap/tr4_plinthblade.cpp +++ b/TombEngine/Objects/TR4/Trap/tr4_plinthblade.cpp @@ -1,28 +1,35 @@ #include "framework.h" -#include "tr4_plinthblade.h" -#include "Specific/level.h" -#include "Game/control/control.h" +#include "Objects/TR4/Trap/tr4_plinthblade.h" + #include "Game/animation.h" +#include "Game/control/control.h" #include "Game/items.h" +#include "Specific/level.h" -namespace TEN::Entities::TR4 +namespace TEN::Entities::Traps { - void PlinthBladeControl(short itemNumber) + void ControlPlinthBlade(short itemNumber) { - auto* item = &g_Level.Items[itemNumber]; + auto& item = g_Level.Items[itemNumber]; - if (!TriggerActive(item)) - item->Animation.FrameNumber = GetAnimData(item).frameBase; + if (!TriggerActive(&item)) + { + item.Animation.FrameNumber = GetAnimData(item).frameBase; + } else { - int frameNumber = item->Animation.FrameNumber - GetAnimData(item).frameBase; + int frameNumber = item.Animation.FrameNumber - GetAnimData(item).frameBase; - if (item->Animation.FrameNumber == GetAnimData(item).frameEnd) - item->ItemFlags[3] = 0; + if (item.Animation.FrameNumber == GetAnimData(item).frameEnd) + { + item.ItemFlags[3] = 0; + } else - item->ItemFlags[3] = 200; + { + item.ItemFlags[3] = 200; + } - AnimateItem(item); + AnimateItem(&item); } } } diff --git a/TombEngine/Objects/TR4/Trap/tr4_plinthblade.h b/TombEngine/Objects/TR4/Trap/tr4_plinthblade.h index b0393769a..30668c5f2 100644 --- a/TombEngine/Objects/TR4/Trap/tr4_plinthblade.h +++ b/TombEngine/Objects/TR4/Trap/tr4_plinthblade.h @@ -1,6 +1,6 @@ #pragma once -namespace TEN::Entities::TR4 +namespace TEN::Entities::Traps { - void PlinthBladeControl(short itemNumber); + void ControlPlinthBlade(short itemNumber); } diff --git a/TombEngine/Objects/TR4/Trap/tr4_plough.cpp b/TombEngine/Objects/TR4/Trap/tr4_plough.cpp index 465c303ea..1985d08c3 100644 --- a/TombEngine/Objects/TR4/Trap/tr4_plough.cpp +++ b/TombEngine/Objects/TR4/Trap/tr4_plough.cpp @@ -1,24 +1,34 @@ #include "framework.h" -#include "tr4_plough.h" -#include "Specific/level.h" -#include "Game/control/control.h" +#include "Objects/TR4/Trap/tr4_plough.h" + #include "Game/animation.h" +#include "Game/control/control.h" #include "Game/items.h" +#include "Specific/level.h" -namespace TEN::Entities::TR4 +namespace TEN::Entities::Traps { - void PloughControl(short itemNumber) + void InitializePlough(short itemNumber) { - auto* item = &g_Level.Items[itemNumber]; + auto& item = g_Level.Items[itemNumber]; - item->ItemFlags[3] = 50; + item.ItemFlags[4] = 1; + } - if (TriggerActive(item)) + void ControlPlough(short itemNumber) + { + auto& item = g_Level.Items[itemNumber]; + + item.ItemFlags[3] = 50; + + if (TriggerActive(&item)) { - *((int*)&item->ItemFlags) = 0x3F000; - AnimateItem(item); + *((int*)&item.ItemFlags) = 0x3F000; + AnimateItem(&item); } else - *((int*)&item->ItemFlags) = 0; + { + *((int*)&item.ItemFlags) = 0; + } } } diff --git a/TombEngine/Objects/TR4/Trap/tr4_plough.h b/TombEngine/Objects/TR4/Trap/tr4_plough.h index 3485dc324..3b37cfdf3 100644 --- a/TombEngine/Objects/TR4/Trap/tr4_plough.h +++ b/TombEngine/Objects/TR4/Trap/tr4_plough.h @@ -1,6 +1,7 @@ #pragma once -namespace TEN::Entities::TR4 +namespace TEN::Entities::Traps { - void PloughControl(short itemNumber); + void InitializePlough(short itemNumber); + void ControlPlough(short itemNumber); } diff --git a/TombEngine/Objects/TR4/Trap/tr4_sethblade.cpp b/TombEngine/Objects/TR4/Trap/tr4_sethblade.cpp index b690d616d..105abf25f 100644 --- a/TombEngine/Objects/TR4/Trap/tr4_sethblade.cpp +++ b/TombEngine/Objects/TR4/Trap/tr4_sethblade.cpp @@ -7,7 +7,7 @@ #include "Game/Setup.h" #include "Specific/level.h" -namespace TEN::Entities::TR4 +namespace TEN::Entities::Traps { constexpr auto SETH_BLADE_HARM_DAMAGE = 1000; @@ -32,9 +32,10 @@ namespace TEN::Entities::TR4 item.ItemFlags[2] = item.TriggerFlags >= 0 ? 1 : abs(item.TriggerFlags); //ItemFlags[2] stores blade timer. item.ItemFlags[3] = SETH_BLADE_HARM_DAMAGE; //ItemFlags[3] stored blade harm damage. + item.ItemFlags[4] = 1; } - void SethBladeControl(short itemNumber) + void ControlSethBlade(short itemNumber) { auto& item = g_Level.Items[itemNumber]; diff --git a/TombEngine/Objects/TR4/Trap/tr4_sethblade.h b/TombEngine/Objects/TR4/Trap/tr4_sethblade.h index d0ad89ef0..d042b3e3a 100644 --- a/TombEngine/Objects/TR4/Trap/tr4_sethblade.h +++ b/TombEngine/Objects/TR4/Trap/tr4_sethblade.h @@ -1,7 +1,7 @@ #pragma once -namespace TEN::Entities::TR4 +namespace TEN::Entities::Traps { void InitializeSethBlade(short itemNumber); - void SethBladeControl(short itemNumber); + void ControlSethBlade(short itemNumber); } diff --git a/TombEngine/Objects/TR4/Trap/tr4_slicerdicer.cpp b/TombEngine/Objects/TR4/Trap/tr4_slicerdicer.cpp index 34d56af4d..c7b23f32d 100644 --- a/TombEngine/Objects/TR4/Trap/tr4_slicerdicer.cpp +++ b/TombEngine/Objects/TR4/Trap/tr4_slicerdicer.cpp @@ -1,47 +1,52 @@ #include "framework.h" -#include "tr4_slicerdicer.h" -#include "Specific/level.h" -#include "Sound/sound.h" -#include "Game/collision/collide_room.h" -#include "Game/items.h" -#include "Game/animation.h" -#include "Math/Math.h" +#include "Objects/TR4/Trap/tr4_slicerdicer.h" -namespace TEN::Entities::TR4 +#include "Game/animation.h" +#include "Game/collision/collide_room.h" +#include "Game/collision/Point.h" +#include "Game/items.h" +#include "Math/Math.h" +#include "Sound/sound.h" +#include "Specific/level.h" + +using namespace TEN::Collision::Point; +using namespace TEN::Math; + +namespace TEN::Entities::Traps { void InitializeSlicerDicer(short itemNumber) { - auto* item = &g_Level.Items[itemNumber]; + auto& item = g_Level.Items[itemNumber]; - int dx = phd_sin(item->Pose.Orientation.y + ANGLE(90.0f)) * 512; - int dz = phd_cos(item->Pose.Orientation.y + ANGLE(90.0f)) * 512; + int dx = phd_sin(item.Pose.Orientation.y + ANGLE(90.0f)) * 512; + int dz = phd_cos(item.Pose.Orientation.y + ANGLE(90.0f)) * 512; - item->Pose.Position.x += dx; - item->Pose.Position.z += dz; + item.Pose.Position.x += dx; + item.Pose.Position.z += dz; - item->ItemFlags[0] = item->Pose.Position.x / 256; - item->ItemFlags[1] = (item->Pose.Position.y - 4608) / 256; - item->ItemFlags[2] = item->Pose.Position.z / 256; - item->ItemFlags[3] = 50; + item.ItemFlags[0] = item.Pose.Position.x / 256; + item.ItemFlags[1] = (item.Pose.Position.y - 4608) / 256; + item.ItemFlags[2] = item.Pose.Position.z / 256; + item.ItemFlags[3] = 50; } - void SlicerDicerControl(short itemNumber) + void ControlSlicerDicer(short itemNumber) { - auto* item = &g_Level.Items[itemNumber]; + auto& item = g_Level.Items[itemNumber]; - SoundEffect(SFX_TR4_METAL_SCRAPE_LOOP1, &item->Pose); - SoundEffect(SFX_TR4_METAL_SCRAPE_LOOP2, &item->Pose); + SoundEffect(SFX_TR4_METAL_SCRAPE_LOOP1, &item.Pose); + SoundEffect(SFX_TR4_METAL_SCRAPE_LOOP2, &item.Pose); - item->Pose.Position.x = (item->ItemFlags[0] * 256) + 4608 * phd_cos(item->TriggerFlags) * phd_sin(item->Pose.Orientation.y); - item->Pose.Position.y = (item->ItemFlags[1] * 256) - 4608 * phd_sin(item->TriggerFlags); - item->Pose.Position.z = (item->ItemFlags[2] * 256) + 4608 * phd_cos(item->TriggerFlags) * phd_cos(item->Pose.Orientation.y); + item.Pose.Position.x = (item.ItemFlags[0] * 256) + 4608 * phd_cos(item.TriggerFlags) * phd_sin(item.Pose.Orientation.y); + item.Pose.Position.y = (item.ItemFlags[1] * 256) - 4608 * phd_sin(item.TriggerFlags); + item.Pose.Position.z = (item.ItemFlags[2] * 256) + 4608 * phd_cos(item.TriggerFlags) * phd_cos(item.Pose.Orientation.y); - item->TriggerFlags += 170; + item.TriggerFlags += 170; - auto probedRoomNumber = GetCollision(item).RoomNumber; - if (item->RoomNumber != probedRoomNumber) + int probedRoomNumber = GetPointCollision(item).GetRoomNumber(); + if (item.RoomNumber != probedRoomNumber) ItemNewRoom(itemNumber, probedRoomNumber); - AnimateItem(item); + AnimateItem(&item); } } diff --git a/TombEngine/Objects/TR4/Trap/tr4_slicerdicer.h b/TombEngine/Objects/TR4/Trap/tr4_slicerdicer.h index 524476fb6..1b25343f2 100644 --- a/TombEngine/Objects/TR4/Trap/tr4_slicerdicer.h +++ b/TombEngine/Objects/TR4/Trap/tr4_slicerdicer.h @@ -1,7 +1,7 @@ #pragma once -namespace TEN::Entities::TR4 +namespace TEN::Entities::Traps { void InitializeSlicerDicer(short itemNumber); - void SlicerDicerControl(short itemNumber); + void ControlSlicerDicer(short itemNumber); } diff --git a/TombEngine/Objects/TR4/Trap/tr4_spikeball.cpp b/TombEngine/Objects/TR4/Trap/tr4_spikeball.cpp index 79d353557..95fd2cc79 100644 --- a/TombEngine/Objects/TR4/Trap/tr4_spikeball.cpp +++ b/TombEngine/Objects/TR4/Trap/tr4_spikeball.cpp @@ -1,43 +1,46 @@ #include "framework.h" -#include "tr4_spikeball.h" -#include "Specific/level.h" -#include "Game/control/control.h" +#include "Objects/TR4/Trap/tr4_spikeball.h" + #include "Game/animation.h" +#include "Game/control/control.h" #include "Game/items.h" +#include "Specific/level.h" -namespace TEN::Entities::TR4 +namespace TEN::Entities::Traps { - void SpikeballControl(short itemNumber) + void ControlSpikeball(short itemNumber) { - auto* item = &g_Level.Items[itemNumber]; + auto& item = g_Level.Items[itemNumber]; - if (TriggerActive(item)) + if (TriggerActive(&item)) { - int frameNumber = item->Animation.FrameNumber - GetAnimData(item).frameBase; + int frameNumber = item.Animation.FrameNumber - GetAnimData(item).frameBase; if ((frameNumber <= 14 || frameNumber >= 24) && (frameNumber < 138 || frameNumber > 140)) { if (frameNumber < 141) - *((int*)&item->ItemFlags[0]) = 0; + { + *((int*)&item.ItemFlags[0]) = 0; + } else { - item->ItemFlags[3] = 50; - *((int*)&item->ItemFlags[0]) = 0x7FF800; + item.ItemFlags[3] = 50; + *((int*)&item.ItemFlags[0]) = 0x7FF800; } } else { - item->ItemFlags[3] = 150; - *((int*)&item->ItemFlags[0]) = 0x7FF800; + item.ItemFlags[3] = 150; + *((int*)&item.ItemFlags[0]) = 0x7FF800; } - AnimateItem(item); + AnimateItem(&item); } else { - item->Animation.FrameNumber = GetAnimData(item).frameBase; - *((int*)&item->ItemFlags[0]) = 0; + item.Animation.FrameNumber = GetAnimData(item).frameBase; + *((int*)&item.ItemFlags[0]) = 0; } } } diff --git a/TombEngine/Objects/TR4/Trap/tr4_spikeball.h b/TombEngine/Objects/TR4/Trap/tr4_spikeball.h index 3603e843d..38a001cee 100644 --- a/TombEngine/Objects/TR4/Trap/tr4_spikeball.h +++ b/TombEngine/Objects/TR4/Trap/tr4_spikeball.h @@ -1,6 +1,6 @@ #pragma once -namespace TEN::Entities::TR4 +namespace TEN::Entities::Traps { - void SpikeballControl(short itemNumber); + void ControlSpikeball(short itemNumber); } diff --git a/TombEngine/Objects/TR4/Trap/tr4_stargate.cpp b/TombEngine/Objects/TR4/Trap/tr4_stargate.cpp index 4e2956f2e..606015596 100644 --- a/TombEngine/Objects/TR4/Trap/tr4_stargate.cpp +++ b/TombEngine/Objects/TR4/Trap/tr4_stargate.cpp @@ -4,7 +4,7 @@ #include "Game/animation.h" #include "Game/collision/collide_item.h" #include "Game/collision/collide_room.h" -#include "Game/collision/sphere.h" +#include "Game/collision/Sphere.h" #include "Game/control/control.h" #include "Game/effects/effects.h" #include "Game/items.h" @@ -12,6 +12,8 @@ #include "Sound/sound.h" #include "Specific/level.h" +using namespace TEN::Collision::Sphere; + namespace TEN::Entities::Traps { constexpr auto STARGATE_HARM_DAMAGE = 100; @@ -61,12 +63,13 @@ namespace TEN::Entities::Traps if (!TestBoundsCollide(item, laraItem, coll->Setup.Radius)) return; - if (TestCollision(item, laraItem) && + if (HandleItemSphereCollision(*item, *laraItem) && TriggerActive(item) && item->Animation.FrameNumber > GetAnimData(item).frameBase + 20 && // Hardcoded frame range. item->Animation.FrameNumber < GetAnimData(item).frameBase + 60) { // Blades deal damage cumulatively. + auto spheres = item->GetSpheres(); for (int i = 0; i < StargateHarmJoints.size(); i++) { if (item->TouchBits.Test(StargateHarmJoints[i])) @@ -74,7 +77,7 @@ namespace TEN::Entities::Traps DoDamage(laraItem, STARGATE_HARM_DAMAGE); DoBloodSplat( (GetRandomControl() & 0x3F) + laraItem->Pose.Position.x - 32, - (GetRandomControl() & 0x1F) + CreatureSpheres[i].y - 16, + (GetRandomControl() & 0x1F) + spheres[i].Center.y - 16, (GetRandomControl() & 0x3F) + laraItem->Pose.Position.z - 32, (GetRandomControl() & 3) + 2, GetRandomControl() * 2, diff --git a/TombEngine/Objects/TR4/Trap/tr4_teethspike.cpp b/TombEngine/Objects/TR4/Trap/tr4_teethspike.cpp index fbd65574d..a07bcac0d 100644 --- a/TombEngine/Objects/TR4/Trap/tr4_teethspike.cpp +++ b/TombEngine/Objects/TR4/Trap/tr4_teethspike.cpp @@ -1,34 +1,38 @@ #include "framework.h" #include "Objects/TR4/Trap/tr4_teethspike.h" +#include "Game/camera.h" #include "Game/collision/collide_item.h" #include "Game/collision/collide_room.h" +#include "Game/collision/Point.h" #include "Game/effects/tomb4fx.h" #include "Game/items.h" #include "Game/Lara/lara.h" #include "Game/Setup.h" +#include "Math/Math.h" #include "Sound/sound.h" #include "Specific/level.h" -#include "Math/Math.h" -namespace TEN::Entities::TR4 +using namespace TEN::Collision::Point; + +namespace TEN::Entities::Traps { - constexpr auto TEETH_SPIKE_HARM_DAMAGE_CONSTANT = 8; - constexpr auto TEETH_SPIKE_HARM_DAMAGE_EMERGING = 30; - constexpr auto TEETH_SPIKES_DEFAULT_INTERVAL = 64; + constexpr auto TEETH_SPIKE_HARM_DAMAGE_CONSTANT = 8; + constexpr auto TEETH_SPIKE_HARM_DAMAGE_EMERGING = 30; + constexpr auto TEETH_SPIKES_DEFAULT_INTERVAL = 64; constexpr auto TEETH_SPIKE_BOUNDS_TOLERANCE_RATIO = 0.95f; void InitializeTeethSpikes(short itemNumber) { - auto* item = &g_Level.Items[itemNumber]; + auto& item = g_Level.Items[itemNumber]; // Set mutators to EulerAngles identity by default. - for (auto& mutator : item->Model.Mutators) + for (auto& mutator : item.Model.Mutators) mutator.Scale.y = 0.0f; - item->Status = ITEM_INVISIBLE; - item->ItemFlags[0] = 1024; - item->ItemFlags[2] = 0; + item.Status = ITEM_INVISIBLE; + item.ItemFlags[0] = 1024; + item.ItemFlags[2] = 0; } ContainmentType TestBoundsCollideTeethSpikes(ItemInfo* item, ItemInfo* collidingItem) @@ -54,94 +58,96 @@ namespace TEN::Entities::TR4 void ControlTeethSpikes(short itemNumber) { - auto* item = &g_Level.Items[itemNumber]; + auto& item = g_Level.Items[itemNumber]; - if (TriggerActive(item) && item->ItemFlags[2] == 0) + if (TriggerActive(&item) && item.ItemFlags[2] == 0) { // Get current item bounds and radius. - const auto& bounds = GetBestFrame(*item).BoundingBox; + const auto& bounds = GetBestFrame(item).BoundingBox; int radius = std::max(abs(bounds.X2 - bounds.X1), abs(bounds.Z2 - bounds.Z1)) / 2; // Play sound only if spikes are just emerging. - if (item->ItemFlags[0] == 1024 && item->TriggerFlags != 1) - SoundEffect(SFX_TR4_TEETH_SPIKES, &item->Pose); + if (item.ItemFlags[0] == 1024 && item.TriggerFlags != 1) + SoundEffect(SFX_TR4_TEETH_SPIKES, &item.Pose); // Immediately set spike state to fully protruded if flag is set. - if (item->TriggerFlags == 1) - item->ItemFlags[1] = 5120; + if (item.TriggerFlags == 1) + item.ItemFlags[1] = 5120; // Kill enemies. - item->Animation.Velocity.z = VEHICLE_COLLISION_TERMINAL_VELOCITY; - DoTeethSpikeCollision(item, radius * TEETH_SPIKE_BOUNDS_TOLERANCE_RATIO); + item.Animation.Velocity.z = VEHICLE_COLLISION_TERMINAL_VELOCITY; + DoTeethSpikeCollision(&item, radius * TEETH_SPIKE_BOUNDS_TOLERANCE_RATIO); - item->Status = ITEM_ACTIVE; + item.Status = ITEM_ACTIVE; - auto intersection = TestBoundsCollideTeethSpikes(item, LaraItem); + auto intersection = TestBoundsCollideTeethSpikes(&item, LaraItem); if (LaraItem->Animation.ActiveState != LS_DEATH && intersection != ContainmentType::DISJOINT) { - // Calculate spike angle to the horizon. If angle is upward, impale Lara. - auto normal = Vector3::Transform(Vector3::UnitY, item->Pose.Orientation.ToRotationMatrix()); + // Calculate spike angle to horizon. If angle is upward, impale player. + auto normal = Vector3::Transform(Vector3::UnitY, item.Pose.Orientation.ToRotationMatrix()); float dot = Vector3::UnitX.Dot(normal); float angle = acos(dot / sqrt(normal.LengthSquared() * Vector3::UnitX.LengthSquared())); - const auto& laraBounds = GetBestFrame(*LaraItem).BoundingBox; + const auto& playerBounds = GetBestFrame(*LaraItem).BoundingBox; int bloodCount = 0; - // Spikes are upward and Lara is jumping, or spikes have just emerged - impale. - if ((item->ItemFlags[0] >= 1024 || LaraItem->Animation.IsAirborne) && + // Spikes are upward and player is jumping, or spikes have just emerged - impale. + if ((item.ItemFlags[0] >= 1024 || LaraItem->Animation.IsAirborne) && (angle > PI * 0.25f && angle < PI * 0.75f)) { - if (LaraItem->Animation.Velocity.y > 6.0f || item->ItemFlags[0] > 1024) + if (LaraItem->Animation.Velocity.y > 6.0f || item.ItemFlags[0] > 1024) { LaraItem->HitPoints = -1; bloodCount = 20; } } - // Spikes are emerging or already fully protruded (in latter case, only damage Lara if she runs). - else if ((item->TriggerFlags != 1) || LaraItem->Animation.Velocity.z >= 30.0f) + // Spikes emerging or already fully protruded (in latter case, only damage player if running). + else if ((item.TriggerFlags != 1) || LaraItem->Animation.Velocity.z >= 30.0f) { - int damage = item->ItemFlags[0] == 1024 ? TEETH_SPIKE_HARM_DAMAGE_EMERGING : TEETH_SPIKE_HARM_DAMAGE_CONSTANT; + int damage = item.ItemFlags[0] == 1024 ? TEETH_SPIKE_HARM_DAMAGE_EMERGING : TEETH_SPIKE_HARM_DAMAGE_CONSTANT; DoDamage(LaraItem, damage); bloodCount = (GetRandomControl() & 3) + 2; } - // Spikes are retracting; do nothing. + // Spikes retracting; do nothing. else + { bloodCount = 0; + } int y1, y2; - int yTop = laraBounds.Y1 + LaraItem->Pose.Position.y; - int yBottom = laraBounds.Y2 + LaraItem->Pose.Position.y; + int yTop = playerBounds.Y1 + LaraItem->Pose.Position.y; + int yBottom = playerBounds.Y2 + LaraItem->Pose.Position.y; - // Spikes are downward; move blood origin to top. + // Spikes pointing downward; move blood origin to top. if (angle < PI * 0.125f || angle > PI * 0.825f) { y1 = -bounds.Y2; y2 = -bounds.Y1; } - // Spikes are upward; leave origin as is. + // Spikes pointing upward; leave origin as is. else { y1 = bounds.Y1; y2 = bounds.Y2; } - if (yTop < y1 + item->Pose.Position.y) - yTop = y1 + item->Pose.Position.y; - if (yBottom > y2 + item->Pose.Position.y) - yBottom = y2 + item->Pose.Position.y; + if (yTop < y1 + item.Pose.Position.y) + yTop = y1 + item.Pose.Position.y; + if (yBottom > y2 + item.Pose.Position.y) + yBottom = y2 + item.Pose.Position.y; int dy = (abs(yTop - yBottom)) + 1; - // Increase blood if spikes are protruding from the side. + // Increase blood if spikes are protruding from side. if ((angle > PI * 0.125f && angle < PI * 0.375f) || (angle > PI * 0.625f && angle < PI * 0.750f)) { bloodCount >>= 1; } - for (size_t i = 0; i < bloodCount; i++) + for (int i = 0; i < bloodCount; i++) { int dx = LaraItem->Pose.Position.x + (GetRandomControl() & 127) - 64; int dz = LaraItem->Pose.Position.z + (GetRandomControl() & 127) - 64; @@ -150,73 +156,85 @@ namespace TEN::Entities::TR4 if (LaraItem->HitPoints <= 0 && Lara.Context.Vehicle == NO_VALUE) { - int heightFromFloor = GetCollision(LaraItem).Position.Floor - LaraItem->Pose.Position.y; + int heightFromFloor = GetPointCollision(*LaraItem).GetFloorHeight() - LaraItem->Pose.Position.y; - if (item->Pose.Position.y >= LaraItem->Pose.Position.y && heightFromFloor < CLICK(1) && - intersection == ContainmentType::CONTAINS) + if (item.Pose.Position.y >= LaraItem->Pose.Position.y && heightFromFloor < CLICK(1)) { SetAnimation(LaraItem, LA_SPIKE_DEATH); LaraItem->Animation.IsAirborne = false; + + Camera.flags = CF_FOLLOW_CENTER; + Camera.targetAngle = ANGLE(-150.0f); + Camera.targetElevation = ANGLE(-25.0f); + Camera.targetDistance = BLOCK(2); } } } - item->ItemFlags[0] += 128; - item->ItemFlags[1] += item->ItemFlags[0]; - if (item->ItemFlags[1] >= 5120) + item.ItemFlags[0] += 128; + item.ItemFlags[1] += item.ItemFlags[0]; + if (item.ItemFlags[1] >= 5120) { - item->ItemFlags[1] = 5120; - if (item->ItemFlags[0] <= 1024) + item.ItemFlags[1] = 5120; + if (item.ItemFlags[0] <= 1024) { - item->ItemFlags[0] = 0; - if (item->TriggerFlags != 1 && LaraItem->HitPoints > 0) + item.ItemFlags[0] = 0; + if (item.TriggerFlags != 1 && LaraItem->HitPoints > 0) { - int customInterval = item->TriggerFlags; - item->ItemFlags[2] = customInterval ? customInterval : TEETH_SPIKES_DEFAULT_INTERVAL; + int customInterval = item.TriggerFlags; + item.ItemFlags[2] = customInterval ? customInterval : TEETH_SPIKES_DEFAULT_INTERVAL; } } else - item->ItemFlags[0] = -item->ItemFlags[0] >> 1; + { + item.ItemFlags[0] = -item.ItemFlags[0] >> 1; + } } } - else if (TriggerActive(item)) + else if (TriggerActive(&item)) { - item->ItemFlags[0] += (item->ItemFlags[0] >> 3) + 32; - item->ItemFlags[1] -= item->ItemFlags[0]; - if (item->ItemFlags[1] <= 0) + item.ItemFlags[0] += (item.ItemFlags[0] >> 3) + 32; + item.ItemFlags[1] -= item.ItemFlags[0]; + if (item.ItemFlags[1] <= 0) { - item->ItemFlags[0] = 1024; - item->ItemFlags[1] = 0; - item->Status = ITEM_INVISIBLE; + item.ItemFlags[0] = 1024; + item.ItemFlags[1] = 0; + item.Status = ITEM_INVISIBLE; } - if (item->TriggerFlags != 2) + if (item.TriggerFlags != 2) { - if (item->ItemFlags[2]) - item->ItemFlags[2]--; + if (item.ItemFlags[2]) + item.ItemFlags[2]--; } else - item->ItemFlags[2] = 1; - } - else if (!item->Timer) - { - if (item->ItemFlags[1] > 0) { - item->ItemFlags[0] += (item->ItemFlags[0] >> 3) + 32; - item->ItemFlags[1] -= item->ItemFlags[0]; - if (item->ItemFlags[1] < 0) - item->ItemFlags[1] = 0; + item.ItemFlags[2] = 1; + } + } + else if (!item.Timer) + { + if (item.ItemFlags[1] > 0) + { + item.ItemFlags[0] += (item.ItemFlags[0] >> 3) + 32; + item.ItemFlags[1] -= item.ItemFlags[0]; + if (item.ItemFlags[1] < 0) + item.ItemFlags[1] = 0; } } // Update bone mutators. - for (auto& mutator : item->Model.Mutators) + for (auto& mutator : item.Model.Mutators) { - float scale = (float)item->ItemFlags[1] / 4096.0f; + float scale = (float)item.ItemFlags[1] / 4096.0f; if (scale > 0.0f) + { mutator.Scale = Vector3(1.0f, scale, 1.0f); + } else + { mutator.Scale = Vector3::Zero; + } } } } diff --git a/TombEngine/Objects/TR4/Trap/tr4_teethspike.h b/TombEngine/Objects/TR4/Trap/tr4_teethspike.h index 8f075cde3..5bcc50302 100644 --- a/TombEngine/Objects/TR4/Trap/tr4_teethspike.h +++ b/TombEngine/Objects/TR4/Trap/tr4_teethspike.h @@ -1,6 +1,6 @@ #pragma once -namespace TEN::Entities::TR4 +namespace TEN::Entities::Traps { void InitializeTeethSpikes(short itemNumber); void ControlTeethSpikes(short itemNumber); diff --git a/TombEngine/Objects/TR4/Vehicles/jeep.cpp b/TombEngine/Objects/TR4/Vehicles/jeep.cpp index 02bee3c80..70258a975 100644 --- a/TombEngine/Objects/TR4/Vehicles/jeep.cpp +++ b/TombEngine/Objects/TR4/Vehicles/jeep.cpp @@ -4,6 +4,7 @@ #include "Game/animation.h" #include "Game/camera.h" #include "Game/collision/collide_item.h" +#include "Game/collision/Point.h" #include "Game/effects/effects.h" #include "Game/effects/simple_particle.h" #include "Game/effects/tomb4fx.h" @@ -22,6 +23,7 @@ #include "Specific/Input/Input.h" #include "Specific/level.h" +using namespace TEN::Collision::Point; using namespace TEN::Input; namespace TEN::Entities::Vehicles @@ -372,16 +374,16 @@ namespace TEN::Entities::Vehicles int y = jeepItem->Pose.Position.y; int z = jeepItem->Pose.Position.z - JEEP_DISMOUNT_DISTANCE * phd_cos(angle); - auto probe = GetCollision(x, y, z, jeepItem->RoomNumber); + auto probe = GetPointCollision(Vector3i(x, y, z), jeepItem->RoomNumber); - if (probe.Position.FloorSlope || probe.Position.Floor == NO_HEIGHT) + if (probe.IsSteepFloor() || probe.GetFloorHeight() == NO_HEIGHT) return false; - if (abs(probe.Position.Floor - jeepItem->Pose.Position.y) > BLOCK(1 / 2.0f)) + if (abs(probe.GetFloorHeight() - jeepItem->Pose.Position.y) > BLOCK(1 / 2.0f)) return false; - if ((probe.Position.Ceiling - jeepItem->Pose.Position.y) > -LARA_HEIGHT || - (probe.Position.Floor - probe.Position.Ceiling) < LARA_HEIGHT) + if ((probe.GetCeilingHeight() - jeepItem->Pose.Position.y) > -LARA_HEIGHT || + (probe.GetFloorHeight() - probe.GetCeilingHeight()) < LARA_HEIGHT) { return false; } diff --git a/TombEngine/Objects/TR4/Vehicles/motorbike.cpp b/TombEngine/Objects/TR4/Vehicles/motorbike.cpp index 184577689..8ccee05d0 100644 --- a/TombEngine/Objects/TR4/Vehicles/motorbike.cpp +++ b/TombEngine/Objects/TR4/Vehicles/motorbike.cpp @@ -5,6 +5,7 @@ #include "Game/animation.h" #include "Game/camera.h" #include "Game/collision/collide_item.h" +#include "Game/collision/Point.h" #include "Game/control/control.h" #include "Game/effects/effects.h" #include "Game/effects/simple_particle.h" @@ -23,6 +24,7 @@ #include "Specific/level.h" using std::vector; +using namespace TEN::Collision::Point; using namespace TEN::Input; using namespace TEN::Math::Random; @@ -259,7 +261,7 @@ namespace TEN::Entities::Vehicles x = 0; z = 0; - int floorHeight = GetCollision(old->x, pos->y, pos->z, motorbikeItem->RoomNumber).Position.Floor; + int floorHeight = GetPointCollision(Vector3i(old->x, pos->y, pos->z), motorbikeItem->RoomNumber).GetFloorHeight(); if (floorHeight < (old->y - CLICK(1))) { if (pos->z > old->z) @@ -268,7 +270,7 @@ namespace TEN::Entities::Vehicles z = BLOCK(1) - shiftZ; } - floorHeight = GetCollision(pos->x, pos->y, old->z, motorbikeItem->RoomNumber).Position.Floor; + floorHeight = GetPointCollision(Vector3i(pos->x, pos->y, old->z), motorbikeItem->RoomNumber).GetFloorHeight(); if (floorHeight < (old->y - CLICK(1))) { if (pos->x > old->x) @@ -606,7 +608,7 @@ namespace TEN::Entities::Vehicles motorbike->MomentumAngle = motorbikeItem->Pose.Orientation.y; } - floorHeight = GetCollision(motorbikeItem).Position.Floor; + floorHeight = GetPointCollision(*motorbikeItem).GetFloorHeight(); if (motorbikeItem->Pose.Position.y >= floorHeight) speed = motorbikeItem->Animation.Velocity.z * phd_cos(motorbikeItem->Pose.Orientation.x); else @@ -702,7 +704,7 @@ namespace TEN::Entities::Vehicles if (rot1) rot2 = rot1; - floorHeight = GetCollision(motorbikeItem).Position.Floor; + floorHeight = GetPointCollision(*motorbikeItem).GetFloorHeight(); if (floorHeight < (motorbikeItem->Pose.Position.y - CLICK(1))) DoMotorbikeShift(motorbikeItem, (Vector3i*)&motorbikeItem->Pose, &oldPos); @@ -745,18 +747,18 @@ namespace TEN::Entities::Vehicles auto* lara = GetLaraInfo(laraItem); short angle = motorbikeItem->Pose.Orientation.y + ANGLE(90.0f); - auto collResult = GetCollision(motorbikeItem, angle, MOTORBIKE_RADIUS); + auto collResult = GetPointCollision(*motorbikeItem, angle, MOTORBIKE_RADIUS); - if (collResult.Position.FloorSlope || collResult.Position.Floor == NO_HEIGHT) // Was previously set to -NO_HEIGHT by TokyoSU -- Lwmte 23.08.21 + if (collResult.IsSteepFloor() || collResult.GetFloorHeight() == NO_HEIGHT) // Was previously set to -NO_HEIGHT by TokyoSU -- Lwmte 23.08.21 return false; - if (abs(collResult.Position.Floor - motorbikeItem->Pose.Position.y) > CLICK(1)) + if (abs(collResult.GetFloorHeight() - motorbikeItem->Pose.Position.y) > CLICK(1)) return false; - if ((collResult.Position.Ceiling - motorbikeItem->Pose.Position.y) > -LARA_HEIGHT) + if ((collResult.GetCeilingHeight() - motorbikeItem->Pose.Position.y) > -LARA_HEIGHT) return false; - if ((collResult.Position.Floor - collResult.Position.Ceiling) < LARA_HEIGHT) + if ((collResult.GetFloorHeight() - collResult.GetCeilingHeight()) < LARA_HEIGHT) return false; return true; @@ -1162,7 +1164,7 @@ namespace TEN::Entities::Vehicles int heightFrontRight = GetVehicleHeight(motorbikeItem, MOTORBIKE_FRONT, CLICK(0.5f), true, &frontRight); int heightFrontMiddle = GetVehicleHeight(motorbikeItem, -MOTORBIKE_FRONT, 0, true, &frontMiddle); - auto probe = GetCollision(motorbikeItem); + auto probe = GetPointCollision(*motorbikeItem); TestTriggers(motorbikeItem, true); TestTriggers(motorbikeItem, false); @@ -1187,7 +1189,7 @@ namespace TEN::Entities::Vehicles DrawMotorbikeLight(motorbikeItem); motorbikeItem->MeshBits.Set(MotorbikeHeadLightJoints); - drive = MotorbikeUserControl(motorbikeItem, laraItem, probe.Position.Floor, &pitch); + drive = MotorbikeUserControl(motorbikeItem, laraItem, probe.GetFloorHeight(), &pitch); HandleVehicleSpeedometer(motorbikeItem->Animation.Velocity.z, MOTORBIKE_ACCEL_MAX / (float)VEHICLE_VELOCITY_SCALE); } else @@ -1224,14 +1226,14 @@ namespace TEN::Entities::Vehicles if (motorbike->Velocity < MOTORBIKE_ACCEL_1) DrawMotorBikeSmoke(motorbikeItem, laraItem); - motorbikeItem->Floor = probe.Position.Floor; + motorbikeItem->Floor = probe.GetFloorHeight(); int rotation = motorbike->Velocity / 4; motorbike->LeftWheelRotation -= rotation; motorbike->RightWheelsRotation -= rotation; int newY = motorbikeItem->Pose.Position.y; - motorbikeItem->Animation.Velocity.y = DoMotorBikeDynamics(probe.Position.Floor, motorbikeItem->Animation.Velocity.y, &motorbikeItem->Pose.Position.y, 0); + motorbikeItem->Animation.Velocity.y = DoMotorBikeDynamics(probe.GetFloorHeight(), motorbikeItem->Animation.Velocity.y, &motorbikeItem->Pose.Position.y, 0); motorbike->Velocity = DoVehicleWaterMovement(motorbikeItem, laraItem, motorbike->Velocity, MOTORBIKE_RADIUS, &motorbike->TurnRate, MOTORBIKE_WAKE_OFFSET); int r1 = (frontRight.y + frontLeft.y) / 2; @@ -1265,10 +1267,10 @@ namespace TEN::Entities::Vehicles motorbikeItem->Pose.Orientation.x += (xRot - motorbikeItem->Pose.Orientation.x) / 4; motorbikeItem->Pose.Orientation.z += (zRot - motorbikeItem->Pose.Orientation.z) / 4; - if (probe.RoomNumber != motorbikeItem->RoomNumber) + if (probe.GetRoomNumber() != motorbikeItem->RoomNumber) { - ItemNewRoom(lara->Context.Vehicle, probe.RoomNumber); - ItemNewRoom(laraItem->Index, probe.RoomNumber); + ItemNewRoom(lara->Context.Vehicle, probe.GetRoomNumber()); + ItemNewRoom(laraItem->Index, probe.GetRoomNumber()); } laraItem->Pose = motorbikeItem->Pose; diff --git a/TombEngine/Objects/TR4/tr4_objects.cpp b/TombEngine/Objects/TR4/tr4_objects.cpp index b54171260..9ce44ab8d 100644 --- a/TombEngine/Objects/TR4/tr4_objects.cpp +++ b/TombEngine/Objects/TR4/tr4_objects.cpp @@ -11,7 +11,7 @@ #include "Specific/level.h" // Creatures -#include "Objects/TR4/Entity/Wraith.h" // OFF +#include "Objects/TR4/Entity/Wraith.h" #include "Objects/TR4/Entity/tr4_enemy_jeep.h" #include "Objects/TR4/Entity/tr4_ahmet.h" // OK #include "Objects/TR4/Entity/tr4_baddy.h" // OK @@ -410,7 +410,7 @@ namespace TEN::Entities if (obj->loaded) { obj->Initialize = InitializeSentryGun; - obj->control = SentryGunControl; + obj->control = ControlSentryGun; obj->collision = CreatureCollision; obj->shadowType = ShadowMode::All; obj->damageType = DamageMode::None; @@ -420,9 +420,8 @@ namespace TEN::Entities obj->intelligent = true; obj->explodableMeshbits = 0x40; obj->SetBoneRotationFlags(0, ROT_X | ROT_Y); - obj->SetBoneRotationFlags(1, ROT_X | ROT_X); - obj->SetBoneRotationFlags(2, ROT_X | ROT_Z); - obj->SetBoneRotationFlags(3, ROT_X | ROT_Z); + obj->SetBoneRotationFlags(2, ROT_Z); + obj->SetBoneRotationFlags(3, ROT_Z); obj->SetHitEffect(true); } @@ -627,7 +626,7 @@ namespace TEN::Entities if (obj->loaded) { obj->Initialize = InitializeEnemyJeep; - obj->control = EnemyJeepControl; + obj->control = ControlEnemyJeep; obj->collision = CreatureCollision; obj->shadowType = ShadowMode::All; obj->HitPoints = 40; @@ -635,8 +634,8 @@ namespace TEN::Entities obj->radius = 512; obj->intelligent = true; obj->damageType = DamageMode::None; // NOTE: Prevents enemy jeep from being killed with skidoo gun or something like that. - obj->LotType = LotType::HumanPlusJumpAndMonkey; - obj->SetBoneRotationFlags(8, ROT_X); + obj->LotType = LotType::EnemyJeep; + obj->SetBoneRotationFlags(8, ROT_X); // Wheel rotation. obj->SetBoneRotationFlags(9, ROT_X); obj->SetBoneRotationFlags(11, ROT_X); obj->SetBoneRotationFlags(12, ROT_X); @@ -768,7 +767,7 @@ namespace TEN::Entities obj = &Objects[ID_CHAIN]; if (obj->loaded) { - obj->control = ChainControl; + obj->control = ControlChain; obj->collision = GenericSphereBoxCollision; obj->SetHitEffect(true); } @@ -776,7 +775,8 @@ namespace TEN::Entities obj = &Objects[ID_PLOUGH]; if (obj->loaded) { - obj->control = PloughControl; + obj->Initialize = InitializePlough; + obj->control = ControlPlough; obj->collision = GenericSphereBoxCollision; obj->SetHitEffect(true); } @@ -784,8 +784,8 @@ namespace TEN::Entities obj = &Objects[ID_CATWALK_BLADE]; if (obj->loaded) { - obj->control = CatwalkBladeControl; - obj->collision = BladeCollision; + obj->control = ControlCatwalkBlade; + obj->collision = CollideBlade; obj->SetHitEffect(true); } @@ -793,7 +793,7 @@ namespace TEN::Entities if (obj->loaded) { obj->Initialize = InitializeSethBlade; - obj->control = SethBladeControl; + obj->control = ControlSethBlade; obj->collision = GenericSphereBoxCollision; obj->SetHitEffect(true); } @@ -801,15 +801,16 @@ namespace TEN::Entities obj = &Objects[ID_PLINTH_BLADE]; if (obj->loaded) { - obj->control = PlinthBladeControl; - obj->collision = BladeCollision; + obj->control = ControlPlinthBlade; + obj->collision = CollideBlade; obj->SetHitEffect(true); } obj = &Objects[ID_BIRD_BLADE]; if (obj->loaded) { - obj->control = BirdBladeControl; + obj->Initialize = InitializeBirdBlade; + obj->control = ControlBirdBlade; obj->collision = GenericSphereBoxCollision; obj->SetHitEffect(true); } @@ -818,39 +819,22 @@ namespace TEN::Entities if (obj->loaded) { obj->Initialize = InitializeJobySpikes; - obj->control = JobySpikesControl; - obj->collision = GenericSphereBoxCollision; + obj->control = ControlJobySpikes; obj->SetHitEffect(true); } obj = &Objects[ID_MOVING_BLADE]; if (obj->loaded) { - obj->control = MovingBladeControl; - obj->collision = BladeCollision; + obj->control = ControlMovingBlade; + obj->collision = CollideBlade; obj->SetHitEffect(true); } obj = &Objects[ID_SPIKEBALL]; if (obj->loaded) { - obj->control = SpikeballControl; - obj->collision = GenericSphereBoxCollision; - obj->SetHitEffect(true); - } - - obj = &Objects[ID_CHAIN]; - if (obj->loaded) - { - obj->control = ChainControl; - obj->collision = GenericSphereBoxCollision; - obj->SetHitEffect(true); - } - - obj = &Objects[ID_PLOUGH]; - if (obj->loaded) - { - obj->control = PloughControl; + obj->control = ControlSpikeball; obj->collision = GenericSphereBoxCollision; obj->SetHitEffect(true); } @@ -858,7 +842,8 @@ namespace TEN::Entities obj = &Objects[ID_FLOOR_4BLADES]; if (obj->loaded) { - obj->control = FourBladesControl; + obj->Initialize = InitializeFourBlades; + obj->control = ControlFourBlades; obj->collision = GenericSphereBoxCollision; obj->SetHitEffect(true); } @@ -866,7 +851,8 @@ namespace TEN::Entities obj = &Objects[ID_CEILING_4BLADES]; if (obj->loaded) { - obj->control = FourBladesControl; + obj->Initialize = InitializeFourBlades; + obj->control = ControlFourBlades; obj->collision = GenericSphereBoxCollision; obj->SetHitEffect(true); } @@ -883,8 +869,8 @@ namespace TEN::Entities if (obj->loaded) { obj->Initialize = InitializeSlicerDicer; - obj->control = SlicerDicerControl; - obj->collision = BladeCollision; + obj->control = ControlSlicerDicer; + obj->collision = CollideBlade; obj->SetHitEffect(true); } @@ -892,8 +878,8 @@ namespace TEN::Entities if (obj->loaded) { obj->Initialize = InitializeMine; - obj->control = MineControl; - obj->collision = MineCollision; + obj->control = ControlMine; + obj->collision = CollideMine; } obj = &Objects[ID_SPIKY_WALL]; @@ -917,8 +903,8 @@ namespace TEN::Entities obj = &Objects[ID_COG]; if (obj->loaded) { - obj->control = CogControl; - obj->collision = CogCollision; + obj->control = ControlCog; + obj->collision = CollideCog; obj->SetHitEffect(true); } @@ -946,7 +932,7 @@ namespace TEN::Entities obj = &Objects[ID_HAMMER]; if (obj->loaded) { - obj->control = HammerControl; + obj->control = ControlHammer; obj->collision = GenericSphereBoxCollision; obj->SetHitEffect(true); } diff --git a/TombEngine/Objects/TR5/Emitter/tr5_bats_emitter.cpp b/TombEngine/Objects/TR5/Emitter/tr5_bats_emitter.cpp index a071909d9..865b71bd2 100644 --- a/TombEngine/Objects/TR5/Emitter/tr5_bats_emitter.cpp +++ b/TombEngine/Objects/TR5/Emitter/tr5_bats_emitter.cpp @@ -130,6 +130,8 @@ void UpdateBats() if (!bat->On) continue; + bat->StoreInterpolationData(); + if ((LaraItem->Effect.Type != EffectType::None || LaraItem->HitPoints <= 0) && bat->Counter > 90 && !(GetRandomControl() & 7)) diff --git a/TombEngine/Objects/TR5/Emitter/tr5_bats_emitter.h b/TombEngine/Objects/TR5/Emitter/tr5_bats_emitter.h index 9eeee5fb2..ab0a0b03c 100644 --- a/TombEngine/Objects/TR5/Emitter/tr5_bats_emitter.h +++ b/TombEngine/Objects/TR5/Emitter/tr5_bats_emitter.h @@ -16,8 +16,14 @@ struct BatData byte ZTarget; byte Flags; + + Matrix Transform = Matrix::Identity; + Matrix PrevTransform = Matrix::Identity; - Matrix Transform; + void StoreInterpolationData() + { + PrevTransform = Transform; + } }; extern int NextBat; diff --git a/TombEngine/Objects/TR5/Emitter/tr5_rats_emitter.cpp b/TombEngine/Objects/TR5/Emitter/tr5_rats_emitter.cpp index bb1a1403a..ceaa25115 100644 --- a/TombEngine/Objects/TR5/Emitter/tr5_rats_emitter.cpp +++ b/TombEngine/Objects/TR5/Emitter/tr5_rats_emitter.cpp @@ -96,7 +96,7 @@ void ClearRats() { ZeroMemory(Rats, NUM_RATS * sizeof(RatData)); NextRat = 0; - FlipEffect = -1; + FlipEffect = NO_VALUE; } } @@ -141,6 +141,8 @@ void UpdateRats() if (rat->On) { + rat->StoreInterpolationData(); + int oldX = rat->Pose.Position.x; int oldY = rat->Pose.Position.y; int oldZ = rat->Pose.Position.z; @@ -266,7 +268,7 @@ void UpdateRats() if (TestEnvironment(ENV_FLAG_WATER, room)) { - rat->Pose.Position.y = room->maxceiling + 50; + rat->Pose.Position.y = room->TopHeight + 50; rat->Velocity = 16; rat->VerticalVelocity = 0; @@ -274,16 +276,16 @@ void UpdateRats() { if (!(GetRandomControl() & 0xF)) SpawnRipple( - Vector3(rat->Pose.Position.x, room->maxceiling, rat->Pose.Position.z), + Vector3(rat->Pose.Position.x, room->TopHeight, rat->Pose.Position.z), rat->RoomNumber, Random::GenerateFloat(48.0f, 52.0f), (int)RippleFlags::SlowFade); } else { - AddWaterSparks(rat->Pose.Position.x, room->maxceiling, rat->Pose.Position.z, 16); + AddWaterSparks(rat->Pose.Position.x, room->TopHeight, rat->Pose.Position.z, 16); SpawnRipple( - Vector3(rat->Pose.Position.x, room->maxceiling, rat->Pose.Position.z), + Vector3(rat->Pose.Position.x, room->TopHeight, rat->Pose.Position.z), rat->RoomNumber, Random::GenerateFloat(48.0f, 52.0f), (int)RippleFlags::SlowFade); diff --git a/TombEngine/Objects/TR5/Emitter/tr5_rats_emitter.h b/TombEngine/Objects/TR5/Emitter/tr5_rats_emitter.h index 306f5604e..3f57a8f9f 100644 --- a/TombEngine/Objects/TR5/Emitter/tr5_rats_emitter.h +++ b/TombEngine/Objects/TR5/Emitter/tr5_rats_emitter.h @@ -14,7 +14,13 @@ struct RatData byte Flags; - Matrix Transform; + Matrix Transform = Matrix::Identity; + Matrix PrevTransform = Matrix::Identity; + + void StoreInterpolationData() + { + PrevTransform = Transform; + } }; extern int NextRat; diff --git a/TombEngine/Objects/TR5/Emitter/tr5_smoke_emitter.cpp b/TombEngine/Objects/TR5/Emitter/tr5_smoke_emitter.cpp index 7f5294edf..5a432a1e7 100644 --- a/TombEngine/Objects/TR5/Emitter/tr5_smoke_emitter.cpp +++ b/TombEngine/Objects/TR5/Emitter/tr5_smoke_emitter.cpp @@ -1,265 +1,399 @@ #include "framework.h" -#include "tr5_smoke_emitter.h" +#include "Objects/TR5/Emitter/tr5_smoke_emitter.h" -#include "Game/camera.h" -#include "Game/items.h" -#include "Game/effects/Bubble.h" -#include "Game/effects/effects.h" +#include "Game/collision/collide_room.h" #include "Game/control/control.h" #include "Game/control/trigger.h" -#include "Game/collision/collide_room.h" -#include "Specific/level.h" +#include "Game/effects/Bubble.h" +#include "Game/effects/effects.h" +#include "Game/items.h" #include "Math/Math.h" #include "Objects/objectslist.h" #include "Renderer/RendererEnums.h" +#include "Specific/clock.h" +#include "Specific/level.h" using namespace TEN::Effects::Bubble; +using namespace TEN::Math; -void InitializeSmokeEmitter(short itemNumber) +// NOTES: +// OCB 0: Default smoke behaviour. +// OCB < 0: Disable harm from the steam by setting the NoDamage flag in ItemFlags[3]. +// OCB != 0: Enable horizontal steam effect. +// OCB >> 4: Sets number of game frames it pauses between steam releases. Calculation: x = 16 * gameFrameCount. +// +// item.ItemFlags[0]: Timer in frame time for pause between steam releases +// item.ItemFlags[1]: Timer in frame time for active steam effect. +// item.ItemFlags[2]: Acceleration of steam particles. +// item.ItemFlags[3]: Smoke emiter flags. +// +// In underwater rooms: +// OCB 0: Intermitent bubble emission. +// OCB 1: Continuous bubble emission. +// OCB +2: Spawn large bubbles. +// +// item.ItemFlags[0]: Bubble count. +// item.ItemFlags[1]: Spawn a series of bubbles with no delay (flag). +// item.ItemFlags[2]: Bubble spawn radius on horizontal plane (default is 32). + +namespace TEN::Effects::SmokeEmitter { - auto* item = &g_Level.Items[itemNumber]; + constexpr auto SMOKE_VISIBILITY_DISTANCE_MAX = BLOCK(16); + constexpr auto SMOKE_ACCEL_MAX = BLOCK(4); + constexpr auto BUBBLE_DEFAULT_RADIUS = BLOCK(1 / 32.0f); - if (item->TriggerFlags == 111) + enum SmokeEmitterFlags { - if (item->Pose.Orientation.y > 0) - { - if (item->Pose.Orientation.y == ANGLE(90.0f)) - item->Pose.Position.x += CLICK(2); - } - else if (item->Pose.Orientation.y) - { - if (item->Pose.Orientation.y == -ANGLE(180.0f)) - item->Pose.Position.z -= CLICK(2); - else if (item->Pose.Orientation.y == -ANGLE(90.0f)) - item->Pose.Position.x -= CLICK(2); - } - else - item->Pose.Position.z += CLICK(2); - } - else if (item->ObjectNumber != ID_SMOKE_EMITTER) - return; - else if (item->TriggerFlags & 8) - { - item->ItemFlags[0] = item->TriggerFlags / 16; + NoDamage = 1 << 0 + }; - if (item->Pose.Orientation.y > 0) + static void SpawnSteamParticle(const ItemInfo& item, int currentAccel) + { + constexpr auto COLOR_BLACK = Color(0.4f, 0.4f, 0.4f); + constexpr auto COLOR_WHITE_START = Color(0.4f, 0.4f, 0.4f); + constexpr auto COLOR_WHITE_END = Color(0.25f, 0.25f, 0.25f); + constexpr auto LIFE_MAX = 24; + constexpr auto LIFE_MIN = 16; + constexpr auto PART_SIZE_MAX = 160.0f; + constexpr auto PART_SIZE_MIN = 128.0f; + constexpr auto FADE_SPEED_MAX = 9; + constexpr auto FADE_SPEED_MIN = 6; + constexpr auto DAMAGE_TIME_INTERVAL = 0.02f; + + static const auto SPHERE = BoundingSphere(Vector3::Zero, 32.0f); + + auto& part = *GetFreeParticle(); + part.on = true; + + if (item.ObjectNumber == ID_SMOKE_EMITTER_BLACK) { - if (item->Pose.Orientation.y == ANGLE(90.0f)) - item->Pose.Position.x += CLICK(1); + part.sR = COLOR_BLACK.R() * UCHAR_MAX; + part.sG = COLOR_BLACK.G() * UCHAR_MAX; + part.sB = COLOR_BLACK.B() * UCHAR_MAX; + part.dR = COLOR_BLACK.R() * UCHAR_MAX; + part.dG = COLOR_BLACK.G() * UCHAR_MAX; + part.dB = COLOR_BLACK.B() * UCHAR_MAX; + } + else if (item.ObjectNumber == ID_SMOKE_EMITTER_WHITE) + { + part.sR = COLOR_WHITE_START.R() * UCHAR_MAX; + part.sG = COLOR_WHITE_START.G() * UCHAR_MAX; + part.sB = COLOR_WHITE_START.B() * UCHAR_MAX; + part.dR = COLOR_WHITE_END.R() * UCHAR_MAX; + part.dG = COLOR_WHITE_END.G() * UCHAR_MAX; + part.dB = COLOR_WHITE_END.B() * UCHAR_MAX; } else { - if (item->Pose.Orientation.y == 0) - item->Pose.Position.z += CLICK(1); - else if (item->Pose.Orientation.y == -ANGLE(180.0f)) - item->Pose.Position.z -= CLICK(1); - else if (item->Pose.Orientation.y == -ANGLE(90.0f)) - item->Pose.Position.x -= CLICK(1); + unsigned char r = std::clamp(item.Model.Color.x / 2, 0.0f, 1.0f) * UCHAR_MAX; + unsigned char g = std::clamp(item.Model.Color.y / 2, 0.0f, 1.0f) * UCHAR_MAX; + unsigned char b = std::clamp(item.Model.Color.z / 2, 0.0f, 1.0f) * UCHAR_MAX; + + part.sR = r / 3; + part.sG = g / 3; + part.sB = b / 3; + part.dR = r; + part.dG = g; + part.dB = b; + } + + part.fadeToBlack = 6; + part.colFadeSpeed = Random::GenerateInt(FADE_SPEED_MIN, FADE_SPEED_MAX); + + if (item.ObjectNumber == ID_SMOKE_EMITTER_BLACK) + { + part.blendMode = BlendMode::Subtractive; + } + else + { + part.blendMode = BlendMode::Additive; } - if ((signed short)(item->TriggerFlags / 16) <= 0) + part.life = + part.sLife = Random::GenerateInt(LIFE_MIN, LIFE_MAX); + + auto pos = item.Pose.Position.ToVector3() + Random::GeneratePointInSphere(SPHERE); + part.x = pos.x; + part.y = pos.y; + part.z = pos.z; + + int accel = currentAccel; + if (currentAccel == SMOKE_ACCEL_MAX) + accel = Random::GenerateInt(SMOKE_ACCEL_MAX / 2, SMOKE_ACCEL_MAX - 1); + + int pitchAngle = item.Pose.Orientation.x; + int yawAngle = item.Pose.Orientation.y + ANGLE(180.0f); + + auto dir = Vector3::Zero;; + dir.x = phd_cos(pitchAngle) * phd_sin(yawAngle); + dir.y = phd_sin(pitchAngle); + dir.z = phd_cos(pitchAngle) * phd_cos(yawAngle); + + dir.Normalize(); + int offset = Random::GenerateInt(-8, 8); + part.xVel = (dir.x * accel) + offset; + part.yVel = (dir.y * accel) + offset; + part.zVel = (dir.z * accel) + offset; + + part.friction = 4; + part.flags = SP_SCALE | SP_DEF | SP_ROTATE | SP_EXPDEF; + + bool ignoreDamage = item.ItemFlags[3] & SmokeEmitterFlags::NoDamage; + if (!ignoreDamage && TestGlobalTimeInterval(DAMAGE_TIME_INTERVAL)) + part.flags |= SP_DAMAGE; + + part.rotAng = Random::GenerateAngle(ANGLE(0.0f), ANGLE(22.5f)); + part.rotAdd = Random::GenerateAngle(ANGLE(0.04f), ANGLE(0.08f)) * (Random::TestProbability(1 / 2.0f) ? 1 : -1); + + part.scalar = 2; + part.gravity = Random::GenerateFloat(-24.0f, -15.0f); + part.maxYvel = 0; + + float size = Random::GenerateFloat(PART_SIZE_MIN, PART_SIZE_MAX); + part.dSize = size; + part.sSize = + part.size = part.dSize / 2; + } + + static void SpawnSmokeEmitterParticle(const ItemInfo& item) + { + auto& part = *GetFreeParticle(); + part.on = true; + + part.sR = 0; + part.sG = 0; + part.sB = 0; + + if (item.ObjectNumber == ID_SMOKE_EMITTER_BLACK) { - item->ItemFlags[2] = 4096; - item->TriggerFlags |= 4; + part.dR = 0.4f * UCHAR_MAX; + part.dG = 0.4f * UCHAR_MAX; + part.dB = 0.4f * UCHAR_MAX; + } + else if (item.ObjectNumber == ID_SMOKE_EMITTER_WHITE) + { + part.dR = 0.25f * UCHAR_MAX; + part.dG = 0.25f * UCHAR_MAX; + part.dB = 0.25f * UCHAR_MAX; + } + else + { + unsigned char r = std::clamp(item.Model.Color.x / 2.0f, 0.0f, 1.0f) * UCHAR_MAX; + unsigned char g = std::clamp(item.Model.Color.y / 2.0f, 0.0f, 1.0f) * UCHAR_MAX; + unsigned char b = std::clamp(item.Model.Color.z / 2.0f, 0.0f, 1.0f) * UCHAR_MAX; + + part.dR = r; + part.dG = g; + part.dB = b; + } + + part.fadeToBlack = 16; + part.colFadeSpeed = Random::GenerateInt(8, 11); + + if (item.ObjectNumber == ID_SMOKE_EMITTER_BLACK) + { + part.blendMode = BlendMode::Subtractive; + } + else + { + part.blendMode = BlendMode::Additive; + } + + part.sLife = part.life = Random::GenerateInt(28, 35); + + part.x = item.Pose.Position.x + Random::GenerateInt(-32, 32); + part.y = item.Pose.Position.y + Random::GenerateInt(-32, 32); + part.z = item.Pose.Position.z + Random::GenerateInt(-32, 32); + + part.xVel = Random::GenerateInt(-128, 128); + part.yVel = Random::GenerateInt(-16, 0); + part.zVel = Random::GenerateInt(-128, 128); + + part.friction = 3; + part.flags = SP_SCALE | SP_DEF | SP_ROTATE | SP_EXPDEF; + + if (TestEnvironment(RoomEnvFlags::ENV_FLAG_OUTSIDE, item.RoomNumber)) + part.flags |= SP_WIND; + + part.rotAng = Random::GenerateInt(0, 4095); + + if (Random::TestProbability(1 / 2.0f)) + { + part.rotAdd = Random::GenerateInt(-15, -7); + } + else + { + part.rotAdd = Random::GenerateInt(7, 15); + } + + part.scalar = 2; + part.gravity = Random::GenerateInt(-24, -15); + part.maxYvel = Random::GenerateInt(-15, -7); + + float size = Random::GenerateFloat(128.0f, 160.0f); + part.dSize = size; + part.sSize = + part.size = size / 4; + + if (item.ObjectNumber == ID_SMOKE_EMITTER) + { + part.gravity /= 2; + part.yVel /= 2; + part.maxYvel /= 2; + part.life += 16; + part.sLife += 16; } } - else if (g_Level.Rooms[item->RoomNumber].flags & 1 && item->TriggerFlags == 1) + + static void SpawnSmokeEmitterBubble(const ItemInfo& item, int radius, bool spawnLargeBubbles) { - item->ItemFlags[0] = 20; - item->ItemFlags[1] = 1; - } -} + auto pos = item.Pose.Position.ToVector3() + Vector3(Random::GenerateInt(-radius, radius), Random::GenerateInt(-16, 16), Random::GenerateInt(-radius, radius)); -void SmokeEmitterControl(short itemNumber) -{ - auto* item = &g_Level.Items[itemNumber]; - - if (!TriggerActive(item)) - return; - - if (TestEnvironment(RoomEnvFlags::ENV_FLAG_WATER, item->RoomNumber)) - { - if (item->ItemFlags[0] || !(GetRandomControl() & 0x1F) || item->TriggerFlags == 1) + if (spawnLargeBubbles) { - if (!(GetRandomControl() & 3) || item->ItemFlags[1]) + SpawnBubble(pos, item.RoomNumber, (int)BubbleFlags::HighAmplitude | (int)BubbleFlags::LargeScale); + } + else + { + SpawnBubble(pos, item.RoomNumber); + } + } + + void InitializeSmokeEmitter(short itemNumber) + { + auto& item = g_Level.Items[itemNumber]; + + bool isUnderwater = TestEnvironment(RoomEnvFlags::ENV_FLAG_WATER, item.RoomNumber); + bool isSteamEffect = (item.TriggerFlags != 0); + + if (isUnderwater) + { + auto& bubbleCount = item.ItemFlags[0]; + auto& bubbleForceSpawnFlag = item.ItemFlags[1]; + auto& bubbleSpawnRadius = item.ItemFlags[2]; + + bool spawnContinuousBubbles = (item.TriggerFlags == 1); + if (spawnContinuousBubbles) { - auto pos = Vector3( - (GetRandomControl() & 0x3F) + item->Pose.Position.x - 32, - item->Pose.Position.y - (GetRandomControl() & 0x1F) - 16, - (GetRandomControl() & 0x3F) + item->Pose.Position.z - 32); + bubbleCount = 20; + bubbleForceSpawnFlag = 1; + } - if (item->TriggerFlags == 1) - SpawnBubble(pos, item->RoomNumber); - else - SpawnBubble(pos, item->RoomNumber); + if (bubbleSpawnRadius == 0) + bubbleSpawnRadius = BUBBLE_DEFAULT_RADIUS; + } + else if (isSteamEffect) + { + auto& ocb = item.TriggerFlags; + auto& steamPauseTimer = item.ItemFlags[0]; + auto& steamAccel = item.ItemFlags[2]; + auto& steamFlags = item.ItemFlags[3]; - if (item->ItemFlags[0]) + if (ocb < 0) + { + ocb = -ocb; + steamFlags |= SmokeEmitterFlags::NoDamage; + } + + steamPauseTimer = ocb / 16; + + if (steamPauseTimer <= 0) + steamAccel = SMOKE_ACCEL_MAX; + } + } + + void ControlSmokeEmitter(short itemNumber) + { + auto& item = g_Level.Items[itemNumber]; + + if (!TriggerActive(&item)) + return; + + // Draw underwater bubbles. + if (TestEnvironment(RoomEnvFlags::ENV_FLAG_WATER, item.RoomNumber)) + { + bool spawnContinuousBubbles = (item.TriggerFlags == 1); + bool spawnLargeBubbles = item.TriggerFlags & 2; + + auto& bubbleCount = item.ItemFlags[0]; + auto& bubbleForceSpawnFlag = item.ItemFlags[1]; + auto& bubbleSpawnRadius = item.ItemFlags[2]; + + if (bubbleCount || Random::TestProbability(1 / 30.0f) || spawnContinuousBubbles) + { + if (Random::TestProbability(1 / 3.0f) || bubbleForceSpawnFlag) { - item->ItemFlags[0]--; + SpawnSmokeEmitterBubble(item, bubbleSpawnRadius, spawnLargeBubbles); - if (!item->ItemFlags[0]) - item->ItemFlags[1] = 0; + if (bubbleCount) + { + bubbleCount--; + + if (!bubbleCount) + bubbleForceSpawnFlag = 0; + } } } - } - else if (!(GetRandomControl() & 0x1F)) - { - item->ItemFlags[0] = (GetRandomControl() & 3) + 4; + else if (Random::TestProbability(1 / 30.0f)) + { + bubbleCount = Random::GenerateInt(4, 7); + } + + return; } - return; - } - - if (item->ObjectNumber == ID_SMOKE_EMITTER && item->TriggerFlags & 8) - { - bool normal = false; - - if (item->ItemFlags[0]) + // Draw horizontal steam. + bool isSteamShotEffect = item.TriggerFlags != 0; + if (isSteamShotEffect) { - item->ItemFlags[0]--; + bool drawNormalSmoke = false; - if (!item->ItemFlags[0]) - item->ItemFlags[1] = (GetRandomControl() & 0x3F) + 30; + auto& steamPauseTimer = item.ItemFlags[0]; + auto& steamActiveTimer = item.ItemFlags[1]; + auto& steamAccel = item.ItemFlags[2]; - normal = true; + if (steamPauseTimer != 0) + { + drawNormalSmoke = true; + steamPauseTimer--; - if (item->ItemFlags[2]) - item->ItemFlags[2] -= 256; - } - else if (item->ItemFlags[2] < 4096) - item->ItemFlags[2] += 256; + if (steamPauseTimer <= 0) + { + steamActiveTimer = Random::GenerateInt(30, 94); + SoundEffect(SFX_TR4_STEAM, &item.Pose); + } + + if (steamAccel) + steamAccel -= 256; + } + else if (steamAccel < SMOKE_ACCEL_MAX) + { + steamAccel += 256; + } - if (item->ItemFlags[2]) - { - int dx = Camera.pos.x - item->Pose.Position.x; - int dz = Camera.pos.z - item->Pose.Position.z; + if (steamAccel != 0) + { + SpawnSteamParticle(item, steamAccel); - if (dx < -BLOCK(16) || dx > BLOCK(16) || dz < -BLOCK(16) || dz > BLOCK(16)) + if (steamActiveTimer) + { + steamActiveTimer--; + } + else + { + steamPauseTimer = item.TriggerFlags >> 4; + } + + SoundEffect(SFX_TEN_STEAM_EMITTER_LOOP, &item.Pose); + } + + if (!drawNormalSmoke) return; - - auto* sptr = GetFreeParticle(); - sptr->on = true; - sptr->sR = 96; - sptr->sG = 96; - sptr->sB = 96; - sptr->dR = 48; - sptr->dG = 48; - sptr->dB = 48; - sptr->fadeToBlack = 6; - sptr->colFadeSpeed = (GetRandomControl() & 3) + 6; - sptr->blendMode = BlendMode::Additive; - sptr->life = (GetRandomControl() & 7) + 16; - sptr->sLife = sptr->life; - sptr->x = (GetRandomControl() & 0x3F) + item->Pose.Position.x - 32; - sptr->y = (GetRandomControl() & 0x3F) + item->Pose.Position.y - 32; - sptr->z = (GetRandomControl() & 0x3F) + item->Pose.Position.z - 32; - int size = item->ItemFlags[2]; - - if (item->ItemFlags[2] == 4096) - size = (GetRandomControl() & 0x7FF) + 2048; - - sptr->xVel = (short)((size * phd_sin(item->Pose.Orientation.y - 32768)) / BLOCK(1)); - sptr->yVel = -16 - (GetRandomControl() & 0xF); - sptr->zVel = (short)((size * phd_cos(item->Pose.Orientation.y - 32768)) / BLOCK(1)); - sptr->friction = 4; - sptr->flags = SP_SCALE | SP_DEF | SP_ROTATE | SP_EXPDEF; - - if (!(GlobalCounter & 0x03)) - sptr->flags |= SP_DAMAGE; - - sptr->rotAng = GetRandomControl() & 0xFFF; - - if (GetRandomControl() & 1) - sptr->rotAdd = -8 - (GetRandomControl() & 7); - else - sptr->rotAdd = (GetRandomControl() & 7) + 8; - - sptr->scalar = 2; - sptr->gravity = -8 - (GetRandomControl() & 0xF); - sptr->maxYvel = -8 - (GetRandomControl() & 7); - size = (GetRandomControl() & 0x1F) + 128; - sptr->dSize = float(size); - sptr->sSize = sptr->size = sptr->dSize / 2.0f; - - if (item->ItemFlags[1]) - item->ItemFlags[1]--; - else - item->ItemFlags[0] = item->TriggerFlags >> 4; } - if (!normal) - return; - } - - if (!(Wibble & 0x0F) && (item->ObjectNumber != ID_SMOKE_EMITTER || !(Wibble & 0x1F))) - { - int dx = Camera.pos.x - item->Pose.Position.x; - int dz = Camera.pos.z - item->Pose.Position.z; - - if (dx < -BLOCK(16) || dx > BLOCK(16) || dz < -BLOCK(16) || dz > BLOCK(16)) - return; - - auto* sptr = GetFreeParticle(); - sptr->on = 1; - sptr->sR = 0; - sptr->sG = 0; - sptr->sB = 0; - sptr->dR = 64; - sptr->dG = 64; - sptr->dB = 64; - - if (item->ObjectNumber == ID_SMOKE_EMITTER_BLACK) - { - sptr->dR = 96; - sptr->dG = 96; - sptr->dB = 96; - } - - sptr->fadeToBlack = 16; - sptr->colFadeSpeed = (GetRandomControl() & 3) + 8; - sptr->sLife = sptr->life = (GetRandomControl() & 7) + 28; - - if (item->ObjectNumber == ID_SMOKE_EMITTER_BLACK) - sptr->blendMode = BlendMode::Subtractive; - else - sptr->blendMode = BlendMode::Additive; - - sptr->x = (GetRandomControl() & 0x3F) + item->Pose.Position.x - 32; - sptr->y = (GetRandomControl() & 0x3F) + item->Pose.Position.y - 32; - sptr->z = (GetRandomControl() & 0x3F) + item->Pose.Position.z - 32; - sptr->xVel = (GetRandomControl() & 0xFF) - 128; - sptr->yVel = -16 - (GetRandomControl() & 0xF); - sptr->zVel = (GetRandomControl() & 0xFF) - 128; - sptr->friction = 3; - sptr->flags = SP_SCALE | SP_DEF | SP_ROTATE | SP_EXPDEF; - - if (TestEnvironment(RoomEnvFlags::ENV_FLAG_OUTSIDE, item->RoomNumber)) - sptr->flags |= SP_WIND; - - sptr->rotAng = GetRandomControl() & 0xFFF; - - if (GetRandomControl() & 1) - sptr->rotAdd = -8 - (GetRandomControl() & 7); - else - sptr->rotAdd = (GetRandomControl() & 7) + 8; - - sptr->scalar = 2; - sptr->gravity = -8 - (GetRandomControl() & 0xF); - sptr->maxYvel = -8 - (GetRandomControl() & 7); - int size = (GetRandomControl() & 0x1F) + 128; - sptr->dSize = float(size); - sptr->sSize = sptr->size = float(size / 4); - - if (item->ObjectNumber == ID_SMOKE_EMITTER) - { - sptr->gravity /= 2; - sptr->yVel /= 2; - sptr->maxYvel /= 2; - sptr->life += 16; - sptr->sLife += 16; - sptr->dR = 32; - sptr->dG = 32; - sptr->dB = 32; - } + // Draw normal smoke. + bool drawSmoke = (!(Wibble & 0x0F) && (item.ObjectNumber != ID_SMOKE_EMITTER || !(Wibble & 0x1F))); + if (drawSmoke) + SpawnSmokeEmitterParticle(item); } } diff --git a/TombEngine/Objects/TR5/Emitter/tr5_smoke_emitter.h b/TombEngine/Objects/TR5/Emitter/tr5_smoke_emitter.h index fe4c57652..11e80a14c 100644 --- a/TombEngine/Objects/TR5/Emitter/tr5_smoke_emitter.h +++ b/TombEngine/Objects/TR5/Emitter/tr5_smoke_emitter.h @@ -1,4 +1,7 @@ #pragma once -void InitializeSmokeEmitter(short itemNumber); -void SmokeEmitterControl(short itemNumber); +namespace TEN::Effects::SmokeEmitter +{ + void InitializeSmokeEmitter(short itemNumber); + void ControlSmokeEmitter(short itemNumber); +} diff --git a/TombEngine/Objects/TR5/Emitter/tr5_spider_emitter.cpp b/TombEngine/Objects/TR5/Emitter/tr5_spider_emitter.cpp index 8e201afe2..04d090de9 100644 --- a/TombEngine/Objects/TR5/Emitter/tr5_spider_emitter.cpp +++ b/TombEngine/Objects/TR5/Emitter/tr5_spider_emitter.cpp @@ -47,7 +47,7 @@ void ClearSpiders() { ZeroMemory(Spiders, NUM_SPIDERS * sizeof(SpiderData)); NextSpider = 0; - FlipEffect = -1; + FlipEffect = NO_VALUE; } } @@ -134,6 +134,8 @@ void UpdateSpiders() if (spider->On) { + spider->StoreInterpolationData(); + int x = spider->Pose.Position.x; int y = spider->Pose.Position.y; int z = spider->Pose.Position.z; @@ -232,15 +234,19 @@ void UpdateSpiders() spider->VerticalVelocity = 0; } - if (spider->Pose.Position.y < g_Level.Rooms[spider->RoomNumber].maxceiling + 50) + if (spider->Pose.Position.y < g_Level.Rooms[spider->RoomNumber].TopHeight + 50) { - spider->Pose.Position.y = g_Level.Rooms[spider->RoomNumber].maxceiling + 50; + spider->Pose.Position.y = g_Level.Rooms[spider->RoomNumber].TopHeight + 50; spider->Pose.Orientation.y += -ANGLE(180.0f); spider->VerticalVelocity = 1; } if (!i && !(GetRandomControl() & 4)) SoundEffect(SFX_TR5_INSECTS,&spider->Pose); + + auto tMatrix = Matrix::CreateTranslation(spider->Pose.Position.ToVector3()); + auto rotMatrix = spider->Pose.Orientation.ToRotationMatrix(); + spider->Transform = rotMatrix * tMatrix; } } } diff --git a/TombEngine/Objects/TR5/Emitter/tr5_spider_emitter.h b/TombEngine/Objects/TR5/Emitter/tr5_spider_emitter.h index d2064d187..636868907 100644 --- a/TombEngine/Objects/TR5/Emitter/tr5_spider_emitter.h +++ b/TombEngine/Objects/TR5/Emitter/tr5_spider_emitter.h @@ -13,6 +13,14 @@ struct SpiderData short VerticalVelocity; byte Flags; + + Matrix Transform = Matrix::Identity; + Matrix PrevTransform = Matrix::Identity; + + void StoreInterpolationData() + { + PrevTransform = Transform; + } }; extern int NextSpider; diff --git a/TombEngine/Objects/TR5/Entity/AutoGun.cpp b/TombEngine/Objects/TR5/Entity/AutoGun.cpp index 6ed1cb87c..7379eeb67 100644 --- a/TombEngine/Objects/TR5/Entity/AutoGun.cpp +++ b/TombEngine/Objects/TR5/Entity/AutoGun.cpp @@ -2,7 +2,7 @@ #include "Objects/TR5/Entity/AutoGun.h" #include "Game/animation.h" -#include "Game/collision/sphere.h" +#include "Game/collision/Sphere.h" #include "Game/control/los.h" #include "Game/effects/effects.h" #include "Game/effects/tomb4fx.h" @@ -14,6 +14,7 @@ #include "Sound/sound.h" #include "Specific/level.h" +using namespace TEN::Collision::Sphere; using namespace TEN::Math; // NOTES: @@ -62,12 +63,10 @@ namespace TEN::Entities::Creatures::TR5 smoke.fadeToBlack = 32; smoke.blendMode = BlendMode::Additive; smoke.life = smoke.sLife = Random::GenerateInt(40, 44); - smoke.x = smokePos.x; - smoke.y = smokePos.y; - smoke.z = smokePos.z; - smoke.xVel = 0; - smoke.yVel = 0; - smoke.zVel = 0; + smoke.position.x = smokePos.x; + smoke.position.y = smokePos.y; + smoke.position.z = smokePos.z; + smoke.velocity = Vector3i::Zero; smoke.friction = 4; smoke.flags = SP_ROTATE; smoke.rotAng = Random::GenerateInt(0, 4096); diff --git a/TombEngine/Objects/TR5/Entity/tr5_cyborg.cpp b/TombEngine/Objects/TR5/Entity/tr5_cyborg.cpp index a538341e5..65d0fdd13 100644 --- a/TombEngine/Objects/TR5/Entity/tr5_cyborg.cpp +++ b/TombEngine/Objects/TR5/Entity/tr5_cyborg.cpp @@ -3,6 +3,7 @@ #include "Game/animation.h" #include "Game/collision/collide_room.h" +#include "Game/collision/Point.h" #include "Game/control/box.h" #include "Game/effects/effects.h" #include "Game/effects/Electricity.h" @@ -20,6 +21,7 @@ #include "Sound/sound.h" #include "Specific/level.h" +using namespace TEN::Collision::Point; using namespace TEN::Effects::Electricity; using namespace TEN::Effects::Items; using namespace TEN::Effects::Spark; @@ -144,17 +146,17 @@ namespace TEN::Entities::Creatures::TR5 x += dx; z += dz; - int height1 = GetCollision(x, item.Pose.Position.y, z, item.RoomNumber).Position.Floor; + int height1 = GetPointCollision(Vector3i(x, item.Pose.Position.y, z), item.RoomNumber).GetFloorHeight(); x += dx; z += dz; - int height2 = GetCollision(x, item.Pose.Position.y, z, item.RoomNumber).Position.Floor; + int height2 = GetPointCollision(Vector3i(x, item.Pose.Position.y, z), item.RoomNumber).GetFloorHeight(); x += dx; z += dz; - auto pointColl = GetCollision(x, item.Pose.Position.y, z, item.RoomNumber); - int roomNumber = pointColl.RoomNumber; - int height3 = pointColl.Position.Floor; + auto pointColl = GetPointCollision(Vector3i(x, item.Pose.Position.y, z), item.RoomNumber); + int roomNumber = pointColl.GetRoomNumber(); + int height3 = pointColl.GetFloorHeight(); bool canJump1block = true; if (item.BoxNumber == LaraItem->BoxNumber || @@ -220,7 +222,7 @@ namespace TEN::Entities::Creatures::TR5 if (randomIndex == 5 || randomIndex == 7 || randomIndex == 10) { auto pos2 = Vector3::Zero; - auto pointColl2 = GetCollision(pos2.x, pos2.y, pos2.z, item.RoomNumber); + auto pointColl2 = GetPointCollision(pos2, item.RoomNumber); switch (randomIndex) { @@ -230,9 +232,9 @@ namespace TEN::Entities::Creatures::TR5 case 7: pos2 = GetJointPosition(&item, 6, Vector3i(0, 0, 50)).ToVector3(); - pointColl2 = GetCollision(pos2.x, pos2.y, pos2.z, item.RoomNumber); + pointColl2 = GetPointCollision(pos2, item.RoomNumber); - if (TestEnvironment(ENV_FLAG_WATER, pointColl2.RoomNumber) && item.HitPoints > 0) + if (TestEnvironment(ENV_FLAG_WATER, pointColl2.GetRoomNumber()) && item.HitPoints > 0) { DropPickups(&item); DoDamage(&item, INT_MAX); @@ -379,11 +381,11 @@ namespace TEN::Entities::Creatures::TR5 } else { - pointColl = GetCollision(item.Pose.Position.x, item.Pose.Position.y, item.Pose.Position.z, roomNumber); - roomNumber = pointColl.RoomNumber; - height = pointColl.Position.Floor; + pointColl = GetPointCollision(item.Pose.Position, roomNumber); + roomNumber = pointColl.GetRoomNumber(); + height = pointColl.GetFloorHeight(); - if (pointColl.Position.Ceiling == (height - BLOCK(1.5f))) + if (pointColl.GetCeilingHeight() == (height - BLOCK(1.5f))) item.Animation.TargetState = CYBORG_STATE_START_END_MONKEY; else item.Animation.TargetState = CYBORG_STATE_WALK; @@ -481,11 +483,11 @@ namespace TEN::Entities::Creatures::TR5 if (item.BoxNumber == creature.LOT.TargetBox || !creature.MonkeySwingAhead) { - pointColl = GetCollision(item.Pose.Position.x, item.Pose.Position.y, item.Pose.Position.z, roomNumber); - roomNumber = pointColl.RoomNumber; - height = pointColl.Position.Floor; + pointColl = GetPointCollision(item.Pose.Position, roomNumber); + roomNumber = pointColl.GetRoomNumber(); + height = pointColl.GetFloorHeight(); - if (pointColl.Position.Ceiling == height - BLOCK(1.5f), 2) + if (pointColl.GetCeilingHeight() == height - BLOCK(1.5f), 2) item.Animation.TargetState = CYBORG_STATE_IDLE; } else @@ -503,11 +505,11 @@ namespace TEN::Entities::Creatures::TR5 if (item.BoxNumber == creature.LOT.TargetBox || !creature.MonkeySwingAhead) { - pointColl = GetCollision(item.Pose.Position.x, item.Pose.Position.y, item.Pose.Position.z, roomNumber); - roomNumber = pointColl.RoomNumber; - height = pointColl.Position.Floor; + pointColl = GetPointCollision(item.Pose.Position, roomNumber); + roomNumber = pointColl.GetRoomNumber(); + height = pointColl.GetFloorHeight(); - if (pointColl.Position.Ceiling == height - BLOCK(1.5f), 2) + if (pointColl.GetCeilingHeight() == height - BLOCK(1.5f), 2) item.Animation.TargetState = CYBORG_STATE_START_END_MONKEY; } @@ -583,9 +585,9 @@ namespace TEN::Entities::Creatures::TR5 else if (item.Animation.ActiveState == CYBORG_STATE_DEATH && LaraItem->Effect.Type == EffectType::None) { auto pos = GetJointPosition(LaraItem, LM_RFOOT); - auto footProbeRight = GetCollision(pos.x, pos.y, pos.z, LaraItem->RoomNumber); + auto footProbeRight = GetPointCollision(pos, LaraItem->RoomNumber); pos = GetJointPosition(LaraItem, LM_LFOOT); - auto footProbeLeft = GetCollision(pos.x, pos.y, pos.z, LaraItem->RoomNumber); + auto footProbeLeft = GetPointCollision(pos, LaraItem->RoomNumber); short roomNumberLeft = LaraItem->RoomNumber; GetFloor(pos.x, pos.y, pos.z, &roomNumberLeft); @@ -599,8 +601,8 @@ namespace TEN::Entities::Creatures::TR5 short flipNumber = g_Level.Rooms[item.RoomNumber].flipNumber; - if (TestEnvironment(ENV_FLAG_WATER, footProbeLeft.RoomNumber) || - TestEnvironment(ENV_FLAG_WATER, footProbeRight.RoomNumber)) + if (TestEnvironment(ENV_FLAG_WATER, footProbeLeft.GetRoomNumber()) || + TestEnvironment(ENV_FLAG_WATER, footProbeRight.GetRoomNumber())) { if (roomLeft->flipNumber == flipNumber || roomRight->flipNumber == flipNumber) { diff --git a/TombEngine/Objects/TR5/Entity/tr5_gladiator.cpp b/TombEngine/Objects/TR5/Entity/tr5_gladiator.cpp index 2eff58d0d..cd8759512 100644 --- a/TombEngine/Objects/TR5/Entity/tr5_gladiator.cpp +++ b/TombEngine/Objects/TR5/Entity/tr5_gladiator.cpp @@ -15,6 +15,7 @@ #include "Sound/sound.h" #include "Specific/level.h" +using namespace TEN::Collision::Room; using namespace TEN::Math; namespace TEN::Entities::Creatures::TR5 @@ -336,7 +337,7 @@ namespace TEN::Entities::Creatures::TR5 auto pos = GetJointPosition(item, 16); - auto* floor = GetSector(room, pos.x - room->x, pos.z - room->z); + auto* floor = GetSector(room, pos.x - room->Position.x, pos.z - room->Position.z); if (floor->Stopper) { for (int i = 0; i < room->mesh.size(); i++) diff --git a/TombEngine/Objects/TR5/Entity/tr5_guard.cpp b/TombEngine/Objects/TR5/Entity/tr5_guard.cpp index 649419145..b405ab487 100644 --- a/TombEngine/Objects/TR5/Entity/tr5_guard.cpp +++ b/TombEngine/Objects/TR5/Entity/tr5_guard.cpp @@ -3,6 +3,7 @@ #include "Game/animation.h" #include "Game/collision/collide_room.h" +#include "Game/collision/Point.h" #include "Game/control/box.h" #include "Game/control/los.h" #include "Game/effects/effects.h" @@ -17,6 +18,7 @@ #include "Sound/sound.h" #include "Specific/level.h" +using namespace TEN::Collision::Point; using namespace TEN::Math; namespace TEN::Entities::Creatures::TR5 @@ -262,7 +264,7 @@ namespace TEN::Entities::Creatures::TR5 case GuardOcb::RopeDownFast: SetAnimation(item, GUARD_ANIM_ROPE_DOWN_FAST); - item->Pose.Position.y = GetCollision(*item).Position.Ceiling - BLOCK(2); + item->Pose.Position.y = GetPointCollision(*item).GetCeilingHeight() - BLOCK(2); break; case GuardOcb::WaitOnWall: @@ -1147,15 +1149,15 @@ namespace TEN::Entities::Creatures::TR5 x += dx; z += dz; - int height1 = GetCollision(x, y, z, item->RoomNumber).Position.Floor; + int height1 = GetPointCollision(Vector3i(x, y, z), item->RoomNumber).GetFloorHeight(); x += dx; z += dz; - int height2 = GetCollision(x, y, z, item->RoomNumber).Position.Floor; + int height2 = GetPointCollision(Vector3i(x, y, z), item->RoomNumber).GetFloorHeight(); x += dx; z += dz; - int height3 = GetCollision(x, y, z, item->RoomNumber).Position.Floor; + int height3 = GetPointCollision(Vector3i(x, y, z), item->RoomNumber).GetFloorHeight(); int height = 0; bool canJump1Sector = true; diff --git a/TombEngine/Objects/TR5/Entity/tr5_larson.cpp b/TombEngine/Objects/TR5/Entity/tr5_larson.cpp index a9adcaf22..e34fbc7d3 100644 --- a/TombEngine/Objects/TR5/Entity/tr5_larson.cpp +++ b/TombEngine/Objects/TR5/Entity/tr5_larson.cpp @@ -18,6 +18,8 @@ using namespace TEN::Math; namespace TEN::Entities::Creatures::TR5 { + constexpr auto LARSON_ALERT_RANGE = SQUARE(BLOCK(2)); + #define STATE_TR5_LARSON_STOP 1 #define STATE_TR5_LARSON_WALK 2 #define STATE_TR5_LARSON_RUN 3 @@ -58,6 +60,8 @@ namespace TEN::Entities::Creatures::TR5 item->Pose.Position.z += STEPUP_HEIGHT; } + // TODO: Make larson 1:1 from TOMB5 code. TokyoSU: 10/27/2024 + // This code is a mess... void LarsonControl(short itemNumber) { if (!CreatureActive(itemNumber)) @@ -73,15 +77,14 @@ namespace TEN::Entities::Creatures::TR5 short joint2 = 0; // TODO: When Larson's HP is below 40, he runs away in Streets of Rome. Keeping block commented for reference. - /*if (item->HitPoints <= TR5_LARSON_MIN_HP && !(item->flags & IFLAG_INVISIBLE)) + if (item->HitPoints <= TR5_LARSON_MIN_HP && !(item->Flags & IFLAG_INVISIBLE)) { item->HitPoints = TR5_LARSON_MIN_HP; - creature->flags++; - }*/ + creature->Flags++; + } if (creature->MuzzleFlash[0].Delay != 0) creature->MuzzleFlash[0].Delay--; - if (creature->MuzzleFlash[1].Delay != 0) creature->MuzzleFlash[1].Delay--; @@ -89,20 +92,13 @@ namespace TEN::Entities::Creatures::TR5 { if (CurrentLevel == 2) { - item->Animation.IsAirborne = false; - item->Status = ITEM_DEACTIVATED; - item->Collidable = false; - item->HitStatus = false; + item->AIBits = AMBUSH; item->ItemFlags[3] = 1; } else { - item->Animation.IsAirborne = false; - item->Status = ITEM_ACTIVE; - item->Collidable = false; - item->HitStatus = false; + item->AIBits = GUARD; } - item->TriggerFlags = 0; } @@ -120,25 +116,23 @@ namespace TEN::Entities::Creatures::TR5 joint2 = AI.angle; // FIXME: This should make Larson run away, but it doesn't work. - /*if (creature->flags) + // FIXME: 10/27/2024 - TokyoSU: Implemented TOMB5 way, should work now but need test. + if (creature->Flags) { item->HitPoints = 60; - item->IsAirborne = false; - item->HitStatus = false; - item->Collidable = false; - item->Status = ITEM_DESACTIVATED; + item->AIBits = AMBUSH; creature->Flags = 0; - }*/ + } GetCreatureMood(item, &AI, true); CreatureMood(item, &AI, true); - if (AI.distance < SQUARE(BLOCK(2)) && + if (AI.distance < LARSON_ALERT_RANGE && LaraItem->Animation.Velocity.z > 20.0f || item->HitStatus || TargetVisible(item, &AI) != 0) { - item->Status &= ~ITEM_ACTIVE; + item->AIBits &= ~GUARD; creature->Alerted = true; } @@ -363,9 +357,9 @@ namespace TEN::Entities::Creatures::TR5 auto* room = &g_Level.Rooms[roomNumber]; - int x = room->x + (creature->Tosspad / 256 & 0xFF) * BLOCK(1) + 512; - int y = room->minfloor + floorHeight; - int z = room->z + (creature->Tosspad & 0xFF) * BLOCK(1) + 512; + int x = room->Position.x + (creature->Tosspad / 256 & 0xFF) * BLOCK(1) + 512; + int y = room->BottomHeight + floorHeight; + int z = room->Position.z + (creature->Tosspad & 0xFF) * BLOCK(1) + 512; TestTriggers(x, y, z, roomNumber, true); diff --git a/TombEngine/Objects/TR5/Entity/tr5_laser_head.cpp b/TombEngine/Objects/TR5/Entity/tr5_laser_head.cpp index 11ff82fb0..0be4cdc39 100644 --- a/TombEngine/Objects/TR5/Entity/tr5_laser_head.cpp +++ b/TombEngine/Objects/TR5/Entity/tr5_laser_head.cpp @@ -591,9 +591,12 @@ namespace TEN::Entities::Creatures::TR5 void DoGuardianDeath(int itemNumber, ItemInfo& item) { const auto& guardian = GetGuardianInfo(item); - - ExplodeItemNode(&g_Level.Items[guardian.BaseItem], 0, 0, 128); - KillItem(guardian.BaseItem); + + if (g_Level.Items[guardian.BaseItem].ObjectNumber == ID_LASERHEAD_BASE) + { + ExplodeItemNode(&g_Level.Items[guardian.BaseItem], 0, 0, 128); + KillItem(guardian.BaseItem); + } ExplodeItemNode(&item, 0, 0, 128); @@ -605,7 +608,6 @@ namespace TEN::Entities::Creatures::TR5 TriggerShockwave(&item.Pose, 32, 160, 64, 0, 128, 64, 36, EulerAngles(0x3000, 0.0f, 0.0f), 0, true, false, false, (int)ShockwaveStyle::Normal); TriggerShockwave(&item.Pose, 32, 160, 64, 0, 128, 64, 36, EulerAngles(0x6000, 0.0f, 0.0f), 0, true, false, false, (int)ShockwaveStyle::Normal); - g_Level.Items[guardian.PuzzleItem].Pose.Position.y = item.Pose.Position.y; TestTriggers(&item, true); SoundEffect(SFX_TR5_GOD_HEAD_BLAST, &item.Pose, SoundEnvironment::Land, 0.5f); diff --git a/TombEngine/Objects/TR5/Entity/tr5_laserhead_info.h b/TombEngine/Objects/TR5/Entity/tr5_laserhead_info.h index b32f7ea90..393348eeb 100644 --- a/TombEngine/Objects/TR5/Entity/tr5_laserhead_info.h +++ b/TombEngine/Objects/TR5/Entity/tr5_laserhead_info.h @@ -21,6 +21,5 @@ namespace TEN::Entities::Creatures::TR5 short yRot; int BaseItem; std::array Tentacles = {}; - int PuzzleItem; }; } diff --git a/TombEngine/Objects/TR5/Entity/tr5_roman_statue.cpp b/TombEngine/Objects/TR5/Entity/tr5_roman_statue.cpp index 72a8d3aa8..bdb8dbcfc 100644 --- a/TombEngine/Objects/TR5/Entity/tr5_roman_statue.cpp +++ b/TombEngine/Objects/TR5/Entity/tr5_roman_statue.cpp @@ -20,6 +20,7 @@ #include "Sound/sound.h" #include "Specific/level.h" +using namespace TEN::Collision::Room; using namespace TEN::Effects::Electricity; using namespace TEN::Effects::Spark; using namespace TEN::Math; @@ -112,12 +113,16 @@ namespace TEN::Entities::Creatures::TR5 spark->dShade = (GetRandomControl() & 0xF) + 64; spark->blendMode = BlendMode::Additive; spark->life = spark->sLife = (GetRandomControl() & 3) + 64; - spark->x = (GetRandomControl() & 0x1F) + pos->x - 16; - spark->y = (GetRandomControl() & 0x1F) + pos->y - 16; - spark->z = (GetRandomControl() & 0x1F) + pos->z - 16; - spark->xVel = (GetRandomControl() & 0x7F) - 64; - spark->yVel = 0; - spark->zVel = (GetRandomControl() & 0x7F) - 64; + spark->position = Vector3i( + (GetRandomControl() & 0x1F) + pos->x - 16, + (GetRandomControl() & 0x1F) + pos->y - 16, + (GetRandomControl() & 0x1F) + pos->z - 16 + ); + spark->velocity = Vector3i( + (GetRandomControl() & 0x7F) - 64, + 0, + (GetRandomControl() & 0x7F) - 64 + ); spark->friction = 4; spark->flags = SP_ROTATE; spark->rotAng = GetRandomControl() & 0xFFF; @@ -155,7 +160,7 @@ namespace TEN::Entities::Creatures::TR5 spark->flags = SP_SCALE | SP_DEF; spark->scalar = 3; spark->maxYvel = 0; - spark->spriteIndex = Objects[ID_DEFAULT_SPRITES].meshIndex + SPR_LENSFLARE_LIGHT; + spark->spriteIndex = Objects[ID_DEFAULT_SPRITES].meshIndex + SPR_LENS_FLARE_LIGHT; spark->gravity = 0; spark->dSize = spark->sSize = spark->size = size + (GetRandomControl() & 3); } @@ -542,7 +547,7 @@ namespace TEN::Entities::Creatures::TR5 pos = GetJointPosition(item, 16); auto* room = &g_Level.Rooms[item->RoomNumber]; - FloorInfo* floor = GetSector(room, pos.x - room->x, pos.z - room->z); + FloorInfo* floor = GetSector(room, pos.x - room->Position.x, pos.z - room->Position.z); // If floor is stopped, then try to find static meshes and shatter them, activating heavy triggers below if (floor->Stopper) @@ -822,9 +827,9 @@ namespace TEN::Entities::Creatures::TR5 short floorHeight = item->ItemFlags[2] & 0xFF00; auto* room = &g_Level.Rooms[roomNumber]; - int x = room->x + (creature->Tosspad / 256 & 0xFF) * BLOCK(1) + 512; - int y = room->minfloor + floorHeight; - int z = room->z + (creature->Tosspad & 0xFF) * BLOCK(1) + 512; + int x = room->Position.x + (creature->Tosspad / 256 & 0xFF) * BLOCK(1) + 512; + int y = room->BottomHeight + floorHeight; + int z = room->Position.z + (creature->Tosspad & 0xFF) * BLOCK(1) + 512; TestTriggers(x, y, z, roomNumber, true); } diff --git a/TombEngine/Objects/TR5/Entity/tr5_submarine.cpp b/TombEngine/Objects/TR5/Entity/tr5_submarine.cpp index 89b06613d..04a4b4ed3 100644 --- a/TombEngine/Objects/TR5/Entity/tr5_submarine.cpp +++ b/TombEngine/Objects/TR5/Entity/tr5_submarine.cpp @@ -4,6 +4,7 @@ #include "Game/animation.h" #include "Game/collision/collide_item.h" #include "Game/collision/collide_room.h" +#include "Game/collision/Point.h" #include "Game/control/box.h" #include "Game/control/los.h" #include "Game/effects/effects.h" @@ -19,6 +20,8 @@ #include "Sound/sound.h" #include "Specific/level.h" +using namespace TEN::Collision::Point; + using namespace TEN::Math; namespace TEN::Entities::Creatures::TR5 @@ -62,7 +65,7 @@ namespace TEN::Entities::Creatures::TR5 spark->maxYvel = 0; spark->gravity = 0; spark->scalar = 1; - spark->spriteIndex = Objects[ID_DEFAULT_SPRITES].meshIndex + SPR_LENSFLARE_LIGHT; + spark->spriteIndex = Objects[ID_DEFAULT_SPRITES].meshIndex + SPR_LENS_FLARE_LIGHT; spark->dSize = spark->sSize = spark->size = (GetRandomControl() & 7) + 192; } @@ -486,11 +489,11 @@ namespace TEN::Entities::Creatures::TR5 TranslateItem(item, item->Pose.Orientation, item->Animation.Velocity.z); - auto probe = GetCollision(item); + auto probe = GetPointCollision(*item); - if (item->Pose.Position.y < probe.Position.Floor && - item->Pose.Position.y > probe.Position.Ceiling && - TestEnvironment(ENV_FLAG_WATER, probe.RoomNumber)) + if (item->Pose.Position.y < probe.GetFloorHeight() && + item->Pose.Position.y > probe.GetCeilingHeight() && + TestEnvironment(ENV_FLAG_WATER, probe.GetRoomNumber())) { if (ItemNearLara(item->Pose.Position, 200)) { @@ -506,8 +509,8 @@ namespace TEN::Entities::Creatures::TR5 // if (ItemNearLara(&item->pos, 400) && Lara.anxiety < 0xE0) // Lara.anxiety += 32; - if (probe.RoomNumber != item->RoomNumber) - ItemNewRoom(itemNumber, probe.RoomNumber); + if (probe.GetRoomNumber() != item->RoomNumber) + ItemNewRoom(itemNumber, probe.GetRoomNumber()); auto pos1 = GetJointPosition(item, 0, Vector3i(0, 0, -64)); auto pos2 = GetJointPosition(item, 0, Vector3i(0, 0, -64 << ((GlobalCounter & 1) + 2))); diff --git a/TombEngine/Objects/TR5/Object/tr5_bodypart.cpp b/TombEngine/Objects/TR5/Object/tr5_bodypart.cpp index 4ad272ffc..e95d695c4 100644 --- a/TombEngine/Objects/TR5/Object/tr5_bodypart.cpp +++ b/TombEngine/Objects/TR5/Object/tr5_bodypart.cpp @@ -1,5 +1,6 @@ #include "framework.h" #include "tr5_bodypart.h" + #include "Game/effects/effects.h" #include "Math/Math.h" #include "Sound/sound.h" @@ -7,10 +8,12 @@ #include "Game/Lara/lara.h" #include "Game/collision/collide_item.h" #include "Game/collision/collide_room.h" +#include "Game/collision/Point.h" #include "Game/items.h" #include "Game/effects/tomb4fx.h" #include "Math/Random.h" +using namespace TEN::Collision::Point; using namespace TEN::Math::Random; constexpr int BODY_PART_LIFE = 64; @@ -76,18 +79,18 @@ void ControlBodyPart(short fxNumber) TriggerFireFlame(fx->pos.Position.x, fx->pos.Position.y, fx->pos.Position.z, FlameType::Medium); } - auto pointColl = GetCollision(fx->pos.Position.x, fx->pos.Position.y, fx->pos.Position.z, fx->roomNumber); + auto pointColl = GetPointCollision(fx->pos.Position, fx->roomNumber); if (!fx->counter) { - if (fx->pos.Position.y < pointColl.Position.Ceiling) + if (fx->pos.Position.y < pointColl.GetCeilingHeight()) { - fx->pos.Position.y = pointColl.Position.Ceiling; + fx->pos.Position.y = pointColl.GetCeilingHeight(); fx->fallspeed = -fx->fallspeed; fx->speed -= (fx->speed / 8); } - if (fx->pos.Position.y >= pointColl.Position.Floor) + if (fx->pos.Position.y >= pointColl.GetFloorHeight()) { if (fx->flag2 & BODY_NO_BOUNCE) { @@ -119,7 +122,7 @@ void ControlBodyPart(short fxNumber) return; } - if (y <= pointColl.Position.Floor) + if (y <= pointColl.GetFloorHeight()) { // Remove if touched floor (no bounce mode). if (fx->flag2 & BODY_PART_EXPLODE) @@ -201,19 +204,19 @@ void ControlBodyPart(short fxNumber) } } - if (pointColl.RoomNumber != fx->roomNumber) + if (pointColl.GetRoomNumber() != fx->roomNumber) { - if (TestEnvironment(RoomEnvFlags::ENV_FLAG_WATER, pointColl.RoomNumber) && + if (TestEnvironment(RoomEnvFlags::ENV_FLAG_WATER, pointColl.GetRoomNumber()) && !TestEnvironment(RoomEnvFlags::ENV_FLAG_WATER, fx->roomNumber)) { - int waterHeight = GetWaterHeight(fx->pos.Position.x, fx->pos.Position.y, fx->pos.Position.z, pointColl.RoomNumber); + int waterHeight = GetPointCollision(fx->pos.Position, pointColl.GetRoomNumber()).GetWaterTopHeight(); SplashSetup.y = waterHeight - 1; SplashSetup.x = fx->pos.Position.x; SplashSetup.z = fx->pos.Position.z; SplashSetup.splashPower = fx->fallspeed; SplashSetup.innerRadius = 48; - SetupSplash(&SplashSetup, pointColl.RoomNumber); + SetupSplash(&SplashSetup, pointColl.GetRoomNumber()); // Remove if touched water. if (fx->flag2 & BODY_PART_EXPLODE) @@ -223,6 +226,6 @@ void ControlBodyPart(short fxNumber) } } - EffectNewRoom(fxNumber, pointColl.RoomNumber); + EffectNewRoom(fxNumber, pointColl.GetRoomNumber()); } } \ No newline at end of file diff --git a/TombEngine/Objects/TR5/Object/tr5_expandingplatform.cpp b/TombEngine/Objects/TR5/Object/tr5_expandingplatform.cpp index 9fb14881b..b6de0dbcd 100644 --- a/TombEngine/Objects/TR5/Object/tr5_expandingplatform.cpp +++ b/TombEngine/Objects/TR5/Object/tr5_expandingplatform.cpp @@ -131,7 +131,7 @@ namespace TEN::Entities::Generic short roomNumber = item.RoomNumber; FloorInfo* floor = GetFloor(item.Pose.Position.x, item.Pose.Position.y, item.Pose.Position.z, &roomNumber); - g_Level.Boxes[floor->Box].flags &= ~BLOCKED; + g_Level.PathfindingBoxes[floor->PathfindingBoxID].flags &= ~BLOCKED; // Set mutators to default. UpdateExpandingPlatformMutators(itemNumber); diff --git a/TombEngine/Objects/TR5/Object/tr5_missile.cpp b/TombEngine/Objects/TR5/Object/tr5_missile.cpp index 93bdbe42b..8bcffe8d7 100644 --- a/TombEngine/Objects/TR5/Object/tr5_missile.cpp +++ b/TombEngine/Objects/TR5/Object/tr5_missile.cpp @@ -2,7 +2,7 @@ #include "tr5_missile.h" #include "Game/items.h" #include "Game/collision/collide_room.h" -#include "Game/collision/sphere.h" +#include "Game/collision/Point.h" #include "Game/effects/tomb4fx.h" #include "Game/effects/effects.h" #include "Specific/level.h" @@ -15,6 +15,7 @@ #include "Game/effects/item_fx.h" #include "Math/Math.h" +using namespace TEN::Collision::Point; using namespace TEN::Effects::Items; using namespace TEN::Math; @@ -106,9 +107,9 @@ void MissileControl(short itemNumber) fx->pos.Position.y += fx->speed * phd_sin(-fx->pos.Orientation.x); fx->pos.Position.z += c * phd_cos(fx->pos.Orientation.y); - auto probe = GetCollision(fx->pos.Position.x, fx->pos.Position.y, fx->pos.Position.z, fx->roomNumber); + auto probe = GetPointCollision(fx->pos.Position, fx->roomNumber); - if (fx->pos.Position.y >= probe.Position.Floor || fx->pos.Position.y <= probe.Position.Ceiling) + if (fx->pos.Position.y >= probe.GetFloorHeight() || fx->pos.Position.y <= probe.GetCeilingHeight()) { fx->pos.Position.x = x; fx->pos.Position.y = y; @@ -182,8 +183,8 @@ void MissileControl(short itemNumber) } else { - if (probe.RoomNumber != fx->roomNumber) - EffectNewRoom(itemNumber, probe.RoomNumber); + if (probe.GetRoomNumber() != fx->roomNumber) + EffectNewRoom(itemNumber, probe.GetRoomNumber()); if (GlobalCounter & 1) { @@ -208,9 +209,7 @@ void ExplodeFX(FX_INFO* fx, int noXZVel, int bits) ShatterItem.yRot = fx->pos.Orientation.y; ShatterItem.meshIndex = fx->frameNumber; ShatterItem.color = Vector4::One; - ShatterItem.sphere.x = fx->pos.Position.x; - ShatterItem.sphere.y = fx->pos.Position.y; - ShatterItem.sphere.z = fx->pos.Position.z; + ShatterItem.sphere.Center = fx->pos.Position.ToVector3(); ShatterItem.bit = 0; ShatterItem.flags = fx->flag2 & 0x1400; diff --git a/TombEngine/Objects/TR5/Object/tr5_raisingblock.cpp b/TombEngine/Objects/TR5/Object/tr5_raisingblock.cpp index 24835ed4d..94fc6c279 100644 --- a/TombEngine/Objects/TR5/Object/tr5_raisingblock.cpp +++ b/TombEngine/Objects/TR5/Object/tr5_raisingblock.cpp @@ -74,8 +74,8 @@ namespace TEN::Entities::Generic short roomNumber = item->RoomNumber; auto* floor = GetFloor(item->Pose.Position.x, item->Pose.Position.y, item->Pose.Position.z, &roomNumber); - if (floor->Box != NO_VALUE) - g_Level.Boxes[floor->Box].flags &= ~BLOCKED; + if (floor->PathfindingBoxID != NO_VALUE) + g_Level.PathfindingBoxes[floor->PathfindingBoxID].flags &= ~BLOCKED; // Set mutators to EulerAngles identity by default. for (auto& mutator : item->Model.Mutators) @@ -93,7 +93,7 @@ namespace TEN::Entities::Generic void ShakeRaisingBlock(ItemInfo* item) { - SoundEffect(SFX_TR4_RAISING_BLOCK, &item->Pose); + SoundEffect(SFX_TR4_RAISING_BLOCK_2, &item->Pose); if (item->TriggerFlags == 0) return; diff --git a/TombEngine/Objects/TR5/Object/tr5_rollingball.cpp b/TombEngine/Objects/TR5/Object/tr5_rollingball.cpp index 02774019c..fe7088ff1 100644 --- a/TombEngine/Objects/TR5/Object/tr5_rollingball.cpp +++ b/TombEngine/Objects/TR5/Object/tr5_rollingball.cpp @@ -4,7 +4,8 @@ #include "Game/animation.h" #include "Game/camera.h" #include "Game/collision/collide_item.h" -#include "Game/collision/sphere.h" +#include "Game/collision/Point.h" +#include "Game/collision/Sphere.h" #include "Game/control/control.h" #include "Game/effects/effects.h" #include "Game/items.h" @@ -15,6 +16,10 @@ #include "Sound/sound.h" #include "Specific/level.h" +using namespace TEN::Collision::Point; + +using namespace TEN::Collision::Sphere; + constexpr auto ROLLING_BALL_MAX_VELOCITY = BLOCK(3); void RollingBallCollision(short itemNumber, ItemInfo* laraItem, CollisionInfo* coll) @@ -22,7 +27,7 @@ void RollingBallCollision(short itemNumber, ItemInfo* laraItem, CollisionInfo* c auto* ballItem = &g_Level.Items[itemNumber]; if (!TestBoundsCollide(ballItem, laraItem, coll->Setup.Radius) || - !TestCollision(ballItem, laraItem)) + !HandleItemSphereCollision(*ballItem, *laraItem)) { return; } @@ -36,6 +41,11 @@ void RollingBallCollision(short itemNumber, ItemInfo* laraItem, CollisionInfo* c !TestEnvironment(RoomEnvFlags::ENV_FLAG_WATER, laraItem)) { SetAnimation(laraItem, LA_BOULDER_DEATH); + + Camera.flags = CF_FOLLOW_CENTER; + Camera.targetAngle = ANGLE(170.0f); + Camera.targetElevation = ANGLE(-25.0f); + Camera.targetDistance = BLOCK(2); } } else @@ -66,7 +76,7 @@ void RollingBallControl(short itemNumber) item->Pose.Position.z += item->ItemFlags[1] / hDivider; item->Animation.Velocity.z = Vector3i::Distance(item->Pose.Position, oldPos.Position); - int dh = GetCollision(item).Position.Floor - bigRadius; + int dh = GetPointCollision(*item).GetFloorHeight() - bigRadius; if (item->Pose.Position.y > dh) { @@ -94,7 +104,10 @@ void RollingBallControl(short itemNumber) item->Animation.Velocity.y = -(GetRandomControl() % int(round(item->Animation.Velocity.z) / 8.0f)); } else + { item->Animation.Velocity.y = -item->Animation.Velocity.y / 4.0f; + item->DisableInterpolation = true; + } } int frontX = item->Pose.Position.x; @@ -106,20 +119,20 @@ void RollingBallControl(short itemNumber) int leftX = item->Pose.Position.x - smallRadius; int leftZ = item->Pose.Position.z; - auto frontFloor = GetCollision(frontX, item->Pose.Position.y, frontZ, item->RoomNumber); - auto backFloor = GetCollision(backX, item->Pose.Position.y, backZ, item->RoomNumber); - auto rightFloor = GetCollision(rightX, item->Pose.Position.y, rightZ, item->RoomNumber); - auto leftFloor = GetCollision(leftX, item->Pose.Position.y, leftZ, item->RoomNumber); + auto frontFloor = GetPointCollision(Vector3i(frontX, item->Pose.Position.y, frontZ), item->RoomNumber); + auto backFloor = GetPointCollision(Vector3i(backX, item->Pose.Position.y, backZ), item->RoomNumber); + auto rightFloor = GetPointCollision(Vector3i(rightX, item->Pose.Position.y, rightZ), item->RoomNumber); + auto leftFloor = GetPointCollision(Vector3i(leftX, item->Pose.Position.y, leftZ), item->RoomNumber); - int frontHeight = frontFloor.Position.Floor - bigRadius; - int backHeight = backFloor.Position.Floor - bigRadius; - int rightHeight = rightFloor.Position.Floor - bigRadius; - int leftHeight = leftFloor.Position.Floor - bigRadius; + int frontHeight = frontFloor.GetFloorHeight() - bigRadius; + int backHeight = backFloor.GetFloorHeight() - bigRadius; + int rightHeight = rightFloor.GetFloorHeight() - bigRadius; + int leftHeight = leftFloor.GetFloorHeight() - bigRadius; - int frontCeiling = frontFloor.Position.Ceiling + bigRadius; - int backCeiling = backFloor.Position.Ceiling + bigRadius; - int rightCeiling = rightFloor.Position.Ceiling + bigRadius; - int leftCeiling = leftFloor.Position.Ceiling + bigRadius; + int frontCeiling = frontFloor.GetCeilingHeight() + bigRadius; + int backCeiling = backFloor.GetCeilingHeight() + bigRadius; + int rightCeiling = rightFloor.GetCeilingHeight() + bigRadius; + int leftCeiling = leftFloor.GetCeilingHeight() + bigRadius; frontX = item->Pose.Position.x; frontZ = item->Pose.Position.z + bigRadius; @@ -130,20 +143,20 @@ void RollingBallControl(short itemNumber) leftX = item->Pose.Position.x - bigRadius; leftZ = item->Pose.Position.z; - auto fronFarFloor = GetCollision(frontX, item->Pose.Position.y, frontZ, item->RoomNumber); - auto backFarFloor = GetCollision(backX, item->Pose.Position.y, backZ, item->RoomNumber); - auto rightFarFloor = GetCollision(rightX, item->Pose.Position.y, rightZ, item->RoomNumber); - auto leftFarFloor = GetCollision(leftX, item->Pose.Position.y, leftZ, item->RoomNumber); + auto fronFarFloor = GetPointCollision(Vector3i(frontX, item->Pose.Position.y, frontZ), item->RoomNumber); + auto backFarFloor = GetPointCollision(Vector3i(backX, item->Pose.Position.y, backZ), item->RoomNumber); + auto rightFarFloor = GetPointCollision(Vector3i(rightX, item->Pose.Position.y, rightZ), item->RoomNumber); + auto leftFarFloor = GetPointCollision(Vector3i(leftX, item->Pose.Position.y, leftZ), item->RoomNumber); - int frontFarHeight = fronFarFloor.Position.Floor - bigRadius; - int backFarHeight = backFarFloor.Position.Floor - bigRadius; - int rightFarHeight = rightFarFloor.Position.Floor - bigRadius; - int leftFarHeight = leftFarFloor.Position.Floor - bigRadius; + int frontFarHeight = fronFarFloor.GetFloorHeight() - bigRadius; + int backFarHeight = backFarFloor.GetFloorHeight() - bigRadius; + int rightFarHeight = rightFarFloor.GetFloorHeight() - bigRadius; + int leftFarHeight = leftFarFloor.GetFloorHeight() - bigRadius; - int frontFarCeiling = fronFarFloor.Position.Ceiling + bigRadius; - int backFarCeiling = backFarFloor.Position.Ceiling + bigRadius; - int rightFarCeiling = rightFarFloor.Position.Ceiling + bigRadius; - int leftFarCeiling = leftFarFloor.Position.Ceiling + bigRadius; + int frontFarCeiling = fronFarFloor.GetCeilingHeight() + bigRadius; + int backFarCeiling = backFarFloor.GetCeilingHeight() + bigRadius; + int rightFarCeiling = rightFarFloor.GetCeilingHeight() + bigRadius; + int leftFarCeiling = leftFarFloor.GetCeilingHeight() + bigRadius; if (item->Pose.Position.y - dh > -CLICK(1) || item->Pose.Position.y - frontFarHeight >= CLICK(2) || @@ -256,23 +269,22 @@ void RollingBallControl(short itemNumber) } } - auto roomNumber = GetCollision(item).RoomNumber; - - if (item->RoomNumber != roomNumber) + auto pointColl = GetPointCollision(*item); + if (item->RoomNumber != pointColl.GetRoomNumber()) { - if (TestEnvironment(RoomEnvFlags::ENV_FLAG_WATER, roomNumber) && + if (TestEnvironment(RoomEnvFlags::ENV_FLAG_WATER, pointColl.GetRoomNumber()) && !TestEnvironment(RoomEnvFlags::ENV_FLAG_WATER, item->RoomNumber)) { - int waterHeight = GetWaterHeight(item->Pose.Position.x, item->Pose.Position.y, item->Pose.Position.z, roomNumber); + int waterHeight = pointColl.GetWaterTopHeight(); SplashSetup.y = waterHeight - 1; SplashSetup.x = item->Pose.Position.x; SplashSetup.z = item->Pose.Position.z; SplashSetup.splashPower = item->Animation.Velocity.y * 4; SplashSetup.innerRadius = 160; - SetupSplash(&SplashSetup, roomNumber); + SetupSplash(&SplashSetup, pointColl.GetRoomNumber()); } - ItemNewRoom(itemNumber, roomNumber); + ItemNewRoom(itemNumber, pointColl.GetRoomNumber()); } if (item->ItemFlags[0] > ROLLING_BALL_MAX_VELOCITY) @@ -303,6 +315,8 @@ void RollingBallControl(short itemNumber) } else item->Pose.Orientation.y = angle; + + item->DisableInterpolation = true; } item->Pose.Orientation.x -= ((abs(item->ItemFlags[0]) + abs(item->ItemFlags[1])) / 2) / vDivider; @@ -320,7 +334,7 @@ void ClassicRollingBallCollision(short itemNum, ItemInfo* lara, CollisionInfo* c if (!TestBoundsCollide(item, lara, coll->Setup.Radius)) return; - if (!TestCollision(item, lara)) + if (!HandleItemSphereCollision(*item, *lara)) return; if (lara->Animation.IsAirborne) @@ -355,8 +369,9 @@ void ClassicRollingBallCollision(short itemNum, ItemInfo* lara, CollisionInfo* c SetAnimation(lara, LA_BOULDER_DEATH); Camera.flags = CF_FOLLOW_CENTER; - Camera.targetAngle = ANGLE(170); - Camera.targetElevation = -ANGLE(25); + Camera.targetAngle = ANGLE(170.0f); + Camera.targetElevation = -ANGLE(-25.0f); + Camera.targetDistance = BLOCK(2); for (int i = 0; i < 15; i++) { @@ -406,12 +421,12 @@ void ClassicRollingBallControl(short itemNum) AnimateItem(item); - auto coll = GetCollision(item->Pose.Position.x, item->Pose.Position.y, item->Pose.Position.z, item->RoomNumber); + auto pointColl = GetPointCollision(*item); - item->Floor = coll.Position.Floor; + item->Floor = pointColl.GetFloorHeight(); - if (item->RoomNumber != coll.RoomNumber) - ItemNewRoom(itemNum, coll.RoomNumber); + if (item->RoomNumber != pointColl.GetRoomNumber()) + ItemNewRoom(itemNum, pointColl.GetRoomNumber()); if (item->Pose.Position.y >= item->Floor - CLICK(1)) { @@ -443,8 +458,8 @@ void ClassicRollingBallControl(short itemNum) int x = item->Pose.Position.x + dist * phd_sin(item->Pose.Orientation.y); int z = item->Pose.Position.z + dist * phd_cos(item->Pose.Orientation.y); - int y1 = GetCollision(x, item->Pose.Position.y, z, item->RoomNumber).Position.Floor; - int y2 = GetCollision(x, item->Pose.Position.y - ydist, z, item->RoomNumber).Position.Ceiling; + int y1 = GetPointCollision(Vector3i(x, item->Pose.Position.y, z), item->RoomNumber).GetFloorHeight(); + int y2 = GetPointCollision(Vector3i(x, item->Pose.Position.y - ydist, z), item->RoomNumber).GetCeilingHeight(); if (y1 < item->Pose.Position.y || y2 > (item->Pose.Position.y - ydist)) { diff --git a/TombEngine/Objects/TR5/Object/tr5_twoblockplatform.cpp b/TombEngine/Objects/TR5/Object/tr5_twoblockplatform.cpp index 5fa94b726..99bfeee01 100644 --- a/TombEngine/Objects/TR5/Object/tr5_twoblockplatform.cpp +++ b/TombEngine/Objects/TR5/Object/tr5_twoblockplatform.cpp @@ -3,6 +3,7 @@ #include "Game/collision/collide_room.h" #include "Game/collision/floordata.h" +#include "Game/collision/Point.h" #include "Game/control/control.h" #include "Game/items.h" #include "Game/Lara/lara.h" @@ -11,8 +12,8 @@ #include "Renderer/Renderer.h" #include "Sound/sound.h" -using namespace TEN::Renderer; using namespace TEN::Collision::Floordata; +using namespace TEN::Collision::Point; using namespace TEN::Math; using namespace TEN::Renderer; @@ -81,18 +82,18 @@ namespace TEN::Entities::Generic return; } - int distToPortal = *&g_Level.Rooms[item->RoomNumber].maxceiling - item->Pose.Position.y; + int distToPortal = *&g_Level.Rooms[item->RoomNumber].TopHeight - item->Pose.Position.y; if (distToPortal <= speed) UpdateBridgeItem(*item); - auto probe = GetCollision(item); + auto probe = GetPointCollision(*item); - item->Floor = probe.Position.Floor; + item->Floor = probe.GetFloorHeight(); - if (probe.RoomNumber != item->RoomNumber) + if (probe.GetRoomNumber() != item->RoomNumber) { UpdateBridgeItem(*item, true); - ItemNewRoom(itemNumber, probe.RoomNumber); + ItemNewRoom(itemNumber, probe.GetRoomNumber()); UpdateBridgeItem(*item); } } @@ -127,7 +128,7 @@ namespace TEN::Entities::Generic } else { - SoundEffect(SFX_TR4_RUMBLE_NEXTDOOR, &item->Pose); + SoundEffect(SFX_TR4_RAISING_BLOCK_2, &item->Pose); item->Pose.Position.y -= 4; } } @@ -139,7 +140,7 @@ namespace TEN::Entities::Generic } else { - SoundEffect(SFX_TR4_RUMBLE_NEXTDOOR, &item->Pose); + SoundEffect(SFX_TR4_RAISING_BLOCK_2, &item->Pose); item->Pose.Position.y += 4; } } diff --git a/TombEngine/Objects/TR5/Shatter/tr5_smashobject.cpp b/TombEngine/Objects/TR5/Shatter/tr5_smashobject.cpp index 42eda843b..04ede674b 100644 --- a/TombEngine/Objects/TR5/Shatter/tr5_smashobject.cpp +++ b/TombEngine/Objects/TR5/Shatter/tr5_smashobject.cpp @@ -6,23 +6,25 @@ #include "Game/effects/tomb4fx.h" #include "Game/items.h" +using namespace TEN::Collision::Room; + void InitializeSmashObject(short itemNumber) { auto* item = &g_Level.Items[itemNumber]; item->Flags = 0; item->MeshBits = 1; - auto* room = &g_Level.Rooms[item->RoomNumber]; + auto& room = g_Level.Rooms[item->RoomNumber]; // NOTE: Avoids crash when attempting to access Boxes[] array while box is equal to NO_VALUE. -- TokyoSU 2022.12.20 - FloorInfo* floor = GetSector(room, item->Pose.Position.x - room->x, item->Pose.Position.z - room->z); - if (floor->Box == NO_VALUE) + FloorInfo* floor = GetSector(&room, item->Pose.Position.x - room.Position.x, item->Pose.Position.z - room.Position.z); + if (floor->PathfindingBoxID == NO_VALUE) { TENLog("Smash object with ID " + std::to_string(itemNumber) + " may be inside a wall." , LogLevel::Warning); return; } - auto* box = &g_Level.Boxes[floor->Box]; + auto* box = &g_Level.PathfindingBoxes[floor->PathfindingBoxID]; if (box->flags & 0x8000) box->flags |= BLOCKED; } @@ -32,11 +34,11 @@ void SmashObject(short itemNumber) auto* item = &g_Level.Items[itemNumber]; auto* room = &g_Level.Rooms[item->RoomNumber]; - int sector = ((item->Pose.Position.z - room->z) / 1024) + room->zSize * ((item->Pose.Position.x - room->x) / 1024); + int sector = ((item->Pose.Position.z - room->Position.z) / 1024) + room->ZSize * ((item->Pose.Position.x - room->Position.z) / 1024); - auto* box = &g_Level.Boxes[room->floor[sector].Box]; + auto* box = &g_Level.PathfindingBoxes[room->Sectors[sector].PathfindingBoxID]; if (box->flags & 0x8000) - box->flags &= ~BOX_BLOCKED; + box->flags &= ~BLOCKED; SoundEffect(SFX_TR5_SMASH_GLASS, &item->Pose); diff --git a/TombEngine/Objects/TR5/Trap/LaserBarrier.cpp b/TombEngine/Objects/TR5/Trap/LaserBarrier.cpp index ad6020acf..45db36846 100644 --- a/TombEngine/Objects/TR5/Trap/LaserBarrier.cpp +++ b/TombEngine/Objects/TR5/Trap/LaserBarrier.cpp @@ -3,15 +3,17 @@ #include "Game/collision/collide_room.h" #include "Game/collision/floordata.h" +#include "Game/collision/Point.h" #include "Game/effects/effects.h" #include "Game/effects/item_fx.h" #include "Game/items.h" #include "Game/Lara/lara.h" #include "Specific/level.h" +using namespace TEN::Collision::Point; using namespace TEN::Effects::Items; -namespace TEN::Traps::TR5 +namespace TEN::Entities::Traps { // NOTES: // item.ItemFlags[0] = barrier height. @@ -58,7 +60,7 @@ namespace TEN::Traps::TR5 auto beamOffset = Vector3(0.0f, -LaserBarrierBeam::HEIGHT, 0.0f); for (auto& beam : Beams) { - assertion(beam.VertexPoints.size() == baseVertices.size(), "Laser barrier beam vertex count out of sync."); + TENAssert(beam.VertexPoints.size() == baseVertices.size(), "Laser barrier beam vertex count out of sync."); for (int i = 0; i < beam.VertexPoints.size(); i++) beam.VertexPoints[i] = baseVertices[i] + beamOffset; @@ -85,8 +87,8 @@ namespace TEN::Traps::TR5 auto& item = g_Level.Items[itemNumber]; // Initialize barrier height. - auto pointColl = GetCollision(&item); - float barrierHeight = item.Pose.Position.y - pointColl.Position.Ceiling; + auto pointColl = GetPointCollision(item); + float barrierHeight = item.Pose.Position.y - pointColl.GetCeilingHeight(); item.ItemFlags[0] = barrierHeight; // Initialize barrier effect. diff --git a/TombEngine/Objects/TR5/Trap/LaserBarrier.h b/TombEngine/Objects/TR5/Trap/LaserBarrier.h index 4f0f6bfa2..e2c9e6023 100644 --- a/TombEngine/Objects/TR5/Trap/LaserBarrier.h +++ b/TombEngine/Objects/TR5/Trap/LaserBarrier.h @@ -6,7 +6,7 @@ using namespace TEN::Math; struct CollisionInfo; struct ItemInfo; -namespace TEN::Traps::TR5 +namespace TEN::Entities::Traps { struct LaserBarrierBeam { diff --git a/TombEngine/Objects/TR5/Trap/LaserBeam.cpp b/TombEngine/Objects/TR5/Trap/LaserBeam.cpp index 24440830b..83574e637 100644 --- a/TombEngine/Objects/TR5/Trap/LaserBeam.cpp +++ b/TombEngine/Objects/TR5/Trap/LaserBeam.cpp @@ -3,6 +3,7 @@ #include "Game/collision/collide_room.h" #include "Game/collision/floordata.h" +#include "Game/collision/Point.h" #include "Game/control/los.h" #include "Game/effects/effects.h" #include "Game/effects/item_fx.h" @@ -14,12 +15,13 @@ #include "Renderer/Renderer.h" #include "Specific/level.h" +using namespace TEN::Collision::Point; using namespace TEN::Effects::Items; using namespace TEN::Effects::Spark; using namespace TEN::Math; using namespace TEN::Renderer; -namespace TEN::Traps::TR5 +namespace TEN::Entities::Traps { constexpr auto LASER_BEAM_LIGHT_INTENSITY = 0.2f; constexpr auto LASER_BEAM_LIGHT_AMPLITUDE_MAX = 0.1f; @@ -78,6 +80,16 @@ namespace TEN::Traps::TR5 TriggerDynamicLight(pos, color * intensityNorm, FALLOFF); } + void LaserBeamEffect::StoreInterpolationData() + { + for (int i = 0; i < Vertices.size(); i++) + { + OldVertices[i] = Vertices[i]; + } + + OldColor = Color; + } + void LaserBeamEffect::Update(const ItemInfo& item) { auto orient = EulerAngles(item.Pose.Orientation.x + ANGLE(180.0f), item.Pose.Orientation.y, item.Pose.Orientation.z); @@ -87,7 +99,7 @@ namespace TEN::Traps::TR5 auto origin = GameVector(item.Pose.Position, item.RoomNumber); auto target = GameVector( Geometry::TranslatePoint(origin.ToVector3(), dir, MAX_VISIBILITY_DISTANCE), - GetCollision(origin.ToVector3i(), origin.RoomNumber, dir, MAX_VISIBILITY_DISTANCE).RoomNumber); + GetPointCollision(origin.ToVector3i(), origin.RoomNumber, dir, MAX_VISIBILITY_DISTANCE).GetRoomNumber()); // Hit wall; spawn sparks and light. if (!LOS(&origin, &target)) @@ -152,6 +164,8 @@ namespace TEN::Traps::TR5 return; } + beam.StoreInterpolationData(); + // Brightness fade-in and distortion. if (item.Model.Color.w < 1.0f) item.Model.Color.w += 0.02f; @@ -192,9 +206,9 @@ namespace TEN::Traps::TR5 auto rotMatrix = EulerAngles(item.Pose.Orientation.x + ANGLE(180.0f), item.Pose.Orientation.y, item.Pose.Orientation.z); auto target = GameVector(Geometry::TranslatePoint(origin.ToVector3(), rotMatrix, MAX_VISIBILITY_DISTANCE), 0); - auto pointColl = GetCollision(target.ToVector3i(),item.RoomNumber); - if (pointColl.RoomNumber != target.RoomNumber) - target.RoomNumber = pointColl.RoomNumber; + auto pointColl = GetPointCollision(target.ToVector3i(), item.RoomNumber); + if (pointColl.GetRoomNumber() != target.RoomNumber) + target.RoomNumber = pointColl.GetRoomNumber(); bool los2 = LOS(&origin, &target); diff --git a/TombEngine/Objects/TR5/Trap/LaserBeam.h b/TombEngine/Objects/TR5/Trap/LaserBeam.h index 168e1e2fc..c1d7dc8a0 100644 --- a/TombEngine/Objects/TR5/Trap/LaserBeam.h +++ b/TombEngine/Objects/TR5/Trap/LaserBeam.h @@ -6,7 +6,7 @@ using namespace TEN::Math; struct CollisionInfo; struct ItemInfo; -namespace TEN::Traps::TR5 +namespace TEN::Entities::Traps { struct LaserBeamEffect { @@ -22,8 +22,13 @@ namespace TEN::Traps::TR5 bool IsLethal = false; bool IsHeavyActivator = false; + std::array OldVertices = {}; + Vector4 OldColor; + void Initialize(const ItemInfo& item); void Update(const ItemInfo& item); + + void StoreInterpolationData(); }; extern std::unordered_map LaserBeams; diff --git a/TombEngine/Objects/TR5/Trap/ZipLine.cpp b/TombEngine/Objects/TR5/Trap/ZipLine.cpp index 252103105..661ecfc8b 100644 --- a/TombEngine/Objects/TR5/Trap/ZipLine.cpp +++ b/TombEngine/Objects/TR5/Trap/ZipLine.cpp @@ -3,6 +3,7 @@ #include "Game/animation.h" #include "Game/collision/collide_item.h" +#include "Game/collision/Point.h" #include "Game/control/box.h" #include "Game/items.h" #include "Game/Lara/lara.h" @@ -12,10 +13,11 @@ #include "Sound/sound.h" #include "Specific/Input/Input.h" +using namespace TEN::Collision::Point; using namespace TEN::Input; using namespace TEN::Math; -namespace TEN::Traps::TR5 +namespace TEN::Entities::Traps { const auto ZipLineInteractOffset = Vector3i(0, 0, 371); const auto ZipLineInteractBasis = ObjectCollisionBounds @@ -129,13 +131,13 @@ namespace TEN::Traps::TR5 zipLineItem.Pose.Position.y += ((int)zipLineItem.Animation.Velocity.y >> 2); int vPos = zipLineItem.Pose.Position.y + CLICK(0.25f); - auto pointColl = GetCollision(&zipLineItem, zipLineItem.Pose.Orientation.y, zipLineItem.Animation.Velocity.y); + auto pointColl = GetPointCollision(zipLineItem, zipLineItem.Pose.Orientation.y, zipLineItem.Animation.Velocity.y); // Update zip line room number. - if (pointColl.RoomNumber != zipLineItem.RoomNumber) - ItemNewRoom(itemNumber, pointColl.RoomNumber); + if (pointColl.GetRoomNumber() != zipLineItem.RoomNumber) + ItemNewRoom(itemNumber, pointColl.GetRoomNumber()); - if (pointColl.Position.Floor <= (vPos + CLICK(1)) || pointColl.Position.Ceiling >= (vPos - CLICK(1))) + if (pointColl.GetFloorHeight() <= (vPos + CLICK(1)) || pointColl.GetCeilingHeight() >= (vPos - CLICK(1))) { // Dismount. if (laraItem.Animation.ActiveState == LS_ZIP_LINE) diff --git a/TombEngine/Objects/TR5/Trap/ZipLine.h b/TombEngine/Objects/TR5/Trap/ZipLine.h index 3cdcb5c50..5c5c2fbab 100644 --- a/TombEngine/Objects/TR5/Trap/ZipLine.h +++ b/TombEngine/Objects/TR5/Trap/ZipLine.h @@ -3,7 +3,7 @@ struct CollisionInfo; struct ItemInfo; -namespace TEN::Traps::TR5 +namespace TEN::Entities::Traps { void InitializeZipLine(short itemNumber); void CollideZipLine(short itemNumber, ItemInfo* laraItem, CollisionInfo* coll); diff --git a/TombEngine/Objects/TR5/Trap/tr5_explosion.cpp b/TombEngine/Objects/TR5/Trap/tr5_explosion.cpp index 8b33196a2..b80b0de18 100644 --- a/TombEngine/Objects/TR5/Trap/tr5_explosion.cpp +++ b/TombEngine/Objects/TR5/Trap/tr5_explosion.cpp @@ -23,168 +23,175 @@ using namespace TEN::Effects::Items; using namespace TEN::Entities::Switches; -void InitializeExplosion(short itemNumber) +namespace TEN::Entities::Traps { - auto* item = &g_Level.Items[itemNumber]; - - if (item->TriggerFlags >= 30000) + void InitializeExplosion(short itemNumber) { - item->ItemFlags[1] = 3; - item->TriggerFlags -= 30000; - } - else if (item->TriggerFlags >= 20000) - { - item->ItemFlags[1] = 2; - item->TriggerFlags -= 20000; - } - else if (item->TriggerFlags >= 10000) - { - item->ItemFlags[1] = 1; - item->TriggerFlags -= 10000; - } + auto& item = g_Level.Items[itemNumber]; - if (item->TriggerFlags >= 1000) - { - item->ItemFlags[3] = 1; - item->TriggerFlags -= 1000; - } - - item->ItemFlags[2] = item->TriggerFlags / 100; - item->TriggerFlags = 7 * (item->TriggerFlags % 100); -} - -void ExplosionControl(short itemNumber) -{ - auto* item = &g_Level.Items[itemNumber]; - - if (TriggerActive(item)) - { - item->Flags |= IFLAG_INVISIBLE; - - if (item->ItemFlags[0] < item->TriggerFlags) + if (item.TriggerFlags >= 30000) { - ++item->ItemFlags[0]; + item.ItemFlags[1] = 3; + item.TriggerFlags -= 30000; } - else if (item->ItemFlags[0] == item->TriggerFlags) + else if (item.TriggerFlags >= 20000) { - int flag; - ++item->ItemFlags[0]; + item.ItemFlags[1] = 2; + item.TriggerFlags -= 20000; + } + else if (item.TriggerFlags >= 10000) + { + item.ItemFlags[1] = 1; + item.TriggerFlags -= 10000; + } - if (TestEnvironment(ENV_FLAG_WATER, item->RoomNumber) || - TestEnvironment(ENV_FLAG_SWAMP, item->RoomNumber)) - { - flag = 1; - } - else - { - flag = item->ItemFlags[1] == 1 ? 2 : 0; - } - - SoundEffect(SFX_TR4_EXPLOSION1, &item->Pose, SoundEnvironment::Land, 1.5f); - SoundEffect(SFX_TR4_EXPLOSION2, &item->Pose); - TriggerExplosionSparks(item->Pose.Position.x, item->Pose.Position.y, item->Pose.Position.z, 3, -2, flag, item->RoomNumber); - - for (int i = 0; i < item->ItemFlags[2]; ++i) - { - TriggerExplosionSparks( - item->Pose.Position.x + (GetRandomControl() % 128 - 64) * item->ItemFlags[2], - item->Pose.Position.y + (GetRandomControl() % 128 - 64) * item->ItemFlags[2], - item->Pose.Position.z + (GetRandomControl() % 128 - 64) * item->ItemFlags[2], - 2, 0, flag, item->RoomNumber); - } - - Pose pos; - pos.Position.x = item->Pose.Position.x; - pos.Position.y = item->Pose.Position.y - 128; - pos.Position.z = item->Pose.Position.z; - - if (item->ItemFlags[3]) - { - if (flag == 2) - TriggerShockwave(&pos, 48, 32 * item->ItemFlags[2] + 304, 4 * item->ItemFlags[2] + 96, 0, 96, 128, 24, EulerAngles(2048, 0.0f, 0.0f), 0, true, false, false, (int)ShockwaveStyle::Normal); - else - TriggerShockwave(&pos, 48, 32 * item->ItemFlags[2] + 304, 4 * item->ItemFlags[2] + 96, 128, 96, 0, 24, EulerAngles(2048, 0.0f, 0.0f), 0, true, false, false, (int)ShockwaveStyle::Normal); - } + if (item.TriggerFlags >= 1000) + { + item.ItemFlags[3] = 1; + item.TriggerFlags -= 1000; + } - if (flag != 2) - { - auto vec = GetJointPosition(LaraItem, LM_HIPS); + item.ItemFlags[2] = item.TriggerFlags / 100; + item.TriggerFlags = 7 * (item.TriggerFlags % 100); + } - int dx = vec.x - item->Pose.Position.x; - int dy = vec.y - item->Pose.Position.y; - int dz = vec.z - item->Pose.Position.z; - - if (abs(dx) < BLOCK(1) && - abs(dy) < BLOCK(1) && - abs(dz) < BLOCK(1)) + void ControlExplosion(short itemNumber) + { + auto& item = g_Level.Items[itemNumber]; + + if (TriggerActive(&item)) + { + item.Flags |= IFLAG_INVISIBLE; + + if (item.ItemFlags[0] < item.TriggerFlags) + { + ++item.ItemFlags[0]; + } + else if (item.ItemFlags[0] == item.TriggerFlags) + { + int flag; + ++item.ItemFlags[0]; + + if (TestEnvironment(ENV_FLAG_WATER, item.RoomNumber) || + TestEnvironment(ENV_FLAG_SWAMP, item.RoomNumber)) { - int distance = sqrt(pow(dx, 2) + pow(dy, 2) + pow(dz, 2)); - if (distance < BLOCK(2)) - { - DoDamage(LaraItem, distance / 16); - - if (distance < CLICK(3)) - ItemBurn(LaraItem); - } + flag = 1; } - } - - auto collObjects = GetCollidedObjects(*item, true, true, BLOCK(2), ObjectCollectionMode::All); - if (!collObjects.IsEmpty()) - { - for (auto* itemPtr : collObjects.ItemPtrs) + else { - if (itemPtr->ObjectNumber >= ID_SMASH_OBJECT1 && itemPtr->ObjectNumber <= ID_SMASH_OBJECT16) + flag = item.ItemFlags[1] == 1 ? 2 : 0; + } + + SoundEffect(SFX_TR4_EXPLOSION1, &item.Pose, SoundEnvironment::Land, 1.5f); + SoundEffect(SFX_TR4_EXPLOSION2, &item.Pose); + TriggerExplosionSparks(item.Pose.Position.x, item.Pose.Position.y, item.Pose.Position.z, 3, -2, flag, item.RoomNumber); + + for (int i = 0; i < item.ItemFlags[2]; ++i) + { + TriggerExplosionSparks( + item.Pose.Position.x + (GetRandomControl() % 128 - 64) * item.ItemFlags[2], + item.Pose.Position.y + (GetRandomControl() % 128 - 64) * item.ItemFlags[2], + item.Pose.Position.z + (GetRandomControl() % 128 - 64) * item.ItemFlags[2], + 2, 0, flag, item.RoomNumber); + } + + auto pose = Pose::Zero; + pose.Position.x = item.Pose.Position.x; + pose.Position.y = item.Pose.Position.y - 128; + pose.Position.z = item.Pose.Position.z; + + if (item.ItemFlags[3]) + { + if (flag == 2) { - TriggerExplosionSparks(itemPtr->Pose.Position.x, itemPtr->Pose.Position.y, itemPtr->Pose.Position.z, 3, -2, 0, itemPtr->RoomNumber); - itemPtr->Pose.Position.y -= 128; - TriggerShockwave(&itemPtr->Pose, 48, 304, 96, 128, 96, 0, 24, EulerAngles::Identity, 0, true, false, false, (int)ShockwaveStyle::Normal); - itemPtr->Pose.Position.y += 128; - ExplodeItemNode(itemPtr, 0, 0, 80); - SmashObject(itemPtr->Index); - KillItem(itemPtr->Index); - } - else if (itemPtr->ObjectNumber != ID_SWITCH_TYPE7 && itemPtr->ObjectNumber != ID_SWITCH_TYPE8) - { - if (Objects[itemPtr->ObjectNumber].intelligent) - DoExplosiveDamage(*LaraItem, *itemPtr, *item, Weapons[(int)LaraWeaponType::GrenadeLauncher].ExplosiveDamage); + TriggerShockwave(&pose, 48, 32 * item.ItemFlags[2] + 304, 4 * item.ItemFlags[2] + 96, 0, 96, 128, 24, EulerAngles(2048, 0.0f, 0.0f), 0, true, false, false, (int)ShockwaveStyle::Normal); } else { - // @FIXME: This calls CrossbowHitSwitchType78() + TriggerShockwave(&pose, 48, 32 * item.ItemFlags[2] + 304, 4 * item.ItemFlags[2] + 96, 128, 96, 0, 24, EulerAngles(2048, 0.0f, 0.0f), 0, true, false, false, (int)ShockwaveStyle::Normal); } } - for (auto* staticPtr : collObjects.StaticPtrs) + if (flag != 2) { - if (StaticObjects[staticPtr->staticNumber].shatterType != ShatterType::None) + auto vec = GetJointPosition(LaraItem, LM_HIPS); + + int dx = vec.x - item.Pose.Position.x; + int dy = vec.y - item.Pose.Position.y; + int dz = vec.z - item.Pose.Position.z; + + if (abs(dx) < BLOCK(1) && + abs(dy) < BLOCK(1) && + abs(dz) < BLOCK(1)) { - TriggerExplosionSparks(staticPtr->pos.Position.x, staticPtr->pos.Position.y, staticPtr->pos.Position.z, 3, -2, 0, item->RoomNumber); - staticPtr->pos.Position.y -= 128; - TriggerShockwave(&staticPtr->pos, 40, 176, 64, 128, 96, 0, 16, EulerAngles::Identity, 0, true, false, false, (int)ShockwaveStyle::Normal); - staticPtr->pos.Position.y += 128; - SoundEffect(GetShatterSound(staticPtr->staticNumber), &staticPtr->pos); - ShatterObject(nullptr, staticPtr, -128, item->RoomNumber, 0); + int distance = sqrt(pow(dx, 2) + pow(dy, 2) + pow(dz, 2)); + if (distance < BLOCK(2)) + { + DoDamage(LaraItem, distance / 16); + + if (distance < CLICK(3)) + ItemBurn(LaraItem); + } } } - AlertNearbyGuards(item); - } - - if (item->ItemFlags[1] >= 2) - { - if (item->ItemFlags[1] == 3) + auto collObjects = GetCollidedObjects(item, true, true, BLOCK(2), ObjectCollectionMode::All); + if (!collObjects.IsEmpty()) { - short triggerItems[8]; - for (int i = GetSwitchTrigger(item, triggerItems, 1); i > 0; --i) - g_Level.Items[triggerItems[i - 1]].ItemFlags[0] = 0; - - item->ItemFlags[0] = 0; + for (auto* itemPtr : collObjects.Items) + { + if (itemPtr->ObjectNumber >= ID_SMASH_OBJECT1 && itemPtr->ObjectNumber <= ID_SMASH_OBJECT16) + { + TriggerExplosionSparks(itemPtr->Pose.Position.x, itemPtr->Pose.Position.y, itemPtr->Pose.Position.z, 3, -2, 0, itemPtr->RoomNumber); + itemPtr->Pose.Position.y -= 128; + TriggerShockwave(&itemPtr->Pose, 48, 304, 96, 128, 96, 0, 24, EulerAngles::Identity, 0, true, false, false, (int)ShockwaveStyle::Normal); + itemPtr->Pose.Position.y += 128; + ExplodeItemNode(itemPtr, 0, 0, 80); + SmashObject(itemPtr->Index); + KillItem(itemPtr->Index); + } + else if (itemPtr->ObjectNumber != ID_SWITCH_TYPE7 && itemPtr->ObjectNumber != ID_SWITCH_TYPE8) + { + if (Objects[itemPtr->ObjectNumber].intelligent) + DoExplosiveDamage(*LaraItem, *itemPtr, item, Weapons[(int)LaraWeaponType::GrenadeLauncher].ExplosiveDamage); + } + else + { + // @FIXME: This calls CrossbowHitSwitchType78() + } + } + + for (auto* staticPtr : collObjects.Statics) + { + if (StaticObjects[staticPtr->staticNumber].shatterType != ShatterType::None) + { + TriggerExplosionSparks(staticPtr->pos.Position.x, staticPtr->pos.Position.y, staticPtr->pos.Position.z, 3, -2, 0, item.RoomNumber); + staticPtr->pos.Position.y -= 128; + TriggerShockwave(&staticPtr->pos, 40, 176, 64, 128, 96, 0, 16, EulerAngles::Identity, 0, true, false, false, (int)ShockwaveStyle::Normal); + staticPtr->pos.Position.y += 128; + SoundEffect(GetShatterSound(staticPtr->staticNumber), &staticPtr->pos); + ShatterObject(nullptr, staticPtr, -128, item.RoomNumber, 0); + } + } + + AlertNearbyGuards(&item); + } + + if (item.ItemFlags[1] >= 2) + { + if (item.ItemFlags[1] == 3) + { + short triggerItems[8]; + for (int i = GetSwitchTrigger(&item, triggerItems, 1); i > 0; --i) + g_Level.Items[triggerItems[i - 1]].ItemFlags[0] = 0; + + item.ItemFlags[0] = 0; + } + } + else + { + KillItem(itemNumber); } - } - else - { - KillItem(itemNumber); } } } diff --git a/TombEngine/Objects/TR5/Trap/tr5_explosion.h b/TombEngine/Objects/TR5/Trap/tr5_explosion.h index d8e67972e..54f49956b 100644 --- a/TombEngine/Objects/TR5/Trap/tr5_explosion.h +++ b/TombEngine/Objects/TR5/Trap/tr5_explosion.h @@ -1,4 +1,7 @@ #pragma once -void InitializeExplosion(short itemNumber); -void ExplosionControl(short itemNumber); +namespace TEN::Entities::Traps +{ + void InitializeExplosion(short itemNumber); + void ControlExplosion(short itemNumber); +} diff --git a/TombEngine/Objects/TR5/Trap/tr5_fallingceiling.cpp b/TombEngine/Objects/TR5/Trap/tr5_fallingceiling.cpp index 7d722d741..624b65c70 100644 --- a/TombEngine/Objects/TR5/Trap/tr5_fallingceiling.cpp +++ b/TombEngine/Objects/TR5/Trap/tr5_fallingceiling.cpp @@ -3,47 +3,57 @@ #include "Game/animation.h" #include "Game/collision/collide_room.h" +#include "Game/collision/Point.h" #include "Game/control/control.h" #include "Game/items.h" #include "Game/Lara/lara.h" #include "Specific/level.h" -void FallingCeilingControl(short itemNumber) +using namespace TEN::Collision::Point; + +namespace TEN::Entities::Traps { - auto* item = &g_Level.Items[itemNumber]; + constexpr auto FALLING_CEILING_HARM_DAMAGE = 300; - if (item->Animation.ActiveState) + void ControlFallingCeiling(short itemNumber) { - if (item->Animation.ActiveState == 1 && item->TouchBits.TestAny()) - DoDamage(LaraItem, 300); - } - else - { - item->Animation.TargetState = 1; - item->Animation.IsAirborne = true; - } + auto& item = g_Level.Items[itemNumber]; - AnimateItem(item); - - if (item->Status == ITEM_DEACTIVATED) - RemoveActiveItem(itemNumber); - else - { - auto probe = GetCollision(item); - - item->Floor = probe.Position.Floor; - - if (probe.RoomNumber != item->RoomNumber) - ItemNewRoom(itemNumber, probe.RoomNumber); - - if (item->Animation.ActiveState == 1) + if (item.Animation.ActiveState) { - if (item->Pose.Position.y >= item->Floor) + if (item.Animation.ActiveState == 1 && item.TouchBits.TestAny()) + DoDamage(LaraItem, FALLING_CEILING_HARM_DAMAGE); + } + else + { + item.Animation.TargetState = 1; + item.Animation.IsAirborne = true; + } + + AnimateItem(&item); + + if (item.Status == ITEM_DEACTIVATED) + { + RemoveActiveItem(itemNumber); + } + else + { + auto pointColl = GetPointCollision(item); + + item.Floor = pointColl.GetFloorHeight(); + + if (pointColl.GetRoomNumber() != item.RoomNumber) + ItemNewRoom(itemNumber, pointColl.GetRoomNumber()); + + if (item.Animation.ActiveState == 1) { - item->Pose.Position.y = item->Floor; - item->Animation.TargetState = 2; - item->Animation.IsAirborne = false; - item->Animation.Velocity.y = 0.0f; + if (item.Pose.Position.y >= item.Floor) + { + item.Pose.Position.y = item.Floor; + item.Animation.TargetState = 2; + item.Animation.IsAirborne = false; + item.Animation.Velocity.y = 0.0f; + } } } } diff --git a/TombEngine/Objects/TR5/Trap/tr5_fallingceiling.h b/TombEngine/Objects/TR5/Trap/tr5_fallingceiling.h index dc01af23d..30f0ab103 100644 --- a/TombEngine/Objects/TR5/Trap/tr5_fallingceiling.h +++ b/TombEngine/Objects/TR5/Trap/tr5_fallingceiling.h @@ -1,3 +1,6 @@ #pragma once -void FallingCeilingControl(short itemNumber); +namespace TEN::Entities::Traps +{ + void ControlFallingCeiling(short itemNumber); +} diff --git a/TombEngine/Objects/TR5/Trap/tr5_romehammer.cpp b/TombEngine/Objects/TR5/Trap/tr5_romehammer.cpp index 9b26ff77c..14d198faf 100644 --- a/TombEngine/Objects/TR5/Trap/tr5_romehammer.cpp +++ b/TombEngine/Objects/TR5/Trap/tr5_romehammer.cpp @@ -4,10 +4,38 @@ #include "Game/items.h" #include "Specific/level.h" -void InitializeRomeHammer(short itemNumber) -{ - auto* item = &g_Level.Items[itemNumber]; +// NOTES: +// item.ItemFlags[0] = Harm joints. +// item.ItemFlags[3] = Damage. +// item.ItemFlags[4] = Push player (bool). - item->ItemFlags[0] = 2; - item->ItemFlags[3] = 250; +namespace TEN::Entities::Traps +{ + constexpr auto ROME_HAMMER_HARM_DAMAGE = 250; + constexpr auto ROME_HAMMER_JOINTS = MESH_BITS(1); + + const auto RomeHammerHarmJoints = std::vector{ 2 }; + + void InitializeRomeHammer(short itemNumber) + { + auto& item = g_Level.Items[itemNumber]; + + item.ItemFlags[0] = ROME_HAMMER_JOINTS; + item.ItemFlags[3] = 0; + } + + void ControlRomeHammer(short itemNumber) + { + auto& item = g_Level.Items[itemNumber]; + + if (TriggerActive(&item)) + { + item.ItemFlags[3] = ROME_HAMMER_HARM_DAMAGE; + AnimateItem(&item); + } + else + { + item.ItemFlags[3] = 0; + } + } } diff --git a/TombEngine/Objects/TR5/Trap/tr5_romehammer.h b/TombEngine/Objects/TR5/Trap/tr5_romehammer.h index fe2442331..0b436861f 100644 --- a/TombEngine/Objects/TR5/Trap/tr5_romehammer.h +++ b/TombEngine/Objects/TR5/Trap/tr5_romehammer.h @@ -1,3 +1,7 @@ #pragma once -void InitializeRomeHammer(short itemNumber); +namespace TEN::Entities::Traps +{ + void InitializeRomeHammer(short itemNumber); + void ControlRomeHammer(short itemNumber); +} diff --git a/TombEngine/Objects/TR5/Trap/tr5_ventilator.cpp b/TombEngine/Objects/TR5/Trap/tr5_ventilator.cpp index 37515b543..d6ffc7706 100644 --- a/TombEngine/Objects/TR5/Trap/tr5_ventilator.cpp +++ b/TombEngine/Objects/TR5/Trap/tr5_ventilator.cpp @@ -9,305 +9,327 @@ #include "Game/Lara/lara.h" #include "Specific/level.h" -void VentilatorEffect(GameBoundingBox* bounds, int intensity, short rot, int speed) +namespace TEN::Entities::Traps { - int x, y, z; + void VentilatorEffect(GameBoundingBox* bounds, int intensity, short rot, int speed) + { + int x, y, z; - if (abs(intensity) == 1) - { - x = (bounds->X1 + bounds->X2) / 2; - if (intensity >= 0) - y = bounds->Y2; - else - y = bounds->Y1; - z = (bounds->Z1 + bounds->Z2) / 2; - } - else - { - y = (bounds->Y1 + bounds->Y2) / 2; - if (rot & 0x7FFF) + if (abs(intensity) == 1) { - if (intensity >= 0) - z = bounds->Z2; - else - z = bounds->Z1; x = (bounds->X1 + bounds->X2) / 2; - } - else - { if (intensity >= 0) - x = bounds->X2; + { + y = bounds->Y2; + } else - x = bounds->X1; + { + y = bounds->Y1; + } z = (bounds->Z1 + bounds->Z2) / 2; } - } - - if (abs(Camera.pos.x - x) <= BLOCK(7)) - { - if (abs(Camera.pos.y - y) <= BLOCK(7)) - { - if (abs(Camera.pos.z - z) <= BLOCK(7)) - { - auto* spark = GetFreeParticle(); - - spark->on = 1; - spark->sR = 0; - spark->sG = 0; - spark->sB = 0; - spark->dR = spark->dG = (48 * speed) / 128; - spark->colFadeSpeed = 4; - spark->fadeToBlack = 8; - spark->dB = (speed * ((GetRandomControl() & 8) + 48)) / 128; - spark->blendMode = BlendMode::Additive; - spark->life = spark->sLife = (GetRandomControl() & 3) + 20; - - if (abs(intensity) == 1) - { - int factor = 3 * (bounds->X2 - bounds->X1) / 8; - short angle = 2 * GetRandomControl(); - - spark->x = ((bounds->X1 + bounds->X2) / 2) + (GetRandomControl() % factor) * phd_sin(angle); - spark->z = ((bounds->Z1 + bounds->Z2) / 2) + (GetRandomControl() % factor) * phd_cos(angle); - - if (intensity >= 0) - spark->y = bounds->Y2; - else - spark->y = bounds->Y1; - - spark->zVel = 0; - spark->xVel = 0; - spark->yVel = 32 * intensity * ((GetRandomControl() & 0x1F) + 224); - } - else - { - int factor = 3 * bounds->GetHeight() / 8; - short angle = 2 * GetRandomControl(); - - spark->y = (bounds->Y1 + bounds->Y2) / 2; - - if (rot & 0x7FFF) - { - if (intensity >= 0) - spark->z = bounds->Z2; - else - spark->z = bounds->Z1; - - spark->x = ((bounds->X1 + bounds->X2) / 2) + (GetRandomControl() % factor) * phd_cos(angle); - spark->y += (GetRandomControl() % factor) * phd_sin(angle); - spark->xVel = 0; - spark->zVel = 16 * intensity * ((GetRandomControl() & 0x1F) + 224); - } - else - { - if (intensity >= 0) - spark->x = bounds->X2; - else - spark->x = bounds->X1; - - spark->y += (GetRandomControl() % factor) * phd_sin(angle); - spark->z = ((bounds->Z1 + bounds->Z2) / 2) + (GetRandomControl() % factor) * phd_cos(angle); - spark->zVel = 0; - spark->xVel = 16 * intensity * ((GetRandomControl() & 0x1F) + 224); - } - - spark->yVel = 0; - } - - spark->friction = 85; - spark->xVel = (speed * spark->xVel) / 128; - spark->yVel = (speed * spark->yVel) / 128; - spark->zVel = (speed * spark->zVel) / 128; - spark->maxYvel = 0; - spark->gravity = 0; - spark->flags = SP_NONE; - } - } - } -} - -void InitializeVentilator(short itemNumber) -{ - auto* item = &g_Level.Items[itemNumber]; - - item->ItemFlags[0] = item->TriggerFlags * BLOCK(1); - if (item->ItemFlags[0] < 2048) - item->ItemFlags[0] = 3072; -} - -void VentilatorControl(short itemNumber) -{ - auto* item = &g_Level.Items[itemNumber]; - - AnimateItem(item); - - int xChange = 0; - int zChange = 0; - - if (TriggerActive(item)) - xChange = 1; - else - { - xChange = 1; - TestTriggers(item, true); - if (item->Animation.ActiveState == 1) - { - //result = 5 * item->animNumber; - if (item->Animation.FrameNumber == GetAnimData(item).frameEnd) - return; - } else - item->Animation.TargetState = 1; - } - - int speed = 0; - if (item->Animation.ActiveState == 1) - speed = GetAnimData(item).frameEnd - item->Animation.FrameNumber; - else - speed = 128; - - auto bounds = GameBoundingBox(item); - auto effectBounds = GameBoundingBox::Zero; - - effectBounds.Y1 = item->Pose.Position.y + bounds.Y1; - effectBounds.Y2 = item->Pose.Position.y + bounds.Y2; - - if (item->ObjectNumber != ID_PROPELLER_V) // TODO: check this ID - { - if (item->Pose.Orientation.y != -ANGLE(180.0f)) { - if (item->Pose.Orientation.y == -ANGLE(90.0f)) + y = (bounds->Y1 + bounds->Y2) / 2; + if (rot & 0x7FFF) { - effectBounds.X1 = item->Pose.Position.x - bounds.Z2; - effectBounds.X2 = item->Pose.Position.x - bounds.Z1; - effectBounds.Z1 = item->Pose.Position.z + bounds.X1; - effectBounds.Z2 = item->Pose.Position.z + bounds.X2; - xChange = 0; - zChange = 1; + if (intensity >= 0) + z = bounds->Z2; + else + z = bounds->Z1; + x = (bounds->X1 + bounds->X2) / 2; } else { - if (item->Pose.Orientation.y != ANGLE(90.0f)) + if (intensity >= 0) + x = bounds->X2; + else + x = bounds->X1; + z = (bounds->Z1 + bounds->Z2) / 2; + } + } + + auto& part = *GetFreeParticle(); + + part.on = 1; + part.sR = 0; + part.sG = 0; + part.sB = 0; + part.dR = part.dG = (48 * speed) / 128; + part.colFadeSpeed = 4; + part.fadeToBlack = 8; + part.dB = (speed * ((GetRandomControl() & 8) + 48)) / 128; + part.blendMode = BlendMode::Additive; + part.life = part.sLife = (GetRandomControl() & 3) + 20; + + if (abs(intensity) == 1) + { + int factor = 3 * (bounds->X2 - bounds->X1) / 8; + short angle = 2 * GetRandomControl(); + + part.x = ((bounds->X1 + bounds->X2) / 2) + (GetRandomControl() % factor) * phd_sin(angle); + part.z = ((bounds->Z1 + bounds->Z2) / 2) + (GetRandomControl() % factor) * phd_cos(angle); + + if (intensity >= 0) + { + part.y = bounds->Y2; + } + else + { + part.y = bounds->Y1; + } + + part.zVel = 0; + part.xVel = 0; + part.yVel = 32 * intensity * ((GetRandomControl() & 0x1F) + 224); + } + else + { + int factor = 3 * bounds->GetHeight() / 8; + short angle = Random::GenerateAngle(); + + part.y = (bounds->Y1 + bounds->Y2) / 2; + + if (rot & 0x7FFF) + { + if (intensity >= 0) { - effectBounds.X1 = item->Pose.Position.x + bounds.X1; - effectBounds.X2 = item->Pose.Position.x + bounds.X2; - effectBounds.Z1 = item->Pose.Position.z + bounds.Z1; - effectBounds.Z2 = item->Pose.Position.z + bounds.Z2; - zChange = 0; + part.z = bounds->Z2; } else { - effectBounds.X1 = item->Pose.Position.x + bounds.Z1; - effectBounds.X2 = item->Pose.Position.x + bounds.Z2; - effectBounds.Z1 = item->Pose.Position.z - bounds.X2; - effectBounds.Z2 = item->Pose.Position.z - bounds.X1; + part.z = bounds->Z1; + } + + part.x = ((bounds->X1 + bounds->X2) / 2) + (GetRandomControl() % factor) * phd_cos(angle); + part.y += (GetRandomControl() % factor) * phd_sin(angle); + part.xVel = 0; + part.zVel = 16 * intensity * ((GetRandomControl() & 0x1F) + 224); + } + else + { + if (intensity >= 0) + { + part.x = bounds->X2; + } + else + { + part.x = bounds->X1; + } + + part.y += (GetRandomControl() % factor) * phd_sin(angle); + part.z = ((bounds->Z1 + bounds->Z2) / 2) + (GetRandomControl() % factor) * phd_cos(angle); + part.zVel = 0; + part.xVel = 16 * intensity * ((GetRandomControl() & 0x1F) + 224); + } + + part.yVel = 0; + } + + part.friction = 85; + part.xVel = (speed * part.xVel) / 128; + part.yVel = (speed * part.yVel) / 128; + part.zVel = (speed * part.zVel) / 128; + part.maxYvel = 0; + part.gravity = 0; + part.flags = SP_NONE; + } + + void InitializeVentilator(short itemNumber) + { + auto& item = g_Level.Items[itemNumber]; + + item.ItemFlags[0] = item.TriggerFlags * BLOCK(1); + if (item.ItemFlags[0] < 2048) + item.ItemFlags[0] = 3072; + } + + void ControlVentilator(short itemNumber) + { + auto& item = g_Level.Items[itemNumber]; + + AnimateItem(&item); + + int xChange = 0; + int zChange = 0; + + if (TriggerActive(&item)) + { + xChange = 1; + } + else + { + xChange = 1; + TestTriggers(&item, true); + if (item.Animation.ActiveState == 1) + { + // result = 5 * item.animNumber; + if (item.Animation.FrameNumber == GetAnimData(item).frameEnd) + return; + } + else + { + item.Animation.TargetState = 1; + } + } + + int speed = 0; + if (item.Animation.ActiveState == 1) + { + speed = GetAnimData(item).frameEnd - item.Animation.FrameNumber; + } + else + { + speed = 128; + } + + auto bounds = GameBoundingBox(&item); + auto effectBounds = GameBoundingBox::Zero; + + effectBounds.Y1 = item.Pose.Position.y + bounds.Y1; + effectBounds.Y2 = item.Pose.Position.y + bounds.Y2; + + if (item.ObjectNumber != ID_PROPELLER_V) // TODO: check this ID + { + if (item.Pose.Orientation.y != -ANGLE(180.0f)) + { + if (item.Pose.Orientation.y == -ANGLE(90.0f)) + { + effectBounds.X1 = item.Pose.Position.x - bounds.Z2; + effectBounds.X2 = item.Pose.Position.x - bounds.Z1; + effectBounds.Z1 = item.Pose.Position.z + bounds.X1; + effectBounds.Z2 = item.Pose.Position.z + bounds.X2; xChange = 0; zChange = 1; } - } - } - else - { - effectBounds.X1 = item->Pose.Position.x - bounds.X2; - effectBounds.X2 = item->Pose.Position.x - bounds.X1; - effectBounds.Z1 = item->Pose.Position.z - bounds.Z2; - effectBounds.Z2 = item->Pose.Position.z - bounds.Z1; - zChange = 0; - } - - VentilatorEffect(&effectBounds, 2, item->Pose.Orientation.y, speed); - VentilatorEffect(&effectBounds, -2, item->Pose.Orientation.y, speed); - - if (LaraItem->Pose.Position.y >= effectBounds.Y1 && LaraItem->Pose.Position.y <= effectBounds.Y2) - { - if (zChange) - { - if (LaraItem->Pose.Position.x >= effectBounds.X1 && LaraItem->Pose.Position.x <= effectBounds.X2) + else { - int z1 = abs(LaraItem->Pose.Position.z - effectBounds.Z1); - int z2 = abs(LaraItem->Pose.Position.z - effectBounds.Z2); - - if (z2 >= z1) - zChange = -zChange; - else - z1 = z2; - - if (z1 < item->ItemFlags[0]) + if (item.Pose.Orientation.y != ANGLE(90.0f)) { - int dz = 96 * zChange * (item->ItemFlags[0] - z1) / item->ItemFlags[0]; - - if (item->Animation.ActiveState == 1) - dz = speed * dz / 120; - - LaraItem->Pose.Position.z += dz; + effectBounds.X1 = item.Pose.Position.x + bounds.X1; + effectBounds.X2 = item.Pose.Position.x + bounds.X2; + effectBounds.Z1 = item.Pose.Position.z + bounds.Z1; + effectBounds.Z2 = item.Pose.Position.z + bounds.Z2; + zChange = 0; + } + else + { + effectBounds.X1 = item.Pose.Position.x + bounds.Z1; + effectBounds.X2 = item.Pose.Position.x + bounds.Z2; + effectBounds.Z1 = item.Pose.Position.z - bounds.X2; + effectBounds.Z2 = item.Pose.Position.z - bounds.X1; + xChange = 0; + zChange = 1; } } } else { - if (LaraItem->Pose.Position.z >= effectBounds.Z1 && LaraItem->Pose.Position.z <= effectBounds.Z2) + effectBounds.X1 = item.Pose.Position.x - bounds.X2; + effectBounds.X2 = item.Pose.Position.x - bounds.X1; + effectBounds.Z1 = item.Pose.Position.z - bounds.Z2; + effectBounds.Z2 = item.Pose.Position.z - bounds.Z1; + zChange = 0; + } + + VentilatorEffect(&effectBounds, 2, item.Pose.Orientation.y, speed); + VentilatorEffect(&effectBounds, -2, item.Pose.Orientation.y, speed); + + if (LaraItem->Pose.Position.y >= effectBounds.Y1 && LaraItem->Pose.Position.y <= effectBounds.Y2) + { + if (zChange) { - int x1 = abs(LaraItem->Pose.Position.x - effectBounds.X1); - int x2 = abs(LaraItem->Pose.Position.x - effectBounds.X2); - - if (x2 >= x1) - xChange = -xChange; - else - x1 = x2; - - if (x1 < item->ItemFlags[0]) + if (LaraItem->Pose.Position.x >= effectBounds.X1 && LaraItem->Pose.Position.x <= effectBounds.X2) { - int dx = 96 * xChange * (item->ItemFlags[0] - x1) / item->ItemFlags[0]; + int z1 = abs(LaraItem->Pose.Position.z - effectBounds.Z1); + int z2 = abs(LaraItem->Pose.Position.z - effectBounds.Z2); - if (item->Animation.ActiveState == 1) - dx = speed * dx / 120; + if (z2 >= z1) + zChange = -zChange; + else + z1 = z2; - LaraItem->Pose.Position.x += dx; + if (z1 < item.ItemFlags[0]) + { + int dz = 96 * zChange * (item.ItemFlags[0] - z1) / item.ItemFlags[0]; + + if (item.Animation.ActiveState == 1) + dz = speed * dz / 120; + + LaraItem->Pose.Position.z += dz; + } + } + } + else + { + if (LaraItem->Pose.Position.z >= effectBounds.Z1 && LaraItem->Pose.Position.z <= effectBounds.Z2) + { + int x1 = abs(LaraItem->Pose.Position.x - effectBounds.X1); + int x2 = abs(LaraItem->Pose.Position.x - effectBounds.X2); + + if (x2 >= x1) + { + xChange = -xChange; + } + else + { + x1 = x2; + } + + if (x1 < item.ItemFlags[0]) + { + int dx = 96 * xChange * (item.ItemFlags[0] - x1) / item.ItemFlags[0]; + + if (item.Animation.ActiveState == 1) + dx = speed * dx / 120; + + LaraItem->Pose.Position.x += dx; + } } } } } - } - else - { - auto tBounds = bounds; - tBounds.Rotate(item->Pose.Orientation); - - effectBounds.X1 = item->Pose.Position.x + tBounds.X1; - effectBounds.X2 = item->Pose.Position.x + tBounds.X2; - effectBounds.Z1 = item->Pose.Position.z + tBounds.Z1; - effectBounds.Z2 = item->Pose.Position.z + tBounds.Z2; - - VentilatorEffect(&effectBounds, 1, 0, speed); - VentilatorEffect(&effectBounds, -1, 0, speed); - - if (LaraItem->Pose.Position.x >= effectBounds.X1 && - LaraItem->Pose.Position.x <= effectBounds.X2) + else { - if (LaraItem->Pose.Position.z >= effectBounds.Z1 && - LaraItem->Pose.Position.z <= effectBounds.Z2) + auto tBounds = bounds; + tBounds.Rotate(item.Pose.Orientation); + + effectBounds.X1 = item.Pose.Position.x + tBounds.X1; + effectBounds.X2 = item.Pose.Position.x + tBounds.X2; + effectBounds.Z1 = item.Pose.Position.z + tBounds.Z1; + effectBounds.Z2 = item.Pose.Position.z + tBounds.Z2; + + VentilatorEffect(&effectBounds, 1, 0, speed); + VentilatorEffect(&effectBounds, -1, 0, speed); + + if (LaraItem->Pose.Position.x >= effectBounds.X1 && + LaraItem->Pose.Position.x <= effectBounds.X2) { - int y = effectBounds.Y2; - - if (LaraItem->Pose.Position.y <= effectBounds.Y2) + if (LaraItem->Pose.Position.z >= effectBounds.Z1 && + LaraItem->Pose.Position.z <= effectBounds.Z2) { - if (effectBounds.Y1 - LaraItem->Pose.Position.y >= item->ItemFlags[0]) - return; + int y = effectBounds.Y2; - y = 96 * (effectBounds.Y2 - item->ItemFlags[0]) / item->ItemFlags[0]; + if (LaraItem->Pose.Position.y <= effectBounds.Y2) + { + if (effectBounds.Y1 - LaraItem->Pose.Position.y >= item.ItemFlags[0]) + return; + + y = 96 * (effectBounds.Y2 - item.ItemFlags[0]) / item.ItemFlags[0]; + } + else + { + if (LaraItem->Pose.Position.y - effectBounds.Y2 >= item.ItemFlags[0]) + return; + + y = 96 * (item.ItemFlags[0] - (LaraItem->Pose.Position.y - effectBounds.Y2)) / item.ItemFlags[0]; + } + + if (item.Animation.ActiveState == 1) + y = speed * y / 120; + + LaraItem->Pose.Position.y += y; } - else - { - if (LaraItem->Pose.Position.y - effectBounds.Y2 >= item->ItemFlags[0]) - return; - - y = 96 * (item->ItemFlags[0] - (LaraItem->Pose.Position.y - effectBounds.Y2)) / item->ItemFlags[0]; - } - - if (item->Animation.ActiveState == 1) - y = speed * y / 120; - - LaraItem->Pose.Position.y += y; } } } diff --git a/TombEngine/Objects/TR5/Trap/tr5_ventilator.h b/TombEngine/Objects/TR5/Trap/tr5_ventilator.h index a80f1fe83..d0e920538 100644 --- a/TombEngine/Objects/TR5/Trap/tr5_ventilator.h +++ b/TombEngine/Objects/TR5/Trap/tr5_ventilator.h @@ -1,4 +1,7 @@ #pragma once -void InitializeVentilator(short itemNumber); -void VentilatorControl(short itemNumber); +namespace TEN::Entities::Traps +{ + void InitializeVentilator(short itemNumber); + void ControlVentilator(short itemNumber); +} diff --git a/TombEngine/Objects/TR5/Trap/tr5_wreckingball.cpp b/TombEngine/Objects/TR5/Trap/tr5_wreckingball.cpp index 3df7c9b76..55909c4ca 100644 --- a/TombEngine/Objects/TR5/Trap/tr5_wreckingball.cpp +++ b/TombEngine/Objects/TR5/Trap/tr5_wreckingball.cpp @@ -5,7 +5,6 @@ #include "Game/camera.h" #include "Game/collision/collide_item.h" #include "Game/collision/collide_room.h" -#include "Game/collision/sphere.h" #include "Game/effects/effects.h" #include "Game/effects/tomb4fx.h" #include "Game/effects/weather.h" @@ -19,296 +18,347 @@ using namespace TEN::Effects::Environment; -static short WreckingBallData[2] = { 0, 0 }; +// TODO: Refactor. +// - Modularize. +// - Remove all legacy magic garbage. +// - Revise logic to remove constrains and allow it to move around different structures. +// - Change management of base object (item2). +// - Make light effect optional. +// +// NOTES +// ItemFlags[0] = ?? +// ItemFlags[1] = ?? +// ItemFlags[2] = ?? +// ItemFlags[3] = Base object ID (by default ANIMATING16) -void InitializeWreckingBall(short itemNumber) +namespace TEN::Entities::Traps { - auto* item = &g_Level.Items[itemNumber]; + static short WreckingBallData[2] = { 0, 0 }; - item->ItemFlags[3] = FindAllItems(ID_ANIMATING16)[0]; - - short RoomNumber = item->RoomNumber; - item->Pose.Position.y = GetCeiling(GetFloor(item->Pose.Position.x, item->Pose.Position.y, item->Pose.Position.z, &RoomNumber), item->Pose.Position.x, item->Pose.Position.y, item->Pose.Position.z) + 1644; - GetFloor(item->Pose.Position.x, item->Pose.Position.y, item->Pose.Position.z, &RoomNumber); - - if (RoomNumber != item->RoomNumber) - ItemNewRoom(itemNumber, RoomNumber); -} - -void WreckingBallCollision(short itemNumber, ItemInfo* laraItem, CollisionInfo* coll) -{ - auto* item = &g_Level.Items[itemNumber]; - - if (TestBoundsCollide(item, laraItem, coll->Setup.Radius)) + void InitializeWreckingBall(short itemNumber) { - int x = laraItem->Pose.Position.x; - int y = laraItem->Pose.Position.y; - int z = laraItem->Pose.Position.z; + auto& item = g_Level.Items[itemNumber]; - bool test = false; - if ((x & WALL_MASK) > CLICK(1) && - (x & WALL_MASK) < CLICK(3) && - (z & WALL_MASK) > CLICK(1) && - (z & WALL_MASK) < CLICK(3)) + auto pointColl = GetPointCollision(item); + + item.ItemFlags[3] = FindAllItems(ID_ANIMATING16)[0]; + item.Pose.Position.y = pointColl.GetCeilingHeight() + 1644; + + if (pointColl.GetRoomNumber() != item.RoomNumber) + ItemNewRoom(itemNumber, pointColl.GetRoomNumber()); + } + + void CollideWreckingBall(short itemNumber, ItemInfo* playerItem, CollisionInfo* coll) + { + auto& item = g_Level.Items[itemNumber]; + + if (TestBoundsCollide(&item, playerItem, coll->Setup.Radius)) { - test = true; + auto prevPos = playerItem->Pose.Position; + + bool test = false; + if ((prevPos.x & WALL_MASK) > CLICK(1) && + (prevPos.x & WALL_MASK) < CLICK(3) && + (prevPos.z & WALL_MASK) > CLICK(1) && + (prevPos.z & WALL_MASK) < CLICK(3)) + { + test = true; + } + + int damage = (item.Animation.Velocity.y > 0.0f) ? 96 : 0; + + if (ItemPushItem(&item, playerItem, coll, coll->Setup.EnableSpasm, 1)) + { + if (test) + { + DoDamage(playerItem, INT_MAX); + } + else + { + DoDamage(playerItem, damage); + } + + prevPos -= playerItem->Pose.Position; + + if (damage != 0) + { + for (int i = 14 + (GetRandomControl() & 3); i > 0; --i) + { + TriggerBlood(playerItem->Pose.Position.x + (GetRandomControl() & 63) - 32, playerItem->Pose.Position.y - (GetRandomControl() & 511) - 256, + playerItem->Pose.Position.z + (GetRandomControl() & 63) - 32, -1, 1); + } + } + + if (!coll->Setup.EnableObjectPush || test) + playerItem->Pose.Position += prevPos; + } + } + } + + void ControlWreckingBall(short itemNumber) + { + int x, z, oldX, oldZ, wx, wz, flagX, flagZ, height, dx, dz, ceilingX, ceilingZ, adx, adz; + short room; + + auto& item = g_Level.Items[itemNumber]; + auto& item2 = g_Level.Items[item.ItemFlags[3]]; + + bool test = true; + + if ((LaraItem->Pose.Position.x >= BLOCK(44) && + LaraItem->Pose.Position.x <= BLOCK(56) && + LaraItem->Pose.Position.z >= BLOCK(26) && + LaraItem->Pose.Position.z <= BLOCK(42)) || + item.ItemFlags[2] < 900) + { + if (item.ItemFlags[2] < 900) + { + if (!item.ItemFlags[2] || !(GlobalCounter & 0x3F)) + { + WreckingBallData[0] = GetRandomControl() % 7 - 3; + WreckingBallData[1] = GetRandomControl() % 7 - 3; + } + + x = (WreckingBallData[0] << 10) + 51712; + z = (WreckingBallData[1] << 10) + 34304; + test = false; + } + else + { + x = LaraItem->Pose.Position.x; + z = LaraItem->Pose.Position.z; + } + } + else + { + x = 51200; + z = 33792; + test = false; } - int damage = (item->Animation.Velocity.y > 0.0f) ? 96 : 0; + if (item.ItemFlags[2] < 900) + ++item.ItemFlags[2]; - if (ItemPushItem(item, laraItem, coll, coll->Setup.EnableSpasm, 1)) + if (item.ItemFlags[1] <= 0) { - if (test) - DoDamage(laraItem, INT_MAX); - else - DoDamage(laraItem, damage); + oldX = item.Pose.Position.x; + oldZ = item.Pose.Position.z; + x = x & 0xFFFFFE00 | 0x200; + z = z & 0xFFFFFE00 | 0x200; + dx = x - item.Pose.Position.x; + dz = z - item.Pose.Position.z; + wx = 0; - x -= laraItem->Pose.Position.x; - y -= laraItem->Pose.Position.y; - z -= laraItem->Pose.Position.z; - - if (damage) + if (dx < 0) { - for (int i = 14 + (GetRandomControl() & 3); i > 0; --i) + wx = -1024; + } + else if (dx > 0) + { + wx = 1024; + } + wz = 0; + + if (dz < 0) + { + wz = -1024; + } + else if (dz > 0) + { + wz = 1024; + } + + room = item.RoomNumber; + ceilingX = GetCeiling(GetFloor(item.Pose.Position.x + wx, item2.Pose.Position.y, item.Pose.Position.z, &room), item.Pose.Position.x + wx, item2.Pose.Position.y, item.Pose.Position.z); + room = item.RoomNumber; + + ceilingZ = GetCeiling(GetFloor(item.Pose.Position.x, item2.Pose.Position.y, item.Pose.Position.z + wz, &room), item.Pose.Position.x, item2.Pose.Position.y, item.Pose.Position.z + wz); + if (ceilingX <= item2.Pose.Position.y && ceilingX != NO_HEIGHT) + { + flagX = 1; + } + else + { + flagX = 0; + } + + if (ceilingZ <= item2.Pose.Position.y && ceilingZ != NO_HEIGHT) + { + flagZ = 1; + } + else + { + flagZ = 0; + } + + if (!item.ItemFlags[0]) + { + if (flagX && dx && (abs(dx) > abs(dz) || !flagZ || GetRandomControl() & 1)) { - TriggerBlood(laraItem->Pose.Position.x + (GetRandomControl() & 63) - 32, laraItem->Pose.Position.y - (GetRandomControl() & 511) - 256, - laraItem->Pose.Position.z + (GetRandomControl() & 63) - 32, -1, 1); + item.ItemFlags[0] = 1; + } + else if (flagZ && dz) + { + item.ItemFlags[0] = 2; } } - if (!coll->Setup.EnableObjectPush || test) + if (item.ItemFlags[0] == 1) { - laraItem->Pose.Position.x += x; - laraItem->Pose.Position.y += y; - laraItem->Pose.Position.z += z; + SoundEffect(SFX_TR5_BASE_CLAW_MOTOR_B_LOOP, &item.Pose); + + adx = abs(dx); + if (adx >= 32) + adx = 32; + + if (dx > 0) + { + item.Pose.Position.x += adx; + } + else if (dx < 0) + { + item.Pose.Position.x -= adx; + } + else + { + item.ItemFlags[0] = 0; + } + } + + if (item.ItemFlags[0] == 2) + { + SoundEffect(SFX_TR5_BASE_CLAW_MOTOR_B_LOOP, &item.Pose); + + adz = abs(dz); + if (adz >= 32) + adz = 32; + + if (dz > 0) + { + item.Pose.Position.z += adz; + } + else if (dz < 0) + { + item.Pose.Position.z -= adz; + } + else + { + item.ItemFlags[0] = 0; + } + } + + if (item.ItemFlags[1] == -1 && (oldX != item.Pose.Position.x || oldZ != item.Pose.Position.z)) + { + item.ItemFlags[1] = 0; + SoundEffect(SFX_TR5_BASE_CLAW_MOTOR_A, &item.Pose); + } + + if ((item.Pose.Position.x & 0x3FF) == 512 && (item.Pose.Position.z & 0x3FF) == 512) + item.ItemFlags[0] = 0; + + if (x == item.Pose.Position.x && z == item.Pose.Position.z && test) + { + if (item.ItemFlags[1] != -1) + { + StopSoundEffect(SFX_TR5_BASE_CLAW_MOTOR_B_LOOP); + SoundEffect(SFX_TR5_BASE_CLAW_MOTOR_C, &item.Pose); + } + + item.ItemFlags[1] = 1; + item.TriggerFlags = 30; } } + else if (item.ItemFlags[1] == 1) + { + if (!item.TriggerFlags) + { + --item.TriggerFlags; + } + else if (!item.Animation.ActiveState) + { + item.Animation.TargetState = 1; + } + else if (item.Animation.FrameNumber == GetAnimData(item).frameEnd) + { + SoundEffect(SFX_TR5_BASE_CLAW_DROP, &item.Pose); + ++item.ItemFlags[1]; + item.Animation.Velocity.y = 6; + item.Pose.Position.y += item.Animation.Velocity.y; + } + } + else if (item.ItemFlags[1] == 2) + { + item.Animation.Velocity.y += 24; + item.Pose.Position.y += item.Animation.Velocity.y; + room = item.RoomNumber; + + height = GetFloorHeight(GetFloor(item.Pose.Position.x, item.Pose.Position.y, item.Pose.Position.z, &room), item.Pose.Position.x, item.Pose.Position.y, item.Pose.Position.z); + if (height < item.Pose.Position.y) + { + item.Pose.Position.y = height; + if (item.Animation.Velocity.y > 48) + { + BounceCamera(&item, 64, 8192); + item.Animation.Velocity.y = -item.Animation.Velocity.y / 8.0f; + } + else + { + ++item.ItemFlags[1]; + item.Animation.Velocity.y = 0; + } + } + else if (height - item.Pose.Position.y < 1536 && item.Animation.ActiveState) + { + item.Animation.TargetState = 0; + } + } + else if (item.ItemFlags[1] == 3) + { + item.Animation.Velocity.y -= 3; + item.Pose.Position.y += item.Animation.Velocity.y; + + if (item.Pose.Position.y < item2.Pose.Position.y + 1644) + { + StopSoundEffect(SFX_TR5_BASE_CLAW_WINCH_UP_LOOP); + item.ItemFlags[0] = 1; + item.Pose.Position.y = item2.Pose.Position.y + 1644; + + if (item.Animation.Velocity.y < -32.0f) + { + SoundEffect(SFX_TR5_BASE_CLAW_TOP_IMPACT, &item.Pose, SoundEnvironment::Land, 1.0f, 0.5f); + item.Animation.Velocity.y = -item.Animation.Velocity.y / 8.0f; + BounceCamera(&item, 16, 8192); + } + else + { + item.ItemFlags[1] = -1; + item.Animation.Velocity.y = 0; + item.ItemFlags[0] = 0; + } + } + else if (!item.ItemFlags[0]) + { + SoundEffect(SFX_TR5_BASE_CLAW_WINCH_UP_LOOP, &item.Pose); + } + } + + item2.Pose.Position.x = item.Pose.Position.x; + item2.Pose.Position.z = item.Pose.Position.z; + room = item.RoomNumber; + item2.Pose.Position.y = GetCeiling(GetFloor(item.Pose.Position.x, item.Pose.Position.y, item.Pose.Position.z, &room), item.Pose.Position.x, item.Pose.Position.y, item.Pose.Position.z); + + GetFloor(item2.Pose.Position.x, item2.Pose.Position.y, item2.Pose.Position.z, &room); + if (room != item2.RoomNumber) + ItemNewRoom(item.ItemFlags[3], room); + + TriggerAlertLight(item2.Pose.Position.x, item2.Pose.Position.y + 64, item2.Pose.Position.z, 255, 64, 0, 64 * (GlobalCounter & 0x3F), item2.RoomNumber, 24); + TriggerAlertLight(item2.Pose.Position.x, item2.Pose.Position.y + 64, item2.Pose.Position.z, 255, 64, 0, 64 * (GlobalCounter - 32) & 0xFFF, item2.RoomNumber, 24); + + room = item.RoomNumber; + GetFloor(item.Pose.Position.x, item.Pose.Position.y, item.Pose.Position.z, &room); + if (room != item.RoomNumber) + ItemNewRoom(itemNumber, room); + + AnimateItem(&item); } } - -void WreckingBallControl(short itemNumber) -{ - int x, z, oldX, oldZ, wx, wz, flagX, flagZ, height, dx, dz, ceilingX, ceilingZ, adx, adz; - short room; - - auto* item = &g_Level.Items[itemNumber]; - auto* item2 = &g_Level.Items[item->ItemFlags[3]]; - - bool test = true; - - if ((LaraItem->Pose.Position.x >= BLOCK(44) && - LaraItem->Pose.Position.x <= BLOCK(56)&& - LaraItem->Pose.Position.z >= BLOCK(26) && - LaraItem->Pose.Position.z <= BLOCK(42)) || - item->ItemFlags[2] < 900) - { - if (item->ItemFlags[2] < 900) - { - if (!item->ItemFlags[2] || !(GlobalCounter & 0x3F)) - { - WreckingBallData[0] = GetRandomControl() % 7 - 3; - WreckingBallData[1] = GetRandomControl() % 7 - 3; - } - - x = (WreckingBallData[0] << 10) + 51712; - z = (WreckingBallData[1] << 10) + 34304; - test = false; - } - else - { - x = LaraItem->Pose.Position.x; - z = LaraItem->Pose.Position.z; - } - } - else - { - x = 51200; - z = 33792; - test = false; - } - - if (item->ItemFlags[2] < 900) - ++item->ItemFlags[2]; - - if (item->ItemFlags[1] <= 0) - { - oldX = item->Pose.Position.x; - oldZ = item->Pose.Position.z; - x = x & 0xFFFFFE00 | 0x200; - z = z & 0xFFFFFE00 | 0x200; - dx = x - item->Pose.Position.x; - dz = z - item->Pose.Position.z; - wx = 0; - - if (dx < 0) - wx = -1024; - else if (dx > 0) - wx = 1024; - wz = 0; - - if (dz < 0) - wz = -1024; - else if (dz > 0) - wz = 1024; - - room = item->RoomNumber; - ceilingX = GetCeiling(GetFloor(item->Pose.Position.x + wx, item2->Pose.Position.y, item->Pose.Position.z, &room), item->Pose.Position.x + wx, item2->Pose.Position.y, item->Pose.Position.z); - room = item->RoomNumber; - - ceilingZ = GetCeiling(GetFloor(item->Pose.Position.x, item2->Pose.Position.y, item->Pose.Position.z + wz, &room), item->Pose.Position.x, item2->Pose.Position.y, item->Pose.Position.z + wz); - if (ceilingX <= item2->Pose.Position.y && ceilingX != NO_HEIGHT) - flagX = 1; - else - flagX = 0; - - if (ceilingZ <= item2->Pose.Position.y && ceilingZ != NO_HEIGHT) - flagZ = 1; - else - flagZ = 0; - - if (!item->ItemFlags[0]) - { - if (flagX && dx && (abs(dx) > abs(dz) || !flagZ || GetRandomControl() & 1)) - item->ItemFlags[0] = 1; - else if (flagZ && dz) - item->ItemFlags[0] = 2; - } - - if (item->ItemFlags[0] == 1) - { - SoundEffect(SFX_TR5_BASE_CLAW_MOTOR_B_LOOP, &item->Pose); - - adx = abs(dx); - if (adx >= 32) - adx = 32; - - if (dx > 0) - item->Pose.Position.x += adx; - else if (dx < 0) - item->Pose.Position.x -= adx; - else - item->ItemFlags[0] = 0; - } - - if (item->ItemFlags[0] == 2) - { - SoundEffect(SFX_TR5_BASE_CLAW_MOTOR_B_LOOP, &item->Pose); - - adz = abs(dz); - if (adz >= 32) - adz = 32; - - if (dz > 0) - item->Pose.Position.z += adz; - else if (dz < 0) - item->Pose.Position.z -= adz; - else - item->ItemFlags[0] = 0; - } - - if (item->ItemFlags[1] == -1 && (oldX != item->Pose.Position.x || oldZ != item->Pose.Position.z)) - { - item->ItemFlags[1] = 0; - SoundEffect(SFX_TR5_BASE_CLAW_MOTOR_A, &item->Pose); - } - - if ((item->Pose.Position.x & 0x3FF) == 512 && (item->Pose.Position.z & 0x3FF) == 512) - item->ItemFlags[0] = 0; - - if (x == item->Pose.Position.x && z == item->Pose.Position.z && test) - { - if (item->ItemFlags[1] != -1) - { - StopSoundEffect(SFX_TR5_BASE_CLAW_MOTOR_B_LOOP); - SoundEffect(SFX_TR5_BASE_CLAW_MOTOR_C, &item->Pose); - } - - item->ItemFlags[1] = 1; - item->TriggerFlags = 30; - } - } - else if (item->ItemFlags[1] == 1) - { - if (!item->TriggerFlags) - --item->TriggerFlags; - else if (!item->Animation.ActiveState) - item->Animation.TargetState = 1; - else if (item->Animation.FrameNumber == GetAnimData(item).frameEnd) - { - SoundEffect(SFX_TR5_BASE_CLAW_DROP, &item->Pose); - ++item->ItemFlags[1]; - item->Animation.Velocity.y = 6; - item->Pose.Position.y += item->Animation.Velocity.y; - } - } - else if (item->ItemFlags[1] == 2) - { - item->Animation.Velocity.y += 24; - item->Pose.Position.y += item->Animation.Velocity.y; - room = item->RoomNumber; - - height = GetFloorHeight(GetFloor(item->Pose.Position.x, item->Pose.Position.y, item->Pose.Position.z, &room), item->Pose.Position.x, item->Pose.Position.y, item->Pose.Position.z); - if (height < item->Pose.Position.y) - { - item->Pose.Position.y = height; - if (item->Animation.Velocity.y > 48) - { - BounceCamera(item, 64, 8192); - item->Animation.Velocity.y = -item->Animation.Velocity.y / 8.0f; - } - else - { - ++item->ItemFlags[1]; - item->Animation.Velocity.y = 0; - } - } - else if (height - item->Pose.Position.y < 1536 && item->Animation.ActiveState) - item->Animation.TargetState = 0; - } - else if (item->ItemFlags[1] == 3) - { - item->Animation.Velocity.y -= 3; - item->Pose.Position.y += item->Animation.Velocity.y; - - if (item->Pose.Position.y < item2->Pose.Position.y + 1644) - { - StopSoundEffect(SFX_TR5_BASE_CLAW_WINCH_UP_LOOP); - item->ItemFlags[0] = 1; - item->Pose.Position.y = item2->Pose.Position.y + 1644; - - if (item->Animation.Velocity.y < -32.0f) - { - SoundEffect(SFX_TR5_BASE_CLAW_TOP_IMPACT, &item->Pose, SoundEnvironment::Land, 1.0f, 0.5f); - item->Animation.Velocity.y = -item->Animation.Velocity.y / 8.0f; - BounceCamera(item, 16, 8192); - } - else - { - item->ItemFlags[1] = -1; - item->Animation.Velocity.y = 0; - item->ItemFlags[0] = 0; - } - } - else if (!item->ItemFlags[0]) - SoundEffect(SFX_TR5_BASE_CLAW_WINCH_UP_LOOP, &item->Pose); - } - - item2->Pose.Position.x = item->Pose.Position.x; - item2->Pose.Position.z = item->Pose.Position.z; - room = item->RoomNumber; - item2->Pose.Position.y = GetCeiling(GetFloor(item->Pose.Position.x, item->Pose.Position.y, item->Pose.Position.z, &room), item->Pose.Position.x, item->Pose.Position.y, item->Pose.Position.z); - - GetFloor(item2->Pose.Position.x, item2->Pose.Position.y, item2->Pose.Position.z, &room); - if (room != item2->RoomNumber) - ItemNewRoom(item->ItemFlags[3], room); - - TriggerAlertLight(item2->Pose.Position.x, item2->Pose.Position.y + 64, item2->Pose.Position.z, 255, 64, 0, 64 * (GlobalCounter & 0x3F), item2->RoomNumber, 24); - TriggerAlertLight(item2->Pose.Position.x, item2->Pose.Position.y + 64, item2->Pose.Position.z, 255, 64, 0, 64 * (GlobalCounter - 32) & 0xFFF, item2->RoomNumber, 24); - - room = item->RoomNumber; - GetFloor(item->Pose.Position.x, item->Pose.Position.y, item->Pose.Position.z, &room); - if (room != item->RoomNumber) - ItemNewRoom(itemNumber, room); - - AnimateItem(item); -} diff --git a/TombEngine/Objects/TR5/Trap/tr5_wreckingball.h b/TombEngine/Objects/TR5/Trap/tr5_wreckingball.h index f69f4ebef..625c2749e 100644 --- a/TombEngine/Objects/TR5/Trap/tr5_wreckingball.h +++ b/TombEngine/Objects/TR5/Trap/tr5_wreckingball.h @@ -3,6 +3,9 @@ struct CollisionInfo; struct ItemInfo; -void InitializeWreckingBall(short itemNumber); -void WreckingBallCollision(short itemNumber, ItemInfo* l, CollisionInfo* coll); -void WreckingBallControl(short itemNumber); \ No newline at end of file +namespace TEN::Entities::Traps +{ + void InitializeWreckingBall(short itemNumber); + void CollideWreckingBall(short itemNumber, ItemInfo* playerItem, CollisionInfo* coll); + void ControlWreckingBall(short itemNumber); +} diff --git a/TombEngine/Objects/TR5/tr5_objects.cpp b/TombEngine/Objects/TR5/tr5_objects.cpp index 686c9d28c..42c6add7c 100644 --- a/TombEngine/Objects/TR5/tr5_objects.cpp +++ b/TombEngine/Objects/TR5/tr5_objects.cpp @@ -37,10 +37,10 @@ #include "Objects/TR5/Entity/tr5_willowwisp.h" // OK // Emitters +#include "Objects/TR5/Emitter/tr5_smoke_emitter.h" #include "Objects/TR5/Emitter/tr5_rats_emitter.h" #include "Objects/TR5/Emitter/tr5_bats_emitter.h" #include "Objects/TR5/Emitter/tr5_spider_emitter.h" -#include "Objects/TR5/Emitter/tr5_smoke_emitter.h" // Objects #include "Objects/TR5/Light/tr5_light.h" @@ -74,9 +74,10 @@ #include "Objects/TR5/Shatter/tr5_smashobject.h" using namespace TEN::Effects::EmberEmitter; +using namespace TEN::Effects::SmokeEmitter; using namespace TEN::Entities::Creatures::TR5; using namespace TEN::Entities::Switches; -using namespace TEN::Traps::TR5; +using namespace TEN::Entities::Traps; static void StartEntity(ObjectInfo *obj) { @@ -374,7 +375,7 @@ static void StartEntity(ObjectInfo *obj) obj->pivotLength = 50; obj->radius = 102; obj->intelligent = true; - obj->LotType = LotType::Human; + obj->LotType = LotType::Basic; obj->SetBoneRotationFlags(6, ROT_X | ROT_Y); obj->SetBoneRotationFlags(7, ROT_X | ROT_Y); obj->SetHitEffect(); @@ -391,7 +392,7 @@ static void StartEntity(ObjectInfo *obj) obj->pivotLength = 50; obj->radius = 102; obj->intelligent = true; - obj->LotType = LotType::Human; + obj->LotType = LotType::Basic; obj->SetBoneRotationFlags(6, ROT_X | ROT_Y); obj->SetBoneRotationFlags(7, ROT_X | ROT_Y); obj->SetHitEffect(); @@ -733,7 +734,7 @@ static void StartObject(ObjectInfo *obj) if (obj->loaded) { obj->Initialize = InitializeSmokeEmitter; - obj->control = SmokeEmitterControl; + obj->control = ControlSmokeEmitter; obj->drawRoutine = nullptr; obj->usingDrawAnimatingItem = false; } @@ -742,7 +743,7 @@ static void StartObject(ObjectInfo *obj) if (obj->loaded) { obj->Initialize = InitializeSmokeEmitter; - obj->control = SmokeEmitterControl; + obj->control = ControlSmokeEmitter; obj->drawRoutine = nullptr; obj->usingDrawAnimatingItem = false; } @@ -751,7 +752,7 @@ static void StartObject(ObjectInfo *obj) if (obj->loaded) { obj->Initialize = InitializeSmokeEmitter; - obj->control = SmokeEmitterControl; + obj->control = ControlSmokeEmitter; obj->drawRoutine = nullptr; obj->usingDrawAnimatingItem = false; } @@ -818,13 +819,6 @@ static void StartObject(ObjectInfo *obj) obj->usingDrawAnimatingItem = false; } - obj = &Objects[ID_LENS_FLARE]; - if (obj->loaded) - { - //obj->drawRoutine = DrawLensFlare; - - } - obj = &Objects[ID_WATERFALLSS1]; if (obj->loaded) { @@ -853,7 +847,7 @@ static void StartTrap(ObjectInfo *obj) if (obj->loaded) { obj->Initialize = InitializeVentilator; - obj->control = VentilatorControl; + obj->control = ControlVentilator; obj->SetHitEffect(true); } @@ -861,7 +855,7 @@ static void StartTrap(ObjectInfo *obj) if (obj->loaded) { obj->Initialize = InitializeVentilator; - obj->control = VentilatorControl; + obj->control = ControlVentilator; obj->SetHitEffect(true); } @@ -876,7 +870,7 @@ static void StartTrap(ObjectInfo *obj) { obj->Initialize = InitializeRomeHammer; obj->collision = GenericSphereBoxCollision; - obj->control = AnimatingControl; + obj->control = ControlRomeHammer; obj->SetHitEffect(true); } @@ -884,7 +878,7 @@ static void StartTrap(ObjectInfo *obj) if (obj->loaded) { obj->collision = TrapCollision; - obj->control = FallingCeilingControl; + obj->control = ControlFallingCeiling; } obj = &Objects[ID_ROLLINGBALL]; @@ -934,7 +928,7 @@ static void StartTrap(ObjectInfo *obj) if (obj->loaded) { obj->Initialize = InitializeExplosion; - obj->control = ExplosionControl; + obj->control = ControlExplosion; obj->drawRoutine = nullptr; obj->usingDrawAnimatingItem = false; } @@ -983,8 +977,8 @@ static void StartSwitch(ObjectInfo *obj) if (obj->loaded) { obj->Initialize = InitializeWreckingBall; - obj->collision = WreckingBallCollision; - obj->control = WreckingBallControl; + obj->collision = CollideWreckingBall; + obj->control = ControlWreckingBall; obj->SetHitEffect(true); } } diff --git a/TombEngine/Objects/Utils/VehicleHelpers.cpp b/TombEngine/Objects/Utils/VehicleHelpers.cpp index d007215fc..3f0244684 100644 --- a/TombEngine/Objects/Utils/VehicleHelpers.cpp +++ b/TombEngine/Objects/Utils/VehicleHelpers.cpp @@ -2,7 +2,8 @@ #include "Objects/Utils/VehicleHelpers.h" #include "Game/collision/collide_item.h" -#include "Game/collision/sphere.h" +#include "Game/collision/Point.h" +#include "Game/collision/Sphere.h" #include "Game/effects/simple_particle.h" #include "Game/effects/Streamer.h" #include "Game/effects/tomb4fx.h" @@ -15,6 +16,8 @@ #include "Sound/sound.h" #include "Specific/Input/Input.h" +using namespace TEN::Collision::Point; +using namespace TEN::Collision::Sphere; using namespace TEN::Effects::Streamer; using namespace TEN::Hud; using namespace TEN::Input; @@ -54,7 +57,7 @@ namespace TEN::Entities::Vehicles return VehicleMountType::None; // Assess object collision. - if (!TestBoundsCollide(vehicleItem, laraItem, coll->Setup.Radius) || !TestCollision(vehicleItem, laraItem)) + if (!TestBoundsCollide(vehicleItem, laraItem, coll->Setup.Radius) || !HandleItemSphereCollision(*vehicleItem, *laraItem)) return VehicleMountType::None; bool hasInputAction = IsHeld(In::Action); @@ -154,15 +157,15 @@ namespace TEN::Entities::Vehicles pos->z = vehicleItem->Pose.Position.z + (forward * cosY) - (right * sinY); // Get collision a bit higher to be able to detect bridges. - auto probe = GetCollision(pos->x, pos->y - CLICK(2), pos->z, vehicleItem->RoomNumber); + auto probe = GetPointCollision(Vector3i(pos->x, pos->y - CLICK(2), pos->z), vehicleItem->RoomNumber); - if (pos->y < probe.Position.Ceiling || probe.Position.Ceiling == NO_HEIGHT) + if (pos->y < probe.GetCeilingHeight() || probe.GetCeilingHeight() == NO_HEIGHT) return NO_HEIGHT; - if (pos->y > probe.Position.Floor && clamp) - pos->y = probe.Position.Floor; + if (pos->y > probe.GetFloorHeight() && clamp) + pos->y = probe.GetFloorHeight(); - return probe.Position.Floor; + return probe.GetFloorHeight(); } int GetVehicleWaterHeight(ItemInfo* vehicleItem, int forward, int right, bool clamp, Vector3i* pos) @@ -175,12 +178,12 @@ namespace TEN::Entities::Vehicles point = Vector3::Transform(point, world); *pos = Vector3i(point); - auto pointColl = GetCollision(pos->x, pos->y, pos->z, vehicleItem->RoomNumber); - int height = GetWaterHeight(pos->x, pos->y, pos->z, pointColl.RoomNumber); + auto pointColl = GetPointCollision(*pos, vehicleItem->RoomNumber); + int height = GetPointCollision(Vector3i(pos->x, pos->y, pos->z), pointColl.GetRoomNumber()).GetWaterTopHeight(); if (height == NO_HEIGHT) { - height = pointColl.Position.Floor; + height = pointColl.GetFloorHeight(); if (height == NO_HEIGHT) return height; } @@ -216,7 +219,7 @@ namespace TEN::Entities::Vehicles if (TestEnvironment(ENV_FLAG_WATER, vehicleItem) || TestEnvironment(ENV_FLAG_SWAMP, vehicleItem)) { - auto waterDepth = (float)GetWaterDepth(vehicleItem); + int waterDepth = GetPointCollision(*vehicleItem).GetWaterBottomHeight(); // HACK: Sometimes quadbike test position may end up under non-portal ceiling block. // GetWaterDepth returns DEEP_WATER constant in that case, which is too large for our needs. @@ -237,11 +240,8 @@ namespace TEN::Entities::Vehicles if (isWater) { - int waterHeight = GetWaterHeight(vehicleItem); + int waterHeight = GetPointCollision(*vehicleItem).GetWaterTopHeight(); SpawnVehicleWake(*vehicleItem, wakeOffset, waterHeight); - - //SpawnStreamer(vehicleItem, -wakeOffset.x, waterHeight / 2, wakeOffset.z, 1, true, 5.0f, 50, 9.0f); - //SpawnStreamer(vehicleItem, wakeOffset.x, waterHeight / 2, wakeOffset.z, 2, true, 5.0f, 50, 9.0f); } } @@ -253,7 +253,7 @@ namespace TEN::Entities::Vehicles } else { - int waterHeight = vehicleItem->Pose.Position.y - GetWaterHeight(vehicleItem); + int waterHeight = vehicleItem->Pose.Position.y - GetPointCollision(*vehicleItem).GetWaterTopHeight(); if (waterDepth > VEHICLE_WATER_HEIGHT_MAX && waterHeight > VEHICLE_WATER_HEIGHT_MAX) { diff --git a/TombEngine/Objects/game_object_ids.h b/TombEngine/Objects/game_object_ids.h index f04d93296..5bf09dace 100644 --- a/TombEngine/Objects/game_object_ids.h +++ b/TombEngine/Objects/game_object_ids.h @@ -40,7 +40,8 @@ enum GAME_OBJECT_ID : short ID_LARA_DIRT_MESH, ID_LARA_CROWBAR_ANIM, ID_LARA_TORCH_ANIM, - ID_HAIR, + ID_HAIR_PRIMARY, + ID_HAIR_SECONDARY, ID_SNOWMOBILE_LARA_ANIMS = 50, ID_SNOWMOBILE, @@ -165,7 +166,7 @@ enum GAME_OBJECT_ID : short ID_GIANT_MUTANT = 225, // 226 ID_PROJ_SHARD = 227, - ID_PROJ_BOMB, + ID_PROJ_BOMB, ID_YETI, ID_BIRDMONSTER, ID_MARCO_BARTOLI, @@ -232,10 +233,10 @@ enum GAME_OBJECT_ID : short ID_CLAW_MUTANT, ID_WASP_MUTANT, ID_TWIN_AUTO_GUN, - ID_SKATEBOARD = 296, + ID_SKATEBOARD, ID_SKATEBOARD_KID, ID_WINSTON, - //299 + ID_SEAL_MUTANT, ID_SPRINGBOARD = 320, ID_ROLLING_SPINDLE, @@ -1000,6 +1001,8 @@ enum GAME_OBJECT_ID : short // NOTE: 1378 - 1379 reserved for blood effects. -- Sezz 2023.05.29 ID_CROSSHAIR = 1380, ID_SPEEDOMETER, + ID_CUSTOM_BAR_GRAPHIC, + ID_CUSTOM_AMMO_GRAPHIC, ID_PANEL_BORDER = 1400, ID_PANEL_MIDDLE, diff --git a/TombEngine/Objects/objectslist.h b/TombEngine/Objects/objectslist.h index f8f04a841..267bbfb7a 100644 --- a/TombEngine/Objects/objectslist.h +++ b/TombEngine/Objects/objectslist.h @@ -32,8 +32,8 @@ template std::enable_if_t std::enable_if_t + #include +#include + +#include "Game/Debug/Debug.h" +#include "Renderer/RendererUtils.h" namespace TEN::Renderer::ConstantBuffers { @@ -15,12 +17,12 @@ namespace TEN::Renderer::ConstantBuffers ConstantBuffer() = default; ConstantBuffer(ID3D11Device* device) { - D3D11_BUFFER_DESC desc = {}; + auto desc = D3D11_BUFFER_DESC{}; desc.ByteWidth = sizeof(CBuff); desc.Usage = D3D11_USAGE_DYNAMIC; desc.BindFlags = D3D11_BIND_CONSTANT_BUFFER; desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; - Utils::throwIfFailed(device->CreateBuffer(&desc, NULL, buffer.GetAddressOf())); + Utils::throwIfFailed(device->CreateBuffer(&desc, nullptr, buffer.GetAddressOf())); buffer->SetPrivateData(WKPDID_D3DDebugObjectName, 32, typeid(CBuff).name()); } @@ -31,8 +33,8 @@ namespace TEN::Renderer::ConstantBuffers void UpdateData(CBuff& data, ID3D11DeviceContext* ctx) { - D3D11_MAPPED_SUBRESOURCE mappedResource; - HRESULT res = ctx->Map(buffer.Get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource); + auto mappedResource = D3D11_MAPPED_SUBRESOURCE{}; + auto res = ctx->Map(buffer.Get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource); if (SUCCEEDED(res)) { void* dataPtr = (mappedResource.pData); @@ -40,7 +42,9 @@ namespace TEN::Renderer::ConstantBuffers ctx->Unmap(buffer.Get(), 0); } else - TENLog("Could not update constant buffer!", LogLevel::Error); + { + TENLog("Could not update constant buffer.", LogLevel::Error); + } } }; } diff --git a/TombEngine/Renderer/ConstantBuffers/PostProcessBuffer.h b/TombEngine/Renderer/ConstantBuffers/PostProcessBuffer.h index e0fc6821f..577c6d37f 100644 --- a/TombEngine/Renderer/ConstantBuffers/PostProcessBuffer.h +++ b/TombEngine/Renderer/ConstantBuffers/PostProcessBuffer.h @@ -1,8 +1,15 @@ -#include +#include "Renderer/RendererEnums.h" namespace TEN::Renderer::ConstantBuffers { - using namespace DirectX::SimpleMath; + struct alignas(16) ShaderLensFlare + { + Vector3 Position; + float Padding1; + //-- + Vector3 Color; + float Padding2; + }; struct alignas(16) CPostProcessBuffer { @@ -15,5 +22,9 @@ namespace TEN::Renderer::ConstantBuffers Vector3 Tint; //-- Vector4 SSAOKernel[64]; + //-- + ShaderLensFlare LensFlares[MAX_LENS_FLARES_DRAW]; + //-- + int NumLensFlares; }; -} \ No newline at end of file +} diff --git a/TombEngine/Renderer/Graphics/IndexBuffer.h b/TombEngine/Renderer/Graphics/IndexBuffer.h index d2869b00e..690264d5d 100644 --- a/TombEngine/Renderer/Graphics/IndexBuffer.h +++ b/TombEngine/Renderer/Graphics/IndexBuffer.h @@ -3,7 +3,7 @@ #include #include #include "Renderer/RendererUtils.h" -#include "Game/debug/debug.h" +#include "Game/Debug/Debug.h" #include "Specific/fast_vector.h" namespace TEN::Renderer::Graphics diff --git a/TombEngine/Renderer/RenderView.cpp b/TombEngine/Renderer/RenderView.cpp index 3c6d416b3..1f02db401 100644 --- a/TombEngine/Renderer/RenderView.cpp +++ b/TombEngine/Renderer/RenderView.cpp @@ -51,6 +51,8 @@ namespace TEN::Renderer DisplaySpritesToDraw.clear(); SortedStaticsToDraw.clear(); FogBulbsToDraw.clear(); + LensFlaresToDraw.clear(); + TransparentObjectsToDraw.clear(); } RenderViewCamera::RenderViewCamera(CAMERA_INFO* cam, float roll, float fov, float n, float f, int w, int h) diff --git a/TombEngine/Renderer/RenderView.h b/TombEngine/Renderer/RenderView.h index 5260b7626..b028b4788 100644 --- a/TombEngine/Renderer/RenderView.h +++ b/TombEngine/Renderer/RenderView.h @@ -14,6 +14,7 @@ #include "Renderer/Structures/RendererRoom.h" #include "Renderer/Structures/RendererSortableObject.h" #include "Renderer/Structures/RendererSpriteToDraw.h" +#include "Renderer/Structures/RendererLensFlare.h" namespace TEN::Renderer { @@ -51,6 +52,7 @@ namespace TEN::Renderer std::vector DisplaySpritesToDraw = {}; std::map> SortedStaticsToDraw = {}; std::vector TransparentObjectsToDraw = {}; + std::vector LensFlaresToDraw = {}; RenderView(CAMERA_INFO* cam, float roll, float fov, float nearPlane, float farPlane, int w, int h); RenderView(const Vector3& pos, const Vector3& dir, const Vector3& up, int w, int h, int room, float nearPlane, float farPlane, float fov); diff --git a/TombEngine/Renderer/Renderer.cpp b/TombEngine/Renderer/Renderer.cpp index 32a97e24c..e251bc09e 100644 --- a/TombEngine/Renderer/Renderer.cpp +++ b/TombEngine/Renderer/Renderer.cpp @@ -18,7 +18,10 @@ namespace TEN::Renderer using namespace Utils; Renderer g_Renderer; - Renderer::Renderer() : _gameCamera({0, 0, 0}, {0, 0, 1}, {0, 1, 0}, 1, 1, 0, 1, 10, 90) + Renderer::Renderer() : + _gameCamera({0, 0, 0}, {0, 0, 1}, {0, 1, 0}, 1, 1, 0, 1, 10, 90), + _oldGameCamera({ 0, 0, 0 }, { 0, 0, 1 }, { 0, 1, 0 }, 1, 1, 0, 1, 10, 90), + _currentGameCamera({ 0, 0, 0 }, { 0, 0, 1 }, { 0, 1, 0 }, 1, 1, 0, 1, 10, 90) { } @@ -31,8 +34,6 @@ namespace TEN::Renderer { _shadowLight = nullptr; - ClearSceneItems(); - _moveableObjects.resize(0); _staticObjects.resize(0); _sprites.resize(0); @@ -50,6 +51,7 @@ namespace TEN::Renderer for (auto& item : _items) { + item.DisableInterpolation = true; item.PrevRoomNumber = NO_VALUE; item.RoomNumber = NO_VALUE; item.ItemNumber = NO_VALUE; @@ -57,14 +59,6 @@ namespace TEN::Renderer } } - void Renderer::ClearSceneItems() - { - _lines2DToDraw.clear(); - _lines3DToDraw.clear(); - _triangles3DToDraw.clear(); - _gameCamera.Clear(); - } - void Renderer::Lock() { _isLocked = true; @@ -73,14 +67,14 @@ namespace TEN::Renderer int Renderer::Synchronize() { // Sync the renderer - int nf = Sync(); + int nf = TimeSync(); if (nf < 2) { int i = 2 - nf; nf = 2; do { - while (!Sync()); + while (!TimeSync()); i--; } while (i); @@ -425,4 +419,9 @@ namespace TEN::Renderer _context->RSSetScissorRects(1, rects); } + + void Renderer::SetGraphicsSettingsChanged() + { + _graphicsSettingsChanged = true; + } } diff --git a/TombEngine/Renderer/Renderer.h b/TombEngine/Renderer/Renderer.h index d738ae413..360ae55d9 100644 --- a/TombEngine/Renderer/Renderer.h +++ b/TombEngine/Renderer/Renderer.h @@ -66,8 +66,10 @@ #include "Renderer/ConstantBuffers/SMAABuffer.h" #include "Renderer/Structures/RendererObject.h" #include "Graphics/Vertices/PostProcessVertex.h" +#include "Renderer/Structures/RendererStar.h" enum GAME_OBJECT_ID : short; +enum class SphereSpaceType; class EulerAngles; struct AnimFrameInterpData; struct CAMERA_INFO; @@ -150,6 +152,7 @@ namespace TEN::Renderer ComPtr _psHUDTexture; ComPtr _psHUDBarColor; ComPtr _vsGBufferRooms; + ComPtr _vsGBufferRoomsAnimated; ComPtr _vsGBufferItems; ComPtr _vsGBufferStatics; ComPtr _vsGBufferInstancedStatics; @@ -160,6 +163,8 @@ namespace TEN::Renderer // Constant buffers RenderView _gameCamera; + RenderView _oldGameCamera; + RenderView _currentGameCamera; ConstantBuffer _cbCameraMatrices; CItemBuffer _stItem; ConstantBuffer _cbItem; @@ -199,7 +204,7 @@ namespace TEN::Renderer std::vector _stringsToDraw; float _blinkColorValue = 0.0f; float _blinkTime = 0.0f; - bool _isBlinkUpdated = false; + float _oldBlinkTime = 0.0f; // Graphics resources Texture2D _logo; @@ -288,7 +293,7 @@ namespace TEN::Renderer int _numCheckPortalCalls = 0; int _numGetVisibleRoomsCalls = 0; - int _currentY; + float _currentLineHeight = 0.0f;; RendererDebugPage _debugPage = RendererDebugPage::None; @@ -303,10 +308,11 @@ namespace TEN::Renderer // Screen settings int _screenWidth; int _screenHeight; + int _refreshRate; bool _isWindowed; float _farView = DEFAULT_FAR_VIEW; - // A flag to prevent extra renderer object addition + // A flag to prevent extra renderer object additions bool _isLocked = false; // Caching state changes @@ -353,6 +359,8 @@ namespace TEN::Renderer ComPtr _psPostProcessNegative; ComPtr _psPostProcessExclusion; ComPtr _psPostProcessFinalPass; + ComPtr _psPostProcessLensFlare; + bool _doingFullscreenPass = false; // SSAO @@ -373,6 +381,11 @@ namespace TEN::Renderer VertexBuffer _sortedPolygonsVertexBuffer; IndexBuffer _sortedPolygonsIndexBuffer; + // High framerate. + float _interpolationFactor = 0.0f; + + bool _graphicsSettingsChanged = false; + // Private functions void ApplySMAA(RenderTarget2D* renderTarget, RenderView& view); void ApplyFXAA(RenderTarget2D* renderTarget, RenderView& view); @@ -399,12 +412,9 @@ namespace TEN::Renderer void CollectLightsForCamera(); void CalculateLightFades(RendererItem* item); void CollectEffects(short roomNumber); - void ClearSceneItems(); - void ClearDynamicLights(); void ClearShadowMap(); void CalculateSSAO(RenderView& view); void UpdateItemAnimations(RenderView& view); - bool PrintDebugMessage(int x, int y, int alpha, byte r, byte g, byte b, LPCSTR Message); void InitializeScreen(int w, int h, HWND handle, bool reset); void InitializeCommonTextures(); void InitializeGameBars(); @@ -495,10 +505,11 @@ namespace TEN::Renderer void SetScissor(RendererRectangle rectangle); bool SetupBlendModeAndAlphaTest(BlendMode blendMode, RendererPass rendererPass, int drawPass); void SortAndPrepareSprites(RenderView& view); - void ResetAnimations(); + void ResetItems(); void ResetScissor(); void ResetDebugVariables(); float CalculateFrameRate(); + void InterpolateCamera(float interpFactor); void CopyRenderTarget(RenderTarget2D* source, RenderTarget2D* dest, RenderView& view); void AddSpriteBillboard(RendererSprite* sprite, const Vector3& pos, const Vector4& color, float orient2D, float scale, @@ -582,25 +593,27 @@ namespace TEN::Renderer void DrawBar(float percent, const RendererHudBar& bar, GAME_OBJECT_ID textureSlot, int frame, bool poison); void Create(); void Initialize(int w, int h, bool windowed, HWND handle); - void Render(); - void RenderTitle(); + void Render(float interpFactor); + void RenderTitle(float interpFactor); void Lock(); bool PrepareDataForTheRenderer(); - void UpdateCameraMatrices(CAMERA_INFO* cam, float roll, float fov, float farView); + void UpdateCameraMatrices(CAMERA_INFO* cam, float farView); void RenderSimpleSceneToParaboloid(RenderTarget2D* renderTarget, Vector3 position, int emisphere); void DumpGameScene(); void RenderInventory(); void RenderScene(RenderTarget2D* renderTarget, bool doAntialiasing, RenderView& view); + void PrepareScene(); void ClearScene(); void SaveScreenshot(); - void PrintDebugMessage(LPCSTR message, ...); + void PrintDebugMessage(LPCSTR msg, va_list args); + void PrintDebugMessage(LPCSTR msg, ...); void DrawDebugInfo(RenderView& view); void SwitchDebugPage(bool goBack); void DrawDisplayPickup(const DisplayPickup& pickup); int Synchronize(); void AddString(int x, int y, const std::string& string, D3DCOLOR color, int flags); void AddString(const std::string& string, const Vector2& pos, const Color& color, float scale, int flags); - void AddDebugString(const std::string& string, const Vector2& pos, const Color& color, float scale, int flags, RendererDebugPage page); + void AddDebugString(const std::string& string, const Vector2& pos, const Color& color, float scale, RendererDebugPage page = RendererDebugPage::None); void FreeRendererData(); void AddDynamicLight(int x, int y, int z, short falloff, byte r, byte g, byte b); void RenderLoadingScreen(float percentage); @@ -615,11 +628,11 @@ namespace TEN::Renderer void AddDebugLine(const Vector3& origin, const Vector3& target, const Color& color, RendererDebugPage page = RendererDebugPage::None); void AddDebugTriangle(const Vector3& vertex0, const Vector3& vertex1, const Vector3& vertex2, const Color& color, RendererDebugPage page = RendererDebugPage::None); void AddDebugTarget(const Vector3& center, const Quaternion& orient, float radius, const Color& color, RendererDebugPage page = RendererDebugPage::None); - void AddDebugBox(const std::array& corners, const Color& color, RendererDebugPage page = RendererDebugPage::None, bool isWireframe = true); + void AddDebugBox(const std::array& corners, const Color& color, RendererDebugPage page = RendererDebugPage::None, bool isWireframe = true); void AddDebugBox(const Vector3& min, const Vector3& max, const Color& color, RendererDebugPage page = RendererDebugPage::None, bool isWireframe = true); void AddDebugBox(const BoundingOrientedBox& box, const Color& color, RendererDebugPage page = RendererDebugPage::None, bool isWireframe = true); void AddDebugBox(const BoundingBox& box, const Color& color, RendererDebugPage page = RendererDebugPage::None, bool isWireframe = true); - void AddDebugCone(const Vector3& center, const Quaternion& orient, float radius, float length, const Vector4& color, RendererDebugPage page, bool isWireframe); + void AddDebugCone(const Vector3& center, const Quaternion& orient, float radius, float length, const Vector4& color, RendererDebugPage page, bool isWireframe = true); void AddDebugCylinder(const Vector3& center, const Quaternion& orient, float radius, float length, const Color& color, RendererDebugPage page = RendererDebugPage::None, bool isWireframe = true); void AddDebugSphere(const Vector3& center, float radius, const Color& color, RendererDebugPage page = RendererDebugPage::None, bool isWireframe = true); void AddDebugSphere(const BoundingSphere& sphere, const Color& color, RendererDebugPage page = RendererDebugPage::None, bool isWireframe = true); @@ -628,18 +641,24 @@ namespace TEN::Renderer void FlipRooms(short roomNumber1, short roomNumber2); void UpdateLaraAnimations(bool force); void UpdateItemAnimations(int itemNumber, bool force); - int GetSpheres(short itemNumber, BoundingSphere* ptr, char worldSpace, Matrix local); + std::vector GetSpheres(int itemNumber); void GetBoneMatrix(short itemNumber, int jointIndex, Matrix* outMatrix); void DrawObjectIn2DSpace(int objectNumber, Vector2 pos2D, EulerAngles orient, float scale1, float opacity = 1.0f, int meshBits = NO_JOINT_BITS); void SetLoadingScreen(std::wstring& fileName); void SetTextureOrDefault(Texture2D& texture, std::wstring path); std::string GetDefaultAdapterName(); + void SaveOldState(); - Vector2i GetScreenResolution() const; - std::optional Get2DPosition(const Vector3& pos) const; - Vector3 GetAbsEntityBonePosition(int itemNumber, int jointIndex, const Vector3& relOffset = Vector3::Zero); + float GetFramerateMultiplier() const; + float GetInterpolationFactor() const; + Vector2i GetScreenResolution() const; + int GetScreenRefreshRate() const; + std::optional Get2DPosition(const Vector3& pos) const; std::pair GetRay(const Vector2& pos) const; - + + Vector3 GetMoveableBonePosition(int itemNumber, int boneID, const Vector3& relOffset = Vector3::Zero); + Quaternion GetMoveableBoneOrientation(int itemNumber, int boneID); + void AddDisplaySprite(const RendererSprite& sprite, const Vector2& pos2D, short orient, const Vector2& size, const Vector4& color, int priority, BlendMode blendMode, const Vector2& aspectCorrection, RenderView& renderView); void CollectDisplaySprites(RenderView& renderView); @@ -650,6 +669,8 @@ namespace TEN::Renderer void SetPostProcessStrength(float strength); Vector3 GetPostProcessTint(); void SetPostProcessTint(Vector3 color); + + void SetGraphicsSettingsChanged(); }; extern Renderer g_Renderer; diff --git a/TombEngine/Renderer/RendererCompatibility.cpp b/TombEngine/Renderer/RendererCompatibility.cpp index 4b22ff7e3..2bf5f89eb 100644 --- a/TombEngine/Renderer/RendererCompatibility.cpp +++ b/TombEngine/Renderer/RendererCompatibility.cpp @@ -212,14 +212,14 @@ namespace TEN::Renderer r->ItemsToDraw.reserve(MAX_ITEMS_DRAW); r->EffectsToDraw.reserve(MAX_ITEMS_DRAW); - Vector3 boxMin = Vector3(room.x + BLOCK(1), room.maxceiling - CLICK(1), room.z + BLOCK(1)); - Vector3 boxMax = Vector3(room.x + (room.xSize - 1) * BLOCK(1), room.minfloor + CLICK(1), room.z + (room.zSize - 1) * BLOCK(1)); + Vector3 boxMin = Vector3(room.Position.x + BLOCK(1), room.TopHeight - CLICK(1), room.Position.z + BLOCK(1)); + Vector3 boxMax = Vector3(room.Position.x + (room.XSize - 1) * BLOCK(1), room.BottomHeight + CLICK(1), room.Position.z + (room.ZSize - 1) * BLOCK(1)); Vector3 center = (boxMin + boxMax) / 2.0f; Vector3 extents = boxMax - center; r->BoundingBox = BoundingBox(center, extents); r->Neighbors.clear(); - for (int j : room.neighbors) + for (int j : room.NeighborRoomNumbers) if (g_Level.Rooms[j].Active()) r->Neighbors.push_back(j); @@ -238,9 +238,9 @@ namespace TEN::Renderer for (int k = 0; k < 4; k++) { door->AbsoluteVertices[k] = Vector4( - room.x + oldDoor->vertices[k].x, - room.y + oldDoor->vertices[k].y, - room.z + oldDoor->vertices[k].z, + room.Position.x + oldDoor->vertices[k].x, + room.Position.y + oldDoor->vertices[k].y, + room.Position.z + oldDoor->vertices[k].z, 1.0f); } } @@ -312,9 +312,9 @@ namespace TEN::Renderer Vertex* vertex = &_roomsVertices[lastVertex]; int index = poly.indices[k]; - vertex->Position.x = room.x + room.positions[index].x; - vertex->Position.y = room.y + room.positions[index].y; - vertex->Position.z = room.z + room.positions[index].z; + vertex->Position.x = room.Position.x + room.positions[index].x; + vertex->Position.y = room.Position.y + room.positions[index].y; + vertex->Position.z = room.Position.z + room.positions[index].z; bucket.Centre += vertex->Position; @@ -516,7 +516,7 @@ namespace TEN::Renderer &moveable, &g_Level.Meshes[obj->meshIndex + j], j, MoveablesIds[i] == ID_LARA_SKIN_JOINTS, - MoveablesIds[i] == ID_HAIR, &lastVertex, &lastIndex); + MoveablesIds[i] == ID_HAIR_PRIMARY || MoveablesIds[i] == ID_HAIR_SECONDARY, &lastVertex, &lastIndex); moveable.ObjectMeshes.push_back(mesh); _meshes.push_back(mesh); @@ -687,7 +687,7 @@ namespace TEN::Renderer } } } - else if (MoveablesIds[i] == ID_HAIR && isSkinPresent) + else if (MoveablesIds[i] == ID_HAIR_PRIMARY && isSkinPresent) { for (int j = 0; j < obj->nmeshes; j++) { @@ -709,7 +709,6 @@ namespace TEN::Renderer // HACK: Hardcoded hair base parent vertices. int parentVertices0[] = { 37, 39, 40, 38 }; // Single braid. int parentVertices1[] = { 79, 78, 76, 77 }; // Left pigtail. - int parentVertices2[] = { 68, 69, 70, 71 }; // Right pigtail. auto& skinObj = GetRendererObject(GAME_OBJECT_ID::ID_LARA_SKIN); auto* parentMesh = skinObj.ObjectMeshes[LM_HEAD]; @@ -724,25 +723,98 @@ namespace TEN::Renderer for (int v2 = 0; v2 < parentBucket->NumVertices; v2++) { auto* parentVertex = &_moveablesVertices[parentBucket->StartVertex + v2]; - - // TODO - if (isYoung) + if (isYoung && parentVertex->OriginalIndex == parentVertices1[currentVertex->OriginalIndex]) { - if (parentVertex->OriginalIndex == parentVertices1[currentVertex->OriginalIndex]) - { - currentVertex->Bone = 0; - currentVertex->Position = parentVertex->Position; - currentVertex->Normal = parentVertex->Normal; - } + currentVertex->Bone = 0; + currentVertex->Position = parentVertex->Position; + currentVertex->Normal = parentVertex->Normal; } - else + else if (parentVertex->OriginalIndex == parentVertices0[currentVertex->OriginalIndex]) { - if (parentVertex->OriginalIndex == parentVertices0[currentVertex->OriginalIndex]) - { - currentVertex->Bone = 0; - currentVertex->Position = parentVertex->Position; - currentVertex->Normal = parentVertex->Normal; - } + currentVertex->Bone = 0; + currentVertex->Position = parentVertex->Position; + currentVertex->Normal = parentVertex->Normal; + } + } + } + } + } + // Link meshes > 0 to parent meshes. + else + { + auto* parentMesh = moveable.ObjectMeshes[j - 1]; + auto* parentBone = moveable.LinearizedBones[j - 1]; + + for (int b2 = 0; b2 < parentMesh->Buckets.size(); b2++) + { + auto* parentBucket = &parentMesh->Buckets[b2]; + for (int v2 = 0; v2 < parentBucket->NumVertices; v2++) + { + auto* parentVertex = &_moveablesVertices[parentBucket->StartVertex + v2]; + + int x1 = _moveablesVertices[currentBucket.StartVertex + v1].Position.x + currentBone->GlobalTranslation.x; + int y1 = _moveablesVertices[currentBucket.StartVertex + v1].Position.y + currentBone->GlobalTranslation.y; + int z1 = _moveablesVertices[currentBucket.StartVertex + v1].Position.z + currentBone->GlobalTranslation.z; + + int x2 = _moveablesVertices[parentBucket->StartVertex + v2].Position.x + parentBone->GlobalTranslation.x; + int y2 = _moveablesVertices[parentBucket->StartVertex + v2].Position.y + parentBone->GlobalTranslation.y; + int z2 = _moveablesVertices[parentBucket->StartVertex + v2].Position.z + parentBone->GlobalTranslation.z; + + if (abs(x1 - x2) < 2 && abs(y1 - y2) < 2 && abs(z1 - z2) < 2) + { + currentVertex->Bone = j; + currentVertex->Position = parentVertex->Position; + currentVertex->Normal = parentVertex->Normal; + currentVertex->AnimationFrameOffset = parentVertex->AnimationFrameOffset; + currentVertex->Tangent = parentVertex->Tangent; + break; + } + } + } + } + } + } + } + } + else if (MoveablesIds[i] == ID_HAIR_SECONDARY && isSkinPresent) + { + for (int j = 0; j < obj->nmeshes; j++) + { + auto* currentMesh = moveable.ObjectMeshes[j]; + auto* currentBone = moveable.LinearizedBones[j]; + + for (const auto& currentBucket : currentMesh->Buckets) + { + for (int v1 = 0; v1 < currentBucket.NumVertices; v1++) + { + auto* currentVertex = &_moveablesVertices[currentBucket.StartVertex + v1]; + currentVertex->Bone = j + 1; + + // Link mesh 0 to head. + if (j == 0) + { + bool isYoung = (g_GameFlow->GetLevel(CurrentLevel)->GetLaraType() == LaraType::Young); + + // HACK: Hardcoded hair base parent vertices. + int parentVertices2[] = { 68, 69, 70, 71 }; // Right pigtail. + + auto& skinObj = GetRendererObject(GAME_OBJECT_ID::ID_LARA_SKIN); + auto* parentMesh = skinObj.ObjectMeshes[LM_HEAD]; + auto* parentBone = skinObj.LinearizedBones[LM_HEAD]; + + if (currentVertex->OriginalIndex < 4) + { + for (int b2 = 0; b2 < parentMesh->Buckets.size(); b2++) + { + auto* parentBucket = &parentMesh->Buckets[b2]; + for (int v2 = 0; v2 < parentBucket->NumVertices; v2++) + { + auto* parentVertex = &_moveablesVertices[parentBucket->StartVertex + v2]; + if (isYoung && parentVertex->OriginalIndex == parentVertices2[currentVertex->OriginalIndex]) + { + currentVertex->Bone = 0; + currentVertex->Position = parentVertex->Position; + currentVertex->Normal = parentVertex->Normal; } } } diff --git a/TombEngine/Renderer/RendererDebug.cpp b/TombEngine/Renderer/RendererDebug.cpp index 89ce89d6e..b65d6d02c 100644 --- a/TombEngine/Renderer/RendererDebug.cpp +++ b/TombEngine/Renderer/RendererDebug.cpp @@ -35,26 +35,29 @@ namespace TEN::Renderer _numCheckPortalCalls = 0; _numGetVisibleRoomsCalls = 0; - _currentY; + _currentLineHeight; } - bool Renderer::PrintDebugMessage(int x, int y, int alpha, byte r, byte g, byte b, LPCSTR Message) + void Renderer::PrintDebugMessage(LPCSTR msg, va_list args) { - return true; - } + constexpr auto LINE_X_POS = DISPLAY_SPACE_RES.x / 100; + constexpr auto LINE_SPACING = DISPLAY_SPACE_RES.y / 30; + constexpr auto COLOR = Color(1.0f, 1.0f, 1.0f); + constexpr auto SCALE = 0.8f; - void Renderer::PrintDebugMessage(LPCSTR message, ...) - { char buffer[255]; ZeroMemory(buffer, 255); + _vsprintf_l(buffer, msg, nullptr, args); + AddString(buffer, Vector2(LINE_X_POS, _currentLineHeight), COLOR, SCALE, (int)PrintStringFlags::Outline); - va_list args; - va_start(args, message); - _vsprintf_l(buffer, message, NULL, args); + _currentLineHeight += LINE_SPACING; + } + + void Renderer::PrintDebugMessage(LPCSTR msg, ...) + { + auto args = va_list{}; + va_start(args, msg); + PrintDebugMessage(msg, args); va_end(args); - - AddString(10, _currentY, buffer, 0xFFFFFFFF, (int)PrintStringFlags::Outline); - - _currentY += 20; } } diff --git a/TombEngine/Renderer/RendererDraw.cpp b/TombEngine/Renderer/RendererDraw.cpp index 573141b8c..f0e73c32a 100644 --- a/TombEngine/Renderer/RendererDraw.cpp +++ b/TombEngine/Renderer/RendererDraw.cpp @@ -10,6 +10,7 @@ #include "Game/camera.h" #include "Game/control/control.h" #include "Game/control/volume.h" +#include "Game/effects/DisplaySprite.h" #include "Game/effects/Hair.h" #include "Game/effects/tomb4fx.h" #include "Game/effects/weather.h" @@ -27,10 +28,10 @@ #include "Objects/TR5/Emitter/tr5_rats_emitter.h" #include "Renderer/RenderView.h" #include "Renderer/Renderer.h" +#include "Renderer/Structures/RendererSortableObject.h" #include "Specific/configuration.h" #include "Specific/level.h" #include "Specific/winmain.h" -#include "Renderer/Structures/RendererSortableObject.h" using namespace std::chrono; using namespace TEN::Effects::Hair; @@ -38,6 +39,8 @@ using namespace TEN::Entities::Creatures::TR3; using namespace TEN::Entities::Generic; using namespace TEN::Hud; using namespace TEN::Renderer::Structures; +using namespace TEN::Effects::Environment; +using namespace TEN::Effects::DisplaySprite; extern GUNSHELL_STRUCT Gunshells[MAX_GUNSHELL]; @@ -126,11 +129,11 @@ namespace TEN::Renderer void Renderer::RenderShadowMap(RendererItem* item, RenderView& renderView) { // Doesn't cast shadow - if (_moveableObjects[item->ObjectNumber].value().ShadowType == ShadowMode::None) + if (_moveableObjects[item->ObjectID].value().ShadowType == ShadowMode::None) return; // Only render for Lara if such setting is active - if (g_Configuration.ShadowType == ShadowMode::Lara && _moveableObjects[item->ObjectNumber].value().ShadowType != ShadowMode::Lara) + if (g_Configuration.ShadowType == ShadowMode::Lara && _moveableObjects[item->ObjectID].value().ShadowType != ShadowMode::Lara) return; // No shadow light found @@ -206,12 +209,12 @@ namespace TEN::Renderer SetAlphaTest(AlphaTestMode::GreatherThan, ALPHA_TEST_THRESHOLD); - RendererObject& obj = GetRendererObject((GAME_OBJECT_ID)item->ObjectNumber); + RendererObject& obj = GetRendererObject((GAME_OBJECT_ID)item->ObjectID); - _stItem.World = item->World; + _stItem.World = item->InterpolatedWorld; _stItem.Color = item->Color; _stItem.AmbientLight = item->AmbientLight; - memcpy(_stItem.BonesMatrices, item->AnimationTransforms, sizeof(Matrix) * MAX_BONES); + memcpy(_stItem.BonesMatrices, item->InterpolatedAnimTransforms, sizeof(Matrix) * MAX_BONES); for (int k = 0; k < MAX_BONES; k++) _stItem.BoneLightModes[k] = (int)LightMode::Static; @@ -221,7 +224,7 @@ namespace TEN::Renderer for (int k = 0; k < obj.ObjectMeshes.size(); k++) { - auto* mesh = GetMesh(item->MeshIndex[k]); + auto* mesh = GetMesh(item->MeshIds[k]); for (auto& bucket : mesh->Buckets) { @@ -238,7 +241,7 @@ namespace TEN::Renderer } } - if (item->ObjectNumber == ID_LARA) + if (item->ObjectID == ID_LARA) { RendererRoom& room = _rooms[item->RoomNumber]; @@ -254,46 +257,49 @@ namespace TEN::Renderer auto& room = _rooms[LaraItem->RoomNumber]; auto* item = &_items[LaraItem->Index]; - int gunShellsCount = 0; - short objectNumber = 0; + int gunShellCount = 0; + int objectID = 0; for (int i = 0; i < MAX_GUNSHELL; i++) { auto* gunshell = &Gunshells[i]; if (gunshell->counter <= 0) - { continue; - } - objectNumber = gunshell->objectNumber; + objectID = gunshell->objectNumber; - Matrix translation = Matrix::CreateTranslation( - gunshell->pos.Position.x, - gunshell->pos.Position.y, - gunshell->pos.Position.z - ); - Matrix rotation = gunshell->pos.Orientation.ToRotationMatrix(); - Matrix world = rotation * translation; + auto translation = Matrix::CreateTranslation(gunshell->pos.Position.ToVector3()); + auto rotMatrix = gunshell->pos.Orientation.ToRotationMatrix(); + auto worldMatrix = rotMatrix * translation; - _stInstancedStaticMeshBuffer.StaticMeshes[gunShellsCount].World = world; - _stInstancedStaticMeshBuffer.StaticMeshes[gunShellsCount].Ambient = room.AmbientLight; - _stInstancedStaticMeshBuffer.StaticMeshes[gunShellsCount].Color = room.AmbientLight; - _stInstancedStaticMeshBuffer.StaticMeshes[gunShellsCount].LightMode = (int)LightMode::Dynamic; - BindInstancedStaticLights(item->LightsToDraw, gunShellsCount); + auto prevTranslation = Matrix::CreateTranslation( + gunshell->oldPos.Position.x, + gunshell->oldPos.Position.y, + gunshell->oldPos.Position.z); + auto prevRotMatrix = gunshell->oldPos.Orientation.ToRotationMatrix(); + auto prevWorldMatrix = prevRotMatrix * prevTranslation; - gunShellsCount++; + worldMatrix = Matrix::Lerp(prevWorldMatrix, worldMatrix, _interpolationFactor); + + _stInstancedStaticMeshBuffer.StaticMeshes[gunShellCount].World = worldMatrix; + _stInstancedStaticMeshBuffer.StaticMeshes[gunShellCount].Ambient = room.AmbientLight; + _stInstancedStaticMeshBuffer.StaticMeshes[gunShellCount].Color = room.AmbientLight; + _stInstancedStaticMeshBuffer.StaticMeshes[gunShellCount].LightMode = (int)LightMode::Dynamic; + BindInstancedStaticLights(item->LightsToDraw, gunShellCount); + + gunShellCount++; } - if (gunShellsCount > 0) + if (gunShellCount > 0) { - auto& moveableObject = *_moveableObjects[objectNumber]; + auto& moveableObject = *_moveableObjects[objectID]; _context->VSSetShader(_vsInstancedStaticMeshes.Get(), nullptr, 0); _context->PSSetShader(_psInstancedStaticMeshes.Get(), nullptr, 0); - UINT stride = sizeof(Vertex); - UINT offset = 0; + unsigned int stride = sizeof(Vertex); + unsigned int offset = 0; _context->IASetVertexBuffers(0, 1, _moveablesVertexBuffer.Buffer.GetAddressOf(), &stride, &offset); _context->IASetIndexBuffer(_moveablesIndexBuffer.Buffer.Get(), DXGI_FORMAT_R32_UINT, 0); @@ -302,19 +308,16 @@ namespace TEN::Renderer _cbInstancedStaticMeshBuffer.UpdateData(_stInstancedStaticMeshBuffer, _context.Get()); - auto* mesh = moveableObject.ObjectMeshes[0]; - - for (auto& bucket : mesh->Buckets) + const auto& mesh = *moveableObject.ObjectMeshes[0]; + for (const auto& bucket : mesh.Buckets) { if (bucket.NumVertices == 0) - { continue; - } BindTexture(TextureRegister::ColorMap, &std::get<0>(_moveablesTextures[bucket.Texture]), SamplerStateRegister::AnisotropicClamp); BindTexture(TextureRegister::NormalMap, &std::get<1>(_moveablesTextures[bucket.Texture]), SamplerStateRegister::AnisotropicClamp); - DrawIndexedInstancedTriangles(bucket.NumIndices, gunShellsCount, bucket.StartIndex, 0); + DrawIndexedInstancedTriangles(bucket.NumIndices, gunShellCount, bucket.StartIndex, 0); _numInstancedStaticsDrawCalls++; } @@ -323,50 +326,43 @@ namespace TEN::Renderer void Renderer::PrepareRopes(RenderView& view) { - for (auto& rope : Ropes) + for (const auto& rope : Ropes) { if (!rope.active) continue; - auto world = Matrix::CreateTranslation(rope.position.x, rope.position.y, rope.position.z); + auto translationMatrix = Matrix::CreateTranslation(rope.position.ToVector3()); + auto absolutePoints = std::array{}; - Vector3 absolute[24]; - - for (int n = 0; n < ROPE_SEGMENTS; n++) + for (int i = 0; i < rope.segment.size(); i++) { - auto* s = &rope.meshSegment[n]; - Vector3 t; - Vector3 output; + const auto* segment = &rope.PrevMeshSegments[i]; - t.x = s->x >> FP_SHIFT; - t.y = s->y >> FP_SHIFT; - t.z = s->z >> FP_SHIFT; + auto relPos = Vector3(segment->x >> FP_SHIFT, segment->y >> FP_SHIFT, segment->z >> FP_SHIFT); + auto prevOutput = Vector3::Transform(relPos, translationMatrix); - output = Vector3::Transform(t, world); + segment = &rope.meshSegment[i]; - auto absolutePosition = output; - absolute[n] = absolutePosition; + relPos = Vector3(segment->x >> FP_SHIFT, segment->y >> FP_SHIFT, segment->z >> FP_SHIFT); + auto currentOutput = Vector3::Transform(relPos, translationMatrix); + + auto absolutePos = Vector3::Lerp(prevOutput, currentOutput, _interpolationFactor); + absolutePoints[i] = absolutePos; } - for (int n = 0; n < ROPE_SEGMENTS - 1; n++) + for (int i = 0; i < (rope.segment.size() - 1); i++) { - auto pos1 = absolute[n]; - auto pos2 = absolute[n + 1]; + const auto& pos0 = absolutePoints[i]; + const auto& pos1 = absolutePoints[i + 1]; + auto pos = (pos0 + pos1) / 2; - auto d = pos2 - pos1; - d.Normalize(); + auto dir = pos1 - pos0; + dir.Normalize(); - auto c = (pos1 + pos2) / 2.0f; - - AddSpriteBillboardConstrained(&_sprites[Objects[ID_DEFAULT_SPRITES].meshIndex + SPR_EMPTY1], - c, - _rooms[rope.room].AmbientLight, - (PI / 2), - 1.0f, - { 32, - Vector3::Distance(pos1, pos2) }, - BlendMode::AlphaBlend, - d, false, view); + AddSpriteBillboardConstrained( + &_sprites[Objects[ID_DEFAULT_SPRITES].meshIndex + SPR_EMPTY1], + pos, _rooms[rope.room].AmbientLight, + PI_DIV_2, 1.0f, Vector2(32.0f, Vector3::Distance(pos0, pos1)), BlendMode::AlphaBlend, dir, false, view); } } } @@ -632,7 +628,7 @@ namespace TEN::Renderer for (auto& poly : bucket.Polygons) { - auto worldMatrix = fish.Orientation.ToRotationMatrix() * Matrix::CreateTranslation(fish.Position); + auto worldMatrix = Matrix::Lerp(fish.PrevTransform, fish.Transform, _interpolationFactor); auto center = Vector3::Transform(poly.Centre, worldMatrix); float dist = Vector3::Distance(center, view.Camera.WorldPosition); @@ -693,7 +689,7 @@ namespace TEN::Renderer const auto& mesh = *GetMesh(Objects[ID_FISH_EMITTER].meshIndex + fish.MeshIndex); - _stStatic.World = fish.Orientation.ToRotationMatrix() * Matrix::CreateTranslation(fish.Position); + _stStatic.World = Matrix::Lerp(fish.PrevTransform, fish.Transform, _interpolationFactor); _stStatic.Color = Vector4::One; _stStatic.AmbientLight = _rooms[fish.RoomNumber].AmbientLight; @@ -731,72 +727,67 @@ namespace TEN::Renderer if (!Objects[ID_BATS_EMITTER].loaded) return; - auto* mesh = GetMesh(Objects[ID_BATS_EMITTER].meshIndex + (GlobalCounter & 3)); + auto& mesh = *GetMesh(Objects[ID_BATS_EMITTER].meshIndex + (GlobalCounter & 3)); if (rendererPass == RendererPass::CollectTransparentFaces) { - for (int i = 0; i < NUM_BATS; i++) + for (const auto& bat : Bats) { - auto* bat = &Bats[i]; + if (!bat.On) + continue; - if (bat->On) + for (auto& bucket : mesh.Buckets) { - for (int j = 0; j < mesh->Buckets.size(); j++) + if (!IsSortedBlendMode(bucket.BlendMode)) + continue; + + for (int p = 0; p < bucket.Polygons.size(); p++) { - auto& bucket = mesh->Buckets[j]; + auto transformMatrix = Matrix::Lerp(bat.PrevTransform, bat.Transform, _interpolationFactor); + auto centre = Vector3::Transform(bucket.Polygons[p].Centre, transformMatrix); + float dist = (centre - view.Camera.WorldPosition).Length(); - if (IsSortedBlendMode(bucket.BlendMode)) - { - for (int p = 0; p < bucket.Polygons.size(); p++) - { - auto centre = Vector3::Transform( - bucket.Polygons[p].Centre, bat->Transform); - int distance = (centre - view.Camera.WorldPosition).Length(); + auto object = RendererSortableObject{}; + object.ObjectType = RendererObjectType::MoveableAsStatic; + object.Centre = centre; + object.Distance = dist; + object.Bucket = &bucket; + object.Mesh = &mesh; + object.Polygon = &bucket.Polygons[p]; + object.World = transformMatrix; + object.Room = &_rooms[bat.RoomNumber]; - RendererSortableObject object; - object.ObjectType = RendererObjectType::MoveableAsStatic; - object.Centre = centre; - object.Distance = distance; - object.Bucket = &bucket; - object.Mesh = mesh; - object.Polygon = &bucket.Polygons[p]; - object.World = bat->Transform; - object.Room = &_rooms[bat->RoomNumber]; - - view.TransparentObjectsToDraw.push_back(object); - } - } + view.TransparentObjectsToDraw.push_back(object); } } } } else { - int batsCount = 0; - + int batCount = 0; for (int i = 0; i < NUM_BATS; i++) { - auto* bat = &Bats[i]; + const auto& bat = Bats[i]; - if (bat->On) + if (bat.On) { - RendererRoom& room = _rooms[bat->RoomNumber]; + auto& room = _rooms[bat.RoomNumber]; - _stInstancedStaticMeshBuffer.StaticMeshes[batsCount].World = bat->Transform; - _stInstancedStaticMeshBuffer.StaticMeshes[batsCount].Ambient = room.AmbientLight; - _stInstancedStaticMeshBuffer.StaticMeshes[batsCount].Color = Vector4::One; - _stInstancedStaticMeshBuffer.StaticMeshes[batsCount].LightMode = (int)mesh->LightMode; + auto transformMatrix = Matrix::Lerp(bat.PrevTransform, bat.Transform, _interpolationFactor); + + _stInstancedStaticMeshBuffer.StaticMeshes[batCount].World = transformMatrix; + _stInstancedStaticMeshBuffer.StaticMeshes[batCount].Ambient = room.AmbientLight; + _stInstancedStaticMeshBuffer.StaticMeshes[batCount].Color = Vector4::One; + _stInstancedStaticMeshBuffer.StaticMeshes[batCount].LightMode = (int)mesh.LightMode; if (rendererPass != RendererPass::GBuffer) - { - BindInstancedStaticLights(room.LightsToDraw, batsCount); - } + BindInstancedStaticLights(room.LightsToDraw, batCount); - batsCount++; + batCount++; } - if (batsCount == INSTANCED_STATIC_MESH_BUCKET_SIZE || - (i == NUM_BATS - 1 && batsCount > 0)) + if (batCount == INSTANCED_STATIC_MESH_BUCKET_SIZE || + (i == (NUM_BATS - 1) && batCount > 0)) { if (rendererPass == RendererPass::GBuffer) { @@ -809,40 +800,35 @@ namespace TEN::Renderer _context->PSSetShader(_psInstancedStaticMeshes.Get(), nullptr, 0); } - UINT stride = sizeof(Vertex); - UINT offset = 0; + unsigned int stride = sizeof(Vertex); + unsigned int offset = 0; _context->IASetVertexBuffers(0, 1, _moveablesVertexBuffer.Buffer.GetAddressOf(), &stride, &offset); _context->IASetIndexBuffer(_moveablesIndexBuffer.Buffer.Get(), DXGI_FORMAT_R32_UINT, 0); _cbInstancedStaticMeshBuffer.UpdateData(_stInstancedStaticMeshBuffer, _context.Get()); - for (auto& bucket : mesh->Buckets) + for (auto& bucket : mesh.Buckets) { if (bucket.NumVertices == 0) - { continue; - } - int passes = rendererPass == RendererPass::Opaque && bucket.BlendMode == BlendMode::AlphaTest ? 2 : 1; - - for (int p = 0; p < passes; p++) + int passCount = (rendererPass == RendererPass::Opaque && bucket.BlendMode == BlendMode::AlphaTest) ? 2 : 1; + for (int p = 0; p < passCount; p++) { if (!SetupBlendModeAndAlphaTest(bucket.BlendMode, rendererPass, p)) - { continue; - } BindTexture(TextureRegister::ColorMap, &std::get<0>(_moveablesTextures[bucket.Texture]), SamplerStateRegister::AnisotropicClamp); BindTexture(TextureRegister::NormalMap, &std::get<1>(_moveablesTextures[bucket.Texture]), SamplerStateRegister::AnisotropicClamp); - DrawIndexedInstancedTriangles(bucket.NumIndices, batsCount, bucket.StartIndex, 0); + DrawIndexedInstancedTriangles(bucket.NumIndices, batCount, bucket.StartIndex, 0); _numMoveablesDrawCalls++; } } - batsCount = 0; + batCount = 0; } } } @@ -851,55 +837,47 @@ namespace TEN::Renderer void Renderer::DrawScarabs(RenderView& view, RendererPass rendererPass) { if (!Objects[ID_LITTLE_BEETLE].loaded) - { return; - } - RendererMesh* mesh = GetMesh(Objects[ID_LITTLE_BEETLE].meshIndex + ((Wibble >> 2) % 2)); + auto& mesh = *GetMesh(Objects[ID_LITTLE_BEETLE].meshIndex + ((Wibble >> 2) % 2)); if (rendererPass == RendererPass::CollectTransparentFaces) { - for (int i = 0; i < TEN::Entities::TR4::NUM_BEETLES; i++) + for (const auto& beetle : TEN::Entities::TR4::BeetleSwarm) { - auto beetle = &TEN::Entities::TR4::BeetleSwarm[i]; + if (!beetle.On) + continue; - if (beetle->On) + auto transformMatrix = Matrix::Lerp(beetle.PrevTransform, beetle.Transform, _interpolationFactor); + + for (auto& bucket : mesh.Buckets) { - for (int j = 0; j < mesh->Buckets.size(); j++) + if (!IsSortedBlendMode(bucket.BlendMode)) + continue; + + for (int p = 0; p < bucket.Polygons.size(); p++) { - auto& bucket = mesh->Buckets[j]; + auto centre = Vector3::Transform(bucket.Polygons[p].Centre, transformMatrix); + float dist = (centre - view.Camera.WorldPosition).Length(); - if (IsSortedBlendMode(bucket.BlendMode)) - { - for (int p = 0; p < bucket.Polygons.size(); p++) - { - auto centre = Vector3::Transform( - bucket.Polygons[p].Centre, beetle->Transform); - int distance = (centre - view.Camera.WorldPosition).Length(); + auto object = RendererSortableObject{}; + object.ObjectType = RendererObjectType::MoveableAsStatic; + object.Centre = centre; + object.Distance = dist; + object.Bucket = &bucket; + object.Mesh = &mesh; + object.Polygon = &bucket.Polygons[p]; + object.World = transformMatrix; + object.Room = &_rooms[beetle.RoomNumber]; - RendererSortableObject object; - object.ObjectType = RendererObjectType::MoveableAsStatic; - object.Centre = centre; - object.Distance = distance; - object.Bucket = &bucket; - object.Mesh = mesh; - object.Polygon = &bucket.Polygons[p]; - object.World = beetle->Transform; - object.Room = &_rooms[beetle->RoomNumber]; - - view.TransparentObjectsToDraw.push_back(object); - } - } + view.TransparentObjectsToDraw.push_back(object); } } } } else { - std::vector> beetlesBuckets; - - unsigned int beetleCount = 0; - + int beetleCount = 0; for (int i = 0; i < TEN::Entities::TR4::NUM_BEETLES; i++) { const auto& beetle = TEN::Entities::TR4::BeetleSwarm[i]; @@ -908,18 +886,19 @@ namespace TEN::Renderer { auto& room = _rooms[beetle.RoomNumber]; - _stInstancedStaticMeshBuffer.StaticMeshes[beetleCount].World = beetle.Transform; + auto transformMatrix = Matrix::Lerp(beetle.PrevTransform, beetle.Transform, _interpolationFactor); + + _stInstancedStaticMeshBuffer.StaticMeshes[beetleCount].World = transformMatrix; _stInstancedStaticMeshBuffer.StaticMeshes[beetleCount].Ambient = room.AmbientLight; _stInstancedStaticMeshBuffer.StaticMeshes[beetleCount].Color = Vector4::One; - _stInstancedStaticMeshBuffer.StaticMeshes[beetleCount].LightMode = (int)mesh->LightMode; + _stInstancedStaticMeshBuffer.StaticMeshes[beetleCount].LightMode = (int)mesh.LightMode; if (rendererPass != RendererPass::GBuffer) { - std::vector lights; + auto lights = std::vector{}; for (int i = 0; i < std::min((int)room.LightsToDraw.size(), MAX_LIGHTS_PER_ITEM); i++) - { lights.push_back(room.LightsToDraw[i]); - } + BindInstancedStaticLights(lights, beetleCount); } @@ -948,21 +927,16 @@ namespace TEN::Renderer _cbInstancedStaticMeshBuffer.UpdateData(_stInstancedStaticMeshBuffer, _context.Get()); - for (const auto& bucket : mesh->Buckets) + for (auto& bucket : mesh.Buckets) { if (bucket.NumVertices == 0) - { continue; - } - int passes = rendererPass == RendererPass::Opaque && bucket.BlendMode == BlendMode::AlphaTest ? 2 : 1; - - for (int p = 0; p < passes; p++) + int passCount = (rendererPass == RendererPass::Opaque && bucket.BlendMode == BlendMode::AlphaTest) ? 2 : 1; + for (int p = 0; p < passCount; p++) { if (!SetupBlendModeAndAlphaTest(bucket.BlendMode, rendererPass, p)) - { continue; - } BindTexture(TextureRegister::ColorMap, &std::get<0>(_moveablesTextures[bucket.Texture]), SamplerStateRegister::AnisotropicClamp); BindTexture(TextureRegister::NormalMap, &std::get<1>(_moveablesTextures[bucket.Texture]), SamplerStateRegister::AnisotropicClamp); @@ -982,48 +956,42 @@ namespace TEN::Renderer void Renderer::DrawLocusts(RenderView& view, RendererPass rendererPass) { if (!Objects[ID_LOCUSTS].loaded) - { return; - } if (rendererPass == RendererPass::CollectTransparentFaces) { - ObjectInfo* obj = &Objects[ID_LOCUSTS]; - RendererObject& moveableObj = *_moveableObjects[ID_LOCUSTS]; + auto* obj = &Objects[ID_LOCUSTS]; + auto& moveableObj = *_moveableObjects[ID_LOCUSTS]; - for (int i = 0; i < TEN::Entities::TR4::MAX_LOCUSTS; i++) + for (const auto& locust : TEN::Entities::TR4::Locusts) { - LOCUST_INFO* locust = &TEN::Entities::TR4::Locusts[i]; + if (!locust.on) + continue; - if (locust->on) + auto& mesh = *GetMesh(Objects[ID_LOCUSTS].meshIndex + (-locust.counter & 3)); + + for (auto& bucket : mesh.Buckets) { - RendererMesh* mesh = GetMesh(Objects[ID_LOCUSTS].meshIndex + (-locust->counter & 3)); + if (!IsSortedBlendMode(bucket.BlendMode)) + continue; - for (int j = 0; j < mesh->Buckets.size(); j++) + for (int p = 0; p < bucket.Polygons.size(); p++) { - auto& bucket = mesh->Buckets[j]; + auto transformMatrix = Matrix::Lerp(locust.PrevTransform, locust.Transform, _interpolationFactor); + auto centre = Vector3::Transform(bucket.Polygons[p].Centre, transformMatrix); + float dist = (centre - view.Camera.WorldPosition).Length(); - if (IsSortedBlendMode(bucket.BlendMode)) - { - for (int p = 0; p < bucket.Polygons.size(); p++) - { - auto centre = Vector3::Transform( - bucket.Polygons[p].Centre, locust->Transform); - int distance = (centre - view.Camera.WorldPosition).Length(); + auto object = RendererSortableObject{}; + object.ObjectType = RendererObjectType::MoveableAsStatic; + object.Centre = centre; + object.Distance = dist; + object.Bucket = &bucket; + object.Mesh = &mesh; + object.Polygon = &bucket.Polygons[p]; + object.World = transformMatrix; + object.Room = &_rooms[locust.roomNumber]; - RendererSortableObject object; - object.ObjectType = RendererObjectType::MoveableAsStatic; - object.Centre = centre; - object.Distance = distance; - object.Bucket = &bucket; - object.Mesh = mesh; - object.Polygon = &bucket.Polygons[p]; - object.World = locust->Transform; - object.Room = &_rooms[locust->roomNumber]; - - view.TransparentObjectsToDraw.push_back(object); - } - } + view.TransparentObjectsToDraw.push_back(object); } } } @@ -1031,15 +999,13 @@ namespace TEN::Renderer else { int activeLocustsExist = false; - for (int i = 0; i < TEN::Entities::TR4::MAX_LOCUSTS; i++) + for (const auto& locust : TEN::Entities::TR4::Locusts) { - LOCUST_INFO* locust = &TEN::Entities::TR4::Locusts[i]; + if (!locust.on) + continue; - if (locust->on) - { - activeLocustsExist = true; - break; - } + activeLocustsExist = true; + break; } if (activeLocustsExist) @@ -1055,53 +1021,46 @@ namespace TEN::Renderer _context->PSSetShader(_psStatics.Get(), nullptr, 0); } - UINT stride = sizeof(Vertex); - UINT offset = 0; + unsigned int stride = sizeof(Vertex); + unsigned int offset = 0; _context->IASetVertexBuffers(0, 1, _moveablesVertexBuffer.Buffer.GetAddressOf(), &stride, &offset); _context->IASetIndexBuffer(_moveablesIndexBuffer.Buffer.Get(), DXGI_FORMAT_R32_UINT, 0); - ObjectInfo* obj = &Objects[ID_LOCUSTS]; - RendererObject& moveableObj = *_moveableObjects[ID_LOCUSTS]; + auto* obj = &Objects[ID_LOCUSTS]; + auto& moveableObj = *_moveableObjects[ID_LOCUSTS]; _stStatic.LightMode = (int)moveableObj.ObjectMeshes[0]->LightMode; - for (int i = 0; i < TEN::Entities::TR4::MAX_LOCUSTS; i++) + for (const auto& locust : TEN::Entities::TR4::Locusts) { - LOCUST_INFO* locust = &TEN::Entities::TR4::Locusts[i]; + if (!locust.on) + continue; - if (locust->on) + auto& mesh = *GetMesh(Objects[ID_LOCUSTS].meshIndex + (-locust.counter & 3)); + + _stStatic.World = Matrix::Lerp(locust.PrevTransform, locust.Transform, _interpolationFactor); + _stStatic.Color = Vector4::One; + _stStatic.AmbientLight = _rooms[locust.roomNumber].AmbientLight; + _cbStatic.UpdateData(_stStatic, _context.Get()); + + for (auto& bucket : mesh.Buckets) { - RendererMesh* mesh = GetMesh(Objects[ID_LOCUSTS].meshIndex + (-locust->counter & 3)); + if (bucket.NumVertices == 0) + continue; - _stStatic.World = locust->Transform; - _stStatic.Color = Vector4::One; - _stStatic.AmbientLight = _rooms[locust->roomNumber].AmbientLight; - _cbStatic.UpdateData(_stStatic, _context.Get()); - - for (auto& bucket : mesh->Buckets) + int passCount = (rendererPass == RendererPass::Opaque && bucket.BlendMode == BlendMode::AlphaTest) ? 2 : 1; + for (int p = 0; p < passCount; p++) { - if (bucket.NumVertices == 0) - { + if (!SetupBlendModeAndAlphaTest(bucket.BlendMode, rendererPass, p)) continue; - } - int passes = rendererPass == RendererPass::Opaque && bucket.BlendMode == BlendMode::AlphaTest ? 2 : 1; + BindTexture(TextureRegister::ColorMap, &std::get<0>(_moveablesTextures[bucket.Texture]), SamplerStateRegister::AnisotropicClamp); + BindTexture(TextureRegister::NormalMap, &std::get<1>(_moveablesTextures[bucket.Texture]), SamplerStateRegister::AnisotropicClamp); - for (int p = 0; p < passes; p++) - { - if (!SetupBlendModeAndAlphaTest(bucket.BlendMode, rendererPass, p)) - { - continue; - } + DrawIndexedTriangles(bucket.NumIndices, bucket.StartIndex, 0); - BindTexture(TextureRegister::ColorMap, &std::get<0>(_moveablesTextures[bucket.Texture]), SamplerStateRegister::AnisotropicClamp); - BindTexture(TextureRegister::NormalMap, &std::get<1>(_moveablesTextures[bucket.Texture]), SamplerStateRegister::AnisotropicClamp); - - DrawIndexedTriangles(bucket.NumIndices, bucket.StartIndex, 0); - - _numMoveablesDrawCalls++; - } + _numMoveablesDrawCalls++; } } } @@ -1236,11 +1195,8 @@ namespace TEN::Renderer AddDebugLine(origin2, target2, color); } - void Renderer::AddDebugBox(const std::array& corners, const Color& color, RendererDebugPage page, bool isWireframe) + void Renderer::AddDebugBox(const std::array& corners, const Color& color, RendererDebugPage page, bool isWireframe) { - constexpr auto LINE_COUNT = 12; - constexpr auto PLANE_COUNT = 6; - if (_isLocked) return; @@ -1250,7 +1206,7 @@ namespace TEN::Renderer // Construct box. if (isWireframe) { - for (int i = 0; i < LINE_COUNT; i++) + for (int i = 0; i < BOX_EDGE_COUNT; i++) { switch (i) { @@ -1297,7 +1253,7 @@ namespace TEN::Renderer } else { - for (int i = 0; i < PLANE_COUNT; i++) + for (int i = 0; i < BOX_FACE_COUNT; i++) { switch (i) { @@ -1349,15 +1305,13 @@ namespace TEN::Renderer void Renderer::AddDebugBox(const BoundingOrientedBox& box, const Color& color, RendererDebugPage page, bool isWireframe) { - constexpr auto CORNER_COUNT = 8; - if (_isLocked) return; if (!DebugMode || (_debugPage != page && page != RendererDebugPage::None)) return; - auto corners = std::array{}; + auto corners = std::array{}; box.GetCorners(corners.data()); AddDebugBox(corners, color, page, isWireframe); @@ -1365,15 +1319,13 @@ namespace TEN::Renderer void Renderer::AddDebugBox(const BoundingBox& box, const Color& color, RendererDebugPage page, bool isWireframe) { - constexpr auto CORNER_COUNT = 8; - if (_isLocked) return; if (!DebugMode || (_debugPage != page && page != RendererDebugPage::None)) return; - auto corners = std::array{}; + auto corners = std::array{}; box.GetCorners(corners.data()); AddDebugBox(corners, color, page, isWireframe); @@ -1615,24 +1567,41 @@ namespace TEN::Renderer _dynamicLights.push_back(dynamicLight); } - void Renderer::ClearDynamicLights() + void Renderer::PrepareScene() { _dynamicLights.clear(); - } - - void Renderer::ClearScene() - { - ResetAnimations(); - - ClearFires(); - ClearDynamicLights(); - ClearSceneItems(); - ClearShadowMap(); + _lines2DToDraw.clear(); + _lines3DToDraw.clear(); + _triangles3DToDraw.clear(); + _stringsToDraw.clear(); _currentCausticsFrame++; _currentCausticsFrame %= 32; - CalculateFrameRate(); + constexpr auto BLINK_VALUE_MAX = 1.0f; + constexpr auto BLINK_VALUE_MIN = 0.1f; + constexpr auto BLINK_TIME_STEP = 0.2f; + + // Calculate blink increment based on sine wave. + _blinkColorValue = ((sin(_blinkTime) + BLINK_VALUE_MAX) * 0.5f) + BLINK_VALUE_MIN; + + // Update blink time. + _blinkTime += BLINK_TIME_STEP; + if (_blinkTime > PI_MUL_2) + _blinkTime -= PI_MUL_2; + + _oldGameCamera = _currentGameCamera; + Camera.DisableInterpolation = false; + + _isLocked = false; + } + + void Renderer::ClearScene() + { + _gameCamera.Clear(); + + ResetItems(); + ClearShadowMap(); } void Renderer::RenderScene(RenderTarget2D* renderTarget, bool doAntialiasing, RenderView& view) @@ -1641,7 +1610,7 @@ namespace TEN::Renderer using get_time = std::chrono::steady_clock; ResetDebugVariables(); - _isLocked = false; + _doingFullscreenPass = false; auto& level = *g_GameFlow->GetLevel(CurrentLevel); @@ -1686,7 +1655,7 @@ namespace TEN::Renderer PrepareLaserBarriers(view); PrepareSingleLaserBeam(view); - // Sprites grouped in buckets for instancing. Non-commutative sprites are collected for a later stage. + // Sprites grouped in buckets for instancing. Non-commutative sprites are collected at a later stage. SortAndPrepareSprites(view); auto time2 = std::chrono::high_resolution_clock::now(); @@ -1698,7 +1667,7 @@ namespace TEN::Renderer SetDepthState(DepthState::Write, true); SetCullMode(CullMode::CounterClockwise, true); - // Bind constant buffers + // Bind constant buffers. BindConstantBufferVS(ConstantBufferRegister::Camera, _cbCameraMatrices.get()); BindConstantBufferVS(ConstantBufferRegister::Item, _cbItem.get()); BindConstantBufferVS(ConstantBufferRegister::InstancedStatics, _cbInstancedStaticMeshBuffer.get()); @@ -1732,10 +1701,10 @@ namespace TEN::Renderer //RenderSimpleSceneToParaboloid(&_roomAmbientMapsCache[ambientMapCacheIndex].Back, LaraItem->Pose.Position.ToVector3(), -1); // Bind and clear render target. - _context->ClearRenderTargetView(_renderTarget.RenderTargetView.Get(), _debugPage == RendererDebugPage::WireframeMode ? Colors::White : Colors::Black); + _context->ClearRenderTargetView(_renderTarget.RenderTargetView.Get(), _debugPage == RendererDebugPage::WireframeMode ? Colors::DimGray : Colors::Black); _context->ClearDepthStencilView(_renderTarget.DepthStencilView.Get(), D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.0f, 0); - // Reset viewport and scissor + // Reset viewport and scissor. _context->RSSetViewports(1, &view.Viewport); ResetScissor(); @@ -1743,6 +1712,7 @@ namespace TEN::Renderer CCameraMatrixBuffer cameraConstantBuffer; view.FillConstantBuffer(cameraConstantBuffer); cameraConstantBuffer.Frame = GlobalCounter; + cameraConstantBuffer.RefreshRate = _refreshRate; cameraConstantBuffer.CameraUnderwater = g_Level.Rooms[cameraConstantBuffer.RoomNumber].flags & ENV_FLAG_WATER; cameraConstantBuffer.DualParaboloidView = Matrix::CreateLookAt(LaraItem->Pose.Position.ToVector3(), LaraItem->Pose.Position.ToVector3() + Vector3(0, 0, 1024), -Vector3::UnitY); @@ -1784,7 +1754,7 @@ namespace TEN::Renderer // Draw horizon and sky. DrawHorizonAndSky(view, _renderTarget.DepthStencilView.Get()); - // Build G-Buffer (Normals + Depth). + // Build G-Buffer (normals + depth). _context->ClearRenderTargetView(_normalsRenderTarget.RenderTargetView.Get(), Colors::Black); _context->ClearRenderTargetView(_depthRenderTarget.RenderTargetView.Get(), Colors::White); @@ -1803,7 +1773,7 @@ namespace TEN::Renderer DrawRats(view, RendererPass::GBuffer); DrawLocusts(view, RendererPass::GBuffer); - // Calculate ambient occlusion + // Calculate ambient occlusion. if (g_Configuration.EnableAmbientOcclusion) { _doingFullscreenPass = true; @@ -1821,8 +1791,7 @@ namespace TEN::Renderer _context->RSSetViewports(1, &view.Viewport); ResetScissor(); - // Bind main render target again. - // At this point, main depth buffer is already filled and avoids overdraw in following steps. + // Bind main render target again. Main depth buffer is already filled and avoids overdraw in following steps. _context->OMSetRenderTargets(1, _renderTarget.RenderTargetView.GetAddressOf(), _renderTarget.DepthStencilView.Get()); // Draw opaque, alpha test, and fast alpha blend faces. @@ -1855,7 +1824,7 @@ namespace TEN::Renderer DrawFishSwarm(view, RendererPass::Additive); // Collect all non-commutative transparent faces. - // NOTE: Sorted sprites are already collected at the beginning of the frame. + // NOTE: Sorted sprites already collected at beginning of frame. DrawRooms(view, RendererPass::CollectTransparentFaces); DrawItems(view, RendererPass::CollectTransparentFaces); DrawStatics(view, RendererPass::CollectTransparentFaces); @@ -1868,15 +1837,17 @@ namespace TEN::Renderer // Draw sorted faces. DrawSortedFaces(view); - // HACK: Draw gunflashes after everything because they are very near the camera. + // HACK: Gunflashes drawn after everything because they are very near the camera. DrawGunFlashes(view); DrawBaddyGunflashes(view); - // Draw 3D debug lines and trianges. + // Draw 3D debug lines and triangles. DrawLines3D(view); DrawTriangles3D(view); // Draw HUD. + ClearDrawPhaseDisplaySprites(); + _context->ClearDepthStencilView(_renderTarget.DepthStencilView.Get(), D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.0f, 0); g_Hud.Draw(*LaraItem); @@ -1924,6 +1895,7 @@ namespace TEN::Renderer DrawAllStrings(); ClearScene(); + CalculateFrameRate(); } void Renderer::RenderSimpleSceneToParaboloid(RenderTarget2D* renderTarget, Vector3 position, int emisphere) @@ -1969,7 +1941,7 @@ namespace TEN::Renderer SetCullMode(CullMode::Clockwise); } - RenderView view = RenderView(&Camera, 0, PI / 2.0f, 32, DEFAULT_FAR_VIEW, ROOM_AMBIENT_MAP_SIZE, ROOM_AMBIENT_MAP_SIZE); + auto view = RenderView(&Camera, 0, PI / 2.0f, 32, DEFAULT_FAR_VIEW, ROOM_AMBIENT_MAP_SIZE, ROOM_AMBIENT_MAP_SIZE); CCameraMatrixBuffer cameraConstantBuffer; cameraConstantBuffer.DualParaboloidView = Matrix::CreateLookAt(position, position + Vector3(0, 0, 1024), -Vector3::UnitY); @@ -1985,12 +1957,10 @@ namespace TEN::Renderer _context->VSSetShader(_vsRoomAmbientSky.Get(), nullptr, 0); if (Lara.Control.Look.OpticRange != 0) - { AlterFOV(ANGLE(DEFAULT_FOV) - Lara.Control.Look.OpticRange, false); - } - UINT stride = sizeof(Vertex); - UINT offset = 0; + unsigned int stride = sizeof(Vertex); + unsigned int offset = 0; // Draw sky. auto rotation = Matrix::CreateRotationX(PI); @@ -2185,17 +2155,19 @@ namespace TEN::Renderer void Renderer::DumpGameScene() { RenderScene(&_dumpScreenRenderTarget, false, _gameCamera); + + _graphicsSettingsChanged = false; } void Renderer::DrawItems(RenderView& view, RendererPass rendererPass) { - UINT stride = sizeof(Vertex); - UINT offset = 0; + unsigned int stride = sizeof(Vertex); + unsigned int offset = 0; _context->IASetVertexBuffers(0, 1, _moveablesVertexBuffer.Buffer.GetAddressOf(), &stride, &offset); _context->IASetIndexBuffer(_moveablesIndexBuffer.Buffer.Get(), DXGI_FORMAT_R32_UINT, 0); - // Set shaders + // Set shaders. if (rendererPass == RendererPass::GBuffer) { _context->VSSetShader(_vsGBufferItems.Get(), nullptr, 0); @@ -2213,7 +2185,7 @@ namespace TEN::Renderer { for (auto itemToDraw : room->ItemsToDraw) { - switch (itemToDraw->ObjectNumber) + switch (itemToDraw->ObjectID) { case ID_LARA: DrawLara(view, rendererPass); @@ -2255,7 +2227,7 @@ namespace TEN::Renderer // Extremely hacky function to get first rendered face of a waterfall object mesh, calculate // its texture height and scroll all the textures according to that height. - RendererObject& moveableObj = *_moveableObjects[item->ObjectNumber]; + RendererObject& moveableObj = *_moveableObjects[item->ObjectID]; // No mesh or bucket, abort if (!moveableObj.ObjectMeshes.size() || !moveableObj.ObjectMeshes[0]->Buckets.size()) @@ -2297,13 +2269,13 @@ namespace TEN::Renderer { ItemInfo* nativeItem = &g_Level.Items[item->ItemNumber]; RendererRoom* room = &_rooms[item->RoomNumber]; - RendererObject& moveableObj = *_moveableObjects[item->ObjectNumber]; + RendererObject& moveableObj = *_moveableObjects[item->ObjectID]; // Bind item main properties - _stItem.World = item->World; + _stItem.World = item->InterpolatedWorld; _stItem.Color = item->Color; _stItem.AmbientLight = item->AmbientLight; - memcpy(_stItem.BonesMatrices, item->AnimationTransforms, sizeof(Matrix) * MAX_BONES); + memcpy(_stItem.BonesMatrices, item->InterpolatedAnimTransforms, sizeof(Matrix) * MAX_BONES); for (int k = 0; k < moveableObj.ObjectMeshes.size(); k++) _stItem.BoneLightModes[k] = (int)moveableObj.ObjectMeshes[k]->LightMode; @@ -2316,7 +2288,7 @@ namespace TEN::Renderer if (!(nativeItem->MeshBits & (1 << k))) continue; - DrawMoveableMesh(item, GetMesh(item->MeshIndex[k]), room, k, view, rendererPass); + DrawMoveableMesh(item, GetMesh(item->MeshIds[k]), room, k, view, rendererPass); } } @@ -2599,7 +2571,6 @@ namespace TEN::Renderer { if (rendererPass == RendererPass::GBuffer) { - _context->VSSetShader(_vsGBufferRooms.Get(), nullptr, 0); _context->PSSetShader(_psGBuffer.Get(), nullptr, 0); } else @@ -2649,58 +2620,48 @@ namespace TEN::Renderer for (int i = (int)view.RoomsToDraw.size() - 1; i >= 0; i--) { - int index = i; - RendererRoom* room = view.RoomsToDraw[index]; - ROOM_INFO* nativeRoom = &g_Level.Rooms[room->RoomNumber]; + const auto& room = *view.RoomsToDraw[i]; + const auto& nativeRoom = g_Level.Rooms[room.RoomNumber]; if (rendererPass != RendererPass::GBuffer) { - _stRoom.Caustics = (int)(g_Configuration.EnableCaustics && (nativeRoom->flags & ENV_FLAG_WATER)); - _stRoom.AmbientColor = room->AmbientLight; + _stRoom.Caustics = int(g_Configuration.EnableCaustics && (nativeRoom.flags & ENV_FLAG_WATER)); + _stRoom.AmbientColor = room.AmbientLight; BindRoomLights(view.LightsToDraw); } - _stRoom.Water = (nativeRoom->flags & ENV_FLAG_WATER) != 0 ? 1 : 0; + _stRoom.Water = (nativeRoom.flags & ENV_FLAG_WATER) != 0 ? 1 : 0; _cbRoom.UpdateData(_stRoom, _context.Get()); - SetScissor(room->ClipBounds); + SetScissor(room.ClipBounds); for (int animated = 0; animated < 2; animated++) { if (rendererPass != RendererPass::GBuffer) { - if (animated == 0) - { - _context->VSSetShader(_vsRooms.Get(), nullptr, 0); - } - else - { - _context->VSSetShader(_vsRoomsAnimatedTextures.Get(), nullptr, 0); - } + _context->VSSetShader((animated == 0) ? _vsRooms.Get() : _vsRoomsAnimatedTextures.Get(), nullptr, 0); + } + else + { + _context->VSSetShader((animated == 0) ? _vsGBufferRooms.Get() : _vsGBufferRoomsAnimated.Get(), nullptr, 0); } - for (auto& bucket : room->Buckets) + for (const auto& bucket : room.Buckets) { if ((animated == 1) ^ bucket.Animated) - { continue; - } if (bucket.NumVertices == 0) - { continue; - } int passes = rendererPass == RendererPass::Opaque && bucket.BlendMode == BlendMode::AlphaTest ? 2 : 1; for (int p = 0; p < passes; p++) { if (!SetupBlendModeAndAlphaTest(bucket.BlendMode, rendererPass, p)) - { continue; - } - // Draw geometry + // Draw geometry. if (animated) { BindTexture(TextureRegister::ColorMap, @@ -2709,7 +2670,7 @@ namespace TEN::Renderer BindTexture(TextureRegister::NormalMap, &std::get<1>(_animatedTextures[bucket.Texture]), SamplerStateRegister::AnisotropicClamp); - RendererAnimatedTextureSet& set = _animatedTextureSets[bucket.Texture]; + const auto& set = _animatedTextureSets[bucket.Texture]; _stAnimated.NumFrames = set.NumTextures; _stAnimated.Type = 0; _stAnimated.Fps = set.Fps; @@ -2718,7 +2679,7 @@ namespace TEN::Renderer { if (j >= _stAnimated.Textures.size()) { - TENLog("Animated frame " + std::to_string(j) + " is out of bounds, too many frames in sequence."); + TENLog("Animated frame " + std::to_string(j) + " out of bounds. Too many frames in sequence."); break; } @@ -2731,10 +2692,12 @@ namespace TEN::Renderer } else { - BindTexture(TextureRegister::ColorMap, &std::get<0>(_roomTextures[bucket.Texture]), + BindTexture( + TextureRegister::ColorMap, &std::get<0>(_roomTextures[bucket.Texture]), + SamplerStateRegister::AnisotropicClamp); + BindTexture( + TextureRegister::NormalMap, &std::get<1>(_roomTextures[bucket.Texture]), SamplerStateRegister::AnisotropicClamp); - BindTexture(TextureRegister::NormalMap, - &std::get<1>(_roomTextures[bucket.Texture]), SamplerStateRegister::AnisotropicClamp); } DrawIndexedTriangles(bucket.NumIndices, bucket.StartIndex, 0); @@ -2751,6 +2714,9 @@ namespace TEN::Renderer void Renderer::DrawHorizonAndSky(RenderView& renderView, ID3D11DepthStencilView* depthTarget) { + constexpr auto STAR_SIZE = 2; + constexpr auto SUN_SIZE = 64; + auto* levelPtr = g_GameFlow->GetLevel(CurrentLevel); bool anyOutsideRooms = false; @@ -2792,8 +2758,10 @@ namespace TEN::Renderer { auto weather = TEN::Effects::Environment::Weather; - auto translation = Matrix::CreateTranslation(Camera.pos.x + weather.SkyPosition(s) - i * SKY_SIZE, - Camera.pos.y - 1536.0f, Camera.pos.z); + auto translation = Matrix::CreateTranslation( + renderView.Camera.WorldPosition.x + weather.SkyPosition(s) - i * SKY_SIZE, + renderView.Camera.WorldPosition.y - 1536.0f, + renderView.Camera.WorldPosition.z); auto world = rotation * translation; _stStatic.World = (rotation * translation); @@ -2809,19 +2777,164 @@ namespace TEN::Renderer _context->ClearDepthStencilView(depthTarget, D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1, 0); + if (Weather.GetStars().size() > 0) + { + SetDepthState(DepthState::Read); + SetBlendMode(BlendMode::Additive); + SetCullMode(CullMode::None); + + _context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP); + + _context->VSSetShader(_vsInstancedSprites.Get(), nullptr, 0); + _context->PSSetShader(_psInstancedSprites.Get(), nullptr, 0); + + // Set up vertex buffer and parameters. + UINT stride = sizeof(Vertex); + UINT offset = 0; + _context->IASetVertexBuffers(0, 1, _quadVertexBuffer.Buffer.GetAddressOf(), &stride, &offset); + + BindTexture(TextureRegister::ColorMap, _sprites[Objects[ID_DEFAULT_SPRITES].meshIndex + SPR_LENS_FLARE_3].Texture, SamplerStateRegister::LinearClamp); + + int drawnStars = 0; + int starCount = (int)Weather.GetStars().size(); + + while (drawnStars < starCount) + { + int starsToDraw = + (starCount - drawnStars) > INSTANCED_SPRITES_BUCKET_SIZE ? + INSTANCED_SPRITES_BUCKET_SIZE : + (starCount - drawnStars); + int i = 0; + + for (int i = 0; i < starsToDraw; i++) + { + auto& star = Weather.GetStars()[drawnStars + i]; + + RendererSpriteToDraw rDrawSprite; + rDrawSprite.Sprite = &_sprites[Objects[ID_DEFAULT_SPRITES].meshIndex + SPR_LENS_FLARE_3]; + + rDrawSprite.Type = SpriteType::Billboard; + rDrawSprite.pos = renderView.Camera.WorldPosition + star.Direction * BLOCK(1); + rDrawSprite.Rotation = 0; + rDrawSprite.Scale = 1; + rDrawSprite.Width = STAR_SIZE * star.Scale; + rDrawSprite.Height = STAR_SIZE * star.Scale; + + _stInstancedSpriteBuffer.Sprites[i].World = GetWorldMatrixForSprite(&rDrawSprite, renderView); + _stInstancedSpriteBuffer.Sprites[i].Color = Vector4( + star.Color.x, + star.Color.y, + star.Color.z, + star.Blinking * star.Extinction); + _stInstancedSpriteBuffer.Sprites[i].IsBillboard = 1; + _stInstancedSpriteBuffer.Sprites[i].IsSoftParticle = 0; + + // NOTE: Strange packing due to particular HLSL 16 byte alignment requirements. + _stInstancedSpriteBuffer.Sprites[i].UV[0].x = rDrawSprite.Sprite->UV[0].x; + _stInstancedSpriteBuffer.Sprites[i].UV[0].y = rDrawSprite.Sprite->UV[1].x; + _stInstancedSpriteBuffer.Sprites[i].UV[0].z = rDrawSprite.Sprite->UV[2].x; + _stInstancedSpriteBuffer.Sprites[i].UV[0].w = rDrawSprite.Sprite->UV[3].x; + _stInstancedSpriteBuffer.Sprites[i].UV[1].x = rDrawSprite.Sprite->UV[0].y; + _stInstancedSpriteBuffer.Sprites[i].UV[1].y = rDrawSprite.Sprite->UV[1].y; + _stInstancedSpriteBuffer.Sprites[i].UV[1].z = rDrawSprite.Sprite->UV[2].y; + _stInstancedSpriteBuffer.Sprites[i].UV[1].w = rDrawSprite.Sprite->UV[3].y; + } + + _cbInstancedSpriteBuffer.UpdateData(_stInstancedSpriteBuffer, _context.Get()); + + // Draw sprites with instancing. + DrawInstancedTriangles(4, starsToDraw, 0); + + drawnStars += starsToDraw; + } + + // Draw meteors + if (Weather.GetMeteors().size() > 0) + { + RendererSpriteToDraw rDrawSprite; + rDrawSprite.Sprite = &_sprites[Objects[ID_DEFAULT_SPRITES].meshIndex + SPR_LENS_FLARE_3]; + BindTexture(TextureRegister::ColorMap, rDrawSprite.Sprite->Texture, SamplerStateRegister::LinearClamp); + + int drawnMeteors = 0; + int meteorsCount = (int)Weather.GetMeteors().size(); + + while (drawnMeteors < meteorsCount) + { + int meteorsToDraw = + (meteorsCount - drawnMeteors) > INSTANCED_SPRITES_BUCKET_SIZE ? + INSTANCED_SPRITES_BUCKET_SIZE : + (meteorsCount - drawnMeteors); + int i = 0; + + for (int i = 0; i < meteorsToDraw; i++) + { + auto meteor = Weather.GetMeteors()[drawnMeteors + i]; + + if (meteor.Active == false) + continue; + + rDrawSprite.Type = SpriteType::CustomBillboard; + rDrawSprite.pos = + renderView.Camera.WorldPosition + + Vector3::Lerp(meteor.PrevPosition, meteor.Position, _interpolationFactor); + rDrawSprite.Rotation = 0; + rDrawSprite.Scale = 1; + rDrawSprite.Width = 2; + rDrawSprite.Height = 192; + rDrawSprite.ConstrainAxis = meteor.Direction; + + _stInstancedSpriteBuffer.Sprites[i].World = GetWorldMatrixForSprite(&rDrawSprite, renderView); + _stInstancedSpriteBuffer.Sprites[i].Color = Vector4( + meteor.Color.x, + meteor.Color.y, + meteor.Color.z, + Lerp(meteor.PrevFade, meteor.Fade, _interpolationFactor)); + _stInstancedSpriteBuffer.Sprites[i].IsBillboard = 1; + _stInstancedSpriteBuffer.Sprites[i].IsSoftParticle = 0; + + // NOTE: Strange packing due to particular HLSL 16 byte alignment requirements. + _stInstancedSpriteBuffer.Sprites[i].UV[0].x = rDrawSprite.Sprite->UV[0].x; + _stInstancedSpriteBuffer.Sprites[i].UV[0].y = rDrawSprite.Sprite->UV[1].x; + _stInstancedSpriteBuffer.Sprites[i].UV[0].z = rDrawSprite.Sprite->UV[2].x; + _stInstancedSpriteBuffer.Sprites[i].UV[0].w = rDrawSprite.Sprite->UV[3].x; + _stInstancedSpriteBuffer.Sprites[i].UV[1].x = rDrawSprite.Sprite->UV[0].y; + _stInstancedSpriteBuffer.Sprites[i].UV[1].y = rDrawSprite.Sprite->UV[1].y; + _stInstancedSpriteBuffer.Sprites[i].UV[1].z = rDrawSprite.Sprite->UV[2].y; + _stInstancedSpriteBuffer.Sprites[i].UV[1].w = rDrawSprite.Sprite->UV[3].y; + } + + _cbInstancedSpriteBuffer.UpdateData(_stInstancedSpriteBuffer, _context.Get()); + + // Draw sprites with instancing. + DrawInstancedTriangles(4, meteorsToDraw, 0); + + drawnMeteors += meteorsToDraw; + } + } + + _context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST); + } + // Draw horizon. if (_moveableObjects[ID_HORIZON].has_value()) { + SetDepthState(DepthState::None); + SetBlendMode(BlendMode::Opaque); + SetCullMode(CullMode::CounterClockwise); + _context->IASetVertexBuffers(0, 1, _moveablesVertexBuffer.Buffer.GetAddressOf(), &stride, &offset); _context->IASetIndexBuffer(_moveablesIndexBuffer.Buffer.Get(), DXGI_FORMAT_R32_UINT, 0); + _context->VSSetShader(_vsSky.Get(), nullptr, 0); + _context->PSSetShader(_psSky.Get(), nullptr, 0); + auto& moveableObj = *_moveableObjects[ID_HORIZON]; - _stStatic.World = Matrix::CreateTranslation(Camera.pos.x, Camera.pos.y, Camera.pos.z); + _stStatic.World = Matrix::CreateTranslation(renderView.Camera.WorldPosition); _stStatic.Color = Vector4::One; _stStatic.ApplyFogBulbs = 1; _cbStatic.UpdateData(_stStatic, _context.Get()); - + for (int k = 0; k < moveableObj.ObjectMeshes.size(); k++) { auto* meshPtr = moveableObj.ObjectMeshes[k]; @@ -2848,14 +2961,72 @@ namespace TEN::Renderer } } + // Eventually draw the sun sprite. + if (!renderView.LensFlaresToDraw.empty() && renderView.LensFlaresToDraw[0].IsGlobal) + { + SetDepthState(DepthState::Read); + SetBlendMode(BlendMode::Additive); + SetCullMode(CullMode::None); + + _context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP); + + _context->VSSetShader(_vsInstancedSprites.Get(), nullptr, 0); + _context->PSSetShader(_psInstancedSprites.Get(), nullptr, 0); + + // Set up vertex buffer and parameters. + unsigned int stride = sizeof(Vertex); + unsigned int offset = 0; + _context->IASetVertexBuffers(0, 1, _quadVertexBuffer.Buffer.GetAddressOf(), &stride, &offset); + + auto rDrawSprite = RendererSpriteToDraw{}; + rDrawSprite.Sprite = &_sprites[Objects[ID_DEFAULT_SPRITES].meshIndex + renderView.LensFlaresToDraw[0].SpriteID]; + + rDrawSprite.Type = SpriteType::Billboard; + rDrawSprite.pos = renderView.Camera.WorldPosition + renderView.LensFlaresToDraw[0].Direction * BLOCK(1); + rDrawSprite.Rotation = 0.0f; + rDrawSprite.Scale = 1.0f; + rDrawSprite.Width = SUN_SIZE; + rDrawSprite.Height = SUN_SIZE; + + _stInstancedSpriteBuffer.Sprites[0].World = GetWorldMatrixForSprite(&rDrawSprite, renderView); + _stInstancedSpriteBuffer.Sprites[0].Color = Vector4::One; + _stInstancedSpriteBuffer.Sprites[0].IsBillboard = 1; + _stInstancedSpriteBuffer.Sprites[0].IsSoftParticle = 0; + + // NOTE: Strange packing due to particular HLSL 16 byte alignment requirements. + _stInstancedSpriteBuffer.Sprites[0].UV[0].x = rDrawSprite.Sprite->UV[0].x; + _stInstancedSpriteBuffer.Sprites[0].UV[0].y = rDrawSprite.Sprite->UV[1].x; + _stInstancedSpriteBuffer.Sprites[0].UV[0].z = rDrawSprite.Sprite->UV[2].x; + _stInstancedSpriteBuffer.Sprites[0].UV[0].w = rDrawSprite.Sprite->UV[3].x; + _stInstancedSpriteBuffer.Sprites[0].UV[1].x = rDrawSprite.Sprite->UV[0].y; + _stInstancedSpriteBuffer.Sprites[0].UV[1].y = rDrawSprite.Sprite->UV[1].y; + _stInstancedSpriteBuffer.Sprites[0].UV[1].z = rDrawSprite.Sprite->UV[2].y; + _stInstancedSpriteBuffer.Sprites[0].UV[1].w = rDrawSprite.Sprite->UV[3].y; + + BindTexture(TextureRegister::ColorMap, rDrawSprite.Sprite->Texture, SamplerStateRegister::LinearClamp); + + _cbInstancedSpriteBuffer.UpdateData(_stInstancedSpriteBuffer, _context.Get()); + + // Draw sprites with instancing. + DrawInstancedTriangles(4, 1, 0); + + _context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST); + } + // Clear just the Z-buffer to start drawing on top of horizon. _context->ClearDepthStencilView(depthTarget, D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.0f, 0); + + // Reset the GPU state + SetDepthState(DepthState::Write); + SetBlendMode(BlendMode::Opaque); + SetCullMode(CullMode::CounterClockwise); } - void Renderer::Render() + void Renderer::Render(float interpFactor) { - //RenderToCubemap(reflectionCubemap, Vector3(LaraItem->pos.xPos, LaraItem->pos.yPos - 1024, LaraItem->pos.zPos), LaraItem->roomNumber); + InterpolateCamera(interpFactor); RenderScene(&_backBuffer, true, _gameCamera); + _context->ClearState(); _swapChain->Present(1, 0); } @@ -2877,7 +3048,7 @@ namespace TEN::Renderer for (int p = 0; p < bucket.Polygons.size(); p++) { auto centre = Vector3::Transform( - bucket.Polygons[p].Centre, itemToDraw->AnimationTransforms[boneIndex] * itemToDraw->World); + bucket.Polygons[p].Centre, itemToDraw->InterpolatedAnimTransforms[boneIndex] * itemToDraw->InterpolatedWorld); int distance = (centre - cameraPos).Length(); RendererSortableObject object; @@ -2900,9 +3071,7 @@ namespace TEN::Renderer for (auto& bucket : mesh->Buckets) { if (bucket.NumVertices == 0) - { continue; - } if (rendererPass == RendererPass::ShadowMap) { @@ -3303,8 +3472,8 @@ namespace TEN::Renderer void Renderer::DrawItemSorted(RendererSortableObject* objectInfo, RendererObjectType lastObjectType, RenderView& view) { - UINT stride = sizeof(Vertex); - UINT offset = 0; + unsigned int stride = sizeof(Vertex); + unsigned int offset = 0; _context->IASetVertexBuffers(0, 1, _moveablesVertexBuffer.Buffer.GetAddressOf(), &stride, &offset); _context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST); _context->IASetInputLayout(_inputLayout.Get()); @@ -3317,25 +3486,24 @@ namespace TEN::Renderer _context->VSSetShader(_vsItems.Get(), nullptr, 0); _context->PSSetShader(_psItems.Get(), nullptr, 0); - ItemInfo* nativeItem = &g_Level.Items[objectInfo->Item->ItemNumber]; - RendererRoom* room = &_rooms[objectInfo->Item->RoomNumber]; - RendererObject& moveableObj = *_moveableObjects[objectInfo->Item->ObjectNumber]; - - // Bind item main properties - _stItem.World = objectInfo->Item->World; + // Bind main item properties. + _stItem.World = objectInfo->Item->InterpolatedWorld; _stItem.Color = objectInfo->Item->Color; _stItem.AmbientLight = objectInfo->Item->AmbientLight; - memcpy(_stItem.BonesMatrices, objectInfo->Item->AnimationTransforms, sizeof(Matrix) * MAX_BONES); + memcpy(_stItem.BonesMatrices, objectInfo->Item->InterpolatedAnimTransforms, sizeof(Matrix) * MAX_BONES); + const auto& moveableObj = *_moveableObjects[objectInfo->Item->ObjectID]; for (int k = 0; k < moveableObj.ObjectMeshes.size(); k++) _stItem.BoneLightModes[k] = (int)moveableObj.ObjectMeshes[k]->LightMode; BindMoveableLights(objectInfo->Item->LightsToDraw, objectInfo->Item->RoomNumber, objectInfo->Item->PrevRoomNumber, objectInfo->Item->LightFade); _cbItem.UpdateData(_stItem, _context.Get()); - BindTexture(TextureRegister::ColorMap, &std::get<0>(_moveablesTextures[objectInfo->Bucket->Texture]), + BindTexture( + TextureRegister::ColorMap, &std::get<0>(_moveablesTextures[objectInfo->Bucket->Texture]), SamplerStateRegister::AnisotropicClamp); - BindTexture(TextureRegister::NormalMap, &std::get<1>(_moveablesTextures[objectInfo->Bucket->Texture]), + BindTexture( + TextureRegister::NormalMap, &std::get<1>(_moveablesTextures[objectInfo->Bucket->Texture]), SamplerStateRegister::AnisotropicClamp); _sortedPolygonsIndexBuffer.Update(_context.Get(), _sortedPolygonsIndices, 0, (int)_sortedPolygonsIndices.size()); @@ -3484,4 +3652,36 @@ namespace TEN::Renderer DrawTriangles(3, 0); } + + void Renderer::InterpolateCamera(float interpFactor) + { + _interpolationFactor = interpFactor; + + // Interpolate camera. + if (!Camera.DisableInterpolation) + { + _gameCamera.Camera.WorldPosition = Vector3::Lerp(_oldGameCamera.Camera.WorldPosition, _currentGameCamera.Camera.WorldPosition, interpFactor); + _gameCamera.Camera.WorldDirection = Vector3::Lerp(_oldGameCamera.Camera.WorldDirection, _currentGameCamera.Camera.WorldDirection, interpFactor); + _gameCamera.Camera.View = Matrix::Lerp(_oldGameCamera.Camera.View, _currentGameCamera.Camera.View, interpFactor); + _gameCamera.Camera.Projection = Matrix::Lerp(_oldGameCamera.Camera.Projection, _currentGameCamera.Camera.Projection, interpFactor); + _gameCamera.Camera.ViewProjection = _gameCamera.Camera.View * _gameCamera.Camera.Projection; + _gameCamera.Camera.FOV = Lerp(_oldGameCamera.Camera.FOV, _currentGameCamera.Camera.FOV, interpFactor); + _gameCamera.Camera.Frustum.Update(_gameCamera.Camera.View, _gameCamera.Camera.Projection); + } + else + { + _gameCamera.Camera.WorldPosition = _currentGameCamera.Camera.WorldPosition; + _gameCamera.Camera.WorldDirection = _currentGameCamera.Camera.WorldDirection; + _gameCamera.Camera.View = _currentGameCamera.Camera.View; + _gameCamera.Camera.Projection = _currentGameCamera.Camera.Projection; + _gameCamera.Camera.ViewProjection = _gameCamera.Camera.View * _gameCamera.Camera.Projection; + _gameCamera.Camera.FOV = _currentGameCamera.Camera.FOV; + _gameCamera.Camera.Frustum = _currentGameCamera.Camera.Frustum; + } + + _gameCamera.Camera.ViewSize = _currentGameCamera.Camera.ViewSize; + _gameCamera.Camera.InvViewSize = _currentGameCamera.Camera.InvViewSize; + _gameCamera.Camera.NearPlane = _currentGameCamera.Camera.NearPlane; + _gameCamera.Camera.FarPlane = _currentGameCamera.Camera.FarPlane; + } } diff --git a/TombEngine/Renderer/RendererDrawEffect.cpp b/TombEngine/Renderer/RendererDrawEffect.cpp index 8055fe78d..bd031cee0 100644 --- a/TombEngine/Renderer/RendererDrawEffect.cpp +++ b/TombEngine/Renderer/RendererDrawEffect.cpp @@ -43,15 +43,13 @@ using namespace TEN::Effects::Footprint; using namespace TEN::Effects::Ripple; using namespace TEN::Effects::Streamer; using namespace TEN::Entities::Creatures::TR5; +using namespace TEN::Entities::Traps; using namespace TEN::Math; -using namespace TEN::Traps::TR5; extern BLOOD_STRUCT Blood[MAX_SPARKS_BLOOD]; extern FIRE_SPARKS FireSparks[MAX_SPARKS_FIRE]; extern SMOKE_SPARKS SmokeSparks[MAX_SPARKS_SMOKE]; extern SHOCKWAVE_STRUCT ShockWaves[MAX_SHOCKWAVE]; -extern FIRE_LIST Fires[MAX_FIRE_LIST]; -extern Particle Particles[MAX_PARTICLES]; extern SPLASH_STRUCT Splashes[MAX_SPLASHES]; extern std::array DebrisFragments; @@ -88,13 +86,23 @@ namespace TEN::Renderer { bool isLastSubdivision = (i == (LaserBeamEffect::SUBDIVISION_COUNT - 1)); + auto color = Color::Lerp(beam.OldColor, beam.Color, _interpolationFactor); + AddColoredQuad( - beam.Vertices[i], - beam.Vertices[isLastSubdivision ? 0 : (i + 1)], - beam.Vertices[LaserBeamEffect::SUBDIVISION_COUNT + (isLastSubdivision ? 0 : (i + 1))], - beam.Vertices[LaserBeamEffect::SUBDIVISION_COUNT + i], - beam.Color, beam.Color, - beam.Color, beam.Color, + Vector3::Lerp(beam.OldVertices[i], beam.Vertices[i], _interpolationFactor), + Vector3::Lerp( + beam.OldVertices[isLastSubdivision ? 0 : (i + 1)], + beam.Vertices[isLastSubdivision ? 0 : (i + 1)], + _interpolationFactor), + Vector3::Lerp( + beam.OldVertices[LaserBeamEffect::SUBDIVISION_COUNT + (isLastSubdivision ? 0 : (i + 1))], + beam.Vertices[LaserBeamEffect::SUBDIVISION_COUNT + (isLastSubdivision ? 0 : (i + 1))], + _interpolationFactor), + Vector3::Lerp( + beam.OldVertices[LaserBeamEffect::SUBDIVISION_COUNT + i], + beam.Vertices[LaserBeamEffect::SUBDIVISION_COUNT + i], + _interpolationFactor), + color, color, color, color, BlendMode::Additive, view, SpriteRenderType::LaserBeam); } } @@ -126,28 +134,40 @@ namespace TEN::Renderer if (segment.Flags & (int)StreamerFlags::FadeLeft) { AddColoredQuad( - segment.Vertices[0], segment.Vertices[1], - prevSegment.Vertices[1], prevSegment.Vertices[0], - Vector4::Zero, segment.Color, - prevSegment.Color, Vector4::Zero, + Vector3::Lerp(segment.PrevVertices[0], segment.Vertices[0], _interpolationFactor), + Vector3::Lerp(segment.PrevVertices[1], segment.Vertices[1], _interpolationFactor), + Vector3::Lerp(prevSegment.PrevVertices[1], prevSegment.Vertices[1], _interpolationFactor), + Vector3::Lerp(prevSegment.PrevVertices[0], prevSegment.Vertices[0], _interpolationFactor), + Vector4::Zero, + Vector4::Lerp(segment.PrevColor, segment.Color, _interpolationFactor), + Vector4::Lerp(prevSegment.PrevColor, prevSegment.Color, _interpolationFactor), + Vector4::Zero, blendMode, view); } else if (segment.Flags & (int)StreamerFlags::FadeRight) { AddColoredQuad( - segment.Vertices[0], segment.Vertices[1], - prevSegment.Vertices[1], prevSegment.Vertices[0], - segment.Color, Vector4::Zero, - Vector4::Zero, prevSegment.Color, + Vector3::Lerp(segment.PrevVertices[0], segment.Vertices[0], _interpolationFactor), + Vector3::Lerp(segment.PrevVertices[1], segment.Vertices[1], _interpolationFactor), + Vector3::Lerp(prevSegment.PrevVertices[1], prevSegment.Vertices[1], _interpolationFactor), + Vector3::Lerp(prevSegment.PrevVertices[0], prevSegment.Vertices[0], _interpolationFactor), + Vector4::Lerp(segment.PrevColor, segment.Color, _interpolationFactor), + Vector4::Zero, + Vector4::Zero, + Vector4::Lerp(prevSegment.PrevColor, prevSegment.Color, _interpolationFactor), blendMode, view); } else { AddColoredQuad( - segment.Vertices[0], segment.Vertices[1], - prevSegment.Vertices[1], prevSegment.Vertices[0], - segment.Color, segment.Color, - prevSegment.Color, prevSegment.Color, + Vector3::Lerp(segment.PrevVertices[0], segment.Vertices[0], _interpolationFactor), + Vector3::Lerp(segment.PrevVertices[1], segment.Vertices[1], _interpolationFactor), + Vector3::Lerp(prevSegment.PrevVertices[1], prevSegment.Vertices[1], _interpolationFactor), + Vector3::Lerp(prevSegment.PrevVertices[0], prevSegment.Vertices[0], _interpolationFactor), + Vector4::Lerp(segment.PrevColor, segment.Color, _interpolationFactor), + Vector4::Lerp(segment.PrevColor, segment.Color, _interpolationFactor), + Vector4::Lerp(prevSegment.PrevColor, prevSegment.Color, _interpolationFactor), + Vector4::Lerp(prevSegment.PrevColor, prevSegment.Color, _interpolationFactor), blendMode, view); } } @@ -169,14 +189,16 @@ namespace TEN::Renderer if (laser.Life <= 0.0f) continue; - auto color = laser.Color; - color.w = laser.Opacity; + auto color = Vector4::Lerp(laser.PrevColor, laser.Color, _interpolationFactor); + color.w = Lerp(laser.PrevOpacity, laser.Opacity, _interpolationFactor); - ElectricityKnots[0] = laser.Target; - ElectricityKnots[1] = laser.Origin; + auto laserTarget = Vector3::Lerp(laser.PrevTarget, laser.Target, _interpolationFactor); + + ElectricityKnots[0] = laserTarget; + ElectricityKnots[1] = Vector3::Lerp(laser.PrevOrigin, laser.Origin, _interpolationFactor); for (int j = 0; j < 2; j++) - ElectricityKnots[j] -= laser.Target; + ElectricityKnots[j] -= laserTarget; CalculateHelixSpline(laser, ElectricityKnots, ElectricityBuffer); @@ -189,21 +211,20 @@ namespace TEN::Renderer auto& interpPosArray = ElectricityBuffer; for (int s = 0; s < laser.NumSegments ; s++) { - auto origin = laser.Target + interpPosArray[bufferIndex]; + auto origin = laserTarget + interpPosArray[bufferIndex]; bufferIndex++; - auto target = laser.Target + interpPosArray[bufferIndex]; + auto target = laserTarget + interpPosArray[bufferIndex]; auto center = (origin + target) / 2; - auto direction = target - origin; - direction.Normalize(); + auto dir = target - origin; + dir.Normalize(); AddSpriteBillboardConstrained( &_sprites[Objects[ID_DEFAULT_SPRITES].meshIndex + SPR_LIGHTHING], - center, - color, - PI_DIV_2, 1.0f, Vector2(5 * 8.0f, Vector3::Distance(origin, target)), BlendMode::Additive, direction, true, view); + center, color, PI_DIV_2, 1.0f, Vector2(5 * 8.0f, Vector3::Distance(origin, target)), + BlendMode::Additive, dir, true, view); } - } + } } } @@ -220,12 +241,12 @@ namespace TEN::Renderer if (arc.life <= 0) continue; - ElectricityKnots[0] = arc.pos1; - ElectricityKnots[1] = arc.pos1; - ElectricityKnots[2] = arc.pos2; - ElectricityKnots[3] = arc.pos3; - ElectricityKnots[4] = arc.pos4; - ElectricityKnots[5] = arc.pos4; + ElectricityKnots[0] = Vector3::Lerp(arc.PrevPos1, arc.pos1, _interpolationFactor); + ElectricityKnots[1] = Vector3::Lerp(arc.PrevPos1, arc.pos1, _interpolationFactor); + ElectricityKnots[2] = Vector3::Lerp(arc.PrevPos2, arc.pos2, _interpolationFactor); + ElectricityKnots[3] = Vector3::Lerp(arc.PrevPos3, arc.pos3, _interpolationFactor); + ElectricityKnots[4] = Vector3::Lerp(arc.PrevPos4, arc.pos4, _interpolationFactor); + ElectricityKnots[5] = Vector3::Lerp(arc.PrevPos4, arc.pos4, _interpolationFactor); for (int j = 0; j < ElectricityKnots.size(); j++) ElectricityKnots[j] -= LaraItem->Pose.Position.ToVector3(); @@ -239,6 +260,7 @@ namespace TEN::Renderer int bufferIndex = 0; auto& interpPosArray = ElectricityBuffer; + for (int s = 0; s < ((arc.segments * 3) - 1); s++) { auto origin = (LaraItem->Pose.Position + interpPosArray[bufferIndex]).ToVector3(); @@ -246,8 +268,8 @@ namespace TEN::Renderer auto target = (LaraItem->Pose.Position + interpPosArray[bufferIndex]).ToVector3(); auto center = (origin + target) / 2; - auto direction = target - origin; - direction.Normalize(); + auto dir = target - origin; + dir.Normalize(); byte r, g, b; if (arc.life >= 16) @@ -263,11 +285,30 @@ namespace TEN::Renderer b = (arc.life * arc.b) / 16; } + + byte oldR, oldG, oldB; + if (arc.PrevLife >= 16) + { + oldR = arc.PrevR; + oldG = arc.PrevG; + oldB = arc.PrevB; + } + else + { + oldR = (arc.PrevLife * arc.PrevR) / 16; + oldG = (arc.PrevLife * arc.PrevG) / 16; + oldB = (arc.PrevLife * arc.PrevB) / 16; + } + + r = (byte)Lerp(oldR, r, _interpolationFactor); + g = (byte)Lerp(oldG, g, _interpolationFactor); + b = (byte)Lerp(oldB, b, _interpolationFactor); + AddSpriteBillboardConstrained( &_sprites[Objects[ID_DEFAULT_SPRITES].meshIndex + SPR_LIGHTHING], - center, - Vector4(r / 255.0f, g / 255.0f, b / 255.0f, 1.0f), - PI_DIV_2, 1.0f, Vector2(arc.width * 8, Vector3::Distance(origin, target)), BlendMode::Additive, direction, true, view); + center, Vector4(r / 255.0f, g / 255.0f, b / 255.0f, 1.0f), PI_DIV_2, 1.0f, + Vector2(arc.width * 8, Vector3::Distance(origin, target)), + BlendMode::Additive, dir, true, view); } } } @@ -275,44 +316,75 @@ namespace TEN::Renderer void Renderer::PrepareSmokes(RenderView& view) { - for (int i = 0; i < 32; i++) + for (const auto& smoke : SmokeSparks) { - SMOKE_SPARKS* spark = &SmokeSparks[i]; + if (!smoke.on) + continue; - if (spark->on) - { - AddSpriteBillboard(&_sprites[spark->def], - Vector3(spark->x, spark->y, spark->z), - Vector4(spark->shade / 255.0f, spark->shade / 255.0f, spark->shade / 255.0f, 1.0f), - TO_RAD(spark->rotAng << 4), spark->scalar, { spark->size * 4.0f, spark->size * 4.0f }, - BlendMode::Additive, true, view); - } + AddSpriteBillboard( + &_sprites[smoke.def], + Vector3::Lerp( + Vector3(smoke.oldPosition.x, smoke.oldPosition.y, smoke.oldPosition.z), + Vector3(smoke.position.x, smoke.position.y, smoke.position.z), + _interpolationFactor), + Vector4::Lerp( + Vector4(smoke.oldShade / 255.0f, smoke.oldShade / 255.0f, smoke.oldShade / 255.0f, 1.0f), + Vector4(smoke.shade / 255.0f, smoke.shade / 255.0f, smoke.shade / 255.0f, 1.0f), + _interpolationFactor), + TO_RAD(Lerp(smoke.oldRotAng << 4, smoke.rotAng << 4, _interpolationFactor)), + Lerp(smoke.oldScalar, smoke.scalar, _interpolationFactor), + { + Lerp(smoke.oldSize, smoke.size, _interpolationFactor) * 4.0f, + Lerp(smoke.oldSize, smoke.size, _interpolationFactor) * 4.0f + }, + BlendMode::Additive, true, view); } } - void Renderer::PrepareFires(RenderView& view) { - for (int k = 0; k < MAX_FIRE_LIST; k++) + for (const auto& fire : Fires) { - auto* fire = &Fires[k]; - if (fire->on) - { - auto fade = fire->on == 1 ? 1.0f : (float)(255 - fire->on) / 255.0f; + auto oldFade = fire.oldFade == 1 ? 1.0f : (float)(255 - fire.oldFade) / 255.0f; + auto fade = fire.fade == 1 ? 1.0f : (float)(255 - fire.fade) / 255.0f; + fade = Lerp(oldFade, fade, _interpolationFactor); - for (int i = 0; i < MAX_SPARKS_FIRE; i++) + for (int i = 0; i < MAX_SPARKS_FIRE; i++) + { + auto* spark = &FireSparks[i]; + if (spark->on) { - auto* spark = &FireSparks[i]; - if (spark->on) - { - AddSpriteBillboard( - &_sprites[spark->def], - Vector3(fire->x + spark->x * fire->size / 2, fire->y + spark->y * fire->size / 2, fire->z + spark->z * fire->size / 2), - Vector4(spark->r / 255.0f * fade, spark->g / 255.0f * fade, spark->b / 255.0f * fade, 1.0f), - TO_RAD(spark->rotAng << 4), - spark->scalar, - Vector2(spark->size * fire->size, spark->size * fire->size), BlendMode::Additive, true, view); - } + AddSpriteBillboard( + &_sprites[spark->def], + Vector3::Lerp( + Vector3( + fire.oldPosition.x + spark->oldPosition.x * fire.oldSize / 2, + fire.oldPosition.y + spark->oldPosition.y * fire.oldSize / 2, + fire.oldPosition.z + spark->oldPosition.z * fire.oldSize / 2), + Vector3( + fire.position.x + spark->position.x * fire.size / 2, + fire.position.y + spark->position.y * fire.size / 2, + fire.position.z + spark->position.z * fire.size / 2), + _interpolationFactor), + Vector4::Lerp( + Vector4( + spark->oldColor.x / 255.0f * fade, + spark->oldColor.y / 255.0f * fade, + spark->oldColor.z / 255.0f * fade, + 1.0f), + Vector4( + spark->color.x / 255.0f * fade, + spark->color.y / 255.0f * fade, + spark->color.z / 255.0f * fade, + 1.0f), + _interpolationFactor), + TO_RAD(Lerp(spark->oldRotAng << 4, spark->rotAng << 4, _interpolationFactor)), + Lerp(spark->oldScalar, spark->scalar, _interpolationFactor), + Vector2::Lerp( + Vector2(fire.oldSize * spark->oldSize, fire.oldSize * spark->oldSize), + Vector2(fire.size * spark->size, fire.size * spark->size), + _interpolationFactor), + BlendMode::Additive, true, view); } } } @@ -330,27 +402,49 @@ namespace TEN::Renderer if (particle.flags & SP_DEF) { - auto pos = Vector3(particle.x, particle.y, particle.z); + auto pos = Vector3::Lerp( + Vector3(particle.PrevX, particle.PrevY, particle.PrevZ), + Vector3(particle.x, particle.y, particle.z), + _interpolationFactor); if (particle.flags & SP_FX) { const auto& fx = EffectList[particle.fxObj]; - pos += fx.pos.Position.ToVector3(); + auto& newEffect = _effects[particle.fxObj]; + + newEffect.Translation = Matrix::CreateTranslation(fx.pos.Position.ToVector3()); + newEffect.Rotation = fx.pos.Orientation.ToRotationMatrix(); + newEffect.Scale = Matrix::CreateScale(1.0f); + newEffect.World = newEffect.Rotation * newEffect.Translation; + newEffect.ObjectID = fx.objectNumber; + newEffect.RoomNumber = fx.roomNumber; + newEffect.Position = fx.pos.Position.ToVector3(); + + newEffect.InterpolatedPosition = Vector3::Lerp(newEffect.PrevPosition, newEffect.Position, _interpolationFactor); + newEffect.InterpolatedTranslation = Matrix::Lerp(newEffect.PrevTranslation, newEffect.Translation, _interpolationFactor); + newEffect.InterpolatedRotation = Matrix::Lerp(newEffect.InterpolatedRotation, newEffect.Rotation, _interpolationFactor); + newEffect.InterpolatedWorld = Matrix::Lerp(newEffect.PrevWorld, newEffect.World, _interpolationFactor); + newEffect.InterpolatedScale = Matrix::Lerp(newEffect.PrevScale, newEffect.Scale, _interpolationFactor); + + pos += newEffect.InterpolatedPosition; if ((particle.sLife - particle.life) > Random::GenerateInt(8, 12)) { + // Particle becomes autonome. particle.flags &= ~SP_FX; - particle.x = pos.x; - particle.y = pos.y; - particle.z = pos.z; + + particle.x = particle.PrevX = pos.x; + particle.y = particle.PrevY = pos.y; + particle.z = particle.PrevZ = pos.z; } } else if (!(particle.flags & SP_ITEM)) { - pos.x = particle.x; - pos.y = particle.y; - pos.z = particle.z; + // NOTE: pos already set previously. + //pos.x = particle.x; + //pos.y = particle.y; + //pos.z = particle.z; } else { @@ -371,9 +465,13 @@ namespace TEN::Renderer int meshIndex = NodeOffsets[particle.nodeNumber].meshNum; if (meshIndex >= 0) + { nodePos = GetJointPosition(item, meshIndex, nodePos); + } else + { nodePos = GetJointPosition(LaraItem, -meshIndex, nodePos); + } NodeOffsets[particle.nodeNumber].gotIt = true; NodeVectors[particle.nodeNumber] = nodePos; @@ -383,25 +481,27 @@ namespace TEN::Renderer if ((particle.sLife - particle.life) > Random::GenerateInt(4, 8)) { + // Particle becomes autonome. particle.flags &= ~SP_ITEM; - particle.x = pos.x; - particle.y = pos.y; - particle.z = pos.z; + + particle.x = particle.PrevX = pos.x; + particle.y = particle.PrevY = pos.y; + particle.z = particle.PrevZ = pos.z; } } else { - pos += item->Pose.Position.ToVector3(); + pos += _items[particle.fxObj].InterpolatedPosition; } } - // Don't allow sprites out of bounds. + // Disallow sprites out of bounds. int spriteIndex = std::clamp((int)particle.spriteIndex, 0, (int)_sprites.size()); AddSpriteBillboard( &_sprites[spriteIndex], pos, - Vector4(particle.r / (float)UCHAR_MAX, particle.g / (float)UCHAR_MAX, particle.b / (float)UCHAR_MAX, 1.0f), + Color(particle.r / (float)UCHAR_MAX, particle.g / (float)UCHAR_MAX, particle.b / (float)UCHAR_MAX, 1.0f), TO_RAD(particle.rotAng << 4), particle.scalar, Vector2(particle.size, particle.size), particle.blendMode, true, view); @@ -411,7 +511,11 @@ namespace TEN::Renderer if (!CheckIfSlotExists(ID_SPARK_SPRITE, "Particle rendering")) continue; - auto pos = Vector3(particle.x, particle.y, particle.z); + auto pos = Vector3::Lerp( + Vector3(particle.PrevX, particle.PrevY, particle.PrevZ), + Vector3(particle.x, particle.y, particle.z), + _interpolationFactor); + auto axis = Vector3(particle.xVel, particle.yVel, particle.zVel); axis.Normalize(); @@ -452,8 +556,19 @@ namespace TEN::Renderer } } - float innerRadius = splash.innerRad; - float outerRadius = splash.outerRad; + byte prevColor = (splash.PrevLife >= 32 ? 128 : (byte)((splash.PrevLife / 32.0f) * 128)); + + if (!splash.isRipple) + { + if (splash.PrevHeightSpeed < 0 && splash.PrevHeight < 1024) + { + float multiplier = splash.PrevHeight / 1024.0f; + prevColor = (float)prevColor * multiplier; + } + } + + color = (byte)Lerp(prevColor, color, _interpolationFactor); + float xInner; float zInner; float xOuter; @@ -465,6 +580,9 @@ namespace TEN::Renderer float yInner = splash.y; float yOuter = splash.y - splash.height; + float innerRadius = Lerp(splash.PrevInnerRad, splash.innerRad, _interpolationFactor); + float outerRadius = Lerp(splash.PrevOuterRad, splash.outerRad, _interpolationFactor); + for (int i = 0; i < NUM_POINTS; i++) { xInner = innerRadius * sin(alpha * i * PI / 180); @@ -484,12 +602,15 @@ namespace TEN::Renderer x2Outer += splash.x; z2Outer = outerRadius * cos(alpha * j * PI / 180); z2Outer += splash.z; + AddQuad(&_sprites[Objects[ID_DEFAULT_SPRITES].meshIndex + splash.spriteSequenceStart + (int)splash.animationPhase], - Vector3(xOuter, yOuter, zOuter), - Vector3(x2Outer, yOuter, z2Outer), - Vector3(x2Inner, yInner, z2Inner), - Vector3(xInner, yInner, zInner), Vector4(color / 255.0f, color / 255.0f, color / 255.0f, 1.0f), - 0, 1, { 0, 0 }, BlendMode::Additive, false, view); + Vector3(xOuter, yOuter, zOuter), + Vector3(x2Outer, yOuter, z2Outer), + Vector3(x2Inner, yInner, z2Inner), + Vector3(xInner, yInner, zInner), + Vector4(color / 255.0f, color / 255.0f, color / 255.0f, 1.0f), + 0, 1, Vector2::Zero, + BlendMode::Additive, false, view); } } } @@ -509,8 +630,12 @@ namespace TEN::Renderer AddSpriteBillboard( &_sprites[Objects[ID_DEFAULT_SPRITES].meshIndex + bubble.SpriteIndex], - bubble.Position, - bubble.Color, 0.0f, 1.0f, bubble.Size / 2, BlendMode::Additive, true, view); + Vector3::Lerp(bubble.PrevPosition, bubble.Position, _interpolationFactor), + Vector4::Lerp(bubble.PrevColor, bubble.Color, _interpolationFactor), + 0.0f, + 1.0f, + Vector2::Lerp(bubble.PrevSize, bubble.Size, _interpolationFactor) / 2, + BlendMode::Additive, true, view); } } @@ -530,10 +655,15 @@ namespace TEN::Renderer auto axis = drip.Velocity; drip.Velocity.Normalize(axis); + auto prevAxis = drip.PrevVelocity; + drip.PrevVelocity.Normalize(prevAxis); + AddSpriteBillboardConstrained( &_sprites[Objects[ID_DRIP_SPRITE].meshIndex], - drip.Position, - drip.Color, 0.0f, 1.0f, drip.Size, BlendMode::Additive, -axis, false, view); + Vector3::Lerp(drip.PrevPosition, drip.Position, _interpolationFactor), + Vector4::Lerp(drip.PrevColor, drip.Color, _interpolationFactor), + 0.0f, 1.0f, Vector2::Lerp(drip.PrevSize, drip.Size, _interpolationFactor), + BlendMode::Additive, -Vector3::Lerp(prevAxis, axis, _interpolationFactor), false, view); } } @@ -551,10 +681,16 @@ namespace TEN::Renderer auto color = ripple.Color; color.w = opacity; + float oldOpacity = ripple.PrevColor.w * ((ripple.Flags & (int)RippleFlags::LowOpacity) ? 0.5f : 1.0f); + auto oldColor = ripple.PrevColor; + oldColor.w = oldOpacity; + AddSpriteBillboardConstrainedLookAt( &_sprites[ripple.SpriteIndex], - ripple.Position, - color, 0.0f, 1.0f, Vector2(ripple.Size * 2), BlendMode::Additive, ripple.Normal, true, view); + Vector3::Lerp(ripple.PrevPosition, ripple.Position, _interpolationFactor), + Vector4::Lerp(oldColor, color, _interpolationFactor), + 0.0f, 1.0f, Vector2(Lerp(ripple.PrevSize, ripple.Size, _interpolationFactor) * 2), + BlendMode::Additive, ripple.Normal, true, view); } } @@ -570,19 +706,39 @@ namespace TEN::Renderer auto color = Vector4::Zero; if (uwBlood.Init) + { color = Vector4(uwBlood.Init / 2, 0, uwBlood.Init / 16, UCHAR_MAX); + } else + { color = Vector4(uwBlood.Life / 2, 0, uwBlood.Life / 16, UCHAR_MAX); + } color.x = (int)std::clamp((int)color.x, 0, UCHAR_MAX); color.y = (int)std::clamp((int)color.y, 0, UCHAR_MAX); color.z = (int)std::clamp((int)color.z, 0, UCHAR_MAX); color /= UCHAR_MAX; + auto oldColor = Vector4::Zero; + if (uwBlood.Init) + oldColor = Vector4(uwBlood.Init / 2, 0, uwBlood.Init / 16, UCHAR_MAX); + else + oldColor = Vector4(uwBlood.PrevLife / 2, 0, uwBlood.PrevLife / 16, UCHAR_MAX); + + oldColor.x = (int)std::clamp((int)oldColor.x, 0, UCHAR_MAX); + oldColor.y = (int)std::clamp((int)oldColor.y, 0, UCHAR_MAX); + oldColor.z = (int)std::clamp((int)oldColor.z, 0, UCHAR_MAX); + oldColor /= UCHAR_MAX; + AddSpriteBillboard( &_sprites[uwBlood.SpriteIndex], - uwBlood.Position, - color, 0.0f, 1.0f, Vector2(uwBlood.Size, uwBlood.Size) * 2, BlendMode::Additive, true, view); + Vector3::Lerp(uwBlood.PrevPosition, uwBlood.Position, _interpolationFactor), + Vector4::Lerp(oldColor, color, _interpolationFactor), + 0.0f, 1.0f, + Vector2( + Lerp(uwBlood.PrevSize, uwBlood.Size, _interpolationFactor), + Lerp(uwBlood.PrevSize, uwBlood.Size, _interpolationFactor)) * 2, + BlendMode::Additive, true, view); } } @@ -597,7 +753,7 @@ namespace TEN::Renderer for (int i = 0; i < MAX_SHOCKWAVE; i++) { - SHOCKWAVE_STRUCT* shockwave = &ShockWaves[i]; + auto* shockwave = &ShockWaves[i]; if (!shockwave->life) continue; @@ -616,6 +772,9 @@ namespace TEN::Renderer auto pos = Vector3(shockwave->x, shockwave->y, shockwave->z); + float innerRadius = Lerp(shockwave->oldInnerRad, shockwave->innerRad, _interpolationFactor); + float outerRadius = Lerp(shockwave->oldOuterRad, shockwave->outerRad, _interpolationFactor); + // Inner circle if (shockwave->style == (int)ShockwaveStyle::Normal) { @@ -632,10 +791,10 @@ namespace TEN::Renderer angle -= PI / 4.0f; } - float x1 = (shockwave->innerRad * c); - float z1 = (shockwave->innerRad * s); - float x4 = (shockwave->outerRad * c); - float z4 = (shockwave->outerRad * s); + float x1 = (innerRadius * c); + float z1 = (innerRadius * s); + float x4 = (innerRadius * c); + float z4 = (innerRadius * s); auto p1 = Vector3(x1, 0, z1); auto p4 = Vector3(x4, 0, z4); @@ -655,7 +814,6 @@ namespace TEN::Renderer r = shockwave->r * shockwave->life / 255.0f; } - if (shockwave->sg < shockwave->g) { shockwave->sg += shockwave->g / 18; @@ -666,7 +824,6 @@ namespace TEN::Renderer g = shockwave->g * shockwave->life / 255.0f; } - if (shockwave->sb < shockwave->b) { shockwave->sb += shockwave->b / 18; @@ -688,16 +845,16 @@ namespace TEN::Renderer b = shockwave->b * shockwave->life / 255.0f; } - for (int j = 0; j < 16; j++) + for (int j = 0; j <= 16; j++) { c = cos(angle); s = sin(angle); - float x2 = (shockwave->innerRad * c); - float z2 = (shockwave->innerRad * s); + float x2 = (innerRadius * c); + float z2 = (innerRadius * s); - float x3 = (shockwave->outerRad * c); - float z3 = (shockwave->outerRad * s); + float x3 = (outerRadius * c); + float z3 = (outerRadius * s); auto p2 = Vector3(x2, 0, z2); auto p3 = Vector3(x3, 0, z3); @@ -735,7 +892,7 @@ namespace TEN::Renderer g / 16.0f, b / 16.0f, 1.0f), - 0, 1, { 0,0 }, BlendMode::Additive, true, view); + 0, 1, Vector2::Zero, BlendMode::Additive, true, view); } else if (shockwave->style == (int)ShockwaveStyle::Knockback) @@ -752,7 +909,7 @@ namespace TEN::Renderer g / 16.0f, b / 16.0f, 1.0f), - 0, 1, { 0,0 }, BlendMode::Additive, true, view); + 0, 1, Vector2::Zero, BlendMode::Additive, true, view); } p1 = p2; @@ -765,18 +922,29 @@ namespace TEN::Renderer { for (int i = 0; i < 32; i++) { - BLOOD_STRUCT* blood = &Blood[i]; + auto* blood = &Blood[i]; - if (blood->on) + if (blood->on) { if (!CheckIfSlotExists(ID_DEFAULT_SPRITES, "Blood rendering")) return; - AddSpriteBillboard(&_sprites[Objects[ID_DEFAULT_SPRITES].meshIndex + SPR_BLOOD], - Vector3(blood->x, blood->y, blood->z), - Vector4(blood->shade / 255.0f, blood->shade * 0, blood->shade * 0, 1.0f), - TO_RAD(blood->rotAng << 4), 1.0f, { blood->size * 8.0f, blood->size * 8.0f }, - BlendMode::Additive, true, view); + AddSpriteBillboard( + &_sprites[Objects[ID_DEFAULT_SPRITES].meshIndex + SPR_BLOOD], + Vector3::Lerp( + Vector3(blood->oldX, blood->oldY, blood->oldZ), + Vector3(blood->x, blood->y, blood->z), + _interpolationFactor), + Vector4::Lerp( + Vector4(blood->oldShade / 255.0f, blood->oldShade * 0, blood->oldShade * 0, 1.0f), + Vector4(blood->shade / 255.0f, blood->shade * 0, blood->shade * 0, 1.0f), + _interpolationFactor), + TO_RAD(Lerp(blood->oldRotAng << 4, blood->rotAng << 4, _interpolationFactor)), + 1.0f, + Vector2( + Lerp(blood->oldSize, blood->size, _interpolationFactor) * 8.0f, + Lerp(blood->oldSize, blood->size, _interpolationFactor) * 8.0f), + BlendMode::Additive, true, view); } } } @@ -785,12 +953,12 @@ namespace TEN::Renderer { constexpr auto RAIN_WIDTH = 4.0f; - for (auto& p : Weather.GetParticles()) + for (const auto& part : Weather.GetParticles()) { - if (!p.Enabled) + if (!part.Enabled) continue; - switch (p.Type) + switch (part.Type) { case WeatherType::None: @@ -799,9 +967,9 @@ namespace TEN::Renderer AddSpriteBillboard( &_sprites[Objects[ID_DEFAULT_SPRITES].meshIndex + SPR_UNDERWATERDUST], - p.Position, - Vector4(1.0f, 1.0f, 1.0f, p.Transparency()), - 0.0f, 1.0f, Vector2(p.Size), + Vector3::Lerp(part.PrevPosition, part.Position, _interpolationFactor), + Color(1.0f, 1.0f, 1.0f, part.Transparency()), + 0.0f, 1.0f, Vector2(Lerp(part.PrevSize, part.Size, _interpolationFactor)), BlendMode::Additive, true, view); break; @@ -813,9 +981,9 @@ namespace TEN::Renderer AddSpriteBillboard( &_sprites[Objects[ID_DEFAULT_SPRITES].meshIndex + SPR_UNDERWATERDUST], - p.Position, - Vector4(1.0f, 1.0f, 1.0f, p.Transparency()), - 0.0f, 1.0f, Vector2(p.Size), + Vector3::Lerp(part.PrevPosition, part.Position, _interpolationFactor), + Color(1.0f, 1.0f, 1.0f, part.Transparency()), + 0.0f, 1.0f, Vector2(Lerp(part.PrevSize, part.Size, _interpolationFactor)), BlendMode::Additive, true, view); break; @@ -826,13 +994,15 @@ namespace TEN::Renderer return; Vector3 v; - p.Velocity.Normalize(v); + part.Velocity.Normalize(v); AddSpriteBillboardConstrained( &_sprites[Objects[ID_DRIP_SPRITE].meshIndex], - p.Position, - Vector4(0.8f, 1.0f, 1.0f, p.Transparency()), - 0.0f, 1.0f, Vector2(RAIN_WIDTH, p.Size), BlendMode::Additive, -v, true, view); + Vector3::Lerp(part.PrevPosition, part.Position, _interpolationFactor), + Color(0.8f, 1.0f, 1.0f, part.Transparency()), + 0.0f, 1.0f, + Vector2(RAIN_WIDTH, Lerp(part.PrevSize, part.Size, _interpolationFactor)), + BlendMode::Additive, -v, true, view); break; } @@ -844,8 +1014,8 @@ namespace TEN::Renderer _context->VSSetShader(_vsStatics.Get(), nullptr, 0); _context->PSSetShader(_psStatics.Get(), nullptr, 0); - UINT stride = sizeof(Vertex); - UINT offset = 0; + unsigned int stride = sizeof(Vertex); + unsigned int offset = 0; _context->IASetVertexBuffers(0, 1, _moveablesVertexBuffer.Buffer.GetAddressOf(), &stride, &offset); _context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST); @@ -932,7 +1102,7 @@ namespace TEN::Renderer auto worldMatrix = Matrix::Identity; if (Lara.LeftArm.GunFlash) { - worldMatrix = itemPtr->AnimationTransforms[LM_LHAND] * itemPtr->World; + worldMatrix = itemPtr->AnimTransforms[LM_LHAND] * itemPtr->World; worldMatrix = tMatrix * worldMatrix; worldMatrix = rotMatrix * worldMatrix; @@ -946,7 +1116,7 @@ namespace TEN::Renderer if (Lara.RightArm.GunFlash) { - worldMatrix = itemPtr->AnimationTransforms[LM_RHAND] * itemPtr->World; + worldMatrix = itemPtr->AnimTransforms[LM_RHAND] * itemPtr->World; worldMatrix = tMatrix * worldMatrix; worldMatrix = rotMatrix * worldMatrix; @@ -969,8 +1139,8 @@ namespace TEN::Renderer _context->VSSetShader(_vsStatics.Get(), nullptr, 0); _context->PSSetShader(_psStatics.Get(), nullptr, 0); - UINT stride = sizeof(Vertex); - UINT offset = 0; + unsigned int stride = sizeof(Vertex); + unsigned int offset = 0; _context->IASetVertexBuffers(0, 1, _moveablesVertexBuffer.Buffer.GetAddressOf(), &stride, &offset); _context->IASetIndexBuffer(_moveablesIndexBuffer.Buffer.Get(), DXGI_FORMAT_R32_UINT, 0); @@ -1017,7 +1187,7 @@ namespace TEN::Renderer auto rotMatrixX = Matrix::CreateRotationX(TO_RAD(ANGLE(270.0f))); auto rotMatrixZ = Matrix::CreateRotationZ(TO_RAD(2 * GetRandomControl())); - auto worldMatrix = rItemPtr->AnimationTransforms[creature.MuzzleFlash[0].Bite.BoneID] * rItemPtr->World; + auto worldMatrix = rItemPtr->AnimTransforms[creature.MuzzleFlash[0].Bite.BoneID] * rItemPtr->World; worldMatrix = tMatrix * worldMatrix; if (creature.MuzzleFlash[0].ApplyXRotation) @@ -1057,7 +1227,7 @@ namespace TEN::Renderer auto rotMatrixX = Matrix::CreateRotationX(TO_RAD(ANGLE(270.0f))); auto rotMatrixZ = Matrix::CreateRotationZ(TO_RAD(2 * GetRandomControl())); - auto worldMatrix = rItemPtr->AnimationTransforms[creature.MuzzleFlash[1].Bite.BoneID] * rItemPtr->World; + auto worldMatrix = rItemPtr->AnimTransforms[creature.MuzzleFlash[1].Bite.BoneID] * rItemPtr->World; worldMatrix = tMatrix * worldMatrix; if (creature.MuzzleFlash[1].ApplyXRotation) @@ -1082,7 +1252,7 @@ namespace TEN::Renderer Texture2D Renderer::CreateDefaultNormalTexture() { - std::vector data = { 128, 128, 255, 1 }; + auto data = std::vector{ 128, 128, 255, 1 }; return Texture2D(_device.Get(), 1, 1, data.data()); } @@ -1144,31 +1314,25 @@ namespace TEN::Renderer { const auto& room = _rooms[effect->RoomNumber]; - _stStatic.World = effect->World; + _stStatic.World = effect->InterpolatedWorld; _stStatic.Color = effect->Color; _stStatic.AmbientLight = effect->AmbientLight; _stStatic.LightMode = (int)LightMode::Dynamic; BindStaticLights(effect->LightsToDraw); _cbStatic.UpdateData(_stStatic, _context.Get()); - auto* meshPtr = effect->Mesh; - auto m_lastBlendMode = BlendMode::Unknown; - - for (auto& bucket : meshPtr->Buckets) + auto& mesh = *effect->Mesh; + for (auto& bucket : mesh.Buckets) { if (bucket.NumVertices == 0) - { continue; - } - int passes = rendererPass == RendererPass::Opaque && bucket.BlendMode == BlendMode::AlphaTest ? 2 : 1; + int passes = (rendererPass == RendererPass::Opaque && bucket.BlendMode == BlendMode::AlphaTest) ? 2 : 1; for (int p = 0; p < passes; p++) { if (!SetupBlendModeAndAlphaTest(bucket.BlendMode, rendererPass, p)) - { continue; - } BindTexture(TextureRegister::ColorMap, &std::get<0>(_moveablesTextures[bucket.Texture]), SamplerStateRegister::AnisotropicClamp); BindTexture(TextureRegister::NormalMap, &std::get<1>(_moveablesTextures[bucket.Texture]), SamplerStateRegister::AnisotropicClamp); @@ -1185,8 +1349,8 @@ namespace TEN::Renderer _context->VSSetShader(_vsStatics.Get(), nullptr, 0); _context->PSSetShader(_psStatics.Get(), nullptr, 0); - UINT stride = sizeof(Vertex); - UINT offset = 0; + unsigned int stride = sizeof(Vertex); + unsigned int offset = 0; _context->IASetVertexBuffers(0, 1, _moveablesVertexBuffer.Buffer.GetAddressOf(), &stride, &offset); _context->IASetIndexBuffer(_moveablesIndexBuffer.Buffer.Get(), DXGI_FORMAT_R32_UINT, 0); @@ -1196,7 +1360,7 @@ namespace TEN::Renderer for (auto* effectPtr : roomPtr->EffectsToDraw) { const auto& room = _rooms[effectPtr->RoomNumber]; - const auto& object = Objects[effectPtr->ObjectNumber]; + const auto& object = Objects[effectPtr->ObjectID]; if (object.drawRoutine && object.loaded) DrawEffect(view, effectPtr, rendererPass); @@ -1206,8 +1370,6 @@ namespace TEN::Renderer void Renderer::DrawDebris(RenderView& view, RendererPass rendererPass) { - std::vector vertices; - bool activeDebrisExist = false; for (auto& deb : DebrisFragments) { @@ -1230,9 +1392,7 @@ namespace TEN::Renderer if (deb.active) { if (!SetupBlendModeAndAlphaTest(deb.mesh.blendMode, rendererPass, 0)) - { continue; - } if (deb.isStatic) { @@ -1243,7 +1403,7 @@ namespace TEN::Renderer BindTexture(TextureRegister::ColorMap, &std::get<0>(_moveablesTextures[deb.mesh.tex]), SamplerStateRegister::LinearClamp); } - _stStatic.World = deb.Transform; + _stStatic.World = Matrix::Lerp(deb.PrevTransform, deb.Transform, _interpolationFactor); _stStatic.Color = deb.color; _stStatic.AmbientLight = _rooms[deb.roomNumber].AmbientLight; _stStatic.LightMode = (int)deb.lightMode; @@ -1302,8 +1462,14 @@ namespace TEN::Renderer AddSpriteBillboard( &_sprites[Objects[ID_SMOKE_SPRITES].meshIndex + smoke.sprite], - smoke.position, - smoke.color, smoke.rotation, 1.0f, { smoke.size, smoke.size }, BlendMode::AlphaBlend, true, view); + Vector3::Lerp(smoke.PrevPosition, smoke.position, _interpolationFactor), + Vector4::Lerp(smoke.PrevColor, smoke.color, _interpolationFactor), + Lerp(smoke.PrevRotation, smoke.rotation, _interpolationFactor), + 1.0f, + Vector2( + Lerp(smoke.PrevSize, smoke.size, _interpolationFactor), + Lerp(smoke.PrevSize, smoke.size, _interpolationFactor)), + BlendMode::AlphaBlend, true, view); } } @@ -1316,20 +1482,33 @@ namespace TEN::Renderer for (int i = 0; i < SparkParticles.size(); i++) { - SparkParticle& s = SparkParticles[i]; + auto& s = SparkParticles[i]; if (!s.active) continue; if (!CheckIfSlotExists(ID_SPARK_SPRITE, "Spark particle rendering")) return; - Vector3 v; - s.velocity.Normalize(v); + Vector3 prevVelocity; + Vector3 velocity; + s.PrevVelocity.Normalize(prevVelocity); + s.velocity.Normalize(velocity); + + velocity = Vector3::Lerp(prevVelocity, velocity, _interpolationFactor); + velocity.Normalize(); float normalizedLife = s.age / s.life; auto height = Lerp(1.0f, 0.0f, normalizedLife); auto color = Vector4::Lerp(s.sourceColor, s.destinationColor, normalizedLife); - AddSpriteBillboardConstrained(&_sprites[Objects[ID_SPARK_SPRITE].meshIndex], s.pos, color, 0, 1, { s.width, s.height * height }, BlendMode::Additive, -v, false, view); + AddSpriteBillboardConstrained( + &_sprites[Objects[ID_SPARK_SPRITE].meshIndex], + Vector3::Lerp(s.PrevPosition, s.pos, _interpolationFactor), + color, + 0, 1, + Vector2( + s.width, + s.height * height), + BlendMode::Additive, -velocity, false, view); } } @@ -1340,14 +1519,22 @@ namespace TEN::Renderer for (int i = 0; i < explosionParticles.size(); i++) { - ExplosionParticle& e = explosionParticles[i]; - if (!e.active) continue; + auto& exp = explosionParticles[i]; + if (!exp.active) continue; if (!CheckIfSlotExists(ID_EXPLOSION_SPRITES, "Explosion particles rendering")) return; - AddSpriteBillboard(&_sprites[Objects[ID_EXPLOSION_SPRITES].meshIndex + e.sprite], - e.pos, e.tint, e.rotation, 1.0f, { e.size, e.size }, BlendMode::Additive, true, view); + AddSpriteBillboard( + &_sprites[Objects[ID_EXPLOSION_SPRITES].meshIndex + exp.sprite], + Vector3::Lerp(exp.oldPos, exp.pos, _interpolationFactor), + Vector4::Lerp(exp.oldTint, exp.tint, _interpolationFactor), + Lerp(exp.oldRotation, exp.rotation, _interpolationFactor), + 1.0f, + Vector2( + Lerp(exp.oldSize, exp.size, _interpolationFactor), + Lerp(exp.oldSize, exp.size, _interpolationFactor)), + BlendMode::Additive, true, view); } } @@ -1355,14 +1542,22 @@ namespace TEN::Renderer { using namespace TEN::Effects; - for (SimpleParticle& s : simpleParticles) + for (const auto& part : simpleParticles) { - if (!s.active) continue; - - if (!CheckIfSlotExists(s.sequence, "Particle rendering")) + if (!part.active) continue; - AddSpriteBillboard(&_sprites[Objects[s.sequence].meshIndex + s.sprite], s.worldPosition, Vector4(1, 1, 1, 1), 0, 1.0f, { s.size, s.size / 2 }, BlendMode::AlphaBlend, true, view); + if (!CheckIfSlotExists(part.sequence, "Particle rendering")) + continue; + + AddSpriteBillboard( + &_sprites[Objects[part.sequence].meshIndex + part.sprite], + Vector3::Lerp(part.PrevWorldPosition, part.worldPosition, _interpolationFactor), + Color(1.0f, 1.0f, 1.0f), 0, 1.0f, + Vector2( + Lerp(part.PrevSize, part.size, _interpolationFactor), + Lerp(part.PrevSize, part.size, _interpolationFactor) / 2), + BlendMode::AlphaBlend, true, view); } } } diff --git a/TombEngine/Renderer/RendererDrawMenu.cpp b/TombEngine/Renderer/RendererDrawMenu.cpp index 8289f1453..c9fb4888b 100644 --- a/TombEngine/Renderer/RendererDrawMenu.cpp +++ b/TombEngine/Renderer/RendererDrawMenu.cpp @@ -38,7 +38,7 @@ namespace TEN::Renderer constexpr auto MenuVerticalLineSpacing = 30; constexpr auto MenuVerticalNarrowLineSpacing = 24; constexpr auto MenuVerticalBlockSpacing = 50; - + // Vertical menu positioning templates constexpr auto MenuVerticalControls = 30; constexpr auto MenuVerticalDisplaySettings = 160; @@ -65,10 +65,25 @@ namespace TEN::Renderer // Helper functions to construct string flags inline int SF(bool selected = false) { return (int)PrintStringFlags::Outline | (selected ? (int)PrintStringFlags::Blink : 0); } inline int SF_Center(bool selected = false) { return (int)PrintStringFlags::Outline | (int)PrintStringFlags::Center | (selected ? (int)PrintStringFlags::Blink : 0); } - + // Helper functions to get specific generic strings - inline const char* Str_Enabled(bool enabled = false) { return g_GameFlow->GetString(enabled ? STRING_ENABLED : STRING_DISABLED); } - inline const char* Str_LoadSave(bool save = false) { return g_GameFlow->GetString(save ? STRING_SAVE_GAME : STRING_LOAD_GAME); } + inline const std::string Str_Enabled(bool enabled = false) { return g_GameFlow->GetString(enabled ? STRING_ENABLED : STRING_DISABLED); } + inline const std::string Str_LoadSave(bool save = false) { return g_GameFlow->GetString(save ? STRING_SAVE_GAME : STRING_LOAD_GAME); } + inline const std::string Str_MenuOptionLoopingMode(MenuOptionLoopingMode loopingMode) + { + switch (loopingMode) + { + default: + case MenuOptionLoopingMode::AllMenus: + return g_GameFlow->GetString(STRING_MENU_OPT_LOOP_ALL_MENUS); + + case MenuOptionLoopingMode::SaveLoadOnly: + return g_GameFlow->GetString(STRING_MENU_OPT_LOOP_SAVE_LOAD_ONLY); + + case MenuOptionLoopingMode::Disabled: + return g_GameFlow->GetString(STRING_MENU_OPT_LOOP_DISABLED); + } + } // These bars are only used in menus. TEN::Renderer::RendererHudBar* g_MusicVolumeBar = nullptr; @@ -189,14 +204,19 @@ namespace TEN::Renderer // Enable SSAO AddString(MenuLeftSideEntry, y, g_GameFlow->GetString(STRING_AMBIENT_OCCLUSION), PRINTSTRING_COLOR_ORANGE, SF(titleOption == 5)); AddString(MenuRightSideEntry, y, Str_Enabled(g_Gui.GetCurrentSettings().Configuration.EnableAmbientOcclusion), PRINTSTRING_COLOR_WHITE, SF(titleOption == 5)); + GetNextLinePosition(&y); + + // Enable high framerate + AddString(MenuLeftSideEntry, y, g_GameFlow->GetString(STRING_HIGH_FRAMERATE), PRINTSTRING_COLOR_ORANGE, SF(titleOption == 6)); + AddString(MenuRightSideEntry, y, Str_Enabled(g_Gui.GetCurrentSettings().Configuration.EnableHighFramerate), PRINTSTRING_COLOR_WHITE, SF(titleOption == 6)); GetNextBlockPosition(&y); // Apply - AddString(MenuCenterEntry, y, g_GameFlow->GetString(STRING_APPLY), PRINTSTRING_COLOR_ORANGE, SF_Center(titleOption == 6)); + AddString(MenuCenterEntry, y, g_GameFlow->GetString(STRING_APPLY), PRINTSTRING_COLOR_ORANGE, SF_Center(titleOption == 7)); GetNextLinePosition(&y); // Cancel - AddString(MenuCenterEntry, y, g_GameFlow->GetString(STRING_CANCEL), PRINTSTRING_COLOR_ORANGE, SF_Center(titleOption == 7)); + AddString(MenuCenterEntry, y, g_GameFlow->GetString(STRING_CANCEL), PRINTSTRING_COLOR_ORANGE, SF_Center(titleOption == 8)); break; case Menu::OtherSettings: @@ -231,36 +251,36 @@ namespace TEN::Renderer AddString(MenuRightSideEntry, y, Str_Enabled(g_Gui.GetCurrentSettings().Configuration.EnableSubtitles), PRINTSTRING_COLOR_WHITE, SF(titleOption == 3)); GetNextLinePosition(&y); + // Auto monkey swing jump + AddString(MenuLeftSideEntry, y, g_GameFlow->GetString(STRING_AUTO_MONKEY_SWING_JUMP), PRINTSTRING_COLOR_ORANGE, SF(titleOption == 4)); + AddString(MenuRightSideEntry, y, Str_Enabled(g_Gui.GetCurrentSettings().Configuration.EnableAutoMonkeySwingJump), PRINTSTRING_COLOR_WHITE, SF(titleOption == 4)); + GetNextLinePosition(&y); + // Auto targeting - AddString(MenuLeftSideEntry, y, g_GameFlow->GetString(STRING_AUTOMATIC_TARGETING), PRINTSTRING_COLOR_ORANGE, SF(titleOption == 4)); - AddString(MenuRightSideEntry, y, Str_Enabled(g_Gui.GetCurrentSettings().Configuration.EnableAutoTargeting), PRINTSTRING_COLOR_WHITE, SF(titleOption == 4)); + AddString(MenuLeftSideEntry, y, g_GameFlow->GetString(STRING_AUTO_TARGETING), PRINTSTRING_COLOR_ORANGE, SF(titleOption == 5)); + AddString(MenuRightSideEntry, y, Str_Enabled(g_Gui.GetCurrentSettings().Configuration.EnableAutoTargeting), PRINTSTRING_COLOR_WHITE, SF(titleOption == 5)); GetNextLinePosition(&y); // Target highlighter - AddString(MenuLeftSideEntry, y, g_GameFlow->GetString(STRING_TARGET_HIGHLIGHTER), PRINTSTRING_COLOR_ORANGE, SF(titleOption == 5)); - AddString(MenuRightSideEntry, y, Str_Enabled(g_Gui.GetCurrentSettings().Configuration.EnableTargetHighlighter), PRINTSTRING_COLOR_WHITE, SF(titleOption == 5)); + AddString(MenuLeftSideEntry, y, g_GameFlow->GetString(STRING_TARGET_HIGHLIGHTER), PRINTSTRING_COLOR_ORANGE, SF(titleOption == 6)); + AddString(MenuRightSideEntry, y, Str_Enabled(g_Gui.GetCurrentSettings().Configuration.EnableTargetHighlighter), PRINTSTRING_COLOR_WHITE, SF(titleOption == 6)); GetNextLinePosition(&y); - + // Vibration - AddString(MenuLeftSideEntry, y, g_GameFlow->GetString(STRING_RUMBLE), PRINTSTRING_COLOR_ORANGE, SF(titleOption == 6)); - AddString(MenuRightSideEntry, y, Str_Enabled(g_Gui.GetCurrentSettings().Configuration.EnableRumble), PRINTSTRING_COLOR_WHITE, SF(titleOption == 6)); + AddString(MenuLeftSideEntry, y, g_GameFlow->GetString(STRING_RUMBLE), PRINTSTRING_COLOR_ORANGE, SF(titleOption == 7)); + AddString(MenuRightSideEntry, y, Str_Enabled(g_Gui.GetCurrentSettings().Configuration.EnableRumble), PRINTSTRING_COLOR_WHITE, SF(titleOption == 7)); GetNextLinePosition(&y); // Thumbstick camera - AddString(MenuLeftSideEntry, y, g_GameFlow->GetString(STRING_THUMBSTICK_CAMERA), PRINTSTRING_COLOR_ORANGE, SF(titleOption == 7)); - AddString(MenuRightSideEntry, y, Str_Enabled(g_Gui.GetCurrentSettings().Configuration.EnableThumbstickCamera), PRINTSTRING_COLOR_WHITE, SF(titleOption == 7)); + AddString(MenuLeftSideEntry, y, g_GameFlow->GetString(STRING_THUMBSTICK_CAMERA), PRINTSTRING_COLOR_ORANGE, SF(titleOption == 8)); + AddString(MenuRightSideEntry, y, Str_Enabled(g_Gui.GetCurrentSettings().Configuration.EnableThumbstickCamera), PRINTSTRING_COLOR_WHITE, SF(titleOption == 8)); GetNextBlockPosition(&y); // Mouse sensitivity - AddString(MenuLeftSideEntry, y, g_GameFlow->GetString(STRING_MOUSE_SENSITIVITY), PRINTSTRING_COLOR_ORANGE, SF(titleOption == 8)); - AddString(MenuRightSideEntry, y, std::to_string(g_Gui.GetCurrentSettings().Configuration.MouseSensitivity).c_str(), PRINTSTRING_COLOR_WHITE, SF(titleOption == 8)); + AddString(MenuLeftSideEntry, y, g_GameFlow->GetString(STRING_MOUSE_SENSITIVITY), PRINTSTRING_COLOR_ORANGE, SF(titleOption == 9)); + AddString(MenuRightSideEntry, y, std::to_string(g_Gui.GetCurrentSettings().Configuration.MouseSensitivity).c_str(), PRINTSTRING_COLOR_WHITE, SF(titleOption == 9)); GetNextLinePosition(&y); - // Mouse smoothing - AddString(MenuLeftSideEntry, y, g_GameFlow->GetString(STRING_MOUSE_SMOOTHING), PRINTSTRING_COLOR_ORANGE, SF(titleOption == 9)); - AddString(MenuRightSideEntry, y, std::to_string(g_Gui.GetCurrentSettings().Configuration.MouseSmoothing).c_str(), PRINTSTRING_COLOR_WHITE, SF(titleOption == 9)); - GetNextBlockPosition(&y); - // Apply AddString(MenuCenterEntry, y, g_GameFlow->GetString(STRING_APPLY), PRINTSTRING_COLOR_ORANGE, SF_Center(titleOption == 10)); GetNextLinePosition(&y); @@ -488,6 +508,14 @@ namespace TEN::Renderer GetNextLinePosition(&y); selectedOption++; + // Home Level + if (g_GameFlow->IsHomeLevelEnabled()) + { + AddString(MenuCenterEntry, y, g_GameFlow->GetString(STRING_HOME_LEVEL), PRINTSTRING_COLOR_WHITE, SF_Center(titleOption == selectedOption)); + GetNextLinePosition(&y); + selectedOption++; + } + // Load game if (g_GameFlow->IsLoadSaveEnabled()) { @@ -518,13 +546,15 @@ namespace TEN::Renderer AddString(MenuCenterEntry, 26, g_GameFlow->GetString(STRING_SELECT_LEVEL), PRINTSTRING_COLOR_ORANGE, SF_Center()); GetNextBlockPosition(&y); - // Level listing (starts with 1 because 0 is always title) - for (int i = 1; i < g_GameFlow->GetNumLevels(); i++, selectedOption++) + // Level 0 is always Title Level and level 1 might be Home Level. + for (int i = (g_GameFlow->IsHomeLevelEnabled() ? 2 : 1); i < g_GameFlow->GetNumLevels(); i++, selectedOption++) { - AddString(MenuCenterEntry, y, g_GameFlow->GetString(g_GameFlow->GetLevel(i)->NameStringKey.c_str()), + AddString( + MenuCenterEntry, y, g_GameFlow->GetString(g_GameFlow->GetLevel(i)->NameStringKey.c_str()), PRINTSTRING_COLOR_WHITE, SF_Center(titleOption == selectedOption)); GetNextNarrowLinePosition(&y); } + break; case Menu::Options: @@ -537,6 +567,8 @@ namespace TEN::Renderer RenderOptionsMenu(menu, MenuVerticalOptionsTitle); break; } + + DrawAllStrings(); } void Renderer::RenderPauseMenu(Menu menu) @@ -600,7 +632,7 @@ namespace TEN::Renderer char stringBuffer[255]; // Title - AddString(MenuCenterEntry, MenuVerticalNarrowLineSpacing, Str_LoadSave(g_Gui.GetInventoryMode() == InventoryMode::Save), + AddString(MenuCenterEntry, MenuVerticalNarrowLineSpacing, Str_LoadSave(g_Gui.GetInventoryMode() == InventoryMode::Save), PRINTSTRING_COLOR_ORANGE, SF_Center()); GetNextBlockPosition(&y); @@ -713,13 +745,18 @@ namespace TEN::Renderer constexpr auto COUNT_STRING_INF = "Inf"; constexpr auto COUNT_STRING_OFFSET = Vector2(DISPLAY_SPACE_RES.x / 40, 0.0f); + auto pos = Vector2::Lerp(pickup.PrevPosition, pickup.Position, _interpolationFactor); + auto orient = EulerAngles::Lerp(pickup.PrevOrientation, pickup.Orientation, _interpolationFactor); + float scale = Lerp(pickup.PrevScale, pickup.Scale, _interpolationFactor); + float opacity = Lerp(pickup.PrevOpacity, pickup.Opacity, _interpolationFactor); + // Draw display pickup. - DrawObjectIn2DSpace(pickup.ObjectID, pickup.Position, pickup.Orientation, pickup.Scale); + DrawObjectIn2DSpace(pickup.ObjectID, pos, orient, scale); // Draw count string. if (pickup.Count != 1) { - auto countString = (pickup.Count != -1) ? std::to_string(pickup.Count) : COUNT_STRING_INF; + auto countString = (pickup.Count != NO_VALUE) ? std::to_string(pickup.Count) : COUNT_STRING_INF; auto countStringPos = pickup.Position + COUNT_STRING_OFFSET; AddString(countString, countStringPos, Color(PRINTSTRING_COLOR_WHITE), pickup.StringScale, SF()); @@ -793,7 +830,7 @@ namespace TEN::Renderer { if (meshBits && !(meshBits & (1 << n))) continue; - + auto* mesh = (*moveableObject).ObjectMeshes[n]; // HACK: Rotate compass needle. @@ -818,7 +855,7 @@ namespace TEN::Renderer _cbItem.UpdateData(_stItem, _context.Get()); BindConstantBufferVS(ConstantBufferRegister::Item, _cbItem.get()); BindConstantBufferPS(ConstantBufferRegister::Item, _cbItem.get()); - + for (const auto& bucket : mesh->Buckets) { if (bucket.NumVertices == 0) @@ -830,10 +867,10 @@ namespace TEN::Renderer BindTexture(TextureRegister::ColorMap, &std::get<0>(_moveablesTextures[bucket.Texture]), SamplerStateRegister::AnisotropicClamp); BindTexture(TextureRegister::NormalMap, &std::get<1>(_moveablesTextures[bucket.Texture]), SamplerStateRegister::AnisotropicClamp); - + if (bucket.BlendMode != BlendMode::Opaque) Renderer::SetBlendMode(bucket.BlendMode, true); - + SetAlphaTest( (bucket.BlendMode == BlendMode::AlphaTest) ? AlphaTestMode::GreatherThan : AlphaTestMode::None, ALPHA_TEST_THRESHOLD); @@ -871,7 +908,7 @@ namespace TEN::Renderer DrawFullScreenImage(texture.ShaderResourceView.Get(), Smoothstep(currentFade), _backBuffer.RenderTargetView.Get(), _backBuffer.DepthStencilView.Get()); Synchronize(); - _swapChain->Present(0, 0); + _swapChain->Present(1, 0); _context->ClearDepthStencilView(_backBuffer.DepthStencilView.Get(), D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.0f, 0); } } @@ -882,28 +919,29 @@ namespace TEN::Renderer static EulerAngles orient = EulerAngles::Identity; static float scaler = 1.2f; + float multiplier = g_Renderer.GetFramerateMultiplier(); short invItem = g_Gui.GetRing(RingTypes::Inventory).CurrentObjectList[g_Gui.GetRing(RingTypes::Inventory).CurrentObjectInList].InventoryItem; auto& object = InventoryObjectTable[invItem]; if (IsHeld(In::Forward)) - orient.x += ANGLE(3.0f); + orient.x += ANGLE(3.0f / multiplier); if (IsHeld(In::Back)) - orient.x -= ANGLE(3.0f); + orient.x -= ANGLE(3.0f / multiplier); if (IsHeld(In::Left)) - orient.y += ANGLE(3.0f); + orient.y += ANGLE(3.0f / multiplier); if (IsHeld(In::Right)) - orient.y -= ANGLE(3.0f); + orient.y -= ANGLE(3.0f / multiplier); if (IsHeld(In::Sprint)) - scaler += 0.03f; + scaler += 0.03f / multiplier; if (IsHeld(In::Crouch)) - scaler -= 0.03f; + scaler -= 0.03f / multiplier; if (scaler > 1.6f) scaler = 1.6f; @@ -952,6 +990,9 @@ namespace TEN::Renderer _context->RSSetViewports(1, &_viewport); ResetScissor(); + _context->ClearDepthStencilView(_renderTarget.DepthStencilView.Get(), D3D11_CLEAR_STENCIL | D3D11_CLEAR_DEPTH, 1.0f, 0); + _context->ClearRenderTargetView(_renderTarget.RenderTargetView.Get(), Colors::Black); + if (background != nullptr) { DrawFullScreenImage(background->ShaderResourceView.Get(), backgroundFade, _renderTarget.RenderTargetView.Get(), _renderTarget.DepthStencilView.Get()); @@ -1086,7 +1127,7 @@ namespace TEN::Renderer if (ScreenFadeCurrent && percentage > 0.0f && percentage < 100.0f) DrawLoadingBar(percentage); - _swapChain->Present(0, 0); + _swapChain->Present(1, 0); _context->ClearState(); Synchronize(); @@ -1097,209 +1138,226 @@ namespace TEN::Renderer void Renderer::RenderInventory() { + if (_graphicsSettingsChanged) + { + UpdateCameraMatrices(&Camera, BLOCK(g_GameFlow->GetLevel(CurrentLevel)->GetFarView())); + Camera.DisableInterpolation = true; + DumpGameScene(); + } + _context->ClearDepthStencilView(_backBuffer.DepthStencilView.Get(), D3D11_CLEAR_STENCIL | D3D11_CLEAR_DEPTH, 1.0f, 0); _context->ClearRenderTargetView(_backBuffer.RenderTargetView.Get(), Colors::Black); - + + // Reset GPU state. + SetBlendMode(BlendMode::Opaque, true); + SetDepthState(DepthState::Write, true); + SetCullMode(CullMode::CounterClockwise, true); + RenderInventoryScene(&_backBuffer, &_dumpScreenRenderTarget, 0.5f); - - _swapChain->Present(0, 0); + + _swapChain->Present(1, 0); } - void Renderer::RenderTitle() + void Renderer::RenderTitle(float interpFactor) { - RenderScene(&_dumpScreenRenderTarget, false, _gameCamera); + _stringsToDraw.clear(); + _isLocked = false; + + InterpolateCamera(interpFactor); + DumpGameScene(); _context->ClearDepthStencilView(_backBuffer.DepthStencilView.Get(), D3D11_CLEAR_STENCIL | D3D11_CLEAR_DEPTH, 1.0f, 0); _context->ClearRenderTargetView(_backBuffer.RenderTargetView.Get(), Colors::Black); RenderInventoryScene(&_backBuffer, &_dumpScreenRenderTarget, 1.0f); - DrawAllStrings(); + + _swapChain->Present(1, 0); - _swapChain->Present(0, 0); + _isLocked = true; } void Renderer::DrawDebugInfo(RenderView& view) { - if (CurrentLevel != 0) + if (!DebugMode || CurrentLevel == 0) + return; + + _currentLineHeight = DISPLAY_SPACE_RES.y / 30; + + const auto& room = g_Level.Rooms[LaraItem->RoomNumber]; + + float aspectRatio = _screenWidth / (float)_screenHeight; + int thumbWidth = _screenWidth / 6; + auto rect = RECT{}; + int thumbY = 0; + + switch (_debugPage) { - _currentY = 20; + case RendererDebugPage::None: + break; - ROOM_INFO* r = &g_Level.Rooms[LaraItem->RoomNumber]; + case RendererDebugPage::RendererStats: + PrintDebugMessage("RENDERER STATS"); + PrintDebugMessage("FPS: %3.2f", _fps); + PrintDebugMessage("Resolution: %d x %d", _screenWidth, _screenHeight); + PrintDebugMessage("GPU: %s", g_Configuration.AdapterName.c_str()); + PrintDebugMessage("Update time: %d", _timeUpdate); + PrintDebugMessage("Frame time: %d", _timeFrame); + PrintDebugMessage("ControlPhase() time: %d", ControlPhaseTime); + PrintDebugMessage("TOTAL draw calls: %d", _numDrawCalls); + PrintDebugMessage(" Rooms: %d", _numRoomsDrawCalls); + PrintDebugMessage(" Movables: %d", _numMoveablesDrawCalls); + PrintDebugMessage(" Statics: %d", _numStaticsDrawCalls); + PrintDebugMessage(" Instanced Statics: %d", _numInstancedStaticsDrawCalls); + PrintDebugMessage(" Sprites: %d", _numSpritesDrawCalls); + PrintDebugMessage(" Instanced Sprites: %d", _numInstancedSpritesDrawCalls); + PrintDebugMessage("TOTAL triangles: %d", _numTriangles); + PrintDebugMessage("Sprites: %d", view.SpritesToDraw.size()); + PrintDebugMessage("SORTED draw calls: %d", (_numSortedRoomsDrawCalls + _numSortedMoveablesDrawCalls + _numSortedStaticsDrawCalls + _numSortedSpritesDrawCalls)); + PrintDebugMessage(" Rooms: %d", _numSortedRoomsDrawCalls); + PrintDebugMessage(" Movables: %d", _numSortedMoveablesDrawCalls); + PrintDebugMessage(" Statics: %d", _numSortedStaticsDrawCalls); + PrintDebugMessage(" Sprites: %d", _numSortedSpritesDrawCalls); + PrintDebugMessage("SHADOW MAP draw calls: %d", _numShadowMapDrawCalls); + PrintDebugMessage("DEBRIS draw calls: %d", _numDebrisDrawCalls); - float aspectRatio = _screenWidth / (float)_screenHeight; - int thumbWidth = _screenWidth / 6; - RECT rect; - int thumbY = 0; + _spriteBatch->Begin(SpriteSortMode_Deferred, _renderStates->Opaque()); - switch (_debugPage) + rect.left = _screenWidth - thumbWidth; + rect.top = thumbY; + rect.right = rect.left+ thumbWidth; + rect.bottom = rect.top+thumbWidth / aspectRatio; + + _spriteBatch->Draw(_normalsRenderTarget.ShaderResourceView.Get(), rect); + thumbY += thumbWidth / aspectRatio; + + rect.left = _screenWidth - thumbWidth; + rect.top = thumbY; + rect.right = rect.left + thumbWidth; + rect.bottom = rect.top + thumbWidth / aspectRatio; + + rect.left = _screenWidth - thumbWidth; + rect.top = thumbY; + rect.right = rect.left + thumbWidth; + rect.bottom = rect.top + thumbWidth / aspectRatio; + + _spriteBatch->Draw(_SSAOBlurredRenderTarget.ShaderResourceView.Get(), rect); + thumbY += thumbWidth / aspectRatio; + + if (g_Configuration.AntialiasingMode > AntialiasingMode::Low) { - case RendererDebugPage::None: - break; - - case RendererDebugPage::RendererStats: - PrintDebugMessage("RENDERER STATS"); - PrintDebugMessage("FPS: %3.2f", _fps); - PrintDebugMessage("Resolution: %d x %d", _screenWidth, _screenHeight); - PrintDebugMessage("GPU: %s", g_Configuration.AdapterName.c_str()); - PrintDebugMessage("Update time: %d", _timeUpdate); - PrintDebugMessage("Frame time: %d", _timeFrame); - PrintDebugMessage("ControlPhase() time: %d", ControlPhaseTime); - PrintDebugMessage("Room collector time: %d", _timeRoomsCollector); - PrintDebugMessage("TOTAL Draw calls: %d", _numDrawCalls); - PrintDebugMessage(" Rooms: %d", _numRoomsDrawCalls); - PrintDebugMessage(" Movables: %d", _numMoveablesDrawCalls); - PrintDebugMessage(" Statics: %d", _numStaticsDrawCalls); - PrintDebugMessage(" Instanced Statics: %d", _numInstancedStaticsDrawCalls); - PrintDebugMessage(" Sprites: %d", _numSpritesDrawCalls); - PrintDebugMessage(" Instanced Sprites: %d", _numInstancedSpritesDrawCalls); - PrintDebugMessage("TOTAL Triangles: %d", _numTriangles); - PrintDebugMessage("Sprites: %d", view.SpritesToDraw.size()); - PrintDebugMessage("SORTED Draw calls: %d", (_numSortedRoomsDrawCalls + _numSortedMoveablesDrawCalls + _numSortedStaticsDrawCalls - + _numSortedSpritesDrawCalls)); - PrintDebugMessage(" Rooms: %d", _numSortedRoomsDrawCalls); - PrintDebugMessage(" Movables: %d", _numSortedMoveablesDrawCalls); - PrintDebugMessage(" Statics: %d", _numSortedStaticsDrawCalls); - PrintDebugMessage(" Sprites: %d", _numSortedSpritesDrawCalls); - PrintDebugMessage("SHADOW MAPS Draw calls: %d", _numShadowMapDrawCalls); - PrintDebugMessage("DEBRIS Draw calls: %d", _numDebrisDrawCalls); - PrintDebugMessage("Rooms: %d", view.RoomsToDraw.size()); - PrintDebugMessage(" CheckPortal() calls: %d", _numCheckPortalCalls); - PrintDebugMessage(" GetVisibleRooms() calls: %d", _numGetVisibleRoomsCalls); - PrintDebugMessage(" Dot products: %d", _numDotProducts); - - _spriteBatch->Begin(SpriteSortMode_Deferred, _renderStates->Opaque()); - rect.left = _screenWidth - thumbWidth; rect.top = thumbY; - rect.right = rect.left+ thumbWidth; - rect.bottom = rect.top+thumbWidth / aspectRatio; - - _spriteBatch->Draw(_normalsRenderTarget.ShaderResourceView.Get(), rect); + rect.right = rect.left + thumbWidth; + rect.bottom = rect.top + thumbWidth / aspectRatio; + + _spriteBatch->Draw(_SMAAEdgesRenderTarget.ShaderResourceView.Get(), rect); thumbY += thumbWidth / aspectRatio; rect.left = _screenWidth - thumbWidth; rect.top = thumbY; rect.right = rect.left + thumbWidth; rect.bottom = rect.top + thumbWidth / aspectRatio; - - //_spriteBatch->Draw(_depthRenderTarget.ShaderResourceView.Get(), rect); - //thumbY += thumbWidth / aspectRatio; - rect.left = _screenWidth - thumbWidth; - rect.top = thumbY; - rect.right = rect.left + thumbWidth; - rect.bottom = rect.top + thumbWidth / aspectRatio; - - _spriteBatch->Draw(_SSAOBlurredRenderTarget.ShaderResourceView.Get(), rect); + _spriteBatch->Draw(_SMAABlendRenderTarget.ShaderResourceView.Get(), rect); thumbY += thumbWidth / aspectRatio; - - if (g_Configuration.AntialiasingMode > AntialiasingMode::Low) - { - rect.left = _screenWidth - thumbWidth; - rect.top = thumbY; - rect.right = rect.left + thumbWidth; - rect.bottom = rect.top + thumbWidth / aspectRatio; + } - _spriteBatch->Draw(_SMAAEdgesRenderTarget.ShaderResourceView.Get(), rect); - thumbY += thumbWidth / aspectRatio; + _spriteBatch->End(); - rect.left = _screenWidth - thumbWidth; - rect.top = thumbY; - rect.right = rect.left + thumbWidth; - rect.bottom = rect.top + thumbWidth / aspectRatio; + break; - _spriteBatch->Draw(_SMAABlendRenderTarget.ShaderResourceView.Get(), rect); - thumbY += thumbWidth / aspectRatio; - } + case RendererDebugPage::DimensionStats: + PrintDebugMessage("DIMENSION STATS"); + PrintDebugMessage("Position: %d, %d, %d", LaraItem->Pose.Position.x, LaraItem->Pose.Position.y, LaraItem->Pose.Position.z); + PrintDebugMessage("Orientation: %d, %d, %d", LaraItem->Pose.Orientation.x, LaraItem->Pose.Orientation.y, LaraItem->Pose.Orientation.z); + PrintDebugMessage("RoomNumber: %d", LaraItem->RoomNumber); + PrintDebugMessage("PathfindingBoxID: %d", LaraItem->BoxNumber); + PrintDebugMessage((Lara.Context.WaterSurfaceDist == -NO_HEIGHT ? "WaterSurfaceDist: N/A" : "WaterSurfaceDist: %d"), Lara.Context.WaterSurfaceDist); + PrintDebugMessage("Room Position: %d, %d, %d, %d", room.Position.z, room.Position.z, room.Position.z + BLOCK(room.XSize), room.Position.z + BLOCK(room.ZSize)); + PrintDebugMessage("Room.y, minFloor, maxCeiling: %d, %d, %d ", room.Position.y, room.BottomHeight, room.TopHeight); + PrintDebugMessage("Camera Position: %d, %d, %d", Camera.pos.x, Camera.pos.y, Camera.pos.z); + PrintDebugMessage("Camera LookAt: %d, %d, %d", Camera.target.x, Camera.target.y, Camera.target.z); + PrintDebugMessage("Camera RoomNumber: %d", Camera.pos.RoomNumber); + break; - _spriteBatch->End(); + case RendererDebugPage::PlayerStats: + PrintDebugMessage("PLAYER STATS"); + PrintDebugMessage("AnimObjectID: %d", LaraItem->Animation.AnimObjectID); + PrintDebugMessage("AnimNumber: %d", LaraItem->Animation.AnimNumber - Objects[LaraItem->Animation.AnimObjectID].animIndex); + PrintDebugMessage("FrameNumber: %d", LaraItem->Animation.FrameNumber - GetAnimData(LaraItem).frameBase); + PrintDebugMessage("ActiveState: %d", LaraItem->Animation.ActiveState); + PrintDebugMessage("TargetState: %d", LaraItem->Animation.TargetState); + PrintDebugMessage("Velocity: %.3f, %.3f, %.3f", LaraItem->Animation.Velocity.z, LaraItem->Animation.Velocity.y, LaraItem->Animation.Velocity.x); + PrintDebugMessage("IsAirborne: %d", LaraItem->Animation.IsAirborne); + PrintDebugMessage("HandStatus: %d", Lara.Control.HandStatus); + PrintDebugMessage("WaterStatus: %d", Lara.Control.WaterStatus); + PrintDebugMessage("CanClimbLadder: %d", Lara.Control.CanClimbLadder); + PrintDebugMessage("CanMonkeySwing: %d", Lara.Control.CanMonkeySwing); + PrintDebugMessage("Target HitPoints: %d", Lara.TargetEntity ? Lara.TargetEntity->HitPoints : 0); + break; - break; + case RendererDebugPage::InputStats: + { + auto clickedActions = BitField((int)In::Count); + auto heldActions = BitField((int)In::Count); + auto releasedActions = BitField((int)In::Count); - case RendererDebugPage::DimensionStats: - PrintDebugMessage("DIMENSION STATS"); - PrintDebugMessage("Pos: %d %d %d", LaraItem->Pose.Position.x, LaraItem->Pose.Position.y, LaraItem->Pose.Position.z); - PrintDebugMessage("Orient: %d %d %d", LaraItem->Pose.Orientation.x, LaraItem->Pose.Orientation.y, LaraItem->Pose.Orientation.z); - PrintDebugMessage("RoomNumber: %d", LaraItem->RoomNumber); - PrintDebugMessage("Room location: %d %d", LaraItem->Location.RoomNumber, LaraItem->Location.Height); - PrintDebugMessage("BoxNumber: %d", LaraItem->BoxNumber); - PrintDebugMessage("WaterSurfaceDist: %d", Lara.Context.WaterSurfaceDist); - PrintDebugMessage("Room: %d %d %d %d", r->x, r->z, r->x + r->xSize * BLOCK(1), r->z + r->zSize * BLOCK(1)); - PrintDebugMessage("Room.y, minFloor, maxCeiling: %d %d %d ", r->y, r->minfloor, r->maxceiling); - PrintDebugMessage("Camera.pos: %d %d %d", Camera.pos.x, Camera.pos.y, Camera.pos.z); - PrintDebugMessage("Camera.target: %d %d %d", Camera.target.x, Camera.target.y, Camera.target.z); - PrintDebugMessage("Camera.RoomNumber: %d", Camera.pos.RoomNumber); - break; - - case RendererDebugPage::PlayerStats: - PrintDebugMessage("PLAYER STATS"); - PrintDebugMessage("AnimObjectID: %d", LaraItem->Animation.AnimObjectID); - PrintDebugMessage("AnimNumber: %d", LaraItem->Animation.AnimNumber); - PrintDebugMessage("FrameNumber: %d", LaraItem->Animation.FrameNumber); - PrintDebugMessage("ActiveState: %d", LaraItem->Animation.ActiveState); - PrintDebugMessage("TargetState: %d", LaraItem->Animation.TargetState); - PrintDebugMessage("Velocity: %.3f %.3f %.3f", LaraItem->Animation.Velocity.z, LaraItem->Animation.Velocity.y, LaraItem->Animation.Velocity.x); - PrintDebugMessage("IsAirborne: %d", LaraItem->Animation.IsAirborne); - PrintDebugMessage("HandStatus: %d", Lara.Control.HandStatus); - PrintDebugMessage("WaterStatus: %d", Lara.Control.WaterStatus); - PrintDebugMessage("CanClimbLadder: %d", Lara.Control.CanClimbLadder); - PrintDebugMessage("CanMonkeySwing: %d", Lara.Control.CanMonkeySwing); - PrintDebugMessage("Target HitPoints: %d", Lara.TargetEntity ? Lara.TargetEntity->HitPoints : 0); - break; - - case RendererDebugPage::InputStats: + for (const auto& action : ActionMap) { - auto clickedActions = BitField((int)In::Count); - auto heldActions = BitField((int)In::Count); - auto releasedActions = BitField((int)In::Count); + if (action.IsClicked()) + clickedActions.Set((int)action.GetID()); - for (const auto& action : ActionMap) - { - if (action.IsClicked()) - clickedActions.Set((int)action.GetID()); + if (action.IsHeld()) + heldActions.Set((int)action.GetID()); - if (action.IsHeld()) - heldActions.Set((int)action.GetID()); - - if (action.IsReleased()) - releasedActions.Set((int)action.GetID()); - } - - PrintDebugMessage("INPUT STATS"); - PrintDebugMessage(("Clicked actions: " + clickedActions.ToString()).c_str()); - PrintDebugMessage(("Held actions: " + heldActions.ToString()).c_str()); - PrintDebugMessage(("Released actions: " + releasedActions.ToString()).c_str()); - PrintDebugMessage("Move axes: %.3f, %.3f", AxisMap[(int)InputAxis::Move].x, AxisMap[(int)InputAxis::Move].y); - PrintDebugMessage("Camera axes: %.3f, %.3f", AxisMap[(int)InputAxis::Camera].x, AxisMap[(int)InputAxis::Camera].y); - PrintDebugMessage("Mouse axes: %.3f, %.3f", AxisMap[(int)InputAxis::Mouse].x, AxisMap[(int)InputAxis::Mouse].y); - PrintDebugMessage("Cursor pos: %.3f, %.3f", GetMouse2DPosition().x, GetMouse2DPosition().y); + if (action.IsReleased()) + releasedActions.Set((int)action.GetID()); } - break; - case RendererDebugPage::CollisionStats: - PrintDebugMessage("COLLISION STATS"); - PrintDebugMessage("Collision type: %d", LaraCollision.CollisionType); - PrintDebugMessage("Bridge item ID: %d", LaraCollision.Middle.Bridge); - PrintDebugMessage("Front floor: %d", LaraCollision.Front.Floor); - PrintDebugMessage("Front left floor: %d", LaraCollision.FrontLeft.Floor); - PrintDebugMessage("Front right floor: %d", LaraCollision.FrontRight.Floor); - PrintDebugMessage("Front ceil: %d", LaraCollision.Front.Ceiling); - PrintDebugMessage("Front left ceil: %d", LaraCollision.FrontLeft.Ceiling); - PrintDebugMessage("Front right ceil: %d", LaraCollision.FrontRight.Ceiling); - break; - - case RendererDebugPage::PathfindingStats: - PrintDebugMessage("PATHFINDING STATS"); - PrintDebugMessage("BoxNumber: %d", LaraItem->BoxNumber); - break; - - case RendererDebugPage::WireframeMode: - PrintDebugMessage("WIREFRAME MODE"); - break; + PrintDebugMessage("INPUT STATS"); + PrintDebugMessage(("Clicked actions: " + clickedActions.ToString()).c_str()); + PrintDebugMessage(("Held actions: " + heldActions.ToString()).c_str()); + PrintDebugMessage(("Released actions: " + releasedActions.ToString()).c_str()); + PrintDebugMessage("Move axes: %.3f, %.3f", AxisMap[(int)InputAxis::Move].x, AxisMap[(int)InputAxis::Move].y); + PrintDebugMessage("Camera axes: %.3f, %.3f", AxisMap[(int)InputAxis::Camera].x, AxisMap[(int)InputAxis::Camera].y); + PrintDebugMessage("Mouse axes: %.3f, %.3f", AxisMap[(int)InputAxis::Mouse].x, AxisMap[(int)InputAxis::Mouse].y); + PrintDebugMessage("Cursor pos: %.3f, %.3f", GetMouse2DPosition().x, GetMouse2DPosition().y); + } + break; - default: - break; - } + case RendererDebugPage::CollisionStats: + PrintDebugMessage("COLLISION STATS"); + PrintDebugMessage("Collision type: %d", LaraCollision.CollisionType); + PrintDebugMessage("Bridge item ID: %d", LaraCollision.Middle.Bridge); + PrintDebugMessage("Front floor: %d", LaraCollision.Front.Floor); + PrintDebugMessage("Front left floor: %d", LaraCollision.FrontLeft.Floor); + PrintDebugMessage("Front right floor: %d", LaraCollision.FrontRight.Floor); + PrintDebugMessage("Front ceil: %d", LaraCollision.Front.Ceiling); + PrintDebugMessage("Front left ceil: %d", LaraCollision.FrontLeft.Ceiling); + PrintDebugMessage("Front right ceil: %d", LaraCollision.FrontRight.Ceiling); + break; + + case RendererDebugPage::PathfindingStats: + PrintDebugMessage("PATHFINDING STATS"); + PrintDebugMessage("BoxNumber: %d", LaraItem->BoxNumber); + break; + + case RendererDebugPage::WireframeMode: + PrintDebugMessage("WIREFRAME MODE"); + break; + + case RendererDebugPage::PortalDebug: + PrintDebugMessage("PORTAL DEBUG"); + PrintDebugMessage("Camera RoomNumber: %d", Camera.pos.RoomNumber); + PrintDebugMessage("Room collector time: %d", _timeRoomsCollector); + PrintDebugMessage("Rooms: %d", view.RoomsToDraw.size()); + PrintDebugMessage(" CheckPortal() calls: %d", _numCheckPortalCalls); + PrintDebugMessage(" GetVisibleRooms() calls: %d", _numGetVisibleRoomsCalls); + PrintDebugMessage(" Dot products: %d", _numDotProducts); + break; + + default: + break; } } @@ -1312,7 +1370,7 @@ namespace TEN::Renderer { page = (int)RendererDebugPage::Count - 1; } - else if (page > (int)RendererDebugPage::WireframeMode) + else if (page >= (int)RendererDebugPage::Count) { page = (int)RendererDebugPage::None; } diff --git a/TombEngine/Renderer/RendererEnums.h b/TombEngine/Renderer/RendererEnums.h index 5d46489ce..c159c1c42 100644 --- a/TombEngine/Renderer/RendererEnums.h +++ b/TombEngine/Renderer/RendererEnums.h @@ -64,6 +64,7 @@ constexpr auto MAX_ITEMS_DRAW = 128; constexpr auto MAX_LIGHTS_DRAW = 48; constexpr auto MAX_FOG_BULBS_DRAW = 32; constexpr auto MAX_SPRITES_DRAW = 512; +constexpr auto MAX_LENS_FLARES_DRAW = 8; constexpr auto ROOM_AMBIENT_MAP_SIZE = 32; constexpr auto MAX_ROOM_AMBIENT_MAPS = 10; @@ -147,6 +148,7 @@ enum class RendererDebugPage InputStats, CollisionStats, PathfindingStats, + PortalDebug, WireframeMode, Count diff --git a/TombEngine/Renderer/RendererFrame.cpp b/TombEngine/Renderer/RendererFrame.cpp index 525a4631f..64d6bea6e 100644 --- a/TombEngine/Renderer/RendererFrame.cpp +++ b/TombEngine/Renderer/RendererFrame.cpp @@ -1,19 +1,22 @@ #include "framework.h" #include "Renderer/Renderer.h" -#include "Scripting/Include/Flow/ScriptInterfaceFlowHandler.h" #include "Game/animation.h" #include "Game/camera.h" -#include "Game/collision/sphere.h" +#include "Game/collision/Sphere.h" #include "Game/effects/effects.h" #include "Game/items.h" #include "Game/Lara/lara.h" -#include "Game/spotcam.h" #include "Game/Setup.h" +#include "Game/spotcam.h" #include "Math/Math.h" -#include "Specific/level.h" +#include "Objects/Effects/LensFlare.h" #include "Renderer/RenderView.h" +#include "Scripting/Include/Flow/ScriptInterfaceFlowHandler.h" +#include "Specific/level.h" +using namespace TEN::Entities::Effects; +using namespace TEN::Collision::Sphere; using namespace TEN::Math; namespace TEN::Renderer @@ -27,10 +30,10 @@ namespace TEN::Renderer _visitedRoomsStack.clear(); for (int i = 0; i < g_Level.Rooms.size(); i++) - { + { auto& room = _rooms[i]; - - room.ItemsToDraw.clear(); + + room.ItemsToDraw.clear(); room.EffectsToDraw.clear(); room.StaticsToDraw.clear(); room.LightsToDraw.clear(); @@ -47,7 +50,7 @@ namespace TEN::Renderer GetVisibleRooms(NO_VALUE, renderView.Camera.RoomNumber, VIEW_PORT, false, 0, onlyRooms, renderView); - _invalidateCache = false; + _invalidateCache = false; // Prepare real DX scissor test rectangle. for (auto* roomPtr : renderView.RoomsToDraw) @@ -56,17 +59,17 @@ namespace TEN::Renderer roomPtr->ClipBounds.Bottom = (1.0f - roomPtr->ViewPort.y) * _screenHeight * 0.5f; roomPtr->ClipBounds.Right = (roomPtr->ViewPort.z + 1.0f) * _screenWidth * 0.5f; roomPtr->ClipBounds.Top = (1.0f - roomPtr->ViewPort.w) * _screenHeight * 0.5f; - } + } // Collect fog bulbs. std::vector tempFogBulbs; tempFogBulbs.reserve(MAX_FOG_BULBS_DRAW); - for (auto& room : _rooms) + for (auto& room : _rooms) { if (!g_Level.Rooms[room.RoomNumber].Active()) continue; - + for (const auto& light : room.Lights) { if (light.Type != LightType::FogBulb) @@ -76,7 +79,7 @@ namespace TEN::Renderer if (renderView.Camera.Frustum.SphereInFrustum(light.Position, light.Out * 1.2f)) { RendererFogBulb bulb; - + bulb.Position = light.Position; bulb.Density = light.Intensity; bulb.Color = light.Color; @@ -88,11 +91,9 @@ namespace TEN::Renderer } } } - - // Sort fog bulbs. + std::sort( - tempFogBulbs.begin(), - tempFogBulbs.end(), + tempFogBulbs.begin(), tempFogBulbs.end(), [](const RendererFogBulb& bulb0, const RendererFogBulb& bulb1) { return bulb0.Distance < bulb1.Distance; @@ -100,6 +101,52 @@ namespace TEN::Renderer for (int i = 0; i < std::min(MAX_FOG_BULBS_DRAW, (int)tempFogBulbs.size()); i++) renderView.FogBulbsToDraw.push_back(tempFogBulbs[i]); + + // Collect lens flares. + auto tempLensFlares = std::vector{}; + tempLensFlares.reserve(MAX_LENS_FLARES_DRAW); + + for (const auto& lensFlare : LensFlares) + { + auto lensFlareToCamera = lensFlare.Position - renderView.Camera.WorldPosition; + + float dist = 0.0f; + if (!lensFlare.IsGlobal) + dist = lensFlareToCamera.Length(); + lensFlareToCamera.Normalize(); + + auto cameraDir = renderView.Camera.WorldDirection; + cameraDir.Normalize(); + + if (lensFlareToCamera.Dot(cameraDir) >= 0.0f) + { + auto lensFlareToDraw = RendererLensFlare{}; + lensFlareToDraw.Position = lensFlare.Position; + lensFlareToDraw.Distance = dist; + lensFlareToDraw.Color = lensFlare.Color; + lensFlareToDraw.SpriteID = lensFlare.SpriteID; + lensFlareToDraw.Direction = lensFlareToCamera; + lensFlareToDraw.IsGlobal = lensFlare.IsGlobal; + + tempLensFlares.push_back(lensFlareToDraw); + } + } + + std::sort( + tempLensFlares.begin(), tempLensFlares.end(), + [](const RendererLensFlare& lensFlare0, const RendererLensFlare& lensFlare1) + { + if (lensFlare0.IsGlobal && !lensFlare1.IsGlobal) + return true; + + if (!lensFlare0.IsGlobal && lensFlare1.IsGlobal) + return false; + + return (lensFlare0.Distance < lensFlare1.Distance); + }); + + for (int i = 0; i < std::min(MAX_LENS_FLARES_DRAW, (int)tempLensFlares.size()); i++) + renderView.LensFlaresToDraw.push_back(tempLensFlares[i]); } bool Renderer::CheckPortal(short parentRoomNumber, RendererDoor* door, Vector4 viewPort, Vector4* clipPort, RenderView& renderView) @@ -147,16 +194,14 @@ namespace TEN::Renderer door->Visited = true; if (zClip == 4) - { return false; - } if (zClip > 0) { for (int i = 0; i < 4; i++) { - Vector4 a = p[i]; - Vector4 b = p[(i + 1) % 4]; + auto a = p[i]; + auto b = p[(i + 1) % 4]; if ((a.w > 0.0f) ^ (b.w > 0.0f)) { @@ -198,7 +243,10 @@ namespace TEN::Renderer } } - if (clipPort->x > viewPort.z || clipPort->y > viewPort.w || clipPort->z < viewPort.x || clipPort->w < viewPort.y) + if (clipPort->x > viewPort.z || + clipPort->y > viewPort.w || + clipPort->z < viewPort.x || + clipPort->w < viewPort.y) { return false; } @@ -224,7 +272,7 @@ namespace TEN::Renderer { if (_visitedRoomsStack[i] == to) { - TENLog("Circle detected! Room " + std::to_string(to), LogLevel::Warning, LogConfig::Debug); + TENLog("Circle detected in room " + std::to_string(to), LogLevel::Warning, LogConfig::Debug); return; } } @@ -232,8 +280,9 @@ namespace TEN::Renderer static constexpr int MAX_SEARCH_DEPTH = 64; if (_rooms[to].Visited && count > MAX_SEARCH_DEPTH) { - TENLog("Maximum room collection depth of " + std::to_string(MAX_SEARCH_DEPTH) + - " was reached with room " + std::to_string(to), LogLevel::Warning, LogConfig::Debug); + TENLog( + "Maximum room collection depth of " + std::to_string(MAX_SEARCH_DEPTH) + " was reached with room " + std::to_string(to), + LogLevel::Warning, LogConfig::Debug); return; } @@ -241,7 +290,7 @@ namespace TEN::Renderer _numGetVisibleRoomsCalls++; - RendererRoom* room = &_rooms[to]; + auto* room = &_rooms[to]; if (!room->Visited) { @@ -267,12 +316,10 @@ namespace TEN::Renderer Vector4 clipPort; for (int i = 0; i < room->Doors.size(); i++) { - RendererDoor* door = &room->Doors[i]; + auto* door = &room->Doors[i]; if (door->InvisibleFromCamera) - { continue; - } if (!door->Visited) { @@ -311,49 +358,35 @@ namespace TEN::Renderer void Renderer::CollectItems(short roomNumber, RenderView& renderView) { if (_rooms.size() < roomNumber) - { return; - } - RendererRoom& room = _rooms[roomNumber]; - ROOM_INFO* r = &g_Level.Rooms[room.RoomNumber]; + auto& room = _rooms[roomNumber]; + auto* r = &g_Level.Rooms[room.RoomNumber]; short itemNum = NO_VALUE; for (itemNum = r->itemNumber; itemNum != NO_VALUE; itemNum = g_Level.Items[itemNum].NextItem) { - ItemInfo* item = &g_Level.Items[itemNum]; + auto* item = &g_Level.Items[itemNum]; if (item->ObjectNumber == ID_LARA && itemNum == g_Level.Items[itemNum].NextItem) - { break; - } if (item->Status == ITEM_INVISIBLE) - { continue; - } if (item->ObjectNumber == ID_LARA && (Lara.Control.Look.OpticRange || SpotcamOverlay || SpotcamDontDrawLara)) - { continue; - } if (item->ObjectNumber == ID_LARA && CurrentLevel == 0 && !g_GameFlow->IsLaraInTitleEnabled()) - { continue; - } if (!_moveableObjects[item->ObjectNumber].has_value()) - { continue; - } auto& obj = _moveableObjects[item->ObjectNumber].value(); if (obj.DoNotDraw) - { continue; - } // Clip object by frustum only if it doesn't cast shadows. Otherwise we may see // disappearing shadows if object gets out of frustum. @@ -361,15 +394,14 @@ namespace TEN::Renderer if (obj.ShadowType == ShadowMode::None) { // Get all spheres and check if frustum intersects any of them. - static BoundingSphere spheres[MAX_BONES]; - int cnt = GetSpheres(itemNum, spheres, SPHERES_SPACE_WORLD, Matrix::Identity); + auto spheres = GetSpheres(itemNum); bool inFrustum = false; - for (int i = 0; !inFrustum, i < cnt; i++) + for (int i = 0; !inFrustum, i < spheres.size(); i++) // Blow up sphere radius by half for cases of too small calculated spheres. if (renderView.Camera.Frustum.SphereInFrustum(spheres[i].Center, spheres[i].Radius * 1.5f)) inFrustum = true; - + if (!inFrustum) continue; } @@ -377,14 +409,41 @@ namespace TEN::Renderer auto newItem = &_items[itemNum]; newItem->ItemNumber = itemNum; - newItem->ObjectNumber = item->ObjectNumber; + newItem->ObjectID = item->ObjectNumber; newItem->Color = item->Model.Color; newItem->Position = item->Pose.Position.ToVector3(); - newItem->Translation = Matrix::CreateTranslation(item->Pose.Position.x, item->Pose.Position.y, item->Pose.Position.z); + newItem->Translation = Matrix::CreateTranslation(newItem->Position.x, newItem->Position.y, newItem->Position.z); newItem->Rotation = item->Pose.Orientation.ToRotationMatrix(); newItem->Scale = Matrix::CreateScale(1.0f); newItem->World = newItem->Rotation * newItem->Translation; + // Disable interpolation either when renderer slot or item slot has flag. + // Renderer slot has no interpolation flag set in case it is fetched for the first time (e.g. item first time in frustum). + newItem->DisableInterpolation = item->DisableInterpolation || newItem->DisableInterpolation; + + if (newItem->DisableInterpolation) + { + // NOTE: Interpolation alwasy returns same result. + newItem->PrevPosition = newItem->Position; + newItem->PrevTranslation = newItem->Translation; + newItem->PrevRotation = newItem->Rotation; + newItem->PrevWorld = newItem->World; + + // Otherwise all frames until the next ControlPhase will not be interpolated. + newItem->DisableInterpolation = false; + + for (int j = 0; j < MAX_BONES; j++) + newItem->PrevAnimTransforms[j] = newItem->AnimTransforms[j]; + } + + newItem->InterpolatedPosition = Vector3::Lerp(newItem->PrevPosition, newItem->Position, _interpolationFactor); + newItem->InterpolatedTranslation = Matrix::Lerp(newItem->PrevTranslation, newItem->Translation, _interpolationFactor); + newItem->InterpolatedRotation = Matrix::Lerp(newItem->InterpolatedRotation, newItem->Rotation, _interpolationFactor); + newItem->InterpolatedWorld = Matrix::Lerp(newItem->PrevWorld, newItem->World, _interpolationFactor); + + for (int j = 0; j < MAX_BONES; j++) + newItem->InterpolatedAnimTransforms[j] = Matrix::Lerp(newItem->PrevAnimTransforms[j], newItem->AnimTransforms[j], _interpolationFactor); + CalculateLightFades(newItem); CollectLightsForItem(newItem); @@ -395,22 +454,18 @@ namespace TEN::Renderer void Renderer::CollectStatics(short roomNumber, RenderView& renderView) { if (_rooms.size() < roomNumber) - { return; - } - RendererRoom& room = _rooms[roomNumber]; - ROOM_INFO* r = &g_Level.Rooms[room.RoomNumber]; + auto& room = _rooms[roomNumber]; + auto* r = &g_Level.Rooms[room.RoomNumber]; if (r->mesh.empty()) - { return; - } for (int i = 0; i < room.Statics.size(); i++) { auto* mesh = &room.Statics[i]; - MESH_INFO* nativeMesh = &r->mesh[i]; + auto* nativeMesh = &r->mesh[i]; if (nativeMesh->Dirty || _invalidateCache) { @@ -425,26 +480,18 @@ namespace TEN::Renderer } if (!(nativeMesh->flags & StaticMeshFlags::SM_VISIBLE)) - { continue; - } if (!_staticObjects[mesh->ObjectNumber].has_value()) - { continue; - } auto& obj = *_staticObjects[mesh->ObjectNumber]; if (obj.ObjectMeshes.empty()) - { continue; - } if (!renderView.Camera.Frustum.SphereInFrustum(mesh->VisibilityBox.Center, mesh->VisibilitySphereRadius)) - { continue; - } // Collect the lights std::vector lights; @@ -481,15 +528,13 @@ namespace TEN::Renderer void Renderer::CollectLights(Vector3 position, float radius, int roomNumber, int prevRoomNumber, bool prioritizeShadowLight, bool useCachedRoomLights, std::vector* roomsLights, std::vector* outputLights) { if (_rooms.size() < roomNumber) - { return; - } // Now collect lights from dynamic list and from rooms std::vector tempLights; tempLights.reserve(MAX_LIGHTS_DRAW); - RendererRoom& room = _rooms[roomNumber]; + auto& room = _rooms[roomNumber]; RendererLight* brightestLight = nullptr; float brightest = 0.0f; @@ -497,24 +542,20 @@ namespace TEN::Renderer // Dynamic lights have the priority for (auto& light : _dynamicLights) { - float distanceSquared = + float distSqr = SQUARE(position.x - light.Position.x) + SQUARE(position.y - light.Position.y) + SQUARE(position.z - light.Position.z); // Collect only lights nearer than 20 sectors - if (distanceSquared >= SQUARE(BLOCK(20))) - { + if (distSqr >= SQUARE(BLOCK(20))) continue; - } // Check the out radius - if (distanceSquared > SQUARE(light.Out + radius)) - { + if (distSqr > SQUARE(light.Out + radius)) continue; - } - float distance = sqrt(distanceSquared); + float distance = sqrt(distSqr); float attenuation = 1.0f - distance / light.Out; float intensity = attenuation * light.Intensity * light.Luma; @@ -524,55 +565,49 @@ namespace TEN::Renderer if (!useCachedRoomLights) { - // Check current room and also neighbour rooms + // Check current room and neighbor rooms. for (int roomToCheck : room.Neighbors) { - RendererRoom& currentRoom = _rooms[roomToCheck]; - int numLights = (int)currentRoom.Lights.size(); + auto& currentRoom = _rooms[roomToCheck]; + int lightCount = (int)currentRoom.Lights.size(); - for (int j = 0; j < numLights; j++) + for (int j = 0; j < lightCount; j++) { - RendererLight* light = ¤tRoom.Lights[j]; + auto* light = ¤tRoom.Lights[j]; float intensity = 0; - float distance = 0; + float dist = 0; - // Check only lights different from sun + // Check only lights different from sun. if (light->Type == LightType::Sun) { - // Suns from non-adjacent rooms are not added! + // Suns from non-adjacent rooms not added. if (roomToCheck != roomNumber && (prevRoomNumber != roomToCheck || prevRoomNumber == NO_VALUE)) - { continue; - } - // Sun is added without distance checks + // Sun is added without distance checks. intensity = light->Intensity * Luma(light->Color); } else if (light->Type == LightType::Point || light->Type == LightType::Shadow) { - float distanceSquared = + float distSqr = SQUARE(position.x - light->Position.x) + SQUARE(position.y - light->Position.y) + SQUARE(position.z - light->Position.z); - // Collect only lights nearer than 20 sectors - if (distanceSquared >= SQUARE(BLOCK(20))) - { + // Collect only lights nearer than 20 blocks. + if (distSqr >= SQUARE(BLOCK(20))) continue; - } - // Check the out radius - if (distanceSquared > SQUARE(light->Out + radius)) - { + // Check out radius. + if (distSqr > SQUARE(light->Out + radius)) continue; - } - distance = sqrt(distanceSquared); - float attenuation = 1.0f - distance / light->Out; + dist = sqrt(distSqr); + float attenuation = 1.0f - dist / light->Out; intensity = attenuation * light->Intensity * Luma(light->Color); - // If collecting shadows, try to collect shadow casting light + // If collecting shadows, try collecting shadow-casting light. if (light->CastShadows && prioritizeShadowLight && light->Type == LightType::Point) { if (intensity >= brightest) @@ -584,28 +619,24 @@ namespace TEN::Renderer } else if (light->Type == LightType::Spot) { - float distanceSquared = + float distSqr = SQUARE(position.x - light->Position.x) + SQUARE(position.y - light->Position.y) + SQUARE(position.z - light->Position.z); - // Collect only lights nearer than 20 sectors - if (distanceSquared >= SQUARE(BLOCK(20))) - { + // Collect only lights nearer than 20 blocks. + if (distSqr >= SQUARE(BLOCK(20))) continue; - } - // Check the range - if (distanceSquared > SQUARE(light->Out + radius)) - { + // Check range. + if (distSqr > SQUARE(light->Out + radius)) continue; - } - distance = sqrt(distanceSquared); - float attenuation = 1.0f - distance / light->Out; + dist = sqrt(distSqr); + float attenuation = 1.0f - dist / light->Out; intensity = attenuation * light->Intensity * light->Luma; - // If shadow pointer provided, try to collect shadow casting light + // If shadow pointer provided, try collecting shadow-casting light. if (light->CastShadows && prioritizeShadowLight) { if (intensity >= brightest) @@ -617,16 +648,14 @@ namespace TEN::Renderer } else { - // Invalid light type + // Invalid light type. continue; } - RendererLightNode node = { light, intensity, distance, 0 }; + RendererLightNode node = { light, intensity, dist, 0 }; if (roomsLights != nullptr) - { roomsLights->push_back(node); - } tempLights.push_back(node); } @@ -635,12 +664,10 @@ namespace TEN::Renderer else { for (int i = 0; i < roomsLights->size(); i++) - { tempLights.push_back(roomsLights->at(i)); - } } - // Sort lights + // Sort lights. if (tempLights.size() > MAX_LIGHTS_PER_ITEM) { std::sort( @@ -649,36 +676,33 @@ namespace TEN::Renderer [](RendererLightNode a, RendererLightNode b) { if (a.Dynamic == b.Dynamic) - return a.LocalIntensity > b.LocalIntensity; + { + return (a.LocalIntensity > b.LocalIntensity); + } else - return a.Dynamic > b.Dynamic; - } - ); + { + return (a.Dynamic > b.Dynamic); + } + }); } - // Now put actual lights to provided vector + // Put actual lights in provided vector. outputLights->clear(); - // Always add brightest light, if collecting shadow light is specified, even if it's far in range + // Add brightest ligh, if collecting shadow light is specified, even if it's far in range. if (prioritizeShadowLight && brightestLight) - { outputLights->push_back(brightestLight); - } - // Add max 8 lights per item, including the shadow light for Lara eventually + // Add max 8 lights per item, including shadow light for player eventually. for (auto l : tempLights) { if (prioritizeShadowLight && brightestLight == l.Light) - { continue; - } outputLights->push_back(l.Light); if (outputLights->size() == MAX_LIGHTS_PER_ITEM) - { break; - } } } @@ -809,28 +833,66 @@ namespace TEN::Renderer RendererEffect *newEffect = &_effects[fxNum]; - Matrix translation = Matrix::CreateTranslation(fx->pos.Position.x, fx->pos.Position.y, fx->pos.Position.z); - Matrix rotation = fx->pos.Orientation.ToRotationMatrix(); - - newEffect->ObjectNumber = fx->objectNumber; + newEffect->Translation = Matrix::CreateTranslation(fx->pos.Position.x, fx->pos.Position.y, fx->pos.Position.z); + newEffect->Rotation = fx->pos.Orientation.ToRotationMatrix(); + newEffect->Scale = Matrix::CreateScale(1.0f); + newEffect->World = newEffect->Rotation * newEffect->Translation; + newEffect->ObjectID = fx->objectNumber; newEffect->RoomNumber = fx->roomNumber; newEffect->Position = fx->pos.Position.ToVector3(); newEffect->AmbientLight = room.AmbientLight; newEffect->Color = fx->color; - newEffect->World = rotation * translation; newEffect->Mesh = GetMesh(obj->nmeshes ? obj->meshIndex : fx->frameNumber); + if (fx->DisableInterpolation) + { + // In this way the interpolation will return always the same result + newEffect->PrevPosition = newEffect->Position; + newEffect->PrevTranslation = newEffect->Translation; + newEffect->PrevRotation = newEffect->Rotation; + newEffect->PrevWorld = newEffect->World; + newEffect->PrevScale = newEffect->Scale; + } + + newEffect->InterpolatedPosition = Vector3::Lerp(newEffect->PrevPosition, newEffect->Position, _interpolationFactor); + newEffect->InterpolatedTranslation = Matrix::Lerp(newEffect->PrevTranslation, newEffect->Translation, _interpolationFactor); + newEffect->InterpolatedRotation = Matrix::Lerp(newEffect->InterpolatedRotation, newEffect->Rotation, _interpolationFactor); + newEffect->InterpolatedWorld = Matrix::Lerp(newEffect->PrevWorld, newEffect->World, _interpolationFactor); + newEffect->InterpolatedScale = Matrix::Lerp(newEffect->PrevScale, newEffect->Scale, _interpolationFactor); + CollectLightsForEffect(fx->roomNumber, newEffect); room.EffectsToDraw.push_back(newEffect); } } - void Renderer::ResetAnimations() + void Renderer::ResetItems() { for (int i = 0; i < ITEM_COUNT_MAX; i++) _items[i].DoneAnimations = false; } -} // namespace TEN::Renderer + void Renderer::SaveOldState() + { + for (int i = 0; i < g_Level.Items.size(); i++) + { + _items[i].PrevPosition = _items[i].Position; + _items[i].PrevWorld = _items[i].World; + _items[i].PrevTranslation = _items[i].Translation; + _items[i].PrevRotation = _items[i].Rotation; + _items[i].PrevScale = _items[i].Scale; + for (int j = 0; j < MAX_BONES; j++) + _items[i].PrevAnimTransforms[j] = _items[i].AnimTransforms[j]; + } + + for (int i = 0; i < ITEM_COUNT_MAX; i++) + { + _effects[i].PrevPosition = _effects[i].Position; + _effects[i].PrevWorld = _effects[i].World; + _effects[i].PrevTranslation = _effects[i].Translation; + _effects[i].PrevRotation = _effects[i].Rotation; + _effects[i].PrevScale = _effects[i].Scale; + } + } +} diff --git a/TombEngine/Renderer/RendererHelper.cpp b/TombEngine/Renderer/RendererHelper.cpp index 490a0b48d..7a2544541 100644 --- a/TombEngine/Renderer/RendererHelper.cpp +++ b/TombEngine/Renderer/RendererHelper.cpp @@ -9,7 +9,7 @@ #include "Scripting/Include/Flow/ScriptInterfaceFlowHandler.h" #include "Game/animation.h" #include "Game/camera.h" -#include "Game/collision/sphere.h" +#include "Game/collision/Sphere.h" #include "Game/control/control.h" #include "Game/itemdata/creature_info.h" #include "Game/items.h" @@ -34,6 +34,7 @@ #include "Specific/level.h" #include "Specific/trutils.h" +using namespace TEN::Collision::Sphere; using namespace TEN::Math; extern GameConfiguration g_Configuration; @@ -49,7 +50,7 @@ namespace TEN::Renderer RendererBone* bones[MAX_BONES] = {}; int nextBone = 0; - auto* transforms = ((rItem == nullptr) ? rObject.AnimationTransforms.data() : &rItem->AnimationTransforms[0]); + auto* transforms = ((rItem == nullptr) ? rObject.AnimationTransforms.data() : &rItem->AnimTransforms[0]); // Push. bones[nextBone++] = rObject.Skeleton; @@ -67,7 +68,7 @@ namespace TEN::Renderer (frameData.Alpha != 0.0f && frameData.FramePtr0->BoneOrientations.size() <= bonePtr->Index)) { TENLog( - "Attempted to animate object with ID " + GetObjectName((GAME_OBJECT_ID)rItem->ObjectNumber) + + "Attempted to animate object with ID " + GetObjectName((GAME_OBJECT_ID)rItem->ObjectID) + " using incorrect animation data. Bad animations set for slot?", LogLevel::Error); @@ -79,7 +80,7 @@ namespace TEN::Renderer { auto offset0 = frameData.FramePtr0->Offset; auto rotMatrix = Matrix::CreateFromQuaternion(frameData.FramePtr0->BoneOrientations[bonePtr->Index]); - + if (frameData.Alpha != 0.0f) { auto offset1 = frameData.FramePtr1->Offset; @@ -94,6 +95,10 @@ namespace TEN::Renderer rotMatrix = Matrix::CreateFromQuaternion(quat3); } + // Store bone orientation on current frame. + if (rItem != nullptr) + rItem->BoneOrientations[bonePtr->Index] = Quaternion::CreateFromRotationMatrix(rotMatrix); + auto tMatrix = (bonePtr == rObject.Skeleton) ? Matrix::CreateTranslation(offset0) : Matrix::Identity; auto extraRotMatrix = Matrix::CreateFromQuaternion(bonePtr->ExtraRotation); @@ -174,7 +179,7 @@ namespace TEN::Renderer auto& moveableObj = *_moveableObjects[nativeItem->ObjectNumber]; // Copy meshswaps - itemToDraw->MeshIndex = nativeItem->Model.MeshIndex; + itemToDraw->MeshIds = nativeItem->Model.MeshIndex; if (obj->animIndex == -1) return; @@ -310,7 +315,7 @@ namespace TEN::Renderer UpdateAnimation(itemToDraw, moveableObj, frameData, UINT_MAX); for (int m = 0; m < obj->nmeshes; m++) - itemToDraw->AnimationTransforms[m] = itemToDraw->AnimationTransforms[m]; + itemToDraw->AnimTransforms[m] = itemToDraw->AnimTransforms[m]; } void Renderer::UpdateItemAnimations(RenderView& view) @@ -356,13 +361,13 @@ namespace TEN::Renderer return (!_isWindowed); } - void Renderer::UpdateCameraMatrices(CAMERA_INFO *cam, float roll, float fov, float farView) + void Renderer::UpdateCameraMatrices(CAMERA_INFO *cam, float farView) { if (farView < MIN_FAR_VIEW) farView = DEFAULT_FAR_VIEW; - farView = farView; - _gameCamera = RenderView(cam, roll, fov, 32, farView, g_Configuration.ScreenWidth, g_Configuration.ScreenHeight); + _currentGameCamera = RenderView(cam, cam->Roll, cam->Fov, 32, farView, g_Configuration.ScreenWidth, g_Configuration.ScreenHeight); + _gameCamera = RenderView(cam, cam->Roll, cam->Fov, 32, farView, g_Configuration.ScreenWidth, g_Configuration.ScreenHeight); } bool Renderer::SphereBoxIntersection(BoundingBox box, Vector3 sphereCentre, float sphereRadius) @@ -408,17 +413,16 @@ namespace TEN::Renderer return _meshes[meshIndex]; } - int Renderer::GetSpheres(short itemNumber, BoundingSphere* spheres, char worldSpace, Matrix local) + std::vector Renderer::GetSpheres(int itemNumber) { - auto* itemToDraw = &_items[itemNumber]; - auto* nativeItem = &g_Level.Items[itemNumber]; + auto& itemToDraw = _items[itemNumber]; + itemToDraw.ItemNumber = itemNumber; - itemToDraw->ItemNumber = itemNumber; + const auto* nativeItem = &g_Level.Items[itemNumber]; + if (nativeItem == nullptr) + return {}; - if (!nativeItem) - return 0; - - if (!itemToDraw->DoneAnimations) + if (!itemToDraw.DoneAnimations) { if (itemNumber == LaraItem->Index) { @@ -430,38 +434,26 @@ namespace TEN::Renderer } } - auto world = Matrix::Identity; - if (worldSpace & SPHERES_SPACE_WORLD) + auto translationMatrix = Matrix::CreateTranslation(nativeItem->Pose.Position.ToVector3()); + auto rotMatrix = nativeItem->Pose.Orientation.ToRotationMatrix(); + auto worldMatrix = rotMatrix * translationMatrix; + + const auto& moveable = GetRendererObject(nativeItem->ObjectNumber); + + // Collect spheres. + auto spheres = std::vector{}; + for (int i = 0; i < moveable.ObjectMeshes.size(); i++) { - world = Matrix::CreateTranslation(nativeItem->Pose.Position.x, nativeItem->Pose.Position.y, nativeItem->Pose.Position.z) * local; + const auto& mesh = *moveable.ObjectMeshes[i]; + + const auto& translationMatrix = itemToDraw.AnimTransforms[i]; + auto pos = Vector3::Transform(mesh.Sphere.Center, translationMatrix * worldMatrix); + + auto sphere = BoundingSphere(pos, mesh.Sphere.Radius); + spheres.push_back(sphere); } - else - { - world = Matrix::Identity * local; - } - - world = nativeItem->Pose.Orientation.ToRotationMatrix() * world; - - auto& moveable = GetRendererObject(nativeItem->ObjectNumber); - - for (int i = 0; i< moveable.ObjectMeshes.size();i++) - { - auto mesh = moveable.ObjectMeshes[i]; - - auto pos = (Vector3)mesh->Sphere.Center; - if (worldSpace & SPHERES_SPACE_BONE_ORIGIN) - pos += moveable.LinearizedBones[i]->Translation; - - spheres[i].Center = Vector3::Transform(pos, (itemToDraw->AnimationTransforms[i] * world)); - spheres[i].Radius = mesh->Sphere.Radius; - - // Spheres debug - // auto v1 = Vector3(spheres[i].Center.x - spheres[i].Radius, spheres[i].Center.y, spheres[i].Center.z); - // auto v2 = Vector3(spheres[i].Center.x + spheres[i].Radius, spheres[i].Center.y, spheres[i].Center.z); - // AddDebugLine(v1, v2, Vector4::One); - } - - return (int)moveable.ObjectMeshes.size(); + + return spheres; } void Renderer::GetBoneMatrix(short itemNumber, int jointIndex, Matrix* outMatrix) @@ -516,11 +508,26 @@ namespace TEN::Renderer return s; } + float Renderer::GetFramerateMultiplier() const + { + return g_Configuration.EnableHighFramerate ? (g_Renderer.GetScreenRefreshRate() / (float)FPS) : 1.0f; + } + + float Renderer::GetInterpolationFactor() const + { + return _interpolationFactor; + } + Vector2i Renderer::GetScreenResolution() const { return Vector2i(_screenWidth, _screenHeight); } + int Renderer::GetScreenRefreshRate() const + { + return _refreshRate; + } + std::optional Renderer::Get2DPosition(const Vector3& pos) const { auto point = Vector4(pos.x, pos.y, pos.z, 1.0f); @@ -561,30 +568,41 @@ namespace TEN::Renderer return std::pair(nearPoint, farPoint); } - Vector3 Renderer::GetAbsEntityBonePosition(int itemNumber, int jointIndex, const Vector3& relOffset) + Vector3 Renderer::GetMoveableBonePosition(int itemNumber, int boneID, const Vector3& relOffset) { auto* rendererItem = &_items[itemNumber]; - rendererItem->ItemNumber = itemNumber; - if (!rendererItem) + if (rendererItem == nullptr) return Vector3::Zero; if (!rendererItem->DoneAnimations) - { - if (itemNumber == LaraItem->Index) - UpdateLaraAnimations(false); - else - UpdateItemAnimations(itemNumber, false); - } + (itemNumber == LaraItem->Index) ? UpdateLaraAnimations(false) : UpdateItemAnimations(itemNumber, false); - if (jointIndex >= MAX_BONES) - jointIndex = 0; + if (boneID >= MAX_BONES) + boneID = 0; + + auto world = rendererItem->AnimTransforms[boneID] * rendererItem->World; - auto world = rendererItem->AnimationTransforms[jointIndex] * rendererItem->World; return Vector3::Transform(relOffset, world); } + Quaternion Renderer::GetMoveableBoneOrientation(int itemNumber, int boneID) + { + const auto* rendererItem = &_items[itemNumber]; + + if (rendererItem == nullptr) + return Quaternion::Identity; + + if (!rendererItem->DoneAnimations) + (itemNumber == LaraItem->Index) ? UpdateLaraAnimations(false) : UpdateItemAnimations(itemNumber, false); + + if (boneID >= MAX_BONES) + boneID = 0; + + return rendererItem->BoneOrientations[boneID]; + } + void Renderer::SaveScreenshot() { char buffer[64]; diff --git a/TombEngine/Renderer/RendererInit.cpp b/TombEngine/Renderer/RendererInit.cpp index c6d339c5e..86fb7ced3 100644 --- a/TombEngine/Renderer/RendererInit.cpp +++ b/TombEngine/Renderer/RendererInit.cpp @@ -86,6 +86,7 @@ namespace TEN::Renderer _vsInstancedSprites = Utils::compileVertexShader(_device.Get(), GetAssetPath(L"Shaders\\InstancedSprites.fx"), "VS", "vs_5_0", nullptr, blob); _psInstancedSprites = Utils::compilePixelShader(_device.Get(), GetAssetPath(L"Shaders\\InstancedSprites.fx"), "PS", "ps_5_0", nullptr, blob); _vsGBufferRooms = Utils::compileVertexShader(_device.Get(), GetAssetPath(L"Shaders\\GBuffer.fx"), "VSRooms", "vs_5_0", nullptr, blob); + _vsGBufferRoomsAnimated = Utils::compileVertexShader(_device.Get(), GetAssetPath(L"Shaders\\GBuffer.fx"), "VSRooms", "vs_5_0", &roomDefinesAnimated[0], blob); _vsGBufferItems = Utils::compileVertexShader(_device.Get(), GetAssetPath(L"Shaders\\GBuffer.fx"), "VSItems", "vs_5_0", nullptr, blob); _vsGBufferStatics = Utils::compileVertexShader(_device.Get(), GetAssetPath(L"Shaders\\GBuffer.fx"), "VSStatics", "vs_5_0", nullptr, blob); _vsGBufferInstancedStatics = Utils::compileVertexShader(_device.Get(), GetAssetPath(L"Shaders\\GBuffer.fx"), "VSInstancedStatics", "vs_5_0", nullptr, blob); @@ -297,12 +298,13 @@ namespace TEN::Renderer { "COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA, 0 } }; Utils::throwIfFailed(_device->CreateInputLayout(postProcessInputLayoutItems, 3, blob->GetBufferPointer(), blob->GetBufferSize(), &_fullscreenTriangleInputLayout)); - + _psPostProcessCopy = Utils::compilePixelShader(_device.Get(), GetAssetPath(L"Shaders\\PostProcess.fx"), "PSCopy", "ps_5_0", nullptr, blob); _psPostProcessMonochrome = Utils::compilePixelShader(_device.Get(), GetAssetPath(L"Shaders\\PostProcess.fx"), "PSMonochrome", "ps_5_0", nullptr, blob); _psPostProcessNegative = Utils::compilePixelShader(_device.Get(), GetAssetPath(L"Shaders\\PostProcess.fx"), "PSNegative", "ps_5_0", nullptr, blob); _psPostProcessExclusion = Utils::compilePixelShader(_device.Get(), GetAssetPath(L"Shaders\\PostProcess.fx"), "PSExclusion", "ps_5_0", nullptr, blob); _psPostProcessFinalPass = Utils::compilePixelShader(_device.Get(), GetAssetPath(L"Shaders\\PostProcess.fx"), "PSFinalPass", "ps_5_0", nullptr, blob); + _psPostProcessLensFlare = Utils::compilePixelShader(_device.Get(), GetAssetPath(L"Shaders\\PostProcess.fx"), "PSLensFlare", "ps_5_0", nullptr, blob); } void Renderer::CreateSSAONoiseTexture() @@ -462,8 +464,26 @@ namespace TEN::Renderer DXGI_SWAP_CHAIN_DESC sd; sd.BufferDesc.Width = w; sd.BufferDesc.Height = h; - sd.BufferDesc.RefreshRate.Numerator = 0; - sd.BufferDesc.RefreshRate.Denominator = 0; + if (!g_Configuration.EnableHighFramerate) + { + _refreshRate = 30; + + sd.BufferDesc.RefreshRate.Numerator = 0; + sd.BufferDesc.RefreshRate.Denominator = 0; + } + else + { + _refreshRate = GetCurrentScreenRefreshRate(); + if (_refreshRate == 0) + { + _refreshRate = 60; + } + + sd.BufferDesc.RefreshRate.Numerator = _refreshRate; + sd.BufferDesc.RefreshRate.Denominator = 1; + } + sd.BufferDesc.RefreshRate.Numerator = 60; + sd.BufferDesc.RefreshRate.Denominator = 1; sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; sd.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED; sd.BufferDesc.Scaling = DXGI_MODE_SCALING_STRETCHED; diff --git a/TombEngine/Renderer/RendererLara.cpp b/TombEngine/Renderer/RendererLara.cpp index bccc49e04..b1e29ebe5 100644 --- a/TombEngine/Renderer/RendererLara.cpp +++ b/TombEngine/Renderer/RendererLara.cpp @@ -9,7 +9,7 @@ #include "Game/control/control.h" #include "Game/spotcam.h" #include "Game/camera.h" -#include "Game/collision/sphere.h" +#include "Game/collision/Sphere.h" #include "Game/Setup.h" #include "Math/Math.h" #include "Scripting/Include/Flow/ScriptInterfaceFlowHandler.h" @@ -85,6 +85,9 @@ void Renderer::UpdateLaraAnimations(bool force) if (!force && rItem.DoneAnimations) return; + if (_moveableObjects.empty()) + return; + auto& playerObject = *_moveableObjects[ID_LARA]; // Clear extra rotations. @@ -268,10 +271,10 @@ void Renderer::UpdateLaraAnimations(bool force) // Copy matrices in player object. for (int m = 0; m < NUM_LARA_MESHES; m++) - playerObject.AnimationTransforms[m] = rItem.AnimationTransforms[m]; + playerObject.AnimationTransforms[m] = rItem.AnimTransforms[m]; // Copy meshswap indices. - rItem.MeshIndex = LaraItem->Model.MeshIndex; + rItem.MeshIds = LaraItem->Model.MeshIndex; rItem.DoneAnimations = true; } @@ -302,10 +305,10 @@ void TEN::Renderer::Renderer::DrawLara(RenderView& view, RendererPass rendererPa RendererRoom* room = &_rooms[LaraItem->RoomNumber]; - _stItem.World = _laraWorldMatrix; + _stItem.World = item->InterpolatedWorld; // _laraWorldMatrix; _stItem.Color = item->Color; _stItem.AmbientLight = item->AmbientLight; - memcpy(_stItem.BonesMatrices, laraObj.AnimationTransforms.data(), laraObj.AnimationTransforms.size() * sizeof(Matrix)); + memcpy(_stItem.BonesMatrices, item->InterpolatedAnimTransforms, laraObj.AnimationTransforms.size() * sizeof(Matrix)); for (int k = 0; k < laraSkin.ObjectMeshes.size(); k++) { _stItem.BoneLightModes[k] = (int)GetMesh(nativeItem->Model.MeshIndex[k])->LightMode; @@ -328,29 +331,29 @@ void TEN::Renderer::Renderer::DrawLara(RenderView& view, RendererPass rendererPa void Renderer::DrawLaraHair(RendererItem* itemToDraw, RendererRoom* room, RenderView& view, RendererPass rendererPass) { - if (!Objects[ID_HAIR].loaded) - return; - - const auto& hairObject = *_moveableObjects[ID_HAIR]; - - // TODO - bool isYoung = (g_GameFlow->GetLevel(CurrentLevel)->GetLaraType() == LaraType::Young); - - bool isHead = true; - for (const auto& unit : HairEffect.Units) + for (int i = 0; i < HairEffect.Units.size(); i++) { + const auto& unit = HairEffect.Units[i]; if (!unit.IsEnabled) continue; - // First matrix is Lara's head matrix, then all hair unit segment matrices. - // Bones are adjusted at load time to account for this. + const auto& object = Objects[unit.ObjectID]; + if (!object.loaded) + continue; + + const auto& rendererObject = *_moveableObjects[unit.ObjectID]; + _stItem.World = Matrix::Identity; - _stItem.BonesMatrices[0] = itemToDraw->AnimationTransforms[LM_HEAD] * _laraWorldMatrix; + _stItem.BonesMatrices[0] = itemToDraw->InterpolatedAnimTransforms[LM_HEAD] * itemToDraw->InterpolatedWorld; for (int i = 0; i < unit.Segments.size(); i++) { const auto& segment = unit.Segments[i]; - auto worldMatrix = Matrix::CreateFromQuaternion(segment.Orientation) * Matrix::CreateTranslation(segment.Position); + auto worldMatrix = + Matrix::CreateFromQuaternion( + Quaternion::Lerp(segment.PrevOrientation, segment.Orientation, _interpolationFactor)) * + Matrix::CreateTranslation( + Vector3::Lerp(segment.PrevPosition, segment.Position, _interpolationFactor)); _stItem.BonesMatrices[i + 1] = worldMatrix; _stItem.BoneLightModes[i] = (int)LightMode::Dynamic; @@ -358,13 +361,11 @@ void Renderer::DrawLaraHair(RendererItem* itemToDraw, RendererRoom* room, Render _cbItem.UpdateData(_stItem, _context.Get()); - for (int i = 0; i < hairObject.ObjectMeshes.size(); i++) + for (int i = 0; i < rendererObject.ObjectMeshes.size(); i++) { - auto& rMesh = *hairObject.ObjectMeshes[i]; - DrawMoveableMesh(itemToDraw, &rMesh, room, i, view, rendererPass); + auto& rendererMesh = *rendererObject.ObjectMeshes[i]; + DrawMoveableMesh(itemToDraw, &rendererMesh, room, i, view, rendererPass); } - - isHead = false; } } diff --git a/TombEngine/Renderer/RendererPostProcess.cpp b/TombEngine/Renderer/RendererPostProcess.cpp index 5742f01c6..1c00be4a8 100644 --- a/TombEngine/Renderer/RendererPostProcess.cpp +++ b/TombEngine/Renderer/RendererPostProcess.cpp @@ -20,15 +20,15 @@ namespace TEN::Renderer _stPostProcessBuffer.Tint = _postProcessTint; _cbPostProcessBuffer.UpdateData(_stPostProcessBuffer, _context.Get()); - // Common vertex shader to all fullscreen effects + // Common vertex shader to all fullscreen effects. _context->VSSetShader(_vsPostProcess.Get(), nullptr, 0); - // We draw a fullscreen triangle + // Draw fullscreen triangle. _context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST); _context->IASetInputLayout(_fullscreenTriangleInputLayout.Get()); - UINT stride = sizeof(PostProcessVertex); - UINT offset = 0; + unsigned int stride = sizeof(PostProcessVertex); + unsigned int offset = 0; _context->IASetVertexBuffers(0, 1, _fullscreenTriangleVertexBuffer.Buffer.GetAddressOf(), &stride, &offset); @@ -41,16 +41,16 @@ namespace TEN::Renderer BindRenderTargetAsTexture(TextureRegister::ColorMap, &_renderTarget, SamplerStateRegister::PointWrap); DrawTriangles(3, 0); - // Let's do ping-pong between the two post-process render targets + // Ping-pong between two post-process render targets. int currentRenderTarget = 0; - int destinationRenderTarget = 1; + int destRenderTarget = 1; - // Apply color scheme + // Apply color scheme. if (_postProcessMode != PostProcessMode::None && _postProcessStrength > EPSILON) { - _context->ClearRenderTargetView(_postProcessRenderTarget[destinationRenderTarget].RenderTargetView.Get(), clearColor); - _context->OMSetRenderTargets(1, _postProcessRenderTarget[destinationRenderTarget].RenderTargetView.GetAddressOf(), nullptr); - + _context->ClearRenderTargetView(_postProcessRenderTarget[destRenderTarget].RenderTargetView.Get(), clearColor); + _context->OMSetRenderTargets(1, _postProcessRenderTarget[destRenderTarget].RenderTargetView.GetAddressOf(), nullptr); + switch (_postProcessMode) { case PostProcessMode::Monochrome: @@ -64,7 +64,7 @@ namespace TEN::Renderer case PostProcessMode::Exclusion: _context->PSSetShader(_psPostProcessExclusion.Get(), nullptr, 0); break; - + default: return; } @@ -72,11 +72,34 @@ namespace TEN::Renderer BindRenderTargetAsTexture(TextureRegister::ColorMap, &_postProcessRenderTarget[currentRenderTarget], SamplerStateRegister::PointWrap); DrawTriangles(3, 0); - destinationRenderTarget = 0; - currentRenderTarget = 1; - } + destRenderTarget = (destRenderTarget == 1) ? 0 : 1; + currentRenderTarget = (currentRenderTarget == 1) ? 0 : 1; + } - // Do the final pass + // Lens flares. + if (!view.LensFlaresToDraw.empty()) + { + _context->ClearRenderTargetView(_postProcessRenderTarget[destRenderTarget].RenderTargetView.Get(), clearColor); + _context->OMSetRenderTargets(1, _postProcessRenderTarget[destRenderTarget].RenderTargetView.GetAddressOf(), nullptr); + + _context->PSSetShader(_psPostProcessLensFlare.Get(), nullptr, 0); + + for (int i = 0; i < view.LensFlaresToDraw.size(); i++) + { + _stPostProcessBuffer.LensFlares[i].Position = view.LensFlaresToDraw[i].Position; + _stPostProcessBuffer.LensFlares[i].Color = view.LensFlaresToDraw[i].Color.ToVector3(); + } + _stPostProcessBuffer.NumLensFlares = (int)view.LensFlaresToDraw.size(); + _cbPostProcessBuffer.UpdateData(_stPostProcessBuffer, _context.Get()); + + BindRenderTargetAsTexture(TextureRegister::ColorMap, &_postProcessRenderTarget[currentRenderTarget], SamplerStateRegister::PointWrap); + DrawTriangles(3, 0); + + destRenderTarget = (destRenderTarget) == 1 ? 0 : 1; + currentRenderTarget = (currentRenderTarget == 1) ? 0 : 1; + } + + // Do final pass. _context->PSSetShader(_psPostProcessFinalPass.Get(), nullptr, 0); _context->ClearRenderTargetView(renderTarget->RenderTargetView.Get(), Colors::Black); diff --git a/TombEngine/Renderer/RendererSprites.cpp b/TombEngine/Renderer/RendererSprites.cpp index b969a0efe..417155097 100644 --- a/TombEngine/Renderer/RendererSprites.cpp +++ b/TombEngine/Renderer/RendererSprites.cpp @@ -10,9 +10,6 @@ namespace TEN::Renderer void Renderer::AddSpriteBillboard(RendererSprite* sprite, const Vector3& pos, const Vector4& color, float orient2D, float scale, Vector2 size, BlendMode blendMode, bool isSoftParticle, RenderView& view, SpriteRenderType renderType) { - if (_isLocked) - return; - if (scale <= 0.0f) scale = 1.0f; @@ -42,11 +39,8 @@ namespace TEN::Renderer void Renderer::AddSpriteBillboardConstrained(RendererSprite* sprite, const Vector3& pos, const Vector4& color, float orient2D, float scale, Vector2 size, BlendMode blendMode, const Vector3& constrainAxis, - bool softParticles, RenderView& view, SpriteRenderType renderType) + bool isSoftParticle, RenderView& view, SpriteRenderType renderType) { - if (_isLocked) - return; - if (scale <= 0.0f) scale = 1.0f; @@ -64,7 +58,7 @@ namespace TEN::Renderer spr.Height = size.y; spr.BlendMode = blendMode; spr.ConstrainAxis = constrainAxis; - spr.SoftParticle = softParticles; + spr.SoftParticle = isSoftParticle; spr.c1 = color; spr.c2 = color; spr.c3 = color; @@ -79,9 +73,6 @@ namespace TEN::Renderer float scale, Vector2 size, BlendMode blendMode, const Vector3& lookAtAxis, bool isSoftParticle, RenderView& view, SpriteRenderType renderType) { - if (_isLocked) - return; - if (scale <= 0.0f) scale = 1.0f; @@ -121,9 +112,6 @@ namespace TEN::Renderer const Vector4& color0, const Vector4& color1, const Vector4& color2, const Vector4& color3, float orient2D, float scale, Vector2 size, BlendMode blendMode, bool isSoftParticle, RenderView& view, SpriteRenderType renderType) { - if (_isLocked) - return; - if (scale <= 0.0f) scale = 1.0f; @@ -164,9 +152,6 @@ namespace TEN::Renderer const Vector4& color0, const Vector4& color1, const Vector4& color2, const Vector4& color3, BlendMode blendMode, RenderView& view, SpriteRenderType renderType) { - if (_isLocked) - return; - auto sprite = RendererSpriteToDraw{}; sprite.Type = SpriteType::ThreeD; @@ -274,24 +259,17 @@ namespace TEN::Renderer void Renderer::DrawSprites(RenderView& view, RendererPass rendererPass) { if (view.SpritesToDraw.empty()) - { return; - } - // Draw instanced sprites + // Draw instanced sprites. bool wasGPUSet = false; - for (auto& spriteBucket : _spriteBuckets) { if (spriteBucket.SpritesToDraw.size() == 0 || !spriteBucket.IsBillboard) - { continue; - } if (!SetupBlendModeAndAlphaTest(spriteBucket.BlendMode, rendererPass, 0)) - { continue; - } if (!wasGPUSet) { @@ -306,8 +284,8 @@ namespace TEN::Renderer _context->PSSetShader(_psInstancedSprites.Get(), nullptr, 0); // Set up vertex buffer and parameters. - UINT stride = sizeof(Vertex); - UINT offset = 0; + unsigned int stride = sizeof(Vertex); + unsigned int offset = 0; _context->IASetVertexBuffers(0, 1, _quadVertexBuffer.Buffer.GetAddressOf(), &stride, &offset); wasGPUSet = true; @@ -344,20 +322,16 @@ namespace TEN::Renderer _numInstancedSpritesDrawCalls++; } - // Draw 3D non instanced sprites + // Draw 3D non-instanced sprites. wasGPUSet = false; for (auto& spriteBucket : _spriteBuckets) { if (spriteBucket.SpritesToDraw.empty() || spriteBucket.IsBillboard) - { continue; - } if (!SetupBlendModeAndAlphaTest(spriteBucket.BlendMode, rendererPass, 0)) - { continue; - } if (!wasGPUSet) { diff --git a/TombEngine/Renderer/RendererString.cpp b/TombEngine/Renderer/RendererString.cpp index b26196f44..7ed550a34 100644 --- a/TombEngine/Renderer/RendererString.cpp +++ b/TombEngine/Renderer/RendererString.cpp @@ -2,14 +2,18 @@ #include "Renderer/Renderer.h" #include "Specific/trutils.h" +#include "Specific/winmain.h" namespace TEN::Renderer { - void Renderer::AddDebugString(const std::string& string, const Vector2& pos, const Color& color, float scale, int flags, RendererDebugPage page) + void Renderer::AddDebugString(const std::string& string, const Vector2& pos, const Color& color, float scale, RendererDebugPage page) { constexpr auto FLAGS = (int)PrintStringFlags::Outline | (int)PrintStringFlags::Center; - if (_debugPage != page) + if (_isLocked) + return; + + if (!DebugMode || (_debugPage != page && page != RendererDebugPage::None)) return; AddString(string, pos, color, scale, FLAGS); @@ -22,10 +26,6 @@ namespace TEN::Renderer void Renderer::AddString(const std::string& string, const Vector2& pos, const Color& color, float scale, int flags) { - constexpr auto BLINK_VALUE_MAX = 1.0f; - constexpr auto BLINK_VALUE_MIN = 0.1f; - constexpr auto BLINK_TIME_STEP = 0.2f; - if (_isLocked) return; @@ -75,19 +75,6 @@ namespace TEN::Renderer if (flags & (int)PrintStringFlags::Blink) { rString.Color *= _blinkColorValue; - - if (!_isBlinkUpdated) - { - // Calculate blink increment based on sine wave. - _blinkColorValue = ((sin(_blinkTime) + BLINK_VALUE_MAX) * 0.5f) + BLINK_VALUE_MIN; - - // Update blink time. - _blinkTime += BLINK_TIME_STEP; - if (_blinkTime > PI_MUL_2) - _blinkTime -= PI_MUL_2; - - _isBlinkUpdated = true; - } } yOffset += size.y; @@ -127,8 +114,5 @@ namespace TEN::Renderer } _spriteBatch->End(); - - _isBlinkUpdated = false; - _stringsToDraw.clear(); } } diff --git a/TombEngine/Renderer/RendererUtils.cpp b/TombEngine/Renderer/RendererUtils.cpp index de53f6f07..b6601bbe2 100644 --- a/TombEngine/Renderer/RendererUtils.cpp +++ b/TombEngine/Renderer/RendererUtils.cpp @@ -68,7 +68,7 @@ namespace TEN::Renderer::Utils ComPtr shader; throwIfFailed(device->CreateVertexShader(bytecode->GetBufferPointer(), bytecode->GetBufferSize(), nullptr, shader.GetAddressOf())); - if constexpr(DebugBuild) + if constexpr (DebugBuild) { char buffer[100]; unsigned int size = (unsigned int)std::wcstombs(buffer, fileName.c_str(), 100); diff --git a/TombEngine/Renderer/Structures/RendererEffect.h b/TombEngine/Renderer/Structures/RendererEffect.h index e4c7904e7..1473c784c 100644 --- a/TombEngine/Renderer/Structures/RendererEffect.h +++ b/TombEngine/Renderer/Structures/RendererEffect.h @@ -1,21 +1,36 @@ #pragma once -#include -#include "Renderer/Structures/RendererMesh.h" + #include "Renderer/Structures/RendererLight.h" +#include "Renderer/Structures/RendererMesh.h" namespace TEN::Renderer::Structures { - using namespace DirectX::SimpleMath; - struct RendererEffect { - int ObjectNumber; - int RoomNumber; - Vector3 Position; - Matrix World; - Vector4 Color; - Vector4 AmbientLight; - RendererMesh* Mesh; - std::vector LightsToDraw; + int ObjectID = 0; + int RoomNumber = 0; + + Vector3 Position = Vector3::Zero; + Matrix World = Matrix::Identity; + Matrix Translation = Matrix::Identity; + Matrix Rotation = Matrix::Identity; + Matrix Scale = Matrix::Identity; + Vector4 Color = Vector4::Zero; + Vector4 AmbientLight = Vector4::Zero; + + RendererMesh* Mesh = nullptr; + std::vector LightsToDraw = {}; + + Vector3 InterpolatedPosition = Vector3::Zero; + Matrix InterpolatedWorld = Matrix::Identity; + Matrix InterpolatedTranslation = Matrix::Identity; + Matrix InterpolatedRotation = Matrix::Identity; + Matrix InterpolatedScale = Matrix::Identity; + + Vector3 PrevPosition = Vector3::Zero; + Matrix PrevWorld = Matrix::Identity; + Matrix PrevTranslation = Matrix::Identity; + Matrix PrevRotation = Matrix::Identity; + Matrix PrevScale = Matrix::Identity; }; } diff --git a/TombEngine/Renderer/Structures/RendererItem.h b/TombEngine/Renderer/Structures/RendererItem.h index 48780aa27..0924ec4bb 100644 --- a/TombEngine/Renderer/Structures/RendererItem.h +++ b/TombEngine/Renderer/Structures/RendererItem.h @@ -1,34 +1,49 @@ #pragma once -#include + +#include "Game/room.h" #include "Renderer/RendererEnums.h" #include "Renderer/Structures/RendererLight.h" -#include "Game/room.h" namespace TEN::Renderer::Structures { - using namespace DirectX::SimpleMath; - struct RendererItem { - int ItemNumber; - int ObjectNumber; + int ItemNumber = 0; + int ObjectID = 0; - Vector3 Position; - Matrix World; - Matrix Translation; - Matrix Rotation; - Matrix Scale; - Matrix AnimationTransforms[MAX_BONES]; + Vector3 Position = Vector3::Zero; + int RoomNumber = NO_VALUE; + Matrix World = Matrix::Identity; + Matrix Translation = Matrix::Identity; + Matrix Rotation = Matrix::Identity; + Matrix Scale = Matrix::Identity; + Matrix AnimTransforms[MAX_BONES] = {}; - int RoomNumber = NO_VALUE; - int PrevRoomNumber = NO_VALUE; - Vector4 Color; - Vector4 AmbientLight; - std::vector LightsToDraw; - float LightFade; + Quaternion BoneOrientations[MAX_BONES]; - std::vector MeshIndex; + Vector4 Color = Vector4::One; + Vector4 AmbientLight = Vector4::One; - bool DoneAnimations; + std::vector MeshIds = {}; + std::vector LightsToDraw = {}; + float LightFade = 0.0f; + + bool DoneAnimations = false; + bool DisableInterpolation = true; + + Vector3 InterpolatedPosition = Vector3::Zero; + Matrix InterpolatedWorld = Matrix::Identity; + Matrix InterpolatedTranslation = Matrix::Identity; + Matrix InterpolatedRotation = Matrix::Identity; + Matrix InterpolatedScale = Matrix::Identity; + Matrix InterpolatedAnimTransforms[MAX_BONES]; + + Vector3 PrevPosition = Vector3::Zero; + int PrevRoomNumber = NO_VALUE; + Matrix PrevWorld = Matrix::Identity; + Matrix PrevTranslation = Matrix::Identity; + Matrix PrevRotation = Matrix::Identity; + Matrix PrevScale = Matrix::Identity; + Matrix PrevAnimTransforms[MAX_BONES]; }; } diff --git a/TombEngine/Renderer/Structures/RendererLensFlare.h b/TombEngine/Renderer/Structures/RendererLensFlare.h new file mode 100644 index 000000000..1fa9edfdd --- /dev/null +++ b/TombEngine/Renderer/Structures/RendererLensFlare.h @@ -0,0 +1,17 @@ +#pragma once +#include "Renderer/RendererEnums.h" + +namespace TEN::Renderer::Structures +{ + struct RendererLensFlare + { + int SpriteID = 0; + + Vector3 Position = Vector3::Zero; + Vector3 Direction = Vector3::Zero; + Color Color = {}; + + float Distance = 0.0f; + bool IsGlobal = false; + }; +} diff --git a/TombEngine/Renderer/Structures/RendererStar.h b/TombEngine/Renderer/Structures/RendererStar.h new file mode 100644 index 000000000..d0bb916f9 --- /dev/null +++ b/TombEngine/Renderer/Structures/RendererStar.h @@ -0,0 +1,17 @@ +#pragma once +#include +#include "Renderer/RendererEnums.h" + +namespace TEN::Renderer::Structures +{ + using namespace DirectX::SimpleMath; + + struct RendererStar + { + Vector3 Direction; + Vector3 Color; + float Blinking; + float Scale; + float Extinction; + }; +} \ No newline at end of file diff --git a/TombEngine/Resources.rc b/TombEngine/Resources.rc index c475bfd5e..7984b7c19 100644 --- a/TombEngine/Resources.rc +++ b/TombEngine/Resources.rc @@ -26,8 +26,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_NEUTRAL // VS_VERSION_INFO VERSIONINFO - FILEVERSION 1,4,0,0 - PRODUCTVERSION 1,7,1,0 + FILEVERSION 1,5,0,1 + PRODUCTVERSION 1,7,2,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -43,12 +43,12 @@ BEGIN BLOCK "000904b0" BEGIN VALUE "CompanyName", "Tomb Engine Development Community" - VALUE "FileVersion", "1.4.0.0" + VALUE "FileVersion", "1.5.0.1" VALUE "InternalName", "TombEngine.exe" VALUE "LegalCopyright", "Copyright (c) 2024" VALUE "OriginalFilename", "TombEngine.exe" VALUE "ProductName", "Tomb Engine" - VALUE "ProductVersion", "1.7.1.0" + VALUE "ProductVersion", "1.7.2.0" END END BLOCK "VarFileInfo" diff --git a/TombEngine/Scripting/Include/Flow/ScriptInterfaceFlowHandler.h b/TombEngine/Scripting/Include/Flow/ScriptInterfaceFlowHandler.h index ac3382e1c..4a3f67354 100644 --- a/TombEngine/Scripting/Include/Flow/ScriptInterfaceFlowHandler.h +++ b/TombEngine/Scripting/Include/Flow/ScriptInterfaceFlowHandler.h @@ -35,11 +35,12 @@ public: virtual bool IsMassPickupEnabled() const = 0; virtual bool IsPointFilterEnabled() const = 0; virtual bool IsLaraInTitleEnabled() const = 0; + virtual bool IsHomeLevelEnabled() const = 0; virtual bool IsLoadSaveEnabled() const = 0; virtual bool HasCrawlExtended() const = 0; virtual bool HasCrouchRoll() const = 0; virtual bool HasCrawlspaceDive() const = 0; - virtual bool HasMonkeyAutoJump() const = 0; + virtual bool HasAutoMonkeySwingJump() const = 0; virtual bool HasSprintJump() const = 0; virtual bool HasAFKPose() const = 0; virtual bool HasOverhangClimb() const = 0; diff --git a/TombEngine/Scripting/Include/ScriptInterfaceGame.h b/TombEngine/Scripting/Include/ScriptInterfaceGame.h index 7be1be9ec..81958aaaf 100644 --- a/TombEngine/Scripting/Include/ScriptInterfaceGame.h +++ b/TombEngine/Scripting/Include/ScriptInterfaceGame.h @@ -57,8 +57,9 @@ public: virtual void OnLoop(float deltaTime, bool postLoop) = 0; virtual void OnSave() = 0; virtual void OnEnd(GameStatus reason) = 0; - virtual void ShortenTENCalls() = 0; + virtual void OnUseItem(GAME_OBJECT_ID objectNumber) = 0; + virtual void ShortenTENCalls() = 0; virtual void FreeLevelScripts() = 0; virtual void ResetScripts(bool clearGameVars) = 0; virtual void ExecuteScriptFile(const std::string& luaFileName) = 0; diff --git a/TombEngine/Scripting/Include/ScriptInterfaceLevel.h b/TombEngine/Scripting/Include/ScriptInterfaceLevel.h index 072bee854..f1c442cca 100644 --- a/TombEngine/Scripting/Include/ScriptInterfaceLevel.h +++ b/TombEngine/Scripting/Include/ScriptInterfaceLevel.h @@ -48,4 +48,19 @@ public: virtual int GetSecrets() const = 0; virtual std::string GetAmbientTrack() const = 0; virtual bool GetResetHubEnabled() const = 0; + + // Lens flare getters + virtual bool GetLensFlareEnabled() const = 0; + virtual int GetLensFlareSunSpriteID() const = 0; + virtual short GetLensFlarePitch() const = 0; + virtual short GetLensFlareYaw() const = 0; + virtual Color GetLensFlareColor() const = 0; + + // Starfield getters + virtual bool GetStarfieldStarsEnabled() const = 0; + virtual bool GetStarfieldMeteorsEnabled() const = 0; + virtual int GetStarfieldStarCount() const = 0; + virtual int GetStarfieldMeteorCount() const = 0; + virtual int GetStarfieldMeteorSpawnDensity() const = 0; + virtual float GetStarfieldMeteorVelocity() const = 0; }; diff --git a/TombEngine/Scripting/Internal/LanguageScript.h b/TombEngine/Scripting/Internal/LanguageScript.h index a2c04feaf..69d63698b 100644 --- a/TombEngine/Scripting/Internal/LanguageScript.h +++ b/TombEngine/Scripting/Internal/LanguageScript.h @@ -3,7 +3,7 @@ // std::string ID macros #define STRING_WINDOW_TITLE "window_title" #define STRING_PASSPORT "passport" -#define STRING_LARA_HOME "lara_home" +#define STRING_HOME_LEVEL "home_level" #define STRING_CONTROLS "controls" #define STRING_DISPLAY "display" #define STRING_OTHER_SETTINGS "other_settings" @@ -11,7 +11,7 @@ #define STRING_LOAD_GAME "load_game" #define STRING_SAVE_GAME "save_game" #define STRING_EXIT_GAME "exit_game" -#define STRING_EXIT_TO_TITLE "exit_to_title" +#define STRING_EXIT_TO_TITLE "exit_to_title" #define STRING_OPTIONS "options" #define STRING_UZIS "uzis" #define STRING_PISTOLS "pistols" @@ -71,6 +71,7 @@ #define STRING_VOLUMETRIC_FOG "volumetric_fog" #define STRING_ANTIALIASING "antialiasing" #define STRING_AMBIENT_OCCLUSION "ambient_occlusion" +#define STRING_HIGH_FRAMERATE "high_framerate" #define STRING_ANTIALIASING_NONE "none" #define STRING_ANTIALIASING_LOW "low" #define STRING_ANTIALIASING_MEDIUM "medium" @@ -80,13 +81,17 @@ #define STRING_SOUND "sound" #define STRING_ENABLE_SOUND "enable_sound" #define STRING_REVERB "reverb" -#define STRING_AUTOMATIC_TARGETING "automatic_targeting" +#define STRING_AUTO_MONKEY_SWING_JUMP "auto_monkey_swing_jump" +#define STRING_AUTO_TARGETING "auto_targeting" #define STRING_TARGET_HIGHLIGHTER "target_highlighter" #define STRING_RUMBLE "rumble" #define STRING_THUMBSTICK_CAMERA "thumbstick_camera" #define STRING_SUBTITLES "subtitles" +#define STRING_MENU_OPT_LOOP "menu_option_looping" +#define STRING_MENU_OPT_LOOP_ALL_MENUS "menu_option_looping_all_menus" +#define STRING_MENU_OPT_LOOP_DISABLED "menu_option_looping_disabled" +#define STRING_MENU_OPT_LOOP_SAVE_LOAD_ONLY "menu_option_looping_save_load_only" #define STRING_MOUSE_SENSITIVITY "mouse_sensitivity" -#define STRING_MOUSE_SMOOTHING "mouse_smoothing" #define STRING_ACTIONS_FORWARD "actions_forward" #define STRING_ACTIONS_BACKWARD "actions_backward" #define STRING_ACTIONS_LEFT "actions_left" diff --git a/TombEngine/Scripting/Internal/LuaHandler.cpp b/TombEngine/Scripting/Internal/LuaHandler.cpp index acbb76621..1b3140f9a 100644 --- a/TombEngine/Scripting/Internal/LuaHandler.cpp +++ b/TombEngine/Scripting/Internal/LuaHandler.cpp @@ -4,18 +4,18 @@ #include #include "Scripting/Internal/LuaHandler.h" -LuaHandler::LuaHandler(sol::state* lua) : m_lua{ lua } +LuaHandler::LuaHandler(sol::state* lua) : _lua{ lua } { } void LuaHandler::ResetGlobals() { - auto mt = sol::table{ *m_lua, sol::create }; - m_globals = sol::table{ *m_lua, sol::create }; - mt.set(sol::meta_function::new_index, m_globals); - mt.set(sol::meta_function::index, m_globals); + auto mt = sol::table{ *_lua, sol::create }; + _globals = sol::table{ *_lua, sol::create }; + mt.set(sol::meta_function::new_index, _globals); + mt.set(sol::meta_function::index, _globals); - m_lua->set(sol::metatable_key, mt); + _lua->set(sol::metatable_key, mt); } void LuaHandler::ExecuteScript(const std::string& luaFilename, bool isOptional) @@ -23,7 +23,7 @@ void LuaHandler::ExecuteScript(const std::string& luaFilename, bool isOptional) if (isOptional && !std::filesystem::is_regular_file(luaFilename)) return; - auto result = m_lua->safe_script_file(luaFilename, sol::script_pass_on_error); + auto result = _lua->safe_script_file(luaFilename, sol::script_pass_on_error); if (!result.valid()) { sol::error error = result; @@ -33,7 +33,7 @@ void LuaHandler::ExecuteScript(const std::string& luaFilename, bool isOptional) void LuaHandler::ExecuteString(const std::string& command) { - auto result = m_lua->safe_script(command, sol::environment(m_lua->lua_state(), sol::create, m_lua->globals()), sol::script_pass_on_error); + auto result = _lua->safe_script(command, sol::environment(_lua->lua_state(), sol::create, _lua->globals()), sol::script_pass_on_error); if (!result.valid()) { sol::error error = result; diff --git a/TombEngine/Scripting/Internal/LuaHandler.h b/TombEngine/Scripting/Internal/LuaHandler.h index 050e93dac..53e59eaba 100644 --- a/TombEngine/Scripting/Internal/LuaHandler.h +++ b/TombEngine/Scripting/Internal/LuaHandler.h @@ -6,8 +6,8 @@ class LuaHandler { protected: - sol::state* m_lua; - sol::table m_globals; + sol::state* _lua; + sol::table _globals; public: LuaHandler(sol::state* lua); @@ -22,17 +22,17 @@ public: sol::state* GetState() { - return m_lua; + return _lua; }; template void MakeReadOnlyTable(sol::table parent, const std::string& tableName, const T& container) { // Put all data into metatable. auto metatable = tableName + "Meta"; - m_lua->set(metatable, sol::as_table(container)); + _lua->set(metatable, sol::as_table(container)); auto mtmt = tableName + "MetaMeta"; - auto mtmtTable = m_lua->create_named_table(mtmt); + auto mtmtTable = _lua->create_named_table(mtmt); // Make metatable's metatable's __index fail an assert to generate warning/error when trying to use missing variable. auto lam = [tableName](sol::table tab, std::string const& key) @@ -41,29 +41,29 @@ public: }; mtmtTable[sol::meta_method::index] = lam; - m_lua->safe_script("setmetatable(" + metatable + ", " + mtmt + ")"); + _lua->safe_script("setmetatable(" + metatable + ", " + mtmt + ")"); // Make metatable's __index refer to itself so that requests to main table will go through to metatable // (and thus container's members). - m_lua->safe_script(metatable + ".__index = " + metatable); + _lua->safe_script(metatable + ".__index = " + metatable); - m_lua->safe_script(metatable + ".__type = \"readonly\""); + _lua->safe_script(metatable + ".__type = \"readonly\""); // Don't allow table to have new elements put into it. - m_lua->safe_script(metatable + ".__newindex = function() error('" + tableName + " is read-only') end"); + _lua->safe_script(metatable + ".__newindex = function() error('" + tableName + " is read-only') end"); // Protect metatable. - m_lua->safe_script(metatable + ".__metatable = 'metatable is protected'"); + _lua->safe_script(metatable + ".__metatable = 'metatable is protected'"); - auto tab = m_lua->create_named_table(tableName); + auto tab = _lua->create_named_table(tableName); - m_lua->safe_script("setmetatable(" + tableName + ", " + metatable + ")"); + _lua->safe_script("setmetatable(" + tableName + ", " + metatable + ")"); // Point initial metatable variable away from its contents. This is just for cleanliness. parent.set(tableName, tab); - m_lua->safe_script(tableName + " = nil"); - m_lua->safe_script(metatable + " = nil"); - m_lua->safe_script(mtmt + " = nil"); + _lua->safe_script(tableName + " = nil"); + _lua->safe_script(metatable + " = nil"); + _lua->safe_script(mtmt + " = nil"); } }; diff --git a/TombEngine/Scripting/Internal/ReservedScriptNames.h b/TombEngine/Scripting/Internal/ReservedScriptNames.h index e8821218c..42022655e 100644 --- a/TombEngine/Scripting/Internal/ReservedScriptNames.h +++ b/TombEngine/Scripting/Internal/ReservedScriptNames.h @@ -22,10 +22,8 @@ static constexpr char ScriptReserved_Sink[] = "Sink"; static constexpr char ScriptReserved_SoundSource[] = "SoundSource"; static constexpr char ScriptReserved_AIObject[] = "AIObject"; static constexpr char ScriptReserved_Volume[] = "Volume"; -static constexpr char ScriptReserved_Room[] = "Room"; static constexpr char ScriptReserved_Color[] = "Color"; static constexpr char ScriptReserved_DisplayString[] = "DisplayString"; -static constexpr char ScriptReserved_Vec2[] = "Vec2"; static constexpr char ScriptReserved_Rotation[] = "Rotation"; static constexpr char ScriptReserved_LevelFunc[] = "LevelFunc"; @@ -53,14 +51,6 @@ static constexpr char ScriptReserved_DisplayStringSetScale[] = "SetScale"; static constexpr char ScriptReserved_DisplayStringSetColor[] = "SetColor"; static constexpr char ScriptReserved_DisplaySpriteDraw[] = "Draw"; -// Built-in LevelFuncs -static constexpr char ScriptReserved_OnStart[] = "OnStart"; -static constexpr char ScriptReserved_OnLoad[] = "OnLoad"; -static constexpr char ScriptReserved_OnLoop[] = "OnLoop"; -static constexpr char ScriptReserved_OnControlPhase[] = "OnControlPhase"; // DEPRECATED -static constexpr char ScriptReserved_OnSave[] = "OnSave"; -static constexpr char ScriptReserved_OnEnd[] = "OnEnd"; - static constexpr char ScriptReserved_EndReasonExitToTitle[] = "EXITTOTITLE"; static constexpr char ScriptReserved_EndReasonLevelComplete[] = "LEVELCOMPLETE"; static constexpr char ScriptReserved_EndReasonLoadGame[] = "LOADGAME"; @@ -81,15 +71,25 @@ static constexpr char ScriptReserved_PostLoop[] = "POSTLOOP"; static constexpr char ScriptReserved_PreControlPhase[] = "PRECONTROLPHASE"; static constexpr char ScriptReserved_PostControlPhase[] = "POSTCONTROLPHASE"; -// Event types -static constexpr char ScriptReserved_EventOnEnter[] = "ENTER"; -static constexpr char ScriptReserved_EventOnInside[] = "INSIDE"; -static constexpr char ScriptReserved_EventOnLeave[] = "LEAVE"; -static constexpr char ScriptReserved_EventOnLoad[] = "LOAD"; -static constexpr char ScriptReserved_EventOnSave[] = "SAVE"; -static constexpr char ScriptReserved_EventOnStart[] = "START"; -static constexpr char ScriptReserved_EventOnEnd[] = "END"; -static constexpr char ScriptReserved_EventOnLoop[] = "LOOP"; +// Built-in LevelFuncs +static constexpr char ScriptReserved_OnStart[] = "OnStart"; +static constexpr char ScriptReserved_OnLoad[] = "OnLoad"; +static constexpr char ScriptReserved_OnLoop[] = "OnLoop"; +static constexpr char ScriptReserved_OnControlPhase[] = "OnControlPhase"; // DEPRECATED +static constexpr char ScriptReserved_OnSave[] = "OnSave"; +static constexpr char ScriptReserved_OnEnd[] = "OnEnd"; +static constexpr char ScriptReserved_OnUseItem[] = "OnUseItem"; + +// Event types (volume events + global events) +static constexpr char ScriptReserved_EventOnEnter[] = "ENTER"; +static constexpr char ScriptReserved_EventOnInside[] = "INSIDE"; +static constexpr char ScriptReserved_EventOnLeave[] = "LEAVE"; +static constexpr char ScriptReserved_EventOnStart[] = "START"; +static constexpr char ScriptReserved_EventOnLoad[] = "LOAD"; +static constexpr char ScriptReserved_EventOnLoop[] = "LOOP"; +static constexpr char ScriptReserved_EventOnSave[] = "SAVE"; +static constexpr char ScriptReserved_EventOnEnd[] = "END"; +static constexpr char ScriptReserved_EventOnUseItem[] = "USEITEM"; // Member functions static constexpr char ScriptReserved_New[] = "New"; @@ -120,6 +120,7 @@ static constexpr char ScriptReserved_SetFlags[] = "SetFlags"; static constexpr char ScriptReserved_SetTranslated[] = "SetTranslated"; static constexpr char ScriptReserved_GetPosition[] = "GetPosition"; static constexpr char ScriptReserved_GetJointPosition[] = "GetJointPosition"; +static constexpr char ScriptReserved_GetJointRotation[] = "GetJointRotation"; static constexpr char ScriptReserved_SetPosition[] = "SetPosition"; static constexpr char ScriptReserved_GetRotation[] = "GetRotation"; static constexpr char ScriptReserved_SetRotation[] = "SetRotation"; @@ -148,6 +149,7 @@ static constexpr char ScriptReserved_SetFrameNumber[] = "SetFrame"; static constexpr char ScriptReserved_GetAnimNumber[] = "GetAnim"; static constexpr char ScriptReserved_SetAnimNumber[] = "SetAnim"; static constexpr char ScriptReserved_GetStateNumber[] = "GetState"; +static constexpr char ScriptReserved_GetTargetStateNumber[] = "GetTargetState"; static constexpr char ScriptReserved_SetStateNumber[] = "SetState"; static constexpr char ScriptReserved_GetOCB[] = "GetOCB"; static constexpr char ScriptReserved_SetOCB[] = "SetOCB"; @@ -196,7 +198,6 @@ static constexpr char ScriptReserved_IsMoveableInside[] = "IsMoveableInside"; static constexpr char ScriptReserved_GetFlag[] = "GetFlag"; static constexpr char ScriptReserved_SetFlag[] = "SetFlag"; static constexpr char ScriptReserved_IsTagPresent[] = "IsTagPresent"; -static constexpr char ScriptReserved_SetReverbType[] = "SetReverbType"; // Flow Functions static constexpr char ScriptReserved_AddLevel[] = "AddLevel"; @@ -221,12 +222,14 @@ static constexpr char ScriptReserved_EnableFlyCheat[] = "EnableFlyCheat"; static constexpr char ScriptReserved_EnableMassPickup[] = "EnableMassPickup"; static constexpr char ScriptReserved_EnableLaraInTitle[] = "EnableLaraInTitle"; static constexpr char ScriptReserved_EnableLevelSelect[] = "EnableLevelSelect"; +static constexpr char ScriptReserved_EnableHomeLevel[] = "EnableHomeLevel"; static constexpr char ScriptReserved_EnableLoadSave[] = "EnableLoadSave"; static constexpr char ScriptReserved_EnablePointFilter[] = "EnablePointFilter"; // Flow Functions static constexpr char ScriptReserved_SetStrings[] = "SetStrings"; static constexpr char ScriptReserved_GetString[] = "GetString"; +static constexpr char ScriptReserved_IsStringPresent[] = "IsStringPresent"; static constexpr char ScriptReserved_SetLanguageNames[] = "SetLanguageNames"; // Flow Tables @@ -259,6 +262,9 @@ static constexpr char ScriptReserved_GiveInvItem[] = "GiveItem"; static constexpr char ScriptReserved_TakeInvItem[] = "TakeItem"; static constexpr char ScriptReserved_GetInvItemCount[] = "GetItemCount"; static constexpr char ScriptReserved_SetInvItemCount[] = "SetItemCount"; +static constexpr char ScriptReserved_GetUsedItem[] = "GetUsedItem"; +static constexpr char ScriptReserved_SetUsedItem[] = "SetUsedItem"; +static constexpr char ScriptReserved_ClearUsedItem[] = "ClearUsedItem"; static constexpr char ScriptReserved_GetMoveableByName[] = "GetMoveableByName"; static constexpr char ScriptReserved_GetStaticByName[] = "GetStaticByName"; static constexpr char ScriptReserved_GetMoveablesBySlot[] = "GetMoveablesBySlot"; @@ -313,6 +319,7 @@ static constexpr char ScriptReserved_KeyIsHeld[] = "KeyIsHeld"; static constexpr char ScriptReserved_KeyIsHit[] = "KeyIsHit"; static constexpr char ScriptReserved_KeyPush[] = "KeyPush"; static constexpr char ScriptReserved_KeyClear[] = "KeyClear"; +static constexpr char ScriptReserved_KeyClearAll[] = "KeyClearAll"; static constexpr char ScriptReserved_FlipMap[] = "FlipMap"; static constexpr char ScriptReserved_GetFlipMapStatus[] = "GetFlipMapStatus"; @@ -364,22 +371,39 @@ static constexpr char ScriptReserved_LogLevelError[] = "ERROR"; // Internal static constexpr char ScriptReserved_LaraObject[] = "LaraObject"; +// Room + +constexpr char ScriptReserved_Room[] = "Room"; +constexpr char ScriptReserved_RoomGetActive[] = "GetActive"; +constexpr char ScriptReserved_RoomGetColor[] = "GetColor"; +constexpr char ScriptReserved_RoomGetFlag[] = "GetFlag"; +constexpr char ScriptReserved_RoomGetName[] = "GetName"; +constexpr char ScriptReserved_RoomGetReverbType[] = "GetReverbType"; +constexpr char ScriptReserved_RoomGetRoomNumber[] = "GetRoomNumber"; +constexpr char ScriptReserved_RoomIsTagPresent[] = "IsTagPresent"; +constexpr char ScriptReserved_RoomSetFlag[] = "SetFlag"; +constexpr char ScriptReserved_RoomSetName[] = "SetName"; +constexpr char ScriptReserved_RoomSetReverbType[] = "SetReverbType"; + // Vec2 + +constexpr char ScriptReserved_Vec2[] = "Vec2"; +constexpr char ScriptReserved_Vec2Cross[] = "Cross"; +constexpr char ScriptReserved_Vec2Distance[] = "Distance"; +constexpr char ScriptReserved_Vec2Dot[] = "Dot"; +constexpr char ScriptReserved_Vec2Length[] = "Length"; +constexpr char ScriptReserved_Vec2Lerp[] = "Lerp"; constexpr char ScriptReserved_Vec2SetLength[] = "ToLength"; constexpr char ScriptReserved_Vec2Normalize[] = "Normalize"; constexpr char ScriptReserved_Vec2Rotate[] = "Rotate"; -constexpr char ScriptReserved_Vec2Lerp[] = "Lerp"; -constexpr char ScriptReserved_Vec2Cross[] = "Cross"; -constexpr char ScriptReserved_Vec2Dot[] = "Dot"; -constexpr char ScriptReserved_Vec2Distance[] = "Distance"; -constexpr char ScriptReserved_Vec2Length[] = "Length"; // Vec3 + constexpr char ScriptReserved_Vec3[] = "Vec3"; +constexpr char ScriptReserved_Vec3Cross[] = "Cross"; +constexpr char ScriptReserved_Vec3Distance[] = "Distance"; +constexpr char ScriptReserved_Vec3Dot[] = "Dot"; +constexpr char ScriptReserved_Vec3Length[] = "Length"; +constexpr char ScriptReserved_Vec3Lerp[] = "Lerp"; constexpr char ScriptReserved_Vec3Normalize[] = "Normalize"; constexpr char ScriptReserved_Vec3Rotate[] = "Rotate"; -constexpr char ScriptReserved_Vec3Lerp[] = "Lerp"; -constexpr char ScriptReserved_Vec3Cross[] = "Cross"; -constexpr char ScriptReserved_Vec3Dot[] = "Dot"; -constexpr char ScriptReserved_Vec3Distance[] = "Distance"; -constexpr char ScriptReserved_Vec3Length[] = "Length"; diff --git a/TombEngine/Scripting/Internal/TEN/Color/Color.cpp b/TombEngine/Scripting/Internal/TEN/Color/Color.cpp index ab08c199c..88af10b3f 100644 --- a/TombEngine/Scripting/Internal/TEN/Color/Color.cpp +++ b/TombEngine/Scripting/Internal/TEN/Color/Color.cpp @@ -1,150 +1,147 @@ #include "framework.h" #include "Scripting/Internal/TEN/Color/Color.h" -#include - -/*** -An RGBA or RGB color. -Components are specified in bytes; all values are clamped to [0, 255]. - -@tenprimitive Color -@pragma nostrip -*/ +/// Represents an RGBA or RGB color. +// Components are specified in bytes. All values are clamped to the range [0, 255]. +// +// @tenprimitive Color +// @pragma nostrip void ScriptColor::Register(sol::table& parent) { using ctors = sol::constructors; + + // Register type. parent.new_usertype( "Color", ctors(), sol::call_constructor, ctors(), sol::meta_function::to_string, &ScriptColor::ToString, -/// (int) red component -//@mem r + /// (int) Red component. + // @mem r "r", sol::property(&ScriptColor::GetR, &ScriptColor::SetR), -/// (int) green component -//@mem g + /// (int) Green component. + // @mem g "g", sol::property(&ScriptColor::GetG, &ScriptColor::SetG), -/// (int) blue component -//@mem b + /// (int) Blue component. + // @mem b "b", sol::property(&ScriptColor::GetB, &ScriptColor::SetB), -/// (int) alpha component (255 = opaque, 0 = invisible) -//@mem a - "a", sol::property(&ScriptColor::GetA, &ScriptColor::SetA) - ); + /// (int) Alpha component (0 = invisible, 255 = opaque). + // @mem a + "a", sol::property(&ScriptColor::GetA, &ScriptColor::SetA)); } -/*** -@int R red component -@int G green component -@int B blue component -@treturn Color A new Color object. -@function Color -*/ +/// @int R red component +// @int G green component +// @int B blue component +// @treturn Color A new Color object. +// @function Color ScriptColor::ScriptColor(byte r, byte g, byte b) : -m_color(r, g, b) +_color(r, g, b) { } -/*** -@int R red component -@int G green component -@int B blue component -@int A alpha component (255 is opaque, 0 is invisible) -@treturn Color A new Color object. -@function Color -*/ -ScriptColor::ScriptColor(byte r, byte g, byte b, byte a) : ScriptColor(r, g, b) +// @function Color() +// @int R Red component. +// @int G Green component. +// @int B Blue component. +// @int A Alpha component (0 = invisible, 255 = opaque). +// @treturn Color A new Color object. +ScriptColor::ScriptColor(byte r, byte g, byte b, byte a) : + ScriptColor(r, g, b) { SetA(a); } ScriptColor::ScriptColor(const Vector3& color) : - m_color(color) + _color(color) { } ScriptColor::ScriptColor(const Vector4& color) : - m_color(color) + _color(color) { } ScriptColor::ScriptColor(D3DCOLOR color) : - m_color(color) + _color(color) { } -ScriptColor::operator Vector3() const -{ - return m_color; -} - -ScriptColor::operator Vector4() const -{ - return m_color; -} - -// D3DCOLOR is 32 bits and is laid out as ARGB. -ScriptColor::operator D3DCOLOR() const -{ - return m_color; -} - -ScriptColor::operator RGBAColor8Byte() const -{ - return m_color; -} - byte ScriptColor::GetR() const { - return m_color.GetR(); -} - -void ScriptColor::SetR(byte value) -{ - m_color.SetR(value); + return _color.GetR(); } byte ScriptColor::GetG() const { - return m_color.GetG(); -} - -void ScriptColor::SetG(byte value) -{ - m_color.SetG(value); + return _color.GetG(); } byte ScriptColor::GetB() const { - return m_color.GetB(); -} - -void ScriptColor::SetB(byte value) -{ - m_color.SetB(value); + return _color.GetB(); } byte ScriptColor::GetA() const { - return m_color.GetA(); + return _color.GetA(); +} + +void ScriptColor::SetR(byte value) +{ + _color.SetR(value); +} + +void ScriptColor::SetG(byte value) +{ + _color.SetG(value); +} + +void ScriptColor::SetB(byte value) +{ + _color.SetB(value); } void ScriptColor::SetA(byte value) { - m_color.SetA(value); + _color.SetA(value); } -/*** -@tparam Color color this color -@treturn string A string showing the r, g, b, and a values of the color -@function __tostring -*/ +/// @tparam Color color This color. +// @treturn string A string representing the r, g, b, and a values of the color. +// @function __tostring std::string ScriptColor::ToString() const { - return "{" + std::to_string(GetR()) + ", " + std::to_string(GetG()) + ", " + std::to_string(GetB()) + ", " + std::to_string(GetA()) + "}"; + return "{ " + std::to_string(GetR()) + ", " + std::to_string(GetG()) + ", " + std::to_string(GetB()) + ", " + std::to_string(GetA()) + " }"; +} + +ScriptColor::operator Color() const +{ + return _color; +} + +ScriptColor::operator Vector3() const +{ + return _color; +} + +ScriptColor::operator Vector4() const +{ + return _color; +} + +// NOTE: D3DCOLOR is 32 bits and is laid out as ARGB. +ScriptColor::operator D3DCOLOR() const +{ + return _color; +} + +ScriptColor::operator RGBAColor8Byte() const +{ + return _color; } diff --git a/TombEngine/Scripting/Internal/TEN/Color/Color.h b/TombEngine/Scripting/Internal/TEN/Color/Color.h index 03f75630d..701d133d5 100644 --- a/TombEngine/Scripting/Internal/TEN/Color/Color.h +++ b/TombEngine/Scripting/Internal/TEN/Color/Color.h @@ -11,32 +11,39 @@ namespace sol class ScriptColor { +private: + // Members + RGBAColor8Byte _color; + public: + static void Register(sol::table& parent); + + // Constructors ScriptColor(byte r, byte g, byte b); ScriptColor(byte r, byte g, byte b, byte a); ScriptColor(const Vector3& color); ScriptColor(const Vector4& color); ScriptColor(D3DCOLOR); + // Getters byte GetR() const; byte GetG() const; byte GetB() const; byte GetA() const; + // Setters void SetR(byte value); void SetG(byte value); void SetB(byte value); void SetA(byte value); + // Converters std::string ToString() const; + // Operators + operator Color() const; operator Vector3() const; operator Vector4() const; operator D3DCOLOR() const; operator RGBAColor8Byte() const; - - static void Register(sol::table& parent); - -private: - RGBAColor8Byte m_color; }; diff --git a/TombEngine/Scripting/Internal/TEN/DisplaySprite/ScriptDisplaySprite.cpp b/TombEngine/Scripting/Internal/TEN/DisplaySprite/ScriptDisplaySprite.cpp index 12529806d..285a3a9ad 100644 --- a/TombEngine/Scripting/Internal/TEN/DisplaySprite/ScriptDisplaySprite.cpp +++ b/TombEngine/Scripting/Internal/TEN/DisplaySprite/ScriptDisplaySprite.cpp @@ -12,12 +12,10 @@ using TEN::Renderer::g_Renderer; -/*** -Represents a display sprite. - -@tenclass View.DisplaySprite -@pragma nostrip -*/ +/// Represents a display sprite. +// +// @tenclass View.DisplaySprite +// @pragma nostrip namespace TEN::Scripting::DisplaySprite { @@ -50,7 +48,7 @@ namespace TEN::Scripting::DisplaySprite } /// Create a DisplaySprite object. - // @function DisplaySprite + // @function DisplaySprite() // @tparam Objects.ObjID ID of the sprite sequence object. // @tparam int int spriteID ID of the sprite in the sequence. // @tparam Vec2 pos Display position in percent. @@ -190,7 +188,7 @@ namespace TEN::Scripting::DisplaySprite constexpr auto DEFAULT_SCALE_MODE = DisplaySpriteScaleMode::Fit; constexpr auto DEFAULT_BLEND_MODE = BlendMode::AlphaBlend; - // Object is not a sprite sequence object; return early. + // Object is not a sprite sequence; return early. if (_objectID < GAME_OBJECT_ID::ID_HORIZON || _objectID >= GAME_OBJECT_ID::ID_NUMBER_OBJECTS) { TENLog("Attempted to draw display sprite from non-sprite sequence object " + std::to_string(_objectID), LogLevel::Warning); @@ -220,6 +218,7 @@ namespace TEN::Scripting::DisplaySprite priority.value_or(DEFAULT_PRIORITY), alignMode.value_or(DEFAULT_ALIGN_MODE), scaleMode.value_or(DEFAULT_SCALE_MODE), - blendMode.value_or(DEFAULT_BLEND_MODE)); + blendMode.value_or(DEFAULT_BLEND_MODE), + DisplaySpritePhase::Control); } } diff --git a/TombEngine/Scripting/Internal/TEN/Effects/EffectsFunctions.cpp b/TombEngine/Scripting/Internal/TEN/Effects/EffectsFunctions.cpp index e76903b6b..9514e38e8 100644 --- a/TombEngine/Scripting/Internal/TEN/Effects/EffectsFunctions.cpp +++ b/TombEngine/Scripting/Internal/TEN/Effects/EffectsFunctions.cpp @@ -284,7 +284,7 @@ namespace TEN::Scripting::Effects */ static void EmitFire(Vec3 pos, TypeOrNil size) { - AddFire(pos.x, pos.y, pos.z, FindRoomNumber(Vector3i(pos.x, pos.y, pos.z)), USE_IF_HAVE(float, size, 1), 0); + AddFire(pos.x, pos.y, pos.z, FindRoomNumber(Vector3i(pos.x, pos.y, pos.z)), USE_IF_HAVE(float, size, 1)); } /***Make an explosion. Does not hurt Lara diff --git a/TombEngine/Scripting/Internal/TEN/Flow/Animations/Animations.cpp b/TombEngine/Scripting/Internal/TEN/Flow/Animations/Animations.cpp index 856472784..eb820fb21 100644 --- a/TombEngine/Scripting/Internal/TEN/Flow/Animations/Animations.cpp +++ b/TombEngine/Scripting/Internal/TEN/Flow/Animations/Animations.cpp @@ -15,11 +15,12 @@ void Animations::Register(sol::table& parent) "crawlExtended", &Animations::HasCrawlExtended, "crouchRoll", &Animations::HasCrouchRoll, "crawlspaceSwandive", &Animations::HasCrawlspaceDive, - "monkeyAutoJump", &Animations::HasMonkeyAutoJump, "overhangClimb", &Animations::HasOverhangClimb, "slideExtended", &Animations::HasSlideExtended, "sprintJump", &Animations::HasSprintJump, "pose", &Animations::HasPose, - "ledgeJumps", &Animations::HasLedgeJumps - ); + "ledgeJumps", &Animations::HasLedgeJumps, + + // NOTE: Removed. Keep for now to maintain compatibility. -- Sezz 2024.06.06 + "monkeyAutoJump", & Animations::HasAutoMonkeySwingJump); } diff --git a/TombEngine/Scripting/Internal/TEN/Flow/Animations/Animations.h b/TombEngine/Scripting/Internal/TEN/Flow/Animations/Animations.h index 763fc789c..781f62653 100644 --- a/TombEngine/Scripting/Internal/TEN/Flow/Animations/Animations.h +++ b/TombEngine/Scripting/Internal/TEN/Flow/Animations/Animations.h @@ -13,12 +13,14 @@ struct Animations bool HasPose; // Crossed arms AFK posing. bool HasSlideExtended; // Extended slope sliding functionality (not ready yet). bool HasSprintJump; // Sprint jump. - bool HasMonkeyAutoJump; // Auto jump to monkey swing when pressing UP + ACTION. TODO: Make this a player setting. bool HasCrawlspaceDive; // Dive into crawlspaces. bool HasCrawlExtended; // Extended crawl moveset. bool HasCrouchRoll; // Crouch roll. bool HasOverhangClimb; // Overhang functionality. bool HasLedgeJumps; // Jump up or back from a ledge. + // NOTE: Removed. Keep for now to maintain compatibility. -- Sezz 2024.06.06 + bool HasAutoMonkeySwingJump; + static void Register(sol::table&); }; diff --git a/TombEngine/Scripting/Internal/TEN/Flow/FlowHandler.cpp b/TombEngine/Scripting/Internal/TEN/Flow/FlowHandler.cpp index 5240789f8..af4c9a948 100644 --- a/TombEngine/Scripting/Internal/TEN/Flow/FlowHandler.cpp +++ b/TombEngine/Scripting/Internal/TEN/Flow/FlowHandler.cpp @@ -30,7 +30,7 @@ ScriptInterfaceStringsHandler* g_GameStringsHandler; ScriptInterfaceFlowHandler* g_GameFlow; FlowHandler::FlowHandler(sol::state* lua, sol::table& parent) : - m_handler(lua) + _handler(lua) { /*** gameflow.lua. These functions are called in gameflow.lua, a file loosely equivalent to winroomedit's SCRIPT.DAT. @@ -38,7 +38,7 @@ They handle a game's 'metadata'; i.e., things such as level titles, loading scre ambient tracks. @section Flowlua */ - sol::table tableFlow{ m_handler.GetState()->lua_state(), sol::create }; + sol::table tableFlow{ _handler.GetState()->lua_state(), sol::create }; parent.set(ScriptReserved_Flow, tableFlow); /*** @@ -77,21 +77,23 @@ Must be true or false */ tableFlow.set_function(ScriptReserved_EnableLevelSelect, &FlowHandler::EnableLevelSelect, this); - /*** Enable or disable saving and loading of savegames. - @function EnableLoadSave - @tparam bool enabled true or false. - */ + /// Enable or disable Home Level entry in the main menu. + // @function EnableHomeLevel() + // @tparam bool enabled True or false. + tableFlow.set_function(ScriptReserved_EnableHomeLevel, &FlowHandler::EnableHomeLevel, this); + + /// Enable or disable saving and loading of savegames. + // @function EnableLoadSave() + // @tparam bool enabled True or false. tableFlow.set_function(ScriptReserved_EnableLoadSave, &FlowHandler::EnableLoadSave, this); /*** gameflow.lua or level scripts. @section FlowluaOrScripts */ -/*** Enable or disable DOZY mode (fly cheat). -Must be true or false -@function EnableFlyCheat -@tparam bool enabled true or false -*/ + /// Enable or disable the fly cheat. + // @function EnableFlyCheat() + // @tparam bool enabled True or false. tableFlow.set_function(ScriptReserved_EnableFlyCheat, &FlowHandler::EnableFlyCheat, this); /*** Enable or disable point texture filter. @@ -246,6 +248,12 @@ You will not need to call them manually. */ tableFlow.set_function(ScriptReserved_GetString, &FlowHandler::GetString, this); +/*** Check if translated string is present. +@function IsStringPresent +@tparam key string key for translated string +*/ + tableFlow.set_function(ScriptReserved_IsStringPresent, &FlowHandler::IsStringPresent, this); + /*** Set language names for translations. Specify which translations in the strings table correspond to which languages. @function SetLanguageNames @@ -264,13 +272,15 @@ Specify which translations in the strings table correspond to which languages. Animations::Register(tableFlow); Settings::Register(tableFlow); Fog::Register(tableFlow); - - m_handler.MakeReadOnlyTable(tableFlow, ScriptReserved_WeatherType, WEATHER_TYPES); - m_handler.MakeReadOnlyTable(tableFlow, ScriptReserved_LaraType, PLAYER_TYPES); - m_handler.MakeReadOnlyTable(tableFlow, ScriptReserved_RotationAxis, ROTATION_AXES); - m_handler.MakeReadOnlyTable(tableFlow, ScriptReserved_ItemAction, ITEM_MENU_ACTIONS); - m_handler.MakeReadOnlyTable(tableFlow, ScriptReserved_ErrorMode, ERROR_MODES); - m_handler.MakeReadOnlyTable(tableFlow, ScriptReserved_GameStatus, GAME_STATUSES); + LensFlare::Register(tableFlow); + Starfield::Register(tableFlow); + + _handler.MakeReadOnlyTable(tableFlow, ScriptReserved_WeatherType, WEATHER_TYPES); + _handler.MakeReadOnlyTable(tableFlow, ScriptReserved_LaraType, PLAYER_TYPES); + _handler.MakeReadOnlyTable(tableFlow, ScriptReserved_RotationAxis, ROTATION_AXES); + _handler.MakeReadOnlyTable(tableFlow, ScriptReserved_ItemAction, ITEM_MENU_ACTIONS); + _handler.MakeReadOnlyTable(tableFlow, ScriptReserved_ErrorMode, ERROR_MODES); + _handler.MakeReadOnlyTable(tableFlow, ScriptReserved_GameStatus, GAME_STATUSES); } FlowHandler::~FlowHandler() @@ -281,35 +291,35 @@ FlowHandler::~FlowHandler() std::string FlowHandler::GetGameDir() { - return m_gameDir; + return _gameDir; } void FlowHandler::SetGameDir(const std::string& assetDir) { - m_gameDir = assetDir; + _gameDir = assetDir; } void FlowHandler::SetLanguageNames(sol::as_table_t>&& src) { - m_languageNames = std::move(src); + _languageNames = std::move(src); } void FlowHandler::SetStrings(sol::nested>>&& src) { - if (m_translationsMap.empty()) + if (_translationMap.empty()) { - m_translationsMap = std::move(src); + _translationMap = std::move(src); } else { for (auto& stringPair : src.value()) - m_translationsMap.insert_or_assign(stringPair.first, stringPair.second); + _translationMap.insert_or_assign(stringPair.first, stringPair.second); } } void FlowHandler::SetSettings(Settings const & src) { - m_settings = src; + _settings = src; } void FlowHandler::SetAnimations(Animations const& src) @@ -339,10 +349,10 @@ void FlowHandler::SetTotalSecretCount(int secretsNumber) void FlowHandler::LoadFlowScript() { - m_handler.ExecuteScript(m_gameDir + "Scripts/Gameflow.lua"); - m_handler.ExecuteScript(m_gameDir + "Scripts/SystemStrings.lua", true); - m_handler.ExecuteScript(m_gameDir + "Scripts/Strings.lua", true); - m_handler.ExecuteScript(m_gameDir + "Scripts/Settings.lua", true); + _handler.ExecuteScript(_gameDir + "Scripts/Gameflow.lua"); + _handler.ExecuteScript(_gameDir + "Scripts/SystemStrings.lua", true); + _handler.ExecuteScript(_gameDir + "Scripts/Strings.lua", true); + _handler.ExecuteScript(_gameDir + "Scripts/Settings.lua", true); SetScriptErrorMode(GetSettings()->ErrorMode); @@ -359,19 +369,24 @@ void FlowHandler::LoadFlowScript() char const * FlowHandler::GetString(const char* id) const { - if (!ScriptAssert(m_translationsMap.find(id) != m_translationsMap.end(), std::string{ "Couldn't find string " } + id)) + if (!ScriptAssert(_translationMap.find(id) != _translationMap.end(), std::string{ "Couldn't find string " } + id)) { return id; } else { - return m_translationsMap.at(std::string(id)).at(0).c_str(); + return _translationMap.at(std::string(id)).at(0).c_str(); } } +bool FlowHandler::IsStringPresent(const char* id) const +{ + return _translationMap.find(id) != _translationMap.end(); +} + Settings* FlowHandler::GetSettings() { - return &m_settings; + return &_settings; } Level* FlowHandler::GetLevel(int id) @@ -537,9 +552,9 @@ bool FlowHandler::IsFlyCheatEnabled() const return FlyCheat; } -void FlowHandler::EnableFlyCheat(bool flyCheat) +void FlowHandler::EnableFlyCheat(bool enable) { - FlyCheat = flyCheat; + FlyCheat = enable; } bool FlowHandler::IsPointFilterEnabled() const @@ -547,9 +562,9 @@ bool FlowHandler::IsPointFilterEnabled() const return PointFilter; } -void FlowHandler::EnablePointFilter(bool pointFilter) +void FlowHandler::EnablePointFilter(bool enable) { - PointFilter = pointFilter; + PointFilter = enable; } bool FlowHandler::IsMassPickupEnabled() const @@ -557,9 +572,9 @@ bool FlowHandler::IsMassPickupEnabled() const return MassPickup; } -void FlowHandler::EnableMassPickup(bool massPickup) +void FlowHandler::EnableMassPickup(bool enable) { - MassPickup = massPickup; + MassPickup = enable; } bool FlowHandler::IsLaraInTitleEnabled() const @@ -567,14 +582,24 @@ bool FlowHandler::IsLaraInTitleEnabled() const return LaraInTitle; } -void FlowHandler::EnableLaraInTitle(bool laraInTitle) +void FlowHandler::EnableLaraInTitle(bool enable) { - LaraInTitle = laraInTitle; + LaraInTitle = enable; } -void FlowHandler::EnableLevelSelect(bool levelSelect) +void FlowHandler::EnableLevelSelect(bool enable) { - LevelSelect = levelSelect; + LevelSelect = enable; +} + +bool FlowHandler::IsHomeLevelEnabled() const +{ + return HomeLevel; +} + +void FlowHandler::EnableHomeLevel(bool enable) +{ + HomeLevel = enable; } bool FlowHandler::IsLoadSaveEnabled() const @@ -582,9 +607,9 @@ bool FlowHandler::IsLoadSaveEnabled() const return LoadSave; } -void FlowHandler::EnableLoadSave(bool loadSave) +void FlowHandler::EnableLoadSave(bool enable) { - LoadSave = loadSave; + LoadSave = enable; } void FlowHandler::PrepareInventoryObjects() @@ -674,12 +699,20 @@ bool FlowHandler::DoFlow() break; case GameStatus::NewGame: - CurrentLevel = (SelectedLevelForNewGame != 0 ? SelectedLevelForNewGame : 1); + // NOTE: 0 reserved for title level and 1 reserved for home level. + CurrentLevel = (SelectedLevelForNewGame != 0) ? SelectedLevelForNewGame : (IsHomeLevelEnabled() ? 2 : 1); + RequiredStartPos = 0; SelectedLevelForNewGame = 0; InitializeGame = true; break; + case GameStatus::HomeLevel: + CurrentLevel = 1; + RequiredStartPos = 0; + InitializeGame = true; + break; + case GameStatus::LoadGame: // Load header of savegame to get level to load. SaveGame::LoadHeader(SelectedSaveGame, &header); diff --git a/TombEngine/Scripting/Internal/TEN/Flow/FlowHandler.h b/TombEngine/Scripting/Internal/TEN/Flow/FlowHandler.h index b6aa5a417..55092b97d 100644 --- a/TombEngine/Scripting/Internal/TEN/Flow/FlowHandler.h +++ b/TombEngine/Scripting/Internal/TEN/Flow/FlowHandler.h @@ -1,29 +1,27 @@ #pragma once #include +#include "Scripting/Include/Flow/ScriptInterfaceFlowHandler.h" +#include "Scripting/Include/ScriptInterfaceGame.h" #include "Scripting/Internal/LanguageScript.h" #include "Scripting/Internal/LuaHandler.h" -#include "Scripting/Internal/TEN/Logic/LogicHandler.h" #include "Scripting/Internal/TEN/Color/Color.h" +#include "Scripting/Internal/TEN/Logic/LogicHandler.h" +#include "Scripting/Internal/TEN/Flow/Animations/Animations.h" #include "Scripting/Internal/TEN/Flow/Level/FlowLevel.h" #include "Scripting/Internal/TEN/Flow/Settings/Settings.h" -#include "Scripting/Internal/TEN/Flow/Animations/Animations.h" -#include "Scripting/Include/ScriptInterfaceGame.h" -#include "Scripting/Include/Flow/ScriptInterfaceFlowHandler.h" class FlowHandler : public ScriptInterfaceFlowHandler { private: - Settings m_settings; + LuaHandler _handler; + Settings _settings = {}; + std::string _gameDir = {}; - std::unordered_map> m_translationsMap; - std::vector m_languageNames; + std::map _moveableMap = {}; - std::map m_itemsMap; - - std::string m_gameDir; - - LuaHandler m_handler; + std::unordered_map> _translationMap = {}; + std::vector _languageNames = {}; void PrepareInventoryObjects(); @@ -32,10 +30,11 @@ public: int FogOutDistance = 0; bool LevelSelect = true; + bool HomeLevel = false; bool LoadSave = true; bool FlyCheat = true; bool PointFilter = false; - bool MassPickup = true; + bool MassPickup = true; bool LaraInTitle = false; bool DebugMode = false; @@ -52,6 +51,7 @@ public: void AddLevel(Level const& level); void LoadFlowScript(); char const* GetString(const char* id) const; + bool IsStringPresent(const char* id) const; void SetStrings(sol::nested>>&& src); void SetLanguageNames(sol::as_table_t>&& src); void SetAnimations(const Animations& src); @@ -76,27 +76,30 @@ public: void SetTitleScreenImagePath(const std::string& path); void SetTotalSecretCount(int secretsNumber); bool IsFlyCheatEnabled() const; - void EnableFlyCheat(bool flyCheat); + void EnableFlyCheat(bool enable); bool IsPointFilterEnabled() const; - void EnablePointFilter(bool pointFilter); - bool IsMassPickupEnabled() const; - void EnableMassPickup(bool massPickup); + void EnablePointFilter(bool enable); + bool IsMassPickupEnabled() const; + void EnableMassPickup(bool enable); bool IsLaraInTitleEnabled() const; - void EnableLaraInTitle(bool laraInTitle); + void EnableLaraInTitle(bool enable); bool IsLevelSelectEnabled() const; - void EnableLevelSelect(bool laraInTitle); + void EnableLevelSelect(bool enable); + bool IsHomeLevelEnabled() const; + void EnableHomeLevel(bool enable); bool IsLoadSaveEnabled() const; - void EnableLoadSave(bool loadSave); + void EnableLoadSave(bool enable); bool HasCrawlExtended() const override { return Anims.HasCrawlExtended; } bool HasCrouchRoll() const override { return Anims.HasCrouchRoll; } bool HasCrawlspaceDive() const override { return Anims.HasCrawlspaceDive; } - bool HasMonkeyAutoJump() const override { return Anims.HasMonkeyAutoJump; } bool HasAFKPose() const override { return Anims.HasPose; } bool HasOverhangClimb() const override { return Anims.HasOverhangClimb; } bool HasSlideExtended() const override { return Anims.HasSlideExtended; } bool HasSprintJump() const override { return Anims.HasSprintJump; } bool HasLedgeJumps() const override { return Anims.HasLedgeJumps; } bool DoFlow() override; -}; + // NOTE: Removed. Keep for now to maintain compatibility. -- Sezz 2024.06.06 + bool HasAutoMonkeySwingJump() const override { return Anims.HasAutoMonkeySwingJump; } +}; diff --git a/TombEngine/Scripting/Internal/TEN/Flow/LensFlare/LensFlare.cpp b/TombEngine/Scripting/Internal/TEN/Flow/LensFlare/LensFlare.cpp new file mode 100644 index 000000000..8b9664593 --- /dev/null +++ b/TombEngine/Scripting/Internal/TEN/Flow/LensFlare/LensFlare.cpp @@ -0,0 +1,124 @@ +#include "framework.h" +#include "Scripting/Internal/TEN/Flow/LensFlare/LensFlare.h" + +#include "Objects/game_object_ids.h" +#include "Scripting/Internal/TEN/Rotation/Rotation.h" +#include "Specific\level.h" + +/// Represents a lens flare. +// +// @tenclass Flow.LensFlare +// @pragma nostrip + +namespace TEN::Scripting +{ + void LensFlare::Register(sol::table& parent) + { + using ctors = sol::constructors< + LensFlare(float pitch, float yaw, const ScriptColor& color)>; + + // Register type. + parent.new_usertype( + "LensFlare", ctors(), sol::call_constructor, ctors(), + "GetEnabled", &LensFlare::GetEnabled, + "GetSunSpriteID", &LensFlare::GetSunSpriteID, + "GetPitch", &LensFlare::GetPitch, + "GetYaw", &LensFlare::GetYaw, + "GetColor", &LensFlare::GetColor, + "SetSunSpriteID", &LensFlare::SetSunSpriteID, + "SetPitch", &LensFlare::SetPitch, + "SetYaw", &LensFlare::SetYaw, + "SetColor", &LensFlare::SetColor); + } + + /// Create a LensFlare object. + // @function LensFlare() + // @tparam float Pitch angle in degrees. + // @tparam float Yaw angle in degrees. + // @tparam Color Color. + // @treturn LensFlare A new LensFlare object. + LensFlare::LensFlare(float pitch, float yaw, const ScriptColor& color) + { + _isEnabled = true; + _color = color; + _rotation = Rotation(pitch, yaw, 0.0f); + } + + /// Get the lens flare's enabled status. + // @function LensFlare:GetEnabled() + // @treturn bool Lens flare's enabled status. + bool LensFlare::GetEnabled() const + { + return _isEnabled; + } + + /// Get the sun's sprite ID. + // @function LensFlare:GetSunSpriteID() + // @treturn int Sprite ID. + int LensFlare::GetSunSpriteID() const + { + return _sunSpriteID; + } + + /// Get the lens flare's pitch angle in degrees. + // @function LensFlare:GetPitch() + // @treturn float Pitch angle in degrees. + float LensFlare::GetPitch() const + { + return _rotation.x; + } + + /// Get the lens flare's yaw angle in degrees. + // @function LensFlare:GetYaw() + // @treturn float Yaw angle in degrees. + float LensFlare::GetYaw() const + { + return _rotation.y; + } + + /// Get the lens flare's color. + // @function LensFlare:SetColor() + ScriptColor LensFlare::GetColor() const + { + return _color; + } + + /// Set the lens flare's sun sprite ID. + // @function LensFlare:SetSunSpriteID() + // @tparam int New sprite ID. + void LensFlare::SetSunSpriteID(int spriteID) + { + // Sprite ID out of range; return early. + if (spriteID < 0 || g_Level.Sprites.size() > spriteID) + { + TENLog("Sun sprite ID out of range."); + return; + } + + _sunSpriteID = spriteID; + } + + /// Set the lens flare's pitch angle. + // @function LensFlare:SetPitch(float) + // @tparam float New pitch angle in degrees. + void LensFlare::SetPitch(float pitch) + { + _rotation.x = pitch; + } + + /// Set the lens flare's yaw angle. + // @function LensFlare:SetYaw(float) + // @tparam float New yaw angle in degrees. + void LensFlare::SetYaw(float yaw) + { + _rotation.y = yaw; + } + + /// Set the lens flare's color. + // @function LensFlare:SetColor(Color) + // @tparam Color New color. + void LensFlare::SetColor(const ScriptColor& color) + { + _color = color; + } +} diff --git a/TombEngine/Scripting/Internal/TEN/Flow/LensFlare/LensFlare.h b/TombEngine/Scripting/Internal/TEN/Flow/LensFlare/LensFlare.h new file mode 100644 index 000000000..ab450b6e1 --- /dev/null +++ b/TombEngine/Scripting/Internal/TEN/Flow/LensFlare/LensFlare.h @@ -0,0 +1,41 @@ +#pragma once +#include "Objects/game_object_ids.h" +#include "Objects/objectslist.h" +#include "Scripting/Internal/TEN/Color/Color.h" +#include "Scripting/Internal/TEN/Rotation/Rotation.h" + +namespace sol { class state; } + +namespace TEN::Scripting +{ + class LensFlare + { + private: + // Members + int _sunSpriteID = SPRITE_TYPES::SPR_LENS_FLARE_3; + bool _isEnabled = false; + + Rotation _rotation = {}; + ScriptColor _color = 0; + + public: + static void Register(sol::table& table); + + // Constructors + LensFlare() = default; + LensFlare(float pitch, float yaw, const ScriptColor& color); + + // Getters + bool GetEnabled() const; + int GetSunSpriteID() const; + float GetPitch() const; + float GetYaw() const; + ScriptColor GetColor() const; + + // Setters + void SetSunSpriteID(int spriteID); + void SetPitch(float pitch); + void SetYaw(float yaw); + void SetColor(const ScriptColor& color); + }; +} diff --git a/TombEngine/Scripting/Internal/TEN/Flow/Level/FlowLevel.cpp b/TombEngine/Scripting/Internal/TEN/Flow/Level/FlowLevel.cpp index 55067e76a..ed5fe3145 100644 --- a/TombEngine/Scripting/Internal/TEN/Flow/Level/FlowLevel.cpp +++ b/TombEngine/Scripting/Internal/TEN/Flow/Level/FlowLevel.cpp @@ -1,7 +1,10 @@ #include "framework.h" -#include "FlowLevel.h" +#include "Scripting/Internal/TEN/Flow/Level/FlowLevel.h" + #include "Scripting/Internal/ScriptAssert.h" +using namespace TEN::Scripting; + /*** Stores level metadata. These are things things which aren't present in the compiled level file itself. @@ -15,12 +18,14 @@ These are things things which aren't present in the compiled level file itself. @treturn Level a Level object */ void Level::Register(sol::table& parent) -{ - parent.new_usertype("Level", +{ + // Register type. + parent.new_usertype( + "Level", sol::constructors(), sol::call_constructor, sol::constructors(), -/// (string) string key for the level's (localised) name. -// Corresponds to an entry in strings.lua. + + // Corresponds to an entry in strings.lua. //@mem nameKey "nameKey", &Level::NameStringKey, @@ -52,6 +57,14 @@ void Level::Register(sol::table& parent) //@mem layer2 "layer2", &Level::Layer2, + /// (@{Flow.Starfield}) Starfield. + // @mem starfield + "starfield", &Level::Starfield, + + /// (@{Flow.LensFlare}) Global lens flare . + // @mem lensFlare + "lensFlare", &Level::LensFlare, + /// (@{Flow.Fog}) omni fog RGB color and distance. // As seen in TR4's Desert Railroad. // If not provided, distance fog will be black. @@ -167,7 +180,7 @@ void Level::SetLevelFarView(short val) RGBAColor8Byte Level::GetSkyLayerColor(int index) const { - assertion(index == 0 || index == 1, "Sky layer index must be 0 or 1."); + TENAssert(index == 0 || index == 1, "Sky layer index must be 0 or 1."); if (index == 0) { @@ -181,7 +194,7 @@ RGBAColor8Byte Level::GetSkyLayerColor(int index) const bool Level::GetSkyLayerEnabled(int index) const { - assertion(index == 0 || index == 1, "Sky layer index must be 0 or 1."); + TENAssert(index == 0 || index == 1, "Sky layer index must be 0 or 1."); if (index == 0) { @@ -195,7 +208,7 @@ bool Level::GetSkyLayerEnabled(int index) const short Level::GetSkyLayerSpeed(int index) const { - assertion(index == 0 || index == 1, "Sky layer index must be 0 or 1."); + TENAssert(index == 0 || index == 1, "Sky layer index must be 0 or 1."); if (index == 0) { @@ -277,3 +290,58 @@ std::string Level::GetAmbientTrack() const { return AmbientTrack; } + +bool Level::GetLensFlareEnabled() const +{ + return LensFlare.GetEnabled(); +} + +int Level::GetLensFlareSunSpriteID() const +{ + return LensFlare.GetSunSpriteID(); +} + +short Level::GetLensFlarePitch() const +{ + return ANGLE(LensFlare.GetPitch()); +} + +short Level::GetLensFlareYaw() const +{ + return ANGLE(LensFlare.GetYaw()); +} + +Color Level::GetLensFlareColor() const +{ + return LensFlare.GetColor(); +} + +bool Level::GetStarfieldStarsEnabled() const +{ + return Starfield.GetStarsEnabled(); +} + +bool Level::GetStarfieldMeteorsEnabled() const +{ + return Starfield.GetMeteorsEnabled(); +} + +int Level::GetStarfieldStarCount() const +{ + return Starfield.GetStarCount(); +} + +int Level::GetStarfieldMeteorCount() const +{ + return Starfield.GetMeteorCount(); +} + +int Level::GetStarfieldMeteorSpawnDensity() const +{ + return Starfield.GetMeteorSpawnDensity(); +} + +float Level::GetStarfieldMeteorVelocity() const +{ + return Starfield.GetMeteorVelocity(); +} diff --git a/TombEngine/Scripting/Internal/TEN/Flow/Level/FlowLevel.h b/TombEngine/Scripting/Internal/TEN/Flow/Level/FlowLevel.h index e3d632e80..18d52df5b 100644 --- a/TombEngine/Scripting/Internal/TEN/Flow/Level/FlowLevel.h +++ b/TombEngine/Scripting/Internal/TEN/Flow/Level/FlowLevel.h @@ -1,11 +1,14 @@ #pragma once -#include #include "Scripting/Internal/TEN/Flow/SkyLayer/SkyLayer.h" +#include "Scripting/Internal/TEN/Flow/LensFlare/LensFlare.h" +#include "Scripting/Internal/TEN/Flow/Starfield/Starfield.h" #include "Scripting/Internal/TEN/Flow/Mirror/Mirror.h" #include "Scripting/Internal/TEN/Flow/Fog/Fog.h" #include "Scripting/Include/ScriptInterfaceLevel.h" #include "Scripting/Internal/TEN/Flow/InventoryItem/InventoryItem.h" +using namespace TEN::Scripting; + static const std::unordered_map WEATHER_TYPES { { "None", WeatherType::None }, @@ -31,7 +34,10 @@ struct Level : public ScriptInterfaceLevel Mirror Mirror = {}; int LevelFarView = 0; std::string AmbientTrack = {}; - + + LensFlare LensFlare = {}; + Starfield Starfield = {}; + WeatherType Weather = WeatherType::None; float WeatherStrength = 1.0f; bool Storm = false; @@ -42,6 +48,8 @@ struct Level : public ScriptInterfaceLevel bool ResetHub = false; + // TODO: Clean up this mess. + RGBAColor8Byte GetFogColor() const override; bool GetFogEnabled() const override; float GetWeatherStrength() const override; @@ -62,4 +70,19 @@ struct Level : public ScriptInterfaceLevel int GetSecrets() const override; std::string GetAmbientTrack() const override; bool GetResetHubEnabled() const override; + + // Lens flare getters + bool GetLensFlareEnabled() const override; + int GetLensFlareSunSpriteID() const override; + short GetLensFlarePitch() const override; + short GetLensFlareYaw() const override; + Color GetLensFlareColor() const override; + + // Starfield getters + bool GetStarfieldStarsEnabled() const override; + bool GetStarfieldMeteorsEnabled() const override; + int GetStarfieldStarCount() const override; + int GetStarfieldMeteorCount() const override; + int GetStarfieldMeteorSpawnDensity() const override; + float GetStarfieldMeteorVelocity() const override; }; diff --git a/TombEngine/Scripting/Internal/TEN/Flow/SkyLayer/SkyLayer.cpp b/TombEngine/Scripting/Internal/TEN/Flow/SkyLayer/SkyLayer.cpp index 5e9586a5f..e37d1dbeb 100644 --- a/TombEngine/Scripting/Internal/TEN/Flow/SkyLayer/SkyLayer.cpp +++ b/TombEngine/Scripting/Internal/TEN/Flow/SkyLayer/SkyLayer.cpp @@ -1,58 +1,58 @@ -#include "framework.h" -#include "SkyLayer.h" - -/*** Describes a layer of moving clouds. -As seen in TR4's City of the Dead. - -@tenclass Flow.SkyLayer -@pragma nostrip -*/ - -void SkyLayer::Register(sol::table & parent) -{ - using ctors = sol::constructors; - parent.new_usertype("SkyLayer", - ctors(), - sol::call_constructor, ctors(), - -/// (@{Color}) RGB sky color -//@mem color - "color", sol::property(&SkyLayer::GetColor, &SkyLayer::SetColor), - -/*** (int) cloud speed. - -Values can be between [-32768, 32767], with positive numbers resulting in a sky that scrolls from -west to east, and negative numbers resulting in one that travels east to west. - -Please note that speeds outside of the range of about [-1000, 1000] will cause the -sky to scroll so fast that it will no longer appear as a coherent stream of clouds. -Less is more. City of The Dead, for example, uses a speed value of 16. - -@mem speed*/ - "speed", &SkyLayer::CloudSpeed - ); -} - -/*** -@tparam Color color RGB color -@tparam int speed cloud speed -@treturn SkyLayer A SkyLayer object. -@function SkyLayer -*/ -SkyLayer::SkyLayer(ScriptColor const& col, short speed) -{ - SetColor(col); - CloudSpeed = speed; - Enabled = true; -} - -void SkyLayer::SetColor(ScriptColor const & col) -{ - R = col.GetR(); - G = col.GetG(); - B = col.GetB(); -} - -ScriptColor SkyLayer::GetColor() const { - return ScriptColor{ R, G, B }; -} +#include "framework.h" +#include "SkyLayer.h" + +/*** Describes a layer of moving clouds. +As seen in TR4's City of the Dead. + +@tenclass Flow.SkyLayer +@pragma nostrip +*/ + +void SkyLayer::Register(sol::table & parent) +{ + using ctors = sol::constructors; + parent.new_usertype("SkyLayer", + ctors(), + sol::call_constructor, ctors(), + + /// (@{Color}) RGB sky color + //@mem color + "color", sol::property(&SkyLayer::GetColor, &SkyLayer::SetColor), + + /*** (int) cloud speed. + + Values can be between [-32768, 32767], with positive numbers resulting in a sky that scrolls from + west to east, and negative numbers resulting in one that travels east to west. + + Please note that speeds outside of the range of about [-1000, 1000] will cause the + sky to scroll so fast that it will no longer appear as a coherent stream of clouds. + Less is more. City of The Dead, for example, uses a speed value of 16. + + @mem speed*/ + "speed", &SkyLayer::CloudSpeed + ); +} + +/*** +@tparam Color color RGB color +@tparam int speed cloud speed +@treturn SkyLayer A SkyLayer object. +@function SkyLayer +*/ +SkyLayer::SkyLayer(ScriptColor const& col, short speed) +{ + SetColor(col); + CloudSpeed = speed; + Enabled = true; +} + +void SkyLayer::SetColor(ScriptColor const & col) +{ + R = col.GetR(); + G = col.GetG(); + B = col.GetB(); +} + +ScriptColor SkyLayer::GetColor() const { + return ScriptColor{ R, G, B }; +} diff --git a/TombEngine/Scripting/Internal/TEN/Flow/Starfield/Starfield.cpp b/TombEngine/Scripting/Internal/TEN/Flow/Starfield/Starfield.cpp new file mode 100644 index 000000000..60d277a17 --- /dev/null +++ b/TombEngine/Scripting/Internal/TEN/Flow/Starfield/Starfield.cpp @@ -0,0 +1,150 @@ +#include "framework.h" +#include "Scripting/Internal/TEN/Flow/Starfield/Starfield.h" + +#include "Game/effects/weather.h" +#include "Specific/level.h" + +using namespace TEN::Effects::Environment; + +/// Represents a starfield. +// +// @tenclass Flow.Starfield +// @pragma nostrip + +namespace TEN::Scripting +{ + void Starfield::Register(sol::table& parent) + { + using ctors = sol::constructors< + Starfield(int starCount), + Starfield(int starCount, int meteorCount, int meteorSpawnDensity, float meteorVel)>; + + // Register type. + parent.new_usertype( + "Starfield", ctors(), sol::call_constructor, ctors(), + "GetStarsEnabled", &Starfield::GetStarsEnabled, + "GetMeteorsEnabled", &Starfield::GetMeteorsEnabled, + "GetStarCount", &Starfield::GetStarCount, + "GetMeteorCount", &Starfield::GetMeteorCount, + "GetMeteorSpawnDensity", &Starfield::GetMeteorSpawnDensity, + "GetMeteorVelocity", &Starfield::GetMeteorVelocity, + "SetStarCount", &Starfield::SetStarCount, + "SetMeteorCount", &Starfield::SetMeteorCount, + "SetMeteorSpawnDensity", &Starfield::SetMeteorSpawnDensity, + "SetMeteorVelocity", &Starfield::SetMeteorVelocity); + } + + /// Create a starfield object with only stars. + // @function Starfield() + // @tparam int starCount Star count. + // @treturn Starfield A new Starfield object. + Starfield::Starfield(int starCount) + { + _starCount = starCount; + } + + /// Create a starfield object with stars and meteors. + // @function Starfield() + // @tparam int starCount Star count (6000 max). + // @tparam int meteorCount Meteor count (100 max). + // @treturn Starfield A new Starfield object. + Starfield::Starfield(int starCount, int meteorCount, int meteorSpawnDensity, float meteorVel) + { + if (starCount < 0 || starCount > STAR_COUNT_MAX) + TENLog("Star count must be in range [0, " + std::to_string(STAR_COUNT_MAX) + "].", LogLevel::Warning); + + if (meteorCount < 0 || meteorCount > METEOR_COUNT_MAX) + TENLog("Meteor count must be in range [0, " + std::to_string(METEOR_COUNT_MAX) + "].", LogLevel::Warning); + + _starCount = std::clamp(starCount, 0, STAR_COUNT_MAX); + _meteorCount = std::clamp(meteorCount, 0, METEOR_COUNT_MAX); + _meteorSpawnDensity = meteorSpawnDensity; + _meteorVelocity = meteorVel; + } + + /// Get the starfield's enabled status of stars. + // @function Starfield:GetStarsEnabled() + // @treturn bool Stars enabled status. + bool Starfield::GetStarsEnabled() const + { + return (_starCount > 0); + } + + /// Get the starfield's enabled status of meteors. + // @function Starfield:GetMeteorsEnabled() + // @treturn bool Meteors enabled status. + bool Starfield::GetMeteorsEnabled() const + { + return (_meteorCount > 0); + } + + /// Get the starfield's number of stars. + // @function Starfield:GetStarCount() + // @treturn int Count. + int Starfield::GetStarCount() const + { + return _starCount; + } + + /// Get the starfield's number of meteors. + // @function Starfield:GetMeteorCount() + // @treturn int Count. + int Starfield::GetMeteorCount() const + { + return _meteorCount; + } + + /// Get the starfield's meteor spawn density. + // @function Starfield:GetMeteorSpawnDensity() + // @treturn int Spawn density. + int Starfield::GetMeteorSpawnDensity() const + { + return _meteorSpawnDensity; + } + + /// Get the starfield's meteor velocity. + // @function Starfield:GetMeteorVelocity() + // @treturn float Velocity. + float Starfield::GetMeteorVelocity() const + { + return _meteorVelocity; + } + + /// Set the starfield's number of stars (6000 max). + // @function Starfield:SetStarCount(int) + // @tparam int New count. + void Starfield::SetStarCount(int count) + { + if (count < 0 || count > STAR_COUNT_MAX) + TENLog("Star count must be in range [0, " + std::to_string(STAR_COUNT_MAX) + "].", LogLevel::Warning); + + _starCount = std::clamp(count, 0, STAR_COUNT_MAX); + } + + /// Set the starfield's number of meteors (100 max). + // @function Starfield:SetMeteorCount(int) + // @tparam int New count. + void Starfield::SetMeteorCount(int count) + { + if (count < 0 || count > METEOR_COUNT_MAX) + TENLog("Meteor count must be in range [0, " + std::to_string(METEOR_COUNT_MAX) + "].", LogLevel::Warning); + + _meteorCount = std::clamp(count, 0, METEOR_COUNT_MAX); + } + + /// Set the starfield's meteor spawn density. + // @function Starfield:SetMeteorSpawnDensity(int) + // @tparam int New spawn density. + void Starfield::SetMeteorSpawnDensity(int spawnDensity) + { + _meteorSpawnDensity = spawnDensity; + } + + /// Set the starfield's meteor velocity. + // @function Starfield:SetMeteorVelocity(float) + // @tparam int New velocity. + void Starfield::SetMeteorVelocity(float vel) + { + _meteorVelocity = vel; + } +} diff --git a/TombEngine/Scripting/Internal/TEN/Flow/Starfield/Starfield.h b/TombEngine/Scripting/Internal/TEN/Flow/Starfield/Starfield.h new file mode 100644 index 000000000..913314088 --- /dev/null +++ b/TombEngine/Scripting/Internal/TEN/Flow/Starfield/Starfield.h @@ -0,0 +1,42 @@ +#pragma once + +namespace sol { class state; } + +namespace TEN::Scripting +{ + class Starfield + { + private: + // Constants + static const auto STAR_COUNT_MAX = 6000; + static const auto METEOR_COUNT_MAX = 100; + + // Members + int _starCount = 0; + int _meteorCount = 0; + int _meteorSpawnDensity = 0; + float _meteorVelocity = 0; + + public: + static void Register(sol::table& table); + + // Constructors + Starfield() = default; + Starfield(int starCount); + Starfield(int starCount, int meteorCount, int meteorSpawnDensity, float meteorVel); + + // Getters + bool GetStarsEnabled() const; + bool GetMeteorsEnabled() const; + int GetStarCount() const; + int GetMeteorCount() const; + int GetMeteorSpawnDensity() const; + float GetMeteorVelocity() const; + + // Setters + void SetStarCount(int count); + void SetMeteorCount(int count); + void SetMeteorSpawnDensity(int spawnDensity); + void SetMeteorVelocity(float vel); + }; +} diff --git a/TombEngine/Scripting/Internal/TEN/Input/InputHandler.cpp b/TombEngine/Scripting/Internal/TEN/Input/InputHandler.cpp index 650854e48..803e02ec1 100644 --- a/TombEngine/Scripting/Internal/TEN/Input/InputHandler.cpp +++ b/TombEngine/Scripting/Internal/TEN/Input/InputHandler.cpp @@ -87,6 +87,14 @@ namespace TEN::Scripting::Input ActionQueue[actionID] = QueueState::Clear; } + /// Clear all action keys. + // @function KeyClearAll + static void KeyClearAll() + { + for (auto& queue : ActionQueue) + queue = QueueState::Clear; + } + /// Get the display position of the cursor in percent. // @function GetMouseDisplayPosition() // @treturn Vec2 Cursor display position in percent. @@ -110,6 +118,7 @@ namespace TEN::Scripting::Input table.set_function(ScriptReserved_KeyIsHit, &KeyIsHit); table.set_function(ScriptReserved_KeyPush, &KeyPush); table.set_function(ScriptReserved_KeyClear, &KeyClear); + table.set_function(ScriptReserved_KeyClearAll, &KeyClearAll); table.set_function(ScriptReserved_GetMouseDisplayPosition, &GetMouseDisplayPosition); table.set_function(ScriptReserved_GetCursorDisplayPosition, &GetMouseDisplayPosition); diff --git a/TombEngine/Scripting/Internal/TEN/Inventory/InventoryHandler.cpp b/TombEngine/Scripting/Internal/TEN/Inventory/InventoryHandler.cpp index 1eb1414ba..8cce6990e 100644 --- a/TombEngine/Scripting/Internal/TEN/Inventory/InventoryHandler.cpp +++ b/TombEngine/Scripting/Internal/TEN/Inventory/InventoryHandler.cpp @@ -1,12 +1,14 @@ #include "framework.h" #include "Scripting/Internal/TEN/Inventory/InventoryHandler.h" +#include "Game/gui.h" #include "Game/Hud/Hud.h" #include "Game/Lara/lara.h" #include "Game/pickup/pickup.h" #include "Scripting/Internal/ReservedScriptNames.h" using namespace TEN::Hud; +using namespace TEN::Gui; /*** Inventory manipulation @@ -59,6 +61,36 @@ namespace TEN::Scripting::InventoryHandler SetInventoryCount(objectID, count); } + /// Get last item used in the player's inventory. + // This value will be valid only for a single frame after exiting inventory, after which Lara says "No". + // Therefore, this function must be preferably used either in OnLoop or OnUseItem events. + //@function GetUsedItem + //@treturn Objects.ObjID Last item used in the inventory. + static GAME_OBJECT_ID GetUsedItem() + { + return (GAME_OBJECT_ID)g_Gui.GetInventoryItemChosen(); + } + + /// Set last item used in the player's inventory. + // You will be able to specify only objects which already exist in the inventory. + // Will only be valid for the next frame. If not processed by the game, Lara will say "No". + //@function SetUsedItem + //@tparam Objects.ObjID objectID Object ID of the item to select from inventory. + static void SetUsedItem(GAME_OBJECT_ID objectID) + { + if (g_Gui.IsObjectInInventory(objectID)) + g_Gui.SetInventoryItemChosen(objectID); + } + + /// Clear last item used in the player's inventory. + // When this function is used in OnUseItem level function, it allows to override existing item functionality. + // For items without existing functionality, this function is needed to avoid Lara saying "No" after using it. + //@function ClearUsedItem + static void ClearUsedItem() + { + g_Gui.SetInventoryItemChosen(GAME_OBJECT_ID::ID_NO_OBJECT); + } + void Register(sol::state* state, sol::table& parent) { auto tableInventory = sol::table{ state->lua_state(), sol::create }; @@ -68,5 +100,8 @@ namespace TEN::Scripting::InventoryHandler tableInventory.set_function(ScriptReserved_TakeInvItem, &TakeItem); tableInventory.set_function(ScriptReserved_GetInvItemCount, &GetItemCount); tableInventory.set_function(ScriptReserved_SetInvItemCount, &SetItemCount); + tableInventory.set_function(ScriptReserved_SetUsedItem, &SetUsedItem); + tableInventory.set_function(ScriptReserved_GetUsedItem, &GetUsedItem); + tableInventory.set_function(ScriptReserved_ClearUsedItem, &ClearUsedItem); } } diff --git a/TombEngine/Scripting/Internal/TEN/Logic/LogicHandler.cpp b/TombEngine/Scripting/Internal/TEN/Logic/LogicHandler.cpp index cef0ba8b2..9b96d12d7 100644 --- a/TombEngine/Scripting/Internal/TEN/Logic/LogicHandler.cpp +++ b/TombEngine/Scripting/Internal/TEN/Logic/LogicHandler.cpp @@ -65,7 +65,8 @@ static const std::unordered_map EVENT_TYPES { ScriptReserved_EventOnLoad, EventType::Load }, { ScriptReserved_EventOnSave, EventType::Save }, { ScriptReserved_EventOnStart, EventType::Start }, - { ScriptReserved_EventOnEnd, EventType::End } + { ScriptReserved_EventOnEnd, EventType::End }, + { ScriptReserved_EventOnUseItem, EventType::UseItem } }; enum class LevelEndReason @@ -293,6 +294,7 @@ Possible event type values: START END LOOP + USEITEM @function HandleEvent @tparam string name Name of the event set to find. @@ -457,6 +459,7 @@ void LogicHandler::FreeLevelScripts() m_onLoop = sol::nil; m_onSave = sol::nil; m_onEnd = sol::nil; + m_onUseItem = sol::nil; m_handler.GetState()->collect_garbage(); } @@ -999,6 +1002,12 @@ void LogicHandler::OnEnd(GameStatus reason) CallLevelFuncByName(name, endReason); } +void LogicHandler::OnUseItem(GAME_OBJECT_ID objectNumber) +{ + if (m_onUseItem.valid()) + CallLevelFunc(m_onUseItem, objectNumber); +} + /*** Special tables TombEngine uses the following tables for specific things. @@ -1142,4 +1151,5 @@ void LogicHandler::InitCallbacks() assignCB(m_onLoop, ScriptReserved_OnLoop); assignCB(m_onSave, ScriptReserved_OnSave); assignCB(m_onEnd, ScriptReserved_OnEnd); + assignCB(m_onUseItem, ScriptReserved_OnUseItem); } diff --git a/TombEngine/Scripting/Internal/TEN/Logic/LogicHandler.h b/TombEngine/Scripting/Internal/TEN/Logic/LogicHandler.h index 1e1b485f9..c1f4f2108 100644 --- a/TombEngine/Scripting/Internal/TEN/Logic/LogicHandler.h +++ b/TombEngine/Scripting/Internal/TEN/Logic/LogicHandler.h @@ -56,6 +56,7 @@ private: sol::protected_function m_onLoop{}; sol::protected_function m_onSave{}; sol::protected_function m_onEnd{}; + sol::protected_function m_onUseItem{}; std::unordered_set m_callbacksPreSave; std::unordered_set m_callbacksPostSave; @@ -170,4 +171,5 @@ public: void OnLoop(float deltaTime, bool postLoop) override; void OnSave() override; void OnEnd(GameStatus reason) override; + void OnUseItem(GAME_OBJECT_ID item) override; }; diff --git a/TombEngine/Scripting/Internal/TEN/Objects/Moveable/MoveableObject.cpp b/TombEngine/Scripting/Internal/TEN/Objects/Moveable/MoveableObject.cpp index 76610d3f1..fa72ce12c 100644 --- a/TombEngine/Scripting/Internal/TEN/Objects/Moveable/MoveableObject.cpp +++ b/TombEngine/Scripting/Internal/TEN/Objects/Moveable/MoveableObject.cpp @@ -79,7 +79,7 @@ most can just be ignored (see usage). @tparam string name Lua name of the item @tparam Vec3 position position in level @tparam Rotation rotation rotation rotation about x, y, and z axes (default Rotation(0, 0, 0)) - @tparam int roomID room ID item is in (default: calculated automatically) + @tparam int roomNumber the room number the moveable is in (default: calculated automatically). @tparam int animNumber animation number @tparam int frameNumber frame number @tparam int hp HP of item @@ -179,14 +179,14 @@ void Moveable::Register(sol::state& state, sol::table& parent) /// Set effect to moveable // @function Moveable:SetEffect // @tparam Effects.EffectID effect Type of effect to assign. -// @tparam float timeout time (in seconds) after which effect turns off (optional). +// @tparam[opt] float timeout time (in seconds) after which effect turns off. ScriptReserved_SetEffect, &Moveable::SetEffect, /// Set custom colored burn effect to moveable // @function Moveable:SetCustomEffect // @tparam Color Color1 color the primary color of the effect (also used for lighting). // @tparam Color Color2 color the secondary color of the effect. -// @tparam float timeout time (in seconds) after which effect turns off (optional). +// @tparam[opt] float timeout time (in seconds) after which effect turns off. ScriptReserved_SetCustomEffect, &Moveable::SetCustomEffect, /// Get current moveable effect @@ -223,6 +223,12 @@ void Moveable::Register(sol::state& state, sol::table& parent) // @treturn int the index of the active state ScriptReserved_GetStateNumber, &Moveable::GetStateNumber, +/// Retrieve the index of the target state. +// This corresponds to the state the object is trying to get into, which is sometimes different from the active state. +// @function Moveable:GetTargetState +// @treturn int the index of the target state + ScriptReserved_GetTargetStateNumber, &Moveable::GetTargetStateNumber, + /// Set the object's state to the one specified by the given index. // Performs no bounds checking. *Ensure the number given is correct, else // object may end up in corrupted animation state.* @@ -365,9 +371,16 @@ ScriptReserved_GetSlotHP, & Moveable::GetSlotHP, /// Get the object's joint position // @function Moveable:GetJointPosition // @tparam int index of a joint to get position -// @treturn Vec3 a copy of the moveable's position +// @tparam[opt] Vec3 offset a pre-rotation offset to the joint +// @treturn Vec3 a copy of the moveable's joint position ScriptReserved_GetJointPosition, & Moveable::GetJointPos, +/// Get the object's joint rotation +// @function Moveable:GetJointRotation +// @tparam int index of a joint to get rotation +// @treturn Rotation a calculated copy of the moveable's joint rotation + ScriptReserved_GetJointRotation, & Moveable::GetJointRot, + ScriptReserved_SetPosition, & Moveable::SetPos, /// Get the moveable's rotation @@ -428,6 +441,9 @@ ScriptReserved_GetSlotHP, & Moveable::GetSlotHP, // @function Moveable:SetOnCollidedWithObject // @tparam function func callback function to be called (must be in LevelFuncs hierarchy). This function can take two arguments; these will store the two @{Moveable}s taking part in the collision. // @usage +// -- obj1 is the collision moveable +// -- obj2 is the collider moveable +// // LevelFuncs.objCollided = function(obj1, obj2) // print(obj1:GetName() .. " collided with " .. obj2:GetName()) // end @@ -580,8 +596,12 @@ Vec3 Moveable::GetPos() const // @bool[opt] updateRoom Will room changes be automatically detected? Set to false if you are using overlapping rooms (default: true) void Moveable::SetPos(const Vec3& pos, sol::optional updateRoom) { - auto prevPos = m_item->Pose.Position.ToVector3(); - m_item->Pose.Position = pos.ToVector3i(); + constexpr auto BIG_DISTANCE_THRESHOLD = BLOCK(1); + + auto newPos = pos.ToVector3i(); + bool bigDistance = Vector3i::Distance(newPos, m_item->Pose.Position) > BIG_DISTANCE_THRESHOLD; + + m_item->Pose.Position = newPos; bool willUpdate = !updateRoom.has_value() || updateRoom.value(); @@ -590,8 +610,7 @@ void Moveable::SetPos(const Vec3& pos, sol::optional updateRoom) bool isRoomUpdated = m_item->IsLara() ? UpdateLaraRoom(m_item, pos.y) : UpdateItemRoom(m_item->Index); // In case direct portal room update didn't happen and distance between old and new points is significant, do predictive room update. - if (!isRoomUpdated && - (willUpdate || Vector3::Distance(prevPos, m_item->Pose.Position.ToVector3()) > BLOCK(1))) + if (!isRoomUpdated && (willUpdate || bigDistance)) { int potentialNewRoom = FindRoomNumber(m_item->Pose.Position, m_item->RoomNumber); if (potentialNewRoom != m_item->RoomNumber) @@ -601,14 +620,23 @@ void Moveable::SetPos(const Vec3& pos, sol::optional updateRoom) if (m_item->IsBridge()) UpdateBridgeItem(*m_item); + + if (bigDistance) + m_item->DisableInterpolation = true; } -Vec3 Moveable::GetJointPos(int jointIndex) const +Vec3 Moveable::GetJointPos(int jointIndex, sol::optional offset) const { - auto result = GetJointPosition(m_item, jointIndex); + Vector3i vec = offset.has_value() ? offset->ToVector3i() : Vector3i(0, 0, 0); + auto result = GetJointPosition(m_item, jointIndex, vec); return Vec3(result.x, result.y, result.z); } +Rotation Moveable::GetJointRot(int jointIndex) const +{ + return GetBoneOrientation(*m_item, jointIndex); +} + // This does not guarantee that the returned value will be identical // to a value written in via SetRot - only that the angle measures // will be mathematically equal @@ -625,12 +653,18 @@ Rotation Moveable::GetRot() const void Moveable::SetRot(const Rotation& rot) { - m_item->Pose.Orientation.x = ANGLE(rot.x); - m_item->Pose.Orientation.y = ANGLE(rot.y); - m_item->Pose.Orientation.z = ANGLE(rot.z); + constexpr auto BIG_ANGLE_THRESHOLD = ANGLE(30.0f); + + auto newRot = rot.ToEulerAngles(); + bool bigRotation = !EulerAngles::Compare(newRot, m_item->Pose.Orientation, BIG_ANGLE_THRESHOLD); + + m_item->Pose.Orientation = newRot; if (m_item->IsBridge()) UpdateBridgeItem(*m_item); + + if (bigRotation) + m_item->DisableInterpolation = true; } short Moveable::GetHP() const @@ -818,6 +852,11 @@ int Moveable::GetStateNumber() const return m_item->Animation.ActiveState; } +int Moveable::GetTargetStateNumber() const +{ + return m_item->Animation.TargetState; +} + void Moveable::SetStateNumber(int stateNumber) { m_item->Animation.TargetState = stateNumber; diff --git a/TombEngine/Scripting/Internal/TEN/Objects/Moveable/MoveableObject.h b/TombEngine/Scripting/Internal/TEN/Objects/Moveable/MoveableObject.h index 931cfbdbc..dfb795f31 100644 --- a/TombEngine/Scripting/Internal/TEN/Objects/Moveable/MoveableObject.h +++ b/TombEngine/Scripting/Internal/TEN/Objects/Moveable/MoveableObject.h @@ -1,4 +1,5 @@ #pragma once + #include "Scripting/Internal/ScriptUtil.h" #include "Scripting/Internal/TEN/Objects/NamedBase.h" #include "Scripting/Internal/TEN/Objects/Room/RoomObject.h" @@ -47,13 +48,15 @@ public: void Destroy(); [[nodiscard]] Vec3 GetPos() const; - [[nodiscard]] Vec3 GetJointPos(int index) const; + [[nodiscard]] Vec3 GetJointPos(int index, sol::optional offset) const; void SetPos(const Vec3& pos, sol::optional updateRoom); + [[nodiscard]] Rotation GetJointRot(int index) const; [[nodiscard]] Rotation GetRot() const; void SetRot(const Rotation& rot); [[nodiscard]] int GetStateNumber() const; + [[nodiscard]] int GetTargetStateNumber() const; void SetStateNumber(int stateNumber); [[nodiscard]] int GetAnimNumber() const; diff --git a/TombEngine/Scripting/Internal/TEN/Objects/ObjectIDs.h b/TombEngine/Scripting/Internal/TEN/Objects/ObjectIDs.h index 351402296..2f1e2e899 100644 --- a/TombEngine/Scripting/Internal/TEN/Objects/ObjectIDs.h +++ b/TombEngine/Scripting/Internal/TEN/Objects/ObjectIDs.h @@ -53,7 +53,8 @@ The following constants are inside ObjID. LARA_DIRT_MESH LARA_CROWBAR_ANIM LARA_TORCH_ANIM - HAIR + HAIR_PRIMARY + HAIR_SECONDARY SNOWMOBILE_LARA_ANIMS SNOWMOBILE QUAD_LARA_ANIMS @@ -239,7 +240,7 @@ The following constants are inside ObjID. SKATEBOARD SKATEBOARD_KID WINSTON - ARMY_WINSTON + SEAL_MUTANT SPRINGBOARD ROLLING_SPINDLE DISK_SHOOTER @@ -825,6 +826,7 @@ The following constants are inside ObjID. MESHSWAP_ROMAN_GOD2 MESHSWAP_MONKEY_MEDIPACK MESHSWAP_MONKEY_KEY + MESHSWAP_WINSTON_ARMY_OUTFIT ANIMATING1 ANIMATING2 ANIMATING3 @@ -986,6 +988,10 @@ The following constants are inside ObjID. AIR_BAR_TEXTURE DASH_BAR_TEXTURE SFX_BAR_TEXTURE + SPEEDOMETER + CUSTOM_BAR_GRAPHIC + CUSTOM_AMMO_GRAPHIC + PANEL_BORDER PANEL_MIDDLE PANEL_CORNER @@ -1229,7 +1235,8 @@ static const std::unordered_map kObjIDs { { "LARA_DIRT_MESH", ID_LARA_DIRT_MESH }, { "LARA_CROWBAR_ANIM", ID_LARA_CROWBAR_ANIM }, { "LARA_TORCH_ANIM", ID_LARA_TORCH_ANIM }, - { "HAIR", ID_HAIR }, + { "HAIR_PRIMARY", ID_HAIR_PRIMARY }, + { "HAIR_SECONDARY", ID_HAIR_SECONDARY }, { "SNOWMOBILE_LARA_ANIMS", ID_SNOWMOBILE_LARA_ANIMS }, { "SNOWMOBILE", ID_SNOWMOBILE }, { "QUAD_LARA_ANIMS", ID_QUAD_LARA_ANIMS }, @@ -1415,6 +1422,7 @@ static const std::unordered_map kObjIDs { { "SKATEBOARD", ID_SKATEBOARD }, { "SKATEBOARD_KID", ID_SKATEBOARD_KID }, { "WINSTON", ID_WINSTON }, + { "SEAL_MUTANT", ID_SEAL_MUTANT }, { "SPRINGBOARD", ID_SPRINGBOARD }, { "ROLLING_SPINDLE", ID_ROLLING_SPINDLE }, { "DISK_SHOOTER", ID_DISK_SHOOTER }, @@ -2162,6 +2170,9 @@ static const std::unordered_map kObjIDs { { "DASH_BAR_TEXTURE", ID_DASH_BAR_TEXTURE }, { "SFX_BAR_TEXTURE", ID_SFX_BAR_TEXTURE }, { "CROSSHAIR", ID_CROSSHAIR }, + { "SPEEDOMETER", ID_SPEEDOMETER }, + { "CUSTOM_BAR_GRAPHIC", ID_CUSTOM_BAR_GRAPHIC }, + { "CUSTOM_AMMO_GRAPHIC",ID_CUSTOM_AMMO_GRAPHIC }, { "PANEL_BORDER", ID_PANEL_BORDER }, { "PANEL_MIDDLE", ID_PANEL_MIDDLE }, { "PANEL_CORNER", ID_PANEL_CORNER }, diff --git a/TombEngine/Scripting/Internal/TEN/Objects/ObjectsHandler.cpp b/TombEngine/Scripting/Internal/TEN/Objects/ObjectsHandler.cpp index 64b74ca70..df52b1c59 100644 --- a/TombEngine/Scripting/Internal/TEN/Objects/ObjectsHandler.cpp +++ b/TombEngine/Scripting/Internal/TEN/Objects/ObjectsHandler.cpp @@ -184,7 +184,7 @@ void ObjectsHandler::TestCollidingObjects() { // Test against other moveables. auto collObjects = GetCollidedObjects(item, true, false); - for (const auto& collidedItemPtr : collObjects.ItemPtrs) + for (const auto& collidedItemPtr : collObjects.Items) g_GameScript->ExecuteFunction(item.Callbacks.OnObjectCollided, itemNumber0, collidedItemPtr->Index); } diff --git a/TombEngine/Scripting/Internal/TEN/Objects/ObjectsHandler.h b/TombEngine/Scripting/Internal/TEN/Objects/ObjectsHandler.h index a63c510a1..ef8703b88 100644 --- a/TombEngine/Scripting/Internal/TEN/Objects/ObjectsHandler.h +++ b/TombEngine/Scripting/Internal/TEN/Objects/ObjectsHandler.h @@ -111,7 +111,7 @@ private: auto room = std::get>(val).get(); - if (std::any_of(room.tags.begin(), room.tags.end(), + if (std::any_of(room.Tags.begin(), room.Tags.end(), [&tag](const std::string& value) { return value == tag; })) { rooms.push_back(GetByName(key)); diff --git a/TombEngine/Scripting/Internal/TEN/Objects/Room/RoomObject.cpp b/TombEngine/Scripting/Internal/TEN/Objects/Room/RoomObject.cpp index 3ca57da71..3cac49f14 100644 --- a/TombEngine/Scripting/Internal/TEN/Objects/Room/RoomObject.cpp +++ b/TombEngine/Scripting/Internal/TEN/Objects/Room/RoomObject.cpp @@ -13,141 +13,148 @@ #include "Specific/level.h" #include "Specific/trutils.h" -/*** -Rooms +/// Room object. +// @tenclass Objects.Room +// @pragma nostrip -@tenclass Objects.Room -@pragma nostrip -*/ +//namespace TEN::Scripting +//{ + static auto IndexError = index_error_maker(Room, ScriptReserved_Volume); + static auto NewIndexError = newindex_error_maker(Room, ScriptReserved_Volume); -static auto IndexError = index_error_maker(Room, ScriptReserved_Volume); -static auto NewIndexError = newindex_error_maker(Room, ScriptReserved_Volume); - -Room::Room(ROOM_INFO& room) : m_room{ room } -{}; - -void Room::Register(sol::table& parent) -{ - parent.new_usertype(ScriptReserved_Room, - sol::no_constructor, - sol::meta_function::index, IndexError, - sol::meta_function::new_index, NewIndexError, - - /// Determine whether the room is active or not - // @function Room:GetActive - // @treturn bool true if the room is active - ScriptReserved_GetActive, &Room::GetActive, - - /// Get the room's ambient light color. - // @function Room:GetColor - // @treturn Color ambient light color of the room - ScriptReserved_GetColor, & Room::GetColor, - - /// Get the room's reverb type. - // @function Room:GetReverbType - // @treturn Objects.RoomReverb room's reverb type - ScriptReserved_GetPosition, &Room::GetReverbType, - - /// Set the room's reverb type. - // @function Room:SetReverbType - // @tparam Objects.RoomReverb new reverb type of the room - ScriptReserved_SetReverbType, &Room::SetReverbType, - - /// Get the room's unique string identifier. - // @function Room:GetName - // @treturn string the room's name - ScriptReserved_GetName, &Room::GetName, - - /// Set the room's name (its unique string identifier). - // @function Room:SetName - // @tparam string name The room's new name - ScriptReserved_SetName, &Room::SetName, - - /// Get the room's specified flag value (true or false). - // @function Room:GetFlag - // @tparam Objects.RoomFlagID flagID The room's flag ID - // @treturn bool the room's specified flag value - ScriptReserved_GetFlag, &Room::GetFlag, - - /// Set the room's specified flag value. - // @function Room:SetFlag - // @tparam Objects.RoomFlagID flagID The room's flag ID - // @tparam bool the room's new flag value - ScriptReserved_SetFlag, &Room::SetFlag, - - /// Checks if specified tag is set for this room. - // @function Room:IsTagPresent - // @tparam string tag A text tag to check (case sensitive) - // @treturn bool true if tag is present, false if not - ScriptReserved_IsTagPresent, &Room::IsTagPresent); -} - -bool Room::GetActive() const -{ - return m_room.Active(); -} - -ScriptColor Room::GetColor() const -{ - return ScriptColor{ m_room.ambient }; -} - -ReverbType Room::GetReverbType() const -{ - return m_room.reverbType; -} - -void Room::SetReverbType(ReverbType reverb) -{ - m_room.reverbType = reverb; -} - -std::string Room::GetName() const -{ - return m_room.name; -} - -void Room::SetName(const std::string& name) -{ - if (!ScriptAssert(!name.empty(), "Unable to set name. Name cannot be blank.")) - return; - - // Remove old name if it already exists. - if (s_callbackSetName(name, m_room)) + Room::Room(ROOM_INFO& room) : + _room(room) { - s_callbackRemoveName(m_room.name); - m_room.name = name; - } - else + }; + + void Room::Register(sol::table& parent) { - ScriptAssertF(false, "Could not add name {} - does an object with this name already exist?", name); - TENLog("Name will not be set", LogLevel::Warning, LogConfig::All); + // Register type. + parent.new_usertype( + ScriptReserved_Room, + sol::no_constructor, + sol::meta_function::index, IndexError, + sol::meta_function::new_index, NewIndexError, + + ScriptReserved_RoomGetRoomNumber, &Room::GetRoomNumber, + ScriptReserved_RoomGetName, &Room::GetName, + ScriptReserved_RoomGetColor, &Room::GetColor, + ScriptReserved_RoomGetReverbType, &Room::GetReverbType, + ScriptReserved_RoomSetName, &Room::SetName, + ScriptReserved_RoomSetReverbType, &Room::SetReverbType, + ScriptReserved_RoomSetFlag, &Room::SetFlag, + ScriptReserved_RoomIsTagPresent, &Room::IsTagPresent, + ScriptReserved_RoomGetActive, &Room::GetActive, + ScriptReserved_RoomGetFlag, &Room::GetFlag); } -} -bool Room::GetFlag(RoomEnvFlags flag) const -{ - return ((m_room.flags & flag) == flag); -} - -void Room::SetFlag(RoomEnvFlags flag, bool value) -{ - if (value) + /// Get the room's number. + // @function Room:GetRoomNumber() + // @treturn int Room number. + int Room::GetRoomNumber() const { - m_room.flags |= flag; + return _room.RoomNumber; } - else + + /// Get the room's unique string identifier. + // @function Room:GetName() + // @treturn string Room name. + std::string Room::GetName() const { - m_room.flags &= ~flag; + return _room.Name; } -} -bool Room::IsTagPresent(const std::string& tag) const -{ - if (m_room.tags.empty()) - return false; + /// Get the room's ambient light color. + // @function Room:GetColor() + // @treturn Color Ambient light color. + ScriptColor Room::GetColor() const + { + return ScriptColor(_room.ambient); + } - return std::any_of( - m_room.tags.begin(), m_room.tags.end(), - [&tag](const std::string& value) { return (value == tag); }); -} + /// Get the room's reverb type. + // @function Room:GetReverbType() + // @treturn Objects.RoomReverb Reverb type. + ReverbType Room::GetReverbType() const + { + return _room.reverbType; + } + + /// Set the room's unique string identifier. + // @function Room:SetName() + // @tparam string name New name. + void Room::SetName(const std::string& name) + { + if (!ScriptAssert(!name.empty(), "Unable to set name. Name cannot be blank.")) + return; + + // Remove previous name if it already exists. + if (s_callbackSetName(name, _room)) + { + s_callbackRemoveName(_room.Name); + _room.Name = name; + } + else + { + ScriptAssertF(false, "Could not add name {} - does an object with this name already exist?", name); + TENLog("Name will not be set", LogLevel::Warning, LogConfig::All); + } + } + + /// Set the room's reverb type. + // @function Room:SetReverbType() + // @tparam Objects.RoomReverb Reverb type. + void Room::SetReverbType(ReverbType reverb) + { + _room.reverbType = reverb; + } + + /// Set the room's specified flag. + // @function Room:SetFlag() + // @tparam Objects.RoomFlagID flagID Room flag ID. + // @tparam bool Boolean to set the flag to. + void Room::SetFlag(RoomEnvFlags flag, bool value) + { + if (value) + { + _room.flags |= flag; + } + else + { + _room.flags &= ~flag; + } + } + + /// Get the room's specified flag value (true or false). + // @function Room:GetFlag() + // @tparam Objects.RoomFlagID flagID Room flag ID. + bool Room::IsTagPresent(const std::string& tag) const + { + if (_room.Tags.empty()) + return false; + + return std::any_of( + _room.Tags.begin(), _room.Tags.end(), + [&tag](const std::string& value) + { + return (value == tag); + }); + } + + /// Check if the specified tag is set for the room. + // @function Room:IsTagPresent() + // @tparam string tag Text tag to check (case sensitive). + // @treturn bool Boolean of the tag's presence. + bool Room::GetActive() const + { + return _room.Active(); + } + + /// Check if the room is active. + // @function Room:GetActive() + // @treturn bool Boolean of the room's active status. + bool Room::GetFlag(RoomEnvFlags flag) const + { + return ((_room.flags & flag) == flag); + } +//} diff --git a/TombEngine/Scripting/Internal/TEN/Objects/Room/RoomObject.h b/TombEngine/Scripting/Internal/TEN/Objects/Room/RoomObject.h index 8569a1e5c..3672b5b24 100644 --- a/TombEngine/Scripting/Internal/TEN/Objects/Room/RoomObject.h +++ b/TombEngine/Scripting/Internal/TEN/Objects/Room/RoomObject.h @@ -1,4 +1,5 @@ #pragma once + #include "Game/room.h" #include "Scripting/Internal/TEN/Objects/NamedBase.h" @@ -7,32 +8,50 @@ enum class ReverbType; class ScriptColor; class Vec3; -class Room : public NamedBase -{ -public: - using IdentifierType = std::reference_wrapper; - Room(ROOM_INFO& room); - ~Room() = default; +//namespace TEN::Scripting +//{ + class Room : public NamedBase + { + private: + // Members - Room& operator =(const Room& other) = delete; - Room(const Room& other) = delete; + ROOM_INFO& _room; - static void Register(sol::table& parent); + public: + using IdentifierType = std::reference_wrapper; - [[nodiscard]] bool GetActive() const; - [[nodiscard]] ScriptColor GetColor() const; + static void Register(sol::table& parent); - [[nodiscard]] std::string GetName() const; - void SetName(const std::string& name); + // Constructors - [[nodiscard]] bool GetFlag(RoomEnvFlags flag) const; - void SetFlag(RoomEnvFlags flag, bool value); + Room(ROOM_INFO& room); + Room(const Room& room) = delete; - [[nodiscard]] ReverbType GetReverbType() const; - void SetReverbType(ReverbType reverbType); + // Destructors - [[nodiscard]] bool IsTagPresent(const std::string& tag) const; + ~Room() = default; -private: - ROOM_INFO& m_room; -}; + // Getters + + int GetRoomNumber() const; + std::string GetName() const; + ScriptColor GetColor() const; + ReverbType GetReverbType() const; + + // Setters + + void SetName(const std::string& name); + void SetReverbType(ReverbType reverbType); + void SetFlag(RoomEnvFlags flag, bool value); + + // Inquirers + + bool IsTagPresent(const std::string& tag) const; + bool GetActive() const; // TODO: Rename to IsActive(). + bool GetFlag(RoomEnvFlags flag) const; // TODO: Rename to HasFlag(). + + // Operators + + Room& operator =(const Room& room) = delete; + }; +//} diff --git a/TombEngine/Scripting/Internal/TEN/Objects/Volume/VolumeObject.cpp b/TombEngine/Scripting/Internal/TEN/Objects/Volume/VolumeObject.cpp index 4338ce52f..a39198ad7 100644 --- a/TombEngine/Scripting/Internal/TEN/Objects/Volume/VolumeObject.cpp +++ b/TombEngine/Scripting/Internal/TEN/Objects/Volume/VolumeObject.cpp @@ -44,7 +44,7 @@ void Volume::Register(sol::table& parent) ScriptReserved_Disable, &Volume::Disable, ScriptReserved_ClearActivators, &Volume::ClearActivators, - ScriptReserved_GetName, &Volume::GetActive, + ScriptReserved_GetActive, &Volume::GetActive, ScriptReserved_IsMoveableInside, &Volume::IsMoveableInside); } diff --git a/TombEngine/Scripting/Internal/TEN/Rotation/Rotation.cpp b/TombEngine/Scripting/Internal/TEN/Rotation/Rotation.cpp index 1fad9a411..3a2cdf881 100644 --- a/TombEngine/Scripting/Internal/TEN/Rotation/Rotation.cpp +++ b/TombEngine/Scripting/Internal/TEN/Rotation/Rotation.cpp @@ -6,11 +6,10 @@ using namespace TEN::Math; -/*** Represents a degree-based 3D rotation. -All values are clamped to the range [0.0, 360.0]. -@tenprimitive Rotation -@pragma nostrip -*/ +/// Represents a degree-based 3D rotation. +// All values are clamped to the range [0.0, 360.0]. +// @tenprimitive Rotation +// @pragma nostrip void Rotation::Register(sol::table& parent) { @@ -21,25 +20,23 @@ void Rotation::Register(sol::table& parent) sol::meta_function::to_string, &Rotation::ToString, /// (float) X angle component. - //@mem x + // @mem x "x", &Rotation::x, /// (float) Y angle component. - //@mem y + // @mem y "y", &Rotation::y, /// (float) Z angle component. - //@mem z + // @mem z "z", &Rotation::z); } -/*** -@tparam float x X angle component. -@tparam float y Y angle component. -@tparam float z Z angle component. -@treturn Rotation A Rotation. -@function Rotation -*/ +/// @tparam float x X angle component. +// @tparam float y Y angle component. +// @tparam float z Z angle component. +// @treturn Rotation A Rotation. +// @function Rotation Rotation::Rotation(float x, float y, float z) { this->x = x; @@ -80,11 +77,9 @@ Rotation::operator Vector3() const return Vector3(x, y, z); }; -/*** -@tparam Rotation rotation this Rotation. -@treturn string A string showing the X, Y, and Z angle components of the Rotation. -@function __tostring -*/ +/// @tparam Rotation rotation this Rotation. +// @treturn string A string showing the X, Y, and Z angle components of the Rotation. +// @function __tostring std::string Rotation::ToString() const { return ("{ " + std::to_string(x) + ", " + std::to_string(y) + ", " + std::to_string(z) + " }"); diff --git a/TombEngine/Scripting/Internal/TEN/Strings/StringsHandler.cpp b/TombEngine/Scripting/Internal/TEN/Strings/StringsHandler.cpp index d2b5ba582..e90ea50b6 100644 --- a/TombEngine/Scripting/Internal/TEN/Strings/StringsHandler.cpp +++ b/TombEngine/Scripting/Internal/TEN/Strings/StringsHandler.cpp @@ -16,7 +16,7 @@ Display strings. StringsHandler::StringsHandler(sol::state* lua, sol::table& parent) : LuaHandler(lua) { - auto table = sol::table(m_lua->lua_state(), sol::create); + auto table = sol::table(_lua->lua_state(), sol::create); parent.set(ScriptReserved_Strings, table); /*** @@ -27,6 +27,10 @@ Show some text on-screen. If not given, the string will have an "infinite" life, and will show until @{HideString} is called or until the level is finished. Default: nil (i.e. infinite) +@tparam bool autoDelete should be string automatically deleted after timeout is reached. +If not given, the string will remain allocated even after timeout is reached, and can be +shown again without re-initialization. +Default: false */ table.set_function(ScriptReserved_ShowString, &StringsHandler::ShowString, this); @@ -36,7 +40,7 @@ Hide some on-screen text. @tparam DisplayString str the string object to hide. Must previously have been shown with a call to @{ShowString}, or this function will have no effect. */ - table.set_function(ScriptReserved_HideString, [this](const DisplayString& string) { ShowString(string, 0.0f); }); + table.set_function(ScriptReserved_HideString, [this](const DisplayString& string) { ShowString(string, 0.0f, false); }); /*** Checks if the string is shown @@ -84,11 +88,12 @@ bool StringsHandler::SetDisplayString(DisplayStringID id, const UserDisplayStrin return m_userDisplayStrings.insert_or_assign(id, displayString).second; } -void StringsHandler::ShowString(const DisplayString& str, sol::optional numSeconds) +void StringsHandler::ShowString(const DisplayString& str, sol::optional numSeconds, sol::optional autoDelete) { auto it = m_userDisplayStrings.find(str.GetID()); it->second._timeRemaining = numSeconds.value_or(0.0f); it->second._isInfinite = !numSeconds.has_value(); + it->second._deleteWhenZero = autoDelete.value_or(false); } bool StringsHandler::IsStringDisplaying(const DisplayString& displayString) diff --git a/TombEngine/Scripting/Internal/TEN/Strings/StringsHandler.h b/TombEngine/Scripting/Internal/TEN/Strings/StringsHandler.h index 6b28edf85..96c10f7db 100644 --- a/TombEngine/Scripting/Internal/TEN/Strings/StringsHandler.h +++ b/TombEngine/Scripting/Internal/TEN/Strings/StringsHandler.h @@ -26,7 +26,7 @@ public: std::optional> GetDisplayString(DisplayStringID id); bool ScheduleRemoveDisplayString(DisplayStringID id); - void ShowString(DisplayString const&, sol::optional nSeconds); + void ShowString(DisplayString const&, sol::optional nSeconds, sol::optional autoDelete); bool IsStringDisplaying(DisplayString const& str); diff --git a/TombEngine/Scripting/Internal/TEN/Util/LevelLog.h b/TombEngine/Scripting/Internal/TEN/Util/LevelLog.h index a87fd9351..554dde516 100644 --- a/TombEngine/Scripting/Internal/TEN/Util/LevelLog.h +++ b/TombEngine/Scripting/Internal/TEN/Util/LevelLog.h @@ -2,7 +2,7 @@ #include #include -#include "Game/debug/debug.h" +#include "Game/Debug/Debug.h" #include "Scripting/Internal/ReservedScriptNames.h" /*** diff --git a/TombEngine/Scripting/Internal/TEN/Vec2/Vec2.h b/TombEngine/Scripting/Internal/TEN/Vec2/Vec2.h index 0bae39e3b..072e0dfa9 100644 --- a/TombEngine/Scripting/Internal/TEN/Vec2/Vec2.h +++ b/TombEngine/Scripting/Internal/TEN/Vec2/Vec2.h @@ -9,10 +9,12 @@ public: static void Register(sol::table& parent); // Members + float x = 0; float y = 0; // Constructors + Vec2(float x, float y); Vec2(float value); Vec2(const Vector2& vector); @@ -28,6 +30,7 @@ public: float Length() const; // Meta functions + std::string ToString() const; static Vec2 Add(const Vec2& vector0, const Vec2& vector1); static Vec2 Subtract(const Vec2& vector0, const Vec2& vector1); @@ -38,9 +41,11 @@ public: static bool IsEqualTo(const Vec2& vector0, const Vec2& vector1); // Converters + Vector2 ToVector2() const; //Vector2i ToVector2i() const; // Operators + operator Vector2() const; }; diff --git a/TombEngine/Scripting/Internal/TEN/Vec3/Vec3.h b/TombEngine/Scripting/Internal/TEN/Vec3/Vec3.h index 1087ed117..de2d95365 100644 --- a/TombEngine/Scripting/Internal/TEN/Vec3/Vec3.h +++ b/TombEngine/Scripting/Internal/TEN/Vec3/Vec3.h @@ -1,10 +1,10 @@ #pragma once +namespace sol { class state; } class GameVector; class Pose; class Rotation; class Vector3i; -namespace sol { class state; } class Vec3 { @@ -12,11 +12,13 @@ public: static void Register(sol::table& parent); // Members + float x = 0; float y = 0; float z = 0; // Constructors + Vec3() {}; Vec3(float x, float y, float z); Vec3(float value); @@ -24,6 +26,7 @@ public: Vec3(const Vector3i& vector); // Utilities + Vec3 Normalize() const; Vec3 Rotate(const Rotation& rot) const; Vec3 Lerp(const Vec3& vector, float alpha) const; @@ -33,6 +36,7 @@ public: float Length() const; // Meta functions + std::string ToString() const; static Vec3 Add(const Vec3& vector0, const Vec3& vector1); static Vec3 Subtract(const Vec3& vector0, const Vec3& vector1); @@ -43,10 +47,12 @@ public: static bool IsEqualTo(const Vec3& vector0, const Vec3& vector1); // Converters + Vector3 ToVector3() const; Vector3i ToVector3i() const; GameVector ToGameVector() const; // Operators + operator Vector3() const; }; diff --git a/TombEngine/Shaders/AnimatedTextures.hlsli b/TombEngine/Shaders/AnimatedTextures.hlsli index bf733d74a..f43de521b 100644 --- a/TombEngine/Shaders/AnimatedTextures.hlsli +++ b/TombEngine/Shaders/AnimatedTextures.hlsli @@ -36,7 +36,7 @@ float2 CalculateUVRotate(float2 uv, unsigned int frame) float2 GetFrame(unsigned int index, unsigned int offset) { - float speed = FPS / 30.0f; + float speed = (float)FPS / 30.0f; int frame = int(Frame * speed + offset) % NumAnimFrames; float2 result = 0; diff --git a/TombEngine/Shaders/CBCamera.hlsli b/TombEngine/Shaders/CBCamera.hlsli index ff1f232db..2c627acb5 100644 --- a/TombEngine/Shaders/CBCamera.hlsli +++ b/TombEngine/Shaders/CBCamera.hlsli @@ -1,3 +1,6 @@ +#ifndef CBCAMERASHADER +#define CBCAMERASHADER + #include "./Math.hlsli" cbuffer CBCamera : register(b0) @@ -30,8 +33,11 @@ cbuffer CBCamera : register(b0) float NearPlane; float FarPlane; //-- + int RefreshRate; int NumFogBulbs; - float3 Padding2; + float2 Padding2; //-- ShaderFogBulb FogBulbs[MAX_FOG_BULBS]; -}; \ No newline at end of file +}; + +#endif \ No newline at end of file diff --git a/TombEngine/Shaders/CBPostProcess.hlsli b/TombEngine/Shaders/CBPostProcess.hlsli index a4ab6cd8b..f31a7d75f 100644 --- a/TombEngine/Shaders/CBPostProcess.hlsli +++ b/TombEngine/Shaders/CBPostProcess.hlsli @@ -1,5 +1,14 @@ #include "./Math.hlsli" +struct ShaderLensFlare +{ + float3 Position; + float Padding1; + //-- + float3 Color; + float Padding2; +}; + cbuffer CBPostProcess : register(b7) { float CinematicBarsHeight; @@ -11,4 +20,8 @@ cbuffer CBPostProcess : register(b7) float3 Tint; //-- float4 SSAOKernel[64]; + //-- + ShaderLensFlare LensFlares[8]; + //-- + int NumLensFlares; }; \ No newline at end of file diff --git a/TombEngine/Shaders/GBuffer.fx b/TombEngine/Shaders/GBuffer.fx index 830ed1c33..566ca6518 100644 --- a/TombEngine/Shaders/GBuffer.fx +++ b/TombEngine/Shaders/GBuffer.fx @@ -1,6 +1,7 @@ #include "./CBCamera.hlsli" #include "./VertexInput.hlsli" #include "./VertexEffects.hlsli" +#include "./AnimatedTextures.hlsli" #include "./Blending.hlsli" #include "./Math.hlsli" @@ -102,7 +103,16 @@ PixelShaderInput VSRooms(VertexShaderInput input) output.Tangent = input.Tangent; output.Binormal = input.Binormal; output.PositionCopy = screenPos; - output.UV = input.UV; + +#ifdef ANIMATED + + if (Type == 0) + output.UV = GetFrame(input.PolyIndex, input.AnimationFrameOffset); + else + output.UV = input.UV; // TODO: true UVRotate in future? +#else + output.UV = input.UV; +#endif return output; } diff --git a/TombEngine/Shaders/PostProcess.fx b/TombEngine/Shaders/PostProcess.fx index bc17733d0..3d7b7b786 100644 --- a/TombEngine/Shaders/PostProcess.fx +++ b/TombEngine/Shaders/PostProcess.fx @@ -89,4 +89,80 @@ float4 PSFinalPass(PixelShaderInput input) : SV_TARGET output.xyz = output.xyz * Tint; return output; +} + +float3 LensFlare(float2 uv, float2 pos) +{ + float intensity = 1.5f; + float2 main = uv - pos; + float2 uvd = uv * (length(uv)); + + float dist = length(main); + dist = pow(dist, 0.1f); + + float f1 = max(0.01f - pow(length(uv + 1.2f * pos), 1.9f), 0.0f) * 7.0f; + + float f2 = max(1.0f / (1.0f + 32.0f * pow(length(uvd + 0.8f * pos), 2.0f)), 0.0f) * 0.1f; + float f22 = max(1.0f / (1.0f + 32.0f * pow(length(uvd + 0.85f * pos), 2.0f)), 0.0f) * 0.08f; + float f23 = max(1.0f / (1.0f + 32.0f * pow(length(uvd + 0.9f * pos), 2.0f)), 0.0f) * 0.06f; + + float2 uvx = lerp(uv, uvd, -0.5f); + + float f4 = max(0.01f - pow(length(uvx + 0.4f * pos), 2.4f), 0.0f) * 6.0f; + float f42 = max(0.01f - pow(length(uvx + 0.45f * pos), 2.4f), 0.0f) * 5.0f; + float f43 = max(0.01f - pow(length(uvx + 0.5f * pos), 2.4f), 0.0f) * 3.0f; + + uvx = lerp(uv, uvd, -0.4f); + + float f5 = max(0.01f - pow(length(uvx + 0.2f * pos), 5.5f), 0.0f) * 2.0f; + float f52 = max(0.01f - pow(length(uvx + 0.4f * pos), 5.5f), 0.0f) * 2.0f; + float f53 = max(0.01f - pow(length(uvx + 0.6f * pos), 5.5f), 0.0f) * 2.0f; + + uvx = lerp(uv, uvd, -0.5f); + + float f6 = max(0.01f - pow(length(uvx - 0.3f * pos), 1.6f), 0.0f) * 6.0f; + float f62 = max(0.01f - pow(length(uvx - 0.325f * pos), 1.6f), 0.0f) * 3.0f; + float f63 = max(0.01f - pow(length(uvx - 0.35f * pos), 1.6f), 0.0f) * 5.0f; + + float3 c = float3(0.0f, 0.0f, 0.0f); + + c.r += f2 + f4 + f5 + f6; + c.g += f22 + f42 + f52 + f62; + c.b += f23 + f43 + f53 + f63; + + c = c * 1.3f - float3(length(uvd) * 0.05f, length(uvd) * 0.05f, length(uvd) * 0.05f); + + return c * intensity; +} + +float3 LensFlareColorCorrection(float3 color, float factor,float factor2) +{ + float w = color.x + color.y + color.z; + return lerp(color, float3(w, w, w) * factor, w * factor2); +} + +float4 PSLensFlare(PixelShaderInput input) : SV_Target +{ + float4 color = ColorTexture.Sample(ColorSampler, input.UV); + + float4 position = input.PositionCopy; + float3 totalLensFlareColor = float3(0.0f, 0.0f, 0.0f); + + for (int i = 0; i < NumLensFlares; i++) + { + float4 lensFlarePosition = float4(LensFlares[i].Position, 1.0f); + lensFlarePosition = mul(mul(lensFlarePosition, View), Projection); + lensFlarePosition.xyz /= lensFlarePosition.w; + + float3 lensFlareColor = max(float3(0.0f, 0.0f, 0.0f), + LensFlares[i].Color * + float3(4.5f, 3.6f, 3.6f) * + LensFlare(position.xy, lensFlarePosition.xy)); + lensFlareColor = LensFlareColorCorrection(lensFlareColor, 0.5f, 0.1f); + totalLensFlareColor += lensFlareColor; + } + + color.xyz += totalLensFlareColor; + + return color; } \ No newline at end of file diff --git a/TombEngine/Shaders/ShaderLight.hlsli b/TombEngine/Shaders/ShaderLight.hlsli index eae69ab08..64c47f13e 100644 --- a/TombEngine/Shaders/ShaderLight.hlsli +++ b/TombEngine/Shaders/ShaderLight.hlsli @@ -1,6 +1,7 @@ #ifndef SHADER_LIGHT #define SHADER_LIGHT +#include "./CBCamera.hlsli" #include "./Math.hlsli" float3 DoSpecularPoint(float3 pos, float3 n, ShaderLight light, float strength) diff --git a/TombEngine/Shaders/VertexInput.hlsli b/TombEngine/Shaders/VertexInput.hlsli index 1252a7e8c..197c17d80 100644 --- a/TombEngine/Shaders/VertexInput.hlsli +++ b/TombEngine/Shaders/VertexInput.hlsli @@ -1,16 +1,16 @@ - -struct VertexShaderInput -{ - float3 Position: POSITION0; - float3 Normal: NORMAL0; - float2 UV: TEXCOORD0; - float4 Color: COLOR0; - float3 Tangent: TANGENT0; - float3 Binormal: BINORMAL0; - unsigned int AnimationFrameOffset: ANIMATIONFRAMEOFFSET; - float4 Effects: EFFECTS; - float Bone: BLENDINDICES; - unsigned int PolyIndex : POLYINDEX; - unsigned int Index: DRAWINDEX; - int Hash : HASH; -}; + +struct VertexShaderInput +{ + float3 Position: POSITION0; + float3 Normal: NORMAL0; + float2 UV: TEXCOORD0; + float4 Color: COLOR0; + float3 Tangent: TANGENT0; + float3 Binormal: BINORMAL0; + unsigned int AnimationFrameOffset: ANIMATIONFRAMEOFFSET; + float4 Effects: EFFECTS; + float Bone: BLENDINDICES; + unsigned int PolyIndex : POLYINDEX; + unsigned int Index: DRAWINDEX; + int Hash : HASH; +}; diff --git a/TombEngine/Sound/sound.cpp b/TombEngine/Sound/sound.cpp index 7580fe325..fdd831cbc 100644 --- a/TombEngine/Sound/sound.cpp +++ b/TombEngine/Sound/sound.cpp @@ -11,12 +11,21 @@ #include "Game/Lara/lara.h" #include "Game/room.h" #include "Game/Setup.h" +#include "Math/Math.h" #include "Specific/configuration.h" #include "Specific/level.h" #include "Specific/trutils.h" #include "Specific/winmain.h" using namespace TEN::Gui; +using namespace TEN::Math; + +enum SoundSourceFlags +{ + SS_FLAG_PLAY_FLIP_ROOM = 1 << 13, + SS_FLAG_PLAY_BASE_ROOM = 1 << 14, + SS_FLAG_PLAY_ALWAYS = 1 << 15 +}; HSAMPLE BASS_SamplePointer[SOUND_MAX_SAMPLES]; HSTREAM BASS_3D_Mixdown; @@ -150,44 +159,46 @@ bool LoadSample(char* pointer, int compSize, int uncompSize, int index) return true; } -bool SoundEffect(int effectID, Pose* position, SoundEnvironment condition, float pitchMultiplier, float gainMultiplier) +// TODO: Use std::optional for pose argument. +bool SoundEffect(int soundID, Pose* pose, SoundEnvironment soundEnv, float pitchMult, float gainMult) { if (!g_Configuration.EnableSound) return false; - if (effectID >= g_Level.SoundMap.size()) + if (soundID >= g_Level.SoundMap.size()) return false; if (BASS_GetDevice() == -1) return false; - if (condition != SoundEnvironment::Always) + // Test if sound effect environment matches camera environment. + if (soundEnv != SoundEnvironment::Always) { - // Get current camera room's environment - auto cameraCondition = TestEnvironment(ENV_FLAG_WATER, Camera.pos.RoomNumber) ? SoundEnvironment::Water : SoundEnvironment::Land; - - // Don't play effect if effect's environment isn't the same as camera position's environment - if (condition != cameraCondition) + bool isCameraUnderwater = TestEnvironment(ENV_FLAG_WATER, Camera.pos.RoomNumber); + if ((soundEnv == SoundEnvironment::Underwater && !isCameraUnderwater) || + (soundEnv != SoundEnvironment::Underwater && isCameraUnderwater)) + { return false; + } } - // Get actual sample index from SoundMap - int sampleIndex = g_Level.SoundMap[effectID]; + // Get actual sample index from sound map. + int sampleIndex = g_Level.SoundMap[soundID]; - // -1 means no such effect exists in level file. - // We set it to -2 afterwards to prevent further debug message firings. + // -1 means no such effect exists in level file.Set to -2 afterwards to prevent further debug message firings. if (sampleIndex == -1) { - TENLog("Missing sound effect: " + std::to_string(effectID), LogLevel::Warning); - g_Level.SoundMap[effectID] = -2; + TENLog("Missing sound effect " + std::to_string(soundID), LogLevel::Warning); + g_Level.SoundMap[soundID] = -2; return false; } else if (sampleIndex == -2) + { return false; + } - SampleInfo* sampleInfo = &g_Level.SoundDetails[sampleIndex]; - - if (sampleInfo->Number < 0) + const auto& sample = g_Level.SoundDetails[sampleIndex]; + if (sample.Number < 0) { TENLog("No valid samples count for effect " + std::to_string(sampleIndex), LogLevel::Warning); return false; @@ -196,77 +207,85 @@ bool SoundEffect(int effectID, Pose* position, SoundEnvironment condition, float // Assign common sample flags. DWORD sampleFlags = SOUND_SAMPLE_FLAGS; - // Effect's chance to play. - if ((sampleInfo->Randomness) && ((GetRandomControl() & UCHAR_MAX) > sampleInfo->Randomness)) + // Test play chance. + if (sample.Randomness && ((GetRandomControl() & UCHAR_MAX) > sample.Randomness)) return false; - // Apply 3D attrib only to sound with position property - if (position) + // Apply 3D attribute only to sound with position property. + if (pose != nullptr) sampleFlags |= BASS_SAMPLE_3D; - // Set & randomize volume (if needed) - float gain = (static_cast(sampleInfo->Volume) / UCHAR_MAX) * std::clamp(gainMultiplier, SOUND_MIN_PARAM_MULTIPLIER, SOUND_MAX_PARAM_MULTIPLIER); - if ((sampleInfo->Flags & SOUND_FLAG_RND_GAIN)) - gain -= (static_cast(GetRandomControl()) / static_cast(RAND_MAX))* SOUND_MAX_GAIN_CHANGE; + // Set and randomize volume (if needed). + float gain = ((float)sample.Volume / UCHAR_MAX) * std::clamp(gainMult, SOUND_MIN_PARAM_MULTIPLIER, SOUND_MAX_PARAM_MULTIPLIER); + if ((sample.Flags & SOUND_FLAG_RND_GAIN)) + gain -= Random::GenerateFloat(0.0f, 1.0f) * SOUND_MAX_GAIN_CHANGE; - // Set and randomize pitch and additionally multiply by provided value (for vehicles etc) - float pitch = (1.0f + static_cast(sampleInfo->Pitch) / 127.0f) * std::clamp(pitchMultiplier, SOUND_MIN_PARAM_MULTIPLIER, SOUND_MAX_PARAM_MULTIPLIER); + // Set and randomize pitch and additionally multiply by provided value (e.g. for vehicles). + float pitch = (1.0f + ((float)sample.Pitch / 127.0f)) * std::clamp(pitchMult, SOUND_MIN_PARAM_MULTIPLIER, SOUND_MAX_PARAM_MULTIPLIER); // Randomize pitch (if needed) - if ((sampleInfo->Flags & SOUND_FLAG_RND_PITCH)) - pitch += ((static_cast(GetRandomControl()) / static_cast(RAND_MAX)) - 0.5f) * SOUND_MAX_PITCH_CHANGE * 2.0f; + if ((sample.Flags & SOUND_FLAG_RND_PITCH)) + pitch += Random::GenerateFloat(-0.5f, 0.5f) * (SOUND_MAX_PITCH_CHANGE * 2); - // Calculate sound radius and distance to sound - float radius = (float)(sampleInfo->Radius) * BLOCK(1); - float distance = Sound_DistanceToListener(position); + // Calculate sound radius and distance to sound. + float radius = BLOCK(sample.Radius); + float dist = Sound_DistanceToListener(pose); - // Don't play sound if it's too far from listener's position. - if (distance > radius) + // Skip playing if too far from listener position. + if (dist > radius) return false; - // Get final volume of a sound. - float volume = Sound_Attenuate(gain, distance, radius); + // Get final sound volume. + float volume = Sound_Attenuate(gain, dist, radius); - // Get existing index, if any, of sound which is playing. - int existingChannel = Sound_EffectIsPlaying(effectID, position); + // Get existing index, if any, of playing sound. + int existingChannel = Sound_EffectIsPlaying(soundID, pose); - // Select behaviour based on effect playback type (bytes 0-1 of flags field) - auto playType = (SoundPlayMode)(sampleInfo->Flags & 3); - switch (playType) + // Select behaviour based on effect playback type (bytes 0-1 of flags field). + auto playMode = (SoundPlayMode)(sample.Flags & 3); + switch (playMode) { case SoundPlayMode::Normal: break; case SoundPlayMode::Wait: - if (existingChannel != SOUND_NO_CHANNEL) // Don't play until stopped + // Don't play until stopped. + if (existingChannel != SOUND_NO_CHANNEL) return false; break; case SoundPlayMode::Restart: - if (existingChannel != SOUND_NO_CHANNEL) // Stop existing and continue + // Stop existing and continue. + if (existingChannel != SOUND_NO_CHANNEL) Sound_FreeSlot(existingChannel, SOUND_XFADETIME_CUTSOUND); break; case SoundPlayMode::Looped: - if (existingChannel != SOUND_NO_CHANNEL) // Just update parameters and return, if already playing + // Update parameters and return if already playing. + if (existingChannel != SOUND_NO_CHANNEL) { - Sound_UpdateEffectPosition(existingChannel, position); + Sound_UpdateEffectPosition(existingChannel, pose); Sound_UpdateEffectAttributes(existingChannel, pitch, volume); return false; } + sampleFlags |= BASS_SAMPLE_LOOP; break; } - // Randomly select arbitrary sample from the list, if more than one is present + // Randomly select arbitrary sample from list if more than one is present. int sampleToPlay = 0; - int numSamples = (sampleInfo->Flags >> 2) & 15; - if (numSamples == 1) - sampleToPlay = sampleInfo->Number; + int sampleCount = (sample.Flags >> 2) & 15; + if (sampleCount == 1) + { + sampleToPlay = sample.Number; + } else - sampleToPlay = sampleInfo->Number + (int)((GetRandomControl() * numSamples) >> 15); + { + sampleToPlay = sample.Number + (int)((GetRandomControl() * sampleCount) >> 15); + } - // Get free channel to play sample + // Get free channel to play sample. int freeSlot = Sound_GetFreeSlot(); if (freeSlot == SOUND_NO_CHANNEL) { @@ -280,32 +299,33 @@ bool SoundEffect(int effectID, Pose* position, SoundEnvironment condition, float if (Sound_CheckBASSError("Trying to create channel for sample %d", false, sampleToPlay)) return false; - // Finally ready to play sound, assign it to sound slot. - SoundSlot[freeSlot].State = SoundState::Idle; - SoundSlot[freeSlot].EffectID = effectID; - SoundSlot[freeSlot].Channel = channel; - SoundSlot[freeSlot].Gain = gain; - SoundSlot[freeSlot].Origin = position ? Vector3(position->Position.x, position->Position.y, position->Position.z) : SOUND_OMNIPRESENT_ORIGIN; + // Ready to play sound; assign to sound slot. + auto& soundSlot = SoundSlot[freeSlot]; + soundSlot.State = SoundState::Idle; + soundSlot.EffectID = soundID; + soundSlot.Channel = channel; + soundSlot.Gain = gain; + soundSlot.Origin = (pose != nullptr) ? pose->Position.ToVector3() : SOUND_OMNIPRESENT_ORIGIN; if (Sound_CheckBASSError("Applying pitch/gain attribs on channel %x, sample %d", false, channel, sampleToPlay)) return false; - // Set looped flag, if necessary - if (playType == SoundPlayMode::Looped) + // Set looped flag if necessary. + if (playMode == SoundPlayMode::Looped) BASS_ChannelFlags(channel, BASS_SAMPLE_LOOP, BASS_SAMPLE_LOOP); - // Play channel + // Play channel. BASS_ChannelPlay(channel, false); if (Sound_CheckBASSError("Queuing channel %x on sample mixer", false, freeSlot)) return false; - // Set attributes - BASS_ChannelSet3DAttributes(channel, position ? BASS_3DMODE_NORMAL : BASS_3DMODE_OFF, SOUND_MAXVOL_RADIUS, radius, 360, 360, 0.0f); - Sound_UpdateEffectPosition(freeSlot, position, true); + // Set attributes. + BASS_ChannelSet3DAttributes(channel, pose ? BASS_3DMODE_NORMAL : BASS_3DMODE_OFF, SOUND_MAXVOL_RADIUS, radius, 360, 360, 0.0f); + Sound_UpdateEffectPosition(freeSlot, pose, true); Sound_UpdateEffectAttributes(freeSlot, pitch, volume); - if (Sound_CheckBASSError("Applying 3D attribs on channel %x, sound %d", false, channel, effectID)) + if (Sound_CheckBASSError("Applying 3D attribs on channel %x, sound %d", false, channel, soundID)) return false; return true; @@ -1070,24 +1090,19 @@ int GetShatterSound(int shatterID) void PlaySoundSources() { - static constexpr int PLAY_ALWAYS = 0x8000; - static constexpr int PLAY_BASE_ROOM = 0x4000; - static constexpr int PLAY_FLIP_ROOM = 0x2000; - - for (size_t i = 0; i < g_Level.SoundSources.size(); i++) + for (const auto& soundSource : g_Level.SoundSources) { - const auto& sound = g_Level.SoundSources[i]; - - int group = sound.Flags & 0x1FFF; + int group = soundSource.Flags & 0x1FFF; if (group >= MAX_FLIPMAP) continue; - if (!FlipStats[group] && (sound.Flags & PLAY_FLIP_ROOM)) - continue; - else if (FlipStats[group] && (sound.Flags & PLAY_BASE_ROOM)) + if ((!FlipStats[group] && (soundSource.Flags & SS_FLAG_PLAY_FLIP_ROOM)) || + (FlipStats[group] && (soundSource.Flags & SS_FLAG_PLAY_BASE_ROOM))) + { continue; + } - SoundEffect(sound.SoundID, (Pose*)&sound.Position); + SoundEffect(soundSource.SoundID, (Pose*)&soundSource.Position); } } diff --git a/TombEngine/Sound/sound.h b/TombEngine/Sound/sound.h index 8b70f7ea2..eb18997e9 100644 --- a/TombEngine/Sound/sound.h +++ b/TombEngine/Sound/sound.h @@ -48,9 +48,11 @@ enum class SoundTrackType enum class SoundEnvironment { + Always, Land, - Water, - Always + ShallowWater, + Swamp, + Underwater }; enum class SoundPlayMode @@ -121,9 +123,9 @@ struct SoundTrackInfo struct SoundSourceInfo { Vector3i Position = Vector3i::Zero; - int SoundID = 0; - int Flags = 0; - std::string Name {}; + int SoundID = 0; + int Flags = 0; + std::string Name = {}; SoundSourceInfo() { @@ -131,27 +133,27 @@ struct SoundSourceInfo SoundSourceInfo(int xPos, int yPos, int zPos) { - this->Position = Vector3i(xPos, yPos, zPos); + Position = Vector3i(xPos, yPos, zPos); } SoundSourceInfo(int xPos, int yPos, int zPos, short soundID) { - this->Position = Vector3i(xPos, yPos, zPos); - this->SoundID = soundID; + Position = Vector3i(xPos, yPos, zPos); + SoundID = soundID; } SoundSourceInfo(int xPos, int yPos, int zPos, short soundID, short newflags) { - this->Position = Vector3i(xPos, yPos, zPos); - this->SoundID = soundID; - this->Flags = newflags; + Position = Vector3i(xPos, yPos, zPos); + SoundID = soundID; + Flags = newflags; } }; extern std::map SoundTrackMap; extern std::unordered_map SoundTracks; -bool SoundEffect(int effectID, Pose* position, SoundEnvironment condition = SoundEnvironment::Land, float pitchMultiplier = 1.0f, float gainMultiplier = 1.0f); +bool SoundEffect(int soundID, Pose* pose, SoundEnvironment soundEnv = SoundEnvironment::Land, float pitchMult = 1.0f, float gainMult = 1.0f); void StopSoundEffect(short effectID); bool LoadSample(char *buffer, int compSize, int uncompSize, int currentIndex); void FreeSamples(); diff --git a/TombEngine/Sound/sound_effects.h b/TombEngine/Sound/sound_effects.h index f0643e6c8..37b3d2ae2 100644 --- a/TombEngine/Sound/sound_effects.h +++ b/TombEngine/Sound/sound_effects.h @@ -2,6 +2,8 @@ enum SOUND_EFFECTS { + // Default TR4 sounds + SFX_TR4_LARA_FOOTSTEPS = 0, SFX_TR4_LARA_CLIMB2 = 1, SFX_TR4_LARA_NO_ENGLISH = 2, @@ -151,7 +153,7 @@ enum SOUND_EFFECTS SFX_TR4_LARA_DEATH3 = 146, SFX_TR4_ROLLING_BALL = 147, SFX_TR4_RAISING_BLOCK = 148, - SFX_TR4_RUMBLE_NEXTDOOR = 149, + SFX_TR4_RAISING_BLOCK_2 = 149, SFX_TR4_LOOP_FOR_SMALL_FIRES = 150, SFX_TR4_CHAINS_LIBRARY = 151, SFX_TR4_VEHICLE_JEEP_START = 152, @@ -361,7 +363,7 @@ enum SOUND_EFFECTS SFX_TR4_LIGHT_BEAM_LOOP = 356, SFX_TR4_GUIDE_FIRE_LIGHT = 357, SFX_TR4_AUTOGUNS = 358, - SFX_TR4_EMPTY8 = 359, + SFX_TR4_ENVIORONMENT_RUMBLE = 359, SFX_TR4_STEAM = 360, SFX_TR4_GARAGE_DOOR = 361, SFX_TR4_WIND = 362, @@ -384,6 +386,9 @@ enum SOUND_EFFECTS SFX_TR4_FLARE_IGNITE_DRY = 379, SFX_TR4_FLARE_IGNITE_UNDERWATER = 380, SFX_TR4_FLARE_BURN_UNDERWATER = 381, + + // TR1 sounds + SFX_TR1_BEAR_GROWL = 382, SFX_TR1_BEAR_FEET = 383, SFX_TR1_BEAR_ATTACK = 384, @@ -476,6 +481,9 @@ enum SOUND_EFFECTS SFX_TR1_PIERRE_DEATH = 471, SFX_TR1_PIERRE_SPEECH = 472, SFX_TR1_PIERRE_MAGNUM_FIRE = 473, + + // TR2 sounds + SFX_TR2_LARA_M16_FIRE = 474, SFX_TR2_LARA_M16_STOP = 475, SFX_TR2_HENCHMAN_BELT_JINGLE = 476, @@ -686,6 +694,9 @@ enum SOUND_EFFECTS SFX_TR2_SLAM_DOOR_CLOSE = 681, SFX_TR2_SWINGING = 682, SFX_TR2_SWINGING_TRAP = 683, + + // TR3 sounds + SFX_TR3_TONY_ATTACK = 684, SFX_TR3_TONY_LAUGH = 685, SFX_TR3_TONY_NORMAL_DEATH = 686, @@ -900,6 +911,9 @@ enum SOUND_EFFECTS SFX_TR3_VERY_SMALL_WINCH = 895, SFX_TR3_WALL_BLADES = 896, SFX_TR3_WATER_MILL = 897, + + // TR5 sounds + SFX_TR5_INSIDEMILL = 898, SFX_TR5_2GUNTEX_LASER_START = 899, SFX_TR5_2GUNTEX_STAIR_FALL = 900, @@ -1150,17 +1164,19 @@ enum SOUND_EFFECTS SFX_TR2_DRAGON_FALL = 1145, SFX_TR2_MARCO_BARTOLLI_TRANSFORM = 1146, - // Custom footsteps - SFX_CUSTOM_FOOTSTEP_1 = 1150, - SFX_CUSTOM_FOOTSTEP_2 = 1151, - SFX_CUSTOM_FOOTSTEP_3 = 1152, - SFX_CUSTOM_FOOTSTEP_4 = 1153, - SFX_CUSTOM_FOOTSTEP_5 = 1154, - SFX_CUSTOM_FOOTSTEP_6 = 1155, - SFX_CUSTOM_FOOTSTEP_7 = 1156, - SFX_CUSTOM_FOOTSTEP_8 = 1157, + // TombEngine sounds + + SFX_TEN_CUSTOM_FOOTSTEPS_1 = 1150, + SFX_TEN_CUSTOM_FOOTSTEPS_2 = 1151, + SFX_TEN_CUSTOM_FOOTSTEPS_3 = 1152, + SFX_TEN_CUSTOM_FOOTSTEPS_4 = 1153, + SFX_TEN_CUSTOM_FOOTSTEPS_5 = 1154, + SFX_TEN_CUSTOM_FOOTSTEPS_6 = 1155, + SFX_TEN_CUSTOM_FOOTSTEPS_7 = 1156, + SFX_TEN_CUSTOM_FOOTSTEPS_8 = 1157, // Custom pushable sounds + SFX_TEN_PUSHABLES_MOVE_MUD = 1158, SFX_TEN_PUSHABLES_STOP_MUD = 1159, SFX_TEN_PUSHABLES_COLLIDE_MUD = 1160, @@ -1179,7 +1195,7 @@ enum SOUND_EFFECTS SFX_TEN_PUSHABLES_MOVE_WATER = 1173, SFX_TEN_PUSHABLES_STOP_WATER = 1174, SFX_TEN_PUSHABLES_COLLIDE_WATER = 1175, - SFX_TEN_PUSHABLES_STOP_MOVE_STONE = 1176, + SFX_TEN_PUSHABLES_MOVE_STONE = 1176, SFX_TEN_PUSHABLES_STOP_STONE = 1177, SFX_TEN_PUSHABLES_COLLIDE_STONE = 1178, SFX_TEN_PUSHABLES_MOVE_WOOD = 1179, @@ -1198,5 +1214,11 @@ enum SOUND_EFFECTS SFX_TEN_PUSHABLES_STOP_CONCRETE = 1192, SFX_TEN_PUSHABLES_COLLIDE_CONCRETE = 1193, - NUM_SFX //Custom Sound Effects from 1158 onwards + // New sounds + + SFX_TEN_STEAM_EMITTER_LOOP = 1194, + + // Custom sounds from 1195 onward + + NUM_SFX }; diff --git a/TombEngine/Specific/BitField.h b/TombEngine/Specific/BitField.h index 98592fd1e..5bd3104e3 100644 --- a/TombEngine/Specific/BitField.h +++ b/TombEngine/Specific/BitField.h @@ -9,23 +9,28 @@ namespace TEN::Utils { private: // Constants + static constexpr auto SIZE_DEFAULT = 32; // Members + std::vector _bits = {}; public: // Presets + static const BitField Empty; static const BitField Default; // Constructors + BitField(); BitField(unsigned int size); BitField(unsigned int size, unsigned int packedBits); BitField(const std::string& bitString); // Getters + unsigned int GetSize() const; unsigned int GetCount() const; @@ -41,17 +46,20 @@ namespace TEN::Utils void FlipAll(); // Inquirers + bool Test(const std::vector& indices, bool testAny = true) const; bool Test(unsigned int index) const; bool TestAny() const; bool TestAll() const; // Converters + unsigned int ToPackedBits() const; std::string ToString() const; // Operators // NOTE: packedBits will not be assessed in full if the size of the given BitField object is less than SIZE_DEFAULT. + bool operator ==(unsigned int packedBits) const; bool operator !=(unsigned int packedBits) const; BitField& operator =(unsigned int packedBits); @@ -62,6 +70,7 @@ namespace TEN::Utils private: // Helpers + void Fill(bool value); }; } diff --git a/TombEngine/Specific/Input/Input.cpp b/TombEngine/Specific/Input/Input.cpp index c93607644..594992735 100644 --- a/TombEngine/Specific/Input/Input.cpp +++ b/TombEngine/Specific/Input/Input.cpp @@ -463,10 +463,9 @@ namespace TEN::Input (((rawAxes.x - -DISPLAY_SPACE_RES.x) * 2) / (DISPLAY_SPACE_RES.x - -DISPLAY_SPACE_RES.x)) - 1.0f, (((rawAxes.y - -DISPLAY_SPACE_RES.y) * 2) / (DISPLAY_SPACE_RES.y - -DISPLAY_SPACE_RES.y)) - 1.0f); - // Apply sensitivity and smoothing. + // Apply sensitivity. float sensitivity = (g_Configuration.MouseSensitivity * 0.1f) + 0.4f; - float smoothing = 1.0f - (g_Configuration.MouseSmoothing * 0.1f); - normAxes *= sensitivity * smoothing; + normAxes *= sensitivity; // Set mouse axis values. AxisMap[(int)InputAxis::Mouse] = normAxes; @@ -633,9 +632,9 @@ namespace TEN::Input { // Save screenshot. static bool dbScreenshot = true; - if (KeyMap[KC_SYSRQ] && dbScreenshot) + if ((KeyMap[KC_SYSRQ] || KeyMap[KC_F12]) && dbScreenshot) g_Renderer.SaveScreenshot(); - dbScreenshot = !KeyMap[KC_SYSRQ]; + dbScreenshot = !(KeyMap[KC_SYSRQ] || KeyMap[KC_F12]); // Toggle fullscreen. static bool dbFullscreen = true; @@ -719,7 +718,10 @@ namespace TEN::Input action.Update(Key((int)action.GetID())); if (applyQueue) + { ApplyActionQueue(); + ClearActionQueue(); + } // Additional handling. HandleHotkeyActions(); diff --git a/TombEngine/Specific/Input/InputAction.cpp b/TombEngine/Specific/Input/InputAction.cpp index 6c62db8b7..48b28f6c2 100644 --- a/TombEngine/Specific/Input/InputAction.cpp +++ b/TombEngine/Specific/Input/InputAction.cpp @@ -1,47 +1,44 @@ #include "framework.h" #include "Specific/Input/InputAction.h" -#include "Renderer/Renderer.h" #include "Specific/clock.h" -using TEN::Renderer::g_Renderer; - namespace TEN::Input { InputAction::InputAction(ActionID actionID) { - ID = actionID; + _id = actionID; } ActionID InputAction::GetID() const { - return ID; + return _id; } float InputAction::GetValue() const { - return Value; + return _value; } float InputAction::GetTimeActive() const { - return TimeActive; + return _timeActive; } float InputAction::GetTimeInactive() const { - return TimeInactive; + return _timeInactive; } bool InputAction::IsClicked() const { - return ((Value != 0.0f) && (PrevValue == 0.0f)); + return ((_value != 0.0f) && (_prevValue == 0.0f)); } bool InputAction::IsHeld(float delayInSec) const { float delayInFrameTime = (delayInSec == 0.0f) ? 0.0f : round(delayInSec / DELTA_TIME); - return ((Value != 0.0f) && (TimeActive >= delayInFrameTime)); + return ((_value != 0.0f) && (_timeActive >= delayInFrameTime)); } // NOTE: To avoid stutter on second pulse, ensure initialDelayInSec is multiple of delayInSec. @@ -50,12 +47,12 @@ namespace TEN::Input if (IsClicked()) return true; - if (!IsHeld() || PrevTimeActive == 0.0f || TimeActive == PrevTimeActive) + if (!IsHeld() || _prevTimeActive == 0.0f || _timeActive == _prevTimeActive) return false; - float activeDelayInFrameTime = (TimeActive > round(initialDelayInSec / DELTA_TIME)) ? round(delayInSec / DELTA_TIME) : round(initialDelayInSec / DELTA_TIME); - float delayInFrameTime = std::floor(TimeActive / activeDelayInFrameTime) * activeDelayInFrameTime; - if (delayInFrameTime > (std::floor(PrevTimeActive / activeDelayInFrameTime) * activeDelayInFrameTime)) + float activeDelayInFrameTime = (_timeActive > round(initialDelayInSec / DELTA_TIME)) ? round(delayInSec / DELTA_TIME) : round(initialDelayInSec / DELTA_TIME); + float delayInFrameTime = std::floor(_timeActive / activeDelayInFrameTime) * activeDelayInFrameTime; + if (delayInFrameTime > (std::floor(_prevTimeActive / activeDelayInFrameTime) * activeDelayInFrameTime)) return true; // Keeping version counting real time for future reference. -- Sezz 2022.10.01 @@ -72,7 +69,7 @@ namespace TEN::Input bool InputAction::IsReleased(float maxDelayInSec) const { float maxDelayInFrameTime = (maxDelayInSec == INFINITY) ? INFINITY : round(maxDelayInSec / DELTA_TIME); - return ((Value == 0.0f) && (PrevValue != 0.0f) && (TimeActive <= maxDelayInFrameTime)); + return ((_value == 0.0f) && (_prevValue != 0.0f) && (_timeActive <= maxDelayInFrameTime)); } void InputAction::Update(bool value) @@ -91,57 +88,57 @@ namespace TEN::Input if (IsClicked()) { - PrevTimeActive = 0.0f; - TimeActive = 0.0f; - TimeInactive += FRAME_TIME;// DELTA_TIME; + _prevTimeActive = 0.0f; + _timeActive = 0.0f; + _timeInactive += FRAME_TIME;// DELTA_TIME; } else if (IsReleased()) { - PrevTimeActive = TimeActive; - TimeActive += FRAME_TIME;// DELTA_TIME; - TimeInactive = 0.0f; + _prevTimeActive = _timeActive; + _timeActive += FRAME_TIME;// DELTA_TIME; + _timeInactive = 0.0f; } else if (IsHeld()) { - PrevTimeActive = TimeActive; - TimeActive += FRAME_TIME;// DELTA_TIME; - TimeInactive = 0.0f; + _prevTimeActive = _timeActive; + _timeActive += FRAME_TIME;// DELTA_TIME; + _timeInactive = 0.0f; } else { - PrevTimeActive = 0.0f; - TimeActive = 0.0f; - TimeInactive += FRAME_TIME;// DELTA_TIME; + _prevTimeActive = 0.0f; + _timeActive = 0.0f; + _timeInactive += FRAME_TIME;// DELTA_TIME; } } void InputAction::Clear() { - Value = 0.0f; - PrevValue = 0.0f; - TimeActive = 0.0f; - PrevTimeActive = 0.0f; - TimeInactive = 0.0f; + _value = 0.0f; + _prevValue = 0.0f; + _timeActive = 0.0f; + _prevTimeActive = 0.0f; + _timeInactive = 0.0f; } void InputAction::DrawDebug() const { - g_Renderer.PrintDebugMessage("ID: %d", (int)ID); - g_Renderer.PrintDebugMessage("IsClicked: %d", IsClicked()); - g_Renderer.PrintDebugMessage("IsHeld: %d", IsHeld()); - g_Renderer.PrintDebugMessage("IsPulsed (.2s, .6s): %d", IsPulsed(0.2f, 0.6f)); - g_Renderer.PrintDebugMessage("IsReleased: %d", IsReleased()); - g_Renderer.PrintDebugMessage(""); - g_Renderer.PrintDebugMessage("Value: %.3f", Value); - g_Renderer.PrintDebugMessage("PrevValue: %.3f", PrevValue); - g_Renderer.PrintDebugMessage("TimeActive: %.3f", TimeActive); - g_Renderer.PrintDebugMessage("PrevTimeActive: %.3f", PrevTimeActive); - g_Renderer.PrintDebugMessage("TimeInactive: %.3f", TimeInactive); + PrintDebugMessage("ID: %d", (int)_id); + PrintDebugMessage("IsClicked: %d", IsClicked()); + PrintDebugMessage("IsHeld: %d", IsHeld()); + PrintDebugMessage("IsPulsed (.2s, .6s): %d", IsPulsed(0.2f, 0.6f)); + PrintDebugMessage("IsReleased: %d", IsReleased()); + PrintDebugMessage(""); + PrintDebugMessage("Value: %.3f", _value); + PrintDebugMessage("PrevValue: %.3f", _prevValue); + PrintDebugMessage("TimeActive: %.3f", _timeActive); + PrintDebugMessage("PrevTimeActive: %.3f", _prevTimeActive); + PrintDebugMessage("TimeInactive: %.3f", _timeInactive); } void InputAction::UpdateValue(float value) { - PrevValue = Value; - Value = value; + _prevValue = _value; + _value = value; } } diff --git a/TombEngine/Specific/Input/InputAction.h b/TombEngine/Specific/Input/InputAction.h index 29abd2bf0..3287b85c7 100644 --- a/TombEngine/Specific/Input/InputAction.h +++ b/TombEngine/Specific/Input/InputAction.h @@ -5,6 +5,7 @@ namespace TEN::Input typedef enum class ActionID { // General actions + Forward, Back, Left, @@ -21,6 +22,7 @@ namespace TEN::Input Look, // Vehicle actions + Accelerate, Reverse, Faster, @@ -29,6 +31,7 @@ namespace TEN::Input Fire, // Quick actions + Flare, SmallMedipack, LargeMedipack, @@ -46,6 +49,7 @@ namespace TEN::Input Weapon10, // Menu actions + Select, Deselect, Pause, @@ -56,35 +60,39 @@ namespace TEN::Input Count } In; - // TODO: For analog triggers, use Value range [0.0f, 1.0f] with deadzone up to a quarter press. class InputAction { private: // Members - ActionID ID = In::Forward; - float Value = 0.0f; - float PrevValue = 0.0f; - float TimeActive = 0.0f; - float PrevTimeActive = 0.0f; - float TimeInactive = 0.0f; + + ActionID _id = In::Forward; + float _value = 0.0f; + float _prevValue = 0.0f; + float _timeActive = 0.0f; + float _prevTimeActive = 0.0f; + float _timeInactive = 0.0f; public: // Constructors + InputAction(ActionID actionID); // Getters + ActionID GetID() const; float GetValue() const; float GetTimeActive() const; float GetTimeInactive() const; // Inquirers + bool IsClicked() const; bool IsHeld(float delayInSec = 0.0f) const; bool IsPulsed(float delayInSec, float initialDelayInSec = 0.0f) const; bool IsReleased(float maxDelayInSec = INFINITY) const; // Utilities + void Update(bool value); void Update(float value); void Clear(); @@ -93,6 +101,7 @@ namespace TEN::Input private: // Helpers + void UpdateValue(float value); }; } diff --git a/TombEngine/Specific/RGBAColor8Byte.cpp b/TombEngine/Specific/RGBAColor8Byte.cpp index b627b21e4..7ec856d3e 100644 --- a/TombEngine/Specific/RGBAColor8Byte.cpp +++ b/TombEngine/Specific/RGBAColor8Byte.cpp @@ -1,46 +1,57 @@ #include "framework.h" -#include "RGBAColor8Byte.h" +#include "Specific/RGBAColor8Byte.h" -static byte FloatComponentToByte(float v) +static byte FloatComponentToByte(float value) { - //todo look into what these actually do AND TEST THEM - //todo like, see if these are actually not undefined or some shit - auto lval = std::lroundf((v / 2.0f) * 255.0f); - return static_cast(lval); + // TODO: Look into what these actually do and test them to see if they are actually not undefined. + long byteValue = std::lroundf((value / 2.0f) * 255.0f); + return (byte)byteValue; } static float ByteComponentToFloat(byte b) { - //todo look into what these actually do AND TEST THEM - //todo like, see if these are actually not undefined or some shit - float f = b; - f = (f / 255.0f) * 2.0f; - return f; + // TODO: Look into what these actually do and test them to see if they are actually not undefined. + float value = (b / 255.0f) * 2; + return value; } - -RGBAColor8Byte::RGBAColor8Byte(D3DCOLOR col) +RGBAColor8Byte::RGBAColor8Byte(D3DCOLOR color) { - b = col & 0xFF; - col >>= 8; - g = col & 0xFF; - col >>= 8; - r = col & 0xFF; - col >>= 8; - a = col & 0xFF; + b = color & 0xFF; + color >>= 8; + g = color & 0xFF; + color >>= 8; + r = color & 0xFF; + color >>= 8; + a = color & 0xFF; } -RGBAColor8Byte::operator D3DCOLOR() const +RGBAColor8Byte::RGBAColor8Byte(byte r, byte g, byte b) { - D3DCOLOR col = a; - col <<= 8; - col += r; - col <<= 8; - col += g; - col <<= 8; - col += b; + SetR(r); + SetG(g); + SetB(b); +} - return col; +RGBAColor8Byte::RGBAColor8Byte(byte r, byte g, byte b, byte a) : + RGBAColor8Byte(r, g, b) +{ + SetA(a); +} + +RGBAColor8Byte::RGBAColor8Byte(const Vector3& color) +{ + r = FloatComponentToByte(color.x); + g = FloatComponentToByte(color.y); + b = FloatComponentToByte(color.z); +} + +RGBAColor8Byte::RGBAColor8Byte(const Vector4& color) +{ + r = FloatComponentToByte(color.x); + g = FloatComponentToByte(color.y); + b = FloatComponentToByte(color.z); + a = FloatComponentToByte(color.w); } byte RGBAColor8Byte::GetR() const @@ -83,40 +94,30 @@ void RGBAColor8Byte::SetA(byte v) a = std::clamp(v, 0, 255); } -RGBAColor8Byte::RGBAColor8Byte(Vector3 const& col) : - r(FloatComponentToByte(col.x)), - g(FloatComponentToByte(col.y)), - b(FloatComponentToByte(col.z)) -{ -} - -RGBAColor8Byte::RGBAColor8Byte(Vector4 const& col) : - r(FloatComponentToByte(col.x)), - g(FloatComponentToByte(col.y)), - b(FloatComponentToByte(col.z)), - a(FloatComponentToByte(col.w)) +RGBAColor8Byte::operator Color() const { + return Color(ByteComponentToFloat(r), ByteComponentToFloat(g), ByteComponentToFloat(b)); } RGBAColor8Byte::operator Vector3() const { - return Vector3{ ByteComponentToFloat(r), ByteComponentToFloat(g), ByteComponentToFloat(b) }; + return Vector3(ByteComponentToFloat(r), ByteComponentToFloat(g), ByteComponentToFloat(b)); } RGBAColor8Byte::operator Vector4() const { - return Vector4{ ByteComponentToFloat(r), ByteComponentToFloat(g), ByteComponentToFloat(b), ByteComponentToFloat(a) }; + return Vector4(ByteComponentToFloat(r), ByteComponentToFloat(g), ByteComponentToFloat(b), ByteComponentToFloat(a)); } -RGBAColor8Byte::RGBAColor8Byte(byte r, byte g, byte b) +RGBAColor8Byte::operator D3DCOLOR() const { - SetR(r); - SetG(g); - SetB(b); -} + D3DCOLOR color = a; + color <<= 8; + color += r; + color <<= 8; + color += g; + color <<= 8; + color += b; -RGBAColor8Byte::RGBAColor8Byte(byte r, byte g, byte b, byte a) : RGBAColor8Byte(r, g, b) -{ - SetA(a); + return color; } - diff --git a/TombEngine/Specific/RGBAColor8Byte.h b/TombEngine/Specific/RGBAColor8Byte.h index e89eb7cd3..8566b00b0 100644 --- a/TombEngine/Specific/RGBAColor8Byte.h +++ b/TombEngine/Specific/RGBAColor8Byte.h @@ -4,32 +4,36 @@ typedef DWORD D3DCOLOR; class RGBAColor8Byte { -public: - RGBAColor8Byte(D3DCOLOR); +private: + // Members + byte r = 0; + byte g = 0; + byte b = 0; + byte a = 255; +public: + // Constructors + RGBAColor8Byte(D3DCOLOR color); RGBAColor8Byte(byte r, byte g, byte b); RGBAColor8Byte(byte r, byte g, byte b, byte a); - RGBAColor8Byte(Vector3 const &); - RGBAColor8Byte(Vector4 const &); + RGBAColor8Byte(const Vector3& color); + RGBAColor8Byte(const Vector4& color); + // Getters + byte GetR() const; + byte GetG() const; + byte GetB() const; + byte GetA() const; + + // Setters + void SetR(byte value); + void SetG(byte value); + void SetB(byte value); + void SetA(byte value); + + // Operators + operator Color() const; operator Vector3() const; operator Vector4() const; operator D3DCOLOR() const; - - byte GetR() const; - void SetR(byte v); - byte GetG() const; - void SetG(byte v); - byte GetB() const; - void SetB(byte v); - byte GetA() const; - void SetA(byte v); - -private: - byte r{ 0 }; - byte g{ 0 }; - byte b{ 0 }; - byte a{ 255 }; - }; - diff --git a/TombEngine/Specific/clock.cpp b/TombEngine/Specific/clock.cpp index 6f79c529c..854ecd4ec 100644 --- a/TombEngine/Specific/clock.cpp +++ b/TombEngine/Specific/clock.cpp @@ -1,12 +1,76 @@ #include "framework.h" + +#include #include "Specific/clock.h" +#include "winmain.h" + +constexpr auto CONTROL_FRAME_TIME = 1000.0f / 30.0f; +constexpr auto DEBUG_SKIP_FRAME_TIME = 10 * CONTROL_FRAME_TIME; // Globals -LARGE_INTEGER PerformanceCount = {}; -double LdFreq = 0.0; -double LdSync = 0.0; +double LdFreq = 0.0; +double LdSync = 0.0; -int Sync() +HighFramerateSynchronizer g_Synchronizer; + +void HighFramerateSynchronizer::Init() +{ + _controlDelay = 0; + _frameTime = 0; + + _lastTime.QuadPart = 0; + _currentTime.QuadPart = 0; + _frequency.QuadPart = 0; + + QueryPerformanceFrequency(&_frequency); + QueryPerformanceCounter(&_lastTime); +} + +void HighFramerateSynchronizer::Sync() +{ + if (App.ResetClock) + { + App.ResetClock = false; + QueryPerformanceCounter(&_lastTime); + _currentTime = _lastTime; + _controlDelay = 0; + _frameTime = 0; + } + else + { + QueryPerformanceCounter(&_currentTime); + _frameTime = (_currentTime.QuadPart - _lastTime.QuadPart) * 1000.0 / _frequency.QuadPart; + _lastTime = _currentTime; + _controlDelay += _frameTime; + } +} + +bool HighFramerateSynchronizer::Synced() +{ +#if _DEBUG + if (_controlDelay >= DEBUG_SKIP_FRAME_TIME) + { + TENLog("Game loop is running too slow.", LogLevel::Warning); + App.ResetClock = true; + return false; + } +#endif + + return (_controlDelay >= CONTROL_FRAME_TIME); +} + +void HighFramerateSynchronizer::Step() +{ + _controlDelay -= CONTROL_FRAME_TIME; +} + +float HighFramerateSynchronizer::GetInterpolationFactor() +{ + return std::min((float)_controlDelay / (float)CONTROL_FRAME_TIME, 1.0f); +} + + +int TimeSync() { auto ct = LARGE_INTEGER{}; QueryPerformanceCounter(&ct); @@ -65,4 +129,4 @@ bool TestGlobalTimeInterval(float intervalSecs, float offsetSecs) } return ((GlobalCounter % intervalGameFrames) == offsetGameFrames); -} +} \ No newline at end of file diff --git a/TombEngine/Specific/clock.h b/TombEngine/Specific/clock.h index 883b89085..5d59e3856 100644 --- a/TombEngine/Specific/clock.h +++ b/TombEngine/Specific/clock.h @@ -17,10 +17,29 @@ struct GameTime int Seconds = 0; }; -int Sync(); +class HighFramerateSynchronizer +{ +private: + LARGE_INTEGER _lastTime; + LARGE_INTEGER _currentTime; + LARGE_INTEGER _frequency; + double _controlDelay = 0.0; + double _frameTime = 0.0; + +public: + void Init(); + void Sync(); + void Step(); + bool Synced(); + float GetInterpolationFactor(); +}; + +int TimeSync(); bool TimeInit(); bool TimeReset(); GameTime GetGameTime(int ticks); bool TestGlobalTimeInterval(float intervalSecs, float offsetSecs = 0.0f); + +extern HighFramerateSynchronizer g_Synchronizer; \ No newline at end of file diff --git a/TombEngine/Specific/configuration.cpp b/TombEngine/Specific/configuration.cpp index 6e0b39d26..7cec85e39 100644 --- a/TombEngine/Specific/configuration.cpp +++ b/TombEngine/Specific/configuration.cpp @@ -187,7 +187,8 @@ bool SaveConfiguration() SetDWORDRegKey(graphicsKey, REGKEY_SHADOW_BLOBS_MAX, g_Configuration.ShadowBlobsMax) != ERROR_SUCCESS || SetBoolRegKey(graphicsKey, REGKEY_ENABLE_CAUSTICS, g_Configuration.EnableCaustics) != ERROR_SUCCESS || SetDWORDRegKey(graphicsKey, REGKEY_ANTIALIASING_MODE, (DWORD)g_Configuration.AntialiasingMode) != ERROR_SUCCESS || - SetBoolRegKey(graphicsKey, REGKEY_AMBIENT_OCCLUSION, g_Configuration.EnableAmbientOcclusion) != ERROR_SUCCESS) + SetBoolRegKey(graphicsKey, REGKEY_AMBIENT_OCCLUSION, g_Configuration.EnableAmbientOcclusion) != ERROR_SUCCESS || + SetBoolRegKey(graphicsKey, REGKEY_HIGH_FRAMERATE, g_Configuration.EnableHighFramerate) != ERROR_SUCCESS) { RegCloseKey(rootKey); RegCloseKey(graphicsKey); @@ -230,6 +231,7 @@ bool SaveConfiguration() // Set Gameplay keys. if (SetBoolRegKey(gameplayKey, REGKEY_ENABLE_SUBTITLES, g_Configuration.EnableSubtitles) != ERROR_SUCCESS || + SetBoolRegKey(gameplayKey, REGKEY_ENABLE_AUTO_MONKEY_JUMP, g_Configuration.EnableAutoMonkeySwingJump) != ERROR_SUCCESS || SetBoolRegKey(gameplayKey, REGKEY_ENABLE_AUTO_TARGETING, g_Configuration.EnableAutoTargeting) != ERROR_SUCCESS || SetBoolRegKey(gameplayKey, REGKEY_ENABLE_TARGET_HIGHLIGHTER, g_Configuration.EnableTargetHighlighter) != ERROR_SUCCESS || SetBoolRegKey(gameplayKey, REGKEY_ENABLE_RUMBLE, g_Configuration.EnableRumble) != ERROR_SUCCESS || @@ -256,7 +258,7 @@ bool SaveConfiguration() // Set Input keys. if (SetDWORDRegKey(inputKey, REGKEY_MOUSE_SENSITIVITY, g_Configuration.MouseSensitivity) != ERROR_SUCCESS || - SetDWORDRegKey(inputKey, REGKEY_MOUSE_SMOOTHING, g_Configuration.MouseSmoothing) != ERROR_SUCCESS) + SetDWORDRegKey(inputKey, REGKEY_MENU_OPTION_LOOPING_MODE, (int)g_Configuration.MenuOptionLoopingMode) != ERROR_SUCCESS) { RegCloseKey(rootKey); RegCloseKey(graphicsKey); @@ -314,6 +316,7 @@ void InitDefaultConfiguration() g_Configuration.EnableCaustics = true; g_Configuration.AntialiasingMode = AntialiasingMode::Medium; g_Configuration.EnableAmbientOcclusion = true; + g_Configuration.EnableHighFramerate = true; g_Configuration.SoundDevice = 1; g_Configuration.EnableSound = true; @@ -322,13 +325,14 @@ void InitDefaultConfiguration() g_Configuration.SfxVolume = 100; g_Configuration.EnableSubtitles = true; + g_Configuration.EnableAutoMonkeySwingJump = false; g_Configuration.EnableAutoTargeting = true; g_Configuration.EnableTargetHighlighter = true; g_Configuration.EnableRumble = true; g_Configuration.EnableThumbstickCamera = false; g_Configuration.MouseSensitivity = GameConfiguration::DEFAULT_MOUSE_SENSITIVITY; - g_Configuration.MouseSmoothing = GameConfiguration::DEFAULT_MOUSE_SMOOTHING; + g_Configuration.MenuOptionLoopingMode = MenuOptionLoopingMode::SaveLoadOnly; g_Configuration.SupportedScreenResolutions = GetAllSupportedScreenResolutions(); g_Configuration.AdapterName = g_Renderer.GetDefaultAdapterName(); @@ -362,6 +366,7 @@ bool LoadConfiguration() bool enableCaustics = false; DWORD antialiasingMode = 1; bool enableAmbientOcclusion = false; + bool enableHighFramerate = false; // Load Graphics keys. if (GetDWORDRegKey(graphicsKey, REGKEY_SCREEN_WIDTH, &screenWidth, 0) != ERROR_SUCCESS || @@ -372,7 +377,8 @@ bool LoadConfiguration() GetDWORDRegKey(graphicsKey, REGKEY_SHADOW_BLOBS_MAX, &shadowBlobsMax, GameConfiguration::DEFAULT_SHADOW_BLOBS_MAX) != ERROR_SUCCESS || GetBoolRegKey(graphicsKey, REGKEY_ENABLE_CAUSTICS, &enableCaustics, true) != ERROR_SUCCESS || GetDWORDRegKey(graphicsKey, REGKEY_ANTIALIASING_MODE, &antialiasingMode, true) != ERROR_SUCCESS || - GetBoolRegKey(graphicsKey, REGKEY_AMBIENT_OCCLUSION, &enableAmbientOcclusion, false) != ERROR_SUCCESS) + GetBoolRegKey(graphicsKey, REGKEY_AMBIENT_OCCLUSION, &enableAmbientOcclusion, false) != ERROR_SUCCESS || + GetBoolRegKey(graphicsKey, REGKEY_HIGH_FRAMERATE, &enableHighFramerate, false) != ERROR_SUCCESS) { RegCloseKey(rootKey); RegCloseKey(graphicsKey); @@ -419,6 +425,7 @@ bool LoadConfiguration() return false; } + bool enableAutoMonkeySwingJump = false; bool enableSubtitles = true; bool enableAutoTargeting = true; bool enableTargetHighlighter = true; @@ -426,7 +433,8 @@ bool LoadConfiguration() bool enableThumbstickCamera = true; // Load Gameplay keys. - if (GetBoolRegKey(gameplayKey, REGKEY_ENABLE_SUBTITLES, &enableSubtitles, true) != ERROR_SUCCESS || + if (GetBoolRegKey(gameplayKey, REGKEY_ENABLE_AUTO_MONKEY_JUMP, &enableAutoMonkeySwingJump, true) != ERROR_SUCCESS || + GetBoolRegKey(gameplayKey, REGKEY_ENABLE_SUBTITLES, &enableSubtitles, true) != ERROR_SUCCESS || GetBoolRegKey(gameplayKey, REGKEY_ENABLE_AUTO_TARGETING, &enableAutoTargeting, true) != ERROR_SUCCESS || GetBoolRegKey(gameplayKey, REGKEY_ENABLE_TARGET_HIGHLIGHTER, &enableTargetHighlighter, true) != ERROR_SUCCESS || GetBoolRegKey(gameplayKey, REGKEY_ENABLE_RUMBLE, &enableRumble, true) != ERROR_SUCCESS || @@ -440,14 +448,14 @@ bool LoadConfiguration() } DWORD mouseSensitivity = GameConfiguration::DEFAULT_MOUSE_SENSITIVITY; - DWORD mouseSmoothing = GameConfiguration::DEFAULT_MOUSE_SMOOTHING; + DWORD menuOptionLoopingMode = (DWORD)MenuOptionLoopingMode::SaveLoadOnly; // Load Input keys. HKEY inputKey = NULL; if (RegOpenKeyExA(rootKey, REGKEY_INPUT, 0, KEY_READ, &inputKey) == ERROR_SUCCESS) { if (GetDWORDRegKey(inputKey, REGKEY_MOUSE_SENSITIVITY, &mouseSensitivity, GameConfiguration::DEFAULT_MOUSE_SENSITIVITY) != ERROR_SUCCESS || - GetDWORDRegKey(inputKey, REGKEY_MOUSE_SMOOTHING, &mouseSmoothing, GameConfiguration::DEFAULT_MOUSE_SMOOTHING) != ERROR_SUCCESS) + GetDWORDRegKey(inputKey, REGKEY_MENU_OPTION_LOOPING_MODE, &menuOptionLoopingMode, (DWORD)MenuOptionLoopingMode::SaveLoadOnly) != ERROR_SUCCESS) { RegCloseKey(rootKey); RegCloseKey(graphicsKey); @@ -500,6 +508,7 @@ bool LoadConfiguration() g_Configuration.AntialiasingMode = AntialiasingMode(antialiasingMode); g_Configuration.ShadowMapSize = shadowMapSize; g_Configuration.EnableAmbientOcclusion = enableAmbientOcclusion; + g_Configuration.EnableHighFramerate = enableHighFramerate; g_Configuration.EnableSound = enableSound; g_Configuration.EnableReverb = enableReverb; @@ -507,14 +516,15 @@ bool LoadConfiguration() g_Configuration.SfxVolume = sfxVolume; g_Configuration.SoundDevice = soundDevice; + g_Configuration.EnableSubtitles = enableSubtitles; + g_Configuration.EnableAutoMonkeySwingJump = enableAutoMonkeySwingJump; g_Configuration.EnableAutoTargeting = enableAutoTargeting; g_Configuration.EnableTargetHighlighter = enableTargetHighlighter; g_Configuration.EnableRumble = enableRumble; g_Configuration.EnableThumbstickCamera = enableThumbstickCamera; - g_Configuration.EnableSubtitles = enableSubtitles; g_Configuration.MouseSensitivity = mouseSensitivity; - g_Configuration.MouseSmoothing = mouseSmoothing; + g_Configuration.MenuOptionLoopingMode = (MenuOptionLoopingMode)menuOptionLoopingMode; // Set legacy variables. SetVolumeTracks(musicVolume); @@ -553,10 +563,10 @@ LONG GetDWORDRegKey(HKEY hKey, LPCSTR strValueName, DWORD* nValue, DWORD nDefaul NULL, reinterpret_cast(&nResult), &dwBufferSize); - + if (ERROR_SUCCESS == nError) *nValue = nResult; - + return nError; } @@ -565,10 +575,10 @@ LONG GetBoolRegKey(HKEY hKey, LPCSTR strValueName, bool* bValue, bool bDefaultVa DWORD nDefValue((bDefaultValue) ? 1 : 0); DWORD nResult(nDefValue); LONG nError = GetDWORDRegKey(hKey, strValueName, &nResult, nDefValue); - + if (ERROR_SUCCESS == nError) *bValue = (nResult != 0); - + return nError; } diff --git a/TombEngine/Specific/configuration.h b/TombEngine/Specific/configuration.h index cf10f970c..51f70ca4e 100644 --- a/TombEngine/Specific/configuration.h +++ b/TombEngine/Specific/configuration.h @@ -7,6 +7,7 @@ using namespace TEN::Input; using namespace TEN::Math; // Directories + constexpr auto REGKEY_ROOT = "Software\\TombEngine\\1.1.0"; constexpr auto REGKEY_GRAPHICS = "Graphics"; constexpr auto REGKEY_SOUND = "Sound"; @@ -14,6 +15,7 @@ constexpr auto REGKEY_GAMEPLAY = "Gameplay"; constexpr auto REGKEY_INPUT = "Input"; // Graphics keys + constexpr auto REGKEY_SCREEN_WIDTH = "ScreenWidth"; constexpr auto REGKEY_SCREEN_HEIGHT = "ScreenHeight"; constexpr auto REGKEY_ENABLE_WINDOWED_MODE = "EnableWindowedMode"; @@ -23,8 +25,10 @@ constexpr auto REGKEY_SHADOW_BLOBS_MAX = "ShadowBlobsMax"; constexpr auto REGKEY_ENABLE_CAUSTICS = "EnableCaustics"; constexpr auto REGKEY_ANTIALIASING_MODE = "AntialiasingMode"; constexpr auto REGKEY_AMBIENT_OCCLUSION = "AmbientOcclusion"; +constexpr auto REGKEY_HIGH_FRAMERATE = "EnableHighFramerate"; // Sound keys + constexpr auto REGKEY_SOUND_DEVICE = "SoundDevice"; constexpr auto REGKEY_ENABLE_SOUND = "EnableSound"; constexpr auto REGKEY_ENABLE_REVERB = "EnableReverb"; @@ -32,24 +36,34 @@ constexpr auto REGKEY_MUSIC_VOLUME = "MusicVolume"; constexpr auto REGKEY_SFX_VOLUME = "SfxVolume"; // Gameplay keys + constexpr auto REGKEY_ENABLE_SUBTITLES = "EnableSubtitles"; +constexpr auto REGKEY_ENABLE_AUTO_MONKEY_JUMP = "EnableAutoMonkeySwingJump"; constexpr auto REGKEY_ENABLE_AUTO_TARGETING = "EnableAutoTargeting"; constexpr auto REGKEY_ENABLE_TARGET_HIGHLIGHTER = "EnableTargetHighlighter"; constexpr auto REGKEY_ENABLE_RUMBLE = "EnableRumble"; constexpr auto REGKEY_ENABLE_THUMBSTICK_CAMERA = "EnableThumbstickCamera"; // Input keys -constexpr auto REGKEY_MOUSE_SENSITIVITY = "MouseSensitivity"; -constexpr auto REGKEY_MOUSE_SMOOTHING = "MouseSmoothing"; -struct GameConfiguration +constexpr auto REGKEY_MOUSE_SENSITIVITY = "MouseSensitivity"; +constexpr auto REGKEY_MENU_OPTION_LOOPING_MODE = "MenuOptionLoopingMode"; + +enum class MenuOptionLoopingMode +{ + AllMenus, + SaveLoadOnly, + Disabled +}; + +struct GameConfiguration { static constexpr auto DEFAULT_SHADOW_MAP_SIZE = 1024; static constexpr auto DEFAULT_SHADOW_BLOBS_MAX = 16; static constexpr auto DEFAULT_MOUSE_SENSITIVITY = 6; - static constexpr auto DEFAULT_MOUSE_SMOOTHING = 1; // Graphics + int ScreenWidth = 0; int ScreenHeight = 0; bool EnableWindowedMode = false; @@ -58,9 +72,11 @@ struct GameConfiguration int ShadowBlobsMax = DEFAULT_SHADOW_BLOBS_MAX; bool EnableCaustics = false; bool EnableAmbientOcclusion = false; + bool EnableHighFramerate = true; AntialiasingMode AntialiasingMode = AntialiasingMode::None; // Sound + int SoundDevice = 0; bool EnableSound = false; bool EnableReverb = false; @@ -68,16 +84,19 @@ struct GameConfiguration int SfxVolume = 0; // Gameplay - bool EnableSubtitles = false; - bool EnableAutoTargeting = false; - bool EnableTargetHighlighter = false; - bool EnableRumble = false; - bool EnableThumbstickCamera = false; + + bool EnableSubtitles = false; + bool EnableAutoMonkeySwingJump = false; + bool EnableAutoTargeting = false; + bool EnableTargetHighlighter = false; + bool EnableRumble = false; + bool EnableThumbstickCamera = false; // Input - int MouseSensitivity = DEFAULT_MOUSE_SENSITIVITY; - int MouseSmoothing = DEFAULT_MOUSE_SMOOTHING; - std::vector Bindings = {}; + + int MouseSensitivity = DEFAULT_MOUSE_SENSITIVITY; + MenuOptionLoopingMode MenuOptionLoopingMode = MenuOptionLoopingMode::SaveLoadOnly; + std::vector Bindings = {}; std::vector SupportedScreenResolutions = {}; std::string AdapterName = {}; diff --git a/TombEngine/Specific/level.cpp b/TombEngine/Specific/level.cpp index 6e5941fe8..2d7388c6e 100644 --- a/TombEngine/Specific/level.cpp +++ b/TombEngine/Specific/level.cpp @@ -38,8 +38,6 @@ using namespace TEN::Utils; const std::vector BRIDGE_OBJECT_IDS = { ID_EXPANDING_PLATFORM, - ID_SQUISHY_BLOCK_HORIZONTAL, - ID_SQUISHY_BLOCK_VERTICAL, ID_FALLING_BLOCK, ID_FALLING_BLOCK2, @@ -689,17 +687,17 @@ void ReadRooms() { auto& room = g_Level.Rooms.emplace_back(); - room.name = ReadString(); + room.Name = ReadString(); int tagCount = ReadInt32(); for (int j = 0; j < tagCount; j++) - room.tags.push_back(ReadString()); + room.Tags.push_back(ReadString()); - room.x = ReadInt32(); - room.y = 0; - room.z = ReadInt32(); - room.minfloor = ReadInt32(); - room.maxceiling = ReadInt32(); + room.Position.x = ReadInt32(); + room.Position.y = 0; + room.Position.z = ReadInt32(); + room.BottomHeight = ReadInt32(); + room.TopHeight = ReadInt32(); int vertexCount = ReadInt32(); @@ -771,63 +769,68 @@ void ReadRooms() for (int j = 0; j < portalCount; j++) LoadPortal(room); - room.zSize = ReadInt32(); - room.xSize = ReadInt32(); + room.ZSize = ReadInt32(); + room.XSize = ReadInt32(); + auto roomPos = Vector2i(room.Position.x, room.Position.z); - room.floor.reserve(room.zSize * room.xSize); - for (int j = 0; j < (room.zSize * room.xSize); j++) + room.Sectors.reserve(room.XSize * room.ZSize); + for (int x = 0; x < room.XSize; x++) { - auto sector = FloorInfo{}; + for (int z = 0; z < room.ZSize; z++) + { + auto sector = FloorInfo{}; - sector.TriggerIndex = ReadInt32(); - sector.Box = ReadInt32(); + sector.Position = roomPos + Vector2i(BLOCK(x), BLOCK(z)); + sector.RoomNumber = i; - sector.FloorSurface.Triangles[0].Material = - sector.FloorSurface.Triangles[1].Material = - sector.CeilingSurface.Triangles[0].Material = - sector.CeilingSurface.Triangles[1].Material = (MaterialType)ReadInt32(); + sector.TriggerIndex = ReadInt32(); + sector.PathfindingBoxID = ReadInt32(); - sector.Stopper = (bool)ReadInt32(); + sector.FloorSurface.Triangles[0].Material = + sector.FloorSurface.Triangles[1].Material = + sector.CeilingSurface.Triangles[0].Material = + sector.CeilingSurface.Triangles[1].Material = (MaterialType)ReadInt32(); - sector.FloorSurface.SplitAngle = FROM_RAD(ReadFloat()); - sector.FloorSurface.Triangles[0].IllegalSlopeAngle = ILLEGAL_FLOOR_SLOPE_ANGLE; - sector.FloorSurface.Triangles[1].IllegalSlopeAngle = ILLEGAL_FLOOR_SLOPE_ANGLE; - sector.FloorSurface.Triangles[0].PortalRoomNumber = ReadInt32(); - sector.FloorSurface.Triangles[1].PortalRoomNumber = ReadInt32(); - sector.FloorSurface.Triangles[0].Plane = ConvertFakePlaneToPlane(ReadVector3(), true); - sector.FloorSurface.Triangles[1].Plane = ConvertFakePlaneToPlane(ReadVector3(), true); + sector.Stopper = (bool)ReadInt32(); - sector.CeilingSurface.SplitAngle = FROM_RAD(ReadFloat()); - sector.CeilingSurface.Triangles[0].IllegalSlopeAngle = ILLEGAL_CEILING_SLOPE_ANGLE; - sector.CeilingSurface.Triangles[1].IllegalSlopeAngle = ILLEGAL_CEILING_SLOPE_ANGLE; - sector.CeilingSurface.Triangles[0].PortalRoomNumber = ReadInt32(); - sector.CeilingSurface.Triangles[1].PortalRoomNumber = ReadInt32(); - sector.CeilingSurface.Triangles[0].Plane = ConvertFakePlaneToPlane(ReadVector3(), false); - sector.CeilingSurface.Triangles[1].Plane = ConvertFakePlaneToPlane(ReadVector3(), false); + sector.FloorSurface.SplitAngle = FROM_RAD(ReadFloat()); + sector.FloorSurface.Triangles[0].SteepSlopeAngle = ILLEGAL_FLOOR_SLOPE_ANGLE; + sector.FloorSurface.Triangles[1].SteepSlopeAngle = ILLEGAL_FLOOR_SLOPE_ANGLE; + sector.FloorSurface.Triangles[0].PortalRoomNumber = ReadInt32(); + sector.FloorSurface.Triangles[1].PortalRoomNumber = ReadInt32(); + sector.FloorSurface.Triangles[0].Plane = ConvertFakePlaneToPlane(ReadVector3(), true); + sector.FloorSurface.Triangles[1].Plane = ConvertFakePlaneToPlane(ReadVector3(), true); - sector.SidePortalRoomNumber = ReadInt32(); - sector.Flags.Death = ReadBool(); - sector.Flags.Monkeyswing = ReadBool(); - sector.Flags.ClimbNorth = ReadBool(); - sector.Flags.ClimbSouth = ReadBool(); - sector.Flags.ClimbEast = ReadBool(); - sector.Flags.ClimbWest = ReadBool(); - sector.Flags.MarkTriggerer = ReadBool(); - sector.Flags.MarkTriggererActive = 0; // TODO: Needs to be written to and read from savegames. - sector.Flags.MarkBeetle = ReadBool(); + sector.CeilingSurface.SplitAngle = FROM_RAD(ReadFloat()); + sector.CeilingSurface.Triangles[0].SteepSlopeAngle = ILLEGAL_CEILING_SLOPE_ANGLE; + sector.CeilingSurface.Triangles[1].SteepSlopeAngle = ILLEGAL_CEILING_SLOPE_ANGLE; + sector.CeilingSurface.Triangles[0].PortalRoomNumber = ReadInt32(); + sector.CeilingSurface.Triangles[1].PortalRoomNumber = ReadInt32(); + sector.CeilingSurface.Triangles[0].Plane = ConvertFakePlaneToPlane(ReadVector3(), false); + sector.CeilingSurface.Triangles[1].Plane = ConvertFakePlaneToPlane(ReadVector3(), false); - sector.RoomNumber = i; + sector.SidePortalRoomNumber = ReadInt32(); + sector.Flags.Death = ReadBool(); + sector.Flags.Monkeyswing = ReadBool(); + sector.Flags.ClimbNorth = ReadBool(); + sector.Flags.ClimbSouth = ReadBool(); + sector.Flags.ClimbEast = ReadBool(); + sector.Flags.ClimbWest = ReadBool(); + sector.Flags.MarkTriggerer = ReadBool(); + sector.Flags.MarkTriggererActive = 0; // TODO: Needs to be written to and read from savegames. + sector.Flags.MarkBeetle = ReadBool(); - room.floor.push_back(sector); + room.Sectors.push_back(sector); + } } room.ambient = ReadVector3(); - int numLights = ReadInt32(); - room.lights.reserve(numLights); - for (int j = 0; j < numLights; j++) + int lightCount = ReadInt32(); + room.lights.reserve(lightCount); + for (int j = 0; j < lightCount; j++) { - ROOM_LIGHT light; + auto light = ROOM_LIGHT{}; light.x = ReadInt32(); light.y = ReadInt32(); @@ -849,9 +852,9 @@ void ReadRooms() room.lights.push_back(light); } - int numStatics = ReadInt32(); - room.mesh.reserve(numStatics); - for (int j = 0; j < numStatics; j++) + int staticCount = ReadInt32(); + room.mesh.reserve(staticCount); + for (int j = 0; j < staticCount; j++) { auto& mesh = room.mesh.emplace_back(); @@ -872,21 +875,17 @@ void ReadRooms() g_GameScriptEntities->AddName(mesh.Name, mesh); } - int numTriggerVolumes = ReadInt32(); - - // Reserve in advance so the vector doesn't resize itself and leave anything - // in the script name-to-reference map obsolete. - room.triggerVolumes.reserve(numTriggerVolumes); - for (int j = 0; j < numTriggerVolumes; j++) + int triggerVolumeCount = ReadInt32(); + room.TriggerVolumes.reserve(triggerVolumeCount); + for (int j = 0; j < triggerVolumeCount; j++) { - auto& volume = room.triggerVolumes.emplace_back(); + auto& volume = room.TriggerVolumes.emplace_back(); volume.Type = (VolumeType)ReadInt32(); - // NOTE: Braces are necessary to ensure correct value init order. - auto pos = Vector3{ ReadFloat(), ReadFloat(), ReadFloat() }; - auto orient = Quaternion{ ReadFloat(), ReadFloat(), ReadFloat(), ReadFloat() }; - auto scale = Vector3{ ReadFloat(), ReadFloat(), ReadFloat() }; + auto pos = ReadVector3(); + auto orient = ReadVector4(); + auto scale = ReadVector3(); volume.Enabled = ReadBool(); volume.DetectInAdjacentRooms = ReadBool(); @@ -894,7 +893,7 @@ void ReadRooms() volume.Name = ReadString(); volume.EventSetIndex = ReadInt32(); - volume.Box = BoundingOrientedBox(pos, scale, orient); + volume.Box = BoundingOrientedBox(pos, scale, orient); volume.Sphere = BoundingSphere(pos, scale.x); volume.StateQueue.reserve(VOLUME_STATE_QUEUE_SIZE); @@ -910,9 +909,9 @@ void ReadRooms() room.itemNumber = NO_VALUE; room.fxNumber = NO_VALUE; - room.index = i; + room.RoomNumber = i; - g_GameScriptEntities->AddName(room.name, room); + g_GameScriptEntities->AddName(room.Name, room); } } @@ -950,7 +949,7 @@ void FreeLevel() g_Level.Meshes.resize(0); MoveablesIds.resize(0); SpriteSequencesIds.resize(0); - g_Level.Boxes.resize(0); + g_Level.PathfindingBoxes.resize(0); g_Level.Overlaps.resize(0); g_Level.Anims.resize(0); g_Level.Changes.resize(0); @@ -1365,8 +1364,8 @@ void LoadBoxes() // Read boxes int numBoxes = ReadInt32(); TENLog("Num boxes: " + std::to_string(numBoxes), LogLevel::Info); - g_Level.Boxes.resize(numBoxes); - ReadBytes(g_Level.Boxes.data(), numBoxes * sizeof(BOX_INFO)); + g_Level.PathfindingBoxes.resize(numBoxes); + ReadBytes(g_Level.PathfindingBoxes.data(), numBoxes * sizeof(BOX_INFO)); // Read overlaps int numOverlaps = ReadInt32(); @@ -1400,8 +1399,8 @@ void LoadBoxes() // By default all blockable boxes are blocked for (int i = 0; i < numBoxes; i++) { - if (g_Level.Boxes[i].flags & BLOCKABLE) - g_Level.Boxes[i].flags |= BLOCKED; + if (g_Level.PathfindingBoxes[i].flags & BLOCKABLE) + g_Level.PathfindingBoxes[i].flags |= BLOCKED; } } @@ -1472,7 +1471,8 @@ void GetCarriedItems() const auto& object = Objects[item.ObjectNumber]; if (object.intelligent || - (item.ObjectNumber >= ID_SEARCH_OBJECT1 && item.ObjectNumber <= ID_SEARCH_OBJECT3)) + (item.ObjectNumber >= ID_SEARCH_OBJECT1 && item.ObjectNumber <= ID_SEARCH_OBJECT3) || + (item.ObjectNumber == ID_SARCOPHAGUS)) { for (short linkNumber = g_Level.Rooms[item.RoomNumber].itemNumber; linkNumber != NO_VALUE; linkNumber = g_Level.Items[linkNumber].NextItem) { @@ -1540,17 +1540,17 @@ void BuildOutsideRoomsTable() { auto* room = &g_Level.Rooms[i]; - int rx = (room->x / BLOCK(1)); - int rz = (room->z / BLOCK(1)); + int rx = (room->Position.x / BLOCK(1)); + int rz = (room->Position.z / BLOCK(1)); for (int x = 0; x < OUTSIDE_SIZE; x++) { - if (x < (rx + 1) || x > (rx + room->xSize - 2)) + if (x < (rx + 1) || x > (rx + room->XSize - 2)) continue; for (int z = 0; z < OUTSIDE_SIZE; z++) { - if (z < (rz + 1) || z > (rz + room->zSize - 2)) + if (z < (rz + 1) || z > (rz + room->ZSize - 2)) continue; OutsideRoomTable[x][z].push_back(i); diff --git a/TombEngine/Specific/level.h b/TombEngine/Specific/level.h index 01c65380a..1c0c84853 100644 --- a/TombEngine/Specific/level.h +++ b/TombEngine/Specific/level.h @@ -107,8 +107,8 @@ struct LEVEL std::vector Sinks = {}; // Pathfinding data - std::vector Boxes = {}; - std::vector Overlaps = {}; + std::vector PathfindingBoxes = {}; + std::vector Overlaps = {}; std::vector Zones[(int)ZoneType::MaxZone][2] = {}; // Sound data diff --git a/TombEngine/Specific/savegame/flatbuffers/ten_itemdata_generated.h b/TombEngine/Specific/savegame/flatbuffers/ten_itemdata_generated.h index 9e37eea3b..3f179e2ef 100644 --- a/TombEngine/Specific/savegame/flatbuffers/ten_itemdata_generated.h +++ b/TombEngine/Specific/savegame/flatbuffers/ten_itemdata_generated.h @@ -59,10 +59,6 @@ struct Motorboat; struct MotorboatBuilder; struct MotorboatT; -struct GameVector; -struct GameVectorBuilder; -struct GameVectorT; - struct Wraith; struct WraithBuilder; struct WraithT; @@ -111,6 +107,8 @@ struct Vector3; struct Vector4; +struct GameVector; + enum class ItemData : uint8_t { NONE = 0, Int = 1, @@ -130,16 +128,15 @@ enum class ItemData : uint8_t { Skidoo = 15, UPV = 16, Motorboat = 17, - GameVector = 18, - Wraith = 19, - Rubberboat = 20, - Pushable = 21, - Minecart = 22, + Wraith = 18, + Rubberboat = 19, + Pushable = 20, + Minecart = 21, MIN = NONE, MAX = Minecart }; -inline const ItemData (&EnumValuesItemData())[23] { +inline const ItemData (&EnumValuesItemData())[22] { static const ItemData values[] = { ItemData::NONE, ItemData::Int, @@ -159,7 +156,6 @@ inline const ItemData (&EnumValuesItemData())[23] { ItemData::Skidoo, ItemData::UPV, ItemData::Motorboat, - ItemData::GameVector, ItemData::Wraith, ItemData::Rubberboat, ItemData::Pushable, @@ -169,7 +165,7 @@ inline const ItemData (&EnumValuesItemData())[23] { } inline const char * const *EnumNamesItemData() { - static const char * const names[24] = { + static const char * const names[23] = { "NONE", "Int", "Short", @@ -188,7 +184,6 @@ inline const char * const *EnumNamesItemData() { "Skidoo", "UPV", "Motorboat", - "GameVector", "Wraith", "Rubberboat", "Pushable", @@ -276,10 +271,6 @@ template<> struct ItemDataTraits { static const ItemData enum_value = ItemData::Motorboat; }; -template<> struct ItemDataTraits { - static const ItemData enum_value = ItemData::GameVector; -}; - template<> struct ItemDataTraits { static const ItemData enum_value = ItemData::Wraith; }; @@ -464,14 +455,6 @@ struct ItemDataUnion { return type == ItemData::Motorboat ? reinterpret_cast(value) : nullptr; } - TEN::Save::GameVectorT *AsGameVector() { - return type == ItemData::GameVector ? - reinterpret_cast(value) : nullptr; - } - const TEN::Save::GameVectorT *AsGameVector() const { - return type == ItemData::GameVector ? - reinterpret_cast(value) : nullptr; - } TEN::Save::WraithT *AsWraith() { return type == ItemData::Wraith ? reinterpret_cast(value) : nullptr; @@ -697,6 +680,46 @@ struct Vector4::Traits { using type = Vector4; }; +FLATBUFFERS_MANUALLY_ALIGNED_STRUCT(4) GameVector FLATBUFFERS_FINAL_CLASS { + private: + int32_t x_; + int32_t y_; + int32_t z_; + int32_t room_number_; + + public: + struct Traits; + GameVector() + : x_(0), + y_(0), + z_(0), + room_number_(0) { + } + GameVector(int32_t _x, int32_t _y, int32_t _z, int32_t _room_number) + : x_(flatbuffers::EndianScalar(_x)), + y_(flatbuffers::EndianScalar(_y)), + z_(flatbuffers::EndianScalar(_z)), + room_number_(flatbuffers::EndianScalar(_room_number)) { + } + int32_t x() const { + return flatbuffers::EndianScalar(x_); + } + int32_t y() const { + return flatbuffers::EndianScalar(y_); + } + int32_t z() const { + return flatbuffers::EndianScalar(z_); + } + int32_t room_number() const { + return flatbuffers::EndianScalar(room_number_); + } +}; +FLATBUFFERS_STRUCT_END(GameVector, 16); + +struct GameVector::Traits { + using type = GameVector; +}; + struct CreatureTargetT : public flatbuffers::NativeTable { typedef CreatureTarget TableType; int32_t object_number = 0; @@ -2031,51 +2054,6 @@ struct Motorboat::Traits { flatbuffers::Offset CreateMotorboat(flatbuffers::FlatBufferBuilder &_fbb, const MotorboatT *_o, const flatbuffers::rehasher_function_t *_rehasher = nullptr); -struct GameVectorT : public flatbuffers::NativeTable { - typedef GameVector TableType; -}; - -struct GameVector FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table { - typedef GameVectorT NativeTableType; - typedef GameVectorBuilder Builder; - struct Traits; - bool Verify(flatbuffers::Verifier &verifier) const { - return VerifyTableStart(verifier) && - verifier.EndTable(); - } - GameVectorT *UnPack(const flatbuffers::resolver_function_t *_resolver = nullptr) const; - void UnPackTo(GameVectorT *_o, const flatbuffers::resolver_function_t *_resolver = nullptr) const; - static flatbuffers::Offset Pack(flatbuffers::FlatBufferBuilder &_fbb, const GameVectorT* _o, const flatbuffers::rehasher_function_t *_rehasher = nullptr); -}; - -struct GameVectorBuilder { - typedef GameVector Table; - flatbuffers::FlatBufferBuilder &fbb_; - flatbuffers::uoffset_t start_; - explicit GameVectorBuilder(flatbuffers::FlatBufferBuilder &_fbb) - : fbb_(_fbb) { - start_ = fbb_.StartTable(); - } - flatbuffers::Offset Finish() { - const auto end = fbb_.EndTable(start_); - auto o = flatbuffers::Offset(end); - return o; - } -}; - -inline flatbuffers::Offset CreateGameVector( - flatbuffers::FlatBufferBuilder &_fbb) { - GameVectorBuilder builder_(_fbb); - return builder_.Finish(); -} - -struct GameVector::Traits { - using type = GameVector; - static auto constexpr Create = CreateGameVector; -}; - -flatbuffers::Offset CreateGameVector(flatbuffers::FlatBufferBuilder &_fbb, const GameVectorT *_o, const flatbuffers::rehasher_function_t *_rehasher = nullptr); - struct WraithT : public flatbuffers::NativeTable { typedef Wraith TableType; }; @@ -3442,29 +3420,6 @@ inline flatbuffers::Offset CreateMotorboat(flatbuffers::FlatBufferBui _fbb); } -inline GameVectorT *GameVector::UnPack(const flatbuffers::resolver_function_t *_resolver) const { - auto _o = std::make_unique(); - UnPackTo(_o.get(), _resolver); - return _o.release(); -} - -inline void GameVector::UnPackTo(GameVectorT *_o, const flatbuffers::resolver_function_t *_resolver) const { - (void)_o; - (void)_resolver; -} - -inline flatbuffers::Offset GameVector::Pack(flatbuffers::FlatBufferBuilder &_fbb, const GameVectorT* _o, const flatbuffers::rehasher_function_t *_rehasher) { - return CreateGameVector(_fbb, _o, _rehasher); -} - -inline flatbuffers::Offset CreateGameVector(flatbuffers::FlatBufferBuilder &_fbb, const GameVectorT *_o, const flatbuffers::rehasher_function_t *_rehasher) { - (void)_rehasher; - (void)_o; - struct _VectorArgs { flatbuffers::FlatBufferBuilder *__fbb; const GameVectorT* __o; const flatbuffers::rehasher_function_t *__rehasher; } _va = { &_fbb, _o, _rehasher}; (void)_va; - return TEN::Save::CreateGameVector( - _fbb); -} - inline WraithT *Wraith::UnPack(const flatbuffers::resolver_function_t *_resolver) const { auto _o = std::make_unique(); UnPackTo(_o.get(), _resolver); @@ -3882,10 +3837,6 @@ inline bool VerifyItemData(flatbuffers::Verifier &verifier, const void *obj, Ite auto ptr = reinterpret_cast(obj); return verifier.VerifyTable(ptr); } - case ItemData::GameVector: { - auto ptr = reinterpret_cast(obj); - return verifier.VerifyTable(ptr); - } case ItemData::Wraith: { auto ptr = reinterpret_cast(obj); return verifier.VerifyTable(ptr); @@ -3988,10 +3939,6 @@ inline void *ItemDataUnion::UnPack(const void *obj, ItemData type, const flatbuf auto ptr = reinterpret_cast(obj); return ptr->UnPack(resolver); } - case ItemData::GameVector: { - auto ptr = reinterpret_cast(obj); - return ptr->UnPack(resolver); - } case ItemData::Wraith: { auto ptr = reinterpret_cast(obj); return ptr->UnPack(resolver); @@ -4082,10 +4029,6 @@ inline flatbuffers::Offset ItemDataUnion::Pack(flatbuffers::FlatBufferBuil auto ptr = reinterpret_cast(value); return CreateMotorboat(_fbb, ptr, _rehasher).Union(); } - case ItemData::GameVector: { - auto ptr = reinterpret_cast(value); - return CreateGameVector(_fbb, ptr, _rehasher).Union(); - } case ItemData::Wraith: { auto ptr = reinterpret_cast(value); return CreateWraith(_fbb, ptr, _rehasher).Union(); @@ -4176,10 +4119,6 @@ inline ItemDataUnion::ItemDataUnion(const ItemDataUnion &u) : type(u.type), valu value = new TEN::Save::MotorboatT(*reinterpret_cast(u.value)); break; } - case ItemData::GameVector: { - value = new TEN::Save::GameVectorT(*reinterpret_cast(u.value)); - break; - } case ItemData::Wraith: { value = new TEN::Save::WraithT(*reinterpret_cast(u.value)); break; @@ -4288,11 +4227,6 @@ inline void ItemDataUnion::Reset() { delete ptr; break; } - case ItemData::GameVector: { - auto ptr = reinterpret_cast(value); - delete ptr; - break; - } case ItemData::Wraith: { auto ptr = reinterpret_cast(value); delete ptr; diff --git a/TombEngine/Specific/savegame/flatbuffers/ten_savegame_generated.h b/TombEngine/Specific/savegame/flatbuffers/ten_savegame_generated.h index de658eaa8..3bbe2dae6 100644 --- a/TombEngine/Specific/savegame/flatbuffers/ten_savegame_generated.h +++ b/TombEngine/Specific/savegame/flatbuffers/ten_savegame_generated.h @@ -97,10 +97,18 @@ struct PlayerContextData; struct PlayerContextDataBuilder; struct PlayerContextDataT; +struct CollisionInfoData; +struct CollisionInfoDataBuilder; +struct CollisionInfoDataT; + struct Lara; struct LaraBuilder; struct LaraT; +struct Camera; +struct CameraBuilder; +struct CameraT; + struct FixedCamera; struct FixedCameraBuilder; struct FixedCameraT; @@ -129,6 +137,10 @@ struct SwarmObjectInfo; struct SwarmObjectInfoBuilder; struct SwarmObjectInfoT; +struct RopeSegment; +struct RopeSegmentBuilder; +struct RopeSegmentT; + struct Rope; struct RopeBuilder; struct RopeT; @@ -863,9 +875,6 @@ struct Item FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table { const TEN::Save::Motorboat *data_as_Motorboat() const { return data_type() == TEN::Save::ItemData::Motorboat ? static_cast(data()) : nullptr; } - const TEN::Save::GameVector *data_as_GameVector() const { - return data_type() == TEN::Save::ItemData::GameVector ? static_cast(data()) : nullptr; - } const TEN::Save::Wraith *data_as_Wraith() const { return data_type() == TEN::Save::ItemData::Wraith ? static_cast(data()) : nullptr; } @@ -1044,10 +1053,6 @@ template<> inline const TEN::Save::Motorboat *Item::data_as inline const TEN::Save::GameVector *Item::data_as() const { - return data_as_GameVector(); -} - template<> inline const TEN::Save::Wraith *Item::data_as() const { return data_as_Wraith(); } @@ -4049,11 +4054,81 @@ struct PlayerContextData::Traits { flatbuffers::Offset CreatePlayerContextData(flatbuffers::FlatBufferBuilder &_fbb, const PlayerContextDataT *_o, const flatbuffers::rehasher_function_t *_rehasher = nullptr); +struct CollisionInfoDataT : public flatbuffers::NativeTable { + typedef CollisionInfoData TableType; + int32_t last_bridge_item_number = 0; + std::unique_ptr last_bridge_item_pose{}; +}; + +struct CollisionInfoData FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table { + typedef CollisionInfoDataT NativeTableType; + typedef CollisionInfoDataBuilder Builder; + struct Traits; + enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE { + VT_LAST_BRIDGE_ITEM_NUMBER = 4, + VT_LAST_BRIDGE_ITEM_POSE = 6 + }; + int32_t last_bridge_item_number() const { + return GetField(VT_LAST_BRIDGE_ITEM_NUMBER, 0); + } + const TEN::Save::Pose *last_bridge_item_pose() const { + return GetStruct(VT_LAST_BRIDGE_ITEM_POSE); + } + bool Verify(flatbuffers::Verifier &verifier) const { + return VerifyTableStart(verifier) && + VerifyField(verifier, VT_LAST_BRIDGE_ITEM_NUMBER) && + VerifyField(verifier, VT_LAST_BRIDGE_ITEM_POSE) && + verifier.EndTable(); + } + CollisionInfoDataT *UnPack(const flatbuffers::resolver_function_t *_resolver = nullptr) const; + void UnPackTo(CollisionInfoDataT *_o, const flatbuffers::resolver_function_t *_resolver = nullptr) const; + static flatbuffers::Offset Pack(flatbuffers::FlatBufferBuilder &_fbb, const CollisionInfoDataT* _o, const flatbuffers::rehasher_function_t *_rehasher = nullptr); +}; + +struct CollisionInfoDataBuilder { + typedef CollisionInfoData Table; + flatbuffers::FlatBufferBuilder &fbb_; + flatbuffers::uoffset_t start_; + void add_last_bridge_item_number(int32_t last_bridge_item_number) { + fbb_.AddElement(CollisionInfoData::VT_LAST_BRIDGE_ITEM_NUMBER, last_bridge_item_number, 0); + } + void add_last_bridge_item_pose(const TEN::Save::Pose *last_bridge_item_pose) { + fbb_.AddStruct(CollisionInfoData::VT_LAST_BRIDGE_ITEM_POSE, last_bridge_item_pose); + } + explicit CollisionInfoDataBuilder(flatbuffers::FlatBufferBuilder &_fbb) + : fbb_(_fbb) { + start_ = fbb_.StartTable(); + } + flatbuffers::Offset Finish() { + const auto end = fbb_.EndTable(start_); + auto o = flatbuffers::Offset(end); + return o; + } +}; + +inline flatbuffers::Offset CreateCollisionInfoData( + flatbuffers::FlatBufferBuilder &_fbb, + int32_t last_bridge_item_number = 0, + const TEN::Save::Pose *last_bridge_item_pose = 0) { + CollisionInfoDataBuilder builder_(_fbb); + builder_.add_last_bridge_item_pose(last_bridge_item_pose); + builder_.add_last_bridge_item_number(last_bridge_item_number); + return builder_.Finish(); +} + +struct CollisionInfoData::Traits { + using type = CollisionInfoData; + static auto constexpr Create = CreateCollisionInfoData; +}; + +flatbuffers::Offset CreateCollisionInfoData(flatbuffers::FlatBufferBuilder &_fbb, const CollisionInfoDataT *_o, const flatbuffers::rehasher_function_t *_rehasher = nullptr); + struct LaraT : public flatbuffers::NativeTable { typedef Lara TableType; std::unique_ptr context{}; std::unique_ptr control{}; std::unique_ptr effect{}; + std::unique_ptr collision{}; int32_t extra_anim = 0; std::unique_ptr extra_head_rot{}; std::unique_ptr extra_torso_rot{}; @@ -4081,23 +4156,24 @@ struct Lara FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table { VT_CONTEXT = 4, VT_CONTROL = 6, VT_EFFECT = 8, - VT_EXTRA_ANIM = 10, - VT_EXTRA_HEAD_ROT = 12, - VT_EXTRA_TORSO_ROT = 14, - VT_FLARE = 16, - VT_HIGHEST_LOCATION = 18, - VT_HIT_DIRECTION = 20, - VT_HIT_FRAME = 22, - VT_INVENTORY = 24, - VT_LEFT_ARM = 26, - VT_LOCATION = 28, - VT_LOCATION_PAD = 30, - VT_RIGHT_ARM = 32, - VT_STATUS = 34, - VT_TARGET_ARM_ORIENT = 36, - VT_TARGET_ENTITY_NUMBER = 38, - VT_TORCH = 40, - VT_WEAPONS = 42 + VT_COLLISION = 10, + VT_EXTRA_ANIM = 12, + VT_EXTRA_HEAD_ROT = 14, + VT_EXTRA_TORSO_ROT = 16, + VT_FLARE = 18, + VT_HIGHEST_LOCATION = 20, + VT_HIT_DIRECTION = 22, + VT_HIT_FRAME = 24, + VT_INVENTORY = 26, + VT_LEFT_ARM = 28, + VT_LOCATION = 30, + VT_LOCATION_PAD = 32, + VT_RIGHT_ARM = 34, + VT_STATUS = 36, + VT_TARGET_ARM_ORIENT = 38, + VT_TARGET_ENTITY_NUMBER = 40, + VT_TORCH = 42, + VT_WEAPONS = 44 }; const TEN::Save::PlayerContextData *context() const { return GetPointer(VT_CONTEXT); @@ -4108,6 +4184,9 @@ struct Lara FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table { const TEN::Save::PlayerEffectData *effect() const { return GetPointer(VT_EFFECT); } + const TEN::Save::CollisionInfoData *collision() const { + return GetPointer(VT_COLLISION); + } int32_t extra_anim() const { return GetField(VT_EXTRA_ANIM, 0); } @@ -4167,6 +4246,8 @@ struct Lara FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table { verifier.VerifyTable(control()) && VerifyOffset(verifier, VT_EFFECT) && verifier.VerifyTable(effect()) && + VerifyOffset(verifier, VT_COLLISION) && + verifier.VerifyTable(collision()) && VerifyField(verifier, VT_EXTRA_ANIM) && VerifyField(verifier, VT_EXTRA_HEAD_ROT) && VerifyField(verifier, VT_EXTRA_TORSO_ROT) && @@ -4212,6 +4293,9 @@ struct LaraBuilder { void add_effect(flatbuffers::Offset effect) { fbb_.AddOffset(Lara::VT_EFFECT, effect); } + void add_collision(flatbuffers::Offset collision) { + fbb_.AddOffset(Lara::VT_COLLISION, collision); + } void add_extra_anim(int32_t extra_anim) { fbb_.AddElement(Lara::VT_EXTRA_ANIM, extra_anim, 0); } @@ -4279,6 +4363,7 @@ inline flatbuffers::Offset CreateLara( flatbuffers::Offset context = 0, flatbuffers::Offset control = 0, flatbuffers::Offset effect = 0, + flatbuffers::Offset collision = 0, int32_t extra_anim = 0, const TEN::Save::EulerAngles *extra_head_rot = 0, const TEN::Save::EulerAngles *extra_torso_rot = 0, @@ -4314,6 +4399,7 @@ inline flatbuffers::Offset CreateLara( builder_.add_extra_torso_rot(extra_torso_rot); builder_.add_extra_head_rot(extra_head_rot); builder_.add_extra_anim(extra_anim); + builder_.add_collision(collision); builder_.add_effect(effect); builder_.add_control(control); builder_.add_context(context); @@ -4330,6 +4416,7 @@ inline flatbuffers::Offset CreateLaraDirect( flatbuffers::Offset context = 0, flatbuffers::Offset control = 0, flatbuffers::Offset effect = 0, + flatbuffers::Offset collision = 0, int32_t extra_anim = 0, const TEN::Save::EulerAngles *extra_head_rot = 0, const TEN::Save::EulerAngles *extra_torso_rot = 0, @@ -4353,6 +4440,7 @@ inline flatbuffers::Offset CreateLaraDirect( context, control, effect, + collision, extra_anim, extra_head_rot, extra_torso_rot, @@ -4374,6 +4462,75 @@ inline flatbuffers::Offset CreateLaraDirect( flatbuffers::Offset CreateLara(flatbuffers::FlatBufferBuilder &_fbb, const LaraT *_o, const flatbuffers::rehasher_function_t *_rehasher = nullptr); +struct CameraT : public flatbuffers::NativeTable { + typedef Camera TableType; + std::unique_ptr position{}; + std::unique_ptr target{}; +}; + +struct Camera FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table { + typedef CameraT NativeTableType; + typedef CameraBuilder Builder; + struct Traits; + enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE { + VT_POSITION = 4, + VT_TARGET = 6 + }; + const TEN::Save::GameVector *position() const { + return GetStruct(VT_POSITION); + } + const TEN::Save::GameVector *target() const { + return GetStruct(VT_TARGET); + } + bool Verify(flatbuffers::Verifier &verifier) const { + return VerifyTableStart(verifier) && + VerifyField(verifier, VT_POSITION) && + VerifyField(verifier, VT_TARGET) && + verifier.EndTable(); + } + CameraT *UnPack(const flatbuffers::resolver_function_t *_resolver = nullptr) const; + void UnPackTo(CameraT *_o, const flatbuffers::resolver_function_t *_resolver = nullptr) const; + static flatbuffers::Offset Pack(flatbuffers::FlatBufferBuilder &_fbb, const CameraT* _o, const flatbuffers::rehasher_function_t *_rehasher = nullptr); +}; + +struct CameraBuilder { + typedef Camera Table; + flatbuffers::FlatBufferBuilder &fbb_; + flatbuffers::uoffset_t start_; + void add_position(const TEN::Save::GameVector *position) { + fbb_.AddStruct(Camera::VT_POSITION, position); + } + void add_target(const TEN::Save::GameVector *target) { + fbb_.AddStruct(Camera::VT_TARGET, target); + } + explicit CameraBuilder(flatbuffers::FlatBufferBuilder &_fbb) + : fbb_(_fbb) { + start_ = fbb_.StartTable(); + } + flatbuffers::Offset Finish() { + const auto end = fbb_.EndTable(start_); + auto o = flatbuffers::Offset(end); + return o; + } +}; + +inline flatbuffers::Offset CreateCamera( + flatbuffers::FlatBufferBuilder &_fbb, + const TEN::Save::GameVector *position = 0, + const TEN::Save::GameVector *target = 0) { + CameraBuilder builder_(_fbb); + builder_.add_target(target); + builder_.add_position(position); + return builder_.Finish(); +} + +struct Camera::Traits { + using type = Camera; + static auto constexpr Create = CreateCamera; +}; + +flatbuffers::Offset CreateCamera(flatbuffers::FlatBufferBuilder &_fbb, const CameraT *_o, const flatbuffers::rehasher_function_t *_rehasher = nullptr); + struct FixedCameraT : public flatbuffers::NativeTable { typedef FixedCamera TableType; int32_t flags = 0; @@ -5298,14 +5455,112 @@ struct SwarmObjectInfo::Traits { flatbuffers::Offset CreateSwarmObjectInfo(flatbuffers::FlatBufferBuilder &_fbb, const SwarmObjectInfoT *_o, const flatbuffers::rehasher_function_t *_rehasher = nullptr); +struct RopeSegmentT : public flatbuffers::NativeTable { + typedef RopeSegment TableType; + std::unique_ptr segment{}; + std::unique_ptr velocity{}; + std::unique_ptr normalised_segment{}; + std::unique_ptr mesh_segment{}; + std::unique_ptr coord{}; +}; + +struct RopeSegment FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table { + typedef RopeSegmentT NativeTableType; + typedef RopeSegmentBuilder Builder; + struct Traits; + enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE { + VT_SEGMENT = 4, + VT_VELOCITY = 6, + VT_NORMALISED_SEGMENT = 8, + VT_MESH_SEGMENT = 10, + VT_COORD = 12 + }; + const TEN::Save::Vector3 *segment() const { + return GetStruct(VT_SEGMENT); + } + const TEN::Save::Vector3 *velocity() const { + return GetStruct(VT_VELOCITY); + } + const TEN::Save::Vector3 *normalised_segment() const { + return GetStruct(VT_NORMALISED_SEGMENT); + } + const TEN::Save::Vector3 *mesh_segment() const { + return GetStruct(VT_MESH_SEGMENT); + } + const TEN::Save::Vector3 *coord() const { + return GetStruct(VT_COORD); + } + bool Verify(flatbuffers::Verifier &verifier) const { + return VerifyTableStart(verifier) && + VerifyField(verifier, VT_SEGMENT) && + VerifyField(verifier, VT_VELOCITY) && + VerifyField(verifier, VT_NORMALISED_SEGMENT) && + VerifyField(verifier, VT_MESH_SEGMENT) && + VerifyField(verifier, VT_COORD) && + verifier.EndTable(); + } + RopeSegmentT *UnPack(const flatbuffers::resolver_function_t *_resolver = nullptr) const; + void UnPackTo(RopeSegmentT *_o, const flatbuffers::resolver_function_t *_resolver = nullptr) const; + static flatbuffers::Offset Pack(flatbuffers::FlatBufferBuilder &_fbb, const RopeSegmentT* _o, const flatbuffers::rehasher_function_t *_rehasher = nullptr); +}; + +struct RopeSegmentBuilder { + typedef RopeSegment Table; + flatbuffers::FlatBufferBuilder &fbb_; + flatbuffers::uoffset_t start_; + void add_segment(const TEN::Save::Vector3 *segment) { + fbb_.AddStruct(RopeSegment::VT_SEGMENT, segment); + } + void add_velocity(const TEN::Save::Vector3 *velocity) { + fbb_.AddStruct(RopeSegment::VT_VELOCITY, velocity); + } + void add_normalised_segment(const TEN::Save::Vector3 *normalised_segment) { + fbb_.AddStruct(RopeSegment::VT_NORMALISED_SEGMENT, normalised_segment); + } + void add_mesh_segment(const TEN::Save::Vector3 *mesh_segment) { + fbb_.AddStruct(RopeSegment::VT_MESH_SEGMENT, mesh_segment); + } + void add_coord(const TEN::Save::Vector3 *coord) { + fbb_.AddStruct(RopeSegment::VT_COORD, coord); + } + explicit RopeSegmentBuilder(flatbuffers::FlatBufferBuilder &_fbb) + : fbb_(_fbb) { + start_ = fbb_.StartTable(); + } + flatbuffers::Offset Finish() { + const auto end = fbb_.EndTable(start_); + auto o = flatbuffers::Offset(end); + return o; + } +}; + +inline flatbuffers::Offset CreateRopeSegment( + flatbuffers::FlatBufferBuilder &_fbb, + const TEN::Save::Vector3 *segment = 0, + const TEN::Save::Vector3 *velocity = 0, + const TEN::Save::Vector3 *normalised_segment = 0, + const TEN::Save::Vector3 *mesh_segment = 0, + const TEN::Save::Vector3 *coord = 0) { + RopeSegmentBuilder builder_(_fbb); + builder_.add_coord(coord); + builder_.add_mesh_segment(mesh_segment); + builder_.add_normalised_segment(normalised_segment); + builder_.add_velocity(velocity); + builder_.add_segment(segment); + return builder_.Finish(); +} + +struct RopeSegment::Traits { + using type = RopeSegment; + static auto constexpr Create = CreateRopeSegment; +}; + +flatbuffers::Offset CreateRopeSegment(flatbuffers::FlatBufferBuilder &_fbb, const RopeSegmentT *_o, const flatbuffers::rehasher_function_t *_rehasher = nullptr); + struct RopeT : public flatbuffers::NativeTable { typedef Rope TableType; - std::vector segments{}; - std::vector velocities{}; - std::vector normalised_segments{}; - std::vector mesh_segments{}; + std::vector> segments{}; std::unique_ptr position{}; - std::vector coords{}; int32_t segment_length = 0; int32_t active = 0; int32_t coiled = 0; @@ -5317,33 +5572,17 @@ struct Rope FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table { struct Traits; enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE { VT_SEGMENTS = 4, - VT_VELOCITIES = 6, - VT_NORMALISED_SEGMENTS = 8, - VT_MESH_SEGMENTS = 10, - VT_POSITION = 12, - VT_COORDS = 14, - VT_SEGMENT_LENGTH = 16, - VT_ACTIVE = 18, - VT_COILED = 20 + VT_POSITION = 6, + VT_SEGMENT_LENGTH = 8, + VT_ACTIVE = 10, + VT_COILED = 12 }; - const flatbuffers::Vector *segments() const { - return GetPointer *>(VT_SEGMENTS); - } - const flatbuffers::Vector *velocities() const { - return GetPointer *>(VT_VELOCITIES); - } - const flatbuffers::Vector *normalised_segments() const { - return GetPointer *>(VT_NORMALISED_SEGMENTS); - } - const flatbuffers::Vector *mesh_segments() const { - return GetPointer *>(VT_MESH_SEGMENTS); + const flatbuffers::Vector> *segments() const { + return GetPointer> *>(VT_SEGMENTS); } const TEN::Save::Vector3 *position() const { return GetStruct(VT_POSITION); } - const flatbuffers::Vector *coords() const { - return GetPointer *>(VT_COORDS); - } int32_t segment_length() const { return GetField(VT_SEGMENT_LENGTH, 0); } @@ -5357,15 +5596,8 @@ struct Rope FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table { return VerifyTableStart(verifier) && VerifyOffset(verifier, VT_SEGMENTS) && verifier.VerifyVector(segments()) && - VerifyOffset(verifier, VT_VELOCITIES) && - verifier.VerifyVector(velocities()) && - VerifyOffset(verifier, VT_NORMALISED_SEGMENTS) && - verifier.VerifyVector(normalised_segments()) && - VerifyOffset(verifier, VT_MESH_SEGMENTS) && - verifier.VerifyVector(mesh_segments()) && + verifier.VerifyVectorOfTables(segments()) && VerifyField(verifier, VT_POSITION) && - VerifyOffset(verifier, VT_COORDS) && - verifier.VerifyVector(coords()) && VerifyField(verifier, VT_SEGMENT_LENGTH) && VerifyField(verifier, VT_ACTIVE) && VerifyField(verifier, VT_COILED) && @@ -5380,24 +5612,12 @@ struct RopeBuilder { typedef Rope Table; flatbuffers::FlatBufferBuilder &fbb_; flatbuffers::uoffset_t start_; - void add_segments(flatbuffers::Offset> segments) { + void add_segments(flatbuffers::Offset>> segments) { fbb_.AddOffset(Rope::VT_SEGMENTS, segments); } - void add_velocities(flatbuffers::Offset> velocities) { - fbb_.AddOffset(Rope::VT_VELOCITIES, velocities); - } - void add_normalised_segments(flatbuffers::Offset> normalised_segments) { - fbb_.AddOffset(Rope::VT_NORMALISED_SEGMENTS, normalised_segments); - } - void add_mesh_segments(flatbuffers::Offset> mesh_segments) { - fbb_.AddOffset(Rope::VT_MESH_SEGMENTS, mesh_segments); - } void add_position(const TEN::Save::Vector3 *position) { fbb_.AddStruct(Rope::VT_POSITION, position); } - void add_coords(flatbuffers::Offset> coords) { - fbb_.AddOffset(Rope::VT_COORDS, coords); - } void add_segment_length(int32_t segment_length) { fbb_.AddElement(Rope::VT_SEGMENT_LENGTH, segment_length, 0); } @@ -5420,12 +5640,8 @@ struct RopeBuilder { inline flatbuffers::Offset CreateRope( flatbuffers::FlatBufferBuilder &_fbb, - flatbuffers::Offset> segments = 0, - flatbuffers::Offset> velocities = 0, - flatbuffers::Offset> normalised_segments = 0, - flatbuffers::Offset> mesh_segments = 0, + flatbuffers::Offset>> segments = 0, const TEN::Save::Vector3 *position = 0, - flatbuffers::Offset> coords = 0, int32_t segment_length = 0, int32_t active = 0, int32_t coiled = 0) { @@ -5433,11 +5649,7 @@ inline flatbuffers::Offset CreateRope( builder_.add_coiled(coiled); builder_.add_active(active); builder_.add_segment_length(segment_length); - builder_.add_coords(coords); builder_.add_position(position); - builder_.add_mesh_segments(mesh_segments); - builder_.add_normalised_segments(normalised_segments); - builder_.add_velocities(velocities); builder_.add_segments(segments); return builder_.Finish(); } @@ -5449,28 +5661,16 @@ struct Rope::Traits { inline flatbuffers::Offset CreateRopeDirect( flatbuffers::FlatBufferBuilder &_fbb, - const std::vector *segments = nullptr, - const std::vector *velocities = nullptr, - const std::vector *normalised_segments = nullptr, - const std::vector *mesh_segments = nullptr, + const std::vector> *segments = nullptr, const TEN::Save::Vector3 *position = 0, - const std::vector *coords = nullptr, int32_t segment_length = 0, int32_t active = 0, int32_t coiled = 0) { - auto segments__ = segments ? _fbb.CreateVectorOfStructs(*segments) : 0; - auto velocities__ = velocities ? _fbb.CreateVectorOfStructs(*velocities) : 0; - auto normalised_segments__ = normalised_segments ? _fbb.CreateVectorOfStructs(*normalised_segments) : 0; - auto mesh_segments__ = mesh_segments ? _fbb.CreateVectorOfStructs(*mesh_segments) : 0; - auto coords__ = coords ? _fbb.CreateVectorOfStructs(*coords) : 0; + auto segments__ = segments ? _fbb.CreateVector>(*segments) : 0; return TEN::Save::CreateRope( _fbb, segments__, - velocities__, - normalised_segments__, - mesh_segments__, position, - coords__, segment_length, active, coiled); @@ -7102,6 +7302,7 @@ struct SaveGameT : public flatbuffers::NativeTable { std::unique_ptr header{}; std::unique_ptr game{}; std::unique_ptr level{}; + std::unique_ptr camera{}; std::unique_ptr lara{}; std::vector> rooms{}; std::vector> items{}; @@ -7161,55 +7362,56 @@ struct SaveGame FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table { VT_HEADER = 4, VT_GAME = 6, VT_LEVEL = 8, - VT_LARA = 10, - VT_ROOMS = 12, - VT_ITEMS = 14, - VT_NEXT_ITEM_FREE = 16, - VT_NEXT_ITEM_ACTIVE = 18, - VT_ROOM_ITEMS = 20, - VT_FISH_SWARM = 22, - VT_FXINFOS = 24, - VT_NEXT_FX_FREE = 26, - VT_NEXT_FX_ACTIVE = 28, - VT_FIXED_CAMERAS = 30, - VT_SINKS = 32, - VT_STATIC_MESHES = 34, - VT_FLYBY_CAMERAS = 36, - VT_PARTICLES = 38, - VT_RATS = 40, - VT_SPIDERS = 42, - VT_SCARABS = 44, - VT_BATS = 46, - VT_FLIP_MAPS = 48, - VT_FLIP_STATS = 50, - VT_FLIP_EFFECT = 52, - VT_FLIP_TIMER = 54, - VT_FLIP_STATUS = 56, - VT_CURRENT_FOV = 58, - VT_LAST_INV_ITEM = 60, - VT_ACTION_QUEUE = 62, - VT_SOUNDTRACKS = 64, - VT_CD_FLAGS = 66, - VT_POSTPROCESS_MODE = 68, - VT_POSTPROCESS_STRENGTH = 70, - VT_POSTPROCESS_TINT = 72, - VT_ROPE = 74, - VT_PENDULUM = 76, - VT_ALTERNATE_PENDULUM = 78, - VT_VOLUMES = 80, - VT_GLOBAL_EVENT_SETS = 82, - VT_VOLUME_EVENT_SETS = 84, - VT_SCRIPT_VARS = 86, - VT_CALLBACKS_PRE_START = 88, - VT_CALLBACKS_POST_START = 90, - VT_CALLBACKS_PRE_END = 92, - VT_CALLBACKS_POST_END = 94, - VT_CALLBACKS_PRE_SAVE = 96, - VT_CALLBACKS_POST_SAVE = 98, - VT_CALLBACKS_PRE_LOAD = 100, - VT_CALLBACKS_POST_LOAD = 102, - VT_CALLBACKS_PRE_LOOP = 104, - VT_CALLBACKS_POST_LOOP = 106 + VT_CAMERA = 10, + VT_LARA = 12, + VT_ROOMS = 14, + VT_ITEMS = 16, + VT_NEXT_ITEM_FREE = 18, + VT_NEXT_ITEM_ACTIVE = 20, + VT_ROOM_ITEMS = 22, + VT_FISH_SWARM = 24, + VT_FXINFOS = 26, + VT_NEXT_FX_FREE = 28, + VT_NEXT_FX_ACTIVE = 30, + VT_FIXED_CAMERAS = 32, + VT_SINKS = 34, + VT_STATIC_MESHES = 36, + VT_FLYBY_CAMERAS = 38, + VT_PARTICLES = 40, + VT_RATS = 42, + VT_SPIDERS = 44, + VT_SCARABS = 46, + VT_BATS = 48, + VT_FLIP_MAPS = 50, + VT_FLIP_STATS = 52, + VT_FLIP_EFFECT = 54, + VT_FLIP_TIMER = 56, + VT_FLIP_STATUS = 58, + VT_CURRENT_FOV = 60, + VT_LAST_INV_ITEM = 62, + VT_ACTION_QUEUE = 64, + VT_SOUNDTRACKS = 66, + VT_CD_FLAGS = 68, + VT_POSTPROCESS_MODE = 70, + VT_POSTPROCESS_STRENGTH = 72, + VT_POSTPROCESS_TINT = 74, + VT_ROPE = 76, + VT_PENDULUM = 78, + VT_ALTERNATE_PENDULUM = 80, + VT_VOLUMES = 82, + VT_GLOBAL_EVENT_SETS = 84, + VT_VOLUME_EVENT_SETS = 86, + VT_SCRIPT_VARS = 88, + VT_CALLBACKS_PRE_START = 90, + VT_CALLBACKS_POST_START = 92, + VT_CALLBACKS_PRE_END = 94, + VT_CALLBACKS_POST_END = 96, + VT_CALLBACKS_PRE_SAVE = 98, + VT_CALLBACKS_POST_SAVE = 100, + VT_CALLBACKS_PRE_LOAD = 102, + VT_CALLBACKS_POST_LOAD = 104, + VT_CALLBACKS_PRE_LOOP = 106, + VT_CALLBACKS_POST_LOOP = 108 }; const TEN::Save::SaveGameHeader *header() const { return GetPointer(VT_HEADER); @@ -7220,6 +7422,9 @@ struct SaveGame FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table { const TEN::Save::SaveGameStatistics *level() const { return GetPointer(VT_LEVEL); } + const TEN::Save::Camera *camera() const { + return GetPointer(VT_CAMERA); + } const TEN::Save::Lara *lara() const { return GetPointer(VT_LARA); } @@ -7375,6 +7580,8 @@ struct SaveGame FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table { verifier.VerifyTable(game()) && VerifyOffset(verifier, VT_LEVEL) && verifier.VerifyTable(level()) && + VerifyOffset(verifier, VT_CAMERA) && + verifier.VerifyTable(camera()) && VerifyOffset(verifier, VT_LARA) && verifier.VerifyTable(lara()) && VerifyOffset(verifier, VT_ROOMS) && @@ -7508,6 +7715,9 @@ struct SaveGameBuilder { void add_level(flatbuffers::Offset level) { fbb_.AddOffset(SaveGame::VT_LEVEL, level); } + void add_camera(flatbuffers::Offset camera) { + fbb_.AddOffset(SaveGame::VT_CAMERA, camera); + } void add_lara(flatbuffers::Offset lara) { fbb_.AddOffset(SaveGame::VT_LARA, lara); } @@ -7671,6 +7881,7 @@ inline flatbuffers::Offset CreateSaveGame( flatbuffers::Offset header = 0, flatbuffers::Offset game = 0, flatbuffers::Offset level = 0, + flatbuffers::Offset camera = 0, flatbuffers::Offset lara = 0, flatbuffers::Offset>> rooms = 0, flatbuffers::Offset>> items = 0, @@ -7769,6 +7980,7 @@ inline flatbuffers::Offset CreateSaveGame( builder_.add_items(items); builder_.add_rooms(rooms); builder_.add_lara(lara); + builder_.add_camera(camera); builder_.add_level(level); builder_.add_game(game); builder_.add_header(header); @@ -7786,6 +7998,7 @@ inline flatbuffers::Offset CreateSaveGameDirect( flatbuffers::Offset header = 0, flatbuffers::Offset game = 0, flatbuffers::Offset level = 0, + flatbuffers::Offset camera = 0, flatbuffers::Offset lara = 0, const std::vector> *rooms = nullptr, const std::vector> *items = nullptr, @@ -7872,6 +8085,7 @@ inline flatbuffers::Offset CreateSaveGameDirect( header, game, level, + camera, lara, rooms__, items__, @@ -8993,6 +9207,35 @@ inline flatbuffers::Offset CreatePlayerContextData(flatbuffer _water_surface_dist); } +inline CollisionInfoDataT *CollisionInfoData::UnPack(const flatbuffers::resolver_function_t *_resolver) const { + auto _o = std::make_unique(); + UnPackTo(_o.get(), _resolver); + return _o.release(); +} + +inline void CollisionInfoData::UnPackTo(CollisionInfoDataT *_o, const flatbuffers::resolver_function_t *_resolver) const { + (void)_o; + (void)_resolver; + { auto _e = last_bridge_item_number(); _o->last_bridge_item_number = _e; } + { auto _e = last_bridge_item_pose(); if (_e) _o->last_bridge_item_pose = std::unique_ptr(new TEN::Save::Pose(*_e)); } +} + +inline flatbuffers::Offset CollisionInfoData::Pack(flatbuffers::FlatBufferBuilder &_fbb, const CollisionInfoDataT* _o, const flatbuffers::rehasher_function_t *_rehasher) { + return CreateCollisionInfoData(_fbb, _o, _rehasher); +} + +inline flatbuffers::Offset CreateCollisionInfoData(flatbuffers::FlatBufferBuilder &_fbb, const CollisionInfoDataT *_o, const flatbuffers::rehasher_function_t *_rehasher) { + (void)_rehasher; + (void)_o; + struct _VectorArgs { flatbuffers::FlatBufferBuilder *__fbb; const CollisionInfoDataT* __o; const flatbuffers::rehasher_function_t *__rehasher; } _va = { &_fbb, _o, _rehasher}; (void)_va; + auto _last_bridge_item_number = _o->last_bridge_item_number; + auto _last_bridge_item_pose = _o->last_bridge_item_pose ? _o->last_bridge_item_pose.get() : 0; + return TEN::Save::CreateCollisionInfoData( + _fbb, + _last_bridge_item_number, + _last_bridge_item_pose); +} + inline LaraT *Lara::UnPack(const flatbuffers::resolver_function_t *_resolver) const { auto _o = std::make_unique(); UnPackTo(_o.get(), _resolver); @@ -9005,6 +9248,7 @@ inline void Lara::UnPackTo(LaraT *_o, const flatbuffers::resolver_function_t *_r { auto _e = context(); if (_e) _o->context = std::unique_ptr(_e->UnPack(_resolver)); } { auto _e = control(); if (_e) _o->control = std::unique_ptr(_e->UnPack(_resolver)); } { auto _e = effect(); if (_e) _o->effect = std::unique_ptr(_e->UnPack(_resolver)); } + { auto _e = collision(); if (_e) _o->collision = std::unique_ptr(_e->UnPack(_resolver)); } { auto _e = extra_anim(); _o->extra_anim = _e; } { auto _e = extra_head_rot(); if (_e) _o->extra_head_rot = std::unique_ptr(new TEN::Save::EulerAngles(*_e)); } { auto _e = extra_torso_rot(); if (_e) _o->extra_torso_rot = std::unique_ptr(new TEN::Save::EulerAngles(*_e)); } @@ -9035,6 +9279,7 @@ inline flatbuffers::Offset CreateLara(flatbuffers::FlatBufferBuilder &_fbb auto _context = _o->context ? CreatePlayerContextData(_fbb, _o->context.get(), _rehasher) : 0; auto _control = _o->control ? CreateLaraControlData(_fbb, _o->control.get(), _rehasher) : 0; auto _effect = _o->effect ? CreatePlayerEffectData(_fbb, _o->effect.get(), _rehasher) : 0; + auto _collision = _o->collision ? CreateCollisionInfoData(_fbb, _o->collision.get(), _rehasher) : 0; auto _extra_anim = _o->extra_anim; auto _extra_head_rot = _o->extra_head_rot ? _o->extra_head_rot.get() : 0; auto _extra_torso_rot = _o->extra_torso_rot ? _o->extra_torso_rot.get() : 0; @@ -9057,6 +9302,7 @@ inline flatbuffers::Offset CreateLara(flatbuffers::FlatBufferBuilder &_fbb _context, _control, _effect, + _collision, _extra_anim, _extra_head_rot, _extra_torso_rot, @@ -9076,6 +9322,35 @@ inline flatbuffers::Offset CreateLara(flatbuffers::FlatBufferBuilder &_fbb _weapons); } +inline CameraT *Camera::UnPack(const flatbuffers::resolver_function_t *_resolver) const { + auto _o = std::make_unique(); + UnPackTo(_o.get(), _resolver); + return _o.release(); +} + +inline void Camera::UnPackTo(CameraT *_o, const flatbuffers::resolver_function_t *_resolver) const { + (void)_o; + (void)_resolver; + { auto _e = position(); if (_e) _o->position = std::unique_ptr(new TEN::Save::GameVector(*_e)); } + { auto _e = target(); if (_e) _o->target = std::unique_ptr(new TEN::Save::GameVector(*_e)); } +} + +inline flatbuffers::Offset Camera::Pack(flatbuffers::FlatBufferBuilder &_fbb, const CameraT* _o, const flatbuffers::rehasher_function_t *_rehasher) { + return CreateCamera(_fbb, _o, _rehasher); +} + +inline flatbuffers::Offset CreateCamera(flatbuffers::FlatBufferBuilder &_fbb, const CameraT *_o, const flatbuffers::rehasher_function_t *_rehasher) { + (void)_rehasher; + (void)_o; + struct _VectorArgs { flatbuffers::FlatBufferBuilder *__fbb; const CameraT* __o; const flatbuffers::rehasher_function_t *__rehasher; } _va = { &_fbb, _o, _rehasher}; (void)_va; + auto _position = _o->position ? _o->position.get() : 0; + auto _target = _o->target ? _o->target.get() : 0; + return TEN::Save::CreateCamera( + _fbb, + _position, + _target); +} + inline FixedCameraT *FixedCamera::UnPack(const flatbuffers::resolver_function_t *_resolver) const { auto _o = std::make_unique(); UnPackTo(_o.get(), _resolver); @@ -9396,6 +9671,44 @@ inline flatbuffers::Offset CreateSwarmObjectInfo(flatbuffers::F _flags); } +inline RopeSegmentT *RopeSegment::UnPack(const flatbuffers::resolver_function_t *_resolver) const { + auto _o = std::make_unique(); + UnPackTo(_o.get(), _resolver); + return _o.release(); +} + +inline void RopeSegment::UnPackTo(RopeSegmentT *_o, const flatbuffers::resolver_function_t *_resolver) const { + (void)_o; + (void)_resolver; + { auto _e = segment(); if (_e) _o->segment = std::unique_ptr(new TEN::Save::Vector3(*_e)); } + { auto _e = velocity(); if (_e) _o->velocity = std::unique_ptr(new TEN::Save::Vector3(*_e)); } + { auto _e = normalised_segment(); if (_e) _o->normalised_segment = std::unique_ptr(new TEN::Save::Vector3(*_e)); } + { auto _e = mesh_segment(); if (_e) _o->mesh_segment = std::unique_ptr(new TEN::Save::Vector3(*_e)); } + { auto _e = coord(); if (_e) _o->coord = std::unique_ptr(new TEN::Save::Vector3(*_e)); } +} + +inline flatbuffers::Offset RopeSegment::Pack(flatbuffers::FlatBufferBuilder &_fbb, const RopeSegmentT* _o, const flatbuffers::rehasher_function_t *_rehasher) { + return CreateRopeSegment(_fbb, _o, _rehasher); +} + +inline flatbuffers::Offset CreateRopeSegment(flatbuffers::FlatBufferBuilder &_fbb, const RopeSegmentT *_o, const flatbuffers::rehasher_function_t *_rehasher) { + (void)_rehasher; + (void)_o; + struct _VectorArgs { flatbuffers::FlatBufferBuilder *__fbb; const RopeSegmentT* __o; const flatbuffers::rehasher_function_t *__rehasher; } _va = { &_fbb, _o, _rehasher}; (void)_va; + auto _segment = _o->segment ? _o->segment.get() : 0; + auto _velocity = _o->velocity ? _o->velocity.get() : 0; + auto _normalised_segment = _o->normalised_segment ? _o->normalised_segment.get() : 0; + auto _mesh_segment = _o->mesh_segment ? _o->mesh_segment.get() : 0; + auto _coord = _o->coord ? _o->coord.get() : 0; + return TEN::Save::CreateRopeSegment( + _fbb, + _segment, + _velocity, + _normalised_segment, + _mesh_segment, + _coord); +} + inline RopeT *Rope::UnPack(const flatbuffers::resolver_function_t *_resolver) const { auto _o = std::make_unique(); UnPackTo(_o.get(), _resolver); @@ -9405,12 +9718,8 @@ inline RopeT *Rope::UnPack(const flatbuffers::resolver_function_t *_resolver) co inline void Rope::UnPackTo(RopeT *_o, const flatbuffers::resolver_function_t *_resolver) const { (void)_o; (void)_resolver; - { auto _e = segments(); if (_e) { _o->segments.resize(_e->size()); for (flatbuffers::uoffset_t _i = 0; _i < _e->size(); _i++) { _o->segments[_i] = *_e->Get(_i); } } } - { auto _e = velocities(); if (_e) { _o->velocities.resize(_e->size()); for (flatbuffers::uoffset_t _i = 0; _i < _e->size(); _i++) { _o->velocities[_i] = *_e->Get(_i); } } } - { auto _e = normalised_segments(); if (_e) { _o->normalised_segments.resize(_e->size()); for (flatbuffers::uoffset_t _i = 0; _i < _e->size(); _i++) { _o->normalised_segments[_i] = *_e->Get(_i); } } } - { auto _e = mesh_segments(); if (_e) { _o->mesh_segments.resize(_e->size()); for (flatbuffers::uoffset_t _i = 0; _i < _e->size(); _i++) { _o->mesh_segments[_i] = *_e->Get(_i); } } } + { auto _e = segments(); if (_e) { _o->segments.resize(_e->size()); for (flatbuffers::uoffset_t _i = 0; _i < _e->size(); _i++) { _o->segments[_i] = std::unique_ptr(_e->Get(_i)->UnPack(_resolver)); } } } { auto _e = position(); if (_e) _o->position = std::unique_ptr(new TEN::Save::Vector3(*_e)); } - { auto _e = coords(); if (_e) { _o->coords.resize(_e->size()); for (flatbuffers::uoffset_t _i = 0; _i < _e->size(); _i++) { _o->coords[_i] = *_e->Get(_i); } } } { auto _e = segment_length(); _o->segment_length = _e; } { auto _e = active(); _o->active = _e; } { auto _e = coiled(); _o->coiled = _e; } @@ -9424,23 +9733,15 @@ inline flatbuffers::Offset CreateRope(flatbuffers::FlatBufferBuilder &_fbb (void)_rehasher; (void)_o; struct _VectorArgs { flatbuffers::FlatBufferBuilder *__fbb; const RopeT* __o; const flatbuffers::rehasher_function_t *__rehasher; } _va = { &_fbb, _o, _rehasher}; (void)_va; - auto _segments = _fbb.CreateVectorOfStructs(_o->segments); - auto _velocities = _fbb.CreateVectorOfStructs(_o->velocities); - auto _normalised_segments = _fbb.CreateVectorOfStructs(_o->normalised_segments); - auto _mesh_segments = _fbb.CreateVectorOfStructs(_o->mesh_segments); + auto _segments = _fbb.CreateVector> (_o->segments.size(), [](size_t i, _VectorArgs *__va) { return CreateRopeSegment(*__va->__fbb, __va->__o->segments[i].get(), __va->__rehasher); }, &_va ); auto _position = _o->position ? _o->position.get() : 0; - auto _coords = _fbb.CreateVectorOfStructs(_o->coords); auto _segment_length = _o->segment_length; auto _active = _o->active; auto _coiled = _o->coiled; return TEN::Save::CreateRope( _fbb, _segments, - _velocities, - _normalised_segments, - _mesh_segments, _position, - _coords, _segment_length, _active, _coiled); @@ -10037,6 +10338,7 @@ inline void SaveGame::UnPackTo(SaveGameT *_o, const flatbuffers::resolver_functi { auto _e = header(); if (_e) _o->header = std::unique_ptr(_e->UnPack(_resolver)); } { auto _e = game(); if (_e) _o->game = std::unique_ptr(_e->UnPack(_resolver)); } { auto _e = level(); if (_e) _o->level = std::unique_ptr(_e->UnPack(_resolver)); } + { auto _e = camera(); if (_e) _o->camera = std::unique_ptr(_e->UnPack(_resolver)); } { auto _e = lara(); if (_e) _o->lara = std::unique_ptr(_e->UnPack(_resolver)); } { auto _e = rooms(); if (_e) { _o->rooms.resize(_e->size()); for (flatbuffers::uoffset_t _i = 0; _i < _e->size(); _i++) { _o->rooms[_i] = std::unique_ptr(_e->Get(_i)->UnPack(_resolver)); } } } { auto _e = items(); if (_e) { _o->items.resize(_e->size()); for (flatbuffers::uoffset_t _i = 0; _i < _e->size(); _i++) { _o->items[_i] = std::unique_ptr(_e->Get(_i)->UnPack(_resolver)); } } } @@ -10099,6 +10401,7 @@ inline flatbuffers::Offset CreateSaveGame(flatbuffers::FlatBufferBuild auto _header = _o->header ? CreateSaveGameHeader(_fbb, _o->header.get(), _rehasher) : 0; auto _game = _o->game ? CreateSaveGameStatistics(_fbb, _o->game.get(), _rehasher) : 0; auto _level = _o->level ? CreateSaveGameStatistics(_fbb, _o->level.get(), _rehasher) : 0; + auto _camera = _o->camera ? CreateCamera(_fbb, _o->camera.get(), _rehasher) : 0; auto _lara = _o->lara ? CreateLara(_fbb, _o->lara.get(), _rehasher) : 0; auto _rooms = _fbb.CreateVector> (_o->rooms.size(), [](size_t i, _VectorArgs *__va) { return CreateRoom(*__va->__fbb, __va->__o->rooms[i].get(), __va->__rehasher); }, &_va ); auto _items = _fbb.CreateVector> (_o->items.size(), [](size_t i, _VectorArgs *__va) { return CreateItem(*__va->__fbb, __va->__o->items[i].get(), __va->__rehasher); }, &_va ); @@ -10153,6 +10456,7 @@ inline flatbuffers::Offset CreateSaveGame(flatbuffers::FlatBufferBuild _header, _game, _level, + _camera, _lara, _rooms, _items, diff --git a/TombEngine/Specific/savegame/schema/ten_itemdata.fbs b/TombEngine/Specific/savegame/schema/ten_itemdata.fbs index 7e3d8166f..d14c69973 100644 --- a/TombEngine/Specific/savegame/schema/ten_itemdata.fbs +++ b/TombEngine/Specific/savegame/schema/ten_itemdata.fbs @@ -123,10 +123,6 @@ table Motorboat { } -table GameVector { - -} - table Wraith { } @@ -232,18 +228,14 @@ struct Vector4 { w: float; } +struct GameVector { + x: int32; + y: int32; + z: int32; + room_number: int32; +} + union ItemData { - /*byte, - ubyte, - bool, - short, - ushort, - int, - uint, - float, - long, - ulong, - double,*/ Int, Short, Float, @@ -261,7 +253,6 @@ union ItemData { Skidoo, UPV, Motorboat, - GameVector, Wraith, Rubberboat, Pushable, diff --git a/TombEngine/Specific/savegame/schema/ten_savegame.fbs b/TombEngine/Specific/savegame/schema/ten_savegame.fbs index 2c67b42ec..d7089ad40 100644 --- a/TombEngine/Specific/savegame/schema/ten_savegame.fbs +++ b/TombEngine/Specific/savegame/schema/ten_savegame.fbs @@ -274,10 +274,16 @@ table PlayerContextData { water_surface_dist: int32; } +table CollisionInfoData { + last_bridge_item_number: int32; + last_bridge_item_pose: Pose; +} + table Lara { context: PlayerContextData; control: LaraControlData; effect: PlayerEffectData; + collision: CollisionInfoData; extra_anim: int32; extra_head_rot: EulerAngles; extra_torso_rot: EulerAngles; @@ -297,6 +303,11 @@ table Lara { weapons: [CarriedWeaponInfo]; } +table Camera { + position: GameVector; + target: GameVector; +} + table FixedCamera { flags: int32; } @@ -371,13 +382,17 @@ table SwarmObjectInfo { flags: int32; } +table RopeSegment { + segment: Vector3; + velocity: Vector3; + normalised_segment: Vector3; + mesh_segment: Vector3; + coord: Vector3; +} + table Rope { - segments: [Vector3]; - velocities: [Vector3]; - normalised_segments: [Vector3]; - mesh_segments: [Vector3]; + segments: [RopeSegment]; position: Vector3; - coords: [Vector3]; segment_length: int32; active: int32; coiled: int32; @@ -512,6 +527,7 @@ table SaveGame { header: SaveGameHeader; game: SaveGameStatistics; level: SaveGameStatistics; + camera: Camera; lara: Lara; rooms: [Room]; items: [Item]; diff --git a/TombEngine/Specific/winmain.cpp b/TombEngine/Specific/winmain.cpp index fe1cd8bc3..a601d0d36 100644 --- a/TombEngine/Specific/winmain.cpp +++ b/TombEngine/Specific/winmain.cpp @@ -60,6 +60,22 @@ Vector2i GetScreenResolution() return resolution; } +int GetCurrentScreenRefreshRate() +{ + DEVMODE devmode; + memset(&devmode, 0, sizeof(devmode)); + devmode.dmSize = sizeof(devmode); + + if (EnumDisplaySettings(NULL, ENUM_CURRENT_SETTINGS, &devmode)) + { + return devmode.dmDisplayFrequency; + } + else + { + return 0; + } +} + std::vector GetAllSupportedScreenResolutions() { auto resList = std::vector{}; @@ -140,17 +156,6 @@ void CALLBACK HandleWmCommand(unsigned short wParam) SuspendThread((HANDLE)ThreadHandle); g_Renderer.ToggleFullScreen(); ResumeThread((HANDLE)ThreadHandle); - - if (g_Renderer.IsFullsScreen()) - { - SetCursor(nullptr); - ShowCursor(false); - } - else - { - SetCursor(LoadCursorA(App.hInstance, (LPCSTR)0x68)); - ShowCursor(true); - } } } } @@ -164,6 +169,21 @@ LRESULT CALLBACK WinAppProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { return 0; } + + if (msg == WM_SETCURSOR) + { + if (LOWORD(lParam) == HTCLIENT) + { + SetCursor(g_Renderer.IsFullsScreen() ? nullptr : App.WindowClass.hCursor); + return 1; + } + } + + if (msg == WM_ACTIVATEAPP) + { + App.ResetClock = true; + return DefWindowProcA(hWnd, msg, wParam, (LPARAM)lParam); + } if (msg > WM_CLOSE) { @@ -348,7 +368,7 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine App.WindowClass.lpfnWndProc = WinAppProc; App.WindowClass.cbClsExtra = 0; App.WindowClass.cbWndExtra = 0; - App.WindowClass.hCursor = LoadCursor(App.hInstance, IDC_ARROW); + App.WindowClass.hCursor = LoadCursorA(NULL, IDC_ARROW); // Register main window. if (!RegisterClass(&App.WindowClass)) @@ -439,8 +459,6 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine UpdateWindow(WindowsHandle); ShowWindow(WindowsHandle, nShowCmd); - SetCursor(NULL); - ShowCursor(FALSE); hAccTable = LoadAccelerators(hInstance, (LPCSTR)0x65); } catch (std::exception& ex) diff --git a/TombEngine/Specific/winmain.h b/TombEngine/Specific/winmain.h index 5162e48c9..14158adb5 100644 --- a/TombEngine/Specific/winmain.h +++ b/TombEngine/Specific/winmain.h @@ -1,40 +1,43 @@ -#pragma once -#pragma comment(linker,"/manifestdependency:\"" \ - "type='win32' " \ - "name='Microsoft.Windows.Common-Controls' " \ - "version='6.0.0.0' " \ - "processorArchitecture='*' " \ - "publicKeyToken='6595b64144ccf1df' " \ - "language='*'\"") - -#include -#include - -#include "Math/Math.h" - -using namespace TEN::Math; - -struct WINAPP -{ - HINSTANCE hInstance; - int nFillMode; - WNDCLASS WindowClass; - HWND WindowHandle; - bool bNoFocus; - bool isInScene; -}; - -extern bool DebugMode; -extern HWND WindowsHandle; - -// return handle -#define BeginThread(function, threadid) _beginthreadex(0, 0, &function, 0, 0, &threadid) -#define EndThread() _endthreadex(1) - -void WinProcMsg(); -int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd); -void WinClose(); -LRESULT CALLBACK WinAppProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); -void CALLBACK HandleWmCommand(unsigned short wParam); -Vector2i GetScreenResolution(); -std::vector GetAllSupportedScreenResolutions(); \ No newline at end of file +#pragma once +#pragma comment(linker,"/manifestdependency:\"" \ + "type='win32' " \ + "name='Microsoft.Windows.Common-Controls' " \ + "version='6.0.0.0' " \ + "processorArchitecture='*' " \ + "publicKeyToken='6595b64144ccf1df' " \ + "language='*'\"") + +#include +#include + +#include "Math/Math.h" + +using namespace TEN::Math; + +struct WINAPP +{ + HINSTANCE hInstance; + int nFillMode; + WNDCLASS WindowClass; + HWND WindowHandle; + bool bNoFocus; + bool isInScene; + bool ResetClock; +}; + +extern bool DebugMode; +extern HWND WindowsHandle; +extern WINAPP App; + +// return handle +#define BeginThread(function, threadid) _beginthreadex(0, 0, &function, 0, 0, &threadid) +#define EndThread() _endthreadex(1) + +void WinProcMsg(); +int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd); +void WinClose(); +LRESULT CALLBACK WinAppProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); +void CALLBACK HandleWmCommand(unsigned short wParam); +Vector2i GetScreenResolution(); +std::vector GetAllSupportedScreenResolutions(); +int GetCurrentScreenRefreshRate(); \ No newline at end of file diff --git a/TombEngine/TombEngine.vcxproj b/TombEngine/TombEngine.vcxproj index 32b5b12f6..455bf7851 100644 --- a/TombEngine/TombEngine.vcxproj +++ b/TombEngine/TombEngine.vcxproj @@ -1,4 +1,4 @@ - + @@ -139,20 +139,58 @@ - CD $(ProjectDir)..\Documentation\ + SETLOCAL EnableDelayedExpansion + +SET SchemaDir=$(ProjectDir)Specific\savegame\schema +SET TimestampFile=$(TargetDir)flatbuffers.timestamp + +FOR %%F IN ("%SchemaDir%\ten_itemdata.fbs" "%SchemaDir%\ten_savegame.fbs") DO ( + SET "CurrentTimestamp=!CurrentTimestamp! %%~tF" +) + +IF EXIST "%TimestampFile%" ( + SET /P LastTimestamp=<"%TimestampFile%" +) + +SET CurrentTimestamp=%CurrentTimestamp: =% +SET LastTimestamp=%LastTimestamp: =% + +IF "%CurrentTimestamp%" == "%LastTimestamp%" ( + ECHO Skipping gen.bat, schema files are unchanged +) ELSE ( + ECHO Generating savegame code from flatbuffer schema... + CD "%SchemaDir%" + CALL gen.bat + ECHO !CurrentTimestamp! > "%TimestampFile%" +) + +ENDLOCAL + +CD "$(ProjectDir)..\Documentation\" CALL compile.bat . -CD $(ProjectDir)Specific\savegame\schema\ -CALL gen.bat +SET BuildDir=$(SolutionDir)Build\$(Configuration) +SET ShaderDir=%BuildDir%\Shaders +SET ScriptsDir=%BuildDir%\Scripts +SET EngineDir=%ScriptsDir%\Engine -md "$(SolutionDir)Build\$(Configuration)\Shaders" -xcopy /Y /D "$(ProjectDir)Shaders\*.*" "$(SolutionDir)Build\$(Configuration)\Shaders\" -xcopy /Y /D "$(ProjectDir)Shaders\HUD\*.hlsl" "$(SolutionDir)Build\$(Configuration)\Shaders\HUD\" +if not exist %ShaderDir% mkdir %ShaderDir% +if not exist %ScriptsDir% mkdir %ScriptsDir% +if not exist %EngineDir% mkdir %EngineDir% + +xcopy /D /Y /I /E "$(ProjectDir)Shaders\*.*" %ShaderDir% xcopy /Y "$(SolutionDir)Libs\bass\x86\*.dll" "$(TargetDir)" xcopy /Y "$(SolutionDir)Libs\lua\x86\*.dll" "$(TargetDir)" xcopy /Y "$(SolutionDir)Libs\ois\x86\*.dll" "$(TargetDir)" -xcopy /Y "$(SolutionDir)Libs\zlib\x86\*.dll" "$(TargetDir)" +xcopy /Y "$(SolutionDir)Libs\zlib\x86\*.dll" "$(TargetDir)" + +xcopy /D /Y /I /E "$(SolutionDir)Scripts\Engine\*.*" %EngineDir% +xcopy /D /Y "$(SolutionDir)Scripts\SystemStrings.lua" %ScriptsDir% + +if not exist "%ScriptsDir%\Gameflow.lua" xcopy /Y "$(SolutionDir)Scripts\Gameflow.lua" %ScriptsDir% +if not exist "%ScriptsDir%\Settings.lua" xcopy /Y "$(SolutionDir)Scripts\Settings.lua" %ScriptsDir% +if not exist "%ScriptsDir%\Strings.lua" xcopy /Y "$(SolutionDir)Scripts\Strings.lua" %ScriptsDir% Generating documentation, savegame flatbuffer and copying needed files... @@ -190,20 +228,58 @@ xcopy /Y "$(SolutionDir)Libs\zlib\x86\*.dll" "$(TargetDir)" - CD $(ProjectDir)..\Documentation\ + SETLOCAL EnableDelayedExpansion + +SET SchemaDir=$(ProjectDir)Specific\savegame\schema +SET TimestampFile=$(TargetDir)flatbuffers.timestamp + +FOR %%F IN ("%SchemaDir%\ten_itemdata.fbs" "%SchemaDir%\ten_savegame.fbs") DO ( + SET "CurrentTimestamp=!CurrentTimestamp! %%~tF" +) + +IF EXIST "%TimestampFile%" ( + SET /P LastTimestamp=<"%TimestampFile%" +) + +SET CurrentTimestamp=%CurrentTimestamp: =% +SET LastTimestamp=%LastTimestamp: =% + +IF "%CurrentTimestamp%" == "%LastTimestamp%" ( + ECHO Skipping gen.bat, schema files are unchanged +) ELSE ( + ECHO Generating savegame code from flatbuffer schema... + CD "%SchemaDir%" + CALL gen.bat + ECHO !CurrentTimestamp! > "%TimestampFile%" +) + +ENDLOCAL + +CD "$(ProjectDir)..\Documentation\" CALL compile.bat . -CD $(ProjectDir)Specific\savegame\schema\ -CALL gen.bat +SET BuildDir=$(SolutionDir)Build\$(Configuration) +SET ShaderDir=%BuildDir%\Shaders +SET ScriptsDir=%BuildDir%\Scripts +SET EngineDir=%ScriptsDir%\Engine -md "$(SolutionDir)Build\$(Configuration)\Shaders" -xcopy /Y /D "$(ProjectDir)Shaders\*.*" "$(SolutionDir)Build\$(Configuration)\Shaders\" -xcopy /Y /D "$(ProjectDir)Shaders\HUD\*.hlsl" "$(SolutionDir)Build\$(Configuration)\Shaders\HUD\" +if not exist %ShaderDir% mkdir %ShaderDir% +if not exist %ScriptsDir% mkdir %ScriptsDir% +if not exist %EngineDir% mkdir %EngineDir% + +xcopy /D /Y /I /E "$(ProjectDir)Shaders\*.*" %ShaderDir% xcopy /Y "$(SolutionDir)Libs\bass\x64\*.dll" "$(TargetDir)" xcopy /Y "$(SolutionDir)Libs\lua\x64\*.dll" "$(TargetDir)" xcopy /Y "$(SolutionDir)Libs\ois\x64\*.dll" "$(TargetDir)" -xcopy /Y "$(SolutionDir)Libs\zlib\x64\*.dll" "$(TargetDir)" +xcopy /Y "$(SolutionDir)Libs\zlib\x64\*.dll" "$(TargetDir)" + +xcopy /D /Y /I /E "$(SolutionDir)Scripts\Engine\*.*" %EngineDir% +xcopy /D /Y "$(SolutionDir)Scripts\SystemStrings.lua" %ScriptsDir% + +if not exist "%ScriptsDir%\Gameflow.lua" xcopy /Y "$(SolutionDir)Scripts\Gameflow.lua" %ScriptsDir% +if not exist "%ScriptsDir%\Settings.lua" xcopy /Y "$(SolutionDir)Scripts\Settings.lua" %ScriptsDir% +if not exist "%ScriptsDir%\Strings.lua" xcopy /Y "$(SolutionDir)Scripts\Strings.lua" %ScriptsDir% Generating documentation, savegame flatbuffer and copying needed files... @@ -235,6 +311,7 @@ xcopy /Y "$(SolutionDir)Libs\zlib\x64\*.dll" "$(TargetDir)" /Zc:__cplusplus /experimental:external /external:anglebrackets 4018;4244;4996;%(DisableSpecificWarnings) true + $(IntDir)/%(RelativeDir)/ Console @@ -253,20 +330,34 @@ del "$(TargetDir)*.exp" /q del "$(TargetDir)OIS_d.dll" /q - CD $(ProjectDir)..\Documentation\ + CD "$(ProjectDir)..\Documentation\" CALL compile.bat . -CD $(ProjectDir)Specific\savegame\schema\ +CD "$(ProjectDir)Specific\savegame\schema\" CALL gen.bat -md "$(SolutionDir)Build\$(Configuration)\Shaders" -xcopy /Y /D "$(ProjectDir)Shaders\*.*" "$(SolutionDir)Build\$(Configuration)\Shaders\" -xcopy /Y /D "$(ProjectDir)Shaders\HUD\*.hlsl" "$(SolutionDir)Build\$(Configuration)\Shaders\HUD\" +SET BuildDir=$(SolutionDir)Build\$(Configuration) +SET ShaderDir=%BuildDir%\Shaders +SET ScriptsDir=%BuildDir%\Scripts +SET EngineDir=%ScriptsDir%\Engine + +if not exist %ShaderDir% mkdir %ShaderDir% +if not exist %ScriptsDir% mkdir %ScriptsDir% +if not exist %EngineDir% mkdir %EngineDir% + +xcopy /D /Y /I /E "$(ProjectDir)Shaders\*.*" %ShaderDir% xcopy /Y "$(SolutionDir)Libs\bass\x86\*.dll" "$(TargetDir)" xcopy /Y "$(SolutionDir)Libs\lua\x86\*.dll" "$(TargetDir)" xcopy /Y "$(SolutionDir)Libs\ois\x86\*.dll" "$(TargetDir)" -xcopy /Y "$(SolutionDir)Libs\zlib\x86\*.dll" "$(TargetDir)" +xcopy /Y "$(SolutionDir)Libs\zlib\x86\*.dll" "$(TargetDir)" + +xcopy /D /Y /I /E "$(SolutionDir)Scripts\Engine\*.*" %EngineDir% +xcopy /D /Y "$(SolutionDir)Scripts\SystemStrings.lua" %ScriptsDir% + +if not exist "%ScriptsDir%\Gameflow.lua" xcopy /Y "$(SolutionDir)Scripts\Gameflow.lua" %ScriptsDir% +if not exist "%ScriptsDir%\Settings.lua" xcopy /Y "$(SolutionDir)Scripts\Settings.lua" %ScriptsDir% +if not exist "%ScriptsDir%\Strings.lua" xcopy /Y "$(SolutionDir)Scripts\Strings.lua" %ScriptsDir% Generating documentation, savegame flatbuffer and copying needed files... @@ -300,6 +391,7 @@ xcopy /Y "$(SolutionDir)Libs\zlib\x86\*.dll" "$(TargetDir)" /Zc:__cplusplus /experimental:external /external:anglebrackets 4018;4244;4996;%(DisableSpecificWarnings) true + $(IntDir)/%(RelativeDir)/ Console @@ -317,26 +409,43 @@ del "$(TargetDir)*.exp" /q del "$(TargetDir)OIS_d.dll" /q - CD $(ProjectDir)..\Documentation\ + CD "$(ProjectDir)..\Documentation\" CALL compile.bat . -CD $(ProjectDir)Specific\savegame\schema\ +CD "$(ProjectDir)Specific\savegame\schema\" CALL gen.bat -md "$(SolutionDir)Build\$(Configuration)\Shaders" -xcopy /Y /D "$(ProjectDir)Shaders\*.*" "$(SolutionDir)Build\$(Configuration)\Shaders\" -xcopy /Y /D "$(ProjectDir)Shaders\HUD\*.hlsl" "$(SolutionDir)Build\$(Configuration)\Shaders\HUD\" +SET BuildDir=$(SolutionDir)Build\$(Configuration) +SET ShaderDir=%BuildDir%\Shaders +SET ScriptsDir=%BuildDir%\Scripts +SET EngineDir=%ScriptsDir%\Engine + +if not exist %ShaderDir% mkdir %ShaderDir% +if not exist %ScriptsDir% mkdir %ScriptsDir% +if not exist %EngineDir% mkdir %EngineDir% + +xcopy /D /Y /I /E "$(ProjectDir)Shaders\*.*" %ShaderDir% xcopy /Y "$(SolutionDir)Libs\bass\x64\*.dll" "$(TargetDir)" xcopy /Y "$(SolutionDir)Libs\lua\x64\*.dll" "$(TargetDir)" xcopy /Y "$(SolutionDir)Libs\ois\x64\*.dll" "$(TargetDir)" -xcopy /Y "$(SolutionDir)Libs\zlib\x64\*.dll" "$(TargetDir)" +xcopy /Y "$(SolutionDir)Libs\zlib\x64\*.dll" "$(TargetDir)" + +xcopy /D /Y /I /E "$(SolutionDir)Scripts\Engine\*.*" %EngineDir% +xcopy /D /Y "$(SolutionDir)Scripts\SystemStrings.lua" %ScriptsDir% + +if not exist "%ScriptsDir%\Gameflow.lua" xcopy /Y "$(SolutionDir)Scripts\Gameflow.lua" %ScriptsDir% +if not exist "%ScriptsDir%\Settings.lua" xcopy /Y "$(SolutionDir)Scripts\Settings.lua" %ScriptsDir% +if not exist "%ScriptsDir%\Strings.lua" xcopy /Y "$(SolutionDir)Scripts\Strings.lua" %ScriptsDir% Generating documentation, savegame flatbuffer and copying needed files... + + + @@ -373,7 +482,6 @@ xcopy /Y "$(SolutionDir)Libs\zlib\x64\*.dll" "$(TargetDir)" - @@ -382,7 +490,6 @@ xcopy /Y "$(SolutionDir)Libs\zlib\x64\*.dll" "$(TargetDir)" - @@ -424,7 +531,6 @@ xcopy /Y "$(SolutionDir)Libs\zlib\x64\*.dll" "$(TargetDir)" - @@ -436,11 +542,13 @@ xcopy /Y "$(SolutionDir)Libs\zlib\x64\*.dll" "$(TargetDir)" + + @@ -480,18 +588,18 @@ xcopy /Y "$(SolutionDir)Libs\zlib\x64\*.dll" "$(TargetDir)" + - - + @@ -527,7 +635,9 @@ xcopy /Y "$(SolutionDir)Libs\zlib\x64\*.dll" "$(TargetDir)" + + @@ -539,7 +649,6 @@ xcopy /Y "$(SolutionDir)Libs\zlib\x64\*.dll" "$(TargetDir)" - @@ -628,9 +737,9 @@ xcopy /Y "$(SolutionDir)Libs\zlib\x64\*.dll" "$(TargetDir)" + - @@ -732,6 +841,7 @@ xcopy /Y "$(SolutionDir)Libs\zlib\x64\*.dll" "$(TargetDir)" + @@ -748,6 +858,7 @@ xcopy /Y "$(SolutionDir)Libs\zlib\x64\*.dll" "$(TargetDir)" + @@ -774,10 +885,12 @@ xcopy /Y "$(SolutionDir)Libs\zlib\x64\*.dll" "$(TargetDir)" + + @@ -857,7 +970,8 @@ xcopy /Y "$(SolutionDir)Libs\zlib\x64\*.dll" "$(TargetDir)" - + + @@ -865,7 +979,7 @@ xcopy /Y "$(SolutionDir)Libs\zlib\x64\*.dll" "$(TargetDir)" - + @@ -932,9 +1046,7 @@ xcopy /Y "$(SolutionDir)Libs\zlib\x64\*.dll" "$(TargetDir)" - - @@ -944,11 +1056,13 @@ xcopy /Y "$(SolutionDir)Libs\zlib\x64\*.dll" "$(TargetDir)" + + @@ -988,18 +1102,18 @@ xcopy /Y "$(SolutionDir)Libs\zlib\x64\*.dll" "$(TargetDir)" + - - + @@ -1033,7 +1147,9 @@ xcopy /Y "$(SolutionDir)Libs\zlib\x64\*.dll" "$(TargetDir)" + + @@ -1043,7 +1159,6 @@ xcopy /Y "$(SolutionDir)Libs\zlib\x64\*.dll" "$(TargetDir)" - @@ -1125,9 +1240,9 @@ xcopy /Y "$(SolutionDir)Libs\zlib\x64\*.dll" "$(TargetDir)" + - @@ -1204,10 +1319,12 @@ xcopy /Y "$(SolutionDir)Libs\zlib\x64\*.dll" "$(TargetDir)" + + diff --git a/TombEngine/framework.h b/TombEngine/framework.h index 9f5b27cb4..6740dadd0 100644 --- a/TombEngine/framework.h +++ b/TombEngine/framework.h @@ -18,15 +18,11 @@ #include #include -#include "Game/debug/debug.h" - using namespace DirectX; using namespace DirectX::SimpleMath; -#if __cplusplus >= 202002L -#define USE_FEATURE_IF_CPP20(x) x -#else -#define USE_FEATURE_IF_CPP20(x) -#endif +#include "Game/Debug/Debug.h" + +using namespace TEN::Debug; constexpr auto NO_VALUE = -1; diff --git a/pull_request_template.md b/pull_request_template.md index b4eb2e74e..9be755808 100644 --- a/pull_request_template.md +++ b/pull_request_template.md @@ -1,4 +1,4 @@ -## todo +## Checklist - [ ] I have added a changelog entry to CHANGELOG.md file on the branch/fork (if it is an internal change then it is not needed) - [ ] Pull request meets the Coding Conventions standards: https://github.com/MontyTRC89/TombEngine/blob/master/CONTRIBUTING.md#coding-conventions
    ColorAn RGBA or RGB color.Represents an RGBA or RGB color.
    Rotation