Compare commits

..

94 commits

Author SHA1 Message Date
654589355d fix version name 2025-06-18 18:18:55 +03:00
0b3bac264f port to 1.21.6 2025-06-18 18:17:57 +03:00
59a2175fb2 port to 1.21.5 2025-06-18 16:18:48 +03:00
ac1ee8ec6d fix record list widget 2025-06-18 15:57:29 +03:00
c65e906500 port to 1.21.4, RECORDS LIST IS BROKEN 2025-06-15 20:36:55 +03:00
f082788406 set fabric mod json 1.21. 2025-06-15 20:06:31 +03:00
072efb64f6 Merge branch '1.21' 2025-06-15 19:59:44 +03:00
06634a8704 set license wtfpl in mod 2025-06-15 19:59:29 +03:00
d101b11c36 fix movements 1.21.2 2025-06-15 16:23:04 +03:00
412c3f47ab port to 1.21.2, MOVEMENTS ARE BROKEN 2025-06-14 23:46:18 +03:00
9d7ac6b213 Merge pull request 'add "compatible with" comment' (#1) from 1.21 into master
Reviewed-on: MeexReay/repeating_mod#1
2025-06-14 22:23:13 +03:00
4714b1dbfb add compatible with comment 2025-06-14 22:22:27 +03:00
5c9df13d15 fix gitea actions 2025-06-14 21:38:30 +03:00
38127eb69f make img folder for readme images 2025-06-14 20:35:23 +03:00
5d5e628cdf add dev artifacts 2025-06-14 20:18:04 +03:00
42bf96701e Merge branch 'master' of https://git.meex.lol/MeexReay/repeating_mod 2025-06-14 20:05:07 +03:00
666377e5ee fix pos event 2025-06-14 20:04:53 +03:00
63132684cd Update README.md 2025-06-14 19:28:24 +03:00
d4abb39c80 downgrade upload-artifact 2025-06-14 08:01:57 +03:00
2e62b69a21 action refactor 2025-06-14 07:45:08 +03:00
62dcb75960 fix upload artifact 2025-06-14 06:48:00 +03:00
b6084063a1 make gradlew exec 2025-06-14 06:23:35 +03:00
1e4f4fdaeb gitea actions fix x3 2025-06-14 06:00:20 +03:00
f49b1e21a3 maybefix gitea actions 2025-06-14 05:23:49 +03:00
759b6cb05e gitea action 2025-06-14 05:17:53 +03:00
2944395e54 remove some crap 2025-06-14 05:09:39 +03:00
45998bbe2e rename group-id and artifact-id, fix recording points render 2025-06-14 05:02:06 +03:00
8242b2c601 readme update 2025-06-14 03:13:26 +03:00
e7abf3e15b open source! 2025-06-14 03:04:16 +03:00
61c755aabc move to 1.21 and fix for nixos 2025-06-14 03:02:04 +03:00
c02d96edca Merge branch 'main' of https://git.meex.lol/MeexReay/repeating_mod 2024-12-08 02:40:33 +03:00
cc12eab006 wh 2024-12-08 02:39:49 +03:00
MeexReay
41706ade80
Update README.md 2024-04-26 20:08:14 +03:00
5d9b27cd9a Merge branch 'main' of https://github.com/MeexReay/repeating-mod 2024-04-26 17:54:52 +03:00
73154eff2b sort records by last modified + fix errors 2024-04-26 17:54:43 +03:00
MeexReay
1ceea8bcd4
Update README.md 2024-04-23 20:44:56 +03:00
ed998cc701 Merge branch 'main' of https://github.com/MeexReay/repeating-mod 2024-04-23 20:44:02 +03:00
e808b6ca09 import now copies file to your record folder 2024-04-23 20:43:49 +03:00
MeexReay
e5981e6e60
Update README.md 2024-04-23 20:38:34 +03:00
a345f65797 Merge branch 'main' of https://github.com/MeexReay/repeating-mod 2024-04-23 20:37:44 +03:00
8d47394acc gui not working, but im tried 2024-04-23 20:37:36 +03:00
MeexReay
5fa8fba00d
Update README.md 2024-04-23 20:23:06 +03:00
MeexReay
8a6e680ff9
Update README.md 2024-04-23 19:20:14 +03:00
f38629a294 events refactor + mixin testing 2024-04-23 19:18:22 +03:00
e76c3dfccf events refactor + mixin testing 2024-04-23 19:18:14 +03:00
28b4265a55 license 2024-04-23 15:17:39 +03:00
a255e4b804 license 2024-04-23 15:15:36 +03:00
aad9a07484 license 2024-04-23 15:13:55 +03:00
278561d374 Merge branch 'main' of https://github.com/MeexReay/repeating-mod 2024-04-23 15:07:45 +03:00
ffc4423b1f license 2024-04-23 15:07:37 +03:00
MeexReay
26496ca3f2
Update README.md 2024-04-23 14:46:56 +03:00
84eb4e026f Merge branch 'main' of https://github.com/MeexReay/repeating-mod 2024-04-23 03:55:44 +03:00
9596fedb6a license 2024-04-23 03:55:36 +03:00
MeexReay
50927fd2f7
Update README.md 2024-04-23 03:48:40 +03:00
MeexReay
99c057c692
Update README.md 2024-04-23 03:47:53 +03:00
e80ee84442 Merge branch 'main' of https://github.com/MeexReay/repeating-mod 2024-04-22 20:08:43 +03:00
MeexReay
74c061c29f
Update README.md 2024-04-22 20:08:26 +03:00
b162304a0b change icon to new one 2024-04-22 20:08:14 +03:00
MeexReay
3e96a7b360
Update README.md 2024-04-22 20:06:43 +03:00
596ca3c0d3 some fixes 2024-04-22 20:06:18 +03:00
MeexReay
70e97ecf92
Update README.md 2024-04-22 19:38:08 +03:00
MeexReay
e194d7f204
Update README.md 2024-04-22 18:55:50 +03:00
MeexReay
3f3ce10d7f
Update README.md 2024-04-22 18:52:44 +03:00
MeexReay
f603f5ad27
Update README.md 2024-04-22 18:50:46 +03:00
MeexReay
a1e4cb0c8d
Update README.md 2024-04-22 18:43:20 +03:00
27627e8404 1.20.4 compatibility 2024-04-22 18:17:31 +03:00
c5132221ae 1.20.4 compatibility 2024-04-22 18:17:22 +03:00
2f02658630 change version to 1.1.0 2024-04-22 14:09:46 +03:00
ae03aa9627 menu interface move 2024-04-22 14:09:06 +03:00
efcc61b578 browse file directory exception 2024-04-22 13:28:45 +03:00
38dad10048 fix records list and add btns 2024-04-22 00:25:43 +03:00
b6589b3629 screen rewrite what LOL 2024-04-21 15:35:14 +03:00
d78c9a35ca Merge branch 'main' of https://github.com/MeexReay/repeating-mod 2024-04-20 18:57:43 +03:00
f3acb4df10 small refactoring 2024-04-20 18:39:35 +03:00
MeexReay
f3e5cfabf6
Create LICENSE 2024-04-04 13:56:53 +03:00
MeexReay
605c54a154
Merge pull request #4 from MeexReay/1.19
Merge pull request #1 from MeexReay/main
2024-04-04 13:53:49 +03:00
MeexReay
cd42d8e5b1
Merge pull request #1 from MeexReay/main
Main Pull request
2024-04-04 13:53:11 +03:00
MeexReay
e95f520b14
Delete LICENSE 2024-04-04 13:52:33 +03:00
MeexReay
543f2a18cf fixed 1.20.2 incompatibility 2023-10-20 22:27:39 +03:00
MeexReay
e593859723 1.0.6 update
removed owo lib
fixed some bugs
2023-10-20 16:46:07 +03:00
MeexReay
1ad16e6343 version 1.0.5 2023-05-31 22:43:33 +03:00
MeexReay
7dfbbdc959 ms -> ticks 2023-05-31 22:42:20 +03:00
MeexReay
51b68dd48f fix easy config 2023-05-31 12:22:28 +03:00
MeexReay
629a44a27a fix -1 pos delay 2023-05-31 00:38:00 +03:00
MeexReay
96c4ce4b9d added move tp when repeating is wrong 2023-05-30 22:58:25 +03:00
MeexReay
887bcd2221 fix crashing bug 2023-05-30 21:31:13 +03:00
MeexReay
4dfd0f8aba some fixes 2023-05-30 19:56:19 +03:00
MeexReay
68e7572139 MOVEMENT move -> input 2023-05-30 19:45:23 +03:00
MeexReay
0f7bfff5d3 json -> yaml 2023-05-30 02:08:33 +03:00
MeexReay
8800995bbd added org json simple 2023-05-30 00:50:21 +03:00
MeexReay
0d043e40ea Merge branch 'main' of https://github.com/MeexReay/repeating-mod 2023-05-30 00:49:45 +03:00
MeexReay
4c7ebd7618 added org json simple 2023-05-30 00:49:37 +03:00
MeexReay
3f0e5b8733
Update README.md 2023-05-30 00:08:48 +03:00
MeexReay
6936357366 add preview gif 2023-05-30 00:08:00 +03:00
68 changed files with 3259 additions and 1062 deletions

29
.gitea/workflows/java.yml Normal file
View file

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

42
.gitignore vendored Normal file
View file

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

126
LICENSE
View file

@ -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 <sam@hocevar.net>
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.

View file

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

View file

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

BIN
gif.gif

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 MiB

View file

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

Binary file not shown.

View file

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

29
gradlew vendored Executable file → Normal file
View file

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

20
gradlew.bat vendored
View file

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

BIN
img/menu.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

BIN
img/preview.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 MiB

View file

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

6
shell.nix Normal file
View file

@ -0,0 +1,6 @@
{ pkgs ? import <nixpkgs> {} }:
pkgs.mkShell {
shellHook = ''
export LD_LIBRARY_PATH="''${LD_LIBRARY_PATH}''${LD_LIBRARY_PATH:+:}${pkgs.libglvnd}/lib"
'';
}

View file

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

View file

@ -0,0 +1,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<String,String> def = new HashMap<>();
def.put("record_pos_delay", String.valueOf(record_pos_delay));
conf = new EasyConfig(loader.getConfigDir(),"repeating-mod",def);
record_pos_delay = Long.parseLong(conf.data.get("record_pos_delay"));
menu_key = KeyBindingHelper.registerKeyBinding(new KeyBinding(
"key.repeating-mod.menu",InputUtil.Type.KEYSYM,
GLFW.GLFW_KEY_J,"text.repeating-mod.name"));
toggle_replay_key = KeyBindingHelper.registerKeyBinding(new KeyBinding(
"key.repeating-mod.toggle_replay",InputUtil.Type.KEYSYM,
-1,"text.repeating-mod.name"));
toggle_record_key = KeyBindingHelper.registerKeyBinding(new KeyBinding(
"key.repeating-mod.toggle_record",InputUtil.Type.KEYSYM,
-1,"text.repeating-mod.name"));
menu = new RepeatingScreen();
ClientTickEvents.END_CLIENT_TICK.register(client -> {
if (menu_key.wasPressed())
client.setScreen(menu);
if (toggle_replay_key.wasPressed()) {
if (now_record != null) {
if (!is_recording) {
if (is_replaying)
stopReplay();
else startReplay();
menu.updateButtons();
}
}
}
if (toggle_record_key.wasPressed()) {
if (!is_replaying) {
if (is_recording)
stopRecording();
else startRecording();
menu.updateButtons();
}
}
});
new TickTask(0,0) {
@Override
public void run() {
living_ticks++;
}
};
System.setProperty("java.awt.headless", "false");
}
public void setNowRecord(RecordState record) {
now_record = record;
}
public void drawRecordPos(WorldBuffer buffer, Vec3d pos, Color color) {
RenderHelper.drawRectFromTri(buffer,
(float) pos.getX() - 0.25F,
(float) pos.getY() + 0.01F,
(float) pos.getZ() - 0.25F,
(float) pos.getX() + 0.25F,
(float) pos.getY() + 0.01F,
(float) pos.getZ() - 0.25F,
(float) pos.getX() + 0.25F,
(float) pos.getY() + 0.01F,
(float) pos.getZ() + 0.25F,
(float) pos.getX() - 0.25F,
(float) pos.getY() + 0.01F,
(float) pos.getZ() + 0.25F,
color);
}
public void startRecording() {
is_recording = true;
menu.updateButtons();
now_record = record_list.newRecord();
Vec3d start_pos = client.player.getPos();
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<RecordEvent> 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);
// }
}

View file

@ -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<RecordState> 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<RecordState> getRecords() {
return records;
}
public File getFolder() {
return folder;
}
public RecordListWidget getWidget() {
return widget;
}
public void loadRecords() {
LinkedList<File> 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;
}
}

View file

@ -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<RecordEvent> events;
private Vec3d start_record_pos;
private Vec3d finish_record_pos;
public RecordState(File file,
String name,
Date date,
String author,
List<RecordEvent> events,
Vec3d start_record_pos,
Vec3d finish_record_pos) {
this.file = file;
this.name = name;
this.date = date;
this.author = author;
this.events = events;
this.start_record_pos = start_record_pos;
this.finish_record_pos = finish_record_pos;
}
public File getFile() {
return file;
}
public String getName() {
return name;
}
public String getAuthor() {
return author;
}
public Date getDate() {
return date;
}
public List<RecordEvent> getEvents() {
return events;
}
public Vec3d getFinishRecordPos() {
return finish_record_pos;
}
public Vec3d getStartRecordPos() {
return start_record_pos;
}
public void setAuthor(String author) {
this.author = author;
}
public void setDate(Date date) {
this.date = date;
}
public void setName(String name) {
this.name = name;
}
public void setEvents(List<RecordEvent> events) {
this.events = events;
}
public void setFinishRecordPos(Vec3d finish_record_pos) {
this.finish_record_pos = finish_record_pos;
}
public void setStartRecordPos(Vec3d start_record_pos) {
this.start_record_pos = start_record_pos;
}
public void addEvent(RecordEvent event) {
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<String> lines = List.of(text.split("\n"));
List<String> signature = lines.subList(0,4);
String name = signature.get(0);
Date date = DATE_FORMAT.parse(signature.get(1));
String author = signature.get(2);
String record_pos = signature.get(3);
String[] lss0 = record_pos.split("x");
String[] lss1 = lss0[0].split("n");
String[] lss2 = lss0[1].split("n");
Vec3d start_record_pos = new Vec3d(
Float.parseFloat(lss1[0]),
Float.parseFloat(lss1[1]),
Float.parseFloat(lss1[2]));
Vec3d finish_record_pos = new Vec3d(
Float.parseFloat(lss2[0]),
Float.parseFloat(lss2[1]),
Float.parseFloat(lss2[2]));
List<String> event_lines = lines.subList(4,lines.size());
List<RecordEvent> events = event_lines.stream().map(RecordEvent::deserialize).toList();
return new RecordState(file, name, date, author, events, start_record_pos, finish_record_pos);
}
public void remove() {
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();
}
}

View file

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

View file

@ -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<RenderListener> render_listeners = new ArrayList<>();
public ButtonWidget record_btn;
public ButtonWidget loop_btn;
public ButtonWidget import_btn;
public SliderWidget pos_delay_slider;
public boolean was_build = false;
protected RepeatingScreen() {
super(Text.empty());
}
public static void addRenderListener(RenderListener render) {
render_listeners.add(render);
}
public static void removeRenderListener(RenderListener render) {
render_listeners.remove(render);
}
public void updateButtons() {
if (was_build) {
record_btn.setMessage(Text.translatable("text.repeating-mod." + ((Main.me.is_recording) ? "stop_record" : "start_record")));
loop_btn.setMessage(Text.translatable("text.repeating-mod." + ((Main.me.loop_replay) ? "off_loop" : "on_loop")));
}
}
@Override
public void render(DrawContext context, int mouseX, int mouseY, float delta) {
// renderBackground(context, mouseX, mouseY, delta);
for (RenderListener l : render_listeners) {
if (l.beforeRender()) {
l.render(context, mouseX, mouseY, delta);
}
}
super.render(context, mouseX, mouseY, delta);
for (RenderListener l : render_listeners) {
if (!l.beforeRender()) {
l.render(context, mouseX, mouseY, delta);
}
}
}
@Override
protected void init() {
RecordListWidget list_widget = Main.me.record_list.getWidget();
list_widget.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 extends Element & Drawable & Selectable> T addDrawableChild(T drawableElement) {
return super.addDrawableChild(drawableElement);
}
public <T extends Drawable> T addDrawable(T drawable) {
return super.addDrawable(drawable);
}
public <T extends Element & Selectable> T addSelectableChild(T child) {
return super.addSelectableChild(child);
}
public void remove(Element child) {
super.remove(child);
}
}

View file

@ -0,0 +1,95 @@
package ru.themixray.repeating_mod;
import java.util.ArrayList;
import java.util.List;
public abstract class TickTask implements Runnable {
public static List<TickTask> tasks = new ArrayList<>();
public static void tickTasks(TickAt at) {
for (TickTask t:new ArrayList<>(tasks))
if (t.getAt() == at) t.tick();
}
private long living;
private long delay;
private boolean is_repeating;
private long period;
private boolean is_cancelled;
private TickAt at;
public enum TickAt {
CLIENT_HEAD, CLIENT_TAIL,
MOVEMENT_HEAD, MOVEMENT_TAIL,
RENDER_HEAD, RENDER_TAIL,
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++;
}
}

View file

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

View file

@ -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<? extends RecordEvent> ev;
private char ch;
private String name;
RecordEventType(char ch, String name, Class<? extends RecordEvent> ev) {
this.ev = ev;
this.ch = ch;
this.name = name;
}
public Class<? extends RecordEvent> 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;
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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<ClickableWidget> children;
private RecordListWidget parent;
private int x;
private int y;
private int width;
private int height;
public RecordWidget(int x, int y, int width, int height, RecordState record, RecordListWidget parent) {
this.parent = parent;
this.record = record;
this.x = x;
this.y = y;
this.width = width;
this.height = height;
this.children = new ArrayList<>();
}
public 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<ClickableWidget> getChildren() {
return children;
}
@Override
public void forEachChild(Consumer<ClickableWidget> consumer) {
children.forEach(consumer);
}
public void init(RepeatingScreen screen) {
this.children = new ArrayList<>();
TextFieldWidget name_widget = new TextFieldWidget(
Main.client.textRenderer,
parent.getX() + getX() + 5,
parent.getY() + getY() + 5,
102,
10,
Text.empty());
name_widget.setText(record.getName());
name_widget.setChangedListener((s) -> {
record.setName(s);
try {
record.save();
} catch (IOException e) {
throw new RuntimeException(e);
}
});
children.add(name_widget);
ButtonWidget delete_button = ButtonWidget.builder(Text.translatable("text.repeating-mod.delete"), (i) -> {
record.remove();
}).dimensions(parent.getX() + getX() + 110,parent.getY() + getY() + 4, 65, 13).build();
children.add(delete_button);
ButtonWidget export_button = ButtonWidget.builder(Text.translatable("text.repeating-mod.export"), (i) -> {
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<Text> 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);
}
}
}

View file

@ -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<String,Object> data;
public EasyConfig(Path path, Map<String,Object> 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<String,Object> p) {
return JSONValue.toJSONString(p);
}
private Map<String,Object> toMap(String j) {
return (Map<String, Object>) JSONValue.parse(j);
}
private Map<String,Object> read() {
try {
return toMap(Files.readString(path));
} catch (IOException e) {
e.printStackTrace();
}
return new HashMap<>();
}
private void write(Map<String,Object> p) {
try {
Files.write(path,toJson(p).getBytes());
} catch (IOException e) {
e.printStackTrace();
}
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,12 @@
#version 330
in vec4 vertexColor;
out vec4 fragmentColor;
void main() {
if (vertexColor.a == 0.0f) {
discard;
}
fragmentColor = vertexColor;
}

View file

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

View file

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

View file

@ -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": "Стоп"
}

View file

@ -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": "*"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Before After
Before After

View file

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