Entity не работает на клиенте

Версия Minecraft
1.16.5
API
Forge
52
1
1
Метод net.minecraft.client.world.ClientWorld.addEntity не работает для моего Entity. Проверяю сторону в методе tick() - только сервер.

В консоли никаких ошибок или предупреждений не наблюдается.

Регистрация Entity:
Java:
DistExecutor.unsafeCallWhenOn(Dist.CLIENT, () -> () -> proxy = new ClientProxy());
proxy.registerHandlers();
IEventBus modBus = FMLJavaModLoadingContext.get().getModEventBus();
modBus.addGenericListener(EntityType.class, EntityRegistry::registryEntity);

Java:
public class EntityRegistry {

    public static final EntityType<EntityTransformationSpell> TRANSFORMATION_SPELL =
            EntityType.Builder.<EntityTransformationSpell>of(EntityTransformationSpell::new, EntityClassification.MISC)
                    .sized(0.5f, 0.5f)
                    .setShouldReceiveVelocityUpdates(true)
                    .setUpdateInterval(5)
                    .fireImmune()
                    .build("winter_crystal:spell_transformer");

    public static void registryEntity(final RegistryEvent.Register<EntityType<?>> entityRegisterEvent) {
        IForgeRegistry<EntityType<?>> reg = entityRegisterEvent.getRegistry();
        reg.register(TRANSFORMATION_SPELL.setRegistryName("winter_crystal:spell_transformer"));
    }
}

Регистрация рендера:

Java:
public void onClientSetupEvent(FMLClientSetupEvent event) {
        RenderTypeLookup.setRenderLayer(BlockRegistry.BLOCK_WEB, RenderType.solid());
        registerEntityRenderers();
    }

    private static void registerEntityRenderers() {
        RenderingRegistry.registerEntityRenderingHandler(EntityRegistry.TRANSFORMATION_SPELL, RenderNoop::new);
    }

Собственно, сам рендер:

Java:
// Code copied from Botania
public class RenderNoop<T extends Entity> extends EntityRenderer<T> {
    public RenderNoop(EntityRendererManager manager) {
        super(manager);
    }

    @Override
    public boolean shouldRender(T entity, @Nonnull ClippingHelper clipping, double x, double y, double z) {
        return false;     // Возвращаемое значение никак не помогает решить проблему
    }

    @Nonnull
    @Override
    public ResourceLocation getTextureLocation(T p_110775_1_) {
        return AtlasTexture.LOCATION_BLOCKS;
    }

}

Место, где ентити вызывается (вставлю только часть кода):

Java:
if (!world.isClientSide) {
                EntityTransformationSpell spell = new EntityTransformationSpell(world, player, binding);
                spell.setPos(player.position().x, player.position().y + 0.6, player.position().z);
                Vector3d vec = player.getDeltaMovement().normalize().scale(0.1);
                spell.setDeltaMovement(vec);
                world.addFreshEntity(spell);
            }

И сам ентити:

Java:
public class EntityTransformationSpell extends ThrowableEntity {

    private UUID throwerUUID = null;

    private UUID attachedEntityUUID = null;
    private int age;
    public static final int MAX_AGE = 60;

    public EntityTransformationSpell(EntityType<EntityTransformationSpell> entity, World world) {
        super(entity, world);
    }

    public EntityTransformationSpell(World world, @Nullable LivingEntity thrower, LivingEntity attachedEntity) {
        super(EntityRegistry.TRANSFORMATION_SPELL, world);
        if (attachedEntity != null)
            this.attachedEntityUUID = attachedEntity.getUUID();
        if (thrower != null)
            throwerUUID = thrower.getUUID();
        this.setNoGravity(true);
    }

    @Nullable
    public LivingEntity getThrower() {
        if(this.level instanceof ServerWorld && this.throwerUUID != null)
            return (LivingEntity) ((ServerWorld) level).getEntity(throwerUUID);
        return null;
    }

    @Nullable
    public LivingEntity getAttachedEntity() {
        if(this.level instanceof ServerWorld && this.attachedEntityUUID != null)
            return (LivingEntity) ((ServerWorld) level).getEntity(attachedEntityUUID);
        return null;
    }

    public int getAge() {
        return age;
    }

    public static int getMaxAge() {
        return MAX_AGE;
    }

    @Override
    public void tick() {
        System.out.println(this.level.isClientSide());
        for(int i = 0; i < 3; i++)
            level.addParticle(
                    new WispParticleType.WispParticleData(0.3f, 1.0f, 1.0f, 1.0f),
                    true, position().x,
                    position().y,
                    position().z,
                    0,
                    0,
                    0);

        age++;
        if(age > MAX_AGE)
            this.remove();

        super.tick();
    }

    @Override
    public boolean isInWater() {
        return false;
    }

    @Override
    public boolean isInLava() {
        return false;
    }

    @Override
    protected void onHitBlock(BlockRayTraceResult block) {
        super.onHitBlock(block);
        if(!level.getBlockState(block.getBlockPos()).isAir())
            this.remove();
    }

    @Override
    protected void onHitEntity(@Nonnull EntityRayTraceResult rtr) {
        if(!(rtr.getEntity() instanceof LivingEntity))
            return;
        LivingEntity entity = (LivingEntity) rtr.getEntity();
        LivingEntity attachedEntity = this.getAttachedEntity();
        if(attachedEntity == null) {
            LOGGER.warn("Attached entity cannot be null");
            return;
        }
        if (entity.getUUID() == this.throwerUUID) return;
        LivingTransformation transformation = entity.getCapability(TransformerProvider.TRANSFORMATION).orElse(null);
        if(transformation != null) {
            LivingEntity result = transformation.getResult().orElse(attachedEntity);
            if(attachedEntity.equals(result))
                transformation.addCharge(1);
            else {
                transformation.setResult(attachedEntity);
                transformation.setCharge(1);
            }
            if(transformation.getCharge() == transformation.getChargeForTransformation())
                transformation.transform(entity, result);
        }

        this.remove();
    }

    @Override
    protected void defineSynchedData() {}

    @Override
    public @Nonnull IPacket<?> getAddEntityPacket() {
        return new SSpawnObjectPacket(this);
    }

    private static final String ENTITY_TAG = "AttachedEntity";
    private static final String THROWER_NBT = "Thrower";
    private static final String AGE_TAG = "Age";

    @Override
    public void addAdditionalSaveData(CompoundNBT compound) {
        if (this.attachedEntityUUID != null) {
            compound.putUUID(ENTITY_TAG, this.attachedEntityUUID);
        }
        if (this.throwerUUID != null) {
            compound.putUUID(THROWER_NBT, this.throwerUUID);
        }
        compound.putInt(AGE_TAG, this.age);
    }

    @Override
    public void readAdditionalSaveData(CompoundNBT compound) {
        if (compound.contains(ENTITY_TAG) && this.level instanceof ServerWorld) {
             this.attachedEntityUUID = compound.getUUID(ENTITY_TAG);
        }
        if (compound.contains(THROWER_NBT)) {
            this.throwerUUID = compound.getUUID(THROWER_NBT);
        }
        this.age = compound.getInt(AGE_TAG);
    }


}
 
1,373
113
241
1) Призыв Entity осуществляется ТОЛЬКО со стороны сервера (ибо вся логика сущности расположена на сервере. Если ты призовёшь сущность на клиенте, то она переживёт экзистенциальный кризис и впадёт в ступор, в большинстве случаев).
2) Ты в месте, где используется клиентский мир (предположительно, судя из контекста темы), проверяешь его на серверный мир:
Браво.
 
52
1
1
Призыв Entity осуществляется ТОЛЬКО со стороны сервера
Я и призываю сущность только на сервере. Это происходит именно в том месте, на которое ты мне указал (как на ошибку).

Суть в том, что метод addEntity реализован ещё в классе ClientWorld (только он уже майновским кодом вызывается, не нами). И он отвечает как раз-таки за то, чтобы добавить entity в список, во время итерации которого и вызывается метод tick со стороны клиента (что у меня не происходит).
 
Сверху