diff --git a/.luarc.json b/.luarc.json new file mode 100644 index 0000000..3acd1c4 --- /dev/null +++ b/.luarc.json @@ -0,0 +1,7 @@ +{ + "diagnostics.disable": [ + "undefined-global", + "lowercase-global", + "unused-local" + ] +} \ No newline at end of file diff --git a/build.gradle b/build.gradle index 602cfb0..cb6efba 100644 --- a/build.gradle +++ b/build.gradle @@ -76,7 +76,7 @@ dependencies { implementation 'com.fasterxml.jackson.core:jackson-databind:2.15.2' implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8" - // GraalVM JavaScript for scripting (replacement for Nashorn) + // GraalVM JavaScript for core (replacement for Nashorn) include(implementation('org.graalvm.js:js:22.3.1')) include(implementation('org.graalvm.js:js-scriptengine:22.3.1')) include(implementation('org.graalvm.regex:regex:22.3.1')) @@ -92,6 +92,9 @@ dependencies { // i do not want to make a renderer include(modImplementation("io.github.0x3c50.renderer:renderer-fabric:2.1.1")) + + // LuaJ for Lua core + include(implementation("org.luaj:luaj-jse:3.0.1")) } processResources { diff --git a/src/client/java/works/alya/AlyaClient.java b/src/client/java/works/alya/AlyaClient.java index bc47a7a..e664dba 100644 --- a/src/client/java/works/alya/AlyaClient.java +++ b/src/client/java/works/alya/AlyaClient.java @@ -42,6 +42,7 @@ import works.alya.module.impl.world.TimerModule; import works.alya.module.impl.visual.clickgui.ClickGUIModule; import works.alya.module.impl.visual.esp.ESPModule; +import works.alya.script.core.ScriptManager; import works.alya.utilities.misc.IconLoader; import works.alya.utilities.misc.AlyaConstants; import net.fabricmc.api.ClientModInitializer; @@ -87,6 +88,8 @@ public void onInitializeClient() { KeybindManager.getInstance().initialize(); VisualManager.getInstance().initialize(); + ScriptManager.getInstance().init(); + CommandRegistrationCallback.EVENT.register(( dispatcher, registryAccess, @@ -178,7 +181,9 @@ private static void initializeCommands() { new ConfigCommand(), new BindCommand(), new SettingsCommand(), - new VClipCommand() + new VClipCommand(), + new ScriptCommand(), + new NameCommand() ); } diff --git a/src/client/java/works/alya/command/impl/NameCommand.java b/src/client/java/works/alya/command/impl/NameCommand.java new file mode 100644 index 0000000..8e5da36 --- /dev/null +++ b/src/client/java/works/alya/command/impl/NameCommand.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) Alya Client 2024-2025. + * + * This file belongs to Alya Client, + * an open-source Fabric injection client. + * Rye GitHub: https://github.com/AlyaClient/alya-beta.git + * + * THIS PROJECT DOES NOT HAVE A WARRANTY. + * + * Alya (and subsequently, its files) are all licensed under the MIT License. + * Alya should have come with a copy of the MIT License. + * If it did not, you may obtain a copy here: + * MIT License: https://opensource.org/license/mit + * + */ + +package works.alya.command.impl; + +import com.mojang.brigadier.arguments.StringArgumentType; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.context.CommandContext; +import works.alya.command.AbstractCommand; +import works.alya.utilities.misc.AlyaConstants; +import works.alya.utilities.misc.ChatUtility; +import net.minecraft.server.command.CommandManager; +import net.minecraft.server.command.ServerCommandSource; + +public class NameCommand extends AbstractCommand { + public NameCommand() { + super("name"); + } + + @Override + protected void build(LiteralArgumentBuilder builder) { + builder + .then(CommandManager.argument("name", StringArgumentType.word()) + .executes(context -> { + AlyaConstants.NAME = StringArgumentType.getString(context, "name"); + return 1; + })) + .executes(this::usage); + } + + private int usage(CommandContext serverCommandSourceCommandContext) { + ChatUtility.sendInfo("Usage: .name "); + return 1; + } +} diff --git a/src/client/java/works/alya/command/impl/ScriptCommand.java b/src/client/java/works/alya/command/impl/ScriptCommand.java index e69de29..26a49c5 100644 --- a/src/client/java/works/alya/command/impl/ScriptCommand.java +++ b/src/client/java/works/alya/command/impl/ScriptCommand.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) Alya Client 2024-2025. + * + * This file belongs to Alya Client, + * an open-source Fabric injection client. + * Rye GitHub: https://github.com/AlyaClient/alya-beta.git + * + * THIS PROJECT DOES NOT HAVE A WARRANTY. + * + * Alya (and subsequently, its files) are all licensed under the MIT License. + * Alya should have come with a copy of the MIT License. + * If it did not, you may obtain a copy here: + * MIT License: https://opensource.org/license/mit + * + */ + +package works.alya.command.impl; + +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import net.minecraft.server.command.ServerCommandSource; +import works.alya.AlyaClient; +import works.alya.command.AbstractCommand; +import works.alya.module.Module; +import works.alya.module.ModuleCategory; +import works.alya.script.core.ScriptManager; +import works.alya.utilities.misc.ChatUtility; + +/** + * Command for managing scripts in the Alya Client. + */ +public class ScriptCommand extends AbstractCommand { + + public ScriptCommand() { + super("scripts"); + } + + @Override + protected void build(LiteralArgumentBuilder builder) { + builder + .then(literal("reload") + .executes(context -> { + for(Module module : AlyaClient.INSTANCE.getModuleRepository().getModules()) { + if(module.getCategory().equals(ModuleCategory.SCRIPTS)) { + module.setEnabled(false); + } + } + + ScriptManager.getInstance().loadScripts(); + ChatUtility.sendSuccess("§aScripts reloaded successfully!"); + return 1; + })); + } + + private static LiteralArgumentBuilder literal(String name) { + if(name.isEmpty()) throw new IllegalArgumentException("Name cannot be empty!"); + + return LiteralArgumentBuilder.literal(name); + } +} \ No newline at end of file diff --git a/src/client/java/works/alya/config/VisualManager.java b/src/client/java/works/alya/config/VisualManager.java index 46b8174..3fa5e7c 100644 --- a/src/client/java/works/alya/config/VisualManager.java +++ b/src/client/java/works/alya/config/VisualManager.java @@ -126,6 +126,14 @@ public void updateVisualModuleData(Module module) { LOGGER.error("Failed to get DragUtility from module {}: {}", moduleName, e.getMessage()); } + Map settingValues = new HashMap<>(); + for(works.alya.config.setting.Setting setting : module.getSettings()) { + if(!setting.getValue().equals(setting.getDefaultValue())) { + settingValues.put(setting.getName(), setting.getValue()); + } + } + data.setSettingValues(settingValues); + visualModulesData.put(moduleName, data); } @@ -177,7 +185,22 @@ public void applyVisualDataToModule(Module module) { VisualModuleData data = visualModulesData.get(moduleName); if(data != null) { - module.setEnabled(data.isEnabled()); + Map settingValues = data.getSettingValues(); + if(settingValues != null && !settingValues.isEmpty()) { + for(works.alya.config.setting.Setting setting : module.getSettings()) { + Object value = settingValues.get(setting.getName()); + if(value != null) { + try { + if(!value.equals(setting.getDefaultValue())) { + setting.setValueFromObject(value); + } + } catch(Exception e) { + LOGGER.error("Failed to set value for setting {} in module {}: {}", + setting.getName(), moduleName, e.getMessage()); + } + } + } + } try { for(Field field : module.getClass().getDeclaredFields()) { @@ -194,6 +217,8 @@ public void applyVisualDataToModule(Module module) { } catch(Exception e) { LOGGER.error("Failed to set DragUtility for module {}: {}", moduleName, e.getMessage()); } + + module.setEnabled(data.isEnabled()); } } @@ -204,6 +229,7 @@ private static class VisualModuleData { private boolean enabled = false; private int x = 4; private int y = 4; + private Map settingValues = new HashMap<>(); public boolean isEnabled() { return enabled; @@ -228,5 +254,13 @@ public int getY() { public void setY(int y) { this.y = y; } + + public Map getSettingValues() { + return settingValues; + } + + public void setSettingValues(Map settingValues) { + this.settingValues = settingValues; + } } -} \ No newline at end of file +} diff --git a/src/client/java/works/alya/config/setting/Setting.java b/src/client/java/works/alya/config/setting/Setting.java index b939907..7f10e87 100644 --- a/src/client/java/works/alya/config/setting/Setting.java +++ b/src/client/java/works/alya/config/setting/Setting.java @@ -17,6 +17,7 @@ package works.alya.config.setting; import works.alya.config.VisibilityCondition; +import java.util.function.Consumer; public class Setting { private final String name; @@ -26,6 +27,7 @@ public class Setting { private final T minValue; private final T maxValue; private VisibilityCondition visibilityCondition = () -> true; + private Consumer> changeCallback = null; @SuppressWarnings("unused") public Setting(String name, String description, T defaultValue) { @@ -35,8 +37,8 @@ public Setting(String name, String description, T defaultValue) { public Setting(String name, String description, T defaultValue, T minValue, T maxValue) { this.name = name; this.description = description; - this.value = defaultValue; this.defaultValue = defaultValue; + this.value = defaultValue; this.minValue = minValue; this.maxValue = maxValue; } @@ -73,6 +75,19 @@ public void setValue(T value) { } this.value = value; + + if(this.changeCallback != null) { + this.changeCallback.accept(this); + } + } + + /** + * Sets a callback to be called when the setting value changes. + * + * @param callback The callback to call when the value changes + */ + public void setChangeCallback(Consumer> callback) { + this.changeCallback = callback; } @SuppressWarnings("unchecked") @@ -137,4 +152,4 @@ public Setting setVisibilityCondition(VisibilityCondition condition) { public boolean isVisible() { return visibilityCondition.isVisible(); } -} \ No newline at end of file +} diff --git a/src/client/java/works/alya/config/setting/impl/ModeSetting.java b/src/client/java/works/alya/config/setting/impl/ModeSetting.java index 93869b7..d511979 100644 --- a/src/client/java/works/alya/config/setting/impl/ModeSetting.java +++ b/src/client/java/works/alya/config/setting/impl/ModeSetting.java @@ -42,10 +42,6 @@ public class ModeSetting extends Setting { public ModeSetting(String name, String description, String defaultValue, String... modes) { super(name, description, defaultValue); this.modes = new ArrayList<>(Arrays.asList(modes)); - -// if(!this.modes.contains(defaultValue)) { -// throw new IllegalArgumentException("Default value must be one of the modes"); -// } } /** diff --git a/src/client/java/works/alya/event/EventBus.java b/src/client/java/works/alya/event/EventBus.java index 10be793..ccb447c 100644 --- a/src/client/java/works/alya/event/EventBus.java +++ b/src/client/java/works/alya/event/EventBus.java @@ -16,6 +16,7 @@ package works.alya.event; +import works.alya.AlyaClient; import works.alya.event.pool.EventSubscriberPool; import java.util.ArrayList; @@ -23,7 +24,7 @@ @SuppressWarnings("unchecked") public final class EventBus { - private List> listeners = new ArrayList<>(); + private final List> listeners = new ArrayList<>(); public void register(IEventListener listener) { listeners.add(listener); diff --git a/src/client/java/works/alya/mixin/client/misc/WorldRendererMixin.java b/src/client/java/works/alya/mixin/client/misc/WorldRendererMixin.java index ff410f6..e13ff58 100644 --- a/src/client/java/works/alya/mixin/client/misc/WorldRendererMixin.java +++ b/src/client/java/works/alya/mixin/client/misc/WorldRendererMixin.java @@ -17,7 +17,9 @@ package works.alya.mixin.client.misc; import com.mojang.blaze3d.buffers.GpuBufferSlice; +import org.spongepowered.asm.mixin.Unique; import works.alya.AlyaClient; +import works.alya.config.VisualManager; import works.alya.event.impl.Render3DEvent; import net.minecraft.client.render.Camera; import net.minecraft.client.render.RenderTickCounter; @@ -49,4 +51,4 @@ private void onRender( new Render3DEvent(tickCounter.getFixedDeltaTicks()) ); } -} \ No newline at end of file +} diff --git a/src/client/java/works/alya/mixin/client/ui/ScreenMixin.java b/src/client/java/works/alya/mixin/client/ui/ScreenMixin.java index 561b86a..a8b3646 100644 --- a/src/client/java/works/alya/mixin/client/ui/ScreenMixin.java +++ b/src/client/java/works/alya/mixin/client/ui/ScreenMixin.java @@ -31,7 +31,7 @@ public class ScreenMixin { @Inject(method = "renderPanoramaBackground", at = @At("HEAD"), cancellable = true) private void interceptPanoramaBackground(DrawContext context, float deltaTicks, CallbackInfo ci) { if(!Settings.SHOW_PANORAMA) { - BackgroundUtility.drawBackground(context); + BackgroundUtility.drawStarField(context); ci.cancel(); } diff --git a/src/client/java/works/alya/mixin/client/ui/WatermarkTitleScreenMixin.java b/src/client/java/works/alya/mixin/client/ui/WatermarkTitleScreenMixin.java index db7ce49..08b08cb 100644 --- a/src/client/java/works/alya/mixin/client/ui/WatermarkTitleScreenMixin.java +++ b/src/client/java/works/alya/mixin/client/ui/WatermarkTitleScreenMixin.java @@ -23,7 +23,6 @@ import net.minecraft.client.gui.*; import net.minecraft.client.gui.screen.SplashTextRenderer; import net.minecraft.client.gui.screen.TitleScreen; -import net.minecraft.client.gui.widget.ButtonWidget; import org.jetbrains.annotations.Nullable; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; @@ -32,9 +31,6 @@ import org.spongepowered.asm.mixin.injection.Redirect; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; -import java.util.ArrayList; -import java.util.List; - @Mixin(TitleScreen.class) public class WatermarkTitleScreenMixin { @@ -91,42 +87,6 @@ private void disableSplashText(CallbackInfo ci) { private void cancelSplashTextRendering(SplashTextRenderer splashTextRenderer, DrawContext context, int width, net.minecraft.client.font.TextRenderer textRenderer, float alpha) { } - @Inject(method = "init", at = @At("TAIL")) - private void centerButtons(CallbackInfo ci) { - TitleScreen screen = (TitleScreen) (Object) this; - - int screenWidth = screen.width; - int screenHeight = screen.height; - int centerX = screenWidth / 2; - int centerY = screenHeight / 2; - - List buttons = new ArrayList<>(); - for(Element element : screen.children()) { - if(element instanceof ButtonWidget button) { - buttons.add(button); - } - } - - if(!buttons.isEmpty()) { - int totalHeight = 0; - for(int i = 0; i < buttons.size(); i++) { - totalHeight += buttons.get(i).getHeight(); - if(i < buttons.size() - 1) { - totalHeight += 24; - } - } - - int currentY = centerY - (totalHeight / 2); - for(ButtonWidget button : buttons) { - int buttonWidth = button.getWidth(); - int newX = centerX - (buttonWidth / 2); - - button.setPosition(newX, currentY); - currentY += button.getHeight() + 24; - } - } - } - @Inject(method = "init", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screen/AccessibilityOnboardingButtons;createLanguageButton(ILnet/minecraft/client/gui/widget/ButtonWidget$PressAction;Z)Lnet/minecraft/client/gui/widget/TextIconButtonWidget;", shift = At.Shift.AFTER), cancellable = true) private void skipLanguageButtonSetup(CallbackInfo ci) { ci.cancel(); diff --git a/src/client/java/works/alya/module/Module.java b/src/client/java/works/alya/module/Module.java index e7132de..5343e01 100644 --- a/src/client/java/works/alya/module/Module.java +++ b/src/client/java/works/alya/module/Module.java @@ -134,6 +134,16 @@ public void toggle() { protected void addSetting(Setting setting) { settings.put(setting.getName().toLowerCase(), setting); + + // For visual modules, add a callback to save settings when they change + if(this.category == ModuleCategory.VISUAL) { + setting.setChangeCallback(changedSetting -> { + if(MinecraftClient.getInstance().player != null) { + VisualManager.getInstance().updateVisualModuleData(this); + VisualManager.getInstance().saveVisualData(); + } + }); + } } public Setting getSetting(String name) { diff --git a/src/client/java/works/alya/module/impl/combat/killaura/KillauraModule.java b/src/client/java/works/alya/module/impl/combat/killaura/KillauraModule.java index c5414b4..46e61bc 100644 --- a/src/client/java/works/alya/module/impl/combat/killaura/KillauraModule.java +++ b/src/client/java/works/alya/module/impl/combat/killaura/KillauraModule.java @@ -1,19 +1,3 @@ -/* - * Copyright (c) Alya Client 2024-2025. - * - * This file belongs to Alya Client, - * an open-source Fabric injection client. - * Rye GitHub: https://github.com/AlyaClient/alya-beta.git - * - * THIS PROJECT DOES NOT HAVE A WARRANTY. - * - * Alya (and subsequently, its files) are all licensed under the MIT License. - * Alya should have come with a copy of the MIT License. - * If it did not, you may obtain a copy here: - * MIT License: https://opensource.org/license/mit - * - */ - package works.alya.module.impl.combat.killaura; import works.alya.AlyaClient; @@ -26,18 +10,20 @@ import works.alya.module.Module; import works.alya.module.ModuleCategory; import works.alya.module.impl.combat.AttackDelayModule; -import works.alya.utilities.misc.ChatUtility; import net.minecraft.entity.Entity; import net.minecraft.entity.LivingEntity; import net.minecraft.entity.passive.PassiveEntity; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.entity.mob.HostileEntity; +import net.minecraft.entity.decoration.ArmorStandEntity; import net.minecraft.item.ItemStack; +import net.minecraft.item.AxeItem; import net.minecraft.util.Hand; import net.minecraft.util.hit.BlockHitResult; import net.minecraft.util.hit.HitResult; import net.minecraft.util.math.MathHelper; import net.minecraft.util.math.Vec3d; +import net.minecraft.util.math.Box; import net.minecraft.network.packet.c2s.play.PlayerActionC2SPacket; import net.minecraft.network.packet.c2s.play.PlayerInteractItemC2SPacket; import net.minecraft.util.math.BlockPos; @@ -54,63 +40,122 @@ public class KillauraModule extends Module { private final ModeSetting attackMode = new ModeSetting("AttackMode", "Attack mode", "Single", "Single", "Switch", "Multi"); private final ModeSetting targetMode = new ModeSetting("Target", "Target types", "Players", "Players", "Passive", "Hostile", "All"); - private final NumberSetting swingDistance = new NumberSetting<>("SwingDistance", "Distance to start swinging", 4.5, 3.0, 6.0); + private final ModeSetting sortMode = new ModeSetting("Sort", "Target sorting", "Distance", "Distance", "Health", "Angle", "Hurt"); + private final NumberSetting swingDistance = new NumberSetting<>("SwingDistance", "Distance to start swinging", 4.5, 3.0, 8.0); private final NumberSetting reach = new NumberSetting<>("Reach", "Distance to actually attack from", 3.0, 3.0, 6.0); private final NumberSetting cps = new NumberSetting<>("CPS", "Attacks per second", 12, 1, 20); + private final NumberSetting minCps = new NumberSetting<>("MinCPS", "Minimum attacks per second", 10, 1, 20); + private final NumberSetting maxCps = new NumberSetting<>("MaxCPS", "Maximum attacks per second", 14, 1, 20); + private final BooleanSetting randomizeCps = new BooleanSetting("RandomCPS", "Randomize CPS", false); private final BooleanSetting noHitDelay = new BooleanSetting("NoHitDelay", "Remove attack delay", false); private final BooleanSetting raycast = new BooleanSetting("Raycast", "Check line of sight to target", true); + private final BooleanSetting wallCheck = new BooleanSetting("WallCheck", "Don't attack through walls", true); private final BooleanSetting movementCorrection = new BooleanSetting("MoveCorrection", "Do not attack impossibly", true); private final BooleanSetting rotate = new BooleanSetting("Rotate", "Rotate to targets", true); - private final ModeSetting rotationType = new ModeSetting("Rotations", "Rotation mode", "Smooth", "Snap", "Smooth", "Linear"); + private final ModeSetting rotationType = new ModeSetting("Rotations", "Rotation mode", "Smooth", "Snap", "Smooth", "Linear", "Predict"); private final NumberSetting rotationSpeed = new NumberSetting<>("RotationSpeed", "Rotation smoothness", 3.0, 1.0, 10.0); + private final NumberSetting maxRotationAngle = new NumberSetting<>("MaxRotation", "Max rotation per tick", 45.0, 10.0, 180.0); private final BooleanSetting gcd = new BooleanSetting("GCD", "Greatest Common Divisor stuff idk", true); private final BooleanSetting autoBlock = new BooleanSetting("AutoBlock", "Auto block with sword", false); - private final ModeSetting autoBlockMode = new ModeSetting("AutoBlockMode", "AutoBlock mode", "Vanilla", "Vanilla", "Packet"); + private final ModeSetting autoBlockMode = new ModeSetting("AutoBlockMode", "AutoBlock mode", "Vanilla", "Vanilla", "Packet", "Fake"); + private final NumberSetting blockDelay = new NumberSetting<>("BlockDelay", "Block delay in ms", 50, 0, 200); + private final NumberSetting unblockDelay = new NumberSetting<>("UnblockDelay", "Unblock delay in ms", 25, 0, 200); + private final BooleanSetting smartBlock = new BooleanSetting("SmartBlock", "Smart blocking timing", true); + private final BooleanSetting throughWalls = new BooleanSetting("ThroughWalls", "Attack through walls", false); + private final BooleanSetting predictMovement = new BooleanSetting("Predict", "Predict target movement", false); + private final BooleanSetting criticals = new BooleanSetting("Criticals", "Try to get critical hits", false); + private final BooleanSetting keepSprint = new BooleanSetting("KeepSprint", "Keep sprinting while attacking", false); + private final BooleanSetting ignoreInvisible = new BooleanSetting("IgnoreInvis", "Ignore invisible entities", true); + private final BooleanSetting prioritizeTarget = new BooleanSetting("PrioritizeTarget", "Prioritize certain targets", false); + private final NumberSetting fov = new NumberSetting<>("FOV", "Field of view", 360.0, 30.0, 360.0); + private final NumberSetting switchDelay = new NumberSetting<>("SwitchDelay", "Switch target delay", 200, 50, 1000); + private final List targets = new ArrayList<>(); private Entity currentTarget; + private Entity lastTarget; private int switchTimer = 0; private boolean isBlocking = false; private long lastAttackTime = 0; + private long lastBlockTime = 0; + private long lastUnblockTime = 0; + private long lastSwitchTime = 0; private final Random random = new Random(); private float lastYaw = 0f; private float lastPitch = 0f; private float targetYaw = 0f; private float targetPitch = 0f; private long lastRotationTime = 0; + private Vec3d lastTargetPos = null; + private int attackCount = 0; + private boolean wasBlocking = false; + private int currentCps = 12; public KillauraModule() { super("Killaura", "Kill Aura", "Automatically attack entities", ModuleCategory.COMBAT); rotationType.setVisibilityCondition(rotate::getValue); - rotationSpeed.setVisibilityCondition(() -> rotate.getValue() && rotationType.getValue().equals("Smooth")); + rotationSpeed.setVisibilityCondition(() -> rotate.getValue() && (rotationType.getValue().equals("Smooth") || rotationType.getValue().equals("Predict"))); + maxRotationAngle.setVisibilityCondition(() -> rotate.getValue() && !rotationType.getValue().equals("Snap")); autoBlockMode.setVisibilityCondition(autoBlock::getValue); + blockDelay.setVisibilityCondition(() -> autoBlock.getValue() && !autoBlockMode.getValue().equals("Fake")); + unblockDelay.setVisibilityCondition(() -> autoBlock.getValue() && !autoBlockMode.getValue().equals("Fake")); + smartBlock.setVisibilityCondition(autoBlock::getValue); gcd.setVisibilityCondition(rotate::getValue); + minCps.setVisibilityCondition(randomizeCps::getValue); + maxCps.setVisibilityCondition(randomizeCps::getValue); + switchDelay.setVisibilityCondition(() -> attackMode.getValue().equals("Switch")); + wallCheck.setVisibilityCondition(() -> !throughWalls.getValue()); addSetting(attackMode); addSetting(targetMode); + addSetting(sortMode); addSetting(swingDistance); addSetting(reach); addSetting(cps); + addSetting(randomizeCps); + addSetting(minCps); + addSetting(maxCps); addSetting(rotate); addSetting(rotationType); addSetting(rotationSpeed); + addSetting(maxRotationAngle); addSetting(autoBlock); addSetting(autoBlockMode); + addSetting(blockDelay); + addSetting(unblockDelay); + addSetting(smartBlock); addSetting(movementCorrection); addSetting(gcd); addSetting(raycast); + addSetting(wallCheck); + addSetting(throughWalls); + addSetting(predictMovement); + addSetting(criticals); + addSetting(keepSprint); + addSetting(ignoreInvisible); + addSetting(prioritizeTarget); + addSetting(fov); + addSetting(switchDelay); addSetting(noHitDelay); } private final IEventListener tickEvent = event -> { AttackDelayModule attackDelayModule = AlyaClient.INSTANCE.getModuleRepository().getModule(AttackDelayModule.class); - if(attackDelayModule.isEnabled() && attackDelayModule.isNewPvpDelay() || noHitDelay.getValue()) { + if(attackDelayModule != null && attackDelayModule.isEnabled() && attackDelayModule.isNewPvpDelay() || noHitDelay.getValue()) { cps.setVisibilityCondition(() -> false); } else { cps.setVisibilityCondition(() -> true); } - setPrefix(rotationType.getValue()); + if(randomizeCps.getValue()) { + if(System.currentTimeMillis() - lastAttackTime > 1000) { + currentCps = random.nextInt(maxCps.getValue() - minCps.getValue() + 1) + minCps.getValue(); + } + } else { + currentCps = cps.getValue(); + } + + setPrefix(rotationType.getValue() + " " + targets.size()); }; private final IEventListener motionEvent = event -> { @@ -118,23 +163,23 @@ public KillauraModule() { findTargets(); selectTarget(); - handleAutoBlock(); if(targets.isEmpty()) { - if(isBlocking) - stopBlocking(); + if(isBlocking) stopBlocking(); return; } if(rotate.getValue() && currentTarget != null) { calculateRotations(); - event.setYaw(targetYaw); event.setPitch(targetPitch); } - if(canAttack() && isValidHit()) + handleAutoBlock(); + + if(canAttack() && isValidHit()) { performAttack(); + } }; private void findTargets() { @@ -142,17 +187,74 @@ private void findTargets() { if(mc.player == null || mc.world == null) return; - List entities = mc.world.getEntitiesByClass(Entity.class, mc.player.getBoundingBox().expand(1000), this::isValidTarget); - entities.sort(Comparator.comparingDouble(entity -> entity.squaredDistanceTo(mc.player))); + double maxDistance = Math.max(swingDistance.getValue(), reach.getValue()) + 1.0; + List entities = mc.world.getEntitiesByClass(Entity.class, + mc.player.getBoundingBox().expand(maxDistance), this::isValidTarget); for(Entity entity : entities) { - if(raycast.getValue() && !canSeeTarget(entity)) continue; + if(entity.squaredDistanceTo(mc.player) > maxDistance * maxDistance) continue; + if(fov.getValue() < 360.0 && !isInFOV(entity)) continue; + if(raycast.getValue() && wallCheck.getValue() && !canSeeTarget(entity)) continue; + if(ignoreInvisible.getValue() && entity.isInvisible()) continue; + targets.add(entity); } + + sortTargets(); + } + + private void sortTargets() { + if(mc.player == null) return; + + switch(sortMode.getValue()) { + case "Distance" -> targets.sort(Comparator.comparingDouble(entity -> entity.squaredDistanceTo(mc.player))); + case "Health" -> targets.sort(Comparator.comparingDouble(entity -> { + if(entity instanceof LivingEntity living) { + return living.getHealth(); + } + return 0.0; + })); + case "Angle" -> targets.sort(Comparator.comparingDouble(this::getAngleToEntity)); + case "Hurt" -> targets.sort(Comparator.comparingInt(entity -> { + if(entity instanceof LivingEntity living) { + return living.hurtTime; + } + return 0; + })); + } + } + + private boolean isInFOV(Entity entity) { + if(mc.player == null) return false; + + Vec3d playerPos = mc.player.getEyePos(); + Vec3d entityPos = entity.getBoundingBox().getCenter(); + Vec3d direction = entityPos.subtract(playerPos).normalize(); + + Vec3d lookDirection = getLookDirection(mc.player.getYaw(), mc.player.getPitch()); + + double dotProduct = direction.dotProduct(lookDirection); + double angle = Math.toDegrees(Math.acos(MathHelper.clamp(dotProduct, -1.0, 1.0))); + + return angle <= fov.getValue() / 2.0; + } + + private double getAngleToEntity(Entity entity) { + if(mc.player == null) return 0.0; + + Vec3d playerPos = mc.player.getEyePos(); + Vec3d entityPos = entity.getBoundingBox().getCenter(); + Vec3d direction = entityPos.subtract(playerPos).normalize(); + + Vec3d lookDirection = getLookDirection(mc.player.getYaw(), mc.player.getPitch()); + + double dotProduct = direction.dotProduct(lookDirection); + return Math.acos(MathHelper.clamp(dotProduct, -1.0, 1.0)); } private boolean canSeeTarget(Entity target) { if(mc.player == null || mc.world == null) return false; + if(throughWalls.getValue()) return true; Vec3d playerEyes = mc.player.getEyePos(); Vec3d targetPos = getTargetPosition(target); @@ -175,33 +277,46 @@ private boolean isValidTarget(Entity entity) { return false; } - if(entity.isInvisible() && !mc.player.canSee(entity)) { - return false; - } + if(entity instanceof ArmorStandEntity) return false; return switch(targetMode.getValue()) { case "Players" -> entity instanceof PlayerEntity; case "Passive" -> entity instanceof PassiveEntity; case "Hostile" -> entity instanceof HostileEntity; - case "All" -> - entity instanceof PlayerEntity || entity instanceof PassiveEntity || entity instanceof HostileEntity; + case "All" -> entity instanceof PlayerEntity || entity instanceof PassiveEntity || entity instanceof HostileEntity; default -> false; }; } private void selectTarget() { + long currentTime = System.currentTimeMillis(); + switch(attackMode.getValue()) { - case "Single", "Multi" -> currentTarget = targets.isEmpty() ? null : targets.getFirst(); + case "Single", "Multi" -> { + if(prioritizeTarget.getValue() && currentTarget != null && targets.contains(currentTarget)) { + return; + } + currentTarget = targets.isEmpty() ? null : targets.getFirst(); + } case "Switch" -> { - if(switchTimer++ >= 20 && targets.size() > 1) { - switchTimer = 0; - int currentIndex = targets.indexOf(currentTarget); - currentTarget = targets.get((currentIndex + 1) % targets.size()); + if(currentTime - lastSwitchTime >= switchDelay.getValue()) { + if(targets.size() > 1) { + int currentIndex = targets.indexOf(currentTarget); + currentTarget = targets.get((currentIndex + 1) % targets.size()); + lastSwitchTime = currentTime; + } else if(currentTarget == null || !targets.contains(currentTarget)) { + currentTarget = targets.isEmpty() ? null : targets.getFirst(); + } } else if(currentTarget == null || !targets.contains(currentTarget)) { currentTarget = targets.isEmpty() ? null : targets.getFirst(); } } } + + if(currentTarget != lastTarget) { + lastTarget = currentTarget; + lastTargetPos = currentTarget != null ? currentTarget.getPos() : null; + } } private float[] applyGCDCorrection(float yaw, float pitch) { @@ -209,11 +324,9 @@ private float[] applyGCDCorrection(float yaw, float pitch) { return new float[]{yaw, pitch}; } - float gcd = getGCD(); - - float correctedYaw = Math.round(yaw / gcd) * gcd; - float correctedPitch = Math.round(pitch / gcd) * gcd; - + float gcdValue = getGCD(); + float correctedYaw = Math.round(yaw / gcdValue) * gcdValue; + float correctedPitch = Math.round(pitch / gcdValue) * gcdValue; correctedPitch = MathHelper.clamp(correctedPitch, -90.0F, 90.0F); return new float[]{correctedYaw, correctedPitch}; @@ -230,6 +343,10 @@ private void calculateRotations() { Vec3d playerPos = mc.player.getEyePos(); Vec3d targetPos = getTargetPosition(currentTarget); + if(predictMovement.getValue()) { + targetPos = getPredictedPosition(currentTarget); + } + double deltaX = targetPos.x - playerPos.x; double deltaY = targetPos.y - playerPos.y; double deltaZ = targetPos.z - playerPos.z; @@ -250,23 +367,52 @@ private void calculateRotations() { float yawDelta = MathHelper.wrapDegrees(targetYaw - lastYaw); float pitchDelta = targetPitch - lastPitch; + float maxAngle = maxRotationAngle.getValue().floatValue(); + yawDelta = MathHelper.clamp(yawDelta, -maxAngle, maxAngle); + pitchDelta = MathHelper.clamp(pitchDelta, -maxAngle, maxAngle); + lastYaw += yawDelta / smoothness; lastPitch += pitchDelta / smoothness; lastPitch = MathHelper.clamp(lastPitch, -90.0F, 90.0F); } case "Linear" -> { long currentTime = System.currentTimeMillis(); - if(currentTime - lastRotationTime > 50) { lastRotationTime = currentTime; - float jitterYaw = (random.nextFloat() - 0.5f) * 2.0f; float jitterPitch = (random.nextFloat() - 0.5f); - lastYaw = targetYaw + jitterYaw; lastPitch = MathHelper.clamp(targetPitch + jitterPitch, -90.0F, 90.0F); } } + case "Predict" -> { + Vec3d predictedPos = getPredictedTargetPosition(currentTarget); + + double predDeltaX = predictedPos.x - playerPos.x; + double predDeltaY = predictedPos.y - playerPos.y; + double predDeltaZ = predictedPos.z - playerPos.z; + + double predHorizontalDistance = Math.sqrt(predDeltaX * predDeltaX + predDeltaZ * predDeltaZ); + + float predictedYaw = (float) Math.toDegrees(Math.atan2(predDeltaZ, predDeltaX)) - 90.0F; + float predictedPitch = (float) -Math.toDegrees(Math.atan2(predDeltaY, predHorizontalDistance)); + predictedPitch = MathHelper.clamp(predictedPitch, -90.0F, 90.0F); + + float smoothness = rotationSpeed.getValue().floatValue(); + float yawDelta = MathHelper.wrapDegrees(predictedYaw - lastYaw); + float pitchDelta = predictedPitch - lastPitch; + + float accelerationFactor = calculateAccelerationFactor(currentTarget); + smoothness *= accelerationFactor; + + float maxAngle = maxRotationAngle.getValue().floatValue(); + yawDelta = MathHelper.clamp(yawDelta, -maxAngle, maxAngle); + pitchDelta = MathHelper.clamp(pitchDelta, -maxAngle, maxAngle); + + lastYaw += yawDelta / smoothness; + lastPitch += pitchDelta / smoothness; + lastPitch = MathHelper.clamp(lastPitch, -90.0F, 90.0F); + } } float[] correctedRotations = applyGCDCorrection(lastYaw, lastPitch); @@ -274,6 +420,100 @@ private void calculateRotations() { targetPitch = correctedRotations[1]; } + private Vec3d getPredictedTargetPosition(Entity target) { + if(mc.player == null) return target.getBoundingBox().getCenter(); + if(!(target instanceof LivingEntity living)) { + return getTargetPosition(target); + } + + Vec3d currentPos = target.getPos(); + Vec3d velocity = target.getVelocity(); + + double distance = mc.player.distanceTo(target); + + double targetSpeed = velocity.length(); + double predictionTime = Math.min(distance / 10.0, 1.0); + + if(targetSpeed > 0.1) { + predictionTime *= (1.0 + targetSpeed * 2.0); + } + + predictionTime += (random.nextGaussian() * 0.1); + predictionTime = Math.max(0.1, Math.min(predictionTime, 1.5)); + + Vec3d predictedVelocity = velocity; + if(!target.isOnGround()) { + predictedVelocity = velocity.add(0, -0.08 * predictionTime, 0); + } + + if(target instanceof PlayerEntity player) { + Vec3d lookDir = player.getRotationVector(); + double alignment = velocity.normalize().dotProduct(lookDir); + if(alignment < 0.5) { + predictionTime *= 0.7; + } + } + + Vec3d predictedPos = currentPos.add(predictedVelocity.multiply(predictionTime)); + + Box targetBox = target.getBoundingBox(); + Vec3d offset = new Vec3d( + targetBox.getLengthX() * 0.5, + targetBox.getLengthY() * 0.3, + targetBox.getLengthZ() * 0.5 + ); + + return predictedPos.add(offset); + } + + private float calculateAccelerationFactor(Entity target) { + if(mc.player == null) return 1.0f; + if(target == null) return 1.0f; + + Vec3d velocity = target.getVelocity(); + double speed = velocity.length(); + + float factor = 1.0f; + + if(speed > 0.2) { + factor *= (float)(0.7 + speed * 1.5); + } + + double distance = mc.player.distanceTo(target); + if(distance < 3.0) { + factor *= (float)(0.8 + (3.0 - distance) * 0.3); + } + + if(lastTargetPos != null) { + Vec3d currentPos = target.getPos(); + Vec3d currentMovement = currentPos.subtract(lastTargetPos); + + if(target instanceof LivingEntity living) { + float yawChange = Math.abs(living.getYaw() - living.bodyYaw); + if(yawChange > 5.0f) { + factor *= 1.3f; + } + } + } + + return Math.max(0.5f, Math.min(factor, 3.0f)); + } + + private Vec3d getPredictedPosition(Entity entity) { + if(mc.player == null) return entity.getBoundingBox().getCenter(); + if(lastTargetPos == null) return entity.getBoundingBox().getCenter(); + + Vec3d currentPos = entity.getPos(); + Vec3d velocity = currentPos.subtract(lastTargetPos); + + lastTargetPos = currentPos; + + double distance = mc.player.distanceTo(entity); + double predictionMultiplier = Math.min(distance / 5.0, 2.0); + + return entity.getBoundingBox().getCenter().add(velocity.multiply(predictionMultiplier)); + } + private boolean isValidHit() { if(currentTarget == null || mc.player == null) return false; if(!movementCorrection.getValue()) return true; @@ -295,12 +535,7 @@ private boolean canHitTarget(Entity target) { if(distance > reach.getValue()) return false; Vec3d direction = targetPos.subtract(playerEyes).normalize(); - - float currentYaw = targetYaw; - float currentPitch = targetPitch; - - Vec3d lookDirection = getLookDirection(currentYaw, currentPitch); - + Vec3d lookDirection = getLookDirection(targetYaw, targetPitch); double dotProduct = direction.dotProduct(lookDirection); double angleRadians = Math.acos(MathHelper.clamp(dotProduct, -1.0, 1.0)); double angleDegrees = Math.toDegrees(angleRadians); @@ -316,7 +551,6 @@ private boolean canHitTarget(Entity target) { if(playerSpeed > 0.3) { Vec3d movementDirection = new Vec3d(playerVelocity.x, 0, playerVelocity.z).normalize(); Vec3d toTarget = new Vec3d(direction.x, 0, direction.z).normalize(); - double movementDot = movementDirection.dotProduct(toTarget); double movementAngle = Math.toDegrees(Math.acos(MathHelper.clamp(movementDot, -1.0, 1.0))); @@ -341,7 +575,12 @@ private Vec3d getLookDirection(float yaw, float pitch) { } private Vec3d getTargetPosition(Entity target) { - return target.getBoundingBox().getCenter(); + Box box = target.getBoundingBox(); + return new Vec3d( + box.minX + (box.maxX - box.minX) * 0.5, + box.minY + (box.maxY - box.minY) * 0.3, + box.minZ + (box.maxZ - box.minZ) * 0.5 + ); } private boolean canAttack() { @@ -351,25 +590,29 @@ private boolean canAttack() { return false; } + if(criticals.getValue() && mc.player.isOnGround() && !mc.player.isInLava() && !mc.player.isSubmergedInWater()) { + return false; + } + if(noHitDelay.getValue()) return true; AttackDelayModule attackDelayModule = AlyaClient.INSTANCE.getModuleRepository().getModule(AttackDelayModule.class); if(attackDelayModule != null && attackDelayModule.isEnabled()) { if(attackDelayModule.isNewPvpDelay()) { - return mc.player.getAttackCooldownProgress(0.0f) >= 1.0f; + return mc.player.getAttackCooldownProgress(0.0f) >= 0.9f; } } if(cps.isVisible()) { long currentTime = System.currentTimeMillis(); - long baseDelay = 1000L / cps.getValue(); + long baseDelay = 1000L / currentCps; double randomFactor = 0.8 + (random.nextDouble() * 0.4); long actualDelay = Math.round(baseDelay * randomFactor); - actualDelay = Math.max(actualDelay, 50L); + actualDelay = Math.max(actualDelay, 25L); return currentTime - lastAttackTime >= actualDelay; } else { - return mc.player.getAttackCooldownProgress(0.0f) >= 1.0f; + return mc.player.getAttackCooldownProgress(0.0f) >= 0.9f; } } @@ -377,22 +620,29 @@ private void performAttack() { if(mc.player == null || mc.interactionManager == null) return; AttackDelayModule attackDelayModule = AlyaClient.INSTANCE.getModuleRepository().getModule(AttackDelayModule.class); + if(attackDelayModule != null && attackDelayModule.isEnabled() && !attackDelayModule.canAttack()) { + return; + } - if(attackDelayModule != null && attackDelayModule.isEnabled()) { - if(!attackDelayModule.canAttack()) { - return; - } + wasBlocking = isBlocking; + if(isBlocking && smartBlock.getValue()) { + unblockForAttack(); } - boolean wasBlocking = isBlocking; - if(isBlocking) stopBlocking(); + if(criticals.getValue() && mc.player.isOnGround()) { + mc.player.jump(); + } if(attackMode.getValue().equals("Multi")) { for(Entity target : targets) { + if(target == null) continue; double distanceToTarget = mc.player.distanceTo(target); if(distanceToTarget <= swingDistance.getValue()) { mc.player.swingHand(Hand.MAIN_HAND); if(distanceToTarget <= reach.getValue()) { + if(!keepSprint.getValue()) { + mc.player.setSprinting(false); + } mc.interactionManager.attackEntity(mc.player, target); } } @@ -402,85 +652,110 @@ private void performAttack() { if(distanceToTarget <= swingDistance.getValue()) { mc.player.swingHand(Hand.MAIN_HAND); if(distanceToTarget <= reach.getValue()) { + if(!keepSprint.getValue()) { + mc.player.setSprinting(false); + } mc.interactionManager.attackEntity(mc.player, currentTarget); } } } - if(!noHitDelay.getValue()) { - lastAttackTime = System.currentTimeMillis(); - } - - if(wasBlocking && autoBlock.getValue()) { - new Thread(() -> { - try { - Thread.sleep(random.nextInt(50) + 25); - if(isEnabled() && autoBlock.getValue()) { - startBlocking(); - } - } catch(InterruptedException ignored) { - } - }).start(); - } - } - - private void attackEntity(Entity target) { - if(target == null || mc.interactionManager == null || mc.player == null) return; - - double distanceToTarget = mc.player.distanceTo(target); - - ChatUtility.sendDebug("Distance to target: " + distanceToTarget); - ChatUtility.sendDebug("Swing distance: " + swingDistance.getValue()); - ChatUtility.sendDebug("Reach distance: " + reach.getValue()); - - if(distanceToTarget <= swingDistance.getValue()) { - ChatUtility.sendDebug("Within swing distance - swinging hand"); - mc.player.swingHand(Hand.MAIN_HAND); + lastAttackTime = System.currentTimeMillis(); + attackCount++; - if(distanceToTarget <= reach.getValue()) { - ChatUtility.sendDebug("Within reach distance - attacking"); - mc.interactionManager.attackEntity(mc.player, target); - } else { - ChatUtility.sendDebug("Not within reach distance - only swinging"); - } - } else { - ChatUtility.sendDebug("Not within swing distance - no action"); + if(wasBlocking && autoBlock.getValue() && smartBlock.getValue()) { + scheduleReblock(); } } private void handleAutoBlock() { - if(mc.player == null) return; - if(!autoBlock.getValue() || targets.isEmpty()) { + if(mc.player == null || !autoBlock.getValue()) { if(isBlocking) stopBlocking(); return; } - if(!canBlock()) { + if(targets.isEmpty() || !canBlock()) { if(isBlocking) stopBlocking(); return; } - if(!isBlocking && mc.player.getAttackCooldownProgress(0.0f) >= 0.9f) { - startBlocking(); + if(smartBlock.getValue()) { + boolean shouldBlock = shouldAutoBlock(); + if(shouldBlock && !isBlocking) { + startBlocking(); + } else if(!shouldBlock && isBlocking) { + stopBlocking(); + } + } else { + if(!isBlocking) { + startBlocking(); + } } } + private boolean shouldAutoBlock() { + if(mc.player == null || currentTarget == null) return false; + + float cooldown = mc.player.getAttackCooldownProgress(0.0f); + if(cooldown < 0.8f) return true; + + double distance = mc.player.distanceTo(currentTarget); + if(distance > reach.getValue() + 1.0) return true; + + if(currentTarget instanceof LivingEntity living) { + return living.hurtTime <= 0; + } + + return false; + } + + private void unblockForAttack() { + if(!isBlocking) return; + + long currentTime = System.currentTimeMillis(); + if(currentTime - lastUnblockTime < unblockDelay.getValue()) return; + + stopBlocking(); + lastUnblockTime = currentTime; + } + + private void scheduleReblock() { + new Thread(() -> { + try { + Thread.sleep(blockDelay.getValue()); + if(isEnabled() && autoBlock.getValue() && !isBlocking) { + startBlocking(); + } + } catch(InterruptedException ignored) { + } + }).start(); + } + private boolean canBlock() { if(mc.player == null) return false; - if(mc.player.getMainHandStack().isEmpty()) return false; + ItemStack mainHand = mc.player.getMainHandStack(); + if(mainHand.isEmpty()) return false; - return mc.player.getMainHandStack().getItem().toString().toLowerCase().contains("sword"); + return mainHand.getItem() instanceof AxeItem || mainHand.getItem().getName().toString().toLowerCase().contains("sword"); } private void startBlocking() { if(isBlocking || mc.player == null) return; + long currentTime = System.currentTimeMillis(); + if(currentTime - lastBlockTime < blockDelay.getValue()) return; + switch(autoBlockMode.getValue()) { case "Vanilla" -> mc.options.useKey.setPressed(true); case "Packet" -> { ItemStack mainHand = mc.player.getMainHandStack(); if(!mainHand.isEmpty()) { - mc.player.networkHandler.sendPacket(new PlayerInteractItemC2SPacket(Hand.MAIN_HAND, 0, mc.player.getYaw(), mc.player.getPitch())); + mc.player.networkHandler.sendPacket(new PlayerInteractItemC2SPacket( + Hand.MAIN_HAND, + 0, + mc.player.getYaw(), + mc.player.getPitch() + )); } } case "Fake" -> { @@ -488,6 +763,7 @@ private void startBlocking() { } isBlocking = true; + lastBlockTime = currentTime; } private void stopBlocking() { @@ -500,36 +776,33 @@ private void stopBlocking() { BlockPos.ORIGIN, Direction.DOWN )); - default -> throw new IllegalStateException("Unexpected value: " + autoBlockMode.getValue()); + case "Fake" -> { + } } isBlocking = false; } - /** - * Get the current attack mode for Target Strafe integration - * @return Current attack mode - */ public String getAttackMode() { return attackMode.getValue(); } - /** - * Get the current target for Target Strafe integration - * @return Current target entity - */ public Entity getCurrentTarget() { return currentTarget; } - /** - * Check if the module has valid targets - * @return true if there are valid targets - */ public boolean hasTargets() { return !targets.isEmpty(); } + public List getTargets() { + return new ArrayList<>(targets); + } + + public int getAttackCount() { + return attackCount; + } + @Override protected void onEnable() { if(mc.player != null) { @@ -538,8 +811,15 @@ protected void onEnable() { } lastAttackTime = 0; + lastBlockTime = 0; + lastUnblockTime = 0; + lastSwitchTime = 0; switchTimer = 0; isBlocking = false; + attackCount = 0; + wasBlocking = false; + lastTargetPos = null; + currentCps = cps.getValue(); } @Override @@ -550,7 +830,11 @@ protected void onDisable() { targets.clear(); currentTarget = null; + lastTarget = null; switchTimer = 0; isBlocking = false; + attackCount = 0; + wasBlocking = false; + lastTargetPos = null; } } \ No newline at end of file diff --git a/src/client/java/works/alya/module/impl/movement/ScaffoldModule.java b/src/client/java/works/alya/module/impl/movement/ScaffoldModule.java index 3e76451..013088e 100644 --- a/src/client/java/works/alya/module/impl/movement/ScaffoldModule.java +++ b/src/client/java/works/alya/module/impl/movement/ScaffoldModule.java @@ -311,10 +311,11 @@ private void handleTower() { break; case "Verus": if(mc.player.isOnGround()) { - MoveUtility.setMotionY(-0.1f); + Vec3d position = mc.player.getPos(); + mc.player.setPosition(position.x, position.y - 0.1, position.z); mc.player.jump(); } else { - MoveUtility.setMotionY(1f); + MoveUtility.setMotionY(1.0f); } break; case "Vulcan": diff --git a/src/client/java/works/alya/module/impl/movement/flight/FlightModule.java b/src/client/java/works/alya/module/impl/movement/flight/FlightModule.java index a345401..d611440 100644 --- a/src/client/java/works/alya/module/impl/movement/flight/FlightModule.java +++ b/src/client/java/works/alya/module/impl/movement/flight/FlightModule.java @@ -18,6 +18,7 @@ import works.alya.module.Module; import works.alya.module.ModuleCategory; +import works.alya.module.impl.movement.flight.ncp.NCPFlight; import works.alya.module.impl.movement.flight.vanilla.CreativeFlight; import works.alya.module.impl.movement.flight.vanilla.NormalFlight; import works.alya.module.impl.movement.flight.verus.VerusDamageFly; @@ -35,7 +36,8 @@ public FlightModule() { new CreativeFlight(this), new VerusPacketFlight(this), new VerusDamageFly(this), - new VerusGlideFly(this) + new VerusGlideFly(this), + new NCPFlight(this) ); } diff --git a/src/client/java/works/alya/module/impl/movement/flight/ncp/NCPFlight.java b/src/client/java/works/alya/module/impl/movement/flight/ncp/NCPFlight.java new file mode 100644 index 0000000..800a9c0 --- /dev/null +++ b/src/client/java/works/alya/module/impl/movement/flight/ncp/NCPFlight.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) Alya Client 2024-2025. + * + * This file belongs to Alya Client, + * an open-source Fabric injection client. + * Rye GitHub: https://github.com/AlyaClient/alya-beta.git + * + * THIS PROJECT DOES NOT HAVE A WARRANTY. + * + * Alya (and subsequently, its files) are all licensed under the MIT License. + * Alya should have come with a copy of the MIT License. + * If it did not, you may obtain a copy here: + * MIT License: https://opensource.org/license/mit + * + */ + +package works.alya.module.impl.movement.flight.ncp; + +import net.minecraft.network.packet.s2c.play.MoveMinecartAlongTrackS2CPacket; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Vec3d; +import works.alya.event.IEventListener; +import works.alya.event.impl.MotionEvent; +import works.alya.module.Module; +import works.alya.module.SubModule; +import works.alya.utilities.misc.ChatUtility; +import works.alya.utilities.player.MoveUtility; + +public class NCPFlight extends SubModule { + static boolean messageSent = false; + static boolean clipped = false; + + public NCPFlight(Module parent) { + super("NCP", parent); + } + + private final IEventListener motionEvent = event -> { + if(mc.player == null) return; + if(mc.world == null) return; + if(!event.isPre()) return; + + Vec3d pos = mc.player.getPos(); + BlockPos blockAbovePlayer = new BlockPos((int)pos.x, (int)(pos.y + mc.player.getHeight() + 0.5), (int)pos.z); + boolean isBlockAbovePlayer = !mc.world.getBlockState(blockAbovePlayer).isAir(); + + if(true) { + if(!clipped) { + mc.player.setPosition(pos.x, pos.y + 0.1, pos.z); + MoveUtility.setMotionY(0.2); + clipped = true; + } + } else { + if(!messageSent) { + ChatUtility.sendWarning("Please be under a block before enabling this fly!"); + messageSent = true; + } + } + + if(clipped) { + MoveUtility.setSpeed(2.0f, true); + MoveUtility.setMotionY(-0.2); + } + }; + + @Override + public void reset() { + super.reset(); + + messageSent = false; + clipped = false; + } +} diff --git a/src/client/java/works/alya/module/impl/movement/speed/SpeedModule.java b/src/client/java/works/alya/module/impl/movement/speed/SpeedModule.java index 7decc5a..2bf3c3c 100644 --- a/src/client/java/works/alya/module/impl/movement/speed/SpeedModule.java +++ b/src/client/java/works/alya/module/impl/movement/speed/SpeedModule.java @@ -23,6 +23,7 @@ import works.alya.module.impl.movement.speed.normal.NormalSpeed; import works.alya.module.impl.movement.speed.spartan.SpartanSpeed; import works.alya.module.impl.movement.speed.verus.VerusSpeed; +import works.alya.module.impl.movement.speed.vulcan.VulcanSpeed; import works.alya.utilities.player.TimerUtility; public class SpeedModule extends Module { @@ -36,7 +37,8 @@ public SpeedModule() { new NCPSpeed(this), new VerusSpeed(this), new BlocksMCSpeed(this), - new SpartanSpeed(this) + new SpartanSpeed(this), + new VulcanSpeed(this) ); } diff --git a/src/client/java/works/alya/module/impl/movement/speed/vulcan/VulcanSpeed.java b/src/client/java/works/alya/module/impl/movement/speed/vulcan/VulcanSpeed.java new file mode 100644 index 0000000..156cfc7 --- /dev/null +++ b/src/client/java/works/alya/module/impl/movement/speed/vulcan/VulcanSpeed.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) Alya Client 2024-2025. + * + * This file belongs to Alya Client, + * an open-source Fabric injection client. + * Rye GitHub: https://github.com/AlyaClient/alya-beta.git + * + * THIS PROJECT DOES NOT HAVE A WARRANTY. + * + * Alya (and subsequently, its files) are all licensed under the MIT License. + * Alya should have come with a copy of the MIT License. + * If it did not, you may obtain a copy here: + * MIT License: https://opensource.org/license/mit + * + */ + +package works.alya.module.impl.movement.speed.vulcan; + +import works.alya.event.IEventListener; +import works.alya.event.impl.MotionEvent; +import works.alya.mixin.client.accessors.LivingEntityJumpAccessor; +import works.alya.module.Module; +import works.alya.module.SubModule; +import works.alya.utilities.player.MoveUtility; +import works.alya.utilities.player.TimerUtility; + +public class VulcanSpeed extends SubModule { + public VulcanSpeed(Module parent) { + super("Vulcan", parent); + } + + @SuppressWarnings("unused") + private final IEventListener motionEvent = event -> { + if(!event.isPre()) return; + if(mc.player == null) return; + + if(mc.options.jumpKey.isPressed()) { + MoveUtility.setSpeed(0.1f, false); + } + + if(MoveUtility.isMoving()) { + LivingEntityJumpAccessor livingEntityJumpAccessor = (LivingEntityJumpAccessor) mc.player; + livingEntityJumpAccessor.setJumpingCooldown(0); + + if(mc.player.isOnGround()) { + mc.player.jump(); + } + } + }; + + @Override + public void onDisable() { + TimerUtility.resetTimer(); + super.onDisable(); + } +} diff --git a/src/client/java/works/alya/module/impl/visual/ArraylistModule.java b/src/client/java/works/alya/module/impl/visual/ArraylistModule.java index 347ce96..f1134e8 100644 --- a/src/client/java/works/alya/module/impl/visual/ArraylistModule.java +++ b/src/client/java/works/alya/module/impl/visual/ArraylistModule.java @@ -87,7 +87,7 @@ private float getAnimationValue(Module module) { private int getWaveColor(int index, int totalModules) { float time = System.currentTimeMillis() / 1000.0f; float waveOffset = (float) index / Math.max(1, totalModules - 1); - float phase = time * 2.0f + waveOffset * 4.0f; + float phase = time * 4.0f + waveOffset * 6.0f; float factor = (float) (Math.sin(phase) + 1.0) / 2.0f; return Theme.getInterpolatedColors(factor); diff --git a/src/client/java/works/alya/module/impl/visual/HUDModule.java b/src/client/java/works/alya/module/impl/visual/HUDModule.java index a8df80e..b53fd95 100644 --- a/src/client/java/works/alya/module/impl/visual/HUDModule.java +++ b/src/client/java/works/alya/module/impl/visual/HUDModule.java @@ -16,7 +16,9 @@ package works.alya.module.impl.visual; +import org.jetbrains.annotations.NotNull; import works.alya.AlyaClient; +import works.alya.config.setting.impl.BooleanSetting; import works.alya.event.IEventListener; import works.alya.event.impl.Render2DEvent; import works.alya.module.Module; @@ -26,12 +28,28 @@ import works.alya.utilities.render.TextRendererUtility; import net.minecraft.util.Identifier; import net.minecraft.util.math.Vec3d; +import works.alya.utilities.render.Theme; public class HUDModule extends Module { + private final BooleanSetting showLogo = new BooleanSetting("Logo", "Show the Alya logo", false); + private final BooleanSetting showFPS = new BooleanSetting("FPS", "Show the FPS", true); + private final BooleanSetting showBPS = new BooleanSetting("BPS", "Show the BPS", true); + private final BooleanSetting showTime = new BooleanSetting("Time", "Show the time", false); + private final BooleanSetting showCords = new BooleanSetting("Cords", "Show the player's coordinates", true); public HUDModule() { super("HUD", "Shows Heads Up Display", ModuleCategory.VISUAL); + showFPS.setVisibilityCondition(() -> !showLogo.getValue()); + showBPS.setVisibilityCondition(() -> !showLogo.getValue()); + showTime.setVisibilityCondition(() -> !showLogo.getValue()); + + addSetting(showLogo); + addSetting(showFPS); + addSetting(showBPS); + addSetting(showTime); + addSetting(showCords); + this.setEnabled(true); } @@ -43,52 +61,79 @@ public HUDModule() { final int xPosition = 2; final int yPosition = 4; - RenderUtility.drawImage( - Identifier.of("alya", "images/alya_logo.png"), - xPosition, - yPosition, - xPosition + padding * 6, - yPosition + padding * 6, - xPosition + padding * 6, - yPosition + padding * 6, - event.getContext() - ); - - int lowerHudY = mc.getWindow().getScaledHeight() - 30; - Vec3d position = mc.player.getPos(); - String fpsText = String.format("FPS: %s", AlyaClient.getFps()); - String bpsText = String.format("BPS: %s", AlyaClient.getBps()); - String cordsText = String.format("XYZ: %.1f %.1f %.1f", position.x, position.y, position.z); - - TextRendererUtility.renderText( - event.getContext(), - fpsText, - ColorUtility.Colors.WHITE, - xPosition - 1, - lowerHudY, - false - ); - - lowerHudY += TextRendererUtility.getTextHeight() + 1; - - TextRendererUtility.renderText( - event.getContext(), - bpsText, - ColorUtility.Colors.WHITE, - xPosition - 1, - lowerHudY, - false - ); - - lowerHudY += TextRendererUtility.getTextHeight() + 1; - - TextRendererUtility.renderText( - event.getContext(), - cordsText, - ColorUtility.Colors.WHITE, - xPosition - 1, - lowerHudY, - false - ); + if(showLogo.getValue()) { + RenderUtility.drawImage( + Identifier.of("alya", "images/alya_logo.png"), + xPosition, + yPosition, + xPosition + padding * 6, + yPosition + padding * 6, + xPosition + padding * 6, + yPosition + padding * 6, + event.getContext() + ); + } else { + String hudText = getHudSuffix(); + String firstLetter = hudText.substring(0, 1); + String rest = hudText.substring(1); + + float time = System.currentTimeMillis() / 1000.0f; + float waveOffset = (float) 10 / Math.max(1, 3 - 1); + float phase = time + waveOffset * 4.0f; + float factor = (float) (Math.sin(phase) + 1.0) / 2.0f; + + int interpolatedColors = Theme.getInterpolatedColors(factor); + TextRendererUtility.renderDynamicText( + event.getContext(), + firstLetter, + interpolatedColors, + xPosition, + yPosition + 1, + false, + "sf_pro_rounded_bold", + 12 + ); + + TextRendererUtility.renderDynamicText( + event.getContext(), + rest, + ColorUtility.getColor(ColorUtility.Colors.WHITE), + xPosition + TextRendererUtility.getTextWidth(firstLetter) + padding - 5, + yPosition + 1, + false, + "sf_pro_rounded_regular", + 12 + ); + } + + if(showCords.getValue()) { + final int lowerHudY = mc.getWindow().getScaledHeight() - 10; + Vec3d position = mc.player.getPos(); + String cordsText = String.format("XYZ: %.1f %.1f %.1f", position.x, position.y, position.z); + + TextRendererUtility.renderText( + event.getContext(), + cordsText, + ColorUtility.Colors.WHITE, + xPosition - 1, + lowerHudY, + false + ); + } }; + + private @NotNull String getHudSuffix() { + String fpsText = String.format("§7[§r%s FPS§7]§r", AlyaClient.getFps()); + String bpsText = String.format("§7[§r%s BPS§7]§r", AlyaClient.getBps()); + String timeText = String.format("§7[§r%s§7]§r", AlyaClient.getTime()); + String hudText = AlyaClient.getName(); + + if(showFPS.getValue()) + hudText += " " + fpsText; + if(showBPS.getValue()) + hudText += " " + bpsText; + if(showTime.getValue()) + hudText += " " + timeText; + return hudText; + } } \ No newline at end of file diff --git a/src/client/java/works/alya/module/impl/visual/SpeedMonitorModule.java b/src/client/java/works/alya/module/impl/visual/SpeedMonitorModule.java index e0aa15c..7841a87 100644 --- a/src/client/java/works/alya/module/impl/visual/SpeedMonitorModule.java +++ b/src/client/java/works/alya/module/impl/visual/SpeedMonitorModule.java @@ -32,12 +32,13 @@ @SuppressWarnings("SameParameterValue") public class SpeedMonitorModule extends Module { - private final double[] bpsHistory = new double[1000]; + private final double[] bpsHistory = new double[200]; private int historyIndex = 0; private int historySize = 0; private final DragUtility dragUtility = new DragUtility(4, 60); private static final int WHITE_COLOR = ColorUtility.getColor(ColorUtility.Colors.WHITE); - private static final int MAX_HISTORY_SIZE = 1000; + private static final int MAX_HISTORY_SIZE = 200; + private static final int MAX_RENDER_POINTS = 100; public SpeedMonitorModule() { super("SpeedMonitor", "Speed Monitor", "Shows the average player speed and a graph", ModuleCategory.VISUAL); @@ -70,30 +71,23 @@ public SpeedMonitorModule() { String currentBps = AlyaClient.getBps(); double bpsValue = Double.parseDouble(currentBps); - int maxHistorySize = 1000; - bpsHistory[historyIndex] = bpsValue; - historyIndex = (historyIndex + 1) % maxHistorySize; - if(historySize < maxHistorySize) { - historySize++; - } + updateSpeedHistory(bpsValue); int contentX = xPosition + (padding - 1); int contentY = yPosition + (padding * 2) + 2; int graphY; int graphHeight = 50 - padding; int graphWidth = rectWidth - (padding * 2) + 2; - int cordsY = mc.getWindow().getScaledHeight() - 10; Vec3d position = mc.player.getPos(); - String cordsText = String.format("XYZ: %.1f %.1f %.1f", position.x, position.y, position.z); String averageBps = AlyaClient.getBps(); String averageText = String.format("Average: %.1f", Double.parseDouble(averageBps)); TextRendererUtility.renderDynamicText( event.getContext(), "Speed", - ColorUtility.Colors.WHITE, + 0xFFFFFFFF, contentX + padding, contentY, false, @@ -107,7 +101,7 @@ public SpeedMonitorModule() { TextRendererUtility.renderDynamicText( event.getContext(), averageText, - ColorUtility.Colors.WHITE, + ColorUtility.getColor(ColorUtility.Colors.WHITE), averageTextX, contentY, false, @@ -128,37 +122,55 @@ public SpeedMonitorModule() { 0x44000000 ); - if(historySize > 1) { - double maxBps = 100; + renderSpeedGraph(event, contentX, graphY, graphWidth, graphHeight); + }; + + private void updateSpeedHistory(double bpsValue) { + bpsHistory[historyIndex] = bpsValue; + historyIndex = (historyIndex + 1) % MAX_HISTORY_SIZE; + if(historySize < MAX_HISTORY_SIZE) { + historySize++; + } + } + + private void renderSpeedGraph(Render2DEvent event, int contentX, int graphY, int graphWidth, int graphHeight) { + if(historySize <= 1) return; - int graphWidthMinus1 = graphWidth - 1; - int historySizeMinus1 = historySize - 1; - int startIndex = (historyIndex - historySize + MAX_HISTORY_SIZE) % MAX_HISTORY_SIZE; + double maxBps = 100; - for(int i = 1; i < historySize; i++) { - int prevIndex = (startIndex + i - 1) % MAX_HISTORY_SIZE; - int currIndex = (startIndex + i) % MAX_HISTORY_SIZE; + int renderPoints = Math.min(historySize, MAX_RENDER_POINTS); + int stepSize = Math.max(1, historySize / renderPoints); + int graphWidthMinus1 = graphWidth - 1; + int renderPointsMinus1 = renderPoints - 1; + int startIndex = (historyIndex - historySize + MAX_HISTORY_SIZE) % MAX_HISTORY_SIZE; - double prevBps = bpsHistory[prevIndex]; - double currBps = bpsHistory[currIndex]; + for(int i = 1; i < renderPoints; i++) { + int prevDataIndex = (startIndex + (i - 1) * stepSize) % MAX_HISTORY_SIZE; + int currDataIndex = (startIndex + i * stepSize) % MAX_HISTORY_SIZE; - int x1 = contentX + ((i - 1) * graphWidthMinus1) / historySizeMinus1; - int y1 = graphY + graphHeight - (int) (Math.min(prevBps, maxBps) * graphHeight / maxBps); - int x2 = contentX + (i * graphWidthMinus1) / historySizeMinus1; - int y2 = graphY + graphHeight - (int) (Math.min(currBps, maxBps) * graphHeight / maxBps); + double prevBps = bpsHistory[prevDataIndex]; + double currBps = bpsHistory[currDataIndex]; - y1 = Math.max(y1, graphY); - y2 = Math.max(y2, graphY); + int x1 = contentX + ((i - 1) * graphWidthMinus1) / renderPointsMinus1; + int y1 = graphY + graphHeight - (int) (Math.min(prevBps, maxBps) * graphHeight / maxBps); + int x2 = contentX + (i * graphWidthMinus1) / renderPointsMinus1; + int y2 = graphY + graphHeight - (int) (Math.min(currBps, maxBps) * graphHeight / maxBps); + y1 = Math.max(y1, graphY); + y2 = Math.max(y2, graphY); + y1 = Math.min(y1, graphY + graphHeight); + y2 = Math.min(y2, graphY + graphHeight); + + if(x1 != x2 || y1 != y2) { RenderUtility.drawLine( event.getContext(), x1, y1, x2, y2, - 2f, + 1.5f, WHITE_COLOR ); } } - }; + } private void handleMouseInput(int x, int y, int width, int height) { if(mc.mouse == null) return; @@ -181,4 +193,12 @@ private void handleMouseInput(int x, int y, int width, int height) { } } } + + @Override + protected void onDisable() { + historyIndex = 0; + historySize = 0; + + super.onDisable(); + } } \ No newline at end of file diff --git a/src/client/java/works/alya/module/impl/visual/clickgui/ClickGUIModule.java b/src/client/java/works/alya/module/impl/visual/clickgui/ClickGUIModule.java index 0ee0334..452f6d3 100644 --- a/src/client/java/works/alya/module/impl/visual/clickgui/ClickGUIModule.java +++ b/src/client/java/works/alya/module/impl/visual/clickgui/ClickGUIModule.java @@ -19,14 +19,18 @@ import works.alya.AlyaClient; import works.alya.config.KeybindManager; import works.alya.config.setting.impl.BooleanSetting; +import works.alya.config.setting.impl.ModeSetting; import works.alya.module.Module; import works.alya.module.ModuleCategory; +import works.alya.module.impl.visual.clickgui.astolfo.AstolfoClickGUI; import works.alya.module.impl.visual.clickgui.dropdown.DropDownClickGUI; import net.minecraft.client.MinecraftClient; import org.lwjgl.glfw.GLFW; +@SuppressWarnings("FieldCanBeLocal") public class ClickGUIModule extends Module { + private final ModeSetting guiMode = new ModeSetting("GUI Mode", "GUI Mode", "Dropdown", "Dropdown", "Astolfo"); private final BooleanSetting showTooltips = new BooleanSetting("ShowTooltips", "Show tooltips when hovering over modules and settings", true); public ClickGUIModule() { @@ -35,12 +39,31 @@ public ClickGUIModule() { KeybindManager.getInstance().bind(this, GLFW.GLFW_KEY_RIGHT_SHIFT); AlyaClient.getEventBus().subscribe(this); + addSetting(guiMode); addSetting(showTooltips); + + // Set default value after settings are added + guiMode.setDefaultValue("Dropdown"); } @Override protected void onEnable() { - MinecraftClient.getInstance().setScreen(new DropDownClickGUI(showTooltips.getValue())); + String mode = ((ModeSetting) getSetting("GUI Mode")).getValue(); + + switch(mode) { + case "Dropdown": + MinecraftClient.getInstance().setScreen(new DropDownClickGUI(showTooltips.getValue())); + break; + case "Astolfo": + MinecraftClient.getInstance().setScreen(new AstolfoClickGUI()); + break; + } + + super.onEnable(); super.setEnabled(false); } -} \ No newline at end of file + + public ModeSetting getMode() { + return mode; + } +} diff --git a/src/client/java/works/alya/module/impl/visual/clickgui/astolfo/AstolfoClickGUI.java b/src/client/java/works/alya/module/impl/visual/clickgui/astolfo/AstolfoClickGUI.java new file mode 100644 index 0000000..43118bc --- /dev/null +++ b/src/client/java/works/alya/module/impl/visual/clickgui/astolfo/AstolfoClickGUI.java @@ -0,0 +1,404 @@ +/* + * Copyright (c) Alya Client 2024-2025. + * + * This file belongs to Alya Client, + * an open-source Fabric injection client. + * Rye GitHub: https://github.com/AlyaClient/alya-beta.git + * + * THIS PROJECT DOES NOT HAVE A WARRANTY. + * + * Alya (and subsequently, its files) are all licensed under the MIT License. + * Alya should have come with a copy of the MIT License. + * If it did not, you may obtain a copy here: + * MIT License: https://opensource.org/license/mit + * + */ + +package works.alya.module.impl.visual.clickgui.astolfo; + +import works.alya.config.setting.impl.BooleanSetting; +import works.alya.config.setting.impl.ModeSetting; +import works.alya.config.setting.impl.NumberSetting; +import works.alya.config.setting.Setting; +import works.alya.module.Module; +import works.alya.module.ModuleCategory; +import works.alya.module.ModuleRepository; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.text.Text; +import works.alya.utilities.render.*; + +import java.util.ArrayList; +import java.util.EnumMap; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * This ClickGUI was inspired by Astolfo Clients ClickGUI. + * The file is based on/adapted from + * Astolfo-Like-ClickGUI + * and modified to work inside FabricMC + */ +@SuppressWarnings({"rawtypes", "MismatchedQueryAndUpdateOfCollection"}) +public class AstolfoClickGUI extends Screen { + private final Map> categorizedModules = new EnumMap<>(ModuleCategory.class); + private final Map expandedCategories = new EnumMap<>(ModuleCategory.class); + private final Map expandedModules = new HashMap<>(); + private final Map categoryDragUtils = new EnumMap<>(ModuleCategory.class); + private final Map, Integer> numberSettingPositions = new HashMap<>(); + + private static final int PANEL_WIDTH = 100; + private static final int PANEL_HEIGHT = 18; + private static final int MODULE_HEIGHT = 18; + private static final int SETTING_HEIGHT = 9; + private static final int PANEL_SPACING = 120; + private static final int BACKGROUND_COLOR = 0xFF181A17; + private static final int MODULE_BACKGROUND_COLOR = 0xFF232623; + private static final int TEXT_COLOR = 0xFFFFFFFF; + private static final int BORDER_WIDTH = 2; + private static final int DEFAULT_CATEGORY_COLOR = 0xFF666666; + + private static final Map CATEGORY_COLORS = new EnumMap<>(ModuleCategory.class); + + static { + CATEGORY_COLORS.put(ModuleCategory.COMBAT, 0xFFE64D3A); + CATEGORY_COLORS.put(ModuleCategory.MOVEMENT, 0xFF2ECD6F); + CATEGORY_COLORS.put(ModuleCategory.VISUAL, 0xFF8F2DF7); + CATEGORY_COLORS.put(ModuleCategory.WORLD, 0xFF3A9DE6); + CATEGORY_COLORS.put(ModuleCategory.UTILITY, 0xFFF29D11); + CATEGORY_COLORS.put(ModuleCategory.SCRIPTS, 0xFF230057); + } + + private boolean dragging = false; + private int dragOffsetX = 0; + private int dragOffsetY = 0; + private ModuleCategory draggingCategory = null; + private NumberSetting currentDraggedNumberSetting = null; + private int currentDraggedSettingX = 0; + + public AstolfoClickGUI() { + super(Text.literal("Click GUI")); + initializeModules(); + + int x = 4; + for(ModuleCategory category : ModuleCategory.values()) { + int finalX = x; + categoryDragUtils.computeIfAbsent(category, k -> new DragUtility(finalX, 4)); + x += PANEL_SPACING; + } + } + + private void initializeModules() { + categorizedModules.clear(); + expandedCategories.clear(); + + for(ModuleCategory category : ModuleCategory.values()) { + categorizedModules.put(category, new ArrayList<>()); + expandedCategories.put(category, true); + } + + ModuleRepository repository = works.alya.AlyaClient.INSTANCE.getModuleRepository(); + for(Module module : repository.getModules()) { + List categoryModules = categorizedModules.get(module.getCategory()); + if(categoryModules != null) { + categoryModules.add(module); + } + } + } + + @Override + public void render(DrawContext context, int mouseX, int mouseY, float delta) { + context.fill(0, 0, width, height, 0x80000000); + + for(ModuleCategory category : ModuleCategory.values()) { + renderCategoryPanel(context, category, mouseX, mouseY); + } + + super.render(context, mouseX, mouseY, delta); + } + + private void renderCategoryPanel(DrawContext context, ModuleCategory category, int mouseX, int mouseY) { + DragUtility dragUtil = categoryDragUtils.get(category); + if(dragUtil == null) return; + + int panelX = dragUtil.getX(); + int panelY = dragUtil.getY(); + + if(dragging && draggingCategory == category) { + panelX = mouseX + dragOffsetX; + panelY = mouseY + dragOffsetY; + dragUtil.setX(panelX); + dragUtil.setY(panelY); + } + + List modules = categorizedModules.get(category); + if(modules == null) return; + + int totalHeight = PANEL_HEIGHT; + + if(expandedCategories.get(category)) { + for(Module module : modules) { + totalHeight += MODULE_HEIGHT; + if(expandedModules.getOrDefault(module, false)) { + for(Setting setting : module.getSettings()) { + if(setting.isVisible()) { + totalHeight += SETTING_HEIGHT; + } + } + } + } + } + + RenderUtility.drawRect(context, panelX, panelY, PANEL_WIDTH, PANEL_HEIGHT, BACKGROUND_COLOR); + + String categoryName = category.name().toLowerCase(); + TextRendererUtility.renderText(context, categoryName, TEXT_COLOR, panelX + 4, panelY + 5, false); + + if(expandedCategories.get(category)) { + int currentY = panelY + PANEL_HEIGHT; + + for(Module module : modules) { + renderModuleButton(context, module, panelX, currentY, category); + currentY += MODULE_HEIGHT; + + if(expandedModules.getOrDefault(module, false)) { + for(Setting setting : module.getSettings()) { + if(setting.isVisible()) { + renderSettingButton(context, setting, panelX, currentY, category); + currentY += SETTING_HEIGHT; + } + } + } + } + } + + int categoryColor = getCategoryColor(category); + RenderUtility.drawRoundedRectOutline(context, panelX, panelY, PANEL_WIDTH, totalHeight, 0, BORDER_WIDTH, categoryColor); + + RenderUtility.drawRect(context, panelX, panelY + totalHeight, PANEL_WIDTH, 2, BACKGROUND_COLOR); + } + + private void renderModuleButton(DrawContext context, Module module, int x, int y, ModuleCategory category) { + RenderUtility.drawRect(context, x, y, PANEL_WIDTH, MODULE_HEIGHT, BACKGROUND_COLOR); + + boolean extended = expandedModules.getOrDefault(module, false); + if(!extended) { + int bgColor = module.isEnabled() ? getCategoryColor(category) : MODULE_BACKGROUND_COLOR; + RenderUtility.drawRect(context, x + 2, y, PANEL_WIDTH - 4, MODULE_HEIGHT, bgColor); + } else { + RenderUtility.drawRect(context, x + 2, y, PANEL_WIDTH - 4, MODULE_HEIGHT, BACKGROUND_COLOR); + } + + String moduleName = module.getName().toLowerCase(); + int textColor = extended ? (module.isEnabled() ? getCategoryColor(category) : TEXT_COLOR) : TEXT_COLOR; + int textX = x + PANEL_WIDTH - TextRendererUtility.getTextWidth(moduleName) - 3; + TextRendererUtility.renderText(context, moduleName, textColor, textX, y + 5, false); + } + + private void renderSettingButton(DrawContext context, Setting setting, int x, int y, ModuleCategory category) { + RenderUtility.drawRect(context, x, y, PANEL_WIDTH, SETTING_HEIGHT, BACKGROUND_COLOR); + + if(setting instanceof BooleanSetting boolSetting) { + if(boolSetting.getValue()) { + RenderUtility.drawRect(context, x + 3, y, PANEL_WIDTH - 6, SETTING_HEIGHT, getCategoryColor(category)); + } + TextRendererUtility.renderDynamicText( + context, + setting.getName(), + TEXT_COLOR, + x + 4, + y, + false, + "sf_pro_rounded_regular", + 8 + ); + + } else if(setting instanceof ModeSetting modeSetting) { + String text = setting.getName() + " > " + modeSetting.getValue(); + TextRendererUtility.renderDynamicText( + context, + text, + TEXT_COLOR, + x + 4, + y + 1, + false, + "sf_pro_rounded_regular", + 8 + ); + + } else if(setting instanceof NumberSetting numberSetting) { + numberSettingPositions.put(numberSetting, x); + + RenderUtility.drawRect(context, x, y, PANEL_WIDTH, SETTING_HEIGHT, BACKGROUND_COLOR); + + double value = numberSetting.getValue().doubleValue(); + double min = numberSetting.getMinValue().doubleValue(); + double max = numberSetting.getMaxValue().doubleValue(); + double percentage = (value - min) / (max - min); + + int fillWidth = (int) (percentage * (PANEL_WIDTH - 6)); + RenderUtility.drawRect(context, x + 3, y, fillWidth, SETTING_HEIGHT, getCategoryColor(category)); + + String text = setting.getName() + ": " + Math.round(value * 100.0) / 100.0; + TextRendererUtility.renderDynamicText( + context, + text, + TEXT_COLOR, + x + 4, + y, + false, + "sf_pro_rounded_regular", + 8 + ); + } + } + + private int getCategoryColor(ModuleCategory category) { + return CATEGORY_COLORS.getOrDefault(category, DEFAULT_CATEGORY_COLOR); + } + + @Override + public boolean mouseClicked(double mouseX, double mouseY, int button) { + int intMouseX = (int) mouseX; + int intMouseY = (int) mouseY; + + for(ModuleCategory category : ModuleCategory.values()) { + DragUtility dragUtil = categoryDragUtils.get(category); + if(dragUtil == null) continue; + + int panelX = dragUtil.getX(); + int panelY = dragUtil.getY(); + + if(isMouseOver(intMouseX, intMouseY, panelX, panelY, PANEL_HEIGHT)) { + if(button == 0) { + dragging = true; + draggingCategory = category; + dragOffsetX = panelX - intMouseX; + dragOffsetY = panelY - intMouseY; + } else if(button == 1) { + expandedCategories.put(category, !expandedCategories.get(category)); + } + return true; + } + + if(expandedCategories.get(category)) { + List modules = categorizedModules.get(category); + if(modules != null) { + int currentY = panelY + PANEL_HEIGHT; + + for(Module module : modules) { + if(isMouseOver(intMouseX, intMouseY, panelX, currentY, MODULE_HEIGHT)) { + if(button == 0) { + module.toggle(); + } else if(button == 1 && !module.getSettings().isEmpty()) { + expandedModules.put(module, !expandedModules.getOrDefault(module, false)); + } + return true; + } + currentY += MODULE_HEIGHT; + + if(expandedModules.getOrDefault(module, false)) { + for(Setting setting : module.getSettings()) { + if(setting.isVisible()) { + if(isMouseOver(intMouseX, intMouseY, panelX, currentY, SETTING_HEIGHT)) { + if(setting instanceof NumberSetting numberSetting && button == 0) { + currentDraggedNumberSetting = numberSetting; + currentDraggedSettingX = panelX; + updateNumberSettingFromMouse(intMouseX); + } else { + handleSettingClick(setting, button); + } + return true; + } + currentY += SETTING_HEIGHT; + } + } + } + } + } + } + } + + return super.mouseClicked(mouseX, mouseY, button); + } + + private void handleSettingClick(Setting setting, int button) { + if(setting instanceof BooleanSetting boolSetting) { + boolSetting.setValue(!boolSetting.getValue()); + } else if(setting instanceof ModeSetting modeSetting) { + if(button == 0) { + modeSetting.cycle(); + } else if(button == 1) { + String currentValue = modeSetting.getValue(); + List modes = modeSetting.getModes(); + int currentIndex = modes.indexOf(currentValue); + int previousIndex = (currentIndex - 1 + modes.size()) % modes.size(); + modeSetting.setValue(modes.get(previousIndex)); + } + } + } + + @Override + public boolean mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY) { + if(dragging && draggingCategory != null) { + return true; + } + + if(currentDraggedNumberSetting != null) { + updateNumberSettingFromMouse((int) mouseX); + return true; + } + + return super.mouseDragged(mouseX, mouseY, button, deltaX, deltaY); + } + + @Override + public boolean mouseReleased(double mouseX, double mouseY, int button) { + dragging = false; + draggingCategory = null; + currentDraggedNumberSetting = null; + + return super.mouseReleased(mouseX, mouseY, button); + } + + @SuppressWarnings("unchecked") + private void updateNumberSettingFromMouse(int mouseX) { + if(currentDraggedNumberSetting == null) return; + + int settingX = currentDraggedSettingX; + int sliderStart = settingX + 3; + int sliderWidth = PANEL_WIDTH - 6; + + int relativeX = mouseX - sliderStart; + relativeX = Math.max(0, Math.min(relativeX, sliderWidth)); + + double percentage = (double) relativeX / sliderWidth; + + double min = currentDraggedNumberSetting.getMinValue().doubleValue(); + double max = currentDraggedNumberSetting.getMaxValue().doubleValue(); + double newValue = min + (percentage * (max - min)); + + Object currentValue = currentDraggedNumberSetting.getValue(); + var setting = (NumberSetting) currentDraggedNumberSetting; + if(currentValue instanceof Integer) { + setting.setValue((int) Math.round(newValue)); + } else if(currentValue instanceof Float) { + setting.setValue((float) newValue); + } else if(currentValue instanceof Double) { + setting.setValue(newValue); + } else if(currentValue instanceof Long) { + setting.setValue(Math.round(newValue)); + } + } + + private boolean isMouseOver(int mouseX, int mouseY, int x, int y, int height) { + return mouseX >= x && mouseX <= x + AstolfoClickGUI.PANEL_WIDTH && mouseY >= y && mouseY <= y + height; + } + + @Override + public boolean shouldPause() { + return false; + } + +} \ No newline at end of file diff --git a/src/client/java/works/alya/module/impl/visual/clickgui/dropdown/DropDownClickGUI.java b/src/client/java/works/alya/module/impl/visual/clickgui/dropdown/DropDownClickGUI.java index 270d3ef..338b58c 100644 --- a/src/client/java/works/alya/module/impl/visual/clickgui/dropdown/DropDownClickGUI.java +++ b/src/client/java/works/alya/module/impl/visual/clickgui/dropdown/DropDownClickGUI.java @@ -1,3 +1,4 @@ + /* * Copyright (c) Alya Client 2024-2025. * @@ -45,7 +46,6 @@ public class DropDownClickGUI extends Screen { private final Map categoryDragUtils = new EnumMap<>(ModuleCategory.class); private static final int SETTING_HEIGHT = 22; - private static final int SETTING_INDENT = 2; private static final int CATEGORY_HEIGHT = 18; private static final int MODULE_HEIGHT = 18; private static final int PANEL_WIDTH = 110; @@ -54,7 +54,7 @@ public class DropDownClickGUI extends Screen { private static final int PANEL_SPACING = 8; private static final int PADDING = 5; private static final int BACKGROUND_COLOR = 0xFF222222; - private static final int SETTING_BACKGROUND_COLOR = 0xFF121212;; + private static final int SETTING_BACKGROUND_COLOR = 0xFF121212; private static final int HOVER_COLOR = 0x30FFFFFF; private static final float CORNER_RADIUS = 6f; private static final int TOOLTIP_BACKGROUND = 0xFF000000; @@ -282,7 +282,7 @@ public void render(DrawContext context, int mouseX, int mouseY, float delta) { RenderUtility.drawRoundedRect(context, categoryX + 1, y, PANEL_WIDTH - 2, SETTING_HEIGHT, hoverRadius, HOVER_COLOR); } - ColorUtility.Colors settingTextColor = ColorUtility.Colors.LIGHT_GRAY; + int settingTextColor = ColorUtility.getColor(ColorUtility.Colors.LIGHT_GRAY); TextRendererUtility.renderDynamicText( context, @@ -326,7 +326,7 @@ public void render(DrawContext context, int mouseX, int mouseY, float delta) { TextRendererUtility.renderDynamicText( context, currentMode, - ColorUtility.Colors.LIGHT_GRAY, + ColorUtility.getColor(ColorUtility.Colors.LIGHT_GRAY), dropdownX + 2, dropdownY + 1, false, @@ -368,7 +368,7 @@ public void render(DrawContext context, int mouseX, int mouseY, float delta) { TextRendererUtility.renderDynamicText( context, valueText, - ColorUtility.Colors.LIGHT_GRAY, + ColorUtility.getColor(ColorUtility.Colors.LIGHT_GRAY), sliderX + sliderWidth / 2 - TextRendererUtility.getTextWidth(valueText) / 2, y - 2, false, @@ -766,25 +766,12 @@ private void handleRightClick(int mouseX, int mouseY) { for(Setting setting : module.getSettings()) { if(!setting.isVisible()) continue; - int controlWidth = PANEL_WIDTH - TextRendererUtility.getTextWidth(setting.getName() + ": ") - PADDING * 6 - SETTING_INDENT; - int controlX = categoryX + PANEL_WIDTH - controlWidth - PADDING * 3; - int controlY = y + PADDING + 2; - int controlHeight = 14; - if(isMouseOver(mouseX, mouseY, categoryX, y, PANEL_WIDTH, SETTING_HEIGHT)) { switch(setting) { - case BooleanSetting booleanSetting -> { - booleanSetting.toggle(); - } - case ModeSetting modeSetting -> { - modeSetting.cycle(); - } - case NumberSetting numberSetting -> { - numberSetting.decrement(false); - } - case MultipleBooleanSetting multipleBooleanSetting -> { - multipleBooleanSetting.toggleExpanded(); - } + case BooleanSetting booleanSetting -> booleanSetting.toggle(); + case ModeSetting modeSetting -> modeSetting.cycle(); + case NumberSetting numberSetting -> numberSetting.decrement(false); + case MultipleBooleanSetting multipleBooleanSetting -> multipleBooleanSetting.toggleExpanded(); default -> { } } @@ -805,10 +792,6 @@ private boolean isMouseOver(int mouseX, int mouseY, int x, int y, int width, int mouseY >= y && mouseY <= y + height; } - private void renderGradientRoundedRect(DrawContext context, int x, int y, int width, int height, int color1, int color2, Vector4f radius) { - RenderUtility.drawGradientRoundedRect(context, x, y, width, height, radius, color1, color2); - } - @Override public boolean shouldPause() { return false; diff --git a/src/client/java/works/alya/script/ScriptManager.java b/src/client/java/works/alya/script/ScriptManager.java deleted file mode 100644 index e69de29..0000000 diff --git a/src/client/java/works/alya/script/api/LuaAPI.java b/src/client/java/works/alya/script/api/LuaAPI.java new file mode 100644 index 0000000..bb620b0 --- /dev/null +++ b/src/client/java/works/alya/script/api/LuaAPI.java @@ -0,0 +1,1148 @@ +/* + * Copyright (c) Alya Client 2024-2025. + * + * This file belongs to Alya Client, + * an open-source Fabric injection client. + * Rye GitHub: https://github.com/AlyaClient/alya-beta.git + * + * THIS PROJECT DOES NOT HAVE A WARRANTY. + * + * Alya (and subsequently, its files) are all licensed under the MIT License. + * Alya should have come with a copy of the MIT License. + * If it did not, you may obtain a copy here: + * MIT License: https://opensource.org/license/mit + * + */ + +package works.alya.script.api; + +import org.luaj.vm2.*; +import org.luaj.vm2.lib.TwoArgFunction; +import works.alya.AlyaClient; +import works.alya.module.Module; +import works.alya.module.ModuleRepository; +import works.alya.script.core.Script; +import works.alya.script.integration.ScriptModule; +import works.alya.script.integration.ScriptRenderQueue; +import works.alya.utilities.misc.ChatUtility; +import works.alya.utilities.player.MoveUtility; +import works.alya.utilities.render.ColorUtility; +import net.minecraft.client.MinecraftClient; +import net.minecraft.entity.Entity; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.entity.LivingEntity; +import net.minecraft.util.math.Vec3d; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.MathHelper; +import net.minecraft.util.math.Box; +import net.minecraft.block.BlockState; +import net.minecraft.item.ItemStack; +import net.minecraft.util.hit.HitResult; +import net.minecraft.util.Hand; +import net.minecraft.network.packet.c2s.play.PlayerMoveC2SPacket; +import net.minecraft.network.packet.c2s.play.HandSwingC2SPacket; +import net.minecraft.network.packet.c2s.play.UpdateSelectedSlotC2SPacket; +import net.minecraft.sound.SoundEvent; +import net.minecraft.sound.SoundCategory; +import net.minecraft.registry.Registries; +import net.minecraft.util.Identifier; +import org.luaj.vm2.lib.OneArgFunction; +import org.luaj.vm2.lib.VarArgFunction; +import org.luaj.vm2.lib.ZeroArgFunction; +import org.luaj.vm2.lib.jse.CoerceJavaToLua; +import org.luaj.vm2.lib.ThreeArgFunction; + +import java.awt.Color; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; +import java.util.Collection; + +public class LuaAPI { + private final MinecraftClient mc = MinecraftClient.getInstance(); + private Script currentScript; + + public void register(Globals globals, Script script) { + currentScript = script; + + LuaTable alyaTable = new LuaTable(); + globals.set("alya", alyaTable); + + registerMinecraftAPI(globals); + registerAlyaAPI(alyaTable); + registerRenderAPI(globals); + registerUtilityAPI(globals); + registerWorldAPI(globals); + registerPlayerAPI(globals); + registerInventoryAPI(globals); + registerMathAPI(globals); + registerNetworkAPI(globals); + registerSoundAPI(globals); + registerEntityAPI(globals); + + AlyaClient.getEventBus().subscribe(this); + } + + public void updateCurrentScript(Script script) { + currentScript = script; + } + + private boolean isScriptEnabled() { + if(currentScript == null) return false; + + for(Module module : AlyaClient.INSTANCE.getModuleRepository().getModules()) { + if(module instanceof ScriptModule scriptModule) { + if(scriptModule.getScript() == currentScript) { + return module.isEnabled(); + } + } + } + + return false; + } + + private void registerMinecraftAPI(Globals globals) { + LuaTable mcTable = new LuaTable(); + globals.set("mc", mcTable); + + mcTable.set("getPlayer", new ZeroArgFunction() { + @Override + public LuaValue call() { + return mc.player != null ? CoerceJavaToLua.coerce(mc.player) : LuaValue.NIL; + } + }); + + mcTable.set("getPlayerPosition", new ZeroArgFunction() { + @Override + public LuaValue call() { + if(mc.player == null) return LuaValue.NIL; + Vec3d pos = mc.player.getPos(); + LuaTable posTable = new LuaTable(); + posTable.set("x", pos.x); + posTable.set("y", pos.y); + posTable.set("z", pos.z); + return posTable; + } + }); + + mcTable.set("getPlayerSpeed", new ZeroArgFunction() { + @Override + public LuaValue call() { + return LuaValue.valueOf(Double.parseDouble(AlyaClient.getBps())); + } + }); + + mcTable.set("getWorld", new ZeroArgFunction() { + @Override + public LuaValue call() { + return mc.world != null ? CoerceJavaToLua.coerce(mc.world) : LuaValue.NIL; + } + }); + + mcTable.set("getEntities", new ZeroArgFunction() { + @Override + public LuaValue call() { + if(mc.world == null) return LuaValue.NIL; + List entities = new ArrayList<>(); + for(Entity entity : mc.world.getEntities()) { + entities.add(entity); + } + return CoerceJavaToLua.coerce(entities); + } + }); + + mcTable.set("getPlayers", new ZeroArgFunction() { + @Override + public LuaValue call() { + if(mc.world == null) return LuaValue.NIL; + List players = new ArrayList<>(); + for(Entity entity : mc.world.getEntities()) { + if(entity instanceof PlayerEntity) { + players.add((PlayerEntity) entity); + } + } + return CoerceJavaToLua.coerce(players); + } + }); + + mcTable.set("getFPS", new ZeroArgFunction() { + @Override + public LuaValue call() { + return LuaValue.valueOf(mc.getCurrentFps()); + } + }); + + mcTable.set("getGameTime", new ZeroArgFunction() { + @Override + public LuaValue call() { + return mc.world != null ? LuaValue.valueOf(mc.world.getTime()) : LuaValue.valueOf(0); + } + }); + + mcTable.set("getDayTime", new ZeroArgFunction() { + @Override + public LuaValue call() { + return mc.world != null ? LuaValue.valueOf(mc.world.getTimeOfDay()) : LuaValue.valueOf(0); + } + }); + + mcTable.set("isInGame", new ZeroArgFunction() { + @Override + public LuaValue call() { + return LuaValue.valueOf(mc.player != null && mc.world != null); + } + }); + + mcTable.set("getMouseX", new ZeroArgFunction() { + @Override + public LuaValue call() { + return LuaValue.valueOf(mc.mouse.getX()); + } + }); + + mcTable.set("getMouseY", new ZeroArgFunction() { + @Override + public LuaValue call() { + return LuaValue.valueOf(mc.mouse.getY()); + } + }); + + mcTable.set("getScreenWidth", new ZeroArgFunction() { + @Override + public LuaValue call() { + return LuaValue.valueOf(mc.getWindow().getScaledWidth()); + } + }); + + mcTable.set("getScreenHeight", new ZeroArgFunction() { + @Override + public LuaValue call() { + return LuaValue.valueOf(mc.getWindow().getScaledHeight()); + } + }); + } + + private void registerAlyaAPI(LuaTable alyaTable) { + alyaTable.set("getModule", new OneArgFunction() { + @Override + public LuaValue call(LuaValue arg) { + String moduleName = arg.checkjstring(); + Module module = ModuleRepository.getInstance().getModuleByName(moduleName); + return module != null ? CoerceJavaToLua.coerce(module) : LuaValue.NIL; + } + }); + + alyaTable.set("isModuleEnabled", new OneArgFunction() { + @Override + public LuaValue call(LuaValue arg) { + String moduleName = arg.checkjstring(); + Module module = ModuleRepository.getInstance().getModuleByName(moduleName); + return LuaValue.valueOf(module != null && module.isEnabled()); + } + }); + + alyaTable.set("enableModule", new OneArgFunction() { + @Override + public LuaValue call(LuaValue arg) { + String moduleName = arg.checkjstring(); + Module module = ModuleRepository.getInstance().getModuleByName(moduleName); + if(module != null && !module.isEnabled()) { + module.setEnabled(true); + return LuaValue.TRUE; + } + return LuaValue.FALSE; + } + }); + + alyaTable.set("disableModule", new OneArgFunction() { + @Override + public LuaValue call(LuaValue arg) { + String moduleName = arg.checkjstring(); + Module module = ModuleRepository.getInstance().getModuleByName(moduleName); + if(module != null && module.isEnabled()) { + module.setEnabled(false); + return LuaValue.TRUE; + } + return LuaValue.FALSE; + } + }); + + alyaTable.set("toggleModule", new OneArgFunction() { + @Override + public LuaValue call(LuaValue arg) { + String moduleName = arg.checkjstring(); + Module module = ModuleRepository.getInstance().getModuleByName(moduleName); + if(module != null) { + module.toggle(); + return LuaValue.valueOf(module.isEnabled()); + } + return LuaValue.FALSE; + } + }); + + alyaTable.set("getModules", new ZeroArgFunction() { + @Override + public LuaValue call() { + Collection modules = ModuleRepository.getInstance().getModules(); + List moduleList = new ArrayList<>(modules); + return CoerceJavaToLua.coerce(moduleList); + } + }); + + alyaTable.set("getEnabledModules", new ZeroArgFunction() { + @Override + public LuaValue call() { + Collection modules = ModuleRepository.getInstance().getModules(); + List enabled = modules.stream() + .filter(Module::isEnabled) + .collect(Collectors.toList()); + return CoerceJavaToLua.coerce(enabled); + } + }); + } + + private void registerRenderAPI(Globals globals) { + LuaTable renderTable = new LuaTable(); + globals.set("render", renderTable); + + renderTable.set("color", new VarArgFunction() { + @Override + public Varargs invoke(Varargs args) { + if(args.narg() == 1) { + String colorName = args.checkjstring(1); + try { + ColorUtility.Colors color = ColorUtility.Colors.valueOf(colorName.toUpperCase()); + return LuaValue.valueOf(ColorUtility.getColor(color)); + } catch(IllegalArgumentException e) { + ChatUtility.sendError("Invalid color name: " + colorName); + ChatUtility.sendScriptError(e); + return LuaValue.valueOf(0xFFFFFFFF); + } + } else if(args.narg() >= 3) { + int r = args.checkint(1); + int g = args.checkint(2); + int b = args.checkint(3); + int a = args.optint(4, 255); + Color color = new Color(r, g, b, a); + return LuaValue.valueOf(ColorUtility.getIntFromColor(color)); + } + return LuaValue.valueOf(0xFFFFFFFF); + } + }); + + renderTable.set("drawText", new VarArgFunction() { + @Override + public Varargs invoke(Varargs args) { + String text = args.checkjstring(1); + int x = args.checkint(2); + int y = args.checkint(3); + int color = args.optint(4, 0xFFFFFFFF); + boolean shadow = args.optboolean(5, false); + + ScriptRenderQueue.setCurrentScript(currentScript); + ScriptRenderQueue.addTextRenderCommand(text, x, y, color, shadow); + return LuaValue.NIL; + } + }); + + renderTable.set("drawRect", new VarArgFunction() { + @Override + public Varargs invoke(Varargs args) { + float x = (float) args.checkdouble(1); + float y = (float) args.checkdouble(2); + float width = (float) args.checkdouble(3); + float height = (float) args.checkdouble(4); + int color = args.optint(5, 0xFFFFFFFF); + + ScriptRenderQueue.setCurrentScript(currentScript); + ScriptRenderQueue.addRectRenderCommand(x, y, width, height, color); + return LuaValue.NIL; + } + }); + + renderTable.set("getStringWidth", new OneArgFunction() { + @Override + public LuaValue call(LuaValue arg) { + String text = arg.checkjstring(); + return LuaValue.valueOf(mc.textRenderer.getWidth(text)); + } + }); + + renderTable.set("getStringHeight", new ZeroArgFunction() { + @Override + public LuaValue call() { + return LuaValue.valueOf(mc.textRenderer.fontHeight); + } + }); + } + + private void registerUtilityAPI(Globals globals) { + LuaTable utilTable = new LuaTable(); + globals.set("util", utilTable); + + utilTable.set("log", new OneArgFunction() { + @Override + public LuaValue call(LuaValue arg) { + String message = arg.checkjstring(); + System.out.println("[Script] " + message); + return LuaValue.NIL; + } + }); + + utilTable.set("chatInfo", new OneArgFunction() { + @Override + public LuaValue call(LuaValue arg) { + String message = arg.checkjstring(); + ChatUtility.sendInfo(message); + return LuaValue.NIL; + } + }); + + utilTable.set("chatError", new OneArgFunction() { + @Override + public LuaValue call(LuaValue arg) { + String message = arg.checkjstring(); + ChatUtility.sendError(message); + return LuaValue.NIL; + } + }); + + utilTable.set("setSpeed", new TwoArgFunction() { + @Override + public LuaValue call(LuaValue arg$1, LuaValue arg$2) { + double speed = arg$1.checkdouble(); + boolean strafe = arg$2.checkboolean(); + MoveUtility.setSpeed(speed, strafe); + return LuaValue.NIL; + } + }); + + utilTable.set("currentTimeMillis", new ZeroArgFunction() { + @Override + public LuaValue call() { + return LuaValue.valueOf(System.currentTimeMillis()); + } + }); + + utilTable.set("sleep", new OneArgFunction() { + @Override + public LuaValue call(LuaValue arg) { + long ms = arg.checklong(); + try { + Thread.sleep(ms); + } catch(InterruptedException e) { + Thread.currentThread().interrupt(); + } + return LuaValue.NIL; + } + }); + + utilTable.set("randomInt", new TwoArgFunction() { + @Override + public LuaValue call(LuaValue arg1, LuaValue arg2) { + int min = arg1.checkint(); + int max = arg2.checkint(); + return LuaValue.valueOf(min + (int) (Math.random() * (max - min + 1))); + } + }); + + utilTable.set("randomDouble", new TwoArgFunction() { + @Override + public LuaValue call(LuaValue arg1, LuaValue arg2) { + double min = arg1.checkdouble(); + double max = arg2.checkdouble(); + return LuaValue.valueOf(min + Math.random() * (max - min)); + } + }); + } + + private void registerWorldAPI(Globals globals) { + LuaTable worldTable = new LuaTable(); + globals.set("world", worldTable); + + worldTable.set("getBlock", new VarArgFunction() { + @Override + public Varargs invoke(Varargs args) { + if(mc.world == null) return LuaValue.NIL; + int x = args.checkint(1); + int y = args.checkint(2); + int z = args.checkint(3); + BlockPos pos = new BlockPos(x, y, z); + BlockState state = mc.world.getBlockState(pos); + return CoerceJavaToLua.coerce(state.getBlock()); + } + }); + + worldTable.set("getBlockState", new VarArgFunction() { + @Override + public Varargs invoke(Varargs args) { + if(mc.world == null) return LuaValue.NIL; + int x = args.checkint(1); + int y = args.checkint(2); + int z = args.checkint(3); + BlockPos pos = new BlockPos(x, y, z); + BlockState state = mc.world.getBlockState(pos); + return CoerceJavaToLua.coerce(state); + } + }); + + worldTable.set("isAir", new VarArgFunction() { + @Override + public Varargs invoke(Varargs args) { + if(mc.world == null) return LuaValue.FALSE; + int x = args.checkint(1); + int y = args.checkint(2); + int z = args.checkint(3); + BlockPos pos = new BlockPos(x, y, z); + return LuaValue.valueOf(mc.world.getBlockState(pos).isAir()); + } + }); + + worldTable.set("getLightLevel", new VarArgFunction() { + @Override + public Varargs invoke(Varargs args) { + if(mc.world == null) return LuaValue.valueOf(0); + int x = args.checkint(1); + int y = args.checkint(2); + int z = args.checkint(3); + BlockPos pos = new BlockPos(x, y, z); + return LuaValue.valueOf(mc.world.getLightLevel(pos)); + } + }); + + worldTable.set("getEntitiesInRange", new VarArgFunction() { + @Override + public Varargs invoke(Varargs args) { + if(mc.world == null) return LuaValue.NIL; + double x = args.checkdouble(1); + double y = args.checkdouble(2); + double z = args.checkdouble(3); + double range = args.checkdouble(4); + + Vec3d center = new Vec3d(x, y, z); + Box box = new Box(center.subtract(range, range, range), center.add(range, range, range)); + List entities = mc.world.getOtherEntities(null, box); + return CoerceJavaToLua.coerce(entities); + } + }); + + worldTable.set("raycast", new VarArgFunction() { + @Override + public Varargs invoke(Varargs args) { + if(mc.world == null || mc.player == null) return LuaValue.NIL; + double distance = args.optdouble(1, 4.5); + HitResult hit = mc.player.raycast(distance, 0, false); + return CoerceJavaToLua.coerce(hit); + } + }); + + worldTable.set("getDimension", new ZeroArgFunction() { + @Override + public LuaValue call() { + if(mc.world == null) return LuaValue.NIL; + return LuaValue.valueOf(mc.world.getRegistryKey().getValue().toString()); + } + }); + + worldTable.set("getWeather", new ZeroArgFunction() { + @Override + public LuaValue call() { + if(mc.world == null) return LuaValue.NIL; + LuaTable weather = new LuaTable(); + weather.set("raining", LuaValue.valueOf(mc.world.isRaining())); + weather.set("thundering", LuaValue.valueOf(mc.world.isThundering())); + weather.set("rainGradient", LuaValue.valueOf(mc.world.getRainGradient(0))); + weather.set("thunderGradient", LuaValue.valueOf(mc.world.getThunderGradient(0))); + return weather; + } + }); + } + + private void registerPlayerAPI(Globals globals) { + LuaTable playerTable = new LuaTable(); + globals.set("player", playerTable); + + playerTable.set("getPlayersInRange", new OneArgFunction() { + @Override + public LuaValue call(LuaValue arg) { + if(mc.player == null || mc.world == null) return LuaValue.NIL; + + double range = arg.checkdouble(); + List players = new ArrayList<>(); + + for(Entity entity : mc.world.getEntities()) { + if(entity instanceof PlayerEntity player && entity != mc.player) { + if(mc.player.distanceTo(player) <= range) { + players.add(player); + } + } + } + + return CoerceJavaToLua.coerce(players); + } + }); + + playerTable.set("getHealth", new ZeroArgFunction() { + @Override + public LuaValue call() { + return mc.player != null ? LuaValue.valueOf(mc.player.getHealth()) : LuaValue.valueOf(0); + } + }); + + playerTable.set("getMaxHealth", new ZeroArgFunction() { + @Override + public LuaValue call() { + return mc.player != null ? LuaValue.valueOf(mc.player.getMaxHealth()) : LuaValue.valueOf(0); + } + }); + + playerTable.set("getFoodLevel", new ZeroArgFunction() { + @Override + public LuaValue call() { + return mc.player != null ? LuaValue.valueOf(mc.player.getHungerManager().getFoodLevel()) : LuaValue.valueOf(0); + } + }); + + playerTable.set("getSaturation", new ZeroArgFunction() { + @Override + public LuaValue call() { + return mc.player != null ? LuaValue.valueOf(mc.player.getHungerManager().getSaturationLevel()) : LuaValue.valueOf(0); + } + }); + + playerTable.set("getExperience", new ZeroArgFunction() { + @Override + public LuaValue call() { + return mc.player != null ? LuaValue.valueOf(mc.player.experienceLevel) : LuaValue.valueOf(0); + } + }); + + playerTable.set("getVelocity", new ZeroArgFunction() { + @Override + public LuaValue call() { + if(mc.player == null) return LuaValue.NIL; + Vec3d vel = mc.player.getVelocity(); + LuaTable velTable = new LuaTable(); + velTable.set("x", vel.x); + velTable.set("y", vel.y); + velTable.set("z", vel.z); + return velTable; + } + }); + + playerTable.set("getYaw", new ZeroArgFunction() { + @Override + public LuaValue call() { + return mc.player != null ? LuaValue.valueOf(mc.player.getYaw()) : LuaValue.valueOf(0); + } + }); + + playerTable.set("getPitch", new ZeroArgFunction() { + @Override + public LuaValue call() { + return mc.player != null ? LuaValue.valueOf(mc.player.getPitch()) : LuaValue.valueOf(0); + } + }); + + playerTable.set("isOnGround", new ZeroArgFunction() { + @Override + public LuaValue call() { + return mc.player != null ? LuaValue.valueOf(mc.player.isOnGround()) : LuaValue.FALSE; + } + }); + + playerTable.set("isTouchingWater", new ZeroArgFunction() { + @Override + public LuaValue call() { + return mc.player != null ? LuaValue.valueOf(mc.player.isTouchingWater()) : LuaValue.FALSE; + } + }); + + playerTable.set("isInLava", new ZeroArgFunction() { + @Override + public LuaValue call() { + return mc.player != null ? LuaValue.valueOf(mc.player.isInLava()) : LuaValue.FALSE; + } + }); + + playerTable.set("isCreative", new ZeroArgFunction() { + @Override + public LuaValue call() { + return mc.player != null ? LuaValue.valueOf(mc.player.getAbilities().creativeMode) : LuaValue.FALSE; + } + }); + + playerTable.set("canFly", new ZeroArgFunction() { + @Override + public LuaValue call() { + return mc.player != null ? LuaValue.valueOf(mc.player.getAbilities().allowFlying) : LuaValue.FALSE; + } + }); + + playerTable.set("setPosition", new VarArgFunction() { + @Override + public Varargs invoke(Varargs args) { + if(mc.player == null) return LuaValue.NIL; + double x = args.checkdouble(1); + double y = args.checkdouble(2); + double z = args.checkdouble(3); + mc.player.setPosition(x, y, z); + return LuaValue.NIL; + } + }); + + playerTable.set("setVelocity", new VarArgFunction() { + @Override + public Varargs invoke(Varargs args) { + if(mc.player == null) return LuaValue.NIL; + double x = args.checkdouble(1); + double y = args.checkdouble(2); + double z = args.checkdouble(3); + mc.player.setVelocity(x, y, z); + return LuaValue.NIL; + } + }); + + playerTable.set("setYaw", new OneArgFunction() { + @Override + public LuaValue call(LuaValue arg) { + if(mc.player == null) return LuaValue.NIL; + float yaw = (float) arg.checkdouble(); + mc.player.setYaw(yaw); + return LuaValue.NIL; + } + }); + + playerTable.set("setPitch", new OneArgFunction() { + @Override + public LuaValue call(LuaValue arg) { + if(mc.player == null) return LuaValue.NIL; + float pitch = (float) arg.checkdouble(); + mc.player.setPitch(pitch); + return LuaValue.NIL; + } + }); + + playerTable.set("jump", new ZeroArgFunction() { + @Override + public LuaValue call() { + if(mc.player != null) { + mc.player.jump(); + } + return LuaValue.NIL; + } + }); + + playerTable.set("swingHand", new ZeroArgFunction() { + @Override + public LuaValue call() { + if(mc.player != null) { + mc.player.swingHand(Hand.MAIN_HAND); + } + return LuaValue.NIL; + } + }); + } + + private void registerInventoryAPI(Globals globals) { + LuaTable invTable = new LuaTable(); + globals.set("inventory", invTable); + + invTable.set("getHeldItem", new ZeroArgFunction() { + @Override + public LuaValue call() { + if(mc.player == null) return LuaValue.NIL; + ItemStack stack = mc.player.getMainHandStack(); + return CoerceJavaToLua.coerce(stack); + } + }); + + invTable.set("getOffHandItem", new ZeroArgFunction() { + @Override + public LuaValue call() { + if(mc.player == null) return LuaValue.NIL; + ItemStack stack = mc.player.getOffHandStack(); + return CoerceJavaToLua.coerce(stack); + } + }); + + invTable.set("getSlot", new OneArgFunction() { + @Override + public LuaValue call(LuaValue arg) { + if(mc.player == null) return LuaValue.NIL; + int slot = arg.checkint(); + if(slot < 0 || slot >= mc.player.getInventory().size()) return LuaValue.NIL; + ItemStack stack = mc.player.getInventory().getStack(slot); + return CoerceJavaToLua.coerce(stack); + } + }); + + invTable.set("getSelectedSlot", new ZeroArgFunction() { + @Override + public LuaValue call() { + if(mc.player == null) return LuaValue.valueOf(0); + return LuaValue.valueOf(mc.player.getInventory().getSelectedSlot()); + } + }); + + invTable.set("setSelectedSlot", new OneArgFunction() { + @Override + public LuaValue call(LuaValue arg) { + if(mc.player == null || mc.getNetworkHandler() == null) return LuaValue.NIL; + int slot = arg.checkint(); + if(slot >= 0 && slot < 9) { + mc.getNetworkHandler().sendPacket(new UpdateSelectedSlotC2SPacket(slot)); + } + return LuaValue.NIL; + } + }); + + invTable.set("findItem", new OneArgFunction() { + @Override + public LuaValue call(LuaValue arg) { + if(mc.player == null) return LuaValue.valueOf(-1); + String itemName = arg.checkjstring(); + for(int i = 0; i < mc.player.getInventory().size(); i++) { + ItemStack stack = mc.player.getInventory().getStack(i); + if(!stack.isEmpty() && stack.getItem().toString().contains(itemName)) { + return LuaValue.valueOf(i); + } + } + return LuaValue.valueOf(-1); + } + }); + + invTable.set("getItemCount", new OneArgFunction() { + @Override + public LuaValue call(LuaValue arg) { + if(mc.player == null) return LuaValue.valueOf(0); + String itemName = arg.checkjstring(); + int count = 0; + for(int i = 0; i < mc.player.getInventory().size(); i++) { + ItemStack stack = mc.player.getInventory().getStack(i); + if(!stack.isEmpty() && stack.getItem().toString().contains(itemName)) { + count += stack.getCount(); + } + } + return LuaValue.valueOf(count); + } + }); + } + + private void registerMathAPI(Globals globals) { + LuaTable mathTable = new LuaTable(); + globals.set("math", mathTable); + + mathTable.set("distance", new VarArgFunction() { + @Override + public Varargs invoke(Varargs args) { + double x1 = args.checkdouble(1); + double y1 = args.checkdouble(2); + double z1 = args.checkdouble(3); + double x2 = args.checkdouble(4); + double y2 = args.checkdouble(5); + double z2 = args.checkdouble(6); + double distance = Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2) + Math.pow(z2 - z1, 2)); + return LuaValue.valueOf(distance); + } + }); + + mathTable.set("distance2D", new VarArgFunction() { + @Override + public Varargs invoke(Varargs args) { + double x1 = args.checkdouble(1); + double z1 = args.checkdouble(2); + double x2 = args.checkdouble(3); + double z2 = args.checkdouble(4); + double distance = Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(z2 - z1, 2)); + return LuaValue.valueOf(distance); + } + }); + + mathTable.set("wrapAngle", new OneArgFunction() { + @Override + public LuaValue call(LuaValue arg) { + float angle = (float) arg.checkdouble(); + return LuaValue.valueOf(MathHelper.wrapDegrees(angle)); + } + }); + + mathTable.set("angleTo", new VarArgFunction() { + @Override + public Varargs invoke(Varargs args) { + if(mc.player == null) return LuaValue.NIL; + double x = args.checkdouble(1); + double y = args.checkdouble(2); + double z = args.checkdouble(3); + + Vec3d playerPos = mc.player.getPos(); + double deltaX = x - playerPos.x; + double deltaZ = z - playerPos.z; + double deltaY = y - (playerPos.y + mc.player.getEyeHeight(mc.player.getPose())); + + double yaw = Math.toDegrees(Math.atan2(deltaZ, deltaX)) - 90; + double pitch = -Math.toDegrees(Math.atan2(deltaY, Math.sqrt(deltaX * deltaX + deltaZ * deltaZ))); + + LuaTable angles = new LuaTable(); + angles.set("yaw", MathHelper.wrapDegrees((float) yaw)); + angles.set("pitch", MathHelper.wrapDegrees((float) pitch)); + return angles; + } + }); + + mathTable.set("lerp", new ThreeArgFunction() { + @Override + public LuaValue call(LuaValue arg1, LuaValue arg2, LuaValue arg3) { + double start = arg1.checkdouble(); + double end = arg2.checkdouble(); + double factor = arg3.checkdouble(); + return LuaValue.valueOf(start + (end - start) * factor); + } + }); + + mathTable.set("clamp", new ThreeArgFunction() { + @Override + public LuaValue call(LuaValue arg1, LuaValue arg2, LuaValue arg3) { + double value = arg1.checkdouble(); + double min = arg2.checkdouble(); + double max = arg3.checkdouble(); + return LuaValue.valueOf(Math.max(min, Math.min(max, value))); + } + }); + } + + private void registerNetworkAPI(Globals globals) { + LuaTable netTable = new LuaTable(); + globals.set("network", netTable); + + netTable.set("sendPacket", new OneArgFunction() { + @Override + public LuaValue call(LuaValue arg) { + if(mc.player == null || mc.getNetworkHandler() == null) return LuaValue.NIL; + Object packet = arg.checkuserdata(); + if(packet instanceof net.minecraft.network.packet.Packet) { + mc.getNetworkHandler().sendPacket((net.minecraft.network.packet.Packet) packet); + } + return LuaValue.NIL; + } + }); + + netTable.set("sendMovementPacket", new VarArgFunction() { + @Override + public Varargs invoke(Varargs args) { + if(mc.player == null || mc.getNetworkHandler() == null) return LuaValue.NIL; + double x = args.checkdouble(1); + double y = args.checkdouble(2); + double z = args.checkdouble(3); + float yaw = (float) args.optdouble(4, mc.player.getYaw()); + float pitch = (float) args.optdouble(5, mc.player.getPitch()); + boolean onGround = args.optboolean(6, mc.player.isOnGround()); + boolean changePosition = args.optboolean(7, true); + + PlayerMoveC2SPacket packet = new PlayerMoveC2SPacket.Full(x, y, z, yaw, pitch, onGround, changePosition); + mc.getNetworkHandler().sendPacket(packet); + return LuaValue.NIL; + } + }); + + netTable.set("sendHandSwing", new ZeroArgFunction() { + @Override + public LuaValue call() { + if(mc.player == null || mc.getNetworkHandler() == null) return LuaValue.NIL; + mc.getNetworkHandler().sendPacket(new HandSwingC2SPacket(Hand.MAIN_HAND)); + return LuaValue.NIL; + } + }); + } + + private void registerSoundAPI(Globals globals) { + LuaTable soundTable = new LuaTable(); + globals.set("sound", soundTable); + + soundTable.set("playSound", new VarArgFunction() { + @Override + public Varargs invoke(Varargs args) { + if(mc.player == null || mc.world == null) return LuaValue.NIL; + String soundName = args.checkjstring(1); + float volume = (float) args.optdouble(2, 1.0); + float pitch = (float) args.optdouble(3, 1.0); + + try { + String[] parts = soundName.split(":"); + if(parts.length != 2) return LuaValue.NIL; + + Identifier soundId = Identifier.of(parts[0], parts[1]); + SoundEvent sound = Registries.SOUND_EVENT.get(soundId); + if(sound != null) { + mc.world.playSound(mc.player, mc.player.getBlockPos(), sound, SoundCategory.MASTER, volume, pitch); + } + } catch(Exception e) { + ChatUtility.sendError("Failed to play sound \"" + soundName + "\": " + e.getMessage()); + ChatUtility.sendScriptError(e); + } + return LuaValue.NIL; + } + }); + + soundTable.set("playSoundAt", new VarArgFunction() { + @Override + public Varargs invoke(Varargs args) { + if(mc.world == null) return LuaValue.NIL; + String soundName = args.checkjstring(1); + double x = args.checkdouble(2); + double y = args.checkdouble(3); + double z = args.checkdouble(4); + float volume = (float) args.optdouble(5, 1.0); + float pitch = (float) args.optdouble(6, 1.0); + + try { + String[] parts = soundName.split(":"); + if(parts.length != 2) return LuaValue.NIL; + + Identifier soundId = Identifier.of(parts[0], parts[1]); + SoundEvent sound = Registries.SOUND_EVENT.get(soundId); + if(sound != null) { + mc.world.playSound(null, new BlockPos((int) Math.floor(x), (int) Math.floor(y), (int) Math.floor(z)), sound, SoundCategory.MASTER, volume, pitch); + } + } catch(Exception e) { + ChatUtility.sendError("Failed to play sound \"" + soundName + "\": " + e.getMessage()); + ChatUtility.sendScriptError(e); + } + return LuaValue.NIL; + } + }); + } + + private void registerEntityAPI(Globals globals) { + LuaTable entityTable = new LuaTable(); + globals.set("entity", entityTable); + + entityTable.set("getClosestPlayer", new ZeroArgFunction() { + @Override + public LuaValue call() { + if(mc.player == null || mc.world == null) return LuaValue.NIL; + + PlayerEntity closest = null; + double closestDistance = Double.MAX_VALUE; + + for(Entity entity : mc.world.getEntities()) { + if(entity instanceof PlayerEntity player && entity != mc.player) { + double distance = mc.player.distanceTo(player); + if(distance < closestDistance) { + closestDistance = distance; + closest = player; + } + } + } + + return closest != null ? CoerceJavaToLua.coerce(closest) : LuaValue.NIL; + } + }); + + entityTable.set("getClosestEntity", new OneArgFunction() { + @Override + public LuaValue call(LuaValue arg) { + if(mc.player == null || mc.world == null) return LuaValue.NIL; + + String entityType = arg.checkjstring(); + Entity closest = null; + double closestDistance = Double.MAX_VALUE; + + for(Entity entity : mc.world.getEntities()) { + if(entity != mc.player && entity.getType().toString().contains(entityType)) { + double distance = mc.player.distanceTo(entity); + if(distance < closestDistance) { + closestDistance = distance; + closest = entity; + } + } + } + + return closest != null ? CoerceJavaToLua.coerce(closest) : LuaValue.NIL; + } + }); + + entityTable.set("getEntityPosition", new OneArgFunction() { + @Override + public LuaValue call(LuaValue arg) { + Entity entity = (Entity) arg.checkuserdata(); + if(entity == null) return LuaValue.NIL; + + Vec3d pos = entity.getPos(); + LuaTable posTable = new LuaTable(); + posTable.set("x", pos.x); + posTable.set("y", pos.y); + posTable.set("z", pos.z); + return posTable; + } + }); + + entityTable.set("getEntityHealth", new OneArgFunction() { + @Override + public LuaValue call(LuaValue arg) { + Entity entity = (Entity) arg.checkuserdata(); + if(entity instanceof LivingEntity living) { + return LuaValue.valueOf(living.getHealth()); + } + return LuaValue.valueOf(0); + } + }); + + entityTable.set("getEntityMaxHealth", new OneArgFunction() { + @Override + public LuaValue call(LuaValue arg) { + Entity entity = (Entity) arg.checkuserdata(); + if(entity instanceof LivingEntity living) { + return LuaValue.valueOf(living.getMaxHealth()); + } + return LuaValue.valueOf(0); + } + }); + + entityTable.set("isEntityAlive", new OneArgFunction() { + @Override + public LuaValue call(LuaValue arg) { + Entity entity = (Entity) arg.checkuserdata(); + return LuaValue.valueOf(entity != null && entity.isAlive()); + } + }); + + entityTable.set("getEntityName", new OneArgFunction() { + @Override + public LuaValue call(LuaValue arg) { + Entity entity = (Entity) arg.checkuserdata(); + return entity != null ? LuaValue.valueOf(entity.getName().getString()) : LuaValue.NIL; + } + }); + + entityTable.set("getEntityType", new OneArgFunction() { + @Override + public LuaValue call(LuaValue arg) { + Entity entity = (Entity) arg.checkuserdata(); + return entity != null ? LuaValue.valueOf(entity.getType().toString()) : LuaValue.NIL; + } + }); + + + entityTable.set("getEntitiesInRange", new TwoArgFunction() { + @Override + public LuaValue call(LuaValue arg1, LuaValue arg2) { + if(mc.player == null || mc.world == null) return LuaValue.NIL; + + double range = arg1.checkdouble(); + String entityType = arg2.checkjstring(); + List entities = new ArrayList<>(); + + for(Entity entity : mc.world.getEntities()) { + if(entity != mc.player && entity.getType().toString().contains(entityType)) { + if(mc.player.distanceTo(entity) <= range) { + entities.add(entity); + } + } + } + + return CoerceJavaToLua.coerce(entities); + } + }); + } +} diff --git a/src/client/java/works/alya/script/core/Script.java b/src/client/java/works/alya/script/core/Script.java new file mode 100644 index 0000000..ab7b044 --- /dev/null +++ b/src/client/java/works/alya/script/core/Script.java @@ -0,0 +1,190 @@ +/* + * Copyright (c) Alya Client 2024-2025. + * + * This file belongs to Alya Client, + * an open-source Fabric injection client. + * Rye GitHub: https://github.com/AlyaClient/alya-beta.git + * + * THIS PROJECT DOES NOT HAVE A WARRANTY. + * + * Alya (and subsequently, its files) are all licensed under the MIT License. + * Alya should have come with a copy of the MIT License. + * If it did not, you may obtain a copy here: + * MIT License: https://opensource.org/license/mit + * + */ + +package works.alya.script.core; + +import org.luaj.vm2.Globals; +import org.luaj.vm2.LuaError; +import org.luaj.vm2.LuaValue; +import works.alya.script.data.NumberSettingData; +import works.alya.utilities.misc.ChatUtility; + +import java.io.File; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +/** + * Represents a Lua script in the Alya Client. + * Contains script metadata and execution environment. + */ +@SuppressWarnings("CallToPrintStackTrace") +public class Script { + private final File file; + private final String name; + private final String description; + private final Globals globals; + private final Map settings = new HashMap<>(); + private final Set reportedErrors = new HashSet<>(); + + /** + * Creates a new Script instance. + * + * @param file The Lua script file + * @param name The name of the script + * @param description The description of the script + * @param globals The Lua globals environment + */ + public Script(File file, String name, String description, Globals globals) { + this.file = file; + this.name = name; + this.description = description; + this.globals = globals; + + loadSettings(); + } + + /** + * Loads settings defined in the script. + */ + private void loadSettings() { + LuaValue settingsTable = globals.get("settings"); + if(!settingsTable.isnil() && settingsTable.istable()) { + LuaValue[] keys = settingsTable.checktable().keys(); + for(LuaValue key : keys) { + String settingName = key.tojstring(); + LuaValue settingValue = settingsTable.get(key); + + if(settingValue.isboolean()) { + settings.put(settingName, settingValue.toboolean()); + } else if(settingValue.istable()) { + LuaValue valueField = settingValue.get("value"); + LuaValue minField = settingValue.get("min"); + LuaValue maxField = settingValue.get("max"); + + if(!valueField.isnil()) { + if(valueField.isint()) { + int value = valueField.toint(); + int min = minField.isnil() ? Integer.MIN_VALUE : minField.toint(); + int max = maxField.isnil() ? Integer.MAX_VALUE : maxField.toint(); + settings.put(settingName, new NumberSettingData(value, min, max)); + } else if(valueField.isnumber()) { + double value = valueField.todouble(); + double min = minField.isnil() ? Double.MIN_VALUE : minField.todouble(); + double max = maxField.isnil() ? Double.MAX_VALUE : maxField.todouble(); + settings.put(settingName, new NumberSettingData(value, min, max)); + } else if(valueField.isstring()) { + settings.put(settingName, valueField.tojstring()); + } + } + } else if(settingValue.isint()) { + settings.put(settingName, new NumberSettingData(settingValue.toint(), 0, 100)); + } else if(settingValue.isnumber()) { + settings.put(settingName, new NumberSettingData(settingValue.todouble(), 0.0, 100.0)); + } else if(settingValue.isstring()) { + settings.put(settingName, settingValue.tojstring()); + } + } + } + } + + /** + * Calls a function in the script. + * + * @param functionName The name of the function to call + * @param args The arguments to pass to the function + */ + public void callFunction(String functionName, LuaValue... args) { + LuaValue function = globals.get(functionName); + if(function.isfunction()) { + try { + function.invoke(LuaValue.varargsOf(args)); + } catch(LuaError luaError) { + String errorMessage = luaError.getMessage(); + String errorKey = name + ":" + functionName + ":" + errorMessage; + + if(!reportedErrors.contains(errorKey)) { + reportedErrors.add(errorKey); + String formattedError = "Error in script '" + name + "', function '" + functionName + "': " + errorMessage; + ChatUtility.sendError(formattedError); + luaError.printStackTrace(); + } + } catch(Exception ex) { + String errorKey = name + ":" + functionName + ":" + ex.getClass().getSimpleName(); + + if(!reportedErrors.contains(errorKey)) { + reportedErrors.add(errorKey); + ChatUtility.sendError("Error calling function '" + functionName + "' in script '" + name + "'"); + ex.printStackTrace(); + } + } + } + } + + /** + * Clears the reported errors set, allowing errors to be reported again. + * This can be useful when reloading scripts or when you want to reset error reporting. + */ + public void clearReportedErrors() { + reportedErrors.clear(); + } + + /** + * Gets the file for this script. + * + * @return The script file + */ + public File getFile() { + return file; + } + + /** + * Gets the name of this script. + * + * @return The script name + */ + public String getName() { + return name; + } + + /** + * Gets the description of this script. + * + * @return The script description + */ + public String getDescription() { + return description; + } + + /** + * Gets the Lua globals environment for this script. + * + * @return The Lua globals + */ + public Globals getGlobals() { + return globals; + } + + /** + * Gets the settings for this script. + * + * @return Map of setting name to value + */ + public Map getSettings() { + return settings; + } +} \ No newline at end of file diff --git a/src/client/java/works/alya/script/core/ScriptManager.java b/src/client/java/works/alya/script/core/ScriptManager.java new file mode 100644 index 0000000..7037f74 --- /dev/null +++ b/src/client/java/works/alya/script/core/ScriptManager.java @@ -0,0 +1,154 @@ +/* + * Copyright (c) Alya Client 2024-2025. + * + * This file belongs to Alya Client, + * an open-source Fabric injection client. + * Rye GitHub: https://github.com/AlyaClient/alya-beta.git + * + * THIS PROJECT DOES NOT HAVE A WARRANTY. + * + * Alya (and subsequently, its files) are all licensed under the MIT License. + * Alya should have come with a copy of the MIT License. + * If it did not, you may obtain a copy here: + * MIT License: https://opensource.org/license/mit + * + */ + +package works.alya.script.core; + +import net.minecraft.client.MinecraftClient; +import org.luaj.vm2.Globals; +import org.luaj.vm2.LuaValue; +import org.luaj.vm2.lib.jse.JsePlatform; +import works.alya.AlyaClient; +import works.alya.script.api.LuaAPI; +import works.alya.script.integration.ScriptModule; +import works.alya.utilities.misc.ChatUtility; + +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Manages Lua scripts for the Alya Client. + * Handles loading, execution, and management of scripts. + */ +@SuppressWarnings({"CallToPrintStackTrace", "unused", "FieldCanBeLocal"}) +public class ScriptManager { + private static final ScriptManager INSTANCE = new ScriptManager(); + private final MinecraftClient mc = MinecraftClient.getInstance(); + private final List