├── gradle.properties ├── src └── main │ ├── java │ └── net │ │ └── earthcomputer │ │ └── clientcommands │ │ └── script │ │ ├── ScriptInterruptedException.java │ │ ├── ducks │ │ ├── IMinecraftClient.java │ │ └── IMaterial.java │ │ ├── ScriptFunction.java │ │ ├── mixin │ │ ├── KeyBindingAccessor.java │ │ ├── AbstractBlockAccessor.java │ │ ├── AbstractBlockSettingsAccessor.java │ │ ├── FireBlockAccessor.java │ │ ├── MixinMaterial.java │ │ ├── MixinClientCommands.java │ │ ├── MixinMouse.java │ │ ├── MixinKeyboardInput.java │ │ ├── MixinMinecraftClient.java │ │ └── MixinClientPlayerEntity.java │ │ ├── ScriptLivingEntity.java │ │ ├── ClientCommandsScripting.java │ │ ├── ScriptPosition.java │ │ ├── ScriptThread.java │ │ ├── ClientCommandsLibrary.java │ │ ├── ClientCommandsLanguage.java │ │ ├── BeanWrapper.java │ │ ├── ScriptItemStack.java │ │ ├── ScriptEntity.java │ │ ├── ScriptWorld.java │ │ ├── ScriptBuiltins.java │ │ ├── ScriptCommand.java │ │ ├── ScriptBlockState.java │ │ ├── ScriptInventory.java │ │ ├── ScriptUtil.java │ │ ├── ScriptManager.java │ │ └── ScriptPlayer.java │ └── resources │ ├── assets │ └── clientcommands-scripting │ │ └── lang │ │ ├── zh_cn.json │ │ ├── zh_tw.json │ │ ├── id_id.json │ │ ├── nl_nl.json │ │ ├── pl_pl.json │ │ └── en_us.json │ ├── clientcommands-scripting.mixins.json │ └── fabric.mod.json ├── LICENSE ├── .gitignore ├── README.md └── docs ├── example_scripts ├── build_platform.js └── branch_mine.js └── clientcommands.ts /gradle.properties: -------------------------------------------------------------------------------- 1 | # Mod Properties 2 | mod_version=1.3.2 3 | maven_group=net.earthcomputer.clientcommands 4 | archives_base_name=clientcommands-scripting 5 | jsmacros_version=d50a755cab 6 | graal_version=21.1.0 7 | -------------------------------------------------------------------------------- /src/main/java/net/earthcomputer/clientcommands/script/ScriptInterruptedException.java: -------------------------------------------------------------------------------- 1 | package net.earthcomputer.clientcommands.script; 2 | 3 | public class ScriptInterruptedException extends RuntimeException { 4 | } 5 | -------------------------------------------------------------------------------- /src/main/resources/assets/clientcommands-scripting/lang/zh_cn.json: -------------------------------------------------------------------------------- 1 | { 2 | "commands.cscript.notFound": "未找到脚本 \"%s\"", 3 | "commands.cscript.reload.success": "已重新加载脚本", 4 | "commands.cscript.run.success": "成功运行脚本" 5 | } 6 | -------------------------------------------------------------------------------- /src/main/resources/assets/clientcommands-scripting/lang/zh_tw.json: -------------------------------------------------------------------------------- 1 | { 2 | "commands.cscript.notFound": "未找到腳本 \"%s\"", 3 | "commands.cscript.reload.success": "已重新加載腳本", 4 | "commands.cscript.run.success": "成功運行腳本" 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/net/earthcomputer/clientcommands/script/ducks/IMinecraftClient.java: -------------------------------------------------------------------------------- 1 | package net.earthcomputer.clientcommands.script.ducks; 2 | 3 | public interface IMinecraftClient { 4 | void resetAttackCooldown(); 5 | void continueBreakingBlock(); 6 | } 7 | -------------------------------------------------------------------------------- /src/main/resources/assets/clientcommands-scripting/lang/id_id.json: -------------------------------------------------------------------------------- 1 | { 2 | "commands.cscript.notFound": "Skrip \"%s\" tidak ketemu", 3 | "commands.cscript.reload.success": "Skrip telah direload", 4 | "commands.cscript.run.success": "Skrip telah berhasil jalan" 5 | } 6 | -------------------------------------------------------------------------------- /src/main/resources/assets/clientcommands-scripting/lang/nl_nl.json: -------------------------------------------------------------------------------- 1 | { 2 | "commands.cscript.notFound": "Script \"%s\" niet gevonden", 3 | "commands.cscript.reload.success": "scripten herladen", 4 | "commands.cscript.run.success": "Script succesvol uitgevoerd" 5 | } 6 | -------------------------------------------------------------------------------- /src/main/resources/assets/clientcommands-scripting/lang/pl_pl.json: -------------------------------------------------------------------------------- 1 | { 2 | "commands.cscript.notFound": "Skrypt \"%s\" nie istnieje", 3 | "commands.cscript.reload.success": "Przeładowano skrypty", 4 | "commands.cscript.run.success": "Wykonano skrypt pomyślnie" 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/net/earthcomputer/clientcommands/script/ScriptFunction.java: -------------------------------------------------------------------------------- 1 | package net.earthcomputer.clientcommands.script; 2 | 3 | import org.graalvm.polyglot.Value; 4 | 5 | @FunctionalInterface 6 | public interface ScriptFunction { 7 | Value call(Object... args); 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/net/earthcomputer/clientcommands/script/ducks/IMaterial.java: -------------------------------------------------------------------------------- 1 | package net.earthcomputer.clientcommands.script.ducks; 2 | 3 | import java.util.concurrent.atomic.AtomicInteger; 4 | 5 | public interface IMaterial { 6 | 7 | AtomicInteger nextId = new AtomicInteger(); 8 | 9 | int clientcommands_getId(); 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/net/earthcomputer/clientcommands/script/mixin/KeyBindingAccessor.java: -------------------------------------------------------------------------------- 1 | package net.earthcomputer.clientcommands.script.mixin; 2 | 3 | import net.minecraft.client.option.KeyBinding; 4 | import org.spongepowered.asm.mixin.Mixin; 5 | import org.spongepowered.asm.mixin.gen.Accessor; 6 | 7 | @Mixin(KeyBinding.class) 8 | public interface KeyBindingAccessor { 9 | @Accessor 10 | void setPressed(boolean pressed); 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/net/earthcomputer/clientcommands/script/mixin/AbstractBlockAccessor.java: -------------------------------------------------------------------------------- 1 | package net.earthcomputer.clientcommands.script.mixin; 2 | 3 | import net.minecraft.block.AbstractBlock; 4 | import org.spongepowered.asm.mixin.Mixin; 5 | import org.spongepowered.asm.mixin.gen.Accessor; 6 | 7 | @Mixin(AbstractBlock.class) 8 | public interface AbstractBlockAccessor { 9 | @Accessor 10 | AbstractBlock.Settings getSettings(); 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/net/earthcomputer/clientcommands/script/mixin/AbstractBlockSettingsAccessor.java: -------------------------------------------------------------------------------- 1 | package net.earthcomputer.clientcommands.script.mixin; 2 | 3 | import net.minecraft.block.AbstractBlock; 4 | import org.spongepowered.asm.mixin.Mixin; 5 | import org.spongepowered.asm.mixin.gen.Accessor; 6 | 7 | @Mixin(AbstractBlock.Settings.class) 8 | public interface AbstractBlockSettingsAccessor { 9 | @Accessor 10 | float getHardness(); 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/net/earthcomputer/clientcommands/script/mixin/FireBlockAccessor.java: -------------------------------------------------------------------------------- 1 | package net.earthcomputer.clientcommands.script.mixin; 2 | 3 | import net.minecraft.block.BlockState; 4 | import net.minecraft.block.FireBlock; 5 | import org.spongepowered.asm.mixin.Mixin; 6 | import org.spongepowered.asm.mixin.gen.Invoker; 7 | 8 | @Mixin(FireBlock.class) 9 | public interface FireBlockAccessor { 10 | @Invoker 11 | int callGetSpreadChance(BlockState state); 12 | 13 | @Invoker 14 | int callGetBurnChance(BlockState state); 15 | } 16 | -------------------------------------------------------------------------------- /src/main/resources/clientcommands-scripting.mixins.json: -------------------------------------------------------------------------------- 1 | { 2 | "required": true, 3 | "package": "net.earthcomputer.clientcommands.script.mixin", 4 | "compatibilityLevel": "JAVA_8", 5 | "mixins": [ 6 | "AbstractBlockAccessor", 7 | "AbstractBlockSettingsAccessor", 8 | "FireBlockAccessor", 9 | "KeyBindingAccessor", 10 | "MixinClientCommands", 11 | "MixinClientPlayerEntity", 12 | "MixinKeyboardInput", 13 | "MixinMaterial", 14 | "MixinMinecraftClient", 15 | "MixinMouse" 16 | ], 17 | "injectors": { 18 | "defaultRequire": 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/resources/assets/clientcommands-scripting/lang/en_us.json: -------------------------------------------------------------------------------- 1 | { 2 | "commands.cscript.deprecated": "The clientcommands scripting API is deprecated and will be removed in 1.19. JSMacros is the recommended replacement. Download JSMacros from %s.", 3 | "commands.cscript.nojsmacros": "JSMacros must be installed to use this command. Download JSMacros from %s.", 4 | "commands.cscript.nojsmacros.link": "here", 5 | "commands.cscript.notFound": "Script \"%s\" not found", 6 | "commands.cscript.reload.success": "Reloaded legacy scripts", 7 | "commands.cscript.run.success": "Script ran successfully" 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/net/earthcomputer/clientcommands/script/ScriptLivingEntity.java: -------------------------------------------------------------------------------- 1 | package net.earthcomputer.clientcommands.script; 2 | 3 | import net.minecraft.entity.LivingEntity; 4 | 5 | @SuppressWarnings("unused") 6 | public class ScriptLivingEntity extends ScriptEntity { 7 | 8 | ScriptLivingEntity(LivingEntity entity) { 9 | super(entity); 10 | } 11 | 12 | @Override 13 | LivingEntity getEntity() { 14 | return (LivingEntity) super.getEntity(); 15 | } 16 | 17 | public double getStandingEyeHeight() { 18 | return getEntity().getStandingEyeHeight(); 19 | } 20 | 21 | public double getEyeHeight() { 22 | return getEntity().getEyeHeight(getEntity().getPose()); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/net/earthcomputer/clientcommands/script/mixin/MixinMaterial.java: -------------------------------------------------------------------------------- 1 | package net.earthcomputer.clientcommands.script.mixin; 2 | 3 | import net.earthcomputer.clientcommands.script.ducks.IMaterial; 4 | import net.minecraft.block.Material; 5 | import org.spongepowered.asm.mixin.Mixin; 6 | import org.spongepowered.asm.mixin.Unique; 7 | import org.spongepowered.asm.mixin.injection.At; 8 | import org.spongepowered.asm.mixin.injection.Inject; 9 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 10 | 11 | @Mixin(Material.class) 12 | public class MixinMaterial implements IMaterial { 13 | 14 | @Unique private int id; 15 | 16 | @Inject(method = "", at = @At("RETURN")) 17 | private void onConstruct(CallbackInfo ci) { 18 | this.id = nextId.getAndIncrement(); 19 | } 20 | 21 | @Override 22 | public int clientcommands_getId() { 23 | return id; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/net/earthcomputer/clientcommands/script/mixin/MixinClientCommands.java: -------------------------------------------------------------------------------- 1 | package net.earthcomputer.clientcommands.script.mixin; 2 | 3 | import com.mojang.brigadier.CommandDispatcher; 4 | import net.earthcomputer.clientcommands.ClientCommands; 5 | import net.earthcomputer.clientcommands.script.ScriptCommand; 6 | import net.fabricmc.fabric.api.client.command.v1.FabricClientCommandSource; 7 | import org.spongepowered.asm.mixin.Mixin; 8 | import org.spongepowered.asm.mixin.injection.At; 9 | import org.spongepowered.asm.mixin.injection.Inject; 10 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 11 | 12 | @Mixin(value = ClientCommands.class, remap = false) 13 | public class MixinClientCommands { 14 | @Inject(method = "registerCommands", at = @At("TAIL")) 15 | private static void onRegisterCommands(CommandDispatcher dispatcher, CallbackInfo ci) { 16 | ScriptCommand.register(dispatcher); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/resources/fabric.mod.json: -------------------------------------------------------------------------------- 1 | { 2 | "schemaVersion": 1, 3 | "id": "clientcommands-scripting", 4 | "version": "${version}", 5 | "name": "Clientcommands Scripting", 6 | "description": "JSMacros scripting API for clientcommands", 7 | "authors": [ 8 | "Earthcomputer" 9 | ], 10 | "contact": { 11 | "repo": "https://github.com/Earthcomputer/clientcommands-scripting" 12 | }, 13 | "license": "MIT", 14 | "icon": "assets/clientcommands-scripting/icon.png", 15 | "environment": "client", 16 | "entrypoints": { 17 | "main": [ 18 | "net.earthcomputer.clientcommands.script.ClientCommandsScripting" 19 | ] 20 | }, 21 | "depends": { 22 | "fabricloader": ">=0.11.3", 23 | "minecraft": "1.18.2" 24 | }, 25 | "breaks": { 26 | "jsmacros": ["<=1.5", ">=1.7"] 27 | }, 28 | "mixins": [ 29 | "clientcommands-scripting.mixins.json" 30 | ], 31 | "custom": { 32 | "modmenu": { 33 | "parent": "clientcommands" 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/net/earthcomputer/clientcommands/script/mixin/MixinMouse.java: -------------------------------------------------------------------------------- 1 | package net.earthcomputer.clientcommands.script.mixin; 2 | 3 | import net.earthcomputer.clientcommands.script.ClientCommandsScripting; 4 | import net.earthcomputer.clientcommands.script.ScriptManager; 5 | import net.minecraft.client.Mouse; 6 | import org.spongepowered.asm.mixin.Mixin; 7 | import org.spongepowered.asm.mixin.Shadow; 8 | import org.spongepowered.asm.mixin.injection.At; 9 | import org.spongepowered.asm.mixin.injection.Inject; 10 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 11 | 12 | @Mixin(Mouse.class) 13 | public class MixinMouse { 14 | @Shadow private double cursorDeltaX; 15 | @Shadow private double cursorDeltaY; 16 | 17 | @Inject(method = "updateMouse", at = @At("HEAD")) 18 | private void onUpdateMouse(CallbackInfo ci) { 19 | if (ClientCommandsScripting.isJsMacrosPresent && ScriptManager.blockingInput()) { 20 | cursorDeltaX = 0; 21 | cursorDeltaY = 0; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2021 Earthcomputer 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/main/java/net/earthcomputer/clientcommands/script/ClientCommandsScripting.java: -------------------------------------------------------------------------------- 1 | package net.earthcomputer.clientcommands.script; 2 | 3 | import com.mojang.logging.LogUtils; 4 | import net.fabricmc.api.ModInitializer; 5 | import net.fabricmc.loader.api.FabricLoader; 6 | import org.slf4j.Logger; 7 | 8 | import java.io.IOException; 9 | import java.nio.file.Files; 10 | import java.nio.file.Path; 11 | 12 | public class ClientCommandsScripting implements ModInitializer { 13 | private static final Logger LOGGER = LogUtils.getLogger(); 14 | public static Path configDir; 15 | public static boolean isJsMacrosPresent = FabricLoader.getInstance().isModLoaded("jsmacros"); 16 | 17 | @Override 18 | public void onInitialize() { 19 | configDir = FabricLoader.getInstance().getConfigDir().resolve("clientcommands"); 20 | try { 21 | if (!Files.exists(configDir)) { 22 | Files.createDirectories(configDir); 23 | } 24 | } catch (IOException e) { 25 | LOGGER.error("Unable to create config directory", e); 26 | } 27 | 28 | if (isJsMacrosPresent) { 29 | ScriptManager.inject(); 30 | ScriptManager.reloadLegacyScripts(); 31 | } else { 32 | LOGGER.info("Clientcommands scripts are disabled because jsmacros is not present"); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/net/earthcomputer/clientcommands/script/mixin/MixinKeyboardInput.java: -------------------------------------------------------------------------------- 1 | package net.earthcomputer.clientcommands.script.mixin; 2 | 3 | import net.earthcomputer.clientcommands.script.ClientCommandsScripting; 4 | import net.earthcomputer.clientcommands.script.ScriptManager; 5 | import net.minecraft.client.input.Input; 6 | import net.minecraft.client.input.KeyboardInput; 7 | import org.spongepowered.asm.mixin.Mixin; 8 | import org.spongepowered.asm.mixin.injection.At; 9 | import org.spongepowered.asm.mixin.injection.Inject; 10 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 11 | 12 | @Mixin(KeyboardInput.class) 13 | public class MixinKeyboardInput { 14 | @Inject(method = "tick", at = @At("HEAD"), cancellable = true) 15 | private void preTick(boolean inSneakingPose, CallbackInfo ci) { 16 | if (ClientCommandsScripting.isJsMacrosPresent && ScriptManager.blockingInput()) { 17 | Input _this = (Input) (Object) this; 18 | _this.pressingForward = _this.pressingBack = _this.pressingLeft = _this.pressingRight = _this.jumping = _this.sneaking = false; 19 | ScriptManager.copyScriptInputToPlayer(inSneakingPose); 20 | ci.cancel(); 21 | } 22 | } 23 | 24 | @Inject(method = "tick", at = @At("TAIL")) 25 | private void postTick(boolean inSneakingPose, CallbackInfo ci) { 26 | if (ClientCommandsScripting.isJsMacrosPresent) { 27 | ScriptManager.copyScriptInputToPlayer(inSneakingPose); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/net/earthcomputer/clientcommands/script/mixin/MixinMinecraftClient.java: -------------------------------------------------------------------------------- 1 | package net.earthcomputer.clientcommands.script.mixin; 2 | 3 | import net.earthcomputer.clientcommands.script.ClientCommandsScripting; 4 | import net.earthcomputer.clientcommands.script.ScriptManager; 5 | import net.earthcomputer.clientcommands.script.ducks.IMinecraftClient; 6 | import net.minecraft.client.MinecraftClient; 7 | import org.spongepowered.asm.mixin.Mixin; 8 | import org.spongepowered.asm.mixin.Shadow; 9 | import org.spongepowered.asm.mixin.injection.At; 10 | import org.spongepowered.asm.mixin.injection.Inject; 11 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 12 | 13 | @Mixin(value = MinecraftClient.class, priority = -1000) 14 | public abstract class MixinMinecraftClient implements IMinecraftClient { 15 | @Shadow protected int attackCooldown; 16 | 17 | @Shadow protected abstract void handleBlockBreaking(boolean bl); 18 | 19 | @Inject(method = "handleInputEvents", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/network/ClientPlayerEntity;isUsingItem()Z", ordinal = 0), cancellable = true) 20 | public void onHandleInputEvents(CallbackInfo ci) { 21 | if (ClientCommandsScripting.isJsMacrosPresent) { 22 | if (ScriptManager.blockingInput()) { 23 | ci.cancel(); 24 | } 25 | } 26 | } 27 | 28 | @Override 29 | public void continueBreakingBlock() { 30 | handleBlockBreaking(true); 31 | } 32 | 33 | @Override 34 | public void resetAttackCooldown() { 35 | attackCooldown = 0; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/net/earthcomputer/clientcommands/script/mixin/MixinClientPlayerEntity.java: -------------------------------------------------------------------------------- 1 | package net.earthcomputer.clientcommands.script.mixin; 2 | 3 | import net.earthcomputer.clientcommands.script.ClientCommandsScripting; 4 | import net.earthcomputer.clientcommands.script.ScriptManager; 5 | import net.minecraft.client.MinecraftClient; 6 | import net.minecraft.client.network.ClientPlayerEntity; 7 | import org.spongepowered.asm.mixin.Mixin; 8 | import org.spongepowered.asm.mixin.Unique; 9 | import org.spongepowered.asm.mixin.injection.At; 10 | import org.spongepowered.asm.mixin.injection.Inject; 11 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 12 | 13 | @Mixin(ClientPlayerEntity.class) 14 | public class MixinClientPlayerEntity { 15 | @Unique private boolean wasSprintPressed = false; 16 | 17 | @Inject(method = "tickMovement", at = @At("HEAD")) 18 | public void onStartTickMovement(CallbackInfo ci) { 19 | if (ClientCommandsScripting.isJsMacrosPresent) { 20 | wasSprintPressed = MinecraftClient.getInstance().options.sprintKey.isPressed(); 21 | boolean shouldBeSprinting = (wasSprintPressed && !ScriptManager.blockingInput()) || ScriptManager.isSprinting(); 22 | ((KeyBindingAccessor) MinecraftClient.getInstance().options.sprintKey).setPressed(shouldBeSprinting); 23 | } 24 | } 25 | 26 | @Inject(method = "tickMovement", at = @At("RETURN")) 27 | public void onEndTickMovement(CallbackInfo ci) { 28 | if (ClientCommandsScripting.isJsMacrosPresent) { 29 | ((KeyBindingAccessor) MinecraftClient.getInstance().options.sprintKey).setPressed(wasSprintPressed); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/net/earthcomputer/clientcommands/script/ScriptPosition.java: -------------------------------------------------------------------------------- 1 | package net.earthcomputer.clientcommands.script; 2 | 3 | import net.minecraft.util.math.Vec3d; 4 | 5 | public class ScriptPosition { 6 | 7 | private double x; 8 | private double y; 9 | private double z; 10 | 11 | public ScriptPosition(double x, double y, double z) { 12 | this.x = x; 13 | this.y = y; 14 | this.z = z; 15 | } 16 | 17 | public ScriptPosition(Vec3d pos) { 18 | this(pos.x, pos.y, pos.z); 19 | } 20 | 21 | public double getX() { 22 | return x; 23 | } 24 | 25 | public void setX(double x) { 26 | this.x = x; 27 | } 28 | 29 | public double getY() { 30 | return y; 31 | } 32 | 33 | public void setY(double y) { 34 | this.y = y; 35 | } 36 | 37 | public double getZ() { 38 | return z; 39 | } 40 | 41 | public void setZ(double z) { 42 | this.z = z; 43 | } 44 | 45 | @Override 46 | public String toString() { 47 | return "ScriptPosition{" + 48 | "x=" + x + 49 | ", y=" + y + 50 | ", z=" + z + 51 | '}'; 52 | } 53 | 54 | @Override 55 | public boolean equals(Object o) { 56 | if (this == o) return true; 57 | if (o == null || getClass() != o.getClass()) return false; 58 | 59 | ScriptPosition that = (ScriptPosition) o; 60 | 61 | if (Double.compare(that.x, x) != 0) return false; 62 | if (Double.compare(that.y, y) != 0) return false; 63 | return Double.compare(that.z, z) == 0; 64 | } 65 | 66 | @Override 67 | public int hashCode() { 68 | int result; 69 | long temp; 70 | temp = Double.doubleToLongBits(x); 71 | result = (int) (temp ^ (temp >>> 32)); 72 | temp = Double.doubleToLongBits(y); 73 | result = 31 * result + (int) (temp ^ (temp >>> 32)); 74 | temp = Double.doubleToLongBits(z); 75 | result = 31 * result + (int) (temp ^ (temp >>> 32)); 76 | return result; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/net/earthcomputer/clientcommands/script/ScriptThread.java: -------------------------------------------------------------------------------- 1 | package net.earthcomputer.clientcommands.script; 2 | 3 | import com.google.common.collect.Lists; 4 | 5 | import java.util.Collections; 6 | import java.util.List; 7 | import java.util.concurrent.Callable; 8 | 9 | @SuppressWarnings("unused") 10 | public class ScriptThread { 11 | 12 | final ScriptManager.ThreadInstance thread; 13 | 14 | public ScriptThread(Callable task) { 15 | this(task, true); 16 | } 17 | 18 | public ScriptThread(Callable task, boolean daemon) { 19 | this.thread = ScriptManager.createThread(this, task, daemon); 20 | } 21 | 22 | public static ScriptThread current() { 23 | ScriptManager.ThreadInstance thread = ScriptManager.currentThread(); 24 | return thread == null ? null : thread.handle; 25 | } 26 | 27 | public static ScriptThread getCurrent() { 28 | return current(); 29 | } 30 | 31 | public boolean isRunning() { 32 | return thread.running && !thread.isKilled(); 33 | } 34 | 35 | public boolean isPaused() { 36 | return thread.paused; 37 | } 38 | 39 | public boolean isDaemon() { 40 | return thread.daemon; 41 | } 42 | 43 | public ScriptThread getParent() { 44 | return thread.parent == null || thread.parent.isKilled() ? null : thread.parent.handle; 45 | } 46 | 47 | public List getChildren() { 48 | //noinspection StaticPseudoFunctionalStyleMethod 49 | return Collections.unmodifiableList(Lists.transform(thread.children, thread -> thread.handle)); 50 | } 51 | 52 | public void run() { 53 | if (!thread.running) 54 | ScriptManager.runThread(thread, false); 55 | } 56 | 57 | public void pause() { 58 | thread.paused = true; 59 | } 60 | 61 | public void unpause() { 62 | thread.paused = false; 63 | } 64 | 65 | public void kill() { 66 | thread.kill(); 67 | } 68 | 69 | public void waitFor() { 70 | if (thread == ScriptManager.currentThread()) 71 | throw new IllegalStateException(); 72 | while (thread.running) 73 | ScriptManager.passTick(); 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # User-specific stuff 2 | .idea/ 3 | 4 | *.iml 5 | *.ipr 6 | *.iws 7 | 8 | # IntelliJ 9 | out/ 10 | # mpeltonen/sbt-idea plugin 11 | .idea_modules/ 12 | 13 | # JIRA plugin 14 | atlassian-ide-plugin.xml 15 | 16 | # Compiled class file 17 | *.class 18 | 19 | # Log file 20 | *.log 21 | 22 | # BlueJ files 23 | *.ctxt 24 | 25 | # Package Files # 26 | *.jar 27 | *.war 28 | *.nar 29 | *.ear 30 | *.zip 31 | *.tar.gz 32 | *.rar 33 | 34 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 35 | hs_err_pid* 36 | 37 | *~ 38 | 39 | # temporary files which can be created if a process still has a handle open of a deleted file 40 | .fuse_hidden* 41 | 42 | # KDE directory preferences 43 | .directory 44 | 45 | # Linux trash folder which might appear on any partition or disk 46 | .Trash-* 47 | 48 | # .nfs files are created when an open file is removed but is still being accessed 49 | .nfs* 50 | 51 | # General 52 | .DS_Store 53 | .AppleDouble 54 | .LSOverride 55 | 56 | # Icon must end with two \r 57 | Icon 58 | 59 | # Thumbnails 60 | ._* 61 | 62 | # Files that might appear in the root of a volume 63 | .DocumentRevisions-V100 64 | .fseventsd 65 | .Spotlight-V100 66 | .TemporaryItems 67 | .Trashes 68 | .VolumeIcon.icns 69 | .com.apple.timemachine.donotpresent 70 | 71 | # Directories potentially created on remote AFP share 72 | .AppleDB 73 | .AppleDesktop 74 | Network Trash Folder 75 | Temporary Items 76 | .apdisk 77 | 78 | # Windows thumbnail cache files 79 | Thumbs.db 80 | Thumbs.db:encryptable 81 | ehthumbs.db 82 | ehthumbs_vista.db 83 | 84 | # Dump file 85 | *.stackdump 86 | 87 | # Folder config file 88 | [Dd]esktop.ini 89 | 90 | # Recycle Bin used on file shares 91 | $RECYCLE.BIN/ 92 | 93 | # Windows Installer files 94 | *.cab 95 | *.msi 96 | *.msix 97 | *.msm 98 | *.msp 99 | 100 | # Windows shortcuts 101 | *.lnk 102 | 103 | .gradle 104 | build/ 105 | 106 | # Ignore Gradle GUI config 107 | gradle-app.setting 108 | 109 | # Cache of project 110 | .gradletasknamecache 111 | 112 | **/build/ 113 | 114 | # Common working directory 115 | run/ 116 | 117 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) 118 | !gradle-wrapper.jar 119 | -------------------------------------------------------------------------------- /src/main/java/net/earthcomputer/clientcommands/script/ClientCommandsLibrary.java: -------------------------------------------------------------------------------- 1 | package net.earthcomputer.clientcommands.script; 2 | 3 | import net.minecraft.client.MinecraftClient; 4 | import org.graalvm.polyglot.Value; 5 | import xyz.wagyourtail.jsmacros.core.language.impl.JavascriptLanguageDefinition; 6 | import xyz.wagyourtail.jsmacros.core.library.BaseLibrary; 7 | import xyz.wagyourtail.jsmacros.core.library.Library; 8 | 9 | import java.util.concurrent.Callable; 10 | 11 | @SuppressWarnings("unused") 12 | @Library(value = "cc", languages = JavascriptLanguageDefinition.class) 13 | public class ClientCommandsLibrary extends BaseLibrary { 14 | public Object exec(String command) { 15 | return ScriptBuiltins.exec(command); 16 | } 17 | 18 | public void print(String message) { 19 | ScriptBuiltins.print(message); 20 | } 21 | 22 | public void chat(String message) { 23 | ScriptBuiltins.chat(message); 24 | } 25 | 26 | public void tick() { 27 | ScriptManager.passTick(); 28 | } 29 | 30 | public boolean isLoggedIn() { 31 | return MinecraftClient.getInstance().player != null; 32 | } 33 | 34 | public Object player = BeanWrapper.wrap(ScriptPlayer.INSTANCE); 35 | public Object world = BeanWrapper.wrap(ScriptWorld.INSTANCE); 36 | 37 | public ThreadLibrary Thread = new ThreadLibrary(); 38 | public BlockStateLibrary BlockState = new BlockStateLibrary(); 39 | public ItemStackLibrary ItemStack = new ItemStackLibrary(); 40 | 41 | public static class ThreadLibrary extends BaseLibrary { 42 | public ScriptThread current() { 43 | return ScriptThread.current(); 44 | } 45 | 46 | public ScriptThread create(Callable task) { 47 | return new ScriptThread(task); 48 | } 49 | 50 | public ScriptThread create(Callable task, boolean daemon) { 51 | return new ScriptThread(task, daemon); 52 | } 53 | } 54 | 55 | public static class BlockStateLibrary extends BaseLibrary { 56 | public Object defaultState(String block) { 57 | return ScriptBlockState.defaultState(block); 58 | } 59 | } 60 | 61 | public static class ItemStackLibrary extends BaseLibrary { 62 | public Object of(Value obj) { 63 | return ScriptItemStack.of(obj); 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/net/earthcomputer/clientcommands/script/ClientCommandsLanguage.java: -------------------------------------------------------------------------------- 1 | package net.earthcomputer.clientcommands.script; 2 | 3 | import org.graalvm.polyglot.Context; 4 | import org.graalvm.polyglot.Engine; 5 | import org.graalvm.polyglot.HostAccess; 6 | import org.graalvm.polyglot.Value; 7 | import xyz.wagyourtail.jsmacros.client.JsMacros; 8 | import xyz.wagyourtail.jsmacros.core.Core; 9 | import xyz.wagyourtail.jsmacros.core.language.BaseScriptContext; 10 | import xyz.wagyourtail.jsmacros.core.language.impl.JavascriptLanguageDefinition; 11 | import xyz.wagyourtail.jsmacros.core.library.BaseLibrary; 12 | 13 | import java.io.File; 14 | import java.io.IOException; 15 | import java.util.Map; 16 | 17 | public class ClientCommandsLanguage extends JavascriptLanguageDefinition { 18 | private static final Engine engine = Engine.create(); 19 | private static final JavascriptLanguageDefinition jsLanguage = JsMacros.core.languages.stream() 20 | .filter(it -> it.getClass() == JavascriptLanguageDefinition.class) 21 | .findFirst() 22 | .map(it -> (JavascriptLanguageDefinition) it) 23 | .orElseThrow(AssertionError::new); 24 | 25 | public ClientCommandsLanguage(String extension, Core runner) { 26 | super(extension, runner); 27 | } 28 | 29 | @Override 30 | protected Context buildContext(File currentDir, Map extraJsOptions, Map globals, Map libs) throws IOException { 31 | Context.Builder build = Context.newBuilder("js") 32 | .engine(engine) 33 | .allowHostAccess(HostAccess.ALL) 34 | .allowHostClassLookup(s -> true) 35 | .allowAllAccess(true) 36 | .allowIO(true) 37 | .allowExperimentalOptions(true) 38 | .option("js.commonjs-require", "true") 39 | .option("js.nashorn-compat", "true") 40 | .option("js.ecmascript-version", "2022"); 41 | 42 | build.options(extraJsOptions); 43 | if (currentDir == null) { 44 | currentDir = runner.config.macroFolder; 45 | } 46 | build.currentWorkingDirectory(currentDir.toPath()); 47 | build.option("js.commonjs-require-cwd", currentDir.getCanonicalPath()); 48 | 49 | final Context con = build.build(); 50 | 51 | // Set Bindings 52 | final Value binds = con.getBindings("js"); 53 | 54 | globals.putAll(ScriptBuiltins.getGlobalFunctions()); 55 | globals.putAll(ScriptBuiltins.getGlobalVars()); 56 | ScriptBuiltins.getGlobalTypes().forEach((name, clazz) -> { 57 | globals.put(name, con.eval("js", "Java.type('" + clazz.getName() + "')")); 58 | }); 59 | 60 | globals.forEach(binds::putMember); 61 | libs.forEach(binds::putMember); 62 | 63 | return con; 64 | } 65 | 66 | @Override 67 | public Map retrieveLibs(BaseScriptContext context) { 68 | return jsLanguage.retrieveLibs(context); 69 | } 70 | 71 | @Override 72 | public Map retrieveOnceLibs() { 73 | return jsLanguage.retrieveOnceLibs(); 74 | } 75 | 76 | @Override 77 | public Map retrievePerExecLibs(BaseScriptContext context) { 78 | return jsLanguage.retrievePerExecLibs(context); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Clientcommands Scripting 2 | This repository is the scripting module of [clientcommands](https://github.com/Earthcomputer/clientcommands). This mod is distributed as part of clientcommands, so please refer to clientcommands' readme for installation instructions. 3 | 4 | ## Deprecated for Removal 5 | 6 | Clientcommands Scripting is deprecated for removal in 1.19. [JSMacros](https://github.com/wagyourtail/JSMacros) is the recommended replacement. 7 | 8 | ## Contributing 9 | As this mod is a submodule of clientcommands, you must clone it as a submodule of clientcommands and work with it that way. 10 | 11 | 1. Clone the clientcommands repository and submodules 12 | ``` 13 | git clone --recursive-submodules https://github.com/Earthcomputer/clientcommands 14 | cd clientcommands 15 | ``` 16 | 1. Generate the Minecraft source code 17 | ``` 18 | ./gradlew genSources 19 | ``` 20 | - Note: on Windows, use `gradlew` rather than `./gradlew`. 21 | 1. Import the clientcommands project into your preferred IDE. 22 | 1. If you use IntelliJ (the preferred option), you can simply import the project as a Gradle project. 23 | 1. If you use Eclipse, you need to `./gradlew eclipse` before importing the project as an Eclipse project. 24 | 1. After importing, you should see the clientcommands module in the root project directory, and the clientcommands-scripting module under the `clientcommands-scripting` directory. Most of the time you will be editing files inside the clientcommands-scripting module, but feel free to edit files inside clientcommands if required for your feature. 25 | 1. Code inside clientcommands cannot reference code inside clientcommands-scripting, but code inside clientcommands-scripting can reference code inside clientcommands. 26 | 1. To test clientcommands-scripting inside the IDE, you need to edit the run configuration that was generated by Fabric loom to run the clientcommands-scripting module. In IntelliJ, the option can be found here: 27 | ![Image showing where to edit the module of a run configuration](https://user-images.githubusercontent.com/13084089/124387081-10077100-dcd5-11eb-826e-c84f717eafcc.png) 28 | 1. After testing in the IDE, build a JAR to test whether it works outside the IDE too. Build clientcommands by running the following command in the root project directory 29 | ``` 30 | ./gradlew build 31 | ``` 32 | The mod JAR may be found in the `build/libs` directory 33 | - Note: the build is currently slightly broken, in that you may need to run this command twice before the build is successful. So if the build fails, just run it again and it should hopefully succeed the second time. 34 | 1. [Create a pull request](https://help.github.com/en/articles/creating-a-pull-request) 35 | so that your changes can be integrated into Clientcommands Scripting 36 | - Note: for large contributions, create an issue before doing all that 37 | work, to ask whether your pull request is likely to be accepted 38 | - Fork the Clientcommands Scripting repository and run `git push master` inside the `clientcommands-scripting` directory (not the root project directory). 39 | - If you made changes to clientcommands itself, fork clientcommands and push in the root project directory. Make clear in the pull request descriptions that the two are linked. Do not update the clientcommands scripting submodule in your clientcommands pull request, that will be done separately after the two are merged. 40 | -------------------------------------------------------------------------------- /src/main/java/net/earthcomputer/clientcommands/script/BeanWrapper.java: -------------------------------------------------------------------------------- 1 | package net.earthcomputer.clientcommands.script; 2 | 3 | import org.apache.commons.lang3.StringUtils; 4 | import org.graalvm.polyglot.Context; 5 | import org.graalvm.polyglot.Value; 6 | import org.graalvm.polyglot.proxy.ProxyObject; 7 | 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | 11 | public class BeanWrapper implements ProxyObject { 12 | private final Object delegate; 13 | 14 | private BeanWrapper(Object delegate) { 15 | this.delegate = delegate; 16 | } 17 | 18 | public static Object wrap(Object javaBean) { 19 | if (javaBean == null) { 20 | return null; 21 | } 22 | if (javaBean instanceof BeanWrapper) { 23 | return javaBean; 24 | } 25 | if (javaBean instanceof Value) { 26 | Value value = (Value) javaBean; 27 | if (value.isHostObject() && value.asHostObject() instanceof BeanWrapper) { 28 | return javaBean; 29 | } 30 | } 31 | return new BeanWrapper(javaBean); 32 | } 33 | 34 | Value getDelegate() { 35 | return Context.getCurrent().asValue(this.delegate); 36 | } 37 | 38 | @Override 39 | public Object getMember(String key) { 40 | Value delegate = Context.getCurrent().asValue(this.delegate); 41 | Value getter = findGetter(delegate, key); 42 | if (getter != null && getter.canExecute()) { 43 | return getter.execute(); 44 | } else { 45 | return delegate.getMember(key); 46 | } 47 | } 48 | 49 | @Override 50 | public Object getMemberKeys() { 51 | Value delegate = Context.getCurrent().asValue(this.delegate); 52 | List memberKeys = new ArrayList<>(); 53 | for (String memberKey : delegate.getMemberKeys()) { 54 | memberKeys.add(memberKey); 55 | if (memberKey.startsWith("get") && memberKey.length() > 3 && Character.isUpperCase(memberKey.charAt(3))) { 56 | memberKeys.add(StringUtils.uncapitalize(memberKey.substring(3))); 57 | } else if (memberKey.startsWith("is") && memberKey.length() > 2 && Character.isUpperCase(memberKey.charAt(2))) { 58 | memberKeys.add(StringUtils.uncapitalize(memberKey.substring(2))); 59 | } 60 | } 61 | return memberKeys; 62 | } 63 | 64 | @Override 65 | public boolean hasMember(String key) { 66 | Value delegate = Context.getCurrent().asValue(this.delegate); 67 | return delegate.hasMember(key) || findGetter(delegate, key) != null; 68 | } 69 | 70 | @Override 71 | public void putMember(String key, Value value) { 72 | Value delegate = Context.getCurrent().asValue(this.delegate); 73 | Value setter = findSetter(delegate, key); 74 | if (setter != null && setter.canExecute()) { 75 | setter.execute(value); 76 | } else { 77 | delegate.putMember(key, value); 78 | } 79 | } 80 | 81 | private Value findGetter(Value delegate, String propertyName) { 82 | if (Character.isUpperCase(propertyName.charAt(0))) { 83 | return null; 84 | } 85 | propertyName = StringUtils.capitalize(propertyName); 86 | Value getter = delegate.getMember("get" + propertyName); 87 | if (getter != null) { 88 | return getter; 89 | } 90 | return delegate.getMember("is" + propertyName); 91 | } 92 | 93 | private Value findSetter(Value delegate, String propertyName) { 94 | if (Character.isUpperCase(propertyName.charAt(0))) { 95 | return null; 96 | } 97 | return delegate.getMember("set" + StringUtils.capitalize(propertyName)); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/main/java/net/earthcomputer/clientcommands/script/ScriptItemStack.java: -------------------------------------------------------------------------------- 1 | package net.earthcomputer.clientcommands.script; 2 | 3 | import net.minecraft.client.MinecraftClient; 4 | import net.minecraft.client.network.ClientPlayNetworkHandler; 5 | import net.minecraft.item.FoodComponent; 6 | import net.minecraft.item.ItemStack; 7 | import net.minecraft.nbt.NbtCompound; 8 | import net.minecraft.nbt.NbtElement; 9 | import net.minecraft.util.Identifier; 10 | import net.minecraft.util.registry.Registry; 11 | import org.graalvm.polyglot.Value; 12 | 13 | import java.util.ArrayList; 14 | import java.util.List; 15 | 16 | @SuppressWarnings("unused") 17 | public class ScriptItemStack { 18 | private final ItemStack stack; 19 | 20 | ScriptItemStack(ItemStack stack) { 21 | this.stack = stack; 22 | } 23 | 24 | static ScriptItemStack ofUnchecked(Value obj) { 25 | NbtElement itemNbt = ScriptUtil.toNbt(obj); 26 | if (!(itemNbt instanceof NbtCompound)) { 27 | Identifier itemId = new Identifier(ScriptUtil.asString(obj)); 28 | if (!Registry.ITEM.containsId(itemId)) 29 | throw new IllegalArgumentException("Cannot convert " + obj + " to item"); 30 | return new ScriptItemStack(new ItemStack(Registry.ITEM.get(itemId))); 31 | } 32 | ItemStack stack = ItemStack.fromNbt((NbtCompound) itemNbt); 33 | return new ScriptItemStack(stack); 34 | } 35 | 36 | public static Object of(Value obj) { 37 | return BeanWrapper.wrap(ofUnchecked(obj)); 38 | } 39 | 40 | public Object getStack() { 41 | return ScriptUtil.fromNbtCompound(stack.writeNbt(new NbtCompound())); 42 | } 43 | 44 | public float getMiningSpeed(String block) { 45 | return getMiningSpeed(ScriptBlockState.uncheckedDefaultState(block)); 46 | } 47 | 48 | public float getMiningSpeed(ScriptBlockState block) { 49 | return stack.getMiningSpeedMultiplier(block.state); 50 | } 51 | 52 | public boolean isEffectiveOn(String block) { 53 | return isEffectiveOn(ScriptBlockState.uncheckedDefaultState(block)); 54 | } 55 | 56 | public boolean isEffectiveOn(ScriptBlockState block) { 57 | return stack.isSuitableFor(block.state); 58 | } 59 | 60 | public int getMaxCount() { 61 | return stack.getMaxCount(); 62 | } 63 | 64 | public int getMaxDamage() { 65 | return stack.getMaxDamage(); 66 | } 67 | 68 | public boolean isIsFood() { 69 | return stack.isFood(); 70 | } 71 | 72 | public int getHungerRestored() { 73 | FoodComponent food = stack.getItem().getFoodComponent(); 74 | return food == null ? 0 : food.getHunger(); 75 | } 76 | 77 | public float getSaturationRestored() { 78 | FoodComponent food = stack.getItem().getFoodComponent(); 79 | return food == null ? 0 : food.getSaturationModifier(); 80 | } 81 | 82 | public boolean isIsMeat() { 83 | FoodComponent food = stack.getItem().getFoodComponent(); 84 | return food != null && food.isMeat(); 85 | } 86 | 87 | public boolean isAlwaysEdible() { 88 | FoodComponent food = stack.getItem().getFoodComponent(); 89 | return food != null && food.isAlwaysEdible(); 90 | } 91 | 92 | public boolean isIsSnack() { 93 | FoodComponent food = stack.getItem().getFoodComponent(); 94 | return food != null && food.isSnack(); 95 | } 96 | 97 | public List getTags() { 98 | ClientPlayNetworkHandler networkHandler = MinecraftClient.getInstance().getNetworkHandler(); 99 | if (networkHandler == null) { 100 | return new ArrayList<>(); 101 | } 102 | return stack.streamTags().map(tag -> ScriptUtil.simplifyIdentifier(tag.id())).toList(); 103 | } 104 | 105 | } 106 | -------------------------------------------------------------------------------- /src/main/java/net/earthcomputer/clientcommands/script/ScriptEntity.java: -------------------------------------------------------------------------------- 1 | package net.earthcomputer.clientcommands.script; 2 | 3 | import net.minecraft.client.MinecraftClient; 4 | import net.minecraft.client.world.ClientWorld; 5 | import net.minecraft.entity.Entity; 6 | import net.minecraft.entity.LivingEntity; 7 | import net.minecraft.nbt.NbtCompound; 8 | import net.minecraft.util.registry.Registry; 9 | 10 | import java.lang.ref.WeakReference; 11 | 12 | @SuppressWarnings("unused") 13 | public class ScriptEntity { 14 | 15 | private WeakReference entity; 16 | private int entityId; 17 | 18 | static Object create(Entity entity) { 19 | return BeanWrapper.wrap(createUnchecked(entity)); 20 | } 21 | 22 | static ScriptEntity createUnchecked(Entity entity) { 23 | if (entity == MinecraftClient.getInstance().player) { 24 | return new ScriptPlayer(); 25 | } else if (entity instanceof LivingEntity) { 26 | return new ScriptLivingEntity((LivingEntity) entity); 27 | } else { 28 | return new ScriptEntity(entity); 29 | } 30 | } 31 | 32 | ScriptEntity(Entity entity) { 33 | if (entity != null) { 34 | this.entity = new WeakReference<>(entity); 35 | this.entityId = entity.getId(); 36 | } 37 | } 38 | 39 | Entity getNullableEntity() { 40 | ClientWorld theWorld = MinecraftClient.getInstance().world; 41 | if (this.entity == null || theWorld == null) { 42 | return null; 43 | } 44 | Entity entity = this.entity.get(); 45 | if (entity == null || entity.world != theWorld) { 46 | entity = theWorld.getEntityById(entityId); 47 | if (entity != null) { 48 | this.entity = new WeakReference<>(entity); 49 | } 50 | } 51 | if (entity != null && entity.isRemoved()) { 52 | entity = null; 53 | } 54 | if (entity == null) { 55 | this.entity = null; 56 | } 57 | return entity; 58 | } 59 | 60 | Entity getEntity() { 61 | Entity entity = getNullableEntity(); 62 | if (entity == null) { 63 | throw new NullPointerException("Invalid entity reference"); 64 | } 65 | return entity; 66 | } 67 | 68 | public boolean isValid() { 69 | return getNullableEntity() != null; 70 | } 71 | 72 | public String getType() { 73 | return ScriptUtil.simplifyIdentifier(Registry.ENTITY_TYPE.getId(getEntity().getType())); 74 | } 75 | 76 | public double getX() { 77 | return getEntity().getX(); 78 | } 79 | 80 | public double getY() { 81 | return getEntity().getY(); 82 | } 83 | 84 | public double getZ() { 85 | return getEntity().getZ(); 86 | } 87 | 88 | public float getYaw() { 89 | return getEntity().getYaw(); 90 | } 91 | 92 | public float getPitch() { 93 | return getEntity().getPitch(); 94 | } 95 | 96 | public double getMotionX() { 97 | return getEntity().getVelocity().x; 98 | } 99 | 100 | public double getMotionY() { 101 | return getEntity().getVelocity().y; 102 | } 103 | 104 | public double getMotionZ() { 105 | return getEntity().getVelocity().z; 106 | } 107 | 108 | public Object getNbt() { 109 | return ScriptUtil.fromNbt(getEntity().writeNbt(new NbtCompound())); 110 | } 111 | 112 | @Override 113 | public int hashCode() { 114 | return entity.hashCode(); 115 | } 116 | 117 | @Override 118 | public boolean equals(Object o) { 119 | if (this == o) return true; 120 | if (!(o instanceof ScriptEntity)) return false; 121 | return entity.equals(((ScriptEntity) o).entity); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/main/java/net/earthcomputer/clientcommands/script/ScriptWorld.java: -------------------------------------------------------------------------------- 1 | package net.earthcomputer.clientcommands.script; 2 | 3 | import net.earthcomputer.clientcommands.MathUtil; 4 | import net.minecraft.block.Block; 5 | import net.minecraft.block.BlockState; 6 | import net.minecraft.block.entity.BlockEntity; 7 | import net.minecraft.client.MinecraftClient; 8 | import net.minecraft.client.network.ClientPlayNetworkHandler; 9 | import net.minecraft.client.world.ClientWorld; 10 | import net.minecraft.entity.player.PlayerEntity; 11 | import net.minecraft.util.hit.BlockHitResult; 12 | import net.minecraft.util.hit.HitResult; 13 | import net.minecraft.util.math.BlockPos; 14 | import net.minecraft.util.math.Direction; 15 | import net.minecraft.util.math.Vec3d; 16 | import net.minecraft.util.registry.Registry; 17 | import net.minecraft.world.LightType; 18 | 19 | @SuppressWarnings("unused") 20 | public class ScriptWorld { 21 | static final ScriptWorld INSTANCE = new ScriptWorld(); 22 | 23 | ScriptWorld() {} 24 | 25 | private static ClientWorld getWorld() { 26 | return MinecraftClient.getInstance().world; 27 | } 28 | 29 | public String getDimension() { 30 | ClientPlayNetworkHandler networkHandler = MinecraftClient.getInstance().getNetworkHandler(); 31 | assert networkHandler != null; 32 | return ScriptUtil.simplifyIdentifier(networkHandler.getRegistryManager().get(Registry.DIMENSION_TYPE_KEY).getId(getWorld().getDimension())); 33 | } 34 | 35 | public String getBlock(int x, int y, int z) { 36 | Block block = getWorld().getBlockState(new BlockPos(x, y, z)).getBlock(); 37 | return ScriptUtil.simplifyIdentifier(Registry.BLOCK.getId(block)); 38 | } 39 | 40 | public Object getBlockProperty(int x, int y, int z, String property) { 41 | return getBlockStateUnchecked(x, y, z).getProperty(property); 42 | } 43 | 44 | ScriptBlockState getBlockStateUnchecked(int x, int y, int z) { 45 | return new ScriptBlockState(getWorld().getBlockState(new BlockPos(x, y, z))); 46 | } 47 | 48 | public Object getBlockState(int x, int y, int z) { 49 | return BeanWrapper.wrap(getBlockStateUnchecked(x, y, z)); 50 | } 51 | 52 | public Object getBlockEntityNbt(int x, int y, int z) { 53 | BlockEntity be = getWorld().getBlockEntity(new BlockPos(x, y, z)); 54 | if (be == null) 55 | return null; 56 | return ScriptUtil.fromNbt(be.createNbt()); 57 | } 58 | 59 | public int getBlockLight(int x, int y, int z) { 60 | return getWorld().getLightLevel(LightType.BLOCK, new BlockPos(x, y, z)); 61 | } 62 | 63 | public int getSkyLight(int x, int y, int z) { 64 | return getWorld().getLightLevel(LightType.SKY, new BlockPos(x, y, z)); 65 | } 66 | 67 | public Object getClosestVisiblePoint(int x, int y, int z) { 68 | return getClosestVisiblePoint(x, y, z, null); 69 | } 70 | 71 | public Object getClosestVisiblePoint(int x, int y, int z, String side) { 72 | Vec3d ret = getClosestVisiblePoint0(x, y, z, side, false); 73 | return ret == null ? null : BeanWrapper.wrap(new ScriptPosition(ret)); 74 | } 75 | 76 | static Vec3d getClosestVisiblePoint0(int x, int y, int z, String side, boolean keepExistingHitResult) { 77 | BlockPos pos = new BlockPos(x, y, z); 78 | Direction dir = ScriptUtil.getDirectionFromString(side); 79 | 80 | ClientWorld world = MinecraftClient.getInstance().world; 81 | BlockState state = world.getBlockState(pos); 82 | if (state.isAir()) 83 | return null; 84 | PlayerEntity player = MinecraftClient.getInstance().player; 85 | Vec3d origin = player.getCameraPosVec(0); 86 | HitResult hitResult = MinecraftClient.getInstance().crosshairTarget; 87 | Vec3d closestPos; 88 | if (keepExistingHitResult && hitResult.getType() == HitResult.Type.BLOCK && ((BlockHitResult) hitResult).getBlockPos().equals(pos)) { 89 | closestPos = hitResult.getPos(); 90 | } else { 91 | closestPos = MathUtil.getClosestVisiblePoint(world, pos, origin, player, dir); 92 | } 93 | if (closestPos != null && origin.squaredDistanceTo(closestPos) > 6 * 6) { 94 | return null; 95 | } 96 | return closestPos; 97 | } 98 | 99 | } 100 | -------------------------------------------------------------------------------- /src/main/java/net/earthcomputer/clientcommands/script/ScriptBuiltins.java: -------------------------------------------------------------------------------- 1 | package net.earthcomputer.clientcommands.script; 2 | 3 | import com.google.common.collect.ImmutableMap; 4 | import com.mojang.brigadier.StringReader; 5 | import com.mojang.brigadier.exceptions.CommandSyntaxException; 6 | import dev.xpple.clientarguments.arguments.CEntityArgumentType; 7 | import dev.xpple.clientarguments.arguments.CEntitySelector; 8 | import net.fabricmc.fabric.api.client.command.v1.FabricClientCommandSource; 9 | import net.fabricmc.fabric.impl.command.client.ClientCommandInternals; 10 | import net.minecraft.client.MinecraftClient; 11 | import net.minecraft.client.network.ClientCommandSource; 12 | import net.minecraft.client.network.ClientPlayerEntity; 13 | import net.minecraft.entity.Entity; 14 | import net.minecraft.text.LiteralText; 15 | 16 | import java.util.ArrayList; 17 | import java.util.List; 18 | import java.util.Map; 19 | import java.util.Objects; 20 | import java.util.function.BooleanSupplier; 21 | import java.util.function.Consumer; 22 | import java.util.function.Function; 23 | 24 | class ScriptBuiltins { 25 | private static final Map GLOBAL_FUNCTIONS = ImmutableMap.builder() 26 | .put("$", (Function) ScriptBuiltins::exec) 27 | .put("print", (Consumer) ScriptBuiltins::print) 28 | .put("chat", (Consumer) ScriptBuiltins::chat) 29 | .put("tick", (Runnable) ScriptManager::passTick) 30 | .put("isLoggedIn", (BooleanSupplier) () -> MinecraftClient.getInstance().player != null) 31 | .build(); 32 | 33 | private static final Map GLOBAL_VARS = ImmutableMap.builder() 34 | .put("player", ScriptPlayer.INSTANCE) 35 | .put("world", ScriptWorld.INSTANCE) 36 | .build(); 37 | 38 | private static final Map> GLOBAL_TYPES = ImmutableMap.>builder() 39 | .put("Thread", ScriptThread.class) 40 | .put("BlockState", ScriptBlockState.class) 41 | .put("ItemStack", ScriptItemStack.class) 42 | .build(); 43 | 44 | public static Map getGlobalFunctions() { 45 | return GLOBAL_FUNCTIONS; 46 | } 47 | 48 | public static Map getGlobalVars() { 49 | return GLOBAL_VARS; 50 | } 51 | 52 | public static Map> getGlobalTypes() { 53 | return GLOBAL_TYPES; 54 | } 55 | 56 | public static Object exec(String command) { 57 | if (MinecraftClient.getInstance().player == null) { 58 | throw new IllegalStateException("Not ingame"); 59 | } 60 | if (command.startsWith("@")) { 61 | StringReader reader = new StringReader(command); 62 | try { 63 | CEntitySelector selector = CEntityArgumentType.entities().parse(reader); 64 | if (reader.getRemainingLength() != 0) 65 | throw CommandSyntaxException.BUILT_IN_EXCEPTIONS.dispatcherUnknownCommand().createWithContext(reader); 66 | //noinspection ConstantConditions 67 | List entities = selector.getEntities((FabricClientCommandSource) new ClientCommandSource(MinecraftClient.getInstance().getNetworkHandler(), MinecraftClient.getInstance())); 68 | List ret = new ArrayList<>(entities.size()); 69 | for (Entity entity : entities) 70 | ret.add(ScriptEntity.create(entity)); 71 | return ret; 72 | } catch (CommandSyntaxException e) { 73 | throw new IllegalArgumentException("Invalid selector syntax", e); 74 | } 75 | } 76 | return ClientCommandInternals.executeCommand(command); 77 | } 78 | 79 | public static void print(String message) { 80 | if (MinecraftClient.getInstance().player == null) { 81 | throw new IllegalStateException("Not ingame"); 82 | } 83 | Objects.requireNonNull(message, "message"); 84 | MinecraftClient.getInstance().inGameHud.getChatHud().addMessage(new LiteralText(message)); 85 | } 86 | 87 | public static void chat(String message) { 88 | ClientPlayerEntity player = MinecraftClient.getInstance().player; 89 | if (player == null) { 90 | throw new IllegalStateException("Not ingame"); 91 | } 92 | Objects.requireNonNull(message, "message"); 93 | player.sendChatMessage(message); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/main/java/net/earthcomputer/clientcommands/script/ScriptCommand.java: -------------------------------------------------------------------------------- 1 | package net.earthcomputer.clientcommands.script; 2 | 3 | import com.mojang.brigadier.CommandDispatcher; 4 | import com.mojang.brigadier.exceptions.CommandSyntaxException; 5 | import com.mojang.brigadier.exceptions.SimpleCommandExceptionType; 6 | import net.fabricmc.fabric.api.client.command.v1.FabricClientCommandSource; 7 | import net.minecraft.command.CommandSource; 8 | import net.minecraft.text.ClickEvent; 9 | import net.minecraft.text.HoverEvent; 10 | import net.minecraft.text.LiteralText; 11 | import net.minecraft.text.TranslatableText; 12 | import net.minecraft.util.Formatting; 13 | 14 | import static com.mojang.brigadier.arguments.StringArgumentType.*; 15 | import static net.earthcomputer.clientcommands.command.ClientCommandHelper.*; 16 | import static net.fabricmc.fabric.api.client.command.v1.ClientCommandManager.*; 17 | 18 | public class ScriptCommand { 19 | private static final String JSMACROS_URL = "https://www.curseforge.com/minecraft/mc-mods/jsmacros"; 20 | private static final SimpleCommandExceptionType NO_JSMACROS_EXCEPTION = new SimpleCommandExceptionType(new TranslatableText("commands.cscript.nojsmacros", 21 | new TranslatableText("commands.cscript.nojsmacros.link") 22 | .styled(style -> style.withClickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, JSMACROS_URL)) 23 | .withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new LiteralText(JSMACROS_URL))) 24 | .withUnderline(true)))); 25 | private static boolean warnedDeprecated = false; 26 | 27 | public static void register(CommandDispatcher dispatcher) { 28 | dispatcher.register(literal("cscript") 29 | .then(literal("reload") 30 | .executes(ctx -> reloadScripts())) 31 | .then(literal("run") 32 | .then(argument("script", string()) 33 | .suggests((ctx, builder) -> { 34 | if (!ClientCommandsScripting.isJsMacrosPresent) { 35 | return builder.buildFuture(); 36 | } 37 | return CommandSource.suggestMatching(ScriptManager.getLegacyScriptNames(), builder); 38 | }) 39 | .executes(ctx -> runLegacyScript(getString(ctx, "script"))))) 40 | .then(literal("exec") 41 | .then(argument("script", string()) 42 | .suggests(ClientCommandsScripting.isJsMacrosPresent ? ScriptManager.getScriptSuggestions() : (ctx, builder) -> builder.buildFuture()) 43 | .executes(ctx -> execScript(getString(ctx, "script")))))); 44 | } 45 | 46 | private static int reloadScripts() throws CommandSyntaxException { 47 | if (!ClientCommandsScripting.isJsMacrosPresent) { 48 | throw NO_JSMACROS_EXCEPTION.create(); 49 | } 50 | warnDeprecated(); 51 | ScriptManager.reloadLegacyScripts(); 52 | sendFeedback("commands.cscript.reload.success"); 53 | return ScriptManager.getLegacyScriptNames().size(); 54 | } 55 | 56 | private static int runLegacyScript(String name) throws CommandSyntaxException { 57 | if (!ClientCommandsScripting.isJsMacrosPresent) { 58 | throw NO_JSMACROS_EXCEPTION.create(); 59 | } 60 | warnDeprecated(); 61 | ScriptManager.executeLegacyScript(name); 62 | sendFeedback("commands.cscript.run.success"); 63 | return 0; 64 | } 65 | 66 | private static int execScript(String name) throws CommandSyntaxException { 67 | if (!ClientCommandsScripting.isJsMacrosPresent) { 68 | throw NO_JSMACROS_EXCEPTION.create(); 69 | } 70 | warnDeprecated(); 71 | ScriptManager.executeScript(name); 72 | sendFeedback("commands.cscript.run.success"); 73 | return 0; 74 | } 75 | 76 | private static void warnDeprecated() { 77 | if (!warnedDeprecated) { 78 | warnedDeprecated = true; 79 | sendFeedback(new TranslatableText("commands.cscript.deprecated") 80 | .append(new TranslatableText("commands.cscript.nojsmacros.link") 81 | .styled(style -> style.withClickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, JSMACROS_URL)) 82 | .withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new LiteralText(JSMACROS_URL))) 83 | .withUnderline(true))) 84 | .formatted(Formatting.YELLOW)); 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/main/java/net/earthcomputer/clientcommands/script/ScriptBlockState.java: -------------------------------------------------------------------------------- 1 | package net.earthcomputer.clientcommands.script; 2 | 3 | import com.google.common.collect.Lists; 4 | import net.earthcomputer.clientcommands.script.ducks.IMaterial; 5 | import net.earthcomputer.clientcommands.script.mixin.AbstractBlockAccessor; 6 | import net.earthcomputer.clientcommands.script.mixin.AbstractBlockSettingsAccessor; 7 | import net.earthcomputer.clientcommands.script.mixin.FireBlockAccessor; 8 | import net.minecraft.block.BlockState; 9 | import net.minecraft.block.Blocks; 10 | import net.minecraft.block.FallingBlock; 11 | import net.minecraft.client.MinecraftClient; 12 | import net.minecraft.client.network.ClientPlayNetworkHandler; 13 | import net.minecraft.state.property.Property; 14 | import net.minecraft.util.Identifier; 15 | import net.minecraft.util.registry.Registry; 16 | 17 | import java.util.ArrayList; 18 | import java.util.List; 19 | import java.util.Locale; 20 | 21 | @SuppressWarnings("unused") 22 | public class ScriptBlockState { 23 | 24 | static ScriptBlockState uncheckedDefaultState(String block) { 25 | Identifier id = new Identifier(block); 26 | if (!Registry.BLOCK.containsId(id)) 27 | throw new IllegalArgumentException("No such block: " + block); 28 | return new ScriptBlockState(Registry.BLOCK.get(id).getDefaultState()); 29 | } 30 | 31 | public static Object defaultState(String block) { 32 | return BeanWrapper.wrap(uncheckedDefaultState(block)); 33 | } 34 | 35 | BlockState state; 36 | 37 | ScriptBlockState(BlockState state) { 38 | this.state = state; 39 | } 40 | 41 | public String getBlock() { 42 | return ScriptUtil.simplifyIdentifier(Registry.BLOCK.getId(state.getBlock())); 43 | } 44 | 45 | public Object getProperty(String property) { 46 | for (Property propertyObj : state.getProperties()) { 47 | if (propertyObj.getName().equals(property)) { 48 | Object val = state.get(propertyObj); 49 | if (val instanceof Boolean) 50 | return val; 51 | if (val instanceof Number) 52 | return val; 53 | else 54 | return propertyGetName(propertyObj, val); 55 | } 56 | } 57 | return null; 58 | } 59 | 60 | public int getLuminance() { 61 | return state.getLuminance(); 62 | } 63 | 64 | public float getHardness() { 65 | return ((AbstractBlockSettingsAccessor) ((AbstractBlockAccessor) state.getBlock()).getSettings()).getHardness(); 66 | } 67 | 68 | public float getBlastResistance() { 69 | return state.getBlock().getBlastResistance(); 70 | } 71 | 72 | public boolean isRandomTickable() { 73 | return state.hasRandomTicks(); 74 | } 75 | 76 | public float getSlipperiness() { 77 | return state.getBlock().getSlipperiness(); 78 | } 79 | 80 | public List getStateProperties() { 81 | return Lists.transform(new ArrayList<>(state.getProperties()), Property::getName); 82 | } 83 | 84 | public String getLootTable() { 85 | return ScriptUtil.simplifyIdentifier(state.getBlock().getLootTableId()); 86 | } 87 | 88 | public String getTranslationKey() { 89 | return state.getBlock().getTranslationKey(); 90 | } 91 | 92 | public String getItem() { 93 | return ScriptUtil.simplifyIdentifier(Registry.ITEM.getId(state.getBlock().asItem())); 94 | } 95 | 96 | public int getMaterialId() { 97 | //noinspection ConstantConditions 98 | return ((IMaterial) (Object) state.getMaterial()).clientcommands_getId(); 99 | } 100 | 101 | public int getMapColor() { 102 | try { 103 | return state.getMapColor(null, null).color; 104 | } catch (Throwable e) { 105 | return state.getMaterial().getColor().color; 106 | } 107 | } 108 | 109 | public String getPistonBehavior() { 110 | return state.getPistonBehavior().name().toLowerCase(Locale.ENGLISH); 111 | } 112 | 113 | public boolean isFlammable() { 114 | return state.getMaterial().isBurnable(); 115 | } 116 | 117 | public boolean isCanBreakByHand() { 118 | return !state.isToolRequired(); 119 | } 120 | 121 | public boolean isLiquid() { 122 | return state.getMaterial().isLiquid(); 123 | } 124 | 125 | public boolean isBlocksLight() { 126 | return state.getMaterial().blocksLight(); 127 | } 128 | 129 | public boolean isReplaceable() { 130 | return state.getMaterial().isReplaceable(); 131 | } 132 | 133 | public boolean isSolid() { 134 | return state.getMaterial().isSolid(); 135 | } 136 | 137 | public int getBurnChance() { 138 | return ((FireBlockAccessor) Blocks.FIRE).callGetBurnChance(state); 139 | } 140 | 141 | public int getSpreadChance() { 142 | return ((FireBlockAccessor) Blocks.FIRE).callGetSpreadChance(state); 143 | } 144 | 145 | public boolean isFallable() { 146 | return state.getBlock() instanceof FallingBlock; 147 | } 148 | 149 | public List getTags() { 150 | ClientPlayNetworkHandler networkHandler = MinecraftClient.getInstance().getNetworkHandler(); 151 | if (networkHandler == null) { 152 | return new ArrayList<>(); 153 | } 154 | return state.streamTags().map(tag -> ScriptUtil.simplifyIdentifier(tag.id())).toList(); 155 | } 156 | 157 | @SuppressWarnings("unchecked") 158 | private static > String propertyGetName(Property prop, Object val) { 159 | return prop.name((T) val); 160 | } 161 | 162 | } 163 | -------------------------------------------------------------------------------- /docs/example_scripts/build_platform.js: -------------------------------------------------------------------------------- 1 | 2 | var GROUP_NAME = "rsf"; 3 | var PLATFORM_Y = 40; 4 | var TEMPLATE_Y = 20; 5 | var TEMPLATE_Z = -3367; 6 | var PLATFORM_X_MIN = 3227; 7 | var PLATFORM_X_MAX = 3305; 8 | var PLATFORM_Z_MIN = -3442; 9 | var PLATFORM_Z_MAX = -3364; 10 | var STONE_BLOCK = "minecraft:stone"; 11 | var SLAB_BLOCK = "minecraft:smooth_stone_slab"; 12 | 13 | var craftingTable; 14 | 15 | var directionNames = ["north", "east", "south", "west"]; 16 | var dx = [0, 1, 0, -1]; 17 | var dz = [-1, 0, 1, 0]; 18 | var opposite = function(dir) { return (dir + 2) % 4; }; 19 | 20 | var isChestContainer = function(type) { return type === "generic_9x3" || type === "generic_9x6"; }; 21 | 22 | var openContainer = function(x, y, z, type) { 23 | if (typeof(type) === "string") { 24 | var typeName = type; 25 | type = function(it) { return it === typeName }; 26 | } 27 | player.lookAt(x, y, z); 28 | player.syncRotation(); 29 | if (!player.rightClick(x, y, z)) 30 | throw new Error("Could not right click on container at " + x + ", " + y + ", " + z); 31 | var timeout = 0; 32 | while (player.currentContainer === null || !type(player.currentContainer.type)) { 33 | tick(); 34 | timeout++; 35 | if (timeout > 100) 36 | throw new Error("Failed to open container at " + x + ", " + y + ", " + z); 37 | } 38 | anticheatDelay(); 39 | }; 40 | 41 | var anticheatDelay = function() { 42 | for (var i = 0; i < 20; i++) 43 | tick(); 44 | }; 45 | 46 | var anticheatMediumDelay = function() { 47 | for (var i = 0; i < 4; i++) 48 | tick(); 49 | }; 50 | 51 | var anticheatLessDelay = function() { 52 | tick(); 53 | }; 54 | 55 | var findReplenishArea = function() { 56 | var found = false; 57 | var minDistanceSq = 1000000; 58 | for (var xDelta = -7; xDelta <= 7; xDelta++) { 59 | for (var zDelta = -7; zDelta <= 7; zDelta++) { 60 | for (var yDelta = -7; yDelta <= 7; yDelta++) { 61 | var distanceSq = xDelta * xDelta + yDelta * yDelta + zDelta * zDelta; 62 | if (distanceSq >= minDistanceSq) 63 | continue; 64 | var x = player.x + xDelta; 65 | var y = player.y + player.eyeHeight + yDelta; 66 | var z = player.z + zDelta; 67 | if (world.getBlock(x, y, z) === "crafting_table") { 68 | craftingTable = [x, y, z]; 69 | minDistanceSq = distanceSq; 70 | found = true; 71 | } 72 | } 73 | } 74 | } 75 | if (!found) 76 | throw new Error("Could not find replenish area"); 77 | return craftingTable; 78 | }; 79 | 80 | var gatherStone = function(stoneNeeded) { 81 | for (var xDelta = -5; xDelta <= 5; xDelta++) { 82 | for (var zDelta = -5; zDelta <= 5; zDelta++) { 83 | for (var yDelta = -5; yDelta <= 5; yDelta++) { 84 | var x = player.x + xDelta; 85 | var y = player.y + player.eyeHeight + yDelta; 86 | var z = player.z + zDelta; 87 | if (world.getBlock(x, y, z) === "chest" || world.getBlock(x, y, z) === "trapped_chest") { 88 | try { 89 | openContainer(x, y, z, isChestContainer); 90 | var chestItems = player.currentContainer.items; 91 | for (var i = 0; i < chestItems.length; i++) { 92 | if (chestItems[i].id === STONE_BLOCK) { 93 | stoneNeeded -= chestItems[i].Count; 94 | player.currentContainer.click(i, {type: "quick_move"}); 95 | anticheatLessDelay(); 96 | if (stoneNeeded <= 0) 97 | return; 98 | } 99 | } 100 | player.closeContainer(); 101 | anticheatDelay(); 102 | } catch (e) { 103 | if (!(e instanceof Error)) 104 | throw e; 105 | } 106 | } 107 | } 108 | } 109 | } 110 | throw new Error("Not enough stone"); 111 | }; 112 | 113 | var ensureResources = function() { 114 | // Check if we already have the resources 115 | var foundStone = 0, foundSlabs = 0; 116 | var items = player.inventory.items; 117 | for (var slot = 0; slot < 36; slot++) { 118 | if (items[slot].id === STONE_BLOCK) 119 | foundStone += items[slot].Count; 120 | else if (items[slot].id === SLAB_BLOCK) 121 | foundSlabs += items[slot].Count; 122 | } 123 | if (foundStone !== 0 && foundSlabs !== 0) 124 | return; 125 | 126 | var slabsNeeded = Math.max(0, 64 * 10 - foundSlabs); 127 | slabsNeeded = Math.ceil(slabsNeeded / 6) * 6; 128 | var stoneNeeded = 64 * 10 - foundStone; 129 | stoneNeeded += slabsNeeded / 2; 130 | 131 | // Travel near to the replenish area (blocks may not be visible until nearby) 132 | var xDistanceToReplenishArea = replenishArea[0] + 0.5 - player.x; 133 | var zDistanceToReplenishArea = replenishArea[2] + 0.5 - player.z; 134 | var hDistanceToReplenishArea = Math.sqrt(xDistanceToReplenishArea * xDistanceToReplenishArea + zDistanceToReplenishArea * zDistanceToReplenishArea); 135 | var targetX = replenishArea[0] + 0.5 - xDistanceToReplenishArea * 2 / hDistanceToReplenishArea; 136 | var targetZ = replenishArea[2] + 0.5 - zDistanceToReplenishArea * 2 / hDistanceToReplenishArea; 137 | 138 | if (!player.pathTo(targetX, player.y, targetZ)) 139 | throw new Error("Could not move to replenish area"); 140 | 141 | // Re-find the replenish area in case it has moved 142 | findReplenishArea(); 143 | 144 | if (stoneNeeded > 0) { 145 | gatherStone(stoneNeeded); 146 | } 147 | 148 | // Craft slabs 149 | if (slabsNeeded > 0) { 150 | openContainer(craftingTable[0], craftingTable[1], craftingTable[2], "crafting"); 151 | var recipeCount = slabsNeeded / 6; 152 | while (recipeCount > 0) { 153 | for (var slot = 1; slot <= 3; slot++) { 154 | var itemsNeeded = Math.min(64, recipeCount); 155 | items = player.inventory.items; 156 | for (var i = 0; i < 36; i++) { 157 | if (items[i].id === STONE_BLOCK) { 158 | var count = items[i].Count; 159 | player.inventory.click(i); // pickup the stone 160 | anticheatMediumDelay(); 161 | if (count <= itemsNeeded || itemsNeeded === 64) { 162 | // drop down all the stone 163 | itemsNeeded -= count; 164 | player.currentContainer.click(slot); 165 | anticheatMediumDelay(); 166 | } else { 167 | // drop down as many stone as needed then put the rest back 168 | for (var j = 0; j < itemsNeeded; j++) { 169 | player.currentContainer.click(slot, {rightClick: true}); 170 | anticheatMediumDelay(); 171 | } 172 | player.inventory.click(i); 173 | anticheatMediumDelay(); 174 | itemsNeeded = 0; 175 | } 176 | if (itemsNeeded <= 0) 177 | break; 178 | } 179 | } 180 | } 181 | 182 | var timeout = 0; 183 | while (player.currentContainer.items[0].id !== SLAB_BLOCK) { 184 | tick(); 185 | timeout++; 186 | if (timeout > 100) 187 | throw new Error("Failed to craft slabs"); 188 | } 189 | 190 | player.currentContainer.click(0, {type: "quick_move"}); 191 | anticheatMediumDelay(); 192 | 193 | recipeCount -= 64; 194 | } 195 | player.closeContainer(); 196 | anticheatDelay(); 197 | } 198 | 199 | if (foundStone === 0) { 200 | if (!player.pick(STONE_BLOCK)) 201 | throw new Error("Someone is tampering with the bot"); 202 | anticheatDelay(); 203 | chat("/ctf " + GROUP_NAME); 204 | } 205 | }; 206 | 207 | var placePlatform = function() { 208 | for (var x = PLATFORM_X_MAX; x >= PLATFORM_X_MIN; x--) { 209 | var countDown = x % 2 === PLATFORM_X_MAX % 2; 210 | var standingX = x; 211 | if (!countDown) 212 | standingX--; 213 | for (var z = countDown ? PLATFORM_Z_MAX : PLATFORM_Z_MIN; countDown ? z >= PLATFORM_Z_MIN : z <= PLATFORM_Z_MAX; z += countDown ? -1 : 1) { 214 | ensureResources(); 215 | if (world.getBlock(x, PLATFORM_Y, z).endsWith("_slab")) 216 | continue; 217 | if (!world.getBlockState(x, TEMPLATE_Y, z).solid || world.getBlock(x, TEMPLATE_Y, z).endsWith("sign")) 218 | continue; 219 | if (!player.pathTo(standingX + 0.5, player.y, z + 0.5)) 220 | throw new Error("Movement to " + standingX + ", " + z + " failed"); 221 | player.pick(SLAB_BLOCK); 222 | var placementSide = -1; 223 | for (var dir = 0; dir < 4; dir++) { 224 | if (world.getBlockState(x + dx[dir], PLATFORM_Y, z + dz[dir]).solid) { 225 | placementSide = dir; 226 | break; 227 | } 228 | } 229 | if (placementSide === -1) 230 | throw new Error("Nothing to place the slab against at " + x + ", " + z); 231 | var closestPoint = world.getClosestVisiblePoint(x + dx[placementSide], PLATFORM_Y, z + dz[placementSide], directionNames[opposite(dir)]); 232 | if (!closestPoint) { 233 | if (!player.moveTo(standingX + 0.5, z + 0.5)) 234 | throw new Error("Movement to " + standingX + ", " + z + " failed"); 235 | closestPoint = world.getClosestVisiblePoint(x + dx[placementSide], PLATFORM_Y, z + dz[placementSide], directionNames[opposite(dir)]); 236 | if (!closestPoint) 237 | throw new Error("Slab not in view"); 238 | } 239 | var clickX = closestPoint.x; 240 | var clickY = closestPoint.y; 241 | var clickZ = closestPoint.z; 242 | if (clickY - Math.floor(clickY) < 0.6) 243 | clickY += 0.1; 244 | anticheatMediumDelay(); 245 | player.lookAt(clickX, clickY, clickZ); 246 | anticheatMediumDelay(); 247 | player.rightClick(x + dx[placementSide], PLATFORM_Y, z + dz[placementSide], directionNames[opposite(dir)]); 248 | } 249 | } 250 | }; 251 | 252 | replenishArea = findReplenishArea(); 253 | chat("/cto"); 254 | if (player.pick(STONE_BLOCK)) { 255 | anticheatDelay(); 256 | chat("/ctf " + GROUP_NAME); 257 | } 258 | placePlatform(); 259 | -------------------------------------------------------------------------------- /src/main/java/net/earthcomputer/clientcommands/script/ScriptInventory.java: -------------------------------------------------------------------------------- 1 | package net.earthcomputer.clientcommands.script; 2 | 3 | import com.google.common.collect.Lists; 4 | import net.minecraft.client.MinecraftClient; 5 | import net.minecraft.client.gui.screen.ingame.CreativeInventoryScreen; 6 | import net.minecraft.client.network.ClientPlayerEntity; 7 | import net.minecraft.client.network.ClientPlayerInteractionManager; 8 | import net.minecraft.item.ItemStack; 9 | import net.minecraft.nbt.NbtCompound; 10 | import net.minecraft.screen.HorseScreenHandler; 11 | import net.minecraft.screen.PlayerScreenHandler; 12 | import net.minecraft.screen.ScreenHandler; 13 | import net.minecraft.screen.ScreenHandlerType; 14 | import net.minecraft.screen.slot.Slot; 15 | import net.minecraft.screen.slot.SlotActionType; 16 | import net.minecraft.util.math.MathHelper; 17 | import net.minecraft.util.registry.Registry; 18 | import org.graalvm.polyglot.Value; 19 | 20 | import java.util.ArrayList; 21 | import java.util.Arrays; 22 | import java.util.Comparator; 23 | import java.util.List; 24 | import java.util.function.Predicate; 25 | import java.util.stream.Collectors; 26 | 27 | @SuppressWarnings("unused") 28 | public class ScriptInventory { 29 | 30 | private final ScreenHandler container; 31 | 32 | ScriptInventory(ScreenHandler container) { 33 | this.container = container; 34 | } 35 | 36 | public String getType() { 37 | if (container instanceof PlayerScreenHandler) 38 | return "player"; 39 | if (container instanceof CreativeInventoryScreen.CreativeScreenHandler) 40 | return "creative"; 41 | if (container instanceof HorseScreenHandler) 42 | return "horse"; 43 | ScreenHandlerType type = container.getType(); 44 | if (type == null) 45 | return null; 46 | return ScriptUtil.simplifyIdentifier(Registry.SCREEN_HANDLER.getId(type)); 47 | } 48 | 49 | /** 50 | * If this is a player container, then slots are the hotbar, main inventory, armor, offhand, crafting result and crafting grid, 51 | * in that order. 52 | * Otherwise, they are the container items in order. 53 | */ 54 | public List getItems() { 55 | List ret = new ArrayList<>(); 56 | ClientPlayerEntity player = MinecraftClient.getInstance().player; 57 | if (container == player.playerScreenHandler) { 58 | for (int i = 0; i < player.getInventory().size(); i++) { 59 | ret.add(ScriptUtil.fromNbt(player.getInventory().getStack(i).writeNbt(new NbtCompound()))); 60 | } 61 | // crafting grid 62 | for (int i = 0; i < 5; i++) 63 | ret.add(ScriptUtil.fromNbt(container.slots.get(i).getStack().writeNbt(new NbtCompound()))); 64 | } else { 65 | for (Slot slot : container.slots) { 66 | if (slot.inventory != player.getInventory()) { 67 | ret.add(ScriptUtil.fromNbt(slot.getStack().writeNbt(new NbtCompound()))); 68 | } 69 | } 70 | } 71 | return ret; 72 | } 73 | 74 | public void click(Integer slot) { 75 | click(slot, null); 76 | } 77 | 78 | public void click(Integer slot, Value options) { 79 | String typeStr = options != null && options.hasMember("type") ? ScriptUtil.asString(options.getMember("type")) : null; 80 | SlotActionType type = typeStr == null ? SlotActionType.PICKUP : 81 | Arrays.stream(SlotActionType.values()).filter(it -> it.name().equalsIgnoreCase(typeStr)).findAny().orElse(SlotActionType.PICKUP); 82 | 83 | int mouseButton; 84 | if (type == SlotActionType.SWAP) { 85 | if (!options.hasMember("hotbarSlot")) 86 | throw new IllegalArgumentException("When the click type is swap, the options must also contain the hotbar slot to swap with"); 87 | mouseButton = MathHelper.clamp(options.getMember("hotbarSlot").asInt(), 0, 8); 88 | } else if (type == SlotActionType.QUICK_CRAFT) { 89 | if (!options.hasMember("quickCraftStage")) 90 | throw new IllegalArgumentException("When the click type is quick_craft, the options must also contain the quick craft stage"); 91 | int quickCraftStage = options.getMember("quickCraftStage").asInt(); 92 | int button = options.hasMember("rightClick") ? ScriptUtil.asBoolean(options.getMember("rightClick")) ? 1 : 0 : 0; 93 | mouseButton = ScreenHandler.packQuickCraftData(quickCraftStage, button); 94 | } else { 95 | mouseButton = options != null && options.hasMember("rightClick") ? ScriptUtil.asBoolean(options.getMember("rightClick")) ? 1 : 0 : 0; 96 | } 97 | 98 | int slotId; 99 | if (slot == null) { 100 | slotId = -999; 101 | } else { 102 | Slot theSlot = getSlot(slot); 103 | if (theSlot == null) 104 | throw new IllegalArgumentException("Slot not in open container"); 105 | slotId = theSlot.id; 106 | } 107 | 108 | ClientPlayerEntity player = MinecraftClient.getInstance().player; 109 | MinecraftClient.getInstance().interactionManager.clickSlot(player.currentScreenHandler.syncId, slotId, mouseButton, type, player); 110 | } 111 | 112 | public Integer findSlot(Value item) { 113 | return findSlot(item, false); 114 | } 115 | 116 | public Integer findSlot(Value item, boolean reverse) { 117 | Predicate itemPredicate = ScriptUtil.asItemStackPredicate(item); 118 | 119 | List slots = getSlots(); 120 | if (reverse) 121 | slots = Lists.reverse(slots); 122 | 123 | for (Slot slot : slots) { 124 | if (itemPredicate.test(slot.getStack())) { 125 | return getIdForSlot(slot); 126 | } 127 | } 128 | 129 | return null; 130 | } 131 | 132 | public List findSlots(Value item, int count) { 133 | return findSlots(item, count, false); 134 | } 135 | 136 | public List findSlots(Value item, int count, boolean reverse) { 137 | Predicate itemPredicate = ScriptUtil.asItemStackPredicate(item); 138 | 139 | List slots = getSlots(); 140 | if (reverse) 141 | slots = Lists.reverse(slots); 142 | 143 | List slotIds = new ArrayList<>(); 144 | int itemsFound = 0; 145 | for (Slot slot : slots) { 146 | if (itemPredicate.test(slot.getStack())) { 147 | slotIds.add(getIdForSlot(slot)); 148 | itemsFound += slot.getStack().getCount(); 149 | if (count != -1 && itemsFound >= count) 150 | break; 151 | } 152 | } 153 | 154 | return slotIds; 155 | } 156 | 157 | public int moveItems(Value item, int count) { 158 | return moveItems(item, count, false); 159 | } 160 | 161 | public int moveItems(Value item, int count, boolean reverse) { 162 | Predicate itemPredicate = ScriptUtil.asItemStackPredicate(item); 163 | 164 | ClientPlayerEntity player = MinecraftClient.getInstance().player; 165 | assert player != null; 166 | ClientPlayerInteractionManager interactionManager = MinecraftClient.getInstance().interactionManager; 167 | assert interactionManager != null; 168 | 169 | List slots = getSlots(); 170 | if (reverse) 171 | slots = Lists.reverse(slots); 172 | 173 | int itemsFound = 0; 174 | for (Slot slot : slots) { 175 | if (itemPredicate.test(slot.getStack())) { 176 | interactionManager.clickSlot(player.currentScreenHandler.syncId, slot.id, 0, SlotActionType.QUICK_MOVE, player); 177 | itemsFound += slot.getStack().getCount(); 178 | if (count != -1 && itemsFound >= count) 179 | break; 180 | } 181 | } 182 | 183 | return itemsFound; 184 | } 185 | 186 | private Slot getSlot(int id) { 187 | ClientPlayerEntity player = MinecraftClient.getInstance().player; 188 | assert player != null; 189 | 190 | if (container == player.playerScreenHandler) { 191 | if (id < player.getInventory().size()) { 192 | for (Slot slot : player.currentScreenHandler.slots) { 193 | if (slot.inventory == player.getInventory() && slot.getIndex() == id) { 194 | return slot; 195 | } 196 | } 197 | } 198 | if (container == player.currentScreenHandler) { 199 | return container.getSlot(id - player.getInventory().size()); 200 | } 201 | } else { 202 | int containerId = 0; 203 | for (int i = 0; i < container.slots.size(); i++) { 204 | Slot slot = container.getSlot(i); 205 | if (slot.inventory != player.getInventory()) { 206 | if (id == containerId) 207 | return slot; 208 | containerId++; 209 | } 210 | } 211 | } 212 | 213 | return null; 214 | } 215 | 216 | private int getIdForSlot(Slot slot) { 217 | ClientPlayerEntity player = MinecraftClient.getInstance().player; 218 | assert player != null; 219 | 220 | if (container == player.playerScreenHandler) { 221 | if (slot.inventory == player.getInventory()) { 222 | return slot.getIndex(); 223 | } else { 224 | return slot.getIndex() + player.getInventory().size(); 225 | } 226 | } else { 227 | int containerId = 0; 228 | for (int i = 0; i < container.slots.size(); i++) { 229 | Slot otherSlot = container.getSlot(i); 230 | if (otherSlot.inventory != player.getInventory()) { 231 | if (otherSlot == slot) 232 | return containerId; 233 | containerId++; 234 | } 235 | } 236 | return -1; 237 | } 238 | } 239 | 240 | private List getSlots() { 241 | ClientPlayerEntity player = MinecraftClient.getInstance().player; 242 | assert player != null; 243 | 244 | if (container == player.playerScreenHandler) { 245 | return player.currentScreenHandler.slots.stream() 246 | .filter(slot -> slot.inventory == player.getInventory()) 247 | .sorted(Comparator.comparingInt(this::getIdForSlot)) 248 | .collect(Collectors.toList()); 249 | } else { 250 | return container.slots.stream() 251 | .filter(slot -> slot.inventory != player.getInventory()) 252 | .collect(Collectors.toList()); 253 | } 254 | } 255 | 256 | @Override 257 | public int hashCode() { 258 | return container.hashCode(); 259 | } 260 | 261 | @Override 262 | public boolean equals(Object o) { 263 | if (o == this) return true; 264 | if (!(o instanceof ScriptInventory)) return false; 265 | return container.equals(((ScriptInventory) o).container); 266 | } 267 | } 268 | -------------------------------------------------------------------------------- /src/main/java/net/earthcomputer/clientcommands/script/ScriptUtil.java: -------------------------------------------------------------------------------- 1 | package net.earthcomputer.clientcommands.script; 2 | 3 | import net.minecraft.item.Item; 4 | import net.minecraft.item.ItemStack; 5 | import net.minecraft.nbt.AbstractNbtList; 6 | import net.minecraft.nbt.AbstractNbtNumber; 7 | import net.minecraft.nbt.NbtByte; 8 | import net.minecraft.nbt.NbtByteArray; 9 | import net.minecraft.nbt.NbtCompound; 10 | import net.minecraft.nbt.NbtDouble; 11 | import net.minecraft.nbt.NbtElement; 12 | import net.minecraft.nbt.NbtFloat; 13 | import net.minecraft.nbt.NbtHelper; 14 | import net.minecraft.nbt.NbtInt; 15 | import net.minecraft.nbt.NbtIntArray; 16 | import net.minecraft.nbt.NbtList; 17 | import net.minecraft.nbt.NbtLong; 18 | import net.minecraft.nbt.NbtLongArray; 19 | import net.minecraft.nbt.NbtShort; 20 | import net.minecraft.nbt.NbtString; 21 | import net.minecraft.util.Identifier; 22 | import net.minecraft.util.math.Direction; 23 | import net.minecraft.util.registry.Registry; 24 | import org.graalvm.polyglot.Value; 25 | 26 | import java.util.ArrayList; 27 | import java.util.HashMap; 28 | import java.util.List; 29 | import java.util.Map; 30 | import java.util.function.Predicate; 31 | 32 | public class ScriptUtil { 33 | 34 | public static Object fromNbt(NbtElement tag) { 35 | if (tag instanceof NbtCompound) { 36 | return fromNbtCompound((NbtCompound) tag); 37 | } else if (tag instanceof AbstractNbtList) { 38 | return fromNbtList((AbstractNbtList) tag); 39 | } else if (tag instanceof NbtString) { 40 | return tag.asString(); 41 | } else if (tag instanceof NbtLong) { 42 | return ((NbtLong) tag).longValue(); 43 | } else if (tag instanceof AbstractNbtNumber) { 44 | return ((AbstractNbtNumber) tag).doubleValue(); 45 | } else { 46 | throw new IllegalStateException("Unknown tag type " + tag.getType()); 47 | } 48 | } 49 | 50 | public static Object fromNbtCompound(NbtCompound tag) { 51 | Map map = new HashMap<>(tag.getSize()); 52 | tag.getKeys().forEach(key -> map.put(key, fromNbt(tag.get(key)))); 53 | return map; 54 | } 55 | 56 | public static Object fromNbtList(AbstractNbtList tag) { 57 | List list = new ArrayList<>(tag.size()); 58 | tag.forEach(val -> list.add(fromNbt(val))); 59 | return list; 60 | } 61 | 62 | public static NbtElement toNbt(Value obj) { 63 | if (obj.isBoolean()) { 64 | return NbtByte.of(obj.asBoolean()); 65 | } else if (obj.isNumber()) { 66 | if (obj.fitsInByte()) { 67 | return NbtByte.of(obj.asByte()); 68 | } else if (obj.fitsInShort()) { 69 | return NbtShort.of(obj.asShort()); 70 | } else if (obj.fitsInInt()) { 71 | return NbtInt.of(obj.asInt()); 72 | } else if (obj.fitsInFloat()) { 73 | return NbtFloat.of(obj.asFloat()); 74 | } else if (obj.fitsInLong()) { 75 | return NbtLong.of(obj.asLong()); 76 | } else if (obj.fitsInDouble()) { 77 | return NbtDouble.of(obj.asDouble()); 78 | } else { 79 | return NbtDouble.of(Double.NaN); 80 | } 81 | } else if (obj.isString()) { 82 | return NbtString.of(obj.asString()); 83 | } else if (obj.hasArrayElements()) { 84 | return arrayToNbtList(obj); 85 | } else { 86 | return objectToNbtCompound(obj); 87 | } 88 | } 89 | 90 | private static NbtCompound objectToNbtCompound(Value obj) { 91 | NbtCompound compound = new NbtCompound(); 92 | for (String key : obj.getMemberKeys()) { 93 | compound.put(key, toNbt(obj.getMember(key))); 94 | } 95 | return compound; 96 | } 97 | 98 | private static byte getArrayType(Value array) { 99 | byte type = 0; 100 | int len = (int) array.getArraySize(); 101 | for (int i = 0; i < len; i++) { 102 | Value element = array.getArrayElement(i); 103 | if (element.isBoolean()) { 104 | return 1; // byte 105 | } else if (element.isNumber()) { 106 | byte newType; 107 | if (element.fitsInByte()) 108 | newType = 1; // byte 109 | else if (element.fitsInShort()) 110 | newType = 2; // short 111 | else if (element.fitsInInt()) 112 | newType = 3; // int 113 | else if (element.fitsInFloat()) 114 | newType = 5; // float 115 | else if (element.fitsInLong()) 116 | newType = 4; // long 117 | else 118 | newType = 6; // double 119 | if (newType == 5) { // float 120 | if (type <= 2) // byte, short 121 | type = 5; // float 122 | else 123 | type = 6; // double 124 | } else if (newType == 6) // double 125 | type = 6; // double 126 | else 127 | type = (byte) Math.max(type, newType); 128 | } else if (element.isString()) { 129 | return 8; // string 130 | } else if (element.hasArrayElements()) { 131 | byte sublistType = getArrayType(element); 132 | byte newType; 133 | if (sublistType == 1) // byte 134 | newType = 7; // byte array 135 | else if (sublistType == 2 || sublistType == 3) 136 | newType = 11; // int array 137 | else if (sublistType == 4) 138 | newType = 12; // long array 139 | else 140 | return 9; // list 141 | type = (byte) Math.max(type, newType); 142 | } else { 143 | return 10; // compound 144 | } 145 | } 146 | return type; 147 | } 148 | 149 | private static AbstractNbtList arrayToNbtList(Value array) { 150 | byte type = getArrayType(array); 151 | int len = (int) array.getArraySize(); 152 | AbstractNbtList listTag; 153 | if (type == 1) // byte 154 | listTag = new NbtByteArray(new byte[len]); 155 | else if (type == 2 || type == 3) // short, int 156 | listTag = new NbtIntArray(new int[len]); 157 | else if (type == 4) // long 158 | listTag = new NbtLongArray(new long[len]); 159 | else 160 | listTag = new NbtList(); 161 | 162 | for (int index = 0; index < len; index++) { 163 | Value element = array.getArrayElement(index); 164 | NbtElement elementTag = toNbt(element); 165 | if (type <= 4) { // integral number 166 | if (!(elementTag instanceof AbstractNbtNumber)) 167 | throw new IllegalStateException(); 168 | AbstractNbtNumber num = (AbstractNbtNumber) elementTag; 169 | if (type == 1) // byte 170 | ((NbtByteArray) listTag).getByteArray()[index] = num.byteValue(); 171 | else if (type == 2 || type == 3) // short, int 172 | ((NbtIntArray) listTag).getIntArray()[index] = num.intValue(); 173 | else if (type == 4) // long 174 | ((NbtLongArray) listTag).getLongArray()[index] = num.longValue(); 175 | } else if (type == 7 || type == 11 || type == 12) { // integral arrays 176 | if (!(elementTag instanceof AbstractNbtList)) 177 | throw new IllegalStateException(); 178 | AbstractNbtList converted; 179 | if (type == 7) { // byte array 180 | if (elementTag instanceof NbtByteArray) { 181 | converted = (NbtByteArray) elementTag; 182 | } else if (elementTag instanceof NbtIntArray) { 183 | int[] from = ((NbtIntArray) elementTag).getIntArray(); 184 | byte[] to = new byte[from.length]; 185 | for (int i = 0; i < from.length; i++) 186 | to[i] = (byte) from[i]; 187 | converted = new NbtByteArray(to); 188 | } else { 189 | long[] from = ((NbtLongArray) elementTag).getLongArray(); 190 | byte[] to = new byte[from.length]; 191 | for (int i = 0; i < from.length; i++) 192 | to[i] = (byte) from[i]; 193 | converted = new NbtByteArray(to); 194 | } 195 | } else if (type == 11) { // int array 196 | if (elementTag instanceof NbtByteArray) { 197 | byte[] from = ((NbtByteArray) elementTag).getByteArray(); 198 | int[] to = new int[from.length]; 199 | for (int i = 0; i < from.length; i++) 200 | to[i] = from[i]; 201 | converted = new NbtIntArray(to); 202 | } else if (elementTag instanceof NbtIntArray) { 203 | converted = (NbtIntArray) elementTag; 204 | } else { 205 | long[] from = ((NbtLongArray) elementTag).getLongArray(); 206 | int[] to = new int[from.length]; 207 | for (int i = 0; i < from.length; i++) 208 | to[i] = (int) from[i]; 209 | converted = new NbtIntArray(to); 210 | } 211 | } else { // long array 212 | if (elementTag instanceof NbtByteArray) { 213 | byte[] from = ((NbtByteArray) elementTag).getByteArray(); 214 | long[] to = new long[from.length]; 215 | for (int i = 0; i < from.length; i++) 216 | to[i] = from[i]; 217 | converted = new NbtLongArray(to); 218 | } else if (elementTag instanceof NbtIntArray) { 219 | int[] from = ((NbtIntArray) elementTag).getIntArray(); 220 | long[] to = new long[from.length]; 221 | for (int i = 0; i < from.length; i++) 222 | to[i] = from[i]; 223 | converted = new NbtLongArray(to); 224 | } else { 225 | converted = (NbtLongArray) elementTag; 226 | } 227 | } 228 | ((NbtList) listTag).add(converted); 229 | } else { 230 | ((NbtList) listTag).add(elementTag); 231 | } 232 | } 233 | return listTag; 234 | } 235 | 236 | public static String simplifyIdentifier(Identifier id) { 237 | if (id == null) 238 | return "null"; 239 | if ("minecraft".equals(id.getNamespace())) 240 | return id.getPath(); 241 | else 242 | return id.toString(); 243 | } 244 | 245 | public static boolean asBoolean(Value obj) { 246 | if (obj.isNull()) return false; 247 | if (obj.isBoolean()) return obj.asBoolean(); 248 | if (obj.isNumber() && obj.fitsInDouble()) return obj.asDouble() != 0; 249 | throw new IllegalArgumentException("Cannot interpret " + obj + " as a boolean"); 250 | } 251 | 252 | public static String asString(Value obj) { 253 | if (obj == null || obj.isNull()) return null; 254 | if (obj.isString()) return obj.asString(); 255 | return String.valueOf(obj); 256 | } 257 | 258 | public static boolean isFunction(Value obj) { 259 | return obj.canExecute(); 260 | } 261 | 262 | public static ScriptFunction asFunction(Value obj) { 263 | if (!obj.canExecute()) { 264 | throw new IllegalArgumentException("Cannot interpret " + obj + " as a function"); 265 | } 266 | return obj::execute; 267 | } 268 | 269 | static Direction getDirectionFromString(String side) { 270 | if (side == null) { 271 | return null; 272 | } 273 | 274 | for (Direction dir : Direction.values()) { 275 | if (dir.name().equalsIgnoreCase(side)) { 276 | return dir; 277 | } 278 | } 279 | 280 | return null; 281 | } 282 | 283 | static Predicate asItemStackPredicate(Value obj) { 284 | if (obj.isString()) { 285 | Item item = Registry.ITEM.get(new Identifier(asString(obj))); 286 | return stack -> stack.getItem() == item; 287 | } else if (isFunction(obj)) { 288 | ScriptFunction func = asFunction(obj); 289 | return stack -> { 290 | Object tag = fromNbt(stack.writeNbt(new NbtCompound())); 291 | return asBoolean(func.call(tag)); 292 | }; 293 | } else { 294 | NbtElement nbt = toNbt(obj); 295 | if (!(nbt instanceof NbtCompound)) 296 | throw new IllegalArgumentException(obj.toString()); 297 | return stack -> NbtHelper.matches(nbt, stack.writeNbt(new NbtCompound()), true); 298 | } 299 | } 300 | 301 | static T unwrap(Value obj, Class type) { 302 | if (obj.isHostObject() && obj.asHostObject() instanceof BeanWrapper) { 303 | obj = ((BeanWrapper) obj.asHostObject()).getDelegate(); 304 | } 305 | if (obj.isHostObject() && type.isInstance(obj)) { 306 | return type.cast(obj.asHostObject()); 307 | } 308 | return null; 309 | } 310 | } 311 | -------------------------------------------------------------------------------- /docs/example_scripts/branch_mine.js: -------------------------------------------------------------------------------- 1 | 2 | var wantedBlocks = ["emerald_ore", "diamond_ore", "gold_ore", "iron_ore", "coal_ore", "redstone_ore", "lapis_ore"]; 3 | 4 | var isWantedBlock = function(block) { 5 | return wantedBlocks.indexOf(block) !== -1; 6 | }; 7 | var isWantedItemEntity = function(entity) { 8 | if (entity.type !== "item") 9 | return false; 10 | var nbt = entity.nbt; 11 | if (!nbt.Item) 12 | return false; 13 | var type = nbt.Item.id; 14 | for (var i = 0; i < wantedBlocks.length; i++) 15 | if (type === "minecraft:" + wantedBlocks[i]) 16 | return true; 17 | return type === "minecraft:emerald" || type === "minecraft:diamond" 18 | || type === "minecraft:coal" || type === "minecraft:redstone" 19 | || type === "minecraft:lapis_lazuli"; 20 | }; 21 | 22 | var getDirectionName = function(dx, dz) { 23 | if (Math.abs(dx) > Math.abs(dz)) { 24 | if (dx > 0) 25 | return "east"; 26 | else 27 | return "west"; 28 | } else { 29 | if (dz > 0) 30 | return "south"; 31 | else 32 | return "north"; 33 | } 34 | }; 35 | 36 | var canWalkThrough = function(block) { 37 | return block === "air" || block === "cave_air" || block === "torch"; 38 | }; 39 | 40 | var canWalkOn = function(block) { 41 | if (canWalkThrough(block)) 42 | return false; 43 | if (BlockState.defaultState(block).liquid) 44 | return false; 45 | return true; 46 | }; 47 | 48 | var getTool = function(block) { 49 | var effective = function(item, block) { 50 | var stack = ItemStack.of(item); 51 | if (!BlockState.defaultState(block).canBreakByHand && !stack.isEffectiveOn(block)) 52 | return false; 53 | return stack.getMiningSpeed(block) > 1; 54 | }; 55 | if (effective("diamond_pickaxe", block)) 56 | return "pickaxe"; 57 | if (effective("diamond_shovel", block)) 58 | return "shovel"; 59 | if (effective("diamond_axe", block)) 60 | return "axe"; 61 | if (effective("diamond_sword", block)) 62 | return "sword"; 63 | return null; 64 | }; 65 | 66 | var centerPlayer = function() { 67 | player.pressingForward = false; 68 | while (player.motionX * player.motionX + player.motionZ * player.motionZ > 0.001) 69 | tick(); 70 | if (Math.abs(Math.floor(player.x) + 0.5 - player.x) >= 0.2 || Math.abs(Math.floor(player.z) + 0.5 - player.z) >= 0.2) 71 | return player.moveTo(Math.floor(player.x) + 0.5, Math.floor(player.z) + 0.5); 72 | return true; 73 | }; 74 | 75 | var clearWay = function(x, y, z, dx, dz) { 76 | // mine block in front of player's face if necessary 77 | if (!canWalkThrough(world.getBlock(x + dx, y + 1, z + dz))) { 78 | if (!mineBlock(x + dx, y + 1, z + dz)) { 79 | throw new Error(); 80 | } 81 | } 82 | // mine block in front of player's feet if necessary 83 | if (!canWalkThrough(world.getBlock(x + dx, y, z + dz))) { 84 | if (!mineBlock(x + dx, y, z + dz)) { 85 | throw new Error(); 86 | } 87 | } 88 | // build bridge if necessary 89 | if (!canWalkOn(world.getBlock(x + dx, y - 1, z + dz))) { 90 | if (!makeBridge(x, y, z, dx, dz)) { 91 | throw new Error(); 92 | } 93 | } 94 | return true; 95 | }; 96 | 97 | var placeBlock = function(x, y, z) { 98 | if (!player.pick(function(itemNbt) { 99 | return itemNbt.id === "minecraft:cobblestone" || itemNbt.id === "minecraft:stone"; 100 | })) 101 | throw new Error(); 102 | 103 | if (player.rightClick(x - 1, y, z, "east")) return true; 104 | if (player.rightClick(x + 1, y, z, "west")) return true; 105 | if (player.rightClick(x, y, z - 1, "south")) return true; 106 | if (player.rightClick(x, y, z + 1, "north")) return true; 107 | if (player.rightClick(x, y - 1, z, "up")) return true; 108 | if (player.rightClick(x, y + 1, z, "down")) return true; 109 | return false; 110 | }; 111 | 112 | var mineBlock = function(x, y, z) { 113 | var toolMaterialOrder = ["netherite","diamond", "iron", "stone", "wooden", "golden"]; 114 | var tool = getTool(world.getBlock(x, y, z)); 115 | if (tool) { 116 | var picked = false; 117 | for (var i = 0; i < toolMaterialOrder.length; i++) { 118 | if (player.pick(toolMaterialOrder[i] + "_" + tool)) { 119 | picked = true; 120 | break; 121 | } 122 | } 123 | if (!picked && !world.getBlockState(x, y, z).canBreakByHand) 124 | throw new Error(); 125 | } 126 | 127 | var oldBlock = world.getBlock(x, y, z); 128 | if (oldBlock === "air") return true; 129 | var failCount = 0; 130 | do { 131 | if (!player.longMineBlock(x, y, z)) { 132 | failCount++; 133 | tick(); 134 | } 135 | if (failCount > 5) 136 | throw new Error("Block pos: (" + x + ", " + y + ", " + z + ")"); 137 | } while (world.getBlock(x, y, z) === oldBlock); 138 | 139 | var stateAbove = world.getBlockState(x, y + 1, z); 140 | if (stateAbove.fallable) { 141 | while (world.getBlock(x, y, z) !== stateAbove.block) 142 | tick(); 143 | if (!mineBlock(x, y, z)) 144 | throw new Error(); 145 | } 146 | 147 | return true; 148 | }; 149 | 150 | var makeBridge = function(x, y, z, dx, dz) { 151 | // face backwards 152 | player.lookAt(player.x - dx, player.y, player.z - dz); 153 | // sneak backwards 154 | var continueSneaking = function() { 155 | player.pressingBack = true; 156 | tick(); 157 | timeout++; 158 | if (timeout % 20 === 0) { 159 | player.pressingBack = false; 160 | player.sneaking = false; 161 | if (!clearWay(x, y, z, dx, dz)) { 162 | player.unblockInput(); 163 | throw new Error(); 164 | } 165 | player.sneaking = true; 166 | } 167 | return true; 168 | }; 169 | player.blockInput(); 170 | player.sneaking = true; 171 | var timeout = 0; 172 | while (Math.floor(player.x) === x && Math.floor(player.z) === z) { 173 | if (!continueSneaking()) 174 | throw new Error(); 175 | } 176 | // keep sneaking for an extra 5 ticks to make sure there's part of the block in view 177 | for (var i = 0; i < 5; i++) { 178 | if (!continueSneaking()) 179 | throw new Error(); 180 | } 181 | player.pressingBack = false; 182 | player.sneaking = false; 183 | player.unblockInput(); 184 | return placeBlock(x + dx, y - 1, z + dz); 185 | }; 186 | 187 | var mineNearbyOre = function(x, y, z) { 188 | centerPlayer(); 189 | 190 | var cardinals4 = [[-1, 0], [1, 0], [0, -1], [0, 1]]; 191 | var cardinals6 = [[-1, 0, 0], [1, 0, 0], [0, 0, -1], [0, 0, 1], [0, -1, 0], [0, 1, 0]]; 192 | 193 | // mine blocks around head and above head 194 | for (var dy = 1; dy <= 2; dy++) { 195 | if (canWalkThrough(world.getBlock(x, y + dy, z))) { 196 | for (var dir = 0; dir < cardinals6.length; dir++) { 197 | var ddx = cardinals6[dir][0], ddy = cardinals6[dir][1], ddz = cardinals6[dir][2]; 198 | if (isWantedBlock(world.getBlock(x + ddx, y + dy + ddy, z + ddz))) { 199 | if (!mineBlock(x + ddx, y + dy + ddy, z + ddz)) 200 | throw new Error(); 201 | } 202 | } 203 | } 204 | } 205 | 206 | // step up 207 | for (var i = 0; i < 4; i++) { 208 | var dx = cardinals4[i][0], dz = cardinals4[i][1]; 209 | 210 | // check if we want to step up 211 | if (!canWalkThrough(world.getBlock(x + dx, y + 2, z + dz))) continue; 212 | if (!canWalkThrough(world.getBlock(x + dx, y + 1, z + dz)) && !canWalkThrough(world.getBlock(x, y + 2, z))) continue; 213 | var wantToStepUp = false; 214 | for (var j = 0; j < 6; j++) { 215 | var ddx = cardinals6[j][0], ddy = cardinals6[j][1], ddz = cardinals6[j][2]; 216 | if (isWantedBlock(world.getBlock(x + dx + ddx, y + 2 + ddy, z + dz + ddz))) { 217 | wantToStepUp = true; 218 | break; 219 | } 220 | } 221 | if (!wantToStepUp) continue; 222 | 223 | // mine block(s) to allow us to step up if necessary 224 | if (!canWalkThrough(world.getBlock(x + dx, y + 1, z + dz))) 225 | if (!mineBlock(x + dx, y + 1, z + dz)) 226 | throw new Error(); 227 | if (!canWalkThrough(world.getBlock(x, y + 2, z))) 228 | if (!mineBlock(x, y + 2, z)) 229 | throw new Error(); 230 | if (!canWalkOn(world.getBlock(x + dx, y, z + dz))) 231 | if (!placeBlock(x + dx, y, z + dz)) 232 | continue; 233 | 234 | centerPlayer(); 235 | 236 | // do the step up 237 | if (!player.moveTo(x + dx + 0.5, z + dz + 0.5)) throw new Error(); 238 | if (!mineNearbyOre(x + dx, y + 1, z + dz)) throw new Error(); 239 | centerPlayer(); 240 | for (var dy = 2; dy >= 0; dy--) { 241 | if (!canWalkThrough(world.getBlock(x, y + dy, z))) 242 | if (!mineBlock(x, y + dy, z)) 243 | throw new Error(); 244 | } 245 | if (!canWalkOn(world.getBlock(x, y - 1, z))) 246 | if (!placeBlock(x, y - 1, z)) throw new Error(); 247 | if (!player.moveTo(x + 0.5, z + 0.5)) throw new Error(); 248 | } 249 | 250 | // mine blocks around feet level 251 | for (var i = 0; i < 4; i++) { 252 | var dx = cardinals4[i][0], dz = cardinals4[i][1]; 253 | if (isWantedBlock(world.getBlock(x + dx, y, z + dz))) { 254 | if (!mineBlock(x + dx, y, z + dz)) 255 | throw new Error(); 256 | 257 | if (!canWalkOn(world.getBlock(x + dx, y - 1, z + dz))) 258 | if (!placeBlock(x + dx, y - 1, z + dz)) 259 | continue; 260 | 261 | if (!canWalkThrough(world.getBlock(x + dx, y + 1, z + dz))) { 262 | if (!mineBlock(x + dx, y + 1, z + dz)) 263 | throw new Error(); 264 | } 265 | 266 | centerPlayer(); 267 | if (!player.moveTo(x + dx + 0.5, z + dz + 0.5)) throw new Error(); 268 | if (!mineNearbyOre(x + dx, y, z + dz)) throw new Error(); 269 | centerPlayer(); 270 | for (var dy = 1; dy >= 0; dy--) { 271 | if (!canWalkThrough(world.getBlock(x, y + dy, z))) 272 | if (!mineBlock(x, y + dy, z)) 273 | throw new Error(); 274 | } 275 | if (!canWalkOn(world.getBlock(x, y - 1, z))) 276 | if (!placeBlock(x, y - 1, z)) throw new Error(); 277 | if (!player.moveTo(x + 0.5, z + 0.5)) throw new Error(); 278 | } 279 | } 280 | 281 | // keep mining for blocks possibly exposed by the mining operation 282 | for (var dy = 1; dy >= 0; dy--) { 283 | for (var i = 0; i < 4; i++) { 284 | var dx = cardinals4[i][0], dz = cardinals4[i][1]; 285 | if (canWalkThrough(world.getBlock(x + dx, y + dy, z + dz))) { 286 | for (var j = 0; j < 6; j++) { 287 | var ddx = cardinals6[j][0], ddy = cardinals6[j][1], ddz = cardinals6[j][2]; 288 | if (ddy === -1 && dy === 0) continue; // mineNearbyOre doesn't mine straight down 289 | if (isWantedBlock(world.getBlock(x + dx + ddx, y + dy + ddy, z + dz + ddz))) { 290 | if (!clearWay(x, y, z, dx, dz)) 291 | throw new Error(); 292 | 293 | centerPlayer(); 294 | if (!player.moveTo(x + dx + 0.5, z + dz + 0.5)) throw new Error(); 295 | if (!mineNearbyOre(x + dx, y, z + dz)) throw new Error(); 296 | centerPlayer(); 297 | for (var dy = 1; dy >= 0; dy--) { 298 | if (!canWalkThrough(world.getBlock(x, y + dy, z))) 299 | if (!mineBlock(x, y + dy, z)) 300 | throw new Error(); 301 | } 302 | if (!canWalkOn(world.getBlock(x, y - 1, z))) 303 | if (!placeBlock(x, y - 1, z)) throw new Error(); 304 | if (!player.moveTo(x + 0.5, z + 0.5)) throw new Error(); 305 | } 306 | } 307 | } 308 | } 309 | } 310 | 311 | // mine block below feet level 312 | for (var i = 0; i < 4; i++) { 313 | var dx = cardinals4[i][0], dz = cardinals4[i][1]; 314 | 315 | if (canWalkThrough(world.getBlock(x + dx, y, z + dz)) && isWantedBlock(world.getBlock(x + dx, y - 1, z + dz))) { 316 | if (!mineBlock(x + dx, y - 1, z + dz)) 317 | throw new Error(); 318 | if (!canWalkOn(world.getBlock(x + dx, y - 2, z + dz))) 319 | if (!placeBlock(x + dx, y - 2, z + dz)) 320 | continue; 321 | 322 | // collect the block 323 | if (!player.moveTo(x + dx + 0.5, z + dz + 0.5)) throw new Error(); 324 | if (!mineNearbyOre(x + dx, y - 1, z + dz)) throw new Error(); 325 | centerPlayer(); 326 | if (!canWalkThrough(x + dx, y + 1, z + dz)) 327 | if (!mineBlock(x + dx, y + 1, z + dz)) 328 | throw new Error(); 329 | for (var dy = 1; dy >= 0; dy--) { 330 | if (!canWalkThrough(world.getBlock(x, y + dy, z))) 331 | if (!mineBlock(x, y + dy, z)) 332 | throw new Error(); 333 | } 334 | if (!canWalkOn(world.getBlock(x, y - 1, z))) 335 | if (!placeBlock(x, y - 1, z)) throw new Error(); 336 | if (!player.moveTo(x + 0.5, z + 0.5)) throw new Error(); 337 | } 338 | } 339 | 340 | return true; 341 | }; 342 | 343 | var makeTunnel = function(x, y, z, dx, dz) { 344 | centerPlayer(); 345 | 346 | if (!clearWay(x, y, z, dx, dz)) 347 | throw new Error(); 348 | 349 | // walk to next spot 350 | if (!player.moveTo(x + dx + 0.5, z + dz + 0.5, false)) 351 | throw new Error(); 352 | 353 | // place torch 354 | if (world.getBlockLight(x, y, z) <= 1) { 355 | if (!player.pick("torch")) 356 | throw new Error(); 357 | if (!player.rightClick(x, y - 1, z, "up")) 358 | print("Couldn't place torch"); 359 | } 360 | 361 | if (!mineNearbyOre(x + dx, y, z + dz)) throw new Error(); 362 | 363 | return true; 364 | }; 365 | 366 | var makeTunnelLoop = function() { 367 | try { 368 | var x = Math.floor(player.x); 369 | var y = Math.floor(player.y); 370 | var z = Math.floor(player.z); 371 | var dx = 1, dz = 0; 372 | 373 | while (makeTunnel(x, y, z, dx, dz)) { 374 | x += dx; 375 | z += dz; 376 | } 377 | } finally { 378 | mainThread.kill(); 379 | } 380 | }; 381 | 382 | var pickUpItemsLoop = function() { 383 | while (true) { 384 | // find items needed to be picked up 385 | var items = $("@e[type=item,distance=..3]"); 386 | var itemsWanted = []; 387 | for (var i = 0; i < items.length; i++) { 388 | var item = items[i]; 389 | if (Math.abs(item.x - player.x) < 1.425 && Math.abs(item.z - player.z) < 1.425 390 | && item.y - player.y >= -0.5 && item.y - player.y < 2.3 - player.standingEyeHeight + player.eyeHeight) { 391 | if (isWantedItemEntity(item)) { 392 | var nbt = item.nbt; 393 | var itemId; 394 | if (nbt.Item && itemsWanted.indexOf(itemId = nbt.Item.id) === -1) 395 | itemsWanted.push(itemId); 396 | } 397 | } 398 | } 399 | 400 | // throw out items from inventory if there's no space 401 | var playerItems = player.inventory.items; 402 | var cobblestoneCount = 0; 403 | for (var slot = 0; slot < 36; slot++) { 404 | if (playerItems[slot].Count < 64 && itemsWanted.indexOf(playerItems[slot].id) !== -1) 405 | itemsWanted.splice(itemsWanted.indexOf(playerItems[slot].id), 1); 406 | if (playerItems[slot].id === "minecraft:cobblestone" || playerItems[slot].id === "minecraft:stone") 407 | cobblestoneCount++; 408 | } 409 | for (var slot = 0; slot < 36 && itemsWanted.length > 0; slot++) { 410 | if (playerItems[slot].id === "minecraft:air") 411 | itemsWanted.pop(); 412 | } 413 | for (var slot = 0; slot < 36 && itemsWanted.length > 0; slot++) { 414 | var itemId = playerItems[slot].id; 415 | var canThrow = false; 416 | if ((itemId === "minecraft:cobblestone" || itemId === "minecraft:stone") && cobblestoneCount > 1) { 417 | cobblestoneCount--; 418 | canThrow = true; 419 | } else if (itemId === "minecraft:dirt" || itemId === "minecraft:gravel" 420 | || itemId === "minecraft:granite" || itemId === "minecraft:diorite" 421 | || itemId === "minecraft:andesite") { 422 | canThrow = true; 423 | } 424 | if (canThrow) { 425 | player.inventory.click(slot, {type: 'throw', rightClick: true}); 426 | itemsWanted.pop(); 427 | } 428 | } 429 | 430 | if (itemsWanted.length !== 0) 431 | break; 432 | 433 | tick(); 434 | } 435 | mainThread.kill(); 436 | }; 437 | 438 | var mainThread = Thread.current; 439 | 440 | new Thread(pickUpItemsLoop).run(); 441 | new Thread(makeTunnelLoop).run(); 442 | 443 | while (true) tick(); // keep running until killed 444 | -------------------------------------------------------------------------------- /src/main/java/net/earthcomputer/clientcommands/script/ScriptManager.java: -------------------------------------------------------------------------------- 1 | package net.earthcomputer.clientcommands.script; 2 | 3 | import com.mojang.brigadier.exceptions.CommandSyntaxException; 4 | import com.mojang.brigadier.exceptions.DynamicCommandExceptionType; 5 | import com.mojang.brigadier.suggestion.SuggestionProvider; 6 | import com.mojang.logging.LogUtils; 7 | import net.earthcomputer.clientcommands.task.LongTask; 8 | import net.earthcomputer.clientcommands.task.SimpleTask; 9 | import net.earthcomputer.clientcommands.task.TaskManager; 10 | import net.fabricmc.fabric.api.client.command.v1.FabricClientCommandSource; 11 | import net.minecraft.client.MinecraftClient; 12 | import net.minecraft.client.input.Input; 13 | import net.minecraft.client.network.ClientPlayerEntity; 14 | import net.minecraft.command.CommandSource; 15 | import net.minecraft.text.TranslatableText; 16 | import org.apache.commons.io.FileUtils; 17 | import org.graalvm.polyglot.Context; 18 | import org.jetbrains.annotations.Nullable; 19 | import org.slf4j.Logger; 20 | import xyz.wagyourtail.jsmacros.client.JsMacros; 21 | import xyz.wagyourtail.jsmacros.core.Core; 22 | import xyz.wagyourtail.jsmacros.core.config.ScriptTrigger; 23 | import xyz.wagyourtail.jsmacros.core.language.BaseScriptContext; 24 | import xyz.wagyourtail.jsmacros.core.language.EventContainer; 25 | import xyz.wagyourtail.jsmacros.core.library.impl.FJsMacros; 26 | import xyz.wagyourtail.jsmacros.core.library.impl.FWrapper; 27 | 28 | import java.io.IOException; 29 | import java.io.UncheckedIOException; 30 | import java.lang.ref.WeakReference; 31 | import java.nio.charset.StandardCharsets; 32 | import java.nio.file.FileVisitOption; 33 | import java.nio.file.Files; 34 | import java.nio.file.Path; 35 | import java.util.ArrayList; 36 | import java.util.Collections; 37 | import java.util.HashMap; 38 | import java.util.Iterator; 39 | import java.util.LinkedHashSet; 40 | import java.util.List; 41 | import java.util.Map; 42 | import java.util.Set; 43 | import java.util.WeakHashMap; 44 | import java.util.concurrent.Callable; 45 | import java.util.concurrent.CompletableFuture; 46 | import java.util.concurrent.Semaphore; 47 | import java.util.function.Consumer; 48 | import java.util.function.Predicate; 49 | 50 | public class ScriptManager { 51 | private static final Logger LOGGER = LogUtils.getLogger(); 52 | private static final DynamicCommandExceptionType SCRIPT_NOT_FOUND_EXCEPTION = new DynamicCommandExceptionType(arg -> new TranslatableText("commands.cscript.notFound", arg)); 53 | 54 | private static ClientCommandsLanguage language; 55 | 56 | private static Path legacyScriptsDir; 57 | private static final Map legacyScripts = new HashMap<>(); 58 | 59 | private static final List runningThreads = new ArrayList<>(); 60 | private static final Map, AdditionalContextInfo> additionalContextInfo = new WeakHashMap<>(); 61 | 62 | public static void inject() { 63 | LOGGER.info("Injecting clientcommands into jsmacros"); 64 | language = new ClientCommandsLanguage(".clientcommands", JsMacros.core); 65 | JsMacros.core.addLanguage(language); 66 | JsMacros.core.libraryRegistry.addLibrary(ClientCommandsLibrary.class); 67 | } 68 | 69 | public static void reloadLegacyScripts() { 70 | LOGGER.info("Reloading legacy clientcommands scripts"); 71 | 72 | legacyScriptsDir = ClientCommandsScripting.configDir.resolve("scripts"); 73 | 74 | legacyScripts.clear(); 75 | 76 | if (!Files.exists(legacyScriptsDir)) { 77 | return; 78 | } 79 | 80 | try { 81 | Files.walk(legacyScriptsDir, FileVisitOption.FOLLOW_LINKS) 82 | .filter(Files::isRegularFile) 83 | .forEach(path -> { 84 | try { 85 | legacyScripts.put(legacyScriptsDir.relativize(path).toString(), FileUtils.readFileToString(path.toFile(), StandardCharsets.UTF_8)); 86 | } catch (IOException e) { 87 | throw new UncheckedIOException(e); 88 | } 89 | }); 90 | } catch (IOException | UncheckedIOException e) { 91 | LOGGER.error("Error reloading clientcommands scripts", e); 92 | } 93 | } 94 | 95 | public static SuggestionProvider getScriptSuggestions() { 96 | return (ctx, builder) -> CompletableFuture.supplyAsync(() -> { 97 | Path macroFolder = JsMacros.core.config.macroFolder.toPath(); 98 | if (!Files.exists(macroFolder)) { 99 | return builder.build(); 100 | } 101 | try { 102 | return CommandSource.suggestMatching(Files.walk(macroFolder) 103 | .filter(Files::isRegularFile) 104 | .map(path -> macroFolder.relativize(path).toString()), builder).join(); 105 | } catch (IOException e) { 106 | return builder.build(); 107 | } 108 | }); 109 | } 110 | 111 | public static Set getLegacyScriptNames() { 112 | return Collections.unmodifiableSet(legacyScripts.keySet()); 113 | } 114 | 115 | @Nullable 116 | static ThreadInstance currentThread() { 117 | AdditionalContextInfo additionalContext = additionalContext(); 118 | ThreadInstance thread = additionalContext.currentClientcommandsThread.get(); 119 | if (thread != null) { 120 | return thread; 121 | } 122 | Thread currentThread = Thread.currentThread(); 123 | if (currentThread != scriptContext().getMainThread()) { 124 | return null; 125 | } 126 | thread = new ScriptThread(() -> null, false).thread; 127 | thread.mainThread = new WeakReference<>(currentThread); 128 | runThread(thread, true); 129 | return thread; 130 | } 131 | 132 | static ThreadInstance requireCurrentThread() { 133 | ThreadInstance thread = currentThread(); 134 | if (thread == null) { 135 | throw new IllegalStateException("This operation must be called in a clientcommands thread or the main thread"); 136 | } 137 | return thread; 138 | } 139 | 140 | @Nullable 141 | static ThreadInstance getFirstRunningThread() { 142 | AdditionalContextInfo additionalContext = additionalContext(); 143 | Iterator threadItr = additionalContext.runningClientcommandsThreads.iterator(); 144 | if (!threadItr.hasNext()) { 145 | return null; 146 | } 147 | 148 | ThreadInstance thread = threadItr.next(); 149 | if (thread.mainThread != null || isMainThreadRunning()) { 150 | return thread; 151 | } 152 | 153 | threadItr.remove(); 154 | if (!threadItr.hasNext()) { 155 | return null; 156 | } 157 | return threadItr.next(); 158 | } 159 | 160 | static boolean isMainThreadRunning() { 161 | Thread mainThread = scriptContext().getMainThread(); 162 | return mainThread != null && mainThread.isAlive(); 163 | } 164 | 165 | @Nullable 166 | static EventContainer currentContext() { 167 | return scriptContext().getBoundEvents().get(Thread.currentThread()); 168 | } 169 | 170 | static BaseScriptContext scriptContext() { 171 | Thread current = Thread.currentThread(); 172 | return Core.instance.getContexts().stream().filter(e -> e.getBoundThreads().contains(current)).findFirst().orElseThrow(); 173 | } 174 | 175 | static AdditionalContextInfo additionalContext() { 176 | return additionalContextInfo.computeIfAbsent(scriptContext(), k -> new AdditionalContextInfo()); 177 | } 178 | 179 | static T getBinding(String name) { 180 | Context context = (Context) scriptContext().getContext(); 181 | if (context == null) { 182 | throw new IllegalStateException("Could not get " + name + " because context is null"); 183 | } 184 | return context.getBindings("js").getMember(name).asHostObject(); 185 | } 186 | 187 | static FJsMacros jsMacros() { 188 | return getBinding("JsMacros"); 189 | } 190 | 191 | static FWrapper javaWrapper() { 192 | return getBinding("JavaWrapper"); 193 | } 194 | 195 | public static void executeScript(String scriptFile) throws CommandSyntaxException { 196 | if (!Files.exists(JsMacros.core.config.macroFolder.toPath().resolve(scriptFile))) { 197 | throw SCRIPT_NOT_FOUND_EXCEPTION.create(scriptFile); 198 | } 199 | 200 | JsMacros.core.exec(new ScriptTrigger(ScriptTrigger.TriggerType.EVENT, "", scriptFile, true), null); 201 | } 202 | 203 | public static void executeLegacyScript(String scriptName) throws CommandSyntaxException { 204 | String scriptSource = legacyScripts.get(scriptName); 205 | if (scriptSource == null) 206 | throw SCRIPT_NOT_FOUND_EXCEPTION.create(scriptName); 207 | 208 | language.trigger(scriptSource, legacyScriptsDir.resolve(scriptName).toFile(), null, null); 209 | } 210 | 211 | static ThreadInstance createThread(ScriptThread handle, Callable task, boolean daemon) { 212 | ThreadInstance thread = new ThreadInstance(); 213 | thread.handle = handle; 214 | thread.onRun = task; 215 | thread.task = new SimpleTask() { 216 | @Override 217 | public boolean condition() { 218 | return thread.running; 219 | } 220 | 221 | @Override 222 | protected void onTick() { 223 | } 224 | }; 225 | thread.daemon = daemon; 226 | 227 | return thread; 228 | } 229 | 230 | static void runThread(ThreadInstance thread, boolean mainThread) { 231 | ThreadInstance parentThread = mainThread ? null : currentThread(); 232 | 233 | TaskManager.addTask("cscript", thread.task); 234 | runningThreads.add(thread); 235 | thread.running = true; 236 | 237 | if (parentThread != null) { 238 | parentThread.children.add(thread); 239 | thread.parent = parentThread; 240 | } 241 | 242 | if (mainThread) { 243 | AdditionalContextInfo additionalContext = additionalContext(); 244 | additionalContext.currentClientcommandsThread.set(thread); 245 | // insert main thread at the start of the running clientcommands threads 246 | ArrayList copy = new ArrayList<>(additionalContext.runningClientcommandsThreads); 247 | additionalContext.runningClientcommandsThreads.clear(); 248 | additionalContext.runningClientcommandsThreads.add(thread); 249 | additionalContext.runningClientcommandsThreads.addAll(copy); 250 | } else { 251 | FWrapper javaWrapper = javaWrapper(); 252 | FWrapper.WrappedThread currentTask = javaWrapper.tasks.peek(); 253 | assert currentTask != null && currentTask.thread == Thread.currentThread(); 254 | // Hack: clear the task list to make our task run straight away 255 | List oldTasks = new ArrayList<>(javaWrapper.tasks); 256 | javaWrapper.tasks.clear(); 257 | 258 | Semaphore threadStarted = new Semaphore(0); 259 | 260 | Context context = (Context) scriptContext().getContext(); 261 | assert context != null; 262 | 263 | context.leave(); 264 | 265 | javaWrapper.methodToJavaAsync(args -> { 266 | // add back the tasks we cleared. 267 | // note that this adds the parent thread task first, 268 | javaWrapper().tasks.addAll(oldTasks); 269 | threadStarted.release(); 270 | 271 | AdditionalContextInfo additionalContext = additionalContext(); 272 | additionalContext.currentClientcommandsThread.set(thread); 273 | additionalContext.runningClientcommandsThreads.add(thread); 274 | 275 | try { 276 | thread.onRun.call(); 277 | } catch (Throwable e) { 278 | if (!thread.killed && !thread.task.isCompleted()) { 279 | Core.instance.profile.logError(e); 280 | } 281 | } 282 | 283 | runningThreads.remove(thread); 284 | additionalContext.runningClientcommandsThreads.remove(thread); 285 | additionalContext.currentClientcommandsThread.set(null); 286 | 287 | if (thread.parent != null) { 288 | thread.parent.children.remove(thread); 289 | } 290 | for (ThreadInstance child : thread.children) { 291 | child.parent = null; 292 | if (child.daemon) { 293 | child.killed = true; 294 | } 295 | } 296 | thread.running = false; 297 | 298 | return null; 299 | }).run(); 300 | 301 | // Wait for started thread to either finish or reach a tick() method 302 | try { 303 | threadStarted.acquire(); 304 | 305 | FWrapper.WrappedThread joinable = javaWrapper.tasks.peek(); 306 | while (true) { 307 | assert joinable != null; 308 | if (joinable.thread == Thread.currentThread()) break; 309 | joinable.waitFor(); 310 | joinable = javaWrapper.tasks.peek(); 311 | } 312 | } catch (InterruptedException e) { 313 | thread.kill(); 314 | } 315 | 316 | context.enter(); 317 | } 318 | } 319 | 320 | static void passTick() { 321 | ThreadInstance thread = requireCurrentThread(); 322 | try { 323 | if (thread == getFirstRunningThread()) { 324 | jsMacros().waitForEvent("JoinedTick", null, javaWrapper().methodToJava(args -> { 325 | EventContainer context = currentContext(); 326 | if (context != null) { 327 | context.releaseLock(); 328 | } 329 | try { 330 | javaWrapper().deferCurrentTask(); 331 | } catch (InterruptedException e) { 332 | thread.kill(); 333 | } 334 | return null; 335 | })); 336 | } else { 337 | javaWrapper().deferCurrentTask(); 338 | } 339 | } catch (InterruptedException e) { 340 | thread.kill(); 341 | } 342 | if (thread.daemon && thread.parent != null && thread.parent.isKilled()) { 343 | thread.parent = null; 344 | thread.kill(); 345 | } 346 | if (thread.killed || thread.task.isCompleted()) { 347 | throw new ScriptInterruptedException(); 348 | } 349 | } 350 | 351 | static void blockInput(boolean blockInput) { 352 | requireCurrentThread().blockingInput = blockInput; 353 | } 354 | 355 | static boolean isCurrentScriptBlockingInput() { 356 | return requireCurrentThread().blockingInput; 357 | } 358 | 359 | public static boolean blockingInput() { 360 | return anyRunningThread(t -> t.blockingInput); 361 | } 362 | 363 | static Input getScriptInput() { 364 | return requireCurrentThread().input; 365 | } 366 | 367 | public static void copyScriptInputToPlayer(boolean inSneakingPose) { 368 | ClientPlayerEntity player = MinecraftClient.getInstance().player; 369 | if (player == null) { 370 | return; 371 | } 372 | Input playerInput = player.input; 373 | forEachRunningThread(thread -> { 374 | playerInput.pressingForward |= thread.input.pressingForward; 375 | playerInput.pressingBack |= thread.input.pressingBack; 376 | playerInput.pressingLeft |= thread.input.pressingLeft; 377 | playerInput.pressingRight |= thread.input.pressingRight; 378 | playerInput.jumping |= thread.input.jumping; 379 | playerInput.sneaking |= thread.input.sneaking; 380 | }); 381 | playerInput.movementForward = playerInput.pressingForward ^ playerInput.pressingBack ? (playerInput.pressingForward ? 1 : -1) : 0; 382 | playerInput.movementSideways = playerInput.pressingLeft ^ playerInput.pressingRight ? (playerInput.pressingLeft ? 1 : -1) : 0; 383 | if (playerInput.sneaking || inSneakingPose) { 384 | playerInput.movementSideways = (float)(playerInput.movementSideways * 0.3D); 385 | playerInput.movementForward = (float)(playerInput.movementForward * 0.3D); 386 | } 387 | } 388 | 389 | static void setSprinting(boolean sprinting) { 390 | requireCurrentThread().sprinting = sprinting; 391 | } 392 | 393 | static boolean isCurrentThreadSprinting() { 394 | return requireCurrentThread().sprinting; 395 | } 396 | 397 | public static boolean isSprinting() { 398 | return anyRunningThread(t -> t.sprinting); 399 | } 400 | 401 | private static void forEachRunningThread(Consumer consumer) { 402 | anyRunningThread(t -> { 403 | consumer.accept(t); 404 | return false; 405 | }); 406 | } 407 | 408 | private static boolean anyRunningThread(Predicate predicate) { 409 | boolean result = false; 410 | Iterator itr = runningThreads.iterator(); 411 | while (itr.hasNext()) { 412 | ThreadInstance thread = itr.next(); 413 | if (thread.isKilled()) { 414 | itr.remove(); 415 | } else { 416 | result |= predicate.test(thread); 417 | } 418 | } 419 | return result; 420 | } 421 | 422 | static class ThreadInstance { 423 | ScriptThread handle; 424 | 425 | // A reference to the current thread if this is the main thread 426 | WeakReference mainThread = null; 427 | Callable onRun; 428 | boolean daemon; 429 | boolean paused; 430 | private boolean killed; 431 | ThreadInstance parent; 432 | List children = new ArrayList<>(0); 433 | 434 | boolean running; 435 | private LongTask task; 436 | private boolean blockingInput = false; 437 | @SuppressWarnings("NewExpressionSideOnly") 438 | private final Input input = new Input(); 439 | private boolean sprinting = false; 440 | 441 | boolean isKilled() { 442 | if (killed) { 443 | return true; 444 | } 445 | if (mainThread != null) { 446 | Thread mainThread = this.mainThread.get(); 447 | if (mainThread == null || !mainThread.isAlive()) { 448 | running = false; 449 | return killed = true; 450 | } 451 | } 452 | return false; 453 | } 454 | 455 | void kill() { 456 | killed = true; 457 | } 458 | } 459 | 460 | static class AdditionalContextInfo { 461 | Set runningClientcommandsThreads = new LinkedHashSet<>(); 462 | ThreadLocal currentClientcommandsThread = new ThreadLocal<>(); 463 | } 464 | 465 | } 466 | -------------------------------------------------------------------------------- /src/main/java/net/earthcomputer/clientcommands/script/ScriptPlayer.java: -------------------------------------------------------------------------------- 1 | package net.earthcomputer.clientcommands.script; 2 | 3 | import com.google.common.collect.ImmutableSet; 4 | import net.earthcomputer.clientcommands.MathUtil; 5 | import net.earthcomputer.clientcommands.features.PathfindingHints; 6 | import net.earthcomputer.clientcommands.features.PlayerPathfinder; 7 | import net.earthcomputer.clientcommands.features.Relogger; 8 | import net.earthcomputer.clientcommands.interfaces.IBlockChangeListener; 9 | import net.earthcomputer.clientcommands.script.ducks.IMinecraftClient; 10 | import net.minecraft.block.BlockState; 11 | import net.minecraft.client.MinecraftClient; 12 | import net.minecraft.client.network.ClientPlayerEntity; 13 | import net.minecraft.client.network.ClientPlayerInteractionManager; 14 | import net.minecraft.entity.Entity; 15 | import net.minecraft.entity.ai.pathing.Path; 16 | import net.minecraft.entity.ai.pathing.PathNode; 17 | import net.minecraft.entity.ai.pathing.PathNodeType; 18 | import net.minecraft.entity.player.PlayerInventory; 19 | import net.minecraft.item.ItemStack; 20 | import net.minecraft.network.packet.c2s.play.PlayerMoveC2SPacket; 21 | import net.minecraft.screen.AbstractRecipeScreenHandler; 22 | import net.minecraft.screen.CraftingScreenHandler; 23 | import net.minecraft.screen.PlayerScreenHandler; 24 | import net.minecraft.screen.ScreenHandler; 25 | import net.minecraft.screen.slot.Slot; 26 | import net.minecraft.screen.slot.SlotActionType; 27 | import net.minecraft.util.ActionResult; 28 | import net.minecraft.util.Hand; 29 | import net.minecraft.util.hit.BlockHitResult; 30 | import net.minecraft.util.hit.HitResult; 31 | import net.minecraft.util.math.BlockPos; 32 | import net.minecraft.util.math.Box; 33 | import net.minecraft.util.math.Direction; 34 | import net.minecraft.util.math.MathHelper; 35 | import net.minecraft.util.math.Vec3d; 36 | import net.minecraft.world.BlockView; 37 | import net.minecraft.world.World; 38 | import org.graalvm.polyglot.Value; 39 | 40 | import java.util.ArrayList; 41 | import java.util.HashMap; 42 | import java.util.List; 43 | import java.util.Locale; 44 | import java.util.Map; 45 | import java.util.function.BooleanSupplier; 46 | import java.util.function.Predicate; 47 | import java.util.function.Supplier; 48 | 49 | @SuppressWarnings("unused") 50 | public class ScriptPlayer extends ScriptLivingEntity { 51 | static final ScriptPlayer INSTANCE = new ScriptPlayer(); 52 | 53 | ScriptPlayer() { 54 | super(null); 55 | } 56 | 57 | @Override 58 | Entity getNullableEntity() { 59 | return MinecraftClient.getInstance().player; 60 | } 61 | 62 | @Override 63 | ClientPlayerEntity getEntity() { 64 | return (ClientPlayerEntity) super.getEntity(); 65 | } 66 | 67 | public boolean snapTo(double x, double y, double z) { 68 | return snapTo(x, y, z, false); 69 | } 70 | 71 | public boolean snapTo(double x, double y, double z, boolean sync) { 72 | double dx = x - getX(); 73 | double dy = y - getY(); 74 | double dz = z - getZ(); 75 | if (dx * dx + dy * dy + dz * dz > 0.5 * 0.5) 76 | return false; 77 | 78 | getEntity().setPos(x, y, z); 79 | 80 | if (sync) 81 | getEntity().networkHandler.sendPacket(new PlayerMoveC2SPacket.PositionAndOnGround(x, y, z, getEntity().isOnGround())); 82 | 83 | return true; 84 | } 85 | 86 | public boolean moveTo(double x, double z) { 87 | return moveTo(x, z, true); 88 | } 89 | 90 | public boolean moveTo(double x, double z, boolean smart) { 91 | if (getEntity().squaredDistanceTo(x, getY(), z) < 0.01) { 92 | snapTo(x, getY(), z); 93 | return true; 94 | } 95 | 96 | lookAt(x, getY() + getEyeHeight(), z); 97 | boolean wasBlockingInput = ScriptManager.isCurrentScriptBlockingInput(); 98 | ScriptManager.blockInput(true); 99 | boolean wasPressingForward = ScriptManager.getScriptInput().pressingForward; 100 | ScriptManager.getScriptInput().pressingForward = true; 101 | 102 | double lastDistanceSq = getEntity().squaredDistanceTo(x, getY(), z); 103 | int tickCounter = 0; 104 | boolean successful = true; 105 | 106 | do { 107 | if (smart) { 108 | double dx = x - getX(); 109 | double dz = z - getZ(); 110 | double n = Math.sqrt(dx * dx + dz * dz); 111 | dx /= n; 112 | dz /= n; 113 | BlockPos pos = new BlockPos(MathHelper.floor(getX() + dx), MathHelper.floor(getY()), MathHelper.floor(getZ() + dz)); 114 | World world = getEntity().world; 115 | if (!world.getBlockState(pos).getCollisionShape(world, pos).isEmpty() 116 | && world.getBlockState(pos.up()).getCollisionShape(world, pos.up()).isEmpty() 117 | && world.getBlockState(pos.up(2)).getCollisionShape(world, pos.up(2)).isEmpty()) { 118 | BlockPos aboveHead = getEntity().getBlockPos().up(2); 119 | if (world.getBlockState(aboveHead).getCollisionShape(world, aboveHead).isEmpty()) { 120 | if (getEntity().squaredDistanceTo(x, getY(), z) > 1 121 | || getEntity().getBoundingBox().offset(x - getX(), 0, z - getZ()).intersects(new Box(pos))) { 122 | boolean wasJumping = ScriptManager.getScriptInput().jumping; 123 | ScriptManager.getScriptInput().jumping = true; 124 | ScriptManager.passTick(); 125 | ScriptManager.getScriptInput().jumping = wasJumping; 126 | } 127 | } 128 | } 129 | } 130 | lookAt(x, getY() + getEyeHeight(), z); 131 | ScriptManager.passTick(); 132 | 133 | tickCounter++; 134 | if (tickCounter % 20 == 0) { 135 | double distanceSq = getEntity().squaredDistanceTo(x, getY(), z); 136 | if (distanceSq >= lastDistanceSq) { 137 | successful = false; 138 | break; 139 | } 140 | lastDistanceSq = distanceSq; 141 | } 142 | } while (getEntity().squaredDistanceTo(x, getY(), z) > 0.25 * 0.25); 143 | snapTo(x, getY(), z); 144 | 145 | ScriptManager.getScriptInput().pressingForward = wasPressingForward; 146 | ScriptManager.blockInput(wasBlockingInput); 147 | 148 | return successful; 149 | } 150 | 151 | public boolean pathTo(double x, double y, double z) { 152 | return pathTo(x, y, z, null); 153 | } 154 | 155 | public boolean pathTo(double x, double y, double z, Value hints) { 156 | BlockPos pos = new BlockPos(x, y, z); 157 | return pathTo0(() -> pos, hints, false); 158 | } 159 | 160 | public boolean pathTo(Value thing) { 161 | return pathTo(thing, null); 162 | } 163 | 164 | public boolean pathTo(Value thing, Value hints) { 165 | ScriptEntity scriptEntity = ScriptUtil.unwrap(thing, ScriptEntity.class); 166 | if (scriptEntity != null) { 167 | Entity entity = scriptEntity.getEntity(); 168 | return pathTo0(entity::getBlockPos, hints, true); 169 | } else { 170 | ScriptFunction func = ScriptUtil.asFunction(thing); 171 | return pathTo0(() -> { 172 | Value posObj = func.call(); 173 | double x = posObj.getMember("x").asDouble(); 174 | double y = posObj.getMember("y").asDouble(); 175 | double z = posObj.getMember("z").asDouble(); 176 | return new BlockPos(x, y, z); 177 | }, hints, true); 178 | } 179 | } 180 | 181 | private boolean pathTo0(Supplier target, Value hints, boolean movingTarget) { 182 | ScriptFunction nodeTypeFunction = hints != null && hints.hasMember("nodeTypeFunction") ? ScriptUtil.asFunction(hints.getMember("nodeTypeFunction")) : null; 183 | ScriptFunction penaltyFunction = hints != null && hints.hasMember("penaltyFunction") ? ScriptUtil.asFunction(hints.getMember("penaltyFunction")) : null; 184 | Float followRange = hints != null && hints.hasMember("followRange") ? hints.getMember("followRange").asFloat() : null; 185 | int reachDistance = hints != null && hints.hasMember("reachDistance") ? hints.getMember("reachDistance").asInt() : 0; 186 | Float maxPathLength = hints != null && hints.hasMember("maxPathLength") ? hints.getMember("maxPathLength").asFloat() : null; 187 | 188 | BlockPos[] targetPos = {target.get()}; 189 | 190 | PathfindingHints javaHints = new PathfindingHints() { 191 | @Override 192 | public PathNodeType getNodeType(BlockView world, BlockPos pos) { 193 | if (nodeTypeFunction == null) 194 | return null; 195 | 196 | Value typeObj = nodeTypeFunction.call(pos.getX(), pos.getY(), pos.getZ()); 197 | if (typeObj == null || typeObj.isNull()) 198 | return null; 199 | 200 | String typeName = ScriptUtil.asString(typeObj); 201 | try { 202 | return PathNodeType.valueOf(typeName.toUpperCase(Locale.ROOT)); 203 | } catch (IllegalArgumentException e) { 204 | throw new IllegalArgumentException("Unknown path node type \"" + typeName + "\""); 205 | } 206 | } 207 | 208 | @Override 209 | public float getPathfindingPenalty(PathNodeType type) { 210 | if (penaltyFunction == null) 211 | return type.getDefaultPenalty(); 212 | 213 | String typeName = type.name().toLowerCase(Locale.ROOT); 214 | Value penaltyObj = penaltyFunction.call(typeName); 215 | 216 | if (penaltyObj == null || penaltyObj.isNull()) 217 | return type.getDefaultPenalty(); 218 | 219 | return penaltyObj.asFloat(); 220 | } 221 | 222 | @Override 223 | public float getFollowRange() { 224 | if (followRange != null) 225 | return followRange; 226 | return (float) Math.sqrt(getEntity().squaredDistanceTo(targetPos[0].getX() + 0.5, targetPos[0].getY() + 0.5, targetPos[0].getZ() + 0.5)) * 2; 227 | } 228 | 229 | @Override 230 | public int getReachDistance() { 231 | return reachDistance; 232 | } 233 | 234 | @Override 235 | public float getMaxPathLength() { 236 | if (maxPathLength != null) 237 | return maxPathLength; 238 | return (float) Math.sqrt(getEntity().squaredDistanceTo(targetPos[0].getX() + 0.5, targetPos[0].getY() + 0.5, targetPos[0].getZ() + 0.5)) * 2; 239 | } 240 | }; 241 | 242 | Path[] path = {PlayerPathfinder.findPathToAny(ImmutableSet.of(targetPos[0]), javaHints)}; 243 | boolean[] needsRecalc = {false}; 244 | //noinspection Convert2Lambda - need a new instance every time 245 | IBlockChangeListener blockChangeListener = new IBlockChangeListener() { 246 | @Override 247 | public void onBlockChange(BlockPos pos, BlockState oldState, BlockState newState) { 248 | if (path[0] == null || path[0].isFinished() || path[0].getLength() == 0) 249 | return; 250 | PathNode end = path[0].getEnd(); 251 | Vec3d halfway = new Vec3d((end.x + getEntity().getX()) / 2, (end.y + getEntity().getY()) / 2, (end.z + getEntity().getZ()) / 2); 252 | if (pos.isWithinDistance(halfway, path[0].getLength() - path[0].getCurrentNodeIndex())) { 253 | needsRecalc[0] = true; 254 | } 255 | } 256 | }; 257 | IBlockChangeListener.LISTENERS.add(blockChangeListener); 258 | 259 | try { 260 | while (path[0] != null && !path[0].isFinished()) { 261 | Vec3d currentPosition = path[0].getNode(path[0].getCurrentNodeIndex()).getPos(); 262 | if (!moveTo(currentPosition.getX() + 0.5, currentPosition.getZ() + 0.5)) 263 | return false; 264 | path[0].setCurrentNodeIndex(path[0].getCurrentNodeIndex() + 1); 265 | if (movingTarget || needsRecalc[0]) { 266 | BlockPos newTargetPos = target.get(); 267 | if (!newTargetPos.equals(targetPos[0]) || needsRecalc[0]) { 268 | targetPos[0] = newTargetPos; 269 | needsRecalc[0] = false; 270 | path[0] = PlayerPathfinder.findPathToAny(ImmutableSet.of(targetPos[0]), javaHints); 271 | } 272 | } 273 | } 274 | } finally { 275 | IBlockChangeListener.LISTENERS.remove(blockChangeListener); 276 | } 277 | 278 | return path[0] != null && path[0].getEnd() != null && path[0].getEnd().getPos().equals(targetPos[0]); 279 | } 280 | 281 | public void setYaw(float yaw) { 282 | getEntity().setYaw(yaw); 283 | } 284 | 285 | public void setPitch(float pitch) { 286 | getEntity().setPitch(pitch); 287 | } 288 | 289 | public void lookAt(double x, double y, double z) { 290 | ClientPlayerEntity player = getEntity(); 291 | double dx = x - player.getX(); 292 | double dy = y - (player.getY() + getEyeHeight()); 293 | double dz = z - player.getZ(); 294 | double dh = Math.sqrt(dx * dx + dz * dz); 295 | player.setYaw((float) Math.toDegrees(Math.atan2(dz, dx)) - 90); 296 | player.setPitch((float) -Math.toDegrees(Math.atan2(dy, dh))); 297 | } 298 | 299 | public void lookAt(ScriptEntity entity) { 300 | if (entity instanceof ScriptLivingEntity) { 301 | double eyeHeight = ((ScriptLivingEntity) entity).getEyeHeight(); 302 | lookAt(entity.getX(), entity.getY() + eyeHeight, entity.getZ()); 303 | } else { 304 | lookAt(entity.getX(), entity.getY(), entity.getZ()); 305 | } 306 | } 307 | 308 | public void syncRotation() { 309 | getEntity().networkHandler.sendPacket(new PlayerMoveC2SPacket.LookAndOnGround(getEntity().getYaw(), getEntity().getPitch(), getEntity().isOnGround())); 310 | } 311 | 312 | public int getSelectedSlot() { 313 | return getEntity().getInventory().selectedSlot; 314 | } 315 | 316 | public void setSelectedSlot(int slot) { 317 | getEntity().getInventory().selectedSlot = MathHelper.clamp(slot, 0, 8); 318 | } 319 | 320 | public Object getInventory() { 321 | return BeanWrapper.wrap(new ScriptInventory(getEntity().playerScreenHandler)); 322 | } 323 | 324 | public Object getCurrentContainer() { 325 | return BeanWrapper.wrap(getCurrentContainerUnchecked()); 326 | } 327 | 328 | private ScriptInventory getCurrentContainerUnchecked() { 329 | ScreenHandler container = getEntity().currentScreenHandler; 330 | if (container == getEntity().playerScreenHandler) 331 | return null; 332 | else 333 | return new ScriptInventory(getEntity().currentScreenHandler); 334 | } 335 | 336 | public boolean openContainer(int x, int y, int z, Value containerType) { 337 | return openContainer0(() -> rightClick(x, y, z), containerType); 338 | } 339 | 340 | public boolean openContainer(ScriptEntity entity, Value containerType) { 341 | return openContainer0(() -> rightClick(entity), containerType); 342 | } 343 | 344 | private boolean openContainer0(BooleanSupplier rightClickFunction, Value containerType) { 345 | Predicate containerTypePredicate; 346 | if (ScriptUtil.isFunction(containerType)) { 347 | ScriptFunction containerTypeFunc = ScriptUtil.asFunction(containerType); 348 | containerTypePredicate = type -> ScriptUtil.asBoolean(containerTypeFunc.call(type)); 349 | } else { 350 | String containerTypeName = ScriptUtil.asString(containerType); 351 | containerTypePredicate = containerTypeName::equals; 352 | } 353 | 354 | if (!rightClickFunction.getAsBoolean()) 355 | return false; 356 | 357 | int timeout = 0; 358 | while (getCurrentContainerUnchecked() == null || !containerTypePredicate.test(getCurrentContainerUnchecked().getType())) { 359 | ScriptManager.passTick(); 360 | timeout++; 361 | if (timeout > 100) 362 | return false; 363 | } 364 | 365 | return true; 366 | } 367 | 368 | public void closeContainer() { 369 | if (getEntity().currentScreenHandler != getEntity().playerScreenHandler) { 370 | MinecraftClient.getInstance().execute(getEntity()::closeHandledScreen); 371 | ScriptManager.passTick(); 372 | } 373 | } 374 | 375 | public int craft(Value result, int count, String[] pattern, Value ingredients) { 376 | // Convert js input to something we can handle in Java 377 | 378 | Predicate resultPredicate = ScriptUtil.asItemStackPredicate(result); 379 | 380 | int patternWidth = -1; 381 | Map> ingredientPredicates = new HashMap<>(); 382 | for (String row : pattern) { 383 | if (patternWidth == -1) 384 | patternWidth = row.length(); 385 | else if (row.length() != patternWidth) 386 | throw new IllegalArgumentException("Inconsistent pattern width"); 387 | for (int i = 0; i < row.length(); i++) { 388 | char c = row.charAt(i); 389 | if (c != ' ') { 390 | ingredientPredicates.computeIfAbsent(c, k -> { 391 | String s = String.valueOf(k); 392 | if (!ingredients.hasMember(s)) 393 | throw new IllegalArgumentException("Character '" + k + "' in pattern not found in ingredients"); 394 | return ScriptUtil.asItemStackPredicate(ingredients.getMember(s)); 395 | }); 396 | } 397 | } 398 | } 399 | if (ingredientPredicates.isEmpty()) 400 | throw new IllegalArgumentException("Empty pattern"); 401 | 402 | // Check if we are actually in a container with a crafting grid, or return if the recipe is too big for the grid 403 | if (!(getEntity().currentScreenHandler instanceof CraftingScreenHandler) && !(getEntity().currentScreenHandler instanceof PlayerScreenHandler)) { 404 | return 0; 405 | } 406 | AbstractRecipeScreenHandler container = (AbstractRecipeScreenHandler) getEntity().currentScreenHandler; 407 | int resultSlotIndex = container.getCraftingResultSlotIndex(); 408 | int craftingSlotCount = container.getCraftingSlotCount(); 409 | if (pattern.length > container.getCraftingHeight()) 410 | return 0; 411 | if (patternWidth > container.getCraftingWidth()) 412 | return 0; 413 | 414 | ClientPlayerInteractionManager interactionManager = MinecraftClient.getInstance().interactionManager; 415 | assert interactionManager != null; 416 | 417 | emptyCraftingGrid(container, resultSlotIndex, craftingSlotCount, interactionManager); 418 | 419 | // Craft the items 420 | int craftsNeeded = count; 421 | craftLoop: while (craftsNeeded > 0) { 422 | Map> ingredientsNeeded = new HashMap<>(); 423 | for (int x = 0; x < patternWidth; x++) { 424 | for (int y = 0; y < pattern.length; y++) { 425 | char c = pattern[y].charAt(x); 426 | if (c != ' ') { 427 | int slotId = 1 + x + container.getCraftingWidth() * y; 428 | Slot slot = container.getSlot(slotId); 429 | if (!ingredientPredicates.get(c).test(slot.getStack())) { 430 | if (slot.hasStack()) { 431 | if (!container.getCursorStack().isEmpty()) { 432 | craftInsertIntoPlayerInv(container, interactionManager); 433 | } 434 | interactionManager.clickSlot(container.syncId, slotId, 0, SlotActionType.QUICK_MOVE, getEntity()); 435 | if (slot.hasStack()) { 436 | interactionManager.clickSlot(container.syncId, slotId, 1, SlotActionType.THROW, getEntity()); 437 | } 438 | } 439 | ingredientsNeeded.computeIfAbsent(c, k -> new ArrayList<>()).add(slotId); 440 | } 441 | } 442 | } 443 | } 444 | 445 | if (ingredientsNeeded.isEmpty()) { 446 | int timeout = 0; 447 | Slot resultSlot = container.getSlot(resultSlotIndex); 448 | while (!resultPredicate.test(resultSlot.getStack())) { 449 | ScriptManager.passTick(); 450 | if (timeout++ > 100) { 451 | break craftLoop; 452 | } 453 | } 454 | ItemStack cursorStack = container.getCursorStack(); 455 | if (!ItemStack.canCombine(container.getCursorStack(), resultSlot.getStack()) 456 | || cursorStack.getCount() + resultSlot.getStack().getCount() > cursorStack.getMaxCount()) { 457 | craftInsertIntoPlayerInv(container, interactionManager); 458 | } 459 | interactionManager.clickSlot(container.syncId, resultSlotIndex, 0, SlotActionType.PICKUP, getEntity()); 460 | craftsNeeded--; 461 | } else { 462 | if (!container.getCursorStack().isEmpty()) { 463 | craftInsertIntoPlayerInv(container, interactionManager); 464 | } 465 | boolean craftingGridStartedEmpty = true; 466 | for (int slotId = resultSlotIndex + 1; slotId < resultSlotIndex + craftingSlotCount; slotId++) { 467 | if (container.getSlot(slotId).hasStack()) { 468 | craftingGridStartedEmpty = false; 469 | break; 470 | } 471 | } 472 | ingredientFillLoop: for (Map.Entry> ingredient : ingredientsNeeded.entrySet()) { 473 | Predicate ingredientPredicate = ingredientPredicates.get(ingredient.getKey()); 474 | List destSlots = ingredient.getValue(); 475 | while (!destSlots.isEmpty()) { 476 | boolean foundIngredient = false; 477 | for (Slot slot : container.slots) { 478 | if (isPlayerInvSlot(container, slot)) { 479 | if (ingredientPredicate.test(slot.getStack())) { 480 | List slotsToPlace = slot.getStack().getCount() < destSlots.size() ? destSlots.subList(0, slot.getStack().getCount()) : destSlots; 481 | interactionManager.clickSlot(container.syncId, slot.id, 0, SlotActionType.PICKUP, getEntity()); 482 | if (slotsToPlace.size() == 1) { 483 | interactionManager.clickSlot(container.syncId, slotsToPlace.get(0), 0, SlotActionType.PICKUP, getEntity()); 484 | } else { 485 | interactionManager.clickSlot(container.syncId, slotsToPlace.get(0), ScreenHandler.packQuickCraftData(0, 0), SlotActionType.QUICK_CRAFT, getEntity()); 486 | for (int destSlot : slotsToPlace) { 487 | interactionManager.clickSlot(container.syncId, destSlot, ScreenHandler.packQuickCraftData(1, 0), SlotActionType.QUICK_CRAFT, getEntity()); 488 | } 489 | interactionManager.clickSlot(container.syncId, slotsToPlace.get(slotsToPlace.size() - 1), ScreenHandler.packQuickCraftData(2, 0), SlotActionType.QUICK_CRAFT, getEntity()); 490 | } 491 | foundIngredient = !slotsToPlace.isEmpty(); 492 | slotsToPlace.clear(); 493 | break; 494 | } 495 | } 496 | } 497 | if (!foundIngredient) { 498 | if (craftingGridStartedEmpty) { 499 | break craftLoop; 500 | } else { 501 | emptyCraftingGrid(container, resultSlotIndex, craftingSlotCount, interactionManager); 502 | break ingredientFillLoop; 503 | } 504 | } 505 | } 506 | } 507 | } 508 | } 509 | 510 | emptyCraftingGrid(container, resultSlotIndex, craftingSlotCount, interactionManager); 511 | 512 | return count - craftsNeeded; 513 | } 514 | 515 | private void emptyCraftingGrid(AbstractRecipeScreenHandler container, int resultSlotIndex, int craftingSlotCount, ClientPlayerInteractionManager interactionManager) { 516 | // Get rid of all items in the cursor + the crafting grid 517 | if (!container.getCursorStack().isEmpty()) { 518 | craftInsertIntoPlayerInv(container, interactionManager); 519 | } 520 | for (int slotIndex = resultSlotIndex + 1; slotIndex < resultSlotIndex + craftingSlotCount; slotIndex++) { 521 | Slot slot = container.getSlot(slotIndex); 522 | if (slot.hasStack()) { 523 | interactionManager.clickSlot(container.syncId, slotIndex, 0, SlotActionType.QUICK_MOVE, getEntity()); 524 | if (slot.hasStack()) { 525 | interactionManager.clickSlot(container.syncId, slotIndex, 1, SlotActionType.THROW, getEntity()); 526 | } 527 | } 528 | } 529 | } 530 | 531 | private void craftInsertIntoPlayerInv(AbstractRecipeScreenHandler container, ClientPlayerInteractionManager interactionManager) { 532 | ItemStack cursorStack = container.getCursorStack(); 533 | int cursorStackCount = cursorStack.getCount(); 534 | 535 | for (int playerSlotIndex = 0; playerSlotIndex < container.slots.size(); playerSlotIndex++) { 536 | Slot slot = container.getSlot(playerSlotIndex); 537 | if (isPlayerInvSlot(container, slot)) { 538 | if (ItemStack.canCombine(cursorStack, slot.getStack())) { 539 | int maxCount = Math.min(cursorStack.getMaxCount(), slot.getMaxItemCount(cursorStack)); 540 | if (slot.getStack().getCount() < maxCount) { 541 | interactionManager.clickSlot(container.syncId, playerSlotIndex, 0, SlotActionType.PICKUP, getEntity()); 542 | cursorStackCount -= maxCount - slot.getStack().getCount(); 543 | if (cursorStackCount <= 0) 544 | return; 545 | } 546 | } 547 | } 548 | } 549 | for (int playerSlotIndex = 0; playerSlotIndex < container.slots.size(); playerSlotIndex++) { 550 | Slot slot = container.getSlot(playerSlotIndex); 551 | if (isPlayerInvSlot(container, slot)) { 552 | if (!slot.hasStack()) { 553 | int maxCount = Math.min(cursorStack.getMaxCount(), slot.getMaxItemCount(cursorStack)); 554 | interactionManager.clickSlot(container.syncId, playerSlotIndex, 0, SlotActionType.PICKUP, getEntity()); 555 | cursorStackCount -= maxCount; 556 | if (cursorStackCount <= 0) 557 | return; 558 | } 559 | } 560 | } 561 | interactionManager.clickSlot(container.syncId, -999, 0, SlotActionType.PICKUP, getEntity()); 562 | } 563 | 564 | private boolean isPlayerInvSlot(AbstractRecipeScreenHandler container, Slot slot) { 565 | return slot.inventory instanceof PlayerInventory 566 | && (slot.id < container.getCraftingResultSlotIndex() || slot.id >= container.getCraftingResultSlotIndex() + container.getCraftingSlotCount()); 567 | } 568 | 569 | public boolean pick(Value itemStack) { 570 | Predicate predicate = ScriptUtil.asItemStackPredicate(itemStack); 571 | 572 | PlayerInventory inv = getEntity().getInventory(); 573 | int slot; 574 | for (slot = 0; slot < inv.main.size(); slot++) { 575 | if (predicate.test(inv.main.get(slot))) 576 | break; 577 | } 578 | if (slot == inv.main.size()) 579 | return false; 580 | 581 | if (PlayerInventory.isValidHotbarIndex(slot)) { 582 | setSelectedSlot(slot); 583 | } else { 584 | int hotbarSlot = getSelectedSlot(); 585 | do { 586 | if (inv.main.get(hotbarSlot).isEmpty()) 587 | break; 588 | hotbarSlot = (hotbarSlot + 1) % 9; 589 | } while (hotbarSlot != getSelectedSlot()); 590 | setSelectedSlot(hotbarSlot); 591 | MinecraftClient.getInstance().interactionManager.pickFromInventory(slot); 592 | } 593 | return true; 594 | } 595 | 596 | public boolean rightClick() { 597 | for (Hand hand : Hand.values()) { 598 | ActionResult result = MinecraftClient.getInstance().interactionManager.interactItem(getEntity(), getEntity().world, hand); 599 | if (result.isAccepted()) { 600 | MinecraftClient.getInstance().gameRenderer.firstPersonRenderer.resetEquipProgress(hand); 601 | return true; 602 | } 603 | if (result == ActionResult.FAIL) 604 | return false; 605 | } 606 | return false; 607 | } 608 | 609 | public boolean leftClick(int x, int y, int z) { 610 | return leftClick(x, y, z, null); 611 | } 612 | 613 | public boolean leftClick(int x, int y, int z, String side) { 614 | Vec3d closestPos = ScriptWorld.getClosestVisiblePoint0(x, y, z, side, true); 615 | if (closestPos == null) 616 | return false; 617 | lookAt(closestPos.x, closestPos.y, closestPos.z); 618 | getEntity().swingHand(Hand.MAIN_HAND); 619 | Vec3d origin = getEntity().getCameraPosVec(0); 620 | Direction dir = ScriptUtil.getDirectionFromString(side); 621 | return MinecraftClient.getInstance().interactionManager.attackBlock(new BlockPos(x, y, z), 622 | dir == null ? Direction.getFacing((float) (closestPos.x - origin.x), (float) (closestPos.y - origin.y), (float) (closestPos.z - origin.z)) : dir); 623 | } 624 | 625 | public boolean rightClick(int x, int y, int z) { 626 | return rightClick(x, y, z, null); 627 | } 628 | 629 | public boolean rightClick(int x, int y, int z, String side) { 630 | Vec3d closestPos = ScriptWorld.getClosestVisiblePoint0(x, y, z, side, true); 631 | if (closestPos == null) 632 | return false; 633 | lookAt(closestPos.x, closestPos.y, closestPos.z); 634 | Vec3d origin = getEntity().getCameraPosVec(0); 635 | Direction dir = ScriptUtil.getDirectionFromString(side); 636 | for (Hand hand : Hand.values()) { 637 | ActionResult result = MinecraftClient.getInstance().interactionManager.interactBlock(getEntity(), MinecraftClient.getInstance().world, hand, 638 | new BlockHitResult(closestPos, 639 | dir == null ? Direction.getFacing((float) (closestPos.x - origin.x), (float) (closestPos.y - origin.y), (float) (closestPos.z - origin.z)) : dir, 640 | new BlockPos(x, y, z), false)); 641 | if (result.shouldSwingHand()) { 642 | getEntity().swingHand(hand); 643 | return true; 644 | } 645 | if (result == ActionResult.FAIL) 646 | return false; 647 | } 648 | return false; 649 | } 650 | 651 | public boolean leftClick(ScriptEntity entity) { 652 | if (getEntity().squaredDistanceTo(entity.getEntity()) > 6 * 6) 653 | return false; 654 | 655 | lookAt(entity); 656 | getEntity().swingHand(Hand.MAIN_HAND); 657 | MinecraftClient.getInstance().interactionManager.attackEntity(getEntity(), entity.getEntity()); 658 | return true; 659 | } 660 | 661 | public boolean rightClick(ScriptEntity entity) { 662 | if (getEntity().squaredDistanceTo(entity.getEntity()) > 6 * 6) 663 | return false; 664 | 665 | for (Hand hand : Hand.values()) { 666 | ActionResult result = MinecraftClient.getInstance().interactionManager.interactEntity(getEntity(), entity.getEntity(), hand); 667 | if (result.isAccepted()) { 668 | lookAt(entity); 669 | return true; 670 | } 671 | if (result == ActionResult.FAIL) 672 | return false; 673 | } 674 | return false; 675 | } 676 | 677 | public void blockInput() { 678 | ScriptManager.blockInput(true); 679 | } 680 | 681 | public void unblockInput() { 682 | ScriptManager.blockInput(false); 683 | } 684 | 685 | public boolean longUseItem() { 686 | if (!rightClick()) 687 | return false; 688 | if (!getEntity().isUsingItem()) 689 | return false; 690 | boolean wasBlockingInput = ScriptManager.isCurrentScriptBlockingInput(); 691 | ScriptManager.blockInput(true); 692 | do { 693 | ScriptManager.passTick(); 694 | } while (getEntity().isUsingItem()); 695 | ScriptManager.blockInput(wasBlockingInput); 696 | return true; 697 | } 698 | 699 | public boolean longMineBlock(int x, int y, int z) { 700 | if (!leftClick(x, y, z)) 701 | return false; 702 | ClientPlayerInteractionManager interactionManager = MinecraftClient.getInstance().interactionManager; 703 | if (!interactionManager.isBreakingBlock()) 704 | return false; 705 | boolean wasBlockingInput = ScriptManager.isCurrentScriptBlockingInput(); 706 | boolean successful = true; 707 | ScriptManager.blockInput(true); 708 | BlockPos pos = new BlockPos(x, y, z); 709 | do { 710 | HitResult hitResult = MinecraftClient.getInstance().crosshairTarget; 711 | if (hitResult.getType() != HitResult.Type.BLOCK || !((BlockHitResult) hitResult).getBlockPos().equals(pos)) { 712 | Vec3d closestPos = MathUtil.getClosestVisiblePoint(MinecraftClient.getInstance().world, pos, getEntity().getCameraPosVec(0), getEntity()); 713 | if (closestPos == null) { 714 | successful = false; 715 | break; 716 | } 717 | lookAt(closestPos.x, closestPos.y, closestPos.z); 718 | } 719 | IMinecraftClient imc = (IMinecraftClient) MinecraftClient.getInstance(); 720 | imc.resetAttackCooldown(); 721 | imc.continueBreakingBlock(); 722 | ScriptManager.passTick(); 723 | } while (interactionManager.isBreakingBlock()); 724 | ScriptManager.blockInput(wasBlockingInput); 725 | return successful; 726 | } 727 | 728 | public void setPressingForward(boolean pressingForward) { 729 | ScriptManager.getScriptInput().pressingForward = pressingForward; 730 | } 731 | 732 | public boolean isPressingForward() { 733 | return ScriptManager.getScriptInput().pressingForward || getEntity().input.pressingForward; 734 | } 735 | 736 | public void setPressingBack(boolean pressingBack) { 737 | ScriptManager.getScriptInput().pressingBack = pressingBack; 738 | } 739 | 740 | public boolean isPressingBack() { 741 | return ScriptManager.getScriptInput().pressingBack || getEntity().input.pressingBack; 742 | } 743 | 744 | public void setPressingLeft(boolean pressingLeft) { 745 | ScriptManager.getScriptInput().pressingLeft = pressingLeft; 746 | } 747 | 748 | public boolean isPressingLeft() { 749 | return ScriptManager.getScriptInput().pressingLeft || getEntity().input.pressingLeft; 750 | } 751 | 752 | public void setPressingRight(boolean pressingRight) { 753 | ScriptManager.getScriptInput().pressingRight = pressingRight; 754 | } 755 | 756 | public boolean isPressingRight() { 757 | return ScriptManager.getScriptInput().pressingRight || getEntity().input.pressingRight; 758 | } 759 | 760 | public void setJumping(boolean jumping) { 761 | ScriptManager.getScriptInput().jumping = jumping; 762 | } 763 | 764 | public boolean isJumping() { 765 | return ScriptManager.getScriptInput().jumping || getEntity().input.jumping; 766 | } 767 | 768 | public void setSneaking(boolean sneaking) { 769 | ScriptManager.getScriptInput().sneaking = sneaking; 770 | } 771 | 772 | public boolean isSneaking() { 773 | return ScriptManager.getScriptInput().sneaking || getEntity().input.sneaking; 774 | } 775 | 776 | public void setSprinting(boolean sprinting) { 777 | ScriptManager.setSprinting(sprinting); 778 | } 779 | 780 | public boolean isSprinting() { 781 | return ScriptManager.isCurrentThreadSprinting() || MinecraftClient.getInstance().options.sprintKey.isPressed(); 782 | } 783 | 784 | public void disconnect() { 785 | MinecraftClient.getInstance().send(Relogger::disconnect); 786 | ScriptManager.passTick(); 787 | } 788 | 789 | public boolean relog() { 790 | boolean[] ret = new boolean[1]; 791 | MinecraftClient.getInstance().send(() -> ret[0] = Relogger.relog()); 792 | ScriptManager.passTick(); 793 | return ret[0]; 794 | } 795 | 796 | } 797 | -------------------------------------------------------------------------------- /docs/clientcommands.ts: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * The player which the user has control over 4 | */ 5 | declare const player: ControllablePlayer; 6 | /** 7 | * The client-side world which the player is in 8 | */ 9 | declare const world: World; 10 | 11 | /** 12 | * Runs a client-side command and returns the result. Can also be an entity selector. 13 | * @param command The command or entity selector to run. 14 | * @return The integer result of the command, or list of matching entities in the case of an entity selector. 15 | */ 16 | declare function $(command: string): number | Array; 17 | 18 | /** 19 | * Prints a string to client-side chat 20 | * @param x The string to print 21 | */ 22 | declare function print(x: string): void; 23 | 24 | /** 25 | * Sends a message to the game chat, prefix with "/" to run a server-side command. 26 | * @param msg The message to send 27 | */ 28 | declare function chat(msg: string): void; 29 | 30 | /** 31 | * Allows the game to run a tick. Pauses script execution until the next tick 32 | */ 33 | declare function tick(): void; 34 | 35 | /** 36 | * Returns true if you are logged in to a game, false otherwise. Many operations are invalid if you are not logged in. 37 | */ 38 | declare function isLoggedIn(): boolean; 39 | 40 | /** 41 | * If a string, matches items by their name, with the "minecraft:" prefix removed if it exists. 42 | * If an object, matches the item NBT. 43 | * If a function, it should return true or false based on the input item NBT. 44 | */ 45 | type ItemPredicate = string | object | ((itemNbt: object) => boolean); 46 | 47 | /** 48 | * Represents a generic entity 49 | */ 50 | declare class Entity { 51 | /** 52 | * Whether this is a valid reference to an entity. A reference may be invalid if you are not ingame, you are in a 53 | * different dimension to the entity, or the entity has died or unloaded. All other operations on this entity will 54 | * fail if it is invalid. 55 | */ 56 | readonly valid: boolean; 57 | /** 58 | * The type of the entity, as used in commands. If the prefix, would be "minecraft:", then that prefix is stripped 59 | */ 60 | readonly type: string; 61 | /** 62 | * The x-position of the entity 63 | */ 64 | readonly x: number; 65 | /** 66 | * The y-position of the entity 67 | */ 68 | readonly y: number; 69 | /** 70 | * The z-position of the entity 71 | */ 72 | readonly z: number; 73 | /** 74 | * The yaw of the entity in degrees. 0 degrees is to the south, increasing clockwise 75 | */ 76 | readonly yaw: number; 77 | /** 78 | * The pitch of the entity in degrees. 0 degrees is forwards, increasing downwards 79 | */ 80 | readonly pitch: number; 81 | /** 82 | * The x-velocity of the entity 83 | */ 84 | readonly motionX: number; 85 | /** 86 | * The y-velocity of the entity 87 | */ 88 | readonly motionY: number; 89 | /** 90 | * The z-velocity of the entity 91 | */ 92 | readonly motionZ: number; 93 | /** 94 | * The NBT of the entity 95 | */ 96 | readonly nbt: object; 97 | 98 | /** 99 | * Returns whether this entity is the same entity as the other entity 100 | * @param other The other entity 101 | */ 102 | equals(other: Entity): boolean; 103 | } 104 | 105 | /** 106 | * A living entity, e.g. mobs, players 107 | */ 108 | declare class LivingEntity extends Entity { 109 | /** 110 | * The eye height of the entity in its current pose 111 | */ 112 | readonly eyeHeight: number; 113 | /** 114 | * The eye height of the entity in its standing pose 115 | */ 116 | readonly standingEyeHeight: number; 117 | } 118 | 119 | /** 120 | * A player 121 | */ 122 | declare class Player extends LivingEntity { 123 | 124 | } 125 | 126 | /** 127 | * A player which the user has control over 128 | */ 129 | declare class ControllablePlayer extends Player { 130 | 131 | /** 132 | * Teleports the player a limited distance. This function cannot teleport the player more than 0.5 blocks, 133 | * and is meant for alignment rather than movement. Use properties like {@link pressingForward} and 134 | * {@link sprinting} for movement. Returns whether successful (the player was close enough to the 135 | * given position). 136 | * @param x The x-position to snap the player to 137 | * @param y The y-position to snap the player to 138 | * @param z The z-position to snap the player to 139 | * @param sync Whether to sync the position with the server immediately after the teleport, rather than 140 | * at the start of the next tick. If absent, defaults to false 141 | */ 142 | snapTo(x: number, y: number, z: number, sync?: boolean): boolean; 143 | 144 | /** 145 | * Moves the player in a straight line to the specified target position. Blocks until it reaches there. 146 | * Returns whether successful 147 | * @param x The x-position of the target 148 | * @param z The z-position of the target 149 | * @param smart Default true, whether to jump up single block gaps 150 | */ 151 | moveTo(x: number, z: number, smart?: boolean): boolean; 152 | 153 | /** 154 | * Pathfinds the player to the specified target position. Blocks until it reaches there. 155 | * Returns whether successful 156 | * @param x The x-position of the target 157 | * @param y The y-position of the target 158 | * @param z The z-position of the target 159 | * @param hints The pathfinding hints 160 | */ 161 | pathTo(x: number, y: number, z: number, hints?: PathfindingHints): boolean; 162 | /** 163 | * Pathfinds the player to the specified moving target entity. Blocks until it reaches there. 164 | * Returns whether successful 165 | * @param target The entity to follow 166 | * @param hints The pathfinding hints 167 | */ 168 | pathTo(target: Entity, hints?: PathfindingHints): boolean; 169 | /** 170 | * Pathfinds the player to the specified moving target. The input function will be called periodically 171 | * to update the target position. Returns whether successful 172 | * @param movingTarget The moving target to follow 173 | * @param hints The pathfinding hints 174 | */ 175 | pathTo(movingTarget: () => Position, hints?: PathfindingHints): boolean; 176 | 177 | /** 178 | * The yaw of the player in degrees. 0 degrees is to the south, increasing clockwise 179 | */ 180 | yaw: number; 181 | /** 182 | * The pitch of the player in degrees. 0 degrees is forwards, increasing downwards 183 | */ 184 | pitch: number; 185 | 186 | /** 187 | * Causes the player to look towards a point in space. 188 | * @param x The x-position of the point to look at 189 | * @param y The y-position of the point to look at 190 | * @param z The z-position of the point to look at 191 | */ 192 | lookAt(x: number, y: number, z: number): void; 193 | /** 194 | * Causes the player to look towards an entity. If target is a {@link LivingEntity}, then 195 | * the player will look at the eye height of that entity. Otherwise, it will look at the bottom 196 | * of the entity. 197 | * @param target The entity to look at 198 | */ 199 | lookAt(target: Entity): void; 200 | 201 | /** 202 | * Forces a synchronization of the player's look angles with the server. By default, the player's 203 | * new rotation is only sent to the server at the start of the next tick, meaning that if you 204 | * perform certain actions that depend on the player's rotation, such as dropping an item, it will 205 | * happen as if the player was still facing in the previous direction. Calling this function will 206 | * immediately send the player's rotation to the server, so that further actions (like dropping an 207 | * item) will be performed with the correct rotation. 208 | */ 209 | syncRotation(): void; 210 | 211 | /** 212 | * The currently selected hotbar slot 213 | */ 214 | selectedSlot: number; 215 | /** 216 | * The player's inventory. Slot numbers are as follows: 217 | * 218 | * 219 | * 220 | * 221 | * 222 | * 223 | * 224 | * 225 | *
NumberSlot
0-8Hotbar
9-35The rest of the main inventory
36-39Armor (head, torso, legs, feet)
40Offhand
41Crafting result slot
42-45Crafting grid
226 | * Slots beyond the hotbar and main inventory will be inaccessible while the player is looking into 227 | * a container. 228 | */ 229 | readonly inventory: Inventory; 230 | /** 231 | * The inventory of the container the player is looking in, or null if the player isn't looking 232 | * in any container 233 | */ 234 | readonly currentContainer: Inventory | null; 235 | 236 | /** 237 | * Opens a container by right clicking the block at the given block position, and wait for a container 238 | * of type expectedContainerType to be opened. 239 | * @param x The x-position of the container block 240 | * @param y The y-position of the container block 241 | * @param z The z-position of the container block 242 | * @param expectedContainerType The container type to expect, or a function that returns true/false 243 | * depending on whether a container type should be accepted 244 | * @return Whether successful 245 | */ 246 | openContainer(x: number, y: number, z: number, expectedContainerType: string | ((containerType: string) => boolean)): boolean; 247 | 248 | /** 249 | * Opens a container by right clicking the given entity, and wait for a container of type 250 | * expectedContainerType to be opened. 251 | * @param entity The entity to click on 252 | * @param expectedContainerType The container type to expect, or a function that returns true/false 253 | * depending on whether a container type should be accepted 254 | * @return Whether successful 255 | */ 256 | openContainer(entity: Entity, expectedContainerType: string | ((containerType: string) => boolean)): boolean; 257 | 258 | /** 259 | * Closes the currently opened container, if any is open. Due to a threading issue in 1.16, this method delays 260 | * the script by 1 tick, if a container was open. 261 | */ 262 | closeContainer(): void; 263 | 264 | /** 265 | * If the player currently has a crafting container open, crafts as many times as possible up to the given 266 | * number of times. Places items matching the given parameters into the given pattern, waits for a result matching 267 | * result to appear, and pulls it out of the crafting grid. 268 | * @param result The expected result of the recipe 269 | * @param craftCount The number of crafts 270 | * @param pattern An array of rows of the pattern. Each character corresponds to an item, use spaces for blank slots 271 | * @param ingredients Associates characters with items 272 | * @return The number of crafts managed 273 | */ 274 | craft(result: ItemPredicate, 275 | craftCount: number, 276 | pattern: Array, 277 | ingredients: {[keys: string]: ItemPredicate}): number; 278 | 279 | /** 280 | * "picks" an item from the player's inventory, and selects it in the hotbar, in a similar fashion to the 281 | * vanilla pick block feature. 282 | * @param item The item to pick 283 | * @return Whether a matching item could be found in the inventory 284 | */ 285 | pick(item: ItemPredicate): boolean; 286 | 287 | /** 288 | * Right clicks the currently held item in air 289 | * 290 | * Warning: right-clicking some items is a continuous action. This function on its own 291 | * will not work for this! For an easy way to eat food, see {@link longUseItem} 292 | * @return Whether the item use was considered successful 293 | */ 294 | rightClick(): boolean; 295 | /** 296 | * Right clicks a block. Will click on the closest part of the block at the given position. 297 | * This function also modifies the player rotation to look at where they clicked, if they are 298 | * not already hovering over the block. 299 | * @param x The x-position of the block to right click 300 | * @param y The y-position of the block to right click 301 | * @param z The z-position of the block to right click 302 | * @param side The side of the block to click on. If not specified, will click on the closest side. 303 | * @return Whether the right click was successful 304 | */ 305 | rightClick(x: number, y: number, z: number, side?: string): boolean; 306 | /** 307 | * Right clicks an entity. This function also modifies the player rotation to look at the entity they 308 | * clicked on. 309 | * @param entity The entity to right click 310 | * @return Whether the right click was successful 311 | */ 312 | rightClick(entity: Entity): boolean; 313 | 314 | /** 315 | * Left clicks on a block. Will click on the closest part of the block at the given position. 316 | * This function also modifies the player rotation to look at where they clicked, if they are 317 | * not already hovering over the block. 318 | * 319 | * Warning: left-clicking many blocks is a continuous mining action. This function on its own 320 | * will not work for this! For an easy way to mine blocks, see {@link longMineBlock} 321 | * @param x The x-position of the block to right click 322 | * @param y The y-position of the block to right click 323 | * @param z The z-position of the block to right click 324 | * @param side The side of the block to click on. If not specified, will click on the closest side. 325 | * @return Whether the left click was successful 326 | */ 327 | leftClick(x: number, y: number, z: number, side?: string): boolean; 328 | /** 329 | * Left clicks on an entity (i.e. usually attacking it). This function also modifies the player rotation to 330 | * look at the entity they clicked on. 331 | * @param entity The entity to left click 332 | * @return Whether the left click was successful 333 | */ 334 | leftClick(entity: Entity): boolean; 335 | 336 | /** 337 | * Blocks user input until either this script terminates or {@link unblockInput} is called (and so long as 338 | * no other scripts are also blocking input). 339 | */ 340 | blockInput(): void; 341 | 342 | /** 343 | * Stops this script blocking user input 344 | */ 345 | unblockInput(): void; 346 | 347 | /** 348 | * Holds down right click until the item can be used no more! For food, this has the effect of eating a single 349 | * food item. Pauses execution of the script until the action is finished. Also blocks input for the duration 350 | * of item use. 351 | */ 352 | longUseItem(): boolean; 353 | 354 | /** 355 | * Mines a block with the currently held item until it is broken. Pauses execution of the script until the 356 | * action is finished. Also blocks input for the duration of the block breaking. 357 | * @param x The x-position of the block to mine 358 | * @param y The y-position of the block to mine 359 | * @param z The z-position of the block to mine 360 | */ 361 | longMineBlock(x: number, y: number, z: number): boolean; 362 | 363 | /** 364 | * Whether the script is pressing forward for the player. This shouldn't be used to get whether forward is 365 | * being pressed, it will produce inconsistent results. It should be used to set whether forward 366 | * is pressed by the current script. 367 | */ 368 | pressingForward: boolean; 369 | /** 370 | * Whether the script is pressing back for the player. This shouldn't be used to get whether back is 371 | * being pressed, it will produce inconsistent results. It should be used to set whether back 372 | * is pressed by the current script. 373 | */ 374 | pressingBack: boolean; 375 | /** 376 | * Whether the script is pressing left for the player. This shouldn't be used to get whether left is 377 | * being pressed, it will produce inconsistent results. It should be used to set whether left 378 | * is pressed by the current script. 379 | */ 380 | pressingLeft: boolean; 381 | /** 382 | * Whether the script is pressing right for the player. This shouldn't be used to get whether right is 383 | * being pressed, it will produce inconsistent results. It should be used to set whether right 384 | * is pressed by the current script. 385 | */ 386 | pressingRight: boolean; 387 | /** 388 | * Whether the script is pressing the jump key for the player. This shouldn't be used to get whether jump is 389 | * being pressed, it will produce inconsistent results. It should be used to set whether jump 390 | * is pressed by the current script. 391 | */ 392 | jumping: boolean; 393 | /** 394 | * Whether the script is pressing the sneak key for the player. This shouldn't be used to get whether sneak is 395 | * being pressed, it will produce inconsistent results. It should be used to set whether sneak 396 | * is pressed by the current script. 397 | */ 398 | sneaking: boolean; 399 | /** 400 | * Whether the script is pressing the sprint key for the player. This shouldn't be used to get whether sprint is 401 | * being pressed, it will produce inconsistent results. It should be used to set whether sprint 402 | * is pressed by the current script. 403 | */ 404 | sprinting: boolean; 405 | 406 | /** 407 | * Attempts to disconnect from the server. Terminates the script if successful. 408 | */ 409 | disconnect(): void; 410 | 411 | /** 412 | * Disconnects and reconnects the player to the server. Returns whether successful. This function may return before 413 | * the player has fully logged in again; check {@link isLoggedIn()} in a loop to wait for the player to be logged 414 | * in. If the relog failed, depending on how it failed, the script may terminate after the next tick. The script 415 | * will continue running if successful. 416 | */ 417 | relog(): boolean; 418 | } 419 | 420 | /** 421 | * The options for an inventory click 422 | */ 423 | interface InventoryClickOptions { 424 | /** 425 | * The click type, one of: 426 | * 427 | * 428 | * 429 | * 430 | * 431 | * 432 | * 433 | * 434 | * 435 | *
ValueDescription
"pickup"Swaps the item held by the cursor with the item in the given slot. This is the default click type.
"quick_move""Shift-clicks" on the given slot, which is commonly a quick way of moving items into and out of a container
"swap"Swaps the given slot with a hotbar slot, specified by also setting the {@link hotbarSlot} property on this object
"clone"(Creative-mode-only) clones the item in the given slot, equivalent to a middle-click on it
"throw"Throws the item in the given slot out of the inventory
"quick_craft"Performs a quick-craft. Must be performed in multiple stages, specified by also setting the {@link quickCraftStage} property in this object
"pickup_all"Picks up all items of the type in the slot
436 | */ 437 | type?: string; 438 | /** 439 | * Whether to simulate a right click rather than a left click (which is the default). 440 | * When {@link type} is "throw", then setting this to true throws all items rather than just one. 441 | */ 442 | rightClick?: boolean; 443 | /** 444 | * When {@link type} is "swap", specifies the hotbar slot to swap with 445 | */ 446 | hotbarSlot?: number; 447 | /** 448 | * When {@link type} is "quick_craft", specifies the quick craft stage (0-3) 449 | */ 450 | quickCraftStage?: number; 451 | } 452 | 453 | /** 454 | * Represents an inventory/container of items 455 | */ 456 | declare class Inventory { 457 | /** 458 | * The type of inventory. Returns either "player", "creative", "horse" or the container ID. 459 | * Note that the container ID may be more generic than you might expect, e.g. "generic_9x3" 460 | * for different types of chests. 461 | */ 462 | readonly type: string; 463 | /** 464 | * The items in the inventory. This is an array of item stack NBT objects. 465 | */ 466 | readonly items: Array; 467 | 468 | /** 469 | * Simulates a click in an inventory slot. This function covers most inventory actions. 470 | * @param slot The slot ID to click on. If null, this represents clicking outside 471 | * the window (usually with the effect of dropping the stack under the cursor). 472 | * @param options The options of the inventory action. See {@link InventoryClickOptions}. 473 | */ 474 | click(slot: number | null, options?: InventoryClickOptions): void; 475 | 476 | /** 477 | * Finds the first slot in the container with an item that matches the given item predicate 478 | * @param item The item to search for 479 | * @param reverse If true, returns the last matching slot rather than the first one 480 | * @return The index of the first matching slot, or null if no such slot is found. 481 | */ 482 | findSlot(item: ItemPredicate, reverse?: boolean): number | null; 483 | 484 | /** 485 | * Lists the slots in the container with an item that matches the given item predicate, up 486 | * until the maximum number of items has been reached. 487 | * @param item The item to search for 488 | * @param count The maximum amount of this item to search for, or -1 for no maximum 489 | * @param reverse If true, returns the last matching slots first rather than the first matching slot first 490 | * @return An array of matching slot indices 491 | */ 492 | findSlots(item: ItemPredicate, count: number, reverse?: boolean): Array; 493 | 494 | /** 495 | * Shift clicks all the matching items in this container, up until the maximum number of items has been reached. 496 | * @param item The item to search for 497 | * @param count The maximum amount of this item to shift lick, or -1 for no maximum 498 | * @param reverse If true, shift clicks the items in reverse order of slot indices 499 | * @return The actual number of items shift clicked, may be greater than or equal to count if there 500 | * are sufficient items in this container, or equal to the number of matching items if there are 501 | * insufficient items. 502 | */ 503 | moveItems(item: ItemPredicate, count: number, reverse?: boolean): number; 504 | 505 | /** 506 | * Return whether this inventory is the same as another inventory 507 | * @param other The other inventory 508 | */ 509 | equals(other: Inventory): boolean; 510 | } 511 | 512 | /** 513 | * The type of the global world variable, the client-side world. 514 | */ 515 | declare class World { 516 | /** 517 | * The dimension ID of the current dimension. In vanilla, this can either be "overworld", 518 | * "the_nether" or "the_end"; with datapacks or in modded this may take other values. 519 | */ 520 | readonly dimension: string; 521 | 522 | /** 523 | * Gets the name of the block at the given position in the client-side world. 524 | * @param x The x-position of the block to query 525 | * @param y The y-position of the block to query 526 | * @param z The z-position of the block to query 527 | * @return The block name, as used in commands. If there would be a "minecraft:" prefix, the prefix is removed. 528 | */ 529 | getBlock(x: number, y: number, z: number): string; 530 | 531 | /** 532 | * Gets the block state property with the given name at the given position. 533 | * Equivalent to getBlockState(x, y, z).getProperty(property) 534 | * @param x The x-position of the block state to query 535 | * @param y The y-position of the block state to query 536 | * @param z The z-position of the block state to query 537 | * @param property The property name to query. E.g. could be "power" for redstone dust, or "bed_part" for beds. 538 | */ 539 | getBlockProperty(x: number, y: number, z: number, property: string): boolean | number | string; 540 | 541 | /** 542 | * Gets the block state at the given position. 543 | * @param x The x-position of the block state to get 544 | * @param y The y-position of the block state to get 545 | * @param z The z-position of the block state to get 546 | */ 547 | getBlockState(x: number, y: number, z: number): BlockState; 548 | 549 | /** 550 | * Gets the client-side block entity NBT at the given coordinates 551 | * @param x The x-position of the block entity whose NBT to get 552 | * @param y The y-position of the block entity whose NBT to get 553 | * @param z The z-position of the block entity whose NBT to get 554 | * @return The NBT object of the block entity, or null if there was no block entity 555 | */ 556 | getBlockEntityNbt(x: number, y: number, z: number): object | null; 557 | 558 | /** 559 | * Gets the block light at the given position, 0-15 560 | * @param x The x-coordinate of the position to get the block light of 561 | * @param y The y-coordinate of the position to get the block light of 562 | * @param z The z-coordinate of the position to get the block light of 563 | */ 564 | getBlockLight(x: number, y: number, z: number): number; 565 | 566 | /** 567 | * Gets the sky light at the given position, 0-15 568 | * @param x The x-coordinate of the position to get the sky light of 569 | * @param y The y-coordinate of the position to get the sky light of 570 | * @param z The z-coordinate of the position to get the sky light of 571 | */ 572 | getSkyLight(x: number, y: number, z: number): number; 573 | 574 | /** 575 | * Finds the closest visible point on a block 576 | * @param x The x-position of the block to find the closest visible point on 577 | * @param y The y-position of the block to find the closest visible point on 578 | * @param z The z-position of the block to find the closest visible point on 579 | * @param side The side of the block to find the closest visible point on. If not specified, will use the closest side. 580 | * @return Whether there was a visible point on the block within reach 581 | */ 582 | getClosestVisiblePoint(x: number, y: number, z: number, side?: string): Position | null; 583 | } 584 | 585 | /** 586 | * Defines a "thread", which is an action which can run "concurrently" with other threads. 587 | * This is not concurrency in the sense you may be used to as a programmer. Only one thread 588 | * can run at a time. When you start another thread, that thread gains control immediately, 589 | * until either it stops or it calls the {@link tick} function, which allows other script 590 | * threads to run their code, and the game to run a tick. Therefore, you do not have to 591 | * worry about thread safety, as all operations are thread safe. 592 | */ 593 | declare class Thread { 594 | /** 595 | * The currently executing thread. If this thread is not a clientcommands thread or the main script thread, then 596 | * this returns null. This is possible for example via JsMacros' JavaWrapper. 597 | */ 598 | static readonly current: Thread | null; 599 | 600 | /** 601 | * Whether the thread is currently running. Note this does not necessarily mean this 602 | * thread is the currently executing thread. To test that, use thread === Thread.current 603 | */ 604 | readonly running: boolean; 605 | 606 | /** 607 | * Whether the thread has been paused by {@link pause}. To pause an unpause threads, use the methods 608 | */ 609 | readonly paused: boolean; 610 | 611 | /** 612 | * Whether the thread is a daemon thread. If true, this thread will be killed when the parent thread 613 | * stops; if false, this thread may outlive its parent 614 | */ 615 | readonly daemon: boolean; 616 | 617 | /** 618 | * The thread which started this thread. If null, either this thread was not started by a script, or 619 | * this thread is not a daemon and the parent thread has died 620 | */ 621 | readonly parent: Thread | null; 622 | 623 | /** 624 | * An array view of running threads started by this thread 625 | */ 626 | readonly children: Array; 627 | 628 | /** 629 | * Creates a thread which will execute the given function on it when run. Does not 630 | * run automatically, remember to explicitly call the {@link run} function. 631 | * @param action The function to be executed on this thread 632 | * @param daemon Whether this thread will be killed when the thread which started it 633 | * is terminated. Defaults to true. If false, the thread can outlive the thread which 634 | * started it. 635 | */ 636 | constructor(action: () => void, daemon?: boolean); 637 | 638 | /** 639 | * Starts the thread. Does nothing if the thread has already started. 640 | */ 641 | run(): void; 642 | 643 | /** 644 | * Pauses the thread. The thread will not return from the {@link tick} function until it 645 | * has been unpaused. If a thread pauses itself, it will continue execution until the next 646 | * time it calls the {@link tick} function. 647 | */ 648 | pause(): void; 649 | 650 | /** 651 | * Unpauses the thread 652 | */ 653 | unpause(): void; 654 | 655 | /** 656 | * Kills the thread, which stops it running any more code. If a thread tries to kill itself, 657 | * it will continue running until it calls the {@link tick} function, at which point it will 658 | * be killed 659 | */ 660 | kill(): void; 661 | 662 | /** 663 | * Blocks the currently executing thread until this thread has finished executing. An exception 664 | * is thrown if a thread tries to wait for itself 665 | */ 666 | waitFor(): void; 667 | } 668 | 669 | /** 670 | * Contains information about a block state 671 | */ 672 | declare class BlockState { 673 | 674 | /** 675 | * Returns the default block state of the given block 676 | * @param block The block to get the default block state of 677 | */ 678 | static defaultState(block: string): BlockState; 679 | 680 | /** 681 | * The block of this block state 682 | */ 683 | readonly block: string; 684 | 685 | /** 686 | * A list of block state properties supported by this block, i.e. those that can be used 687 | * in {@link World.getBlockProperty} 688 | */ 689 | readonly stateProperties: Array; 690 | 691 | /** 692 | * Returns the value of the given property of the block state. 693 | * If the property is a boolean or numeric property, then the value of the property 694 | * is returned directly. Otherwise, a string representation of the value is returned. 695 | * @param property The property to get 696 | */ 697 | getProperty(property: string): boolean | number | string; 698 | 699 | /** 700 | * The light level emitted, 0-15 701 | */ 702 | readonly luminance: number; 703 | 704 | /** 705 | * The hardness of the block, proportional to how long it takes to mine. For example: 706 | *
    707 | *
  • Tall grass: 0
  • 708 | *
  • Dirt: 0.5
  • 709 | *
  • Stone: 1.5
  • 710 | *
  • Obsidian: 50
  • 711 | *
  • Bedrock: -1
  • 712 | *
713 | */ 714 | readonly hardness: number; 715 | 716 | /** 717 | * How resistant this block is to explosions. For example: 718 | *
    719 | *
  • Stone: 6
  • 720 | *
  • Obsidian: 1200
  • 721 | *
  • Bedrock: 3600000
  • 722 | *
723 | * Note: the wiki has these values 5 times larger than they should be 724 | */ 725 | readonly blastResistance: number; 726 | 727 | /** 728 | * Whether this block responds to random ticks 729 | */ 730 | readonly randomTickable: boolean; 731 | 732 | /** 733 | * A value between 0-1 indicating how slippery a block is. A value of 1 means no friction at all 734 | * (as if an entity was moving sideways in air), a value of 0 will stop an entity instantly. Most 735 | * blocks have a slipperiness of 0.6, while ice has a slipperiness of 0.98 736 | */ 737 | readonly slipperiness: number; 738 | 739 | /** 740 | * The loot table used to drop items after this block is mined 741 | */ 742 | readonly lootTable: string; 743 | 744 | /** 745 | * The translation key used to get the name of this block 746 | */ 747 | readonly translationKey: string; 748 | 749 | /** 750 | * The item corresponding to this block, or null if the block has no corresponding item 751 | */ 752 | readonly item: string | null; 753 | 754 | /** 755 | * A unique ID of the material of the block, may change across Minecraft versions or when other mods 756 | * add materials. It's safest to compare against the material ID of a block with a known material 757 | */ 758 | readonly materialId: number; 759 | 760 | /** 761 | * The map color of this block, in packed 0xRRGGBB format 762 | */ 763 | readonly mapColor: number; 764 | 765 | /** 766 | * How this block reacts to being pushed by a piston. 767 | * 768 | * 769 | * 770 | * 771 | * 772 | * 773 | *
ValueExampleDescription
"normal"StonePiston will move the block
"destroy"TorchPiston will destroy the block (it will "pop off")
"block"ObsidianPiston cannot pull the block and block will prevent piston from pushing
"push_only"Glazed terracottaBlock can only be pushed, does not stick to slime
774 | */ 775 | readonly pistonBehavior: string; 776 | 777 | /** 778 | * Whether this block is flammable 779 | */ 780 | readonly flammable: boolean; 781 | 782 | /** 783 | * Whether this block will drop without using the correct tool 784 | */ 785 | readonly canBreakByHand: boolean; 786 | 787 | /** 788 | * Whether this block is a liquid 789 | */ 790 | readonly liquid: boolean; 791 | 792 | /** 793 | * Whether this block blocks light TODO: investigate 794 | */ 795 | readonly blocksLight: boolean; 796 | 797 | /** 798 | * Whether this block is replaced when placing a block, e.g. tall grass 799 | */ 800 | readonly replaceable: boolean; 801 | 802 | /** 803 | * Whether this is a solid block TODO: investigate 804 | */ 805 | readonly solid: boolean; 806 | 807 | /** 808 | * The burn chance, related to how quickly the block burns once it has caught fire 809 | */ 810 | readonly burnChance: number; 811 | 812 | /** 813 | * The spread chance, related to how quickly a block catches fire in response to nearby fire 814 | */ 815 | readonly spreadChance: number; 816 | 817 | /** 818 | * Whether this block will fall like sand when unsupported 819 | */ 820 | readonly fallable: boolean; 821 | 822 | /** 823 | * The tags applying to this block 824 | */ 825 | readonly tags: Array; 826 | } 827 | 828 | /** 829 | * Represents an item stack, used to get certain properties of that stack. Note that translating 830 | * to and from this representation is inefficient, so shouldn't be done unnecessarily frequently 831 | */ 832 | declare class ItemStack { 833 | 834 | /** 835 | * Returns the ItemStack representation of the given item stack NBT 836 | * @param stack The NBT representation of the item stack 837 | */ 838 | static of(stack: object): ItemStack; 839 | 840 | /** 841 | * Creates an item stack of size 1 with the given item 842 | * @param item The item to make a stack of 843 | */ 844 | static of(item: string): ItemStack; 845 | 846 | /** 847 | * Returns the NBT representation of this item stack 848 | */ 849 | readonly stack: object; 850 | 851 | /** 852 | * Gets the mining speed of this item stack against a given block state. This is a multiplier, 853 | * where a value of 1 indicates the same speed as with a fist against a block which doesn't require 854 | * a tool 855 | * @param block The block or block state to test against 856 | */ 857 | getMiningSpeed(block: string | BlockState): number; 858 | 859 | /** 860 | * Returns whether this item is effective against a block which requires a tool. Note this does not 861 | * affect mining speed, only whether the block drops its items or not 862 | * @param block The block or block state to test against 863 | */ 864 | isEffectiveOn(block: string | BlockState): boolean; 865 | 866 | /** 867 | * The maximum stack size of this item. Usually 64, but may also be 1 or 16 in vanilla. 868 | * A value of 1 indicates that this item is non-stackable 869 | */ 870 | readonly maxCount: number; 871 | 872 | /** 873 | * The maximum amount of damage this item can take, if it is for example a tool 874 | */ 875 | readonly maxDamage: number; 876 | 877 | /** 878 | * Whether this item is a food item 879 | */ 880 | readonly isFood: boolean; 881 | 882 | /** 883 | * The amount of hunger this item restores, or 0 if this is not a food item 884 | */ 885 | readonly hungerRestored: number; 886 | 887 | /** 888 | * The amount of saturation this item restores, or 0 if this is not a food item 889 | */ 890 | readonly saturationRestored: number; 891 | 892 | /** 893 | * Whether this is a food item and is meat (used for whether wolves like it) 894 | */ 895 | readonly isMeat: boolean; 896 | 897 | /** 898 | * Whether this is a food item and can be eaten even with full hunger (e.g. golden apple) 899 | */ 900 | readonly alwaysEdible: boolean; 901 | 902 | /** 903 | * Whether this is a snack food item, which doesn't take as long to eat (e.g. berries) 904 | */ 905 | readonly isSnack: boolean; 906 | 907 | /** 908 | * The tags applying to this item 909 | */ 910 | readonly tags: Array; 911 | } 912 | 913 | /** 914 | * Represents a 3D position 915 | */ 916 | interface Position { 917 | x: number; 918 | y: number; 919 | z: number; 920 | } 921 | 922 | /** 923 | * Pathfinding hints 924 | */ 925 | interface PathfindingHints { 926 | 927 | /** 928 | * A function that gets the path node type for the given coordinates. Returns null for vanilla behavior. 929 | * A list of current path node types and their penalties as of 1.15 is as follows: 930 | * 931 | * 932 | * 933 | * 934 | * 935 | * 936 | * 937 | * 938 | * 939 | * 940 | * 941 | * 942 | * 943 | * 944 | * 945 | * 946 | * 947 | * 948 | * 949 | * 950 | * 951 | * 952 | * 953 | * 954 | *
NameDefault penalty
"blocked"-1
"open"0
"walkable"0
"trapdoor"0
"fence"-1
"lava"-1
"water"8
"water_border"8
"rail"0
"danger_fire"8
"damage_fire"16
"danger_cactus"8
"damage_cactus"-1
"danger_other"8
"damage_other"-1
"door_open"0
"door_wood_closed"-1
"door_iron_closed"-1
"breach"4
"leaves"-1
"sticky_honey"8
"cocoa"0
955 | */ 956 | nodeTypeFunction?: (x: number, y: number, z: number) => string | null; 957 | 958 | /** 959 | * A function that gets the pathfinding penalty for the given path node type. 960 | *
    961 | *
  • Return 0 for no penalty.
  • 962 | *
  • Return -1 for a completely impassable block.
  • 963 | *
  • Return higher positive numbers for higher penalties.
  • 964 | *
965 | * See {@link nodeTypeFunction} for a list of path node types and their default penalties 966 | */ 967 | penaltyFunction?: (type: string) => number | null; 968 | 969 | /** 970 | * The maximum Euclidean distance to the target that the player can be at any one time. 971 | * Defaults to twice the distance to the target. 972 | */ 973 | followRange?: number; 974 | 975 | /** 976 | * The maximum distance from the target which is sufficient to reach. Defaults to 0 977 | */ 978 | reachDistance?: number; 979 | 980 | /** 981 | * The maximum length of a path at any one time. Defaults to twice the distance to the target. 982 | */ 983 | maxPathLength?: number; 984 | 985 | } 986 | --------------------------------------------------------------------------------