Сущности с множественными хитбоксами

Версия Minecraft
1.16.4
API
Forge
Пытаюсь сделать кастомного моба, который должен получать повреждения отдельно по разным частям тела. Для начал решил добавить ему второй хитбокс в районе головы. В качестве примера использовал ванильного дракона (он имеет определенный список сущностей-частей, которые позволяют наносить родительскому мобу урон по разным частям тела). Я сделал подобный класс сущности-части. Проблема в том, что я не понимаю, как правильно ее добавить в мир. Если сделать так же, как в ваниле (создавать экземпляр класса части без непосредственно спавна методом addEntity() класса World), сущность в мире не появится (я проверял ее координаты, и она находилась там, где и нужно, но ее хитбокса не было видно и взаимодействовать с ним я не мог). Я так понимаю, что тут дело в том, как forge регистрирует сущности. Но, в таком случае, как правильно создать сущность, состоящую из множества частей, которые будут иметь EntityType своего родителя? Буду благодарен за любые прояснения в этом вопросе.

Класс родительского Entity (код, не касающийся вопроса, вырезан):
Java:
public class EntityFowler extends ZombieEntity
{
    private byte meleeAttackTimer;

    private final EntityPart[] bodyParts;
    private final EntityPart headEntity;

    public EntityFowler(EntityType<? extends ZombieEntity> type, World worldIn)
    {
        super(type, worldIn);
        headEntity = new EntityPart(this, "head", 0.7f, 0.7f);
        bodyParts = new EntityPart[] { headEntity };
    }

    @Override
    public void livingTick()
    {
        setPartPosition(headEntity, 0, 0.6f, 0);

        super.livingTick();
    }

    private void setPartPosition(EntityPart part, double offsetX, double offsetY, double offsetZ)
    {
        //System.out.println(part.getPosX() + " " + part.getPosY() + " " + part.getPosZ());
        part.setPosition(this.getPosX() + offsetX, this.getPosY() + offsetY, this.getPosZ() + offsetZ);
    }
}

Код класса EntityPart:
Java:
public class EntityPart extends Entity
{
    public final String name;
    public final LivingEntity parent;
    private final EntitySize size;
    private boolean isCollidable = true;

    public EntityPart(LivingEntity parentEntity, String name, float width, float height) { this(parentEntity, name, width, height, true); }

    public EntityPart(LivingEntity parentEntity, String name, float width, float height, boolean isCollidable)
    {
        super(parentEntity.getType(), parentEntity.getEntityWorld());
        this.size = EntitySize.flexible(width, height);
        this.recalculateSize();
        this.parent = parentEntity;
        this.name = name;
        this.isCollidable = isCollidable;
    }

    @Override
    public boolean canBeCollidedWith() { return true; }

    public void setCollidable(boolean value) { isCollidable = value; }

    @Override
    public boolean attackEntityFrom(DamageSource source, float amount)
    {
        if(parent instanceof IMultipartEntity) return ((IMultipartEntity)parent).attackEntityPartFrom(this, source, amount);
        return !this.isInvulnerableTo(source) && this.parent.attackEntityFrom(source, amount);
    }

    @Override
    protected void registerData() { }

    @Override
    protected void readAdditional(CompoundNBT compound){}

    @Override
    protected void writeAdditional(CompoundNBT compound){}

    @Override
    public IPacket<?> createSpawnPacket()
    {
        //return NetworkHooks.getEntitySpawningPacket(this);
        throw new UnsupportedOperationException();
    }

    @Override
    public boolean isEntityEqual(Entity entityIn) { return entityIn == this || entityIn == parent; }

    @Override
    public EntitySize getSize(Pose pose) { return size; }

    @Override
    public ActionResultType applyPlayerInteraction(PlayerEntity player, Vector3d vec, Hand hand)
    {
        return super.applyPlayerInteraction(player, vec, hand);
    }
}
 
7,099
324
1,510
Можно заспаунить часть как обычно, в апдейте главной части нужно следить за синхронизацией движения.
Можно сделать прикольные вещи, по типу отлетания башки, когда зомбака бьют с кнокбэком
 
Синхронизация есть (метод setPartPosition()). По поводу обычного спавна - я пробовал это делать. Проблема в том, что тогда спавнится моб родитель, а не часть. По крайней мере, оно имеет модель родителя (хотя модели у части вообще не должно быть) и его хитбокс такой же, как у родителя. А вообще, мне интересно, можно ли средствами форжа реализовать мультихитбокс, аналогичный ванильной реализации дракона.
 
7,099
324
1,510
Зачем создавать экземпляр второй сущности внутри конструктора первой? Это может вызвать проблемы при загрузке мира.
Обе должны создаваться и спауниться внешним кодом
А вообще, мне интересно, можно ли средствами форжа реализовать мультихитбокс, аналогичный ванильной реализации дракона.
Если в ванили возможно, значит моды тоже могут сделать это
 
Зачем создавать экземпляр второй сущности внутри конструктора первой?
Это буквально то, как оно реализовано в ванили. Когда у меня не получилось сделать по своему, я сразу решил попробовать полностью ванильный метод. Увы, в чистом виде он не работает для модов. Есть подозрения, что это из-за того, как форж регистрирует объекты из модов. Хотя, возможно, что я упустил какую то деталь.
 
Последнее редактирование:
В итоге, все, что я нашел - это некоторые косвенные подтверждения того, что на данный момент такие сущности (как ванильный дракон) не могут быть реализованы средствами стандартного forge ванильными методами. Скорее всего, это связано с тем, как forge регистрирует объекты из модов. Судя по всему, на данный момент сущности-части лучше всего создавать отдельно, со своим собственным EntityType, без унаследования типа родителя и применять стандартный способ спавна - используя метод addEntity() класса World.
 
Сверху