Skip to content

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expose a method to support setting/placing BlockState to a specific Location #9720

Closed
anjoismysign opened this issue Sep 13, 2023 · 1 comment
Labels
type: feature Request for a new Feature.

Comments

@anjoismysign
Copy link

Is your feature request related to a problem?

There are some cases in which BlockState is provided but it's not "fine". Fine in terms of being able to invoke BlockState#update.
One of such cases are https://jd.papermc.io/paper/1.20/org/bukkit/structure/Structure.html
Structures have a Palette, a BlockState collection with each BlockState having its Location as an offset from an origin.
There's an issue with Structure#place. This is done in a single tick and this affects performance (main thread) as bigger it gets the Structure.
A solution for this would be splitting through multiple ticks, then we are found at the issue that TileState entities cannot copy their data (since with current API we can only set/change Material and BlockData)

It would also allow not having to depend on WorldEdit or schematics since it would rely on the API

Describe the solution you'd like.

It seems to be possible to solve the issue through NMS. I attempted doing it later this afternoon but I still got no results. My approach was adding a fourth BlockState#update method which allows passing a Location parameter, being this location the desired location in which the operation would be done instead of BlockState#getLocation

Another approach might be exposing a method either in RegionAccessor or World API interfaces

Describe alternatives you've considered.

Not relying on TileEntities...

Other

No response

@anjoismysign anjoismysign added status: needs triage type: feature Request for a new Feature. labels Sep 13, 2023
@anjoismysign
Copy link
Author

anjoismysign commented Sep 14, 2023

I was able to figure out a way of going around it. It forces using reflection. I'll provide details:

In a nutshell, it requires exposing in the method both CraftBlockState's "position" and "world" fields.
Then in the new method (as I suggested as an approach), which would be update(boolean,boolean,@nullable Location)
the idea would be that if Location is null, to execute as it normally does. Else, if passed Location is not null, it would make an instance of BlockPosition passing X,Y,Z of Location.
Then it temporarily sets BlockPosition and World from Location (can check if Location is holding a null World, if so throw). It also requires CraftBlockState storing weak reference of Location's World.
Doing so, you can do update(boolean,boolean) to return update(boolean,boolean,@nullable Location)
It's important to store temporarily these variables since if not, it would permanently modify the Location of a BlockState. I've not checked the data structure of Structures but it might be the case that they would require loading from an InputStream each time it requires placing (like in my case)

Here's in Java, excuse if it's messy 🦦

public class BlockStateDecorator {
    private final CraftBlockState blockState;
    private final static Field positionField;

    static {
        try {
            positionField = CraftBlockState.class.getDeclaredField("position");
        } catch (NoSuchFieldException e) {
            throw new RuntimeException(e);
        }
    }

    private final static Field worldField;

    static {
        try {
            worldField = CraftBlockState.class.getDeclaredField("world");
        } catch (NoSuchFieldException e) {
            throw new RuntimeException(e);
        }
    }

    @NotNull
    public static BlockStateDecorator of(BlockState blockState) {
        return new BlockStateDecorator((CraftBlockState) blockState);
    }

    private BlockStateDecorator(CraftBlockState blockState) {
        this.blockState = blockState;
    }

    /**
     * Gets the implementation of BlockState
     *
     * @return the implementation of BlockState
     */
    public CraftBlockState getImplementation() {
        return blockState;
    }

    /**
     * Allows updating a block state in a specific location
     * without changing the original block state's location.
     *
     * @param force        true to forcefully set the state
     * @param applyPhysics false to cancel updating physics on surrounding blocks
     * @param location     the location to update the block state at
     * @return true if the update was successful, false otherwise
     */
    public boolean update(boolean force, boolean applyPhysics, @NotNull Location location) {
        CraftWorld world = (CraftWorld) Objects.requireNonNull(location.getWorld());
        GeneratorAccess oldAccess = world.getHandle();
        blockState.setWorldHandle(world.getHandle());
        BlockPosition blockPosition = new BlockPosition(location.getBlockX(), location.getBlockY(), location.getBlockZ());
        positionField.setAccessible(true);
        worldField.setAccessible(true);
        BlockPosition oldPosition;
        CraftWorld oldWorld;
        boolean update = false;
        try {
            oldPosition = (BlockPosition) positionField.get(blockState);
            oldWorld = (CraftWorld) worldField.get(blockState);
            positionField.set(blockState, blockPosition);
            worldField.set(blockState, world);
            update = blockState.update(force, applyPhysics);
            worldField.set(blockState, oldWorld);
            positionField.set(blockState, oldPosition);
        } catch (IllegalAccessException ignored) {
            Bukkit.getLogger().severe("Failed to update block state");
        }
        positionField.setAccessible(false);
        blockState.setWorldHandle(oldAccess);
        return update;
    }
}

@PaperMC PaperMC locked and limited conversation to collaborators Apr 28, 2024
@codebycam codebycam converted this issue into discussion #10590 Apr 28, 2024

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

Labels
type: feature Request for a new Feature.
Projects
None yet
Development

No branches or pull requests

2 participants