# JeanAddons

A lightweight, personal-use **client-side** [Fabric](https://fabricmc.net/) mod for
Minecraft **26.1.2**: rendering tweaks, chat-screen conveniences, and Hypixel
Skyblock QoL.

**Core contract — client-side only.** Every feature is a purely client-side
change: cosmetic rendering tweaks, conveniences inside the chat screen, and
Skyblock conveniences that reproduce vanilla-client behavior. The mod is
still strictly client-side (`"environment": "client"`, never loads on a
dedicated server) and produces outbound traffic in exactly two deliberate,
Hypixel-allowed places: (a) it sends user-configured chat commands through
the vanilla `sendCommand` path, always under a game-tick cooldown, and (b) it
rewrites the user's real click on the currently open screen into the
1.8.9-equivalent middle-click form before vanilla builds the packet. Outside
those two patterns the mod never alters any packet the client sends, nor any
input, movement, or gameplay behavior.

## Features

| Feature | Category | Default | Effect |
| --- | --- | --- | --- |
| `remove-text-shadow` | Visual | `true` | Strips the drop shadow from all text drawn through the game's font renderer: GUIs, HUD, chat, tooltips, scoreboard sidebar, action bar, item name pop-ups, entity name tags, the F3 debug overlay, and other mods' HUD text drawn via the vanilla font classes. |
| `optifine-cape-flow` | Visual | `true` | Restores OptiFine's cape movement. Vanilla clamps how far the cape may lean against the player (150 degrees), pinning it to the back during fast movement and long falls; removing the cap lets the cape lean freely for every player you see, exactly like OptiFine's cape physics. |
| `old-cape-movement` | Visual | `false` | Restores the cape movement of 1.12.x and earlier (Animatium's Old Cape Movement): the body yaw behind the cloak direction is interpolated with a plain lerp, the cape lean is not scaled down while elytra-flying, and the cape's side lean is left unclamped. Independent of `optifine-cape-flow`, which only removes the 150° forward-lean cap — both can be combined. |
| `force-cape` | Visual | `true` | Always renders your own player with the bundled 2016 MineCon cape, even if your account has none; while enabled it also replaces a cape you already own. Only your own view of your own player is affected — other players keep their own capes. The vanilla guards still apply: the cape hides for invisible players, hidden cape model parts, and a worn elytra. |
| `copy-chat-line` | Chat | `true` | Right-click a chat line to copy the whole message it belongs to, as plain text, to the system clipboard. A green confirmation is briefly drawn in the bottom-right corner. Vanilla ignores right-clicks in the chat screen, so nothing vanilla is shadowed. |
| `chat-history-size` | Chat | `1000` | How many chat messages the client keeps in memory and can scroll back through, counting the lines a long message wraps into. Vanilla hard-caps this at 100. |
| `command-keybinds` | Skyblock | `true` | Press a bound key or mouse button (side buttons included) to send its chat command straight through the vanilla `sendCommand`/`sendChat` path — no chat screen involved. Binds only fire while no screen is open, so chat and GUI input are never touched; a matched key no longer triggers its vanilla behavior (e.g. hotbar switching). The bind list is the `bind-N-*` lines in `[Skyblock]`, migrated as-is from NoFrills (`/nofrills` commands rewritten to `/jeanaddons`). |
| `command-cooldown-ticks` | Skyblock | `60` | Minimum interval between two command-keybind sends, counted in game ticks (20 = 1 second). One global value, because what it rate-limits is the account's overall command rate. While on cooldown the press is blocked and the remaining seconds are shown in local chat. 0 disables. |
| `middle-click-fix` | Skyblock | `true` | Lets the middle mouse button send its container click in survival mode, exactly like the 1.8.9 client did. Modern vanilla gates the middle click's `CLONE` path behind creative-only `hasInfiniteMaterials()`, so in survival it sends nothing; this restores the original behavior everywhere — the feature carries no Skyblock gating of its own. |
| `middle-click-override` | Skyblock | `true` | In Skyblock menus whose intended action is the middle click (Pets, Bits Shop, shops with `Cost`/`Sell Price`/`Bazaar Price` lore, …), a plain left click is rewritten to that middle click (`CLONE`, button 2), so items never get picked up onto the cursor under latency. Blacklisted menus (chests, storage, anvils, auctions, …) keep vanilla behavior; inactive outside Skyblock. |
| `middle-click-override-debug` | Skyblock | `false` | Prints one local chat line for every left click the override evaluates in a container screen: the menu title as received and stripped of `§` codes, every check's value (blacklist/whitelist/Skyblock/id/lore), and the rewrite decision. |

**Known limitation (remove-text-shadow):** mods that render text with their
own font engine (for example NanoVG-based HUDs, like Odin's custom HUD)
bypass `Font` entirely and cannot be affected. Everything using the vanilla
font pipeline is covered.

**Notes (chat features):** `copy-chat-line` copies the *whole* logical message
under the cursor, not just the wrapped line, strips legacy `§` color codes,
and consumes the click. `chat-history-size` widens both vanilla caps (stored
messages and display lines) to the same number, which is what lets scrolling
reach the extra history; values beyond the newest `10000` are clamped.

**Notes (cape features):** the cape tweaks are ported from
[Animatium](https://github.com/mixces/Animatium) (its cape-movement options)
and stay purely client-side render changes. Both movement options act inside
`AvatarRenderer#extractCapeState` and are independent knobs that compose:
`optifine-cape-flow` removes only the 150-degree bound on the cape lean, while
`old-cape-movement` switches the body-yaw interpolation to a plain lerp, keeps
the cape lean unscaled while elytra-flying, and unclamps the side lean, like
1.12.x and earlier. `force-cape` swaps the local player's cape texture asset
for the single bundled one inside `CapeLayer#submit` and restores it before the
method returns, so the skin object is never permanently modified — in
particular the mod never claims you own a cape anywhere outside your own view
of your own player. The bundled texture is the original 64x32 MineCon 2016
cape export from `reference/cape/`, used as-is: the vanilla cape model
declares a 64x64 layer with `texScaleV` 0.5, which makes its effective UV
space 64x32 — exactly the classic (1.8.9-era) cape format, so the file needs
no re-layout.

**Notes (Skyblock features):** the keybind, refill, and middle-click
features are ported from [NoFrills](https://github.com/WhatYouThing/NoFrills).
What makes them safe on Hypixel is that they only reproduce vanilla-client
behavior, never invent it: keybind commands go through the vanilla
`sendCommand` path — the same thing typing the command does; the middle
clicks produce exactly the `CLONE` (button 2) container packet a 1.8.9 client
sent unconditionally; and the override only rewrites the user's own real
click on the *currently open* screen, so `containerId`, `stateId`, carried
item, and changed slots all come from vanilla's own `slotClicked →
handleContainerInput` chain — byte-identical to a 1.8.9 player pressing
middle click. There is no auto-clicking anywhere in the mod; nothing ever
touches a UI the server has replaced (Skyblock covers old container UIs with
new ones instead of closing them — interacting with a stale one is what gets
accounts banned), no menu/screen reference outlives an UI change, and no
`ServerboundContainerClickPacket` is ever constructed by hand. All automated
command sends are rate-limited in game ticks to match the server's per-tick
processing: one shared configurable cooldown for keybind sends
(`command-cooldown-ticks`, default 60 = 3 s) and a hard-coded 60-tick
cooldown on the `/gfs` refill send — a keybind running `/jeanaddons refill`
passes through both, each guarding its own packets. The command itself is
`/jeanaddons refill <item> [amount]` (alias `/ja`), topping an item up from
your sacks via `/gfs` (default amount 64), matching items by Skyblock id or
plain item name with underscores read as spaces.

## Directory layout

| Path | Purpose |
| --- | --- |
| `src/` | Sources and resources, standard Gradle layout. Kotlin (`src/main/kotlin`) holds game logic; Java (`src/main/java`) holds mixin classes. |
| `bin/` | Binary output. `copyJarToBin` copies the built mod jar here on every `build`. Disposable: git-ignored, safe to delete, refreshed by the next build. |
| `conf/` | Human-readable review copy of the default config. `syncDefaultConf` re-copies `src/main/resources/assets/jeanaddons/default.conf` to `conf/jeanaddons.conf` on every `build`, so the review copy can never drift from the template that ships in the jar. The jar template stays the single source of truth. |
| `reference/` | Read-only reference projects ([Odin](https://github.com/odtheking/odin) for the Gradle/mixin setup, [chat-utils](https://github.com/tierrif/chat-utils) for the chat features' approach, [NoFrills](https://github.com/WhatYouThing/NoFrills) for this round's three Skyblock features). Not part of the build; git-ignored; never modified. |
| `reference/nofrills_conf/` | The user's real NoFrills configuration, the source the migrated `[Skyblock]` keybind defaults were taken from verbatim. Read-only like the rest of `reference/`. |
| `build.gradle.kts`, `settings.gradle.kts`, `gradle.properties`, `gradle/wrapper/` | Gradle build. All identity values (mod id, name, version, group) and pinned dependency versions live in `gradle.properties`. |

## Building and running

```sh
./gradlew build       # jar lands in bin/jeanaddons-<version>.jar
./gradlew runClient   # launches a dev client
```

Mixin debugging escape hatch when something refuses to apply: add
`-Dmixin.debug.export=true` to the client run config's VM args to dump the
transformed classes.

## Configuration

There is **no config GUI**. The entire configuration is one hand-edited,
self-documenting text file:

- On first launch the mod generates `<game directory>/config/jeanaddons.conf`
  from the bundled template (`assets/jeanaddons/default.conf` inside the jar)
  and logs one line saying so.
- Format: `[Section]` headers group features by category (the section name is
  the category display name, e.g. `[Visual]`); each feature toggle is one
  `key = value` line with a stable kebab-case key; `#` starts a comment; blank
  lines are ignored; whitespace around keys and values is trimmed.
- The file is read **once at startup**. There is no hot reload and the mod
  never rewrites the file — **restart the game to apply edits**. Deleting the
  file restores defaults on the next launch.
- Unknown sections or keys log a warning and are ignored; missing keys fall
  back to the feature's built-in default; numeric values outside a feature's
  documented range log a warning and are clamped; a corrupt or unreadable file
  logs an error and all defaults are used — the game never crashes over the
  config.

## Adding a new feature

Three steps:

1. Create one new class under `src/main/kotlin/dev/chatfatigue/feature/impl/<category>/`
   extending `Feature` (a toggle) or `IntOption` (a numeric setting); pick a
   stable, unique kebab-case `id`.
2. Add one line to the `features` or `intOptions` list in `FeatureManager`
   (plus, if the mixin side needs it, one `@JvmStatic` accessor in
   `FeatureAccess`).
3. Add one commented block for the feature to
   `src/main/resources/assets/jeanaddons/default.conf` (under the right
   `[Section]`, with its valid values documented).

The command keybinds are the one structured entry: each bind is four flat
`bind-N-*` lines in `[Skyblock]`, parsed by `KeybindRegistry` — still the
same two steps of a commented block in `default.conf` plus one registry, just
a registry of binds instead of features.

## Toolchain

| Component | Version |
| --- | --- |
| Minecraft | `26.1.2` (unobfuscated, official Mojang names — no mappings needed) |
| Fabric Loader | `0.19.5` |
| Fabric API | `0.155.3+26.1.2` |
| Fabric Language Kotlin | `1.14.1+kotlin.2.4.20` |
| Kotlin (Gradle plugin) | `2.4.20` |
| Fabric Loom | `1.17-SNAPSHOT` |
| Gradle wrapper | `9.7.1` |
| Java toolchain / JVM target | `25` |

**Mixin compatibility note:** Fabric Loader `0.19.4` updated the bundled
sponge-mixin (0.17.3 → 0.17.4) and reworked how per-mod mixin compatibility is
selected: the loader derives a `fabric-compat` level from the *loader version
each mod declares in its own `fabric.mod.json`*. Mods built against older
loaders get routed onto legacy paths. Since 0.1.1 this mod declares
`fabricloader >= 0.19.5` (new behavior) and its mixin config declares
`JAVA_25`, matching the actual class files (JVM target 25).

**Historical note:** before 0.1.1 the build pinned Loom `1.16-SNAPSHOT` because
the then-current Gradle `9.4.1` wrapper rejected Loom 1.17.x (built against
Gradle plugin API 9.5.0). The wrapper was upgraded to `9.7.1`, which resolves
the deviation and allows following the Loom 1.17-SNAPSHOT line again. The mod
was known as *ChatAddons* until 0.2.0.

## License

MIT — see [LICENSE](LICENSE).
