Compare commits

...
59 Commits
Author SHA1 Message Date
Florian Reuth d8e4746053 ViaRewind 4.2.0
Signed-off-by: Florian Reuth <git@florianreuth.de>
2026-09-18 17:06:16 +02:00
BeanesandFlorian Reuth 82a3ee84e0 Replace 1.9.4 mappings with 1.9 mappings, fixes invalid sound (#689)
Signed-off-by: Florian Reuth <git@florianreuth.de>
Co-authored-by: Florian Reuth <git@florianreuth.de>
2026-09-16 18:28:43 +02:00
Beanes 9df4d11f8c Implement 1.9->1.8 command block translation (#692)
* Implement 1.9->1.8 command block translation

* Inline methods
2026-09-16 17:30:23 +02:00
Term4 c92916a265 Send the delayed potion spawn's velocity as its own packet in 1.9->1.8 (#722) 2026-09-16 17:27:33 +02:00
Term4 dc04defeb6 Pass view-relative teleport flags through in 1.9->1.8 (#719)
1.8 handles the rotation-relative position flags (0x08/0x10) natively,
and resolving them against the tracked rotation snaps the camera to a
stale look on every teleport. Keep the rotation bits and their deltas on
the wire and only resolve the position bits.

Since the client applies kept deltas to its own live look, its teleport
confirm echoes a rotation the tracker cannot predict - match those
confirms on position only and adopt the echoed rotation.
2026-09-16 17:27:04 +02:00
Beanes c7edeba340 Fix using an item in air packet not matching the 1.8 packet (#718) 2026-09-16 17:26:34 +02:00
dependabot[bot] d232bd020f [ci skip] Update GH actions (#720)
Bumps the all-dependencies group with 1 update: [actions/setup-java](https://github.com/actions/setup-java).


Updates `actions/setup-java` from 5.7.0 to 6.0.0
- [Release notes](https://github.com/actions/setup-java/releases)
- [Commits](https://github.com/actions/setup-java/compare/b6effb05e454b25005698d916606bdc6ffcbf961...dd06d9cba3e5552c54d9f8ea23572deb30010f7c)

---
updated-dependencies:
- dependency-name: actions/setup-java
  dependency-version: 6.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: all-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-02 15:29:08 +02:00
dependabot[bot] 2525d42666 [ci skip] Update deps (#721)
Bumps the all-dependencies group with 1 update: [gradle-wrapper](https://github.com/gradle/gradle).


Updates `gradle-wrapper` from 9.7.0 to 9.7.1
- [Release notes](https://github.com/gradle/gradle/releases)
- [Commits](https://github.com/gradle/gradle/compare/v9.7.0...v9.7.1)

---
updated-dependencies:
- dependency-name: gradle-wrapper
  dependency-version: 9.7.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: all-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-02 15:28:43 +02:00
Florian Reuth 4d51a62410 Fix class token replacing
Signed-off-by: Florian Reuth <git@florianreuth.de>
2026-08-19 19:56:12 +02:00
Florian Reuth b177e048dd Update BaseProject
Signed-off-by: Florian Reuth <git@florianreuth.de>
2026-08-19 19:32:39 +02:00
Florian Reuth 7577279fcb Update GH files
Signed-off-by: Florian Reuth <git@florianreuth.de>
2026-08-19 19:29:40 +02:00
dependabot[bot] e3211f99ef [ci skip] Bump gradle-wrapper from 9.6.1 to 9.7.0 (#717)
Bumps [gradle-wrapper](https://github.com/gradle/gradle) from 9.6.1 to 9.7.0.
- [Release notes](https://github.com/gradle/gradle/releases)
- [Commits](https://github.com/gradle/gradle/compare/v9.6.1...v9.7.0)

---
updated-dependencies:
- dependency-name: gradle-wrapper
  dependency-version: 9.7.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-17 15:06:58 +02:00
Florian Reuth d562f9745a Update Via* API usage
Signed-off-by: Florian Reuth <git@florianreuth.de>
2026-08-13 17:48:09 +02:00
dependabot[bot] 7339df9fdb [ci skip] Bump gradle/actions/setup-gradle from 6.2.0 to 6.3.0 (#713)
Bumps [gradle/actions/setup-gradle](https://github.com/gradle/actions) from 6.2.0 to 6.3.0.
- [Release notes](https://github.com/gradle/actions/releases)
- [Commits](https://github.com/gradle/actions/compare/3f131e8634966bd73d06cc69884922b02e6faf92...9c971963bec38e04b3d30dcc455b5382be2fdbfb)

---
updated-dependencies:
- dependency-name: gradle/actions/setup-gradle
  dependency-version: 6.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-13 17:17:59 +02:00
dependabot[bot] d98d5bed9b [ci skip] Bump actions/setup-java from 5.6.0 to 5.7.0 (#715)
Bumps [actions/setup-java](https://github.com/actions/setup-java) from 5.6.0 to 5.7.0.
- [Release notes](https://github.com/actions/setup-java/releases)
- [Commits](https://github.com/actions/setup-java/compare/03ad4de0992f5dab5e18fcb136590ce7c4a0ac95...b6effb05e454b25005698d916606bdc6ffcbf961)

---
updated-dependencies:
- dependency-name: actions/setup-java
  dependency-version: 5.7.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-13 17:17:44 +02:00
dependabot[bot] 786529efbe [ci skip] Bump actions/setup-java from 5.5.0 to 5.6.0 (#712)
Bumps [actions/setup-java](https://github.com/actions/setup-java) from 5.5.0 to 5.6.0.
- [Release notes](https://github.com/actions/setup-java/releases)
- [Commits](https://github.com/actions/setup-java/compare/0f481fcb613427c0f801b606911222b5b6f3083a...03ad4de0992f5dab5e18fcb136590ce7c4a0ac95)

---
updated-dependencies:
- dependency-name: actions/setup-java
  dependency-version: 5.6.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-28 18:41:34 +02:00
dependabot[bot] 71546a7699 [ci skip] Bump actions/checkout from 7.0.0 to 7.0.1 (#711)
Bumps [actions/checkout](https://github.com/actions/checkout) from 7.0.0 to 7.0.1.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0...3d3c42e5aac5ba805825da76410c181273ba90b1)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: 7.0.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-28 18:41:24 +02:00
Term4andFlorian Reuth 9e7c853a5d Fix splash potions turning into drinkable potions in 1.9->1.8 (#703)
Co-authored-by: Florian Reuth <git@florianreuth.de>
2026-07-25 21:51:08 +02:00
315a1a0fab Handle single player inventory slot updates in 1.9->1.8 (#699)
Co-authored-by: Beanes <sikyserver@gmail.com>
Co-authored-by: Florian Reuth <git@florianreuth.de>
2026-07-25 21:27:09 +02:00
BeanesandFlorian Reuth 7a2898e44a Send server titles back to the client after cooldown visualization in 1.9->1.8 (#693)
Fixes https://github.com/ViaVersion/ViaRewind/issues/160

Co-authored-by: Florian Reuth <git@florianreuth.de>
2026-07-25 21:06:50 +02:00
Beanes 5783fa98e9 Queue teleport confirmation acks to match client behaviour in 1.9->1.8 (#704)
Fixes https://github.com/ViaVersion/ViaRewind/issues/469
2026-07-25 20:12:44 +02:00
Term4 77d9d9143e Fix double->int*32 conversion rounding in 1.9->1.8 (#707) 2026-07-25 20:11:19 +02:00
Term4 ede1920686 Fix use animation cancelling upon status updates in 1.9->1.8 (#708)
Fixes https://github.com/ViaVersion/ViaRewind/issues/520
2026-07-25 19:00:29 +02:00
dependabot[bot] 189249478f [ci skip] Bump actions/setup-java from 5.4.0 to 5.5.0 (#710)
Bumps [actions/setup-java](https://github.com/actions/setup-java) from 5.4.0 to 5.5.0.
- [Release notes](https://github.com/actions/setup-java/releases)
- [Commits](https://github.com/actions/setup-java/compare/1bcf9fb12cf4aa7d266a90ae39939e61372fe520...0f481fcb613427c0f801b606911222b5b6f3083a)

---
updated-dependencies:
- dependency-name: actions/setup-java
  dependency-version: 5.5.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-25 18:49:53 +02:00
dependabot[bot] 6c19760af6 Bump actions/setup-java from 5.3.0 to 5.4.0 (#705)
Bumps [actions/setup-java](https://github.com/actions/setup-java) from 5.3.0 to 5.4.0.
- [Release notes](https://github.com/actions/setup-java/releases)
- [Commits](https://github.com/actions/setup-java/compare/ad2b38190b15e4d6bdf0c97fb4fca8412226d287...1bcf9fb12cf4aa7d266a90ae39939e61372fe520)

---
updated-dependencies:
- dependency-name: actions/setup-java
  dependency-version: 5.4.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-12 20:22:51 +02:00
dependabot[bot] 69f8213445 Bump gradle-wrapper from 9.6.0 to 9.6.1 (#706)
Bumps [gradle-wrapper](https://github.com/gradle/gradle) from 9.6.0 to 9.6.1.
- [Release notes](https://github.com/gradle/gradle/releases)
- [Commits](https://github.com/gradle/gradle/compare/v9.6.0...v9.6.1)

---
updated-dependencies:
- dependency-name: gradle-wrapper
  dependency-version: 9.6.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-12 20:22:43 +02:00
Florian Reuth f95871bdc5 Start 4.1.4 cycle
Signed-off-by: Florian Reuth <git@florianreuth.de>
2026-07-12 20:18:36 +02:00
Florian Reuth 7d6c75ad31 ViaRewind 4.1.3
Signed-off-by: Florian Reuth <git@florianreuth.de>
2026-07-12 20:13:54 +02:00
Nassim Jahnke 0bb7be88ff Update mapping files 2026-07-12 11:05:47 +02:00
Nassim Jahnke 4b7264c944 Update VV usage 2026-07-09 15:22:35 +02:00
Beanes db6afa5b1b Fix potential 1.7 teleport confirmation desync (#695) 2026-07-02 16:21:32 +02:00
Beanes 02f55edb0c Fix edge cases with chat newline rewriter; make it more like vanilla (#696) 2026-07-02 16:14:48 +02:00
dependabot[bot] 5ef900a61e [ci skip] Bump actions/checkout from 6.0.3 to 7.0.0 (#701)
Bumps [actions/checkout](https://github.com/actions/checkout) from 6.0.3 to 7.0.0.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/df4cb1c069e1874edd31b4311f1884172cec0e10...9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: 7.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-02 15:32:45 +02:00
dependabot[bot] 4fe7e35f6b [ci skip] Bump gradle-wrapper from 9.5.1 to 9.6.0 (#700)
Bumps [gradle-wrapper](https://github.com/gradle/gradle) from 9.5.1 to 9.6.0.
- [Release notes](https://github.com/gradle/gradle/releases)
- [Commits](https://github.com/gradle/gradle/compare/v9.5.1...v9.6.0)

---
updated-dependencies:
- dependency-name: gradle-wrapper
  dependency-version: 9.6.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-02 15:32:30 +02:00
dependabot[bot] 1af08ced41 [ci skip] Bump actions/setup-java from 5.2.0 to 5.3.0 (#702)
Bumps [actions/setup-java](https://github.com/actions/setup-java) from 5.2.0 to 5.3.0.
- [Release notes](https://github.com/actions/setup-java/releases)
- [Commits](https://github.com/actions/setup-java/compare/be666c2fcd27ec809703dec50e508c2fdc7f6654...ad2b38190b15e4d6bdf0c97fb4fca8412226d287)

---
updated-dependencies:
- dependency-name: actions/setup-java
  dependency-version: 5.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-02 15:32:20 +02:00
Nassim Jahnke fd1d3ed221 Update VV use 2026-07-01 21:05:36 +02:00
dependabot[bot] 1d6ef8833f [ci skip] Bump gradle/actions from 6.1.0 to 6.2.0 (#698)
Bumps [gradle/actions](https://github.com/gradle/actions) from 6.1.0 to 6.2.0.
- [Release notes](https://github.com/gradle/actions/releases)
- [Commits](https://github.com/gradle/actions/compare/50e97c2cd7a37755bbfafc9c5b7cafaece252f6e...3f131e8634966bd73d06cc69884922b02e6faf92)

---
updated-dependencies:
- dependency-name: gradle/actions
  dependency-version: 6.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-24 05:15:50 +02:00
Beanes f2e2f18511 Fix custom sound handling in 1.9->1.8 (#691) 2026-06-20 00:16:36 +02:00
Florian Reuth 2adf7757f0 Start 4.1.3 cycle
Signed-off-by: Florian Reuth <git@florianreuth.de>
2026-06-20 00:13:53 +02:00
Florian Reuth 4dd3de2ce9 ViaRewind 4.1.2
Signed-off-by: Florian Reuth <git@florianreuth.de>
2026-06-19 21:52:24 +02:00
Florian Reuth ddbdbdb1cc Cleanup code to reproduce the original tag
Signed-off-by: Florian Reuth <git@florianreuth.de>
2026-06-19 21:45:16 +02:00
Beanes 28b4998912 Use gray color for elytra & brown color for shield in 1.9->1.8 (#688) 2026-06-19 21:38:32 +02:00
Beanes f9b0fd5c5a Custom payload fixes (#687)
* Fix remaining bytes issue of 1.7.10 custom payload

* Properly rewrite 1.7 books to 1.8 books

* Small code cleanup
2026-06-19 21:35:54 +02:00
Florian Reuth a676368588 26.2 2026-06-19 21:32:14 +02:00
Beanes 966231ad1c Null check storages in global tasks (#690)
Fixes https://github.com/ViaVersion/ViaRewind/issues/564
Fixes https://github.com/ViaVersion/ViaRewind/issues/680
2026-06-19 21:21:00 +02:00
dependabot[bot] d73c2198ea [ci skip] Bump actions/checkout from 6.0.2 to 6.0.3 (#686)
Bumps [actions/checkout](https://github.com/actions/checkout) from 6.0.2 to 6.0.3.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/de0fac2e4500dabe0009e67214ff5f5447ce83dd...df4cb1c069e1874edd31b4311f1884172cec0e10)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: 6.0.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-18 21:57:00 +02:00
Florian Reuth 6f319932be Start 4.1.2 cycle 2026-06-09 20:50:39 +02:00
Beanes f172de2742 Rewrite to direct chunk buffer handling in 1.8->1.7 (#670)
* Use netty directly for chunks in 1.8->1.7, additionally support native compression by velocity for 1.7 chunk compression

* Refactor the velocity code a bit
2026-06-09 20:49:09 +02:00
dependabot[bot] ca25da9445 [ci skip] Bump gradle-wrapper from 9.5.0 to 9.5.1 (#683)
Bumps [gradle-wrapper](https://github.com/gradle/gradle) from 9.5.0 to 9.5.1.
- [Release notes](https://github.com/gradle/gradle/releases)
- [Commits](https://github.com/gradle/gradle/compare/v9.5.0...v9.5.1)

---
updated-dependencies:
- dependency-name: gradle-wrapper
  dependency-version: 9.5.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-07 01:41:05 +02:00
dependabot[bot] ea548dfd91 [ci skip] Bump gradle-wrapper from 9.4.1 to 9.5.0 (#681)
Bumps [gradle-wrapper](https://github.com/gradle/gradle) from 9.4.1 to 9.5.0.
- [Release notes](https://github.com/gradle/gradle/releases)
- [Commits](https://github.com/gradle/gradle/compare/v9.4.1...v9.5.0)

---
updated-dependencies:
- dependency-name: gradle-wrapper
  dependency-version: 9.5.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-05 20:48:44 +02:00
Florian Reuth d0561f2e19 ViaRewind 4.1.1 2026-05-05 20:42:51 +02:00
Beanes 2c8189a447 Fix edge case when server sends a useless packet (#679) 2026-04-28 23:06:11 +02:00
Beanes 48c9e45bcf Also rewrite extra entity ids for other interaction modes (#678) 2026-04-26 16:56:26 +02:00
Gabik21 ecb4f4bf00 Fix nametag visibility duplicate names edge case (#675) 2026-04-24 21:34:24 +02:00
Kichura 6ea9377622 [ci skip] Fix version comments, Unsubscribe from proprietary caching tier. (#677) 2026-04-24 21:34:05 +02:00
Beanes b789fdebb7 Fix set score packet crashing (#674) 2026-04-22 15:16:00 +02:00
Beanes e7b0606e18 Rewrite "no face" in digging packet to a valid 1.8 value (#673) 2026-04-22 13:43:35 +02:00
NotAlexNoyle bf490c3805 Fix brewing stand titles for 1.8 clients on 1.9+ servers (#672) 2026-04-20 22:10:04 +02:00
Florian Reuth 7ca7078141 Start 4.1.1 cycle
Signed-off-by: Florian Reuth <git@florianreuth.de>
2026-04-20 22:09:12 +02:00
52 changed files with 1457 additions and 542 deletions

No files matched your search

+9 -1
View File
@@ -6,9 +6,17 @@ updates:
directory: "/"
schedule:
interval: "monthly"
groups:
all-dependencies:
patterns:
- "*"
- package-ecosystem: "github-actions"
cooldown:
default-days: 7
directory: "/"
schedule:
interval: "monthly"
interval: "monthly"
groups:
all-dependencies:
patterns:
- "*"
+13 -11
View File
@@ -2,29 +2,31 @@ name: Build
on: [pull_request, push, workflow_dispatch]
permissions:
contents: read
jobs:
build:
# Only run on PRs if the source branch is on a different repo. We do not need to run everything twice.
if: ${{ github.event_name != 'pull_request' || github.repository != github.event.pull_request.head.repo.full_name }}
runs-on: ubuntu-24.04
runs-on: ubuntu-26.04
timeout-minutes: 30
steps:
- name: Checkout Repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # 6.0.2
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Set up Gradle
uses: gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # 6.1.0
- name: Set up JDK 17
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # 5.2.0
uses: gradle/actions/setup-gradle@9c971963bec38e04b3d30dcc455b5382be2fdbfb # v6.3.0
with:
cache-disabled: true
cache-provider: basic
- name: Set up Java
uses: actions/setup-java@dd06d9cba3e5552c54d9f8ea23572deb30010f7c # v6.0.0
with:
distribution: 'temurin'
java-version: 17
java-version: 25
check-latest: true
- name: Build with Gradle
run: ./gradlew build
run: ./gradlew build --stacktrace
- name: Upload Artifacts to GitHub
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # 7.0.1
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: Artifacts
path: build/libs/
if-no-files-found: error
+11 -7
View File
@@ -12,22 +12,26 @@ permissions:
jobs:
publish:
if: github.repository_owner == 'ViaVersion'
runs-on: ubuntu-24.04
runs-on: ubuntu-26.04
timeout-minutes: 30
steps:
- name: Checkout Repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # 6.0.2
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Set up Gradle
uses: gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # 6.1.0
- name: Set up JDK 17
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # 5.2.0
uses: gradle/actions/setup-gradle@9c971963bec38e04b3d30dcc455b5382be2fdbfb # v6.3.0
with:
cache-disabled: true
cache-provider: basic
- name: Set up Java
uses: actions/setup-java@dd06d9cba3e5552c54d9f8ea23572deb30010f7c # v6.0.0
with:
distribution: 'temurin'
java-version: 17
java-version: 25
check-latest: true
- name: Build with Gradle
run: ./gradlew build
run: ./gradlew build --stacktrace
- name: Publish to Hangar
env:
HANGAR_TOKEN: ${{ secrets.HANGAR_TOKEN }}
+22 -9
View File
@@ -1,10 +1,17 @@
import de.florianreuth.baseproject.*
import de.florianreuth.baseproject.core.configureEmbeddedDependencies
import de.florianreuth.baseproject.integration.branchName
import de.florianreuth.baseproject.integration.latestCommitHash
import de.florianreuth.baseproject.integration.latestCommitMessage
import de.florianreuth.baseproject.setupProject
import de.florianreuth.baseproject.setupViaPublishing
import net.raphimc.classtokenreplacer.extension.ClassTokenReplacerExtension
plugins {
`java-library`
id("io.papermc.hangar-publish-plugin")
id("com.modrinth.minotaur")
id("de.florianreuth.baseproject")
id("net.raphimc.class-token-replacer") apply false
}
allprojects {
@@ -13,7 +20,6 @@ allprojects {
setupViaPublishing()
repositories {
mavenCentral()
maven("https://repo.viaversion.com")
maven("https://repo.papermc.io/repository/maven-public")
maven("https://maven.fabricmc.net")
@@ -21,11 +27,22 @@ allprojects {
}
val commitHash = latestCommitHash()
subprojects {
apply(plugin = "net.raphimc.class-token-replacer")
dependencies {
compileOnly("com.viaversion:viaversion:5.7.2")
compileOnly("com.viaversion:viabackwards:5.7.2")
compileOnly("com.viaversion:viaversion:5.12.0")
compileOnly("com.viaversion:viabackwards:5.12.0")
}
extensions.getByType<SourceSetContainer>().configureEach {
extensions.getByType(ClassTokenReplacerExtension::class.java).apply {
property("\${version}", project.version)
property("\${impl_version}", "git-ViaRewind-${project.version}:${commitHash}")
}
}
tasks {
@@ -40,11 +57,7 @@ subprojects {
}
base {
archivesName.set("ViaRewind")
}
val shade = configureShadedDependencies(false) // Only shade, don't add them as dependency
val shade = configureEmbeddedDependencies()
dependencies {
subprojects.forEach {
-15
View File
@@ -1,9 +1,3 @@
import de.florianreuth.baseproject.latestCommitHash
plugins {
id("net.raphimc.class-token-replacer")
}
val viaProxy: Configuration by configurations.creating
dependencies {
@@ -16,15 +10,6 @@ dependencies {
}
}
sourceSets.configureEach {
classTokenReplacer {
replaceInPlace.set(true)
property("\${version}", project.version)
property("\${impl_version}", "git-ViaRewind-${project.version}:${latestCommitHash()}")
}
}
// Task to quickly test/debug code changes using https://github.com/ViaVersion/ViaProxy
// For further instructions see the ViaProxy repository README
val prepareViaProxyFiles by tasks.registering(Copy::class) {
@@ -0,0 +1,157 @@
/*
* This file is part of ViaRewind - https://github.com/ViaVersion/ViaRewind
* Copyright (C) 2018-2026 ViaVersion and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.viaversion.viarewind.api.compression;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import java.util.zip.DataFormatException;
import java.util.zip.Deflater;
import java.util.zip.Inflater;
/**
* A ThreadLocal-based compression provider that either uses Velocity's native compression
* or falls back to Java's built-in compression (Deflater/Inflater).
*/
public final class ThreadLocalCompressionProvider {
private static final boolean VELOCITY_NATIVES_AVAILABLE;
static {
boolean velocityAvailable = false;
try {
Class.forName("com.velocitypowered.natives.compression.VelocityCompressor");
velocityAvailable = true;
} catch (final ClassNotFoundException ignored) {
}
VELOCITY_NATIVES_AVAILABLE = velocityAvailable;
}
// ThreadLocal for Java's Deflater (used when Velocity natives are not available)
private static final ThreadLocal<Deflater> JAVA_DEFLATER = ThreadLocal.withInitial(Deflater::new);
// ThreadLocal for Java's Inflater (used when Velocity natives are not available)
private static final ThreadLocal<Inflater> JAVA_INFLATER = ThreadLocal.withInitial(Inflater::new);
private ThreadLocalCompressionProvider() {
}
/**
* Compresses data from the source buffer into the destination buffer.
*
* @param source the source buffer containing uncompressed data
* @param destination the destination buffer to write compressed data to
* @throws DataFormatException if compression fails
*/
public static void deflate(final ByteBuf source, final ByteBuf destination) throws DataFormatException {
if (VELOCITY_NATIVES_AVAILABLE) {
deflateVelocity(source, destination);
} else {
deflateJava(source, destination);
}
}
/**
* Decompresses data from the source buffer into the destination buffer.
*
* @param source the source buffer containing compressed data
* @param destination the destination buffer to write decompressed data to
* @param expectedSize the expected size of the decompressed data
* @throws DataFormatException if decompression fails
*/
public static void inflate(final ByteBuf source, final ByteBuf destination, final int expectedSize) throws DataFormatException {
if (VELOCITY_NATIVES_AVAILABLE) {
inflateVelocity(source, destination, expectedSize);
} else {
inflateJava(source, destination, expectedSize);
}
}
// Java native implementation
private static void deflateJava(final ByteBuf source, final ByteBuf destination) throws DataFormatException {
ByteBuf temp = source;
if (!source.hasArray()) {
temp = ByteBufAllocator.DEFAULT.heapBuffer().writeBytes(source);
} else {
source.retain();
}
ByteBuf output = ByteBufAllocator.DEFAULT.heapBuffer();
try {
final Deflater deflater = JAVA_DEFLATER.get();
deflater.setInput(temp.array(), temp.arrayOffset() + temp.readerIndex(), temp.readableBytes());
deflater.finish();
while (!deflater.finished()) {
output.ensureWritable(4096);
output.writerIndex(output.writerIndex() + deflater.deflate(output.array(), output.arrayOffset() + output.writerIndex(), output.writableBytes()));
}
destination.writeBytes(output);
} finally {
output.release();
temp.release();
JAVA_DEFLATER.get().reset();
}
}
private static void inflateJava(final ByteBuf source, final ByteBuf destination, final int expectedSize) throws DataFormatException {
ByteBuf temp = source;
if (!source.hasArray()) {
temp = ByteBufAllocator.DEFAULT.heapBuffer().writeBytes(source);
} else {
source.retain();
}
ByteBuf output = ByteBufAllocator.DEFAULT.heapBuffer(expectedSize, expectedSize);
try {
final Inflater inflater = JAVA_INFLATER.get();
inflater.setInput(temp.array(), temp.arrayOffset() + temp.readerIndex(), temp.readableBytes());
output.writerIndex(output.writerIndex() + inflater.inflate(output.array(), output.arrayOffset(), expectedSize));
destination.writeBytes(output);
} finally {
output.release();
temp.release();
JAVA_INFLATER.get().reset();
}
}
// Velocity native implementation
private static void deflateVelocity(final ByteBuf source, final ByteBuf destination) throws DataFormatException {
VelocityHolder.deflate(source, destination);
}
private static void inflateVelocity(final ByteBuf source, final ByteBuf destination, final int expectedSize) throws DataFormatException {
VelocityHolder.inflate(source, destination, expectedSize);
}
private static final class VelocityHolder {
private static final ThreadLocal<com.velocitypowered.natives.compression.VelocityCompressor> COMPRESSOR =
ThreadLocal.withInitial(() -> com.velocitypowered.natives.util.Natives.compress.get().create(-1));
static void deflate(final ByteBuf source, final ByteBuf destination) throws DataFormatException {
COMPRESSOR.get().deflate(source, destination);
}
static void inflate(final ByteBuf source, final ByteBuf destination, final int expectedSize) throws DataFormatException {
COMPRESSOR.get().inflate(source, destination, expectedSize);
}
}
}
@@ -20,6 +20,10 @@ package com.viaversion.viarewind.api.data;
import com.viaversion.nbt.tag.CompoundTag;
import com.viaversion.viabackwards.api.data.BackwardsMappingData;
import com.viaversion.viarewind.ViaRewind;
import com.viaversion.viaversion.api.data.FullMappings;
import com.viaversion.viaversion.api.data.LegacyFullStringMappingsBase;
import com.viaversion.viaversion.api.data.MappingDataLoader;
import com.viaversion.viaversion.api.data.Mappings;
import java.util.logging.Logger;
public class RewindMappingData extends BackwardsMappingData {
@@ -37,4 +41,9 @@ public class RewindMappingData extends BackwardsMappingData {
protected CompoundTag readMappingsFile(String name) {
return RewindMappingDataLoader.INSTANCE.loadNBTFromDir(name);
}
@Override
protected FullMappings createFullMappings(MappingDataLoader.IdentifiersPair identifiersPair, Mappings mappings) {
return new LegacyFullStringMappingsBase(identifiersPair, mappings);
}
}
@@ -17,17 +17,13 @@
*/
package com.viaversion.viarewind.api.minecraft.math;
import com.viaversion.viarewind.protocol.v1_9to1_8.Protocol1_9To1_8;
import com.viaversion.viarewind.protocol.v1_9to1_8.storage.EntityTracker1_9;
import com.viaversion.viaversion.api.connection.UserConnection;
import com.viaversion.viaversion.api.minecraft.Vector;
@Deprecated // ?????????
public class RelativeMoveUtil {
public static Vector[] calculateRelativeMoves(UserConnection user, int entityId, int relX, int relY, int relZ) {
EntityTracker1_9 tracker = user.getEntityTracker(Protocol1_9To1_8.class);
public static Vector[] calculateRelativeMoves(EntityTracker1_9 tracker, int entityId, int relX, int relY, int relZ) {
int x;
int y;
int z;
@@ -17,11 +17,13 @@
*/
package com.viaversion.viarewind.api.type.chunk;
import com.viaversion.viarewind.api.compression.ThreadLocalCompressionProvider;
import com.viaversion.viaversion.api.minecraft.chunks.Chunk;
import com.viaversion.viaversion.api.type.Type;
import io.netty.buffer.ByteBuf;
import java.util.zip.Deflater;
import java.util.zip.DataFormatException;
public class BulkChunkType1_7_6 extends Type<Chunk[]> {
@@ -63,40 +65,47 @@ public class BulkChunkType1_7_6 extends Type<Chunk[]> {
);
}
final byte[] data = new byte[totalSize];
int offset = 0;
final ByteBuf uncompressed = buffer.alloc().buffer(totalSize);
for (int i = 0; i < chunkCount; i++) {
Chunk chunk = chunks[i];
boolean biomes = chunk.isFullChunk() && chunk.getBiomeData() != null;
offset = ChunkType1_7_6.serialize(
chunk,
data,
offset,
addBitMasks[i],
anySkyLight,
biomes
);
}
buffer.writeShort(chunkCount);
final Deflater deflater = new Deflater();
byte[] compressedData;
int compressedSize;
try {
deflater.setInput(data, 0, data.length);
deflater.finish();
compressedData = new byte[data.length];
compressedSize = deflater.deflate(compressedData);
} finally {
deflater.end();
}
for (int i = 0; i < chunkCount; i++) {
Chunk chunk = chunks[i];
boolean biomes = chunk.isFullChunk() && chunk.getBiomeData() != null;
buffer.writeInt(compressedSize);
buffer.writeBoolean(anySkyLight);
buffer.writeBytes(compressedData, 0, compressedSize);
ChunkType1_7_6.serialize(
chunk,
uncompressed,
addBitMasks[i],
anySkyLight,
biomes
);
}
buffer.writeShort(chunkCount);
// Reserve 4 bytes for the compressed size
final int sizeIndex = buffer.writerIndex();
buffer.writeInt(0); // Placeholder for compressed size
buffer.writeBoolean(anySkyLight);
// Write compressed data directly to output buffer
final int compressedStart = buffer.writerIndex();
try {
ThreadLocalCompressionProvider.deflate(uncompressed, buffer);
} catch (DataFormatException e) {
throw new RuntimeException("Failed to compress bulk chunk data", e);
}
final int compressedSize = buffer.writerIndex() - compressedStart;
// Go back and write the compressed size
final int endIndex = buffer.writerIndex();
buffer.writerIndex(sizeIndex);
buffer.writeInt(compressedSize);
buffer.writerIndex(endIndex);
} finally {
uncompressed.release();
}
for (int i = 0; i < chunkCount; i++) {
Chunk chunk = chunks[i];
@@ -17,13 +17,15 @@
*/
package com.viaversion.viarewind.api.type.chunk;
import com.viaversion.viarewind.api.compression.ThreadLocalCompressionProvider;
import com.viaversion.viaversion.api.minecraft.chunks.Chunk;
import com.viaversion.viaversion.api.minecraft.chunks.ChunkSection;
import com.viaversion.viaversion.api.minecraft.chunks.DataPalette;
import com.viaversion.viaversion.api.minecraft.chunks.PaletteType;
import com.viaversion.viaversion.api.type.Type;
import io.netty.buffer.ByteBuf;
import java.util.zip.Deflater;
import java.util.zip.DataFormatException;
import static com.viaversion.viaversion.api.minecraft.chunks.ChunkSection.SIZE;
import static com.viaversion.viaversion.api.minecraft.chunks.ChunkSectionLight.LIGHT_LENGTH;
@@ -49,33 +51,41 @@ public class ChunkType1_7_6 extends Type<Chunk> {
final boolean biomes = chunk.isFullChunk() && chunk.getBiomeData() != null;
final int size = calcSize(bitmask, addBitmask, hasSkyLight, biomes);
final byte[] data = new byte[size];
final ByteBuf uncompressed = buffer.alloc().buffer(size);
serialize(chunk, data, 0, addBitmask, hasSkyLight, biomes);
buffer.writeInt(chunk.getX());
buffer.writeInt(chunk.getZ());
buffer.writeBoolean(chunk.isFullChunk());
buffer.writeShort(bitmask);
buffer.writeShort(addBitmask);
final Deflater deflater = new Deflater();
byte[] compressedData;
int compressedSize;
try {
deflater.setInput(data, 0, data.length);
deflater.finish();
compressedData = new byte[data.length];
compressedSize = deflater.deflate(compressedData);
} finally {
deflater.end();
}
serialize(chunk, uncompressed, addBitmask, hasSkyLight, biomes);
buffer.writeInt(compressedSize);
buffer.writeBytes(compressedData, 0, compressedSize);
buffer.writeInt(chunk.getX());
buffer.writeInt(chunk.getZ());
buffer.writeBoolean(chunk.isFullChunk());
buffer.writeShort(bitmask);
buffer.writeShort(addBitmask);
// Reserve 4 bytes for the compressed size
final int sizeIndex = buffer.writerIndex();
buffer.writeInt(0); // Placeholder for compressed size
// Write compressed data directly to output buffer
final int compressedStart = buffer.writerIndex();
try {
ThreadLocalCompressionProvider.deflate(uncompressed, buffer);
} catch (DataFormatException e) {
throw new RuntimeException("Failed to compress chunk data", e);
}
final int compressedSize = buffer.writerIndex() - compressedStart;
// Go back and write the compressed size
final int endIndex = buffer.writerIndex();
buffer.writerIndex(sizeIndex);
buffer.writeInt(compressedSize);
buffer.writerIndex(endIndex);
} finally {
uncompressed.release();
}
}
public static int serialize(Chunk chunk, byte[] output, int offset, int addBitmask, boolean writeSkyLight, boolean biomes) {
public static void serialize(Chunk chunk, ByteBuf output, int addBitmask, boolean writeSkyLight, boolean biomes) {
final ChunkSection[] sections = chunk.getSections();
final int bitmask = chunk.getBitmask();
@@ -85,7 +95,7 @@ public class ChunkType1_7_6 extends Type<Chunk> {
final DataPalette palette = section.palette(PaletteType.BLOCKS);
for (int j = 0; j < SIZE; j++) {
final int block = palette.idAt(j);
output[offset++] = (byte) ((block >> 4) & 0xFF);
output.writeByte((block >> 4) & 0xFF);
}
}
}
@@ -97,7 +107,7 @@ public class ChunkType1_7_6 extends Type<Chunk> {
for (int j = 0; j < ChunkSection.SIZE; j += 2) {
final int meta1 = palette.idAt(j) & 0xF;
final int meta2 = palette.idAt(j + 1) & 0xF;
output[offset++] = (byte) (meta1 | (meta2 << 4));
output.writeByte(meta1 | (meta2 << 4));
}
}
}
@@ -105,8 +115,7 @@ public class ChunkType1_7_6 extends Type<Chunk> {
for (int i = 0; i < 16; i++) {
if ((bitmask & (1 << i)) != 0) {
final byte[] blockLight = sections[i].getLight().getBlockLight();
System.arraycopy(blockLight, 0, output, offset, LIGHT_LENGTH);
offset += LIGHT_LENGTH;
output.writeBytes(blockLight);
}
}
@@ -115,9 +124,11 @@ public class ChunkType1_7_6 extends Type<Chunk> {
if ((bitmask & (1 << i)) != 0) {
if (sections[i].getLight().hasSkyLight()) {
final byte[] skyLight = sections[i].getLight().getSkyLight();
System.arraycopy(skyLight, 0, output, offset, LIGHT_LENGTH);
output.writeBytes(skyLight);
} else {
// Write empty skylight data
output.writeZero(LIGHT_LENGTH);
}
offset += LIGHT_LENGTH;
}
}
}
@@ -130,7 +141,7 @@ public class ChunkType1_7_6 extends Type<Chunk> {
for (int j = 0; j < SIZE; j += 2) {
final int add1 = (palette.idAt(j) >> 12) & 0xF;
final int add2 = (palette.idAt(j + 1) >> 12) & 0xF;
output[offset++] = (byte) (add1 | (add2 << 4));
output.writeByte(add1 | (add2 << 4));
}
}
}
@@ -139,11 +150,9 @@ public class ChunkType1_7_6 extends Type<Chunk> {
if (biomes && chunk.getBiomeData() != null) {
final int[] biomeData = chunk.getBiomeData();
for (int biome : biomeData) {
output[offset++] = (byte) biome;
output.writeByte(biome);
}
}
return offset;
}
public static int calcSize(int bitmask, int addBitmask, boolean hasSkyLight, boolean biomes) {
@@ -38,6 +38,7 @@ import com.viaversion.viarewind.protocol.v1_8to1_7_6_10.storage.ScoreboardTracke
import com.viaversion.viarewind.protocol.v1_8to1_7_6_10.storage.WorldBorderEmulator;
import com.viaversion.viarewind.protocol.v1_8to1_7_6_10.task.WorldBorderUpdateTask;
import com.viaversion.viaversion.api.Via;
import com.viaversion.viaversion.api.connection.ProtocolStorables;
import com.viaversion.viaversion.api.connection.UserConnection;
import com.viaversion.viaversion.api.minecraft.ClientWorld;
import com.viaversion.viaversion.api.platform.providers.ViaProviders;
@@ -122,8 +123,9 @@ public class Protocol1_8To1_7_6_10 extends BackwardsProtocol<ClientboundPackets1
@Override
public void init(UserConnection connection) {
connection.addEntityTracker(this.getClass(), new EntityTracker1_8(connection));
connection.addClientWorld(this.getClass(), new ClientWorld());
final ProtocolStorables storables = connection.storables(this);
storables.setEntityTracker(new EntityTracker1_8(connection));
storables.setClientWorld(new ClientWorld());
connection.put(new InventoryTracker(connection));
connection.put(new PlayerSessionStorage(connection));
@@ -26,6 +26,18 @@ import java.util.List;
import java.util.Map;
public class ChatNewlineRewriter {
private static final String[] STYLE_KEYS = {
"color",
"bold",
"italic",
"underlined",
"strikethrough",
"obfuscated",
"clickEvent",
"hoverEvent",
"insertion"
};
public static List<JsonObject> splitChatComponentByNewline(JsonElement element) {
return splitChatComponentByNewline(element, new JsonObject());
}
@@ -47,8 +59,28 @@ public class ChatNewlineRewriter {
}
if (element.isJsonArray()) {
List<JsonObject> currentLine = new ArrayList<>();
JsonObject siblingStyle = inheritedStyle;
boolean foundRoot = false;
for (JsonElement child : element.getAsJsonArray()) {
results.addAll(splitChatComponentByNewline(child, inheritedStyle));
List<JsonObject> splitParts = splitChatComponentByNewline(child, siblingStyle);
for (int i = 0; i < splitParts.size(); i++) {
currentLine.add(splitParts.get(i));
if (i < splitParts.size() - 1) {
results.add(mergeLine(currentLine));
currentLine.clear();
}
}
if (!foundRoot && child != null && !child.isJsonNull()) {
siblingStyle = rootStyle(inheritedStyle, child);
foundRoot = true;
}
}
if (!currentLine.isEmpty()) {
results.add(mergeLine(currentLine));
}
return results;
}
@@ -60,13 +92,7 @@ public class ChatNewlineRewriter {
JsonObject obj = element.getAsJsonObject();
// Build the style for this node: inherit first, then override with this object's style
JsonObject currentStyle = inheritedStyle.deepCopy();
for (Map.Entry<String, JsonElement> entry : obj.entrySet()) {
String key = entry.getKey();
if (!"text".equals(key) && !"extra".equals(key)) {
currentStyle.add(key, entry.getValue());
}
}
JsonObject currentStyle = mergeStyle(inheritedStyle, obj);
// Gather components in order: base text then extra array
List<JsonElement> ordered = new ArrayList<>();
@@ -81,12 +107,19 @@ public class ChatNewlineRewriter {
// If there is no text/extra, return a single object with the style
if (ordered.isEmpty()) {
results.add(currentStyle);
JsonObject baseComponent = copyComponentWithoutExtra(obj, currentStyle);
results.add(baseComponent != null ? baseComponent : currentStyle);
return results;
}
// Build lines by concatenating split pieces
List<JsonObject> currentLine = new ArrayList<>();
if (!obj.has("text")) {
JsonObject baseComponent = copyComponentWithoutExtra(obj, currentStyle);
if (baseComponent != null) {
currentLine.add(baseComponent);
}
}
for (JsonElement part : ordered) {
if (part == null || part.isJsonNull()) {
continue;
@@ -127,6 +160,55 @@ public class ChatNewlineRewriter {
return results;
}
private static JsonObject rootStyle(JsonObject inheritedStyle, JsonElement element) {
if (element == null || element.isJsonNull()) {
return inheritedStyle;
}
if (element.isJsonObject()) {
return mergeStyle(inheritedStyle, element.getAsJsonObject());
}
if (element.isJsonArray()) {
for (JsonElement child : element.getAsJsonArray()) {
if (child != null && !child.isJsonNull()) {
return rootStyle(inheritedStyle, child);
}
}
}
return inheritedStyle;
}
private static JsonObject mergeStyle(JsonObject inheritedStyle, JsonObject obj) {
JsonObject currentStyle = inheritedStyle.deepCopy();
for (String key : STYLE_KEYS) {
if (obj.has(key)) {
currentStyle.add(key, obj.get(key).deepCopy());
}
}
return currentStyle;
}
private static JsonObject copyComponentWithoutExtra(JsonObject obj, JsonObject currentStyle) {
JsonObject component = currentStyle.deepCopy();
for (Map.Entry<String, JsonElement> entry : obj.entrySet()) {
String key = entry.getKey();
if ("extra".equals(key) || isStyleKey(key)) {
continue;
}
component.add(key, entry.getValue().deepCopy());
}
return component.size() == currentStyle.size() ? null : component;
}
private static boolean isStyleKey(String key) {
for (String styleKey : STYLE_KEYS) {
if (styleKey.equals(key)) {
return true;
}
}
return false;
}
private static JsonObject mergeLine(List<JsonObject> lineParts) {
if (lineParts.isEmpty()) {
JsonObject empty = new JsonObject();
@@ -139,11 +221,14 @@ public class ChatNewlineRewriter {
return first;
}
JsonObject merged = new JsonObject();
merged.addProperty("text", "");
JsonArray extra = new JsonArray();
for (int i = 1; i < lineParts.size(); i++) {
extra.add(lineParts.get(i));
for (JsonObject linePart : lineParts) {
extra.add(linePart);
}
first.add("extra", extra);
return first;
merged.add("extra", extra);
return merged;
}
}
@@ -20,7 +20,6 @@ package com.viaversion.viarewind.protocol.v1_8to1_7_6_10.provider;
import com.viaversion.viarewind.protocol.v1_8to1_7_6_10.storage.CompressionStatusTracker;
import com.viaversion.viaversion.api.connection.UserConnection;
import com.viaversion.viaversion.api.platform.providers.Provider;
import io.netty.channel.ChannelHandler;
public abstract class CompressionHandlerProvider implements Provider {
@@ -28,23 +27,6 @@ public abstract class CompressionHandlerProvider implements Provider {
public abstract void onTransformPacket(UserConnection user);
/**
* Creates the compression encoder
*
* @param compressor A nullable object that may present a native backend for compression (if available)
* @param threshold The compression threshold
* @return The encoder
*/
public abstract ChannelHandler getEncoder(Object compressor, int threshold);
/**
* Creates the compression decoder
*
* @param compressor A nullable object that may present a native backend for compression (if available)
* @param threshold The compression threshold
* @return The decoder
*/
public abstract ChannelHandler getDecoder(Object compressor, int threshold);
public boolean isRemoveCompression(UserConnection user) {
return user.get(CompressionStatusTracker.class).removeCompression;
@@ -17,18 +17,15 @@
*/
package com.viaversion.viarewind.protocol.v1_8to1_7_6_10.provider.compression;
import com.viaversion.viarewind.api.compression.ThreadLocalCompressionProvider;
import com.viaversion.viaversion.api.type.Types;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.DecoderException;
import io.netty.handler.codec.MessageToMessageDecoder;
import java.util.List;
import java.util.zip.DataFormatException;
import java.util.zip.Inflater;
public class CompressionDecoder extends MessageToMessageDecoder<ByteBuf> {
private final Inflater inflater = new Inflater();
private int threshold;
@@ -58,25 +55,12 @@ public class CompressionDecoder extends MessageToMessageDecoder<ByteBuf> {
throw new DecoderException("Badly compressed packet - size of " + outLength + " is larger than protocol maximum of " + 2097152);
}
inflate(ctx, in, outLength, out);
}
protected void inflate(final ChannelHandlerContext ctx, final ByteBuf source, final int outLength, final List<Object> out) throws DataFormatException {
ByteBuf temp = source;
if (!source.hasArray()) {
temp = ByteBufAllocator.DEFAULT.heapBuffer().writeBytes(source);
} else {
source.retain();
}
ByteBuf output = ByteBufAllocator.DEFAULT.heapBuffer(outLength, outLength);
ByteBuf output = ctx.alloc().buffer(outLength);
try {
this.inflater.setInput(temp.array(), temp.arrayOffset() + temp.readerIndex(), temp.readableBytes());
output.writerIndex(output.writerIndex() + this.inflater.inflate(output.array(), output.arrayOffset(), outLength));
ThreadLocalCompressionProvider.inflate(in, output, outLength);
out.add(output.retain());
} finally {
output.release();
temp.release();
this.inflater.reset();
}
}
@@ -17,16 +17,13 @@
*/
package com.viaversion.viarewind.protocol.v1_8to1_7_6_10.provider.compression;
import com.viaversion.viarewind.api.compression.ThreadLocalCompressionProvider;
import com.viaversion.viaversion.api.type.Types;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
import java.util.zip.DataFormatException;
import java.util.zip.Deflater;
public class CompressionEncoder extends MessageToByteEncoder<ByteBuf> {
private final Deflater deflater = new Deflater();
private int threshold;
@@ -48,32 +45,7 @@ public class CompressionEncoder extends MessageToByteEncoder<ByteBuf> {
}
Types.VAR_INT.writePrimitive(out, frameLength);
deflate(in, out);
}
protected void deflate(final ByteBuf source, final ByteBuf destination) throws DataFormatException {
ByteBuf temp = source;
if (!source.hasArray()) {
temp = ByteBufAllocator.DEFAULT.heapBuffer().writeBytes(source);
} else {
source.retain();
}
ByteBuf output = ByteBufAllocator.DEFAULT.heapBuffer();
try {
this.deflater.setInput(temp.array(), temp.arrayOffset() + temp.readerIndex(), temp.readableBytes());
deflater.finish();
while (!deflater.finished()) {
output.ensureWritable(4096);
output.writerIndex(output.writerIndex() + this.deflater.deflate(output.array(), output.arrayOffset() + output.writerIndex(), output.writableBytes()));
}
destination.writeBytes(output);
} finally {
output.release();
temp.release();
this.deflater.reset();
}
ThreadLocalCompressionProvider.deflate(in, out);
}
}
@@ -21,28 +21,12 @@ import com.viaversion.viarewind.ViaRewind;
import com.viaversion.viarewind.api.minecraft.netty.EmptyChannelHandler;
import com.viaversion.viarewind.api.minecraft.netty.ForwardMessageToByteEncoder;
import com.viaversion.viarewind.protocol.v1_8to1_7_6_10.provider.CompressionHandlerProvider;
import com.viaversion.viarewind.protocol.v1_8to1_7_6_10.provider.compression.velocity.VelocityCompressionDecoder;
import com.viaversion.viarewind.protocol.v1_8to1_7_6_10.provider.compression.velocity.VelocityCompressionEncoder;
import com.viaversion.viaversion.api.Via;
import com.viaversion.viaversion.api.connection.UserConnection;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelPipeline;
public class TrackingCompressionHandlerProvider extends CompressionHandlerProvider {
// Use Velocity's compression backend if ViaRewind is loaded on a Velocity server or a custom server software
// is shipping them in the classpath.
private static boolean velocityNatives;
static {
try {
Class.forName("com.velocitypowered.natives.compression.VelocityCompressor");
velocityNatives = true;
} catch (final ClassNotFoundException ignored) {
velocityNatives = false;
}
}
@Override
public void setCompressionThreshold(UserConnection user, int threshold) {
final ChannelPipeline pipeline = user.getChannel().pipeline();
@@ -51,17 +35,12 @@ public class TrackingCompressionHandlerProvider extends CompressionHandlerProvid
return;
}
Object compressor = null;
if (velocityNatives) {
compressor = com.velocitypowered.natives.util.Natives.compress.get().create(-1);
}
final String compressHandlerName = ViaRewind.getPlatform().compressHandlerName();
final CompressionEncoder encoder = (CompressionEncoder) pipeline.get(compressHandlerName);
if (encoder != null) {
encoder.setThreshold(threshold);
} else {
pipeline.addBefore(Via.getManager().getInjector().getEncoderName(), compressHandlerName, getEncoder(compressor, threshold));
pipeline.addBefore(Via.getManager().getInjector().getEncoderName(), compressHandlerName, new CompressionEncoder(threshold));
}
final String decompressHandlerName = ViaRewind.getPlatform().decompressHandlerName();
@@ -69,7 +48,7 @@ public class TrackingCompressionHandlerProvider extends CompressionHandlerProvid
if (decoder != null) {
decoder.setThreshold(threshold);
} else {
pipeline.addBefore(Via.getManager().getInjector().getDecoderName(), decompressHandlerName, getDecoder(compressor, threshold));
pipeline.addBefore(Via.getManager().getInjector().getDecoderName(), decompressHandlerName, new CompressionDecoder(threshold));
}
}
@@ -96,22 +75,5 @@ public class TrackingCompressionHandlerProvider extends CompressionHandlerProvid
}
}
@Override
public ChannelHandler getEncoder(final Object compressor, int threshold) {
if (velocityNatives) {
return new VelocityCompressionEncoder(threshold, (com.velocitypowered.natives.compression.VelocityCompressor) compressor);
} else {
return new CompressionEncoder(threshold);
}
}
@Override
public ChannelHandler getDecoder(final Object compressor, int threshold) {
if (velocityNatives) {
return new VelocityCompressionDecoder(threshold, (com.velocitypowered.natives.compression.VelocityCompressor) compressor);
} else {
return new CompressionDecoder(threshold);
}
}
}
@@ -1,47 +0,0 @@
/*
* This file is part of ViaRewind - https://github.com/ViaVersion/ViaRewind
* Copyright (C) 2018-2026 ViaVersion and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.viaversion.viarewind.protocol.v1_8to1_7_6_10.provider.compression.velocity;
import com.velocitypowered.natives.compression.VelocityCompressor;
import com.viaversion.viarewind.protocol.v1_8to1_7_6_10.provider.compression.CompressionDecoder;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import java.util.List;
import java.util.zip.DataFormatException;
public final class VelocityCompressionDecoder extends CompressionDecoder {
private final VelocityCompressor compressor;
public VelocityCompressionDecoder(final int threshold, final VelocityCompressor compressor) {
super(threshold);
this.compressor = compressor;
}
@Override
protected void inflate(final ChannelHandlerContext ctx, final ByteBuf source, final int outLength, final List<Object> out) throws DataFormatException {
ByteBuf output = ctx.alloc().buffer(outLength);
try {
this.compressor.inflate(source, output, outLength);
out.add(output.retain());
} finally {
output.release();
}
}
}
@@ -1,39 +0,0 @@
/*
* This file is part of ViaRewind - https://github.com/ViaVersion/ViaRewind
* Copyright (C) 2018-2026 ViaVersion and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.viaversion.viarewind.protocol.v1_8to1_7_6_10.provider.compression.velocity;
import com.velocitypowered.natives.compression.VelocityCompressor;
import com.viaversion.viarewind.protocol.v1_8to1_7_6_10.provider.compression.CompressionEncoder;
import io.netty.buffer.ByteBuf;
import java.util.zip.DataFormatException;
public final class VelocityCompressionEncoder extends CompressionEncoder {
private final VelocityCompressor compressor;
public VelocityCompressionEncoder(final int threshold, final VelocityCompressor compressor) {
super(threshold);
this.compressor = compressor;
}
@Override
protected void deflate(final ByteBuf source, final ByteBuf destination) throws DataFormatException {
this.compressor.deflate(source, destination);
}
}
@@ -113,7 +113,7 @@ public class BlockItemPacketRewriter1_8 extends VRBlockItemRewriter<ClientboundP
final Item item = wrapper.get(RewindTypes.COMPRESSED_NBT_ITEM, 0);
playerSession.setPlayerEquipment(wrapper.user().getProtocolInfo().getUuid(), item, 8 - slot);
final EntityTracker1_8 tracker = wrapper.user().getEntityTracker(Protocol1_8To1_7_6_10.class);
final EntityTracker1_8 tracker = wrapper.user().getEntityTracker(protocol);
if (tracker.isSpectator()) { // Spectator mode didn't exist in 1.7.10
wrapper.cancel();
}
@@ -148,7 +148,7 @@ public class BlockItemPacketRewriter1_8 extends VRBlockItemRewriter<ClientboundP
if (windowId != 0) {
return;
}
final EntityTracker1_8 tracker = wrapper.user().getEntityTracker(Protocol1_8To1_7_6_10.class);
final EntityTracker1_8 tracker = wrapper.user().getEntityTracker(protocol);
final UUID userId = wrapper.user().getProtocolInfo().getUuid();
final Item[] items = wrapper.get(RewindTypes.COMPRESSED_NBT_ITEM_ARRAY, 0);
@@ -76,7 +76,7 @@ public class EntityPacketRewriter1_8 extends VREntityRewriter<ClientboundPackets
}
}
final EntityTracker1_8 tracker = wrapper.user().getEntityTracker(Protocol1_8To1_7_6_10.class);
final EntityTracker1_8 tracker = wrapper.user().getEntityTracker(protocol);
tracker.addPlayer(entityId, wrapper.user().getProtocolInfo().getUuid());
tracker.setClientEntityGameMode(wrapper.get(Types.UNSIGNED_BYTE, 0));
@@ -275,7 +275,7 @@ public class EntityPacketRewriter1_8 extends VREntityRewriter<ClientboundPackets
final byte pitch = wrapper.get(Types.BYTE, 1);
final byte yaw = wrapper.get(Types.BYTE, 0);
final byte headYaw = wrapper.get(Types.BYTE, 2);
final EntityTracker1_8 tracker = wrapper.user().getEntityTracker(Protocol1_8To1_7_6_10.class);
final EntityTracker1_8 tracker = wrapper.user().getEntityTracker(protocol);
final VirtualHologramEntity hologram = tracker.getHolograms().get(entityId);
hologram.setPosition(x / 32.0, y / 32.0, z / 32.0);
@@ -314,7 +314,7 @@ public class EntityPacketRewriter1_8 extends VREntityRewriter<ClientboundPackets
final int entityId = wrapper.get(Types.VAR_INT, 0);
final EntityTracker1_8 tracker = wrapper.user().getEntityTracker(Protocol1_8To1_7_6_10.class);
final EntityTracker1_8 tracker = wrapper.user().getEntityTracker(protocol);
if (gameProfile != null && gameProfile.gamemode == 3) { // Spectator mode
for (short i = 0; i < 5; i++) {
final PacketWrapper entityEquipment = PacketWrapper.create(ClientboundPackets1_7_2_5.SET_EQUIPPED_ITEM, wrapper.user());
@@ -354,7 +354,7 @@ public class EntityPacketRewriter1_8 extends VREntityRewriter<ClientboundPackets
handler(wrapper -> protocol.getItemRewriter().handleItemToClient(wrapper.user(), wrapper.get(RewindTypes.COMPRESSED_NBT_ITEM, 0)));
handler(wrapper -> {
final EntityTracker1_8 tracker = wrapper.user().getEntityTracker(Protocol1_8To1_7_6_10.class);
final EntityTracker1_8 tracker = wrapper.user().getEntityTracker(protocol);
final int id = wrapper.get(Types.INT, 0);
final int limit = tracker.clientEntityId() == id ? 3 : 4;
@@ -364,7 +364,7 @@ public class EntityPacketRewriter1_8 extends VREntityRewriter<ClientboundPackets
});
handler(wrapper -> {
final EntityTracker1_8 tracker = wrapper.user().getEntityTracker(Protocol1_8To1_7_6_10.class);
final EntityTracker1_8 tracker = wrapper.user().getEntityTracker(protocol);
final short slot = wrapper.get(Types.SHORT, 0);
final UUID uuid = tracker.getPlayerUUID(wrapper.get(Types.INT, 0));
if (uuid == null) {
@@ -396,7 +396,7 @@ public class EntityPacketRewriter1_8 extends VREntityRewriter<ClientboundPackets
map(Types.VAR_INT, Types.INT); // Collected Entity id
map(Types.VAR_INT, Types.INT); // Collector Entity id
handler(wrapper -> wrapper.user().getEntityTracker(Protocol1_8To1_7_6_10.class).removeEntity(wrapper.get(Types.INT, 0)));
handler(wrapper -> wrapper.user().getEntityTracker(protocol).removeEntity(wrapper.get(Types.INT, 0)));
}
});
@@ -424,7 +424,7 @@ public class EntityPacketRewriter1_8 extends VREntityRewriter<ClientboundPackets
read(Types.BOOLEAN); // On ground
handler(wrapper -> {
final EntityTracker1_8 tracker = wrapper.user().getEntityTracker(Protocol1_8To1_7_6_10.class);
final EntityTracker1_8 tracker = wrapper.user().getEntityTracker(protocol);
final VirtualHologramEntity hologram = tracker.getHolograms().get(wrapper.get(Types.INT, 0));
if (hologram != null) {
@@ -448,7 +448,7 @@ public class EntityPacketRewriter1_8 extends VREntityRewriter<ClientboundPackets
read(Types.BOOLEAN); // On ground
handler(wrapper -> {
final EntityTracker1_8 tracker = wrapper.user().getEntityTracker(Protocol1_8To1_7_6_10.class);
final EntityTracker1_8 tracker = wrapper.user().getEntityTracker(protocol);
final VirtualHologramEntity hologram = tracker.getHolograms().get(wrapper.get(Types.INT, 0));
if (hologram != null) {
@@ -474,7 +474,7 @@ public class EntityPacketRewriter1_8 extends VREntityRewriter<ClientboundPackets
read(Types.BOOLEAN); // On ground
handler(wrapper -> {
final EntityTracker1_8 tracker = wrapper.user().getEntityTracker(Protocol1_8To1_7_6_10.class);
final EntityTracker1_8 tracker = wrapper.user().getEntityTracker(protocol);
final VirtualHologramEntity hologram = tracker.getHolograms().get(wrapper.get(Types.INT, 0));
if (hologram != null) {
@@ -506,7 +506,7 @@ public class EntityPacketRewriter1_8 extends VREntityRewriter<ClientboundPackets
handler(wrapper -> {
final int entityId = wrapper.get(Types.INT, 0);
final EntityTracker1_8 tracker = wrapper.user().getEntityTracker(Protocol1_8To1_7_6_10.class);
final EntityTracker1_8 tracker = wrapper.user().getEntityTracker(protocol);
if (tracker.entityType(entityId) == EntityTypes1_8.EntityType.ABSTRACT_MINECART) { // TODO | Realign all entities?
int y = wrapper.get(Types.INT, 2);
y += 12;
@@ -537,7 +537,7 @@ public class EntityPacketRewriter1_8 extends VREntityRewriter<ClientboundPackets
map(Types.BYTE); // Head yaw
handler(wrapper -> {
final EntityTracker1_8 tracker = wrapper.user().getEntityTracker(Protocol1_8To1_7_6_10.class);
final EntityTracker1_8 tracker = wrapper.user().getEntityTracker(protocol);
final VirtualHologramEntity hologram = tracker.getHolograms().get(wrapper.get(Types.INT, 0));
if (hologram != null) {
@@ -559,7 +559,7 @@ public class EntityPacketRewriter1_8 extends VREntityRewriter<ClientboundPackets
handler(wrapper -> {
final boolean leash = wrapper.get(Types.BOOLEAN, 0);
if (!leash) {
final EntityTracker1_8 tracker = wrapper.user().getEntityTracker(Protocol1_8To1_7_6_10.class);
final EntityTracker1_8 tracker = wrapper.user().getEntityTracker(protocol);
final int passenger = wrapper.get(Types.INT, 0);
final int vehicle = wrapper.get(Types.INT, 1);
@@ -594,7 +594,7 @@ public class EntityPacketRewriter1_8 extends VREntityRewriter<ClientboundPackets
public void register() {
map(Types.VAR_INT, Types.INT); // Entity id
handler(wrapper -> {
final EntityTracker1_8 tracker = wrapper.user().getEntityTracker(Protocol1_8To1_7_6_10.class);
final EntityTracker1_8 tracker = wrapper.user().getEntityTracker(protocol);
if (tracker.getHolograms().containsKey(wrapper.get(Types.INT, 0))) { // Don't handle properties for hologram emulation
wrapper.cancel();
return;
@@ -38,6 +38,7 @@ import com.viaversion.viarewind.protocol.v1_8to1_7_6_10.storage.InventoryTracker
import com.viaversion.viarewind.protocol.v1_8to1_7_6_10.storage.PlayerSessionStorage;
import com.viaversion.viarewind.utils.ChatUtil;
import com.viaversion.viaversion.api.Via;
import com.viaversion.viaversion.api.minecraft.BlockPosition;
import com.viaversion.viaversion.api.minecraft.ClientWorld;
import com.viaversion.viaversion.api.minecraft.GameProfile;
import com.viaversion.viaversion.api.minecraft.entitydata.EntityData;
@@ -48,6 +49,7 @@ import com.viaversion.viaversion.api.rewriter.RewriterBase;
import com.viaversion.viaversion.api.type.Types;
import com.viaversion.viaversion.libs.gson.JsonElement;
import com.viaversion.viaversion.libs.gson.JsonObject;
import com.viaversion.viaversion.libs.gson.JsonPrimitive;
import com.viaversion.viaversion.protocols.v1_8to1_9.packet.ClientboundPackets1_8;
import com.viaversion.viaversion.protocols.v1_8to1_9.packet.ServerboundPackets1_8;
import com.viaversion.viaversion.util.ComponentUtil;
@@ -127,13 +129,13 @@ public class PlayerPacketRewriter1_8 extends RewriterBase<Protocol1_8To1_7_6_10>
}
}
final EntityTracker1_8 tracker = wrapper.user().getEntityTracker(Protocol1_8To1_7_6_10.class);
final EntityTracker1_8 tracker = wrapper.user().getEntityTracker(protocol);
tracker.setClientEntityGameMode(wrapper.get(Types.UNSIGNED_BYTE, 1));
final int dimension = wrapper.get(Types.INT, 0);
// Clear entities on dimension change and re-track player
final ClientWorld world = wrapper.user().getClientWorld(Protocol1_8To1_7_6_10.class);
final ClientWorld world = wrapper.user().storables(protocol).clientWorld();
if (world.setEnvironment(dimension)) {
tracker.clearEntities();
tracker.addPlayer(tracker.clientEntityId(), wrapper.user().getProtocolInfo().getUuid());
@@ -194,7 +196,7 @@ public class PlayerPacketRewriter1_8 extends RewriterBase<Protocol1_8To1_7_6_10>
wrapper.write(Types.BOOLEAN, playerSession.onGround);
final EntityTracker1_8 tracker = wrapper.user().getEntityTracker(Protocol1_8To1_7_6_10.class);
final EntityTracker1_8 tracker = wrapper.user().getEntityTracker(protocol);
if (!Objects.equals(tracker.spectatingClientEntityId, tracker.clientEntityIdOrNull())) {
wrapper.cancel();
}
@@ -220,7 +222,7 @@ public class PlayerPacketRewriter1_8 extends RewriterBase<Protocol1_8To1_7_6_10>
if (wrapper.get(Types.UNSIGNED_BYTE, 0) != 3) return; // Change game mode
int gameMode = wrapper.get(Types.FLOAT, 0).intValue();
final EntityTracker1_8 tracker = wrapper.user().getEntityTracker(Protocol1_8To1_7_6_10.class);
final EntityTracker1_8 tracker = wrapper.user().getEntityTracker(protocol);
if (gameMode == 3 || tracker.isSpectator()) {
UUID myId = wrapper.user().getProtocolInfo().getUuid();
Item[] equipment = new Item[4];
@@ -292,7 +294,7 @@ public class PlayerPacketRewriter1_8 extends RewriterBase<Protocol1_8To1_7_6_10>
}
if (gamemode == 3 || gameProfile.gamemode == 3) {
EntityTracker1_8 tracker = wrapper.user().getEntityTracker(Protocol1_8To1_7_6_10.class);
EntityTracker1_8 tracker = wrapper.user().getEntityTracker(protocol);
int entityId = tracker.getPlayerEntityId(uuid);
boolean isOwnPlayer = entityId == tracker.clientEntityId();
if (entityId != -1) {
@@ -445,7 +447,7 @@ public class PlayerPacketRewriter1_8 extends RewriterBase<Protocol1_8To1_7_6_10>
wrapper.cancel();
final int entityId = wrapper.read(Types.VAR_INT);
final EntityTracker1_8 tracker = wrapper.user().getEntityTracker(Protocol1_8To1_7_6_10.class);
final EntityTracker1_8 tracker = wrapper.user().getEntityTracker(protocol);
if (tracker.spectatingClientEntityId != entityId) {
tracker.setSpectating(entityId);
}
@@ -506,12 +508,18 @@ public class PlayerPacketRewriter1_8 extends RewriterBase<Protocol1_8To1_7_6_10>
protocol.registerServerbound(ServerboundPackets1_7_2_5.CHAT, wrapper -> {
final String message = wrapper.passthrough(Types.STRING);
final EntityTracker1_8 tracker = wrapper.user().getEntityTracker(Protocol1_8To1_7_6_10.class);
final EntityTracker1_8 tracker = wrapper.user().getEntityTracker(protocol);
if (tracker.isSpectator() && message.toLowerCase().startsWith("/stp ")) { // TODO add setting
String username = message.split(" ")[1];
final GameProfileStorage storage = wrapper.user().get(GameProfileStorage.class);
final GameProfileStorage.GameProfile profile = storage.get(username, true);
// Multiple players with the same name may exist. This will only represent
// fake players so it's fine to just pick the first one.
List<GameProfileStorage.GameProfile> profiles = storage.get(username, true);
if (profiles.isEmpty()) {
return;
}
final GameProfileStorage.GameProfile profile = profiles.get(0);
if (profile != null && profile.uuid != null) {
wrapper.cancel();
@@ -529,10 +537,7 @@ public class PlayerPacketRewriter1_8 extends RewriterBase<Protocol1_8To1_7_6_10>
map(Types.BYTE, Types.VAR_INT); // Mode
handler(wrapper -> {
final int mode = wrapper.get(Types.VAR_INT, 1);
if (mode != 0) {
return;
}
final EntityTracker1_8 tracker = wrapper.user().getEntityTracker(Protocol1_8To1_7_6_10.class);
final EntityTracker1_8 tracker = wrapper.user().getEntityTracker(protocol);
final int entityId = tracker.getHologramIdWithExtra(wrapper.get(Types.VAR_INT, 0));
final PlayerSessionStorage position = wrapper.user().get(PlayerSessionStorage.class);
@@ -541,6 +546,11 @@ public class PlayerPacketRewriter1_8 extends RewriterBase<Protocol1_8To1_7_6_10>
}
wrapper.set(Types.VAR_INT, 0, entityId);
if (mode != 0) {
return;
}
final AABB boundingBox = tracker.getHolograms().get(entityId).getBoundingBox();
Vector3d pos = new Vector3d(position.getPosX(), position.getPosY() + 1.8, position.getPosZ());
@@ -608,6 +618,16 @@ public class PlayerPacketRewriter1_8 extends RewriterBase<Protocol1_8To1_7_6_10>
protected void register() {
map(Types.VAR_INT); // Status
map(RewindTypes.U_BYTE_POSITION, Types.BLOCK_POSITION1_8); // Position
handler(wrapper -> {
// 1.7 uses 255 as "no face"
// 1.8 changed this to only be in the range EnumFacing range (0-5) and uses DOWN when no face is used
// This properly rewrites invalid values to the 1.8 equivalent
int face = wrapper.read(Types.UNSIGNED_BYTE);
if (face > 5) {
face = 0;
}
wrapper.write(Types.UNSIGNED_BYTE, (short) face);
});
}
});
@@ -618,6 +638,16 @@ public class PlayerPacketRewriter1_8 extends RewriterBase<Protocol1_8To1_7_6_10>
map(Types.BYTE); // Direction
map(RewindTypes.COMPRESSED_NBT_ITEM, Types.ITEM1_8); // Item
handler(wrapper -> {
final BlockPosition position = wrapper.get(Types.BLOCK_POSITION1_8, 0);
final byte direction = wrapper.get(Types.BYTE, 0);
// 1.7 serializes the Y component as an unsigned byte, turning the
// use-in-air position from (-1, -1, -1) into (-1, 255, -1).
// we change that back to -1 for Y like 1.8 clients
if (position.x() == -1 && position.y() == 255 && position.z() == -1 && direction == -1) {
wrapper.set(Types.BLOCK_POSITION1_8, 0, new BlockPosition(-1, -1, -1));
}
});
handler(wrapper -> protocol.getItemRewriter().handleItemToServer(wrapper.user(), wrapper.get(Types.ITEM1_8, 0)));
}
});
@@ -690,7 +720,7 @@ public class PlayerPacketRewriter1_8 extends RewriterBase<Protocol1_8To1_7_6_10>
if (!unmount) {
return;
}
final EntityTracker1_8 tracker = wrapper.user().getEntityTracker(Protocol1_8To1_7_6_10.class);
final EntityTracker1_8 tracker = wrapper.user().getEntityTracker(protocol);
if (tracker.spectatingClientEntityId != tracker.clientEntityId()) {
PacketWrapper sneakPacket = PacketWrapper.create(ServerboundPackets1_8.PLAYER_COMMAND, wrapper.user());
sneakPacket.write(Types.VAR_INT, tracker.clientEntityId());
@@ -770,6 +800,8 @@ public class PlayerPacketRewriter1_8 extends RewriterBase<Protocol1_8To1_7_6_10>
}
});
// 1.7.10 custom payload constructor (String, ByteBuf) uses byteBuf.array(), so it sends the whole backing array capacity, not only the written bytes
// After reading the proper packet data we discard the remaining bytes
protocol.registerServerbound(ServerboundPackets1_7_2_5.CUSTOM_PAYLOAD, new PacketHandlers() {
@Override
public void register() {
@@ -780,7 +812,27 @@ public class PlayerPacketRewriter1_8 extends RewriterBase<Protocol1_8To1_7_6_10>
switch (channel) {
case "MC|TrSel": {
wrapper.passthrough(Types.INT);
wrapper.read(Types.REMAINING_BYTES); // unused data ???
wrapper.read(Types.REMAINING_BYTES); // Discard remaining bytes
break;
}
case "MC|AdvCdm": {
final byte commandBlockType = wrapper.passthrough(Types.BYTE);
if (commandBlockType == 0) { // Block
wrapper.passthrough(Types.INT); // X
wrapper.passthrough(Types.INT); // Y
wrapper.passthrough(Types.INT); // Z
} else if (commandBlockType == 1) { // Minecart
wrapper.passthrough(Types.INT); // Entity id
}
wrapper.passthrough(Types.STRING); // Command
wrapper.write(Types.BOOLEAN, true); // Track output, added in 1.8, default to true
wrapper.read(Types.REMAINING_BYTES); // Discard remaining bytes
break;
}
case "MC|Beacon": {
wrapper.passthrough(Types.INT); // Primary effect
wrapper.passthrough(Types.INT); // Secondary effect
wrapper.read(Types.REMAINING_BYTES); // Discard remaining bytes
break;
}
case "MC|ItemName": {
@@ -797,17 +849,28 @@ public class PlayerPacketRewriter1_8 extends RewriterBase<Protocol1_8To1_7_6_10>
}
case "MC|BEdit":
case "MC|BSign": {
final boolean signBook = channel.equals("MC|BSign");
final Item book = wrapper.read(RewindTypes.COMPRESSED_NBT_ITEM);
CompoundTag tag = book.tag();
if (tag != null && tag.contains("pages")) {
ListTag<StringTag> pages = tag.getListTag("pages", StringTag.class);
for (int i = 0; i < pages.size(); i++) {
StringTag page = pages.get(i);
String value = page.getValue();
value = ComponentUtil.legacyToJsonString(value);
page.setValue(value);
wrapper.read(Types.REMAINING_BYTES); // Discard remaining bytes
if (book == null) {
wrapper.write(Types.ITEM1_8, null);
break;
}
final CompoundTag tag = book.tag();
if (signBook && tag != null && tag.contains("pages")) {
final ListTag<StringTag> pages = tag.getListTag("pages", StringTag.class);
if (pages != null) {
for (int i = 0; i < pages.size(); i++) {
final StringTag page = pages.get(i);
String value = page.getValue();
value = new JsonPrimitive(value).toString();
page.setValue(value);
}
}
}
protocol.getItemRewriter().handleItemToServer(wrapper.user(), book);
wrapper.write(Types.ITEM1_8, book);
break;
}
@@ -174,7 +174,7 @@ public class ScoreboardPacketRewriter1_8 extends RewriterBase<Protocol1_8To1_7_6
}
final ScoreboardTracker scoreboard = wrapper.user().get(ScoreboardTracker.class);
final EntityTracker1_8 tracker = wrapper.user().getEntityTracker(Protocol1_8To1_7_6_10.class);
final EntityTracker1_8 tracker = wrapper.user().getEntityTracker(protocol);
final byte mode = wrapper.passthrough(Types.BYTE);
if (mode != 0 && !scoreboard.teamExists(team)) {
@@ -47,7 +47,7 @@ public class WorldPacketRewriter1_8 extends RewriterBase<Protocol1_8To1_7_6_10>
@Override
protected void registerPackets() {
protocol.registerClientbound(ClientboundPackets1_8.LEVEL_CHUNK, wrapper -> {
final ClientWorld world = wrapper.user().getClientWorld(Protocol1_8To1_7_6_10.class);
final ClientWorld world = wrapper.user().storables(protocol).clientWorld();
final Chunk chunk = wrapper.read(ChunkType1_8.forEnvironment(world.getEnvironment()));
protocol.getItemRewriter().handleChunk(chunk);
@@ -22,6 +22,7 @@ import com.viaversion.viarewind.protocol.v1_7_6_10to1_7_2_5.packet.ServerboundPa
import com.viaversion.viarewind.protocol.v1_8to1_7_6_10.Protocol1_8To1_7_6_10;
import com.viaversion.viarewind.protocol.v1_8to1_7_6_10.data.VirtualHologramEntity;
import com.viaversion.viaversion.api.connection.UserConnection;
import com.viaversion.viaversion.api.data.entity.TrackedEntity;
import com.viaversion.viaversion.api.minecraft.entities.EntityType;
import com.viaversion.viaversion.api.minecraft.entities.EntityTypes1_8;
import com.viaversion.viaversion.api.minecraft.entitydata.EntityData;
@@ -68,17 +69,15 @@ public class EntityTracker1_8 extends EntityTrackerBase {
}
@Override
public void addEntity(int id, EntityType type) {
super.addEntity(id, type);
public TrackedEntity addEntity(int id, EntityType type) {
if (type == EntityTypes1_8.EntityType.ARMOR_STAND) {
holograms.put(id, new VirtualHologramEntity(user(), id));
}
return super.addEntity(id, type);
}
@Override
public void removeEntity(int entityId) {
super.removeEntity(entityId);
public TrackedEntity removeEntity(int entityId) {
final VirtualHologramEntity hologram = holograms.get(entityId);
if (hologram != null) {
hologram.deleteEntity();
@@ -95,6 +94,7 @@ public class EntityTracker1_8 extends EntityTrackerBase {
entityUUIDToId.removeInt(playerId);
user().get(PlayerSessionStorage.class).getPlayerEquipment().remove(playerId);
}
return super.removeEntity(entityId);
}
@Override
@@ -222,17 +222,16 @@ public class EntityTracker1_8 extends EntityTrackerBase {
public void checkNametagVisbility(final String username) {
final GameProfileStorage profileStorage = user().get(GameProfileStorage.class);
final GameProfileStorage.GameProfile profile = profileStorage.get(username, false);
if (profile == null) {
return;
}
final List<GameProfileStorage.GameProfile> profiles = profileStorage.get(username, false);
final int entityId = getPlayerEntityId(profile.uuid);
if (entityId == -1) {
return;
}
for (GameProfileStorage.GameProfile profile : profiles) {
final int entityId = getPlayerEntityId(profile.uuid);
if (entityId == -1) {
return;
}
checkNametagVisibility(entityId);
checkNametagVisibility(entityId);
}
}
private boolean isPlayerNametagHidden(final int entityId) {
@@ -17,6 +17,10 @@
*/
package com.viaversion.viarewind.protocol.v1_8to1_7_6_10.storage;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Multimap;
import com.viaversion.nbt.tag.CompoundTag;
import com.viaversion.nbt.tag.ListTag;
import com.viaversion.nbt.tag.StringTag;
@@ -34,6 +38,7 @@ import java.util.UUID;
public class GameProfileStorage extends StoredObject {
private final Map<UUID, GameProfile> properties = new HashMap<>();
private final ListMultimap<String, GameProfile> nameToProfile = ArrayListMultimap.create();
public GameProfileStorage(UserConnection user) {
super(user);
@@ -42,6 +47,7 @@ public class GameProfileStorage extends StoredObject {
public GameProfile put(UUID uuid, String name, String displayName, int ping, int gamemode) {
GameProfile gameProfile = new GameProfile(uuid, name, displayName, ping, gamemode);
properties.put(uuid, gameProfile);
nameToProfile.put(name, gameProfile);
return gameProfile;
}
@@ -49,19 +55,9 @@ public class GameProfileStorage extends StoredObject {
return properties.get(uuid);
}
public GameProfile get(String name, boolean ignoreCase) {
public List<GameProfile> get(String name, boolean ignoreCase) {
if (ignoreCase) name = name.toLowerCase();
for (GameProfile profile : properties.values()) {
if (profile.name == null) continue;
String n = ignoreCase ? profile.name.toLowerCase() : profile.name;
if (n.equals(name)) {
return profile;
}
}
return null;
return nameToProfile.get(name);
}
public List<GameProfile> getAllWithPrefix(String prefix, boolean ignoreCase) {
@@ -81,7 +77,11 @@ public class GameProfileStorage extends StoredObject {
}
public GameProfile remove(UUID uuid) {
return properties.remove(uuid);
GameProfile removedProfile = properties.remove(uuid);
if (removedProfile != null) {
nameToProfile.get(removedProfile.name).remove(removedProfile);
}
return removedProfile;
}
@@ -140,37 +140,43 @@ public class ScoreboardTracker extends StoredObject {
public String sendTeamForScore(String score) {
if (score.length() <= 16) return score;
if (scoreTeams.containsKey(score)) return scoreTeams.get(score).name;
int l = 16;
int i = Math.min(16, score.length() - 16);
String name = score.substring(i, i + l);
while (scoreTeamNames.contains(name) || teams.containsKey(name)) {
i--;
while (score.length() - l - i > 16) {
l--;
if (l < 1) return score;
i = Math.min(16, score.length() - l);
// Find the longest window score[i, i+l) that:
// - is not already claimed as a team name
// - keeps prefix (score[0,i)) at most 16 chars i <= 16
// - keeps suffix (score[i+l, end)) at most 16 chars score.length()-l-i <= 16
for (int l = 16; l >= 1; l--) {
final int iMax = Math.min(16, score.length() - l);
final int iMin = Math.max(0, score.length() - l - 16);
for (int i = iMax; i >= iMin; i--) {
final String name = score.substring(i, i + l);
if (scoreTeamNames.contains(name) || teams.containsKey(name)) {
continue;
}
final String prefix = score.substring(0, i);
final String suffix = i + l >= score.length() ? "" : score.substring(i + l);
final ScoreTeam scoreTeam = new ScoreTeam(name, prefix, suffix);
scoreTeams.put(score, scoreTeam);
scoreTeamNames.add(name);
final PacketWrapper teamPacket = PacketWrapper.create(ClientboundPackets1_7_2_5.SET_PLAYER_TEAM, getUser());
teamPacket.write(Types.STRING, name);
teamPacket.write(Types.BYTE, (byte) 0);
teamPacket.write(Types.STRING, "ViaRewind");
teamPacket.write(Types.STRING, prefix);
teamPacket.write(Types.STRING, suffix);
teamPacket.write(Types.BYTE, (byte) 0);
teamPacket.write(Types.SHORT, (short) 1);
teamPacket.write(Types.STRING, name);
teamPacket.send(Protocol1_8To1_7_6_10.class);
return name;
}
name = score.substring(i, i + l);
}
String prefix = score.substring(0, i);
String suffix = i + l >= score.length() ? "" : score.substring(i + l);
ScoreTeam scoreTeam = new ScoreTeam(name, prefix, suffix);
scoreTeams.put(score, scoreTeam);
scoreTeamNames.add(name);
PacketWrapper teamPacket = PacketWrapper.create(ClientboundPackets1_7_2_5.SET_PLAYER_TEAM, getUser());
teamPacket.write(Types.STRING, name);
teamPacket.write(Types.BYTE, (byte) 0);
teamPacket.write(Types.STRING, "ViaRewind");
teamPacket.write(Types.STRING, prefix);
teamPacket.write(Types.STRING, suffix);
teamPacket.write(Types.BYTE, (byte) 0);
teamPacket.write(Types.SHORT, (short) 1);
teamPacket.write(Types.STRING, name);
teamPacket.send(Protocol1_8To1_7_6_10.class);
return name;
return score;
}
public String removeTeamForScore(String score) {
@@ -35,6 +35,9 @@ public class WorldBorderUpdateTask implements Runnable {
public void run() {
for (UserConnection connection : Via.getManager().getConnectionManager().getConnections()) {
final WorldBorderEmulator worldBorderEmulatorTracker = connection.get(WorldBorderEmulator.class);
if (worldBorderEmulatorTracker == null) {
continue;
}
if (!worldBorderEmulatorTracker.isInit()) continue;
final PlayerSessionStorage playerSession = connection.get(PlayerSessionStorage.class);
@@ -26,14 +26,17 @@ import com.viaversion.viarewind.protocol.v1_9to1_8.rewriter.PlayerPacketRewriter
import com.viaversion.viarewind.protocol.v1_9to1_8.rewriter.WorldPacketRewriter1_9;
import com.viaversion.viarewind.protocol.v1_9to1_8.storage.BlockPlaceDestroyTracker;
import com.viaversion.viarewind.protocol.v1_9to1_8.storage.BossBarStorage;
import com.viaversion.viarewind.protocol.v1_9to1_8.storage.CommandBlockStateStorage;
import com.viaversion.viarewind.protocol.v1_9to1_8.storage.CooldownStorage;
import com.viaversion.viarewind.protocol.v1_9to1_8.storage.EntityTracker1_9;
import com.viaversion.viarewind.protocol.v1_9to1_8.storage.LevitationStorage;
import com.viaversion.viarewind.protocol.v1_9to1_8.storage.PlayerPositionTracker;
import com.viaversion.viarewind.protocol.v1_9to1_8.storage.LastTitle;
import com.viaversion.viarewind.protocol.v1_9to1_8.storage.WindowTracker;
import com.viaversion.viarewind.protocol.v1_9to1_8.task.CooldownIndicatorTask;
import com.viaversion.viarewind.protocol.v1_9to1_8.task.LevitationUpdateTask;
import com.viaversion.viaversion.api.Via;
import com.viaversion.viaversion.api.connection.ProtocolStorables;
import com.viaversion.viaversion.api.connection.UserConnection;
import com.viaversion.viaversion.api.minecraft.ClientWorld;
import com.viaversion.viaversion.api.platform.providers.ViaProviders;
@@ -51,7 +54,7 @@ public class Protocol1_9To1_8 extends BackwardsProtocol<ClientboundPackets1_9, C
public static final ValueTransformer<Double, Integer> DOUBLE_TO_INT_TIMES_32 = new ValueTransformer<>(Types.INT) {
@Override
public Integer transform(PacketWrapper wrapper, Double inputValue) {
return (int) (inputValue * 32.0D);
return (int) Math.round(inputValue * 32.0D);
}
};
public static final ValueTransformer<Float, Byte> DEGREES_TO_ANGLE = new ValueTransformer<>(Types.BYTE) {
@@ -61,7 +64,7 @@ public class Protocol1_9To1_8 extends BackwardsProtocol<ClientboundPackets1_9, C
}
};
public static final RewindMappingData MAPPINGS = new RewindMappingData("1.9.4", "1.8");
public static final RewindMappingData MAPPINGS = new RewindMappingData("1.9", "1.8");
private final BlockItemPacketRewriter1_9 itemRewriter = new BlockItemPacketRewriter1_9(this);
private final EntityPacketRewriter1_9 entityRewriter = new EntityPacketRewriter1_9(this);
@@ -80,15 +83,18 @@ public class Protocol1_9To1_8 extends BackwardsProtocol<ClientboundPackets1_9, C
@Override
public void init(UserConnection connection) {
connection.addEntityTracker(this.getClass(), new EntityTracker1_9(connection));
connection.addClientWorld(this.getClass(), new ClientWorld());
final ProtocolStorables storables = connection.storables(this);
storables.setEntityTracker(new EntityTracker1_9(connection));
storables.setClientWorld(new ClientWorld());
connection.put(new WindowTracker(connection));
connection.put(new LevitationStorage());
connection.put(new PlayerPositionTracker());
connection.put(new CooldownStorage());
connection.put(new LastTitle());
connection.put(new BlockPlaceDestroyTracker());
connection.put(new BossBarStorage(connection));
connection.put(new CommandBlockStateStorage());
}
@Override
@@ -18,21 +18,23 @@
package com.viaversion.viarewind.protocol.v1_9to1_8.cooldown;
import com.viaversion.viarewind.protocol.v1_9to1_8.Protocol1_9To1_8;
import com.viaversion.viarewind.protocol.v1_9to1_8.storage.LastTitle;
import com.viaversion.viaversion.api.connection.UserConnection;
import com.viaversion.viaversion.api.protocol.packet.PacketWrapper;
import com.viaversion.viaversion.api.type.Types;
import com.viaversion.viaversion.libs.gson.JsonPrimitive;
import com.viaversion.viaversion.protocols.v1_8to1_9.packet.ClientboundPackets1_8;
import java.util.function.Consumer;
public class TitleCooldownVisualization implements CooldownVisualization {
private static final int ACTION_SET_TITLE = 0;
private static final int ACTION_SET_SUBTITLE = 1;
private static final int ACTION_SET_TIMES_AND_DISPLAY = 2;
private static final int ACTION_HIDE = 3;
public static final int ACTION_SET_TITLE = 0;
public static final int ACTION_SET_SUBTITLE = 1;
public static final int ACTION_SET_TIMES_AND_DISPLAY = 2;
public static final int ACTION_HIDE = 3;
public static final int ACTION_RESET = 4;
private final UserConnection user;
private boolean visible;
public TitleCooldownVisualization(UserConnection user) {
this.user = user;
@@ -40,28 +42,71 @@ public class TitleCooldownVisualization implements CooldownVisualization {
@Override
public void show(double progress) throws Exception {
final String text = CooldownVisualization.buildProgressText("˙", progress);
final LastTitle title = this.user.get(LastTitle.class);
if (this.visible && title.isVisible()) {
this.restoreTitle(title);
return;
}
sendTitlePacket(ACTION_SET_TITLE, wrapper -> wrapper.write(Types.COMPONENT, new JsonPrimitive("")));
sendTitlePacket(ACTION_SET_SUBTITLE, wrapper -> wrapper.write(Types.COMPONENT, new JsonPrimitive(text)));
sendTitlePacket(ACTION_SET_TIMES_AND_DISPLAY, wrapper -> {
wrapper.write(Types.INT, 0);
wrapper.write(Types.INT, 2);
wrapper.write(Types.INT, 5);
});
final PacketWrapper setTitle = PacketWrapper.create(ClientboundPackets1_8.SET_TITLES, user);
setTitle.write(Types.VAR_INT, ACTION_SET_TITLE);
setTitle.write(Types.COMPONENT, new JsonPrimitive(""));
setTitle.send(Protocol1_9To1_8.class);
final PacketWrapper setSubtitle = PacketWrapper.create(ClientboundPackets1_8.SET_TITLES, user);
setSubtitle.write(Types.VAR_INT, ACTION_SET_SUBTITLE);
setSubtitle.write(Types.COMPONENT, new JsonPrimitive(CooldownVisualization.buildProgressText("˙", progress)));
setSubtitle.send(Protocol1_9To1_8.class);
final PacketWrapper setTitleTimes = PacketWrapper.create(ClientboundPackets1_8.SET_TITLES, user);
setTitleTimes.write(Types.VAR_INT, ACTION_SET_TIMES_AND_DISPLAY);
setTitleTimes.write(Types.INT, 0);
setTitleTimes.write(Types.INT, 2);
setTitleTimes.write(Types.INT, 5);
setTitleTimes.send(Protocol1_9To1_8.class);
this.visible = true;
}
@Override
public void hide() throws Exception {
sendTitlePacket(ACTION_HIDE, wrapper -> {
});
if (!this.visible) {
return;
}
final LastTitle title = this.user.get(LastTitle.class);
if (title.isVisible()) {
this.restoreTitle(title);
return;
}
final PacketWrapper hideTitle = PacketWrapper.create(ClientboundPackets1_8.SET_TITLES, user);
hideTitle.write(Types.VAR_INT, ACTION_HIDE);
hideTitle.send(Protocol1_9To1_8.class);
this.visible = false;
}
private void sendTitlePacket(int action, Consumer<PacketWrapper> writer) {
final PacketWrapper title = PacketWrapper.create(ClientboundPackets1_8.SET_TITLES, user);
title.write(Types.VAR_INT, action);
writer.accept(title);
title.scheduleSend(Protocol1_9To1_8.class);
private void restoreTitle(final LastTitle title) {
final PacketWrapper setTitleTimes = PacketWrapper.create(ClientboundPackets1_8.SET_TITLES, user);
setTitleTimes.write(Types.VAR_INT, ACTION_SET_TIMES_AND_DISPLAY);
final int[] times = title.remainingTimes();
setTitleTimes.write(Types.INT, times[0]);
setTitleTimes.write(Types.INT, times[1]);
setTitleTimes.write(Types.INT, times[2]);
setTitleTimes.send(Protocol1_9To1_8.class);
if (title.subtitle() != null) {
final PacketWrapper setSubtitle = PacketWrapper.create(ClientboundPackets1_8.SET_TITLES, user);
setSubtitle.write(Types.VAR_INT, ACTION_SET_SUBTITLE);
setSubtitle.write(Types.COMPONENT, title.subtitle());
setSubtitle.send(Protocol1_9To1_8.class);
}
final PacketWrapper setTitle = PacketWrapper.create(ClientboundPackets1_8.SET_TITLES, user);
setTitle.write(Types.VAR_INT, ACTION_SET_TITLE);
setTitle.write(Types.COMPONENT, title.title());
setTitle.send(Protocol1_9To1_8.class);
this.visible = false;
}
}
@@ -0,0 +1,165 @@
/*
* This file is part of ViaRewind - https://github.com/ViaVersion/ViaRewind
* Copyright (C) 2018-2026 ViaVersion and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.viaversion.viarewind.protocol.v1_9to1_8.data;
import com.viaversion.nbt.tag.CompoundTag;
import com.viaversion.nbt.tag.NumberTag;
import com.viaversion.nbt.tag.StringTag;
public final class CommandBlockState {
public static final int COMMAND_BLOCK_COMMAND_LIMIT = 32500;
private static final String IMPULSE = "IMPULSE";
private static final String REPEAT = "REPEAT";
private static final String CHAIN = "CHAIN";
private static final String CONDITIONAL = "CONDITIONAL";
private static final String UNCONDITIONAL = "UNCONDITIONAL";
private static final String ALWAYS_ACTIVE = "ALWAYSACTIVE";
private static final String REDSTONE = "REDSTONE";
private CommandBlockState() {
}
public static boolean isCommandBlock(final int blockState) {
return isImpulse(blockState) || isRepeat(blockState) || isChain(blockState);
}
public static void decorateCommand(final CompoundTag tag, final int blockState) {
if (tag == null || !isCommandBlock(blockState)) {
return;
}
final StringTag commandTag = tag.getStringTag("Command");
if (commandTag == null) {
return;
}
final Mode mode = mode(blockState);
final boolean conditional = (blockState & 8) != 0;
final boolean automatic = booleanTag(tag, "auto");
if (mode == Mode.REDSTONE && !conditional && !automatic) {
return;
}
final String prefix = mode.prefix + ' ' + (conditional ? CONDITIONAL : UNCONDITIONAL) + ' ' + (automatic ? ALWAYS_ACTIVE : REDSTONE) + ' ';
final String command = commandTag.getValue();
if (!parseDecoratedCommand(command).prefixed() && prefix.length() + command.length() <= COMMAND_BLOCK_COMMAND_LIMIT) {
commandTag.setValue(prefix + command);
}
}
public static ParsedCommand parseDecoratedCommand(final String command) {
final int first = command.indexOf(' ');
if (first == -1) {
return ParsedCommand.defaultState(command);
}
final int second = command.indexOf(' ', first + 1);
if (second == -1) {
return ParsedCommand.defaultState(command);
}
final int third = command.indexOf(' ', second + 1);
if (third == -1) {
return ParsedCommand.defaultState(command);
}
final Mode mode = Mode.fromPrefix(command.substring(0, first));
if (mode == null) {
return ParsedCommand.defaultState(command);
}
final String conditionalToken = command.substring(first + 1, second);
final boolean conditional;
if (CONDITIONAL.equalsIgnoreCase(conditionalToken)) {
conditional = true;
} else if (UNCONDITIONAL.equalsIgnoreCase(conditionalToken)) {
conditional = false;
} else {
return ParsedCommand.defaultState(command);
}
final String automaticToken = command.substring(second + 1, third);
final boolean automatic;
if (ALWAYS_ACTIVE.equalsIgnoreCase(automaticToken)) {
automatic = true;
} else if (REDSTONE.equalsIgnoreCase(automaticToken)) {
automatic = false;
} else {
return ParsedCommand.defaultState(command);
}
return new ParsedCommand(command.substring(third + 1), mode.serverName, conditional, automatic, true);
}
private static boolean booleanTag(final CompoundTag tag, final String key) {
final NumberTag numberTag = tag.getNumberTag(key);
return numberTag != null && numberTag.asByte() != 0;
}
private static Mode mode(final int blockState) {
if (isRepeat(blockState)) {
return Mode.AUTO;
}
if (isChain(blockState)) {
return Mode.SEQUENCE;
}
return Mode.REDSTONE;
}
private static boolean isImpulse(final int blockState) {
return blockState >= 2192 && blockState <= 2207;
}
private static boolean isRepeat(final int blockState) {
return blockState >= 3360 && blockState <= 3375;
}
private static boolean isChain(final int blockState) {
return blockState >= 3376 && blockState <= 3391;
}
public record ParsedCommand(String command, String mode, boolean conditional, boolean automatic, boolean prefixed) {
private static ParsedCommand defaultState(final String command) {
return new ParsedCommand(command, Mode.REDSTONE.serverName, false, false, false);
}
}
private enum Mode {
REDSTONE(IMPULSE, "REDSTONE"),
AUTO(REPEAT, "AUTO"),
SEQUENCE(CHAIN, "SEQUENCE");
private final String prefix;
private final String serverName;
Mode(final String prefix, final String serverName) {
this.prefix = prefix;
this.serverName = serverName;
}
private static Mode fromPrefix(final String prefix) {
for (final Mode mode : values()) {
if (mode.prefix.equalsIgnoreCase(prefix)) {
return mode;
}
}
return null;
}
}
}
@@ -19,6 +19,7 @@ package com.viaversion.viarewind.protocol.v1_9to1_8.rewriter;
import com.viaversion.nbt.tag.ByteTag;
import com.viaversion.nbt.tag.CompoundTag;
import com.viaversion.nbt.tag.IntTag;
import com.viaversion.nbt.tag.ListTag;
import com.viaversion.nbt.tag.StringTag;
import com.viaversion.nbt.tag.Tag;
@@ -26,8 +27,11 @@ import com.viaversion.viabackwards.api.rewriters.LegacyEnchantmentRewriter;
import com.viaversion.viarewind.api.rewriter.VRBlockItemRewriter;
import com.viaversion.viarewind.protocol.v1_9to1_8.Protocol1_9To1_8;
import com.viaversion.viarewind.protocol.v1_9to1_8.data.PotionIdMappings1_8;
import com.viaversion.viarewind.protocol.v1_9to1_8.storage.CommandBlockStateStorage;
import com.viaversion.viarewind.protocol.v1_9to1_8.storage.WindowTracker;
import com.viaversion.viaversion.api.connection.UserConnection;
import com.viaversion.viaversion.api.minecraft.BlockChangeRecord;
import com.viaversion.viaversion.api.minecraft.BlockPosition;
import com.viaversion.viaversion.api.minecraft.item.Item;
import com.viaversion.viaversion.api.protocol.remapper.PacketHandlers;
import com.viaversion.viaversion.api.type.Types;
@@ -59,8 +63,26 @@ public class BlockItemPacketRewriter1_9 extends VRBlockItemRewriter<ClientboundP
@Override
protected void registerPackets() {
registerBlockChange(ClientboundPackets1_9.BLOCK_UPDATE);
registerMultiBlockChange(ClientboundPackets1_9.CHUNK_BLOCKS_UPDATE);
protocol.registerClientbound(ClientboundPackets1_9.BLOCK_UPDATE, wrapper -> {
final BlockPosition position = wrapper.passthrough(Types.BLOCK_POSITION1_8);
final int blockState = wrapper.passthrough(Types.VAR_INT);
wrapper.user().get(CommandBlockStateStorage.class).storeOrRemove(position, blockState);
wrapper.set(Types.VAR_INT, 0, handleBlockId(blockState));
});
protocol.registerClientbound(ClientboundPackets1_9.CHUNK_BLOCKS_UPDATE, wrapper -> {
final int chunkX = wrapper.passthrough(Types.INT);
final int chunkZ = wrapper.passthrough(Types.INT);
final BlockChangeRecord[] records = wrapper.passthrough(Types.BLOCK_CHANGE_ARRAY);
final CommandBlockStateStorage storage = wrapper.user().get(CommandBlockStateStorage.class);
for (BlockChangeRecord record : records) {
final int blockState = record.getBlockId();
final BlockPosition position = new BlockPosition((chunkX << 4) + record.getSectionX(), record.getY(), (chunkZ << 4) + record.getSectionZ());
storage.storeOrRemove(position, blockState);
record.setBlockId(handleBlockId(blockState));
}
});
registerSetCreativeModeSlot(ServerboundPackets1_8.SET_CREATIVE_MODE_SLOT);
protocol.registerClientbound(ClientboundPackets1_9.CONTAINER_CLOSE, wrapper -> {
@@ -78,8 +100,12 @@ public class BlockItemPacketRewriter1_9 extends VRBlockItemRewriter<ClientboundP
protocol.registerClientbound(ClientboundPackets1_9.OPEN_SCREEN, wrapper -> {
final short windowId = wrapper.passthrough(Types.UNSIGNED_BYTE);
final String windowType = wrapper.passthrough(Types.STRING);
wrapper.passthrough(Types.COMPONENT); // title
final short slotCount = wrapper.passthrough(Types.UNSIGNED_BYTE);
wrapper.user().get(WindowTracker.class).put(windowId, windowType);
final WindowTracker tracker = wrapper.user().get(WindowTracker.class);
tracker.put(windowId, windowType);
tracker.openWindow(windowId, windowType, slotCount);
});
protocol.registerClientbound(ClientboundPackets1_9.CONTAINER_SET_CONTENT, wrapper -> {
@@ -115,10 +141,49 @@ public class BlockItemPacketRewriter1_9 extends VRBlockItemRewriter<ClientboundP
final Item item = wrapper.passthrough(Types.ITEM1_8);
handleItemToClient(wrapper.user(), item);
if (windowId == -2) {
// 1.8 has no window -2 (set a player-inv slot by raw index, ignoring the open container) — retarget it.
final WindowTracker tracker = wrapper.user().get(WindowTracker.class);
final short openWindow = tracker.openWindowId();
if (openWindow != 0) {
// Foreign container open: 1.8 only applies player-inv changes through it (main=size.., hotbar=size+27..).
final int size = tracker.openWindowSize();
final int containerSlot;
if (slot >= 0 && slot <= 8) {
containerSlot = size + 27 + slot;
} else if (slot >= 9 && slot <= 35) {
containerSlot = size + slot - 9;
} else { // armor/offhand: no slot in a foreign container
wrapper.cancel();
return;
}
wrapper.set(Types.BYTE, 0, (byte) openWindow);
wrapper.set(Types.SHORT, 0, (short) containerSlot);
return;
}
// Otherwise window 0 with the menu slot.
final int windowSlot;
if (slot >= 0 && slot <= 8) {
windowSlot = slot + 36;
} else if (slot >= 9 && slot <= 35) {
windowSlot = slot;
} else if (slot >= 36 && slot <= 39) {
windowSlot = 44 - slot;
} else { // offhand/body/saddle: no 1.8 equivalent
wrapper.cancel();
return;
}
wrapper.set(Types.BYTE, 0, (byte) 0);
wrapper.set(Types.SHORT, 0, (short) windowSlot);
return;
}
if (windowId == 0 && slot == 45) {
wrapper.cancel();
return;
}
final WindowTracker windowTracker = wrapper.user().get(WindowTracker.class);
final String windowType = windowTracker.get(windowId);
if (windowType != null && windowType.equalsIgnoreCase("minecraft:brewing_stand")) {
@@ -194,6 +259,7 @@ public class BlockItemPacketRewriter1_9 extends VRBlockItemRewriter<ClientboundP
@Override
public Item handleItemToClient(UserConnection connection, Item item) {
if (item == null) return null;
final int originalId = item.identifier();
super.handleItemToClient(connection, item);
CompoundTag tag = item.tag();
@@ -280,6 +346,32 @@ public class BlockItemPacketRewriter1_9 extends VRBlockItemRewriter<ClientboundP
});
}
// Colors the fake leather armor for an elytra gray
if (originalId == 443) {
if (tag == null) {
item.setTag(tag = new CompoundTag());
}
CompoundTag display = tag.getCompoundTag("display");
if (display == null) {
tag.put("display", display = new CompoundTag());
}
display.put("color", new IntTag(0x737373)); // Gray
tag.put(nbtTagName() + "|noDisplay", new ByteTag(true));
}
// Makes the fake banner for a shield brown if it has no banner patterns
if (originalId == 442) {
final CompoundTag blockEntityTag = tag == null ? null : tag.getCompoundTag("BlockEntityTag");
final ListTag<CompoundTag> patterns = blockEntityTag == null ? null : blockEntityTag.getListTag("Patterns", CompoundTag.class);
if (patterns == null || patterns.isEmpty()) {
item.setData((short) 3); // Brown
if (tag == null) {
item.setTag(tag = new CompoundTag());
}
tag.put(nbtTagName() + "|noData", new ByteTag(true));
}
}
return item;
}
@@ -304,7 +396,8 @@ public class BlockItemPacketRewriter1_9 extends VRBlockItemRewriter<ClientboundP
item.setData((short) 0);
}
if (item.identifier() == 373 && (tag == null || !tag.contains("Potion"))) { // Potions
if (item.identifier() == 373) { // Potions
// Restore the splash id even when a Potion tag is present (e.g. items re-sent by a creative mode client)
if (item.data() >= 16384) {
item.setIdentifier(438);
item.setData((short) (item.data() - 8192));
@@ -322,6 +415,9 @@ public class BlockItemPacketRewriter1_9 extends VRBlockItemRewriter<ClientboundP
if (tag == null) {
return item;
}
if (tag.remove(nbtTagName() + "|noData") != null) {
item.setData((short) 0);
}
final Tag noDisplayTag = tag.remove(nbtTagName() + "|noDisplay");
if (noDisplayTag != null) {
tag.remove("display");
@@ -68,7 +68,7 @@ public class EntityPacketRewriter1_9 extends VREntityRewriter<ClientboundPackets
final int dimension = wrapper.passthrough(Types.BYTE);
trackPlayer(wrapper.user(), entityId);
final ClientWorld clientWorld = wrapper.user().getClientWorld(Protocol1_9To1_8.class);
final ClientWorld clientWorld = wrapper.user().storables(protocol).clientWorld();
clientWorld.setEnvironment(dimension);
});
protocol.registerClientbound(ClientboundPackets1_9.SET_ENTITY_DATA, wrapper -> {
@@ -134,11 +134,24 @@ public class EntityPacketRewriter1_9 extends VREntityRewriter<ClientboundPackets
}
}
if (data > 0) {
wrapper.passthrough(Types.SHORT); // Velocity x
wrapper.passthrough(Types.SHORT); // Velocity y
wrapper.passthrough(Types.SHORT); // Velocity z
} else {
if (type.is(EntityTypes1_9.EntityType.POTION) && data > 0) {
// 1.8 clients read the potion type from the object data, but newer versions only
// send it in the entity data - the next packet. Held until then, see handleEntityData
wrapper.cancel();
final int x = wrapper.get(Types.INT, 0);
final int y = wrapper.get(Types.INT, 1);
final int z = wrapper.get(Types.INT, 2);
final byte pitch = wrapper.get(Types.BYTE, 1);
final byte yaw = wrapper.get(Types.BYTE, 2);
final short velocityX = wrapper.passthrough(Types.SHORT);
final short velocityY = wrapper.passthrough(Types.SHORT);
final short velocityZ = wrapper.passthrough(Types.SHORT);
final EntityTracker1_9 tracker = tracker(wrapper.user());
tracker.getPendingPotions().put(entityId, new EntityTracker1_9.PendingPotionEntity(x, y, z, pitch, yaw, velocityX, velocityY, velocityZ));
return;
}
if (data <= 0) {
final short velocityX = wrapper.read(Types.SHORT);
final short velocityY = wrapper.read(Types.SHORT);
final short velocityZ = wrapper.read(Types.SHORT);
@@ -164,7 +177,7 @@ public class EntityPacketRewriter1_9 extends VREntityRewriter<ClientboundPackets
map(Types.SHORT); // Count
handler(wrapper -> {
final int entityId = wrapper.get(Types.VAR_INT, 0);
wrapper.user().getEntityTracker(Protocol1_9To1_8.class).addEntity(entityId, EntityTypes1_9.EntityType.EXPERIENCE_ORB);
wrapper.user().getEntityTracker(protocol).addEntity(entityId, EntityTypes1_9.EntityType.EXPERIENCE_ORB);
});
}
});
@@ -179,7 +192,7 @@ public class EntityPacketRewriter1_9 extends VREntityRewriter<ClientboundPackets
map(Types.DOUBLE, Protocol1_9To1_8.DOUBLE_TO_INT_TIMES_32); // Z
handler(wrapper -> {
final int entityId = wrapper.get(Types.VAR_INT, 0);
wrapper.user().getEntityTracker(Protocol1_9To1_8.class).addEntity(entityId, EntityTypes1_9.EntityType.LIGHTNING_BOLT);
wrapper.user().getEntityTracker(protocol).addEntity(entityId, EntityTypes1_9.EntityType.LIGHTNING_BOLT);
});
}
});
@@ -216,7 +229,7 @@ public class EntityPacketRewriter1_9 extends VREntityRewriter<ClientboundPackets
map(Types.BYTE, Types.UNSIGNED_BYTE); // Direction
handler(wrapper -> {
final int entityId = wrapper.get(Types.VAR_INT, 0);
wrapper.user().getEntityTracker(Protocol1_9To1_8.class).addEntity(entityId, EntityTypes1_9.EntityType.PAINTING);
wrapper.user().getEntityTracker(protocol).addEntity(entityId, EntityTypes1_9.EntityType.PAINTING);
});
}
});
@@ -259,7 +272,7 @@ public class EntityPacketRewriter1_9 extends VREntityRewriter<ClientboundPackets
final int deltaY = wrapper.read(Types.SHORT);
final int deltaZ = wrapper.read(Types.SHORT);
final Vector[] moves = RelativeMoveUtil.calculateRelativeMoves(wrapper.user(), entityId, deltaX, deltaY, deltaZ);
final Vector[] moves = RelativeMoveUtil.calculateRelativeMoves(tracker(wrapper.user()), entityId, deltaX, deltaY, deltaZ);
wrapper.write(Types.BYTE, (byte) moves[0].blockX());
wrapper.write(Types.BYTE, (byte) moves[0].blockY());
@@ -285,7 +298,7 @@ public class EntityPacketRewriter1_9 extends VREntityRewriter<ClientboundPackets
final int deltaY = wrapper.read(Types.SHORT);
final int deltaZ = wrapper.read(Types.SHORT);
final Vector[] moves = RelativeMoveUtil.calculateRelativeMoves(wrapper.user(), entityId, deltaX, deltaY, deltaZ);
final Vector[] moves = RelativeMoveUtil.calculateRelativeMoves(tracker(wrapper.user()), entityId, deltaX, deltaY, deltaZ);
wrapper.write(Types.BYTE, (byte) moves[0].blockX());
wrapper.write(Types.BYTE, (byte) moves[0].blockY());
@@ -295,7 +308,7 @@ public class EntityPacketRewriter1_9 extends VREntityRewriter<ClientboundPackets
final byte pitch = wrapper.passthrough(Types.BYTE);
final boolean onGround = wrapper.passthrough(Types.BOOLEAN);
com.viaversion.viaversion.api.minecraft.entities.EntityType type = wrapper.user().getEntityTracker(Protocol1_9To1_8.class).entityType(entityId);
com.viaversion.viaversion.api.minecraft.entities.EntityType type = wrapper.user().getEntityTracker(protocol).entityType(entityId);
if (type == EntityTypes1_9.EntityType.BOAT) {
yaw -= 64;
wrapper.set(Types.BYTE, 3, yaw);
@@ -317,7 +330,7 @@ public class EntityPacketRewriter1_9 extends VREntityRewriter<ClientboundPackets
protocol.registerClientbound(ClientboundPackets1_9.MOVE_ENTITY_ROT, wrapper -> {
final int entityId = wrapper.passthrough(Types.VAR_INT);
final com.viaversion.viaversion.api.minecraft.entities.EntityType type = wrapper.user().getEntityTracker(Protocol1_9To1_8.class).entityType(entityId);
final com.viaversion.viaversion.api.minecraft.entities.EntityType type = wrapper.user().getEntityTracker(protocol).entityType(entityId);
if (type == EntityTypes1_9.EntityType.BOAT) {
byte yaw = wrapper.read(Types.BYTE);
yaw -= 64;
@@ -329,7 +342,7 @@ public class EntityPacketRewriter1_9 extends VREntityRewriter<ClientboundPackets
@Override
public void register() {
handler(wrapper -> {
final EntityTracker1_9 tracker = wrapper.user().getEntityTracker(Protocol1_9To1_8.class);
final EntityTracker1_9 tracker = wrapper.user().getEntityTracker(protocol);
final Integer vehicle = tracker.getVehicle(tracker.clientEntityId());
if (vehicle == null) {
wrapper.cancel();
@@ -360,7 +373,7 @@ public class EntityPacketRewriter1_9 extends VREntityRewriter<ClientboundPackets
}
final int entityId = wrapper.get(Types.VAR_INT, 0);
final EntityType type = wrapper.user().getEntityTracker(Protocol1_9To1_8.class).entityType(entityId);
final EntityType type = wrapper.user().getEntityTracker(protocol).entityType(entityId);
if (type == EntityTypes1_9.EntityType.BOAT) {
byte yaw = wrapper.get(Types.BYTE, 0);
yaw -= 64;
@@ -379,7 +392,7 @@ public class EntityPacketRewriter1_9 extends VREntityRewriter<ClientboundPackets
if (effectId > 23) { // Throw away new effects
wrapper.cancel();
}
final EntityTracker1_9 tracker = wrapper.user().getEntityTracker(Protocol1_9To1_8.class);
final EntityTracker1_9 tracker = wrapper.user().getEntityTracker(protocol);
if (effectId == 25 && entityId == tracker.clientEntityId()) {
wrapper.user().get(LevitationStorage.class).setActive(false);
}
@@ -416,7 +429,7 @@ public class EntityPacketRewriter1_9 extends VREntityRewriter<ClientboundPackets
protocol.registerClientbound(ClientboundPackets1_9.SET_PASSENGERS, null, wrapper -> {
wrapper.cancel();
final EntityTracker1_9 tracker = wrapper.user().getEntityTracker(Protocol1_9To1_8.class);
final EntityTracker1_9 tracker = wrapper.user().getEntityTracker(protocol);
final int vehicle = wrapper.read(Types.VAR_INT);
final IntList oldPassengers = tracker.getPassengers(vehicle);
@@ -444,6 +457,19 @@ public class EntityPacketRewriter1_9 extends VREntityRewriter<ClientboundPackets
}
});
protocol.registerClientbound(ClientboundPackets1_9.SET_ENTITY_MOTION, wrapper -> {
final int entityId = wrapper.passthrough(Types.VAR_INT);
final EntityTracker1_9 tracker = tracker(wrapper.user());
final EntityTracker1_9.PendingPotionEntity pending = tracker.getPendingPotions().get(entityId);
if (pending != null) {
wrapper.cancel();
final short velocityX = wrapper.read(Types.SHORT);
final short velocityY = wrapper.read(Types.SHORT);
final short velocityZ = wrapper.read(Types.SHORT);
tracker.getPendingPotions().put(entityId, pending.withVelocity(velocityX, velocityY, velocityZ));
}
});
protocol.registerClientbound(ClientboundPackets1_9.TELEPORT_ENTITY, new PacketHandlers() {
@Override
public void register() {
@@ -457,7 +483,19 @@ public class EntityPacketRewriter1_9 extends VREntityRewriter<ClientboundPackets
handler(wrapper -> {
final int entityId = wrapper.get(Types.VAR_INT, 0);
final EntityTracker1_9 tracker = wrapper.user().getEntityTracker(Protocol1_9To1_8.class);
final EntityTracker1_9 tracker = wrapper.user().getEntityTracker(protocol);
final EntityTracker1_9.PendingPotionEntity pending = tracker.getPendingPotions().get(entityId);
if (pending != null) {
wrapper.cancel();
final int x = wrapper.get(Types.INT, 0);
final int y = wrapper.get(Types.INT, 1);
final int z = wrapper.get(Types.INT, 2);
final byte yaw = wrapper.get(Types.BYTE, 0);
final byte pitch = wrapper.get(Types.BYTE, 1);
tracker.getPendingPotions().put(entityId, pending.withPosition(x, y, z, pitch, yaw));
return;
}
if (tracker.entityType(entityId) == EntityTypes1_9.EntityType.BOAT) {
byte yaw = wrapper.get(Types.BYTE, 0);
yaw -= 64;
@@ -475,7 +513,7 @@ public class EntityPacketRewriter1_9 extends VREntityRewriter<ClientboundPackets
protocol.registerClientbound(ClientboundPackets1_9.UPDATE_ATTRIBUTES, wrapper -> {
final int entityId = wrapper.passthrough(Types.VAR_INT);
final EntityTracker1_9 tracker = wrapper.user().getEntityTracker(Protocol1_9To1_8.class);
final EntityTracker1_9 tracker = wrapper.user().getEntityTracker(protocol);
final boolean player = entityId == tracker.clientEntityId();
// Throw away new attributes and track attack speed
@@ -522,7 +560,7 @@ public class EntityPacketRewriter1_9 extends VREntityRewriter<ClientboundPackets
if (effectId > 23) { // Throw away new effects
wrapper.cancel();
}
final EntityTracker1_9 tracker = wrapper.user().getEntityTracker(Protocol1_9To1_8.class);
final EntityTracker1_9 tracker = wrapper.user().getEntityTracker(protocol);
if (effectId == 25 && entityId == tracker.clientEntityId()) {
final LevitationStorage levitation = wrapper.user().get(LevitationStorage.class);
levitation.setActive(true);
@@ -541,10 +579,56 @@ public class EntityPacketRewriter1_9 extends VREntityRewriter<ClientboundPackets
filter().handler(this::handleEntityData);
}
private void sendDelayedPotionSpawn(final EntityDataHandlerEvent event, final EntityTracker1_9 tracker, final Item item) {
final EntityTracker1_9.PendingPotionEntity spawn = tracker.getPendingPotions().remove(event.entityId());
if (spawn == null) {
return;
}
final Item converted = protocol.getItemRewriter().handleItemToClient(event.user(), item);
short data = converted == null ? 0 : converted.data();
if (data <= 0) {
data = 16384; // Unmapped potion, at least display a splash bottle
}
final PacketWrapper addEntity = PacketWrapper.create(ClientboundPackets1_8.ADD_ENTITY, event.user());
addEntity.write(Types.VAR_INT, event.entityId());
addEntity.write(Types.BYTE, (byte) EntityTypes1_9.ObjectType.POTION.getId());
addEntity.write(Types.INT, spawn.x());
addEntity.write(Types.INT, spawn.y());
addEntity.write(Types.INT, spawn.z());
addEntity.write(Types.BYTE, spawn.pitch());
addEntity.write(Types.BYTE, spawn.yaw());
addEntity.write(Types.INT, (int) data);
addEntity.write(Types.SHORT, spawn.velocityX());
addEntity.write(Types.SHORT, spawn.velocityY());
addEntity.write(Types.SHORT, spawn.velocityZ());
addEntity.send(Protocol1_9To1_8.class); // Before the entity data is sent
// 1.8 clients never apply a potion spawn's velocity - vanilla servers send it in a separate
// velocity packet after the spawn, so do the same
final PacketWrapper setEntityMotion = PacketWrapper.create(ClientboundPackets1_8.SET_ENTITY_MOTION, event.user());
setEntityMotion.write(Types.VAR_INT, event.entityId());
setEntityMotion.write(Types.SHORT, spawn.velocityX());
setEntityMotion.write(Types.SHORT, spawn.velocityY());
setEntityMotion.write(Types.SHORT, spawn.velocityZ());
setEntityMotion.send(Protocol1_9To1_8.class);
}
private void handleEntityData(EntityDataHandlerEvent event, EntityData entityData) {
final EntityTracker1_9 tracker = tracker(event.user());
if (event.entityType() == EntityTypes1_9.EntityType.POTION && entityData.value() instanceof Item potionItem) {
// No 1.8 equivalent; supplies the object data of the held-back spawn. Matched by value type as the index is version dependent
this.sendDelayedPotionSpawn(event, tracker, potionItem);
event.cancel();
return;
}
if (entityData.id() == EntityDataIndex1_9.ENTITY_STATUS.getIndex()) {
tracker.getStatus().put(event.entityId(), (Byte) entityData.value());
if (tracker.isHandActive(event.entityId())) {
entityData.setValue((byte) ((byte) entityData.value() | 1 << STATUS_USE_BIT));
}
}
final EntityDataIndex1_9 metaIndex = EntityDataIndex1_8.searchIndex(event.entityType(), entityData.id());
if (metaIndex == null) {
@@ -554,8 +638,10 @@ public class EntityPacketRewriter1_9 extends VREntityRewriter<ClientboundPackets
}
if (metaIndex.getOldType() == null || metaIndex.getNewType() == null) {
if (metaIndex == EntityDataIndex1_9.PLAYER_HAND) { // Player eating/aiming/drinking
final boolean handActive = (((byte) entityData.value()) & 1 << HAND_ACTIVE_BIT) != 0;
tracker.setHandActive(event.entityId(), handActive);
byte status = (byte) tracker.getStatus().getOrDefault(event.entityId(), 0);
if ((((byte) entityData.value()) & 1 << HAND_ACTIVE_BIT) != 0) {
if (handActive) {
status = (byte) (status | 1 << STATUS_USE_BIT);
} else {
status = (byte) (status & ~(1 << STATUS_USE_BIT));
@@ -21,13 +21,14 @@ import com.viaversion.nbt.tag.CompoundTag;
import com.viaversion.nbt.tag.ListTag;
import com.viaversion.nbt.tag.StringTag;
import com.viaversion.viarewind.ViaRewind;
import com.viaversion.viarewind.api.type.RewindTypes;
import com.viaversion.viarewind.protocol.v1_9to1_8.Protocol1_9To1_8;
import com.viaversion.viarewind.protocol.v1_9to1_8.data.CommandBlockState;
import com.viaversion.viarewind.protocol.v1_9to1_8.provider.InventoryProvider;
import com.viaversion.viarewind.protocol.v1_9to1_8.storage.BlockPlaceDestroyTracker;
import com.viaversion.viarewind.protocol.v1_9to1_8.storage.BossBarStorage;
import com.viaversion.viarewind.protocol.v1_9to1_8.storage.CooldownStorage;
import com.viaversion.viarewind.protocol.v1_9to1_8.storage.EntityTracker1_9;
import com.viaversion.viarewind.protocol.v1_9to1_8.storage.LastTitle;
import com.viaversion.viarewind.protocol.v1_9to1_8.storage.PlayerPositionTracker;
import com.viaversion.viarewind.utils.ChatUtil;
import com.viaversion.viaversion.api.Via;
@@ -43,7 +44,6 @@ import com.viaversion.viaversion.api.protocol.remapper.PacketHandlers;
import com.viaversion.viaversion.api.rewriter.RewriterBase;
import com.viaversion.viaversion.api.type.Types;
import com.viaversion.viaversion.libs.gson.JsonElement;
import com.viaversion.viaversion.libs.gson.JsonParser;
import com.viaversion.viaversion.protocols.v1_8to1_9.packet.ClientboundPackets1_8;
import com.viaversion.viaversion.protocols.v1_8to1_9.packet.ClientboundPackets1_9;
import com.viaversion.viaversion.protocols.v1_8to1_9.packet.ServerboundPackets1_8;
@@ -53,6 +53,12 @@ import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import static com.viaversion.viarewind.protocol.v1_9to1_8.cooldown.TitleCooldownVisualization.ACTION_HIDE;
import static com.viaversion.viarewind.protocol.v1_9to1_8.cooldown.TitleCooldownVisualization.ACTION_RESET;
import static com.viaversion.viarewind.protocol.v1_9to1_8.cooldown.TitleCooldownVisualization.ACTION_SET_SUBTITLE;
import static com.viaversion.viarewind.protocol.v1_9to1_8.cooldown.TitleCooldownVisualization.ACTION_SET_TIMES_AND_DISPLAY;
import static com.viaversion.viarewind.protocol.v1_9to1_8.cooldown.TitleCooldownVisualization.ACTION_SET_TITLE;
public class PlayerPacketRewriter1_9 extends RewriterBase<Protocol1_9To1_8> {
public PlayerPacketRewriter1_9(Protocol1_9To1_8 protocol) {
@@ -107,6 +113,26 @@ public class PlayerPacketRewriter1_9 extends RewriterBase<Protocol1_9To1_8> {
protocol.cancelClientbound(ClientboundPackets1_9.COOLDOWN);
protocol.registerClientbound(ClientboundPackets1_9.SET_TITLES, wrapper -> {
final int action = wrapper.passthrough(Types.VAR_INT);
if (action == ACTION_SET_TITLE) {
final JsonElement title = wrapper.passthrough(Types.COMPONENT);
wrapper.user().get(LastTitle.class).setTitle(title);
} else if (action == ACTION_SET_SUBTITLE) {
final JsonElement subtitle = wrapper.passthrough(Types.COMPONENT);
wrapper.user().get(LastTitle.class).setSubtitle(subtitle);
} else if (action == ACTION_SET_TIMES_AND_DISPLAY) {
final int fadeIn = wrapper.passthrough(Types.INT);
final int stay = wrapper.passthrough(Types.INT);
final int fadeOut = wrapper.passthrough(Types.INT);
wrapper.user().get(LastTitle.class).setTimes(fadeIn, stay, fadeOut);
} else if (action == ACTION_HIDE) {
wrapper.user().get(LastTitle.class).hide();
} else if (action == ACTION_RESET) {
wrapper.user().get(LastTitle.class).reset();
}
});
protocol.registerClientbound(ClientboundPackets1_9.CUSTOM_PAYLOAD, new PacketHandlers() {
@Override
public void register() {
@@ -133,8 +159,7 @@ public class PlayerPacketRewriter1_9 extends RewriterBase<Protocol1_9To1_8> {
map(Types.BYTE); // Relative arguments
handler(wrapper -> {
final PlayerPositionTracker pos = wrapper.user().get(PlayerPositionTracker.class);
pos.setConfirmId(wrapper.read(Types.VAR_INT));
final int teleportId = wrapper.read(Types.VAR_INT);
byte flags = wrapper.get(Types.BYTE, 0);
double x = wrapper.get(Types.DOUBLE, 0);
@@ -143,7 +168,11 @@ public class PlayerPacketRewriter1_9 extends RewriterBase<Protocol1_9To1_8> {
float yaw = wrapper.get(Types.FLOAT, 0);
float pitch = wrapper.get(Types.FLOAT, 1);
wrapper.set(Types.BYTE, 0, (byte) 0);
// 1.8 supports the rotation-relative flags (0x08/0x10) natively, and resolving them here
// would snap the camera to the tracked rotation, which lags the client's live look. Keep
// them on the wire and only resolve the position bits (native 1.8 servers use zero-delta
// rotation-relative teleports to leave the camera untouched).
wrapper.set(Types.BYTE, 0, (byte) (flags & 0x18));
if (flags != 0) {
if ((flags & 0x01) != 0) {
@@ -159,18 +188,17 @@ public class PlayerPacketRewriter1_9 extends RewriterBase<Protocol1_9To1_8> {
wrapper.set(Types.DOUBLE, 2, z);
}
if ((flags & 0x08) != 0) {
yaw += pos.getYaw();
wrapper.set(Types.FLOAT, 0, yaw);
yaw += pos.getYaw(); // tracked estimate only; the wire keeps the delta
}
if ((flags & 0x10) != 0) {
pitch += pos.getPitch();
wrapper.set(Types.FLOAT, 1, pitch);
}
}
pos.setPos(x, y, z);
pos.setYaw(yaw);
pos.setPitch(pitch);
pos.queueTeleport(teleportId, x, y, z, pos.getYaw(), pos.getPitch(), (flags & 0x18) != 0);
});
}
});
@@ -180,11 +208,11 @@ public class PlayerPacketRewriter1_9 extends RewriterBase<Protocol1_9To1_8> {
public void register() {
map(Types.INT); // Dimension
handler(wrapper -> {
final ClientWorld world = wrapper.user().getClientWorld(Protocol1_9To1_8.class);
final ClientWorld world = wrapper.user().storables(protocol).clientWorld();
final int dimension = wrapper.get(Types.INT, 0);
if (world.setEnvironment(dimension)) {
final EntityTracker tracker = wrapper.user().getEntityTracker(Protocol1_9To1_8.class);
final EntityTracker tracker = wrapper.user().getEntityTracker(protocol);
tracker.clearEntities();
wrapper.user().get(BossBarStorage.class).reset();
}
@@ -203,7 +231,7 @@ public class PlayerPacketRewriter1_9 extends RewriterBase<Protocol1_9To1_8> {
return;
}
final EntityTracker1_9 tracker = wrapper.user().getEntityTracker(Protocol1_9To1_8.class);
final EntityTracker1_9 tracker = wrapper.user().getEntityTracker(protocol);
final int entityId = wrapper.passthrough(Types.VAR_INT);
wrapper.passthrough(Types.INT); // Killer id
@@ -273,7 +301,7 @@ public class PlayerPacketRewriter1_9 extends RewriterBase<Protocol1_9To1_8> {
handler(wrapper -> {
wrapper.user().get(PlayerPositionTracker.class).sendAnimations();
final EntityTracker1_9 tracker = wrapper.user().getEntityTracker(Protocol1_9To1_8.class);
final EntityTracker1_9 tracker = wrapper.user().getEntityTracker(protocol);
if (tracker.isInsideVehicle(tracker.clientEntityId())) {
wrapper.cancel();
}
@@ -291,7 +319,7 @@ public class PlayerPacketRewriter1_9 extends RewriterBase<Protocol1_9To1_8> {
handler(wrapper -> {
final PlayerPositionTracker storage = wrapper.user().get(PlayerPositionTracker.class);
storage.sendAnimations();
if (storage.getConfirmId() != -1) {
if (storage.hasPendingTeleports()) {
return;
}
@@ -311,7 +339,7 @@ public class PlayerPacketRewriter1_9 extends RewriterBase<Protocol1_9To1_8> {
handler(wrapper -> {
final PlayerPositionTracker storage = wrapper.user().get(PlayerPositionTracker.class);
storage.sendAnimations();
if (storage.getConfirmId() != -1) {
if (storage.hasPendingTeleports()) {
return;
}
@@ -333,13 +361,26 @@ public class PlayerPacketRewriter1_9 extends RewriterBase<Protocol1_9To1_8> {
final PlayerPositionTracker storage = wrapper.user().get(PlayerPositionTracker.class);
storage.sendAnimations();
if (storage.getConfirmId() != -1) {
if (storage.getPosX() == x && storage.getPosY() == y && storage.getPosZ() == z && storage.getYaw() == yaw && storage.getPitch() == pitch) {
final PlayerPositionTracker.PendingTeleport teleport = storage.peekTeleport();
if (teleport != null) {
// 1.7 uses resynced hitbox minY for y however this can have small floating point error due to the calculations it does to get there on teleport confirm
// This Y error gets propagated from 1.7 to 1.8 to 1.9 which causes teleport confirmation to not properly be detected
// This fixes it similarly to how anticheats detect teleports: https://github.com/GrimAnticheat/Grim/blob/67aa3a9483a9b2a6987d594092697b4104c781f0/common/src/main/java/ac/grim/grimac/manager/SetbackTeleportUtil.java#L315
boolean closeEnoughY = Math.abs(teleport.y() - y) <= 1e-7;
// A view-relative teleport keeps its rotation deltas on the wire, so the client applies them
// to its own live look and echoes that - match on position only and adopt the echoed rotation
final boolean rotationMatches = teleport.viewRelative() || (teleport.yaw() == yaw && teleport.pitch() == pitch);
if (teleport.x() == x && closeEnoughY && teleport.z() == z && rotationMatches) {
final PacketWrapper confirmTeleport = PacketWrapper.create(ServerboundPackets1_9.ACCEPT_TELEPORTATION, wrapper.user());
confirmTeleport.write(Types.VAR_INT, storage.getConfirmId());
confirmTeleport.write(Types.VAR_INT, teleport.id());
confirmTeleport.sendToServer(Protocol1_9To1_8.class);
storage.setConfirmId(-1);
if (teleport.viewRelative()) {
storage.setYaw(yaw);
storage.setPitch(pitch);
}
storage.confirmTeleport();
}
} else {
storage.setPos(x, y, z);
@@ -430,7 +471,7 @@ public class PlayerPacketRewriter1_9 extends RewriterBase<Protocol1_9To1_8> {
final float sideways = wrapper.passthrough(Types.FLOAT);
final float forward = wrapper.passthrough(Types.FLOAT);
final EntityTracker1_9 tracker = wrapper.user().getEntityTracker(Protocol1_9To1_8.class);
final EntityTracker1_9 tracker = wrapper.user().getEntityTracker(protocol);
final Integer vehicle = tracker.getVehicle(tracker.clientEntityId());
if (vehicle != null && tracker.entityType(vehicle) == EntityTypes1_9.EntityType.BOAT) {
@@ -480,7 +521,7 @@ public class PlayerPacketRewriter1_9 extends RewriterBase<Protocol1_9To1_8> {
final short flags = wrapper.get(Types.UNSIGNED_BYTE, 0);
final PacketWrapper updateSkin = PacketWrapper.create(ClientboundPackets1_8.SET_ENTITY_DATA, wrapper.user());
updateSkin.write(Types.VAR_INT, wrapper.user().getEntityTracker(Protocol1_9To1_8.class).clientEntityId());
updateSkin.write(Types.VAR_INT, wrapper.user().getEntityTracker(protocol).clientEntityId());
final List<EntityData> entityData = new ArrayList<>();
entityData.add(new EntityData(10, EntityDataTypes1_8.BYTE, (byte) flags));
@@ -524,7 +565,39 @@ public class PlayerPacketRewriter1_9 extends RewriterBase<Protocol1_9To1_8> {
pageTag.setValue(ChatUtil.jsonToLegacy(value));
}
} else if (channel.equals("MC|AdvCdm")) {
wrapper.set(Types.STRING, 0, "MC|AdvCmd");
final byte type = wrapper.read(Types.BYTE);
if (type == 0) {
final int x = wrapper.read(Types.INT);
final int y = wrapper.read(Types.INT);
final int z = wrapper.read(Types.INT);
final CommandBlockState.ParsedCommand command = CommandBlockState.parseDecoratedCommand(wrapper.read(Types.STRING));
final boolean trackOutput = wrapper.read(Types.BOOLEAN);
if (command.prefixed()) {
wrapper.set(Types.STRING, 0, "MC|AutoCmd");
wrapper.write(Types.INT, x);
wrapper.write(Types.INT, y);
wrapper.write(Types.INT, z);
wrapper.write(Types.STRING, command.command());
wrapper.write(Types.BOOLEAN, trackOutput);
wrapper.write(Types.STRING, command.mode());
wrapper.write(Types.BOOLEAN, command.conditional());
wrapper.write(Types.BOOLEAN, command.automatic());
} else {
wrapper.set(Types.STRING, 0, "MC|AdvCmd");
wrapper.write(Types.BYTE, type);
wrapper.write(Types.INT, x);
wrapper.write(Types.INT, y);
wrapper.write(Types.INT, z);
wrapper.write(Types.STRING, command.command());
wrapper.write(Types.BOOLEAN, trackOutput);
}
} else {
wrapper.set(Types.STRING, 0, "MC|AdvCmd");
wrapper.write(Types.BYTE, type);
wrapper.passthrough(Types.INT); // Entity id
wrapper.passthrough(Types.STRING); // Command
wrapper.passthrough(Types.BOOLEAN); // Track output
}
}
});
}
@@ -22,7 +22,9 @@ import com.viaversion.nbt.tag.StringTag;
import com.viaversion.nbt.tag.Tag;
import com.viaversion.viarewind.ViaRewind;
import com.viaversion.viarewind.protocol.v1_9to1_8.Protocol1_9To1_8;
import com.viaversion.viarewind.protocol.v1_9to1_8.data.CommandBlockState;
import com.viaversion.viarewind.protocol.v1_9to1_8.data.EffectIdMappings1_8;
import com.viaversion.viarewind.protocol.v1_9to1_8.storage.CommandBlockStateStorage;
import com.viaversion.viaversion.api.minecraft.BlockPosition;
import com.viaversion.viaversion.api.minecraft.Environment;
import com.viaversion.viaversion.api.minecraft.chunks.BaseChunk;
@@ -57,6 +59,12 @@ public class WorldPacketRewriter1_9 extends RewriterBase<Protocol1_9To1_8> {
map(Types.NAMED_COMPOUND_TAG); // Tag
handler(wrapper -> {
final CompoundTag tag = wrapper.get(Types.NAMED_COMPOUND_TAG, 0);
final short action = wrapper.get(Types.UNSIGNED_BYTE, 0);
if (action == 2) {
final BlockPosition position = wrapper.get(Types.BLOCK_POSITION1_8, 0);
CommandBlockState.decorateCommand(tag, wrapper.user().get(CommandBlockStateStorage.class).state(position));
}
if (tag.remove("SpawnData") instanceof CompoundTag spawnData) {
final Tag id = spawnData.remove("id");
@@ -92,9 +100,12 @@ public class WorldPacketRewriter1_9 extends RewriterBase<Protocol1_9To1_8> {
String name = wrapper.get(Types.STRING, 0);
name = protocol.getMappingData().getMappedNamedSound(name);
if (name == null) {
wrapper.cancel();
} else {
return;
}
if (!name.isEmpty()) {
wrapper.set(Types.STRING, 0, name);
} else {
wrapper.cancel();
}
});
read(Types.VAR_INT); // Sound category
@@ -110,10 +121,12 @@ public class WorldPacketRewriter1_9 extends RewriterBase<Protocol1_9To1_8> {
@Override
public void register() {
handler(wrapper -> {
final Environment environment = wrapper.user().getClientWorld(Protocol1_9To1_8.class).getEnvironment();
final Environment environment = wrapper.user().storables(protocol).clientWorld().getEnvironment();
final CommandBlockStateStorage commandBlockStates = wrapper.user().get(CommandBlockStateStorage.class);
final int chunkX = wrapper.read(Types.INT);
final int chunkZ = wrapper.read(Types.INT);
commandBlockStates.unloadChunk(chunkX, chunkZ);
wrapper.write(ChunkType1_8.forEnvironment(environment), new BaseChunk(chunkX, chunkZ, true, false, 0, new ChunkSection[16], null, new ArrayList<>()));
});
@@ -124,9 +137,24 @@ public class WorldPacketRewriter1_9 extends RewriterBase<Protocol1_9To1_8> {
@Override
public void register() {
handler(wrapper -> {
final Environment environment = wrapper.user().getClientWorld(Protocol1_9To1_8.class).getEnvironment();
final Environment environment = wrapper.user().storables(protocol).clientWorld().getEnvironment();
final CommandBlockStateStorage commandBlockStates = wrapper.user().get(CommandBlockStateStorage.class);
Chunk chunk = wrapper.read(ChunkType1_9_1.forEnvironment(environment));
final Chunk originalChunk = chunk;
chunk.getBlockEntities().forEach(nbt -> {
if (!nbt.contains("x") || !nbt.contains("y") || !nbt.contains("z")) {
return;
}
final BlockPosition position = new BlockPosition((int) nbt.get("x").getValue(), (int) nbt.get("y").getValue(), (int) nbt.get("z").getValue());
final int blockState = blockStateAt(originalChunk, position);
if (CommandBlockState.isCommandBlock(blockState)) {
commandBlockStates.storeOrRemove(position, blockState);
CommandBlockState.decorateCommand(nbt, blockState);
}
});
for (ChunkSection section : chunk.getSections()) {
if (section == null) continue;
@@ -267,4 +295,18 @@ public class WorldPacketRewriter1_9 extends RewriterBase<Protocol1_9To1_8> {
}
});
}
private static int blockStateAt(final Chunk chunk, final BlockPosition position) {
final int sectionY = position.y() >> 4;
if (sectionY < 0 || sectionY >= chunk.getSections().length) {
return -1;
}
final ChunkSection section = chunk.getSections()[sectionY];
if (section == null) {
return -1;
}
return section.palette(PaletteType.BLOCKS).idAt(position.x() & 0xF, position.y() & 0xF, position.z() & 0xF);
}
}
@@ -0,0 +1,52 @@
/*
* This file is part of ViaRewind - https://github.com/ViaVersion/ViaRewind
* Copyright (C) 2018-2026 ViaVersion and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.viaversion.viarewind.protocol.v1_9to1_8.storage;
import com.viaversion.viarewind.protocol.v1_9to1_8.data.CommandBlockState;
import com.viaversion.viaversion.api.connection.StorableObject;
import com.viaversion.viaversion.api.minecraft.BlockPosition;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
public final class CommandBlockStateStorage implements StorableObject {
private final Map<BlockPosition, Integer> commandBlockStates = new HashMap<>();
public void storeOrRemove(final BlockPosition position, final int blockState) {
if (CommandBlockState.isCommandBlock(blockState)) {
commandBlockStates.put(position, blockState);
} else {
commandBlockStates.remove(position);
}
}
public int state(final BlockPosition position) {
return commandBlockStates.getOrDefault(position, -1);
}
public void unloadChunk(final int chunkX, final int chunkZ) {
final Iterator<BlockPosition> iterator = commandBlockStates.keySet().iterator();
while (iterator.hasNext()) {
final BlockPosition position = iterator.next();
if (position.x() >> 4 == chunkX && position.z() >> 4 == chunkZ) {
iterator.remove();
}
}
}
}
@@ -18,6 +18,7 @@
package com.viaversion.viarewind.protocol.v1_9to1_8.storage;
import com.viaversion.viaversion.api.connection.UserConnection;
import com.viaversion.viaversion.api.data.entity.TrackedEntity;
import com.viaversion.viaversion.api.minecraft.Vector;
import com.viaversion.viaversion.api.minecraft.entities.EntityTypes1_9;
import com.viaversion.viaversion.data.entity.EntityTrackerBase;
@@ -27,6 +28,8 @@ import com.viaversion.viaversion.libs.fastutil.ints.Int2ObjectMap;
import com.viaversion.viaversion.libs.fastutil.ints.Int2ObjectOpenHashMap;
import com.viaversion.viaversion.libs.fastutil.ints.IntArrayList;
import com.viaversion.viaversion.libs.fastutil.ints.IntList;
import com.viaversion.viaversion.libs.fastutil.ints.IntOpenHashSet;
import com.viaversion.viaversion.libs.fastutil.ints.IntSet;
import java.util.List;
import java.util.Map;
@@ -35,20 +38,24 @@ public class EntityTracker1_9 extends EntityTrackerBase {
private final Int2ObjectMap<IntList> vehicles = new Int2ObjectOpenHashMap<>();
private final Int2ObjectMap<Vector> offsets = new Int2ObjectOpenHashMap<>();
private final Int2IntMap status = new Int2IntOpenHashMap();
private final IntSet handActive = new IntOpenHashSet();
private final Int2ObjectMap<PendingPotionEntity> pendingPotions = new Int2ObjectOpenHashMap<>();
public EntityTracker1_9(UserConnection connection) {
super(connection, EntityTypes1_9.EntityType.PLAYER);
}
@Override
public void removeEntity(int id) {
public TrackedEntity removeEntity(int id) {
vehicles.remove(id);
offsets.remove(id);
status.remove(id);
handActive.remove(id);
pendingPotions.remove(id);
vehicles.forEach((vehicle, passengers) -> passengers.rem(id));
vehicles.int2ObjectEntrySet().removeIf(entry -> entry.getValue().isEmpty());
super.removeEntity(id);
return super.removeEntity(id);
}
public void resetEntityOffset(final int id) {
@@ -88,4 +95,33 @@ public class EntityTracker1_9 extends EntityTrackerBase {
public Int2IntMap getStatus() {
return status;
}
public boolean isHandActive(final int id) {
return handActive.contains(id);
}
public void setHandActive(final int id, final boolean active) {
if (active) {
handActive.add(id);
} else {
handActive.remove(id);
}
}
public Int2ObjectMap<PendingPotionEntity> getPendingPotions() {
return pendingPotions;
}
public record PendingPotionEntity(int x, int y, int z, byte pitch, byte yaw, short velocityX, short velocityY, short velocityZ) {
public PendingPotionEntity withPosition(final int x, final int y, final int z, final byte pitch, final byte yaw) {
return new PendingPotionEntity(x, y, z, pitch, yaw, velocityX, velocityY, velocityZ);
}
public PendingPotionEntity withVelocity(final short velocityX, final short velocityY, final short velocityZ) {
return new PendingPotionEntity(x, y, z, pitch, yaw, velocityX, velocityY, velocityZ);
}
}
}
@@ -0,0 +1,107 @@
/*
* This file is part of ViaRewind - https://github.com/ViaVersion/ViaRewind
* Copyright (C) 2018-2026 ViaVersion and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.viaversion.viarewind.protocol.v1_9to1_8.storage;
import com.viaversion.viaversion.api.connection.StorableObject;
import com.viaversion.viaversion.libs.gson.JsonElement;
public final class LastTitle implements StorableObject {
private JsonElement title;
private JsonElement subtitle;
private long visibleSince;
private long visibleUntil;
private int fadeIn;
private int stay;
private int fadeOut;
public LastTitle() {
this.reset();
}
public void setTitle(final JsonElement title) {
this.title = title;
final long now = System.currentTimeMillis();
this.visibleSince = now;
this.visibleUntil = now + titleLength();
}
public void setSubtitle(final JsonElement subtitle) {
this.subtitle = subtitle;
}
public void setTimes(final int fadeIn, final int stay, final int fadeOut) {
this.fadeIn = fadeIn;
this.stay = stay;
this.fadeOut = fadeOut;
if (this.visibleUntil > System.currentTimeMillis()) {
final long now = System.currentTimeMillis();
this.visibleSince = now;
this.visibleUntil = now + titleLength();
}
}
public void hide() {
this.visibleSince = 0;
this.visibleUntil = 0;
}
public void reset() {
this.hide();
this.fadeIn = 10;
this.stay = 70;
this.fadeOut = 20;
}
public boolean isVisible() {
return this.title != null && this.visibleUntil > System.currentTimeMillis();
}
public JsonElement title() {
return this.title;
}
public JsonElement subtitle() {
return this.subtitle;
}
public int[] remainingTimes() {
final int fullLength = Math.max(0, this.fadeIn + this.stay + this.fadeOut);
final int elapsed = (int) Math.min(fullLength, Math.max(0, (System.currentTimeMillis() - this.visibleSince) / 50L));
if (elapsed < this.fadeIn) {
return new int[]{this.fadeIn - elapsed, this.stay, this.fadeOut};
}
final int titleEnd = this.fadeIn + this.stay;
if (elapsed < titleEnd) {
return new int[]{0, titleEnd - elapsed, this.fadeOut};
}
return new int[]{0, 0, fullLength - elapsed};
}
private long titleLength() {
return Math.max(0, this.fadeIn + this.stay + this.fadeOut) * 50L;
}
}
@@ -26,10 +26,10 @@ import java.util.concurrent.ConcurrentLinkedQueue;
public class PlayerPositionTracker implements StorableObject {
private final Queue<PacketWrapper> animations = new ConcurrentLinkedQueue<>();
private final Queue<PendingTeleport> pendingTeleports = new ConcurrentLinkedQueue<>();
private double posX, posY, posZ;
private float yaw, pitch;
private boolean onGround;
private int confirmId = -1;
public void setPos(double x, double y, double z) {
this.posX = x;
@@ -84,12 +84,23 @@ public class PlayerPositionTracker implements StorableObject {
this.onGround = onGround;
}
public int getConfirmId() {
return this.confirmId;
public boolean hasPendingTeleports() {
return !this.pendingTeleports.isEmpty();
}
public void setConfirmId(int confirmId) {
this.confirmId = confirmId;
public void queueTeleport(final int id, final double x, final double y, final double z, final float yaw, final float pitch, final boolean viewRelative) {
this.pendingTeleports.add(new PendingTeleport(id, x, y, z, yaw, pitch, viewRelative));
}
public PendingTeleport peekTeleport() {
return this.pendingTeleports.peek();
}
public void confirmTeleport() {
this.pendingTeleports.poll();
}
public record PendingTeleport(int id, double x, double y, double z, float yaw, float pitch, boolean viewRelative) {
}
}
@@ -38,6 +38,9 @@ public class WindowTracker extends StoredObject {
private final HashMap<Short, Item[]> brewingItems = new HashMap<>();
private final Map<Short, Short> enchantmentProperties = new HashMap<>();
private short openWindowId = 0;
private int openWindowSize = 0; // Translated client container slots, excluding the player inventory
public WindowTracker(UserConnection user) {
super(user);
}
@@ -52,11 +55,11 @@ public class WindowTracker extends StoredObject {
openWindow.write(Types.UNSIGNED_BYTE, windowId);
openWindow.write(Types.STRING, "minecraft:brewing_stand");
TextComponent title = new StringComponent().
append(new TranslationComponent("container.brewing")).
append(new StringComponent(": " + TextFormatting.DARK_GRAY)).
append(new StringComponent(amount + " " + TextFormatting.DARK_RED)).
append(new TranslationComponent("item.blazePowder.name", TextFormatting.DARK_RED));
TextComponent title = new StringComponent()
.append(new TranslationComponent("container.brewing"))
.append(new StringComponent(": ").formatted(TextFormatting.DARK_GRAY))
.append(new StringComponent(amount + " ").formatted(TextFormatting.DARK_RED))
.append(new TranslationComponent("item.blazePowder.name").formatted(TextFormatting.DARK_RED));
openWindow.write(Types.COMPONENT, TextComponentSerializer.V1_8.serializeJson(title));
openWindow.write(Types.UNSIGNED_BYTE, (short) 420);
@@ -83,6 +86,36 @@ public class WindowTracker extends StoredObject {
public void remove(short windowId) {
types.remove(windowId);
brewingItems.remove(windowId);
if (windowId == openWindowId) {
openWindowId = 0;
openWindowSize = 0;
}
}
public void openWindow(final short windowId, final String windowType, final int slotCount) {
openWindowId = windowId;
// The client only uses the packet's slot count when constructing dynamic inventories.
// Fixed menus construct their own slots; use the translated 1.8 layout for those.
openWindowSize = switch (windowType) {
case "minecraft:furnace", "minecraft:villager", "minecraft:anvil" -> 3;
case "minecraft:brewing_stand" -> 4;
case "minecraft:beacon" -> 1;
case "minecraft:dispenser", "minecraft:dropper" -> 9;
case "minecraft:enchanting_table" -> 2;
case "minecraft:crafting_table" -> 10;
case "minecraft:hopper" -> slotCount;
case "EntityHorse" -> slotCount > 2 ? 17 : 2;
default -> slotCount / 9 * 9;
};
}
public short openWindowId() {
return openWindowId;
}
public int openWindowSize() {
return openWindowSize;
}
public Item[] getBrewingItems(short windowId) {
@@ -26,7 +26,11 @@ public class CooldownIndicatorTask implements Runnable {
@Override
public void run() {
for (UserConnection connection : Via.getManager().getConnectionManager().getConnections()) {
connection.get(CooldownStorage.class).tick(connection);
final CooldownStorage cooldown = connection.get(CooldownStorage.class);
if (cooldown == null) {
continue;
}
cooldown.tick(connection);
}
}
}
@@ -36,12 +36,15 @@ public class LevitationUpdateTask implements Runnable {
}
for (UserConnection connection : Via.getManager().getConnectionManager().getConnections()) {
final LevitationStorage levitation = connection.get(LevitationStorage.class);
if (levitation == null) {
continue;
}
if (!levitation.isActive()) {
continue;
}
final PacketWrapper velocityPacket = PacketWrapper.create(ClientboundPackets1_8.SET_ENTITY_MOTION, connection);
velocityPacket.write(Types.VAR_INT, connection.getEntityTracker(Protocol1_9To1_8.class).clientEntityId());
velocityPacket.write(Types.VAR_INT, connection.storables(Protocol1_9To1_8.class).entityTracker().clientEntityId());
velocityPacket.write(Types.SHORT, (short) 0);
velocityPacket.write(Types.SHORT, (short) ((levitation.getAmplifier() + 1) * 360));
velocityPacket.write(Types.SHORT, (short) 0);
+1 -1
View File
@@ -24,7 +24,7 @@
},
"depends": {
"viafabric": ">=0.4.14",
"viabackwards": ">=5.7.2"
"viabackwards": ">=5.12.0"
},
"custom": {
"modmenu:api": true,
+8 -7
View File
@@ -4,17 +4,18 @@ org.gradle.parallel=true
org.gradle.configuration-cache=true
# Project Details
project_jvm_version=17
jvm_version=17
project_group=com.viaversion
project_version=4.1.0
project_name=ViaRewind
project_version=4.2.0
project_description=ViaVersion addon to allow 1.8.x and 1.7.x clients on newer server versions.
publishing_dev_id=florianreuth
publishing_dev_name=Florian Reuth
publishing_dev_mail=git@florianreuth.de
publish_owner_id=florianreuth
publish_owner_name=Florian Reuth
publish_owner_mail=git@florianreuth.de
# Modrinth/Hangar Publishing
minecraft_versions=26.1.2, 26.1.1, 26.1, 1.21.11, 1.21.10, 1.21.9, 1.21.8, 1.21.7, 1.21.6, 1.21.5, 1.21.4, 1.21.3, 1.21.2, 1.21.1, 1.21, 1.20.6, 1.20.5, 1.20.4, 1.20.3, 1.20.2, 1.20.1, 1.20, 1.19.4, 1.19.3, 1.19.2, 1.19.1, 1.19, 1.18.2, 1.18.1, 1.18, 1.17.1, 1.17, 1.16.5, 1.16.4, 1.16.3, 1.16.2, 1.16.1, 1.16, 1.15.2, 1.15.1, 1.15, 1.14.4, 1.14.3, 1.14.2, 1.14.1, 1.14, 1.13.2, 1.13.1, 1.13, 1.12.2, 1.12.1, 1.12, 1.11.2, 1.11.1, 1.11, 1.10.2, 1.10.1, 1.10, 1.9.4, 1.9.3, 1.9.2, 1.9.1, 1.9, 1.8.9, 1.8.8
minecraft_version_range=1.8-26.1.2
minecraft_versions=26.3, 26.2, 26.1.2, 26.1.1, 26.1, 1.21.11, 1.21.10, 1.21.9, 1.21.8, 1.21.7, 1.21.6, 1.21.5, 1.21.4, 1.21.3, 1.21.2, 1.21.1, 1.21, 1.20.6, 1.20.5, 1.20.4, 1.20.3, 1.20.2, 1.20.1, 1.20, 1.19.4, 1.19.3, 1.19.2, 1.19.1, 1.19, 1.18.2, 1.18.1, 1.18, 1.17.1, 1.17, 1.16.5, 1.16.4, 1.16.3, 1.16.2, 1.16.1, 1.16, 1.15.2, 1.15.1, 1.15, 1.14.4, 1.14.3, 1.14.2, 1.14.1, 1.14, 1.13.2, 1.13.1, 1.13, 1.12.2, 1.12.1, 1.12, 1.11.2, 1.11.1, 1.11, 1.10.2, 1.10.1, 1.10, 1.9.4, 1.9.3, 1.9.2, 1.9.1, 1.9, 1.8.9, 1.8.8
minecraft_version_range=1.8-26.3
velocity_version=3.4-3.5
Binary file not shown.
+4 -2
View File
@@ -1,8 +1,10 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionSha256Sum=2ab2958f2a1e51120c326cad6f385153bb11ee93b3c216c5fccebfdfbb7ec6cb
distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip
distributionSha256Sum=acd53f1edaf02f1a8ff99879f8a34b302661a057d9b063ae9e35b552f804d20a
distributionUrl=https\://services.gradle.org/distributions/gradle-9.7.1-bin.zip
networkTimeout=10000
retries=0
retryBackOffMs=500
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Vendored
+3 -3
View File
@@ -20,7 +20,7 @@
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
# gradlew start up script for POSIX generated by Gradle.
#
# Important for running:
#
@@ -29,7 +29,7 @@
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
# ksh gradlew
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
@@ -57,7 +57,7 @@
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/2d6327017519d23b96af35865dc997fcb544fb40/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# https://github.com/gradle/gradle/blob/3d91ce3b8caaf77ad09f381f43615b715b53f72c/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
Vendored
+12 -23
View File
@@ -19,12 +19,12 @@
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem gradlew startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
@rem Set local scope for the variables, and ensure extensions are enabled
setlocal EnableExtensions
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@@ -51,7 +51,7 @@ echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
"%COMSPEC%" /c exit 1
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
@@ -65,29 +65,18 @@ echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
"%COMSPEC%" /c exit 1
:execute
@rem Setup the command line
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
@rem Execute gradlew
@rem endlocal doesn't take effect until after the line is parsed and variables are expanded
@rem which allows us to clear the local environment before executing the java command
endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel
:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
:exitWithErrorLevel
@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts
"%COMSPEC%" /c exit %ERRORLEVEL%
+1 -1
View File
@@ -6,7 +6,7 @@ pluginManagement {
}
plugins {
id("de.florianreuth.baseproject") version "2.0.2"
id("de.florianreuth.baseproject") version "3.0.2"
id("io.papermc.hangar-publish-plugin") version "0.1.4"
id("net.raphimc.class-token-replacer") version "1.1.7"
id("com.modrinth.minotaur") version "2.+"
@@ -36,11 +36,11 @@
},
{
"id": "viaversion",
"version": "[5.7.2,)"
"version": "[5.12.0,)"
},
{
"id": "viabackwards",
"version": "[5.7.2,)"
"version": "[5.12.0,)"
}
]
},