Философия погромирования

205
12
103
Всем привет. За последнее время у меня всё чаще начинает назревать вопрос о правильной структуризации своего проекта. Расскажу небольшую предысторию на примере реальной проблемы:
Я занимаюсь портированием контента игры Terraria на кубач версии 1.15.2. Цель довольно абстрактная и глупая, согласен, но я занимаюсь этим не для чего-либо/кого-либо, а просто для практики и понимания. Сейчас у меня возникает такая проблема: наплодилось достаточно много классов с примерно похожим содержанием, и, ладно, если бы они были по 40 строк, но тут их обычно около 200. Допустим, недавно я портировал все виды бумерангов в игру. На каждый такой бумеранг нужно по 3 класса: предмет, энтить, рендер. Предмет строк на 50, в нем контент зачастую одинаковый, просто указывается какая энтить будет бросаться и какой размер стака броска. Энтить обычно на ~200 строк, и тут уже все сложнее. Отличия, в основном, заключаются в следующем: какие партиклы, какой звук, есть ли шанс на доп.дроп или какие-либо бонусы, какой урон и эффекты, реже - особая физика и всякое такое. И я не знаю, правильно ли, что у меня так много классов с примерно похожим контентом под это дело. Можно и нужно ли это как-то стандартизировать. если нужно, то как лучше?

В качестве примера прилагаю один такой класс:

Java:
public class LightDiscEntity extends ProjectileItemEntity {
    private int TICKS_UNTIL_RETURN = 25;
    private static final int TICKS_UNTIL_DEATH = 300;
    private static final double SPEED = 1.0;
    private static final int MAX_BOUNCES = 2;
    private static final DataParameter<Byte> IS_RETURNING = EntityDataManager.createKey(LightDiscEntity.class, DataSerializers.BYTE);
    private static DataParameter<Integer> BOUNCES = EntityDataManager.createKey(LightDiscEntity.class, DataSerializers.VARINT);
    private static final DataParameter<String> OWNER = EntityDataManager.createKey(LightDiscEntity.class, DataSerializers.STRING);
    private static boolean SUCCESSFULLY_BOUNCED = false;
    private ItemStack lightDiscThrown = ItemStack.EMPTY;
    private PlayerEntity targetEntity;

    public LightDiscEntity(EntityType<? extends ProjectileItemEntity> type, World worldIn) {
        super(type, worldIn);
    }

    public LightDiscEntity(World worldIn, LivingEntity throwerIn) {
        super(ProjectileEntities.Entities.light_disk, throwerIn, worldIn);
    }

    @Override
    protected void registerData() {
        super.registerData();
        this.dataManager.register(IS_RETURNING, (byte) 0);
        this.dataManager.register(BOUNCES, 0);
        this.dataManager.register(OWNER, "");
    }

    public void setLightDiscEntityThrown(ItemStack lightDiscThrown) {
        this.lightDiscThrown = lightDiscThrown;
    }

    @Override
    public void writeAdditional(CompoundNBT tag) {
        tag.putString("owner", dataManager.get(OWNER));
        tag.putByte("returning", dataManager.get(IS_RETURNING));
        tag.putInt("bounces", dataManager.get(BOUNCES));
        lightDiscThrown.write(tag);
        super.writeAdditional(tag);
    }

    @Override
    public void readAdditional(CompoundNBT tag) {
        dataManager.set(OWNER, tag.getString("owner"));
        dataManager.set(IS_RETURNING, tag.getByte("returning"));
        dataManager.set(BOUNCES, tag.getInt("bounces"));
        lightDiscThrown = ItemStack.read(tag);
        super.readAdditional(tag);
    }

    public void setOwner(PlayerEntity playerIn) {
        this.targetEntity = playerIn;
        if (playerIn != null && playerIn.getUniqueID() != null)
            dataManager.set(OWNER, playerIn.getUniqueID().toString());
    }

    private void movementReturnCheck() {
        boolean returning = dataManager.get(IS_RETURNING) == 1;
        if (returning) {
            if (this.targetEntity != null) {
                this.moveTowardsTarget();
            }
        }
    }

    public static float yawDegreesBetweenPoints(double posX, double posY, double posZ, double posX2, double posY2, double posZ2) {
        float f = (float) ((180.0f * Math.atan2(posX2 - posX, posZ2 - posZ)) / (float) Math.PI);
        return f;
    }

    public static float pitchDegreesBetweenPoints(double posX, double posY, double posZ, double posX2, double posY2, double posZ2) {
        return (float) Math.toDegrees(Math.atan2(posY2 - posY, Math.sqrt((posX2 - posX) * (posX2 - posX) + (posZ2 - posZ) * (posZ2 - posZ))));
    }

    public static Vec3d lookVector(float rotYaw, float rotPitch) {
        return new Vec3d(
                Math.sin(rotYaw) * Math.cos(rotPitch),
                Math.sin(rotPitch),
                Math.cos(rotYaw) * Math.cos(rotPitch));
    }

    private void moveTowardsTarget() {
        rotationYaw = (float) Math.toRadians(yawDegreesBetweenPoints(getPosX(), getPosY(), getPosZ(), targetEntity.getPosX(), targetEntity.getPosY(), targetEntity.getPosZ()));
        rotationPitch = (float) Math.toRadians(pitchDegreesBetweenPoints(getPosX(), getPosY(), getPosZ(), targetEntity.getPosX(), targetEntity.getPosY(), targetEntity.getPosZ()));
        Vec3d moveVec = lookVector(this.rotationYaw, this.rotationPitch).scale(SPEED);
        this.setMotion(
                0.5f * this.getMotion().getX() + 0.5F * moveVec.x,
                0.5f * this.getMotion().getY() + 0.5F * moveVec.y,
                0.5f * this.getMotion().getZ() + 0.5F * moveVec.z);
    }


    private void setIsReturning() {
        dataManager.set(IS_RETURNING, (byte) 1);
    }

    @Override
    public void tick() {
        Vec3d defaultMotion = getMotion();

        super.tick();

        if (!SUCCESSFULLY_BOUNCED) {
            setMotion(defaultMotion);
        }

        SUCCESSFULLY_BOUNCED = false;

        if (this.ticksExisted > TICKS_UNTIL_DEATH) {
            this.remove();
            return;
        }

        if (this.ticksExisted > TICKS_UNTIL_RETURN) {
            setIsReturning();
        }

        final BlockPos pos = this.getPosition();

        if (owner != null && TerraUtils.distanceBetweenHorizontal(pos, this.owner.getPosition()) < 1.0F && this.ticksExisted > 10) {
            this.remove();
            return;
        }
        movementReturnCheck();
    }

    @Override
    protected void onImpact(@Nonnull RayTraceResult rayTraceResult) {
        if (!world.isRemote) {
            switch (rayTraceResult.getType()) {
                case BLOCK: {
                    BlockRayTraceResult blockRayTraceResult = (BlockRayTraceResult) rayTraceResult;
                    if (world.getBlockState(blockRayTraceResult.getPos()).isSolid()) {
                        Vec3d currentMovementVec = new Vec3d(getMotion().x, getMotion().y, getMotion().z);
                        int bounces = getTimesBounced();
                        if (bounces < MAX_BOUNCES) {
                            Direction dir = blockRayTraceResult.getFace();
                            Vec3d normalVector = new Vec3d(-2 * dir.getXOffset(), -2 * dir.getYOffset(), -2 * dir.getZOffset()).normalize();
                            Vec3d movementVec = normalVector.mul( //vec.w = vec.w * -abs(offs.w), w - component
                                    -2 * currentMovementVec.dotProduct(normalVector),
                                    -2 * currentMovementVec.dotProduct(normalVector),
                                    -2 * currentMovementVec.dotProduct(normalVector))
                                    .add(currentMovementVec);
                            setMotion(movementVec);
                            world.playSound((PlayerEntity) null, this.getPosX(), this.getPosY(), this.getPosZ(), ModSounds.DIG, SoundCategory.NEUTRAL, 1.0F, 1.0F / (new Random().nextFloat() * 0.4F + 0.8F));
                            setTimesBounced(getTimesBounced() + 1);
                            TICKS_UNTIL_RETURN = 20 + new Random().nextInt(5);
                            SUCCESSFULLY_BOUNCED = true;
                        } else {
                            TICKS_UNTIL_RETURN = 10 + new Random().nextInt(5);
                        }
                    } else {
                        world.destroyBlock(blockRayTraceResult.getPos(), true); //replace with a passable block array instance
                    }
                    break;
                }
                case ENTITY: {
                    EntityRayTraceResult entityRayTraceResult = (EntityRayTraceResult) rayTraceResult;
                    if (entityRayTraceResult.getEntity() instanceof LivingEntity && entityRayTraceResult.getEntity() != getThrower()) {
                        LivingEntity thrower = getThrower();
                        entityRayTraceResult.getEntity().attackEntityFrom(thrower != null
                                ? thrower instanceof PlayerEntity
                                ? DamageSource.causeThrownDamage(this, thrower)
                                : DamageSource.causeMobDamage(thrower)
                                : DamageSource.GENERIC, 57);
                    }
                    break;
                }
                default:
                    break;
            }
        }
    }

    private int getTimesBounced() {
        return dataManager.get(BOUNCES);
    }

    private void setTimesBounced(int times) {
        dataManager.set(BOUNCES, times);
    }

    @Override
    protected float getGravityVelocity() {
        return 0;
    }

    @Nonnull
    @Override
    protected Item getDefaultItem() {
        return ThrowableItems.Items.light_disk;
    }

    @Nonnull
    @Override
    public IPacket<?> createSpawnPacket() {
        return NetworkHooks.getEntitySpawningPacket(this);
    }
}

Очень надеюсь на советы по этому поводу. Спасибо.
 

Icosider

Kotliner
Администратор
3,603
99
664
Создай абстрактный класс, туда напихай всё что хочешь по своему предмету/энтите. А далее уже используй. В террарии вообще итемы особо не отличаются, особенно бумеранги. Куча классов плодить не стоит, по причине того, что скорее всего у тебя код повторяется с незначительными изменениями.
 
205
12
103
Создай абстрактный класс, туда напихай всё что хочешь по своему предмету/энтите
Думал об этом ещё при старте портирования бумерангов, но не осилил регистрацию, ибо класс был не абстрактным и касты у меня все полетели. На самом деле я вообще не работал с абстракцией (как и с проганьем в целом), потому сложности явно будут. Почитаю доков, завтра попробую сделать и отпишусь о результатах.

Спасибо за совет.
 

Icosider

Kotliner
Администратор
3,603
99
664
Ещё если у тебя однотипные предметы, т.е. имеют различия лишь в названии, описании, уроне и ещё в какой-нить фигне, то используй в предмете getSubItems метод, через него можно создать подтипы предметам, это считай ещё -N классов
 
Сверху