diff options
18 files changed, 1144 insertions, 1 deletions
diff --git a/src/main/java/de/hysky/skyblocker/SkyblockerMod.java b/src/main/java/de/hysky/skyblocker/SkyblockerMod.java index 05c7fb1e..c3b0e0f2 100644 --- a/src/main/java/de/hysky/skyblocker/SkyblockerMod.java +++ b/src/main/java/de/hysky/skyblocker/SkyblockerMod.java @@ -11,6 +11,7 @@ import de.hysky.skyblocker.skyblock.chat.ChatRuleAnnouncementScreen; import de.hysky.skyblocker.skyblock.chat.ChatRulesHandler; import de.hysky.skyblocker.skyblock.chocolatefactory.EggFinder; import de.hysky.skyblocker.skyblock.chocolatefactory.TimeTowerReminder; +import de.hysky.skyblocker.skyblock.crimson.dojo.DojoManager; import de.hysky.skyblocker.skyblock.crimson.kuudra.Kuudra; import de.hysky.skyblocker.skyblock.dungeon.*; import de.hysky.skyblocker.skyblock.dungeon.partyfinder.PartyFinderScreen; @@ -181,6 +182,7 @@ public class SkyblockerMod implements ClientModInitializer { ApiUtils.init(); Debug.init(); Kuudra.init(); + DojoManager.init(); RenderHelper.init(); FancyStatusBars.init(); EventNotifications.init(); diff --git a/src/main/java/de/hysky/skyblocker/config/categories/CrimsonIsleCategory.java b/src/main/java/de/hysky/skyblocker/config/categories/CrimsonIsleCategory.java index fed8fa96..e8127a0e 100644 --- a/src/main/java/de/hysky/skyblocker/config/categories/CrimsonIsleCategory.java +++ b/src/main/java/de/hysky/skyblocker/config/categories/CrimsonIsleCategory.java @@ -81,6 +81,68 @@ public class CrimsonIsleCategory { .controller(IntegerFieldControllerBuilder::create) .build()) .build()) - .build(); + //dojo + .group(OptionGroup.createBuilder() + .name(Text.translatable("skyblocker.crimson.dojo")) + .collapsed(false) + .option(Option.<Boolean>createBuilder() + .name(Text.translatable("skyblocker.crimson.dojo.forceHelper")) + .description(OptionDescription.of(Text.translatable("skyblocker.crimson.dojo.forceHelper.@Tooltip"))) + .binding(config.crimsonIsle.dojo.enableForceHelper, + () -> config.crimsonIsle.dojo.enableForceHelper, + newValue -> config.crimsonIsle.dojo.enableForceHelper = newValue) + .controller(ConfigUtils::createBooleanController) + .build()) + .option(Option.<Boolean>createBuilder() + .name(Text.translatable("skyblocker.crimson.dojo.staminaHelper")) + .description(OptionDescription.of(Text.translatable("skyblocker.crimson.dojo.staminaHelper.@Tooltip"))) + .binding(config.crimsonIsle.dojo.enableStaminaHelper, + () -> config.crimsonIsle.dojo.enableStaminaHelper, + newValue -> config.crimsonIsle.dojo.enableStaminaHelper = newValue) + .controller(ConfigUtils::createBooleanController) + .build()) + .option(Option.<Boolean>createBuilder() + .name(Text.translatable("skyblocker.crimson.dojo.masteryHelper")) + .description(OptionDescription.of(Text.translatable("skyblocker.crimson.dojo.masteryHelper.@Tooltip"))) + .binding(config.crimsonIsle.dojo.enableMasteryHelper, + () -> config.crimsonIsle.dojo.enableMasteryHelper, + newValue -> config.crimsonIsle.dojo.enableMasteryHelper = newValue) + .controller(ConfigUtils::createBooleanController) + .build()) + .option(Option.<Boolean>createBuilder() + .name(Text.translatable("skyblocker.crimson.dojo.disciplineHelper")) + .description(OptionDescription.of(Text.translatable("skyblocker.crimson.dojo.disciplineHelper.@Tooltip"))) + .binding(config.crimsonIsle.dojo.enableDisciplineHelper, + () -> config.crimsonIsle.dojo.enableDisciplineHelper, + newValue -> config.crimsonIsle.dojo.enableDisciplineHelper = newValue) + .controller(ConfigUtils::createBooleanController) + .build()) + .option(Option.<Boolean>createBuilder() + .name(Text.translatable("skyblocker.crimson.dojo.swiftnessHelper")) + .description(OptionDescription.of(Text.translatable("skyblocker.crimson.dojo.swiftnessHelper.@Tooltip"))) + .binding(config.crimsonIsle.dojo.enableSwiftnessHelper, + () -> config.crimsonIsle.dojo.enableSwiftnessHelper, + newValue -> config.crimsonIsle.dojo.enableSwiftnessHelper = newValue) + .controller(ConfigUtils::createBooleanController) + .build()) + .option(Option.<Boolean>createBuilder() + .name(Text.translatable("skyblocker.crimson.dojo.controlHelper")) + .description(OptionDescription.of(Text.translatable("skyblocker.crimson.dojo.controlHelper.@Tooltip"))) + .binding(config.crimsonIsle.dojo.enableControlHelper, + () -> config.crimsonIsle.dojo.enableControlHelper, + newValue -> config.crimsonIsle.dojo.enableControlHelper = newValue) + .controller(ConfigUtils::createBooleanController) + .build()) + .option(Option.<Boolean>createBuilder() + .name(Text.translatable("skyblocker.crimson.dojo.tenacityHelper")) + .description(OptionDescription.of(Text.translatable("skyblocker.crimson.dojo.tenacityHelper.@Tooltip"))) + .binding(config.crimsonIsle.dojo.enableTenacityHelper, + () -> config.crimsonIsle.dojo.enableTenacityHelper, + newValue -> config.crimsonIsle.dojo.enableTenacityHelper = newValue) + .controller(ConfigUtils::createBooleanController) + .build()) + .build()) + + .build(); } } diff --git a/src/main/java/de/hysky/skyblocker/config/configs/CrimsonIsleConfig.java b/src/main/java/de/hysky/skyblocker/config/configs/CrimsonIsleConfig.java index 8dd93aee..451d1983 100644 --- a/src/main/java/de/hysky/skyblocker/config/configs/CrimsonIsleConfig.java +++ b/src/main/java/de/hysky/skyblocker/config/configs/CrimsonIsleConfig.java @@ -7,6 +7,10 @@ public class CrimsonIsleConfig { @SerialEntry public Kuudra kuudra = new Kuudra(); + @SerialEntry + public Dojo dojo = new Dojo(); + + public static class Kuudra { @SerialEntry public boolean supplyWaypoints = true; @@ -32,4 +36,27 @@ public class CrimsonIsleConfig { @SerialEntry public int arrowPoisonThreshold = 32; } + + public static class Dojo { + @SerialEntry + public boolean enableForceHelper = true; + + @SerialEntry + public boolean enableStaminaHelper = true; + + @SerialEntry + public boolean enableMasteryHelper = true; + + @SerialEntry + public boolean enableDisciplineHelper = true; + + @SerialEntry + public boolean enableSwiftnessHelper = true; + + @SerialEntry + public boolean enableControlHelper = true; + + @SerialEntry + public boolean enableTenacityHelper = true; + } } diff --git a/src/main/java/de/hysky/skyblocker/mixins/ClientPlayNetworkHandlerMixin.java b/src/main/java/de/hysky/skyblocker/mixins/ClientPlayNetworkHandlerMixin.java index 48389d40..7bbbac81 100644 --- a/src/main/java/de/hysky/skyblocker/mixins/ClientPlayNetworkHandlerMixin.java +++ b/src/main/java/de/hysky/skyblocker/mixins/ClientPlayNetworkHandlerMixin.java @@ -6,6 +6,7 @@ import com.llamalad7.mixinextras.sugar.Local; import de.hysky.skyblocker.skyblock.CompactDamage; import de.hysky.skyblocker.skyblock.FishingHelper; import de.hysky.skyblocker.skyblock.chocolatefactory.EggFinder; +import de.hysky.skyblocker.skyblock.crimson.dojo.DojoManager; import de.hysky.skyblocker.skyblock.dungeon.DungeonScore; import de.hysky.skyblocker.skyblock.dungeon.secrets.DungeonManager; import de.hysky.skyblocker.skyblock.end.BeaconHighlighter; @@ -97,6 +98,7 @@ public abstract class ClientPlayNetworkHandlerMixin { @Inject(method = "onParticle", at = @At("RETURN")) private void skyblocker$onParticle(ParticleS2CPacket packet, CallbackInfo ci) { MythologicalRitual.onParticle(packet); + DojoManager.onParticle(packet); EnderNodes.onParticle(packet); } diff --git a/src/main/java/de/hysky/skyblocker/mixins/ClientWorldMixin.java b/src/main/java/de/hysky/skyblocker/mixins/ClientWorldMixin.java new file mode 100644 index 00000000..6c10e5d2 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/mixins/ClientWorldMixin.java @@ -0,0 +1,23 @@ +package de.hysky.skyblocker.mixins; + + +import de.hysky.skyblocker.skyblock.crimson.dojo.DojoManager; +import de.hysky.skyblocker.utils.Utils; +import net.minecraft.block.BlockState; +import net.minecraft.client.world.ClientWorld; +import net.minecraft.util.math.BlockPos; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(ClientWorld.class) +public class ClientWorldMixin { + + @Inject(method = "handleBlockUpdate", at = @At("RETURN")) + private void skyblocker$handleBlockUpdate(BlockPos pos, BlockState state, int flags, CallbackInfo ci) { + if (Utils.isInCrimson()) { + DojoManager.onBlockUpdate(pos.toImmutable(), state); + } + } +} diff --git a/src/main/java/de/hysky/skyblocker/mixins/PingMeasurerMixin.java b/src/main/java/de/hysky/skyblocker/mixins/PingMeasurerMixin.java new file mode 100644 index 00000000..a9fae752 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/mixins/PingMeasurerMixin.java @@ -0,0 +1,22 @@ +package de.hysky.skyblocker.mixins; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import de.hysky.skyblocker.skyblock.crimson.dojo.DojoManager; +import de.hysky.skyblocker.utils.Utils; +import net.minecraft.client.network.PingMeasurer; +import net.minecraft.util.profiler.MultiValueDebugSampleLogImpl; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; + +@Mixin(PingMeasurer.class) +public class PingMeasurerMixin { + + @WrapOperation(method = "onPingResult", at = @At(value = "INVOKE", target = "Lnet/minecraft/util/profiler/MultiValueDebugSampleLogImpl;push(J)V")) + private void skyblocker$onPingResult(MultiValueDebugSampleLogImpl log, long ping, Operation<Void> operation) { + if (Utils.isInCrimson()) { + DojoManager.onPingResult(ping); + } + operation.call(log, ping); + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/crimson/dojo/ControlTestHelper.java b/src/main/java/de/hysky/skyblocker/skyblock/crimson/dojo/ControlTestHelper.java new file mode 100644 index 00000000..2f616a1e --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/crimson/dojo/ControlTestHelper.java @@ -0,0 +1,77 @@ +package de.hysky.skyblocker.skyblock.crimson.dojo; + +import de.hysky.skyblocker.utils.render.RenderHelper; +import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderContext; +import net.minecraft.client.MinecraftClient; +import net.minecraft.entity.Entity; +import net.minecraft.entity.mob.WitherSkeletonEntity; +import net.minecraft.util.math.Box; +import net.minecraft.util.math.Vec3d; + +import java.awt.*; + +public class ControlTestHelper { + private static final MinecraftClient CLIENT = MinecraftClient.getInstance(); + + private static WitherSkeletonEntity correctWitherSkeleton; + private static Vec3d lastPos; + private static long lastUpdate; + private static Vec3d pingOffset; + private static Vec3d lastPingOffset; + + protected static void reset() { + correctWitherSkeleton = null; + lastPos = null; + lastUpdate = -1; + pingOffset = null; + lastPingOffset = null; + } + + /** + * Find the correct WitherSkeleton entity when it spawns to start tracking it + * + * @param entity spawned entity + */ + protected static void onEntitySpawn(Entity entity) { + if (entity instanceof WitherSkeletonEntity witherSkeleton && correctWitherSkeleton == null) { + correctWitherSkeleton = witherSkeleton; + } + } + + /** + * Finds where to look in 3 ticks effected by ping + */ + protected static void update() { + if (correctWitherSkeleton != null) { + //smoothly adjust the ping throughout the test + if (lastPos != null) { + lastPingOffset = pingOffset; + double ping = DojoManager.ping / 1000d; + //find distance between last position and current position of skeleton + Vec3d movementVector = correctWitherSkeleton.getPos().subtract(lastPos).multiply(1, 0.1, 1); + //adjust the vector to current ping (multiply by 1 + time in second until the next update offset by the players ping) + pingOffset = movementVector.multiply(1 + 3 / 20d + ping); + } + lastPos = correctWitherSkeleton.getPos(); + lastUpdate = System.currentTimeMillis(); + } + } + + /** + * Renders an outline around where the player should aim (assumes values are updated every 3 ticks) + * + * @param context render context + */ + protected static void render(WorldRenderContext context) { + if (CLIENT.player != null && correctWitherSkeleton != null && pingOffset != null && lastPingOffset != null) { + float tickDelta = context.tickCounter().getTickDelta(false); + //how long until net update + double updatePercent = (double) (System.currentTimeMillis() - lastUpdate) / 150; + Vec3d aimPos = correctWitherSkeleton.getEyePos().add(pingOffset.multiply(updatePercent)).add(lastPingOffset.multiply(1 - updatePercent)); + Box targetBox = new Box(aimPos.add(-0.5, -0.5, -0.5), aimPos.add(0.5, 0.5, 0.5)); + boolean playerLookingAtBox = targetBox.raycast(CLIENT.player.getCameraPosVec(tickDelta), CLIENT.player.getCameraPosVec(tickDelta).add(CLIENT.player.getRotationVec(tickDelta).multiply(30))).isPresent(); + float[] boxColor = playerLookingAtBox ? Color.GREEN.getColorComponents(new float[]{0, 0, 0}) : Color.LIGHT_GRAY.getColorComponents(new float[]{0, 0, 0}); + RenderHelper.renderOutline(context, targetBox, boxColor, 3, true); + } + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/crimson/dojo/DisciplineTestHelper.java b/src/main/java/de/hysky/skyblocker/skyblock/crimson/dojo/DisciplineTestHelper.java new file mode 100644 index 00000000..ab0a0781 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/crimson/dojo/DisciplineTestHelper.java @@ -0,0 +1,66 @@ +package de.hysky.skyblocker.skyblock.crimson.dojo; + +import it.unimi.dsi.fastutil.objects.Object2IntMap; +import it.unimi.dsi.fastutil.objects.Object2IntMaps; +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import net.minecraft.client.MinecraftClient; + +import java.util.Map; +import java.util.Objects; + +public class DisciplineTestHelper { + private static final MinecraftClient CLIENT = MinecraftClient.getInstance(); + + /** + * Stores what sword is needed for the name of a zombie + */ + private static final Map<String, String> SWORD_TO_NAME_LOOKUP = Map.of( + "WOOD_SWORD", "Wood", + "IRON_SWORD", "Iron", + "GOLD_SWORD", "Gold", + "DIAMOND_SWORD", "Diamond" + ); + + /** + * Stores a color related to the color of the sword: wood = brown, iron = silver, gold = gold, diamond = cyan + */ + private static final Object2IntMap<String> SWORD_TO_COLOR_LOOKUP = Object2IntMaps.unmodifiable(new Object2IntOpenHashMap<>(Map.of( + "WOOD_SWORD", 0xa52a2a, + "IRON_SWORD", 0xc0c0c0, + "GOLD_SWORD", 0xffd700, + "DIAMOND_SWORD", 0x00ffff + ))); + + /** + * Works out if a zombie should glow based on its name and the currently held item by the player + * + * @param name name of the zombie to see if it should glow + * @return if the zombie should glow + */ + protected static boolean shouldGlow(String name) { + if (CLIENT == null || CLIENT.player == null) { + return false; + } + String heldId = CLIENT.player.getMainHandStack().getSkyblockId(); + if (heldId == null) { + return false; + } + return Objects.equals(SWORD_TO_NAME_LOOKUP.get(heldId), name); + } + + /** + * gets the color linked to the currently held sword for zombies to glow + * + * @return color linked to sword + */ + protected static int getColor() { + if (DojoManager.currentChallenge != DojoManager.DojoChallenges.DISCIPLINE || CLIENT == null || CLIENT.player == null) { + return 0; + } + String heldId = CLIENT.player.getMainHandStack().getSkyblockId(); + if (heldId == null) { + return 0; + } + return SWORD_TO_COLOR_LOOKUP.getOrDefault(heldId, 0); + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/crimson/dojo/DojoManager.java b/src/main/java/de/hysky/skyblocker/skyblock/crimson/dojo/DojoManager.java new file mode 100644 index 00000000..323c985c --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/crimson/dojo/DojoManager.java @@ -0,0 +1,256 @@ +package de.hysky.skyblocker.skyblock.crimson.dojo; + +import de.hysky.skyblocker.config.SkyblockerConfigManager; +import de.hysky.skyblocker.utils.Utils; +import de.hysky.skyblocker.utils.scheduler.Scheduler; +import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientEntityEvents; +import net.fabricmc.fabric.api.client.message.v1.ClientReceiveMessageEvents; +import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents; +import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderContext; +import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderEvents; +import net.fabricmc.fabric.api.event.player.AttackEntityCallback; +import net.minecraft.block.BlockState; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.network.ClientPlayNetworkHandler; +import net.minecraft.client.world.ClientWorld; +import net.minecraft.entity.Entity; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.network.packet.c2s.query.QueryPingC2SPacket; +import net.minecraft.network.packet.s2c.play.ParticleS2CPacket; +import net.minecraft.text.Text; +import net.minecraft.util.ActionResult; +import net.minecraft.util.Formatting; +import net.minecraft.util.Hand; +import net.minecraft.util.Util; +import net.minecraft.util.hit.EntityHitResult; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; + +import java.util.Arrays; +import java.util.Objects; +import java.util.function.Predicate; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class DojoManager { + + private static final MinecraftClient CLIENT = MinecraftClient.getInstance(); + private static final String START_MESSAGE = "[NPC] Master Tao: Ahhh, here we go! Let's get you into the Arena."; + private static final Pattern TEST_OF_PATTERN = Pattern.compile("\\s+Test of (\\w+) OBJECTIVES"); + private static final String CHALLENGE_FINISHED_REGEX = "\\s+CHALLENGE ((COMPLETED)|(FAILED))"; + + + protected enum DojoChallenges { + NONE("none", enabled -> false), + FORCE("Force", enabled -> SkyblockerConfigManager.get().crimsonIsle.dojo.enableForceHelper), + STAMINA("Stamina", enabled -> SkyblockerConfigManager.get().crimsonIsle.dojo.enableStaminaHelper), + MASTERY("Mastery", enabled -> SkyblockerConfigManager.get().crimsonIsle.dojo.enableMasteryHelper), + DISCIPLINE("Discipline", enabled -> SkyblockerConfigManager.get().crimsonIsle.dojo.enableDisciplineHelper), + SWIFTNESS("Swiftness", enabled -> SkyblockerConfigManager.get().crimsonIsle.dojo.enableSwiftnessHelper), + CONTROL("Control", enabled -> SkyblockerConfigManager.get().crimsonIsle.dojo.enableControlHelper), + TENACITY("Tenacity", enabled -> SkyblockerConfigManager.get().crimsonIsle.dojo.enableTenacityHelper); + + private final String name; + private final Predicate<Boolean> enabled; + + DojoChallenges(String name, Predicate<Boolean> enabled) { + this.name = name; + this.enabled = enabled; + } + + public static DojoChallenges from(String name) { + return Arrays.stream(DojoChallenges.values()).filter(n -> name.equals(n.name)).findFirst().orElse(NONE); + } + } + + protected static DojoChallenges currentChallenge = DojoChallenges.NONE; + public static boolean inArena = false; + protected static long ping = -1; + + public static void init() { + ClientReceiveMessageEvents.GAME.register(DojoManager::onMessage); + WorldRenderEvents.AFTER_TRANSLUCENT.register(DojoManager::render); + ClientPlayConnectionEvents.JOIN.register((_handler, _sender, _client) -> reset()); + ClientEntityEvents.ENTITY_LOAD.register(DojoManager::onEntitySpawn); + ClientEntityEvents.ENTITY_UNLOAD.register(DojoManager::onEntityDespawn); + AttackEntityCallback.EVENT.register(DojoManager::onEntityAttacked); + Scheduler.INSTANCE.scheduleCyclic(DojoManager::update, 3); + } + + private static void reset() { + inArena = false; + currentChallenge = DojoChallenges.NONE; + ForceTestHelper.reset(); + StaminaTestHelper.reset(); + MasteryTestHelper.reset(); + SwiftnessTestHelper.reset(); + ControlTestHelper.reset(); + TenacityTestHelper.reset(); + } + + /** + * works out if the player is in dojo and if so what challenge based on chat messages + * + * @param text message + * @param overlay is overlay + */ + private static void onMessage(Text text, Boolean overlay) { + if (!Utils.isInCrimson() || overlay) { + return; + } + if (Objects.equals(Formatting.strip(text.getString()), START_MESSAGE)) { + inArena = true; + //update the players ping + getPing(); + return; + } + if (!inArena) { + return; + } + if (text.getString().matches(CHALLENGE_FINISHED_REGEX)) { + reset(); + return; + } + + //look for a message saying what challenge is starting if one has not already been found + if (currentChallenge != DojoChallenges.NONE) { + return; + } + Matcher nextChallenge = TEST_OF_PATTERN.matcher(text.getString()); + if (nextChallenge.matches()) { + currentChallenge = DojoChallenges.from(nextChallenge.group(1)); + if (!currentChallenge.enabled.test(true)) { + currentChallenge = DojoChallenges.NONE; + } + } + } + + private static void getPing() { + ClientPlayNetworkHandler networkHandler = CLIENT.getNetworkHandler(); + if (networkHandler != null) { + networkHandler.sendPacket(new QueryPingC2SPacket(Util.getMeasuringTimeMs())); + } + } + + public static void onPingResult(long ping) { + DojoManager.ping = ping; + } + + private static void update() { + if (!Utils.isInCrimson() || !inArena) { + return; + } + switch (currentChallenge) { + case STAMINA -> StaminaTestHelper.update(); + case CONTROL -> ControlTestHelper.update(); + } + } + + /** + * called from the {@link de.hysky.skyblocker.skyblock.entity.MobGlow} class and checks the current challenge to see if zombies should be glowing + * + * @param name name of the zombie + * @return if the zombie should glow + */ + public static boolean shouldGlow(String name) { + if (!Utils.isInCrimson() || !inArena) { + return false; + } + return switch (currentChallenge) { + case FORCE -> ForceTestHelper.shouldGlow(name); + case DISCIPLINE -> DisciplineTestHelper.shouldGlow(name); + default -> false; + }; + } + + /** + * called from the {@link de.hysky.skyblocker.skyblock.entity.MobGlow} class and checks the current challenge to see zombie outline color + * + * @return if the zombie should glow + */ + public static int getColor() { + if (!Utils.isInCrimson() || !inArena) { + return 0xf57738; + } + return switch (currentChallenge) { + case FORCE -> ForceTestHelper.getColor(); + case DISCIPLINE -> DisciplineTestHelper.getColor(); + default -> 0xf57738; + }; + } + + /** + * when a block is updated check the current challenge and send the packet to correct helper + * + * @param pos the location of the updated block + * @param state the state of the new block + */ + public static void onBlockUpdate(BlockPos pos, BlockState state) { + if (!Utils.isInCrimson() || !inArena) { + return; + } + switch (currentChallenge) { + case MASTERY -> MasteryTestHelper.onBlockUpdate(pos, state); + case SWIFTNESS -> SwiftnessTestHelper.onBlockUpdate(pos, state); + } + } + + private static void onEntitySpawn(Entity entity, ClientWorld clientWorld) { + if (!Utils.isInCrimson() || !inArena || CLIENT == null || CLIENT.player == null) { + return; + } + // Check if within 50 blocks and 5 blocks vertically + if (entity.squaredDistanceTo(CLIENT.player) > 2500 || Math.abs(entity.getBlockY() - CLIENT.player.getBlockY()) > 5) { + return; + } + switch (currentChallenge) { + case FORCE -> ForceTestHelper.onEntitySpawn(entity); + case CONTROL -> ControlTestHelper.onEntitySpawn(entity); + case TENACITY -> TenacityTestHelper.onEntitySpawn(entity); + } + } + + private static void onEntityDespawn(Entity entity, ClientWorld clientWorld) { + if (!Utils.isInCrimson() || !inArena) { + return; + } + switch (currentChallenge) { + case FORCE -> ForceTestHelper.onEntityDespawn(entity); + case TENACITY -> TenacityTestHelper.onEntityDespawn(entity); + } + } + + private static ActionResult onEntityAttacked(PlayerEntity playerEntity, World world, Hand hand, Entity entity, EntityHitResult entityHitResult) { + if (!Utils.isInCrimson() || !inArena) { + return ActionResult.PASS; + } + if (currentChallenge == DojoChallenges.FORCE) { + ForceTestHelper.onEntityAttacked(entity); + } + return ActionResult.PASS; + } + + public static void onParticle(ParticleS2CPacket packet) { + if (!Utils.isInCrimson() || !inArena) { + return; + } + if (currentChallenge == DojoChallenges.TENACITY) { + TenacityTestHelper.onParticle(packet); + } + } + + private static void render(WorldRenderContext context) { + if (!Utils.isInCrimson() || !inArena) { + return; + } + switch (currentChallenge) { + case FORCE -> ForceTestHelper.render(context); + case STAMINA -> StaminaTestHelper.render(context); + case MASTERY -> MasteryTestHelper.render(context); + case SWIFTNESS -> SwiftnessTestHelper.render(context); + case CONTROL -> ControlTestHelper.render(context); + case TENACITY -> TenacityTestHelper.render(context); + } + } + +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/crimson/dojo/ForceTestHelper.java b/src/main/java/de/hysky/skyblocker/skyblock/crimson/dojo/ForceTestHelper.java new file mode 100644 index 00000000..70d6a401 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/crimson/dojo/ForceTestHelper.java @@ -0,0 +1,81 @@ +package de.hysky.skyblocker.skyblock.crimson.dojo; + +import de.hysky.skyblocker.utils.render.RenderHelper; +import it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap; +import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderContext; +import net.minecraft.entity.Entity; +import net.minecraft.entity.mob.ZombieEntity; +import net.minecraft.text.MutableText; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; +import net.minecraft.util.math.Vec3d; + +import java.awt.*; +import java.text.DecimalFormat; +import java.util.Map; + +public class ForceTestHelper { + + private static final DecimalFormat FORMATTER = new DecimalFormat("0.0"); + private static final int ZOMBIE_LIFE_TIME = 10100; + + private static final Object2LongOpenHashMap<ZombieEntity> zombies = new Object2LongOpenHashMap<>(); + + protected static void reset() { + zombies.clear(); + } + + /** + * If a zombie value is negative make it glow + * + * @param name zombies value + * @return if the zombie should glow + */ + protected static boolean shouldGlow(String name) { + return name.contains("-"); + } + + protected static int getColor() { + return Color.RED.getRGB(); + } + + protected static void onEntitySpawn(Entity entity) { + if (entity instanceof ZombieEntity zombie) { + zombies.put(zombie, System.currentTimeMillis() + ZOMBIE_LIFE_TIME); + } + } + + protected static void onEntityAttacked(Entity entity) { + if (entity instanceof ZombieEntity zombie) { + if (zombies.containsKey(zombie)) { + zombies.put(zombie, System.currentTimeMillis() + ZOMBIE_LIFE_TIME); //timer is reset when they are hit + } + } + } + + protected static void onEntityDespawn(Entity entity) { + if (entity instanceof ZombieEntity zombie) { + zombies.removeLong(zombie); + } + } + + protected static void render(WorldRenderContext context) { + //render times + long currentTime = System.currentTimeMillis(); + for (Map.Entry<ZombieEntity, Long> zombie : zombies.object2LongEntrySet()) { + float secondsTime = Math.max((zombie.getValue() - currentTime) / 1000f, 0); + + MutableText text = Text.literal(FORMATTER.format(secondsTime)); + if (secondsTime > 1) { + text = text.formatted(Formatting.GREEN); + } else if (secondsTime > 0) { + text = text.formatted(Formatting.YELLOW); + } else { + text = text.formatted(Formatting.RED); + } + + Vec3d labelPos = zombie.getKey().getCameraPosVec(context.tickCounter().getTickDelta(false)); + RenderHelper.renderText(context, text, labelPos, 1.5f, true); + } + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/crimson/dojo/MasteryTestHelper.java b/src/main/java/de/hysky/skyblocker/skyblock/crimson/dojo/MasteryTestHelper.java new file mode 100644 index 00000000..625b91eb --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/crimson/dojo/MasteryTestHelper.java @@ -0,0 +1,68 @@ +package de.hysky.skyblocker.skyblock.crimson.dojo; + +import de.hysky.skyblocker.utils.render.RenderHelper; +import it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap; +import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderContext; +import net.minecraft.block.BlockState; +import net.minecraft.block.Blocks; +import net.minecraft.client.MinecraftClient; +import net.minecraft.text.Text; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Vec3d; + +import java.awt.*; +import java.text.DecimalFormat; +import java.util.ArrayList; +import java.util.List; + +public class MasteryTestHelper { + private static final MinecraftClient CLIENT = MinecraftClient.getInstance(); + private static final DecimalFormat FORMATTER = new DecimalFormat("0.00"); + /** + * How long it takes for a block to turn red + */ + private static final int BLOCK_LIFE_TIME = 6550; + + private static final List<BlockPos> blockOrder = new ArrayList<>(); + private static final Object2LongOpenHashMap<BlockPos> endTimes = new Object2LongOpenHashMap<>(); + + protected static void reset() { + blockOrder.clear(); + endTimes.clear(); + } + + protected static void onBlockUpdate(BlockPos pos, BlockState state) { + if (CLIENT == null || CLIENT.player == null) { + return; + } + if (state.isOf(Blocks.LIME_WOOL)) { + blockOrder.add(pos); + //add lifetime of a block to the time to get time when block expires + // work out how long it will take between the player firing and arrow hitting the block and to subtract from time + long travelTime = (long) (CLIENT.player.getPos().distanceTo(pos.toCenterPos()) * 1000 / 60); //an arrow speed is about 60 blocks a second from a full draw + endTimes.put(pos, System.currentTimeMillis() + BLOCK_LIFE_TIME - DojoManager.ping - travelTime); + } + if (state.isAir()) { + blockOrder.remove(pos); + |
