|
7 | 7 | */ |
8 | 8 | package net.wurstclient.hacks; |
9 | 9 |
|
10 | | -import java.lang.reflect.Method; |
11 | | -import java.util.HashMap; |
12 | | -import java.util.Map; |
| 10 | +import com.mojang.blaze3d.vertex.PoseStack; |
| 11 | +import com.mojang.math.Axis; |
| 12 | +import java.awt.Color; |
13 | 13 | import java.util.UUID; |
14 | | -import net.minecraft.ChatFormatting; |
15 | 14 | import net.minecraft.network.chat.Component; |
| 15 | +import net.minecraft.client.gui.Font; |
| 16 | +import net.minecraft.client.renderer.MultiBufferSource; |
16 | 17 | import net.minecraft.world.entity.Entity; |
17 | | -import net.minecraft.world.entity.TamableAnimal; |
18 | | -import net.minecraft.world.entity.animal.equine.AbstractHorse; |
19 | | -import net.minecraft.world.entity.projectile.Projectile; |
| 18 | +import net.minecraft.world.entity.Mob; |
20 | 19 | import net.minecraft.world.entity.player.Player; |
| 20 | +import net.minecraft.world.entity.projectile.Projectile; |
| 21 | +import net.minecraft.world.entity.animal.equine.AbstractHorse; |
| 22 | +import net.minecraft.world.phys.AABB; |
| 23 | +import net.minecraft.world.phys.Vec3; |
21 | 24 | import net.wurstclient.Category; |
22 | 25 | import net.wurstclient.SearchTags; |
| 26 | +import net.wurstclient.events.RenderListener; |
23 | 27 | import net.wurstclient.hack.Hack; |
24 | 28 | import net.wurstclient.settings.CheckboxSetting; |
| 29 | +import net.wurstclient.settings.ColorSetting; |
| 30 | +import net.wurstclient.settings.SliderSetting; |
| 31 | +import net.wurstclient.settings.SliderSetting.ValueDisplay; |
| 32 | +import net.wurstclient.util.OwnerResolver; |
| 33 | +import net.wurstclient.util.EntityUtils; |
| 34 | +import net.wurstclient.util.RenderUtils; |
25 | 35 |
|
26 | 36 | @SearchTags({"mob owners", "pet owners", "projectile owners"}) |
27 | | -public final class MobOwnersHack extends Hack |
| 37 | +public final class MobOwnersHack extends Hack implements RenderListener |
28 | 38 | { |
29 | 39 | private final CheckboxSetting projectiles = |
30 | 40 | new CheckboxSetting("Projectiles", false); |
| 41 | + private final ColorSetting color = new ColorSetting("Color", |
| 42 | + "Color of projectile owner labels.", new Color(0x55FFFF)); |
| 43 | + private final SliderSetting labelScale = |
| 44 | + new SliderSetting("Label scale", "Scale of the owner labels.", 1.0, 0.5, |
| 45 | + 2.0, 0.05, ValueDisplay.DECIMAL); |
31 | 46 | private final CheckboxSetting showUuidWhenUnknown = new CheckboxSetting( |
32 | 47 | "Show UUID when unknown", |
33 | 48 | "Shows the owner's shortened UUID when the player name is not known.", |
34 | 49 | true); |
35 | 50 |
|
36 | | - private final Map<UUID, String> uuidNameCache = new HashMap<>(); |
37 | | - |
38 | 51 | public MobOwnersHack() |
39 | 52 | { |
40 | 53 | super("MobOwners", |
41 | 54 | "Shows which player owns tamed mobs, horses, and optionally projectiles.", |
42 | 55 | false); |
43 | 56 | setCategory(Category.RENDER); |
44 | 57 | addSetting(projectiles); |
| 58 | + addSetting(color); |
| 59 | + addSetting(labelScale); |
45 | 60 | addSetting(showUuidWhenUnknown); |
46 | 61 | } |
47 | 62 |
|
| 63 | + @Override |
| 64 | + protected void onEnable() |
| 65 | + { |
| 66 | + EVENTS.add(RenderListener.class, this); |
| 67 | + } |
| 68 | + |
| 69 | + @Override |
| 70 | + protected void onDisable() |
| 71 | + { |
| 72 | + EVENTS.remove(RenderListener.class, this); |
| 73 | + } |
| 74 | + |
48 | 75 | public Component addOwnerInfo(Entity entity, Component original) |
| 76 | + { |
| 77 | + Component ownerNameTag = buildOwnerNameTag(entity, original); |
| 78 | + return ownerNameTag == null ? original : ownerNameTag; |
| 79 | + } |
| 80 | + |
| 81 | + public boolean shouldForceMobNametag(Mob mob) |
| 82 | + { |
| 83 | + return buildOwnerNameTag(mob, null) != null; |
| 84 | + } |
| 85 | + |
| 86 | + private Component buildOwnerNameTag(Entity entity, Component original) |
49 | 87 | { |
50 | 88 | if(!isEnabled()) |
51 | | - return original; |
| 89 | + return null; |
52 | 90 |
|
53 | 91 | UUID owner = getOwnerUuid(entity); |
54 | 92 | if(owner == null) |
55 | | - return original; |
| 93 | + return null; |
56 | 94 |
|
57 | 95 | String name = resolveOwnerName(owner); |
58 | 96 | if(name == null) |
59 | 97 | { |
60 | 98 | if(!showUuidWhenUnknown.isChecked()) |
61 | | - return original; |
| 99 | + return null; |
62 | 100 | String raw = owner.toString(); |
63 | 101 | name = raw.substring(0, Math.min(8, raw.length())); |
64 | 102 | } |
65 | 103 |
|
66 | 104 | Component base = original == null ? entity.getName() : original; |
67 | | - return base.copy().append(Component.literal(" owned by " + name) |
68 | | - .withStyle(ChatFormatting.GRAY)); |
| 105 | + return Component.literal(name + "'s ").append(base); |
69 | 106 | } |
70 | 107 |
|
71 | 108 | private UUID getOwnerUuid(Entity entity) |
72 | 109 | { |
73 | | - if(entity instanceof TamableAnimal tameable) |
74 | | - return getOwnerReferenceUuid(tameable); |
75 | 110 | if(entity instanceof AbstractHorse horse) |
76 | | - return getHorseOwner(horse); |
77 | | - if(projectiles.isChecked() && entity instanceof Projectile projectile) |
78 | | - { |
79 | | - Entity owner = projectile.getOwner(); |
80 | | - return owner != null ? owner.getUUID() : null; |
81 | | - } |
82 | | - |
83 | | - return null; |
| 111 | + return OwnerResolver.getOwnerUuid(horse, projectiles.isChecked()); |
| 112 | + return OwnerResolver.getOwnerUuid(entity, projectiles.isChecked()); |
84 | 113 | } |
85 | 114 |
|
86 | | - private UUID getHorseOwner(AbstractHorse horse) |
| 115 | + private String resolveOwnerName(UUID owner) |
87 | 116 | { |
88 | | - for(String methodName : new String[]{"getOwnerUUID", "getOwnerId"}) |
89 | | - { |
90 | | - try |
91 | | - { |
92 | | - Method method = horse.getClass().getMethod(methodName); |
93 | | - Object value = method.invoke(horse); |
94 | | - if(value instanceof UUID uuid) |
95 | | - return uuid; |
96 | | - }catch(ReflectiveOperationException ignored) |
97 | | - {} |
98 | | - } |
99 | | - |
100 | | - return getOwnerReferenceUuid(horse); |
| 117 | + return OwnerResolver.lookupPlayerName(owner); |
101 | 118 | } |
102 | 119 |
|
103 | | - private UUID getOwnerReferenceUuid(Entity entity) |
| 120 | + @Override |
| 121 | + public void onRender(PoseStack matrixStack, float partialTicks) |
104 | 122 | { |
105 | | - for(String methodName : new String[]{"getOwnerReference", |
106 | | - "getOwnerUUID", "getOwnerId"}) |
| 123 | + if(MC.level == null || !projectiles.isChecked()) |
| 124 | + return; |
| 125 | + |
| 126 | + int labelColor = color.getColorI(); |
| 127 | + float scale = (float)labelScale.getValue(); |
| 128 | + |
| 129 | + for(Entity entity : MC.level.entitiesForRendering()) |
107 | 130 | { |
108 | | - try |
109 | | - { |
110 | | - Method method = entity.getClass().getMethod(methodName); |
111 | | - Object value = method.invoke(entity); |
112 | | - UUID uuid = uuidFrom(value); |
113 | | - if(uuid != null) |
114 | | - return uuid; |
115 | | - }catch(ReflectiveOperationException ignored) |
116 | | - {} |
| 131 | + if(!(entity instanceof Projectile projectile)) |
| 132 | + continue; |
| 133 | + |
| 134 | + Entity owner = projectile.getOwner(); |
| 135 | + UUID ownerUuid = OwnerResolver.getOwnerUuid(projectile); |
| 136 | + if(owner == MC.player || ownerUuid != null && MC.player != null |
| 137 | + && ownerUuid.equals(MC.player.getUUID())) |
| 138 | + continue; |
| 139 | + |
| 140 | + String label = getProjectileOwnerLabel(projectile, owner); |
| 141 | + if(label == null) |
| 142 | + continue; |
| 143 | + |
| 144 | + AABB box = EntityUtils.getLerpedBox(entity, partialTicks); |
| 145 | + Vec3 center = box.getCenter(); |
| 146 | + drawWorldLabel(matrixStack, label, center.x, box.maxY + 0.45, |
| 147 | + center.z, labelColor, scale); |
117 | 148 | } |
118 | | - return null; |
119 | 149 | } |
120 | 150 |
|
121 | | - private UUID uuidFrom(Object value) |
| 151 | + private String getProjectileOwnerLabel(Entity projectile, Entity owner) |
122 | 152 | { |
123 | | - if(value instanceof UUID uuid) |
124 | | - return uuid; |
125 | | - if(value == null) |
126 | | - return null; |
127 | | - |
128 | | - for(String methodName : new String[]{"uuid", "id", "getUuid", |
129 | | - "getUUID"}) |
| 153 | + UUID ownerUuid = OwnerResolver.getOwnerUuid(projectile); |
| 154 | + if(ownerUuid != null) |
130 | 155 | { |
131 | | - try |
132 | | - { |
133 | | - Method method = value.getClass().getMethod(methodName); |
134 | | - Object result = method.invoke(value); |
135 | | - if(result instanceof UUID uuid) |
136 | | - return uuid; |
137 | | - }catch(ReflectiveOperationException ignored) |
138 | | - {} |
| 156 | + String name = OwnerResolver.lookupPlayerName(ownerUuid); |
| 157 | + if(name != null) |
| 158 | + return "Owner: " + name; |
139 | 159 | } |
140 | 160 |
|
141 | | - return null; |
| 161 | + if(owner == null) |
| 162 | + return showUuidWhenUnknown.isChecked() ? "Owner: Unknown" : null; |
| 163 | + if(owner instanceof Player player) |
| 164 | + return "Owner: " + player.getName().getString(); |
| 165 | + |
| 166 | + String name = owner.getName().getString(); |
| 167 | + if(name == null || name.isBlank()) |
| 168 | + name = owner.getType().toShortString(); |
| 169 | + return "Owner: " + name; |
142 | 170 | } |
143 | 171 |
|
144 | | - private String resolveOwnerName(UUID owner) |
| 172 | + private void drawWorldLabel(PoseStack matrices, String text, double x, |
| 173 | + double y, double z, int argb, float scale) |
145 | 174 | { |
146 | | - String cached = uuidNameCache.get(owner); |
147 | | - if(cached != null) |
148 | | - return cached; |
| 175 | + matrices.pushPose(); |
| 176 | + Vec3 cam = RenderUtils.getCameraPos(); |
| 177 | + matrices.translate(x - cam.x, y - cam.y, z - cam.z); |
149 | 178 |
|
150 | | - if(MC.level != null) |
151 | | - for(Player player : MC.level.players()) |
152 | | - if(owner.equals(player.getUUID())) |
153 | | - { |
154 | | - String name = player.getName().getString(); |
155 | | - uuidNameCache.put(owner, name); |
156 | | - return name; |
157 | | - } |
158 | | - |
159 | | - return null; |
| 179 | + var camEntity = MC.getCameraEntity(); |
| 180 | + if(camEntity != null) |
| 181 | + { |
| 182 | + matrices.mulPose(Axis.YP.rotationDegrees(-camEntity.getYRot())); |
| 183 | + matrices.mulPose(Axis.XP.rotationDegrees(camEntity.getXRot())); |
| 184 | + } |
| 185 | + |
| 186 | + matrices.mulPose(Axis.YP.rotationDegrees(180.0F)); |
| 187 | + float s = 0.025F * scale; |
| 188 | + matrices.scale(s, -s, s); |
| 189 | + |
| 190 | + Font font = MC.font; |
| 191 | + MultiBufferSource.BufferSource vcp = RenderUtils.getVCP(); |
| 192 | + float w = font.width(text) / 2F; |
| 193 | + int baseAlpha = (argb >>> 24) & 0xFF; |
| 194 | + int bgAlpha = |
| 195 | + (int)Math.round(MC.options.getBackgroundOpacity(0.25F) * baseAlpha); |
| 196 | + int bg = bgAlpha << 24; |
| 197 | + int strokeColor = |
| 198 | + (Math.max(0, Math.min(255, baseAlpha)) << 24) | 0x000000; |
| 199 | + var matrix = matrices.last().pose(); |
| 200 | + |
| 201 | + font.drawInBatch(text, -w - 1, 0, strokeColor, false, matrix, vcp, |
| 202 | + Font.DisplayMode.SEE_THROUGH, 0, 0xF000F0); |
| 203 | + font.drawInBatch(text, -w + 1, 0, strokeColor, false, matrix, vcp, |
| 204 | + Font.DisplayMode.SEE_THROUGH, 0, 0xF000F0); |
| 205 | + font.drawInBatch(text, -w, -1, strokeColor, false, matrix, vcp, |
| 206 | + Font.DisplayMode.SEE_THROUGH, 0, 0xF000F0); |
| 207 | + font.drawInBatch(text, -w, 1, strokeColor, false, matrix, vcp, |
| 208 | + Font.DisplayMode.SEE_THROUGH, 0, 0xF000F0); |
| 209 | + font.drawInBatch(text, -w, 0, argb, false, matrix, vcp, |
| 210 | + Font.DisplayMode.SEE_THROUGH, bg, 0xF000F0); |
| 211 | + |
| 212 | + vcp.endBatch(); |
| 213 | + matrices.popPose(); |
160 | 214 | } |
161 | 215 | } |
0 commit comments