Palworld - Random Pals
Click here for the mod on Nexus Mods ;
Here for the code on GitHub ;
Or here for the YouTube video .
Setup
To make and use the mod we need the Unreal Engine 4/5 Scripting System. Download the UE4SS_Xinput_v2.5.2.zip from the releases page. Unzip and install it in your \Palworld\Pal\Binaries\Win64\ directory. This will put, among other things, a Mods directory in that directory.
If you need more info on installation, check out the Installation info on this mod page.
When you start up the game two windows should pop up: the game window and the UE4SS Debugging Tools window. To get access to the code we need click “DUmp Objects & Properties”; then go to “Dumpers” and click “Generate UHT Compatible Headers” and “Dump CXX Headers”. You can find the files that are created by doing this in in \Palworld\Pal\Binaries\Win64.
For this mod we use Lua.
Mod
The list of Pal types in game, located in the EPalTribeID class, is currently longer than the ones that are actually implemented. Therefore I had to go through the entire list to see which ones worked and which ones don’t.
The pals that are currently implemented in the game:
PalTypes = {"Anubis","Baphomet","Baphomet_Dark","Bastet","Bastet_Ice","Boar","Carbunclo","ColorfulBird","Deer","DrillGame","Eagle", "ElecPanda",
"Ganesha","Garm","Gorilla","Hedgehog","Hedgehog_Ice","Kirin","Kitsunebi","LittleBriarRose","Mutant","Penguin","RaijinDaughter",
"SharkKid","SheepBall","Umihebi","Werewolf","WindChimes","Suzaku","Suzaku_Water","FireKirin","FairyDragon","FairyDragon_Water","SweetsSheep",
"WhiteTiger","Alpaca","Serpent","Serpent_Ground","DarkCrow","BlueDragon","PinkCat","NegativeKoala","FengyunDeeper","VolcanicMonster",
"VolcanicMonster_Ice","GhostBeast","RobinHood","LazyDragon","LazyDragon_Electric","AmaterasuWolf","LizardMan","Blueplatypus",
"BlackFurDragon","BirdDragon","BirdDragon_Ice","ChickenPal","FlowerDinosaur","FlowerDinosaur_Electric",
"ElecCat","IceHorse","IceHorse_Dark","GrassMammoth","CatVampire","SakuraSaurus","SakuraSaurus_Water","Horus","KingBahamut",
"BerryGoat","IceDeer","BlackGriffon","WhiteMoth","CuteFox","FoxMage","PinkLizard","ElecLion",
"WizardOwl","Kelpie","NegativeOctopus","CowPal","Yeti","Yeti_Grass","VioletFairy","HawkBird","FlowerRabbit",
"LilyQueen","LilyQueen_Dark","QueenBee","SoldierBee","CatBat","GrassPanda","GrassPanda_Electric","FlameBuffalo","ThunderDog",
"CuteMole","BlackMetalDragon","GrassRabbitMan","IceFox","JetDragon","DreamDemon","Monkey","Manticore","Manticore_Dark",
"KingAlpaca","PlantSlime","DarkMutant","MopBaby","MopKing","CatMage","PinkRabbit","ThunderBird","HerculesBeetle","SaintCentaur",
"NightFox","CaptainPenguin","WeaselDragon","SkyDragon","HadesBird","RedArmorBird","Ronin","FlyingManta","BlackCentaur",
"FlowerDoll","NaughtyCat","CuteButterfly","DarkScorpion","ThunderDragonMan","WoolFox",
"LazyCatfish","Deer_Ground","FireKirin_Dark",
"KingAlpaca_Ice","RobinHood_Ground","GrassMammoth_Ice","Kelpie_Fire","SharkKid_Fire","LizardMan_Fire","LavaGirl","FlameBambi",
"Umihebi_Fire","WindChimes_Ice"}
The pals that are in the list of types but do not (yet) work:
PalTypesTakenOut = {"Owl","PinkKangaroo","BeardedDragon","WaterLizard","GuardianDog","GrassDragon","SifuDog","TentacleTurtle","GoldenHorse","BadCatgirl","BrownRabbit",
"FeatherOstrich","WingGolem","ScorpionMan","BlueberryFairy"}
We need the following parameters for optimization:
component = nil
index = 0
When we look at the different moves listed in EPalWazaID we can see that there are a lot that are either unique to a single type of Pal or are not yet implemented. The moves that can be used by any Pal:
Moves = {10,11,12,14,22,33,34,35,36,37,38,39,40,42,43,48,49,50,51,52,53,54,55,57,58,59,60,61,62,63,65,66,67,68,69,70,71,72,73,74,75,78,79,80,81,83,84,85,86,87,90,91,92,93,94,95,97,98,99,100,104,105,106,107,108,109,110,111,112,113}
To hook into the game code we use the
RegisterHook("class_name:function_name", function(self, [parameters])
functionality
end)
functionality grom UE4SS. Where self will be the class_name that called function_name. If the function_name function has any additional parameters, you can interact with these by putting them in [parameters]. This will look as follows: function(self, parameter1, parameter2, …). Whenever the class_name:function_name function is called our functionality code will also be called.
Note: You do not necessarily have to have any parameters in the function part, instead of function(self, [parameters]) you can also just use function() if you do not need the class itself.
The mod functionality looks as follows:
RegisterHook("/Script/Pal.PalMonsterCharacter:MasterWazaUpdateWhenLevelUp", function(self)
We hook into PalMonsterCharacter:MasterWazaUpdateWhenLevelUp, since this is (one of) the functions that is called when a Pal levels up.
First we try to find the slot in which the Pal that just updated is located in our party. We need the PalOtomoHolderComponentBase for our player to access this. To gain a slight speedboost we only get the component once.
if component == nil then
component = FindAllOf("PalOtomoHolderComponentBase")[1]
end
local slot = -1
Then we get all of the info we need from the Pal that just levelled up:
local params = self:get():GetCharacterParameterComponent():GetIndividualParameter().SaveParameter
local temp = self:get():GetCharacterParameterComponent():GetIndividualParameter().SaveParameter.EquipWaza
To prevent having to get them over and over we also get all of the values for the parameters we want to check:
local charID = params.CharacterID
local level = params.Level
local attack = params.Rank_Attack
local defence = params.Rank_Defence
local craft = params.Rank_CraftSpeed
local exp = params.Exp
local num_Otomo = component:GetMaxOtomoNum()
We iterate over every Pal in the player’s party. If the CharacterID (Pal type), Level, Exp, Attack, Defence, Craft speed, and EquipWaza (moves) are the same they are probably the same Pal. If this is the case we set the slot to that number. We have to subtract 1 because Lua starts counting at 1 while most programming languages start at 0:
for i=1,num_Otomo do
local flag = true
if component:GetOtomoIndividualCharacterSlot((index + i - 1) % num_Otomo):IsEmpty() then
flag = false
end
if flag then
local compare = component:GetOtomoIndividualHandle((index + i - 1) % num_Otomo):TryGetIndividualParameter().SaveParameter
if charID ~= compare.CharacterID then
flag = false
elseif level ~= compare.Level then
flag = false
elseif attack ~= compare.Rank_Attack then
flag = false
elseif defence ~= compare.Rank_Defence then
flag = false
elseif craft ~= compare.Rank_CraftSpeed then
flag = false
elseif exp ~= compare.Exp then
flag = false
end
for j=1,#temp do
if flag and temp[j] ~= compare.EquipWaza[j] then
flag = false
end
if not flag then
break
end
end
if flag then
slot = (index + i - 1) % num_Otomo
index = (index + i) % num_Otomo
break
end
end
end
By using if and elseif, we make sure that the conditions are only checked if the previous ones evaluate to true. This gives us a slight speedup. By using the flag we can check if the Pals indeed are equal.
To speed up the process we do not iterate through the Pals top to bottom. Instead we keep an index that decides where we start. When multiple Pals level up at the same time, they will always be updated top to bottom. We use this to our advantage by keeping track of the index of the previous pal that leveled up and adding 1 to that index. This means that if Pal 3 and 4 level up we will check the following slots: 1, 2, 3; This pal is equal so we set the index to 4; 4; This pal is equal so we set the index to 5. If we instead just iterated top to bottom we would check the following slots: 1,2,3; Find the pal; 1,2,3,4; Find the pal.
If we found a slot for the Pal that just updated we have to randomize the Pal and update the slot. By doing it this way only pals in our party get randomized. Otherwise ones that you encounter in the wild will also be randomized:
if slot ~= -1 then
params.CharacterID = FName(PalTypes[math.random(#PalTypes)])
for i=1,#temp do
temp[i] = Moves[math.random(#Moves)]
end
component:OnUpdateSlot(component:GetOtomoIndividualCharacterSlot(slot), component:GetOtomoIndividualHandle(slot))
end
end)
Lastly we just print to show that the mod was loaded successfully:
print("---------------------------------RandomPals loaded successfully---------------------------------")
To get the mod into the game we have to make a new folder in \Palworld\Pal\Binaries\Win64\Mods. We just name it RandomPals, but name this anything you want. Then, in that folder create an empty txt file called enabled.txt and a folder called Scripts. Within that Scripts folder place your code in a file named main.lua.
Then, in \Palworld\Pal\Binaries\Win64\Mods\mods.txt add a line RandomPals : 1 to enable the mod.
Note: Although setting the 1 in that line to 0 should disable the mod, it does not. So, if you do not want to use the mod, make sure to remove the file from your game!