diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..84a69d1 --- /dev/null +++ b/build.gradle @@ -0,0 +1,83 @@ +plugins { + id 'fabric-loom' version '1.6-SNAPSHOT' + id 'maven-publish' +} + +version = project.mod_version +group = project.maven_group + +base { + archivesName = project.archives_base_name +} + +repositories { + // Add repositories to retrieve artifacts from in here. + // You should only use this when depending on other mods because + // Loom adds the essential maven repositories to download Minecraft and libraries from automatically. + // See https://docs.gradle.org/current/userguide/declaring_repositories.html + // for more information about repositories. +} + +dependencies { + compileOnly 'org.projectlombok:lombok:1.18.24' + annotationProcessor 'org.projectlombok:lombok:1.18.24' + + //add joml + modImplementation 'org.joml:joml:1.10.4' + include 'org.joml:joml:1.10.4' + + // To change the versions see the gradle.properties file + minecraft "com.mojang:minecraft:${project.minecraft_version}" + mappings "net.fabricmc:yarn:${project.yarn_mappings}:v2" + modImplementation "net.fabricmc:fabric-loader:${project.loader_version}" + + // Fabric API. This is technically optional, but you probably want it anyway. + modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}" + +} + +processResources { + inputs.property "version", project.version + + filesMatching("fabric.mod.json") { + expand "version": project.version + } +} + +tasks.withType(JavaCompile).configureEach { + it.options.release = 17 +} + +java { + // Loom will automatically attach sourcesJar to a RemapSourcesJar task and to the "build" task + // if it is present. + // If you remove this line, sources will not be generated. + withSourcesJar() + + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 +} + +jar { + from("LICENSE") { + rename { "${it}_${project.base.archivesName.get()}"} + } +} + +// configure the maven publication +publishing { + publications { + create("mavenJava", MavenPublication) { + artifactId = project.archives_base_name + from components.java + } + } + + // See https://docs.gradle.org/current/userguide/publishing_maven.html for information on how to set up publishing. + repositories { + // Add repositories to publish to here. + // Notice: This block does NOT have the same function as the block in the top level. + // The repositories here will be used for publishing your artifact, not for + // retrieving dependencies. + } +} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index e62f209..8655bba 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,14 +4,14 @@ org.gradle.parallel=true # Fabric Properties # check these on https://fabricmc.net/develop -minecraft_version=1.20.1 -yarn_mappings=1.20.1+build.10 +minecraft_version=1.20.4 +yarn_mappings=1.20.4+build.3 loader_version=0.15.10 #Fabric api -fabric_version=0.92.1+1.20.1 +fabric_version=0.97.0+1.20.4 # Mod Properties -mod_version = 1.1.0 +mod_version = 1.1.0+1.20.4 maven_group = themixray.repeating.mod archives_base_name = repeating-mod \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..e644113 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..b82aa23 --- /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-8.7-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..1aa94a4 --- /dev/null +++ b/gradlew @@ -0,0 +1,249 @@ +#!/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##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && 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 + if ! command -v java >/dev/null 2>&1 + then + 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 +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=SC2039,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=SC2039,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, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +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" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..25da30d --- /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. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +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 diff --git a/remappedSrc/themixray/repeating/mod/EasyConfig.java b/remappedSrc/themixray/repeating/mod/EasyConfig.java new file mode 100644 index 0000000..8836722 --- /dev/null +++ b/remappedSrc/themixray/repeating/mod/EasyConfig.java @@ -0,0 +1,103 @@ +package themixray.repeating.mod; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.HashMap; +import java.util.Map; +import java.io.File; + +public class EasyConfig { + public final Path path; + public final File file; + public Map data; + + public EasyConfig(File f, Map def) { + this.path = f.toPath(); + this.file = f; + this.data = new HashMap<>(); + + if (!file.exists()) { + try { + file.createNewFile(); + write(def); + } catch (IOException e) { + e.printStackTrace(); + } + } + + reload(); + + for (Map.Entry m:def.entrySet()) + if (!data.containsKey(m.getKey())) + data.put(m.getKey(),m.getValue()); + + save(); + } + public EasyConfig(Path f, Map def) { + this(f.toFile(),def); + } + public EasyConfig(String parent,String child,Map def) { + this(new File(parent,child),def); + } + public EasyConfig(File parent,String child,Map def) { + this(new File(parent,child),def); + } + public EasyConfig(Path parent,String child,Map def) { + this(new File(parent.toFile(),child),def); + } + + public EasyConfig(File f) { + this(f,new HashMap<>()); + } + public EasyConfig(Path path) { + this(path.toFile(),new HashMap<>()); + } + public EasyConfig(String parent,String child) { + this(new File(parent,child),new HashMap<>()); + } + public EasyConfig(File parent,String child) { + this(new File(parent,child),new HashMap<>()); + } + public EasyConfig(Path parent,String child) { + this(new File(parent.toFile(),child),new HashMap<>()); + } + + public void reload() { + data = read(); + } + public void save() { + write(data); + } + + private String toText(Map p) { + StringBuilder t = new StringBuilder(); + for (Map.Entry e:p.entrySet()) + t.append(e.getKey()).append("=").append(e.getValue()).append("\n"); + return t.toString(); + } + private Map toMap(String j) { + Map m = new HashMap<>(); + for (String l:j.split("\n")) { + String s[] = l.split("="); + m.put(s[0],s[1]); + } + return m; + } + + private Map read() { + try { + return toMap(Files.readString(path)); + } catch (IOException e) { + e.printStackTrace(); + } + return new HashMap<>(); + } + private void write(Map p) { + try { + Files.write(path, toText(p).getBytes()); + } catch (IOException e) { + e.printStackTrace(); + } + } +} diff --git a/remappedSrc/themixray/repeating/mod/Main.java b/remappedSrc/themixray/repeating/mod/Main.java new file mode 100644 index 0000000..6a851ea --- /dev/null +++ b/remappedSrc/themixray/repeating/mod/Main.java @@ -0,0 +1,325 @@ +package themixray.repeating.mod; + +import net.fabricmc.api.ClientModInitializer; +import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; +import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper; +import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderEvents; +import net.fabricmc.loader.api.FabricLoader; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.option.KeyBinding; +import net.minecraft.client.util.InputUtil; +import net.minecraft.text.MutableText; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; +import net.minecraft.util.math.Vec3d; +import org.lwjgl.glfw.GLFW; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import themixray.repeating.mod.event.RecordDelayEvent; +import themixray.repeating.mod.event.RecordEvent; +import themixray.repeating.mod.event.RecordInputEvent; +import themixray.repeating.mod.event.RecordMoveEvent; +import themixray.repeating.mod.render.RenderHelper; +import themixray.repeating.mod.render.RenderSystem; +import themixray.repeating.mod.render.buffer.WorldBuffer; + +import java.awt.*; +import java.io.File; +import java.io.IOException; +import java.util.*; + +public class Main implements ClientModInitializer { + public static final Logger LOGGER = LoggerFactory.getLogger("repeating-mod"); + public static final MinecraftClient client = MinecraftClient.getInstance(); + public static final FabricLoader loader = FabricLoader.getInstance(); + public static Main me; + + public RecordList record_list; + public RecordState now_record; + + public boolean is_recording = false; + public long last_record = -1; + public TickTask move_tick = null; + + public TickTask replay_tick = null; + public boolean is_replaying = false; + public boolean loop_replay = false; + public static RecordInputEvent input_replay = null; + + public long living_ticks = 0; + + public static RepeatingScreen menu; + private static KeyBinding menu_key; + private static KeyBinding toggle_replay_key; + private static KeyBinding toggle_record_key; + + public long record_pos_delay = 20; + + public static Random rand = new Random(); + + public EasyConfig conf; + public File records_folder; + + @Override + public void onInitializeClient() { + LOGGER.info("Repeating mod initialized"); + me = this; + + now_record = null; + + records_folder = new File(FabricLoader.getInstance().getGameDir().toFile(),"repeating_mod_records"); + if (!records_folder.exists()) records_folder.mkdir(); + + record_list = new RecordList(records_folder); + record_list.loadRecords(); + + RenderSystem.init(); + WorldRenderEvents.LAST.register(context -> { + WorldBuffer buffer = RenderHelper.startTri(context); + if (now_record != null) { + Vec3d start_pos = now_record.getStartRecordPos(); + Vec3d finish_pos = now_record.getFinishRecordPos(); + + if (start_pos != null) drawRecordPos(buffer, start_pos, new Color(70, 230, 70, 128)); + if (finish_pos != null) drawRecordPos(buffer, finish_pos, new Color(230, 70, 70, 128)); + } + RenderHelper.endTri(buffer); + }); + + Map def = new HashMap<>(); + def.put("record_pos_delay", String.valueOf(record_pos_delay)); + + conf = new EasyConfig(loader.getConfigDir(),"repeating-mod",def); + + record_pos_delay = Long.parseLong(conf.data.get("record_pos_delay")); + + menu_key = KeyBindingHelper.registerKeyBinding(new KeyBinding( + "key.repeating-mod.menu",InputUtil.Type.KEYSYM, + GLFW.GLFW_KEY_J,"text.repeating-mod.name")); + toggle_replay_key = KeyBindingHelper.registerKeyBinding(new KeyBinding( + "key.repeating-mod.toggle_replay",InputUtil.Type.KEYSYM, + -1,"text.repeating-mod.name")); + toggle_record_key = KeyBindingHelper.registerKeyBinding(new KeyBinding( + "key.repeating-mod.toggle_record",InputUtil.Type.KEYSYM, + -1,"text.repeating-mod.name")); + + menu = new RepeatingScreen(); + ClientTickEvents.END_CLIENT_TICK.register(client -> { + if (menu_key.wasPressed()) + client.setScreen(menu); + if (toggle_replay_key.wasPressed()) { + if (now_record != null) { + if (!is_recording) { + if (is_replaying) + stopReplay(); + else startReplay(); + menu.updateButtons(); + } + } + } + if (toggle_record_key.wasPressed()) { + if (!is_replaying) { + if (is_recording) + stopRecording(); + else startRecording(); + menu.updateButtons(); + } + } + }); + + new TickTask(0,0) { + @Override + public void run() { + living_ticks++; + } + }; + + System.setProperty("java.awt.headless", "false"); + } + + public void setNowRecord(RecordState record) { + now_record = record; + } + + public void drawRecordPos(WorldBuffer buffer, Vec3d pos, Color color) { + RenderHelper.drawRectFromTri(buffer, + (float) pos.getX() - 0.25F, + (float) pos.getY() + 0.01F, + (float) pos.getZ() - 0.25F, + + (float) pos.getX() + 0.25F, + (float) pos.getY() + 0.01F, + (float) pos.getZ() - 0.25F, + + (float) pos.getX() + 0.25F, + (float) pos.getY() + 0.01F, + (float) pos.getZ() + 0.25F, + + (float) pos.getX() - 0.25F, + (float) pos.getY() + 0.01F, + (float) pos.getZ() + 0.25F, + color); + } + + public void startRecording() { + is_recording = true; + menu.updateButtons(); + + now_record = record_list.newRecord(); + + Vec3d start_pos = client.player.getPos(); + now_record.addEvent(new RecordMoveEvent(start_pos,client.player.getHeadYaw(),client.player.getPitch())); + now_record.setStartRecordPos(start_pos); + + if (record_pos_delay > 0) { + move_tick = new TickTask( + record_pos_delay, + record_pos_delay) { + @Override + public void run() { + now_record.addEvent(new RecordMoveEvent(client.player.getPos(), + client.player.getHeadYaw(), client.player.getPitch())); + } + }; + } + + sendMessage(Text.translatable("message.repeating-mod.record_start")); + } + + public void recordTick(RecordEvent e) { + if (is_recording) { + long now = living_ticks; + if (last_record != -1) { + long diff = now - last_record - 2; + if (diff > 0) now_record.addEvent(new RecordDelayEvent(diff)); + } + now_record.addEvent(e); + last_record = now; + } + } + + public void recordAllInput() { + if (client.player == null) { + stopRecording(); + return; + } + + RecordInputEvent l = ((RecordInputEvent) now_record.getLastEvent("input")); + if (l == null) { + RecordInputEvent e = new RecordInputEvent( + client.player.input.sneaking, + client.player.input.jumping, + client.player.input.movementSideways, + client.player.input.movementForward, + client.player.input.pressingForward, + client.player.input.pressingBack, + client.player.input.pressingLeft, + client.player.input.pressingRight, + client.player.getHeadYaw(), + client.player.getBodyYaw(), + client.player.getPitch(), + client.player.isSprinting(), + client.player.getYaw(), + client.player.getMovementSpeed()); + recordTick(e); + } else { + RecordInputEvent e = new RecordInputEvent( + ((Boolean) client.player.input.sneaking == l.sneaking) ? null : client.player.input.sneaking, + ((Boolean) client.player.input.jumping == l.jumping) ? null : client.player.input.jumping, + (((Float) client.player.input.movementSideways).equals(l.movementSideways)) ? null : client.player.input.movementSideways, + (((Float) client.player.input.movementForward).equals(l.movementForward)) ? null : client.player.input.movementForward, + ((Boolean) client.player.input.pressingForward == l.pressingForward) ? null : client.player.input.pressingForward, + ((Boolean) client.player.input.pressingBack == l.pressingBack) ? null : client.player.input.pressingBack, + ((Boolean) client.player.input.pressingLeft == l.pressingLeft) ? null : client.player.input.pressingLeft, + ((Boolean) client.player.input.pressingRight == l.pressingRight) ? null : client.player.input.pressingRight, + client.player.getHeadYaw(), Main.client.player.getBodyYaw(),client.player.getPitch(), + ((Boolean) client.player.isSprinting() == l.sprinting) ? null : client.player.isSprinting(), + client.player.getYaw(),client.player.getMovementSpeed()); + + if (!(e.isEmpty() && + e.yaw == l.yaw && + e.head_yaw == l.head_yaw && + e.pitch == l.pitch && + e.body_yaw == l.body_yaw)) { + e.fillEmpty(l); + recordTick(e); + } + } + } + + public void stopRecording() { + is_recording = false; + now_record.setFinishRecordPos(client.player.getPos()); + try { + now_record.save(); + } catch (IOException e) { + throw new RuntimeException(e); + } + if (move_tick != null) { + move_tick.cancel(); + move_tick = null; + } + menu.updateButtons(); + last_record = -1; + sendMessage(Text.translatable("message.repeating-mod.record_stop")); + } + + + public void startReplay() { + is_recording = false; + is_replaying = true; + menu.updateButtons(); + + List events = now_record.getEvents(); + + replay_tick = new TickTask(0,0, TickTask.TickAt.CLIENT_TAIL) { + public int replay_index = 0; + + @Override + public void run() { + if (!is_replaying) cancel(); + RecordEvent e = events.get(replay_index); + if (e instanceof RecordDelayEvent) { + setDelay(((RecordDelayEvent) e).delay); + } else { + e.replay(); + } + + replay_index++; + if (!loop_replay) { + if (replay_index == events.size()) { + stopReplay(); + cancel(); + } + } else if (replay_index == events.size()) { + replay_index = 0; + } + } + }; + + sendMessage(Text.translatable("message.repeating-mod.replay_start")); + } + + public void stopReplay() { + is_recording = false; + is_replaying = false; + if (replay_tick != null) { + replay_tick.cancel(); + replay_tick = null; + } + menu.updateButtons(); + record_list.getWidget().getWidget(now_record).getChildren().get(3).setMessage(Text.translatable("text.repeating-mod.start")); + sendMessage(Text.translatable("message.repeating-mod.replay_stop")); + } + + public static void sendMessage(MutableText text) { + client.player.sendMessage(Text.literal("[") + .append(Text.translatable("text.repeating-mod.name")) + .append("] ").formatted(Formatting.BOLD,Formatting.DARK_GRAY) + .append(text.formatted(Formatting.RESET).formatted(Formatting.GRAY))); + } + + public static void sendDebug(String s) { + client.player.sendMessage(Text.literal("[DEBUG] ").append(Text.of(s))); + } +} diff --git a/remappedSrc/themixray/repeating/mod/RecordList.java b/remappedSrc/themixray/repeating/mod/RecordList.java new file mode 100644 index 0000000..e17a0e8 --- /dev/null +++ b/remappedSrc/themixray/repeating/mod/RecordList.java @@ -0,0 +1,77 @@ +package themixray.repeating.mod; + +import net.minecraft.text.Text; +import themixray.repeating.mod.widget.RecordListWidget; + +import java.io.File; +import java.io.IOException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +public class RecordList { + private final File folder; + private List records; + private RecordListWidget widget; + + public RecordList(File folder) { + this.folder = folder; + this.records = new ArrayList<>(); + this.widget = new RecordListWidget(0, 0, 180, 200); + } + + public List getRecords() { + return records; + } + + public File getFolder() { + return folder; + } + + public RecordListWidget getWidget() { + return widget; + } + + public void loadRecords() { + for (File file : folder.listFiles()) { + try { + addRecord(file); + } catch (Exception e) {} + } + } + + public void addRecord(File file) throws Exception { + addRecord(RecordState.load(file)); + } + + public void addRecord(RecordState record) { + records.add(record); + widget.addWidget(record); + } + + public void removeRecord(RecordState record) { + records.remove(record); + widget.removeWidget(record); + } + + public RecordState newRecord() { + Date date = new Date(); + String name = "Unnamed"; + String author = Main.client.player.getName().getString(); + + File file = new File(Main.me.records_folder, + "record_" + RecordState.FILE_DATE_FORMAT.format(date) + + "_" + Main.rand.nextInt(10) + ".rrm"); + + RecordState state = new RecordState( + file, name, date, author, + new ArrayList<>(), + null, + null); + + addRecord(state); + + return state; + } +} diff --git a/remappedSrc/themixray/repeating/mod/RecordState.java b/remappedSrc/themixray/repeating/mod/RecordState.java new file mode 100644 index 0000000..239194a --- /dev/null +++ b/remappedSrc/themixray/repeating/mod/RecordState.java @@ -0,0 +1,169 @@ +package themixray.repeating.mod; + +import com.google.common.collect.Lists; +import net.minecraft.util.math.Vec3d; +import themixray.repeating.mod.event.RecordEvent; + +import java.awt.*; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +public class RecordState { + public static SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("MM.dd.yyyy HH:mm:ss"); + public static SimpleDateFormat FILE_DATE_FORMAT = new SimpleDateFormat("MM-dd-yyyy_HH-mm-ss"); + + private final File file; + private String name; + private Date date; + private String author; + + private List events; + private Vec3d start_record_pos; + private Vec3d finish_record_pos; + + public RecordState(File file, + String name, + Date date, + String author, + List events, + Vec3d start_record_pos, + Vec3d finish_record_pos) { + this.file = file; + this.name = name; + this.date = date; + this.author = author; + + this.events = events; + this.start_record_pos = start_record_pos; + this.finish_record_pos = finish_record_pos; + } + + public File getFile() { + return file; + } + + public String getName() { + return name; + } + + public String getAuthor() { + return author; + } + + public Date getDate() { + return date; + } + + public List getEvents() { + return events; + } + + public Vec3d getFinishRecordPos() { + return finish_record_pos; + } + + public Vec3d getStartRecordPos() { + return start_record_pos; + } + + public void setAuthor(String author) { + this.author = author; + } + + public void setDate(Date date) { + this.date = date; + } + + public void setName(String name) { + this.name = name; + } + + public void setEvents(List events) { + this.events = events; + } + + public void setFinishRecordPos(Vec3d finish_record_pos) { + this.finish_record_pos = finish_record_pos; + } + + public void setStartRecordPos(Vec3d start_record_pos) { + this.start_record_pos = start_record_pos; + } + + public void addEvent(RecordEvent event) { + events.add(event); + } + + public RecordEvent getLastEvent(String type) { + for (RecordEvent r: Lists.reverse(new ArrayList<>(events))) { + if (r.getType().equals(type)) { + return r; + } + } + return null; + } + + public void save() throws IOException { + StringBuilder text = new StringBuilder(); + + text.append(name).append("\n") + .append(DATE_FORMAT.format(date)).append("\n") + .append(author).append("\n"); + + text.append(start_record_pos.getX()).append("n") + .append(start_record_pos.getY()).append("n") + .append(start_record_pos.getZ()).append("x") + .append(finish_record_pos.getX()).append("n") + .append(finish_record_pos.getY()).append("n") + .append(finish_record_pos.getZ()); + + for (int i = 0; i < events.size(); i++) { + text.append("\n"); + text.append(events.get(i).serialize()); + } + + Files.write(file.toPath(), text.toString().getBytes()); + } + + public static RecordState load(File file) throws Exception { + String text = Files.readString(file.toPath()); + List lines = List.of(text.split("\n")); + + List signature = lines.subList(0,4); + + String name = signature.get(0); + Date date = DATE_FORMAT.parse(signature.get(1)); + String author = signature.get(2); + + String record_pos = signature.get(3); + + String[] lss0 = record_pos.split("x"); + String[] lss1 = lss0[0].split("n"); + String[] lss2 = lss0[1].split("n"); + + Vec3d start_record_pos = new Vec3d( + Float.parseFloat(lss1[0]), + Float.parseFloat(lss1[1]), + Float.parseFloat(lss1[2])); + Vec3d finish_record_pos = new Vec3d( + Float.parseFloat(lss2[0]), + Float.parseFloat(lss2[1]), + Float.parseFloat(lss2[2])); + + List event_lines = lines.subList(4,lines.size()); + List events = event_lines.stream().map(RecordEvent::deserialize).toList(); + + return new RecordState(file, name, date, author, events, start_record_pos, finish_record_pos); + } + + public void remove() { + file.delete(); + Main.me.record_list.removeRecord(this); + Main.me.record_list.getWidget().removeWidget(this); + } +} diff --git a/remappedSrc/themixray/repeating/mod/RenderListener.java b/remappedSrc/themixray/repeating/mod/RenderListener.java new file mode 100644 index 0000000..9992e48 --- /dev/null +++ b/remappedSrc/themixray/repeating/mod/RenderListener.java @@ -0,0 +1,11 @@ +package themixray.repeating.mod; + +import net.minecraft.client.gui.DrawContext; + +public interface RenderListener { + default boolean beforeRender() { + return true; + } + + void render(DrawContext context, int mouseX, int mouseY, float delta); +} diff --git a/remappedSrc/themixray/repeating/mod/RepeatingScreen.java b/remappedSrc/themixray/repeating/mod/RepeatingScreen.java new file mode 100644 index 0000000..19609e4 --- /dev/null +++ b/remappedSrc/themixray/repeating/mod/RepeatingScreen.java @@ -0,0 +1,193 @@ +package themixray.repeating.mod; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.gui.Drawable; +import net.minecraft.client.gui.Element; +import net.minecraft.client.gui.Selectable; +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.client.gui.tooltip.Tooltip; +import net.minecraft.client.gui.widget.ButtonWidget; +import net.minecraft.client.gui.widget.SliderWidget; +import net.minecraft.text.Text; +import themixray.repeating.mod.widget.RecordListWidget; + +import java.awt.*; +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +@Environment(EnvType.CLIENT) +public class RepeatingScreen extends Screen { + private static List render_listeners = new ArrayList<>(); + + public ButtonWidget record_btn; + public ButtonWidget loop_btn; + public ButtonWidget import_btn; + + public SliderWidget pos_delay_slider; + + public boolean was_build = false; + + protected RepeatingScreen() { + super(Text.empty()); + } + + public static void addRenderListener(RenderListener render) { + render_listeners.add(render); + } + + public static void removeRenderListener(RenderListener render) { + render_listeners.remove(render); + } + + public void updateButtons() { + if (was_build) { + record_btn.setMessage(Text.translatable("text.repeating-mod." + ((Main.me.is_recording) ? "stop_record" : "start_record"))); + loop_btn.setMessage(Text.translatable("text.repeating-mod." + ((Main.me.loop_replay) ? "off_loop" : "on_loop"))); + } + } + + @Override + public void render(DrawContext context, int mouseX, int mouseY, float delta) { + renderBackground(context,mouseX,mouseY,delta); + + for (RenderListener l : render_listeners) { + if (l.beforeRender()) { + l.render(context, mouseX, mouseY, delta); + } + } + + super.render(context, mouseX, mouseY, delta); + + for (RenderListener l : render_listeners) { + if (!l.beforeRender()) { + l.render(context, mouseX, mouseY, delta); + } + } + } + + @Override + protected void init() { + RecordListWidget list_widget = Main.me.record_list.getWidget(); + + list_widget.method_46421(width / 2 + 2); + list_widget.method_46419(height / 2 - list_widget.getHeight() / 2); + list_widget.init(this); + + + record_btn = ButtonWidget.builder( + Text.translatable("text.repeating-mod.start_record"), button -> { + if (!Main.me.is_replaying) { + if (Main.me.is_recording) + Main.me.stopRecording(); + else Main.me.startRecording(); + updateButtons(); + } + }) + .dimensions(width / 2 - 120, height / 2 - 32, 120, 20) + .tooltip(Tooltip.of(Text.translatable("text.repeating-mod.record_tooltip"))) + .build(); + + loop_btn = ButtonWidget.builder(Text.empty(), button -> { + Main.me.loop_replay = !Main.me.loop_replay; + updateButtons(); + }) + .dimensions(width / 2 - 120, height / 2 - 10, 120, 20) + .tooltip(Tooltip.of(Text.translatable("text.repeating-mod.loop_tooltip"))) + .build(); + + pos_delay_slider = new SliderWidget( + width / 2 - 120, height / 2 + 12, 120, 20, + (Main.me.record_pos_delay < 0) ? Text.translatable("text.repeating-mod.nan_pos_delay") : + Text.translatable("text.repeating-mod.pos_delay", String.valueOf(Main.me.record_pos_delay)), + (Main.me.record_pos_delay+1d)/101d) { + + @Override + protected void updateMessage() { + double v = value*101d-1d; + if (v <= 1) setMessage(Text.translatable("text.repeating-mod.nan_pos_delay")); + else setMessage(Text.translatable("text.repeating-mod.pos_delay", String.valueOf((long) v))); + } + + @Override + protected void applyValue() { + double v = value*101d-1d; + if (v <= 1) setMessage(Text.translatable("text.repeating-mod.nan_pos_delay")); + else setMessage(Text.translatable("text.repeating-mod.pos_delay", String.valueOf((long) v))); + Main.me.record_pos_delay = (long) v; + Main.me.conf.data.put("record_pos_delay",String.valueOf(Main.me.record_pos_delay)); + Main.me.conf.save(); + } + + @Override + public void onRelease(double mouseX, double mouseY) { + super.onRelease(mouseX, mouseY); + applyValue(); + } + + @Override + protected void onDrag(double mouseX, double mouseY, double deltaX, double deltaY) { + super.onDrag(mouseX, mouseY, deltaX, deltaY); + applyValue(); + } + + @Override + public void render(DrawContext context, int mouseX, int mouseY, float delta) { + super.render(context, mouseX, mouseY, delta); + updateMessage(); + } + }; + pos_delay_slider.setTooltip(Tooltip.of(Text.translatable("text.repeating-mod.pos_delay_tooltip"))); + + import_btn = ButtonWidget.builder(Text.translatable("text.repeating-mod.import"), button -> { + new Thread(() -> { + FileDialog fd = new FileDialog((java.awt.Frame) null); + fd.setMultipleMode(true); + fd.setName("Choose record files"); + fd.setTitle("Choose record files"); + fd.setFilenameFilter((dir, name) -> name.endsWith(".rrm")); + fd.setVisible(true); + + File[] files = fd.getFiles(); + if (files != null) { + for (File file : files) { + try { + Main.me.record_list.addRecord(file); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + }}).start(); + }) + .dimensions(width / 2 + 2, height / 2 - list_widget.getHeight() / 2 - 22, 180, 20) + .tooltip(Tooltip.of(Text.translatable("text.repeating-mod.import_tooltip"))) + .build(); + + was_build = true; + + updateButtons(); + + addDrawableChild(loop_btn); + addDrawableChild(record_btn); + addDrawableChild(import_btn); + addDrawableChild(pos_delay_slider); + } + + public T addDrawableChild(T drawableElement) { + return super.addDrawableChild(drawableElement); + } + + public T addDrawable(T drawable) { + return super.addDrawable(drawable); + } + + public T addSelectableChild(T child) { + return super.addSelectableChild(child); + } + + public void remove(Element child) { + super.remove(child); + } +} diff --git a/remappedSrc/themixray/repeating/mod/TickTask.java b/remappedSrc/themixray/repeating/mod/TickTask.java new file mode 100644 index 0000000..f3beed1 --- /dev/null +++ b/remappedSrc/themixray/repeating/mod/TickTask.java @@ -0,0 +1,94 @@ +package themixray.repeating.mod; + +import java.util.ArrayList; +import java.util.List; + +public abstract class TickTask implements Runnable { + public static List tasks = new ArrayList<>(); + + public static void tickTasks(TickAt at) { + for (TickTask t:new ArrayList<>(tasks)) + if (t.getAt() == at) t.tick(); + } + + private long living; + private long delay; + + private boolean is_repeating; + private long period; + + private boolean is_cancelled; + private TickAt at; + + public enum TickAt { + CLIENT_HEAD, CLIENT_TAIL, + MOVEMENT_HEAD, MOVEMENT_TAIL, + RENDER_HEAD, RENDER_TAIL + } + + public TickTask(long delay, TickAt at) { + this.is_cancelled = false; + this.is_repeating = false; + this.delay = delay; + this.living = 0; + this.period = 0; + this.at = at; + tasks.add(this); + } + + public TickTask(long delay, long period, TickAt at) { + this.is_cancelled = false; + this.is_repeating = true; + this.delay = delay; + this.period = period; + this.living = 0; + this.at = at; + tasks.add(this); + } + + public TickTask(long delay) { + this(delay,TickAt.CLIENT_HEAD); + } + + public TickTask(long delay, long period) { + this(delay,period,TickAt.CLIENT_HEAD); + } + + public void cancel() { + if (!is_cancelled) { + is_cancelled = true; + tasks.remove(this); + } + } + + public boolean isCancelled() { + return is_cancelled; + } + + public TickAt getAt() { + return at; + } + + public void setDelay(long delay) { + if (is_repeating) { + this.delay = delay; + } + } + public long getDelay() { + return this.delay; + } + + public void tick() { + if (living >= delay) { + if (is_repeating) { + delay = period; + run(); + living = -1; + } else { + run(); + cancel(); + } + } + living++; + } +} diff --git a/remappedSrc/themixray/repeating/mod/event/RecordBlockBreakEvent.java b/remappedSrc/themixray/repeating/mod/event/RecordBlockBreakEvent.java new file mode 100644 index 0000000..a39380c --- /dev/null +++ b/remappedSrc/themixray/repeating/mod/event/RecordBlockBreakEvent.java @@ -0,0 +1,32 @@ +package themixray.repeating.mod.event; + +import net.minecraft.util.math.BlockPos; +import themixray.repeating.mod.Main; + +public class RecordBlockBreakEvent extends RecordEvent { + public BlockPos pos; + + public static RecordBlockBreakEvent fromArgs(String[] a) { + return new RecordBlockBreakEvent(new BlockPos( + Integer.parseInt(a[0]), + Integer.parseInt(a[1]), + Integer.parseInt(a[2]))); + } + + public RecordBlockBreakEvent( + BlockPos pos) { + this.pos = pos; + } + + public void replay() { + Main.client.interactionManager.breakBlock(pos); + } + + public String serialize() { + return "b=" + pos.getX() + "&" + pos.getY() + "&" + pos.getZ(); + } + + public String getType() { + return "block_break"; + } +} diff --git a/remappedSrc/themixray/repeating/mod/event/RecordBlockInteractEvent.java b/remappedSrc/themixray/repeating/mod/event/RecordBlockInteractEvent.java new file mode 100644 index 0000000..59d0b76 --- /dev/null +++ b/remappedSrc/themixray/repeating/mod/event/RecordBlockInteractEvent.java @@ -0,0 +1,46 @@ +package themixray.repeating.mod.event; + +import net.minecraft.util.Hand; +import net.minecraft.util.hit.BlockHitResult; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Direction; +import net.minecraft.util.math.Vec3d; +import themixray.repeating.mod.Main; + +public class RecordBlockInteractEvent extends RecordEvent { + public Hand hand; + public BlockHitResult hitResult; + + public static RecordBlockInteractEvent fromArgs(String[] a) { + return new RecordBlockInteractEvent( + Hand.valueOf(a[5]), + new BlockHitResult(new Vec3d( + Double.parseDouble(a[0]), + Double.parseDouble(a[1]), + Double.parseDouble(a[2])), + Direction.byId(Integer.parseInt(a[4])), + new BlockPos( + Integer.parseInt(a[0]), + Integer.parseInt(a[1]), + Integer.parseInt(a[2])), + a[3].equals("1"))); + } + + public RecordBlockInteractEvent(Hand hand, BlockHitResult hitResult) { + this.hand = hand; + this.hitResult = hitResult; + } + + public void replay() { + Main.client.interactionManager.interactBlock(Main.client.player, hand, hitResult); + } + + public String serialize() { + return "i=" + hitResult.getBlockPos().getX() + "&" + hitResult.getBlockPos().getY() + "&" + hitResult.getBlockPos().getZ() + + "&" + (hitResult.isInsideBlock() ? "1" : "0") + "&" + hitResult.getSide().getId() + "&" + hand.name(); + } + + public String getType() { + return "block_interact"; + } +} diff --git a/remappedSrc/themixray/repeating/mod/event/RecordDelayEvent.java b/remappedSrc/themixray/repeating/mod/event/RecordDelayEvent.java new file mode 100644 index 0000000..7e4bcca --- /dev/null +++ b/remappedSrc/themixray/repeating/mod/event/RecordDelayEvent.java @@ -0,0 +1,29 @@ +package themixray.repeating.mod.event; + +public class RecordDelayEvent extends RecordEvent { + public long delay; + + public static RecordDelayEvent fromArgs(String[] a) { + return new RecordDelayEvent(Long.parseLong(a[0])); + } + + public RecordDelayEvent(long delay) { + this.delay = delay; + } + + public void replay() { + try { + Thread.sleep(delay / 20 * 1000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + public String serialize() { + return "d=" + delay; + } + + public String getType() { + return "delay"; + } +} diff --git a/remappedSrc/themixray/repeating/mod/event/RecordEvent.java b/remappedSrc/themixray/repeating/mod/event/RecordEvent.java new file mode 100644 index 0000000..e7c2ffe --- /dev/null +++ b/remappedSrc/themixray/repeating/mod/event/RecordEvent.java @@ -0,0 +1,28 @@ +package themixray.repeating.mod.event; + +public abstract class RecordEvent { + public abstract void replay(); + public abstract String serialize(); + public abstract String getType(); + + public static RecordEvent deserialize(String t) { + try { + String type = String.valueOf(t.charAt(0)); + String[] args = t.substring(2).split("&"); + if (type.equals("d")) { + return RecordDelayEvent.fromArgs(args); + } else if (type.equals("m")) { + return RecordMoveEvent.fromArgs(args); + } else if (type.equals("p")) { + return RecordInputEvent.fromArgs(args); + } else if (type.equals("b")) { + return RecordBlockBreakEvent.fromArgs(args); + } else if (type.equals("i")) { + return RecordBlockInteractEvent.fromArgs(args); + } + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } +} diff --git a/remappedSrc/themixray/repeating/mod/event/RecordInputEvent.java b/remappedSrc/themixray/repeating/mod/event/RecordInputEvent.java new file mode 100644 index 0000000..a760440 --- /dev/null +++ b/remappedSrc/themixray/repeating/mod/event/RecordInputEvent.java @@ -0,0 +1,147 @@ +package themixray.repeating.mod.event; + +import themixray.repeating.mod.Main; + +public class RecordInputEvent extends RecordEvent { + public Boolean sneaking; + public Boolean jumping; + public Boolean pressingForward; + public Boolean pressingBack; + public Boolean pressingLeft; + public Boolean pressingRight; + public Boolean sprinting; + + public Float movementSideways; + public Float movementForward; + + public float yaw; + public float head_yaw; + public float body_yaw; + public float pitch; + public float speed; + + public static RecordInputEvent fromArgs(String[] a) { + return new RecordInputEvent( + (a[0].equals("n") ? null : a[0].equals("1")), + (a[1].equals("n") ? null : a[1].equals("1")), + (a[2].equals("n") ? null : Float.parseFloat(a[2])), + (a[3].equals("n") ? null : Float.parseFloat(a[3])), + (a[4].equals("n") ? null : a[4].equals("1")), + (a[5].equals("n") ? null : a[5].equals("1")), + (a[6].equals("n") ? null : a[6].equals("1")), + (a[7].equals("n") ? null : a[7].equals("1")), + Float.parseFloat(a[8]), Float.parseFloat(a[9]), + Float.parseFloat(a[10]), + (a[11].equals("n") ? null : a[11].equals("1")), + Float.parseFloat(a[12]), + Float.parseFloat(a[13])); + } + + public RecordInputEvent(Boolean sneaking, + Boolean jumping, + Float movementSideways, + Float movementForward, + Boolean pressingForward, + Boolean pressingBack, + Boolean pressingLeft, + Boolean pressingRight, + float head_yaw, + float body_yaw, + float head_pitch, + Boolean sprinting, + float yaw, + float speed) { + this.sneaking = sneaking; + this.jumping = jumping; + this.movementSideways = movementSideways; + this.movementForward = movementForward; + this.pressingForward = pressingForward; + this.pressingBack = pressingBack; + this.pressingLeft = pressingLeft; + this.pressingRight = pressingRight; + this.head_yaw = head_yaw; + this.body_yaw = body_yaw; + this.pitch = head_pitch; + this.sprinting = sprinting; + this.yaw = yaw; + this.speed = speed; + } + + public void fillEmpty(RecordInputEvent e) { + if (sneaking == null) sneaking = e.sneaking; + if (jumping == null) jumping = e.jumping; + if (movementSideways == null) movementSideways = e.movementSideways; + if (movementForward == null) movementForward = e.movementForward; + if (pressingForward == null) pressingForward = e.pressingForward; + if (pressingBack == null) pressingBack = e.pressingBack; + if (pressingLeft == null) pressingLeft = e.pressingLeft; + if (pressingRight == null) pressingRight = e.pressingRight; + if (sprinting == null) sprinting = e.sprinting; + } + + public boolean isEmpty() { + return sneaking == null && + jumping == null && + movementSideways == null && + movementForward == null && + pressingForward == null && + pressingBack == null && + pressingLeft == null && + pressingRight == null && + sprinting == null; + } + + public void replay() { + Main.input_replay = this; + } + + public void inputCallback() { + if (sprinting != null && Main.client.player.isSprinting() != sprinting) + Main.client.player.setSprinting(sprinting); + if (Main.client.player.getYaw() != yaw) + Main.client.player.setYaw(yaw); + if (Main.client.player.getHeadYaw() != head_yaw) + Main.client.player.setHeadYaw(head_yaw); + if (Main.client.player.getBodyYaw() != body_yaw) + Main.client.player.setBodyYaw(body_yaw); + if (Main.client.player.getPitch() != pitch) + Main.client.player.setPitch(pitch); + if (Main.client.player.getMovementSpeed() != speed) + Main.client.player.setMovementSpeed(speed); + if (sneaking != null && Main.client.player.input.sneaking != sneaking) + Main.client.player.input.sneaking = sneaking; + if (jumping != null && Main.client.player.input.jumping != jumping) + Main.client.player.input.jumping = jumping; + if (movementSideways != null && Main.client.player.input.movementSideways != movementSideways) + Main.client.player.input.movementSideways = movementSideways; + if (movementForward != null && Main.client.player.input.movementForward != movementForward) + Main.client.player.input.movementForward = movementForward; + if (pressingForward != null && Main.client.player.input.pressingForward != pressingForward) + Main.client.player.input.pressingForward = pressingForward; + if (pressingBack != null && Main.client.player.input.pressingBack != pressingBack) + Main.client.player.input.pressingBack = pressingBack; + if (pressingLeft != null && Main.client.player.input.pressingLeft != pressingLeft) + Main.client.player.input.pressingLeft = pressingLeft; + if (pressingRight != null && Main.client.player.input.pressingRight != pressingRight) + Main.client.player.input.pressingRight = pressingRight; + } + + public String serialize() { + return "p=" + + ((sneaking == null) ? "n" : (sneaking ? "1" : "0")) + "&" + + ((jumping == null) ? "n" : (jumping ? "1" : "0")) + "&" + + ((movementSideways == null) ? "n" : movementSideways) + "&" + + ((movementForward == null) ? "n" : movementForward) + "&" + + ((pressingForward == null) ? "n" : (pressingForward ? "1" : "0")) + "&" + + ((pressingBack == null) ? "n" : (pressingBack ? "1" : "0")) + "&" + + ((pressingLeft == null) ? "n" : (pressingLeft ? "1" : "0")) + "&" + + ((pressingRight == null) ? "n" : (pressingRight ? "1" : "0")) + "&" + + head_yaw + "&" + body_yaw + "&" + pitch + "&" + + ((sprinting == null) ? "n" : (sprinting ? "1" : "0") + + "&" + yaw + "&" + speed); + } + + public String getType() { + return "input"; + } +} diff --git a/remappedSrc/themixray/repeating/mod/event/RecordMoveEvent.java b/remappedSrc/themixray/repeating/mod/event/RecordMoveEvent.java new file mode 100644 index 0000000..7b964de --- /dev/null +++ b/remappedSrc/themixray/repeating/mod/event/RecordMoveEvent.java @@ -0,0 +1,42 @@ +package themixray.repeating.mod.event; + +import net.minecraft.entity.MovementType; +import net.minecraft.util.math.Vec3d; +import themixray.repeating.mod.Main; + +public class RecordMoveEvent extends RecordEvent { + public Vec3d vec; + public float yaw; + public float pitch; + + public static RecordMoveEvent fromArgs(String[] a) { + return new RecordMoveEvent(new Vec3d( + Double.parseDouble(a[0]), + Double.parseDouble(a[1]), + Double.parseDouble(a[2])), + Float.parseFloat(a[3]), + Float.parseFloat(a[4])); + } + + public RecordMoveEvent(Vec3d vec, float yaw, float pitch) { + this.vec = vec; + this.yaw = yaw; + this.pitch = pitch; + } + + public void replay() { + Vec3d p = Main.client.player.getPos(); + Vec3d v = new Vec3d(vec.getX() - p.getX(), vec.getY() - p.getY(), vec.getZ() - p.getZ()); + Main.client.player.move(MovementType.SELF, v); + Main.client.player.setYaw(yaw); + Main.client.player.setPitch(pitch); + } + + public String serialize() { + return "m=" + vec.getX() + "&" + vec.getY() + "&" + vec.getZ() + "&" + yaw + "&" + pitch; + } + + public String getType() { + return "move"; + } +} diff --git a/remappedSrc/themixray/repeating/mod/mixin/ClientMixin.java b/remappedSrc/themixray/repeating/mod/mixin/ClientMixin.java new file mode 100644 index 0000000..60e9e64 --- /dev/null +++ b/remappedSrc/themixray/repeating/mod/mixin/ClientMixin.java @@ -0,0 +1,24 @@ +package themixray.repeating.mod.mixin; + +import net.minecraft.client.MinecraftClient; +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 themixray.repeating.mod.Main; +import themixray.repeating.mod.TickTask; + +@Mixin(MinecraftClient.class) +public abstract class ClientMixin { + @Inject(at = @At(value = "HEAD"), method = "tick") + private void onTickHead(CallbackInfo ci) { + if (Main.me.is_recording) + Main.me.recordAllInput(); + TickTask.tickTasks(TickTask.TickAt.CLIENT_HEAD); + } + + @Inject(at = @At(value = "TAIL"), method = "tick") + private void onTickTail(CallbackInfo ci) { + TickTask.tickTasks(TickTask.TickAt.CLIENT_TAIL); + } +} diff --git a/remappedSrc/themixray/repeating/mod/mixin/EntityMixin.java b/remappedSrc/themixray/repeating/mod/mixin/EntityMixin.java new file mode 100644 index 0000000..eed7b58 --- /dev/null +++ b/remappedSrc/themixray/repeating/mod/mixin/EntityMixin.java @@ -0,0 +1,31 @@ +package themixray.repeating.mod.mixin; + +import net.minecraft.entity.Entity; +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 themixray.repeating.mod.Main; + +import java.util.UUID; + +@Mixin(Entity.class) +public abstract class EntityMixin { + @Shadow public abstract UUID getUuid(); + + @Inject(at = @At(value = "HEAD"), method = "setSprinting", cancellable = true) + private void onSprint(boolean sprinting,CallbackInfo ci) { + if (Main.client.player != null) { + if (getUuid().equals(Main.client.player.getUuid())) { + if (Main.me.is_replaying) { + if (Main.input_replay != null && + Main.input_replay.sprinting != null && + Main.input_replay.sprinting != sprinting) { + ci.cancel(); + } + } + } + } + } +} diff --git a/remappedSrc/themixray/repeating/mod/mixin/InputMixin.java b/remappedSrc/themixray/repeating/mod/mixin/InputMixin.java new file mode 100644 index 0000000..800e147 --- /dev/null +++ b/remappedSrc/themixray/repeating/mod/mixin/InputMixin.java @@ -0,0 +1,20 @@ +package themixray.repeating.mod.mixin; + +import net.minecraft.client.input.KeyboardInput; +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 themixray.repeating.mod.Main; + +@Mixin(KeyboardInput.class) +public abstract class InputMixin { + @Inject(at = @At(value = "TAIL"), method = "tick") + private void onTickTail(boolean slowDown, float f, CallbackInfo ci) { + if (Main.me.is_replaying) { + if (Main.input_replay != null) { + Main.input_replay.inputCallback(); + } + } + } +} diff --git a/remappedSrc/themixray/repeating/mod/mixin/MovementMixin.java b/remappedSrc/themixray/repeating/mod/mixin/MovementMixin.java new file mode 100644 index 0000000..45190ba --- /dev/null +++ b/remappedSrc/themixray/repeating/mod/mixin/MovementMixin.java @@ -0,0 +1,44 @@ +package themixray.repeating.mod.mixin; + +import net.fabricmc.fabric.api.event.player.PlayerBlockBreakEvents; +import net.fabricmc.fabric.api.event.player.UseBlockCallback; +import net.minecraft.client.network.ClientPlayerEntity; +import net.minecraft.util.ActionResult; +import net.minecraft.util.hit.HitResult; +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 themixray.repeating.mod.Main; +import themixray.repeating.mod.event.RecordBlockBreakEvent; +import themixray.repeating.mod.event.RecordBlockInteractEvent; +import themixray.repeating.mod.TickTask; + +@Mixin(ClientPlayerEntity.class) +public abstract class MovementMixin { + + @Inject(at = @At(value = "HEAD"), method = "init") + private void init(CallbackInfo ci) { + PlayerBlockBreakEvents.AFTER.register((world, player, pos, blockState, blockEntity) -> { + if (Main.me.is_recording) + Main.me.recordTick(new RecordBlockBreakEvent(pos)); + }); + + UseBlockCallback.EVENT.register((player, world, hand, hitResult) -> { + if (hitResult.getType().equals(HitResult.Type.BLOCK)) + if (Main.me.is_recording) + Main.me.recordTick(new RecordBlockInteractEvent(hand,hitResult)); + return ActionResult.PASS; + }); + } + + @Inject(at = @At(value = "HEAD"), method = "tickMovement") + private void onMoveHead(CallbackInfo ci) { + TickTask.tickTasks(TickTask.TickAt.MOVEMENT_HEAD); + } + + @Inject(at = @At(value = "TAIL"), method = "tick") + private void onMoveTail(CallbackInfo ci) { + TickTask.tickTasks(TickTask.TickAt.MOVEMENT_TAIL); + } +} diff --git a/remappedSrc/themixray/repeating/mod/mixin/NetworkMixin.java b/remappedSrc/themixray/repeating/mod/mixin/NetworkMixin.java new file mode 100644 index 0000000..4f85878 --- /dev/null +++ b/remappedSrc/themixray/repeating/mod/mixin/NetworkMixin.java @@ -0,0 +1,29 @@ +package themixray.repeating.mod.mixin; + +import net.minecraft.client.network.ClientPlayNetworkHandler; +import net.minecraft.network.listener.ServerPlayPacketListener; +import net.minecraft.network.packet.Packet; +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 java.time.Duration; +import java.util.function.BooleanSupplier; + +@Mixin(ClientPlayNetworkHandler.class) +public abstract class NetworkMixin { +// @Inject(at = @At(value = "HEAD"), method = "sendPacket(Lnet/minecraft/network/packet/Packet;)V") +// private void onSendPacket1Head(Packet packet, +// CallbackInfo ci) { +// +// } +// +// @Inject(at = @At(value = "HEAD"), method = "sendPacket(Lnet/minecraft/network/packet/Packet;Ljava/util/function/BooleanSupplier;Ljava/time/Duration;)V") +// private void onSendPacket2Head(Packet packet, +// BooleanSupplier sendCondition, +// Duration expirationTime, +// CallbackInfo ci) { +// +// } +} diff --git a/remappedSrc/themixray/repeating/mod/mixin/RendererMixin.java b/remappedSrc/themixray/repeating/mod/mixin/RendererMixin.java new file mode 100644 index 0000000..edb363d --- /dev/null +++ b/remappedSrc/themixray/repeating/mod/mixin/RendererMixin.java @@ -0,0 +1,21 @@ +package themixray.repeating.mod.mixin; + +import net.minecraft.client.render.*; +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 themixray.repeating.mod.TickTask; + +@Mixin(GameRenderer.class) +public abstract class RendererMixin { + @Inject(at = @At(value = "HEAD"), method = "tick") + private void onTickHead(CallbackInfo ci) { + TickTask.tickTasks(TickTask.TickAt.RENDER_HEAD); + } + + @Inject(at = @At(value = "TAIL"), method = "tick") + private void onTickTail(CallbackInfo ci) { + TickTask.tickTasks(TickTask.TickAt.RENDER_TAIL); + } +} diff --git a/remappedSrc/themixray/repeating/mod/render/RenderHelper.java b/remappedSrc/themixray/repeating/mod/render/RenderHelper.java new file mode 100644 index 0000000..4f80464 --- /dev/null +++ b/remappedSrc/themixray/repeating/mod/render/RenderHelper.java @@ -0,0 +1,200 @@ +package themixray.repeating.mod.render; + +import lombok.experimental.UtilityClass; +import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderContext; +import net.minecraft.util.math.Vec3d; +import themixray.repeating.mod.render.buffer.WorldBuffer; +import themixray.repeating.mod.render.shader.ShaderManager; + +import java.awt.*; + +import static org.lwjgl.opengl.GL33.*; + +@UtilityClass +public class RenderHelper { + public WorldBuffer startLines(WorldRenderContext context) { + glEnable(GL_LINE_SMOOTH); + return new WorldBuffer(GL_LINES, ShaderManager.getPositionColorShader(), context); + } + + public void endLines(WorldBuffer buffer) { + glEnable(GL_BLEND); + glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA); + glDepthMask(false); + buffer.draw(); + glDepthMask(true); + glDisable(GL_BLEND); + } + + public void drawLine(WorldBuffer buffer, float x1, float y1, float z1, float x2, float y2, float z2, Color color) { + buffer.vert(x1, y1, z1, color.getRed() / 255f, color.getGreen() / 255f, color.getBlue() / 255f, color.getAlpha() / 255f); + buffer.vert(x2, y2, z2, color.getRed() / 255f, color.getGreen() / 255f, color.getBlue() / 255f, color.getAlpha() / 255f); + } + + public static WorldBuffer startTri(WorldRenderContext context) { + return new WorldBuffer(GL_TRIANGLES, ShaderManager.getPositionColorShader(), context); + } + + public static void endTri(WorldBuffer buffer) { + //glDepthRange(0, 0.7); + glEnable(GL_BLEND); + glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA); + glDisable(GL_CULL_FACE); + glDepthMask(false); + buffer.draw(); + glDepthMask(true); + glEnable(GL_CULL_FACE); + glDisable(GL_BLEND); + glDepthRange(0, 1); + } + + public void drawTri(WorldBuffer buffer, float x1, float y1, float z1, float x2, float y2, float z2, float x3, float y3, float z3, Color color) { + buffer.vert(x1, y1, z1, color.getRed() / 255f, color.getGreen() / 255f, color.getBlue() / 255f, color.getAlpha() / 255f); + buffer.vert(x2, y2, z2, color.getRed() / 255f, color.getGreen() / 255f, color.getBlue() / 255f, color.getAlpha() / 255f); + buffer.vert(x3, y3, z3, color.getRed() / 255f, color.getGreen() / 255f, color.getBlue() / 255f, color.getAlpha() / 255f); + } + + public static void drawRectFromTri(WorldBuffer buffer, + float x1, float y1, float z1, + float x2, float y2, float z2, + float x3, float y3, float z3, + float x4, float y4, float z4, + Color color) { + drawTri(buffer, + x1, y1, z1, + x2, y2, z2, + x3, y3, z3, + color); + drawTri(buffer, + x3, y3, z3, + x4, y4, z4, + x1, y1, z1, + color); + } + + public void drawRectFromLines(WorldBuffer buffer, + float x1, float y1, float z1, + float x2, float y2, float z2, + float x3, float y3, float z3, + float x4, float y4, float z4, + Color color) { + drawLine(buffer, + x1, y1, z1, + x2, y2, z2, + color); + drawLine(buffer, + x2, y2, z2, + x3, y3, z3, + color); + drawLine(buffer, + x3, y3, z3, + x4, y4, z4, + color); + drawLine(buffer, + x4, y4, z4, + x1, y1, z1, + color); + } + + public void drawBoxFromTri(WorldBuffer buffer, + float x1, float y1, float z1, + float x2, float y2, float z2, + Color color) { + float[][] v = new float[][]{ + new float[]{Math.min(x1, x2), Math.min(y1, y2), Math.min(z1, z2)}, + new float[]{Math.max(x1, x2), Math.max(y1, y2), Math.max(z1, z2)}}; + + drawRectFromTri(buffer, + v[0][0], v[0][1], v[0][2], + v[1][0], v[0][1], v[0][2], + v[1][0], v[1][1], v[0][2], + v[0][0], v[1][1], v[0][2], + color); + + drawRectFromTri(buffer, + v[0][0], v[0][1], v[0][2], + v[1][0], v[0][1], v[0][2], + v[1][0], v[0][1], v[1][2], + v[0][0], v[0][1], v[1][2], + color); + + drawRectFromTri(buffer, + v[0][0], v[0][1], v[0][2], + v[0][0], v[0][1], v[1][2], + v[0][0], v[1][1], v[1][2], + v[0][0], v[1][1], v[0][2], + color); + + drawRectFromTri(buffer, + v[0][0], v[0][1], v[1][2], + v[1][0], v[0][1], v[1][2], + v[1][0], v[1][1], v[1][2], + v[0][0], v[1][1], v[1][2], + color); + + drawRectFromTri(buffer, + v[0][0], v[1][1], v[0][2], + v[1][0], v[1][1], v[0][2], + v[1][0], v[1][1], v[1][2], + v[0][0], v[1][1], v[1][2], + color); + + drawRectFromTri(buffer, + v[1][0], v[0][1], v[0][2], + v[1][0], v[0][1], v[1][2], + v[1][0], v[1][1], v[1][2], + v[1][0], v[1][1], v[0][2], + color); + } + + public void drawBoxFromLines(WorldBuffer buffer, + float x1, float y1, float z1, + float x2, float y2, float z2, + Color color) { + float[][] v = new float[][]{ + new float[]{Math.min(x1, x2), Math.min(y1, y2), Math.min(z1, z2)}, + new float[]{Math.max(x1, x2), Math.max(y1, y2), Math.max(z1, z2)}}; + + drawRectFromLines(buffer, + v[0][0], v[0][1], v[0][2], + v[1][0], v[0][1], v[0][2], + v[1][0], v[1][1], v[0][2], + v[0][0], v[1][1], v[0][2], + color); + + drawRectFromLines(buffer, + v[0][0], v[0][1], v[0][2], + v[1][0], v[0][1], v[0][2], + v[1][0], v[0][1], v[1][2], + v[0][0], v[0][1], v[1][2], + color); + + drawRectFromLines(buffer, + v[0][0], v[0][1], v[0][2], + v[0][0], v[0][1], v[1][2], + v[0][0], v[1][1], v[1][2], + v[0][0], v[1][1], v[0][2], + color); + + drawRectFromLines(buffer, + v[0][0], v[0][1], v[1][2], + v[1][0], v[0][1], v[1][2], + v[1][0], v[1][1], v[1][2], + v[0][0], v[1][1], v[1][2], + color); + + drawRectFromLines(buffer, + v[0][0], v[1][1], v[0][2], + v[1][0], v[1][1], v[0][2], + v[1][0], v[1][1], v[1][2], + v[0][0], v[1][1], v[1][2], + color); + + drawRectFromLines(buffer, + v[1][0], v[0][1], v[0][2], + v[1][0], v[0][1], v[1][2], + v[1][0], v[1][1], v[1][2], + v[1][0], v[1][1], v[0][2], + color); + } +} diff --git a/remappedSrc/themixray/repeating/mod/render/RenderSystem.java b/remappedSrc/themixray/repeating/mod/render/RenderSystem.java new file mode 100644 index 0000000..a5fa15c --- /dev/null +++ b/remappedSrc/themixray/repeating/mod/render/RenderSystem.java @@ -0,0 +1,13 @@ +package themixray.repeating.mod.render; + +import lombok.experimental.UtilityClass; +import themixray.repeating.mod.render.buffer.BufferManager; +import themixray.repeating.mod.render.shader.ShaderManager; + +@UtilityClass +public class RenderSystem { + public static void init() { + BufferManager.init(); + ShaderManager.init(); + } +} diff --git a/remappedSrc/themixray/repeating/mod/render/buffer/BufferManager.java b/remappedSrc/themixray/repeating/mod/render/buffer/BufferManager.java new file mode 100644 index 0000000..76d2853 --- /dev/null +++ b/remappedSrc/themixray/repeating/mod/render/buffer/BufferManager.java @@ -0,0 +1,48 @@ +package themixray.repeating.mod.render.buffer; + +import lombok.experimental.UtilityClass; +import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents; + +import java.nio.FloatBuffer; + +import static org.lwjgl.opengl.GL33.*; + +@UtilityClass +public class BufferManager { + private int vao; + private int vbo; + + private int prevVao; + + public void init() { + ClientLifecycleEvents.CLIENT_STARTED.register(client -> { + vao = glGenVertexArrays(); + vbo = glGenBuffers(); + }); + } + + public static void bindBuffer() { + glBindBuffer(GL_ARRAY_BUFFER, vbo); + } + + public static void unbindBuffer() { + glBindBuffer(GL_ARRAY_BUFFER, 0); + } + + public static void writeBuffer(FloatBuffer buffer) { + glBufferData(GL_ARRAY_BUFFER, buffer, GL_STATIC_DRAW); + } + + public static void draw(int drawMode, int verts) { + glDrawArrays(drawMode, 0, verts); + } + + public static void bind() { + prevVao = glGetInteger(GL_VERTEX_ARRAY_BINDING); + glBindVertexArray(vao); + } + + public static void unbind() { + glBindVertexArray(prevVao); + } +} diff --git a/remappedSrc/themixray/repeating/mod/render/buffer/Vertex.java b/remappedSrc/themixray/repeating/mod/render/buffer/Vertex.java new file mode 100644 index 0000000..5aaa113 --- /dev/null +++ b/remappedSrc/themixray/repeating/mod/render/buffer/Vertex.java @@ -0,0 +1,30 @@ +package themixray.repeating.mod.render.buffer; + +import lombok.Getter; + +public class Vertex { + @Getter + private float x; + @Getter + private float y; + @Getter + private float z; + @Getter + private float r; + @Getter + private float g; + @Getter + private float b; + @Getter + private float a; + + public Vertex(float x, float y, float z, float r, float g, float b, float a) { + this.x = x; + this.y = y; + this.z = z; + this.r = r; + this.g = g; + this.b = b; + this.a = a; + } +} \ No newline at end of file diff --git a/remappedSrc/themixray/repeating/mod/render/buffer/WorldBuffer.java b/remappedSrc/themixray/repeating/mod/render/buffer/WorldBuffer.java new file mode 100644 index 0000000..390f97d --- /dev/null +++ b/remappedSrc/themixray/repeating/mod/render/buffer/WorldBuffer.java @@ -0,0 +1,82 @@ +package themixray.repeating.mod.render.buffer; + +import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderContext; +import net.minecraft.util.math.Vec3d; +import org.apache.commons.lang3.ArrayUtils; +import org.joml.Matrix4f; +import org.lwjgl.BufferUtils; +import themixray.repeating.mod.render.shader.Shader; + +import java.nio.FloatBuffer; +import java.util.ArrayList; +import java.util.List; + +import static org.lwjgl.opengl.GL33.*; + +public class WorldBuffer { + private final List vertices = new ArrayList<>(); + private final int drawMode; + private final Shader shader; + private FloatBuffer projectionMatrix; + private final Vec3d cameraPos; + + public WorldBuffer(int drawMode, Shader shader, WorldRenderContext worldRenderContext) { + this.drawMode = drawMode; + this.shader = shader; + this.cameraPos = worldRenderContext.camera().getPos(); + makeProjectionMatrix(worldRenderContext.projectionMatrix(), worldRenderContext.matrixStack().peek().getPositionMatrix()); + } + + public void vert(float x, float y, float z, float r, float g, float b, float a) { + vertices.add(new Vertex(x - (float) cameraPos.x, y - (float) cameraPos.y, z - (float) cameraPos.z, r, g, b, a)); + } + + public void draw() { + BufferManager.bind(); + BufferManager.bindBuffer(); + + BufferManager.writeBuffer(getBuffer()); + applyProjectionMatrix(); + + glVertexAttribPointer(0, 3, GL_FLOAT, false, 0, 0); + glEnableVertexAttribArray(0); + glVertexAttribPointer(1, 4, GL_FLOAT, false, 0, vertices.size() * 3 * 4L); + glEnableVertexAttribArray(1); + + BufferManager.unbindBuffer(); + + shader.bind(); + BufferManager.draw(drawMode, this.vertices.size()); + shader.unbind(); + + BufferManager.unbind(); + } + + private FloatBuffer getBuffer() { + FloatBuffer floatBuffer = BufferUtils.createFloatBuffer(vertices.size() * 7); + ArrayList floats = new ArrayList<>(); + for (Vertex vertex : vertices) { + floats.add(vertex.getX()); + floats.add(vertex.getY()); + floats.add(vertex.getZ()); + } + for (Vertex vertex : vertices) { + floats.add(vertex.getR()); + floats.add(vertex.getG()); + floats.add(vertex.getB()); + floats.add(vertex.getA()); + } + Float[] floatArray = new Float[floats.size()]; + floats.toArray(floatArray); + floatBuffer.put(ArrayUtils.toPrimitive(floatArray)); + return floatBuffer.flip(); + } + + private void makeProjectionMatrix(Matrix4f projectionMatrix, Matrix4f viewModelMatrix) { + this.projectionMatrix = projectionMatrix.mul(viewModelMatrix).get(BufferUtils.createFloatBuffer(16)); + } + + private void applyProjectionMatrix() { + shader.uniformMatrix4f("u_projection", projectionMatrix); + } +} diff --git a/remappedSrc/themixray/repeating/mod/render/shader/Shader.java b/remappedSrc/themixray/repeating/mod/render/shader/Shader.java new file mode 100644 index 0000000..e11dd9a --- /dev/null +++ b/remappedSrc/themixray/repeating/mod/render/shader/Shader.java @@ -0,0 +1,42 @@ +package themixray.repeating.mod.render.shader; + +import lombok.Getter; + +import java.nio.FloatBuffer; + +import static org.lwjgl.opengl.GL33.*; + +public class Shader { + @Getter + private final int id; + + + public Shader(String name) { + int v = ShaderManager.loadShaderProgram(name, ShaderManager.ShaderType.VERTEX); + int f = ShaderManager.loadShaderProgram(name, ShaderManager.ShaderType.FRAGMENT); + this.id = glCreateProgram(); + glAttachShader(id, v); + glAttachShader(id, f); + glLinkProgram(id); + } + + public void bind() { + glUseProgram(id); + } + + public void unbind() { + glUseProgram(0); + } + + public void uniformMatrix4f(String name, FloatBuffer matrix) { + bind(); + glUniformMatrix4fv(glGetUniformLocation(id, name), false, matrix); + unbind(); + } + + public void uniformValue2f(String name, float value1, float value2) { + bind(); + glUniform2f(glGetUniformLocation(id, name), value1, value2); + unbind(); + } +} diff --git a/remappedSrc/themixray/repeating/mod/render/shader/ShaderManager.java b/remappedSrc/themixray/repeating/mod/render/shader/ShaderManager.java new file mode 100644 index 0000000..f11c98b --- /dev/null +++ b/remappedSrc/themixray/repeating/mod/render/shader/ShaderManager.java @@ -0,0 +1,97 @@ +package themixray.repeating.mod.render.shader; + +import com.mojang.blaze3d.platform.GlStateManager; +import com.mojang.blaze3d.platform.TextureUtil; +import lombok.Getter; +import lombok.SneakyThrows; +import lombok.experimental.UtilityClass; +import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gl.GlImportProcessor; +import net.minecraft.resource.Resource; +import net.minecraft.resource.ResourceFactory; +import net.minecraft.util.Identifier; +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.StringUtils; +import org.jetbrains.annotations.Nullable; +import org.lwjgl.system.MemoryUtil; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.util.Optional; + +import static org.lwjgl.opengl.GL33.*; + +@UtilityClass +public class ShaderManager { + @Getter + private Shader positionColorShader; + + public void init() { + ClientLifecycleEvents.CLIENT_STARTED.register(client -> loadShaders()); + } + + private void loadShaders() { + positionColorShader = new Shader("position_color"); + } + + public int loadShaderProgram(String name, ShaderType type) { + try { + boolean file_present = true; + ResourceFactory resourceFactory = MinecraftClient.getInstance().getResourceManager(); + Optional resource = resourceFactory.getResource(new Identifier("renderer", "shader/" + name + type.fileExtension)); + int i = glCreateShader(type.glType); + if (resource.isPresent()) { + GlStateManager.glShaderSource(i, new GlImportProcessor() { + @SneakyThrows + @Nullable + @Override + public String loadImport(boolean inline, String name) { + return IOUtils.toString(resource.get().getInputStream(), StandardCharsets.UTF_8); + } + }.readSource(readResourceAsString(resource.get().getInputStream()))); + } else file_present = false; + glCompileShader(i); + if (glGetShaderi(i, GL_COMPILE_STATUS) == 0 || !file_present) { + String shaderInfo = StringUtils.trim(glGetShaderInfoLog(i, 32768)); + throw new IOException("Couldn't compile " + type.name + " program (" + name + ") : " + shaderInfo); + } + return i; + } catch (IOException e) { + e.printStackTrace(); + } + return 0; + } + + private String readResourceAsString(InputStream inputStream) { + ByteBuffer byteBuffer = null; + try { + byteBuffer = TextureUtil.readResource(inputStream); + int i = byteBuffer.position(); + byteBuffer.rewind(); + return MemoryUtil.memASCII(byteBuffer, i); + } catch (IOException ignored) { + } finally { + if (byteBuffer != null) { + MemoryUtil.memFree(byteBuffer); + } + } + return null; + } + + public enum ShaderType { + VERTEX("vertex", ".vsh", GL_VERTEX_SHADER), + FRAGMENT("fragment", ".fsh", GL_FRAGMENT_SHADER); + private final String name; + private final String fileExtension; + private final int glType; + + ShaderType(String name, String fileExtension, int glType) { + this.name = name; + this.fileExtension = fileExtension; + this.glType = glType; + } + } +} diff --git a/remappedSrc/themixray/repeating/mod/widget/RecordListWidget.java b/remappedSrc/themixray/repeating/mod/widget/RecordListWidget.java new file mode 100644 index 0000000..1ac42b2 --- /dev/null +++ b/remappedSrc/themixray/repeating/mod/widget/RecordListWidget.java @@ -0,0 +1,150 @@ +package themixray.repeating.mod.widget; + +import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.gui.Drawable; +import net.minecraft.client.gui.Element; +import net.minecraft.client.gui.Selectable; +import net.minecraft.client.gui.screen.narration.NarrationMessageBuilder; +import net.minecraft.client.gui.tooltip.Tooltip; +import net.minecraft.client.gui.widget.*; +import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.text.Text; +import themixray.repeating.mod.Main; +import themixray.repeating.mod.RecordState; +import themixray.repeating.mod.RepeatingScreen; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +public class RecordListWidget extends ScrollableWidget { + private List widgets = new ArrayList<>(); + private boolean focused = false; + + public RecordListWidget(int x, int y, int width, int height) { + super(x,y,width,height,Text.empty()); + } + + @Override + public boolean mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY) { + focused = true; + boolean res = super.mouseDragged(mouseX, mouseY, button, deltaX, deltaY); + focused = false; + return res; + } + + @Override + protected double getDeltaYPerScroll() { + return 10; + } + + @Override + protected void renderContents(DrawContext ctx, int mouseX, int mouseY, float delta) { + int y = 0; + for (RecordWidget wid: widgets) { + wid.method_46419(y); + wid.render(ctx, mouseX, (int) (mouseY + this.getScrollY()), delta); + + y += wid.getHeight(); + y += 2; + } + } + + public void addWidget(RecordState record) { + RecordWidget widget = new RecordWidget(0, 0, width, 55, record, this); + widget.init(null); + widgets.add(0, widget); + } + + public void removeWidget(RecordState record) { + widgets.removeIf(i -> i.getRecord().equals(record)); + } + + @Override + public void setFocused(boolean focused) { + + } + + @Override + public boolean isFocused() { + return focused; + } + + @Override + protected int getContentsHeight() { + return !widgets.isEmpty() ? widgets.size() * 55 + (widgets.size() - 1) * 2 : 0; + } + + public void init(RepeatingScreen screen) { + for (RecordWidget widget : widgets) { + widget.init(screen); + } + + screen.addDrawableChild(this); + } + + @Override + protected void appendClickableNarrations(NarrationMessageBuilder builder) { + + } + + public RecordWidget getWidget(RecordState record) { + for (RecordWidget widget : widgets) { + if (widget.getRecord().equals(record)) { + return widget; + } + } + return null; + } + + public interface transport { + boolean check(ClickableWidget ch); + } + + public boolean checkTransport(transport tr) { + for (RecordWidget wid : widgets) { + for (ClickableWidget child : wid.getChildren()) { + if (tr.check(child)) { + return true; + } + } + } + return false; + } + + public boolean checkTransportNF(transport tr) { + for (RecordWidget wid : widgets) { + for (ClickableWidget child : wid.getChildren()) { + boolean res = tr.check(child); + + if (res) { + child.setFocused(true); + return true; + } else { + child.setFocused(false); + } + } + } + return false; + } + + @Override + public boolean mouseClicked(double mouseX, double mouseY, int button) { + return checkTransportNF((c) -> c.mouseClicked(mouseX, mouseY + this.getScrollY(), button)) || super.mouseClicked(mouseX, mouseY, button); + } + + @Override + public boolean charTyped(char chr, int modifiers) { + return checkTransport((c) -> c.charTyped(chr, modifiers)) || super.charTyped(chr, modifiers); + } + + @Override + public boolean keyPressed(int keyCode, int scanCode, int modifiers) { + return checkTransport((c) -> c.keyPressed(keyCode, scanCode, modifiers)) || super.keyPressed(keyCode, scanCode, modifiers); + } + + @Override + public boolean keyReleased(int keyCode, int scanCode, int modifiers) { + return checkTransport((c) -> c.keyReleased(keyCode, scanCode, modifiers)) || super.keyReleased(keyCode, scanCode, modifiers); + } +} diff --git a/remappedSrc/themixray/repeating/mod/widget/RecordWidget.java b/remappedSrc/themixray/repeating/mod/widget/RecordWidget.java new file mode 100644 index 0000000..bef439a --- /dev/null +++ b/remappedSrc/themixray/repeating/mod/widget/RecordWidget.java @@ -0,0 +1,196 @@ +package themixray.repeating.mod.widget; + +import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.gui.Drawable; +import net.minecraft.client.gui.tooltip.Tooltip; +import net.minecraft.client.gui.widget.*; +import net.minecraft.text.Style; +import net.minecraft.text.Text; +import themixray.repeating.mod.Main; +import themixray.repeating.mod.RecordState; +import themixray.repeating.mod.RenderListener; +import themixray.repeating.mod.RepeatingScreen; + +import java.awt.*; +import java.io.IOException; +import java.util.ArrayList; + +import java.util.List; +import java.util.function.Consumer; + +public class RecordWidget implements Drawable, Widget { + private RecordState record; + + private List children; + + private RecordListWidget parent; + + private int x; + private int y; + private int width; + private int height; + + public RecordWidget(int x, int y, int width, int height, RecordState record, RecordListWidget parent) { + this.parent = parent; + this.record = record; + + this.x = x; + this.y = y; + this.width = width; + this.height = height; + + this.children = new ArrayList<>(); + } + + public void method_46421(int x) { + this.x = x; + } + public void method_46419(int y) { + this.y = y; + } + public int getX() { + return x; + } + public int getY() { + return y; + } + public int getWidth() { + return width; + } + public int getHeight() { + return height; + } + + public List getChildren() { + return children; + } + + @Override + public void forEachChild(Consumer consumer) { + children.forEach(consumer); + } + + public void init(RepeatingScreen screen) { + this.children = new ArrayList<>(); + + TextFieldWidget name_widget = new TextFieldWidget( + Main.client.textRenderer, + parent.getX() + getX() + 5, + parent.getY() + getY() + 5, + 102, + 10, + Text.empty()); + + name_widget.setText(record.getName()); + + name_widget.setChangedListener((s) -> { + record.setName(s); + try { + record.save(); + } catch (IOException e) { + throw new RuntimeException(e); + } + }); + + children.add(name_widget); + + ButtonWidget delete_button = ButtonWidget.builder(Text.translatable("text.repeating-mod.delete"), (i) -> { + record.remove(); + }).dimensions(parent.getX() + getX() + 110,parent.getY() + getY() + 4, 65, 13).build(); + + children.add(delete_button); + + ButtonWidget export_button = ButtonWidget.builder(Text.translatable("text.repeating-mod.export"), (i) -> { + if (Desktop.isDesktopSupported()) { + Desktop desk = Desktop.getDesktop(); + try { + desk.browseFileDirectory(record.getFile()); + } catch (Exception e) { + try { + desk.browse(record.getFile().toURI()); + } catch (IOException ex) { + throw new RuntimeException(ex); + } + } + } + }).dimensions(parent.getX() + getX() + 110,parent.getY() + getY() + 4 + 14, 65, 13).build(); + + children.add(export_button); + + ButtonWidget replay_button = ButtonWidget.builder(Text.translatable("text.repeating-mod.start"), (i) -> { + if (Main.me.is_replaying) { + Main.me.stopReplay(); + } + + i.setMessage(Text.translatable("text.repeating-mod.stop")); + Main.me.now_record = record; + Main.me.startReplay(); + }).dimensions(parent.getX() + getX() + 110,parent.getY() + getY() + 4 + 28, 65, 13) + .tooltip(Tooltip.of(Text.translatable("text.repeating-mod.replay_tooltip"))).build(); + + children.add(replay_button); + } + + public RecordState getRecord() { + return record; + } + + public void drawText(int x, int y, DrawContext ctx, List lines, float size, int line_height, boolean shadow) { + ctx.getMatrices().push(); + ctx.getMatrices().scale(size, size, size); + + int now_y = y; + + for (Text line : lines) { + ctx.drawText(Main.client.textRenderer, line, (int) (x / size), (int) (now_y / size), line.getStyle().getColor().getRgb(), shadow); + now_y += line_height; + } + + ctx.getMatrices().pop(); + } + + @Override + public void render(DrawContext ctx, int mouseX, int mouseY, float delta) { + int color = record.equals(Main.me.now_record) ? 0xFF555555 : 0xFF333333; + + ctx.fill(parent.getX() + getX(), + parent.getY() + getY(), + parent.getX() + getX() + getWidth(), + parent.getY() + getY() + getHeight(), + color); + + drawText( + parent.getX() + getX() + 5, + parent.getY() + getY() + 5 + 12, + ctx, List.of( + Text.translatable("text.repeating-mod.recorded_at") + .append(": ") + .styled((s) -> s.withColor(0xbbbbbb)), + Text.literal(RecordState.DATE_FORMAT.format(record.getDate())).styled((s) -> s.withColor(0xffffff)), + Text.translatable("text.repeating-mod.author") + .append(": ") + .styled((s) -> s.withColor(0xbbbbbb)), + Text.literal(record.getAuthor()).styled((s) -> s.withColor(0xffffff)) + ), 1, + 9, + false); + + if (!children.isEmpty()) { + ClickableWidget name_widget = children.get(0); + name_widget.setPosition(parent.getX() + getX() + 5, parent.getY() + getY() + 5); + + ClickableWidget delete_button = children.get(1); + delete_button.setPosition(parent.getX() + getX() + 110,parent.getY() + getY() + 4); + + ClickableWidget export_button = children.get(2); + export_button.setPosition(parent.getX() + getX() + 110,parent.getY() + getY() + 4 + 14); + + ClickableWidget replay_button = children.get(3); + replay_button.setPosition(parent.getX() + getX() + 110,parent.getY() + getY() + 4 + 28); + } + + for (ClickableWidget child : children) { + child.render(ctx, mouseX, mouseY, delta); + } + } +} diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..75c4d72 --- /dev/null +++ b/settings.gradle @@ -0,0 +1,10 @@ +pluginManagement { + repositories { + maven { + name = 'Fabric' + url = 'https://maven.fabricmc.net/' + } + mavenCentral() + gradlePluginPortal() + } +} \ No newline at end of file diff --git a/src/main/java/themixray/repeating/mod/RepeatingScreen.java b/src/main/java/themixray/repeating/mod/RepeatingScreen.java index 46d5877..7d13d12 100644 --- a/src/main/java/themixray/repeating/mod/RepeatingScreen.java +++ b/src/main/java/themixray/repeating/mod/RepeatingScreen.java @@ -51,7 +51,7 @@ public class RepeatingScreen extends Screen { @Override public void render(DrawContext context, int mouseX, int mouseY, float delta) { - renderBackground(context); + renderBackground(context,mouseX,mouseY,delta); for (RenderListener l : render_listeners) { if (l.beforeRender()) { @@ -132,12 +132,6 @@ public class RepeatingScreen extends Screen { super.onDrag(mouseX, mouseY, deltaX, deltaY); applyValue(); } - - @Override - public void render(DrawContext context, int mouseX, int mouseY, float delta) { - super.render(context, mouseX, mouseY, delta); - updateMessage(); - } }; pos_delay_slider.setTooltip(Tooltip.of(Text.translatable("text.repeating-mod.pos_delay_tooltip"))); diff --git a/src/main/java/themixray/repeating/mod/mixin/EntityMixin.java b/src/main/java/themixray/repeating/mod/mixin/EntityMixin.java index 8d45d11..eed7b58 100644 --- a/src/main/java/themixray/repeating/mod/mixin/EntityMixin.java +++ b/src/main/java/themixray/repeating/mod/mixin/EntityMixin.java @@ -16,13 +16,14 @@ public abstract class EntityMixin { @Inject(at = @At(value = "HEAD"), method = "setSprinting", cancellable = true) private void onSprint(boolean sprinting,CallbackInfo ci) { - if (getUuid().equals(Main.client.player.getUuid())) { - if (Main.me.is_replaying) { - if (Main.input_replay != null && - Main.input_replay.sprinting != null && - Main.input_replay.sprinting != sprinting) { - ci.cancel(); - return; + if (Main.client.player != null) { + if (getUuid().equals(Main.client.player.getUuid())) { + if (Main.me.is_replaying) { + if (Main.input_replay != null && + Main.input_replay.sprinting != null && + Main.input_replay.sprinting != sprinting) { + ci.cancel(); + } } } } diff --git a/src/main/java/themixray/repeating/mod/mixin/NetworkMixin.java b/src/main/java/themixray/repeating/mod/mixin/NetworkMixin.java index 1ffb3c6..4f85878 100644 --- a/src/main/java/themixray/repeating/mod/mixin/NetworkMixin.java +++ b/src/main/java/themixray/repeating/mod/mixin/NetworkMixin.java @@ -13,17 +13,17 @@ import java.util.function.BooleanSupplier; @Mixin(ClientPlayNetworkHandler.class) public abstract class NetworkMixin { - @Inject(at = @At(value = "HEAD"), method = "sendPacket(Lnet/minecraft/network/packet/Packet;)V") - private void onSendPacket1Head(Packet packet, - CallbackInfo ci) { - - } - - @Inject(at = @At(value = "HEAD"), method = "sendPacket(Lnet/minecraft/network/packet/Packet;Ljava/util/function/BooleanSupplier;Ljava/time/Duration;)V") - private void onSendPacket2Head(Packet packet, - BooleanSupplier sendCondition, - Duration expirationTime, - CallbackInfo ci) { - - } +// @Inject(at = @At(value = "HEAD"), method = "sendPacket(Lnet/minecraft/network/packet/Packet;)V") +// private void onSendPacket1Head(Packet packet, +// CallbackInfo ci) { +// +// } +// +// @Inject(at = @At(value = "HEAD"), method = "sendPacket(Lnet/minecraft/network/packet/Packet;Ljava/util/function/BooleanSupplier;Ljava/time/Duration;)V") +// private void onSendPacket2Head(Packet packet, +// BooleanSupplier sendCondition, +// Duration expirationTime, +// CallbackInfo ci) { +// +// } }