chore(history): squash 59 commit(s) from 2026-09-23
- refactor(hud/weather/render): Scoreboard 931→240 + Rain 903→159 + UiMeshGeometry 956→99 (8.5.2) - refactor(chams): Chams 985 → 129 + 4×≤195 (8.5.2) - refactor(module): Module 1031 → 139 + 4×≤134 (8.5.2) - refactor(tooltips): BetterTooltips 960 → 181 + 4×≤199 (8.5.2) - refactor(clickgui): ClickGuiPickerState 970→166 + ModulesMenuScreen 966→145 (8.5.2) - refactor(clickgui): CombatProtocolHeuristicsEditorState 932→79 + 4×≤192 (8.5.2) - refactor(hud): CompactHudStatModel 894 → 125 + 4×≤175 (8.5.2) - refactor(iris): ShaderPatchCompiler 843→66 + 4×≤174 (8.5.2) - refactor(tab): CustomTabList 834→136 + 4×≤166 (8.5.2) - refactor(relations): OnlineRelationPlayerPickerComponent 837→74 + 4×≤192 (8.5.2) - refactor(hits): HitEffect 770→120 + 4×≤176 (8.5.2) - refactor(predict): Predictions 815→150 + 4×≤195 (8.5.2) - refactor(config): ConfigProfilesComponent 799→125 + 4×≤195 (8.5.2) - refactor(sim): PlayerMovementSimulation 757→157 + 4×≤194 (8.5.2) - refactor(predict): ProjectilePuncher 747→104 + 3×≤134 (8.5.2) - refactor(crosshair): Crosshair 743→143 + 4×≤167 (8.5.2) - refactor(mediaplayer): MediaPlayer 743→174 + 4×≤196 (8.5.2) - refactor(hud): Itemizer 699→193 + Cooldowns 707→151 (8.5.2) - refactor(hud): Potions 617→176 + 4×≤166 (8.5.2) - refactor(hud): Admins 702→118 + 4×≤138 (8.5.2) - refactor(hud): Keybinds 611→157 + 4×≤160 (8.5.2) - refactor(hud): ModuleList 499→181 + 3×≤150 (8.5.2) - refactor(hud): Radar 392→161 + 2×≤124 (8.5.2) - refactor(hud): Fps 309→168 + 3×≤107 (8.5.2) - refactor(hud): Memory 304→151 + 3×≤144 (8.5.2) - refactor(hud): Tps 339→154 + 3×≤143 (8.5.2) - refactor(hud): Ping 309→145 + 3×≤125 (8.5.2) - refactor(hud): SpeedBps 334→156 + 3×≤142 (8.5.2) - refactor(hud): GameTime 314→150 + 3×≤127 (8.5.2) - refactor(hud): SystemTime 300→136 + 3×≤130 (8.5.2) - refactor(hud): Coordinates 337→174 + 2×≤172 (8.5.2) - refactor(hud): Armor 324→162 + 2×≤148 (8.5.2) - refactor(hud): Inventory 476→153 + 3×≤181 (8.5.2) - docs(todo): обновлён 8.5.2 — отмечены готовые гиганты (BetterButtons/InventorySwap/Svg/Темы/WorldParticles/Module/Chams и хвост) - docs(api): папка combatant-client-26.2/docs + addons.md (API можно ломать при рефакторе) - refactor(visuals): BlockHighlight 692→271 + 4×≤200 (8.5.2) - refactor(mixins): GameRendererMixin 830→344 (хуки) + 4 хелпера (8.5.2) - refactor(visuals): WorldParticles 821→260 + 6×≤200, режимы добиты (8.5.2) - docs(todo): 8.5.2 — WorldParticles/GameRendererMixin/BlockHighlight отмечены ГОТОВО - refactor(render): MeshBuilder 738→447 (фасад write-API) + 3 хелпера, FrameStats снесён (8.5.2) - docs(todo): Фаза 10 — дизайн платформы (аккаунты/конфиги/аватарки), backlog RPC-чата - docs(todo): Подсистема 3 — Аддоны 2.0 (скрипты/версии) + витрина аддонов + админка - docs(todo): Подсистема 2 — RPC-чат/друзья/виджеты-телеметрия + рейт-лимиты, доп. правки Подсистемы 3 - chore: переименование combatant-client-26.2 -> mod, добавлены backend/ и frontend/ - docs(backend): implementation plan for accounts-service (Подсистема 1, часть 1) - chore: пересоздать backend/frontend через cargo new и bun create vite + tailwind; убрать таблицу компонентов из TODO.md - docs(backend): edition 2024 в плане вместо 2021 - chore: обновить версии до реально актуальных (проверено компиляцией) - docs: правила платформы (лимит 250 строк, антипаттерны backend/frontend по итогам ресёрча) + frontend/ARCHITECTURE.md - docs: добавить правило ≤4 файла на папку в правила платформы (backend/frontend) - docs: commit messages in English from now on - chore: gitignore .superpowers/ scratch directory - docs: commit messages in English from now on (mod) - feat(backend): scaffold accounts-service with health check - chore(backend): remove target/ build artifacts from git, add gitignore - chore: track docs/ in git (was accidentally gitignored, never committed) - chore(backend): convert to Cargo workspace, plan full microservice layout - feat(backend): accounts/avatars/device_links schema + migration - feat(backend): argon2id password hashing
This commit is contained in:
parent
ec10395511
commit
9d08fa910a
2239 changed files with 35265 additions and 28110 deletions
|
|
@ -0,0 +1,120 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.mixins;
|
||||
|
||||
import net.fabricmc.loader.api.FabricLoader;
|
||||
import org.objectweb.asm.tree.ClassNode;
|
||||
import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin;
|
||||
import org.spongepowered.asm.mixin.extensibility.IMixinInfo;
|
||||
import dev.loki.lovisual.util.logging.DebugLog;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public final class LoVisualMixinPlugin implements IMixinConfigPlugin {
|
||||
// Sodium 26.2 uses Mojang RenderPipeline for terrain on both GL and Vulkan backends.
|
||||
private static final boolean STUB_SODIUM = false;
|
||||
private static final boolean STUB_IRIS = false;
|
||||
private static final Set<String> DISABLED_SODIUM_SHADOW_GRAPH_MIXINS = Set.of();
|
||||
private static final Set<String> OPTIONAL_SODIUM_MIXINS = Set.of(
|
||||
"dev.loki.lovisual.mixins.sodium.terrain.SodiumBlockRendererMixin",
|
||||
"dev.loki.lovisual.mixins.sodium.terrain.SodiumChunkMeshFormatsMixin",
|
||||
"dev.loki.lovisual.mixins.sodium.vertex.SodiumChunkVertexMixin",
|
||||
"dev.loki.lovisual.mixins.sodium.terrain.SodiumShaderChunkRendererMixin",
|
||||
"dev.loki.lovisual.mixins.sodium.vertex.SodiumVertexConsumerTrackerMixin"
|
||||
);
|
||||
private static final Set<String> OPTIONAL_IRIS_MIXINS = Set.of(
|
||||
"dev.loki.lovisual.mixins.iris.render.IrisCommonUniformsMixin",
|
||||
"dev.loki.lovisual.mixins.iris.render.IrisGameRendererInteropMixin",
|
||||
"dev.loki.lovisual.mixins.iris.hand.IrisHandRendererAccessor",
|
||||
"dev.loki.lovisual.mixins.iris.hand.IrisHandRendererMixin",
|
||||
"dev.loki.lovisual.mixins.iris.processor.IrisIncludeProcessorMixin",
|
||||
"dev.loki.lovisual.mixins.iris.render.IrisShaderPackLoadMixin",
|
||||
"dev.loki.lovisual.mixins.iris.render.IrisRenderingPipelineFinalizeMixin",
|
||||
"dev.loki.lovisual.mixins.iris.hand.IrisVanillaHandInteropMixin"
|
||||
);
|
||||
private static final Set<String> DISABLED_WITH_IRIS_MIXINS = Set.of(
|
||||
"dev.loki.lovisual.mixins.sodium.terrain.SodiumBlockRendererMixin",
|
||||
"dev.loki.lovisual.mixins.sodium.terrain.SodiumChunkMeshFormatsMixin",
|
||||
"dev.loki.lovisual.mixins.sodium.vertex.SodiumChunkVertexMixin",
|
||||
"dev.loki.lovisual.mixins.sodium.terrain.SodiumShaderChunkRendererMixin"
|
||||
);
|
||||
private static final Set<String> OPTIONAL_XAERO_MINIMAP_MIXINS = Set.of(
|
||||
"dev.loki.lovisual.mixins.xaero.minimap.AbstractWaypointRenderProviderMixin"
|
||||
);
|
||||
private static final Set<String> OPTIONAL_XAERO_WORLDMAP_MIXINS = Set.of(
|
||||
"dev.loki.lovisual.mixins.xaero.worldmap.WaypointRenderProviderMixin"
|
||||
);
|
||||
private static final Set<String> OPTIONAL_MORECULLING_MIXINS = Set.of(
|
||||
"dev.loki.lovisual.mixins.moreculling.CullingUtilsMixin"
|
||||
);
|
||||
private static volatile boolean loggedSodiumDecision;
|
||||
|
||||
@Override
|
||||
public void onLoad(String mixinPackage) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRefMapperConfig() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldApplyMixin(String targetClassName, String mixinClassName) {
|
||||
if (STUB_SODIUM && OPTIONAL_SODIUM_MIXINS.contains(mixinClassName)) {
|
||||
return false;
|
||||
}
|
||||
if (STUB_IRIS && OPTIONAL_IRIS_MIXINS.contains(mixinClassName)) {
|
||||
return false;
|
||||
}
|
||||
if (DISABLED_SODIUM_SHADOW_GRAPH_MIXINS.contains(mixinClassName)) {
|
||||
return false;
|
||||
}
|
||||
if (OPTIONAL_XAERO_MINIMAP_MIXINS.contains(mixinClassName)) {
|
||||
return FabricLoader.getInstance().isModLoaded("xaerominimap");
|
||||
}
|
||||
if (OPTIONAL_XAERO_WORLDMAP_MIXINS.contains(mixinClassName)) {
|
||||
return FabricLoader.getInstance().isModLoaded("xaeroworldmap");
|
||||
}
|
||||
if (OPTIONAL_MORECULLING_MIXINS.contains(mixinClassName)) {
|
||||
return FabricLoader.getInstance().isModLoaded("moreculling");
|
||||
}
|
||||
if (OPTIONAL_IRIS_MIXINS.contains(mixinClassName)) {
|
||||
return FabricLoader.getInstance().isModLoaded("iris");
|
||||
}
|
||||
if (DISABLED_WITH_IRIS_MIXINS.contains(mixinClassName)) {
|
||||
return !FabricLoader.getInstance().isModLoaded("iris");
|
||||
}
|
||||
if (!OPTIONAL_SODIUM_MIXINS.contains(mixinClassName)) {
|
||||
return true;
|
||||
}
|
||||
boolean sodiumLoaded = FabricLoader.getInstance().isModLoaded("sodium");
|
||||
if (!loggedSodiumDecision) {
|
||||
loggedSodiumDecision = true;
|
||||
DebugLog.warn("[SodiumCompat] optional mixins %s", sodiumLoaded ? "enabled" : "disabled");
|
||||
}
|
||||
return sodiumLoaded;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void acceptTargets(Set<String> myTargets, Set<String> otherTargets) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getMixins() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) {
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.mixins.accessors.blaze3d;
|
||||
|
||||
import com.mojang.blaze3d.GpuFormat;
|
||||
import com.mojang.blaze3d.opengl.FrameBufferCache;
|
||||
import com.mojang.blaze3d.opengl.GlTexture;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Invoker;
|
||||
|
||||
@Mixin(GlTexture.class)
|
||||
public interface GlTextureInvoker {
|
||||
@Invoker("<init>")
|
||||
static GlTexture lovisual$create(int usage,
|
||||
String label,
|
||||
GpuFormat format,
|
||||
int width,
|
||||
int height,
|
||||
int depthOrLayers,
|
||||
int mipLevels,
|
||||
int glId,
|
||||
FrameBufferCache frameBufferCache) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.mixins.accessors.blaze3d;
|
||||
|
||||
import com.mojang.blaze3d.systems.GpuDevice;
|
||||
import com.mojang.blaze3d.systems.GpuDeviceBackend;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||
|
||||
@Mixin(GpuDevice.class)
|
||||
public interface GpuDeviceAccessor {
|
||||
@Accessor("backend")
|
||||
GpuDeviceBackend lovisual$getBackend();
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.mixins.accessors.blaze3d;
|
||||
|
||||
import net.minecraft.client.renderer.PostChainConfig;
|
||||
import net.minecraft.client.renderer.ShaderManager;
|
||||
import net.minecraft.resources.Identifier;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@Mixin(ShaderManager.Configs.class)
|
||||
public interface ShaderLoaderDefinitionsAccessor {
|
||||
@Accessor("shaderSources")
|
||||
Map<?, String> lovisual$getShaderSources();
|
||||
|
||||
@Accessor("postChains")
|
||||
Map<Identifier, PostChainConfig> lovisual$getPostChains();
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.mixins.accessors.blaze3d;
|
||||
|
||||
import com.mojang.blaze3d.shaders.ShaderType;
|
||||
import net.minecraft.resources.Identifier;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||
|
||||
@Mixin(targets = "net.minecraft.client.renderer.ShaderManager$ShaderSourceKey")
|
||||
public interface ShaderSourceKeyAccessor {
|
||||
@Accessor("id")
|
||||
Identifier lovisual$getId();
|
||||
|
||||
@Accessor("type")
|
||||
ShaderType lovisual$getType();
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.mixins.accessors.chat;
|
||||
|
||||
import com.mojang.brigadier.suggestion.Suggestions;
|
||||
import net.minecraft.client.gui.components.CommandSuggestions;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
@Mixin(CommandSuggestions.class)
|
||||
public interface ChatInputSuggestorAccessor {
|
||||
@Accessor("suggestions")
|
||||
CommandSuggestions.SuggestionsList getLoVisual$window();
|
||||
|
||||
@Accessor("pendingSuggestions")
|
||||
CompletableFuture<Suggestions> getLoVisual$pendingSuggestions();
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.mixins.accessors.chat;
|
||||
|
||||
import net.minecraft.client.gui.components.EditBox;
|
||||
import net.minecraft.client.gui.screens.ChatScreen;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||
|
||||
@Mixin(ChatScreen.class)
|
||||
public interface ChatScreenAccessor {
|
||||
@Accessor("input")
|
||||
EditBox getChatField();
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.mixins.accessors.chat;
|
||||
|
||||
import net.minecraft.client.gui.components.CommandSuggestions;
|
||||
import net.minecraft.client.gui.screens.ChatScreen;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||
|
||||
@Mixin(ChatScreen.class)
|
||||
public interface ChatScreenSuggestorAccessor {
|
||||
@Accessor("commandSuggestions")
|
||||
CommandSuggestions getLoVisual$suggestor();
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.mixins.accessors.chat;
|
||||
|
||||
import net.minecraft.client.gui.components.CommandSuggestions;
|
||||
import net.minecraft.client.renderer.Rect2i;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Mixin(CommandSuggestions.SuggestionsList.class)
|
||||
public interface SuggestionWindowAccessor {
|
||||
@Accessor("rect")
|
||||
Rect2i getLoVisual$area();
|
||||
|
||||
@Accessor("suggestionList")
|
||||
List<?> getLoVisual$suggestions();
|
||||
|
||||
@Accessor("current")
|
||||
int getLoVisual$selection();
|
||||
|
||||
@Accessor("current")
|
||||
void setLoVisual$selection(int selection);
|
||||
|
||||
@Accessor("offset")
|
||||
int getLoVisual$inWindowIndex();
|
||||
|
||||
@Accessor("offset")
|
||||
void setLoVisual$inWindowIndex(int index);
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.mixins.accessors.entity;
|
||||
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.PortalProcessor;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||
import org.spongepowered.asm.mixin.gen.Invoker;
|
||||
|
||||
@Mixin(Entity.class)
|
||||
public interface EntityAccessor {
|
||||
|
||||
@Accessor("portalProcess")
|
||||
PortalProcessor lovisual$getPortalManager();
|
||||
|
||||
@Accessor("stuckSpeedMultiplier")
|
||||
void lovisual$setMovementMultiplier(Vec3 multiplier);
|
||||
|
||||
@Invoker("unsetRemoved")
|
||||
void lovisual$unsetRemoved();
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.mixins.accessors.entity;
|
||||
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Invoker;
|
||||
|
||||
@Mixin(Entity.class)
|
||||
public interface EntityInvoker {
|
||||
|
||||
@Invoker("getInputVector")
|
||||
static Vec3 lovisual$movementInputToVelocity(Vec3 movementInput, float speed, float yaw) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.mixins.accessors.entity;
|
||||
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||
|
||||
// Это новый Mixin для доступа к полю или методу, который управляет временем последней атаки
|
||||
@Mixin(LivingEntity.class)
|
||||
public interface LivingEntityAccessor {
|
||||
|
||||
// Пример использования нового поля или метода, если оно называется ticksSinceLastAttack
|
||||
@Accessor("attackStrengthTicker")
|
||||
void setTicksSinceLastAttack(int ticks);
|
||||
|
||||
@Accessor("noJumpDelay")
|
||||
void lovisual$setJumpingCooldown(int cooldown);
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.mixins.accessors.entity;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.player.LocalPlayer;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||
import org.spongepowered.asm.mixin.gen.Invoker;
|
||||
|
||||
@Mixin(LocalPlayer.class)
|
||||
public interface LocalPlayerAccessor {
|
||||
|
||||
@Accessor("minecraft")
|
||||
Minecraft getClient();
|
||||
|
||||
@Accessor("portalEffectIntensity")
|
||||
float lovisual$getNauseaIntensity();
|
||||
|
||||
@Accessor("portalEffectIntensity")
|
||||
void lovisual$setNauseaIntensity(float value);
|
||||
|
||||
@Accessor("oPortalEffectIntensity")
|
||||
float lovisual$getLastNauseaIntensity();
|
||||
|
||||
@Accessor("oPortalEffectIntensity")
|
||||
void lovisual$setLastNauseaIntensity(float value);
|
||||
|
||||
@Accessor("positionReminder")
|
||||
void lovisual$setTicksSinceLastPositionPacketSent(int ticks);
|
||||
|
||||
@Accessor("wasSprinting")
|
||||
void lovisual$setLastSprinting(boolean sprinting);
|
||||
|
||||
@Invoker("canStartSprinting")
|
||||
boolean lovisual$canStartSprinting();
|
||||
|
||||
@Invoker("shouldStopRunSprinting")
|
||||
boolean lovisual$shouldStopSprinting();
|
||||
|
||||
@Invoker("shouldStopSwimSprinting")
|
||||
boolean lovisual$shouldStopSwimSprinting();
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.mixins.accessors.hud;
|
||||
|
||||
import net.minecraft.client.gui.components.BossHealthOverlay;
|
||||
import net.minecraft.client.gui.components.LerpingBossEvent;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
@Mixin(BossHealthOverlay.class)
|
||||
public interface BossHealthOverlayAccessor {
|
||||
@Accessor("events")
|
||||
Map<UUID, LerpingBossEvent> getBars();
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.mixins.accessors.hud;
|
||||
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import net.minecraft.client.renderer.ScreenEffectRenderer;
|
||||
import net.minecraft.client.renderer.SubmitNodeCollector;
|
||||
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Invoker;
|
||||
|
||||
@Mixin(ScreenEffectRenderer.class)
|
||||
public interface InGameOverlayRendererAccessor {
|
||||
|
||||
@Invoker("submitFire")
|
||||
static void callRenderFireOverlay(PoseStack matrices,
|
||||
SubmitNodeCollector vertexConsumers,
|
||||
TextureAtlasSprite sprite) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
@Invoker("submitBlockSprite")
|
||||
static void callRenderInWallOverlay(TextureAtlasSprite sprite,
|
||||
PoseStack matrices,
|
||||
SubmitNodeCollector vertexConsumers,
|
||||
int color) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.mixins.accessors.hud;
|
||||
|
||||
import net.minecraft.client.gui.components.PlayerTabOverlay;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||
|
||||
@Mixin(PlayerTabOverlay.class)
|
||||
public interface PlayerTabOverlayAccessor {
|
||||
@Accessor("header")
|
||||
Component lovisual$getHeader();
|
||||
|
||||
@Accessor("footer")
|
||||
Component lovisual$getFooter();
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.mixins.accessors.hud;
|
||||
|
||||
import net.minecraft.client.gui.components.SpriteIconButton;
|
||||
import net.minecraft.client.gui.components.WidgetSprites;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||
|
||||
@Mixin(SpriteIconButton.class)
|
||||
public interface TextIconButtonWidgetAccessor {
|
||||
@Accessor("spriteWidth")
|
||||
int lovisual$getTextureWidth();
|
||||
|
||||
@Accessor("spriteHeight")
|
||||
int lovisual$getTextureHeight();
|
||||
|
||||
@Accessor("sprite")
|
||||
WidgetSprites lovisual$getTexture();
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.mixins.accessors.inventory;
|
||||
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||
|
||||
@Mixin(targets = "net.minecraft.world.item.ItemCooldowns$CooldownInstance")
|
||||
public interface ItemCooldownEntryAccessor {
|
||||
@Accessor("endTime")
|
||||
int getEndTick();
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.mixins.accessors.inventory;
|
||||
|
||||
import net.minecraft.resources.Identifier;
|
||||
import net.minecraft.world.item.ItemCooldowns;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@Mixin(ItemCooldowns.class)
|
||||
public interface ItemCooldownManagerAccessor {
|
||||
@Accessor("cooldowns")
|
||||
Map<Identifier, Object> getEntries();
|
||||
|
||||
@Accessor("tickCount")
|
||||
int getTick();
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.mixins.accessors.inventory;
|
||||
|
||||
import net.minecraft.world.entity.player.Inventory;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||
|
||||
@Mixin(Inventory.class)
|
||||
public interface PlayerInventoryAccessor {
|
||||
@Accessor("selected")
|
||||
int lovisual$getSelectedSlot();
|
||||
|
||||
@Accessor("selected")
|
||||
void lovisual$setSelectedSlot(int slot);
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.mixins.accessors.inventory;
|
||||
|
||||
import net.minecraft.world.inventory.Slot;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||
|
||||
@Mixin(Slot.class)
|
||||
public interface SlotAccessor {
|
||||
@Accessor("slot")
|
||||
int lovisual$getIndex();
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.mixins.accessors.misc;
|
||||
|
||||
import net.minecraft.client.player.ClientInput;
|
||||
import net.minecraft.world.entity.player.Input;
|
||||
import net.minecraft.world.phys.Vec2;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||
|
||||
@Mixin(ClientInput.class)
|
||||
public interface InputAccessor {
|
||||
|
||||
@Accessor("moveVector")
|
||||
void setMovementVector(Vec2 vec);
|
||||
|
||||
@Accessor("keyPresses")
|
||||
void setPlayerInput(Input input);
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.mixins.accessors.misc;
|
||||
|
||||
import com.mojang.blaze3d.platform.InputConstants;
|
||||
import net.minecraft.client.KeyMapping;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||
|
||||
@Mixin(KeyMapping.class)
|
||||
public interface KeyMappingAccessor {
|
||||
|
||||
@Accessor("key")
|
||||
InputConstants.Key lovisual$key();
|
||||
}
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.mixins.accessors.misc;
|
||||
|
||||
import com.mojang.authlib.minecraft.UserApiService;
|
||||
import com.mojang.authlib.yggdrasil.FriendsService;
|
||||
import com.mojang.authlib.yggdrasil.ProfileResult;
|
||||
import com.mojang.authlib.yggdrasil.YggdrasilAuthenticationService;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.User;
|
||||
import net.minecraft.client.gui.screens.social.PlayerSocialManager;
|
||||
import net.minecraft.client.gui.screens.social.RemoteFriendListUpdateHandler;
|
||||
import net.minecraft.client.main.GameConfig;
|
||||
import net.minecraft.client.multiplayer.ProfileKeyPairManager;
|
||||
import net.minecraft.client.multiplayer.chat.report.ReportingContext;
|
||||
import net.minecraft.client.telemetry.ClientTelemetryManager;
|
||||
import net.minecraft.server.Services;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Mutable;
|
||||
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||
import org.spongepowered.asm.mixin.gen.Invoker;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
@Mixin(Minecraft.class)
|
||||
public interface MinecraftAccessor {
|
||||
|
||||
@Accessor("rightClickDelay")
|
||||
int lovisual$getItemUseCooldown();
|
||||
|
||||
@Accessor("rightClickDelay")
|
||||
void lovisual$setItemUseCooldown(int itemUseCooldown);
|
||||
|
||||
@Mutable
|
||||
@Accessor("user")
|
||||
void lovisual$setSession(User session);
|
||||
|
||||
@Mutable
|
||||
@Accessor("services")
|
||||
void lovisual$setServices(Services services);
|
||||
|
||||
@Mutable
|
||||
@Accessor("profileFuture")
|
||||
void lovisual$setProfileFuture(CompletableFuture<ProfileResult> profileFuture);
|
||||
|
||||
@Mutable
|
||||
@Accessor("userApiService")
|
||||
void lovisual$setUserApiService(UserApiService userApiService);
|
||||
|
||||
@Mutable
|
||||
@Accessor("userPropertiesFuture")
|
||||
void lovisual$setUserPropertiesFuture(CompletableFuture<UserApiService.UserProperties> userPropertiesFuture);
|
||||
|
||||
@Mutable
|
||||
@Accessor("playerSocialManager")
|
||||
void lovisual$setPlayerSocialManager(PlayerSocialManager playerSocialManager);
|
||||
|
||||
@Accessor("remoteFriendListUpdateHandler")
|
||||
RemoteFriendListUpdateHandler lovisual$getRemoteFriendListUpdateHandler();
|
||||
|
||||
@Mutable
|
||||
@Accessor("remoteFriendListUpdateHandler")
|
||||
void lovisual$setRemoteFriendListUpdateHandler(RemoteFriendListUpdateHandler remoteFriendListUpdateHandler);
|
||||
|
||||
@Mutable
|
||||
@Accessor("telemetryManager")
|
||||
void lovisual$setTelemetryManager(ClientTelemetryManager telemetryManager);
|
||||
|
||||
@Mutable
|
||||
@Accessor("profileKeyPairManager")
|
||||
void lovisual$setProfileKeyPairManager(ProfileKeyPairManager profileKeyPairManager);
|
||||
|
||||
@Mutable
|
||||
@Accessor("reportingContext")
|
||||
void lovisual$setReportingContext(ReportingContext reportingContext);
|
||||
|
||||
@Invoker("createUserApiService")
|
||||
static UserApiService lovisual$createUserApiService(YggdrasilAuthenticationService service, GameConfig config) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.mixins.accessors.misc;
|
||||
|
||||
import net.minecraft.client.multiplayer.MultiPlayerGameMode;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||
import org.spongepowered.asm.mixin.gen.Invoker;
|
||||
|
||||
@Mixin(MultiPlayerGameMode.class)
|
||||
public interface MultiPlayerGameModeAccessor {
|
||||
|
||||
@Accessor("destroyBlockPos")
|
||||
BlockPos lovisual$getCurrentBreakingPos();
|
||||
|
||||
@Accessor("destroyProgress")
|
||||
float lovisual$getCurrentBreakingProgress();
|
||||
|
||||
@Accessor("destroyProgress")
|
||||
void lovisual$setCurrentBreakingProgress(float progress);
|
||||
|
||||
@Accessor("destroyDelay")
|
||||
int lovisual$getBlockBreakingCooldown();
|
||||
|
||||
@Accessor("destroyDelay")
|
||||
void lovisual$setBlockBreakingCooldown(int cooldown);
|
||||
|
||||
@Accessor("carriedIndex")
|
||||
int lovisual$getLastSelectedSlot();
|
||||
|
||||
@Accessor("carriedIndex")
|
||||
void lovisual$setLastSelectedSlot(int slot);
|
||||
|
||||
@Invoker("ensureHasSentCarriedItem")
|
||||
void lovisual$syncSelectedSlot();
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.mixins.accessors.packet;
|
||||
|
||||
import net.minecraft.network.protocol.game.ClientboundExplodePacket;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Mutable;
|
||||
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
@Mixin(ClientboundExplodePacket.class)
|
||||
public interface ClientboundExplodePacketAccessor {
|
||||
|
||||
@Mutable
|
||||
@Accessor("playerKnockback")
|
||||
void lovisual$setPlayerKnockback(Optional<Vec3> playerKnockback);
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.mixins.accessors.packet;
|
||||
|
||||
import net.minecraft.network.protocol.game.ClientboundSetEntityMotionPacket;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Mutable;
|
||||
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||
|
||||
@Mixin(ClientboundSetEntityMotionPacket.class)
|
||||
public interface ClientboundSetEntityMotionPacketAccessor {
|
||||
|
||||
@Mutable
|
||||
@Accessor("movement")
|
||||
void lovisual$setVelocity(Vec3 velocity);
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.mixins.accessors.packet;
|
||||
|
||||
import net.minecraft.network.protocol.game.ServerboundInteractPacket;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||
|
||||
@Mixin(ServerboundInteractPacket.class)
|
||||
public interface ServerboundInteractPacketAccessor {
|
||||
@Accessor("entityId")
|
||||
int lovisual$getEntityId();
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.mixins.accessors.packet;
|
||||
|
||||
import net.minecraft.network.protocol.game.ServerboundMovePlayerPacket;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Mutable;
|
||||
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||
|
||||
@Mixin(ServerboundMovePlayerPacket.class)
|
||||
public interface ServerboundMovePlayerPacketAccessor {
|
||||
@Mutable
|
||||
@Accessor("x")
|
||||
void lovisual$setX(double x);
|
||||
|
||||
@Mutable
|
||||
@Accessor("y")
|
||||
void lovisual$setY(double y);
|
||||
|
||||
@Mutable
|
||||
@Accessor("z")
|
||||
void lovisual$setZ(double z);
|
||||
|
||||
@Mutable
|
||||
@Accessor("onGround")
|
||||
void lovisual$setOnGround(boolean onGround);
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.mixins.accessors.projectile;
|
||||
|
||||
import net.minecraft.world.entity.projectile.arrow.AbstractArrow;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Invoker;
|
||||
|
||||
@Mixin(AbstractArrow.class)
|
||||
public interface PersistentProjectileEntityAccessor {
|
||||
@Invoker("isInGround")
|
||||
boolean lovisual$isInGround();
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.mixins.accessors.renderer;
|
||||
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import net.minecraft.client.renderer.SubmitNodeCollector;
|
||||
import net.minecraft.client.renderer.entity.EntityRenderDispatcher;
|
||||
import net.minecraft.client.renderer.entity.EntityRenderer;
|
||||
import net.minecraft.client.renderer.entity.state.EntityRenderState;
|
||||
import net.minecraft.client.renderer.state.level.CameraRenderState;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Invoker;
|
||||
|
||||
@Mixin(EntityRenderDispatcher.class)
|
||||
public interface EntityRenderManagerAccessor {
|
||||
@Invoker("getRenderer")
|
||||
EntityRenderer<?, ?> lovisual$invokeGetRenderer(Entity entity);
|
||||
|
||||
@Invoker("submit")
|
||||
void lovisual$invokeRender(EntityRenderState renderState,
|
||||
CameraRenderState cameraState,
|
||||
double offsetX,
|
||||
double offsetY,
|
||||
double offsetZ,
|
||||
PoseStack matrices,
|
||||
SubmitNodeCollector queue);
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.mixins.accessors.renderer;
|
||||
|
||||
import net.minecraft.client.renderer.entity.EntityRenderer;
|
||||
import net.minecraft.client.renderer.entity.state.EntityRenderState;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Invoker;
|
||||
|
||||
@Mixin(EntityRenderer.class)
|
||||
public interface EntityRendererAccessor {
|
||||
@Invoker("createRenderState")
|
||||
EntityRenderState lovisual$invokeCreateRenderState();
|
||||
|
||||
@Invoker("extractRenderState")
|
||||
void lovisual$invokeUpdateRenderState(Entity entity, EntityRenderState state, float tickProgress);
|
||||
}
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.mixins.accessors.renderer;
|
||||
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import com.mojang.blaze3d.pipeline.RenderTarget;
|
||||
import net.minecraft.client.gui.render.GuiRenderer;
|
||||
import net.minecraft.client.renderer.GameRenderer;
|
||||
import net.minecraft.client.renderer.SubmitNodeStorage;
|
||||
import net.minecraft.client.renderer.fog.FogRenderer;
|
||||
import net.minecraft.client.renderer.state.GameRenderState;
|
||||
import net.minecraft.client.renderer.state.level.CameraRenderState;
|
||||
import org.joml.Matrix4fc;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||
import org.spongepowered.asm.mixin.gen.Invoker;
|
||||
|
||||
@Mixin(GameRenderer.class)
|
||||
public interface GameRendererAccessor {
|
||||
|
||||
@Invoker("bobHurt")
|
||||
void invokeTiltViewWhenHurt(CameraRenderState cameraRenderState, PoseStack matrices);
|
||||
|
||||
@Invoker("bobView")
|
||||
void invokeBobView(CameraRenderState cameraRenderState, PoseStack matrices);
|
||||
|
||||
@Invoker("renderItemInHand")
|
||||
void invokeRenderHand(CameraRenderState cameraRenderState, float tickDelta, Matrix4fc positionMatrix);
|
||||
|
||||
@Accessor("gameRenderState")
|
||||
GameRenderState lovisual$getGameRenderState();
|
||||
|
||||
@Accessor("handAndScreenSubmitNodeStorage")
|
||||
SubmitNodeStorage lovisual$getHandAndScreenSubmitNodeStorage();
|
||||
|
||||
@Accessor("mainRenderTarget")
|
||||
RenderTarget lovisual$getMainRenderTargetRaw();
|
||||
|
||||
@Accessor("guiRenderer")
|
||||
GuiRenderer lovisual$getGuiRenderer();
|
||||
|
||||
@Accessor("fogRenderer")
|
||||
FogRenderer lovisual$getFogRenderer();
|
||||
|
||||
@Accessor("spinningEffectTime")
|
||||
float lovisual$getNauseaEffectTime();
|
||||
|
||||
@Accessor("spinningEffectSpeed")
|
||||
float lovisual$getNauseaEffectSpeed();
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.mixins.accessors.renderer;
|
||||
|
||||
import net.minecraft.client.renderer.rendertype.RenderSetup;
|
||||
import net.minecraft.client.renderer.rendertype.RenderType;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Invoker;
|
||||
|
||||
@Mixin(RenderType.class)
|
||||
public interface RenderLayerInvokerAccessor {
|
||||
|
||||
@Invoker("create")
|
||||
static RenderType callOf(String name, RenderSetup setup) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.mixins.accessors.state;
|
||||
|
||||
import net.minecraft.core.particles.SpellParticleOption;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||
|
||||
@Mixin(SpellParticleOption.class)
|
||||
public interface EffectParticleEffectAccessor {
|
||||
@Accessor("color")
|
||||
int lovisual$getColor();
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.mixins.accessors.state;
|
||||
|
||||
import net.minecraft.world.item.component.Fireworks;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||
|
||||
@Mixin(Fireworks.class)
|
||||
public interface FireworksComponentAccessor {
|
||||
@Accessor("flightDuration")
|
||||
int lovisual$getFlightDuration();
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.mixins.accessors.state;
|
||||
|
||||
import net.minecraft.client.renderer.entity.state.LivingEntityRenderState;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||
|
||||
@Mixin(LivingEntityRenderState.class)
|
||||
public interface LivingEntityRenderStateAccessor {
|
||||
|
||||
@Accessor("bodyRot")
|
||||
void lovisual$setBodyYaw(float bodyYaw);
|
||||
|
||||
@Accessor("yRot")
|
||||
void lovisual$setRelativeHeadYaw(float relativeHeadYaw);
|
||||
|
||||
@Accessor("xRot")
|
||||
void lovisual$setPitch(float pitch);
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.mixins.accessors.widget;
|
||||
|
||||
import net.minecraft.client.gui.components.AbstractWidget;
|
||||
import net.minecraft.client.gui.components.WidgetTooltipHolder;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||
|
||||
@Mixin(AbstractWidget.class)
|
||||
public interface ClickableWidgetAccessor {
|
||||
@Accessor("tooltip")
|
||||
WidgetTooltipHolder lovisual$getTooltipState();
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.mixins.accessors.widget;
|
||||
|
||||
import net.minecraft.client.gui.components.CycleButton;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
@Mixin(CycleButton.class)
|
||||
public interface CyclingButtonWidgetAccessor<T> {
|
||||
@Accessor("spriteSupplier")
|
||||
CycleButton.SpriteSupplier<T> lovisual$getIcon();
|
||||
|
||||
@Accessor("displayState")
|
||||
CycleButton.DisplayState lovisual$getLabelType();
|
||||
|
||||
@Accessor("valueStringifier")
|
||||
Function<T, net.minecraft.network.chat.Component> lovisual$getValueToText();
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.mixins.accessors.widget;
|
||||
|
||||
import net.minecraft.client.gui.components.AbstractSliderButton;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||
|
||||
@Mixin(AbstractSliderButton.class)
|
||||
public interface SliderWidgetAccessor {
|
||||
@Accessor("value")
|
||||
double lovisual$getValue();
|
||||
|
||||
@Accessor("dragging")
|
||||
boolean lovisual$isDragging();
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.mixins.accessors.widget;
|
||||
|
||||
import net.minecraft.client.gui.components.EditBox;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||
|
||||
@Mixin(EditBox.class)
|
||||
public interface TextFieldWidgetAccessor {
|
||||
@Accessor("cursorPos")
|
||||
int lovisual$getSelectionStart();
|
||||
|
||||
@Accessor("highlightPos")
|
||||
int lovisual$getSelectionEnd();
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.mixins.accessors.world;
|
||||
|
||||
import net.minecraft.client.multiplayer.ClientLevel;
|
||||
import net.minecraft.client.multiplayer.prediction.BlockStatePredictionHandler;
|
||||
import net.minecraft.core.particles.ParticleOptions;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Invoker;
|
||||
|
||||
@Mixin(ClientLevel.class)
|
||||
public interface ClientLevelAccessor {
|
||||
@Invoker("doAddParticle")
|
||||
void invokeAddParticle(ParticleOptions effect,
|
||||
boolean force,
|
||||
boolean canSpawnOnMinimal,
|
||||
double x, double y, double z,
|
||||
double vx, double vy, double vz);
|
||||
|
||||
@Invoker("getBlockStatePredictionHandler")
|
||||
BlockStatePredictionHandler lovisual$getPendingUpdateManager();
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.mixins.accessors.world;
|
||||
|
||||
import net.minecraft.client.renderer.entity.state.EntityRenderState;
|
||||
import net.minecraft.client.renderer.extract.LevelExtractor;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Invoker;
|
||||
|
||||
@Mixin(LevelExtractor.class)
|
||||
public interface LevelExtractorAccessor {
|
||||
|
||||
@Invoker("extractEntity")
|
||||
EntityRenderState lovisual$invokeExtractEntity(Entity entity, float tickDelta);
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.mixins.accessors.world;
|
||||
|
||||
import com.mojang.blaze3d.textures.GpuSampler;
|
||||
import net.minecraft.client.renderer.*;
|
||||
import net.minecraft.client.renderer.feature.FeatureRenderDispatcher;
|
||||
import net.minecraft.client.renderer.state.level.LevelRenderState;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||
import org.spongepowered.asm.mixin.gen.Invoker;
|
||||
|
||||
@Mixin(LevelRenderer.class)
|
||||
public interface LevelRendererAccessor {
|
||||
|
||||
@Accessor("levelRenderState")
|
||||
LevelRenderState lovisual$getWorldRenderState();
|
||||
|
||||
@Accessor("skyRenderer")
|
||||
SkyRenderer lovisual$getSkyRendering();
|
||||
|
||||
@Accessor("submitNodeStorage")
|
||||
SubmitNodeStorage lovisual$getEntityRenderCommandQueue();
|
||||
|
||||
@Accessor("featureRenderDispatcher")
|
||||
FeatureRenderDispatcher lovisual$getEntityRenderDispatcher();
|
||||
|
||||
@Accessor("renderBuffers")
|
||||
RenderBuffers lovisual$getRenderBuffers();
|
||||
|
||||
@Accessor("renderBuffers")
|
||||
void lovisual$setRenderBuffers(RenderBuffers renderBuffers);
|
||||
|
||||
@Accessor("chunkLayerSampler")
|
||||
GpuSampler lovisual$getTerrainSampler();
|
||||
|
||||
@Accessor("chunkLayerSampler")
|
||||
void lovisual$setTerrainSampler(GpuSampler terrainSampler);
|
||||
|
||||
@Invoker("submitEntities")
|
||||
void lovisual$invokeSubmitEntities(com.mojang.blaze3d.vertex.PoseStack poseStack,
|
||||
LevelRenderState levelRenderState,
|
||||
SubmitNodeCollector collector);
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.mixins.accessors.world;
|
||||
|
||||
import net.minecraft.world.level.Level;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||
|
||||
@Mixin(Level.class)
|
||||
public interface WorldAccessor {
|
||||
|
||||
@Accessor("isClientSide")
|
||||
boolean lovisual$isClient();
|
||||
|
||||
@Accessor("rainLevel")
|
||||
float lovisual$getRainGradientRaw();
|
||||
|
||||
@Accessor("oRainLevel")
|
||||
float lovisual$getLastRainGradientRaw();
|
||||
|
||||
@Accessor("thunderLevel")
|
||||
float lovisual$getThunderGradientRaw();
|
||||
|
||||
@Accessor("oThunderLevel")
|
||||
float lovisual$getLastThunderGradientRaw();
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.mixins.blaze3d.device;
|
||||
|
||||
import com.mojang.blaze3d.opengl.DirectStateAccess;
|
||||
import com.mojang.blaze3d.opengl.FrameBufferCache;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Invoker;
|
||||
import dev.loki.lovisual.mixininterface.device.IGlBackendInfo;
|
||||
|
||||
@Mixin(targets = "com.mojang.blaze3d.opengl.GlDevice")
|
||||
public abstract class GlDeviceBackendMixin implements IGlBackendInfo {
|
||||
@Override
|
||||
@Invoker("directStateAccess")
|
||||
public abstract DirectStateAccess lovisual$directStateAccess();
|
||||
|
||||
@Override
|
||||
@Invoker("frameBufferCache")
|
||||
public abstract FrameBufferCache lovisual$frameBufferCache();
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.mixins.blaze3d.device;
|
||||
|
||||
import com.mojang.blaze3d.GpuFormat;
|
||||
import com.mojang.blaze3d.systems.GpuDevice;
|
||||
import com.mojang.blaze3d.textures.GpuTexture;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import dev.loki.lovisual.mixininterface.device.IGpuDevice;
|
||||
import dev.loki.lovisual.mixininterface.device.IMsaaDevice;
|
||||
import dev.loki.lovisual.render.engine.core.system.LoVisualRenderSystem;
|
||||
import dev.loki.lovisual.render.engine.rhi.scissor.GlobalScissorState;
|
||||
|
||||
@Mixin(GpuDevice.class)
|
||||
public abstract class GpuDeviceMixin implements IGpuDevice, IMsaaDevice {
|
||||
@Override
|
||||
public void lovisual$pushScissor(int x, int y, int width, int height) {
|
||||
GlobalScissorState.push(x, y, width, height);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void lovisual$popScissor() {
|
||||
GlobalScissorState.pop();
|
||||
}
|
||||
|
||||
@Override
|
||||
public GpuTexture lovisual$createMsaaTexture(String label, int usage, GpuFormat format, int width, int height, int samples) {
|
||||
return LoVisualRenderSystem.rhi().msaa().createTexture(label, usage, format, width, height, samples);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.mixins.blaze3d.device;
|
||||
|
||||
import com.mojang.blaze3d.systems.GpuSurface;
|
||||
import com.mojang.blaze3d.systems.CommandEncoder;
|
||||
import com.mojang.blaze3d.textures.GpuTextureView;
|
||||
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;
|
||||
import dev.loki.lovisual.render.engine.core.system.LoVisualRenderSystem;
|
||||
import dev.loki.lovisual.render.engine.profiler.bridge.TracyGpuProfiler;
|
||||
import dev.loki.lovisual.render.engine.profiler.bridge.TracyProfiler;
|
||||
import dev.loki.lovisual.render.engine.renderer.Renderer2D;
|
||||
import dev.loki.lovisual.util.FastFps;
|
||||
|
||||
@Mixin(GpuSurface.class)
|
||||
public abstract class GpuSurfaceMixin {
|
||||
/**
|
||||
* This is the last safe point before Minecraft copies the main render target into the
|
||||
* window surface. LoVisual queued text/UI/RHI submission must be flushed here, not in
|
||||
* present(), otherwise those draws happen after the surface blit and are not visible in
|
||||
* the frame that produced them.
|
||||
*/
|
||||
@Inject(method = "blitFromTexture", at = @At("HEAD"))
|
||||
private void lovisual$beforeSurfaceBlit(CommandEncoder encoder, GpuTextureView textureView, CallbackInfo info) {
|
||||
LoVisualRenderSystem.endRenderSubmission();
|
||||
}
|
||||
|
||||
@Inject(method = "present", at = @At("HEAD"))
|
||||
private void lovisual$onPresentHead(CallbackInfo info) {
|
||||
FastFps.onFrame();
|
||||
}
|
||||
|
||||
@Inject(method = "present", at = @At("TAIL"))
|
||||
private void lovisual$onPresentTail(CallbackInfo info) {
|
||||
LoVisualRenderSystem.onFramePresented();
|
||||
if (TracyGpuProfiler.isEnabled()) {
|
||||
TracyGpuProfiler.onFrameEnd();
|
||||
}
|
||||
if (TracyProfiler.isEnabled()) {
|
||||
Renderer2D.BatchStats stats = Renderer2D.getBatchStats();
|
||||
TracyProfiler.plotUiBatch(stats.getFrameDraws(), stats.getFrameVertices());
|
||||
}
|
||||
Renderer2D.getBatchStats().onFrameStart();
|
||||
Renderer2D.invalidateWorldGlassSource();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,193 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.mixins.blaze3d.device;
|
||||
|
||||
import com.mojang.blaze3d.GpuFormat;
|
||||
import com.mojang.blaze3d.pipeline.RenderPipeline;
|
||||
import com.mojang.blaze3d.shaders.ShaderSource;
|
||||
import com.mojang.blaze3d.textures.GpuTexture;
|
||||
import com.mojang.blaze3d.vulkan.VulkanConst;
|
||||
import com.mojang.blaze3d.vulkan.VulkanDevice;
|
||||
import com.mojang.blaze3d.vulkan.VulkanInstance;
|
||||
import com.mojang.blaze3d.vulkan.VulkanPhysicalDevice;
|
||||
import com.mojang.blaze3d.vulkan.VulkanRenderPipeline;
|
||||
import com.mojang.blaze3d.vulkan.checkpoints.CheckpointExtension;
|
||||
import org.lwjgl.system.MemoryStack;
|
||||
import org.lwjgl.vulkan.VkDevice;
|
||||
import org.lwjgl.vulkan.VkImageFormatProperties;
|
||||
import org.lwjgl.vulkan.VkPhysicalDeviceDepthStencilResolvePropertiesKHR;
|
||||
import org.lwjgl.vulkan.VkPhysicalDeviceLimits;
|
||||
import org.lwjgl.vulkan.VkPhysicalDeviceProperties2;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.gen.Invoker;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
import dev.loki.lovisual.render.engine.rhi.backend.vulkan.util.VulkanRenderStateBridge;
|
||||
import dev.loki.lovisual.util.logging.DebugLog;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.lwjgl.vulkan.VK12.*;
|
||||
|
||||
@Mixin(VulkanDevice.class)
|
||||
public abstract class VulkanDeviceMixin {
|
||||
@Shadow
|
||||
private ShaderSource defaultShaderSource;
|
||||
|
||||
@Unique
|
||||
private final Map<VulkanRenderStateBridge.PipelineVariantKey, VulkanRenderPipeline> lovisual$pipelineVariants = new HashMap<>();
|
||||
|
||||
@Invoker("compilePipeline")
|
||||
protected abstract VulkanRenderPipeline lovisual$compilePipelineVariant(RenderPipeline pipeline, ShaderSource source);
|
||||
|
||||
@Inject(method = "<init>", at = @At("RETURN"))
|
||||
private void lovisual$captureMsaaCapabilities(ShaderSource shaderSource,
|
||||
VulkanInstance instance,
|
||||
VulkanPhysicalDevice physicalDevice,
|
||||
Set<String> enabledExtensions,
|
||||
VkDevice device,
|
||||
long vma,
|
||||
CheckpointExtension checkpointExtension,
|
||||
CallbackInfo ci) {
|
||||
if (physicalDevice == null || physicalDevice.vkPhysicalDeviceProperties() == null) return;
|
||||
VkPhysicalDeviceLimits limits = physicalDevice.vkPhysicalDeviceProperties().limits();
|
||||
int framebufferSamples = limits.framebufferColorSampleCounts()
|
||||
& limits.framebufferDepthSampleCounts()
|
||||
& limits.framebufferStencilSampleCounts();
|
||||
|
||||
try (MemoryStack stack = MemoryStack.stackPush()) {
|
||||
int textureUsage = GpuTexture.USAGE_COPY_DST
|
||||
| GpuTexture.USAGE_COPY_SRC
|
||||
| GpuTexture.USAGE_TEXTURE_BINDING
|
||||
| GpuTexture.USAGE_RENDER_ATTACHMENT;
|
||||
framebufferSamples &= lovisual$imageSampleCounts(
|
||||
stack,
|
||||
physicalDevice,
|
||||
GpuFormat.RGBA8_UNORM,
|
||||
VulkanConst.textureUsageToVk(textureUsage, GpuFormat.RGBA8_UNORM)
|
||||
);
|
||||
framebufferSamples &= lovisual$imageSampleCounts(
|
||||
stack,
|
||||
physicalDevice,
|
||||
GpuFormat.D32_FLOAT,
|
||||
VulkanConst.textureUsageToVk(textureUsage, GpuFormat.D32_FLOAT)
|
||||
);
|
||||
framebufferSamples &= lovisual$imageSampleCounts(
|
||||
stack,
|
||||
physicalDevice,
|
||||
GpuFormat.S8_UINT,
|
||||
VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT
|
||||
);
|
||||
|
||||
VkPhysicalDeviceDepthStencilResolvePropertiesKHR resolve =
|
||||
VkPhysicalDeviceDepthStencilResolvePropertiesKHR.calloc(stack).sType$Default();
|
||||
VkPhysicalDeviceProperties2 properties = VkPhysicalDeviceProperties2.calloc(stack)
|
||||
.sType$Default()
|
||||
.pNext(resolve);
|
||||
vkGetPhysicalDeviceProperties2(physicalDevice.vkPhysicalDevice(), properties);
|
||||
VulkanRenderStateBridge.configureMsaaCapabilities(
|
||||
framebufferSamples,
|
||||
resolve.supportedDepthResolveModes(),
|
||||
resolve.independentResolveNone()
|
||||
);
|
||||
} catch (Throwable t) {
|
||||
VulkanRenderStateBridge.configureMsaaCapabilities(1, 0, false);
|
||||
DebugLog.warnOnChange(
|
||||
"msaa.vulkan.capabilities.failed",
|
||||
t.getClass().getSimpleName() + "|" + t.getMessage(),
|
||||
"[MSAA/Vulkan] capability query failed; MSAA remains disabled: %s: %s",
|
||||
t.getClass().getSimpleName(),
|
||||
t.getMessage()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Unique
|
||||
private static int lovisual$imageSampleCounts(MemoryStack stack,
|
||||
VulkanPhysicalDevice physicalDevice,
|
||||
GpuFormat format,
|
||||
int usage) {
|
||||
VkImageFormatProperties properties = VkImageFormatProperties.calloc(stack);
|
||||
int result = vkGetPhysicalDeviceImageFormatProperties(
|
||||
physicalDevice.vkPhysicalDevice(),
|
||||
VulkanConst.toVk(format),
|
||||
VK_IMAGE_TYPE_2D,
|
||||
VK_IMAGE_TILING_OPTIMAL,
|
||||
usage,
|
||||
0,
|
||||
properties
|
||||
);
|
||||
return result == VK_SUCCESS ? properties.sampleCounts() : VK_SAMPLE_COUNT_1_BIT;
|
||||
}
|
||||
|
||||
@Inject(method = "getOrCompilePipeline", at = @At("HEAD"), cancellable = true)
|
||||
private void lovisual$getOrCompilePipelineVariant(RenderPipeline pipeline,
|
||||
CallbackInfoReturnable<VulkanRenderPipeline> cir) {
|
||||
if (!VulkanRenderStateBridge.needsPipelineVariant(pipeline)) return;
|
||||
|
||||
VulkanRenderStateBridge.PipelineVariantKey key = VulkanRenderStateBridge.pipelineVariantKey(pipeline);
|
||||
VulkanRenderPipeline variant = lovisual$pipelineVariants.get(key);
|
||||
if (variant != null) {
|
||||
cir.setReturnValue(variant);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
variant = lovisual$compilePipelineVariant(pipeline, defaultShaderSource);
|
||||
if (variant == null || !variant.isValid()) {
|
||||
throw new IllegalStateException("Vulkan returned an invalid pipeline variant");
|
||||
}
|
||||
lovisual$pipelineVariants.put(key, variant);
|
||||
DebugLog.stencilOnChange(
|
||||
"vulkan.pipeline.variant.compile",
|
||||
key.toString(),
|
||||
"[Vulkan/RHI] compiled pipeline variant %s",
|
||||
key
|
||||
);
|
||||
cir.setReturnValue(variant);
|
||||
} catch (Throwable t) {
|
||||
if (key.usesMsaa()) {
|
||||
VulkanRenderStateBridge.disableMsaaAfterFailure("pipeline variant compile " + key, t);
|
||||
}
|
||||
if (key.usesStencil()) {
|
||||
VulkanRenderStateBridge.disableStencilAfterFailure("pipeline variant compile " + key, t);
|
||||
}
|
||||
DebugLog.error("[Vulkan/RHI] pipeline variant compile failed for " + key, t);
|
||||
// The current dynamic-rendering scope is already active. Falling through to vanilla's
|
||||
// single-sample/no-stencil pipeline would bind an incompatible VkPipeline and can lose
|
||||
// the device, so abort on the CPU and only disable the feature for future passes.
|
||||
throw t instanceof RuntimeException runtime ? runtime
|
||||
: new IllegalStateException("Failed to compile Vulkan pipeline variant " + key, t);
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "clearPipelineCache", at = @At("RETURN"))
|
||||
private void lovisual$clearPipelineVariants(CallbackInfo ci) {
|
||||
if (lovisual$pipelineVariants.isEmpty()) return;
|
||||
for (VulkanRenderPipeline pipeline : lovisual$pipelineVariants.values()) {
|
||||
try {
|
||||
pipeline.destroy();
|
||||
} catch (Throwable t) {
|
||||
DebugLog.warnOnChange(
|
||||
"vulkan.pipeline.variant.destroy.failed",
|
||||
t.getClass().getSimpleName() + "|" + t.getMessage(),
|
||||
"[Vulkan/RHI] failed to destroy pipeline variant: %s: %s",
|
||||
t.getClass().getSimpleName(),
|
||||
t.getMessage()
|
||||
);
|
||||
}
|
||||
}
|
||||
lovisual$pipelineVariants.clear();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.mixins.blaze3d.encoder;
|
||||
|
||||
import net.minecraft.client.renderer.SubmitNodeCollection;
|
||||
import net.minecraft.util.Mth;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.ModifyVariable;
|
||||
import dev.loki.lovisual.render.fade.ViewObstructionFadeContext;
|
||||
import dev.loki.lovisual.render.engine.msaa.MsaaWorldTarget;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
@Mixin(SubmitNodeCollection.class)
|
||||
public abstract class BatchingRenderCommandQueueMixin {
|
||||
@Unique
|
||||
private static final int LOVISUAL_VIEW_FADE_DITHER_MARK = 0x00010001;
|
||||
@Unique
|
||||
private static final int LOVISUAL_VIEW_FADE_A2C_MARK = 0x00010100;
|
||||
|
||||
@Unique
|
||||
private static int lovisual$markViewObstructionFade(int argb, float alpha, boolean msaaActive) {
|
||||
int rgb = (argb & 0x00FEFEFE) | (msaaActive ? LOVISUAL_VIEW_FADE_A2C_MARK : LOVISUAL_VIEW_FADE_DITHER_MARK);
|
||||
int alphaByte = Math.round(255.0f * Mth.clamp(alpha, 0.0f, 1.0f));
|
||||
return rgb | ((alphaByte & 0xFF) << 24);
|
||||
}
|
||||
|
||||
@Unique
|
||||
private static boolean lovisual$shouldApplyViewObstructionFade() {
|
||||
return ViewObstructionFadeContext.isActive() && ViewObstructionFadeContext.alpha() < 0.99f;
|
||||
}
|
||||
|
||||
@Unique
|
||||
private static int lovisual$applyFadeToArgb(int color) {
|
||||
int baseColor = color == -1 ? 0xFFFFFFFF : ((color & 0x00FFFFFF) | 0xFF000000);
|
||||
return lovisual$markViewObstructionFade(baseColor, ViewObstructionFadeContext.alpha(), MsaaWorldTarget.isActive());
|
||||
}
|
||||
|
||||
@ModifyVariable(method = "submitModel", at = @At("HEAD"), argsOnly = true, index = 7)
|
||||
private int lovisual$applyViewObstructionFadeAlpha(int tintedColor) {
|
||||
if (!lovisual$shouldApplyViewObstructionFade()) {
|
||||
return tintedColor;
|
||||
}
|
||||
return lovisual$applyFadeToArgb(tintedColor);
|
||||
}
|
||||
|
||||
@ModifyVariable(method = "submitItem", at = @At("HEAD"), argsOnly = true, index = 6)
|
||||
private int[] lovisual$applyViewObstructionFadeAlphaToItemTints(int[] tints) {
|
||||
if (!lovisual$shouldApplyViewObstructionFade()) {
|
||||
return tints;
|
||||
}
|
||||
|
||||
int sentinel = lovisual$applyFadeToArgb(-1);
|
||||
if (tints == null || tints.length == 0) {
|
||||
return new int[]{sentinel};
|
||||
}
|
||||
|
||||
int[] faded = Arrays.copyOf(tints, tints.length);
|
||||
faded[0] = lovisual$applyFadeToArgb(faded[0]);
|
||||
for (int i = 1; i < faded.length; i++) {
|
||||
faded[i] = lovisual$applyFadeToArgb(faded[i]);
|
||||
}
|
||||
return faded;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,128 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.mixins.blaze3d.encoder;
|
||||
|
||||
import com.mojang.blaze3d.systems.CommandEncoder;
|
||||
import com.mojang.blaze3d.systems.RenderPass;
|
||||
import com.mojang.blaze3d.systems.RenderPassDescriptor;
|
||||
import com.mojang.blaze3d.systems.RenderSystem;
|
||||
import com.mojang.blaze3d.textures.GpuTextureView;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.joml.Vector4fc;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
import dev.loki.lovisual.render.engine.core.system.LoVisualRenderSystem;
|
||||
import dev.loki.lovisual.render.engine.rhi.scissor.GlobalScissorState;
|
||||
import dev.loki.lovisual.util.logging.DebugLog;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.OptionalDouble;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
@Mixin(CommandEncoder.class)
|
||||
public abstract class CommandEncoderMixin {
|
||||
@Unique
|
||||
private static String lovisual$label(Supplier<String> label) {
|
||||
try {
|
||||
return label != null ? label.get() : "<null-label>";
|
||||
} catch (Throwable t) {
|
||||
return "<label-threw-" + t.getClass().getSimpleName() + ">";
|
||||
}
|
||||
}
|
||||
|
||||
@Unique
|
||||
private static @Nullable GpuTextureView lovisual$firstColorView(
|
||||
List<RenderPassDescriptor.Attachment<Optional<Vector4fc>>> colorAttachments
|
||||
) {
|
||||
if (colorAttachments == null) {
|
||||
return null;
|
||||
}
|
||||
for (RenderPassDescriptor.Attachment<Optional<Vector4fc>> attachment : colorAttachments) {
|
||||
if (attachment != null) {
|
||||
return attachment.textureView();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Unique
|
||||
private static @Nullable GpuTextureView lovisual$depthView(
|
||||
@Nullable RenderPassDescriptor.Attachment<OptionalDouble> depthAttachment
|
||||
) {
|
||||
return depthAttachment == null ? null : depthAttachment.textureView();
|
||||
}
|
||||
|
||||
@Unique
|
||||
private static Boolean lovisual$vulkanBackendCached;
|
||||
|
||||
@Unique
|
||||
private static boolean lovisual$isVulkanBackend() {
|
||||
// submitRenderPass runs every frame; device info is static per session — resolve once.
|
||||
Boolean cached = lovisual$vulkanBackendCached;
|
||||
if (cached != null) {
|
||||
return cached;
|
||||
}
|
||||
boolean vulkan = false;
|
||||
try {
|
||||
if (RenderSystem.getDevice() != null && RenderSystem.getDevice().getDeviceInfo() != null) {
|
||||
String backendName = RenderSystem.getDevice().getDeviceInfo().backendName();
|
||||
vulkan = backendName != null && backendName.toLowerCase(java.util.Locale.ROOT).contains("vulkan");
|
||||
}
|
||||
} catch (Throwable ignored) {
|
||||
vulkan = false;
|
||||
}
|
||||
// Only cache once the device exists; before init, retry next call.
|
||||
if (RenderSystem.getDevice() != null) {
|
||||
lovisual$vulkanBackendCached = vulkan;
|
||||
}
|
||||
return vulkan;
|
||||
}
|
||||
|
||||
@Inject(method = "createRenderPass(Lcom/mojang/blaze3d/systems/RenderPassDescriptor;)Lcom/mojang/blaze3d/systems/RenderPass;", at = @At("RETURN"))
|
||||
private void lovisual$onCreateRenderPass(
|
||||
RenderPassDescriptor descriptor,
|
||||
CallbackInfoReturnable<RenderPass> cir
|
||||
) {
|
||||
RenderPass pass = cir.getReturnValue();
|
||||
if (pass == null || descriptor == null) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
GlobalScissorState.applyTo(pass, descriptor.renderArea);
|
||||
} catch (Throwable t) {
|
||||
DebugLog.error("[Scissor] render pass scissor hook failed for %s", t, lovisual$label(descriptor.label()));
|
||||
}
|
||||
|
||||
try {
|
||||
LoVisualRenderSystem.rhi().shapeClip().beginRenderPass(
|
||||
lovisual$label(descriptor.label()),
|
||||
lovisual$firstColorView(descriptor.colorAttachments()),
|
||||
lovisual$depthView(descriptor.depthAttachment())
|
||||
);
|
||||
} catch (Throwable t) {
|
||||
DebugLog.error("[ShapeClip] beginRenderPass hook failed for %s", t, lovisual$label(descriptor.label()));
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "submitRenderPass", at = @At("HEAD"))
|
||||
private void lovisual$resetRhiRenderPassState(CallbackInfo ci) {
|
||||
if (lovisual$isVulkanBackend()) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
LoVisualRenderSystem.rhi().pipelineState().resetRenderPassState();
|
||||
} catch (Throwable t) {
|
||||
DebugLog.error("[ShapeClip/GL] render pass reset hook failed", t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.mixins.blaze3d.encoder;
|
||||
|
||||
import com.llamalad7.mixinextras.sugar.Local;
|
||||
import com.mojang.blaze3d.systems.RenderPassBackend;
|
||||
import com.mojang.blaze3d.systems.RenderPassDescriptor;
|
||||
import com.mojang.blaze3d.textures.GpuTextureView;
|
||||
import com.mojang.blaze3d.vulkan.VulkanCommandEncoder;
|
||||
import com.mojang.blaze3d.vulkan.VulkanGpuTextureView;
|
||||
import org.lwjgl.system.MemoryStack;
|
||||
import org.lwjgl.vulkan.VkClearDepthStencilValue;
|
||||
import org.lwjgl.vulkan.VkClearValue;
|
||||
import org.lwjgl.vulkan.VkRenderingAttachmentInfo;
|
||||
import org.lwjgl.vulkan.VkRenderingInfo;
|
||||
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;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
import dev.loki.lovisual.render.engine.core.system.LoVisualRenderSystem;
|
||||
import dev.loki.lovisual.render.engine.rhi.backend.vulkan.clip.VulkanShapeClipBridge;
|
||||
import dev.loki.lovisual.render.engine.rhi.backend.vulkan.msaa.VulkanMsaaResolveBridge;
|
||||
import dev.loki.lovisual.render.engine.rhi.backend.vulkan.util.VulkanRenderStateBridge;
|
||||
|
||||
import static org.lwjgl.vulkan.VK12.*;
|
||||
|
||||
@Mixin(VulkanCommandEncoder.class)
|
||||
public abstract class VulkanCommandEncoderMixin {
|
||||
@Inject(method = "createRenderPass", at = @At("HEAD"))
|
||||
private void lovisual$beginVulkanRenderPassState(RenderPassDescriptor descriptor, CallbackInfoReturnable<RenderPassBackend> cir) {
|
||||
VulkanMsaaResolveBridge.beginRenderPass();
|
||||
VulkanRenderStateBridge.beginRenderPass(descriptor);
|
||||
}
|
||||
|
||||
@Inject(
|
||||
method = "createRenderPass",
|
||||
at = @At(value = "INVOKE", target = "Lorg/lwjgl/vulkan/KHRDynamicRendering;vkCmdBeginRenderingKHR(Lorg/lwjgl/vulkan/VkCommandBuffer;Lorg/lwjgl/vulkan/VkRenderingInfo;)V", shift = At.Shift.BEFORE)
|
||||
)
|
||||
private void lovisual$configureAttachments(RenderPassDescriptor descriptor,
|
||||
CallbackInfoReturnable<RenderPassBackend> cir,
|
||||
@Local MemoryStack stack,
|
||||
@Local VkRenderingInfo renderingInfo) {
|
||||
if (VulkanRenderStateBridge.currentRenderPassNeedsStencilAttachment()) {
|
||||
GpuTextureView view = VulkanShapeClipBridge.stencilAttachmentFor(descriptor);
|
||||
if (!(view instanceof VulkanGpuTextureView vkView) || view.isClosed()) {
|
||||
VulkanRenderStateBridge.markCurrentRenderPassStencilAttachment(false);
|
||||
} else {
|
||||
VulkanRenderStateBridge.markCurrentRenderPassStencilAttachment(true);
|
||||
boolean clear = VulkanShapeClipBridge.consumeStencilClear();
|
||||
VkRenderingAttachmentInfo stencilAttachment = VkRenderingAttachmentInfo.calloc(stack).sType$Default();
|
||||
stencilAttachment
|
||||
.imageView(vkView.vkImageView())
|
||||
.imageLayout(VK_IMAGE_LAYOUT_GENERAL)
|
||||
.storeOp(VK_ATTACHMENT_STORE_OP_STORE)
|
||||
.loadOp(clear ? VK_ATTACHMENT_LOAD_OP_CLEAR : VK_ATTACHMENT_LOAD_OP_LOAD);
|
||||
if (clear) {
|
||||
VkClearDepthStencilValue clearValue = VkClearDepthStencilValue.calloc(stack).depth(0.0f).stencil(0);
|
||||
stencilAttachment.clearValue(VkClearValue.calloc(stack).depthStencil(clearValue));
|
||||
}
|
||||
renderingInfo.pStencilAttachment(stencilAttachment);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
VulkanMsaaResolveBridge.configure(descriptor, renderingInfo);
|
||||
} catch (Throwable t) {
|
||||
boolean snapshotResolve = VulkanMsaaResolveBridge.snapshotActive();
|
||||
VulkanMsaaResolveBridge.abortRenderPass();
|
||||
if (!snapshotResolve) {
|
||||
VulkanRenderStateBridge.disableMsaaAfterFailure("dynamic rendering resolve configuration", t);
|
||||
}
|
||||
throw t instanceof RuntimeException runtime ? runtime
|
||||
: new IllegalStateException("Failed to configure Vulkan MSAA resolve", t);
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "submitRenderPass", at = @At("TAIL"))
|
||||
private void lovisual$endVulkanRenderPassState(CallbackInfo ci) {
|
||||
VulkanMsaaResolveBridge.finishRenderPass();
|
||||
try {
|
||||
LoVisualRenderSystem.rhi().shapeClip().endRenderPass();
|
||||
} catch (Throwable ignored) {
|
||||
// Render pass teardown must never make Vulkan submission fail.
|
||||
}
|
||||
VulkanRenderStateBridge.endRenderPass();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.mixins.blaze3d.pipeline;
|
||||
|
||||
import com.mojang.blaze3d.pipeline.RenderPipeline;
|
||||
import com.mojang.blaze3d.systems.RenderPass;
|
||||
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;
|
||||
import dev.loki.lovisual.render.engine.core.system.LoVisualRenderSystem;
|
||||
import dev.loki.lovisual.util.logging.DebugLog;
|
||||
|
||||
@Mixin(RenderPass.class)
|
||||
public abstract class RenderPassMixin {
|
||||
@Inject(method = "setPipeline", at = @At("RETURN"))
|
||||
private void lovisual$applyRhiPipelineState(RenderPipeline pipeline, CallbackInfo ci) {
|
||||
try {
|
||||
LoVisualRenderSystem.rhi().pipelineState().applyPipelineState(pipeline);
|
||||
} catch (Throwable t) {
|
||||
DebugLog.error("[ShapeClip] pipeline state hook failed for " + pipeline, t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.mixins.blaze3d.pipeline;
|
||||
|
||||
import com.mojang.blaze3d.pipeline.RenderPipeline;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import dev.loki.lovisual.mixininterface.render.IRenderPipeline;
|
||||
import dev.loki.lovisual.render.engine.rhi.clip.ShapeClipRenderPassContract;
|
||||
|
||||
@Mixin(RenderPipeline.class)
|
||||
public abstract class RenderPipelineMixin implements IRenderPipeline {
|
||||
@Unique
|
||||
private boolean lovisual$lineSmooth;
|
||||
|
||||
@Unique
|
||||
private ShapeClipRenderPassContract lovisual$shapeClipContract = ShapeClipRenderPassContract.NONE;
|
||||
|
||||
@Override
|
||||
public void lovisual$setLineSmooth(boolean lineSmooth) {
|
||||
this.lovisual$lineSmooth = lineSmooth;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean lovisual$getLineSmooth() {
|
||||
return lovisual$lineSmooth;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void lovisual$setShapeClipContract(ShapeClipRenderPassContract contract) {
|
||||
this.lovisual$shapeClipContract = contract == null ? ShapeClipRenderPassContract.NONE : contract;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ShapeClipRenderPassContract lovisual$getShapeClipContract() {
|
||||
return lovisual$shapeClipContract;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.mixins.blaze3d.pipeline;
|
||||
|
||||
import com.mojang.blaze3d.pipeline.RenderPipeline;
|
||||
import com.mojang.blaze3d.vulkan.VulkanRenderPass;
|
||||
import org.lwjgl.vulkan.VK12;
|
||||
import org.lwjgl.vulkan.VkCommandBuffer;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Invoker;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
import dev.loki.lovisual.render.engine.rhi.backend.vulkan.util.VulkanRenderStateBridge;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
@Mixin(VulkanRenderPass.class)
|
||||
public abstract class VulkanRenderPassMixin {
|
||||
@Invoker("commandBuffer")
|
||||
protected abstract VkCommandBuffer lovisual$commandBuffer();
|
||||
|
||||
@Inject(method = "pushDebugGroup", at = @At("HEAD"), cancellable = true)
|
||||
private void lovisual$disableRenderPassDebugGroup(Supplier<String> label, CallbackInfo ci) {
|
||||
if (!VulkanRenderStateBridge.forceDisableDebugLabels()) return;
|
||||
ci.cancel();
|
||||
}
|
||||
|
||||
@Inject(method = "popDebugGroup", at = @At("HEAD"), cancellable = true)
|
||||
private void lovisual$disableRenderPassDebugGroupPop(CallbackInfo ci) {
|
||||
if (!VulkanRenderStateBridge.forceDisableDebugLabels()) return;
|
||||
ci.cancel();
|
||||
}
|
||||
|
||||
@Inject(method = "setPipeline", at = @At("TAIL"))
|
||||
private void lovisual$applyDynamicPipelineState(RenderPipeline pipeline, CallbackInfo ci) {
|
||||
if (!VulkanRenderStateBridge.vulkanBackendActive()) return;
|
||||
VkCommandBuffer commandBuffer = lovisual$commandBuffer();
|
||||
if (!VulkanRenderStateBridge.forceDisableStencil() && VulkanRenderStateBridge.currentRenderPassHasStencilAttachment()) {
|
||||
VK12.vkCmdSetStencilReference(
|
||||
commandBuffer,
|
||||
VK12.VK_STENCIL_FACE_FRONT_AND_BACK,
|
||||
VulkanRenderStateBridge.dynamicStencilReference()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,180 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.mixins.blaze3d.pipeline;
|
||||
|
||||
import com.llamalad7.mixinextras.sugar.Local;
|
||||
import com.mojang.blaze3d.pipeline.RenderPipeline;
|
||||
import com.mojang.blaze3d.vulkan.VulkanBindGroupLayout;
|
||||
import com.mojang.blaze3d.vulkan.VulkanConst;
|
||||
import com.mojang.blaze3d.vulkan.VulkanDevice;
|
||||
import com.mojang.blaze3d.vulkan.VulkanRenderPipeline;
|
||||
import org.lwjgl.system.MemoryStack;
|
||||
import org.lwjgl.vulkan.VkPipelineDepthStencilStateCreateInfo;
|
||||
import org.lwjgl.vulkan.VkPipelineRenderingCreateInfoKHR;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.ModifyArg;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
import dev.loki.lovisual.render.engine.rhi.backend.vulkan.util.VulkanRenderStateBridge;
|
||||
|
||||
import java.nio.IntBuffer;
|
||||
|
||||
import static com.mojang.blaze3d.GpuFormat.S8_UINT;
|
||||
import static org.lwjgl.vulkan.VK12.*;
|
||||
|
||||
@Mixin(VulkanRenderPipeline.class)
|
||||
public abstract class VulkanRenderPipelineMixin {
|
||||
@Inject(method = "compile", at = @At("HEAD"))
|
||||
private static void lovisual$beginCompile(VulkanDevice device,
|
||||
VulkanBindGroupLayout layout,
|
||||
RenderPipeline pipeline,
|
||||
long vertexModule,
|
||||
long fragmentModule,
|
||||
CallbackInfoReturnable<VulkanRenderPipeline> cir) {
|
||||
if (VulkanRenderStateBridge.vulkanBackendActive()) {
|
||||
VulkanRenderStateBridge.beginPipelineCompile(pipeline);
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "compile", at = @At("RETURN"))
|
||||
private static void lovisual$endCompile(VulkanDevice device,
|
||||
VulkanBindGroupLayout layout,
|
||||
RenderPipeline pipeline,
|
||||
long vertexModule,
|
||||
long fragmentModule,
|
||||
CallbackInfoReturnable<VulkanRenderPipeline> cir) {
|
||||
VulkanRenderStateBridge.endPipelineCompile();
|
||||
}
|
||||
|
||||
@Redirect(
|
||||
method = "compile",
|
||||
at = @At(value = "INVOKE", target = "Lorg/lwjgl/system/MemoryStack;ints(II)Ljava/nio/IntBuffer;")
|
||||
)
|
||||
private static IntBuffer lovisual$addDynamicStates(MemoryStack stack, int first, int second) {
|
||||
if (!VulkanRenderStateBridge.vulkanBackendActive()) {
|
||||
return stack.ints(first, second);
|
||||
}
|
||||
if (!VulkanRenderStateBridge.forceDisableStencil()) {
|
||||
return stack.ints(first, second, VK_DYNAMIC_STATE_STENCIL_REFERENCE);
|
||||
}
|
||||
return stack.ints(first, second);
|
||||
}
|
||||
|
||||
@ModifyArg(
|
||||
method = "compile",
|
||||
at = @At(value = "INVOKE", target = "Lorg/lwjgl/vulkan/VkPipelineRasterizationStateCreateInfo;cullMode(I)Lorg/lwjgl/vulkan/VkPipelineRasterizationStateCreateInfo;"),
|
||||
index = 0
|
||||
)
|
||||
private static int lovisual$disableCullForStencilMask(int original) {
|
||||
return VulkanRenderStateBridge.vulkanBackendActive() && VulkanRenderStateBridge.suppressColorWrites() ? VK_CULL_MODE_NONE : original;
|
||||
}
|
||||
|
||||
@ModifyArg(
|
||||
method = "compile",
|
||||
at = @At(value = "INVOKE", target = "Lorg/lwjgl/vulkan/VkPipelineDepthStencilStateCreateInfo;depthTestEnable(Z)Lorg/lwjgl/vulkan/VkPipelineDepthStencilStateCreateInfo;"),
|
||||
index = 0
|
||||
)
|
||||
private static boolean lovisual$disableDepthTestForStencilMask(boolean original) {
|
||||
return (!VulkanRenderStateBridge.vulkanBackendActive() || !VulkanRenderStateBridge.suppressColorWrites()) && original;
|
||||
}
|
||||
|
||||
@ModifyArg(
|
||||
method = "compile",
|
||||
at = @At(value = "INVOKE", target = "Lorg/lwjgl/vulkan/VkPipelineDepthStencilStateCreateInfo;depthWriteEnable(Z)Lorg/lwjgl/vulkan/VkPipelineDepthStencilStateCreateInfo;"),
|
||||
index = 0
|
||||
)
|
||||
private static boolean lovisual$disableDepthWriteForStencilMask(boolean original) {
|
||||
return (!VulkanRenderStateBridge.vulkanBackendActive() || !VulkanRenderStateBridge.suppressColorWrites()) && original;
|
||||
}
|
||||
|
||||
@ModifyArg(
|
||||
method = "compile",
|
||||
at = @At(value = "INVOKE", target = "Lorg/lwjgl/vulkan/VkPipelineMultisampleStateCreateInfo;rasterizationSamples(I)Lorg/lwjgl/vulkan/VkPipelineMultisampleStateCreateInfo;"),
|
||||
index = 0
|
||||
)
|
||||
private static int lovisual$useCurrentRenderPassSamples(int original) {
|
||||
return VulkanRenderStateBridge.vulkanBackendActive() ? VulkanRenderStateBridge.currentVkSampleCount() : original;
|
||||
}
|
||||
|
||||
@ModifyArg(
|
||||
method = "compile",
|
||||
at = @At(value = "INVOKE", target = "Lorg/lwjgl/vulkan/VkPipelineColorBlendAttachmentState$Buffer;colorWriteMask(I)Lorg/lwjgl/vulkan/VkPipelineColorBlendAttachmentState$Buffer;"),
|
||||
index = 0
|
||||
)
|
||||
private static int lovisual$suppressColorWritesForStencilMask(int original) {
|
||||
return VulkanRenderStateBridge.vulkanBackendActive() && VulkanRenderStateBridge.suppressColorWrites() ? 0 : original;
|
||||
}
|
||||
|
||||
@Inject(
|
||||
method = "compile",
|
||||
at = @At(value = "INVOKE", target = "Lorg/lwjgl/vulkan/VK12;vkCreateGraphicsPipelines(Lorg/lwjgl/vulkan/VkDevice;JLorg/lwjgl/vulkan/VkGraphicsPipelineCreateInfo$Buffer;Lorg/lwjgl/vulkan/VkAllocationCallbacks;Ljava/nio/LongBuffer;)I", ordinal = 0, shift = At.Shift.BEFORE)
|
||||
)
|
||||
private static void lovisual$configureStencilPipeline(VulkanDevice device,
|
||||
VulkanBindGroupLayout layout,
|
||||
RenderPipeline pipeline,
|
||||
long vertexModule,
|
||||
long fragmentModule,
|
||||
CallbackInfoReturnable<VulkanRenderPipeline> cir,
|
||||
@Local VkPipelineRenderingCreateInfoKHR rendering,
|
||||
@Local VkPipelineDepthStencilStateCreateInfo depthStencil) {
|
||||
if (!VulkanRenderStateBridge.vulkanBackendActive()) return;
|
||||
if (!VulkanRenderStateBridge.pipelineNeedsStencilFormat(pipeline)) {
|
||||
depthStencil.stencilTestEnable(false);
|
||||
rendering.stencilAttachmentFormat(VK_FORMAT_UNDEFINED);
|
||||
return;
|
||||
}
|
||||
rendering.stencilAttachmentFormat(VulkanConst.toVk(S8_UINT));
|
||||
configureStencilState(depthStencil, pipeline);
|
||||
}
|
||||
|
||||
@Unique
|
||||
private static void configureStencilState(VkPipelineDepthStencilStateCreateInfo state, RenderPipeline pipeline) {
|
||||
VulkanRenderStateBridge.StencilMode mode = VulkanRenderStateBridge.stencilTestEnabledForCurrentPipeline(pipeline)
|
||||
? VulkanRenderStateBridge.stencilMode()
|
||||
: VulkanRenderStateBridge.StencilMode.DISABLED;
|
||||
if (mode == VulkanRenderStateBridge.StencilMode.DISABLED) {
|
||||
state.stencilTestEnable(false);
|
||||
return;
|
||||
}
|
||||
|
||||
int passOp;
|
||||
int writeMask;
|
||||
switch (mode) {
|
||||
case WRITE -> {
|
||||
passOp = VK_STENCIL_OP_INCREMENT_AND_CLAMP;
|
||||
writeMask = 0xFF;
|
||||
}
|
||||
case RESTORE -> {
|
||||
passOp = VK_STENCIL_OP_DECREMENT_AND_CLAMP;
|
||||
writeMask = 0xFF;
|
||||
}
|
||||
case TEST -> {
|
||||
passOp = VK_STENCIL_OP_KEEP;
|
||||
writeMask = 0x00;
|
||||
}
|
||||
default -> {
|
||||
state.stencilTestEnable(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
state.stencilTestEnable(true);
|
||||
state.front()
|
||||
.failOp(VK_STENCIL_OP_KEEP)
|
||||
.passOp(passOp)
|
||||
.depthFailOp(VK_STENCIL_OP_KEEP)
|
||||
.compareOp(VK_COMPARE_OP_EQUAL)
|
||||
.compareMask(0xFF)
|
||||
.writeMask(writeMask)
|
||||
.reference(VulkanRenderStateBridge.dynamicStencilReference());
|
||||
state.back().set(state.front());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.mixins.blaze3d.shader;
|
||||
|
||||
import net.minecraft.client.renderer.DynamicUniformStorage;
|
||||
import org.slf4j.Logger;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
import dev.loki.lovisual.util.logging.DebugLog;
|
||||
|
||||
@Mixin(DynamicUniformStorage.class)
|
||||
public abstract class DynamicUniformStorageMixin {
|
||||
@Final
|
||||
@Shadow
|
||||
private String label;
|
||||
|
||||
@Redirect(
|
||||
method = {"writeUniform", "writeUniforms"},
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lorg/slf4j/Logger;info(Ljava/lang/String;[Ljava/lang/Object;)V"
|
||||
)
|
||||
)
|
||||
private void lovisual$redirectResizeLog(Logger logger, String format, Object[] args) {
|
||||
if (label != null && label.startsWith("LoVisual")) {
|
||||
String message = "Resizing " + args[0]
|
||||
+ ", capacity limit of " + args[1]
|
||||
+ " reached during a single frame. New capacity will be " + args[2] + ".";
|
||||
DebugLog.renderThread("%s", message);
|
||||
return;
|
||||
}
|
||||
logger.info(format, args);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.mixins.blaze3d.shader;
|
||||
|
||||
import com.mojang.blaze3d.opengl.GlStateManager;
|
||||
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.CallbackInfoReturnable;
|
||||
import dev.loki.lovisual.render.engine.profiler.frame.GlSyncTracker;
|
||||
|
||||
@Mixin(GlStateManager.class)
|
||||
public class GlStateManagerMixin {
|
||||
@Inject(method = "_glFenceSync", at = @At("RETURN"))
|
||||
private static void lovisual$onFenceSync(int condition, int flags, CallbackInfoReturnable<Long> cir) {
|
||||
GlSyncTracker.onFence(cir.getReturnValue());
|
||||
}
|
||||
|
||||
@Inject(method = "_glClientWaitSync", at = @At("HEAD"))
|
||||
private static void lovisual$onClientWaitSync(long sync, int flags, long timeout, CallbackInfoReturnable<Integer> cir) {
|
||||
GlSyncTracker.onWaitStart(sync);
|
||||
}
|
||||
|
||||
@Inject(method = "_glClientWaitSync", at = @At("RETURN"))
|
||||
private static void lovisual$onClientWaitSyncReturn(long sync, int flags, long timeout, CallbackInfoReturnable<Integer> cir) {
|
||||
GlSyncTracker.onWaitEnd();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,236 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.mixins.blaze3d.shader;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.llamalad7.mixinextras.injector.ModifyReturnValue;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
|
||||
import com.mojang.blaze3d.shaders.ShaderType;
|
||||
import net.minecraft.client.renderer.PostChainConfig;
|
||||
import net.minecraft.client.renderer.ShaderManager;
|
||||
import net.minecraft.resources.Identifier;
|
||||
import net.minecraft.server.packs.resources.Resource;
|
||||
import net.minecraft.server.packs.resources.ResourceManager;
|
||||
import net.minecraft.util.profiling.ProfilerFiller;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
import dev.loki.lovisual.mixins.accessors.blaze3d.ShaderLoaderDefinitionsAccessor;
|
||||
import dev.loki.lovisual.mixins.accessors.blaze3d.ShaderSourceKeyAccessor;
|
||||
import dev.loki.lovisual.render.engine.shader.LoVisualShaderSources;
|
||||
import dev.loki.lovisual.render.iris.compat.IrisCompatibilityGuards;
|
||||
import dev.loki.lovisual.util.logging.DebugLog;
|
||||
import dev.loki.lovisual.util.resources.RenderResourceReadiness;
|
||||
import dev.loki.lovisual.util.resources.ResourceReloadHooks;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@Mixin(ShaderManager.class)
|
||||
public abstract class ShaderLoaderMixin {
|
||||
@Unique
|
||||
private static final Identifier LOVISUAL_ENTITY_VERTEX = Identifier.fromNamespaceAndPath("lovisual", "shaders/core/entity_dither.vsh");
|
||||
@Unique
|
||||
private static final Identifier LOVISUAL_ENTITY_FRAGMENT = Identifier.fromNamespaceAndPath("lovisual", "shaders/core/entity_dither.fsh");
|
||||
@Unique
|
||||
private static final Identifier VANILLA_ENTITY_ID = Identifier.withDefaultNamespace("core/entity");
|
||||
@Unique
|
||||
private static boolean lovisual$loggedEntityOverride;
|
||||
@Unique
|
||||
private static boolean lovisual$loggedExtendedShaderSources;
|
||||
@Unique
|
||||
private static boolean lovisual$loggedShaderLoadError;
|
||||
@Unique
|
||||
private static Constructor<?> lovisual$shaderSourceKeyConstructor;
|
||||
|
||||
@Unique
|
||||
private static int lovisual$shaderLoadFailCount;
|
||||
|
||||
@WrapOperation(
|
||||
method = "prepare",
|
||||
at = @At(value = "INVOKE",
|
||||
target = "Lnet/minecraft/client/renderer/ShaderManager;loadShader(Lnet/minecraft/resources/Identifier;Lnet/minecraft/server/packs/resources/Resource;Lcom/mojang/blaze3d/shaders/ShaderType;Ljava/util/Map;Lcom/google/common/collect/ImmutableMap$Builder;)V")
|
||||
)
|
||||
private static void lovisual$wrapLoadShader(Identifier id, Resource resource, ShaderType type,
|
||||
Map<?, ?> allResources, ImmutableMap.Builder<?, ?> builder,
|
||||
Operation<Void> original) {
|
||||
try {
|
||||
original.call(id, resource, type, allResources, builder);
|
||||
} catch (Throwable t) {
|
||||
lovisual$shaderLoadFailCount++;
|
||||
if (!lovisual$loggedShaderLoadError) {
|
||||
lovisual$loggedShaderLoadError = true;
|
||||
DebugLog.error("[Shader] Failed to load shader", t);
|
||||
}
|
||||
DebugLog.warn("[Shader] Skipping shader {}: {}", id, t.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Unique
|
||||
private static boolean lovisual$addExtendedShaderSources(ResourceManager resourceManager, Map<Object, String> shaderSources) {
|
||||
Map<Identifier, Resource> resources = resourceManager.listResources("shaders", LoVisualShaderSources::isLoVisualExtendedSource);
|
||||
if (resources.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int added = 0;
|
||||
for (Map.Entry<Identifier, Resource> entry : resources.entrySet()) {
|
||||
Identifier resourceId = entry.getKey();
|
||||
ShaderType type = LoVisualShaderSources.typeByLocation(resourceId);
|
||||
if (type == null) continue;
|
||||
|
||||
String source = LoVisualShaderSources.load(resourceManager, resourceId, entry.getValue());
|
||||
shaderSources.put(lovisual$newShaderSourceKey(resourceId, type), source);
|
||||
added++;
|
||||
|
||||
Identifier canonicalId = LoVisualShaderSources.canonicalShaderId(resourceId);
|
||||
if (!canonicalId.equals(resourceId)) {
|
||||
shaderSources.put(lovisual$newShaderSourceKey(canonicalId, type), source);
|
||||
added++;
|
||||
}
|
||||
}
|
||||
|
||||
if (added > 0 && !lovisual$loggedExtendedShaderSources) {
|
||||
lovisual$loggedExtendedShaderSources = true;
|
||||
DebugLog.renderThread("[ShaderSource] registered LoVisual .vert/.frag sources in ShaderManager: " + added + " keys");
|
||||
}
|
||||
return added > 0;
|
||||
}
|
||||
|
||||
@Unique
|
||||
private static boolean lovisual$allowVanillaEntityShaderOverride() {
|
||||
if (IrisCompatibilityGuards.suppressLoVisualTerrainShaderOverrides()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Unique
|
||||
private static boolean lovisual$applyEntityOverride(ResourceManager resourceManager, Map<Object, String> shaderSources) {
|
||||
String entityFragmentSrc = lovisual$loadShaderSource(resourceManager, LOVISUAL_ENTITY_FRAGMENT);
|
||||
String entityVertexSrc = lovisual$loadShaderSource(resourceManager, LOVISUAL_ENTITY_VERTEX);
|
||||
if (((entityFragmentSrc == null || entityFragmentSrc.isEmpty()) || (entityVertexSrc == null || entityVertexSrc.isEmpty())) && !lovisual$loggedEntityOverride) {
|
||||
lovisual$loggedEntityOverride = true;
|
||||
DebugLog.warn("[ShaderOverride] entity fade override skipped: lovisual entity_dither shader missing");
|
||||
}
|
||||
|
||||
if ((entityFragmentSrc == null || entityFragmentSrc.isEmpty()) || (entityVertexSrc == null || entityVertexSrc.isEmpty())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean entityFragmentChanged = false;
|
||||
boolean entityVertexChanged = false;
|
||||
|
||||
for (Map.Entry<Object, String> entry : shaderSources.entrySet()) {
|
||||
Object key = entry.getKey();
|
||||
if (entityVertexSrc != null && !entityVertexSrc.isEmpty() && lovisual$isEntityVertexKey(key)) {
|
||||
entry.setValue(entityVertexSrc);
|
||||
entityVertexChanged = true;
|
||||
continue;
|
||||
}
|
||||
if (entityFragmentSrc != null && !entityFragmentSrc.isEmpty() && lovisual$isEntityFragmentKey(key)) {
|
||||
entry.setValue(entityFragmentSrc);
|
||||
entityFragmentChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ((!entityFragmentChanged || !entityVertexChanged) && !lovisual$loggedEntityOverride) {
|
||||
lovisual$loggedEntityOverride = true;
|
||||
DebugLog.warn("[ShaderOverride] entity fade override skipped: entity shader key not found");
|
||||
}
|
||||
|
||||
if (entityFragmentChanged && entityVertexChanged && !lovisual$loggedEntityOverride) {
|
||||
lovisual$loggedEntityOverride = true;
|
||||
DebugLog.renderThread("[ShaderOverride] override applied: core/entity -> lovisual entity_dither");
|
||||
}
|
||||
|
||||
return entityFragmentChanged || entityVertexChanged;
|
||||
}
|
||||
|
||||
@Unique
|
||||
private static boolean lovisual$isEntityFragmentKey(Object key) {
|
||||
if (!(key instanceof ShaderSourceKeyAccessor accessor)) return false;
|
||||
return VANILLA_ENTITY_ID.equals(accessor.lovisual$getId()) && accessor.lovisual$getType() == ShaderType.FRAGMENT;
|
||||
}
|
||||
|
||||
@Unique
|
||||
private static boolean lovisual$isEntityVertexKey(Object key) {
|
||||
if (!(key instanceof ShaderSourceKeyAccessor accessor)) return false;
|
||||
return VANILLA_ENTITY_ID.equals(accessor.lovisual$getId()) && accessor.lovisual$getType() == ShaderType.VERTEX;
|
||||
}
|
||||
|
||||
@Unique
|
||||
private static String lovisual$loadShaderSource(ResourceManager resourceManager, Identifier id) {
|
||||
try {
|
||||
return LoVisualShaderSources.load(resourceManager, id);
|
||||
} catch (Exception e) {
|
||||
DebugLog.error("[ShaderOverride] read shader override failed: " + id, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Unique
|
||||
private static Object lovisual$newShaderSourceKey(Identifier id, ShaderType type) {
|
||||
try {
|
||||
if (lovisual$shaderSourceKeyConstructor == null) {
|
||||
Class<?> keyClass = Class.forName("net.minecraft.client.renderer.ShaderManager$ShaderSourceKey");
|
||||
Constructor<?> constructor = keyClass.getDeclaredConstructor(Identifier.class, ShaderType.class);
|
||||
constructor.setAccessible(true);
|
||||
lovisual$shaderSourceKeyConstructor = constructor;
|
||||
}
|
||||
return lovisual$shaderSourceKeyConstructor.newInstance(id, type);
|
||||
} catch (ReflectiveOperationException e) {
|
||||
throw new IllegalStateException("Failed to create ShaderManager shader source key for " + type + " " + id, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "apply", at = @At("HEAD"))
|
||||
private void lovisual$beforeShaderReload(ShaderManager.Configs definitions,
|
||||
ResourceManager resourceManager,
|
||||
ProfilerFiller profiler,
|
||||
CallbackInfo ci) {
|
||||
RenderResourceReadiness.markReloading("shader reload");
|
||||
}
|
||||
|
||||
@Inject(method = "apply", at = @At("TAIL"))
|
||||
private void lovisual$afterShaderReload(ShaderManager.Configs definitions,
|
||||
ResourceManager resourceManager,
|
||||
ProfilerFiller profiler,
|
||||
CallbackInfo ci) {
|
||||
ResourceReloadHooks.onReload(resourceManager);
|
||||
}
|
||||
|
||||
@ModifyReturnValue(method = "prepare", at = @At("RETURN"))
|
||||
private ShaderManager.Configs lovisual$extendShaderSources(ShaderManager.Configs definitions,
|
||||
ResourceManager resourceManager,
|
||||
ProfilerFiller profiler) {
|
||||
ShaderLoaderDefinitionsAccessor accessor = (ShaderLoaderDefinitionsAccessor) (Object) definitions;
|
||||
Map<?, String> shaderSources = accessor.lovisual$getShaderSources();
|
||||
Map<Object, String> replaced = new LinkedHashMap<>(shaderSources.size() + 64);
|
||||
replaced.putAll(shaderSources);
|
||||
|
||||
boolean changed = lovisual$addExtendedShaderSources(resourceManager, replaced);
|
||||
if (lovisual$allowVanillaEntityShaderOverride()) {
|
||||
changed |= lovisual$applyEntityOverride(resourceManager, replaced);
|
||||
}
|
||||
|
||||
if (!changed) {
|
||||
return definitions;
|
||||
}
|
||||
|
||||
Map<Identifier, PostChainConfig> postChains = accessor.lovisual$getPostChains();
|
||||
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||
ShaderManager.Configs updated = new ShaderManager.Configs((Map) replaced, postChains);
|
||||
return updated;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.mixins.blaze3d.texture;
|
||||
|
||||
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;
|
||||
import dev.loki.lovisual.render.engine.core.system.LoVisualRenderSystem;
|
||||
|
||||
@Mixin(targets = "com.mojang.blaze3d.opengl.DirectStateAccess$Emulated")
|
||||
public abstract class BufferManagerDefaultBufferManagerMixin {
|
||||
@Inject(method = "bindFrameBufferTextures", at = @At("HEAD"), cancellable = true)
|
||||
private void lovisual$setupFramebuffer(int framebuffer,
|
||||
int[] colorAttachments,
|
||||
int[] colorMipLevels,
|
||||
int depthAttachment,
|
||||
int depthMipLevel,
|
||||
int bindTarget,
|
||||
CallbackInfo ci) {
|
||||
if (colorAttachments == null || colorAttachments.length != 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
int colorAttachment = colorAttachments[0];
|
||||
int mipLevel = colorMipLevels != null && colorMipLevels.length > 0 ? colorMipLevels[0] : depthMipLevel;
|
||||
if (LoVisualRenderSystem.rhi().msaa().setupFramebuffer(framebuffer, colorAttachment, depthAttachment, mipLevel, bindTarget)) {
|
||||
ci.cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.mixins.blaze3d.texture;
|
||||
|
||||
import com.mojang.blaze3d.opengl.GlTexture;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
import dev.loki.lovisual.mixininterface.device.IMsaaTexture;
|
||||
import dev.loki.lovisual.render.engine.core.system.LoVisualRenderSystem;
|
||||
|
||||
@Mixin(GlTexture.class)
|
||||
public abstract class GlTextureMixin implements IMsaaTexture {
|
||||
@Final
|
||||
@Shadow
|
||||
protected int id;
|
||||
|
||||
@Unique
|
||||
private int lovisual$samples = 1;
|
||||
|
||||
@Override
|
||||
public void lovisual$setSamples(int samples) {
|
||||
lovisual$samples = samples;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int lovisual$getSamples() {
|
||||
return lovisual$samples;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean lovisual$isMsaa() {
|
||||
return lovisual$samples > 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int lovisual$getGlId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Inject(method = "destroyImmediately", at = @At("HEAD"))
|
||||
private void lovisual$onFree(CallbackInfo ci) {
|
||||
LoVisualRenderSystem.rhi().msaa().unregisterTexture(id);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,114 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.mixins.blaze3d.texture;
|
||||
|
||||
import com.mojang.blaze3d.GpuFormat;
|
||||
import com.mojang.blaze3d.vulkan.VulkanDevice;
|
||||
import com.mojang.blaze3d.vulkan.VulkanGpuTexture;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.ModifyArg;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
import dev.loki.lovisual.mixininterface.device.IMsaaTexture;
|
||||
import dev.loki.lovisual.render.engine.rhi.backend.vulkan.util.VulkanRenderStateBridge;
|
||||
import org.lwjgl.vulkan.VkImageSubresourceRange;
|
||||
|
||||
import static org.lwjgl.vulkan.VK12.*;
|
||||
|
||||
@Mixin(VulkanGpuTexture.class)
|
||||
public abstract class VulkanGpuTextureMixin implements IMsaaTexture {
|
||||
@Unique
|
||||
private int lovisual$samples = 1;
|
||||
|
||||
/**
|
||||
* Mojang 26.2 maps render-attachment usage only for color/depth aspects. Pure S8_UINT
|
||||
* is a valid Vulkan stencil attachment, but without this OR the image is created without
|
||||
* VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT and using it in pStencilAttachment is invalid.
|
||||
*/
|
||||
@Redirect(
|
||||
method = "<init>",
|
||||
at = @At(value = "INVOKE", target = "Lcom/mojang/blaze3d/vulkan/VulkanConst;textureUsageToVk(ILcom/mojang/blaze3d/GpuFormat;)I")
|
||||
)
|
||||
private int lovisual$stencilAttachmentUsage(int usage, GpuFormat format) {
|
||||
int vkUsage = com.mojang.blaze3d.vulkan.VulkanConst.textureUsageToVk(usage, format);
|
||||
if (format != null && format.hasStencilAspect() && (usage & com.mojang.blaze3d.textures.GpuTexture.USAGE_RENDER_ATTACHMENT) != 0) {
|
||||
vkUsage |= VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
|
||||
}
|
||||
return vkUsage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mojang picks DEPTH_BIT for every non-color format. For S8_UINT this records an invalid
|
||||
* initialization barrier and later the image view is invalid too. Keep the patch scoped to
|
||||
* actual stencil formats; color/depth formats stay vanilla.
|
||||
*/
|
||||
@Redirect(
|
||||
method = "<init>",
|
||||
at = @At(value = "INVOKE", target = "Lorg/lwjgl/vulkan/VkImageSubresourceRange;aspectMask(I)Lorg/lwjgl/vulkan/VkImageSubresourceRange;")
|
||||
)
|
||||
private VkImageSubresourceRange lovisual$stencilImageAspect(VkImageSubresourceRange range,
|
||||
int original,
|
||||
VulkanDevice device,
|
||||
int usage,
|
||||
String label,
|
||||
GpuFormat format,
|
||||
int width,
|
||||
int height,
|
||||
int depthOrLayers,
|
||||
int mipLevels) {
|
||||
if (format != null && format.hasStencilAspect() && !format.hasDepthAspect()) {
|
||||
return range.aspectMask(VK_IMAGE_ASPECT_STENCIL_BIT);
|
||||
}
|
||||
return range.aspectMask(original);
|
||||
}
|
||||
|
||||
@ModifyArg(
|
||||
method = "<init>",
|
||||
at = @At(value = "INVOKE", target = "Lorg/lwjgl/vulkan/VkImageCreateInfo;samples(I)Lorg/lwjgl/vulkan/VkImageCreateInfo;"),
|
||||
index = 0
|
||||
)
|
||||
private int lovisual$useRequestedMsaaSamples(int original) {
|
||||
return VulkanRenderStateBridge.requestedTextureSamples();
|
||||
}
|
||||
|
||||
@Inject(method = "<init>", at = @At("RETURN"))
|
||||
private void lovisual$rememberSamples(VulkanDevice device,
|
||||
int usage,
|
||||
String label,
|
||||
GpuFormat format,
|
||||
int width,
|
||||
int height,
|
||||
int depthOrLayers,
|
||||
int mipLevels,
|
||||
CallbackInfo ci) {
|
||||
lovisual$samples = VulkanRenderStateBridge.requestedTextureSamples();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void lovisual$setSamples(int samples) {
|
||||
lovisual$samples = VulkanRenderStateBridge.normalizeSamples(samples);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int lovisual$getSamples() {
|
||||
return lovisual$samples;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean lovisual$isMsaa() {
|
||||
return lovisual$samples > 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int lovisual$getGlId() {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.mixins.blaze3d.texture;
|
||||
|
||||
import com.mojang.blaze3d.GpuFormat;
|
||||
import com.mojang.blaze3d.vulkan.VulkanDevice;
|
||||
import com.mojang.blaze3d.vulkan.VulkanGpuTexture;
|
||||
import com.mojang.blaze3d.vulkan.VulkanGpuTextureView;
|
||||
import org.lwjgl.vulkan.VkImageSubresourceRange;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
|
||||
import static org.lwjgl.vulkan.VK12.VK_IMAGE_ASPECT_STENCIL_BIT;
|
||||
|
||||
@Mixin(VulkanGpuTextureView.class)
|
||||
public abstract class VulkanGpuTextureViewMixin {
|
||||
/**
|
||||
* Mojang 26.2 treats every non-color view as depth. A pure S8_UINT attachment must use
|
||||
* STENCIL_BIT or vkCreateImageView is invalid and dynamic rendering cannot bind it as
|
||||
* pStencilAttachment.
|
||||
*/
|
||||
@Redirect(
|
||||
method = "<init>",
|
||||
at = @At(value = "INVOKE", target = "Lorg/lwjgl/vulkan/VkImageSubresourceRange;aspectMask(I)Lorg/lwjgl/vulkan/VkImageSubresourceRange;")
|
||||
)
|
||||
private VkImageSubresourceRange lovisual$stencilViewAspect(VkImageSubresourceRange range,
|
||||
int original,
|
||||
VulkanDevice device,
|
||||
VulkanGpuTexture texture,
|
||||
int baseMipLevel,
|
||||
int mipLevels) {
|
||||
GpuFormat format = texture == null ? null : texture.getFormat();
|
||||
if (format != null && format.hasStencilAspect() && !format.hasDepthAspect()) {
|
||||
return range.aspectMask(VK_IMAGE_ASPECT_STENCIL_BIT);
|
||||
}
|
||||
return range.aspectMask(original);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.mixins.entity.inventory;
|
||||
|
||||
import net.minecraft.resources.Identifier;
|
||||
import net.minecraft.world.item.ItemCooldowns;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
import dev.loki.lovisual.features.module.lifecycle.Modules;
|
||||
import dev.loki.lovisual.features.module.modules.combat.pvp.TPSSync;
|
||||
import dev.loki.lovisual.mixins.accessors.inventory.ItemCooldownEntryAccessor;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
|
||||
@Mixin(ItemCooldowns.class)
|
||||
public abstract class ItemCooldownsMixin {
|
||||
|
||||
@Shadow
|
||||
private int tickCount;
|
||||
@Shadow
|
||||
@Final
|
||||
private Map<Identifier, ?> cooldowns;
|
||||
@Unique
|
||||
private float lovisual$timerRemainder = 0f;
|
||||
|
||||
@Shadow
|
||||
protected abstract void onCooldownEnded(Identifier groupId);
|
||||
|
||||
@Inject(method = "tick", at = @At("HEAD"), cancellable = true)
|
||||
private void lovisual$timerCompensate(CallbackInfo ci) {
|
||||
// Timer module removed; scale only by server TPS when active.
|
||||
float serverDelta = 1.0f;
|
||||
TPSSync tps = Modules.get(TPSSync.class);
|
||||
if (tps != null && tps.isEnabled()) {
|
||||
serverDelta = tps.getServerTickDelta();
|
||||
}
|
||||
|
||||
if (serverDelta > 1.0f) serverDelta = 1.0f;
|
||||
if (serverDelta < 0.0f) serverDelta = 0.0f;
|
||||
|
||||
if (Math.abs(serverDelta - 1.0f) < 0.0001f) {
|
||||
return;
|
||||
}
|
||||
|
||||
lovisual$timerRemainder += serverDelta;
|
||||
int inc = (int) lovisual$timerRemainder;
|
||||
if (inc <= 0) {
|
||||
ci.cancel();
|
||||
return;
|
||||
}
|
||||
lovisual$timerRemainder -= inc;
|
||||
|
||||
tickCount += inc;
|
||||
if (!cooldowns.isEmpty()) {
|
||||
@SuppressWarnings("unchecked")
|
||||
Iterator<Map.Entry<Identifier, Object>> iterator =
|
||||
((Map<Identifier, Object>) cooldowns).entrySet().iterator();
|
||||
while (iterator.hasNext()) {
|
||||
Map.Entry<Identifier, Object> entry = iterator.next();
|
||||
Object value = entry.getValue();
|
||||
if (value instanceof ItemCooldownEntryAccessor accessor && accessor.getEndTick() <= tickCount) {
|
||||
iterator.remove();
|
||||
onCooldownEnded(entry.getKey());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ci.cancel();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.mixins.entity.inventory;
|
||||
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.item.Item;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
import dev.loki.lovisual.util.aiming.rotation.RotationManager;
|
||||
|
||||
@Mixin(Item.class)
|
||||
public class ItemMixin {
|
||||
|
||||
@Redirect(
|
||||
method = "getPlayerPOVHitResult",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/world/entity/player/Player;calculateViewVector(FF)Lnet/minecraft/world/phys/Vec3;"
|
||||
)
|
||||
)
|
||||
private static Vec3 lovisual$raycastRotation(Player player, float pitch, float yaw) {
|
||||
return RotationManager.INSTANCE.getMovementRotation().directionVector();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,104 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.mixins.entity.inventory;
|
||||
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.item.Item;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.TooltipFlag;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
import dev.loki.lovisual.features.gui.hud.nondraggable.impl.tooltips.BetterTooltips;
|
||||
import dev.loki.lovisual.util.item.food.IllegalItemUtil;
|
||||
import dev.loki.lovisual.util.item.rarity.RarityColorUtil;
|
||||
import dev.loki.lovisual.util.item.enchant.TopEnchantUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
@Mixin(ItemStack.class)
|
||||
public class ItemStackMixin {
|
||||
|
||||
@Unique
|
||||
private static Component recolor(Component text, int argb) {
|
||||
int rgb = argb & 0x00FFFFFF;
|
||||
return text.copy().setStyle(text.getStyle().withColor(rgb));
|
||||
}
|
||||
|
||||
@Unique
|
||||
private static int argbFrom(float[] rgba) {
|
||||
if (rgba == null || rgba.length < 3) return 0xFFFFFFFF;
|
||||
float r = rgba[0], g = rgba[1], b = rgba[2];
|
||||
float a = rgba.length > 3 ? rgba[3] : 1f;
|
||||
int ai = (int) (a * 255) & 0xFF;
|
||||
int ri = (int) (r * 255) & 0xFF;
|
||||
int gi = (int) (g * 255) & 0xFF;
|
||||
int bi = (int) (b * 255) & 0xFF;
|
||||
return (ai << 24) | (ri << 16) | (gi << 8) | bi;
|
||||
}
|
||||
|
||||
@Inject(method = "getTooltipLines", at = @At("RETURN"), cancellable = true)
|
||||
private void lovisual$colorizeTooltip(Item.TooltipContext tooltipContext, @Nullable Player player, TooltipFlag type, CallbackInfoReturnable<List<Component>> cir) {
|
||||
BetterTooltips tooltips = BetterTooltips.get();
|
||||
if (tooltips == null || !tooltips.isItemInfoColorizeEnabled()) return;
|
||||
|
||||
ItemStack self = (ItemStack) (Object) this;
|
||||
BetterTooltips.setLastTooltipStack(self);
|
||||
List<Component> original = cir.getReturnValue();
|
||||
if (original == null || original.isEmpty()) return;
|
||||
|
||||
List<Component> modified = new ArrayList<>(original);
|
||||
|
||||
int illegalColor = IllegalItemUtil.illegalColor();
|
||||
int topColor = TopEnchantUtil.topColor();
|
||||
int rarityColor = argbFrom(RarityColorUtil.INSTANCE.getRarityColor(self));
|
||||
|
||||
Set<String> illegalLabels = new HashSet<>();
|
||||
Set<String> topLabels = new HashSet<>();
|
||||
IllegalItemUtil.collectEnchants(self, (label, overMax) -> {
|
||||
if (overMax) illegalLabels.add(label.trim());
|
||||
});
|
||||
TopEnchantUtil.collectTopEnchants(self, (label, isTop) -> {
|
||||
if (isTop) topLabels.add(label.trim());
|
||||
});
|
||||
|
||||
boolean hasIllegal = !illegalLabels.isEmpty();
|
||||
boolean hasTop = !topLabels.isEmpty();
|
||||
|
||||
if (!modified.isEmpty()) {
|
||||
int nameColor = hasIllegal ? illegalColor : (hasTop ? topColor : rarityColor);
|
||||
modified.set(0, recolor(modified.get(0), nameColor));
|
||||
}
|
||||
|
||||
for (int i = 1; i < modified.size(); i++) {
|
||||
Component t = modified.get(i);
|
||||
String trimmed = t.getString().trim();
|
||||
if (illegalLabels.contains(trimmed)) {
|
||||
modified.set(i, recolor(t, illegalColor));
|
||||
continue;
|
||||
}
|
||||
if (topLabels.contains(trimmed)) {
|
||||
modified.set(i, recolor(t, topColor));
|
||||
continue;
|
||||
}
|
||||
if (hasIllegal && t.getStyle().getColor() == null) {
|
||||
modified.set(i, recolor(t, illegalColor));
|
||||
}
|
||||
}
|
||||
|
||||
cir.setReturnValue(modified);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.mixins.entity.inventory;
|
||||
|
||||
import net.minecraft.world.entity.player.Inventory;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
|
||||
@Mixin(Inventory.class)
|
||||
public abstract class PlayerInventoryMixin {
|
||||
|
||||
@Shadow
|
||||
@Final
|
||||
public Player player;
|
||||
|
||||
@Shadow
|
||||
public abstract ItemStack getItem(int slot);
|
||||
|
||||
// Hotbar lease removed (was InventorySwap) — keep original behavior
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,386 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.mixins.entity.living;
|
||||
|
||||
import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.player.LocalPlayer;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.util.Mth;
|
||||
import net.minecraft.world.damagesource.DamageSource;
|
||||
import net.minecraft.world.effect.MobEffect;
|
||||
import net.minecraft.world.effect.MobEffectInstance;
|
||||
import net.minecraft.world.effect.MobEffects;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.entity.ai.attributes.Attribute;
|
||||
import net.minecraft.world.entity.ai.attributes.Attributes;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.item.Item;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.phys.AABB;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
import dev.loki.lovisual.config.values.collection.ItemCooldownRulesValue;
|
||||
import dev.loki.lovisual.events.Events;
|
||||
import dev.loki.lovisual.events.impl.player.PlayerJumpEvent;
|
||||
import dev.loki.lovisual.features.module.lifecycle.Modules;
|
||||
import dev.loki.lovisual.features.module.modules.combat.pvp.PvpCooldowns;
|
||||
import dev.loki.lovisual.features.module.modules.combat.pvp.TPSSync;
|
||||
import dev.loki.lovisual.features.module.modules.visuals.visibility.NoRender;
|
||||
import dev.loki.lovisual.features.module.modules.visuals.weather.WorldTweaks;
|
||||
import dev.loki.lovisual.util.aiming.rotation.RotationManager;
|
||||
import dev.loki.lovisual.util.aiming.data.Rotation;
|
||||
import dev.loki.lovisual.util.aiming.features.MovementCorrection;
|
||||
import dev.loki.lovisual.util.pvp.client.CooldownsState;
|
||||
import dev.loki.lovisual.util.pvp.opponents.TotemPopCounter;
|
||||
|
||||
@Mixin(LivingEntity.class)
|
||||
public abstract class LivingEntityMixin {
|
||||
|
||||
@Unique
|
||||
private float lovisual$itemUseRemainder = 0f;
|
||||
|
||||
@Unique
|
||||
private float lovisual$effectTickRemainder = 0f;
|
||||
|
||||
@Shadow
|
||||
protected int useItemRemaining;
|
||||
@Unique
|
||||
private PlayerJumpEvent lovisual$playerJumpEvent;
|
||||
|
||||
@Shadow
|
||||
protected abstract float getJumpPower();
|
||||
|
||||
@Inject(method = "getAttributeValue", at = @At("RETURN"))
|
||||
private void lovisual$noStunSlownessMovementSpeed(Holder<Attribute> attribute, CallbackInfoReturnable<Double> cir) {
|
||||
if (!attribute.equals(Attributes.MOVEMENT_SPEED)) return;
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional local cooldown synthesis for servers that lock consumables without sending vanilla cooldown packets.
|
||||
* This never cancels item use; it only starts a visible local timer when explicitly configured.
|
||||
*/
|
||||
@Inject(method = "updatingUsingItem", at = @At("HEAD"))
|
||||
private void lovisual$onFinishEating(CallbackInfo ci) {
|
||||
LivingEntity self = (LivingEntity) (Object) this;
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (self != mc.player) return;
|
||||
|
||||
ItemStack stack = self.getUseItem();
|
||||
if (stack == null || stack.isEmpty()) return;
|
||||
if (self.getUseItemRemainingTicks() != 1) return;
|
||||
|
||||
PvpCooldowns cooldowns = Modules.get(PvpCooldowns.class);
|
||||
if (cooldowns == null) return;
|
||||
|
||||
Item item = stack.getItem();
|
||||
cooldowns.tryStartLocalCooldown(item, ItemCooldownRulesValue.Trigger.CONSUME_FINISH);
|
||||
}
|
||||
|
||||
@Inject(method = "updatingUsingItem", at = @At("HEAD"), cancellable = true)
|
||||
private void lovisual$fixItemUseSpeed(CallbackInfo ci) {
|
||||
LivingEntity self = (LivingEntity) (Object) this;
|
||||
if (!(self instanceof Player player)) {
|
||||
lovisual$itemUseRemainder = 0f;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!player.level().isClientSide()) return;
|
||||
if (!player.isUsingItem()) {
|
||||
lovisual$itemUseRemainder = 0f;
|
||||
return;
|
||||
}
|
||||
|
||||
float scale = lovisual$clientTickScale();
|
||||
if (scale >= 0.999f) {
|
||||
lovisual$itemUseRemainder = 0f;
|
||||
return;
|
||||
}
|
||||
|
||||
lovisual$itemUseRemainder += scale;
|
||||
if (lovisual$itemUseRemainder < 1.0f) {
|
||||
ci.cancel();
|
||||
return;
|
||||
}
|
||||
|
||||
lovisual$itemUseRemainder -= 1.0f;
|
||||
}
|
||||
|
||||
@Redirect(
|
||||
method = "tickEffects",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/world/effect/MobEffectInstance;tickClient()V"
|
||||
)
|
||||
)
|
||||
private void lovisual$scaleClientEffects(MobEffectInstance instance) {
|
||||
LivingEntity self = (LivingEntity) (Object) this;
|
||||
if (!self.level().isClientSide()) {
|
||||
instance.tickClient();
|
||||
return;
|
||||
}
|
||||
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (!(self instanceof Player player) || mc == null || player != mc.player) {
|
||||
instance.tickClient();
|
||||
return;
|
||||
}
|
||||
|
||||
float scale = lovisual$clientTickScale();
|
||||
if (scale >= 0.999f) {
|
||||
lovisual$effectTickRemainder = 0f;
|
||||
instance.tickClient();
|
||||
return;
|
||||
}
|
||||
|
||||
lovisual$effectTickRemainder += scale;
|
||||
if (lovisual$effectTickRemainder < 1.0f) {
|
||||
return;
|
||||
}
|
||||
lovisual$effectTickRemainder -= 1.0f;
|
||||
instance.tickClient();
|
||||
}
|
||||
|
||||
@Unique
|
||||
private float lovisual$clientTickScale() {
|
||||
// Timer module removed; scale only by server TPS when active.
|
||||
float serverDelta = 1.0f;
|
||||
TPSSync tps = Modules.get(TPSSync.class);
|
||||
if (tps != null && tps.isEnabled()) {
|
||||
serverDelta = tps.getServerTickDelta();
|
||||
}
|
||||
|
||||
if (serverDelta > 1.0f) return 1.0f;
|
||||
return Math.max(serverDelta, 0.0f);
|
||||
}
|
||||
|
||||
/**
|
||||
* Local totem cooldowns are handled from the confirmed entity-status packet when explicitly configured.
|
||||
*/
|
||||
@Inject(method = "checkTotemDeathProtection", at = @At("RETURN"))
|
||||
private void lovisual$onTotemUse(DamageSource source, CallbackInfoReturnable<Boolean> cir) {
|
||||
// no local use blocking or prediction here
|
||||
}
|
||||
|
||||
/**
|
||||
* Сброс кулдаунов при смерти.
|
||||
*/
|
||||
@Inject(method = "die", at = @At("HEAD"))
|
||||
private void lovisual$onDeath(DamageSource source, CallbackInfo ci) {
|
||||
LivingEntity self = (LivingEntity) (Object) this;
|
||||
if (self instanceof Player player) {
|
||||
TotemPopCounter.onPlayerDeath(player.getUUID());
|
||||
}
|
||||
|
||||
PvpCooldowns cooldowns = Modules.get(PvpCooldowns.class);
|
||||
if (cooldowns == null || cooldowns.isSystemEnabled()) {
|
||||
CooldownsState.MANAGER.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(
|
||||
method = "getBlockSpeedFactor",
|
||||
at = @At("HEAD"),
|
||||
cancellable = true
|
||||
)
|
||||
private void weathercontrol$dontDampRiptide(CallbackInfoReturnable<Float> cir) {
|
||||
LivingEntity self = (LivingEntity) (Object) this;
|
||||
|
||||
if (!(self instanceof Player player)) return;
|
||||
if (!player.isAutoSpinAttack()) return;
|
||||
|
||||
Level world = player.level();
|
||||
if (world == null) return;
|
||||
|
||||
if (WorldTweaks.isServerRaining(world)) {
|
||||
cir.setReturnValue(1.0F);
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "aiStep", at = @At("HEAD"))
|
||||
private void preventCrash(CallbackInfo ci) {
|
||||
// ElytraFly module removed; no crash-prevention override remains.
|
||||
}
|
||||
|
||||
@ModifyExpressionValue(
|
||||
method = "updateFallFlyingMovement",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/world/entity/LivingEntity;getLookAngle()Lnet/minecraft/world/phys/Vec3;"
|
||||
)
|
||||
)
|
||||
private Vec3 lovisual$modifyGlidingRotationVector(Vec3 original) {
|
||||
LivingEntity self = (LivingEntity) (Object) this;
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (!(self instanceof LocalPlayer player) || mc == null || player != mc.player) {
|
||||
return original;
|
||||
}
|
||||
|
||||
Rotation rotation = RotationManager.INSTANCE.getMovementRotation();
|
||||
var rotationTarget = RotationManager.INSTANCE.getActiveRotationTarget();
|
||||
if (rotationTarget == null
|
||||
|| rotationTarget.movementCorrection == MovementCorrection.OFF
|
||||
|| rotationTarget.movementCorrection == MovementCorrection.STRICT) {
|
||||
return original;
|
||||
}
|
||||
|
||||
return rotation.directionVector();
|
||||
}
|
||||
|
||||
@ModifyExpressionValue(
|
||||
method = "updateFallFlyingMovement",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/world/entity/LivingEntity;getXRot()F"
|
||||
)
|
||||
)
|
||||
private float lovisual$modifyGlidingPitch(float original) {
|
||||
LivingEntity self = (LivingEntity) (Object) this;
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (!(self instanceof LocalPlayer player) || mc == null || player != mc.player) {
|
||||
return original;
|
||||
}
|
||||
|
||||
Rotation rotation = RotationManager.INSTANCE.getMovementRotation();
|
||||
var rotationTarget = RotationManager.INSTANCE.getActiveRotationTarget();
|
||||
if (rotationTarget == null || rotationTarget.movementCorrection == MovementCorrection.OFF) {
|
||||
return original;
|
||||
}
|
||||
|
||||
return rotation.pitch();
|
||||
}
|
||||
|
||||
@Inject(method = "hasEffect", at = @At("HEAD"), cancellable = true)
|
||||
private void killHasEffect(Holder<MobEffect> effect,
|
||||
CallbackInfoReturnable<Boolean> cir) {
|
||||
NoRender noRender = Modules.get(NoRender.class);
|
||||
if (noRender == null || !noRender.off("bad_effects")) return;
|
||||
|
||||
if (isBad(effect)) {
|
||||
cir.setReturnValue(false);
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "getEffect", at = @At("HEAD"), cancellable = true)
|
||||
private void killGetEffect(Holder<MobEffect> effect,
|
||||
CallbackInfoReturnable<MobEffectInstance> cir) {
|
||||
NoRender noRender = Modules.get(NoRender.class);
|
||||
if (noRender == null || !noRender.off("bad_effects")) return;
|
||||
|
||||
if (isBad(effect)) {
|
||||
cir.setReturnValue(null);
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "jumpFromGround", at = @At("HEAD"), cancellable = true)
|
||||
private void lovisual$onJumpHead(CallbackInfo ci) {
|
||||
LivingEntity self = (LivingEntity) (Object) this;
|
||||
if (!(self instanceof LocalPlayer player)) {
|
||||
lovisual$playerJumpEvent = null;
|
||||
return;
|
||||
}
|
||||
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc == null || player != mc.player) {
|
||||
lovisual$playerJumpEvent = null;
|
||||
return;
|
||||
}
|
||||
|
||||
PlayerJumpEvent event = new PlayerJumpEvent(self.getYRot(), getJumpPower());
|
||||
Events.BUS.post(event);
|
||||
if (event.isCancelled()) {
|
||||
lovisual$playerJumpEvent = null;
|
||||
ci.cancel();
|
||||
return;
|
||||
}
|
||||
|
||||
lovisual$playerJumpEvent = event;
|
||||
}
|
||||
|
||||
@ModifyExpressionValue(
|
||||
method = "jumpFromGround",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/world/entity/LivingEntity;getJumpPower()F"
|
||||
)
|
||||
)
|
||||
private float lovisual$modifyJumpMotion(float original) {
|
||||
return lovisual$playerJumpEvent != null ? lovisual$playerJumpEvent.getMotion() : original;
|
||||
}
|
||||
|
||||
@ModifyExpressionValue(
|
||||
method = "jumpFromGround",
|
||||
at = @At(
|
||||
value = "NEW",
|
||||
target = "(DDD)Lnet/minecraft/world/phys/Vec3;"
|
||||
)
|
||||
)
|
||||
private Vec3 lovisual$fixJumpRotation(Vec3 original) {
|
||||
LivingEntity self = (LivingEntity) (Object) this;
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (!(self instanceof LocalPlayer player) || mc == null || player != mc.player) {
|
||||
return original;
|
||||
}
|
||||
|
||||
Rotation rotation = RotationManager.INSTANCE.getMovementRotation();
|
||||
var rotationTarget = RotationManager.INSTANCE.getActiveRotationTarget();
|
||||
if (rotationTarget == null || rotationTarget.movementCorrection == MovementCorrection.OFF) {
|
||||
return original;
|
||||
}
|
||||
|
||||
float yaw = rotation.yaw() * Mth.DEG_TO_RAD;
|
||||
return new Vec3(-Mth.sin(yaw) * 0.2F, 0.0, Mth.cos(yaw) * 0.2F);
|
||||
}
|
||||
|
||||
@Redirect(
|
||||
method = "jumpFromGround",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/world/entity/LivingEntity;getYRot()F"
|
||||
)
|
||||
)
|
||||
private float lovisual$omniSprintJumpYaw(LivingEntity instance) {
|
||||
return lovisual$playerJumpEvent != null ? lovisual$playerJumpEvent.getYaw() : instance.getYRot();
|
||||
}
|
||||
|
||||
@Inject(method = "jumpFromGround", at = @At("RETURN"))
|
||||
private void lovisual$onJumpReturn(CallbackInfo ci) {
|
||||
lovisual$playerJumpEvent = null;
|
||||
}
|
||||
|
||||
@Unique
|
||||
private boolean isBad(Holder<MobEffect> entry) {
|
||||
MobEffect eff = entry.value();
|
||||
return eff == MobEffects.BLINDNESS.value()
|
||||
|| eff == MobEffects.DARKNESS.value()
|
||||
|| eff == MobEffects.NAUSEA.value();
|
||||
}
|
||||
|
||||
@Inject(
|
||||
method = "getBlockSpeedFactor",
|
||||
at = @At("RETURN"),
|
||||
cancellable = true
|
||||
)
|
||||
private void nostun$soulSandVelocity(CallbackInfoReturnable<Float> cir) {
|
||||
// NoStun module removed; no velocity override remains.
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,224 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.mixins.entity.living;
|
||||
|
||||
import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
|
||||
import com.llamalad7.mixinextras.sugar.Local;
|
||||
import dev.loki.lovisual.util.screen.ClientScreen;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
|
||||
import net.minecraft.client.player.LocalPlayer;
|
||||
import net.minecraft.client.renderer.entity.EntityRenderer;
|
||||
import net.minecraft.client.renderer.entity.EntityRendererProvider;
|
||||
import net.minecraft.client.renderer.entity.LivingEntityRenderer;
|
||||
import net.minecraft.client.renderer.entity.state.LivingEntityRenderState;
|
||||
import net.minecraft.client.renderer.rendertype.RenderType;
|
||||
import net.minecraft.client.renderer.rendertype.RenderTypes;
|
||||
import net.minecraft.resources.Identifier;
|
||||
import net.minecraft.util.Mth;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Constant;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.ModifyConstant;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
import dev.loki.lovisual.features.module.lifecycle.Modules;
|
||||
import dev.loki.lovisual.features.module.modules.visuals.esp.overlay.InvisEsp;
|
||||
import dev.loki.lovisual.features.module.modules.visuals.nametags.NameTags;
|
||||
import dev.loki.lovisual.features.module.modules.visuals.visibility.NoRender;
|
||||
import dev.loki.lovisual.render.invisible.SeeInvisiblesReveal;
|
||||
import dev.loki.lovisual.render.invisible.SeeInvisiblesState;
|
||||
import dev.loki.lovisual.render.fade.ViewObstructionFadeContext;
|
||||
import dev.loki.lovisual.render.fade.ViewObstructionFadeState;
|
||||
import dev.loki.lovisual.render.iris.runtime.IrisRuntime;
|
||||
import dev.loki.lovisual.util.aiming.rotation.RotationManager;
|
||||
import dev.loki.lovisual.util.aiming.data.Rotation;
|
||||
|
||||
@Mixin(LivingEntityRenderer.class)
|
||||
public abstract class LivingEntityRendererMixin<T extends LivingEntity, S extends LivingEntityRenderState> extends EntityRenderer<T, S> {
|
||||
protected LivingEntityRendererMixin(EntityRendererProvider.Context ctx) {
|
||||
super(ctx);
|
||||
}
|
||||
|
||||
@Shadow
|
||||
public abstract Identifier getTextureLocation(S state);
|
||||
|
||||
@Inject(
|
||||
method = "shouldShowName(Lnet/minecraft/world/entity/LivingEntity;D)Z",
|
||||
at = @At("HEAD"),
|
||||
cancellable = true
|
||||
)
|
||||
private void lovisual$hideVanillaLabels(T entity, double distanceSq, CallbackInfoReturnable<Boolean> cir) {
|
||||
if (entity instanceof Player && Modules.get(NameTags.class) != null && Modules.get(NameTags.class).isEnabled()) {
|
||||
cir.setReturnValue(false);
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(
|
||||
method = "extractRenderState(Lnet/minecraft/world/entity/LivingEntity;Lnet/minecraft/client/renderer/entity/state/LivingEntityRenderState;F)V",
|
||||
at = @At("TAIL")
|
||||
)
|
||||
private void lovisual$seeInvisiblesReveal(T entity, S state, float tickProgress, CallbackInfo ci) {
|
||||
SeeInvisiblesReveal.reveal(entity, state);
|
||||
}
|
||||
@ModifyExpressionValue(
|
||||
method = "submit",
|
||||
at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/entity/LivingEntityRenderer;getRenderType(Lnet/minecraft/client/renderer/entity/state/LivingEntityRenderState;ZZZ)Lnet/minecraft/client/renderer/rendertype/RenderType;")
|
||||
)
|
||||
private RenderType lovisual$seeInvisiblesTranslucent(RenderType original, @Local(argsOnly = true) S state) {
|
||||
if (!(state instanceof SeeInvisiblesState seeInvis) || !seeInvis.lovisual$isSeeInvisRevealed()) {
|
||||
return original;
|
||||
}
|
||||
return SeeInvisiblesReveal.renderTypeFor(original, getTextureLocation(state));
|
||||
}
|
||||
|
||||
@Inject(
|
||||
method = "extractRenderState(Lnet/minecraft/world/entity/LivingEntity;Lnet/minecraft/client/renderer/entity/state/LivingEntityRenderState;F)V",
|
||||
at = @At("TAIL")
|
||||
)
|
||||
private void lovisual$updateViewObstructionFadeState(T entity, S state, float tickProgress, CallbackInfo ci) {
|
||||
if (!(state instanceof ViewObstructionFadeState fadeState)) {
|
||||
return;
|
||||
}
|
||||
|
||||
NoRender noRender = Modules.get(NoRender.class);
|
||||
boolean obstructionFadeActive = noRender != null && noRender.shouldFadeEntity(entity);
|
||||
float obstructionFadeAlpha = obstructionFadeActive ? noRender.getEntityFadeAlpha(entity) : 1.0f;
|
||||
obstructionFadeActive = obstructionFadeActive && obstructionFadeAlpha < 0.99f;
|
||||
|
||||
boolean active = obstructionFadeActive;
|
||||
float alpha = obstructionFadeAlpha;
|
||||
fadeState.lovisual$setViewObstructionFadeActive(active);
|
||||
fadeState.lovisual$setViewObstructionFadeAlpha(active ? alpha : 1.0f);
|
||||
}
|
||||
|
||||
@ModifyExpressionValue(
|
||||
method = "extractRenderState(Lnet/minecraft/world/entity/LivingEntity;Lnet/minecraft/client/renderer/entity/state/LivingEntityRenderState;F)V",
|
||||
at = @At(value = "INVOKE", target = "Lnet/minecraft/util/Mth;rotLerp(FFF)F")
|
||||
)
|
||||
private float lovisual$renderRotationYaw(float original, @Local(argsOnly = true) T entity, @Local(argsOnly = true) float tickProgress) {
|
||||
if (!(entity instanceof LocalPlayer player)) {
|
||||
return original;
|
||||
}
|
||||
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc.player != player || ClientScreen.current() instanceof AbstractContainerScreen<?>) {
|
||||
return original;
|
||||
}
|
||||
|
||||
Rotation current = RotationManager.INSTANCE.getCurrentRotation();
|
||||
Rotation previous = RotationManager.INSTANCE.getPreviousRotation();
|
||||
if (current == null) {
|
||||
return original;
|
||||
}
|
||||
|
||||
float prevYaw = previous != null ? previous.yaw() : current.yaw();
|
||||
if (prevYaw == player.getYRot() && current.yaw() == player.getYRot()) {
|
||||
return original;
|
||||
}
|
||||
|
||||
return Mth.rotLerp(tickProgress, prevYaw, current.yaw());
|
||||
}
|
||||
|
||||
@ModifyExpressionValue(
|
||||
method = "extractRenderState(Lnet/minecraft/world/entity/LivingEntity;Lnet/minecraft/client/renderer/entity/state/LivingEntityRenderState;F)V",
|
||||
at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/LivingEntity;getXRot(F)F")
|
||||
)
|
||||
private float lovisual$renderRotationPitch(float original, @Local(argsOnly = true) T entity, @Local(argsOnly = true) float tickProgress) {
|
||||
if (!(entity instanceof LocalPlayer player)) {
|
||||
return original;
|
||||
}
|
||||
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc.player != player || ClientScreen.current() instanceof AbstractContainerScreen<?>) {
|
||||
return original;
|
||||
}
|
||||
|
||||
Rotation current = RotationManager.INSTANCE.getCurrentRotation();
|
||||
Rotation previous = RotationManager.INSTANCE.getPreviousRotation();
|
||||
if (current == null) {
|
||||
return original;
|
||||
}
|
||||
|
||||
float prevPitch = previous != null ? previous.pitch() : current.pitch();
|
||||
if (prevPitch == player.getXRot() && current.pitch() == player.getXRot()) {
|
||||
return original;
|
||||
}
|
||||
|
||||
return Mth.lerp(tickProgress, prevPitch, current.pitch());
|
||||
}
|
||||
|
||||
@ModifyExpressionValue(
|
||||
method = "submit",
|
||||
at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/entity/LivingEntityRenderer;getRenderType(Lnet/minecraft/client/renderer/entity/state/LivingEntityRenderState;ZZZ)Lnet/minecraft/client/renderer/rendertype/RenderType;")
|
||||
)
|
||||
private RenderType lovisual$useTranslucentRenderTypeForViewFade(RenderType original, @Local(argsOnly = true) S state) {
|
||||
if (original == null || original.hasBlending()) {
|
||||
return original;
|
||||
}
|
||||
if (IrisRuntime.isRenderingShadowPass()) {
|
||||
return original;
|
||||
}
|
||||
if (!(state instanceof ViewObstructionFadeState fadeState)
|
||||
|| !fadeState.lovisual$isViewObstructionFadeActive()
|
||||
|| fadeState.lovisual$getViewObstructionFadeAlpha() >= 0.99f) {
|
||||
return original;
|
||||
}
|
||||
return RenderTypes.entityTranslucent(getTextureLocation(state), true);
|
||||
}
|
||||
|
||||
@Inject(method = "submit", at = @At("HEAD"))
|
||||
private void lovisual$pushViewObstructionFadeContext(S state, com.mojang.blaze3d.vertex.PoseStack matrixStack, net.minecraft.client.renderer.SubmitNodeCollector orderedRenderCommandQueue, net.minecraft.client.renderer.state.level.CameraRenderState cameraRenderState, CallbackInfo ci) {
|
||||
ViewObstructionFadeContext.push(state instanceof ViewObstructionFadeState fadeState ? fadeState : null);
|
||||
}
|
||||
|
||||
/**
|
||||
* InvisESP alpha is applied only around the body's own submitModel call
|
||||
* (not around the whole submit()), so gear layers (armor, held items,
|
||||
* elytra) rendered afterwards stay fully opaque, matching vanilla
|
||||
* behaviour for invisible players.
|
||||
*/
|
||||
@com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation(
|
||||
method = "submit",
|
||||
at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/SubmitNodeCollector;submitModel(Lnet/minecraft/client/model/Model;Ljava/lang/Object;Lcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/rendertype/RenderType;IIILnet/minecraft/client/renderer/texture/TextureAtlasSprite;ILnet/minecraft/client/renderer/feature/ModelFeatureRenderer$CrumblingOverlay;)V")
|
||||
)
|
||||
private void lovisual$fadeBodyOnlyForInvisReveal(
|
||||
net.minecraft.client.renderer.SubmitNodeCollector collector,
|
||||
net.minecraft.client.model.Model model,
|
||||
Object state2,
|
||||
com.mojang.blaze3d.vertex.PoseStack poseStack,
|
||||
RenderType renderType,
|
||||
int light,
|
||||
int overlay,
|
||||
int color,
|
||||
net.minecraft.client.renderer.texture.TextureAtlasSprite sprite,
|
||||
int outlineColor,
|
||||
net.minecraft.client.renderer.feature.ModelFeatureRenderer.CrumblingOverlay crumbling,
|
||||
com.llamalad7.mixinextras.injector.wrapoperation.Operation<Void> original) {
|
||||
if (!(state2 instanceof LivingEntityRenderState livingState) || !SeeInvisiblesReveal.isRevealedTranslucent(livingState)) {
|
||||
original.call(collector, model, state2, poseStack, renderType, light, overlay, color, sprite, outlineColor, crumbling);
|
||||
return;
|
||||
}
|
||||
float priorAlpha = ViewObstructionFadeContext.isActive() ? ViewObstructionFadeContext.alpha() : 1.0f;
|
||||
ViewObstructionFadeContext.push(true, priorAlpha * SeeInvisiblesReveal.revealAlpha());
|
||||
try {
|
||||
original.call(collector, model, state2, poseStack, renderType, light, overlay, color, sprite, outlineColor, crumbling);
|
||||
} finally {
|
||||
ViewObstructionFadeContext.pop();
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "submit", at = @At("RETURN"))
|
||||
private void lovisual$popViewObstructionFadeContext(S state, com.mojang.blaze3d.vertex.PoseStack matrixStack, net.minecraft.client.renderer.SubmitNodeCollector orderedRenderCommandQueue, net.minecraft.client.renderer.state.level.CameraRenderState cameraRenderState, CallbackInfo ci) {
|
||||
ViewObstructionFadeContext.pop();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,134 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.mixins.entity.player;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.player.AbstractClientPlayer;
|
||||
import net.minecraft.client.resources.DefaultPlayerSkin;
|
||||
import net.minecraft.util.Mth;
|
||||
import net.minecraft.world.effect.MobEffects;
|
||||
import net.minecraft.world.entity.ai.attributes.Attributes;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.Items;
|
||||
import net.minecraft.world.entity.player.PlayerSkin;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
import dev.loki.lovisual.features.module.lifecycle.Modules;
|
||||
import dev.loki.lovisual.features.module.modules.combat.pvp.PvpCooldowns;
|
||||
import dev.loki.lovisual.features.module.modules.misc.protect.StreamerMode;
|
||||
import dev.loki.lovisual.features.module.modules.visuals.FovControl;
|
||||
import dev.loki.lovisual.util.pvp.cooldown.CooldownRegistry;
|
||||
import dev.loki.lovisual.util.pvp.state.PvpTargetState;
|
||||
import dev.loki.lovisual.util.pvp.opponents.OpponentCooldownManager;
|
||||
|
||||
@Mixin(AbstractClientPlayer.class)
|
||||
public class AbstractClientPlayerMixin {
|
||||
|
||||
@Unique
|
||||
private boolean lovisual$wasUsing = false;
|
||||
|
||||
@Inject(method = "getFieldOfViewModifier", at = @At("HEAD"), cancellable = true)
|
||||
private void lovisual$overrideFovMultiplier(boolean changingFov, float fovEffectScale,
|
||||
CallbackInfoReturnable<Float> cir) {
|
||||
FovControl module = Modules.get(FovControl.class);
|
||||
if (module == null || !module.isEnabled()) return;
|
||||
|
||||
AbstractClientPlayer player = (AbstractClientPlayer) (Object) this;
|
||||
|
||||
float f = 1.0f;
|
||||
|
||||
if (player.getAbilities().flying && !module.disableFlyingFov()) {
|
||||
f *= 1.1f;
|
||||
}
|
||||
|
||||
boolean skipSpeed = module.disableSprintFov() && player.isSprinting();
|
||||
if (module.disableSpeedFov() && player.hasEffect(MobEffects.SPEED)) skipSpeed = true;
|
||||
if (module.disableSlownessFov() && player.hasEffect(MobEffects.SLOWNESS)) skipSpeed = true;
|
||||
|
||||
if (!skipSpeed) {
|
||||
float walkSpeed = player.getAbilities().getWalkingSpeed();
|
||||
if (walkSpeed != 0.0f) {
|
||||
float attr = (float) player.getAttributeValue(Attributes.MOVEMENT_SPEED);
|
||||
float speedFactor = attr / walkSpeed;
|
||||
f *= (speedFactor + 1.0f) / 2.0f;
|
||||
}
|
||||
}
|
||||
|
||||
if (player.isUsingItem()) {
|
||||
if (player.getUseItem().is(Items.BOW)) {
|
||||
if (!module.disableBowFov()) {
|
||||
float use = player.getTicksUsingItem() / 20.0f;
|
||||
use = Math.min(use, 1.0f);
|
||||
f *= 1.0f - (Mth.sqrt(use) * 0.15f);
|
||||
}
|
||||
} else if (changingFov && player.isScoping()) {
|
||||
if (!module.disableSpyglassFov()) {
|
||||
cir.setReturnValue(0.1f);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
float mult = Mth.lerp(fovEffectScale, 1.0f, f);
|
||||
|
||||
float expand = Math.max(0.0f, module.getExpandScale());
|
||||
float shrink = Math.max(0.0f, module.getShrinkScale());
|
||||
if (mult > 1.0f) {
|
||||
mult = 1.0f + (mult - 1.0f) * expand;
|
||||
} else if (mult < 1.0f) {
|
||||
mult = 1.0f - (1.0f - mult) * shrink;
|
||||
}
|
||||
|
||||
cir.setReturnValue(mult);
|
||||
}
|
||||
|
||||
@Inject(method = "getSkin", at = @At("RETURN"), cancellable = true)
|
||||
private void lovisual$streamerSkin(CallbackInfoReturnable<PlayerSkin> cir) {
|
||||
StreamerMode module = Modules.get(StreamerMode.class);
|
||||
if (module == null || !module.shouldHideSkins()) return;
|
||||
|
||||
AbstractClientPlayer player = (AbstractClientPlayer) (Object) this;
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc.player != null && player.getUUID().equals(mc.player.getUUID())) return;
|
||||
|
||||
cir.setReturnValue(DefaultPlayerSkin.get(player.getUUID()));
|
||||
}
|
||||
|
||||
@Inject(method = "tick", at = @At("TAIL"))
|
||||
private void lovisual$trackEat(CallbackInfo ci) {
|
||||
AbstractClientPlayer p = (AbstractClientPlayer) (Object) this;
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
|
||||
if (mc.player == null || p == mc.player) {
|
||||
lovisual$wasUsing = p.isUsingItem();
|
||||
return;
|
||||
}
|
||||
|
||||
boolean using = p.isUsingItem();
|
||||
|
||||
PvpCooldowns mod = Modules.get(PvpCooldowns.class);
|
||||
if (mod == null || !mod.isSystemEnabled() || !PvpTargetState.isTargetInPvp(p.getUUID())) {
|
||||
lovisual$wasUsing = using;
|
||||
return;
|
||||
}
|
||||
|
||||
if (using && !lovisual$wasUsing) {
|
||||
ItemStack stack = p.getUseItem();
|
||||
if (!stack.isEmpty() && CooldownRegistry.isTracked(stack.getItem())) {
|
||||
OpponentCooldownManager.recordUse(p.getUUID(), stack.getItem());
|
||||
}
|
||||
}
|
||||
|
||||
lovisual$wasUsing = using;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.mixins.entity.player;
|
||||
|
||||
import dev.loki.lovisual.features.module.lifecycle.Modules;
|
||||
import dev.loki.lovisual.features.module.modules.player.inventory.LockSlot;
|
||||
import net.minecraft.client.player.LocalPlayer;
|
||||
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.CallbackInfoReturnable;
|
||||
|
||||
/**
|
||||
* Cancels the Q / Ctrl+Q drop of the selected hotbar slot
|
||||
* while LockSlot protects it.
|
||||
*/
|
||||
@Mixin(LocalPlayer.class)
|
||||
public class LocalPlayerDropMixin {
|
||||
|
||||
@Inject(method = "drop(Z)Z", at = @At("HEAD"), cancellable = true)
|
||||
private void lovisual$lockSlot$blockSelectedDrop(boolean wholeStack, CallbackInfoReturnable<Boolean> cir) {
|
||||
LockSlot lockSlot = Modules.get(LockSlot.class);
|
||||
if (lockSlot != null && lockSlot.shouldBlockSelectedDrop()) {
|
||||
cir.setReturnValue(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,691 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.mixins.entity.player;
|
||||
|
||||
import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
|
||||
import dev.loki.lovisual.events.Events;
|
||||
import dev.loki.lovisual.events.impl.input.SprintControlEvent;
|
||||
import dev.loki.lovisual.events.impl.movement.MovementInputEvent;
|
||||
import dev.loki.lovisual.events.impl.network.EventPostSync;
|
||||
import dev.loki.lovisual.events.impl.network.EventSync;
|
||||
import dev.loki.lovisual.events.impl.player.PostPlayerUpdateEvent;
|
||||
import dev.loki.lovisual.events.impl.world.EventPushOutOfBlocks;
|
||||
import dev.loki.lovisual.util.combat.rotation.SprintController;
|
||||
import dev.loki.lovisual.util.screen.ClientScreen;
|
||||
import net.minecraft.client.player.ClientInput;
|
||||
import net.minecraft.client.player.LocalPlayer;
|
||||
import net.minecraft.network.protocol.game.ServerboundMovePlayerPacket;
|
||||
import net.minecraft.util.Mth;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.entity.PortalProcessor;
|
||||
import net.minecraft.world.entity.player.Input;
|
||||
import net.minecraft.world.entity.projectile.throwableitemprojectile.ThrownEnderpearl;
|
||||
import net.minecraft.world.inventory.InventoryMenu;
|
||||
import net.minecraft.world.level.entity.EntityTypeTest;
|
||||
import net.minecraft.world.phys.Vec2;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
import dev.loki.lovisual.features.module.lifecycle.Modules;
|
||||
import dev.loki.lovisual.features.module.modules.visuals.camera.Freecam;
|
||||
import dev.loki.lovisual.mixininterface.entity.ILocalPlayer;
|
||||
import dev.loki.lovisual.mixins.accessors.entity.EntityAccessor;
|
||||
import dev.loki.lovisual.mixins.accessors.misc.InputAccessor;
|
||||
import dev.loki.lovisual.mixins.accessors.entity.LocalPlayerAccessor;
|
||||
import dev.loki.lovisual.util.aiming.rotation.RotationManager;
|
||||
import dev.loki.lovisual.util.aiming.data.Rotation;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Mixin(LocalPlayer.class)
|
||||
public abstract class LocalPlayerMixin implements ILocalPlayer {
|
||||
|
||||
@Shadow
|
||||
public ClientInput input;
|
||||
@Unique
|
||||
private float lovisual$lastYaw;
|
||||
@Unique
|
||||
private float lovisual$lastPitch;
|
||||
@Unique
|
||||
private double lovisual$rotationPrevX;
|
||||
@Unique
|
||||
private double lovisual$rotationPrevZ;
|
||||
@Unique
|
||||
private float lovisual$rotationPrevBodyYaw;
|
||||
@Unique
|
||||
private boolean lovisual$rotationBodyYawInitialized;
|
||||
@Unique
|
||||
private float freecam$lastHealth = Float.NaN;
|
||||
@Unique
|
||||
private boolean frozen = false;
|
||||
@Unique
|
||||
private Vec3 savedVelocity = Vec3.ZERO;
|
||||
@Unique
|
||||
private double savedX, savedY, savedZ;
|
||||
@Unique
|
||||
private float savedBodyYaw;
|
||||
@Unique
|
||||
private float savedHeadYaw;
|
||||
@Unique
|
||||
private Runnable lovisual$eventPostAction;
|
||||
@Unique
|
||||
private EventSync lovisual$activeSyncEvent;
|
||||
@Unique
|
||||
private boolean lovisual$postUpdateLock = false;
|
||||
@Unique
|
||||
private Input lastInput;
|
||||
@Unique
|
||||
private boolean pendingSpoof = false;
|
||||
@Unique
|
||||
private int boostTicks = 0;
|
||||
@Unique
|
||||
private double boostX = 0, boostZ = 0;
|
||||
@Unique
|
||||
private boolean wasGround = true;
|
||||
|
||||
@Unique
|
||||
private static boolean lovisual$sameInput(Input a, Input b) {
|
||||
if (a == b) return true;
|
||||
if (a == null || b == null) return false;
|
||||
|
||||
return a.forward() == b.forward()
|
||||
&& a.backward() == b.backward()
|
||||
&& a.left() == b.left()
|
||||
&& a.right() == b.right()
|
||||
&& a.jump() == b.jump()
|
||||
&& a.shift() == b.shift()
|
||||
&& a.sprint() == b.sprint();
|
||||
}
|
||||
|
||||
@Unique
|
||||
private static float lovisual$impulse(boolean positive, boolean negative) {
|
||||
if (positive == negative) {
|
||||
return 0.0f;
|
||||
}
|
||||
return positive ? 1.0f : -1.0f;
|
||||
}
|
||||
|
||||
@Unique
|
||||
private static float lovisual$simulateBodyYaw(
|
||||
float yaw,
|
||||
float prevBodyYaw,
|
||||
double prevX,
|
||||
double prevZ,
|
||||
double currentX,
|
||||
double currentZ,
|
||||
float handSwingProgress
|
||||
) {
|
||||
double motionX = currentX - prevX;
|
||||
double motionZ = currentZ - prevZ;
|
||||
float motionSquared = (float) (motionX * motionX + motionZ * motionZ);
|
||||
float bodyYaw = prevBodyYaw;
|
||||
|
||||
if (motionSquared > 0.0025000002F) {
|
||||
float movementYaw = (float) Mth.atan2(motionZ, motionX) * Mth.RAD_TO_DEG - 90.0F;
|
||||
float yawDiff = Math.abs(Mth.wrapDegrees(yaw) - movementYaw);
|
||||
bodyYaw = (yawDiff > 95.0F && yawDiff < 265.0F) ? movementYaw - 180.0F : movementYaw;
|
||||
}
|
||||
|
||||
if (handSwingProgress - 0.2F > 0.0F) {
|
||||
bodyYaw = yaw;
|
||||
}
|
||||
|
||||
float deltaYaw = Mth.wrapDegrees(bodyYaw - prevBodyYaw);
|
||||
bodyYaw = prevBodyYaw + deltaYaw * 0.3F;
|
||||
|
||||
float yawOffsetDiff = Mth.wrapDegrees(yaw - bodyYaw);
|
||||
float maxHeadRotation = 52.0F;
|
||||
if (Math.abs(yawOffsetDiff) > maxHeadRotation) {
|
||||
bodyYaw += yawOffsetDiff - Math.signum(yawOffsetDiff) * maxHeadRotation;
|
||||
}
|
||||
|
||||
return bodyYaw;
|
||||
}
|
||||
|
||||
@Inject(method = "tick", at = @At("HEAD"))
|
||||
private void lovisual$cacheLastRotations(CallbackInfo ci) {
|
||||
LocalPlayer self = (LocalPlayer) (Object) this;
|
||||
lovisual$lastYaw = self.getYRot();
|
||||
lovisual$lastPitch = self.getXRot();
|
||||
RotationManager.INSTANCE.runTickLifecycle();
|
||||
if (!lovisual$rotationBodyYawInitialized) {
|
||||
lovisual$rotationPrevX = self.getX();
|
||||
lovisual$rotationPrevZ = self.getZ();
|
||||
lovisual$rotationPrevBodyYaw = self.getVisualRotationYInDegrees();
|
||||
lovisual$rotationBodyYawInitialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "handlePortalTransitionEffect", at = @At("HEAD"), cancellable = true)
|
||||
private void lovisual$disablePortalNausea(boolean inPortal, CallbackInfo ci) {
|
||||
|
||||
LocalPlayer self = (LocalPlayer) (Object) this;
|
||||
PortalProcessor portalManager = null;
|
||||
if (self instanceof EntityAccessor accessor) {
|
||||
portalManager = accessor.lovisual$getPortalManager();
|
||||
}
|
||||
if (portalManager != null && portalManager.isInsidePortalThisTick()) {
|
||||
if (self instanceof LocalPlayerAccessor accessor) {
|
||||
accessor.lovisual$setNauseaIntensity(0.0f);
|
||||
accessor.lovisual$setLastNauseaIntensity(0.0f);
|
||||
}
|
||||
ci.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public float lovisual$getLastYaw() {
|
||||
return lovisual$lastYaw;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float lovisual$getLastPitch() {
|
||||
return lovisual$lastPitch;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void lovisual$setLastYaw(float yaw) {
|
||||
this.lovisual$lastYaw = yaw;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void lovisual$setLastPitch(float pitch) {
|
||||
this.lovisual$lastPitch = pitch;
|
||||
}
|
||||
|
||||
@ModifyExpressionValue(
|
||||
method = "aiStep",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/client/player/LocalPlayer;canStartSprinting()Z"
|
||||
)
|
||||
)
|
||||
private boolean lovisual$sprintMovementStart(boolean original) {
|
||||
return lovisual$postSprintControl(original, SprintControlEvent.Source.MOVEMENT_TICK);
|
||||
}
|
||||
|
||||
@ModifyExpressionValue(
|
||||
method = "aiStep",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/world/entity/player/Input;sprint()Z"
|
||||
)
|
||||
)
|
||||
private boolean lovisual$sprintMovementInput(boolean original) {
|
||||
return lovisual$postSprintControl(original, SprintControlEvent.Source.MOVEMENT_TICK);
|
||||
}
|
||||
|
||||
@ModifyExpressionValue(
|
||||
method = "sendIsSprintingIfNeeded",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/client/player/LocalPlayer;isSprinting()Z"
|
||||
)
|
||||
)
|
||||
private boolean lovisual$sprintNetwork(boolean original) {
|
||||
if (SprintController.INSTANCE.isSprintBlocked()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return original;
|
||||
}
|
||||
|
||||
@Unique
|
||||
private boolean lovisual$postSprintControl(boolean original, SprintControlEvent.Source source) {
|
||||
LocalPlayer self = (LocalPlayer) (Object) this;
|
||||
|
||||
if (self.input == null) {
|
||||
return original;
|
||||
}
|
||||
|
||||
if (!Events.BUS.hasListeners(SprintControlEvent.class)) {
|
||||
return original;
|
||||
}
|
||||
|
||||
Vec2 movement = self.input.getMoveVector();
|
||||
SprintControlEvent event = new SprintControlEvent(
|
||||
movement.y,
|
||||
movement.x,
|
||||
original,
|
||||
source
|
||||
);
|
||||
Events.BUS.post(event);
|
||||
return event.shouldSprint();
|
||||
}
|
||||
|
||||
@Inject(method = "modifyInput", at = @At("RETURN"), cancellable = true)
|
||||
private void boostWhenUsing(Vec2 input, CallbackInfoReturnable<Vec2> cir) {
|
||||
// NoStun module removed; no item-use speed boost remains.
|
||||
}
|
||||
|
||||
@Inject(method = "aiStep", at = @At("HEAD"))
|
||||
private void lovisual$elytraFlyVanilla(CallbackInfo ci) {
|
||||
// ElytraFly module removed; no vanilla elytra override remains.
|
||||
}
|
||||
|
||||
@Shadow
|
||||
protected abstract void sendPosition();
|
||||
|
||||
@Inject(method = "applyInput", at = @At("HEAD"), cancellable = true)
|
||||
private void freecam$blockWASD(CallbackInfo ci) {
|
||||
Freecam fc = Modules.get(Freecam.class);
|
||||
if (fc != null && fc.isEnabled() && fc.isCameraInput()) {
|
||||
ci.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(
|
||||
method = "aiStep",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/client/player/ClientInput;tick()V",
|
||||
shift = At.Shift.AFTER
|
||||
)
|
||||
)
|
||||
private void freecam$afterInputTick(CallbackInfo ci) {
|
||||
Freecam fc = Modules.get(Freecam.class);
|
||||
if (fc == null || !fc.isEnabled() || !fc.isCameraInput()) return;
|
||||
|
||||
((InputAccessor) this.input).setMovementVector(Vec2.ZERO);
|
||||
|
||||
boolean sneak = false;
|
||||
boolean sprint = this.input.keyPresses.sprint();
|
||||
|
||||
this.input.keyPresses = new Input(
|
||||
false, false, false, false,
|
||||
false,
|
||||
sneak,
|
||||
sprint
|
||||
);
|
||||
}
|
||||
|
||||
@Inject(
|
||||
method = "aiStep",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/client/player/ClientInput;tick()V",
|
||||
shift = At.Shift.AFTER
|
||||
)
|
||||
)
|
||||
private void lovisual$afterInputTickSprint(CallbackInfo ci) {
|
||||
if (this.input == null) return;
|
||||
|
||||
Input original = this.input.keyPresses;
|
||||
if (original == null) return;
|
||||
|
||||
Input input = original;
|
||||
|
||||
if (Events.BUS.hasListeners(MovementInputEvent.class)) {
|
||||
MovementInputEvent event = new MovementInputEvent(input);
|
||||
Events.BUS.post(event);
|
||||
|
||||
Input eventInput = event.toPlayerInput();
|
||||
if (!lovisual$sameInput(input, eventInput)) {
|
||||
input = eventInput;
|
||||
}
|
||||
}
|
||||
|
||||
Input transformedInput = lovisual$transformDirection(input);
|
||||
if (!lovisual$sameInput(input, transformedInput)) {
|
||||
input = transformedInput;
|
||||
}
|
||||
|
||||
if (Events.BUS.hasListeners(SprintControlEvent.class)) {
|
||||
float forward = lovisual$impulse(input.forward(), input.backward());
|
||||
float sideways = lovisual$impulse(input.left(), input.right());
|
||||
|
||||
SprintControlEvent event = new SprintControlEvent(
|
||||
forward,
|
||||
sideways,
|
||||
input.sprint(),
|
||||
SprintControlEvent.Source.INPUT
|
||||
);
|
||||
Events.BUS.post(event);
|
||||
|
||||
if (event.shouldSprint() != input.sprint()) {
|
||||
input = new Input(
|
||||
input.forward(),
|
||||
input.backward(),
|
||||
input.left(),
|
||||
input.right(),
|
||||
input.jump(),
|
||||
input.shift(),
|
||||
event.shouldSprint()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (!lovisual$sameInput(original, input)) {
|
||||
lovisual$applyPlayerInput(input);
|
||||
}
|
||||
}
|
||||
|
||||
@Unique
|
||||
private void lovisual$applyPlayerInput(Input input) {
|
||||
if (this.input == null || input == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
InputAccessor accessor = (InputAccessor) this.input;
|
||||
accessor.setPlayerInput(input);
|
||||
|
||||
float forward = lovisual$impulse(input.forward(), input.backward());
|
||||
float sideways = lovisual$impulse(input.left(), input.right());
|
||||
|
||||
accessor.setMovementVector(new Vec2(sideways, forward).normalized());
|
||||
}
|
||||
|
||||
@Unique
|
||||
private Input lovisual$transformDirection(Input input) {
|
||||
// Free correction now runs at KeyboardInput.tick(), matching the input pipeline.
|
||||
// Keep this post-input hook inert to avoid double-transforming movement input.
|
||||
return input;
|
||||
}
|
||||
|
||||
@Redirect(
|
||||
method = "aiStep",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/world/entity/player/Input;shift()Z",
|
||||
ordinal = 0
|
||||
)
|
||||
)
|
||||
private boolean freecam$noFlyDown0(Input in) {
|
||||
Freecam fc = Modules.get(Freecam.class);
|
||||
if (fc != null && fc.isEnabled() && fc.isCameraInput()) {
|
||||
LocalPlayer self = (LocalPlayer) (Object) this;
|
||||
if (self.getAbilities().flying) return false;
|
||||
}
|
||||
return in.shift();
|
||||
}
|
||||
|
||||
@Redirect(
|
||||
method = "aiStep",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/world/entity/player/Input;shift()Z",
|
||||
ordinal = 1
|
||||
),
|
||||
require = 0
|
||||
)
|
||||
private boolean freecam$noFlyDown1(Input in) {
|
||||
Freecam fc = Modules.get(Freecam.class);
|
||||
if (fc != null && fc.isEnabled() && fc.isCameraInput()) {
|
||||
LocalPlayer self = (LocalPlayer) (Object) this;
|
||||
if (self.getAbilities().flying) return false;
|
||||
}
|
||||
return in.shift();
|
||||
}
|
||||
|
||||
@Inject(method = "hurtTo", at = @At("HEAD"))
|
||||
private void freecam$onUpdateHealth(float newHealth, CallbackInfo ci) {
|
||||
Freecam fc = Modules.get(Freecam.class);
|
||||
|
||||
if (fc == null || !fc.isEnabled() || !fc.disableOnDamage()) {
|
||||
freecam$lastHealth = newHealth;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Float.isNaN(freecam$lastHealth) && newHealth < freecam$lastHealth) {
|
||||
fc.toggle();
|
||||
}
|
||||
|
||||
freecam$lastHealth = newHealth;
|
||||
}
|
||||
|
||||
@Unique
|
||||
private boolean shouldFreezeNow() {
|
||||
Freecam freecam = Modules.get(Freecam.class);
|
||||
return freecam != null && freecam.freezePlayer() && freecam.isCameraInput();
|
||||
}
|
||||
|
||||
@Inject(method = "sendPosition", at = @At("HEAD"), cancellable = true)
|
||||
private void lovisual$eventSync(CallbackInfo ci) {
|
||||
if (!Events.BUS.hasListeners(EventSync.class)) return;
|
||||
lovisual$eventPostAction = null;
|
||||
LocalPlayer self = (LocalPlayer) (Object) this;
|
||||
EventSync event = new EventSync(self.getYRot(), self.getXRot());
|
||||
Events.BUS.post(event);
|
||||
lovisual$activeSyncEvent = event;
|
||||
lovisual$eventPostAction = event.getPostAction();
|
||||
if (event.isCancelled()) {
|
||||
lovisual$activeSyncEvent = null;
|
||||
lovisual$eventPostAction = null;
|
||||
ci.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "sendPosition", at = @At("RETURN"))
|
||||
private void lovisual$eventPostSync(CallbackInfo ci) {
|
||||
if (Events.BUS.hasListeners(EventPostSync.class)) {
|
||||
Events.BUS.post(new EventPostSync());
|
||||
}
|
||||
if (lovisual$eventPostAction != null) {
|
||||
lovisual$eventPostAction.run();
|
||||
lovisual$eventPostAction = null;
|
||||
}
|
||||
lovisual$activeSyncEvent = null;
|
||||
}
|
||||
|
||||
@ModifyExpressionValue(
|
||||
method = "sendPosition",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/client/player/LocalPlayer;getYRot()F"
|
||||
)
|
||||
)
|
||||
private float lovisual$eventSyncYaw(float original) {
|
||||
lovisual$updateVisualBodyYaw();
|
||||
|
||||
EventSync event = lovisual$activeSyncEvent;
|
||||
if (event != null && event.hasRotationOverride()) return event.getYaw();
|
||||
|
||||
Rotation rotation = RotationManager.INSTANCE.getCurrentRotation();
|
||||
return rotation != null ? rotation.yaw() : original;
|
||||
}
|
||||
|
||||
@ModifyExpressionValue(
|
||||
method = "sendPosition",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/client/player/LocalPlayer;getXRot()F"
|
||||
)
|
||||
)
|
||||
private float lovisual$eventSyncPitch(float original) {
|
||||
EventSync event = lovisual$activeSyncEvent;
|
||||
if (event != null && event.hasRotationOverride()) return Mth.clamp(event.getPitch(), -90.0f, 90.0f);
|
||||
|
||||
Rotation rotation = RotationManager.INSTANCE.getCurrentRotation();
|
||||
return rotation != null ? Mth.clamp(rotation.pitch(), -90.0f, 90.0f) : original;
|
||||
}
|
||||
|
||||
@ModifyExpressionValue(
|
||||
method = "tick",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/client/player/LocalPlayer;getYRot()F"
|
||||
)
|
||||
)
|
||||
private float lovisual$visualTickYaw(float original) {
|
||||
lovisual$updateVisualBodyYaw();
|
||||
|
||||
Rotation rotation = RotationManager.INSTANCE.getCurrentRotation();
|
||||
return rotation != null ? rotation.yaw() : original;
|
||||
}
|
||||
|
||||
@ModifyExpressionValue(
|
||||
method = "tick",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/client/player/LocalPlayer;getXRot()F"
|
||||
)
|
||||
)
|
||||
private float lovisual$visualTickPitch(float original) {
|
||||
Rotation rotation = RotationManager.INSTANCE.getCurrentRotation();
|
||||
return rotation != null ? Mth.clamp(rotation.pitch(), -90.0f, 90.0f) : original;
|
||||
}
|
||||
|
||||
@Inject(
|
||||
method = "tick",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/client/player/LocalPlayer;sendPosition()V",
|
||||
ordinal = 0,
|
||||
shift = At.Shift.AFTER
|
||||
),
|
||||
cancellable = true
|
||||
)
|
||||
private void lovisual$postPlayerUpdate(CallbackInfo ci) {
|
||||
if (!Events.BUS.hasListeners(PostPlayerUpdateEvent.class)) return;
|
||||
if (lovisual$postUpdateLock) return;
|
||||
|
||||
PostPlayerUpdateEvent event = new PostPlayerUpdateEvent();
|
||||
Events.BUS.post(event);
|
||||
|
||||
if (!event.isCancelled()) return;
|
||||
|
||||
ci.cancel();
|
||||
|
||||
int iterations = event.getIterations();
|
||||
if (iterations <= 0) return;
|
||||
|
||||
LocalPlayer self = (LocalPlayer) (Object) this;
|
||||
for (int i = 0; i < iterations; i++) {
|
||||
lovisual$postUpdateLock = true;
|
||||
self.tick();
|
||||
lovisual$postUpdateLock = false;
|
||||
sendPosition();
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "sendPosition", at = @At("HEAD"), cancellable = true)
|
||||
private void cancelPackets(CallbackInfo ci) {
|
||||
// Freeze module removed; only Freecam may still freeze movement.
|
||||
}
|
||||
|
||||
@Inject(method = "aiStep", at = @At("HEAD"), cancellable = true)
|
||||
private void lovisual$freezeTickMovement(CallbackInfo ci) {
|
||||
LocalPlayer p = (LocalPlayer) (Object) this;
|
||||
|
||||
if (!shouldFreezeNow()) {
|
||||
if (frozen) {
|
||||
p.setDeltaMovement(savedVelocity);
|
||||
frozen = false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!frozen) {
|
||||
frozen = true;
|
||||
savedX = p.getX();
|
||||
savedY = p.getY();
|
||||
savedZ = p.getZ();
|
||||
savedVelocity = p.getDeltaMovement();
|
||||
|
||||
if (Modules.get(Freecam.class) == null || !Modules.get(Freecam.class).isEnabled()) {
|
||||
savedBodyYaw = p.yBodyRot;
|
||||
savedHeadYaw = p.yHeadRot;
|
||||
}
|
||||
}
|
||||
|
||||
p.setDeltaMovement(Vec3.ZERO);
|
||||
p.absSnapTo(savedX, savedY, savedZ);
|
||||
|
||||
if (Modules.get(Freecam.class) == null || !Modules.get(Freecam.class).isEnabled()) {
|
||||
p.yBodyRot = savedBodyYaw;
|
||||
p.yHeadRot = savedHeadYaw;
|
||||
}
|
||||
|
||||
ci.cancel();
|
||||
}
|
||||
|
||||
@Inject(method = "aiStep", at = @At("HEAD"))
|
||||
private void lovisual$noStunTickMovement(CallbackInfo ci) {
|
||||
// NoStun module removed; no hurt-time input override remains.
|
||||
}
|
||||
|
||||
@Unique
|
||||
private void lovisual$updateVisualBodyYaw() {
|
||||
LocalPlayer self = (LocalPlayer) (Object) this;
|
||||
Rotation rotation = RotationManager.INSTANCE.getCurrentRotation();
|
||||
if (rotation == null) {
|
||||
lovisual$rotationPrevX = self.getX();
|
||||
lovisual$rotationPrevZ = self.getZ();
|
||||
lovisual$rotationPrevBodyYaw = self.getVisualRotationYInDegrees();
|
||||
return;
|
||||
}
|
||||
|
||||
float newBodyYaw = lovisual$simulateBodyYaw(
|
||||
rotation.yaw(),
|
||||
lovisual$rotationPrevBodyYaw,
|
||||
lovisual$rotationPrevX,
|
||||
lovisual$rotationPrevZ,
|
||||
self.getX(),
|
||||
self.getZ(),
|
||||
self.attackAnim
|
||||
);
|
||||
|
||||
self.setYBodyRot(newBodyYaw);
|
||||
lovisual$rotationPrevBodyYaw = newBodyYaw;
|
||||
lovisual$rotationPrevX = self.getX();
|
||||
lovisual$rotationPrevZ = self.getZ();
|
||||
}
|
||||
|
||||
@Inject(method = "tick", at = @At("HEAD"))
|
||||
private void detectPearlImpact(CallbackInfo ci) {
|
||||
// NoPush module removed; no pearl-phase spoofing remains.
|
||||
}
|
||||
|
||||
@Inject(method = "sendPosition", at = @At("HEAD"))
|
||||
private void sendSpoofAfterPearl(CallbackInfo ci) {
|
||||
// NoPush module removed; no pearl-phase spoofing remains.
|
||||
}
|
||||
|
||||
@Inject(method = "aiStep", at = @At("TAIL"))
|
||||
private void onCollideBoost(CallbackInfo ci) {
|
||||
// Speed module removed; no entity-collision boost remains.
|
||||
}
|
||||
|
||||
@Inject(method = "aiStep", at = @At("TAIL"))
|
||||
private void omniLongJump(CallbackInfo ci) {
|
||||
// Speed module removed; no jump boost remains.
|
||||
}
|
||||
|
||||
@Inject(method = "modifyInput", at = @At("RETURN"), cancellable = true)
|
||||
private void applyLegitSpeed(Vec2 input, CallbackInfoReturnable<Vec2> cir) {
|
||||
// Speed module removed; no input speed multiplier remains.
|
||||
}
|
||||
|
||||
@Inject(method = "closeContainer", at = @At("HEAD"), cancellable = true)
|
||||
private void lovisual$inventoryMove$deferCloseHandledScreen(CallbackInfo ci) {
|
||||
// InventoryMove module removed; no container close deferral remains.
|
||||
}
|
||||
|
||||
@Inject(method = "closeContainer", at = @At("HEAD"), cancellable = true)
|
||||
private void lovisual$keepInventoryOpen(CallbackInfo ci) {
|
||||
// XCarry module removed; no inventory close suppression remains.
|
||||
}
|
||||
|
||||
@Inject(method = "moveTowardsClosestSpace", at = @At("HEAD"), cancellable = true)
|
||||
private void lovisual$noPushOutOfBlocks(double x, double z, CallbackInfo ci) {
|
||||
if (!Events.BUS.hasListeners(EventPushOutOfBlocks.class)) return;
|
||||
EventPushOutOfBlocks event = new EventPushOutOfBlocks(x, z);
|
||||
Events.BUS.post(event);
|
||||
if (event.isCancelled()) {
|
||||
ci.cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,142 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.mixins.entity.player;
|
||||
|
||||
import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
|
||||
import com.llamalad7.mixinextras.injector.ModifyReturnValue;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.core.registries.BuiltInRegistries;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.item.ItemCooldowns;
|
||||
import net.minecraft.world.item.Items;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
import dev.loki.lovisual.events.Events;
|
||||
import dev.loki.lovisual.events.impl.movement.PlayerSafeWalkEvent;
|
||||
import dev.loki.lovisual.features.module.lifecycle.Modules;
|
||||
import dev.loki.lovisual.features.module.modules.player.interact.NoDelay;
|
||||
import dev.loki.lovisual.features.module.modules.combat.pvp.TPSSync;
|
||||
import dev.loki.lovisual.mixininterface.entity.IPlayerAttackCooldown;
|
||||
import dev.loki.lovisual.util.aiming.rotation.RotationManager;
|
||||
|
||||
@Mixin(Player.class)
|
||||
public abstract class PlayerEntityMixin implements IPlayerAttackCooldown {
|
||||
|
||||
@Shadow
|
||||
@Final
|
||||
private ItemCooldowns cooldowns;
|
||||
|
||||
@Inject(method = "isStayingOnGroundSurface", at = @At("RETURN"), cancellable = true)
|
||||
private void lovisual$safeWalk(CallbackInfoReturnable<Boolean> cir) {
|
||||
if (cir.getReturnValue()) return;
|
||||
if ((Object) this != Minecraft.getInstance().player) return;
|
||||
if (!Events.BUS.hasListeners(PlayerSafeWalkEvent.class)) return;
|
||||
|
||||
PlayerSafeWalkEvent event = new PlayerSafeWalkEvent();
|
||||
Events.BUS.post(event);
|
||||
if (event.isSafeWalk()) {
|
||||
cir.setReturnValue(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "isMobilityRestricted", at = @At("HEAD"), cancellable = true)
|
||||
private void lovisual$noStunBlindnessSprint(CallbackInfoReturnable<Boolean> cir) {
|
||||
// NoStun module removed; no mobility override remains.
|
||||
}
|
||||
|
||||
@ModifyExpressionValue(
|
||||
method = "causeExtraKnockback",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/world/entity/player/Player;getYRot()F"
|
||||
)
|
||||
)
|
||||
private float lovisual$fixKnockbackYaw(float original) {
|
||||
if ((Object) this != Minecraft.getInstance().player) {
|
||||
return original;
|
||||
}
|
||||
return RotationManager.INSTANCE.getMovementRotation().yaw();
|
||||
}
|
||||
|
||||
@ModifyExpressionValue(
|
||||
method = "doSweepAttack",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/world/entity/player/Player;getYRot()F"
|
||||
)
|
||||
)
|
||||
private float lovisual$fixSweepYaw(float original) {
|
||||
if ((Object) this != Minecraft.getInstance().player) {
|
||||
return original;
|
||||
}
|
||||
return RotationManager.INSTANCE.getMovementRotation().yaw();
|
||||
}
|
||||
|
||||
@ModifyExpressionValue(
|
||||
method = "travel",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/world/entity/player/Player;getLookAngle()Lnet/minecraft/world/phys/Vec3;"
|
||||
)
|
||||
)
|
||||
private Vec3 lovisual$fixTravelRotationVector(Vec3 original) {
|
||||
if ((Object) this != Minecraft.getInstance().player) {
|
||||
return original;
|
||||
}
|
||||
return RotationManager.INSTANCE.getMovementRotation().directionVector();
|
||||
}
|
||||
|
||||
@Inject(method = "tick", at = @At("HEAD"))
|
||||
private void handleShieldCooldown(CallbackInfo ci) {
|
||||
NoDelay noDelay = Modules.get(NoDelay.class);
|
||||
if (noDelay == null || !noDelay.shouldDisableShieldCooldown()) return;
|
||||
|
||||
cooldowns.removeCooldown(BuiltInRegistries.ITEM.getKey(Items.SHIELD));
|
||||
}
|
||||
|
||||
/**
|
||||
* Синхронизация attack cooldown с серверным TPS.
|
||||
* НЕ трогает ItemCooldownManager.
|
||||
* НЕ влияет на shield.
|
||||
*/
|
||||
@ModifyReturnValue(
|
||||
method = "getCurrentItemAttackStrengthDelay",
|
||||
at = @At("RETURN")
|
||||
)
|
||||
private float lovisual$tpsSyncAttackCooldown(float original) {
|
||||
float result = original;
|
||||
|
||||
TPSSync tpsSync = Modules.get(TPSSync.class);
|
||||
if (tpsSync != null && tpsSync.isEnabled()) {
|
||||
float serverDelta = tpsSync.getServerTickDelta();
|
||||
if (serverDelta > 0.0f && serverDelta < 1.0f) {
|
||||
// замедляем заполнение cooldown’а под TPS
|
||||
result = result / serverDelta;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float lovisual$getAttackCooldownProgress(float tickDelta) {
|
||||
Player self = (Player) (Object) this;
|
||||
return self.getAttackStrengthScale(tickDelta);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean lovisual$isAttackCharged(float tickDelta) {
|
||||
return lovisual$getAttackCooldownProgress(tickDelta) >= 1.0f;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,169 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.mixins.entity.world;
|
||||
|
||||
import com.llamalad7.mixinextras.injector.ModifyReturnValue;
|
||||
import net.minecraft.client.player.LocalPlayer;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.MoverType;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.ModifyVariable;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
import dev.loki.lovisual.events.Events;
|
||||
import dev.loki.lovisual.events.impl.player.PlayerMoveEvent;
|
||||
import dev.loki.lovisual.events.impl.movement.PlayerStepEvent;
|
||||
import dev.loki.lovisual.events.impl.movement.PlayerStepSuccessEvent;
|
||||
import dev.loki.lovisual.events.impl.world.PlayerVelocityStrafe;
|
||||
import dev.loki.lovisual.features.module.lifecycle.Modules;
|
||||
import dev.loki.lovisual.mixininterface.entity.IEntity;
|
||||
import dev.loki.lovisual.mixins.accessors.entity.EntityAccessor;
|
||||
import dev.loki.lovisual.mixins.accessors.entity.EntityInvoker;
|
||||
import dev.loki.lovisual.render.helpers.player.TrailPoint;
|
||||
import dev.loki.lovisual.util.aiming.rotation.RotationManager;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Mixin(Entity.class)
|
||||
public abstract class EntityMixin implements IEntity {
|
||||
|
||||
@Unique
|
||||
private final List<TrailPoint> trails = new ArrayList<>();
|
||||
@Shadow
|
||||
public double xOld;
|
||||
@Shadow
|
||||
public double yOld;
|
||||
@Shadow
|
||||
public double zOld;
|
||||
@Unique
|
||||
private MoverType lovisual$lastMoveType = MoverType.SELF;
|
||||
@Unique
|
||||
private Vec3 lovisual$stepBeforePos = Vec3.ZERO;
|
||||
@Unique
|
||||
private Vec3 lovisual$stepRequestedMovement = Vec3.ZERO;
|
||||
|
||||
@Unique
|
||||
@Override
|
||||
public Vec3 get$InstantRenderPos() {
|
||||
return new Vec3(xOld, yOld, zOld);
|
||||
}
|
||||
|
||||
@Unique
|
||||
@Override
|
||||
public List<TrailPoint> lovisual$getTrails() {
|
||||
return trails;
|
||||
}
|
||||
|
||||
@Inject(method = "move", at = @At("HEAD"))
|
||||
private void lovisual$cacheMoveType(MoverType type, Vec3 movement, CallbackInfo ci) {
|
||||
lovisual$lastMoveType = type;
|
||||
Entity self = (Entity) (Object) this;
|
||||
if (self instanceof LocalPlayer) {
|
||||
lovisual$stepBeforePos = self.position();
|
||||
lovisual$stepRequestedMovement = movement;
|
||||
}
|
||||
}
|
||||
|
||||
@ModifyVariable(method = "move", at = @At("HEAD"), argsOnly = true, index = 2)
|
||||
private Vec3 lovisual$onMove(Vec3 movement) {
|
||||
Entity self = (Entity) (Object) this;
|
||||
if (!(self instanceof LocalPlayer)) return movement;
|
||||
if (!Events.BUS.hasListeners(PlayerMoveEvent.class)) return movement;
|
||||
|
||||
PlayerMoveEvent event = new PlayerMoveEvent(lovisual$lastMoveType, movement);
|
||||
Events.BUS.post(event);
|
||||
Vec3 out = event.getMovement();
|
||||
return out != null ? out : movement;
|
||||
}
|
||||
|
||||
@Inject(method = "move", at = @At("RETURN"))
|
||||
private void lovisual$onMoveReturn(MoverType type, Vec3 movement, CallbackInfo ci) {
|
||||
Entity self = (Entity) (Object) this;
|
||||
if (!(self instanceof LocalPlayer)) return;
|
||||
if (!Events.BUS.hasListeners(PlayerStepSuccessEvent.class)) return;
|
||||
|
||||
Vec3 before = lovisual$stepBeforePos;
|
||||
Vec3 after = self.position();
|
||||
Vec3 requested = lovisual$stepRequestedMovement;
|
||||
double yDelta = after.y - before.y;
|
||||
double horizontalRequested = Math.hypot(requested.x, requested.z);
|
||||
if (yDelta <= 0.5 || horizontalRequested <= 1.0E-5) return;
|
||||
|
||||
Events.BUS.post(new PlayerStepSuccessEvent(after.subtract(before)));
|
||||
}
|
||||
|
||||
@ModifyReturnValue(method = "maxUpStep", at = @At("RETURN"))
|
||||
private float lovisual$modifyStepHeight(float original) {
|
||||
Entity self = (Entity) (Object) this;
|
||||
if (!(self instanceof LocalPlayer)) return original;
|
||||
if (!Events.BUS.hasListeners(PlayerStepEvent.class)) return original;
|
||||
|
||||
PlayerStepEvent event = new PlayerStepEvent(original);
|
||||
Events.BUS.post(event);
|
||||
return event.getHeight();
|
||||
}
|
||||
|
||||
@Redirect(
|
||||
method = "moveRelative",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/world/entity/Entity;getInputVector(Lnet/minecraft/world/phys/Vec3;FF)Lnet/minecraft/world/phys/Vec3;"
|
||||
)
|
||||
)
|
||||
private Vec3 lovisual$velocityStrafe(Vec3 movementInput, float speed, float yaw) {
|
||||
Entity self = (Entity) (Object) this;
|
||||
Vec3 vanilla = EntityInvoker.lovisual$movementInputToVelocity(movementInput, speed, yaw);
|
||||
if (!(self instanceof LocalPlayer)) return vanilla;
|
||||
if (!Events.BUS.hasListeners(PlayerVelocityStrafe.class)) return vanilla;
|
||||
|
||||
PlayerVelocityStrafe event = new PlayerVelocityStrafe(movementInput, speed, yaw, vanilla);
|
||||
Events.BUS.post(event);
|
||||
return event.getVelocity();
|
||||
}
|
||||
|
||||
@ModifyVariable(
|
||||
method = "calculateViewVector(FF)Lnet/minecraft/world/phys/Vec3;",
|
||||
at = @At("HEAD"),
|
||||
ordinal = 0,
|
||||
argsOnly = true
|
||||
)
|
||||
private float lovisual$rotationVectorPitch(float pitch) {
|
||||
if (!((Object) this instanceof LocalPlayer)) {
|
||||
return pitch;
|
||||
}
|
||||
|
||||
return RotationManager.INSTANCE.getCurrentRotation() != null
|
||||
? RotationManager.INSTANCE.getCurrentRotation().pitch()
|
||||
: pitch;
|
||||
}
|
||||
|
||||
@Redirect(
|
||||
method = "push(Lnet/minecraft/world/entity/Entity;)V",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/world/entity/Entity;push(DDD)V"
|
||||
)
|
||||
)
|
||||
private void lovisual$cancelEntityPush(Entity instance, double x, double y, double z) {
|
||||
// NoPush module removed; keep vanilla entity pushing.
|
||||
instance.push(x, y, z);
|
||||
}
|
||||
|
||||
@Inject(method = "makeStuckInBlock", at = @At("HEAD"), cancellable = true)
|
||||
private void nostun$cancelSlowMovement(BlockState state, Vec3 multiplier, CallbackInfo ci) {
|
||||
// NoStun module removed; no slow-movement override remains.
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.mixins.entity.world;
|
||||
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.player.LocalPlayer;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.entity.projectile.FireworkRocketEntity;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import dev.loki.lovisual.events.Events;
|
||||
import dev.loki.lovisual.events.impl.entity.FireworkEvent;
|
||||
import dev.loki.lovisual.util.aiming.rotation.RotationManager;
|
||||
import dev.loki.lovisual.util.aiming.data.Rotation;
|
||||
|
||||
@Mixin(FireworkRocketEntity.class)
|
||||
public abstract class FireworkRocketEntityMixin {
|
||||
|
||||
@Shadow
|
||||
@Nullable
|
||||
private LivingEntity attachedToEntity;
|
||||
|
||||
@Unique
|
||||
private static Rotation resolveMoveRotation(LocalPlayer player) {
|
||||
return RotationManager.INSTANCE.getMovementRotation();
|
||||
}
|
||||
|
||||
@WrapOperation(
|
||||
method = "tick",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/world/entity/LivingEntity;getLookAngle()Lnet/minecraft/world/phys/Vec3;"
|
||||
)
|
||||
)
|
||||
private Vec3 lovisual$fireworkMoveRotation(LivingEntity instance, Operation<Vec3> original) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc == null || attachedToEntity != mc.player) {
|
||||
return original.call(instance);
|
||||
}
|
||||
|
||||
return resolveMoveRotation(mc.player).directionVector();
|
||||
}
|
||||
|
||||
@WrapOperation(
|
||||
method = "tick",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/world/entity/LivingEntity;getDeltaMovement()Lnet/minecraft/world/phys/Vec3;",
|
||||
ordinal = 0
|
||||
)
|
||||
)
|
||||
private Vec3 lovisual$fireworkVelocityHook(LivingEntity instance, Operation<Vec3> original) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
Vec3 velocity = original.call(instance);
|
||||
if (mc == null || attachedToEntity != mc.player || !Events.BUS.hasListeners(FireworkEvent.class)) {
|
||||
return velocity;
|
||||
}
|
||||
|
||||
FireworkEvent event = new FireworkEvent(velocity);
|
||||
Events.BUS.post(event);
|
||||
return event.getVector();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.mixins.entity.world;
|
||||
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.entity.projectile.ProjectileUtil;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.phys.AABB;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
@Mixin(ProjectileUtil.class)
|
||||
public class ProjectileUtilMixin {
|
||||
|
||||
// Intercept getOtherEntities in ProjectileUtil.raycast:
|
||||
// - If Hitbox is disabled -> keep vanilla list.
|
||||
// - If enabled -> drop dead LivingEntity and those Hitbox wants to ignore.
|
||||
@Redirect(
|
||||
method = "getEntityHitResult(Lnet/minecraft/world/entity/Entity;Lnet/minecraft/world/phys/Vec3;Lnet/minecraft/world/phys/Vec3;Lnet/minecraft/world/phys/AABB;Ljava/util/function/Predicate;D)Lnet/minecraft/world/phys/EntityHitResult;",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/world/level/Level;getEntities(Lnet/minecraft/world/entity/Entity;Lnet/minecraft/world/phys/AABB;Ljava/util/function/Predicate;)Ljava/util/List;"
|
||||
)
|
||||
)
|
||||
private static List<Entity> lovisual$filterEntities(Level world, Entity source, AABB box, Predicate<Entity> predicate) {
|
||||
// Hitbox module removed; keep vanilla entity list.
|
||||
return world.getEntities(source, box, predicate);
|
||||
}
|
||||
|
||||
// Intercept getBoundingBox: add padding only for live targets allowed by Hitbox.
|
||||
@Redirect(
|
||||
method = "getEntityHitResult(Lnet/minecraft/world/entity/Entity;Lnet/minecraft/world/phys/Vec3;Lnet/minecraft/world/phys/Vec3;Lnet/minecraft/world/phys/AABB;Ljava/util/function/Predicate;D)Lnet/minecraft/world/phys/EntityHitResult;",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/world/entity/Entity;getBoundingBox()Lnet/minecraft/world/phys/AABB;"
|
||||
)
|
||||
)
|
||||
private static AABB lovisual$padBoundingBox(Entity target,
|
||||
Entity source,
|
||||
Vec3 start,
|
||||
Vec3 end,
|
||||
AABB box,
|
||||
Predicate<Entity> predicate,
|
||||
double maxDistance) {
|
||||
// Hitbox module removed; keep vanilla bounding box.
|
||||
return target.getBoundingBox();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,101 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.mixins.gui.chat;
|
||||
|
||||
import net.minecraft.client.gui.Font;
|
||||
import net.minecraft.client.gui.GuiGraphicsExtractor;
|
||||
import net.minecraft.client.gui.components.ChatComponent;
|
||||
import net.minecraft.client.gui.components.ChatComponent.DisplayMode;
|
||||
import net.minecraft.client.multiplayer.chat.GuiMessageSource;
|
||||
import net.minecraft.client.multiplayer.chat.GuiMessageTag;
|
||||
import net.minecraft.client.multiplayer.chat.GuiMessage;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.network.chat.MessageSignature;
|
||||
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.ModifyVariable;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
import dev.loki.lovisual.features.command.util.CommandOutput;
|
||||
import dev.loki.lovisual.features.gui.chat.model.BetterChatStoreManager;
|
||||
import dev.loki.lovisual.features.gui.hud.draggable.impl.BetterChat;
|
||||
import dev.loki.lovisual.features.module.lifecycle.Modules;
|
||||
import dev.loki.lovisual.features.module.modules.misc.protect.MessageFilter;
|
||||
import dev.loki.lovisual.features.module.modules.misc.protect.NameProtect;
|
||||
import dev.loki.lovisual.features.module.modules.misc.protect.StreamerMode;
|
||||
import dev.loki.lovisual.Lifecycle;
|
||||
|
||||
@Mixin(ChatComponent.class)
|
||||
public class ChatComponentMixin {
|
||||
|
||||
@ModifyVariable(
|
||||
method = "addMessage(Lnet/minecraft/network/chat/Component;Lnet/minecraft/network/chat/MessageSignature;Lnet/minecraft/client/multiplayer/chat/GuiMessageSource;Lnet/minecraft/client/multiplayer/chat/GuiMessageTag;)V",
|
||||
at = @At("HEAD"),
|
||||
argsOnly = true
|
||||
)
|
||||
private Component lovisual$nameProtectChat(Component message) {
|
||||
if (!Lifecycle.canRunHud()) return message;
|
||||
Component protectedMessage = NameProtect.protect(message);
|
||||
return StreamerMode.protectComponent(protectedMessage);
|
||||
}
|
||||
|
||||
@Inject(
|
||||
method = "addMessage(Lnet/minecraft/network/chat/Component;Lnet/minecraft/network/chat/MessageSignature;Lnet/minecraft/client/multiplayer/chat/GuiMessageSource;Lnet/minecraft/client/multiplayer/chat/GuiMessageTag;)V",
|
||||
at = @At("HEAD"),
|
||||
cancellable = true
|
||||
)
|
||||
private void lovisual$filterChatFull(Component message,
|
||||
MessageSignature signatureData,
|
||||
GuiMessageSource source,
|
||||
GuiMessageTag indicator,
|
||||
CallbackInfo ci) {
|
||||
if (!Lifecycle.canRunHud()) return;
|
||||
MessageFilter messageFilter = Modules.get(MessageFilter.class);
|
||||
if (messageFilter != null && messageFilter.shouldHideChat(message)) {
|
||||
ci.cancel();
|
||||
return;
|
||||
}
|
||||
if (!CommandOutput.isLoVisualMessage(message)) {
|
||||
BetterChatStoreManager.addMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "logChatMessage(Lnet/minecraft/client/multiplayer/chat/GuiMessage;)V", at = @At("HEAD"), cancellable = true)
|
||||
private void lovisual$commands$suppressClientLog(GuiMessage message, CallbackInfo ci) {
|
||||
if (message != null && CommandOutput.isLoVisualMessage(message.content())) {
|
||||
ci.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "clearMessages(Z)V", at = @At("TAIL"))
|
||||
private void lovisual$betterChat$clear(boolean clearHistory, CallbackInfo ci) {
|
||||
if (!Lifecycle.canRunHud()) return;
|
||||
if (!BetterChat.isActive()) return;
|
||||
// Never wipe BetterChat store on vanilla clear; just persist to disk.
|
||||
BetterChatStoreManager.flushActive();
|
||||
}
|
||||
|
||||
@Inject(method = "extractRenderState(Lnet/minecraft/client/gui/GuiGraphicsExtractor;Lnet/minecraft/client/gui/Font;IIILnet/minecraft/client/gui/components/ChatComponent$DisplayMode;Z)V",
|
||||
at = @At("HEAD"), cancellable = true)
|
||||
private void lovisual$betterChat$hideVanilla(GuiGraphicsExtractor context,
|
||||
Font textRenderer,
|
||||
int currentTick,
|
||||
int mouseX,
|
||||
int mouseY,
|
||||
DisplayMode displayMode,
|
||||
boolean inChat,
|
||||
CallbackInfo ci) {
|
||||
if (!Lifecycle.canRunHud()) return;
|
||||
if (!BetterChat.isActive()) return;
|
||||
BetterChat cfg = BetterChat.get();
|
||||
if (cfg != null && cfg.hideVanilla()) {
|
||||
ci.cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,112 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.mixins.gui.chat;
|
||||
|
||||
import net.minecraft.client.gui.ActiveTextCollector;
|
||||
import net.minecraft.client.gui.GuiGraphicsExtractor;
|
||||
import net.minecraft.client.gui.components.ChatComponent;
|
||||
import net.minecraft.client.gui.components.ChatComponent.DisplayMode;
|
||||
import net.minecraft.client.gui.components.CommandSuggestions;
|
||||
import net.minecraft.client.gui.screens.ChatScreen;
|
||||
import net.minecraft.client.input.KeyEvent;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
import dev.loki.lovisual.features.command.core.CommandManager;
|
||||
import dev.loki.lovisual.features.gui.chat.render.BetterChatRenderer;
|
||||
import dev.loki.lovisual.features.gui.chat.messages.BetterChatTabHandler;
|
||||
import dev.loki.lovisual.features.gui.hud.draggable.impl.BetterChat;
|
||||
import dev.loki.lovisual.mixins.accessors.chat.ChatScreenSuggestorAccessor;
|
||||
import dev.loki.lovisual.Lifecycle;
|
||||
|
||||
@Mixin(ChatScreen.class)
|
||||
public class ChatScreenMixin {
|
||||
|
||||
@Inject(method = "extractRenderState(Lnet/minecraft/client/gui/GuiGraphicsExtractor;IIF)V", at = @At("HEAD"), cancellable = true)
|
||||
private void lovisual$betterChat$render(GuiGraphicsExtractor context, int mouseX, int mouseY, float delta, CallbackInfo ci) {
|
||||
if (!Lifecycle.canRunHud()) return;
|
||||
if (BetterChat.isActive()) {
|
||||
BetterChat cfg = BetterChat.get();
|
||||
if (cfg != null && cfg.hideVanilla()) {
|
||||
// Stash suggestion context; BetterChatRenderer will draw it in correct Z-order.
|
||||
BetterChatRenderer.setSuggestionContext((ChatScreen) (Object) this, context, mouseX, mouseY);
|
||||
ci.cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Redirect(
|
||||
method = "mouseClicked(Lnet/minecraft/client/input/MouseButtonEvent;Z)Z",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/client/gui/components/ChatComponent;captureClickableText(Lnet/minecraft/client/gui/ActiveTextCollector;IILnet/minecraft/client/gui/components/ChatComponent$DisplayMode;)V"
|
||||
)
|
||||
)
|
||||
private void lovisual$betterChat$disableVanillaChatClickHitbox(ChatComponent chatHud,
|
||||
ActiveTextCollector textConsumer,
|
||||
int windowHeight,
|
||||
int currentTick,
|
||||
DisplayMode displayMode) {
|
||||
if (lovisual$betterChat$shouldHideVanillaChat()) {
|
||||
return;
|
||||
}
|
||||
chatHud.captureClickableText(textConsumer, windowHeight, currentTick, displayMode);
|
||||
}
|
||||
|
||||
@Inject(method = "mouseScrolled(DDDD)Z", at = @At("HEAD"), cancellable = true)
|
||||
private void lovisual$betterChat$disableVanillaChatScroll(double mouseX,
|
||||
double mouseY,
|
||||
double horizontalAmount,
|
||||
double verticalAmount,
|
||||
CallbackInfoReturnable<Boolean> cir) {
|
||||
if (lovisual$betterChat$shouldHideVanillaChat()) {
|
||||
cir.setReturnValue(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "keyPressed(Lnet/minecraft/client/input/KeyEvent;)Z", at = @At("HEAD"))
|
||||
private void lovisual$betterChat$allowEscape(KeyEvent input, CallbackInfoReturnable<Boolean> cir) {
|
||||
if (!Lifecycle.canRunHud()) return;
|
||||
if (!BetterChat.isActive()) return;
|
||||
if (!input.isEscape()) return;
|
||||
CommandSuggestions suggestor = ((ChatScreenSuggestorAccessor) this).getLoVisual$suggestor();
|
||||
if (suggestor != null) {
|
||||
suggestor.setAllowSuggestions(false);
|
||||
suggestor.hide();
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "handleChatInput(Ljava/lang/String;Z)V", at = @At("HEAD"), cancellable = true, require = 0)
|
||||
private void lovisual$commands$sendMessage(String message, boolean addToHistory, CallbackInfo ci) {
|
||||
if (!Lifecycle.isActive()) return;
|
||||
if (CommandManager.handle(message)) {
|
||||
ci.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "keyPressed(Lnet/minecraft/client/input/KeyEvent;)Z", at = @At("HEAD"), cancellable = true, require = 0)
|
||||
private void lovisual$betterChat$tabCompleteCommands(KeyEvent input, CallbackInfoReturnable<Boolean> cir) {
|
||||
if (!Lifecycle.canRunHud()) return;
|
||||
if (!BetterChat.isActive()) return;
|
||||
if (BetterChatTabHandler.handleKey(input)) {
|
||||
cir.setReturnValue(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Unique
|
||||
private boolean lovisual$betterChat$shouldHideVanillaChat() {
|
||||
if (!Lifecycle.canRunHud()) return false;
|
||||
if (!BetterChat.isActive()) return false;
|
||||
BetterChat cfg = BetterChat.get();
|
||||
return cfg != null && cfg.hideVanilla();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.mixins.gui.chat;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.Font;
|
||||
import net.minecraft.client.gui.GuiGraphicsExtractor;
|
||||
import net.minecraft.client.gui.contextualbar.ContextualBar;
|
||||
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;
|
||||
import dev.loki.lovisual.features.gui.hud.nondraggable.impl.bars.CustomBar;
|
||||
import dev.loki.lovisual.Lifecycle;
|
||||
|
||||
@Mixin(ContextualBar.class)
|
||||
public interface ContextualBarMixin {
|
||||
|
||||
@Inject(method = "extractExperienceLevel", at = @At("HEAD"), cancellable = true)
|
||||
private static void lovisual$drawExperienceLevel(GuiGraphicsExtractor context,
|
||||
Font textRenderer,
|
||||
int level,
|
||||
CallbackInfo ci) {
|
||||
if (!Lifecycle.canRunHud()) return;
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc == null || mc.player == null) return;
|
||||
|
||||
CustomBar bar = CustomBar.get();
|
||||
if (bar == null) return;
|
||||
if (!bar.isHudBarEnabled() || !bar.isHotbarXpBarEnabled()) return;
|
||||
|
||||
ci.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,151 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.mixins.gui.core;
|
||||
|
||||
import dev.loki.lovisual.features.module.lifecycle.Modules;
|
||||
import dev.loki.lovisual.features.module.modules.misc.esp.ShitEsp;
|
||||
import dev.loki.lovisual.features.module.modules.misc.items.ItemScroller;
|
||||
import dev.loki.lovisual.features.module.modules.visuals.player.Animations;
|
||||
import dev.loki.lovisual.features.module.modules.visuals.items.ShulkerPreview;
|
||||
import net.minecraft.client.gui.GuiGraphicsExtractor;
|
||||
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
|
||||
import net.minecraft.client.renderer.RenderPipelines;
|
||||
import net.minecraft.world.inventory.AbstractContainerMenu;
|
||||
import net.minecraft.world.inventory.Slot;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import org.joml.Matrix3x2fStack;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
|
||||
@Mixin(AbstractContainerScreen.class)
|
||||
public abstract class AbstractContainerScreenMixin {
|
||||
|
||||
@Shadow
|
||||
protected Slot hoveredSlot;
|
||||
|
||||
@Shadow
|
||||
protected AbstractContainerMenu menu;
|
||||
|
||||
@Shadow
|
||||
protected int leftPos;
|
||||
|
||||
@Shadow
|
||||
protected int topPos;
|
||||
|
||||
@Unique
|
||||
private boolean lovisual$animations$scaleApplied;
|
||||
|
||||
@Inject(method = "onClose", at = @At("HEAD"), cancellable = true)
|
||||
private void lovisual$inventoryMove$deferClose(CallbackInfo ci) {
|
||||
// InventoryMove module removed; keep hook for compatibility.
|
||||
}
|
||||
|
||||
/**
|
||||
* Smooth open animation from the Animations module: the whole screen is drawn
|
||||
* with a scale between 0.9 and 1.0 around the screen center. The animated
|
||||
* value itself is advanced once per frame inside the module; this only reads
|
||||
* the settled value and wraps the extraction in a matching push/pop.
|
||||
*/
|
||||
@Inject(
|
||||
method = "extractRenderState(Lnet/minecraft/client/gui/GuiGraphicsExtractor;IIF)V",
|
||||
at = @At("HEAD")
|
||||
)
|
||||
private void lovisual$animations$beginOpenScale(GuiGraphicsExtractor graphics,
|
||||
int mouseX,
|
||||
int mouseY,
|
||||
float partialTick,
|
||||
CallbackInfo ci) {
|
||||
lovisual$animations$scaleApplied = false;
|
||||
|
||||
Animations animations = Modules.get(Animations.class);
|
||||
if (animations == null) return;
|
||||
|
||||
float scale = animations.getInventoryScale();
|
||||
if (scale >= 0.999f) return;
|
||||
|
||||
Matrix3x2fStack pose = graphics.pose();
|
||||
float centerX = graphics.guiWidth() * 0.5f;
|
||||
float centerY = graphics.guiHeight() * 0.5f;
|
||||
|
||||
pose.pushMatrix();
|
||||
pose.translate(centerX, centerY);
|
||||
pose.scale(scale, scale);
|
||||
pose.translate(-centerX, -centerY);
|
||||
lovisual$animations$scaleApplied = true;
|
||||
}
|
||||
|
||||
@Inject(
|
||||
method = "extractRenderState(Lnet/minecraft/client/gui/GuiGraphicsExtractor;IIF)V",
|
||||
at = @At("RETURN")
|
||||
)
|
||||
private void lovisual$animations$endOpenScale(GuiGraphicsExtractor graphics,
|
||||
int mouseX,
|
||||
int mouseY,
|
||||
float partialTick,
|
||||
CallbackInfo ci) {
|
||||
if (!lovisual$animations$scaleApplied) return;
|
||||
lovisual$animations$scaleApplied = false;
|
||||
graphics.pose().popMatrix();
|
||||
}
|
||||
|
||||
@Inject(
|
||||
method = "extractRenderState(Lnet/minecraft/client/gui/GuiGraphicsExtractor;IIF)V",
|
||||
at = @At("TAIL")
|
||||
)
|
||||
private void lovisual$shulkerPreview$draw(GuiGraphicsExtractor graphics,
|
||||
int mouseX,
|
||||
int mouseY,
|
||||
float partialTick,
|
||||
CallbackInfo ci) {
|
||||
Slot slot = this.hoveredSlot;
|
||||
ItemStack stack = slot == null ? ItemStack.EMPTY : slot.getItem();
|
||||
if (stack.isEmpty()) return;
|
||||
ShulkerPreview.onScreenExtract(graphics, stack, mouseX, mouseY);
|
||||
}
|
||||
|
||||
@Inject(
|
||||
method = "mouseScrolled(DDDD)Z",
|
||||
at = @At("HEAD"),
|
||||
cancellable = true
|
||||
)
|
||||
private void lovisual$itemScroller$quickMove(double mouseX, double mouseY,
|
||||
double horizontal, double vertical,
|
||||
CallbackInfoReturnable<Boolean> cir) {
|
||||
ItemScroller scroller = Modules.get(ItemScroller.class);
|
||||
if (scroller == null || !scroller.isEnabled()) return;
|
||||
|
||||
Slot slot = this.hoveredSlot;
|
||||
ItemStack stack = slot == null ? null : slot.getItem();
|
||||
if (scroller.onMouseScrolled(vertical, slot, stack)) {
|
||||
cir.setReturnValue(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "extractRenderState(Lnet/minecraft/client/gui/GuiGraphicsExtractor;IIF)V", at = @At("TAIL"))
|
||||
private void lovisual$shitEsp$highlight(GuiGraphicsExtractor graphics, int mouseX, int mouseY, float partialTick, CallbackInfo ci) {
|
||||
ShitEsp esp = Modules.get(ShitEsp.class);
|
||||
if (esp == null || !esp.isEnabled() || !esp.hasAnyGroup() || menu == null) return;
|
||||
for (Slot slot : menu.slots) {
|
||||
if (slot == null || !slot.hasItem()) continue;
|
||||
ItemStack stack = slot.getItem();
|
||||
Integer col = esp.colorFor(stack);
|
||||
if (col == null) continue;
|
||||
int x1 = leftPos + slot.x;
|
||||
int y1 = topPos + slot.y;
|
||||
int x2 = x1 + 16;
|
||||
int y2 = y1 + 16;
|
||||
graphics.fill(RenderPipelines.GUI, x1, y1, x2, y2, col);
|
||||
graphics.outline(x1, y1, x2 - x1, y2 - y1, col | 0xFF000000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.mixins.gui.core;
|
||||
|
||||
import net.minecraft.client.gui.GuiGraphicsExtractor;
|
||||
import net.minecraft.client.gui.components.AbstractSelectionList;
|
||||
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;
|
||||
import dev.loki.lovisual.features.gui.hud.nondraggable.impl.buttons.BetterButtons;
|
||||
|
||||
@Mixin(AbstractSelectionList.class)
|
||||
public abstract class AbstractSelectionListMixin {
|
||||
|
||||
@Inject(method = "enableScissor(Lnet/minecraft/client/gui/GuiGraphicsExtractor;)V", at = @At("HEAD"))
|
||||
private void lovisual$pushScissor(GuiGraphicsExtractor context, CallbackInfo ci) {
|
||||
AbstractSelectionList<?> self = (AbstractSelectionList<?>) (Object) this;
|
||||
BetterButtons.pushScissorContext(self.getX(), self.getY(), self.getRight(), self.getBottom());
|
||||
}
|
||||
|
||||
@Inject(
|
||||
method = "extractWidgetRenderState(Lnet/minecraft/client/gui/GuiGraphicsExtractor;IIF)V",
|
||||
at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/GuiGraphicsExtractor;disableScissor()V")
|
||||
)
|
||||
private void lovisual$popScissor(GuiGraphicsExtractor context, int mouseX, int mouseY, float deltaTicks, CallbackInfo ci) {
|
||||
BetterButtons.popScissorContext();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.mixins.gui.core;
|
||||
|
||||
import dev.loki.lovisual.util.screen.ClientScreen;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.GuiGraphicsExtractor;
|
||||
import net.minecraft.client.gui.components.AbstractSliderButton;
|
||||
import net.minecraft.client.gui.components.AbstractWidget;
|
||||
import net.minecraft.client.gui.screens.inventory.MerchantScreen;
|
||||
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;
|
||||
import dev.loki.lovisual.features.gui.hud.nondraggable.impl.buttons.BetterButtons;
|
||||
import dev.loki.lovisual.mixins.accessors.widget.SliderWidgetAccessor;
|
||||
|
||||
@Mixin(AbstractSliderButton.class)
|
||||
public abstract class AbstractSliderButtonMixin {
|
||||
|
||||
@Inject(method = "extractWidgetRenderState(Lnet/minecraft/client/gui/GuiGraphicsExtractor;IIF)V", at = @At("HEAD"), cancellable = true)
|
||||
private void lovisual$renderWidget(GuiGraphicsExtractor ctx, int mouseX, int mouseY, float delta, CallbackInfo ci) {
|
||||
BetterButtons buttons = BetterButtons.get();
|
||||
if (buttons == null || !buttons.useUiButtons()) return;
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc != null && ClientScreen.current() instanceof MerchantScreen) return;
|
||||
AbstractSliderButton self = (AbstractSliderButton) (Object) this;
|
||||
AbstractWidget widget = self;
|
||||
|
||||
boolean hovered = widget.isMouseOver(mouseX, mouseY);
|
||||
boolean dragging = ((SliderWidgetAccessor) self).lovisual$isDragging();
|
||||
float hover = BetterButtons.updateHover(widget, hovered || dragging);
|
||||
|
||||
double value = ((SliderWidgetAccessor) self).lovisual$getValue();
|
||||
BetterButtons.enqueueSlider(widget, (float) value, dragging, hover);
|
||||
if (hovered) {
|
||||
BetterButtons.captureTooltip(widget, mouseX, mouseY);
|
||||
}
|
||||
ci.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,84 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.mixins.gui.core;
|
||||
|
||||
import net.minecraft.client.gui.components.AbstractWidget;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
import dev.loki.lovisual.mixininterface.entity.IRoundedHitbox;
|
||||
|
||||
@Mixin(AbstractWidget.class)
|
||||
public abstract class AbstractWidgetMixin implements IRoundedHitbox {
|
||||
|
||||
@Unique
|
||||
private float lovisual$roundedRadius = 0.0f;
|
||||
@Unique
|
||||
private boolean lovisual$roundedEnabled = false;
|
||||
|
||||
@Override
|
||||
public void lovisual$setRoundedHitbox(float radius) {
|
||||
lovisual$roundedRadius = Math.max(0.0f, radius);
|
||||
lovisual$roundedEnabled = lovisual$roundedRadius > 0.0f;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void lovisual$clearRoundedHitbox() {
|
||||
lovisual$roundedRadius = 0.0f;
|
||||
lovisual$roundedEnabled = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float lovisual$getRoundedHitbox() {
|
||||
return lovisual$roundedRadius;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean lovisual$useRoundedHitbox() {
|
||||
return lovisual$roundedEnabled;
|
||||
}
|
||||
|
||||
@Inject(method = "isMouseOver(DD)Z", at = @At("HEAD"), cancellable = true)
|
||||
private void lovisual$isMouseOverRounded(double mouseX, double mouseY, CallbackInfoReturnable<Boolean> cir) {
|
||||
if (!lovisual$roundedEnabled) return;
|
||||
|
||||
AbstractWidget self = (AbstractWidget) (Object) this;
|
||||
float x = self.getX();
|
||||
float y = self.getY();
|
||||
float w = self.getWidth();
|
||||
float h = self.getHeight();
|
||||
|
||||
if (mouseX < x || mouseY < y || mouseX > x + w || mouseY > y + h) {
|
||||
cir.setReturnValue(false);
|
||||
return;
|
||||
}
|
||||
|
||||
float r = Math.min(lovisual$roundedRadius, Math.min(w, h) * 0.5f);
|
||||
if (r <= 0.0f) {
|
||||
cir.setReturnValue(true);
|
||||
return;
|
||||
}
|
||||
|
||||
float px = (float) (mouseX - x);
|
||||
float py = (float) (mouseY - y);
|
||||
|
||||
if ((px >= r && px <= w - r) || (py >= r && py <= h - r)) {
|
||||
cir.setReturnValue(true);
|
||||
return;
|
||||
}
|
||||
|
||||
float cx = (px < r) ? r : (w - r);
|
||||
float cy = (py < r) ? r : (h - r);
|
||||
float dx = px - cx;
|
||||
float dy = py - cy;
|
||||
cir.setReturnValue((dx * dx + dy * dy) <= (r * r));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,114 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.mixins.gui.multiplayer;
|
||||
|
||||
import dev.loki.lovisual.features.theme.Theme;
|
||||
import dev.loki.lovisual.features.gui.hud.config.HudRenderUtil;
|
||||
import dev.loki.lovisual.features.theme.Themes;
|
||||
import dev.loki.lovisual.render.engine.animation.AnimationUtility;
|
||||
import dev.loki.lovisual.render.engine.core.context.ViewportContext;
|
||||
import dev.loki.lovisual.render.engine.renderer.Renderer2D;
|
||||
import dev.loki.lovisual.render.engine.svg.model.SvgRenderOptions;
|
||||
import dev.loki.lovisual.util.proxy.ProxyBackend;
|
||||
import dev.loki.lovisual.util.proxy.model.ProxyEntry;
|
||||
import net.minecraft.client.gui.GuiGraphicsExtractor;
|
||||
import net.minecraft.client.gui.screens.Screen;
|
||||
import net.minecraft.client.gui.screens.multiplayer.JoinMultiplayerScreen;
|
||||
import net.minecraft.util.Mth;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
@Mixin(Screen.class)
|
||||
public abstract class JoinMultiplayerScreenMixin {
|
||||
@Unique
|
||||
private static final float LOVISUAL_PROXY_BUTTON_X = 8f;
|
||||
@Unique
|
||||
private static final float LOVISUAL_PROXY_BUTTON_Y = 8f;
|
||||
@Unique
|
||||
private static final float LOVISUAL_PROXY_BUTTON_W = 24f;
|
||||
@Unique
|
||||
private static final float LOVISUAL_PROXY_BUTTON_H = 24f;
|
||||
@Unique
|
||||
private float lovisual$proxyHover;
|
||||
@Unique
|
||||
private float lovisual$proxyPress;
|
||||
|
||||
@Inject(method = "extractRenderState(Lnet/minecraft/client/gui/GuiGraphicsExtractor;IIF)V", at = @At("RETURN"))
|
||||
private void lovisual$renderProxyEntry(GuiGraphicsExtractor context, int mouseX, int mouseY, float delta, CallbackInfo ci) {
|
||||
Screen self = (Screen) (Object) this;
|
||||
if (!(self instanceof JoinMultiplayerScreen)) return;
|
||||
float dt = AnimationUtility.deltaTime();
|
||||
boolean hovered = lovisual$inside(mouseX, mouseY, LOVISUAL_PROXY_BUTTON_X, LOVISUAL_PROXY_BUTTON_Y,
|
||||
LOVISUAL_PROXY_BUTTON_W, LOVISUAL_PROXY_BUTTON_H);
|
||||
lovisual$proxyHover = lovisual$animate(lovisual$proxyHover, hovered ? 1f : 0f, dt, 11f);
|
||||
lovisual$proxyPress = lovisual$animate(lovisual$proxyPress, 0f, dt, 8f);
|
||||
|
||||
ViewportContext.beginScaled(context);
|
||||
Renderer2D.COLOR.begin();
|
||||
try {
|
||||
lovisual$drawProxyButton();
|
||||
} finally {
|
||||
Renderer2D.COLOR.render();
|
||||
ViewportContext.end(context);
|
||||
}
|
||||
}
|
||||
|
||||
@Unique
|
||||
private void lovisual$drawProxyButton() {
|
||||
float hover = AnimationUtility.easeOutCubic(lovisual$proxyHover);
|
||||
float press = AnimationUtility.easeOutCubic(lovisual$proxyPress);
|
||||
Themes.Theme theme = Theme.theme();
|
||||
boolean enabled = ProxyBackend.isEnabled();
|
||||
ProxyEntry active = ProxyBackend.activeProxy();
|
||||
boolean configured = active.isConfigured();
|
||||
|
||||
float size = LOVISUAL_PROXY_BUTTON_W * (1.0f + hover * 0.035f - press * 0.025f);
|
||||
float x = LOVISUAL_PROXY_BUTTON_X + (LOVISUAL_PROXY_BUTTON_W - size) * 0.5f;
|
||||
float y = LOVISUAL_PROXY_BUTTON_Y + (LOVISUAL_PROXY_BUTTON_H - size) * 0.5f - hover * 0.35f;
|
||||
float radius = 5.0f;
|
||||
float centerX = x + size * 0.5f;
|
||||
float centerY = y + size * 0.5f;
|
||||
int accent = enabled && configured ? theme.accent() : 0x8A94A3;
|
||||
int baseTop = enabled && configured ? 0x222A20 : 0x1B2028;
|
||||
int baseBottom = enabled && configured ? 0x121812 : 0x10141B;
|
||||
int fillTop = HudRenderUtil.mixColor(withAlpha(baseTop, 226), withAlpha(accent, 96), (enabled && configured ? 0.18f : 0.06f) + hover * 0.12f);
|
||||
int fillBottom = HudRenderUtil.mixColor(withAlpha(baseBottom, 236), withAlpha(accent, 70), (enabled && configured ? 0.12f : 0.04f) + hover * 0.08f);
|
||||
int stroke = HudRenderUtil.mixColor(withAlpha(0x46505D, 180), withAlpha(accent, 218), (enabled && configured ? 0.38f : 0.10f) + hover * 0.35f);
|
||||
int iconColor = enabled && configured
|
||||
? HudRenderUtil.mixColor(withAlpha(0xFFFFFF, 245), withAlpha(theme.accent(), 255), 0.18f)
|
||||
: withAlpha(0xC5CCD6, 235);
|
||||
|
||||
Renderer2D.COLOR.roundedRectSoftShadow(x, y + 0.8f, size, size, radius,
|
||||
7.5f, 0.026f + hover * 0.012f, withAlpha(0x060810, 96));
|
||||
Renderer2D.COLOR.roundedRectGradientQuad(x, y, size, size, radius, 1f,
|
||||
fillTop, fillTop, fillBottom, fillBottom);
|
||||
Renderer2D.COLOR.roundedRectStrokeGradient(x + 0.5f, y + 0.5f, size - 1.0f, size - 1.0f,
|
||||
radius - 0.5f, 1f, 0.55f, HudRenderUtil.mixColor(stroke, 0xFFFFFFFF, 0.08f), stroke, 90f);
|
||||
Renderer2D.COLOR.svg("shield-user", centerX - 5.6f, centerY - 5.6f, 11.2f, 11.2f, SvgRenderOptions.overrideColor(iconColor));
|
||||
}
|
||||
|
||||
@Unique
|
||||
private static float lovisual$animate(float current, float target, float dt, float speed) {
|
||||
float next = AnimationUtility.approach(current, target, dt, speed);
|
||||
return AnimationUtility.snap(next, target, 0.001f);
|
||||
}
|
||||
|
||||
@Unique
|
||||
private static boolean lovisual$inside(float mx, float my, float x, float y, float w, float h) {
|
||||
return mx >= x && mx <= x + w && my >= y && my <= y + h;
|
||||
}
|
||||
|
||||
@Unique
|
||||
private static int withAlpha(int rgb, int alpha) {
|
||||
return (rgb & 0x00FFFFFF) | (Mth.clamp(alpha, 0, 255) << 24);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.mixins.gui.multiplayer;
|
||||
|
||||
import net.minecraft.client.gui.screens.inventory.AbstractRecipeBookScreen;
|
||||
import net.minecraft.client.gui.screens.recipebook.RecipeBookComponent;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
import dev.loki.lovisual.features.module.lifecycle.Modules;
|
||||
import dev.loki.lovisual.features.module.modules.misc.vanilla.BetterMinecraft;
|
||||
|
||||
@Mixin(AbstractRecipeBookScreen.class)
|
||||
public abstract class RecipeBookScreenMixin {
|
||||
|
||||
@Final
|
||||
@Shadow
|
||||
private RecipeBookComponent<?> recipeBookComponent;
|
||||
|
||||
@Inject(method = "initButton", at = @At("HEAD"), cancellable = true)
|
||||
private void lovisual$disableRecipeBook(CallbackInfo ci) {
|
||||
BetterMinecraft module = Modules.get(BetterMinecraft.class);
|
||||
if (module != null && module.isHideRecipeBookEnabled()) {
|
||||
ci.cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,102 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.mixins.gui.screens;
|
||||
|
||||
import com.llamalad7.mixinextras.sugar.Local;
|
||||
import dev.loki.lovisual.features.module.core.ModuleManager;
|
||||
import dev.loki.lovisual.util.screen.ClientScreen;
|
||||
import net.minecraft.client.DeltaTracker;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.Gui;
|
||||
import net.minecraft.client.gui.GuiGraphicsExtractor;
|
||||
import net.minecraft.client.gui.screens.Screen;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
import dev.loki.lovisual.features.gui.clickgui.editor.ClickGuiEditorScreen;
|
||||
import dev.loki.lovisual.features.gui.clickgui.picker.screen.ClickGuiPickerScreen;
|
||||
import dev.loki.lovisual.features.gui.clickgui.render.ClickGuiRenderer;
|
||||
import dev.loki.lovisual.features.gui.clickgui.screen.ClickGuiScreen;
|
||||
import dev.loki.lovisual.features.gui.hud.nondraggable.impl.bars.CustomBar;
|
||||
import dev.loki.lovisual.features.gui.hud.nondraggable.impl.dynamicisland.DynamicIsland;
|
||||
import dev.loki.lovisual.features.module.modules.misc.ClickGui;
|
||||
import dev.loki.lovisual.render.engine.core.system.LoVisualRenderSystem;
|
||||
import dev.loki.lovisual.render.engine.core.frame.RenderPhase;
|
||||
import dev.loki.lovisual.render.engine.core.frame.RenderPhaseScope;
|
||||
import dev.loki.lovisual.render.engine.renderer.Renderer2D;
|
||||
|
||||
@Mixin(Gui.class)
|
||||
public abstract class GuiScreenMixin {
|
||||
|
||||
@Unique
|
||||
private static void lovisual$extractCustomBarHeads(GuiGraphicsExtractor drawContext, DeltaTracker tickCounter) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc == null || mc.player == null) return;
|
||||
if (ClientScreen.current() != null) return;
|
||||
|
||||
CustomBar bar = CustomBar.get();
|
||||
if (bar == null || !bar.isHudBarEnabled() || !bar.isHotbarXpBarEnabled()) return;
|
||||
if (CustomBar.shouldUseJumpBar(mc) || !CustomBar.shouldUseLocator(mc, bar)) return;
|
||||
|
||||
LoVisualRenderSystem.ensureFrameContext();
|
||||
try (RenderPhaseScope ignored = LoVisualRenderSystem.phase(RenderPhase.SCREEN_TOP, "2d:hud:custom_bar_heads")) {
|
||||
Renderer2D.withDeferredLayer(Renderer2D.Deferred2DLayer.AFTER_VANILLA_GUI, () -> {
|
||||
CustomBar.renderLocatorHeads(drawContext, tickCounter, mc);
|
||||
Renderer2D.COLOR.render();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "setScreen", at = @At("HEAD"))
|
||||
private void lovisual$closeClickGuiOnScreen(Screen screen, CallbackInfo ci) {
|
||||
if (!ModuleManager.isEnabled("clickgui")) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ClickGuiRenderer.waitingForKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (screen instanceof ClickGuiScreen
|
||||
|| screen instanceof ClickGuiPickerScreen
|
||||
|| screen instanceof ClickGuiEditorScreen) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (screen != null) {
|
||||
ClickGui.setSuppressScreenClose(true);
|
||||
try {
|
||||
ModuleManager.setEnabled("clickgui", false);
|
||||
} finally {
|
||||
ClickGui.setSuppressScreenClose(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(
|
||||
method = "extractRenderState",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/client/gui/GuiGraphicsExtractor;applyCursor(Lcom/mojang/blaze3d/platform/Window;)V"
|
||||
)
|
||||
)
|
||||
private void lovisual$extractTopLayer(DeltaTracker tickCounter, boolean renderLevel, boolean renderScreens, CallbackInfo ci,
|
||||
@Local(ordinal = 0) GuiGraphicsExtractor drawContext) {
|
||||
if (drawContext == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
drawContext.nextStratum();
|
||||
lovisual$extractCustomBarHeads(drawContext, tickCounter);
|
||||
DynamicIsland.renderScreenOverlay(drawContext, tickCounter.getGameTimeDeltaTicks());
|
||||
ClickGuiRenderer.renderTopLayer(drawContext, tickCounter.getGameTimeDeltaTicks());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.mixins.gui.screens;
|
||||
|
||||
import net.minecraft.client.gui.GuiGraphicsExtractor;
|
||||
import net.minecraft.client.gui.components.Button;
|
||||
import net.minecraft.client.gui.components.Tooltip;
|
||||
import net.minecraft.client.gui.screens.PauseScreen;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
import dev.loki.lovisual.features.module.lifecycle.Modules;
|
||||
import dev.loki.lovisual.features.module.modules.combat.pvp.PvpCooldowns;
|
||||
|
||||
@Mixin(PauseScreen.class)
|
||||
public abstract class PauseScreenMixin {
|
||||
@Shadow
|
||||
@Nullable
|
||||
private Button disconnectButton;
|
||||
|
||||
@Unique
|
||||
private Button lovisual$lastExitButton;
|
||||
@Unique
|
||||
private boolean lovisual$disconnectBlocked;
|
||||
@Unique
|
||||
private boolean lovisual$previousExitActive = true;
|
||||
|
||||
@Inject(method = "init", at = @At("TAIL"))
|
||||
private void lovisual$blockDisconnectOnInit(CallbackInfo ci) {
|
||||
lovisual$syncDisconnectButton();
|
||||
}
|
||||
|
||||
@Inject(method = "extractRenderState", at = @At("HEAD"))
|
||||
private void lovisual$blockDisconnectOnRender(GuiGraphicsExtractor context, int mouseX, int mouseY, float deltaTicks, CallbackInfo ci) {
|
||||
lovisual$syncDisconnectButton();
|
||||
}
|
||||
|
||||
@Unique
|
||||
private void lovisual$syncDisconnectButton() {
|
||||
Button button = disconnectButton;
|
||||
if (button == null) {
|
||||
lovisual$lastExitButton = null;
|
||||
lovisual$disconnectBlocked = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (button != lovisual$lastExitButton) {
|
||||
lovisual$lastExitButton = button;
|
||||
lovisual$disconnectBlocked = false;
|
||||
lovisual$previousExitActive = button.active;
|
||||
}
|
||||
|
||||
PvpCooldowns cooldowns = Modules.get(PvpCooldowns.class);
|
||||
boolean shouldBlock = cooldowns != null && cooldowns.shouldBlockDisconnect();
|
||||
if (shouldBlock) {
|
||||
if (!lovisual$disconnectBlocked) {
|
||||
lovisual$previousExitActive = button.active;
|
||||
}
|
||||
lovisual$disconnectBlocked = true;
|
||||
button.active = false;
|
||||
button.setTooltip(Tooltip.create(cooldowns.getDisconnectBlockTooltip()));
|
||||
return;
|
||||
}
|
||||
|
||||
if (lovisual$disconnectBlocked) {
|
||||
button.active = lovisual$previousExitActive;
|
||||
button.setTooltip(null);
|
||||
lovisual$disconnectBlocked = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,160 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.mixins.gui.screens;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.GuiGraphicsExtractor;
|
||||
import net.minecraft.client.gui.screens.Screen;
|
||||
import net.minecraft.client.gui.screens.TitleScreen;
|
||||
import net.minecraft.network.chat.ClickEvent;
|
||||
import net.minecraft.resources.Identifier;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
import dev.loki.lovisual.config.i18n.MainConfig;
|
||||
import dev.loki.lovisual.features.command.core.CommandManager;
|
||||
import dev.loki.lovisual.features.command.util.CommandPrefix;
|
||||
import dev.loki.lovisual.features.gui.hud.nondraggable.impl.buttons.BetterButtons;
|
||||
import dev.loki.lovisual.features.module.lifecycle.Modules;
|
||||
import dev.loki.lovisual.features.module.modules.visuals.visibility.NoRender;
|
||||
import dev.loki.lovisual.render.engine.core.system.LoVisualRenderSystem;
|
||||
import dev.loki.lovisual.render.engine.core.frame.RenderPhase;
|
||||
import dev.loki.lovisual.render.engine.core.frame.RenderPhaseScope;
|
||||
import dev.loki.lovisual.render.engine.postprocess.render.MenuBackgroundRenderer;
|
||||
import dev.loki.lovisual.util.NarratorBlocker;
|
||||
|
||||
@Mixin(Screen.class)
|
||||
public abstract class ScreenMixin {
|
||||
@Unique
|
||||
private RenderPhaseScope lovisual$screenPhaseScope;
|
||||
|
||||
@Inject(method = "defaultHandleGameClickEvent", at = @At("HEAD"), cancellable = true)
|
||||
private static void lovisual$runClientChatCommand(ClickEvent event,
|
||||
Minecraft client,
|
||||
Screen screen,
|
||||
CallbackInfo ci) {
|
||||
if (!(event instanceof ClickEvent.RunCommand run)) return;
|
||||
String command = run.command();
|
||||
if (command == null || !CommandPrefix.isCommandLike(command)) return;
|
||||
if (CommandManager.handle(command)) {
|
||||
ci.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "extractMenuBackgroundTexture", at = @At("HEAD"), cancellable = true)
|
||||
private static void lovisual$replaceMenuBackground(GuiGraphicsExtractor context,
|
||||
Identifier texture,
|
||||
int x,
|
||||
int y,
|
||||
float u,
|
||||
float v,
|
||||
int width,
|
||||
int height,
|
||||
CallbackInfo ci) {
|
||||
if (texture == null || context == null) return;
|
||||
|
||||
if (!Screen.MENU_BACKGROUND.equals(texture)) return;
|
||||
if (x != 0 || y != 0) return;
|
||||
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc == null) return;
|
||||
if (width != context.guiWidth() || height != context.guiHeight()) return;
|
||||
|
||||
MainConfig cfg = MainConfig.get();
|
||||
if (cfg == null) return;
|
||||
String mode = cfg.getMenuBackgroundMode();
|
||||
if (mode == null || mode.equalsIgnoreCase("off")) return;
|
||||
|
||||
boolean aurora = mode.equalsIgnoreCase("aurora");
|
||||
MenuBackgroundRenderer.render(mc, aurora);
|
||||
ci.cancel();
|
||||
}
|
||||
|
||||
@Inject(method = "shouldRunNarration", at = @At("HEAD"), cancellable = true)
|
||||
private void lovisual$disableNarratorUi(CallbackInfoReturnable<Boolean> cir) {
|
||||
if (!NarratorBlocker.isBlocked()) return;
|
||||
cir.setReturnValue(false);
|
||||
}
|
||||
|
||||
@Inject(method = "extractBackground(Lnet/minecraft/client/gui/GuiGraphicsExtractor;IIF)V", at = @At("HEAD"), cancellable = true)
|
||||
private void lovisual$replaceScreenBackground(GuiGraphicsExtractor context, int mouseX, int mouseY, float delta, CallbackInfo ci) {
|
||||
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc == null || mc.level != null) return;
|
||||
MainConfig cfg = MainConfig.get();
|
||||
String mode = (cfg != null) ? cfg.getMenuBackgroundMode() : "off";
|
||||
if (mode == null || mode.equalsIgnoreCase("off")) return;
|
||||
|
||||
boolean aurora = mode.equalsIgnoreCase("aurora");
|
||||
MenuBackgroundRenderer.render(mc, aurora);
|
||||
ci.cancel();
|
||||
}
|
||||
|
||||
@Inject(method = "extractBackground(Lnet/minecraft/client/gui/GuiGraphicsExtractor;IIF)V", at = @At("HEAD"), cancellable = true)
|
||||
private void lovisual$noRender$cancelDarkening(GuiGraphicsExtractor context, int mouseX, int mouseY, float delta, CallbackInfo ci) {
|
||||
NoRender module = Modules.get(NoRender.class);
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (module != null && mc != null && mc.level != null && module.screenDarkeningDisabled()) {
|
||||
ci.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "extractTransparentBackground(Lnet/minecraft/client/gui/GuiGraphicsExtractor;)V", at = @At("HEAD"), cancellable = true)
|
||||
private void lovisual$noRender$cancelInGameBackground(GuiGraphicsExtractor context, CallbackInfo ci) {
|
||||
NoRender module = Modules.get(NoRender.class);
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (module != null && mc != null && mc.level != null && module.screenDarkeningDisabled()) {
|
||||
ci.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "extractPanorama", at = @At("HEAD"), cancellable = true, require = 0)
|
||||
private void lovisual$replaceTitlePanorama(GuiGraphicsExtractor context, float delta, CallbackInfo ci) {
|
||||
|
||||
Screen self = (Screen) (Object) this;
|
||||
if (!(self instanceof TitleScreen)) return;
|
||||
|
||||
MainConfig cfg = MainConfig.get();
|
||||
String mode = (cfg != null) ? cfg.getMenuBackgroundMode() : "off";
|
||||
if (mode == null || mode.equalsIgnoreCase("off")) return;
|
||||
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc == null) return;
|
||||
|
||||
boolean aurora = mode.equalsIgnoreCase("aurora");
|
||||
MenuBackgroundRenderer.render(mc, aurora);
|
||||
ci.cancel();
|
||||
}
|
||||
|
||||
@Inject(method = "extractRenderStateWithTooltipAndSubtitles(Lnet/minecraft/client/gui/GuiGraphicsExtractor;IIF)V", at = @At("HEAD"))
|
||||
private void lovisual$beginButtonQueue(GuiGraphicsExtractor ctx, int mouseX, int mouseY, float delta, CallbackInfo ci) {
|
||||
BetterButtons.beginFrame(ctx);
|
||||
}
|
||||
|
||||
@Inject(method = "extractRenderStateWithTooltipAndSubtitles(Lnet/minecraft/client/gui/GuiGraphicsExtractor;IIF)V", at = @At("HEAD"))
|
||||
private void lovisual$phaseScreenHead(GuiGraphicsExtractor ctx, int mouseX, int mouseY, float delta, CallbackInfo ci) {
|
||||
Screen self = (Screen) (Object) this;
|
||||
String label = "2d:screen:" + self.getClass().getSimpleName();
|
||||
LoVisualRenderSystem.ensureFrameContext();
|
||||
lovisual$screenPhaseScope = LoVisualRenderSystem.phase(RenderPhase.SCREEN, label);
|
||||
}
|
||||
|
||||
@Inject(method = "extractRenderStateWithTooltipAndSubtitles(Lnet/minecraft/client/gui/GuiGraphicsExtractor;IIF)V", at = @At("RETURN"))
|
||||
private void lovisual$phaseScreenTail(GuiGraphicsExtractor ctx, int mouseX, int mouseY, float delta, CallbackInfo ci) {
|
||||
Screen self = (Screen) (Object) this;
|
||||
String label = "2d:screen:" + self.getClass().getSimpleName();
|
||||
if (lovisual$screenPhaseScope != null) {
|
||||
lovisual$screenPhaseScope.close();
|
||||
lovisual$screenPhaseScope = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.mixins.gui.screens;
|
||||
|
||||
import dev.loki.lovisual.util.screen.ClientScreen;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.screens.TitleScreen;
|
||||
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;
|
||||
import dev.loki.lovisual.features.gui.mainmenu.LoVisualMainMenuScreen;
|
||||
|
||||
@Mixin(TitleScreen.class)
|
||||
public abstract class TitleScreenMixin {
|
||||
|
||||
@Inject(method = "init", at = @At("HEAD"), cancellable = true)
|
||||
private void lovisual$replaceTitleScreen(CallbackInfo ci) {
|
||||
|
||||
if (LoVisualMainMenuScreen.shouldUseVanillaTitleScreen()) return;
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc == null) return;
|
||||
if (ClientScreen.current() instanceof LoVisualMainMenuScreen) return;
|
||||
ClientScreen.show(mc, new LoVisualMainMenuScreen());
|
||||
ci.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.mixins.gui.widgets;
|
||||
|
||||
import dev.loki.lovisual.util.screen.ClientScreen;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.GuiGraphicsExtractor;
|
||||
import net.minecraft.client.gui.components.AbstractWidget;
|
||||
import net.minecraft.client.gui.components.Button;
|
||||
import net.minecraft.client.gui.screens.inventory.MerchantScreen;
|
||||
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;
|
||||
import dev.loki.lovisual.features.gui.hud.nondraggable.impl.buttons.BetterButtons;
|
||||
|
||||
@Mixin(Button.Plain.class)
|
||||
public abstract class ButtonWidgetTextMixin {
|
||||
|
||||
@Inject(method = "extractContents(Lnet/minecraft/client/gui/GuiGraphicsExtractor;IIF)V", at = @At("HEAD"), cancellable = true)
|
||||
private void lovisual$drawIcon(GuiGraphicsExtractor ctx, int mouseX, int mouseY, float delta, CallbackInfo ci) {
|
||||
BetterButtons buttons = BetterButtons.get();
|
||||
if (buttons == null || !buttons.useUiButtons()) return;
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc != null && ClientScreen.current() instanceof MerchantScreen) return;
|
||||
AbstractWidget widget = (AbstractWidget) (Object) this;
|
||||
boolean hovered = widget.isMouseOver(mouseX, mouseY);
|
||||
float hover = BetterButtons.updateHover(widget, hovered, mouseX, mouseY);
|
||||
BetterButtons.enqueueButton(widget, widget.getMessage(), hover, widget.isActive(), widget.isFocused());
|
||||
if (hovered) {
|
||||
BetterButtons.captureTooltip(widget, mouseX, mouseY);
|
||||
}
|
||||
ci.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.mixins.gui.widgets;
|
||||
|
||||
import dev.loki.lovisual.util.screen.ClientScreen;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.GuiGraphicsExtractor;
|
||||
import net.minecraft.client.gui.components.AbstractWidget;
|
||||
import net.minecraft.client.gui.components.CycleButton;
|
||||
import net.minecraft.client.gui.screens.inventory.MerchantScreen;
|
||||
import net.minecraft.resources.Identifier;
|
||||
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;
|
||||
import dev.loki.lovisual.features.gui.hud.nondraggable.impl.buttons.BetterButtons;
|
||||
import dev.loki.lovisual.mixins.accessors.widget.CyclingButtonWidgetAccessor;
|
||||
|
||||
@Mixin(CycleButton.class)
|
||||
public abstract class CycleButtonMixin<T> {
|
||||
|
||||
@Inject(method = "extractContents(Lnet/minecraft/client/gui/GuiGraphicsExtractor;IIF)V", at = @At("HEAD"), cancellable = true)
|
||||
private void lovisual$drawIcon(GuiGraphicsExtractor ctx, int mouseX, int mouseY, float delta, CallbackInfo ci) {
|
||||
BetterButtons buttons = BetterButtons.get();
|
||||
if (buttons == null || !buttons.useUiButtons()) return;
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc != null && ClientScreen.current() instanceof MerchantScreen) return;
|
||||
CycleButton<T> self = (CycleButton<T>) (Object) this;
|
||||
AbstractWidget widget = self;
|
||||
|
||||
boolean hovered = widget.isMouseOver(mouseX, mouseY);
|
||||
float hover = BetterButtons.updateHover(widget, hovered, mouseX, mouseY);
|
||||
|
||||
CyclingButtonWidgetAccessor<T> accessor = (CyclingButtonWidgetAccessor<T>) self;
|
||||
Identifier icon = accessor.lovisual$getIcon() != null
|
||||
? accessor.lovisual$getIcon().apply(self, self.getValue())
|
||||
: null;
|
||||
|
||||
boolean showLabel = accessor.lovisual$getLabelType() != CycleButton.DisplayState.HIDE;
|
||||
BetterButtons.enqueueCyclingButton(self, icon, showLabel, hover, widget.isActive(), widget.isFocused());
|
||||
if (hovered) {
|
||||
BetterButtons.captureTooltip(widget, mouseX, mouseY);
|
||||
}
|
||||
|
||||
ci.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.mixins.gui.widgets;
|
||||
|
||||
import dev.loki.lovisual.util.screen.ClientScreen;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.GuiGraphicsExtractor;
|
||||
import net.minecraft.client.gui.components.AbstractWidget;
|
||||
import net.minecraft.client.gui.components.SpriteIconButton;
|
||||
import net.minecraft.client.gui.screens.inventory.MerchantScreen;
|
||||
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;
|
||||
import dev.loki.lovisual.features.gui.hud.nondraggable.impl.buttons.BetterButtons;
|
||||
|
||||
@Mixin(SpriteIconButton.CenteredIcon.class)
|
||||
public abstract class SpriteIconButtonCenteredIconMixin {
|
||||
|
||||
@Inject(method = "extractContents(Lnet/minecraft/client/gui/GuiGraphicsExtractor;IIF)V", at = @At("HEAD"), cancellable = true)
|
||||
private void lovisual$drawIcon(GuiGraphicsExtractor ctx, int mouseX, int mouseY, float delta, CallbackInfo ci) {
|
||||
BetterButtons buttons = BetterButtons.get();
|
||||
if (buttons == null || !buttons.useUiButtons()) return;
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc != null && ClientScreen.current() instanceof MerchantScreen) return;
|
||||
SpriteIconButton self = (SpriteIconButton) (Object) this;
|
||||
AbstractWidget widget = self;
|
||||
|
||||
boolean hovered = widget.isMouseOver(mouseX, mouseY);
|
||||
float hover = BetterButtons.updateHover(widget, hovered, mouseX, mouseY);
|
||||
|
||||
BetterButtons.enqueueTextIconButton(self, true, hover, widget.isActive(), widget.isFocused());
|
||||
if (hovered) {
|
||||
BetterButtons.captureTooltip(widget, mouseX, mouseY);
|
||||
}
|
||||
|
||||
ci.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.mixins.gui.widgets;
|
||||
|
||||
import dev.loki.lovisual.util.screen.ClientScreen;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.GuiGraphicsExtractor;
|
||||
import net.minecraft.client.gui.components.AbstractWidget;
|
||||
import net.minecraft.client.gui.components.SpriteIconButton;
|
||||
import net.minecraft.client.gui.screens.inventory.MerchantScreen;
|
||||
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;
|
||||
import dev.loki.lovisual.features.gui.hud.nondraggable.impl.buttons.BetterButtons;
|
||||
|
||||
@Mixin(SpriteIconButton.TextAndIcon.class)
|
||||
public abstract class SpriteIconButtonTextAndIconMixin {
|
||||
|
||||
@Inject(method = "extractContents(Lnet/minecraft/client/gui/GuiGraphicsExtractor;IIF)V", at = @At("HEAD"), cancellable = true)
|
||||
private void lovisual$drawIcon(GuiGraphicsExtractor ctx, int mouseX, int mouseY, float delta, CallbackInfo ci) {
|
||||
BetterButtons buttons = BetterButtons.get();
|
||||
if (buttons == null || !buttons.useUiButtons()) return;
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc != null && ClientScreen.current() instanceof MerchantScreen) return;
|
||||
SpriteIconButton self = (SpriteIconButton) (Object) this;
|
||||
AbstractWidget widget = self;
|
||||
|
||||
boolean hovered = widget.isMouseOver(mouseX, mouseY);
|
||||
float hover = BetterButtons.updateHover(widget, hovered, mouseX, mouseY);
|
||||
|
||||
BetterButtons.enqueueTextIconButton(self, false, hover, widget.isActive(), widget.isFocused());
|
||||
if (hovered) {
|
||||
BetterButtons.captureTooltip(widget, mouseX, mouseY);
|
||||
}
|
||||
|
||||
ci.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,141 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.mixins.hud;
|
||||
|
||||
import net.minecraft.client.gui.components.DebugScreenOverlay;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.ModifyArg;
|
||||
import dev.loki.lovisual.config.i18n.MainConfig;
|
||||
import dev.loki.lovisual.render.engine.core.system.LoVisualRenderSystem;
|
||||
import dev.loki.lovisual.render.engine.debug.RenderThread2DDebugRenderer;
|
||||
import dev.loki.lovisual.render.engine.msaa.MsaaWorldTarget;
|
||||
import dev.loki.lovisual.render.engine.renderer.Renderer2D;
|
||||
import dev.loki.lovisual.render.engine.text.font.info.FontDebugStats;
|
||||
import dev.loki.lovisual.util.logging.DebugMode;
|
||||
import dev.loki.lovisual.util.wav.util.WavDebugStats;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Mixin(DebugScreenOverlay.class)
|
||||
public abstract class DebugHudMixin {
|
||||
@ModifyArg(
|
||||
method = "extractRenderState",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/client/gui/components/DebugScreenOverlay;extractLines(Lnet/minecraft/client/gui/GuiGraphicsExtractor;Ljava/util/List;Z)V",
|
||||
ordinal = 0
|
||||
),
|
||||
index = 1
|
||||
)
|
||||
private List<String> lovisual$addEngineInfo(List<String> list) {
|
||||
if (list == null) return list;
|
||||
DebugMode debugMode = MainConfig.get().getDebugMode();
|
||||
if (debugMode != DebugMode.ALL && debugMode != DebugMode.RENDER_THREAD) return list;
|
||||
|
||||
int samples = MainConfig.get().getMsaa3dSamples();
|
||||
boolean supported = LoVisualRenderSystem.rhi().msaa().supported();
|
||||
MsaaWorldTarget.DebugStatus msaaStatus = MsaaWorldTarget.debugStatus();
|
||||
String msaa;
|
||||
if (samples <= 1) {
|
||||
msaa = "msaa: off";
|
||||
} else if (supported) {
|
||||
msaa = String.format("msaa: cfg=%dx target=%dx %s %dx%d alloc=%s active=%s resolve=%s",
|
||||
samples,
|
||||
msaaStatus.targetSamples(),
|
||||
msaaStatus.state(),
|
||||
msaaStatus.width(),
|
||||
msaaStatus.height(),
|
||||
msaaStatus.allocated() ? "yes" : "no",
|
||||
msaaStatus.active() ? "yes" : "no",
|
||||
msaaStatus.lastResolveOk() ? "ok" : "no"
|
||||
);
|
||||
} else {
|
||||
msaa = "msaa: unavailable";
|
||||
}
|
||||
|
||||
List<String> out = new ArrayList<>(list.size() + 20);
|
||||
out.add("§cLoVisual Engine");
|
||||
out.add("status:§aok");
|
||||
out.add(msaa);
|
||||
|
||||
RenderThread2DDebugRenderer.Snapshot debug2d = RenderThread2DDebugRenderer.snapshot();
|
||||
out.add(String.format("2d rt gui: frame=%d gui=%dx%d rects=%d err=%s",
|
||||
debug2d.guiStateFrame(),
|
||||
debug2d.guiWidth(),
|
||||
debug2d.guiHeight(),
|
||||
debug2d.guiStateRects(),
|
||||
debug2d.guiStateError() == null || debug2d.guiStateError().isBlank() ? "none" : debug2d.guiStateError()
|
||||
));
|
||||
out.add(String.format("2d rt imm: frame=%d fb=%dx%d draws=%d/%d v=%d i=%d err=%s",
|
||||
debug2d.immediateFrame(),
|
||||
debug2d.framebufferWidth(),
|
||||
debug2d.framebufferHeight(),
|
||||
debug2d.immediateDraws(),
|
||||
debug2d.immediateBatches(),
|
||||
debug2d.immediateVertices(),
|
||||
debug2d.immediateIndices(),
|
||||
debug2d.immediateError() == null || debug2d.immediateError().isBlank() ? "none" : debug2d.immediateError()
|
||||
));
|
||||
|
||||
Renderer2D.BatchStats batchStats = Renderer2D.getBatchStats();
|
||||
out.add(String.format("ui batch: %s, last %d draws / %d batches",
|
||||
batchStats.isActive() ? "active" : "idle",
|
||||
batchStats.getLastDraws(),
|
||||
batchStats.getLastOrder()
|
||||
));
|
||||
out.add(String.format("ui batch frame: %d draws / %d batches",
|
||||
batchStats.getFrameDraws(),
|
||||
batchStats.getFrameBatches()
|
||||
));
|
||||
out.add(String.format("ui batch geom: v=%d, i=%d, pool=%d",
|
||||
batchStats.getLastVertices(),
|
||||
batchStats.getLastIndices(),
|
||||
batchStats.getPoolTotal()
|
||||
));
|
||||
out.add(String.format("ui batch frame geom: v=%d, i=%d",
|
||||
batchStats.getFrameVertices(),
|
||||
batchStats.getFrameIndices()
|
||||
));
|
||||
if (batchStats.getLastError() != null && !batchStats.getLastError().isBlank()) {
|
||||
out.add("ui batch warn: " + batchStats.getLastError());
|
||||
}
|
||||
|
||||
out.add(String.format("text msdf: ok=%d, fb=%d, attempts=%d",
|
||||
FontDebugStats.getMsdfSuccess(),
|
||||
FontDebugStats.getMsdfFallbacks(),
|
||||
FontDebugStats.getMsdfAttempts()
|
||||
));
|
||||
if (!FontDebugStats.getLastMsdfFallback().isBlank()) {
|
||||
String reason = FontDebugStats.getLastMsdfFallbackReason();
|
||||
out.add("text msdf last fb: " + FontDebugStats.getLastMsdfFallback()
|
||||
+ (reason.isBlank() ? "" : " (" + reason + ")"));
|
||||
}
|
||||
|
||||
out.add(String.format("wav(al): buffers %d/%d, sources %d/%d",
|
||||
WavDebugStats.getBuffersAlive(),
|
||||
WavDebugStats.getBuffersCreated(),
|
||||
WavDebugStats.getSourcesAlive(),
|
||||
WavDebugStats.getSourcesCreated()
|
||||
));
|
||||
out.add(String.format("wav(al): allocs=%d (buf=%d, src=%d)",
|
||||
WavDebugStats.getBuffersCreated() + WavDebugStats.getSourcesCreated(),
|
||||
WavDebugStats.getBuffersCreated(),
|
||||
WavDebugStats.getSourcesCreated()
|
||||
));
|
||||
out.add(String.format("wav(al): uploads=%d, bytes=%.2f MB",
|
||||
WavDebugStats.getBufferUploads(),
|
||||
WavDebugStats.getBufferBytes() / (1024.0 * 1024.0)
|
||||
));
|
||||
out.add("");
|
||||
out.addAll(list);
|
||||
return out;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,311 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.mixins.hud;
|
||||
|
||||
import dev.loki.lovisual.util.screen.ClientScreen;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.GuiGraphicsExtractor;
|
||||
import net.minecraft.client.gui.screens.Screen;
|
||||
import net.minecraft.client.gui.screens.inventory.tooltip.ClientTooltipPositioner;
|
||||
import net.minecraft.client.renderer.RenderPipelines;
|
||||
import net.minecraft.util.FormattedCharSequence;
|
||||
import net.minecraft.util.Mth;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.Items;
|
||||
import org.joml.Matrix3x2fStack;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||
import org.spongepowered.asm.mixin.gen.Invoker;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
import dev.loki.lovisual.features.gui.hud.base.CooldownRender;
|
||||
import dev.loki.lovisual.features.gui.hud.nondraggable.impl.tooltips.BetterTooltips;
|
||||
import dev.loki.lovisual.features.module.lifecycle.Modules;
|
||||
import dev.loki.lovisual.features.module.modules.combat.pvp.PvpCooldowns;
|
||||
import dev.loki.lovisual.mixininterface.render.IGuiGraphics;
|
||||
import dev.loki.lovisual.render.engine.core.context.ViewportContext;
|
||||
import dev.loki.lovisual.render.engine.renderer.Renderer2D;
|
||||
import dev.loki.lovisual.render.engine.renderer.ui.deferred.sched.HudDeferredGuiElement;
|
||||
import net.minecraft.client.gui.navigation.ScreenRectangle;
|
||||
import net.minecraft.client.renderer.state.gui.GuiRenderState;
|
||||
import dev.loki.lovisual.util.pvp.cooldown.CooldownRegistry;
|
||||
import dev.loki.lovisual.util.pvp.cooldown.ItemCooldownSnapshot;
|
||||
import dev.loki.lovisual.util.pvp.state.PvpState;
|
||||
import dev.loki.lovisual.util.pvp.client.CooldownsState;
|
||||
import dev.loki.lovisual.render.engine.text.font.Font;
|
||||
|
||||
@Mixin(GuiGraphicsExtractor.class)
|
||||
public abstract class GuiGraphicsMixin implements IGuiGraphics {
|
||||
@Final
|
||||
@Shadow
|
||||
private Matrix3x2fStack pose;
|
||||
|
||||
@Final
|
||||
@Shadow
|
||||
public GuiRenderState guiRenderState;
|
||||
|
||||
@Shadow
|
||||
public abstract int guiWidth();
|
||||
|
||||
@Shadow
|
||||
public abstract int guiHeight();
|
||||
|
||||
@Override
|
||||
public void lovisual$runUnscaled(Runnable task) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
double scale = mc.getWindow().getGuiScale();
|
||||
double uiScale = ViewportContext.getUiScale();
|
||||
float ratio = (float) (uiScale / scale);
|
||||
pose.scale(ratio, ratio);
|
||||
try {
|
||||
task.run();
|
||||
} finally {
|
||||
pose.scale((float) (scale / uiScale), (float) (scale / uiScale));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void lovisual$withTransform(float tx, float ty, float angleRad, Runnable task) {
|
||||
pose.pushMatrix();
|
||||
try {
|
||||
pose.translate(tx, ty);
|
||||
pose.rotate(angleRad);
|
||||
task.run();
|
||||
} finally {
|
||||
pose.popMatrix();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void lovisual$addDeferredHudMarker(Renderer2D.Deferred2DLayer layer) {
|
||||
if (layer == null || guiRenderState == null) return;
|
||||
int width = Math.max(1, guiWidth());
|
||||
int height = Math.max(1, guiHeight());
|
||||
guiRenderState.addGlyphToCurrentLayer(new HudDeferredGuiElement(layer, new ScreenRectangle(0, 0, width, height)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void lovisual$drawItemBar(ItemStack stack, int x, int y) {
|
||||
lovisual$invokeDrawItemBar(stack, x, y);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void lovisual$drawCooldownProgress(ItemStack stack, int x, int y) {
|
||||
lovisual$invokeDrawCooldownProgress(stack, x, y);
|
||||
}
|
||||
|
||||
@Invoker("itemBar")
|
||||
public abstract void lovisual$invokeDrawItemBar(ItemStack stack, int x, int y);
|
||||
|
||||
@Invoker("itemCooldown")
|
||||
public abstract void lovisual$invokeDrawCooldownProgress(ItemStack stack, int x, int y);
|
||||
|
||||
@Accessor("deferredTooltip")
|
||||
public abstract Runnable lovisual$getTooltipDrawer();
|
||||
|
||||
@Accessor("deferredTooltip")
|
||||
public abstract void lovisual$setTooltipDrawer(Runnable drawer);
|
||||
|
||||
@Inject(method = "extractDeferredElements", at = @At("HEAD"))
|
||||
private void lovisual$suppressVanillaTooltip(int mouseX, int mouseY, float tickDelta, CallbackInfo ci) {
|
||||
if (!BetterTooltips.hasTooltip()) return;
|
||||
if (lovisual$getTooltipDrawer() == null) return;
|
||||
lovisual$setTooltipDrawer(null);
|
||||
}
|
||||
|
||||
@Inject(
|
||||
method = "setTooltipForNextFrame(Lnet/minecraft/client/gui/Font;Ljava/util/List;Lnet/minecraft/client/gui/screens/inventory/tooltip/ClientTooltipPositioner;IIZ)V",
|
||||
at = @At("HEAD"),
|
||||
cancellable = true
|
||||
)
|
||||
private void lovisual$drawCustomTooltip(net.minecraft.client.gui.Font tr,
|
||||
java.util.List<FormattedCharSequence> lines,
|
||||
ClientTooltipPositioner positioner,
|
||||
int x, int y, boolean focused,
|
||||
CallbackInfo ci) {
|
||||
BetterTooltips tooltips = BetterTooltips.get();
|
||||
if (tooltips == null || !tooltips.useCustomGuiTooltips()) return;
|
||||
if (lines == null || lines.isEmpty()) return;
|
||||
BetterTooltips.setDrawContext((GuiGraphicsExtractor) (Object) this);
|
||||
BetterTooltips.captureTooltipOrdered(lines, positioner, x, y);
|
||||
ci.cancel();
|
||||
}
|
||||
|
||||
@Inject(
|
||||
method = "setTooltipForNextFrame(Lnet/minecraft/client/gui/Font;Lnet/minecraft/world/item/ItemStack;II)V",
|
||||
at = @At("HEAD"),
|
||||
cancellable = true
|
||||
)
|
||||
private void lovisual$drawCustomItemTooltip(net.minecraft.client.gui.Font tr,
|
||||
ItemStack stack,
|
||||
int x, int y,
|
||||
CallbackInfo ci) {
|
||||
BetterTooltips tooltips = BetterTooltips.get();
|
||||
if (tooltips == null || !tooltips.useCustomItemTooltips()) return;
|
||||
if (stack == null || stack.isEmpty()) return;
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc == null) return;
|
||||
java.util.List<net.minecraft.network.chat.Component> textLines = Screen.getTooltipFromItem(mc, stack);
|
||||
if (textLines == null || textLines.isEmpty()) return;
|
||||
java.util.List<FormattedCharSequence> ordered = new java.util.ArrayList<>();
|
||||
for (net.minecraft.network.chat.Component t : textLines) {
|
||||
ordered.addAll(tr.split(t, 200));
|
||||
}
|
||||
BetterTooltips.captureItemTooltipOrdered(
|
||||
ordered,
|
||||
net.minecraft.client.gui.screens.inventory.tooltip.DefaultTooltipPositioner.INSTANCE,
|
||||
x, y,
|
||||
stack,
|
||||
(GuiGraphicsExtractor) (Object) this
|
||||
);
|
||||
ci.cancel();
|
||||
}
|
||||
|
||||
@Inject(
|
||||
method = "setTooltipForNextFrame(Lnet/minecraft/client/gui/Font;Ljava/util/List;Ljava/util/Optional;IILnet/minecraft/resources/Identifier;)V",
|
||||
at = @At("HEAD"),
|
||||
cancellable = true
|
||||
)
|
||||
private void lovisual$drawCustomTooltipWithData(net.minecraft.client.gui.Font tr,
|
||||
java.util.List<net.minecraft.network.chat.Component> lines,
|
||||
java.util.Optional<net.minecraft.world.inventory.tooltip.TooltipComponent> data,
|
||||
int x, int y,
|
||||
net.minecraft.resources.Identifier style,
|
||||
CallbackInfo ci) {
|
||||
BetterTooltips tooltips = BetterTooltips.get();
|
||||
if (tooltips == null || !tooltips.useCustomItemTooltips()) return;
|
||||
if (lines == null || lines.isEmpty()) return;
|
||||
java.util.List<FormattedCharSequence> ordered = new java.util.ArrayList<>();
|
||||
for (net.minecraft.network.chat.Component t : lines) {
|
||||
ordered.addAll(tr.split(t, 200));
|
||||
}
|
||||
BetterTooltips.setDrawContext((GuiGraphicsExtractor) (Object) this);
|
||||
ItemStack stack = BetterTooltips.consumeLastTooltipStack();
|
||||
if (stack != null && !stack.isEmpty()) {
|
||||
BetterTooltips.captureItemTooltipOrdered(
|
||||
ordered,
|
||||
net.minecraft.client.gui.screens.inventory.tooltip.DefaultTooltipPositioner.INSTANCE,
|
||||
x, y,
|
||||
stack,
|
||||
(GuiGraphicsExtractor) (Object) this
|
||||
);
|
||||
} else {
|
||||
BetterTooltips.captureTooltipOrdered(ordered, net.minecraft.client.gui.screens.inventory.tooltip.DefaultTooltipPositioner.INSTANCE, x, y);
|
||||
}
|
||||
ci.cancel();
|
||||
}
|
||||
|
||||
@Inject(
|
||||
method = "setTooltipForNextFrame(Lnet/minecraft/client/gui/Font;Ljava/util/List;Ljava/util/Optional;II)V",
|
||||
at = @At("HEAD"),
|
||||
cancellable = true
|
||||
)
|
||||
private void lovisual$drawCustomTooltipWithDataNoStyle(net.minecraft.client.gui.Font tr,
|
||||
java.util.List<net.minecraft.network.chat.Component> lines,
|
||||
java.util.Optional<net.minecraft.world.inventory.tooltip.TooltipComponent> data,
|
||||
int x, int y,
|
||||
CallbackInfo ci) {
|
||||
BetterTooltips tooltips = BetterTooltips.get();
|
||||
if (tooltips == null || !tooltips.useCustomItemTooltips()) return;
|
||||
if (lines == null || lines.isEmpty()) return;
|
||||
java.util.List<FormattedCharSequence> ordered = new java.util.ArrayList<>();
|
||||
for (net.minecraft.network.chat.Component t : lines) {
|
||||
ordered.addAll(tr.split(t, 200));
|
||||
}
|
||||
BetterTooltips.setDrawContext((GuiGraphicsExtractor) (Object) this);
|
||||
ItemStack stack = BetterTooltips.consumeLastTooltipStack();
|
||||
if (stack != null && !stack.isEmpty()) {
|
||||
BetterTooltips.captureItemTooltipOrdered(
|
||||
ordered,
|
||||
net.minecraft.client.gui.screens.inventory.tooltip.DefaultTooltipPositioner.INSTANCE,
|
||||
x, y,
|
||||
stack,
|
||||
(GuiGraphicsExtractor) (Object) this
|
||||
);
|
||||
} else {
|
||||
BetterTooltips.captureTooltipOrdered(ordered, net.minecraft.client.gui.screens.inventory.tooltip.DefaultTooltipPositioner.INSTANCE, x, y);
|
||||
}
|
||||
ci.cancel();
|
||||
}
|
||||
|
||||
@Inject(method = "itemCooldown", at = @At("HEAD"), cancellable = true)
|
||||
private void lovisual$renderCustomPvpCooldown(ItemStack stack, int x, int y, CallbackInfo ci) {
|
||||
if (stack.isEmpty()) return;
|
||||
PvpCooldowns cooldowns = Modules.get(PvpCooldowns.class);
|
||||
if (cooldowns == null || !cooldowns.isSystemEnabled()) return;
|
||||
|
||||
if (stack.is(Items.ENDER_PEARL) || stack.is(Items.CHORUS_FRUIT)) {
|
||||
if (!PvpState.isActive()) return;
|
||||
Float secondsLeft = PvpState.getSecondsLeft();
|
||||
if (secondsLeft == null || secondsLeft <= 0f) return;
|
||||
Float maxSeconds = PvpState.getMaxSeconds();
|
||||
float total = (maxSeconds == null || maxSeconds <= 0f) ? secondsLeft : maxSeconds;
|
||||
float progress = Mth.clamp(secondsLeft / total, 0f, 1f);
|
||||
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
GuiGraphicsExtractor ctx = (GuiGraphicsExtractor) (Object) this;
|
||||
|
||||
float secondsLeftValue = secondsLeft;
|
||||
float displaySeconds = secondsLeftValue >= 10f
|
||||
? (float) ((int) secondsLeftValue)
|
||||
: Math.round(secondsLeftValue * 10f) / 10f;
|
||||
|
||||
int top = y + Mth.floor(16.0F * (1.0F - progress));
|
||||
int bottom = top + Mth.ceil(16.0F * progress);
|
||||
|
||||
int color = 0x80FF5656;
|
||||
ctx.fill(RenderPipelines.GUI, x, top, x + 16, bottom, color);
|
||||
|
||||
CooldownRender.renderTime(
|
||||
ctx, progress, x, y, displaySeconds
|
||||
);
|
||||
|
||||
if (ClientScreen.current() == null) {
|
||||
ci.cancel();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!cooldowns.shouldRenderSlots()) return;
|
||||
if (!CooldownRegistry.isTracked(stack.getItem())) return;
|
||||
|
||||
ItemCooldownSnapshot snapshot = CooldownsState.MANAGER.snapshot(stack.getItem());
|
||||
if (!snapshot.visible()) return;
|
||||
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
GuiGraphicsExtractor ctx = (GuiGraphicsExtractor) (Object) this;
|
||||
|
||||
float progress = snapshot.cooling() ? snapshot.cooldownProgress() : snapshot.usesProgress();
|
||||
if (progress <= 0f) return;
|
||||
|
||||
float secondsLeft = snapshot.cooling() ? snapshot.cooldownRemainingMs() / 1000.0f : snapshot.useWindowRemainingMs() / 1000.0f;
|
||||
float displaySeconds = snapshot.cooling()
|
||||
? (secondsLeft >= 10 ? (float) ((int) secondsLeft) : Math.round(secondsLeft * 10f) / 10f)
|
||||
: 0f;
|
||||
|
||||
int top = y + Mth.floor(16.0F * (1.0F - progress));
|
||||
int bottom = top + Mth.ceil(16.0F * progress);
|
||||
|
||||
int color = snapshot.cooling()
|
||||
? (CooldownsState.MANAGER.isInPvp() ? 0x80FFA500 : 0x8000A0FF)
|
||||
: 0x706D9DFF;
|
||||
|
||||
ctx.fill(RenderPipelines.GUI, x, top, x + 16, bottom, color);
|
||||
|
||||
if (snapshot.cooling()) {
|
||||
CooldownRender.renderTime(ctx, progress, x, y, displaySeconds);
|
||||
}
|
||||
|
||||
if (ClientScreen.current() == null) {
|
||||
ci.cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
429
mod/src/main/java/dev/loki/lovisual/mixins/hud/GuiMixin.java
Normal file
429
mod/src/main/java/dev/loki/lovisual/mixins/hud/GuiMixin.java
Normal file
|
|
@ -0,0 +1,429 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.mixins.hud;
|
||||
|
||||
import net.minecraft.client.DeltaTracker;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.GuiGraphicsExtractor;
|
||||
import net.minecraft.client.gui.Hud;
|
||||
import net.minecraft.client.player.LocalPlayer;
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.network.chat.MutableComponent;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.ai.attributes.Attribute;
|
||||
import net.minecraft.world.entity.ai.attributes.Attributes;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.scores.Objective;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
import dev.loki.lovisual.events.Events;
|
||||
import dev.loki.lovisual.events.impl.pvp.PvpOverlayEvent;
|
||||
import dev.loki.lovisual.features.gui.hud.draggable.impl.panels.Scoreboard;
|
||||
import dev.loki.lovisual.features.gui.hud.draggable.DraggableHudElementRegistry;
|
||||
import dev.loki.lovisual.features.gui.hud.draggable.impl.status.Potions;
|
||||
import dev.loki.lovisual.features.gui.hud.nondraggable.impl.buttons.SwapTooltip;
|
||||
import dev.loki.lovisual.features.gui.hud.nondraggable.impl.bars.CustomBar;
|
||||
import dev.loki.lovisual.features.gui.hud.nondraggable.impl.bars.CustomHealthBar;
|
||||
import dev.loki.lovisual.features.gui.hud.nondraggable.impl.hotbar.CustomHotbar;
|
||||
import dev.loki.lovisual.features.module.lifecycle.Modules;
|
||||
import dev.loki.lovisual.features.module.modules.misc.protect.MessageFilter;
|
||||
import dev.loki.lovisual.features.module.modules.visuals.player.Crosshair;
|
||||
import dev.loki.lovisual.features.module.modules.visuals.visibility.NoRender;
|
||||
import dev.loki.lovisual.mixininterface.render.IGuiGraphics;
|
||||
import dev.loki.lovisual.render.engine.core.system.LoVisualRenderSystem;
|
||||
import dev.loki.lovisual.render.engine.core.frame.RenderPhase;
|
||||
import dev.loki.lovisual.render.engine.core.frame.RenderPhaseScope;
|
||||
import dev.loki.lovisual.render.engine.core.context.ViewportContext;
|
||||
// import dev.loki.lovisual.render.engine.debug.RenderThread2DDebugRenderer; // disabled: debug probes are manual-only
|
||||
import dev.loki.lovisual.render.engine.renderer.Renderer2D;
|
||||
import dev.loki.lovisual.Lifecycle;
|
||||
import dev.loki.lovisual.util.item.food.IllegalItemUtil;
|
||||
import dev.loki.lovisual.util.item.rarity.RarityColorUtil;
|
||||
import dev.loki.lovisual.util.item.enchant.TopEnchantUtil;
|
||||
import dev.loki.lovisual.util.logging.DebugLog;
|
||||
import dev.loki.lovisual.util.text.format.TextJsonUtil;
|
||||
|
||||
@Mixin(Hud.class)
|
||||
public abstract class GuiMixin {
|
||||
|
||||
@Shadow
|
||||
private int toolHighlightTimer;
|
||||
@Shadow
|
||||
private net.minecraft.world.item.ItemStack lastToolHighlight;
|
||||
@Shadow
|
||||
@Final
|
||||
private Minecraft minecraft;
|
||||
|
||||
@Inject(method = "extractRenderState(Lnet/minecraft/client/gui/GuiGraphicsExtractor;Lnet/minecraft/client/DeltaTracker;)V", at = @At("TAIL"))
|
||||
private void lovisual$debug2dGuiStateProbe(GuiGraphicsExtractor ctx, DeltaTracker tickCounter, CallbackInfo ci) {
|
||||
// RenderThread2DDebugRenderer.extractGuiStateProbe(ctx);
|
||||
}
|
||||
|
||||
@Unique
|
||||
private static void lovisual$renderCustomBarOnly(GuiGraphicsExtractor ctx,
|
||||
DeltaTracker tickCounter,
|
||||
Minecraft mc,
|
||||
CustomBar bar) {
|
||||
boolean useJump = CustomBar.shouldUseJumpBar(mc);
|
||||
boolean useLocator = !useJump && CustomBar.shouldUseLocator(mc, bar);
|
||||
boolean drawXp = !useJump && !useLocator;
|
||||
|
||||
LoVisualRenderSystem.ensureFrameContext();
|
||||
try (RenderPhaseScope ignored = LoVisualRenderSystem.phase(RenderPhase.HUD_MAIN, "2d:hud:custom_bar")) {
|
||||
Renderer2D.withDeferredLayer(LOVISUAL$VANILLA_HUD_REPLACEMENT_LAYER, () -> {
|
||||
ViewportContext.beginUnscaled(ctx);
|
||||
Renderer2D.COLOR.begin();
|
||||
if (drawXp) {
|
||||
CustomBar.renderXpBarBackground(Renderer2D.COLOR, mc);
|
||||
}
|
||||
if (useJump) {
|
||||
CustomBar.renderJumpBarBackground(Renderer2D.COLOR, mc);
|
||||
}
|
||||
if (useLocator) {
|
||||
CustomBar.renderLocatorBackground(Renderer2D.COLOR, mc);
|
||||
}
|
||||
Renderer2D.COLOR.render();
|
||||
ViewportContext.end(ctx);
|
||||
|
||||
if (drawXp) {
|
||||
CustomBar.renderXpBarText(ctx, mc);
|
||||
}
|
||||
});
|
||||
lovisual$appendVanillaHudReplacementMarker(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
@Unique
|
||||
private static final Renderer2D.Deferred2DLayer LOVISUAL$VANILLA_HUD_REPLACEMENT_LAYER =
|
||||
Renderer2D.Deferred2DLayer.HUD_AFTER_MISC_OVERLAYS;
|
||||
|
||||
@Unique
|
||||
private static void lovisual$appendVanillaHudReplacementMarker(GuiGraphicsExtractor ctx) {
|
||||
if (!(ctx instanceof IGuiGraphics graphics)) return;
|
||||
ctx.nextStratum();
|
||||
graphics.lovisual$addDeferredHudMarker(LOVISUAL$VANILLA_HUD_REPLACEMENT_LAYER);
|
||||
ctx.nextStratum();
|
||||
}
|
||||
|
||||
@Unique
|
||||
private static int argbFrom(float[] rgba) {
|
||||
if (rgba == null || rgba.length < 3) return 0xFFFFFFFF;
|
||||
float r = rgba[0], g = rgba[1], b = rgba[2];
|
||||
float a = rgba.length > 3 ? rgba[3] : 1f;
|
||||
int ai = (int) (a * 255) & 0xFF;
|
||||
int ri = (int) (r * 255) & 0xFF;
|
||||
int gi = (int) (g * 255) & 0xFF;
|
||||
int bi = (int) (b * 255) & 0xFF;
|
||||
return (ai << 24) | (ri << 16) | (gi << 8) | bi;
|
||||
}
|
||||
|
||||
@Inject(
|
||||
method = "displayScoreboardSidebar(Lnet/minecraft/client/gui/GuiGraphicsExtractor;Lnet/minecraft/world/scores/Objective;)V",
|
||||
at = @At("HEAD"),
|
||||
cancellable = true
|
||||
)
|
||||
private void lovisual$hideScoreboardSidebar(GuiGraphicsExtractor context, Objective objective, CallbackInfo ci) {
|
||||
|
||||
NoRender noRender = Modules.get(NoRender.class);
|
||||
if ((noRender != null && noRender.hideScoreboard()) || Scoreboard.shouldReplaceVanilla()) {
|
||||
ci.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(
|
||||
method = "setOverlayMessage(Lnet/minecraft/network/chat/Component;Z)V",
|
||||
at = @At("HEAD"),
|
||||
cancellable = true
|
||||
)
|
||||
private void lovisual$filterOverlayMessage(net.minecraft.network.chat.Component message, boolean tinted, CallbackInfo ci) {
|
||||
if (DebugLog.serverOnly()) {
|
||||
if (message == null) {
|
||||
// DebugLog.server("HUD overlay message: tinted=%s raw=null", tinted);
|
||||
} else {
|
||||
String raw = message.getString();
|
||||
String json = TextJsonUtil.toJson(message);
|
||||
// DebugLog.server("HUD overlay message: tinted=%s raw=\"%s\" json=%s", tinted, raw, json);
|
||||
}
|
||||
}
|
||||
if (Events.BUS.hasListeners(PvpOverlayEvent.class)) {
|
||||
Events.BUS.post(new PvpOverlayEvent(message, tinted));
|
||||
}
|
||||
|
||||
MessageFilter messageFilter = Modules.get(MessageFilter.class);
|
||||
if (messageFilter != null && messageFilter.shouldHideHud(message)) {
|
||||
ci.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(
|
||||
method = "extractEffects(Lnet/minecraft/client/gui/GuiGraphicsExtractor;Lnet/minecraft/client/DeltaTracker;)V",
|
||||
at = @At("HEAD"),
|
||||
cancellable = true
|
||||
)
|
||||
private void lovisual$hideVanillaPotions(GuiGraphicsExtractor context, DeltaTracker tickCounter, CallbackInfo ci) {
|
||||
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc == null || mc.player == null) return;
|
||||
if (!DraggableHudElementRegistry.isEnabled(Potions.class)) return;
|
||||
if (mc.player.getActiveEffects().isEmpty()) return;
|
||||
ci.cancel();
|
||||
}
|
||||
|
||||
@Inject(
|
||||
method = "extractCrosshair(Lnet/minecraft/client/gui/GuiGraphicsExtractor;Lnet/minecraft/client/DeltaTracker;)V",
|
||||
at = @At("HEAD"),
|
||||
cancellable = true
|
||||
)
|
||||
private void lovisual$replaceCrosshair(GuiGraphicsExtractor context, DeltaTracker tickCounter, CallbackInfo ci) {
|
||||
|
||||
Crosshair crosshair = Modules.get(Crosshair.class);
|
||||
if (crosshair != null && crosshair.shouldHideVanilla()) {
|
||||
ci.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(
|
||||
method = "extractVignette(Lnet/minecraft/client/gui/GuiGraphicsExtractor;Lnet/minecraft/world/entity/Entity;)V",
|
||||
at = @At("HEAD"),
|
||||
cancellable = true
|
||||
)
|
||||
private void lovisual$killVignette(GuiGraphicsExtractor context, Entity entity, CallbackInfo ci) {
|
||||
|
||||
// Completely remove vanilla vignette post-processing
|
||||
ci.cancel();
|
||||
}
|
||||
|
||||
@Inject(
|
||||
method = "extractItemHotbar(Lnet/minecraft/client/gui/GuiGraphicsExtractor;Lnet/minecraft/client/DeltaTracker;)V",
|
||||
at = @At("HEAD"),
|
||||
cancellable = true
|
||||
)
|
||||
private void lovisual$customHotbar(GuiGraphicsExtractor ctx, DeltaTracker tickCounter, CallbackInfo ci) {
|
||||
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc == null || mc.player == null) return;
|
||||
|
||||
CustomHotbar hotbar = CustomHotbar.get();
|
||||
CustomBar bar = CustomBar.get();
|
||||
if (hotbar == null && bar == null) return;
|
||||
|
||||
boolean hotbarEnabled = hotbar != null && hotbar.isHudHotbarEnabled();
|
||||
boolean barEnabled = bar != null && bar.isHudBarEnabled() && bar.isHotbarXpBarEnabled();
|
||||
if (!hotbarEnabled && !barEnabled) return;
|
||||
if (!hotbarEnabled) return;
|
||||
|
||||
boolean useJump = barEnabled && CustomBar.shouldUseJumpBar(mc);
|
||||
boolean useLocator = barEnabled && !useJump && CustomBar.shouldUseLocator(mc, bar);
|
||||
boolean drawXp = barEnabled && !useJump && !useLocator;
|
||||
|
||||
LoVisualRenderSystem.ensureFrameContext();
|
||||
try (RenderPhaseScope ignored = LoVisualRenderSystem.phase(RenderPhase.HUD_MAIN, "2d:hud:custom_hotbar")) {
|
||||
Renderer2D.withDeferredLayer(LOVISUAL$VANILLA_HUD_REPLACEMENT_LAYER, () -> {
|
||||
ViewportContext.beginUnscaled(ctx);
|
||||
Renderer2D.COLOR.begin();
|
||||
if (hotbarEnabled) {
|
||||
CustomHotbar.renderBackground(Renderer2D.COLOR, mc);
|
||||
}
|
||||
if (drawXp) {
|
||||
CustomBar.renderXpBarBackground(Renderer2D.COLOR, mc);
|
||||
}
|
||||
if (barEnabled && useJump) {
|
||||
CustomBar.renderJumpBarBackground(Renderer2D.COLOR, mc);
|
||||
}
|
||||
if (barEnabled && useLocator) {
|
||||
CustomBar.renderLocatorBackground(Renderer2D.COLOR, mc);
|
||||
}
|
||||
Renderer2D.COLOR.render();
|
||||
ViewportContext.end(ctx);
|
||||
|
||||
if (hotbarEnabled) {
|
||||
CustomHotbar.renderItems(ctx, tickCounter, mc);
|
||||
}
|
||||
});
|
||||
lovisual$appendVanillaHudReplacementMarker(ctx);
|
||||
|
||||
if (drawXp) {
|
||||
CustomBar.renderXpBarText(ctx, mc);
|
||||
}
|
||||
if (hotbarEnabled) {
|
||||
ci.cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(
|
||||
method = "extractItemHotbar(Lnet/minecraft/client/gui/GuiGraphicsExtractor;Lnet/minecraft/client/DeltaTracker;)V",
|
||||
at = @At("TAIL")
|
||||
)
|
||||
private void lovisual$customBarAfterVanillaHotbar(GuiGraphicsExtractor ctx, DeltaTracker tickCounter, CallbackInfo ci) {
|
||||
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc == null || mc.player == null) return;
|
||||
|
||||
CustomHotbar hotbar = CustomHotbar.get();
|
||||
if (hotbar != null && hotbar.isHudHotbarEnabled()) return;
|
||||
|
||||
CustomBar bar = CustomBar.get();
|
||||
if (bar == null || !bar.isHudBarEnabled() || !bar.isHotbarXpBarEnabled()) return;
|
||||
|
||||
lovisual$renderCustomBarOnly(ctx, tickCounter, mc, bar);
|
||||
}
|
||||
|
||||
@Inject(
|
||||
method = "extractPortalOverlay(Lnet/minecraft/client/gui/GuiGraphicsExtractor;F)V",
|
||||
at = @At("HEAD"),
|
||||
cancellable = true
|
||||
)
|
||||
private void lovisual$disableVanillaPortalOverlay(GuiGraphicsExtractor context, float nauseaStrength, CallbackInfo ci) {
|
||||
|
||||
ci.cancel();
|
||||
}
|
||||
|
||||
@Inject(
|
||||
method = "extractSleepOverlay(Lnet/minecraft/client/gui/GuiGraphicsExtractor;Lnet/minecraft/client/DeltaTracker;)V",
|
||||
at = @At("HEAD"),
|
||||
cancellable = true
|
||||
)
|
||||
private void lovisual$disableVanillaSleepOverlay(GuiGraphicsExtractor context, DeltaTracker tickCounter, CallbackInfo ci) {
|
||||
|
||||
ci.cancel();
|
||||
}
|
||||
|
||||
@Inject(
|
||||
method = "extractHearts(Lnet/minecraft/client/gui/GuiGraphicsExtractor;Lnet/minecraft/world/entity/player/Player;IIIIFIIIZ)V",
|
||||
at = @At("HEAD"),
|
||||
cancellable = true
|
||||
)
|
||||
private void lovisual$renderHealthBar(GuiGraphicsExtractor context,
|
||||
Player player,
|
||||
int x, int y, int lines,
|
||||
int regeneratingHeartIndex, float maxHealth,
|
||||
int lastHealth, int health,
|
||||
int absorption, boolean blinking,
|
||||
CallbackInfo ci) {
|
||||
if (!Lifecycle.canRunHud()) return;
|
||||
if (!(player instanceof LocalPlayer clientPlayer)) return;
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc == null) return;
|
||||
|
||||
if (!CustomHealthBar.shouldRenderHealthBar(mc, clientPlayer, maxHealth)) return;
|
||||
|
||||
LoVisualRenderSystem.ensureFrameContext();
|
||||
try (RenderPhaseScope ignored = LoVisualRenderSystem.phase(RenderPhase.HUD_MAIN, "2d:hud:custom_health")) {
|
||||
Renderer2D.withDeferredLayer(LOVISUAL$VANILLA_HUD_REPLACEMENT_LAYER, () -> {
|
||||
ViewportContext.beginUnscaled(context);
|
||||
Renderer2D.COLOR.begin();
|
||||
CustomHealthBar.HealthTextInfo textInfo =
|
||||
CustomHealthBar.renderHealthBarBackground(
|
||||
Renderer2D.COLOR,
|
||||
mc,
|
||||
clientPlayer,
|
||||
x,
|
||||
y,
|
||||
maxHealth
|
||||
);
|
||||
CustomHealthBar.renderHealthBarText(textInfo);
|
||||
Renderer2D.COLOR.render();
|
||||
ViewportContext.end(context);
|
||||
});
|
||||
lovisual$appendVanillaHudReplacementMarker(context);
|
||||
ci.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "extractSelectedItemName", at = @At("HEAD"), cancellable = true)
|
||||
private void lovisual$colorizeHeldItemTooltip(GuiGraphicsExtractor context, CallbackInfo ci) {
|
||||
if (!Lifecycle.canRunHud()) return;
|
||||
SwapTooltip swapTooltip = SwapTooltip.get();
|
||||
if (swapTooltip == null || !swapTooltip.isEnabled()) {
|
||||
return;
|
||||
}
|
||||
if (this.toolHighlightTimer <= 0 || this.lastToolHighlight == null || this.lastToolHighlight.isEmpty()) {
|
||||
SwapTooltip.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
MutableComponent name = Component.empty().append(this.lastToolHighlight.getHoverName());
|
||||
if (this.lastToolHighlight.has(net.minecraft.core.component.DataComponents.CUSTOM_NAME)) {
|
||||
name.withStyle(net.minecraft.ChatFormatting.ITALIC);
|
||||
}
|
||||
|
||||
int illegalColor = IllegalItemUtil.illegalColor();
|
||||
int topColor = TopEnchantUtil.topColor();
|
||||
int rarityColor = argbFrom(RarityColorUtil.INSTANCE.getRarityColor(this.lastToolHighlight));
|
||||
boolean hasIllegal = IllegalItemUtil.isIllegal(this.lastToolHighlight);
|
||||
boolean hasTop = !hasIllegal && TopEnchantUtil.hasTopEnchant(this.lastToolHighlight);
|
||||
int nameColor = hasIllegal ? illegalColor : (hasTop ? topColor : rarityColor);
|
||||
int alpha = (int) ((float) this.toolHighlightTimer * 256.0F / 10.0F);
|
||||
if (alpha > 255) alpha = 255;
|
||||
if (alpha <= 0) {
|
||||
SwapTooltip.clear();
|
||||
return;
|
||||
}
|
||||
float fade = alpha / 255f;
|
||||
int argb = 0xFF000000 | (nameColor & 0x00FFFFFF);
|
||||
SwapTooltip.capture(
|
||||
name.getString(),
|
||||
argb,
|
||||
fade,
|
||||
this.minecraft.gameMode.canHurtPlayer()
|
||||
);
|
||||
ci.cancel();
|
||||
}
|
||||
|
||||
@Redirect(
|
||||
method = "extractPlayerHealth(Lnet/minecraft/client/gui/GuiGraphicsExtractor;)V",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/world/entity/player/Player;getAbsorptionAmount()F"
|
||||
)
|
||||
)
|
||||
private float lovisual$ignoreAbsorptionForLayout(Player player) {
|
||||
if (!Lifecycle.canRunHud()) {
|
||||
return player.getAbsorptionAmount();
|
||||
}
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc != null && player instanceof LocalPlayer clientPlayer) {
|
||||
float actualMaxHealth = (float) player.getAttributeValue(Attributes.MAX_HEALTH);
|
||||
if (CustomHealthBar.shouldRenderHealthBar(mc, clientPlayer, actualMaxHealth)) {
|
||||
return 0.0f;
|
||||
}
|
||||
}
|
||||
return player.getAbsorptionAmount();
|
||||
}
|
||||
|
||||
@Redirect(
|
||||
method = "extractPlayerHealth(Lnet/minecraft/client/gui/GuiGraphicsExtractor;)V",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/world/entity/player/Player;getAttributeValue(Lnet/minecraft/core/Holder;)D"
|
||||
)
|
||||
)
|
||||
private double lovisual$clampMaxHealthForLayout(Player player, Holder<Attribute> attribute) {
|
||||
if (!Lifecycle.canRunHud()) {
|
||||
return player.getAttributes().getValue(attribute);
|
||||
}
|
||||
if (attribute == Attributes.MAX_HEALTH) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc != null && player instanceof LocalPlayer clientPlayer) {
|
||||
float actualMaxHealth = (float) player.getAttributes().getValue(attribute);
|
||||
if (CustomHealthBar.shouldRenderHealthBar(mc, clientPlayer, actualMaxHealth)) {
|
||||
return 20.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
return player.getAttributes().getValue(attribute);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,263 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.mixins.hud;
|
||||
|
||||
import com.mojang.blaze3d.ProjectionType;
|
||||
import com.mojang.blaze3d.buffers.GpuBufferSlice;
|
||||
import com.mojang.blaze3d.pipeline.RenderTarget;
|
||||
import com.mojang.blaze3d.systems.RenderSystem;
|
||||
import dev.loki.lovisual.render.engine.renderer.Renderer2D;
|
||||
import dev.loki.lovisual.render.engine.renderer.ui.deferred.sched.HudDeferredGuiElement;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.render.GuiRenderer;
|
||||
import net.minecraft.client.renderer.Projection;
|
||||
import net.minecraft.client.renderer.ProjectionMatrixBuffer;
|
||||
import net.minecraft.client.renderer.state.WindowRenderState;
|
||||
import net.minecraft.client.renderer.state.gui.GuiElementRenderState;
|
||||
import net.minecraft.client.renderer.state.gui.GuiRenderState;
|
||||
import org.joml.Matrix4f;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
@Mixin(GuiRenderer.class)
|
||||
public abstract class GuiRendererMixin {
|
||||
@Shadow
|
||||
@Final
|
||||
private List<?> draws;
|
||||
|
||||
@Shadow
|
||||
private int firstDrawIndexAfterBlur;
|
||||
|
||||
@Shadow
|
||||
@Final
|
||||
private Projection guiProjection;
|
||||
|
||||
@Shadow
|
||||
@Final
|
||||
private ProjectionMatrixBuffer guiProjectionMatrixBuffer;
|
||||
|
||||
@Unique
|
||||
private final ObjectArrayList<QueuedHudMarker> lovisual$hudMarkers = new ObjectArrayList<>(16);
|
||||
|
||||
@Unique
|
||||
private GuiRenderState.TraverseRange lovisual$currentTraverseRange;
|
||||
|
||||
@Shadow
|
||||
private void executeDrawRange(Supplier<String> supplier,
|
||||
RenderTarget renderTarget,
|
||||
GpuBufferSlice gpuBufferSlice,
|
||||
int start,
|
||||
int end) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
@Inject(method = "render", at = @At("HEAD"))
|
||||
private void lovisual$clearHudMarkersAtFrameStart(CallbackInfo ci) {
|
||||
lovisual$hudMarkers.clear();
|
||||
}
|
||||
|
||||
@Inject(method = "render", at = @At("RETURN"))
|
||||
private void lovisual$clearHudMarkersAtFrameEnd(CallbackInfo ci) {
|
||||
lovisual$hudMarkers.clear();
|
||||
}
|
||||
|
||||
@Inject(method = "addElementsToMeshes", at = @At("HEAD"))
|
||||
private void lovisual$enterTraverseRange(GuiRenderState.TraverseRange range, CallbackInfo ci) {
|
||||
lovisual$currentTraverseRange = range;
|
||||
}
|
||||
|
||||
@Inject(method = "addElementsToMeshes", at = @At("RETURN"))
|
||||
private void lovisual$exitTraverseRange(GuiRenderState.TraverseRange range, CallbackInfo ci) {
|
||||
lovisual$currentTraverseRange = null;
|
||||
}
|
||||
|
||||
@Inject(method = "addElementToMesh", at = @At("HEAD"), cancellable = true)
|
||||
private void lovisual$captureHudDeferredMarker(GuiElementRenderState element, CallbackInfo ci) {
|
||||
if (!(element instanceof HudDeferredGuiElement marker)) return;
|
||||
int drawIndex = draws != null ? draws.size() : 0;
|
||||
GuiRenderState.TraverseRange range = lovisual$currentTraverseRange != null
|
||||
? lovisual$currentTraverseRange
|
||||
: GuiRenderState.TraverseRange.BEFORE_BLUR;
|
||||
lovisual$hudMarkers.add(new QueuedHudMarker(drawIndex, range, marker.layer()));
|
||||
ci.cancel();
|
||||
}
|
||||
|
||||
@Inject(method = "draw", at = @At("HEAD"), cancellable = true)
|
||||
private void lovisual$drawWithDeferredHudMarkers(CallbackInfo ci) {
|
||||
if (lovisual$hudMarkers.isEmpty()) return;
|
||||
|
||||
if (draws == null || draws.isEmpty()) {
|
||||
lovisual$drainHudMarkersUpTo(Integer.MAX_VALUE);
|
||||
ci.cancel();
|
||||
return;
|
||||
}
|
||||
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc == null || mc.gameRenderer == null) {
|
||||
lovisual$drainHudMarkersUpTo(Integer.MAX_VALUE);
|
||||
ci.cancel();
|
||||
return;
|
||||
}
|
||||
|
||||
WindowRenderState window = mc.gameRenderer.gameRenderState().windowRenderState;
|
||||
if (window == null || window.guiScale == 0) {
|
||||
lovisual$drainHudMarkersUpTo(Integer.MAX_VALUE);
|
||||
ci.cancel();
|
||||
return;
|
||||
}
|
||||
|
||||
guiProjection.setupOrtho(
|
||||
1000.0F,
|
||||
11000.0F,
|
||||
(float) window.width / (float) window.guiScale,
|
||||
(float) window.height / (float) window.guiScale,
|
||||
true
|
||||
);
|
||||
RenderSystem.setProjectionMatrix(guiProjectionMatrixBuffer.getBuffer(guiProjection), ProjectionType.ORTHOGRAPHIC);
|
||||
|
||||
RenderTarget target = mc.gameRenderer.mainRenderTarget();
|
||||
if (target == null) {
|
||||
lovisual$drainHudMarkersUpTo(Integer.MAX_VALUE);
|
||||
ci.cancel();
|
||||
return;
|
||||
}
|
||||
|
||||
GpuBufferSlice transform = RenderSystem.getDynamicUniforms()
|
||||
.writeTransform(new Matrix4f().setTranslation(0.0F, 0.0F, -11000.0F));
|
||||
|
||||
int beforeBlurEnd = Math.min(firstDrawIndexAfterBlur, draws.size());
|
||||
if (beforeBlurEnd > 0) {
|
||||
lovisual$executeDrawRangeWithHudMarkers(
|
||||
() -> "gui before blur",
|
||||
target,
|
||||
transform,
|
||||
0,
|
||||
beforeBlurEnd,
|
||||
GuiRenderState.TraverseRange.BEFORE_BLUR
|
||||
);
|
||||
} else {
|
||||
lovisual$drainHudMarkersInRange(GuiRenderState.TraverseRange.BEFORE_BLUR, Integer.MAX_VALUE);
|
||||
}
|
||||
|
||||
boolean hasAfterBlurMarkers = lovisual$hasPendingHudMarkersInRange(GuiRenderState.TraverseRange.AFTER_BLUR);
|
||||
if (draws.size() <= firstDrawIndexAfterBlur && !hasAfterBlurMarkers) {
|
||||
lovisual$drainHudMarkersUpTo(Integer.MAX_VALUE);
|
||||
ci.cancel();
|
||||
return;
|
||||
}
|
||||
|
||||
RenderSystem.getDevice().createCommandEncoder().clearDepthTexture(target.getDepthTexture(), 0.0D);
|
||||
mc.gameRenderer.processBlurEffect();
|
||||
lovisual$executeDrawRangeWithHudMarkers(
|
||||
() -> "gui after blur",
|
||||
target,
|
||||
transform,
|
||||
firstDrawIndexAfterBlur,
|
||||
draws.size(),
|
||||
GuiRenderState.TraverseRange.AFTER_BLUR
|
||||
);
|
||||
lovisual$drainHudMarkersUpTo(Integer.MAX_VALUE);
|
||||
ci.cancel();
|
||||
}
|
||||
|
||||
@Unique
|
||||
private void lovisual$executeDrawRangeWithHudMarkers(Supplier<String> label,
|
||||
RenderTarget target,
|
||||
GpuBufferSlice transform,
|
||||
int start,
|
||||
int end,
|
||||
GuiRenderState.TraverseRange range) {
|
||||
int cursor = start;
|
||||
for (int i = 0, size = lovisual$hudMarkers.size(); i < size; i++) {
|
||||
QueuedHudMarker marker = lovisual$hudMarkers.get(i);
|
||||
if (marker == null || marker.drained || marker.range != range) continue;
|
||||
|
||||
int markerIndex = marker.drawIndex;
|
||||
if (markerIndex < start) {
|
||||
lovisual$drainHudMarker(marker);
|
||||
continue;
|
||||
}
|
||||
if (markerIndex > end) continue;
|
||||
|
||||
if (cursor < markerIndex) {
|
||||
executeDrawRange(label, target, transform, cursor, markerIndex);
|
||||
}
|
||||
lovisual$drainHudMarker(marker);
|
||||
cursor = Math.max(cursor, markerIndex);
|
||||
}
|
||||
|
||||
if (cursor < end) {
|
||||
executeDrawRange(label, target, transform, cursor, end);
|
||||
}
|
||||
}
|
||||
|
||||
@Unique
|
||||
private boolean lovisual$hasPendingHudMarkersInRange(GuiRenderState.TraverseRange range) {
|
||||
for (int i = 0, size = lovisual$hudMarkers.size(); i < size; i++) {
|
||||
QueuedHudMarker marker = lovisual$hudMarkers.get(i);
|
||||
if (marker != null && !marker.drained && marker.range == range) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Unique
|
||||
private void lovisual$drainHudMarkersInRange(GuiRenderState.TraverseRange range, int drawIndexInclusive) {
|
||||
for (int i = 0, size = lovisual$hudMarkers.size(); i < size; i++) {
|
||||
QueuedHudMarker marker = lovisual$hudMarkers.get(i);
|
||||
if (marker == null || marker.drained || marker.range != range) continue;
|
||||
if (marker.drawIndex <= drawIndexInclusive) {
|
||||
lovisual$drainHudMarker(marker);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Unique
|
||||
private void lovisual$drainHudMarkersUpTo(int drawIndexInclusive) {
|
||||
for (int i = 0, size = lovisual$hudMarkers.size(); i < size; i++) {
|
||||
QueuedHudMarker marker = lovisual$hudMarkers.get(i);
|
||||
if (marker == null || marker.drained) continue;
|
||||
if (marker.drawIndex <= drawIndexInclusive) {
|
||||
lovisual$drainHudMarker(marker);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Unique
|
||||
private void lovisual$drainHudMarker(QueuedHudMarker marker) {
|
||||
if (marker == null || marker.drained) return;
|
||||
marker.drained = true;
|
||||
Renderer2D.drainDeferred2D(marker.layer);
|
||||
}
|
||||
|
||||
@Unique
|
||||
private static final class QueuedHudMarker {
|
||||
private final int drawIndex;
|
||||
private final GuiRenderState.TraverseRange range;
|
||||
private final Renderer2D.Deferred2DLayer layer;
|
||||
private boolean drained;
|
||||
|
||||
private QueuedHudMarker(int drawIndex,
|
||||
GuiRenderState.TraverseRange range,
|
||||
Renderer2D.Deferred2DLayer layer) {
|
||||
this.drawIndex = drawIndex;
|
||||
this.range = range;
|
||||
this.layer = layer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,104 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.mixins.hud.accessibility;
|
||||
|
||||
import net.minecraft.client.GameNarrator;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.NarratorStatus;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
import dev.loki.lovisual.util.NarratorBlocker;
|
||||
|
||||
@Mixin(GameNarrator.class)
|
||||
public class NarratorManagerMixin {
|
||||
|
||||
@Shadow
|
||||
@Final
|
||||
private Minecraft minecraft;
|
||||
|
||||
@Inject(method = "sayChatQueued(Lnet/minecraft/network/chat/Component;)V", at = @At("HEAD"), cancellable = true)
|
||||
private void lovisual$muteChat(Component message, CallbackInfo ci) {
|
||||
if (shouldMute()) {
|
||||
NarratorBlocker.enforce(this.minecraft);
|
||||
ci.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "saySystemChatQueued(Lnet/minecraft/network/chat/Component;)V", at = @At("HEAD"), cancellable = true)
|
||||
private void lovisual$muteNarrator(Component message, CallbackInfo ci) {
|
||||
if (shouldMute()) {
|
||||
NarratorBlocker.enforce(this.minecraft);
|
||||
ci.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "saySystemQueued(Lnet/minecraft/network/chat/Component;)V", at = @At("HEAD"), cancellable = true)
|
||||
private void lovisual$muteSystem(Component message, CallbackInfo ci) {
|
||||
if (shouldMute()) {
|
||||
NarratorBlocker.enforce(this.minecraft);
|
||||
ci.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "saySystemNow(Lnet/minecraft/network/chat/Component;)V", at = @At("HEAD"), cancellable = true)
|
||||
private void lovisual$muteImmediateText(Component message, CallbackInfo ci) {
|
||||
if (shouldMute()) {
|
||||
NarratorBlocker.enforce(this.minecraft);
|
||||
ci.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "saySystemNow(Ljava/lang/String;)V", at = @At("HEAD"), cancellable = true)
|
||||
private void lovisual$muteImmediateString(String message, CallbackInfo ci) {
|
||||
if (shouldMute()) {
|
||||
NarratorBlocker.enforce(this.minecraft);
|
||||
ci.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "narrateMessage(Ljava/lang/String;Z)V", at = @At("HEAD"), cancellable = true)
|
||||
private void lovisual$muteSay(String message, boolean interrupt, CallbackInfo ci) {
|
||||
if (shouldMute()) {
|
||||
NarratorBlocker.enforce(this.minecraft);
|
||||
ci.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "updateNarratorStatus", at = @At("HEAD"), cancellable = true)
|
||||
private void lovisual$blockToggle(NarratorStatus mode, CallbackInfo ci) {
|
||||
if (!shouldMute()) return;
|
||||
NarratorBlocker.enforce(this.minecraft);
|
||||
ci.cancel();
|
||||
}
|
||||
|
||||
@Inject(method = "checkStatus", at = @At("HEAD"), cancellable = true)
|
||||
private void lovisual$skipNarratorLibraryCheck(boolean narratorEnabled, CallbackInfo ci) {
|
||||
if (!shouldMute()) return;
|
||||
NarratorBlocker.enforce(this.minecraft);
|
||||
ci.cancel();
|
||||
}
|
||||
|
||||
@Inject(method = "isActive", at = @At("HEAD"), cancellable = true)
|
||||
private void lovisual$reportInactive(CallbackInfoReturnable<Boolean> cir) {
|
||||
if (!shouldMute()) return;
|
||||
cir.setReturnValue(false);
|
||||
}
|
||||
|
||||
@Unique
|
||||
private boolean shouldMute() {
|
||||
return NarratorBlocker.isBlocked();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.mixins.hud.accessibility;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.NarratorStatus;
|
||||
import net.minecraft.client.OptionInstance;
|
||||
import net.minecraft.client.Options;
|
||||
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;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
import dev.loki.lovisual.util.NarratorBlocker;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
@Mixin(Options.class)
|
||||
public abstract class OptionsNarratorMixin {
|
||||
|
||||
@Inject(method = "<init>(Lnet/minecraft/client/Minecraft;Ljava/io/File;)V", at = @At("RETURN"))
|
||||
private void lovisual$enforceNarratorDefaults(Minecraft client, File optionsFile, CallbackInfo ci) {
|
||||
NarratorBlocker.enforce((Options) (Object) this);
|
||||
}
|
||||
|
||||
@Inject(method = "save", at = @At("HEAD"))
|
||||
private void lovisual$enforceNarratorBeforeWrite(CallbackInfo ci) {
|
||||
NarratorBlocker.enforce((Options) (Object) this);
|
||||
}
|
||||
|
||||
@Inject(method = "narrator", at = @At("HEAD"))
|
||||
private void lovisual$enforceNarratorGetter(CallbackInfoReturnable<OptionInstance<NarratorStatus>> cir) {
|
||||
NarratorBlocker.enforce((Options) (Object) this);
|
||||
}
|
||||
|
||||
@Inject(method = "narratorHotkey", at = @At("HEAD"))
|
||||
private void lovisual$enforceNarratorHotkeyGetter(CallbackInfoReturnable<OptionInstance<Boolean>> cir) {
|
||||
NarratorBlocker.enforce((Options) (Object) this);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.mixins.hud.bars;
|
||||
|
||||
import dev.loki.lovisual.events.Events;
|
||||
import dev.loki.lovisual.events.impl.pvp.CombatProtocolBossbarEvent;
|
||||
import dev.loki.lovisual.mixins.accessors.hud.BossHealthOverlayAccessor;
|
||||
import net.minecraft.client.gui.GuiGraphicsExtractor;
|
||||
import net.minecraft.client.gui.components.BossHealthOverlay;
|
||||
import net.minecraft.client.gui.components.LerpingBossEvent;
|
||||
import net.minecraft.network.chat.Component;
|
||||
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;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import dev.loki.lovisual.util.pvp.client.CooldownsState;
|
||||
|
||||
@Mixin(BossHealthOverlay.class)
|
||||
public class BossHealthOverlayMixin {
|
||||
|
||||
@Inject(method = "extractRenderState", at = @At("HEAD"))
|
||||
private void onRender(GuiGraphicsExtractor ctx, CallbackInfo ci) {
|
||||
if (Events.BUS.hasListeners(CombatProtocolBossbarEvent.class)) {
|
||||
List<String> names = new ArrayList<>();
|
||||
for (LerpingBossEvent bar : ((BossHealthOverlayAccessor) this).getBars().values()) {
|
||||
if (bar == null) continue;
|
||||
Component name = bar.getName();
|
||||
if (name != null && !name.getString().isBlank()) {
|
||||
names.add(name.getString());
|
||||
}
|
||||
}
|
||||
if (!names.isEmpty()) {
|
||||
Events.BUS.post(new CombatProtocolBossbarEvent(names));
|
||||
}
|
||||
}
|
||||
|
||||
// PvP bossbar detection disabled: bossbar no longer reliable.
|
||||
/*
|
||||
BossBarHud hud = (BossBarHud) (Object) this;
|
||||
|
||||
Map<UUID, ClientBossBar> bars =
|
||||
((BossHealthOverlayAccessor) hud).getBars();
|
||||
|
||||
boolean pvpActive = false;
|
||||
|
||||
// проверяем только боссбары
|
||||
for (ClientBossBar bar : bars.values()) {
|
||||
|
||||
String name = bar.getName().getString();
|
||||
|
||||
if (name.startsWith("Режим PVP -")) {
|
||||
pvpActive = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// === только тут меняем состояние PvP ===
|
||||
|
||||
if (pvpActive && !CooldownsState.MANAGER.isInPvp()) {
|
||||
//System.out.println("[PvPDetector] → ENTER PVP");
|
||||
CooldownsState.MANAGER.enterPvp();
|
||||
}
|
||||
|
||||
if (!pvpActive && CooldownsState.MANAGER.isInPvp()) {
|
||||
//System.out.println("[PvPDetector] → EXIT PVP");
|
||||
CooldownsState.MANAGER.exitPvp();
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue