From 27627e8404d5f3881bc0e46e34ddfb7c218bb693 Mon Sep 17 00:00:00 2001 From: MeexReay Date: Mon, 22 Apr 2024 18:17:31 +0300 Subject: [PATCH] 1.20.4 compatibility --- build.gradle | 83 +++++ gradle.properties | 8 +- gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 43453 bytes gradle/wrapper/gradle-wrapper.properties | 7 + gradlew | 249 ++++++++++++++ gradlew.bat | 92 +++++ .../themixray/repeating/mod/EasyConfig.java | 103 ++++++ remappedSrc/themixray/repeating/mod/Main.java | 325 ++++++++++++++++++ .../themixray/repeating/mod/RecordList.java | 77 +++++ .../themixray/repeating/mod/RecordState.java | 169 +++++++++ .../repeating/mod/RenderListener.java | 11 + .../repeating/mod/RepeatingScreen.java | 193 +++++++++++ .../themixray/repeating/mod/TickTask.java | 94 +++++ .../mod/event/RecordBlockBreakEvent.java | 32 ++ .../mod/event/RecordBlockInteractEvent.java | 46 +++ .../repeating/mod/event/RecordDelayEvent.java | 29 ++ .../repeating/mod/event/RecordEvent.java | 28 ++ .../repeating/mod/event/RecordInputEvent.java | 147 ++++++++ .../repeating/mod/event/RecordMoveEvent.java | 42 +++ .../repeating/mod/mixin/ClientMixin.java | 24 ++ .../repeating/mod/mixin/EntityMixin.java | 31 ++ .../repeating/mod/mixin/InputMixin.java | 20 ++ .../repeating/mod/mixin/MovementMixin.java | 44 +++ .../repeating/mod/mixin/NetworkMixin.java | 29 ++ .../repeating/mod/mixin/RendererMixin.java | 21 ++ .../repeating/mod/render/RenderHelper.java | 200 +++++++++++ .../repeating/mod/render/RenderSystem.java | 13 + .../mod/render/buffer/BufferManager.java | 48 +++ .../repeating/mod/render/buffer/Vertex.java | 30 ++ .../mod/render/buffer/WorldBuffer.java | 82 +++++ .../repeating/mod/render/shader/Shader.java | 42 +++ .../mod/render/shader/ShaderManager.java | 97 ++++++ .../mod/widget/RecordListWidget.java | 150 ++++++++ .../repeating/mod/widget/RecordWidget.java | 196 +++++++++++ settings.gradle | 10 + .../repeating/mod/RepeatingScreen.java | 8 +- .../repeating/mod/mixin/EntityMixin.java | 15 +- .../repeating/mod/mixin/NetworkMixin.java | 26 +- 38 files changed, 2790 insertions(+), 31 deletions(-) create mode 100644 build.gradle create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100644 gradlew create mode 100644 gradlew.bat create mode 100644 remappedSrc/themixray/repeating/mod/EasyConfig.java create mode 100644 remappedSrc/themixray/repeating/mod/Main.java create mode 100644 remappedSrc/themixray/repeating/mod/RecordList.java create mode 100644 remappedSrc/themixray/repeating/mod/RecordState.java create mode 100644 remappedSrc/themixray/repeating/mod/RenderListener.java create mode 100644 remappedSrc/themixray/repeating/mod/RepeatingScreen.java create mode 100644 remappedSrc/themixray/repeating/mod/TickTask.java create mode 100644 remappedSrc/themixray/repeating/mod/event/RecordBlockBreakEvent.java create mode 100644 remappedSrc/themixray/repeating/mod/event/RecordBlockInteractEvent.java create mode 100644 remappedSrc/themixray/repeating/mod/event/RecordDelayEvent.java create mode 100644 remappedSrc/themixray/repeating/mod/event/RecordEvent.java create mode 100644 remappedSrc/themixray/repeating/mod/event/RecordInputEvent.java create mode 100644 remappedSrc/themixray/repeating/mod/event/RecordMoveEvent.java create mode 100644 remappedSrc/themixray/repeating/mod/mixin/ClientMixin.java create mode 100644 remappedSrc/themixray/repeating/mod/mixin/EntityMixin.java create mode 100644 remappedSrc/themixray/repeating/mod/mixin/InputMixin.java create mode 100644 remappedSrc/themixray/repeating/mod/mixin/MovementMixin.java create mode 100644 remappedSrc/themixray/repeating/mod/mixin/NetworkMixin.java create mode 100644 remappedSrc/themixray/repeating/mod/mixin/RendererMixin.java create mode 100644 remappedSrc/themixray/repeating/mod/render/RenderHelper.java create mode 100644 remappedSrc/themixray/repeating/mod/render/RenderSystem.java create mode 100644 remappedSrc/themixray/repeating/mod/render/buffer/BufferManager.java create mode 100644 remappedSrc/themixray/repeating/mod/render/buffer/Vertex.java create mode 100644 remappedSrc/themixray/repeating/mod/render/buffer/WorldBuffer.java create mode 100644 remappedSrc/themixray/repeating/mod/render/shader/Shader.java create mode 100644 remappedSrc/themixray/repeating/mod/render/shader/ShaderManager.java create mode 100644 remappedSrc/themixray/repeating/mod/widget/RecordListWidget.java create mode 100644 remappedSrc/themixray/repeating/mod/widget/RecordWidget.java create mode 100644 settings.gradle 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 0000000000000000000000000000000000000000..e6441136f3d4ba8a0da8d277868979cfbc8ad796 GIT binary patch literal 43453 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 Vo0ScuU6DbNL5P=uVd@+Z4*=P9Hgf;~ literal 0 HcmV?d00001 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) { +// +// } }