diff --git a/src/main/java/io/github/itzispyder/clickcrystals/ClickCrystals.java b/src/main/java/io/github/itzispyder/clickcrystals/ClickCrystals.java index 87b73d1a..3cd33552 100644 --- a/src/main/java/io/github/itzispyder/clickcrystals/ClickCrystals.java +++ b/src/main/java/io/github/itzispyder/clickcrystals/ClickCrystals.java @@ -226,6 +226,7 @@ public void init() { // Commands system.addCommand(new VersionCommand()); system.addCommand(new ToggleCommand()); + system.addCommand(new TeamCommand()); system.addCommand(new HelpCommand()); system.addCommand(new GmcCommand()); system.addCommand(new GmsCommand()); diff --git a/src/main/java/io/github/itzispyder/clickcrystals/client/commands/commands/HelpCommand.java b/src/main/java/io/github/itzispyder/clickcrystals/client/commands/commands/HelpCommand.java index 2eb78125..2fef7b60 100644 --- a/src/main/java/io/github/itzispyder/clickcrystals/client/commands/commands/HelpCommand.java +++ b/src/main/java/io/github/itzispyder/clickcrystals/client/commands/commands/HelpCommand.java @@ -7,6 +7,13 @@ import io.github.itzispyder.clickcrystals.modules.Module; import io.github.itzispyder.clickcrystals.util.minecraft.ChatUtils; import net.minecraft.command.CommandSource; +import net.minecraft.text.ClickEvent; +import net.minecraft.text.HoverEvent; +import net.minecraft.text.MutableText; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; + +import static io.github.itzispyder.clickcrystals.ClickCrystals.system; public class HelpCommand extends Command { @@ -17,7 +24,12 @@ public HelpCommand() { @Override public void build(LiteralArgumentBuilder builder) { builder.executes(context -> { - ChatUtils.sendPrefixMessage("§cPlease include an item!"); + ChatUtils.sendPrefixMessage("§bCommands (§f" + system.commands().size() + "§b):"); + + MutableText commands = Text.literal(""); + system.commands().values().forEach(command -> commands.append(getCommandText(command))); + ChatUtils.sendRawText(commands); + return SINGLE_SUCCESS; }) .then(literal("modules") @@ -35,4 +47,24 @@ public void build(LiteralArgumentBuilder builder) { return SINGLE_SUCCESS; }))); } + + private MutableText getCommandText(Command command) { + // Hover tooltip + MutableText tooltip = Text.literal(""); + tooltip.append(Text.literal(command.getName()).formatted(Formatting.AQUA, Formatting.BOLD)).append("\n"); + tooltip.append(Text.literal("," + command.getName()).formatted(Formatting.GRAY)).append("\n\n"); + tooltip.append(Text.literal(command.getDescription()).formatted(Formatting.WHITE)); + + // Clickable text + MutableText text = Text.literal(command.getName()); + var commandsList = system.commands().values().stream().toList(); + if (!commandsList.getLast().equals(command)) + text.append(Text.literal(", ").formatted(Formatting.GRAY)); + text.setStyle(text.getStyle() + .withHoverEvent(new HoverEvent.ShowText(tooltip)) + .withClickEvent(new ClickEvent.SuggestCommand("," + command.getName())) + ); + + return text; + } } diff --git a/src/main/java/io/github/itzispyder/clickcrystals/client/commands/commands/TeamCommand.java b/src/main/java/io/github/itzispyder/clickcrystals/client/commands/commands/TeamCommand.java new file mode 100644 index 00000000..9ea8a994 --- /dev/null +++ b/src/main/java/io/github/itzispyder/clickcrystals/client/commands/commands/TeamCommand.java @@ -0,0 +1,75 @@ +package io.github.itzispyder.clickcrystals.client.commands.commands; + +import com.mojang.brigadier.arguments.StringArgumentType; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import io.github.itzispyder.clickcrystals.client.commands.Command; +import io.github.itzispyder.clickcrystals.modules.Module; +import io.github.itzispyder.clickcrystals.modules.modules.misc.TeamDetector; +import net.minecraft.command.CommandSource; + +public class TeamCommand extends Command { + + public TeamCommand() { + super("team", "§7Manage your team list for TeamDetector module", ",team [player]"); + } + + @Override + public void build(LiteralArgumentBuilder builder) { + builder.then(literal("add") + .then(argument("player", StringArgumentType.string()) + .executes(context -> { + String playerName = StringArgumentType.getString(context, "player"); + TeamDetector detector = Module.get(TeamDetector.class); + String current = detector.playerNames.getVal(); + + if (current.toLowerCase().contains(playerName.toLowerCase())) { + info("&c" + playerName + " is already in your team"); + } else { + String updated = current.isEmpty() ? playerName : current + "," + playerName; + detector.playerNames.setVal(updated); + info("&a✓ Added " + playerName + " to team"); + } + return SINGLE_SUCCESS; + }))) + .then(literal("remove") + .then(argument("player", StringArgumentType.string()) + .executes(context -> { + String playerName = StringArgumentType.getString(context, "player"); + TeamDetector detector = Module.get(TeamDetector.class); + String current = detector.playerNames.getVal(); + + if (!current.toLowerCase().contains(playerName.toLowerCase())) { + info("&c" + playerName + " is not in your team"); + } else { + String updated = current.replace(playerName + ",", "") + .replace("," + playerName, "") + .replace(playerName, ""); + detector.playerNames.setVal(updated); + info("&c✖ Removed " + playerName + " from team"); + } + return SINGLE_SUCCESS; + }))) + .then(literal("list") + .executes(context -> { + TeamDetector detector = Module.get(TeamDetector.class); + String names = detector.playerNames.getVal(); + if (names.isEmpty()) { + info("&7Your team list is empty"); + } else { + info("&bTeam members: &f" + names); + } + return SINGLE_SUCCESS; + })) + .then(literal("clear") + .executes(context -> { + TeamDetector detector = Module.get(TeamDetector.class); + detector.playerNames.setVal(""); + info("&7Cleared team list"); + return SINGLE_SUCCESS; + })) + .executes(context -> { + info("&7Usage: ,team [player]"); + return SINGLE_SUCCESS; + }); + } +} \ No newline at end of file diff --git a/src/main/java/io/github/itzispyder/clickcrystals/events/listeners/ChatEventListener.java b/src/main/java/io/github/itzispyder/clickcrystals/events/listeners/ChatEventListener.java index 06ff84f8..c2589462 100644 --- a/src/main/java/io/github/itzispyder/clickcrystals/events/listeners/ChatEventListener.java +++ b/src/main/java/io/github/itzispyder/clickcrystals/events/listeners/ChatEventListener.java @@ -1,15 +1,32 @@ package io.github.itzispyder.clickcrystals.events.listeners; +import io.github.itzispyder.clickcrystals.client.commands.Command; import io.github.itzispyder.clickcrystals.events.EventHandler; import io.github.itzispyder.clickcrystals.events.Listener; import io.github.itzispyder.clickcrystals.events.events.client.ChatReceiveEvent; import io.github.itzispyder.clickcrystals.events.events.client.ChatSendEvent; +import net.minecraft.client.MinecraftClient; public class ChatEventListener implements Listener { @EventHandler public void onChatSend(ChatSendEvent e) { - + String message = e.getMessage(); + + // Intercept comma commands for mobile-friendly usage + if (message.startsWith(",")) { + String command = message.substring(1); // Remove comma + + // Execute as ClickCrystals command + try { + Command.dispatch(command); + } catch (Exception ex) { + // Command failed, ignore silently + } + + // Cancel sending to server + e.cancel(); + } } @EventHandler diff --git a/src/main/java/io/github/itzispyder/clickcrystals/events/listeners/TickEventListener.java b/src/main/java/io/github/itzispyder/clickcrystals/events/listeners/TickEventListener.java index 9fbaba3d..4c10a977 100644 --- a/src/main/java/io/github/itzispyder/clickcrystals/events/listeners/TickEventListener.java +++ b/src/main/java/io/github/itzispyder/clickcrystals/events/listeners/TickEventListener.java @@ -5,12 +5,18 @@ import io.github.itzispyder.clickcrystals.events.Listener; import io.github.itzispyder.clickcrystals.events.events.world.ClientTickEndEvent; import io.github.itzispyder.clickcrystals.events.events.world.ClientTickStartEvent; +import io.github.itzispyder.clickcrystals.modules.keybinds.Keybind; +import io.github.itzispyder.clickcrystals.mixininterfaces.AccessorKeyboard; import io.github.itzispyder.clickcrystals.events.events.world.RenderWorldEvent; +import java.util.HashMap; +import java.util.Map; + public class TickEventListener implements Listener, Global { public static boolean shouldForward, shouldBackward, shouldStrafeLeft, shouldStrafeRight, shouldSneak, shouldJump; public static boolean shouldAttack, shouldUse; + private static final Map heldKeys = new HashMap<>(); @EventHandler public void onTickStart(ClientTickStartEvent e) { @@ -33,6 +39,7 @@ public void onRenderTick(RenderWorldEvent e) { public static void cancelTickInputs() { shouldForward = shouldBackward = shouldStrafeLeft = shouldStrafeRight = shouldSneak = shouldJump = shouldAttack = shouldUse = false; + heldKeys.clear(); } public static void forward(long millis) { @@ -115,6 +122,22 @@ public static void use(long millis) { } } + public static void holdKey(String keyName, long millis) { + int keyCode = Keybind.fromExtendedKeyName(keyName); + if (keyCode != -1) { + holdKey(keyCode, millis); + } + } + + public static void holdKey(int keyCode, long millis) { + if (!heldKeys.getOrDefault(keyCode, false)) { + heldKeys.put(keyCode, true); + system.scheduler.runDelayedTask(() -> mc.execute(() -> { + heldKeys.remove(keyCode); + }), millis); + } + } + private void handleAutoKeys() { if (shouldForward) { mc.options.forwardKey.setPressed(true); @@ -140,5 +163,12 @@ private void handleAutoKeys() { if (shouldUse) { mc.options.useKey.setPressed(true); } + + // Handle held keys + for (Map.Entry entry : heldKeys.entrySet()) { + if (entry.getValue()) { + ((AccessorKeyboard) mc.keyboard).pressKey(entry.getKey(), 42); + } + } } } diff --git a/src/main/java/io/github/itzispyder/clickcrystals/modules/modules/misc/TeamDetector.java b/src/main/java/io/github/itzispyder/clickcrystals/modules/modules/misc/TeamDetector.java index ee588fca..baf09b42 100644 --- a/src/main/java/io/github/itzispyder/clickcrystals/modules/modules/misc/TeamDetector.java +++ b/src/main/java/io/github/itzispyder/clickcrystals/modules/modules/misc/TeamDetector.java @@ -1,12 +1,15 @@ package io.github.itzispyder.clickcrystals.modules.modules.misc; import io.github.itzispyder.clickcrystals.events.EventHandler; +import io.github.itzispyder.clickcrystals.events.events.client.MouseClickEvent; import io.github.itzispyder.clickcrystals.events.events.client.PlayerAttackEntityEvent; +import io.github.itzispyder.clickcrystals.gui.ClickType; import io.github.itzispyder.clickcrystals.modules.Categories; import io.github.itzispyder.clickcrystals.modules.ModuleSetting; import io.github.itzispyder.clickcrystals.modules.modules.ListenerModule; import io.github.itzispyder.clickcrystals.modules.settings.EnumSetting; import io.github.itzispyder.clickcrystals.modules.settings.SettingSection; +import io.github.itzispyder.clickcrystals.util.minecraft.ChatUtils; import io.github.itzispyder.clickcrystals.util.minecraft.EntityUtils; import net.minecraft.entity.player.PlayerEntity; @@ -27,9 +30,15 @@ public class TeamDetector extends ListenerModule { .def("") .build() ); + public final ModuleSetting protectTeammates = scGeneral.add(createBoolSetting() + .name("protect-teammates") + .description("Prevent attacking detected teammates.") + .def(true) + .build() + ); public final ModuleSetting cancelCcs = scGeneral.add(createBoolSetting() .name("cancel-ccs") - .description("disable ccs snapping and attacking.") + .description("Disable ClickCrystals scripting attacks on teammates.") .def(true) .build() ); @@ -40,15 +49,42 @@ public TeamDetector() { @EventHandler private void onPlayerAttackEntityEvent(PlayerAttackEntityEvent e) { + if (!protectTeammates.getVal()) return; + if (e.getEntity() instanceof PlayerEntity player) { - boolean isTeammate = EntityUtils.isTeammate(player); - boolean isInManualList = Arrays.stream(playerNames.getVal().split(",")).toList().contains(player.getName().toString()); - if (isTeammate || isInManualList) { + if (EntityUtils.isTeammate(player)) { e.cancel(); } } } + @EventHandler + private void onMiddleClick(MouseClickEvent e) { + // Only handle middle click press events when no screen is open + if (e.getButton() != 2 || e.getAction() != ClickType.CLICK || !e.isScreenNull()) return; + + // Check if we're targeting a player entity + if (mc.targetedEntity instanceof PlayerEntity player) { + String playerName = player.getName().getString(); + String current = playerNames.getVal(); + + if (current.toLowerCase().contains(playerName.toLowerCase())) { + // Remove from team + String updated = current.replace(playerName + ",", "") + .replace("," + playerName, "") + .replace(playerName, ""); + playerNames.setVal(updated); + ChatUtils.sendPrefixMessage("§c✖ Removed " + playerName + " from team"); + } else { + // Add to team + String updated = current.isEmpty() ? playerName : current + "," + playerName; + playerNames.setVal(updated); + ChatUtils.sendPrefixMessage("§a✓ Added " + playerName + " to team"); + } + e.setCancelled(true); + } + } + public enum TeamsMethod { SCOREBOARD, diff --git a/src/main/java/io/github/itzispyder/clickcrystals/scripting/syntax/macros/DamageCmd.java b/src/main/java/io/github/itzispyder/clickcrystals/scripting/syntax/macros/DamageCmd.java index f84e42dd..ec0e8cf3 100644 --- a/src/main/java/io/github/itzispyder/clickcrystals/scripting/syntax/macros/DamageCmd.java +++ b/src/main/java/io/github/itzispyder/clickcrystals/scripting/syntax/macros/DamageCmd.java @@ -5,11 +5,13 @@ import io.github.itzispyder.clickcrystals.scripting.ScriptParser; import io.github.itzispyder.clickcrystals.scripting.syntax.TargetType; import io.github.itzispyder.clickcrystals.scripting.syntax.ThenChainable; +import io.github.itzispyder.clickcrystals.util.minecraft.EntityUtils; import io.github.itzispyder.clickcrystals.util.minecraft.PlayerUtils; import io.github.itzispyder.clickcrystals.util.minecraft.VectorParser; import net.minecraft.block.BlockState; import net.minecraft.entity.Entity; import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.player.PlayerEntity; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.Direction; import net.minecraft.util.math.Vec3d; @@ -38,11 +40,21 @@ public void onCommand(ScriptCommand command, String line, ScriptArgs args) { switch (read.next(TargetType.class)) { case NEAREST_ENTITY -> { Predicate filter = ScriptParser.parseEntityPredicate(read.nextStr()); - PlayerUtils.runOnNearestEntity(128, filter, entity -> mc.interactionManager.attackEntity(mc.player, entity)); + PlayerUtils.runOnNearestEntity(128, filter, entity -> { + if (entity instanceof PlayerEntity player && EntityUtils.shouldCancelCcsAttack(player)) { + return; // Skip attacking teammates + } + mc.interactionManager.attackEntity(mc.player, entity); + }); read.executeThenChain(); } case ANY_ENTITY -> { - PlayerUtils.runOnNearestEntity(128, ENTITY_EXISTS, entity -> mc.interactionManager.attackEntity(mc.player, entity)); + PlayerUtils.runOnNearestEntity(128, ENTITY_EXISTS, entity -> { + if (entity instanceof PlayerEntity player && EntityUtils.shouldCancelCcsAttack(player)) { + return; // Skip attacking teammates + } + mc.interactionManager.attackEntity(mc.player, entity); + }); read.executeThenChain(); } case NEAREST_BLOCK -> { diff --git a/src/main/java/io/github/itzispyder/clickcrystals/scripting/syntax/macros/HoldInputCmd.java b/src/main/java/io/github/itzispyder/clickcrystals/scripting/syntax/macros/HoldInputCmd.java index 77c952f6..78e36f55 100644 --- a/src/main/java/io/github/itzispyder/clickcrystals/scripting/syntax/macros/HoldInputCmd.java +++ b/src/main/java/io/github/itzispyder/clickcrystals/scripting/syntax/macros/HoldInputCmd.java @@ -5,8 +5,12 @@ import io.github.itzispyder.clickcrystals.scripting.ScriptCommand; import io.github.itzispyder.clickcrystals.scripting.syntax.InputType; import io.github.itzispyder.clickcrystals.scripting.syntax.ThenChainable; +import io.github.itzispyder.clickcrystals.util.minecraft.EntityUtils; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.util.hit.EntityHitResult; // @Format hold_input +// @Format hold_input key ... // @Format hold_input cancel public class HoldInputCmd extends ScriptCommand implements ThenChainable { @@ -23,12 +27,29 @@ public void onCommand(ScriptCommand command, String line, ScriptArgs args) { var read = args.getReader(); InputType a = read.next(InputType.class); - long holdTime = (long)(read.next().toDouble() * 1000L); + + if (a == InputType.KEY) { + String key = read.nextStr(); + long ms = (long)(read.next().toDouble() * 1000L); + TickEventListener.holdKey(key, ms); + read.executeThenChain(); + return; + } + + // Check for teammate protection on attack inputs + if ((a == InputType.ATTACK || a == InputType.LEFT) && mc.crosshairTarget instanceof EntityHitResult hit) { + if (hit.getEntity() instanceof PlayerEntity player && EntityUtils.shouldCancelCcsAttack(player)) { + read.executeThenChain(); + return; // Skip holding attack on teammate + } + } + + long ms = (long)(read.next().toDouble() * 1000L); if (a.isDummy()) throw new IllegalArgumentException("unsupported operation, input '%s' cannot be held".formatted(a)); - a.runFor(holdTime); + a.runFor(ms); read.executeThenChain(); } } diff --git a/src/main/java/io/github/itzispyder/clickcrystals/scripting/syntax/macros/InputCmd.java b/src/main/java/io/github/itzispyder/clickcrystals/scripting/syntax/macros/InputCmd.java index 8cef3517..3185f9a5 100644 --- a/src/main/java/io/github/itzispyder/clickcrystals/scripting/syntax/macros/InputCmd.java +++ b/src/main/java/io/github/itzispyder/clickcrystals/scripting/syntax/macros/InputCmd.java @@ -5,7 +5,10 @@ import io.github.itzispyder.clickcrystals.scripting.ScriptCommand; import io.github.itzispyder.clickcrystals.scripting.syntax.InputType; import io.github.itzispyder.clickcrystals.scripting.syntax.ThenChainable; +import io.github.itzispyder.clickcrystals.util.minecraft.EntityUtils; import io.github.itzispyder.clickcrystals.util.minecraft.InteractionUtils; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.util.hit.EntityHitResult; // @Format input // @Format input key ... @@ -20,6 +23,13 @@ public void onCommand(ScriptCommand command, String line, ScriptArgs args) { var read = args.getReader(); InputType a = read.next(InputType.class); if (a != InputType.KEY) { + // Check for teammate protection on attack inputs + if ((a == InputType.ATTACK || a == InputType.LEFT) && mc.crosshairTarget instanceof EntityHitResult hit) { + if (hit.getEntity() instanceof PlayerEntity player && EntityUtils.shouldCancelCcsAttack(player)) { + read.executeThenChain(); + return; // Skip attack on teammate + } + } a.run(); read.executeThenChain(); } diff --git a/src/main/java/io/github/itzispyder/clickcrystals/scripting/syntax/macros/InteractCmd.java b/src/main/java/io/github/itzispyder/clickcrystals/scripting/syntax/macros/InteractCmd.java index 412aede8..ff6cdca4 100644 --- a/src/main/java/io/github/itzispyder/clickcrystals/scripting/syntax/macros/InteractCmd.java +++ b/src/main/java/io/github/itzispyder/clickcrystals/scripting/syntax/macros/InteractCmd.java @@ -5,10 +5,12 @@ import io.github.itzispyder.clickcrystals.scripting.ScriptParser; import io.github.itzispyder.clickcrystals.scripting.syntax.TargetType; import io.github.itzispyder.clickcrystals.scripting.syntax.ThenChainable; +import io.github.itzispyder.clickcrystals.util.minecraft.EntityUtils; import io.github.itzispyder.clickcrystals.util.minecraft.PlayerUtils; import io.github.itzispyder.clickcrystals.util.minecraft.VectorParser; import net.minecraft.block.BlockState; import net.minecraft.entity.Entity; +import net.minecraft.entity.player.PlayerEntity; import net.minecraft.util.Hand; import net.minecraft.util.hit.BlockHitResult; import net.minecraft.util.hit.EntityHitResult; @@ -39,6 +41,9 @@ public void onCommand(ScriptCommand command, String line, ScriptArgs args) { case NEAREST_ENTITY -> { Predicate filter = ScriptParser.parseEntityPredicate(read.nextStr()); PlayerUtils.runOnNearestEntity(128, filter, entity -> { + if (entity instanceof PlayerEntity player && EntityUtils.shouldCancelCcsAttack(player)) { + return; // Skip interacting with teammates + } EntityHitResult hitResult = new EntityHitResult(entity, entity.getBoundingBox().getCenter()); mc.interactionManager.interactEntityAtLocation(mc.player, entity, hitResult, Hand.MAIN_HAND); mc.interactionManager.interactEntity(mc.player, entity, Hand.MAIN_HAND); @@ -47,6 +52,9 @@ public void onCommand(ScriptCommand command, String line, ScriptArgs args) { } case ANY_ENTITY -> { PlayerUtils.runOnNearestEntity(128, DamageCmd.ENTITY_EXISTS, entity -> { + if (entity instanceof PlayerEntity player && EntityUtils.shouldCancelCcsAttack(player)) { + return; // Skip interacting with teammates + } EntityHitResult hitResult = new EntityHitResult(entity, entity.getBoundingBox().getCenter()); mc.interactionManager.interactEntityAtLocation(mc.player, entity, hitResult, Hand.MAIN_HAND); mc.interactionManager.interactEntity(mc.player, entity, Hand.MAIN_HAND); diff --git a/src/main/java/io/github/itzispyder/clickcrystals/util/minecraft/EntityUtils.java b/src/main/java/io/github/itzispyder/clickcrystals/util/minecraft/EntityUtils.java index 99e7d17a..f1dbc8e4 100644 --- a/src/main/java/io/github/itzispyder/clickcrystals/util/minecraft/EntityUtils.java +++ b/src/main/java/io/github/itzispyder/clickcrystals/util/minecraft/EntityUtils.java @@ -248,15 +248,30 @@ public static void runOnNearestEntity(Entity ref, double range, Predicate getEntitiesAt(BlockPos pos) {