7. díl - Minecraft Modding - ItemFood, Potion s EventHook

Java Minecraft Modding Minecraft Modding - ItemFood, Potion s EventHook

V tomto díle si vytvoříme salát pomocí třídy ItemFood a přidáme mu vlastní PotionEffect.

ItemSalat

Nejprve si vytvoříme nový itemSalat, který bude dědit z ItemFood. Zde je konstruktor s parametry:

public ItemFood(int foodLevel, float sytost/saturace, boolean isWolfsFavoriteMeal)
  1. IsWolfsFavoriteMeal je true v případě jídla, kterým můžeme nakrmit vlka. (Ve vanille je to např: raw a cooked Porkchop)
  2. FoodLevel udává počet "půl-stehýnek", která se mají přidat po snězení tohoto itemu. (Hráč má maximum 20)
  3. Saturace (sytost) narozdíl od foodlevelu není znázorněna vizuálně. Když saturace dosáhne nuly, hráči začnou ubývat stehýnka. Maximum možné saturace se rovná počtu stehýnek: např. Pokud má hráč plný foodBar (všech 20 půl-stehýnek) bude maximum saturace 20F.

Dalším faktorem týkajícím se jídla je například vyčerpání (exhaustion). Tento faktor ovlivňuje jak rychle hráči bude ubývat saturace a foodLevel. Na MinecraftWiki je přehledná tabulka všech činností, které zvětšují vyčerpání. (např. sprint)

V konstruktoru ItemFood můžeme nastavit také:

this.setAlwaysEdible();

Pokud nastaveno, hráč bude moci jídlo sníst i v případě, pokud by měl foodLevel všech 20/20 (plně najeden). Normálně nastaveno na false.

Po troše teorie si zkusíme konstruktor:
public ItemSalat(){
        super(5,8F,false)//foodLevel, saturace, isWolfsFavoriteMeal
        this.setUnlocalizedName("salat");
        this.setTextureName("salat");
}

Doporučuji si psát komentáře, aby bylo přesně vidět, co je co.

Metody určené k Override
public int getMaxItemUseDuration(ItemStack itemstack)
   {
       return 32;
   }

Určuje v tickách, jak dlouho trvá snězení.

public EnumAction getItemUseAction(ItemStack itemstack)
    {
        return EnumAction.eat;
    }

Určuje animaci, která se má provést při události itemInUse. (Když používám item) Dále je možno použít také EnumAction.drink.

Událost jezení v ItemFood

Metoda OnItemRightClick() zjistí, zdali se hráč může najíst a pokud ano nastaví player.setIte­mInUse(getMaxI­temUseDuration()). Nyní se přehrává animace zvolená v metodě getItemUseAction(). Když hráč dojedl, zavolá se metoda onEaten() (která mimochodem přebíjí metodu z Item). Ta se postará o zmenšení počtu itemstacku o jedna, přehraje zvuk, nakrmí hráče pomocí stejné metody, kterou jsme použili ve 4. díle Solná Hůlka a crafting a nakonec zavolá metodu onFoodEaten(), která se postará o přidání nějakého potion efektu, pokud je nastaven.

Forge's event hooks

V překladu něco jako událostní háčky, je možnost reagovat na eventy pomocí jiných metod, než které jsou normálně volány u objektů, což může být problém ve chvíli, kdy chceme pracovat s nějakými vannila objekty, protože jejich třídy a metody upravovat nemůžeme. Tyto háčky nám umožní "zaháknout se" k nějakému eventu, reagovat na něj nebo ho zkonzumovat metodou setCanceled(bo­olean). Existuje mnoho eventů, na které můžeme reagovat. Pro příklad uvedu LivingJumpEvent, který se zavolá když entita skáče, EntityConstructing se zavolá při vytvoření entity a LivingUpdateEvent se zavolá při každém updatu entity. (Zvědavci nechť hodí oko na LivingUpdateEvent a třídy z které dědí atd.)

Vytvoření custom EventHooku

Pro obsluhování eventů si vytvoříme v novém balíčku nazvaném event_hooks novou třídu, která (což zní možná divně) nebude dědit z žadné jiné třídy, o vše se postará Forge. Tuto novou třídu si nazvěme ChokingPotionE­ventHook, která bude zajišťovat funkci zatím neexistujícího dávícího potion efektu, který hráč dostane ve chvíli, když sní salát s pravděpodobností 1/20. Zatím si připravíme metodu, která bude obsluhovat LivingUpdateEvent.

  1. Důležité je, aby se jmenovala public void onEntityUpdate(Li­vingUpdateEvent event).
  2. Musí mít anotaci @SubscribeEvent

Tato pravidla jsou důležitá z toho důvodu, že Forge si tuto třídu přečte, zjistí zdali je v ní metoda s anotací @SubscribeEvent a pokud ano, zjistí zdali se jmenuje správně a zdali má správný počet a typ parametrů, respektive jestli má hlavičku, jakou by měla mít.

public class ChokingPotionEventHook{

        @SubscribeEvent
        public void onEntityUpdate(LivingUpdateEvent event){}
}

Tímto jsme si vytvořili nový háček chytající událost, který s ní zatím nic nedělá. Nyní se přesuňme do Main třídy do metody init(FMLIniti­alizationEven­t).

Instanci ChokingPotionE­ventHook musíme registrovat, aby Forge věděl, že má spravovat nějaký eventHook. K tomu nám slouží v třídě MinecraftForge instance třídy EventBus nazvaná EVENT_BUS, na které zavoláme metodu register(Object) a vložíme do ní instanci naší "háčkové" třídy.

MinecraftForge.EVENT_BUS.register(new ChokingPotionEventHook());

Teď když spustíme Minecraft a otevřeme nějaký svět, metoda onEntityUpdate() v naší "háčkové" třídě se bude volat při každém ticku/updatu každé livingEntity. (To znamená, že se volá u každé entity zvlášť.)

PotionEffects

Minecraft má mnoho způsobu, jak vytvořit nějaké potiony nebo efekty. Já jsem pro tento díl naplánoval jednodušší cestu, a tak si vytvoříme jenom potionEffect a ne potionItem (lahvička s lektvarem). Potiony jsou uchovávány ve třídě Potion v public poli nazvaném potionTypes nebo field_76425_a (záleží na verzi Forge). Problém ovšem je, že toto pole je finální a taky že je to pole -> má finální délku. Toto pole má délku 32, znamená to tedy že pouhých 32 potionů může v Minecraftu existovat? Naštěstí je zde reflexion.

Reflection

Reflection popisuje kód, který je schopen zkontrolovat/u­pravovat jiný kód. Ve zkratce jsme schopni kód měnit za běhu programu, jako například JavaScript a práce s elementy. Můžeme například zjistit jaké všechny metody (i private) třída má nebo upravit velikost pole což je to, co hledáme. Metodu níže nebudu popisovat, protože odbočujeme z Minecraft Moddingu a dostáváme se do úplně jiné kapitoly. Stačí vědět, že tato metoda přepíše pole v Potion polem novým a větším a překopíruje do tohoto nového pole ze starého pole všechny potiony, takže o nic nepřijdeme a zvětšíme si pole na kolik chceme.

private void incrementPotionFieldLength(int newLength){
       Potion[] potionTypes = null;

       for (Field f : Potion.class.getDeclaredFields()) {
               f.setAccessible(true);
               try {
                       if (f.getName().equals("potionTypes") || f.getName().equals("field_76425_a")) {
                               Field modfield = Field.class.getDeclaredField("modifiers");
                               modfield.setAccessible(true);
                               modfield.setInt(f, f.getModifiers() & ~Modifier.FINAL);

                               potionTypes = (Potion[])f.get(null);
                               final Potion[] newPotionTypes = new Potion[newLength];
                               System.arraycopy(potionTypes, 0, newPotionTypes, 0, potionTypes.length);
                               f.set(null, newPotionTypes);
                       }
               }
               catch (Exception e) {
               System.err.println("Error, cant increment length of potions field. Report this to a mod author.");
               System.err.println(e);
               }
       }
   }

Tuto metodu si vytvoříme v naší Main třídě a budeme ji volat z preInit() jako první z celé metody. Jako novou velikost zvolím 50.

Potion podruhé

V novém balíčku nazvaném potions vytvoříme třídu ChokingPotion, která bude dědit z Potion.

public class ChokingPotion extends Potion {
        public ChokingPotion(int id) {
                super(id, false, 999999);//id, isBadEffect, int color
                this.setPotionName("potion.choking");
        }
}

Jak vidíte, super konstruktor má v parametrech id(index v poli Potion.potion­Types), jestli je potion špatný, a barvu particles. Dále nastavujeme potionName, který lze samozřejmě později upravit v lang souboru jako všechno ostatní:

potion.choking=Choking/Dávení

Tímto jsme dodělali ChokingPotion. Ještě bychom ale chtěli k našemu potionu přistupovat, jako je tomu v Potion (např. Potion.confusion). Do stejného balíčku, jako je ChokingPotion si vytvoříme třídu pojmenovanou SaltModPotions. Tato třída bude sloužit pouze jako úschovna všech našich potionů.

public static final Potion chokingPotion = new ChokingPotion(33);//id ==index v poli

Tímto máme celý Potion za sebou (kromě funkcionality) a můžeme ho aplikovat na hráče. Jak jsem již zmínil před chvílí, hráč se může zakuckat / dávit se, když sní ItemSalat. Do konstruktoru ItemSalat tedy přidáme řádek:

this.setPotionEffect(SaltModPotions.chokingPotion.getId(), 8, 1, 0.2F);// potion id, délka (v sekundách), level, pravděpodobnost

Zde snad stojí za zmínku pouze poslední parametr, který určuje pravděpodobnost, že efekt nastane. Parametr může být od 0F do 1F, kde platí, že 0 == nulová pravděpodobnost a 1==stoprocentní pravděpodobnost. Já zvolil 20% pravděpodobnost, že efekt nastane.

Závěrečná funkcionalita

Nyní pokud sníme salát, možná dostaneme efekt. Budou kolem nás vířit particles, ale to je tak všechno. Pro funkcionalitu jsme již dříve vyhradili ChokingPotionE­ventHook. Přesuňme se tedy do této třídy do jediné metody.

@SubscribeEvent
public void onEntityUpdate(LivingUpdateEvent event) {

}

Možná si říkáte, kde máme tu entitu. Ona je totiž mrška schovaná v eventu v atributu entityLiving. Teď bychom chtěli zjistit, zdali má tato entita náš ChokingPotionEf­fect.

if(event.entityLiving.getActivePotionEffect(SaltModPotions.chokingPotion)!=null){
        if (event.entityLiving.getActivePotionEffect(SaltModPotions.chokingPotion).getDuration()==0) {//někdy to funguje i po vyprchání efektu
                event.entityLiving.removePotionEffect(SaltModPotions.chokingPotion.id);
                return;
        }

        //Výše jsme zjistili, zdali má entita aktivní náš potionEffect, ještě se musí otestovat, zdali potionEffect ještě trvá, pokud ne, tento efekt odstranit.

        event.entityLiving.attackEntityFrom(DamageSource.drown, 1F);// Zranit entitu o půl srdíčka, a typ zranění topení

        Random rand = event.entityLiving.worldObj.rand;
        for(int i =0;i<40;i++){//vytvoření malé fontánky se splash particles na hlavě entity
                boolean dir1 = rand.nextBoolean();
                boolean dir2 = rand.nextBoolean();
                double velZ=rand.nextInt(10)+1;
                double velX=rand.nextInt(10)+1;
                velZ=dir1?velZ:-velZ;
                velX=dir2?velX:-velX;

                event.entityLiving.worldObj.spawnParticle("splash", event.entityLiving.posX, event.entityLiving.posY, event.entityLiving.posZ,velX,rand.nextDouble()*3,velZ);
        }
}
Závěr

Třídy ItemSalat, ChokingPotionE­ventHook, Main, ChokingPotion, SaltModPotions jsou ke stáhnutí pod článkem + obrázek salátu.

Nyní by vše mělo fungovat: pokud hráč sní salát, přibyde mu 5 půl-stehýnek, možná dostane ChokingPotionEf­fect, začne se dusit, ubývají mu srdíčka, a stříká mu z hlavy voda. Ehmm to nedává moc smysl co? :D


 

Stáhnout

Staženo 121x (3.23 kB)

 

  Aktivity (1)

Článek pro vás napsal Matěj Černý
Avatar
Autor se věnuje programování v Javě, moduje Java hru Minecraft a pracuje se Cinema4D.

Jak se ti líbí článek?
Celkem (4 hlasů) :
4.754.754.754.754.75


 


Miniatura
Všechny články v sekci
Minecraft Modding

 

 

Komentáře

Avatar
Jakub Rohla
Člen
Avatar
Jakub Rohla:

Neměl by ten index choking potionu být 32 namísto 33? Přecijen je to pole 0-31, ne?

Odpovědět 27.12.2015 8:48
Minecraft can do ANYTHING, it's coded in Java and you got the full power of Java behind you when you code. So no...
Avatar
Matěj Černý
Redaktor
Avatar
Odpovědět 31.12.2015 8:16
Všechno jde obejít.
Děláme co je v našich silách, aby byly zdejší diskuze co nejkvalitnější. Proto do nich také mohou přispívat pouze registrovaní členové. Pro zapojení do diskuze se přihlas. Pokud ještě nemáš účet, zaregistruj se, je to zdarma.

Zobrazeno 2 zpráv z 2.