Использование миксина в миксине...

Версия Minecraft
1.20.1
API
Forge
38
2
1
Всем добра.
Совсем недавно руки мои добрались до миксинов, потому я мало чего в них понимаю.
Я создал миксин с классом FoodData, встроенным в Player. Теперь я хочу через Player как-то вытянуть ту информацию, которую я занес в фуддату (для дебага), но упорно не понимаю, как именно это сделать, потому как миксин игрока не видит то, что я добавил. Код:
FoodDataMixin:
@Mixin(FoodData.class)
public abstract class FoodDataMixin implements IPlayerDietContainer {

    private float proteins = 20;

    public float the_most_complete_food_mod$getValue(int pId) {
            return switch (pId) {
                case 0 -> this.proteins;
                ...
            default ->
                throw new NullPointerException("\nat PlayerDietContainer.getValue(): \nTried to get value with Id other from 0 to 6.");
            };
    }
...
}

PlayerMixin:
@Mixin(Player.class)
public abstract class PlayerMixin {

    @Shadow protected FoodData foodData;

    private Player self() {
        return (Player) (Object) this;
    }

    private FoodData selfFoodData() {
        return (FoodData) this.foodData;
    }




    @Inject(method = "tick", at = @At("TAIL"))
    private void writeProteins(CallbackInfo ci) {
        if(!self().level().isClientSide()) {
            self().sendSystemMessage(Component.literal("Proteins level is: " + selfFoodData().the_most_complete_food_mod$getValue(0)));
        }
    }

    @Inject(method = "drop(Lnet/minecraft/world/item/ItemStack;ZZ)Lnet/minecraft/world/entity/item/ItemEntity;", at = @At("TAIL"))
    private void increaseProteins(ItemStack pDroppedItem, boolean pDropAround, boolean pIncludeThrowerName, CallbackInfoReturnable<ItemEntity> cir) {
        if (pDroppedItem.getItem() == Items.DIAMOND) {
            selfFoodData().the_most_complete_food_mod$increaseValue(0, 0.5f);
        } else {
            selfFoodData().the_most_complete_food_mod$decreaseValue(0, 0.5f);
        }
    }



}
Прошу разъяснить, как и что бы мне такого сделать, чтобы нормально читать все написанное в миксинах. Заодно хочу спросить, где удобнее располагать весь код - в Player или FoodData, потому как затем мне нужно будет вытащить все данные для использования в ui чтобы отобразить их в качестве прогрессбаров.
А еще я хочу после поменять те данные, которые передает конструктор еды (добавить значения тех полей, которые я внес в FoodData) и заставить eat() принимать эти значения, что, в теории, вызовет ту же проблему, что я описывал выше. Стою на коленях, моля о советах.
 
Миксинов в рантайме(во время запуска) нет, все их содержимое применяется к оригинальному классу.
Тут воспользуйся интерфейсом:
Java:
public interface IAdditionalFoodData {
    float the_most_complete_food_mod$getValue(int pId);
}
Реализуй(impliments) интерфейс своим миксином, переопределив метод.

Пример использования:
Java:
 @Inject(method = "drop(Lnet/minecraft/world/item/ItemStack;ZZ)Lnet/minecraft/world/entity/item/ItemEntity;", at = @At("TAIL"))
    private void increaseProteins(ItemStack pDroppedItem, boolean pDropAround, boolean pIncludeThrowerName, CallbackInfoReturnable<ItemEntity> cir) {
        IAdditionalFoodData dat = (IAdditionalFoodData) foodData;
        if (pDroppedItem.getItem() == Items.DIAMOND) {
           dat.the_most_complete_food_mod$increaseValue(0, 0.5f);
        } else {
            dat.the_most_complete_food_mod$decreaseValue(0, 0.5f);
        }
    }
Прошу разъяснить, как и что бы мне такого сделать, чтобы нормально читать все написанное в миксинах.
Ничего особо сложного тут нет.
Вот тут можно про всё основное прочитать.
Ну и если вот это кажется страшным:
drop(Lnet/minecraft/world/item/ItemStack;ZZ)Lnet/minecraft/world/entity/item/ItemEntity; - дескриптор ASM, описывает метод(имя метода, в скобках параметры, далее return)
L + путь = объект
Z = boolean
I = int
B = byte
и так далее.
Заодно хочу спросить, где удобнее располагать весь код - в Player или FoodData
Если твой код никак не связан с FoodData то лучше вынести куда-то в другое место(Любой класс), всё зависит от ситуации.
 
Прошу разъяснить, как и что бы мне такого сделать, чтобы нормально читать все написанное в миксинах.
Не совсем понял суть вопроса

Если проблема в том, что не можешь вызвать метод "the_most_complete_food_mod$getValue" (боже а по-нормальному назвать никак?)

То тупо приведи к FoodDataMixin (проблем быть не должно, если делать это внутри другого миксина)

Ну или как уже ответили выше - сделай интерфейс, реализуй метод и приводи к своему интерфейсу
 
"the_most_complete_food_mod$getValue" (боже а по-нормальному назвать никак?)
Боюсь что вот эта белиберда перед $ - ID мода, IDE просит называть методы/поля внутри миксинов с приставкой ID мода, так что он просто две кнопки нажал в IDE и всё.
А еще я хочу после поменять те данные, которые передает конструктор еды (добавить значения тех полей, которые я внес в FoodData) и заставить eat() принимать эти значения, что, в теории, вызовет ту же проблему, что я описывал выше
Ниче не понял, в конструктор FoodData нельзя ничего добавить.
потому как затем мне нужно будет вытащить все данные для использования в ui чтобы отобразить их в качестве прогрессбаров.
Тоже самое с интерфейсом сделай, игрок - Minecraft.getInstance().player
 
Миксинов в рантайме(во время запуска) нет, все их содержимое применяется к оригинальному классу.
Тут воспользуйся интерфейсом:
Java:
public interface IAdditionalFoodData {
    float the_most_complete_food_mod$getValue(int pId);
}
Реализуй(impliments) интерфейс своим миксином, переопределив метод.

Пример использования:
Java:
 @Inject(method = "drop(Lnet/minecraft/world/item/ItemStack;ZZ)Lnet/minecraft/world/entity/item/ItemEntity;", at = @At("TAIL"))
    private void increaseProteins(ItemStack pDroppedItem, boolean pDropAround, boolean pIncludeThrowerName, CallbackInfoReturnable<ItemEntity> cir) {
        IAdditionalFoodData dat = (IAdditionalFoodData) foodData;
        if (pDroppedItem.getItem() == Items.DIAMOND) {
           dat.the_most_complete_food_mod$increaseValue(0, 0.5f);
        } else {
            dat.the_most_complete_food_mod$decreaseValue(0, 0.5f);
        }
    }
Не совсем понял. То есть, мне нужно в другом интерфейсе (ну, к примеру, IPlayerDietContainer, который уже реализуется в FoodDataMixin и, к слову, имеет этот метод) описать нужный метод, а затем переопределить его в PlayerMixin? А будет ли он тогда вообще работать с переменными, взятыми из объекта на основе FoodData?
IPlayerDietContainer:
public interface IPlayerDietContainer {
    float proteins();
    ...

    float the_most_complete_food_mod$getValue(int pId);
    ...
}
Ниче не понял, в конструктор FoodData нельзя ничего добавить.
Сейчас при поедании через метод eat() внутри FoodData передается два параметра: foodLevel и saturationlevel. Я хочу добавить к ним новые поля, то есть мне придется переписывать конструктор в целом предмета еды FoodItem (вроде, так называется), всех его вызовов и передаваемых констант и всех его приемов в разных классах.
FoodData.eat():
public void eat(int pFoodLevelModifier, float pSaturationLevelModifier) {
      this.foodLevel = Math.min(pFoodLevelModifier + this.foodLevel, 20);
      this.saturationLevel = Math.min(this.saturationLevel + (float)pFoodLevelModifier * pSaturationLevelModifier * 2.0F, (float)this.foodLevel);
   }

То тупо приведи к FoodDataMixin (проблем быть не должно, если делать это внутри другого миксина)
А это как можно сделать?

P.S. - Немного поразбирался с интерфейсами и, как я понял, если я что-то добавил в миксин и хочу это что-то использовать вне самого миксина, я могу просто обратиться к нему как к реализуемому интерфейсу. Если я правильно понял, то это очень круто и сильно упрощает жизнь.
 
Последнее редактирование:
Немного поразбирался с интерфейсами и, как я понял, если я что-то добавил в миксин и хочу это что-то использовать вне самого миксина, я могу просто обратиться к нему как к реализуемому интерфейсу. Если я правильно понял, то это очень круто и сильно упрощает жизнь.
Да.
то есть мне придется переписывать конструктор в целом предмета еды
Нет такого предмета, вся еда - просто Item с FoodProperties
Java:
public static final FoodProperties COOKIE = (new FoodProperties.Builder()).nutrition(2).saturationMod(0.1F).build();

Можно использовать что-то такое:
FoodDataMixin:
    private float proteins = 20.0F;

    //Обязательно добавляешь в NBT все новые данные.
    @Inject(method = "addAdditionalSaveData", at = @At("TAIL"))
    public void saveNewData(CompoundTag nbt, CallbackInfo ci) {
        nbt.putFloat("Proteins", this.proteins);
    }
    
    @Inject(method = "readAdditionalSaveData", at = @At("TAIL"))
    public void readNewData(CompoundTag nbt, CallbackInfo ci) {
        this.proteins = nbt.getFloat("Proteins");
    }
    
    //Гетеры и сетеры через интерфейс

Через мапу добавляешь еде новые параметры
Любой класс:
private static final Map<Item, NewFoodProperties> MAP = Maps.newHashMap();

public static init() {
   MAP.put(Items.COOKED_PORKCHOP, new NewFoodProperties(16.0F));
   //Также и с другими видами еды.
}
    
@Nullable
private static NewFoodProperties get(Item item) {
   return MAP.get(item);
}

//Здесь новые поля для еды
public record NewFoodProperties(float proteins) {}
Далее:
FoodDataMixin:
//Тут внедряемся в определённую точку, после getFoodProperties
@Inject(method = "eat(Lnet/minecraft/world/item/Item;Lnet/minecraft/world/item/ItemStack;Lnet/minecraft/world/entity/LivingEntity;)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/item/ItemStack;getFoodProperties(Lnet/minecraft/world/entity/LivingEntity;)Lnet/minecraft/world/food/FoodProperties;", shift = At.Shift.AFTER))
public void eat(Item item, ItemStack itemStack, LivingEntity player, CallbackInfo ci) {
    //Здесь получаешь NewFoodProperties и применяешь
}

У клиентского игрока(который Minecraft.getInstance().player) получаешь FoodData, с помощью интерфейса свои данные оттуда, затем рендеришь.
Также в FoodDataMixin можешь сделать @Inject на метод tick где будет уменьшатся уровень новых данных.
 
Нет такого предмета, вся еда - просто Item с FoodProperties
Да, я ошибся, я имел в виду не Item, а FoodPropetries. Я ведь могу достичь того же результата, если добавлю поля в сам класс, затем методы для них в builder(), переопределение FoodProperties в Foods. Затем останется просто новым (в будущем) созданным eat() получать новые поля, а для совместимости с едой из других модов оставить старые методы. Так?1742884878373.png1742884889154.png1742884901583.png
 
Да, я ошибся, я имел в виду не Item, а FoodPropetries. Я ведь могу достичь того же результата, если добавлю поля в сам класс, затем методы для них в builder(), переопределение FoodProperties в Foods. Затем останется просто новым (в будущем) созданным eat() получать новые поля, а для совместимости с едой из других модов оставить старые методы. Так?
Можно конечно и так, но вариант выше, как по мне, лучше(меньше миксинов, удобнее).
 
  • Like
Реакции: S1nc
Назад
Сверху