Работа с Capability.

Версия Minecraft
1.16.5
API
Forge
Всем привет!

У меня появилась задача, сделать дополнительную характеристику игроку. К примеру взял выносливость (а так их будет очень много).
Посмотрел на форуме, вроде как лучше всего нужно использовать Capability.

Сперва хотел бы спросить, правильно ли я делаю?

Написал интерфейс ICapability:
Java:
public interface ICapability {
    int  getValue();
    void addValue(int value);
    void setValue(int value);
}

Далее класс DataCapability от ICapability:
Java:
public static class DataCapability implements ICapability{
    private int Stamina;

    @Override
    public int getValue() {
        return this.Stamina;
    }

    @Override
    public void addValue(int value) {
        this.Stamina += value;
    }

    @Override
    public void setValue(int value) {
        this.Stamina = value;
    }
}

Следующий класс - Storage:
Java:
public static class Storage implements Capability.IStorage<DataCapability> {
    @Override
    public INBT writeNBT(Capability<DataCapability> capability, DataCapability instance, Direction side) {
        CompoundNBT compound = new CompoundNBT();
        compound.putInt(Stamina, instance.getValue());
        return compound;
    }

    @Override
    public void readNBT(Capability<DataCapability> capability, DataCapability instance, Direction side, INBT nbt) {
        instance.setValue(((CompoundNBT) nbt).getInt(Stamina));
    }
}

Класс провайдера (Provider):
Java:
public static class Provider implements ICapabilitySerializable<INBT> {
    private final DataCapability playerStamina = new DataCapability();
    private final Capability.IStorage<DataCapability> storage = PLAYER_STAMINA.getStorage();

    @Nonnull
    @Override
    public <T> LazyOptional<T> getCapability(@Nonnull Capability<T> cap, @Nullable Direction side) {
        if (cap.equals(PLAYER_STAMINA)) return LazyOptional.of(() -> playerStamina).cast();
        else return LazyOptional.empty();
    }

    @Override
    public INBT serializeNBT() {
        return storage.writeNBT(PLAYER_STAMINA, playerStamina, null);
    }

    @Override
    public void deserializeNBT(INBT nbt) {
        storage.readNBT(PLAYER_STAMINA, playerStamina, null, nbt);
    }
}

Все эти классы поместил в один класс StaminaCapability:
Java:
public class StaminaCapability {
    @CapabilityInject(DataCapability.class)
    public static Capability<DataCapability> PLAYER_STAMINA;
    public static String Stamina = "Stamina";

    public interface ICapability {}
    public static class DataCapability implements ICapability {}
    public static class Storage implements Capability.IStorage<DataCapability> {}
    public static class Provider implements ICapabilitySerializable<INBT> {}
}

Следующий мой шаг - класс CapabilityRegistry, который будет регистрировать все Capability.
Java:
public class CapabilityRegistry {
    public static void init() {
        CapabilityManager.INSTANCE.register(StaminaCapability.DataCapability.class, new StaminaCapability.Storage(), StaminaCapability.DataCapability::new);
    }
}

Конечный шаг - регистрация в главном классе мода:
Java:
private void setup(final FMLCommonSetupEvent event) {
    CapabilityRegistry.init();
}

Есть ли какие-то ошибки?
Какие шаги следующие?
Как проверить работоспособность?
Есть ли какие-то советы?
Впервые вообще с Capability, да и вообще в моддинге.
Заранее спасибо!
 
Последнее редактирование:
Решение
Первое, че стоит отметить: это можно сделать проще. Интерфейс не обязателен. Хранимая структура и провайдер могут быть одним и тем же. Для простых данных это удобно.
Ты забыл присоединить капу к игроку. Лови событие AttachCapabilityEvent<Entity>, проверяй, что это игрок и добавляй экземпляр провайдера.
Как проверить работоспособность?
Написать какую-то логику ввода/вывода значений капы. Например, команды /get_my_capa, /set_my_capa <value>
7,099
324
1,510
Первое, че стоит отметить: это можно сделать проще. Интерфейс не обязателен. Хранимая структура и провайдер могут быть одним и тем же. Для простых данных это удобно.
Ты забыл присоединить капу к игроку. Лови событие AttachCapabilityEvent<Entity>, проверяй, что это игрок и добавляй экземпляр провайдера.
Как проверить работоспособность?
Написать какую-то логику ввода/вывода значений капы. Например, команды /get_my_capa, /set_my_capa <value>
 
Интерфейс не обязателен.
Это интерфейс будет использоваться для других похожих структур.
Хранимая структура и провайдер могут быть одним и тем же.
Окей. А зачем некоторые их отделают? Так, интересно просто.
Ты забыл присоединить капу к игроку. Лови событие AttachCapabilityEvent<Entity>, проверяй, что это игрок и добавляй экземпляр провайдера.
Спасибо! Сделаю как ты сказал.
Написать какую-то логику ввода/вывода значений капы. Например, команды /get_my_capa, /set_my_capa <value>
Это то да, понятно. Но вот как получить эту капу? :(
Еще хотел бы узнать, информация из капы как-то будет сама обновляться? Или же нужно пакеты кидать? Если да, то как?
Еще раз спасибо за помощь и подсказки <3
 
7,099
324
1,510
Окей. А зачем некоторые их отделают? Так, интересно просто.
Потому что можно. Как правильно - зависит от стиля и паттернов
как получить эту капу?
player.getCapability(StaminaCapability.PLAYER_STAMINA)
информация из капы как-то будет сама обновляться?
С клиентами нужно самому синхронизировать, отправлять свои пакеты. Стратегий разных много, зависит от игровой механии. Например, могут ли другие игроки видеть стамину игрока? Если нет, то другим игрокам не имеет смысла отправлять пакет синхронизации.
 
Лови событие AttachCapabilityEvent<Entity>, проверяй, что это игрок и добавляй экземпляр провайдера.
Ловлю:
Java:
@SubscribeEvent
public static void AddCapabilities(AttachCapabilitiesEvent<Entity> event) {
    if (event.getObject() instanceof PlayerEntity && !(event.getObject() instanceof FakePlayer)) {
        event.addCapability(new ResourceLocation(ModSettings.MODID, ("stamina")), new StaminaCapability.Provider());
    }
}

И сразу же вопрос, в строке new ResourceLocation(ModSettings.MODID, ("stamina")) под значением слова "stamina" писать то же, что и писал при создании Capability? Просто при создании капы писал большими буквами, а тут именно маленькими нужно, и сразу подумал, что я что-то путаю...

Далее хочу посмотреть, что добавилось ли хоть что-то.
В этот же ивент пишу следующее:
Java:
Player.getCapability(StaminaCapability.PLAYER_STAMINA).ifPresent(Data -> Data.setValue(10));
Player.getCapability(StaminaCapability.PLAYER_STAMINA).ifPresent(Data -> Value = Data.getValue());
// Пытаюсь вывести результат в консоль
System.out.println("\u001B[32m" + "Test: " + Value + "\u001B[0m");
И ничего не происходит. Value = 0, хотя я менял же её на 10, а потом считывал...

Мне очень стыдно просить рассказать поподробнее, я глуп и ничего не пойму (
 
7,099
324
1,510
7,099
324
1,510
Возможно, слишком рано. Капабилити появляется после отправки ивента и сбора всех кап
Вы правы! Я проверил в другом ивенте, и это сработало! Большое спасибо <3

Осталось разобраться как передавать пакеты. Я так понял, при заходе на сервер, пакеты скидываются автоматически, верно? Так как я изменяю значение капы во время игры, а затем перезайдя в мир, значения сохраняются. Следовательно был отправлен пакет :) Или я что-то путаю?

Значит мне сейчас нужно, допустим, в каком нибудь ивенте скидывать обновленную капу?
 
Еще хотел бы задать один вопрос, связанный с незнанием языка Java.
На данный момент я получаю значение капы так:
Java:
final int[] Value = {0};
Player.getCapability(StaminaCapability.PLAYER_STAMINA).ifPresent((Data) -> Data.addValue(1));
Player.getCapability(StaminaCapability.PLAYER_STAMINA).ifPresent((Data) -> Value[0] = Data.getValue());

Почему я делаю так? Так мне предложила среда разработки.
Что хотел бы спросить:
1. Как мне достичь такого?
Java:
Player.getCapability(StaminaCapability.PLAYER_STAMINA).ifPresent((Data) -> Value = Data.getValue());
2. А лучше такого:
Java:
??? DataStamina = Player.getCapability(StaminaCapability.PLAYER_STAMINA);
DataStamina.setStamina(10);
int stamina = DataStamina.getStamina();

На данный момент у меня реализация getCapability сделана так:
Java:
@Override
public <T> LazyOptional <T> getCapability(@Nonnull Capability<T> cap, @Nullable Direction side) {
    if (cap.equals(PLAYER_WATER_LEVEL)) return LazyOptional.of(() -> playerWaterLevel).cast();
    else return LazyOptional.empty();
}
 
Последнее редактирование:
7,099
324
1,510
Так как я изменяю значение капы во время игры, а затем перезайдя в мир, значения сохраняются. Следовательно был отправлен пакет :) Или я что-то путаю?
Пакеты и сохранение никак не связаны
при заходе на сервер, пакеты скидываются автоматически, верно?
Их вообще нет. Для синхронизации с клиентом нужно делать свои

А лучше такого:
getCapabilityвозвращает LazyOptional, это такая коллекция, которая либо содержит один элемент, либо ниче не содержит(капа может отсутствовать).
У него есть несколько полезных методов, например, map, flatMap, ifPresent.
В целом эта штука позволяет избегать шаблонного кода if(capa!=null).
Можно написать че-то типо того, для регенерации стамины
Java:
player.getCapability(StaminaCapability.PLAYER_STAMINA)
    .filter(capa -> capa.value <= 100)
    .ifPresent(capa -> capa.value++)
А чтобы просто получить значение куда-то во вне:
Java:
int stamina = player.getCapability(StaminaCapability.PLAYER_STAMINA)
    .map(capa -> capa.value)
    .orElse(0)
 
Пакеты и сохранение никак не связаны
Я походу не соображал, что писал ночью. Я значение выводил в консоль, с сервера. И думал что на клиенте они... Нужно было просто лечь спать, извиняюсь за бред
int stamina = player.getCapability(StaminaCapability.PLAYER_STAMINA) .map(capa -> capa.value) .orElse(0)
Спасибо, пробовал использовать это, но не знал про .orElse(0). Сейчас прочитал, и вдуплил, что очень важно было это написать. Я постоянно получал Optional.empty(), и считал, что делаю что-то не то. Благодарю!

player.getCapability(StaminaCapability.PLAYER_STAMINA) .filter(capa -> capa.value <= 100) .ifPresent(capa -> capa.value++)
Стамину взял как пример, а так спасибо за пример работы с filter, а так же с map и orElse!

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