/** * This class was created by <Vazkii>. It's distributed as * part of the Botania Mod. Get the Source Code in github: * https://github.com/Vazkii/Botania * * Botania is Open Source and distributed under the * Botania License: http://botaniamod.net/license.php * * File Created @ [Apr 9, 2014, 5:11:34 PM (GMT)] */ package vazkii.botania.common.item; import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Random; import java.util.Set; import javax.annotation.Nonnull; import gnu.trove.map.hash.TIntObjectHashMap; import net.minecraft.block.Block; import net.minecraft.block.BlockDirt; import net.minecraft.block.state.IBlockState; import net.minecraft.creativetab.CreativeTabs; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.init.Blocks; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; import net.minecraft.util.EnumActionResult; import net.minecraft.util.EnumFacing; import net.minecraft.util.EnumHand; import net.minecraft.util.NonNullList; import net.minecraft.util.math.BlockPos; import net.minecraft.world.World; import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; import net.minecraftforge.fml.common.gameevent.TickEvent; import net.minecraftforge.fml.common.gameevent.TickEvent.Phase; import net.minecraftforge.fml.relauncher.Side; import net.minecraftforge.fml.relauncher.SideOnly; import vazkii.botania.api.item.IFloatingFlower.IslandType; import vazkii.botania.api.state.BotaniaStateProps; import vazkii.botania.api.state.enums.AltGrassVariant; import vazkii.botania.client.core.handler.ModelHandler; import vazkii.botania.common.Botania; import vazkii.botania.common.block.ModBlocks; import vazkii.botania.common.lib.LibItemNames; public class ItemGrassSeeds extends ItemMod implements IFloatingFlowerVariant { /** * Represents a map of dimension IDs to a set of all block swappers * active in that dimension. */ private static final TIntObjectHashMap<Set<BlockSwapper>> blockSwappers = new TIntObjectHashMap<>(); private static final IslandType[] ISLAND_TYPES = { IslandType.GRASS, IslandType.PODZOL, IslandType.MYCEL, IslandType.DRY, IslandType.GOLDEN, IslandType.VIVID, IslandType.SCORCHED, IslandType.INFUSED, IslandType.MUTATED }; private static final int SUBTYPES = 9; public ItemGrassSeeds() { super(LibItemNames.GRASS_SEEDS); setHasSubtypes(true); MinecraftForge.EVENT_BUS.register(this); } @Override @SideOnly(Side.CLIENT) public void getSubItems(@Nonnull Item item, CreativeTabs par2, NonNullList<ItemStack> par3) { for(int i = 0; i < SUBTYPES; i++) par3.add(new ItemStack(item, 1, i)); } @Nonnull @Override public String getUnlocalizedName(ItemStack stack) { return super.getUnlocalizedName() + stack.getItemDamage(); } @Nonnull @Override public EnumActionResult onItemUse(EntityPlayer player, World world, BlockPos pos, EnumHand hand, EnumFacing side, float par8, float par9, float par10) { IBlockState state = world.getBlockState(pos); ItemStack stack = player.getHeldItem(hand); if(state.getBlock() == Blocks.DIRT && state.getValue(BlockDirt.VARIANT) == BlockDirt.DirtType.DIRT || state.getBlock() == Blocks.GRASS && stack.getItemDamage() != 0) { int meta = stack.getItemDamage(); if(!world.isRemote) { BlockSwapper swapper = addBlockSwapper(world, pos, meta); world.setBlockState(pos, swapper.stateToSet, 1 | 2); stack.shrink(1); } else { for(int i = 0; i < 50; i++) { double x = (Math.random() - 0.5) * 3; double y = Math.random() - 0.5 + 1; double z = (Math.random() - 0.5) * 3; float r = 0F; float g = 0.4F; float b = 0F; switch(meta) { case 1: { r = 0.5F; g = 0.37F; b = 0F; break; } case 2: { r = 0.27F; g = 0F; b = 0.33F; break; } case 3: { r = 0.4F; g = 0.5F; b = 0.05F; break; } case 4: { r = 0.75F; g = 0.7F; b = 0F; break; } case 5: { r = 0F; g = 0.5F; b = 0.1F; break; } case 6: { r = 0.75F; g = 0F; b = 0F; break; } case 7: { r = 0F; g = 0.55F; b = 0.55F; break; } case 8: { r = 0.4F; g = 0.1F; b = 0.4F; break; } } float velMul = 0.025F; Botania.proxy.wispFX(pos.getX() + 0.5 + x, pos.getY() + 0.5 + y, pos.getZ() + 0.5 + z, r, g, b, (float) Math.random() * 0.15F + 0.15F, (float) -x * velMul, (float) -y * velMul, (float) -z * velMul); } } return EnumActionResult.SUCCESS; } return EnumActionResult.PASS; } @SideOnly(Side.CLIENT) @Override public void registerModels() { ModelHandler.registerItemAppendMeta(this, 9, LibItemNames.GRASS_SEEDS); } @SubscribeEvent public void onTickEnd(TickEvent.WorldTickEvent event) { // Block swapper updates should only occur on the server if(event.world.isRemote) return; if(event.phase == Phase.END) { int dim = event.world.provider.getDimension(); if(blockSwappers.containsKey(dim)) { Set<BlockSwapper> swappers = blockSwappers.get(dim); Iterator<BlockSwapper> iter = swappers.iterator(); while(iter.hasNext()) { BlockSwapper next = iter.next(); if(next == null || !next.tick()) iter.remove(); } } } } /** * Adds a grass seed block swapper to the world at the provided positiona * and with the provided meta (which designates the type of the grass * being spread). * * Block swappers are only actually created on the server, so a client * calling this method will recieve a marker block swapper which contains * the provided information but is not ticked. * @param world The world the swapper will be in. * @param pos The position of the swapper. * @param meta The meta value representing the type of block being swapped. * @return The created block swapper. */ private static BlockSwapper addBlockSwapper(World world, BlockPos pos, int meta) { BlockSwapper swapper = swapperFromMeta(world, pos, meta); // If a set for the dimension doesn't exist, create it. int dim = world.provider.getDimension(); if(!blockSwappers.containsKey(dim)) blockSwappers.put(dim, new HashSet<>()); // Add the block swapper blockSwappers.get(dim).add(swapper); return swapper; } private static BlockSwapper swapperFromMeta(World world, BlockPos pos, int meta) { switch(meta) { case 1 : return new BlockSwapper(world, pos, Blocks.DIRT.getDefaultState().withProperty(BlockDirt.VARIANT, BlockDirt.DirtType.PODZOL)); case 2 : return new BlockSwapper(world, pos, Blocks.MYCELIUM.getDefaultState()); case 3 : return new BlockSwapper(world, pos, ModBlocks.altGrass.getDefaultState().withProperty(BotaniaStateProps.ALTGRASS_VARIANT, AltGrassVariant.DRY)); case 4 : return new BlockSwapper(world, pos, ModBlocks.altGrass.getDefaultState().withProperty(BotaniaStateProps.ALTGRASS_VARIANT, AltGrassVariant.GOLDEN)); case 5 : return new BlockSwapper(world, pos, ModBlocks.altGrass.getDefaultState().withProperty(BotaniaStateProps.ALTGRASS_VARIANT, AltGrassVariant.VIVID)); case 6 : return new BlockSwapper(world, pos, ModBlocks.altGrass.getDefaultState().withProperty(BotaniaStateProps.ALTGRASS_VARIANT, AltGrassVariant.SCORCHED)); case 7 : return new BlockSwapper(world, pos, ModBlocks.altGrass.getDefaultState().withProperty(BotaniaStateProps.ALTGRASS_VARIANT, AltGrassVariant.INFUSED)); case 8 : return new BlockSwapper(world, pos, ModBlocks.altGrass.getDefaultState().withProperty(BotaniaStateProps.ALTGRASS_VARIANT, AltGrassVariant.MUTATED)); default : return new BlockSwapper(world, pos, Blocks.GRASS.getDefaultState()); } } /** * A block swapper for the Pasture Seeds, which swaps dirt and grass blocks * centered around a provided point to a provided block/metadata. */ private static class BlockSwapper { /** * The range of the block swapper, in blocks. */ public static final int RANGE = 3; /** * The range around which a block can spread in a single tick. */ public static final int TICK_RANGE = 1; private final World world; private final Random rand; private final IBlockState stateToSet; private final BlockPos startCoords; private int ticksExisted = 0; /** * Constructs a new block swapper with the provided world, starting * coordinates, target block, and target metadata. * @param world The world to swap blocks in. * @param coords The central coordinates to swap blocks around. * @param state The target blockstate to swap dirt and grass to. */ public BlockSwapper(World world, BlockPos coords, IBlockState state) { this.world = world; stateToSet = state; rand = new Random(coords.hashCode()); startCoords = coords; } /** * Ticks this block swapper, allowing it to make an action during * this game tick. This method should return "false" when the swapper * has finished operation and should be removed from the world. * @return true if the swapper should continue to exist, false if it * should be removed. */ public boolean tick() { if(++ticksExisted % 20 == 0) { for(BlockPos pos : BlockPos.getAllInBox(startCoords.add(-RANGE, 0, -RANGE), startCoords.add(RANGE, 0, RANGE))) { if(world.getBlockState(pos) == stateToSet) tickBlock(pos); } } // This swapper should exist for 80 ticks return ticksExisted < 80; } /** * Tick a specific block position, finding the valid blocks * immediately adjacent to it and then replacing one at random. * @param pos The positions to use. */ public void tickBlock(BlockPos pos) { List<BlockPos> validCoords = new ArrayList<>(); // Go around this block and aggregate valid blocks. for(int xOffset = -TICK_RANGE; xOffset <= TICK_RANGE; xOffset++) { for(int zOffset = -TICK_RANGE; zOffset <= TICK_RANGE; zOffset++) { // Skip the current block if(xOffset == 0 && zOffset == 0) continue; if(isValidSwapPosition(pos.add(xOffset, 0, zOffset))) validCoords.add(pos.add(xOffset, 0, zOffset)); } } // If we can make changes, and have at least 1 block to swap, // then swap a random block from the valid blocks we could swap. if(!validCoords.isEmpty()) { BlockPos toSwap = validCoords.get(rand.nextInt(validCoords.size())); world.setBlockState(toSwap, stateToSet); } } /** * Determines if a given position is a valid location to spread to, which * means that the block must be either dirt or grass (with meta 0), * and have a block above it which does not block grass growth. * @param pos The position to check. * @return True if the position is valid to swap, false otherwise. */ public boolean isValidSwapPosition(BlockPos pos) { IBlockState state = world.getBlockState(pos); Block block = state.getBlock(); // Valid blocks to spread to are either dirt or grass, and do not // have blocks which block grass growth. // See http://minecraft.gamepedia.com/Grass_Block // The major rule is that a block which reduces light // levels by 2 or more blocks grass growth. return (block == Blocks.DIRT || block == Blocks.GRASS) && (block != Blocks.DIRT || state.getValue(BlockDirt.VARIANT) == BlockDirt.DirtType.DIRT) && world.getBlockState(pos.up()).getLightOpacity(world, pos.up()) <= 1; } } @Override public IslandType getIslandType(ItemStack stack) { return ISLAND_TYPES[Math.min(stack.getItemDamage(), ISLAND_TYPES.length - 1)]; } }