Не изменяется BlockState

Версия Minecraft
1.16.5
API
Forge
192
2
9
Здравствуйте. Я наверняка что-то не доганяю. Пробовал так, но не получилось (два варианта присвоения ради теста):
BlockCrops:
    public static final IntegerProperty AGE = BlockStateProperties.AGE_7;
    private static final BooleanProperty BOOSTED = BooleanProperty.create("boosted");

    public BlockCropsExp() {
        super();
        this.registerDefaultState(this.stateDefinition.any().setValue(AGE, 0).setValue(BOOSTED, false));
    }

    @Override
    public void performBonemeal(ServerWorld world, Random rand, BlockPos bpos, BlockState bstate) {
        bstate = bstate.setValue(BOOSTED, true);
        world.getBlockState(bpos).setValue(BOOSTED, true);
        this.growCrops(world, bpos, bstate);
        System.out.println(world.getBlockState(bpos).toString());
    }

    @Override
    protected void createBlockStateDefinition(StateContainer.Builder<Block, BlockState> container) {
        container.add(AGE).add(BOOSTED);
    }
Лог отвечается всегда дефолтным значением для моего состояния, но для состояния AGE всё меняется как надо:
Лог:
[11:50:16] [Server thread/INFO] [STDOUT/]: [bbrains.mod.morebushes.block.BlockCropsExp:performBonemeal:56]: Block{morebushes:exp}[age=0,boosted=false]
[11:50:20] [Server thread/INFO] [STDOUT/]: [bbrains.mod.morebushes.block.BlockCropsExp:performBonemeal:56]: Block{morebushes:exp}[age=2,boosted=false]
Что в общем-то я делаю... Пытаюсь обозначить свой блок, как искуственно выращенный (с помощью муки). Возможно есть готовые решения в игре?
 
Последнее редактирование:
Решение
Ты изменяешь состояние локальной копии блока, не уведомляя мир об изменениях. Корректные варианты для твоих строк:

world.setBlock(pos, state.setValue(BOOSTED, true), 3);

world.setBlock(pos, world.getBlockState(pos).setValue(BOOSTED, true), 3);

Используй первый вариант.
63
3
38
Ты изменяешь состояние локальной копии блока, не уведомляя мир об изменениях. Корректные варианты для твоих строк:

world.setBlock(pos, state.setValue(BOOSTED, true), 3);

world.setBlock(pos, world.getBlockState(pos).setValue(BOOSTED, true), 3);

Используй первый вариант.
 
Последнее редактирование:
192
2
9
Ты изменяешь состояние локальной копии блока, не уведомляя мир об изменениях. Корректные варианты для своих строк:

world.setBlock(state.setValue(BOOSTED, true), 3);

world.setBlock(world.getBlockState(pos).setValue(BOOSTED, true), 3);

Используй первый вариант.
Не подумал что-то, что в мире блок нужно переустановить, ага :)
Спасибо! Только маленький вопрос еще) Что за тройка в методе? Посмотрел в исходниках, пришел к этому коду
world.markAndNotifyBlock():
if ((p_241211_3_ & 16) == 0 && p_241211_4_ > 0) {
    int i = p_241211_3_ & -34;
    blockstate.updateIndirectNeighbourShapes(this, p_241211_1_, i, p_241211_4_ - 1);
    p_241211_2_.updateNeighbourShapes(this, p_241211_1_, i, p_241211_4_ - 1);
    p_241211_2_.updateIndirectNeighbourShapes(this, p_241211_1_, i, p_241211_4_ - 1);
}
p_241211_4_ - выступает здесь в качестве второго аргумента из world.setBlock(state, int)
То есть это что-то типо обновить данные, если p_241211_4_ > 0?
Так же не понял, какие значения допустимы и за что отвечают :D
 
63
3
38
Какой то флаг, отвечающий за синхронизацию и обновление соседних блоков, фиг его знает зачем ему так много значений. Подобные флаги - зло, ибо они интуитивно не понятны и сложно разобрать что делает код. А ещё нарушается инкапсуляция. Лучше сделать на несколько методов больше, отразить суть их работы в названии (setBlock, setBlockAndSync, setBlockAndScheduleSync) и самому управлять поведением через приватный флаг, чем использовать флаги в публичном методе, перевешивая внутреннюю ответственность на клиент.

Ванильные блоки при похожем обновлении состояния используют тройку, поэтому и я использую тройку. С ней клиенты сразу получают обновление блока. Возможно, какие нибудь стены или заборы используют друге значение флага, чтобы корректно оповещать соседей.
 
Последнее редактирование:
192
2
9
Дополню, что в моем случае действия выполняются с кустом, так что в методе роста культуры growCrops() уже есть world.setBlock(), логику которого так же нужно переопределить и переписать, чтобы кастомный стейт не сбрасывался на дефолтное значение.
CropsBlock:
   public void growCrops(World world, BlockPos bpos, BlockState bstate) {
      int i = this.getAge(bstate) + this.getBonemealAgeIncrease(world);
      int j = this.getMaxAge();
      if (i > j) {
         i = j;
      }
      world.setBlock(bpos, this.getStateForAge(i), 2);
   }

    @Override
    public void growCrops(World world, BlockPos bpos, BlockState bstate) {
        int i = this.getAge(bstate) + this.getBonemealAgeIncrease(world);
        int j = this.getMaxAge();
        bstate = bstate.setValue(AGE, i > j ? j : i);
        world.setBlock(bpos, bstate, 2);
   }
 

necauqua

когда-то был anti344
Администратор
1,216
27
172
А ещё нарушается инкапсуляция
ват
перевешивая внутреннюю ответственность на клиент
ват²
Разницы в логике между несколькими методами и одним методом с несколькими флагами не вижу, какая ответственность где перекладывается.

Хотя я докопался, ты просто имел ввиду что лучше методы (а они лучше), потому-что тупо флаги интом неудобно/неинтуитивно/можно поставить 1337 и шо тогда какое поведение будет, неясно

Флаги просто в джаве делаются просто интом и константами, которые в изначальном коде используются с |, но после перекомпиляции те флаги evaluat-ются в числовой литерал и фиг знаешь откуда он взялся. Эта проблема называется magic constants, особенно заметно в gl-коде.

Тройка это Block.UPDATE_ALL, он-же Block.UPDATE_NEIGHBORS | Block.UPDATE_CLIENTS.
(3 = 0b11 = 2 | 1 = 0b10 | 0b01)
Там ещё есть несколько флагов под ними.
 
63
3
38
Вызывающая сторона НЕ должна решать, как будет работать публичное API, это нарушение принципа ООП, инкапсуляции и общепринятого code style.

В продакшене такое могут себе позволить только индусы, которые и null'ы без исключений возвращают, и enum'ами пользоваться не умеют, либо стартапы, ограниченные во времени и ресурсах. В идеале, ты вызываешь метод - он делает то, что указано в названии. Если метод принимает на вход какой то эфемерный флаг, предназначение которого интуитивно не понятно, чревато сайд-эффектами, требует от программиста инструкции по использованию или перемещение на более низкий уровень абстракции (открытие метода и его анализ) - то это плохое решение. Флаги имеют место быть только в пределах одного класса (на одном уровне абстракции) в приватных методах - тогда это удобно и не вводит в заблуждение клиентов.
 
Последнее редактирование:
63
3
38
Тройка это Block.UPDATE_ALL
Вероятно, тут проблема даже не столько в наличии флага в методе, сколько в отсутствии информации о наличии именованных констант. Был бы хоть метод документирован и ссылался на эти константы, уже бы было намного меньше неясности.
 

necauqua

когда-то был anti344
Администратор
1,216
27
172
Вызывающая сторона НЕ должна решать, как будет работать публичное API
ээээм, все ещё ват?. даже мега-ват
Ты когда "вызываешь публичное апи" ты же решаешь какой именно кусок кода надо чтобы отработал как-то?.

Я ещё раз повторю, разницы между callA()/callB()/callC() и call(Flags.A)/call(Flags.B)/call(Flags.C) буквально не существует

Битмаска это просто компактный способ передать кучу булеанов.
Типа что лучше, иметь 16 очень похожих методов, иметь 1 метод что принимает 4 булева, или метод что принимает флаги - всё одна хрень, лол.

Поэтому я не могу понять, где ты тут видишь "нарушение принципа ООП, инкапсуляции, ещё десяти сууупер полезных и вовсе не душных джавашных паттернов (особенно когда мы тут моды пишем) по которым надо десяток книг перечитать", общепринятый кодстайл ещё ладно, я даже не спорю что нафиг они конкретно там сдались, просто ты чёт странное втираешь.

которого интуитивно не понятно
Это из-за magic constants и рекомпиляции, джава такая уж 🤷

Я же привёл пример как оно должно было выглядеть по их изначальной задумке - setBlock(x, y, z, state, Block.UPDATE_NEIGHBORS)
Это тупо классик джава - ещё раз напоминаю что я не спорю что так лучше не надо в новом коде писать, но это и не "индусы делают контрол флов эксепшнами" (то что у меня на проде буквально было, кек)

чревато сайд-эффектам
брух, это джава а не хаскель, опять-же
И глядя ещё раз на пример три строчки выше - setBlock это буквально сайд-эффект.

перемещение на более низкий уровень абстракции (открытие метода и его анализ)
Перемещение на более низкий уровень абстрации и банально анализ кода это разные вещи
Анализ кода тут есть - потому что у нас нету доков, нету оригинальных констант, и тип там стоит int а не BlockPlacementBitflags, как можно было бы сделать в расте
А нарушения инкапсуляции, ООП и всего такого тут таки нема
Вот если таким образом это предоставить то я полностью согласен, и даже что сами флаги это антипаттерн из-за описанных проблем, особенно в майне, или того что тип там всё-равно тупо int я тоже согласен.

А то что там нарушаются умные слова я не согласен, не нарушаются они там, лол, просто апи и само такое-себе, и документации конечно-же нету, и самодокументации (вроде названия метода/типа, например если бы там был тип BlockPlacementBitflags, типа как в расте) тоже
 
63
3
38
флаги это антипаттерн
Добавлю только про сайд-эффекты и перекладывание ответственности, с остальным пришли к согласию.

Если метод setBlock() просто устанавливает блок, то сайд-эффектов он не имеет, так как о чём заявил в названии - то и выполнил. Если тот же setBlock(int flag) устанавливает блок, но в зависимости от флага может ещё и синхронизировать его/обновить соседей - это сайд-эффекты, которые появляются из за аргумента. Если бы существовал метод setBlockAndSync(), устанавливающий блок и синхронизирующий его, он был бы более чистым решением, поскольку он не делает ничего кроме того, о чем заявляет.

Теперь о перекладывании ответственности. Когда клиент вызывает публичное API и оно самостоятельно занимается реализацией и логикой того, о чем заявило - это окей и вся ответственность лежит на API. Но если при вызове API клиент может определять реализацию через аргументы (флаг напрямую влияет на реализацию, да ещё и сайд-эффекты порождает) - то это уже Не окей. API должно предоставлять интерфейсы для взаимодействия с программой без необходимости знать о её реализации, а отправка числового флага подразумевает, что вы будете копаться в реализации и изучать сорсы. Все равно что ты подошёл к банкомату, а он тебе: «Выберите код операции, которую хотите выполнить: 1, 2, 3, 4, 5». И что, я знаю чтоль под каким числом скрывается нужная мне операция xD? Да, в нашем случае всё таки существуют константы (о которых я не знал), поэтому эта не такая жёсткая проблема, как если бы ответственность API перекладывалась на клиент голыми int или bool без пояснений (а я с таким уже сталкивался).

Оба пункта могут значительно увеличить время работы и породить отложенное некорректное поведение или ошибки, причину которых может быть муторно и сложно обнаружить, поэтому их и не стоит использовать, а не потому, что они нарушают умные слова. Эти слова - просто короткое представление о проблемах, с которыми можно столкнуться, если не придерживаться правил. Ничего с сорсами сделать мы не можем, но можем обратить внимание на плохие примеры, понять почему они плохие и постараться не допускать их в собственных проектах для экономии своего времени и времени тех, что будет использовать ваш код.
 
Последнее редактирование:
Сверху