Block Place Event

The first event handler we will write is the block place event handler. We begin with the following code snippet:

@SubscribeEvent
public void onBlockPlacement(BlockEvent.PlaceEvent e) {

}

A few things to note:

We now grab the World, which allows for querying blocks and entities, as well as a BlockPos denoting the position where a block is being placed.

@SubscribeEvent
public void onBlockPlacement(BlockEvent.PlaceEvent e) {

        World w = e.world;
        BlockPos pos = e.pos;
}

Now, we define another helper function which tells us whether a block position, taken as a 3-D Vector, is within an Axis Aligned Bound Box:

public boolean pointInAABB(AxisAlignedBB aabb, Vec3 vec) {
        return (aabb.minX <= vec.xCoord && aabb.maxX >= vec.xCoord &&
                aabb.minY <= vec.yCoord && aabb.maxY >= vec.yCoord &&
                aabb.minZ <= vec.zCoord && aabb.maxZ >= vec.zCoord);
}

We use the above function to cancel block place events that have the block position as outside the player structure boundary:

@SubscribeEvent
public void onBlockPlacement(BlockEvent.PlaceEvent e) {

        World w = e.world;
        BlockPos pos = e.pos;

        if(!pointInAABB(playerStructureAABB, new Vec3(pos.getX(), pos.getY(), pos.getZ()))) {
            e.setCanceled(true);
            return;
        }
}

Next, we find the corresponding goal structure block position and calculate the reward to get the following. Note below that some Java code comments, denoted with //, have been added for a better understanding.

@SubscribeEvent
public void onBlockPlacement(BlockEvent.PlaceEvent e) {

    World w = e.world;
    BlockPos pos = e.pos;

    // Cancel the build event if the player is trying to place a
    // block outisde the player structure boundary.
    if(!pointInAABB(playerStructureAABB, new Vec3(pos.getX(), pos.getY(), pos.getZ()))) {
        e.setCanceled(true);
        return;
    }

    BlockPos goalPos = pos.add(delta);

    // This block was air, and a correct block has been placed (Positive Reward).
    if (w.getBlockState(pos).equals(w.getBlockState(goalPos))) {

        // When a correct block is placed, change the state of both the 
        // block being placed and the corresponding goal structure block to 
        // blockTypeOnCorrectPlacement
        if(blockTypeOnCorrectPlacement != null) {

            // blockStateSave store the current goal block state before it 
            // changes to the blockTypeOnCurrentPlacement. This will be used 
            // when a player tries to break the block being placed now to 
            // change the corresponding goal block back to what it was.
            blockStateSave.put(goalPos, w.getBlockState(goalPos));

            w.setBlockState(pos, blockTypeOnCorrectPlacement);
            w.setBlockState(goalPos, blockTypeOnCorrectPlacement);
        }

        // If the Reward Density is PER_BLOCK or MISSION_END, add the 
        // delta change in exact similarity (1 / Structure Volume) 
        if(rewardDensity == RewardDensityForBuildAndBreak.PER_BLOCK || 
           rewardDensity == RewardDensityForBuildAndBreak.MISSION_END)
            reward += 1 .0F/ structureVolume;

        // Else, the reward density is ACCUMULATED. Add the total similarity to the reward. 
        // Store the total similarity to bootstrapReward.
        else if(rewardDensity == RewardDensityForBuildAndBreak.ACCUMULATED) {
            reward = reward + (reward + 1.0F / structureVolume);
            bootstrapReward += 1.0F / structureVolume;
        }
    }

    else {
        // 1. Case where both player structure blocks and goal structure blocks were Air,
        // no need to place a block (Reward Penalty).
        // 2. Case where the block being placed is not the same as the goal structure block.
        if(rewardDensity == RewardDensityForBuildAndBreak.PER_BLOCK || 
           rewardDensity == RewardDensityForBuildAndBreak.MISSION_END)
                reward -= 1 / structureVolume;
        else if(rewardDensity == RewardDensityForBuildAndBreak.ACCUMULATED) {
                reward = (reward - (1.0F / structureVolume)) / 2.0F;
                bootstrapReward -= 1.0F / structureVolume;
        }

        if(blockTypeOnIncorrectPlacement != null) {
            w.setBlockState(pos, blockTypeOnIncorrectPlacement);
        }
    }
}

Block Break Event

The second event handler we will write is very similar, the code for which is given below.

@SubscribeEvent
public void onBlockBreak(BlockEvent.BreakEvent e) {
    World w = e.world;
    BlockPos pos = e.pos;

    if(!pointInAABB(playerStructureAABB, new Vec3(pos.getX(), pos.getY(), pos.getZ()))) {
        e.setCanceled(true);
        return;
    }

    BlockPos goalPos = pos.add(delta);

    // Case where the block states matched, no need to break 
    // the player structure block (Reward Penalty).
    if (w.getBlockState(pos).equals(w.getBlockState(goalPos))) {
        if(rewardDensity == RewardDensityForBuildAndBreak.PER_BLOCK || 
           rewardDensity == RewardDensityForBuildAndBreak.MISSION_END)
            reward -= 1 / structureVolume;
        else if(rewardDensity == RewardDensityForBuildAndBreak.ACCUMULATED) {
            reward = (reward - (1 / structureVolume)) / 2;
            bootstrapReward -= 1 / structureVolume;
        }

        if(blockTypeOnCorrectPlacement != null) {
            w.setBlockState(goalPos, blockStateSave.get(goalPos));
        }
    }

    else
    {
        // Goal Structure block is an Air Block or is a different block than the 
        // player structure block. Breaking increases similarity (Positive Reward).
        if(w.isAirBlock(goalPos)) {
            if (rewardDensity == RewardDensityForBuildAndBreak.PER_BLOCK || 
                rewardDensity == RewardDensityForBuildAndBreak.MISSION_END)
                reward += 1 / structureVolume;
            else if (rewardDensity == RewardDensityForBuildAndBreak.ACCUMULATED) {
                reward = reward + (reward + 1 / structureVolume);
                bootstrapReward += 1 / structureVolume;
            }
        }
    }
}