Skip to content

Commit

Permalink
Version 1.01!
Browse files Browse the repository at this point in the history
CHANGELOG:

[BUG FIX]
- Removed emulation of vanilla auto-repeat as having both systems confused some users

[FUTURE COMPAT]
- Holding right click and changing selected item will now block auto placement until right click is release and pressed again
- Added new check to make sure vanilla behavior is triggered when holding an 'usable' item
- Added new check to make sure vanilla behavior is trigger when looking at an 'activatable' block
- Added new double check to ensure no keystroke can ever be dropped by the system

Signed-off-by: Clayborn <4501752+orbwoi@users.noreply.github.com>
  • Loading branch information
orbwoi committed Jun 4, 2019
1 parent 8ce87ed commit 414cd0a
Show file tree
Hide file tree
Showing 5 changed files with 232 additions and 61 deletions.
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ org.gradle.jvmargs=-Xmx3G
loader_version=0.4.8+build.154

# Mod Properties
mod_version = 1.0.0
mod_version = 1.0.1
maven_group = net.clayborn
archives_base_name = accurate-block-placement

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package net.clayborn.accurateblockplacement;

public interface IKeyBindingAccessor {
int accurateblockplacement_GetTimesPressed();
}
Original file line number Diff line number Diff line change
@@ -1,29 +1,37 @@
package net.clayborn.accurateblockplacement.mixin;

import java.lang.reflect.Method;

import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;

import net.clayborn.accurateblockplacement.AccurateBlockPlacementMod;
import net.clayborn.accurateblockplacement.IKeyBindingAccessor;
import net.clayborn.accurateblockplacement.IMinecraftClientAccessor;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.render.GameRenderer;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.BlockItem;
import net.minecraft.item.Item;
import net.minecraft.item.ItemPlacementContext;
import net.minecraft.item.ItemStack;
import net.minecraft.item.ItemUsageContext;
import net.minecraft.util.Hand;
import net.minecraft.util.hit.BlockHitResult;
import net.minecraft.util.hit.HitResult;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;

@Mixin(GameRenderer.class)
public abstract class GameRendererMixin {

Hand lastBlockHoldingHand;
Hand handOfCurrentItemInUse;

private Boolean isItemToUseBlock(MinecraftClient client) {
private Item getItemInUse(MinecraftClient client) {
// have to check each hand
Hand[] hands = Hand.values();
int numHands = hands.length;
Expand All @@ -35,33 +43,155 @@ private Boolean isItemToUseBlock(MinecraftClient client) {
if (itemInHand.isEmpty()) {
// hand is empty try the next one
continue;
} else if (itemInHand.getItem() instanceof BlockItem) {
lastBlockHoldingHand = thisHand;
return true;

} else {
// item in use is not a block, abort!
return false;
handOfCurrentItemInUse = thisHand;
return itemInHand.getItem();
}

}

return null;
}

private static String getBlockActivateMethodName()
{
Method[] methods = Block.class.getMethods();

for (Method method: methods)
{
Class<?>[] types = method.getParameterTypes();
if (types.length != 6) continue;
if (types[0] != BlockState.class) continue;
if (types[1] != World.class) continue;
if (types[2] != BlockPos.class) continue;
if (types[3] != PlayerEntity.class) continue;
if (types[4] != Hand.class) continue;
if (types[5] != BlockHitResult.class) continue;

return method.getName();
}

return null;
}

private static final String blockActivateMethodName = getBlockActivateMethodName();

private Boolean doesBlockHaveOverriddenActivateMethod(Block block) {
if (blockActivateMethodName == null)
{
System.out.println("[ERROR] blockActivateMethodName is null!");
}

// TODO: consider cache of results

try {
return !block.getClass()
.getMethod(blockActivateMethodName, BlockState.class, World.class, BlockPos.class, PlayerEntity.class, Hand.class, BlockHitResult.class)
.getDeclaringClass().equals(Block.class);
} catch (Exception e) {
System.out.println("Unable to find block " + block.getClass().getName() + " activate method!");
return false;
}
}

private static String getItemUseMethodName()
{
Method[] methods = Item.class.getMethods();

for (Method method: methods)
{
Class<?>[] types = method.getParameterTypes();
if (types.length != 3) continue;
if (types[0] != World.class) continue;
if (types[1] != PlayerEntity.class) continue;
if (types[2] != Hand.class) continue;

return method.getName();
}

return null;
}

private static final String itemUseMethodName = getItemUseMethodName();

private Boolean doesItemHaveOverriddenUseMethod(Item item) {
if (itemUseMethodName == null)
{
System.out.println("[ERROR] itemUseMethodName is null!");
}

// TODO: consider cache of results

try {
return !item.getClass()
.getMethod(itemUseMethodName, World.class, PlayerEntity.class, Hand.class)
.getDeclaringClass().equals(Item.class);
} catch (Exception e) {
System.out.println("Unable to find item " + item.getClass().getName() + " use method!");
return false;
}

return false;
}

private static String getItemUseOnBlockMethodName()
{
Method[] methods = Item.class.getMethods();

for (Method method: methods)
{
Class<?>[] types = method.getParameterTypes();
if (types.length != 1) continue;
if (types[0] != ItemUsageContext.class) continue;

return method.getName();
}

return null;
}

private static final String itemUseOnBlockMethodName = getItemUseOnBlockMethodName();

private Boolean doesItemHaveOverriddenUseOnBlockMethod(Item item) {
if (itemUseOnBlockMethodName == null)
{
System.out.println("[ERROR] itemUseOnBlockMethodName is null!");
}

// TODO: consider cache of results

try {
return !item.getClass()
.getMethod(itemUseOnBlockMethodName, ItemUsageContext.class)
.getDeclaringClass().equals(BlockItem.class);
} catch (Exception e) {
System.out.println("Unable to find item " + item.getClass().getName() + " useOnBlock method!");
return false;
}
}

Item lastItemInUse = null;

@Inject(method = "net/minecraft/client/render/GameRenderer.updateTargetedEntity(F)V", at = @At("RETURN"))
private void onUpdateTargetedEntityComplete(CallbackInfo info) {

MinecraftClient client = MinecraftClient.getInstance();

// safety check
if (client == null || client.options == null || client.options.keyUse == null)
// safety checks
if (client == null ||
client.options == null ||
client.options.keyUse == null ||
client.hitResult == null ||
client.player == null ||
client.world == null)
{
return;
}

// will be set to true only if needed
AccurateBlockPlacementMod.disableNormalItemUse = false;
Boolean freshKeyPress = client.options.keyUse.wasPressed();
IKeyBindingAccessor keyUseAccessor = (IKeyBindingAccessor)(Object)client.options.keyUse;
Boolean freshKeyPress = keyUseAccessor.accurateblockplacement_GetTimesPressed() > 0;

Item currentItem = getItemInUse(client);

// reset state if the key was actually pressed
// note: at very low frame rates they might have let go and hit it again before we get back here
Expand All @@ -70,58 +200,74 @@ private void onUpdateTargetedEntityComplete(CallbackInfo info) {
// clear history since they let go of the button
AccurateBlockPlacementMod.lastSeenBlockPos = null;
AccurateBlockPlacementMod.lastPlacedBlockPos = null;
}

// safety check
if (client.hitResult == null || client.player == null)
{
return;

// a fresh keypress is required each time the item being used changes
lastItemInUse = currentItem;
}

// nothing do it if nothing in hand.. let vanilla minecraft do it's normal flow
if (currentItem == null) return;

// this this item isn't a block, let vanilla take over
if (!(currentItem instanceof BlockItem)) return;

Boolean isItemUsable = currentItem.isFood() || doesItemHaveOverriddenUseMethod(currentItem) || doesItemHaveOverriddenUseOnBlockMethod(currentItem);

// if the item we are holding is activatable, let vanilla take over
if (isItemUsable) return;

// if we aren't looking a block (so we can place), let vanilla take over
if (client.hitResult.getType() != HitResult.Type.BLOCK) return;

// did we find a block?
if (client.hitResult.getType() == HitResult.Type.BLOCK) {
BlockHitResult blockHitResult = (BlockHitResult) client.hitResult;
BlockPos blockHitPos = blockHitResult.getBlockPos();
Boolean isTargetBlockActivatable = doesBlockHaveOverriddenActivateMethod(client.world.getBlockState(blockHitPos).getBlock());

// don't override behavior of clicking activatable blocks
// unless holding SNEAKING to replicate vanilla behaviors
if (isTargetBlockActivatable && !client.player.isSneaking()) return;

// are they holding the use key and is the item to use a block?
// also is the the SAME item we started with if we are in repeat mode?
// note: check both freshKey and current state in cause of shitty frame rates
if ((freshKeyPress || client.options.keyUse.isPressed())) {

BlockHitResult blockHitResult = (BlockHitResult) client.hitResult;
BlockPos blockHitPos = blockHitResult.getBlockPos();

// are they holding the use key and is the item to use a block?
// note: check both freshKey and current state in cause of shitty frame rates
if ((freshKeyPress || client.options.keyUse.isPressed()) && isItemToUseBlock(client)) {
// it's a block!! it's go time!
AccurateBlockPlacementMod.disableNormalItemUse = true;

// if [ we are still holding the same block we starting pressing 'use' with] AND
// [ [ we have a fresh key press ] OR
// [ [ we have no 'seen' history or the 'seen' history isn't a match ] AND
// [ we have no 'place' history or the 'place' history isn't a match ] ] ]
// we can try to place a block
if (lastItemInUse == currentItem && // note: this is always true on a fresh keypress
(freshKeyPress ||
(
(AccurateBlockPlacementMod.lastSeenBlockPos == null
|| !AccurateBlockPlacementMod.lastSeenBlockPos.equals(blockHitPos))
&& (AccurateBlockPlacementMod.lastPlacedBlockPos == null
|| !AccurateBlockPlacementMod.lastPlacedBlockPos.equals(blockHitPos))
))) {

// it's a block!! it's go time!
IMinecraftClientAccessor clientAccessor = (IMinecraftClientAccessor) client;
AccurateBlockPlacementMod.disableNormalItemUse = true;

// if [ we have a fresh key press ] OR
// [ we should apply the vanilla auto-repeat ] OR
// [ [ we have no 'seen' history or the 'seen' history isn't a match ] AND
// [ we have no 'place' history or the 'place' history isn't a match ] ]
// we can try to place a block
if (freshKeyPress || clientAccessor.accurateblockplacement_GetItemUseCooldown() <= 0 ||
(
(AccurateBlockPlacementMod.lastSeenBlockPos == null
|| !AccurateBlockPlacementMod.lastSeenBlockPos.equals(blockHitPos))
&& (AccurateBlockPlacementMod.lastPlacedBlockPos == null
|| !AccurateBlockPlacementMod.lastPlacedBlockPos.equals(blockHitPos))
)) {

Boolean runOnceFlag = true;

// in case they manage to push the button multiple times per frame
// note: we already subtracted one from the press count earlier so the total should be the same
while(runOnceFlag || client.options.keyUse.wasPressed()) {

// use item
clientAccessor.accurateblockplacement_DoItemUseBypassDisable();

// update last placed
// TODO: don't update if placement failed
AccurateBlockPlacementMod.lastPlacedBlockPos = new ItemPlacementContext(
new ItemUsageContext(client.player, lastBlockHoldingHand, blockHitResult)).getBlockPos();

runOnceFlag = false;
}

// always run at least once if we reach here
// if this isn't a freshkey press, turn on the run once flag
Boolean runOnceFlag = !freshKeyPress;

// in case they manage to push the button multiple times per frame
// note: we already subtracted one from the press count earlier so the total should be the same
while(runOnceFlag || client.options.keyUse.wasPressed()) {

// use item
clientAccessor.accurateblockplacement_DoItemUseBypassDisable();

// update last placed
// TODO: don't update if placement failed
AccurateBlockPlacementMod.lastPlacedBlockPos = new ItemPlacementContext(
new ItemUsageContext(client.player, handOfCurrentItemInUse, blockHitResult)).getBlockPos();

runOnceFlag = false;
}

}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package net.clayborn.accurateblockplacement.mixin;

import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;

import net.clayborn.accurateblockplacement.IKeyBindingAccessor;
import net.minecraft.client.options.KeyBinding;

@Mixin(KeyBinding.class)
public abstract class KeyBindingMixin implements IKeyBindingAccessor {
@Shadow
private int timesPressed;

@Override
public int accurateblockplacement_GetTimesPressed()
{
return timesPressed;
}
}
3 changes: 2 additions & 1 deletion src/main/resources/accurateblockplacement.mixins.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
"compatibilityLevel": "JAVA_8",
"client": [
"GameRendererMixin",
"MinecraftClientMixin"
"MinecraftClientMixin",
"KeyBindingMixin"
],
"injectors": {
"defaultRequire": 1
Expand Down

0 comments on commit 414cd0a

Please sign in to comment.