Проблемы со спавном частиц

Версия Minecraft
1.12.2
Пытаюсь реализовать спавн частиц для своей TileEntity. Столкнулся со следующей проблемой: вызов частиц хоть и работает, но с оговорками - при попытке смещения точки спавна в центр блока путем добавления + 0.5D к координатам x и z вылетает ошибка по причине "Ticking particle". Вдобавок у меня есть подозрения, что частицы спавнятся только на клиенте и не будут видны при игре на сервере (я плохо понимаю методы, написанные разработчиками игры для спавна частиц). Очевидное решение - использовать метод randomDisplayTick в классе блока - мне не подходит, т.к. мне нужно иметь контроль над спавнрейтом частиц, а в этом методе они создаются случайно. Также я бы все таки хотел вызывать спавн из класса TileEntity, если это возможно. Прошу знающих подсказать правильную методику спавна частиц. Заранее спасибо.
Для вызова частиц использую:

Java:
Minecraft.getMinecraft().effectRenderer.spawnEffectParticle(EnumParticleTypes.CLOUD.getParticleID(), this.pos.getX(), this.pos.getY() + 1, this.pos.getZ(), 0, 0, 0, new int[0]);
(Строка в коде ниже: 74)

Код метода update класса TileEntity:
Java:
public void update()
    {
        boolean canEjectSteam = !this.world.getBlockState(this.pos.up(1)).isFullCube();
        boolean flag = this.isBurning();
        boolean flag1 = false;

        if (this.isBurning())
        {
            --this.burnTime;
        }

        if (!this.world.isRemote)
        {
            ItemStack fuel = this.inventory.get(0);

            if (this.isBurning() || !fuel.isEmpty() && !((ItemStack)this.inventory.get(0)).isEmpty())
            {
                if (!this.isBurning() && this.waterAmount >= waterConsumptionRate)
                {
                    this.burnTime = getItemBurnTime(fuel);
                    this.totalBurnTime = this.burnTime;

                    if (this.isBurning())
                    {
                        flag1 = true;

                        if (!fuel.isEmpty())
                        {
                            Item item = fuel.getItem();
                            fuel.shrink(1);

                            if (fuel.isEmpty())
                            {
                                ItemStack item1 = item.getContainerItem(fuel);
                                this.inventory.set(0, item1);
                            }
                        }
                    }
                }

                if(this.isBurning())
                {
                    if(this.temperature < maxTemperature) this.temperature++;
                }
            }

            if (this.temperature >= conversionTemperature && this.waterAmount >= waterConsumptionRate)
            {
                this.waterAmount -= waterConsumptionRate;
                if(this.steamAmount <= internalVolumeSteam - waterConsumptionRate * conversionFactor)
                {
                    this.steamAmount += waterConsumptionRate * conversionFactor;
                }
            }

            if(!this.isBurning() && this.temperature > minTemperature) this.temperature--;

            if (flag != this.isBurning())
            {
                flag1 = true;
                BlockSteamBoiler.setState(this.isBurning(), this.world, this.pos);
            }

            int multiplier = internalVolumeSteam / maxPressure;
            int targetPressure = MathHelper.clamp((this.steamAmount / multiplier), minPressure, canEjectSteam ? ejectionPressure : maxPressure);
            if(this.pressure <= targetPressure)
            {
                if(this.pressure < ejectionPressure)
                {
                    this.pressure++;
                }
                else if(this.isBurning() && waterAmount >= waterConsumptionRate)
                {
                    Minecraft.getMinecraft().effectRenderer.spawnEffectParticle(EnumParticleTypes.CLOUD.getParticleID(), this.pos.getX(), this.pos.getY() + 1, this.pos.getZ(), 0, 0, 0, new int[0]);
                    this.pressure++;
                }
            }
            else if(this.pressure > targetPressure)
            {
                this.pressure--;
            }

            if(this.pressure >= maxPressure)
            {
                this.world.createExplosion((Entity)null, this.pos.getX(), this.pos.getY(), this.pos.getZ(), 1.0F, true);
            }
        }

        if (flag1)
        {
            this.markDirty();
        }
    }
 
Решение
Скинь краш. Крашит, когда используешь дробные координаты?
~~~
Чтобы спавнить частицы из тайла можно юзать World#addBlockEvent, когда эта штука вызывается на сервере, то отправляется ванильный пакет на клиент и на клиенте вызывается...
5,397
179
984
Скинь краш. Крашит, когда используешь дробные координаты?
~~~
Чтобы спавнить частицы из тайла можно юзать World#addBlockEvent, когда эта штука вызывается на сервере, то отправляется ванильный пакет на клиент и на клиенте вызывается метод receiveClientEvent у блока для которого был вызвал addBlockEvent.
Пример:
Частицы спаунятся, когда происходит крафт на кастомном верстаке. Крафт происходит в тайле на сервере
 
Решение
Спасибо за наводку, но, к сожалению, я не совсем понимаю код на scala. Мне так же непонятен принцип работы этих ивентов, а точнее мне не понятно, как с помощью метода addBlockEvent спавнить частицы. В ваших примерах этот метод вызывается из класса блока. Как там происходит взаимодействие с TileEntity ?
 
5,397
179
984
Когда на сервере вызывается addBlockEvent, то на клиенте вызывается receiveClientEvent, его можно переопределить и добавить логику спавна частиц.
addBlockEvent вызывается из тайла, или вообще откуда угодно
 
Хм. А почему в примере в receiveClientEvent делается проверка isRemote, и если сторона - сервер, то выполняется действие? Я в итоге должен вызывать спавн частиц с помощью этого ивента используя клиентские методы спавна, или серверные?
 
5,397
179
984
Метод receiveClientEvent должен находиться именно в классе блока, правильно? Но у меня нет доступных для перезаписи методов с таким названием. Мой блок обязан быть BlockContainer'ом?
 
5,397
179
984
receiveClientEvent это метод тайла, извини, мне показалось, что блока, бегло смотрел свой старый код xD
 
Я добавил в тайл этот метод. Частицы в игре не появляются...
Вот код:
Java:
@Override
    public boolean receiveClientEvent(int id, int type)
    {
        if(this.world.isRemote)
        {
            world.spawnParticle(EnumParticleTypes.CLOUD, this.pos.getX(), this.pos.getY(), this.pos.getZ(), 0, 0.2D, 0, new int[0]);
            return true;
        }

        return false;
    }
А это фрагмент из update():
Java:
if(!this.world.isRemote)
        {
            world.addBlockEvent(this.pos, world.getBlockState(pos).getBlock(), 0, 0);
        }
 
5,397
179
984
Нашел такой кусок кода в BlockContainer
Java:
/**
     * Called on server when World#addBlockEvent is called. If server returns true, then also called on the client. On
     * the Server, this may perform additional changes to the world, like pistons replacing the block with an extended
     * base. On the client, the update may involve replacing tile entities or effects such as sounds or particles
     */
public boolean eventReceived(IBlockState state, World worldIn, BlockPos pos, int id, int param)
{
    super.eventReceived(state, worldIn, pos, id, param);
    TileEntity tileentity = worldIn.getTileEntity(pos);
    return tileentity == null ? false : tileentity.receiveClientEvent(id, param);
}
Если твой блок наследует че-то другое+ITileEntityProvider, то добавь себе это переопределение
 
У меня получилось заставить частицы появляться, но сделал я это путем вызова addBlockEvent() при условии world.isRemote (Код ниже). Но так как было сказано, что этот метод должен вызываться на сервере, мне кажется, что эти частицы не будут видны на сервере. Или я не прав?
Код:
1. update() в TE
Java:
if(this.world.isRemote)
        {
            world.addBlockEvent(this.pos, (BlockSteamBoiler)(world.getBlockState(pos).getBlock()), 0, 0);
        }
2. Получение события на клиенте в TE
Java:
@Override
    public boolean receiveClientEvent(int id, int type)
    {
        if(this.world.isRemote)
        {
            world.spawnParticle(EnumParticleTypes.CLOUD, this.pos.getX(), this.pos.getY(), this.pos.getZ(), 0, 0.2D, 0, new int[0]);
            return true;
        }

        return false;
    }
3. Метод в классе блока
Java:
@Override
    public boolean eventReceived(IBlockState state, World worldIn, BlockPos pos, int id, int param)
    {
        super.eventReceived(state, worldIn, pos, id, param);
        TileEntity tileentity = worldIn.getTileEntity(pos);
        return tileentity == null ? false : ((TileEntitySteamBoiler)tileentity).receiveClientEvent(id, param);
    }
 
5,397
179
984
If server returns true, then also called on the client
Ты неправильно реализовал receiveClientEvent, на сервере он всегда возвращает false и на клиент никогда не отправляются пакеты.
Возвращай true когда параметры соответствуют события спавна частиц. Например, в моем коде id == 5 это частицы, в этом случае receiveClientEvent возвращает true, при этом проверка if(this.world.isRemote) не влияет на возвращаемое значение
 
Я поменял проверку в update(), чтобы задействовать addBlockEvent() со стороны сервера и переделал receiveClientEvent(). Частицы видны в игре. Предоставляю код измененного метода:

Java:
@Override
    public boolean receiveClientEvent(int id, int type)
    {
        if(id == 0)
        {
            if (this.world.isRemote)
            {
                world.spawnParticle(EnumParticleTypes.CLOUD, this.pos.getX() + 0.5D, this.pos.getY() + 1, this.pos.getZ() + 0.5D, 0, 0.2D, 0, new int[0]);
            }

            return true;
        }
        return false;
    }
Спасибо большое за помощь! Думаю, если больше нет замечаний, можно закрывать тему.
 
Сверху