The previous attempt only made Renderer.render share GuiGraphics.pose()
with DrawContext when guiGraphics was non-null. But every sub-panel
call (drawSearchBar, drawToasts, ModuleListPanel, SettingsPanel,
CategoryBar, ProfileWidget, SettingsPopup, KeybindOverlay,
ScaleSwitchOverlay) used Renderer.renderConsumer, which falls back
to Renderer.render(null, ...) when currentCanvas == null — that's
exactly the Screen.render path. With guiGraphics == null the
DrawContext still got a private new PoseStack(), so any element
rendered through DrawContext was identity-posed while the search
background drawn via guiGraphics.pose() rode along with
pushPose + scale(0.98+0.02*eased), and the two drifted apart.
Wrap the entire PanelClickGui body inside Renderer.render(
guiGraphics, ...). That sets currentCanvas to a DrawContext whose
PoseStack is GuiGraphics.pose(), so every nested renderConsumer
short-circuits into the same DrawContext and shares the same
matrix. Search icon, placeholder, query text and the blinking
cursor now sit on the same scaled pose as the search background.
Toasts get the fix for free.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Two related render bugs in PanelClickGui (Screen context):
1) Module list overflow when a category has more entries than fit:
ModuleListPanel.render called Renderer.renderConsumer, which in
the Screen context (currentCanvas == null) falls through to
Renderer.render(null, ...). That built a DrawContext(null), and
DrawContext.clipRect short-circuits when guiGraphics is null, so
enableScissor was never issued. The inner clip in renderModuleList
was also skipped, so rows past the panel bottom were drawn freely.
Switch ModuleListPanel.render and renderSearchResults to
Renderer.render(guiGraphics, ...). The DrawContext now has the
live GuiGraphics, so both the outer clip and the renderModuleList
inner clip actually call enableScissor and the list stays inside
its panel.
2) Search bar (and other PanelClickGui inner draw lambdas) drifted
relative to their backgrounds. Renderer.render created a
DrawContext with a fresh "new PoseStack()" regardless of the
GuiGraphics that was passed in. PanelClickGui wraps its render in
pushPose + scale(0.98+0.02*eased), so primitives drawn through
guiGraphics.pose() (search background, cursor rect, toasts) got
the scaled pose while primitives drawn through the DrawContext
(search icon, query text, placeholder) used identity — they
visually drifted apart whenever scaleFactor != 1.
Construct DrawContext with the GuiGraphics's PoseStack instead of
a private one, so every path shares the same matrix stack and
the search bar and toasts render coherently with the rest of
PanelClickGui.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Two related rendering regressions:
1) OldClickGui category headers — the switch in legacy CategoryPanel
only mapped COMBAT..EXPLOIT to icon glyphs 'a'..'e' and fell back
to '?'. The Category enum has WORLD ("Misc") and MISC ("Ghost")
too, so those two headers rendered as a question mark. Extend the
switch with WORLD -> 'f' and MISC -> 'g'.
2) PanelClickGui rendering — several Material Icon PUA codepoints
had been silently stripped to empty strings during earlier source
passes, leaving the panel without bind icon, profile-popup logo /
close button / dropdown arrow, and the number-setting edit icon.
Restore them per Recaf:
- ModuleListPanel bind icon \uE312 (2 sites)
- SettingsPopup logo \uE8B8
- SettingsPopup close button \uE5CD
- SettingsPopup dropdown arrow \uE313
- NumberSettingRenderer edit pen \uE3C9
CategoryBar's 7 category glyphs and PanelClickGui's search icon
already had the correct PUA bytes on disk; they were just
invisible in tooling, so no change there.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The hand-written PlayerEntry never initialized widthAnim, so the
render path read 0 for entry width and drew nothing visible. Other
gaps:
- nameWidth was never measured, so item layouts collapsed.
- displayName bypassed NameProtect.replacePlayerName, leaking real
IGNs through the streamer/anti-doxx filter.
- startRemove() flipped a flag but never triggered the slide-out or
height-collapse animations, so isRemoveDone() (which used a custom
alpha decay) effectively never returned true.
- tick() mutated alpha / currentY by hand instead of just ticking
the animation timers the render code already consults.
Rewrite per Recaf's PlayerListHud$PlayerEntry:
- Constructor seeds itemStacks/cheatItems from initialItems, calls
updateItems(...) to set displayName, nameWidth, totalWidth, then
primes slide/height/alpha/width anims (width starts at totalWidth
so the row is visible from frame 1, then animates if it grows).
- updateItems() recomputes displayName via NameProtect, the cached
nameWidth via headerFont, the totalWidth from padding/head/items,
and animates widthAnim to the new total.
- startRemove() actually slides the entry out and animates height
to 0; isRemoveDone() defers to heightAnim.isDone().
- Drop the dead alpha / targetY / currentY / parent fields.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The previous EffectEntry had two render-killing inversions: show()
animated heightAnim to the target row height and widthAnim to 100,
but the render loop uses heightAnim as the 0..1 alpha factor
(multiplied by 80/140/160/185) and widthAnim as the actual row
height. That made every entry render with massively saturated alpha
and a 100-pixel-tall pill. getTotalWidth() also returned the height
animation value, so the sort order was wrong too.
Rewrite to mirror Recaf's PotionEffectsHud$EffectEntry:
- show(h): heightAnim -> 1.0 (alpha), widthAnim -> h (row height).
- startRemove() animates both back to 0; isRemoveDone() checks anim
isDone() instead of a hand-rolled alpha field.
- tick() drops the custom alpha decay and keeps the duration text
fresh for non-instantaneous effects.
- getTotalWidth() actually measures the text (icon + name + duration
+ padding) so sort-by-width works.
- updateEffect() bumps originalDuration when the new instance has a
longer remaining duration (stacked refresh).
- refreshDisplayText() delegates to the outer HUD for the infinity
symbol and roman-numeral amplifier formatting.
- Rename instance -> effectInstance and update render-site reads.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The previous KeyBindRow was a hand-written stub that left
displayName/keyName/nameWidth uninitialized, never animated entries
in/out, and had a placeholder getFittingFont that didn't compute a
key width. So module rows rendered as blank zero-width strips.
Rewrite KeyBindRow to mirror the original (per Recaf):
- The constructor calls update(entry), pulls rightAligned from the
parent HUD and seeds slide/height/alpha animations.
- update() refreshes displayName/keyName/enabled/nameWidth and
invalidates the cached fitting font when the key text changes.
- startRemove() actually triggers the slide-out, height and alpha
animations; isRemoveDone() checks heightAnim/alphaAnim.isDone().
- tick() no longer mutates a custom alpha field; animations drive
visibility.
- getFittingFont() computes a scaled font when the key text exceeds
the slot width, caches the result, and stores cachedKeyWidth so
getKeyWidth() returns the right value.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The render path in KeyBindsHud.renderRows draws row.displayName /
row.keyName and lays out rows using row.nameWidth, but the
KeyBindRow constructor (and update()) only assigned name/key,
leaving displayName, keyName and nameWidth at their default null/0.
That's why only the 'Hotkeys' header showed up while every module
row stayed blank with zero width.
Initialize displayName, keyName, nameWidth and widthValue from name
/ key in both the constructor and update().
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Add ZenClient.extractCloudAssets() that copies panel.png, ptr.png,
lie.wav and truth.wav from /assets/zen/cloud_assets/ on the classpath
into ConfigManager.CONFIG_DIR (~/.zen/configs). Existing files are
left untouched so user-replaced assets stick around. Called from
init() right after ConfigManager is constructed, since LieDetector's
TextureUtil/SoundUtil look up these files there.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- HudElement: drop the parallel 'visible' field and the isEnabled/
setEnabled overrides. The dual flag was getting out of sync with
Module.enabled, so HUD listeners never registered on the event bus
and onTick/onPacket never fired (PlayerListHud, KeyBindsHud and
PotionEffectsHud rendered as empty panels). The base Module.enabled
is now the single source of truth.
- HudElement: rename hudWidth/hudHeight to width/height and let
Lombok generate getters/setters; expose 'dragging' via @Setter so
callers don't need a hand-written setDragging.
- ChatScreenPatch: the chat-screen drag hook was reading/writing
isEnabled/setEnabled when it really meant dragging/setDragging,
which is why opening the chat would silently disable any HUD you
had on screen. Switch to isDragging/setDragging.
- Add cloud_assets/ (panel.png, ptr.png, lie.wav, truth.wav)
needed by LieDetector.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
HudElement.stopDragging() was clearing the 'visible' flag via
setEnabled(false), but HudManager.onTick calls stopDragging() on
every HUD every tick whenever no screen is open. The result is that
ModuleListHud, KeyBindsHud and every other HUD got disabled one tick
after construction and never rendered.
Make stopDragging() actually stop dragging — it now just flips the
'dragging' flag. Also fix the companion bugs: mousePressed() now sets
'dragging' (not 'visible'), and the 'dragging' field defaults to
false so HUDs are not born mid-drag.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Register HudManager and a fresh IntroAnimation on the event bus
during ZenClient bootstrap so HUDs actually tick and the intro
plays on first launch.
- Strip leftover System.out.println debug breadcrumbs from
NewClickGui, CategoryPanel, ModuleElement and the unused
BUILD_TAG print in ModuleElement.
- Drop the redundant 'initialized' static flag and the stray
NewClickGui instance constructed in the class initializer; the
Lombok @NonNull null check on render() was already enforced, so
the hand-written guard goes too.
- Remove the unused NumberSettingElement.sliderX field.
- Replace Module.getBind() with a Lombok @Getter on the field.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Remove AntiDebug, CallerTracker, GamePathLocator, SignatureUtil,
Unused1 and Unused2 from shit.zen.utils.misc — none are referenced
anywhere in the source tree. Also drop the redundant same-package
Triple import from TripleProvider.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Remove unused Encryption class (AES wrapper around shit.zen.utils)
and its construction from ZenClient bootstrap; drop dead TimerPatch
import.
- Defer ConfigManager.loadAll() and CommandManager.initCommands() so
configs load only after modules are registered, fixing values that
were silently discarded.
- Make isOwner() return true so the local user is treated as owner.
- Rename ZenClient.disconnectFromServer() to shutdown() and pin the
MinecraftPatch close hook to At.Type.HEAD so it runs before the
game tears down its subsystems.
- README: invite users to file issues / open PRs for missing features.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Rename ~2887 unreadable Procyon-decompiler locals/params (f/f2/fN,
n/n2, bl/bl2, nArray, bufferedImage, etc.) across 169 files to
context-aware camelCase identifiers (x, y, width, height, alpha,
color, sensitivity, etc.). Logic, control flow, field/method names
and method signatures (types/arity) are preserved; only local
variable and parameter names changed.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>