Skip to content
Merged
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
@@ -0,0 +1,83 @@
/*
* 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.axolotlclient.modules.hitboxes;

import lombok.Getter;

/**
* Entity groups the Hitboxes module can toggle and color independently.
* Classification is first-match in the order below so more specific groups
* (self, arrows) win over broader ones (players, projectiles, living).
*/
@Getter
public enum HitboxType {
SELF("hitboxes.self", 0xFF00FFFF),
PLAYERS("hitboxes.players", 0xFFFFFFFF),
ARROWS("hitboxes.arrows", 0xFFFF5555),
PROJECTILES("hitboxes.projectiles", 0xFFFFAA00),
ITEMS("hitboxes.items", 0xFFFFFF55),
HOSTILE("hitboxes.hostile", 0xFFAA0000),
PASSIVE("hitboxes.passive", 0xFF55FF55),
OTHER("hitboxes.other", 0xFFAAAAAA);

private final String translationKey;
private final int defaultColor;

HitboxType(String translationKey, int defaultColor) {
this.translationKey = translationKey;
this.defaultColor = defaultColor;
}

public static HitboxType classify(
boolean self,
boolean player,
boolean arrow,
boolean projectile,
boolean item,
boolean hostile,
boolean living
) {
if (self) {
return SELF;
}
if (player) {
return PLAYERS;
}
if (arrow) {
return ARROWS;
}
if (projectile) {
return PROJECTILES;
}
if (item) {
return ITEMS;
}
if (hostile) {
return HOSTILE;
}
if (living) {
return PASSIVE;
}
return OTHER;
}
}
19 changes: 19 additions & 0 deletions common/src/main/resources/assets/dolphinclient/lang/en_us.json
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,24 @@
"hitColor": "Hit Color",
"hitColor.tooltip": "The Color used for entities when they are being hurt.",
"hit_color_on_armor": "Overlay Hit Color on Armor",
"hitboxes": "Hitboxes",
"hitboxes.arrows": "Arrows",
"hitboxes.hostile": "Hostile Mobs",
"hitboxes.items": "Items",
"hitboxes.line_width": "Line Width",
"hitboxes.other": "Other",
"hitboxes.passive": "Passive Mobs",
"hitboxes.players": "Players",
"hitboxes.projectiles": "Projectiles",
"hitboxes.self": "Self",
"hitboxes.show": "Show",
"hitboxes.show.tooltip": "Render hitboxes for this entity type.",
"hitboxes.show_eye_height": "Eye Height Line",
"hitboxes.show_eye_height.tooltip": "Draws the red eye-height debug line on living entities.",
"hitboxes.show_invisible": "Invisible Entities",
"hitboxes.show_invisible.tooltip": "Show hitboxes on entities that are invisible.",
"hitboxes.show_look_vector": "Look Vector",
"hitboxes.show_look_vector.tooltip": "Draws the blue look-direction line from the entity's eyes.",
"horizontal": "Horizontal speed only",
"hotbarhud": "Hotbar",
"hud": "Options",
Expand Down Expand Up @@ -570,6 +588,7 @@
"hud.entry.usage": "Press Space to enter movement mode. Press Enter to open this module's options.",
"hud.entry.usage.move": "Press Escape or Space to exit movement mode, use arrow keys to move this HUD. Press Enter to open this module's options.",
"toggle_fullbright": "Toggle Full Bright",
"toggle_hitboxes": "Toggle Hitboxes",
"inventoryhud": "Inventory",
"key.toggle_hud": "Toggle HUD modules",
"coordshud.unknown_biome": "Unknown",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*
* 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.axolotlclient.modules.hitboxes;

import java.util.HashSet;
import java.util.Set;

import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

class HitboxTypeTest {

@Test
void classifiesInSpecificToBroadOrder() {
assertEquals(HitboxType.SELF, HitboxType.classify(true, true, false, false, false, false, false));
assertEquals(HitboxType.PLAYERS, HitboxType.classify(false, true, false, false, false, false, true));
assertEquals(HitboxType.ARROWS, HitboxType.classify(false, false, true, true, false, false, false));
assertEquals(HitboxType.PROJECTILES, HitboxType.classify(false, false, false, true, false, false, false));
assertEquals(HitboxType.ITEMS, HitboxType.classify(false, false, false, false, true, false, false));
assertEquals(HitboxType.HOSTILE, HitboxType.classify(false, false, false, false, false, true, true));
assertEquals(HitboxType.PASSIVE, HitboxType.classify(false, false, false, false, false, false, true));
assertEquals(HitboxType.OTHER, HitboxType.classify(false, false, false, false, false, false, false));
}

@Test
void keepsTranslationKeysAndDefaultColorsUnique() {
Set<String> keys = new HashSet<>();
Set<Integer> colors = new HashSet<>();
for (HitboxType type : HitboxType.values()) {
assertTrue(type.getTranslationKey().startsWith("hitboxes."));
assertTrue(keys.add(type.getTranslationKey()), type.name());
assertNotEquals(0, type.getDefaultColor() >>> 24, type.name());
assertTrue(colors.add(type.getDefaultColor()), type.name());
}
assertEquals(HitboxType.values().length, keys.size());
assertEquals(HitboxType.values().length, colors.size());
}

@Test
void langFileContainsHitboxEntries() throws Exception {
try (var in = HitboxType.class.getClassLoader()
.getResourceAsStream("assets/dolphinclient/lang/en_us.json")) {
assertTrue(in != null, "en_us.json");
String json = new String(in.readAllBytes());
assertTrue(json.contains("\"hitboxes\""));
assertTrue(json.contains("\"hitboxes.show\""));
assertTrue(json.contains("\"toggle_hitboxes\""));
for (HitboxType type : HitboxType.values()) {
assertTrue(json.contains("\"" + type.getTranslationKey() + "\""), type.getTranslationKey());
}
}
}
}
Binary file added docs/hitboxes.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import io.github.axolotlclient.modules.blur.MotionBlur;
import io.github.axolotlclient.modules.hud.HudManager;
import io.github.axolotlclient.modules.hud.gui.hud.PackDisplayHud;
import io.github.axolotlclient.modules.hitboxes.Hitboxes;
import io.github.axolotlclient.modules.hypixel.HypixelMods;
import io.github.axolotlclient.modules.particles.Particles;
import io.github.axolotlclient.modules.screenshotUtils.ScreenshotUtils;
Expand All @@ -52,6 +53,7 @@ private void addBuiltinModules() {
registerModule(MenuBlur.getInstance());
registerModule(ScrollableTooltips.getInstance());

registerModule(Hitboxes.getInstance());
registerModule(Particles.getInstance());
registerModule(ScreenshotUtils.getInstance());
registerModule(UnfocusedFpsLimiter.getInstance());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ public static List<Module> build() {
absorbAnimations(modules, category);
continue;
}
Tab tab = isSettings(name) ? Tab.SETTINGS : Tab.MODS;
Tab tab = isSettings(name) ? Tab.SETTINGS : isHudTab(name) ? Tab.HUD : Tab.MODS;
absorb(modules, category, tab);
}

Expand Down Expand Up @@ -193,13 +193,18 @@ private static Module moduleFrom(String nameKey, Tab tab, BooleanOption enabled,
/**
* Settings keeps a tile per child even when only Screenshots remains
* under General, matching the layout from before Authentication was
* removed. Mods still need at least two children before splitting.
* removed. Mods still need at least two children before splitting. HUD
* tiles always stay whole so an overlay's groups render as sections rather
* than as one tile per group.
*/
private static boolean shouldPromote(Collection<OptionCategory> children, Tab tab) {
int size = children.size();
if (size == 0 || size > 12) {
return false;
}
if (tab == Tab.HUD) {
return false;
}
return tab == Tab.SETTINGS || size >= 2;
}

Expand Down Expand Up @@ -257,6 +262,14 @@ private static boolean isSettings(String name) {
return "general".equals(name);
}

/**
* Overlays that draw in the world rather than on a HUD widget still belong
* on the HUD tab, so they are routed there instead of Mods.
*/
private static boolean isHudTab(String name) {
return "hitboxes".equals(name);
}

private static OptionCategory find(Collection<OptionCategory> categories, String name) {
for (OptionCategory category : categories) {
if (name.equals(category.getName())) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,45 @@
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import io.github.axolotlclient.modules.freelook.Freelook;
import io.github.axolotlclient.modules.hitboxes.Hitboxes;
import net.minecraft.client.render.entity.EntityRenderDispatcher;
import net.minecraft.entity.Entity;
import org.objectweb.asm.Opcodes;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;

@Mixin(EntityRenderDispatcher.class)
public abstract class EntityRenderDispatcherMixin {

@WrapOperation(method = "render(Lnet/minecraft/entity/Entity;DDDFFZ)Z", at = @At(value = "FIELD", target = "Lnet/minecraft/client/render/entity/EntityRenderDispatcher;renderHitboxes:Z", opcode = Opcodes.GETFIELD))
private boolean dolphinclient$forceHitboxes(EntityRenderDispatcher instance, Operation<Boolean> original, Entity entity) {
Hitboxes hitboxes = Hitboxes.getInstance();
if (hitboxes.isEnabled()) {
return hitboxes.shouldRender(entity);
}
return original.call(instance);
}

@WrapOperation(method = "render(Lnet/minecraft/entity/Entity;DDDFFZ)Z", at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/Entity;isInvisible()Z"))
private boolean dolphinclient$showInvisibleHitboxes(Entity entity, Operation<Boolean> original) {
if (Hitboxes.getInstance().shouldRender(entity)) {
return false;
}
return original.call(entity);
}

@Inject(method = "renderHitbox", at = @At("HEAD"), cancellable = true)
private void dolphinclient$renderHitboxes(Entity entity, double dx, double dy, double dz, float yaw, float tickDelta, CallbackInfo ci) {
Hitboxes hitboxes = Hitboxes.getInstance();
if (!hitboxes.isEnabled()) {
return;
}
hitboxes.render(entity, dx, dy, dz, tickDelta);
ci.cancel();
}

@WrapOperation(method = "prepare", at = @At(value = "FIELD", target = "Lnet/minecraft/entity/Entity;yaw:F", opcode = Opcodes.GETFIELD))
public float axolotlclient$freelook$yaw(Entity instance, Operation<Float> original) {
return Freelook.getInstance().yaw(original.call(instance));
Expand Down
Loading