Синхронизировать предмет на хотбаре по инициативе сервера

Версия Minecraft
1.18.2
API
Fabric
78
3
10
Делаю предмет, который ставит блок в мир. При установке сервер проверяет некоторые условия, и если они не соблюдены - может отказать в установке.
Клиент эти же условия проверить не может - данных на клиенте недостаточно (ae2 grid).
Получается, что код сначала отрабатывает на клиенте, и BlockItem#place возвращает ActionResult.SUCCESS. Клиент уменьшает размер пачки предмета. Следом серверная часть отрабатывает, и возвращает ActionResult.FAIL. С сервера прилетает синхронизация чанка, блок пропадает. Однако, пачка предмета на хотбаре по-прежнему меньше на единицу, чем она же на сервере.
Без дополнительных пакетов клиент не сможет узнать, разрешит ли сервер установку блока.
Единственным решеним вижу только принудительную синхронизацию стака по инициативе сервера после того, как он отказал.
Однако, пытаясь найти способ - везде находил какие-то странные костыли...
Подскажите, есть ли адекватный способ засинхронить конкретную пачку предметов в инвентаре?

Псевдокод (полагаю, будет дочтаточно):
Java:
class MyBlockItem extends BlockItem{
    public ActionResult place(ItemPlacementContext context){
        if (something_on_server){
            // --> is only server thread touch this
            // todo: send message to the player
            // todo: sync main hand stack to the player
            return ActionResult.FAIL;
        }
        return super.place(context);
    }
}

Сижу на Fabric, но предположу, что и классическое форджевое решение подойдёт, хотя бы понять принцип. Отправлять пакет инвентаря самостоятельно очень бы не хотелось, оставлю это на крайний вариант.
 
78
3
10
Возвращай не ActionResult.FAIL, а ActionResult.PASS
Какжется, пробовал все отклоняющие типы, и это ничего не меняло... Немного позже смогу проверить. Спасибо.
Супер убрать не можу, там еще АЕшная логика (айтем наследую от AEBaseBlockItem на самом деле, но решил этого не учитывать, я там вдоль и поперек по стэку уже излазил).
 
78
3
10
Возвращай не ActionResult.FAIL, а ActionResult.PASS
Нет, не помогло. Стек все равно уменьшается. Если это был единственный предмет, то он исчезает. Однако, если кликнуть на пустом слоте - он берется под курсор.
Более того, PASS при клике на другую энтити как на опорный блок, будет вызывать клик на ней.

Ну и супер колл не убрать - в супере довольно важный код:
Java:
        world.playSound(playerEntity, blockPos, this.getPlaceSound(blockState2), SoundCategory.BLOCKS, (blockSoundGroup.getVolume() + 1.0f) / 2.0f, blockSoundGroup.getPitch() * 0.8f);
        world.emitGameEvent((Entity)playerEntity, GameEvent.BLOCK_PLACE, blockPos);
        if (playerEntity == null || !playerEntity.getAbilities().creativeMode) {
            itemStack.decrement(1);
        }
        return ActionResult.success(world.isClient);

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

Продолжаю считать, что мне нужна именно синхронизация предмета в случае отказа на сервере.
 
Последнее редактирование:
78
3
10
Подразумевает под собой хотя-бы одну проверку на !world.isClientSide?
Нет. Там в основном проверки, связанные с сетью AE2, а эта сеть не транслируется на клиент.
Одна из проверок касается необходимости рядом с блоком контроллера АЕ - размеется, она на клиенте тоже работает, и в таких случаях всё ок.
 
78
3
10
Вставляй проверку
Замени (something_on_server) на (!world.isClientSide && Math.random() > 0.5) - в половине случаев сервер будет отказывать в установке блоков, а клиент будет всегда его устанавливать. Это всё, что нужно знать об этой переменной. Клиент точно так же будет мигать блоком и уменьшать пачку.
Если же из этого кода на клиенте всегда возвращать PASS/FAIL - то клиентская часть не будет воспроизводить звук, уменьшать пачку, и фаерить событие GameEvent.BLOCK_PLACE
 
78
3
10
насильно отправляя со стаком и слотом и фиксил
Да, мне пока только это и приходит в голову. Но... Не красиво. По любому же должен быть более "правильный" путь, раз ядро предполагает FAIL.
 
1,357
109
233
то клиентская часть не будет воспроизводить звук, уменьшать пачку, и фаерить событие GameEvent.BLOCK_PLACE
Все эти события можно воспроизвести на сервере: пачка уменьшается на сервере, звук воспроизводится через world.
playSound(), передавая в игрока null, GameEvent тоже засчитывается только на сервере.
(!world.isClientSide && Math.random() > 0.5) - в половине случаев сервер будет отказывать в установке блоков, а клиент будет всегда его устанавливать
Как раз таки он всегда будет его НЕ устанавливать.
 
78
3
10
Как раз таки он всегда будет его НЕ устанавливать.
Java:
    public ActionResult place(ItemPlacementContext context){
        if (!world.isClientSide && Math.random() > 0.5){
            // --> is only server thread touch this
            // todo: send message to the player
            // todo: sync main hand stack to the player
            return ActionResult.FAIL;
        }
        return super.place(context);
    }

Действительно?
 
78
3
10
Все эти события можно воспроизвести на сервере
Можно из хлебной буханки сделать троллейбус, но... зачем? Дублирование кода всегда есть плохо, и хуже всего то, что мне приходится объяснять, почему костыли - это плохо, если твоя нога не сломана.
 
1,357
109
233
Java:
    public ActionResult place(ItemPlacementContext context){
        if (!world.isClientSide && Math.random() > 0.5){
            // --> is only server thread touch this
            // todo: send message to the player
            // todo: sync main hand stack to the player
            return ActionResult.FAIL;
        }
        return super.place(context);
    }

Действительно?
Так лол, ты не то делаешь. Ты проверяешь ВСЮ логику на сервере, а если это дело не на сервере фэйлишь.
Чтобы был звук вводи перед всем этим делом (внутри метода) переменную булин, которую на сервере делаешь тру. На клиенте проверяешь, верна ли переменная, откуда и воспроизводишь звук.

Вообще, я бы на твоём месте взаимодействовал с блоком в onPlace (внутри блока), откуда разрушал бы его, если он несоответствует требованиям. Ну и, конечно же, выводил бы сообщение игроку, что он поставил блок не там где надо/игрок не раскачан.
 
78
3
10
Ты проверяешь ВСЮ логику на сервере
Третий раз повторю, логика основана на проверках AE2 Grid. Состояние сети не транслируется на клиент, и на клиенте я не могу её проверить. Только на сервере.
И переменная здесь ничем не поможет. Как ни крути - эту логику на клиенте не проверить. И либо смириться с этим, либо отдельным пакетом запрашивать с сервера.
 
78
3
10
onPlace (внутри блока)
У меня были такие мысли... Но мне показалось это тоже странным. Блок установится, и вся сеть пойдёт пересчитываться. Как бы вроде и ничего плохого. Но если ценой тому - требование синхронизировать инвентарь, то я выберу именно второе. Тупо путь наименьшей затраты ресурсов - как сетевых, так и вычислительных.
 
1,357
109
233
Состояние сети не транслируется на клиент, и на клиенте я не могу её проверить
А тебе и не надо на клиенте проверять. Я говорю тебе проверяй на сервере её.

Java:
    public ActionResult place(ItemPlacementContext context){
        if (!world.isClientSide) {
            //do serever stuff
            boolean a = hz_cho_tam_proveryat;
            if(!a)
                return ActionResult.FAIL;
            else
                return super.place(ctx);
        }
        return ActionResult.PASS;
    }
 
78
3
10
!world.isClientSide вообще не нужен. Там всё нулль-чекнутое, на клиенте грид и так нулль. Это масло масляное. Одна из проверок на клиенте нормально отрабатывает - и даже только ради неё я не буду ограничивать.

Давай предположения закончим, я это всё давно уже проверял вдоль и поперёк, я немношко не новичок ни в программировании, ни в джаве, и очевидные варианты уже все рассмотрел.
Я предпочту подождать, мало ли кто-то посоветует синхронизацию инвентаря кошерную.
 
1,357
109
233
Тупо путь наименьшей затраты ресурсов - как сетевых, так и вычислительных.
Не сказал бы что там так много рес-ов тратится. Если сеть размером с замок - да, скорее всего пролагает. Но это уже по вине пользователя, на мой взгляд, мало где нужны НАСТОЛЬКО огромные системы.
Там всё нулль-чекнутое, на клиенте грид и так нулль
Так суть не в отсечении нуллов, а в "отмене" ивента вычислений на клиенте
 
78
3
10
Но это уже по вине пользователя
1. Нет, если я ограничиваю свой аддон какими-либо условиями - это по моей вине.
2. Нет, клиент (в данном случае пользователь) - всегда врёт и всегда хочет навредить. Это первое правило сетевого программирования, которое я усвоил 15-20 лет назад и навсегда.
3. Сеть всегда стоит считать размером в 100 чанков. Более того, пользователь однозначно попытается снова, если ему отказали.

Пролаги будут, даже если "тупо по приколу как весь чат ноет на лаги". Я предпочту использовать меньше ресурсов там, где вижу способ.
 
Сверху