UE4SS Function Overview
UE4SS comes with a built-in Lua API. Part of that is an array of useful functions that let us do cool stuff. In this section we're going to cover some of the more useful functions in the context of how they might get used in Palworld.
We have two functions that make up the bread and butter of our Lua scripting: RegisterHook
and NotifyOnNewObject
. You'll get a better hands-on experience with them in the next tutorial, but for now let's just introduce you to them.
RegisterHook
This hooks on to SomeFunction and fires after SomeFunction is executed.
For example, one of the most common RegisterHooks
you'll see in scripts right now is:
RegisterHook("/Script/Engine.PlayerController:ClientRestart", function (Context)
-- do something
end)
What's the function being hooked?
Hopefully you were thinking ClientRestart, or we might have a long road ahead of us
Now, what is Context in this example?
If you said PlayerController, good job! I'm proud of you. The first parameter in the callback is always the UObject calling the function. Aka, the context.
What's the point of this hook, why do so many scripts use it?
Well, what does it do? It executes --do something
after the client restarts. It's a handy way to init stuff.
Why do most scripts use it? Well, not everything is available right away when the game launches, so sometimes you need to delay your logic until you know whatever you want will be accessible. A general rule of thumb is anything that starts with /Script/
should be available immediately. Anything else, you probably should put behind a hook like this.
But this hook also sucks because it doesn't work for dedicated servers. So I'mma teach you a better one:
RegisterHook("/Script/Engine.PlayerController:ServerAcknowledgePossession", function(Context)
-- do something
end)
This should get called whenever a client connects to the server. It also works for local games. I use it in most my scripts.
Sometimes it doesn't work and I don't know why but we don't talk about that and just claim ignorance and blame the person running the server for setting up something wrong, idk.
We said before the first parameter of the callback function is the UObject, but you can also get the params from the invoked function. The callback function of this is always: function(UObject self, UFunctionParams)
So if I have StupidFunction(bool isTrue, int Id, string Message)
I can do
RegisterHook("/Script/Example.SomeObject:StupidFunction", function(Context, isTrue, Id, Message)
print("This message is: " .. Message:get())
print("The bool is: " .. isTrue:get())
print("The id is: " .. id:get())
)
What's up with the :get()
?
If you don't remember from last lesson, some of the params we get from hooks are actually these weird things calledRemoteUnrealParam
, for some reason that is above my level of understanding, so to get the actual value of them, we need to call:get()
Of course it's never that easy because UE never uses easy to work with parameters, but you get the idea.
NotifyOnNewObject
This is magic sauce #2 which allows us to watch for particular objects to be created. Wanna know every time someone goes to build an Electric Heater?
NotifyOnNewObject("/Game/Pal/Blueprint/MapObject/BuildObject/BP_BuildObject_HeaterElectric.BP_BuildObject_HeaterElectric_C", function(Context)
print("woah its a heater")
end)
But Teh! How do you know the long address string thing? Baby steps! That's a problem for future you. Just get an understanding of the functions and how to use them for now, I promise we'll get to it.
If you were to run this code above as is, it may or may not work. Why? Remember what I said about things not always being available? Notice this isn't a /Script/
. This might not exist yet when you're trying to create the notify. So to be sure, you wrap it in a RegisterHook
with SAP or CR. And even that might not be enough...SAP sometimes fires too early, so we might need to wrap it with a delay too. We'll cover that in a bit, so for now we'll just take it as is.
RegisterHook("/Script/Engine.PlayerController:ServerAcknowledgePossession", function(Context)
ExecuteWithDelay(5000,function()
NotifyOnNewObject("/Game/Pal/Blueprint/MapObject/BuildObject/BP_BuildObject_HeaterElectric_BP_BuildObject_HeaterElectric_C", function(Context)
print("woah its a heater")
end)
end)
end)
But if you do that you're stupid. Because now every time that hook fires, you're creating a new notification. Two more players just joined your game. Now you have 3 NotifyOnNewObject
. Remember, these execute whatever is in it every time they fire. So don't be stupid, and wrap it up.
local not_hooked = true
RegisterHook("/Script/Engine.PlayerController:ServerAcknowledgePossession", function(Context)
if not_hooked then
ExecuteWithDelay(5000,function()
NotifyOnNewObject("/Game/Pal/Blueprint/MapObject/BuildObject/BP_BuildObject_HeaterElectric_BP_BuildObject_HeaterElectric_C", function(Context)
print("woah its a heater")
end)
end)
not_hooked = false
end
end)
Now we're cooking with fire.
Those two are going to be your bread and butter, but lets touch on a couple more useful UE4SS functions.
StaticFindObject
Sometimes you just want the default class object. A good example is PalUtility
. This bad boy has a lot of great commands in it. But you can't just do PalUtility:AwesomeFunction
in your code, you need the default class to call it. In comes StaticFindObject
local PalUtil = StaticFindObject("/Script/Pal.Default__PalUtility")
PalUtil:AwesomeFunction()
Find Functions
These are for locating objects. There are a bunch. You probably will use FindFirstOf
and FindAllOf
the most but there's also FindObject
and FindObjects
.
The first two can use short names, which is nice because we don't need those big long adddresses and be lazy and just do something like:
local player = FindFirstOf("PalPlayerCharacter")
LoopAsync and ExecuteWithDelay
You saw ExecuteWithDelay
in the previous example. These are both useful at times in their own right and you'll probably use them occasionally. Sometimes you want to make sure something happens a bit later and in that case you can do:
ExecuteWithDelay(later_in_ms, function()
--something
end)
Other times you want something to happen every so often, in which case you can do
LoopAsync(every_so_often, function()
--something
end)
FName + FText
FName
and FText
are special string based shit that UE uses for some reason idk why I'm not a UE guy I just know they're annoying on the Lua side. If you ever need to turn a string into FName
or FText
you can do that with these functions:
local fname = FName(some_fname)
local ftext = FText(some_text)
You can also do the reverse in case some function hands you the nasty stuff:
RegisterHook("/idk/some:function", function(fname_param, ftext_param)
local cool_string = fname_param:get():ToString()
local also_cool_string = ftext_param:get():ToString()
print(cool_string .. also_cool_string)
end)