Solved How to know when a player enters a structure?

Discussion in 'Spigot Plugin Development' started by Swiftlicious, Jul 6, 2020.

Thread Status:
Not open for further replies.
  1. I'm trying to detect when a player enters any of the pre made structure types like an end city, nether fort, village, jungle/desert temple, ocean monuments, strongholds, etc...

    I feel like it is possible since an achievement exists when you enter a nether fort or stronghold..
    There's also a command called /locate I believe that lets you locate one of the nearest structures..I rather not do something like detect on player move event if they're in the nether and standing on a nether brick block as that could lead to so many false checks.

    If this is achievable using nms does anyone know where I could look for it
  2. You could run a check every 10/30 seconds or so that compares a players location to that of a specific structure.

    For example, you can get the location of a mansion via World#locateNearestStructure(...) and then check if a player is within say 50 blocks of that location and do whatever you wanted to do.
    • Like Like x 1
  3. how would I get the structure type the player is in compared to the one I'm checking for?
    Code (Text):
                            Location structure = player.getWorld().locateNearestStructure(player.getLocation(), getStructureName(ID), 2, true);
                                if (structure??? == structureTypeFromConfig???) {
                                    // do stuff
  4. I don't think this would work, cause if I am not wrong the way the game detects the stronghold, is by the stair and if the stronghold is big, and the stairs is in one side of the stronghold, and the player is in the other side, this method wont detect it.
    And in the village I think it is the well.
  5. is there another solution then for this?
  6. I've had a look around and I think the best solution, for now, is probably #locateNearestStructure and just use a generous radius - perhaps with other checks.
    For example, when checking if they're near a stronghold you could check how far away they are in terms of Y, or if they're near a villager when nearby a village (though these are not the most accurate).

    #locateNearestStructure ( takes an input of StructureType ( so you can specify the type of structure you wish to check for there. (Be sure to read the java docs for this method)

    The only thing I can think of as an alternative is to come up with your own checks, possibly to use alongside or alone.
    • Informative Informative x 1
  7. Idk as Reparo said, I think it is the best solution for now.
  8. There is another solution, but it involves NMS. I answered this in another thread, unfortunately I can‘t find it anymore. Have a look at the implementation of World#locateNearestStructure(). Information about BoundingBoxes of structures are stored within each chunk; that might help you...
    • Agree Agree x 1
    • Informative Informative x 1
  9. Did some digging around in nms to further add to what @Schottky said, you can get the bounding box for a specific structure type via

    Code (Text):
    • The 'chunk' variable is nms
    • Chunk#a(StructureGenerator) gets the start of the structure which contains variables for its chunk coordinates (StructureStart#f() for X and and g() for Z)
    • .c() gets the bounding box
    You can go here for more info about what the methods do.
    • Informative Informative x 1
    Are you referring to this thread? They did seem to have a solution in the end so I'll try out what they did.
    • Like Like x 1
  11. Is the Chunk variable referring to "net.minecraft.server.v1_15_R1.Chunk" because I can't reference any such method if so trying for example:
    Code (Text):
    I could only find these two:
    Code (Text):
    chunk.a(Map<String,StructureStart> map)
    Code (Text):
    chunk.a(String s, StructureStart structurestart)
    after casting a chunk to
    Code (Text):
    Chunk chunk = (Chunk) player.getWorld().getChunkAt(nearestStructureLocation)
    I can however do "StructureStart.a.c()" to get the bounding box it looks like of the one I'm closest to but yes it would be better if I could decide which one I am trying to get though. "StructureStart.a.f()" and "StructureStart.a.g()" however return as 0.
    For coordinates though these seem really like high numbers..
    #11 Swiftlicious, Jul 7, 2020
    Last edited: Jul 7, 2020
  12. Apologies, my bad for not reading the thread's version tag! I was referring to 1.16.

    You'll definitely want to use this method in the case of 1.15.2:
    Code (Text):
    chunk.a(String s)
    This method returns a StructureStart object, however im not too sure what you need to put for the string. I'll have a look around and get back to you on this. I'd assume its just some key e.g. "nether_fortress" but who knows.
    • Like Like x 2
  13. I just used my structureType's getName() and it worked but I had to instead use Chunk.h().get(structureName).c() to get the StructureBoundingBox, my only wonder is now how can I check if the player is inside of the structure's BoundingBox.. I saw a few boolean options from the StructureBoundingBox but after testing they all returned true even if I wasn't near the structure. I could assume I would have to check the player's BoundingBox to the structure's BoundingBox. but I'm not sure. I don't see many things to go off of.

    There is a method that returns a boolean which is something like:
    Chunk.h().get(structureName).c().a(val1, val2, val3, val4) which I could assume stands for the minimum and maximum x and z coordinates but I don't have much of a way to check those player wise either to my knowledge.
  14. I think there are a couple ways to do this.

    There is a built in method StructureBoundingBox#b(BaseBlockPosition) which i believe returns true if that position is inside of the bounding box. You can create a new position by making a new instance of that object using the players coordinates.

    You could also use the lengths of the box to construct two points and manually do a check if the players coordinates are between the two points. StructureBoundingBox#c(), d(), and e() get the x, y, and z length.

    I tried the first method and it seemed to work for me, however the boundingbox was throwing an NPE until i loaded in the chunk of the ruined portal so just be aware. I guess since the chunk wont be loaded until the player is near it anyways, you can probably just assume they aren't inside the structure.
    Code (Text):
    World world = ((CraftWorld) p.getWorld()).getHandle();
    Chunk chunk = world.getChunkAt(p.getLocation().getChunk().getX(), p.getLocation().getChunk().getZ());
    boolean isInside = chunk.a(StructureGenerator.RUINED_PORTAL).c() != null && chunk.a(StructureGenerator.RUINED_PORTAL).c().b(new BaseBlockPosition(p.getLocation().getX(), p.getLocation().getY(), p.getLocation().getZ()));
    if (isInside) {
        p.sendMessage("Player is inside the box!");
    } else {
        p.sendMessage("Player is outside the box!");

    Sorry this is in 1.16, i don't currently have a 1.15 work environment but im sure it can be adjusted.
    #14 Secil, Jul 9, 2020
    Last edited: Jul 9, 2020
    • Like Like x 1
  15. I might just be dumb but tried it with your code, not much changed of course but well if I do this for let's say a bigger structure like a nether fortress.. a lot of the fortress returns false while only a few blocks of it return true. It is very weird.
    And this only like a few blocks away from each other while still in the fortress.
    Code (Text):
                    net.minecraft.server.v1_15_R1.World world = ((CraftWorld) player.getWorld()).getHandle();
                    net.minecraft.server.v1_15_R1.Chunk chunk = world.getChunkAt(player.getLocation().getChunk().getX(), player.getLocation().getChunk().getZ());
                    boolean isInside = chunk.h().get(ItemCreator.capitalizeFully(structure.getName())).c() != null && chunk.h().get(ItemCreator.capitalizeFully(structure.getName())).c().b(new BaseBlockPosition(player.getLocation().getX(), player.getLocation().getY(), player.getLocation().getZ()));
    I changed it to this instead and it seemed to work:
    Code (Text):
                    net.minecraft.server.v1_15_R1.Chunk structureChunk = ((org.bukkit.craftbukkit.v1_15_R1.CraftChunk) player.getWorld().getChunkAt(closestStructure)).getHandle();
                    net.minecraft.server.v1_15_R1.StructureBoundingBox boundingBox = structureChunk.h().get(ItemCreator.capitalizeFully(structure.getName())).c();
                    boolean isInside = boundingBox != null && boundingBox.b(new BaseBlockPosition(player.getLocation().getX(), player.getLocation().getY(), player.getLocation().getZ()));
    #15 Swiftlicious, Jul 9, 2020
    Last edited: Jul 9, 2020
  16. Just make sure whatever you end up doing isn't essentially a copy of the locateNearestStructure method! :)
  17. well I assume it would have to be near it but i'm not quite sure, it's kinda working but kinda not so maybe i don't need to use the locateNearestStructure at all.

    The problem is big structures don't seem to be very accurate such as a nether fortress or a stronghold where smaller ones like a swamp hut or an igloo seem to be right on spot.
    #17 Swiftlicious, Jul 9, 2020
    Last edited: Jul 9, 2020
  18. Hmm it looks like a StructureStart has a list of StructurePieces which also have a bounding box, so i'd assume larger structures are made up of multiple pieces.
    • StructureStart#d() - returns list of pieces
    • StructurePiece#g() - returns its StructureBoundingBox
  19. yeah, stuff like end temple or nether fortress consist of multiple parts, that's why you only get the advancement when you actually collide with it
  20. so am I ok to assume if StructurePiece is null that the structure is something that can listen to just the singular StructureBoundingBox or do all structures happen to return a List of StructureStarts which I can get the bounding box from using g(). I'll test this out now just to be sure it does work of course.

    I tried
    Code (Text):
                net.minecraft.server.v1_15_R1.World world = ((org.bukkit.craftbukkit.v1_15_R1.CraftWorld) player.getWorld()).getHandle();
                net.minecraft.server.v1_15_R1.Chunk chunk = world.getChunkAt(player.getLocation().getChunk().getX(), player.getLocation().getChunk().getZ());
                net.minecraft.server.v1_15_R1.StructureStart structureStart = chunk.h().get(ItemCreator.capitalizeFully(structure.getName())); // <-- turns the structure name from "fortress" to "Fortress" or "swamp_hut" to "Swamp_Hut" etc
                List<net.minecraft.server.v1_15_R1.StructurePiece> structurePieces = structureStart.d();
                for (net.minecraft.server.v1_15_R1.StructurePiece structurePiece : structurePieces) {
                    net.minecraft.server.v1_15_R1.StructureBoundingBox boundingBox = structurePiece.g();
                    boolean isInsideStructureBox = boundingBox != null && boundingBox.b(new net.minecraft.server.v1_15_R1.BaseBlockPosition(player.getLocation().getX(), player.getLocation().getY(), player.getLocation().getZ()));
                    return isInsideStructureBox;
    and it made no difference, it's still only the main chunk that the /locate command recgonizes to result in this returning true, otherwise it's still false.
    #20 Swiftlicious, Jul 9, 2020
    Last edited: Jul 9, 2020
Thread Status:
Not open for further replies.