|
38 | 38 | import net.wurstclient.util.RenderUtils; |
39 | 39 | import net.wurstclient.util.RenderUtils.ColoredBox; |
40 | 40 | import net.wurstclient.util.RenderUtils.ColoredPoint; |
| 41 | +import com.mojang.math.Axis; |
| 42 | +import net.minecraft.client.gui.Font; |
| 43 | +import net.minecraft.resources.ResourceKey; |
| 44 | +import net.minecraft.world.entity.npc.villager.Villager; |
| 45 | +import net.minecraft.world.entity.npc.villager.VillagerProfession; |
| 46 | +import net.minecraft.world.phys.Vec3; |
41 | 47 |
|
42 | 48 | @SearchTags({"mob esp", "MobTracers", "mob tracers"}) |
43 | 49 | public final class MobEspHack extends Hack implements UpdateListener, |
@@ -102,6 +108,18 @@ public final class MobEspHack extends Hack implements UpdateListener, |
102 | 108 | "Appends the number of detected mobs to this hack's entry in the HackList.", |
103 | 109 | false); |
104 | 110 |
|
| 111 | + // Villager professions label |
| 112 | + private final CheckboxSetting showVillagerProfessions = |
| 113 | + new CheckboxSetting("Show villager professions", |
| 114 | + "Displays the profession of villagers above their heads.", false); |
| 115 | + |
| 116 | + // Range limiter |
| 117 | + private final CheckboxSetting rangeLimit = new CheckboxSetting( |
| 118 | + "Range limit", "Only show mobs within the configured range.", false); |
| 119 | + |
| 120 | + private final SliderSetting espRange = new SliderSetting("ESP range", 64, 1, |
| 121 | + 150, 1, SliderSetting.ValueDisplay.INTEGER.withSuffix(" blocks")); |
| 122 | + |
105 | 123 | // Above-ground filter |
106 | 124 | private final CheckboxSetting onlyAboveGround = |
107 | 125 | new CheckboxSetting("Above ground only", |
@@ -143,6 +161,9 @@ public MobEspHack() |
143 | 161 | addSetting(highlightShulkerProjectiles); |
144 | 162 | addSetting(highlightWitherProjectiles); |
145 | 163 | addSetting(showCountInHackList); |
| 164 | + addSetting(showVillagerProfessions); |
| 165 | + addSetting(rangeLimit); |
| 166 | + addSetting(espRange); |
146 | 167 | } |
147 | 168 |
|
148 | 169 | @Override |
@@ -187,6 +208,11 @@ public void onUpdate() |
187 | 208 | if(onlyAboveGround.isChecked()) |
188 | 209 | stream = stream.filter(e -> e.getY() >= aboveGroundY.getValue()); |
189 | 210 |
|
| 211 | + // optionally limit range |
| 212 | + if(rangeLimit.isChecked()) |
| 213 | + stream = stream |
| 214 | + .filter(e -> MC.player.distanceTo(e) <= espRange.getValue()); |
| 215 | + |
190 | 216 | stream = entityFilters.applyTo(stream); |
191 | 217 |
|
192 | 218 | mobs.addAll(stream.collect(Collectors.toList())); |
@@ -399,6 +425,111 @@ public void onRender(PoseStack matrixStack, float partialTicks) |
399 | 425 | if(glowMode && highlightWitherProjectiles.isChecked() |
400 | 426 | && !witherSkulls.isEmpty()) |
401 | 427 | renderWitherProjectileFallback(matrixStack, partialTicks); |
| 428 | + |
| 429 | + // render villager professions |
| 430 | + if(showVillagerProfessions.isChecked()) |
| 431 | + renderVillagerProfessions(matrixStack, partialTicks); |
| 432 | + } |
| 433 | + |
| 434 | + private static final double VANILLA_NAMETAG_DIST = 5.0; |
| 435 | + |
| 436 | + private void renderVillagerProfessions(PoseStack matrices, |
| 437 | + float partialTicks) |
| 438 | + { |
| 439 | + Vec3 cam = RenderUtils.getCameraPos(); |
| 440 | + var camEntity = MC.getCameraEntity(); |
| 441 | + |
| 442 | + for(LivingEntity e : mobs) |
| 443 | + { |
| 444 | + if(!(e instanceof Villager villager)) |
| 445 | + continue; |
| 446 | + |
| 447 | + // If vanilla is already showing the profession name tag |
| 448 | + // up close, skip the MobESP label to avoid duplicates. |
| 449 | + if(MC.player.distanceTo(villager) < VANILLA_NAMETAG_DIST) |
| 450 | + continue; |
| 451 | + |
| 452 | + String profession = getVillagerProfessionName(villager); |
| 453 | + if(profession.isEmpty()) |
| 454 | + continue; |
| 455 | + |
| 456 | + // position above head |
| 457 | + AABB box = EntityUtils.getLerpedBox(e, partialTicks); |
| 458 | + double lx = box.getCenter().x; |
| 459 | + double ly = box.maxY + 0.6; |
| 460 | + double lz = box.getCenter().z; |
| 461 | + |
| 462 | + float[] rgb = getColorRgb(); |
| 463 | + int labelColor = RenderUtils.toIntColor(rgb, 0.9F); |
| 464 | + |
| 465 | + matrices.pushPose(); |
| 466 | + matrices.translate(lx - cam.x, ly - cam.y, lz - cam.z); |
| 467 | + if(camEntity != null) |
| 468 | + { |
| 469 | + matrices.mulPose(Axis.YP.rotationDegrees(-camEntity.getYRot())); |
| 470 | + matrices.mulPose(Axis.XP.rotationDegrees(camEntity.getXRot())); |
| 471 | + } |
| 472 | + matrices.mulPose(Axis.YP.rotationDegrees(180.0F)); |
| 473 | + |
| 474 | + double dist = MC.player.distanceTo(e); |
| 475 | + float scale = 0.025F * (float)Math.max(1.0, dist * 0.1); |
| 476 | + matrices.scale(scale, -scale, scale); |
| 477 | + |
| 478 | + float w = MC.font.width(profession) / 2F; |
| 479 | + int baseAlpha = (labelColor >>> 24) & 0xFF; |
| 480 | + int bgAlpha = (int)Math |
| 481 | + .round(MC.options.getBackgroundOpacity(0.25F) * baseAlpha); |
| 482 | + int bg = bgAlpha << 24; |
| 483 | + int strokeColor = |
| 484 | + (Math.max(0, Math.min(255, baseAlpha)) << 24) | 0x000000; |
| 485 | + var matrix = matrices.last().pose(); |
| 486 | + RenderUtils.drawOutlinedTextInBatch(MC.font, profession, -w, 0, |
| 487 | + labelColor, strokeColor, matrix, Font.DisplayMode.SEE_THROUGH, |
| 488 | + bg, 0xF000F0); |
| 489 | + matrices.popPose(); |
| 490 | + } |
| 491 | + } |
| 492 | + |
| 493 | + private String getVillagerProfessionName(Villager villager) |
| 494 | + { |
| 495 | + ResourceKey<VillagerProfession> key = |
| 496 | + villager.getVillagerData().profession().unwrapKey().orElse(null); |
| 497 | + |
| 498 | + if(key == null) |
| 499 | + return ""; |
| 500 | + |
| 501 | + if(key == VillagerProfession.NONE) |
| 502 | + return "Unemployed"; |
| 503 | + if(key == VillagerProfession.NITWIT) |
| 504 | + return "Nitwit"; |
| 505 | + if(key == VillagerProfession.FARMER) |
| 506 | + return "Farmer"; |
| 507 | + if(key == VillagerProfession.FISHERMAN) |
| 508 | + return "Fisherman"; |
| 509 | + if(key == VillagerProfession.SHEPHERD) |
| 510 | + return "Shepherd"; |
| 511 | + if(key == VillagerProfession.FLETCHER) |
| 512 | + return "Fletcher"; |
| 513 | + if(key == VillagerProfession.LIBRARIAN) |
| 514 | + return "Librarian"; |
| 515 | + if(key == VillagerProfession.CARTOGRAPHER) |
| 516 | + return "Cartographer"; |
| 517 | + if(key == VillagerProfession.CLERIC) |
| 518 | + return "Cleric"; |
| 519 | + if(key == VillagerProfession.ARMORER) |
| 520 | + return "Armorer"; |
| 521 | + if(key == VillagerProfession.WEAPONSMITH) |
| 522 | + return "Weaponsmith"; |
| 523 | + if(key == VillagerProfession.TOOLSMITH) |
| 524 | + return "Toolsmith"; |
| 525 | + if(key == VillagerProfession.BUTCHER) |
| 526 | + return "Butcher"; |
| 527 | + if(key == VillagerProfession.LEATHERWORKER) |
| 528 | + return "Leatherworker"; |
| 529 | + if(key == VillagerProfession.MASON) |
| 530 | + return "Mason"; |
| 531 | + |
| 532 | + return ""; |
402 | 533 | } |
403 | 534 |
|
404 | 535 | private void renderShulkerProjectileFallback(PoseStack matrixStack, |
|
0 commit comments