Skip to content

Commit 6a29818

Browse files
committed
Player sensor component
1 parent d0f9612 commit 6a29818

17 files changed

+556
-7
lines changed

build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ neoForge {
3737
runs {
3838
client {
3939
client()
40-
programArguments.addAll '--username', 'Dev' + new Random().nextInt(999)
40+
programArguments.addAll '--username', '"Dev###"'
4141
}
4242

4343
server {

src/main/java/com/lovetropics/extras/ExtraDataComponents.java

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import com.lovetropics.extras.data.poi.MapConfig;
55
import com.lovetropics.extras.item.CollectibleCompassItem;
66
import com.lovetropics.extras.item.ImageData;
7+
import com.lovetropics.extras.item.sensor.PlayerSensor;
78
import com.mojang.serialization.Codec;
89
import net.minecraft.core.BlockPos;
910
import net.minecraft.core.Holder;
@@ -24,11 +25,11 @@ public class ExtraDataComponents {
2425
// Components for specific items
2526
public static final DeferredHolder<DataComponentType<?>, DataComponentType<CollectibleMarker>> COLLECTIBLE = REGISTER.registerComponentType(
2627
"collectible",
27-
builder -> builder.persistent(CollectibleMarker.CODEC).networkSynchronized(CollectibleMarker.STREAM_CODEC)
28+
builder -> builder.persistent(CollectibleMarker.CODEC).networkSynchronized(CollectibleMarker.STREAM_CODEC).cacheEncoding()
2829
);
2930
public static final DeferredHolder<DataComponentType<?>, DataComponentType<CollectibleCompassItem.Target>> COLLECTIBLE_TARGET = REGISTER.registerComponentType(
3031
"collectible_target",
31-
builder -> builder.persistent(CollectibleCompassItem.Target.CODEC).networkSynchronized(CollectibleCompassItem.Target.STREAM_CODEC)
32+
builder -> builder.persistent(CollectibleCompassItem.Target.CODEC).networkSynchronized(CollectibleCompassItem.Target.STREAM_CODEC).cacheEncoding()
3233
);
3334
public static final DeferredHolder<DataComponentType<?>, DataComponentType<Integer>> COIN_COUNT = REGISTER.registerComponentType(
3435
"coin_count",
@@ -40,11 +41,11 @@ public class ExtraDataComponents {
4041
);
4142
public static final DeferredHolder<DataComponentType<?>, DataComponentType<BlockPos>> JUMP_PAD = REGISTER.registerComponentType(
4243
"jump_pad",
43-
builder -> builder.persistent(BlockPos.CODEC).networkSynchronized(BlockPos.STREAM_CODEC)
44+
builder -> builder.persistent(BlockPos.CODEC).networkSynchronized(BlockPos.STREAM_CODEC).cacheEncoding()
4445
);
4546
public static final DeferredHolder<DataComponentType<?>, DataComponentType<Holder<MapConfig>>> MAP = REGISTER.registerComponentType(
4647
"map",
47-
builder -> builder.persistent(MapConfig.CODEC).networkSynchronized(MapConfig.STREAM_CODEC)
48+
builder -> builder.persistent(MapConfig.CODEC).networkSynchronized(MapConfig.STREAM_CODEC).cacheEncoding()
4849
);
4950

5051
// General extensions
@@ -54,10 +55,14 @@ public class ExtraDataComponents {
5455
);
5556
public static final DeferredHolder<DataComponentType<?>, DataComponentType<Integer>> COOLDOWN_OVERRIDE = REGISTER.registerComponentType(
5657
"cooldown_override",
57-
builder -> builder.persistent(ExtraCodecs.NON_NEGATIVE_INT).networkSynchronized(ByteBufCodecs.VAR_INT)
58+
builder -> builder.persistent(ExtraCodecs.NON_NEGATIVE_INT).networkSynchronized(ByteBufCodecs.VAR_INT).cacheEncoding()
5859
);
5960
public static final DeferredHolder<DataComponentType<?>, DataComponentType<ImageData>> IMAGE = REGISTER.registerComponentType(
6061
"image",
61-
builder -> builder.persistent(ImageData.CODEC)
62+
builder -> builder.persistent(ImageData.CODEC).cacheEncoding()
63+
);
64+
public static final DeferredHolder<DataComponentType<?>, DataComponentType<PlayerSensor>> PLAYER_SENSOR = REGISTER.registerComponentType(
65+
"player_sensor",
66+
builder -> builder.persistent(PlayerSensor.CODEC).networkSynchronized(PlayerSensor.STREAM_CODEC).cacheEncoding()
6267
);
6368
}

src/main/java/com/lovetropics/extras/LTExtras.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.lovetropics.extras;
22

3+
import com.lovetropics.extras.client.ClientPlayerSensorEffects;
34
import com.lovetropics.extras.client.command.NameTagModeCommand;
45
import com.lovetropics.extras.client.entity.model.RaveKoaModel;
56
import com.lovetropics.extras.client.particle.ExtraParticles;
@@ -42,6 +43,7 @@
4243
import net.neoforged.neoforge.client.event.EntityRenderersEvent;
4344
import net.neoforged.neoforge.client.event.RegisterClientCommandsEvent;
4445
import net.neoforged.neoforge.client.event.RegisterColorHandlersEvent;
46+
import net.neoforged.neoforge.client.event.RegisterGuiLayersEvent;
4547
import net.neoforged.neoforge.common.NeoForge;
4648
import net.neoforged.neoforge.event.RegisterCommandsEvent;
4749
import net.neoforged.neoforge.event.entity.EntityAttributeModificationEvent;
@@ -172,5 +174,10 @@ public static void registerItemColors(RegisterColorHandlersEvent.Item evt) {
172174
public static void registerLayerDefinitions(EntityRenderersEvent.RegisterLayerDefinitions event) {
173175
event.registerLayerDefinition(RaveKoaModel.LAYER_LOCATION, RaveKoaModel::createBodyLayer);
174176
}
177+
178+
@SubscribeEvent
179+
public static void registerGuiLayers(RegisterGuiLayersEvent event) {
180+
ClientPlayerSensorEffects.registerGuiLayers(event);
181+
}
175182
}
176183
}
Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
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+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package com.lovetropics.extras.item.sensor;
2+
3+
import com.mojang.serialization.Codec;
4+
import com.mojang.serialization.codecs.RecordCodecBuilder;
5+
import io.netty.buffer.ByteBuf;
6+
import net.minecraft.network.codec.ByteBufCodecs;
7+
import net.minecraft.network.codec.StreamCodec;
8+
import net.minecraft.resources.ResourceLocation;
9+
import net.minecraft.server.level.ServerPlayer;
10+
11+
import java.util.Optional;
12+
13+
public record PlayerSensor(
14+
String tag,
15+
Appearance appearance
16+
) {
17+
public static final Codec<PlayerSensor> CODEC = RecordCodecBuilder.create(i -> i.group(
18+
Codec.STRING.fieldOf("tag").forGetter(PlayerSensor::tag),
19+
Appearance.CODEC.fieldOf("appearance").forGetter(PlayerSensor::appearance)
20+
).apply(i, PlayerSensor::new));
21+
22+
public static final StreamCodec<ByteBuf, PlayerSensor> STREAM_CODEC = StreamCodec.composite(
23+
ByteBufCodecs.STRING_UTF8, PlayerSensor::tag,
24+
Appearance.STREAM_CODEC, PlayerSensor::appearance,
25+
PlayerSensor::new
26+
);
27+
28+
public boolean matches(ServerPlayer player) {
29+
return player.getTags().contains(tag);
30+
}
31+
32+
public record Appearance(
33+
int color,
34+
Optional<Sprite> faceDecoration
35+
) {
36+
public static final Codec<Appearance> CODEC = RecordCodecBuilder.create(i -> i.group(
37+
Codec.INT.fieldOf("color").forGetter(Appearance::color),
38+
Sprite.CODEC.optionalFieldOf("face_decoration").forGetter(Appearance::faceDecoration)
39+
).apply(i, Appearance::new));
40+
41+
public static final StreamCodec<ByteBuf, Appearance> STREAM_CODEC = StreamCodec.composite(
42+
ByteBufCodecs.INT, Appearance::color,
43+
Sprite.STREAM_CODEC.apply(ByteBufCodecs::optional), Appearance::faceDecoration,
44+
Appearance::new
45+
);
46+
}
47+
48+
public record Sprite(ResourceLocation location, int width, int height) {
49+
public static final Codec<Sprite> CODEC = RecordCodecBuilder.create(i -> i.group(
50+
ResourceLocation.CODEC.fieldOf("location").forGetter(Sprite::location),
51+
Codec.INT.fieldOf("width").forGetter(Sprite::width),
52+
Codec.INT.fieldOf("height").forGetter(Sprite::height)
53+
).apply(i, Sprite::new));
54+
55+
public static final StreamCodec<ByteBuf, Sprite> STREAM_CODEC = StreamCodec.composite(
56+
ResourceLocation.STREAM_CODEC, Sprite::location,
57+
ByteBufCodecs.VAR_INT, Sprite::width,
58+
ByteBufCodecs.VAR_INT, Sprite::height,
59+
Sprite::new
60+
);
61+
}
62+
}

0 commit comments

Comments
 (0)