Skip to content
Open
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand All @@ -17,7 +24,12 @@ public HelpCommand() {
@Override
public void build(LiteralArgumentBuilder<CommandSource> 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")
Expand All @@ -35,4 +47,24 @@ public void build(LiteralArgumentBuilder<CommandSource> 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;
}
}
Original file line number Diff line number Diff line change
@@ -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 <add|remove|list|clear> [player]");
}

@Override
public void build(LiteralArgumentBuilder<CommandSource> 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 <add|remove|list|clear> [player]");
return SINGLE_SUCCESS;
});
}
}
Original file line number Diff line number Diff line change
@@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<Integer, Boolean> heldKeys = new HashMap<>();

@EventHandler
public void onTickStart(ClientTickStartEvent e) {
Expand All @@ -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) {
Expand Down Expand Up @@ -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);
Expand All @@ -140,5 +163,12 @@ private void handleAutoKeys() {
if (shouldUse) {
mc.options.useKey.setPressed(true);
}

// Handle held keys
for (Map.Entry<Integer, Boolean> entry : heldKeys.entrySet()) {
if (entry.getValue()) {
((AccessorKeyboard) mc.keyboard).pressKey(entry.getKey(), 42);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -27,9 +30,15 @@ public class TeamDetector extends ListenerModule {
.def("")
.build()
);
public final ModuleSetting<Boolean> protectTeammates = scGeneral.add(createBoolSetting()
.name("protect-teammates")
.description("Prevent attacking detected teammates.")
.def(true)
.build()
);
public final ModuleSetting<Boolean> cancelCcs = scGeneral.add(createBoolSetting()
.name("cancel-ccs")
.description("disable ccs snapping and attacking.")
.description("Disable ClickCrystals scripting attacks on teammates.")
.def(true)
.build()
);
Expand All @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -38,11 +40,21 @@ public void onCommand(ScriptCommand command, String line, ScriptArgs args) {
switch (read.next(TargetType.class)) {
case NEAREST_ENTITY -> {
Predicate<Entity> 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 -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 <input> <num>
// @Format hold_input key ... <num>
// @Format hold_input cancel
public class HoldInputCmd extends ScriptCommand implements ThenChainable {

Expand All @@ -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();
}
}
Loading