Grandpa Minion
Hunter
- Version
- https://discord.gg/ZfcsJU9TQC
- Platform
- Windows
Summary: Traders stop working (no stock, no quests) — EntityTrader.TraderData becomes null, and NetPackageTraderData.Setup() then silently serialises the null and sends it to the client
Game Version: V 3.0.1 (b4) — latest_experimental
OS/Platform: Linux dedicated server (LinuxServer 64 Bit), Windows clients
Game mode: Dedicated server, MP client
Did you wipe old saves? Yes — fresh world/seed created on V3.0.x
Did you start a new game? Yes
Did you validate your files? Yes
Are you using any mods? Yes — see "About the mods" below. No mod touches EntityTrader, TraderData, or NetPackageTraderData. Both defects below are visible by reading your own compiled code.
EAC on/off: ON
Status: Sometimes — recurring. Multiple players reporting it independently over several days. Resetting the trader restores it until it happens again.
Root cause: EntityTrader.TraderData becomes null on the affected trader.
There are really two defects here — one causes it, one makes it invisible and ships it to the player.
call get_TraderData()
brfalse -> ldnull; ret <-- TraderData null => TraderInfo NULL
The trader's stock list and quests are resolved through a static lookup:
TraderData.get_TraderInfo():
ldfld TraderData::TraderID
ldsfld TraderInfo::traderInfoList
-> traderInfoList[ TraderID ]
So a null TraderData means no TraderInfo, which means no stock and no quests — while the entity itself stays alive and interactable. That is exactly the reported symptom.
callvirt EntityTrader::get_TraderData()
dup
brtrue.s -> TraderData::Clone() <-- healthy: clone and send
pop
ldnull <-- BROKEN: package NULL...
stfld NetPackageTraderData::traderData ...and send it to the client
When a player opens a broken trader, the server takes the null, packages it, and transmits it — with no null check, no warning, and no log line. The client receives a trader payload containing null trader data and renders an empty trader.
This second one is a defect on inspection, independent of how the null arises: a null here should at minimum be guarded and logged, rather than silently serialised to the client. Had it been logged, this would have been trivially diagnosable instead of being reported as "traders randomly break."
2026-07-11T22:09:25 5975.176 WRN 229960 World unloadEntity !dict [type=EntityTrader, name=npcTraderBob, id=117646], Unloaded, was Unloaded
An entity-tracking desync on an EntityTrader ("Unloaded, was Unloaded"). I have not proven the exact path that leaves TraderData null — that part is a hypothesis. What is proven from the compiled code is that a null TraderData produces exactly the reported symptom, and that Setup() will happily ship that null to the client.
Game Version: V 3.0.1 (b4) — latest_experimental
OS/Platform: Linux dedicated server (LinuxServer 64 Bit), Windows clients
Game mode: Dedicated server, MP client
Did you wipe old saves? Yes — fresh world/seed created on V3.0.x
Did you start a new game? Yes
Did you validate your files? Yes
Are you using any mods? Yes — see "About the mods" below. No mod touches EntityTrader, TraderData, or NetPackageTraderData. Both defects below are visible by reading your own compiled code.
EAC on/off: ON
Status: Sometimes — recurring. Multiple players reporting it independently over several days. Resetting the trader restores it until it happens again.
Bug Description
Symptom: A trader stops working. The NPC is present and interactable, but the trade window shows no inventory to buy and the trader offers no quests. Resetting the trader fixes it.Root cause: EntityTrader.TraderData becomes null on the affected trader.
There are really two defects here — one causes it, one makes it invisible and ships it to the player.
Defect 1 — a trader with null TraderData has no TraderInfo
EntityTrader.get_TraderInfo():call get_TraderData()
brfalse -> ldnull; ret <-- TraderData null => TraderInfo NULL
The trader's stock list and quests are resolved through a static lookup:
TraderData.get_TraderInfo():
ldfld TraderData::TraderID
ldsfld TraderInfo::traderInfoList
-> traderInfoList[ TraderID ]
So a null TraderData means no TraderInfo, which means no stock and no quests — while the entity itself stays alive and interactable. That is exactly the reported symptom.
Defect 2 — the server serialises the null and sends it anyway
NetPackageTraderData.Setup(EntityTrader _entityTrader):callvirt EntityTrader::get_TraderData()
dup
brtrue.s -> TraderData::Clone() <-- healthy: clone and send
pop
ldnull <-- BROKEN: package NULL...
stfld NetPackageTraderData::traderData ...and send it to the client
When a player opens a broken trader, the server takes the null, packages it, and transmits it — with no null check, no warning, and no log line. The client receives a trader payload containing null trader data and renders an empty trader.
This second one is a defect on inspection, independent of how the null arises: a null here should at minimum be guarded and logged, rather than silently serialised to the client. Had it been logged, this would have been trivially diagnosable instead of being reported as "traders randomly break."
How the null likely arises (inferred, not proven)
Seen in the server log, and consistent with a trader being unloaded/reloaded into a state where its TraderData is not restored:2026-07-11T22:09:25 5975.176 WRN 229960 World unloadEntity !dict [type=EntityTrader, name=npcTraderBob, id=117646], Unloaded, was Unloaded
An entity-tracking desync on an EntityTrader ("Unloaded, was Unloaded"). I have not proven the exact path that leaves TraderData null — that part is a hypothesis. What is proven from the compiled code is that a null TraderData produces exactly the reported symptom, and that Setup() will happily ship that null to the client.
- Reproduction Steps
Steps to reproduce
Dedicated server, V3.0.1 b4, players active around trader compounds over a multi-hour session with chunks loading/unloading around traders. Over hours/days, one or more traders enter the broken state: present, interactable, but no stock and no quests.
It is not reproducible on demand from a cold start — it accumulates during normal play as traders are loaded and unloaded. Anything that forces a trader entity through an unload/reload cycle is the area to look at.
Once broken, it is 100% consistent for that trader until it is reset.
Actual result
Trader has no inventory and offers no quests. The server sends the client a trader payload with null trader data, silently.
Expected result
Trader retains its TraderData across unload/reload. Additionally, NetPackageTraderData.Setup() should not serialise a null TraderData silently — it should guard and log so the failure is visible.
Suggested fixes
- Find and fix the null. Ensure EntityTrader.TraderData survives unload/reload (the unloadEntity !dict desync above is the most likely lead).
- Guard the packet regardless — even after (1), this should never silently ship a null:
// NetPackageTraderData.Setup(EntityTrader)
var td = _entityTrader.TraderData;
if (td == null)
{
Log.Error($"Trader {_entityTrader.EntityName} (id {_entityTrader.entityId}) has null TraderData");
// repair, or refuse to send an empty trader
}
- Self-heal is cheap and safe. A null TraderData can be rebuilt with just:
if (TraderData == null) TraderData = new TraderData();
if (NPCInfo != null && NPCInfo.TraderID > 0) TraderData.TraderID = NPCInfo.TraderID;
Because TraderInfo is a static lookup by TraderID, this restores the trader's own correct stock and quests. (Note: calling the full EntityTrader.PostInit() to repair is not safe on a live entity — it chains into EntityAlive.PostInit() → ApplySpawnState() and GameEventManager.HandleSpawnModifier(), which are spawn-time operations.)
- Link to Logs
- https://discord.gg/ZfcsJU9TQC
- Link to Screenshot/Video
- https://discord.gg/ZfcsJU9TQC
Last edited:
