Jump to content

IceMetalPunk

Members
  • Posts

    402
  • Joined

  • Last visited

Posts posted by IceMetalPunk

  1. Is there any way to add an item--which is *not* ore dictionary'd to lapis--to be allowed in the lapis slot of the enchantment table's GUI? I thought I was clever in using reflection to access the slot's "ores" list in the PlayerContainerEvent.open event, but that's how I learned the list is unmodifiable.

     

    What's the best way to allow my custom item to also be used in the enchantment table's lapis slot (without being used like lapis anywhere else)?

  2. Aha! That's the method I was looking for! Now it updates in real time, just as it should. Thanks.

     

    Of course, you fix one bug, and another shows itself...such is programming... Now, it's seemingly not saving its state when you unload the world. I say "seemingly" because adding debug output to the readFromNBT method shows the proper output. If I set the block to look for profession 1 (librarians), which is one click away from the default profession 0 (farmers), then quit the world and come back, the readFromNBT method properly outputs that it's set to professionIndex=1 and checkProf=minecraft:librarian. But any block updates show it's really only looking for farmers, and activating the block cycles it forward to librarian again instead of the next profession on the list.

     

    Since I only get one debug message from readFromNBT, I'm assuming it's loading properly on the server but reverting to default on the client. Or something like that. But since there's no check for remoteness in the readFromNBT method, I don't know why it's one-sided like that.

     

    Any help for someone who's still quite confused? xD

    You need to call the super method, otherwise the position won't be saved as well as other data.

     

    *Edit I meant the super for readFromNBT

     

    I...had the super call for writing...and forgot it when reading...and I looked over the code at least 50 times and missed that every time... >_< *Sigh* This is what happens when the only free time I have to code is in the middle of the night after work.

     

    Thank you to everyone who helped my derpy issues here! <3

  3. Aha! That's the method I was looking for! Now it updates in real time, just as it should. Thanks.

     

    Of course, you fix one bug, and another shows itself...such is programming... Now, it's seemingly not saving its state when you unload the world. I say "seemingly" because adding debug output to the readFromNBT method shows the proper output. If I set the block to look for profession 1 (librarians), which is one click away from the default profession 0 (farmers), then quit the world and come back, the readFromNBT method properly outputs that it's set to professionIndex=1 and checkProf=minecraft:librarian. But any block updates show it's really only looking for farmers, and activating the block cycles it forward to librarian again instead of the next profession on the list.

     

    Since I only get one debug message from readFromNBT, I'm assuming it's loading properly on the server but reverting to default on the client. Or something like that. But since there's no check for remoteness in the readFromNBT method, I don't know why it's one-sided like that.

     

    Any help for someone who's still quite confused? xD

  4. D'oh! I can't believe I got those backwards >_< Thanks! That part is fixed now. However...the redstone signal is still not updating on its own. Even though I'm calling World#neighborChanged() from the tile entity whenever its output changes, I still have to manually provide a block update before the signal updates in the world. Do I instead need to call Block#onNeighborChange() for all 6 adjacent blocks? That seems like an inefficient way to do it; isn't there a more standard approach to this?

  5. I'm trying to make a block which gives a redstone output depending on how many of a certain villager profession are standing on it. It works...except that it will only check for farmers. I have the Forge VillagerProfession it should be checking for stored in a property called checkProf, and in the block's onBlockActivated method, I'm cycling through the list. As debug output, I'm echoing the currently set profession into the chat whenever it's right-clicked, and the cycling seems to be just fine, going through the list of all the professions one-by-one. But no matter what the current setting is, the block only gives a redstone output based on the number of *farmers* above it, and ignores all other professions.

     

    Here's the tile entity code:

     

    package com.IceMetalPunk.breedingseason.tileentities;
    
    import java.util.List;
    
    import net.minecraft.block.state.IBlockState;
    import net.minecraft.entity.passive.EntityVillager;
    import net.minecraft.nbt.NBTTagCompound;
    import net.minecraft.network.NetworkManager;
    import net.minecraft.network.play.server.SPacketUpdateTileEntity;
    import net.minecraft.tileentity.TileEntity;
    import net.minecraft.util.ITickable;
    import net.minecraft.util.math.AxisAlignedBB;
    import net.minecraftforge.fml.common.registry.VillagerRegistry;
    import net.minecraftforge.fml.common.registry.VillagerRegistry.VillagerProfession;
    
    public class TileEntityProfessionalRecognizer extends TileEntity implements ITickable {
    private VillagerProfession checkProf;
    private int output;
    private int lastOutput;
    private int professionIndex;
    
    public TileEntityProfessionalRecognizer() {
    	super();
    	List<VillagerProfession> profList = VillagerRegistry.instance().getRegistry().getValues();
    	this.setProf(profList.get(0));
    	this.professionIndex = 0;
    	this.output = 0;
    	this.lastOutput = 0;
    }
    
    @Override
    public NBTTagCompound writeToNBT(NBTTagCompound tag) {
    	super.writeToNBT(tag);
    	tag.setInteger("Profession", Integer.valueOf(this.professionIndex));
    	return tag;
    }
    
    @Override
    public void readFromNBT(NBTTagCompound tag) {
    	this.professionIndex = tag.getInteger("Profession");
    	List<VillagerProfession> profList = VillagerRegistry.instance().getRegistry().getValues();
    	this.checkProf = profList.get(this.professionIndex);
    }
    
    @Override
    public void update() {
    	AxisAlignedBB boundingBox = this.getRenderBoundingBox().expand(0.0d, 1.0d, 0.0d);
    	List<EntityVillager> villagers = this.getWorld().getEntitiesWithinAABB(EntityVillager.class, boundingBox);
    	this.lastOutput = this.output;
    	this.output = 0;
    	for (EntityVillager villager : villagers) {
    		if (villager.getProfessionForge().equals(this.checkProf)) {
    			this.output = Math.max(0, Math.min(15, this.output + 1));
    		}
    	}
    	if (this.output != this.lastOutput) {
    		IBlockState state = this.getWorld().getBlockState(this.pos);
    		state.getBlock().neighborChanged(state, this.getWorld(), this.pos, state.getBlock());
    	}
    }
    
    @Override
    public SPacketUpdateTileEntity getUpdatePacket() {
    	NBTTagCompound tag = new NBTTagCompound();
    	this.writeToNBT(tag);
    	return new SPacketUpdateTileEntity(this.pos, this.getBlockMetadata(), tag);
    }
    
    @Override
    public void onDataPacket(NetworkManager net, SPacketUpdateTileEntity packet) {
    	this.readFromNBT(packet.getNbtCompound());
    }
    
    public int getOutput() {
    	return this.output;
    }
    
    public void setProf(VillagerProfession prof) {
    	List<VillagerProfession> profList = VillagerRegistry.instance().getRegistry().getValues();
    	this.professionIndex = profList.indexOf(prof);
    	this.checkProf = prof;
    }
    
    public String getProfName() {
    	return this.checkProf.getRegistryName().toString();
    }
    
    public void cycleProf() {
    	List<VillagerProfession> profList = VillagerRegistry.instance().getRegistry().getValues();
    	int index = profList.indexOf(this.checkProf);
    	index = (index + 1) % profList.size();
    	this.setProf(profList.get(index));
    }
    }
    

     

    And the block code:

     

    package com.IceMetalPunk.breedingseason.blocks;
    
    import javax.annotation.Nullable;
    
    import com.IceMetalPunk.breedingseason.BreedingSeason;
    import com.IceMetalPunk.breedingseason.tileentities.TileEntityProfessionalRecognizer;
    
    import net.minecraft.block.Block;
    import net.minecraft.block.ITileEntityProvider;
    import net.minecraft.block.material.Material;
    import net.minecraft.block.state.IBlockState;
    import net.minecraft.creativetab.CreativeTabs;
    import net.minecraft.entity.player.EntityPlayer;
    import net.minecraft.item.ItemStack;
    import net.minecraft.tileentity.TileEntity;
    import net.minecraft.util.EnumFacing;
    import net.minecraft.util.EnumHand;
    import net.minecraft.util.ResourceLocation;
    import net.minecraft.util.math.BlockPos;
    import net.minecraft.util.text.TextComponentString;
    import net.minecraft.world.IBlockAccess;
    import net.minecraft.world.World;
    
    public class BlockProfessionalRecognizer extends Block implements ITileEntityProvider {
    
    public BlockProfessionalRecognizer() {
    	super(Material.IRON);
    	this.setUnlocalizedName("professional_recognizer");
    	this.setDefaultState(blockState.getBaseState());
    	this.setCreativeTab(CreativeTabs.REDSTONE); // TODO: Add creative tab
    												// for the mod
    	this.setRegistryName(new ResourceLocation(BreedingSeason.MODID, "professional_recognizer"));
    }
    
    @Override
    public TileEntity createNewTileEntity(World worldIn, int meta) {
    	return new TileEntityProfessionalRecognizer();
    }
    
    @Override
    public boolean onBlockActivated(World world, BlockPos pos, IBlockState state, EntityPlayer player, EnumHand hand,
    		@Nullable ItemStack heldItem, EnumFacing side, float hitX, float hitY, float hitZ) {
    	if (!world.isRemote) {
    		return true;
    	}
    	TileEntity entity = world.getTileEntity(pos);
    	if (entity instanceof TileEntityProfessionalRecognizer) {
    		((TileEntityProfessionalRecognizer) entity).cycleProf();
    		player.addChatComponentMessage(new TextComponentString(
    				"Profession set: " + ((TileEntityProfessionalRecognizer) entity).getProfName()));
    	}
    	return true;
    }
    
    @Override
    public boolean canProvidePower(IBlockState state) {
    	return true;
    }
    
    @Override
    public int getWeakPower(IBlockState state, IBlockAccess world, BlockPos pos, EnumFacing side) {
    	TileEntity entity = world.getTileEntity(pos);
    	if (entity instanceof TileEntityProfessionalRecognizer) {
    		return ((TileEntityProfessionalRecognizer) entity).getOutput();
    	} else {
    		return 0;
    	}
    }
    }
    

     

    I'm guessing there's something I'm missing about server/client synchronization here, but I can't figure out what, especially when I have no error messages to go from. Any help?

     

    (As a secondary issue, the tile entity's call to neighborChanged() doesn't seem to actually be creating any block updates; I have to manually place a block next to the redstone line before it'll update its value at all. How do I fix that as well?)

  6. So I have this inventory block that shows a GUI for its inventory when right-clicked. For now, I'm essentially copying the UI of a hopper, since it needs 5 slots anyway. It all works fine if I click an item, then click again to place it in the block's inventory. But if I shift-click an item in the UI, either in the block's inventory or my own, the game crashes. I get a stack overflow that shows an infinite loop of calls to Container#retrySlotClick.

     

    I tried following the code myself, but I got lost in the ambiguous variable names like slot6, itemstack11, etc.

     

    Here's the container class:

     

    package com.IceMetalPunk.breedingseason.gui;
    
    import net.minecraft.entity.player.EntityPlayer;
    import net.minecraft.entity.player.InventoryPlayer;
    import net.minecraft.inventory.Container;
    import net.minecraft.inventory.IInventory;
    import net.minecraft.inventory.Slot;
    
    public class ContainerStorageChest extends Container {
    
    public ContainerStorageChest(InventoryPlayer playerInv, IInventory blockInv) {
    	int i = 51;
    
    	// Block inventory
    	for (int j = 0; j < blockInv.getSizeInventory(); ++j) {
    		this.addSlotToContainer(new Slot(blockInv, j, 44 + j * 18, 20));
    	}
    
    	// Player main inventory
    	for (int l = 0; l < 3; ++l) {
    		for (int k = 0; k < 9; ++k) {
    			this.addSlotToContainer(new Slot(playerInv, k + l * 9 + 9, 8 + k * 18, l * 18 + i));
    		}
    	}
    
    	// Hotbar inventory
    	for (int i1 = 0; i1 < 9; ++i1) {
    		this.addSlotToContainer(new Slot(playerInv, i1, 8 + i1 * 18, 58 + i));
    	}
    }
    
    @Override
    public boolean canInteractWith(EntityPlayer playerIn) {
    	return true;
    }
    
    }
    

     

    Here's the GUI class:

     

    package com.IceMetalPunk.breedingseason.gui;
    
    import com.IceMetalPunk.breedingseason.BreedingSeason;
    
    import net.minecraft.client.gui.inventory.GuiContainer;
    import net.minecraft.client.renderer.GlStateManager;
    import net.minecraft.entity.player.InventoryPlayer;
    import net.minecraft.inventory.IInventory;
    import net.minecraft.util.ResourceLocation;
    
    public class GuiStorageChest extends GuiContainer {
    
    private ResourceLocation storageChestGUI = new ResourceLocation(
    		BreedingSeason.MODID + ":textures/gui/container/storage_chest.png");
    private IInventory tileEntity = null;
    private InventoryPlayer playerInventory = null;
    
    public GuiStorageChest(InventoryPlayer playerInv, IInventory blockInv) {
    	super(new ContainerStorageChest(playerInv, blockInv));
    	this.tileEntity = blockInv;
    	this.playerInventory = playerInv;
    }
    
    @Override
    protected void drawGuiContainerBackgroundLayer(float partialTicks, int mouseX, int mouseY) {
    	GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F);
    	mc.getTextureManager().bindTexture(storageChestGUI);
    	int marginHorizontal = (width - xSize) / 2;
    	int marginVertical = (height - ySize) / 2;
    	drawTexturedModalRect(marginHorizontal, marginVertical, 0, 0, xSize, ySize);
    }
    
    @Override
    protected void drawGuiContainerForegroundLayer(int mouseX, int mouseY) {
    
    	this.fontRendererObj.drawString(this.tileEntity.getDisplayName().getUnformattedText(), 8, 6, 4210752);
    	this.fontRendererObj.drawString(this.playerInventory.getDisplayName().getUnformattedText(), 8,
    			this.ySize - 96 + 2, 4210752);
    }
    
    }
    

     

    And here's the tile entity class:

     

    package com.IceMetalPunk.breedingseason.tileentities;
    
    import net.minecraft.entity.player.EntityPlayer;
    import net.minecraft.inventory.IInventory;
    import net.minecraft.item.ItemStack;
    import net.minecraft.tileentity.TileEntity;
    import net.minecraft.util.text.ITextComponent;
    import net.minecraft.util.text.TextComponentString;
    import net.minecraft.util.text.TextComponentTranslation;
    
    public class TileEntityStorageChest extends TileEntity implements IInventory {
    
    private String customName = null;
    private ItemStack[] slots = new ItemStack[5];
    
    @Override
    public String getName() {
    	return this.hasCustomName() ? this.customName : "container.storage_chest";
    }
    
    @Override
    public boolean hasCustomName() {
    	return this.customName != null && this.customName.length() > 0;
    }
    
    public void setCustomInventoryName(String name) {
    	this.customName = name;
    }
    
    @Override
    public ITextComponent getDisplayName() {
    	return this.hasCustomName() ? new TextComponentString(this.getName())
    			: new TextComponentTranslation(this.getName());
    }
    
    @Override
    public int getSizeInventory() {
    	return slots.length;
    }
    
    @Override
    public ItemStack getStackInSlot(int index) {
    	if (index < 0 || index >= this.getSizeInventory()) {
    		return null;
    	} else {
    		return this.slots[index];
    	}
    }
    
    @Override
    public ItemStack decrStackSize(int index, int count) {
    	ItemStack previous = this.getStackInSlot(index);
    	if (previous == null) {
    		return null;
    	} else if (previous.stackSize <= count) {
    		this.setInventorySlotContents(index, null);
    		return previous;
    	} else {
    		ItemStack split = previous.splitStack(count);
    		if (previous.stackSize <= 0) {
    			this.setInventorySlotContents(index, null);
    		}
    		return split;
    	}
    }
    
    @Override
    public ItemStack removeStackFromSlot(int index) {
    	ItemStack previous = this.getStackInSlot(index);
    	this.setInventorySlotContents(index, null);
    	return previous;
    }
    
    @Override
    public void setInventorySlotContents(int index, ItemStack stack) {
    	if (index >= 0 && index < this.getSizeInventory()) {
    		this.slots[index] = stack;
    	}
    }
    
    @Override
    public int getInventoryStackLimit() {
    	return 64; // TODO: Increase limit if possible to 2048 (32 stacks)
    }
    
    @Override
    public boolean isUseableByPlayer(EntityPlayer player) {
    	return true;
    }
    
    @Override
    public void openInventory(EntityPlayer player) {
    }
    
    @Override
    public void closeInventory(EntityPlayer player) {
    }
    
    @Override
    public boolean isItemValidForSlot(int index, ItemStack stack) {
    	return true;
    }
    
    @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() {
    	for (int i = 0; i < this.slots.length; ++i) {
    		this.removeStackFromSlot(i);
    	}
    }
    
    }
    

     

    Can anyone help me get this working so that shift+clicking doesn't...you know...crash the game? Thanks.

  7. Don't use BlockContainer, simply override createTileEntity and hasTileEntity from the Block class.

     

    If you look into the BlockContainer class, you'll see it does a lot with the rendering, and is simply a pain in the proverbial behind.

     

    Thank you! This worked and fixed the problem! :D I was in the habit of using BlockContainer for any block with an inventory...I will not be doing that anymore xD Thanks again!

  8. Sorry for missing it in the OP, but yes, I do have the following blockstate file in the blockstates folder (storage_chest.json):

     

    {
        "variants": {
            "facing=north": { "model": "breedingseason:storage_chest" },
            "facing=south": { "model": "breedingseason:storage_chest", "y": 180 },
            "facing=west":  { "model": "breedingseason:storage_chest", "y": 270 },
            "facing=east":  { "model": "breedingseason:storage_chest", "y": 90 }
        }
    }

  9. Until now, I've only modded for 1.7.10. I thought I should get myself up to date and make my next mod for 1.9; unfortunately, the new block handling system is getting the better of me.

     

    I have a block class, and in the game, the block item renders just fine. However, when placed in the world, the block doesn't render at all, instead simply culling the adjacent blocks' faces leaving a "hole" in the world where the block should be.

     

    Here's the block's model JSON file:

     

    {
        "parent": "block/orientable",
        "textures": 
        {
            "top": "breedingseason:blocks/storage_chest_top",
            "front": "breedingseason:blocks/storage_chest_front",
            "side": "breedingseason:blocks/storage_chest_side"
        }
    }

     

    The textures are basic square PNGs like all Minecraft textures.

     

    I have this "block instantiation helper" class:

     

    package com.IceMetalPunk.breedingseason.blocks;
    
    public class BreedingSeasonBlocks {
    public static final BlockStorageChest StorageChest = new BlockStorageChest();
    }
    

     

    And here's the main class:

     

    package com.IceMetalPunk.breedingseason;
    
    import com.IceMetalPunk.breedingseason.blocks.BreedingSeasonBlocks;
    import com.IceMetalPunk.breedingseason.handlers.BreedingEventHandler;
    import com.IceMetalPunk.breedingseason.items.BreedingSeasonItems;
    
    import net.minecraft.client.Minecraft;
    import net.minecraft.client.renderer.RenderItem;
    import net.minecraft.client.renderer.block.model.ModelResourceLocation;
    import net.minecraftforge.client.model.ModelLoader;
    import net.minecraftforge.common.MinecraftForge;
    import net.minecraftforge.fml.common.Mod;
    import net.minecraftforge.fml.common.Mod.EventHandler;
    import net.minecraftforge.fml.common.event.FMLInitializationEvent;
    import net.minecraftforge.fml.common.registry.GameRegistry;
    
    @Mod(modid = BreedingSeason.MODID, version = BreedingSeason.VERSION)
    public class BreedingSeason {
    public static final String MODID = "breedingseason";
    public static final String VERSION = "1.0";
    
    public static final BreedingSeason instance = new BreedingSeason();
    
    public static final BreedingEventHandler eventListener = new BreedingEventHandler(MinecraftForge.EVENT_BUS);
    public static final BreedingSeasonBlocks blocks = new BreedingSeasonBlocks();
    public static final BreedingSeasonItems items = new BreedingSeasonItems();
    
    public enum GUI_ENUM {
    	STORAGE_CHEST
    }
    
    @EventHandler
    public void init(FMLInitializationEvent event) {
    	GameRegistry.register(blocks.StorageChest);
    	GameRegistry.register(items.StorageChest);
    
    	RenderItem renderItem = Minecraft.getMinecraft().getRenderItem();
    
    	ModelResourceLocation modelLocation = new ModelResourceLocation(
    			BreedingSeason.MODID + ":" + blocks.StorageChest.getUnlocalizedName().substring(5), "inventory");
    	renderItem.getItemModelMesher().register(items.StorageChest, 0, modelLocation);
    	// FIXME: Chest block is not rendering, while item is.
    	ModelLoader.setCustomModelResourceLocation(items.StorageChest, 0, modelLocation);
    }
    }
    

     

    And lastly, this is the block class itself; keep in mind I basically followed a tutorial for 1.8 and tried fixing the bits that were out of date. I get no errors, so I thought I did it right, but of course it also doesn't render, so...yeah.

     

    package com.IceMetalPunk.breedingseason.blocks;
    
    import java.util.Random;
    
    import com.IceMetalPunk.breedingseason.BreedingSeason;
    import com.IceMetalPunk.breedingseason.tileentities.TileEntityStorageChest;
    import com.sun.istack.internal.Nullable;
    
    import net.minecraft.block.BlockContainer;
    import net.minecraft.block.material.Material;
    import net.minecraft.block.properties.IProperty;
    import net.minecraft.block.properties.PropertyDirection;
    import net.minecraft.block.state.BlockStateContainer;
    import net.minecraft.block.state.IBlockState;
    import net.minecraft.creativetab.CreativeTabs;
    import net.minecraft.entity.EntityLivingBase;
    import net.minecraft.entity.player.EntityPlayer;
    import net.minecraft.inventory.InventoryHelper;
    import net.minecraft.item.Item;
    import net.minecraft.item.ItemStack;
    import net.minecraft.tileentity.TileEntity;
    import net.minecraft.util.EnumFacing;
    import net.minecraft.util.EnumHand;
    import net.minecraft.util.ResourceLocation;
    import net.minecraft.util.math.BlockPos;
    import net.minecraft.world.World;
    import net.minecraftforge.fml.relauncher.Side;
    import net.minecraftforge.fml.relauncher.SideOnly;
    
    public class BlockStorageChest extends BlockContainer {
    
    public static final PropertyDirection FACING = PropertyDirection.create("facing", EnumFacing.Plane.HORIZONTAL);
    
    protected BlockStorageChest() {
    	super(Material.IRON);
    	this.setUnlocalizedName("storage_chest");
    	this.setDefaultState(blockState.getBaseState().withProperty(FACING, EnumFacing.NORTH));
    	this.setCreativeTab(CreativeTabs.DECORATIONS); // TODO: Add creative tab
    										// for the mod
    	this.setRegistryName(new ResourceLocation(BreedingSeason.MODID, "storage_chest"));
    }
    
    @Override
    public Item getItemDropped(IBlockState state, Random rand, int fortune) {
    	return Item.getItemFromBlock(BreedingSeasonBlocks.StorageChest);
    }
    
    @Override
    public void onBlockAdded(World world, BlockPos pos, IBlockState state) {
    	if (!world.isRemote) {
    		// Rotate block if the front side is blocked
    		IBlockState blockToNorth = world.getBlockState(pos.offset(EnumFacing.NORTH));
    		IBlockState blockToSouth = world.getBlockState(pos.offset(EnumFacing.SOUTH));
    		IBlockState blockToEast = world.getBlockState(pos.offset(EnumFacing.EAST));
    		IBlockState blockToWest = world.getBlockState(pos.offset(EnumFacing.WEST));
    		EnumFacing enumfacing = (EnumFacing) state.getValue(FACING);
    
    		if (enumfacing == EnumFacing.NORTH && blockToNorth.isFullBlock() && !blockToSouth.isFullBlock()) {
    			enumfacing = EnumFacing.SOUTH;
    		} else if (enumfacing == EnumFacing.SOUTH && blockToSouth.isFullBlock() && !blockToNorth.isFullBlock()) {
    			enumfacing = EnumFacing.NORTH;
    		} else if (enumfacing == EnumFacing.WEST && blockToWest.isFullBlock() && !blockToEast.isFullBlock()) {
    			enumfacing = EnumFacing.EAST;
    		} else if (enumfacing == EnumFacing.EAST && blockToEast.isFullBlock() && !blockToWest.isFullBlock()) {
    			enumfacing = EnumFacing.WEST;
    		}
    
    		world.setBlockState(pos, state.withProperty(FACING, enumfacing), 2);
    	}
    }
    
    @Override
    public boolean onBlockActivated(World world, BlockPos pos, IBlockState state, EntityPlayer player, EnumHand hand,
    		@Nullable ItemStack item, EnumFacing side, float x, float y, float z) {
    	if (!world.isRemote) {
    		player.openGui(BreedingSeason.instance, BreedingSeason.GUI_ENUM.STORAGE_CHEST.ordinal(), world, pos.getX(),
    				pos.getY(), pos.getZ());
    	}
    
    	return true;
    }
    
    @Override
    public TileEntity createNewTileEntity(World worldIn, int meta) {
    	return new TileEntityStorageChest();
    }
    
    @Override
    public IBlockState onBlockPlaced(World worldIn, BlockPos pos, EnumFacing facing, float hitX, float hitY, float hitZ,
    		int meta, EntityLivingBase placer) {
    	return getDefaultState().withProperty(FACING, placer.getAdjustedHorizontalFacing().getOpposite());
    }
    
    @Override
    public void onBlockPlacedBy(World worldIn, BlockPos pos, IBlockState state, EntityLivingBase placer,
    		ItemStack stack) {
    	worldIn.setBlockState(pos, state.withProperty(FACING, placer.getAdjustedHorizontalFacing().getOpposite()), 2);
    }
    
    @Override
    public void breakBlock(World worldIn, BlockPos pos, IBlockState state) {
    
    	TileEntity tileentity = worldIn.getTileEntity(pos);
    
    	if (tileentity instanceof TileEntityStorageChest) {
    		InventoryHelper.dropInventoryItems(worldIn, pos, (TileEntityStorageChest) tileentity);
    		worldIn.updateComparatorOutputLevel(pos, this);
    	}
    	super.breakBlock(worldIn, pos, state);
    }
    
    @Override
    @SideOnly(Side.CLIENT)
    public ItemStack getItem(World worldIn, BlockPos pos, IBlockState state) {
    	return new ItemStack(Item.getItemFromBlock(BreedingSeasonBlocks.StorageChest));
    }
    
    @Override
    public IBlockState getStateFromMeta(int meta) {
    	EnumFacing enumfacing = EnumFacing.getFront(meta);
    
    	if (enumfacing.getAxis() == EnumFacing.Axis.Y) {
    		enumfacing = EnumFacing.NORTH;
    	}
    
    	return getDefaultState().withProperty(FACING, enumfacing);
    }
    
    @Override
    public int getMetaFromState(IBlockState state) {
    	return ((EnumFacing) state.getValue(FACING)).getIndex();
    }
    
    @Override
    protected BlockStateContainer createBlockState() {
    	return new BlockStateContainer(this, new IProperty[] { FACING });
    }
    
    @SideOnly(Side.CLIENT)
    static final class SwitchEnumFacing {
    	static final int[] enumFacingArray = new int[EnumFacing.values().length];
    
    	static {
    		try {
    			enumFacingArray[EnumFacing.WEST.ordinal()] = 1;
    		} catch (NoSuchFieldError var4) {
    			;
    		}
    
    		try {
    			enumFacingArray[EnumFacing.EAST.ordinal()] = 2;
    		} catch (NoSuchFieldError var3) {
    			;
    		}
    
    		try {
    			enumFacingArray[EnumFacing.NORTH.ordinal()] = 3;
    		} catch (NoSuchFieldError var2) {
    			;
    		}
    
    		try {
    			enumFacingArray[EnumFacing.SOUTH.ordinal()] = 4;
    		} catch (NoSuchFieldError var1) {
    			;
    		}
    	}
    }
    }
    

     

    What I don't understand is this: if the item model is just parented to the block model, then how come the item model renders fine but the block model doesn't?

  10. It's breeding season! In Minecraft, that is.

     

    Many tech mods add fancy ways of automatically breeding up those animals which can be bred. But I wanted something more subtle, something more vanilla-feeling, for the same task. And so I created Breeding Season.

     

    Download: https://dl.dropboxusercontent.com/u/11662651/Minecraft/Mods/BreedingSeason.zip

     

    Breeding Season makes a few minor changes to breedable mobs' behaviors:

     

    1) They will eat and breed from the correct item entities on the ground now. For example, a horse will breed from a dropped golden carrot or golden apple, a cow or sheep will breed from a dropped wheat item, etc.

     

    2) For those animals who breed with items which can be blocks (i.e. crops or flowers), they will break those blocks when walking over them and begin breeding that way as well. NOTE: If you don't want this, perhaps because of the broken blocks, you can turn off the mobGriefing gamerule to stop this behavior.

     

    3) Baby animals are now afraid of the dark. This means that if they can get to a brighter spot within 5 blocks of their current position, they'll run there, even ignoring their parents or owners (baby pets will still teleport to their owners when out of range, but they won't walk into darker areas). This allows you to easily separate adults from babies while breeding: as long as the adult pen is darker than the baby pen, they'll separate themselves.

     

    Some notes: All breeding requirements still apply. This means dogs still won't breed unless they're tamed and at full health, etc. When an animal is in breeding mode ("in love"), it won't consume any more items or blocks until it's done breeding and ready to begin again; so if you drop a stack of wheat by a cow, for instance, it'll only eat one and not the whole stack.

     

    Of course, since droppers can drop those items on a clock, this means breeding can now be fully automated using plain old redstone! The design for such a farm, including how to control lighting for baby separation, is up to you :)

     

    Download: https://dl.dropboxusercontent.com/u/11662651/Minecraft/Mods/BreedingSeason.zip

     

    If you have any ideas for things to add to future versions of this mod, please suggest them! I hit a creative block trying to come up with more; keep in mind it should be vanilla-styled but still enhancing the process of breeding.

     

    And of course, all comments are welcome below! :)

     

    (And lastly, this won't work with other mods' mobs, only vanilla ones. I may expose a few methods to make it mod-compatible in the future, if it's popular enough.)

    • Like 1
  11. Not all potionIDs are valid. You need to check if there is a potion with that ID before you use it. What I mean is: Potion.potionTypes it not completely filled with potions, there are null entries for IDs that do not exist.

     

    >_< D'oh! That seems to have solved the problem. Apparently, when it was crashing, it wasn't even outputting the null potion in the debug message first for some reason. I just added a check to return if pot==null, and it works. Go figure. Thank you!

     

    Also, some potion effects will be utterly harmless when you tick them once (poison, hunger, slowness, weakness, fatigue, wither) while others will be super deadly (instant damage).

     

    Yes, balancing will be done through a whitelist/blacklist of effects. I just haven't yet determined which effects should stay and which should go yet; that'll be future work. I'm probably going to end up using addPotionEffect instead after all, but I'm still glad I made this topic because without the explicit check for null potions, it'd still have had this same problem.

     

    Thanks, everyone! :)

  12. I have a fluid block that, when the player touches it, should give them random status effects until they stop touching it. However, upon colliding with the block, it instead crashes the game with a Null Pointer Error. Which I can't figure out, because if I comment out just the line that calls Potion#performEffect, there are no crashes or errors, but some debug output confirms that the potion instance and the player are both not null, so what could possibly be null?

     

    Here's the code:

     

    	public void onEntityCollidedWithBlock(World world, int x, int y, int z, Entity ent) {
    	if (world.isRemote || ent == null || !(ent instanceof EntityLivingBase)) {
    		return;
    	}
    	EntityLivingBase player = (EntityLivingBase) ent;
    	int potID = world.rand.nextInt(Potion.potionTypes.length);
    	Potion pot = Potion.potionTypes[potID];
    	System.out.println("Potion " + pot + ", Player " + player); // Debug output
    	pot.performEffect(player, 0);
    }

     

    If I comment out just that last line, the crash doesn't happen, but of course no status effects are applied. Just before the crash, it outputs the debug message showing that "pot" is an instance of Potion, and "player" is an instance of EntityPlayerMP. So where's the null value here?

     

    (Note: I'm not 100% sure what the second parameter to performEffect is; it looks like it's the level, so I put 0 for level 1, but I also tried with 40 on the guess it might actually be tick duration; that crashed just the same.)

  13. I'm working with custom fluids for the first time, following the tutorial found here: http://www.minecraftforge.net/wiki/Create_a_Fluid

     

    I realize it's a bit outdated since it's using Block#setUnlocalizedName, which no longer exists in 1.7.10, but I figured the differences were minor enough that I could change them as I ran into discrepancies. So I went to work following the tutorial, and I got to the part where it says "now you should be able to use the filled bucket to place your fluid in the world, but not pick it up"...and I can't place the fluid. Everything registers with no errors or warnings, the fluid bucket appears in the Creative menu like it should (barring the missing texture, which is fine for now), but when I right-click on a block with the bucket in hand, nothing happens. No fluid blocks placed, nothing.

     

    Here's the code so far:

     

    Chaotica.java (main class):

    /* CONCEPT:
    * You're controlling the innate probabilities of the universe, condensing probability and entropy itself into a form
    * that can be handled more accessibly. It's inherently improbable, so everything that uses it has a "chaotic risk",
    * a drawback that can be reduced with focusing "chaos core" upgrades.
    */
    
    /* TODO:
    * -Add Pure Chaos fluid
    * --> When placed in world, touching it gives you a random status effect
    * -Add Chaos Cores, generic machine upgrades
    * --> Efficiency core
    * --> Speed core
    * --> Accuracy core
    * --> Production core
    * --> Range core
    * -Add Chaotic Tank, a fluid storage container
    * --> Risk: if you try to give it more liquid than it can hold, it'll dump the excess in the world
    * --> Possible upgrades (right-click with a core on it; drops cores when broken):
    * ----> Speed core = can accept more fluid per tick (it's rate-limited)
    * ----> Production core = can hold more fluid
    * ----> Efficiency core = reduces (or removes, if not stackable) loss (there's some loss on incoming fluid)
    * ----> Accuracy core = reduces (or removes, if not stackable) chances of liquid dumps, opting for blocking instead
    * ----> Range core = N/A
    * ----> If upgrades are stackable, can apply multiple upgrades of a type to increase its number
    * -Add Fluid/Item Tubes, generic pipes
    * --> Attach to blocks like EnderIO conduits and can be set to push, pull, or push/pull
    * --> At the very least, fluid pipes are needed to move liquid chaos around
    * --> Possible upgrades
    * -Add Chaos Purifier that makes liquid pure chaos based on block changes
    * --> Track last 3 block changes, diminishing returns on same position or block IDs.
    * --> Occasionally places liquid chaos blocks in the world instead of the machine
    * ----> Checks for air at random spot in range. If not found, does a BFS until air is found and places there.
    * --> Possible upgrades:
    * ----> Speed core = faster checking for block changes
    * ----> Production core = more chaos per change
    * ----> Range core = detects changes in a larger radius
    * ----> Efficiency core = less diminishing returns
    * ----> Accuracy core = less chance of placing chaos outside machine
    * -Add a Chaotic Destroyer that uses liquid chaos as fuel to break blocks
    * --> Filter slot to specify only certain blocks to break
    * --> Occasionally will center break somewhere other than the block in front of it (a little farther away)
    * --> Possible upgrades:
    * ----> Speed core = lower cooldown between breaks
    * ----> Production core = very occasionally uses Fortune I, II, or III (if upgrades stack; else just Fortune I)
    * ----> Range core = breaks a 3x3 instead of 1x1 (or also 5x5 if stackable)
    * ----> Efficiency core = uses less chaos per break
    * ----> Accuracy core = keeps breaks centered on block in front of it
    * -Add a Chaotic Actualizer that uses liquid chaos as fuel to place blocks
    * --> Occasionally will place blocks somewhere other than right in front of it
    * --> Possible upgrades:
    * ----> Speed core = lower cooldown between placement attempts
    * ----> Production core = occasionally upgrades stone to ore and ore to better ore (only 1 level above; occasionally)
    * ----> Range core = can place 3x3 (or also 5x5 if stackable)
    * ----> Efficiency core = uses less chaos per placement
    * ----> Accuracy core = keeps block placements centered in front of it
    * -Add Chaotic Freezer that uses liquid chaos as fuel
    * --> Freezes nearby water to ice
    * --> Freezes nearby lava sources to obsidian and flowing lava to smoothstone
    * --> Occasionally turns random blocks in range to ice
    * --> Possible upgrades:
    * ----> Speed core = lower cooldown between freezes
    * ----> Production core = occasionally turns water into packed ice and flowing lava into obsidian
    * ----> Range core = increase range of effect
    * ----> Efficiency core = uses less chaos per freeze
    * ----> Accuracy core = lowers chance of turning regular blocks to ice (or, if not stackable, prevents it altogether)
    * -Add Entropic Absorber that soaks up liquid chaos to power items
    * -Add Entropic Infuser to transfer liquid chaos into an absorber
    * -Add Chaos Staff for spells using liquid chaos from absorber
    * --> If possible, figure out the extra hotbar slot like 1.9, but not in 1.9...
    * --> Otherwise, Shift+Right Click to set spell, right click to use
    * --> Ender pearl = teleport short distances, through walls, in direction you're looking
    * --> Blaze rod = set things around you on fire, but not you
    * --> Ice = Inflict massive slowness on mobs around you
    * --> Slime block = disarms mobs around you
    * --> Feather = gust of wind blows mobs up high (~7 blocks) and drops them
    * --> Spider eye = revealing = glow effect (only if can reproduce it)
    * -Add Ender Diamonds, which can't be obtained naturally (only by Productive Chaotic Hubs; see below)
    * --> Used in some recipes
    * -Add a Chaotic Hub block that can be placed anywhere
    * --> When you use the Chaotic Transporter, you can link it to a hub; you teleport within a range of it
    * --> Has a cooldown time after teleporting where you are paralyzed
    * --> Possible upgrades:
    * ----> Speed core = lower paralyzation time after teleporting
    * ----> Production core = occasionally drops an Ender Diamond upon teleporting
    * ----> Efficiency core = uses less chaos on your transporter (i.e. gives you back a bit after)
    * ----> Range core = lets you teleport from farther away (initially, must be within 100 blocks)
    * ----> Accuracy core = reduces (or removes) random range of destination
    * -Add a Chaotic Transporter that uses liquid chaos from absorber to teleport you to a hub
    * --> Level 2 can go across dimensions
    * -Add an Entropy Analyzer that you right-click on mobs to store their type
    * -Add a Chaotic Spawner
    * --> Takes Analyzer to set mob type and liquid chaos to spawn mobs
    * --> Has a chance of spawning a different, random mob
    * --> Possible upgrades:
    * ----> Speed core = faster spawning (lower cooldown)
    * ----> Production core = more mobs spawned each time
    * ----> Efficiency core = less chaos used per spawn
    * ----> Range core = smaller radius of spawning (if stackable, fully upgraded = spawns only in block above it)
    * ----> Accuracy core = less chance of spawning wrong mob (if not stackable, prevents it altogether)
    * -Add a Chaotic Transporter Pedestal
    * --> Right-click empty pedestal with Chaotic Transporter to put it inside
    * --> Right-click filled pedestal to activate the contained Transporter
    * --> Shift+Right-click filled pedestal to remove the contained Transporter
    * --> Can accept liquid chaos to use instead of requiring the Transporter to have any--does NOT charge it!
    * -Add Chaotic Levitator boots that use liquid chaos to give you jetpack-like flight
    * --> Can only reach a certain height; you can't rise forever
    * --> Randomly stop working for a small period of time, possibly dropping you out of the sky
    * --> Possible upgrades (combine with one in a crafting grid):
    * ----> Speed core = you rise faster
    * ----> Efficiency core = uses less chaos per tick while rising
    * ----> Range core = lets you rise higher
    * ----> Production core = (N/A)
    * ----> Accuracy core = less chance (or no chance, if not stackable) of shutting off randomly
    * -Add Chaotic Grinder, a mob grinder that uses chaos for fuel
    * --> Occasionally will deal damage to every player on the server, no matter where they are
    * --> Possible upgrades:
    * ----> Speed core = kills mobs faster (lower cooldown)
    * ----> Efficiency core = uses less chaos per kill
    * ----> Range core = kills in a larger range
    * ----> Production core = occasionally uses Looting I (or Looting II, III, etc. if stackable)
    * ----> Accuracy core = less chance (or no chance, if not stackable) of hurting players
    * -Add a Chaotic Yielder that uses chaos as fuel to harvest crops
    * --> Occasionally will grow a large, artificial tree within a radius outside its range, replacing blocks
    * --> Possible upgrades:
    * ----> Speed core = faster harvesting (lower cooldown between block breaks)
    * ----> Efficiency core = uses less chaos per harvest
    * ----> Range core = harvests in a larger range
    * ----> Accuracy core = lowers (or removes, if not stackable) chance of artificial tree growth
    * ----> Production core = occasionally uses Fortune I on harvests (or II, III, etc. if stackable)
    * -Add Chaos Root, a plant that causes Wither in a range and prevents other crops from growing in a range
    * --> Drops itself and can be planted
    * --> Withers you if you hold it, too
    * --> If planted where there's already crops in its range, they will reverse their growth until at stage 0
    * ----> Growth stage = metadata value
    * -Add a Chaotic Planter that uses chaos as fuel to plant crops
    * --> Occasionally plants, instead of your crops, Chaos Root
    * --> Requires Chaos Root as a crafting ingredient
    * --> Possible upgrades:
    * ----> Speed core = faster planting (lower cooldown between plants)
    * ----> Efficiency core = uses less chaos per plant
    * ----> Range core = plants in a larger range
    * ----> Production core = occasionally bonemeals a crop it's just planted
    * ----> Accuracy core = lowers (or removes, if not stackable) chances of Chaos Root exceptions
    * -Add a Chaotic Dispeller, which uses chaos to teleport non-player mobs out of its radius
    * --> Occasionally spawns an Endermite or Silverfish
    * --> Possible upgrades:
    * ----> Speed core = faster response (lower delay between mob entering range and being teleported away)
    * ----> Efficiency core = uses less chaos per teleportation
    * ----> Range core = teleports mobs out of a larger radius
    * ----> Accuracy core = lowers (or removes, if not stackable) chances of Endermite/Silverfish spawns
    * ----> Production core = will only teleport hostile and neutral mobs, not passive ones
    * -Add a Chaotic Lure, which uses chaos to attract nearby mobs to it
    * --> Occasionally "lets go", letting all the attracted mobs free for a cooldown period, which means a group of mobs roaming around
    * --> Possible upgrades:
    * ----> Speed core = faster response (lower delay between mob entering range and being attracted)
    * ----> Range core = attracts from larger radius
    * ----> Efficiency core = uses less chaos per attracted mob per tick
    * ----> Accuracy core = can now accept an Entropy Analyzer and will only attract that one mob type
    * ----> Production core = occasionally spawns desired mob type (if no analyzer is in it, occasionally spawns a mob at random)
    * -Add a Chaotic XP Bank, which lets you store and retrieve XP using chaos as fuel
    * --> Occasionally ejects some XP as orbs
    * --> Possible upgrades:
    * ----> Speed core = transactions are faster (storing and retrieving increase/decrease per tick, not instantly)
    * ----> Efficiency core = uses less chaos per transaction
    * ----> Range core = N/A
    * ----> Accuracy core = lowers (or removes) chance of ejecting XP orbs
    * ----> Production core = occasionally stores more than 1 XP per XP stored
    * -Add Corrosive Chaos liquid
    * --> When placed in world, will slowly eat away adjacent non-tile-entity blocks based on hardness
    * --> Touching it causes severe burns, i.e. massive fire damage
    * ----> Enough to destroy items instantly, like lava
    * -Add a Novice Chaos Purifier, the first-level purifier which actually creates Corrosive Chaos liquid instead
    * --> Is used in recipe to upgrade to the normal Chaos Purifier
    * --> Has the same upgrade possibilities as the Chaos Purifier
    * -Add a Chaotic Collector, which collects drops from corroded blocks in range "before" they're destroyed
    * --> Powered by Pure Chaos
    * --> Chaotic risk: occasionally duplicates Corrosive Chaos sources in range to another random block
    * --> Possible upgrades:
    * ----> Speed core = causes Corrosive Chaos blocks in range to eat blocks faster
    * ----> Efficiency core = uses less Pure Chaos per collection
    * ----> Range core = increases range of collection and modification
    * ----> Accuracy core = lowers (or removes) chance of randomly duplicating Corrosive Chaos sources
    * ----> Production core = occasionally gets Fortune-style drops from the blocks
    * -Add a guide book
    * --> Tutorial is bookmarked in Firefox
    * --> fontRendererObj is now mc.fontRenderer
    * --> drawSplitString is for word-wrapped text; args = (str, x, y, max_len, color)
    */
    
    package com.IceMetalPunk.chaotica;
    
    import com.IceMetalPunk.chaotica.blocks.ChaoticaBlocks;
    import com.IceMetalPunk.chaotica.items.ChaoticaItems;
    
    import cpw.mods.fml.common.Mod;
    import cpw.mods.fml.common.Mod.EventHandler;
    import cpw.mods.fml.common.event.FMLInitializationEvent;
    
    @Mod(modid = Chaotica.MODID, version = Chaotica.VERSION)
    public class Chaotica {
    public static final String MODID = "chaotica";
    public static final String VERSION = "1.0";
    
    public static ChaoticaBlocks blockRegistry;
    public static ChaoticaItems itemRegistry;
    
    @EventHandler
    public void init(FMLInitializationEvent event) {
    	blockRegistry = new ChaoticaBlocks();
    	itemRegistry = new ChaoticaItems();
    }
    }
    

     

    ChaoticaBlocks.java (block registry class):

    package com.IceMetalPunk.chaotica.blocks;
    
    import net.minecraft.block.Block;
    import net.minecraft.block.material.Material;
    import net.minecraftforge.fluids.Fluid;
    import net.minecraftforge.fluids.FluidRegistry;
    
    public class ChaoticaBlocks {
    public Fluid pureChaos, corrosiveChaos;
    public Block pureChaosBlock, corrosiveChaosBlock;
    
    public ChaoticaBlocks() {
    	registerFluids();
    	registerBlocks();
    }
    
    private void registerFluids() {
    	this.pureChaos = new Fluid("purechaos").setViscosity(4000);
    	FluidRegistry.registerFluid(this.pureChaos);
    
    	this.corrosiveChaos = new Fluid("corrosivechaos").setViscosity(4000);
    	FluidRegistry.registerFluid(this.corrosiveChaos);
    }
    
    private void registerBlocks() {
    	this.pureChaosBlock = new BlockPureChaos(this.pureChaos, Material.water).setBlockName("tile.purechaos");
    }
    }
    

     

    BlockPureChaos.java (fluid source block):

    package com.IceMetalPunk.chaotica.blocks;
    
    import cpw.mods.fml.relauncher.Side;
    import cpw.mods.fml.relauncher.SideOnly;
    import net.minecraft.block.material.Material;
    import net.minecraft.client.renderer.texture.IIconRegister;
    import net.minecraft.entity.Entity;
    import net.minecraft.entity.EntityLivingBase;
    import net.minecraft.potion.Potion;
    import net.minecraft.util.IIcon;
    import net.minecraft.world.IBlockAccess;
    import net.minecraft.world.World;
    import net.minecraftforge.fluids.BlockFluidClassic;
    import net.minecraftforge.fluids.Fluid;
    
    public class BlockPureChaos extends BlockFluidClassic {
    
    @SideOnly(Side.CLIENT)
    protected IIcon stillIcon;
    @SideOnly(Side.CLIENT)
    protected IIcon flowingIcon;
    
    public BlockPureChaos(Fluid fluid, Material material) {
    	super(fluid, material);
    }
    
    @Override
    public void onEntityCollidedWithBlock(World world, int x, int y, int z, Entity ent) {
    	if (!(ent instanceof EntityLivingBase)) {
    		return;
    	}
    	EntityLivingBase player = (EntityLivingBase) ent;
    	int pot = world.rand.nextInt(Potion.potionTypes.length);
    	Potion.potionTypes[pot].performEffect(player, 40);
    }
    
    @Override
    public IIcon getIcon(int side, int meta) {
    	return (side == 0 || side == 1) ? stillIcon : flowingIcon;
    }
    
    @SideOnly(Side.CLIENT)
    @Override
    public void registerBlockIcons(IIconRegister register) {
    	stillIcon = register.registerIcon("chaotica:pureChaosStill");
    	flowingIcon = register.registerIcon("chaotica:pureChaosFlowing");
    }
    
    @Override
    public boolean canDisplace(IBlockAccess world, int x, int y, int z) {
    	if (world.getBlock(x, y, z).getMaterial().isLiquid()) {
    		return false;
    	}
    	return super.canDisplace(world, x, y, z);
    }
    
    @Override
    public boolean displaceIfPossible(World world, int x, int y, int z) {
    	if (world.getBlock(x, y, z).getMaterial().isLiquid()) {
    		return false;
    	}
    	return super.displaceIfPossible(world, x, y, z);
    }
    
    }
    

     

    ChaoticaItems.java (item registry class):

    package com.IceMetalPunk.chaotica.items;
    
    import com.IceMetalPunk.chaotica.Chaotica;
    
    import cpw.mods.fml.common.registry.GameRegistry;
    import net.minecraft.creativetab.CreativeTabs;
    import net.minecraft.init.Items;
    import net.minecraft.item.ItemStack;
    import net.minecraftforge.fluids.FluidContainerRegistry;
    
    public class ChaoticaItems {
    public ItemPureChaosBucket pureChaosBucket;
    
    public ChaoticaItems() {
    	this.pureChaosBucket = (ItemPureChaosBucket) new ItemPureChaosBucket()
    			.setUnlocalizedName("item.pureChaosBucket").setContainerItem(Items.bucket)
    			.setCreativeTab(CreativeTabs.tabMisc);
    
    	registerContainers();
    }
    
    private void registerContainers() {
    	GameRegistry.registerItem(this.pureChaosBucket, "pureChaosBucket");
    	FluidContainerRegistry.registerFluidContainer(Chaotica.blockRegistry.pureChaos,
    			new ItemStack(this.pureChaosBucket), new ItemStack(Items.bucket));
    }
    }
    

     

    ItemPureChaosBucket.java (fluid container):

    package com.IceMetalPunk.chaotica.items;
    
    import com.IceMetalPunk.chaotica.Chaotica;
    
    import net.minecraft.item.ItemBucket;
    
    public class ItemPureChaosBucket extends ItemBucket {
    
    public ItemPureChaosBucket() {
    	super(Chaotica.blockRegistry.pureChaosBlock);
    }
    
    }
    

     

    Looking through the code, the inherited right click method from ItemBucket seems like it should place the source block just fine, and the tutorial says it should as well, yet...nothing happens. No source block of any kind is created, no errors or warnings are shown...I'm at a loss.

     

    What's going wrong here?

     

    *EDIT* Wait, wait...I may have made a *very* stupid mistake here. Before you facepalm and comment, give me a couple minutes to test >_<

     

    *EDIT 2* Yep...yep...I was an idiot. For those wondering, I never actually registered the source block with the GameRegistry...I'mma pretend this never happened...

  14. So you have a method available that runs every tick, but you subscribe to a TickEvent and do it in there... Seems a bit illogical to me, or not?

     

    You only want your mana to run down when you have three pieces of armor equipped. So in the

    onArmorTick 

    method, check if the player has each of those three armor pieces equipped. You already have that. Now your issue is that the method is called 3 times? Maybe that's because you have 3 armor pieces equipped... So a simple if-statement to check if

    ItemStack#getItem() == Mod.YourItem

    should suffice. Why so complicated with tick events?

     

    because how you said before, the method is called 3 times for every armor piece, and making an if statement wouldn't solve this, because everytime when the method is called the if statement would be true and that means it would still be executed 3 times.

     

    No, it wouldn't. What he's saying is that if the player is wearing all three armor pieces, then you check if the currently-running code is from, say, just the helemet or just the chestpiece or whatever before consuming mana. This way, although the armor will check for all three pieces, only one of those pieces (whichever you test for) will actually consume the mana, not all three.

  15. My mod adds a new potion, but for reasons, the splash version doesn't extend EntityPotion; it just extends EntityThrowable and re-implements many of the methods that EntityPotion would. Almost everything works: the items render, the potion effect works, etc. The only thing that isn't working is that the thrown splash potion isn't rendering. It'll render as an item in my hand and inventory, and the particles render when it hits the ground, but while in the air, it's invisible.

     

    I'm piggybacking on RenderSnowball, like most throwable entities do, with this in my mod's main init() method:

     

    RenderingRegistry.registerEntityRenderingHandler(EntityPotionLifelock.class, new RenderSnowball(WeaponTweaks.itemSplashPotionLifelock));

     

    itemSplashPotionLifelock is an instance of ItemPotionLifelock (code below) with "splash" set to true:

     

    package com.IceMetalPunk.weapontweaks.items;
    
    import java.util.Iterator;
    import java.util.List;
    import java.util.Map;
    import java.util.Map.Entry;
    
    import com.IceMetalPunk.weapontweaks.WeaponTweaks;
    import com.IceMetalPunk.weapontweaks.entities.EntityPotionLifelock;
    import com.IceMetalPunk.weapontweaks.potions.PotionLifelock;
    import com.google.common.collect.HashMultimap;
    
    import cpw.mods.fml.relauncher.Side;
    import cpw.mods.fml.relauncher.SideOnly;
    import net.minecraft.client.renderer.texture.IIconRegister;
    import net.minecraft.creativetab.CreativeTabs;
    import net.minecraft.entity.ai.attributes.AttributeModifier;
    import net.minecraft.entity.ai.attributes.IAttribute;
    import net.minecraft.entity.player.EntityPlayer;
    import net.minecraft.init.Items;
    import net.minecraft.item.EnumAction;
    import net.minecraft.item.Item;
    import net.minecraft.item.ItemPotion;
    import net.minecraft.item.ItemStack;
    import net.minecraft.potion.Potion;
    import net.minecraft.potion.PotionEffect;
    import net.minecraft.util.EnumChatFormatting;
    import net.minecraft.util.IIcon;
    import net.minecraft.util.StatCollector;
    import net.minecraft.util.StringUtils;
    import net.minecraft.world.World;
    
    public class ItemPotionLifelock extends Item {
    
    public boolean isSplash;
    public IIcon icon, iconSplash, iconOverlay;
    public int level, duration;
    
    public ItemPotionLifelock(boolean splash) {
    	this.setMaxStackSize(1);
    	this.setMaxDamage(0);
    	this.setCreativeTab(CreativeTabs.tabBrewing);
    	this.isSplash = splash;
    	this.level = 0;
    	this.duration = 600;
    }
    
    public ItemPotionLifelock() {
    	this(false);
    }
    
    /* Override onEaten to make drinking this apply the appropriate effect */
    @Override
    public ItemStack onEaten(ItemStack item, World world, EntityPlayer player) {
    	if (!player.capabilities.isCreativeMode) {
    		--item.stackSize;
    	}
    
    	if (!world.isRemote) {
    		/* Arguments: potion_ID, duration, level, ambient */
    		player.addPotionEffect(new PotionEffect(WeaponTweaks.potionLifeLock.id, this.duration, this.level, false));
    	}
    
    	if (!player.capabilities.isCreativeMode) {
    		if (item.stackSize <= 0) {
    			return new ItemStack(Items.glass_bottle);
    		}
    
    		player.inventory.addItemStackToInventory(new ItemStack(Items.glass_bottle));
    	}
    
    	return item;
    }
    
    @Override
    public int getMaxItemUseDuration(ItemStack item) {
    	return 32;
    }
    
    @Override
    public EnumAction getItemUseAction(ItemStack item) {
    	return EnumAction.drink;
    }
    
    @Override
    public ItemStack onItemRightClick(ItemStack item, World world, EntityPlayer player) {
    	if (isSplash) {
    		if (!player.capabilities.isCreativeMode) {
    			--item.stackSize;
    		}
    
    		world.playSoundAtEntity(player, "random.bow", 0.5F, 0.4F / (itemRand.nextFloat() * 0.4F + 0.8F));
    
    		if (!world.isRemote) {
    			world.spawnEntityInWorld(new EntityPotionLifelock(world, player, item));
    		}
    
    		return item;
    	} else {
    		player.setItemInUse(item, this.getMaxItemUseDuration(item));
    		return item;
    	}
    }
    
    @Override
    public boolean onItemUse(ItemStack item, EntityPlayer player, World world, int x, int y, int z, int side, float px,
    		float py, float pz) {
    	return false;
    }
    
    @Override
    public IIcon getIconFromDamage(int damage) {
    	return (this.isSplash ? this.iconSplash : this.icon);
    }
    
    @SideOnly(Side.CLIENT)
    @Override
    public IIcon getIconFromDamageForRenderPass(int damage, int pass) {
    	return pass == 0 ? this.iconOverlay : super.getIconFromDamageForRenderPass(damage, pass);
    }
    
    @SideOnly(Side.CLIENT)
    public int getColorFromDamage(int damage) {
    	return PotionLifelock.liquidColor;
    }
    
    @SideOnly(Side.CLIENT)
    public int getColorFromItemStack(ItemStack item, int damage) {
    	return damage > 0 ? 16777215 : this.getColorFromDamage(item.getItemDamage());
    }
    
    @SideOnly(Side.CLIENT)
    @Override
    public boolean requiresMultipleRenderPasses() {
    	return true;
    }
    
    @Override
    public String getItemStackDisplayName(ItemStack item) {
    	String s = "";
    
    	if (this.isSplash) {
    		s = StatCollector.translateToLocal("potion.prefix.grenade").trim() + " ";
    	}
    
    	String s1 = "lifeLock.postfix";
    	return s + StatCollector.translateToLocal(s1).trim();
    
    }
    
    @Override
    @SideOnly(Side.CLIENT)
    public void addInformation(ItemStack item, EntityPlayer player, List textLines, boolean boolParam) {
    	HashMultimap hashmultimap = HashMultimap.create();
    	Iterator iterator1;
    
    	String s1 = StatCollector.translateToLocal("potion.lifeLock").trim();
    	Potion potion = WeaponTweaks.potionLifeLock;
    	Map map = potion.func_111186_k();
    
    	if (map != null && map.size() > 0) {
    		Iterator iterator = map.entrySet().iterator();
    
    		while (iterator.hasNext()) {
    			Entry entry = (Entry) iterator.next();
    			AttributeModifier attributemodifier = (AttributeModifier) entry.getValue();
    			AttributeModifier attributemodifier1 = new AttributeModifier(attributemodifier.getName(),
    					potion.func_111183_a(this.level, attributemodifier), attributemodifier.getOperation());
    			hashmultimap.put(((IAttribute) entry.getKey()).getAttributeUnlocalizedName(), attributemodifier1);
    		}
    	}
    
    	s1 += " " + StatCollector.translateToLocal("potion.potency." + this.level).trim();
    
    	s1 += " (" + StringUtils.ticksToElapsedTime(this.duration) + ")";
    
    	if (potion.isBadEffect()) {
    		textLines.add(EnumChatFormatting.RED + s1);
    	} else {
    		textLines.add(EnumChatFormatting.GRAY + s1);
    	}
    
    	if (!hashmultimap.isEmpty()) {
    		textLines.add("");
    		textLines.add(EnumChatFormatting.DARK_PURPLE + StatCollector.translateToLocal("potion.effects.whenDrank"));
    		iterator1 = hashmultimap.entries().iterator();
    
    		while (iterator1.hasNext()) {
    			Entry entry1 = (Entry) iterator1.next();
    			AttributeModifier attributemodifier2 = (AttributeModifier) entry1.getValue();
    			double d0 = attributemodifier2.getAmount();
    			double d1;
    
    			if (attributemodifier2.getOperation() != 1 && attributemodifier2.getOperation() != 2) {
    				d1 = attributemodifier2.getAmount();
    			} else {
    				d1 = attributemodifier2.getAmount() * 100.0D;
    			}
    
    			if (d0 > 0.0D) {
    				textLines
    						.add(EnumChatFormatting.BLUE + StatCollector.translateToLocalFormatted(
    								"attribute.modifier.plus." + attributemodifier2
    										.getOperation(),
    						new Object[] { ItemStack.field_111284_a.format(d1),
    								StatCollector.translateToLocal("attribute.name." + (String) entry1.getKey()) }));
    			} else if (d0 < 0.0D) {
    				d1 *= -1.0D;
    				textLines
    						.add(EnumChatFormatting.RED + StatCollector.translateToLocalFormatted(
    								"attribute.modifier.take." + attributemodifier2
    										.getOperation(),
    						new Object[] { ItemStack.field_111284_a.format(d1),
    								StatCollector.translateToLocal("attribute.name." + (String) entry1.getKey()) }));
    			}
    		}
    	}
    }
    
    public boolean hasEffect(int damage) {
    	return true;
    }
    
    @SideOnly(Side.CLIENT)
    @Override
    public void registerIcons(IIconRegister register) {
    	this.icon = ItemPotion.func_94589_d("bottle_drinkable");
    	this.iconSplash = ItemPotion.func_94589_d("bottle_splash");
    	this.iconOverlay = ItemPotion.func_94589_d("overlay");
    }
    
    }

     

    In the registerIcons method, I originally re-registered the icons, but after it wouldn't render, I thought maybe just grabbing a reference to the original potion icons would help (since there's that line in RenderSnowball that compares the icons directly instead of with .equals()), but it didn't fix the problem.

     

    And here's the EntityPotionLifelock class itself:

     

    package com.IceMetalPunk.weapontweaks.entities;
    
    import java.util.Iterator;
    import java.util.List;
    
    import com.IceMetalPunk.weapontweaks.WeaponTweaks;
    import com.IceMetalPunk.weapontweaks.items.ItemPotionLifelock;
    
    import net.minecraft.entity.EntityLivingBase;
    import net.minecraft.entity.projectile.EntityThrowable;
    import net.minecraft.item.ItemStack;
    import net.minecraft.potion.PotionEffect;
    import net.minecraft.util.AxisAlignedBB;
    import net.minecraft.util.MovingObjectPosition;
    import net.minecraft.world.World;
    
    public class EntityPotionLifelock extends EntityThrowable {
    
    public ItemStack stack;
    public int duration, level;
    
    public EntityPotionLifelock(World world, int duration, int level) {
    	super(world);
    	this.duration = duration;
    	this.level = level;
    }
    
    public EntityPotionLifelock(World world, EntityLivingBase player, ItemStack item) {
    	super(world, player);
    	this.stack = item;
    	this.duration = ((ItemPotionLifelock) item.getItem()).duration;
    	this.level = ((ItemPotionLifelock) item.getItem()).level;
    }
    
    @Override
    protected float getGravityVelocity() {
    	return 0.05F;
    }
    
    @Override
    protected float func_70182_d() {
    	return 0.5F;
    }
    
    @Override
    protected float func_70183_g() {
    	return -20.0F;
    }
    
    public int getPotionDamage() {
    	return 0;
    }
    
    @Override
    protected void onImpact(MovingObjectPosition raytrace) {
    	if (!this.worldObj.isRemote) {
    
    		AxisAlignedBB axisalignedbb = this.boundingBox.expand(4.0D, 2.0D, 4.0D);
    		List list1 = this.worldObj.getEntitiesWithinAABB(EntityLivingBase.class, axisalignedbb);
    
    		Iterator iterator = list1.iterator();
    
    		while (iterator.hasNext()) {
    			EntityLivingBase entitylivingbase = (EntityLivingBase) iterator.next();
    			double d0 = this.getDistanceSqToEntity(entitylivingbase);
    
    			if (d0 < 16.0D) {
    				double d1 = 1.0D - Math.sqrt(d0) / 4.0D;
    
    				if (entitylivingbase == raytrace.entityHit) {
    					d1 = 1.0D;
    				}
    
    				int j = (int) (d1 * (double) this.duration + 0.5D);
    
    				if (j > 20) {
    					entitylivingbase
    							.addPotionEffect(new PotionEffect(WeaponTweaks.potionLifeLock.id, j, this.level));
    				}
    			}
    		}
    
    		this.worldObj.playAuxSFX(2002, (int) Math.round(this.posX), (int) Math.round(this.posY),
    				(int) Math.round(this.posZ), this.getPotionDamage());
    		this.setDead();
    	}
    }
    
    }

     

    What am I doing wrong? As far as I understand it, the code should work, but it doesn't. I even tried copying the RenderSnowball code into a custom class and registering that instead, so I could add some debug output, and while the class was instantiated, its doRender() method was never called.

  16. I've now implemented it the way I mentioned, using extended properties to store and read the previous position and rotation values, and then updating the scoreboard when they change on a ServerTick event. But it turns out, that's even less efficient than I expected: I get 60fps without it running, and only about 30fps with it. It literally halves the FPS.

     

    There must be a better way, but I still haven't thought of one. Can anyone please help me brainstorm a more efficient solution to this?

     

    Below is my current code.

     

    RedPlusCommonEventHandler.java:

    package com.IceMetalPunk.redplus.handlers;
    
    import java.util.Arrays;
    import java.util.Collection;
    import java.util.Iterator;
    import java.util.List;
    
    import com.IceMetalPunk.redplus.RedPlus;
    import com.IceMetalPunk.redplus.properties.RedPlusEntityProperties;
    
    import cpw.mods.fml.common.eventhandler.SubscribeEvent;
    import cpw.mods.fml.common.gameevent.TickEvent;
    import net.minecraft.entity.Entity;
    import net.minecraft.scoreboard.ScoreObjective;
    import net.minecraft.server.MinecraftServer;
    import net.minecraft.world.World;
    
    public class RedPlusCommonEventHandler {
    @SubscribeEvent
    public void handleScoreboardUpdates(TickEvent.ServerTickEvent event) {
    	if (event.phase == TickEvent.Phase.END) {
    		return;
    	}
    
    	// Unnecessary overcautious prevention of possible null-pointer errors.
    	MinecraftServer mc = MinecraftServer.getServer();
    	if (mc == null) {
    		return;
    	}
    
    	World world = mc.getEntityWorld();
    	if (world == null) {
    		return;
    	}
    
    	List entities = world.loadedEntityList;
    
    	// Loop through all loaded entities
    	Entity entity = null;
    	for (Object entry : entities) {
    		entity = (Entity) entry;
    		world = entity.worldObj;
    
    		// Only run on server entities; probably also over-cautious since this is in the server tick event.
    		if (world.isRemote) {
    			continue;
    		}
    
    		// Get the extended properties of the entity.
    		RedPlusEntityProperties props = RedPlusEntityProperties.get(entity);
    		if (props == null) {
    			return;
    		}
    
    		// Update X-positions
    		Collection collection = world.getScoreboard().func_96520_a(RedPlus.scorePosX);
    		Iterator iterator = collection.iterator();
    
    		// Rotation pitch = y, yaw = x
    		if (props.lastX != entity.posX) {
    			while (iterator.hasNext()) {
    				ScoreObjective scoreobjective = (ScoreObjective) iterator.next();
    				world.getScoreboard().func_96529_a(entity.getCommandSenderName(), scoreobjective).func_96651_a(Arrays.asList(new Entity[] {
    						entity }));
    			}
    		}
    
    		// Update Y-positions
    		if (props.lastY != entity.posY) {
    			collection = world.getScoreboard().func_96520_a(RedPlus.scorePosY);
    			iterator = collection.iterator();
    
    			while (iterator.hasNext()) {
    				ScoreObjective scoreobjective = (ScoreObjective) iterator.next();
    				world.getScoreboard().func_96529_a(entity.getCommandSenderName(), scoreobjective).func_96651_a(Arrays.asList(new Entity[] {
    						entity }));
    			}
    		}
    
    		// Update Z-positions
    		if (props.lastZ != entity.posZ) {
    			collection = world.getScoreboard().func_96520_a(RedPlus.scorePosZ);
    			iterator = collection.iterator();
    			while (iterator.hasNext()) {
    				ScoreObjective scoreobjective = (ScoreObjective) iterator.next();
    				world.getScoreboard().func_96529_a(entity.getCommandSenderName(), scoreobjective).func_96651_a(Arrays.asList(new Entity[] {
    						entity }));
    			}
    		}
    
    		// Update yaw-rotations
    		if (props.lastYaw != entity.rotationYaw) {
    			collection = world.getScoreboard().func_96520_a(RedPlus.scoreRotationYaw);
    			iterator = collection.iterator();
    			while (iterator.hasNext()) {
    				ScoreObjective scoreobjective = (ScoreObjective) iterator.next();
    				world.getScoreboard().func_96529_a(entity.getCommandSenderName(), scoreobjective).func_96651_a(Arrays.asList(new Entity[] {
    						entity }));
    			}
    		}
    
    		// Update pitch-rotations
    		if (props.lastPitch != entity.rotationPitch) {
    			collection = world.getScoreboard().func_96520_a(RedPlus.scoreRotationPitch);
    			iterator = collection.iterator();
    			while (iterator.hasNext()) {
    				ScoreObjective scoreobjective = (ScoreObjective) iterator.next();
    				world.getScoreboard().func_96529_a(entity.getCommandSenderName(), scoreobjective).func_96651_a(Arrays.asList(new Entity[] {
    						entity }));
    			}
    		}
    
    		// Update the properties
    		props.update();
    	}
    }
    }

     

    RedPlusEntityProperties.java:

    package com.IceMetalPunk.redplus.properties;
    
    import net.minecraft.entity.Entity;
    import net.minecraft.nbt.NBTTagCompound;
    import net.minecraft.world.World;
    import net.minecraftforge.common.IExtendedEntityProperties;
    
    public class RedPlusEntityProperties implements IExtendedEntityProperties {
    
    public Entity myEntity;
    public double lastX, lastY, lastZ;
    public float lastPitch, lastYaw;
    
    public final static String EXT_PROP_NAME = "RedPlusProps";
    
    public RedPlusEntityProperties(Entity entity, World world) {
    	this.init(entity, world);
    }
    
    @Override
    public void saveNBTData(NBTTagCompound compound) {
    	compound.setDouble("lastX", this.myEntity.posX);
    	compound.setDouble("lastY", this.myEntity.posY);
    	compound.setDouble("lastZ", this.myEntity.posZ);
    	compound.setFloat("lastPitch", this.myEntity.rotationPitch);
    	compound.setFloat("lastYaw", this.myEntity.rotationYaw);
    }
    
    @Override
    public void loadNBTData(NBTTagCompound compound) {
    	this.lastX = compound.getDouble("lastX");
    	this.lastY = compound.getDouble("lasY");
    	this.lastZ = compound.getDouble("lastZ");
    	this.lastPitch = compound.getFloat("lastPitch");
    	this.lastYaw = compound.getFloat("lastYaw");
    }
    
    @Override
    public void init(Entity entity, World world) {
    	this.myEntity = entity;
    	this.lastX = entity.posX;
    	this.lastY = entity.posY;
    	this.lastZ = entity.posZ;
    	this.lastPitch = entity.rotationPitch;
    	this.lastYaw = entity.rotationYaw;
    }
    
    // Grab the extended properties for the given entity, aliased for shorter code elsewhere
    public static final RedPlusEntityProperties get(Entity entity) {
    	return (RedPlusEntityProperties) entity.getExtendedProperties(EXT_PROP_NAME);
    }
    
    // Update the "last" values to the entity's current values.
    public void update() {
    	this.lastX = this.myEntity.posX;
    	this.lastY = this.myEntity.posY;
    	this.lastZ = this.myEntity.posZ;
    	this.lastPitch = this.myEntity.rotationPitch;
    	this.lastYaw = this.myEntity.rotationYaw;
    }
    
    }

     

    (They're both instantiated, registered, and applied per standard use of extended properties and tick event listeners.)

  17. I've added three new scoreboard objectives (posX, posY, and posZ), which are supposed to track an entity's position. That works just fine. However, rather than update the score of every entity every tick with a tick handler, I'm hoping to do the updates more efficiently, updating only when the entity's position changes. I thought I'd be able to do this quite easily by having a LivingEntityUpdate event listener check the current posX/Y/Z values against the prevPosX/Y/Z values and updating the appropriate scoreboard value when needed. But it seems the prevPosX/Y/Z values aren't updated until immediately *after* the Forge event is fired, meaning after the first tick, every event I get has an entity with prevPosX=posX, making that comparison useless.

     

    The second problem with this method is that it only works for living entities; I'd like the scoreboard to be able to track *any* entity, but I haven't found a general EntityUpdate event (EntityEvent only seems to have an event for creation, entering a chunk, and being found not to be updateable).

     

    I've thought of a way to fix these problems using extended entity properties to store the previous positions and a tick handler that runs through every entity, updates the scoreboard if needed, and updates the extended property to the current position...but that seems like an inefficient overkill for this. Can anyone think of a more elegant solution?

  18. Well, I got the functionality I wanted by giving the custom block a tile entity which updates the growth value every second, then triggers a block update when it changes. Unfortunately, this has the effects of making the dirt unpushable by pistons (which is a common crop harvesting method) and also means it has a tile entity ticking and processing each second per each dirt block...which can't be good for lag.

     

    So although I have mostly what I want, I'm keeping this topic open and will check back occasionally in case anyone finds a better way, preferably a way to only cause a block update when a crop grows.

  19. A bit of sleuthing later, and I've found that when crops grow, they update their block with notification flag 2, which causes it to skip notifying its neighbors that it's been updated. (It calls World#markBlockForUpdate on its own position rather than .notifyBlockChange.) This is...well, irksome. I'm still trying to find a way to make this work, but meanwhile, perhaps knowing that it uses flag 2 can inspire someone who knows more about the implementation details of Minecraft's block update system to find a way around this?

     

    *EDIT* I guess one approach would be, is there any way to hook into some kind of watcher or event listener that'll notice when block metadata has changed, even if it wasn't changed with the "notify neighbors" flag bits set?

  20. I have a custom dirt block which the player can plant crops on. It's supposed to give a comparator output based on the growth of the crops planted on it. That works, except when the crops grow (either from bonemeal or naturally), the comparator isn't updated. Looking through the code of various growable plant classes, it looks like every time the crops grow, they update their adjacent blocks. So I thought if I just propagate that update one more block out by putting notifyBlocksofNeighborChange() inside my custom dirt's onNeighborBlockChange() method, it would update the adjacent comparators. However, it's not working; the comparators still won't update until they get a proper block update nearby.

     

    Obviously, I'm overriding my block's getComparatorInputOverride() method so adjacent comparators will output a value based on the growth of the crops above the block. But how do I get the comparators to call that method--i.e. update--when the crop above the block grows?

  21. Ohhhh, so it's basically the ForgeDirection version of the right-hand rule, then? Good to know that little skill from uni will come in handy after all xD

     

    So now it turns out that texturing this block is...not going quite as smoothly as I expected. For the sake of ease of rendering, I changed the class to extend BlockRedstoneDiode. All is well, and it renders perfectly fine...except there doesn't seem to be a way to consistently texture this thing. I have two crossing arrows pointing in the direction of the output, and that's the top texture. But it seems that if I get the arrows pointing in the right direction for one placement, they're pointing the wrong direction for certain other placements. And then of course, if I rotate the texture to fix it for that placement, it ends up pointing the wrong way for the original metadata. I feel like the solution has something to do with changing which metadata checks which sides for input and output, but since that's the part that's been confusing me all along, I don't quite know what calculations need to be applied.

     

    Here's the new class code (basically the same, just extending BlockRedstoneDiode and implementing the required methods for that):

     

    package com.IceMetalPunk.redplus.blocks;
    
    import net.minecraft.block.Block;
    import net.minecraft.block.BlockRedstoneDiode;
    import net.minecraft.entity.EntityLivingBase;
    import net.minecraft.init.Blocks;
    import net.minecraft.item.ItemStack;
    import net.minecraft.util.MathHelper;
    import net.minecraft.world.IBlockAccess;
    import net.minecraft.world.World;
    import net.minecraftforge.common.util.ForgeDirection;
    
    public class BlockRedstoneBridge extends BlockRedstoneDiode {
    
    public BlockRedstoneBridge() {
    	super(false);
    }
    
    @Override
    public void onNeighborBlockChange(World world, int x, int y, int z, Block block) {
    	world.notifyBlocksOfNeighborChange(x, y, z, this);
    }
    
    @Override
    public boolean canProvidePower() {
    	return true;
    }
    
    @Override
    public int isProvidingWeakPower(IBlockAccess access, int x, int y, int z, int side) {
    	World world = (World) access;
    	int out1 = (world.getBlockMetadata(x, y, z));
    
    	int out2 = ForgeDirection.VALID_DIRECTIONS[out1].getRotation(ForgeDirection.UP).ordinal();
    
    	if (side == out1) {
    		return getInputStrength(world, x, y, z, out1) - 1;
    	}
    	if (side == out2) {
    		return getInputStrength(world, x, y, z, out2) - 1;
    	}
    	return 0;
    }
    
    @Override
    public void onBlockPlacedBy(World p_149689_1_, int p_149689_2_, int p_149689_3_, int p_149689_4_,
    		EntityLivingBase p_149689_5_, ItemStack p_149689_6_) {
    	int l = MathHelper.floor_double((double) (p_149689_5_.rotationYaw * 4.0F / 360.0F) + 0.5D) & 3;
    
    	if (l == 0) {
    		p_149689_1_.setBlockMetadataWithNotify(p_149689_2_, p_149689_3_, p_149689_4_, 2, 2);
    	}
    
    	if (l == 1) {
    		p_149689_1_.setBlockMetadataWithNotify(p_149689_2_, p_149689_3_, p_149689_4_, 5, 2);
    	}
    
    	if (l == 2) {
    		p_149689_1_.setBlockMetadataWithNotify(p_149689_2_, p_149689_3_, p_149689_4_, 3, 2);
    	}
    
    	if (l == 3) {
    		p_149689_1_.setBlockMetadataWithNotify(p_149689_2_, p_149689_3_, p_149689_4_, 4, 2);
    	}
    }
    
    @Override
    public int getLightOpacity() {
    	return 0;
    }
    
    protected int getInputStrength(World world, int x, int y, int z, int side) {
    	ForgeDirection i1 = ForgeDirection.VALID_DIRECTIONS[side];
    	int j1 = x + i1.offsetX;
    	int k1 = z + i1.offsetZ;
    
    	int l1 = world.getIndirectPowerLevelTo(j1, y, k1, i1.ordinal());
    	return l1 >= 15	? l1
    					: Math.max(l1, world.getBlock(j1, y, k1) == Blocks.redstone_wire	? world.getBlockMetadata(j1, y, k1)
    																						: 0);
    }
    
    @Override
    protected int func_149901_b(int p_149901_1_) {
    	// TODO Auto-generated method stub
    	return 0;
    }
    
    @Override
    protected BlockRedstoneDiode getBlockPowered() {
    	// TODO Auto-generated method stub
    	return this;
    }
    
    @Override
    protected BlockRedstoneDiode getBlockUnpowered() {
    	// TODO Auto-generated method stub
    	return this;
    }
    }

     

    And this is the texture I'm currently using, which works for some orientations of the block, but is pointing in the wrong direction for others:

     

    width=32 height=32https://dl.dropboxusercontent.com/u/11662651/Graphics/Minecraft/redstone_bridge.png[/img]

     

    Can this be fixed with a modification of the texture alone, or is there some metadata calculation that needs to be done for case-by-case handling? And if that's so...does this mean I'll need to implement a custom block renderer instead of using the default BlockRedstoneDiode renderer?

     

    *EDIT* A little more testing and I think I can be more specific with the problem. Every direction works fine except one. If I place it down while facing towards negative X, both arrows are backwards from the way the inputs and outputs behave. Every other placement direction has them pointing properly.

  22. O_O So, I don't *quite* understand ForgeDirection#getRotation() works, as it sounds like this code would rotate the direction upwards, but that's not at all what's happening...but it works! Well...mostly... It seems that if the input is coming from a comparator, and that comparator is broken, it doesn't give the output line a block update, so it stays on. It's very strange, because that doesn't happen with wire or repeaters, and it doesn't happen when the comparator's signal changes; it only refuses to update when the comparator is broken. I don't see anything special in the class which would change the way it propagates block updates, so..any idea what would cause this and/or how to fix it? O_O

     

    *EDIT* Never mind about how to fix it; I've fixed it myself by forcing a block update with this:

     

    	@Override
    public void onNeighborBlockChange(World world, int x, int y, int z, Block block) {
    	world.notifyBlocksOfNeighborChange(x, y, z, this);
    }

     

    I was a little worried it might cause an infinite loop of notifications and a stack overflow, but it seems to work just fine. I'm still curious as to why every other update propagates just fine, but comparator destruction doesn't xD Anyway, thanks again for your help; you'll be in the mod credits when it's released. Which should be soon, as all that's left now for it is to texture this one block :)

×
×
  • Create New...

Important Information

By using this site, you agree to our Terms of Use.