diff --git a/.gitea/workflows/java.yml b/.gitea/workflows/java.yml new file mode 100644 index 0000000..5c47e28 --- /dev/null +++ b/.gitea/workflows/java.yml @@ -0,0 +1,29 @@ +on: [ push ] + +name: Build fabric mod + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up JDK 21 + uses: actions/setup-java@v4 + with: + java-version: '21' + distribution: 'temurin' + + - name: Setup Gradle 8.12.1 + uses: gradle/actions/setup-gradle@af1da67850ed9a4cedd57bfd976089dd991e2582 + with: + gradle-version: '8.12.1' + + - name: Build with Gradle Wrapper + run: chmod +x ./gradlew; ./gradlew build + + - name: Upload artifact + uses: actions/upload-artifact@v3 + with: + name: build + path: build/libs/* \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..34d600a --- /dev/null +++ b/.gitignore @@ -0,0 +1,42 @@ +# gradle + +.gradle/ +build/ +out/ +classes/ + +# eclipse + +*.launch + +# idea + +.idea/ +*.iml +*.ipr +*.iws + +# vscode + +.settings/ +.vscode/ +bin/ +.classpath +.project + +# macos + +*.DS_Store + +# fabric + +run/ + +# java + +hs_err_*.log +replay_*.log +*.hprof +*.jfr + +remappedSrc/ diff --git a/LICENSE b/LICENSE index 0e259d4..07b7a81 100644 --- a/LICENSE +++ b/LICENSE @@ -1,121 +1,13 @@ -Creative Commons Legal Code + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + Version 2, December 2004 -CC0 1.0 Universal +Copyright (C) 2004 Sam Hocevar - CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE - LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN - ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS - INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES - REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS - PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM - THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED - HEREUNDER. +Everyone is permitted to copy and distribute verbatim or modified +copies of this license document, and changing it is allowed as long +as the name is changed. -Statement of Purpose + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION -The laws of most jurisdictions throughout the world automatically confer -exclusive Copyright and Related Rights (defined below) upon the creator -and subsequent owner(s) (each and all, an "owner") of an original work of -authorship and/or a database (each, a "Work"). - -Certain owners wish to permanently relinquish those rights to a Work for -the purpose of contributing to a commons of creative, cultural and -scientific works ("Commons") that the public can reliably and without fear -of later claims of infringement build upon, modify, incorporate in other -works, reuse and redistribute as freely as possible in any form whatsoever -and for any purposes, including without limitation commercial purposes. -These owners may contribute to the Commons to promote the ideal of a free -culture and the further production of creative, cultural and scientific -works, or to gain reputation or greater distribution for their Work in -part through the use and efforts of others. - -For these and/or other purposes and motivations, and without any -expectation of additional consideration or compensation, the person -associating CC0 with a Work (the "Affirmer"), to the extent that he or she -is an owner of Copyright and Related Rights in the Work, voluntarily -elects to apply CC0 to the Work and publicly distribute the Work under its -terms, with knowledge of his or her Copyright and Related Rights in the -Work and the meaning and intended legal effect of CC0 on those rights. - -1. Copyright and Related Rights. A Work made available under CC0 may be -protected by copyright and related or neighboring rights ("Copyright and -Related Rights"). Copyright and Related Rights include, but are not -limited to, the following: - - i. the right to reproduce, adapt, distribute, perform, display, - communicate, and translate a Work; - ii. moral rights retained by the original author(s) and/or performer(s); -iii. publicity and privacy rights pertaining to a person's image or - likeness depicted in a Work; - iv. rights protecting against unfair competition in regards to a Work, - subject to the limitations in paragraph 4(a), below; - v. rights protecting the extraction, dissemination, use and reuse of data - in a Work; - vi. database rights (such as those arising under Directive 96/9/EC of the - European Parliament and of the Council of 11 March 1996 on the legal - protection of databases, and under any national implementation - thereof, including any amended or successor version of such - directive); and -vii. other similar, equivalent or corresponding rights throughout the - world based on applicable law or treaty, and any national - implementations thereof. - -2. Waiver. To the greatest extent permitted by, but not in contravention -of, applicable law, Affirmer hereby overtly, fully, permanently, -irrevocably and unconditionally waives, abandons, and surrenders all of -Affirmer's Copyright and Related Rights and associated claims and causes -of action, whether now known or unknown (including existing as well as -future claims and causes of action), in the Work (i) in all territories -worldwide, (ii) for the maximum duration provided by applicable law or -treaty (including future time extensions), (iii) in any current or future -medium and for any number of copies, and (iv) for any purpose whatsoever, -including without limitation commercial, advertising or promotional -purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each -member of the public at large and to the detriment of Affirmer's heirs and -successors, fully intending that such Waiver shall not be subject to -revocation, rescission, cancellation, termination, or any other legal or -equitable action to disrupt the quiet enjoyment of the Work by the public -as contemplated by Affirmer's express Statement of Purpose. - -3. Public License Fallback. Should any part of the Waiver for any reason -be judged legally invalid or ineffective under applicable law, then the -Waiver shall be preserved to the maximum extent permitted taking into -account Affirmer's express Statement of Purpose. In addition, to the -extent the Waiver is so judged Affirmer hereby grants to each affected -person a royalty-free, non transferable, non sublicensable, non exclusive, -irrevocable and unconditional license to exercise Affirmer's Copyright and -Related Rights in the Work (i) in all territories worldwide, (ii) for the -maximum duration provided by applicable law or treaty (including future -time extensions), (iii) in any current or future medium and for any number -of copies, and (iv) for any purpose whatsoever, including without -limitation commercial, advertising or promotional purposes (the -"License"). The License shall be deemed effective as of the date CC0 was -applied by Affirmer to the Work. Should any part of the License for any -reason be judged legally invalid or ineffective under applicable law, such -partial invalidity or ineffectiveness shall not invalidate the remainder -of the License, and in such case Affirmer hereby affirms that he or she -will not (i) exercise any of his or her remaining Copyright and Related -Rights in the Work or (ii) assert any associated claims and causes of -action with respect to the Work, in either case contrary to Affirmer's -express Statement of Purpose. - -4. Limitations and Disclaimers. - - a. No trademark or patent rights held by Affirmer are waived, abandoned, - surrendered, licensed or otherwise affected by this document. - b. Affirmer offers the Work as-is and makes no representations or - warranties of any kind concerning the Work, express, implied, - statutory or otherwise, including without limitation warranties of - title, merchantability, fitness for a particular purpose, non - infringement, or the absence of latent or other defects, accuracy, or - the present or absence of errors, whether or not discoverable, all to - the greatest extent permissible under applicable law. - c. Affirmer disclaims responsibility for clearing rights of other persons - that may apply to the Work or any use thereof, including without - limitation any person's Copyright and Related Rights in the Work. - Further, Affirmer disclaims responsibility for obtaining any necessary - consents, permissions or other rights required for any use of the - Work. - d. Affirmer understands and acknowledges that Creative Commons is not a - party to this document and has no duty or obligation with respect to - this CC0 or use of the Work. + 0. You just DO WHAT THE FUCK YOU WANT TO. \ No newline at end of file diff --git a/README.md b/README.md index e082ffb..ca695e9 100644 --- a/README.md +++ b/README.md @@ -2,16 +2,49 @@ This mod can record your movements and play them back. -![Preview gif](gif.gif) +## How to use -## Controls +Simply start recording, perform any actions, stop the recording, return to the starting point, and then you can replay everything you did. + +### Showcase + +![Preview gif](img/preview.gif) + +This is how menu looks like: + +![repeating mod menu](img/menu.png) + + +### Controls + +Default Repeating Mod keys ``` Menu | J -Toggle recording | not specified -Toggle replay | not specified +Toggle recording | not specified by default +Toggle replay | not specified by default ``` -## Menu +## Where to download -![Repeating menu](https://github.com/MeexReay/repeating-mod/assets/127148610/da923fe5-d44d-421b-b601-2a65cb5543eb) +### Stable releases + +You can find stable releases on [Modrinth](https://modrinth.com/mod/repeating-mod/versions) or on [Releases page](https://git.meex.lol/MeexReay/repeating_mod/releases) + +### Development artifacts + +Artifacts are automatically built with [Gitea Actions](https://git.meex.lol/MeexReay/repeating_mod/actions) on each commit. \ +[Download latest artifact](https://git.meex.lol/MeexReay/repeating_mod/actions/runs/latest/artifacts/build) + +## Roadmap + +- [ ] relative mode for repeating actions (like mining) +- [ ] record mouse and keyboard in gui +- [ ] practice mode (like in geometry dash but for parkours) + +### Contributing + +If you would like to contribute to the project, feel free to fork the repository and submit a pull request. + +### License +This project is licensed under the WTFPL License diff --git a/build.gradle b/build.gradle index 3c6b0f5..ffbc124 100644 --- a/build.gradle +++ b/build.gradle @@ -1,21 +1,31 @@ plugins { - id 'fabric-loom' version '1.1-SNAPSHOT' + id 'fabric-loom' version "${loom_version}" 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. - maven { url 'https://maven.wispforest.io' } } dependencies { + compileOnly 'org.projectlombok:lombok:1.18.38' + annotationProcessor 'org.projectlombok:lombok:1.18.38' + + //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" @@ -23,25 +33,6 @@ dependencies { // Fabric API. This is technically optional, but you probably want it anyway. modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}" - - // Uncomment the following line to enable the deprecated Fabric API modules. - // These are included in the Fabric API production distribution and allow you to update your mod to the latest modules at a later more convenient time. - - // modImplementation "net.fabricmc.fabric-api:fabric-api-deprecated:${project.fabric_version}" - - modImplementation "io.wispforest:owo-lib:${project.owo_version}" - // only if you plan to use owo-config - annotationProcessor "io.wispforest:owo-lib:${project.owo_version}" - - // include this if you don't want force your users to install owo - // sentinel will warn them and give the option to download it automatically - include "io.wispforest:owo-sentinel:${project.owo_version}" - // https://mvnrepository.com/artifact/com.googlecode.json-simple/json-simple - implementation group: 'com.googlecode.json-simple', name: 'json-simple', version: '1.1.1' -} - -base { - archivesName = project.archives_base_name } processResources { @@ -53,8 +44,7 @@ processResources { } tasks.withType(JavaCompile).configureEach { - // Minecraft 1.18 (1.18-pre2) upwards uses Java 17. - it.options.release = 17 + it.options.release = 21 } java { @@ -63,20 +53,21 @@ java { // If you remove this line, sources will not be generated. withSourcesJar() - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 + sourceCompatibility = JavaVersion.VERSION_21 + targetCompatibility = JavaVersion.VERSION_21 } jar { from("LICENSE") { - rename { "${it}_${base.archivesName.get()}"} + rename { "${it}_${project.base.archivesName.get()}"} } } // configure the maven publication publishing { publications { - mavenJava(MavenPublication) { + create("mavenJava", MavenPublication) { + artifactId = project.archives_base_name from components.java } } @@ -88,4 +79,4 @@ publishing { // The repositories here will be used for publishing your artifact, not for // retrieving dependencies. } -} +} \ No newline at end of file diff --git a/gif.gif b/gif.gif deleted file mode 100644 index 8e25cbf..0000000 Binary files a/gif.gif and /dev/null differ diff --git a/gradle.properties b/gradle.properties index 2bf89a5..780fbef 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,19 +1,20 @@ -# Done to increase the memory available to gradle. -org.gradle.jvmargs=-Xmx1G -org.gradle.parallel=true - -# Fabric Properties -# check these on https://fabricmc.net/develop -minecraft_version=1.19.3 -yarn_mappings=1.19.3+build.1 -loader_version=0.14.17 - -# Mod Properties -mod_version = 1.0.0 -maven_group = themixray.repeating.mod -archives_base_name = repeating-mod - -# Dependencies -fabric_version=0.76.1+1.19.3 - -owo_version=0.10.3+1.19.3 +# Done to increase the memory available to gradle. +org.gradle.jvmargs=-Xmx1G +org.gradle.parallel=true + +# Fabric Properties +# check these on https://fabricmc.net/develop +minecraft_version=1.21.6 +yarn_mappings=1.21.6+build.1 +loader_version=0.16.14 +loom_version=1.10-SNAPSHOT + +# Fabric API +fabric_version=0.127.0+1.21.6 + +# Mod Properties +mod_version = 1.1.2+1.21.6 +maven_group = ru.themixray +archives_base_name = repeating-mod + +# Compatible with: 1.21.6 \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index ccebba7..a4b76b9 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index fc10b60..e18bc25 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.12.1-bin.zip networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew old mode 100755 new mode 100644 index 79a61d4..1aa94a4 --- a/gradlew +++ b/gradlew @@ -83,10 +83,8 @@ done # This is normally unused # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit - -# 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"' +# 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 @@ -133,10 +131,13 @@ location of your Java installation." fi else JAVACMD=java - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + 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. @@ -144,7 +145,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac @@ -152,7 +153,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then '' | soft) :;; #( *) # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -197,11 +198,15 @@ if "$cygwin" || "$msys" ; then done fi -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. + +# 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" \ diff --git a/gradlew.bat b/gradlew.bat index 6689b85..7101f8e 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -43,11 +43,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +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 @@ -57,11 +57,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +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 diff --git a/img/menu.png b/img/menu.png new file mode 100644 index 0000000..5cf7928 Binary files /dev/null and b/img/menu.png differ diff --git a/img/preview.gif b/img/preview.gif new file mode 100644 index 0000000..fbef477 Binary files /dev/null and b/img/preview.gif differ diff --git a/settings.gradle b/settings.gradle index b02216b..75c4d72 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,10 +1,10 @@ pluginManagement { - repositories { - maven { - name = 'Fabric' - url = 'https://maven.fabricmc.net/' - } - mavenCentral() - gradlePluginPortal() - } -} + repositories { + maven { + name = 'Fabric' + url = 'https://maven.fabricmc.net/' + } + mavenCentral() + gradlePluginPortal() + } +} \ No newline at end of file diff --git a/shell.nix b/shell.nix new file mode 100644 index 0000000..27efeb0 --- /dev/null +++ b/shell.nix @@ -0,0 +1,6 @@ +{ pkgs ? import {} }: +pkgs.mkShell { + shellHook = '' + export LD_LIBRARY_PATH="''${LD_LIBRARY_PATH}''${LD_LIBRARY_PATH:+:}${pkgs.libglvnd}/lib" + ''; +} \ No newline at end of file diff --git a/src/main/java/ru/themixray/repeating_mod/EasyConfig.java b/src/main/java/ru/themixray/repeating_mod/EasyConfig.java new file mode 100644 index 0000000..b592deb --- /dev/null +++ b/src/main/java/ru/themixray/repeating_mod/EasyConfig.java @@ -0,0 +1,103 @@ +package ru.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/src/main/java/ru/themixray/repeating_mod/Main.java b/src/main/java/ru/themixray/repeating_mod/Main.java new file mode 100644 index 0000000..296904c --- /dev/null +++ b/src/main/java/ru/themixray/repeating_mod/Main.java @@ -0,0 +1,309 @@ +package ru.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 ru.themixray.repeating_mod.event.events.DelayEvent; +import ru.themixray.repeating_mod.event.RecordEvent; +import ru.themixray.repeating_mod.event.events.InputEvent; +import ru.themixray.repeating_mod.event.events.MoveEvent; +import ru.themixray.repeating_mod.render.RenderHelper; +import ru.themixray.repeating_mod.render.RenderSystem; +import ru.themixray.repeating_mod.render.buffer.WorldBuffer; + +import java.awt.*; +import java.io.File; +import java.io.IOException; +import java.util.*; +import java.util.List; + +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 InputEvent 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 = -1; + + 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.AFTER_ENTITIES.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); + }); + + ClientTickEvents.END_CLIENT_TICK.register(client -> { + TickTask.tickTasks(TickTask.TickAt.CLIENT_EVENT); + }); + + 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(); + recordTick(new MoveEvent(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() { + recordTick(new MoveEvent(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 DelayEvent(diff)); + } + now_record.addEvent(e); + last_record = now; + } + } + + public void recordAllInput() { + if (client.player == null) { + stopRecording(); + return; + } + + InputEvent curr = InputEvent.current(); + if (curr == null) return; + + InputEvent last = ((InputEvent) now_record.getLastEvent("input")); + if (last == null) { + recordTick(curr); + } else if (!curr.equals(last)) { + recordTick(curr.differs(last)); + } + } + + 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_EVENT) { + public int replay_index = 0; + + @Override + public void run() { + if (!is_replaying) { + cancel(); + return; + } + + RecordEvent e = events.get(replay_index); + if (e != null) { + if (e instanceof DelayEvent) { + setDelay(((DelayEvent) 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; + } + try { + now_record.save(); + } catch (IOException e) { + throw new RuntimeException(e); + } + 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)), false); + } + +// public static void sendDebug(String s) { +// client.player.sendMessage(Text.literal("[DEBUG] ").append(Text.of(s)), false); +// } +} diff --git a/src/main/java/ru/themixray/repeating_mod/RecordList.java b/src/main/java/ru/themixray/repeating_mod/RecordList.java new file mode 100644 index 0000000..cebbc45 --- /dev/null +++ b/src/main/java/ru/themixray/repeating_mod/RecordList.java @@ -0,0 +1,88 @@ +package ru.themixray.repeating_mod; + +import ru.themixray.repeating_mod.widget.RecordListWidget; + +import java.io.File; +import java.nio.file.Files; +import java.util.*; + +public class RecordList { + private final File folder; + private LinkedList records; + private RecordListWidget widget; + + public RecordList(File folder) { + this.folder = folder; + this.records = new LinkedList<>(); + 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() { + LinkedList files = new LinkedList<>(List.of(folder.listFiles())); + + files.sort(Comparator.comparingLong((f) -> f.lastModified())); + + for (File file : files) { + try { + addRecord(file); + } catch (Exception e) {} + } + } + + public RecordState addRecord(File file) throws Exception { + RecordState st = RecordState.load(file); + addRecord(st); + return st; + } + + public void addRecord(RecordState record) { + if (record == null) return; + 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; + } + + public RecordState cloneRecord(File file) throws Exception { + File out = new File(Main.me.records_folder, file.getName()); + Files.copy(file.toPath(), out.toPath()); + RecordState state = RecordState.load(out); + addRecord(state); + return state; + } +} diff --git a/src/main/java/ru/themixray/repeating_mod/RecordState.java b/src/main/java/ru/themixray/repeating_mod/RecordState.java new file mode 100644 index 0000000..3a684f5 --- /dev/null +++ b/src/main/java/ru/themixray/repeating_mod/RecordState.java @@ -0,0 +1,177 @@ +package ru.themixray.repeating_mod; + +import com.google.common.collect.Lists; +import net.minecraft.util.math.Vec3d; +import ru.themixray.repeating_mod.event.RecordEvent; + +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) { + if (event == null) return; + events.add(event); + } + + public RecordEvent getLastEvent(String type) { + for (RecordEvent r: Lists.reverse(new ArrayList<>(events))) { + if (r.getType() != null && r.getType().getName().equals(type)) { + return r; + } + } + return null; + } + + public void save() throws IOException { + if (start_record_pos == null || finish_record_pos == null) return; + + 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 (RecordEvent event : events) { + if (event == null) continue; + text.append("\n"); + text.append(event.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() { + Main.me.record_list.removeRecord(this); + Main.me.record_list.getWidget().removeWidget(this); + if (Main.me.is_recording && this.equals(Main.me.now_record)) { + Main.me.stopRecording(); + Main.me.now_record = null; + return; + } + file.delete(); + } +} diff --git a/src/main/java/ru/themixray/repeating_mod/RenderListener.java b/src/main/java/ru/themixray/repeating_mod/RenderListener.java new file mode 100644 index 0000000..80558be --- /dev/null +++ b/src/main/java/ru/themixray/repeating_mod/RenderListener.java @@ -0,0 +1,11 @@ +package ru.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/src/main/java/ru/themixray/repeating_mod/RepeatingScreen.java b/src/main/java/ru/themixray/repeating_mod/RepeatingScreen.java new file mode 100644 index 0000000..0783386 --- /dev/null +++ b/src/main/java/ru/themixray/repeating_mod/RepeatingScreen.java @@ -0,0 +1,187 @@ +package ru.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 ru.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.setX(width / 2 + 2); + list_widget.setY(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(); + } + }; + 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.setNowRecord(Main.me.record_list.cloneRecord(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/src/main/java/ru/themixray/repeating_mod/TickTask.java b/src/main/java/ru/themixray/repeating_mod/TickTask.java new file mode 100644 index 0000000..d8adbef --- /dev/null +++ b/src/main/java/ru/themixray/repeating_mod/TickTask.java @@ -0,0 +1,95 @@ +package ru.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, + CLIENT_EVENT + } + + 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/src/main/java/ru/themixray/repeating_mod/event/RecordEvent.java b/src/main/java/ru/themixray/repeating_mod/event/RecordEvent.java new file mode 100644 index 0000000..c55db10 --- /dev/null +++ b/src/main/java/ru/themixray/repeating_mod/event/RecordEvent.java @@ -0,0 +1,22 @@ +package ru.themixray.repeating_mod.event; + +import ru.themixray.repeating_mod.event.events.*; + +public abstract class RecordEvent { + public abstract void replay(); + public RecordEventType getType() { + for (RecordEventType ev : RecordEventType.values()) { + if (ev.getEventClass().getTypeName().equals(this.getClass().getTypeName())) { + return ev; + } + } + return null; + } + protected abstract String[] serializeArgs(); + public String serialize() { + return getType().getChar() + "=" + String.join("&", serializeArgs()); + } + public static RecordEvent deserialize(String t) { + return RecordEventType.getByChar(t.charAt(0)).deserialize(t.substring(2).split("&")); + } +} diff --git a/src/main/java/ru/themixray/repeating_mod/event/RecordEventType.java b/src/main/java/ru/themixray/repeating_mod/event/RecordEventType.java new file mode 100644 index 0000000..853f002 --- /dev/null +++ b/src/main/java/ru/themixray/repeating_mod/event/RecordEventType.java @@ -0,0 +1,69 @@ +package ru.themixray.repeating_mod.event; + +import ru.themixray.repeating_mod.event.events.*; + +public enum RecordEventType { + BLOCK_BREAK('b',"block_break",BlockBreakEvent.class), + BLOCK_INTERACT('i',"block_interact",BlockInteractEvent.class), + DELAY('d',"delay",DelayEvent.class), + INPUT('p',"input",InputEvent.class), + MOVE('m',"move",MoveEvent.class); +// GUI_KEY_PRESS('r',"key_press", GuiKeyPressEvent.class), +// GUI_KEY_RELEASE('s',"key_release",GuiKeyReleaseEvent.class), +// GUI_CHAR_TYPE('h',"char_type",GuiCharTypeEvent.class), +// GUI_MOUSE_CLICK('c',"mouse_click",GuiMouseClickEvent.class), +// GUI_MOUSE_RELEASE('l',"mouse_release",GuiMouseReleaseEvent.class), +// GUI_MOUSE_DRAG('g',"mouse_drag",GuiMouseDragEvent.class), +// GUI_MOUSE_MOVE('v',"mouse_move",GuiMouseMoveEvent.class), +// GUI_MOUSE_SCROLL('o',"mouse_scroll",GuiMouseScrollEvent.class), +// GUI_CLOSE('e',"close",GuiCloseEvent.class); + + private Class ev; + private char ch; + private String name; + + RecordEventType(char ch, String name, Class ev) { + this.ev = ev; + this.ch = ch; + this.name = name; + } + + public Class getEventClass() { + return ev; + } + + public char getChar() { + return ch; + } + + public String getName() { + return name; + } + + public RecordEvent deserialize(String[] args) { + try { + return (RecordEvent) ev + .getMethod("deserialize", String[].class) + .invoke(null, (Object) args); + } catch (Throwable e) { + return null; + } + } + + public String serialize(RecordEvent event) { + return event.serialize(); + } + + public static RecordEventType getByChar(String type) { + return getByChar(type.charAt(0)); + } + + public static RecordEventType getByChar(char ch) { + for (RecordEventType t : values()) { + if (t.getChar() == ch) { + return t; + } + } + return null; + } +} diff --git a/src/main/java/ru/themixray/repeating_mod/event/events/BlockBreakEvent.java b/src/main/java/ru/themixray/repeating_mod/event/events/BlockBreakEvent.java new file mode 100644 index 0000000..dd8a656 --- /dev/null +++ b/src/main/java/ru/themixray/repeating_mod/event/events/BlockBreakEvent.java @@ -0,0 +1,35 @@ +package ru.themixray.repeating_mod.event.events; + +import net.minecraft.util.math.BlockPos; +import ru.themixray.repeating_mod.Main; +import ru.themixray.repeating_mod.event.RecordEvent; + +public class BlockBreakEvent extends RecordEvent { + public BlockPos pos; + + public BlockBreakEvent( + BlockPos pos) { + this.pos = pos; + } + + public static BlockBreakEvent deserialize(String[] a) { + return new BlockBreakEvent(new BlockPos( + Integer.parseInt(a[0]), + Integer.parseInt(a[1]), + Integer.parseInt(a[2]))); + } + + protected String[] serializeArgs() { + return new String[]{ + String.valueOf(pos.getX()), + String.valueOf(pos.getY()), + String.valueOf(pos.getZ()) + }; + } + + public void replay() { + if (Main.client.interactionManager != null) { + Main.client.interactionManager.breakBlock(pos); + } + } +} diff --git a/src/main/java/ru/themixray/repeating_mod/event/events/BlockInteractEvent.java b/src/main/java/ru/themixray/repeating_mod/event/events/BlockInteractEvent.java new file mode 100644 index 0000000..16a8425 --- /dev/null +++ b/src/main/java/ru/themixray/repeating_mod/event/events/BlockInteractEvent.java @@ -0,0 +1,51 @@ +package ru.themixray.repeating_mod.event.events; + +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 ru.themixray.repeating_mod.Main; +import ru.themixray.repeating_mod.event.RecordEvent; + +public class BlockInteractEvent extends RecordEvent { + public Hand hand; + public BlockHitResult hitResult; + + public static BlockInteractEvent deserialize(String[] a) { + return new BlockInteractEvent( + Hand.valueOf(a[5]), + new BlockHitResult(new Vec3d( + Double.parseDouble(a[0]), + Double.parseDouble(a[1]), + Double.parseDouble(a[2])), + Direction.byIndex(Integer.parseInt(a[4])), + new BlockPos( + Integer.parseInt(a[0]), + Integer.parseInt(a[1]), + Integer.parseInt(a[2])), + a[3].equals("1"))); + } + + public BlockInteractEvent(Hand hand, BlockHitResult hitResult) { + this.hand = hand; + this.hitResult = hitResult; + } + + public void replay() { + if (Main.client.interactionManager != null) { + Main.client.interactionManager.interactBlock(Main.client.player, hand, hitResult); + } + } + + protected String[] serializeArgs() { + return new String[]{ + String.valueOf(hitResult.getBlockPos().getX()), + String.valueOf(hitResult.getBlockPos().getY()), + String.valueOf(hitResult.getBlockPos().getZ()), + (hitResult.isInsideBlock() ? "1" : "0"), + String.valueOf(hitResult.getSide().getIndex()), + hand.name() + }; + } +} diff --git a/src/main/java/ru/themixray/repeating_mod/event/events/DelayEvent.java b/src/main/java/ru/themixray/repeating_mod/event/events/DelayEvent.java new file mode 100644 index 0000000..25dcbac --- /dev/null +++ b/src/main/java/ru/themixray/repeating_mod/event/events/DelayEvent.java @@ -0,0 +1,29 @@ +package ru.themixray.repeating_mod.event.events; + +import ru.themixray.repeating_mod.event.RecordEvent; + +public class DelayEvent extends RecordEvent { + public long delay; + + public static DelayEvent deserialize(String[] a) { + return new DelayEvent(Long.parseLong(a[0])); + } + + public DelayEvent(long delay) { + this.delay = delay; + } + + public void replay() { + try { + Thread.sleep(delay / 20 * 1000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + protected String[] serializeArgs() { + return new String[]{ + String.valueOf(delay) + }; + } +} diff --git a/src/main/java/ru/themixray/repeating_mod/event/events/GuiCharTypeEvent.java b/src/main/java/ru/themixray/repeating_mod/event/events/GuiCharTypeEvent.java new file mode 100644 index 0000000..7c8dae9 --- /dev/null +++ b/src/main/java/ru/themixray/repeating_mod/event/events/GuiCharTypeEvent.java @@ -0,0 +1,34 @@ +package ru.themixray.repeating_mod.event.events; + +import ru.themixray.repeating_mod.Main; +import ru.themixray.repeating_mod.event.RecordEvent; + +public class GuiCharTypeEvent extends RecordEvent { + private char chr; + private int modifiers; + + public GuiCharTypeEvent(char chr, int modifiers) { + this.chr = chr; + this.modifiers = modifiers; + } + + public void replay() { + if (Main.client.currentScreen != null) { + Main.client.currentScreen.charTyped(chr, modifiers); + } + } + + protected String[] serializeArgs() { + return new String[] { + String.valueOf((int) chr), + String.valueOf(modifiers) + }; + } + + public static GuiCharTypeEvent deserialize(String[] args) { + return new GuiCharTypeEvent( + (char) Integer.parseInt(args[0]), + Integer.parseInt(args[1]) + ); + } +} diff --git a/src/main/java/ru/themixray/repeating_mod/event/events/GuiCloseEvent.java b/src/main/java/ru/themixray/repeating_mod/event/events/GuiCloseEvent.java new file mode 100644 index 0000000..38908fb --- /dev/null +++ b/src/main/java/ru/themixray/repeating_mod/event/events/GuiCloseEvent.java @@ -0,0 +1,22 @@ +package ru.themixray.repeating_mod.event.events; + +import ru.themixray.repeating_mod.Main; +import ru.themixray.repeating_mod.event.RecordEvent; + +public class GuiCloseEvent extends RecordEvent { + public GuiCloseEvent() {} + + public void replay() { + if (Main.client.currentScreen != null) { + Main.client.setScreen(null); + } + } + + protected String[] serializeArgs() { + return new String[] {}; + } + + public static GuiCloseEvent deserialize(String[] args) { + return new GuiCloseEvent(); + } +} diff --git a/src/main/java/ru/themixray/repeating_mod/event/events/GuiKeyPressEvent.java b/src/main/java/ru/themixray/repeating_mod/event/events/GuiKeyPressEvent.java new file mode 100644 index 0000000..0e83969 --- /dev/null +++ b/src/main/java/ru/themixray/repeating_mod/event/events/GuiKeyPressEvent.java @@ -0,0 +1,38 @@ +package ru.themixray.repeating_mod.event.events; + +import ru.themixray.repeating_mod.Main; +import ru.themixray.repeating_mod.event.RecordEvent; + +public class GuiKeyPressEvent extends RecordEvent { + private int keyCode; + private int scanCode; + private int modifiers; + + public GuiKeyPressEvent(int keyCode, int scanCode, int modifiers) { + this.keyCode = keyCode; + this.scanCode = scanCode; + this.modifiers = modifiers; + } + + public void replay() { + if (Main.client.currentScreen != null) { + Main.client.currentScreen.keyPressed(keyCode, scanCode, modifiers); + } + } + + protected String[] serializeArgs() { + return new String[] { + String.valueOf(keyCode), + String.valueOf(scanCode), + String.valueOf(modifiers) + }; + } + + public static GuiKeyPressEvent deserialize(String[] args) { + return new GuiKeyPressEvent( + Integer.parseInt(args[0]), + Integer.parseInt(args[1]), + Integer.parseInt(args[2]) + ); + } +} diff --git a/src/main/java/ru/themixray/repeating_mod/event/events/GuiKeyReleaseEvent.java b/src/main/java/ru/themixray/repeating_mod/event/events/GuiKeyReleaseEvent.java new file mode 100644 index 0000000..253f8bf --- /dev/null +++ b/src/main/java/ru/themixray/repeating_mod/event/events/GuiKeyReleaseEvent.java @@ -0,0 +1,38 @@ +package ru.themixray.repeating_mod.event.events; + +import ru.themixray.repeating_mod.Main; +import ru.themixray.repeating_mod.event.RecordEvent; + +public class GuiKeyReleaseEvent extends RecordEvent { + private int keyCode; + private int scanCode; + private int modifiers; + + public GuiKeyReleaseEvent(int keyCode, int scanCode, int modifiers) { + this.keyCode = keyCode; + this.scanCode = scanCode; + this.modifiers = modifiers; + } + + public void replay() { + if (Main.client.currentScreen != null) { + Main.client.currentScreen.keyReleased(keyCode, scanCode, modifiers); + } + } + + protected String[] serializeArgs() { + return new String[] { + String.valueOf(keyCode), + String.valueOf(scanCode), + String.valueOf(modifiers) + }; + } + + public static GuiKeyReleaseEvent deserialize(String[] args) { + return new GuiKeyReleaseEvent( + Integer.parseInt(args[0]), + Integer.parseInt(args[1]), + Integer.parseInt(args[2]) + ); + } +} diff --git a/src/main/java/ru/themixray/repeating_mod/event/events/GuiMouseClickEvent.java b/src/main/java/ru/themixray/repeating_mod/event/events/GuiMouseClickEvent.java new file mode 100644 index 0000000..6cc2393 --- /dev/null +++ b/src/main/java/ru/themixray/repeating_mod/event/events/GuiMouseClickEvent.java @@ -0,0 +1,38 @@ +package ru.themixray.repeating_mod.event.events; + +import ru.themixray.repeating_mod.Main; +import ru.themixray.repeating_mod.event.RecordEvent; + +public class GuiMouseClickEvent extends RecordEvent { + private double mouseX; + private double mouseY; + private int button; + + public GuiMouseClickEvent(double mouseX, double mouseY, int button) { + this.mouseX = mouseX; + this.mouseY = mouseY; + this.button = button; + } + + public void replay() { + if (Main.client.currentScreen != null) { + Main.client.currentScreen.mouseClicked(mouseX, mouseY, button); + } + } + + protected String[] serializeArgs() { + return new String[] { + String.valueOf(mouseX), + String.valueOf(mouseY), + String.valueOf(button) + }; + } + + public static GuiMouseClickEvent deserialize(String[] args) { + return new GuiMouseClickEvent( + Double.parseDouble(args[0]), + Double.parseDouble(args[1]), + Integer.parseInt(args[2]) + ); + } +} diff --git a/src/main/java/ru/themixray/repeating_mod/event/events/GuiMouseDragEvent.java b/src/main/java/ru/themixray/repeating_mod/event/events/GuiMouseDragEvent.java new file mode 100644 index 0000000..eba566f --- /dev/null +++ b/src/main/java/ru/themixray/repeating_mod/event/events/GuiMouseDragEvent.java @@ -0,0 +1,46 @@ +package ru.themixray.repeating_mod.event.events; + +import ru.themixray.repeating_mod.Main; +import ru.themixray.repeating_mod.event.RecordEvent; + +public class GuiMouseDragEvent extends RecordEvent { + private double mouseX; + private double mouseY; + private double deltaX; + private double deltaY; + private int button; + + public GuiMouseDragEvent(double mouseX, double mouseY, int button, double deltaX, double deltaY) { + this.mouseX = mouseX; + this.mouseY = mouseY; + this.deltaX = deltaX; + this.deltaY = deltaY; + this.button = button; + } + + public void replay() { + if (Main.client.currentScreen != null) { + Main.client.currentScreen.mouseDragged(mouseX, mouseY, button, deltaX, deltaY); + } + } + + protected String[] serializeArgs() { + return new String[] { + String.valueOf(mouseX), + String.valueOf(mouseY), + String.valueOf(button), + String.valueOf(deltaX), + String.valueOf(deltaY) + }; + } + + public static GuiMouseDragEvent deserialize(String[] args) { + return new GuiMouseDragEvent( + Double.parseDouble(args[0]), + Double.parseDouble(args[1]), + Integer.parseInt(args[2]), + Double.parseDouble(args[3]), + Double.parseDouble(args[4]) + ); + } +} diff --git a/src/main/java/ru/themixray/repeating_mod/event/events/GuiMouseMoveEvent.java b/src/main/java/ru/themixray/repeating_mod/event/events/GuiMouseMoveEvent.java new file mode 100644 index 0000000..94480f2 --- /dev/null +++ b/src/main/java/ru/themixray/repeating_mod/event/events/GuiMouseMoveEvent.java @@ -0,0 +1,34 @@ +package ru.themixray.repeating_mod.event.events; + +import ru.themixray.repeating_mod.Main; +import ru.themixray.repeating_mod.event.RecordEvent; + +public class GuiMouseMoveEvent extends RecordEvent { + private double mouseX; + private double mouseY; + + public GuiMouseMoveEvent(double mouseX, double mouseY) { + this.mouseX = mouseX; + this.mouseY = mouseY; + } + + public void replay() { + if (Main.client.currentScreen != null) { + Main.client.currentScreen.mouseMoved(mouseX, mouseY); + } + } + + protected String[] serializeArgs() { + return new String[] { + String.valueOf(mouseX), + String.valueOf(mouseY) + }; + } + + public static GuiMouseMoveEvent deserialize(String[] args) { + return new GuiMouseMoveEvent( + Double.parseDouble(args[0]), + Double.parseDouble(args[1]) + ); + } +} diff --git a/src/main/java/ru/themixray/repeating_mod/event/events/GuiMouseReleaseEvent.java b/src/main/java/ru/themixray/repeating_mod/event/events/GuiMouseReleaseEvent.java new file mode 100644 index 0000000..3e8c1b4 --- /dev/null +++ b/src/main/java/ru/themixray/repeating_mod/event/events/GuiMouseReleaseEvent.java @@ -0,0 +1,38 @@ +package ru.themixray.repeating_mod.event.events; + +import ru.themixray.repeating_mod.Main; +import ru.themixray.repeating_mod.event.RecordEvent; + +public class GuiMouseReleaseEvent extends RecordEvent { + private double mouseX; + private double mouseY; + private int button; + + public GuiMouseReleaseEvent(double mouseX, double mouseY, int button) { + this.mouseX = mouseX; + this.mouseY = mouseY; + this.button = button; + } + + public void replay() { + if (Main.client.currentScreen != null) { + Main.client.currentScreen.mouseReleased(mouseX, mouseY, button); + } + } + + protected String[] serializeArgs() { + return new String[] { + String.valueOf(mouseX), + String.valueOf(mouseY), + String.valueOf(button) + }; + } + + public static GuiMouseReleaseEvent deserialize(String[] args) { + return new GuiMouseReleaseEvent( + Double.parseDouble(args[0]), + Double.parseDouble(args[1]), + Integer.parseInt(args[2]) + ); + } +} diff --git a/src/main/java/ru/themixray/repeating_mod/event/events/GuiMouseScrollEvent.java b/src/main/java/ru/themixray/repeating_mod/event/events/GuiMouseScrollEvent.java new file mode 100644 index 0000000..8df7061 --- /dev/null +++ b/src/main/java/ru/themixray/repeating_mod/event/events/GuiMouseScrollEvent.java @@ -0,0 +1,38 @@ +package ru.themixray.repeating_mod.event.events; + +import ru.themixray.repeating_mod.Main; +import ru.themixray.repeating_mod.event.RecordEvent; + +public class GuiMouseScrollEvent extends RecordEvent { + private double mouseX; + private double mouseY; + private double amount; + + public GuiMouseScrollEvent(double mouseX, double mouseY, double amount) { + this.mouseX = mouseX; + this.mouseY = mouseY; + this.amount = amount; + } + + public void replay() { + if (Main.client.currentScreen != null) { +// Main.client.currentScreen.mouseScrolled(mouseX, mouseY, amount); + } + } + + protected String[] serializeArgs() { + return new String[] { + String.valueOf(mouseX), + String.valueOf(mouseY), + String.valueOf(amount) + }; + } + + public static GuiMouseScrollEvent deserialize(String[] args) { + return new GuiMouseScrollEvent( + Double.parseDouble(args[0]), + Double.parseDouble(args[1]), + Double.parseDouble(args[2]) + ); + } +} diff --git a/src/main/java/ru/themixray/repeating_mod/event/events/InputEvent.java b/src/main/java/ru/themixray/repeating_mod/event/events/InputEvent.java new file mode 100644 index 0000000..01d1f39 --- /dev/null +++ b/src/main/java/ru/themixray/repeating_mod/event/events/InputEvent.java @@ -0,0 +1,195 @@ +package ru.themixray.repeating_mod.event.events; + +import net.minecraft.client.input.Input; +import net.minecraft.util.PlayerInput; +import net.minecraft.util.math.Vec2f; +import ru.themixray.repeating_mod.Main; +import ru.themixray.repeating_mod.event.RecordEvent; + +import java.lang.reflect.Field; + +public class InputEvent extends RecordEvent { + public Boolean forward; + public Boolean backward; + public Boolean left; + public Boolean right; + public Boolean jump; + public Boolean sneak; + public Boolean sprint; + + public float yaw; + public float head_yaw; + public float body_yaw; + public float pitch; + public float speed; + public float movementForward; + public float movementSideways; + + public static InputEvent current() { + if (Main.client.player == null) return null; + return new InputEvent( + Main.client.player.input.playerInput.forward(), + Main.client.player.input.playerInput.backward(), + Main.client.player.input.playerInput.left(), + Main.client.player.input.playerInput.right(), + Main.client.player.input.playerInput.jump(), + Main.client.player.input.playerInput.sneak(), + Main.client.player.input.playerInput.sprint(), + Main.client.player.getHeadYaw(), + Main.client.player.getBodyYaw(), + Main.client.player.getPitch(), + Main.client.player.getYaw(), + Main.client.player.getMovementSpeed(), + Main.client.player.input.getMovementInput().y, + Main.client.player.input.getMovementInput().x + ); + } + + public InputEvent(Boolean forward, + Boolean backward, + Boolean left, + Boolean right, + Boolean jump, + Boolean sneak, + Boolean sprint, + float head_yaw, + float body_yaw, + float head_pitch, + float yaw, + float speed, + float movementForward, + float movementSideways) { + this.forward = forward; + this.backward = backward; + this.left = left; + this.right = right; + this.jump = jump; + this.sneak = sneak; + this.sprint = sprint; + + this.head_yaw = head_yaw; + this.body_yaw = body_yaw; + this.pitch = head_pitch; + this.yaw = yaw; + this.speed = speed; + this.movementForward = movementForward; + this.movementSideways = movementSideways; + } + + /** + * Returns differences of this InputEvent to the provided one, saving first booleans if differ and first floats always + */ + public InputEvent differs(InputEvent event) { + return new InputEvent( + forward == event.forward ? null : forward, + backward == event.backward ? null : backward, + left == event.left ? null : left, + right == event.right ? null : right, + jump == event.jump ? null : jump, + sneak == event.sneak ? null : sneak, + sprint == event.sprint ? null : sprint, + head_yaw, + body_yaw, + pitch, + yaw, + speed, + movementForward, + movementSideways + ); + } + + public static InputEvent deserialize(String[] a) { + return new InputEvent( + (a[0].equals("n") ? null : a[0].equals("1")), + (a[1].equals("n") ? null : a[1].equals("1")), + (a[2].equals("n") ? null : a[2].equals("1")), + (a[3].equals("n") ? null : a[3].equals("1")), + (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")), + Float.parseFloat(a[7]), + Float.parseFloat(a[8]), + Float.parseFloat(a[9]), + Float.parseFloat(a[10]), + Float.parseFloat(a[11]), + Float.parseFloat(a[12]), + Float.parseFloat(a[13]) + ); + } + + protected String[] serializeArgs() { + return new String[] { + ((forward == null) ? "n" : (forward ? "1" : "0")), + ((backward == null) ? "n" : (backward ? "1" : "0")), + ((left == null) ? "n" : (left ? "1" : "0")), + ((right == null) ? "n" : (right ? "1" : "0")), + ((jump == null) ? "n" : (jump ? "1" : "0")), + ((sneak == null) ? "n" : (sneak ? "1" : "0")), + ((sprint == null) ? "n" : (sprint ? "1" : "0")), + String.valueOf(head_yaw), + String.valueOf(body_yaw), + String.valueOf(pitch), + String.valueOf(yaw), + String.valueOf(speed), + String.valueOf(movementForward), + String.valueOf(movementSideways) + }; + } + + public boolean equals(InputEvent event) { + return event.forward == forward && + event.backward == backward && + event.sprint == sprint && + event.jump == jump && + event.sneak == sneak && + event.left == left && + event.right == right && + event.speed == speed && + event.head_yaw == head_yaw && + event.body_yaw == body_yaw && + event.yaw == yaw && + event.pitch == pitch && + event.movementForward == movementForward && + event.movementSideways == movementSideways; + } + + public void replay() { + Main.input_replay = this; + } + + public void inputCallback() { + if (Main.client.player != null) { + if (sprint != null && Main.client.player.isSprinting() != sprint) + Main.client.player.setSprinting(sprint); + 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); + + try { + Field field = Input.class.getDeclaredField("movementVector"); + field.setAccessible(true); + field.set(Main.client.player.input, new Vec2f(movementSideways, movementForward)); + } catch (NoSuchFieldException | IllegalAccessException e) { + e.printStackTrace(); + } + + PlayerInput input = Main.client.player.input.playerInput; + Main.client.player.input.playerInput = new PlayerInput( + this.forward == null ? input.forward() : this.forward, + this.backward == null ? input.backward() : this.backward, + this.left == null ? input.left() : this.left, + this.right == null ? input.right() : this.right, + this.jump == null ? input.jump() : this.jump, + this.sneak == null ? input.sneak() : this.sneak, + this.sprint == null ? input.sprint() : this.sprint + ); + } + } +} diff --git a/src/main/java/ru/themixray/repeating_mod/event/events/MoveEvent.java b/src/main/java/ru/themixray/repeating_mod/event/events/MoveEvent.java new file mode 100644 index 0000000..9506eae --- /dev/null +++ b/src/main/java/ru/themixray/repeating_mod/event/events/MoveEvent.java @@ -0,0 +1,52 @@ +package ru.themixray.repeating_mod.event.events; + +import net.minecraft.entity.MovementType; +import net.minecraft.util.math.Vec3d; +import ru.themixray.repeating_mod.Main; +import ru.themixray.repeating_mod.event.RecordEvent; + +public class MoveEvent extends RecordEvent { + public Vec3d vec; + public float yaw; + public float pitch; + + public static final float MOVE_THRESHOLD = 0.001f; + + public static MoveEvent deserialize(String[] a) { + return new MoveEvent(new Vec3d( + Double.parseDouble(a[0]), + Double.parseDouble(a[1]), + Double.parseDouble(a[2])), + Float.parseFloat(a[3]), + Float.parseFloat(a[4])); + } + + public MoveEvent(Vec3d vec, float yaw, float pitch) { + this.vec = vec; + this.yaw = yaw; + this.pitch = pitch; + } + + public void replay() { + if (Main.client.player != null) { + Vec3d p = Main.client.player.getPos(); + Vec3d v = new Vec3d(vec.getX() - p.getX(), vec.getY() - p.getY(), vec.getZ() - p.getZ()); + if (Math.abs(v.x) > MOVE_THRESHOLD || Math.abs(v.y) > MOVE_THRESHOLD || Math.abs(v.z) > MOVE_THRESHOLD) + Main.client.player.move(MovementType.SELF, v); + + if (Math.abs(Main.client.player.getYaw() - yaw) > MOVE_THRESHOLD) + Main.client.player.setYaw(yaw); + if (Math.abs(Main.client.player.getPitch() - pitch) > MOVE_THRESHOLD) + Main.client.player.setPitch(pitch); + } + } + + protected String[] serializeArgs() { + return new String[]{ + String.valueOf(vec.getX()), + String.valueOf(vec.getZ()), + String.valueOf(yaw), + String.valueOf(pitch) + }; + } +} diff --git a/src/main/java/ru/themixray/repeating_mod/mixin/ClientMixin.java b/src/main/java/ru/themixray/repeating_mod/mixin/ClientMixin.java new file mode 100644 index 0000000..10b1a8e --- /dev/null +++ b/src/main/java/ru/themixray/repeating_mod/mixin/ClientMixin.java @@ -0,0 +1,24 @@ +package ru.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 ru.themixray.repeating_mod.Main; +import ru.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/src/main/java/ru/themixray/repeating_mod/mixin/EntityMixin.java b/src/main/java/ru/themixray/repeating_mod/mixin/EntityMixin.java new file mode 100644 index 0000000..93257cc --- /dev/null +++ b/src/main/java/ru/themixray/repeating_mod/mixin/EntityMixin.java @@ -0,0 +1,31 @@ +package ru.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 ru.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.sprint != null && + Main.input_replay.sprint != sprinting) { + ci.cancel(); + } + } + } + } + } +} diff --git a/src/main/java/ru/themixray/repeating_mod/mixin/InputMixin.java b/src/main/java/ru/themixray/repeating_mod/mixin/InputMixin.java new file mode 100644 index 0000000..261c59c --- /dev/null +++ b/src/main/java/ru/themixray/repeating_mod/mixin/InputMixin.java @@ -0,0 +1,20 @@ +package ru.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 ru.themixray.repeating_mod.Main; + +@Mixin(KeyboardInput.class) +public abstract class InputMixin { + @Inject(at = @At(value = "TAIL"), method = "tick") + private void onTickTail(CallbackInfo ci) { + if (Main.me.is_replaying) { + if (Main.input_replay != null) { + Main.input_replay.inputCallback(); + } + } + } +} diff --git a/src/main/java/ru/themixray/repeating_mod/mixin/MovementMixin.java b/src/main/java/ru/themixray/repeating_mod/mixin/MovementMixin.java new file mode 100644 index 0000000..73f970b --- /dev/null +++ b/src/main/java/ru/themixray/repeating_mod/mixin/MovementMixin.java @@ -0,0 +1,44 @@ +package ru.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 ru.themixray.repeating_mod.Main; +import ru.themixray.repeating_mod.event.events.BlockBreakEvent; +import ru.themixray.repeating_mod.event.events.BlockInteractEvent; +import ru.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 BlockBreakEvent(pos)); + }); + + UseBlockCallback.EVENT.register((player, world, hand, hitResult) -> { + if (hitResult.getType().equals(HitResult.Type.BLOCK)) + if (Main.me.is_recording) + Main.me.recordTick(new BlockInteractEvent(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/src/main/java/ru/themixray/repeating_mod/mixin/NetworkMixin.java b/src/main/java/ru/themixray/repeating_mod/mixin/NetworkMixin.java new file mode 100644 index 0000000..58f7570 --- /dev/null +++ b/src/main/java/ru/themixray/repeating_mod/mixin/NetworkMixin.java @@ -0,0 +1,29 @@ +package ru.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/src/main/java/ru/themixray/repeating_mod/mixin/PlayerMixin.java b/src/main/java/ru/themixray/repeating_mod/mixin/PlayerMixin.java new file mode 100644 index 0000000..c5627c8 --- /dev/null +++ b/src/main/java/ru/themixray/repeating_mod/mixin/PlayerMixin.java @@ -0,0 +1,19 @@ +package ru.themixray.repeating_mod.mixin; + +import net.minecraft.network.ClientConnection; +import net.minecraft.text.Text; +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 ru.themixray.repeating_mod.Main; + +@Mixin(ClientConnection.class) +public abstract class PlayerMixin { + @Inject(at = @At(value = "HEAD"), method = "disconnect") + private void disconnect(Text disconnectReason, CallbackInfo ci) { + if (Main.me.is_replaying) { + Main.me.stopReplay(); + } + } +} diff --git a/src/main/java/ru/themixray/repeating_mod/mixin/RendererMixin.java b/src/main/java/ru/themixray/repeating_mod/mixin/RendererMixin.java new file mode 100644 index 0000000..c035167 --- /dev/null +++ b/src/main/java/ru/themixray/repeating_mod/mixin/RendererMixin.java @@ -0,0 +1,21 @@ +package ru.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 ru.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/src/main/java/ru/themixray/repeating_mod/mixin/ScreenMixin.java b/src/main/java/ru/themixray/repeating_mod/mixin/ScreenMixin.java new file mode 100644 index 0000000..3d74704 --- /dev/null +++ b/src/main/java/ru/themixray/repeating_mod/mixin/ScreenMixin.java @@ -0,0 +1,85 @@ +package ru.themixray.repeating_mod.mixin; + +import net.minecraft.client.gui.AbstractParentElement; +import net.minecraft.client.gui.Drawable; +import net.minecraft.client.gui.screen.Screen; +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 org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; +import ru.themixray.repeating_mod.Main; +import ru.themixray.repeating_mod.event.events.*; + +@Mixin(Screen.class) +public abstract class ScreenMixin extends AbstractParentElement implements Drawable { + @Inject(at = @At(value = "HEAD"), method = "close") + private void close(CallbackInfo ci) { + if (Main.me.is_recording) { +// Main.me.now_record.addEvent(new GuiCloseEvent()); + } + } + + @Inject(at = @At(value = "HEAD"), method = "keyPressed") + private void keyPressed(int keyCode, int scanCode, int modifiers, CallbackInfoReturnable cir) { + if (Main.me.is_recording) { +// Main.me.now_record.addEvent(new GuiKeyPressEvent(keyCode, scanCode, modifiers)); + } + } + + @Override + public boolean charTyped(char chr, int modifiers) { + if (Main.me.is_recording) { +// Main.me.now_record.addEvent(new GuiCharTypeEvent(chr, modifiers)); + } + return super.charTyped(chr, modifiers); + } + + @Override + public boolean keyReleased(int keyCode, int scanCode, int modifiers) { + if (Main.me.is_recording) { +// Main.me.now_record.addEvent(new GuiKeyReleaseEvent(keyCode, scanCode, modifiers)); + } + return super.keyReleased(keyCode, scanCode, modifiers); + } + + @Override + public boolean mouseClicked(double mouseX, double mouseY, int button) { + if (Main.me.is_recording) { +// Main.me.now_record.addEvent(new GuiMouseClickEvent(mouseX, mouseY, button)); + } + return super.mouseClicked(mouseX, mouseY, button); + } + + @Override + public void mouseMoved(double mouseX, double mouseY) { + if (Main.me.is_recording) { +// Main.me.now_record.addEvent(new GuiMouseMoveEvent(mouseX, mouseY)); + } + super.mouseMoved(mouseX, mouseY); + } + + @Override + public boolean mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY) { + if (Main.me.is_recording) { +// Main.me.now_record.addEvent(new GuiMouseDragEvent(mouseX, mouseY, button, deltaX, deltaY)); + } + return super.mouseDragged(mouseX, mouseY, button, deltaX, deltaY); + } + + @Override + public boolean mouseReleased(double mouseX, double mouseY, int button) { + if (Main.me.is_recording) { +// Main.me.now_record.addEvent(new GuiMouseReleaseEvent(mouseX, mouseY, button)); + } + return super.mouseReleased(mouseX, mouseY, button); + } + +// @Override +// public boolean mouseScrolled(double mouseX, double mouseY, double amount) { +// if (Main.me.is_recording) { +//// Main.me.now_record.addEvent(new GuiMouseScrollEvent(mouseX, mouseY, amount)); +// } +// return super.mouseScrolled(mouseX, mouseY, amount); +// } +} diff --git a/src/main/java/ru/themixray/repeating_mod/render/RenderHelper.java b/src/main/java/ru/themixray/repeating_mod/render/RenderHelper.java new file mode 100644 index 0000000..c67abd4 --- /dev/null +++ b/src/main/java/ru/themixray/repeating_mod/render/RenderHelper.java @@ -0,0 +1,200 @@ +package ru.themixray.repeating_mod.render; + +import lombok.experimental.UtilityClass; +import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderContext; +import org.joml.Vector3f; +import ru.themixray.repeating_mod.render.buffer.WorldBuffer; +import ru.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(new Vector3f(x1, y1, z1), color.getRed() / 255f, color.getGreen() / 255f, color.getBlue() / 255f, color.getAlpha() / 255f); + buffer.vert(new Vector3f(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(new Vector3f(x1, y1, z1), color.getRed() / 255f, color.getGreen() / 255f, color.getBlue() / 255f, color.getAlpha() / 255f); + buffer.vert(new Vector3f(x2, y2, z2), color.getRed() / 255f, color.getGreen() / 255f, color.getBlue() / 255f, color.getAlpha() / 255f); + buffer.vert(new Vector3f(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/src/main/java/ru/themixray/repeating_mod/render/RenderSystem.java b/src/main/java/ru/themixray/repeating_mod/render/RenderSystem.java new file mode 100644 index 0000000..90f12e9 --- /dev/null +++ b/src/main/java/ru/themixray/repeating_mod/render/RenderSystem.java @@ -0,0 +1,13 @@ +package ru.themixray.repeating_mod.render; + +import lombok.experimental.UtilityClass; +import ru.themixray.repeating_mod.render.buffer.BufferManager; +import ru.themixray.repeating_mod.render.shader.ShaderManager; + +@UtilityClass +public class RenderSystem { + public static void init() { + BufferManager.init(); + ShaderManager.init(); + } +} diff --git a/src/main/java/ru/themixray/repeating_mod/render/buffer/BufferManager.java b/src/main/java/ru/themixray/repeating_mod/render/buffer/BufferManager.java new file mode 100644 index 0000000..9fb32ff --- /dev/null +++ b/src/main/java/ru/themixray/repeating_mod/render/buffer/BufferManager.java @@ -0,0 +1,48 @@ +package ru.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/src/main/java/ru/themixray/repeating_mod/render/buffer/Vertex.java b/src/main/java/ru/themixray/repeating_mod/render/buffer/Vertex.java new file mode 100644 index 0000000..a87e2e6 --- /dev/null +++ b/src/main/java/ru/themixray/repeating_mod/render/buffer/Vertex.java @@ -0,0 +1,25 @@ +package ru.themixray.repeating_mod.render.buffer; + +import lombok.Getter; +import org.joml.Vector3f; + +public class Vertex { + @Getter + private Vector3f pos; + @Getter + private float r; + @Getter + private float g; + @Getter + private float b; + @Getter + private float a; + + public Vertex(Vector3f pos, float r, float g, float b, float a) { + this.pos = pos; + this.r = r; + this.g = g; + this.b = b; + this.a = a; + } +} \ No newline at end of file diff --git a/src/main/java/ru/themixray/repeating_mod/render/buffer/WorldBuffer.java b/src/main/java/ru/themixray/repeating_mod/render/buffer/WorldBuffer.java new file mode 100644 index 0000000..fa27670 --- /dev/null +++ b/src/main/java/ru/themixray/repeating_mod/render/buffer/WorldBuffer.java @@ -0,0 +1,77 @@ +package ru.themixray.repeating_mod.render.buffer; + +import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderContext; +import org.apache.commons.lang3.ArrayUtils; +import org.joml.Matrix4f; +import org.joml.Quaternionf; +import org.joml.Vector3f; +import org.lwjgl.BufferUtils; +import ru.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 Vector3f cameraPos; + private final Quaternionf cameraRot; + + public WorldBuffer(int drawMode, Shader shader, WorldRenderContext worldRenderContext) { + this.drawMode = drawMode; + this.shader = shader; + this.cameraPos = worldRenderContext.camera().getPos().toVector3f(); + this.cameraRot = worldRenderContext.camera().getRotation().invert(); + this.projectionMatrix = worldRenderContext.projectionMatrix().mul(worldRenderContext.matrixStack().peek().getPositionMatrix()).get(BufferUtils.createFloatBuffer(16)); + } + + public void vert(Vector3f pos, float r, float g, float b, float a) { + vertices.add(new Vertex(cameraRot.transform(pos.sub(cameraPos)), r, g, b, a)); + } + + public void draw() { + BufferManager.bind(); + BufferManager.bindBuffer(); + + BufferManager.writeBuffer(getBuffer()); + shader.uniformMatrix4f("u_projection", projectionMatrix); + + 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 (ru.themixray.repeating_mod.render.buffer.Vertex vertex : vertices) { + floats.add(vertex.getPos().x); + floats.add(vertex.getPos().y); + floats.add(vertex.getPos().z); + } + for (ru.themixray.repeating_mod.render.buffer.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(); + } +} diff --git a/src/main/java/ru/themixray/repeating_mod/render/shader/Shader.java b/src/main/java/ru/themixray/repeating_mod/render/shader/Shader.java new file mode 100644 index 0000000..efe5550 --- /dev/null +++ b/src/main/java/ru/themixray/repeating_mod/render/shader/Shader.java @@ -0,0 +1,42 @@ +package ru.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 = ru.themixray.repeating_mod.render.shader.ShaderManager.loadShaderProgram(name, ru.themixray.repeating_mod.render.shader.ShaderManager.ShaderType.VERTEX); + int f = ru.themixray.repeating_mod.render.shader.ShaderManager.loadShaderProgram(name, ru.themixray.repeating_mod.render.shader.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/src/main/java/ru/themixray/repeating_mod/render/shader/ShaderManager.java b/src/main/java/ru/themixray/repeating_mod/render/shader/ShaderManager.java new file mode 100644 index 0000000..f5e26a6 --- /dev/null +++ b/src/main/java/ru/themixray/repeating_mod/render/shader/ShaderManager.java @@ -0,0 +1,97 @@ +package ru.themixray.repeating_mod.render.shader; + +import com.mojang.blaze3d.opengl.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 ru.themixray.repeating_mod.render.shader.Shader positionColorShader; + + public void init() { + ClientLifecycleEvents.CLIENT_STARTED.register(client -> loadShaders()); + } + + private void loadShaders() { + positionColorShader = new ru.themixray.repeating_mod.render.shader.Shader("position_color"); + } + + public int loadShaderProgram(String name, ShaderType type) { + try { + boolean file_present = true; + ResourceFactory resourceFactory = MinecraftClient.getInstance().getResourceManager(); + Optional resource = resourceFactory.getResource(Identifier.of("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())).getFirst()); + } 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/src/main/java/ru/themixray/repeating_mod/widget/RecordListWidget.java b/src/main/java/ru/themixray/repeating_mod/widget/RecordListWidget.java new file mode 100644 index 0000000..b17af92 --- /dev/null +++ b/src/main/java/ru/themixray/repeating_mod/widget/RecordListWidget.java @@ -0,0 +1,153 @@ +package ru.themixray.repeating_mod.widget; + +import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.gui.screen.narration.NarrationMessageBuilder; +import net.minecraft.client.gui.widget.*; +import net.minecraft.text.Text; +import ru.themixray.repeating_mod.Main; +import ru.themixray.repeating_mod.RecordState; +import ru.themixray.repeating_mod.RepeatingScreen; + +import java.util.LinkedList; + +public class RecordListWidget extends ScrollableWidget { + private LinkedList widgets = new LinkedList<>(); + 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 int getContentsHeightWithPadding() { + return !widgets.isEmpty() ? widgets.size() * 55 + (widgets.size() - 1) * 2 : 0; + } + + @Override + protected double getDeltaYPerScroll() { + return 10; + } + + @Override + protected void renderWidget(DrawContext ctx, int mouseX, int mouseY, float delta) { + ctx.fill(this.getX(), this.getY(), this.getX() + this.getWidth(), this.getY() + this.getHeight(), 0xff000000); + + ctx.enableScissor(this.getX(), this.getY(), this.getX() + this.getWidth(), this.getY() + this.getHeight()); + + int y = (int) -this.getScrollY(); + for (RecordWidget wid: widgets) { + wid.setY(y); + wid.render(ctx, mouseX, (int) (mouseY), delta); + + y += wid.getHeight(); + y += 2; + } + + ctx.disableScissor(); + + this.drawScrollbar(ctx); + } + + public void addWidget(RecordState record) { + RecordWidget widget = new RecordWidget(0, 0, width - 6, 55, record, this); + widget.init(null); + widgets.addFirst(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; + } + + 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 (child.isFocused() && tr.check(child)) { + return true; + } + } + } + return false; + } + + public boolean checkTransportNF(double mouseX, double mouseY, int button) { + for (RecordWidget wid : widgets) { + if (wid.contains((int) mouseX, (int) mouseY)) { + Main.me.setNowRecord(wid.getRecord()); + } + + for (ClickableWidget child : wid.getChildren()) { + if (child.mouseClicked(mouseX, mouseY, button)) { + child.setFocused(true); + return true; + } else { + child.setFocused(false); + } + } + } + return false; + } + + @Override + public boolean mouseClicked(double mouseX, double mouseY, int button) { + return checkTransportNF(mouseX, mouseY, button) || super.checkScrollbarDragged(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/src/main/java/ru/themixray/repeating_mod/widget/RecordWidget.java b/src/main/java/ru/themixray/repeating_mod/widget/RecordWidget.java new file mode 100644 index 0000000..5b9954e --- /dev/null +++ b/src/main/java/ru/themixray/repeating_mod/widget/RecordWidget.java @@ -0,0 +1,208 @@ +package ru.themixray.repeating_mod.widget; + +import lombok.Getter; +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.Text; +import net.minecraft.util.Formatting; +import ru.themixray.repeating_mod.Main; +import ru.themixray.repeating_mod.RecordState; +import ru.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 { + @Getter + 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 boolean contains(int x, int y) { + return parent.getX() + getX() <= x && + parent.getY() + getY() <= y && + x <= parent.getX() + getX() + getWidth() && + y <= parent.getY() + getY() + getHeight(); + } + + public void setX(int x) { + this.x = x; + } + public void setY(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) -> { + try { + record.save(); + } catch (IOException e) { + throw new RuntimeException(e); + } + 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); + } + } + } else { + Main.sendMessage(Text.literal("Record file is ").append( + Text.literal("["+record.getFile().getAbsolutePath()+"]").styled((s) -> s.withColor(Formatting.GRAY)))); + } + }).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." + + (getRecord().equals(Main.me.now_record) && Main.me.is_replaying ? "stop" : "start")), (i) -> { + if (Main.me.is_replaying) { + Main.me.stopReplay(); + if (getRecord().equals(Main.me.now_record)) { + return; + } + } + + i.setMessage(Text.translatable("text.repeating-mod.stop")); + Main.me.now_record = getRecord(); + Main.me.startReplay(); + Main.client.setScreen(null); + }).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 void drawText(int x, int y, DrawContext ctx, List lines, int line_height, boolean shadow) { + int now_y = y; + + for (Text line : lines) { + ctx.drawText(Main.client.textRenderer, line, x, now_y, 0xff000000 + line.getStyle().getColor().getRgb(), shadow); + now_y += line_height; + } + } + + @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)) + ), + 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/src/main/java/themixray/repeating/mod/EasyConfig.java b/src/main/java/themixray/repeating/mod/EasyConfig.java deleted file mode 100644 index 0a2843a..0000000 --- a/src/main/java/themixray/repeating/mod/EasyConfig.java +++ /dev/null @@ -1,68 +0,0 @@ -package themixray.repeating.mod; - -import org.json.simple.JSONValue; -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(Path path, Map def) { - this.path = path; - this.file = path.toFile(); - this.data = new HashMap<>(); - - if (!file.exists()) { - try { - file.createNewFile(); - write(def); - } catch (IOException e) { - e.printStackTrace(); - } - } - reload(); - } - - public EasyConfig(Path path) { - this(path,new HashMap<>()); - } - - public void reload() { - data = read(); - } - - public void save() { - write(data); - } - - private String toJson(Map p) { - return JSONValue.toJSONString(p); - } - - private Map toMap(String j) { - return (Map) JSONValue.parse(j); - } - - 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,toJson(p).getBytes()); - } catch (IOException e) { - e.printStackTrace(); - } - } -} diff --git a/src/main/java/themixray/repeating/mod/RepeatingMod.java b/src/main/java/themixray/repeating/mod/RepeatingMod.java deleted file mode 100644 index aa090b2..0000000 --- a/src/main/java/themixray/repeating/mod/RepeatingMod.java +++ /dev/null @@ -1,338 +0,0 @@ -package themixray.repeating.mod; - -import com.google.common.collect.Lists; -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.renderer.v1.RendererAccess; -import net.fabricmc.fabric.api.resource.ResourceManagerHelper; -import net.fabricmc.fabric.api.resource.SimpleSynchronousResourceReloadListener; -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.entity.MovementType; -import net.minecraft.registry.Registries; -import net.minecraft.registry.Registry; -import net.minecraft.resource.Resource; -import net.minecraft.resource.ResourceManager; -import net.minecraft.resource.ResourceType; -import net.minecraft.text.Text; -import net.minecraft.util.Hand; -import net.minecraft.util.Identifier; -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 org.lwjgl.glfw.GLFW; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.File; -import java.io.InputStream; -import java.util.*; - -public class RepeatingMod 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 RepeatingMod me; - - public List record = new ArrayList<>(); - public boolean is_recording = false; - public Date last_record = null; - - public Thread replay = null; - public boolean is_replaying = false; - public boolean loop_replay = false; - public static boolean replay_sneaking = false; - - public static RepeatingScreen menu; - private static KeyBinding menu_key; - private static KeyBinding toggle_replay_key; - private static KeyBinding toggle_record_key; - - public double record_blocks_limit = 2; - public long record_time_limit = 50; - - public EasyConfig conf; - - @Override - public void onInitializeClient() { - LOGGER.info("Repeating mod initialized"); - me = this; - - Map def = new HashMap<>(); - def.put("record_blocks_limit", record_blocks_limit); - def.put("record_time_limit", record_time_limit); - conf = new EasyConfig(new File(loader.getConfigDir().toFile(),"repeating-mod.json").toPath(),def); - - record_blocks_limit = (double) conf.data.get("record_blocks_limit"); - record_time_limit = (long) conf.data.get("record_time_limit"); - - 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 (!is_recording) { - if (is_replaying) - stopReplay(); - else startReplay(); - menu.update_btns(); - } - } - if (toggle_record_key.wasPressed()) { - if (!is_replaying) { - if (is_recording) - stopRecording(); - else startRecording(); - menu.update_btns(); - } - } - }); - } - - public RecordEvent getLastRecord(String t) { - for (RecordEvent r:Lists.reverse(new ArrayList<>(record))) { - if (r.getType().equals(t)) { - return r; - } - } - return null; - } - - - public void startRecording() { - is_recording = true; - menu.update_btns(); - record.clear(); - sendMessage(Text.translatable("message.repeating-mod.record_start")); - } - - public void recordTick(RecordEvent e) { - Date now = new Date(); - if (last_record != null) { - long diff = now.getTime() - last_record.getTime(); - if (diff >= 0) record.add(new RecordDelayEvent(diff)); - } - record.add(e); - last_record = now; - } - - public void stopRecording() { - is_recording = false; - menu.update_btns(); - last_record = null; - sendMessage(Text.translatable("message.repeating-mod.record_stop")); - } - - - public void startReplay() { - is_recording = false; - is_replaying = true; - menu.update_btns(); - client.player.setNoGravity(true); - replay = new Thread(() -> { - while (true) { - for (RecordEvent e : record) - if (is_replaying) - e.callback(); - if (!loop_replay || !is_replaying) break; - } - stopReplay(); - }); - replay.start(); - sendMessage(Text.translatable("message.repeating-mod.replay_start")); - } - - public void stopReplay() { - is_recording = false; - is_replaying = false; - menu.update_btns(); - client.player.setNoGravity(false); - sendMessage(Text.translatable("message.repeating-mod.replay_stop")); - } - - public static double round(double value, int places) { - if (places < 0) throw new IllegalArgumentException(); - long factor = (long) Math.pow(10, places); - return (double) Math.round(value * factor) / factor; - } - - public static void sendMessage(Text text) { - client.player.sendMessage(Text.literal("[") - .append(Text.translatable("text.repeating-mod.name")) - .append("] ").append(text)); - } - - public static abstract class RecordEvent { - abstract void callback(); - abstract String toText(); - abstract String getType(); - - public static RecordEvent fromText(String t) { - try { - String type = String.valueOf(t.charAt(0)); - String[] args = t.substring(2).split("&"); - if (type.equals("d")) { - return new RecordDelayEvent( - Long.parseLong(args[0])); - } else if (type.equals("m")) { - return new RecordMoveEvent(new Vec3d( - Double.parseDouble(args[0]), - Double.parseDouble(args[1]), - Double.parseDouble(args[2])), - Float.parseFloat(args[3]), - Float.parseFloat(args[4])); - } else if (type.equals("s")) { - return new RecordSneakEvent( - args[0].equals("1")); - } else if (type.equals("b")) { - return new RecordBlockBreakEvent(new BlockPos( - Integer.parseInt(args[0]), - Integer.parseInt(args[1]), - Integer.parseInt(args[2]))); - } else if (type.equals("i")) { - return new RecordBlockInteractEvent( - Hand.valueOf(args[5]), - new BlockHitResult(new Vec3d( - Double.parseDouble(args[0]), - Double.parseDouble(args[1]), - Double.parseDouble(args[2])), - Direction.byId(Integer.parseInt(args[4])), - new BlockPos( - Integer.parseInt(args[0]), - Integer.parseInt(args[1]), - Integer.parseInt(args[2])), - args[3].equals("1"))); - } - } catch (Exception e) { - e.printStackTrace(); - } - return null; - } - } - - public static class RecordDelayEvent extends RecordEvent { - public long delay; - - public RecordDelayEvent(long delay) { - this.delay = delay; - } - - public void callback() { - try { - Thread.sleep(delay); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - - public String toText() { - return "d="+delay; - } - public String getType() { - return "delay"; - } - } - - public static class RecordMoveEvent extends RecordEvent { - public Vec3d vec; - public float yaw; - public float pitch; - - public RecordMoveEvent(Vec3d vec,float yaw,float pitch) { - this.vec = vec; - this.yaw = yaw; - this.pitch = pitch; - } - - public void callback() { - Vec3d p = client.player.getPos(); - Vec3d v = new Vec3d(vec.getX()-p.getX(),vec.getY()-p.getY(),vec.getZ()-p.getZ()); - client.player.move(MovementType.SELF,v); - client.player.setYaw(yaw); - client.player.setPitch(pitch); - } - - public String toText() { - return "m="+vec.getX()+"&"+vec.getY()+"&"+vec.getZ()+"&"+yaw+"&"+pitch; - } - public String getType() { - return "move"; - } - } - - public static class RecordSneakEvent extends RecordEvent { - public boolean sneaking; - - public RecordSneakEvent(boolean sneaking) { - this.sneaking = sneaking; - } - - public void callback() { - RepeatingMod.replay_sneaking = sneaking; - } - - public String toText() { - return "s="+(sneaking?"1":"0"); - } - public String getType() { - return "sneak"; - } - } - - public static class RecordBlockBreakEvent extends RecordEvent { - public BlockPos pos; - - public RecordBlockBreakEvent( - BlockPos pos) { - this.pos = pos; - } - - public void callback() { - client.interactionManager.breakBlock(pos); - } - - public String toText() { - return "b="+pos.getX()+"&"+pos.getY()+"&"+pos.getZ(); - } - public String getType() { - return "block_break"; - } - } - - public static class RecordBlockInteractEvent extends RecordEvent { - public Hand hand; - public BlockHitResult hitResult; - - public RecordBlockInteractEvent(Hand hand, BlockHitResult hitResult) { - this.hand = hand; - this.hitResult = hitResult; - } - - public void callback() { - client.interactionManager.interactBlock(client.player,hand,hitResult); - } - - public String toText() { - 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/src/main/java/themixray/repeating/mod/RepeatingScreen.java b/src/main/java/themixray/repeating/mod/RepeatingScreen.java deleted file mode 100644 index b59022b..0000000 --- a/src/main/java/themixray/repeating/mod/RepeatingScreen.java +++ /dev/null @@ -1,211 +0,0 @@ -package themixray.repeating.mod; - -import io.wispforest.owo.ui.base.*; -import io.wispforest.owo.ui.component.*; -import io.wispforest.owo.ui.container.*; -import io.wispforest.owo.ui.container.FlowLayout; -import io.wispforest.owo.ui.core.*; -import io.wispforest.owo.ui.core.Insets; -import io.wispforest.owo.util.EventSource; -import net.fabricmc.fabric.api.resource.ResourceManagerHelper; -import net.fabricmc.loader.api.FabricLoader; -import net.minecraft.resource.ResourceManager; -import net.minecraft.resource.ResourceType; -import net.minecraft.text.Text; -import net.minecraft.util.Identifier; -import org.jetbrains.annotations.NotNull; - -import javax.swing.*; -import java.awt.*; -import java.awt.datatransfer.Clipboard; -import java.awt.datatransfer.DataFlavor; -import java.awt.datatransfer.StringSelection; -import java.awt.datatransfer.UnsupportedFlavorException; -import java.io.*; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.OpenOption; -import java.nio.file.Path; - -public class RepeatingScreen extends BaseOwoScreen { - public RepeatingMod mod; - public ButtonComponent replay_btn; - public ButtonComponent record_btn; - public ButtonComponent loop_btn; - - public RepeatingScreen() { - this.mod = RepeatingMod.me; - } - - @Override - protected @NotNull OwoUIAdapter createAdapter() { - return OwoUIAdapter.create(this, Containers::horizontalFlow); - } - - public void update_btns() { - replay_btn.setMessage(Text.translatable("text.repeating-mod." + - ((mod.is_replaying) ? "stop" : "start")).append(" ") - .append(Text.translatable("text.repeating-mod.replay"))); - record_btn.setMessage(Text.translatable("text.repeating-mod." + - ((mod.is_recording) ? "stop" : "start")).append(" ") - .append(Text.translatable("text.repeating-mod.record"))); - loop_btn.setMessage(Text.of(((mod.loop_replay) ? "\uefff" : "\ueffe"))); - } - - @Override - protected void build(FlowLayout rootComponent) { - rootComponent - .surface(Surface.VANILLA_TRANSLUCENT) - .horizontalAlignment(HorizontalAlignment.CENTER) - .verticalAlignment(VerticalAlignment.TOP); - - replay_btn = (ButtonComponent) Components.button(Text.of("replay"), - (ButtonComponent btn) -> { - if (!mod.is_recording) { - if (mod.is_replaying) - mod.stopReplay(); - else mod.startReplay(); - update_btns(); - } - }).margins(Insets.of(1)).sizing( - Sizing.fixed(98),Sizing.fixed(20)); - - loop_btn = (ButtonComponent) Components.button(Text.of(""), - (ButtonComponent btn) -> { - mod.loop_replay = !mod.loop_replay; - update_btns(); - }).margins(Insets.of(1)) - .sizing(Sizing.fixed(20),Sizing.fixed(20)); - - record_btn = (ButtonComponent) Components.button(Text.of("record"), - (ButtonComponent btn) -> { - if (!mod.is_replaying) { - if (mod.is_recording) - mod.stopRecording(); - else mod.startRecording(); - update_btns(); - } - }).margins(Insets.of(1)).sizing( - Sizing.fixed(120),Sizing.fixed(20)); - - rootComponent.child( - Containers.verticalFlow(Sizing.content(), Sizing.content()) - .child(Containers.verticalFlow(Sizing.content(), Sizing.content()) - .child(Components.label(Text.translatable("text.repeating-mod.basic")).margins(Insets.of(1))) - .padding(Insets.of(5)) - .surface(Surface.DARK_PANEL) - .verticalAlignment(VerticalAlignment.CENTER) - .horizontalAlignment(HorizontalAlignment.CENTER) - .margins(Insets.of(1))) - .child(Containers.verticalFlow(Sizing.content(), Sizing.content()) - .child(Containers.horizontalFlow(Sizing.content(), Sizing.content()) - .child(replay_btn).child(loop_btn)) - .child(record_btn) - .child(Components.button(Text.translatable( - "text.repeating-mod.export"), - (ButtonComponent btn) -> { - String t = ""; - for (int i = 0; i < mod.record.size(); i++) { - t += mod.record.get(i).toText(); - if (i != mod.record.size()-1) - t += ";"; - } - - File p = new File(FabricLoader.getInstance().getGameDir().toFile(),"repeating"); - if (!p.exists()) p.mkdir(); - File file = new File(p,"export.txt"); - - try { - if (!file.exists()) file.createNewFile(); - Files.write(file.toPath(), t.getBytes()); - Runtime.getRuntime().exec("explorer /select,\""+file.getAbsolutePath()+"\""); - } catch (Exception e) { - e.printStackTrace(); - } - }).margins(Insets.of(10,1,1,1)).sizing( - Sizing.fixed(120),Sizing.fixed(20))) - .child(Components.button(Text.translatable( - "text.repeating-mod.import"), - (ButtonComponent btn) -> { - mod.record.clear(); - - File p = new File(FabricLoader.getInstance().getGameDir().toFile(),"repeating"); - if (!p.exists()) p.mkdir(); - File file = new File(p,"import.txt"); - - try { - if (!file.exists()) { - file.createNewFile(); - Runtime.getRuntime().exec("explorer /select,\""+file.getAbsolutePath()+"\""); - return; - } - String t = Files.readString(file.toPath()); - for (String s:t.split(";")) - mod.record.add(RepeatingMod.RecordEvent.fromText(s)); - } catch (Exception e) { - e.printStackTrace(); - } - }).margins(Insets.of(1)).sizing( - Sizing.fixed(120),Sizing.fixed(20))) - .padding(Insets.of(10)) - .surface(Surface.DARK_PANEL) - .verticalAlignment(VerticalAlignment.CENTER) - .horizontalAlignment(HorizontalAlignment.CENTER) - .margins(Insets.of(1))) - /*).child( - Containers.verticalFlow(Sizing.content(), Sizing.content()) - .child(Containers.verticalFlow(Sizing.content(), Sizing.content()) - .child(Components.label(Text.translatable("text.repeating-mod.parkour")).margins(Insets.of(1))) - .padding(Insets.of(5)) - .surface(Surface.DARK_PANEL) - .verticalAlignment(VerticalAlignment.CENTER) - .horizontalAlignment(HorizontalAlignment.CENTER) - .margins(Insets.of(1))) - .child(Containers.verticalFlow(Sizing.content(), Sizing.content()) - .child(Components.label(Text.translatable("text.repeating-mod.dev")).margins(Insets.of(1))) - .padding(Insets.of(10)) - .surface(Surface.DARK_PANEL) - .verticalAlignment(VerticalAlignment.CENTER) - .horizontalAlignment(HorizontalAlignment.CENTER) - .margins(Insets.of(1)))*/ - ).child( - Containers.verticalFlow(Sizing.content(), Sizing.content()) - .child(Containers.verticalFlow(Sizing.content(), Sizing.content()) - .child(Components.label(Text.translatable("text.repeating-mod.settings")).margins(Insets.of(1))) - .padding(Insets.of(5)) - .surface(Surface.DARK_PANEL) - .verticalAlignment(VerticalAlignment.CENTER) - .horizontalAlignment(HorizontalAlignment.CENTER) - .margins(Insets.of(1))) - .child(Containers.verticalFlow(Sizing.content(), Sizing.content()) - .child(Components.discreteSlider(Sizing.fixed(120),0,5) - .decimalPlaces(2) - .setFromDiscreteValue(mod.record_blocks_limit) - .message((String s)->{ - mod.record_blocks_limit = Double.parseDouble(s.replace(",",".")); - mod.conf.data.put("record_blocks_limit",mod.record_blocks_limit); - mod.conf.save(); - return Text.translatable("text.repeating-mod.block_limit",s); - }).scrollStep(0.2) - .margins(Insets.of(1)) - .tooltip(Text.translatable("text.repeating-mod.block_limit_tooltip"))) - .child(Components.discreteSlider(Sizing.fixed(120),0,1000) - .decimalPlaces(0) - .setFromDiscreteValue(mod.record_time_limit) - .message((String s)->{ - mod.record_time_limit = (long) Double.parseDouble(s.replace(",",".")); - mod.conf.data.put("record_time_limit",mod.record_time_limit); - mod.conf.save(); - return Text.translatable("text.repeating-mod.time_limit",s); - }).scrollStep(2) - .margins(Insets.of(1)) - .tooltip(Text.translatable("text.repeating-mod.time_limit_tooltip"))) - .padding(Insets.of(10)) - .surface(Surface.DARK_PANEL) - .verticalAlignment(VerticalAlignment.CENTER) - .horizontalAlignment(HorizontalAlignment.CENTER) - .margins(Insets.of(1))) - ); - update_btns(); - } -} diff --git a/src/main/java/themixray/repeating/mod/mixin/InputMixin.java b/src/main/java/themixray/repeating/mod/mixin/InputMixin.java deleted file mode 100644 index 5f3874e..0000000 --- a/src/main/java/themixray/repeating/mod/mixin/InputMixin.java +++ /dev/null @@ -1,30 +0,0 @@ -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.RepeatingMod; - -@Mixin(KeyboardInput.class) -public abstract class InputMixin { - @Inject(at = @At(value = "TAIL"), method = "tick") - private void onTickTail(boolean slowDown, float f, CallbackInfo ci) { - if (RepeatingMod.me.is_replaying) { - RepeatingMod.client.player.input.sneaking = RepeatingMod.replay_sneaking; - } - } - - @Inject(at = @At(value = "HEAD"), method = "tick") - private void onTickHead(boolean slowDown, float f, CallbackInfo ci) { - if (RepeatingMod.me.is_recording) { - RepeatingMod.RecordSneakEvent e = new RepeatingMod. - RecordSneakEvent(RepeatingMod.client.player.input.sneaking); - RepeatingMod.RecordSneakEvent l = ((RepeatingMod.RecordSneakEvent) - RepeatingMod.me.getLastRecord("sneak")); - if (l == null || l.sneaking != e.sneaking) - RepeatingMod.me.recordTick(e); - } - } -} diff --git a/src/main/java/themixray/repeating/mod/mixin/MovementMixin.java b/src/main/java/themixray/repeating/mod/mixin/MovementMixin.java deleted file mode 100644 index 83a18e5..0000000 --- a/src/main/java/themixray/repeating/mod/mixin/MovementMixin.java +++ /dev/null @@ -1,92 +0,0 @@ -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.MinecraftClient; -import net.minecraft.client.network.ClientPlayerEntity; -import net.minecraft.entity.MovementType; -import net.minecraft.text.Text; -import net.minecraft.util.ActionResult; -import net.minecraft.util.hit.HitResult; -import net.minecraft.util.math.Vec3d; -import org.spongepowered.asm.mixin.Final; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Shadow; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; -import themixray.repeating.mod.RepeatingMod; - -import java.util.Date; - -@Mixin(ClientPlayerEntity.class) -public abstract class MovementMixin { - public Vec3d lastVec = null; - @Shadow public abstract void sendMessage(Text message); - @Shadow @Final protected MinecraftClient client; - @Shadow public abstract float getYaw(float tickDelta); - @Shadow private float lastYaw; - @Shadow private float lastPitch; - - @Inject(at = @At(value = "HEAD"), method = "init") - private void init(CallbackInfo ci) { - PlayerBlockBreakEvents.AFTER.register((world, player, pos, blockState, blockEntity) -> { - if (RepeatingMod.me.is_recording) - RepeatingMod.me.recordTick(new RepeatingMod.RecordBlockBreakEvent(pos)); - }); - - UseBlockCallback.EVENT.register((player, world, hand, hitResult) -> { - if (hitResult.getType().equals(HitResult.Type.BLOCK)) - if (RepeatingMod.me.is_recording) - RepeatingMod.me.recordTick(new RepeatingMod.RecordBlockInteractEvent(hand,hitResult)); - return ActionResult.PASS; - }); - } - - @Inject(at = @At(value = "HEAD"), method = "move") - private void onMove(MovementType movementType, Vec3d vec, CallbackInfo ci) { - if (RepeatingMod.me.is_recording) { - if (vec != lastVec) { - double dist = 0; - if (lastVec != null) - dist = vec.distanceTo(lastVec); - if (dist > 0.0) { - Vec3d c = client.player.getPos(); - - RepeatingMod.RecordMoveEvent ev = new RepeatingMod.RecordMoveEvent( - new Vec3d(c.getX() + vec.getX(), - c.getY() + vec.getY(), - c.getZ() + vec.getZ()), - lastYaw, lastPitch); - - boolean just_add = true; - Date now = new Date(); - if (RepeatingMod.me.last_record != null) { - long diff = now.getTime() - RepeatingMod.me.last_record.getTime(); - boolean add_delay = true; - if (diff > 0) { - RepeatingMod.RecordEvent last_ev = RepeatingMod.me.record.get(RepeatingMod.me.record.size()-1); - if (last_ev instanceof RepeatingMod.RecordMoveEvent) { - RepeatingMod.RecordMoveEvent last_ev1 = (RepeatingMod.RecordMoveEvent) last_ev; - if (last_ev1.vec.distanceTo(ev.vec) < RepeatingMod.me.record_blocks_limit && - diff < RepeatingMod.me.record_time_limit) { - just_add = false; - add_delay = false; - last_ev1.vec = ev.vec; - } - } - } - if (add_delay) { - RepeatingMod.me.record.add(new RepeatingMod.RecordDelayEvent(diff)); - } - } - if (just_add) { - RepeatingMod.me.record.add(ev); - RepeatingMod.me.last_record = now; - } - } - } - lastVec = vec; - } - } -} diff --git a/src/main/resources/assets/minecraft/font/default.json b/src/main/resources/assets/minecraft/font/default.json index 6501fb4..ef1f683 100644 --- a/src/main/resources/assets/minecraft/font/default.json +++ b/src/main/resources/assets/minecraft/font/default.json @@ -1,17 +1,17 @@ -{ - "providers": [ - { - "file":"repeating-mod:ui/no-loop.png", - "chars":["\ueffe"], - "height":16, - "ascent":12, - "type":"bitmap" - },{ - "file":"repeating-mod:ui/loop.png", - "chars":["\uefff"], - "height":16, - "ascent":12, - "type":"bitmap" - } - ] +{ + "providers": [ + { + "file":"repeating-mod:ui/no-loop.png", + "chars":["\ueffe"], + "height":16, + "ascent":12, + "type":"bitmap" + },{ + "file":"repeating-mod:ui/loop.png", + "chars":["\uefff"], + "height":16, + "ascent":12, + "type":"bitmap" + } + ] } \ No newline at end of file diff --git a/src/main/resources/assets/renderer/shader/position_color.fsh b/src/main/resources/assets/renderer/shader/position_color.fsh new file mode 100644 index 0000000..aecb5f7 --- /dev/null +++ b/src/main/resources/assets/renderer/shader/position_color.fsh @@ -0,0 +1,12 @@ +#version 330 + +in vec4 vertexColor; + +out vec4 fragmentColor; + +void main() { + if (vertexColor.a == 0.0f) { + discard; + } + fragmentColor = vertexColor; +} \ No newline at end of file diff --git a/src/main/resources/assets/renderer/shader/position_color.vsh b/src/main/resources/assets/renderer/shader/position_color.vsh new file mode 100644 index 0000000..1f31658 --- /dev/null +++ b/src/main/resources/assets/renderer/shader/position_color.vsh @@ -0,0 +1,13 @@ +#version 330 + +layout (location = 0) in vec3 i_pos; +layout (location = 1) in vec4 i_color; + +uniform mat4 u_projection; + +out vec4 vertexColor; + +void main() { + gl_Position = u_projection * vec4(i_pos, 1.0f); + vertexColor = i_color; +} \ No newline at end of file diff --git a/src/main/resources/assets/repeating-mod/lang/en_us.json b/src/main/resources/assets/repeating-mod/lang/en_us.json index 57f79f0..d2f69be 100644 --- a/src/main/resources/assets/repeating-mod/lang/en_us.json +++ b/src/main/resources/assets/repeating-mod/lang/en_us.json @@ -1,26 +1,36 @@ -{ - "key.repeating-mod.menu": "Repeating menu", - "key.repeating-mod.toggle_replay": "Toggle replay", - "key.repeating-mod.toggle_record": "Toggle recording", - - "text.repeating-mod.name": "Repeating Mod", - "text.repeating-mod.record": "record", - "text.repeating-mod.replay": "replay", - "text.repeating-mod.start": "Start", - "text.repeating-mod.stop": "Stop", - "text.repeating-mod.export": "Export record", - "text.repeating-mod.import": "Import record", - "text.repeating-mod.basic": "Basic mode", - "text.repeating-mod.parkour": "Parkour mode", - "text.repeating-mod.settings": "Settings", - "text.repeating-mod.dev": "In development...", - "text.repeating-mod.block_limit": "Block limit: %s", - "text.repeating-mod.block_limit_tooltip": "Two recording events will\nbe summed up if the\ndistance between them is\nless than the limit.", - "text.repeating-mod.time_limit": "Time limit: %s ms", - "text.repeating-mod.time_limit_tooltip": "Two recording events will\nbe summed up if the time\nbetween them is less than\nthe limit.", - - "message.repeating-mod.replay_start": "Replay started", - "message.repeating-mod.replay_stop": "Replay finished", - "message.repeating-mod.record_start": "Record started", - "message.repeating-mod.record_stop": "Record finished" +{ + "key.repeating-mod.menu": "Repeating menu", + "key.repeating-mod.toggle_replay": "Toggle replay", + "key.repeating-mod.toggle_record": "Toggle recording", + + "text.repeating-mod.name": "Repeating Mod", + "text.repeating-mod.start_record": "Start record", + "text.repeating-mod.stop_record": "Stop record", + "text.repeating-mod.start_replay": "Start replay", + "text.repeating-mod.stop_replay": "Stop replay", + "text.repeating-mod.record_tooltip": "Start/stop recording all activities", + "text.repeating-mod.replay_tooltip": "Start/stop repeating recorded actions", + "text.repeating-mod.loop_tooltip": "Enable/disable repeating of recorded actions replay", + "text.repeating-mod.export_record": "Export record", + "text.repeating-mod.import": "Import record", + "text.repeating-mod.export_tooltip": "Exporting a recording to a file", + "text.repeating-mod.import_tooltip": "Importing a recording from a file", + "text.repeating-mod.dev": "In development...", + "text.repeating-mod.nan_pos_delay": "No pos timer", + "text.repeating-mod.pos_delay": "Pos timer: %s ticks", + "text.repeating-mod.pos_delay_tooltip": "Timer after which the pos\nevent is added (20 ticks = 1 sec)", + "text.repeating-mod.unnamed": "Unnamed Record", + "text.repeating-mod.on_loop": "Enable repeat", + "text.repeating-mod.off_loop": "Disable repeat", + "text.repeating-mod.recorded_at": "Recorded at", + "text.repeating-mod.author": "Author", + "text.repeating-mod.delete": "Delete", + "text.repeating-mod.start": "Start", + "text.repeating-mod.stop": "Stop", + "text.repeating-mod.export": "Export", + + "message.repeating-mod.replay_start": "Replay started", + "message.repeating-mod.replay_stop": "Replay finished", + "message.repeating-mod.record_start": "Record started", + "message.repeating-mod.record_stop": "Record finished" } \ No newline at end of file diff --git a/src/main/resources/assets/repeating-mod/lang/ru_ru.json b/src/main/resources/assets/repeating-mod/lang/ru_ru.json index 8b62f15..84bf569 100644 --- a/src/main/resources/assets/repeating-mod/lang/ru_ru.json +++ b/src/main/resources/assets/repeating-mod/lang/ru_ru.json @@ -1,27 +1,38 @@ -{ - "key.repeating-mod.menu": "Меню репитинга", - "key.repeating-mod.toggle_replay": "Вкл/выкл повтор", - "key.repeating-mod.toggle_record": "Вкл/выкл запись", - - "text.repeating-mod.name": "Репитинг Мод", - "text.repeating-mod.record": "запись", - "text.repeating-mod.replay": "повтор", - "text.repeating-mod.start": "Начать", - "text.repeating-mod.stop": "Остановить", - "text.repeating-mod.export": "Экспорт записи", - "text.repeating-mod.import": "Импорт записи", - "text.repeating-mod.basic": "Обычный режим", - "text.repeating-mod.parkour": "Режим паркура", - "text.repeating-mod.settings": "Настройки", - "text.repeating-mod.dev": "В разработке...", - "text.repeating-mod.block_limit": "Лимит блоков: %s", - "text.repeating-mod.block_limit_tooltip": "Два ивента записи будут\nсуммироваться, если\nрасстояние между ними\nменьше лимита.", - "text.repeating-mod.time_limit": "Лимит времени: %s мс", - "text.repeating-mod.time_limit_tooltip": "Два ивента записи будут\nсуммироваться, если время\nмежду ними меньше лимита.", - - "message.repeating-mod.replay_start": "Повтор начат", - "message.repeating-mod.replay_stop": "Повтор закончен", - "message.repeating-mod.record_start": "Запись начата", - "message.repeating-mod.record_stop": "Запись закончена" -} - +{ + "key.repeating-mod.menu": "Меню репитинга", + "key.repeating-mod.toggle_replay": "Вкл/выкл повтор", + "key.repeating-mod.toggle_record": "Вкл/выкл запись", + + "text.repeating-mod.name": "Репитинг Мод", + "text.repeating-mod.start_record": "Начать запись", + "text.repeating-mod.stop_record": "Остановить запись", + "text.repeating-mod.start_replay": "Начать повтор", + "text.repeating-mod.stop_replay": "Остановить повтор", + "text.repeating-mod.record_tooltip": "Начать/остановить запись всех действий", + "text.repeating-mod.replay_tooltip": "Начать/остановить повтор записанных действий", + "text.repeating-mod.loop_tooltip": "Вкл/выкл повтор записи", + "text.repeating-mod.import": "Импорт записи", + "text.repeating-mod.export_tooltip": "Экспорт записи в файл", + "text.repeating-mod.import_tooltip": "Импорт записи из файла", + "text.repeating-mod.dev": "В разработке...", + "text.repeating-mod.nan_pos_delay": "Таймера позиции нету", + "text.repeating-mod.pos_delay": "Таймер позиции: %s тиков", + "text.repeating-mod.pos_delay_tooltip": "Таймер, после которой добавляется\nивент позиции (20 тиков = 1 сек)", + "text.repeating-mod.unnamed": "Безымянная Запись", + "text.repeating-mod.on_loop": "Включить повторение", + "text.repeating-mod.off_loop": "Выключить повторение", + "text.repeating-mod.recorded_at": "Записано в", + "text.repeating-mod.author": "Автор", + + "message.repeating-mod.replay_start": "Повтор начат", + "message.repeating-mod.replay_stop": "Повтор закончен", + "message.repeating-mod.record_start": "Запись начата", + "message.repeating-mod.record_stop": "Запись закончена", + + "text.repeating-mod.export_record": "Экпорт записи", + "text.repeating-mod.export": "Экспорт", + "text.repeating-mod.delete": "Удалить", + "text.repeating-mod.start": "Старт", + "text.repeating-mod.stop": "Стоп" +} + diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index 5328b3f..4a40393 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -1,38 +1,38 @@ -{ - "schemaVersion": 1, - "id": "repeating-mod", - "version": "${version}", - - "name": "Repeating Mod", - "description": "Mod that repeats your actions. ", - "authors": [ - "TheMixRay" - ], - "contact": { - "homepage": "https://fabricmc.net/", - "sources": "https://github.com/FabricMC/fabric-example-mod" - }, - - "license": "CC0-1.0", - "icon": "icon.png", - - "environment": "client", - "entrypoints": { - "client": [ - "themixray.repeating.mod.RepeatingMod" - ] - }, - "mixins": [ - "repeating-mod.mixins.json" - ], - - "depends": { - "fabricloader": ">=0.14.14", - "fabric-api": "*", - "minecraft": "~1.19.3", - "java": ">=17" - }, - "suggests": { - "another-mod": "*" - } -} +{ + "schemaVersion": 1, + "id": "repeating-mod", + "version": "${version}", + + "name": "Repeating Mod", + "description": "Mod that repeats your recorded actions. ", + "authors": [ + "TheMixRay" + ], + "contact": { + "homepage": "https://modrinth.com/mod/repeating-mod", + "sources": "https://github.com/MeexReay/repeating-mod" + }, + + "license": "WTFPL", + "icon": "icon.png", + + "environment": "client", + "entrypoints": { + "client": [ + "ru.themixray.repeating_mod.Main" + ] + }, + "mixins": [ + "repeating-mod.mixins.json" + ], + + "depends": { + "fabricloader": ">=0.14.14", + "fabric-api": "*", + "minecraft": ">=1.21.4", + "java": ">=17" + }, + "suggests": { + "another-mod": "*" + } +} diff --git a/src/main/resources/icon.png b/src/main/resources/icon.png index f0987b0..0108e5d 100644 Binary files a/src/main/resources/icon.png and b/src/main/resources/icon.png differ diff --git a/src/main/resources/repeating-mod.mixins.json b/src/main/resources/repeating-mod.mixins.json index 50f497d..7bfbaa8 100644 --- a/src/main/resources/repeating-mod.mixins.json +++ b/src/main/resources/repeating-mod.mixins.json @@ -1,15 +1,20 @@ -{ - "required": true, - "minVersion": "0.8", - "package": "themixray.repeating.mod.mixin", - "compatibilityLevel": "JAVA_17", - "mixins": [ - ], - "client": [ - "MovementMixin", - "InputMixin" - ], - "injectors": { - "defaultRequire": 1 - } -} +{ + "required": true, + "minVersion": "0.8", + "package": "ru.themixray.repeating_mod.mixin", + "compatibilityLevel": "JAVA_17", + "mixins": [ + ], + "client": [ + "MovementMixin", + "InputMixin", + "RendererMixin", + "EntityMixin", + "ClientMixin", + "ScreenMixin", + "PlayerMixin" + ], + "injectors": { + "defaultRequire": 1 + } +}