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)