Skip to content

Egg hatching and special eggs

If a Lua mod wants to change what an egg hatches into, or leave certain eggs alone, it works on the incubator model server-side. This page covers the hooks, the egg data object, a timing trap that catches everyone once, and how to tell a base-game special egg (mutation or WorldTree) apart from a normal one.

Hatching runs on PalMapObjectHatchingEggModel and its base PalMapObjectHatchingEggModelBase. The hooks worth knowing:

  • OnUpdateContainerContentInServer - fires when the incubator’s contents change, for example when an egg is placed.
  • OnFinishWorkInServer - fires when incubation completes. This is where the egg data and the hatched pal are settled.
  • ObtainHatchedCharacter_ServerInternal - the final spawn step for the newborn pal.

All are server-side (...InServer / ...ServerInternal). In single player the listen host is the server, so they fire locally.

Each incubator exposes its egg as HatchedPalEggData (single incubator), or per slot via RepInfoArray.Items[i].PalEggData (multi-slot). Both are PalDynamicPalEggItemDataBase, which:

  • declares CharacterID (the species that will hatch) and SaveParameter, and
  • inherits StaticId (the egg item id, such as PalEgg_Normal_01) from PalDynamicItemDataBase.

So eggData.StaticId sits right next to eggData.CharacterID.

Read the egg at the wrong hook and it is empty:

  • At the place hook, HatchedPalEggData is usually not valid yet. The egg data is not populated at the instant the container updates.
  • At the finish hook, the egg data and its StaticId are populated. Act here.

One more subtlety: the pal that actually hatches is spawned from the model’s HatchedCharacterSaveParameter (the replicated notification copy), not only from HatchedPalEggData. If you rewrite an egg’s species, set both, or the egg hatches its original pal.

The base game has two special egg types, defined by the enum EPalEggSpecialType:

  • WorldTree (1) - the glowing late-game egg. Players sometimes call it the “celestial” egg; the game has no “Celestial” type, so that name maps to WorldTree.
  • Mutation (2) - the bred mutation egg.

Both carry a distinct item StaticId: PalEgg_WorldTree_01..05 and PalEgg_MutationPal(_01..05). That is the reliable signal, and it reads off the same object you already hold at the finish hook. A skip check, failing closed when the id cannot be read:

local sid = ""
pcall(function() sid = eggData.StaticId:ToString() end)
if sid == "" or sid:find("PalEgg_WorldTree", 1, true) or sid:find("PalEgg_MutationPal", 1, true) then
return -- leave special eggs alone
end

Do not use IsRarePal, bIsAwakening or PalRarity for this. Those belong to the alpha and condenser (soul) systems and have nothing to do with breeding mutation.

These incubator hooks are hatch-time only. There is no hook here into breeding generation, the egg lottery or the mutation rate, so nothing on this path changes how often an egg spawns or how often a mutation egg is rolled. That behaviour lives on the breeding-farm model (PalMapObjectBreedFarmModel), not the incubator.

  • Egg and special-egg internals verified on Palworld 1.0 against the UE4SS object dump and a live incubator test, while building the Palvolve egg filter. By DooDesch.

    Corrections and questions: the DooDesch Mods Discord.