Skip to content

Commit c2a8866

Browse files
authored
UEHelpers improvements. Ensure that all functions return a RemoteObject and not nil. Add more functions, Rework some (#650)
1 parent d0ebf12 commit c2a8866

File tree

4 files changed

+234
-34
lines changed

4 files changed

+234
-34
lines changed

assets/Changelog.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,21 @@ Added global function `CreateInvalidObject`, which returns an invalid UObject. (
2626
Added GenerateLuaTypes function. ([UE4SS #664](https://github.com/UE4SS-RE/RE-UE4SS/pull/664))
2727
Added global Dumpers functions to Types.lua. ([UE4SS #664](https://github.com/UE4SS-RE/RE-UE4SS/pull/664))
2828

29+
#### Types.lua [PR #650](https://github.com/UE4SS-RE/RE-UE4SS/pull/650)
30+
- Added `NAME_None` definition
31+
- Added `EFindName` enum definition
32+
- Added `FName` function overloads with FindType parameter
33+
34+
#### UEHelpers [PR #650](https://github.com/UE4SS-RE/RE-UE4SS/pull/650)
35+
- 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)
36+
- Added functions: `GetEngine`, `GetGameInstance`, `GetGameViewportClient`, `GetGameModeBase`, `GetGameStateBase`,`GetPersistentLevel` and `GetWorldSettings`
37+
- Added functions to get static objects: `GetKismetStringLibrary`, `GetKismetTextLibrary`
38+
- Added function `GetActorFromHitResult` which extracts the hit actor from a `FHitResult` struct based on UE's version
39+
- Added FName utility functions:
40+
- `FindFName`: wrapper for `FName(Name, EFindName.FNAME_Find)`
41+
- `AddFName`: wrapper for `FName(Name, EFindName.FNAME_Add)`
42+
- Added [Lua Server Annotations](https://luals.github.io/wiki/annotations/) to all UEHelpers functions
43+
2944
### C++ API
3045
Key binds created with `UE4SSProgram::register_keydown_event` end up being duplicated upon mod hot-reload.
3146
To fix this, `CppUserModBase::register_keydown_event` has been introduced.
@@ -67,6 +82,14 @@ The following search filters now allow multiple values, with each value separate
6782

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

85+
#### UEHelpers [UE4SS #650](https://github.com/UE4SS-RE/RE-UE4SS/pull/650)
86+
- Increased version to 3
87+
- Reworked all UEHelpers functions to ensure that they always return an object which can be checked with the function `IsValid` for validation
88+
- 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)
89+
- Reworked `UEHelpers.GetWorld` function to use UWorld cache (UWorld usually never changes)
90+
- Change `UEHelpers.GetWorldContextObject` function annotation to return `UObject`. (Any UObject with a GetWorld() function is a valid WorldContext)
91+
- Removed duplicate function `UEHelpers.GetKismetMathLibrary`
92+
7093
### C++ API
7194

7295
### BPModLoader

assets/Mods/shared/Types.lua

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,6 @@ EInternalObjectFlags = {
277277
---@alias float number
278278
---@alias double number
279279

280-
281280
-- # Global Functions
282281

283282
---Creates an blank UObject whose IsValid function always returns false
@@ -370,16 +369,37 @@ function UnregisterHook(UFunctionName, PreId, PostId) end
370369
---@param Callback fun()
371370
function ExecuteInGameThread(Callback) end
372371

373-
---Returns the FName for this string/ComparisonIndex or the FName for "None" if the name doesn't exist
372+
---FName with "None" as value
373+
NAME_None = FName(0)
374+
375+
---@enum EFindName
376+
EFindName = {
377+
FNAME_Find = 0,
378+
FNAME_Add = 1
379+
}
380+
381+
---Returns the FName for this string or the FName for "None" if the name doesn't exist
374382
---@param Name string
375383
---@return FName
376384
function FName(Name) end
377385

378-
---Returns the FName for this string/ComparisonIndex or the FName for "None" if the name doesn't exist
386+
---Returns the FName for this ComparisonIndex or the FName for "None" if the name doesn't exist
379387
---@param ComparisonIndex integer
380388
---@return FName
381389
function FName(ComparisonIndex) end
382390

391+
---Finds or adds FName for the string, depending on FindType
392+
---@param Name string
393+
---@param FindType EFindName|integer # Find = 0, Add = 1
394+
---@return FName
395+
function FName(Name, FindType) end
396+
397+
---Finds or adds FName for the ComparisonIndex, depending on FindType
398+
---@param ComparisonIndex integer
399+
---@param FindType EFindName|integer # Find = 0, Add = 1
400+
---@return FName
401+
function FName(ComparisonIndex, FindType) end
402+
383403
---Attempts to construct a UObject of the passed UClass
384404
---(>=4.26) Maps to https://docs.unrealengine.com/4.27/en-US/API/Runtime/CoreUObject/UObject/StaticConstructObject_Internal/1/
385405
---(<4.25) Maps to https://docs.unrealengine.com/4.27/en-US/API/Runtime/CoreUObject/UObject/StaticConstructObject_Internal/2/

assets/Mods/shared/UEHelpers/UEHelpers.lua

Lines changed: 173 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,16 @@ local UEHelpers = {}
33
-- local jsb = require "jsbProfi"
44

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

8-
-- Functions local to this module, do not attempt to use!
9-
local CacheDefaultObject = function(ObjectFullName, VariableName, ForceInvalidateCache)
10-
local DefaultObject
8+
-- Functions and classes local to this module, do not attempt to use!
9+
10+
---@param ObjectFullName string
11+
---@param VariableName string
12+
---@param ForceInvalidateCache boolean?
13+
---@return UObject
14+
local function CacheDefaultObject(ObjectFullName, VariableName, ForceInvalidateCache)
15+
local DefaultObject = CreateInvalidObject()
1116

1217
if not ForceInvalidateCache then
1318
DefaultObject = ModRef:GetSharedVariable(VariableName)
@@ -28,68 +33,206 @@ function UEHelpers.GetUEHelpersVersion()
2833
return Version
2934
end
3035

31-
--- Returns the first valid PlayerController that is currently controlled by a player.
36+
local EngineCache = CreateInvalidObject() ---@cast EngineCache UEngine
37+
---Returns instance of UEngine
38+
---@return UEngine
39+
function UEHelpers.GetEngine()
40+
if EngineCache:IsValid() then return EngineCache end
41+
42+
EngineCache = FindFirstOf("Engine") ---@type UEngine
43+
return EngineCache
44+
end
45+
46+
local GameInstanceCache = CreateInvalidObject() ---@cast GameInstanceCache UGameInstance
47+
---Returns instance of UGameInstance
48+
---@return UGameInstance
49+
function UEHelpers.GetGameInstance()
50+
if GameInstanceCache:IsValid() then return GameInstanceCache end
51+
52+
GameInstanceCache = FindFirstOf("GameInstance") ---@type UGameInstance
53+
return GameInstanceCache
54+
end
55+
56+
---Returns the main UGameViewportClient
57+
---@return UGameViewportClient
58+
function UEHelpers.GetGameViewportClient()
59+
local Engine = UEHelpers.GetEngine()
60+
if Engine:IsValid() then
61+
return Engine.GameViewport
62+
end
63+
return CreateInvalidObject() ---@type UGameViewportClient
64+
end
65+
66+
local PlayerControllerCache = CreateInvalidObject() ---@cast PlayerControllerCache APlayerController
67+
---Returns first player controller
3268
---@return APlayerController
33-
local PlayerController = nil
3469
function UEHelpers.GetPlayerController()
35-
if PlayerController and PlayerController:IsValid() then return PlayerController end
36-
-- local PlayerControllers = jsb.simpleBench("findallof", FindAllOf, "Controller")
37-
-- Uncomment line above and comment line below to profile this function
38-
local PlayerControllers = FindAllOf("PlayerController") or FindAllOf("Controller")
39-
if not PlayerControllers then return Print("No PlayerControllers found\n") end
40-
for _, Controller in pairs(PlayerControllers or {}) do
41-
if Controller.Pawn:IsValid() and Controller.Pawn:IsPlayerControlled() then
42-
PlayerController = Controller
43-
break
44-
-- else
45-
-- print("Not valid or not player controlled\n")
70+
if PlayerControllerCache:IsValid() then return PlayerControllerCache end
71+
72+
local Controllers = FindAllOf("Controller") ---@type AController[]?
73+
if Controllers then
74+
for _, Controller in ipairs(Controllers) do
75+
if Controller:IsValid() and Controller:IsPlayerController() then
76+
PlayerControllerCache = Controller
77+
break
78+
end
4679
end
4780
end
48-
if PlayerController and PlayerController:IsValid() then
49-
return PlayerController
81+
82+
return PlayerControllerCache
83+
end
84+
85+
---Returns local player pawn
86+
---@return APawn
87+
function UEHelpers.GetPlayer()
88+
local playerController = UEHelpers.GetPlayerController()
89+
if playerController:IsValid() then
90+
return playerController.Pawn
5091
end
51-
error("No PlayerController found\n")
92+
return CreateInvalidObject() ---@type APawn
5293
end
5394

54-
--- Returns the UWorld that the player is currenlty in.
95+
local WorldCache = CreateInvalidObject() ---@cast WorldCache UWorld
96+
--- Returns the main UWorld
5597
---@return UWorld
5698
function UEHelpers.GetWorld()
57-
return UEHelpers.GetPlayerController():GetWorld()
99+
if WorldCache:IsValid() then return WorldCache end
100+
101+
local PlayerController = UEHelpers.GetPlayerController()
102+
if PlayerController:IsValid() then
103+
WorldCache = PlayerController:GetWorld()
104+
return WorldCache
105+
end
106+
107+
return WorldCache
58108
end
59109

60-
--- Returns the UGameViewportClient for the player.
61-
---@return AActor
62-
function UEHelpers.GetGameViewportClient()
63-
return UEHelpers.GetPlayerController().Player.ViewportClient
110+
---Returns UWorld->PersistentLevel
111+
---@return ULevel
112+
function UEHelpers.GetPersistentLevel()
113+
local World = UEHelpers.GetWorld()
114+
if World:IsValid() and World.PersistentLevel:IsValid() then
115+
return World.PersistentLevel
116+
end
117+
return CreateInvalidObject() ---@type ULevel
64118
end
65119

66-
--- Returns an object that's useable with UFunctions that have a WorldContextObject param.
120+
---Returns UWorld->AuthorityGameMode<br>
121+
---The function doesn't guarantee it to be an AGameMode, as many games derive their own game modes directly from AGameModeBase!
122+
---@return AGameModeBase
123+
function UEHelpers.GetGameModeBase()
124+
local World = UEHelpers.GetWorld()
125+
if World:IsValid() and World.AuthorityGameMode:IsValid() then
126+
return World.AuthorityGameMode
127+
end
128+
return CreateInvalidObject() ---@type AGameModeBase
129+
end
130+
131+
---Returns UWorld->GameState<br>
132+
---The function doesn't guarantee it to be an AGameState, as many games derive their own game states directly from AGameStateBase!
133+
---@return AGameStateBase
134+
function UEHelpers.GetGameStateBase()
135+
local World = UEHelpers.GetWorld()
136+
if World:IsValid() and World.GameState:IsValid() then
137+
return World.GameState
138+
end
139+
return CreateInvalidObject() ---@type AGameStateBase
140+
end
141+
142+
---Returns PersistentLevel->WorldSettings
143+
---@return AWorldSettings
144+
function UEHelpers.GetWorldSettings()
145+
local PersistentLevel = UEHelpers.GetPersistentLevel()
146+
if PersistentLevel:IsValid() and PersistentLevel.WorldSettings:IsValid() then
147+
return PersistentLevel.WorldSettings
148+
end
149+
return CreateInvalidObject() ---@type AWorldSettings
150+
end
151+
152+
--- Returns an object that's useable with UFunctions that have a WorldContext parameter.<br>
67153
--- Prefer to use an actor that you already have access to whenever possible over this function.
68-
---@return AActor
154+
--- Any UObject that has a GetWorld() function can be used as WorldContext.
155+
---@return UObject
69156
function UEHelpers.GetWorldContextObject()
70157
return UEHelpers.GetPlayerController()
71158
end
72159

160+
---Returns hit actor from FHitResult.<br>
161+
---The function handles the struct differance between UE4 and UE5
162+
---@param HitResult FHitResult
163+
---@return AActor
164+
function UEHelpers.GetActorFromHitResult(HitResult)
165+
if not HitResult or not HitResult:IsValid() then
166+
return CreateInvalidObject() ---@type AActor
167+
end
168+
169+
if UnrealVersion:IsBelow(5, 0) then
170+
return HitResult.Actor:Get()
171+
elseif UnrealVersion:IsBelow(5, 4) then
172+
return HitResult.HitObjectHandle.Actor:Get()
173+
end
174+
return HitResult.HitObjectHandle.ReferenceObject:Get()
175+
end
176+
177+
---@param ForceInvalidateCache boolean? # Force update the cache
178+
---@return UGameplayStatics
73179
function UEHelpers.GetGameplayStatics(ForceInvalidateCache)
180+
---@type UGameplayStatics
74181
return CacheDefaultObject("/Script/Engine.Default__GameplayStatics", "UEHelpers_GameplayStatics", ForceInvalidateCache)
75182
end
76183

184+
---@param ForceInvalidateCache boolean? # Force update the cache
185+
---@return UKismetSystemLibrary
77186
function UEHelpers.GetKismetSystemLibrary(ForceInvalidateCache)
187+
---@type UKismetSystemLibrary
78188
return CacheDefaultObject("/Script/Engine.Default__KismetSystemLibrary", "UEHelpers_KismetSystemLibrary", ForceInvalidateCache)
79189
end
80190

191+
---@param ForceInvalidateCache boolean? # Force update the cache
192+
---@return UKismetMathLibrary
81193
function UEHelpers.GetKismetMathLibrary(ForceInvalidateCache)
194+
---@type UKismetMathLibrary
82195
return CacheDefaultObject("/Script/Engine.Default__KismetMathLibrary", "UEHelpers_KismetMathLibrary", ForceInvalidateCache)
83196
end
84197

85-
function UEHelpers.GetKismetMathLibrary(ForceInvalidateCache)
86-
return CacheDefaultObject("/Script/Engine.Default__KismetMathLibrary", "UEHelpers_KismetMathLibrary", ForceInvalidateCache)
198+
---@param ForceInvalidateCache boolean? # Force update the cache
199+
---@return UKismetStringLibrary
200+
function UEHelpers.GetKismetStringLibrary(ForceInvalidateCache)
201+
---@type UKismetStringLibrary
202+
return CacheDefaultObject("/Script/Engine.Default__KismetStringLibrary", "UEHelpers_KismetStringLibrary", ForceInvalidateCache)
203+
end
204+
205+
---@param ForceInvalidateCache boolean? # Force update the cache
206+
---@return UKismetTextLibrary
207+
function UEHelpers.GetKismetTextLibrary(ForceInvalidateCache)
208+
---@type UKismetTextLibrary
209+
return CacheDefaultObject("/Script/Engine.Default__KismetTextLibrary", "UEHelpers_KismetTextLibrary", ForceInvalidateCache)
87210
end
88211

212+
---@param ForceInvalidateCache boolean? # Force update the cache
213+
---@return UGameMapsSettings
89214
function UEHelpers.GetGameMapsSettings(ForceInvalidateCache)
215+
---@type UGameMapsSettings
90216
return CacheDefaultObject("/Script/EngineSettings.Default__GameMapsSettings", "UEHelpers_GameMapsSettings", ForceInvalidateCache)
91217
end
92218

219+
---Returns found FName or "None" FName if the operation faled
220+
---@param Name string
221+
---@return FName
222+
function UEHelpers.FindFName(Name)
223+
return FName(Name, EFindName.FNAME_Find)
224+
end
225+
226+
---Returns added FName or "None" FName if the operation faled
227+
---@param Name string
228+
---@return FName
229+
function UEHelpers.AddFName(Name)
230+
return FName(Name, EFindName.FNAME_Add)
231+
end
232+
233+
---Tries to find existing FName, if it doesn't exist a new FName will be added to the pool
234+
---@param Name string
235+
---@return FName # Returns found or added FName, “None” FName if both operations fail
93236
function UEHelpers.FindOrAddFName(Name)
94237
local NameFound = FName(Name, EFindName.FNAME_Find)
95238
if NameFound == NAME_None then

docs/guides/using-custom-lua-bindings.md

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,22 @@ When developing your Lua mods, the language server should automatically parse al
2828
}
2929
```
3030

31-
To get context sensitive information about the custom game types, you need to [annotate your code](https://emmylua.github.io/annotation.html). This is done by adding a comment above the function/class/object that you want to annotate.
31+
### How to use your mod's directory as workspace
32+
As alternative you can open just your mod's root directory as workspace.
33+
In this case you need to add a `.luarc.json` with `"workspace.library"` entries containing a path to the "shared" folder and the "Scripts" directory of your mod.
34+
Both paths can be relative.
35+
**Example .luarc.json:**
36+
```json
37+
{
38+
"$schema": "https://raw.githubusercontent.com/sumneko/vscode-lua/master/setting/schema.json",
39+
"workspace.maxPreload": 50000,
40+
"workspace.preloadFileSize": 5000,
41+
"workspace.library": ["../shared", "Scripts"]
42+
}
43+
```
3244

45+
## Annotating
46+
To get context sensitive information about the custom game types, you need to [annotate your code](https://emmylua.github.io/annotation.html) ([alternative documentation](https://luals.github.io/wiki/annotations/)). This is done by adding a comment above the function/class/object that you want to annotate.
3347
## Example
3448

3549
```lua

0 commit comments

Comments
 (0)