mirror of
https://github.com/FabricMC/fabric.git
synced 2024-11-14 19:25:23 -05:00
Add an event that fires when client player left-clicks (#3043)
The ClientPreAttackCallback will fire every tick when the attack key is pressed, before vanilla attack handling. If the callback returns true, then the vanilla handling (breaking block, attacking entity, swining hand) will be cancelled. For multiple callbacks, if the former callback returns true, the later callback won't execute. This event does not consider attack cooldown.
This commit is contained in:
parent
e808a8f296
commit
4014940769
8 changed files with 193 additions and 0 deletions
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.fabricmc.fabric.api.event.client.player;
|
||||
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.client.network.ClientPlayerEntity;
|
||||
|
||||
import net.fabricmc.fabric.api.event.Event;
|
||||
import net.fabricmc.fabric.api.event.EventFactory;
|
||||
|
||||
/**
|
||||
* This event fires every tick when the attack key (left mouse button by default) is pressed
|
||||
* (including clicking and holding the attack key).
|
||||
* If the callback returns true,
|
||||
* the vanilla handling (block breaking, entity attacking, hand swing) will be cancelled,
|
||||
* and the later callbacks of this event are also cancelled.
|
||||
*
|
||||
* <p>This event is client-only, which means handling it may require sending custom packets.
|
||||
*
|
||||
* <p>The event fires both when clicking and holding attack key.
|
||||
* To check whether the attack key is just clicked, use {@code clickCount != 0}
|
||||
*
|
||||
* <p>The vanilla attack cooldown and player game mode does not affect this event.
|
||||
* The mod probably needs to check {@link net.minecraft.client.MinecraftClient#attackCooldown} and the game mode.
|
||||
* {@link net.minecraft.entity.player.ItemCooldownManager} can be used for custom item cooldown handling.
|
||||
*/
|
||||
public interface ClientPreAttackCallback {
|
||||
Event<ClientPreAttackCallback> EVENT = EventFactory.createArrayBacked(
|
||||
ClientPreAttackCallback.class,
|
||||
(listeners) -> (client, player, clickCount) -> {
|
||||
for (ClientPreAttackCallback event : listeners) {
|
||||
if (event.onClientPlayerPreAttack(client, player, clickCount)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* @param player the client player
|
||||
* @param clickCount the click count of the attack key in this tick.
|
||||
* @return whether to intercept attack handling
|
||||
*/
|
||||
boolean onClientPlayerPreAttack(MinecraftClient client, ClientPlayerEntity player, int clickCount);
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.fabricmc.fabric.mixin.event.interaction.client;
|
||||
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||
|
||||
import net.minecraft.client.option.KeyBinding;
|
||||
|
||||
@Mixin(KeyBinding.class)
|
||||
public interface KeyBindingAccessor {
|
||||
@Accessor("timesPressed")
|
||||
int fabric_getTimesPressed();
|
||||
}
|
|
@ -16,12 +16,15 @@
|
|||
|
||||
package net.fabricmc.fabric.mixin.event.interaction.client;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.ModifyVariable;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
|
||||
|
||||
import net.minecraft.block.entity.BlockEntity;
|
||||
|
@ -29,6 +32,8 @@ import net.minecraft.client.MinecraftClient;
|
|||
import net.minecraft.client.gui.screen.Screen;
|
||||
import net.minecraft.client.network.ClientPlayNetworkHandler;
|
||||
import net.minecraft.client.network.ClientPlayerEntity;
|
||||
import net.minecraft.client.network.ClientPlayerInteractionManager;
|
||||
import net.minecraft.client.option.GameOptions;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.player.PlayerInventory;
|
||||
import net.minecraft.item.ItemStack;
|
||||
|
@ -43,11 +48,13 @@ import net.minecraft.util.math.Vec3d;
|
|||
import net.fabricmc.fabric.api.event.client.player.ClientPickBlockApplyCallback;
|
||||
import net.fabricmc.fabric.api.event.client.player.ClientPickBlockCallback;
|
||||
import net.fabricmc.fabric.api.event.client.player.ClientPickBlockGatherCallback;
|
||||
import net.fabricmc.fabric.api.event.client.player.ClientPreAttackCallback;
|
||||
import net.fabricmc.fabric.api.event.player.UseEntityCallback;
|
||||
|
||||
@Mixin(MinecraftClient.class)
|
||||
public abstract class MinecraftClientMixin {
|
||||
private boolean fabric_itemPickCancelled;
|
||||
private boolean fabric_attackCancelled;
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
private ItemStack fabric_emulateOldPick() {
|
||||
|
@ -132,6 +139,14 @@ public abstract class MinecraftClientMixin {
|
|||
@Shadow
|
||||
public abstract ClientPlayNetworkHandler getNetworkHandler();
|
||||
|
||||
@Shadow
|
||||
@Final
|
||||
public GameOptions options;
|
||||
|
||||
@Shadow
|
||||
@Nullable
|
||||
public ClientPlayerInteractionManager interactionManager;
|
||||
|
||||
@Inject(
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
|
@ -157,4 +172,42 @@ public abstract class MinecraftClientMixin {
|
|||
ci.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(
|
||||
method = "handleInputEvents",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/client/network/ClientPlayerEntity;isUsingItem()Z",
|
||||
ordinal = 0
|
||||
)
|
||||
)
|
||||
private void injectHandleInputEventsForPreAttackCallback(CallbackInfo ci) {
|
||||
int attackKeyPressCount = ((KeyBindingAccessor) options.attackKey).fabric_getTimesPressed();
|
||||
|
||||
if (options.attackKey.isPressed() || attackKeyPressCount != 0) {
|
||||
fabric_attackCancelled = ClientPreAttackCallback.EVENT.invoker().onClientPlayerPreAttack(
|
||||
(MinecraftClient) (Object) this, player, attackKeyPressCount
|
||||
);
|
||||
} else {
|
||||
fabric_attackCancelled = false;
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "doAttack", at = @At("HEAD"), cancellable = true)
|
||||
private void injectDoAttackForCancelling(CallbackInfoReturnable<Boolean> cir) {
|
||||
if (fabric_attackCancelled) {
|
||||
cir.setReturnValue(false);
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "handleBlockBreaking", at = @At("HEAD"), cancellable = true)
|
||||
private void injectHandleBlockBreakingForCancelling(boolean breaking, CallbackInfo ci) {
|
||||
if (fabric_attackCancelled) {
|
||||
if (interactionManager != null) {
|
||||
interactionManager.cancelBlockBreaking();
|
||||
}
|
||||
|
||||
ci.cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
"compatibilityLevel": "JAVA_16",
|
||||
"client": [
|
||||
"ClientPlayerInteractionManagerMixin",
|
||||
"KeyBindingAccessor",
|
||||
"MinecraftClientMixin"
|
||||
],
|
||||
"injectors": {
|
||||
|
|
|
@ -17,6 +17,9 @@
|
|||
],
|
||||
"fabric-gametest": [
|
||||
"net.fabricmc.fabric.test.event.interaction.FakePlayerTests"
|
||||
],
|
||||
"client": [
|
||||
"net.fabricmc.fabric.test.client.event.interaction.ClientPreAttackTests"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.fabricmc.fabric.test.client.event.interaction;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import net.minecraft.item.Items;
|
||||
|
||||
import net.fabricmc.api.ClientModInitializer;
|
||||
import net.fabricmc.fabric.api.event.client.player.ClientPreAttackCallback;
|
||||
|
||||
public class ClientPreAttackTests implements ClientModInitializer {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(ClientPreAttackTests.class);
|
||||
|
||||
@Override
|
||||
public void onInitializeClient() {
|
||||
ClientPreAttackCallback.EVENT.register((client, player, clickCount) -> {
|
||||
if (!player.isSpectator() && player.getMainHandStack().getItem() == Items.TORCH) {
|
||||
LOGGER.info("Attacking using torch intercepted. Attack key clicks: {}", clickCount != 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
}
|
|
@ -127,6 +127,9 @@ transitive-accessible method net/minecraft/entity/damage/DamageSources create (L
|
|||
transitive-accessible method net/minecraft/entity/damage/DamageSources create (Lnet/minecraft/registry/RegistryKey;Lnet/minecraft/entity/Entity;)Lnet/minecraft/entity/damage/DamageSource;
|
||||
transitive-accessible method net/minecraft/entity/damage/DamageSources create (Lnet/minecraft/registry/RegistryKey;Lnet/minecraft/entity/Entity;Lnet/minecraft/entity/Entity;)Lnet/minecraft/entity/damage/DamageSource;
|
||||
|
||||
# The attack cooldown
|
||||
transitive-accessible field net/minecraft/client/MinecraftClient attackCooldown I
|
||||
|
||||
### Generated access wideners below
|
||||
# Constructors of non-abstract block classes
|
||||
transitive-accessible method net/minecraft/block/AirBlock <init> (Lnet/minecraft/block/AbstractBlock$Settings;)V
|
||||
|
|
|
@ -122,4 +122,7 @@ transitive-accessible method net/minecraft/entity/damage/DamageSources create (L
|
|||
transitive-accessible method net/minecraft/entity/damage/DamageSources create (Lnet/minecraft/registry/RegistryKey;Lnet/minecraft/entity/Entity;)Lnet/minecraft/entity/damage/DamageSource;
|
||||
transitive-accessible method net/minecraft/entity/damage/DamageSources create (Lnet/minecraft/registry/RegistryKey;Lnet/minecraft/entity/Entity;Lnet/minecraft/entity/Entity;)Lnet/minecraft/entity/damage/DamageSource;
|
||||
|
||||
# The attack cooldown
|
||||
transitive-accessible field net/minecraft/client/MinecraftClient attackCooldown I
|
||||
|
||||
### Generated access wideners below
|
||||
|
|
Loading…
Reference in a new issue