Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

UEHelpers improvements. Ensure that all functions return a RemoteObject and not nil. Add more functions, Rework some #650

Merged
merged 26 commits into from
Oct 2, 2024
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
1073e87
docs: FindFirstOf annotation, it never returns a nil
igromanru Sep 7, 2024
307fb37
feat: Add RemoteObject class that allows us to create a placeholder w…
igromanru Sep 7, 2024
e8a7159
feat: UEHelpers: Remove duplicate function GetKismetMathLibrary()
igromanru Sep 7, 2024
ed67854
feat: UEHelpers: Aad an additional check in GetPlayerController() if …
igromanru Sep 7, 2024
855f13a
feat: Change GetGameEngine() to GetEngine()
igromanru Sep 7, 2024
5ca63e4
fix: Implemented WorldCache but forget to use it
igromanru Sep 7, 2024
ee09c7c
refactor: formatting
igromanru Sep 7, 2024
c824c42
feat: UEHelpers: Add GetGameInstance() function
igromanru Sep 7, 2024
a82bd54
feat: UEHelpers: Rework GetPlayerController() to search through all A…
igromanru Sep 8, 2024
6a23b68
feat: UEHelpers:
igromanru Sep 8, 2024
b9e1825
fix: GetPersistentLevel() and GetWorldSettings() weren't UEHelpers fu…
igromanru Sep 8, 2024
1379728
feat: UEHelpers: Add and annotate FName utility functions to Find, Ad…
igromanru Sep 8, 2024
3796514
docs: UEHelpers: Added annoations to CacheDefaultObject function and …
igromanru Sep 8, 2024
32ca093
docs: UEHelpers: Fix and improve comments
igromanru Sep 9, 2024
4d1f091
Merge branch 'main' of github.com:UE4SS-RE/RE-UE4SS
igromanru Sep 19, 2024
8eea542
docs: Add NAME_None as alias to Types.lua
igromanru Sep 21, 2024
846312a
docs: Updated Changelog to match last commit
igromanru Sep 21, 2024
dd4bc4b
docs: Add NAME_None, EFindName and FName overloads with FindType para…
igromanru Sep 21, 2024
23679f8
docs: Add "How to use your mod's directory as workspace" to "Using Cu…
igromanru Sep 21, 2024
461ec81
Merge branch 'main' of github.com:UE4SS-RE/RE-UE4SS
igromanru Sep 26, 2024
7c9a738
Revert FindFirstOf changes, it will be done through PR #666
igromanru Sep 27, 2024
d491a33
feat: Replace RemoteObject:new() with CreateBlankObject()
igromanru Sep 28, 2024
058ed3f
docs: Remove UEHelpers->RemoteObject changes from Changelog.md since …
igromanru Sep 28, 2024
ef474af
feat: Update GetActorFromHitResult for UE v5.4
igromanru Sep 28, 2024
84afc76
refactor: Replace CreateBlankObject() with CreateInvalidObject()
igromanru Oct 1, 2024
94cae3c
Merge branch 'main' of github.com:UE4SS-RE/RE-UE4SS
igromanru Oct 1, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions assets/Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,17 @@ Added search filter: `IncludeClassNames`. ([UE4SS #472](https://github.com/UE4SS
### UHT Dumper

### Lua API
#### UEHelpers [PR #650](https://github.com/UE4SS-RE/RE-UE4SS/pull/650)
- Added local class `RemoteObject` with method `IsValid`. A new instance of the class should be used as return value in all UEHelpers functions instead of nil
- Added function `GetPlayer` which is just a fast way to get player controlled Pawn (the majority of the time it will be the player character)
- Added functions: `GetEngine`, `GetGameInstance`, `GetGameViewportClient`, `GetGameModeBase`, `GetGameStateBase`,`GetPersistentLevel` and `GetWorldSettings`
- Added functions to get static objects: `GetKismetStringLibrary`, `GetKismetTextLibrary`
- Added function `GetActorFromHitResult` which extracts the hit actor from a `FHitResult` struct based on UE's version
- Added FName utility functions:
- `FName_None`: returns a `None` FName (FName with ComparisonIndex = 0)
- `FindFName`: wrapper for `FName(Name, EFindName.FNAME_Find)`
- `AddFName`: wrapper for `FName(Name, EFindName.FNAME_Add)`
- Added [Lua Server Annotations](https://luals.github.io/wiki/annotations/) to all UEHelpers functions

### C++ API
Key binds created with `UE4SSProgram::register_keydown_event` end up being duplicated upon mod hot-reload.
Expand Down Expand Up @@ -63,6 +74,14 @@ The following search filters now allow multiple values, with each value separate

The callback of `NotifyOnNewObject` can now optionally return `true` to unregister itself ([UE4SS #432](https://github.com/UE4SS-RE/RE-UE4SS/pull/432)) - Lyrth

#### UEHelpers [UE4SS #650](https://github.com/UE4SS-RE/RE-UE4SS/pull/650)
- Increased version to 3
- Reworked all UEHelpers functions to ensure that they always return an object which can be checked with the function `IsValid` for validation
- Reworked `UEHelpers.GetPlayerController` to return first valid player controller (It will now return a player controller even if it doesn't control a pawn at the time)
- Reworked `UEHelpers.GetWorld` function to use UWorld cache (UWorld usually never changes)
- Change `UEHelpers.GetWorldContextObject` function annotation to return `UObject`. (Any UObject with a GetWorld() function is a valid WorldContext)
- Removed duplicate function `UEHelpers.GetKismetMathLibrary`

### C++ API

### BPModLoader
Expand Down Expand Up @@ -110,6 +129,8 @@ Fixed crash when calling UFunctions that take one or more 'out' params of type T

Fixed `RegisterProcessConsoleExecPostHook`. ([UE4SS #631](https://github.com/UE4SS-RE/RE-UE4SS/pull/631))

Fixed `FindFirstOf` return type annotation in `Types.lua` to signal that the return value will never be nil. ([UE4SS #650](https://github.com/UE4SS-RE/RE-UE4SS/pull/650))

### C++ API
Fixed a crash caused by a race condition enabled by C++ mods using `UE4SS_ENABLE_IMGUI` in their constructor ([UE4SS #481](https://github.com/UE4SS-RE/RE-UE4SS/pull/481))

Expand Down
2 changes: 1 addition & 1 deletion assets/Mods/shared/Types.lua
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,7 @@ function StaticFindObject(Class, InOuter, ObjectName, ExactClass) end

---Find the first non-default instance of the supplied class name
---@param ShortClassName string Should only contains the class name itself without path info
---@return UObject?
---@return UObject
function FindFirstOf(ShortClassName) end

---Find all non-default instances of the supplied class name
Expand Down
222 changes: 192 additions & 30 deletions assets/Mods/shared/UEHelpers/UEHelpers.lua
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,31 @@ local UEHelpers = {}
-- local jsb = require "jsbProfi"

-- Version 1 does not exist, we start at version 2 because the original version didn't have a version at all.
local Version = 2
local Version = 3

-- Functions local to this module, do not attempt to use!
local CacheDefaultObject = function(ObjectFullName, VariableName, ForceInvalidateCache)
local DefaultObject
-- Functions and classes local to this module, do not attempt to use!

---Class that allows to create a blank RemoteObject
---@class RemoteObject
local RemoteObject = {}
RemoteObject.__index = RemoteObject

---Creates a new instance of RemoteObject
---@return RemoteObject
function RemoteObject:new()
return setmetatable({}, RemoteObject)
end

function RemoteObject:IsValid()
return false
end

---@param ObjectFullName string
---@param VariableName string
---@param ForceInvalidateCache boolean?
---@return UObject
local function CacheDefaultObject(ObjectFullName, VariableName, ForceInvalidateCache)
local DefaultObject = RemoteObject:new()

if not ForceInvalidateCache then
DefaultObject = ModRef:GetSharedVariable(VariableName)
Expand All @@ -28,68 +48,210 @@ function UEHelpers.GetUEHelpersVersion()
return Version
end

--- Returns the first valid PlayerController that is currently controlled by a player.
local EngineCache = RemoteObject:new() ---@cast EngineCache UEngine
---Returns instance of UEngine
---@return UEngine
function UEHelpers.GetEngine()
if EngineCache:IsValid() then return EngineCache end

EngineCache = FindFirstOf("Engine") ---@type UEngine
return EngineCache
end

local GameInstanceCache = RemoteObject:new() ---@cast GameInstanceCache UGameInstance
---Returns instance of UGameInstance
---@return UGameInstance
function UEHelpers.GetGameInstance()
if GameInstanceCache:IsValid() then return GameInstanceCache end

GameInstanceCache = FindFirstOf("GameInstance") ---@type UGameInstance
return GameInstanceCache
end

---Returns the main UGameViewportClient
---@return UGameViewportClient
function UEHelpers.GetGameViewportClient()
local Engine = UEHelpers.GetEngine()
if Engine:IsValid() then
return Engine.GameViewport
end
return RemoteObject:new() ---@type UGameViewportClient
end

local PlayerControllerCache = RemoteObject:new() ---@cast PlayerControllerCache APlayerController
---Returns first player controller
---@return APlayerController
local PlayerController = nil
function UEHelpers.GetPlayerController()
if PlayerController and PlayerController:IsValid() then return PlayerController end
-- local PlayerControllers = jsb.simpleBench("findallof", FindAllOf, "Controller")
-- Uncomment line above and comment line below to profile this function
local PlayerControllers = FindAllOf("PlayerController") or FindAllOf("Controller")
if not PlayerControllers then return Print("No PlayerControllers found\n") end
for _, Controller in pairs(PlayerControllers or {}) do
if Controller.Pawn:IsValid() and Controller.Pawn:IsPlayerControlled() then
PlayerController = Controller
break
-- else
-- print("Not valid or not player controlled\n")
if PlayerControllerCache:IsValid() then return PlayerControllerCache end

local Controllers = FindAllOf("Controller") ---@type AController[]?
if Controllers then
for _, Controller in ipairs(Controllers) do
if Controller:IsValid() and Controller:IsPlayerController() then
PlayerControllerCache = Controller
break
end
end
end
if PlayerController and PlayerController:IsValid() then
return PlayerController

return PlayerControllerCache
end

---Returns local player pawn
---@return APawn
function UEHelpers.GetPlayer()
local playerController = UEHelpers.GetPlayerController()
if playerController:IsValid() then
return playerController.Pawn
end
error("No PlayerController found\n")
return RemoteObject:new() ---@type APawn
end

--- Returns the UWorld that the player is currenlty in.
local WorldCache = RemoteObject:new() ---@cast WorldCache UWorld
--- Returns the main UWorld
---@return UWorld
function UEHelpers.GetWorld()
return UEHelpers.GetPlayerController():GetWorld()
if WorldCache:IsValid() then return WorldCache end

local PlayerController = UEHelpers.GetPlayerController()
if PlayerController:IsValid() then
WorldCache = PlayerController:GetWorld()
return WorldCache
end

return WorldCache
end

--- Returns the UGameViewportClient for the player.
---@return AActor
function UEHelpers.GetGameViewportClient()
return UEHelpers.GetPlayerController().Player.ViewportClient
---Returns UWorld->PersistentLevel
---@return ULevel
function UEHelpers.GetPersistentLevel()
local World = UEHelpers.GetWorld()
if World:IsValid() and World.PersistentLevel:IsValid() then
return World.PersistentLevel
end
return RemoteObject:new() ---@type ULevel
end

--- Returns an object that's useable with UFunctions that have a WorldContextObject param.
---Returns UWorld->AuthorityGameMode<br>
---The function doesn't guarantee it to be an AGameMode, as many games derive their own game modes directly from AGameModeBase!
---@return AGameModeBase
function UEHelpers.GetGameModeBase()
local World = UEHelpers.GetWorld()
if World:IsValid() and World.AuthorityGameMode:IsValid() then
return World.AuthorityGameMode
end
return RemoteObject:new() ---@type AGameModeBase
end

---Returns UWorld->GameState<br>
---The function doesn't guarantee it to be an AGameState, as many games derive their own game states directly from AGameStateBase!
---@return AGameStateBase
function UEHelpers.GetGameStateBase()
local World = UEHelpers.GetWorld()
if World:IsValid() and World.GameState:IsValid() then
return World.GameState
end
return RemoteObject:new() ---@type AGameStateBase
end

---Returns PersistentLevel->WorldSettings
---@return AWorldSettings
function UEHelpers.GetWorldSettings()
local PersistentLevel = UEHelpers.GetPersistentLevel()
if PersistentLevel:IsValid() and PersistentLevel.WorldSettings:IsValid() then
return PersistentLevel.WorldSettings
end
return RemoteObject:new() ---@type AWorldSettings
end

--- Returns an object that's useable with UFunctions that have a WorldContext parameter.<br>
--- Prefer to use an actor that you already have access to whenever possible over this function.
---@return AActor
--- Any UObject that has a GetWorld() function can be used as WorldContext.
---@return UObject
function UEHelpers.GetWorldContextObject()
return UEHelpers.GetPlayerController()
end

---Returns hit actor from FHitResult.<br>
---The function handles the struct differance between UE4 and UE5
---@param HitResult FHitResult
---@return AActor
function UEHelpers.GetActorFromHitResult(HitResult)
if not HitResult or not HitResult:IsValid() then
return RemoteObject:new() ---@type AActor
end

if UnrealVersion:IsBelow(5, 0) then
return HitResult.Actor:Get()
end
return HitResult.HitObjectHandle.Actor:Get()
end

---@param ForceInvalidateCache boolean? # Force update the cache
---@return UGameplayStatics
function UEHelpers.GetGameplayStatics(ForceInvalidateCache)
---@type UGameplayStatics
return CacheDefaultObject("/Script/Engine.Default__GameplayStatics", "UEHelpers_GameplayStatics", ForceInvalidateCache)
end

---@param ForceInvalidateCache boolean? # Force update the cache
---@return UKismetSystemLibrary
function UEHelpers.GetKismetSystemLibrary(ForceInvalidateCache)
---@type UKismetSystemLibrary
return CacheDefaultObject("/Script/Engine.Default__KismetSystemLibrary", "UEHelpers_KismetSystemLibrary", ForceInvalidateCache)
end

---@param ForceInvalidateCache boolean? # Force update the cache
---@return UKismetMathLibrary
function UEHelpers.GetKismetMathLibrary(ForceInvalidateCache)
---@type UKismetMathLibrary
return CacheDefaultObject("/Script/Engine.Default__KismetMathLibrary", "UEHelpers_KismetMathLibrary", ForceInvalidateCache)
end

function UEHelpers.GetKismetMathLibrary(ForceInvalidateCache)
return CacheDefaultObject("/Script/Engine.Default__KismetMathLibrary", "UEHelpers_KismetMathLibrary", ForceInvalidateCache)
---@param ForceInvalidateCache boolean? # Force update the cache
---@return UKismetStringLibrary
function UEHelpers.GetKismetStringLibrary(ForceInvalidateCache)
---@type UKismetStringLibrary
return CacheDefaultObject("/Script/Engine.Default__KismetStringLibrary", "UEHelpers_KismetStringLibrary", ForceInvalidateCache)
end

---@param ForceInvalidateCache boolean? # Force update the cache
---@return UKismetTextLibrary
function UEHelpers.GetKismetTextLibrary(ForceInvalidateCache)
---@type UKismetTextLibrary
return CacheDefaultObject("/Script/Engine.Default__KismetTextLibrary", "UEHelpers_KismetTextLibrary", ForceInvalidateCache)
end

---@param ForceInvalidateCache boolean? # Force update the cache
---@return UGameMapsSettings
function UEHelpers.GetGameMapsSettings(ForceInvalidateCache)
---@type UGameMapsSettings
return CacheDefaultObject("/Script/EngineSettings.Default__GameMapsSettings", "UEHelpers_GameMapsSettings", ForceInvalidateCache)
end

---Returns Empty FName aka. "None"
---@return FName
function UEHelpers.FName_None()
return NAME_None
end
UE4SS marked this conversation as resolved.
Show resolved Hide resolved

---Returns found FName or "None" FName if the operation faled
---@param Name string
---@return FName
function UEHelpers.FindFName(Name)
return FName(Name, EFindName.FNAME_Find)
end

---Returns added FName or "None" FName if the operation faled
---@param Name string
---@return FName
function UEHelpers.AddFName(Name)
return FName(Name, EFindName.FNAME_Add)
end

---Tries to find existing FName, if it doesn't exist a new FName will be added to the pool
---@param Name string
---@return FName # Returns found or added FName, “None” FName if both operations fail
function UEHelpers.FindOrAddFName(Name)
local NameFound = FName(Name, EFindName.FNAME_Find)
if NameFound == NAME_None then
Expand Down