diff --git a/src/main/java/com/lovetropics/extras/client/ClientMapPoiManager.java b/src/main/java/com/lovetropics/extras/client/ClientMapPoiManager.java index 4dbf8a41..fa924555 100644 --- a/src/main/java/com/lovetropics/extras/client/ClientMapPoiManager.java +++ b/src/main/java/com/lovetropics/extras/client/ClientMapPoiManager.java @@ -4,15 +4,22 @@ import com.lovetropics.extras.client.screen.map.TropicalMapScreen; import com.lovetropics.extras.data.poi.Poi; import net.minecraft.client.Minecraft; +import net.minecraft.client.multiplayer.ClientPacketListener; +import net.minecraft.client.multiplayer.PlayerInfo; +import net.minecraft.client.resources.DefaultPlayerSkin; import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; import net.minecraft.world.entity.player.Player; import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.client.event.ClientPlayerNetworkEvent; +import net.minecraftforge.event.entity.EntityLeaveLevelEvent; import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.fml.common.Mod; +import javax.annotation.Nullable; import java.util.HashMap; import java.util.Map; +import java.util.UUID; @Mod.EventBusSubscriber(modid = LTExtras.MODID, value = Dist.CLIENT) public class ClientMapPoiManager { @@ -38,4 +45,18 @@ public static void openScreen(final Player player) { public static void onLoggingOut(final ClientPlayerNetworkEvent.LoggingOut event) { POIS.clear(); } + + public static ResourceLocation getFace(final UUID uuid) { + return getPlayerSkinOrDefault(uuid); + } + + private static ResourceLocation getPlayerSkinOrDefault(final UUID uuid) { + final ClientPacketListener connection = Minecraft.getInstance().getConnection(); + + final PlayerInfo playerInfo; + if (connection == null || (playerInfo = connection.getPlayerInfo(uuid)) == null) { + return DefaultPlayerSkin.getDefaultSkin(uuid); + } + return playerInfo.getSkinLocation(); + } } diff --git a/src/main/java/com/lovetropics/extras/client/screen/map/PoiButton.java b/src/main/java/com/lovetropics/extras/client/screen/map/PoiButton.java index b6529f12..04f36ff2 100644 --- a/src/main/java/com/lovetropics/extras/client/screen/map/PoiButton.java +++ b/src/main/java/com/lovetropics/extras/client/screen/map/PoiButton.java @@ -1,20 +1,31 @@ package com.lovetropics.extras.client.screen.map; +import com.lovetropics.extras.client.ClientMapPoiManager; import com.lovetropics.extras.data.poi.Poi; import net.minecraft.ChatFormatting; +import net.minecraft.client.Minecraft; import net.minecraft.client.gui.Font; import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.gui.components.AbstractButton; +import net.minecraft.client.gui.components.PlayerFaceRenderer; import net.minecraft.client.gui.narration.NarrationElementOutput; +import net.minecraft.client.multiplayer.PlayerInfo; +import net.minecraft.client.resources.DefaultPlayerSkin; +import net.minecraft.client.resources.SkinManager; import net.minecraft.network.chat.Component; import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerPlayer; import net.minecraft.util.CommonColors; import net.minecraft.util.Mth; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; import java.util.function.Consumer; class PoiButton extends AbstractButton { private static final int ICON_SIZE = 16; + private static final int HALF_ICON_SIZE = ICON_SIZE / 2; private static final int BORDER_SIZE = 3; private static final int SIZE = ICON_SIZE + BORDER_SIZE * 2; private static final int TOOLTIP_HEIGHT = 18; @@ -91,6 +102,19 @@ protected void renderWidget(final GuiGraphics graphics, final int mouseX, final final ResourceLocation icon = poi.resourceLocation(); graphics.blit(icon, getX() + BORDER_SIZE, getY() + BORDER_SIZE, zOffset, 0.0f, 0.0f, ICON_SIZE, ICON_SIZE, ICON_SIZE, ICON_SIZE); + + final List faces = poi.faces(); + if (!faces.isEmpty()) { + final int faceFactor = faces.size() > 2 ? 2 : 1; + graphics.pose().pushPose(); + graphics.pose().translate(0.0f, 0.0f, zOffset); + for (int i = 0; i < faces.size(); i++) { + final UUID uuid = faces.get(i); + final ResourceLocation face = ClientMapPoiManager.getFace(uuid); + PlayerFaceRenderer.draw(graphics, face, getX() + BORDER_SIZE + i * HALF_ICON_SIZE / faceFactor + i, getY() + ICON_SIZE, HALF_ICON_SIZE / faceFactor); + } + graphics.pose().popPose(); + } } @Override diff --git a/src/main/java/com/lovetropics/extras/command/PoiCommand.java b/src/main/java/com/lovetropics/extras/command/PoiCommand.java index c301ab4e..7962ccd3 100644 --- a/src/main/java/com/lovetropics/extras/command/PoiCommand.java +++ b/src/main/java/com/lovetropics/extras/command/PoiCommand.java @@ -14,6 +14,8 @@ import net.minecraft.network.chat.Component; import net.minecraft.resources.ResourceLocation; +import java.util.List; +import java.util.UUID; import java.util.stream.Stream; import static com.mojang.brigadier.arguments.BoolArgumentType.bool; @@ -154,8 +156,9 @@ private static int addWithDefaults(CommandContext ctx) { final ResourceLocation icon = getId(ctx, "icon"); final GlobalPos globalPos = GlobalPos.of(ctx.getSource().getLevel().dimension(), ctx.getSource().getPlayer().getOnPos().above()); final boolean enabled = false; + final List faces = List.of(); - final Poi newPoi = new Poi(name, description, icon, globalPos, enabled); + final Poi newPoi = new Poi(name, description, icon, globalPos, enabled, faces); MapPoiManager.get(ctx.getSource().getServer()).add(newPoi); ctx.getSource().sendSuccess(() -> Component.literal("Added new disabled POI " + newPoi.name() + " at your current position"), false); return Command.SINGLE_SUCCESS; @@ -236,8 +239,9 @@ private static Poi createPoiFromCtx(CommandContext ctx) { final WorldCoordinates worldCoordinates = ctx.getArgument("blockpos", WorldCoordinates.class); final GlobalPos globalPos = GlobalPos.of(ctx.getSource().getLevel().dimension(), worldCoordinates.getBlockPos(ctx.getSource())); final boolean enabled = getBool(ctx, "enabled"); + final List faces = List.of(); - return new Poi(name, description, icon, globalPos, enabled); + return new Poi(name, description, icon, globalPos, enabled, faces); } private static Stream suggestGlobalPos(CommandContext ctx) { diff --git a/src/main/java/com/lovetropics/extras/data/poi/MapPoiManager.java b/src/main/java/com/lovetropics/extras/data/poi/MapPoiManager.java index 1f29afd1..fc009eef 100644 --- a/src/main/java/com/lovetropics/extras/data/poi/MapPoiManager.java +++ b/src/main/java/com/lovetropics/extras/data/poi/MapPoiManager.java @@ -3,24 +3,25 @@ import com.lovetropics.extras.LTExtras; import com.lovetropics.extras.network.ClientboundPoiPacket; import com.lovetropics.extras.network.LTExtrasNetwork; +import com.lovetropics.lib.permission.PermissionsApi; +import com.lovetropics.lib.permission.role.RoleOverrideType; import com.mojang.serialization.Codec; import net.minecraft.Util; +import net.minecraft.core.BlockPos; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.NbtOps; import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.level.levelgen.structure.BoundingBox; import net.minecraft.world.level.saveddata.SavedData; +import net.minecraftforge.event.TickEvent; import net.minecraftforge.event.entity.player.PlayerEvent; import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.fml.common.Mod; import net.minecraftforge.network.PacketDistributor; import javax.annotation.Nullable; -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; +import java.util.*; import java.util.function.Predicate; import java.util.stream.Collectors; @@ -33,8 +34,11 @@ public class MapPoiManager extends SavedData { public static final BoundingBox MAP_BB = new BoundingBox(2013, 0, 1883, 2910, 0, 2799); private static final Codec> CODEC = Codec.unboundedMap(Codec.STRING, Poi.CODEC); private static final String STORAGE_ID = LTExtras.MODID + "_map_poi"; + private static final RoleOverrideType HOST_ROLE = (RoleOverrideType) RoleOverrideType.byId("host"); + private static final Predicate SPECIAL_RULE = p -> PermissionsApi.lookup().byPlayer(p).overrides().test(HOST_ROLE); private final Map pois; + public MapPoiManager(final Map pois) { this.pois = pois; } @@ -77,6 +81,37 @@ public Set getEnabledPois() { .collect(Collectors.toSet()); } + private void removeFace(String name, UUID face) { + final Poi poi = pois.get(name); + if (poi != null) { + poi.removeFace(face); + LTExtrasNetwork.CHANNEL.send(PacketDistributor.ALL.noArg(), new ClientboundPoiPacket(poi, false)); + } + setDirty(); + } + + public void addFace(String name, UUID face) { + final Poi poi = pois.get(name); + if (poi != null) { + getAllPois().stream() + .filter(p -> p != poi) + .filter(p -> p.faces().contains(face)) + .forEach(p -> removeFace(p.name(), face)); + poi.addFace(face); + setDirty(); + LTExtrasNetwork.CHANNEL.send(PacketDistributor.ALL.noArg(), new ClientboundPoiPacket(poi, false)); + } + } + + public void clearFaces() { + pois.values() + .stream() + .filter(Poi::hasFaces) + .map(Poi::clearFaces) + .forEach(p -> LTExtrasNetwork.CHANNEL.send(PacketDistributor.ALL.noArg(), new ClientboundPoiPacket(p, false))); + setDirty(); + } + public Set getDisabledPois() { return pois.values().stream() .filter(Predicate.not(Poi::enabled)) @@ -126,4 +161,61 @@ public void remove(String name) { setDirty(); LTExtrasNetwork.CHANNEL.send(PacketDistributor.ALL.noArg(), new ClientboundPoiPacket(poi, true)); } + + @SubscribeEvent + public static void onServerTick(TickEvent.ServerTickEvent event) { + if (event.phase != TickEvent.Phase.START || event.getServer().getTickCount() % 20 != 0) { + return; + } + final MapPoiManager manager = MapPoiManager.get(event.getServer()); + final Set hosts = getHosts(event.getServer()); + + if (hosts.isEmpty()) { + manager.clearFaces(); + return; + } + + addNewFacesToPois(manager, hosts); + removeNoLongerHostingFaces(manager, hosts); + removeFacesInWrongDimensions(manager, hosts); + } + + private static void removeFacesInWrongDimensions(MapPoiManager manager, Set hosts) { + for (Poi poi : manager.getAllPois()) { + for (ServerPlayer host : hosts) { + if (poi.faces().contains(host.getUUID()) && poi.globalPos().dimension() != host.level().dimension()) { + manager.removeFace(poi.name(), host.getUUID()); + } + } + } + } + + private static Set getHosts(final MinecraftServer server) { + return server.getPlayerList().getPlayers() + .stream() + .filter(SPECIAL_RULE) + .collect(Collectors.toSet()); + } + + private static void addNewFacesToPois(final MapPoiManager manager, final Set hosts) { + for (ServerPlayer host : hosts) { + final BlockPos hostPosition = host.blockPosition(); + manager.getEnabledPois() + .stream() + .filter(p -> p.globalPos().dimension() == host.level().dimension()) + .min(Comparator.comparingDouble(p -> p.globalPos().pos().distSqr(hostPosition))) + .filter(p -> !p.faces().contains(host.getUUID())) + .ifPresent(p -> manager.addFace(p.name(), host.getUUID())); + } + } + + private static void removeNoLongerHostingFaces(final MapPoiManager manager, final Set currentHosts) { + for (Poi poi : manager.getAllPois()) { + for (UUID face : poi.faces()) { + if (currentHosts.stream().noneMatch(p -> p.getUUID().equals(face))) { + manager.removeFace(poi.name(), face); + } + } + } + } } diff --git a/src/main/java/com/lovetropics/extras/data/poi/Poi.java b/src/main/java/com/lovetropics/extras/data/poi/Poi.java index fa2e821d..11ab05d8 100644 --- a/src/main/java/com/lovetropics/extras/data/poi/Poi.java +++ b/src/main/java/com/lovetropics/extras/data/poi/Poi.java @@ -3,11 +3,16 @@ import com.mojang.serialization.Codec; import com.mojang.serialization.codecs.RecordCodecBuilder; import net.minecraft.core.GlobalPos; +import net.minecraft.core.UUIDUtil; import net.minecraft.network.chat.Component; import net.minecraft.resources.ResourceLocation; import net.minecraft.util.ExtraCodecs; +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.List; import java.util.Objects; +import java.util.UUID; public final class Poi { public static final Codec CODEC = RecordCodecBuilder.create(i -> i.group( @@ -23,6 +28,22 @@ public final class Poi { private final ResourceLocation resourceLocation; private final GlobalPos globalPos; private boolean enabled; + private List faces; + + public Poi( + String name, + Component description, + ResourceLocation resourceLocation, + GlobalPos globalPos, + boolean enabled, + List faces) { + this.name = name; + this.description = description; + this.resourceLocation = resourceLocation; + this.globalPos = globalPos; + this.enabled = enabled; + this.faces = new ArrayList<>(faces); + } public Poi( String name, @@ -35,6 +56,7 @@ public Poi( this.resourceLocation = resourceLocation; this.globalPos = globalPos; this.enabled = enabled; + this.faces = new ArrayList<>(); } //Use only the name for equals&hashCode. Maybe tiny bit risky but dupe handling is free @@ -71,6 +93,23 @@ public boolean enabled() { return enabled; } + public List faces() { + return List.copyOf(faces); + } + + public boolean hasFaces() { + return !faces.isEmpty(); + } + + public Poi clearFaces() { + faces.clear(); + return this; + } + + public void addFace(UUID face) { + faces.add(face); + } + @Override public String toString() { return "Poi[" + @@ -84,4 +123,8 @@ public String toString() { public void setEnabled(boolean enabled) { this.enabled = enabled; } + + public void removeFace(final UUID face) { + faces.remove(face); + } } diff --git a/src/main/java/com/lovetropics/extras/network/ClientboundPoiPacket.java b/src/main/java/com/lovetropics/extras/network/ClientboundPoiPacket.java index 55017a93..3382ccb2 100644 --- a/src/main/java/com/lovetropics/extras/network/ClientboundPoiPacket.java +++ b/src/main/java/com/lovetropics/extras/network/ClientboundPoiPacket.java @@ -3,7 +3,6 @@ import com.lovetropics.extras.client.ClientMapPoiManager; import com.lovetropics.extras.data.poi.Poi; import net.minecraft.network.FriendlyByteBuf; -import net.minecraft.resources.ResourceLocation; import net.minecraftforge.network.NetworkEvent; import java.util.function.Supplier; @@ -14,7 +13,8 @@ public ClientboundPoiPacket(final FriendlyByteBuf input) { input.readComponent(), input.readResourceLocation(), input.readGlobalPos(), - input.readBoolean()), + input.readBoolean(), + input.readList(FriendlyByteBuf::readUUID)), input.readBoolean()); } @@ -24,6 +24,7 @@ public void write(final FriendlyByteBuf output) { output.writeResourceLocation(poi.resourceLocation()); output.writeGlobalPos(poi.globalPos()); output.writeBoolean(poi.enabled()); + output.writeCollection(poi.faces(), FriendlyByteBuf::writeUUID); output.writeBoolean(delete); }