Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ public boolean showButton() {
public final BooleanOption showOwnNametag = new BooleanOption("showOwnNametag", false);
public final BooleanOption useShadows = new BooleanOption("useShadows", false);
public final BooleanOption nametagBackground = new BooleanOption("nametagBackground", true);
public final BooleanOption alwaysShowEntityNametags = new BooleanOption("alwaysShowEntityNametags", true);

public final ForceableBooleanOption timeChangerEnabled = new ForceableBooleanOption("enabled", false);
public final IntegerOption customTime = new IntegerOption("time", 0, 0, 24000);
Expand Down Expand Up @@ -128,6 +129,7 @@ public DolphinClientConfigCommon() {
nametagOptions.add(showOwnNametag);
nametagOptions.add(useShadows);
nametagOptions.add(nametagBackground);
nametagOptions.add(alwaysShowEntityNametags);

general.add(customWindowTitle);
general.add(debugLogOutput);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@
"nametagOptions": {
"showOwnNametag": "false",
"useShadows": "true",
"nametagBackground": "false"
"nametagBackground": "false",
"alwaysShowEntityNametags": "true"
},
"timeChanger": {
"enabled": "false",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@
"allocated": "Allocated",
"alwaysCrit": "Always Show",
"alwaysCrit.tooltip": "Whether to always emit these particles when hitting.",
"alwaysShowEntityNametags": "Always Show Entity Name Tags",
"alwaysShowEntityNametags.tooltip": "Renders name tags on named mobs without having to look at them, like in newer versions.",
"alwaysShowHeadLayer": "Always Show Hat Layer",
"anchorpoint": "Anchor Point",
"applyBlend": "Apply Blending",
Expand Down
3 changes: 2 additions & 1 deletion pack/overrides/config/dolphinclient/Default.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@
"nametagOptions": {
"showOwnNametag": "false",
"useShadows": "true",
"nametagBackground": "false"
"nametagBackground": "false",
"alwaysShowEntityNametags": "true"
},
"timeChanger": {
"enabled": "false",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,7 @@

package io.github.dolphinclient.mixin;

import java.util.ArrayList;
import java.util.List;
import java.util.Arrays;

import com.llamalad7.mixinextras.injector.v2.WrapWithCondition;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
Expand All @@ -35,6 +34,7 @@
import io.github.dolphinclient.modules.hypixel.LevelHeadPlacement;
import io.github.dolphinclient.modules.hypixel.bedwars.BedwarsMod;
import io.github.dolphinclient.util.Util;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import net.minecraft.client.Minecraft;
import net.minecraft.client.entity.living.player.ClientPlayerEntity;
import net.minecraft.client.render.TextRenderer;
Expand Down Expand Up @@ -131,9 +131,21 @@ public abstract class EntityRendererMixin<T extends Entity> {
/**
* Pixels to lift the level so it clears the below-name scoreboard row and holograms
* parked just above the username. Vanilla only draws that scoreboard row inside 10 blocks.
*
* <p>The scan iterates every loaded entity in a box, so the result is cached per entity
* per tick. The world's entity set can only change the outcome once per tick anyway.
* Cache layout: {@code [tick, baseAbove, belowName, nameAnchor, tagAnchor...]}; the tag
* anchors are the payload {@link LevelHeadPlacement#extraPixels} expects.</p>
*/
@Unique
private int dolphinclient$tagsAbove(Entity entity, int fontHeight, int baseAbove) {
int tick = entity.ticks;
double[] cached = dolphinclient$tagsAboveCache.get(entity.getNetworkId());
if (cached != null && (int) cached[0] == tick && cached[1] == baseAbove) {
double[] tags = Arrays.copyOfRange(cached, 4, cached.length);
return LevelHeadPlacement.extraPixels(fontHeight, cached[2] != 0, cached[3], tags, baseAbove);
}

boolean belowName = false;
if (entity instanceof PlayerEntity player && !player.isSneaking() && this.dispatcher.camera != null
&& player.squaredDistanceTo(this.dispatcher.camera) < 100.0) {
Expand All @@ -142,13 +154,12 @@ public abstract class EntityRendererMixin<T extends Entity> {
}

double nameAnchor = entity.y + entity.height + 0.5;
double[] tags = null;
double[] tags = new double[4];
if (entity.world != null) {
// A nametag floats up to a couple of blocks above its carrier's hitbox, so the
// query has to reach below the anchor to find tags parked right above the username.
Box box = new Box(entity.x - 1.0, nameAnchor - 3.0, entity.z - 1.0,
entity.x + 1.0, nameAnchor + 3.0, entity.z + 1.0);
List<Double> above = new ArrayList<>();
for (Entity other : entity.world.getEntities(entity, box)) {
if (other == entity || !other.hasCustomName() || !other.isCustomNameVisible()) {
continue;
Expand All @@ -165,19 +176,26 @@ public abstract class EntityRendererMixin<T extends Entity> {
anchor -= other.height / 2.0;
}
if (anchor > nameAnchor + 0.05) {
above.add(anchor);
}
}
if (!above.isEmpty()) {
tags = new double[above.size()];
for (int i = 0; i < above.size(); i++) {
tags[i] = above.get(i);
tags = Arrays.copyOf(tags, tags.length + 1);
tags[tags.length - 1] = anchor;
}
}
}
return LevelHeadPlacement.extraPixels(fontHeight, belowName, nameAnchor, tags, baseAbove);

tags[0] = tick;
tags[1] = baseAbove;
tags[2] = belowName ? 1 : 0;
tags[3] = nameAnchor;
if (dolphinclient$tagsAboveCache.size() > 1024) {
dolphinclient$tagsAboveCache.clear();
}
dolphinclient$tagsAboveCache.put(entity.getNetworkId(), tags);
return LevelHeadPlacement.extraPixels(fontHeight, belowName, nameAnchor, tags.length > 4 ? Arrays.copyOfRange(tags, 4, tags.length) : null, baseAbove);
}

@Unique
private static final Int2ObjectOpenHashMap<double[]> dolphinclient$tagsAboveCache = new Int2ObjectOpenHashMap<>();

@WrapWithCondition(method = "renderNameTag(Lnet/minecraft/entity/Entity;Ljava/lang/String;DDDI)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/vertex/BufferBuilder;begin(ILnet/minecraft/client/render/vertex/VertexFormat;)V"))
private boolean disableNameTagBackground(BufferBuilder instance, int drawMode, VertexFormat format) {
return DolphinClient.config().nametagBackground.get();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* Copyright © 2026 DSNS <dominic@seung.dev>
*
* This file is part of DolphinClient.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* For more information, see the LICENSE file.
*/

package io.github.dolphinclient.mixin;

import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
import com.llamalad7.mixinextras.sugar.Local;
import io.github.dolphinclient.DolphinClient;
import net.minecraft.client.render.entity.EntityRenderDispatcher;
import net.minecraft.client.render.entity.MobRenderer;
import net.minecraft.entity.Entity;
import net.minecraft.entity.living.mob.MobEntity;
import org.objectweb.asm.Opcodes;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;

@Mixin(MobRenderer.class)
public class MobRendererMixin {

/**
* Vanilla 1.8 only renders a mob's name tag while it is under the crosshair; newer versions
* always show it. Fake the crosshair check to match modern behavior. The preceding
* {@code hasCustomName()} check is left untouched so unnamed mobs stay tag-free.
*/
@ModifyExpressionValue(method = "shouldRenderNameTag(Lnet/minecraft/entity/living/mob/MobEntity;)Z", at = @At(value = "FIELD", opcode = Opcodes.GETFIELD, target = "Lnet/minecraft/client/render/entity/EntityRenderDispatcher;targetEntity:Lnet/minecraft/entity/Entity;"))
private Entity dolphinclient$renderNametagsWithoutCrosshair(Entity original, @Local(argsOnly = true) MobEntity mob) {
if (DolphinClient.config().alwaysShowEntityNametags.get()) {
return mob;
}
return original;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
"LocalRemoteClientPlayerEntityMixin",
"MinecraftClientAccessor",
"MinecraftClientMixin",
"MobRendererMixin",
"ParticleAccessor",
"ParticleManagerMixin",
"PlayerEntityMixin",
Expand Down
Loading