This commit is contained in:
BRanulf_Explode 2025-07-29 09:14:11 +08:00
parent c1de6e5ac4
commit bfc37b3b61
6 changed files with 379 additions and 59 deletions

View File

@ -6,7 +6,7 @@ minecraft_version=1.21.4
yarn_mappings=1.21.4+build.8 yarn_mappings=1.21.4+build.8
loader_version=0.16.10 loader_version=0.16.10
# Mod Properties # Mod Properties
mod_version=1.14.514.004 mod_version=1.14.514.006
maven_group=com.example maven_group=com.example
archives_base_name=autocrossbow archives_base_name=autocrossbow
# Dependencies # Dependencies

View File

@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https://mirrors.aliyun.com/macports/distfiles/gradle/gradle-8.14.1-bin.zip distributionUrl=https://mirrors.aliyun.com/macports/distfiles/gradle/gradle-8.14.3-bin.zip
networkTimeout=10000 networkTimeout=10000
validateDistributionUrl=true validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME

View File

@ -1,46 +1,103 @@
package com.example.autocrossbow; package com.example.autocrossbow;
import net.fabricmc.api.ClientModInitializer; import net.fabricmc.api.ClientModInitializer;
import net.fabricmc.fabric.api.client.command.v2.ClientCommandManager;
import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback;
import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource;
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper; import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper;
import net.minecraft.client.MinecraftClient; import net.minecraft.client.MinecraftClient;
import net.minecraft.client.option.KeyBinding; import net.minecraft.client.option.KeyBinding;
import net.minecraft.client.util.InputUtil; import net.minecraft.client.util.InputUtil;
import net.minecraft.entity.player.PlayerEntity; import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.entity.player.PlayerInventory;
import net.minecraft.item.CrossbowItem; import net.minecraft.item.CrossbowItem;
import net.minecraft.item.ItemStack; import net.minecraft.item.ItemStack;
import net.minecraft.network.packet.c2s.play.UpdateSelectedSlotC2SPacket;
import net.minecraft.screen.slot.SlotActionType;
import net.minecraft.text.ClickEvent;
import net.minecraft.text.MutableText;
import net.minecraft.text.Text; import net.minecraft.text.Text;
import net.minecraft.util.Hand; import net.minecraft.util.Hand;
import org.lwjgl.glfw.GLFW; import org.lwjgl.glfw.GLFW;
import com.mojang.brigadier.arguments.BoolArgumentType;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.suggestion.SuggestionProvider;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import java.util.Arrays;
import java.util.Locale;
public class AutoCrossbowMod implements ClientModInitializer { public class AutoCrossbowMod implements ClientModInitializer {
private static boolean enabled = false; private static boolean enabled = false;
private static KeyBinding toggleKeyBinding; private static AutoCrossbowMode currentMode = AutoCrossbowMode.NORMAL;
private static boolean wasUsing = false;
private static KeyBinding toggleModKeyBinding;
private static KeyBinding toggleModeKeyBinding;
private static boolean isRightClickPressed = false;
private static boolean wasRightClickPressed = false;
private static int chargeProgress = 0; private static int chargeProgress = 0;
private static long lastActionTime = 0;
private static final long ACTION_COOLDOWN_MS = 50;
public enum AutoCrossbowMode {
NORMAL("normal"), // 普通自动
RAPID_FIRE_SHOOT("rapid_shoot"), // 连发发射
RAPID_FIRE_LOAD("rapid_load"); // 连发装填
private final String name;
AutoCrossbowMode(String name) {
this.name = name;
}
public String getName() {
return name;
}
public static AutoCrossbowMode fromString(String text) {
for (AutoCrossbowMode mode : AutoCrossbowMode.values()) {
if (mode.name.equalsIgnoreCase(text)) {
return mode;
}
}
return null;
}
public AutoCrossbowMode next() {
return values()[(this.ordinal() + 1) % values().length];
}
}
@Override @Override
public void onInitializeClient() { public void onInitializeClient() {
toggleKeyBinding = KeyBindingHelper.registerKeyBinding(new KeyBinding(
"key.autocrossbow.toggle", toggleModKeyBinding = KeyBindingHelper.registerKeyBinding(new KeyBinding(
"key.autocrossbow.toggle_mod",
InputUtil.Type.KEYSYM, InputUtil.Type.KEYSYM,
GLFW.GLFW_KEY_UNKNOWN, // 默认UNKNOW自己去设置 GLFW.GLFW_KEY_UNKNOWN,
"category.autocrossbow" "category.autocrossbow"
)); ));
toggleModeKeyBinding = KeyBindingHelper.registerKeyBinding(new KeyBinding(
"key.autocrossbow.toggle_mode",
InputUtil.Type.KEYSYM,
GLFW.GLFW_KEY_UNKNOWN,
"category.autocrossbow"
));
ClientTickEvents.END_CLIENT_TICK.register(client -> { ClientTickEvents.END_CLIENT_TICK.register(client -> {
while (toggleKeyBinding.wasPressed()) { handleKeybinds(client);
enabled = !enabled;
if (client.player != null) {
client.player.sendMessage(
Text.translatable("message.autocrossbow.status",
Text.translatable(enabled ? "message.autocrossbow.enabled" : "message.autocrossbow.disabled")),
true
);
}
}
if (!enabled || client.player == null || client.interactionManager == null) { if (!enabled || client.player == null || client.interactionManager == null) {
return; return;
@ -50,38 +107,290 @@ public class AutoCrossbowMod implements ClientModInitializer {
Hand hand = Hand.MAIN_HAND; Hand hand = Hand.MAIN_HAND;
ItemStack stack = player.getStackInHand(hand); ItemStack stack = player.getStackInHand(hand);
isRightClickPressed = client.options.useKey.isPressed();
if (!(stack.getItem() instanceof CrossbowItem)) { if (!(stack.getItem() instanceof CrossbowItem)) {
resetActionStates();
return; return;
} }
boolean isUsing = client.options.useKey.isPressed();
if (CrossbowItem.isCharged(stack) && isUsing) { switch (currentMode) {
case NORMAL:
handleNormalMode(client, player, hand, stack);
break;
case RAPID_FIRE_SHOOT:
handleRapidFireShootMode(client, player, hand, stack);
break;
case RAPID_FIRE_LOAD:
handleRapidFireLoadMode(client, player, hand, stack);
break;
}
wasRightClickPressed = isRightClickPressed;
});
ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) -> {
dispatcher.register(ClientCommandManager.literal("autocrossbow")
.executes(context -> {
PlayerEntity player = MinecraftClient.getInstance().player;
if (player != null) {
player.sendMessage(Text.translatable("command.autocrossbow.status",
Text.translatable(enabled ? "message.autocrossbow.enabled" : "message.autocrossbow.disabled"),
Text.translatable("message.autocrossbow.mode." + currentMode.getName())), false);
MutableText toggleEnabledText = Text.translatable("command.autocrossbow.toggle_enabled")
.styled(s -> s.withClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/autocrossbow enabled " + !enabled)));
player.sendMessage(toggleEnabledText, false);
MutableText toggleModeText = Text.translatable("command.autocrossbow.toggle_mode")
.styled(s -> s.withClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/autocrossbow mode " + currentMode.next().getName())));
player.sendMessage(toggleModeText, false);
}
return 1;
})
.then(ClientCommandManager.literal("enabled")
.then(ClientCommandManager.argument("value", BoolArgumentType.bool())
.executes(context -> {
enabled = BoolArgumentType.getBool(context, "value");
PlayerEntity player = MinecraftClient.getInstance().player;
if (player != null) {
player.sendMessage(Text.translatable("message.autocrossbow.status",
Text.translatable(enabled ? "message.autocrossbow.enabled" : "message.autocrossbow.disabled"),
Text.translatable("message.autocrossbow.mode." + currentMode.getName())), false);
}
return 1;
})
)
)
.then(ClientCommandManager.literal("mode")
.then(ClientCommandManager.argument("modeName", StringArgumentType.word())
.suggests(MODE_SUGGESTION_PROVIDER)
.executes(context -> {
String modeName = StringArgumentType.getString(context, "modeName");
AutoCrossbowMode newMode = AutoCrossbowMode.fromString(modeName);
PlayerEntity player = MinecraftClient.getInstance().player;
if (newMode != null) {
currentMode = newMode;
if (player != null) {
player.sendMessage(Text.translatable("message.autocrossbow.mode_changed",
Text.translatable("message.autocrossbow.mode." + currentMode.getName())), false);
}
} else {
if (player != null) {
player.sendMessage(Text.translatable("command.autocrossbow.invalid_mode", modeName), false);
}
}
return 1;
})
)
)
);
});
}
private void handleKeybinds(MinecraftClient client) {
while (toggleModKeyBinding.wasPressed()) {
enabled = !enabled;
if (client.player != null) {
client.player.sendMessage(
Text.translatable("message.autocrossbow.status",
Text.translatable(enabled ? "message.autocrossbow.enabled" : "message.autocrossbow.disabled"),
Text.translatable("message.autocrossbow.mode." + currentMode.getName())),
true
);
}
}
while (toggleModeKeyBinding.wasPressed()) {
currentMode = currentMode.next();
if (client.player != null) {
client.player.sendMessage(
Text.translatable("message.autocrossbow.mode_changed",
Text.translatable("message.autocrossbow.mode." + currentMode.getName())),
true
);
}
}
}
private void resetActionStates() {
isRightClickPressed = false;
wasRightClickPressed = false;
chargeProgress = 0;
lastActionTime = 0;
}
private void handleNormalMode(MinecraftClient client, PlayerEntity player, Hand hand, ItemStack stack) {
if (CrossbowItem.isCharged(stack) && isRightClickPressed) {
client.interactionManager.interactItem(player, hand); client.interactionManager.interactItem(player, hand);
return; return;
} }
if (isUsing) { if (isRightClickPressed) {
if (!player.isUsingItem()) { if (!player.isUsingItem()) {
client.interactionManager.interactItem(player, hand); client.interactionManager.interactItem(player, hand);
chargeProgress = 0; chargeProgress = 0;
} } else if (player.getActiveHand() == hand) {
else if (player.getActiveHand() == hand) {
chargeProgress++; chargeProgress++;
int pullTime = CrossbowItem.getPullTime(stack, player); int pullTime = CrossbowItem.getPullTime(stack, player);
// TODO 目前测试稳定的只有快速装填3其他有待优化
if (chargeProgress >= pullTime * 1.1f) { if (chargeProgress >= pullTime * 1.1f) {
client.interactionManager.stopUsingItem(player); client.interactionManager.stopUsingItem(player);
wasUsing = true;
} }
} }
} } else if (wasRightClickPressed) {
else if (wasUsing) {
wasUsing = false;
chargeProgress = 0; chargeProgress = 0;
} }
}); }
private void handleRapidFireShootMode(MinecraftClient client, PlayerEntity player, Hand hand, ItemStack stack) {
if (!isRightClickPressed) {
return;
}
if (System.currentTimeMillis() - lastActionTime < ACTION_COOLDOWN_MS) {
return;
}
if (CrossbowItem.isCharged(stack)) {
client.interactionManager.interactItem(player, hand);
player.swingHand(hand);
lastActionTime = System.currentTimeMillis();
}
int nextSlot = findNextCrossbow(player.getInventory(), true, player.getInventory().selectedSlot);
if (nextSlot != -1) {
swapToSlot(client, player, nextSlot);
} else {
player.sendMessage(Text.translatable("message.autocrossbow.no_more_charged"), true);
currentMode = AutoCrossbowMode.NORMAL;
} }
} }
private void handleRapidFireLoadMode(MinecraftClient client, PlayerEntity player, Hand hand, ItemStack stack) {
if (!isRightClickPressed) {
return;
}
if (player.isUsingItem() && player.getActiveHand() == hand) {
chargeProgress++;
int pullTime = CrossbowItem.getPullTime(stack, player);
if (chargeProgress >= pullTime * 1.1f) {
client.interactionManager.stopUsingItem(player);
chargeProgress = 0;
lastActionTime = System.currentTimeMillis();
int nextSlot = findNextCrossbow(player.getInventory(), false, player.getInventory().selectedSlot);
if (nextSlot != -1) {
swapToSlot(client, player, nextSlot);
} else {
player.sendMessage(Text.translatable("message.autocrossbow.no_more_uncharged"), true);
currentMode = AutoCrossbowMode.NORMAL;
}
}
} else if (!CrossbowItem.isCharged(stack)) {
if (System.currentTimeMillis() - lastActionTime < ACTION_COOLDOWN_MS) {
return;
}
client.interactionManager.interactItem(player, hand);
chargeProgress = 0;
lastActionTime = System.currentTimeMillis();
} else {
if (System.currentTimeMillis() - lastActionTime < ACTION_COOLDOWN_MS) {
return;
}
int nextSlot = findNextCrossbow(player.getInventory(), false, player.getInventory().selectedSlot);
if (nextSlot != -1) {
swapToSlot(client, player, nextSlot);
lastActionTime = System.currentTimeMillis();
} else {
player.sendMessage(Text.translatable("message.autocrossbow.no_more_uncharged"), true);
currentMode = AutoCrossbowMode.NORMAL;
}
}
}
private int findNextCrossbow(PlayerInventory inventory, boolean chargedRequired, int currentSelectedSlot) {
for (int i = 0; i < PlayerInventory.HOTBAR_SIZE; i++) {
if (i == currentSelectedSlot) continue;
ItemStack stack = inventory.getStack(i);
if (stack.getItem() instanceof CrossbowItem) {
boolean isCharged = CrossbowItem.isCharged(stack);
if (chargedRequired == isCharged) {
return i;
}
}
}
for (int i = PlayerInventory.HOTBAR_SIZE; i < inventory.main.size(); i++) {
ItemStack stack = inventory.getStack(i);
if (stack.getItem() instanceof CrossbowItem) {
boolean isCharged = CrossbowItem.isCharged(stack);
if (chargedRequired == isCharged) {
return i;
}
}
}
return -1;
}
private void swapToSlot(MinecraftClient client, PlayerEntity player, int targetSlot) {
if (client.interactionManager == null || player.playerScreenHandler == null) return;
if (targetSlot >= 0 && targetSlot < PlayerInventory.HOTBAR_SIZE) {
player.getInventory().selectedSlot = targetSlot;
client.getNetworkHandler().sendPacket(new UpdateSelectedSlotC2SPacket(targetSlot));
} else {
client.interactionManager.clickSlot(
player.playerScreenHandler.syncId,
targetSlot,
player.getInventory().selectedSlot,
SlotActionType.SWAP,
player
);
}
}
private static final SuggestionProvider<FabricClientCommandSource> MODE_SUGGESTION_PROVIDER = (context, builder) -> {
Arrays.stream(AutoCrossbowMode.values())
.map(AutoCrossbowMode::getName)
.forEach(builder::suggest);
return builder.buildFuture();
};
}

View File

@ -1,11 +0,0 @@
package com.example.autocrossbow.client;
import net.fabricmc.api.ClientModInitializer;
public class AutoCrossbowModClient implements ClientModInitializer {
//
@Override
public void onInitializeClient() {
}
}

View File

@ -1,7 +1,18 @@
{ {
"key.autocrossbow.toggle": "Toggle Auto Crossbow", "key.autocrossbow.toggle_mod": "Toggle Auto Crossbow Mod",
"key.autocrossbow.toggle_mode": "Cycle Crossbow Mode",
"category.autocrossbow": "Auto Crossbow Mod", "category.autocrossbow": "Auto Crossbow Mod",
"message.autocrossbow.status": "Auto Crossbow: %s", "message.autocrossbow.status": "Auto Crossbow: %s, Mode: %s",
"message.autocrossbow.enabled": "Enabled", "message.autocrossbow.enabled": "Enabled",
"message.autocrossbow.disabled": "Disabled" "message.autocrossbow.disabled": "Disabled",
"message.autocrossbow.mode_changed": "Crossbow Mode: %s",
"message.autocrossbow.mode.normal": "Normal",
"message.autocrossbow.mode.rapid_shoot": "Rapid Fire (Shoot)",
"message.autocrossbow.mode.rapid_load": "Rapid Fire (Load)",
"message.autocrossbow.no_more_charged": "No more charged crossbows found!",
"message.autocrossbow.no_more_uncharged": "No more uncharged crossbows found!",
"command.autocrossbow.status": "§b[Auto Crossbow]§f Status: %s, Mode: %s",
"command.autocrossbow.toggle_enabled": "Click to toggle mod enabled/disabled",
"command.autocrossbow.toggle_mode": "Click to cycle mode",
"command.autocrossbow.invalid_mode": "Invalid mode: '%s'. Available modes: normal, rapid_shoot, rapid_load."
} }

View File

@ -1,7 +1,18 @@
{ {
"key.autocrossbow.toggle": "切换连弩模式", "key.autocrossbow.toggle_mod": "切换诸葛连弩开关",
"key.autocrossbow.toggle_mode": "切换连弩模式",
"category.autocrossbow": "诸葛连弩", "category.autocrossbow": "诸葛连弩",
"message.autocrossbow.status": "§b[诸葛连弩]§f 状态: %s", "message.autocrossbow.status": "§b[诸葛连弩]§f 状态:%s模式%s",
"message.autocrossbow.enabled": "§a已启用", "message.autocrossbow.enabled": "§a已启用",
"message.autocrossbow.disabled": "§c已禁用" "message.autocrossbow.disabled": "§c已禁用",
"message.autocrossbow.mode_changed": "连弩模式:%s",
"message.autocrossbow.mode.normal": "正常模式",
"message.autocrossbow.mode.rapid_shoot": "连发 - 发射模式",
"message.autocrossbow.mode.rapid_load": "连发 - 装填模式",
"message.autocrossbow.no_more_charged": "背包中没有更多已上膛的弩了!",
"message.autocrossbow.no_more_uncharged": "背包中没有更多未上膛的弩了!",
"command.autocrossbow.status": "§b[诸葛连弩]§f 状态:%s模式%s",
"command.autocrossbow.toggle_enabled": "点击切换模组启用/禁用",
"command.autocrossbow.toggle_mode": "点击切换模式",
"command.autocrossbow.invalid_mode": "无效模式:“%s”。可用模式normal, rapid_shoot, rapid_load。"
} }