From 6a29818d57fc4fd30e3c29df9105bdf9cb00fcc9 Mon Sep 17 00:00:00 2001 From: Gegy Date: Fri, 22 Nov 2024 10:44:10 +0100 Subject: [PATCH] Player sensor component --- build.gradle | 2 +- .../extras/ExtraDataComponents.java | 17 +- .../java/com/lovetropics/extras/LTExtras.java | 7 + .../client/ClientPlayerSensorEffects.java | 234 ++++++++++++++++++ .../extras/item/sensor/PlayerSensor.java | 62 +++++ .../sensor/ServerPlayerSensorManager.java | 128 ++++++++++ .../extras/item/sensor/package-info.java | 9 + .../client/LivingEntityRendererMixin.java | 24 ++ .../extras/network/LTExtrasNetwork.java | 2 + .../ClientboundSetEntityMarkedPacket.java | 38 +++ .../textures/gui/sprites/cents_marker.png | Bin 0 -> 512 bytes .../gui/sprites/cents_marker.png.mcmeta | 12 + .../textures/gui/sprites/marker_box.png | Bin 0 -> 155 bytes .../gui/sprites/marker_box.png.mcmeta | 15 ++ .../textures/gui/sprites/no_cents_marker.png | Bin 0 -> 540 bytes .../gui/sprites/no_cents_marker.png.mcmeta | 12 + src/main/resources/ltextras.mixins.json | 1 + 17 files changed, 556 insertions(+), 7 deletions(-) create mode 100644 src/main/java/com/lovetropics/extras/client/ClientPlayerSensorEffects.java create mode 100644 src/main/java/com/lovetropics/extras/item/sensor/PlayerSensor.java create mode 100644 src/main/java/com/lovetropics/extras/item/sensor/ServerPlayerSensorManager.java create mode 100644 src/main/java/com/lovetropics/extras/item/sensor/package-info.java create mode 100644 src/main/java/com/lovetropics/extras/mixin/client/LivingEntityRendererMixin.java create mode 100644 src/main/java/com/lovetropics/extras/network/message/ClientboundSetEntityMarkedPacket.java create mode 100644 src/main/resources/assets/ltextras/textures/gui/sprites/cents_marker.png create mode 100644 src/main/resources/assets/ltextras/textures/gui/sprites/cents_marker.png.mcmeta create mode 100644 src/main/resources/assets/ltextras/textures/gui/sprites/marker_box.png create mode 100644 src/main/resources/assets/ltextras/textures/gui/sprites/marker_box.png.mcmeta create mode 100644 src/main/resources/assets/ltextras/textures/gui/sprites/no_cents_marker.png create mode 100644 src/main/resources/assets/ltextras/textures/gui/sprites/no_cents_marker.png.mcmeta diff --git a/build.gradle b/build.gradle index 44fae93..05b04c5 100644 --- a/build.gradle +++ b/build.gradle @@ -37,7 +37,7 @@ neoForge { runs { client { client() - programArguments.addAll '--username', 'Dev' + new Random().nextInt(999) + programArguments.addAll '--username', '"Dev###"' } server { diff --git a/src/main/java/com/lovetropics/extras/ExtraDataComponents.java b/src/main/java/com/lovetropics/extras/ExtraDataComponents.java index 9204028..ca3273c 100644 --- a/src/main/java/com/lovetropics/extras/ExtraDataComponents.java +++ b/src/main/java/com/lovetropics/extras/ExtraDataComponents.java @@ -4,6 +4,7 @@ import com.lovetropics.extras.data.poi.MapConfig; import com.lovetropics.extras.item.CollectibleCompassItem; import com.lovetropics.extras.item.ImageData; +import com.lovetropics.extras.item.sensor.PlayerSensor; import com.mojang.serialization.Codec; import net.minecraft.core.BlockPos; import net.minecraft.core.Holder; @@ -24,11 +25,11 @@ public class ExtraDataComponents { // Components for specific items public static final DeferredHolder, DataComponentType> COLLECTIBLE = REGISTER.registerComponentType( "collectible", - builder -> builder.persistent(CollectibleMarker.CODEC).networkSynchronized(CollectibleMarker.STREAM_CODEC) + builder -> builder.persistent(CollectibleMarker.CODEC).networkSynchronized(CollectibleMarker.STREAM_CODEC).cacheEncoding() ); public static final DeferredHolder, DataComponentType> COLLECTIBLE_TARGET = REGISTER.registerComponentType( "collectible_target", - builder -> builder.persistent(CollectibleCompassItem.Target.CODEC).networkSynchronized(CollectibleCompassItem.Target.STREAM_CODEC) + builder -> builder.persistent(CollectibleCompassItem.Target.CODEC).networkSynchronized(CollectibleCompassItem.Target.STREAM_CODEC).cacheEncoding() ); public static final DeferredHolder, DataComponentType> COIN_COUNT = REGISTER.registerComponentType( "coin_count", @@ -40,11 +41,11 @@ public class ExtraDataComponents { ); public static final DeferredHolder, DataComponentType> JUMP_PAD = REGISTER.registerComponentType( "jump_pad", - builder -> builder.persistent(BlockPos.CODEC).networkSynchronized(BlockPos.STREAM_CODEC) + builder -> builder.persistent(BlockPos.CODEC).networkSynchronized(BlockPos.STREAM_CODEC).cacheEncoding() ); public static final DeferredHolder, DataComponentType>> MAP = REGISTER.registerComponentType( "map", - builder -> builder.persistent(MapConfig.CODEC).networkSynchronized(MapConfig.STREAM_CODEC) + builder -> builder.persistent(MapConfig.CODEC).networkSynchronized(MapConfig.STREAM_CODEC).cacheEncoding() ); // General extensions @@ -54,10 +55,14 @@ public class ExtraDataComponents { ); public static final DeferredHolder, DataComponentType> COOLDOWN_OVERRIDE = REGISTER.registerComponentType( "cooldown_override", - builder -> builder.persistent(ExtraCodecs.NON_NEGATIVE_INT).networkSynchronized(ByteBufCodecs.VAR_INT) + builder -> builder.persistent(ExtraCodecs.NON_NEGATIVE_INT).networkSynchronized(ByteBufCodecs.VAR_INT).cacheEncoding() ); public static final DeferredHolder, DataComponentType> IMAGE = REGISTER.registerComponentType( "image", - builder -> builder.persistent(ImageData.CODEC) + builder -> builder.persistent(ImageData.CODEC).cacheEncoding() + ); + public static final DeferredHolder, DataComponentType> PLAYER_SENSOR = REGISTER.registerComponentType( + "player_sensor", + builder -> builder.persistent(PlayerSensor.CODEC).networkSynchronized(PlayerSensor.STREAM_CODEC).cacheEncoding() ); } diff --git a/src/main/java/com/lovetropics/extras/LTExtras.java b/src/main/java/com/lovetropics/extras/LTExtras.java index 419bfbd..df9f839 100644 --- a/src/main/java/com/lovetropics/extras/LTExtras.java +++ b/src/main/java/com/lovetropics/extras/LTExtras.java @@ -1,5 +1,6 @@ package com.lovetropics.extras; +import com.lovetropics.extras.client.ClientPlayerSensorEffects; import com.lovetropics.extras.client.command.NameTagModeCommand; import com.lovetropics.extras.client.entity.model.RaveKoaModel; import com.lovetropics.extras.client.particle.ExtraParticles; @@ -42,6 +43,7 @@ import net.neoforged.neoforge.client.event.EntityRenderersEvent; import net.neoforged.neoforge.client.event.RegisterClientCommandsEvent; import net.neoforged.neoforge.client.event.RegisterColorHandlersEvent; +import net.neoforged.neoforge.client.event.RegisterGuiLayersEvent; import net.neoforged.neoforge.common.NeoForge; import net.neoforged.neoforge.event.RegisterCommandsEvent; import net.neoforged.neoforge.event.entity.EntityAttributeModificationEvent; @@ -172,5 +174,10 @@ public static void registerItemColors(RegisterColorHandlersEvent.Item evt) { public static void registerLayerDefinitions(EntityRenderersEvent.RegisterLayerDefinitions event) { event.registerLayerDefinition(RaveKoaModel.LAYER_LOCATION, RaveKoaModel::createBodyLayer); } + + @SubscribeEvent + public static void registerGuiLayers(RegisterGuiLayersEvent event) { + ClientPlayerSensorEffects.registerGuiLayers(event); + } } } diff --git a/src/main/java/com/lovetropics/extras/client/ClientPlayerSensorEffects.java b/src/main/java/com/lovetropics/extras/client/ClientPlayerSensorEffects.java new file mode 100644 index 0000000..08d9d03 --- /dev/null +++ b/src/main/java/com/lovetropics/extras/client/ClientPlayerSensorEffects.java @@ -0,0 +1,234 @@ +package com.lovetropics.extras.client; + +import com.lovetropics.extras.LTExtras; +import com.lovetropics.extras.item.sensor.PlayerSensor; +import com.mojang.blaze3d.systems.RenderSystem; +import com.mojang.blaze3d.vertex.PoseStack; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectArraySet; +import net.minecraft.client.DeltaTracker; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.model.EntityModel; +import net.minecraft.client.model.HumanoidModel; +import net.minecraft.client.multiplayer.ClientLevel; +import net.minecraft.client.player.LocalPlayer; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.FastColor; +import net.minecraft.util.Mth; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.level.Level; +import net.neoforged.api.distmarker.Dist; +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.fml.common.EventBusSubscriber; +import net.neoforged.neoforge.client.event.ClientTickEvent; +import net.neoforged.neoforge.client.event.RegisterGuiLayersEvent; +import net.neoforged.neoforge.client.gui.VanillaGuiLayers; +import net.neoforged.neoforge.event.entity.EntityLeaveLevelEvent; +import org.joml.Vector3f; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.UUID; + +@EventBusSubscriber(modid = LTExtras.MODID, value = Dist.CLIENT) +public class ClientPlayerSensorEffects { + private static final ResourceLocation MARKER_BOX_SPRITE = LTExtras.location("marker_box"); + private static final int MARKER_BOX_INNER_PADDING = 32; + + private static final Minecraft CLIENT = Minecraft.getInstance(); + + private static final int VISIBLE_REFRESH_INTERVAL = 10; + + private static final Map MARKED_PLAYERS = new Object2ObjectOpenHashMap<>(); + private static final Set VISIBLE_MARKED_PLAYERS = new ObjectArraySet<>(); + + private static final List CAPTURED_SCREEN_POS = new ArrayList<>(); + + public static void mark(int entityId, PlayerSensor.Appearance appearance) { + ClientLevel level = CLIENT.level; + if (level != null && level.getEntity(entityId) instanceof Player player) { + MARKED_PLAYERS.put(player.getUUID(), appearance); + } + } + + public static void clear(int entityId) { + ClientLevel level = CLIENT.level; + if (level != null && level.getEntity(entityId) instanceof Player player) { + MARKED_PLAYERS.remove(player.getUUID()); + } + } + + public static void registerGuiLayers(RegisterGuiLayersEvent event) { + event.registerBelow(VanillaGuiLayers.CAMERA_OVERLAYS, LTExtras.location("player_sensor"), ClientPlayerSensorEffects::renderGui); + } + + private static void renderGui(GuiGraphics graphics, DeltaTracker deltaTracker) { + ClientLevel level = CLIENT.level; + if (level == null) { + return; + } + + for (CapturedScreenBoxes capturedScreenBoxes : CAPTURED_SCREEN_POS) { + UUID playerId = capturedScreenBoxes.playerId; + Player target = level.getPlayerByUUID(playerId); + PlayerSensor.Appearance appearance = MARKED_PLAYERS.get(playerId); + if (target == null || appearance == null) { + continue; + } + renderGuiMarker(graphics, capturedScreenBoxes, appearance); + } + + CAPTURED_SCREEN_POS.clear(); + } + + private static void renderGuiMarker(GuiGraphics graphics, CapturedScreenBoxes screenBoxes, PlayerSensor.Appearance appearance) { + GuiBox face = screenBoxes.face.toGui(graphics); + int faceSize = Math.max(face.width(), face.height()); + + float alpha = Mth.clampedMap(faceSize, 10, 20, 0.0f, 1.0f); + if (alpha <= Mth.EPSILON) { + return; + } + + int faceBoxSize = faceSize + MARKER_BOX_INNER_PADDING; + RenderSystem.enableBlend(); + + setColor(graphics, appearance.color(), alpha); + graphics.blitSprite(MARKER_BOX_SPRITE, face.centerX() - faceBoxSize / 2, face.centerY() - faceBoxSize / 2, faceBoxSize, faceBoxSize); + + Optional faceSprite = appearance.faceDecoration(); + if (faceSprite.isPresent()) { + int spriteWidth = faceSprite.get().width(); + int spriteHeight = faceSprite.get().height(); + graphics.setColor(1.0f, 1.0f, 1.0f, alpha); + graphics.blitSprite(faceSprite.get().location(), face.centerX() - spriteWidth / 2, face.centerY() - faceBoxSize / 2 - spriteHeight, spriteWidth, spriteHeight); + } + + graphics.setColor(1.0f, 1.0f, 1.0f, 1.0f); + + RenderSystem.disableBlend(); + } + + private static void setColor(GuiGraphics graphics, int color, float alpha) { + graphics.setColor( + FastColor.ARGB32.red(color) / 255.0f, + FastColor.ARGB32.green(color) / 255.0f, + FastColor.ARGB32.blue(color) / 255.0f, + alpha + ); + } + + @SubscribeEvent + public static void onStopTracking(EntityLeaveLevelEvent event) { + if (event.getLevel() instanceof ClientLevel) { + MARKED_PLAYERS.remove(event.getEntity().getUUID()); + } + } + + @SubscribeEvent + public static void onClientTick(ClientTickEvent.Post event) { + LocalPlayer player = CLIENT.player; + if (player == null) { + return; + } + if (player.tickCount % VISIBLE_REFRESH_INTERVAL == 0) { + refreshVisiblePlayers(player); + } + } + + private static void refreshVisiblePlayers(LocalPlayer player) { + VISIBLE_MARKED_PLAYERS.clear(); + Level level = player.level(); + for (UUID playerId : MARKED_PLAYERS.keySet()) { + Player target = level.getPlayerByUUID(playerId); + if (target != null && player.hasLineOfSight(target)) { + VISIBLE_MARKED_PLAYERS.add(target.getUUID()); + } + } + } + + public static void captureModelPose(T entity, EntityModel model, PoseStack poseStack) { + if (entity.getType() != EntityType.PLAYER || !VISIBLE_MARKED_PLAYERS.contains(entity.getUUID())) { + return; + } + if (model instanceof HumanoidModel humanoidModel) { + CAPTURED_SCREEN_POS.add(capturePlayerPose(entity, poseStack, humanoidModel)); + } + } + + private static CapturedScreenBoxes capturePlayerPose(LivingEntity entity, PoseStack poseStack, HumanoidModel humanoidModel) { + poseStack.pushPose(); + humanoidModel.head.translateAndRotate(poseStack); + ScreenBox faceBox = toScreenBox(poseStack, -4.0f, -8.0f, -4.0f, 4.0f, 0.0f, 4.0f); + poseStack.popPose(); + return new CapturedScreenBoxes(entity.getUUID(), faceBox); + } + + private static ScreenBox toScreenBox(PoseStack poseStack, float x0, float y0, float z0, float x1, float y1, float z1) { + Vector3f[] vertices = { + toScreenPos(poseStack, x0, y0, z0), + toScreenPos(poseStack, x0, y0, z1), + toScreenPos(poseStack, x0, y1, z0), + toScreenPos(poseStack, x0, y1, z1), + toScreenPos(poseStack, x1, y0, z0), + toScreenPos(poseStack, x1, y0, z1), + toScreenPos(poseStack, x1, y1, z0), + toScreenPos(poseStack, x1, y1, z1) + }; + float minX = Float.MAX_VALUE; + float minY = Float.MAX_VALUE; + float maxX = -Float.MAX_VALUE; + float maxY = -Float.MAX_VALUE; + for (Vector3f vertex : vertices) { + minX = Math.min(minX, vertex.x); + minY = Math.min(minY, vertex.y); + maxX = Math.max(maxX, vertex.x); + maxY = Math.max(maxY, vertex.y); + } + return new ScreenBox(minX, minY, maxX, maxY); + } + + private static Vector3f toScreenPos(PoseStack poseStack, float x, float y, float z) { + Vector3f pos = new Vector3f(x, y, z).mul(1.0f / 16.0f); + poseStack.last().pose().transformPosition(pos); + RenderSystem.getModelViewMatrix().transformPosition(pos); + RenderSystem.getProjectionMatrix().transformProject(pos); + return pos.set((pos.x + 1.0f) / 2.0f, 1.0f - (pos.y + 1.0f) / 2.0f, 0.0f); + } + + private record CapturedScreenBoxes(UUID playerId, ScreenBox face) { + } + + private record ScreenBox(float x0, float y0, float x1, float y1) { + public GuiBox toGui(GuiGraphics graphics) { + return new GuiBox( + Mth.floor(x0 * graphics.guiWidth()), Mth.floor(y0 * graphics.guiHeight()), + Mth.floor(x1 * graphics.guiWidth()), Mth.floor(y1 * graphics.guiHeight()) + ); + } + } + + private record GuiBox(int x0, int y0, int x1, int y1) { + public int centerX() { + return (x0 + x1) / 2; + } + + public int centerY() { + return (y0 + y1) / 2; + } + + public int width() { + return x1 - x0; + } + + public int height() { + return y1 - y0; + } + } +} diff --git a/src/main/java/com/lovetropics/extras/item/sensor/PlayerSensor.java b/src/main/java/com/lovetropics/extras/item/sensor/PlayerSensor.java new file mode 100644 index 0000000..83687f6 --- /dev/null +++ b/src/main/java/com/lovetropics/extras/item/sensor/PlayerSensor.java @@ -0,0 +1,62 @@ +package com.lovetropics.extras.item.sensor; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import io.netty.buffer.ByteBuf; +import net.minecraft.network.codec.ByteBufCodecs; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerPlayer; + +import java.util.Optional; + +public record PlayerSensor( + String tag, + Appearance appearance +) { + public static final Codec CODEC = RecordCodecBuilder.create(i -> i.group( + Codec.STRING.fieldOf("tag").forGetter(PlayerSensor::tag), + Appearance.CODEC.fieldOf("appearance").forGetter(PlayerSensor::appearance) + ).apply(i, PlayerSensor::new)); + + public static final StreamCodec STREAM_CODEC = StreamCodec.composite( + ByteBufCodecs.STRING_UTF8, PlayerSensor::tag, + Appearance.STREAM_CODEC, PlayerSensor::appearance, + PlayerSensor::new + ); + + public boolean matches(ServerPlayer player) { + return player.getTags().contains(tag); + } + + public record Appearance( + int color, + Optional faceDecoration + ) { + public static final Codec CODEC = RecordCodecBuilder.create(i -> i.group( + Codec.INT.fieldOf("color").forGetter(Appearance::color), + Sprite.CODEC.optionalFieldOf("face_decoration").forGetter(Appearance::faceDecoration) + ).apply(i, Appearance::new)); + + public static final StreamCodec STREAM_CODEC = StreamCodec.composite( + ByteBufCodecs.INT, Appearance::color, + Sprite.STREAM_CODEC.apply(ByteBufCodecs::optional), Appearance::faceDecoration, + Appearance::new + ); + } + + public record Sprite(ResourceLocation location, int width, int height) { + public static final Codec CODEC = RecordCodecBuilder.create(i -> i.group( + ResourceLocation.CODEC.fieldOf("location").forGetter(Sprite::location), + Codec.INT.fieldOf("width").forGetter(Sprite::width), + Codec.INT.fieldOf("height").forGetter(Sprite::height) + ).apply(i, Sprite::new)); + + public static final StreamCodec STREAM_CODEC = StreamCodec.composite( + ResourceLocation.STREAM_CODEC, Sprite::location, + ByteBufCodecs.VAR_INT, Sprite::width, + ByteBufCodecs.VAR_INT, Sprite::height, + Sprite::new + ); + } +} diff --git a/src/main/java/com/lovetropics/extras/item/sensor/ServerPlayerSensorManager.java b/src/main/java/com/lovetropics/extras/item/sensor/ServerPlayerSensorManager.java new file mode 100644 index 0000000..bdfb28e --- /dev/null +++ b/src/main/java/com/lovetropics/extras/item/sensor/ServerPlayerSensorManager.java @@ -0,0 +1,128 @@ +package com.lovetropics.extras.item.sensor; + +import com.lovetropics.extras.ExtraDataComponents; +import com.lovetropics.extras.LTExtras; +import com.lovetropics.extras.network.message.ClientboundSetEntityMarkedPacket; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.EquipmentSlot; +import net.minecraft.world.item.ItemStack; +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.fml.common.EventBusSubscriber; +import net.neoforged.neoforge.event.entity.player.PlayerEvent; +import net.neoforged.neoforge.event.tick.PlayerTickEvent; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.UUID; + +@EventBusSubscriber(modid = LTExtras.MODID) +public class ServerPlayerSensorManager { + private static final int REFRESH_INTERVAL_TICKS = 5; + + private static final Map SENSOR_STATES = new HashMap<>(); + + @SubscribeEvent + public static void onPlayerLoggedOut(PlayerEvent.PlayerLoggedOutEvent event) { + SENSOR_STATES.remove(event.getEntity().getUUID()); + } + + @SubscribeEvent + public static void onPlayerTick(PlayerTickEvent.Post event) { + if (event.getEntity() instanceof ServerPlayer player) { + ItemStack headItem = player.getItemBySlot(EquipmentSlot.HEAD); + PlayerSensor sensor = headItem.get(ExtraDataComponents.PLAYER_SENSOR); + if (sensor != null) { + SensorState sensorState = SENSOR_STATES.computeIfAbsent(player.getUUID(), playerId -> new SensorState(sensor)); + sensorState.activeSensor = sensor; + if (player.tickCount % REFRESH_INTERVAL_TICKS == 0) { + sensorState.refresh(player); + } + } else { + SensorState sensorState = SENSOR_STATES.remove(player.getUUID()); + if (sensorState != null) { + sensorState.clearAllMarked(player); + } + } + } + } + + @SubscribeEvent + public static void onPlayerTracked(PlayerEvent.StartTracking event) { + if (event.getEntity() instanceof ServerPlayer player && event.getTarget() instanceof ServerPlayer target) { + SensorState state = SENSOR_STATES.get(player.getUUID()); + if (state != null) { + state.startTracking(player, target); + } + } + } + + @SubscribeEvent + public static void onPlayerUntracked(PlayerEvent.StopTracking event) { + if (event.getEntity() instanceof ServerPlayer player && event.getTarget() instanceof ServerPlayer target) { + SensorState state = SENSOR_STATES.get(player.getUUID()); + if (state != null) { + state.stopTracking(target); + } + } + } + + private static class SensorState { + private PlayerSensor activeSensor; + private final Set trackedPlayers = new HashSet<>(); + private final Set markedPlayers = new HashSet<>(); + + private SensorState(PlayerSensor activeSensor) { + this.activeSensor = activeSensor; + } + + public void refresh(ServerPlayer player) { + ServerLevel level = player.serverLevel(); + + markedPlayers.removeIf(playerId -> { + if (!(level.getPlayerByUUID(playerId) instanceof ServerPlayer target)) { + // Shouldn't get here - but the client probably forgot about the player if we did too + return true; + } + if (!activeSensor.matches(target)) { + player.connection.send(new ClientboundSetEntityMarkedPacket(target.getId(), Optional.empty())); + return true; + } + return false; + }); + + for (UUID playerId : trackedPlayers) { + if (markedPlayers.contains(playerId)) { + continue; + } + if (level.getPlayerByUUID(playerId) instanceof ServerPlayer target && activeSensor.matches(target)) { + markedPlayers.add(playerId); + player.connection.send(new ClientboundSetEntityMarkedPacket(target.getId(), Optional.of(activeSensor.appearance()))); + } + } + } + + public void startTracking(ServerPlayer player, ServerPlayer target) { + trackedPlayers.add(target.getUUID()); + refresh(player); + } + + public void stopTracking(ServerPlayer target) { + // Client will forget the entity, we don't need to send anything + trackedPlayers.remove(target.getUUID()); + markedPlayers.remove(target.getUUID()); + } + + public void clearAllMarked(ServerPlayer player) { + for (UUID playerId : markedPlayers) { + if (player.level().getPlayerByUUID(playerId) instanceof ServerPlayer target) { + player.connection.send(new ClientboundSetEntityMarkedPacket(target.getId(), Optional.empty())); + } + } + markedPlayers.clear(); + } + } +} diff --git a/src/main/java/com/lovetropics/extras/item/sensor/package-info.java b/src/main/java/com/lovetropics/extras/item/sensor/package-info.java new file mode 100644 index 0000000..e30fcf4 --- /dev/null +++ b/src/main/java/com/lovetropics/extras/item/sensor/package-info.java @@ -0,0 +1,9 @@ +@ParametersAreNonnullByDefault +@MethodsReturnNonnullByDefault +@FieldsAreNonnullByDefault +package com.lovetropics.extras.item.sensor; + +import com.mojang.blaze3d.FieldsAreNonnullByDefault; +import com.mojang.blaze3d.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/src/main/java/com/lovetropics/extras/mixin/client/LivingEntityRendererMixin.java b/src/main/java/com/lovetropics/extras/mixin/client/LivingEntityRendererMixin.java new file mode 100644 index 0000000..20bece5 --- /dev/null +++ b/src/main/java/com/lovetropics/extras/mixin/client/LivingEntityRendererMixin.java @@ -0,0 +1,24 @@ +package com.lovetropics.extras.mixin.client; + +import com.lovetropics.extras.client.ClientPlayerSensorEffects; +import com.mojang.blaze3d.vertex.PoseStack; +import net.minecraft.client.model.EntityModel; +import net.minecraft.client.renderer.MultiBufferSource; +import net.minecraft.client.renderer.entity.LivingEntityRenderer; +import net.minecraft.world.entity.LivingEntity; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(LivingEntityRenderer.class) +public class LivingEntityRendererMixin { + @Shadow + protected EntityModel model; + + @Inject(method = "render(Lnet/minecraft/world/entity/LivingEntity;FFLcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/MultiBufferSource;I)V", at = @At(value = "INVOKE", target = "Lcom/mojang/blaze3d/vertex/PoseStack;popPose()V")) + private void render(T entity, float entityYaw, float partialTicks, PoseStack poseStack, MultiBufferSource buffer, int packedLight, CallbackInfo ci) { + ClientPlayerSensorEffects.captureModelPose(entity, model, poseStack); + } +} diff --git a/src/main/java/com/lovetropics/extras/network/LTExtrasNetwork.java b/src/main/java/com/lovetropics/extras/network/LTExtrasNetwork.java index 95c575f..40e2531 100644 --- a/src/main/java/com/lovetropics/extras/network/LTExtrasNetwork.java +++ b/src/main/java/com/lovetropics/extras/network/LTExtrasNetwork.java @@ -5,6 +5,7 @@ import com.lovetropics.extras.network.message.ClientboundOpenCollectibleBasketPacket; import com.lovetropics.extras.network.message.ClientboundPoiFacesPacket; import com.lovetropics.extras.network.message.ClientboundRemovePoiPacket; +import com.lovetropics.extras.network.message.ClientboundSetEntityMarkedPacket; import com.lovetropics.extras.network.message.ClientboundUpdatePackControl; import com.lovetropics.extras.network.message.ClientboundUpdatePoiPacket; import com.lovetropics.extras.network.message.ClientboundSetAutoRejoinIntent; @@ -38,5 +39,6 @@ public static void registerPackets(RegisterPayloadHandlersEvent event) { registrar.playToClient(ClientboundPoiFacesPacket.TYPE, ClientboundPoiFacesPacket.STREAM_CODEC, ClientboundPoiFacesPacket::handle); registrar.playToClient(ClientboundOpenCollectibleBasketPacket.TYPE, ClientboundOpenCollectibleBasketPacket.STREAM_CODEC, ClientboundOpenCollectibleBasketPacket::handle); registrar.playToClient(ClientboundUpdatePackControl.TYPE, ClientboundUpdatePackControl.STREAM_CODEC, ClientboundUpdatePackControl::handle); + registrar.playToClient(ClientboundSetEntityMarkedPacket.TYPE, ClientboundSetEntityMarkedPacket.STREAM_CODEC, ClientboundSetEntityMarkedPacket::handle); } } diff --git a/src/main/java/com/lovetropics/extras/network/message/ClientboundSetEntityMarkedPacket.java b/src/main/java/com/lovetropics/extras/network/message/ClientboundSetEntityMarkedPacket.java new file mode 100644 index 0000000..160fb58 --- /dev/null +++ b/src/main/java/com/lovetropics/extras/network/message/ClientboundSetEntityMarkedPacket.java @@ -0,0 +1,38 @@ +package com.lovetropics.extras.network.message; + +import com.lovetropics.extras.LTExtras; +import com.lovetropics.extras.client.ClientPlayerSensorEffects; +import com.lovetropics.extras.item.sensor.PlayerSensor; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.ByteBufCodecs; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.neoforged.neoforge.network.handling.IPayloadContext; + +import java.util.Optional; + +public record ClientboundSetEntityMarkedPacket( + int entityId, + Optional appearance +) implements CustomPacketPayload { + public static final StreamCodec STREAM_CODEC = StreamCodec.composite( + ByteBufCodecs.VAR_INT, ClientboundSetEntityMarkedPacket::entityId, + PlayerSensor.Appearance.STREAM_CODEC.apply(ByteBufCodecs::optional), ClientboundSetEntityMarkedPacket::appearance, + ClientboundSetEntityMarkedPacket::new + ); + + public static final Type TYPE = new Type<>(LTExtras.location("set_entity_marked")); + + public static void handle(ClientboundSetEntityMarkedPacket packet, IPayloadContext context) { + if (packet.appearance.isPresent()) { + ClientPlayerSensorEffects.mark(packet.entityId, packet.appearance.get()); + } else { + ClientPlayerSensorEffects.clear(packet.entityId); + } + } + + @Override + public Type type() { + return TYPE; + } +} diff --git a/src/main/resources/assets/ltextras/textures/gui/sprites/cents_marker.png b/src/main/resources/assets/ltextras/textures/gui/sprites/cents_marker.png new file mode 100644 index 0000000000000000000000000000000000000000..ac02b1e9af99e3f47f19166974b3eecc2c820f55 GIT binary patch literal 512 zcmV+b0{{JqP)+EurJA`k4UNuDl_Ar-fh7byMcXW>|VaEcSN zN@UXnhYg*D+HDLg?;Sm)dYOY`vJJC<&m_jLJPP5z8zdbXw;S?GupZ{RU>z8cXYX|X zgJHwFRHd|)Jgh&aCSUVa3CaGyk!g-sf%Qx_RY8V-kF2g75dLHXw2i^j)z4*}Q$iB} DNI^8l literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/ltextras/textures/gui/sprites/marker_box.png.mcmeta b/src/main/resources/assets/ltextras/textures/gui/sprites/marker_box.png.mcmeta new file mode 100644 index 0000000..dcddeb6 --- /dev/null +++ b/src/main/resources/assets/ltextras/textures/gui/sprites/marker_box.png.mcmeta @@ -0,0 +1,15 @@ +{ + "gui": { + "scaling": { + "type": "nine_slice", + "width": 64, + "height": 64, + "border": { + "left": 24, + "top": 26, + "right": 26, + "bottom": 24 + } + } + } +} diff --git a/src/main/resources/assets/ltextras/textures/gui/sprites/no_cents_marker.png b/src/main/resources/assets/ltextras/textures/gui/sprites/no_cents_marker.png new file mode 100644 index 0000000000000000000000000000000000000000..67a954f9a5f27916366e1672d8d83cffffa50453 GIT binary patch literal 540 zcmeAS@N?(olHy`uVBq!ia0vp^7C@Z9!3-qpTOGFnDb4_&5LY0rmGwZ|yvS3hG?9ukqXQvhZZqTQGi|^}6tV*2}`TPz+*&u&jpdy2VS6m;UqVXS8r+FDRdW z{X#DPt7qj~Qfez-z0EMt{Js30m>WY^_u;oMpT@+rZcfUHXb(Id^x(PRi~2yT7FiR< z+u0rW6ImXxykdOwc9SFbp08zF4p^;vr+B?&Pw6VTDF-JO+I!_2Jme4A%C;$G>-UW* zUxVcvpDgHSvV6H%`ueXMiSLu!YpveYzKJc>*?;?L#KS^4?lq4mnD*PQaW4(2-_rM= ZVSy5RW7C=YSAj9h;OXk;vd$@?2>`YI@%#V) literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/ltextras/textures/gui/sprites/no_cents_marker.png.mcmeta b/src/main/resources/assets/ltextras/textures/gui/sprites/no_cents_marker.png.mcmeta new file mode 100644 index 0000000..509c6af --- /dev/null +++ b/src/main/resources/assets/ltextras/textures/gui/sprites/no_cents_marker.png.mcmeta @@ -0,0 +1,12 @@ +{ + "animation": { + "height": 24, + "frametime": 10, + "frames": [ + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2 + ] + } +} diff --git a/src/main/resources/ltextras.mixins.json b/src/main/resources/ltextras.mixins.json index a1f2189..943ecab 100644 --- a/src/main/resources/ltextras.mixins.json +++ b/src/main/resources/ltextras.mixins.json @@ -35,6 +35,7 @@ ], "client": [ "client.IconSetMixin", + "client.LivingEntityRendererMixin", "client.LocalPlayerMixin", "client.collectible.MultiPlayerGameModeMixin", "client.menu.LogoRendererMixin",