Build: Citizens 2.0.42 (b4173) on Folia 1.21.11-14-529aabc.
Spawning any Citizens NPC (reproduced with a PLAYER NPC) intermittently NPEs in the spawn callback because NMS.setLocationDirectly(getEntity(), location) is called before checking that getEntity() is non-null.
java.lang.NullPointerException: Cannot invoke "org.bukkit.entity.Entity.getWorld()" because "entity" is null
at net.citizensnpcs.nms.v1_21_R7.util.NMSImpl.setLocationDirectly(NMSImpl.java:1662)
at net.citizensnpcs.util.NMS.setLocationDirectly(NMS.java:960)
at net.citizensnpcs.npc.CitizensNPC.lambda$spawn$1(CitizensNPC.java:341)
at net.citizensnpcs.nms.v1_21_R7.util.NMSImpl.lambda$addEntityToWorld$0(NMSImpl.java:442)
at net.citizensnpcs.api.util.schedulers.adapter.FoliaScheduler.lambda$runRegionTask$1(FoliaScheduler.java:101)
at io.papermc.paper.threadedregions.scheduler.FoliaRegionScheduler$LocationScheduledTask.run(FoliaRegionScheduler.java:334)
CitizensNPC.java#L341:
entityController.spawn(location, couldSpawn -> {
if (!couldSpawn) { ... return; }
NMS.setLocationDirectly(getEntity(), location); // ← getEntity() can be null on Folia
NMS.setHeadAndBodyYaw(getEntity(), location.getYaw());
...
Consumer<Runnable> postSpawn = new Consumer<Runnable>() {
public void accept(Runnable cancel) {
if (getEntity() == null || (!hasTrait(PacketNPC.class) && !getEntity().isValid())) {
...
On Folia, addEntityToWorld queues the actual level.addFreshEntity as a region task; couldSpawn=true only means addFreshEntity returned true, not that AbstractEntityController.bukkitEntity is still non-null when the callback fires (something else on the region tick can have remove()'d the controller between create + callback). The postSpawn retry loop already null-guards getEntity() — the two NMS calls right above it should too.
Suggested fix: null-check at the top of the couldSpawn=true branch and fall into the existing NPCNeedsRespawnEvent retry path so the spawn just retries instead of throwing.
Entity entity = getEntity();
if (entity == null) {
entityController.remove();
Bukkit.getPluginManager().callEvent(new NPCNeedsRespawnEvent(this, location));
data().remove(NPC.Metadata.NPC_SPAWNING_IN_PROGRESS);
return;
}
NMS.setLocationDirectly(entity, location);
NMS.setHeadAndBodyYaw(entity, location.getYaw());
Build: Citizens 2.0.42 (b4173) on Folia 1.21.11-14-529aabc.
Spawning any Citizens NPC (reproduced with a PLAYER NPC) intermittently NPEs in the spawn callback because
NMS.setLocationDirectly(getEntity(), location)is called before checking thatgetEntity()is non-null.CitizensNPC.java#L341:
On Folia,
addEntityToWorldqueues the actuallevel.addFreshEntityas a region task;couldSpawn=trueonly meansaddFreshEntityreturned true, not thatAbstractEntityController.bukkitEntityis still non-null when the callback fires (something else on the region tick can haveremove()'d the controller between create + callback). ThepostSpawnretry loop already null-guardsgetEntity()— the two NMS calls right above it should too.Suggested fix: null-check at the top of the
couldSpawn=truebranch and fall into the existingNPCNeedsRespawnEventretry path so the spawn just retries instead of throwing.