package net.minecraft.client.renderer.debug; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.mojang.blaze3d.vertex.PoseStack; import com.mojang.logging.LogUtils; import java.util.Collection; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.UUID; import java.util.stream.Collectors; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.MultiBufferSource; import net.minecraft.core.BlockPos; import net.minecraft.core.Position; import net.minecraft.network.protocol.common.custom.BrainDebugPayload.BrainDump; import net.minecraft.network.protocol.game.DebugEntityNameGenerator; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.player.Player; import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; @Environment(EnvType.CLIENT) public class BrainDebugRenderer implements DebugRenderer.SimpleDebugRenderer { private static final Logger LOGGER = LogUtils.getLogger(); private static final boolean SHOW_NAME_FOR_ALL = true; private static final boolean SHOW_PROFESSION_FOR_ALL = false; private static final boolean SHOW_BEHAVIORS_FOR_ALL = false; private static final boolean SHOW_ACTIVITIES_FOR_ALL = false; private static final boolean SHOW_INVENTORY_FOR_ALL = false; private static final boolean SHOW_GOSSIPS_FOR_ALL = false; private static final boolean SHOW_PATH_FOR_ALL = false; private static final boolean SHOW_HEALTH_FOR_ALL = false; private static final boolean SHOW_WANTS_GOLEM_FOR_ALL = true; private static final boolean SHOW_ANGER_LEVEL_FOR_ALL = false; private static final boolean SHOW_NAME_FOR_SELECTED = true; private static final boolean SHOW_PROFESSION_FOR_SELECTED = true; private static final boolean SHOW_BEHAVIORS_FOR_SELECTED = true; private static final boolean SHOW_ACTIVITIES_FOR_SELECTED = true; private static final boolean SHOW_MEMORIES_FOR_SELECTED = true; private static final boolean SHOW_INVENTORY_FOR_SELECTED = true; private static final boolean SHOW_GOSSIPS_FOR_SELECTED = true; private static final boolean SHOW_PATH_FOR_SELECTED = true; private static final boolean SHOW_HEALTH_FOR_SELECTED = true; private static final boolean SHOW_WANTS_GOLEM_FOR_SELECTED = true; private static final boolean SHOW_ANGER_LEVEL_FOR_SELECTED = true; private static final boolean SHOW_POI_INFO = true; private static final int MAX_RENDER_DIST_FOR_BRAIN_INFO = 30; private static final int MAX_RENDER_DIST_FOR_POI_INFO = 30; private static final int MAX_TARGETING_DIST = 8; private static final float TEXT_SCALE = 0.02F; private static final int CYAN = -16711681; private static final int GRAY = -3355444; private static final int PINK = -98404; private static final int ORANGE = -23296; private final Minecraft minecraft; private final Map pois = Maps.newHashMap(); private final Map brainDumpsPerEntity = Maps.newHashMap(); @Nullable private UUID lastLookedAtUuid; public BrainDebugRenderer(Minecraft minecraft) { this.minecraft = minecraft; } @Override public void clear() { this.pois.clear(); this.brainDumpsPerEntity.clear(); this.lastLookedAtUuid = null; } public void addPoi(BrainDebugRenderer.PoiInfo poiInfo) { this.pois.put(poiInfo.pos, poiInfo); } public void removePoi(BlockPos pos) { this.pois.remove(pos); } public void setFreeTicketCount(BlockPos pos, int freeTicketCount) { BrainDebugRenderer.PoiInfo poiInfo = (BrainDebugRenderer.PoiInfo)this.pois.get(pos); if (poiInfo == null) { LOGGER.warn("Strange, setFreeTicketCount was called for an unknown POI: {}", pos); } else { poiInfo.freeTicketCount = freeTicketCount; } } public void addOrUpdateBrainDump(BrainDump brainDump) { this.brainDumpsPerEntity.put(brainDump.uuid(), brainDump); } public void removeBrainDump(int id) { this.brainDumpsPerEntity.values().removeIf(brainDump -> brainDump.id() == id); } @Override public void render(PoseStack poseStack, MultiBufferSource bufferSource, double camX, double camY, double camZ) { this.clearRemovedEntities(); this.doRender(poseStack, bufferSource, camX, camY, camZ); if (!this.minecraft.player.isSpectator()) { this.updateLastLookedAtUuid(); } } private void clearRemovedEntities() { this.brainDumpsPerEntity.entrySet().removeIf(entry -> { Entity entity = this.minecraft.level.getEntity(((BrainDump)entry.getValue()).id()); return entity == null || entity.isRemoved(); }); } private void doRender(PoseStack poseStack, MultiBufferSource buffer, double x, double y, double z) { BlockPos blockPos = BlockPos.containing(x, y, z); this.brainDumpsPerEntity.values().forEach(brainDump -> { if (this.isPlayerCloseEnoughToMob(brainDump)) { this.renderBrainInfo(poseStack, buffer, brainDump, x, y, z); } }); for (BlockPos blockPos2 : this.pois.keySet()) { if (blockPos.closerThan(blockPos2, 30.0)) { highlightPoi(poseStack, buffer, blockPos2); } } this.pois.values().forEach(poiInfo -> { if (blockPos.closerThan(poiInfo.pos, 30.0)) { this.renderPoiInfo(poseStack, buffer, poiInfo); } }); this.getGhostPois().forEach((blockPos2x, list) -> { if (blockPos.closerThan(blockPos2x, 30.0)) { this.renderGhostPoi(poseStack, buffer, blockPos2x, list); } }); } private static void highlightPoi(PoseStack poseStack, MultiBufferSource buffer, BlockPos pos) { float f = 0.05F; DebugRenderer.renderFilledBox(poseStack, buffer, pos, 0.05F, 0.2F, 0.2F, 1.0F, 0.3F); } private void renderGhostPoi(PoseStack poseStack, MultiBufferSource buffer, BlockPos pos, List poiName) { float f = 0.05F; DebugRenderer.renderFilledBox(poseStack, buffer, pos, 0.05F, 0.2F, 0.2F, 1.0F, 0.3F); renderTextOverPos(poseStack, buffer, poiName + "", pos, 0, -256); renderTextOverPos(poseStack, buffer, "Ghost POI", pos, 1, -65536); } private void renderPoiInfo(PoseStack poseStack, MultiBufferSource buffer, BrainDebugRenderer.PoiInfo poiInfo) { int i = 0; Set set = this.getTicketHolderNames(poiInfo); if (set.size() < 4) { renderTextOverPoi(poseStack, buffer, "Owners: " + set, poiInfo, i, -256); } else { renderTextOverPoi(poseStack, buffer, set.size() + " ticket holders", poiInfo, i, -256); } i++; Set set2 = this.getPotentialTicketHolderNames(poiInfo); if (set2.size() < 4) { renderTextOverPoi(poseStack, buffer, "Candidates: " + set2, poiInfo, i, -23296); } else { renderTextOverPoi(poseStack, buffer, set2.size() + " potential owners", poiInfo, i, -23296); } renderTextOverPoi(poseStack, buffer, "Free tickets: " + poiInfo.freeTicketCount, poiInfo, ++i, -256); renderTextOverPoi(poseStack, buffer, poiInfo.type, poiInfo, ++i, -1); } private void renderPath(PoseStack poseStack, MultiBufferSource buffer, BrainDump brainDump, double x, double y, double z) { if (brainDump.path() != null) { PathfindingRenderer.renderPath(poseStack, buffer, brainDump.path(), 0.5F, false, false, x, y, z); } } private void renderBrainInfo(PoseStack poseStack, MultiBufferSource buffer, BrainDump brainDump, double x, double y, double z) { boolean bl = this.isMobSelected(brainDump); int i = 0; renderTextOverMob(poseStack, buffer, brainDump.pos(), i, brainDump.name(), -1, 0.03F); i++; if (bl) { renderTextOverMob(poseStack, buffer, brainDump.pos(), i, brainDump.profession() + " " + brainDump.xp() + " xp", -1, 0.02F); i++; } if (bl) { int j = brainDump.health() < brainDump.maxHealth() ? -23296 : -1; renderTextOverMob( poseStack, buffer, brainDump.pos(), i, "health: " + String.format(Locale.ROOT, "%.1f", brainDump.health()) + " / " + String.format(Locale.ROOT, "%.1f", brainDump.maxHealth()), j, 0.02F ); i++; } if (bl && !brainDump.inventory().equals("")) { renderTextOverMob(poseStack, buffer, brainDump.pos(), i, brainDump.inventory(), -98404, 0.02F); i++; } if (bl) { for (String string : brainDump.behaviors()) { renderTextOverMob(poseStack, buffer, brainDump.pos(), i, string, -16711681, 0.02F); i++; } } if (bl) { for (String string : brainDump.activities()) { renderTextOverMob(poseStack, buffer, brainDump.pos(), i, string, -16711936, 0.02F); i++; } } if (brainDump.wantsGolem()) { renderTextOverMob(poseStack, buffer, brainDump.pos(), i, "Wants Golem", -23296, 0.02F); i++; } if (bl && brainDump.angerLevel() != -1) { renderTextOverMob(poseStack, buffer, brainDump.pos(), i, "Anger Level: " + brainDump.angerLevel(), -98404, 0.02F); i++; } if (bl) { for (String string : brainDump.gossips()) { if (string.startsWith(brainDump.name())) { renderTextOverMob(poseStack, buffer, brainDump.pos(), i, string, -1, 0.02F); } else { renderTextOverMob(poseStack, buffer, brainDump.pos(), i, string, -23296, 0.02F); } i++; } } if (bl) { for (String string : Lists.reverse(brainDump.memories())) { renderTextOverMob(poseStack, buffer, brainDump.pos(), i, string, -3355444, 0.02F); i++; } } if (bl) { this.renderPath(poseStack, buffer, brainDump, x, y, z); } } private static void renderTextOverPoi(PoseStack poseStack, MultiBufferSource buffer, String text, BrainDebugRenderer.PoiInfo poiInfo, int layer, int color) { renderTextOverPos(poseStack, buffer, text, poiInfo.pos, layer, color); } private static void renderTextOverPos(PoseStack poseStack, MultiBufferSource buffer, String text, BlockPos pos, int layer, int color) { double d = 1.3; double e = 0.2; double f = pos.getX() + 0.5; double g = pos.getY() + 1.3 + layer * 0.2; double h = pos.getZ() + 0.5; DebugRenderer.renderFloatingText(poseStack, buffer, text, f, g, h, color, 0.02F, true, 0.0F, true); } private static void renderTextOverMob(PoseStack poseStack, MultiBufferSource buffer, Position pos, int layer, String text, int color, float scale) { double d = 2.4; double e = 0.25; BlockPos blockPos = BlockPos.containing(pos); double f = blockPos.getX() + 0.5; double g = pos.y() + 2.4 + layer * 0.25; double h = blockPos.getZ() + 0.5; float i = 0.5F; DebugRenderer.renderFloatingText(poseStack, buffer, text, f, g, h, color, scale, false, 0.5F, true); } private Set getTicketHolderNames(BrainDebugRenderer.PoiInfo poiInfo) { return (Set)this.getTicketHolders(poiInfo.pos).stream().map(DebugEntityNameGenerator::getEntityName).collect(Collectors.toSet()); } private Set getPotentialTicketHolderNames(BrainDebugRenderer.PoiInfo poiInfo) { return (Set)this.getPotentialTicketHolders(poiInfo.pos).stream().map(DebugEntityNameGenerator::getEntityName).collect(Collectors.toSet()); } private boolean isMobSelected(BrainDump brainDump) { return Objects.equals(this.lastLookedAtUuid, brainDump.uuid()); } private boolean isPlayerCloseEnoughToMob(BrainDump brainDump) { Player player = this.minecraft.player; BlockPos blockPos = BlockPos.containing(player.getX(), brainDump.pos().y(), player.getZ()); BlockPos blockPos2 = BlockPos.containing(brainDump.pos()); return blockPos.closerThan(blockPos2, 30.0); } private Collection getTicketHolders(BlockPos pos) { return (Collection)this.brainDumpsPerEntity .values() .stream() .filter(brainDump -> brainDump.hasPoi(pos)) .map(BrainDump::uuid) .collect(Collectors.toSet()); } private Collection getPotentialTicketHolders(BlockPos pos) { return (Collection)this.brainDumpsPerEntity .values() .stream() .filter(brainDump -> brainDump.hasPotentialPoi(pos)) .map(BrainDump::uuid) .collect(Collectors.toSet()); } private Map> getGhostPois() { Map> map = Maps.>newHashMap(); for (BrainDump brainDump : this.brainDumpsPerEntity.values()) { for (BlockPos blockPos : Iterables.concat(brainDump.pois(), brainDump.potentialPois())) { if (!this.pois.containsKey(blockPos)) { ((List)map.computeIfAbsent(blockPos, blockPosx -> Lists.newArrayList())).add(brainDump.name()); } } } return map; } private void updateLastLookedAtUuid() { DebugRenderer.getTargetedEntity(this.minecraft.getCameraEntity(), 8).ifPresent(entity -> this.lastLookedAtUuid = entity.getUUID()); } @Environment(EnvType.CLIENT) public static class PoiInfo { public final BlockPos pos; public final String type; public int freeTicketCount; public PoiInfo(BlockPos pos, String type, int freeTicketCount) { this.pos = pos; this.type = type; this.freeTicketCount = freeTicketCount; } } }