Minecraft Forge 1.7.2/1.7.10 Creating Custom Spawn Egg

Background


For a while, the way provided to register a custom entity was to use the global ID system of entity registration.  The problem however was that the limited range of 255 had to be shared between vanilla entities as well as all loaded mods.  This raised the risk of running out of IDs or of having conflicting IDs.

To make entity registration more likely to be compatible, a per-mod method of entity registration was introduced with the EntityRegistry.registerModEntity()method.

The EntityRegistry.registerModEntity()method allows each mod to have its own range of mapped IDs so there isn't issue of overlap.  However, in 1.7.x versions one deficiency is that it doesn't take parameters to help create a spawn egg.

Note: In 1.8 they have restored the ability to create custom spawn eggs. Use the EntityRegistry registerEggs() method. You do not need this tutorial for 1.7.x!

Therefore, when using this method it is up to you to create a custom spawn egg.  This tutorial explains how this can be done.

Create Custom Spawn Egg Class That Extends ItemMonsterPlacer


ItemMonsterPlacer is the name used for the spawn egg item.  You can create a class that extends this such as:

public class WildAnimalsMonsterPlacer extends ItemMonsterPlacer
{
    @SideOnly(Side.CLIENT)
    private IIcon theIcon;
    protected int colorBase = 0x000000;
    protected int colorSpots = 0xFFFFFF;
    protected String entityToSpawnName = "";
    protected String entityToSpawnNameFull = "";
    protected EntityLiving entityToSpawn = null;

    public WildAnimalsMonsterPlacer()
    {
        super();
    }
    
    public WildAnimalsMonsterPlacer(String parEntityToSpawnName, int parPrimaryColor, 
          int parSecondaryColor)
    {
        setHasSubtypes(false);
        maxStackSize = 64;
        setCreativeTab(CreativeTabs.tabMisc);
        setEntityToSpawnName(parEntityToSpawnName);
        colorBase = parPrimaryColor;
        colorSpots = parSecondaryColor;
        // DEBUG
        System.out.println("Spawn egg constructor for "+entityToSpawnName);
    }

    /**
     * Callback for item usage. If the item does something special on right clicking, 
     * he will have one of those. Return
     * True if something happen and false if it don't. This is for ITEMS, not BLOCKS
     */
    @Override
    public boolean onItemUse(ItemStack par1ItemStack, EntityPlayer par2EntityPlayer, 
          World par3World, int par4, int par5, int par6, int par7, float par8, 
          float par9, float par10)
    {
        if (par3World.isRemote)
        {
            return true;
        }
        else
        {
            Block block = par3World.getBlock(par4, par5, par6);
            par4 += Facing.offsetsXForSide[par7];
            par5 += Facing.offsetsYForSide[par7];
            par6 += Facing.offsetsZForSide[par7];
            double d0 = 0.0D;

            if (par7 == 1 && block.getRenderType() == 11)
            {
                d0 = 0.5D;
            }

            Entity entity = spawnEntity(par3World, par4 + 0.5D, par5 + d0, par6 + 0.5D);

            if (entity != null)
            {
                if (entity instanceof EntityLivingBase && par1ItemStack.hasDisplayName())
                {
                    ((EntityLiving)entity).setCustomNameTag(par1ItemStack.getDisplayName());
                }

                if (!par2EntityPlayer.capabilities.isCreativeMode)
                {
                    --par1ItemStack.stackSize;
                }
            }

            return true;
        }
    }

    /**
     * Called whenever this item is equipped and the right mouse button is pressed. 
     *Args: itemStack, world, entityPlayer
     */
    @Override
    public ItemStack onItemRightClick(ItemStack par1ItemStack, World par2World, 
          EntityPlayer par3EntityPlayer)
    {
        if (par2World.isRemote)
        {
            return par1ItemStack;
        }
        else
        {
            MovingObjectPosition movingobjectposition = 
                  getMovingObjectPositionFromPlayer(par2World, par3EntityPlayer, true);

            if (movingobjectposition == null)
            {
                return par1ItemStack;
            }
            else
            {
                if (movingobjectposition.typeOfHit == MovingObjectPosition
                      .MovingObjectType.BLOCK)
                {
                    int i = movingobjectposition.blockX;
                    int j = movingobjectposition.blockY;
                    int k = movingobjectposition.blockZ;

                    if (!par2World.canMineBlock(par3EntityPlayer, i, j, k))
                    {
                        return par1ItemStack;
                    }

                    if (!par3EntityPlayer.canPlayerEdit(i, j, k, movingobjectposition
                          .sideHit, par1ItemStack))
                    {
                        return par1ItemStack;
                    }

                    if (par2World.getBlock(i, j, k) instanceof BlockLiquid)
                    {
                        Entity entity = spawnEntity(par2World, i, j, k);

                        if (entity != null)
                        {
                            if (entity instanceof EntityLivingBase && par1ItemStack
                                  .hasDisplayName())
                            {
                                ((EntityLiving)entity).setCustomNameTag(par1ItemStack
                                      .getDisplayName());
                            }

                            if (!par3EntityPlayer.capabilities.isCreativeMode)
                            {
                                --par1ItemStack.stackSize;
                            }
                        }
                    }
                }

                return par1ItemStack;
            }
        }
    }

    /**
     * Spawns the creature specified by the egg's type in the location specified by 
     * the last three parameters.
     * Parameters: world, entityID, x, y, z.
     */
    public Entity spawnEntity(World parWorld, double parX, double parY, double parZ)
    {
     
       if (!parWorld.isRemote) // never spawn entity on client side
       {
            entityToSpawnNameFull = WildAnimals.MODID+"."+entityToSpawnName;
            if (EntityList.stringToClassMapping.containsKey(entityToSpawnNameFull))
            {
                entityToSpawn = (EntityLiving) EntityList
                      .createEntityByName(entityToSpawnNameFull, parWorld);
                entityToSpawn.setLocationAndAngles(parX, parY, parZ, 
                      MathHelper.wrapAngleTo180_float(parWorld.rand.nextFloat()
                      * 360.0F), 0.0F);
                parWorld.spawnEntityInWorld(entityToSpawn);
                entityToSpawn.onSpawnWithEgg((IEntityLivingData)null);
                entityToSpawn.playLivingSound();
            }
            else
            {
                //DEBUG
                System.out.println("Entity not found "+entityToSpawnName);
            }
        }
      
        return entityToSpawn;
    }


    /**
     * returns a list of items with the same ID, but different meta (eg: dye returns 16 items)
     */
    @Override
    @SideOnly(Side.CLIENT)
    public void getSubItems(Item parItem, CreativeTabs parTab, List parList)
    {
        parList.add(new ItemStack(parItem, 1, 0));     
    }

    @Override
    @SideOnly(Side.CLIENT)
    public int getColorFromItemStack(ItemStack par1ItemStack, int parColorType)
    {
        return (parColorType == 0) ? colorBase : colorSpots;
    }

    @Override
    @SideOnly(Side.CLIENT)
    public boolean requiresMultipleRenderPasses()
    {
        return true;
    }
    
    @Override
    // Doing this override means that there is no localization for language
    // unless you specifically check for localization here and convert
    public String getItemStackDisplayName(ItemStack par1ItemStack)
    {
        return "Spawn "+entityToSpawnName;
    }  


    @Override
    @SideOnly(Side.CLIENT)
    public void registerIcons(IIconRegister par1IconRegister)
    {
        super.registerIcons(par1IconRegister);
        theIcon = par1IconRegister.registerIcon(getIconString() + "_overlay");
    }
    
    /**
     * Gets an icon index based on an item's damage value and the given render pass
     */
    @Override
    @SideOnly(Side.CLIENT)
    public IIcon getIconFromDamageForRenderPass(int parDamageVal, int parRenderPass)
    {
        return parRenderPass > 0 ? theIcon : super.getIconFromDamageForRenderPass(parDamageVal, 
              parRenderPass);
    }
    
    public void setColors(int parColorBase, int parColorSpots)
    {
     colorBase = parColorBase;
     colorSpots = parColorSpots;
    }
    
    public int getColorBase()
    {
     return colorBase;
    }
    
    public int getColorSpots()
    {
     return colorSpots;
    }
    
    public void setEntityToSpawnName(String parEntityToSpawnName)
    {
        entityToSpawnName = parEntityToSpawnName;
        entityToSpawnNameFull = WildAnimals.MODID+"."+entityToSpawnName; 
    }

}

The major difference between this code and the built-in spawn eggs is that this doesn't use any registry.  Instead, the colors and entity are contained in instance fields passed in with the constructor.

The way the spawn egg textures work is that there are actually two "icons" one for the egg and one with the spots pattern. These textures are gray-scale and then colors are blended on to them. There are two passes of rendering, so it is multi-pass.

The normal ItemMonsterPlacer has multiple subtypes which help it get displayed as multiple items in the creative tab. So in this case we still have to return a subtype, but always return 0 as we have only one.

Register The Custom Spawn Egg Like Regular Item


In your main class or common proxy, in the method that handles the pre-init FML lifecycle stage, put something like this (use the name of your custom spawn egg class instead of mine):

Item itemSpawnEgg = new WildAnimalsMonsterPlacer("Tiger", 0xE18519, 0x000000)
      .setUnlocalizedName("spawn_egg_"+parSpawnName.toLowerCase())
      .setTextureName("wildanimals:spawn_egg");
GameRegistry.registerItem(itemSpawnEgg, "spawnEgg"+parSpawnName);

Put in the name of your entity and the colors for the base and "spots" in place of mine.

Key Point: The entity name must match the one used to register your entity, because it will be the key for looking it up when you use the spawn egg.

The Color Fields


Tip: Colors in computers are often represented as RGB (red, green, blue) in a hexadecimal format.  To pick colors in this format, there are a number of color pickers available, for example I use: ColorPicker.  Just use such a tool to pick the color and then copy the hexadecimal value.

Tip: To represent a hexadecimal (integer) value in many programming languages, including Java, you precede the value by "0x".  For example, a nice color red is represented by 0xF50C27.

Tip: If there are certain colors you use a lot in your code, don't be afraid to make a constant to represent the value.

Make Sure You Have The Icon Texture Assets In Proper Resources Package


You need to have two textures in your Item textures package folder (assets.<modid>.textures.items). The first should be called spawn_egg.png and should have the shape of the egg.  The second should be called spawn_egg_overlay.png and can contain the spots (or stripes or whatever you want).

Key Point: Both textures should be gray-scale.  (The colors are applied by blending.)

Conclusion


While a slight pain, it really isn't too hard to create a custom spawn egg that behaves like the vanilla ones.  Have fun with this -- you can make the textures different (what about a square egg, or a striped egg).

As always, if you have corrections or clarifications needed don't hesitate to write!

3 comments:

  1. What's with parSpawnName? Do I need this variable?

    ReplyDelete
    Replies
    1. You don't need to use a field for that, you can hard-code it to the name of your egg item if you want. I used a field because I tend to make lots of eggs and therefore easiest to pass the name and then create the spawn egg name by putting "spawn egg" in front of that. It should be the name of the entity basically ("spawn egg eagle" if you had an eagle entity, etc. The total name is used to control what is displayed in the inventory.

      Delete
  2. You wrote "You do not need this tutorial for 1.7.x!" I think you meant 1.8.x?

    ReplyDelete