diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..37c5cf1
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2023 3arthqu4ke, alpha432, cattyn
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/README.md b/README.md
index 78e0c04..d3d9c92 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,14 @@
-# fros.cc
-A utility client for Minecraft 1.21.11.
\ No newline at end of file
+
+
+# oyvey-ported
+Kosher client base ported to modern Minecraft by [@cattyngmd](https://github.com/cattyngmd)
+
+
+
+# Why
+
+
+# What
+Add stuff to it like you would add stuff to your 1.12.2 OyVey paste
+
+
diff --git a/build.gradle.kts b/build.gradle.kts
new file mode 100644
index 0000000..17301c7
--- /dev/null
+++ b/build.gradle.kts
@@ -0,0 +1,111 @@
+import java.nio.file.Paths
+import java.text.SimpleDateFormat
+import java.util.Date
+
+plugins {
+ id("fabric-loom")
+ id("com.github.gmazzo.buildconfig")
+ java
+}
+
+val mod_version: String by project
+val maven_group: String by project
+val mod_name: String by project
+val archives_base_name: String by project
+val minecraft_version: String by project
+val loader_version: String by project
+val fabric_version: String by project
+val catformat_version: String by project
+
+val USING_GIT = Paths.get(rootDir.absolutePath).resolve(".git").toFile().exists()
+val GIT_HASH = execute("git rev-parse --short HEAD")
+
+version = mod_version
+group = maven_group
+
+buildConfig {
+ packageName("$group.util")
+ useJavaOutput()
+ generateAtSync.set(true)
+
+ val typeString = String::class.java
+ val typeBool = Boolean::class.java
+
+ val buildTime = SimpleDateFormat("MM/dd/yyyy HH:mm").format(Date())
+
+ buildConfigField(typeString, "NAME", mod_name)
+ buildConfigField(typeString, "GROUP", maven_group)
+ buildConfigField(typeString, "VERSION", mod_version)
+ buildConfigField(typeString, "BUILD_TIME", buildTime)
+ buildConfigField(typeBool, "USING_GIT", USING_GIT)
+ buildConfigField(typeString, "HASH", GIT_HASH)
+ buildConfigField(typeString, "BRANCH", execute("git branch --show-current"))
+}
+
+sourceSets.main {
+ java.srcDirs("build/gen/buildconfig/src/main")
+}
+
+base {
+ archivesName.set("$archives_base_name-$GIT_HASH")
+}
+
+repositories {
+ mavenCentral()
+ maven { url = uri("https://jitpack.io") }
+}
+
+loom {
+ accessWidenerPath.set(file("src/main/resources/oyvey.accesswidener"))
+}
+
+dependencies {
+ minecraft("com.mojang:minecraft:$minecraft_version")
+ mappings(loom.officialMojangMappings())
+ modImplementation("net.fabricmc:fabric-loader:$loader_version")
+ modImplementation("net.fabricmc.fabric-api:fabric-api:$fabric_version")
+
+ include(implementation("com.github.cattyngmd.catformat:catformat-core:$catformat_version")!!)
+ include(modImplementation("com.github.cattyngmd.catformat:catformat-fabric:$catformat_version")!!)
+}
+
+tasks.processResources {
+ inputs.property("version", version)
+ inputs.property("minecraft_version", minecraft_version)
+ inputs.property("loader_version", loader_version)
+ filteringCharset = "UTF-8"
+
+ filesMatching("fabric.mod.json") {
+ expand(
+ "version" to version,
+ "minecraft_version" to minecraft_version,
+ "loader_version" to loader_version
+ )
+ }
+}
+
+tasks.withType().configureEach {
+ options.release = 21
+ dependsOn("generateBuildConfig")
+}
+
+java {
+ withSourcesJar()
+ sourceCompatibility = JavaVersion.VERSION_21
+ targetCompatibility = JavaVersion.VERSION_21
+}
+
+tasks.named("sourcesJar") {
+ dependsOn("generateBuildConfig")
+}
+
+tasks.jar {
+ from("LICENSE") {
+ rename { "${it}_$archives_base_name" }
+ }
+}
+
+fun execute(cmd: String): String = providers.exec {
+ commandLine(cmd.split(" "))
+ isIgnoreExitValue = true
+}.standardOutput.asText.getOrElse("nogit").trim().replace("\n", "")
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 0000000..e7b8197
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,17 @@
+# Done to increase the memory available to gradle.
+org.gradle.jvmargs=-Xmx1G
+# Fabric Properties
+# check these on https://modmuss50.me/fabric.html
+minecraft_version=1.21.11
+loader_version=0.18.4
+loom_version=1.14-SNAPSHOT
+# Mod Properties
+maven_group=me.alpha432.oyvey
+mod_version=1.0.0
+mod_name=OyVey
+archives_base_name=oyvey
+# Dependencies
+# check this on https://modmuss50.me/fabric.html
+fabric_version=0.141.1+1.21.11
+catformat_version=2.1.0
+buildconfig_version=5.3.5
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..c1962a7
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..bad7c24
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,7 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.0-bin.zip
+networkTimeout=10000
+validateDistributionUrl=true
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/gradlew b/gradlew
new file mode 100644
index 0000000..6146088
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,245 @@
+#!/bin/sh
+
+#
+# Copyright © 2015-2021 the original authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+##############################################################################
+#
+# Gradle start up script for POSIX generated by Gradle.
+#
+# Important for running:
+#
+# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+# noncompliant, but you have some other compliant shell such as ksh or
+# bash, then to run this script, type that shell name before the whole
+# command line, like:
+#
+# ksh Gradle
+#
+# Busybox and similar reduced shells will NOT work, because this script
+# requires all of these POSIX shell features:
+# * functions;
+# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+# * compound commands having a testable exit status, especially «case»;
+# * various built-in commands including «command», «set», and «ulimit».
+#
+# Important for patching:
+#
+# (2) This script targets any POSIX shell, so it avoids extensions provided
+# by Bash, Ksh, etc; in particular arrays are avoided.
+#
+# The "traditional" practice of packing multiple parameters into a
+# space-separated string is a well documented source of bugs and security
+# problems, so this is (mostly) avoided, by progressively accumulating
+# options in "$@", and eventually passing that to Java.
+#
+# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+# see the in-line comments for details.
+#
+# There are tweaks for specific operating systems such as AIX, CygWin,
+# Darwin, MinGW, and NonStop.
+#
+# (3) This script is generated from the Groovy template
+# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# within the Gradle project.
+#
+# You can find Gradle at https://github.com/gradle/gradle/.
+#
+##############################################################################
+
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+ APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
+ [ -h "$app_path" ]
+do
+ ls=$( ls -ld "$app_path" )
+ link=${ls#*' -> '}
+ case $link in #(
+ /*) app_path=$link ;; #(
+ *) app_path=$APP_HOME$link ;;
+ esac
+done
+
+# This is normally unused
+# shellcheck disable=SC2034
+APP_BASE_NAME=${0##*/}
+APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD=maximum
+
+warn () {
+ echo "$*"
+} >&2
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+} >&2
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "$( uname )" in #(
+ CYGWIN* ) cygwin=true ;; #(
+ Darwin* ) darwin=true ;; #(
+ MSYS* | MINGW* ) msys=true ;; #(
+ NONSTOP* ) nonstop=true ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD=$JAVA_HOME/jre/sh/java
+ else
+ JAVACMD=$JAVA_HOME/bin/java
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD=java
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC3045
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC3045
+ ulimit -n "$MAX_FD" ||
+ warn "Could not set maximum file descriptor limit to $MAX_FD"
+ esac
+fi
+
+# Collect all arguments for the java command, stacking in reverse order:
+# * args from the command line
+# * the main class name
+# * -classpath
+# * -D...appname settings
+# * --module-path (only if needed)
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+ APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+ CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
+
+ JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ for arg do
+ if
+ case $arg in #(
+ -*) false ;; # don't mess with options #(
+ /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
+ [ -e "$t" ] ;; #(
+ *) false ;;
+ esac
+ then
+ arg=$( cygpath --path --ignore --mixed "$arg" )
+ fi
+ # Roll the args list around exactly as many times as the number of
+ # args, so each arg winds up back in the position where it started, but
+ # possibly modified.
+ #
+ # NB: a `for` loop captures its iteration list before it begins, so
+ # changing the positional parameters here affects neither the number of
+ # iterations, nor the values presented in `arg`.
+ shift # remove old arg
+ set -- "$@" "$arg" # push replacement arg
+ done
+fi
+
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Collect all arguments for the java command;
+# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
+# shell script including quotes and variable substitutions, so put them in
+# double quotes to make sure that they get re-expanded; and
+# * put everything else in single quotes, so that it's not re-expanded.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -classpath "$CLASSPATH" \
+ org.gradle.wrapper.GradleWrapperMain \
+ "$@"
+
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+ die "xargs is not available"
+fi
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+# set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+ printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+ xargs -n1 |
+ sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+ tr '\n' ' '
+ )" '"$@"'
+
+exec "$JAVACMD" "$@"
\ No newline at end of file
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000..2578005
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,92 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%"=="" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%"=="" set DIRNAME=.
+@rem This is normally unused
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if %ERRORLEVEL% equ 0 goto execute
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if %ERRORLEVEL% equ 0 goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+set EXIT_CODE=%ERRORLEVEL%
+if %EXIT_CODE% equ 0 set EXIT_CODE=1
+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
+exit /b %EXIT_CODE%
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
\ No newline at end of file
diff --git a/images/ui.png b/images/ui.png
new file mode 100644
index 0000000..a26d337
Binary files /dev/null and b/images/ui.png differ
diff --git a/settings.gradle.kts b/settings.gradle.kts
new file mode 100644
index 0000000..854d175
--- /dev/null
+++ b/settings.gradle.kts
@@ -0,0 +1,17 @@
+pluginManagement {
+ repositories {
+ maven {
+ name = "Fabric"
+ url = uri("https://maven.fabricmc.net/")
+ }
+ gradlePluginPortal()
+ }
+
+ plugins {
+ id("fabric-loom") version providers.gradleProperty("loom_version")
+ id("com.github.gmazzo.buildconfig") version providers.gradleProperty("buildconfig_version")
+ java
+ }
+}
+
+rootProject.name = "oyvey-ported"
\ No newline at end of file
diff --git a/src/main/java/me/alpha432/oyvey/OyVey.java b/src/main/java/me/alpha432/oyvey/OyVey.java
new file mode 100644
index 0000000..f9a1154
--- /dev/null
+++ b/src/main/java/me/alpha432/oyvey/OyVey.java
@@ -0,0 +1,66 @@
+package me.alpha432.oyvey;
+
+import me.alpha432.oyvey.manager.*;
+import me.alpha432.oyvey.util.BuildConfig;
+import me.alpha432.oyvey.util.TextUtil;
+import net.fabricmc.api.ClientModInitializer;
+import net.fabricmc.api.ModInitializer;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+public class OyVey implements ModInitializer, ClientModInitializer {
+ public static float TIMER = 1f;
+
+ public static final Logger LOGGER = LogManager.getLogger("OyVey");
+ public static ServerManager serverManager;
+ public static ColorManager colorManager;
+ public static RotationManager rotationManager;
+ public static PositionManager positionManager;
+ public static HoleManager holeManager;
+ public static EventManager eventManager;
+ public static SpeedManager speedManager;
+ public static CommandManager commandManager;
+ public static FriendManager friendManager;
+ public static ModuleManager moduleManager;
+ public static ConfigManager configManager;
+
+ @Override
+ public void onInitialize() {
+ LOGGER.info("Pre-initializing {} v{}",
+ BuildConfig.NAME, BuildConfig.VERSION);
+ configManager = new ConfigManager();
+ eventManager = new EventManager();
+ serverManager = new ServerManager();
+ rotationManager = new RotationManager();
+ positionManager = new PositionManager();
+ friendManager = new FriendManager();
+ colorManager = new ColorManager();
+ commandManager = new CommandManager();
+ moduleManager = new ModuleManager();
+ speedManager = new SpeedManager();
+ holeManager = new HoleManager();
+
+ TextUtil.init();
+ }
+
+ @Override
+ public void onInitializeClient() {
+ LOGGER.info("Initializing {}", BuildConfig.NAME);
+
+ long startTime = System.nanoTime();
+
+ eventManager.init();
+ commandManager.init();
+ moduleManager.init();
+ friendManager.init();
+
+ configManager.load();
+ colorManager.init();
+ Runtime.getRuntime().addShutdownHook(new Thread(() -> configManager.save()));
+
+ long endTime = System.nanoTime();
+
+ LOGGER.info("Initialized {} in {}ms",
+ BuildConfig.NAME, (endTime - startTime) / 1000000.0);
+ }
+}
diff --git a/src/main/java/me/alpha432/oyvey/ducks/render/IChatComponent.java b/src/main/java/me/alpha432/oyvey/ducks/render/IChatComponent.java
new file mode 100644
index 0000000..282475e
--- /dev/null
+++ b/src/main/java/me/alpha432/oyvey/ducks/render/IChatComponent.java
@@ -0,0 +1,9 @@
+package me.alpha432.oyvey.ducks.render;
+
+import net.minecraft.client.GuiMessage;
+import net.minecraft.network.chat.MessageSignature;
+
+public interface IChatComponent {
+ void oyvey$addMessage(GuiMessage message);
+ void oyvey$removeMessage(MessageSignature signature);
+}
\ No newline at end of file
diff --git a/src/main/java/me/alpha432/oyvey/event/Event.java b/src/main/java/me/alpha432/oyvey/event/Event.java
new file mode 100644
index 0000000..68fac19
--- /dev/null
+++ b/src/main/java/me/alpha432/oyvey/event/Event.java
@@ -0,0 +1,13 @@
+package me.alpha432.oyvey.event;
+
+public class Event {
+ private boolean cancelled;
+
+ public boolean isCancelled() {
+ return cancelled;
+ }
+
+ public void cancel() {
+ cancelled = true;
+ }
+}
diff --git a/src/main/java/me/alpha432/oyvey/event/Stage.java b/src/main/java/me/alpha432/oyvey/event/Stage.java
new file mode 100644
index 0000000..861daf8
--- /dev/null
+++ b/src/main/java/me/alpha432/oyvey/event/Stage.java
@@ -0,0 +1,6 @@
+package me.alpha432.oyvey.event;
+
+public enum Stage {
+ PRE,
+ POST
+}
diff --git a/src/main/java/me/alpha432/oyvey/event/impl/ClientEvent.java b/src/main/java/me/alpha432/oyvey/event/impl/ClientEvent.java
new file mode 100644
index 0000000..6722b27
--- /dev/null
+++ b/src/main/java/me/alpha432/oyvey/event/impl/ClientEvent.java
@@ -0,0 +1,38 @@
+package me.alpha432.oyvey.event.impl;
+
+import me.alpha432.oyvey.event.Event;
+import me.alpha432.oyvey.features.Feature;
+import me.alpha432.oyvey.features.settings.Setting;
+
+public class ClientEvent extends Event {
+ private final Type type;
+ private final Feature feature;
+ private Setting> setting;
+
+ public ClientEvent(Type type, Feature feature) {
+ this.type = type;
+ this.feature = feature;
+ }
+
+ public ClientEvent(Setting> setting) {
+ this(Type.SETTING_UPDATE, setting.getFeature());
+ this.setting = setting;
+ }
+
+ public Type getType() {
+ return type;
+ }
+
+ public Feature getFeature() {
+ return this.feature;
+ }
+
+ public Setting> getSetting() {
+ return this.setting;
+ }
+
+ public enum Type {
+ TOGGLE_MODULE,
+ SETTING_UPDATE
+ }
+}
diff --git a/src/main/java/me/alpha432/oyvey/event/impl/entity/DeathEvent.java b/src/main/java/me/alpha432/oyvey/event/impl/entity/DeathEvent.java
new file mode 100644
index 0000000..5bdb8d2
--- /dev/null
+++ b/src/main/java/me/alpha432/oyvey/event/impl/entity/DeathEvent.java
@@ -0,0 +1,16 @@
+package me.alpha432.oyvey.event.impl.entity;
+
+import me.alpha432.oyvey.event.Event;
+import net.minecraft.world.entity.LivingEntity;
+
+public class DeathEvent extends Event {
+ private final LivingEntity entity;
+
+ public DeathEvent(LivingEntity entity) {
+ this.entity = entity;
+ }
+
+ public LivingEntity getEntity() {
+ return entity;
+ }
+}
diff --git a/src/main/java/me/alpha432/oyvey/event/impl/entity/player/MoveEvent.java b/src/main/java/me/alpha432/oyvey/event/impl/entity/player/MoveEvent.java
new file mode 100644
index 0000000..48a9429
--- /dev/null
+++ b/src/main/java/me/alpha432/oyvey/event/impl/entity/player/MoveEvent.java
@@ -0,0 +1,31 @@
+package me.alpha432.oyvey.event.impl.entity.player;
+
+import me.alpha432.oyvey.event.Event;
+import net.minecraft.world.entity.MoverType;
+import net.minecraft.world.phys.Vec3;
+
+public class MoveEvent extends Event {
+ private final MoverType type;
+ private Vec3 movement;
+
+ public MoveEvent(MoverType type, Vec3 movement) {
+ this.type = type;
+ this.movement = movement;
+ }
+
+ public MoverType getType() {
+ return type;
+ }
+
+ public Vec3 getMovement() {
+ return movement;
+ }
+
+ public void setMovement(Vec3 movement) {
+ this.movement = movement;
+ }
+
+ public void setMovement(double x, double y, double z) {
+ this.movement = new Vec3(x, y, z);
+ }
+}
diff --git a/src/main/java/me/alpha432/oyvey/event/impl/entity/player/TickEvent.java b/src/main/java/me/alpha432/oyvey/event/impl/entity/player/TickEvent.java
new file mode 100644
index 0000000..4311f08
--- /dev/null
+++ b/src/main/java/me/alpha432/oyvey/event/impl/entity/player/TickEvent.java
@@ -0,0 +1,9 @@
+package me.alpha432.oyvey.event.impl.entity.player;
+
+import me.alpha432.oyvey.event.Event;
+
+public class TickEvent extends Event {
+ public static class Post extends TickEvent { }
+
+ public static class Pre extends TickEvent { }
+}
diff --git a/src/main/java/me/alpha432/oyvey/event/impl/entity/player/TravelEvent.java b/src/main/java/me/alpha432/oyvey/event/impl/entity/player/TravelEvent.java
new file mode 100644
index 0000000..557d293
--- /dev/null
+++ b/src/main/java/me/alpha432/oyvey/event/impl/entity/player/TravelEvent.java
@@ -0,0 +1,9 @@
+package me.alpha432.oyvey.event.impl.entity.player;
+
+import me.alpha432.oyvey.event.Event;
+
+public class TravelEvent extends Event {
+ public static class Post extends TravelEvent { }
+
+ public static class Pre extends TravelEvent { }
+}
diff --git a/src/main/java/me/alpha432/oyvey/event/impl/entity/player/UpdateWalkingPlayerEvent.java b/src/main/java/me/alpha432/oyvey/event/impl/entity/player/UpdateWalkingPlayerEvent.java
new file mode 100644
index 0000000..ba82bc8
--- /dev/null
+++ b/src/main/java/me/alpha432/oyvey/event/impl/entity/player/UpdateWalkingPlayerEvent.java
@@ -0,0 +1,16 @@
+package me.alpha432.oyvey.event.impl.entity.player;
+
+import me.alpha432.oyvey.event.Event;
+import me.alpha432.oyvey.event.Stage;
+
+public class UpdateWalkingPlayerEvent extends Event {
+ private final Stage stage;
+
+ public UpdateWalkingPlayerEvent(Stage stage) {
+ this.stage = stage;
+ }
+
+ public Stage getStage() {
+ return stage;
+ }
+}
diff --git a/src/main/java/me/alpha432/oyvey/event/impl/input/KeyInputEvent.java b/src/main/java/me/alpha432/oyvey/event/impl/input/KeyInputEvent.java
new file mode 100644
index 0000000..8bdee21
--- /dev/null
+++ b/src/main/java/me/alpha432/oyvey/event/impl/input/KeyInputEvent.java
@@ -0,0 +1,15 @@
+package me.alpha432.oyvey.event.impl.input;
+
+import me.alpha432.oyvey.event.Event;
+
+public class KeyInputEvent extends Event {
+ private final int key;
+
+ public KeyInputEvent(int key) {
+ this.key = key;
+ }
+
+ public int getKey() {
+ return key;
+ }
+}
diff --git a/src/main/java/me/alpha432/oyvey/event/impl/input/MouseInputEvent.java b/src/main/java/me/alpha432/oyvey/event/impl/input/MouseInputEvent.java
new file mode 100644
index 0000000..b17c7c4
--- /dev/null
+++ b/src/main/java/me/alpha432/oyvey/event/impl/input/MouseInputEvent.java
@@ -0,0 +1,22 @@
+package me.alpha432.oyvey.event.impl.input;
+
+import me.alpha432.oyvey.event.Event;
+
+public class MouseInputEvent extends Event {
+ private final int button;
+ private final int action;
+
+ public MouseInputEvent(int button, int action) {
+ this.button = button;
+ this.action = action;
+ }
+
+ public int getButton() {
+ return button;
+ }
+
+ public int getAction() {
+ return action;
+ }
+}
+
diff --git a/src/main/java/me/alpha432/oyvey/event/impl/network/ChatEvent.java b/src/main/java/me/alpha432/oyvey/event/impl/network/ChatEvent.java
new file mode 100644
index 0000000..65291a1
--- /dev/null
+++ b/src/main/java/me/alpha432/oyvey/event/impl/network/ChatEvent.java
@@ -0,0 +1,15 @@
+package me.alpha432.oyvey.event.impl.network;
+
+import me.alpha432.oyvey.event.Event;
+
+public class ChatEvent extends Event {
+ private final String content;
+
+ public ChatEvent(String content) {
+ this.content = content;
+ }
+
+ public String getMessage() {
+ return content;
+ }
+}
diff --git a/src/main/java/me/alpha432/oyvey/event/impl/network/PacketEvent.java b/src/main/java/me/alpha432/oyvey/event/impl/network/PacketEvent.java
new file mode 100644
index 0000000..8f9d7b5
--- /dev/null
+++ b/src/main/java/me/alpha432/oyvey/event/impl/network/PacketEvent.java
@@ -0,0 +1,30 @@
+package me.alpha432.oyvey.event.impl.network;
+
+import me.alpha432.oyvey.event.Event;
+import net.minecraft.network.protocol.Packet;
+
+public abstract class PacketEvent extends Event {
+
+ private final Packet> packet;
+
+ public PacketEvent(Packet> packet) {
+ this.packet = packet;
+ }
+
+ public Packet> getPacket() {
+ return packet;
+ }
+
+ public static class Receive extends PacketEvent {
+ public Receive(Packet> packet) {
+ super(packet);
+ }
+ }
+
+ public static class Send extends PacketEvent {
+ public Send(Packet> packet) {
+ super(packet);
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/me/alpha432/oyvey/event/impl/render/Render2DEvent.java b/src/main/java/me/alpha432/oyvey/event/impl/render/Render2DEvent.java
new file mode 100644
index 0000000..37da00a
--- /dev/null
+++ b/src/main/java/me/alpha432/oyvey/event/impl/render/Render2DEvent.java
@@ -0,0 +1,22 @@
+package me.alpha432.oyvey.event.impl.render;
+
+import me.alpha432.oyvey.event.Event;
+import net.minecraft.client.gui.GuiGraphics;
+
+public class Render2DEvent extends Event {
+ private final GuiGraphics context;
+ private final float delta;
+
+ public Render2DEvent(GuiGraphics context, float delta) {
+ this.context = context;
+ this.delta = delta;
+ }
+
+ public GuiGraphics getContext() {
+ return context;
+ }
+
+ public float getDelta() {
+ return delta;
+ }
+}
diff --git a/src/main/java/me/alpha432/oyvey/event/impl/render/Render3DEvent.java b/src/main/java/me/alpha432/oyvey/event/impl/render/Render3DEvent.java
new file mode 100644
index 0000000..adf8786
--- /dev/null
+++ b/src/main/java/me/alpha432/oyvey/event/impl/render/Render3DEvent.java
@@ -0,0 +1,22 @@
+package me.alpha432.oyvey.event.impl.render;
+
+import com.mojang.blaze3d.vertex.PoseStack;
+import me.alpha432.oyvey.event.Event;
+
+public class Render3DEvent extends Event {
+ private final PoseStack matrix;
+ private final float delta;
+
+ public Render3DEvent(PoseStack matrix, float delta) {
+ this.matrix = matrix;
+ this.delta = delta;
+ }
+
+ public PoseStack getMatrix() {
+ return matrix;
+ }
+
+ public float getDelta() {
+ return delta;
+ }
+}
diff --git a/src/main/java/me/alpha432/oyvey/event/impl/render/RenderBlockOutlineEvent.java b/src/main/java/me/alpha432/oyvey/event/impl/render/RenderBlockOutlineEvent.java
new file mode 100644
index 0000000..e259b64
--- /dev/null
+++ b/src/main/java/me/alpha432/oyvey/event/impl/render/RenderBlockOutlineEvent.java
@@ -0,0 +1,6 @@
+package me.alpha432.oyvey.event.impl.render;
+
+import me.alpha432.oyvey.event.Event;
+
+public class RenderBlockOutlineEvent extends Event {
+}
diff --git a/src/main/java/me/alpha432/oyvey/event/system/EventBus.java b/src/main/java/me/alpha432/oyvey/event/system/EventBus.java
new file mode 100644
index 0000000..baea470
--- /dev/null
+++ b/src/main/java/me/alpha432/oyvey/event/system/EventBus.java
@@ -0,0 +1,74 @@
+package me.alpha432.oyvey.event.system;
+
+import me.alpha432.oyvey.event.Event;
+
+import java.lang.reflect.Method;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+public class EventBus {
+ private final Map, CopyOnWriteArrayList> listeners = new ConcurrentHashMap<>();
+
+ public void register(Object host) {
+ register(host, host.getClass());
+ }
+
+ public void unregister(Object host) {
+ for (CopyOnWriteArrayList list : listeners.values()) {
+ list.removeIf(listener -> listener.getHost().equals(host));
+ }
+ }
+
+ public boolean post(Event event) {
+ List list = listeners.get(event.getClass());
+ if (list == null) return false;
+
+ for (Listener listener : list) {
+ listener.invoke(event);
+
+ if (event.isCancelled())
+ return true;
+ }
+
+ return false;
+ }
+
+ private void register(Object host, Class> klass) {
+ for (Method method : klass.getDeclaredMethods()) {
+ Subscribe subscribe = method.getAnnotation(Subscribe.class);
+ if (subscribe == null) continue;
+
+ Class>[] params = method.getParameterTypes();
+ if (params.length == 1) {
+ Class> eventType = params[0];
+
+ Listener listener = Listener.of(host, subscribe.priority(), method);
+ List registry = listeners.computeIfAbsent(
+ eventType,
+ k -> new CopyOnWriteArrayList<>()
+ );
+
+ register(registry, listener);
+ }
+ }
+
+ if (klass.getSuperclass() != null)
+ register(host, klass.getSuperclass());
+ }
+
+ private void register(List registry, Listener target) {
+ int i = 0;
+
+ for (Listener listener : registry) {
+ if (target.priority() > listener.priority()) {
+ break;
+ }
+
+ i++;
+ }
+
+ registry.add(i, target);
+ }
+}
diff --git a/src/main/java/me/alpha432/oyvey/event/system/Listener.java b/src/main/java/me/alpha432/oyvey/event/system/Listener.java
new file mode 100644
index 0000000..196e968
--- /dev/null
+++ b/src/main/java/me/alpha432/oyvey/event/system/Listener.java
@@ -0,0 +1,38 @@
+package me.alpha432.oyvey.event.system;
+
+import java.lang.invoke.*;
+import java.lang.reflect.Method;
+import java.util.function.Consumer;
+
+public record Listener(Object host, int priority, Consumer consumer) {
+ public static Listener of(Object host, int priority, Method method) {
+ return new Listener(host, priority, buildLambdaMetafactory(host, method));
+ }
+
+ public void invoke(Object event) {
+ consumer.accept(event);
+ }
+
+ public Object getHost() {
+ return host;
+ }
+
+ @SuppressWarnings("unchecked")
+ private static Consumer buildLambdaMetafactory(Object host, Method method) {
+ try {
+ MethodHandles.Lookup lookup = MethodHandles.privateLookupIn(method.getDeclaringClass(), MethodHandles.lookup());
+ CallSite site = LambdaMetafactory.metafactory(
+ lookup,
+ "accept",
+ MethodType.methodType(Consumer.class, method.getDeclaringClass()),
+ MethodType.methodType(void.class, Object.class),
+ lookup.unreflect(method),
+ MethodType.methodType(void.class, method.getParameterTypes()[0])
+ );
+ MethodHandle target = site.getTarget();
+ return (Consumer) (target.invoke(host));
+ } catch (Throwable e) {
+ throw new RuntimeException("Failed to build lambda from %s method".formatted(method.getName()), e);
+ }
+ }
+}
diff --git a/src/main/java/me/alpha432/oyvey/event/system/Subscribe.java b/src/main/java/me/alpha432/oyvey/event/system/Subscribe.java
new file mode 100644
index 0000000..241042d
--- /dev/null
+++ b/src/main/java/me/alpha432/oyvey/event/system/Subscribe.java
@@ -0,0 +1,13 @@
+package me.alpha432.oyvey.event.system;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface Subscribe {
+ int priority() default 0;
+}
+
diff --git a/src/main/java/me/alpha432/oyvey/features/Feature.java b/src/main/java/me/alpha432/oyvey/features/Feature.java
new file mode 100644
index 0000000..322443b
--- /dev/null
+++ b/src/main/java/me/alpha432/oyvey/features/Feature.java
@@ -0,0 +1,65 @@
+package me.alpha432.oyvey.features;
+
+import me.alpha432.oyvey.features.settings.Setting;
+import me.alpha432.oyvey.features.settings.SettingFactory;
+import me.alpha432.oyvey.util.traits.Util;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class Feature
+ implements Util, SettingFactory {
+ public List> settings = new ArrayList<>();
+ private String name;
+
+ public Feature() {
+ }
+
+ public Feature(String name) {
+ this.name = name;
+ }
+
+ public static boolean nullCheck() {
+ return Feature.mc.player == null || Feature.mc.level == null;
+ }
+
+ public String getName() {
+ return this.name;
+ }
+
+ public List> getSettings() {
+ return this.settings;
+ }
+
+ public boolean hasSettings() {
+ return !this.settings.isEmpty();
+ }
+
+ public boolean isEnabled() {
+ return false;
+ }
+
+ public boolean isDisabled() {
+ return !this.isEnabled();
+ }
+
+ public > T register(T setting) {
+ setting.setFeature(this);
+ this.settings.add(setting);
+ return setting;
+ }
+
+ public Setting> getSettingByName(String name) {
+ for (Setting> setting : this.settings) {
+ if (!setting.getName().equalsIgnoreCase(name)) continue;
+ return setting;
+ }
+ return null;
+ }
+
+ public void reset() {
+ for (Setting> setting : this.settings) {
+ setting.reset();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/me/alpha432/oyvey/features/commands/ArgumentSuggestions.java b/src/main/java/me/alpha432/oyvey/features/commands/ArgumentSuggestions.java
new file mode 100644
index 0000000..df39a4a
--- /dev/null
+++ b/src/main/java/me/alpha432/oyvey/features/commands/ArgumentSuggestions.java
@@ -0,0 +1,37 @@
+package me.alpha432.oyvey.features.commands;
+
+import com.mojang.brigadier.suggestion.Suggestions;
+import com.mojang.brigadier.suggestion.SuggestionsBuilder;
+
+import java.util.Locale;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.Function;
+import java.util.stream.Stream;
+
+public final class ArgumentSuggestions {
+ public static CompletableFuture suggest(Stream stream, SuggestionsBuilder builder) {
+ return suggest(stream.toList(), x -> x, builder);
+ }
+
+ public static CompletableFuture suggest(Iterable iterable, SuggestionsBuilder builder) {
+ return suggest(iterable, x -> x, builder);
+ }
+
+ public static CompletableFuture suggest(Iterable iterable, Function map, SuggestionsBuilder builder) {
+ String remaining = builder.getRemainingLowerCase();
+ for (T t : iterable) {
+ String target = map.apply(t).toLowerCase(Locale.ROOT);
+ if (target.equals(remaining)) {
+ builder.suggest(target);
+ return builder.buildFuture();
+ }
+ }
+
+ for (T t : iterable) {
+ String target = map.apply(t).toLowerCase(Locale.ROOT);
+ if (target.contains(remaining)) builder.suggest(target);
+ }
+
+ return builder.buildFuture();
+ }
+}
diff --git a/src/main/java/me/alpha432/oyvey/features/commands/Command.java b/src/main/java/me/alpha432/oyvey/features/commands/Command.java
new file mode 100644
index 0000000..c654be1
--- /dev/null
+++ b/src/main/java/me/alpha432/oyvey/features/commands/Command.java
@@ -0,0 +1,81 @@
+package me.alpha432.oyvey.features.commands;
+
+import com.mojang.brigadier.arguments.ArgumentType;
+import com.mojang.brigadier.builder.LiteralArgumentBuilder;
+import com.mojang.brigadier.builder.RequiredArgumentBuilder;
+import me.alpha432.oyvey.features.Feature;
+import me.alpha432.oyvey.manager.CommandManager;
+import me.alpha432.oyvey.util.TextUtil;
+import me.alpha432.oyvey.util.chat.ChatUtil;
+import me.alpha432.oyvey.util.chat.Signature;
+
+import static com.mojang.brigadier.Command.SINGLE_SUCCESS;
+import static me.alpha432.oyvey.features.commands.MessageSignatures.FAIL;
+import static me.alpha432.oyvey.features.commands.MessageSignatures.SUCCESS;
+
+public abstract class Command extends Feature {
+ public static final int NO_OP = -1;
+ public static final int SINGLE_FAILURE = 0;
+
+ private final String[] aliases;
+ private String description = "No description was provided for this command.";
+
+ public Command(String... aliases) {
+ super(aliases[0]);
+ this.aliases = aliases;
+ }
+
+ public abstract void createArgumentBuilder(LiteralArgumentBuilder builder);
+
+ protected int success(String message, Object... format) {
+ sendMessage(message, SUCCESS, format);
+ return NO_OP;
+ }
+
+ protected int success() {
+ return SINGLE_SUCCESS;
+ }
+
+ protected int fail(String message, Object... format) {
+ sendMessage("{red} " + message, FAIL, format);
+ return NO_OP;
+ }
+
+ protected int fail() {
+ return SINGLE_FAILURE;
+ }
+
+ public String[] getAliases() {
+ return aliases;
+ }
+
+ protected void setDescription(String description) {
+ this.description = description;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public boolean isShown() {
+ return true;
+ }
+
+ public static void sendMessage(String message, Signature identifier) {
+ if (message == null) return;
+ ChatUtil.sendMessage(TextUtil.text(message), identifier);
+ }
+
+ public static void sendMessage(String message, Signature identifier, Object... format) {
+ if (message == null) return;
+ ChatUtil.sendMessage(TextUtil.text(message, format), identifier);
+ }
+
+ public static LiteralArgumentBuilder literal(String literal) {
+ return LiteralArgumentBuilder.literal(literal);
+ }
+
+ public static RequiredArgumentBuilder argument(String name, ArgumentType type) {
+ return RequiredArgumentBuilder.argument(name, type);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/me/alpha432/oyvey/features/commands/CommandExceptions.java b/src/main/java/me/alpha432/oyvey/features/commands/CommandExceptions.java
new file mode 100644
index 0000000..f728abc
--- /dev/null
+++ b/src/main/java/me/alpha432/oyvey/features/commands/CommandExceptions.java
@@ -0,0 +1,10 @@
+package me.alpha432.oyvey.features.commands;
+
+import com.mojang.brigadier.LiteralMessage;
+import com.mojang.brigadier.exceptions.SimpleCommandExceptionType;
+
+public class CommandExceptions {
+ public static SimpleCommandExceptionType invalidArgument(String message, Object... format) {
+ return new SimpleCommandExceptionType(new LiteralMessage(String.format(message, format)));
+ }
+}
diff --git a/src/main/java/me/alpha432/oyvey/features/commands/MessageSignatures.java b/src/main/java/me/alpha432/oyvey/features/commands/MessageSignatures.java
new file mode 100644
index 0000000..ca8782a
--- /dev/null
+++ b/src/main/java/me/alpha432/oyvey/features/commands/MessageSignatures.java
@@ -0,0 +1,10 @@
+package me.alpha432.oyvey.features.commands;
+
+import me.alpha432.oyvey.util.chat.Signature;
+import me.alpha432.oyvey.util.chat.SimpleSignature;
+
+public class MessageSignatures {
+ public static final Signature GENERAL = SimpleSignature.fromLong(-1L);
+ public static final Signature SUCCESS = SimpleSignature.fromLong(0L);
+ public static final Signature FAIL = SimpleSignature.fromLong(1L);
+}
\ No newline at end of file
diff --git a/src/main/java/me/alpha432/oyvey/features/commands/ModuleCommand.java b/src/main/java/me/alpha432/oyvey/features/commands/ModuleCommand.java
new file mode 100644
index 0000000..1960918
--- /dev/null
+++ b/src/main/java/me/alpha432/oyvey/features/commands/ModuleCommand.java
@@ -0,0 +1,160 @@
+package me.alpha432.oyvey.features.commands;
+
+import com.mojang.brigadier.arguments.BoolArgumentType;
+import com.mojang.brigadier.builder.LiteralArgumentBuilder;
+import me.alpha432.oyvey.OyVey;
+import me.alpha432.oyvey.features.commands.argument.ColorArgumentType;
+import me.alpha432.oyvey.features.modules.Module;
+import me.alpha432.oyvey.features.settings.Setting;
+import me.alpha432.oyvey.manager.CommandManager;
+import me.alpha432.oyvey.util.chat.ChatUtil;
+import net.minecraft.ChatFormatting;
+import net.minecraft.network.chat.ClickEvent;
+import net.minecraft.network.chat.Component;
+import net.minecraft.network.chat.Style;
+
+import java.awt.*;
+
+import static com.mojang.brigadier.arguments.BoolArgumentType.getBool;
+import static com.mojang.brigadier.arguments.StringArgumentType.getString;
+import static com.mojang.brigadier.arguments.StringArgumentType.greedyString;
+import static me.alpha432.oyvey.features.commands.argument.ColorArgumentType.getColor;
+import static me.alpha432.oyvey.features.commands.argument.EnumArgumentType._enum;
+import static me.alpha432.oyvey.features.commands.argument.EnumArgumentType.getEnum;
+import static me.alpha432.oyvey.features.commands.argument.NumberArgumentType.*;
+
+public class ModuleCommand extends Command {
+ private final Module module;
+
+ public ModuleCommand(Module module) {
+ super(module.getName().toLowerCase());
+ setDescription("Command line configuration implementation for \"" + module.getName() + "\"");
+ this.module = module;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public void createArgumentBuilder(LiteralArgumentBuilder builder) {
+ registerDefault(builder);
+ for (Setting> setting : module.getSettings()) {
+ Class> type = setting.getDefaultValue().getClass();
+
+ if (Boolean.class.isAssignableFrom(type)) {
+ registerBooleanArgument(builder, (Setting) setting);
+ } else if (Number.class.isAssignableFrom(type)) {
+ registerNumberArgument(builder, (Setting extends Number>) setting);
+ } else if (Enum.class.isAssignableFrom(type)) {
+ registerEnumArgument(builder, (Setting>) setting);
+ } else if (String.class.isAssignableFrom(type)) {
+ registerStringArgument(builder, (Setting) setting);
+ } else if (Color.class.isAssignableFrom(type)) {
+ registerColorArgument(builder, (Setting) setting);
+ }
+ }
+ }
+
+ private void registerDefault(LiteralArgumentBuilder builder) {
+ builder.executes(ctx -> {
+ var message = Component.empty();
+
+ message.append("[ ");
+ message.append(Component.literal(module.getName())
+ .withStyle(s -> s.withColor(module.isToggled() ? ChatFormatting.GREEN : ChatFormatting.RED))
+ .withStyle(s -> s.withClickEvent(suggestCommand("toggle " + module.getName())))
+ );
+ message.append(" ]");
+
+ for (Setting> setting : module.getSettings()) {
+ message.append("\n");
+ message.append(Component.literal(setting.getName() + ": ").withStyle(ChatFormatting.GRAY));
+ message.append(Component.literal(setting.getValueAsString()).withStyle(s -> styledValue(setting, s)));
+ }
+
+ ChatUtil.sendMessage(message, MessageSignatures.SUCCESS);
+
+ return success();
+ });
+ }
+
+ private void registerColorArgument(LiteralArgumentBuilder builder,
+ Setting setting) {
+ builder.then(literal(setting.getName().toLowerCase())
+ .then(argument("value", ColorArgumentType.color())
+ .executes((ctx) -> {
+ setting.setValue(getColor(ctx, "value"));
+ Color value = setting.getValue();
+ return success("Set %s.%s to RGB(%s, %s, %s)",
+ module.getName(),
+ setting.getName(),
+ value.getRed(),
+ value.getGreen(),
+ value.getBlue());
+ })));
+ }
+
+ private void registerStringArgument(LiteralArgumentBuilder builder,
+ Setting setting) {
+ builder.then(literal(setting.getName().toLowerCase())
+ .then(argument("value", greedyString())
+ .executes((ctx) -> {
+ setting.setValue(getString(ctx, "value"));
+ return settingChangeReturn(setting);
+ })));
+ }
+
+ @SuppressWarnings("unchecked")
+ private void registerEnumArgument(LiteralArgumentBuilder builder,
+ Setting> setting) {
+ Class> type = (Class>) setting.getDefaultValue().getClass();
+ builder.then(literal(setting.getName().toLowerCase())
+ .then(argument("value", _enum(type))
+ .executes((ctx) -> {
+ setting.setValue(getEnum(ctx, "value"));
+ return settingChangeReturn(setting);
+ })));
+ }
+
+ @SuppressWarnings("unchecked")
+ private void registerNumberArgument(LiteralArgumentBuilder builder,
+ Setting setting) {
+ Class type = (Class) setting.getDefaultValue().getClass();
+ builder.then(literal(setting.getName().toLowerCase())
+ .then(argument("value", number(type, minMax(setting.getMin(), setting.getMax())))
+ .executes((ctx) -> {
+ setting.setValue(get(type, ctx, "value"));
+ return settingChangeReturn(setting);
+ })));
+ }
+
+ private void registerBooleanArgument(LiteralArgumentBuilder builder,
+ Setting setting) {
+ builder.then(literal(setting.getName().toLowerCase())
+ .then(argument("value", BoolArgumentType.bool())
+ .executes((ctx) -> {
+ setting.setValue(getBool(ctx, "value"));
+ return settingChangeReturn(setting);
+ })));
+ }
+
+ private int settingChangeReturn(final Setting> setting) {
+ return success("Set %s.%s to %s", module.getName(), setting.getName(), setting.getValue());
+ }
+
+ private static ClickEvent suggestCommand(String command) {
+ return new ClickEvent.SuggestCommand(OyVey.commandManager.getCommandPrefix() + command);
+ }
+
+ private static Style styledValue(Setting> setting, Style style) {
+ return switch (setting.getValue()) {
+ case Boolean value -> style.withColor(value ? ChatFormatting.GREEN : ChatFormatting.RED);
+ case Color value -> style.withColor(value.getRGB());
+ case Enum> ignored -> style.withColor(ChatFormatting.YELLOW);
+ default -> style.withColor(ChatFormatting.WHITE);
+ };
+ }
+
+ @Override
+ public boolean isShown() {
+ return false;
+ }
+}
diff --git a/src/main/java/me/alpha432/oyvey/features/commands/argument/ColorArgumentType.java b/src/main/java/me/alpha432/oyvey/features/commands/argument/ColorArgumentType.java
new file mode 100644
index 0000000..e4ebe92
--- /dev/null
+++ b/src/main/java/me/alpha432/oyvey/features/commands/argument/ColorArgumentType.java
@@ -0,0 +1,148 @@
+package me.alpha432.oyvey.features.commands.argument;
+
+import com.mojang.brigadier.StringReader;
+import com.mojang.brigadier.arguments.ArgumentType;
+import com.mojang.brigadier.context.CommandContext;
+import com.mojang.brigadier.exceptions.CommandSyntaxException;
+import com.mojang.brigadier.suggestion.Suggestions;
+import com.mojang.brigadier.suggestion.SuggestionsBuilder;
+import me.alpha432.oyvey.features.commands.CommandExceptions;
+import net.minecraft.ChatFormatting;
+
+import java.awt.*;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+
+import static me.alpha432.oyvey.features.commands.ArgumentSuggestions.suggest;
+
+public final class ColorArgumentType implements ArgumentType {
+ private static final List EXAMPLES = List.of("hsb:55,100,100", "255,0,255", "0,255,255,80");
+ private static final String[] RGBA_VALUES = {"Red", "Green", "Blue", "Alpha"};
+
+ @Override
+ public Color parse(StringReader reader) throws CommandSyntaxException {
+ String value = reader.getRemaining();
+ reader.setCursor(reader.getTotalLength());
+
+ // if input begins with "#" its a hex color
+ if (value.startsWith("#")) {
+ int color;
+ if (value.length() == 4) {
+ char[] c = value.toCharArray();
+ String hex = "" + c[1] + c[1] + c[2] + c[2] + c[3] + c[3];
+ color = Integer.parseInt(hex, 16);
+ } else {
+ color = Integer.parseInt(value.substring(1), 16);
+ }
+
+ return new Color(color);
+ }
+
+ if (value.startsWith("hsb:")) {
+ int[] elements = parseColorElements(reader, value.substring(4));
+
+ int hue = elements[0];
+ if (hue < 0 || hue > 360) {
+ throw CommandExceptions.invalidArgument("Hue value must be between 0-360")
+ .createWithContext(reader);
+ }
+
+ int saturation = elements[1];
+ if (saturation < 0 || saturation > 100) {
+ throw CommandExceptions.invalidArgument("Saturation value must be between 0-100")
+ .createWithContext(reader);
+ }
+
+ int brightness = elements[2];
+ if (brightness < 0 || brightness > 100) {
+ throw CommandExceptions.invalidArgument("Brightness value must be between 0-100")
+ .createWithContext(reader);
+ }
+
+ return Color.getHSBColor((float) hue / 360.0f,
+ (float) saturation / 100.0f,
+ (float) brightness / 100.0f);
+ }
+
+ ChatFormatting format = parseFormatting(value);
+ if (format != null) {
+ return new Color(format.getColor(), false);
+ }
+
+ int[] elements = parseColorElements(reader, value);
+ for (int i = 0; i < elements.length; ++i) {
+ int element = elements[i];
+ if (element < 0 || element > 255) {
+ throw CommandExceptions.invalidArgument("%s value must be between 0-255", RGBA_VALUES[i])
+ .createWithContext(reader);
+ }
+ }
+
+ return new Color(elements[0],
+ elements[1],
+ elements[2],
+ elements.length == 4 ? elements[3] : 255);
+ }
+
+ @Override
+ public CompletableFuture listSuggestions(CommandContext context, SuggestionsBuilder builder) {
+ String input = builder.getRemainingLowerCase();
+ if (input.isEmpty()) {
+ builder.suggest("#");
+ builder.suggest("hsb:");
+ return builder.buildFuture();
+ }
+
+ if (input.startsWith("#")) {
+ builder.suggest(input);
+ } else if (input.startsWith("h")) {
+ if (input.startsWith("hsb:")) builder.suggest(input);
+ else builder.suggest("hsb:");
+ } else if (!Character.isDigit(input.charAt(0))){
+ return suggest(Arrays.stream(ChatFormatting.values())
+ .filter(ChatFormatting::isColor)
+ .map(ChatFormatting::getName), builder);
+ }
+
+ return builder.buildFuture();
+ }
+
+ private int[] parseColorElements(StringReader reader, String value) throws CommandSyntaxException {
+ String[] parts = value.split(",");
+ if (parts.length < 3) {
+ throw CommandExceptions.invalidArgument("Color element must have three values (with optional alpha value), either RGB(A) or hsb:HSB")
+ .createWithContext(reader);
+ }
+
+ int[] elements = new int[parts.length];
+ for (int i = 0; i < parts.length; ++i) {
+ elements[i] = Integer.parseInt(parts[i]);
+ }
+ return elements;
+ }
+
+ private ChatFormatting parseFormatting(String value) {
+ for (ChatFormatting format : ChatFormatting.values()) {
+ if (!format.isColor()) continue;
+ if (value.equalsIgnoreCase(format.getName())) {
+ return format;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public Collection getExamples() {
+ return EXAMPLES;
+ }
+
+ public static Color getColor(CommandContext> ctx, String name) {
+ return ctx.getArgument(name, Color.class);
+ }
+
+ public static ColorArgumentType color() {
+ return new ColorArgumentType();
+ }
+}
diff --git a/src/main/java/me/alpha432/oyvey/features/commands/argument/CommandArgumentType.java b/src/main/java/me/alpha432/oyvey/features/commands/argument/CommandArgumentType.java
new file mode 100644
index 0000000..bb358e8
--- /dev/null
+++ b/src/main/java/me/alpha432/oyvey/features/commands/argument/CommandArgumentType.java
@@ -0,0 +1,44 @@
+package me.alpha432.oyvey.features.commands.argument;
+
+import com.mojang.brigadier.StringReader;
+import com.mojang.brigadier.arguments.ArgumentType;
+import com.mojang.brigadier.context.CommandContext;
+import com.mojang.brigadier.exceptions.CommandExceptionType;
+import com.mojang.brigadier.exceptions.CommandSyntaxException;
+import com.mojang.brigadier.suggestion.Suggestions;
+import com.mojang.brigadier.suggestion.SuggestionsBuilder;
+import me.alpha432.oyvey.OyVey;
+import me.alpha432.oyvey.features.commands.Command;
+import me.alpha432.oyvey.features.commands.CommandExceptions;
+
+import java.util.concurrent.CompletableFuture;
+
+import static me.alpha432.oyvey.features.commands.ArgumentSuggestions.suggest;
+
+public final class CommandArgumentType implements ArgumentType, CommandExceptionType {
+ @Override
+ public Command parse(StringReader reader) throws CommandSyntaxException {
+ String value = reader.readString().toLowerCase();
+
+ for (String alias : OyVey.commandManager.getCommandAliases()) {
+ if (value.equalsIgnoreCase(alias)) {
+ return OyVey.commandManager.getCommand(alias);
+ }
+ }
+
+ throw CommandExceptions.invalidArgument("Invalid command").createWithContext(reader);
+ }
+
+ @Override
+ public CompletableFuture listSuggestions(CommandContext context, SuggestionsBuilder builder) {
+ return suggest(OyVey.commandManager.getCommandAliases(), builder);
+ }
+
+ public static Command getCommand(CommandContext> ctx, String name) {
+ return ctx.getArgument(name, Command.class);
+ }
+
+ public static CommandArgumentType command() {
+ return new CommandArgumentType();
+ }
+}
diff --git a/src/main/java/me/alpha432/oyvey/features/commands/argument/EnumArgumentType.java b/src/main/java/me/alpha432/oyvey/features/commands/argument/EnumArgumentType.java
new file mode 100644
index 0000000..663a167
--- /dev/null
+++ b/src/main/java/me/alpha432/oyvey/features/commands/argument/EnumArgumentType.java
@@ -0,0 +1,62 @@
+package me.alpha432.oyvey.features.commands.argument;
+
+import com.mojang.brigadier.StringReader;
+import com.mojang.brigadier.arguments.ArgumentType;
+import com.mojang.brigadier.context.CommandContext;
+import com.mojang.brigadier.exceptions.CommandSyntaxException;
+import com.mojang.brigadier.suggestion.Suggestions;
+import com.mojang.brigadier.suggestion.SuggestionsBuilder;
+import me.alpha432.oyvey.features.commands.CommandExceptions;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+
+import static me.alpha432.oyvey.features.commands.ArgumentSuggestions.suggest;
+
+public final class EnumArgumentType> implements ArgumentType {
+ private final Map enumNameMap = new HashMap<>();
+ private final T defaultValue;
+
+ public EnumArgumentType(Class clazz, T defaultValue) {
+ this.defaultValue = defaultValue;
+
+ for (T constant : clazz.getEnumConstants()) {
+ enumNameMap.put(constant.toString(), constant);
+ }
+ }
+
+ @Override
+ public T parse(StringReader reader) throws CommandSyntaxException {
+ String value = reader.readString();
+ for (String name : enumNameMap.keySet()) {
+ if (value.equalsIgnoreCase(name)) {
+ return enumNameMap.get(name);
+ }
+ }
+
+ if (defaultValue != null) {
+ return defaultValue;
+ }
+
+ throw CommandExceptions.invalidArgument("Invalid enum type").createWithContext(reader);
+ }
+
+ @Override
+ public CompletableFuture listSuggestions(CommandContext context, SuggestionsBuilder builder) {
+ return suggest(enumNameMap.keySet(), builder);
+ }
+
+ @SuppressWarnings("unchecked")
+ public static > T getEnum(CommandContext> ctx, String name) {
+ return (T) ctx.getArgument(name, Object.class);
+ }
+
+ public static > EnumArgumentType _enum(Class clazz) {
+ return _enum(clazz, null);
+ }
+
+ public static > EnumArgumentType _enum(Class clazz, T defaultValue) {
+ return new EnumArgumentType<>(clazz, defaultValue);
+ }
+}
diff --git a/src/main/java/me/alpha432/oyvey/features/commands/argument/GreedyArgumentType.java b/src/main/java/me/alpha432/oyvey/features/commands/argument/GreedyArgumentType.java
new file mode 100644
index 0000000..b0a4366
--- /dev/null
+++ b/src/main/java/me/alpha432/oyvey/features/commands/argument/GreedyArgumentType.java
@@ -0,0 +1,40 @@
+package me.alpha432.oyvey.features.commands.argument;
+
+import com.mojang.brigadier.StringReader;
+import com.mojang.brigadier.arguments.ArgumentType;
+import com.mojang.brigadier.context.CommandContext;
+import com.mojang.brigadier.exceptions.CommandSyntaxException;
+import com.mojang.brigadier.suggestion.Suggestions;
+import com.mojang.brigadier.suggestion.SuggestionsBuilder;
+
+import java.lang.reflect.Array;
+import java.util.concurrent.CompletableFuture;
+
+public record GreedyArgumentType(ArgumentType parent, Class type) implements ArgumentType {
+ @Override
+ @SuppressWarnings("unchecked")
+ public T[] parse(StringReader reader) throws CommandSyntaxException {
+ String input = reader.getRemaining();
+ reader.setCursor(reader.getTotalLength());
+ String[] split = input.split(" ");
+ T[] values = (T[]) Array.newInstance(type, split.length);
+ for (int i = 0; i < split.length; i++) {
+ values[i] = parent.parse(new StringReader(split[i]));
+ }
+ return values;
+ }
+
+ @Override public CompletableFuture listSuggestions(CommandContext context, SuggestionsBuilder builder) {
+ String[] s = builder.getRemaining().split(" ", -1);
+ if (s.length == 0) {
+ return parent.listSuggestions(context, builder);
+ }
+
+ int st = builder.getStart() + builder.getRemaining().length() - s[s.length - 1].length();
+ return parent.listSuggestions(context, new SuggestionsBuilder(builder.getInput(), st));
+ }
+
+ public static GreedyArgumentType greedy(ArgumentType parent, Class type) {
+ return new GreedyArgumentType<>(parent, type);
+ }
+}
diff --git a/src/main/java/me/alpha432/oyvey/features/commands/argument/KeybindArgumentType.java b/src/main/java/me/alpha432/oyvey/features/commands/argument/KeybindArgumentType.java
new file mode 100644
index 0000000..96f41e4
--- /dev/null
+++ b/src/main/java/me/alpha432/oyvey/features/commands/argument/KeybindArgumentType.java
@@ -0,0 +1,45 @@
+package me.alpha432.oyvey.features.commands.argument;
+
+import com.mojang.brigadier.StringReader;
+import com.mojang.brigadier.arguments.ArgumentType;
+import com.mojang.brigadier.context.CommandContext;
+import com.mojang.brigadier.exceptions.CommandSyntaxException;
+import com.mojang.brigadier.suggestion.Suggestions;
+import com.mojang.brigadier.suggestion.SuggestionsBuilder;
+import me.alpha432.oyvey.features.commands.CommandExceptions;
+import me.alpha432.oyvey.features.settings.Bind;
+import me.alpha432.oyvey.util.KeyboardUtil;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+
+import static me.alpha432.oyvey.features.commands.ArgumentSuggestions.suggest;
+
+public final class KeybindArgumentType implements ArgumentType {
+ private static final List EXAMPLES = List.of("A", "RIGHT_SHIFT", "MOUSE0");
+
+ @Override
+ public Bind parse(StringReader reader) throws CommandSyntaxException {
+ String input = reader.readString();
+ Bind bind = KeyboardUtil.getBind(input);
+ if (bind == null) {
+ throw CommandExceptions.invalidArgument("Invalid key name").createWithContext(reader);
+ }
+ return bind;
+ }
+
+ @Override
+ public CompletableFuture listSuggestions(CommandContext context, SuggestionsBuilder builder) {
+ return suggest(KeyboardUtil.iterateKeys(), builder);
+ }
+
+ @Override
+ public Collection getExamples() {
+ return EXAMPLES;
+ }
+
+ public static KeybindArgumentType keybind() {
+ return new KeybindArgumentType();
+ }
+}
diff --git a/src/main/java/me/alpha432/oyvey/features/commands/argument/ModuleArgumentType.java b/src/main/java/me/alpha432/oyvey/features/commands/argument/ModuleArgumentType.java
new file mode 100644
index 0000000..08bffec
--- /dev/null
+++ b/src/main/java/me/alpha432/oyvey/features/commands/argument/ModuleArgumentType.java
@@ -0,0 +1,57 @@
+package me.alpha432.oyvey.features.commands.argument;
+
+import com.mojang.brigadier.StringReader;
+import com.mojang.brigadier.arguments.ArgumentType;
+import com.mojang.brigadier.context.CommandContext;
+import com.mojang.brigadier.exceptions.CommandSyntaxException;
+import com.mojang.brigadier.suggestion.Suggestions;
+import com.mojang.brigadier.suggestion.SuggestionsBuilder;
+import me.alpha432.oyvey.OyVey;
+import me.alpha432.oyvey.features.Feature;
+import me.alpha432.oyvey.features.commands.CommandExceptions;
+import me.alpha432.oyvey.features.modules.Module;
+
+import java.util.Locale;
+import java.util.concurrent.CompletableFuture;
+
+import static me.alpha432.oyvey.features.commands.ArgumentSuggestions.suggest;
+
+public record ModuleArgumentType(boolean fullName) implements ArgumentType {
+ @Override
+ public Module parse(StringReader reader) throws CommandSyntaxException {
+ String value = reader.readString().toLowerCase(Locale.ROOT);
+
+ for (Module module : OyVey.moduleManager.getModules()) {
+ if (value.equalsIgnoreCase(module.getName())) {
+ return module;
+ }
+ }
+
+ if (!fullName) {
+ for (Module module : OyVey.moduleManager.getModules()) {
+ if (module.getName().toLowerCase(Locale.ROOT).startsWith(value)) {
+ return module;
+ }
+ }
+ }
+
+ throw CommandExceptions.invalidArgument("Invalid module").createWithContext(reader);
+ }
+
+ @Override
+ public CompletableFuture listSuggestions(CommandContext context, SuggestionsBuilder builder) {
+ return suggest(OyVey.moduleManager.getModules(), Feature::getName, builder);
+ }
+
+ public static ModuleArgumentType module() {
+ return module(false);
+ }
+
+ public static ModuleArgumentType module(boolean fullName) {
+ return new ModuleArgumentType(fullName);
+ }
+
+ public static Module getModule(CommandContext> ctx, String name) {
+ return ctx.getArgument(name, Module.class);
+ }
+}
diff --git a/src/main/java/me/alpha432/oyvey/features/commands/argument/NumberArgumentType.java b/src/main/java/me/alpha432/oyvey/features/commands/argument/NumberArgumentType.java
new file mode 100644
index 0000000..01c7ea8
--- /dev/null
+++ b/src/main/java/me/alpha432/oyvey/features/commands/argument/NumberArgumentType.java
@@ -0,0 +1,70 @@
+package me.alpha432.oyvey.features.commands.argument;
+
+import com.mojang.brigadier.StringReader;
+import com.mojang.brigadier.arguments.ArgumentType;
+import com.mojang.brigadier.context.CommandContext;
+import com.mojang.brigadier.exceptions.CommandSyntaxException;
+import me.alpha432.oyvey.features.commands.CommandExceptions;
+
+public record NumberArgumentType(Class type, Constraint constraint) implements ArgumentType {
+ @SuppressWarnings("unchecked")
+ @Override
+ public T parse(StringReader reader) throws CommandSyntaxException {
+ String input = reader.readString();
+
+ T value = null;
+ try {
+ if (type.isAssignableFrom(Integer.class)) {
+ value = (T) Integer.valueOf(input);
+ } else if (type.isAssignableFrom(Double.class)) {
+ value = (T) Double.valueOf(input);
+ } else if (type.isAssignableFrom(Float.class)) {
+ value = (T) Float.valueOf(input);
+ } else if (type.isAssignableFrom(Long.class)) {
+ value = (T) Long.valueOf(input);
+ }
+ } catch (NumberFormatException ignored) {
+ }
+
+ if (value == null) {
+ throw CommandExceptions.invalidArgument("Could not parse \"%s\"", input)
+ .createWithContext(reader);
+ }
+
+ if (constraint != null) {
+ constraint.validate(reader, value);
+ }
+
+ return value;
+ }
+
+ public static Constraint minMax(T min, T max) {
+ return (reader, input) -> {
+ double value = input.doubleValue();
+ if (min.doubleValue() > value) {
+ throw CommandExceptions.invalidArgument("Value is less than minimum(%s)", min.toString())
+ .createWithContext(reader);
+ }
+ if (max.doubleValue() < value) {
+ throw CommandExceptions.invalidArgument("Value is more than maximum(%s)", max.toString())
+ .createWithContext(reader);
+ }
+ };
+ }
+
+ public static T get(Class type, CommandContext> ctx, String name) {
+ return ctx.getArgument(name, type);
+ }
+
+ public static NumberArgumentType number(Class type) {
+ return number(type, null);
+ }
+
+ public static NumberArgumentType number(Class type, Constraint constraint) {
+ return new NumberArgumentType<>(type, constraint);
+ }
+
+ public interface Constraint {
+ void validate(StringReader reader, T input) throws CommandSyntaxException;
+ }
+}
diff --git a/src/main/java/me/alpha432/oyvey/features/commands/impl/BindCommand.java b/src/main/java/me/alpha432/oyvey/features/commands/impl/BindCommand.java
new file mode 100644
index 0000000..8294664
--- /dev/null
+++ b/src/main/java/me/alpha432/oyvey/features/commands/impl/BindCommand.java
@@ -0,0 +1,64 @@
+package me.alpha432.oyvey.features.commands.impl;
+
+import com.mojang.brigadier.builder.LiteralArgumentBuilder;
+import me.alpha432.oyvey.event.impl.input.KeyInputEvent;
+import me.alpha432.oyvey.event.system.Subscribe;
+import me.alpha432.oyvey.features.commands.Command;
+import me.alpha432.oyvey.features.modules.Module;
+import me.alpha432.oyvey.features.settings.Bind;
+import me.alpha432.oyvey.manager.CommandManager;
+import me.alpha432.oyvey.util.KeyboardUtil;
+
+import static me.alpha432.oyvey.features.commands.argument.KeybindArgumentType.keybind;
+import static me.alpha432.oyvey.features.commands.argument.ModuleArgumentType.module;
+import static org.lwjgl.glfw.GLFW.GLFW_KEY_ESCAPE;
+import static org.lwjgl.glfw.GLFW.GLFW_KEY_UNKNOWN;
+
+public class BindCommand extends Command {
+ private Module module;
+
+ public BindCommand() {
+ super("bind", "setbind");
+ setDescription("Sets a key bind for a module");
+ EVENT_BUS.register(this);
+ }
+
+ @Override
+ public void createArgumentBuilder(LiteralArgumentBuilder builder) {
+ builder.then(argument("module", module(true)).then(
+ argument("keybind", keybind()).executes(ctx -> {
+ Module module = ctx.getArgument("module", Module.class);
+ Bind bind = ctx.getArgument("keybind", Bind.class);
+ module.bind.setValue(bind);
+ return success("Bind for {green} %s {} set to {green} %s",
+ module.getName(),
+ KeyboardUtil.getKeyName(bind)
+ );
+ })
+ ).executes((ctx) -> {
+ module = ctx.getArgument("module", Module.class);
+ return success("Press any key...");
+ }));
+ }
+
+ @Subscribe
+ public void onKey(final KeyInputEvent event) {
+ if (nullCheck() || module == null || event.getKey() == GLFW_KEY_UNKNOWN) {
+ return;
+ }
+
+ if (event.getKey() == GLFW_KEY_ESCAPE) {
+ module = null;
+ fail("Operation canceled.");
+ return;
+ }
+
+ module.bind.setValue(Bind.keyboard(event.getKey()));
+ success("Bind for {green} %s {} set to {green} %s",
+ module.getName(),
+ KeyboardUtil.getKeyName(module.getBind())
+ );
+
+ module = null;
+ }
+}
diff --git a/src/main/java/me/alpha432/oyvey/features/commands/impl/DrawnCommand.java b/src/main/java/me/alpha432/oyvey/features/commands/impl/DrawnCommand.java
new file mode 100644
index 0000000..93ae5cd
--- /dev/null
+++ b/src/main/java/me/alpha432/oyvey/features/commands/impl/DrawnCommand.java
@@ -0,0 +1,30 @@
+package me.alpha432.oyvey.features.commands.impl;
+
+import com.mojang.brigadier.builder.LiteralArgumentBuilder;
+import me.alpha432.oyvey.features.commands.Command;
+import me.alpha432.oyvey.features.modules.Module;
+import me.alpha432.oyvey.manager.CommandManager;
+
+import static me.alpha432.oyvey.features.commands.argument.ModuleArgumentType.getModule;
+import static me.alpha432.oyvey.features.commands.argument.ModuleArgumentType.module;
+
+public class DrawnCommand extends Command {
+ public DrawnCommand() {
+ super("drawn");
+ setDescription("Sets a module to be drawn to the arraylist or not");
+ }
+
+ @Override
+ public void createArgumentBuilder(LiteralArgumentBuilder builder) {
+ builder.then(argument("module", module())
+ .executes((ctx) -> {
+ Module module = getModule(ctx, "module");
+ module.setDrawn(!module.isDrawn());
+ boolean drawn = module.isDrawn();
+ return success("{gray} %s {reset} is now %s %s",
+ module.getDisplayName(),
+ drawn ? "{green}" : "{red}",
+ drawn ? "drawn" : "hidden");
+ }));
+ }
+}
diff --git a/src/main/java/me/alpha432/oyvey/features/commands/impl/FriendCommand.java b/src/main/java/me/alpha432/oyvey/features/commands/impl/FriendCommand.java
new file mode 100644
index 0000000..3f95668
--- /dev/null
+++ b/src/main/java/me/alpha432/oyvey/features/commands/impl/FriendCommand.java
@@ -0,0 +1,58 @@
+package me.alpha432.oyvey.features.commands.impl;
+
+import com.mojang.brigadier.builder.LiteralArgumentBuilder;
+import me.alpha432.oyvey.OyVey;
+import me.alpha432.oyvey.features.commands.Command;
+import me.alpha432.oyvey.manager.CommandManager;
+
+import java.util.List;
+import java.util.StringJoiner;
+
+import static com.mojang.brigadier.arguments.StringArgumentType.getString;
+import static com.mojang.brigadier.arguments.StringArgumentType.word;
+
+public class FriendCommand extends Command {
+ public FriendCommand() {
+ super("friend", "friends", "f");
+ setDescription("Manages your friends list");
+ }
+
+ @Override
+ public void createArgumentBuilder(LiteralArgumentBuilder builder) {
+ builder.then(literal("list")
+ .executes((ctx) -> {
+ List friends = OyVey.friendManager.getFriends();
+ if (friends.isEmpty()) {
+ return success("You have no friends :(");
+ }
+ StringJoiner joiner = new StringJoiner(",");
+ friends.forEach(joiner::add);
+ return success("Friends (%s): %s", friends.size(), joiner);
+ }))
+ .then(literal("clear")
+ .executes((ctx) -> {
+ OyVey.friendManager.getFriends().clear();
+ return success("Cleared friends list");
+ }))
+ .then(literal("add")
+ .then(argument("username", word())
+ .executes((ctx) -> {
+ String username = getString(ctx, "username");
+ if (OyVey.friendManager.isFriend(username)) {
+ return success("{green} %s {reset} is already on your friends list.", username);
+ }
+ OyVey.friendManager.addFriend(username);
+ return success("Added {green} %s {reset} to your friends list", username);
+ })))
+ .then(literal("remove")
+ .then(argument("username", word())
+ .executes((ctx) -> {
+ String username = getString(ctx, "username");
+ if (!OyVey.friendManager.isFriend(username)) {
+ return success("{green} %s {reset} is not on your friends list.", username);
+ }
+ OyVey.friendManager.removeFriend(username);
+ return success("Removed {green} %s {reset} from your friends list", username);
+ })));
+ }
+}
diff --git a/src/main/java/me/alpha432/oyvey/features/commands/impl/HelpCommand.java b/src/main/java/me/alpha432/oyvey/features/commands/impl/HelpCommand.java
new file mode 100644
index 0000000..3ddc5ab
--- /dev/null
+++ b/src/main/java/me/alpha432/oyvey/features/commands/impl/HelpCommand.java
@@ -0,0 +1,118 @@
+package me.alpha432.oyvey.features.commands.impl;
+
+import com.google.common.collect.Iterables;
+import com.mojang.brigadier.ParseResults;
+import com.mojang.brigadier.builder.LiteralArgumentBuilder;
+import com.mojang.brigadier.context.CommandContext;
+import com.mojang.brigadier.tree.CommandNode;
+import me.alpha432.oyvey.OyVey;
+import me.alpha432.oyvey.features.commands.Command;
+import me.alpha432.oyvey.manager.CommandManager;
+import me.alpha432.oyvey.util.chat.ChatUtil;
+import net.minecraft.network.chat.ClickEvent;
+import net.minecraft.network.chat.Component;
+import net.minecraft.network.chat.ComponentUtils;
+import net.minecraft.network.chat.MutableComponent;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import static me.alpha432.oyvey.features.commands.MessageSignatures.GENERAL;
+import static me.alpha432.oyvey.features.commands.argument.CommandArgumentType.command;
+import static me.alpha432.oyvey.features.commands.argument.CommandArgumentType.getCommand;
+import static me.alpha432.oyvey.features.commands.argument.NumberArgumentType.get;
+import static me.alpha432.oyvey.features.commands.argument.NumberArgumentType.number;
+import static me.alpha432.oyvey.util.TextUtil.text;
+import static me.alpha432.oyvey.util.chat.ChatUtil.prefix;
+import static me.alpha432.oyvey.util.chat.ChatUtil.withPrefix;
+
+public class HelpCommand extends Command {
+ private static final int ITEMS_PER_PAGE = 5;
+
+ public HelpCommand() {
+ super("help", "commands", "h", "cmds");
+ setDescription("Displays all executable commands and additional information");
+ }
+
+ @Override
+ public void createArgumentBuilder(LiteralArgumentBuilder builder) {
+ builder.then(argument("page", number(Integer.class))
+ .executes((ctx) -> helpCommands(ctx, get(Integer.class, ctx, "page"))))
+ .then(argument("command_name", command())
+ .executes(this::helpSpecific))
+ .executes((ctx) -> helpCommands(ctx, 1));
+ }
+
+ private int helpSpecific(CommandContext ctx) {
+ Command command = getCommand(ctx, "command_name");
+
+ MutableComponent text = Component.empty();
+ text.append(withPrefix("Usages for %s:\n", command.getName()));
+
+ ParseResults results = ctx.getSource().getDispatcher()
+ .parse(command.getName(), ctx.getSource());
+
+ if (results.getContext().getNodes().isEmpty()) {
+ text.append(withPrefix("No usages available"));
+ } else {
+ Map, String> smartUsages = ctx.getSource().getDispatcher().getSmartUsage(
+ Iterables.getLast(results.getContext().getNodes()).getNode(), ctx.getSource());
+ List usages = new ArrayList<>();
+
+ for (String usage : smartUsages.values()) {
+ usages.add(text(" {gray}%s%s %s", ctx.getSource().getCommandPrefix(), command.getName(), usage));
+ }
+
+ text.append(ComponentUtils.formatList(usages, Component.literal("\n")));
+ }
+
+ ChatUtil.sendClientSideMessage(text, GENERAL);
+ return success();
+ }
+
+ private int helpCommands(CommandContext ctx, int page) {
+ List commands = ctx.getSource().getCommands()
+ .stream().filter(Command::isShown).toList();
+
+ int pages = Math.max(1, (int) Math.ceil((double) commands.size() / ITEMS_PER_PAGE));
+ int pageIndex = page < 1 || page > pages ? 1 : page;
+
+ List paginated = commands.subList((pageIndex - 1) * ITEMS_PER_PAGE,
+ Math.min(commands.size(), pageIndex * ITEMS_PER_PAGE));
+
+ MutableComponent text = Component.empty();
+ text.append(withPrefix("Commands (%s): \n", commands.size()));
+
+ for (Command command : paginated) {
+ String aliases = String.join(", ", command.getAliases());
+ String description = command.getDescription();
+ text.append(withPrefix("{dark_gray} %s: {reset}\n %s\n", aliases, description));
+ }
+
+ text.append(buildPages(pageIndex, pages));
+
+ ChatUtil.sendClientSideMessage(text, GENERAL);
+ return success();
+ }
+
+ private static Component buildPages(int index, int pages) {
+ String command = OyVey.commandManager.getCommandPrefix() + "help ";
+ MutableComponent text = prefix();
+ boolean hasPages = pages > 1;
+
+ if (hasPages && index != 1)
+ text.append(suggest(Component.literal(" <"), command + (index - 1)));
+
+ text.append(text(" Page %d/%d", index, pages));
+
+ if (hasPages && index != pages)
+ text.append(suggest(Component.literal(" > "), command + (index + 1)));
+
+ return text;
+ }
+
+ private static Component suggest(MutableComponent component, String command) {
+ return component.withStyle(s -> s.withClickEvent(new ClickEvent.SuggestCommand(command.formatted())));
+ }
+}
diff --git a/src/main/java/me/alpha432/oyvey/features/commands/impl/PrefixCommand.java b/src/main/java/me/alpha432/oyvey/features/commands/impl/PrefixCommand.java
new file mode 100644
index 0000000..bc2ed9f
--- /dev/null
+++ b/src/main/java/me/alpha432/oyvey/features/commands/impl/PrefixCommand.java
@@ -0,0 +1,28 @@
+package me.alpha432.oyvey.features.commands.impl;
+
+import com.mojang.brigadier.builder.LiteralArgumentBuilder;
+import me.alpha432.oyvey.features.commands.Command;
+import me.alpha432.oyvey.manager.CommandManager;
+
+import static com.mojang.brigadier.arguments.StringArgumentType.getString;
+import static com.mojang.brigadier.arguments.StringArgumentType.greedyString;
+
+public class PrefixCommand extends Command {
+ public PrefixCommand() {
+ super("prefix", "setprefix");
+ setDescription("Sets the command prefix");
+ }
+
+ @Override
+ public void createArgumentBuilder(LiteralArgumentBuilder builder) {
+ builder.then(argument("prefix", greedyString())
+ .executes((ctx) -> {
+ String prefix = getString(ctx, "prefix").trim();
+ if (prefix.isBlank()) {
+ return fail("Prefix must contain at least one character");
+ }
+ ctx.getSource().setCommandPrefix(prefix);
+ return success("Prefix changed to {green} %s", prefix);
+ }));
+ }
+}
diff --git a/src/main/java/me/alpha432/oyvey/features/commands/impl/ToggleCommand.java b/src/main/java/me/alpha432/oyvey/features/commands/impl/ToggleCommand.java
new file mode 100644
index 0000000..67493c5
--- /dev/null
+++ b/src/main/java/me/alpha432/oyvey/features/commands/impl/ToggleCommand.java
@@ -0,0 +1,30 @@
+package me.alpha432.oyvey.features.commands.impl;
+
+import com.mojang.brigadier.builder.LiteralArgumentBuilder;
+import me.alpha432.oyvey.features.commands.Command;
+import me.alpha432.oyvey.features.modules.Module;
+import me.alpha432.oyvey.manager.CommandManager;
+
+import static me.alpha432.oyvey.features.commands.argument.ModuleArgumentType.getModule;
+import static me.alpha432.oyvey.features.commands.argument.ModuleArgumentType.module;
+
+public class ToggleCommand extends Command {
+ public ToggleCommand() {
+ super("toggle", "t");
+ setDescription("Toggles a module");
+ }
+
+ @Override
+ public void createArgumentBuilder(LiteralArgumentBuilder builder) {
+ builder.then(argument("module", module(true))
+ .executes((ctx) -> {
+ Module module = getModule(ctx, "module");
+ module.toggle();
+ boolean toggled = module.isEnabled();
+ return success("{gray} %s {reset} is now %s %s",
+ module.getDisplayName(),
+ toggled ? "{green}" : "{red}",
+ toggled ? "enabled" : "disabled");
+ }));
+ }
+}
diff --git a/src/main/java/me/alpha432/oyvey/features/gui/HudEditorScreen.java b/src/main/java/me/alpha432/oyvey/features/gui/HudEditorScreen.java
new file mode 100644
index 0000000..b1ceff6
--- /dev/null
+++ b/src/main/java/me/alpha432/oyvey/features/gui/HudEditorScreen.java
@@ -0,0 +1,100 @@
+package me.alpha432.oyvey.features.gui;
+
+import me.alpha432.oyvey.OyVey;
+import me.alpha432.oyvey.features.Feature;
+import me.alpha432.oyvey.features.gui.items.buttons.ModuleButton;
+import me.alpha432.oyvey.features.modules.Module;
+import me.alpha432.oyvey.features.modules.client.HudModule;
+import net.minecraft.client.gui.GuiGraphics;
+import net.minecraft.client.gui.screens.Screen;
+import net.minecraft.client.input.CharacterEvent;
+import net.minecraft.client.input.KeyEvent;
+import net.minecraft.client.input.MouseButtonEvent;
+import net.minecraft.network.chat.Component;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+
+public class HudEditorScreen extends Screen {
+ private static HudEditorScreen INSTANCE;
+
+ private final ArrayList components = new ArrayList<>();
+ public HudModule currentDragging;
+ public boolean anyHover;
+
+ private HudEditorScreen() {
+ super(Component.literal("oyvey-hudeditor"));
+ load();
+ }
+
+ private void load() {
+ Widget hud = new Widget("Hud", 50, 50, true);
+ OyVey.moduleManager.stream()
+ .filter(m -> m.getCategory() == Module.Category.HUD && !m.hidden)
+ .map(ModuleButton::new)
+ .forEach(hud::addButton);
+ this.components.add(hud);
+ this.components.forEach(component -> component.getItems().sort(Comparator.comparing(Feature::getName)));
+ }
+
+ @Override
+ public void render(GuiGraphics context, int mouseX, int mouseY, float delta) {
+ anyHover = false;
+ this.components.forEach(component -> component.drawScreen(context, mouseX, mouseY, delta));
+ }
+
+ @Override
+ public boolean mouseClicked(MouseButtonEvent click, boolean doubled) {
+ this.components.forEach(component -> component.mouseClicked((int) click.x(), (int) click.y(), click.button()));
+ return super.mouseClicked(click, doubled);
+ }
+
+ @Override
+ public boolean mouseReleased(MouseButtonEvent click) {
+ this.components.forEach(component -> component.mouseReleased((int) click.x(), (int) click.y(), click.button()));
+ return super.mouseReleased(click);
+ }
+
+ @Override
+ public boolean mouseScrolled(double mouseX, double mouseY, double horizontalAmount, double verticalAmount) {
+ if (verticalAmount < 0) {
+ this.components.forEach(component -> component.setY(component.getY() - 10));
+ } else if (verticalAmount > 0) {
+ this.components.forEach(component -> component.setY(component.getY() + 10));
+ }
+ return super.mouseScrolled(mouseX, mouseY, horizontalAmount, verticalAmount);
+ }
+
+ @Override
+ public boolean keyPressed(KeyEvent input) {
+ this.components.forEach(component -> component.onKeyPressed(input.input()));
+ return super.keyPressed(input);
+ }
+
+ @Override
+ public boolean charTyped(CharacterEvent input) {
+ this.components.forEach(component -> component.onKeyTyped(input.codepointAsString(), input.modifiers()));
+ return super.charTyped(input);
+ }
+
+ @Override
+ public boolean isPauseScreen() {
+ return false;
+ }
+
+ @Override // ignore 1.21.8 menu blur thing
+ public void renderBackground(GuiGraphics context, int mouseX, int mouseY, float delta) {
+ }
+
+ public ArrayList getComponents() {
+ return components;
+ }
+
+ public static HudEditorScreen getInstance() {
+ if (INSTANCE == null) {
+ INSTANCE = new HudEditorScreen();
+ }
+ return INSTANCE;
+ }
+}
+
diff --git a/src/main/java/me/alpha432/oyvey/features/gui/OyVeyGui.java b/src/main/java/me/alpha432/oyvey/features/gui/OyVeyGui.java
new file mode 100644
index 0000000..6b3ebb9
--- /dev/null
+++ b/src/main/java/me/alpha432/oyvey/features/gui/OyVeyGui.java
@@ -0,0 +1,147 @@
+package me.alpha432.oyvey.features.gui;
+
+import me.alpha432.oyvey.OyVey;
+import me.alpha432.oyvey.features.Feature;
+import me.alpha432.oyvey.features.gui.items.buttons.ModuleButton;
+import me.alpha432.oyvey.features.modules.Module;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.gui.GuiGraphics;
+import net.minecraft.client.gui.screens.Screen;
+import net.minecraft.client.input.CharacterEvent;
+import net.minecraft.client.input.KeyEvent;
+import net.minecraft.client.input.MouseButtonEvent;
+import net.minecraft.network.chat.Component;
+
+import java.awt.*;
+import java.util.ArrayList;
+import java.util.Comparator;
+
+public class OyVeyGui extends Screen {
+ private static OyVeyGui INSTANCE;
+ private static Color colorClipboard = null;
+
+ static {
+ INSTANCE = new OyVeyGui();
+ }
+
+ private Screen lastScreen;
+ private final ArrayList widgets = new ArrayList<>();
+
+ public OyVeyGui() {
+ super(Component.literal("OyVey"));
+ setInstance();
+ load();
+ }
+
+ public static OyVeyGui getInstance() {
+ if (INSTANCE == null) {
+ INSTANCE = new OyVeyGui();
+ }
+ return INSTANCE;
+ }
+
+ public static OyVeyGui getClickGui() {
+ return OyVeyGui.getInstance();
+ }
+
+ public static void open(Screen previous) {
+ OyVeyGui gui = getInstance();
+ gui.lastScreen = previous;
+ Minecraft.getInstance().setScreen(gui);
+ }
+
+ @Override
+ public void onClose() {
+ Screen previous = this.lastScreen;
+ this.lastScreen = null;
+ this.minecraft.setScreen(previous);
+ }
+
+ private void setInstance() {
+ INSTANCE = this;
+ }
+
+ private void load() {
+ int x = -84;
+ for (Module.Category category : OyVey.moduleManager.getCategories()) {
+ if (category == Module.Category.HUD) continue;
+ Widget panel = new Widget(category.getName(), x += 90, 4, true);
+ OyVey.moduleManager.stream()
+ .filter(m -> m.getCategory() == category && !m.hidden)
+ .map(ModuleButton::new)
+ .forEach(panel::addButton);
+ this.widgets.add(panel);
+ }
+ this.widgets.forEach(components -> components.getItems().sort(Comparator.comparing(Feature::getName)));
+ }
+
+ @Override
+ public void render(GuiGraphics context, int mouseX, int mouseY, float delta) {
+ if (lastScreen != null) {
+ lastScreen.renderBackground(context, Integer.MIN_VALUE, Integer.MIN_VALUE, delta);
+ lastScreen.render(context, Integer.MIN_VALUE, Integer.MIN_VALUE, delta);
+ context.nextStratum();
+ }
+
+ context.fill(0, 0, context.guiWidth(), context.guiHeight(), new Color(0, 0, 0, 120).hashCode());
+ this.widgets.forEach(components -> components.drawScreen(context, mouseX, mouseY, delta));
+ }
+
+ @Override
+ public boolean mouseClicked(MouseButtonEvent click, boolean doubled) {
+ this.widgets.forEach(components -> components.mouseClicked((int) click.x(), (int) click.y(), click.button()));
+ return super.mouseClicked(click, doubled);
+ }
+
+ @Override
+ public boolean mouseReleased(MouseButtonEvent click) {
+ this.widgets.forEach(components -> components.mouseReleased((int) click.x(), (int) click.y(), click.button()));
+ return super.mouseReleased(click);
+ }
+
+ @Override
+ public boolean mouseScrolled(double mouseX, double mouseY, double horizontalAmount, double verticalAmount) {
+ if (verticalAmount < 0) {
+ this.widgets.forEach(component -> component.setY(component.getY() - 10));
+ } else if (verticalAmount > 0) {
+ this.widgets.forEach(component -> component.setY(component.getY() + 10));
+ }
+ return super.mouseScrolled(mouseX, mouseY, horizontalAmount, verticalAmount);
+ }
+
+ @Override
+ public boolean keyPressed(KeyEvent input) {
+ this.widgets.forEach(component -> component.onKeyPressed(input.input()));
+ return super.keyPressed(input);
+ }
+
+ @Override
+ public boolean charTyped(CharacterEvent input) {
+ this.widgets.forEach(component -> component.onKeyTyped(input.codepointAsString(), input.modifiers()));
+ return super.charTyped(input);
+ }
+
+ @Override
+ public boolean isPauseScreen() {
+ return false;
+ }
+ @Override
+ public void renderBackground(GuiGraphics context, int mouseX, int mouseY, float delta) {
+ }//ignore 1.21.8 blur thing
+
+ public final ArrayList getComponents() {
+ return this.widgets;
+ }
+
+ public int getTextOffset() {
+ return -6;
+ }
+
+ public static Color getColorClipboard() {
+ return colorClipboard;
+ }
+
+ public static void setColorClipboard(Color color) {
+ colorClipboard = color;
+ }
+}
diff --git a/src/main/java/me/alpha432/oyvey/features/gui/Widget.java b/src/main/java/me/alpha432/oyvey/features/gui/Widget.java
new file mode 100644
index 0000000..d321f1b
--- /dev/null
+++ b/src/main/java/me/alpha432/oyvey/features/gui/Widget.java
@@ -0,0 +1,198 @@
+package me.alpha432.oyvey.features.gui;
+
+import me.alpha432.oyvey.features.Feature;
+import me.alpha432.oyvey.features.gui.items.Item;
+import me.alpha432.oyvey.features.gui.items.buttons.Button;
+import me.alpha432.oyvey.features.modules.client.ClickGuiModule;
+import me.alpha432.oyvey.util.ColorUtil;
+import me.alpha432.oyvey.util.render.RenderUtil;
+import me.alpha432.oyvey.util.render.ScissorUtil;
+import net.minecraft.client.gui.GuiGraphics;
+import net.minecraft.client.resources.sounds.SimpleSoundInstance;
+import net.minecraft.sounds.SoundEvents;
+
+import java.awt.*;
+import java.util.ArrayList;
+import java.util.List;
+
+public class Widget
+ extends Feature {
+ protected GuiGraphics context;
+ private final List- items = new ArrayList<>();
+ public boolean drag;
+ private int x;
+ private int y;
+ private int x2;
+ private int y2;
+ private int width;
+ private int height;
+ private boolean open;
+ private boolean hidden = false;
+
+ public Widget(String name, int x, int y, boolean open) {
+ super(name);
+ this.x = x;
+ this.y = y;
+ this.width = 88;
+ this.height = 18;
+ this.open = open;
+ }
+
+ private void drag(int mouseX, int mouseY) {
+ if (!this.drag) {
+ return;
+ }
+ this.x = this.x2 + mouseX;
+ this.y = this.y2 + mouseY;
+ }
+
+ public void drawScreen(GuiGraphics context, int mouseX, int mouseY, float partialTicks) {
+ this.context = context;
+ this.drag(mouseX, mouseY);
+ float totalItemHeight = this.open ? this.getTotalItemHeight() - 2.0f : 0.0f;
+ int color = ClickGuiModule.getInstance().topColor.getValue().getRGB();
+ context.fill(this.x, this.y - 1, this.x + this.width, this.y + this.height - 6, ClickGuiModule.getInstance().rainbow.getValue() ? ColorUtil.rainbow(ClickGuiModule.getInstance().rainbowHue.getValue()).getRGB() : color);
+ if (this.open) {
+ RenderUtil.rect(context, this.x, (float) this.y + 12.5f, this.x + this.width, (float) (this.y + this.height) + totalItemHeight, 0x77000000);
+ }
+ drawString(this.getName(), (float) this.x + 3.0f, (float) this.y - 4.0f - (float) OyVeyGui.getClickGui().getTextOffset(), -1);
+ ScissorUtil.enable(context, x, 0, x + width, mc.getWindow().getGuiScaledHeight());
+
+ if (this.open) {
+ float y = (float) (this.getY() + this.getHeight()) - 3.0f;
+ for (Item item : this.getItems()) {
+ if (item.isHidden()) continue;
+ item.setLocation((float) this.x + 2.0f, y);
+ item.setWidth(this.getWidth() - 4);
+ if (item.isHovering(mouseX, mouseY)) {
+ ScissorUtil.disable(context);
+ }
+ item.drawScreen(context, mouseX, mouseY, partialTicks);
+
+ if (item.isHovering(mouseX, mouseY)) {
+ ScissorUtil.enable(context);
+ }
+ y += (float) item.getHeight() + 2f;
+ }
+ }
+
+ ScissorUtil.disable(context);
+ }
+
+ public void mouseClicked(int mouseX, int mouseY, int mouseButton) {
+ if (mouseButton == 0 && this.isHovering(mouseX, mouseY)) {
+ this.x2 = this.x - mouseX;
+ this.y2 = this.y - mouseY;
+ OyVeyGui.getClickGui().getComponents().forEach(component -> {
+ if (component.drag) {
+ component.drag = false;
+ }
+ });
+ this.drag = true;
+ return;
+ }
+ if (mouseButton == 1 && this.isHovering(mouseX, mouseY)) {
+ this.open = !this.open;
+ mc.getSoundManager().play(SimpleSoundInstance.forUI(SoundEvents.UI_BUTTON_CLICK, 1f));
+ return;
+ }
+ if (!this.open) {
+ return;
+ }
+ this.getItems().forEach(item -> item.mouseClicked(mouseX, mouseY, mouseButton));
+ }
+
+ public void mouseReleased(int mouseX, int mouseY, int releaseButton) {
+ if (releaseButton == 0) {
+ this.drag = false;
+ }
+ if (!this.open) {
+ return;
+ }
+ this.getItems().forEach(item -> item.mouseReleased(mouseX, mouseY, releaseButton));
+ }
+
+ public void onKeyTyped(String typedChar, int keyCode) {
+ if (!this.open) {
+ return;
+ }
+ this.getItems().forEach(item -> item.onKeyTyped(typedChar, keyCode));
+ }
+
+ public void onKeyPressed(int key) {
+ if (!open) return;
+ this.getItems().forEach(item -> item.onKeyPressed(key));
+ }
+
+ public void addButton(Button button) {
+ this.items.add(button);
+ }
+
+ public int getX() {
+ return this.x;
+ }
+
+ public void setX(int x) {
+ this.x = x;
+ }
+
+ public int getY() {
+ return this.y;
+ }
+
+ public void setY(int y) {
+ this.y = y;
+ }
+
+ public int getWidth() {
+ return this.width;
+ }
+
+ public void setWidth(int width) {
+ this.width = width;
+ }
+
+ public int getHeight() {
+ return this.height;
+ }
+
+ public void setHeight(int height) {
+ this.height = height;
+ }
+
+ public boolean isHidden() {
+ return this.hidden;
+ }
+
+ public void setHidden(boolean hidden) {
+ this.hidden = hidden;
+ }
+
+ public boolean isOpen() {
+ return this.open;
+ }
+
+ public final List
- getItems() {
+ return this.items;
+ }
+
+ public boolean isHovering(int mouseX, int mouseY) {
+ return mouseX >= this.getX() && mouseX <= this.getX() + this.getWidth() && mouseY >= this.getY() && mouseY <= this.getY() + this.getHeight() - (this.open ? 2 : 0);
+ }
+
+ private float getTotalItemHeight() {
+ float height = 0.0f;
+ for (Item item : this.getItems()) {
+ height += (float) item.getHeight() + 2;
+ }
+ return height;
+ }
+
+ protected void drawString(String text, double x, double y, Color color) {
+ drawString(text, x, y, color.hashCode());
+ }
+
+ protected void drawString(String text, double x, double y, int color) {
+ context.drawString(mc.font, text, (int) x, (int) y, color);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/me/alpha432/oyvey/features/gui/items/Item.java b/src/main/java/me/alpha432/oyvey/features/gui/items/Item.java
new file mode 100644
index 0000000..bf35c1b
--- /dev/null
+++ b/src/main/java/me/alpha432/oyvey/features/gui/items/Item.java
@@ -0,0 +1,87 @@
+package me.alpha432.oyvey.features.gui.items;
+
+import me.alpha432.oyvey.features.Feature;
+import net.minecraft.client.gui.GuiGraphics;
+
+import java.awt.*;
+
+public class Item
+ extends Feature {
+ protected float x;
+ protected float y;
+ protected int width;
+ protected int height;
+ private boolean hidden;
+
+ public Item(String name) {
+ super(name);
+ }
+
+ public void setLocation(float x, float y) {
+ this.x = x;
+ this.y = y;
+ }
+
+ public void drawScreen(GuiGraphics context, int mouseX, int mouseY, float partialTicks) {
+ }
+
+ public void mouseClicked(int mouseX, int mouseY, int mouseButton) {
+ }
+
+ public void mouseReleased(int mouseX, int mouseY, int releaseButton) {
+ }
+
+ public void update() {
+ }
+
+ public void onKeyTyped(String typedChar, int keyCode) {
+ }
+
+ public void onKeyPressed(int key) {
+ }
+
+ public float getX() {
+ return this.x;
+ }
+
+ public float getY() {
+ return this.y;
+ }
+
+ public int getWidth() {
+ return this.width;
+ }
+
+ public void setWidth(int width) {
+ this.width = width;
+ }
+
+ public int getHeight() {
+ return this.height;
+ }
+
+ public void setHeight(int height) {
+ this.height = height;
+ }
+
+ public boolean isHidden() {
+ return this.hidden;
+ }
+
+ public boolean setHidden(boolean hidden) {
+ this.hidden = hidden;
+ return this.hidden;
+ }
+
+ protected void drawString(GuiGraphics context, String text, double x, double y, Color color) {
+ drawString(context, text, x, y, color.getRGB());
+ }
+
+ protected void drawString(GuiGraphics context, String text, double x, double y, int color) {
+ context.drawString(mc.font, text, (int) x, (int) y, color);
+ }
+
+ public boolean isHovering(int mouseX, int mouseY) {
+ return false;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/me/alpha432/oyvey/features/gui/items/buttons/BindButton.java b/src/main/java/me/alpha432/oyvey/features/gui/items/buttons/BindButton.java
new file mode 100644
index 0000000..2c81276
--- /dev/null
+++ b/src/main/java/me/alpha432/oyvey/features/gui/items/buttons/BindButton.java
@@ -0,0 +1,80 @@
+package me.alpha432.oyvey.features.gui.items.buttons;
+
+import me.alpha432.oyvey.OyVey;
+import me.alpha432.oyvey.features.gui.OyVeyGui;
+import me.alpha432.oyvey.features.modules.client.ClickGuiModule;
+import me.alpha432.oyvey.features.settings.Bind;
+import me.alpha432.oyvey.features.settings.Setting;
+import me.alpha432.oyvey.util.KeyboardUtil;
+import me.alpha432.oyvey.util.render.RenderUtil;
+import net.minecraft.ChatFormatting;
+import net.minecraft.client.gui.GuiGraphics;
+import net.minecraft.client.resources.sounds.SimpleSoundInstance;
+import net.minecraft.sounds.SoundEvents;
+import org.lwjgl.glfw.GLFW;
+
+public class BindButton
+ extends Button {
+ private final Setting
setting;
+ public boolean isListening;
+
+ public BindButton(Setting setting) {
+ super(setting.getName());
+ this.setting = setting;
+ this.width = 15;
+ }
+
+ @Override
+ public void drawScreen(GuiGraphics context, int mouseX, int mouseY, float partialTicks) {
+ int color = ClickGuiModule.getInstance().color.getValue().getRGB();
+ RenderUtil.rect(context, this.x, this.y, this.x + (float) this.width + 7.4f, this.y + (float) this.height - 0.5f, this.getState() ? (!this.isHovering(mouseX, mouseY) ? 0x11555555 : -2007673515) : (!this.isHovering(mouseX, mouseY) ? OyVey.colorManager.getColorWithAlpha(y, ClickGuiModule.getInstance().color.getValue().getAlpha()) : OyVey.colorManager.getColorWithAlpha(y, ClickGuiModule.getInstance().topColor.getValue().getAlpha())));
+ if (this.isListening) {
+ drawString(context, "Press a Key...", this.x + 2.3f, this.y - 1.7f - (float) OyVeyGui.getClickGui().getTextOffset(), -1);
+ } else {
+ String str = KeyboardUtil.getKeyName(setting.getValue());
+ drawString(context, this.setting.getName() + " " + ChatFormatting.GRAY + str, this.x + 2.3f, this.y - 1.7f - (float) OyVeyGui.getClickGui().getTextOffset(), this.getState() ? -1 : -5592406);
+ }
+ }
+
+ @Override
+ public void update() {
+ this.setHidden(!this.setting.isVisible());
+ }
+
+ @Override
+ public void mouseClicked(int mouseX, int mouseY, int mouseButton) {
+ super.mouseClicked(mouseX, mouseY, mouseButton);
+ if (this.isListening) {
+ if (mouseButton != 0 && mouseButton != 1) {
+ this.setting.setValue(new Bind(-mouseButton - 2));
+ this.onMouseClick();
+ }
+ } else if (this.isHovering(mouseX, mouseY)) {
+ mc.getSoundManager().play(SimpleSoundInstance.forUI(SoundEvents.UI_BUTTON_CLICK, 1f));
+ }
+ }
+
+ @Override
+ public void onKeyPressed(int key) {
+ if (this.isListening) {
+ Bind bind = new Bind(key);
+ if (key == GLFW.GLFW_KEY_DELETE
+ || key == GLFW.GLFW_KEY_BACKSPACE
+ || key == GLFW.GLFW_KEY_ESCAPE) {
+ bind = new Bind(-1);
+ }
+ this.setting.setValue(bind);
+ this.onMouseClick();
+ }
+ }
+
+ @Override
+ public void toggle() {
+ this.isListening = !this.isListening;
+ }
+
+ @Override
+ public boolean getState() {
+ return !this.isListening;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/me/alpha432/oyvey/features/gui/items/buttons/BooleanButton.java b/src/main/java/me/alpha432/oyvey/features/gui/items/buttons/BooleanButton.java
new file mode 100644
index 0000000..358ffad
--- /dev/null
+++ b/src/main/java/me/alpha432/oyvey/features/gui/items/buttons/BooleanButton.java
@@ -0,0 +1,51 @@
+package me.alpha432.oyvey.features.gui.items.buttons;
+
+
+import me.alpha432.oyvey.OyVey;
+import me.alpha432.oyvey.features.gui.OyVeyGui;
+import me.alpha432.oyvey.features.modules.client.ClickGuiModule;
+import me.alpha432.oyvey.features.settings.Setting;
+import me.alpha432.oyvey.util.render.RenderUtil;
+import net.minecraft.client.gui.GuiGraphics;
+import net.minecraft.client.resources.sounds.SimpleSoundInstance;
+import net.minecraft.sounds.SoundEvents;
+
+public class BooleanButton
+ extends Button {
+ private final Setting setting;
+
+ public BooleanButton(Setting setting) {
+ super(setting.getName());
+ this.setting = setting;
+ this.width = 15;
+ }
+
+ @Override
+ public void drawScreen(GuiGraphics context, int mouseX, int mouseY, float partialTicks) {
+ RenderUtil.rect(context, this.x, this.y, this.x + (float) this.width + 7.4f, this.y + (float) this.height - 0.5f, this.getState() ? (!this.isHovering(mouseX, mouseY) ? OyVey.colorManager.getColorWithAlpha(y, ClickGuiModule.getInstance().color.getValue().getAlpha()) : OyVey.colorManager.getColorWithAlpha(y, ClickGuiModule.getInstance().topColor.getValue().getAlpha())) : (!this.isHovering(mouseX, mouseY) ? 0x11555555 : -2007673515));
+ drawString(context, this.getName(), this.x + 2.3f, this.y - 1.7f - (float) OyVeyGui.getClickGui().getTextOffset(), this.getState() ? -1 : -5592406);
+ }
+
+ @Override
+ public void update() {
+ this.setHidden(!this.setting.isVisible());
+ }
+
+ @Override
+ public void mouseClicked(int mouseX, int mouseY, int mouseButton) {
+ super.mouseClicked(mouseX, mouseY, mouseButton);
+ if (this.isHovering(mouseX, mouseY)) {
+ mc.getSoundManager().play(SimpleSoundInstance.forUI(SoundEvents.UI_BUTTON_CLICK, 1f));
+ }
+ }
+
+ @Override
+ public void toggle() {
+ this.setting.setValue(!this.setting.getValue());
+ }
+
+ @Override
+ public boolean getState() {
+ return this.setting.getValue();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/me/alpha432/oyvey/features/gui/items/buttons/Button.java b/src/main/java/me/alpha432/oyvey/features/gui/items/buttons/Button.java
new file mode 100644
index 0000000..583e853
--- /dev/null
+++ b/src/main/java/me/alpha432/oyvey/features/gui/items/buttons/Button.java
@@ -0,0 +1,60 @@
+package me.alpha432.oyvey.features.gui.items.buttons;
+
+import me.alpha432.oyvey.OyVey;
+import me.alpha432.oyvey.features.gui.OyVeyGui;
+import me.alpha432.oyvey.features.gui.Widget;
+import me.alpha432.oyvey.features.gui.items.Item;
+import me.alpha432.oyvey.features.modules.client.ClickGuiModule;
+import me.alpha432.oyvey.util.render.RenderUtil;
+import net.minecraft.client.gui.GuiGraphics;
+import net.minecraft.client.resources.sounds.SimpleSoundInstance;
+import net.minecraft.sounds.SoundEvents;
+
+public class Button
+ extends Item {
+ private boolean state;
+
+ public Button(String name) {
+ super(name);
+ this.height = 15;
+ }
+
+ @Override
+ public void drawScreen(GuiGraphics context, int mouseX, int mouseY, float partialTicks) {
+ RenderUtil.rect(context, this.x, this.y, this.x + (float) this.width, this.y + (float) this.height - 0.5f, this.getState() ? (!this.isHovering(mouseX, mouseY) ? OyVey.colorManager.getColorWithAlpha(y, ClickGuiModule.getInstance().color.getValue().getAlpha()) : OyVey.colorManager.getColorWithAlpha(y, ClickGuiModule.getInstance().topColor.getValue().getAlpha())) : (!this.isHovering(mouseX, mouseY) ? 0x11555555 : -2007673515));
+ drawString(context, this.getName(), this.x + 2.3f, this.y - 2.0f - (float) OyVeyGui.getClickGui().getTextOffset(), this.getState() ? -1 : -5592406);
+ }
+
+ @Override
+ public void mouseClicked(int mouseX, int mouseY, int mouseButton) {
+ if (mouseButton == 0 && this.isHovering(mouseX, mouseY)) {
+ this.onMouseClick();
+ }
+ }
+
+ public void onMouseClick() {
+ this.state = !this.state;
+ this.toggle();
+ mc.getSoundManager().play(SimpleSoundInstance.forUI(SoundEvents.UI_BUTTON_CLICK, 1f));
+ }
+
+ public void toggle() {
+ }
+
+ public boolean getState() {
+ return this.state;
+ }
+
+ @Override
+ public int getHeight() {
+ return 14;
+ }
+
+ public boolean isHovering(int mouseX, int mouseY) {
+ for (Widget widget : OyVeyGui.getClickGui().getComponents()) {
+ if (!widget.drag) continue;
+ return false;
+ }
+ return (float) mouseX >= this.getX() && (float) mouseX <= this.getX() + (float) this.getWidth() && (float) mouseY >= this.getY() && (float) mouseY < this.getY() + (float) this.height;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/me/alpha432/oyvey/features/gui/items/buttons/ColorButton.java b/src/main/java/me/alpha432/oyvey/features/gui/items/buttons/ColorButton.java
new file mode 100644
index 0000000..9489ea5
--- /dev/null
+++ b/src/main/java/me/alpha432/oyvey/features/gui/items/buttons/ColorButton.java
@@ -0,0 +1,201 @@
+package me.alpha432.oyvey.features.gui.items.buttons;
+
+import me.alpha432.oyvey.OyVey;
+import me.alpha432.oyvey.features.gui.OyVeyGui;
+import me.alpha432.oyvey.features.gui.Widget;
+import me.alpha432.oyvey.features.modules.client.ClickGuiModule;
+import me.alpha432.oyvey.features.settings.Setting;
+import me.alpha432.oyvey.util.render.RenderUtil;
+import net.minecraft.client.gui.GuiGraphics;
+import net.minecraft.client.resources.sounds.SimpleSoundInstance;
+import net.minecraft.sounds.SoundEvents;
+import net.minecraft.util.Mth;
+
+import java.awt.*;
+
+public class ColorButton extends Button {
+ private static final int PICKER_SIZE = 84;
+
+ private final Setting setting;
+ private boolean open = false;
+ private boolean hoveringHue = false, hoveringColor = false, hoveringAlpha = false, hoveringCopy = false, hoveringPaste = false;
+ private boolean draggingHue = false, draggingColor = false, draggingAlpha = false;
+ private float[] hsb;
+
+ public ColorButton(Setting setting) {
+ super(setting.getName());
+ this.setting = setting;
+ this.width = 15;
+ hsb = Color.RGBtoHSB(setting.getValue().getRed(), setting.getValue().getGreen(), setting.getValue().getBlue(), null);
+ }
+
+ @Override
+ public void drawScreen(GuiGraphics context, int mouseX, int mouseY, float partialTicks) {
+ Color currentColor = setting.getValue();
+ Color outlineColor = Color.BLACK;
+ Color realColor = Color.getHSBColor(hsb[0], 1, 1);
+
+ RenderUtil.rect(context, this.x, this.y, this.x + (float) this.width + 7.4f, this.y + (float) this.height - 0.5f,
+ !this.isHovering(mouseX, mouseY) ? 0x11555555 : -2007673515);
+
+ RenderUtil.rect(context, this.x + (float) this.width - 4f, this.y + 3f,
+ this.x + (float) this.width + 5.0f, this.y + (float) this.height - 2.5f,
+ new Color(currentColor.getRGB(), false).getRGB());
+
+ drawString(context, this.getName(), this.x + 2.3f, this.y - 1.7f - (float) OyVeyGui.getClickGui().getTextOffset(), -1);
+
+ if (open) {
+ float yOffset = this.height + 1;
+ int availableWidth = this.width + 3;
+ int pickerWidth = Math.min(PICKER_SIZE, availableWidth);
+ float pickerX = this.x + 2.0f;
+
+ int dragX = Mth.clamp(mouseX - (int) pickerX, 0, pickerWidth);
+ int dragY = Mth.clamp(mouseY - (int) (getY() + yOffset), 0, pickerWidth);
+ float dragHue = Math.max(pickerWidth * hsb[0] - .5f, 1);
+ float dragSaturation = Math.max(pickerWidth * hsb[1] - 1, 2);
+ float dragBrightness = Math.max(pickerWidth * (1.0f - hsb[2]) - 1, 2);
+ float dragAlpha = Math.max(pickerWidth * (currentColor.getAlpha() / 255.0f) - .5f, 1);
+
+ RenderUtil.horizontalGradient(context, pickerX, this.y + yOffset, pickerX + pickerWidth, this.y + yOffset + pickerWidth, Color.WHITE, realColor);
+ RenderUtil.verticalGradient(context, pickerX, this.y + yOffset, pickerX + pickerWidth, this.y + yOffset + pickerWidth, new Color(0, 0, 0, 0), Color.BLACK);
+ RenderUtil.rect(context, pickerX, this.y + yOffset, pickerX + pickerWidth, this.y + yOffset + pickerWidth, outlineColor.getRGB(), 1.0f);
+
+ hoveringColor = isHoveringArea(mouseX, mouseY, pickerX, this.y + yOffset, pickerX + pickerWidth, this.y + yOffset + pickerWidth);
+
+ if (dragSaturation < pickerWidth && dragBrightness < pickerWidth) {
+ RenderUtil.rect(context, pickerX + dragSaturation - 2.5f, this.y + yOffset + dragBrightness - 2.5f,
+ pickerX + dragSaturation + 0.5f, this.y + yOffset + dragBrightness + 0.5f, outlineColor.getRGB());
+ RenderUtil.rect(context, pickerX + dragSaturation - 1.5f, this.y + yOffset + dragBrightness - 1.5f,
+ pickerX + dragSaturation - 0.5f, this.y + yOffset + dragBrightness - 0.5f, Color.WHITE.getRGB());
+ }
+
+ if (draggingColor) {
+ hsb[1] = (float) dragX / pickerWidth;
+ hsb[2] = 1.0f - (float) dragY / pickerWidth;
+ setColor(hsb);
+ }
+
+ yOffset += pickerWidth + 2;
+
+ RenderUtil.horizontalGradient(context, pickerX, this.y + yOffset, pickerX + pickerWidth, this.y + yOffset + 8,
+ new Color(currentColor.getRed(), currentColor.getGreen(), currentColor.getBlue(), 0),
+ new Color(currentColor.getRed(), currentColor.getGreen(), currentColor.getBlue(), 255));
+ RenderUtil.rect(context, pickerX, this.y + yOffset, pickerX + pickerWidth, this.y + yOffset + 8, outlineColor.getRGB(), 1.0f);
+ hoveringAlpha = isHoveringArea(mouseX, mouseY, pickerX, this.y + yOffset, pickerX + pickerWidth, this.y + yOffset + 8);
+
+ RenderUtil.rect(context, pickerX + dragAlpha - 1.5f, this.y + yOffset - 1,
+ pickerX + dragAlpha + 1.5f, this.y + yOffset + 9, outlineColor.getRGB());
+ RenderUtil.rect(context, pickerX + dragAlpha - 0.5f, this.y + yOffset,
+ pickerX + dragAlpha + 0.5f, this.y + yOffset + 8, Color.WHITE.getRGB());
+
+ if (draggingAlpha) {
+ setColor(hsb, (int) (255 * (float) dragX / pickerWidth));
+ }
+
+ yOffset += 10;
+
+ for (float i = 0; i < pickerWidth; i += 0.5f) {
+ RenderUtil.rect(context, pickerX + i, this.y + yOffset, pickerX + i + 0.5f, this.y + yOffset + 8,
+ Color.getHSBColor(i / pickerWidth, 1.0f, 1.0f).getRGB());
+ }
+ RenderUtil.rect(context, pickerX, this.y + yOffset, pickerX + pickerWidth, this.y + yOffset + 8, outlineColor.getRGB(), 1.0f);
+ hoveringHue = isHoveringArea(mouseX, mouseY, pickerX, this.y + yOffset, pickerX + pickerWidth, this.y + yOffset + 8);
+
+ if (dragHue < pickerWidth) {
+ RenderUtil.rect(context, pickerX + dragHue - 1.5f, this.y + yOffset - 1,
+ pickerX + dragHue + 1.5f, this.y + yOffset + 9, outlineColor.getRGB());
+ RenderUtil.rect(context, pickerX + dragHue - 0.5f, this.y + yOffset,
+ pickerX + dragHue + 0.5f, this.y + yOffset + 8, Color.WHITE.getRGB());
+ }
+
+ if (draggingHue) {
+ hsb[0] = (float) dragX / pickerWidth;
+ setColor(hsb);
+ }
+
+ yOffset += 10;
+
+ int buttonWidth = availableWidth / 2;
+ RenderUtil.rect(context, pickerX, this.y + yOffset, pickerX + buttonWidth, this.y + yOffset + 14,
+ hoveringCopy ? OyVey.colorManager.getColorWithAlpha(y, ClickGuiModule.getInstance().topColor.getValue().getAlpha()) : 0x11555555);
+ drawString(context, "Copy", pickerX + buttonWidth / 2.0 - mc.font.width("Copy") / 2.0, this.y + yOffset + 3, -1);
+ hoveringCopy = isHoveringArea(mouseX, mouseY, pickerX, this.y + yOffset, pickerX + buttonWidth, this.y + yOffset + 14);
+
+ RenderUtil.rect(context, pickerX + buttonWidth + 1, this.y + yOffset, pickerX + buttonWidth * 2 + 1, this.y + yOffset + 14,
+ hoveringPaste ? OyVey.colorManager.getColorWithAlpha(y, ClickGuiModule.getInstance().topColor.getValue().getAlpha()) : 0x11555555);
+ drawString(context, "Paste", pickerX + buttonWidth + buttonWidth / 2.0 - mc.font.width("Paste") / 2.0 + 1, this.y + yOffset + 3, -1);
+ hoveringPaste = isHoveringArea(mouseX, mouseY, pickerX + buttonWidth + 1, this.y + yOffset, pickerX + buttonWidth * 2 + 1, this.y + yOffset + 14);
+ }
+ }
+
+ @Override
+ public void mouseClicked(int mouseX, int mouseY, int mouseButton) {
+ if (this.isHovering(mouseX, mouseY) && mouseButton == 1) {
+ open = !open;
+ mc.getSoundManager().play(SimpleSoundInstance.forUI(SoundEvents.UI_BUTTON_CLICK, 1f));
+ }
+
+ if (mouseButton == 0) {
+ if (hoveringHue) draggingHue = true;
+ if (hoveringColor) draggingColor = true;
+ if (hoveringAlpha) draggingAlpha = true;
+
+ if (hoveringCopy) {
+ OyVeyGui.setColorClipboard(setting.getValue());
+ mc.getSoundManager().play(SimpleSoundInstance.forUI(SoundEvents.UI_BUTTON_CLICK, 1f));
+ }
+ if (hoveringPaste && OyVeyGui.getColorClipboard() != null) {
+ setting.setValue(OyVeyGui.getColorClipboard());
+ hsb = Color.RGBtoHSB(setting.getValue().getRed(), setting.getValue().getGreen(), setting.getValue().getBlue(), null);
+ mc.getSoundManager().play(SimpleSoundInstance.forUI(SoundEvents.UI_BUTTON_CLICK, 1f));
+ }
+ }
+ }
+
+ @Override
+ public void mouseReleased(int mouseX, int mouseY, int releaseButton) {
+ if (releaseButton == 0) {
+ draggingHue = false;
+ draggingColor = false;
+ draggingAlpha = false;
+ }
+ }
+
+ @Override
+ public void update() {
+ this.setHidden(!this.setting.isVisible());
+ }
+
+ @Override
+ public int getHeight() {
+ if (!open) return 14;
+ int pickerWidth = Math.min(PICKER_SIZE, this.width + 3);
+ return 14 + pickerWidth + 8 + 8 + 14 + 8;
+ }
+
+ @Override
+ public boolean isHovering(int mouseX, int mouseY) {
+ for (Widget widget : OyVeyGui.getClickGui().getComponents()) {
+ if (widget.drag) return false;
+ }
+ return (float) mouseX >= this.getX() && (float) mouseX <= this.getX() + (float) this.getWidth() + 8.0f
+ && (float) mouseY >= this.getY() && (float) mouseY < this.getY() + (float) this.height;
+ }
+
+ private boolean isHoveringArea(int mouseX, int mouseY, float left, float top, float right, float bottom) {
+ for (Widget widget : OyVeyGui.getClickGui().getComponents()) {
+ if (widget.drag) return false;
+ }
+ return left <= mouseX && top <= mouseY && right > mouseX && bottom > mouseY;
+ }
+
+ private void setColor(float[] hsb) {
+ setColor(hsb, setting.getValue().getAlpha());
+ }
+
+ private void setColor(float[] hsb, int alpha) {
+ Color color = new Color(Color.HSBtoRGB(hsb[0], hsb[1], hsb[2]));
+ setting.setValue(new Color(color.getRed(), color.getGreen(), color.getBlue(), alpha));
+ }
+}
diff --git a/src/main/java/me/alpha432/oyvey/features/gui/items/buttons/EnumButton.java b/src/main/java/me/alpha432/oyvey/features/gui/items/buttons/EnumButton.java
new file mode 100644
index 0000000..0e22751
--- /dev/null
+++ b/src/main/java/me/alpha432/oyvey/features/gui/items/buttons/EnumButton.java
@@ -0,0 +1,52 @@
+package me.alpha432.oyvey.features.gui.items.buttons;
+
+
+import me.alpha432.oyvey.OyVey;
+import me.alpha432.oyvey.features.gui.OyVeyGui;
+import me.alpha432.oyvey.features.modules.client.ClickGuiModule;
+import me.alpha432.oyvey.features.settings.Setting;
+import me.alpha432.oyvey.util.render.RenderUtil;
+import net.minecraft.ChatFormatting;
+import net.minecraft.client.gui.GuiGraphics;
+import net.minecraft.client.resources.sounds.SimpleSoundInstance;
+import net.minecraft.sounds.SoundEvents;
+
+public class EnumButton
+ extends Button {
+ public Setting> setting;
+
+ public EnumButton(Setting> setting) {
+ super(setting.getName());
+ this.setting = setting;
+ this.width = 15;
+ }
+
+ @Override
+ public void drawScreen(GuiGraphics context, int mouseX, int mouseY, float partialTicks) {
+ RenderUtil.rect(context, this.x, this.y, this.x + (float) this.width + 7.4f, this.y + (float) this.height - 0.5f, this.getState() ? (!this.isHovering(mouseX, mouseY) ? OyVey.colorManager.getColorWithAlpha(y, ClickGuiModule.getInstance().color.getValue().getAlpha()) : OyVey.colorManager.getColorWithAlpha(y, ClickGuiModule.getInstance().topColor.getValue().getAlpha())) : (!this.isHovering(mouseX, mouseY) ? 0x11555555 : -2007673515));
+ drawString(context, this.setting.getName() + " " + ChatFormatting.GRAY + (this.setting.currentEnumName().equalsIgnoreCase("ABC") ? "ABC" : this.setting.currentEnumName()), this.x + 2.3f, this.y - 1.7f - (float) OyVeyGui.getClickGui().getTextOffset(), this.getState() ? -1 : -5592406);
+ }
+
+ @Override
+ public void update() {
+ this.setHidden(!this.setting.isVisible());
+ }
+
+ @Override
+ public void mouseClicked(int mouseX, int mouseY, int mouseButton) {
+ super.mouseClicked(mouseX, mouseY, mouseButton);
+ if (this.isHovering(mouseX, mouseY)) {
+ mc.getSoundManager().play(SimpleSoundInstance.forUI(SoundEvents.UI_BUTTON_CLICK, 1f));
+ }
+ }
+
+ @Override
+ public void toggle() {
+ this.setting.increaseEnum();
+ }
+
+ @Override
+ public boolean getState() {
+ return true;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/me/alpha432/oyvey/features/gui/items/buttons/ModuleButton.java b/src/main/java/me/alpha432/oyvey/features/gui/items/buttons/ModuleButton.java
new file mode 100644
index 0000000..ff84f69
--- /dev/null
+++ b/src/main/java/me/alpha432/oyvey/features/gui/items/buttons/ModuleButton.java
@@ -0,0 +1,151 @@
+package me.alpha432.oyvey.features.gui.items.buttons;
+
+import me.alpha432.oyvey.features.gui.items.Item;
+import me.alpha432.oyvey.features.modules.Module;
+import me.alpha432.oyvey.features.settings.Bind;
+import me.alpha432.oyvey.features.settings.Setting;
+import net.minecraft.client.gui.GuiGraphics;
+import net.minecraft.client.resources.sounds.SimpleSoundInstance;
+import net.minecraft.sounds.SoundEvents;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ModuleButton
+ extends Button {
+ private final Module module;
+ private List- items = new ArrayList<>();
+ private boolean subOpen;
+
+ public ModuleButton(Module module) {
+ super(module.getName());
+ this.module = module;
+ this.initSettings();
+ }
+
+ @SuppressWarnings("unchecked")
+ public void initSettings() {
+ ArrayList
- newItems = new ArrayList<>();
+ if (!this.module.getSettings().isEmpty()) {
+ for (Setting> setting : this.module.getSettings()) {
+ if (setting.getValue() instanceof Boolean && !setting.getName().equals("Enabled")) {
+ newItems.add(new BooleanButton((Setting
) setting));
+ }
+ if (setting.getValue() instanceof Bind && !setting.getName().equalsIgnoreCase("Keybind") && !this.module.getName().equalsIgnoreCase("Hud")) {
+ newItems.add(new BindButton((Setting) setting));
+ }
+ if ((setting.getValue() instanceof String || setting.getValue() instanceof Character) && !setting.getName().equalsIgnoreCase("displayName")) {
+ newItems.add(new StringButton((Setting) setting));
+ }
+ if (setting.isColorSetting()) {
+ newItems.add(new ColorButton((Setting) setting));
+ continue;
+ }
+ if (setting.isNumberSetting() && setting.hasRestriction()) {
+ newItems.add(new Slider((Setting) setting));
+ continue;
+ }
+ if (!setting.isEnumSetting()) continue;
+ newItems.add(new EnumButton((Setting>) setting));
+ }
+ }
+ newItems.add(new BindButton((Setting) this.module.getSettingByName("Keybind")));
+ this.items = newItems;
+ }
+
+ @Override
+ public void drawScreen(GuiGraphics context, int mouseX, int mouseY, float partialTicks) {
+ super.drawScreen(context, mouseX, mouseY, partialTicks);
+ if (!this.items.isEmpty()) {
+ if (this.subOpen) {
+ float height = 16.0f;
+ for (Item item : this.items) {
+ if (!item.isHidden()) {
+ item.setLocation(this.x + 1.0f, this.y + height);
+ item.setWidth(this.width - 9);
+ item.drawScreen(context, mouseX, mouseY, partialTicks);
+ height += item.getHeight() + 1f;
+ }
+ item.update();
+ }
+ }
+ }
+ }
+
+ @Override
+ public void mouseClicked(int mouseX, int mouseY, int mouseButton) {
+ super.mouseClicked(mouseX, mouseY, mouseButton);
+ if (!this.items.isEmpty()) {
+ if (mouseButton == 1 && this.isHovering(mouseX, mouseY)) {
+ this.subOpen = !this.subOpen;
+ mc.getSoundManager().play(SimpleSoundInstance.forUI(SoundEvents.UI_BUTTON_CLICK, 1f));
+ }
+ if (this.subOpen) {
+ for (Item item : this.items) {
+ if (item.isHidden()) continue;
+ item.mouseClicked(mouseX, mouseY, mouseButton);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void mouseReleased(int mouseX, int mouseY, int releaseButton) {
+ super.mouseReleased(mouseX, mouseY, releaseButton);
+ if (!this.items.isEmpty() && this.subOpen) {
+ for (Item item : this.items) {
+ if (item.isHidden()) continue;
+ item.mouseReleased(mouseX, mouseY, releaseButton);
+ }
+ }
+ }
+
+ @Override
+ public void onKeyTyped(String typedChar, int keyCode) {
+ super.onKeyTyped(typedChar, keyCode);
+ if (!this.items.isEmpty() && this.subOpen) {
+ for (Item item : this.items) {
+ if (item.isHidden()) continue;
+ item.onKeyTyped(typedChar, keyCode);
+ }
+ }
+ }
+
+ @Override
+ public void onKeyPressed(int key) {
+ super.onKeyPressed(key);
+ if (!this.items.isEmpty() && this.subOpen) {
+ for (Item item : this.items) {
+ if (item.isHidden()) continue;
+ item.onKeyPressed(key);
+ }
+ }
+ }
+
+ @Override
+ public int getHeight() {
+ if (this.subOpen) {
+ int height = 16;
+ for (Item item : this.items) {
+ if (item.isHidden()) continue;
+ height += item.getHeight() + 1;
+ }
+ return height;
+ }
+ return 14;
+ }
+
+ public Module getModule() {
+ return this.module;
+ }
+
+ @Override
+ public void toggle() {
+ this.module.toggle();
+ }
+
+ @Override
+ public boolean getState() {
+ return this.module.isEnabled();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/me/alpha432/oyvey/features/gui/items/buttons/Slider.java b/src/main/java/me/alpha432/oyvey/features/gui/items/buttons/Slider.java
new file mode 100644
index 0000000..b6c36a9
--- /dev/null
+++ b/src/main/java/me/alpha432/oyvey/features/gui/items/buttons/Slider.java
@@ -0,0 +1,95 @@
+package me.alpha432.oyvey.features.gui.items.buttons;
+
+import me.alpha432.oyvey.OyVey;
+import me.alpha432.oyvey.features.gui.OyVeyGui;
+import me.alpha432.oyvey.features.gui.Widget;
+import me.alpha432.oyvey.features.modules.client.ClickGuiModule;
+import me.alpha432.oyvey.features.settings.Setting;
+import me.alpha432.oyvey.util.render.RenderUtil;
+import net.minecraft.ChatFormatting;
+import net.minecraft.client.gui.GuiGraphics;
+import org.lwjgl.glfw.GLFW;
+
+public class Slider
+ extends Button {
+ private final Number min;
+ private final Number max;
+ private final double difference;
+ public Setting setting;
+ private boolean dragging;
+
+ public Slider(Setting setting) {
+ super(setting.getName());
+ this.setting = setting;
+ this.min = setting.getMin();
+ this.max = setting.getMax();
+ this.difference = this.max.doubleValue() - this.min.doubleValue();
+ this.width = 15;
+ }
+
+ @Override
+ public void drawScreen(GuiGraphics context, int mouseX, int mouseY, float partialTicks) {
+ this.dragSetting(mouseX, mouseY);
+ RenderUtil.rect(context, this.x, this.y, this.x + (float) this.width + 7.4f, this.y + (float) this.height - 0.5f, !(this.dragging || this.isHovering(mouseX, mouseY)) ? 0x11555555 : -2007673515);
+ RenderUtil.rect(context, this.x, this.y, (this.setting.getValue()).floatValue() <= this.min.floatValue() ? this.x : this.x + ((float) this.width + 7.4f) * this.partialMultiplier(), this.y + (float) this.height - 0.5f, !(this.dragging || this.isHovering(mouseX, mouseY)) ? OyVey.colorManager.getColorWithAlpha(y, ClickGuiModule.getInstance().color.getValue().getAlpha()) : OyVey.colorManager.getColorWithAlpha(y, ClickGuiModule.getInstance().topColor.getValue().getAlpha()));
+ drawString(context, this.getName() + " " + ChatFormatting.GRAY + (this.setting.getValue() instanceof Float ? this.setting.getValue() : Double.valueOf((this.setting.getValue()).doubleValue())), this.x + 2.3f, this.y - 1.7f - (float) OyVeyGui.getClickGui().getTextOffset(), -1);
+ }
+
+ @Override
+ public void mouseClicked(int mouseX, int mouseY, int mouseButton) {
+ super.mouseClicked(mouseX, mouseY, mouseButton);
+ if (mouseButton == 0 && this.isHovering(mouseX, mouseY)) {
+ this.dragging = true;
+ this.setSettingFromX(mouseX);
+ }
+ }
+
+ @Override
+ public boolean isHovering(int mouseX, int mouseY) {
+ for (Widget widget : OyVeyGui.getClickGui().getComponents()) {
+ if (!widget.drag) continue;
+ return false;
+ }
+ return (float) mouseX >= this.getX() && (float) mouseX <= this.getX() + (float) this.getWidth() + 8.0f && (float) mouseY >= this.getY() && (float) mouseY < this.getY() + (float) this.height;
+ }
+
+ @Override
+ public void update() {
+ this.setHidden(!this.setting.isVisible());
+ }
+
+ private void dragSetting(int mouseX, int mouseY) {
+ if (GLFW.glfwGetMouseButton(mc.getWindow().handle(), 0) != 1) {
+ this.dragging = false;
+ return;
+ }
+ if (this.dragging) {
+ this.setSettingFromX(mouseX);
+ }
+ }
+
+ private void setSettingFromX(int mouseX) {
+ float percent = Math.clamp(((float) mouseX - this.x) / ((float) this.width + 7.4f), 0.0f, 1.0f);
+ if (this.setting.getValue() instanceof Double) {
+ double result = this.setting.getMin().doubleValue() + this.difference * percent;
+ this.setting.setValue((double) Math.round(10.0 * result) / 10.0);
+ } else if (this.setting.getValue() instanceof Float) {
+ float result = this.setting.getMin().floatValue() + (float) (this.difference * percent);
+ this.setting.setValue((float) Math.round(10.0f * result) / 10.0f);
+ } else if (this.setting.getValue() instanceof Integer) {
+ this.setting.setValue(this.setting.getMin().intValue() + (int) Math.round(this.difference * percent));
+ }
+ }
+
+ private float middle() {
+ return this.max.floatValue() - this.min.floatValue();
+ }
+
+ private float part() {
+ return (this.setting.getValue()).floatValue() - this.min.floatValue();
+ }
+
+ private float partialMultiplier() {
+ return this.part() / this.middle();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/me/alpha432/oyvey/features/gui/items/buttons/StringButton.java b/src/main/java/me/alpha432/oyvey/features/gui/items/buttons/StringButton.java
new file mode 100644
index 0000000..27c167f
--- /dev/null
+++ b/src/main/java/me/alpha432/oyvey/features/gui/items/buttons/StringButton.java
@@ -0,0 +1,117 @@
+package me.alpha432.oyvey.features.gui.items.buttons;
+
+import me.alpha432.oyvey.OyVey;
+import me.alpha432.oyvey.features.gui.OyVeyGui;
+import me.alpha432.oyvey.features.modules.client.ClickGuiModule;
+import me.alpha432.oyvey.features.settings.Setting;
+import me.alpha432.oyvey.util.models.Timer;
+import me.alpha432.oyvey.util.render.RenderUtil;
+import net.minecraft.ChatFormatting;
+import net.minecraft.client.gui.GuiGraphics;
+import net.minecraft.client.resources.sounds.SimpleSoundInstance;
+import net.minecraft.sounds.SoundEvents;
+import org.lwjgl.glfw.GLFW;
+
+public class StringButton
+ extends Button {
+ private static final Timer idleTimer = new Timer();
+ private static boolean idle;
+ private final Setting setting;
+ public boolean isListening;
+ private CurrentString currentString = new CurrentString("");
+
+ public StringButton(Setting setting) {
+ super(setting.getName());
+ this.setting = setting;
+ this.width = 15;
+ }
+
+ public static String removeLastChar(String str) {
+ String output = "";
+ if (str != null && !str.isEmpty()) {
+ output = str.substring(0, str.length() - 1);
+ }
+ return output;
+ }
+
+ @Override
+ public void drawScreen(GuiGraphics context, int mouseX, int mouseY, float partialTicks) {
+ RenderUtil.rect(context, this.x, this.y, this.x + (float) this.width + 7.4f, this.y + (float) this.height - 0.5f, this.getState() ? (!this.isHovering(mouseX, mouseY) ? OyVey.colorManager.getColorWithAlpha(y, ClickGuiModule.getInstance().color.getValue().getAlpha()) : OyVey.colorManager.getColorWithAlpha(y, ClickGuiModule.getInstance().topColor.getValue().getAlpha())) : (!this.isHovering(mouseX, mouseY) ? 0x11555555 : -2007673515));
+ if (this.isListening) {
+ drawString(context, this.currentString.string() + getIdleSign(), this.x + 2.3f, this.y - 1.7f - (float) OyVeyGui.getClickGui().getTextOffset(), this.getState() ? -1 : -5592406);
+ } else {
+ drawString(context, (this.setting.getName().equals("Buttons") ? "Buttons " : (this.setting.getName().equals("Prefix") ? "Prefix " + ChatFormatting.GRAY : "")) + this.setting.getValue(), this.x + 2.3f, this.y - 1.7f - (float) OyVeyGui.getClickGui().getTextOffset(), this.getState() ? -1 : -5592406);
+ }
+ }
+
+ @Override
+ public void mouseClicked(int mouseX, int mouseY, int mouseButton) {
+ super.mouseClicked(mouseX, mouseY, mouseButton);
+ if (this.isHovering(mouseX, mouseY)) {
+ mc.getSoundManager().play(SimpleSoundInstance.forUI(SoundEvents.UI_BUTTON_CLICK, 1f));
+ }
+ }
+
+ @Override
+ public void onKeyTyped(String typedChar, int keyCode) {
+ if (this.isListening) {
+ this.setString(this.currentString.string() + typedChar);
+ }
+ }
+
+ @Override
+ public void onKeyPressed(int key) {
+ if (isListening) {
+ switch (key) {
+ case GLFW.GLFW_KEY_ENTER: {
+ this.enterString();
+ break;
+ }
+ case GLFW.GLFW_KEY_BACKSPACE: {
+ this.setString(StringButton.removeLastChar(this.currentString.string()));
+ }
+ }
+ }
+ }
+
+ @Override
+ public void update() {
+ this.setHidden(!this.setting.isVisible());
+ }
+
+ private void enterString() {
+ if (this.currentString.string().isEmpty()) {
+ this.setting.setValue(this.setting.getDefaultValue());
+ } else {
+ this.setting.setValue(this.currentString.string());
+ }
+ this.setString("");
+ this.onMouseClick();
+ }
+
+ @Override
+ public void toggle() {
+ this.isListening = !this.isListening;
+ }
+
+ @Override
+ public boolean getState() {
+ return !this.isListening;
+ }
+
+ public void setString(String newString) {
+ this.currentString = new CurrentString(newString);
+ }
+
+ public static String getIdleSign() {
+ if (idleTimer.passedMs(500)) {
+ idle = !idle;
+ idleTimer.reset();
+ }
+ if (idle) return "_";
+ return "";
+ }
+
+ public record CurrentString(String string) {
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/me/alpha432/oyvey/features/modules/Module.java b/src/main/java/me/alpha432/oyvey/features/modules/Module.java
new file mode 100644
index 0000000..fe9ff80
--- /dev/null
+++ b/src/main/java/me/alpha432/oyvey/features/modules/Module.java
@@ -0,0 +1,204 @@
+package me.alpha432.oyvey.features.modules;
+
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import me.alpha432.oyvey.OyVey;
+import me.alpha432.oyvey.event.impl.ClientEvent;
+import me.alpha432.oyvey.event.impl.render.Render2DEvent;
+import me.alpha432.oyvey.event.impl.render.Render3DEvent;
+import me.alpha432.oyvey.features.Feature;
+import me.alpha432.oyvey.features.commands.Command;
+import me.alpha432.oyvey.features.commands.MessageSignatures;
+import me.alpha432.oyvey.features.settings.Bind;
+import me.alpha432.oyvey.features.settings.Setting;
+import me.alpha432.oyvey.manager.ConfigManager;
+import me.alpha432.oyvey.util.traits.Jsonable;
+import me.alpha432.oyvey.util.traits.Toggleable;
+import net.minecraft.ChatFormatting;
+import org.joml.Vector2f;
+
+import static org.lwjgl.glfw.GLFW.GLFW_KEY_UNKNOWN;
+
+public class Module extends Feature implements Jsonable, Toggleable {
+ private final String description;
+ private final Category category;
+
+ public final Setting enabled = bool("Enabled", false);
+ public final Setting drawn = bool("Drawn", true);
+ public final Setting bind = key("Keybind", new Bind(GLFW_KEY_UNKNOWN));
+ public final Setting displayName;
+
+ public boolean hidden;
+
+ public Module(String name, String description, Category category) {
+ super(name);
+ this.displayName = str("DisplayName", name);
+ this.description = description;
+ this.category = category;
+ }
+
+ public void onEnable() {
+ }
+
+ public void onDisable() {
+ }
+
+ public void onToggle() {
+ }
+
+ public void onLoad() {
+ }
+
+ public void onTick() {
+ }
+
+ public void onRender2D(Render2DEvent event) {
+ }
+
+ public void onRender3D(Render3DEvent event) {
+ }
+
+ public void onUnload() {
+ }
+
+ public String getDisplayInfo() {
+ return null;
+ }
+
+ public void enable() {
+ this.enabled.setValue(true);
+ EVENT_BUS.register(this);
+ EVENT_BUS.post(new ClientEvent(ClientEvent.Type.TOGGLE_MODULE, this));
+ this.onToggle();
+ this.onEnable();
+ }
+
+ public void disable() {
+ this.enabled.setValue(false);
+ EVENT_BUS.unregister(this);
+ EVENT_BUS.post(new ClientEvent(ClientEvent.Type.TOGGLE_MODULE, this));
+ this.onToggle();
+ this.onDisable();
+ }
+
+ public String getDisplayName() {
+ return this.displayName.getValue();
+ }
+
+ public void setDisplayName(String name) {
+ Module module = OyVey.moduleManager.getModuleByDisplayName(name);
+ Module originalModule = OyVey.moduleManager.getModuleByName(name);
+ if (module == null && originalModule == null) {
+ Command.sendMessage("%s, name: %s, has been renamed to: %s", MessageSignatures.GENERAL,
+ getDisplayName(), getName(), name);
+ this.displayName.setValue(name);
+ return;
+ }
+
+ Command.sendMessage("{red} A module of this name already exists.", MessageSignatures.GENERAL);
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return enabled.getValue();
+ }
+
+ @Override
+ public boolean isToggled() {
+ return isEnabled();
+ }
+
+ public String getDescription() {
+ return this.description;
+ }
+
+ public boolean isDrawn() {
+ return this.drawn.getValue();
+ }
+
+ public void setDrawn(boolean drawn) {
+ this.drawn.setValue(drawn);
+ }
+
+ public Category getCategory() {
+ return this.category;
+ }
+
+ public String getInfo() {
+ return null;
+ }
+
+ public Bind getBind() {
+ return this.bind.getValue();
+ }
+
+ public void setBind(int key) {
+ this.bind.setValue(new Bind(key));
+ }
+
+ public String getFullArrayString() {
+ return this.getDisplayName() + ChatFormatting.GRAY + (this.getDisplayInfo() != null ? " [" + ChatFormatting.WHITE + this.getDisplayInfo() + ChatFormatting.GRAY + "]" : "");
+ }
+
+ @Override
+ public JsonElement toJson() {
+ JsonObject object = new JsonObject();
+ for (Setting> setting : getSettings()) {
+ try {
+ if (setting.getValue() instanceof Bind keyBind) {
+ object.addProperty(setting.getName(), keyBind.getKey());
+ } else if (setting.getValue() instanceof java.awt.Color color) {
+ object.addProperty(setting.getName(), color.getRed() + "," + color.getGreen() + "," + color.getBlue() + "," + color.getAlpha());
+ } else if (setting.getValue() instanceof Vector2f pos) {
+ object.addProperty(setting.getName(), pos.x() + "," + pos.y());
+ } else {
+ object.addProperty(setting.getName(), setting.getValueAsString());
+ }
+ } catch (Throwable e) {
+ OyVey.LOGGER.error("Failed to create JSON field", e);
+ }
+ }
+ return object;
+ }
+
+ @Override
+ public void fromJson(JsonElement element) {
+ if (element == null || element.isJsonNull()) return;
+ JsonObject object = element.getAsJsonObject();
+ if (object.has("Enabled")) {
+ boolean enabled = Boolean.parseBoolean(object.get("Enabled").getAsString());
+ if (enabled != isEnabled()) toggle();
+ }
+ for (Setting> setting : getSettings()) {
+ if (setting == this.enabled) continue;
+ try {
+ JsonElement settingElement = object.get(setting.getName());
+ if (settingElement != null && !settingElement.isJsonNull()) {
+ ConfigManager.setValueFromJson(this, setting, settingElement);
+ }
+ } catch (Throwable throwable) {
+ OyVey.LOGGER.error("Failed to load from JSON", throwable);
+ }
+ }
+ }
+
+ public enum Category {
+ COMBAT("Combat"),
+ MISC("Misc"),
+ RENDER("Render"),
+ MOVEMENT("Movement"),
+ PLAYER("Player"),
+ CLIENT("Client"),
+ HUD("Hud");
+
+ private final String name;
+
+ Category(String name) {
+ this.name = name;
+ }
+
+ public String getName() {
+ return this.name;
+ }
+ }
+}
diff --git a/src/main/java/me/alpha432/oyvey/features/modules/client/ClickGuiModule.java b/src/main/java/me/alpha432/oyvey/features/modules/client/ClickGuiModule.java
new file mode 100644
index 0000000..d065657
--- /dev/null
+++ b/src/main/java/me/alpha432/oyvey/features/modules/client/ClickGuiModule.java
@@ -0,0 +1,73 @@
+package me.alpha432.oyvey.features.modules.client;
+
+import me.alpha432.oyvey.OyVey;
+import me.alpha432.oyvey.event.impl.ClientEvent;
+import me.alpha432.oyvey.event.system.Subscribe;
+import me.alpha432.oyvey.features.commands.Command;
+import me.alpha432.oyvey.features.gui.OyVeyGui;
+import me.alpha432.oyvey.features.modules.Module;
+import me.alpha432.oyvey.features.settings.Setting;
+import org.lwjgl.glfw.GLFW;
+
+import java.awt.*;
+
+import static me.alpha432.oyvey.features.commands.MessageSignatures.GENERAL;
+
+public class ClickGuiModule extends Module {
+ private static ClickGuiModule INSTANCE;
+
+ public final Setting prefix = str("Prefix", ".");
+ public final Setting color = color("Color", 0, 0, 255, 180);
+ public final Setting topColor = color("TopColor", 0, 0, 150, 240);
+ public final Setting rainbow = bool("Rainbow", false);
+ public final Setting rainbowHue = num("Delay", 240, 0, 600);
+ public final Setting rainbowBrightness = num("Brightness", 150.0f, 1.0f, 255.0f);
+ public final Setting rainbowSaturation = num("Saturation", 150.0f, 1.0f, 255.0f);
+
+ public ClickGuiModule() {
+ super("ClickGui", "Opens the ClickGui", Module.Category.CLIENT);
+ setBind(GLFW.GLFW_KEY_RIGHT_SHIFT);
+ rainbowHue.setVisibility(v -> rainbow.getValue());
+ rainbowBrightness.setVisibility(v -> rainbow.getValue());
+ rainbowSaturation.setVisibility(v -> rainbow.getValue());
+ INSTANCE = this;
+ }
+
+ @Subscribe
+ public void onSettingChange(ClientEvent event) {
+ if (event.getType() == ClientEvent.Type.SETTING_UPDATE && event.getSetting().getFeature().equals(this)) {
+ if (event.getSetting().equals(this.prefix)) {
+ OyVey.commandManager.setCommandPrefix(this.prefix.getPlannedValue());
+ Command.sendMessage("Prefix set to {global} %s", GENERAL, OyVey.commandManager.getCommandPrefix());
+ }
+ if (event.getSetting().equals(this.color)) {
+ OyVey.colorManager.setColor(this.color.getPlannedValue());
+ }
+ }
+ }
+
+ @Override
+ public void onEnable() {
+ if (nullCheck()) {
+ return;
+ }
+ mc.setScreen(OyVeyGui.getClickGui());
+ }
+
+ @Override
+ public void onLoad() {
+ OyVey.colorManager.setColor(this.color.getValue());
+ OyVey.commandManager.setCommandPrefix(this.prefix.getValue());
+ }
+
+ @Override
+ public void onTick() {
+ if (!(ClickGuiModule.mc.screen instanceof OyVeyGui)) {
+ this.disable();
+ }
+ }
+
+ public static ClickGuiModule getInstance() {
+ return INSTANCE;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/me/alpha432/oyvey/features/modules/client/HudEditorModule.java b/src/main/java/me/alpha432/oyvey/features/modules/client/HudEditorModule.java
new file mode 100644
index 0000000..7771fb2
--- /dev/null
+++ b/src/main/java/me/alpha432/oyvey/features/modules/client/HudEditorModule.java
@@ -0,0 +1,21 @@
+package me.alpha432.oyvey.features.modules.client;
+
+import me.alpha432.oyvey.features.gui.HudEditorScreen;
+import me.alpha432.oyvey.features.modules.Module;
+
+public class HudEditorModule extends Module {
+ public HudEditorModule() {
+ super("HudEditor", "Edit HUD element positions", Category.CLIENT);
+ }
+
+ @Override
+ public void onEnable() {
+ if (nullCheck()) {
+ disable();
+ return;
+ }
+ mc.setScreen(HudEditorScreen.getInstance());
+ disable();
+ }
+}
+
diff --git a/src/main/java/me/alpha432/oyvey/features/modules/client/HudModule.java b/src/main/java/me/alpha432/oyvey/features/modules/client/HudModule.java
new file mode 100644
index 0000000..7dfa8b1
--- /dev/null
+++ b/src/main/java/me/alpha432/oyvey/features/modules/client/HudModule.java
@@ -0,0 +1,150 @@
+package me.alpha432.oyvey.features.modules.client;
+
+import me.alpha432.oyvey.OyVey;
+import me.alpha432.oyvey.event.impl.input.MouseInputEvent;
+import me.alpha432.oyvey.event.impl.render.Render2DEvent;
+import me.alpha432.oyvey.event.system.Subscribe;
+import me.alpha432.oyvey.features.gui.HudEditorScreen;
+import me.alpha432.oyvey.features.modules.Module;
+import me.alpha432.oyvey.features.settings.Setting;
+import me.alpha432.oyvey.util.render.RenderUtil;
+import net.minecraft.client.gui.screens.ChatScreen;
+import org.joml.Vector2f;
+
+public abstract class HudModule extends Module {
+ public final Setting pos = vec2f("Position", 0.5f, 0.5f);
+
+ private float dragX, dragY, width, height;
+ private boolean dragging, button;
+
+ public HudModule(String name, String description, float width, float height) {
+ super(name, description, Category.HUD);
+ this.width = width;
+ this.height = height;
+ }
+
+ public float getX() {
+ return mc.getWindow().getGuiScaledWidth() * pos.getValue().x();
+ }
+
+ public float getY() {
+ float heightWithChat = mc.getWindow().getGuiScaledHeight() - 14;
+ float baseY = mc.getWindow().getGuiScaledHeight() * pos.getValue().y();
+ float combined = baseY + getHeight();
+
+ if (mc.screen instanceof ChatScreen) {
+ baseY = Math.min(combined, heightWithChat) - getHeight();
+ }
+ return baseY;
+ }
+
+ @Subscribe
+ public void onRender2DHud(Render2DEvent e) {
+ render(e);
+ }
+
+ @Subscribe
+ public void onMouse(MouseInputEvent e) {
+ if (!(mc.screen instanceof HudEditorScreen) || nullCheck()) return;
+
+ if (e.getAction() == 0) {
+ button = false;
+ dragging = false;
+ HudEditorScreen.getInstance().currentDragging = null;
+ }
+
+ if (e.getAction() == 1 && isHovering()) {
+ button = true;
+ }
+ }
+
+ protected void render(Render2DEvent e) {
+ if (!(mc.screen instanceof HudEditorScreen) || nullCheck()) return;
+
+ float x = getX();
+ float y = getY();
+
+ if (button) {
+ if (!dragging && isHovering() && HudEditorScreen.getInstance().currentDragging == null) {
+ dragX = getMouseX() - x;
+ dragY = getMouseY() - y;
+ dragging = true;
+ HudEditorScreen.getInstance().currentDragging = this;
+ }
+
+ if (dragging) {
+ float finalX = Math.min(Math.max(getMouseX() - dragX, 0),
+ mc.getWindow().getGuiScaledWidth() - width);
+ float finalY = Math.min(Math.max(getMouseY() - dragY, 0),
+ mc.getWindow().getGuiScaledHeight() - height);
+
+ pos.setValue(new Vector2f(finalX, finalY)
+ .div(mc.getWindow().getGuiScaledWidth(), mc.getWindow().getGuiScaledHeight()));
+ }
+ } else {
+ dragging = false;
+ }
+
+ boolean shouldDrawDescription = isHovering() && !HudEditorScreen.getInstance().anyHover;
+ if (HudEditorScreen.getInstance().currentDragging != null) {
+ shouldDrawDescription = HudEditorScreen.getInstance().currentDragging == this;
+ }
+
+ if (shouldDrawDescription) {
+ int textWidth = mc.font.width(getName());
+ int textHeight = mc.font.lineHeight;
+ float textX = x + width + 5;
+ if (textX + textWidth > mc.getWindow().getGuiScaledWidth()) {
+ textX = x - 5 - textWidth;
+ }
+ e.getContext().drawString(mc.font, getName(),
+ (int) textX, (int) (y + height / 2f - textHeight / 2f), -1);
+ HudEditorScreen.getInstance().anyHover = true;
+ }
+
+ RenderUtil.rect(e.getContext(),
+ x - 1, y - 1, x + width + 1, y + height + 1,
+ OyVey.colorManager.getColor().getRGB(), 1.0f);
+ }
+
+ public int getMouseX() {
+ return (int) (mc.mouseHandler.xpos() / mc.getWindow().getGuiScale());
+ }
+
+ public int getMouseY() {
+ return (int) (mc.mouseHandler.ypos() / mc.getWindow().getGuiScale());
+ }
+
+ public void setBounds(float x, float y, float width, float height) {
+ this.width = width;
+ this.height = height;
+ pos.setValue(new Vector2f(x, y)
+ .div(mc.getWindow().getGuiScaledWidth(), mc.getWindow().getGuiScaledHeight()));
+ }
+
+ public boolean isHovering() {
+ float x = getX();
+ float y = getY();
+ int mouseX = getMouseX();
+ int mouseY = getMouseY();
+
+ return mouseX >= x - 1 && mouseX <= x + width + 1 &&
+ mouseY >= y - 1 && mouseY <= y + height + 1;
+ }
+
+ public float getWidth() {
+ return width;
+ }
+
+ public float getHeight() {
+ return height;
+ }
+
+ public void setWidth(float width) {
+ this.width = width;
+ }
+
+ public void setHeight(float height) {
+ this.height = height;
+ }
+}
diff --git a/src/main/java/me/alpha432/oyvey/features/modules/client/NotificationsModule.java b/src/main/java/me/alpha432/oyvey/features/modules/client/NotificationsModule.java
new file mode 100644
index 0000000..4bdbbba
--- /dev/null
+++ b/src/main/java/me/alpha432/oyvey/features/modules/client/NotificationsModule.java
@@ -0,0 +1,39 @@
+package me.alpha432.oyvey.features.modules.client;
+
+import me.alpha432.oyvey.event.impl.ClientEvent;
+import me.alpha432.oyvey.event.system.Subscribe;
+import me.alpha432.oyvey.features.modules.Module;
+import me.alpha432.oyvey.features.settings.Setting;
+import me.alpha432.oyvey.util.TextUtil;
+import me.alpha432.oyvey.util.chat.ChatUtil;
+
+import static me.alpha432.oyvey.util.chat.SimpleSignature.from;
+
+public class NotificationsModule extends Module {
+ private static final String MODULE_FORMAT = "{gray} %s %s%s";
+
+ public final Setting moduleToggle = bool("ModuleToggle", true);
+
+ public NotificationsModule() {
+ super("Notifications", "Displays notifications for various client events", Category.CLIENT);
+ }
+
+ @Subscribe
+ public void onClient(ClientEvent event) {
+ if (!moduleToggle.getValue()
+ || event.getType() != ClientEvent.Type.TOGGLE_MODULE
+ || event.getFeature() instanceof ClickGuiModule) {
+ return;
+ }
+
+ boolean moduleState = event.getFeature().isEnabled();
+ String name = event.getFeature() instanceof Module module
+ ? module.getDisplayName()
+ : event.getFeature().getName();
+
+ ChatUtil.sendMessage(TextUtil.text(MODULE_FORMAT,
+ name,
+ moduleState ? "{green}" : "{red}",
+ moduleState ? "on" : "off"), from(event.getFeature().getName()));
+ }
+}
diff --git a/src/main/java/me/alpha432/oyvey/features/modules/combat/CriticalsModule.java b/src/main/java/me/alpha432/oyvey/features/modules/combat/CriticalsModule.java
new file mode 100644
index 0000000..eaadfee
--- /dev/null
+++ b/src/main/java/me/alpha432/oyvey/features/modules/combat/CriticalsModule.java
@@ -0,0 +1,37 @@
+package me.alpha432.oyvey.features.modules.combat;
+
+import me.alpha432.oyvey.event.impl.network.PacketEvent;
+import me.alpha432.oyvey.event.system.Subscribe;
+import me.alpha432.oyvey.features.modules.Module;
+import net.minecraft.network.protocol.game.ServerboundInteractPacket;
+import net.minecraft.network.protocol.game.ServerboundMovePlayerPacket;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.LivingEntity;
+import net.minecraft.world.entity.boss.enderdragon.EndCrystal;
+
+public class CriticalsModule extends Module {
+ public CriticalsModule() {
+ super("Criticals", "Makes you do critical hits", Category.COMBAT);
+ }
+
+ @Subscribe
+ private void onPacketSend(PacketEvent.Send event) {
+ if (event.getPacket() instanceof ServerboundInteractPacket packet && packet.action.getType() == ServerboundInteractPacket.ActionType.ATTACK) {
+ Entity entity = mc.level.getEntity(packet.entityId);
+ if (entity == null
+ || entity instanceof EndCrystal
+ || !mc.player.onGround()
+ || !(entity instanceof LivingEntity)) return;
+
+ boolean bl = mc.player.horizontalCollision;
+ mc.player.connection.send(new ServerboundMovePlayerPacket.Pos(mc.player.getX(), mc.player.getY() + 0.1f, mc.player.getZ(), false, bl));
+ mc.player.connection.send(new ServerboundMovePlayerPacket.Pos(mc.player.getX(), mc.player.getY(), mc.player.getZ(), false, bl));
+ mc.player.crit(entity);
+ }
+ }
+
+ @Override
+ public String getDisplayInfo() {
+ return "Packet";
+ }
+}
diff --git a/src/main/java/me/alpha432/oyvey/features/modules/combat/KeyPearlModule.java b/src/main/java/me/alpha432/oyvey/features/modules/combat/KeyPearlModule.java
new file mode 100644
index 0000000..8c7ee44
--- /dev/null
+++ b/src/main/java/me/alpha432/oyvey/features/modules/combat/KeyPearlModule.java
@@ -0,0 +1,28 @@
+package me.alpha432.oyvey.features.modules.combat;
+
+import me.alpha432.oyvey.features.modules.Module;
+import me.alpha432.oyvey.features.settings.Setting;
+import me.alpha432.oyvey.util.inventory.InventoryUtil;
+import me.alpha432.oyvey.util.inventory.Result;
+import net.minecraft.world.item.Items;
+
+import static me.alpha432.oyvey.util.inventory.InventoryUtil.FULL_SCOPE;
+import static me.alpha432.oyvey.util.inventory.InventoryUtil.HOTBAR_SCOPE;
+
+public class KeyPearlModule extends Module {
+ private final Setting inventory = bool("Inventory", false);
+
+ public KeyPearlModule() {
+ super("KeyPearl", "Throws a pearl when enabled.", Category.COMBAT);
+ }
+
+ @Override
+ public void onEnable() {
+ disable();
+
+ if (nullCheck()) return;
+
+ Result result = InventoryUtil.find(Items.ENDER_PEARL, inventory.getValue() ? FULL_SCOPE : HOTBAR_SCOPE);
+ InventoryUtil.withSwap(result, () -> mc.gameMode.useItem(mc.player, result.hand()));
+ }
+}
diff --git a/src/main/java/me/alpha432/oyvey/features/modules/combat/KillAuraModule.java b/src/main/java/me/alpha432/oyvey/features/modules/combat/KillAuraModule.java
new file mode 100644
index 0000000..bbf8cee
--- /dev/null
+++ b/src/main/java/me/alpha432/oyvey/features/modules/combat/KillAuraModule.java
@@ -0,0 +1,77 @@
+package me.alpha432.oyvey.features.modules.combat;
+
+import me.alpha432.oyvey.OyVey;
+import me.alpha432.oyvey.event.impl.entity.player.TickEvent;
+import me.alpha432.oyvey.event.system.Subscribe;
+import me.alpha432.oyvey.features.modules.Module;
+import me.alpha432.oyvey.features.settings.Setting;
+import me.alpha432.oyvey.util.models.Angles;
+import net.minecraft.tags.ItemTags;
+import net.minecraft.world.InteractionHand;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.player.Player;
+import net.minecraft.world.item.ItemStack;
+import net.minecraft.world.item.Items;
+
+public class KillAuraModule extends Module {
+ private final Setting delay = num("Delay", 1f, 0f, 1f);
+ private final Setting range = num("Range", 3f, 1f, 6f);
+ private final Setting onlyWeapon = bool("OnlyWeapon", false);
+ private final Setting sync = bool("MovementSync", false);
+
+ public KillAuraModule() {
+ super("KillAura", "Attacks nearby players.", Category.COMBAT);
+ }
+
+ @Subscribe public void onPreTick(TickEvent.Pre event) {
+ if (isOnCooldown())
+ return;
+
+ if (onlyWeapon.getValue() && !isHoldingWeapon())
+ return;
+
+ Player target = findPlayerTarget();
+ if (target == null)
+ return;
+
+ Angles angles = Angles.entity(target);
+ int priority = 0;
+
+ if (sync.getValue()) {
+ OyVey.rotationManager.sync(angles, priority, () -> attack(target));
+ } else {
+ OyVey.rotationManager.motion(angles, priority, () -> attack(target));
+ }
+ }
+
+ private void attack(Entity entity) {
+ if (nullCheck() || !entity.isAlive() || entity.isRemoved() || entity.distanceTo(mc.player) > range.getValue())
+ return;
+ mc.gameMode.attack(mc.player, entity);
+ mc.player.swing(InteractionHand.MAIN_HAND);
+ }
+
+ private boolean isOnCooldown() {
+ float attackDelay = sync.getValue() ? 1 : 0;
+ return mc.player.getAttackStrengthScale(attackDelay) < delay.getValue();
+ }
+
+ private boolean isHoldingWeapon() {
+ ItemStack stack = mc.player.getMainHandItem();
+ return stack.is(ItemTags.SWORDS) || stack.is(ItemTags.AXES) || stack.is(ItemTags.SPEARS)
+ || stack.is(Items.MACE) || stack.is(Items.TRIDENT);
+ }
+
+ private Player findPlayerTarget() {
+ Player target = null;
+ float min = range.getValue();
+ for (Player entity : mc.level.players()) {
+ float dist = entity.distanceTo(mc.player);
+ if (entity == mc.player || dist > min) continue;
+ if (OyVey.friendManager.isFriend(entity)) continue;
+ target = entity;
+ min = dist;
+ }
+ return target;
+ }
+}
diff --git a/src/main/java/me/alpha432/oyvey/features/modules/hud/CoordinatesHudModule.java b/src/main/java/me/alpha432/oyvey/features/modules/hud/CoordinatesHudModule.java
new file mode 100644
index 0000000..668b342
--- /dev/null
+++ b/src/main/java/me/alpha432/oyvey/features/modules/hud/CoordinatesHudModule.java
@@ -0,0 +1,43 @@
+package me.alpha432.oyvey.features.modules.hud;
+
+import me.alpha432.oyvey.event.impl.render.Render2DEvent;
+import me.alpha432.oyvey.features.modules.client.HudModule;
+import me.alpha432.oyvey.features.settings.Setting;
+import net.minecraft.resources.ResourceKey;
+import net.minecraft.world.level.Level;
+
+public class CoordinatesHudModule extends HudModule {
+ public final Setting nether = bool("Nether", false);
+
+ public CoordinatesHudModule() {
+ super("Coordinates", "Display coordinates", 150, 20);
+ }
+
+ @Override
+ protected void render(Render2DEvent e) {
+ super.render(e);
+
+ if (nullCheck()) return;
+
+ String coordsStr = String.format("X: %d Y: %d Z: %d",
+ mc.player.getBlockX(),
+ mc.player.getBlockY(),
+ mc.player.getBlockZ());
+
+ if (nether.getValue()) {
+ ResourceKey dimension = mc.player.level().dimension();
+ if (dimension == Level.NETHER) {
+ coordsStr += String.format(" [%d, %d]", mc.player.getBlockX() * 8, mc.player.getBlockZ() * 8);
+ } else if (dimension == Level.OVERWORLD) {
+ coordsStr += String.format(" [%d, %d]", Math.floorDiv(mc.player.getBlockX(), 8), Math.floorDiv(mc.player.getBlockZ(), 8));
+ }
+ }
+
+ e.getContext().drawString(mc.font, coordsStr,
+ (int) getX(), (int) getY(), -1);
+
+ setWidth(mc.font.width(coordsStr));
+ setHeight(mc.font.lineHeight);
+ }
+}
+
diff --git a/src/main/java/me/alpha432/oyvey/features/modules/hud/WatermarkHudModule.java b/src/main/java/me/alpha432/oyvey/features/modules/hud/WatermarkHudModule.java
new file mode 100644
index 0000000..34841ab
--- /dev/null
+++ b/src/main/java/me/alpha432/oyvey/features/modules/hud/WatermarkHudModule.java
@@ -0,0 +1,36 @@
+package me.alpha432.oyvey.features.modules.hud;
+
+import me.alpha432.oyvey.event.impl.render.Render2DEvent;
+import me.alpha432.oyvey.features.modules.client.HudModule;
+import me.alpha432.oyvey.features.settings.Setting;
+import me.alpha432.oyvey.util.BuildConfig;
+import me.alpha432.oyvey.util.TextUtil;
+import net.minecraft.network.chat.Component;
+
+public class WatermarkHudModule extends HudModule {
+ public final Setting text = str("Text", BuildConfig.NAME);
+ public final Setting fullVersion = new Setting<>("FullVersion", false);
+
+ public WatermarkHudModule() {
+ super("Watermark", "Display watermark", 100, 10);
+ if (BuildConfig.USING_GIT) {
+ register(fullVersion);
+ }
+ }
+
+ @Override
+ protected void render(Render2DEvent e) {
+ super.render(e);
+ String watermarkString = "{global} %s {} %s";
+ if (fullVersion.getValue() && BuildConfig.USING_GIT) {
+ watermarkString += "/" + BuildConfig.BRANCH + "-" + BuildConfig.HASH;
+ }
+
+ Component component = TextUtil.text(watermarkString, text.getValue(), BuildConfig.VERSION);
+ e.getContext().drawString(mc.font, component, (int) getX(), (int) getY(), -1);
+
+ setWidth(mc.font.width(component));
+ setHeight(mc.font.lineHeight);
+ }
+}
+
diff --git a/src/main/java/me/alpha432/oyvey/features/modules/misc/MCFModule.java b/src/main/java/me/alpha432/oyvey/features/modules/misc/MCFModule.java
new file mode 100644
index 0000000..5cdc6cf
--- /dev/null
+++ b/src/main/java/me/alpha432/oyvey/features/modules/misc/MCFModule.java
@@ -0,0 +1,48 @@
+package me.alpha432.oyvey.features.modules.misc;
+
+import me.alpha432.oyvey.OyVey;
+import me.alpha432.oyvey.features.commands.Command;
+import me.alpha432.oyvey.features.modules.Module;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.player.Player;
+import org.lwjgl.glfw.GLFW;
+
+import static me.alpha432.oyvey.util.chat.SimpleSignature.from;
+
+public class MCFModule extends Module {
+ private boolean pressed;
+
+ public MCFModule() {
+ super("MCF", "Middle click friend", Category.MISC);
+ }
+
+ @Override
+ public void onTick() {
+ if (mc.screen != null) {
+ pressed = false;
+ return;
+ }
+
+ if (GLFW.glfwGetMouseButton(mc.getWindow().handle(), 2) == 1) {
+ if (!pressed) click();
+ pressed = true;
+ } else {
+ pressed = false;
+ }
+ }
+
+ private void click() {
+ Entity targetedEntity = mc.crosshairPickEntity;
+ if (!(targetedEntity instanceof Player)) return;
+ String name = ((Player) targetedEntity).getGameProfile().name();
+
+ if (OyVey.friendManager.isFriend(name)) {
+ OyVey.friendManager.removeFriend(name);
+
+ Command.sendMessage("{red} %s has been unfriended.", from(getName()), name);
+ } else {
+ OyVey.friendManager.addFriend(name);
+ Command.sendMessage("{aqua} %s has been friended.", from(getName()), name);
+ }
+ }
+}
diff --git a/src/main/java/me/alpha432/oyvey/features/modules/movement/FastAccelModule.java b/src/main/java/me/alpha432/oyvey/features/modules/movement/FastAccelModule.java
new file mode 100644
index 0000000..3d694ee
--- /dev/null
+++ b/src/main/java/me/alpha432/oyvey/features/modules/movement/FastAccelModule.java
@@ -0,0 +1,34 @@
+package me.alpha432.oyvey.features.modules.movement;
+
+import me.alpha432.oyvey.event.impl.entity.player.MoveEvent;
+import me.alpha432.oyvey.event.system.Subscribe;
+import me.alpha432.oyvey.features.modules.Module;
+import me.alpha432.oyvey.features.settings.Setting;
+import me.alpha432.oyvey.util.MovementUtil;
+import net.minecraft.world.entity.MoverType;
+import org.joml.Vector2f;
+
+public class FastAccelModule extends Module {
+ private final Setting speed = num("Speed", 1f, 0.5f, 2f);
+ private final Setting slowness = bool("Slowness", true);
+
+ public FastAccelModule() {
+ super("FastAccel", "Makes the player accelerate faster while walking", Category.MOVEMENT);
+ }
+
+ @Subscribe
+ private void onMove(MoveEvent event) {
+ if (nullCheck() || event.getType() != MoverType.SELF) return;
+ if (mc.player.isInLiquid() || mc.player.isFallFlying()
+ || mc.player.getAbilities().flying || mc.player.isSpectator()) return;
+
+ double speed = MovementUtil.SPRINT_SPEED * this.speed.getValue() * MovementUtil.getSpeedFactor(true, slowness.getValue());
+
+ if (event.getMovement().horizontalDistance() > speed) {
+ speed = event.getMovement().horizontalDistance();
+ }
+
+ Vector2f motion = MovementUtil.calcMoveVector(mc.player.getYRot(), speed);
+ event.setMovement(motion.x, event.getMovement().y, motion.y);
+ }
+}
diff --git a/src/main/java/me/alpha432/oyvey/features/modules/movement/ReverseStepModule.java b/src/main/java/me/alpha432/oyvey/features/modules/movement/ReverseStepModule.java
new file mode 100644
index 0000000..c79c47d
--- /dev/null
+++ b/src/main/java/me/alpha432/oyvey/features/modules/movement/ReverseStepModule.java
@@ -0,0 +1,16 @@
+package me.alpha432.oyvey.features.modules.movement;
+
+import me.alpha432.oyvey.features.modules.Module;
+
+public class ReverseStepModule extends Module {
+ public ReverseStepModule() {
+ super("ReverseStep", "step but reversed..", Category.MOVEMENT);
+ }
+
+ @Override
+ public void onTick() {
+ if (nullCheck()) return;
+ if (mc.player.isInLava() || mc.player.isInWater() || !mc.player.onGround()) return;
+ mc.player.push(0, -1, 0);
+ }
+}
diff --git a/src/main/java/me/alpha432/oyvey/features/modules/movement/StepModule.java b/src/main/java/me/alpha432/oyvey/features/modules/movement/StepModule.java
new file mode 100644
index 0000000..bccb3ad
--- /dev/null
+++ b/src/main/java/me/alpha432/oyvey/features/modules/movement/StepModule.java
@@ -0,0 +1,36 @@
+package me.alpha432.oyvey.features.modules.movement;
+
+import me.alpha432.oyvey.features.modules.Module;
+import me.alpha432.oyvey.features.settings.Setting;
+import net.minecraft.world.entity.ai.attributes.Attributes;
+
+public class StepModule extends Module {
+ private final Setting height = num("Height", 2f, 1f, 3f);
+
+ public StepModule() {
+ super("Step", "step..", Category.MOVEMENT);
+ }
+
+ private float prev;
+
+ @Override
+ public void onEnable() {
+ if (nullCheck()) {
+ prev = 0.6f;
+ return;
+ }
+ prev = mc.player.maxUpStep();
+ }
+
+ @Override
+ public void onDisable() {
+ if (nullCheck()) return;
+ mc.player.getAttribute(Attributes.STEP_HEIGHT).setBaseValue(prev);
+ }
+
+ @Override
+ public void onTick() {
+ if (nullCheck()) return;
+ mc.player.getAttribute(Attributes.STEP_HEIGHT).setBaseValue(height.getValue());
+ }
+}
diff --git a/src/main/java/me/alpha432/oyvey/features/modules/player/FastPlaceModule.java b/src/main/java/me/alpha432/oyvey/features/modules/player/FastPlaceModule.java
new file mode 100644
index 0000000..8bddd01
--- /dev/null
+++ b/src/main/java/me/alpha432/oyvey/features/modules/player/FastPlaceModule.java
@@ -0,0 +1,19 @@
+package me.alpha432.oyvey.features.modules.player;
+
+import me.alpha432.oyvey.features.modules.Module;
+import net.minecraft.world.item.Items;
+
+public class FastPlaceModule extends Module {
+ public FastPlaceModule() {
+ super("FastPlace", "Makes you throw exp faster", Category.PLAYER);
+ }
+
+ @Override
+ public void onTick() {
+ if (nullCheck()) return;
+
+ if (mc.player.isHolding(Items.EXPERIENCE_BOTTLE)) {
+ mc.rightClickDelay = 0;
+ }
+ }
+}
diff --git a/src/main/java/me/alpha432/oyvey/features/modules/player/NoFallModule.java b/src/main/java/me/alpha432/oyvey/features/modules/player/NoFallModule.java
new file mode 100644
index 0000000..889bfc7
--- /dev/null
+++ b/src/main/java/me/alpha432/oyvey/features/modules/player/NoFallModule.java
@@ -0,0 +1,21 @@
+package me.alpha432.oyvey.features.modules.player;
+
+import me.alpha432.oyvey.OyVey;
+import me.alpha432.oyvey.features.modules.Module;
+import net.minecraft.network.protocol.game.ServerboundMovePlayerPacket;
+
+public class NoFallModule extends Module {
+ public NoFallModule() {
+ super("NoFall", "Removes fall damage", Category.PLAYER);
+ }
+
+ @Override
+ public void onTick() {
+ if (!mc.player.onGround() && OyVey.positionManager.getFallDistance() > 3) {
+ boolean bl = mc.player.horizontalCollision;
+ ServerboundMovePlayerPacket.PosRot packet = new ServerboundMovePlayerPacket.PosRot(mc.player.getX(), mc.player.getY() + 0.000000001, mc.player.getZ(),
+ mc.player.getYRot(), mc.player.getXRot(), false, bl);
+ mc.player.connection.send(packet);
+ }
+ }
+}
diff --git a/src/main/java/me/alpha432/oyvey/features/modules/player/VelocityModule.java b/src/main/java/me/alpha432/oyvey/features/modules/player/VelocityModule.java
new file mode 100644
index 0000000..8901e92
--- /dev/null
+++ b/src/main/java/me/alpha432/oyvey/features/modules/player/VelocityModule.java
@@ -0,0 +1,24 @@
+package me.alpha432.oyvey.features.modules.player;
+
+import me.alpha432.oyvey.event.impl.network.PacketEvent;
+import me.alpha432.oyvey.event.system.Subscribe;
+import me.alpha432.oyvey.features.modules.Module;
+import net.minecraft.network.protocol.game.ClientboundExplodePacket;
+import net.minecraft.network.protocol.game.ClientboundSetEntityMotionPacket;
+
+public class VelocityModule extends Module {
+ public VelocityModule() {
+ super("Velocity", "Removes velocity from explosions and entities", Category.PLAYER);
+ }
+
+ @Subscribe
+ private void onPacketReceive(PacketEvent.Receive event) {
+ if (nullCheck())
+ return;
+
+ if (event.getPacket() instanceof ClientboundSetEntityMotionPacket packet && packet.getId() == mc.player.getId()
+ || event.getPacket() instanceof ClientboundExplodePacket) {
+ event.cancel();
+ }
+ }
+}
diff --git a/src/main/java/me/alpha432/oyvey/features/modules/render/BlockHighlightModule.java b/src/main/java/me/alpha432/oyvey/features/modules/render/BlockHighlightModule.java
new file mode 100644
index 0000000..9a25af8
--- /dev/null
+++ b/src/main/java/me/alpha432/oyvey/features/modules/render/BlockHighlightModule.java
@@ -0,0 +1,39 @@
+package me.alpha432.oyvey.features.modules.render;
+
+import me.alpha432.oyvey.event.impl.render.Render3DEvent;
+import me.alpha432.oyvey.event.impl.render.RenderBlockOutlineEvent;
+import me.alpha432.oyvey.event.system.Subscribe;
+import me.alpha432.oyvey.features.modules.Module;
+import me.alpha432.oyvey.features.settings.Setting;
+import me.alpha432.oyvey.util.render.RenderUtil;
+import net.minecraft.world.phys.AABB;
+import net.minecraft.world.phys.BlockHitResult;
+import net.minecraft.world.phys.HitResult;
+import net.minecraft.world.phys.shapes.VoxelShape;
+
+import java.awt.*;
+
+public class BlockHighlightModule extends Module {
+ public final Setting color = color("Color", 255, 0, 0, 255);
+ public final Setting lineWidth = num("LineWidth", 1.0f, 0.1f, 5.0f);
+
+ public BlockHighlightModule() {
+ super("BlockHighlight", "Draws box at the block that you are looking at", Category.RENDER);
+ }
+
+ @Override
+ public void onRender3D(Render3DEvent event) {
+ if (mc.hitResult instanceof BlockHitResult result) {
+ VoxelShape shape = mc.level.getBlockState(result.getBlockPos()).getShape(mc.level, result.getBlockPos());
+ if (shape.isEmpty() || result.getType() == HitResult.Type.MISS) return;
+ AABB aabb = shape.bounds();
+ aabb = aabb.move(result.getBlockPos());
+ RenderUtil.drawBox(event.getMatrix(), aabb, color.getValue(), lineWidth.getValue());
+ }
+ }
+
+ @Subscribe
+ public void onRenderBlockOutline(RenderBlockOutlineEvent event) {
+ event.cancel();
+ }
+}
diff --git a/src/main/java/me/alpha432/oyvey/features/settings/Bind.java b/src/main/java/me/alpha432/oyvey/features/settings/Bind.java
new file mode 100644
index 0000000..a6507b8
--- /dev/null
+++ b/src/main/java/me/alpha432/oyvey/features/settings/Bind.java
@@ -0,0 +1,61 @@
+package me.alpha432.oyvey.features.settings;
+
+import com.mojang.blaze3d.platform.InputConstants;
+import me.alpha432.oyvey.util.traits.Util;
+import net.minecraft.client.input.KeyEvent;
+import org.lwjgl.glfw.GLFW;
+
+public class Bind implements Util {
+ private int key;
+
+ public Bind(int key) {
+ this.key = key;
+ }
+
+ public static Bind keyboard(int key) {
+ return new Bind(key);
+ }
+
+ public static Bind mouse(int key) {
+ return new Bind(-key - 2);
+ }
+
+ public static Bind none() {
+ return new Bind(-1);
+ }
+
+ public int getKey() {
+ return this.key;
+ }
+
+ public void setKey(int key) {
+ this.key = key;
+ }
+
+ public boolean isEmpty() {
+ return this.key == -1;
+ }
+
+ public boolean isMouse() {
+ return key < -1;
+ }
+
+ public String toString() {
+ if (this.isEmpty()) return "None";
+ if (this.key < -1) return "Mouse " + (-this.key - 1);
+ return this.capitalise(InputConstants.getKey(new KeyEvent(this.key, 0, 0)).getName().replace("key.keyboard.", ""));
+ }
+
+ public boolean isDown() {
+ if (this.isEmpty()) return false;
+ if (this.key < -1) return GLFW.glfwGetMouseButton(mc.getWindow().handle(), -this.key - 2) == 1;
+ return GLFW.glfwGetKey(mc.getWindow().handle(), this.getKey()) == 1;
+ }
+
+ private String capitalise(String str) {
+ if (str.isEmpty()) {
+ return "";
+ }
+ return Character.toUpperCase(str.charAt(0)) + (str.length() != 1 ? str.substring(1).toLowerCase() : "");
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/me/alpha432/oyvey/features/settings/EnumConverter.java b/src/main/java/me/alpha432/oyvey/features/settings/EnumConverter.java
new file mode 100644
index 0000000..2b05c8d
--- /dev/null
+++ b/src/main/java/me/alpha432/oyvey/features/settings/EnumConverter.java
@@ -0,0 +1,51 @@
+package me.alpha432.oyvey.features.settings;
+
+import com.google.common.base.Converter;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonPrimitive;
+
+@SuppressWarnings("unchecked")
+public class EnumConverter> extends Converter {
+ private final Class clazz;
+
+ public EnumConverter(Class clazz) {
+ this.clazz = clazz;
+ }
+
+ public static > int currentEnum(T clazz) {
+ for (int i = 0; i < clazz.getDeclaringClass().getEnumConstants().length; ++i) {
+ T e = (T) clazz.getDeclaringClass().getEnumConstants()[i];
+ if (!e.name().equalsIgnoreCase(clazz.name())) continue;
+ return i;
+ }
+ return -1;
+ }
+
+ public static > T increaseEnum(T clazz) {
+ int index = EnumConverter.currentEnum(clazz);
+ for (int i = 0; i < clazz.getDeclaringClass().getEnumConstants().length; ++i) {
+ T e = (T) clazz.getDeclaringClass().getEnumConstants()[i];
+ if (i != index + 1) continue;
+ return e;
+ }
+ return (T) clazz.getDeclaringClass().getEnumConstants()[0];
+ }
+
+ public static > String getProperName(T clazz) {
+ return Character.toUpperCase(clazz.name().charAt(0)) + clazz.name().toLowerCase().substring(1);
+ }
+
+ public JsonElement doForward(Enum anEnum) {
+ return new JsonPrimitive(anEnum.name());
+ }
+
+ public T doBackward(JsonElement jsonElement) {
+ String name = jsonElement.getAsString();
+ for (T constant : this.clazz.getEnumConstants()) {
+ if (constant.name().equalsIgnoreCase(name) || constant.toString().equalsIgnoreCase(name)) {
+ return constant;
+ }
+ }
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/me/alpha432/oyvey/features/settings/Setting.java b/src/main/java/me/alpha432/oyvey/features/settings/Setting.java
new file mode 100644
index 0000000..6ffef74
--- /dev/null
+++ b/src/main/java/me/alpha432/oyvey/features/settings/Setting.java
@@ -0,0 +1,287 @@
+package me.alpha432.oyvey.features.settings;
+
+import me.alpha432.oyvey.event.impl.ClientEvent;
+import me.alpha432.oyvey.features.Feature;
+import org.joml.Vector2f;
+
+import java.awt.*;
+import java.util.Objects;
+import java.util.function.Predicate;
+
+import static me.alpha432.oyvey.util.traits.Util.EVENT_BUS;
+
+public class Setting {
+ private final String name;
+ private final T defaultValue;
+ private T value;
+ private T plannedValue;
+ private T min;
+ private T max;
+ private boolean hasRestriction;
+ private Predicate visibility;
+ private String description;
+ private Feature feature;
+
+ public Setting(String name, T defaultValue) {
+ this.name = name;
+ this.defaultValue = defaultValue;
+ this.value = copy(defaultValue);
+ this.plannedValue = this.value;
+ this.description = "";
+ }
+
+ public Setting(String name, T defaultValue, String description) {
+ this.name = name;
+ this.defaultValue = defaultValue;
+ this.value = copy(defaultValue);
+ this.plannedValue = this.value;
+ this.description = description;
+ }
+
+ public Setting(String name, T defaultValue, T min, T max, String description) {
+ this.name = name;
+ this.defaultValue = defaultValue;
+ this.value = copy(defaultValue);
+ this.min = min;
+ this.max = max;
+ this.plannedValue = this.value;
+ this.description = description;
+ this.hasRestriction = true;
+ }
+
+ public Setting(String name, T defaultValue, T min, T max) {
+ this.name = name;
+ this.defaultValue = defaultValue;
+ this.value = copy(defaultValue);
+ this.min = min;
+ this.max = max;
+ this.plannedValue = this.value;
+ this.description = "";
+ this.hasRestriction = true;
+ }
+
+ public Setting(String name, T defaultValue, T min, T max, Predicate visibility, String description) {
+ this.name = name;
+ this.defaultValue = defaultValue;
+ this.value = copy(defaultValue);
+ this.min = min;
+ this.max = max;
+ this.plannedValue = this.value;
+ this.visibility = visibility;
+ this.description = description;
+ this.hasRestriction = true;
+ }
+
+ public Setting(String name, T defaultValue, T min, T max, Predicate visibility) {
+ this.name = name;
+ this.defaultValue = defaultValue;
+ this.value = copy(defaultValue);
+ this.min = min;
+ this.max = max;
+ this.plannedValue = this.value;
+ this.visibility = visibility;
+ this.description = "";
+ this.hasRestriction = true;
+ }
+
+ public Setting(String name, T defaultValue, Predicate visibility) {
+ this.name = name;
+ this.defaultValue = defaultValue;
+ this.value = copy(defaultValue);
+ this.visibility = visibility;
+ this.plannedValue = this.value;
+ }
+
+ public String getName() {
+ return this.name;
+ }
+
+ public T getValue() {
+ return this.value;
+ }
+
+ public void setValue(T value) {
+ this.setPlannedValue(value);
+ if (this.hasRestriction) {
+ if (((Number) this.min).floatValue() > ((Number) value).floatValue()) {
+ this.setPlannedValue(this.min);
+ }
+ if (((Number) this.max).floatValue() < ((Number) value).floatValue()) {
+ this.setPlannedValue(this.max);
+ }
+ }
+ ClientEvent event = new ClientEvent(this);
+ EVENT_BUS.post(event);
+ if (!event.isCancelled()) {
+ this.value = this.plannedValue;
+ } else {
+ this.plannedValue = this.value;
+ }
+ }
+
+ public void reset() {
+ setValue(copy(getDefaultValue()));
+ }
+
+ @SuppressWarnings("unchecked")
+ private static T copy(T value) {
+ if (value instanceof Vector2f vector) {
+ return (T) new Vector2f(vector);
+ }
+ return value;
+ }
+
+ public T getPlannedValue() {
+ return this.plannedValue;
+ }
+
+ public void setPlannedValue(T value) {
+ this.plannedValue = value;
+ }
+
+ public T getMin() {
+ return this.min;
+ }
+
+ public void setMin(T min) {
+ this.min = min;
+ }
+
+ public T getMax() {
+ return this.max;
+ }
+
+ public void setMax(T max) {
+ this.max = max;
+ }
+
+ public void setValueNoEvent(T value) {
+ this.setPlannedValue(value);
+ if (this.hasRestriction) {
+ if (((Number) this.min).floatValue() > ((Number) value).floatValue()) {
+ this.setPlannedValue(this.min);
+ }
+ if (((Number) this.max).floatValue() < ((Number) value).floatValue()) {
+ this.setPlannedValue(this.max);
+ }
+ }
+ this.value = this.plannedValue;
+ }
+
+ public Feature getFeature() {
+ return this.feature;
+ }
+
+ public void setFeature(Feature feature) {
+ this.feature = feature;
+ }
+
+ public int getEnum(String input) {
+ for (int i = 0; i < this.value.getClass().getEnumConstants().length; ++i) {
+ Enum> e = (Enum>) this.value.getClass().getEnumConstants()[i];
+ if (!e.name().equalsIgnoreCase(input)) continue;
+ return i;
+ }
+ return -1;
+ }
+
+ @SuppressWarnings("unchecked")
+ public void setEnumValue(String value) {
+ for (Enum> e : ((Enum>) this.value).getClass().getEnumConstants()) {
+ if (!e.name().equalsIgnoreCase(value)) continue;
+ this.value = (T) e;
+ }
+ }
+
+ public String currentEnumName() {
+ return EnumConverter.getProperName((Enum>) this.value);
+ }
+
+ public int currentEnum() {
+ return EnumConverter.currentEnum((Enum>) this.value);
+ }
+
+ @SuppressWarnings("unchecked")
+ public void increaseEnum() {
+ this.plannedValue = (T) EnumConverter.increaseEnum((Enum>) this.value);
+ ClientEvent event = new ClientEvent(this);
+ EVENT_BUS.post(event);
+ if (!event.isCancelled()) {
+ this.value = this.plannedValue;
+ } else {
+ this.plannedValue = this.value;
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ public void increaseEnumNoEvent() {
+ this.value = (T) EnumConverter.increaseEnum((Enum>) this.value);
+ }
+
+ public String getType() {
+ if (this.isEnumSetting()) {
+ return "Enum";
+ }
+ if (this.isColorSetting()) {
+ return "Color";
+ }
+ if (this.isVec2fSetting()) {
+ return "Pos";
+ }
+ return this.getClassName(this.defaultValue);
+ }
+
+ public String getClassName(K value) {
+ return value.getClass().getSimpleName();
+ }
+
+ public String getDescription() {
+ return Objects.requireNonNullElse(this.description, "");
+ }
+
+ public boolean isNumberSetting() {
+ return this.value instanceof Number;
+ }
+
+ public boolean isEnumSetting() {
+ return this.value instanceof Enum>;
+ }
+
+ public boolean isStringSetting() {
+ return this.value instanceof String;
+ }
+
+ public boolean isColorSetting() {
+ return this.value instanceof java.awt.Color;
+ }
+
+ public boolean isVec2fSetting() {
+ return this.value instanceof Vector2f;
+ }
+
+ public T getDefaultValue() {
+ return this.defaultValue;
+ }
+
+ public String getValueAsString() {
+ if (value instanceof Color color) {
+ return "%d, %d, %d, %d".formatted(color.getRed(), color.getGreen(), color.getBlue(), color.getAlpha());
+ }
+ if (value instanceof Enum> e) {
+ return e.name();
+ }
+ return this.value.toString();
+ }
+
+ public boolean hasRestriction() {
+ return this.hasRestriction;
+ }
+
+ public void setVisibility(Predicate visibility) {
+ this.visibility = visibility;
+ }
+
+ public boolean isVisible() {
+ return this.visibility == null || this.visibility.test(this.getValue());
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/me/alpha432/oyvey/features/settings/SettingFactory.java b/src/main/java/me/alpha432/oyvey/features/settings/SettingFactory.java
new file mode 100644
index 0000000..8c612f6
--- /dev/null
+++ b/src/main/java/me/alpha432/oyvey/features/settings/SettingFactory.java
@@ -0,0 +1,46 @@
+package me.alpha432.oyvey.features.settings;
+
+import org.joml.Vector2f;
+
+import java.awt.*;
+
+public interface SettingFactory {
+ > T register(T setting);
+
+ default Setting bool(String name, boolean value) {
+ return register(new Setting<>(name, value));
+ }
+
+ default Setting num(String name, T value, T min, T max) {
+ return register(new Setting<>(name, value, min, max));
+ }
+
+ default Setting str(String name, String value) {
+ return register(new Setting<>(name, value));
+ }
+
+ default > Setting mode(String name, T value) {
+ return register(new Setting<>(name, value));
+ }
+
+ default Setting key(String name, Bind bind) {
+ return register(new Setting<>(name, bind));
+ }
+
+ default Setting color(String name, Color value) {
+ return register(new Setting<>(name, value));
+ }
+
+ default Setting color(String name, int r, int g, int b, int a) {
+ return register(new Setting<>(name, new Color(r, g, b, a)));
+ }
+
+ default Setting vec2f(String name, Vector2f value) {
+ return register(new Setting<>(name, value));
+ }
+
+ default Setting vec2f(String name, float x, float y) {
+ return register(new Setting<>(name, new Vector2f(x, y)));
+ }
+
+}
diff --git a/src/main/java/me/alpha432/oyvey/manager/ColorManager.java b/src/main/java/me/alpha432/oyvey/manager/ColorManager.java
new file mode 100644
index 0000000..c85624c
--- /dev/null
+++ b/src/main/java/me/alpha432/oyvey/manager/ColorManager.java
@@ -0,0 +1,39 @@
+package me.alpha432.oyvey.manager;
+
+import me.alpha432.oyvey.features.modules.client.ClickGuiModule;
+import me.alpha432.oyvey.util.ColorUtil;
+
+import java.awt.*;
+
+public class ColorManager {
+ private Color color = new Color(0, 0, 255, 180);
+
+ public void init() {
+ ClickGuiModule ui = ClickGuiModule.getInstance();
+ setColor(ui.color.getValue());
+ }
+
+ public Color getColor() {
+ return this.color;
+ }
+
+ public void setColor(Color color) {
+ this.color = color;
+ }
+
+ public int getColorAsInt() {
+ return this.color.getRGB();
+ }
+
+ public int getColorAsIntFullAlpha() {
+ return new Color(this.color.getRed(), this.color.getGreen(), this.color.getBlue(), 255).getRGB();
+ }
+
+ public int getColorWithAlpha(float offset, int alpha) {
+ if (ClickGuiModule.getInstance().rainbow.getValue()) {
+ int rgb = ColorUtil.rainbow((int) (offset / 10f * ClickGuiModule.getInstance().rainbowHue.getValue())).getRGB();
+ return (rgb & 0xFFFFFF) | ((alpha & 0xFF) << 24);
+ }
+ return new Color(this.color.getRed(), this.color.getGreen(), this.color.getBlue(), alpha).getRGB();
+ }
+}
diff --git a/src/main/java/me/alpha432/oyvey/manager/CommandManager.java b/src/main/java/me/alpha432/oyvey/manager/CommandManager.java
new file mode 100644
index 0000000..fa1727b
--- /dev/null
+++ b/src/main/java/me/alpha432/oyvey/manager/CommandManager.java
@@ -0,0 +1,114 @@
+package me.alpha432.oyvey.manager;
+
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.mojang.brigadier.CommandDispatcher;
+import com.mojang.brigadier.builder.LiteralArgumentBuilder;
+import com.mojang.brigadier.exceptions.CommandSyntaxException;
+import me.alpha432.oyvey.OyVey;
+import me.alpha432.oyvey.features.Feature;
+import me.alpha432.oyvey.features.commands.Command;
+import me.alpha432.oyvey.features.commands.impl.*;
+import me.alpha432.oyvey.util.traits.Jsonable;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.util.*;
+
+import static me.alpha432.oyvey.features.commands.Command.SINGLE_FAILURE;
+import static me.alpha432.oyvey.features.commands.MessageSignatures.GENERAL;
+import static me.alpha432.oyvey.features.commands.MessageSignatures.SUCCESS;
+
+public class CommandManager extends Feature implements Jsonable {
+ private static final Logger LOGGER = LogManager.getLogger("Commands");
+
+ private final CommandDispatcher dispatcher = new CommandDispatcher<>();
+ private final Map commandAliasMap = new LinkedHashMap<>();
+ private final List commandList = new LinkedList<>();
+
+ private String commandPrefix = ".";
+
+ public CommandManager() {
+ super("Commands");
+ }
+
+ public void init() {
+ register(new BindCommand());
+ register(new DrawnCommand());
+ register(new FriendCommand());
+ register(new HelpCommand());
+ register(new PrefixCommand());
+ register(new ToggleCommand());
+
+ LOGGER.info("Registered {} commands", commandList.size());
+ OyVey.configManager.addConfig(this);
+ }
+
+ public void onChatSent(String message) {
+ try {
+ int result = dispatcher.execute(message.substring(getCommandPrefix().length()).trim(), this);
+
+ if (result == SINGLE_FAILURE) {
+ Command.sendMessage("{red} Failed to execute command", SUCCESS);
+ }
+ } catch (CommandSyntaxException e) {
+ LOGGER.error("Failed to execute command", e);
+ Command.sendMessage("{red} %s", GENERAL, e.getMessage());
+ } catch (RuntimeException e) {
+ LOGGER.error("Unexpected error while executing command", e);
+ Command.sendMessage("{red} Failed to execute command", GENERAL);
+ }
+ }
+
+ public void register(Command command) {
+ commandList.add(command);
+ for (String alias : command.getAliases()) {
+ commandAliasMap.put(alias, command);
+ LiteralArgumentBuilder builder = Command.literal(alias);
+ command.createArgumentBuilder(builder);
+ dispatcher.register(builder);
+ }
+ }
+
+ public void setCommandPrefix(String commandPrefix) {
+ this.commandPrefix = commandPrefix;
+ }
+
+ public String getCommandPrefix() {
+ return commandPrefix;
+ }
+
+ public Command getCommand(String alias) {
+ return commandAliasMap.get(alias);
+ }
+
+ public Set getCommandAliases() {
+ return commandAliasMap.keySet();
+ }
+
+ public List getCommands() {
+ return commandList;
+ }
+
+ public CommandDispatcher getDispatcher() {
+ return dispatcher;
+ }
+
+ @Override
+ public JsonElement toJson() {
+ JsonObject object = new JsonObject();
+ object.addProperty("prefix", commandPrefix);
+ return object;
+ }
+
+ @Override
+ public void fromJson(JsonElement element) {
+ if (element == null || !element.isJsonObject()) return;
+
+ JsonObject object = element.getAsJsonObject();
+
+ if (object.has("prefix")) {
+ setCommandPrefix(object.get("prefix").getAsString());
+ }
+ }
+}
diff --git a/src/main/java/me/alpha432/oyvey/manager/ConfigManager.java b/src/main/java/me/alpha432/oyvey/manager/ConfigManager.java
new file mode 100644
index 0000000..5c291d5
--- /dev/null
+++ b/src/main/java/me/alpha432/oyvey/manager/ConfigManager.java
@@ -0,0 +1,120 @@
+package me.alpha432.oyvey.manager;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonParser;
+import me.alpha432.oyvey.features.Feature;
+import me.alpha432.oyvey.features.settings.Bind;
+import me.alpha432.oyvey.features.settings.EnumConverter;
+import me.alpha432.oyvey.features.settings.Setting;
+import me.alpha432.oyvey.util.traits.Jsonable;
+import net.fabricmc.loader.api.FabricLoader;
+import org.joml.Vector2f;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.awt.*;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.LinkedList;
+import java.util.List;
+
+public class ConfigManager {
+ private static final Logger LOGGER = LoggerFactory.getLogger("ConfigManager");
+ private static final Path OYVEY_PATH = FabricLoader.getInstance().getGameDir().resolve("oyvey");
+ private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create();
+
+ private final List jsonables = new LinkedList<>();
+
+ public void addConfig(Jsonable jsonable) {
+ jsonables.add(jsonable);
+ }
+
+ public void load() {
+ mkdirs();
+ for (Jsonable jsonable : jsonables) {
+ Path path = OYVEY_PATH.resolve(jsonable.getFileName());
+ if (!Files.exists(path)) continue;
+ try {
+ String read = Files.readString(path);
+ jsonable.fromJson(JsonParser.parseString(read));
+ } catch (Throwable e) {
+ LOGGER.error("Failed to load", e);
+ }
+ }
+ }
+
+ public synchronized void save() {
+ mkdirs();
+ for (Jsonable jsonable : jsonables) {
+ try {
+ JsonElement json = jsonable.toJson();
+ Path path = OYVEY_PATH.resolve(jsonable.getFileName());
+ Files.writeString(path, GSON.toJson(json));
+ } catch (Throwable e) {
+ LOGGER.error("Failed to write to file", e);
+ }
+ }
+ }
+
+ private void mkdirs() {
+ if (!OYVEY_PATH.toFile().exists()) {
+ boolean success = OYVEY_PATH.toFile().mkdirs();
+ if (!success) {
+ throw new RuntimeException("Failed to create needed directories!");
+ }
+ }
+ }
+
+ @SuppressWarnings({"rawtypes", "unchecked"})
+ public static void setValueFromJson(Feature feature, Setting setting, JsonElement element) {
+ if (element == null || element.isJsonNull()) return;
+ switch (setting.getType()) {
+ case "Boolean" -> setting.setValue(element.getAsBoolean());
+ case "Double" -> setting.setValue(element.getAsDouble());
+ case "Float" -> setting.setValue(element.getAsFloat());
+ case "Integer" -> setting.setValue(element.getAsInt());
+ case "String" -> setting.setValue(element.getAsString());
+ case "Bind" -> setting.setValue(new Bind(element.getAsInt()));
+ case "Color" -> {
+ try {
+ String colorStr = element.getAsString();
+ String[] parts = colorStr.split(",");
+ if (parts.length == 4) {
+ int r = Integer.parseInt(parts[0]);
+ int g = Integer.parseInt(parts[1]);
+ int b = Integer.parseInt(parts[2]);
+ int a = Integer.parseInt(parts[3]);
+ setting.setValue(new Color(r, g, b, a));
+ }
+ } catch (Exception exception) {
+ LOGGER.error("Error parsing color for: {} : {}", feature.getName(), setting.getName());
+ }
+ }
+ case "Pos" -> {
+ try {
+ String posStr = element.getAsString();
+ String[] parts = posStr.split(",");
+ if (parts.length == 2) {
+ float x = Float.parseFloat(parts[0]);
+ float y = Float.parseFloat(parts[1]);
+ setting.setValue(new Vector2f(x, y));
+ }
+ } catch (Exception exception) {
+ LOGGER.error("Error parsing position for: {} : {}", feature.getName(), setting.getName());
+ }
+ }
+ case "Enum" -> {
+ try {
+ EnumConverter converter = new EnumConverter(((Enum) setting.getValue()).getDeclaringClass());
+ Enum value = converter.doBackward(element);
+ setting.setValue(value);
+ } catch (Exception exception) {
+ LOGGER.error("Error parsing enum for {}.{}: {}", feature.getName(), setting.getName(), exception);
+ }
+ }
+ default -> LOGGER.error("Unknown Setting type for: {} : {}", feature.getName(), setting.getName());
+ }
+ }
+}
diff --git a/src/main/java/me/alpha432/oyvey/manager/EventManager.java b/src/main/java/me/alpha432/oyvey/manager/EventManager.java
new file mode 100644
index 0000000..4421774
--- /dev/null
+++ b/src/main/java/me/alpha432/oyvey/manager/EventManager.java
@@ -0,0 +1,96 @@
+package me.alpha432.oyvey.manager;
+
+import me.alpha432.oyvey.OyVey;
+import me.alpha432.oyvey.event.Stage;
+import me.alpha432.oyvey.event.impl.entity.DeathEvent;
+import me.alpha432.oyvey.event.impl.entity.player.TickEvent;
+import me.alpha432.oyvey.event.impl.entity.player.UpdateWalkingPlayerEvent;
+import me.alpha432.oyvey.event.impl.input.KeyInputEvent;
+import me.alpha432.oyvey.event.impl.input.MouseInputEvent;
+import me.alpha432.oyvey.event.impl.network.ChatEvent;
+import me.alpha432.oyvey.event.impl.network.PacketEvent;
+import me.alpha432.oyvey.event.impl.render.Render2DEvent;
+import me.alpha432.oyvey.event.impl.render.Render3DEvent;
+import me.alpha432.oyvey.event.system.Subscribe;
+import me.alpha432.oyvey.features.Feature;
+import net.minecraft.network.protocol.common.ClientboundCustomPayloadPacket;
+import net.minecraft.network.protocol.common.custom.BrandPayload;
+import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
+import net.minecraft.network.protocol.game.ClientboundSetTimePacket;
+import net.minecraft.world.entity.player.Player;
+
+import java.util.HashSet;
+import java.util.Set;
+
+public class EventManager extends Feature {
+ private final Set deadPlayers = new HashSet<>();
+
+ public void init() {
+ EVENT_BUS.register(this);
+ }
+
+ public void onUnload() {
+ EVENT_BUS.unregister(this);
+ }
+
+ @Subscribe
+ public void onTick(TickEvent.Post event) {
+ if (nullCheck())
+ return;
+ OyVey.speedManager.update();
+ OyVey.moduleManager.onTick();
+ Set dead = new HashSet<>();
+ for (Player player : mc.level.players()) {
+ if (player == null || player.getHealth() > 0.0F)
+ continue;
+ dead.add(player.getId());
+ if (!deadPlayers.contains(player.getId()))
+ EVENT_BUS.post(new DeathEvent(player));
+ }
+ deadPlayers.clear();
+ deadPlayers.addAll(dead);
+ }
+
+ @Subscribe
+ public void onPacketReceive(PacketEvent.Receive event) {
+ OyVey.serverManager.onPacketReceived();
+ if (event.getPacket() instanceof ClientboundSetTimePacket)
+ OyVey.serverManager.update();
+ if (event.getPacket() instanceof ClientboundCustomPayloadPacket(CustomPacketPayload payload)
+ && payload instanceof BrandPayload(String brand)) {
+ OyVey.serverManager.setServerBrand(brand);
+ }
+ }
+
+ @Subscribe
+ public void onWorldRender(Render3DEvent event) {
+ OyVey.moduleManager.onRender3D(event);
+ }
+
+ @Subscribe
+ public void onRenderGameOverlayEvent(Render2DEvent event) {
+ OyVey.moduleManager.onRender2D(event);
+ }
+
+ @Subscribe
+ public void onKeyInput(KeyInputEvent event) {
+ OyVey.moduleManager.onKeyPressed(event.getKey());
+ }
+
+ @Subscribe
+ public void onMouseInput(MouseInputEvent event) {
+ if (event.getAction() == 1) {
+ OyVey.moduleManager.onMouseClicked(event.getButton());
+ }
+ }
+
+ @Subscribe
+ public void onChatSent(ChatEvent event) {
+ String message = event.getMessage();
+ if (!message.startsWith(OyVey.commandManager.getCommandPrefix())) {
+ return;
+ }
+ event.cancel();
+ OyVey.commandManager.onChatSent(message);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/me/alpha432/oyvey/manager/FriendManager.java b/src/main/java/me/alpha432/oyvey/manager/FriendManager.java
new file mode 100644
index 0000000..ed10435
--- /dev/null
+++ b/src/main/java/me/alpha432/oyvey/manager/FriendManager.java
@@ -0,0 +1,67 @@
+package me.alpha432.oyvey.manager;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import me.alpha432.oyvey.OyVey;
+import me.alpha432.oyvey.util.traits.Jsonable;
+import net.minecraft.world.entity.player.Player;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class FriendManager implements Jsonable {
+ private final List friends = new ArrayList<>();
+
+ public void init() {
+ OyVey.configManager.addConfig(this);
+ }
+
+ public boolean isFriend(String name) {
+ return this.friends.stream().anyMatch(friend -> friend.equalsIgnoreCase(name));
+ }
+
+ public boolean isFriend(Player player) {
+ return this.isFriend(player.getGameProfile().name());
+ }
+
+ public void addFriend(String name) {
+ this.friends.add(name);
+ }
+
+ public void removeFriend(String name) {
+ friends.removeIf(s -> s.equalsIgnoreCase(name));
+ }
+
+ public List getFriends() {
+ return this.friends;
+ }
+
+ @Override
+ public JsonElement toJson() {
+ JsonObject object = new JsonObject();
+ JsonArray array = new JsonArray();
+ for (String friend : friends) {
+ array.add(friend);
+ }
+ object.add("friends", array);
+ return object;
+ }
+
+ @Override
+ public void fromJson(JsonElement element) {
+ friends.clear();
+ if (element == null || !element.isJsonObject()) return;
+ JsonElement array = element.getAsJsonObject().get("friends");
+ if (array == null || !array.isJsonArray()) return;
+ for (JsonElement e : array.getAsJsonArray()) {
+ String name = e.getAsString();
+ if (!friends.contains(name)) friends.add(name);
+ }
+ }
+
+ @Override
+ public String getFileName() {
+ return "friends.json";
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/me/alpha432/oyvey/manager/HoleManager.java b/src/main/java/me/alpha432/oyvey/manager/HoleManager.java
new file mode 100644
index 0000000..5bbe0ca
--- /dev/null
+++ b/src/main/java/me/alpha432/oyvey/manager/HoleManager.java
@@ -0,0 +1,68 @@
+package me.alpha432.oyvey.manager;
+
+import me.alpha432.oyvey.event.impl.entity.player.TickEvent;
+import me.alpha432.oyvey.event.system.Subscribe;
+import me.alpha432.oyvey.features.Feature;
+import net.minecraft.core.BlockPos;
+import net.minecraft.core.Direction;
+import net.minecraft.world.level.block.Block;
+import net.minecraft.world.level.block.Blocks;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class HoleManager extends Feature {
+ private static final int RANGE = 8;
+
+ private final List holes = new ArrayList<>();
+ private final BlockPos.MutableBlockPos pos = new BlockPos.MutableBlockPos();
+
+ public HoleManager() {
+ EVENT_BUS.register(this);
+ }
+
+ @Subscribe
+ private void onTick(TickEvent.Pre event) {
+ holes.clear();
+ for (int x = -RANGE; x < RANGE; x++) {
+ for (int y = -RANGE; y < RANGE; y++) {
+ for (int z = -RANGE; z < RANGE; z++) {
+ pos.set(mc.player.getX() + x, mc.player.getY() + y, mc.player.getZ() + z);
+ Hole hole = getHole(pos);
+ if (hole == null) continue;
+ holes.add(hole);
+ }
+ }
+ }
+ }
+
+ @Nullable
+ public Hole getHole(BlockPos pos) {
+ if (mc.level.getBlockState(pos).getBlock() != Blocks.AIR)
+ return null;
+ HoleType type = HoleType.BEDROCK;
+ for (Direction direction : Direction.Plane.HORIZONTAL) {
+ Block block = mc.level.getBlockState(pos.relative(direction)).getBlock();
+ if (block == Blocks.OBSIDIAN) type = HoleType.UNSAFE;
+ else if (block != Blocks.BEDROCK) return null;
+ }
+ return new Hole(pos.immutable(), type);
+ }
+
+ public List getHoles() {
+ return holes;
+ }
+
+ public boolean isHole(BlockPos pos) {
+ return holes.stream().anyMatch(hole -> hole.pos().equals(pos));
+ }
+
+ public record Hole(BlockPos pos, HoleType holeType) {
+ }
+
+ private enum HoleType {
+ BEDROCK,
+ UNSAFE
+ }
+}
diff --git a/src/main/java/me/alpha432/oyvey/manager/ModuleManager.java b/src/main/java/me/alpha432/oyvey/manager/ModuleManager.java
new file mode 100644
index 0000000..28c1bf2
--- /dev/null
+++ b/src/main/java/me/alpha432/oyvey/manager/ModuleManager.java
@@ -0,0 +1,165 @@
+package me.alpha432.oyvey.manager;
+
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import me.alpha432.oyvey.OyVey;
+import me.alpha432.oyvey.event.impl.render.Render2DEvent;
+import me.alpha432.oyvey.features.gui.OyVeyGui;
+import net.minecraft.client.gui.screens.TitleScreen;
+import net.minecraft.client.gui.screens.multiplayer.JoinMultiplayerScreen;
+import me.alpha432.oyvey.event.impl.render.Render3DEvent;
+import me.alpha432.oyvey.features.Feature;
+import me.alpha432.oyvey.features.commands.ModuleCommand;
+import me.alpha432.oyvey.features.modules.Module;
+import me.alpha432.oyvey.features.modules.client.ClickGuiModule;
+import me.alpha432.oyvey.features.modules.client.HudEditorModule;
+import me.alpha432.oyvey.features.modules.client.NotificationsModule;
+import me.alpha432.oyvey.features.modules.combat.KillAuraModule;
+import me.alpha432.oyvey.features.modules.combat.CriticalsModule;
+import me.alpha432.oyvey.features.modules.combat.KeyPearlModule;
+import me.alpha432.oyvey.features.modules.hud.CoordinatesHudModule;
+import me.alpha432.oyvey.features.modules.hud.WatermarkHudModule;
+import me.alpha432.oyvey.features.modules.misc.MCFModule;
+import me.alpha432.oyvey.features.modules.movement.FastAccelModule;
+import me.alpha432.oyvey.features.modules.movement.ReverseStepModule;
+import me.alpha432.oyvey.features.modules.movement.StepModule;
+import me.alpha432.oyvey.features.modules.player.FastPlaceModule;
+import me.alpha432.oyvey.features.modules.player.NoFallModule;
+import me.alpha432.oyvey.features.modules.player.VelocityModule;
+import me.alpha432.oyvey.features.modules.render.BlockHighlightModule;
+import me.alpha432.oyvey.util.traits.Jsonable;
+import me.alpha432.oyvey.util.traits.Util;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.*;
+import java.util.stream.Stream;
+
+public class ModuleManager implements Jsonable, Util {
+ private static final Logger LOGGER = LoggerFactory.getLogger("ModuleManager");
+
+ private final Map, Module> fastRegistry = new HashMap<>();
+ private final List modules = new ArrayList<>();
+
+ public void init() {
+ register(new WatermarkHudModule());
+ register(new CoordinatesHudModule());
+ register(new HudEditorModule());
+ register(new ClickGuiModule());
+ register(new NotificationsModule());
+ register(new CriticalsModule());
+ register(new MCFModule());
+ register(new StepModule());
+ register(new ReverseStepModule());
+ register(new FastPlaceModule());
+ register(new VelocityModule());
+ register(new BlockHighlightModule());
+ register(new NoFallModule());
+ register(new KeyPearlModule());
+ register(new KillAuraModule());
+ register(new FastAccelModule());
+
+ LOGGER.info("Registered {} modules", modules.size());
+
+ // Create a command for each module for modules to be configurable via command line
+ for (Module module : modules) {
+ OyVey.commandManager.register(new ModuleCommand(module));
+ }
+
+ OyVey.configManager.addConfig(this);
+ }
+
+ public void register(Module module) {
+ getModules().add(module);
+ fastRegistry.put(module.getClass(), module);
+ }
+
+ public List getModules() {
+ return modules;
+ }
+
+ public Stream stream() {
+ return getModules().stream();
+ }
+
+ @SuppressWarnings("unchecked")
+ public T getModuleByClass(Class clazz) {
+ return (T) fastRegistry.get(clazz);
+ }
+
+ public Module getModuleByName(String name) {
+ return stream().filter(m -> m.getName().equalsIgnoreCase(name)).findFirst().orElse(null);
+ }
+
+ public Module getModuleByDisplayName(String display) {
+ return stream().filter(m -> m.getDisplayName().equalsIgnoreCase(display)).findFirst().orElse(null);
+ }
+
+ public List getModulesByCategory(Module.Category category) {
+ return stream().filter(m -> m.getCategory() == category).toList();
+ }
+
+ public List getCategories() {
+ return Arrays.asList(Module.Category.values());
+ }
+
+ public void onLoad() {
+ getModules().forEach(Module::onLoad);
+ }
+
+ public void onTick() {
+ stream().filter(Feature::isEnabled).forEach(Module::onTick);
+ }
+
+ public void onRender2D(Render2DEvent event) {
+ stream().filter(Feature::isEnabled).forEach(module -> module.onRender2D(event));
+ }
+
+ public void onRender3D(Render3DEvent event) {
+ stream().filter(Feature::isEnabled).forEach(module -> module.onRender3D(event));
+ }
+
+ public void onUnload() {
+ getModules().forEach(EVENT_BUS::unregister);
+ getModules().forEach(Module::onUnload);
+ }
+
+ public void onKeyPressed(int key) {
+ if (key <= 0) return;
+ if (mc.screen instanceof TitleScreen || mc.screen instanceof JoinMultiplayerScreen) {
+ if (ClickGuiModule.getInstance().getBind().getKey() == key) {
+ OyVeyGui.open(mc.screen);
+ }
+ return;
+ }
+ if (mc.screen != null) return;
+ stream().filter(module -> module.getBind().getKey() == key).forEach(Module::toggle);
+ }
+
+ public void onMouseClicked(int button) {
+ if (mc.screen != null) return;
+ int key = -button - 2;
+ stream().filter(module -> module.getBind().getKey() == key).forEach(Module::toggle);
+ }
+
+ @Override
+ public JsonElement toJson() {
+ JsonObject object = new JsonObject();
+ for (Module module : getModules()) {
+ object.add(module.getName(), module.toJson());
+ }
+ return object;
+ }
+
+ @Override
+ public void fromJson(JsonElement element) {
+ for (Module module : getModules()) {
+ module.fromJson(element.getAsJsonObject().get(module.getName()));
+ }
+ }
+
+ @Override
+ public String getFileName() {
+ return "modules.json";
+ }
+}
diff --git a/src/main/java/me/alpha432/oyvey/manager/PositionManager.java b/src/main/java/me/alpha432/oyvey/manager/PositionManager.java
new file mode 100644
index 0000000..c041c4c
--- /dev/null
+++ b/src/main/java/me/alpha432/oyvey/manager/PositionManager.java
@@ -0,0 +1,92 @@
+package me.alpha432.oyvey.manager;
+
+import me.alpha432.oyvey.event.Stage;
+import me.alpha432.oyvey.event.impl.entity.player.UpdateWalkingPlayerEvent;
+import me.alpha432.oyvey.event.system.Subscribe;
+import me.alpha432.oyvey.features.Feature;
+import net.minecraft.network.protocol.game.ServerboundMovePlayerPacket;
+
+public class PositionManager
+ extends Feature {
+ private double x;
+ private double y;
+ private double z;
+ private boolean onground;
+ private double fallDistance;
+
+ public PositionManager() {
+ EVENT_BUS.register(this);
+ }
+
+ @Subscribe
+ public void onUpdateWalkingPlayer(UpdateWalkingPlayerEvent event) {
+ if (event.getStage() == Stage.POST) return;
+
+ double diff = mc.player.yo - mc.player.getY();
+ if (mc.player.onGround() || diff <= 0) {
+ fallDistance = 0;
+ } else {
+ fallDistance += diff;
+ }
+ }
+
+ public void updatePosition() {
+ this.x = mc.player.getX();
+ this.y = mc.player.getY();
+ this.z = mc.player.getZ();
+ this.onground = mc.player.onGround();
+ }
+
+ public void restorePosition() {
+ mc.player.setPos(x, y, z);
+ mc.player.setOnGround(onground);
+ }
+
+ public void setPlayerPosition(double x, double y, double z) {
+ mc.player.setPos(x, y, z);
+ }
+
+ public void setPlayerPosition(double x, double y, double z, boolean onground) {
+ mc.player.setPos(x, y, z);
+ mc.player.setOnGround(onground);
+ }
+
+ public void setPositionPacket(double x, double y, double z, boolean onGround, boolean setPos, boolean noLagBack) {
+ boolean bl = mc.player.horizontalCollision;
+ mc.player.connection.send(new ServerboundMovePlayerPacket.Pos(x, y, z, onGround, bl));
+ if (setPos) {
+ mc.player.setPos(x, y, z);
+ if (noLagBack) {
+ this.updatePosition();
+ }
+ }
+ }
+
+ public double getX() {
+ return this.x;
+ }
+
+ public void setX(double x) {
+ this.x = x;
+ }
+
+ public double getY() {
+ return this.y;
+ }
+
+ public void setY(double y) {
+ this.y = y;
+ }
+
+ public double getZ() {
+ return this.z;
+ }
+
+ public void setZ(double z) {
+ this.z = z;
+ }
+
+ public double getFallDistance() {
+ return fallDistance;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/me/alpha432/oyvey/manager/RotationManager.java b/src/main/java/me/alpha432/oyvey/manager/RotationManager.java
new file mode 100644
index 0000000..b9ab07a
--- /dev/null
+++ b/src/main/java/me/alpha432/oyvey/manager/RotationManager.java
@@ -0,0 +1,162 @@
+package me.alpha432.oyvey.manager;
+
+import me.alpha432.oyvey.event.Stage;
+import me.alpha432.oyvey.event.impl.entity.player.TickEvent;
+import me.alpha432.oyvey.event.impl.entity.player.TravelEvent;
+import me.alpha432.oyvey.event.impl.entity.player.UpdateWalkingPlayerEvent;
+import me.alpha432.oyvey.event.system.Subscribe;
+import me.alpha432.oyvey.util.models.Angles;
+import me.alpha432.oyvey.util.traits.Util;
+import net.minecraft.network.protocol.game.ServerboundMovePlayerPacket;
+import net.minecraft.util.Mth;
+
+import java.util.*;
+import java.util.function.Predicate;
+
+public class RotationManager implements Util {
+ private final List callbacks = new ArrayList<>();
+
+ private boolean updateRender;
+ private Angles renderSnapshot, renderSnapshot0;
+
+ private Angles snapshot;
+ private Angles travelSnapshot;
+
+ public RotationManager() {
+ EVENT_BUS.register(this);
+ }
+
+ @Subscribe(priority = 1000)
+ public void onTickPre(TickEvent.Pre event) {
+ executeCallbacks(callback -> callback.type() == Type.SYNC);
+ }
+
+ @Subscribe
+ public void onTravelPre(TravelEvent.Pre event) {
+ if (callbacks.isEmpty())
+ return;
+
+ Callback highest = callbacks.stream().max(Comparator.comparing(Callback::priority)).orElseThrow();
+ if (highest.type() == Type.SYNC) {
+ travelSnapshot = new Angles(mc.player.getYRot(), mc.player.getXRot());
+ mc.player.setYRot(highest.angles().yRot());
+ mc.player.setXRot(highest.angles().xRot());
+ }
+ }
+
+ @Subscribe
+ public void onTravelPost(TravelEvent.Post event) {
+ if (travelSnapshot != null) {
+ mc.player.setYRot(travelSnapshot.yRot());
+ mc.player.setXRot(travelSnapshot.xRot());
+ travelSnapshot = null;
+ }
+ }
+
+ @Subscribe
+ public void onUpdateWalking(UpdateWalkingPlayerEvent event) {
+ if (event.getStage() == Stage.PRE) {
+ if (callbacks.isEmpty()) {
+ updateRenderSnapshot(new Angles(mc.player.getYRot(), mc.player.getXRot()), false);
+ return;
+ }
+ Callback highest = callbacks.stream().max(Comparator.comparing(Callback::priority)).orElseThrow();
+
+ updateRenderSnapshot(highest.angles(), true);
+
+ snapshot = new Angles(mc.player.getYRot(), mc.player.getXRot());
+ mc.player.setYRot(highest.angles().yRot());
+ mc.player.setXRot(highest.angles().xRot());
+ } else if (snapshot != null) {
+ executeCallbacks(callback -> callback.type() == Type.MOTION);
+
+ mc.player.setYRot(snapshot.yRot());
+ mc.player.setXRot(snapshot.xRot());
+ snapshot = null;
+ }
+ }
+
+ public void sync(Angles angles, Runnable runnable) {
+ sync(angles, 0, runnable);
+ }
+
+ public void sync(Angles angles, int priority, Runnable runnable) {
+ request(Type.SYNC, angles, priority, runnable);
+ }
+
+ public void motion(Angles angles, Runnable runnable) {
+ motion(angles, 0, runnable);
+ }
+
+ public void motion(Angles angles, int priority, Runnable runnable) {
+ request(Type.MOTION, angles, priority, runnable);
+ }
+
+ public void silent(Angles angles, Runnable runnable) {
+ request(Type.SILENT, angles, 0, runnable);
+ }
+
+ public Angles getLerpRenderSnapshot(float lerp) {
+ if (!updateRender) return null;
+ return new Angles(
+ Mth.lerp(lerp, renderSnapshot0.yRot(), renderSnapshot.yRot()),
+ Mth.lerp(lerp, renderSnapshot0.xRot(), renderSnapshot.xRot())
+ );
+ }
+
+ public void sendDuplicate(float yaw, float pitch) {
+ mc.player.positionReminder = 20;
+ mc.player.yRotLast = yaw;
+ mc.player.xRotLast = pitch;
+ mc.player.connection.send(new ServerboundMovePlayerPacket.PosRot(
+ mc.player.getX(), mc.player.getY(), mc.player.getZ(),
+ yaw, pitch,
+ mc.player.onGround(), mc.player.horizontalCollision
+ ));
+ }
+
+ private void request(Type type, Angles angles, int priority, Runnable runnable) {
+ Callback callback = new Callback(type, angles, priority, runnable);
+ if (type == Type.SILENT) {
+ sendDuplicate(angles.yRot(), angles.xRot());
+ callback.execute();
+ } else {
+ callbacks.add(callback);
+ }
+ }
+
+ private void executeCallbacks(Predicate filter) {
+ callbacks.removeIf(callback -> {
+ if (filter.test(callback)) {
+ callback.execute();
+ return true;
+ }
+ return false;
+ });
+ }
+
+ private void updateRenderSnapshot(Angles angles, boolean update) {
+ this.renderSnapshot0 = this.renderSnapshot;
+ this.renderSnapshot = angles;
+ this.updateRender = update;
+
+ if (renderSnapshot0 == null || renderSnapshot == null)
+ return;
+
+ if (Mth.abs(renderSnapshot.yRot() - renderSnapshot0.yRot()) > 320) {
+ this.renderSnapshot0 = this.renderSnapshot;
+ }
+ }
+
+ record Callback(Type type, Angles angles, int priority, Runnable action) {
+ public void execute() {
+ if (action != null) action.run();
+ }
+ }
+
+ public enum Type {
+ MOTION,
+ SYNC,
+ SILENT
+ }
+}
diff --git a/src/main/java/me/alpha432/oyvey/manager/ServerManager.java b/src/main/java/me/alpha432/oyvey/manager/ServerManager.java
new file mode 100644
index 0000000..63a0a30
--- /dev/null
+++ b/src/main/java/me/alpha432/oyvey/manager/ServerManager.java
@@ -0,0 +1,88 @@
+package me.alpha432.oyvey.manager;
+
+import me.alpha432.oyvey.features.Feature;
+import me.alpha432.oyvey.util.models.Timer;
+
+import java.util.Arrays;
+
+public class ServerManager
+ extends Feature {
+ private final float[] tpsCounts = new float[10];
+ private final Timer timer = new Timer();
+ private float tps = 20.0f;
+ private long lastUpdate = -1L;
+ private String serverBrand = "";
+
+ public void onPacketReceived() {
+ this.timer.reset();
+ }
+
+ public void update() {
+ float tps;
+ long currentTime = System.currentTimeMillis();
+ if (this.lastUpdate == -1L) {
+ this.lastUpdate = currentTime;
+ return;
+ }
+ long timeDiff = currentTime - this.lastUpdate;
+ float tickTime = (float) timeDiff / 20.0f;
+ if (tickTime == 0.0f) {
+ tickTime = 50.0f;
+ }
+ if ((tps = 1000.0f / tickTime) > 20.0f) {
+ tps = 20.0f;
+ }
+ System.arraycopy(this.tpsCounts, 0, this.tpsCounts, 1, this.tpsCounts.length - 1);
+ this.tpsCounts[0] = tps;
+ double total = 0.0;
+ for (float f : this.tpsCounts) {
+ total += f;
+ }
+ if ((total /= this.tpsCounts.length) > 20.0) {
+ total = 20.0;
+ }
+ this.tps = (float) total;
+ this.lastUpdate = currentTime;
+ }
+
+ @Override
+ public void reset() {
+ Arrays.fill(this.tpsCounts, 20.0f);
+ this.tps = 20.0f;
+ }
+
+ public boolean isServerNotResponding() {
+ return this.timer.passedMs(2000);
+ }
+
+ public long serverRespondingTime() {
+ return this.timer.getPassedTimeMs();
+ }
+
+ public float getTpsFactor() {
+ return 20.0f / this.tps;
+ }
+
+ public float getTps() {
+ return this.tps;
+ }
+
+ public String getServerBrand() {
+ return this.serverBrand;
+ }
+
+ public void setServerBrand(String brand) {
+ this.serverBrand = brand;
+ }
+
+ public int getPing() {
+ if (ServerManager.nullCheck()) {
+ return 0;
+ }
+ try {
+ return mc.getConnection().getPlayerInfo(mc.player.getGameProfile().name()).getLatency();
+ } catch (Throwable e) {
+ return 0;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/me/alpha432/oyvey/manager/SpeedManager.java b/src/main/java/me/alpha432/oyvey/manager/SpeedManager.java
new file mode 100644
index 0000000..de7094e
--- /dev/null
+++ b/src/main/java/me/alpha432/oyvey/manager/SpeedManager.java
@@ -0,0 +1,47 @@
+package me.alpha432.oyvey.manager;
+
+import me.alpha432.oyvey.features.Feature;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.player.Player;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class SpeedManager extends Feature {
+ private static final int SPEED_NORMALIZATION = 20;
+
+ private final List localSpeed = new ArrayList<>();
+ private float localSpeedNormal = 0f;
+
+ public void update() {
+ localSpeed.add(getCurrentSpeed(mc.player));
+ while (localSpeed.size() > SPEED_NORMALIZATION) localSpeed.removeFirst();
+
+ localSpeedNormal = localSpeed.stream().reduce(0f, Float::sum) / localSpeed.size();
+ }
+
+ public double getSpeed(Player player) {
+ if (mc.player == player) return localSpeedNormal;
+ return getCurrentSpeed(player);
+ }
+
+ public double getSpeedBpS(Player player) {
+ return getSpeed(player) * 20;
+ }
+
+ public double getSpeedKmH(Player player) {
+ return getSpeedBpS(player) * 3.6;
+ }
+
+ public float getCurrentSpeed(Entity entity) {
+ Entity vehicle = entity.getVehicle();
+ double distTraveledX = entity.getX() - entity.xo;
+ double distTraveledZ = entity.getZ() - entity.zo;
+ if (vehicle != null) {
+ distTraveledX = vehicle.getX() - vehicle.xo;
+ distTraveledZ = vehicle.getZ() - vehicle.zo;
+ }
+
+ return (float) Math.hypot(distTraveledX, distTraveledZ);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/me/alpha432/oyvey/mixin/entity/MixinAbstractClientPlayer.java b/src/main/java/me/alpha432/oyvey/mixin/entity/MixinAbstractClientPlayer.java
new file mode 100644
index 0000000..9c9bc56
--- /dev/null
+++ b/src/main/java/me/alpha432/oyvey/mixin/entity/MixinAbstractClientPlayer.java
@@ -0,0 +1,28 @@
+package me.alpha432.oyvey.mixin.entity;
+
+import me.alpha432.oyvey.event.impl.entity.player.TravelEvent;
+import net.minecraft.client.player.AbstractClientPlayer;
+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 static me.alpha432.oyvey.util.traits.Util.EVENT_BUS;
+import static me.alpha432.oyvey.util.traits.Util.isLocalPlayer;
+
+@Mixin(AbstractClientPlayer.class)
+public class MixinAbstractClientPlayer {
+ @Inject(method = "tick", at = @At("HEAD"))
+ private void travelPreHook(CallbackInfo ci) {
+ if (isLocalPlayer(this)) {
+ EVENT_BUS.post(new TravelEvent.Pre());
+ }
+ }
+
+ @Inject(method = "tick", at = @At("TAIL"))
+ private void travelPostHook(CallbackInfo ci) {
+ if (isLocalPlayer(this)) {
+ EVENT_BUS.post(new TravelEvent.Post());
+ }
+ }
+}
diff --git a/src/main/java/me/alpha432/oyvey/mixin/entity/MixinClientPlayerEntity.java b/src/main/java/me/alpha432/oyvey/mixin/entity/MixinClientPlayerEntity.java
new file mode 100644
index 0000000..8a0cdcb
--- /dev/null
+++ b/src/main/java/me/alpha432/oyvey/mixin/entity/MixinClientPlayerEntity.java
@@ -0,0 +1,48 @@
+package me.alpha432.oyvey.mixin.entity;
+
+import me.alpha432.oyvey.event.Stage;
+import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod;
+import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
+import me.alpha432.oyvey.event.impl.entity.player.MoveEvent;
+import me.alpha432.oyvey.event.impl.entity.player.TickEvent;
+import me.alpha432.oyvey.event.impl.entity.player.UpdateWalkingPlayerEvent;
+import net.minecraft.client.player.LocalPlayer;
+import net.minecraft.world.entity.MoverType;
+import net.minecraft.world.phys.Vec3;
+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 static me.alpha432.oyvey.util.traits.Util.EVENT_BUS;
+
+@Mixin(LocalPlayer.class)
+public class MixinClientPlayerEntity {
+ @Inject(method = "tick", at = @At("HEAD"))
+ private void tickPreHook(CallbackInfo ci) {
+ EVENT_BUS.post(new TickEvent.Pre());
+ }
+
+ @Inject(method = "tick", at = @At("TAIL"))
+ private void tickHook(CallbackInfo ci) {
+ EVENT_BUS.post(new TickEvent.Post());
+ }
+
+ @Inject(method = "tick", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/player/AbstractClientPlayer;tick()V", shift = At.Shift.AFTER))
+ private void tickHook2(CallbackInfo ci) {
+ EVENT_BUS.post(new UpdateWalkingPlayerEvent(Stage.PRE));
+ }
+
+ @Inject(method = "tick", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/player/LocalPlayer;sendPosition()V", shift = At.Shift.AFTER))
+ private void tickHook3(CallbackInfo ci) {
+ EVENT_BUS.post(new UpdateWalkingPlayerEvent(Stage.POST));
+ }
+
+ @WrapMethod(method = "move")
+ private void moveHook(MoverType type, Vec3 movement, Operation original) {
+ MoveEvent event = new MoveEvent(type, movement);
+ EVENT_BUS.post(event);
+ if (event.isCancelled()) return;
+ original.call(type, event.getMovement());
+ }
+}
diff --git a/src/main/java/me/alpha432/oyvey/mixin/input/MixinKeyboardHandler.java b/src/main/java/me/alpha432/oyvey/mixin/input/MixinKeyboardHandler.java
new file mode 100644
index 0000000..930e3a4
--- /dev/null
+++ b/src/main/java/me/alpha432/oyvey/mixin/input/MixinKeyboardHandler.java
@@ -0,0 +1,21 @@
+package me.alpha432.oyvey.mixin.input;
+
+import me.alpha432.oyvey.event.impl.input.KeyInputEvent;
+import net.minecraft.client.KeyboardHandler;
+import net.minecraft.client.input.KeyEvent;
+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 static me.alpha432.oyvey.util.traits.Util.EVENT_BUS;
+
+@Mixin(KeyboardHandler.class)
+public class MixinKeyboardHandler {
+ @Inject(method = "keyPress", at = @At("TAIL"))
+ private void keyPress(long l, int i, KeyEvent keyEvent, CallbackInfo ci) {
+ if (i != 1) return;
+
+ EVENT_BUS.post(new KeyInputEvent(keyEvent.key()));
+ }
+}
diff --git a/src/main/java/me/alpha432/oyvey/mixin/input/MixinMouseHandler.java b/src/main/java/me/alpha432/oyvey/mixin/input/MixinMouseHandler.java
new file mode 100644
index 0000000..a92cc48
--- /dev/null
+++ b/src/main/java/me/alpha432/oyvey/mixin/input/MixinMouseHandler.java
@@ -0,0 +1,22 @@
+package me.alpha432.oyvey.mixin.input;
+
+import me.alpha432.oyvey.event.impl.input.MouseInputEvent;
+import net.minecraft.client.MouseHandler;
+import net.minecraft.client.input.MouseButtonInfo;
+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 static me.alpha432.oyvey.util.traits.Util.EVENT_BUS;
+
+@Mixin(MouseHandler.class)
+public class MixinMouseHandler {
+ @Inject(method = "onButton", at = @At("HEAD"), cancellable = true)
+ private void onButton(long window, MouseButtonInfo input, int action, CallbackInfo ci) {
+ if (EVENT_BUS.post(new MouseInputEvent(input.button(), action))) {
+ ci.cancel();
+ }
+ }
+}
+
diff --git a/src/main/java/me/alpha432/oyvey/mixin/network/MixinClientPacketListener.java b/src/main/java/me/alpha432/oyvey/mixin/network/MixinClientPacketListener.java
new file mode 100644
index 0000000..26b357a
--- /dev/null
+++ b/src/main/java/me/alpha432/oyvey/mixin/network/MixinClientPacketListener.java
@@ -0,0 +1,20 @@
+package me.alpha432.oyvey.mixin.network;
+
+import me.alpha432.oyvey.event.impl.network.ChatEvent;
+import net.minecraft.client.multiplayer.ClientPacketListener;
+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 static me.alpha432.oyvey.util.traits.Util.EVENT_BUS;
+
+@Mixin(ClientPacketListener.class)
+public class MixinClientPacketListener {
+ @Inject(method = "sendChat", at = @At("HEAD"), cancellable = true)
+ private void sendChat(String content, CallbackInfo ci) {
+ if (EVENT_BUS.post(new ChatEvent(content))) {
+ ci.cancel();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/me/alpha432/oyvey/mixin/network/MixinConnection.java b/src/main/java/me/alpha432/oyvey/mixin/network/MixinConnection.java
new file mode 100644
index 0000000..68f0647
--- /dev/null
+++ b/src/main/java/me/alpha432/oyvey/mixin/network/MixinConnection.java
@@ -0,0 +1,59 @@
+package me.alpha432.oyvey.mixin.network;
+
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelFutureListener;
+import io.netty.channel.ChannelHandlerContext;
+import me.alpha432.oyvey.event.impl.network.PacketEvent;
+import net.minecraft.network.Connection;
+import net.minecraft.network.protocol.Packet;
+import net.minecraft.network.protocol.PacketFlow;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.spongepowered.asm.mixin.Final;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.Shadow;
+import org.spongepowered.asm.mixin.Unique;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Inject;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
+
+import static me.alpha432.oyvey.util.traits.Util.EVENT_BUS;
+
+@Mixin(Connection.class)
+public class MixinConnection {
+ @Unique
+ private static final Logger LOGGER = LoggerFactory.getLogger("PacketEvent");
+
+ @Shadow
+ private Channel channel;
+ @Shadow
+ @Final
+ private PacketFlow receiving;
+
+ @Inject(method = "channelRead0*", at = @At("HEAD"), cancellable = true)
+ public void channelRead0(ChannelHandlerContext chc, Packet> packet, CallbackInfo ci) {
+ if (this.channel.isOpen() && packet != null) {
+ try {
+ if (EVENT_BUS.post(new PacketEvent.Receive(packet))) {
+ ci.cancel();
+ }
+ } catch (Exception e) {
+ LOGGER.error("Failed to post packet event for {}", packet.getClass().getSimpleName(), e);
+ }
+ }
+ }
+
+ @Inject(method = "sendPacket", at = @At("HEAD"), cancellable = true)
+ private void sendPacket(Packet> packet, ChannelFutureListener callbacks, boolean flush, CallbackInfo ci) {
+ if (this.receiving != PacketFlow.CLIENTBOUND) return;
+ try {
+ if (EVENT_BUS.post(new PacketEvent.Send(packet))) {
+ ci.cancel();
+ }
+ } catch (Exception e) {
+ LOGGER.error("Failed to post packet event for {}", packet.getClass().getSimpleName(), e);
+ }
+ }
+
+
+}
\ No newline at end of file
diff --git a/src/main/java/me/alpha432/oyvey/mixin/render/MixinAvatarRenderer.java b/src/main/java/me/alpha432/oyvey/mixin/render/MixinAvatarRenderer.java
new file mode 100644
index 0000000..59d2ee3
--- /dev/null
+++ b/src/main/java/me/alpha432/oyvey/mixin/render/MixinAvatarRenderer.java
@@ -0,0 +1,26 @@
+package me.alpha432.oyvey.mixin.render;
+
+import me.alpha432.oyvey.OyVey;
+import me.alpha432.oyvey.util.models.Angles;
+import net.minecraft.client.renderer.entity.player.AvatarRenderer;
+import net.minecraft.client.renderer.entity.state.AvatarRenderState;
+import net.minecraft.world.entity.Avatar;
+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 static me.alpha432.oyvey.util.traits.Util.mc;
+
+@Mixin(AvatarRenderer.class)
+public class MixinAvatarRenderer {
+ @Inject(method = "extractRenderState(Lnet/minecraft/world/entity/Avatar;Lnet/minecraft/client/renderer/entity/state/AvatarRenderState;F)V", at = @At("TAIL"))
+ private void extractRenderStateHook(T avatar, AvatarRenderState state, float f, CallbackInfo ci) {
+ if (mc.player != avatar) return;
+ Angles lerped = OyVey.rotationManager.getLerpRenderSnapshot(f);
+ if (lerped == null) return;
+ state.xRot = lerped.xRot();
+ state.yRot = 0;
+ state.bodyRot = lerped.yRot();
+ }
+}
diff --git a/src/main/java/me/alpha432/oyvey/mixin/render/MixinChatComponent.java b/src/main/java/me/alpha432/oyvey/mixin/render/MixinChatComponent.java
new file mode 100644
index 0000000..5e1fcb8
--- /dev/null
+++ b/src/main/java/me/alpha432/oyvey/mixin/render/MixinChatComponent.java
@@ -0,0 +1,32 @@
+package me.alpha432.oyvey.mixin.render;
+
+import me.alpha432.oyvey.ducks.render.IChatComponent;
+import net.minecraft.client.GuiMessage;
+import net.minecraft.client.gui.components.ChatComponent;
+import net.minecraft.network.chat.MessageSignature;
+import org.spongepowered.asm.mixin.Final;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.Shadow;
+
+import java.util.List;
+
+@Mixin(ChatComponent.class)
+public abstract class MixinChatComponent implements IChatComponent {
+ @Final @Shadow
+ private List allMessages;
+
+ @Shadow
+ private void refreshTrimmedMessages() {}
+
+ @Override
+ public void oyvey$addMessage(GuiMessage message) {
+ this.allMessages.addFirst(message);
+ this.refreshTrimmedMessages();
+ }
+
+ @Override
+ public void oyvey$removeMessage(MessageSignature signature) {
+ this.allMessages.removeIf(message -> signature.equals(message.signature()));
+ this.refreshTrimmedMessages();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/me/alpha432/oyvey/mixin/render/MixinDeltaTrackerTimer.java b/src/main/java/me/alpha432/oyvey/mixin/render/MixinDeltaTrackerTimer.java
new file mode 100644
index 0000000..92e85cd
--- /dev/null
+++ b/src/main/java/me/alpha432/oyvey/mixin/render/MixinDeltaTrackerTimer.java
@@ -0,0 +1,21 @@
+package me.alpha432.oyvey.mixin.render;
+
+import me.alpha432.oyvey.OyVey;
+import net.minecraft.client.DeltaTracker;
+import org.objectweb.asm.Opcodes;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.Shadow;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Inject;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
+
+@Mixin(DeltaTracker.Timer.class)
+public class MixinDeltaTrackerTimer {
+ @Shadow
+ private float deltaTicks;
+
+ @Inject(method = "advanceGameTime(J)I", at = @At(value = "FIELD", target = "Lnet/minecraft/client/DeltaTracker$Timer;lastMs:J", opcode = Opcodes.PUTFIELD))
+ public void advanceGameTime(long timeMillis, CallbackInfoReturnable cir) {
+ this.deltaTicks *= OyVey.TIMER;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/me/alpha432/oyvey/mixin/render/MixinLevelRenderer.java b/src/main/java/me/alpha432/oyvey/mixin/render/MixinLevelRenderer.java
new file mode 100644
index 0000000..6558184
--- /dev/null
+++ b/src/main/java/me/alpha432/oyvey/mixin/render/MixinLevelRenderer.java
@@ -0,0 +1,53 @@
+package me.alpha432.oyvey.mixin.render;
+
+import com.llamalad7.mixinextras.sugar.Local;
+import com.mojang.blaze3d.buffers.GpuBufferSlice;
+import com.mojang.blaze3d.resource.GraphicsResourceAllocator;
+import com.mojang.blaze3d.vertex.PoseStack;
+import com.mojang.math.Axis;
+import me.alpha432.oyvey.event.impl.render.Render3DEvent;
+import me.alpha432.oyvey.event.impl.render.RenderBlockOutlineEvent;
+import net.minecraft.client.Camera;
+import net.minecraft.client.DeltaTracker;
+import net.minecraft.client.renderer.LevelRenderer;
+import net.minecraft.client.renderer.MultiBufferSource;
+import net.minecraft.client.renderer.state.LevelRenderState;
+import net.minecraft.util.profiling.ProfilerFiller;
+import org.joml.Matrix4f;
+import org.joml.Vector4f;
+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 static me.alpha432.oyvey.util.traits.Util.EVENT_BUS;
+import static me.alpha432.oyvey.util.traits.Util.mc;
+
+@Mixin(LevelRenderer.class)
+public class MixinLevelRenderer {
+ @Inject(method = "renderBlockOutline", at = @At("HEAD"), cancellable = true)
+ public void renderBlockOutline(MultiBufferSource.BufferSource bufferSource, PoseStack poseStack, boolean bl, LevelRenderState levelRenderState, CallbackInfo ci) {
+ if (EVENT_BUS.post(new RenderBlockOutlineEvent())) {
+ ci.cancel();
+ }
+ }
+
+ @Inject(method = "renderLevel", at = @At("RETURN"))
+ private void render(GraphicsResourceAllocator allocator, DeltaTracker tickCounter, boolean renderBlockOutline,
+ Camera camera, Matrix4f positionMatrix, Matrix4f matrix4f, Matrix4f projectionMatrix,
+ GpuBufferSlice fogBuffer, Vector4f fogColor, boolean renderSky, CallbackInfo ci, @Local ProfilerFiller profiler) {
+
+ PoseStack stack = new PoseStack();
+ stack.pushPose();
+ stack.mulPose(Axis.XP.rotationDegrees(mc.gameRenderer.getMainCamera().xRot()));
+ stack.mulPose(Axis.YP.rotationDegrees(mc.gameRenderer.getMainCamera().yRot() + 180f));
+
+ profiler.push("oyvey-render-3d");
+
+ Render3DEvent event = new Render3DEvent(stack, tickCounter.getGameTimeDeltaPartialTick(true));
+ EVENT_BUS.post(event);
+ stack.popPose();
+ profiler.pop();
+ }
+
+}
diff --git a/src/main/java/me/alpha432/oyvey/mixin/render/gui/MixinCommandSuggestions.java b/src/main/java/me/alpha432/oyvey/mixin/render/gui/MixinCommandSuggestions.java
new file mode 100644
index 0000000..cace0c0
--- /dev/null
+++ b/src/main/java/me/alpha432/oyvey/mixin/render/gui/MixinCommandSuggestions.java
@@ -0,0 +1,61 @@
+package me.alpha432.oyvey.mixin.render.gui;
+
+import com.llamalad7.mixinextras.sugar.Local;
+import com.mojang.brigadier.CommandDispatcher;
+import com.mojang.brigadier.ParseResults;
+import com.mojang.brigadier.StringReader;
+import com.mojang.brigadier.suggestion.Suggestions;
+import me.alpha432.oyvey.OyVey;
+import me.alpha432.oyvey.manager.CommandManager;
+import net.minecraft.client.gui.components.CommandSuggestions;
+import net.minecraft.client.gui.components.EditBox;
+import net.minecraft.client.multiplayer.ClientSuggestionProvider;
+import org.jspecify.annotations.Nullable;
+import org.spongepowered.asm.mixin.Final;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.Shadow;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Inject;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
+
+import java.util.concurrent.CompletableFuture;
+
+@Mixin(CommandSuggestions.class)
+public abstract class MixinCommandSuggestions {
+ @Shadow
+ private CommandSuggestions.SuggestionsList suggestions;
+ @Shadow
+ private @Nullable CompletableFuture pendingSuggestions;
+ @Shadow
+ private @Nullable ParseResults currentParse;
+ @Shadow
+ boolean keepSuggestions;
+ @Shadow
+ @Final
+ EditBox input;
+
+ @Shadow
+ protected abstract void updateUsageInfo();
+
+ @SuppressWarnings("unchecked")
+ @Inject(method = "updateCommandInfo", at = @At("RETURN"))
+ public void updateCommandInfo(CallbackInfo ci, @Local StringReader stringReader) {
+ String prefix = OyVey.commandManager.getCommandPrefix();
+ if (!stringReader.canRead() || !stringReader.getString().startsWith(prefix)) {
+ return;
+ }
+ stringReader.setCursor(prefix.length());
+
+ CommandDispatcher dispatcher = OyVey.commandManager.getDispatcher();
+ ParseResults parse = dispatcher.parse(stringReader, OyVey.commandManager);
+ currentParse = (ParseResults) (Object) parse;
+ if (suggestions == null || !keepSuggestions) {
+ pendingSuggestions = dispatcher.getCompletionSuggestions(parse, input.getCursorPosition());
+ pendingSuggestions.thenRun(() -> {
+ if (pendingSuggestions.isDone()) {
+ updateUsageInfo();
+ }
+ });
+ }
+ }
+}
diff --git a/src/main/java/me/alpha432/oyvey/mixin/render/gui/MixinGui.java b/src/main/java/me/alpha432/oyvey/mixin/render/gui/MixinGui.java
new file mode 100644
index 0000000..44aadc8
--- /dev/null
+++ b/src/main/java/me/alpha432/oyvey/mixin/render/gui/MixinGui.java
@@ -0,0 +1,29 @@
+package me.alpha432.oyvey.mixin.render.gui;
+
+import me.alpha432.oyvey.event.impl.render.Render2DEvent;
+import net.minecraft.client.DeltaTracker;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.gui.Gui;
+import net.minecraft.client.gui.GuiGraphics;
+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 static me.alpha432.oyvey.util.traits.Util.EVENT_BUS;
+import static me.alpha432.oyvey.util.traits.Util.mc;
+
+@Mixin(Gui.class)
+public class MixinGui {
+
+ @Inject(method = "render", at = @At("RETURN"))
+ public void render(GuiGraphics context, DeltaTracker tickCounter, CallbackInfo ci) {
+ boolean debugOpen = mc.debugEntries.isOverlayVisible();
+
+ if (debugOpen) return;
+
+ Render2DEvent event = new Render2DEvent(context, tickCounter.getGameTimeDeltaPartialTick(true));
+ EVENT_BUS.post(event);
+ }
+
+}
diff --git a/src/main/java/me/alpha432/oyvey/util/ColorUtil.java b/src/main/java/me/alpha432/oyvey/util/ColorUtil.java
new file mode 100644
index 0000000..29c4870
--- /dev/null
+++ b/src/main/java/me/alpha432/oyvey/util/ColorUtil.java
@@ -0,0 +1,12 @@
+package me.alpha432.oyvey.util;
+
+import me.alpha432.oyvey.features.modules.client.ClickGuiModule;
+
+import java.awt.*;
+
+public class ColorUtil {
+ public static Color rainbow(int delay) {
+ double rainbowState = Math.ceil((double) (System.currentTimeMillis() + (long) delay) / 20.0);
+ return Color.getHSBColor((float) ((rainbowState % 360.0) / 360.0), ClickGuiModule.getInstance().rainbowSaturation.getValue() / 255.0f, ClickGuiModule.getInstance().rainbowBrightness.getValue() / 255.0f);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/me/alpha432/oyvey/util/EnchantmentUtil.java b/src/main/java/me/alpha432/oyvey/util/EnchantmentUtil.java
new file mode 100644
index 0000000..d453332
--- /dev/null
+++ b/src/main/java/me/alpha432/oyvey/util/EnchantmentUtil.java
@@ -0,0 +1,44 @@
+package me.alpha432.oyvey.util;
+
+import it.unimi.dsi.fastutil.objects.Object2IntMap;
+import me.alpha432.oyvey.util.traits.Util;
+import net.minecraft.core.Holder;
+import net.minecraft.resources.ResourceKey;
+import net.minecraft.world.entity.EquipmentSlot;
+import net.minecraft.world.entity.LivingEntity;
+import net.minecraft.world.item.ItemStack;
+import net.minecraft.world.item.enchantment.Enchantment;
+
+public final class EnchantmentUtil implements Util {
+ private EnchantmentUtil() {
+ throw new IllegalArgumentException();
+ }
+
+ public static int getLevel(ResourceKey key, ItemStack stack) {
+ if (stack.isEmpty()) return 0;
+ for (Object2IntMap.Entry> enchantment : stack.getEnchantments().entrySet()) {
+ if (enchantment.getKey().is(key)) return enchantment.getIntValue();
+ }
+ return 0;
+ }
+
+ public static int getLevel(ResourceKey key, EquipmentSlot slot, LivingEntity entity) {
+ return getLevel(key, entity.getItemBySlot(slot));
+ }
+
+ public static int getLevel(ResourceKey key, EquipmentSlot slot) {
+ return getLevel(key, slot, mc.player);
+ }
+
+ public static boolean has(ResourceKey key, ItemStack stack) {
+ return getLevel(key, stack) > 0;
+ }
+
+ public static boolean has(ResourceKey key, EquipmentSlot slot, LivingEntity entity) {
+ return getLevel(key, slot, entity) > 0;
+ }
+
+ public static boolean has(ResourceKey key, EquipmentSlot slot) {
+ return getLevel(key, slot) > 0;
+ }
+}
diff --git a/src/main/java/me/alpha432/oyvey/util/InteractionUtil.java b/src/main/java/me/alpha432/oyvey/util/InteractionUtil.java
new file mode 100644
index 0000000..2ab1cde
--- /dev/null
+++ b/src/main/java/me/alpha432/oyvey/util/InteractionUtil.java
@@ -0,0 +1,167 @@
+package me.alpha432.oyvey.util;
+
+import me.alpha432.oyvey.util.traits.Util;
+import net.minecraft.core.BlockPos;
+import net.minecraft.core.Direction;
+import net.minecraft.network.protocol.game.ServerboundPlayerActionPacket;
+import net.minecraft.network.protocol.game.ServerboundSwingPacket;
+import net.minecraft.tags.FluidTags;
+import net.minecraft.world.InteractionHand;
+import net.minecraft.world.InteractionResult;
+import net.minecraft.world.effect.MobEffectUtil;
+import net.minecraft.world.effect.MobEffects;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EquipmentSlot;
+import net.minecraft.world.entity.ExperienceOrb;
+import net.minecraft.world.entity.item.ItemEntity;
+import net.minecraft.world.entity.player.Player;
+import net.minecraft.world.entity.projectile.throwableitemprojectile.ThrownExperienceBottle;
+import net.minecraft.world.item.ItemStack;
+import net.minecraft.world.item.enchantment.Enchantments;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.phys.AABB;
+import net.minecraft.world.phys.BlockHitResult;
+import net.minecraft.world.phys.Vec3;
+import net.minecraft.world.phys.shapes.Shapes;
+
+public class InteractionUtil implements Util {
+ public static boolean canBreak(BlockPos blockPos, BlockState state) {
+ if (!mc.player.isCreative() && state.getDestroySpeed(mc.level, blockPos) < 0) return false;
+ return state.getShape(mc.level, blockPos) != Shapes.empty();
+ }
+
+ public static boolean isPlaceable(BlockPos pos, boolean entityCheck) {
+ if (!mc.level.getBlockState(pos).canBeReplaced()) return false;
+ for (Entity e : mc.level.getEntitiesOfClass(Entity.class, new AABB(pos), e -> !(e instanceof ThrownExperienceBottle || e instanceof ItemEntity || e instanceof ExperienceOrb))) {
+ if (e instanceof Player) return false;
+ return !entityCheck;
+ }
+ return true;
+ }
+
+ public static boolean breakBlock(BlockPos pos) {
+ if (!canBreak(pos, mc.level.getBlockState(pos))) return false;
+ BlockPos bp = pos instanceof BlockPos.MutableBlockPos ? new BlockPos(pos) : pos;
+
+ mc.getConnection().send(new ServerboundPlayerActionPacket(ServerboundPlayerActionPacket.Action.START_DESTROY_BLOCK, bp, Direction.UP));
+ mc.player.swing(InteractionHand.MAIN_HAND);
+ mc.getConnection().send(new ServerboundPlayerActionPacket(ServerboundPlayerActionPacket.Action.STOP_DESTROY_BLOCK, bp, Direction.UP));
+
+ mc.getConnection().send(new ServerboundSwingPacket(InteractionHand.MAIN_HAND));
+
+ return true;
+ }
+
+ public static void useItem(BlockPos pos) {
+ useItem(pos, InteractionHand.MAIN_HAND);
+ }
+
+ public static void useItem(BlockPos pos, InteractionHand hand) {
+ if (mc.level == null || mc.player == null || mc.gameMode == null) return;
+ Direction direction = mc.hitResult != null ? ((BlockHitResult) mc.hitResult).getDirection() : Direction.DOWN;
+ InteractionResult result = mc.gameMode.useItemOn(mc.player, hand, new BlockHitResult(
+ Vec3.atCenterOf(pos), direction, pos, false
+ ));
+ if (result instanceof InteractionResult.Success success && success.swingSource() != InteractionResult.SwingSource.NONE) {
+ mc.player.connection.send(new ServerboundSwingPacket(hand));
+ }
+ }
+
+ public static boolean place(BlockPos pos, boolean airPlace) {
+ return place(pos, airPlace, InteractionHand.MAIN_HAND);
+ }
+
+ public static boolean place(BlockPos pos, boolean airPlace, InteractionHand hand) {
+ if (mc.level == null || mc.player == null || mc.gameMode == null) return false;
+ if (!isPlaceable(pos, false)) return false;
+ Direction direction = calcSide(pos);
+ if (direction == null) {
+ if (airPlace)
+ direction = mc.hitResult != null ? ((BlockHitResult) mc.hitResult).getDirection() : Direction.DOWN;
+ else return false;
+ }
+ BlockPos bp = airPlace ? pos : pos.relative(direction);
+ InteractionResult result = mc.gameMode.useItemOn(mc.player, hand, new BlockHitResult(
+ airPlace ? Vec3.atCenterOf(pos) : Vec3.atCenterOf(bp).relative(direction.getOpposite(), 0.5),
+ airPlace ? direction : direction.getOpposite(), bp, false
+ ));
+ if (result instanceof InteractionResult.Success success && success.swingSource() != InteractionResult.SwingSource.NONE) {
+ mc.player.connection.send(new ServerboundSwingPacket(hand));
+ }
+ return true;
+ }
+
+ public static Direction calcSide(BlockPos pos) {
+ for (Direction d : Direction.values())
+ if (!mc.level.getBlockState(pos.offset(d.getUnitVec3i())).canBeReplaced()) return d;
+ return null;
+ }
+
+ public static double getBlockBreakingSpeed(int slot, BlockPos pos) {
+ return getBlockBreakingSpeed(slot, mc.level.getBlockState(pos));
+ }
+
+ public static double getBlockBreakingSpeed(int slot, BlockState block) {
+ double speed = mc.player.getInventory().getNonEquipmentItems().get(slot).getDestroySpeed(block);
+
+ if (speed > 1) {
+ ItemStack tool = mc.player.getInventory().getItem(slot);
+
+ int efficiency = EnchantmentUtil.getLevel(Enchantments.EFFICIENCY, tool);
+
+ if (efficiency > 0 && !tool.isEmpty()) speed += efficiency * efficiency + 1;
+ }
+
+ if (MobEffectUtil.hasDigSpeed(mc.player)) {
+ speed *= 1 + (MobEffectUtil.getDigSpeedAmplification(mc.player) + 1) * 0.2F;
+ }
+
+ if (mc.player.hasEffect(MobEffects.MINING_FATIGUE)) {
+ float k = switch (mc.player.getEffect(MobEffects.MINING_FATIGUE).getAmplifier()) {
+ case 0 -> 0.3F;
+ case 1 -> 0.09F;
+ case 2 -> 0.0027F;
+ default -> 8.1E-4F;
+ };
+
+ speed *= k;
+ }
+
+ if (mc.player.isEyeInFluid(FluidTags.WATER) && EnchantmentUtil.has(Enchantments.AQUA_AFFINITY, EquipmentSlot.HEAD)) {
+ speed /= 5.0F;
+ }
+
+ if (!mc.player.onGround()) {
+ speed /= 5.0F;
+ }
+
+ float hardness = block.getDestroySpeed(null, null);
+ if (hardness == -1) return 0;
+
+ speed /= hardness / (!block.requiresCorrectToolForDrops() || mc.player.getInventory().getNonEquipmentItems().get(slot).isCorrectToolForDrops(block) ? 30 : 100);
+
+ float ticks = (float) (Math.floor(1.0f / speed) + 1.0f);
+
+ return (long) ((ticks / 20.0f) * 1000);
+ }
+
+ public static Direction right(Direction direction) {
+ return switch (direction) {
+ case EAST -> Direction.SOUTH;
+ case SOUTH -> Direction.WEST;
+ case WEST -> Direction.NORTH;
+ case NORTH -> Direction.EAST;
+ default -> throw new IllegalStateException("Unexpected value: " + direction);
+ };
+ }
+
+ public static Direction left(Direction direction) {
+ return switch (direction) {
+ case EAST -> Direction.NORTH;
+ case NORTH -> Direction.WEST;
+ case WEST -> Direction.SOUTH;
+ case SOUTH -> Direction.EAST;
+ default -> throw new IllegalStateException("Unexpected value: " + direction);
+ };
+ }
+}
diff --git a/src/main/java/me/alpha432/oyvey/util/KeyboardUtil.java b/src/main/java/me/alpha432/oyvey/util/KeyboardUtil.java
new file mode 100644
index 0000000..989abd9
--- /dev/null
+++ b/src/main/java/me/alpha432/oyvey/util/KeyboardUtil.java
@@ -0,0 +1,59 @@
+package me.alpha432.oyvey.util;
+
+import com.mojang.blaze3d.platform.InputConstants;
+import me.alpha432.oyvey.features.settings.Bind;
+import net.minecraft.client.input.KeyEvent;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+
+public class KeyboardUtil {
+ public static String getKeyName(int key, boolean mouse) {
+ return getKeyName(mouse ? Bind.mouse(key) : Bind.keyboard(key));
+ }
+
+ public static String getKeyName(Bind bind) {
+ if (bind.isEmpty()) return "NONE";
+ if (bind.isMouse()) return "MOUSE" + (-bind.getKey() - 1);
+ return getKeyName(InputConstants.getKey(new KeyEvent(bind.getKey(), 0, 0)));
+ }
+
+ public static String getKeyName(InputConstants.Key key) {
+ return key.getName()
+ .replace("key.mouse.", "")
+ .replace("key.keyboard.", "")
+ .replace(".", "_")
+ .toUpperCase(Locale.ROOT);
+ }
+
+ public static Bind getBind(String name) {
+ String lowercaseName = name.toLowerCase(Locale.ROOT).replace("_", ".");
+ if (lowercaseName.startsWith("mouse")) {
+ try {
+ int button = Integer.parseInt(lowercaseName.substring(5));
+ if (button < 1) return null;
+ return new Bind(-button - 1);
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+ try {
+ InputConstants.Key key = InputConstants.getKey("key.keyboard." + lowercaseName);
+ return new Bind(key.getValue());
+ } catch (IllegalArgumentException exception) {
+ return null;
+ }
+ }
+
+ public static Iterable iterateKeys() {
+ List keys = new ArrayList<>();
+ for (InputConstants.Key key : InputConstants.Type.KEYSYM.map.values())
+ keys.add(getKeyName(key));
+ for (int i = 1; i <= 9; i++) {
+ keys.add("MOUSE" + i);
+ }
+ return keys;
+ }
+}
diff --git a/src/main/java/me/alpha432/oyvey/util/MathUtil.java b/src/main/java/me/alpha432/oyvey/util/MathUtil.java
new file mode 100644
index 0000000..e267dbb
--- /dev/null
+++ b/src/main/java/me/alpha432/oyvey/util/MathUtil.java
@@ -0,0 +1,215 @@
+package me.alpha432.oyvey.util;
+
+import me.alpha432.oyvey.util.traits.Util;
+import net.minecraft.core.BlockPos;
+import net.minecraft.util.Mth;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.phys.AABB;
+import net.minecraft.world.phys.Vec3;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.util.*;
+
+public class MathUtil
+ implements Util {
+ private static final Random random = new Random();
+
+ public static int getRandom(int min, int max) {
+ return min + random.nextInt(max - min + 1);
+ }
+
+ public static double getRandom(double min, double max) {
+ return Mth.clamp(min + random.nextDouble() * (max - min), min, max);
+ }
+
+ public static float getRandom(float min, float max) {
+ return Mth.clamp(min + random.nextFloat() * (max - min), min, max);
+ }
+
+ public static int clamp(int num, int min, int max) {
+ return num < min ? min : Math.min(num, max);
+ }
+
+ public static float clamp(float num, float min, float max) {
+ return num < min ? min : Math.min(num, max);
+ }
+
+ public static double clamp(double num, double min, double max) {
+ return num < min ? min : Math.min(num, max);
+ }
+
+ public static float sin(float value) {
+ return Mth.sin(value);
+ }
+
+ public static float cos(float value) {
+ return Mth.cos(value);
+ }
+
+ public static float wrapDegrees(float value) {
+ return Mth.wrapDegrees(value);
+ }
+
+ public static double wrapDegrees(double value) {
+ return Mth.wrapDegrees(value);
+ }
+
+ public static Vec3 roundVec(Vec3 vec3d, int places) {
+ return new Vec3(MathUtil.round(vec3d.x, places), MathUtil.round(vec3d.y, places), MathUtil.round(vec3d.z, places));
+ }
+
+ public static double square(double input) {
+ return input * input;
+ }
+
+ public static double round(double value, int places) {
+ if (places < 0) {
+ throw new IllegalArgumentException();
+ }
+ BigDecimal bd = BigDecimal.valueOf(value);
+ bd = bd.setScale(places, RoundingMode.FLOOR);
+ return bd.doubleValue();
+ }
+
+ public static float wrap(float valI) {
+ float val = valI % 360.0f;
+ if (val >= 180.0f) {
+ val -= 360.0f;
+ }
+ if (val < -180.0f) {
+ val += 360.0f;
+ }
+ return val;
+ }
+
+ public static Vec3 direction(float yaw) {
+ return new Vec3(Math.cos(MathUtil.degToRad(yaw + 90.0f)), 0.0, Math.sin(MathUtil.degToRad(yaw + 90.0f)));
+ }
+
+ public static float round(float value, int places) {
+ if (places < 0) {
+ throw new IllegalArgumentException();
+ }
+ BigDecimal bd = BigDecimal.valueOf(value);
+ bd = bd.setScale(places, RoundingMode.FLOOR);
+ return bd.floatValue();
+ }
+
+ public static > Map sortByValue(Map map, boolean descending) {
+ LinkedList> list = new LinkedList<>(map.entrySet());
+ if (descending) {
+ list.sort(Map.Entry.comparingByValue(Comparator.reverseOrder()));
+ } else {
+ list.sort(Map.Entry.comparingByValue());
+ }
+ LinkedHashMap result = new LinkedHashMap<>();
+ for (Map.Entry entry : list) {
+ result.put(entry.getKey(), entry.getValue());
+ }
+ return result;
+ }
+
+ public static String getTimeOfDay() {
+ Calendar c = Calendar.getInstance();
+ int timeOfDay = c.get(Calendar.HOUR_OF_DAY);
+ if (timeOfDay < 12) {
+ return "Good Morning ";
+ }
+ if (timeOfDay < 16) {
+ return "Good Afternoon ";
+ }
+ if (timeOfDay < 21) {
+ return "Good Evening ";
+ }
+ return "Good Night ";
+ }
+
+ public static double radToDeg(double rad) {
+ return rad * (double) 57.29578f;
+ }
+
+ public static double degToRad(double deg) {
+ return deg * 0.01745329238474369;
+ }
+
+ public static double getIncremental(double val, double inc) {
+ double one = 1.0 / inc;
+ return (double) Math.round(val * one) / one;
+ }
+
+ public static double[] directionSpeed(double speed) {
+ float forward = MathUtil.mc.player.input.getMoveVector().y;
+ float side = MathUtil.mc.player.input.getMoveVector().x;
+ float yaw = MathUtil.mc.player.yRotO + (MathUtil.mc.player.getYRot() - MathUtil.mc.player.yRotO) * mc.getDeltaTracker().getGameTimeDeltaPartialTick(false);
+ if (forward != 0.0f) {
+ if (side > 0.0f) {
+ yaw += (float) (forward > 0.0f ? -45 : 45);
+ } else if (side < 0.0f) {
+ yaw += (float) (forward > 0.0f ? 45 : -45);
+ }
+ side = 0.0f;
+ if (forward > 0.0f) {
+ forward = 1.0f;
+ } else if (forward < 0.0f) {
+ forward = -1.0f;
+ }
+ }
+ double sin = Math.sin(Math.toRadians(yaw + 90.0f));
+ double cos = Math.cos(Math.toRadians(yaw + 90.0f));
+ double posX = (double) forward * speed * cos + (double) side * speed * sin;
+ double posZ = (double) forward * speed * sin - (double) side * speed * cos;
+ return new double[]{posX, posZ};
+ }
+
+ public static List