В месте падения снаряда не вызываются партиклы

Версия Minecraft
1.12.2
API
Forge
Привет всем! Пытаюсь добавить магический посох, который бы выстреливал снарядом, который в свою очередь касаясь моба или твердой поверхности вызывал бы эффект снежной метели из партиклов с периодическим аое уроном.
Т.к статьи по этому поводу в учебнике не нашел, колхозил это дело с чатом гпт.
Проблема следующая: снаряд вылетает из посоха, в полете за ним создаются партиклы (пока все по плану), снаряд об что то ударяется. Далее спавнится область с переодическим уроном, но вот партиклы очерчивающие область уже спавниться не хотят.
Я создал событие в котором при прыжке игрока должен вызваться этот же метод (отвечающий за спавн цилиндра из партиклов) и там все работает.
Прилагаю код:

package com.vinelles.testmod.entities;

import com.vinelles.testmod.particle_effects.ParticleCircle;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.util.DamageSource;
import net.minecraft.util.EnumParticleTypes;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.world.World;

import java.util.List;

public class EntityBlizzard extends Entity {
private int duration = 100; // Продолжительность метели в тиках (5 секунд)
private int tickCounter = 0;
private double radius = 5.0;

public EntityBlizzard(World worldIn) {
super(worldIn);
this.setSize(1.0F, 1.0F);
}

public EntityBlizzard(World worldIn, double x, double y, double z) {
this(worldIn);
this.setPosition(x, y, z);
}

@Override
protected void entityInit() {
}

@Override
protected void readEntityFromNBT(NBTTagCompound compound) {
}

@Override
protected void writeEntityToNBT(NBTTagCompound compound) {
}

@Override
public void onUpdate() {
super.onUpdate();

tickCounter++;

if (world.isRemote) {


int layers = 5; // Количество слоев
int particleCount = 20; // Количество частиц в каждом слое


// Создание цилиндра из частиц
ParticleCircle.createCylinder(world, this.posX, this.posY, this.posZ, radius, layers, particleCount, EnumParticleTypes.EXPLOSION_NORMAL);
}

// Нанесение урона всем сущностям в радиусе каждую секунду
if (!this.world.isRemote && tickCounter % 20 == 0) { // Каждые 20 тиков (1 секунда)

List<EntityLivingBase> nearbyEntities = this.world.getEntitiesWithinAABB(EntityLivingBase.class, new AxisAlignedBB(
posX - radius, posY - 1, posZ - radius, posX + radius, posY + 1, posZ + radius));
for (EntityLivingBase entity : nearbyEntities) {
entity.attackEntityFrom(DamageSource.MAGIC, 2.0F); // Урон магией
}
}

// Если метель длится дольше 5 секунд, отключаем её
if (!this.world.isRemote && tickCounter >= duration) {
this.setDead();
}
}
}

Класс ParticleCircle:
package com.vinelles.testmod.particle_effects;

import net.minecraft.util.EnumParticleTypes;
import net.minecraft.world.World;

public class ParticleCircle {

public static void createCylinder(World world, double centerX, double centerY, double centerZ, double radius, int layers, int particleCount, EnumParticleTypes particleType) {
for (int layer = 0; layer < layers; layer++) {
double yOffset = layer * 0.5; // Высота каждого слоя

for (int i = 0; i < particleCount; i++) {
double angle = 2 * Math.PI * i / particleCount;
double xOffset = Math.cos(angle) * radius;
double zOffset = Math.sin(angle) * radius;
world.spawnParticle(particleType, centerX + xOffset, centerY + yOffset, centerZ + zOffset, 0.0, 0.0, 0.0);
}
}
}
}


Там где ParticleCircle нормально функционирует:
@SubscribeEvent
public void onPlayerJump(LivingEvent.LivingJumpEvent event) {
if (event.getEntityLiving() instanceof EntityPlayer) {
EntityPlayer player = (EntityPlayer) event.getEntityLiving();
World world = player.world;

if (world.isRemote) {
double radius = 2.0; // Радиус цилиндра
int layers = 5; // Количество слоев
int particleCount = 20; // Количество частиц в каждом слое

// Создание цилиндра из частиц
ParticleCircle.createCylinder(world, player.posX, player.posY, player.posZ, radius, layers, particleCount, EnumParticleTypes.EXPLOSION_NORMAL);
}
}
}
 
Прикол в том, что твой entity располагается на сервере. Соответственно, получаешь серверный мир, а потом еще и проверяешь его на клиентский. Результат - ничего не работает. Для такого отправляй серверный пакет на клиентскую сторону.
Тутор по отправке серверного пакета с частицами на форуме имеется: \https://forum.mcmodding.ru/resources/spavn-chastic.196/ (хоть и написано для 1.7.10, но на 1.12 у меня вполне работало)

UPD: у WorldServer уже имеется готовая реализация отправки подобного серверного пакета: WorldServer::spawnParticle(). Так что, в твоем случае, лучше убрать проверку на клиентский мир, т.к. она не выполниться
 
Последнее редактирование:
Так что частицы можно перенести в твой рендерер, тогда и костыли с пакетами городить не придётся.
С учетом того, что TC нужно рендерить партиклы в момент попадания снаряда во что-либо, то действительно лучше будет отправить пакет с партиклами клиенту. Пример - EntityLargeFireball
 
С учетом того, что TC нужно рендерить партиклы в момент попадания снаряда во что-либо
Только, из того что я вижу, метель != снаряд.
Как я понял, у него есть условная стрела, стрела попала в энтити/блок, призвалась метель. И вот метель - энтити, которая живёт n времени. Тогда отправлять пакет на клиент с временем жизни партиклов не гуд и больше костыль.
 
Только, из того что я вижу, метель != снаряд.
Как я понял, у него есть условная стрела, стрела попала в энтити/блок, призвалась метель. И вот метель - энтити, которая живёт n времени. Тогда отправлять пакет на клиент с временем жизни партиклов не гуд и больше костыль.
Да вы правы. Есть посох:
public class FrostStaff extends Item {
public FrostStaff() {
this.setRegistryName("frost_staff");
this.setUnlocalizedName("frost_staff");
this.setMaxStackSize(1);
this.setCreativeTab(CreativeTabs.COMBAT);
this.setMaxDamage(100);
}

@Override
public ActionResult<ItemStack> onItemRightClick(World world, EntityPlayer player, EnumHand hand) {
ItemStack itemStack = player.getHeldItem(hand);

if (!world.isRemote) {
// Стреляем снарядом
Vec3d look = player.getLookVec();
EntityFrostStar snowball = new EntityFrostStar(world, player);
snowball.setPosition(player.posX + look.x * 1.5, player.posY + player.getEyeHeight(), player.posZ + look.z * 1.5);
snowball.shoot(player, player.rotationPitch, player.rotationYaw, 0.0F, 1.5F, 1.0F);

world.spawnEntity(snowball);

// Проигрываем звук выстрела
world.playSound(null, player.posX, player.posY, player.posZ, SoundEvents.BLOCK_NOTE_GUITAR, SoundCategory.PLAYERS, 0.5F, 0.4F / (itemRand.nextFloat() * 0.4F + 0.8F));

// Добавляем эффект замедления
itemStack.damageItem(1, player);
}

player.swingArm(hand);
return new ActionResult<>(EnumActionResult.SUCCESS, itemStack);
}
}

Который швыряет снаряд:

public class EntityFrostStar extends EntityThrowable {
private int slownessDuration = 100; // Продолжительность замедления в тиках (5 секунд)
private int slownessAmplifier = 1; // Уровень замедления
private float damage = 4.0F; // Урон снаряда

public EntityFrostStar(World worldIn) {
super(worldIn);
}

public EntityFrostStar(World worldIn, EntityLivingBase throwerIn) {
super(worldIn, throwerIn);
}

@Override
protected void onImpact(RayTraceResult result) {
if (!this.world.isRemote) {
if (result.entityHit != null && result.entityHit != this.getThrower()) {
result.entityHit.attackEntityFrom(DamageSource.causeThrownDamage(this, this.getThrower()), damage);
if (result.entityHit instanceof EntityLivingBase) {
EntityLivingBase entity = (EntityLivingBase) result.entityHit;
entity.addPotionEffect(new PotionEffect(MobEffects.SLOWNESS, slownessDuration, slownessAmplifier)); // Замедление
}
}

// Спавним метель на месте удара
EntityBlizzard blizzard = new EntityBlizzard(world, result.hitVec.x, result.hitVec.y, result.hitVec.z);
this.world.spawnEntity(blizzard);
}
this.setDead();
}

@Override
public void onUpdate() {
super.onUpdate();

// Создание частиц для летящего снаряда
for (int i = 0; i < 4; ++i) {
this.world.spawnParticle(EnumParticleTypes.SNOWBALL, this.posX, this.posY, this.posZ, 0.0D, 0.0D, 0.0D);
}
}

public void setSlownessEffect(int duration, int amplifier) {
this.slownessDuration = duration;
this.slownessAmplifier = amplifier;
}

public void setDamage(float damage) {
this.damage = damage;
}
}

Который в свою очередь и призывает метель.
 
Назад
Сверху