Vydělávej až 160.000 Kč měsíčně! Akreditované rekvalifikační kurzy s garancí práce od 0 Kč. Více informací.
Hledáme nové posily do ITnetwork týmu. Podívej se na volné pozice a přidej se do nejagilnější firmy na trhu - Více informací.

Lekce 7 - Minecraft Modding - ItemFood, Potion s EventHook

V předchozí lekci, Minecraft Modding - Teleport 2. část, jsme si dodělali náš teleportovací systém.

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žením následujícího souboru souhlasíš s licenčními podmínkami

Staženo 594x (3.23 kB)

 

Předchozí článek
Minecraft Modding - Teleport 2. část
Všechny články v sekci
Minecraft Modding
Článek pro vás napsal Matěj Černý
Avatar
Uživatelské hodnocení:
4 hlasů
Autor se věnuje programování v Javě, C++, zajímá se o OpenGL.
Aktivity