public class BlockGraveStone extends BlockContainer {

    public static final PropertyDirection FACING = PropertyDirection.create("facing", EnumFacing.Plane.HORIZONTAL);

    public BlockGraveStone() {
        super(Material.ROCK);
        this.setSoundType(SoundType.STONE);
        this.setHardness(0.5F);
        this.setResistance(5);
        this.setCreativeTab(Tabs.gravesTab);
        this.setTickRandomly(Config.removeEmptyGraves);
        this.setRegistryName("GSGraveStone");
    }

    /**
     * Called when the block is placed in the world
     */
    @Override
    public void onBlockPlacedBy(World world, BlockPos pos, IBlockState state, EntityLivingBase player, ItemStack itemStack) {
        GraveStoneHelper.replaceGround(world, pos.down());

        EnumFacing enumfacing = EnumFacing.getHorizontal(MathHelper.floor((double) (player.rotationYaw * 4 / 360F) + 0.5D) & 3).getOpposite();
        state = state.withProperty(FACING, enumfacing);
        world.setBlockState(pos, state, 2);
        TileEntityGraveStone tileEntity = (TileEntityGraveStone) world.getTileEntity(pos);

        if (tileEntity != null) {
            NBTTagCompound nbt = itemStack.getTagCompound();
            if (nbt != null) {
                tileEntity.setGraveType(itemStack.getItemDamage());

                if (nbt.hasKey("isLocalized") && nbt.getBoolean("isLocalized")) {
                    tileEntity.getDeathTextComponent().setLocalized();

                    if (nbt.hasKey("name") && nbt.hasKey("KillerName")) {
                        tileEntity.getDeathTextComponent().setName(nbt.getString("name"));
                        tileEntity.getDeathTextComponent().setKillerName(nbt.getString("KillerName"));
                    }
                }

                tileEntity.getDeathTextComponent().setDeathText(nbt.getString("DeathText"));

                tileEntity.setAge(nbt.getInteger("Age"));

                tileEntity.setEnchanted(nbt.getBoolean("Enchanted"));

                tileEntity.setMossy(nbt.getBoolean("Mossy"));

                tileEntity.setPurified(nbt.getBoolean("Purified"));

                if (nbt.hasKey("Sword")) {
                    ItemStack sword = new ItemStack(nbt.getCompoundTag("Sword"));
                    tileEntity.setSword(sword);
                    if (sword.isItemEnchanted()) {
                        tileEntity.setEnchanted(true);
                    }
                }
            }
        }
    }

    /**
     * Checks to see if its valid to put this block at the specified
     * coordinates. Args: world, x, y, z
     */
    @Override
    public boolean canPlaceBlockAt(World world, BlockPos pos) {
        return GraveStoneHelper.canPlaceBlockAt(world, pos.down());
    }

    @Nullable
    @Override
    public AxisAlignedBB getCollisionBoundingBox(IBlockState blockState, IBlockAccess worldIn, BlockPos pos) {
        return null;
    }

    private static final AxisAlignedBB VP_SOUTH_BB = new AxisAlignedBB(0.125, 0, 0.0625, 0.875, 0.9375, 0.1875);
    private static final AxisAlignedBB VP_NORTH_BB = new AxisAlignedBB(0.125F, 0, 0.8125F, 0.875F, 0.9375F, 0.9375F);
    private static final AxisAlignedBB VP_EAST_BB = new AxisAlignedBB(0.0625F, 0, 0.125F, 0.1875F, 0.9375F, 0.875F);
    private static final AxisAlignedBB VP_WEST_BB = new AxisAlignedBB(0.8125F, 0, 0.125F, 0.9375F, 0.9375F, 0.875F);
    private static final AxisAlignedBB CROSS_SOUTH_BB = new AxisAlignedBB(0.125F, 0, 0.0625F, 0.875F, 1, 0.1875F);
    private static final AxisAlignedBB CROSS_NORTH_BB = new AxisAlignedBB(0.125F, 0, 0.8125F, 0.875F, 1, 0.9375F);
    private static final AxisAlignedBB CROSS_EAST_BB = new AxisAlignedBB(0.0625F, 0, 0.125F, 0.1875F, 1, 0.875F);
    private static final AxisAlignedBB CROSS_WEST_BB = new AxisAlignedBB(0.8125F, 0, 0.125F, 0.9375F, 1, 0.875F);
    private static final AxisAlignedBB CC_NORTH_SOUTH_BB = new AxisAlignedBB(0.125F, 0, 0.35F, 0.875F, 1.3F, 0.65F);
    private static final AxisAlignedBB CC_EAST_WEST_BB = new AxisAlignedBB(0.35F, 0, 0.125F, 0.65F, 1.3F, 0.875F);
    private static final AxisAlignedBB PL_STATUES_BB = new AxisAlignedBB(0.35F, 0, 0.35F, 0.65F, 0.92F, 0.65F);
    private static final AxisAlignedBB HP_NORTH_SOUTH_BB = new AxisAlignedBB(0.09375F, 0, 0.0625F, 0.90625F, 0.0625F, 0.9375F);
    private static final AxisAlignedBB HP_EAST_WEST_BB = new AxisAlignedBB(0.0625F, 0, 0.09375F, 0.9375F, 0.0625F, 0.90625F);
    private static final AxisAlignedBB DOG_SOUTH_BB = new AxisAlignedBB(0.35F, 0, 0.3F, 0.6F, 0.5F, 0.9F);
    private static final AxisAlignedBB DOG_NORTH_BB = new AxisAlignedBB(0.35F, 0, 0.7F, 0.6F, 0.5F, 0.1F);
    private static final AxisAlignedBB DOG_EAST_BB = new AxisAlignedBB(0.3F, 0, 0.35F, 0.9F, 0.5F, 0.6F);
    private static final AxisAlignedBB DOG_WEST_BB = new AxisAlignedBB(0.7F, 0, 0.35F, 0.1F, 0.5F, 0.6F);
    private static final AxisAlignedBB CAT_SOUTH_BB = new AxisAlignedBB(0.43F, 0, 0.3F, 0.57F, 0.5F, 0.75F);
    private static final AxisAlignedBB CAT_NORTH_BB = new AxisAlignedBB(0.43F, 0, 0.7F, 0.57F, 0.5F, 0.25F);
    private static final AxisAlignedBB CAT_EAST_BB = new AxisAlignedBB(0.3F, 0, 0.43F, 0.75F, 0.5F, 0.57F);
    private static final AxisAlignedBB CAT_WEST_BB = new AxisAlignedBB(0.7F, 0, 0.43F, 0.25F, 0.5F, 0.57F);
    private static final AxisAlignedBB CORPSE_BB = new AxisAlignedBB(0, 0, 0, 1, 0.3F, 1);
    private static final AxisAlignedBB SWORD_SOUTH_NORTH_BB = new AxisAlignedBB(0.375F, 0, 0.4375F, 0.625F, 0.9F, 0.5625F);
    private static final AxisAlignedBB SWORD_EAST_WEST_BB = new AxisAlignedBB(0.4375F, 0, 0.375F, 0.5625F, 0.9F, 0.625F);
    private static final AxisAlignedBB HS_SOUTH_NORTH_BB = new AxisAlignedBB(0.375F, 0, 0.275F, 0.625F, 0.85F, 0.725F);
    private static final AxisAlignedBB HS_EAST_WEST_BB = new AxisAlignedBB(0.275F, 0, 0.375F, 0.725F, 0.85F, 0.625F);

    @Override
    public AxisAlignedBB getBoundingBox(IBlockState state, IBlockAccess access, BlockPos pos) {
        if (state.getBlock() == GSBlock.GRAVE_STONE) {
            EnumFacing facing = state.getValue(FACING);
            EnumGraveType graveType;
            TileEntityGraveStone tileEntity = (TileEntityGraveStone) access.getTileEntity(pos);

            if (tileEntity != null) {
                graveType = tileEntity.getGraveType().getGraveType();
            } else {
                graveType = EnumGraveType.VERTICAL_PLATE;
            }

            switch (graveType) {
                case VERTICAL_PLATE:
                    switch (facing) {
                        case SOUTH:
                            return VP_SOUTH_BB;
                        case NORTH:
                            return VP_NORTH_BB;
                        case EAST:
                            return VP_EAST_BB;
                        case WEST:
                            return VP_WEST_BB;
                    }
                    break;
                case CROSS:
                    switch (facing) {
                        case SOUTH:
                            return CROSS_SOUTH_BB;
                        case NORTH:
                            return CROSS_NORTH_BB;
                        case EAST:
                            return CROSS_EAST_BB;
                        case WEST:
                            return CROSS_WEST_BB;
                    }
                    break;
                case CELTIC_CROSS:
                    switch (facing) {
                        case SOUTH:
                        case NORTH:
                            return CC_NORTH_SOUTH_BB;
                        case EAST:
                        case WEST:
                            return CC_EAST_WEST_BB;
                    }
                    break;
                case OBELISK:
                case CREEPER_STATUE:
                case VILLAGER_STATUE:
                    return PL_STATUES_BB;
                case HORIZONTAL_PLATE:
                    switch (facing) {
                        case SOUTH:
                        case NORTH:
                            return HP_NORTH_SOUTH_BB;
                        case EAST:
                        case WEST:
                            return HP_EAST_WEST_BB;
                    }
                    break;
                case DOG_STATUE:
                    switch (facing) {
                        case SOUTH:
                            return DOG_SOUTH_BB;
                        case NORTH:
                            return DOG_NORTH_BB;
                        case EAST:
                            return DOG_EAST_BB;
                        case WEST:
                            return DOG_WEST_BB;
                    }
                    break;
                case CAT_STATUE:
                    switch (facing) {
                        case SOUTH:
                            return CAT_SOUTH_BB;
                        case NORTH:
                            return CAT_NORTH_BB;
                        case EAST:
                            return CAT_EAST_BB;
                        case WEST:
                            return CAT_WEST_BB;
                    }
                    break;
                case STARVED_CORPSE:
                case WITHERED_CORPSE:
                    return CORPSE_BB;
                case SWORD:
                    switch (facing) {
                        case SOUTH:
                        case NORTH:
                            return SWORD_SOUTH_NORTH_BB;
                        case EAST:
                        case WEST:
                            return SWORD_EAST_WEST_BB;
                    }
                    break;
                case HORSE_STATUE:
                    switch (facing) {
                        case SOUTH:
                        case NORTH:
                            return HS_SOUTH_NORTH_BB;
                        case EAST:
                        case WEST:
                            return HS_EAST_WEST_BB;
                    }
                    break;
            }
        }
        return new AxisAlignedBB(0, 0, 0, 1, 1, 1);
    }

    /**
     * Called when the block is attempted to be harvested
     */
    @Override
    public void onBlockHarvested(World world, BlockPos pos, IBlockState state, EntityPlayer player) {
        player.addExhaustion(0.025F);

        if (!world.isRemote && !world.restoringBlockSnapshots) {
            TileEntityGraveStone tileEntity = (TileEntityGraveStone) world.getTileEntity(pos);
            if (tileEntity != null && tileEntity.canBeLooted(player)) {
                GraveStoneHelper.spawnMob(world, pos);

                if (tileEntity.hasFlower()) {
                    tileEntity.dropFlower();
                }

                GraveStoneHelper.dropBlock(world, pos, state);
            }
        }
    }

    /**
     * This returns a complete list of items dropped from this block.
     */
    @Override
    public List<ItemStack> getDrops(IBlockAccess access, BlockPos pos, IBlockState state, int fortune) {
        List<ItemStack> ret = new ArrayList<>();
        ret.add(GraveStoneHelper.getBlockItemStack(access, pos, state));
        return ret;
    }

    @Override
    public void harvestBlock(World worldIn, EntityPlayer player, BlockPos pos, IBlockState state, @Nullable TileEntity te, @Nullable ItemStack stack) {
    }

    @Override
    public boolean isOpaqueCube(IBlockState state) {
        return false;
    }

    @Override
    public boolean isFullCube(IBlockState state) {
        return false;
    }

    @Override
    public BlockFaceShape getBlockFaceShape(IBlockAccess access, IBlockState state, BlockPos pos, EnumFacing facing) {
        return BlockFaceShape.UNDEFINED;
    }

    @Override
    public void onBlockDestroyedByPlayer(World world, BlockPos pos, IBlockState state) {
        GraveStoneHelper.spawnMob(world, pos);
    }

    @Override
    public float getExplosionResistance(Entity entity) {
        return Float.MAX_VALUE;
    }

    /**
     * Called when the block is destroyed by an explosion. Useful for allowing
     * the block to take into account tile entities, metadata, etc. when
     * exploded, before it is removed.
     */
    @Override
    public void onBlockExploded(World world, BlockPos pos, Explosion explosion) {
    }

    @Override
    public boolean onBlockActivated(World world, BlockPos pos, IBlockState state, EntityPlayer player, EnumHand hand, EnumFacing side, float hitX, float hitY, float hitZ) {
        TileEntityGraveStone te = (TileEntityGraveStone) world.getTileEntity(pos);

        if (te != null) {
            if (player.inventory.getCurrentItem() != null) {
                ItemStack item = player.inventory.getCurrentItem();
                if (item.getItem().getToolClasses(item).contains("shovel")) {
                    if (!world.isRemote) {
                        if (te.canBeLooted(player)) {
                            player.openGui(ModGraveStone.instance, GuiHandler.GRAVE_INVENTORY_GUI_ID, world, pos.getX(), pos.getY(), pos.getZ());
                            GSLogger.logInfoGrave(player.getName() + " open grave inventory at " + pos.getX() + "/" + pos.getY() + "/" + pos.getZ());
                            GraveStoneHelper.replaceGround(world, pos.down());
                        } else {
                            player.sendMessage(new TextComponentTranslation("grave.cant_be_looted").setStyle(new Style().setColor(TextFormatting.RED)));
                        }
                    }
                    return false;
                } else {
                    if (te.isMossy()) {
                        if (item.getItem() instanceof ItemShears) {
                            if (!world.isRemote) {
                                GraveInventory.dropItem(new ItemStack(Blocks.VINE, 1), world, pos);
                            }
                            te.setMossy(false);
                            return false;
                        }
                    } else {
                        if (Block.getBlockFromItem(item.getItem()) instanceof BlockVine && te.canBeMossy()) {
                            te.setMossy(true);
                            player.inventory.getCurrentItem().setCount(player.inventory.getCurrentItem().getCount() - 1);
                            return true;
                        }
                    }
                    if (te.hasFlower()) {
                        if (item.getItem() instanceof ItemShears) {
                            if (!world.isRemote) {
                                te.dropFlower();
                            }
                            te.setFlower(null);
                            return false;
                        }
                    } else {
                        if (GraveStoneHelper.FLOWERS.contains(Block.getBlockFromItem(item.getItem())) &&
                                GraveStoneHelper.canFlowerBePlaced(world, pos, item, te)) {
                            te.setFlower(new ItemStack(item.getItem(), 1, item.getItemDamage()));
                            player.inventory.getCurrentItem().setCount(player.inventory.getCurrentItem().getCount() - 1);
                            return true;
                        }
                    }
                }
            }
            if (world.isRemote) {
                String name;
                String deathText;
                String killerName;
                deathText = te.getDeathTextComponent().getDeathText();

                if (deathText.length() != 0) {
                    if (te.getDeathTextComponent().isLocalized()) {
                        name = ModGraveStone.proxy.getLocalizedEntityName(te.getDeathTextComponent().getName());
                        killerName = ModGraveStone.proxy.getLocalizedEntityName(te.getDeathTextComponent().getKillerName());

                        if (killerName.length() == 0) {
                            player.sendMessage(new TextComponentTranslation(deathText, new Object[]{name}));
                        } else {
                            player.sendMessage(new TextComponentTranslation(deathText, new Object[]{name, killerName}));
                        }
                    } else {
                        player.sendMessage(new TextComponentTranslation(deathText));
                    }

                    if (te.getAge() > 0) {
                        StringBuilder ageStr = new StringBuilder();
                        ageStr.append(ModGraveStone.proxy.getLocalizedString("item.grave.age"))
                                .append(" ")
                                .append(te.getAge())
                                .append(" ")
                                .append(ModGraveStone.proxy.getLocalizedString("item.grave.days"));
                        player.sendMessage(new TextComponentTranslation(ageStr.toString()));
                    }
                }
            }
        }

        return false;
    }

    @Override
    public TileEntity createNewTileEntity(World world, int var2) {
        return new TileEntityGraveStone(world);
    }

    @Override
    public void onBlockAdded(World world, BlockPos pos, IBlockState state) {
        super.onBlockAdded(world, pos, state);
        GraveStoneHelper.replaceGround(world, pos.down());
    }

    /**
     * ejects contained items into the world, and notifies neighbours of an
     * update, as appropriate
     */
    @Override
    public void breakBlock(World world, BlockPos pos, IBlockState state) {
        TileEntityGraveStone tileEntity = (TileEntityGraveStone) world.getTileEntity(pos);

        if (tileEntity != null) {
            tileEntity.getInventory().dropAllItems();
        }

        super.breakBlock(world, pos, state);
    }

    @Override
    public void neighborChanged(IBlockState state, World world, BlockPos pos, Block block, BlockPos fromPos) {
        if (!world.isSideSolid(pos.down(), EnumFacing.DOWN, true)) {
            TileEntityGraveStone te = (TileEntityGraveStone) world.getTileEntity(pos);
            if (te != null) {
                if (te.canBeLooted(null)) {
                    GraveStoneHelper.dropBlockWithoutInfo(te.getWorld(), pos, world.getBlockState(pos));
                    te.getWorld().setBlockToAir(pos);
                }
            }
        }
    }

    @Override
    public boolean removedByPlayer(IBlockState state, World world, BlockPos pos, EntityPlayer player, boolean willHarvest) {
        TileEntityGraveStone te = (TileEntityGraveStone) world.getTileEntity(pos);
        if (te != null && !te.canBeLooted(player)) {
            player.sendMessage(new TextComponentTranslation("grave.cant_be_looted").setStyle(new Style().setColor(TextFormatting.RED)));
            return false;
        }
        return super.removedByPlayer(state, world, pos, player, willHarvest);
    }

    @Override
    public int damageDropped(IBlockState state) {
        return 0;
    }

    @Override
    @SideOnly(Side.CLIENT)
    public void getSubBlocks(CreativeTabs tabs, NonNullList<ItemStack> list) {
        for (int i = 0; i < EnumGraves.values().length - 1; i++) {
            ItemStack stack = new ItemStack(this, 1, i);
            NBTTagCompound nbt = new NBTTagCompound();
            nbt.setBoolean("Purified", false);

            stack.setTagCompound(nbt);
            list.add(stack);
        }

        // custom swords
        for (Item sword : GraveGenerationHelper.swordsList) {
            list.add(GraveStoneHelper.getSwordAsGrave(Item.getItemFromBlock(this), new ItemStack(sword, 1)));
        }
        for (Item sword : GraveGenerationHelper.swordsList) {
            try {
                ItemStack swordStack = new ItemStack(sword, 1);
                EnchantmentHelper.addRandomEnchantment(new Random(), swordStack, 5, true);

                ItemStack graveStoneStack = GraveStoneHelper.getSwordAsGrave(Item.getItemFromBlock(this), swordStack);

                list.add(graveStoneStack);
            } catch (IllegalArgumentException exception) {
                GSLogger.logError("Can't create enchanted sword gravestone");
                exception.printStackTrace();
            }
        }
    }

    @Override
    public ItemStack getPickBlock(IBlockState state, RayTraceResult target, World world, BlockPos pos, EntityPlayer player) {
        ItemStack itemStack = new ItemStack(Item.getItemFromBlock(this), 1);
        TileEntityGraveStone tileEntity = (TileEntityGraveStone) world.getTileEntity(pos);

        if (tileEntity != null) {
            if (itemStack != null) {
                NBTTagCompound nbt = new NBTTagCompound();
                itemStack.setItemDamage(tileEntity.getGraveTypeNum());
                nbt.setBoolean("Mossy", tileEntity.isMossy());

                itemStack.setTagCompound(nbt);
                if (tileEntity.isSwordGrave()) {
                    GraveStoneHelper.addSwordInfo(nbt, tileEntity.getSword());
                }
            }
        }
        return itemStack;
    }

    /**
     * A randomly called display update to be able to add particles or other
     * items for display
     */
    @Override
    public void updateTick(World world, BlockPos pos, IBlockState state, Random random) {
        if (Config.removeEmptyGraves) {
            if (!world.isRemote) {
                TileEntityGraveStone tileEntity = (TileEntityGraveStone) world.getTileEntity(pos);
                if (tileEntity != null) {
                    if (!tileEntity.isSwordGrave() && tileEntity.isEmpty()) {
                        if (Config.showGravesRemovingMessages) {
                            GSLogger.logInfoGrave("Remove empty grave at " + pos.getX() + "/" + pos.getY() + "/" + pos.getZ());
                        }

                        world.removeTileEntity(pos);
                        world.setBlockToAir(pos);
                    }
                }
            }
        }
    }

    @Override
    public IBlockState getStateFromMeta(int meta) {
        EnumFacing enumfacing = EnumFacing.getFront(meta);

        if (enumfacing.getAxis() == EnumFacing.Axis.Y) {
            enumfacing = EnumFacing.NORTH;
        }

        return this.getDefaultState().withProperty(FACING, enumfacing);
    }

    @Override
    public int getMetaFromState(IBlockState state) {
        return state.getValue(FACING).getIndex();
    }

    @Override
    protected BlockStateContainer createBlockState() {
        return new BlockStateContainer(this, new IProperty[]{FACING});
    }

    @Override
    public boolean canEntityDestroy(IBlockState state, IBlockAccess world, BlockPos pos, Entity entity) {
        return false;
    }
} public enum EnumGraves implements IBlockEnum {

    // VERTICAL PLATES
    WOODEN_VERTICAL_PLATE(Resources.GRAVE_WOODEN_VERTICAL_PLATE, EnumGraveType.VERTICAL_PLATE, EnumGraveMaterial.WOOD),
    SANDSTONE_VERTICAL_PLATE(Resources.GRAVE_SANDSTONE_VERTICAL_PLATE, EnumGraveType.VERTICAL_PLATE, EnumGraveMaterial.SANDSTONE),
    RED_SANDSTONE_VERTICAL_PLATE(Resources.GRAVE_RED_SANDSTONE_VERTICAL_PLATE, EnumGraveType.VERTICAL_PLATE, EnumGraveMaterial.RED_SANDSTONE),
    STONE_VERTICAL_PLATE(Resources.GRAVE_STONE_VERTICAL_PLATE, EnumGraveType.VERTICAL_PLATE, EnumGraveMaterial.STONE),
    DIORITE_VERTICAL_PLATE(Resources.GRAVE_DIORITE_VERTICAL_PLATE, EnumGraveType.VERTICAL_PLATE, EnumGraveMaterial.DIORITE),
    ANDESITE_VERTICAL_PLATE(Resources.GRAVE_ANDESITE_VERTICAL_PLATE, EnumGraveType.VERTICAL_PLATE, EnumGraveMaterial.ANDESITE),
    GRANITE_VERTICAL_PLATE(Resources.GRAVE_GRANITE_VERTICAL_PLATE, EnumGraveType.VERTICAL_PLATE, EnumGraveMaterial.GRANITE),
    IRON_VERTICAL_PLATE(Resources.GRAVE_IRON_VERTICAL_PLATE, EnumGraveType.VERTICAL_PLATE, EnumGraveMaterial.IRON),
    GOLDEN_VERTICAL_PLATE(Resources.GRAVE_GOLDEN_VERTICAL_PLATE, EnumGraveType.VERTICAL_PLATE, EnumGraveMaterial.GOLD),
    DIAMOND_VERTICAL_PLATE(Resources.GRAVE_DIAMOND_VERTICAL_PLATE, EnumGraveType.VERTICAL_PLATE, EnumGraveMaterial.DIAMOND),
    EMERALD_VERTICAL_PLATE(Resources.GRAVE_EMERALD_VERTICAL_PLATE, EnumGraveType.VERTICAL_PLATE, EnumGraveMaterial.EMERALD),
    LAPIS_VERTICAL_PLATE(Resources.GRAVE_LAPIS_VERTICAL_PLATE, EnumGraveType.VERTICAL_PLATE, EnumGraveMaterial.LAPIS),
    REDSTONE_VERTICAL_PLATE(Resources.GRAVE_REDSTONE_VERTICAL_PLATE, EnumGraveType.VERTICAL_PLATE, EnumGraveMaterial.REDSTONE),
    OBSIDIAN_VERTICAL_PLATE(Resources.GRAVE_OBSIDIAN_VERTICAL_PLATE, EnumGraveType.VERTICAL_PLATE, EnumGraveMaterial.OBSIDIAN),
    QUARTZ_VERTICAL_PLATE(Resources.GRAVE_QUARTZ_VERTICAL_PLATE, EnumGraveType.VERTICAL_PLATE, EnumGraveMaterial.QUARTZ),
    PRIZMARINE_VERTICAL_PLATE(Resources.GRAVE_PRIZMARINE_VERTICAL_PLATE, EnumGraveType.VERTICAL_PLATE, EnumGraveMaterial.PRIZMARINE),
    ICE_VERTICAL_PLATE(Resources.GRAVE_ICE_VERTICAL_PLATE, EnumGraveType.VERTICAL_PLATE, EnumGraveMaterial.ICE),
    // CROSSES
    WOODEN_CROSS(Resources.GRAVE_WOODEN_CROSS, EnumGraveType.CROSS, EnumGraveMaterial.WOOD),
    SANDSTONE_CROSS(Resources.GRAVE_SANDSTONE_CROSS, EnumGraveType.CROSS, EnumGraveMaterial.SANDSTONE),
    RED_SANDSTONE_CROSS(Resources.GRAVE_RED_SANDSTONE_CROSS, EnumGraveType.CROSS, EnumGraveMaterial.RED_SANDSTONE),
    STONE_CROSS(Resources.GRAVE_STONE_CROSS, EnumGraveType.CROSS, EnumGraveMaterial.STONE),
    DIORITE_CROSS(Resources.GRAVE_DIORITE_CROSS, EnumGraveType.CROSS, EnumGraveMaterial.DIORITE),
    ANDESITE_CROSS(Resources.GRAVE_ANDESITE_CROSS, EnumGraveType.CROSS, EnumGraveMaterial.ANDESITE),
    GRANITE_CROSS(Resources.GRAVE_GRANITE_CROSS, EnumGraveType.CROSS, EnumGraveMaterial.GRANITE),
    IRON_CROSS(Resources.GRAVE_IRON_CROSS, EnumGraveType.CROSS, EnumGraveMaterial.IRON),
    GOLDEN_CROSS(Resources.GRAVE_GOLDEN_CROSS, EnumGraveType.CROSS, EnumGraveMaterial.GOLD),
    DIAMOND_CROSS(Resources.GRAVE_DIAMOND_CROSS, EnumGraveType.CROSS, EnumGraveMaterial.DIAMOND),
    EMERALD_CROSS(Resources.GRAVE_EMERALD_CROSS, EnumGraveType.CROSS, EnumGraveMaterial.EMERALD),
    LAPIS_CROSS(Resources.GRAVE_LAPIS_CROSS, EnumGraveType.CROSS, EnumGraveMaterial.LAPIS),
    REDSTONE_CROSS(Resources.GRAVE_REDSTONE_CROSS, EnumGraveType.CROSS, EnumGraveMaterial.REDSTONE),
    OBSIDIAN_CROSS(Resources.GRAVE_OBSIDIAN_CROSS, EnumGraveType.CROSS, EnumGraveMaterial.OBSIDIAN),
    QUARTZ_CROSS(Resources.GRAVE_QUARTZ_CROSS, EnumGraveType.CROSS, EnumGraveMaterial.QUARTZ),
    PRIZMARINE_CROSS(Resources.GRAVE_PRIZMARINE_CROSS, EnumGraveType.CROSS, EnumGraveMaterial.PRIZMARINE),
    ICE_CROSS(Resources.GRAVE_ICE_CROSS, EnumGraveType.CROSS, EnumGraveMaterial.ICE),
    // OBELISKS
    WOODEN_OBELISK(Resources.WOODEN_OBELISK, EnumGraveType.OBELISK, EnumGraveMaterial.WOOD),
    SANDSTONE_OBELISK(Resources.SANDSTONE_OBELISK, EnumGraveType.OBELISK, EnumGraveMaterial.SANDSTONE),
    RED_SANDSTONE_OBELISK(Resources.RED_SANDSTONE_OBELISK, EnumGraveType.OBELISK, EnumGraveMaterial.RED_SANDSTONE),
    STONE_OBELISK(Resources.STONE_OBELISK, EnumGraveType.OBELISK, EnumGraveMaterial.STONE),
    DIORITE_OBELISK(Resources.DIORITE_OBELISK, EnumGraveType.OBELISK, EnumGraveMaterial.DIORITE),
    ANDESITE_OBELISK(Resources.ANDESITE_OBELISK, EnumGraveType.OBELISK, EnumGraveMaterial.ANDESITE),
    GRANITE_OBELISK(Resources.GRANITE_OBELISK, EnumGraveType.OBELISK, EnumGraveMaterial.GRANITE),
    IRON_OBELISK(Resources.IRON_OBELISK, EnumGraveType.OBELISK, EnumGraveMaterial.IRON),
    GOLDEN_OBELISK(Resources.GOLDEN_OBELISK, EnumGraveType.OBELISK, EnumGraveMaterial.GOLD),
    DIAMOND_OBELISK(Resources.DIAMOND_OBELISK, EnumGraveType.OBELISK, EnumGraveMaterial.DIAMOND),
    EMERALD_OBELISK(Resources.EMERALD_OBELISK, EnumGraveType.OBELISK, EnumGraveMaterial.EMERALD),
    LAPIS_OBELISK(Resources.LAPIS_OBELISK, EnumGraveType.OBELISK, EnumGraveMaterial.LAPIS),
    REDSTONE_OBELISK(Resources.REDSTONE_OBELISK, EnumGraveType.OBELISK, EnumGraveMaterial.REDSTONE),
    OBSIDIAN_OBELISK(Resources.OBSIDIAN_OBELISK, EnumGraveType.OBELISK, EnumGraveMaterial.OBSIDIAN),
    QUARTZ_OBELISK(Resources.QUARTZ_OBELISK, EnumGraveType.OBELISK, EnumGraveMaterial.QUARTZ),
    PRIZMARINE_OBELISK(Resources.PRIZMARINE_OBELISK, EnumGraveType.OBELISK, EnumGraveMaterial.PRIZMARINE),
    ICE_OBELISK(Resources.ICE_OBELISK, EnumGraveType.OBELISK, EnumGraveMaterial.ICE),
    // CELTIC CROSSES
    WOODEN_CELTIC_CROSS(Resources.WOODEN_CELTIC_CROSS, EnumGraveType.CELTIC_CROSS, EnumGraveMaterial.WOOD),
    SANDSTONE_CELTIC_CROSS(Resources.SANDSTONE_CELTIC_CROSS, EnumGraveType.CELTIC_CROSS, EnumGraveMaterial.SANDSTONE),
    RED_SANDSTONE_CELTIC_CROSS(Resources.RED_SANDSTONE_CELTIC_CROSS, EnumGraveType.CELTIC_CROSS, EnumGraveMaterial.RED_SANDSTONE),
    STONE_CELTIC_CROSS(Resources.STONE_CELTIC_CROSS, EnumGraveType.CELTIC_CROSS, EnumGraveMaterial.STONE),
    DIORITE_CELTIC_CROSS(Resources.DIORITE_CELTIC_CROSS, EnumGraveType.CELTIC_CROSS, EnumGraveMaterial.DIORITE),
    ANDESITE_CELTIC_CROSS(Resources.ANDESITE_CELTIC_CROSS, EnumGraveType.CELTIC_CROSS, EnumGraveMaterial.ANDESITE),
    GRANITE_CELTIC_CROSS(Resources.GRANITE_CELTIC_CROSS, EnumGraveType.CELTIC_CROSS, EnumGraveMaterial.GRANITE),
    IRON_CELTIC_CROSS(Resources.IRON_CELTIC_CROSS, EnumGraveType.CELTIC_CROSS, EnumGraveMaterial.IRON),
    GOLDEN_CELTIC_CROSS(Resources.GOLDEN_CELTIC_CROSS, EnumGraveType.CELTIC_CROSS, EnumGraveMaterial.GOLD),
    DIAMOND_CELTIC_CROSS(Resources.DIAMOND_CELTIC_CROSS, EnumGraveType.CELTIC_CROSS, EnumGraveMaterial.DIAMOND),
    EMERALD_CELTIC_CROSS(Resources.EMERALD_CELTIC_CROSS, EnumGraveType.CELTIC_CROSS, EnumGraveMaterial.EMERALD),
    LAPIS_CELTIC_CROSS(Resources.LAPIS_CELTIC_CROSS, EnumGraveType.CELTIC_CROSS, EnumGraveMaterial.LAPIS),
    REDSTONE_CELTIC_CROSS(Resources.REDSTONE_CELTIC_CROSS, EnumGraveType.CELTIC_CROSS, EnumGraveMaterial.REDSTONE),
    OBSIDIAN_CELTIC_CROSS(Resources.OBSIDIAN_CELTIC_CROSS, EnumGraveType.CELTIC_CROSS, EnumGraveMaterial.OBSIDIAN),
    QUARTZ_CELTIC_CROSS(Resources.QUARTZ_CELTIC_CROSS, EnumGraveType.CELTIC_CROSS, EnumGraveMaterial.QUARTZ),
    PRIZMARINE_CELTIC_CROSS(Resources.PRIZMARINE_CELTIC_CROSS, EnumGraveType.CELTIC_CROSS, EnumGraveMaterial.PRIZMARINE),
    ICE_CELTIC_CROSS(Resources.ICE_CELTIC_CROSS, EnumGraveType.CELTIC_CROSS, EnumGraveMaterial.ICE),
    // HORISONTAL PLATES
    WOODEN_HORIZONTAL_PLATE(Resources.GRAVE_WOODEN_HORISONTAL_PLATE, EnumGraveType.HORIZONTAL_PLATE, EnumGraveMaterial.WOOD),
    SANDSTONE_HORIZONTAL_PLATE(Resources.GRAVE_SANDSTONE_HORISONTAL_PLATE, EnumGraveType.HORIZONTAL_PLATE, EnumGraveMaterial.SANDSTONE),
    RED_SANDSTONE_HORIZONTAL_PLATE(Resources.GRAVE_RED_SANDSTONE_HORISONTAL_PLATE, EnumGraveType.HORIZONTAL_PLATE, EnumGraveMaterial.RED_SANDSTONE),
    STONE_HORIZONTAL_PLATE(Resources.GRAVE_STONE_HORISONTAL_PLATE, EnumGraveType.HORIZONTAL_PLATE, EnumGraveMaterial.STONE),
    DIORITE_HORIZONTAL_PLATE(Resources.GRAVE_DIORITE_HORISONTAL_PLATE, EnumGraveType.HORIZONTAL_PLATE, EnumGraveMaterial.DIORITE),
    ANDESITE_HORIZONTAL_PLATE(Resources.GRAVE_ANDESITE_HORISONTAL_PLATE, EnumGraveType.HORIZONTAL_PLATE, EnumGraveMaterial.ANDESITE),
    GRANITE_HORIZONTAL_PLATE(Resources.GRAVE_GRANITE_HORISONTAL_PLATE, EnumGraveType.HORIZONTAL_PLATE, EnumGraveMaterial.GRANITE),
    IRON_HORIZONTAL_PLATE(Resources.GRAVE_IRON_HORISONTAL_PLATE, EnumGraveType.HORIZONTAL_PLATE, EnumGraveMaterial.IRON),
    GOLDEN_HORIZONTAL_PLATE(Resources.GRAVE_GOLDEN_HORISONTAL_PLATE, EnumGraveType.HORIZONTAL_PLATE, EnumGraveMaterial.GOLD),
    DIAMOND_HORIZONTAL_PLATE(Resources.GRAVE_DIAMOND_HORISONTAL_PLATE, EnumGraveType.HORIZONTAL_PLATE, EnumGraveMaterial.DIAMOND),
    EMERALD_HORIZONTAL_PLATE(Resources.GRAVE_EMERALD_HORISONTAL_PLATE, EnumGraveType.HORIZONTAL_PLATE, EnumGraveMaterial.EMERALD),
    LAPIS_HORIZONTAL_PLATE(Resources.GRAVE_LAPIS_HORISONTAL_PLATE, EnumGraveType.HORIZONTAL_PLATE, EnumGraveMaterial.LAPIS),
    REDSTONE_HORIZONTAL_PLATE(Resources.GRAVE_REDSTONE_HORISONTAL_PLATE, EnumGraveType.HORIZONTAL_PLATE, EnumGraveMaterial.REDSTONE),
    OBSIDIAN_HORIZONTAL_PLATE(Resources.GRAVE_OBSIDIAN_HORISONTAL_PLATE, EnumGraveType.HORIZONTAL_PLATE, EnumGraveMaterial.OBSIDIAN),
    QUARTZ_HORIZONTAL_PLATE(Resources.GRAVE_QUARTZ_HORISONTAL_PLATE, EnumGraveType.HORIZONTAL_PLATE, EnumGraveMaterial.QUARTZ),
    PRIZMARINE_HORIZONTAL_PLATE(Resources.GRAVE_PRIZMARINE_HORISONTAL_PLATE, EnumGraveType.HORIZONTAL_PLATE, EnumGraveMaterial.PRIZMARINE),
    ICE_HORIZONTAL_PLATE(Resources.GRAVE_ICE_HORISONTAL_PLATE, EnumGraveType.HORIZONTAL_PLATE, EnumGraveMaterial.ICE),
    // VILLAGERS STATUES
    WOODEN_VILLAGER_STATUE(Resources.WOODEN_VILLAGER_STATUE, EnumGraveType.VILLAGER_STATUE, EnumGraveMaterial.WOOD),
    SANDSTONE_VILLAGER_STATUE(Resources.SANDSTONE_VILLAGER_STATUE, EnumGraveType.VILLAGER_STATUE, EnumGraveMaterial.SANDSTONE),
    RED_SANDSTONE_VILLAGER_STATUE(Resources.RED_SANDSTONE_VILLAGER_STATUE, EnumGraveType.VILLAGER_STATUE, EnumGraveMaterial.RED_SANDSTONE),
    STONE_VILLAGER_STATUE(Resources.STONE_VILLAGER_STATUE, EnumGraveType.VILLAGER_STATUE, EnumGraveMaterial.STONE),
    DIORITE_VILLAGER_STATUE(Resources.DIORITE_VILLAGER_STATUE, EnumGraveType.VILLAGER_STATUE, EnumGraveMaterial.DIORITE),
    ANDESITE_VILLAGER_STATUE(Resources.ANDESITE_VILLAGER_STATUE, EnumGraveType.VILLAGER_STATUE, EnumGraveMaterial.ANDESITE),
    GRANITE_VILLAGER_STATUE(Resources.GRANITE_VILLAGER_STATUE, EnumGraveType.VILLAGER_STATUE, EnumGraveMaterial.GRANITE),
    IRON_VILLAGER_STATUE(Resources.IRON_VILLAGER_STATUE, EnumGraveType.VILLAGER_STATUE, EnumGraveMaterial.IRON),
    GOLDEN_VILLAGER_STATUE(Resources.GOLDEN_VILLAGER_STATUE, EnumGraveType.VILLAGER_STATUE, EnumGraveMaterial.GOLD),
    DIAMOND_VILLAGER_STATUE(Resources.DIAMOND_VILLAGER_STATUE, EnumGraveType.VILLAGER_STATUE, EnumGraveMaterial.DIAMOND),
    EMERALD_VILLAGER_STATUE(Resources.EMERALD_VILLAGER_STATUE, EnumGraveType.VILLAGER_STATUE, EnumGraveMaterial.EMERALD),
    LAPIS_VILLAGER_STATUE(Resources.LAPIS_VILLAGER_STATUE, EnumGraveType.VILLAGER_STATUE, EnumGraveMaterial.LAPIS),
    REDSTONE_VILLAGER_STATUE(Resources.REDSTONE_VILLAGER_STATUE, EnumGraveType.VILLAGER_STATUE, EnumGraveMaterial.REDSTONE),
    OBSIDIAN_VILLAGER_STATUE(Resources.OBSIDIAN_VILLAGER_STATUE, EnumGraveType.VILLAGER_STATUE, EnumGraveMaterial.OBSIDIAN),
    QUARTZ_VILLAGER_STATUE(Resources.QUARTZ_VILLAGER_STATUE, EnumGraveType.VILLAGER_STATUE, EnumGraveMaterial.QUARTZ),
    PRIZMARINE_VILLAGER_STATUE(Resources.PRIZMARINE_VILLAGER_STATUE, EnumGraveType.VILLAGER_STATUE, EnumGraveMaterial.PRIZMARINE),
    ICE_VILLAGER_STATUE(Resources.ICE_VILLAGER_STATUE, EnumGraveType.VILLAGER_STATUE, EnumGraveMaterial.ICE),
    // DOGS GRAVES
    WOODEN_DOG_STATUE(Resources.WOODEN_DOG_STATUE, EnumGraveType.DOG_STATUE, EnumGraveMaterial.WOOD),
    SANDSTONE_DOG_STATUE(Resources.SANDSTONE_DOG_STATUE, EnumGraveType.DOG_STATUE, EnumGraveMaterial.SANDSTONE),
    RED_SANDSTONE_DOG_STATUE(Resources.RED_SANDSTONE_DOG_STATUE, EnumGraveType.DOG_STATUE, EnumGraveMaterial.RED_SANDSTONE),
    STONE_DOG_STATUE(Resources.STONE_DOG_STATUE, EnumGraveType.DOG_STATUE, EnumGraveMaterial.STONE),
    DIORITE_DOG_STATUE(Resources.DIORITE_DOG_STATUE, EnumGraveType.DOG_STATUE, EnumGraveMaterial.DIORITE),
    ANDESITE_DOG_STATUE(Resources.ANDESITE_DOG_STATUE, EnumGraveType.DOG_STATUE, EnumGraveMaterial.ANDESITE),
    GRANITE_DOG_STATUE(Resources.GRANITE_DOG_STATUE, EnumGraveType.DOG_STATUE, EnumGraveMaterial.GRANITE),
    IRON_DOG_STATUE(Resources.IRON_DOG_STATUE, EnumGraveType.DOG_STATUE, EnumGraveMaterial.IRON),
    GOLDEN_DOG_STATUE(Resources.GOLDEN_DOG_STATUE, EnumGraveType.DOG_STATUE, EnumGraveMaterial.GOLD),
    DIAMOND_DOG_STATUE(Resources.DIAMOND_DOG_STATUE, EnumGraveType.DOG_STATUE, EnumGraveMaterial.DIAMOND),
    EMERALD_DOG_STATUE(Resources.EMERALD_DOG_STATUE, EnumGraveType.DOG_STATUE, EnumGraveMaterial.EMERALD),
    LAPIS_DOG_STATUE(Resources.LAPIS_DOG_STATUE, EnumGraveType.DOG_STATUE, EnumGraveMaterial.LAPIS),
    REDSTONE_DOG_STATUE(Resources.REDSTONE_DOG_STATUE, EnumGraveType.DOG_STATUE, EnumGraveMaterial.REDSTONE),
    OBSIDIAN_DOG_STATUE(Resources.OBSIDIAN_DOG_STATUE, EnumGraveType.DOG_STATUE, EnumGraveMaterial.OBSIDIAN),
    QUARTZ_DOG_STATUE(Resources.QUARTZ_DOG_STATUE, EnumGraveType.DOG_STATUE, EnumGraveMaterial.QUARTZ),
    PRIZMARINE_DOG_STATUE(Resources.PRIZMARINE_DOG_STATUE, EnumGraveType.DOG_STATUE, EnumGraveMaterial.PRIZMARINE),
    ICE_DOG_STATUE(Resources.ICE_DOG_STATUE, EnumGraveType.DOG_STATUE, EnumGraveMaterial.ICE),
    // CATS GRAVES
    WOODEN_CAT_STATUE(Resources.WOODEN_CAT_STATUE, EnumGraveType.CAT_STATUE, EnumGraveMaterial.WOOD),
    SANDSTONE_CAT_STATUE(Resources.SANDSTONE_CAT_STATUE, EnumGraveType.CAT_STATUE, EnumGraveMaterial.SANDSTONE),
    RED_SANDSTONE_CAT_STATUE(Resources.RED_SANDSTONE_CAT_STATUE, EnumGraveType.CAT_STATUE, EnumGraveMaterial.RED_SANDSTONE),
    STONE_CAT_STATUE(Resources.STONE_CAT_STATUE, EnumGraveType.CAT_STATUE, EnumGraveMaterial.STONE),
    DIORITE_CAT_STATUE(Resources.DIORITE_CAT_STATUE, EnumGraveType.CAT_STATUE, EnumGraveMaterial.DIORITE),
    ANDESITE_CAT_STATUE(Resources.ANDESITE_CAT_STATUE, EnumGraveType.CAT_STATUE, EnumGraveMaterial.ANDESITE),
    GRANITE_CAT_STATUE(Resources.GRANITE_CAT_STATUE, EnumGraveType.CAT_STATUE, EnumGraveMaterial.GRANITE),
    IRON_CAT_STATUE(Resources.IRON_CAT_STATUE, EnumGraveType.CAT_STATUE, EnumGraveMaterial.IRON),
    GOLDEN_CAT_STATUE(Resources.GOLDEN_CAT_STATUE, EnumGraveType.CAT_STATUE, EnumGraveMaterial.GOLD),
    DIAMOND_CAT_STATUE(Resources.DIAMOND_CAT_STATUE, EnumGraveType.CAT_STATUE, EnumGraveMaterial.DIAMOND),
    EMERALD_CAT_STATUE(Resources.EMERALD_CAT_STATUE, EnumGraveType.CAT_STATUE, EnumGraveMaterial.EMERALD),
    LAPIS_CAT_STATUE(Resources.LAPIS_CAT_STATUE, EnumGraveType.CAT_STATUE, EnumGraveMaterial.LAPIS),
    REDSTONE_CAT_STATUE(Resources.REDSTONE_CAT_STATUE, EnumGraveType.CAT_STATUE, EnumGraveMaterial.REDSTONE),
    OBSIDIAN_CAT_STATUE(Resources.OBSIDIAN_CAT_STATUE, EnumGraveType.CAT_STATUE, EnumGraveMaterial.OBSIDIAN),
    QUARTZ_CAT_STATUE(Resources.QUARTZ_CAT_STATUE, EnumGraveType.CAT_STATUE, EnumGraveMaterial.QUARTZ),
    PRIZMARINE_CAT_STATUE(Resources.PRIZMARINE_CAT_STATUE, EnumGraveType.CAT_STATUE, EnumGraveMaterial.PRIZMARINE),
    ICE_CAT_STATUE(Resources.ICE_CAT_STATUE, EnumGraveType.CAT_STATUE, EnumGraveMaterial.ICE),
    // HORSES GRAVES
    WOODEN_HORSE_STATUE(Resources.GRAVE_WOODEN_HORSE_STATUE, EnumGraveType.HORSE_STATUE, EnumGraveMaterial.WOOD),
    SANDSTONE_HORSE_STATUE(Resources.GRAVE_SANDSTONE_HORSE_STATUE, EnumGraveType.HORSE_STATUE, EnumGraveMaterial.SANDSTONE),
    RED_SANDSTONE_HORSE_STATUE(Resources.GRAVE_RED_SANDSTONE_HORSE_STATUE, EnumGraveType.HORSE_STATUE, EnumGraveMaterial.RED_SANDSTONE),
    STONE_HORSE_STATUE(Resources.GRAVE_STONE_HORSE_STATUE, EnumGraveType.HORSE_STATUE, EnumGraveMaterial.STONE),
    DIORITE_HORSE_STATUE(Resources.GRAVE_DIORITE_HORSE_STATUE, EnumGraveType.HORSE_STATUE, EnumGraveMaterial.DIORITE),
    ANDESITE_HORSE_STATUE(Resources.GRAVE_ANDESITE_HORSE_STATUE, EnumGraveType.HORSE_STATUE, EnumGraveMaterial.ANDESITE),
    GRANITE_HORSE_STATUE(Resources.GRAVE_GRANITE_HORSE_STATUE, EnumGraveType.HORSE_STATUE, EnumGraveMaterial.GRANITE),
    IRON_HORSE_STATUE(Resources.GRAVE_IRON_HORSE_STATUE, EnumGraveType.HORSE_STATUE, EnumGraveMaterial.IRON),
    GOLDEN_HORSE_STATUE(Resources.GRAVE_GOLDEN_HORSE_STATUE, EnumGraveType.HORSE_STATUE, EnumGraveMaterial.GOLD),
    DIAMOND_HORSE_STATUE(Resources.GRAVE_DIAMOND_HORSE_STATUE, EnumGraveType.HORSE_STATUE, EnumGraveMaterial.DIAMOND),
    EMERALD_HORSE_STATUE(Resources.GRAVE_EMERALD_HORSE_STATUE, EnumGraveType.HORSE_STATUE, EnumGraveMaterial.EMERALD),
    LAPIS_HORSE_STATUE(Resources.GRAVE_LAPIS_HORSE_STATUE, EnumGraveType.HORSE_STATUE, EnumGraveMaterial.LAPIS),
    REDSTONE_HORSE_STATUE(Resources.GRAVE_REDSTONE_HORSE_STATUE, EnumGraveType.HORSE_STATUE, EnumGraveMaterial.REDSTONE),
    OBSIDIAN_HORSE_STATUE(Resources.GRAVE_OBSIDIAN_HORSE_STATUE, EnumGraveType.HORSE_STATUE, EnumGraveMaterial.OBSIDIAN),
    QUARTZ_HORSE_STATUE(Resources.GRAVE_QUARTZ_HORSE_STATUE, EnumGraveType.HORSE_STATUE, EnumGraveMaterial.QUARTZ),
    PRIZMARINE_HORSE_STATUE(Resources.GRAVE_PRIZMARINE_HORSE_STATUE, EnumGraveType.HORSE_STATUE, EnumGraveMaterial.PRIZMARINE),
    ICE_HORSE_STATUE(Resources.GRAVE_ICE_HORSE_STATUE, EnumGraveType.HORSE_STATUE, EnumGraveMaterial.ICE),
    // CREEPER STATUES
    WOODEN_CREEPER_STATUE(Resources.WOODEN_CREEPER_STATUE, EnumGraveType.CREEPER_STATUE, EnumGraveMaterial.WOOD),
    SANDSTONE_CREEPER_STATUE(Resources.SANDSTONE_CREEPER_STATUE, EnumGraveType.CREEPER_STATUE, EnumGraveMaterial.SANDSTONE),
    RED_SANDSTONE_CREEPER_STATUE(Resources.RED_SANDSTONE_CREEPER_STATUE, EnumGraveType.CREEPER_STATUE, EnumGraveMaterial.RED_SANDSTONE),
    STONE_CREEPER_STATUE(Resources.STONE_CREEPER_STATUE, EnumGraveType.CREEPER_STATUE, EnumGraveMaterial.STONE),
    DIORITE_CREEPER_STATUE(Resources.DIORITE_CREEPER_STATUE, EnumGraveType.CREEPER_STATUE, EnumGraveMaterial.DIORITE),
    ANDESITE_CREEPER_STATUE(Resources.ANDESITE_CREEPER_STATUE, EnumGraveType.CREEPER_STATUE, EnumGraveMaterial.ANDESITE),
    GRANITE_CREEPER_STATUE(Resources.GRANITE_CREEPER_STATUE, EnumGraveType.CREEPER_STATUE, EnumGraveMaterial.GRANITE),
    IRON_CREEPER_STATUE(Resources.IRON_CREEPER_STATUE, EnumGraveType.CREEPER_STATUE, EnumGraveMaterial.IRON),
    GOLDEN_CREEPER_STATUE(Resources.GOLDEN_CREEPER_STATUE, EnumGraveType.CREEPER_STATUE, EnumGraveMaterial.GOLD),
    DIAMOND_CREEPER_STATUE(Resources.DIAMOND_CREEPER_STATUE, EnumGraveType.CREEPER_STATUE, EnumGraveMaterial.DIAMOND),
    EMERALD_CREEPER_STATUE(Resources.EMERALD_CREEPER_STATUE, EnumGraveType.CREEPER_STATUE, EnumGraveMaterial.EMERALD),
    LAPIS_CREEPER_STATUE(Resources.LAPIS_CREEPER_STATUE, EnumGraveType.CREEPER_STATUE, EnumGraveMaterial.LAPIS),
    REDSTONE_CREEPER_STATUE(Resources.REDSTONE_CREEPER_STATUE, EnumGraveType.CREEPER_STATUE, EnumGraveMaterial.REDSTONE),
    OBSIDIAN_CREEPER_STATUE(Resources.OBSIDIAN_CREEPER_STATUE, EnumGraveType.CREEPER_STATUE, EnumGraveMaterial.OBSIDIAN),
    QUARTZ_CREEPER_STATUE(Resources.QUARTZ_CREEPER_STATUE, EnumGraveType.CREEPER_STATUE, EnumGraveMaterial.QUARTZ),
    PRIZMARINE_CREEPER_STATUE(Resources.PRIZMARINE_CREEPER_STATUE, EnumGraveType.CREEPER_STATUE, EnumGraveMaterial.PRIZMARINE),
    ICE_CREEPER_STATUE(Resources.ICE_CREEPER_STATUE, EnumGraveType.CREEPER_STATUE, EnumGraveMaterial.ICE),
    // CORPSES
    STARVED_CORPSE(Resources.SKELETON, EnumGraveType.STARVED_CORPSE, EnumGraveMaterial.OTHER),
    WITHERED_CORPSE(Resources.WITHER_SKELETON, EnumGraveType.WITHERED_CORPSE, EnumGraveMaterial.OTHER),
    // SWORD
    SWORD(null, EnumGraveType.SWORD, EnumGraveMaterial.OTHER);


    private String name;
    private ResourceLocation texture;
    private EnumGraveType graveType;
    private EnumGraveMaterial material;

    private EnumGraves(ResourceLocation texture, EnumGraveType graveType, EnumGraveMaterial material) {
        this.name = getName(graveType);
        this.texture = texture;
        this.graveType = graveType;
        this.material = material;
    }

    @Override
    public String getUnLocalizedName() {
        return this.name;
    }

    public ResourceLocation getTexture() {
        return this.texture;
    }

    public EnumGraveType getGraveType() {
        return graveType;
    }

    public EnumGraveMaterial getMaterial() {
        return material;
    }

    /**
     * Returns the grave type with the specified ID, or VERTICAL_PLATE if none found.
     *
     * @param id Grave Id
     */
    public static EnumGraves getById(int id) {
        if (id < values().length) {
            return values()[id];
        }
        return STONE_VERTICAL_PLATE;
    }

    public static EnumGraves getByTypeAndMaterial(EnumGraveType graveType, EnumGraveMaterial material) {
        for (EnumGraves grave : EnumGraves.values()) {
            if (grave.getGraveType().equals(graveType) && grave.getMaterial().equals(material)) {
                return grave;
            }
        }
        return STONE_VERTICAL_PLATE;
    }

    private static String getName(EnumGraveType graveType) {
        switch (graveType) {
            case VERTICAL_PLATE:
            default:
                return "block.gravestone.gravestone";
            case CROSS:
                return "block.gravestone.cross";
            case OBELISK:
                return "block.gravestone.obelisk";
            case CELTIC_CROSS:
                return "block.gravestone.celtic_cross";
            case HORIZONTAL_PLATE:
                return "block.gravestone.plate";
            case VILLAGER_STATUE:
                return "block.gravestone.villager_statue";
            case DOG_STATUE:
                return "block.gravestone.dog_statue";
            case CAT_STATUE:
                return "block.gravestone.cat_statue";
            case HORSE_STATUE:
                return "block.gravestone.horse_statue";
            case CREEPER_STATUE:
                return "block.gravestone.creeper_statue";
            case STARVED_CORPSE:
                return "block.gravestone.starved_corpse";
            case WITHERED_CORPSE:
                return "block.gravestone.withered_corpse";
            case SWORD:
                return "block.gravestone.sword";
        }
    }
} public class Config {

    private static Configuration config;
    private static Config instance;
    private static String path;
    // CATEGORIES
    public static final String CATEGORY_COMPATIBILITY = "compatibility";
    public static final String CATEGORY_GRAVES = "graves";

    private Config(String path, File configFile) {
        this.config = new Configuration(configFile);
        this.path = path;
        getConfigs();
    }

    public static Config getInstance(String path, String configFile) {
        if (instance == null) {
            return new Config(path, new File(path + configFile));
        } else {
            return instance;
        }
    }

    public final void getConfigs() {
        config.load();
        gravesConfig();
        compatibilityConfigs();
        config.save();
        getGravesText();
    }


    public static boolean generatePlayerGraves;
    public static boolean generateVillagerGraves;
    public static boolean generatePetGraves;
    public static boolean generateGravesInLava;
    public static int graveItemsCount;
    public static boolean canPlaceGravesEveryWhere;
    public static boolean generateSwordGraves;
    public static boolean removeEmptyGraves;
    public static boolean showGravesRemovingMessages;
    public static boolean onlyOwnerCanLootGrave;
    public static boolean renderGravesFlowers;
    public static boolean vanillaRendererForSwordsGraves;
    public static boolean generateEmptyPlayerGraves;
    public static boolean dropGraveBlockAtDestruction;
    public static List<Integer> playerGravesDimensionalBlackList;
    public static boolean createBackups;

    public static List<GraveStoneHelper.RestrictedArea> restrictGraveGenerationInArea;

    private static void gravesConfig() {
        canPlaceGravesEveryWhere = config.get(CATEGORY_GRAVES, "AllowToPlaceGravesEveryWhere", false).getBoolean(false);
        generatePlayerGraves = config.get(CATEGORY_GRAVES, "GeneratePlayersGraves", true).getBoolean(true);
        generateVillagerGraves = config.get(CATEGORY_GRAVES, "GenerateVillagersGraves", true).getBoolean(true);
        generatePetGraves = config.get(CATEGORY_GRAVES, "GeneratePetsGraves", true).getBoolean(true);
        generateGravesInLava = config.get(CATEGORY_GRAVES, "GenerateGravesInLava", true).getBoolean(true);
        generateSwordGraves = config.get(CATEGORY_GRAVES, "GenerateSwordGraves", true).getBoolean(true);
        generateEmptyPlayerGraves = config.get(CATEGORY_GRAVES, "GenerateEmptyPlayerGraves", true).getBoolean(true);
        onlyOwnerCanLootGrave = config.get(CATEGORY_GRAVES, "OnlyOwnerCanLootGrave", false).getBoolean(false);


        // store items
        Property graveItemsCountProperty = config.get(CATEGORY_GRAVES, "AmountOfSavedItems", 100);
        graveItemsCountProperty.setComment("This value is amount of items which should be saved in percents. It should be in range of 0 an 100!");
        graveItemsCount = graveItemsCountProperty.getInt();

        if (graveItemsCount > 100 || graveItemsCount < 0) {
            graveItemsCount = 100;
        }

        removeEmptyGraves = config.get(CATEGORY_GRAVES, "RemoveEmptyGraves", false).getBoolean(false);
        showGravesRemovingMessages = config.get(CATEGORY_GRAVES, "ShowGravesRemovingMessages", true).getBoolean(true);
        dropGraveBlockAtDestruction = config.get(CATEGORY_GRAVES, "DropGraveBlockAtDestruction", true).getBoolean(true);

        renderGravesFlowers = config.get(CATEGORY_GRAVES, "RenderGravesFlowers", true).getBoolean(true);
        vanillaRendererForSwordsGraves = config.get(CATEGORY_GRAVES, "VanillaRendererForSwordsGraves", true).getBoolean(true);

        Property restrictGraveGenerationInAreaProperty = config.get(CATEGORY_GRAVES, "RestrictGraveGenerationInArea", "");
        restrictGraveGenerationInAreaProperty.setComment("List of coordinates in which graves generation must be disabled. \"dimension_id,start_x,start_y,start_z,end_x,end_y,end_z;\". Dimension id is optional - it will be set to 0 by default.");
        String ar = restrictGraveGenerationInAreaProperty.getString();
        String[] areas = ar.split(";");
        restrictGraveGenerationInArea = new ArrayList<>(areas.length);
        for (String area : areas) {
            GraveStoneHelper.RestrictedArea restrictedArea = GraveStoneHelper.RestrictedArea.getFromString(area);
            if (restrictedArea != null) {
                restrictGraveGenerationInArea.add(restrictedArea);
            }
        }

        playerGravesDimensionalBlackList = ConfigsHelper.getDimensionList(config, CATEGORY_GRAVES, "PlayerGravesDimensionalBlackList", "",
                "List of dimension ids in which player's graves will not be generated at death");

        createBackups = config.get(CATEGORY_GRAVES, "CreateBackups", true).getBoolean();
    }


    // COMPATIBILITY
    public static boolean storeBattlegearItems;
    public static boolean storeTheCampingModItems;
    public static boolean storeBaublesItems;
    public static boolean storeMaricultureItems;
    public static boolean storeRpgInventoryItems;
    public static boolean storeGalacticraftItems;
    public static boolean storeBackpacksItems;
    public static boolean enableArsMagicaSoulbound;
    public static boolean enableEnderIOSoulbound;
    public static boolean enableTconstructSoulbound;
    public static boolean enableTwilightForestKeeping;
    public static boolean addThaumcraftSwordsAsGravestones;

    private static void compatibilityConfigs() {

//        storeBattlegearItems = config.get(CATEGORY_COMPATIBILITY, "StoreBattlegearItems", true).getBoolean(true);
        storeTheCampingModItems = config.get(CATEGORY_COMPATIBILITY, "StoreTheCampingModItems", true).getBoolean(true);
//        storeBaublesItems = config.get(CATEGORY_COMPATIBILITY, "StoreBaublesItems", true).getBoolean(true);
//        storeMaricultureItems = config.get(CATEGORY_COMPATIBILITY, "StoreMaricultureItems", true).getBoolean(true);
//        storeRpgInventoryItems = config.get(CATEGORY_COMPATIBILITY, "StoreRpgInventoryItems", true).getBoolean(true);
//        storeGalacticraftItems = config.get(CATEGORY_COMPATIBILITY, "StoreGalacticraftItems", true).getBoolean(true);
//        storeBackpacksItems = config.get(CATEGORY_COMPATIBILITY, "StoreBackpacksItems", true).getBoolean(true);

//        enableArsMagicaSoulbound = config.get(CATEGORY_COMPATIBILITY, "EnableArsMagicaSoulbound", true).getBoolean(true);
//        enableEnderIOSoulbound = config.get(CATEGORY_COMPATIBILITY, "EnableEnderIOSoulbound", true).getBoolean(true);
//        enableTconstructSoulbound = config.get(CATEGORY_COMPATIBILITY, "EnableTconstructSoulbound", true).getBoolean(true);
//        enableTwilightForestKeeping = config.get(CATEGORY_COMPATIBILITY, "EnableTwilightForestCharmsOfKeeping", true).getBoolean(true);

        addThaumcraftSwordsAsGravestones = config.get(CATEGORY_COMPATIBILITY, "AddThaumcraftSwordsAsGravestones", true).getBoolean(true);
    }

    // grave names
    public static ArrayList<String> graveNames;
    public static ArrayList<String> graveDogsNames;
    public static ArrayList<String> graveCatsNames;
    public static ArrayList<String> graveDeathMessages;

    private void getGravesText() {
        graveNames = readStringsFromFile(path + "graveNames.txt", GravesDefaultText.NAMES);
        graveDogsNames = readStringsFromFile(path + "graveDogsNames.txt", GravesDefaultText.DOG_NAMES);
        graveCatsNames = readStringsFromFile(path + "graveCatsNames.txt", GravesDefaultText.CAT_NAMES);
        graveDeathMessages = readStringsFromFile(path + "graveDeathMessages.txt", GravesDefaultText.DEATH_TEXT);
    }

    /*
     * Read text from file if it exist or get default text
     */
    private static ArrayList<String> readStringsFromFile(String fileName, String[] defaultValues) {
        ArrayList<String> list = new ArrayList<String>();
        list.addAll(Arrays.asList(defaultValues));

        return list;
    }
} @GameRegistry.ObjectHolder(ModInfo.ID)
public class GSBlock {

    public static final BlockGraveStone GRAVE_STONE = new BlockGraveStone();
    public static final ItemBlock GRAVE_STONE_IB = new ItemBlockGraveStone(GRAVE_STONE);

    @Mod.EventBusSubscriber(modid = ModInfo.ID)
    public static class RegistrationHandler {

        @SubscribeEvent
        public static void registerBlocks(final RegistryEvent.Register<Block> event) {
            final IForgeRegistry<Block> registry = event.getRegistry();
            registry.registerAll(GRAVE_STONE);
        }

        @SubscribeEvent
        public static void registerItemBlocks(final RegistryEvent.Register<Item> event) {
            final IForgeRegistry<Item> registry = event.getRegistry();

            registry.register(GRAVE_STONE_IB);
        }
    }
} public class MobHandler {
    public static final String MOBS_SPAWN_TIME_FILE_NAME = "mobsSpawnTime.gs";
    public static final String MOBS_SPAWN_TIME_BACKUP_FILE_NAME = "mobsSpawnTimeBackup.gs";

    private static HashMap<String, Long> mobsSpawnTime = new HashMap<>();

    public static void clearMobsSpawnTime(Entity entity) {
        mobsSpawnTime.remove(entity.getUniqueID().toString());
        saveMobsSpawnTime(entity.getEntityWorld());
    }

    public static long getAndRemoveSpawnTime(Entity entity) {
        if (mobsSpawnTime.containsKey(entity.getUniqueID().toString())) {
            long time = mobsSpawnTime.get(entity.getUniqueID().toString());
            clearMobsSpawnTime(entity);
            return time;
        } else {
            return entity.getEntityWorld().getWorldTime();
        }
    }

    public static long getMobSpawnTime(Entity entity) {
        if (!mobsSpawnTime.containsKey(entity.getUniqueID().toString())) {
            mobsSpawnTime.put(entity.getUniqueID().toString(), entity.getEntityWorld().getWorldTime());
            saveMobsSpawnTime(entity.getEntityWorld());
        }
        return mobsSpawnTime.get(entity.getUniqueID().toString());
    }

    public static void setMobSpawnTime(Entity entity) {
        if (!mobsSpawnTime.containsKey(entity.getUniqueID().toString())) {
            mobsSpawnTime.put(entity.getUniqueID().toString(), entity.getEntityWorld().getWorldTime());
            saveMobsSpawnTime(entity.getEntityWorld());
        }
    }

    public static void loadMobsSpawnTime(World world) {
        try {
            File file = new File(world.getSaveHandler().getWorldDirectory(), MOBS_SPAWN_TIME_FILE_NAME);
            File backup = new File(world.getSaveHandler().getWorldDirectory(), MOBS_SPAWN_TIME_BACKUP_FILE_NAME);

            NBTTagCompound data = null;
            boolean save = false;
            if (file != null && file.exists()) {
                data = getDataFromFile(file);
            }
            if (file == null || !file.exists() || data == null || data.hasNoTags()) {
                GSLogger.logError("Data not found. Trying to load backup data.");
                if (backup != null && backup.exists()) {
                    data = getDataFromFile(backup);
                    save = true;
                }
            }
            if (data != null) {
                Set keySet = data.getKeySet();
                Iterator it = keySet.iterator();
                while (it.hasNext()) {
                    String tagName = (String) it.next();
                    mobsSpawnTime.put(tagName, data.getLong(tagName));
                }
                if (save) {
                    saveMobsSpawnTime(world);
                }
            }
        } catch (Exception e) {
            GSLogger.logError("Error loading mobs spawn time");
            e.printStackTrace();
        }
    }

    public static void saveMobsSpawnTime(World world) {
        if (world != null && !world.isRemote && mobsSpawnTime != null) {
            try {
                File file = new File(world.getSaveHandler().getWorldDirectory(), MOBS_SPAWN_TIME_FILE_NAME);
                File backup = new File(world.getSaveHandler().getWorldDirectory(), MOBS_SPAWN_TIME_BACKUP_FILE_NAME);

                if (file != null && file.exists()) {
                    try {
                        Files.copy(file, backup);
                    } catch (Exception e) {
                        GSLogger.logError("Could not backup old spawn time file");
                    }
                }
                try {
                    if (file != null) {
                        NBTTagCompound data = new NBTTagCompound();
                        for (Map.Entry<String, Long> entry : mobsSpawnTime.entrySet()) {
                            if (entry != null) {
                                data.setLong(entry.getKey(), entry.getValue());
                            }
                        }
                        FileOutputStream fileoutputstream = new FileOutputStream(file);
                        CompressedStreamTools.writeCompressed(data, fileoutputstream);
                        fileoutputstream.close();
                    }
                } catch (Exception e) {
                    GSLogger.logError("Could not save spawn time file");
                    e.printStackTrace();
                    if (file.exists()) {
                        try {
                            file.delete();
                        } catch (Exception e2) {
                        }
                    }
                }
            } catch (Exception e) {
                GSLogger.logError("Error saving mobs spawn time");
                e.printStackTrace();
            }
        }
    }

    private static NBTTagCompound getDataFromFile(File file) {
        NBTTagCompound data = null;
        try {
            FileInputStream fileinputstream = new FileInputStream(file);
            data = CompressedStreamTools.readCompressed(fileinputstream);
            fileinputstream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return data;
    }
} public class Compatibility {

    private static final Compatibility INSTANCE = new Compatibility();

    public static boolean IS_WOLF_ARMOR_INSTALLED;

    private Compatibility() {
        new CompatibilityTheCampingMod();

//        new CompatibilityisArsMagica();

//        new CompatibilityBackpacksMod();

//        new CompatibilityBaubles();

//        new CompatibilityMariculture();

//        new CompatibilityRpgInventory();

//        new CompatibilityGalacticraft();

//        new CompatibilityEnderIO();

        new CompatibilityWitchery();

//        new CompatibilityTinkerConstruct();

        IS_WOLF_ARMOR_INSTALLED = Loader.isModLoaded("wolfarmor");
    }

    public static Compatibility getInstance() {
        return INSTANCE;
    }
} public class CompatibilityWolfArmor {

    public static List<ItemStack> getWolfItems(EntityWolf wolf) {
        if (wolf.hasCapability(Capabilities.CAPABILITY_WOLF_ARMOR, null)) {
            List<ItemStack> list = new ArrayList<>();
            IWolfArmorCapability wolfCapability = wolf.getCapability(Capabilities.CAPABILITY_WOLF_ARMOR, null);
            if (wolfCapability.getHasChest()) {

                InventoryBasic inventory = wolfCapability.getInventory();
                for (int slot = 0; slot < inventory.getSizeInventory(); slot++) {
                    list.add(inventory.getStackInSlot(slot).copy());
                    wolfCapability.setInventoryItem(slot, ItemStack.EMPTY);
                }
                wolfCapability.setHasChest(false);
                list.add(new ItemStack(Blocks.CHEST));
            }
            if (wolfCapability.getHasArmor()) {
                list.add(wolfCapability.getArmorItemStack().copy());
                wolfCapability.equipArmor(ItemStack.EMPTY);
            }
            return list;
        }
        return Collections.emptyList();
    }
}
 public class GSLogger {

    private static Logger logger = LogManager.getLogger(ModInfo.ID);
    private static Logger graveLogger = new GravesLogger();

    public static void log(Level logLevel, String message) {
        logger.log(logLevel, message);
    }

    public static void logInfo(String message) {
        logger.log(Level.INFO, message);
    }

    public static void logError(String message) {
        logger.log(Level.ERROR, message);
    }

    public static void logGrave(Level logLevel, String message) {
        graveLogger.log(logLevel, message);
    }

    public static void logInfoGrave(String message) {
        graveLogger.log(Level.INFO, message);
    }

    public static void logErrorGrave(String message) {
        graveLogger.log(Level.ERROR, message);
    }
} public class APIGraveGeneration implements IGraveGeneration {
    public static final IGraveGeneration INSTANCE = new APIGraveGeneration();

    public static final List<IPlayerDeathHandler> PLAYER_DEATH_HANDLERS = new ArrayList<>();
    public static final List<IVillagerDeathHandler> VILLAGER_DEATH_HANDLERS = new ArrayList<>();
    public static final List<IDogDeathHandler> DOG_DEATH_HANDLERS = new ArrayList<>();
    public static final List<ICatDeathHandler> CAT_DEATH_HANDLERS = new ArrayList<>();
    public static final List<IHorseDeathHandler> HORSE_DEATH_HANDLERS = new ArrayList<>();

    public static final List<ICustomEntityDeathHandler> CUSTOM_ENTITY_DEATH_HANDLERS = new ArrayList<>();

    @Override
    public void addPlayerDeathHandler(IPlayerDeathHandler playerDeathHandler) {
        if (playerDeathHandler != null) {
            PLAYER_DEATH_HANDLERS.add(playerDeathHandler);
        }
    }

    @Override
    public void addVillagerDeathHandler(IVillagerDeathHandler villagerDeathHandler) {
        if (villagerDeathHandler != null) {
            VILLAGER_DEATH_HANDLERS.add(villagerDeathHandler);
        }
    }

    @Override
    public void addDogDeathHandler(IDogDeathHandler dogDeathHandler) {
        if (dogDeathHandler != null) {
            DOG_DEATH_HANDLERS.add(dogDeathHandler);
        }
    }

    @Override
    public void addCatDeathHandler(ICatDeathHandler catDeathHandler) {
        if (catDeathHandler != null) {
            CAT_DEATH_HANDLERS.add(catDeathHandler);
        }
    }

    @Override
    public void addHorseDeathHandler(IHorseDeathHandler horseDeathHandler) {
        if (horseDeathHandler != null) {
            HORSE_DEATH_HANDLERS.add(horseDeathHandler);
        }
    }


    public static final List<IPlayerItems> PLAYER_ITEMS = new ArrayList<>();
    public static final List<IVillagerItems> VILLAGER_ITEMS = new ArrayList<>();
    public static final List<IDogItems> DOG_ITEMS = new ArrayList<>();
    public static final List<ICatItems> CAT_ITEMS = new ArrayList<>();
    public static final List<IHorseItems> HORSE_ITEMS = new ArrayList<>();

    @Override
    public void addPlayerItemsHandler(IPlayerItems playerItems) {
        if (playerItems != null) {
            PLAYER_ITEMS.add(playerItems);
        }
    }

    @Override
    public void addVillagerItemsHandler(IVillagerItems villagerItems) {
        if (villagerItems != null) {
            VILLAGER_ITEMS.add(villagerItems);
        }
    }

    @Override
    public void addDogItemsHandler(IDogItems dogItems) {
        if (dogItems != null) {
            DOG_ITEMS.add(dogItems);
        }
    }

    @Override
    public void addCatItemsHandler(ICatItems catItems) {
        if (catItems != null) {
            CAT_ITEMS.add(catItems);
        }
    }

    @Override
    public void addHorseItemsHandler(IHorseItems horseItems) {
        if (horseItems != null) {
            HORSE_ITEMS.add(horseItems);
        }
    }

    @Override
    public void addCustomEntityDeathHandler(ICustomEntityDeathHandler customEntityDeathHandler) {
        if (customEntityDeathHandler != null) {
            CUSTOM_ENTITY_DEATH_HANDLERS.add(customEntityDeathHandler);
        }
    }

    public static final List<IGravePositionHandler> GRAVE_POSITION_HANDLERS = new ArrayList<>();

    @Override
    public void addGravePositionHandler(IGravePositionHandler gravePositionHandler) {
        if (gravePositionHandler != null) {
            GRAVE_POSITION_HANDLERS.add(gravePositionHandler);
        }
    }
} public class GraveInventory implements IInventory {

    public static final int DEFAULT_INVENTORY_SIZE = 54;
    private TileEntityGraveStone tileEntity;
    protected List<ItemStack> items = new ArrayList<>(DEFAULT_INVENTORY_SIZE);

    public GraveInventory(TileEntityGraveStone tileEntity) {
        this.tileEntity = tileEntity;
    }

    public void readItems(NBTTagCompound nbtTag) {
        NBTTagList ntbItemsList = nbtTag.getTagList("Items", 10);
        items = new ArrayList<>(DEFAULT_INVENTORY_SIZE);

        for (int i = 0; i < ntbItemsList.tagCount(); ++i) {
            items.add(new ItemStack(ntbItemsList.getCompoundTagAt(i)));
        }
    }

    public void saveItems(NBTTagCompound nbtTag) {
        NBTTagList ntbList = new NBTTagList();

        for (ItemStack stack : items) {
            if (stack != null && stack != ItemStack.EMPTY) {
                NBTTagCompound nbt = new NBTTagCompound();
                stack.writeToNBT(nbt);
                ntbList.appendTag(nbt);
            }
        }

        nbtTag.setTag("Items", ntbList);
    }

    @Override
    public boolean isEmpty() {
        return items.isEmpty();
    }

    public void addInventoryContent(ItemStack stack) {
        if (stack != null && stack != ItemStack.EMPTY) {
            items.add(stack);
        }
    }

    @Override
    public int getSizeInventory() {
        return items.size();
    }

    public int getSizeInventoryForGui() {
        return (items.size() > DEFAULT_INVENTORY_SIZE) ? items.size() : DEFAULT_INVENTORY_SIZE;
    }

    @Override
    public ItemStack getStackInSlot(int slot) {
        if (slot < items.size()) {
            return items.get(slot);
        }
        return ItemStack.EMPTY;
    }

    @Override
    public ItemStack decrStackSize(int slot, int amount) {
        ItemStack stack = getStackInSlot(slot);
        if (stack != null && stack != ItemStack.EMPTY) {
            if (stack.getCount() <= amount) {
                setInventorySlotContents(slot, ItemStack.EMPTY);
            } else {
                stack = stack.splitStack(amount);
                if (stack.getCount() == 0) {
                    setInventorySlotContents(slot, ItemStack.EMPTY);
                }
            }
        }
        return stack;
    }

    @Override
    public ItemStack removeStackFromSlot(int slot) {
        ItemStack stack = getStackInSlot(slot);
        if (stack != null && stack != ItemStack.EMPTY) {
            setInventorySlotContents(slot, ItemStack.EMPTY);
        }
        return stack;
    }

    @Override
    public void setInventorySlotContents(int slot, ItemStack stack) {
        if (slot < items.size()) {
            this.items.set(slot, stack);
        }
    }

    /**
     * Returns the maximum stack size for a inventory slot.
     */
    @Override
    public int getInventoryStackLimit() {
        return 64;
    }

    @Override
    public void markDirty() {

    }

    @Override
    public boolean isUsableByPlayer(EntityPlayer player) {
        return player.getEntityWorld().getTileEntity(this.tileEntity.getPos()) == this.tileEntity &&
                player.getDistanceSq(new BlockPos(this.tileEntity.getPos().getX() + 0.5, this.tileEntity.getPos().getY() + 0.5, this.tileEntity.getPos().getZ() + 0.5)) < 64;
    }

    @Override
    public void openInventory(EntityPlayer player) {

    }

    @Override
    public void closeInventory(EntityPlayer player) {

    }

    @Override
    public boolean isItemValidForSlot(int index, ItemStack stack) {
        return false;
    }

    @Override
    public int getField(int id) {
        return 0;
    }

    @Override
    public void setField(int id, int value) {

    }

    @Override
    public int getFieldCount() {
        return 0;
    }

    @Override
    public void clear() {
        this.items.clear();
    }

    @Override
    public String getName() {
        return "";
    }

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

    @Override
    public ITextComponent getDisplayName() {
        return null;
    }

    /**
     * Set items as grave loot
     *
     * @param items Saving items
     */
    public void setItems(List<ItemStack> items) {
        if (items != null) {
            int savedItems;
            if (Config.graveItemsCount == 100) {
                savedItems = items.size();
            } else {
                savedItems = items.size() * Config.graveItemsCount / 100;
            }
            Collections.shuffle(Arrays.asList(items.size()), new Random());

            for (ItemStack item : items) {
                if (item != null && item != ItemStack.EMPTY && savedItems > 0) {
                    addInventoryContent(item);
                    savedItems--;
                } else {
                    dropItem(item, tileEntity.getWorld(), tileEntity.getPos());
                }
            }
        }
    }

    public void setAdditionalItems(ItemStack[] items) {
        if (items != null) {
            for (ItemStack item : items) {
                addInventoryContent(item);
            }
        }
    }

    public void setAdditionalItems(List<ItemStack> items) {
        if (items != null) {
            for (ItemStack item : items) {
                addInventoryContent(item);
            }
        }
    }

    /**
     * Drop item
     *
     * @param stack Dropping item
     */
    public static void dropItem(ItemStack stack, World world, BlockPos pos) {
        if (stack != null && stack != ItemStack.EMPTY) {
            Random random = new Random();
            EntityItem entityItem = new EntityItem(world, pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5, stack.copy());
            entityItem.motionX = random.nextGaussian() * 0.2;
            entityItem.motionY = random.nextGaussian() * 0.2;
            entityItem.motionZ = random.nextGaussian() * 0.2;

            if (stack.hasTagCompound()) {
                entityItem.getItem().setTagCompound(stack.getTagCompound().copy());
            }
            world.spawnEntity(entityItem);
        }
    }

    /**
     * Drop item by slot number
     *
     * @param slot Item slot number
     */
    public void dropItem(int slot) {
        dropItem(items.get(slot), tileEntity.getWorld(), tileEntity.getPos());
    }

    public void dropItem(ItemStack stack) {
        dropItem(stack, tileEntity.getWorld(), tileEntity.getPos());
    }

    /*
     * Drop all holding items
     */
    public void dropAllItems() {
        items.forEach(this::dropItem);
        items.clear();
    }

    public List<ItemStack> getGraveContent() {
        return items;
    }
} public class DeathMessageInfo {

    private String name;
    private String deathMessage;
    private String killerName;
    private String itemName;
    private DeathTextHelper.DeathType deathType;

    public DeathMessageInfo(String name, String deathMessage, String killerName) {
        this(name, deathMessage, killerName, null, null);
    }

    public DeathMessageInfo(String name, String deathMessage, String killerName, String itemName) {
        this(name, deathMessage, killerName, itemName, null);
    }

    public DeathMessageInfo(String name, String deathMessage, String killerName, String itemName, DeathTextHelper.DeathType deathType) {
        this.name = name;
        this.deathMessage = deathMessage;
        this.killerName = killerName;
        this.itemName = itemName;
        this.deathType = deathType;
    }

    public String getName() {
        return name;
    }

    public String getDeathMessage() {
        return deathMessage;
    }

    public String getKillerName() {
        return killerName;
    }

    public void setKillerName(String killerName) {
        this.killerName = killerName;
    }

    public String getKillerNameForTE() {
        return (killerName == null) ? "" : killerName;
    }

    public String getItemName() {
        return itemName;
    }

    public DeathTextHelper.DeathType getDeathType() {
        return deathType;
    }
} public class TileEntityGraveStone extends TileEntityGrave implements ITickable, ISpawnerEntity {

    public static GraveSpawnerHelper graveSpawnerHelper = new GraveSpawnerHelper();

    protected ItemStack sword = null;
    protected ItemStack flower = null;
    protected String playerId = "";
    protected boolean isPurified = false;
    protected int spawnerHelperId;
    protected GroupOfGravesSpawnerHelper spawnerHelper;
    protected ISpawner spawner;
    public static IFog fogHandler = new IFog() {
    };

    public TileEntityGraveStone() {
        super();
        spawner = graveSpawnerHelper.getSpawner(this);
        inventory = new GraveInventory(this);
    }

    public TileEntityGraveStone(World world) {
        this();
        this.setWorld(world);
    }

    @Override
    public void update() {
        if (spawnerHelperId != 0 && spawnerHelper == null) {
            Entity entity = this.getWorld().getEntityByID(spawnerHelperId);

            if (entity instanceof GroupOfGravesSpawnerHelper) {
                spawnerHelper = (GroupOfGravesSpawnerHelper) entity;
            }
        }

        spawner.update();

        fogHandler.addFog(this.getWorld(), this.pos);
    }

    @Override
    public World getIWorld() {
        return getWorld();
    }

    @Override
    public BlockPos getIPos() {
        return getPos();
    }

    @Override
    public boolean receiveClientEvent(int par1, int par2) {
        if (par1 == 1 && this.getWorld().isRemote) {
            spawner.setMinDelay();
        }

        return true;
    }

    @Override
    public void readFromNBT(NBTTagCompound nbtTag) {
        super.readFromNBT(nbtTag);
        // age
        age = nbtTag.getInteger("Age");
        // grave loot
        inventory.readItems(nbtTag);
        // death text
        deathText.readText(nbtTag);
        // sword
        readSwordInfo(nbtTag);
        // flower
        readFlowerInfo(nbtTag);
        // owner
        playerId = nbtTag.getString("PlayerId");

        isPurified = nbtTag.getBoolean("Purified");

        //spawnerHelper
        if (nbtTag.hasKey("SpawnerHelperId")) {
            spawnerHelperId = nbtTag.getInteger("SpawnerHelperId");
        }
    }

    @Override
    public NBTTagCompound writeToNBT(NBTTagCompound nbtTag) {
        nbtTag = super.writeToNBT(nbtTag);
        // age
        nbtTag.setInteger("Age", age);
        // grave loot
        inventory.saveItems(nbtTag);
        // death text
        deathText.saveText(nbtTag);
        // sword
        writeSwordInfo(nbtTag);
        // flower
        writeFlowerInfo(nbtTag);
        // owner
        nbtTag.setString("PlayerId", playerId);

        nbtTag.setBoolean("Purified", isPurified);

        //spawnerHelper
        if (haveSpawnerHelper()) {
            nbtTag.setInteger("SpawnerHelperId", spawnerHelper.getEntityId());
        }
        return nbtTag;
    }

    private void readSwordInfo(NBTTagCompound nbtTag) {
        if (nbtTag.hasKey("Sword")) {
            sword = new ItemStack(nbtTag.getCompoundTag("Sword"));
        }
    }

    private void writeSwordInfo(NBTTagCompound nbtTag) {
        if (sword != null) {
            NBTTagCompound swordNBT = new NBTTagCompound();
            sword.writeToNBT(swordNBT);
            nbtTag.setTag("Sword", swordNBT);
        }
    }

    private void readFlowerInfo(NBTTagCompound nbtTag) {
        if (nbtTag.hasKey("Flower")) {
            flower = new ItemStack(nbtTag.getCompoundTag("Flower"));
        }
    }

    private void writeFlowerInfo(NBTTagCompound nbtTag) {
        if (flower != null) {
            NBTTagCompound flowerNBT = new NBTTagCompound();
            flower.writeToNBT(flowerNBT);
            nbtTag.setTag("Flower", flowerNBT);
        }
    }

    public ItemStack getSword() {
        return this.sword;
    }

    public void setSword(ItemStack sword) {
        this.sword = sword;
    }

    public void dropSword() {
        if (this.sword != null) {
            this.inventory.dropItem(this.sword, this.getWorld(), this.pos);
        }
    }

    public boolean isSwordGrave() {
        return sword != null;
    }

    public boolean canBeMossy() {
        return !isSwordGrave() && this.getGraveType() != EnumGraves.STARVED_CORPSE && this.getGraveType() != EnumGraves.WITHERED_CORPSE;
    }


    public ItemStack getFlower() {
        return this.flower;
    }

    public void setFlower(ItemStack flower) {
        this.flower = flower;
    }

    public void dropFlower() {
        if (this.flower != null) {
            this.inventory.dropItem(this.flower, this.getWorld(), this.pos);
        }
    }

    public boolean hasFlower() {
        return flower != null;
    }

    public EnumGraves getGraveType() {
        return EnumGraves.getById(graveType);
    }

    public boolean isEmpty() {
        return inventory.isEmpty();
    }

    //    @Override
    //    public void setGraveContent(Random random, boolean isPetGrave, GraveInventory.GraveContentType contentType, GraveInventory.GraveCorpseContentType corpseType) {
    ////        setRandomAge();//TODO
    ////        setRandomFlower(random);//TODO
    //    }


    public String getOwner() {
        return this.playerId;
    }

    public void setOwner(String playerId) {
        this.playerId = playerId;
    }

    public boolean canBeLooted(EntityPlayer player) {
        if (Config.onlyOwnerCanLootGrave) {
            if (player != null) {
                String playerId = player.getUniqueID().toString();
                return player.isCreative() || StringUtils.isBlank(this.playerId) || playerId.equals(this.playerId) || inventory.getGraveContent().isEmpty();
            }
            return false;
        }
        return true;
    }

    @Override
    public boolean haveSpawnerHelper() {
        return spawnerHelper != null;
    }

    @Override
    public GroupOfGravesSpawnerHelper getSpawnerHelper() {
        return spawnerHelper;
    }

    public void setSpawnerHelper(GroupOfGravesSpawnerHelper spawnerHelper) {
        this.spawnerHelper = spawnerHelper;
        if (spawnerHelper != null) {
            this.spawnerHelperId = spawnerHelper.getEntityId();
        }
    }

    public boolean isPurified() {
        return isPurified;
    }

    public void setPurified(boolean isPurified) {
        this.isPurified = isPurified;
    }
}
import net.minecraft.block.state.IBlockState;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityList;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.item.EntityItem;
import net.minecraft.entity.passive.*;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.init.Blocks;
import net.minecraft.init.Items;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.util.DamageSource;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.NonNullList;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.text.translation.I18n;
import net.minecraft.world.World;
import net.minecraftforge.common.BiomeDictionary;
import net.minecraftforge.event.entity.living.LivingDeathEvent;
import net.minecraftforge.items.CapabilityItemHandler;
import net.minecraftforge.items.IItemHandler;
import nightkosh.gravestone.api.IGraveStoneHelper;
import nightkosh.gravestone.api.death_handler.ICustomEntityDeathHandler;
import nightkosh.gravestone.api.grave.EnumGraveMaterial;
import nightkosh.gravestone.api.grave.EnumGraveType;
import nightkosh.gravestone.api.grave_items.*;
import nightkosh.gravestone.api.grave_position.IGravePositionHandler;
import nightkosh.gravestone.block.BlockGraveStone;
import nightkosh.gravestone.block.enums.EnumGraves;
import nightkosh.gravestone.config.Config;
import nightkosh.gravestone.core.GSBlock;
import nightkosh.gravestone.core.MobHandler;
import nightkosh.gravestone.core.compatibility.Compatibility;
import nightkosh.gravestone.core.compatibility.CompatibilityWolfArmor;
import nightkosh.gravestone.core.logger.GSLogger;
import nightkosh.gravestone.helper.api.APIGraveGeneration;
import nightkosh.gravestone.inventory.GraveInventory;
import nightkosh.gravestone.tileentity.DeathMessageInfo;
import nightkosh.gravestone.tileentity.TileEntityGraveStone;
import java.util.*;
            items.addAll(additionalItems.addItems(villager, damageSource));
        }

        IItemHandler itemHandler = villager.getCapability(CapabilityItemHandler.ITEM_HANDLER_CAPABILITY, villager.getHorizontalFacing());
        if (villager.hasCapability(CapabilityItemHandler.ITEM_HANDLER_CAPABILITY, villager.getHorizontalFacing())) {
            for (int slot = 0; slot < itemHandler.getSlots(); slot++) {
                ItemStack stack = itemHandler.extractItem(slot, 100500, false);
                if (stack != null && !stack.isEmpty()) {
                    items.add(stack);
                }
            }
        }

        long spawnTime = MobHandler.getAndRemoveSpawnTime(villager);
        createGrave(villager, damageSource, items, GraveGenerationHelper.EnumGraveTypeByEntity.VILLAGERS_GRAVES, true, spawnTime);
    }

    public static void createDogGrave(EntityWolf dog, DamageSource damageSource) {
        if (dog.isTamed()) {
            long spawnTime = MobHandler.getAndRemoveSpawnTime(dog);
            createGrave(dog, damageSource, getDogsItems(dog, damageSource), EnumGraveTypeByEntity.DOGS_GRAVES, false, spawnTime);
        }
    }

    public static void createCatGrave(EntityOcelot cat, DamageSource damageSource) {
        if (cat.isTamed()) {
            long spawnTime = MobHandler.getAndRemoveSpawnTime(cat);
            createGrave(cat, damageSource, getCatsItems(cat, damageSource), EnumGraveTypeByEntity.CATS_GRAVES, false, spawnTime);
        }
    }

    private static List<ItemStack> getDogsItems(EntityWolf dog, DamageSource damageSource) {
        List<ItemStack> items = new ArrayList<>(5);
        for (IDogItems additionalItems : APIGraveGeneration.DOG_ITEMS) {
            items.addAll(additionalItems.addItems(dog, damageSource));
        }

        if (Compatibility.IS_WOLF_ARMOR_INSTALLED) {
            items.addAll(CompatibilityWolfArmor.getWolfItems(dog));
        }

        return items;
    }

    private static List<ItemStack> getCatsItems(EntityOcelot cat, DamageSource damageSource) {
        List<ItemStack> items = new ArrayList<>(5);
        for (ICatItems additionalItems : APIGraveGeneration.CAT_ITEMS) {
            items.addAll(additionalItems.addItems(cat, damageSource));
        }

        IItemHandler itemHandler = cat.getCapability(CapabilityItemHandler.ITEM_HANDLER_CAPABILITY, cat.getHorizontalFacing());
        if (cat.hasCapability(CapabilityItemHandler.ITEM_HANDLER_CAPABILITY, cat.getHorizontalFacing())) {
            for (int slot = 0; slot < itemHandler.getSlots(); slot++) {
                ItemStack stack = itemHandler.extractItem(slot, 100500, false);
                if (stack != null && !stack.isEmpty()) {
                    items.add(stack);
                }
            }
        }
        return items;
    }

    public static void createHorseGrave(AbstractHorse horse, DamageSource damageSource) {
        if (horse.isTame()) {
            List<ItemStack> items = new ArrayList<>();
            items.addAll(getHorseItems(horse));

            for (IHorseItems additionalItems : APIGraveGeneration.HORSE_ITEMS) {
                items.addAll(additionalItems.addItems(horse, damageSource));
            }

            long spawnTime = MobHandler.getAndRemoveSpawnTime(horse);
            createGrave(horse, damageSource, items, EnumGraveTypeByEntity.HORSE_GRAVES, false, spawnTime);
        }
    }

    private static List<ItemStack> getHorseItems(AbstractHorse horse) {
        List<ItemStack> items = new ArrayList<>();

        IItemHandler itemHandler = horse.getCapability(CapabilityItemHandler.ITEM_HANDLER_CAPABILITY, horse.getHorizontalFacing());
        if (horse.hasCapability(CapabilityItemHandler.ITEM_HANDLER_CAPABILITY, horse.getHorizontalFacing())) {
            for (int slot = 0; slot < itemHandler.getSlots(); slot++) {
                ItemStack stack = itemHandler.extractItem(slot, 100500, false);
                if (stack != null && !stack.isEmpty()) {
                    items.add(stack);
                }
            }
        }

        return items;
    }

    public static void createGrave(Entity entity, DamageSource damageSource, List<ItemStack> items, EnumGraveTypeByEntity graveTypeByEntity, boolean isVillager, long spawnTime) {
        if (isInRestrictedArea(entity.getEntityWorld(), entity.getPosition())) {
            GSLogger.logInfo("Can't generate " + entity.getName() + "'s grave in restricted area. " + entity.getPosition().toString());
            if (items != null) {
                items.stream().filter(item -> item != null).forEach(item -> {
                    GraveInventory.dropItem(item, entity.getEntityWorld(), entity.getPosition());
                });
            }
        } else {
            int age = (int) (entity.getEntityWorld().getWorldTime() - spawnTime) / 24000;
            BlockPos pos = new BlockPos(entity.posX, Math.round(entity.posY), entity.posZ - 1);
            GraveInfoOnDeath graveInfo = getGraveOnDeath(entity.getEntityWorld(), pos, entity, graveTypeByEntity, items, age, damageSource);
            DeathMessageInfo messageInfo = getDeathMessage((EntityLivingBase) entity, damageSource.damageType, isVillager);
            createOnDeath(entity, entity.getEntityWorld(), pos, messageInfo, items, age, graveInfo, damageSource);
        }
    }

    public static void createCustomGrave(Entity entity, LivingDeathEvent event, ICustomEntityDeathHandler customEntityDeathHandler) {
        if (isInRestrictedArea(entity.getEntityWorld(), entity.getPosition())) {
            GSLogger.logInfo("Can't generate " + entity.getName() + "'s grave in restricted area. " + entity.getPosition().toString());
            if (customEntityDeathHandler.getItems() != null) {
                customEntityDeathHandler.getItems().stream().filter(item -> item != null).forEach(item -> {
                    GraveInventory.dropItem(item, entity.getEntityWorld(), entity.getPosition());
                });
            }
        } else {
            int age = customEntityDeathHandler.getAge();
            GraveInfoOnDeath graveInfo = new GraveInfoOnDeath();
            graveInfo.setGrave(EnumGraves.getByTypeAndMaterial(customEntityDeathHandler.getGraveType(entity, event.getSource()),