1.20.4 compatibility

This commit is contained in:
MeexReay 2024-04-22 18:17:31 +03:00
parent c5132221ae
commit 27627e8404
38 changed files with 2790 additions and 31 deletions

83
build.gradle Normal file
View File

@ -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.
}
}

View File

@ -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

BIN
gradle/wrapper/gradle-wrapper.jar vendored Normal file

Binary file not shown.

View File

@ -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

249
gradlew vendored Normal file
View File

@ -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" "$@"

92
gradlew.bat vendored Normal file
View File

@ -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

View File

@ -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<String,String> data;
public EasyConfig(File f, Map<String,String> 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<String,String> m:def.entrySet())
if (!data.containsKey(m.getKey()))
data.put(m.getKey(),m.getValue());
save();
}
public EasyConfig(Path f, Map<String,String> def) {
this(f.toFile(),def);
}
public EasyConfig(String parent,String child,Map<String,String> def) {
this(new File(parent,child),def);
}
public EasyConfig(File parent,String child,Map<String,String> def) {
this(new File(parent,child),def);
}
public EasyConfig(Path parent,String child,Map<String,String> 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<String,String> p) {
StringBuilder t = new StringBuilder();
for (Map.Entry<String,String> e:p.entrySet())
t.append(e.getKey()).append("=").append(e.getValue()).append("\n");
return t.toString();
}
private Map<String,String> toMap(String j) {
Map<String,String> m = new HashMap<>();
for (String l:j.split("\n")) {
String s[] = l.split("=");
m.put(s[0],s[1]);
}
return m;
}
private Map<String,String> read() {
try {
return toMap(Files.readString(path));
} catch (IOException e) {
e.printStackTrace();
}
return new HashMap<>();
}
private void write(Map<String,String> p) {
try {
Files.write(path, toText(p).getBytes());
} catch (IOException e) {
e.printStackTrace();
}
}
}

View File

@ -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<String,String> 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<RecordEvent> 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)));
}
}

View File

@ -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<RecordState> 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<RecordState> 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;
}
}

View File

@ -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<RecordEvent> events;
private Vec3d start_record_pos;
private Vec3d finish_record_pos;
public RecordState(File file,
String name,
Date date,
String author,
List<RecordEvent> 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<RecordEvent> 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<RecordEvent> 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<String> lines = List.of(text.split("\n"));
List<String> 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<String> event_lines = lines.subList(4,lines.size());
List<RecordEvent> 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);
}
}

View File

@ -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);
}

View File

@ -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<RenderListener> 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 extends Element & Drawable & Selectable> T addDrawableChild(T drawableElement) {
return super.addDrawableChild(drawableElement);
}
public <T extends Drawable> T addDrawable(T drawable) {
return super.addDrawable(drawable);
}
public <T extends Element & Selectable> T addSelectableChild(T child) {
return super.addSelectableChild(child);
}
public void remove(Element child) {
super.remove(child);
}
}

View File

@ -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<TickTask> 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++;
}
}

View File

@ -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";
}
}

View File

@ -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";
}
}

View File

@ -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";
}
}

View File

@ -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;
}
}

View File

@ -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";
}
}

View File

@ -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";
}
}

View File

@ -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);
}
}

View File

@ -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();
}
}
}
}
}
}

View File

@ -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();
}
}
}
}

View File

@ -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);
}
}

View File

@ -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<ServerPlayPacketListener> packet,
// BooleanSupplier sendCondition,
// Duration expirationTime,
// CallbackInfo ci) {
//
// }
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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();
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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<Vertex> 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<Float> 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);
}
}

View File

@ -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();
}
}

View File

@ -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> 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;
}
}
}

View File

@ -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<RecordWidget> 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);
}
}

View File

@ -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<ClickableWidget> 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<ClickableWidget> getChildren() {
return children;
}
@Override
public void forEachChild(Consumer<ClickableWidget> 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<Text> 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);
}
}
}

10
settings.gradle Normal file
View File

@ -0,0 +1,10 @@
pluginManagement {
repositories {
maven {
name = 'Fabric'
url = 'https://maven.fabricmc.net/'
}
mavenCentral()
gradlePluginPortal()
}
}

View File

@ -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")));

View File

@ -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();
}
}
}
}

View File

@ -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<ServerPlayPacketListener> 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<ServerPlayPacketListener> packet,
// BooleanSupplier sendCondition,
// Duration expirationTime,
// CallbackInfo ci) {
//
// }
}