Skip to content

Fix NPE in foliaAdapt entity wrapper when getEquipment() is null#26

Open
rugiaDev wants to merge 3 commits into
toxicity188:masterfrom
rugiaDev:master
Open

Fix NPE in foliaAdapt entity wrapper when getEquipment() is null#26
rugiaDev wants to merge 3 commits into
toxicity188:masterfrom
rugiaDev:master

Conversation

@rugiaDev

Copy link
Copy Markdown

Problem

On Paper (1.21.10+), MythicMobs throws an NPE from the BetterHealthBar
folia-adapt wrapper while a mob is being unloaded:

Cause

In v1_21_R6/v1_21_R7/v26_R1, the foliaAdapt(LivingEntity) wrapper
overrides getEquipment(): EntityEquipment (non-null) and delegates to the
underlying entity. Paper's CraftLivingEntity.getEquipment() is annotated
@NotNull, so Kotlin inserts a not-null call assertion — but the value is
actually null at runtime for entities without equipment, and for a mob mid
unload. The assertion then throws.

(v1_21_R5 and earlier declared the override as EntityEquipment?, so they
were never affected — this is a regression introduced when the override return
type became non-null.)

Fix

Read equipment through the @Nullable Bukkit LivingEntity interface (so no
not-null assertion is generated) and fall back to an empty
CraftEntityEquipment when null. This never returns null and never throws, so
any caller is safe — MythicMobs, for example, already null-checks the result.

The same null-safe pattern is mirrored on the player wrapper for consistency
(players always have equipment, so it never threw there, but the code is now
uniform).

Affected modules: v1_21_R6, v1_21_R7, v26_R1.

rugiaDev and others added 3 commits June 29, 2026 19:05
…unload

Paper's CraftLivingEntity.getEquipment() is annotated @NotNull but returns
null at runtime for entities without equipment and while a mob is being
unloaded. Kotlin inserts a not-null call assertion on the @NotNull return,
which throws "getEquipment(...) must not be null" when MythicMobs reads the
main-hand item during ActiveMob unload.

Add -Xno-call-assertions to the affected NMS modules (v1_21_R6, v1_21_R7,
v26_R1) so the genuine null passes through to callers, which already
null-check it (e.g. MythicMobs returns Optional.empty()).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…f a compiler flag

Replace the previous -Xno-call-assertions workaround with an explicit null
check, scoped to the one method that needs it.

CraftLivingEntity.getEquipment() is annotated @NotNull but returns null at
runtime for entities without equipment and while a mob is being unloaded.
The overridden getEquipment() now reads through the @nullable Bukkit
LivingEntity interface (so Kotlin inserts no not-null call assertion) and
falls back to an empty CraftEntityEquipment when null. This never returns
null and never throws, so any caller is safe -- not just ones that null-check.

This keeps the module's other Kotlin not-null assertions intact (the compiler
flag had disabled them module-wide).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…r wrapper

Players always have equipment so this never threw, but mirror the entity
wrapper's pattern for consistency: read getEquipment() through the @nullable
Bukkit LivingEntity interface and fall back to an empty CraftEntityEquipment.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant