Getting block hit by projectile (arrow)?

Discussion in 'Plugin Development' started by Mazdah, Dec 9, 2011.

Thread Status:
Not open for further replies.
  1. Offline

    Mazdah

    Is there any good way to calculate what block an arrow is stuck to? Search didn't yield any results. The best hits I got where http://forums.bukkit.org/threads/hook-arrow-hitting-block.3385/ and http://forums.bukkit.org/threads/how-do-i-get-the-block-a-arrow-hit.31452/, which doesn't really solve my problem.

    I'm aware that there are plugins that "do" this, after looking through their code they are not very precise though (they pick a fitting block in the vicinity of the arrow), and I need the exact block hit.

    I'm using onProjectileHitEvent(), getting the arrows' location with .getLocation(). It seems to return very odd results though. I'm unsure if it returns the tip of the arrow, or somewhere in the middle of it. It also sometimes seems to return a location inside the hit block, rather than the tangent I'm looking for.

    Cheers for any suggestions!

    Edit:
    This problem was solved using the BlockIterator class (b1519+).
    yOffset (read this post) and maxDistance could probably be tweaked some for a more optimal result.
    arrow.getVelocity().normalize() can also be changed to arrow.getLocation().getDirection().normalize(), but I found the current setup to give more accurate results (backup'd by this post depending on the bukkit-code).

    Code:
    public void onProjectileHit(ProjectileHitEvent event) {
        if(!(projectile instanceof Arrow)) //Remove to check all projectiles
            return false;
    
        Arrow arrow = (Arrow)projectile;
        if(!(arrow.getShooter() instanceof Player)) //Making sure the shooter is a player
            return false;
    
        Player player = (Player) arrow.getShooter();
        World world = arrow.getWorld();
        BlockIterator iterator = new BlockIterator(world, arrow.getLocation().toVector(), arrow.getVelocity().normalize(), 0, 4);
        Block hitBlock = null;
    
        while(iterator.hasNext()) {
            hitBlock = iterator.next();
            if(hitBlock.getTypeId()!=0) //Check all non-solid blockid's here.
                break;
        }
    
        if(hitBlock.getTypeId()==35)
            player.sendMessage("You hit wool!");
    }
     
    xxyy98 and Mitsugaru like this.
  2. If you have a location, isn't it a simple matter of calling getBlockX(), getBlockY() and getBlockZ() to get a specific block? Not sure what block that would yield though.
     
  3. Offline

    Mazdah

    getBlockX/Y/Z() returns the block the arrow is inside of (the air block). I need the block that the arrow is stuck to/hits.

    I've been fiddling around with using the velocity vector of the arrow (which returns the velocity the arrow had in air). I'm having problems on how to calculate it though.
     
  4. Is the location always the air block where the arrow is? And what does the pitch and yaw give you from that location?

    Maybe (if you always get the air block) check how the arrow is pointed with yaw and pitch.

    Edit: Sheesh, my English sucks today!
     
  5. Offline

    Mazdah

    getPitch() and getYaw() seems to return the pitch and yaw of the arrow. Although the coords are a bit off sometimes, getLocation().getBlock() seems to always return the airblock.

    What confuses me is when the arrow is flying practically vertically, but hits the top of a block (almost lying on-top of it). I assume I'm missing something obvious though, I'm not in top gear at the moment. :p
     
  6. Offline

    halley

    I did look into a 1.8-era CraftBukkit file set that I had lying around. It appears the net.minecraft.server.EntityArrow class has three un-deobfuscated private integers e, f, g, which seem to be the block it is stuck in, though they may be chunk-relative coordinates. They calculate the actual air block locX, locY, locZ after it gets stuck by "going back a little" along the path it came. You can look at the trigonometry that they're doing in the method w_() that tries to calculate this.

    this.e // block X stuck
    this.f // block Y stuck
    this.g // block Z stuck
    this.h // block type stuck
    this.i // block data stuck
    this.k // ticks since stuck
     
  7. Offline

    Mazdah

    I'm looking at the code from a 1.6.6 build (shouldn't have changed that much I presume). w_() seems to be the onUpdate() method. I'm trying to decipher how it operates. Any help is much appreciated though, as I'm not very familiar with this area.

    Edit:
    As you're saying it seems to backtrack out of the block it hit. So I should perhaps do the opposite and calculate what block the arrow was inside of before this collision check?

    Edit2:
    I got it "working" using the above method. Even though I move it by very small amounts I sometimes get results which are ~blocks off from where my arrow seems to hit.
    I'm thinking it might be the same reason as to why the arrows always seem to move a few blocks upwards after they hit (server related?).

    If I hit the middle of a tree-trunk, it works perfectly. If I hit the lowest block of the trunk, I get the grass as result. If I hit the highest block (right before the leaves), I get the leaves returned.

    Posting a test-snippet of the code:
    Code:
    Arrow arrow = (Arrow)projectile;
    Player p = (Player) arrow.getShooter();
    Location arrowLoc = arrow.getLocation();
    Vector vec = arrow.getVelocity().clone().normalize();
    
    vec.setX(vec.getX()*0.1);
    vec.setY(vec.getY()*0.1);
    vec.setZ(vec.getZ()*0.1);
    
    Location l = arrowLoc.getBlock().getLocation();
    
    while(world.getBlockAt(l).getTypeId()==0) {
        l.add(vec);
    }
    
    if(world.getBlockAt(l).getTypeId()==35)
        p.sendMessage("Hit wool!");
     
  8. Offline

    Father Of Time

    Now I am just going to throw this out there because I was trying to figure this out myself awhile ago and this was the first logical solution that came to mind, but I haven't tested it so who knows if it will work.

    Okay, so an arrow is traveling when shot, which means it has a velocity, which means it has a direction of travel. Therefore we can wait for it to impact with a block, get the air block the arrow is currently in, get the direction it was traveling from its velocity vector, get the block face relative to that direction and then get the block in that direction, and bingo, you should have the block you just shot.

    Again, I have no idea if this will work, but the key is figuring out what direction the arrow was traveling while it was flying, and then once it stops flying look in that direction for the relative block...

    Please report back if you try this and if it works, I would be curious to see if it's possible.
     
  9. Offline

    Mazdah

    I edited my post as you were typing yours. My tracing-method there is somewhat similar to what you're describing. Using the BlockFace might(should?) be more optimal though. I have no experience on how the class works, so some help would be helpful so that I can conduct some tests!
     
  10. Offline

    Father Of Time

    Which class? do you know how to get a direction from a vector?
     
  11. Offline

    Mazdah

    I'm a bit rusty but I should be able to handle the vector-part. I was referring to the BlockFace class :)
     
  12. Offline

    Father Of Time

    Okay, I just found some interesting stuff. So arrow has a Location (when it impacts), so do the arrow.getLocation(). Then location has a getDirection() which returns a vector set to the direction the location is facing (based off its pitch and yaw).

    So now you have the location the arrow is in and the direction the arrow was traveling, simply do a GetBlockAtLocation for the arrow location then do a getrelative and pass the vector direction you got from the arrow location above.

    Remember to get the arrows location to get the direction, not the block the arrow is residing in (block locations don’t have yaw/pitch).

    http://jd.bukkit.org/doxygen/da/dac/classorg_1_1bukkit_1_1Location.html
    http://jd.bukkit.org/doxygen/d7/db5/Arrow_8java_source.html#l00006

    This was poorly worded and written in a hurry, so my apologize if it's difficult to understand.

    Blockface is basically just a pre-defines direction:

    Code:
    00001 package org.bukkit.block;
    00002
    00006 public enum BlockFace {
    00007     NORTH(-1, 0, 0),
    00008     EAST(0, 0, -1),
    00009     SOUTH(1, 0, 0),
    00010     WEST(0, 0, 1),
    00011     UP(0, 1, 0),
    00012     DOWN(0, -1, 0),
    00013     NORTH_EAST(NORTH, EAST),
    00014     NORTH_WEST(NORTH, WEST),
    00015     SOUTH_EAST(SOUTH, EAST),
    00016     SOUTH_WEST(SOUTH, WEST),
    00017     WEST_NORTH_WEST(WEST, NORTH_WEST),
    00018     NORTH_NORTH_WEST(NORTH, NORTH_WEST),
    00019     NORTH_NORTH_EAST(NORTH, NORTH_EAST),
    00020     EAST_NORTH_EAST(EAST, NORTH_EAST),
    00021     EAST_SOUTH_EAST(EAST, SOUTH_EAST),
    00022     SOUTH_SOUTH_EAST(SOUTH, SOUTH_EAST),
    00023     SOUTH_SOUTH_WEST(SOUTH, SOUTH_WEST),
    00024     WEST_SOUTH_WEST(WEST, SOUTH_WEST),
    00025     SELF(0, 0, 0);
    00026
    00027     private final int modX;
    00028     private final int modY;
    00029     private final int modZ;
    00030
    00031     private BlockFace(final int modX, final int modY, final int modZ) {
    00032         this.modX = modX;
    00033         this.modY = modY;
    00034         this.modZ = modZ;
    00035     }
    00036
    00037     private BlockFace(final BlockFace face1, final BlockFace face2) {
    00038         this.modX = face1.getModX() + face2.getModX();
    00039         this.modY = face1.getModY() + face2.getModY();
    00040         this.modZ = face1.getModZ() + face2.getModZ();
    00041     }
    00042
    00047     public int getModX() {
    00048         return modX;
    00049     }
    00050
    00055     public int getModY() {
    00056         return modY;
    00057     }
    00058
    00063     public int getModZ() {
    00064         return modZ;
    00065     }
    00066
    00067     public BlockFace getOppositeFace() {
    00068         switch (this) {
    00069         case NORTH:
    00070             return BlockFace.SOUTH;
    00071
    00072         case SOUTH:
    00073             return BlockFace.NORTH;
    00074
    00075         case EAST:
    00076             return BlockFace.WEST;
    00077
    00078         case WEST:
    00079             return BlockFace.EAST;
    00080
    00081         case UP:
    00082             return BlockFace.DOWN;
    00083
    00084         case DOWN:
    00085             return BlockFace.UP;
    00086
    00087         case NORTH_EAST:
    00088             return BlockFace.SOUTH_WEST;
    00089
    00090         case NORTH_WEST:
    00091             return BlockFace.SOUTH_EAST;
    00092
    00093         case SOUTH_EAST:
    00094             return BlockFace.NORTH_WEST;
    00095
    00096         case SOUTH_WEST:
    00097             return BlockFace.NORTH_EAST;
    00098
    00099         case WEST_NORTH_WEST:
    00100             return BlockFace.EAST_SOUTH_EAST;
    00101
    00102         case NORTH_NORTH_WEST:
    00103             return BlockFace.SOUTH_SOUTH_EAST;
    00104
    00105         case NORTH_NORTH_EAST:
    00106             return BlockFace.SOUTH_SOUTH_WEST;
    00107
    00108         case EAST_NORTH_EAST:
    00109             return BlockFace.WEST_SOUTH_WEST;
    00110
    00111         case EAST_SOUTH_EAST:
    00112             return BlockFace.WEST_NORTH_WEST;
    00113
    00114         case SOUTH_SOUTH_EAST:
    00115             return BlockFace.NORTH_NORTH_WEST;
    00116
    00117         case SOUTH_SOUTH_WEST:
    00118             return BlockFace.NORTH_NORTH_EAST;
    00119
    00120         case WEST_SOUTH_WEST:
    00121             return BlockFace.EAST_NORTH_EAST;
    00122
    00123         case SELF:
    00124             return BlockFace.SELF;
    00125         }
    00126
    00127         return BlockFace.SELF;
    00128     }
    00129 }
    
    Basically you can get a block that is relative to another block using a BlockFace. To do it you would first declare a blockface ( BlockFace.North ), then once you know what face you want to check you would actually get the block in that direciton.

    Code:
    Block northblock = EventBlock.getRelative( BlockFace.North );
    The above code would set northblock variable to the block that is to the north of the EventBlock.

    EDIT by Moderator: merged posts, please use the edit button instead of double posting.
     
    Last edited by a moderator: May 22, 2016
  13. Offline

    Mazdah

    arrow.png
    Won't that result in the problem shown in the attached image?
    The arrows direction is towards the "right", so getting the relative block (from the airblock) would get the "wall" instead of the "ground"? Or am I wrong on how that would work?
     
  14. Offline

    Father Of Time

    Hm, no your logic is sound; it would likely return the block that is burried, the one to the right of the one it's actually attached to in that diagram... *thinks*

    BlockIterators, trace the line? that might work!

    Block iterators are awesome, you can give it a start location (the arrows location) a direction to shot a "ray" (the direction the arrows location was traveling) and the distance to travel (1 block).

    This will take the arrow location, look in the direction the arrow was traveling and shot a "ray" out, and any block that ray intersects with return as a Block[] array. So you can simply make the ray shot only one block, and what ever block it hits is the block the arrow would have intersected with. And this won't just look "down" or "north", but rather the exact vector direction the arrow was traveling, so if the arrow clips a group block then the ray should too.

    Again I have no idea if this will work, but it's an option to try. :D

    EDIT by Moderator: merged posts, please use the edit button instead of double posting.
     
    Last edited by a moderator: May 22, 2016
  15. Offline

    Mazdah

    Didn't know such a class existed :D
    So basically my attempted method in the previous post but in a (likely) better way :)

    I tried switching to a BlockIterator, it seems to yield better results than tracing it manually. There are still some marginal errors, but I think that's because of the Minecraft/server code. So this will probably have to do!

    I'll post the code in a min.

    Edit:
    I can probably tweak this a bit more (like the maxDistance, which I had to increase slightly)
    Other than that, it seems pretty precise! Even though I hit near a corner of two blocks it can determine which one was hit :)

    Thanks a lot!

    Code:
    public void onProjectileHit(ProjectileHitEvent event)
    {
        if(!(projectile instanceof Arrow))
            return false;
    
        Arrow arrow = (Arrow)projectile;
        Player p = (Player) arrow.getShooter();
        World world = arrow.getWorld();
        BlockIterator bi = new BlockIterator(world, arrow.getLocation().toVector(), arrow.getVelocity().normalize(), 0, 4);
        Block hit = null;
    
        while(bi.hasNext())
        {
            hit = bi.next();
            if(hit.getTypeId()!=0) //Grass/etc should be added probably since arrows doesn't collide with them
            {
                break;
            }
        }
        if(hit.getTypeId()==35)
            p.sendMessage("Hit wool!");
    }
     
    Father Of Time likes this.
  16. Offline

    Father Of Time

    That is fantastic, I'm extremely happy it's working. Also, thank you for taking the time and spending the effort to report your findings and even providing the public with code snippets for future users; that is a respectable forum trait. :)

    The one variable we overlooked is the Offset variable, which might help "fine tune" the trajectory if it's slightly off. Basically the offset is for when you use a players location as the starting point. Say you wanted to do the trace from the block you are standing on, not the air block you are standing in, then you would pass -1 to the offset variable to make the trace begin 1 Y axis down. As you stated before, who knows where the actual "center" of the arrow is (its arrow head, the center of the animation, etc.) so it might be worthwhile to manipulate that variable a bit to see if you get more precise results. I'm not claiming this will help anything, and in fact it will likely just break it; but it doesn't hurt to mess around with it (set it to 1, 0, -1) just to see how it impacts your project.

    Anyways, again thanks for sharing your code and doing the heavy lifting, your efforts are appreciated by me and I'm sure many more here at Bukkit forums. :D

    Just something I noticed in heinseight. for your third arguement Direction you use the following:

    Code:
    arrow.getVelocity().normalize()
    I'm wondering if this might have better results (although I think they will be identical):

    Code:
    arrow.getLocation().getDirection().normalize()
    I think velocity is pretty much the same vector as your location, and if so the results will be the same; but out of curiosity sake I would personally try both methods to see if one offers more accurate results than the other.

    Either way, congrats on the success!

    EDIT by Moderator: merged posts, please use the edit button instead of double posting.
     
    Last edited by a moderator: May 22, 2016
  17. Offline

    Mazdah

    I know how irritating it is to find a thread which ends in a post like "Nvm I figured it out", so I don't mind helping anyone who might stumble on the same path :) I'll be adding the code-snippet to the first post, just wondering if there is any way to determine if a block is solid or not (Grass, torches etc aren't).

    Yeah I noticed that parameter, and as you said I'm unsure of where the arrows actual location is. However, as the yOffset depends on the arrows pitch (and for simplicity), I decided to leave it as 0. It seems to work fine with the current setup. It deserves some testing though :)

    I tried both getDirection() and getVelocity() before I posted the snippet. getVelocity() gave a lot more accurate results, but I should've probably mentioned it.

    Always glad to be able to contribute :)
     
    Father Of Time and dadaemon like this.
  18. Offline

    halley

    Without looking, isn't Direction the euler angles (yaw, pitch), and Velocity the cartesian vector (delta x, delta y, delta z)? Velocity is a vector which normalization would help some calculations. Direction requires trigonometry to convert to velocity or relative positions.
     
  19. Offline

    Mazdah

    Haven't looked at the source nor done any tests. But if it's true it'd backup the current implementation.
     
  20. Offline

    Father Of Time

    As mazdah stated, I didn't have any time to actually research the difference between these two functions (At work), I was only going off assumptions based off the function names... not the best thing to do! ;)

    God I wish I've had some trigonometry classes by now, every time someone gets into the nitty gritty of ray casting, trajectories and anything of that nature it begins to quickly spiral into terminology that is lost on me.

    I first ran into all of that while programming a 3D gaming engine in C# (XNA); being exposed to matrices and everything associated with them was.... an eye opener to the mechanics of 3D gaming to say the least (without any previous knowledge of trig).

    And that concludes my aimless ranting...
     
  21. Offline

    Stevenpcc

    This isn't possible just using the bukkit api, but requires using craftbukkit methods directly.

    I've made a plugin that fires an event when an arrow hits a block. Source code is included if you'd rather implement it yourself.

    http://dev.bukkit.org/bukkit-plugins/arrowhitblockevent/
     
Thread Status:
Not open for further replies.

Share This Page