From ffb9eb8bc9f252c200b472ed05634168281b7d9a Mon Sep 17 00:00:00 2001 From: RappyTV Date: Tue, 11 Nov 2025 12:17:43 +0100 Subject: [PATCH 01/12] chore: Setup workspace --- build.gradle.kts | 23 ++++++++--------- .../autofisher/core/AutoFisherAddon.java | 18 +++++++++++++ .../autofisher/core/AutoFisherConfig.java} | 6 ++--- .../java/org/example/core/ExampleAddon.java | 25 ------------------- .../core/commands/ExamplePingCommand.java | 25 ------------------- .../core/commands/ExamplePingSubCommand.java | 18 ------------- .../listener/ExampleGameTickListener.java | 24 ------------------ .../{example => autofisher}/i18n/en_us.json | 2 +- gradle.properties | 2 +- settings.gradle.kts | 2 +- 10 files changed, 34 insertions(+), 111 deletions(-) create mode 100644 core/src/main/java/com/rappytv/autofisher/core/AutoFisherAddon.java rename core/src/main/java/{org/example/core/ExampleConfiguration.java => com/rappytv/autofisher/core/AutoFisherConfig.java} (69%) delete mode 100644 core/src/main/java/org/example/core/ExampleAddon.java delete mode 100644 core/src/main/java/org/example/core/commands/ExamplePingCommand.java delete mode 100644 core/src/main/java/org/example/core/commands/ExamplePingSubCommand.java delete mode 100644 core/src/main/java/org/example/core/listener/ExampleGameTickListener.java rename core/src/main/resources/assets/{example => autofisher}/i18n/en_us.json (82%) diff --git a/build.gradle.kts b/build.gradle.kts index da5e473..862433c 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -9,27 +9,26 @@ group = "org.example" version = providers.environmentVariable("VERSION").getOrElse("1.0.0") labyMod { - defaultPackageName = "org.example" //change this to your main package name (used by all modules) + defaultPackageName = "com.rappytv.autofisher" + + addonInfo { + namespace = "autofisher" + displayName = "AutoFisher" + author = "RappyTV" + description = "..." // TODO: Add description + minecraftVersion = "*" + version = rootProject.version.toString() + } minecraft { registerVersion(versions.toTypedArray()) { runs { getByName("client") { - // When the property is set to true, you can log in with a Minecraft account - // devLogin = true + devLogin = true } } } } - - addonInfo { - namespace = "example" - displayName = "ExampleAddon" - author = "Example Author" - description = "Example Description" - minecraftVersion = "*" - version = rootProject.version.toString() - } } subprojects { diff --git a/core/src/main/java/com/rappytv/autofisher/core/AutoFisherAddon.java b/core/src/main/java/com/rappytv/autofisher/core/AutoFisherAddon.java new file mode 100644 index 0000000..e9b5bfd --- /dev/null +++ b/core/src/main/java/com/rappytv/autofisher/core/AutoFisherAddon.java @@ -0,0 +1,18 @@ +package com.rappytv.autofisher.core; + +import net.labymod.api.addon.LabyAddon; +import net.labymod.api.models.addon.annotation.AddonMain; + +@AddonMain +public class AutoFisherAddon extends LabyAddon { + + @Override + protected void enable() { + this.registerSettingCategory(); + } + + @Override + protected Class configurationClass() { + return AutoFisherConfig.class; + } +} diff --git a/core/src/main/java/org/example/core/ExampleConfiguration.java b/core/src/main/java/com/rappytv/autofisher/core/AutoFisherConfig.java similarity index 69% rename from core/src/main/java/org/example/core/ExampleConfiguration.java rename to core/src/main/java/com/rappytv/autofisher/core/AutoFisherConfig.java index 0faafb1..ae30b30 100644 --- a/core/src/main/java/org/example/core/ExampleConfiguration.java +++ b/core/src/main/java/com/rappytv/autofisher/core/AutoFisherConfig.java @@ -1,12 +1,10 @@ -package org.example.core; +package com.rappytv.autofisher.core; import net.labymod.api.addon.AddonConfig; import net.labymod.api.client.gui.screen.widget.widgets.input.SwitchWidget.SwitchSetting; -import net.labymod.api.configuration.loader.annotation.ConfigName; import net.labymod.api.configuration.loader.property.ConfigProperty; -@ConfigName("settings") -public class ExampleConfiguration extends AddonConfig { +public class AutoFisherConfig extends AddonConfig { @SwitchSetting private final ConfigProperty enabled = new ConfigProperty<>(true); diff --git a/core/src/main/java/org/example/core/ExampleAddon.java b/core/src/main/java/org/example/core/ExampleAddon.java deleted file mode 100644 index 47a4563..0000000 --- a/core/src/main/java/org/example/core/ExampleAddon.java +++ /dev/null @@ -1,25 +0,0 @@ -package org.example.core; - -import net.labymod.api.addon.LabyAddon; -import net.labymod.api.models.addon.annotation.AddonMain; -import org.example.core.commands.ExamplePingCommand; -import org.example.core.listener.ExampleGameTickListener; - -@AddonMain -public class ExampleAddon extends LabyAddon { - - @Override - protected void enable() { - this.registerSettingCategory(); - - this.registerListener(new ExampleGameTickListener(this)); - this.registerCommand(new ExamplePingCommand()); - - this.logger().info("Enabled the Addon"); - } - - @Override - protected Class configurationClass() { - return ExampleConfiguration.class; - } -} diff --git a/core/src/main/java/org/example/core/commands/ExamplePingCommand.java b/core/src/main/java/org/example/core/commands/ExamplePingCommand.java deleted file mode 100644 index 5952915..0000000 --- a/core/src/main/java/org/example/core/commands/ExamplePingCommand.java +++ /dev/null @@ -1,25 +0,0 @@ -package org.example.core.commands; - -import net.labymod.api.client.chat.command.Command; -import net.labymod.api.client.component.Component; -import net.labymod.api.client.component.format.NamedTextColor; - -public class ExamplePingCommand extends Command { - - public ExamplePingCommand() { - super("ping", "pong"); - - this.withSubCommand(new ExamplePingSubCommand()); - } - - @Override - public boolean execute(String prefix, String[] arguments) { - if (prefix.equalsIgnoreCase("ping")) { - this.displayMessage(Component.text("Ping!", NamedTextColor.AQUA)); - return false; - } - - this.displayMessage(Component.text("Pong!", NamedTextColor.GOLD)); - return true; - } -} diff --git a/core/src/main/java/org/example/core/commands/ExamplePingSubCommand.java b/core/src/main/java/org/example/core/commands/ExamplePingSubCommand.java deleted file mode 100644 index 8c6a981..0000000 --- a/core/src/main/java/org/example/core/commands/ExamplePingSubCommand.java +++ /dev/null @@ -1,18 +0,0 @@ -package org.example.core.commands; - -import net.labymod.api.client.chat.command.SubCommand; -import net.labymod.api.client.component.Component; -import net.labymod.api.client.component.format.NamedTextColor; - -public class ExamplePingSubCommand extends SubCommand { - - protected ExamplePingSubCommand() { - super("pong"); - } - - @Override - public boolean execute(String prefix, String[] arguments) { - this.displayMessage(Component.text("Ping Pong!", NamedTextColor.GRAY)); - return true; - } -} diff --git a/core/src/main/java/org/example/core/listener/ExampleGameTickListener.java b/core/src/main/java/org/example/core/listener/ExampleGameTickListener.java deleted file mode 100644 index b631b85..0000000 --- a/core/src/main/java/org/example/core/listener/ExampleGameTickListener.java +++ /dev/null @@ -1,24 +0,0 @@ -package org.example.core.listener; - -import net.labymod.api.event.Phase; -import net.labymod.api.event.Subscribe; -import net.labymod.api.event.client.lifecycle.GameTickEvent; -import org.example.core.ExampleAddon; - -public class ExampleGameTickListener { - - private final ExampleAddon addon; - - public ExampleGameTickListener(ExampleAddon addon) { - this.addon = addon; - } - - @Subscribe - public void onGameTick(GameTickEvent event) { - if (event.phase() != Phase.PRE) { - return; - } - - this.addon.logger().info(this.addon.configuration().enabled().get() ? "enabled" : "disabled"); - } -} diff --git a/core/src/main/resources/assets/example/i18n/en_us.json b/core/src/main/resources/assets/autofisher/i18n/en_us.json similarity index 82% rename from core/src/main/resources/assets/example/i18n/en_us.json rename to core/src/main/resources/assets/autofisher/i18n/en_us.json index 0850d97..1e1d928 100644 --- a/core/src/main/resources/assets/example/i18n/en_us.json +++ b/core/src/main/resources/assets/autofisher/i18n/en_us.json @@ -1,5 +1,5 @@ { - "example": { + "autofisher": { "settings": { "enabled": { "name": "Enabled" diff --git a/gradle.properties b/gradle.properties index 44dc10b..9243d49 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,2 +1,2 @@ org.gradle.jvmargs=-Xmx4096m -net.labymod.minecraft-versions=1.8.9;1.12.2;1.16.5;1.17.1;1.18.2;1.19.2;1.19.3;1.19.4;1.20.1;1.20.2;1.20.4;1.20.5;1.20.6;1.21;1.21.1;1.21.3;1.21.4;1.21.5 \ No newline at end of file +net.labymod.minecraft-versions=1.8.9;1.12.2;1.16.5;1.17.1;1.18.2;1.19.2;1.19.3;1.19.4;1.20.1;1.20.2;1.20.4;1.20.5;1.20.6;1.21;1.21.1;1.21.3;1.21.4;1.21.5;1.21.8;1.21.10 \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 6a0b6df..209290c 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,4 +1,4 @@ -rootProject.name = "labymod4-addon-template" +rootProject.name = "autofisher" pluginManagement { val labyGradlePluginVersion = "0.5.9" From 8a55b527024741afedc80b38e97affe10234f1ff Mon Sep 17 00:00:00 2001 From: RappyTV Date: Wed, 12 Nov 2025 01:18:14 +0100 Subject: [PATCH 02/12] feat: Implement basic functionality in 1.19.2 --- .../rappytv/autofisher/FishingController.java | 22 +++++++ .../autofisher/event/FishHookBiteEvent.java | 7 +++ .../event/FishHookRetractEvent.java | 7 +++ .../autofisher/core/AutoFisherAddon.java | 12 ++++ .../autofisher/core/AutoFisherConfig.java | 27 +++++++++ .../core/listener/FishingListener.java | 45 ++++++++++++++ .../assets/autofisher/i18n/en_us.json | 14 +++++ .../v1_19_2/VersionedFishingController.java | 60 +++++++++++++++++++ .../v1_19_2/mixins/MixinFishingHook.java | 52 ++++++++++++++++ 9 files changed, 246 insertions(+) create mode 100644 api/src/main/java/com/rappytv/autofisher/FishingController.java create mode 100644 api/src/main/java/com/rappytv/autofisher/event/FishHookBiteEvent.java create mode 100644 api/src/main/java/com/rappytv/autofisher/event/FishHookRetractEvent.java create mode 100644 core/src/main/java/com/rappytv/autofisher/core/listener/FishingListener.java create mode 100644 game-runner/src/v1_19_2/java/com/rappytv/autofisher/v1_19_2/VersionedFishingController.java create mode 100644 game-runner/src/v1_19_2/java/com/rappytv/autofisher/v1_19_2/mixins/MixinFishingHook.java diff --git a/api/src/main/java/com/rappytv/autofisher/FishingController.java b/api/src/main/java/com/rappytv/autofisher/FishingController.java new file mode 100644 index 0000000..c390273 --- /dev/null +++ b/api/src/main/java/com/rappytv/autofisher/FishingController.java @@ -0,0 +1,22 @@ +package com.rappytv.autofisher; + +import net.labymod.api.reference.annotation.Referenceable; + +@Referenceable +public abstract class FishingController { + + private boolean manualRetraction = true; + + public abstract void releaseFishingRod(); + + public abstract void retractFishingRod(); + + public void setManualRetraction(boolean value) { + this.manualRetraction = value; + } + + public boolean isManualRetraction() { + return this.manualRetraction; + } + +} diff --git a/api/src/main/java/com/rappytv/autofisher/event/FishHookBiteEvent.java b/api/src/main/java/com/rappytv/autofisher/event/FishHookBiteEvent.java new file mode 100644 index 0000000..230bc3f --- /dev/null +++ b/api/src/main/java/com/rappytv/autofisher/event/FishHookBiteEvent.java @@ -0,0 +1,7 @@ +package com.rappytv.autofisher.event; + +import net.labymod.api.event.Event; + +public class FishHookBiteEvent implements Event { + +} diff --git a/api/src/main/java/com/rappytv/autofisher/event/FishHookRetractEvent.java b/api/src/main/java/com/rappytv/autofisher/event/FishHookRetractEvent.java new file mode 100644 index 0000000..80c1b3e --- /dev/null +++ b/api/src/main/java/com/rappytv/autofisher/event/FishHookRetractEvent.java @@ -0,0 +1,7 @@ +package com.rappytv.autofisher.event; + +import net.labymod.api.event.Event; + +public record FishHookRetractEvent(boolean manual) implements Event { + +} diff --git a/core/src/main/java/com/rappytv/autofisher/core/AutoFisherAddon.java b/core/src/main/java/com/rappytv/autofisher/core/AutoFisherAddon.java index e9b5bfd..28c4611 100644 --- a/core/src/main/java/com/rappytv/autofisher/core/AutoFisherAddon.java +++ b/core/src/main/java/com/rappytv/autofisher/core/AutoFisherAddon.java @@ -1,18 +1,30 @@ package com.rappytv.autofisher.core; +import com.rappytv.autofisher.FishingController; +import com.rappytv.autofisher.api.generated.ReferenceStorage; +import com.rappytv.autofisher.core.listener.FishingListener; import net.labymod.api.addon.LabyAddon; import net.labymod.api.models.addon.annotation.AddonMain; @AddonMain public class AutoFisherAddon extends LabyAddon { + private static AutoFisherAddon instance; + @Override protected void enable() { + instance = this; + this.registerSettingCategory(); + this.registerListener(new FishingListener(this)); } @Override protected Class configurationClass() { return AutoFisherConfig.class; } + + public static FishingController fishingController() { + return ((ReferenceStorage) instance.referenceStorageAccessor()).fishingController(); + } } diff --git a/core/src/main/java/com/rappytv/autofisher/core/AutoFisherConfig.java b/core/src/main/java/com/rappytv/autofisher/core/AutoFisherConfig.java index ae30b30..fcfc98e 100644 --- a/core/src/main/java/com/rappytv/autofisher/core/AutoFisherConfig.java +++ b/core/src/main/java/com/rappytv/autofisher/core/AutoFisherConfig.java @@ -1,16 +1,43 @@ package com.rappytv.autofisher.core; import net.labymod.api.addon.AddonConfig; +import net.labymod.api.client.gui.screen.widget.widgets.input.SliderWidget.SliderSetting; import net.labymod.api.client.gui.screen.widget.widgets.input.SwitchWidget.SwitchSetting; import net.labymod.api.configuration.loader.property.ConfigProperty; +import net.labymod.api.configuration.settings.annotation.SettingRequires; +import net.labymod.api.configuration.settings.annotation.SettingSection; public class AutoFisherConfig extends AddonConfig { @SwitchSetting private final ConfigProperty enabled = new ConfigProperty<>(true); + @SettingSection("delay") + @SwitchSetting + private final ConfigProperty enableDelay = new ConfigProperty<>(true); + + @SettingRequires("enableDelay") + @SwitchSetting + private final ConfigProperty randomDelay = new ConfigProperty<>(true); + + @SettingRequires(value = "randomDelay", invert = true) + @SliderSetting(min = 1, max = 20) + private final ConfigProperty delay = new ConfigProperty<>(3); + @Override public ConfigProperty enabled() { return this.enabled; } + + public ConfigProperty enableDelay() { + return this.enableDelay; + } + + public ConfigProperty randomDelay() { + return this.randomDelay; + } + + public ConfigProperty delay() { + return this.delay; + } } diff --git a/core/src/main/java/com/rappytv/autofisher/core/listener/FishingListener.java b/core/src/main/java/com/rappytv/autofisher/core/listener/FishingListener.java new file mode 100644 index 0000000..738fda2 --- /dev/null +++ b/core/src/main/java/com/rappytv/autofisher/core/listener/FishingListener.java @@ -0,0 +1,45 @@ +package com.rappytv.autofisher.core.listener; + +import com.rappytv.autofisher.core.AutoFisherAddon; +import com.rappytv.autofisher.event.FishHookBiteEvent; +import com.rappytv.autofisher.event.FishHookRetractEvent; +import java.util.Random; +import java.util.concurrent.TimeUnit; +import net.labymod.api.event.Subscribe; +import net.labymod.api.util.concurrent.task.Task; + +public class FishingListener { + + private static final Random random = new Random(); + private static final Runnable retraction = AutoFisherAddon.fishingController()::releaseFishingRod; + + private final AutoFisherAddon addon; + + public FishingListener(AutoFisherAddon addon) { + this.addon = addon; + } + + @Subscribe + public void onFishBite(FishHookBiteEvent event) { + AutoFisherAddon.fishingController().retractFishingRod(); + } + + @Subscribe + public void onHookRetract(FishHookRetractEvent event) { + if (!event.manual()) { + if (this.addon.configuration().enableDelay().get()) { + int delay; + if (this.addon.configuration().randomDelay().get()) { + delay = random.nextInt(1, 6); + } else { + delay = this.addon.configuration().delay().get(); + } + + Task.builder(retraction).delay(delay, TimeUnit.SECONDS).build().execute(); + } else { + retraction.run(); + } + } + } + +} diff --git a/core/src/main/resources/assets/autofisher/i18n/en_us.json b/core/src/main/resources/assets/autofisher/i18n/en_us.json index 1e1d928..4d3e9d7 100644 --- a/core/src/main/resources/assets/autofisher/i18n/en_us.json +++ b/core/src/main/resources/assets/autofisher/i18n/en_us.json @@ -1,8 +1,22 @@ { "autofisher": { "settings": { + "header": { + "delay": { + "name": "Delay" + } + }, "enabled": { "name": "Enabled" + }, + "enableDelay": { + "name": "Delay" + }, + "randomDelay": { + "name": "Random delay" + }, + "delay": { + "name": "Delay in seconds" } } } diff --git a/game-runner/src/v1_19_2/java/com/rappytv/autofisher/v1_19_2/VersionedFishingController.java b/game-runner/src/v1_19_2/java/com/rappytv/autofisher/v1_19_2/VersionedFishingController.java new file mode 100644 index 0000000..c61f534 --- /dev/null +++ b/game-runner/src/v1_19_2/java/com/rappytv/autofisher/v1_19_2/VersionedFishingController.java @@ -0,0 +1,60 @@ +package com.rappytv.autofisher.v1_19_2; + +import com.rappytv.autofisher.FishingController; +import com.rappytv.autofisher.core.AutoFisherAddon; +import javax.inject.Singleton; +import net.labymod.api.models.Implements; +import net.minecraft.client.Minecraft; +import net.minecraft.client.multiplayer.MultiPlayerGameMode; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.entity.projectile.FishingHook; +import net.minecraft.world.item.FishingRodItem; +import net.minecraft.world.item.Item; + +@Singleton +@Implements(FishingController.class) +public class VersionedFishingController extends FishingController { + + @Override + public void releaseFishingRod() { + Player player = Minecraft.getInstance().player; + if (player == null) { + return; + } + Item mainHandItem = player.getMainHandItem().getItem(); + if (mainHandItem instanceof FishingRodItem) { + this.useItem(InteractionHand.MAIN_HAND); // TODO: Add off hand support + } + } + + @Override + public void retractFishingRod() { + Player player = Minecraft.getInstance().player; + if (player == null) { + return; + } + FishingHook hook = player.fishing; + if (hook == null) { + return; + } + + AutoFisherAddon.fishingController().setManualRetraction(false); + this.useItem(InteractionHand.MAIN_HAND); + } + + private void useItem(InteractionHand hand) { + Player player = Minecraft.getInstance().player; + if (player == null) { + return; + } + + MultiPlayerGameMode gameMode = Minecraft.getInstance().gameMode; + if (gameMode == null) { + return; + } + + gameMode.useItem(player, hand); + player.swing(hand); + } +} diff --git a/game-runner/src/v1_19_2/java/com/rappytv/autofisher/v1_19_2/mixins/MixinFishingHook.java b/game-runner/src/v1_19_2/java/com/rappytv/autofisher/v1_19_2/mixins/MixinFishingHook.java new file mode 100644 index 0000000..f185990 --- /dev/null +++ b/game-runner/src/v1_19_2/java/com/rappytv/autofisher/v1_19_2/mixins/MixinFishingHook.java @@ -0,0 +1,52 @@ +package com.rappytv.autofisher.v1_19_2.mixins; + +import com.rappytv.autofisher.core.AutoFisherAddon; +import com.rappytv.autofisher.event.FishHookBiteEvent; +import com.rappytv.autofisher.event.FishHookRetractEvent; +import net.labymod.api.Laby; +import net.minecraft.network.syncher.EntityDataAccessor; +import net.minecraft.world.entity.projectile.FishingHook; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.At.Shift; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(FishingHook.class) +public class MixinFishingHook { + + @Unique + private static final int PUTFIELD_OPCODE = 181; + + @Shadow + private boolean biting; + + @Inject( + method = "onClientRemoval", + at = @At("TAIL") + ) + private void onRemove(CallbackInfo ci) { + boolean manual = AutoFisherAddon.fishingController().isManualRetraction(); + Laby.fireEvent(new FishHookRetractEvent(manual)); + if (!manual) { + AutoFisherAddon.fishingController().setManualRetraction(true); + } + } + + @Inject( + method = "onSyncedDataUpdated", + at = @At( + value = "FIELD", + target = "Lnet/minecraft/world/entity/projectile/FishingHook;biting:Z", + opcode = PUTFIELD_OPCODE, + shift = Shift.AFTER + ) + ) + private void onFishBiting(EntityDataAccessor $$0, CallbackInfo ci) { + if (this.biting) { + Laby.fireEvent(new FishHookBiteEvent()); + } + } +} From 99508e9ad99de1add7584850b71546de940a1341 Mon Sep 17 00:00:00 2001 From: RappyTV Date: Wed, 12 Nov 2025 01:23:50 +0100 Subject: [PATCH 03/12] chore: Add description --- build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index 862433c..308d052 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -15,7 +15,7 @@ labyMod { namespace = "autofisher" displayName = "AutoFisher" author = "RappyTV" - description = "..." // TODO: Add description + description = "Completely automate the way your fishing rod works" minecraftVersion = "*" version = rootProject.version.toString() } From 41f426a6e31d2eb1c3c873242ce878d9a96a1f12 Mon Sep 17 00:00:00 2001 From: RappyTV Date: Wed, 12 Nov 2025 01:26:28 +0100 Subject: [PATCH 04/12] feat: Add option to toggle auto cast --- .../rappytv/autofisher/core/AutoFisherConfig.java | 8 ++++++++ .../autofisher/core/listener/FishingListener.java | 2 +- .../resources/assets/autofisher/i18n/en_us.json | 13 ++++++++++--- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/com/rappytv/autofisher/core/AutoFisherConfig.java b/core/src/main/java/com/rappytv/autofisher/core/AutoFisherConfig.java index fcfc98e..d9f8bc9 100644 --- a/core/src/main/java/com/rappytv/autofisher/core/AutoFisherConfig.java +++ b/core/src/main/java/com/rappytv/autofisher/core/AutoFisherConfig.java @@ -12,7 +12,11 @@ public class AutoFisherConfig extends AddonConfig { @SwitchSetting private final ConfigProperty enabled = new ConfigProperty<>(true); + @SwitchSetting + private final ConfigProperty autoCast = new ConfigProperty<>(true); + @SettingSection("delay") + @SettingRequires("autoCast") @SwitchSetting private final ConfigProperty enableDelay = new ConfigProperty<>(true); @@ -29,6 +33,10 @@ public ConfigProperty enabled() { return this.enabled; } + public ConfigProperty autoCast() { + return this.autoCast; + } + public ConfigProperty enableDelay() { return this.enableDelay; } diff --git a/core/src/main/java/com/rappytv/autofisher/core/listener/FishingListener.java b/core/src/main/java/com/rappytv/autofisher/core/listener/FishingListener.java index 738fda2..c0b369b 100644 --- a/core/src/main/java/com/rappytv/autofisher/core/listener/FishingListener.java +++ b/core/src/main/java/com/rappytv/autofisher/core/listener/FishingListener.java @@ -26,7 +26,7 @@ public void onFishBite(FishHookBiteEvent event) { @Subscribe public void onHookRetract(FishHookRetractEvent event) { - if (!event.manual()) { + if (this.addon.configuration().autoCast().get() && !event.manual()) { if (this.addon.configuration().enableDelay().get()) { int delay; if (this.addon.configuration().randomDelay().get()) { diff --git a/core/src/main/resources/assets/autofisher/i18n/en_us.json b/core/src/main/resources/assets/autofisher/i18n/en_us.json index 4d3e9d7..f90d606 100644 --- a/core/src/main/resources/assets/autofisher/i18n/en_us.json +++ b/core/src/main/resources/assets/autofisher/i18n/en_us.json @@ -9,14 +9,21 @@ "enabled": { "name": "Enabled" }, + "autoCast": { + "name": "Auto cast", + "description": "Automatically cast the fishing rod after retracting it" + }, "enableDelay": { - "name": "Delay" + "name": "Delay", + "description": "Enables a delay before a new reel is casted" }, "randomDelay": { - "name": "Random delay" + "name": "Random delay", + "description": "Picks a random delay between §b1 and 5 seconds§r." }, "delay": { - "name": "Delay in seconds" + "name": "Delay in seconds", + "description": "Choose your own delay in seconds" } } } From 039dccfbbc5a4c8be55d01493f1c8cebcbf96c85 Mon Sep 17 00:00:00 2001 From: RappyTV Date: Wed, 12 Nov 2025 01:26:52 +0100 Subject: [PATCH 05/12] chore: Rename FishingController#releaseFishingRod --- api/src/main/java/com/rappytv/autofisher/FishingController.java | 2 +- .../com/rappytv/autofisher/core/listener/FishingListener.java | 2 +- .../rappytv/autofisher/v1_19_2/VersionedFishingController.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/api/src/main/java/com/rappytv/autofisher/FishingController.java b/api/src/main/java/com/rappytv/autofisher/FishingController.java index c390273..d2e1c46 100644 --- a/api/src/main/java/com/rappytv/autofisher/FishingController.java +++ b/api/src/main/java/com/rappytv/autofisher/FishingController.java @@ -7,7 +7,7 @@ public abstract class FishingController { private boolean manualRetraction = true; - public abstract void releaseFishingRod(); + public abstract void castFishingRod(); public abstract void retractFishingRod(); diff --git a/core/src/main/java/com/rappytv/autofisher/core/listener/FishingListener.java b/core/src/main/java/com/rappytv/autofisher/core/listener/FishingListener.java index c0b369b..e1716f0 100644 --- a/core/src/main/java/com/rappytv/autofisher/core/listener/FishingListener.java +++ b/core/src/main/java/com/rappytv/autofisher/core/listener/FishingListener.java @@ -11,7 +11,7 @@ public class FishingListener { private static final Random random = new Random(); - private static final Runnable retraction = AutoFisherAddon.fishingController()::releaseFishingRod; + private static final Runnable retraction = AutoFisherAddon.fishingController()::castFishingRod; private final AutoFisherAddon addon; diff --git a/game-runner/src/v1_19_2/java/com/rappytv/autofisher/v1_19_2/VersionedFishingController.java b/game-runner/src/v1_19_2/java/com/rappytv/autofisher/v1_19_2/VersionedFishingController.java index c61f534..6e98722 100644 --- a/game-runner/src/v1_19_2/java/com/rappytv/autofisher/v1_19_2/VersionedFishingController.java +++ b/game-runner/src/v1_19_2/java/com/rappytv/autofisher/v1_19_2/VersionedFishingController.java @@ -17,7 +17,7 @@ public class VersionedFishingController extends FishingController { @Override - public void releaseFishingRod() { + public void castFishingRod() { Player player = Minecraft.getInstance().player; if (player == null) { return; From 2cfdeffdb0e174122a193ce13a46711f349f95b6 Mon Sep 17 00:00:00 2001 From: RappyTV Date: Wed, 12 Nov 2025 02:19:01 +0100 Subject: [PATCH 06/12] feat: Support both hands (hopefully) --- .../v1_19_2/VersionedFishingController.java | 29 ++++++++++++++----- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/game-runner/src/v1_19_2/java/com/rappytv/autofisher/v1_19_2/VersionedFishingController.java b/game-runner/src/v1_19_2/java/com/rappytv/autofisher/v1_19_2/VersionedFishingController.java index 6e98722..31f23d5 100644 --- a/game-runner/src/v1_19_2/java/com/rappytv/autofisher/v1_19_2/VersionedFishingController.java +++ b/game-runner/src/v1_19_2/java/com/rappytv/autofisher/v1_19_2/VersionedFishingController.java @@ -10,7 +10,8 @@ import net.minecraft.world.entity.player.Player; import net.minecraft.world.entity.projectile.FishingHook; import net.minecraft.world.item.FishingRodItem; -import net.minecraft.world.item.Item; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; @Singleton @Implements(FishingController.class) @@ -22,9 +23,9 @@ public void castFishingRod() { if (player == null) { return; } - Item mainHandItem = player.getMainHandItem().getItem(); - if (mainHandItem instanceof FishingRodItem) { - this.useItem(InteractionHand.MAIN_HAND); // TODO: Add off hand support + InteractionHand hand = this.getFishingRodHand(player); + if (hand != null) { + this.useItem(hand); } } @@ -39,11 +40,25 @@ public void retractFishingRod() { return; } - AutoFisherAddon.fishingController().setManualRetraction(false); - this.useItem(InteractionHand.MAIN_HAND); + InteractionHand hand = this.getFishingRodHand(player); + if (hand != null) { + AutoFisherAddon.fishingController().setManualRetraction(false); + this.useItem(hand); + } + } + + @Nullable + private InteractionHand getFishingRodHand(@NotNull Player player) { + if (player.getMainHandItem().getItem() instanceof FishingRodItem) { + return InteractionHand.MAIN_HAND; + } else if (player.getOffhandItem().getItem() instanceof FishingRodItem) { + return InteractionHand.OFF_HAND; + } + + return null; } - private void useItem(InteractionHand hand) { + private void useItem(@NotNull InteractionHand hand) { Player player = Minecraft.getInstance().player; if (player == null) { return; From 5182dc3d6cff0c2b119ad33dc983020a536e37f2 Mon Sep 17 00:00:00 2001 From: RappyTV Date: Wed, 12 Nov 2025 02:58:11 +0100 Subject: [PATCH 07/12] feat: Add textures --- .../autofisher/core/AutoFisherConfig.java | 8 ++++++++ .../core/listener/FishingListener.java | 2 +- .../assets/autofisher/textures/icon.png | Bin 0 -> 77287 bytes .../themes/vanilla/textures/settings.png | Bin 0 -> 738 bytes 4 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 core/src/main/resources/assets/autofisher/textures/icon.png create mode 100644 core/src/main/resources/assets/autofisher/themes/vanilla/textures/settings.png diff --git a/core/src/main/java/com/rappytv/autofisher/core/AutoFisherConfig.java b/core/src/main/java/com/rappytv/autofisher/core/AutoFisherConfig.java index d9f8bc9..6c4dc9c 100644 --- a/core/src/main/java/com/rappytv/autofisher/core/AutoFisherConfig.java +++ b/core/src/main/java/com/rappytv/autofisher/core/AutoFisherConfig.java @@ -3,27 +3,35 @@ import net.labymod.api.addon.AddonConfig; import net.labymod.api.client.gui.screen.widget.widgets.input.SliderWidget.SliderSetting; import net.labymod.api.client.gui.screen.widget.widgets.input.SwitchWidget.SwitchSetting; +import net.labymod.api.configuration.loader.annotation.SpriteSlot; +import net.labymod.api.configuration.loader.annotation.SpriteTexture; import net.labymod.api.configuration.loader.property.ConfigProperty; import net.labymod.api.configuration.settings.annotation.SettingRequires; import net.labymod.api.configuration.settings.annotation.SettingSection; +@SpriteTexture("settings.png") public class AutoFisherConfig extends AddonConfig { + @SpriteSlot @SwitchSetting private final ConfigProperty enabled = new ConfigProperty<>(true); + @SpriteSlot(x = 1) @SwitchSetting private final ConfigProperty autoCast = new ConfigProperty<>(true); @SettingSection("delay") + @SpriteSlot(x = 2) @SettingRequires("autoCast") @SwitchSetting private final ConfigProperty enableDelay = new ConfigProperty<>(true); + @SpriteSlot(x = 3) @SettingRequires("enableDelay") @SwitchSetting private final ConfigProperty randomDelay = new ConfigProperty<>(true); + @SpriteSlot(x = 4) @SettingRequires(value = "randomDelay", invert = true) @SliderSetting(min = 1, max = 20) private final ConfigProperty delay = new ConfigProperty<>(3); diff --git a/core/src/main/java/com/rappytv/autofisher/core/listener/FishingListener.java b/core/src/main/java/com/rappytv/autofisher/core/listener/FishingListener.java index e1716f0..554877d 100644 --- a/core/src/main/java/com/rappytv/autofisher/core/listener/FishingListener.java +++ b/core/src/main/java/com/rappytv/autofisher/core/listener/FishingListener.java @@ -8,7 +8,7 @@ import net.labymod.api.event.Subscribe; import net.labymod.api.util.concurrent.task.Task; -public class FishingListener { +public class FishingListener { // TODO: Restrict with permission private static final Random random = new Random(); private static final Runnable retraction = AutoFisherAddon.fishingController()::castFishingRod; diff --git a/core/src/main/resources/assets/autofisher/textures/icon.png b/core/src/main/resources/assets/autofisher/textures/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..0552f164d5127a9fd738c180e549ce25ae0c7eb1 GIT binary patch literal 77287 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®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�cL2vv1ISE$udz{_8wV=fBWf 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�HrM}xbwq6?b7>qR-a7;Rib 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 SxmOVXGiT=f8Mmhe@A)4t`MMnd literal 0 HcmV?d00001 diff --git a/core/src/main/resources/assets/autofisher/themes/vanilla/textures/settings.png b/core/src/main/resources/assets/autofisher/themes/vanilla/textures/settings.png new file mode 100644 index 0000000000000000000000000000000000000000..42d305aa241fbc231d6e7e866d67f93a0c9357cd GIT binary patch literal 738 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H3?#oinD`S&bp`l@xB}^G_5ZW&>m@`~)wEr9 zztDwZ*^3|19;&+o6f*CSZQj(ARGUzg_|9jTO#hGE1&j0`a zJ3*_ma-3)Mt7mKGo0*y2{{MRO=FMGc z>Ru+|)22YaNoTt1_cA5-zGPpZYZyy{{DK)Ap4~`eU|?eNba4!+xb^nh zZNEbfBCQW6`ubn!x*!(q$E5Y(<=^=8wzD%cC$7j938$8cF zp5^10#Mn2&%URR*pYJ@tk4I;j$=`uNxP=bz)Xiq0`s-BLb(nd#Brv$Z$d zR+uJCJi`+3Zh!hJmLHiObu)f3UHHwDaBwwS4%_l=($NhS|0ig^^SxN`f}^2^%|cZ{ zgJE$}jr9#ax3!GiY%Y&a{heOB^$LR>qxq9NuQ&ym?MxcnrmnD6n7`Jq;qtyY4x9$+ z4`dnddEY;zD&n|l*4eP`Q~y4HUOcBoi#4HYrPw)UT>Ymw@IIAKJOf!E{9E@ z46?pksGU6bct`ouS+X<#o(IMm2z(G{n0EO00sB3m6b1qpO!n}Xyyt^(7#uEtu)DC= v8p>nvJ^b6@`#TuVg!eu3-QCzV9+>)@ih1y^(;)Spu6{1-oD!M Date: Wed, 12 Nov 2025 03:03:38 +0100 Subject: [PATCH 08/12] chore: Restrict version string --- build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index 308d052..51b3320 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -16,7 +16,7 @@ labyMod { displayName = "AutoFisher" author = "RappyTV" description = "Completely automate the way your fishing rod works" - minecraftVersion = "*" + minecraftVersion = "1.19.2" version = rootProject.version.toString() } From 4093a187040e717bf2ef4c291465255efdcbcf95 Mon Sep 17 00:00:00 2001 From: RappyTV Date: Wed, 12 Nov 2025 16:07:23 +0100 Subject: [PATCH 09/12] feat: Add permission to disallow addon --- .../autofisher/core/AutoFisherAddon.java | 2 + .../core/listener/FishingListener.java | 40 ++++++++++++------- 2 files changed, 28 insertions(+), 14 deletions(-) diff --git a/core/src/main/java/com/rappytv/autofisher/core/AutoFisherAddon.java b/core/src/main/java/com/rappytv/autofisher/core/AutoFisherAddon.java index 28c4611..616f990 100644 --- a/core/src/main/java/com/rappytv/autofisher/core/AutoFisherAddon.java +++ b/core/src/main/java/com/rappytv/autofisher/core/AutoFisherAddon.java @@ -9,6 +9,7 @@ @AddonMain public class AutoFisherAddon extends LabyAddon { + public static final String PERMISSION = "autofisher"; private static AutoFisherAddon instance; @Override @@ -17,6 +18,7 @@ protected void enable() { this.registerSettingCategory(); this.registerListener(new FishingListener(this)); + this.labyAPI().permissionRegistry().register(PERMISSION, true, true); } @Override diff --git a/core/src/main/java/com/rappytv/autofisher/core/listener/FishingListener.java b/core/src/main/java/com/rappytv/autofisher/core/listener/FishingListener.java index 554877d..f51ccb3 100644 --- a/core/src/main/java/com/rappytv/autofisher/core/listener/FishingListener.java +++ b/core/src/main/java/com/rappytv/autofisher/core/listener/FishingListener.java @@ -5,10 +5,11 @@ import com.rappytv.autofisher.event.FishHookRetractEvent; import java.util.Random; import java.util.concurrent.TimeUnit; +import net.labymod.api.Laby; import net.labymod.api.event.Subscribe; import net.labymod.api.util.concurrent.task.Task; -public class FishingListener { // TODO: Restrict with permission +public class FishingListener { private static final Random random = new Random(); private static final Runnable retraction = AutoFisherAddon.fishingController()::castFishingRod; @@ -21,25 +22,36 @@ public FishingListener(AutoFisherAddon addon) { @Subscribe public void onFishBite(FishHookBiteEvent event) { + if (!this.isAllowed()) { + return; + } AutoFisherAddon.fishingController().retractFishingRod(); } @Subscribe public void onHookRetract(FishHookRetractEvent event) { - if (this.addon.configuration().autoCast().get() && !event.manual()) { - if (this.addon.configuration().enableDelay().get()) { - int delay; - if (this.addon.configuration().randomDelay().get()) { - delay = random.nextInt(1, 6); - } else { - delay = this.addon.configuration().delay().get(); - } - - Task.builder(retraction).delay(delay, TimeUnit.SECONDS).build().execute(); - } else { - retraction.run(); - } + if (!this.isAllowed() || !this.addon.configuration().autoCast().get() || !event.manual()) { + return; + } + + if (!this.addon.configuration().enableDelay().get()) { + retraction.run(); + return; + } + + int delay; + if (this.addon.configuration().randomDelay().get()) { + delay = random.nextInt(1, 6); + } else { + delay = this.addon.configuration().delay().get(); } + + Task.builder(retraction).delay(delay, TimeUnit.SECONDS).build().execute(); + } + + @SuppressWarnings("all") + private boolean isAllowed() { + return Laby.labyAPI().permissionRegistry().isPermissionEnabled(AutoFisherAddon.PERMISSION); } } From af007941e384818d0fcbb23f1a742101ff61b2ea Mon Sep 17 00:00:00 2001 From: RappyTV Date: Wed, 12 Nov 2025 19:20:58 +0100 Subject: [PATCH 10/12] feat: Add more version support --- build.gradle.kts | 2 +- .../v1_17_1/VersionedFishingController.java | 75 +++++++++++++++++++ .../v1_17_1/mixins/MixinFishingHook.java | 52 +++++++++++++ .../v1_18_2/VersionedFishingController.java | 75 +++++++++++++++++++ .../v1_18_2/mixins/MixinFishingHook.java | 52 +++++++++++++ .../v1_19_3/VersionedFishingController.java | 75 +++++++++++++++++++ .../v1_19_3/mixins/MixinFishingHook.java | 52 +++++++++++++ .../v1_19_4/VersionedFishingController.java | 75 +++++++++++++++++++ .../v1_19_4/mixins/MixinFishingHook.java | 52 +++++++++++++ .../v1_20_1/VersionedFishingController.java | 75 +++++++++++++++++++ .../v1_20_1/mixins/MixinFishingHook.java | 52 +++++++++++++ .../v1_20_2/VersionedFishingController.java | 75 +++++++++++++++++++ .../v1_20_2/mixins/MixinFishingHook.java | 52 +++++++++++++ .../v1_20_4/VersionedFishingController.java | 75 +++++++++++++++++++ .../v1_20_4/mixins/MixinFishingHook.java | 52 +++++++++++++ .../v1_20_5/VersionedFishingController.java | 75 +++++++++++++++++++ .../v1_20_5/mixins/MixinFishingHook.java | 52 +++++++++++++ .../v1_20_6/VersionedFishingController.java | 75 +++++++++++++++++++ .../v1_20_6/mixins/MixinFishingHook.java | 52 +++++++++++++ .../v1_21/VersionedFishingController.java | 75 +++++++++++++++++++ .../v1_21/mixins/MixinFishingHook.java | 52 +++++++++++++ .../v1_21_1/VersionedFishingController.java | 75 +++++++++++++++++++ .../v1_21_1/mixins/MixinFishingHook.java | 52 +++++++++++++ .../v1_21_10/VersionedFishingController.java | 75 +++++++++++++++++++ .../v1_21_10/mixins/MixinFishingHook.java | 52 +++++++++++++ .../v1_21_3/VersionedFishingController.java | 75 +++++++++++++++++++ .../v1_21_3/mixins/MixinFishingHook.java | 52 +++++++++++++ .../v1_21_4/VersionedFishingController.java | 75 +++++++++++++++++++ .../v1_21_4/mixins/MixinFishingHook.java | 52 +++++++++++++ .../v1_21_5/VersionedFishingController.java | 75 +++++++++++++++++++ .../v1_21_5/mixins/MixinFishingHook.java | 52 +++++++++++++ .../v1_21_8/VersionedFishingController.java | 75 +++++++++++++++++++ .../v1_21_8/mixins/MixinFishingHook.java | 52 +++++++++++++ 33 files changed, 2033 insertions(+), 1 deletion(-) create mode 100644 game-runner/src/v1_17_1/java/com/rappytv/autofisher/v1_17_1/VersionedFishingController.java create mode 100644 game-runner/src/v1_17_1/java/com/rappytv/autofisher/v1_17_1/mixins/MixinFishingHook.java create mode 100644 game-runner/src/v1_18_2/java/com/rappytv/autofisher/v1_18_2/VersionedFishingController.java create mode 100644 game-runner/src/v1_18_2/java/com/rappytv/autofisher/v1_18_2/mixins/MixinFishingHook.java create mode 100644 game-runner/src/v1_19_3/java/com/rappytv/autofisher/v1_19_3/VersionedFishingController.java create mode 100644 game-runner/src/v1_19_3/java/com/rappytv/autofisher/v1_19_3/mixins/MixinFishingHook.java create mode 100644 game-runner/src/v1_19_4/java/com/rappytv/autofisher/v1_19_4/VersionedFishingController.java create mode 100644 game-runner/src/v1_19_4/java/com/rappytv/autofisher/v1_19_4/mixins/MixinFishingHook.java create mode 100644 game-runner/src/v1_20_1/java/com/rappytv/autofisher/v1_20_1/VersionedFishingController.java create mode 100644 game-runner/src/v1_20_1/java/com/rappytv/autofisher/v1_20_1/mixins/MixinFishingHook.java create mode 100644 game-runner/src/v1_20_2/java/com/rappytv/autofisher/v1_20_2/VersionedFishingController.java create mode 100644 game-runner/src/v1_20_2/java/com/rappytv/autofisher/v1_20_2/mixins/MixinFishingHook.java create mode 100644 game-runner/src/v1_20_4/java/com/rappytv/autofisher/v1_20_4/VersionedFishingController.java create mode 100644 game-runner/src/v1_20_4/java/com/rappytv/autofisher/v1_20_4/mixins/MixinFishingHook.java create mode 100644 game-runner/src/v1_20_5/java/com/rappytv/autofisher/v1_20_5/VersionedFishingController.java create mode 100644 game-runner/src/v1_20_5/java/com/rappytv/autofisher/v1_20_5/mixins/MixinFishingHook.java create mode 100644 game-runner/src/v1_20_6/java/com/rappytv/autofisher/v1_20_6/VersionedFishingController.java create mode 100644 game-runner/src/v1_20_6/java/com/rappytv/autofisher/v1_20_6/mixins/MixinFishingHook.java create mode 100644 game-runner/src/v1_21/java/com/rappytv/autofisher/v1_21/VersionedFishingController.java create mode 100644 game-runner/src/v1_21/java/com/rappytv/autofisher/v1_21/mixins/MixinFishingHook.java create mode 100644 game-runner/src/v1_21_1/java/com/rappytv/autofisher/v1_21_1/VersionedFishingController.java create mode 100644 game-runner/src/v1_21_1/java/com/rappytv/autofisher/v1_21_1/mixins/MixinFishingHook.java create mode 100644 game-runner/src/v1_21_10/java/com/rappytv/autofisher/v1_21_10/VersionedFishingController.java create mode 100644 game-runner/src/v1_21_10/java/com/rappytv/autofisher/v1_21_10/mixins/MixinFishingHook.java create mode 100644 game-runner/src/v1_21_3/java/com/rappytv/autofisher/v1_21_3/VersionedFishingController.java create mode 100644 game-runner/src/v1_21_3/java/com/rappytv/autofisher/v1_21_3/mixins/MixinFishingHook.java create mode 100644 game-runner/src/v1_21_4/java/com/rappytv/autofisher/v1_21_4/VersionedFishingController.java create mode 100644 game-runner/src/v1_21_4/java/com/rappytv/autofisher/v1_21_4/mixins/MixinFishingHook.java create mode 100644 game-runner/src/v1_21_5/java/com/rappytv/autofisher/v1_21_5/VersionedFishingController.java create mode 100644 game-runner/src/v1_21_5/java/com/rappytv/autofisher/v1_21_5/mixins/MixinFishingHook.java create mode 100644 game-runner/src/v1_21_8/java/com/rappytv/autofisher/v1_21_8/VersionedFishingController.java create mode 100644 game-runner/src/v1_21_8/java/com/rappytv/autofisher/v1_21_8/mixins/MixinFishingHook.java diff --git a/build.gradle.kts b/build.gradle.kts index 51b3320..6b68168 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -16,7 +16,7 @@ labyMod { displayName = "AutoFisher" author = "RappyTV" description = "Completely automate the way your fishing rod works" - minecraftVersion = "1.19.2" + minecraftVersion = "1.17.1<1.21.10" version = rootProject.version.toString() } diff --git a/game-runner/src/v1_17_1/java/com/rappytv/autofisher/v1_17_1/VersionedFishingController.java b/game-runner/src/v1_17_1/java/com/rappytv/autofisher/v1_17_1/VersionedFishingController.java new file mode 100644 index 0000000..77d4e8b --- /dev/null +++ b/game-runner/src/v1_17_1/java/com/rappytv/autofisher/v1_17_1/VersionedFishingController.java @@ -0,0 +1,75 @@ +package com.rappytv.autofisher.v1_17_1; + +import com.rappytv.autofisher.FishingController; +import com.rappytv.autofisher.core.AutoFisherAddon; +import javax.inject.Singleton; +import net.labymod.api.models.Implements; +import net.minecraft.client.Minecraft; +import net.minecraft.client.multiplayer.MultiPlayerGameMode; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.entity.projectile.FishingHook; +import net.minecraft.world.item.FishingRodItem; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +@Singleton +@Implements(FishingController.class) +public class VersionedFishingController extends FishingController { + + @Override + public void castFishingRod() { + Player player = Minecraft.getInstance().player; + if (player == null) { + return; + } + InteractionHand hand = this.getFishingRodHand(player); + if (hand != null) { + this.useItem(hand); + } + } + + @Override + public void retractFishingRod() { + Player player = Minecraft.getInstance().player; + if (player == null) { + return; + } + FishingHook hook = player.fishing; + if (hook == null) { + return; + } + + InteractionHand hand = this.getFishingRodHand(player); + if (hand != null) { + AutoFisherAddon.fishingController().setManualRetraction(false); + this.useItem(hand); + } + } + + @Nullable + private InteractionHand getFishingRodHand(@NotNull Player player) { + if (player.getMainHandItem().getItem() instanceof FishingRodItem) { + return InteractionHand.MAIN_HAND; + } else if (player.getOffhandItem().getItem() instanceof FishingRodItem) { + return InteractionHand.OFF_HAND; + } + + return null; + } + + private void useItem(@NotNull InteractionHand hand) { + Player player = Minecraft.getInstance().player; + if (player == null) { + return; + } + + MultiPlayerGameMode gameMode = Minecraft.getInstance().gameMode; + if (gameMode == null) { + return; + } + + gameMode.useItem(player, player.level, hand); + player.swing(hand); + } +} diff --git a/game-runner/src/v1_17_1/java/com/rappytv/autofisher/v1_17_1/mixins/MixinFishingHook.java b/game-runner/src/v1_17_1/java/com/rappytv/autofisher/v1_17_1/mixins/MixinFishingHook.java new file mode 100644 index 0000000..49762d6 --- /dev/null +++ b/game-runner/src/v1_17_1/java/com/rappytv/autofisher/v1_17_1/mixins/MixinFishingHook.java @@ -0,0 +1,52 @@ +package com.rappytv.autofisher.v1_17_1.mixins; + +import com.rappytv.autofisher.core.AutoFisherAddon; +import com.rappytv.autofisher.event.FishHookBiteEvent; +import com.rappytv.autofisher.event.FishHookRetractEvent; +import net.labymod.api.Laby; +import net.minecraft.network.syncher.EntityDataAccessor; +import net.minecraft.world.entity.projectile.FishingHook; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.At.Shift; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(FishingHook.class) +public class MixinFishingHook { + + @Unique + private static final int PUTFIELD_OPCODE = 181; + + @Shadow + private boolean biting; + + @Inject( + method = "onClientRemoval", + at = @At("TAIL") + ) + private void onRemove(CallbackInfo ci) { + boolean manual = AutoFisherAddon.fishingController().isManualRetraction(); + Laby.fireEvent(new FishHookRetractEvent(manual)); + if (!manual) { + AutoFisherAddon.fishingController().setManualRetraction(true); + } + } + + @Inject( + method = "onSyncedDataUpdated", + at = @At( + value = "FIELD", + target = "Lnet/minecraft/world/entity/projectile/FishingHook;biting:Z", + opcode = PUTFIELD_OPCODE, + shift = Shift.AFTER + ) + ) + private void onFishBiting(EntityDataAccessor $$0, CallbackInfo ci) { + if (this.biting) { + Laby.fireEvent(new FishHookBiteEvent()); + } + } +} diff --git a/game-runner/src/v1_18_2/java/com/rappytv/autofisher/v1_18_2/VersionedFishingController.java b/game-runner/src/v1_18_2/java/com/rappytv/autofisher/v1_18_2/VersionedFishingController.java new file mode 100644 index 0000000..80e4853 --- /dev/null +++ b/game-runner/src/v1_18_2/java/com/rappytv/autofisher/v1_18_2/VersionedFishingController.java @@ -0,0 +1,75 @@ +package com.rappytv.autofisher.v1_18_2; + +import com.rappytv.autofisher.FishingController; +import com.rappytv.autofisher.core.AutoFisherAddon; +import javax.inject.Singleton; +import net.labymod.api.models.Implements; +import net.minecraft.client.Minecraft; +import net.minecraft.client.multiplayer.MultiPlayerGameMode; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.entity.projectile.FishingHook; +import net.minecraft.world.item.FishingRodItem; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +@Singleton +@Implements(FishingController.class) +public class VersionedFishingController extends FishingController { + + @Override + public void castFishingRod() { + Player player = Minecraft.getInstance().player; + if (player == null) { + return; + } + InteractionHand hand = this.getFishingRodHand(player); + if (hand != null) { + this.useItem(hand); + } + } + + @Override + public void retractFishingRod() { + Player player = Minecraft.getInstance().player; + if (player == null) { + return; + } + FishingHook hook = player.fishing; + if (hook == null) { + return; + } + + InteractionHand hand = this.getFishingRodHand(player); + if (hand != null) { + AutoFisherAddon.fishingController().setManualRetraction(false); + this.useItem(hand); + } + } + + @Nullable + private InteractionHand getFishingRodHand(@NotNull Player player) { + if (player.getMainHandItem().getItem() instanceof FishingRodItem) { + return InteractionHand.MAIN_HAND; + } else if (player.getOffhandItem().getItem() instanceof FishingRodItem) { + return InteractionHand.OFF_HAND; + } + + return null; + } + + private void useItem(@NotNull InteractionHand hand) { + Player player = Minecraft.getInstance().player; + if (player == null) { + return; + } + + MultiPlayerGameMode gameMode = Minecraft.getInstance().gameMode; + if (gameMode == null) { + return; + } + + gameMode.useItem(player, player.level, hand); + player.swing(hand); + } +} diff --git a/game-runner/src/v1_18_2/java/com/rappytv/autofisher/v1_18_2/mixins/MixinFishingHook.java b/game-runner/src/v1_18_2/java/com/rappytv/autofisher/v1_18_2/mixins/MixinFishingHook.java new file mode 100644 index 0000000..1819ebc --- /dev/null +++ b/game-runner/src/v1_18_2/java/com/rappytv/autofisher/v1_18_2/mixins/MixinFishingHook.java @@ -0,0 +1,52 @@ +package com.rappytv.autofisher.v1_18_2.mixins; + +import com.rappytv.autofisher.core.AutoFisherAddon; +import com.rappytv.autofisher.event.FishHookBiteEvent; +import com.rappytv.autofisher.event.FishHookRetractEvent; +import net.labymod.api.Laby; +import net.minecraft.network.syncher.EntityDataAccessor; +import net.minecraft.world.entity.projectile.FishingHook; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.At.Shift; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(FishingHook.class) +public class MixinFishingHook { + + @Unique + private static final int PUTFIELD_OPCODE = 181; + + @Shadow + private boolean biting; + + @Inject( + method = "onClientRemoval", + at = @At("TAIL") + ) + private void onRemove(CallbackInfo ci) { + boolean manual = AutoFisherAddon.fishingController().isManualRetraction(); + Laby.fireEvent(new FishHookRetractEvent(manual)); + if (!manual) { + AutoFisherAddon.fishingController().setManualRetraction(true); + } + } + + @Inject( + method = "onSyncedDataUpdated", + at = @At( + value = "FIELD", + target = "Lnet/minecraft/world/entity/projectile/FishingHook;biting:Z", + opcode = PUTFIELD_OPCODE, + shift = Shift.AFTER + ) + ) + private void onFishBiting(EntityDataAccessor $$0, CallbackInfo ci) { + if (this.biting) { + Laby.fireEvent(new FishHookBiteEvent()); + } + } +} diff --git a/game-runner/src/v1_19_3/java/com/rappytv/autofisher/v1_19_3/VersionedFishingController.java b/game-runner/src/v1_19_3/java/com/rappytv/autofisher/v1_19_3/VersionedFishingController.java new file mode 100644 index 0000000..874e5f2 --- /dev/null +++ b/game-runner/src/v1_19_3/java/com/rappytv/autofisher/v1_19_3/VersionedFishingController.java @@ -0,0 +1,75 @@ +package com.rappytv.autofisher.v1_19_3; + +import com.rappytv.autofisher.FishingController; +import com.rappytv.autofisher.core.AutoFisherAddon; +import javax.inject.Singleton; +import net.labymod.api.models.Implements; +import net.minecraft.client.Minecraft; +import net.minecraft.client.multiplayer.MultiPlayerGameMode; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.entity.projectile.FishingHook; +import net.minecraft.world.item.FishingRodItem; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +@Singleton +@Implements(FishingController.class) +public class VersionedFishingController extends FishingController { + + @Override + public void castFishingRod() { + Player player = Minecraft.getInstance().player; + if (player == null) { + return; + } + InteractionHand hand = this.getFishingRodHand(player); + if (hand != null) { + this.useItem(hand); + } + } + + @Override + public void retractFishingRod() { + Player player = Minecraft.getInstance().player; + if (player == null) { + return; + } + FishingHook hook = player.fishing; + if (hook == null) { + return; + } + + InteractionHand hand = this.getFishingRodHand(player); + if (hand != null) { + AutoFisherAddon.fishingController().setManualRetraction(false); + this.useItem(hand); + } + } + + @Nullable + private InteractionHand getFishingRodHand(@NotNull Player player) { + if (player.getMainHandItem().getItem() instanceof FishingRodItem) { + return InteractionHand.MAIN_HAND; + } else if (player.getOffhandItem().getItem() instanceof FishingRodItem) { + return InteractionHand.OFF_HAND; + } + + return null; + } + + private void useItem(@NotNull InteractionHand hand) { + Player player = Minecraft.getInstance().player; + if (player == null) { + return; + } + + MultiPlayerGameMode gameMode = Minecraft.getInstance().gameMode; + if (gameMode == null) { + return; + } + + gameMode.useItem(player, hand); + player.swing(hand); + } +} diff --git a/game-runner/src/v1_19_3/java/com/rappytv/autofisher/v1_19_3/mixins/MixinFishingHook.java b/game-runner/src/v1_19_3/java/com/rappytv/autofisher/v1_19_3/mixins/MixinFishingHook.java new file mode 100644 index 0000000..d9fbda5 --- /dev/null +++ b/game-runner/src/v1_19_3/java/com/rappytv/autofisher/v1_19_3/mixins/MixinFishingHook.java @@ -0,0 +1,52 @@ +package com.rappytv.autofisher.v1_19_3.mixins; + +import com.rappytv.autofisher.core.AutoFisherAddon; +import com.rappytv.autofisher.event.FishHookBiteEvent; +import com.rappytv.autofisher.event.FishHookRetractEvent; +import net.labymod.api.Laby; +import net.minecraft.network.syncher.EntityDataAccessor; +import net.minecraft.world.entity.projectile.FishingHook; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.At.Shift; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(FishingHook.class) +public class MixinFishingHook { + + @Unique + private static final int PUTFIELD_OPCODE = 181; + + @Shadow + private boolean biting; + + @Inject( + method = "onClientRemoval", + at = @At("TAIL") + ) + private void onRemove(CallbackInfo ci) { + boolean manual = AutoFisherAddon.fishingController().isManualRetraction(); + Laby.fireEvent(new FishHookRetractEvent(manual)); + if (!manual) { + AutoFisherAddon.fishingController().setManualRetraction(true); + } + } + + @Inject( + method = "onSyncedDataUpdated", + at = @At( + value = "FIELD", + target = "Lnet/minecraft/world/entity/projectile/FishingHook;biting:Z", + opcode = PUTFIELD_OPCODE, + shift = Shift.AFTER + ) + ) + private void onFishBiting(EntityDataAccessor $$0, CallbackInfo ci) { + if (this.biting) { + Laby.fireEvent(new FishHookBiteEvent()); + } + } +} diff --git a/game-runner/src/v1_19_4/java/com/rappytv/autofisher/v1_19_4/VersionedFishingController.java b/game-runner/src/v1_19_4/java/com/rappytv/autofisher/v1_19_4/VersionedFishingController.java new file mode 100644 index 0000000..4213da5 --- /dev/null +++ b/game-runner/src/v1_19_4/java/com/rappytv/autofisher/v1_19_4/VersionedFishingController.java @@ -0,0 +1,75 @@ +package com.rappytv.autofisher.v1_19_4; + +import com.rappytv.autofisher.FishingController; +import com.rappytv.autofisher.core.AutoFisherAddon; +import javax.inject.Singleton; +import net.labymod.api.models.Implements; +import net.minecraft.client.Minecraft; +import net.minecraft.client.multiplayer.MultiPlayerGameMode; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.entity.projectile.FishingHook; +import net.minecraft.world.item.FishingRodItem; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +@Singleton +@Implements(FishingController.class) +public class VersionedFishingController extends FishingController { + + @Override + public void castFishingRod() { + Player player = Minecraft.getInstance().player; + if (player == null) { + return; + } + InteractionHand hand = this.getFishingRodHand(player); + if (hand != null) { + this.useItem(hand); + } + } + + @Override + public void retractFishingRod() { + Player player = Minecraft.getInstance().player; + if (player == null) { + return; + } + FishingHook hook = player.fishing; + if (hook == null) { + return; + } + + InteractionHand hand = this.getFishingRodHand(player); + if (hand != null) { + AutoFisherAddon.fishingController().setManualRetraction(false); + this.useItem(hand); + } + } + + @Nullable + private InteractionHand getFishingRodHand(@NotNull Player player) { + if (player.getMainHandItem().getItem() instanceof FishingRodItem) { + return InteractionHand.MAIN_HAND; + } else if (player.getOffhandItem().getItem() instanceof FishingRodItem) { + return InteractionHand.OFF_HAND; + } + + return null; + } + + private void useItem(@NotNull InteractionHand hand) { + Player player = Minecraft.getInstance().player; + if (player == null) { + return; + } + + MultiPlayerGameMode gameMode = Minecraft.getInstance().gameMode; + if (gameMode == null) { + return; + } + + gameMode.useItem(player, hand); + player.swing(hand); + } +} diff --git a/game-runner/src/v1_19_4/java/com/rappytv/autofisher/v1_19_4/mixins/MixinFishingHook.java b/game-runner/src/v1_19_4/java/com/rappytv/autofisher/v1_19_4/mixins/MixinFishingHook.java new file mode 100644 index 0000000..82b2833 --- /dev/null +++ b/game-runner/src/v1_19_4/java/com/rappytv/autofisher/v1_19_4/mixins/MixinFishingHook.java @@ -0,0 +1,52 @@ +package com.rappytv.autofisher.v1_19_4.mixins; + +import com.rappytv.autofisher.core.AutoFisherAddon; +import com.rappytv.autofisher.event.FishHookBiteEvent; +import com.rappytv.autofisher.event.FishHookRetractEvent; +import net.labymod.api.Laby; +import net.minecraft.network.syncher.EntityDataAccessor; +import net.minecraft.world.entity.projectile.FishingHook; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.At.Shift; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(FishingHook.class) +public class MixinFishingHook { + + @Unique + private static final int PUTFIELD_OPCODE = 181; + + @Shadow + private boolean biting; + + @Inject( + method = "onClientRemoval", + at = @At("TAIL") + ) + private void onRemove(CallbackInfo ci) { + boolean manual = AutoFisherAddon.fishingController().isManualRetraction(); + Laby.fireEvent(new FishHookRetractEvent(manual)); + if (!manual) { + AutoFisherAddon.fishingController().setManualRetraction(true); + } + } + + @Inject( + method = "onSyncedDataUpdated", + at = @At( + value = "FIELD", + target = "Lnet/minecraft/world/entity/projectile/FishingHook;biting:Z", + opcode = PUTFIELD_OPCODE, + shift = Shift.AFTER + ) + ) + private void onFishBiting(EntityDataAccessor $$0, CallbackInfo ci) { + if (this.biting) { + Laby.fireEvent(new FishHookBiteEvent()); + } + } +} diff --git a/game-runner/src/v1_20_1/java/com/rappytv/autofisher/v1_20_1/VersionedFishingController.java b/game-runner/src/v1_20_1/java/com/rappytv/autofisher/v1_20_1/VersionedFishingController.java new file mode 100644 index 0000000..5e92de3 --- /dev/null +++ b/game-runner/src/v1_20_1/java/com/rappytv/autofisher/v1_20_1/VersionedFishingController.java @@ -0,0 +1,75 @@ +package com.rappytv.autofisher.v1_20_1; + +import com.rappytv.autofisher.FishingController; +import com.rappytv.autofisher.core.AutoFisherAddon; +import javax.inject.Singleton; +import net.labymod.api.models.Implements; +import net.minecraft.client.Minecraft; +import net.minecraft.client.multiplayer.MultiPlayerGameMode; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.entity.projectile.FishingHook; +import net.minecraft.world.item.FishingRodItem; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +@Singleton +@Implements(FishingController.class) +public class VersionedFishingController extends FishingController { + + @Override + public void castFishingRod() { + Player player = Minecraft.getInstance().player; + if (player == null) { + return; + } + InteractionHand hand = this.getFishingRodHand(player); + if (hand != null) { + this.useItem(hand); + } + } + + @Override + public void retractFishingRod() { + Player player = Minecraft.getInstance().player; + if (player == null) { + return; + } + FishingHook hook = player.fishing; + if (hook == null) { + return; + } + + InteractionHand hand = this.getFishingRodHand(player); + if (hand != null) { + AutoFisherAddon.fishingController().setManualRetraction(false); + this.useItem(hand); + } + } + + @Nullable + private InteractionHand getFishingRodHand(@NotNull Player player) { + if (player.getMainHandItem().getItem() instanceof FishingRodItem) { + return InteractionHand.MAIN_HAND; + } else if (player.getOffhandItem().getItem() instanceof FishingRodItem) { + return InteractionHand.OFF_HAND; + } + + return null; + } + + private void useItem(@NotNull InteractionHand hand) { + Player player = Minecraft.getInstance().player; + if (player == null) { + return; + } + + MultiPlayerGameMode gameMode = Minecraft.getInstance().gameMode; + if (gameMode == null) { + return; + } + + gameMode.useItem(player, hand); + player.swing(hand); + } +} diff --git a/game-runner/src/v1_20_1/java/com/rappytv/autofisher/v1_20_1/mixins/MixinFishingHook.java b/game-runner/src/v1_20_1/java/com/rappytv/autofisher/v1_20_1/mixins/MixinFishingHook.java new file mode 100644 index 0000000..b401fa1 --- /dev/null +++ b/game-runner/src/v1_20_1/java/com/rappytv/autofisher/v1_20_1/mixins/MixinFishingHook.java @@ -0,0 +1,52 @@ +package com.rappytv.autofisher.v1_20_1.mixins; + +import com.rappytv.autofisher.core.AutoFisherAddon; +import com.rappytv.autofisher.event.FishHookBiteEvent; +import com.rappytv.autofisher.event.FishHookRetractEvent; +import net.labymod.api.Laby; +import net.minecraft.network.syncher.EntityDataAccessor; +import net.minecraft.world.entity.projectile.FishingHook; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.At.Shift; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(FishingHook.class) +public class MixinFishingHook { + + @Unique + private static final int PUTFIELD_OPCODE = 181; + + @Shadow + private boolean biting; + + @Inject( + method = "onClientRemoval", + at = @At("TAIL") + ) + private void onRemove(CallbackInfo ci) { + boolean manual = AutoFisherAddon.fishingController().isManualRetraction(); + Laby.fireEvent(new FishHookRetractEvent(manual)); + if (!manual) { + AutoFisherAddon.fishingController().setManualRetraction(true); + } + } + + @Inject( + method = "onSyncedDataUpdated", + at = @At( + value = "FIELD", + target = "Lnet/minecraft/world/entity/projectile/FishingHook;biting:Z", + opcode = PUTFIELD_OPCODE, + shift = Shift.AFTER + ) + ) + private void onFishBiting(EntityDataAccessor $$0, CallbackInfo ci) { + if (this.biting) { + Laby.fireEvent(new FishHookBiteEvent()); + } + } +} diff --git a/game-runner/src/v1_20_2/java/com/rappytv/autofisher/v1_20_2/VersionedFishingController.java b/game-runner/src/v1_20_2/java/com/rappytv/autofisher/v1_20_2/VersionedFishingController.java new file mode 100644 index 0000000..bc3ac24 --- /dev/null +++ b/game-runner/src/v1_20_2/java/com/rappytv/autofisher/v1_20_2/VersionedFishingController.java @@ -0,0 +1,75 @@ +package com.rappytv.autofisher.v1_20_2; + +import com.rappytv.autofisher.FishingController; +import com.rappytv.autofisher.core.AutoFisherAddon; +import javax.inject.Singleton; +import net.labymod.api.models.Implements; +import net.minecraft.client.Minecraft; +import net.minecraft.client.multiplayer.MultiPlayerGameMode; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.entity.projectile.FishingHook; +import net.minecraft.world.item.FishingRodItem; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +@Singleton +@Implements(FishingController.class) +public class VersionedFishingController extends FishingController { + + @Override + public void castFishingRod() { + Player player = Minecraft.getInstance().player; + if (player == null) { + return; + } + InteractionHand hand = this.getFishingRodHand(player); + if (hand != null) { + this.useItem(hand); + } + } + + @Override + public void retractFishingRod() { + Player player = Minecraft.getInstance().player; + if (player == null) { + return; + } + FishingHook hook = player.fishing; + if (hook == null) { + return; + } + + InteractionHand hand = this.getFishingRodHand(player); + if (hand != null) { + AutoFisherAddon.fishingController().setManualRetraction(false); + this.useItem(hand); + } + } + + @Nullable + private InteractionHand getFishingRodHand(@NotNull Player player) { + if (player.getMainHandItem().getItem() instanceof FishingRodItem) { + return InteractionHand.MAIN_HAND; + } else if (player.getOffhandItem().getItem() instanceof FishingRodItem) { + return InteractionHand.OFF_HAND; + } + + return null; + } + + private void useItem(@NotNull InteractionHand hand) { + Player player = Minecraft.getInstance().player; + if (player == null) { + return; + } + + MultiPlayerGameMode gameMode = Minecraft.getInstance().gameMode; + if (gameMode == null) { + return; + } + + gameMode.useItem(player, hand); + player.swing(hand); + } +} diff --git a/game-runner/src/v1_20_2/java/com/rappytv/autofisher/v1_20_2/mixins/MixinFishingHook.java b/game-runner/src/v1_20_2/java/com/rappytv/autofisher/v1_20_2/mixins/MixinFishingHook.java new file mode 100644 index 0000000..7346764 --- /dev/null +++ b/game-runner/src/v1_20_2/java/com/rappytv/autofisher/v1_20_2/mixins/MixinFishingHook.java @@ -0,0 +1,52 @@ +package com.rappytv.autofisher.v1_20_2.mixins; + +import com.rappytv.autofisher.core.AutoFisherAddon; +import com.rappytv.autofisher.event.FishHookBiteEvent; +import com.rappytv.autofisher.event.FishHookRetractEvent; +import net.labymod.api.Laby; +import net.minecraft.network.syncher.EntityDataAccessor; +import net.minecraft.world.entity.projectile.FishingHook; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.At.Shift; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(FishingHook.class) +public class MixinFishingHook { + + @Unique + private static final int PUTFIELD_OPCODE = 181; + + @Shadow + private boolean biting; + + @Inject( + method = "onClientRemoval", + at = @At("TAIL") + ) + private void onRemove(CallbackInfo ci) { + boolean manual = AutoFisherAddon.fishingController().isManualRetraction(); + Laby.fireEvent(new FishHookRetractEvent(manual)); + if (!manual) { + AutoFisherAddon.fishingController().setManualRetraction(true); + } + } + + @Inject( + method = "onSyncedDataUpdated", + at = @At( + value = "FIELD", + target = "Lnet/minecraft/world/entity/projectile/FishingHook;biting:Z", + opcode = PUTFIELD_OPCODE, + shift = Shift.AFTER + ) + ) + private void onFishBiting(EntityDataAccessor $$0, CallbackInfo ci) { + if (this.biting) { + Laby.fireEvent(new FishHookBiteEvent()); + } + } +} diff --git a/game-runner/src/v1_20_4/java/com/rappytv/autofisher/v1_20_4/VersionedFishingController.java b/game-runner/src/v1_20_4/java/com/rappytv/autofisher/v1_20_4/VersionedFishingController.java new file mode 100644 index 0000000..c545e32 --- /dev/null +++ b/game-runner/src/v1_20_4/java/com/rappytv/autofisher/v1_20_4/VersionedFishingController.java @@ -0,0 +1,75 @@ +package com.rappytv.autofisher.v1_20_4; + +import com.rappytv.autofisher.FishingController; +import com.rappytv.autofisher.core.AutoFisherAddon; +import javax.inject.Singleton; +import net.labymod.api.models.Implements; +import net.minecraft.client.Minecraft; +import net.minecraft.client.multiplayer.MultiPlayerGameMode; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.entity.projectile.FishingHook; +import net.minecraft.world.item.FishingRodItem; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +@Singleton +@Implements(FishingController.class) +public class VersionedFishingController extends FishingController { + + @Override + public void castFishingRod() { + Player player = Minecraft.getInstance().player; + if (player == null) { + return; + } + InteractionHand hand = this.getFishingRodHand(player); + if (hand != null) { + this.useItem(hand); + } + } + + @Override + public void retractFishingRod() { + Player player = Minecraft.getInstance().player; + if (player == null) { + return; + } + FishingHook hook = player.fishing; + if (hook == null) { + return; + } + + InteractionHand hand = this.getFishingRodHand(player); + if (hand != null) { + AutoFisherAddon.fishingController().setManualRetraction(false); + this.useItem(hand); + } + } + + @Nullable + private InteractionHand getFishingRodHand(@NotNull Player player) { + if (player.getMainHandItem().getItem() instanceof FishingRodItem) { + return InteractionHand.MAIN_HAND; + } else if (player.getOffhandItem().getItem() instanceof FishingRodItem) { + return InteractionHand.OFF_HAND; + } + + return null; + } + + private void useItem(@NotNull InteractionHand hand) { + Player player = Minecraft.getInstance().player; + if (player == null) { + return; + } + + MultiPlayerGameMode gameMode = Minecraft.getInstance().gameMode; + if (gameMode == null) { + return; + } + + gameMode.useItem(player, hand); + player.swing(hand); + } +} diff --git a/game-runner/src/v1_20_4/java/com/rappytv/autofisher/v1_20_4/mixins/MixinFishingHook.java b/game-runner/src/v1_20_4/java/com/rappytv/autofisher/v1_20_4/mixins/MixinFishingHook.java new file mode 100644 index 0000000..3d167d2 --- /dev/null +++ b/game-runner/src/v1_20_4/java/com/rappytv/autofisher/v1_20_4/mixins/MixinFishingHook.java @@ -0,0 +1,52 @@ +package com.rappytv.autofisher.v1_20_4.mixins; + +import com.rappytv.autofisher.core.AutoFisherAddon; +import com.rappytv.autofisher.event.FishHookBiteEvent; +import com.rappytv.autofisher.event.FishHookRetractEvent; +import net.labymod.api.Laby; +import net.minecraft.network.syncher.EntityDataAccessor; +import net.minecraft.world.entity.projectile.FishingHook; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.At.Shift; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(FishingHook.class) +public class MixinFishingHook { + + @Unique + private static final int PUTFIELD_OPCODE = 181; + + @Shadow + private boolean biting; + + @Inject( + method = "onClientRemoval", + at = @At("TAIL") + ) + private void onRemove(CallbackInfo ci) { + boolean manual = AutoFisherAddon.fishingController().isManualRetraction(); + Laby.fireEvent(new FishHookRetractEvent(manual)); + if (!manual) { + AutoFisherAddon.fishingController().setManualRetraction(true); + } + } + + @Inject( + method = "onSyncedDataUpdated", + at = @At( + value = "FIELD", + target = "Lnet/minecraft/world/entity/projectile/FishingHook;biting:Z", + opcode = PUTFIELD_OPCODE, + shift = Shift.AFTER + ) + ) + private void onFishBiting(EntityDataAccessor $$0, CallbackInfo ci) { + if (this.biting) { + Laby.fireEvent(new FishHookBiteEvent()); + } + } +} diff --git a/game-runner/src/v1_20_5/java/com/rappytv/autofisher/v1_20_5/VersionedFishingController.java b/game-runner/src/v1_20_5/java/com/rappytv/autofisher/v1_20_5/VersionedFishingController.java new file mode 100644 index 0000000..811aad2 --- /dev/null +++ b/game-runner/src/v1_20_5/java/com/rappytv/autofisher/v1_20_5/VersionedFishingController.java @@ -0,0 +1,75 @@ +package com.rappytv.autofisher.v1_20_5; + +import com.rappytv.autofisher.FishingController; +import com.rappytv.autofisher.core.AutoFisherAddon; +import javax.inject.Singleton; +import net.labymod.api.models.Implements; +import net.minecraft.client.Minecraft; +import net.minecraft.client.multiplayer.MultiPlayerGameMode; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.entity.projectile.FishingHook; +import net.minecraft.world.item.FishingRodItem; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +@Singleton +@Implements(FishingController.class) +public class VersionedFishingController extends FishingController { + + @Override + public void castFishingRod() { + Player player = Minecraft.getInstance().player; + if (player == null) { + return; + } + InteractionHand hand = this.getFishingRodHand(player); + if (hand != null) { + this.useItem(hand); + } + } + + @Override + public void retractFishingRod() { + Player player = Minecraft.getInstance().player; + if (player == null) { + return; + } + FishingHook hook = player.fishing; + if (hook == null) { + return; + } + + InteractionHand hand = this.getFishingRodHand(player); + if (hand != null) { + AutoFisherAddon.fishingController().setManualRetraction(false); + this.useItem(hand); + } + } + + @Nullable + private InteractionHand getFishingRodHand(@NotNull Player player) { + if (player.getMainHandItem().getItem() instanceof FishingRodItem) { + return InteractionHand.MAIN_HAND; + } else if (player.getOffhandItem().getItem() instanceof FishingRodItem) { + return InteractionHand.OFF_HAND; + } + + return null; + } + + private void useItem(@NotNull InteractionHand hand) { + Player player = Minecraft.getInstance().player; + if (player == null) { + return; + } + + MultiPlayerGameMode gameMode = Minecraft.getInstance().gameMode; + if (gameMode == null) { + return; + } + + gameMode.useItem(player, hand); + player.swing(hand); + } +} diff --git a/game-runner/src/v1_20_5/java/com/rappytv/autofisher/v1_20_5/mixins/MixinFishingHook.java b/game-runner/src/v1_20_5/java/com/rappytv/autofisher/v1_20_5/mixins/MixinFishingHook.java new file mode 100644 index 0000000..b0606ea --- /dev/null +++ b/game-runner/src/v1_20_5/java/com/rappytv/autofisher/v1_20_5/mixins/MixinFishingHook.java @@ -0,0 +1,52 @@ +package com.rappytv.autofisher.v1_20_5.mixins; + +import com.rappytv.autofisher.core.AutoFisherAddon; +import com.rappytv.autofisher.event.FishHookBiteEvent; +import com.rappytv.autofisher.event.FishHookRetractEvent; +import net.labymod.api.Laby; +import net.minecraft.network.syncher.EntityDataAccessor; +import net.minecraft.world.entity.projectile.FishingHook; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.At.Shift; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(FishingHook.class) +public class MixinFishingHook { + + @Unique + private static final int PUTFIELD_OPCODE = 181; + + @Shadow + private boolean biting; + + @Inject( + method = "onClientRemoval", + at = @At("TAIL") + ) + private void onRemove(CallbackInfo ci) { + boolean manual = AutoFisherAddon.fishingController().isManualRetraction(); + Laby.fireEvent(new FishHookRetractEvent(manual)); + if (!manual) { + AutoFisherAddon.fishingController().setManualRetraction(true); + } + } + + @Inject( + method = "onSyncedDataUpdated", + at = @At( + value = "FIELD", + target = "Lnet/minecraft/world/entity/projectile/FishingHook;biting:Z", + opcode = PUTFIELD_OPCODE, + shift = Shift.AFTER + ) + ) + private void onFishBiting(EntityDataAccessor $$0, CallbackInfo ci) { + if (this.biting) { + Laby.fireEvent(new FishHookBiteEvent()); + } + } +} diff --git a/game-runner/src/v1_20_6/java/com/rappytv/autofisher/v1_20_6/VersionedFishingController.java b/game-runner/src/v1_20_6/java/com/rappytv/autofisher/v1_20_6/VersionedFishingController.java new file mode 100644 index 0000000..0dbea09 --- /dev/null +++ b/game-runner/src/v1_20_6/java/com/rappytv/autofisher/v1_20_6/VersionedFishingController.java @@ -0,0 +1,75 @@ +package com.rappytv.autofisher.v1_20_6; + +import com.rappytv.autofisher.FishingController; +import com.rappytv.autofisher.core.AutoFisherAddon; +import javax.inject.Singleton; +import net.labymod.api.models.Implements; +import net.minecraft.client.Minecraft; +import net.minecraft.client.multiplayer.MultiPlayerGameMode; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.entity.projectile.FishingHook; +import net.minecraft.world.item.FishingRodItem; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +@Singleton +@Implements(FishingController.class) +public class VersionedFishingController extends FishingController { + + @Override + public void castFishingRod() { + Player player = Minecraft.getInstance().player; + if (player == null) { + return; + } + InteractionHand hand = this.getFishingRodHand(player); + if (hand != null) { + this.useItem(hand); + } + } + + @Override + public void retractFishingRod() { + Player player = Minecraft.getInstance().player; + if (player == null) { + return; + } + FishingHook hook = player.fishing; + if (hook == null) { + return; + } + + InteractionHand hand = this.getFishingRodHand(player); + if (hand != null) { + AutoFisherAddon.fishingController().setManualRetraction(false); + this.useItem(hand); + } + } + + @Nullable + private InteractionHand getFishingRodHand(@NotNull Player player) { + if (player.getMainHandItem().getItem() instanceof FishingRodItem) { + return InteractionHand.MAIN_HAND; + } else if (player.getOffhandItem().getItem() instanceof FishingRodItem) { + return InteractionHand.OFF_HAND; + } + + return null; + } + + private void useItem(@NotNull InteractionHand hand) { + Player player = Minecraft.getInstance().player; + if (player == null) { + return; + } + + MultiPlayerGameMode gameMode = Minecraft.getInstance().gameMode; + if (gameMode == null) { + return; + } + + gameMode.useItem(player, hand); + player.swing(hand); + } +} diff --git a/game-runner/src/v1_20_6/java/com/rappytv/autofisher/v1_20_6/mixins/MixinFishingHook.java b/game-runner/src/v1_20_6/java/com/rappytv/autofisher/v1_20_6/mixins/MixinFishingHook.java new file mode 100644 index 0000000..c01713b --- /dev/null +++ b/game-runner/src/v1_20_6/java/com/rappytv/autofisher/v1_20_6/mixins/MixinFishingHook.java @@ -0,0 +1,52 @@ +package com.rappytv.autofisher.v1_20_6.mixins; + +import com.rappytv.autofisher.core.AutoFisherAddon; +import com.rappytv.autofisher.event.FishHookBiteEvent; +import com.rappytv.autofisher.event.FishHookRetractEvent; +import net.labymod.api.Laby; +import net.minecraft.network.syncher.EntityDataAccessor; +import net.minecraft.world.entity.projectile.FishingHook; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.At.Shift; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(FishingHook.class) +public class MixinFishingHook { + + @Unique + private static final int PUTFIELD_OPCODE = 181; + + @Shadow + private boolean biting; + + @Inject( + method = "onClientRemoval", + at = @At("TAIL") + ) + private void onRemove(CallbackInfo ci) { + boolean manual = AutoFisherAddon.fishingController().isManualRetraction(); + Laby.fireEvent(new FishHookRetractEvent(manual)); + if (!manual) { + AutoFisherAddon.fishingController().setManualRetraction(true); + } + } + + @Inject( + method = "onSyncedDataUpdated", + at = @At( + value = "FIELD", + target = "Lnet/minecraft/world/entity/projectile/FishingHook;biting:Z", + opcode = PUTFIELD_OPCODE, + shift = Shift.AFTER + ) + ) + private void onFishBiting(EntityDataAccessor $$0, CallbackInfo ci) { + if (this.biting) { + Laby.fireEvent(new FishHookBiteEvent()); + } + } +} diff --git a/game-runner/src/v1_21/java/com/rappytv/autofisher/v1_21/VersionedFishingController.java b/game-runner/src/v1_21/java/com/rappytv/autofisher/v1_21/VersionedFishingController.java new file mode 100644 index 0000000..fee1825 --- /dev/null +++ b/game-runner/src/v1_21/java/com/rappytv/autofisher/v1_21/VersionedFishingController.java @@ -0,0 +1,75 @@ +package com.rappytv.autofisher.v1_21; + +import com.rappytv.autofisher.FishingController; +import com.rappytv.autofisher.core.AutoFisherAddon; +import javax.inject.Singleton; +import net.labymod.api.models.Implements; +import net.minecraft.client.Minecraft; +import net.minecraft.client.multiplayer.MultiPlayerGameMode; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.entity.projectile.FishingHook; +import net.minecraft.world.item.FishingRodItem; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +@Singleton +@Implements(FishingController.class) +public class VersionedFishingController extends FishingController { + + @Override + public void castFishingRod() { + Player player = Minecraft.getInstance().player; + if (player == null) { + return; + } + InteractionHand hand = this.getFishingRodHand(player); + if (hand != null) { + this.useItem(hand); + } + } + + @Override + public void retractFishingRod() { + Player player = Minecraft.getInstance().player; + if (player == null) { + return; + } + FishingHook hook = player.fishing; + if (hook == null) { + return; + } + + InteractionHand hand = this.getFishingRodHand(player); + if (hand != null) { + AutoFisherAddon.fishingController().setManualRetraction(false); + this.useItem(hand); + } + } + + @Nullable + private InteractionHand getFishingRodHand(@NotNull Player player) { + if (player.getMainHandItem().getItem() instanceof FishingRodItem) { + return InteractionHand.MAIN_HAND; + } else if (player.getOffhandItem().getItem() instanceof FishingRodItem) { + return InteractionHand.OFF_HAND; + } + + return null; + } + + private void useItem(@NotNull InteractionHand hand) { + Player player = Minecraft.getInstance().player; + if (player == null) { + return; + } + + MultiPlayerGameMode gameMode = Minecraft.getInstance().gameMode; + if (gameMode == null) { + return; + } + + gameMode.useItem(player, hand); + player.swing(hand); + } +} diff --git a/game-runner/src/v1_21/java/com/rappytv/autofisher/v1_21/mixins/MixinFishingHook.java b/game-runner/src/v1_21/java/com/rappytv/autofisher/v1_21/mixins/MixinFishingHook.java new file mode 100644 index 0000000..6ad208a --- /dev/null +++ b/game-runner/src/v1_21/java/com/rappytv/autofisher/v1_21/mixins/MixinFishingHook.java @@ -0,0 +1,52 @@ +package com.rappytv.autofisher.v1_21.mixins; + +import com.rappytv.autofisher.core.AutoFisherAddon; +import com.rappytv.autofisher.event.FishHookBiteEvent; +import com.rappytv.autofisher.event.FishHookRetractEvent; +import net.labymod.api.Laby; +import net.minecraft.network.syncher.EntityDataAccessor; +import net.minecraft.world.entity.projectile.FishingHook; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.At.Shift; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(FishingHook.class) +public class MixinFishingHook { + + @Unique + private static final int PUTFIELD_OPCODE = 181; + + @Shadow + private boolean biting; + + @Inject( + method = "onClientRemoval", + at = @At("TAIL") + ) + private void onRemove(CallbackInfo ci) { + boolean manual = AutoFisherAddon.fishingController().isManualRetraction(); + Laby.fireEvent(new FishHookRetractEvent(manual)); + if (!manual) { + AutoFisherAddon.fishingController().setManualRetraction(true); + } + } + + @Inject( + method = "onSyncedDataUpdated", + at = @At( + value = "FIELD", + target = "Lnet/minecraft/world/entity/projectile/FishingHook;biting:Z", + opcode = PUTFIELD_OPCODE, + shift = Shift.AFTER + ) + ) + private void onFishBiting(EntityDataAccessor $$0, CallbackInfo ci) { + if (this.biting) { + Laby.fireEvent(new FishHookBiteEvent()); + } + } +} diff --git a/game-runner/src/v1_21_1/java/com/rappytv/autofisher/v1_21_1/VersionedFishingController.java b/game-runner/src/v1_21_1/java/com/rappytv/autofisher/v1_21_1/VersionedFishingController.java new file mode 100644 index 0000000..c480186 --- /dev/null +++ b/game-runner/src/v1_21_1/java/com/rappytv/autofisher/v1_21_1/VersionedFishingController.java @@ -0,0 +1,75 @@ +package com.rappytv.autofisher.v1_21_1; + +import com.rappytv.autofisher.FishingController; +import com.rappytv.autofisher.core.AutoFisherAddon; +import javax.inject.Singleton; +import net.labymod.api.models.Implements; +import net.minecraft.client.Minecraft; +import net.minecraft.client.multiplayer.MultiPlayerGameMode; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.entity.projectile.FishingHook; +import net.minecraft.world.item.FishingRodItem; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +@Singleton +@Implements(FishingController.class) +public class VersionedFishingController extends FishingController { + + @Override + public void castFishingRod() { + Player player = Minecraft.getInstance().player; + if (player == null) { + return; + } + InteractionHand hand = this.getFishingRodHand(player); + if (hand != null) { + this.useItem(hand); + } + } + + @Override + public void retractFishingRod() { + Player player = Minecraft.getInstance().player; + if (player == null) { + return; + } + FishingHook hook = player.fishing; + if (hook == null) { + return; + } + + InteractionHand hand = this.getFishingRodHand(player); + if (hand != null) { + AutoFisherAddon.fishingController().setManualRetraction(false); + this.useItem(hand); + } + } + + @Nullable + private InteractionHand getFishingRodHand(@NotNull Player player) { + if (player.getMainHandItem().getItem() instanceof FishingRodItem) { + return InteractionHand.MAIN_HAND; + } else if (player.getOffhandItem().getItem() instanceof FishingRodItem) { + return InteractionHand.OFF_HAND; + } + + return null; + } + + private void useItem(@NotNull InteractionHand hand) { + Player player = Minecraft.getInstance().player; + if (player == null) { + return; + } + + MultiPlayerGameMode gameMode = Minecraft.getInstance().gameMode; + if (gameMode == null) { + return; + } + + gameMode.useItem(player, hand); + player.swing(hand); + } +} diff --git a/game-runner/src/v1_21_1/java/com/rappytv/autofisher/v1_21_1/mixins/MixinFishingHook.java b/game-runner/src/v1_21_1/java/com/rappytv/autofisher/v1_21_1/mixins/MixinFishingHook.java new file mode 100644 index 0000000..5b8e3c9 --- /dev/null +++ b/game-runner/src/v1_21_1/java/com/rappytv/autofisher/v1_21_1/mixins/MixinFishingHook.java @@ -0,0 +1,52 @@ +package com.rappytv.autofisher.v1_21_1.mixins; + +import com.rappytv.autofisher.core.AutoFisherAddon; +import com.rappytv.autofisher.event.FishHookBiteEvent; +import com.rappytv.autofisher.event.FishHookRetractEvent; +import net.labymod.api.Laby; +import net.minecraft.network.syncher.EntityDataAccessor; +import net.minecraft.world.entity.projectile.FishingHook; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.At.Shift; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(FishingHook.class) +public class MixinFishingHook { + + @Unique + private static final int PUTFIELD_OPCODE = 181; + + @Shadow + private boolean biting; + + @Inject( + method = "onClientRemoval", + at = @At("TAIL") + ) + private void onRemove(CallbackInfo ci) { + boolean manual = AutoFisherAddon.fishingController().isManualRetraction(); + Laby.fireEvent(new FishHookRetractEvent(manual)); + if (!manual) { + AutoFisherAddon.fishingController().setManualRetraction(true); + } + } + + @Inject( + method = "onSyncedDataUpdated", + at = @At( + value = "FIELD", + target = "Lnet/minecraft/world/entity/projectile/FishingHook;biting:Z", + opcode = PUTFIELD_OPCODE, + shift = Shift.AFTER + ) + ) + private void onFishBiting(EntityDataAccessor $$0, CallbackInfo ci) { + if (this.biting) { + Laby.fireEvent(new FishHookBiteEvent()); + } + } +} diff --git a/game-runner/src/v1_21_10/java/com/rappytv/autofisher/v1_21_10/VersionedFishingController.java b/game-runner/src/v1_21_10/java/com/rappytv/autofisher/v1_21_10/VersionedFishingController.java new file mode 100644 index 0000000..01e48d3 --- /dev/null +++ b/game-runner/src/v1_21_10/java/com/rappytv/autofisher/v1_21_10/VersionedFishingController.java @@ -0,0 +1,75 @@ +package com.rappytv.autofisher.v1_21_10; + +import com.rappytv.autofisher.FishingController; +import com.rappytv.autofisher.core.AutoFisherAddon; +import javax.inject.Singleton; +import net.labymod.api.models.Implements; +import net.minecraft.client.Minecraft; +import net.minecraft.client.multiplayer.MultiPlayerGameMode; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.entity.projectile.FishingHook; +import net.minecraft.world.item.FishingRodItem; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +@Singleton +@Implements(FishingController.class) +public class VersionedFishingController extends FishingController { + + @Override + public void castFishingRod() { + Player player = Minecraft.getInstance().player; + if (player == null) { + return; + } + InteractionHand hand = this.getFishingRodHand(player); + if (hand != null) { + this.useItem(hand); + } + } + + @Override + public void retractFishingRod() { + Player player = Minecraft.getInstance().player; + if (player == null) { + return; + } + FishingHook hook = player.fishing; + if (hook == null) { + return; + } + + InteractionHand hand = this.getFishingRodHand(player); + if (hand != null) { + AutoFisherAddon.fishingController().setManualRetraction(false); + this.useItem(hand); + } + } + + @Nullable + private InteractionHand getFishingRodHand(@NotNull Player player) { + if (player.getMainHandItem().getItem() instanceof FishingRodItem) { + return InteractionHand.MAIN_HAND; + } else if (player.getOffhandItem().getItem() instanceof FishingRodItem) { + return InteractionHand.OFF_HAND; + } + + return null; + } + + private void useItem(@NotNull InteractionHand hand) { + Player player = Minecraft.getInstance().player; + if (player == null) { + return; + } + + MultiPlayerGameMode gameMode = Minecraft.getInstance().gameMode; + if (gameMode == null) { + return; + } + + gameMode.useItem(player, hand); + player.swing(hand); + } +} diff --git a/game-runner/src/v1_21_10/java/com/rappytv/autofisher/v1_21_10/mixins/MixinFishingHook.java b/game-runner/src/v1_21_10/java/com/rappytv/autofisher/v1_21_10/mixins/MixinFishingHook.java new file mode 100644 index 0000000..d692f12 --- /dev/null +++ b/game-runner/src/v1_21_10/java/com/rappytv/autofisher/v1_21_10/mixins/MixinFishingHook.java @@ -0,0 +1,52 @@ +package com.rappytv.autofisher.v1_21_10.mixins; + +import com.rappytv.autofisher.core.AutoFisherAddon; +import com.rappytv.autofisher.event.FishHookBiteEvent; +import com.rappytv.autofisher.event.FishHookRetractEvent; +import net.labymod.api.Laby; +import net.minecraft.network.syncher.EntityDataAccessor; +import net.minecraft.world.entity.projectile.FishingHook; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.At.Shift; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(FishingHook.class) +public class MixinFishingHook { + + @Unique + private static final int PUTFIELD_OPCODE = 181; + + @Shadow + private boolean biting; + + @Inject( + method = "onClientRemoval", + at = @At("TAIL") + ) + private void onRemove(CallbackInfo ci) { + boolean manual = AutoFisherAddon.fishingController().isManualRetraction(); + Laby.fireEvent(new FishHookRetractEvent(manual)); + if (!manual) { + AutoFisherAddon.fishingController().setManualRetraction(true); + } + } + + @Inject( + method = "onSyncedDataUpdated", + at = @At( + value = "FIELD", + target = "Lnet/minecraft/world/entity/projectile/FishingHook;biting:Z", + opcode = PUTFIELD_OPCODE, + shift = Shift.AFTER + ) + ) + private void onFishBiting(EntityDataAccessor $$0, CallbackInfo ci) { + if (this.biting) { + Laby.fireEvent(new FishHookBiteEvent()); + } + } +} diff --git a/game-runner/src/v1_21_3/java/com/rappytv/autofisher/v1_21_3/VersionedFishingController.java b/game-runner/src/v1_21_3/java/com/rappytv/autofisher/v1_21_3/VersionedFishingController.java new file mode 100644 index 0000000..5469c4d --- /dev/null +++ b/game-runner/src/v1_21_3/java/com/rappytv/autofisher/v1_21_3/VersionedFishingController.java @@ -0,0 +1,75 @@ +package com.rappytv.autofisher.v1_21_3; + +import com.rappytv.autofisher.FishingController; +import com.rappytv.autofisher.core.AutoFisherAddon; +import javax.inject.Singleton; +import net.labymod.api.models.Implements; +import net.minecraft.client.Minecraft; +import net.minecraft.client.multiplayer.MultiPlayerGameMode; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.entity.projectile.FishingHook; +import net.minecraft.world.item.FishingRodItem; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +@Singleton +@Implements(FishingController.class) +public class VersionedFishingController extends FishingController { + + @Override + public void castFishingRod() { + Player player = Minecraft.getInstance().player; + if (player == null) { + return; + } + InteractionHand hand = this.getFishingRodHand(player); + if (hand != null) { + this.useItem(hand); + } + } + + @Override + public void retractFishingRod() { + Player player = Minecraft.getInstance().player; + if (player == null) { + return; + } + FishingHook hook = player.fishing; + if (hook == null) { + return; + } + + InteractionHand hand = this.getFishingRodHand(player); + if (hand != null) { + AutoFisherAddon.fishingController().setManualRetraction(false); + this.useItem(hand); + } + } + + @Nullable + private InteractionHand getFishingRodHand(@NotNull Player player) { + if (player.getMainHandItem().getItem() instanceof FishingRodItem) { + return InteractionHand.MAIN_HAND; + } else if (player.getOffhandItem().getItem() instanceof FishingRodItem) { + return InteractionHand.OFF_HAND; + } + + return null; + } + + private void useItem(@NotNull InteractionHand hand) { + Player player = Minecraft.getInstance().player; + if (player == null) { + return; + } + + MultiPlayerGameMode gameMode = Minecraft.getInstance().gameMode; + if (gameMode == null) { + return; + } + + gameMode.useItem(player, hand); + player.swing(hand); + } +} diff --git a/game-runner/src/v1_21_3/java/com/rappytv/autofisher/v1_21_3/mixins/MixinFishingHook.java b/game-runner/src/v1_21_3/java/com/rappytv/autofisher/v1_21_3/mixins/MixinFishingHook.java new file mode 100644 index 0000000..8e72d3b --- /dev/null +++ b/game-runner/src/v1_21_3/java/com/rappytv/autofisher/v1_21_3/mixins/MixinFishingHook.java @@ -0,0 +1,52 @@ +package com.rappytv.autofisher.v1_21_3.mixins; + +import com.rappytv.autofisher.core.AutoFisherAddon; +import com.rappytv.autofisher.event.FishHookBiteEvent; +import com.rappytv.autofisher.event.FishHookRetractEvent; +import net.labymod.api.Laby; +import net.minecraft.network.syncher.EntityDataAccessor; +import net.minecraft.world.entity.projectile.FishingHook; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.At.Shift; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(FishingHook.class) +public class MixinFishingHook { + + @Unique + private static final int PUTFIELD_OPCODE = 181; + + @Shadow + private boolean biting; + + @Inject( + method = "onClientRemoval", + at = @At("TAIL") + ) + private void onRemove(CallbackInfo ci) { + boolean manual = AutoFisherAddon.fishingController().isManualRetraction(); + Laby.fireEvent(new FishHookRetractEvent(manual)); + if (!manual) { + AutoFisherAddon.fishingController().setManualRetraction(true); + } + } + + @Inject( + method = "onSyncedDataUpdated", + at = @At( + value = "FIELD", + target = "Lnet/minecraft/world/entity/projectile/FishingHook;biting:Z", + opcode = PUTFIELD_OPCODE, + shift = Shift.AFTER + ) + ) + private void onFishBiting(EntityDataAccessor $$0, CallbackInfo ci) { + if (this.biting) { + Laby.fireEvent(new FishHookBiteEvent()); + } + } +} diff --git a/game-runner/src/v1_21_4/java/com/rappytv/autofisher/v1_21_4/VersionedFishingController.java b/game-runner/src/v1_21_4/java/com/rappytv/autofisher/v1_21_4/VersionedFishingController.java new file mode 100644 index 0000000..f4ff59b --- /dev/null +++ b/game-runner/src/v1_21_4/java/com/rappytv/autofisher/v1_21_4/VersionedFishingController.java @@ -0,0 +1,75 @@ +package com.rappytv.autofisher.v1_21_4; + +import com.rappytv.autofisher.FishingController; +import com.rappytv.autofisher.core.AutoFisherAddon; +import javax.inject.Singleton; +import net.labymod.api.models.Implements; +import net.minecraft.client.Minecraft; +import net.minecraft.client.multiplayer.MultiPlayerGameMode; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.entity.projectile.FishingHook; +import net.minecraft.world.item.FishingRodItem; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +@Singleton +@Implements(FishingController.class) +public class VersionedFishingController extends FishingController { + + @Override + public void castFishingRod() { + Player player = Minecraft.getInstance().player; + if (player == null) { + return; + } + InteractionHand hand = this.getFishingRodHand(player); + if (hand != null) { + this.useItem(hand); + } + } + + @Override + public void retractFishingRod() { + Player player = Minecraft.getInstance().player; + if (player == null) { + return; + } + FishingHook hook = player.fishing; + if (hook == null) { + return; + } + + InteractionHand hand = this.getFishingRodHand(player); + if (hand != null) { + AutoFisherAddon.fishingController().setManualRetraction(false); + this.useItem(hand); + } + } + + @Nullable + private InteractionHand getFishingRodHand(@NotNull Player player) { + if (player.getMainHandItem().getItem() instanceof FishingRodItem) { + return InteractionHand.MAIN_HAND; + } else if (player.getOffhandItem().getItem() instanceof FishingRodItem) { + return InteractionHand.OFF_HAND; + } + + return null; + } + + private void useItem(@NotNull InteractionHand hand) { + Player player = Minecraft.getInstance().player; + if (player == null) { + return; + } + + MultiPlayerGameMode gameMode = Minecraft.getInstance().gameMode; + if (gameMode == null) { + return; + } + + gameMode.useItem(player, hand); + player.swing(hand); + } +} diff --git a/game-runner/src/v1_21_4/java/com/rappytv/autofisher/v1_21_4/mixins/MixinFishingHook.java b/game-runner/src/v1_21_4/java/com/rappytv/autofisher/v1_21_4/mixins/MixinFishingHook.java new file mode 100644 index 0000000..16626ec --- /dev/null +++ b/game-runner/src/v1_21_4/java/com/rappytv/autofisher/v1_21_4/mixins/MixinFishingHook.java @@ -0,0 +1,52 @@ +package com.rappytv.autofisher.v1_21_4.mixins; + +import com.rappytv.autofisher.core.AutoFisherAddon; +import com.rappytv.autofisher.event.FishHookBiteEvent; +import com.rappytv.autofisher.event.FishHookRetractEvent; +import net.labymod.api.Laby; +import net.minecraft.network.syncher.EntityDataAccessor; +import net.minecraft.world.entity.projectile.FishingHook; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.At.Shift; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(FishingHook.class) +public class MixinFishingHook { + + @Unique + private static final int PUTFIELD_OPCODE = 181; + + @Shadow + private boolean biting; + + @Inject( + method = "onClientRemoval", + at = @At("TAIL") + ) + private void onRemove(CallbackInfo ci) { + boolean manual = AutoFisherAddon.fishingController().isManualRetraction(); + Laby.fireEvent(new FishHookRetractEvent(manual)); + if (!manual) { + AutoFisherAddon.fishingController().setManualRetraction(true); + } + } + + @Inject( + method = "onSyncedDataUpdated", + at = @At( + value = "FIELD", + target = "Lnet/minecraft/world/entity/projectile/FishingHook;biting:Z", + opcode = PUTFIELD_OPCODE, + shift = Shift.AFTER + ) + ) + private void onFishBiting(EntityDataAccessor $$0, CallbackInfo ci) { + if (this.biting) { + Laby.fireEvent(new FishHookBiteEvent()); + } + } +} diff --git a/game-runner/src/v1_21_5/java/com/rappytv/autofisher/v1_21_5/VersionedFishingController.java b/game-runner/src/v1_21_5/java/com/rappytv/autofisher/v1_21_5/VersionedFishingController.java new file mode 100644 index 0000000..843ea35 --- /dev/null +++ b/game-runner/src/v1_21_5/java/com/rappytv/autofisher/v1_21_5/VersionedFishingController.java @@ -0,0 +1,75 @@ +package com.rappytv.autofisher.v1_21_5; + +import com.rappytv.autofisher.FishingController; +import com.rappytv.autofisher.core.AutoFisherAddon; +import javax.inject.Singleton; +import net.labymod.api.models.Implements; +import net.minecraft.client.Minecraft; +import net.minecraft.client.multiplayer.MultiPlayerGameMode; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.entity.projectile.FishingHook; +import net.minecraft.world.item.FishingRodItem; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +@Singleton +@Implements(FishingController.class) +public class VersionedFishingController extends FishingController { + + @Override + public void castFishingRod() { + Player player = Minecraft.getInstance().player; + if (player == null) { + return; + } + InteractionHand hand = this.getFishingRodHand(player); + if (hand != null) { + this.useItem(hand); + } + } + + @Override + public void retractFishingRod() { + Player player = Minecraft.getInstance().player; + if (player == null) { + return; + } + FishingHook hook = player.fishing; + if (hook == null) { + return; + } + + InteractionHand hand = this.getFishingRodHand(player); + if (hand != null) { + AutoFisherAddon.fishingController().setManualRetraction(false); + this.useItem(hand); + } + } + + @Nullable + private InteractionHand getFishingRodHand(@NotNull Player player) { + if (player.getMainHandItem().getItem() instanceof FishingRodItem) { + return InteractionHand.MAIN_HAND; + } else if (player.getOffhandItem().getItem() instanceof FishingRodItem) { + return InteractionHand.OFF_HAND; + } + + return null; + } + + private void useItem(@NotNull InteractionHand hand) { + Player player = Minecraft.getInstance().player; + if (player == null) { + return; + } + + MultiPlayerGameMode gameMode = Minecraft.getInstance().gameMode; + if (gameMode == null) { + return; + } + + gameMode.useItem(player, hand); + player.swing(hand); + } +} diff --git a/game-runner/src/v1_21_5/java/com/rappytv/autofisher/v1_21_5/mixins/MixinFishingHook.java b/game-runner/src/v1_21_5/java/com/rappytv/autofisher/v1_21_5/mixins/MixinFishingHook.java new file mode 100644 index 0000000..3591183 --- /dev/null +++ b/game-runner/src/v1_21_5/java/com/rappytv/autofisher/v1_21_5/mixins/MixinFishingHook.java @@ -0,0 +1,52 @@ +package com.rappytv.autofisher.v1_21_5.mixins; + +import com.rappytv.autofisher.core.AutoFisherAddon; +import com.rappytv.autofisher.event.FishHookBiteEvent; +import com.rappytv.autofisher.event.FishHookRetractEvent; +import net.labymod.api.Laby; +import net.minecraft.network.syncher.EntityDataAccessor; +import net.minecraft.world.entity.projectile.FishingHook; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.At.Shift; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(FishingHook.class) +public class MixinFishingHook { + + @Unique + private static final int PUTFIELD_OPCODE = 181; + + @Shadow + private boolean biting; + + @Inject( + method = "onClientRemoval", + at = @At("TAIL") + ) + private void onRemove(CallbackInfo ci) { + boolean manual = AutoFisherAddon.fishingController().isManualRetraction(); + Laby.fireEvent(new FishHookRetractEvent(manual)); + if (!manual) { + AutoFisherAddon.fishingController().setManualRetraction(true); + } + } + + @Inject( + method = "onSyncedDataUpdated", + at = @At( + value = "FIELD", + target = "Lnet/minecraft/world/entity/projectile/FishingHook;biting:Z", + opcode = PUTFIELD_OPCODE, + shift = Shift.AFTER + ) + ) + private void onFishBiting(EntityDataAccessor $$0, CallbackInfo ci) { + if (this.biting) { + Laby.fireEvent(new FishHookBiteEvent()); + } + } +} diff --git a/game-runner/src/v1_21_8/java/com/rappytv/autofisher/v1_21_8/VersionedFishingController.java b/game-runner/src/v1_21_8/java/com/rappytv/autofisher/v1_21_8/VersionedFishingController.java new file mode 100644 index 0000000..61d4f5e --- /dev/null +++ b/game-runner/src/v1_21_8/java/com/rappytv/autofisher/v1_21_8/VersionedFishingController.java @@ -0,0 +1,75 @@ +package com.rappytv.autofisher.v1_21_8; + +import com.rappytv.autofisher.FishingController; +import com.rappytv.autofisher.core.AutoFisherAddon; +import javax.inject.Singleton; +import net.labymod.api.models.Implements; +import net.minecraft.client.Minecraft; +import net.minecraft.client.multiplayer.MultiPlayerGameMode; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.entity.projectile.FishingHook; +import net.minecraft.world.item.FishingRodItem; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +@Singleton +@Implements(FishingController.class) +public class VersionedFishingController extends FishingController { + + @Override + public void castFishingRod() { + Player player = Minecraft.getInstance().player; + if (player == null) { + return; + } + InteractionHand hand = this.getFishingRodHand(player); + if (hand != null) { + this.useItem(hand); + } + } + + @Override + public void retractFishingRod() { + Player player = Minecraft.getInstance().player; + if (player == null) { + return; + } + FishingHook hook = player.fishing; + if (hook == null) { + return; + } + + InteractionHand hand = this.getFishingRodHand(player); + if (hand != null) { + AutoFisherAddon.fishingController().setManualRetraction(false); + this.useItem(hand); + } + } + + @Nullable + private InteractionHand getFishingRodHand(@NotNull Player player) { + if (player.getMainHandItem().getItem() instanceof FishingRodItem) { + return InteractionHand.MAIN_HAND; + } else if (player.getOffhandItem().getItem() instanceof FishingRodItem) { + return InteractionHand.OFF_HAND; + } + + return null; + } + + private void useItem(@NotNull InteractionHand hand) { + Player player = Minecraft.getInstance().player; + if (player == null) { + return; + } + + MultiPlayerGameMode gameMode = Minecraft.getInstance().gameMode; + if (gameMode == null) { + return; + } + + gameMode.useItem(player, hand); + player.swing(hand); + } +} diff --git a/game-runner/src/v1_21_8/java/com/rappytv/autofisher/v1_21_8/mixins/MixinFishingHook.java b/game-runner/src/v1_21_8/java/com/rappytv/autofisher/v1_21_8/mixins/MixinFishingHook.java new file mode 100644 index 0000000..508e297 --- /dev/null +++ b/game-runner/src/v1_21_8/java/com/rappytv/autofisher/v1_21_8/mixins/MixinFishingHook.java @@ -0,0 +1,52 @@ +package com.rappytv.autofisher.v1_21_8.mixins; + +import com.rappytv.autofisher.core.AutoFisherAddon; +import com.rappytv.autofisher.event.FishHookBiteEvent; +import com.rappytv.autofisher.event.FishHookRetractEvent; +import net.labymod.api.Laby; +import net.minecraft.network.syncher.EntityDataAccessor; +import net.minecraft.world.entity.projectile.FishingHook; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.At.Shift; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(FishingHook.class) +public class MixinFishingHook { + + @Unique + private static final int PUTFIELD_OPCODE = 181; + + @Shadow + private boolean biting; + + @Inject( + method = "onClientRemoval", + at = @At("TAIL") + ) + private void onRemove(CallbackInfo ci) { + boolean manual = AutoFisherAddon.fishingController().isManualRetraction(); + Laby.fireEvent(new FishHookRetractEvent(manual)); + if (!manual) { + AutoFisherAddon.fishingController().setManualRetraction(true); + } + } + + @Inject( + method = "onSyncedDataUpdated", + at = @At( + value = "FIELD", + target = "Lnet/minecraft/world/entity/projectile/FishingHook;biting:Z", + opcode = PUTFIELD_OPCODE, + shift = Shift.AFTER + ) + ) + private void onFishBiting(EntityDataAccessor $$0, CallbackInfo ci) { + if (this.biting) { + Laby.fireEvent(new FishHookBiteEvent()); + } + } +} From 448a72abd2c9455489e4d10c1b379c4dd7140603 Mon Sep 17 00:00:00 2001 From: RappyTV Date: Wed, 12 Nov 2025 19:26:12 +0100 Subject: [PATCH 11/12] chore: Add readme --- readme.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 readme.md diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..a9b5af7 --- /dev/null +++ b/readme.md @@ -0,0 +1,17 @@ +# AutoFisher + +![Downloads](https://labybadges-delta.vercel.app/api/downloads/autofisher/formatted)
+Completely automate the way your fishing rod works + +### 📦 Installation + +1. Press `Win` + `R` +2. Paste this into the window that popped up: `%appdata%/.minecraft/LabyMod-neo/addons` and press + enter +3. It should open your Labymod addon directory; Paste + the [AutoFisher.jar](https://github.com/RappyLabyAddons/AutoFisher/releases/latest/download/AutoFisher.jar) + in there. +4. Launch your Labymod client. + +ℹ️ If you have any problems with the addon/have update ideas, feel free to open an +Issue [here](https://github.com/RappyLabyAddons/AutoFisher/issues/new)! \ No newline at end of file From ca661f293e978e9169329b848f64cda90c06a4e5 Mon Sep 17 00:00:00 2001 From: RappyTV Date: Wed, 12 Nov 2025 19:26:31 +0100 Subject: [PATCH 12/12] chore: Add readme --- readme.md | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/readme.md b/readme.md index a9b5af7..971dce7 100644 --- a/readme.md +++ b/readme.md @@ -1,17 +1,11 @@ # AutoFisher - ![Downloads](https://labybadges-delta.vercel.app/api/downloads/autofisher/formatted)
Completely automate the way your fishing rod works ### 📦 Installation - 1. Press `Win` + `R` -2. Paste this into the window that popped up: `%appdata%/.minecraft/LabyMod-neo/addons` and press - enter -3. It should open your Labymod addon directory; Paste - the [AutoFisher.jar](https://github.com/RappyLabyAddons/AutoFisher/releases/latest/download/AutoFisher.jar) - in there. -4. Launch your Labymod client. +2. Paste this into the window that popped up: `%appdata%/.minecraft/LabyMod-neo/addons` and press enter +3. It should open your LabyMod addon directory; Paste the [AutoFisher.jar](https://github.com/RappyLabyAddons/AutoFisher/releases/latest/download/AutoFisher.jar) in there. +4. Launch your LabyMod client. -ℹ️ If you have any problems with the addon/have update ideas, feel free to open an -Issue [here](https://github.com/RappyLabyAddons/AutoFisher/issues/new)! \ No newline at end of file +ℹ️ If you have any problems with the addon/have update ideas, feel free to open an Issue [here](https://github.com/RappyLabyAddons/AutoFisher/issues/new)! \ No newline at end of file