diff options
Diffstat (limited to 'src')
4 files changed, 143 insertions, 18 deletions
diff --git a/src/main/java/me/xmrvizzy/skyblocker/mixin/ClientPlayNetworkHandlerMixin.java b/src/main/java/me/xmrvizzy/skyblocker/mixin/ClientPlayNetworkHandlerMixin.java index 2d109524..51b88d30 100644 --- a/src/main/java/me/xmrvizzy/skyblocker/mixin/ClientPlayNetworkHandlerMixin.java +++ b/src/main/java/me/xmrvizzy/skyblocker/mixin/ClientPlayNetworkHandlerMixin.java @@ -1,23 +1,42 @@ package me.xmrvizzy.skyblocker.mixin; import com.llamalad7.mixinextras.injector.WrapWithCondition; - +import com.llamalad7.mixinextras.sugar.Local; import dev.cbyrne.betterinject.annotations.Inject; import me.xmrvizzy.skyblocker.skyblock.FishingHelper; +import me.xmrvizzy.skyblocker.skyblock.dungeon.secrets.DungeonSecrets; import me.xmrvizzy.skyblocker.utils.Utils; +import net.minecraft.client.MinecraftClient; import net.minecraft.client.network.ClientPlayNetworkHandler; +import net.minecraft.entity.ItemEntity; +import net.minecraft.entity.LivingEntity; import net.minecraft.network.packet.s2c.play.PlaySoundS2CPacket; import org.slf4j.Logger; +import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.ModifyVariable; @Mixin(ClientPlayNetworkHandler.class) public abstract class ClientPlayNetworkHandlerMixin { + @Shadow + @Final + private MinecraftClient client; + @Inject(method = "onPlaySound", at = @At("RETURN")) private void skyblocker$onPlaySound(PlaySoundS2CPacket packet) { FishingHelper.onSound(packet); } + @ModifyVariable(method = "onItemPickupAnimation", at = @At(value = "STORE", ordinal = 0)) + private ItemEntity skyblocker$onItemPickup(ItemEntity itemEntity, @Local LivingEntity collector) { + if (collector == client.player) { + DungeonSecrets.onItemPickup(itemEntity, collector); + } + return itemEntity; + } + @WrapWithCondition(method = "onEntityPassengersSet", at = @At(value = "INVOKE", target = "Lorg/slf4j/Logger;warn(Ljava/lang/String;)V", remap = false)) private boolean skyblocker$cancelEntityPassengersWarning(Logger instance, String msg) { return !Utils.isOnHypixel(); diff --git a/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/secrets/DungeonSecrets.java b/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/secrets/DungeonSecrets.java index 9e31fe5f..3eb4e036 100644 --- a/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/secrets/DungeonSecrets.java +++ b/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/secrets/DungeonSecrets.java @@ -6,16 +6,23 @@ import it.unimi.dsi.fastutil.objects.Object2ByteOpenHashMap; import me.xmrvizzy.skyblocker.SkyblockerMod; import me.xmrvizzy.skyblocker.config.SkyblockerConfig; import me.xmrvizzy.skyblocker.utils.Utils; +import net.fabricmc.fabric.api.client.message.v1.ClientReceiveMessageEvents; 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.UseBlockCallback; import net.minecraft.client.MinecraftClient; import net.minecraft.client.network.ClientPlayerEntity; +import net.minecraft.entity.ItemEntity; +import net.minecraft.entity.LivingEntity; import net.minecraft.item.FilledMapItem; import net.minecraft.item.ItemStack; import net.minecraft.item.Items; import net.minecraft.item.map.MapState; import net.minecraft.text.Text; +import net.minecraft.util.ActionResult; import net.minecraft.util.Identifier; +import net.minecraft.util.hit.BlockHitResult; +import net.minecraft.world.World; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.joml.Vector2ic; @@ -37,7 +44,6 @@ import java.util.zip.InflaterInputStream; public class DungeonSecrets { protected static final Logger LOGGER = LoggerFactory.getLogger(DungeonSecrets.class); - private static final String DUNGEONS_DATA_DIR = "/assets/skyblocker/dungeons"; /** * Block data for dungeon rooms. See {@link me.xmrvizzy.skyblocker.skyblock.dungeon.secrets.DungeonRoomsDFU DungeonRoomsDFU} for format details and how it's generated. * All access to this map must check {@link #isRoomsLoaded()} to prevent concurrent modification. @@ -73,6 +79,9 @@ public class DungeonSecrets { Map.entry("minecraft:cyan_terracotta", (byte) 20), Map.entry("minecraft:black_terracotta", (byte) 21) )); + private static final String DUNGEONS_DATA_DIR = "/assets/skyblocker/dungeons"; + @NotNull + private static final Map<Vector2ic, Room> rooms = new HashMap<>(); private static JsonObject roomsJson; private static JsonObject waypointsJson; @Nullable @@ -91,14 +100,13 @@ public class DungeonSecrets { */ @Nullable private static Vector2ic physicalEntrancePos; - @NotNull - private static final Map<Vector2ic, Room> rooms = new HashMap<>(); private static Room currentRoom; public static boolean isRoomsLoaded() { return roomsLoaded != null && roomsLoaded.isDone(); } + @SuppressWarnings("unused") public static JsonObject getRoomsJson() { return roomsJson; } @@ -118,6 +126,9 @@ public class DungeonSecrets { CompletableFuture.runAsync(DungeonSecrets::load); SkyblockerMod.getInstance().scheduler.scheduleCyclic(DungeonSecrets::update, 10); WorldRenderEvents.AFTER_TRANSLUCENT.register(DungeonSecrets::render); + ClientReceiveMessageEvents.GAME.register(DungeonSecrets::onChatMessage); + ClientReceiveMessageEvents.GAME_CANCELED.register(DungeonSecrets::onChatMessage); + UseBlockCallback.EVENT.register((world, hand, hitResult, hitResult2) -> onUseBlock(hand, hitResult2)); } private static void load() { @@ -272,11 +283,34 @@ public class DungeonSecrets { } private static void render(WorldRenderContext context) { - if (SkyblockerConfig.get().locations.dungeons.secretWaypoints && Utils.isInDungeons() && currentRoom != null && currentRoom.isMatched()) { + if (isCurrentRoomMatched()) { currentRoom.render(context); } } + private static void onChatMessage(Text text, boolean overlay) { + if (overlay && isCurrentRoomMatched()) { + currentRoom.onChatMessage(text.getString()); + } + } + + private static ActionResult onUseBlock(World world, BlockHitResult hitResult) { + if (isCurrentRoomMatched()) { + currentRoom.onUseBlock(world, hitResult); + } + return ActionResult.PASS; + } + + public static void onItemPickup(ItemEntity itemEntity, LivingEntity collector) { + if (isCurrentRoomMatched()) { + currentRoom.onItemPickup(itemEntity, collector); + } + } + + private static boolean isCurrentRoomMatched() { + return SkyblockerConfig.get().locations.dungeons.secretWaypoints && Utils.isInDungeons() && currentRoom != null && currentRoom.isMatched(); + } + private static void reset() { mapEntrancePos = null; mapRoomSize = 0; diff --git a/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/secrets/Room.java b/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/secrets/Room.java index e7ed7014..7833233f 100644 --- a/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/secrets/Room.java +++ b/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/secrets/Room.java @@ -1,5 +1,8 @@ package me.xmrvizzy.skyblocker.skyblock.dungeon.secrets; +import com.google.common.collect.HashBasedTable; +import com.google.common.collect.ImmutableTable; +import com.google.common.collect.Table; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import it.unimi.dsi.fastutil.ints.IntRBTreeSet; @@ -7,13 +10,19 @@ import it.unimi.dsi.fastutil.ints.IntSortedSet; import it.unimi.dsi.fastutil.ints.IntSortedSets; import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderContext; import net.fabricmc.fabric.api.util.TriState; +import net.minecraft.block.BlockState; +import net.minecraft.block.Blocks; import net.minecraft.block.MapColor; import net.minecraft.client.MinecraftClient; import net.minecraft.client.network.ClientPlayerEntity; import net.minecraft.client.world.ClientWorld; +import net.minecraft.entity.ItemEntity; +import net.minecraft.entity.LivingEntity; import net.minecraft.registry.Registries; +import net.minecraft.util.hit.BlockHitResult; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.MathHelper; +import net.minecraft.world.World; import org.apache.commons.lang3.tuple.MutableTriple; import org.apache.commons.lang3.tuple.Triple; import org.joml.Vector2i; @@ -37,7 +46,7 @@ public class Room { * <li>{@link TriState#TRUE} means that the room has been checked and there is a match. */ private TriState matched = TriState.DEFAULT; - private List<SecretWaypoint> secretWaypoints; + private Table<Integer, BlockPos, SecretWaypoint> secretWaypoints; public Room(Type type, Vector2ic... physicalPositions) { long startTime = System.currentTimeMillis(); @@ -178,25 +187,58 @@ public class Room { private void roomMatched(Triple<Direction, Vector2ic, List<String>> directionRooms) { matched = TriState.TRUE; - secretWaypoints = new ArrayList<>(); + Table<Integer, BlockPos, SecretWaypoint> secretWaypointsMutable = HashBasedTable.create(); String name = directionRooms.getRight().get(0); for (JsonElement waypointElement : DungeonSecrets.getWaypointsJson().get(name).getAsJsonArray()) { JsonObject waypoint = waypointElement.getAsJsonObject(); String secretName = waypoint.get("secretName").getAsString(); int secretIndex = Integer.parseInt(secretName.substring(0, Character.isDigit(secretName.charAt(1)) ? 2 : 1)); - secretWaypoints.add(new SecretWaypoint(secretIndex, waypoint, secretName, DungeonMapUtils.relativeToActual(directionRooms.getMiddle(), directionRooms.getLeft(), waypoint))); + BlockPos pos = DungeonMapUtils.relativeToActual(directionRooms.getMiddle(), directionRooms.getLeft(), waypoint); + secretWaypointsMutable.put(secretIndex, pos, new SecretWaypoint(secretIndex, waypoint, secretName, pos)); } + secretWaypoints = ImmutableTable.copyOf(secretWaypointsMutable); DungeonSecrets.LOGGER.info("[Skyblocker] Room {} matched after checking {} block(s)", name, checkedBlocks.size()); // TODO change to debug } protected void render(WorldRenderContext context) { - for (SecretWaypoint secretWaypoint : secretWaypoints) { - if (secretWaypoint.missing()) { + for (SecretWaypoint secretWaypoint : secretWaypoints.values()) { + if (secretWaypoint.isMissing()) { secretWaypoint.render(context); } } } + protected void onChatMessage(String message) { + if (message.toLowerCase().contains("secret")) { // TODO for dev purposes only + DungeonSecrets.LOGGER.info(message); + } + } + + protected void onUseBlock(World world, BlockHitResult hitResult) { + BlockState state = world.getBlockState(hitResult.getBlockPos()); + DungeonSecrets.LOGGER.info(state.getBlock().toString()); // TODO for dev purposes only + if (!state.isOf(Blocks.CHEST) && !state.isOf(Blocks.PLAYER_HEAD) && !state.isOf(Blocks.PLAYER_WALL_HEAD)) { + return; + } + secretWaypoints.column(hitResult.getBlockPos()).values().stream().filter(secretWaypoint -> secretWaypoint.category.needsInteraction()).findAny() + .ifPresent(secretWaypoint -> onSecretFound(secretWaypoint, "[Skyblocker] Detected {} interaction, setting secret #{} as found", secretWaypoint.category, secretWaypoint.secretIndex)); + } + + protected void onItemPickup(ItemEntity itemEntity, LivingEntity collector) { + String name = itemEntity.getName().getString(); + DungeonSecrets.LOGGER.info(name); // TODO for dev purposes only + if (Arrays.stream(SecretWaypoint.SECRET_ITEMS).noneMatch(name::contains)) { + return; + } + secretWaypoints.values().stream().filter(secretWaypoint -> secretWaypoint.category.needsItemPickup()).min(Comparator.comparingDouble(secretWaypoint -> collector.squaredDistanceTo(secretWaypoint.centerPos))).filter(secretWaypoint -> collector.squaredDistanceTo(secretWaypoint.centerPos) <= 36D) + .ifPresent(secretWaypoint -> onSecretFound(secretWaypoint, "[Skyblocker] Detected {} picked up a {} from a {} secret, setting secret #{} as found", collector.getName().getString(), itemEntity.getName().getString(), secretWaypoint.category, secretWaypoint.secretIndex)); + } + + private void onSecretFound(SecretWaypoint secretWaypoint, String msg, Object... args) { + secretWaypoints.row(secretWaypoint.secretIndex).values().forEach(SecretWaypoint::setFound); + DungeonSecrets.LOGGER.info(msg, args); + } + /** * Resets fields after room matching completes, where either a room is found or none matched. * These fields are no longer needed and are discarded to save memory. diff --git a/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/secrets/SecretWaypoint.java b/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/secrets/SecretWaypoint.java index 08cba782..71459e50 100644 --- a/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/secrets/SecretWaypoint.java +++ b/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/secrets/SecretWaypoint.java @@ -4,24 +4,46 @@ import com.google.gson.JsonObject; import me.xmrvizzy.skyblocker.utils.RenderHelper; import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderContext; import net.minecraft.client.MinecraftClient; -import net.minecraft.entity.player.PlayerEntity; import net.minecraft.text.Text; import net.minecraft.util.Formatting; import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Vec3d; + +public class SecretWaypoint { + static final String[] SECRET_ITEMS = {"Decoy", "Defuse Kit", "Dungeon Chest Key", "Healing VIII", "Inflatable Jerry", "Spirit Leap", "Training Weights", "Trap", "treasure Talisman"}; + final int secretIndex; + final Category category; + private final Text name; + final BlockPos pos; + final Vec3d centerPos; + private boolean missing; -public record SecretWaypoint(int secretIndex, Category category, Text name, BlockPos pos, PlayerEntity player, boolean missing) { SecretWaypoint(int secretIndex, JsonObject waypoint, String name, BlockPos pos) { - this(secretIndex, Category.get(waypoint), Text.of(name), pos, MinecraftClient.getInstance().player, true); + this.secretIndex = secretIndex; + this.category = Category.get(waypoint); + this.name = Text.of(name); + this.pos = pos; + this.centerPos = pos.toCenterPos(); + this.missing = true; + } + + public boolean isMissing() { + return missing; + } + + public void setFound() { + this.missing = false; } void render(WorldRenderContext context) { - RenderHelper.renderFilledThroughWallsWithBeaconBeam(context, pos(), category().colorComponents, 0.5F); - RenderHelper.renderText(context, name(), pos().up().toCenterPos(), true); - double distance = player().getPos().distanceTo(pos().toCenterPos()); - RenderHelper.renderText(context, Text.literal(Math.round(distance) + "m").formatted(Formatting.YELLOW), pos().up().toCenterPos(), 1, MinecraftClient.getInstance().textRenderer.fontHeight + 1, true); + RenderHelper.renderFilledThroughWallsWithBeaconBeam(context, pos, category.colorComponents, 0.5F); + Vec3d posUp = centerPos.add(0, 1, 0); + RenderHelper.renderText(context, name, posUp, true); + double distance = context.camera().getPos().distanceTo(centerPos); + RenderHelper.renderText(context, Text.literal(Math.round(distance) + "m").formatted(Formatting.YELLOW), posUp, 1, MinecraftClient.getInstance().textRenderer.fontHeight + 1, true); } - private enum Category { + enum Category { ENTRANCE(0, 255, 0), SUPERBOOM(255, 0, 0), CHEST(2, 213, 250), @@ -55,5 +77,13 @@ public record SecretWaypoint(int secretIndex, Category category, Text name, Bloc default -> Category.DEFAULT; }; } + + boolean needsInteraction() { + return this == CHEST || this == WITHER; + } + + boolean needsItemPickup() { + return this == ITEM || this == BAT; + } } } |