Запретить/разрешить посадку семян на определенном блоке

Версия Minecraft
1.16.5
API
Forge
192
2
9
Доброго времени суток!
Создаю блок-культуру и хочу запретить её сажать вообще везде, ну пока для теста. Если я возвращаю false в методе mayPlaceOn(), то это не срабатывает. Возможно я не правильно понимаю работу метода, но вроде как он позволяет менять блоки, на которых можно сажать растение. Однако судя по происходящему (дебажил), я лишь могу добавить свои блоки к уже где-то существующему (в ванилле) стандартному списку блоков, разрешенных для использования в качестве пашни и потому false не помогает. Кто-нибудь понимает в чем дело и может объяснить?)
BlockCrops:
public class BlockCrops extends CropsBlock {
   
    private Block[] mayPlaceOn;
    private static Properties props = AbstractBlock.Properties.of(Material.PLANT)
        .sound(SoundType.CROP)
        .harvestLevel(0)
        .strength(0)
        .noCollission();

    public BlockCrops(Block[] mayPlaceOn) {
        super(props);
        this.mayPlaceOn = mayPlaceOn;
    }
   
    public BlockCrops(Block mayPlaceOn) {
        super(props);
        this.mayPlaceOn = new Block[] { mayPlaceOn };
    }
   
    public BlockCrops() {
        super(props);
    }
   
    @Override
    protected IItemProvider getBaseSeedId() {
        return this.asItem();
    }
   
    @Override
    protected boolean mayPlaceOn(BlockState bstate, IBlockReader ibreader, BlockPos bpos) {
        if (mayPlaceOn == null) return bstate.is(Blocks.FARMLAND);
        for (Block block : mayPlaceOn)
            if (bstate.toString().contains(block.getRegistryName().toString())) return true;
        return false;
    }

}
 
Последнее редактирование:
Решение
через событие проверяйте что в руках предмет вашей культуры и игрок нажал ПКМ и проверяйте блок на который он нажал.
либо через свой предмет, в который и засунуть эту проверку.
1,038
57
229
через событие проверяйте что в руках предмет вашей культуры и игрок нажал ПКМ и проверяйте блок на который он нажал.
либо через свой предмет, в который и засунуть эту проверку.
 
192
2
9
через событие проверяйте что в руках предмет вашей культуры и игрок нажал ПКМ и проверяйте блок на который он нажал.
либо через свой предмет, в который и засунуть эту проверку.
Событее будет менее предпочтительно, чем предмет? То есть нужно сделать те же самые семена в качестве предмета и там проверку...
А что на счет метода? Я не корректно понимаю его работу? :) Я надеялся, что именно с помощью данного метода можно будет задать строгий список допустимых блоков, а не добавлять к уже существующему набору, который к тому же я даже не знаю как узнать :D
 
428
41
108
Событее будет менее предпочтительно, чем предмет?
Да, система событий сама по себе очень медленная. Проверь наличие метода, ,,клика предметом по блоку,, (не помню, путаю или еще что) и переопредели там логику - вроде есть такое
 
1,370
112
241
В своём предмет создай коллекцию (set/list) с блоками, на которые садить нельзя. В переопределённом методе useOn проверяй кликнутый блок на наличие в коллекции. Если есть - отменяем установку блока растения. Если же нет, устанавливай.
 
192
2
9
В общем... Сделал я нечто подобное. Кастомный айтем семян! Но мне всё еще кажется, что должен был быть более адекватный путь, чем делать велосипед) Поскольку ответ на вопрос поставленный я так и не получил, тему прошу не закрывать. Вдруг кто приложит более корректный вариант, чем уже озвученные. Если нет, позже помечу первый же коммент, как решение.

При регистрации предмета буду передавать в класс блок, который должен быть установлен в случае всех успешных проверок, а так же блок/блоки, на которых разрешается ставить эти семена.

ItemSeeds:
    private Block[] mayPlaceOn;
    private Block plant;
    private static Properties props = (new Item.Properties()).tab(CreativeTabs.PLANTS);
 
    public ItemSeeds(Block plant, Block[] mayPlaceOn) {
        super(props);
        this.mayPlaceOn = mayPlaceOn;
        this.plant = plant;
    }
 
    public ItemSeeds(Block plant, Block mayPlaceOn) {
        super(props);
        this.mayPlaceOn = new Block[] { mayPlaceOn };
        this.plant = plant;
    }
 
    public ItemSeeds(Block plant) {
        super(props);
        this.mayPlaceOn = new Block[] { Blocks.FARMLAND };
        this.plant = plant;
    }
 
    private void action(ItemUseContext iuc) {
        ItemStack is = iuc.getItemInHand();
        if (is == null) return;
     
        BlockPos pos = iuc.getClickedPos();
        World world = iuc.getLevel();
        Block blockDown = iuc.getLevel().getBlockState(pos).getBlock();
        if (blockDown == null || !Arrays.asList(mayPlaceOn).contains(blockDown)) return;
     
        pos = pos.above(1);
        Block blockUp = world.getBlockState(pos).getBlock();
        if (blockUp == null || blockUp != Blocks.AIR) return;
     
        world.setBlockAndUpdate(pos, plant.defaultBlockState());
        int count = is.getCount();
     
        PlayerEntity p = iuc.getPlayer();
        if (count > 1) is.setCount(count-1);
        else p.setItemInHand(Hand.MAIN_HAND, new ItemStack(Items.AIR));
     
        String sound = plant.getName().getString().contains("bush") ? "block.sweet_berry_bush.place" : "entity.villager.work_farmer";
        world.playSound(p, pos, new SoundEvent(new ResourceLocation(sound)), SoundCategory.BLOCKS, 1.0F, 1.0F);
    }

    @Override
    public ActionResultType useOn(ItemUseContext iuc) {
        action(iuc);
        return ActionResultType.PASS;
    }
 
Последнее редактирование:
1,370
112
241
1. Почитай про разделение клиент/сервер.
2. Проверка на null в руке лишняя. Используй isEmpty()
3. Проверки на null в блоках тоже лишние. Используй isAir()
4. В ItemStack уже есть метод, позволяющий уменьшать стак. Используй shrink(int кол-во)
5. Форматирование кода очень странное, если честно.
Твои конструкторы можно заменить этим:
Java:
public MyBlock(String str, int meta) {
    this.str = str;
    this.meta = meta;
}
public MyBlock(int meta) {
    this("EMPTY", meta)
}
Почему бы не впихнуть твой action в useOn? Да и именование action тоже так себе для Java, лучше уж какое-нибудь "plantBlock" или что-то в этом роде.
Зачем делать поля приватными? Если делаешь поля приватными, к ним должен быть доступ через get/set.
Мой вариант (писал без IDE, могут быть ошибки):
Java:
       private final List<Block> restricted;
       public final Block plant;

    public ItemDemSeeds(List<Block> restricted, Block plant) {
        super(new Item.Properties()).tab(CreativeTabs.PLANTS);
        this.restricted = restricted;
        this.plant = plant;
    }

    public ItemDemSeeds(Block plant) {
        this(Lists.newArrayList(), plant);
    }

    public ItemDemSeeds() {
        this(Blocks.WHEAT);
    }

    @Override
    public ActionResultType useOn(ItemUseContext iuc) {
        World world = iuc.getLevel();
        if(!world.isClientSide) {
            BlockPos pos = iuc.getClickedPos();
            if (restricted.contains(world.getBlockState(pos).getBlock()) ||
               !world.getBlockState(pos.above()).getBlock().isAir())
                return ActionResultType.PASS;
         
            world.setBlockAndUpdate(pos, plant.defaultBlockState());
            iuc.getItemStackInHand(iuc.getHand()).shrink(1);

            String sound = plant.getName().getString().contains("bush") ? "block.sweet_berry_bush.place" : "entity.villager.work_farmer";
            world.playSound(null, pos, new SoundEvent(new ResourceLocation(sound)), SoundCategory.BLOCKS, 1.0F, 1.0F);
        }
        return super.useOn(iuc);
    }
 
    public List<Block> getRestricted() {
        return restricted;
    }
    public Block getPlant() {
        return plant;
    }
 
7,099
324
1,510
Да, система событий сама по себе очень медленная. Проверь наличие метода, ,,клика предметом по блоку,, (не помню, путаю или еще что) и переопредели там логику - вроде есть такое
оно никогда не было медленным
как минимум уже в 1.7.10 юзалась кодогенерация времени загрузки, а не рефлексия(как в бакките например) времени выполнения
 
192
2
9
оно никогда не было медленным
как минимум уже в 1.7.10 юзалась кодогенерация времени загрузки, а не рефлексия(как в бакките например) времени выполнения
То есть что в ивенте, что в предмете/блоке - сути не меняет? Я думал, что в ивент лишний раз лучше не лезть, поскольку он вызывается всегда и любые действия в нем лучше избегать, если есть такая возможность. Таким образом я буду выполнять +1 проверку минимум каждый раз, когда игрок кликает вообще по любому блоку, ведь мне нужно будет его проверить хотя бы, чтобы выполнить действия.
Если же делать всю логику нужную внутри блока/предмета, то эта логика будет вызываться исключительно при клике на кастомный блок.
Это работает не так?) По крайней мере при дебаге консоль молчит, если я кликаю не по кастомному блоку. Это и наталкивает на мысль, что лучше работать именно так.
 
Последнее редактирование:
192
2
9
1. Почитай про разделение клиент/сервер.
2. Проверка на null в руке лишняя. Используй isEmpty()
3. Проверки на null в блоках тоже лишние. Используй isAir()
4. В ItemStack уже есть метод, позволяющий уменьшать стак. Используй shrink(int кол-во)
5. Форматирование кода очень странное, если честно.
Твои конструкторы можно заменить этим:
Java:
public MyBlock(String str, int meta) {
    this.str = str;
    this.meta = meta;
}
public MyBlock(int meta) {
    this("EMPTY", meta)
}
Почему бы не впихнуть твой action в useOn? Да и именование action тоже так себе для Java, лучше уж какое-нибудь "plantBlock" или что-то в этом роде.
Зачем делать поля приватными? Если делаешь поля приватными, к ним должен быть доступ через get/set.
Мой вариант (писал без IDE, могут быть ошибки):
Java:
       private final List<Block> restricted;
       public final Block plant;

    public ItemDemSeeds(List<Block> restricted, Block plant) {
        super(new Item.Properties()).tab(CreativeTabs.PLANTS);
        this.restricted = restricted;
        this.plant = plant;
    }

    public ItemDemSeeds(Block plant) {
        this(Lists.newArrayList(), plant);
    }

    public ItemDemSeeds() {
        this(Blocks.WHEAT);
    }

    @Override
    public ActionResultType useOn(ItemUseContext iuc) {
        World world = iuc.getLevel();
        if(!world.isClientSide) {
            BlockPos pos = iuc.getClickedPos();
            if (restricted.contains(world.getBlockState(pos).getBlock()) ||
               !world.getBlockState(pos.above()).getBlock().isAir())
                return ActionResultType.PASS;
         
            world.setBlockAndUpdate(pos, plant.defaultBlockState());
            iuc.getItemStackInHand(iuc.getHand()).shrink(1);

            String sound = plant.getName().getString().contains("bush") ? "block.sweet_berry_bush.place" : "entity.villager.work_farmer";
            world.playSound(null, pos, new SoundEvent(new ResourceLocation(sound)), SoundCategory.BLOCKS, 1.0F, 1.0F);
        }
        return super.useOn(iuc);
    }
 
    public List<Block> getRestricted() {
        return restricted;
    }
    public Block getPlant() {
        return plant;
    }
1. Как-то выглядит сложно и не совсем понятно зачем, если я беру код из исходников майна и там такой проверки нет) Если я скажу, что метод вызывается дважды при клике, то как раз по причине клиента/сервера? К слову, на сервере мод работает исправно пока.
2-3-4. Не знаком с API forge
5. Имена методов мне без разницы какие, ведь я делаю не для паблика, а самому и так понятно что я и как пишу) Что касается приватных полей - всё правильно, потому что исключительно в текущем классе они и нужны, а не за его пределами. Почему я выношу код в отдельный метод? В данном случае хоть это и выглядит странным, но работу кода никак не нарушает, верно ведь? Делаю я так, чтобы избегать так называемую "лесенку/каскадность" кода, как в случае с if () { if () { if () {} } } и лично мне удобно читать код, когда он столбиком, а не пирамидой :D
А так... Большое спасибо за замечания/критику. Я не знаю API, да и в языке я даже не юниор)
 
Последнее редактирование:
1,370
112
241
Если я скажу, что метод вызывается дважды при клике, то как раз по причине клиента/сервера?
Именно поэтому и нужен !world.isClientSide
Что касается приватных полей - всё правильно, потому что исключительно в текущем классе они и нужны, а не за его пределами.
Но доступ к ним (банально получить данные от них) нужен. Если ты делаешь что-то приватным, то обязательны get/set - правила хорошего тона, если тебе удобно это назвать так.
Почему я выношу код в отдельный метод? В данном случае хоть это и выглядит странным, но работу кода никак не нарушает, верно ведь? Делаю я так, чтобы избегать так называемую "лесенку/каскадность" кода, как в случае с if () { if () { if () {} } } и лично мне удобно читать код, когда он столбиком, а не пирамидой
Когда в одном методе три строки, а в другом под полсотни - читать наоборот гораздо неудобнее (просто потому что надо помнить что какой метод делает). Ну и опять-таки, правила хорошего тона.

Форматирование кода, вообще, дело скорее для создания его большей читабельности. Так-то, теоретически, код можно и в одну строку писать.
Но у тех или иных прогеров сложились негласные правила как можно, а как низзя писать код. Условно говоря, в Java перенос открывающей скобки блока ('{') на новую строку считается так себе решением. В то же время, в том же C++/C# это уже хорошее решение. Пример так себе, о нём много споров, но наиболее наглядный.
Так что, если пишешь код на каком-то языке, будь добр соблюдай его правила форматирования кода. "В чужой храм по своим уставам не ходят."
 

necauqua

когда-то был anti344
Администратор
1,216
27
172
5. Форматирование кода очень странное, если честно.
Это ещё нормальное форматирование, докопался конечно)
Нету у джавы суперстандартного идеального форматирования, как у питона или раста, по крайней мере в таких мелочак как те ифы так точно.

Но в целом да, агрумент про читабельность в том, что людям удобно читать когда всё везде стандартное и люди не выдумывают всякий новый бред 🤷 (напоминаю шо у тебя всё норм, просто бывает ух какой бред)

Или наоборот забивают и у них всё "грязное", вразнобой, там camelCase, там translitomNazvano, там пробелы какие-то кривые, в одном месте ифы как у тебя, а в другом по-другому, когда консистентности нету)
 

necauqua

когда-то был anti344
Администратор
1,216
27
172
Если ты делаешь что-то приватным, то обязательны get/set - правила хорошего тона
Оу, а это кстати полный бред, чего, необязательны

Они нужны если доступ снаружи нужен, а если не нужен - то не нужны, привет.
Другой вопрос это "а почему тогда не public", и вот тут уже "правила хорошего тона", то бишь архаичные джавашные конвенции с вербозными геттерами-сеттерами.
 
7,099
324
1,510
То есть что в ивенте, что в предмете/блоке - сути не меняет?
В обработчике события ты бы писал проверку на предмет/блок. Если логика находится в предмете/блоке, то эта проверка выполняется фреймворком. Проверка через == очень быстрая, поэтому париться за оптимизацию тут не надо. Это вопрос дизайна.

Если у тебя кастомная семка или блок, то лучше в них писать эту логику.
Если семка и блок чужие, то остается только событие.
 
192
2
9
Я хоть и сделал семки, но заметил, что не срабатывает событие BlockPlace, когда я заменяю блок на нужный мне после всех проверок.
Решил посмотреть по форуму, но не нашел. Как вызвать событие у Forge, чтобы всё грамотно работало?)
 
1,038
57
229
в твоём предмете: world.setBlock(...) или level (там сейчас, вместо world)
 
Сверху