Skip to content

Commit

Permalink
Player sensor component
Browse files Browse the repository at this point in the history
  • Loading branch information
Gegy committed Nov 22, 2024
1 parent d0f9612 commit 6a29818
Show file tree
Hide file tree
Showing 17 changed files with 556 additions and 7 deletions.
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ neoForge {
runs {
client {
client()
programArguments.addAll '--username', 'Dev' + new Random().nextInt(999)
programArguments.addAll '--username', '"Dev###"'
}

server {
Expand Down
17 changes: 11 additions & 6 deletions src/main/java/com/lovetropics/extras/ExtraDataComponents.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -24,11 +25,11 @@ public class ExtraDataComponents {
// Components for specific items
public static final DeferredHolder<DataComponentType<?>, DataComponentType<CollectibleMarker>> 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<?>, DataComponentType<CollectibleCompassItem.Target>> 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<?>, DataComponentType<Integer>> COIN_COUNT = REGISTER.registerComponentType(
"coin_count",
Expand All @@ -40,11 +41,11 @@ public class ExtraDataComponents {
);
public static final DeferredHolder<DataComponentType<?>, DataComponentType<BlockPos>> 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<?>, DataComponentType<Holder<MapConfig>>> 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
Expand All @@ -54,10 +55,14 @@ public class ExtraDataComponents {
);
public static final DeferredHolder<DataComponentType<?>, DataComponentType<Integer>> 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<?>, DataComponentType<ImageData>> IMAGE = REGISTER.registerComponentType(
"image",
builder -> builder.persistent(ImageData.CODEC)
builder -> builder.persistent(ImageData.CODEC).cacheEncoding()
);
public static final DeferredHolder<DataComponentType<?>, DataComponentType<PlayerSensor>> PLAYER_SENSOR = REGISTER.registerComponentType(
"player_sensor",
builder -> builder.persistent(PlayerSensor.CODEC).networkSynchronized(PlayerSensor.STREAM_CODEC).cacheEncoding()
);
}
7 changes: 7 additions & 0 deletions src/main/java/com/lovetropics/extras/LTExtras.java
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}
}
}
Original file line number Diff line number Diff line change
@@ -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<UUID, PlayerSensor.Appearance> MARKED_PLAYERS = new Object2ObjectOpenHashMap<>();
private static final Set<UUID> VISIBLE_MARKED_PLAYERS = new ObjectArraySet<>();

private static final List<CapturedScreenBoxes> 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<PlayerSensor.Sprite> 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 <T extends LivingEntity> void captureModelPose(T entity, EntityModel<T> model, PoseStack poseStack) {
if (entity.getType() != EntityType.PLAYER || !VISIBLE_MARKED_PLAYERS.contains(entity.getUUID())) {
return;
}
if (model instanceof HumanoidModel<T> 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;
}
}
}
62 changes: 62 additions & 0 deletions src/main/java/com/lovetropics/extras/item/sensor/PlayerSensor.java
Original file line number Diff line number Diff line change
@@ -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<PlayerSensor> 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<ByteBuf, PlayerSensor> 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<Sprite> faceDecoration
) {
public static final Codec<Appearance> 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<ByteBuf, Appearance> 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<Sprite> 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<ByteBuf, Sprite> STREAM_CODEC = StreamCodec.composite(
ResourceLocation.STREAM_CODEC, Sprite::location,
ByteBufCodecs.VAR_INT, Sprite::width,
ByteBufCodecs.VAR_INT, Sprite::height,
Sprite::new
);
}
}
Loading

0 comments on commit 6a29818

Please sign in to comment.