Jump to content

Major Squirrel

Members
  • Posts

    117
  • Joined

  • Last visited

Everything posted by Major Squirrel

  1. I figured it out by myself and your comment confirmed what I noticed, thanks ! I managed to make it work without a custom inventory and by using EntityLiving#getItemStackFromSlot(EntityEquipmentSlot slotIn) and EntityLiving#setItemStackToSlot(EntityEquipmentSlot slotIn, @Nullable ItemStack stack) instead of going through the Iterable<ItemStack>. EntityNPCDialog: public class EntityNPCDialog extends EntityLiving { // private static final DataParameter<Integer> TEXTURE_INDEX = EntityDataManager.<Integer>createKey(EntityNPCDialog.class, DataSerializers.VARINT); public EntityNPCDialog(World worldIn) { super(worldIn); } @Override protected void initEntityAI() { super.initEntityAI(); this.tasks.addTask(0, new EntityAISwimming(this)); this.tasks.addTask(1, new EntityAIWatchClosest(this, EntityPlayer.class, 16.0F, 42.0F)); this.tasks.addTask(2, new EntityAILookIdle(this)); } @Override public boolean canBePushed() { return (false); } @Override public boolean isPushedByWater() { return (false); } @Override protected boolean canDespawn() { return(false); } @Override public void addVelocity(double x, double y, double z) {} @Override public boolean canRiderInteract() { return (false); } @Override public boolean isEntityInvulnerable(DamageSource source) { return (false); } @Override public boolean isPotionApplicable(PotionEffect potioneffectIn) { return (false); } @Override public boolean isImmuneToExplosions() { return (false); } @Nullable @Override protected SoundEvent getAmbientSound() { return (CraftAndConquerSounds.NPCDialogSoundEvent); } @Nullable @Override protected SoundEvent getHurtSound() { return (CraftAndConquerSounds.NPCDialogSoundEvent); } @Nullable @Override protected SoundEvent getDeathSound() { return (CraftAndConquerSounds.NPCDialogSoundEvent); } @Override protected SoundEvent getFallSound(int heightIn) { return (CraftAndConquerSounds.NPCDialogSoundEvent); } @Override protected SoundEvent getSwimSound() { return (CraftAndConquerSounds.NPCDialogSoundEvent); } @Override protected SoundEvent getSplashSound() { return (CraftAndConquerSounds.NPCDialogSoundEvent); } // public static ThreadDownloadImageData getDownloadImageSkin(ResourceLocation resourceLocationIn) { // TextureManager textureManager = Minecraft.getMinecraft().getTextureManager(); // ThreadDownloadImageData threadDownloadImageData; // // threadDownloadImageData = new ThreadDownloadImageData(null, "http://imgur.com/Z0pbA3P.png", DefaultNPCSkin.getDefaultSkin(), new ImageBufferDownload() { // // @Override // public BufferedImage parseUserSkin(BufferedImage image) { // return (image); // } // // }); // // textureManager.loadTexture(resourceLocationIn, threadDownloadImageData); // // return (threadDownloadImageData); // } } ContainerNPCInventory: public class ContainerNPCInventory extends Container { public static final EntityEquipmentSlot[] VALID_EQUIPMENT_SLOTS = new EntityEquipmentSlot[] { EntityEquipmentSlot.HEAD, EntityEquipmentSlot.CHEST, EntityEquipmentSlot.LEGS, EntityEquipmentSlot.FEET, EntityEquipmentSlot.MAINHAND, EntityEquipmentSlot.OFFHAND }; private EntityNPCDialog entityNPC; private final InventoryPlayer playerInventory; private InventoryBasic entityInventory; public ContainerNPCInventory(EntityPlayer playerIn, EntityNPCDialog entityNPC) { this.entityNPC = entityNPC; this.playerInventory = playerIn.inventory; this.entityInventory = new InventoryBasic("NPC Inventory", true, ContainerNPCInventory.VALID_EQUIPMENT_SLOTS.length); for (int index = 0; index < ContainerNPCInventory.VALID_EQUIPMENT_SLOTS.length; ++index) { this.entityInventory.setInventorySlotContents(index, entityNPC.getItemStackFromSlot(ContainerNPCInventory.VALID_EQUIPMENT_SLOTS[index])); } for (int index = 0; index < 4; ++index) { final EntityEquipmentSlot equipmentSlot = ContainerNPCInventory.VALID_EQUIPMENT_SLOTS[index]; this.addSlotToContainer(new Slot(this.entityInventory, index, 8, 8 + index * 18) { @Override public void onSlotChanged() { entityNPC.setItemStackToSlot(equipmentSlot, this.getStack()); super.onSlotChanged(); } @Override public boolean isItemValid(@Nullable ItemStack stack) { return (stack != null && stack.getItem().isValidArmor(stack, equipmentSlot, entityNPC)); } @Override public int getSlotStackLimit() { return (1); } @Nullable @Override public String getSlotTexture() { return (ItemArmor.EMPTY_SLOT_NAMES[equipmentSlot.getIndex()]); } }); } this.addSlotToContainer(new Slot(this.entityInventory, 4, 77, { @Override public void onSlotChanged() { entityNPC.setItemStackToSlot(ContainerNPCInventory.VALID_EQUIPMENT_SLOTS[4], this.getStack()); super.onSlotChanged(); } }); this.addSlotToContainer(new Slot(this.entityInventory, 5, 95, { @Override public void onSlotChanged() { entityNPC.setItemStackToSlot(ContainerNPCInventory.VALID_EQUIPMENT_SLOTS[5], this.getStack()); super.onSlotChanged(); } @SideOnly(Side.CLIENT) @Nullable @Override public String getSlotTexture() { return ("minecraft:items/empty_armor_slot_shield"); } }); for (int index = 0; index < 9; ++index) { this.addSlotToContainer(new Slot(this.playerInventory, index, 9 + index * 18, 112)); } } @Override public boolean canInteractWith(EntityPlayer playerIn) { return (true); } } It seems to work on multiplayer, I tested and I didn't have any crash. Also, the server stops correctly and restarts correctly with the correct equipement on the Entity. One last question though: when putting a debug message in Slot#onSlotChanged() method, I noticed that this method was called multiple times, either client side or server side. Why is that ? And does it influence the Entity stats when I put the equipment once ? (like summing the armor stats because the method is called multiple times)
  2. Good evening, So I tried the suggestion of Animefan8888: Here is the code. NPCInventoryBasic: public class NPCInventoryBasic extends InventoryBasic { public NPCInventoryBasic(Iterable<ItemStack> inventoryArmor, Iterable<ItemStack> inventoryHands) { super("NPC Inventory Basic", true, Iterables.size(inventoryArmor) + Iterables.size(inventoryHands)); Iterable<ItemStack> equipment = Iterables.concat(inventoryArmor, inventoryHands); for (int index = 0; index < Iterables.size(inventoryArmor) + Iterables.size(inventoryHands); ++index) { this.setInventorySlotContents(index, Iterables.get(equipment, index)); } } } ContainerNPCInventory: public class ContainerNPCInventory extends Container { private static final EntityEquipmentSlot[] VALID_EQUIPMENT_SLOTS = new EntityEquipmentSlot[] { EntityEquipmentSlot.HEAD, EntityEquipmentSlot.CHEST, EntityEquipmentSlot.LEGS, EntityEquipmentSlot.FEET }; private EntityNPCDialog entityNPC; private final InventoryPlayer playerInventory; private NPCInventoryBasic entityInventory; public ContainerNPCInventory(EntityPlayer playerIn, EntityNPCDialog entityNPC) { Iterable<ItemStack> inventoryArmor; Iterable<ItemStack> heldEquipment; this.entityNPC = entityNPC; inventoryArmor = entityNPC.getArmorInventoryList(); heldEquipment = entityNPC.getHeldEquipment(); if (!playerIn.worldObj.isRemote) { inventoryArmor.forEach(armor -> CraftAndConquer.logger.log(Level.INFO, "[ARMOR] I am:\t" + (armor != null ? armor.getDisplayName() : "null"))); heldEquipment.forEach(hand -> CraftAndConquer.logger.log(Level.INFO, "[HANDS] I am:\t" + (hand != null ? hand.getDisplayName() : "null"))); } this.playerInventory = playerIn.inventory; this.entityInventory = new NPCInventoryBasic(inventoryArmor, heldEquipment); //this.entityInventory.addInventoryChangeListener(this.entityNPC); for (int index = 0; index < 4; ++index) { final EntityEquipmentSlot equipmentSlot = ContainerNPCInventory.VALID_EQUIPMENT_SLOTS[index]; this.addSlotToContainer(new Slot(this.entityInventory, index, 8, 8 + index * 18) { @Override public void onSlotChanged() { entityNPC.setItemStackToSlot(equipmentSlot, this.getStack()); super.onSlotChanged(); } @Override public boolean isItemValid(@Nullable ItemStack stack) { return (stack != null && stack.getItem().isValidArmor(stack, equipmentSlot, entityNPC)); } @Override public int getSlotStackLimit() { return (1); } @Nullable @Override public String getSlotTexture() { return (ItemArmor.EMPTY_SLOT_NAMES[equipmentSlot.getIndex()]); } }); } this.addSlotToContainer(new Slot(this.entityInventory, 4, 77, { @Override public void onSlotChanged() { entityNPC.setItemStackToSlot(EntityEquipmentSlot.MAINHAND, this.getStack()); super.onSlotChanged(); } }); this.addSlotToContainer(new Slot(this.entityInventory, 5, 95, { @Override public void onSlotChanged() { entityNPC.setItemStackToSlot(EntityEquipmentSlot.OFFHAND, this.getStack()); super.onSlotChanged(); } @Nullable @Override public String getSlotTexture() { return ("minecraft:items/empty_armor_slot_shield"); } }); for (int index = 0; index < 9; ++index) { this.addSlotToContainer(new Slot(this.playerInventory, index, 9 + index * 18, 112)); } } @Override public boolean canInteractWith(EntityPlayer playerIn) { return (true); } } I think it works. Well, it "half" works. Wtf haha. When I place the equipment in the different slots, it updates the entity correctly as it equips and renders all the stuff nicely. But when I leave the GUI and open it again, this kind of thing happens as above (meaning, all the armor pieces are reversed, but not the hands). I just don't understand why, the indexes of the EntityEquipmentSlot enums are correct, the slots in my container didn't change, so what could happen ??
  3. What do you mean by creating a "custom Inventory" ? Is the InventoryBasic not sufficient for this kind of things ? Also, I am surprised that nobody tried to develop this in Forge. I searched few minutes on google and different forge forums and I have not been able to find a single topic talking about this.
  4. It does create an empty InventoryBasic, which I use it to make the link between the already-existing inventoryHands and inventoryArmor fields from LivingEntity and my Container, but it does not seem to work at all. I use them because Slots don't support Iterable<ItemStack> to be constructed.
  5. Good evening ! Are your buttons clickable right now, even if they appear darker ?
  6. Good evening, After checking my code again, I don't really understand why it does not work at all. The EntityLivingBase#setItemStackToSlot(EntityEquipmentSlot slotIn, @Nullable ItemStack stack) method should work, but in my case I think I'm not using it in the right way. Basically, I've made some simple tests to check if my entity could currently hold those items. One of this test is very simplistic: when I proceed a right-click on my entity with an equipment item, I call the method above from the server. NPCInteractEvent: @SubscribeEvent public void onEntityNPCInteract(PlayerInteractEvent.EntityInteract event) { ItemStack heldItem = event.getEntityPlayer().getHeldItem(EnumHand.MAIN_HAND); ItemBase npcManager = CraftAndConquerItems.npcManager; if (event.getTarget() instanceof EntityNPCDialog && event.getHand().equals(EnumHand.MAIN_HAND) && heldItem != null) { if (!event.getWorld().isRemote) { if (heldItem.getItem().equals(npcManager)) event.getEntityPlayer().openGui(CraftAndConquer.instance, GuiHandler.CAC_NPC_GUI, event.getWorld(), event.getTarget().getEntityId(), 0, 0); else if (heldItem.getItem().equals(Items.DIAMOND_HELMET)) event.getTarget().setItemStackToSlot(EntityEquipmentSlot.HEAD, heldItem); else if (heldItem.getItem().equals(Items.DIAMOND_CHESTPLATE)) event.getTarget().setItemStackToSlot(EntityEquipmentSlot.CHEST, heldItem); else if (heldItem.getItem().equals(Items.DIAMOND_LEGGINGS)) event.getTarget().setItemStackToSlot(EntityEquipmentSlot.LEGS, heldItem); else if (heldItem.getItem().equals(Items.DIAMOND_BOOTS)) event.getTarget().setItemStackToSlot(EntityEquipmentSlot.FEET, heldItem); else if (heldItem.getItem().equals(Items.DIAMOND_SWORD)) event.getTarget().setItemStackToSlot(EntityEquipmentSlot.MAINHAND, heldItem); else if (heldItem.getItem().equals(Items.SHIELD)) event.getTarget().setItemStackToSlot(EntityEquipmentSlot.OFFHAND, heldItem); } } } The event does actually work, even when I disconnect/reconnect, the equipment has been successfully saved in NBTTags, here is the result: https://images.discordapp.net/.eJwFwVEOgyAMANC7cABqO4vM2xAkaNSWQM0-lt19733d0y-3ut2sjRVgO0bWvvlh2lMtvqrWq6R2DJ_1hmSW8n4XsQEUkAnDRMSMFJjeQAu9GJc4R8LIEyLBI6foR3yT6n5_BBkixQ.vHGe_Qq0t2Fo_YJzemzOELu6gjU?width=1250&height=677[/img] But still, through a GUI, my code does not work. What is strange is that, when those entities are wearing the equipment in the event andler way, it does not appear in the GUI: I don't really want to add an Inventory in my Entity class, since I only want to access to the existing inventoryHands and inventoryArmor fields of the EntityLiving class. I don't see the point to add an Inventory that includes what already exists in their own way. I feel that this is something stupid that I am missing, but I don't see what.
  7. Good evening, I am trying to set up a GUI to equip my custom Entity, meaning giving it some armor pieces or items in hands. Here is how it looks like right now : ContainerNPCInventory: public class ContainerNPCInventory extends Container { private static final EntityEquipmentSlot[] VALID_EQUIPMENT_SLOTS = new EntityEquipmentSlot[] { EntityEquipmentSlot.HEAD, EntityEquipmentSlot.CHEST, EntityEquipmentSlot.LEGS, EntityEquipmentSlot.FEET }; private final EntityNPCDialog entityNPC; private Iterable<ItemStack> inventoryArmor; private Iterable<ItemStack> heldEquipment; private final InventoryPlayer playerInventory; private InventoryBasic entityInventory; public ContainerNPCInventory(EntityPlayer playerIn, EntityNPCDialog entityNPC) { this.entityNPC = entityNPC; this.inventoryArmor = entityNPC.getArmorInventoryList(); this.heldEquipment = entityNPC.getHeldEquipment(); this.playerInventory = playerIn.inventory; this.entityInventory = new InventoryBasic("NPC Inventory", true, Iterables.size(this.inventoryArmor) + Iterables.size(this.heldEquipment)); this.entityInventory.addInventoryChangeListener(entityNPC); for (int index = 0; index < 4; ++index) { final EntityEquipmentSlot equipmentSlot = ContainerNPCInventory.VALID_EQUIPMENT_SLOTS[index]; this.addSlotToContainer(new Slot(this.entityInventory, index, 8, 8 + index * 18) { @Override public boolean isItemValid(@Nullable ItemStack stack) { return (stack != null && stack.getItem().isValidArmor(stack, equipmentSlot, entityNPC)); } @Override public int getSlotStackLimit() { return (1); } @Nullable @Override public String getSlotTexture() { return (ItemArmor.EMPTY_SLOT_NAMES[equipmentSlot.getIndex()]); } }); } this.addSlotToContainer(new Slot(this.entityInventory, 4, 77, ); this.addSlotToContainer(new Slot(this.entityInventory, 5, 95, { @Nullable @Override public String getSlotTexture() { return ("minecraft:items/empty_armor_slot_shield"); } }); for (int index = 0; index < 9; ++index) { this.addSlotToContainer(new Slot(this.playerInventory, index, 9 + index * 18, 112)); } } @Override public boolean canInteractWith(EntityPlayer playerIn) { return (true); } } EntityNPCDialog: public class EntityNPCDialog extends EntityLiving implements IInventoryChangedListener { // private static final DataParameter<Integer> TEXTURE_INDEX = EntityDataManager.<Integer>createKey(EntityNPCDialog.class, DataSerializers.VARINT); public EntityNPCDialog(World worldIn) { super(worldIn); } @Override protected void initEntityAI() { super.initEntityAI(); this.tasks.addTask(0, new EntityAISwimming(this)); this.tasks.addTask(1, new EntityAIWatchClosest(this, EntityPlayer.class, 16.0F, 42.0F)); this.tasks.addTask(2, new EntityAILookIdle(this)); } @Override public boolean canBePushed() { return (false); } @Override public boolean isPushedByWater() { return (false); } @Override protected boolean canDespawn() { return(false); } @Override public void addVelocity(double x, double y, double z) {} @Override public boolean canRiderInteract() { return (false); } @Override public boolean isEntityInvulnerable(DamageSource source) { return (false); } @Override public boolean isPotionApplicable(PotionEffect potioneffectIn) { return (false); } @Override public boolean isImmuneToExplosions() { return (false); } @Nullable @Override protected SoundEvent getAmbientSound() { return (CraftAndConquerSounds.NPCDialogSoundEvent); } @Nullable @Override protected SoundEvent getHurtSound() { return (CraftAndConquerSounds.NPCDialogSoundEvent); } @Nullable @Override protected SoundEvent getDeathSound() { return (CraftAndConquerSounds.NPCDialogSoundEvent); } @Override protected SoundEvent getFallSound(int heightIn) { return (CraftAndConquerSounds.NPCDialogSoundEvent); } @Override protected SoundEvent getSwimSound() { return (CraftAndConquerSounds.NPCDialogSoundEvent); } @Override protected SoundEvent getSplashSound() { return (CraftAndConquerSounds.NPCDialogSoundEvent); } public static ThreadDownloadImageData getDownloadImageSkin(ResourceLocation resourceLocationIn) { TextureManager textureManager = Minecraft.getMinecraft().getTextureManager(); ThreadDownloadImageData threadDownloadImageData; threadDownloadImageData = new ThreadDownloadImageData(null, "http://imgur.com/Z0pbA3P.png", DefaultNPCSkin.getDefaultSkin(), new ImageBufferDownload() { @Override public BufferedImage parseUserSkin(BufferedImage image) { return (image); } }); textureManager.loadTexture(resourceLocationIn, threadDownloadImageData); return (threadDownloadImageData); } private void updateNPCSlots(InventoryBasic inventoryBasic) { if (!this.worldObj.isRemote) { this.setItemStackToSlot(EntityEquipmentSlot.HEAD, inventoryBasic.getStackInSlot(0)); this.setItemStackToSlot(EntityEquipmentSlot.CHEST, inventoryBasic.getStackInSlot(1)); this.setItemStackToSlot(EntityEquipmentSlot.LEGS, inventoryBasic.getStackInSlot(2)); this.setItemStackToSlot(EntityEquipmentSlot.FEET, inventoryBasic.getStackInSlot(3)); this.setItemStackToSlot(EntityEquipmentSlot.MAINHAND, inventoryBasic.getStackInSlot(4)); this.setItemStackToSlot(EntityEquipmentSlot.OFFHAND, inventoryBasic.getStackInSlot(5)); } } @Override public void onInventoryChanged(InventoryBasic inventoryBasic) { this.updateNPCSlots(inventoryBasic); } } I must admit I am really confused. I got inspired by the GuiContainerCreative, ContainerHorseInventory (the container for horses) and EntityZombie, but it seems that I am going in the wrong way. What I would like to achieve is that, when I place an item in the Slots of my container, it updates the equipment and hands ItemStack arrays of my Entity (+ rendering the items on the entity, like Zombies or Skeletons but this should work since my Entity model extends from ModelBiped). I think I am complicating the task for no reason. Could you guys highlight me on what should I achieve this ? Thank you for your time.
  8. (I know, I just want to let him figure it out by himself what are the cases and the results)
  9. If I recall correctly, I don't think you would need to this.drawDefaultBackground(); in the drawScreen method if your GUI does directly extend from GuiScreen (because it already draws it, so you would have a darker background, because you summed the dark ones). Maybe that's why your buttons appear darker ?
  10. Seriously, if you just completely disregard anything we say, then yes, your problems will not magically disappear in a puff of smoke. Does that mean it is not possible to do what I want to do? What happens when the player doesn't hold any item in his selected slot in the hotbar ?
  11. Hello, could you please share us how did you manage to solve it ? It could help other people.
  12. I didn't really know where to start, so I took a look at vanilla classes and I noticed the getDownloadImageSkin method in AbstractClientPlayer. I naively and simply copy-pasted the method into my custom entity class and changed the method's content so that it downloads a specific texture (hard-coded, I will change this later once it works) from imgur and then put this texture instead of the NPC default one. Moreover, I created a DefaultNPCSkin class (similar to the existing DefaultPlayerSkin vanilla class) which holds the default texture for NPCs and is accessible from my Renderer and the NPCInteractEvent class. I don't know why, but I'm getting a cast error between ThreadDownloadImageData and SimpleTexture in my own getDownloadImageSkin method. The thing is, I just copy-pasted the existing one from vanilla and it seems to work well, so I don't quite understand what is going on: yet, ThreadDownloadImageData extends from multiple classes (SimpleTexture, which itself extends from AbstractTexture), especially AbstractTexture which implements the ITextureObject. Could you help me on this one ? EntityNPCDialog: public class EntityNPCDialog extends EntityLiving { // private static final DataParameter<Integer> TEXTURE_INDEX = EntityDataManager.<Integer>createKey(EntityNPCDialog.class, DataSerializers.VARINT); public EntityNPCDialog(World worldIn) { super(worldIn); } // @Override // protected void entityInit() { // super.entityInit(); // // this.dataManager.register(TEXTURE_INDEX, 0); // } @Nullable @Override protected SoundEvent getAmbientSound() { return (CraftAndConquerSounds.NPCDialogSoundEvent); } @Nullable @Override protected SoundEvent getHurtSound() { return (CraftAndConquerSounds.NPCDialogSoundEvent); } @Nullable @Override protected SoundEvent getDeathSound() { return (CraftAndConquerSounds.NPCDialogSoundEvent); } @Override protected SoundEvent getFallSound(int heightIn) { return (CraftAndConquerSounds.NPCDialogSoundEvent); } @Override protected SoundEvent getSwimSound() { return (CraftAndConquerSounds.NPCDialogSoundEvent); } @Override protected SoundEvent getSplashSound() { return (CraftAndConquerSounds.NPCDialogSoundEvent); } // public void setTextureIndex() { // int textureID = this.dataManager.get(TEXTURE_INDEX); // // this.dataManager.set(TEXTURE_INDEX, (textureID + 1 < 2) ? textureID + 1 : 0); // } // // public int getTextureIndex() { // return (this.dataManager.get(TEXTURE_INDEX)); // } public static ThreadDownloadImageData getDownloadImageSkin(ResourceLocation resourceLocationIn) { TextureManager texturemanager = Minecraft.getMinecraft().getTextureManager(); ITextureObject itextureobject = texturemanager.getTexture(resourceLocationIn); if (itextureobject == null) { itextureobject = new ThreadDownloadImageData((File) null, "http://imgur.com/Z0pbA3P.png", DefaultNPCSkin.getDefaultSkin(), new ImageBufferDownload()); texturemanager.loadTexture(resourceLocationIn, itextureobject); } return ((ThreadDownloadImageData) itextureobject); } } NPCInteractEvent public class NPCInteractEvent { @SubscribeEvent public void onEntityNPCInteract(PlayerInteractEvent.EntityInteract event) { ItemStack heldItem = event.getEntityPlayer().getHeldItem(EnumHand.MAIN_HAND); ItemBase npcManager = CraftAndConquerItems.npcManager; if (event.getTarget() instanceof EntityNPCDialog && event.getHand().equals(EnumHand.MAIN_HAND) && heldItem.getItem().equals(npcManager)) { if (!event.getWorld().isRemote) EntityNPCDialog.getDownloadImageSkin(DefaultNPCSkin.getDefaultSkin()); } } } DefaultNPCSkin: public class DefaultNPCSkin { private static final ResourceLocation NPC_DIALOG_DEFAULT_TEXTURE = new ResourceLocation(CraftAndConquer.MODID, "textures/entities/npc/npc_dialog.png"); public static ResourceLocation getDefaultSkin() { return (NPC_DIALOG_DEFAULT_TEXTURE); } } RenderNPCDialog: public class RenderNPCDialog extends RenderLiving<EntityNPCDialog> { // public static final ResourceLocation[] NPC_DIALOG_TEXTURES = new ResourceLocation[] { // new ResourceLocation(CraftAndConquer.MODID, "textures/entities/npc/npc_dialog.png"), // new ResourceLocation(CraftAndConquer.MODID, "textures/entities/npc/npc_dialog_2.png"), // }; public RenderNPCDialog(RenderManager renderManagerIn, ModelBase modelBaseIn, float shadowSizeIn) { super(renderManagerIn, modelBaseIn, shadowSizeIn); } @Override protected ResourceLocation getEntityTexture(EntityNPCDialog entity) { return (DefaultNPCSkin.getDefaultSkin()); } } Logs from the console (I tested on singleplayer first, before going further with dedicated server): [18:10:29] [server thread/ERROR] [FML]: Exception caught during firing event net.minecraftforge.event.entity.player.PlayerInteractEvent$EntityInteract@32971884: java.lang.ClassCastException: net.minecraft.client.renderer.texture.SimpleTexture cannot be cast to net.minecraft.client.renderer.ThreadDownloadImageData at net.theviolentsquirrels.craftandconquer.entity.EntityNPCDialog.getDownloadImageSkin(EntityNPCDialog.java:92) ~[EntityNPCDialog.class:?] at net.theviolentsquirrels.craftandconquer.event.NPCInteractEvent.onEntityNPCInteract(NPCInteractEvent.java:27) ~[NPCInteractEvent.class:?] at net.minecraftforge.fml.common.eventhandler.ASMEventHandler_6_NPCInteractEvent_onEntityNPCInteract_EntityInteract.invoke(.dynamic) ~[?:?] at net.minecraftforge.fml.common.eventhandler.ASMEventHandler.invoke(ASMEventHandler.java:90) ~[ASMEventHandler.class:?] at net.minecraftforge.fml.common.eventhandler.EventBus.post(EventBus.java:185) [EventBus.class:?] at net.minecraftforge.common.ForgeHooks.onInteractEntity(ForgeHooks.java:1008) [ForgeHooks.class:?] at net.minecraft.entity.player.EntityPlayer.interact(EntityPlayer.java:1246) [EntityPlayer.class:?] at net.minecraft.network.NetHandlerPlayServer.processUseEntity(NetHandlerPlayServer.java:1072) [NetHandlerPlayServer.class:?] at net.minecraft.network.play.client.CPacketUseEntity.processPacket(CPacketUseEntity.java:93) [CPacketUseEntity.class:?] at net.minecraft.network.play.client.CPacketUseEntity.processPacket(CPacketUseEntity.java:14) [CPacketUseEntity.class:?] at net.minecraft.network.PacketThreadUtil$1.run(PacketThreadUtil.java:15) [PacketThreadUtil$1.class:?] at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) [?:1.8.0_111] at java.util.concurrent.FutureTask.run(FutureTask.java:266) [?:1.8.0_111] at net.minecraft.util.Util.runTask(Util.java:25) [util.class:?] at net.minecraft.server.MinecraftServer.updateTimeLightAndEntities(MinecraftServer.java:742) [MinecraftServer.class:?] at net.minecraft.server.MinecraftServer.tick(MinecraftServer.java:687) [MinecraftServer.class:?] at net.minecraft.server.integrated.IntegratedServer.tick(IntegratedServer.java:156) [integratedServer.class:?] at net.minecraft.server.MinecraftServer.run(MinecraftServer.java:536) [MinecraftServer.class:?] at java.lang.Thread.run(Thread.java:745) [?:1.8.0_111] [18:10:29] [server thread/ERROR] [FML]: Index: 1 Listeners: [18:10:29] [server thread/ERROR] [FML]: 0: NORMAL [18:10:29] [server thread/ERROR] [FML]: 1: ASM: net.theviolentsquirrels.craftandconquer.event.NPCInteractEvent@664a3770 onEntityNPCInteract(Lnet/minecraftforge/event/entity/player/PlayerInteractEvent$EntityInteract;)V [18:10:29] [server thread/FATAL]: Error executing task java.util.concurrent.ExecutionException: java.lang.ClassCastException: net.minecraft.client.renderer.texture.SimpleTexture cannot be cast to net.minecraft.client.renderer.ThreadDownloadImageData at java.util.concurrent.FutureTask.report(FutureTask.java:122) ~[?:1.8.0_111] at java.util.concurrent.FutureTask.get(FutureTask.java:192) ~[?:1.8.0_111] at net.minecraft.util.Util.runTask(Util.java:26) [util.class:?] at net.minecraft.server.MinecraftServer.updateTimeLightAndEntities(MinecraftServer.java:742) [MinecraftServer.class:?] at net.minecraft.server.MinecraftServer.tick(MinecraftServer.java:687) [MinecraftServer.class:?] at net.minecraft.server.integrated.IntegratedServer.tick(IntegratedServer.java:156) [integratedServer.class:?] at net.minecraft.server.MinecraftServer.run(MinecraftServer.java:536) [MinecraftServer.class:?] at java.lang.Thread.run(Thread.java:745) [?:1.8.0_111] Caused by: java.lang.ClassCastException: net.minecraft.client.renderer.texture.SimpleTexture cannot be cast to net.minecraft.client.renderer.ThreadDownloadImageData at net.theviolentsquirrels.craftandconquer.entity.EntityNPCDialog.getDownloadImageSkin(EntityNPCDialog.java:92) ~[EntityNPCDialog.class:?] at net.theviolentsquirrels.craftandconquer.event.NPCInteractEvent.onEntityNPCInteract(NPCInteractEvent.java:27) ~[NPCInteractEvent.class:?] at net.minecraftforge.fml.common.eventhandler.ASMEventHandler_6_NPCInteractEvent_onEntityNPCInteract_EntityInteract.invoke(.dynamic) ~[?:?] at net.minecraftforge.fml.common.eventhandler.ASMEventHandler.invoke(ASMEventHandler.java:90) ~[ASMEventHandler.class:?] at net.minecraftforge.fml.common.eventhandler.EventBus.post(EventBus.java:185) ~[EventBus.class:?] at net.minecraftforge.common.ForgeHooks.onInteractEntity(ForgeHooks.java:1008) ~[ForgeHooks.class:?] at net.minecraft.entity.player.EntityPlayer.interact(EntityPlayer.java:1246) ~[EntityPlayer.class:?] at net.minecraft.network.NetHandlerPlayServer.processUseEntity(NetHandlerPlayServer.java:1072) ~[NetHandlerPlayServer.class:?] at net.minecraft.network.play.client.CPacketUseEntity.processPacket(CPacketUseEntity.java:93) ~[CPacketUseEntity.class:?] at net.minecraft.network.play.client.CPacketUseEntity.processPacket(CPacketUseEntity.java:14) ~[CPacketUseEntity.class:?] at net.minecraft.network.PacketThreadUtil$1.run(PacketThreadUtil.java:15) ~[PacketThreadUtil$1.class:?] at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) ~[?:1.8.0_111] at java.util.concurrent.FutureTask.run(FutureTask.java:266) ~[?:1.8.0_111] at net.minecraft.util.Util.runTask(Util.java:25) ~[util.class:?] ... 5 more
  13. Good evening, I managed to use DataWatchers - not really DataWatchers, more DataParameter and EntityDataManager - to store the texture index in the entity and thus being able to make it work. EntityNPCDialog: public class EntityNPCDialog extends EntityLiving { private static final DataParameter<Integer> TEXTURE_INDEX = EntityDataManager.<Integer>createKey(EntityNPCDialog.class, DataSerializers.VARINT); public EntityNPCDialog(World worldIn) { super(worldIn); } @Override protected void entityInit() { super.entityInit(); this.dataManager.register(TEXTURE_INDEX, 0); } @Nullable @Override protected SoundEvent getAmbientSound() { return (CraftAndConquerSounds.NPCDialogSoundEvent); } @Nullable @Override protected SoundEvent getHurtSound() { return (CraftAndConquerSounds.NPCDialogSoundEvent); } @Nullable @Override protected SoundEvent getDeathSound() { return (CraftAndConquerSounds.NPCDialogSoundEvent); } @Override protected SoundEvent getFallSound(int heightIn) { return (CraftAndConquerSounds.NPCDialogSoundEvent); } @Override protected SoundEvent getSwimSound() { return (CraftAndConquerSounds.NPCDialogSoundEvent); } @Override protected SoundEvent getSplashSound() { return (CraftAndConquerSounds.NPCDialogSoundEvent); } public void setTextureIndex() { int textureID = this.dataManager.get(TEXTURE_INDEX); this.dataManager.set(TEXTURE_INDEX, (textureID + 1 < 2) ? textureID + 1 : 0); } public int getTextureIndex() { return (this.dataManager.get(TEXTURE_INDEX)); } } NPCInteractEvent: public class NPCInteractEvent { @SubscribeEvent public void onEntityNPCInteract(PlayerInteractEvent.EntityInteract event) { ItemStack heldItem = event.getEntityPlayer().getHeldItem(EnumHand.MAIN_HAND); ItemBase npcManager = CraftAndConquerItems.npcManager; if (event.getTarget() instanceof EntityNPCDialog && event.getHand().equals(EnumHand.MAIN_HAND) && heldItem.getItem().equals(npcManager)) { if (!event.getWorld().isRemote) ((EntityNPCDialog) event.getTarget()).setTextureIndex(); } } } NPCInteractEvent @SideOnly(Side.CLIENT) public class RenderNPCDialog extends RenderLiving<EntityNPCDialog> { public static final ResourceLocation[] NPC_DIALOG_TEXTURES = new ResourceLocation[] { new ResourceLocation(CraftAndConquer.MODID, "textures/entities/npc/npc_dialog.png"), new ResourceLocation(CraftAndConquer.MODID, "textures/entities/npc/npc_dialog_2.png"), }; public RenderNPCDialog(RenderManager renderManagerIn, ModelBase modelBaseIn, float shadowSizeIn) { super(renderManagerIn, modelBaseIn, shadowSizeIn); } @Override protected ResourceLocation getEntityTexture(EntityNPCDialog entity) { return (RenderNPCDialog.NPC_DIALOG_TEXTURES[entity.getTextureIndex()]); } } Here is what I did : I took a look at the EntityWolf and RenderWolf vanilla classes which cover the purpose of my question. The Wolf has three skins according to its state (tamed, not tamed and angry as written above). The getEntityTexture method in RenderWolf returns the correct texture according to different checks (isTamed and isAngry). Actually, those booleans are updated at the same time the EntityWolf's DataParameters are. (You can see a DataParameter named TAMED in EntityTameable which is the extend of the EntityWolf class.) All I had to do was to create a custom DataParameter for my entity (TEXTURE_INDEX), override the entityInit method to register my DataParameter into the EntityDataManager of my custom entity, and correctly get and set the value in my TEXTURE_INDEX parameter. The NoClassDefFoundError was there because I was trying to access my Render class from my Entity class, now there is no access anymore. The last thing I have to do is to store the index in NBT tags in order to retrieve the correct texture when the server restarts. I have one question though: what if I don't want to get the texture from an existing array, but loading it from a folder ? Right now, all the clients are aware of the different textures of the entity because they are stored in the class. Would it be possible to load the texture from a folder from the server, applying it to the entity and notify all the clients that the texture has changed ? Right now, I would see a problem because the clients would not be aware of this specific texture applied on the entity (you know, same when a player changes his skin from minecraft.net, there has to be a reconnect on the server in order to update the thing).
  14. Yup, thank you. I will change that so that the texture index has to be stored directly in this entity. Maybe I can do it with DataWatchers ? There was an old tutorial on the wiki, but the link isn't available anymore... I'm gonna try to read through the source code tho.
  15. Okay so I just checked Vanilla and it seems that EntityWolf has the same thing as me: three different textures regarding the wolf behavior (tamed, not tamed and angry) and the getEntityTexture returns the correct one according to the correct behavior. But it does not seem to send any packet to the client. I don't understand why would I need to send any packet here since there is a NoClassDefFoundError. Would you mean to send a packet to the client so that - on the client - it calls the function from RenderNPCDialog ? If yes, is it the correct thing to do in general in Minecraft to notify players of a texture change ?
  16. Good evening, Following my topic today, I would like to change the skin of a custom entity (a NPC) by right-clicking on it with a specific item. As a naive approach, I tried to change the index of the texture array to redirect the correct texture in my getEntityTexture method. So far, this works on a client, the texture of the entity changes when I correctly right-click on the entity, but this does not work on a server. I have a NoClassDefFoundError that pops on the server console when processing the texture change (the client, however, does not crash). RenderNPCDialog (the renderer): public class RenderNPCDialog extends RenderLiving<EntityNPCDialog> { private static final ResourceLocation[] NPC_DIALOG_TEXTURES = new ResourceLocation[] { new ResourceLocation(CraftAndConquer.MODID, "textures/entities/npc/npc_dialog.png"), new ResourceLocation(CraftAndConquer.MODID, "textures/entities/npc/npc_dialog_2.png"), }; private static int textureID = 0; public RenderNPCDialog(RenderManager renderManagerIn, ModelBase modelBaseIn, float shadowSizeIn) { super(renderManagerIn, modelBaseIn, shadowSizeIn); } @Override protected ResourceLocation getEntityTexture(EntityNPCDialog entity) { return (RenderNPCDialog.NPC_DIALOG_TEXTURES[textureID]); } public static void setEntityTextureID() { textureID = (textureID + 1 < NPC_DIALOG_TEXTURES.length) ? textureID + 1 : 0; } } NPCInteractEvent (the event that processes the texture change): public class NPCInteractEvent { @SubscribeEvent public void onEntityNPCInteract(PlayerInteractEvent.EntityInteract event) { ItemStack heldItem = event.getEntityPlayer().getHeldItem(EnumHand.MAIN_HAND); ItemBase npcManager = CraftAndConquerItems.npcManager; if (event.getTarget() instanceof EntityNPCDialog && event.getHand().equals(EnumHand.MAIN_HAND) && heldItem.getItem().equals(npcManager)) { if (!event.getWorld().isRemote) RenderNPCDialog.setEntityTextureID(); } } } ClientProxy: public class ClientProxy extends CommonProxy { @Override public void preInit() { super.preInit(); } @Override public void init() { super.init(); } @Override public void postInit() { super.postInit(); } @Override public void registerItemRenderer(Item item, int meta, String id) { ModelLoader.setCustomModelResourceLocation(item, meta, new ModelResourceLocation(CraftAndConquer.MODID + ":" + id, "inventory")); } @Override public void registerEntityRenderers() { RenderManager renderManager = Minecraft.getMinecraft().getRenderManager(); RenderingRegistry.registerEntityRenderingHandler(EntityNPCDialog.class, new RenderNPCDialog(renderManager, new ModelEntityNPCDialog(), 0.5f)); } } CommonProxy: public class CommonProxy { public void preInit() { CraftAndConquerBlocks.init(); CraftAndConquerItems.init(); CraftAndConquerSounds.registerSounds(); CraftAndConquerEntities.registerEntities(); NetworkRegistry.INSTANCE.registerGuiHandler(CraftAndConquer.instance, new GuiHandler()); } public void init() { CraftAndConquerRecipes.init(); CraftAndConquer.proxy.registerEntityRenderers(); MinecraftForge.EVENT_BUS.register(new NPCInteractEvent()); } public void postInit() { } public void registerItemRenderer(Item item, int meta, String id) { } public void registerEntityRenderers() { } } Logs from the server console: I have explored the code of the Custom NPC mod to get some hints, but it seems that I am unable to find what I would like to do. I have checked the content of my .jar file and the class RenderNPCDialog does exist, so I don't quite understand what is going on. Thank you for your help !
  17. To be honest, I followed tutorial haha. I thought that ID's for this method were directly linked with the vanilla registry. My bad.
  18. I do need to specify an id for the method below: public static void registerModEntity(Class<? extends Entity> entityClass, String entityName, int id, Object mod, int trackingRange, int updateFrequency, boolean sendsVelocityUpdates, int eggPrimary, int eggSecondary) I don't quite understand why would you mean by "do not use global IDs" then. About the entities registration, I noticed my error once the topic has been created, the method call has been moved to the preInit() in the CommonProxy since.
  19. EDIT 2 (solved, 01.14.17 6:38pm): I forgot the "two hands" new mechanic and it seems that the EntityInteract is now fired for each hand ! I just added a check of the hand using event.getHand() and this would return whether the main hand or the off hand. It now works well. EDIT (01.14.17 6:24 pm): Okay so it seems that it only spawns only one entity (I tested on a flat map, and when I summon an entity with its egg, it only shows +1 entity with F3 enabled, sorry I should have done this before) and that the graphic glitch is another topic. But then, why would my function setEntityTextureID be called twice on the same side ? Good evening, I made a custom entity (a dialog NPC) and when I summon this entity with its egg, it seems that there are two entities. Am I missing something obvious ? (like client/server checks) CraftAndConquerEntities (my register class): package net.theviolentsquirrels.craftandconquer.entity; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityList; import net.minecraftforge.fml.common.registry.EntityRegistry; import net.theviolentsquirrels.craftandconquer.CraftAndConquer; import java.util.BitSet; /** * Created by Mathieu on 12/01/2017. */ public class CraftAndConquerEntities { private static final CraftAndConquerEntities INSTANCE = new CraftAndConquerEntities(); private BitSet availableIndices; private CraftAndConquerEntities() { this.availableIndices = new BitSet(256); this.availableIndices.set(0, 255); for (Object id : EntityList.ID_TO_CLASS.keySet()) { this.availableIndices.clear((Integer) id); } } public static CraftAndConquerEntities instance() { return (INSTANCE); } public static void registerEntities() { CraftAndConquerEntities.createEntity(EntityNPCDialog.class, "EntityNPCDialog", 0xC0392B, 0xE67E22); } public static int findGlobalUniqueId() { int result = CraftAndConquerEntities.instance().availableIndices.nextSetBit(0); if (result < 0) { throw (new RuntimeException("No more entity ID left !")); } return (result); } public static void createEntity(Class<? extends Entity> entityClass, String entityName, int solidColor, int spotColor) { int randomID = CraftAndConquerEntities.findGlobalUniqueId(); EntityRegistry.registerModEntity(entityClass, entityName, randomID, CraftAndConquer.instance, 64, 1, true, solidColor, spotColor); } } EntityNPCDialog (my custom entity): package net.theviolentsquirrels.craftandconquer.entity; import net.minecraft.entity.EntityLiving; import net.minecraft.util.SoundEvent; import net.minecraft.world.World; import net.theviolentsquirrels.craftandconquer.client.audio.CraftAndConquerSounds; import javax.annotation.Nullable; /** * Created by Mathieu on 12/01/2017. */ public class EntityNPCDialog extends EntityLiving { public EntityNPCDialog(World worldIn) { super(worldIn); } @Nullable @Override protected SoundEvent getAmbientSound() { return (CraftAndConquerSounds.NPCDialogSoundEvent); } @Nullable @Override protected SoundEvent getHurtSound() { return (CraftAndConquerSounds.NPCDialogSoundEvent); } @Nullable @Override protected SoundEvent getDeathSound() { return (CraftAndConquerSounds.NPCDialogSoundEvent); } @Override protected SoundEvent getFallSound(int heightIn) { return (CraftAndConquerSounds.NPCDialogSoundEvent); } @Override protected SoundEvent getSwimSound() { return (CraftAndConquerSounds.NPCDialogSoundEvent); } @Override protected SoundEvent getSplashSound() { return (CraftAndConquerSounds.NPCDialogSoundEvent); } } ClientProxy (where I register my entities and their renderers): package net.theviolentsquirrels.craftandconquer.proxy; import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.block.model.ModelResourceLocation; import net.minecraft.client.renderer.entity.RenderManager; import net.minecraft.item.Item; import net.minecraftforge.client.model.ModelLoader; import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.fml.client.registry.RenderingRegistry; import net.minecraftforge.fml.common.FMLCommonHandler; import net.minecraftforge.fml.common.network.NetworkRegistry; import net.theviolentsquirrels.craftandconquer.CraftAndConquer; import net.theviolentsquirrels.craftandconquer.GuiHandler; import net.theviolentsquirrels.craftandconquer.block.CraftAndConquerBlocks; import net.theviolentsquirrels.craftandconquer.client.audio.CraftAndConquerSounds; import net.theviolentsquirrels.craftandconquer.client.model.ModelEntityNPCDialog; import net.theviolentsquirrels.craftandconquer.client.renderer.entity.RenderNPCDialog; import net.theviolentsquirrels.craftandconquer.entity.CraftAndConquerEntities; import net.theviolentsquirrels.craftandconquer.entity.EntityNPCDialog; import net.theviolentsquirrels.craftandconquer.event.NPCInteractEvent; import net.theviolentsquirrels.craftandconquer.item.CraftAndConquerItems; import net.theviolentsquirrels.craftandconquer.recipe.CraftAndConquerRecipes; /** * Created by Mathieu on 31/12/2016. */ public class ClientProxy extends CommonProxy { @Override public void preInit() { super.preInit(); CraftAndConquerBlocks.init(); CraftAndConquerItems.init(); CraftAndConquerSounds.registerSounds(); CraftAndConquerEntities.registerEntities(); } @Override public void init() { super.init(); CraftAndConquerRecipes.init(); NetworkRegistry.INSTANCE.registerGuiHandler(CraftAndConquer.instance, new GuiHandler()); CraftAndConquer.proxy.registerEntityRenderers(); } @Override public void postInit() { super.postInit(); } @Override public void registerItemRenderer(Item item, int meta, String id) { ModelLoader.setCustomModelResourceLocation(item, meta, new ModelResourceLocation(CraftAndConquer.MODID + ":" + id, "inventory")); } @Override public void registerEntityRenderers() { RenderManager renderManager = Minecraft.getMinecraft().getRenderManager(); RenderingRegistry.registerEntityRenderingHandler(EntityNPCDialog.class, new RenderNPCDialog(renderManager, new ModelEntityNPCDialog(), 0)); } } RenderNPCDialog (the renderer of my entity): package net.theviolentsquirrels.craftandconquer.client.renderer.entity; import net.minecraft.client.model.ModelBiped; import net.minecraft.client.renderer.entity.RenderBiped; import net.minecraft.client.renderer.entity.RenderManager; import net.minecraft.util.ResourceLocation; import net.theviolentsquirrels.craftandconquer.CraftAndConquer; import net.theviolentsquirrels.craftandconquer.entity.EntityNPCDialog; import org.apache.logging.log4j.Level; /** * Created by Mathieu on 12/01/2017. */ public class RenderNPCDialog extends RenderBiped<EntityNPCDialog> { private static final ResourceLocation[] NPC_DIALOG_TEXTURES = new ResourceLocation[] { new ResourceLocation(CraftAndConquer.MODID, "textures/entities/npc/npc_dialog.png"), new ResourceLocation(CraftAndConquer.MODID, "textures/entities/npc/npc_dialog_2.png"), }; private static int textureID = 0; public RenderNPCDialog(RenderManager renderManagerIn, ModelBiped modelBipedIn, float shadowSizeIn) { super(renderManagerIn, modelBipedIn, shadowSizeIn); } @Override protected ResourceLocation getEntityTexture(EntityNPCDialog entity) { return (RenderNPCDialog.NPC_DIALOG_TEXTURES[textureID]); } public static void setEntityTextureID() { CraftAndConquer.logger.log(Level.INFO, "textureID before:\t" + textureID); textureID = (textureID + 1 < NPC_DIALOG_TEXTURES.length) ? textureID + 1 : 0; CraftAndConquer.logger.log(Level.INFO, "textureID after:\t" + textureID); } } Also, in the console, I can observe this when I interact with my entity once (EntityInteract event): [17:22:11] [Client thread/INFO] [craftandconquer]: textureID before: 0 [17:22:11] [Client thread/INFO] [craftandconquer]: textureID after: 1 [17:22:11] [Client thread/INFO] [craftandconquer]: textureID before: 1 [17:22:11] [Client thread/INFO] [craftandconquer]: textureID after: 0 [17:22:12] [server thread/INFO] [craftandconquer]: textureID before: 0 [17:22:12] [server thread/INFO] [craftandconquer]: textureID after: 1 [17:22:12] [server thread/INFO] [craftandconquer]: textureID before: 1 [17:22:12] [server thread/INFO] [craftandconquer]: textureID after: 0 I think in the event I would need to check isRemote, but even with this check, it would trigger twice so I was thinking about a "double spawn" ? Thank you for your answers.
  20. I assume that - regarding your answers - TileEntitySpecialRenderers would be the simpliest way to implement the subject of this topic, right ?
  21. Good evening, I have been searching some tutorials, sample codes and public GitHub repositories to learn how to render Minecraft items in 3D, in the "world". So I have found some useful links such as or by experience the Bibliocraft mod (as you can see, potions and books are rendered on their shelves). Replicate the same result wouldn't be a big challenge since the tutorial above is nice and well explained, but I've seen some topics such as this one highlighting that TileEntitySpecialRenderers should only be used for animating blocks. Yet, in both links I included above, there is no animation at all, only 3D items rendering alongwith blocks. Is TileEntitySpecialRenderer the only way to render 3D items in the world with a TileEntity ? I've also read that TileEntitySpecialRenderers were super greedy regarding gpu and rendering, why is that ? Would it be "super risky" if about a hundred of TileEntities using a special renderer were placed all around the world ?
  22. Good evening, If you recently updated IntelliJ to 2016.1 version, you should follow diesieben tip !
  23. Okay, so do I need to work with obfuscated fields or does he need to get a server deobfuscated version ?
×
×
  • Create New...

Important Information

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