Jump to content

[1.7.10][Solved] EntityItem (Dropped Item) destroyed by explosion.


Scribblon

Recommended Posts

Greetings,

 

Let me start with the standard disclaimer: I searched high and low for a solution. Google, this forum, the javadoc, other mods (the mods I know use the mechanic I want to implement are closed sourced)... I even experimented with different EventTypes for hours just to see if that gets the result I need.

 

Now for my question/problem: How can I catch the event where an item is destroyed by an explosion? So I am looking for an event which will give an Entity and a DamageSource, of which the Entity is an EntityItem (containing an ItemStack) dropped by the player.

The difficult part is: the item in question I am looking for being destroyed is an Ender Pearl.

In place a new EntityItem will be spawned which is needed to progress in the mod I am working on.

 

What I already tried:

Almost every event available in import net.minecraftforge.event.* . Dropped a block of dirt, set off tnt, and wait for the explosion to trigger the events set to just echo which event triggered. None came up with an EntityItem or ItemStack being destroyed. The ones who I thought might result in something are all specialized in LivingEntities.

 

What I am currently thinking:

- Either I should give up on the vanilla enderPearl and create my own version like crackedEnderPearl, for which I can specify its interaction with explosions.

- Create a custom EnderPearl subClass wich will replace the vanilla one with a ItemToss Listener. However, this I prefer to avoid at all cost.

- Create a custom ItemDestroyEvent extending net.minecraftforge.event.entity.item.ItemEvent which will be fired when an ItemEntity is being destroyed (by explosions). Which I am now investigating.

(-Or use a normal crafting recipe. Added for the ones who will suggest this. Counter-argument: I want to learn this, as I want to do more with explosions.)

 

Maybe I am looking in the wrong direction, maybe this can't be caught in the EventBus in any form, or not with the events available by default. But before I go ahead and start coding a Custom Event, I really want to check if I am just looking in the wrong place.

 

My gratitude in advance.

"I guess as this is pretty much WIP, I can just 'borrow' the sounds from that game without problem. They are just 'placeholders' anyway." -Me, while extracting the Tear sounds of Bioshock Infinite.

Link to comment
Share on other sites

You know I never noticed that before but all the event hooks are in EntityLivingBase and so EntityItem (which extends Entity directly) doesn't get those events.  That sucks actually since all Entities can get "hurt" and "die", so really they should have events too.

 

I think one work-around would be to monitor things from a tick event.  Basically you'd have to detect the death of one of the EntityItems (based on health<=0) and figure out whether it was an explosion that caused it.  Are you controlling the explosion?  It would still be bit of a guess, but if a tick handler saw that the EntityItem had died and if you know an explosion happened in its proximity, you could probably consider it a death by explosion.

 

But yeah this is worth a enhancement or bug request to the Forge people.  They should not limit the hurt and death events to living entities, or at least have an equivalent for general entities.

Check out my tutorials here: http://jabelarminecraft.blogspot.com/

Link to comment
Share on other sites

Diving even deeper into the problem.

Looking at EntityItem I do see a health-variable, but it is set to private with no getter/setter methods available. There is the universal .isDeath() method and it does calculations for another entity hurting the EntityItem.

 

The other way around, I could look for an entity hurt by another entity but this is limited to living and player entities. And explosions (which has no entity) and a primedTNTEntity is just a subclass of Entity will not invoke any event already available.

The Explosion does provide a method with EntityLivingBase, but that will return a player who set it off, or the LivingEntity (Creeper) who exploded.

 

So going into EntityHurtEvents... and no, explosions (and arrows) are excluded. Only whenever the player itself hurts another entity directly (click) it will trigger this event.

 

Out of EventType options I have been diving deeper into the code. Besides finding the Listeners of all Listeners: ASMEventHandler. I did not find any trace of any kind of Listeners for Events from Forges side. Well, besides some complaining about OptiFine being not being so optimal in combination with forge and a whole load of Event posts. ForgeHooks seem to fire Events in the eventBus. However, these are added/implemented/nested deep inside vanilla code and only seem to be triggering certain events combined with Optifine limitations(?).

 

Going further down to MinecraftForge itself, the initialization of the eventBus is just the same as the FML one. So, ASMEventHandler is also used here. ASMEventHandler is a IListener implementation, but it seems this is a generic listener which writes it own listener depending on what the event is submitted. I tried to understand this ClassWriter stuff that does this, but it is all magic to me at the moment. But don't think it will do the same for your custom Events. I tried, and it will not fire and event EntityHurtEvent extending EntityEvent with the paramaters Entity entity and DamageSource damageSource (As seen in constructor: public EntityHurtEvent(Entity entity, DamageSource damageSource) ). But that will not do.

 

Understanding that I totally misunderstood the ASMEventHanlder I started searching on the Eventbus.post() usage in existing code. 207 hits, all tied into the code itself of the item. Coming to the conclusion that simply modifying an existing Event impossible.

 

But I am out of time, I need to go. Next up, creating a Event on each update if the entity involves an item which has isDead() set to true. And look for an explosion nearby. Or the existence of a PrimedTNTEntity...

 

 

"I guess as this is pretty much WIP, I can just 'borrow' the sounds from that game without problem. They are just 'placeholders' anyway." -Me, while extracting the Tear sounds of Bioshock Infinite.

Link to comment
Share on other sites

What you could also do is to create your custom EntityItem class which extends the vanilla one, use the EntityJoinWorldEvent, check if it's an EntityItem (and not your class!) and if it holds an ender pearl, If yes, copy the item stack, kill the original one and spawn your own EntityItem.

I have example code of mine here:

https://github.com/SanAndreasP/EnderStuffPlus/blob/master/java/de/sanandrew/mods/enderstuffplus/registry/event/EntityJoinWorldEventInst.java

https://github.com/SanAndreasP/EnderStuffPlus/blob/master/java/de/sanandrew/mods/enderstuffplus/entity/item/EntityItemTantal.java

Don't ask for support per PM! They'll get ignored! | If a post helped you, click the "Thank You" button at the top right corner of said post! |

mah twitter

This thread makes me sad because people just post copy-paste-ready code when it's obvious that the OP has little to no programming experience. This is not how learning works.

Link to comment
Share on other sites

Thank you for the response. I have investigated that. But I really, really hoped I do not have to those kind of actions.

 

Although compatibility is not my biggest concern right now. I can foresee this becoming a problem, especially when other mods start to implement their own way of "Crafting by Explosion". Especially when it involves vanilla items. But it seems I really can't get around it.

 

So, I went for @SanAndreasP approach. But with a little twist.

 

Custom EntityItem

package nl.scribblon.riftcraft.entity;

import net.minecraft.client.Minecraft;
import net.minecraft.entity.item.EntityItem;
import net.minecraft.item.ItemStack;
import net.minecraft.util.DamageSource;
import net.minecraft.world.World;
import net.minecraftforge.common.MinecraftForge;
import nl.scribblon.riftcraft.event.EntityItemDeathEvent;
import nl.scribblon.riftcraft.event.EntityItemHurtEvent;

/**
* Created by Scribblon for RiftCraft.
* Date Creation: 31-7-2014
*/
public class RCEntityItem extends EntityItem {

    private DamageSource previousDamageSource;

    public RCEntityItem(World p_i1710_1_, double p_i1710_2_, double p_i1710_4_, double p_i1710_6_, ItemStack p_i1710_8_) {
        super(p_i1710_1_, p_i1710_2_, p_i1710_4_, p_i1710_6_, p_i1710_8_);
    }

    @Override
    public boolean attackEntityFrom(DamageSource damageSource, float damage) {
        //Even though this is always false, lets catch this and send it along after being done with it.
        boolean returnValue = super.attackEntityFrom(damageSource, damage);

        this.previousDamageSource = damageSource;

        if (this.velocityChanged)
            MinecraftForge.EVENT_BUS.post(new EntityItemHurtEvent(this, damageSource));

        if (this.isDead)
            MinecraftForge.EVENT_BUS.post(new EntityItemDeathEvent(this, damageSource));


        return returnValue;
    }

    public DamageSource getPreviousDamageSource(){
        return this.previousDamageSource;
    }

    public static RCEntityItem convert(EntityItem oldEntityItem){
        RCEntityItem newEntityItem =  new RCEntityItem(oldEntityItem.worldObj, oldEntityItem.posX, oldEntityItem.posY, oldEntityItem.posZ, oldEntityItem.getEntityItem());
        newEntityItem.motionX = oldEntityItem.motionX;
        newEntityItem.motionY = oldEntityItem.motionY;
        newEntityItem.motionZ = oldEntityItem.motionZ;
        newEntityItem.age = oldEntityItem.age;
        newEntityItem.delayBeforeCanPickup = oldEntityItem.delayBeforeCanPickup;
        newEntityItem.hoverStart = oldEntityItem.hoverStart;
        newEntityItem.lifespan = oldEntityItem.lifespan;

        return newEntityItem;
    }
}

 

EnderTossHandler

package nl.scribblon.riftcraft.handler;

import cpw.mods.fml.common.eventhandler.Event;
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
import net.minecraft.entity.item.EntityItem;
import net.minecraft.init.Items;
import net.minecraft.item.Item;
import net.minecraft.item.ItemEnderPearl;
import net.minecraftforge.event.entity.EntityJoinWorldEvent;
import net.minecraftforge.oredict.OreDictionary;
import nl.scribblon.riftcraft.entity.RCEntityItem;
import nl.scribblon.riftcraft.reference.Settings;
import nl.scribblon.riftcraft.util.LogHelper;

/**
* Created by Scribblon for RiftCraft.
* Date Creation: 31-7-2014
*/
public class EnderTossHandler {

    @SubscribeEvent
    public void onEntityJoinWorld(EntityJoinWorldEvent event){
        //Simple checks for quick returns, could not be memory friendly.
        if (event.world.isRemote) return;

        if (!(event.entity instanceof EntityItem)) return;

        if (event.entity instanceof RCEntityItem){
            if(Settings.Debug.isDebugging)
                LogHelper.info("RCEntityItem just entered the world! " + event.entity);
            return;
        }

        if (Settings.Debug.isDebugging)
            LogHelper.info("EntityItemJoinWorldEvent Triggered! " + event.entity);

        //Process entityItem
        EntityItem entityItem = (EntityItem) event.entity;

        //Again checking if items has been removed since the event triggered.
        if (entityItem.getEntityItem() == null) return;

        if (Settings.Debug.isDebugging) {
            LogHelper.info("Is this an EnderPearl? " + (entityItem.getEntityItem().getItem().getUnlocalizedName().equals(Items.ender_pearl.getUnlocalizedName())));
        }

        //Check if item in question is an EnderPearl
        if (entityItem.getEntityItem().getItem().getUnlocalizedName().equals(Items.ender_pearl.getUnlocalizedName())){
            RCEntityItem rcEntityItem = RCEntityItem.convert(entityItem);
            event.world.spawnEntityInWorld(rcEntityItem);
            entityItem.setDead();
            event.setResult(Event.Result.DENY);
        }
    }
}

 

It works. The eventHandler lets me know that indeed a EntityItem with an EnderPearl is identified and replaced with the custom EntityItem is being spawned in place of it.

However, when this happens I get the following message:

[server thread/WARN]: Fetching addPacket for removed entity

Seen this before while playing the game, and always assumed it was harmless. Still, I would like to know if that really is harmless and how to process the event correctly and prevent the server from throwing such a warning.

I already found out here http://www.minecraftforge.net/forum/index.php/topic,17493.msg88467.html that I could check for the entity to exist longer than 1 tick... How that would fit... I don't know yet, going to bed now. Till tomorrow!

 

EDIT:

Solved the warning part. Just adding the following

if(event.entity.ticksExisted < 1) return;

before the entity cast will prevent the warning from appearing... Yeah, I couldn't help myself. Now I am really going to bed. I will continue posting until I solved the explosion part as well.

"I guess as this is pretty much WIP, I can just 'borrow' the sounds from that game without problem. They are just 'placeholders' anyway." -Me, while extracting the Tear sounds of Bioshock Infinite.

Link to comment
Share on other sites

Good work.  Yeah, when modding it is interesting but you can run into dead ends, at least in the hopes of finding a logically straight forward approach.

 

Regarding the private health variable, you can use Java reflection to check it.  In general programming, Java reflection is often sign of some bad code architecture, but in modding it is quite acceptable since you don't have the ability to really affect changes in the scope of the actual code otherwise.  Anyway, just mentioning this in case you have to do something "deep" like this again.

 

I think it is also worth issuing an enhancement request to the Forge github to ask for events related to EntityItem hurt and death.

Check out my tutorials here: http://jabelarminecraft.blogspot.com/

Link to comment
Share on other sites

[sNIP]

Solved the warning part. Just adding the following

if(event.entity.ticksExisted < 1) return;

before the entity cast will prevent the warning from appearing... Yeah, I couldn't help myself. Now I am really going to bed. I will continue posting until I solved the explosion part as well.

 

Update: the code there is not the solution.

 

But it does work. Here is a snippet of the output log:

 

[00:12:08] [server thread/INFO]: Wow! RCEntityItem['item.item.enderPearl'/316573, l='New World', x=285,47, y=4,13, z=-303,91] was hurt by net.minecraft.util.DamageSource@1634927d how is that possible!
[00:12:08] [server thread/INFO]: Item just got hurt! Nooohs!
[00:12:08] [server thread/INFO]: Item just died! Yes!
[00:12:08] [server thread/INFO]: Wow! RCEntityItem['item.item.enderPearl'/326126, l='New World', x=286,51, y=4,13, z=-302,85] was hurt by net.minecraft.util.DamageSource@557d57ee how is that possible!
[00:12:08] [server thread/INFO]: Item just got hurt! Nooohs!
[00:12:08] [server thread/INFO]: Item just died! Yes!
[00:12:08] [server thread/INFO]: Wow! RCEntityItem['item.item.enderPearl'/331558, l='New World', x=285,66, y=4,13, z=-301,67] was hurt by net.minecraft.util.DamageSource@1817f61b how is that possible!
[00:12:08] [server thread/INFO]: Item just got hurt! Nooohs!
[00:12:08] [server thread/INFO]: Item just died! Yes!
[00:12:08] [server thread/INFO]: Wow! RCEntityItem['item.item.enderPearl'/336817, l='New World', x=286,43, y=4,13, z=-301,18] was hurt by net.minecraft.util.DamageSource@4d1e72f8 how is that possible!
[00:12:08] [server thread/INFO]: Item just got hurt! Nooohs!
[00:12:08] [server thread/INFO]: Item just died! Yes!
[00:12:08] [server thread/INFO]: Wow! RCEntityItem['item.item.enderPearl'/341630, l='New World', x=284,02, y=4,13, z=-301,28] was hurt by net.minecraft.util.DamageSource@a0e1f56 how is that possible!
[00:12:08] [server thread/INFO]: Item just got hurt! Nooohs!
[00:12:08] [server thread/INFO]: Wow! RCEntityItem['item.item.enderPearl'/350647, l='New World', x=284,38, y=4,13, z=-303,00] was hurt by net.minecraft.util.DamageSource@252b4c42 how is that possible!
[00:12:08] [server thread/INFO]: Item just got hurt! Nooohs!

 

The last two who got processed in the EventBus didn't get destroyed, simply hurt. So I call this a success. Now, just to get rid of the "[server thread/WARN]: Fetching addPacket for removed entity"-spam. What I also notice is a the glimpse of both stacks hovering in front of the players face while both exist for that one single tick. Which is not that extremely painful to watch, but still it irritates the perfectionist inside of me.

 

Or hope this: https://github.com/MinecraftForge/MinecraftForge/pull/1274 , pullrequest is getting through fast.

 

Call this one solved, will still post whenever I can fix the 'double-stack-render-glitch' and the console spam.

"I guess as this is pretty much WIP, I can just 'borrow' the sounds from that game without problem. They are just 'placeholders' anyway." -Me, while extracting the Tear sounds of Bioshock Infinite.

Link to comment
Share on other sites

This is the final version of the code which handles the custom EntityItem. The solution to the 'Removed packet' is simply canceling the event.

Back in my bukkit modding days I remembered it would cancel the whole processing of the event. Event things modified/added/spawned in the event-handler. I expected it would back-track everything: undo the spawn, undo the drop, and return the item to the itemstack. In short, I kept myself from it. But in the end it was the answer for the console spam.

 

News from the Pull Request which will make this unified under Forge is still pending. It could be my own bad. I was a bit snarky towards people criticizing my code. I am used to defending my code by following the document to the letter. There was just no information about what to do when coding for a .java.patch file. I feel I should apologize, but I think it will not matter anyway.

 

package nl.scribblon.riftcraft.handler;

import cpw.mods.fml.common.eventhandler.Event;
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
import net.minecraft.client.Minecraft;
import net.minecraft.entity.Entity;
import net.minecraft.entity.item.EntityItem;
import net.minecraft.init.Items;
import net.minecraft.item.Item;
import net.minecraft.item.ItemEnderPearl;
import net.minecraft.item.ItemStack;
import net.minecraft.world.World;
import net.minecraftforge.event.entity.EntityJoinWorldEvent;
import net.minecraftforge.oredict.OreDictionary;
import nl.scribblon.riftcraft.entity.RCEntityItem;
import nl.scribblon.riftcraft.event.EntityItemDeathEvent;
import nl.scribblon.riftcraft.event.EntityItemHurtEvent;
import nl.scribblon.riftcraft.init.ModItems;
import nl.scribblon.riftcraft.reference.Settings;
import nl.scribblon.riftcraft.util.LogHelper;
import nl.scribblon.riftcraft.util.RandomHelper;

import java.util.Random;

/**f
* Created by Scribblon for RiftCraft.
* Date Creation: 31-7-2014
* More ore less the crafting mechanic for EnderShards. A multi-block will also be added to simplify things, but that is also a 'carfting by world-interaction'- thingy
*/
public class EnderPearlHandler {

    //TODO extract to reference (?)
    //SNIPED many public statics which is probably there until it gets extracted to the reference package

    @SubscribeEvent
    public void onEntityJoinWorld(EntityJoinWorldEvent event){
        //Simple checks for quick returns, could not be memory friendly.
        if (event.world.isRemote) return;

        if (!(event.entity instanceof EntityItem)) return;

        if (event.entity instanceof RCEntityItem){
            if(Settings.Debug.isDebugging)
                LogHelper.info("RCEntityItem just entered the world! " + event.entity);
            return;
        }

        //if (Settings.Debug.isDebugging) LogHelper.info("EntityItemJoinWorldEvent Triggered! " + event.entity);

        //if(event.entity.ticksExisted < 1) return;

        //Process entityItem
        EntityItem entityItem = (EntityItem) event.entity;

        //Again checking if items has been removed since the event triggered.
        if (entityItem.getEntityItem() == null) return;

        //if (Settings.Debug.isDebugging) LogHelper.info("Is this an EnderPearl? " + (entityItem.getEntityItem().getItem().getUnlocalizedName().equals(Items.ender_pearl.getUnlocalizedName())));

        //Check if item in question is an EnderPearl
        if (isEnderPearl(entityItem)){
            RCEntityItem rcEntityItem = RCEntityItem.convert(entityItem);
            event.setCanceled(true);
            event.world.spawnEntityInWorld(rcEntityItem);
        }
    }

    @SubscribeEvent
    public void onItemDeath(EntityItemDeathEvent event){
        if(Settings.Debug.isDebugging) LogHelper.info("ItemDeath Triggered");

        if (isEnderPearl(event.entityItem) && event.damageSource.isExplosion()){
            //Calculate chances

            int spawnTotal = 0;

            for(int i = 0; i < event.entityItem.getEntityItem().stackSize; i++) {
                if (RandomHelper.rollD100(EXPLOSION_FIRST_CHANCE)) {
                    ++spawnTotal;
                    if (RandomHelper.rollD100(EXPLOSION_SECOND_CHANCE)) {
                        ++spawnTotal;
                        if (RandomHelper.rollD100(EXPLOSION_THIRD_CHANCE)) {
                            ++spawnTotal;
                            if (RandomHelper.rollD100(EXPLOSION_FOURTH_CHANCE))
                                ++spawnTotal;
                        }
                    }
                }
            }

            if(Settings.Debug.isDebugging) LogHelper.info("Chances rolled: " + spawnTotal);

            if(spawnTotal > 0){
                event.entity.worldObj.spawnEntityInWorld(createShardEntity(event.entity.worldObj, event.entity, spawnTotal));
            }
        }
    }

    private boolean isEnderPearl(EntityItem entityItem){
        return entityItem.getEntityItem().getItem().getUnlocalizedName().equals(Items.ender_pearl.getUnlocalizedName());
    }

    private EntityItem createShardEntity(World world, Entity entity, int amount){
        return new EntityItem(world, entity.posX, entity.posY, entity.posZ, new ItemStack(ModItems.enderShard, amount));
    }

}

"I guess as this is pretty much WIP, I can just 'borrow' the sounds from that game without problem. They are just 'placeholders' anyway." -Me, while extracting the Tear sounds of Bioshock Infinite.

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Unfortunately, your content contains terms that we do not allow. Please edit your content to remove the highlighted words below.
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Announcements



×
×
  • Create New...

Important Information

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