From 6d0a4739ac9618dd1c787d2352592eb3a0ef4c1b Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Tue, 16 Apr 2024 10:11:04 +0300 Subject: [PATCH] A LOT OF WORK --- build.gradle | 2 +- gradle.properties | 1 + src/assets/mcscript.mixins.json | 5 +- .../mcscript/ClientMessageReceiver.java | 4 +- .../mcscript/EnvironmentFactory.java | 2 +- src/java/me/topchetoeu/mcscript/Main.java | 55 +++ src/java/me/topchetoeu/mcscript/McScript.java | 119 ++----- .../me/topchetoeu/mcscript/MessageQueue.java | 131 +++++++ .../me/topchetoeu/mcscript/core/Data.java | 218 ++++++++++++ src/java/me/topchetoeu/mcscript/core/Mod.java | 6 +- .../topchetoeu/mcscript/core/ModManager.java | 147 ++++++-- .../mcscript/events/ChatMessageCallback.java | 8 +- .../events/PlayerBlockPlaceEvent.java | 19 + .../mcscript/events/ScreenHandlerEvents.java | 31 ++ .../topchetoeu/mcscript/lib/MCInternals.java | 66 ++++ .../lib/common/entities/EntityLib.java | 89 +++++ .../lib/common/entities/LivingEntityLib.java | 27 ++ .../lib/common/entities/PlayerLib.java | 14 + .../mcscript/lib/server/ServerLib.java | 326 ++++++++++++++++++ .../lib/server/entities/ServerPlayerLib.java | 151 ++++++++ .../lib/server/inventory/InventoryLib.java | 96 ++++++ .../server/inventory/InventoryScreenLib.java | 31 ++ .../lib/server/world/ServerWorldLib.java | 109 ++++++ .../mcscript/lib/utils/EventLib.java | 80 +++++ .../mcscript/lib/utils/LocationLib.java | 163 +++++++++ .../mcscript/mixin/BlockItemMixin.java | 24 ++ .../mcscript/mixin/ChatScreenMixin.java | 2 +- .../mcscript/mixin/KeyboardMixin.java | 1 + .../mcscript/mixin/ScreenHandlerMixin.java | 34 ++ 29 files changed, 1834 insertions(+), 127 deletions(-) create mode 100644 src/java/me/topchetoeu/mcscript/Main.java create mode 100644 src/java/me/topchetoeu/mcscript/MessageQueue.java create mode 100644 src/java/me/topchetoeu/mcscript/core/Data.java create mode 100755 src/java/me/topchetoeu/mcscript/events/PlayerBlockPlaceEvent.java create mode 100755 src/java/me/topchetoeu/mcscript/events/ScreenHandlerEvents.java create mode 100644 src/java/me/topchetoeu/mcscript/lib/MCInternals.java create mode 100644 src/java/me/topchetoeu/mcscript/lib/common/entities/EntityLib.java create mode 100644 src/java/me/topchetoeu/mcscript/lib/common/entities/LivingEntityLib.java create mode 100644 src/java/me/topchetoeu/mcscript/lib/common/entities/PlayerLib.java create mode 100644 src/java/me/topchetoeu/mcscript/lib/server/ServerLib.java create mode 100644 src/java/me/topchetoeu/mcscript/lib/server/entities/ServerPlayerLib.java create mode 100644 src/java/me/topchetoeu/mcscript/lib/server/inventory/InventoryLib.java create mode 100644 src/java/me/topchetoeu/mcscript/lib/server/inventory/InventoryScreenLib.java create mode 100644 src/java/me/topchetoeu/mcscript/lib/server/world/ServerWorldLib.java create mode 100644 src/java/me/topchetoeu/mcscript/lib/utils/EventLib.java create mode 100644 src/java/me/topchetoeu/mcscript/lib/utils/LocationLib.java create mode 100755 src/java/me/topchetoeu/mcscript/mixin/BlockItemMixin.java create mode 100755 src/java/me/topchetoeu/mcscript/mixin/ScreenHandlerMixin.java diff --git a/build.gradle b/build.gradle index dccf764..06a8d57 100755 --- a/build.gradle +++ b/build.gradle @@ -13,7 +13,7 @@ dependencies { minecraft "com.mojang:minecraft:${project.minecraft_version}" mappings "net.fabricmc:yarn:${project.yarn_mappings}:v2" - implementation 'com.github.topchetoeu:jscript:v0.9.2-beta' + implementation "com.github.TopchetoEU:jscript:${project.jscript_version}" modImplementation "net.fabricmc:fabric-loader:${project.loader_version}" modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}" modImplementation "com.terraformersmc:modmenu:${project.modmenu_version}" diff --git a/gradle.properties b/gradle.properties index 0c8ab04..06bee13 100755 --- a/gradle.properties +++ b/gradle.properties @@ -9,6 +9,7 @@ yarn_mappings = 1.19.4+build.2 loader_version = 0.15.3 fabric_version = 0.87.2+1.19.4 modmenu_version = 6.3.1 +jscript_version = 0.9.41-beta # Project properties diff --git a/src/assets/mcscript.mixins.json b/src/assets/mcscript.mixins.json index 1e3f22d..a4774c2 100755 --- a/src/assets/mcscript.mixins.json +++ b/src/assets/mcscript.mixins.json @@ -3,7 +3,10 @@ "minVersion": "0.8", "package": "me.topchetoeu.mcscript.mixin", "compatibilityLevel": "JAVA_17", - "mixins": [], + "mixins": [ + "BlockItemMixin", + "ScreenHandlerMixin" + ], "client": [ "ChatScreenMixin", "MinecraftClientMixin", diff --git a/src/java/me/topchetoeu/mcscript/ClientMessageReceiver.java b/src/java/me/topchetoeu/mcscript/ClientMessageReceiver.java index 2fed657..b2ac82c 100755 --- a/src/java/me/topchetoeu/mcscript/ClientMessageReceiver.java +++ b/src/java/me/topchetoeu/mcscript/ClientMessageReceiver.java @@ -11,11 +11,11 @@ public class ClientMessageReceiver implements MessageReceiver { public final MinecraftClient client = MinecraftClient.getInstance(); @Override - public void sendError(String msg) { + public synchronized void sendError(String msg) { client.player.sendMessage(Text.literal("").append(msg).formatted(Formatting.RED)); } @Override - public void sendInfo(String msg) { + public synchronized void sendInfo(String msg) { client.player.sendMessage(Text.of(msg)); } } \ No newline at end of file diff --git a/src/java/me/topchetoeu/mcscript/EnvironmentFactory.java b/src/java/me/topchetoeu/mcscript/EnvironmentFactory.java index 7332db2..db13c57 100644 --- a/src/java/me/topchetoeu/mcscript/EnvironmentFactory.java +++ b/src/java/me/topchetoeu/mcscript/EnvironmentFactory.java @@ -2,7 +2,7 @@ package me.topchetoeu.mcscript; import java.util.Map; -import me.topchetoeu.jscript.core.Context; +import me.topchetoeu.jscript.runtime.Context; public interface EnvironmentFactory { Iterable dependancies(); diff --git a/src/java/me/topchetoeu/mcscript/Main.java b/src/java/me/topchetoeu/mcscript/Main.java new file mode 100644 index 0000000..4bac6f3 --- /dev/null +++ b/src/java/me/topchetoeu/mcscript/Main.java @@ -0,0 +1,55 @@ +package me.topchetoeu.mcscript; + +import static net.minecraft.server.command.CommandManager.argument; +import static net.minecraft.server.command.CommandManager.literal; + +import net.fabricmc.api.ModInitializer; +import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback; +import net.minecraft.command.EntitySelector; +import net.minecraft.command.argument.EntityArgumentType; +import net.minecraft.entity.EntityType; +import net.minecraft.entity.LightningEntity; +import net.minecraft.entity.player.PlayerEntity; + +public class Main implements ModInitializer { + private void smite(PlayerEntity player) { + var world = player.getWorld(); + var bolt = new LightningEntity(EntityType.LIGHTNING_BOLT, world); + bolt.setPosition(player.getPos()); + world.spawnEntity(bolt); + } + private void feed(PlayerEntity player) { + player.getHungerManager().setSaturationLevel(20); + player.getHungerManager().setFoodLevel(20); + } + + @Override public void onInitialize() { + CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> { + dispatcher.register( + literal("smite").then(argument("player", EntityArgumentType.players())).executes(context -> { + var selector = context.getArgument("player", EntitySelector.class); + + for (var player : selector.getPlayers(context.getSource())) { + smite(player); + } + + return 1; + }) + ); + dispatcher.register( + literal("feed").then(argument("player", EntityArgumentType.players())).executes(context -> { + var selector = context.getArgument("player", EntitySelector.class); + + for (var player : selector.getPlayers(context.getSource())) { + feed(player); + } + + return 1; + }) + ); + }); + } +} + + + diff --git a/src/java/me/topchetoeu/mcscript/McScript.java b/src/java/me/topchetoeu/mcscript/McScript.java index c01a80e..83caff5 100755 --- a/src/java/me/topchetoeu/mcscript/McScript.java +++ b/src/java/me/topchetoeu/mcscript/McScript.java @@ -1,51 +1,31 @@ package me.topchetoeu.mcscript; +import static net.minecraft.server.command.CommandManager.argument; +import static net.minecraft.server.command.CommandManager.literal; + import java.io.IOException; +import java.net.InetSocketAddress; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import me.topchetoeu.jscript.utils.debug.DebugServer; import me.topchetoeu.jscript.utils.filesystem.File; import me.topchetoeu.mcscript.core.ModManager; -import net.fabricmc.api.ClientModInitializer; -import net.fabricmc.api.EnvType; import net.fabricmc.api.ModInitializer; -import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents; +import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback; +import net.minecraft.command.EntitySelector; +import net.minecraft.command.argument.EntityArgumentType; +import net.minecraft.entity.EntityType; +import net.minecraft.entity.LightningEntity; -public class McScript implements ModInitializer, ClientModInitializer { - // private boolean openDev = false; +public class McScript implements ModInitializer { public static final Logger LOGGER = LoggerFactory.getLogger("jscript"); - // private void execute(int i, Environment env, Engine engine, String raw, MessageReceiver msg) { - // try { - // var res = engine.pushMsg(false, env, new Filename("repl", i + ".js"), raw, null).await(); - // msg.sendInfo(Values.toReadable(new Context(env), res)); - // } - // catch (RuntimeException e) { - // msg.sendError(Values.errorToReadable(e, "")); - // } - // } - - // private Environment createEnv(String location, LineReader in, LineWriter out) { - // var env = new Environment(); - // var inFile = File.ofLineReader(in); - // var outFile = File.ofLineWriter(out); - // Internals.apply(env); - - // var fs = new RootFilesystem(PermissionsProvider.get(env)); - // fs.protocols.put("std", new STDFilesystem().add("in", inFile).add("out", outFile)); - - // var perms = new PermissionsManager(); - - // env.add(PermissionsProvider.KEY, perms); - // env.add(Filesystem.KEY, fs); - - // env.global.define(null, "env", true, location); - // return env; - // } - @Override public void onInitialize() { - var mods = new ModManager("mods", "mod-data"); + var server = new DebugServer(); + server.start(new InetSocketAddress(9229), true); + var mods = new ModManager("mods", "mod-data", server); try { mods.load(); } @@ -53,69 +33,24 @@ public class McScript implements ModInitializer, ClientModInitializer { e.printStackTrace(); } - // ServerLifecycleEvents.CLIENT_STARTED.register((client) -> { - // var receiver = new ClientMessageReceiver(); - // mods.setSTD(null, File.ofLineWriter(receiver::sendInfo), File.ofLineWriter(receiver::sendError)); mods.setSTD(null, File.ofLineWriter(LOGGER::info), File.ofLineWriter(LOGGER::info)); mods.start(); - // }); - // var engine = new Engine(); + CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> { + dispatcher.register( + literal("smite").then(argument("player", EntityArgumentType.players()).executes(context -> { + var player = context.getArgument("player", EntitySelector.class); - // ServerLifecycleEvents.SERVER_STARTING.register(server -> { - // engine.start(); + for (var entity : player.getEntities(context.getSource())) { + var world = entity.getWorld(); + var bolt = new LightningEntity(EntityType.LIGHTNING_BOLT, world); + bolt.setPosition(entity.getPos()); + world.spawnEntity(bolt); + } - // var env = createEnv("SERVER", () -> null, value -> { - // for (var line : value.split("\n", -1)) LOGGER.info(line); - // }); - - // var i = new int[1]; - - // server.getCommandManager().getDispatcher().register(CommandManager.literal("msc") - // .then(CommandManager.argument("code", greedyString()).executes(c -> { - // String str = getString(c, "code"); - // var receiver = new ServerCommandMessageReceiver(c.getSource()); - // execute(i[0]++, env, engine, str, receiver); - // return 1; - // })) - // ); - // }); - // ServerLifecycleEvents.SERVER_STOPPED.register(server -> { - // engine.stop(); - // }); - } - - @net.fabricmc.api.Environment(EnvType.CLIENT) - @Override public void onInitializeClient() { - // var dev = new DevScreen(); - // var engine = new Engine(); - - ClientLifecycleEvents.CLIENT_STARTED.register((client) -> { - // var i = new int[1]; - // ChatMessageCallback.EVENT.register(args -> { - // if (args.message.startsWith("#")) { - // execute(i[0]++, env, engine, args.message.substring(1), receiver); - // args.cancelled = true; - // } - // }); - // ClientTickEvents.END_CLIENT_TICK.register((_1) -> { - // if (openDev) { - // client.setScreenAndRender(dev); - // openDev = false; - // } - // }); - // ClientCommandRegistrationCallback.EVENT.register((disp, _1) -> { - // disp.register(ClientCommandManager.literal("dev") - // .executes(c -> { - // openDev = true; - // return 1; - // }) - // ); - // }); + return 1; + })) + ); }); - - // ClientLifecycleEvents.CLIENT_STOPPING.register((client) -> { - // engine.stop(); - // }); } } diff --git a/src/java/me/topchetoeu/mcscript/MessageQueue.java b/src/java/me/topchetoeu/mcscript/MessageQueue.java new file mode 100644 index 0000000..e56c34a --- /dev/null +++ b/src/java/me/topchetoeu/mcscript/MessageQueue.java @@ -0,0 +1,131 @@ +package me.topchetoeu.mcscript; + +import java.util.Queue; +import java.util.WeakHashMap; +import java.util.concurrent.ConcurrentLinkedDeque; + +import me.topchetoeu.jscript.common.ResultRunnable; +import me.topchetoeu.jscript.common.events.DataNotifier; +import me.topchetoeu.jscript.lib.PromiseLib; +import me.topchetoeu.jscript.runtime.exceptions.EngineException; +import me.topchetoeu.jscript.runtime.exceptions.InterruptException; + +public class MessageQueue { + private static final WeakHashMap queues = new WeakHashMap<>(); + private Queue tasks = new ConcurrentLinkedDeque<>(); + + private final Thread thread; + private boolean awaiting = false; + private boolean interrupted = false; + + public void runQueue() { + synchronized (tasks) { + while (!tasks.isEmpty()) { + var task = tasks.poll(); + task.run(); + } + } + } + + public PromiseLib enqueuePromise(Runnable runnable) { + var res = new PromiseLib(); + + if (Thread.currentThread() == thread) { + runnable.run(); + res.fulfill(null, null); + return res; + } + + synchronized (tasks) { + tasks.add(() -> { + try { + runnable.run(); + res.fulfill(null, null); + } + catch (EngineException e) { + res.reject(null, e); + } + catch (Exception e) { + res.reject(null, new EngineException(e)); + } + }); + } + return res; + } + public T enqueueSync(ResultRunnable runnable) { + if (Thread.currentThread() == thread) { + return runnable.run(); + } + + var notif = new DataNotifier(); + + synchronized (tasks) { + tasks.add(() -> { + try { + notif.next(runnable.run()); + } + catch (RuntimeException e){ + notif.error(e); + } + notif.next(null); + }); + } + + synchronized (this) { + if (awaiting) { + interrupted = true; + thread.interrupt(); + } + } + + return notif.await(); + } + public void enqueueSync(Runnable runnable) { + enqueueSync(() -> { + runnable.run(); + return null; + }); + } + + public T await(DataNotifier notif) { + if (awaiting) { + System.out.println("Tried to double-await, ignoring..."); + return null; + } + + synchronized (Thread.currentThread()) { + runQueue(); + + while (true) { + try { + awaiting = true; + return (T)notif.await(); + } + catch (InterruptException e) { + if (!interrupted) throw new InterruptException(); + + Thread.interrupted(); + synchronized (this) { interrupted = false; } + runQueue(); + } + finally { + awaiting = interrupted = false; + Thread.interrupted(); + runQueue(); + } + } + } + } + + private MessageQueue(Thread thread) { + this.thread = thread; + } + + public static MessageQueue get() { + return get(Thread.currentThread()); + } + public static MessageQueue get(Thread thread) { + queues.putIfAbsent(thread, new MessageQueue(thread)); + return queues.get(thread); + } +} diff --git a/src/java/me/topchetoeu/mcscript/core/Data.java b/src/java/me/topchetoeu/mcscript/core/Data.java new file mode 100644 index 0000000..eff9f9d --- /dev/null +++ b/src/java/me/topchetoeu/mcscript/core/Data.java @@ -0,0 +1,218 @@ +package me.topchetoeu.mcscript.core; + +import me.topchetoeu.jscript.runtime.Extensions; +import me.topchetoeu.jscript.runtime.values.ArrayValue; +import me.topchetoeu.jscript.runtime.values.ConvertHint; +import me.topchetoeu.jscript.runtime.values.ObjectValue; +import me.topchetoeu.jscript.runtime.values.Values; +import me.topchetoeu.jscript.utils.interop.Arguments; +import net.minecraft.block.BlockState; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.AbstractNbtList; +import net.minecraft.nbt.AbstractNbtNumber; +import net.minecraft.nbt.NbtByte; +import net.minecraft.nbt.NbtByteArray; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.nbt.NbtDouble; +import net.minecraft.nbt.NbtElement; +import net.minecraft.nbt.NbtFloat; +import net.minecraft.nbt.NbtInt; +import net.minecraft.nbt.NbtIntArray; +import net.minecraft.nbt.NbtList; +import net.minecraft.nbt.NbtLong; +import net.minecraft.nbt.NbtLongArray; +import net.minecraft.nbt.NbtShort; +import net.minecraft.nbt.NbtString; +import net.minecraft.registry.Registries; +import net.minecraft.state.property.BooleanProperty; +import net.minecraft.state.property.EnumProperty; +import net.minecraft.state.property.IntProperty; +import net.minecraft.state.property.Property; +import net.minecraft.util.Identifier; + +@SuppressWarnings("all") +public class Data { + public static NbtCompound toNBT(Extensions ext, ObjectValue obj) { + var res = new NbtCompound(); + + for (var key : Values.getMembers(ext, obj, true, false)) { + if (!(key instanceof String)) continue; + var skey = (String)key; + String propType = null; + var i = skey.indexOf("$"); + + if (i >= 0) { + propType = skey.substring(i + 1); + skey = skey.substring(0, i); + } + + var val = toNBT(ext, Values.getMember(ext, obj, skey), propType); + + if (val != null) res.put(skey, val); + } + + return res; + } + + public static NbtList toNBT(Extensions ext, ArrayValue arr, String type) { + var res = new NbtList(); + for (var el : arr) { + var val = toNBT(ext, el, type); + if (val != null) res.add(val); + } + return res; + } + public static NbtList toNBT(Extensions ext, ArrayValue arr) { + return toNBT(ext, arr, null); + } + + public static NbtLongArray toNBTLongArr(Extensions ext, ArrayValue arr) { + var res = new long[arr.size()]; + for (var i = 0; i < arr.size(); i++) res[i] = (long)Values.toNumber(ext, arr.get(i)); + return new NbtLongArray(res); + } + public static NbtIntArray toNBTIntArr(Extensions ext, ArrayValue arr) { + var res = new int[arr.size()]; + for (var i = 0; i < arr.size(); i++) res[i] = (int)Values.toNumber(ext, arr.get(i)); + return new NbtIntArray(res); + } + public static NbtByteArray toNBTByteArr(Extensions ext, ArrayValue arr) { + var res = new byte[arr.size()]; + for (var i = 0; i < arr.size(); i++) res[i] = (byte)Values.toNumber(ext, arr.get(i)); + return new NbtByteArray(res); + } + + public static NbtElement toNBT(Extensions ext, Object obj, String type) { + if (type == null) type = ""; + + if (obj instanceof ArrayValue) { + var arr = (ArrayValue)obj; + + switch (type) { + case "l": return toNBTLongArr(ext, arr); + case "i": return toNBTIntArr(ext, arr); + case "b": return toNBTByteArr(ext, arr); + default: return toNBT(ext, arr); + } + } + + switch (type) { + case "l": return NbtLong.of((long)Values.toNumber(ext, obj)); + case "i": return NbtInt.of((int)Values.toNumber(ext, obj)); + case "s": return NbtShort.of((short)Values.toNumber(ext, obj)); + case "b": return NbtByte.of((byte)Values.toNumber(ext, obj)); + case "f": return NbtFloat.of((float)Values.toNumber(ext, obj)); + case "d": return NbtDouble.of((double)Values.toNumber(ext, obj)); + } + + if (obj instanceof ObjectValue) return toNBT(ext, (ObjectValue)obj); + + var prim = Values.toPrimitive(ext, obj, ConvertHint.VALUEOF); + + if (prim instanceof Number) return NbtDouble.of(((Number)prim).doubleValue()); + if (prim instanceof Boolean) return NbtByte.of((boolean)prim); + if (prim instanceof String) return NbtString.of((String)prim); + + return null; + } + public static NbtElement toNBT(Extensions ext, Object obj) { + return toNBT(ext, obj, null); + } + + public static NbtElement toNBT(Arguments args, int i) { + return toNBT(args, args.get(i)); + } + public static NbtCompound toCompound(Arguments args, int i) { + var val = args.get(i); + + if (val instanceof ObjectValue) { + return toNBT(args, (ObjectValue)val); + } + + return new NbtCompound(); + } + + public static ObjectValue toJS(Extensions ext, NbtCompound cmp) { + var res = new ObjectValue(); + + for (var key : cmp.getKeys()) { + var val = toJS(ext, cmp.get(key)); + if (val != null) res.defineProperty(ext, key, val); + } + + return res; + } + + public static Object toJS(Extensions ext, NbtElement obj) { + if (obj instanceof AbstractNbtNumber) return ((AbstractNbtNumber)obj).doubleValue(); + if (obj instanceof NbtString) return ((NbtString)obj).asString(); + if (obj instanceof AbstractNbtList) { + var arr = (AbstractNbtList)obj; + var res = new ArrayValue(arr.size()); + + for (var el : arr) { + var val = toJS(ext, el); + if (val != null) res.set(ext, res.size(), val); + } + + return res; + } + if (obj instanceof NbtCompound) return toJS(ext, (NbtCompound)obj); + + return null; + } + + public static NbtCompound toNBT(BlockState state) { + var res = new NbtCompound(); + + res.put("id", NbtString.of(Registries.BLOCK.getId(state.getBlock()).toString())); + + for (var prop : state.getProperties()) { + if (prop instanceof EnumProperty) res.put(prop.getName(), NbtString.of(state.get(prop).toString())); + if (prop instanceof IntProperty) res.put(prop.getName(), NbtInt.of((int)state.get((IntProperty)prop))); + if (prop instanceof BooleanProperty) res.put(prop.getName(), NbtByte.of((boolean)state.get((BooleanProperty)prop))); + } + + return res; + } + public static BlockState toBlockState(NbtCompound cmp) { + var block = Registries.BLOCK.get(new Identifier(cmp.getString("id"))); + var mgr = block.getStateManager(); + var state = mgr.getDefaultState(); + + for (var key : cmp.getKeys()) { + var prop = (Property)mgr.getProperty(key); + if (prop == null) continue; + + if (prop instanceof IntProperty) state = state.with((IntProperty)prop, cmp.getInt(key)); + if (prop instanceof EnumProperty) state = state.with((Property)prop, (Comparable)prop.parse(cmp.getString(key)).get()); + if (prop instanceof BooleanProperty) state = state.with((BooleanProperty)prop, cmp.getBoolean(key)); + } + + return state; + } + + public static NbtCompound toNBT(ItemStack stack) { + var res = new NbtCompound(); + + res.put("id", NbtString.of(Registries.ITEM.getId(stack.getItem()).toString())); + res.put("count", NbtInt.of(stack.getCount())); + + if (stack.hasNbt()) res.copyFrom(stack.getNbt()); + + return res; + } + public static ItemStack toItemStack(NbtCompound cmp) { + if (cmp == null) return null; + var item = Registries.ITEM.get(new Identifier(cmp.getString("id"))); + var stack = new ItemStack(item, cmp.getInt("count")); + + var nbt = cmp.copy(); + nbt.remove("id"); + nbt.remove("count"); + + stack.setNbt(nbt); + + return stack; + } +} diff --git a/src/java/me/topchetoeu/mcscript/core/Mod.java b/src/java/me/topchetoeu/mcscript/core/Mod.java index 52d7507..7c98bb5 100644 --- a/src/java/me/topchetoeu/mcscript/core/Mod.java +++ b/src/java/me/topchetoeu/mcscript/core/Mod.java @@ -1,8 +1,8 @@ package me.topchetoeu.mcscript.core; -import me.topchetoeu.jscript.core.Engine; -import me.topchetoeu.jscript.core.Environment; -import me.topchetoeu.jscript.core.EventLoop; +import me.topchetoeu.jscript.runtime.Engine; +import me.topchetoeu.jscript.runtime.Environment; +import me.topchetoeu.jscript.runtime.EventLoop; import me.topchetoeu.jscript.utils.filesystem.RootFilesystem; public class Mod { diff --git a/src/java/me/topchetoeu/mcscript/core/ModManager.java b/src/java/me/topchetoeu/mcscript/core/ModManager.java index 9b224d5..8c04f91 100644 --- a/src/java/me/topchetoeu/mcscript/core/ModManager.java +++ b/src/java/me/topchetoeu/mcscript/core/ModManager.java @@ -6,17 +6,29 @@ import java.nio.file.Path; import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; +import java.util.zip.ZipFile; import me.topchetoeu.jscript.common.Filename; import me.topchetoeu.jscript.common.json.JSON; -import me.topchetoeu.jscript.core.Compiler; -import me.topchetoeu.jscript.core.Engine; -import me.topchetoeu.jscript.core.Environment; -import me.topchetoeu.jscript.core.EventLoop; +import me.topchetoeu.jscript.runtime.Compiler; +import me.topchetoeu.jscript.runtime.Engine; +import me.topchetoeu.jscript.runtime.Environment; +import me.topchetoeu.jscript.runtime.EventLoop; +import me.topchetoeu.jscript.runtime.debug.DebugContext; +import me.topchetoeu.jscript.runtime.exceptions.EngineException; +import me.topchetoeu.jscript.runtime.values.Values; import me.topchetoeu.jscript.lib.Internals; import me.topchetoeu.jscript.utils.JSCompiler; +import me.topchetoeu.jscript.utils.debug.DebugServer; +import me.topchetoeu.jscript.utils.debug.SimpleDebugger; +import me.topchetoeu.jscript.utils.filesystem.ActionType; +import me.topchetoeu.jscript.utils.filesystem.EntryType; +import me.topchetoeu.jscript.utils.filesystem.ErrorReason; import me.topchetoeu.jscript.utils.filesystem.File; +import me.topchetoeu.jscript.utils.filesystem.FileStat; import me.topchetoeu.jscript.utils.filesystem.Filesystem; +import me.topchetoeu.jscript.utils.filesystem.FilesystemException; +import me.topchetoeu.jscript.utils.filesystem.Mode; import me.topchetoeu.jscript.utils.filesystem.PhysicalFilesystem; import me.topchetoeu.jscript.utils.filesystem.RootFilesystem; import me.topchetoeu.jscript.utils.filesystem.STDFilesystem; @@ -24,32 +36,109 @@ import me.topchetoeu.jscript.utils.modules.ModuleRepo; import me.topchetoeu.jscript.utils.modules.RootModuleRepo; import me.topchetoeu.jscript.utils.permissions.PermissionsManager; import me.topchetoeu.jscript.utils.permissions.PermissionsProvider; +import me.topchetoeu.mcscript.lib.MCInternals; public class ModManager { public final String codeFolder, dataFolder; public final List mods = new ArrayList<>(); + public final DebugServer debugServer; - private void loadMod(String folder) throws IOException { - var filename = Path.of(folder, "manifest.json"); - if (!filename.toFile().exists() || !filename.toFile().isFile()) return; + private Filesystem zipFs(Path path) { + var zipPath = new Path[] { path }; - var rawManifest = new String(Files.readAllBytes(filename)); - var manifest = JSON.parse(Filename.fromFile(filename.toFile()), rawManifest).map(); + return new Filesystem() { + @Override public void close() { + zipPath[0] = null; + } + @Override public File open(String path, Mode mode) { + path = normalize(path).substring(1); + + try { + if (zipPath[0] == null) throw new FilesystemException(ErrorReason.CLOSED, "Filesystem closed."); + + var file = new ZipFile(zipPath[0].toFile()); + var entry = file.getEntry(path); + + if (entry == null) { + file.close(); + throw new FilesystemException(ErrorReason.DOESNT_EXIST); + } + if (mode.writable) { + file.close(); + throw new FilesystemException(ErrorReason.NO_PERMISSION, "Zip filesystem is read-only.") + .setEntry(entry.isDirectory() ? EntryType.FOLDER : EntryType.FILE); + } + + var res = File.ofStream(file.getInputStream(entry)); + + return new File() { + @Override public int read(byte[] buff) { return res.read(buff); } + @Override public long seek(long offset, int pos) { return res.seek(offset, pos); } + @Override public void write(byte[] buff) { res.write(buff); } + @Override public boolean close() { + if (res.close()) { + try { file.close(); } + catch (IOException e) { } + return true; + } + + return false; + } + }; + } + catch (IOException e) { + throw new FilesystemException(ErrorReason.UNKNOWN, e.getMessage()).setPath(path).setAction(ActionType.OPEN); + } + catch (FilesystemException e) { + throw e.setPath(path).setAction(ActionType.OPEN); + } + } + @Override public FileStat stat(String path) { + path = normalize(path); + + try { + if (zipPath[0] == null) throw new FilesystemException(ErrorReason.CLOSED, "Filesystem closed."); + + var file = new ZipFile(zipPath[0].toFile()); + var entry = file.getEntry(path); + file.close(); + + if (entry == null) return new FileStat(Mode.NONE, EntryType.NONE); + else if (entry.isDirectory()) return new FileStat(Mode.READ, EntryType.FOLDER); + else return new FileStat(Mode.READ, EntryType.FILE); + } + catch (IOException e) { + throw new FilesystemException(ErrorReason.UNKNOWN, e.getMessage()).setPath(path).setAction(ActionType.STAT); + } + catch (FilesystemException e) { + throw e.setPath(path).setAction(ActionType.STAT); + } + } + }; + } + + private void loadFileMod(Filesystem codeFs) throws IOException { + var file = codeFs.open("manifest.json", Mode.READ); + var manifest = JSON.parse(new Filename("code", "manifest.json"), file.readToString()).map(); + file.close(); var name = manifest.string("name"); var version = manifest.string("version"); var author = manifest.string("author"); var main = manifest.string("main"); - var mainSrc = new String(Files.readAllBytes(Path.of(codeFolder.toString(), name, main))); + + file = codeFs.open(main, Mode.READ); + var mainSrc = file.readToString(); + file.close(); var env = new Environment(); var loop = new Engine(); + Files.createDirectories(Path.of(dataFolder.toString(), name)); + var fs = new RootFilesystem(PermissionsProvider.get(env)); fs.protocols.put("file", new PhysicalFilesystem(Path.of(dataFolder.toString(), name).toString())); - fs.protocols.put("code", new PhysicalFilesystem(Path.of(codeFolder.toString(), name).toString())); - - Files.createDirectories(Path.of(codeFolder.toString(), name)); + fs.protocols.put("code", codeFs); var perms = new PermissionsManager(); perms.add("jscript.file.read:**"); @@ -61,13 +150,24 @@ public class ModManager { var modules = new RootModuleRepo(); modules.repos.put("file", ModuleRepo.ofFilesystem(fs.protocols.get("code"))); + var debug = new DebugContext(); + debugServer.targets.put(name, (socket, req) -> new SimpleDebugger(socket).attach(debug)); + Internals.apply(env); + MCInternals.apply(env); env.add(EventLoop.KEY, loop); env.add(Filesystem.KEY, fs); env.add(PermissionsProvider.KEY, perms); env.add(Compiler.KEY, new JSCompiler(env)); + env.add(DebugContext.KEY, debug); - loop.pushMsg(false, env, new Filename("code", main), mainSrc, null); + new Thread(() -> { + try { + loop.pushMsg(false, env, new Filename("code", main), mainSrc, null).await(); + } + catch (EngineException e) { Values.printError(e, "in mod initializer"); } + + }, "Awaiter").start(); var mod = new Mod(name, author, version, loop, env, fs); mods.add(mod); @@ -75,19 +175,17 @@ public class ModManager { public void load() throws IOException { for (var file : Files.list(Path.of(codeFolder)).collect(Collectors.toList())) { - if (!Files.isDirectory(file)) continue; - loadMod(file.toString()); + if (Files.isDirectory(file)) { + loadFileMod(new PhysicalFilesystem(file.toString())); + } + else { + loadFileMod(zipFs(file)); + } } } public void setSTD(File in, File out, File err) { - var std = new STDFilesystem(); - - std.add("in", in); - std.add("out", out); - std.add("err", err); - for (var mod : mods) { - mod.fs.protocols.put("std", std); + mod.fs.protocols.put("std", new STDFilesystem(in, out, err)); } } public void start() { @@ -96,8 +194,9 @@ public class ModManager { } } - public ModManager(String folder, String dataFolder) { + public ModManager(String folder, String dataFolder, DebugServer server) { this.codeFolder = folder; this.dataFolder = dataFolder; + this.debugServer = server; } } \ No newline at end of file diff --git a/src/java/me/topchetoeu/mcscript/events/ChatMessageCallback.java b/src/java/me/topchetoeu/mcscript/events/ChatMessageCallback.java index 6d2a9d0..9b07b05 100755 --- a/src/java/me/topchetoeu/mcscript/events/ChatMessageCallback.java +++ b/src/java/me/topchetoeu/mcscript/events/ChatMessageCallback.java @@ -9,9 +9,13 @@ public interface ChatMessageCallback { public boolean cancelled; } - void execute(ChatArgs args); + boolean execute(ChatArgs args); public static final Event EVENT = EventFactory.createArrayBacked(ChatMessageCallback.class, arr -> args -> { - for (var el : arr) el.execute(args); + for (var el : arr) { + if (!el.execute(args)) return false; + } + + return true; }); } diff --git a/src/java/me/topchetoeu/mcscript/events/PlayerBlockPlaceEvent.java b/src/java/me/topchetoeu/mcscript/events/PlayerBlockPlaceEvent.java new file mode 100755 index 0000000..1b186a1 --- /dev/null +++ b/src/java/me/topchetoeu/mcscript/events/PlayerBlockPlaceEvent.java @@ -0,0 +1,19 @@ +package me.topchetoeu.mcscript.events; + +import net.fabricmc.fabric.api.event.Event; +import net.fabricmc.fabric.api.event.EventFactory; +import net.minecraft.block.BlockState; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.util.math.BlockPos; + +public interface PlayerBlockPlaceEvent { + boolean blockPlaced(ServerPlayerEntity player, BlockPos pos, BlockState state); + + public static final Event EVENT = EventFactory.createArrayBacked(PlayerBlockPlaceEvent.class, arr -> (player, pos, state) -> { + for (var el : arr) { + if (!el.blockPlaced(player, pos, state)) return false; + } + + return true; + }); +} diff --git a/src/java/me/topchetoeu/mcscript/events/ScreenHandlerEvents.java b/src/java/me/topchetoeu/mcscript/events/ScreenHandlerEvents.java new file mode 100755 index 0000000..175d066 --- /dev/null +++ b/src/java/me/topchetoeu/mcscript/events/ScreenHandlerEvents.java @@ -0,0 +1,31 @@ +package me.topchetoeu.mcscript.events; + +import net.fabricmc.fabric.api.event.Event; +import net.fabricmc.fabric.api.event.EventFactory; +import net.minecraft.screen.ScreenHandler; +import net.minecraft.screen.slot.SlotActionType; +import net.minecraft.server.network.ServerPlayerEntity; + +public class ScreenHandlerEvents { + public static interface SlotClickHandler { + boolean slotClicked(ScreenHandler handler, int slotIndex, int button, SlotActionType actionType, ServerPlayerEntity player); + } + public static interface ScreenCloseHandler { + boolean screenClosed(ScreenHandler handler, ServerPlayerEntity player); + } + + public static final Event SLOT_CLICKED = EventFactory.createArrayBacked(SlotClickHandler.class, arr -> (handler, slotIndex, button, actionType, player) -> { + for (var el : arr) { + if (!el.slotClicked(handler, slotIndex, button, actionType, player)) return false; + } + + return true; + }); + public static final Event SCREEN_CLOSE = EventFactory.createArrayBacked(ScreenCloseHandler.class, arr -> (handler, player) -> { + for (var el : arr) { + if (!el.screenClosed(handler, player)) return false; + } + + return true; + }); +} diff --git a/src/java/me/topchetoeu/mcscript/lib/MCInternals.java b/src/java/me/topchetoeu/mcscript/lib/MCInternals.java new file mode 100644 index 0000000..1e8503c --- /dev/null +++ b/src/java/me/topchetoeu/mcscript/lib/MCInternals.java @@ -0,0 +1,66 @@ +package me.topchetoeu.mcscript.lib; + +import me.topchetoeu.jscript.runtime.Environment; +import me.topchetoeu.jscript.runtime.scope.GlobalScope; +import me.topchetoeu.jscript.runtime.values.NativeFunction; +import me.topchetoeu.jscript.utils.interop.ExposeField; +import me.topchetoeu.jscript.utils.interop.ExposeTarget; +import me.topchetoeu.jscript.utils.interop.NativeWrapperProvider; +import me.topchetoeu.mcscript.lib.common.entities.EntityLib; +import me.topchetoeu.mcscript.lib.common.entities.LivingEntityLib; +import me.topchetoeu.mcscript.lib.common.entities.PlayerLib; +import me.topchetoeu.mcscript.lib.server.ServerLib; +import me.topchetoeu.mcscript.lib.server.entities.ServerPlayerLib; +import me.topchetoeu.mcscript.lib.server.inventory.InventoryLib; +import me.topchetoeu.mcscript.lib.server.inventory.InventoryScreenLib; +import me.topchetoeu.mcscript.lib.server.world.ServerWorldLib; +import me.topchetoeu.mcscript.lib.utils.EventLib; +import me.topchetoeu.mcscript.lib.utils.LocationLib; +import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; +import net.minecraft.entity.Entity; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.inventory.Inventory; +import net.minecraft.inventory.SimpleInventory; +import net.minecraft.screen.ScreenHandler; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.server.world.ServerWorld; + +public class MCInternals { + @ExposeField(target = ExposeTarget.STATIC) + public static final EventLib __serverLoad = new EventLib(); + + static { + ServerLifecycleEvents.SERVER_STARTED.register(server -> { + __serverLoad.invoke(server); + }); + } + + public static void apply(Environment env) { + var wp = NativeWrapperProvider.get(env); + var glob = new GlobalScope(wp.getNamespace(MCInternals.class)); + glob.obj.setPrototype(env, GlobalScope.get(env).obj); + env.remove(GlobalScope.KEY); + env.add(GlobalScope.KEY, glob); + + wp.set(Entity.class, EntityLib.class); + wp.set(LivingEntity.class, LivingEntityLib.class); + wp.set(PlayerEntity.class, PlayerLib.class); + wp.set(ServerPlayerEntity.class, ServerPlayerLib.class); + wp.set(MinecraftServer.class, ServerLib.class); + wp.set(ServerWorld.class, ServerWorldLib.class); + wp.set(Inventory.class, InventoryLib.class); + wp.set(ScreenHandler.class, InventoryScreenLib.class); + + glob.define(env, false, wp.getConstr(MinecraftServer.class)); + glob.define(env, false, wp.getConstr(LocationLib.class)); + glob.define(env, false, wp.getConstr(Entity.class)); + glob.define(env, false, wp.getConstr(LivingEntity.class)); + glob.define(env, false, wp.getConstr(PlayerEntity.class)); + glob.define(env, false, wp.getConstr(ServerPlayerEntity.class)); + glob.define(env, false, new NativeFunction("Inventory", args -> { + return new SimpleInventory(args.getInt(0)); + })); + } +} diff --git a/src/java/me/topchetoeu/mcscript/lib/common/entities/EntityLib.java b/src/java/me/topchetoeu/mcscript/lib/common/entities/EntityLib.java new file mode 100644 index 0000000..2b4c69f --- /dev/null +++ b/src/java/me/topchetoeu/mcscript/lib/common/entities/EntityLib.java @@ -0,0 +1,89 @@ +package me.topchetoeu.mcscript.lib.common.entities; + +import me.topchetoeu.jscript.runtime.values.ObjectValue; +import me.topchetoeu.jscript.utils.interop.Arguments; +import me.topchetoeu.jscript.utils.interop.Expose; +import me.topchetoeu.jscript.utils.interop.ExposeType; +import me.topchetoeu.jscript.utils.interop.WrapperName; +import me.topchetoeu.mcscript.core.Data; +import me.topchetoeu.mcscript.lib.server.ServerLib; +import me.topchetoeu.mcscript.lib.utils.LocationLib; +import net.minecraft.entity.Entity; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.text.Text; +import net.minecraft.world.World; + +@WrapperName("Entity") +public class EntityLib { + @Expose(type = ExposeType.GETTER) + public static World __world(Arguments args) { + return args.self(Entity.class).getWorld(); + } + + @Expose(type = ExposeType.GETTER) + public static String __name(Arguments args) { + var self = args.self(Entity.class); + return self.getDisplayName().getString(); + } + @Expose(type = ExposeType.SETTER, value = "name") + public static void __setName(Arguments args) { + var self = args.self(Entity.class); + ServerLib.runSync(self.getServer(), () -> { + self.setCustomName(Text.of(args.getString(0))); + }); + } + + @Expose(type = ExposeType.GETTER) + public static LocationLib __location(Arguments args) { + var self = args.self(Entity.class); + return new LocationLib(self.getX(), self.getY(), self.getZ()); + } + @Expose(type = ExposeType.SETTER, value = "location") + public static void __setLocation(Arguments args) { + var self = args.self(Entity.class); + ServerLib.runSync(self.getServer(), () -> { + var pos = new LocationLib(args); + self.refreshPositionAfterTeleport(pos.x, pos.y, pos.z); + }); + } + + @Expose(type = ExposeType.GETTER) + public static String __uuid(Arguments args) { + return args.self(ServerPlayerEntity.class).getUuidAsString(); + } + + @Expose public static void __clearName(Arguments args) { + var self = args.self(Entity.class); + ServerLib.runSync(self.getServer(), () -> { + self.setCustomName(null); + }); + } + + @Expose(type = ExposeType.GETTER) + public static ObjectValue __nbt(Arguments args) { + var self = args.self(Entity.class); + + return ServerLib.runSync(self.getServer(), () -> { + var res = new NbtCompound(); + self.writeNbt(res); + return Data.toJS(args, res); + }); + } + @Expose(type = ExposeType.SETTER, value = "nbt") + public static void __setNbt(Arguments args) { + var self = args.self(Entity.class); + + ServerLib.runSync(self.getServer(), () -> { + var nbt = Data.toNBT(args, 0); + self.readNbt((NbtCompound)nbt); + }); + } + + @Expose public static void __discard(Arguments args) { + var self = args.self(Entity.class); + ServerLib.runSync(self.getServer(), () -> { + self.discard(); + }); + } +} diff --git a/src/java/me/topchetoeu/mcscript/lib/common/entities/LivingEntityLib.java b/src/java/me/topchetoeu/mcscript/lib/common/entities/LivingEntityLib.java new file mode 100644 index 0000000..f29dd7c --- /dev/null +++ b/src/java/me/topchetoeu/mcscript/lib/common/entities/LivingEntityLib.java @@ -0,0 +1,27 @@ +package me.topchetoeu.mcscript.lib.common.entities; + +import me.topchetoeu.jscript.utils.interop.Arguments; +import me.topchetoeu.jscript.utils.interop.Expose; +import me.topchetoeu.jscript.utils.interop.ExposeType; +import me.topchetoeu.jscript.utils.interop.WrapperName; +import me.topchetoeu.mcscript.lib.server.ServerLib; +import net.minecraft.entity.LivingEntity; + +@WrapperName("LivingEntity") +public class LivingEntityLib { + @Expose(type = ExposeType.GETTER) + public static double __health(Arguments args) { + return args.self(LivingEntity.class).getHealth(); + } + @Expose(type = ExposeType.SETTER, value = "health") + public static void __setHealth(Arguments args) { + var self = args.self(LivingEntity.class); + ServerLib.runSync(self.getServer(), () -> { + self.setHealth(args.getFloat(0)); + }); + } + @Expose(type = ExposeType.GETTER) + public static double __maxHealth(Arguments args) { + return args.self(LivingEntity.class).getMaxHealth(); + } +} diff --git a/src/java/me/topchetoeu/mcscript/lib/common/entities/PlayerLib.java b/src/java/me/topchetoeu/mcscript/lib/common/entities/PlayerLib.java new file mode 100644 index 0000000..f71c480 --- /dev/null +++ b/src/java/me/topchetoeu/mcscript/lib/common/entities/PlayerLib.java @@ -0,0 +1,14 @@ +package me.topchetoeu.mcscript.lib.common.entities; + +import me.topchetoeu.jscript.utils.interop.Arguments; +import me.topchetoeu.jscript.utils.interop.Expose; +import me.topchetoeu.jscript.utils.interop.WrapperName; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.text.Text; + +@WrapperName("Player") +public class PlayerLib { + @Expose public static void __sendMessage(Arguments args) { + args.self(PlayerEntity.class).sendMessage(Text.of(args.getString(0))); + } +} diff --git a/src/java/me/topchetoeu/mcscript/lib/server/ServerLib.java b/src/java/me/topchetoeu/mcscript/lib/server/ServerLib.java new file mode 100644 index 0000000..e73e06e --- /dev/null +++ b/src/java/me/topchetoeu/mcscript/lib/server/ServerLib.java @@ -0,0 +1,326 @@ +package me.topchetoeu.mcscript.lib.server; + +import static net.minecraft.server.command.CommandManager.argument; +import static net.minecraft.server.command.CommandManager.literal; + +import java.util.Map; +import java.util.WeakHashMap; + +import com.mojang.brigadier.Command; +import com.mojang.brigadier.arguments.StringArgumentType; + +import me.topchetoeu.jscript.common.ResultRunnable; +import me.topchetoeu.jscript.common.events.DataNotifier; +import me.topchetoeu.jscript.runtime.Context; +import me.topchetoeu.jscript.runtime.EventLoop; +import me.topchetoeu.jscript.runtime.exceptions.EngineException; +import me.topchetoeu.jscript.runtime.values.ArrayValue; +import me.topchetoeu.jscript.runtime.values.NativeFunction; +import me.topchetoeu.jscript.runtime.values.ObjectValue; +import me.topchetoeu.jscript.runtime.values.Values; +import me.topchetoeu.jscript.utils.interop.Arguments; +import me.topchetoeu.jscript.utils.interop.Expose; +import me.topchetoeu.jscript.utils.interop.ExposeField; +import me.topchetoeu.jscript.utils.interop.ExposeTarget; +import me.topchetoeu.jscript.utils.interop.ExposeType; +import me.topchetoeu.jscript.utils.interop.WrapperName; +import me.topchetoeu.mcscript.MessageQueue; +import me.topchetoeu.mcscript.events.PlayerBlockPlaceEvent; +import me.topchetoeu.mcscript.events.ScreenHandlerEvents; +import me.topchetoeu.mcscript.lib.utils.EventLib; +import me.topchetoeu.mcscript.lib.utils.LocationLib; +import net.fabricmc.fabric.api.entity.event.v1.ServerEntityWorldChangeEvents; +import net.fabricmc.fabric.api.entity.event.v1.ServerLivingEntityEvents; +import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents; +import net.fabricmc.fabric.api.event.player.PlayerBlockBreakEvents; +import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents; +import net.minecraft.entity.Entity; +import net.minecraft.registry.Registries; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.command.CommandOutput; +import net.minecraft.server.command.ServerCommandSource; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.text.Text; +import net.minecraft.util.Identifier; +import net.minecraft.util.math.Vec2f; +import net.minecraft.util.math.Vec3d; + +@WrapperName("Server") +@SuppressWarnings("resource") // for crying outloud +public class ServerLib { + public static class EventCtx { + @ExposeField public final EventLib blockPlace = new EventLib(); + @ExposeField public final EventLib blockBreak = new EventLib(); + @ExposeField public final EventLib tickStart = new EventLib(); + @ExposeField public final EventLib tickEnd = new EventLib(); + @ExposeField public final EventLib playerJoin = new EventLib(); + @ExposeField public final EventLib playerLeave = new EventLib(); + @ExposeField public final EventLib playerChangeWorld = new EventLib(); + @ExposeField public final EventLib entityDamage = new EventLib(); + @ExposeField public final EventLib inventoryScreenClicked = new EventLib(); + @ExposeField public final EventLib inventoryScreenClosed = new EventLib(); + } + + private static final WeakHashMap ctxs = new WeakHashMap<>(); + + public static EventCtx events(MinecraftServer server) { + ctxs.putIfAbsent(server, new EventCtx()); + return ctxs.get(server); + } + public static MessageQueue queue(MinecraftServer server) { + return MessageQueue.get(server.getThread()); + } + public static MinecraftServer get(Entity obj) { + return obj.getServer(); + } + + public static T runSync(MinecraftServer server, ResultRunnable runnable) { + var res = new DataNotifier(); + + queue(server).enqueueSync(() -> { + try { res.next(runnable.run()); } + catch (RuntimeException e) { res.error(e); } + }); + + return res.await(); + } + public static void runSync(MinecraftServer server, Runnable runnable) { + runSync(server, () -> { + runnable.run(); + return null; + }); + } + + @Expose(type = ExposeType.GETTER) + public static EventLib __blockBreak(Arguments args) { + return events(args.self(MinecraftServer.class)).blockBreak; + } + @Expose(type = ExposeType.GETTER) + public static EventLib __blockPlace(Arguments args) { + return events(args.self(MinecraftServer.class)).blockPlace; + } + @Expose(type = ExposeType.GETTER) + public static EventLib __tickStart(Arguments args) { + return events(args.self(MinecraftServer.class)).tickStart; + } + @Expose(type = ExposeType.GETTER) + public static EventLib __tickEnd(Arguments args) { + return events(args.self(MinecraftServer.class)).tickEnd; + } + @Expose(type = ExposeType.GETTER) + public static EventLib __playerJoin(Arguments args) { + return events(args.self(MinecraftServer.class)).playerJoin; + } + @Expose(type = ExposeType.GETTER) + public static EventLib __playerLeave(Arguments args) { + return events(args.self(MinecraftServer.class)).playerLeave; + } + @Expose(type = ExposeType.GETTER) + public static EventLib __playerChangeWorld(Arguments args) { + return events(args.self(MinecraftServer.class)).playerChangeWorld; + } + + @Expose(type = ExposeType.GETTER) + public static EventLib __entityDamage(Arguments args) { + return events(args.self(MinecraftServer.class)).entityDamage; + } + + @Expose(type = ExposeType.GETTER) + public static EventLib __inventoryScreenClicked(Arguments args) { + return events(args.self(MinecraftServer.class)).inventoryScreenClicked; + } + @Expose(type = ExposeType.GETTER) + public static EventLib __inventoryScreenClosed(Arguments args) { + return events(args.self(MinecraftServer.class)).inventoryScreenClosed; + } + + @Expose(type = ExposeType.GETTER) + public static ArrayValue __worlds(Arguments args) { + var server = args.self(MinecraftServer.class); + var res = new ArrayValue(); + + for (var world : server.getWorlds()) { + res.set(args.ctx, res.size(), world); + } + + return res; + } + @Expose(type = ExposeType.GETTER) + public static ArrayValue __players(Arguments args) { + var server = args.self(MinecraftServer.class); + var res = new ArrayValue(); + + for (var player : server.getPlayerManager().getPlayerList()) { + res.set(args.ctx, res.size(), player); + } + + return res; + } + + @Expose public static void __registerCommand(Arguments args) { + var server = args.self(MinecraftServer.class); + var name = args.convert(0, String.class); + var obj = args.convert(1, ObjectValue.class); + + var exec = (Command)(context) -> { + return queue(server).await(EventLoop.get(args.ctx).pushMsg(() -> { + try { + String cmdArgs; + var ctx = Context.clean(args.ctx); + + try { + cmdArgs = context.getArgument("args", String.class); + } + catch (IllegalArgumentException e) { + cmdArgs = ""; + } + + var res = Values.call( + ctx, Values.getMember(ctx, obj, "execute"), obj, + cmdArgs, + context.getSource().getEntity(), + new NativeFunction(_args -> { + context.getSource().sendMessage(Text.of(_args.convert(0, String.class))); + return null; + }), + new NativeFunction(_args -> { + context.getSource().sendError(Text.of(_args.convert(0, String.class))); + return null; + }) + ); + return (int)Values.toNumber(ctx, res); + } + catch (EngineException e) { + context.getSource().sendError(Text.of(Values.errorToReadable(e, ""))); + return 0; + } + }, false)); + }; + + server.getCommandManager().getDispatcher().register(literal(name).executes(exec).then( + argument("args", StringArgumentType.greedyString()).executes(exec) + )); + } + @Expose public static void __sendMessage(Arguments args) { + var server = args.self(MinecraftServer.class); + var text = args.convert(0, String.class); + + server.sendMessage(Text.of(text)); + } + + @Expose public static ObjectValue __cmd(Arguments args) { + var server = args.self(MinecraftServer.class); + var cmd = args.getString(0); + var opts = (ObjectValue)args.getOrDefault(1, new ObjectValue()); + + var loc = Values.wrapper(Values.getMember(args, opts, "at"), LocationLib.class); + var pitch = Values.toNumber(args.ctx, Values.getMember(args, opts, "pitch")); + var yaw = Values.toNumber(args.ctx, Values.getMember(args, opts, "yaw")); + var entity = Values.wrapper(Values.getMember(args, opts, "as"), Entity.class); + var world = Values.wrapper(Values.getMember(args, opts, "world"), ServerWorld.class); + + if (loc == null) loc = new LocationLib(0, 0, 0); + if (world == null) world = server.getWorlds().iterator().next(); + + var output = new ArrayValue(); + + var sender = new ServerCommandSource( + new CommandOutput() { + @Override public void sendMessage(Text text) { + output.set(args, output.size(), text.getString()); + } + @Override public boolean shouldBroadcastConsoleToOps() { return false; } + @Override public boolean shouldReceiveFeedback() { return true; } + @Override public boolean shouldTrackOutput() { return false; } + }, + new Vec3d(loc.x, loc.y, loc.z), new Vec2f((float)pitch, (float)yaw), + world, 4, "js", Text.of("js"), + server, entity + ); + + var code = server.getCommandManager().executeWithPrefix(sender, cmd); + + return new ObjectValue(args, Map.of( + "output", output, + "code", code + )); + } + + static { + ServerTickEvents.START_SERVER_TICK.register(server -> { + events(server).tickStart.invoke(); + }); + ServerTickEvents.END_SERVER_TICK.register(server -> { + events(server).tickEnd.invoke(); + }); + ServerPlayConnectionEvents.JOIN.register((handler, sender, server) -> { + events(server).playerJoin.invoke(handler.player); + }); + ServerPlayConnectionEvents.DISCONNECT.register((handler, server) -> { + events(server).playerLeave.invoke(handler.player); + }); + ServerEntityWorldChangeEvents.AFTER_PLAYER_CHANGE_WORLD.register((player, origin, destination) -> { + events(player.server).playerChangeWorld.invoke(player, origin, destination); + }); + PlayerBlockPlaceEvent.EVENT.register((player, pos, state) -> { + return events(player.getServer()).blockPlace.invokeCancellable( + LocationLib.of(pos), player, player.getWorld() + ); + }); + PlayerBlockBreakEvents.BEFORE.register((world, player, pos, state, blockEntity) -> { + if (!(world instanceof ServerWorld)) return true; + + return events(world.getServer()).blockBreak.invokeCancellable( + LocationLib.of(pos), player, world + ); + }); + ServerLivingEntityEvents.ALLOW_DAMAGE.register((entity, damageSource, damageAmount) -> { + var dmgSrc = new ObjectValue(); + + dmgSrc.defineProperty(null, "damager", damageSource.getAttacker()); + dmgSrc.defineProperty(null, "location", LocationLib.of(damageSource.getPosition())); + dmgSrc.defineProperty(null, "type", damageSource.getType().msgId()); + + return events(entity.getServer()).entityDamage.invokeCancellable(entity, damageAmount, dmgSrc); + }); + ScreenHandlerEvents.SCREEN_CLOSE.register((handler, player) -> { + return events(player.getServer()).inventoryScreenClosed.invokeCancellable( + handler, player + ); + }); + ScreenHandlerEvents.SLOT_CLICKED.register((handler, slotIndex, rawButton, rawActionType, player) -> { + var actType = ""; + + switch (rawActionType) { + case CLONE: actType = "clone"; break; + case PICKUP: actType = "pickup"; break; + case PICKUP_ALL: actType = "pickupAll"; break; + case QUICK_CRAFT: actType = "quickCraft"; break; + case QUICK_MOVE: actType = "quickMove"; break; + case SWAP: actType = "swap"; break; + case THROW: actType = "throw"; break; + } + + return events(player.getServer()).inventoryScreenClicked.invokeCancellable( + handler, player, slotIndex < 0 ? null : slotIndex, rawButton, actType + ); + }); + // .ALLOW_DAMAGE.register((entity, damageSource, damageAmount) -> { + // var dmgSrc = new ObjectValue(); + + // dmgSrc.defineProperty(null, "damager", damageSource.getAttacker()); + // dmgSrc.defineProperty(null, "location", LocationLib.of(damageSource.getPosition())); + // dmgSrc.defineProperty(null, "type", damageSource.getType().msgId()); + + // return events(entity.getServer()).entityDamage.invokeCancellable(entity, damageAmount, dmgSrc); + // }); + } + + @Expose(target = ExposeTarget.STATIC) + public static int __maxStack(Arguments args) { + var item = Registries.ITEM.get(new Identifier(args.getString(0))); + + if (item == null) return 0; + else return item.getMaxCount(); + } +} diff --git a/src/java/me/topchetoeu/mcscript/lib/server/entities/ServerPlayerLib.java b/src/java/me/topchetoeu/mcscript/lib/server/entities/ServerPlayerLib.java new file mode 100644 index 0000000..8c00a43 --- /dev/null +++ b/src/java/me/topchetoeu/mcscript/lib/server/entities/ServerPlayerLib.java @@ -0,0 +1,151 @@ +package me.topchetoeu.mcscript.lib.server.entities; + +import me.topchetoeu.jscript.common.json.JSON; +import me.topchetoeu.jscript.runtime.exceptions.EngineException; +import me.topchetoeu.jscript.runtime.values.ObjectValue; +import me.topchetoeu.jscript.utils.interop.Arguments; +import me.topchetoeu.jscript.utils.interop.Expose; +import me.topchetoeu.jscript.utils.interop.ExposeType; +import me.topchetoeu.jscript.utils.interop.WrapperName; +import me.topchetoeu.mcscript.lib.server.ServerLib; +import net.minecraft.inventory.Inventory; +import net.minecraft.network.packet.s2c.play.SubtitleS2CPacket; +import net.minecraft.network.packet.s2c.play.TitleFadeS2CPacket; +import net.minecraft.network.packet.s2c.play.TitleS2CPacket; +import net.minecraft.screen.Generic3x3ContainerScreenHandler; +import net.minecraft.screen.GenericContainerScreenHandler; +import net.minecraft.screen.ScreenHandler; +import net.minecraft.screen.ScreenHandlerFactory; +import net.minecraft.screen.ScreenHandlerType; +import net.minecraft.screen.SimpleNamedScreenHandlerFactory; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.text.Text; +import net.minecraft.world.GameMode; + +@WrapperName("ServerPlayer") +public class ServerPlayerLib { + @Expose(type = ExposeType.GETTER) + public static String __gamemode(Arguments args) { + switch (args.self(ServerPlayerEntity.class).interactionManager.getGameMode()) { + case SURVIVAL: return "survival"; + case CREATIVE: return "creative"; + case ADVENTURE: return "adventure"; + case SPECTATOR: return "spectator"; + default: return "unknonw"; + } + } + @Expose(type = ExposeType.SETTER, value = "gamemode") + public static void __setGamemode(Arguments args) { + var self = args.self(ServerPlayerEntity.class); + var gm = args.getString(0); + + ServerLib.runSync(self.getServer(), () -> { + // System.out.println("Set %s's gamemode to %s".formatted(self.getName().getString(), gm.toString())); + switch (gm) { + case "survival": self.changeGameMode(GameMode.SURVIVAL); break; + case "creative": self.changeGameMode(GameMode.CREATIVE); break; + case "adventure": self.changeGameMode(GameMode.ADVENTURE); break; + case "spectator": self.changeGameMode(GameMode.SPECTATOR); break; + default: throw EngineException.ofError("Invalid gamemode '%s'.".formatted(gm)); + } + }); + } + + @Expose(type = ExposeType.GETTER) + public static double __foodLevel(Arguments args) { + return args.self(ServerPlayerEntity.class).getHungerManager().getFoodLevel(); + } + @Expose(type = ExposeType.SETTER, value = "foodLevel") + public static void __setFoodLevel(Arguments args) { + var self = args.self(ServerPlayerEntity.class); + + ServerLib.runSync(self.getServer(), () -> { + args.self(ServerPlayerEntity.class).getHungerManager().setFoodLevel(args.getInt(0)); + }); + } + + @Expose(type = ExposeType.GETTER) + public static double __saturation(Arguments args) { + return args.self(ServerPlayerEntity.class).getHungerManager().getSaturationLevel(); + } + @Expose(type = ExposeType.SETTER, value = "saturation") + public static void __setSaturation(Arguments args) { + var self = args.self(ServerPlayerEntity.class); + + self.getInventory(); + + ServerLib.runSync(self.getServer(), () -> { + args.self(ServerPlayerEntity.class).getHungerManager().setSaturationLevel(args.getFloat(0)); + }); + } + + @Expose public static void __sendTitle(Arguments args) { + var self = args.self(ServerPlayerEntity.class); + var conf = JSON.fromJs(args.ctx, args.convert(0, ObjectValue.class)).map(); + + var title = conf.string("title", ""); + var subtitle = conf.string("subtitle", ""); + var fadeIn = conf.number("fadeIn", 0); + var fadeOut = conf.number("fadeOut", 0); + var duration = conf.number("duration", 1); + + self.networkHandler.sendPacket(new TitleFadeS2CPacket((int)(fadeIn * 20), (int)(duration * 20), (int)(fadeOut * 20))); + self.networkHandler.sendPacket(new SubtitleS2CPacket(Text.of(subtitle))); + self.networkHandler.sendPacket(new TitleS2CPacket(Text.of(title))); + } + + @Expose(type = ExposeType.GETTER) + public static Inventory __inventory(Arguments args) { + var self = args.self(ServerPlayerEntity.class); + return self.getInventory(); + } + @Expose(type = ExposeType.GETTER) + public static ScreenHandler __screen(Arguments args) { + var self = args.self(ServerPlayerEntity.class); + + return self.currentScreenHandler; + } + + @Expose public static ScreenHandler __openInventory(Arguments args) { + var self = args.self(ServerPlayerEntity.class); + var name = args.getString(0); + var type = args.getString(1); + var inv = args.convert(2, Inventory.class); + ScreenHandlerFactory factory; + + switch (type) { + case "3x3": + factory = (i, pinv, player) -> new Generic3x3ContainerScreenHandler(i, pinv, inv); + break; + case "9x1": + factory = (i, pinv, player) -> new GenericContainerScreenHandler(ScreenHandlerType.GENERIC_9X1, i, pinv, inv, 1); + break; + case "9x2": + factory = (i, pinv, player) -> new GenericContainerScreenHandler(ScreenHandlerType.GENERIC_9X2, i, pinv, inv, 2); + break; + case "9x3": + factory = (i, pinv, player) -> new GenericContainerScreenHandler(ScreenHandlerType.GENERIC_9X3, i, pinv, inv, 3); + break; + case "9x4": + factory = (i, pinv, player) -> new GenericContainerScreenHandler(ScreenHandlerType.GENERIC_9X4, i, pinv, inv, 4); + break; + case "9x5": + factory = (i, pinv, player) -> new GenericContainerScreenHandler(ScreenHandlerType.GENERIC_9X5, i, pinv, inv, 5); + break; + case "9x6": + default: + factory = (i, pinv, player) -> new GenericContainerScreenHandler(ScreenHandlerType.GENERIC_9X6, i, pinv, inv, 6); + break; + } + + self.openHandledScreen(new SimpleNamedScreenHandlerFactory(factory, Text.of(name))); + return self.currentScreenHandler; + } + @Expose public static void __closeInventory(Arguments args) { + var self = args.self(ServerPlayerEntity.class); + + ServerLib.runSync(self.getServer(), () -> { + self.closeHandledScreen(); + }); + } +} diff --git a/src/java/me/topchetoeu/mcscript/lib/server/inventory/InventoryLib.java b/src/java/me/topchetoeu/mcscript/lib/server/inventory/InventoryLib.java new file mode 100644 index 0000000..afa930a --- /dev/null +++ b/src/java/me/topchetoeu/mcscript/lib/server/inventory/InventoryLib.java @@ -0,0 +1,96 @@ +package me.topchetoeu.mcscript.lib.server.inventory; + +import java.util.Iterator; + +import me.topchetoeu.jscript.runtime.values.ObjectValue; +import me.topchetoeu.jscript.runtime.values.Values; +import me.topchetoeu.jscript.utils.interop.Arguments; +import me.topchetoeu.jscript.utils.interop.Expose; +import me.topchetoeu.jscript.utils.interop.ExposeType; +import me.topchetoeu.jscript.utils.interop.WrapperName; +import me.topchetoeu.mcscript.core.Data; +import net.minecraft.inventory.Inventory; +import net.minecraft.inventory.SimpleInventory; +import net.minecraft.item.Items; +import net.minecraft.nbt.NbtCompound; + +@WrapperName("Inventory") +public class InventoryLib { + @Expose(type = ExposeType.GETTER) + public static int __size(Arguments args) { + var self = args.self(Inventory.class); + return self.size(); + } + + @Expose public static ObjectValue __get(Arguments args) { + var self = args.self(Inventory.class); + var i = args.getInt(0); + return Data.toJS(args, Data.toNBT(self.getStack(i))); + } + @Expose public static void __set(Arguments args) { + var self = args.self(Inventory.class); + + var i = args.getInt(0); + + var item = Data.toItemStack((NbtCompound)Data.toNBT(args, 1)); + if (item != null && item.getItem() != Items.AIR) { + self.setStack(i, item); + return; + } + + self.removeStack(i); + } + @Expose public static void __clear(Arguments args) { + var self = args.self(Inventory.class); + self.clear(); + } + + @Expose public static Inventory __clone(Arguments args) { + var self = args.self(Inventory.class); + var res = new SimpleInventory(self.size()); + + for (var i = 0; i < res.size(); i++) { + res.setStack(i, self.getStack(i)); + } + + return res; + } + + @Expose public static void __copyFrom(Arguments args) { + var self = args.self(Inventory.class); + var from = args.convert(0, Inventory.class); + + for (var i = 0; i < self.size() && i < from.size(); i++) { + self.setStack(i, from.getStack(i)); + } + } + + @Expose("@@Symbol.iterator") + public static ObjectValue __iterator(Arguments args) { + var self = args.self(Inventory.class); + + return Values.toJSIterator(args, new Iterator<>() { + private int i = 0; + + @Override public boolean hasNext() { + while (i < self.size()) { + var item = self.getStack(i); + if (item != null && item.getItem() != Items.AIR) return true; + i++; + } + + return false; + } + @Override public Object next() { + if (i >= self.size()) return null; + + while (i < self.size()) { + var item = self.getStack(i++); + if (item != null && item.getItem() != Items.AIR) return Data.toJS(args, Data.toNBT(item)); + } + + return null; + } + }); + } +} diff --git a/src/java/me/topchetoeu/mcscript/lib/server/inventory/InventoryScreenLib.java b/src/java/me/topchetoeu/mcscript/lib/server/inventory/InventoryScreenLib.java new file mode 100644 index 0000000..918af5d --- /dev/null +++ b/src/java/me/topchetoeu/mcscript/lib/server/inventory/InventoryScreenLib.java @@ -0,0 +1,31 @@ +package me.topchetoeu.mcscript.lib.server.inventory; + +import java.util.HashSet; + +import me.topchetoeu.jscript.runtime.values.ArrayValue; +import me.topchetoeu.jscript.utils.interop.Arguments; +import me.topchetoeu.jscript.utils.interop.Expose; +import me.topchetoeu.jscript.utils.interop.WrapperName; +import net.minecraft.inventory.Inventory; +import net.minecraft.screen.ScreenHandler; + +// I couldn't have come up with a shittier way to do inventories if my life depended on it +// Bravo, Mojang, Bravo + +@WrapperName("InventoryScreen") +public class InventoryScreenLib { + @Expose public static int __id(Arguments args) { + return args.self(ScreenHandler.class).syncId; + } + @Expose public static ArrayValue __inventories(Arguments args) { + var tmp = new HashSet(); + var res = new ArrayValue(); + + for (var slot : args.self(ScreenHandler.class).slots) { + var inv = slot.inventory; + if (tmp.add(inv)) res.set(args, res.size(), inv); + } + + return res; + } +} diff --git a/src/java/me/topchetoeu/mcscript/lib/server/world/ServerWorldLib.java b/src/java/me/topchetoeu/mcscript/lib/server/world/ServerWorldLib.java new file mode 100644 index 0000000..1551d2b --- /dev/null +++ b/src/java/me/topchetoeu/mcscript/lib/server/world/ServerWorldLib.java @@ -0,0 +1,109 @@ +package me.topchetoeu.mcscript.lib.server.world; + +import me.topchetoeu.jscript.common.json.JSON; +import me.topchetoeu.jscript.runtime.exceptions.EngineException; +import me.topchetoeu.jscript.runtime.values.ArrayValue; +import me.topchetoeu.jscript.runtime.values.ObjectValue; +import me.topchetoeu.jscript.utils.interop.Arguments; +import me.topchetoeu.jscript.utils.interop.Expose; +import me.topchetoeu.jscript.utils.interop.ExposeType; +import me.topchetoeu.jscript.utils.interop.WrapperName; +import me.topchetoeu.mcscript.core.Data; +import me.topchetoeu.mcscript.lib.server.ServerLib; +import me.topchetoeu.mcscript.lib.utils.LocationLib; +import net.minecraft.block.Block; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityType; +import net.minecraft.registry.Registries; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.state.property.Property; +import net.minecraft.util.Identifier; +import net.minecraft.util.math.BlockPos; + +@WrapperName("ServerWorld") +public class ServerWorldLib { + @Expose(type = ExposeType.GETTER) public static ArrayValue __players(Arguments args) { + var self = args.self(ServerWorld.class); + var res = new ArrayValue(); + + for (var player : self.getPlayers()) { + res.set(args.ctx, res.size(), player); + } + + return res; + } + + @Expose public static ObjectValue __getBlock(Arguments args) { + var self = args.self(ServerWorld.class); + var loc = args.convert(0, LocationLib.class); + var desc = new ObjectValue(); + + ServerLib.runSync(self.getServer(), () -> { + var state = self.getBlockState(new BlockPos((int)loc.x, (int)loc.y, (int)loc.z)); + + desc.defineProperty(args, "id", Registries.BLOCK.getId(state.getBlock()).toString()); + + for (var prop : state.getProperties()) { + var raw = state.get(prop); + if (raw instanceof Number || raw instanceof Boolean) desc.defineProperty(args, prop.getName(), raw); + else desc.defineProperty(args, prop.getName(), raw.toString()); + } + }); + + return desc; + } + @SuppressWarnings("all") + @Expose public static void __setBlock(Arguments args) { + var self = args.self(ServerWorld.class); + var loc = args.convert(0, LocationLib.class); + var nbt = JSON.fromJs(args, (ObjectValue)args.getOrDefault(1, new ObjectValue())).map(); + var update = args.has(2) ? args.getBoolean(2) : true; + var id = new Identifier(nbt.string("id")); + + ServerLib.runSync(self.getServer(), () -> { + var block = Registries.BLOCK.get(id); + if (block == null) throw EngineException.ofError("No block %s.".formatted(id)); + + var stateMgr = block.getStateManager(); + var state = block.getDefaultState(); + + for (var entry : nbt.entrySet()) { + var prop = stateMgr.getProperty(entry.getKey()); + if (prop == null) continue; + + var val = prop.parse(entry.getValue().toString()); + if (val.isEmpty()) throw EngineException.ofError("Illegal value '%s' provided for property %s.".formatted(entry.getValue(), entry.getKey())); + + // I'm so done with java generics + state = state.with((Property)prop, (Comparable)val.get()); + } + + var pos = new BlockPos((int)loc.x, (int)loc.y, (int)loc.z); + + self.setBlockState(pos, state, Block.NOTIFY_LISTENERS | Block.FORCE_STATE); + + if (update) { + self.updateNeighbors(pos, state.getBlock()); + } + }); + } + + @Expose public static Entity __summon(Arguments args) { + var self = args.self(ServerWorld.class); + var loc = args.convert(0, LocationLib.class); + var nbt = Data.toCompound(args, 1); + + return ServerLib.runSync(self.getServer(), () -> { + var entity = EntityType.loadEntityWithPassengers(nbt, self, val -> { + // val.readNbt(nbt); + val.refreshPositionAndAngles(loc.x, loc.y, loc.z, val.getYaw(), val.getPitch()); + return val; + }); + + if (entity == null) return null; + if (!self.spawnNewEntityAndPassengers(entity)) return null; + + return entity; + }); + } +} diff --git a/src/java/me/topchetoeu/mcscript/lib/utils/EventLib.java b/src/java/me/topchetoeu/mcscript/lib/utils/EventLib.java new file mode 100644 index 0000000..c8bd6c0 --- /dev/null +++ b/src/java/me/topchetoeu/mcscript/lib/utils/EventLib.java @@ -0,0 +1,80 @@ +package me.topchetoeu.mcscript.lib.utils; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import me.topchetoeu.jscript.lib.PromiseLib; +import me.topchetoeu.jscript.runtime.Context; +import me.topchetoeu.jscript.runtime.EventLoop; +import me.topchetoeu.jscript.runtime.Extensions; +import me.topchetoeu.jscript.runtime.exceptions.EngineException; +import me.topchetoeu.jscript.runtime.values.ArrayValue; +import me.topchetoeu.jscript.runtime.values.FunctionValue; +import me.topchetoeu.jscript.runtime.values.NativeFunction; +import me.topchetoeu.jscript.runtime.values.Values; +import me.topchetoeu.jscript.utils.interop.Arguments; +import me.topchetoeu.jscript.utils.interop.Expose; +import me.topchetoeu.jscript.utils.interop.WrapperName; +import me.topchetoeu.mcscript.MessageQueue; + +@WrapperName("Event") +public class EventLib { + private HashMap handles = new HashMap<>(); + private HashMap onceHandles = new HashMap<>(); + + public void invoke(Object ...args) { + List> arr; + + synchronized (handles) { + synchronized (onceHandles) { + arr = Stream.concat(handles.entrySet().stream(), onceHandles.entrySet().stream()).collect(Collectors.toList()); + onceHandles.clear(); + } + } + + for (var handle : arr) { + var env = handle.getValue(); + var func = handle.getKey(); + + try { + MessageQueue.get().await(EventLoop.get(env).pushMsg(false, env, func, null, args)); + } + catch (EngineException e) { Values.printError(e, "in event handler"); } + } + } + + public boolean invokeCancellable(Object ...args) { + var cancelled = new boolean[1]; + var newArgs = new Object[args.length + 1]; + newArgs[0] = new NativeFunction("cancel", _args -> { cancelled[0] = true; return null; }); + System.arraycopy(args, 0, newArgs, 1, args.length); + + invoke(newArgs); + + return !cancelled[0]; + } + + @Expose + public void __on(Arguments args) { + var func = args.convert(0, FunctionValue.class); + handles.put(func, Context.clean(args.ctx)); + } + @Expose + public void __once(Arguments args) { + var func = args.convert(0, FunctionValue.class); + onceHandles.put(func, Context.clean(args.ctx)); + } + @Expose + public PromiseLib __next(Arguments args) { + var promise = new PromiseLib(); + onceHandles.put(new NativeFunction(_args -> { + promise.fulfill(_args.ctx, new ArrayValue(_args.ctx, _args.args)); + return null; + }), Context.clean(args.ctx)); + + return promise; + } +} diff --git a/src/java/me/topchetoeu/mcscript/lib/utils/LocationLib.java b/src/java/me/topchetoeu/mcscript/lib/utils/LocationLib.java new file mode 100644 index 0000000..392932a --- /dev/null +++ b/src/java/me/topchetoeu/mcscript/lib/utils/LocationLib.java @@ -0,0 +1,163 @@ +package me.topchetoeu.mcscript.lib.utils; + +import me.topchetoeu.jscript.utils.interop.Arguments; +import me.topchetoeu.jscript.utils.interop.Expose; +import me.topchetoeu.jscript.utils.interop.ExposeConstructor; +import me.topchetoeu.jscript.utils.interop.ExposeType; +import me.topchetoeu.jscript.utils.interop.WrapperName; +import net.minecraft.util.math.Position; +import net.minecraft.util.math.Vec3i; + +@WrapperName("Location") +public class LocationLib { + public final double x, y, z; + + @Expose(type = ExposeType.GETTER) + public double __x() { return x; } + @Expose(type = ExposeType.GETTER) + public double __y() { return y; } + @Expose(type = ExposeType.GETTER) + public double __z() { return z; } + + @Expose public LocationLib __add(Arguments args) { + var resX = x; + var resY = y; + var resZ = z; + + int i = 0; + + while (i < args.n()) { + if (args.get(i) instanceof Number) { + resX += args.convert(i, Double.class); + resY += args.convert(i + 1, Double.class); + resZ += args.convert(i + 2, Double.class); + i += 3; + } + else { + var val = args.convert(i, LocationLib.class); + resX += val.x; + resY += val.y; + resZ += val.z; + i++; + } + } + + return new LocationLib(resX, resY, resZ); + } + @Expose public LocationLib __subtract(Arguments args) { + var resX = x; + var resY = y; + var resZ = z; + + int i = 0; + + while (i < args.n()) { + if (args.get(i) instanceof Number) { + resX -= args.convert(i, Double.class); + resY -= args.convert(i + 1, Double.class); + resZ -= args.convert(i + 2, Double.class); + i += 3; + } + else { + var val = args.convert(i, LocationLib.class); + resX -= val.x; + resY -= val.y; + resZ -= val.z; + i++; + } + } + + return new LocationLib(resX, resY, resZ); + } + @Expose public double __dot(Arguments args) { + var resX = x; + var resY = y; + var resZ = z; + + if (args.get(0) instanceof Number) { + resX *= args.convert(0, Double.class); + resY *= args.convert(1, Double.class); + resZ *= args.convert(2, Double.class); + } + else { + var val = args.convert(0, LocationLib.class); + resX *= val.x; + resY *= val.y; + resZ *= val.z; + } + + return resX + resY + resZ; + } + @Expose public double __distance(Arguments args) { + var resX = x; + var resY = y; + var resZ = z; + + if (args.get(0) instanceof Number) { + resX -= args.convert(0, Double.class); + resY -= args.convert(1, Double.class); + resZ -= args.convert(2, Double.class); + } + else { + var val = args.convert(0, LocationLib.class); + resX -= val.x; + resY -= val.y; + resZ -= val.z; + } + + return Math.sqrt(resX * resX + resY * resY + resZ * resZ); + } + @Expose public double __length() { + return Math.sqrt(x * x + y * y + z * z); + } + + @Expose public LocationLib __setX(Arguments args) { + if (args.get(0) instanceof Number) return new LocationLib(args.convert(0, Double.class), y, z); + else return new LocationLib(args.convert(0, LocationLib.class).x, y, z); + } + @Expose public LocationLib __setY(Arguments args) { + if (args.get(0) instanceof Number) return new LocationLib(x, args.convert(0, Double.class), z); + else return new LocationLib(x, args.convert(0, LocationLib.class).y, z); + } + @Expose public LocationLib __setZ(Arguments args) { + if (args.get(0) instanceof Number) return new LocationLib(x, y, args.convert(0, Double.class)); + else return new LocationLib(x, y, args.convert(0, LocationLib.class).z); + } + + @Expose public String __toString(Arguments args) { + return "[%s %s %s]".formatted(x, y, z); + } + + public LocationLib(double x, double y, double z) { + this.x = x; + this.y = y; + this.z = z; + } + public LocationLib(Arguments args) { + if (args.get(0) instanceof Number) { + x = args.convert(0, Double.class); + y = args.convert(1, Double.class); + z = args.convert(2, Double.class); + } + else { + var val = args.convert(0, LocationLib.class); + x = val.x; + y = val.y; + z = val.z; + } + } + + @ExposeConstructor + public static LocationLib __constructor(Arguments args) { + return new LocationLib(args); + } + + public static LocationLib of(Position vec) { + if (vec == null) return null; + return new LocationLib(vec.getX(), vec.getY(), vec.getZ()); + } + public static LocationLib of(Vec3i vec) { + if (vec == null) return null; + return new LocationLib(vec.getX(), vec.getY(), vec.getZ()); + } +} diff --git a/src/java/me/topchetoeu/mcscript/mixin/BlockItemMixin.java b/src/java/me/topchetoeu/mcscript/mixin/BlockItemMixin.java new file mode 100755 index 0000000..07c70c2 --- /dev/null +++ b/src/java/me/topchetoeu/mcscript/mixin/BlockItemMixin.java @@ -0,0 +1,24 @@ +package me.topchetoeu.mcscript.mixin; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import me.topchetoeu.mcscript.events.PlayerBlockPlaceEvent; +import net.minecraft.block.BlockState; +import net.minecraft.item.BlockItem; +import net.minecraft.item.ItemPlacementContext; +import net.minecraft.server.network.ServerPlayerEntity; + +@Mixin(BlockItem.class) +public class BlockItemMixin { + @Inject(method = "place(Lnet/minecraft/item/ItemPlacementContext;Lnet/minecraft/block/BlockState;)Z", at = @At("HEAD"), cancellable = true) + private void onPlace(ItemPlacementContext context, BlockState state, CallbackInfoReturnable cbi) { + if (!(context.getPlayer() instanceof ServerPlayerEntity)) return; + + var cancelled = !PlayerBlockPlaceEvent.EVENT.invoker().blockPlaced((ServerPlayerEntity)context.getPlayer(), context.getBlockPos(), state); + + if (cancelled) cbi.setReturnValue(false); + } +} diff --git a/src/java/me/topchetoeu/mcscript/mixin/ChatScreenMixin.java b/src/java/me/topchetoeu/mcscript/mixin/ChatScreenMixin.java index 1bfeba9..3fa32bb 100755 --- a/src/java/me/topchetoeu/mcscript/mixin/ChatScreenMixin.java +++ b/src/java/me/topchetoeu/mcscript/mixin/ChatScreenMixin.java @@ -23,7 +23,7 @@ public abstract class ChatScreenMixin { if (!args.cancelled) { chatText = args.message; - + if (chatText.startsWith("/")) { client.player.networkHandler.sendChatCommand(chatText.substring(1)); } else { diff --git a/src/java/me/topchetoeu/mcscript/mixin/KeyboardMixin.java b/src/java/me/topchetoeu/mcscript/mixin/KeyboardMixin.java index 1ef73ec..7c2cace 100755 --- a/src/java/me/topchetoeu/mcscript/mixin/KeyboardMixin.java +++ b/src/java/me/topchetoeu/mcscript/mixin/KeyboardMixin.java @@ -10,6 +10,7 @@ import net.minecraft.client.MinecraftClient; @Mixin(Keyboard.class) public class KeyboardMixin { + @Inject(method = "onChar", at = @At("HEAD")) private void onChar(long window, int codePoint, int modifiers, CallbackInfo cbi) { var client = MinecraftClient.getInstance(); diff --git a/src/java/me/topchetoeu/mcscript/mixin/ScreenHandlerMixin.java b/src/java/me/topchetoeu/mcscript/mixin/ScreenHandlerMixin.java new file mode 100755 index 0000000..e42a55d --- /dev/null +++ b/src/java/me/topchetoeu/mcscript/mixin/ScreenHandlerMixin.java @@ -0,0 +1,34 @@ +package me.topchetoeu.mcscript.mixin; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import me.topchetoeu.mcscript.events.ScreenHandlerEvents; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.screen.ScreenHandler; +import net.minecraft.screen.slot.SlotActionType; +import net.minecraft.server.network.ServerPlayerEntity; + +@Mixin(ScreenHandler.class) +public class ScreenHandlerMixin { + @Inject(method = "onSlotClick", at = @At("HEAD"), cancellable = true) + private void onSlotClick(int slotIndex, int button, SlotActionType actionType, PlayerEntity player, CallbackInfo cbi) { + if (!(player instanceof ServerPlayerEntity sp)) return; + + var handle = (ScreenHandler)(Object)this; + var cancelled = !ScreenHandlerEvents.SLOT_CLICKED.invoker().slotClicked(handle, slotIndex, button, actionType, sp); + + if (cancelled) cbi.cancel(); + } + @Inject(method = "onClosed", at = @At("HEAD"), cancellable = true) + private void onClosed(PlayerEntity player, CallbackInfo cbi) { + if (!(player instanceof ServerPlayerEntity sp)) return; + + var handle = (ScreenHandler)(Object)this; + var cancelled = !ScreenHandlerEvents.SCREEN_CLOSE.invoker().screenClosed(handle, sp); + + if (cancelled) cbi.cancel(); + } +}