Resource [Tutorial] Create custom modeled mobs

Discussion in 'Spigot Plugin Development' started by NacOJerk, Sep 15, 2016.

  1. NacOJerk

    Supporter

    Before you start reading this guide please note that this tutorial requiers
    my premuim plugin MiniaturePets
    ( You actually dont have to own the plugin to program for it
    You would just have no way to test your code )

    This requiers mini pet 1.2.9 and up

    Also I gonna probably have allot of spelling mistakes so ya if you wanna be nice and state it and not say
    "Wut a nub u need to writ hello with 2 L not one (Stupid nub)"
    and if you have any recommendation for me to reorginaize the tutorial please tell
    :)

    In this guide we are going to create a panda that eats sugar canes
    And that have an hunger stat
    End Result:


    So lets start the first thing you would need is to import the API jar file from here (Follow the guide so you would have full documentation and you would understand the stuff in the API) and you would need to add this to your plugin.yml:
    Code (Text):
    depend: [MiniaturePets]
    Lets first load our model for our mob (a panda) we gonna load the model stright from the jar file so first we need to add the model for that we are going to create a new folder called "models" in the project (Right click the project go to new and than folder) and we are going to add the panda model (Which you can get from here) now we are going to use a util we have in the APIUtils that allowes us to load a file from inside the jar .loadModelByName(Model Name, the plugin instance) this loads the file (Model Name).mpet from the folder "models" inside of your jar example :
    Code (Text):
            File model = null;
            try{
                model = APIUtils.loadModelByName("Panda", this);
            }catch(IOException e){
                getLogger().warning("Couldnt load model for the panda mob");
                e.printStackTrace();
            }
     
    Now lets start with creating our first "APIMobContainer" (a object that you create once per mob type which basicly stores the info needed for constructing the mob on spawing) there are 2 constructors for the "APIMobContainer" object both are the same except from the location you load the model though I would recommand using the one you load the model using a file here is the constructor:
    new APIMobContainer(File file, String name, double health, double speed, EntityType nType, String nmsNType) and here is what every field means (You can also see it in the docs if you loaded it currectly)
    Code (Text):
    file - This is the file where the pet would be loaded from (must be a .mpet file or else this wont work)
    name - This is the name of the mob it would be shown in kill reports and more
    health - The health of the mob
    speed - The speed of the mob
    nType - The type of anchor entity
    nmsNType - The nms tag of the anchor entity (Needed for some nms actions including silencing the mob)
    Example (using the model file we got in the last example) :
    Code (Text):
    APIMobContainer mobContainer = new APIMobContainer(model, "PandaMob", 20, 0.17D, EntityType.CHICKEN, "Chicken");
    Now all mobs spawned from this mob container would have this model :
    [​IMG]
    The mob container name would be "PandaMob" the panda health would be 20 it's speed would be 0.17(Which is a normal speed) the anchor mob would be a chicken.

    Now we would like to clear our anchor mob (The chicken) AI we do that using a MobSpawnAction which is called and executed when ever a mob from this mob container is spawned in there we would do paramAPIMob.clearAI(); (Which throws an exception because this method uses reflect)
    Example:
    Code (Text):
            mobContainer.addSpawnAction(new MobSpawnAction() {
         
                @Override
                public void spawnMob(APIMob paramAPIMob, Location paramLocation) {
                    try {
                        paramAPIMob.clearAI();
                    } catch (Exception e) {
                        getLogger().warning("Couldnt clear AI for the panda mob removing it");
                        paramAPIMob.remove();
                        e.printStackTrace();
                        return;
                    }
                }
            });
    Now lets test our creation state to spawn the mob you need to use the method .spawnMob(Location loc)
    Example (Im spawning the mob using a command):
    Code (Text):
    APIMob mob = mobContainer.spawnMob(((Player) sender).getLocation());
    If you followed the guide completly when you spawn your mob you should have a none moving entity that has a panda model that when killed drops the normal drops for a chicken (or what ever anchor mob you choose)

    Now we would want to create some AI for our silly stuiped panda but before that I want to add a hunger field to my mob to do that we gonna add this line to our MobSpawnAction
    Code (Text):
    paramAPIMob.addObject("hungerLevel", 10);
    Because the way the api works you cant add fields normally I created a map that contains field names and objects for every mob , if you added this currectly your MobSpawnAction should now look like this
    Code (Text):
            mobContainer.addSpawnAction(new MobSpawnAction() {

                @Override
                public void spawnMob(APIMob paramAPIMob, Location paramLocation) {
                    try {
                        paramAPIMob.clearAI();
                    } catch (Exception e) {
                        getLogger().warning(
                                "Couldnt clear AI for the panda mob removing it");
                        paramAPIMob.remove();
                        e.printStackTrace();
                        return;
                    }
                    paramAPIMob.addObject("hungerLevel", 10);
                }
            });
    Now that we added the field its time for us to create our first pathfinder to do that we would need to create a new class that expands the abstarct class "Pathfinder" I called this pathfinder BambukEater as this is the pathfinder that is used to make the panda eat sugar canes if you are completly following the guide you should end up with something like this
    Code (Text):
    package com.kirelcodes.randomproject;

    import com.kirelcodes.miniaturepets.api.pathfinding.Pathfinder;

    public class BambukEater extends Pathfinder {

        @Override
        public boolean shouldStart() {
            return false;
        }

        @Override
        public void updateTask() {
     
        }

    }
    Now we would like to have access to our Mob via the bambuk eater so we gonna make a constructor that takes a "APIMob" type and stores it as a field and we would also want to have easy access to our hunger level.

    First we would want to check the APIMob we are getting is panda type to do that we would like to check the mob.getName() which returns the mobContainer name in our case we would like to check if it is "PandaMob" if not we would warn the owner using the console and would not let the pathfinder to start (Via changing some stuff in the shouldStart method)

    Now we would like to get access to the hunger level (So we could get it and set it)
    To get the hungerLevel you should do this
    Code (Text):
    (int) panda.getObject("hungerLevel");
    And to set the hungerLevel you should do this
    Code (Text):
    panda.updateObject("hungerLevel", level);
    If you were smart enough to do the hungerLevel accessing via methods you should end up with something like this (I also added in the example the full check for the mob type):
    Code (Text):
        private APIMob panda;
        public BambukEater(APIMob panda){
            if(!"PandaMob".equalsIgnoreCase(panda.getName())){
                System.out.println("Trying to start a panda AI on a non panda mob");
                return;
            }
            this.panda = panda;
        }

        private int getHungerLevel(){
            return (int) panda.getObject("hungerLevel");
        }

        private void setHungerLevel(int level){
            panda.updateObject("hungerLevel", level);
        }

        @Override
        public boolean shouldStart() {
            return panda != null; //You can see that if the mob type is not PandaMob we dont set it so it would be null this way we can tell the pathfinder to never start
        }

        @Override
        public void updateTask() {
     
        }
    Now that we can access all of these things we would like to make it so every second the hunger level would go down and that if the hunger level is 0 the mob would have its health lowered by 1 to do that we would add another field called interalClock which would basicly count up all the time and when ever it reaches a multiple of 20 it would recongnize it was a second and lower down the hunger level now to do something happened after every taskUpdate we could override the method afterTask and add what I said above you should end up with this:
    LINK

    Now lets start with the actual AI we would like to check for any near by items and check if they are sugar canes / normal sugar if they are we would like the panda to walk to them and than pickthem up when it is in the location of them , than we would like the panda food level to go up by 1 for sugar and 2 for sugar cane and to add it to its inventory

    First we would need to scan for near by entities to do that we get the anchorMob and scan for near by entities the normal way and check if it is an item or not (You should end up with something like this , from now on we would be focused on the updateTask method so I would only show its content until we end with it):
    Code (Text):
        @Override
        public void updateTask() {
            for(Entity e : panda.getNavigator().getNearbyEntities(5, 5, 5)){
                if(!(e instanceof Item))
                    continue;
            }
        }
    Now we would like to check if the item is a sugar cane or sugar and than we would like to store our target entity so we would create a new field named target item (Which we would null in the onStart method) and set our item to be it we would also want to not scan for aslong as our target item is not null / dead now you should end up with this :
    Code (Text):
        @Override
        public void updateTask() {
            if(this.targetItem != null){
                if(this.targetItem.isDead())
                    return;
                //Target Finding here
            }
            for(Entity e : panda.getNavigator().getNearbyEntities(5, 5, 5)){
                if(!(e instanceof Item))
                    continue;
                Item item = (Item) e;
                ItemStack itemStack = item.getItemStack();
                if(!(itemStack.getType() == Material.SUGAR || itemStack.getType() == Material.SUGAR_CANE))
                    continue;
                this.targetItem = item;
                break;
            }
            try {
                panda.stopPathfinding(); //This is incase the target item got already fully removed from memory before we could stop path finding
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    Now we would like to tell the mob to go to the target location and when it is there we would like for it to pick it up to do that we would use 2 methods
    1 .setTargetLocation(Location loc) (Which throws an exception) if this method returns false it means there is no path to the target
    2 .onTargetLocation() which returns false if the target location is null or if the distance from it is less that 0.1
    We would also want to see if we even have a target location for that we would use
    .getTargetLocation() which gives us null if we have no target location
    First we would like to check if there is a target location so we would know not to set the target location over and over agian now if there is non we would like to set a target location and check if there is no path we would like to null the target item so we could find a new one (null the field not the actuall entity)
    Now if there is a target location we would like to see if we are at the target location if we are we would make the panda to pick up the item and raise its hunger level and we would kill them item after you done that you are basicly done your entiar pathfinder should look like this:
    Code (Text):
    package com.kirelcodes.randomproject;

    import org.bukkit.Material;
    import org.bukkit.entity.Entity;
    import org.bukkit.entity.Item;
    import org.bukkit.inventory.ItemStack;

    import com.kirelcodes.miniaturepets.api.pathfinding.Pathfinder;
    import com.kirelcodes.miniaturepets.api.pets.APIMob;

    public class BambukEater extends Pathfinder {
        private APIMob panda;
        private int interalClock;
        private Item targetItem;

        public BambukEater(APIMob panda) {
            if (!"PandaMob".equalsIgnoreCase(panda.getName())) {
                System.out.println("Trying to start a panda AI on a non panda mob");
                return;
            }
            this.panda = panda;
        }

        private int getHungerLevel() {
            return (int) panda.getObject("hungerLevel");
        }

        private void setHungerLevel(int level) {
            panda.updateObject("hungerLevel", level);
        }

        /**
         * This is called when ever the pathfinding task starts agian (That means
         * that if it got cancelled for some reason this method could reset our
         * fields)
         */
        @Override
        public void onStart() {
            interalClock = 0;
            targetItem = null;
        }

        @Override
        public void afterTask() {
            interalClock++;
            if ((interalClock % 20) == 0) {// Says its been a second
                setHungerLevel(getHungerLevel() - 1);// Lowers the hunger level
                if (getHungerLevel() <= 0) {// If the hunger level is equal or
                                            // smaller (Not that this should ever
                                            // happened but better be safe and than
                                            // sorry)
                    if (panda.getHealth() <= 1) {
                        panda.setHealth(0);// Kills the mob
                        return;
                    }
                    panda.setHealth(panda.getHealth() - 1);// Lowers down the panda
                                                            // anchor mob health
                }
            }
        }

        @Override
        public boolean shouldStart() {
            return panda != null; // You can see that if the mob type is not
                                    // PandaMob we dont set it so it would be null
                                    // this way we can tell the pathfinder to never
                                    // start
        }

        @Override
        public void updateTask() {
            if (this.targetItem != null) {
                if (this.targetItem.isDead()) {
                    try {
                        panda.stopPathfinding();// I didnt tell about this in the
                                                // tutrial this basicly tells the
                                                // mob to start going to the target
                                                // So if the itme is dead it would
                                                // stop walking to it
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    return;
                }
                if (panda.getTargetLocation() == null) {
                    try {
                        if(!this.targetItem.isOnGround())//This would deflect the panda from trying to go to an item that is in the air
                            return;
                        if (!panda.setTargetLocation(targetItem.getLocation())) {
                            this.targetItem = null;// This gets executed when ever
                                                    // the panda cant find a path to
                                                    // the item
                            return;
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    return;
                }
                if (!panda.onTargetLocation())
                    return;
                ItemStack itemStack = targetItem.getItemStack();
                switch (itemStack.getType()) {
                    case SUGAR:
                        setHungerLevel(getHungerLevel() + 1);
                        break;
                    case SUGAR_CANE:
                        setHungerLevel(getHungerLevel() + 2);
                        break;
                    default:
                        break;
                }
                panda.getInventory().addItem(itemStack);
                targetItem.remove();
                targetItem = null;
            }
            for (Entity e : panda.getNavigator().getNearbyEntities(5, 5, 5)) {
                if (!(e instanceof Item))
                    continue;
                Item item = (Item) e;
                ItemStack itemStack = item.getItemStack();
                if (!(itemStack.getType() == Material.SUGAR || itemStack.getType() == Material.SUGAR_CANE))
                    continue;
                this.targetItem = item;
                break;
            }
        try {
                panda.stopPathfinding(); //This is incase the target item got already fully removed from memory before we could stop path finding
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    (If you wanna see some more pathfinders you can take a look at some other projects of myn that uses pathfinding like RobotiCraft)

    Now that we are done with the pathfinding we would like to set the pathfinder to the mob when ever it spawns we do that by adding this line to our MobSpawnAction
    Code (Text):
    paramAPIMob.getPathManager().addPathfinder(new BambukEater(paramAPIMob));

    Now we are almost done we would also like for the panda to drop the items once it dies for that we would have to use one of the events called "MobDeathEvent" there we can change the dropped items
    First we would like to check if the mob type is PandaMob we already learned how to do that so there is no reason for me to reexplain that
    Than we would clear all of the dropped item list which we can do by writing this:
    Code (Text):
    e.getDrops().clear();
    Than we would add all of the mobs item into the dropped item list using a simple for loop no reason for me to explain you guys how to do that you should end up with this :
    Code (Text):
        @EventHandler
        public void mobDeath(MobDeathEvent e){
            if(!"PandaMob".equalsIgnoreCase(e.getMob().getName()))
                return;
            e.getDrops().clear();
            for(ItemStack item : e.getMob().getInventory().getContents())
                e.getDrops().add(item);
        }
    We are basicly done now.
    This is just a basic mob I hope to see really cool shit uing my plugin

    Now all you need to do is to export the jar , please note that to run the plugin you dont use the API jar you use the normal miniature pets jar

    btw
    fun fact this tutrial is the first time Im checking the pathfinding implenation on mini pet api
    (It worked if you didnt get it from the fact I released the tutrial)

    You can see the end result on the top of this page if you didnt see it yet

    Projects using the API
    Harambe (by me)
    MilkMan (by me)

    THERE IS MORE GUIDE BELOW
     
    #1 NacOJerk, Sep 15, 2016
    Last edited: Feb 4, 2017
    • Useful x 10
    • Like x 5
    • Optimistic x 4
    • Winner x 2
    • Informative x 2
    • Creative x 2
    • Funny x 1
    • Friendly x 1
  2. NacOJerk

    Supporter

    _______________________________________________________________________________________

    How to run animations for the pets​
    So as of mpet version 1.4B you can run animations from the .mpet file how do you do that ?
    Very easily first create the animation you want for your model and add the animation inside of a file called
    animation_[NameOfAnimation] into the .mpet file for example lets say I want to make a move head animation
    This is how the file would look
    [​IMG]
    And this is how the folder would look inside of it
    [​IMG]

    Now lets move over to code wise , it is very easy to use this new feature you just use the following new methods
    Code (Text):
        /**
         * Checks if your given model has a certin animation folder (inside the model there is an animation_(NAME) folder)
         * @param name the name of the animation
         * @return if it is there or not
         */
        public boolean containsAnimation(String name)

        /**
         * Runs a given animation
         * @param name the animation name
         */
        public void runAnimation(String name)

        /**
         * Checks if a given animation is running
         * @param name the animation name
         * @return is it running ?
         */
        public boolean isAnimationRunning(String name)
     
        /**
         * Stop a given animation
         * @param name the animation name
         */
        public void stopAnimation(String name)
     
    If we keep on using the MOVEHEAD example to start running the animation I would do
    Code (Text):
    mob.runAnimation("MOVEHEAD"); //Must be caps
    To test if the animation is there I would do
    Code (Text):
    mob.containsAnimation("MOVEHEAD"); //MUST BE CAPS
    To test if the animation is running (You must make sure the animation is not running when you start it agian)
    Code (Text):
    mob.isAnimationRunning("MOVEHEAD"); //MUST BE CAPS !
    And to stop the animation I would just do :
    Code (Text):
    mob.stopAnimation("MOVEHEAD"); //MUST BE CAPS !!!

    Since version 1.4B2
    Added the following methods :
    Code (Text):
        /**
         * Use this to see if any animation is running (Inluding walking and IDLE)
         * @return if any animation is running
         */
        public boolean isAnimationRunning()
       
       
        /**
         * Use this to stop all running animations
         */
        public void stopAllAnimation()
       
     
    This two methods must be used before running any animation so there wont be any problem how to use
    Code (Text):
    if(mob.isAnimationRunning())
    {
        mob.stopAllAnimation();
    }
    mob.runAnimation("MOVEHEAD");
    _________________________________________________________________​
     
    #2 NacOJerk, Sep 15, 2016
    Last edited: Feb 4, 2017
    • Informative Informative x 1
  3. Wow!! This is awesome!
    Really detailed tutorial! Really recommending!
    Good Luck pal!
     
    • Like Like x 1
  4. Love it! Looking forward to see community creations!
     
    • Like Like x 1
  5. NacOJerk

    Supporter

    They can only start using it once you post the update of the plugin ;)
     
  6. NacOJerk

    Supporter

    UPDATE IS OUT
    (You can now fully use the guide)
     
    • Like Like x 1
  7. Ah looks pretty cool, I would like to use it, if it only included casting models to a mob since I can't seem to find any public information on custom models for entities. Sixteen dollars is a lot to pay for just to use since I have no idea how long you are going to be updating this or if I can make my own mob models to use inside the plugin.
     
  8. NacOJerk

    Supporter

    Well mm it is 12$ if Im not wrong also the main thing is the miniaturepets plugin this is just a hook on API also if you are a developer you can get it for free (If you assure to release a plugin to spigot which uses the API)
     
  9. Ah I live in Canada so I was looking at the price in CAD. I am working on a plugin but am unsure if I will ever release anywhere, I am just looking for a way to be able to have custom mob models and this has been the only post I have seen that actually works.
     
  10. NacOJerk

    Supporter

    Well IDK if it is for your own server you can get mini pet by paying idk
     
  11. K, if we go by the tutorial. Does the new panda mob share the same hitbox as a chicken? Is there any way to create our own pathfinder instead of using yours. If we create a mob using your api does it interact like a pet? or is there a way to make it interact like a hostile mob.
     
  12. NacOJerk

    Supporter

    Well the chicken is invisible to the player and if you really want I can make a guide to how to make it so the only hit box that would be interact able would be the model one and not the anchor mob also you can build your own pathfinders and using them you can builds hostile mobs of you really want too I don't mind building a mob with you so you can learn
    I am also going to make a half hostile mob in my next custom mob I gonna release (It's gonna be harmbe :p)
     
  13. Is there any way for me to model my own mob and be able to access it? It sounds good if I can do all of that with it.
     
  14. Very interresting! Your plugin looks like (and is probaly based on) Hypixel's companions. I would be happy (and you are probaly not going to do this, as it explains your premium plugin), if you are able to make a tutorial on how to make the moving animal system, with all the maths.

    These animals are probaly made with multiple moving invisble armor stands right?
     
  15. No they are using just normal animal entities. A detailed guide on entities with pathfinders can be found here: https://www.spigotmc.org/threads/tutorial-creating-custom-entities-with-pathfindergoals.18519/
     
  16. typo:

    requiers should be requires
     
  17. The API mentions nothing about resource pack or forge requirements, and adding new models is completely impossible with bukkit/spigot, what server version is this for?
     
  18. It uses a combination of armour stands and heads.
     
  19. Can heads have alpha areas on them? That duck looks decidedly complex..

    I will try to reverse-engineer this and produce a buggy, nonfunctional mess of code when i am more awake. I was settling on biome-specific textures using optifine, for custom mobs, but this technique looks nice for small stuff.
     
  20. Buy the plugin and check it out :)
     

Share This Page