fix(api): keep every equipment slot inside the array and off old clients - #76

Merged
Tofaa2 merged 1 commit into
Tofaa2:masterfrom
steveb05:fix/equipment-slot-bounds
Jul 31, 2026
Merged

fix(api): keep every equipment slot inside the array and off old clients#76
Tofaa2 merged 1 commit into
Tofaa2:masterfrom
steveb05:fix/equipment-slot-bounds

Conversation

@steveb05

@steveb05steveb05 commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

WrapperEntityEquipment has held six item stacks since before the game had more than six equipment slots, and it indexes them by the slot's ordinal. Body arrived in 1.20.5 at ordinal six and saddle in 1.21.5 at ordinal seven, so both fall off the end of that array. The result is that setItem, getItem and clearSlot throw an ArrayIndexOutOfBoundsException for either of them, well before a packet is ever built, which means there is currently no way through this class to saddle a horse or to put armor on a wolf. Sizing the array from the enum rather than from a number typed in by hand also means the next slot Mojang adds costs nothing here.

Holding a slot is only half the problem, though. A slot travels inside the equipment packet as its ordinal from 1.9 onwards, so a server version that predates the slot has no number the client could map back to it: a saddle sent to a 1.21.4 client is a seven that reads as garbage. Because of that, createPacket now leaves out the slots the running server version does not know rather than assuming every slot is safe to send everywhere. The same was already true of the offhand below 1.9, which nothing on the sending side checked.

That check is exposed as isSlotSupported because callers deciding anything else on the same question should not have to keep a private table of version numbers, which is exactly what downstream ends up doing today. It takes the version as an argument so the answer is a pure function of what you ask about, with an overload that fills in the running version for the common case.

One thing turned up on the way. VersionChecker.verifyVersion compared the wrong way around: it threw when the server was newer than the version handed to it rather than older. Its only caller is getOffhand, which therefore raised InvalidVersionException on every server past 1.9, so the guard has been backwards for as long as it has existed and the fix here needed a version check that works.

Verified against packetevents 2.13.0 that body and saddle are ordinals six and seven, that a six long array throws on the latter, and that the slots left in the packet come out contiguous on 1.8.8, 1.9, 1.20.4, 1.20.5, 1.21.4, 1.21.5 and 1.21.11.

Summary by CodeRabbit

  • Bug Fixes

    • Improved server-version compatibility checks for more reliable version detection.
    • Equipment packets now exclude slots unsupported by the target server version, preventing compatibility issues.
  • New Features

    • Added support for dynamically handling all available equipment slots, including newer offhand, body, and saddle slots.
    • Added version-aware equipment slot support checks for improved cross-version compatibility.

The equipment array was written when the game had six slots and was never grown, so the two that arrived since, body in 1.20.5 and saddle in 1.21.5, sit at ordinals six and seven and land outside it. Anyone handing one of those to setItem, getItem or clearSlot got an ArrayIndexOutOfBoundsException, which meant saddling a horse or putting armor on a wolf was not something a caller could express at all. Sizing the array from the enum means the next slot the game adds costs nothing here.
Holding a slot is only half of it, because a slot travels in the packet as its ordinal from 1.9 onwards, and a version that predates the slot has no number it could map back to. Sending a saddle to a 1.21.4 client is a seven it will read as garbage. So createPacket now leaves out what the running server version does not know rather than trusting that every slot is safe everywhere, and isSlotSupported is public because a caller that wants to decide something else on the same question should not have to keep its own table of version numbers. It takes the version as an argument so the answer is a pure function of what is asked, with the running version filled in by the overload.
The version check it would have used was inverted: verifyVersion threw when the server was newer than the version passed rather than older, so getOffhand raised InvalidVersionException on every server past 1.9 and its single caller had been dead for as long as it existed.
@coderabbitai

coderabbitaiBot commented Jul 31, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: b346cdba-60cc-41ec-9942-1bb1d79c932d

📥 Commits

Reviewing files that changed from the base of the PR and between 9ab4d19 and 093f5fc.

📒 Files selected for processing (2)
  • api/src/main/java/me/tofaa/entitylib/extras/VersionChecker.java
  • api/src/main/java/me/tofaa/entitylib/wrapper/WrapperEntityEquipment.java

📝 Walkthrough

Walkthrough

The change delegates version comparison to VersionUtil and updates entity equipment handling to support all EquipmentSlot values. Equipment packets now exclude slots unavailable on the target server version.

Changes

Version compatibility

Layer / File(s)Summary
Version comparison delegation
api/src/main/java/me/tofaa/entitylib/extras/VersionChecker.java
VersionChecker.verifyVersion uses VersionUtil.isOlderThan and retains its existing exception behavior.
Equipment slot compatibility
api/src/main/java/me/tofaa/entitylib/wrapper/WrapperEntityEquipment.java
Equipment storage uses all EquipmentSlot values. Setters use ordinal indexes. Packet creation filters slots based on server-version support for offhand, body, and saddle slots.

Estimated code review effort: 3 (Moderate) | ~20 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title clearly summarizes the main equipment-slot array and older-client compatibility changes.
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@Tofaa2

Copy link
Copy Markdown
Owner

LGTM

@Tofaa2
Tofaa2 merged commit 82430e3 into Tofaa2:masterJul 31, 2026
3 checks passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@steveb05@Tofaa2
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all \u003cpre\u003e\u003ccode\u003e blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks"); } } catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); } })(); (function(){ try { var __m = "github.com"; var __re = new RegExp('^' + "github\\.com" + '
Skip to content

fix(api): keep every equipment slot inside the array and off old clients - #76

Merged
Tofaa2 merged 1 commit into
Tofaa2:masterfrom
steveb05:fix/equipment-slot-bounds
Jul 31, 2026
Merged

fix(api): keep every equipment slot inside the array and off old clients#76
Tofaa2 merged 1 commit into
Tofaa2:masterfrom
steveb05:fix/equipment-slot-bounds

Conversation

@steveb05

@steveb05steveb05 commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

WrapperEntityEquipment has held six item stacks since before the game had more than six equipment slots, and it indexes them by the slot's ordinal. Body arrived in 1.20.5 at ordinal six and saddle in 1.21.5 at ordinal seven, so both fall off the end of that array. The result is that setItem, getItem and clearSlot throw an ArrayIndexOutOfBoundsException for either of them, well before a packet is ever built, which means there is currently no way through this class to saddle a horse or to put armor on a wolf. Sizing the array from the enum rather than from a number typed in by hand also means the next slot Mojang adds costs nothing here.

Holding a slot is only half the problem, though. A slot travels inside the equipment packet as its ordinal from 1.9 onwards, so a server version that predates the slot has no number the client could map back to it: a saddle sent to a 1.21.4 client is a seven that reads as garbage. Because of that, createPacket now leaves out the slots the running server version does not know rather than assuming every slot is safe to send everywhere. The same was already true of the offhand below 1.9, which nothing on the sending side checked.

That check is exposed as isSlotSupported because callers deciding anything else on the same question should not have to keep a private table of version numbers, which is exactly what downstream ends up doing today. It takes the version as an argument so the answer is a pure function of what you ask about, with an overload that fills in the running version for the common case.

One thing turned up on the way. VersionChecker.verifyVersion compared the wrong way around: it threw when the server was newer than the version handed to it rather than older. Its only caller is getOffhand, which therefore raised InvalidVersionException on every server past 1.9, so the guard has been backwards for as long as it has existed and the fix here needed a version check that works.

Verified against packetevents 2.13.0 that body and saddle are ordinals six and seven, that a six long array throws on the latter, and that the slots left in the packet come out contiguous on 1.8.8, 1.9, 1.20.4, 1.20.5, 1.21.4, 1.21.5 and 1.21.11.

Summary by CodeRabbit

  • Bug Fixes

    • Improved server-version compatibility checks for more reliable version detection.
    • Equipment packets now exclude slots unsupported by the target server version, preventing compatibility issues.
  • New Features

    • Added support for dynamically handling all available equipment slots, including newer offhand, body, and saddle slots.
    • Added version-aware equipment slot support checks for improved cross-version compatibility.

The equipment array was written when the game had six slots and was never grown, so the two that arrived since, body in 1.20.5 and saddle in 1.21.5, sit at ordinals six and seven and land outside it. Anyone handing one of those to setItem, getItem or clearSlot got an ArrayIndexOutOfBoundsException, which meant saddling a horse or putting armor on a wolf was not something a caller could express at all. Sizing the array from the enum means the next slot the game adds costs nothing here.
Holding a slot is only half of it, because a slot travels in the packet as its ordinal from 1.9 onwards, and a version that predates the slot has no number it could map back to. Sending a saddle to a 1.21.4 client is a seven it will read as garbage. So createPacket now leaves out what the running server version does not know rather than trusting that every slot is safe everywhere, and isSlotSupported is public because a caller that wants to decide something else on the same question should not have to keep its own table of version numbers. It takes the version as an argument so the answer is a pure function of what is asked, with the running version filled in by the overload.
The version check it would have used was inverted: verifyVersion threw when the server was newer than the version passed rather than older, so getOffhand raised InvalidVersionException on every server past 1.9 and its single caller had been dead for as long as it existed.
@coderabbitai

coderabbitaiBot commented Jul 31, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: b346cdba-60cc-41ec-9942-1bb1d79c932d

📥 Commits

Reviewing files that changed from the base of the PR and between 9ab4d19 and 093f5fc.

📒 Files selected for processing (2)
  • api/src/main/java/me/tofaa/entitylib/extras/VersionChecker.java
  • api/src/main/java/me/tofaa/entitylib/wrapper/WrapperEntityEquipment.java

📝 Walkthrough

Walkthrough

The change delegates version comparison to VersionUtil and updates entity equipment handling to support all EquipmentSlot values. Equipment packets now exclude slots unavailable on the target server version.

Changes

Version compatibility

Layer / File(s)Summary
Version comparison delegation
api/src/main/java/me/tofaa/entitylib/extras/VersionChecker.java
VersionChecker.verifyVersion uses VersionUtil.isOlderThan and retains its existing exception behavior.
Equipment slot compatibility
api/src/main/java/me/tofaa/entitylib/wrapper/WrapperEntityEquipment.java
Equipment storage uses all EquipmentSlot values. Setters use ordinal indexes. Packet creation filters slots based on server-version support for offhand, body, and saddle slots.

Estimated code review effort: 3 (Moderate) | ~20 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title clearly summarizes the main equipment-slot array and older-client compatibility changes.
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@Tofaa2

Copy link
Copy Markdown
Owner

LGTM

@Tofaa2
Tofaa2 merged commit 82430e3 into Tofaa2:masterJul 31, 2026
3 checks passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@steveb05@Tofaa2
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(api): keep every equipment slot inside the array and off old clients - #76

Merged
Tofaa2 merged 1 commit into
Tofaa2:masterfrom
steveb05:fix/equipment-slot-bounds
Jul 31, 2026
Merged

fix(api): keep every equipment slot inside the array and off old clients#76
Tofaa2 merged 1 commit into
Tofaa2:masterfrom
steveb05:fix/equipment-slot-bounds

Conversation

@steveb05

@steveb05steveb05 commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

WrapperEntityEquipment has held six item stacks since before the game had more than six equipment slots, and it indexes them by the slot's ordinal. Body arrived in 1.20.5 at ordinal six and saddle in 1.21.5 at ordinal seven, so both fall off the end of that array. The result is that setItem, getItem and clearSlot throw an ArrayIndexOutOfBoundsException for either of them, well before a packet is ever built, which means there is currently no way through this class to saddle a horse or to put armor on a wolf. Sizing the array from the enum rather than from a number typed in by hand also means the next slot Mojang adds costs nothing here.

Holding a slot is only half the problem, though. A slot travels inside the equipment packet as its ordinal from 1.9 onwards, so a server version that predates the slot has no number the client could map back to it: a saddle sent to a 1.21.4 client is a seven that reads as garbage. Because of that, createPacket now leaves out the slots the running server version does not know rather than assuming every slot is safe to send everywhere. The same was already true of the offhand below 1.9, which nothing on the sending side checked.

That check is exposed as isSlotSupported because callers deciding anything else on the same question should not have to keep a private table of version numbers, which is exactly what downstream ends up doing today. It takes the version as an argument so the answer is a pure function of what you ask about, with an overload that fills in the running version for the common case.

One thing turned up on the way. VersionChecker.verifyVersion compared the wrong way around: it threw when the server was newer than the version handed to it rather than older. Its only caller is getOffhand, which therefore raised InvalidVersionException on every server past 1.9, so the guard has been backwards for as long as it has existed and the fix here needed a version check that works.

Verified against packetevents 2.13.0 that body and saddle are ordinals six and seven, that a six long array throws on the latter, and that the slots left in the packet come out contiguous on 1.8.8, 1.9, 1.20.4, 1.20.5, 1.21.4, 1.21.5 and 1.21.11.

Summary by CodeRabbit

  • Bug Fixes

    • Improved server-version compatibility checks for more reliable version detection.
    • Equipment packets now exclude slots unsupported by the target server version, preventing compatibility issues.
  • New Features

    • Added support for dynamically handling all available equipment slots, including newer offhand, body, and saddle slots.
    • Added version-aware equipment slot support checks for improved cross-version compatibility.

The equipment array was written when the game had six slots and was never grown, so the two that arrived since, body in 1.20.5 and saddle in 1.21.5, sit at ordinals six and seven and land outside it. Anyone handing one of those to setItem, getItem or clearSlot got an ArrayIndexOutOfBoundsException, which meant saddling a horse or putting armor on a wolf was not something a caller could express at all. Sizing the array from the enum means the next slot the game adds costs nothing here.
Holding a slot is only half of it, because a slot travels in the packet as its ordinal from 1.9 onwards, and a version that predates the slot has no number it could map back to. Sending a saddle to a 1.21.4 client is a seven it will read as garbage. So createPacket now leaves out what the running server version does not know rather than trusting that every slot is safe everywhere, and isSlotSupported is public because a caller that wants to decide something else on the same question should not have to keep its own table of version numbers. It takes the version as an argument so the answer is a pure function of what is asked, with the running version filled in by the overload.
The version check it would have used was inverted: verifyVersion threw when the server was newer than the version passed rather than older, so getOffhand raised InvalidVersionException on every server past 1.9 and its single caller had been dead for as long as it existed.
@coderabbitai

coderabbitaiBot commented Jul 31, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: b346cdba-60cc-41ec-9942-1bb1d79c932d

📥 Commits

Reviewing files that changed from the base of the PR and between 9ab4d19 and 093f5fc.

📒 Files selected for processing (2)
  • api/src/main/java/me/tofaa/entitylib/extras/VersionChecker.java
  • api/src/main/java/me/tofaa/entitylib/wrapper/WrapperEntityEquipment.java

📝 Walkthrough

Walkthrough

The change delegates version comparison to VersionUtil and updates entity equipment handling to support all EquipmentSlot values. Equipment packets now exclude slots unavailable on the target server version.

Changes

Version compatibility

Layer / File(s)Summary
Version comparison delegation
api/src/main/java/me/tofaa/entitylib/extras/VersionChecker.java
VersionChecker.verifyVersion uses VersionUtil.isOlderThan and retains its existing exception behavior.
Equipment slot compatibility
api/src/main/java/me/tofaa/entitylib/wrapper/WrapperEntityEquipment.java
Equipment storage uses all EquipmentSlot values. Setters use ordinal indexes. Packet creation filters slots based on server-version support for offhand, body, and saddle slots.

Estimated code review effort: 3 (Moderate) | ~20 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title clearly summarizes the main equipment-slot array and older-client compatibility changes.
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@Tofaa2

Copy link
Copy Markdown
Owner

LGTM

@Tofaa2
Tofaa2 merged commit 82430e3 into Tofaa2:masterJul 31, 2026
3 checks passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@steveb05@Tofaa2
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length \u003e 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(api): keep every equipment slot inside the array and off old clients - #76

Merged
Tofaa2 merged 1 commit into
Tofaa2:masterfrom
steveb05:fix/equipment-slot-bounds
Jul 31, 2026
Merged

fix(api): keep every equipment slot inside the array and off old clients#76
Tofaa2 merged 1 commit into
Tofaa2:masterfrom
steveb05:fix/equipment-slot-bounds

Conversation

@steveb05

@steveb05steveb05 commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

WrapperEntityEquipment has held six item stacks since before the game had more than six equipment slots, and it indexes them by the slot's ordinal. Body arrived in 1.20.5 at ordinal six and saddle in 1.21.5 at ordinal seven, so both fall off the end of that array. The result is that setItem, getItem and clearSlot throw an ArrayIndexOutOfBoundsException for either of them, well before a packet is ever built, which means there is currently no way through this class to saddle a horse or to put armor on a wolf. Sizing the array from the enum rather than from a number typed in by hand also means the next slot Mojang adds costs nothing here.

Holding a slot is only half the problem, though. A slot travels inside the equipment packet as its ordinal from 1.9 onwards, so a server version that predates the slot has no number the client could map back to it: a saddle sent to a 1.21.4 client is a seven that reads as garbage. Because of that, createPacket now leaves out the slots the running server version does not know rather than assuming every slot is safe to send everywhere. The same was already true of the offhand below 1.9, which nothing on the sending side checked.

That check is exposed as isSlotSupported because callers deciding anything else on the same question should not have to keep a private table of version numbers, which is exactly what downstream ends up doing today. It takes the version as an argument so the answer is a pure function of what you ask about, with an overload that fills in the running version for the common case.

One thing turned up on the way. VersionChecker.verifyVersion compared the wrong way around: it threw when the server was newer than the version handed to it rather than older. Its only caller is getOffhand, which therefore raised InvalidVersionException on every server past 1.9, so the guard has been backwards for as long as it has existed and the fix here needed a version check that works.

Verified against packetevents 2.13.0 that body and saddle are ordinals six and seven, that a six long array throws on the latter, and that the slots left in the packet come out contiguous on 1.8.8, 1.9, 1.20.4, 1.20.5, 1.21.4, 1.21.5 and 1.21.11.

Summary by CodeRabbit

  • Bug Fixes

    • Improved server-version compatibility checks for more reliable version detection.
    • Equipment packets now exclude slots unsupported by the target server version, preventing compatibility issues.
  • New Features

    • Added support for dynamically handling all available equipment slots, including newer offhand, body, and saddle slots.
    • Added version-aware equipment slot support checks for improved cross-version compatibility.

The equipment array was written when the game had six slots and was never grown, so the two that arrived since, body in 1.20.5 and saddle in 1.21.5, sit at ordinals six and seven and land outside it. Anyone handing one of those to setItem, getItem or clearSlot got an ArrayIndexOutOfBoundsException, which meant saddling a horse or putting armor on a wolf was not something a caller could express at all. Sizing the array from the enum means the next slot the game adds costs nothing here.
Holding a slot is only half of it, because a slot travels in the packet as its ordinal from 1.9 onwards, and a version that predates the slot has no number it could map back to. Sending a saddle to a 1.21.4 client is a seven it will read as garbage. So createPacket now leaves out what the running server version does not know rather than trusting that every slot is safe everywhere, and isSlotSupported is public because a caller that wants to decide something else on the same question should not have to keep its own table of version numbers. It takes the version as an argument so the answer is a pure function of what is asked, with the running version filled in by the overload.
The version check it would have used was inverted: verifyVersion threw when the server was newer than the version passed rather than older, so getOffhand raised InvalidVersionException on every server past 1.9 and its single caller had been dead for as long as it existed.
@coderabbitai

coderabbitaiBot commented Jul 31, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: b346cdba-60cc-41ec-9942-1bb1d79c932d

📥 Commits

Reviewing files that changed from the base of the PR and between 9ab4d19 and 093f5fc.

📒 Files selected for processing (2)
  • api/src/main/java/me/tofaa/entitylib/extras/VersionChecker.java
  • api/src/main/java/me/tofaa/entitylib/wrapper/WrapperEntityEquipment.java

📝 Walkthrough

Walkthrough

The change delegates version comparison to VersionUtil and updates entity equipment handling to support all EquipmentSlot values. Equipment packets now exclude slots unavailable on the target server version.

Changes

Version compatibility

Layer / File(s)Summary
Version comparison delegation
api/src/main/java/me/tofaa/entitylib/extras/VersionChecker.java
VersionChecker.verifyVersion uses VersionUtil.isOlderThan and retains its existing exception behavior.
Equipment slot compatibility
api/src/main/java/me/tofaa/entitylib/wrapper/WrapperEntityEquipment.java
Equipment storage uses all EquipmentSlot values. Setters use ordinal indexes. Packet creation filters slots based on server-version support for offhand, body, and saddle slots.

Estimated code review effort: 3 (Moderate) | ~20 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title clearly summarizes the main equipment-slot array and older-client compatibility changes.
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@Tofaa2

Copy link
Copy Markdown
Owner

LGTM

@Tofaa2
Tofaa2 merged commit 82430e3 into Tofaa2:masterJul 31, 2026
3 checks passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@steveb05@Tofaa2
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

fix(api): keep every equipment slot inside the array and off old clients - #76

Merged
Tofaa2 merged 1 commit into
Tofaa2:masterfrom
steveb05:fix/equipment-slot-bounds
Jul 31, 2026
Merged

fix(api): keep every equipment slot inside the array and off old clients#76
Tofaa2 merged 1 commit into
Tofaa2:masterfrom
steveb05:fix/equipment-slot-bounds

Conversation

@steveb05

@steveb05steveb05 commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

WrapperEntityEquipment has held six item stacks since before the game had more than six equipment slots, and it indexes them by the slot's ordinal. Body arrived in 1.20.5 at ordinal six and saddle in 1.21.5 at ordinal seven, so both fall off the end of that array. The result is that setItem, getItem and clearSlot throw an ArrayIndexOutOfBoundsException for either of them, well before a packet is ever built, which means there is currently no way through this class to saddle a horse or to put armor on a wolf. Sizing the array from the enum rather than from a number typed in by hand also means the next slot Mojang adds costs nothing here.

Holding a slot is only half the problem, though. A slot travels inside the equipment packet as its ordinal from 1.9 onwards, so a server version that predates the slot has no number the client could map back to it: a saddle sent to a 1.21.4 client is a seven that reads as garbage. Because of that, createPacket now leaves out the slots the running server version does not know rather than assuming every slot is safe to send everywhere. The same was already true of the offhand below 1.9, which nothing on the sending side checked.

That check is exposed as isSlotSupported because callers deciding anything else on the same question should not have to keep a private table of version numbers, which is exactly what downstream ends up doing today. It takes the version as an argument so the answer is a pure function of what you ask about, with an overload that fills in the running version for the common case.

One thing turned up on the way. VersionChecker.verifyVersion compared the wrong way around: it threw when the server was newer than the version handed to it rather than older. Its only caller is getOffhand, which therefore raised InvalidVersionException on every server past 1.9, so the guard has been backwards for as long as it has existed and the fix here needed a version check that works.

Verified against packetevents 2.13.0 that body and saddle are ordinals six and seven, that a six long array throws on the latter, and that the slots left in the packet come out contiguous on 1.8.8, 1.9, 1.20.4, 1.20.5, 1.21.4, 1.21.5 and 1.21.11.

Summary by CodeRabbit

  • Bug Fixes

    • Improved server-version compatibility checks for more reliable version detection.
    • Equipment packets now exclude slots unsupported by the target server version, preventing compatibility issues.
  • New Features

    • Added support for dynamically handling all available equipment slots, including newer offhand, body, and saddle slots.
    • Added version-aware equipment slot support checks for improved cross-version compatibility.

The equipment array was written when the game had six slots and was never grown, so the two that arrived since, body in 1.20.5 and saddle in 1.21.5, sit at ordinals six and seven and land outside it. Anyone handing one of those to setItem, getItem or clearSlot got an ArrayIndexOutOfBoundsException, which meant saddling a horse or putting armor on a wolf was not something a caller could express at all. Sizing the array from the enum means the next slot the game adds costs nothing here.
Holding a slot is only half of it, because a slot travels in the packet as its ordinal from 1.9 onwards, and a version that predates the slot has no number it could map back to. Sending a saddle to a 1.21.4 client is a seven it will read as garbage. So createPacket now leaves out what the running server version does not know rather than trusting that every slot is safe everywhere, and isSlotSupported is public because a caller that wants to decide something else on the same question should not have to keep its own table of version numbers. It takes the version as an argument so the answer is a pure function of what is asked, with the running version filled in by the overload.
The version check it would have used was inverted: verifyVersion threw when the server was newer than the version passed rather than older, so getOffhand raised InvalidVersionException on every server past 1.9 and its single caller had been dead for as long as it existed.
@coderabbitai

coderabbitaiBot commented Jul 31, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: b346cdba-60cc-41ec-9942-1bb1d79c932d

📥 Commits

Reviewing files that changed from the base of the PR and between 9ab4d19 and 093f5fc.

📒 Files selected for processing (2)
  • api/src/main/java/me/tofaa/entitylib/extras/VersionChecker.java
  • api/src/main/java/me/tofaa/entitylib/wrapper/WrapperEntityEquipment.java

📝 Walkthrough

Walkthrough

The change delegates version comparison to VersionUtil and updates entity equipment handling to support all EquipmentSlot values. Equipment packets now exclude slots unavailable on the target server version.

Changes

Version compatibility

Layer / File(s)Summary
Version comparison delegation
api/src/main/java/me/tofaa/entitylib/extras/VersionChecker.java
VersionChecker.verifyVersion uses VersionUtil.isOlderThan and retains its existing exception behavior.
Equipment slot compatibility
api/src/main/java/me/tofaa/entitylib/wrapper/WrapperEntityEquipment.java
Equipment storage uses all EquipmentSlot values. Setters use ordinal indexes. Packet creation filters slots based on server-version support for offhand, body, and saddle slots.

Estimated code review effort: 3 (Moderate) | ~20 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title clearly summarizes the main equipment-slot array and older-client compatibility changes.
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@Tofaa2

Copy link
Copy Markdown
Owner

LGTM

@Tofaa2
Tofaa2 merged commit 82430e3 into Tofaa2:masterJul 31, 2026
3 checks passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@steveb05@Tofaa2
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(api): keep every equipment slot inside the array and off old clients - #76

Merged
Tofaa2 merged 1 commit into
Tofaa2:masterfrom
steveb05:fix/equipment-slot-bounds
Jul 31, 2026
Merged

fix(api): keep every equipment slot inside the array and off old clients#76
Tofaa2 merged 1 commit into
Tofaa2:masterfrom
steveb05:fix/equipment-slot-bounds

Conversation

@steveb05

@steveb05steveb05 commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

WrapperEntityEquipment has held six item stacks since before the game had more than six equipment slots, and it indexes them by the slot's ordinal. Body arrived in 1.20.5 at ordinal six and saddle in 1.21.5 at ordinal seven, so both fall off the end of that array. The result is that setItem, getItem and clearSlot throw an ArrayIndexOutOfBoundsException for either of them, well before a packet is ever built, which means there is currently no way through this class to saddle a horse or to put armor on a wolf. Sizing the array from the enum rather than from a number typed in by hand also means the next slot Mojang adds costs nothing here.

Holding a slot is only half the problem, though. A slot travels inside the equipment packet as its ordinal from 1.9 onwards, so a server version that predates the slot has no number the client could map back to it: a saddle sent to a 1.21.4 client is a seven that reads as garbage. Because of that, createPacket now leaves out the slots the running server version does not know rather than assuming every slot is safe to send everywhere. The same was already true of the offhand below 1.9, which nothing on the sending side checked.

That check is exposed as isSlotSupported because callers deciding anything else on the same question should not have to keep a private table of version numbers, which is exactly what downstream ends up doing today. It takes the version as an argument so the answer is a pure function of what you ask about, with an overload that fills in the running version for the common case.

One thing turned up on the way. VersionChecker.verifyVersion compared the wrong way around: it threw when the server was newer than the version handed to it rather than older. Its only caller is getOffhand, which therefore raised InvalidVersionException on every server past 1.9, so the guard has been backwards for as long as it has existed and the fix here needed a version check that works.

Verified against packetevents 2.13.0 that body and saddle are ordinals six and seven, that a six long array throws on the latter, and that the slots left in the packet come out contiguous on 1.8.8, 1.9, 1.20.4, 1.20.5, 1.21.4, 1.21.5 and 1.21.11.

Summary by CodeRabbit

  • Bug Fixes

    • Improved server-version compatibility checks for more reliable version detection.
    • Equipment packets now exclude slots unsupported by the target server version, preventing compatibility issues.
  • New Features

    • Added support for dynamically handling all available equipment slots, including newer offhand, body, and saddle slots.
    • Added version-aware equipment slot support checks for improved cross-version compatibility.

The equipment array was written when the game had six slots and was never grown, so the two that arrived since, body in 1.20.5 and saddle in 1.21.5, sit at ordinals six and seven and land outside it. Anyone handing one of those to setItem, getItem or clearSlot got an ArrayIndexOutOfBoundsException, which meant saddling a horse or putting armor on a wolf was not something a caller could express at all. Sizing the array from the enum means the next slot the game adds costs nothing here.
Holding a slot is only half of it, because a slot travels in the packet as its ordinal from 1.9 onwards, and a version that predates the slot has no number it could map back to. Sending a saddle to a 1.21.4 client is a seven it will read as garbage. So createPacket now leaves out what the running server version does not know rather than trusting that every slot is safe everywhere, and isSlotSupported is public because a caller that wants to decide something else on the same question should not have to keep its own table of version numbers. It takes the version as an argument so the answer is a pure function of what is asked, with the running version filled in by the overload.
The version check it would have used was inverted: verifyVersion threw when the server was newer than the version passed rather than older, so getOffhand raised InvalidVersionException on every server past 1.9 and its single caller had been dead for as long as it existed.
@coderabbitai

coderabbitaiBot commented Jul 31, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: b346cdba-60cc-41ec-9942-1bb1d79c932d

📥 Commits

Reviewing files that changed from the base of the PR and between 9ab4d19 and 093f5fc.

📒 Files selected for processing (2)
  • api/src/main/java/me/tofaa/entitylib/extras/VersionChecker.java
  • api/src/main/java/me/tofaa/entitylib/wrapper/WrapperEntityEquipment.java

📝 Walkthrough

Walkthrough

The change delegates version comparison to VersionUtil and updates entity equipment handling to support all EquipmentSlot values. Equipment packets now exclude slots unavailable on the target server version.

Changes

Version compatibility

Layer / File(s)Summary
Version comparison delegation
api/src/main/java/me/tofaa/entitylib/extras/VersionChecker.java
VersionChecker.verifyVersion uses VersionUtil.isOlderThan and retains its existing exception behavior.
Equipment slot compatibility
api/src/main/java/me/tofaa/entitylib/wrapper/WrapperEntityEquipment.java
Equipment storage uses all EquipmentSlot values. Setters use ordinal indexes. Packet creation filters slots based on server-version support for offhand, body, and saddle slots.

Estimated code review effort: 3 (Moderate) | ~20 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title clearly summarizes the main equipment-slot array and older-client compatibility changes.
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@Tofaa2

Copy link
Copy Markdown
Owner

LGTM

@Tofaa2
Tofaa2 merged commit 82430e3 into Tofaa2:masterJul 31, 2026
3 checks passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@steveb05@Tofaa2
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(api): keep every equipment slot inside the array and off old clients - #76

Merged
Tofaa2 merged 1 commit into
Tofaa2:masterfrom
steveb05:fix/equipment-slot-bounds
Jul 31, 2026
Merged

fix(api): keep every equipment slot inside the array and off old clients#76
Tofaa2 merged 1 commit into
Tofaa2:masterfrom
steveb05:fix/equipment-slot-bounds

Conversation

@steveb05

@steveb05steveb05 commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

WrapperEntityEquipment has held six item stacks since before the game had more than six equipment slots, and it indexes them by the slot's ordinal. Body arrived in 1.20.5 at ordinal six and saddle in 1.21.5 at ordinal seven, so both fall off the end of that array. The result is that setItem, getItem and clearSlot throw an ArrayIndexOutOfBoundsException for either of them, well before a packet is ever built, which means there is currently no way through this class to saddle a horse or to put armor on a wolf. Sizing the array from the enum rather than from a number typed in by hand also means the next slot Mojang adds costs nothing here.

Holding a slot is only half the problem, though. A slot travels inside the equipment packet as its ordinal from 1.9 onwards, so a server version that predates the slot has no number the client could map back to it: a saddle sent to a 1.21.4 client is a seven that reads as garbage. Because of that, createPacket now leaves out the slots the running server version does not know rather than assuming every slot is safe to send everywhere. The same was already true of the offhand below 1.9, which nothing on the sending side checked.

That check is exposed as isSlotSupported because callers deciding anything else on the same question should not have to keep a private table of version numbers, which is exactly what downstream ends up doing today. It takes the version as an argument so the answer is a pure function of what you ask about, with an overload that fills in the running version for the common case.

One thing turned up on the way. VersionChecker.verifyVersion compared the wrong way around: it threw when the server was newer than the version handed to it rather than older. Its only caller is getOffhand, which therefore raised InvalidVersionException on every server past 1.9, so the guard has been backwards for as long as it has existed and the fix here needed a version check that works.

Verified against packetevents 2.13.0 that body and saddle are ordinals six and seven, that a six long array throws on the latter, and that the slots left in the packet come out contiguous on 1.8.8, 1.9, 1.20.4, 1.20.5, 1.21.4, 1.21.5 and 1.21.11.

Summary by CodeRabbit

  • Bug Fixes

    • Improved server-version compatibility checks for more reliable version detection.
    • Equipment packets now exclude slots unsupported by the target server version, preventing compatibility issues.
  • New Features

    • Added support for dynamically handling all available equipment slots, including newer offhand, body, and saddle slots.
    • Added version-aware equipment slot support checks for improved cross-version compatibility.

The equipment array was written when the game had six slots and was never grown, so the two that arrived since, body in 1.20.5 and saddle in 1.21.5, sit at ordinals six and seven and land outside it. Anyone handing one of those to setItem, getItem or clearSlot got an ArrayIndexOutOfBoundsException, which meant saddling a horse or putting armor on a wolf was not something a caller could express at all. Sizing the array from the enum means the next slot the game adds costs nothing here.
Holding a slot is only half of it, because a slot travels in the packet as its ordinal from 1.9 onwards, and a version that predates the slot has no number it could map back to. Sending a saddle to a 1.21.4 client is a seven it will read as garbage. So createPacket now leaves out what the running server version does not know rather than trusting that every slot is safe everywhere, and isSlotSupported is public because a caller that wants to decide something else on the same question should not have to keep its own table of version numbers. It takes the version as an argument so the answer is a pure function of what is asked, with the running version filled in by the overload.
The version check it would have used was inverted: verifyVersion threw when the server was newer than the version passed rather than older, so getOffhand raised InvalidVersionException on every server past 1.9 and its single caller had been dead for as long as it existed.
@coderabbitai

coderabbitaiBot commented Jul 31, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: b346cdba-60cc-41ec-9942-1bb1d79c932d

📥 Commits

Reviewing files that changed from the base of the PR and between 9ab4d19 and 093f5fc.

📒 Files selected for processing (2)
  • api/src/main/java/me/tofaa/entitylib/extras/VersionChecker.java
  • api/src/main/java/me/tofaa/entitylib/wrapper/WrapperEntityEquipment.java

📝 Walkthrough

Walkthrough

The change delegates version comparison to VersionUtil and updates entity equipment handling to support all EquipmentSlot values. Equipment packets now exclude slots unavailable on the target server version.

Changes

Version compatibility

Layer / File(s)Summary
Version comparison delegation
api/src/main/java/me/tofaa/entitylib/extras/VersionChecker.java
VersionChecker.verifyVersion uses VersionUtil.isOlderThan and retains its existing exception behavior.
Equipment slot compatibility
api/src/main/java/me/tofaa/entitylib/wrapper/WrapperEntityEquipment.java
Equipment storage uses all EquipmentSlot values. Setters use ordinal indexes. Packet creation filters slots based on server-version support for offhand, body, and saddle slots.

Estimated code review effort: 3 (Moderate) | ~20 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title clearly summarizes the main equipment-slot array and older-client compatibility changes.
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@Tofaa2

Copy link
Copy Markdown
Owner

LGTM

@Tofaa2
Tofaa2 merged commit 82430e3 into Tofaa2:masterJul 31, 2026
3 checks passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@steveb05@Tofaa2
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

fix(api): keep every equipment slot inside the array and off old clients - #76

Merged
Tofaa2 merged 1 commit into
Tofaa2:masterfrom
steveb05:fix/equipment-slot-bounds
Jul 31, 2026
Merged

fix(api): keep every equipment slot inside the array and off old clients#76
Tofaa2 merged 1 commit into
Tofaa2:masterfrom
steveb05:fix/equipment-slot-bounds

Conversation

@steveb05

@steveb05steveb05 commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

WrapperEntityEquipment has held six item stacks since before the game had more than six equipment slots, and it indexes them by the slot's ordinal. Body arrived in 1.20.5 at ordinal six and saddle in 1.21.5 at ordinal seven, so both fall off the end of that array. The result is that setItem, getItem and clearSlot throw an ArrayIndexOutOfBoundsException for either of them, well before a packet is ever built, which means there is currently no way through this class to saddle a horse or to put armor on a wolf. Sizing the array from the enum rather than from a number typed in by hand also means the next slot Mojang adds costs nothing here.

Holding a slot is only half the problem, though. A slot travels inside the equipment packet as its ordinal from 1.9 onwards, so a server version that predates the slot has no number the client could map back to it: a saddle sent to a 1.21.4 client is a seven that reads as garbage. Because of that, createPacket now leaves out the slots the running server version does not know rather than assuming every slot is safe to send everywhere. The same was already true of the offhand below 1.9, which nothing on the sending side checked.

That check is exposed as isSlotSupported because callers deciding anything else on the same question should not have to keep a private table of version numbers, which is exactly what downstream ends up doing today. It takes the version as an argument so the answer is a pure function of what you ask about, with an overload that fills in the running version for the common case.

One thing turned up on the way. VersionChecker.verifyVersion compared the wrong way around: it threw when the server was newer than the version handed to it rather than older. Its only caller is getOffhand, which therefore raised InvalidVersionException on every server past 1.9, so the guard has been backwards for as long as it has existed and the fix here needed a version check that works.

Verified against packetevents 2.13.0 that body and saddle are ordinals six and seven, that a six long array throws on the latter, and that the slots left in the packet come out contiguous on 1.8.8, 1.9, 1.20.4, 1.20.5, 1.21.4, 1.21.5 and 1.21.11.

Summary by CodeRabbit

  • Bug Fixes

    • Improved server-version compatibility checks for more reliable version detection.
    • Equipment packets now exclude slots unsupported by the target server version, preventing compatibility issues.
  • New Features

    • Added support for dynamically handling all available equipment slots, including newer offhand, body, and saddle slots.
    • Added version-aware equipment slot support checks for improved cross-version compatibility.

The equipment array was written when the game had six slots and was never grown, so the two that arrived since, body in 1.20.5 and saddle in 1.21.5, sit at ordinals six and seven and land outside it. Anyone handing one of those to setItem, getItem or clearSlot got an ArrayIndexOutOfBoundsException, which meant saddling a horse or putting armor on a wolf was not something a caller could express at all. Sizing the array from the enum means the next slot the game adds costs nothing here.
Holding a slot is only half of it, because a slot travels in the packet as its ordinal from 1.9 onwards, and a version that predates the slot has no number it could map back to. Sending a saddle to a 1.21.4 client is a seven it will read as garbage. So createPacket now leaves out what the running server version does not know rather than trusting that every slot is safe everywhere, and isSlotSupported is public because a caller that wants to decide something else on the same question should not have to keep its own table of version numbers. It takes the version as an argument so the answer is a pure function of what is asked, with the running version filled in by the overload.
The version check it would have used was inverted: verifyVersion threw when the server was newer than the version passed rather than older, so getOffhand raised InvalidVersionException on every server past 1.9 and its single caller had been dead for as long as it existed.
@coderabbitai

coderabbitaiBot commented Jul 31, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: b346cdba-60cc-41ec-9942-1bb1d79c932d

📥 Commits

Reviewing files that changed from the base of the PR and between 9ab4d19 and 093f5fc.

📒 Files selected for processing (2)
  • api/src/main/java/me/tofaa/entitylib/extras/VersionChecker.java
  • api/src/main/java/me/tofaa/entitylib/wrapper/WrapperEntityEquipment.java

📝 Walkthrough

Walkthrough

The change delegates version comparison to VersionUtil and updates entity equipment handling to support all EquipmentSlot values. Equipment packets now exclude slots unavailable on the target server version.

Changes

Version compatibility

Layer / File(s)Summary
Version comparison delegation
api/src/main/java/me/tofaa/entitylib/extras/VersionChecker.java
VersionChecker.verifyVersion uses VersionUtil.isOlderThan and retains its existing exception behavior.
Equipment slot compatibility
api/src/main/java/me/tofaa/entitylib/wrapper/WrapperEntityEquipment.java
Equipment storage uses all EquipmentSlot values. Setters use ordinal indexes. Packet creation filters slots based on server-version support for offhand, body, and saddle slots.

Estimated code review effort: 3 (Moderate) | ~20 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title clearly summarizes the main equipment-slot array and older-client compatibility changes.
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@Tofaa2

Copy link
Copy Markdown
Owner

LGTM

@Tofaa2
Tofaa2 merged commit 82430e3 into Tofaa2:masterJul 31, 2026
3 checks passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@steveb05@Tofaa2