diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 0e259d4..0000000 --- a/LICENSE +++ /dev/null @@ -1,121 +0,0 @@ -Creative Commons Legal Code - -CC0 1.0 Universal - - 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. - -Statement of Purpose - -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. diff --git a/README.md b/README.md index fd96346..157c82d 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,17 @@ -# Fabric Example Mod +# Repeating Mod (Fabric) -## Setup +This mod can record your movements and play them back. -For setup instructions please see the [fabric wiki page](https://fabricmc.net/wiki/tutorial:setup) that relates to the IDE that you are using. +![Preview gif](preview.gif) -## License +## Controls -This template is available under the CC0 license. Feel free to learn from it and incorporate it in your own projects. +``` +Menu | J +Toggle recording | not specified +Toggle replay | not specified +``` + +## Menu + +![Repeating menu](https://github.com/MeexReay/repeating-mod/assets/127148610/da923fe5-d44d-421b-b601-2a65cb5543eb) diff --git a/build.gradle b/build.gradle index 3c6b0f5..220e433 100644 --- a/build.gradle +++ b/build.gradle @@ -13,9 +13,17 @@ repositories { // See https://docs.gradle.org/current/userguide/declaring_repositories.html // for more information about repositories. maven { url 'https://maven.wispforest.io' } + mavenCentral() } dependencies { + compileOnly 'org.projectlombok:lombok:1.18.24' + annotationProcessor 'org.projectlombok:lombok:1.18.24' + + //add joml + modImplementation 'org.joml:joml:1.10.4' + include 'org.joml:joml:1.10.4' + // To change the versions see the gradle.properties file minecraft "com.mojang:minecraft:${project.minecraft_version}" mappings "net.fabricmc:yarn:${project.yarn_mappings}:v2" @@ -29,15 +37,13 @@ dependencies { // 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}" +// 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' +// include "io.wispforest:owo-sentinel:${project.owo_version}" } base { diff --git a/gradle.properties b/gradle.properties index 2bf89a5..6be8dc4 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,16 +4,16 @@ 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 +minecraft_version=1.20 +yarn_mappings=1.20+build.1 +loader_version=0.14.23 # Mod Properties -mod_version = 1.0.0 +mod_version = 1.0.7 maven_group = themixray.repeating.mod archives_base_name = repeating-mod # Dependencies -fabric_version=0.76.1+1.19.3 +fabric_version=0.83.0+1.20 -owo_version=0.10.3+1.19.3 +#owo_version=0.11.1+1.20 diff --git a/preview.gif b/preview.gif new file mode 100644 index 0000000..fbef477 Binary files /dev/null and b/preview.gif differ diff --git a/src/main/java/themixray/repeating/mod/EasyConfig.java b/src/main/java/themixray/repeating/mod/EasyConfig.java new file mode 100644 index 0000000..8836722 --- /dev/null +++ b/src/main/java/themixray/repeating/mod/EasyConfig.java @@ -0,0 +1,103 @@ +package themixray.repeating.mod; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.HashMap; +import java.util.Map; +import java.io.File; + +public class EasyConfig { + public final Path path; + public final File file; + public Map data; + + public EasyConfig(File f, Map def) { + this.path = f.toPath(); + this.file = f; + this.data = new HashMap<>(); + + if (!file.exists()) { + try { + file.createNewFile(); + write(def); + } catch (IOException e) { + e.printStackTrace(); + } + } + + reload(); + + for (Map.Entry m:def.entrySet()) + if (!data.containsKey(m.getKey())) + data.put(m.getKey(),m.getValue()); + + save(); + } + public EasyConfig(Path f, Map def) { + this(f.toFile(),def); + } + public EasyConfig(String parent,String child,Map def) { + this(new File(parent,child),def); + } + public EasyConfig(File parent,String child,Map def) { + this(new File(parent,child),def); + } + public EasyConfig(Path parent,String child,Map def) { + this(new File(parent.toFile(),child),def); + } + + public EasyConfig(File f) { + this(f,new HashMap<>()); + } + public EasyConfig(Path path) { + this(path.toFile(),new HashMap<>()); + } + public EasyConfig(String parent,String child) { + this(new File(parent,child),new HashMap<>()); + } + public EasyConfig(File parent,String child) { + this(new File(parent,child),new HashMap<>()); + } + public EasyConfig(Path parent,String child) { + this(new File(parent.toFile(),child),new HashMap<>()); + } + + public void reload() { + data = read(); + } + public void save() { + write(data); + } + + private String toText(Map p) { + StringBuilder t = new StringBuilder(); + for (Map.Entry e:p.entrySet()) + t.append(e.getKey()).append("=").append(e.getValue()).append("\n"); + return t.toString(); + } + private Map toMap(String j) { + Map m = new HashMap<>(); + for (String l:j.split("\n")) { + String s[] = l.split("="); + m.put(s[0],s[1]); + } + return m; + } + + private Map read() { + try { + return toMap(Files.readString(path)); + } catch (IOException e) { + e.printStackTrace(); + } + return new HashMap<>(); + } + private void write(Map p) { + try { + Files.write(path, toText(p).getBytes()); + } catch (IOException e) { + e.printStackTrace(); + } + } +} diff --git a/src/main/java/themixray/repeating/mod/RepeatingMod.java b/src/main/java/themixray/repeating/mod/RepeatingMod.java index 40a36bf..068aa81 100644 --- a/src/main/java/themixray/repeating/mod/RepeatingMod.java +++ b/src/main/java/themixray/repeating/mod/RepeatingMod.java @@ -4,20 +4,22 @@ 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.fabric.api.client.rendering.v1.DimensionRenderingRegistry; +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.render.*; 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.registry.RegistryKey; +import net.minecraft.registry.RegistryKeys; +import net.minecraft.text.MutableText; +import net.minecraft.text.Style; import net.minecraft.text.Text; +import net.minecraft.text.TextColor; +import net.minecraft.util.Formatting; import net.minecraft.util.Hand; import net.minecraft.util.Identifier; import net.minecraft.util.hit.BlockHitResult; @@ -25,12 +27,16 @@ 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.lwjgl.opengl.GL11; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import themixray.repeating.mod.render.RenderHelper; +import themixray.repeating.mod.render.RenderSystem; +import themixray.repeating.mod.render.buffer.WorldBuffer; -import java.io.File; -import java.io.InputStream; +import java.awt.*; import java.util.*; +import java.util.List; public class RepeatingMod implements ClientModInitializer { public static final Logger LOGGER = LoggerFactory.getLogger("repeating-mod"); @@ -38,22 +44,29 @@ public class RepeatingMod implements ClientModInitializer { public static final FabricLoader loader = FabricLoader.getInstance(); public static RepeatingMod me; + public Vec3d start_record_pos = null; + public Vec3d finish_record_pos = null; + public List record = new ArrayList<>(); public boolean is_recording = false; - public Date last_record = null; + public long last_record = -1; + public TickTask move_tick = null; - public Thread replay = null; + public TickTask replay_tick = null; public boolean is_replaying = false; public boolean loop_replay = false; - public static boolean replay_sneaking = false; + public static RecordInputEvent input_replay = null; + + public long living_ticks = 0; public static RepeatingScreen menu; private static KeyBinding menu_key; private static KeyBinding toggle_replay_key; private static KeyBinding toggle_record_key; - public double record_blocks_limit = 2; - public long record_time_limit = 50; + public long record_pos_delay = 20; + + public static Random rand = new Random(); public EasyConfig conf; @@ -62,13 +75,56 @@ public class RepeatingMod implements ClientModInitializer { LOGGER.info("Repeating mod initialized"); me = this; - Map def = new HashMap<>(); - def.put("record_blocks_limit", record_blocks_limit); - def.put("record_time_limit", record_time_limit); - conf = new EasyConfig(new File(loader.getConfigDir().toFile(),"repeating-mod").toPath(),def); + RenderSystem.init(); + WorldRenderEvents.LAST.register(context -> { + WorldBuffer buffer = RenderHelper.startTri(context); + if (start_record_pos != null) { + RenderHelper.drawRectFromTri(buffer, + (float) start_record_pos.getX() - 0.25F, + (float) start_record_pos.getY() + 0.01F, + (float) start_record_pos.getZ() - 0.25F, - record_blocks_limit = (double) conf.data.get("record_blocks_limit"); - record_time_limit = (long) conf.data.get("record_time_limit"); + (float) start_record_pos.getX() + 0.25F, + (float) start_record_pos.getY() + 0.01F, + (float) start_record_pos.getZ() - 0.25F, + + (float) start_record_pos.getX() + 0.25F, + (float) start_record_pos.getY() + 0.01F, + (float) start_record_pos.getZ() + 0.25F, + + (float) start_record_pos.getX() - 0.25F, + (float) start_record_pos.getY() + 0.01F, + (float) start_record_pos.getZ() + 0.25F, + new Color(70,230,70,128)); + } + if (finish_record_pos != null) { + RenderHelper.drawRectFromTri(buffer, + (float) finish_record_pos.getX() - 0.25F, + (float) finish_record_pos.getY() + 0.01F, + (float) finish_record_pos.getZ() - 0.25F, + + (float) finish_record_pos.getX() + 0.25F, + (float) finish_record_pos.getY() + 0.01F, + (float) finish_record_pos.getZ() - 0.25F, + + (float) finish_record_pos.getX() + 0.25F, + (float) finish_record_pos.getY() + 0.01F, + (float) finish_record_pos.getZ() + 0.25F, + + (float) finish_record_pos.getX() - 0.25F, + (float) finish_record_pos.getY() + 0.01F, + (float) finish_record_pos.getZ() + 0.25F, + new Color(230,70,70,128)); + } + RenderHelper.endTri(buffer); + }); + + Map def = new HashMap<>(); + def.put("record_pos_delay", String.valueOf(record_pos_delay)); + + conf = new EasyConfig(loader.getConfigDir(),"repeating-mod",def); + + record_pos_delay = Long.parseLong(conf.data.get("record_pos_delay")); menu_key = KeyBindingHelper.registerKeyBinding(new KeyBinding( "key.repeating-mod.menu",InputUtil.Type.KEYSYM, @@ -82,9 +138,8 @@ public class RepeatingMod implements ClientModInitializer { menu = new RepeatingScreen(); ClientTickEvents.END_CLIENT_TICK.register(client -> { - if (menu_key.wasPressed()) { + if (menu_key.wasPressed()) client.setScreen(menu); - } if (toggle_replay_key.wasPressed()) { if (!is_recording) { if (is_replaying) @@ -102,6 +157,13 @@ public class RepeatingMod implements ClientModInitializer { } } }); + + new TickTask(0,0) { + @Override + public void run() { + living_ticks++; + } + }; } public RecordEvent getLastRecord(String t) { @@ -113,28 +175,131 @@ public class RepeatingMod implements ClientModInitializer { return null; } - public void startRecording() { is_recording = true; menu.update_btns(); record.clear(); + + finish_record_pos = null; + Vec3d v = client.player.getPos(); + record.add(new RecordMoveEvent(v,client.player.getHeadYaw(),client.player.getPitch())); + start_record_pos = v; + + if (record_pos_delay > 0) { + move_tick = new TickTask( + record_pos_delay, + record_pos_delay) { + @Override + public void run() { + record.add(new RecordMoveEvent(client.player.getPos(), + client.player.getHeadYaw(), client.player.getPitch())); + } + }; + } + 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)); + if (is_recording) { + long now = living_ticks; + if (last_record != -1) { + long diff = now - last_record - 2; + if (diff > 0) record.add(new RecordDelayEvent(diff)); + } + record.add(e); + last_record = now; + } + } + + public void recordAllInput() { + RecordInputEvent l = ((RecordInputEvent)getLastRecord("input")); + if (l == null) { + RecordInputEvent e = new RecordInputEvent( + client.player.input.sneaking, + client.player.input.jumping, + client.player.input.movementSideways, + client.player.input.movementForward, + client.player.input.pressingForward, + client.player.input.pressingBack, + client.player.input.pressingLeft, + client.player.input.pressingRight, + client.player.getHeadYaw(), + client.player.getBodyYaw(), + client.player.getPitch(), + client.player.isSprinting(), + client.player.getYaw(), + client.player.getMovementSpeed()); + recordTick(e); + } else { + RecordInputEvent e = new RecordInputEvent( + ((Boolean) client.player.input.sneaking == l.sneaking) ? null : client.player.input.sneaking, + ((Boolean) client.player.input.jumping == l.jumping) ? null : client.player.input.jumping, + (((Float) client.player.input.movementSideways).equals(l.movementSideways)) ? null : client.player.input.movementSideways, + (((Float) client.player.input.movementForward).equals(l.movementForward)) ? null : client.player.input.movementForward, + ((Boolean) client.player.input.pressingForward == l.pressingForward) ? null : client.player.input.pressingForward, + ((Boolean) client.player.input.pressingBack == l.pressingBack) ? null : client.player.input.pressingBack, + ((Boolean) client.player.input.pressingLeft == l.pressingLeft) ? null : client.player.input.pressingLeft, + ((Boolean) client.player.input.pressingRight == l.pressingRight) ? null : client.player.input.pressingRight, + client.player.getHeadYaw(),RepeatingMod.client.player.getBodyYaw(),client.player.getPitch(), + ((Boolean) client.player.isSprinting() == l.sprinting) ? null : client.player.isSprinting(), + client.player.getYaw(),client.player.getMovementSpeed()); + + if (!(e.isEmpty() && + e.yaw == l.yaw && + e.head_yaw == l.head_yaw && + e.pitch == l.pitch && + e.body_yaw == l.body_yaw)) { + e.fillEmpty(l); + recordTick(e); + } + } + } + + public void recordCameraInput() { + RecordInputEvent l = ((RecordInputEvent)getLastRecord("input")); + if (l == null) { + RecordInputEvent e = new RecordInputEvent( + client.player.input.sneaking, + client.player.input.jumping, + client.player.input.movementSideways, + client.player.input.movementForward, + client.player.input.pressingForward, + client.player.input.pressingBack, + client.player.input.pressingLeft, + client.player.input.pressingRight, + client.player.getHeadYaw(), + client.player.getBodyYaw(), + client.player.getPitch(), + client.player.isSprinting(), + client.player.getYaw(), + client.player.getMovementSpeed()); + recordTick(e); + } else { + RecordInputEvent e = new RecordInputEvent(null,null,null, + null,null,null,null,null, + client.player.getHeadYaw(),RepeatingMod.client.player.getBodyYaw(),client.player.getPitch(), + null,client.player.getYaw(),client.player.getMovementSpeed()); + + if (!(e.yaw == l.yaw && + e.head_yaw == l.head_yaw && + e.pitch == l.pitch && + e.body_yaw == l.body_yaw)) { + e.fillEmpty(l); + recordTick(e); + } } - record.add(e); - last_record = now; } public void stopRecording() { is_recording = false; + finish_record_pos = client.player.getPos(); + if (move_tick != null) { + move_tick.cancel(); + move_tick = null; + } menu.update_btns(); - last_record = null; + last_record = -1; sendMessage(Text.translatable("message.repeating-mod.record_stop")); } @@ -143,25 +308,42 @@ public class RepeatingMod implements ClientModInitializer { 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; + replay_tick = new TickTask(0,0, TickTask.TickAt.CLIENT_TAIL) { + public int replay_index = 0; + + @Override + public void run() { + if (!is_replaying) cancel(); + RecordEvent e = record.get(replay_index); + if (e instanceof RecordDelayEvent) { + setDelay(((RecordDelayEvent) e).delay); + } else { + e.callback(); + } + + replay_index++; + if (!loop_replay) { + if (replay_index == record.size()) { + stopReplay(); + cancel(); + } + } else if (replay_index == record.size()) { + replay_index = 0; + } } - stopReplay(); - }); - replay.start(); + }; + sendMessage(Text.translatable("message.repeating-mod.replay_start")); } public void stopReplay() { is_recording = false; is_replaying = false; + if (replay_tick != null) { + replay_tick.cancel(); + replay_tick = null; + } menu.update_btns(); - client.player.setNoGravity(false); sendMessage(Text.translatable("message.repeating-mod.replay_stop")); } @@ -171,10 +353,15 @@ public class RepeatingMod implements ClientModInitializer { return (double) Math.round(value * factor) / factor; } - public static void sendMessage(Text text) { + public static void sendMessage(MutableText text) { client.player.sendMessage(Text.literal("[") - .append(Text.translatable("text.repeating-mod.name")) - .append("] ").append(text)); + .append(Text.translatable("text.repeating-mod.name")) + .append("] ").formatted(Formatting.BOLD,Formatting.DARK_GRAY) + .append(text.formatted(Formatting.RESET).formatted(Formatting.GRAY))); + } + + public static void sendDebug(String s) { + client.player.sendMessage(Text.literal("[DEBUG] ").append(Text.of(s))); } public static abstract class RecordEvent { @@ -187,36 +374,15 @@ public class RepeatingMod implements ClientModInitializer { String type = String.valueOf(t.charAt(0)); String[] args = t.substring(2).split("&"); if (type.equals("d")) { - return new RecordDelayEvent( - Long.parseLong(args[0])); + return RecordDelayEvent.fromArgs(args); } 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")); + return RecordMoveEvent.fromArgs(args); + } else if (type.equals("p")) { + return RecordInputEvent.fromArgs(args); } else if (type.equals("b")) { - return new RecordBlockBreakEvent(new BlockPos( - Integer.parseInt(args[0]), - Integer.parseInt(args[1]), - Integer.parseInt(args[2]))); + return RecordBlockBreakEvent.fromArgs(args); } 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"))); + return RecordBlockInteractEvent.fromArgs(args); } } catch (Exception e) { e.printStackTrace(); @@ -228,13 +394,17 @@ public class RepeatingMod implements ClientModInitializer { public static class RecordDelayEvent extends RecordEvent { public long delay; + public static RecordDelayEvent fromArgs(String[] a) { + return new RecordDelayEvent(Long.parseLong(a[0])); + } + public RecordDelayEvent(long delay) { this.delay = delay; } public void callback() { try { - Thread.sleep(delay); + Thread.sleep(delay/20*1000); } catch (InterruptedException e) { e.printStackTrace(); } @@ -253,6 +423,15 @@ public class RepeatingMod implements ClientModInitializer { public float yaw; public float pitch; + public static RecordMoveEvent fromArgs(String[] a) { + return new RecordMoveEvent(new Vec3d( + Double.parseDouble(a[0]), + Double.parseDouble(a[1]), + Double.parseDouble(a[2])), + Float.parseFloat(a[3]), + Float.parseFloat(a[4])); + } + public RecordMoveEvent(Vec3d vec,float yaw,float pitch) { this.vec = vec; this.yaw = yaw; @@ -275,28 +454,160 @@ public class RepeatingMod implements ClientModInitializer { } } - public static class RecordSneakEvent extends RecordEvent { - public boolean sneaking; + public static class RecordInputEvent extends RecordEvent { + public Boolean sneaking; + public Boolean jumping; + public Boolean pressingForward; + public Boolean pressingBack; + public Boolean pressingLeft; + public Boolean pressingRight; + public Boolean sprinting; - public RecordSneakEvent(boolean sneaking) { + public Float movementSideways; + public Float movementForward; + + public float yaw; + public float head_yaw; + public float body_yaw; + public float pitch; + public float speed; + + public static RecordInputEvent fromArgs(String[] a) { + return new RecordInputEvent( + (a[0].equals("n")?null:a[0].equals("1")), + (a[1].equals("n")?null:a[1].equals("1")), + (a[2].equals("n")?null:Float.parseFloat(a[2])), + (a[3].equals("n")?null:Float.parseFloat(a[3])), + (a[4].equals("n")?null:a[4].equals("1")), + (a[5].equals("n")?null:a[5].equals("1")), + (a[6].equals("n")?null:a[6].equals("1")), + (a[7].equals("n")?null:a[7].equals("1")), + Float.parseFloat(a[8]),Float.parseFloat(a[9]), + Float.parseFloat(a[10]), + (a[11].equals("n")?null:a[11].equals("1")), + Float.parseFloat(a[12]), + Float.parseFloat(a[13])); + } + + public RecordInputEvent(Boolean sneaking, + Boolean jumping, + Float movementSideways, + Float movementForward, + Boolean pressingForward, + Boolean pressingBack, + Boolean pressingLeft, + Boolean pressingRight, + float head_yaw, + float body_yaw, + float head_pitch, + Boolean sprinting, + float yaw, + float speed) { this.sneaking = sneaking; + this.jumping = jumping; + this.movementSideways = movementSideways; + this.movementForward = movementForward; + this.pressingForward = pressingForward; + this.pressingBack = pressingBack; + this.pressingLeft = pressingLeft; + this.pressingRight = pressingRight; + this.head_yaw = head_yaw; + this.body_yaw = body_yaw; + this.pitch = head_pitch; + this.sprinting = sprinting; + this.yaw = yaw; + this.speed = speed; + } + + public void fillEmpty(RecordInputEvent e) { + if (sneaking == null) sneaking = e.sneaking; + if (jumping == null) jumping = e.jumping; + if (movementSideways == null) movementSideways = e.movementSideways; + if (movementForward == null) movementForward = e.movementForward; + if (pressingForward == null) pressingForward = e.pressingForward; + if (pressingBack == null) pressingBack = e.pressingBack; + if (pressingLeft == null) pressingLeft = e.pressingLeft; + if (pressingRight == null) pressingRight = e.pressingRight; + if (sprinting == null) sprinting = e.sprinting; + } + + public boolean isEmpty() { + return sneaking == null && + jumping == null && + movementSideways == null && + movementForward == null && + pressingForward == null && + pressingBack == null && + pressingLeft == null && + pressingRight == null && + sprinting == null; } public void callback() { - RepeatingMod.replay_sneaking = sneaking; + input_replay = this; + } + + public void inputCallback() { + if (sprinting != null && client.player.isSprinting() != sprinting) + client.player.setSprinting(sprinting); + if (client.player.getYaw() != yaw) + client.player.setYaw(yaw); + if (client.player.getHeadYaw() != head_yaw) + client.player.setHeadYaw(head_yaw); + if (client.player.getBodyYaw() != body_yaw) + client.player.setBodyYaw(body_yaw); + if (client.player.getPitch() != pitch) + client.player.setPitch(pitch); + if (client.player.getMovementSpeed() != speed) + client.player.setMovementSpeed(speed); + if (sneaking != null && client.player.input.sneaking != sneaking) + client.player.input.sneaking = sneaking; + if (jumping != null && client.player.input.jumping != jumping) + client.player.input.jumping = jumping; + if (movementSideways != null && client.player.input.movementSideways != movementSideways) + client.player.input.movementSideways = movementSideways; + if (movementForward != null && client.player.input.movementForward != movementForward) + client.player.input.movementForward = movementForward; + if (pressingForward != null && client.player.input.pressingForward != pressingForward) + client.player.input.pressingForward = pressingForward; + if (pressingBack != null && client.player.input.pressingBack != pressingBack) + client.player.input.pressingBack = pressingBack; + if (pressingLeft != null && client.player.input.pressingLeft != pressingLeft) + client.player.input.pressingLeft = pressingLeft; + if (pressingRight != null && client.player.input.pressingRight != pressingRight) + client.player.input.pressingRight = pressingRight; } public String toText() { - return "s="+(sneaking?"1":"0"); + return "p="+ + ((sneaking==null)?"n":(sneaking?"1":"0"))+"&"+ + ((jumping==null)?"n":(jumping?"1":"0"))+"&"+ + ((movementSideways==null)?"n":movementSideways)+"&"+ + ((movementForward==null)?"n":movementForward)+"&"+ + ((pressingForward==null)?"n":(pressingForward?"1":"0"))+"&"+ + ((pressingBack==null)?"n":(pressingBack?"1":"0"))+"&"+ + ((pressingLeft==null)?"n":(pressingLeft?"1":"0"))+"&"+ + ((pressingRight==null)?"n":(pressingRight?"1":"0"))+"&"+ + head_yaw+"&"+body_yaw+"&"+ pitch +"&"+ + ((sprinting==null)?"n":(sprinting?"1":"0")+ + "&"+yaw+"&"+speed); } + public String getType() { - return "sneak"; + return "input"; } } public static class RecordBlockBreakEvent extends RecordEvent { public BlockPos pos; + public static RecordBlockBreakEvent fromArgs(String[] a) { + return new RecordBlockBreakEvent(new BlockPos( + Integer.parseInt(a[0]), + Integer.parseInt(a[1]), + Integer.parseInt(a[2]))); + } + public RecordBlockBreakEvent( BlockPos pos) { this.pos = pos; @@ -318,6 +629,21 @@ public class RepeatingMod implements ClientModInitializer { public Hand hand; public BlockHitResult hitResult; + public static RecordBlockInteractEvent fromArgs(String[] a) { + return new RecordBlockInteractEvent( + Hand.valueOf(a[5]), + new BlockHitResult(new Vec3d( + Double.parseDouble(a[0]), + Double.parseDouble(a[1]), + Double.parseDouble(a[2])), + Direction.byId(Integer.parseInt(a[4])), + new BlockPos( + Integer.parseInt(a[0]), + Integer.parseInt(a[1]), + Integer.parseInt(a[2])), + a[3].equals("1"))); + } + public RecordBlockInteractEvent(Hand hand, BlockHitResult hitResult) { this.hand = hand; this.hitResult = hitResult; diff --git a/src/main/java/themixray/repeating/mod/RepeatingScreen.java b/src/main/java/themixray/repeating/mod/RepeatingScreen.java new file mode 100644 index 0000000..dba6192 --- /dev/null +++ b/src/main/java/themixray/repeating/mod/RepeatingScreen.java @@ -0,0 +1,371 @@ +package themixray.repeating.mod; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.fabricmc.loader.api.FabricLoader; +import net.minecraft.client.gui.DrawContext; +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.OptionSliderWidget; +import net.minecraft.client.gui.widget.SliderWidget; +import net.minecraft.text.Text; +import net.minecraft.util.math.Vec3d; + +import java.awt.*; +import java.io.File; +import java.nio.file.Files; +import java.text.SimpleDateFormat; +import java.util.Arrays; +import java.util.Date; +import java.util.List; + +@Environment(EnvType.CLIENT) +public class RepeatingScreen extends Screen { + protected RepeatingScreen() { + super(Text.literal("")); + this.mod = RepeatingMod.me; + } + + public RepeatingMod mod; + + public ButtonWidget record_btn; + public ButtonWidget replay_btn; + public ButtonWidget loop_btn; + + public ButtonWidget export_btn; + public ButtonWidget import_btn; + + public SliderWidget pos_delay_slider; + + public boolean was_build = false; + + public void update_btns() { + if (was_build) { + replay_btn.setMessage(Text.translatable("text.repeating-mod." + + ((mod.is_replaying) ? "stop_replay" : "start_replay"))); + record_btn.setMessage(Text.translatable("text.repeating-mod." + + ((mod.is_recording) ? "stop_record" : "start_record"))); + loop_btn.setMessage(Text.of(((mod.loop_replay) ? "\uefff " : "\ueffe "))); + } + } + + @Override + public void render(DrawContext context, int mouseX, int mouseY, float delta) { + renderBackgroundTexture(context); +// context.drawCenteredTextWithShadow(textRenderer, +// Text.literal("You must see me"), +// width / 2, height / 2, +// Color.white.getRGB()); + super.render(context, mouseX, mouseY, delta); + } + + @Override + protected void init() { + record_btn = ButtonWidget.builder( + Text.translatable("text.repeating-mod.start_record"), button -> { + if (!mod.is_replaying) { + if (mod.is_recording) + mod.stopRecording(); + else mod.startRecording(); + update_btns(); + } + }) + .dimensions(width / 2 - 60, height / 2 - 54, 120, 20) + .tooltip(Tooltip.of(Text.translatable("text.repeating-mod.record_tooltip"))) + .build(); + + replay_btn = ButtonWidget.builder( + Text.translatable("text.repeating-mod.start_replay"), button -> { + if (!mod.is_recording) { + if (mod.is_replaying) + mod.stopReplay(); + else mod.startReplay(); + update_btns(); + } + }) + .dimensions(width / 2 - 60, height / 2 - 32, 98, 20) + .tooltip(Tooltip.of(Text.translatable("text.repeating-mod.replay_tooltip"))) + .build(); + + loop_btn = ButtonWidget.builder(Text.of(""), button -> { + mod.loop_replay = !mod.loop_replay; + update_btns(); + }) + .dimensions(width / 2 + 40, height / 2 - 32, 20, 20) + .tooltip(Tooltip.of(Text.translatable("text.repeating-mod.loop_tooltip"))) + .build(); + + export_btn = ButtonWidget.builder( + Text.translatable("text.repeating-mod.export"), button -> { + if (mod.finish_record_pos == null) return; + StringBuilder t = new StringBuilder(); + for (int i = 0; i < mod.record.size(); i++) { + t.append(mod.record.get(i).toText()); + t.append("\n"); + } + t.append(mod.start_record_pos.getX()+"n"+ + mod.start_record_pos.getY()+"n"+ + mod.start_record_pos.getZ()+"x"+ + mod.finish_record_pos.getX()+"n"+ + mod.finish_record_pos.getY()+"n"+ + mod.finish_record_pos.getZ()); + + File p = new File(FabricLoader.getInstance().getGameDir().toFile(),"repeating"); + if (!p.exists()) p.mkdir(); + File file = new File(p,"export_"+ + new SimpleDateFormat("MM_dd_yyyy").format(new Date()) + +"_"+RepeatingMod.rand.nextInt(10)+".txt"); + try { + if (!file.exists()) file.createNewFile(); + Files.write(file.toPath(), t.toString().getBytes()); + Runtime.getRuntime().exec("explorer /select,\""+file.getAbsolutePath()+"\""); + } catch (Exception e) { + e.printStackTrace(); + } + }) + .dimensions(width / 2 - 60, height / 2 - 10, 120, 20) + .tooltip(Tooltip.of(Text.translatable("text.repeating-mod.export_tooltip"))) + .build(); + + import_btn = ButtonWidget.builder( + Text.translatable("text.repeating-mod.import"), button -> { + 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()); + List ss = List.of(t.split("\n")); + String ls = ss.get(ss.size()-1); + ss = ss.subList(0,ss.size()-1); + for (String s:ss) + mod.record.add(RepeatingMod.RecordEvent.fromText(s)); + String[] lss0 = ls.split("x"); + String[] lss1 = lss0[0].split("n"); + String[] lss2 = lss0[1].split("n"); + mod.start_record_pos = new Vec3d( + Float.parseFloat(lss1[0]), + Float.parseFloat(lss1[1]), + Float.parseFloat(lss1[2])); + mod.finish_record_pos = new Vec3d( + Float.parseFloat(lss2[0]), + Float.parseFloat(lss2[1]), + Float.parseFloat(lss2[2])); + } catch (Exception e) { + e.printStackTrace(); + } + }) + .dimensions(width / 2 - 60, height / 2 + 12, 120, 20) + .tooltip(Tooltip.of(Text.translatable("text.repeating-mod.import_tooltip"))) + .build(); + + pos_delay_slider = new SliderWidget( + width / 2 - 60, height / 2 + 34, 120, 20, + (mod.record_pos_delay < 0) ? Text.translatable("text.repeating-mod.nan_pos_delay") : + Text.translatable("text.repeating-mod.pos_delay", String.valueOf(mod.record_pos_delay)), + (mod.record_pos_delay/10d+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*10)))); + } + + @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*10)))); + mod.record_pos_delay = (long) (v*10); + mod.conf.data.put("record_pos_delay",String.valueOf(mod.record_pos_delay)); + mod.conf.save(); + } + + @Override + public void onRelease(double mouseX, double mouseY) { + super.onRelease(mouseX, mouseY); + applyValue(); + } + + @Override + protected void onDrag(double mouseX, double mouseY, double deltaX, double deltaY) { + super.onDrag(mouseX, mouseY, deltaX, deltaY); + applyValue(); + } + + @Override + public void render(DrawContext context, int mouseX, int mouseY, float delta) { + super.render(context, mouseX, mouseY, delta); + updateMessage(); + } + }; + pos_delay_slider.setTooltip(Tooltip.of(Text.translatable("text.repeating-mod.pos_delay_tooltip"))); + + was_build = true; + + update_btns(); + + addDrawableChild(replay_btn); + addDrawableChild(loop_btn); + addDrawableChild(record_btn); + addDrawableChild(export_btn); + addDrawableChild(import_btn); + addDrawableChild(pos_delay_slider); + + +// rootComponent +// .surface(Surface.VANILLA_TRANSLUCENT) +// .horizontalAlignment(HorizontalAlignment.CENTER) +// .verticalAlignment(VerticalAlignment.CENTER); +// +// 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)); +// was_build = true; +// +// rootComponent.child( +// Containers.horizontalFlow(Sizing.content(), Sizing.content()).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 += "\n"; +// } +// +// 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("\n")) +// 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),-20,100) +// .setFromDiscreteValue(mod.record_pos_delay) +// .message((String s)->{ +// mod.record_pos_delay = Long.parseLong(s); +// mod.conf.data.put("record_pos_delay",String.valueOf(mod.record_pos_delay)); +// mod.conf.save(); +// if (mod.record_pos_delay > -1) +// return Text.translatable("text.repeating-mod.pos_delay", s); +// return Text.translatable("text.repeating-mod.nan_pos_delay"); +// }).scrollStep(25) +// .margins(Insets.of(1)) +// .tooltip(Text.translatable("text.repeating-mod.pos_delay_text"))) +// .padding(Insets.of(10)) +// .surface(Surface.DARK_PANEL) +// .verticalAlignment(VerticalAlignment.CENTER) +// .horizontalAlignment(HorizontalAlignment.CENTER) +// .margins(Insets.of(1))) +// )); + update_btns(); + } +} diff --git a/src/main/java/themixray/repeating/mod/TickTask.java b/src/main/java/themixray/repeating/mod/TickTask.java new file mode 100644 index 0000000..f3beed1 --- /dev/null +++ b/src/main/java/themixray/repeating/mod/TickTask.java @@ -0,0 +1,94 @@ +package themixray.repeating.mod; + +import java.util.ArrayList; +import java.util.List; + +public abstract class TickTask implements Runnable { + public static List tasks = new ArrayList<>(); + + public static void tickTasks(TickAt at) { + for (TickTask t:new ArrayList<>(tasks)) + if (t.getAt() == at) t.tick(); + } + + private long living; + private long delay; + + private boolean is_repeating; + private long period; + + private boolean is_cancelled; + private TickAt at; + + public enum TickAt { + CLIENT_HEAD, CLIENT_TAIL, + MOVEMENT_HEAD, MOVEMENT_TAIL, + RENDER_HEAD, RENDER_TAIL + } + + public TickTask(long delay, TickAt at) { + this.is_cancelled = false; + this.is_repeating = false; + this.delay = delay; + this.living = 0; + this.period = 0; + this.at = at; + tasks.add(this); + } + + public TickTask(long delay, long period, TickAt at) { + this.is_cancelled = false; + this.is_repeating = true; + this.delay = delay; + this.period = period; + this.living = 0; + this.at = at; + tasks.add(this); + } + + public TickTask(long delay) { + this(delay,TickAt.CLIENT_HEAD); + } + + public TickTask(long delay, long period) { + this(delay,period,TickAt.CLIENT_HEAD); + } + + public void cancel() { + if (!is_cancelled) { + is_cancelled = true; + tasks.remove(this); + } + } + + public boolean isCancelled() { + return is_cancelled; + } + + public TickAt getAt() { + return at; + } + + public void setDelay(long delay) { + if (is_repeating) { + this.delay = delay; + } + } + public long getDelay() { + return this.delay; + } + + public void tick() { + if (living >= delay) { + if (is_repeating) { + delay = period; + run(); + living = -1; + } else { + run(); + cancel(); + } + } + living++; + } +} diff --git a/src/main/java/themixray/repeating/mod/mixin/ClientMixin.java b/src/main/java/themixray/repeating/mod/mixin/ClientMixin.java new file mode 100644 index 0000000..1d077ac --- /dev/null +++ b/src/main/java/themixray/repeating/mod/mixin/ClientMixin.java @@ -0,0 +1,24 @@ +package themixray.repeating.mod.mixin; + +import net.minecraft.client.MinecraftClient; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import themixray.repeating.mod.RepeatingMod; +import themixray.repeating.mod.TickTask; + +@Mixin(MinecraftClient.class) +public abstract class ClientMixin { + @Inject(at = @At(value = "HEAD"), method = "tick") + private void onTickHead(CallbackInfo ci) { + if (RepeatingMod.me.is_recording) + RepeatingMod.me.recordAllInput(); + TickTask.tickTasks(TickTask.TickAt.CLIENT_HEAD); + } + + @Inject(at = @At(value = "TAIL"), method = "tick") + private void onTickTail(CallbackInfo ci) { + TickTask.tickTasks(TickTask.TickAt.CLIENT_TAIL); + } +} diff --git a/src/main/java/themixray/repeating/mod/mixin/EntityMixin.java b/src/main/java/themixray/repeating/mod/mixin/EntityMixin.java new file mode 100644 index 0000000..94cfeb7 --- /dev/null +++ b/src/main/java/themixray/repeating/mod/mixin/EntityMixin.java @@ -0,0 +1,31 @@ +package themixray.repeating.mod.mixin; + +import net.minecraft.entity.Entity; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import themixray.repeating.mod.RepeatingMod; +import themixray.repeating.mod.TickTask; + +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 (getUuid().equals(RepeatingMod.client.player.getUuid())) { + if (RepeatingMod.me.is_replaying) { + if (RepeatingMod.input_replay != null && + RepeatingMod.input_replay.sprinting != null && + RepeatingMod.input_replay.sprinting != sprinting) { + ci.cancel(); + return; + } + } + } + } +} diff --git a/src/main/java/themixray/repeating/mod/mixin/InputMixin.java b/src/main/java/themixray/repeating/mod/mixin/InputMixin.java new file mode 100644 index 0000000..bcaf1e0 --- /dev/null +++ b/src/main/java/themixray/repeating/mod/mixin/InputMixin.java @@ -0,0 +1,20 @@ +package themixray.repeating.mod.mixin; + +import net.minecraft.client.input.KeyboardInput; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import themixray.repeating.mod.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) { + if (RepeatingMod.input_replay != null) { + RepeatingMod.input_replay.inputCallback(); + } + } + } +} diff --git a/src/main/java/themixray/repeating/mod/mixin/MovementMixin.java b/src/main/java/themixray/repeating/mod/mixin/MovementMixin.java index 83a18e5..594cbd0 100644 --- a/src/main/java/themixray/repeating/mod/mixin/MovementMixin.java +++ b/src/main/java/themixray/repeating/mod/mixin/MovementMixin.java @@ -2,31 +2,20 @@ 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 themixray.repeating.mod.TickTask; -import java.util.Date; +import java.util.ArrayList; @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) { @@ -43,50 +32,13 @@ public abstract class MovementMixin { }); } - @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(); + @Inject(at = @At(value = "HEAD"), method = "tickMovement") + private void onMoveHead(CallbackInfo ci) { + TickTask.tickTasks(TickTask.TickAt.MOVEMENT_HEAD); + } - 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; - } + @Inject(at = @At(value = "TAIL"), method = "tick") + private void onMoveTail(CallbackInfo ci) { + TickTask.tickTasks(TickTask.TickAt.MOVEMENT_TAIL); } } diff --git a/src/main/java/themixray/repeating/mod/mixin/RendererMixin.java b/src/main/java/themixray/repeating/mod/mixin/RendererMixin.java new file mode 100644 index 0000000..fcaca78 --- /dev/null +++ b/src/main/java/themixray/repeating/mod/mixin/RendererMixin.java @@ -0,0 +1,23 @@ +package themixray.repeating.mod.mixin; + +import net.minecraft.client.input.KeyboardInput; +import net.minecraft.client.render.*; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import themixray.repeating.mod.RepeatingMod; +import themixray.repeating.mod.TickTask; + +@Mixin(GameRenderer.class) +public abstract class RendererMixin { + @Inject(at = @At(value = "HEAD"), method = "tick") + private void onTickHead(CallbackInfo ci) { + TickTask.tickTasks(TickTask.TickAt.RENDER_HEAD); + } + + @Inject(at = @At(value = "TAIL"), method = "tick") + private void onTickTail(CallbackInfo ci) { + TickTask.tickTasks(TickTask.TickAt.RENDER_TAIL); + } +} diff --git a/src/main/java/themixray/repeating/mod/render/RenderHelper.java b/src/main/java/themixray/repeating/mod/render/RenderHelper.java new file mode 100644 index 0000000..edbe614 --- /dev/null +++ b/src/main/java/themixray/repeating/mod/render/RenderHelper.java @@ -0,0 +1,200 @@ +package themixray.repeating.mod.render; + +import lombok.experimental.UtilityClass; +import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderContext; +import net.minecraft.util.math.Vec3d; +import themixray.repeating.mod.render.buffer.WorldBuffer; +import themixray.repeating.mod.render.shader.ShaderManager; + +import java.awt.*; + +import static org.lwjgl.opengl.GL33.*; + +@UtilityClass +public class RenderHelper { + public WorldBuffer startLines(WorldRenderContext context) { + glEnable(GL_LINE_SMOOTH); + return new WorldBuffer(GL_LINES, ShaderManager.getPositionColorShader(), context); + } + + public void endLines(WorldBuffer buffer) { + glEnable(GL_BLEND); + glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA); + glDepthMask(false); + buffer.draw(); + glDepthMask(true); + glDisable(GL_BLEND); + } + + public void drawLine(WorldBuffer buffer, float x1, float y1, float z1, float x2, float y2, float z2, Color color) { + buffer.vert(x1, y1, z1, color.getRed() / 255f, color.getGreen() / 255f, color.getBlue() / 255f, color.getAlpha() / 255f); + buffer.vert(x2, y2, z2, color.getRed() / 255f, color.getGreen() / 255f, color.getBlue() / 255f, color.getAlpha() / 255f); + } + + public WorldBuffer startTri(WorldRenderContext context) { + return new WorldBuffer(GL_TRIANGLES, ShaderManager.getPositionColorShader(), context); + } + + public void endTri(WorldBuffer buffer) { + //glDepthRange(0, 0.7); + glEnable(GL_BLEND); + glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA); + glDisable(GL_CULL_FACE); + glDepthMask(false); + buffer.draw(); + glDepthMask(true); + glEnable(GL_CULL_FACE); + glDisable(GL_BLEND); + glDepthRange(0, 1); + } + + public void drawTri(WorldBuffer buffer, float x1, float y1, float z1, float x2, float y2, float z2, float x3, float y3, float z3, Color color) { + buffer.vert(x1, y1, z1, color.getRed() / 255f, color.getGreen() / 255f, color.getBlue() / 255f, color.getAlpha() / 255f); + buffer.vert(x2, y2, z2, color.getRed() / 255f, color.getGreen() / 255f, color.getBlue() / 255f, color.getAlpha() / 255f); + buffer.vert(x3, y3, z3, color.getRed() / 255f, color.getGreen() / 255f, color.getBlue() / 255f, color.getAlpha() / 255f); + } + + public void drawRectFromTri(WorldBuffer buffer, + float x1, float y1, float z1, + float x2, float y2, float z2, + float x3, float y3, float z3, + float x4, float y4, float z4, + Color color) { + drawTri(buffer, + x1, y1, z1, + x2, y2, z2, + x3, y3, z3, + color); + drawTri(buffer, + x3, y3, z3, + x4, y4, z4, + x1, y1, z1, + color); + } + + public void drawRectFromLines(WorldBuffer buffer, + float x1, float y1, float z1, + float x2, float y2, float z2, + float x3, float y3, float z3, + float x4, float y4, float z4, + Color color) { + drawLine(buffer, + x1, y1, z1, + x2, y2, z2, + color); + drawLine(buffer, + x2, y2, z2, + x3, y3, z3, + color); + drawLine(buffer, + x3, y3, z3, + x4, y4, z4, + color); + drawLine(buffer, + x4, y4, z4, + x1, y1, z1, + color); + } + + public void drawBoxFromTri(WorldBuffer buffer, + float x1, float y1, float z1, + float x2, float y2, float z2, + Color color) { + float[][] v = new float[][]{ + new float[]{Math.min(x1, x2), Math.min(y1, y2), Math.min(z1, z2)}, + new float[]{Math.max(x1, x2), Math.max(y1, y2), Math.max(z1, z2)}}; + + drawRectFromTri(buffer, + v[0][0], v[0][1], v[0][2], + v[1][0], v[0][1], v[0][2], + v[1][0], v[1][1], v[0][2], + v[0][0], v[1][1], v[0][2], + color); + + drawRectFromTri(buffer, + v[0][0], v[0][1], v[0][2], + v[1][0], v[0][1], v[0][2], + v[1][0], v[0][1], v[1][2], + v[0][0], v[0][1], v[1][2], + color); + + drawRectFromTri(buffer, + v[0][0], v[0][1], v[0][2], + v[0][0], v[0][1], v[1][2], + v[0][0], v[1][1], v[1][2], + v[0][0], v[1][1], v[0][2], + color); + + drawRectFromTri(buffer, + v[0][0], v[0][1], v[1][2], + v[1][0], v[0][1], v[1][2], + v[1][0], v[1][1], v[1][2], + v[0][0], v[1][1], v[1][2], + color); + + drawRectFromTri(buffer, + v[0][0], v[1][1], v[0][2], + v[1][0], v[1][1], v[0][2], + v[1][0], v[1][1], v[1][2], + v[0][0], v[1][1], v[1][2], + color); + + drawRectFromTri(buffer, + v[1][0], v[0][1], v[0][2], + v[1][0], v[0][1], v[1][2], + v[1][0], v[1][1], v[1][2], + v[1][0], v[1][1], v[0][2], + color); + } + + public void drawBoxFromLines(WorldBuffer buffer, + float x1, float y1, float z1, + float x2, float y2, float z2, + Color color) { + float[][] v = new float[][]{ + new float[]{Math.min(x1, x2), Math.min(y1, y2), Math.min(z1, z2)}, + new float[]{Math.max(x1, x2), Math.max(y1, y2), Math.max(z1, z2)}}; + + drawRectFromLines(buffer, + v[0][0], v[0][1], v[0][2], + v[1][0], v[0][1], v[0][2], + v[1][0], v[1][1], v[0][2], + v[0][0], v[1][1], v[0][2], + color); + + drawRectFromLines(buffer, + v[0][0], v[0][1], v[0][2], + v[1][0], v[0][1], v[0][2], + v[1][0], v[0][1], v[1][2], + v[0][0], v[0][1], v[1][2], + color); + + drawRectFromLines(buffer, + v[0][0], v[0][1], v[0][2], + v[0][0], v[0][1], v[1][2], + v[0][0], v[1][1], v[1][2], + v[0][0], v[1][1], v[0][2], + color); + + drawRectFromLines(buffer, + v[0][0], v[0][1], v[1][2], + v[1][0], v[0][1], v[1][2], + v[1][0], v[1][1], v[1][2], + v[0][0], v[1][1], v[1][2], + color); + + drawRectFromLines(buffer, + v[0][0], v[1][1], v[0][2], + v[1][0], v[1][1], v[0][2], + v[1][0], v[1][1], v[1][2], + v[0][0], v[1][1], v[1][2], + color); + + drawRectFromLines(buffer, + v[1][0], v[0][1], v[0][2], + v[1][0], v[0][1], v[1][2], + v[1][0], v[1][1], v[1][2], + v[1][0], v[1][1], v[0][2], + color); + } +} diff --git a/src/main/java/themixray/repeating/mod/render/RenderSystem.java b/src/main/java/themixray/repeating/mod/render/RenderSystem.java new file mode 100644 index 0000000..2a095f7 --- /dev/null +++ b/src/main/java/themixray/repeating/mod/render/RenderSystem.java @@ -0,0 +1,13 @@ +package themixray.repeating.mod.render; + +import lombok.experimental.UtilityClass; +import themixray.repeating.mod.render.buffer.BufferManager; +import themixray.repeating.mod.render.shader.ShaderManager; + +@UtilityClass +public class RenderSystem { + public void init() { + BufferManager.init(); + ShaderManager.init(); + } +} diff --git a/src/main/java/themixray/repeating/mod/render/buffer/BufferManager.java b/src/main/java/themixray/repeating/mod/render/buffer/BufferManager.java new file mode 100644 index 0000000..4c3ae98 --- /dev/null +++ b/src/main/java/themixray/repeating/mod/render/buffer/BufferManager.java @@ -0,0 +1,48 @@ +package themixray.repeating.mod.render.buffer; + +import lombok.experimental.UtilityClass; +import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents; + +import java.nio.FloatBuffer; + +import static org.lwjgl.opengl.GL33.*; + +@UtilityClass +public class BufferManager { + private int vao; + private int vbo; + + private int prevVao; + + public void init() { + ClientLifecycleEvents.CLIENT_STARTED.register(client -> { + vao = glGenVertexArrays(); + vbo = glGenBuffers(); + }); + } + + public void bindBuffer() { + glBindBuffer(GL_ARRAY_BUFFER, vbo); + } + + public void unbindBuffer() { + glBindBuffer(GL_ARRAY_BUFFER, 0); + } + + public void writeBuffer(FloatBuffer buffer) { + glBufferData(GL_ARRAY_BUFFER, buffer, GL_STATIC_DRAW); + } + + public void draw(int drawMode, int verts) { + glDrawArrays(drawMode, 0, verts); + } + + public void bind() { + prevVao = glGetInteger(GL_VERTEX_ARRAY_BINDING); + glBindVertexArray(vao); + } + + public void unbind() { + glBindVertexArray(prevVao); + } +} diff --git a/src/main/java/themixray/repeating/mod/render/buffer/Vertex.java b/src/main/java/themixray/repeating/mod/render/buffer/Vertex.java new file mode 100644 index 0000000..5aaa113 --- /dev/null +++ b/src/main/java/themixray/repeating/mod/render/buffer/Vertex.java @@ -0,0 +1,30 @@ +package themixray.repeating.mod.render.buffer; + +import lombok.Getter; + +public class Vertex { + @Getter + private float x; + @Getter + private float y; + @Getter + private float z; + @Getter + private float r; + @Getter + private float g; + @Getter + private float b; + @Getter + private float a; + + public Vertex(float x, float y, float z, float r, float g, float b, float a) { + this.x = x; + this.y = y; + this.z = z; + this.r = r; + this.g = g; + this.b = b; + this.a = a; + } +} \ No newline at end of file diff --git a/src/main/java/themixray/repeating/mod/render/buffer/WorldBuffer.java b/src/main/java/themixray/repeating/mod/render/buffer/WorldBuffer.java new file mode 100644 index 0000000..390f97d --- /dev/null +++ b/src/main/java/themixray/repeating/mod/render/buffer/WorldBuffer.java @@ -0,0 +1,82 @@ +package themixray.repeating.mod.render.buffer; + +import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderContext; +import net.minecraft.util.math.Vec3d; +import org.apache.commons.lang3.ArrayUtils; +import org.joml.Matrix4f; +import org.lwjgl.BufferUtils; +import themixray.repeating.mod.render.shader.Shader; + +import java.nio.FloatBuffer; +import java.util.ArrayList; +import java.util.List; + +import static org.lwjgl.opengl.GL33.*; + +public class WorldBuffer { + private final List vertices = new ArrayList<>(); + private final int drawMode; + private final Shader shader; + private FloatBuffer projectionMatrix; + private final Vec3d cameraPos; + + public WorldBuffer(int drawMode, Shader shader, WorldRenderContext worldRenderContext) { + this.drawMode = drawMode; + this.shader = shader; + this.cameraPos = worldRenderContext.camera().getPos(); + makeProjectionMatrix(worldRenderContext.projectionMatrix(), worldRenderContext.matrixStack().peek().getPositionMatrix()); + } + + public void vert(float x, float y, float z, float r, float g, float b, float a) { + vertices.add(new Vertex(x - (float) cameraPos.x, y - (float) cameraPos.y, z - (float) cameraPos.z, r, g, b, a)); + } + + public void draw() { + BufferManager.bind(); + BufferManager.bindBuffer(); + + BufferManager.writeBuffer(getBuffer()); + applyProjectionMatrix(); + + glVertexAttribPointer(0, 3, GL_FLOAT, false, 0, 0); + glEnableVertexAttribArray(0); + glVertexAttribPointer(1, 4, GL_FLOAT, false, 0, vertices.size() * 3 * 4L); + glEnableVertexAttribArray(1); + + BufferManager.unbindBuffer(); + + shader.bind(); + BufferManager.draw(drawMode, this.vertices.size()); + shader.unbind(); + + BufferManager.unbind(); + } + + private FloatBuffer getBuffer() { + FloatBuffer floatBuffer = BufferUtils.createFloatBuffer(vertices.size() * 7); + ArrayList floats = new ArrayList<>(); + for (Vertex vertex : vertices) { + floats.add(vertex.getX()); + floats.add(vertex.getY()); + floats.add(vertex.getZ()); + } + for (Vertex vertex : vertices) { + floats.add(vertex.getR()); + floats.add(vertex.getG()); + floats.add(vertex.getB()); + floats.add(vertex.getA()); + } + Float[] floatArray = new Float[floats.size()]; + floats.toArray(floatArray); + floatBuffer.put(ArrayUtils.toPrimitive(floatArray)); + return floatBuffer.flip(); + } + + private void makeProjectionMatrix(Matrix4f projectionMatrix, Matrix4f viewModelMatrix) { + this.projectionMatrix = projectionMatrix.mul(viewModelMatrix).get(BufferUtils.createFloatBuffer(16)); + } + + private void applyProjectionMatrix() { + shader.uniformMatrix4f("u_projection", projectionMatrix); + } +} diff --git a/src/main/java/themixray/repeating/mod/render/shader/Shader.java b/src/main/java/themixray/repeating/mod/render/shader/Shader.java new file mode 100644 index 0000000..e11dd9a --- /dev/null +++ b/src/main/java/themixray/repeating/mod/render/shader/Shader.java @@ -0,0 +1,42 @@ +package themixray.repeating.mod.render.shader; + +import lombok.Getter; + +import java.nio.FloatBuffer; + +import static org.lwjgl.opengl.GL33.*; + +public class Shader { + @Getter + private final int id; + + + public Shader(String name) { + int v = ShaderManager.loadShaderProgram(name, ShaderManager.ShaderType.VERTEX); + int f = ShaderManager.loadShaderProgram(name, ShaderManager.ShaderType.FRAGMENT); + this.id = glCreateProgram(); + glAttachShader(id, v); + glAttachShader(id, f); + glLinkProgram(id); + } + + public void bind() { + glUseProgram(id); + } + + public void unbind() { + glUseProgram(0); + } + + public void uniformMatrix4f(String name, FloatBuffer matrix) { + bind(); + glUniformMatrix4fv(glGetUniformLocation(id, name), false, matrix); + unbind(); + } + + public void uniformValue2f(String name, float value1, float value2) { + bind(); + glUniform2f(glGetUniformLocation(id, name), value1, value2); + unbind(); + } +} diff --git a/src/main/java/themixray/repeating/mod/render/shader/ShaderManager.java b/src/main/java/themixray/repeating/mod/render/shader/ShaderManager.java new file mode 100644 index 0000000..f11c98b --- /dev/null +++ b/src/main/java/themixray/repeating/mod/render/shader/ShaderManager.java @@ -0,0 +1,97 @@ +package themixray.repeating.mod.render.shader; + +import com.mojang.blaze3d.platform.GlStateManager; +import com.mojang.blaze3d.platform.TextureUtil; +import lombok.Getter; +import lombok.SneakyThrows; +import lombok.experimental.UtilityClass; +import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gl.GlImportProcessor; +import net.minecraft.resource.Resource; +import net.minecraft.resource.ResourceFactory; +import net.minecraft.util.Identifier; +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.StringUtils; +import org.jetbrains.annotations.Nullable; +import org.lwjgl.system.MemoryUtil; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.util.Optional; + +import static org.lwjgl.opengl.GL33.*; + +@UtilityClass +public class ShaderManager { + @Getter + private Shader positionColorShader; + + public void init() { + ClientLifecycleEvents.CLIENT_STARTED.register(client -> loadShaders()); + } + + private void loadShaders() { + positionColorShader = new Shader("position_color"); + } + + public int loadShaderProgram(String name, ShaderType type) { + try { + boolean file_present = true; + ResourceFactory resourceFactory = MinecraftClient.getInstance().getResourceManager(); + Optional resource = resourceFactory.getResource(new Identifier("renderer", "shader/" + name + type.fileExtension)); + int i = glCreateShader(type.glType); + if (resource.isPresent()) { + GlStateManager.glShaderSource(i, new GlImportProcessor() { + @SneakyThrows + @Nullable + @Override + public String loadImport(boolean inline, String name) { + return IOUtils.toString(resource.get().getInputStream(), StandardCharsets.UTF_8); + } + }.readSource(readResourceAsString(resource.get().getInputStream()))); + } else file_present = false; + glCompileShader(i); + if (glGetShaderi(i, GL_COMPILE_STATUS) == 0 || !file_present) { + String shaderInfo = StringUtils.trim(glGetShaderInfoLog(i, 32768)); + throw new IOException("Couldn't compile " + type.name + " program (" + name + ") : " + shaderInfo); + } + return i; + } catch (IOException e) { + e.printStackTrace(); + } + return 0; + } + + private String readResourceAsString(InputStream inputStream) { + ByteBuffer byteBuffer = null; + try { + byteBuffer = TextureUtil.readResource(inputStream); + int i = byteBuffer.position(); + byteBuffer.rewind(); + return MemoryUtil.memASCII(byteBuffer, i); + } catch (IOException ignored) { + } finally { + if (byteBuffer != null) { + MemoryUtil.memFree(byteBuffer); + } + } + return null; + } + + public enum ShaderType { + VERTEX("vertex", ".vsh", GL_VERTEX_SHADER), + FRAGMENT("fragment", ".fsh", GL_FRAGMENT_SHADER); + private final String name; + private final String fileExtension; + private final int glType; + + ShaderType(String name, String fileExtension, int glType) { + this.name = name; + this.fileExtension = fileExtension; + this.glType = glType; + } + } +} diff --git a/src/main/resources/assets/minecraft/font/default.json b/src/main/resources/assets/minecraft/font/default.json new file mode 100644 index 0000000..6501fb4 --- /dev/null +++ b/src/main/resources/assets/minecraft/font/default.json @@ -0,0 +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" + } + ] +} \ No newline at end of file diff --git a/src/main/resources/assets/renderer/shader/position_color.fsh b/src/main/resources/assets/renderer/shader/position_color.fsh new file mode 100644 index 0000000..aecb5f7 --- /dev/null +++ b/src/main/resources/assets/renderer/shader/position_color.fsh @@ -0,0 +1,12 @@ +#version 330 + +in vec4 vertexColor; + +out vec4 fragmentColor; + +void main() { + if (vertexColor.a == 0.0f) { + discard; + } + fragmentColor = vertexColor; +} \ No newline at end of file diff --git a/src/main/resources/assets/renderer/shader/position_color.vsh b/src/main/resources/assets/renderer/shader/position_color.vsh new file mode 100644 index 0000000..1f31658 --- /dev/null +++ b/src/main/resources/assets/renderer/shader/position_color.vsh @@ -0,0 +1,13 @@ +#version 330 + +layout (location = 0) in vec3 i_pos; +layout (location = 1) in vec4 i_color; + +uniform mat4 u_projection; + +out vec4 vertexColor; + +void main() { + gl_Position = u_projection * vec4(i_pos, 1.0f); + vertexColor = i_color; +} \ No newline at end of file diff --git a/src/main/resources/assets/repeating-mod/lang/en_us.json b/src/main/resources/assets/repeating-mod/lang/en_us.json index 57f79f0..cfab5c4 100644 --- a/src/main/resources/assets/repeating-mod/lang/en_us.json +++ b/src/main/resources/assets/repeating-mod/lang/en_us.json @@ -4,20 +4,21 @@ "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.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": "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.export_tooltip": "Exporting a recording to a file", + "text.repeating-mod.import_tooltip": "Importing an entry from the import.txt file", "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.", + "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)", "message.repeating-mod.replay_start": "Replay started", "message.repeating-mod.replay_stop": "Replay finished", diff --git a/src/main/resources/assets/repeating-mod/lang/ru_ru.json b/src/main/resources/assets/repeating-mod/lang/ru_ru.json index 8b62f15..3fd30f4 100644 --- a/src/main/resources/assets/repeating-mod/lang/ru_ru.json +++ b/src/main/resources/assets/repeating-mod/lang/ru_ru.json @@ -4,20 +4,21 @@ "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.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.export": "Экспорт записи", "text.repeating-mod.import": "Импорт записи", - "text.repeating-mod.basic": "Обычный режим", - "text.repeating-mod.parkour": "Режим паркура", - "text.repeating-mod.settings": "Настройки", + "text.repeating-mod.export_tooltip": "Экспорт записи в файл", + "text.repeating-mod.import_tooltip": "Импорт записи из файла import.txt", "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между ними меньше лимита.", + "text.repeating-mod.nan_pos_delay": "Таймера позиции нету", + "text.repeating-mod.pos_delay": "Таймер позиции: %s тиков", + "text.repeating-mod.pos_delay_tooltip": "Таймер, после которой добавляется\nивент позиции (20 тиков = 1 сек)", "message.repeating-mod.replay_start": "Повтор начат", "message.repeating-mod.replay_stop": "Повтор закончен", diff --git a/src/main/resources/assets/repeating-mod/textures/ui/loop.png b/src/main/resources/assets/repeating-mod/textures/ui/loop.png new file mode 100644 index 0000000..706aff4 Binary files /dev/null and b/src/main/resources/assets/repeating-mod/textures/ui/loop.png differ diff --git a/src/main/resources/assets/repeating-mod/textures/ui/no-loop.png b/src/main/resources/assets/repeating-mod/textures/ui/no-loop.png new file mode 100644 index 0000000..b50d39f Binary files /dev/null and b/src/main/resources/assets/repeating-mod/textures/ui/no-loop.png differ diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index 5328b3f..cdf4d39 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -4,13 +4,13 @@ "version": "${version}", "name": "Repeating Mod", - "description": "Mod that repeats your actions. ", + "description": "Mod that repeats your recorded actions. ", "authors": [ "TheMixRay" ], "contact": { - "homepage": "https://fabricmc.net/", - "sources": "https://github.com/FabricMC/fabric-example-mod" + "homepage": "https://modrinth.com/mod/repeating-mod", + "sources": "https://github.com/MeexReay/repeating-mod" }, "license": "CC0-1.0", @@ -29,7 +29,7 @@ "depends": { "fabricloader": ">=0.14.14", "fabric-api": "*", - "minecraft": "~1.19.3", + "minecraft": ">=1.20", "java": ">=17" }, "suggests": { diff --git a/src/main/resources/icon.png b/src/main/resources/icon.png new file mode 100644 index 0000000..f0987b0 Binary files /dev/null and b/src/main/resources/icon.png differ diff --git a/src/main/resources/repeating-mod.mixins.json b/src/main/resources/repeating-mod.mixins.json index 50f497d..c03d7f6 100644 --- a/src/main/resources/repeating-mod.mixins.json +++ b/src/main/resources/repeating-mod.mixins.json @@ -7,7 +7,10 @@ ], "client": [ "MovementMixin", - "InputMixin" + "InputMixin", + "RendererMixin", + "EntityMixin", + "ClientMixin" ], "injectors": { "defaultRequire": 1