diff --git a/CHANGELOG.md b/CHANGELOG.md index 2cea2b24c..5f5683a87 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,7 @@ TombEngine releases are located in this repository (alongside with Tomb Editor): * Fixed Lara's Home entry not working. * Fixed exploding TR3 bosses. * Fixed original issue with deactivation of Dart Emitter. +* Fixed original issue with weapon hotkeys available in binoculars or lasersight mode. * Fixed Lens Flare object not functioning properly. * Fixed lens flares not being occluded by static meshes and moveables. * Fixed spotlight shadows. @@ -38,7 +39,9 @@ TombEngine releases are located in this repository (alongside with Tomb Editor): ### New Features * Added fast savegame reloading. +* Added dynamic shadow casting on objects and static meshes. * Added ricochet sounds and make the effect more prominent. +* Allow camera shake during flybys. * Allow to run the engine without title level. * Allow more than 1024 objects in a level. * Allow more than 1000 static mesh slots in a level. @@ -56,6 +59,7 @@ TombEngine releases are located in this repository (alongside with Tomb Editor): * Use load camera instead of load screen by playing fixed camera from OnEnd() event and removing loadScreenFile field from level's gameflow entry. * Fixed DisplayString class not supporting some Unicode characters and empty lines in multiline strings. * Fixed DisplayString not being deallocated after showing. +* Fixed GameVars not transferring between levels in hub mode. * Fixed incorrect behaviour of Moveable:GetJointRotation() function. * Fixed incorrect behaviour of Logic.EnableEvent() and Logic.DisableEvent() functions. * Fixed Util.HasLineOfSight() not taking static meshes into consideration. diff --git a/Scripts/Engine/Timer.lua b/Scripts/Engine/Timer.lua index 59ce2f3a3..a47bdc0f7 100644 --- a/Scripts/Engine/Timer.lua +++ b/Scripts/Engine/Timer.lua @@ -202,7 +202,7 @@ Timer = { str:SetColor(pausedColor) end - TEN.Strings.ShowString(str, 1) + TEN.Strings.ShowString(str, 1, false) end end diff --git a/TombEngine/Game/Lara/lara_helpers.cpp b/TombEngine/Game/Lara/lara_helpers.cpp index 48fee7e30..bf5b3a637 100644 --- a/TombEngine/Game/Lara/lara_helpers.cpp +++ b/TombEngine/Game/Lara/lara_helpers.cpp @@ -342,6 +342,10 @@ void HandlePlayerQuickActions(ItemInfo& item) g_Gui.UseItem(item, GAME_OBJECT_ID::ID_BIGMEDI_ITEM); } + // Don't process weapon hotkeys in optics mode. + if (player.Control.Look.IsUsingBinoculars) + return; + // Handle weapon scroll request. if (IsClicked(In::PreviousWeapon) || IsClicked(In::NextWeapon)) { @@ -888,7 +892,9 @@ void HandlePlayerFlyCheat(ItemInfo& item) { if (player.Context.Vehicle == NO_VALUE) { - GivePlayerItemsCheat(item); + if (KeyMap[OIS::KeyCode::KC_LSHIFT] || KeyMap[OIS::KeyCode::KC_RSHIFT]) + GivePlayerItemsCheat(item); + GivePlayerWeaponsCheat(item); if (player.Control.WaterStatus != WaterStatus::FlyCheat) diff --git a/TombEngine/Game/camera.cpp b/TombEngine/Game/camera.cpp index 1a79ecfc8..f1007e352 100644 --- a/TombEngine/Game/camera.cpp +++ b/TombEngine/Game/camera.cpp @@ -215,6 +215,38 @@ inline void RumbleFromBounce() Rumble(std::clamp((float)abs(Camera.bounce) / 70.0f, 0.0f, 0.8f), 0.2f); } +void CalculateBounce(bool binocularMode) +{ + if (Camera.bounce == 0) + return; + + if (Camera.bounce <= 0) + { + if (binocularMode) + { + Camera.target.x += (CLICK(0.25f) / 4) * (GetRandomControl() % (-Camera.bounce) - (-Camera.bounce / 2)); + Camera.target.y += (CLICK(0.25f) / 4) * (GetRandomControl() % (-Camera.bounce) - (-Camera.bounce / 2)); + Camera.target.z += (CLICK(0.25f) / 4) * (GetRandomControl() % (-Camera.bounce) - (-Camera.bounce / 2)); + } + else + { + int bounce = -Camera.bounce; + int bounce2 = bounce / 2; + Camera.target.x += GetRandomControl() % bounce - bounce2; + Camera.target.y += GetRandomControl() % bounce - bounce2; + Camera.target.z += GetRandomControl() % bounce - bounce2; + } + + Camera.bounce += 5; + RumbleFromBounce(); + } + else + { + Camera.pos.y += Camera.bounce; + Camera.target.y += Camera.bounce; + Camera.bounce = 0; + } +} void InitializeCamera() { @@ -313,25 +345,7 @@ void MoveCamera(GameVector* ideal, int speed) Camera.pos.z += (ideal->z - Camera.pos.z) / speed; Camera.pos.RoomNumber = ideal->RoomNumber; - if (Camera.bounce) - { - if (Camera.bounce <= 0) - { - int bounce = -Camera.bounce; - int bounce2 = bounce / 2; - Camera.target.x += GetRandomControl() % bounce - bounce2; - Camera.target.y += GetRandomControl() % bounce - bounce2; - Camera.target.z += GetRandomControl() % bounce - bounce2; - Camera.bounce += 5; - RumbleFromBounce(); - } - else - { - Camera.pos.y += Camera.bounce; - Camera.target.y += Camera.bounce; - Camera.bounce = 0; - } - } + CalculateBounce(false); int y = Camera.pos.y; if (TestEnvironment(ENV_FLAG_SWAMP, Camera.pos.RoomNumber)) @@ -1000,23 +1014,8 @@ void BinocularCamera(ItemInfo* item) Camera.target.RoomNumber = item->RoomNumber; } - if (Camera.bounce && - Camera.type == Camera.oldType) - { - if (Camera.bounce <= 0) - { - Camera.target.x += (CLICK(0.25f) / 4) * (GetRandomControl() % (-Camera.bounce) - (-Camera.bounce / 2)); - Camera.target.y += (CLICK(0.25f) / 4) * (GetRandomControl() % (-Camera.bounce) - (-Camera.bounce / 2)); - Camera.target.z += (CLICK(0.25f) / 4) * (GetRandomControl() % (-Camera.bounce) - (-Camera.bounce / 2)); - Camera.bounce += 5; - RumbleFromBounce(); - } - else - { - Camera.bounce = 0; - Camera.target.y += Camera.bounce; - } - } + if (Camera.type == Camera.oldType) + CalculateBounce(true); Camera.target.RoomNumber = GetPointCollision(Camera.pos.ToVector3i(), Camera.target.RoomNumber).GetRoomNumber(); LookAt(&Camera, 0); diff --git a/TombEngine/Game/camera.h b/TombEngine/Game/camera.h index 6a6939c94..f92703ec7 100644 --- a/TombEngine/Game/camera.h +++ b/TombEngine/Game/camera.h @@ -99,6 +99,7 @@ void BounceCamera(ItemInfo* item, short bounce, short maxDistance); void BinocularCamera(ItemInfo* item); void ConfirmCameraTargetPos(); void CalculateCamera(const CollisionInfo& coll); +void CalculateBounce(bool binocularMode); void RumbleScreen(); bool TestBoundsCollideCamera(const GameBoundingBox& bounds, const Pose& pose, short radius); void ItemPushCamera(GameBoundingBox* bounds, Pose* pos, short radius); diff --git a/TombEngine/Game/control/control.cpp b/TombEngine/Game/control/control.cpp index 9bb64a1c1..8615926e2 100644 --- a/TombEngine/Game/control/control.cpp +++ b/TombEngine/Game/control/control.cpp @@ -157,7 +157,7 @@ GameStatus GamePhase(bool insideMenu) // 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); + HandleAllGlobalEvents(EventType::Loop, (Activator)short(LaraItem->Index)); // Queued input actions are read again after OnLoop, so that remaining control loop can immediately register // emulated keypresses from the script. @@ -244,7 +244,7 @@ GameStatus GamePhase(bool insideMenu) // Call post-loop callbacks last time and end level. g_GameScript->OnLoop(DELTA_TIME, true); g_GameScript->OnEnd(gameStatus); - HandleAllGlobalEvents(EventType::End, (Activator)LaraItem->Index); + HandleAllGlobalEvents(EventType::End, (Activator)short(LaraItem->Index)); } else { @@ -299,7 +299,7 @@ GameStatus FreezePhase() // Poll controls and call scripting events. HandleControls(false); g_GameScript->OnFreeze(); - HandleAllGlobalEvents(EventType::Freeze, (Activator)LaraItem->Index); + HandleAllGlobalEvents(EventType::Freeze, (Activator)short(LaraItem->Index)); // Partially update scene if not using full freeze mode. if (g_GameFlow->LastFreezeMode != FreezeMode::Full) @@ -376,7 +376,6 @@ unsigned CALLBACK GameMain(void *) GameStatus DoLevel(int levelIndex, bool loadGame) { bool isTitle = !levelIndex; - auto loadType = loadGame ? LevelLoadType::Load : (SaveGame::IsOnHub(levelIndex) ? LevelLoadType::Hub : LevelLoadType::New); TENLog(isTitle ? "DoTitle" : "DoLevel", LogLevel::Info); @@ -392,7 +391,7 @@ GameStatus DoLevel(int levelIndex, bool loadGame) InitializeItemBoxData(); // Initialize scripting. - InitializeScripting(levelIndex, loadType); + InitializeScripting(levelIndex, loadGame); InitializeNodeScripts(); // Initialize menu and inventory state. @@ -540,12 +539,12 @@ void CleanUp() ClearObjCamera(); } -void InitializeScripting(int levelIndex, LevelLoadType type) +void InitializeScripting(int levelIndex, bool loadGame) { TENLog("Loading level script...", LogLevel::Info); g_GameStringsHandler->ClearDisplayStrings(); - g_GameScript->ResetScripts(!levelIndex || type != LevelLoadType::New); + g_GameScript->ResetScripts(!levelIndex || loadGame); const auto& level = *g_GameFlow->GetLevel(levelIndex); @@ -575,7 +574,7 @@ void InitializeScripting(int levelIndex, LevelLoadType type) } // Play default background music. - if (type != LevelLoadType::Load) + if (!loadGame) PlaySoundTrack(level.GetAmbientTrack(), SoundTrackType::BGM, 0, SOUND_XFADETIME_LEVELJUMP); } @@ -594,13 +593,19 @@ void InitializeOrLoadGame(bool loadGame) g_Gui.SetEnterInventory(NO_VALUE); // Restore game? - if (loadGame && SaveGame::Load(g_GameFlow->SelectedSaveGame)) + if (loadGame) { + if (!SaveGame::Load(g_GameFlow->SelectedSaveGame)) + { + NextLevel = g_GameFlow->GetNumLevels(); + return; + } + InitializeGame = false; g_GameFlow->SelectedSaveGame = 0; g_GameScript->OnLoad(); - HandleAllGlobalEvents(EventType::Load, (Activator)LaraItem->Index); + HandleAllGlobalEvents(EventType::Load, (Activator)short(LaraItem->Index)); } else { @@ -624,7 +629,7 @@ void InitializeOrLoadGame(bool loadGame) } g_GameScript->OnStart(); - HandleAllGlobalEvents(EventType::Start, (Activator)LaraItem->Index); + HandleAllGlobalEvents(EventType::Start, (Activator)short(LaraItem->Index)); } } diff --git a/TombEngine/Game/control/control.h b/TombEngine/Game/control/control.h index 1a49c42e1..b700595bb 100644 --- a/TombEngine/Game/control/control.h +++ b/TombEngine/Game/control/control.h @@ -34,13 +34,6 @@ enum class FreezeMode Player }; -enum class LevelLoadType -{ - New, - Hub, - Load -}; - enum CardinalDirection { NORTH, @@ -106,8 +99,8 @@ void UpdateShatters(); void CleanUp(); void InitializeOrLoadGame(bool loadGame); -void InitializeScripting(int levelIndex, LevelLoadType type); -void DeInitializeScripting(int levelIndex); +void InitializeScripting(int levelIndex, bool loadGame); +void DeInitializeScripting(int levelIndex, GameStatus reason); void SetupInterpolation(); diff --git a/TombEngine/Game/control/trigger.cpp b/TombEngine/Game/control/trigger.cpp index ac47e9d19..4c90da526 100644 --- a/TombEngine/Game/control/trigger.cpp +++ b/TombEngine/Game/control/trigger.cpp @@ -841,7 +841,7 @@ void TestTriggers(ItemInfo* item, bool isHeavy, int heavyFlags) short roomNumber = item->RoomNumber; auto floor = GetFloor(item->Pose.Position.x, item->Pose.Position.y, item->Pose.Position.z, &roomNumber); - TestTriggers(item->Pose.Position.x, item->Pose.Position.y, item->Pose.Position.z, floor, item->Index, isHeavy, heavyFlags); + TestTriggers(item->Pose.Position.x, item->Pose.Position.y, item->Pose.Position.z, floor, (Activator)short(item->Index), isHeavy, heavyFlags); } void TestTriggers(int x, int y, int z, short roomNumber, bool heavy, int heavyFlags) diff --git a/TombEngine/Game/gui.cpp b/TombEngine/Game/gui.cpp index ae6228b0f..39b480bb2 100644 --- a/TombEngine/Game/gui.cpp +++ b/TombEngine/Game/gui.cpp @@ -2040,7 +2040,7 @@ namespace TEN::Gui // Use item event handling. g_GameScript->OnUseItem((GAME_OBJECT_ID)InventoryItemChosen); - HandleAllGlobalEvents(EventType::UseItem, (Activator)item.Index); + HandleAllGlobalEvents(EventType::UseItem, (Activator)short(item.Index)); // Quickly discard further processing if chosen item was reset in script. if (InventoryItemChosen == NO_VALUE) diff --git a/TombEngine/Game/savegame.cpp b/TombEngine/Game/savegame.cpp index 872beacc7..5d3d899cd 100644 --- a/TombEngine/Game/savegame.cpp +++ b/TombEngine/Game/savegame.cpp @@ -206,6 +206,19 @@ bool SaveGame::DoesSaveGameExist(int slot, bool silent) return true; } +bool SaveGame::IsSaveGameValid(int slot) +{ + SaveGameHeader header; + if (!LoadHeader(slot, &header)) + return false; + + // Hash mismatch between savegame and level file means that level version has changed. + if (header.LevelHash != LastLevelHash) + return false; + + return true; +} + bool SaveGame::IsLoadGamePossible() { for (int i = 0; i < SAVEGAME_MAX; i++) @@ -1574,7 +1587,7 @@ bool SaveGame::Save(int slot) return false; g_GameScript->OnSave(); - HandleAllGlobalEvents(EventType::Save, (Activator)LaraItem->Index); + HandleAllGlobalEvents(EventType::Save, (Activator)short(LaraItem->Index)); // Savegame infos need to be reloaded so that last savegame counter properly increases. LoadHeaders(); @@ -1614,15 +1627,9 @@ bool SaveGame::Save(int slot) bool SaveGame::Load(int slot) { - if (!IsSaveGameSlotValid(slot)) + if (!IsSaveGameValid(slot)) { - TENLog("Savegame slot " + std::to_string(slot) + " is invalid, load is impossible.", LogLevel::Error); - return false; - } - - if (!DoesSaveGameExist(slot)) - { - TENLog("Savegame in slot " + std::to_string(slot) + " does not exist.", LogLevel::Error); + TENLog("Loading from savegame in slot " + std::to_string(slot) + " is impossible, data is missing or level has changed.", LogLevel::Error); return false; } @@ -1704,7 +1711,7 @@ static void ParseStatistics(const Save::SaveGame* s, bool isHub) SaveGame::Statistics.Game.Timer = s->game()->timer(); } -static void ParseLua(const Save::SaveGame* s) +static void ParseLua(const Save::SaveGame* s, bool hubMode) { // Event sets @@ -1808,7 +1815,7 @@ static void ParseLua(const Save::SaveGame* s) } } - g_GameScript->SetVariables(loadedVars); + g_GameScript->SetVariables(loadedVars, hubMode); auto populateCallbackVecs = [&s](auto callbackFunc) { @@ -2672,7 +2679,7 @@ void SaveGame::Parse(const std::vector& buffer, bool hubMode) const Save::SaveGame* s = Save::GetSaveGame(buffer.data()); ParseLevel(s, hubMode); - ParseLua(s); + ParseLua(s, hubMode); ParseStatistics(s, hubMode); // Effects and player data is ignored when loading hub. diff --git a/TombEngine/Game/savegame.h b/TombEngine/Game/savegame.h index 3b09ac92d..067391a93 100644 --- a/TombEngine/Game/savegame.h +++ b/TombEngine/Game/savegame.h @@ -66,6 +66,7 @@ public: static bool DoesSaveGameExist(int slot, bool silent = false); static bool IsLoadGamePossible(); + static bool IsSaveGameValid(int slot); static void SaveHub(int index); static void LoadHub(int index); diff --git a/TombEngine/Game/spotcam.cpp b/TombEngine/Game/spotcam.cpp index e39ba51a0..2d2b3bc54 100644 --- a/TombEngine/Game/spotcam.cpp +++ b/TombEngine/Game/spotcam.cpp @@ -494,6 +494,7 @@ void CalculateSpotCameras() Camera.target.x = ctx; Camera.target.y = cty; Camera.target.z = ctz; + CalculateBounce(false); } int outsideRoom = IsRoomOutside(cpx, cpy, cpz); diff --git a/TombEngine/Renderer/Renderer.cpp b/TombEngine/Renderer/Renderer.cpp index f55770a2e..0db0ed2d0 100644 --- a/TombEngine/Renderer/Renderer.cpp +++ b/TombEngine/Renderer/Renderer.cpp @@ -201,43 +201,62 @@ namespace TEN::Renderer _context->PSSetSamplers((UINT)registerType, 1, &samplerState); } - void Renderer::BindLight(RendererLight& light, ShaderLight* lights, int index) + int Renderer::BindLight(RendererLight& light, ShaderLight* lights, int index) { memcpy(&lights[index], &light, sizeof(ShaderLight)); - if (light.Hash == 0) - return; + // Precalculate ranges so that it's not recalculated in shader for every pixel. + if (light.Type == LightType::Spot) + { + lights[index].InRange = cos(light.InRange * (PI / 180.0f)); + lights[index].OutRange = cos(light.OutRange * (PI / 180.0f)); + } - lights[index].Position = Vector3::Lerp(light.PrevPosition, light.Position, GetInterpolationFactor()); - lights[index].Direction = Vector3::Lerp(light.PrevDirection, light.Direction, GetInterpolationFactor()); + // If light has hash, interpolate its position with previous position. + if (light.Hash != 0) + { + lights[index].Position = Vector3::Lerp(light.PrevPosition, light.Position, GetInterpolationFactor()); + lights[index].Direction = Vector3::Lerp(light.PrevDirection, light.Direction, GetInterpolationFactor()); + } + + // Bitmask light type to filter it in the shader later. + return (1 << (31 - (int)light.Type)); } void Renderer::BindRoomLights(std::vector& lights) { + int lightTypeMask = 0; + for (int i = 0; i < lights.size(); i++) - BindLight(*lights[i], _stRoom.RoomLights, i); + lightTypeMask = lightTypeMask | BindLight(*lights[i], _stRoom.RoomLights, i); - _stRoom.NumRoomLights = (int)lights.size(); + _stRoom.NumRoomLights = (int)lights.size() | lightTypeMask; } void Renderer::BindStaticLights(std::vector& lights) { + int lightTypeMask = 0; + for (int i = 0; i < lights.size(); i++) - BindLight(*lights[i], _stStatic.Lights, i); + lightTypeMask = lightTypeMask | BindLight(*lights[i], _stStatic.Lights, i); - _stStatic.NumLights = (int)lights.size(); + _stStatic.NumLights = (int)lights.size() | lightTypeMask; } void Renderer::BindInstancedStaticLights(std::vector& lights, int instanceID) { - for (int i = 0; i < lights.size(); i++) - BindLight(*lights[i], _stInstancedStaticMeshBuffer.StaticMeshes[instanceID].Lights, i); + int lightTypeMask = 0; - _stInstancedStaticMeshBuffer.StaticMeshes[instanceID].NumLights = (int)lights.size(); + for (int i = 0; i < lights.size(); i++) + lightTypeMask = lightTypeMask | BindLight(*lights[i], _stInstancedStaticMeshBuffer.StaticMeshes[instanceID].Lights, i); + + _stInstancedStaticMeshBuffer.StaticMeshes[instanceID].NumLights = (int)lights.size() | lightTypeMask; } void Renderer::BindMoveableLights(std::vector& lights, int roomNumber, int prevRoomNumber, float fade) { + int lightTypeMask = 0; + int numLights = 0; for (int i = 0; i < lights.size(); i++) { @@ -257,12 +276,12 @@ namespace TEN::Renderer if (fadedCoeff == 0.0f) continue; - BindLight(*lights[i], _stItem.Lights, numLights); + lightTypeMask = lightTypeMask | BindLight(*lights[i], _stItem.Lights, numLights); _stItem.Lights[numLights].Intensity *= fadedCoeff; numLights++; } - _stItem.NumLights = numLights; + _stItem.NumLights = numLights | lightTypeMask; } void Renderer::BindConstantBufferVS(ConstantBufferRegister constantBufferType, ID3D11Buffer** buffer) diff --git a/TombEngine/Renderer/Renderer.h b/TombEngine/Renderer/Renderer.h index 6e733a52f..3fb38bff1 100644 --- a/TombEngine/Renderer/Renderer.h +++ b/TombEngine/Renderer/Renderer.h @@ -418,7 +418,7 @@ namespace TEN::Renderer void ApplySMAA(RenderTarget2D* renderTarget, RenderView& view); void ApplyFXAA(RenderTarget2D* renderTarget, RenderView& view); void BindTexture(TextureRegister registerType, TextureBase* texture, SamplerStateRegister samplerType); - void BindLight(RendererLight& light, ShaderLight* lights, int index); + int BindLight(RendererLight& light, ShaderLight* lights, int index); void BindRoomLights(std::vector& lights); void BindStaticLights(std::vector& lights); void BindInstancedStaticLights(std::vector& lights, int instanceID); diff --git a/TombEngine/Renderer/RendererDraw.cpp b/TombEngine/Renderer/RendererDraw.cpp index 3cc21fec1..81bf29a01 100644 --- a/TombEngine/Renderer/RendererDraw.cpp +++ b/TombEngine/Renderer/RendererDraw.cpp @@ -2653,7 +2653,7 @@ namespace TEN::Renderer // Set shadow map data and bind shadow map texture. if (_shadowLight != nullptr) { - memcpy(&_stShadowMap.Light, _shadowLight, sizeof(ShaderLight)); + BindLight(*_shadowLight, &_stShadowMap.Light, 0); _stShadowMap.ShadowMapSize = g_Configuration.ShadowMapSize; _stShadowMap.CastShadows = true; diff --git a/TombEngine/Renderer/RendererFrame.cpp b/TombEngine/Renderer/RendererFrame.cpp index 696c8ed81..1af738a45 100644 --- a/TombEngine/Renderer/RendererFrame.cpp +++ b/TombEngine/Renderer/RendererFrame.cpp @@ -655,7 +655,7 @@ namespace TEN::Renderer std::vector lightsToDraw; CollectLights(Vector3(Camera.pos.x, Camera.pos.y, Camera.pos.z), CAMERA_LIGHT_COLLECTION_RADIUS, Camera.pos.RoomNumber, NO_VALUE, true, false, nullptr, &lightsToDraw); - if (!lightsToDraw.empty() && lightsToDraw.front()->CastShadows) + if (g_Configuration.ShadowType != ShadowMode::None && !lightsToDraw.empty() && lightsToDraw.front()->CastShadows) { _shadowLight = lightsToDraw.front(); } diff --git a/TombEngine/Scripting/Include/ScriptInterfaceGame.h b/TombEngine/Scripting/Include/ScriptInterfaceGame.h index 75510183a..7fd0d0025 100644 --- a/TombEngine/Scripting/Include/ScriptInterfaceGame.h +++ b/TombEngine/Scripting/Include/ScriptInterfaceGame.h @@ -69,7 +69,7 @@ public: virtual void ExecuteFunction(const std::string& luaFuncName, short idOne, short idTwo = 0) = 0; virtual void GetVariables(std::vector& vars) = 0; - virtual void SetVariables(const std::vector& vars) = 0; + virtual void SetVariables(const std::vector& vars, bool onlyLevelVars) = 0; virtual void GetCallbackStrings( std::vector& preStart, diff --git a/TombEngine/Scripting/Internal/TEN/Logic/LogicHandler.cpp b/TombEngine/Scripting/Internal/TEN/Logic/LogicHandler.cpp index bda6d6213..d34f7c21e 100644 --- a/TombEngine/Scripting/Internal/TEN/Logic/LogicHandler.cpp +++ b/TombEngine/Scripting/Internal/TEN/Logic/LogicHandler.cpp @@ -323,7 +323,7 @@ Possible event type values: */ void LogicHandler::HandleEvent(const std::string& name, EventType type, sol::optional activator) { - TEN::Control::Volumes::HandleEvent(name, type, activator.has_value() ? (Activator)activator.value().GetIndex() : (Activator)LaraItem->Index); + TEN::Control::Volumes::HandleEvent(name, type, activator.has_value() ? (Activator)activator.value().GetIndex() : (Activator)short(LaraItem->Index)); } /*** Attempt to find an event set and enable specified event in it. @@ -485,9 +485,11 @@ void LogicHandler::FreeLevelScripts() } // Used when loading. -void LogicHandler::SetVariables(const std::vector& vars) +void LogicHandler::SetVariables(const std::vector& vars, bool onlyLevelVars) { - ResetGameTables(); + if (!onlyLevelVars) + ResetGameTables(); + ResetLevelTables(); std::unordered_map solTables; @@ -565,6 +567,9 @@ void LogicHandler::SetVariables(const std::vector& vars) for (auto& [first, second] : levelVars) (*m_handler.GetState())[ScriptReserved_LevelVars][first] = second; + if (onlyLevelVars) + return; + sol::table gameVars = rootTable[ScriptReserved_GameVars]; for (auto& [first, second] : gameVars) (*m_handler.GetState())[ScriptReserved_GameVars][first] = second; diff --git a/TombEngine/Scripting/Internal/TEN/Logic/LogicHandler.h b/TombEngine/Scripting/Internal/TEN/Logic/LogicHandler.h index 51fc08a40..556564d75 100644 --- a/TombEngine/Scripting/Internal/TEN/Logic/LogicHandler.h +++ b/TombEngine/Scripting/Internal/TEN/Logic/LogicHandler.h @@ -145,7 +145,7 @@ public: void ExecuteFunction(const std::string& name, short idOne, short idTwo) override; void GetVariables(std::vector& vars) override; - void SetVariables(const std::vector& vars) override; + void SetVariables(const std::vector& vars, bool onlyLevelVars) override; void ResetVariables(); void SetCallbackStrings(const std::vector& preStart, diff --git a/TombEngine/Shaders/Blending.hlsli b/TombEngine/Shaders/Blending.hlsli index e84225083..8433d9de7 100644 --- a/TombEngine/Shaders/Blending.hlsli +++ b/TombEngine/Shaders/Blending.hlsli @@ -7,7 +7,7 @@ #define ALPHATEST_GREATER_THAN 1 #define ALPHATEST_LESS_THAN 2 -#define BLENDMODE_OPAQUE 0, +#define BLENDMODE_OPAQUE 0 #define BLENDMODE_ALPHATEST 1 #define BLENDMODE_ADDITIVE 2 #define BLENDMODE_NOZTEST 4 diff --git a/TombEngine/Shaders/InstancedStatics.fx b/TombEngine/Shaders/InstancedStatics.fx index 627a1e912..cc421cbab 100644 --- a/TombEngine/Shaders/InstancedStatics.fx +++ b/TombEngine/Shaders/InstancedStatics.fx @@ -4,6 +4,7 @@ #include "./VertexEffects.hlsli" #include "./VertexInput.hlsli" #include "./Blending.hlsli" +#include "./Shadows.hlsli" #define INSTANCED_STATIC_MESH_BUCKET_SIZE 100 @@ -124,6 +125,8 @@ PixelShaderOutput PS(PixelShaderInput input) input.FogBulbs.w) : StaticLight(input.Color.xyz, tex.xyz, input.FogBulbs.w); + color = DoShadow(input.WorldPosition, normal, color, -0.5f); + output.Color = float4(color * occlusion, tex.w); output.Color = DoFogBulbsForPixel(output.Color, float4(input.FogBulbs.xyz, 1.0f)); output.Color = DoDistanceFogForPixel(output.Color, FogColor, input.DistanceFog); diff --git a/TombEngine/Shaders/Items.fx b/TombEngine/Shaders/Items.fx index 5c6d0fff6..1b493ae65 100644 --- a/TombEngine/Shaders/Items.fx +++ b/TombEngine/Shaders/Items.fx @@ -5,7 +5,7 @@ #include "./VertexInput.hlsli" #include "./Blending.hlsli" #include "./AnimatedTextures.hlsli" -//#include "./Shadows.hlsli" +#include "./Shadows.hlsli" #define MAX_BONES 32 @@ -155,6 +155,8 @@ PixelShaderOutput PS(PixelShaderInput input) input.FogBulbs.w) : StaticLight(input.Color.xyz, tex.xyz, input.FogBulbs.w); + color = DoShadow(input.WorldPosition, normal, color, -0.5f); + output.Color = saturate(float4(color * occlusion, tex.w)); output.Color = DoFogBulbsForPixel(output.Color, float4(input.FogBulbs.xyz, 1.0f)); output.Color = DoDistanceFogForPixel(output.Color, FogColor, input.DistanceFog); diff --git a/TombEngine/Shaders/Math.hlsli b/TombEngine/Shaders/Math.hlsli index a34584569..b82033c57 100644 --- a/TombEngine/Shaders/Math.hlsli +++ b/TombEngine/Shaders/Math.hlsli @@ -3,12 +3,19 @@ #define PI 3.1415926535897932384626433832795028841971693993751058209749445923 #define PI2 6.2831853071795864769252867665590057683943387987502116419498891846 +#define EPSILON 1e-38 #define OCTAVES 6 -#define LT_SUN 0 -#define LT_POINT 1 -#define LT_SPOT 2 -#define LT_SHADOW 3 +#define LT_SUN 0 +#define LT_POINT 1 +#define LT_SPOT 2 +#define LT_SHADOW 3 + +#define LT_MASK 0xFFFF +#define LT_MASK_SUN (1 << (31 - LT_SUN)) +#define LT_MASK_POINT (1 << (31 - LT_POINT)) +#define LT_MASK_SPOT (1 << (31 - LT_SPOT)) +#define LT_MASK_SHADOW (1 << (31 - LT_SHADOW)) #define MAX_LIGHTS_PER_ROOM 48 #define MAX_LIGHTS_PER_ITEM 8 diff --git a/TombEngine/Shaders/Rooms.fx b/TombEngine/Shaders/Rooms.fx index afb02de4c..1ed9a57ba 100644 --- a/TombEngine/Shaders/Rooms.fx +++ b/TombEngine/Shaders/Rooms.fx @@ -7,6 +7,8 @@ #include "./Shadows.hlsli" #include "./ShaderLight.hlsli" +#define ROOM_LIGHT_COEFF 0.7f + cbuffer RoomBuffer : register(b5) { int Water; @@ -141,28 +143,31 @@ PixelShaderOutput PS(PixelShaderInput input) occlusion = pow(SSAOTexture.Sample(SSAOSampler, samplePosition).x, AmbientOcclusionExponent); } - if (CastShadows) + lighting = DoShadow(input.WorldPosition, normal, lighting, -2.5f); + lighting = DoBlobShadows(input.WorldPosition, lighting); + + bool onlyPointLights = (NumRoomLights & ~LT_MASK) == LT_MASK_POINT; + int numLights = NumRoomLights & LT_MASK; + + for (int i = 0; i < numLights; i++) { - float isPointLight = step(0.5f, Light.Type == LT_POINT); // 1.0 if LT_POINT, 0.0 otherwise - float isSpotLight = step(0.5f, Light.Type == LT_SPOT); // 1.0 if LT_SPOT, 0.0 otherwise - float isOtherLight = 1.0 - (isPointLight + isSpotLight); // 1.0 if neither LT_POINT nor LT_SPOT - - float3 pointLightShadow = DoPointLightShadow(input.WorldPosition, lighting); - float3 spotLightShadow = DoSpotLightShadow(input.WorldPosition, normal, lighting); - - // Blend the shadows based on the light type - lighting = pointLightShadow * isPointLight + spotLightShadow * isSpotLight + lighting * isOtherLight; - } + if (onlyPointLights) + { + lighting += DoPointLight(input.WorldPosition, normal, RoomLights[i]) * ROOM_LIGHT_COEFF; + } + else + { + // Room dynamic lights can only be spot or point, so we use simplified function for that. - lighting = DoBlobShadows(input.WorldPosition, lighting); + float isPoint = step(0.5f, RoomLights[i].Type == LT_POINT); + float isSpot = step(0.5f, RoomLights[i].Type == LT_SPOT); - for (int i = 0; i < NumRoomLights; i++) - { - float isPointLightRoom = step(0.5f, RoomLights[i].Type == LT_POINT); - float isSpotLightRoom = step(0.5f, RoomLights[i].Type == LT_SPOT); - - lighting += DoPointLight(input.WorldPosition, normal, RoomLights[i]) * isPointLightRoom; - lighting += DoSpotLight(input.WorldPosition, normal, RoomLights[i]) * isSpotLightRoom; + float3 pointLight = float3(0.0f, 0.0f, 0.0f); + float3 spotLight = float3(0.0f, 0.0f, 0.0f); + DoPointAndSpotLight(input.WorldPosition, normal, RoomLights[i], pointLight, spotLight); + + lighting += pointLight * isPoint * ROOM_LIGHT_COEFF + spotLight * isSpot * ROOM_LIGHT_COEFF; + } } if (Caustics) diff --git a/TombEngine/Shaders/ShaderLight.hlsli b/TombEngine/Shaders/ShaderLight.hlsli index 4a837cad9..ca5992849 100644 --- a/TombEngine/Shaders/ShaderLight.hlsli +++ b/TombEngine/Shaders/ShaderLight.hlsli @@ -129,21 +129,30 @@ float3 DoSpotLight(float3 pos, float3 normal, ShaderLight light) float3 lightVec = pos - light.Position.xyz; float distance = length(lightVec); float3 lightDir = normalize(lightVec); - float cosine = dot(lightDir, light.Direction.xyz); - // Angle attenuation - float coneInCos = cos(light.InRange * (PI / 180.0f)); - float coneOutCos = cos(light.OutRange * (PI / 180.0f)); - float angleAttenuation = saturate((cosine - coneOutCos) / (coneInCos - coneOutCos)); - - // Distance attenuation + float cosine = dot(lightDir, light.Direction.xyz); + float angleAttenuation = saturate((cosine - light.OutRange) / (light.InRange - light.OutRange)); float distanceAttenuation = saturate((light.Out - distance) / (light.Out - light.In)); - // Surface lighting float d = saturate(dot(normal, -lightDir)); return saturate(light.Color.xyz * light.Intensity * angleAttenuation * distanceAttenuation * d); } +void DoPointAndSpotLight(float3 pos, float3 normal, ShaderLight light, out float3 pointOutput, out float3 spotOutput) +{ + float3 lightVec = light.Position.xyz - pos; + float distance = length(lightVec); + float3 lightDir = normalize(lightVec); + + float cosine = dot(-lightDir, light.Direction.xyz); + float distanceAttenuation = saturate((light.Out - distance) / (light.Out - light.In)); + float angleAttenuation = saturate((cosine - light.OutRange) / (light.InRange - light.OutRange)); + + float d = saturate(dot(normal, lightDir)); + pointOutput = saturate(light.Color.xyz * light.Intensity * distanceAttenuation * d); + spotOutput = saturate(light.Color.xyz * light.Intensity * angleAttenuation * distanceAttenuation * d); +} + float3 DoDirectionalLight(float3 pos, float3 normal, ShaderLight light) { float d = saturate(dot(-light.Direction.xyz, normal)); @@ -310,12 +319,13 @@ float DoFogBulbForSky(float3 pos, ShaderFogBulb bulb) float DoDistanceFogForVertex(float3 pos) { - float d = length(CamPositionWS.xyz - pos); - float fogRange = FogMaxDistance * 1024 - FogMinDistance * 1024; + float fog = 0.0f; - float fogEnabled = step(0.0f, FogMaxDistance); - float fogFactor = (d - FogMinDistance * 1024) / fogRange; - float fog = saturate(fogFactor) * fogEnabled; + if (FogMaxDistance > 0.0f) + { + float d = length(CamPositionWS.xyz - pos); + fog = clamp((d - FogMinDistance * 1024) / (FogMaxDistance * 1024 - FogMinDistance * 1024), 0, 1); + } return fog; } @@ -363,23 +373,37 @@ float3 CombineLights(float3 ambient, float3 vertex, float3 tex, float3 pos, floa float3 shadow = 0; float3 spec = 0; + int lightTypeMask = (numLights & ~LT_MASK); + numLights = numLights & LT_MASK; + for (int i = 0; i < numLights; i++) { - float isPoint = step(0.5f, float(lights[i].Type == LT_POINT)); - float isSpot = step(0.5f, float(lights[i].Type == LT_SPOT)); - float isSun = step(0.5f, float(lights[i].Type == LT_SUN)); - float isShadow = step(0.5f, float(lights[i].Type == LT_SHADOW)); + if (lightTypeMask & LT_MASK_SUN) + { + float isSun = step(0.5f, float(lights[i].Type == LT_SUN)); + diffuse += isSun * DoDirectionalLight(pos, normal, lights[i]); + spec += isSun * DoSpecularSun(normal, lights[i], sheen); + } - diffuse += isPoint * DoPointLight(pos, normal, lights[i]); - spec += isPoint * DoSpecularPoint(pos, normal, lights[i], sheen); + if (lightTypeMask & LT_MASK_POINT) + { + float isPoint = step(0.5f, float(lights[i].Type == LT_POINT)); + diffuse += isPoint * DoPointLight(pos, normal, lights[i]); + spec += isPoint * DoSpecularPoint(pos, normal, lights[i], sheen); + } - diffuse += isSpot * DoSpotLight(pos, normal, lights[i]); - spec += isSpot * DoSpecularSpot(pos, normal, lights[i], sheen); - - diffuse += isSun * DoDirectionalLight(pos, normal, lights[i]); - spec += isSun * DoSpecularSun(normal, lights[i], sheen); - - shadow += isShadow * DoShadowLight(pos, normal, lights[i]); + if (lightTypeMask & LT_MASK_SPOT) + { + float isSpot = step(0.5f, float(lights[i].Type == LT_SPOT)); + diffuse += isSpot * DoSpotLight(pos, normal, lights[i]); + spec += isSpot * DoSpecularSpot(pos, normal, lights[i], sheen); + } + + if (lightTypeMask & LT_MASK_SHADOW) + { + float isShadow = step(0.5f, float(lights[i].Type == LT_SHADOW)); + shadow += isShadow * DoShadowLight(pos, normal, lights[i]); + } } shadow = saturate(shadow); diff --git a/TombEngine/Shaders/Shadows.hlsli b/TombEngine/Shaders/Shadows.hlsli index dfdcbab6a..8d825d8b5 100644 --- a/TombEngine/Shaders/Shadows.hlsli +++ b/TombEngine/Shaders/Shadows.hlsli @@ -1,3 +1,5 @@ +#include "./Blending.hlsli" +#include "./Math.hlsli" #include "./ShaderLight.hlsli" #define SHADOW_INTENSITY (0.55f) @@ -86,20 +88,32 @@ float3 DoBlobShadows(float3 worldPos, float3 lighting) return lighting * saturate(shadowFactor + SHADOW_INTENSITY); } -float3 DoPointLightShadow(float3 worldPos, float3 lighting) +float3 DoShadow(float3 worldPos, float3 normal, float3 lighting, float bias) { + if (!CastShadows) + return lighting; + + if (BlendMode != BLENDMODE_OPAQUE && BlendMode != BLENDMODE_ALPHATEST && BlendMode != BLENDMODE_ALPHABLEND) + return lighting; + float shadowFactor = 1.0f; - + + float3 dir = normalize(Light.Position - worldPos); + float ndot = dot(normal, dir); + float facingFactor = saturate((ndot - bias) / (1.0f - bias + EPSILON)); + [unroll] for (int i = 0; i < 6; i++) { - float3 dir = normalize(worldPos - Light.Position); float4 lightClipSpace = mul(float4(worldPos, 1.0f), LightViewProjections[i]); lightClipSpace.xyz /= lightClipSpace.w; - if (lightClipSpace.x >= -1.0f && lightClipSpace.x <= 1.0f && - lightClipSpace.y >= -1.0f && lightClipSpace.y <= 1.0f && - lightClipSpace.z >= 0.0f && lightClipSpace.z <= 1.0f) + float insideLightBounds = + step(-1.0f, lightClipSpace.x) * step(lightClipSpace.x, 1.0f) * + step(-1.0f, lightClipSpace.y) * step(lightClipSpace.y, 1.0f) * + step( 0.0f, lightClipSpace.z) * step(lightClipSpace.z, 1.0f); + + if (insideLightBounds > 0.0f) { lightClipSpace.x = lightClipSpace.x / 2 + 0.5; lightClipSpace.y = lightClipSpace.y / -2 + 0.5; @@ -108,63 +122,29 @@ float3 DoPointLightShadow(float3 worldPos, float3 lighting) float x, y; // Perform PCF filtering on a 4 x 4 texel neighborhood. - [unroll] + [unroll] for (y = -1.5; y <= 1.5; y += 1.0) { - [unroll] + [unroll] for (x = -1.5; x <= 1.5; x += 1.0) { sum += ShadowMap.SampleCmpLevelZero(ShadowMapSampler, float3(lightClipSpace.xy + TexOffset(x, y), i), lightClipSpace.z); } } - shadowFactor = sum / 16.0; + shadowFactor = lerp(shadowFactor, sum / 16.0, facingFactor * insideLightBounds); } } - - // Compute attenuation and combine lighting contribution with shadow factor - float distanceFactor = saturate(((distance(worldPos, Light.Position)) / (Light.Out))); - return lighting * saturate((shadowFactor + SHADOW_INTENSITY) + (pow(distanceFactor, 4) * INV_SHADOW_INTENSITY)); -} -float3 DoSpotLightShadow(float3 worldPos, float3 normal, float3 lighting) -{ - float influence = 1.0f - Luma(DoSpotLight(worldPos, normal, Light)); - - float shadowFactor = 1.0f; - - [unroll] - for (int i = 0; i < 6; i++) - { - float3 dir = normalize(worldPos - Light.Position); - float4 lightClipSpace = mul(float4(worldPos, 1.0f), LightViewProjections[i]); - lightClipSpace.xyz /= lightClipSpace.w; + float isPoint = step(0.5f, Light.Type == LT_POINT); // 1.0 if LT_POINT, 0.0 otherwise + float isSpot = step(0.5f, Light.Type == LT_SPOT); // 1.0 if LT_SPOT, 0.0 otherwise + float isOther = 1.0 - (isPoint + isSpot); // 1.0 if neither LT_POINT nor LT_SPOT - if (lightClipSpace.x >= -1.0f && lightClipSpace.x <= 1.0f && - lightClipSpace.y >= -1.0f && lightClipSpace.y <= 1.0f && - lightClipSpace.z >= 0.0f && lightClipSpace.z <= 1.0f) - { - lightClipSpace.x = lightClipSpace.x / 2 + 0.5; - lightClipSpace.y = lightClipSpace.y / -2 + 0.5; + float pointFactor = 1.0f - Luma(DoPointLight(worldPos, normal, Light)); + float spotFactor = 1.0f - Luma(DoSpotLight(worldPos, normal, Light)); - float sum = 0; - float x, y; + float3 pointShadow = lighting * saturate((shadowFactor + SHADOW_INTENSITY) + (pow(pointFactor, 4) * INV_SHADOW_INTENSITY)); + float3 spotShadow = lighting * saturate((shadowFactor + SHADOW_INTENSITY) + (pow(spotFactor, 4) * INV_SHADOW_INTENSITY)); - // Perform PCF filtering on a 4 x 4 texel neighborhood. - [unroll] - for (y = -1.5; y <= 1.5; y += 1.0) - { - [unroll] - for (x = -1.5; x <= 1.5; x += 1.0) - { - sum += ShadowMap.SampleCmpLevelZero(ShadowMapSampler, float3(lightClipSpace.xy + TexOffset(x, y), i), lightClipSpace.z); - } - } - - shadowFactor = sum / 16.0; - } - } - - // Compute attenuation and combine lighting contribution with shadow factor - return lighting * saturate((shadowFactor + SHADOW_INTENSITY) + (pow(influence, 4) * INV_SHADOW_INTENSITY)); -} + return pointShadow * isPoint + spotShadow * isSpot + lighting * isOther; +} \ No newline at end of file diff --git a/TombEngine/Shaders/Statics.fx b/TombEngine/Shaders/Statics.fx index 9718c8681..595b6d2c8 100644 --- a/TombEngine/Shaders/Statics.fx +++ b/TombEngine/Shaders/Statics.fx @@ -5,6 +5,7 @@ #include "./VertexInput.hlsli" #include "./Blending.hlsli" #include "./CBStatic.hlsli" +#include "./Shadows.hlsli" struct PixelShaderInput { @@ -92,6 +93,8 @@ PixelShaderOutput PS(PixelShaderInput input) input.FogBulbs.w) : StaticLight(input.Color.xyz, tex.xyz, input.FogBulbs.w); + color = DoShadow(input.WorldPosition, normal, color, -0.5f); + output.Color = float4(color, tex.w); output.Color = DoFogBulbsForPixel(output.Color, float4(input.FogBulbs.xyz, 1.0f)); output.Color = DoDistanceFogForPixel(output.Color, FogColor, input.DistanceFog); diff --git a/TombEngine/Shaders/VertexEffects.hlsli b/TombEngine/Shaders/VertexEffects.hlsli index cb2c3b167..b2ced884e 100644 --- a/TombEngine/Shaders/VertexEffects.hlsli +++ b/TombEngine/Shaders/VertexEffects.hlsli @@ -4,34 +4,26 @@ float Wibble(float3 effect, int hash) { - if (effect.x > 0.0f || effect.y > 0.0f) - return sin((((Frame + hash) % 256) / WIBBLE_FRAME_PERIOD) * (PI2)); - else - return 0.0f; // Don't calculate if not necessary + float shouldWibble = step(0.0f, effect.x + effect.y); + float wibble = sin((((Frame + hash) % 256) / WIBBLE_FRAME_PERIOD) * PI2); + + return wibble * shouldWibble; } float3 Glow(float3 color, float3 effect, float wibble) { - float3 col = color; + float shouldGlow = step(0.0f, effect.x); + float intensity = effect.x * lerp(-0.5f, 1.0f, wibble * 0.5f + 0.5f); + float3 glowEffect = float3(intensity, intensity, intensity) * shouldGlow; - if (effect.x > 0.0f) - { - float intensity = effect.x * lerp(-0.5f, 1.0f, wibble * 0.5f + 0.5f); - col = color + float3(intensity, intensity, intensity); - } - - return col; + return color + glowEffect; } float3 Move(float3 position, float3 effect, float wibble) { - float3 pos = position; - float weight = effect.z; + float weight = effect.z; + float shouldMove = step(0.0f, effect.y) * step(0.0f, weight); + float offset = wibble * effect.y * weight * 128.0f * shouldMove; - if (effect.y > 0.0f && weight > 0.0f) - { - pos.y += wibble * effect.y * weight * 128.0f; // 128 units offset to top and bottom (256 total) - } - - return pos; + return position + float3(0.0f, offset, 0.0f); } \ No newline at end of file diff --git a/TombEngine/TombEngine.vcxproj b/TombEngine/TombEngine.vcxproj index c0bf4859d..e9aa7cfb4 100644 --- a/TombEngine/TombEngine.vcxproj +++ b/TombEngine/TombEngine.vcxproj @@ -1379,7 +1379,10 @@ if not exist "%ScriptsDir%\Strings.lua" xcopy /Y "$(SolutionDir)Scripts\Strings. true Text - + + false + Document + true