|
| 1 | +package com.lovetropics.extras.client; |
| 2 | + |
| 3 | +import com.lovetropics.extras.LTExtras; |
| 4 | +import com.lovetropics.extras.item.sensor.PlayerSensor; |
| 5 | +import com.mojang.blaze3d.systems.RenderSystem; |
| 6 | +import com.mojang.blaze3d.vertex.PoseStack; |
| 7 | +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; |
| 8 | +import it.unimi.dsi.fastutil.objects.ObjectArraySet; |
| 9 | +import net.minecraft.client.DeltaTracker; |
| 10 | +import net.minecraft.client.Minecraft; |
| 11 | +import net.minecraft.client.gui.GuiGraphics; |
| 12 | +import net.minecraft.client.model.EntityModel; |
| 13 | +import net.minecraft.client.model.HumanoidModel; |
| 14 | +import net.minecraft.client.multiplayer.ClientLevel; |
| 15 | +import net.minecraft.client.player.LocalPlayer; |
| 16 | +import net.minecraft.resources.ResourceLocation; |
| 17 | +import net.minecraft.util.FastColor; |
| 18 | +import net.minecraft.util.Mth; |
| 19 | +import net.minecraft.world.entity.EntityType; |
| 20 | +import net.minecraft.world.entity.LivingEntity; |
| 21 | +import net.minecraft.world.entity.player.Player; |
| 22 | +import net.minecraft.world.level.Level; |
| 23 | +import net.neoforged.api.distmarker.Dist; |
| 24 | +import net.neoforged.bus.api.SubscribeEvent; |
| 25 | +import net.neoforged.fml.common.EventBusSubscriber; |
| 26 | +import net.neoforged.neoforge.client.event.ClientTickEvent; |
| 27 | +import net.neoforged.neoforge.client.event.RegisterGuiLayersEvent; |
| 28 | +import net.neoforged.neoforge.client.gui.VanillaGuiLayers; |
| 29 | +import net.neoforged.neoforge.event.entity.EntityLeaveLevelEvent; |
| 30 | +import org.joml.Vector3f; |
| 31 | + |
| 32 | +import java.util.ArrayList; |
| 33 | +import java.util.List; |
| 34 | +import java.util.Map; |
| 35 | +import java.util.Optional; |
| 36 | +import java.util.Set; |
| 37 | +import java.util.UUID; |
| 38 | + |
| 39 | +@EventBusSubscriber(modid = LTExtras.MODID, value = Dist.CLIENT) |
| 40 | +public class ClientPlayerSensorEffects { |
| 41 | + private static final ResourceLocation MARKER_BOX_SPRITE = LTExtras.location("marker_box"); |
| 42 | + private static final int MARKER_BOX_INNER_PADDING = 32; |
| 43 | + |
| 44 | + private static final Minecraft CLIENT = Minecraft.getInstance(); |
| 45 | + |
| 46 | + private static final int VISIBLE_REFRESH_INTERVAL = 10; |
| 47 | + |
| 48 | + private static final Map<UUID, PlayerSensor.Appearance> MARKED_PLAYERS = new Object2ObjectOpenHashMap<>(); |
| 49 | + private static final Set<UUID> VISIBLE_MARKED_PLAYERS = new ObjectArraySet<>(); |
| 50 | + |
| 51 | + private static final List<CapturedScreenBoxes> CAPTURED_SCREEN_POS = new ArrayList<>(); |
| 52 | + |
| 53 | + public static void mark(int entityId, PlayerSensor.Appearance appearance) { |
| 54 | + ClientLevel level = CLIENT.level; |
| 55 | + if (level != null && level.getEntity(entityId) instanceof Player player) { |
| 56 | + MARKED_PLAYERS.put(player.getUUID(), appearance); |
| 57 | + } |
| 58 | + } |
| 59 | + |
| 60 | + public static void clear(int entityId) { |
| 61 | + ClientLevel level = CLIENT.level; |
| 62 | + if (level != null && level.getEntity(entityId) instanceof Player player) { |
| 63 | + MARKED_PLAYERS.remove(player.getUUID()); |
| 64 | + } |
| 65 | + } |
| 66 | + |
| 67 | + public static void registerGuiLayers(RegisterGuiLayersEvent event) { |
| 68 | + event.registerBelow(VanillaGuiLayers.CAMERA_OVERLAYS, LTExtras.location("player_sensor"), ClientPlayerSensorEffects::renderGui); |
| 69 | + } |
| 70 | + |
| 71 | + private static void renderGui(GuiGraphics graphics, DeltaTracker deltaTracker) { |
| 72 | + ClientLevel level = CLIENT.level; |
| 73 | + if (level == null) { |
| 74 | + return; |
| 75 | + } |
| 76 | + |
| 77 | + for (CapturedScreenBoxes capturedScreenBoxes : CAPTURED_SCREEN_POS) { |
| 78 | + UUID playerId = capturedScreenBoxes.playerId; |
| 79 | + Player target = level.getPlayerByUUID(playerId); |
| 80 | + PlayerSensor.Appearance appearance = MARKED_PLAYERS.get(playerId); |
| 81 | + if (target == null || appearance == null) { |
| 82 | + continue; |
| 83 | + } |
| 84 | + renderGuiMarker(graphics, capturedScreenBoxes, appearance); |
| 85 | + } |
| 86 | + |
| 87 | + CAPTURED_SCREEN_POS.clear(); |
| 88 | + } |
| 89 | + |
| 90 | + private static void renderGuiMarker(GuiGraphics graphics, CapturedScreenBoxes screenBoxes, PlayerSensor.Appearance appearance) { |
| 91 | + GuiBox face = screenBoxes.face.toGui(graphics); |
| 92 | + int faceSize = Math.max(face.width(), face.height()); |
| 93 | + |
| 94 | + float alpha = Mth.clampedMap(faceSize, 10, 20, 0.0f, 1.0f); |
| 95 | + if (alpha <= Mth.EPSILON) { |
| 96 | + return; |
| 97 | + } |
| 98 | + |
| 99 | + int faceBoxSize = faceSize + MARKER_BOX_INNER_PADDING; |
| 100 | + RenderSystem.enableBlend(); |
| 101 | + |
| 102 | + setColor(graphics, appearance.color(), alpha); |
| 103 | + graphics.blitSprite(MARKER_BOX_SPRITE, face.centerX() - faceBoxSize / 2, face.centerY() - faceBoxSize / 2, faceBoxSize, faceBoxSize); |
| 104 | + |
| 105 | + Optional<PlayerSensor.Sprite> faceSprite = appearance.faceDecoration(); |
| 106 | + if (faceSprite.isPresent()) { |
| 107 | + int spriteWidth = faceSprite.get().width(); |
| 108 | + int spriteHeight = faceSprite.get().height(); |
| 109 | + graphics.setColor(1.0f, 1.0f, 1.0f, alpha); |
| 110 | + graphics.blitSprite(faceSprite.get().location(), face.centerX() - spriteWidth / 2, face.centerY() - faceBoxSize / 2 - spriteHeight, spriteWidth, spriteHeight); |
| 111 | + } |
| 112 | + |
| 113 | + graphics.setColor(1.0f, 1.0f, 1.0f, 1.0f); |
| 114 | + |
| 115 | + RenderSystem.disableBlend(); |
| 116 | + } |
| 117 | + |
| 118 | + private static void setColor(GuiGraphics graphics, int color, float alpha) { |
| 119 | + graphics.setColor( |
| 120 | + FastColor.ARGB32.red(color) / 255.0f, |
| 121 | + FastColor.ARGB32.green(color) / 255.0f, |
| 122 | + FastColor.ARGB32.blue(color) / 255.0f, |
| 123 | + alpha |
| 124 | + ); |
| 125 | + } |
| 126 | + |
| 127 | + @SubscribeEvent |
| 128 | + public static void onStopTracking(EntityLeaveLevelEvent event) { |
| 129 | + if (event.getLevel() instanceof ClientLevel) { |
| 130 | + MARKED_PLAYERS.remove(event.getEntity().getUUID()); |
| 131 | + } |
| 132 | + } |
| 133 | + |
| 134 | + @SubscribeEvent |
| 135 | + public static void onClientTick(ClientTickEvent.Post event) { |
| 136 | + LocalPlayer player = CLIENT.player; |
| 137 | + if (player == null) { |
| 138 | + return; |
| 139 | + } |
| 140 | + if (player.tickCount % VISIBLE_REFRESH_INTERVAL == 0) { |
| 141 | + refreshVisiblePlayers(player); |
| 142 | + } |
| 143 | + } |
| 144 | + |
| 145 | + private static void refreshVisiblePlayers(LocalPlayer player) { |
| 146 | + VISIBLE_MARKED_PLAYERS.clear(); |
| 147 | + Level level = player.level(); |
| 148 | + for (UUID playerId : MARKED_PLAYERS.keySet()) { |
| 149 | + Player target = level.getPlayerByUUID(playerId); |
| 150 | + if (target != null && player.hasLineOfSight(target)) { |
| 151 | + VISIBLE_MARKED_PLAYERS.add(target.getUUID()); |
| 152 | + } |
| 153 | + } |
| 154 | + } |
| 155 | + |
| 156 | + public static <T extends LivingEntity> void captureModelPose(T entity, EntityModel<T> model, PoseStack poseStack) { |
| 157 | + if (entity.getType() != EntityType.PLAYER || !VISIBLE_MARKED_PLAYERS.contains(entity.getUUID())) { |
| 158 | + return; |
| 159 | + } |
| 160 | + if (model instanceof HumanoidModel<T> humanoidModel) { |
| 161 | + CAPTURED_SCREEN_POS.add(capturePlayerPose(entity, poseStack, humanoidModel)); |
| 162 | + } |
| 163 | + } |
| 164 | + |
| 165 | + private static CapturedScreenBoxes capturePlayerPose(LivingEntity entity, PoseStack poseStack, HumanoidModel<?> humanoidModel) { |
| 166 | + poseStack.pushPose(); |
| 167 | + humanoidModel.head.translateAndRotate(poseStack); |
| 168 | + ScreenBox faceBox = toScreenBox(poseStack, -4.0f, -8.0f, -4.0f, 4.0f, 0.0f, 4.0f); |
| 169 | + poseStack.popPose(); |
| 170 | + return new CapturedScreenBoxes(entity.getUUID(), faceBox); |
| 171 | + } |
| 172 | + |
| 173 | + private static ScreenBox toScreenBox(PoseStack poseStack, float x0, float y0, float z0, float x1, float y1, float z1) { |
| 174 | + Vector3f[] vertices = { |
| 175 | + toScreenPos(poseStack, x0, y0, z0), |
| 176 | + toScreenPos(poseStack, x0, y0, z1), |
| 177 | + toScreenPos(poseStack, x0, y1, z0), |
| 178 | + toScreenPos(poseStack, x0, y1, z1), |
| 179 | + toScreenPos(poseStack, x1, y0, z0), |
| 180 | + toScreenPos(poseStack, x1, y0, z1), |
| 181 | + toScreenPos(poseStack, x1, y1, z0), |
| 182 | + toScreenPos(poseStack, x1, y1, z1) |
| 183 | + }; |
| 184 | + float minX = Float.MAX_VALUE; |
| 185 | + float minY = Float.MAX_VALUE; |
| 186 | + float maxX = -Float.MAX_VALUE; |
| 187 | + float maxY = -Float.MAX_VALUE; |
| 188 | + for (Vector3f vertex : vertices) { |
| 189 | + minX = Math.min(minX, vertex.x); |
| 190 | + minY = Math.min(minY, vertex.y); |
| 191 | + maxX = Math.max(maxX, vertex.x); |
| 192 | + maxY = Math.max(maxY, vertex.y); |
| 193 | + } |
| 194 | + return new ScreenBox(minX, minY, maxX, maxY); |
| 195 | + } |
| 196 | + |
| 197 | + private static Vector3f toScreenPos(PoseStack poseStack, float x, float y, float z) { |
| 198 | + Vector3f pos = new Vector3f(x, y, z).mul(1.0f / 16.0f); |
| 199 | + poseStack.last().pose().transformPosition(pos); |
| 200 | + RenderSystem.getModelViewMatrix().transformPosition(pos); |
| 201 | + RenderSystem.getProjectionMatrix().transformProject(pos); |
| 202 | + return pos.set((pos.x + 1.0f) / 2.0f, 1.0f - (pos.y + 1.0f) / 2.0f, 0.0f); |
| 203 | + } |
| 204 | + |
| 205 | + private record CapturedScreenBoxes(UUID playerId, ScreenBox face) { |
| 206 | + } |
| 207 | + |
| 208 | + private record ScreenBox(float x0, float y0, float x1, float y1) { |
| 209 | + public GuiBox toGui(GuiGraphics graphics) { |
| 210 | + return new GuiBox( |
| 211 | + Mth.floor(x0 * graphics.guiWidth()), Mth.floor(y0 * graphics.guiHeight()), |
| 212 | + Mth.floor(x1 * graphics.guiWidth()), Mth.floor(y1 * graphics.guiHeight()) |
| 213 | + ); |
| 214 | + } |
| 215 | + } |
| 216 | + |
| 217 | + private record GuiBox(int x0, int y0, int x1, int y1) { |
| 218 | + public int centerX() { |
| 219 | + return (x0 + x1) / 2; |
| 220 | + } |
| 221 | + |
| 222 | + public int centerY() { |
| 223 | + return (y0 + y1) / 2; |
| 224 | + } |
| 225 | + |
| 226 | + public int width() { |
| 227 | + return x1 - x0; |
| 228 | + } |
| 229 | + |
| 230 | + public int height() { |
| 231 | + return y1 - y0; |
| 232 | + } |
| 233 | + } |
| 234 | +} |
0 commit comments