diff --git a/README.md b/README.md index 6c7ce00..b42b097 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,5 @@ -# Sun Addon Template +# Sun LiveOverflow Addon -## Setup +Utilities for the LiveOverflow CTF server. -For setup instructions please see the [fabric wiki page](https://fabricmc.net/wiki/tutorial:setup) that relates to the IDE that you are using. - -## License - -This template is available under the CC0 license. Feel free to learn from it and incorporate it in your own projects. +Only works with [Sun Client](https://git.shrecked.dev/Shrecknt/sun). \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 8ad8f38..256b454 100644 --- a/gradle.properties +++ b/gradle.properties @@ -10,8 +10,8 @@ loader_version=0.16.14 # Mod Properties mod_version=1.0.0 -maven_group=com.example -archives_base_name=sun-addon-template +maven_group=dev.shrecked.ctf +archives_base_name=ctf # Dependencies sun_version=1.2.1 diff --git a/src/main/java/com/example/ExampleAddon.java b/src/main/java/com/example/ExampleAddon.java deleted file mode 100644 index 86ca423..0000000 --- a/src/main/java/com/example/ExampleAddon.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.example; - -import com.mojang.logging.LogUtils; -import dev.shrecked.sun.SunMod; -import dev.shrecked.sun.components.SunAddonInitializer; -import dev.shrecked.sun.components.command.CommandRegistry; -import dev.shrecked.sun.components.module.ModuleRegistry; -import org.slf4j.Logger; - -import java.lang.invoke.MethodHandles; - -public class ExampleAddon implements SunAddonInitializer { - public static final Logger LOGGER = LogUtils.getLogger(); - - @Override - public void onInitialize() { - SunMod.NORBIT.registerLambdaFactory("com.example", (lookupInMethod, klass) -> (MethodHandles.Lookup) lookupInMethod.invoke(null, klass, MethodHandles.lookup())); - LOGGER.info("Sun example addon initialized!"); - } - - @Override - public void loadModules() { - ModuleRegistry.reflectModulesFrom("com.example.modules"); - } - - @Override - public void loadCommands() { - CommandRegistry.reflectCommandsFrom("com.example.commands"); - } -} \ No newline at end of file diff --git a/src/main/java/com/example/commands/ExampleCommand.java b/src/main/java/com/example/commands/ExampleCommand.java deleted file mode 100644 index 442774d..0000000 --- a/src/main/java/com/example/commands/ExampleCommand.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.example.commands; - -import com.mojang.brigadier.arguments.StringArgumentType; -import com.mojang.brigadier.builder.LiteralArgumentBuilder; -import dev.shrecked.sun.components.annotation.SunCommand; -import dev.shrecked.sun.components.command.Command; -import dev.shrecked.sun.util.ChatUtil; -import net.minecraft.command.CommandSource; - -@SunCommand(name = "example-command", alias = {"ex"}) -public class ExampleCommand extends Command { - @Override - public void build(LiteralArgumentBuilder builder) { - builder.then(literal("echo") - .then(argument("text", StringArgumentType.greedyString()).executes(context -> { - String text = context.getArgument("text", String.class); - ChatUtil.info(String.format("echo! %s", text)); - return SINGLE_SUCCESS; - })) - ).build(); - } -} diff --git a/src/main/java/com/example/modules/ExampleModule.java b/src/main/java/com/example/modules/ExampleModule.java deleted file mode 100644 index 99b7f37..0000000 --- a/src/main/java/com/example/modules/ExampleModule.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.example.modules; - -import com.example.ExampleAddon; -import dev.shrecked.sun.components.annotation.SunModule; -import dev.shrecked.sun.components.event.Events; -import dev.shrecked.sun.components.module.Category; -import dev.shrecked.sun.components.module.Module; -import dev.shrecked.sun.components.setting.settings.StringSetting; -import meteordevelopment.orbit.EventHandler; -import net.minecraft.client.MinecraftClient; - -import java.awt.*; - -@SunModule(key = "example-module", category = Category.World.class) -public class ExampleModule extends Module { - private final StringSetting text = config.add("text", new StringSetting("Example text display!")); - - @Override - protected void onEnable() { - ExampleAddon.LOGGER.info("enabled example module!"); - } - - @Override - protected void onDisable() { - ExampleAddon.LOGGER.info("disabled example module!"); - } - - @EventHandler - private void onHudRender(Events.HudRender event) { - event.context().drawText( - MinecraftClient.getInstance().textRenderer, - this.text.getValue(), - 5, - 5, - Color.RED.getRGB(), - true - ); - } -} diff --git a/src/main/java/dev/shrecked/ctf/CtfAddon.java b/src/main/java/dev/shrecked/ctf/CtfAddon.java new file mode 100644 index 0000000..384c826 --- /dev/null +++ b/src/main/java/dev/shrecked/ctf/CtfAddon.java @@ -0,0 +1,40 @@ +package dev.shrecked.ctf; + +import com.mojang.logging.LogUtils; +import dev.shrecked.sun.SunMod; +import dev.shrecked.sun.components.SunAddonInitializer; +import dev.shrecked.sun.components.annotation.SunCategory; +import dev.shrecked.sun.components.command.CommandRegistry; +import dev.shrecked.sun.components.module.Category; +import dev.shrecked.sun.components.module.ModuleRegistry; +import org.slf4j.Logger; + +import java.lang.invoke.MethodHandles; + +public class CtfAddon implements SunAddonInitializer { + public static final Logger LOGGER = LogUtils.getLogger(); + + @Override + public void onInitialize() { + SunMod.NORBIT.registerLambdaFactory("dev.shrecked.ctf", (lookupInMethod, klass) -> (MethodHandles.Lookup) lookupInMethod.invoke(null, klass, MethodHandles.lookup())); + LOGGER.info("Sun example addon initialized!"); + } + + @Override + public void loadCategories() { + Category.add(CtfCategory.class); + } + + @Override + public void loadModules() { + ModuleRegistry.reflectModulesFrom("dev.shrecked.ctf.modules"); + } + + @Override + public void loadCommands() { + CommandRegistry.reflectCommandsFrom("dev.shrecked.ctf.commands"); + } + + @SunCategory(key = "ctf") + public static class CtfCategory extends Category {} +} \ No newline at end of file diff --git a/src/main/java/dev/shrecked/ctf/mixin/EventlessMoveMixin.java b/src/main/java/dev/shrecked/ctf/mixin/EventlessMoveMixin.java new file mode 100644 index 0000000..3910b01 --- /dev/null +++ b/src/main/java/dev/shrecked/ctf/mixin/EventlessMoveMixin.java @@ -0,0 +1,25 @@ +package dev.shrecked.ctf.mixin; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import dev.shrecked.ctf.modules.RoboWalk; +import dev.shrecked.sun.components.module.ModuleRegistry; +import dev.shrecked.sun.modules.EventlessMove; +import net.minecraft.client.network.ClientPlayNetworkHandler; +import net.minecraft.network.packet.Packet; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; + +@Mixin(EventlessMove.class) +public class EventlessMoveMixin { + /** + * FIXME: The position generated by {@code from.lerp(to, 0.499)} ({@code ordinal = 1}) is rounded, so this movement is too far and generates a movement event. + */ + @WrapOperation(method = "onPlayerMove", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/network/ClientPlayNetworkHandler;sendPacket(Lnet/minecraft/network/packet/Packet;)V", ordinal = 4)) + private void onPlayerMove(ClientPlayNetworkHandler instance, Packet packet, Operation original) { + RoboWalk roboWalk = ModuleRegistry.fromClass(RoboWalk.class); + if (roboWalk.isEnabled()) roboWalk.forceDisableHandler = true; + original.call(instance, packet); + if (roboWalk.isEnabled()) roboWalk.forceDisableHandler = false; + } +} diff --git a/src/main/java/dev/shrecked/ctf/modules/GameStateSpoofer.java b/src/main/java/dev/shrecked/ctf/modules/GameStateSpoofer.java new file mode 100644 index 0000000..070b253 --- /dev/null +++ b/src/main/java/dev/shrecked/ctf/modules/GameStateSpoofer.java @@ -0,0 +1,41 @@ +package dev.shrecked.ctf.modules; + +import dev.shrecked.ctf.CtfAddon; +import dev.shrecked.sun.components.annotation.SunModule; +import dev.shrecked.sun.components.event.Events; +import dev.shrecked.sun.components.module.Module; +import meteordevelopment.orbit.EventHandler; +import net.minecraft.network.packet.s2c.play.GameStateChangeS2CPacket; +import net.minecraft.network.packet.s2c.play.WorldBorderInitializeS2CPacket; +import net.minecraft.network.packet.s2c.play.WorldBorderSizeChangedS2CPacket; +import net.minecraft.world.border.WorldBorder; + +@SunModule(key = "game-state-spoofer", category = CtfAddon.CtfCategory.class) +public class GameStateSpoofer extends Module { + @EventHandler + private void onInboundPacket(Events.InboundPacket event) { + if (event.packet instanceof WorldBorderSizeChangedS2CPacket) { + event.packet = new WorldBorderSizeChangedS2CPacket(worldBorder()); + } + + if (event.packet instanceof WorldBorderInitializeS2CPacket) { + event.packet = new WorldBorderInitializeS2CPacket(worldBorder()); + } + + if (event.packet instanceof GameStateChangeS2CPacket packet) { + GameStateChangeS2CPacket.Reason reason = packet.getReason(); + if (reason == GameStateChangeS2CPacket.GAME_MODE_CHANGED + || reason == GameStateChangeS2CPacket.GAME_WON + || reason == GameStateChangeS2CPacket.DEMO_MESSAGE_SHOWN) { + event.cancel(); + } + } + } + + private static WorldBorder worldBorder() { + WorldBorder worldBorder = new WorldBorder(); + worldBorder.setCenter(0, 0); + worldBorder.setSize(Double.MAX_VALUE); + return worldBorder; + } +} diff --git a/src/main/java/dev/shrecked/ctf/modules/RoboWalk.java b/src/main/java/dev/shrecked/ctf/modules/RoboWalk.java new file mode 100644 index 0000000..415bee7 --- /dev/null +++ b/src/main/java/dev/shrecked/ctf/modules/RoboWalk.java @@ -0,0 +1,54 @@ +package dev.shrecked.ctf.modules; + +import dev.shrecked.ctf.CtfAddon; +import dev.shrecked.sun.components.annotation.SunModule; +import dev.shrecked.sun.components.event.Events; +import dev.shrecked.sun.components.module.Module; +import dev.shrecked.sun.util.ChatUtil; +import meteordevelopment.orbit.EventHandler; +import meteordevelopment.orbit.EventPriority; +import net.minecraft.network.packet.c2s.play.PlayerMoveC2SPacket; +import net.minecraft.network.packet.c2s.play.VehicleMoveC2SPacket; +import net.minecraft.util.math.Vec3d; + +@SunModule(key = "robo-walk", category = CtfAddon.CtfCategory.class) +public class RoboWalk extends Module { + public boolean forceDisableHandler = false; + + @EventHandler(priority = EventPriority.LOWEST) + private void onOutboundPacket(Events.OutboundPacket event) { + if (forceDisableHandler) return; + if (event.packet instanceof PlayerMoveC2SPacket packet && packet.changesPosition()) { + double newX = round(packet.getX(0f)); + double newY = packet.getY(0f); + double newZ = round(packet.getZ(0f)); + + PlayerMoveC2SPacket newPacket; + if (packet instanceof PlayerMoveC2SPacket.PositionAndOnGround) { + newPacket = new PlayerMoveC2SPacket.PositionAndOnGround(newX, newY, newZ, packet.isOnGround(), packet.horizontalCollision()); + } else if (packet instanceof PlayerMoveC2SPacket.Full) { + newPacket = new PlayerMoveC2SPacket.Full(newX, newY, newZ, packet.getYaw(0f), packet.getPitch(0f), packet.isOnGround(), packet.horizontalCollision()); + } else { + throw new RuntimeException("Unhandled movement packet type '" + packet.getClass().getSimpleName() + "'"); + } + event.packet = newPacket; + } + + if (event.packet instanceof VehicleMoveC2SPacket(Vec3d position, float yaw, float pitch, boolean onGround)) { + Vec3d fixedPosition = new Vec3d( + round(position.getX()), + position.getY(), + round(position.getZ()) + ); + event.packet = new VehicleMoveC2SPacket(fixedPosition, yaw, pitch, onGround); + } + } + + private static double round(double value) { + double rounded = (double) (Math.round(value * 100)) / 100; + double res = Math.nextAfter(rounded, rounded + Math.signum(value)); + long check = ((long) (res * 1000)) % 10; + if (check != 0) ChatUtil.error("Bad round " + value); + return res; + } +} diff --git a/src/main/resources/assets/sun-addon-template/icon.png b/src/main/resources/assets/ctf/icon.png similarity index 100% rename from src/main/resources/assets/sun-addon-template/icon.png rename to src/main/resources/assets/ctf/icon.png diff --git a/src/main/resources/assets/minecraft/langfix/en_us.json b/src/main/resources/assets/minecraft/langfix/en_us.json index 592d27e..f327e60 100644 --- a/src/main/resources/assets/minecraft/langfix/en_us.json +++ b/src/main/resources/assets/minecraft/langfix/en_us.json @@ -1,4 +1,5 @@ { - "sun.modules.example-module": "Example Module", - "sun.modules.example-module.setting.text": "Example Text Config Option" + "sun.category.ctf": "LiveOverflow CTF", + "sun.modules.game-state-spoofer": "Game State Spoof", + "sun.modules.robo-walk": "Robo-Walk" } \ No newline at end of file diff --git a/src/main/resources/ctf.mixins.json b/src/main/resources/ctf.mixins.json new file mode 100644 index 0000000..376bed6 --- /dev/null +++ b/src/main/resources/ctf.mixins.json @@ -0,0 +1,8 @@ +{ + "required": true, + "package": "dev.shrecked.ctf.mixin", + "compatibilityLevel": "JAVA_21", + "client": [ + "EventlessMoveMixin" + ] +} \ No newline at end of file diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index 15a4ca4..e724263 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -1,29 +1,32 @@ { "schemaVersion": 1, - "id": "sun-addon-template", + "id": "ctf", "version": "${version}", - "name": "Sun Addon Template", - "description": "A template addon for Sun client", + "name": "Sun LiveOverflow Addon", + "description": "Sun client addon for the LiveOverflow CTF server", "authors": [ - "Me!" + "Shrecknt" ], "contact": { "homepage": "https://shrecked.dev/", "sources": "https://git.ssi.fyi/Shrecknt/sun-addon-template" }, "license": "CC0-1.0", - "icon": "assets/sun-addon-template/icon.png", + "icon": "assets/ctf/icon.png", "environment": "*", "entrypoints": { "sun-addon": [ - "com.example.ExampleAddon" + "dev.shrecked.ctf.CtfAddon" ] }, + "mixins": [ + "ctf.mixins.json" + ], "depends": { "fabricloader": ">=0.15.0", "minecraft": ">=1.21.1", "java": ">=21", "fabric-api": "*", - "sun": ">=1.1.0" + "sun": ">=1.2.1" } } \ No newline at end of file