Compare commits
94
Commits
0.1.1-beta
...
0.1.5-beta
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7b312f54ae | ||
|
|
a1dcd25710 | ||
|
|
068c15d781 | ||
|
|
46c7bacd04 | ||
|
|
effdbe1c84 | ||
|
|
4b776663bb | ||
|
|
86f326a8b4 | ||
|
|
6b1b79062c | ||
|
|
a4ed30111c | ||
|
|
bd58bfd0a0 | ||
|
|
1fdbf82abd | ||
|
|
4fb48cc647 | ||
|
|
b018db1851 | ||
|
|
950a83c48d | ||
|
|
a7b2e3be8e | ||
|
|
55067e9761 | ||
|
|
09bc223b8b | ||
|
|
af27907356 | ||
|
|
37db94649f | ||
|
|
896f4cccc0 | ||
|
|
de3c554e63 | ||
|
|
8417427059 | ||
|
|
ba5a32fb34 | ||
|
|
8b1c94151a | ||
|
|
3623024cce | ||
|
|
2dcbe0302e | ||
|
|
502bddd720 | ||
|
|
0c22fee8c0 | ||
|
|
f69f6eb883 | ||
|
|
cebd9aec96 | ||
|
|
2554a84950 | ||
|
|
49bbdff418 | ||
|
|
8995de9067 | ||
|
|
18b74212ca | ||
|
|
06190d0ef9 | ||
|
|
254a37d757 | ||
|
|
2b5bcf4098 | ||
|
|
4e00934353 | ||
|
|
c524451704 | ||
|
|
e26ce6d076 | ||
|
|
da3dfaee66 | ||
|
|
463335856f | ||
|
|
3315926f7c | ||
|
|
68d3fb6062 | ||
|
|
b108cc1f4a | ||
|
|
167010380e | ||
|
|
dd91f3844a | ||
|
|
b7cf3092da | ||
|
|
60afc12893 | ||
|
|
8677837a8f | ||
|
|
02f6cf7d71 | ||
|
|
85a46c0bba | ||
|
|
30f65bd914 | ||
|
|
bedb45c8aa | ||
|
|
1602520624 | ||
|
|
f69c379ae1 | ||
|
|
fbd0e23fb3 | ||
|
|
8dbb1d84c1 | ||
|
|
ab70c67a4f | ||
|
|
0a6aa90e23 | ||
|
|
22a7152f0d | ||
|
|
b5ca9dbc8f | ||
|
|
48118f6a84 | ||
|
|
771ed93992 | ||
|
|
a8e6f14022 | ||
|
|
84270c023a | ||
|
|
25a19d7951 | ||
|
|
c5665548a0 | ||
|
|
22142be7b5 | ||
|
|
a949c18ec1 | ||
|
|
6f122b5237 | ||
|
|
85e5ef3f59 | ||
|
|
0bf4fc6808 | ||
|
|
a44b4f4e90 | ||
|
|
f2d30e6611 | ||
|
|
242653054d | ||
|
|
95e4dff448 | ||
|
|
a30cf4d3ab | ||
|
|
eebfe8fa4d | ||
|
|
019e5cfb43 | ||
|
|
6ed2ac1b60 | ||
|
|
1853b09443 | ||
|
|
9126faa11c | ||
|
|
090e5132b2 | ||
|
|
bbe6d02568 | ||
|
|
04e7064790 | ||
|
|
ff0bce4d24 | ||
|
|
2ecb4f80ea | ||
|
|
41f9be8595 | ||
|
|
f73f7e733c | ||
|
|
91b53daac9 | ||
|
|
5bcdc72b83 | ||
|
|
3f0670ea9d | ||
|
|
c54b5ae6d7 |
@@ -0,0 +1,164 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
// Publish the jars attached to a Gitea release to Modrinth.
|
||||||
|
//
|
||||||
|
// Creates one Modrinth version per jar (saturn-client-<version>+<mc>.jar), for
|
||||||
|
// that Minecraft version, with Fabric API as a required dependency and the
|
||||||
|
// Gitea release notes as the changelog. Versions that already exist on
|
||||||
|
// Modrinth are skipped, so the script is safe to re-run.
|
||||||
|
//
|
||||||
|
// Runs on plain Node (>= 18: fetch, FormData, Blob) with no dependencies, since
|
||||||
|
// every Gitea Actions runner has Node to run JavaScript actions.
|
||||||
|
//
|
||||||
|
// Environment:
|
||||||
|
// TAG release tag, e.g. 0.1.1-beta
|
||||||
|
// SERVER_URL public Gitea URL, e.g. https://git.selimaj.dev
|
||||||
|
// REPO owner/name, e.g. saturnclientmc/saturnclient
|
||||||
|
// MODRINTH_TOKEN Modrinth PAT with the "Create versions" scope
|
||||||
|
// DRY_RUN "true" to check everything and print what would be
|
||||||
|
// published without uploading (no token needed)
|
||||||
|
|
||||||
|
const MODRINTH_API = "https://api.modrinth.com/v2";
|
||||||
|
const MODRINTH_PROJECT_ID = "i6JDSY9x"; // saturnclient
|
||||||
|
const FABRIC_API_PROJECT_ID = "P7dR8mSH";
|
||||||
|
const USER_AGENT =
|
||||||
|
"saturnclientmc/saturnclient release workflow (https://git.selimaj.dev/saturnclientmc/saturnclient)";
|
||||||
|
|
||||||
|
const JAR_NAME = /^saturn-client-(?<version>.+)\+(?<mc>\d+\.\d+(?:\.\d+)?)\.jar$/;
|
||||||
|
|
||||||
|
function fail(message) {
|
||||||
|
console.error(message);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function http(url, options = {}) {
|
||||||
|
const res = await fetch(url, {
|
||||||
|
...options,
|
||||||
|
headers: { "User-Agent": USER_AGENT, ...options.headers },
|
||||||
|
});
|
||||||
|
if (!res.ok) {
|
||||||
|
const error = new Error(`${options.method ?? "GET"} ${url} returned ${res.status}: ${await res.text()}`);
|
||||||
|
error.status = res.status;
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getJson(url) {
|
||||||
|
return (await http(url)).json();
|
||||||
|
}
|
||||||
|
|
||||||
|
function versionType(tag) {
|
||||||
|
const t = tag.toLowerCase();
|
||||||
|
if (t.includes("alpha")) return "alpha";
|
||||||
|
if (t.includes("beta") || t.includes("rc") || t.includes("pre")) return "beta";
|
||||||
|
return "release";
|
||||||
|
}
|
||||||
|
|
||||||
|
function compareMc(a, b) {
|
||||||
|
const pa = a.split(".").map(Number);
|
||||||
|
const pb = b.split(".").map(Number);
|
||||||
|
for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
|
||||||
|
const diff = (pa[i] ?? 0) - (pb[i] ?? 0);
|
||||||
|
if (diff !== 0) return diff;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
const { TAG: tag, SERVER_URL, REPO: repo } = process.env;
|
||||||
|
const serverUrl = (SERVER_URL ?? "").replace(/\/+$/, "");
|
||||||
|
const dryRun = (process.env.DRY_RUN ?? "").toLowerCase() === "true";
|
||||||
|
const token = process.env.MODRINTH_TOKEN ?? "";
|
||||||
|
|
||||||
|
if (!tag || !serverUrl || !repo) fail("TAG, SERVER_URL and REPO must be set.");
|
||||||
|
if (!token && !dryRun) {
|
||||||
|
fail("MODRINTH_TOKEN is not set. Add it as a repository secret (Settings → Actions → Secrets).");
|
||||||
|
}
|
||||||
|
|
||||||
|
let release;
|
||||||
|
try {
|
||||||
|
release = await getJson(`${serverUrl}/api/v1/repos/${repo}/releases/tags/${encodeURIComponent(tag)}`);
|
||||||
|
} catch (e) {
|
||||||
|
if (e.status === 404) fail(`No Gitea release with tag ${tag}.`);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
const changelog = release.body ?? "";
|
||||||
|
|
||||||
|
const jars = (release.assets ?? [])
|
||||||
|
.map((asset) => ({ name: asset.name, match: JAR_NAME.exec(asset.name) }))
|
||||||
|
.filter(({ match }) => match)
|
||||||
|
.map(({ name, match }) => ({ name, version: match.groups.version, mc: match.groups.mc }))
|
||||||
|
.sort((a, b) => compareMc(a.mc, b.mc));
|
||||||
|
|
||||||
|
if (jars.length === 0) fail(`Release ${tag} has no saturn-client-*.jar assets. Run the Release workflow first.`);
|
||||||
|
|
||||||
|
for (const jar of jars) {
|
||||||
|
if (jar.version !== tag.replace(/^v/, "")) fail(`${jar.name} is version ${jar.version}, but the release tag is ${tag}.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const knownGameVersions = new Set((await getJson(`${MODRINTH_API}/tag/game_version`)).map((g) => g.version));
|
||||||
|
const unknown = [...new Set(jars.map((j) => j.mc))].filter((mc) => !knownGameVersions.has(mc));
|
||||||
|
if (unknown.length > 0) fail(`Modrinth doesn't know these Minecraft versions: ${unknown.join(", ")}`);
|
||||||
|
|
||||||
|
const existing = new Set(
|
||||||
|
(await getJson(`${MODRINTH_API}/project/${MODRINTH_PROJECT_ID}/version`)).map((v) => v.version_number),
|
||||||
|
);
|
||||||
|
const kind = versionType(tag);
|
||||||
|
const failed = [];
|
||||||
|
|
||||||
|
for (const { name, version, mc } of jars) {
|
||||||
|
const versionNumber = `${version}+${mc}`;
|
||||||
|
|
||||||
|
if (existing.has(versionNumber)) {
|
||||||
|
console.log(`skip ${versionNumber}: already on Modrinth`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gitea's own download URLs use its internal ROOT_URL, so build a public one.
|
||||||
|
const downloadUrl = `${serverUrl}/${repo}/releases/download/${encodeURIComponent(tag)}/${encodeURIComponent(name)}`;
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
project_id: MODRINTH_PROJECT_ID,
|
||||||
|
name: `Saturn Client ${versionNumber}`,
|
||||||
|
version_number: versionNumber,
|
||||||
|
changelog,
|
||||||
|
dependencies: [{ project_id: FABRIC_API_PROJECT_ID, dependency_type: "required" }],
|
||||||
|
game_versions: [mc],
|
||||||
|
version_type: kind,
|
||||||
|
loaders: ["fabric"],
|
||||||
|
featured: false,
|
||||||
|
status: "listed",
|
||||||
|
file_parts: ["file"],
|
||||||
|
primary_file: "file",
|
||||||
|
};
|
||||||
|
|
||||||
|
if (dryRun) {
|
||||||
|
const probe = await http(downloadUrl, { headers: { Range: "bytes=0-0" } });
|
||||||
|
await probe.arrayBuffer();
|
||||||
|
console.log(`would publish ${versionNumber} (${kind}, Minecraft ${mc}) from ${downloadUrl} [reachable]`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const jarBytes = await (await http(downloadUrl)).arrayBuffer();
|
||||||
|
|
||||||
|
const form = new FormData();
|
||||||
|
form.append("data", JSON.stringify(data));
|
||||||
|
form.append("file", new Blob([jarBytes], { type: "application/java-archive" }), name);
|
||||||
|
|
||||||
|
await http(`${MODRINTH_API}/version`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: { Authorization: token },
|
||||||
|
body: form,
|
||||||
|
});
|
||||||
|
console.log(`publish ${versionNumber}: done (${(jarBytes.byteLength / 1_000_000).toFixed(1)} MB)`);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(`publish ${versionNumber}: FAILED ${e.message}`);
|
||||||
|
failed.push(versionNumber);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (failed.length > 0) fail(`Failed to publish: ${failed.join(", ")}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch((e) => fail(e.stack ?? String(e)));
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
name: Check
|
||||||
|
|
||||||
|
# Compiles every Minecraft version on pull requests, so a change that breaks
|
||||||
|
# one version (e.g. a missed or bad port) fails before it's merged instead of
|
||||||
|
# in the release build. Merges to master aren't re-checked: the PR already
|
||||||
|
# checked that code.
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
compile:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: true
|
||||||
|
|
||||||
|
# The JDK comes from the runner's tool cache. `cache: gradle` stores Gradle's
|
||||||
|
# dependencies, Loom's Minecraft cache and the build cache in the runner's
|
||||||
|
# actions cache.
|
||||||
|
# 1.21.x builds with Java 21 and 26.x with Java 25. The last version listed is the default
|
||||||
|
# (Gradle runs on it), and the Gradle commands below point toolchain detection at both.
|
||||||
|
- name: Set up Java 25 and 21
|
||||||
|
uses: actions/setup-java@v5
|
||||||
|
with:
|
||||||
|
distribution: temurin
|
||||||
|
java-version: |
|
||||||
|
25
|
||||||
|
21
|
||||||
|
cache: gradle
|
||||||
|
|
||||||
|
- name: Compile all versions
|
||||||
|
run: |
|
||||||
|
chmod +x gradlew
|
||||||
|
# --continue reports every version that fails, not just the first.
|
||||||
|
./gradlew compileJava --no-daemon --stacktrace --continue \
|
||||||
|
-Porg.gradle.java.installations.fromEnv=JAVA_HOME_21_X64,JAVA_HOME_25_X64
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
name: Publish to Modrinth
|
||||||
|
|
||||||
|
# Publishes the jars already attached to a Gitea release (built by the Release
|
||||||
|
# workflow) to Modrinth, one version per Minecraft version. Run it manually
|
||||||
|
# after smoke-testing the release. It doesn't rebuild anything, and versions
|
||||||
|
# already on Modrinth are skipped, so re-running is safe.
|
||||||
|
#
|
||||||
|
# Needs a MODRINTH_TOKEN repository secret: a Modrinth PAT with the
|
||||||
|
# "Create versions" scope.
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
tag:
|
||||||
|
description: Tag of the Gitea release to publish
|
||||||
|
required: true
|
||||||
|
dry_run:
|
||||||
|
description: Only check and print what would be published
|
||||||
|
type: boolean
|
||||||
|
default: false
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
publish:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Publish to Modrinth
|
||||||
|
env:
|
||||||
|
TAG: ${{ inputs.tag }}
|
||||||
|
DRY_RUN: ${{ inputs.dry_run }}
|
||||||
|
SERVER_URL: ${{ github.server_url }}
|
||||||
|
REPO: ${{ github.repository }}
|
||||||
|
MODRINTH_TOKEN: ${{ secrets.MODRINTH_TOKEN }}
|
||||||
|
# Node is always available on Gitea runners (it runs JavaScript actions).
|
||||||
|
run: node .gitea/scripts/publish_modrinth.mjs
|
||||||
@@ -44,18 +44,27 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
echo "Building Saturn Client $MOD_VERSION"
|
echo "Building Saturn Client $MOD_VERSION"
|
||||||
|
|
||||||
- name: Set up Java 21
|
# The JDK comes from the runner's tool cache. `cache: gradle` stores Gradle's
|
||||||
uses: actions/setup-java@v4
|
# dependencies, Loom's Minecraft cache and the build cache in the runner's
|
||||||
|
# actions cache.
|
||||||
|
# 1.21.x builds with Java 21 and 26.x with Java 25. The last version listed is the default
|
||||||
|
# (Gradle runs on it), and the Gradle commands below point toolchain detection at both.
|
||||||
|
- name: Set up Java 25 and 21
|
||||||
|
uses: actions/setup-java@v5
|
||||||
with:
|
with:
|
||||||
distribution: temurin
|
distribution: temurin
|
||||||
java-version: 21
|
java-version: |
|
||||||
|
25
|
||||||
|
21
|
||||||
cache: gradle
|
cache: gradle
|
||||||
|
|
||||||
- name: Build all versions
|
- name: Build all versions
|
||||||
run: |
|
run: |
|
||||||
chmod +x gradlew
|
chmod +x gradlew
|
||||||
# buildAll remaps every versions/* project in parallel into build/allJars.
|
# buildAll remaps every Minecraft version (two at a time, to stay within the
|
||||||
./gradlew buildAll --no-daemon --stacktrace
|
# Gradle heap) into build/allJars.
|
||||||
|
./gradlew buildAll --no-daemon --stacktrace \
|
||||||
|
-Porg.gradle.java.installations.fromEnv=JAVA_HOME_21_X64,JAVA_HOME_25_X64
|
||||||
|
|
||||||
- name: Upload jars to the release
|
- name: Upload jars to the release
|
||||||
env:
|
env:
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="https://raw.github.com/saturnclientmc/common/master/src/main/resources/assets/saturnclient/textures/logo/logo.png" alt="Saturn Client Logo" width="150px"/>
|
<img src="logo.png" alt="Saturn Client Logo" width="150px"/>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h1 align="center">Saturn Client</h1>
|
<h1 align="center">Saturn Client</h1>
|
||||||
@@ -13,27 +13,47 @@
|
|||||||
<img src="screenshot.png" alt="Saturn Client Screenshot"/>
|
<img src="screenshot.png" alt="Saturn Client Screenshot"/>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
---
|
## About
|
||||||
|
|
||||||
## ✨ About
|
|
||||||
|
|
||||||
Saturn Client is a free, open-source Minecraft client designed to enhance the gameplay experience while staying lightweight and customizable.
|
Saturn Client is a free, open-source Minecraft client designed to enhance the gameplay experience while staying lightweight and customizable.
|
||||||
Our goal is to build a clean, optimized client that the community can contribute to and improve together.
|
Our goal is to build a clean, optimized client that the community can contribute to and improve together.
|
||||||
|
|
||||||
Saturn is currently trying to support multiple versions of minecraft, so please report any issues you are experiencing.
|
Saturn runs on Fabric and supports **Minecraft 1.21.4 to 1.21.11 and 26.1 to 26.3**, with one build per version (the 26.1 build also runs on 26.1.1 and 26.1.2). If something doesn't work on your version, please open an issue.
|
||||||
|
|
||||||
---
|
## Features
|
||||||
|
|
||||||
## 🚀 Features
|
- Free cosmetics that don't require real money: cloaks, including animated ones, and hats
|
||||||
|
- 18 built-in mods: Armor Display, Auto Sprint, Clock, Coordinates, Crosshair, Day Counter, FPS Display, Freelook, Fullbright, Health Display, Keystrokes, Nametags, No Fog, Ping Display, Speedometer, Status Effects, TPS Display and Zoom
|
||||||
- Free cosmetics that don't require real money
|
|
||||||
- Clean and modern UI with animations
|
- Clean and modern UI with animations
|
||||||
- Ongoing performance improvements
|
- Ongoing performance improvements
|
||||||
- Open-source and community-driven
|
- Open-source and community-driven
|
||||||
|
|
||||||
---
|
## Installation
|
||||||
|
|
||||||
## 🤝 Contributing
|
1. Install [Fabric Loader](https://fabricmc.net/use/) and [Fabric API](https://modrinth.com/mod/fabric-api) for your Minecraft version.
|
||||||
|
2. Download the Saturn Client jar for the same version from [Modrinth](https://modrinth.com/mod/saturnclient) or the [releases page](https://git.selimaj.dev/saturnclientmc/saturnclient/releases).
|
||||||
|
3. Put it in your `mods` folder.
|
||||||
|
|
||||||
|
### Network use
|
||||||
|
|
||||||
|
For cosmetics, Saturn Client connects to the Saturn server (`saturn-server.selimaj.dev`). You're signed in the same way as on an online-mode Minecraft server: your client proves who you are to Mojang's session server, and the Saturn server asks Mojang to confirm it. **Your Minecraft access token is only ever sent to Mojang, never to the Saturn server.**
|
||||||
|
|
||||||
|
## Building from source
|
||||||
|
|
||||||
|
The version-independent code lives in the `common` submodule, so clone with submodules:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
git clone --recurse-submodules https://git.selimaj.dev/saturnclientmc/saturnclient.git
|
||||||
|
cd saturnclient
|
||||||
|
|
||||||
|
./gradlew buildAll # every version → build/allJars/
|
||||||
|
./gradlew :1.21.11:build # a single version
|
||||||
|
./gradlew :1.21.11:runClient # launch a development client
|
||||||
|
```
|
||||||
|
|
||||||
|
Building needs Java 21. The 26.x versions compile with Java 25, which Gradle downloads when it isn't installed.
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
We welcome contributions of all kinds:
|
We welcome contributions of all kinds:
|
||||||
|
|
||||||
@@ -43,27 +63,23 @@ We welcome contributions of all kinds:
|
|||||||
- Cosmetic designs
|
- Cosmetic designs
|
||||||
- Suggestions and feedback
|
- Suggestions and feedback
|
||||||
|
|
||||||
If you're an artist or designer interested in helping with cosmetics or visual assets, we’d love to collaborate.
|
If you're an artist or designer interested in helping with cosmetics or visual assets, we'd love to collaborate.
|
||||||
|
|
||||||
---
|
Most features belong in `common`, which every Minecraft version shares. For code that differs between versions, see the [version guide](VERSION_GUIDE.md): it covers how the versions share one copy of the code with Stonecutter, writing version-specific code, and adding a new Minecraft version. Every pull request is compiled for all versions automatically.
|
||||||
|
|
||||||
## 🙌 Credits
|
## Credits
|
||||||
|
|
||||||
- **Cloak Rendering:** `IIpho3nix` & Saturn
|
- **Cloak Rendering:** `IIpho3nix` & Saturn
|
||||||
- **Who Am I:** `FokshaWasTaken`
|
- **Who Am I:** `FokshaWasTaken`
|
||||||
- **Panorama:**
|
- **Panorama:**
|
||||||
https://www.planetminecraft.com/texture-pack/minecraft-cavesand-cliffs-part-ii-main-menu-panorama/
|
https://www.planetminecraft.com/texture-pack/minecraft-cavesand-cliffs-part-ii-main-menu-panorama/
|
||||||
|
|
||||||
If any code or assets belong to you and you would like them removed, please open an issue.
|
If any code or assets belong to you and you would like them removed, please open an issue.
|
||||||
We aim to properly credit all contributors and respect original creators of their respective art.
|
We aim to properly credit all contributors and respect original creators of their respective art.
|
||||||
|
|
||||||
---
|
## License
|
||||||
|
|
||||||
## 📜 License
|
Saturn Client is free and open-source under the **Apache-2.0** license.
|
||||||
|
|
||||||
Saturn Client is free and open-source using **Apache-2.0**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
Made with ❤️ by the Saturn Client team
|
Made with ❤️ by the Saturn Client team
|
||||||
|
|||||||
+80
-1
@@ -1,3 +1,82 @@
|
|||||||
# Version guide
|
# Version guide
|
||||||
|
|
||||||
This guide will help you to add or edit minecraft versions.
|
How Saturn Client supports several Minecraft versions, and how to change or add one.
|
||||||
|
|
||||||
|
## Layout
|
||||||
|
|
||||||
|
Every supported Minecraft version (1.21.4 to 1.21.11, and 26.1 to 26.3) builds from one copy of the version-specific code with [Stonecutter](https://stonecutter.kikugie.dev/):
|
||||||
|
|
||||||
|
- `common/`: all version-independent code (mods, UI, cosmetics, the server client). It has no Minecraft dependency and reaches the game only through the interfaces in `org.saturnclient.common`.
|
||||||
|
- `src/`: the version-specific code (providers, refs, mixins and the three mixin configs), one copy for every version. Where Minecraft's API differs between versions, the code goes behind version comments (see below).
|
||||||
|
- `versions/<mc>/`: each version's `gradle.properties` (Minecraft, Fabric Loader and Fabric API versions), plus its `build/` and `run/` folders. Each version is the Gradle project `:<mc>`.
|
||||||
|
|
||||||
|
`settings.gradle.kts` lists the versions in `stonecutterVersions`. The root `build.gradle.kts` builds each version, and `stonecutter.gradle.kts` configures the root project (shared repositories, the `common` wiring and `buildAll`). Loom's version is `loomx.loom_version` in the root `gradle.properties`.
|
||||||
|
|
||||||
|
Minecraft is obfuscated up to 1.21.11 and ships with Mojang's names from 26.1. The `loom-back-compat` plugin (`loomx`) applies the matching Loom for each version: the remapping one for 1.21.x, whose jar is `remapJar`'s output, and the plain one for 26.x, whose jar is `jar`'s. 26.x also compiles with Java 25 (21 before), set per version in `build.gradle.kts`; Gradle downloads a missing JDK through the foojay resolver, and CI installs both. An optional `minecraft_compat` in a version's `gradle.properties` widens the Minecraft versions its jar loads on, as 26.1's does for its hotfixes.
|
||||||
|
|
||||||
|
## Mappings
|
||||||
|
|
||||||
|
Every version is built with Mojang's official names (`loom.officialMojangMappings()`), the names Minecraft itself ships from 26.1 on. Yarn isn't used any more: 1.21.11 has the last Yarn release.
|
||||||
|
|
||||||
|
Mojang renames things between versions. `src/` uses the active version's (1.21.11's) names, and `stonecutter.gradle.kts` lists replacements that swap in each version's name when Stonecutter processes it: for example `Identifier`, which was `ResourceLocation` before 1.21.11, `AvatarRenderState`, which was `PlayerRenderState` before 1.21.9, and `GuiGraphics`, which is `GuiGraphicsExtractor` from 26.1. Add a replacement there when a rename touches many lines, and only for a name that appears nowhere else in `src/`: replacements work both ways, so the new name is also turned back into the old one for older versions (a `mulPose`/`rotate` replacement would rename `MatrixStackRef.rotate` too). From 26.2 the open screen and overlay live on `Gui` rather than `Minecraft`, so code goes through `impl.ui.Screens` for them.
|
||||||
|
|
||||||
|
From 26.3 Minecraft uses SDL instead of GLFW, with SDL's numbers for keys (scancodes), modifiers and mouse buttons. Saturn keeps GLFW's numbers, which `common` uses and the config's keybinds store, and `impl.provider.Keys` converts at the boundary: key events and mouse buttons coming in, and key codes going to the game. Use `InputConstants`' key constants rather than GLFW's in version-specific code.
|
||||||
|
|
||||||
|
26.3 also compiles every shader through SPIR-V, so shader inputs and outputs need an explicit `layout(location = …)`. Saturn's `glass.fsh` is shared by every version from 1.21.6 and switches its layout with the defines `GlassRenderer` sets, as it already did for `HAS_LINE_WIDTH`. A class that only moved package needs just a version comment on its import, and a renamed method a version comment where it's called.
|
||||||
|
|
||||||
|
Released 1.21.x jars run on intermediary names (`method_1234`), not Mojang's, and Loom renames everything that refers to Minecraft when it builds them. So a mixin can't implement one of `common`'s Ref interfaces by relying on a vanilla method that happens to have the same name: the vanilla method is renamed in the jar, and the interface method is left without an implementation (`AbstractMethodError`). Shadow the vanilla method and implement the Ref method explicitly, as `WindowMixin` does. For the same reason, Ref methods mustn't share a name and parameters with a method of the class they're mixed into, or the mixin replaces the vanilla method in development.
|
||||||
|
|
||||||
|
## Version-specific code with Stonecutter
|
||||||
|
|
||||||
|
The code in `src/` is always plain Java for the **active** version (1.21.11, set in `stonecutter.gradle.kts`). Code for other versions sits in comments that Stonecutter swaps before building each version:
|
||||||
|
|
||||||
|
```java
|
||||||
|
//? if >=1.21.9 {
|
||||||
|
public void submit(PoseStack matrices, SubmitNodeCollector output, int light, AvatarRenderState state, float limbAngle, float limbDistance) {
|
||||||
|
//?} else
|
||||||
|
//public void render(PoseStack matrices, MultiBufferSource output, int light, AvatarRenderState state, float limbAngle, float limbDistance) {
|
||||||
|
```
|
||||||
|
|
||||||
|
A condition without braces covers the next line only. For several lines, use braces and wrap the inactive block in `/* … */`:
|
||||||
|
|
||||||
|
```java
|
||||||
|
//? if >=1.21.11 {
|
||||||
|
import net.minecraft.client.renderer.rendertype.RenderTypes;
|
||||||
|
import net.minecraft.client.renderer.rendertype.RenderSetup;
|
||||||
|
//?} else {
|
||||||
|
/*import net.minecraft.client.renderer.RenderStateShard.MultiTextureStateShard;
|
||||||
|
*///?}
|
||||||
|
```
|
||||||
|
|
||||||
|
Stonecutter rewrites inactive code into its own layout when switching versions (a single inactive line becomes `//code`), so write it that way to begin with, or run a switch and a reset before committing.
|
||||||
|
|
||||||
|
When a newer version leaves something out that an older one implements, keep the older version's code behind a condition and mark the newer branch with a `TODO`, rather than dropping it for every version. The panorama in `SaturnScreenFabric.render` is an example. Search for `TODO` to find what newer versions still lack (tracked in #30), and remove the condition once a version gap is fixed.
|
||||||
|
|
||||||
|
A whole class that only exists for some versions (such as `MatrixStackMixin`, which only makes sense before 1.21.6's GUI rewrite) can keep an empty class body on the other versions. Before splitting a class like that, look for a hook that works on every version: nametags used to hook label rendering, which 1.21.9 changed, and now replace the name while the render state is built (`LivingEntityRendererMixin`), with no version condition at all. An empty mixin changes nothing, and it keeps the mixin configs the same for every version, since JSON can't hold version comments.
|
||||||
|
|
||||||
|
Inactive code sits inside a `/* … */` comment, so it can't contain `/* … */` comments itself: use `//` comments in code that's only active for some versions. Version blocks can nest (Stonecutter writes an inactive block inside another as `/^ … ^/`, as in `GlassRenderer`), but for more than two ranges of the same thing, prefer `//?} else if <condition> {`, as in `PlayerEntityRendererMixin` and `SaturnRenderState`.
|
||||||
|
|
||||||
|
- `./gradlew "Set active project to <mc>"` (or the Stonecutter IntelliJ plugin) rewrites `src/` so that version's code is the uncommented one, for editing it in the IDE.
|
||||||
|
- `./gradlew "Reset active project"` switches back to 1.21.11. **Run it before committing**, so `src/` is always committed with 1.21.11 active.
|
||||||
|
- Each version's processed copy is compiled from `versions/<mc>/build/generated/stonecutter/`, which is also where to look when a non-active version fails to compile.
|
||||||
|
|
||||||
|
The `emotes` repository uses the same setup, and its `BentCubeRenderer` and `PlayerModelMixin` are small examples of version comments.
|
||||||
|
|
||||||
|
## Checking a change on every version
|
||||||
|
|
||||||
|
Since every version shares `src/`, a change applies to all of them at once. Code that uses the Minecraft API may still only compile on some, so build them all:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
./gradlew compileJava
|
||||||
|
```
|
||||||
|
|
||||||
|
The **Check** workflow runs the same compile on every pull request, so a version a change doesn't compile on fails before merging. Mixins whose target changed fail at startup rather than at compile time, so launch the versions a change touches with `./gradlew :<mc>:runClient`.
|
||||||
|
|
||||||
|
For bigger changes, `./gradlew buildAll` builds every version's jar into `build/allJars/`.
|
||||||
|
|
||||||
|
## Adding a Minecraft version
|
||||||
|
|
||||||
|
1. Create `versions/<new>/gradle.properties` with `minecraft_version`, `loader_version` and `fabric_api_version` (copy 1.21.11's and change the values; they're listed at <https://fabricmc.net/develop>). Loom's version is shared, in the root `gradle.properties`.
|
||||||
|
2. Add `<new>` to `stonecutterVersions` in `settings.gradle.kts`.
|
||||||
|
3. Build it with `./gradlew :<new>:compileJava` and put what Minecraft changed behind version comments. Check the mixin configs in `src/main/resources/*.mixins.json` as well: a mixin whose target changed fails at startup (or when its class first loads, such as in a world), not at compile time, so launch it with `./gradlew :<new>:runClient`.
|
||||||
|
4. The release workflow builds everything through `buildAll`, and "Publish to Modrinth" takes the game version from each jar's name, so neither needs changing.
|
||||||
|
|||||||
@@ -1,64 +0,0 @@
|
|||||||
subprojects {
|
|
||||||
apply plugin: "java"
|
|
||||||
|
|
||||||
repositories {
|
|
||||||
maven { url "https://maven.fabricmc.net/" }
|
|
||||||
maven { url "https://git.selimaj.dev/api/packages/selimaj-dev/maven" }
|
|
||||||
mavenCentral()
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
implementation "de.javagl:obj:0.4.0"
|
|
||||||
implementation "com.formdev:svgSalamander:1.1.4"
|
|
||||||
implementation 'com.fasterxml.jackson.core:jackson-databind:2.17.0'
|
|
||||||
implementation 'com.fasterxml.jackson.core:jackson-annotations:2.17.0'
|
|
||||||
implementation 'com.fasterxml.jackson.core:jackson-core:2.17.0'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
configure(subprojects.findAll { it.name != "common" }) {
|
|
||||||
dependencies {
|
|
||||||
implementation project(":common")
|
|
||||||
|
|
||||||
sourceSets {
|
|
||||||
main {
|
|
||||||
resources {
|
|
||||||
srcDir project(":common").file("src/main/resources")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tasks.register("buildAll") {
|
|
||||||
group = "build"
|
|
||||||
description = "Builds all subprojects and collects their remapped jars"
|
|
||||||
|
|
||||||
// Only build remapped jars (not full build)
|
|
||||||
dependsOn subprojects.collect { proj ->
|
|
||||||
proj.tasks.matching { it.name == "remapJar" }
|
|
||||||
}
|
|
||||||
|
|
||||||
doLast {
|
|
||||||
def outputDir = file("$buildDir/allJars")
|
|
||||||
outputDir.mkdirs()
|
|
||||||
|
|
||||||
subprojects.each { proj ->
|
|
||||||
def remapTask = proj.tasks.findByName("remapJar")
|
|
||||||
|
|
||||||
if (remapTask != null) {
|
|
||||||
def jarFile = remapTask.archiveFile.get().asFile
|
|
||||||
|
|
||||||
if (jarFile.exists() && !jarFile.name.contains("sources")) {
|
|
||||||
copy {
|
|
||||||
from jarFile
|
|
||||||
into outputDir
|
|
||||||
rename { "${jarFile.name}" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
println "All remapped jars collected in: $outputDir"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
// Builds one Minecraft version from the shared src/. Stonecutter runs this for each version in
|
||||||
|
// stonecutterVersions (settings.gradle.kts), with that version's versions/<v>/gradle.properties.
|
||||||
|
plugins {
|
||||||
|
id("dev.kikugie.loom-back-compat")
|
||||||
|
`maven-publish`
|
||||||
|
}
|
||||||
|
|
||||||
|
val minecraftVersion = property("minecraft_version") as String
|
||||||
|
// The Minecraft versions the jar loads on (fabric.mod.json); a range where hotfixes share a jar.
|
||||||
|
val minecraftCompat = findProperty("minecraft_compat") as String? ?: minecraftVersion
|
||||||
|
val javaVersion = if (sc.current.parsed >= "26.1") 25 else 21
|
||||||
|
val loaderVersion = property("loader_version") as String
|
||||||
|
|
||||||
|
version = "${property("mod_version")}+$minecraftVersion"
|
||||||
|
group = property("maven_group") as String
|
||||||
|
|
||||||
|
base {
|
||||||
|
archivesName = property("archives_base_name") as String
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
minecraft("com.mojang:minecraft:$minecraftVersion")
|
||||||
|
loomx.applyMojangMappings()
|
||||||
|
modImplementation("net.fabricmc:fabric-loader:$loaderVersion")
|
||||||
|
modImplementation("net.fabricmc.fabric-api:fabric-api:${property("fabric_api_version")}")
|
||||||
|
|
||||||
|
// Saturn's emote library (limb bending, Emotecraft emotes)
|
||||||
|
include(modImplementation("org.saturnclient:emotes-fabric:${property("emotes_version")}+$minecraftVersion")!!)
|
||||||
|
|
||||||
|
implementation(project(":common"))
|
||||||
|
|
||||||
|
for (library in listOf(
|
||||||
|
"dev.selimaj:session-java:0.2.0",
|
||||||
|
"de.javagl:obj:0.4.0",
|
||||||
|
"com.formdev:svgSalamander:1.1.4",
|
||||||
|
"com.fasterxml.jackson.core:jackson-databind:2.17.0",
|
||||||
|
"com.fasterxml.jackson.core:jackson-annotations:2.17.0",
|
||||||
|
"com.fasterxml.jackson.core:jackson-core:2.17.0",
|
||||||
|
)) {
|
||||||
|
include(implementation(library)!!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Injects the version values into fabric.mod.json.
|
||||||
|
tasks.processResources {
|
||||||
|
val values = mapOf(
|
||||||
|
"version" to project.version.toString(),
|
||||||
|
"minecraft_version" to minecraftCompat,
|
||||||
|
"loader_version" to loaderVersion,
|
||||||
|
"java" to "JAVA_$javaVersion",
|
||||||
|
)
|
||||||
|
inputs.properties(values)
|
||||||
|
filteringCharset = "UTF-8"
|
||||||
|
filesMatching(listOf("fabric.mod.json", "*.mixins.json")) { expand(values) }
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.withType<JavaCompile>().configureEach {
|
||||||
|
options.release = javaVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
java {
|
||||||
|
// Loom attaches the sources jar to remapSourcesJar and build.
|
||||||
|
withSourcesJar()
|
||||||
|
toolchain.languageVersion = JavaLanguageVersion.of(javaVersion)
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.jar {
|
||||||
|
val archivesName = base.archivesName
|
||||||
|
inputs.property("archivesName", archivesName)
|
||||||
|
from("LICENSE") {
|
||||||
|
rename { "${it}_${archivesName.get()}" }
|
||||||
|
}
|
||||||
|
from(project(":common").extensions.getByType<SourceSetContainer>()["main"].output)
|
||||||
|
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
||||||
|
}
|
||||||
|
|
||||||
|
publishing {
|
||||||
|
publications {
|
||||||
|
create<MavenPublication>("mavenJava") {
|
||||||
|
artifactId = property("archives_base_name") as String
|
||||||
|
from(components["java"])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-1
Submodule common updated: 5df42e2b7c...e8f686b2d3
+9
-1
@@ -1,7 +1,15 @@
|
|||||||
org.gradle.jvmargs=-Xmx3G
|
org.gradle.jvmargs=-Xmx3G
|
||||||
org.gradle.parallel=true
|
org.gradle.parallel=true
|
||||||
|
# Reuse task outputs (e.g. compiled classes) when inputs haven't changed.
|
||||||
|
org.gradle.caching=true
|
||||||
|
|
||||||
# IntelliJ IDEA is not yet fully compatible with configuration cache, see: https://github.com/FabricMC/fabric-loom/issues/1349
|
# IntelliJ IDEA is not yet fully compatible with configuration cache, see: https://github.com/FabricMC/fabric-loom/issues/1349
|
||||||
org.gradle.configuration-cache=false
|
org.gradle.configuration-cache=false
|
||||||
|
|
||||||
mod_version=0.1.1-beta
|
mod_version=0.1.5-beta
|
||||||
|
|
||||||
|
# Saturn's emote library (git.selimaj.dev/saturnclientmc/emotes)
|
||||||
|
emotes_version=0.1.2
|
||||||
|
|
||||||
|
# Loom, for every Minecraft version (read by loom-back-compat).
|
||||||
|
loomx.loom_version=1.17.21
|
||||||
|
|||||||
Vendored
BIN
Binary file not shown.
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.0-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-9.8.0-bin.zip
|
||||||
networkTimeout=10000
|
networkTimeout=10000
|
||||||
validateDistributionUrl=true
|
validateDistributionUrl=true
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
|||||||
@@ -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:
|
# Important for running:
|
||||||
#
|
#
|
||||||
@@ -29,7 +29,7 @@
|
|||||||
# bash, then to run this script, type that shell name before the whole
|
# bash, then to run this script, type that shell name before the whole
|
||||||
# command line, like:
|
# command line, like:
|
||||||
#
|
#
|
||||||
# ksh Gradle
|
# ksh gradlew
|
||||||
#
|
#
|
||||||
# Busybox and similar reduced shells will NOT work, because this script
|
# Busybox and similar reduced shells will NOT work, because this script
|
||||||
# requires all of these POSIX shell features:
|
# requires all of these POSIX shell features:
|
||||||
@@ -57,7 +57,7 @@
|
|||||||
# Darwin, MinGW, and NonStop.
|
# Darwin, MinGW, and NonStop.
|
||||||
#
|
#
|
||||||
# (3) This script is generated from the Groovy template
|
# (3) This script is generated from the Groovy template
|
||||||
# https://github.com/gradle/gradle/blob/HEAD/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.
|
# within the Gradle project.
|
||||||
#
|
#
|
||||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||||
|
|||||||
Vendored
+52
-33
@@ -19,12 +19,39 @@
|
|||||||
@if "%DEBUG%"=="" @echo off
|
@if "%DEBUG%"=="" @echo off
|
||||||
@rem ##########################################################################
|
@rem ##########################################################################
|
||||||
@rem
|
@rem
|
||||||
@rem Gradle startup script for Windows
|
@rem gradlew startup script for Windows
|
||||||
@rem
|
@rem
|
||||||
@rem ##########################################################################
|
@rem ##########################################################################
|
||||||
|
|
||||||
@rem Set local scope for the variables with windows NT shell
|
@rem Set local scope for the variables, and ensure extensions are enabled
|
||||||
if "%OS%"=="Windows_NT" setlocal
|
setlocal EnableExtensions
|
||||||
|
|
||||||
|
@rem Catch executions from older scripts and ensure they exit cleanly.
|
||||||
|
@rem This can be removed once we can be reasonably confident that few people
|
||||||
|
@rem will be migrating directly to this new wrapper.
|
||||||
|
goto afterSafetyNet
|
||||||
|
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
||||||
|
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
||||||
|
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
||||||
|
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
||||||
|
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
||||||
|
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
||||||
|
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
||||||
|
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
||||||
|
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
||||||
|
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
||||||
|
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
||||||
|
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
||||||
|
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
||||||
|
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
||||||
|
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
||||||
|
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
||||||
|
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
||||||
|
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
||||||
|
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
||||||
|
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
||||||
|
goto exitWithErrorLevel
|
||||||
|
:afterSafetyNet
|
||||||
|
|
||||||
set DIRNAME=%~dp0
|
set DIRNAME=%~dp0
|
||||||
if "%DIRNAME%"=="" set DIRNAME=.
|
if "%DIRNAME%"=="" set DIRNAME=.
|
||||||
@@ -45,13 +72,14 @@ set JAVA_EXE=java.exe
|
|||||||
%JAVA_EXE% -version >NUL 2>&1
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
if %ERRORLEVEL% equ 0 goto execute
|
if %ERRORLEVEL% equ 0 goto execute
|
||||||
|
|
||||||
echo. 1>&2
|
1>&2 echo.
|
||||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
|
1>&2 echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
echo. 1>&2
|
1>&2 echo.
|
||||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
1>&2 echo Please set the JAVA_HOME variable in your environment to match the
|
||||||
echo location of your Java installation. 1>&2
|
1>&2 echo location of your Java installation.
|
||||||
|
|
||||||
goto fail
|
"%COMSPEC%" /c exit 1
|
||||||
|
goto exitWithErrorLevel
|
||||||
|
|
||||||
:findJavaFromJavaHome
|
:findJavaFromJavaHome
|
||||||
set JAVA_HOME=%JAVA_HOME:"=%
|
set JAVA_HOME=%JAVA_HOME:"=%
|
||||||
@@ -59,35 +87,26 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
|||||||
|
|
||||||
if exist "%JAVA_EXE%" goto execute
|
if exist "%JAVA_EXE%" goto execute
|
||||||
|
|
||||||
echo. 1>&2
|
1>&2 echo.
|
||||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
|
1>&2 echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||||
echo. 1>&2
|
1>&2 echo.
|
||||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
1>&2 echo Please set the JAVA_HOME variable in your environment to match the
|
||||||
echo location of your Java installation. 1>&2
|
1>&2 echo location of your Java installation.
|
||||||
|
|
||||||
goto fail
|
"%COMSPEC%" /c exit 1
|
||||||
|
goto exitWithErrorLevel
|
||||||
|
|
||||||
:execute
|
:execute
|
||||||
@rem Setup the command line
|
@rem Setup the command line
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@rem Execute Gradle
|
@rem Execute gradlew
|
||||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
|
@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 & goto exitWithErrorLevel
|
||||||
|
|
||||||
:end
|
@rem This label must not be changed. We rely on old scripts being able to jump to this point.
|
||||||
@rem End local scope for the variables with windows NT shell
|
:exitWithErrorLevel
|
||||||
if %ERRORLEVEL% equ 0 goto mainEnd
|
@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts
|
||||||
|
"%COMSPEC%" /c exit %ERRORLEVEL%
|
||||||
: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
|
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 1.8 MiB After Width: | Height: | Size: 591 KiB |
@@ -1,21 +0,0 @@
|
|||||||
pluginManagement {
|
|
||||||
repositories {
|
|
||||||
maven { url "https://maven.fabricmc.net/" }
|
|
||||||
gradlePluginPortal()
|
|
||||||
mavenCentral()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
rootProject.name = "saturn-client"
|
|
||||||
|
|
||||||
include("common")
|
|
||||||
|
|
||||||
def versionsDir = file("versions")
|
|
||||||
|
|
||||||
versionsDir.eachDir { dir ->
|
|
||||||
def version = dir.name
|
|
||||||
def projectName = "mc-${version}"
|
|
||||||
|
|
||||||
include(projectName)
|
|
||||||
project(":${projectName}").projectDir = dir
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
pluginManagement {
|
||||||
|
repositories {
|
||||||
|
maven("https://maven.fabricmc.net/")
|
||||||
|
maven("https://maven.kikugie.dev/releases")
|
||||||
|
gradlePluginPortal()
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
// Downloads the Java toolchain a version needs (Java 25 for 26.1+) when it isn't installed.
|
||||||
|
id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0"
|
||||||
|
id("dev.kikugie.stonecutter") version "0.9.8"
|
||||||
|
// Picks the Loom variant per version: remapping for 1.21.x, plain for the unobfuscated 26.1+.
|
||||||
|
// Its version is loomx.loom_version in gradle.properties.
|
||||||
|
id("dev.kikugie.loom-back-compat") version "0.4.2"
|
||||||
|
}
|
||||||
|
|
||||||
|
rootProject.name = "saturn-client"
|
||||||
|
|
||||||
|
include("common")
|
||||||
|
|
||||||
|
// Versions built by Stonecutter from the shared src/ (see stonecutter.gradle.kts). Each one's
|
||||||
|
// versions/<v>/gradle.properties holds its Minecraft, Fabric Loader and Fabric API versions.
|
||||||
|
val stonecutterVersions = listOf(
|
||||||
|
"1.21.4", "1.21.5", "1.21.6", "1.21.7", "1.21.8", "1.21.9", "1.21.10", "1.21.11",
|
||||||
|
"26.1", "26.2", "26.3",
|
||||||
|
)
|
||||||
|
|
||||||
|
stonecutter {
|
||||||
|
create(rootProject) {
|
||||||
|
versions(stonecutterVersions)
|
||||||
|
vcsVersion = "1.21.11"
|
||||||
|
}
|
||||||
|
}
|
||||||
+169
-84
@@ -1,37 +1,51 @@
|
|||||||
package org.saturnclient.impl.cosmetics;
|
package org.saturnclient.impl.cosmetics;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.WeakHashMap;
|
||||||
|
|
||||||
import org.joml.Matrix3f;
|
import org.joml.Matrix3f;
|
||||||
import org.joml.Matrix4f;
|
import org.joml.Matrix4f;
|
||||||
|
import org.joml.Quaternionf;
|
||||||
import org.joml.Vector3f;
|
import org.joml.Vector3f;
|
||||||
import org.joml.Vector4f;
|
import org.joml.Vector4f;
|
||||||
import org.saturnclient.client.player.SaturnPlayer;
|
import org.saturnclient.client.player.SaturnPlayer;
|
||||||
import org.saturnclient.common.ref.asset.IdentifierRef;
|
import org.saturnclient.common.ref.asset.IdentifierRef;
|
||||||
import org.saturnclient.config.Config;
|
import org.saturnclient.config.Config;
|
||||||
import org.saturnclient.cosmetics.Cloaks;
|
import org.saturnclient.cosmetics.Cloaks;
|
||||||
|
import org.saturnclient.emotes.core.Animation;
|
||||||
|
import org.saturnclient.emotes.core.Bone;
|
||||||
|
import org.saturnclient.emotes.core.bend.LimbBend;
|
||||||
|
import org.saturnclient.emotes.core.bend.Vec3;
|
||||||
|
import org.saturnclient.emotes.fabric.access.BendablePart;
|
||||||
import org.saturnclient.impl.cosmetics.utils.ShaderUtils;
|
import org.saturnclient.impl.cosmetics.utils.ShaderUtils;
|
||||||
import org.saturnclient.saturnclient.SaturnRenderState;
|
import org.saturnclient.saturnclient.SaturnRenderState;
|
||||||
|
|
||||||
import net.minecraft.client.render.OverlayTexture;
|
import net.minecraft.client.model.geom.ModelPart;
|
||||||
import net.minecraft.client.render.VertexConsumer;
|
import net.minecraft.client.renderer.texture.OverlayTexture;
|
||||||
import net.minecraft.client.render.command.OrderedRenderCommandQueue;
|
import com.mojang.blaze3d.vertex.VertexConsumer;
|
||||||
import net.minecraft.client.render.entity.equipment.EquipmentModel;
|
//? if >=1.21.9 {
|
||||||
import net.minecraft.client.render.entity.equipment.EquipmentModelLoader;
|
import net.minecraft.client.renderer.SubmitNodeCollector;
|
||||||
import net.minecraft.client.render.entity.equipment.EquipmentModel.LayerType;
|
//?} else
|
||||||
import net.minecraft.client.render.entity.feature.FeatureRenderer;
|
//import net.minecraft.client.renderer.MultiBufferSource;
|
||||||
import net.minecraft.client.render.entity.feature.FeatureRendererContext;
|
import net.minecraft.client.resources.model.EquipmentClientInfo;
|
||||||
import net.minecraft.client.render.entity.model.PlayerEntityModel;
|
import net.minecraft.client.resources.model.EquipmentAssetManager;
|
||||||
import net.minecraft.client.render.entity.state.PlayerEntityRenderState;
|
import net.minecraft.client.resources.model.EquipmentClientInfo.LayerType;
|
||||||
import net.minecraft.client.util.math.MatrixStack;
|
import net.minecraft.client.renderer.entity.layers.RenderLayer;
|
||||||
import net.minecraft.component.DataComponentTypes;
|
import net.minecraft.client.renderer.entity.RenderLayerParent;
|
||||||
import net.minecraft.component.type.EquippableComponent;
|
//? if >=1.21.11 {
|
||||||
import net.minecraft.item.ItemStack;
|
import net.minecraft.client.model.player.PlayerModel;
|
||||||
import net.minecraft.item.equipment.EquipmentAsset;
|
//?} else
|
||||||
import net.minecraft.registry.RegistryKey;
|
//import net.minecraft.client.model.PlayerModel;
|
||||||
import net.minecraft.util.math.MathHelper;
|
import net.minecraft.client.renderer.entity.state.AvatarRenderState;
|
||||||
import net.minecraft.util.math.RotationAxis;
|
import com.mojang.blaze3d.vertex.PoseStack;
|
||||||
import net.minecraft.util.math.Vec3d;
|
import net.minecraft.core.component.DataComponents;
|
||||||
|
import net.minecraft.world.item.equipment.Equippable;
|
||||||
|
import net.minecraft.world.item.ItemStack;
|
||||||
|
import net.minecraft.world.item.equipment.EquipmentAsset;
|
||||||
|
import net.minecraft.resources.ResourceKey;
|
||||||
|
import net.minecraft.util.Mth;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The cloak renderer
|
* The cloak renderer
|
||||||
@@ -40,8 +54,8 @@ import net.minecraft.util.math.Vec3d;
|
|||||||
* - **Curving**: Curves when player velocity increases.
|
* - **Curving**: Curves when player velocity increases.
|
||||||
* - **Wavey**: Makes the cloak feel like wind, when jump sprinting.
|
* - **Wavey**: Makes the cloak feel like wind, when jump sprinting.
|
||||||
*/
|
*/
|
||||||
public class CloakFeatureRenderer extends FeatureRenderer<PlayerEntityRenderState, PlayerEntityModel> {
|
public class CloakFeatureRenderer extends RenderLayer<AvatarRenderState, PlayerModel> {
|
||||||
private final EquipmentModelLoader equipmentModelLoader;
|
private final EquipmentAssetManager equipmentModelLoader;
|
||||||
private static final int PARTS = 24;
|
private static final int PARTS = 24;
|
||||||
private final float[] segmentValues = new float[PARTS];
|
private final float[] segmentValues = new float[PARTS];
|
||||||
private static final int H_PARTS = 6;
|
private static final int H_PARTS = 6;
|
||||||
@@ -84,19 +98,19 @@ public class CloakFeatureRenderer extends FeatureRenderer<PlayerEntityRenderStat
|
|||||||
private static final PxRect TOP_RECT = new PxRect(8, 0, 80, 8);
|
private static final PxRect TOP_RECT = new PxRect(8, 0, 80, 8);
|
||||||
private static final PxRect BOTTOM_RECT = new PxRect(88, 0, 80, 8);
|
private static final PxRect BOTTOM_RECT = new PxRect(88, 0, 80, 8);
|
||||||
|
|
||||||
public CloakFeatureRenderer(FeatureRendererContext<PlayerEntityRenderState, PlayerEntityModel> context,
|
public CloakFeatureRenderer(RenderLayerParent<AvatarRenderState, PlayerModel> context,
|
||||||
EquipmentModelLoader equipmentModelLoader) {
|
EquipmentAssetManager equipmentModelLoader) {
|
||||||
super(context);
|
super(context);
|
||||||
this.equipmentModelLoader = equipmentModelLoader;
|
this.equipmentModelLoader = equipmentModelLoader;
|
||||||
Arrays.fill(segmentValues, 0.0f);
|
Arrays.fill(segmentValues, 0.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean hasCustomModelForLayer(ItemStack stack, EquipmentModel.LayerType layerType) {
|
private boolean hasCustomModelForLayer(ItemStack stack, EquipmentClientInfo.LayerType layerType) {
|
||||||
EquippableComponent equippableComponent = (EquippableComponent) stack
|
Equippable equippableComponent = (Equippable) stack
|
||||||
.get(DataComponentTypes.EQUIPPABLE);
|
.get(DataComponents.EQUIPPABLE);
|
||||||
if (equippableComponent != null && !equippableComponent.assetId().isEmpty()) {
|
if (equippableComponent != null && !equippableComponent.assetId().isEmpty()) {
|
||||||
EquipmentModel equipmentModel = this.equipmentModelLoader
|
EquipmentClientInfo equipmentModel = this.equipmentModelLoader
|
||||||
.get((RegistryKey<EquipmentAsset>) equippableComponent.assetId().get());
|
.get((ResourceKey<EquipmentAsset>) equippableComponent.assetId().get());
|
||||||
return !equipmentModel.getLayers(layerType).isEmpty();
|
return !equipmentModel.getLayers(layerType).isEmpty();
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
@@ -104,7 +118,7 @@ public class CloakFeatureRenderer extends FeatureRenderer<PlayerEntityRenderStat
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void vertex(VertexConsumer vc, Matrix4f posMat, Matrix3f normalMat,
|
private void vertex(VertexConsumer vc, Matrix4f posMat, Matrix3f normalMat,
|
||||||
Vec3d pos, int color,
|
net.minecraft.world.phys.Vec3 pos, int color,
|
||||||
float u, float v, int overlay, int light,
|
float u, float v, int overlay, int light,
|
||||||
float nx, float ny, float nz) {
|
float nx, float ny, float nz) {
|
||||||
|
|
||||||
@@ -114,7 +128,7 @@ public class CloakFeatureRenderer extends FeatureRenderer<PlayerEntityRenderStat
|
|||||||
Vector3f n = new Vector3f(nx, ny, nz);
|
Vector3f n = new Vector3f(nx, ny, nz);
|
||||||
n.mul(normalMat);
|
n.mul(normalMat);
|
||||||
|
|
||||||
vc.vertex(
|
vc.addVertex(
|
||||||
p.x(), p.y(), p.z(),
|
p.x(), p.y(), p.z(),
|
||||||
color,
|
color,
|
||||||
u, v,
|
u, v,
|
||||||
@@ -122,13 +136,13 @@ public class CloakFeatureRenderer extends FeatureRenderer<PlayerEntityRenderStat
|
|||||||
n.x(), n.y(), n.z());
|
n.x(), n.y(), n.z());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void renderCapeQuad(VertexConsumer vertexConsumer, MatrixStack.Entry entry,
|
private void renderCapeQuad(VertexConsumer vertexConsumer, PoseStack.Pose entry,
|
||||||
Vec3d bottomLeft, Vec3d bottomRight, Vec3d topRight, Vec3d topLeft,
|
net.minecraft.world.phys.Vec3 bottomLeft, net.minecraft.world.phys.Vec3 bottomRight, net.minecraft.world.phys.Vec3 topRight, net.minecraft.world.phys.Vec3 topLeft,
|
||||||
float u1, float v1, float u2, float v2, int light, int overlay,
|
float u1, float v1, float u2, float v2, int light, int overlay,
|
||||||
float normalX, float normalY, float normalZ, boolean flipWinding) {
|
float normalX, float normalY, float normalZ, boolean flipWinding) {
|
||||||
// Extract matrix (you must transform positions manually now)
|
// Extract matrix (you must transform positions manually now)
|
||||||
Matrix4f pos = entry.getPositionMatrix();
|
Matrix4f pos = entry.pose();
|
||||||
Matrix3f normalMat = entry.getNormalMatrix();
|
Matrix3f normalMat = entry.normal();
|
||||||
|
|
||||||
int color = 0xFFFFFFFF; // white RGBA
|
int color = 0xFFFFFFFF; // white RGBA
|
||||||
|
|
||||||
@@ -151,8 +165,8 @@ public class CloakFeatureRenderer extends FeatureRenderer<PlayerEntityRenderStat
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void renderCape(MatrixStack.Entry entry, VertexConsumer vertexConsumer,
|
private void renderCape(PoseStack.Pose entry, VertexConsumer vertexConsumer,
|
||||||
PlayerEntityRenderState playerState, int light, int overlay) {
|
AvatarRenderState playerState, int light, int overlay) {
|
||||||
|
|
||||||
float capeWidth = 10.0f / 16.0f;
|
float capeWidth = 10.0f / 16.0f;
|
||||||
float capeHeight = 16.0f / 16.0f;
|
float capeHeight = 16.0f / 16.0f;
|
||||||
@@ -214,8 +228,8 @@ public class CloakFeatureRenderer extends FeatureRenderer<PlayerEntityRenderStat
|
|||||||
// 2. Grid
|
// 2. Grid
|
||||||
// =============================
|
// =============================
|
||||||
|
|
||||||
Vec3d[][] inner = new Vec3d[H_PARTS + 1][PARTS + 1];
|
net.minecraft.world.phys.Vec3[][] inner = new net.minecraft.world.phys.Vec3[H_PARTS + 1][PARTS + 1];
|
||||||
Vec3d[][] outer = new Vec3d[H_PARTS + 1][PARTS + 1];
|
net.minecraft.world.phys.Vec3[][] outer = new net.minecraft.world.phys.Vec3[H_PARTS + 1][PARTS + 1];
|
||||||
|
|
||||||
for (int x = 0; x <= H_PARTS; x++) {
|
for (int x = 0; x <= H_PARTS; x++) {
|
||||||
|
|
||||||
@@ -247,8 +261,8 @@ public class CloakFeatureRenderer extends FeatureRenderer<PlayerEntityRenderStat
|
|||||||
float thickY = sinA * capeDepth;
|
float thickY = sinA * capeDepth;
|
||||||
float thickZ = cosA * capeDepth;
|
float thickZ = cosA * capeDepth;
|
||||||
|
|
||||||
inner[x][i] = new Vec3d(xPos, y - curveY, z - curveZ);
|
inner[x][i] = new net.minecraft.world.phys.Vec3(xPos, y - curveY, z - curveZ);
|
||||||
outer[x][i] = new Vec3d(xPos, (y - curveY) - thickY, (z - curveZ) - thickZ);
|
outer[x][i] = new net.minecraft.world.phys.Vec3(xPos, (y - curveY) - thickY, (z - curveZ) - thickZ);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -273,15 +287,15 @@ public class CloakFeatureRenderer extends FeatureRenderer<PlayerEntityRenderStat
|
|||||||
float bvStart = backV1 + backPartV * i;
|
float bvStart = backV1 + backPartV * i;
|
||||||
float bvEnd = backV1 + backPartV * (i + 1);
|
float bvEnd = backV1 + backPartV * (i + 1);
|
||||||
|
|
||||||
Vec3d innerTL = inner[x + 1][i];
|
net.minecraft.world.phys.Vec3 innerTL = inner[x + 1][i];
|
||||||
Vec3d innerTR = inner[x][i];
|
net.minecraft.world.phys.Vec3 innerTR = inner[x][i];
|
||||||
Vec3d innerBL = inner[x + 1][i + 1];
|
net.minecraft.world.phys.Vec3 innerBL = inner[x + 1][i + 1];
|
||||||
Vec3d innerBR = inner[x][i + 1];
|
net.minecraft.world.phys.Vec3 innerBR = inner[x][i + 1];
|
||||||
|
|
||||||
Vec3d outerTL = outer[x + 1][i];
|
net.minecraft.world.phys.Vec3 outerTL = outer[x + 1][i];
|
||||||
Vec3d outerTR = outer[x][i];
|
net.minecraft.world.phys.Vec3 outerTR = outer[x][i];
|
||||||
Vec3d outerBL = outer[x + 1][i + 1];
|
net.minecraft.world.phys.Vec3 outerBL = outer[x + 1][i + 1];
|
||||||
Vec3d outerBR = outer[x][i + 1];
|
net.minecraft.world.phys.Vec3 outerBR = outer[x][i + 1];
|
||||||
|
|
||||||
// FRONT
|
// FRONT
|
||||||
renderCapeQuad(vertexConsumer, entry,
|
renderCapeQuad(vertexConsumer, entry,
|
||||||
@@ -303,10 +317,10 @@ public class CloakFeatureRenderer extends FeatureRenderer<PlayerEntityRenderStat
|
|||||||
|
|
||||||
for (int i = 0; i < PARTS; i++) {
|
for (int i = 0; i < PARTS; i++) {
|
||||||
|
|
||||||
Vec3d innerTop = inner[H_PARTS][i];
|
net.minecraft.world.phys.Vec3 innerTop = inner[H_PARTS][i];
|
||||||
Vec3d innerBot = inner[H_PARTS][i + 1];
|
net.minecraft.world.phys.Vec3 innerBot = inner[H_PARTS][i + 1];
|
||||||
Vec3d outerTop = outer[H_PARTS][i];
|
net.minecraft.world.phys.Vec3 outerTop = outer[H_PARTS][i];
|
||||||
Vec3d outerBot = outer[H_PARTS][i + 1];
|
net.minecraft.world.phys.Vec3 outerBot = outer[H_PARTS][i + 1];
|
||||||
|
|
||||||
float vStart = leftV1 + leftPartV * i;
|
float vStart = leftV1 + leftPartV * i;
|
||||||
float vEnd = leftV1 + leftPartV * (i + 1);
|
float vEnd = leftV1 + leftPartV * (i + 1);
|
||||||
@@ -324,10 +338,10 @@ public class CloakFeatureRenderer extends FeatureRenderer<PlayerEntityRenderStat
|
|||||||
|
|
||||||
for (int i = 0; i < PARTS; i++) {
|
for (int i = 0; i < PARTS; i++) {
|
||||||
|
|
||||||
Vec3d innerTop = inner[0][i];
|
net.minecraft.world.phys.Vec3 innerTop = inner[0][i];
|
||||||
Vec3d innerBot = inner[0][i + 1];
|
net.minecraft.world.phys.Vec3 innerBot = inner[0][i + 1];
|
||||||
Vec3d outerTop = outer[0][i];
|
net.minecraft.world.phys.Vec3 outerTop = outer[0][i];
|
||||||
Vec3d outerBot = outer[0][i + 1];
|
net.minecraft.world.phys.Vec3 outerBot = outer[0][i + 1];
|
||||||
|
|
||||||
float vStart = rightV1 + rightPartV * i;
|
float vStart = rightV1 + rightPartV * i;
|
||||||
float vEnd = rightV1 + rightPartV * (i + 1);
|
float vEnd = rightV1 + rightPartV * (i + 1);
|
||||||
@@ -345,10 +359,10 @@ public class CloakFeatureRenderer extends FeatureRenderer<PlayerEntityRenderStat
|
|||||||
|
|
||||||
for (int x = 0; x < H_PARTS; x++) {
|
for (int x = 0; x < H_PARTS; x++) {
|
||||||
|
|
||||||
Vec3d outerL = outer[x][0];
|
net.minecraft.world.phys.Vec3 outerL = outer[x][0];
|
||||||
Vec3d outerR = outer[x + 1][0];
|
net.minecraft.world.phys.Vec3 outerR = outer[x + 1][0];
|
||||||
Vec3d innerR = inner[x + 1][0];
|
net.minecraft.world.phys.Vec3 innerR = inner[x + 1][0];
|
||||||
Vec3d innerL = inner[x][0];
|
net.minecraft.world.phys.Vec3 innerL = inner[x][0];
|
||||||
|
|
||||||
float uStart = topU1 + (topU2 - topU1) * ((float) x / H_PARTS);
|
float uStart = topU1 + (topU2 - topU1) * ((float) x / H_PARTS);
|
||||||
float uEnd = topU1 + (topU2 - topU1) * ((float) (x + 1) / H_PARTS);
|
float uEnd = topU1 + (topU2 - topU1) * ((float) (x + 1) / H_PARTS);
|
||||||
@@ -365,10 +379,10 @@ public class CloakFeatureRenderer extends FeatureRenderer<PlayerEntityRenderStat
|
|||||||
|
|
||||||
for (int x = 0; x < H_PARTS; x++) {
|
for (int x = 0; x < H_PARTS; x++) {
|
||||||
|
|
||||||
Vec3d innerL = inner[x][PARTS];
|
net.minecraft.world.phys.Vec3 innerL = inner[x][PARTS];
|
||||||
Vec3d innerR = inner[x + 1][PARTS];
|
net.minecraft.world.phys.Vec3 innerR = inner[x + 1][PARTS];
|
||||||
Vec3d outerR = outer[x + 1][PARTS];
|
net.minecraft.world.phys.Vec3 outerR = outer[x + 1][PARTS];
|
||||||
Vec3d outerL = outer[x][PARTS];
|
net.minecraft.world.phys.Vec3 outerL = outer[x][PARTS];
|
||||||
|
|
||||||
float uStart = bottomU1 + (bottomU2 - bottomU1) * ((float) x / H_PARTS);
|
float uStart = bottomU1 + (bottomU2 - bottomU1) * ((float) x / H_PARTS);
|
||||||
float uEnd = bottomU1 + (bottomU2 - bottomU1) * ((float) (x + 1) / H_PARTS);
|
float uEnd = bottomU1 + (bottomU2 - bottomU1) * ((float) (x + 1) / H_PARTS);
|
||||||
@@ -380,6 +394,54 @@ public class CloakFeatureRenderer extends FeatureRenderer<PlayerEntityRenderStat
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** A torso bend past this curves the back more than a flat cloak can follow. */
|
||||||
|
private static final float MAX_CLOAK_BEND = (float) Math.toRadians(10);
|
||||||
|
/** Whether each animation bends the torso past that at any point. */
|
||||||
|
private static final Map<Animation, Boolean> BENDS_TORSO = Collections.synchronizedMap(new WeakHashMap<>());
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the player is playing an emote that curves the torso (a twerk, the griddy). The cloak
|
||||||
|
* would cut through the arched back, so it's hidden for the whole emote rather than only while
|
||||||
|
* the back is curved, which would flicker.
|
||||||
|
*/
|
||||||
|
private static boolean playingTorsoBend(SaturnPlayer player) {
|
||||||
|
return org.saturnclient.emotes.fabric.Emotes.playing(player.uuid)
|
||||||
|
.map(playing -> BENDS_TORSO.computeIfAbsent(playing.animation(), CloakFeatureRenderer::bendsTorso))
|
||||||
|
.orElse(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean bendsTorso(Animation animation) {
|
||||||
|
for (float tick = 0; tick <= animation.length(); tick += 0.5f) {
|
||||||
|
if (animation.sample(tick).get(Bone.BODY).filter(body -> Math.abs(body.bend()) > MAX_CLOAK_BEND).isPresent()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An emote that bends the torso a little leans the chest from the hips, so the cloak hangs from
|
||||||
|
* where the top of the back ends up, turned as much as the torso is there.
|
||||||
|
*/
|
||||||
|
private static void followBend(PoseStack matrices, ModelPart body) {
|
||||||
|
LimbBend bend = ((BendablePart) (Object) body).saturnEmotes$getBend();
|
||||||
|
if (bend == null || bend.isStraight()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The top of the torso, in the body's own pixels
|
||||||
|
float[] top = new float[3];
|
||||||
|
bend.bendPoint(0, 0, 0, top);
|
||||||
|
matrices.translate(top[0] / 16.0f, top[1] / 16.0f, top[2] / 16.0f);
|
||||||
|
|
||||||
|
Vec3 axis = bend.axis();
|
||||||
|
Quaternionf rotation = new Quaternionf().rotationAxis(bend.angleAt(0), axis.x(), axis.y(), axis.z());
|
||||||
|
//? if >=26.3 {
|
||||||
|
/*matrices.rotate(rotation);
|
||||||
|
*///?} else
|
||||||
|
matrices.mulPose(rotation);
|
||||||
|
}
|
||||||
|
|
||||||
// Shift array left and insert new value at the start
|
// Shift array left and insert new value at the start
|
||||||
private void updateSegmentValues(float value) {
|
private void updateSegmentValues(float value) {
|
||||||
// Shift all elements one position to the right (from end to start)
|
// Shift all elements one position to the right (from end to start)
|
||||||
@@ -394,22 +456,35 @@ public class CloakFeatureRenderer extends FeatureRenderer<PlayerEntityRenderStat
|
|||||||
private long lastUpdate = 0;
|
private long lastUpdate = 0;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void render(MatrixStack matrices, OrderedRenderCommandQueue queue, int light, PlayerEntityRenderState state,
|
//? if >=1.21.9 {
|
||||||
float limbAngle, float limbDistance) {
|
public void submit(PoseStack matrices,
|
||||||
if (state.invisible || !state.capeVisible
|
//?} else
|
||||||
|| state.skinTextures.cape() != null) {
|
//public void render(PoseStack matrices,
|
||||||
|
//? if >=1.21.9 {
|
||||||
|
SubmitNodeCollector output,
|
||||||
|
//?} else
|
||||||
|
//MultiBufferSource output,
|
||||||
|
int light, AvatarRenderState state, float limbAngle, float limbDistance) {
|
||||||
|
if (state.isInvisible || !state.showCape
|
||||||
|
//? if >=1.21.9 {
|
||||||
|
|| state.skin.cape() != null) {
|
||||||
|
//?} else
|
||||||
|
//|| state.skin.capeTexture() != null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//? if >=1.21.8 {
|
||||||
SaturnPlayer player = state.getData(SaturnRenderState.saturnDataKey);
|
SaturnPlayer player = state.getData(SaturnRenderState.saturnDataKey);
|
||||||
|
//?} else
|
||||||
|
//SaturnPlayer player = SaturnPlayer.get(state.name);
|
||||||
|
|
||||||
if (player == null) {
|
if (player == null || playingTorsoBend(player)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
IdentifierRef customCape = Cloaks.getCurrentCloakTexture(player.cloak);
|
IdentifierRef customCape = Cloaks.getCurrentCloakTexture(player.cloak);
|
||||||
if (customCape == null
|
if (customCape == null
|
||||||
|| this.hasCustomModelForLayer(state.equippedChestStack, LayerType.WINGS)) {
|
|| this.hasCustomModelForLayer(state.chestEquipment, LayerType.WINGS)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -421,26 +496,33 @@ public class CloakFeatureRenderer extends FeatureRenderer<PlayerEntityRenderStat
|
|||||||
skyLight = Math.max(skyLight, minBrightness);
|
skyLight = Math.max(skyLight, minBrightness);
|
||||||
light = (skyLight << 20) | (blockLight << 4);
|
light = (skyLight << 20) | (blockLight << 4);
|
||||||
|
|
||||||
matrices.push();
|
matrices.pushPose();
|
||||||
|
|
||||||
|
// On the back: the whole player (which emotes like the backflip turn), then the body's position
|
||||||
|
// and turn (which crouching and emotes move), then the chest's lean when an emote bends the torso
|
||||||
|
PlayerModel model = this.getParentModel();
|
||||||
|
//? if >=1.21.5 {
|
||||||
|
model.root().translateAndRotate(matrices);
|
||||||
|
model.body.translateAndRotate(matrices);
|
||||||
|
//?} else {
|
||||||
|
/*model.root().translateAndRotate(matrices);
|
||||||
|
model.body.translateAndRotate(matrices);
|
||||||
|
*///?}
|
||||||
|
followBend(matrices, model.body);
|
||||||
|
|
||||||
matrices.translate(0.0f, 0.0f, 0.12f);
|
matrices.translate(0.0f, 0.0f, 0.12f);
|
||||||
|
|
||||||
if (this.hasCustomModelForLayer(state.equippedChestStack, LayerType.HUMANOID)) {
|
if (this.hasCustomModelForLayer(state.chestEquipment, LayerType.HUMANOID)) {
|
||||||
matrices.translate(0.0F, -0.053125F, 0.06875F);
|
matrices.translate(0.0F, -0.053125F, 0.06875F);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state.isInSneakingPose) {
|
|
||||||
matrices.translate(0, 0.16f, 0.0f);
|
|
||||||
matrices.multiply(RotationAxis.POSITIVE_X.rotationDegrees(29.0f));
|
|
||||||
}
|
|
||||||
|
|
||||||
long now = System.currentTimeMillis();
|
long now = System.currentTimeMillis();
|
||||||
if (now - lastUpdate >= 20) {
|
if (now - lastUpdate >= 20) {
|
||||||
float velX = Math.min(0.8f, state.field_53537 / 108.0f);
|
float velX = Math.min(0.8f, state.capeLean / 108.0f);
|
||||||
float rawVelY = state.field_53536;
|
float rawVelY = state.capeFlap;
|
||||||
float velY = (rawVelY > 3.4f ? rawVelY : rawVelY < -3.4f ? rawVelY : 0.0f) / 16;
|
float velY = (rawVelY > 3.4f ? rawVelY : rawVelY < -3.4f ? rawVelY : 0.0f) / 16;
|
||||||
|
|
||||||
float value = Math.max(0.0f, Math.min(2.0f, state.isSwimming ? 0.0f : velX + velY));
|
float value = Math.max(0.0f, Math.min(2.0f, state.isVisuallySwimming ? 0.0f : velX + velY));
|
||||||
|
|
||||||
if (Config.cloakPhysics.value) {
|
if (Config.cloakPhysics.value) {
|
||||||
updateSegmentValues(segmentValues[0] + (value - segmentValues[0]) * 0.3f);
|
updateSegmentValues(segmentValues[0] + (value - segmentValues[0]) * 0.3f);
|
||||||
@@ -452,12 +534,15 @@ public class CloakFeatureRenderer extends FeatureRenderer<PlayerEntityRenderStat
|
|||||||
|
|
||||||
final int l = light;
|
final int l = light;
|
||||||
|
|
||||||
horizontalCurve = MathHelper.clamp(state.field_53537 / 150.0f, -1.0f, 1.0f);
|
horizontalCurve = Mth.clamp(state.capeLean / 150.0f, -1.0f, 1.0f);
|
||||||
|
|
||||||
queue.submitCustom(matrices, ShaderUtils.getRenderLayer(customCape), (entry, vertexConsumer) -> {
|
//? if >=1.21.9 {
|
||||||
renderCape(entry, vertexConsumer, state, l, OverlayTexture.DEFAULT_UV);
|
output.submitCustomGeometry(matrices, ShaderUtils.getRenderLayer(customCape), (entry, vertexConsumer) -> {
|
||||||
|
renderCape(entry, vertexConsumer, state, l, OverlayTexture.NO_OVERLAY);
|
||||||
});
|
});
|
||||||
|
//?} else
|
||||||
|
//renderCape(matrices.last(), output.getBuffer(ShaderUtils.getRenderLayer(customCape)), state, l, OverlayTexture.NO_OVERLAY);
|
||||||
|
|
||||||
matrices.pop();
|
matrices.popPose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
package org.saturnclient.impl.cosmetics;
|
||||||
|
|
||||||
|
import org.saturnclient.client.player.SaturnPlayer;
|
||||||
|
import org.saturnclient.impl.cosmetics.obj.MtlLoader;
|
||||||
|
import org.saturnclient.impl.cosmetics.obj.ObjModel;
|
||||||
|
import org.saturnclient.saturnclient.SaturnRenderState;
|
||||||
|
|
||||||
|
import net.minecraft.client.renderer.texture.OverlayTexture;
|
||||||
|
//? if >=1.21.9 {
|
||||||
|
import net.minecraft.client.renderer.SubmitNodeCollector;
|
||||||
|
//?} else
|
||||||
|
//import net.minecraft.client.renderer.MultiBufferSource;
|
||||||
|
import net.minecraft.client.renderer.entity.layers.RenderLayer;
|
||||||
|
import net.minecraft.client.renderer.entity.RenderLayerParent;
|
||||||
|
//? if >=1.21.11 {
|
||||||
|
import net.minecraft.client.model.player.PlayerModel;
|
||||||
|
//?} else
|
||||||
|
//import net.minecraft.client.model.PlayerModel;
|
||||||
|
import net.minecraft.client.renderer.entity.state.AvatarRenderState;
|
||||||
|
import com.mojang.blaze3d.vertex.PoseStack;
|
||||||
|
|
||||||
|
public class HatFeatureRenderer extends RenderLayer<AvatarRenderState, PlayerModel> {
|
||||||
|
public HatFeatureRenderer(RenderLayerParent<AvatarRenderState, PlayerModel> context) {
|
||||||
|
super(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
//? if >=1.21.9 {
|
||||||
|
public void submit(PoseStack matrices,
|
||||||
|
//?} else
|
||||||
|
//public void render(PoseStack matrices,
|
||||||
|
//? if >=1.21.9 {
|
||||||
|
SubmitNodeCollector output,
|
||||||
|
//?} else
|
||||||
|
//MultiBufferSource output,
|
||||||
|
int light, AvatarRenderState state, float limbAngle, float limbDistance) {
|
||||||
|
//? if >=1.21.8 {
|
||||||
|
SaturnPlayer player = state.getData(SaturnRenderState.saturnDataKey);
|
||||||
|
//?} else
|
||||||
|
//SaturnPlayer player = SaturnPlayer.get(state.name);
|
||||||
|
|
||||||
|
if (state.isInvisible || player == null || player.hat.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
matrices.pushPose();
|
||||||
|
|
||||||
|
// On the head, as vanilla places head items: the whole player (which emotes like the backflip
|
||||||
|
// turn), then the head's position, turn and scale (which crouching and emotes move)
|
||||||
|
PlayerModel model = this.getParentModel();
|
||||||
|
//? if >=1.21.5 {
|
||||||
|
model.root().translateAndRotate(matrices);
|
||||||
|
model.head.translateAndRotate(matrices);
|
||||||
|
//?} else {
|
||||||
|
/*model.root().translateAndRotate(matrices);
|
||||||
|
model.head.translateAndRotate(matrices);
|
||||||
|
*///?}
|
||||||
|
|
||||||
|
ObjModel.cosmetic("hat", player.hat).render(MtlLoader.cosmetic(player.hat),
|
||||||
|
matrices, output, light,
|
||||||
|
OverlayTexture.NO_OVERLAY);
|
||||||
|
|
||||||
|
matrices.popPose();
|
||||||
|
}
|
||||||
|
}
|
||||||
+5
-5
@@ -8,15 +8,15 @@ import java.util.Map;
|
|||||||
|
|
||||||
import de.javagl.obj.*;
|
import de.javagl.obj.*;
|
||||||
|
|
||||||
import net.minecraft.client.MinecraftClient;
|
import net.minecraft.client.Minecraft;
|
||||||
import net.minecraft.util.Identifier;
|
import net.minecraft.resources.Identifier;
|
||||||
|
|
||||||
public class MtlLoader {
|
public class MtlLoader {
|
||||||
static Map<Identifier, Map<String, Mtl>> loadedMtls = new HashMap<>();
|
static Map<Identifier, Map<String, Mtl>> loadedMtls = new HashMap<>();
|
||||||
|
|
||||||
static Map<String, Mtl> loadMtl(Identifier mtlId) throws IOException {
|
static Map<String, Mtl> loadMtl(Identifier mtlId) throws IOException {
|
||||||
try (InputStream is = MinecraftClient.getInstance().getResourceManager()
|
try (InputStream is = Minecraft.getInstance().getResourceManager()
|
||||||
.getResource(mtlId).get().getInputStream()) {
|
.getResource(mtlId).get().open()) {
|
||||||
List<Mtl> mtlList = MtlReader.read(is);
|
List<Mtl> mtlList = MtlReader.read(is);
|
||||||
Map<String, Mtl> mtlMap = new java.util.LinkedHashMap<>();
|
Map<String, Mtl> mtlMap = new java.util.LinkedHashMap<>();
|
||||||
for (Mtl mtl : mtlList) {
|
for (Mtl mtl : mtlList) {
|
||||||
@@ -51,6 +51,6 @@ public class MtlLoader {
|
|||||||
if (mtlId.isEmpty())
|
if (mtlId.isEmpty())
|
||||||
mtlId = "white";
|
mtlId = "white";
|
||||||
|
|
||||||
return of(Identifier.of("saturnclient", "models/cosmetic/materials/" + mtlId + ".mtl"));
|
return of(Identifier.fromNamespaceAndPath("saturnclient", "models/cosmetic/materials/" + mtlId + ".mtl"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+37
-21
@@ -12,12 +12,16 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
|||||||
|
|
||||||
import de.javagl.obj.Mtl;
|
import de.javagl.obj.Mtl;
|
||||||
import de.javagl.obj.Obj;
|
import de.javagl.obj.Obj;
|
||||||
import net.minecraft.client.MinecraftClient;
|
import net.minecraft.client.Minecraft;
|
||||||
import net.minecraft.client.render.VertexConsumerProvider;
|
//? if >=1.21.9 {
|
||||||
import net.minecraft.client.util.math.MatrixStack;
|
import net.minecraft.client.renderer.SubmitNodeCollector;
|
||||||
import net.minecraft.resource.Resource;
|
//?} else
|
||||||
import net.minecraft.resource.ResourceManager;
|
//import net.minecraft.client.renderer.MultiBufferSource;
|
||||||
import net.minecraft.util.Identifier;
|
import com.mojang.blaze3d.vertex.PoseStack;
|
||||||
|
import org.joml.Quaternionf;
|
||||||
|
import net.minecraft.server.packs.resources.Resource;
|
||||||
|
import net.minecraft.server.packs.resources.ResourceManager;
|
||||||
|
import net.minecraft.resources.Identifier;
|
||||||
|
|
||||||
public class ObjModel {
|
public class ObjModel {
|
||||||
private static final ObjectMapper MAPPER = new ObjectMapper();
|
private static final ObjectMapper MAPPER = new ObjectMapper();
|
||||||
@@ -43,7 +47,7 @@ public class ObjModel {
|
|||||||
Config config;
|
Config config;
|
||||||
|
|
||||||
public static ObjModel cosmetic(String cosmeticKind, String id) {
|
public static ObjModel cosmetic(String cosmeticKind, String id) {
|
||||||
return of(Identifier.of("saturnclient", "models/cosmetic/" + cosmeticKind + "/" + id.split("_")[0] + "/model"));
|
return of(Identifier.fromNamespaceAndPath("saturnclient", "models/cosmetic/" + cosmeticKind + "/" + id.split("_")[0] + "/model"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ObjModel of(Identifier obj) {
|
public static ObjModel of(Identifier obj) {
|
||||||
@@ -56,8 +60,8 @@ public class ObjModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ObjModel(Identifier identifier) {
|
ObjModel(Identifier identifier) {
|
||||||
Identifier objId = Identifier.of(identifier.getNamespace(), identifier.getPath() + ".obj");
|
Identifier objId = Identifier.fromNamespaceAndPath(identifier.getNamespace(), identifier.getPath() + ".obj");
|
||||||
Identifier jsonId = Identifier.of(identifier.getNamespace(), identifier.getPath() + ".json");
|
Identifier jsonId = Identifier.fromNamespaceAndPath(identifier.getNamespace(), identifier.getPath() + ".json");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.obj = ObjRenderer.loadObj(objId);
|
this.obj = ObjRenderer.loadObj(objId);
|
||||||
@@ -66,11 +70,11 @@ public class ObjModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Try loading JSON config if it exists
|
// Try loading JSON config if it exists
|
||||||
ResourceManager resourceManager = MinecraftClient.getInstance().getResourceManager();
|
ResourceManager resourceManager = Minecraft.getInstance().getResourceManager();
|
||||||
Optional<Resource> resource = resourceManager.getResource(jsonId);
|
Optional<Resource> resource = resourceManager.getResource(jsonId);
|
||||||
|
|
||||||
if (resource.isPresent()) {
|
if (resource.isPresent()) {
|
||||||
try (InputStream stream = resource.get().getInputStream()) {
|
try (InputStream stream = resource.get().open()) {
|
||||||
this.config = MAPPER.readValue(stream, Config.class);
|
this.config = MAPPER.readValue(stream, Config.class);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
@@ -78,25 +82,37 @@ public class ObjModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void render(Map<String, Mtl> mtlMap, MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light,
|
public void render(Map<String, Mtl> mtlMap, PoseStack matrices,
|
||||||
int overlay) {
|
//? if >=1.21.9 {
|
||||||
matrices.push();
|
SubmitNodeCollector output,
|
||||||
|
//?} else
|
||||||
|
//MultiBufferSource output,
|
||||||
|
int light, int overlay) {
|
||||||
|
matrices.pushPose();
|
||||||
|
|
||||||
matrices.multiply(net.minecraft.util.math.RotationAxis.POSITIVE_X.rotationDegrees(180.0f));
|
rotate(matrices, com.mojang.math.Axis.XP.rotationDegrees(180.0f));
|
||||||
matrices.multiply(net.minecraft.util.math.RotationAxis.POSITIVE_Y.rotationDegrees(180.0f));
|
rotate(matrices, com.mojang.math.Axis.YP.rotationDegrees(180.0f));
|
||||||
|
|
||||||
if (this.config != null) {
|
if (this.config != null) {
|
||||||
matrices.translate(config.transform.x, config.transform.y, config.transform.z);
|
matrices.translate(config.transform.x, config.transform.y, config.transform.z);
|
||||||
|
|
||||||
matrices.scale(config.scale.x, config.scale.y, config.scale.z);
|
matrices.scale(config.scale.x, config.scale.y, config.scale.z);
|
||||||
|
|
||||||
matrices.multiply(net.minecraft.util.math.RotationAxis.POSITIVE_X.rotationDegrees(config.rotation.x));
|
rotate(matrices, com.mojang.math.Axis.XP.rotationDegrees(config.rotation.x));
|
||||||
matrices.multiply(net.minecraft.util.math.RotationAxis.POSITIVE_Y.rotationDegrees(config.rotation.y));
|
rotate(matrices, com.mojang.math.Axis.YP.rotationDegrees(config.rotation.y));
|
||||||
matrices.multiply(net.minecraft.util.math.RotationAxis.POSITIVE_Z.rotationDegrees(config.rotation.z));
|
rotate(matrices, com.mojang.math.Axis.ZP.rotationDegrees(config.rotation.z));
|
||||||
}
|
}
|
||||||
|
|
||||||
ObjRenderer.renderObj(this.obj, mtlMap, matrices, vertexConsumers, light, overlay);
|
ObjRenderer.renderObj(this.obj, mtlMap, matrices, output, light, overlay);
|
||||||
|
|
||||||
matrices.pop();
|
matrices.popPose();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 26.3 renamed PoseStack's quaternion mulPose to rotate.
|
||||||
|
private static void rotate(PoseStack matrices, Quaternionf rotation) {
|
||||||
|
//? if >=26.3 {
|
||||||
|
/*matrices.rotate(rotation);
|
||||||
|
*///?} else
|
||||||
|
matrices.mulPose(rotation);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+43
-28
@@ -9,26 +9,35 @@ import org.saturnclient.impl.cosmetics.utils.ShaderUtils;
|
|||||||
|
|
||||||
import de.javagl.obj.*;
|
import de.javagl.obj.*;
|
||||||
|
|
||||||
import net.minecraft.client.MinecraftClient;
|
import net.minecraft.client.Minecraft;
|
||||||
import net.minecraft.client.render.RenderLayer;
|
//? if >=1.21.11 {
|
||||||
import net.minecraft.client.render.VertexConsumer;
|
import net.minecraft.client.renderer.rendertype.RenderType;
|
||||||
import net.minecraft.client.render.command.OrderedRenderCommandQueue;
|
//?} else
|
||||||
import net.minecraft.client.util.math.MatrixStack;
|
//import net.minecraft.client.renderer.RenderType;
|
||||||
import net.minecraft.util.Identifier;
|
import com.mojang.blaze3d.vertex.VertexConsumer;
|
||||||
|
//? if >=1.21.9 {
|
||||||
|
import net.minecraft.client.renderer.SubmitNodeCollector;
|
||||||
|
//?} else
|
||||||
|
//import net.minecraft.client.renderer.MultiBufferSource;
|
||||||
|
import com.mojang.blaze3d.vertex.PoseStack;
|
||||||
|
import net.minecraft.resources.Identifier;
|
||||||
|
|
||||||
public class ObjRenderer {
|
public class ObjRenderer {
|
||||||
|
|
||||||
public static Obj loadObj(Identifier objId) throws IOException {
|
public static Obj loadObj(Identifier objId) throws IOException {
|
||||||
try (InputStream is = MinecraftClient.getInstance().getResourceManager()
|
try (InputStream is = Minecraft.getInstance().getResourceManager()
|
||||||
.getResource(objId).get().getInputStream()) {
|
.getResource(objId).get().open()) {
|
||||||
return ObjReader.read(is);
|
return ObjReader.read(is);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void renderObj(Obj obj,
|
public static void renderObj(Obj obj,
|
||||||
Map<String, Mtl> mtlMap,
|
Map<String, Mtl> mtlMap,
|
||||||
MatrixStack matrices,
|
PoseStack matrices,
|
||||||
OrderedRenderCommandQueue queue,
|
//? if >=1.21.9 {
|
||||||
|
SubmitNodeCollector output,
|
||||||
|
//?} else
|
||||||
|
//MultiBufferSource output,
|
||||||
int light,
|
int light,
|
||||||
int overlay) {
|
int overlay) {
|
||||||
|
|
||||||
@@ -59,29 +68,35 @@ public class ObjRenderer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
RenderLayer layer = ShaderUtils.getRenderLayer(texture);
|
RenderType layer = ShaderUtils.getRenderLayer(texture);
|
||||||
|
|
||||||
final float fr = r, fg = g, fb = b;
|
final float fr = r, fg = g, fb = b;
|
||||||
final Obj finalObj = groupObj;
|
final Obj finalObj = groupObj;
|
||||||
|
|
||||||
queue.submitCustom(matrices, layer, (e, vertexConsumer) -> {
|
//? if >=1.21.9 {
|
||||||
|
output.submitCustomGeometry(matrices, layer,
|
||||||
|
(entry, consumer) -> drawGroup(finalObj, consumer, entry, light, overlay, fr, fg, fb));
|
||||||
|
//?} else
|
||||||
|
//drawGroup(finalObj, output.getBuffer(layer), matrices.last(), light, overlay, fr, fg, fb);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
int faces = finalObj.getNumFaces();
|
private static void drawGroup(Obj obj, VertexConsumer consumer, PoseStack.Pose entry, int light, int overlay,
|
||||||
|
float r, float g, float b) {
|
||||||
|
int faces = obj.getNumFaces();
|
||||||
|
|
||||||
for (int f = 0; f < faces; f++) {
|
for (int f = 0; f < faces; f++) {
|
||||||
ObjFace face = finalObj.getFace(f);
|
ObjFace face = obj.getFace(f);
|
||||||
int verts = face.getNumVertices();
|
int verts = face.getNumVertices();
|
||||||
|
|
||||||
for (int i = verts - 1; i >= 0; i--) {
|
for (int i = verts - 1; i >= 0; i--) {
|
||||||
writeVertex(finalObj, face, i, vertexConsumer, e, light, overlay, fr, fg, fb);
|
writeVertex(obj, face, i, consumer, entry, light, overlay, r, g, b);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void writeVertex(Obj obj, ObjFace face, int index, VertexConsumer consumer,
|
private static void writeVertex(Obj obj, ObjFace face, int index, VertexConsumer consumer,
|
||||||
MatrixStack.Entry matrix, int light, int overlay,
|
PoseStack.Pose matrix, int light, int overlay,
|
||||||
float r, float g, float b) {
|
float r, float g, float b) {
|
||||||
|
|
||||||
FloatTuple pos = obj.getVertex(face.getVertexIndex(index));
|
FloatTuple pos = obj.getVertex(face.getVertexIndex(index));
|
||||||
@@ -106,11 +121,11 @@ public class ObjRenderer {
|
|||||||
v = 1f - tc.getY(); // Flip V: OBJ bottom-left origin -> Minecraft top-left
|
v = 1f - tc.getY(); // Flip V: OBJ bottom-left origin -> Minecraft top-left
|
||||||
}
|
}
|
||||||
|
|
||||||
consumer.vertex(matrix, x, y, z)
|
consumer.addVertex(matrix, x, y, z)
|
||||||
.color(r, g, b, 1f)
|
.setColor(r, g, b, 1f)
|
||||||
.texture(u, v)
|
.setUv(u, v)
|
||||||
.overlay(overlay)
|
.setOverlay(overlay)
|
||||||
.light(light)
|
.setLight(light)
|
||||||
.normal(matrix, nx, ny, nz);
|
.setNormal(matrix, nx, ny, nz);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+10
-7
@@ -3,9 +3,9 @@ package org.saturnclient.impl.cosmetics.utils;
|
|||||||
import org.saturnclient.common.ref.asset.IdentifierRef;
|
import org.saturnclient.common.ref.asset.IdentifierRef;
|
||||||
import org.saturnclient.saturnclient.SaturnClient;
|
import org.saturnclient.saturnclient.SaturnClient;
|
||||||
|
|
||||||
import net.minecraft.client.texture.NativeImage;
|
import com.mojang.blaze3d.platform.NativeImage;
|
||||||
import net.minecraft.client.texture.NativeImageBackedTexture;
|
import net.minecraft.client.renderer.texture.DynamicTexture;
|
||||||
import net.minecraft.util.Identifier;
|
import net.minecraft.resources.Identifier;
|
||||||
|
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
import java.awt.image.DataBufferInt;
|
import java.awt.image.DataBufferInt;
|
||||||
@@ -28,7 +28,7 @@ public class IdentifierUtils {
|
|||||||
int index = 0;
|
int index = 0;
|
||||||
for (int y = 0; y < height; y++) {
|
for (int y = 0; y < height; y++) {
|
||||||
for (int x = 0; x < width; x++) {
|
for (int x = 0; x < width; x++) {
|
||||||
nativeImage.setColorArgb(x, y, pixels[index++]);
|
nativeImage.setPixel(x, y, pixels[index++]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,18 +36,21 @@ public class IdentifierUtils {
|
|||||||
|
|
||||||
for (int y = 0; y < height; y++) {
|
for (int y = 0; y < height; y++) {
|
||||||
for (int x = 0; x < width; x++) {
|
for (int x = 0; x < width; x++) {
|
||||||
nativeImage.setColorArgb(x, y, bufferedImage.getRGB(x, y));
|
nativeImage.setPixel(x, y, bufferedImage.getRGB(x, y));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SaturnClient.client.execute(() -> {
|
SaturnClient.client.execute(() -> {
|
||||||
NativeImageBackedTexture texture = new NativeImageBackedTexture(
|
//? if >=1.21.5 {
|
||||||
|
DynamicTexture texture = new DynamicTexture(
|
||||||
() -> identifier.toString(),
|
() -> identifier.toString(),
|
||||||
nativeImage);
|
nativeImage);
|
||||||
|
//?} else
|
||||||
|
//DynamicTexture texture = new DynamicTexture(nativeImage);
|
||||||
|
|
||||||
SaturnClient.client.getTextureManager().registerTexture(identifier, texture);
|
SaturnClient.client.getTextureManager().register(identifier, texture);
|
||||||
});
|
});
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
package org.saturnclient.impl.cosmetics.utils;
|
||||||
|
|
||||||
|
import org.saturnclient.common.ref.asset.IdentifierRef;
|
||||||
|
import org.saturnclient.cosmetics.utils.GifUtils;
|
||||||
|
|
||||||
|
//? if >=1.21.5
|
||||||
|
import com.mojang.blaze3d.pipeline.RenderPipeline;
|
||||||
|
|
||||||
|
//? if >=1.21.5
|
||||||
|
import net.minecraft.client.renderer.RenderPipelines;
|
||||||
|
//? if >=1.21.11 {
|
||||||
|
import net.minecraft.client.renderer.rendertype.RenderType;
|
||||||
|
//?} else
|
||||||
|
//import net.minecraft.client.renderer.RenderType;
|
||||||
|
//? if <1.21.5 {
|
||||||
|
/*import net.minecraft.client.renderer.RenderStateShard;
|
||||||
|
import com.mojang.blaze3d.vertex.VertexFormat.Mode;
|
||||||
|
import com.mojang.blaze3d.vertex.DefaultVertexFormat;
|
||||||
|
*///?}
|
||||||
|
//? if >=1.21.11 {
|
||||||
|
import net.minecraft.client.renderer.rendertype.RenderTypes;
|
||||||
|
import net.minecraft.client.renderer.rendertype.RenderSetup;
|
||||||
|
import net.minecraft.client.renderer.blockentity.AbstractEndPortalRenderer;
|
||||||
|
//?} else {
|
||||||
|
/*import net.minecraft.client.renderer.RenderStateShard.MultiTextureStateShard;
|
||||||
|
import net.minecraft.client.renderer.blockentity.TheEndPortalRenderer;
|
||||||
|
*///?}
|
||||||
|
import net.minecraft.resources.Identifier;
|
||||||
|
//? if >=26.3
|
||||||
|
//import com.mojang.renderpearl.api.pipeline.ColorTargetState;
|
||||||
|
//? if >=26.1 {
|
||||||
|
/*import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
*///?}
|
||||||
|
|
||||||
|
public class ShaderUtils {
|
||||||
|
// Render pipelines exist from 1.21.5; before that the layer names a shader program directly.
|
||||||
|
//? if >=1.21.5 {
|
||||||
|
public static final RenderPipeline END_GATEWAY_PIPELINE = RenderPipelines.register(
|
||||||
|
RenderPipeline.builder(new RenderPipeline.Snippet[] {
|
||||||
|
RenderPipelines.END_PORTAL_SNIPPET })
|
||||||
|
.withLocation("pipeline/end_gateway")
|
||||||
|
.withShaderDefine("PORTAL_LAYERS", 16)
|
||||||
|
.withCull(false)
|
||||||
|
.build());
|
||||||
|
//?}
|
||||||
|
|
||||||
|
//? if >=1.21.11 {
|
||||||
|
public static final RenderType END_GATEWAY = RenderType.create("end_gateway",
|
||||||
|
RenderSetup.builder(END_GATEWAY_PIPELINE)
|
||||||
|
.withTexture("Sampler0", AbstractEndPortalRenderer.END_SKY_LOCATION)
|
||||||
|
.withTexture("Sampler1", AbstractEndPortalRenderer.END_PORTAL_LOCATION).createRenderSetup());
|
||||||
|
//?} else if >=1.21.6 {
|
||||||
|
/*public static final RenderType END_GATEWAY = RenderType.create("end_gateway", 1536, false, false,
|
||||||
|
END_GATEWAY_PIPELINE,
|
||||||
|
RenderType.CompositeState.builder()
|
||||||
|
.setTextureState(MultiTextureStateShard.builder().add(TheEndPortalRenderer.END_SKY_LOCATION, false)
|
||||||
|
.add(TheEndPortalRenderer.END_PORTAL_LOCATION, false).build())
|
||||||
|
.createCompositeState(false));
|
||||||
|
*///?} else if >=1.21.5 {
|
||||||
|
/*public static final RenderType END_GATEWAY = RenderType.create("end_gateway", 1536, false, false,
|
||||||
|
END_GATEWAY_PIPELINE,
|
||||||
|
RenderType.CompositeState.builder()
|
||||||
|
.setTextureState(MultiTextureStateShard.builder().add(TheEndPortalRenderer.END_SKY_LOCATION, false, false)
|
||||||
|
.add(TheEndPortalRenderer.END_PORTAL_LOCATION, false, false).build())
|
||||||
|
.createCompositeState(false));
|
||||||
|
*///?} else {
|
||||||
|
/*public static final RenderType END_GATEWAY = RenderType.create(
|
||||||
|
"end_gateway",
|
||||||
|
DefaultVertexFormat.POSITION,
|
||||||
|
Mode.QUADS,
|
||||||
|
1536,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
RenderType.CompositeState.builder()
|
||||||
|
.setShaderState(RenderType.RENDERTYPE_END_GATEWAY_SHADER)
|
||||||
|
.setTextureState(MultiTextureStateShard.builder()
|
||||||
|
.add(TheEndPortalRenderer.END_SKY_LOCATION, false, false)
|
||||||
|
.add(TheEndPortalRenderer.END_PORTAL_LOCATION, false, false)
|
||||||
|
.build())
|
||||||
|
.setCullState(RenderStateShard.NO_CULL)
|
||||||
|
.createCompositeState(false));
|
||||||
|
*///?}
|
||||||
|
|
||||||
|
public static RenderType getRenderLayer(IdentifierRef texture) {
|
||||||
|
String path = texture.toString();
|
||||||
|
if (path.endsWith(".gif")) {
|
||||||
|
return entityAlpha((Identifier) (Object) GifUtils.get(texture));
|
||||||
|
} else if (path.endsWith("end.png")) {
|
||||||
|
return END_GATEWAY;
|
||||||
|
} else {
|
||||||
|
return entityAlpha((Identifier) (Object) texture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cosmetics are drawn unlit, without the lightmap or the directional shading, as the dragon's
|
||||||
|
// alpha pipeline drew them before 26.1. 26.1 dropped that pipeline, so this is the entity shader
|
||||||
|
// with its lighting and overlay turned off.
|
||||||
|
//? if >=26.1 {
|
||||||
|
/*public static final RenderPipeline UNLIT_CUTOUT_PIPELINE = RenderPipelines.register(
|
||||||
|
RenderPipeline.builder(new RenderPipeline.Snippet[] {
|
||||||
|
RenderPipelines.ENTITY_EMISSIVE_SNIPPET })
|
||||||
|
.withLocation(Identifier.fromNamespaceAndPath("saturnclient", "pipeline/unlit_cutout"))
|
||||||
|
.withShaderDefine("ALPHA_CUTOUT", 0.1f)
|
||||||
|
.withShaderDefine("NO_CARDINAL_LIGHTING")
|
||||||
|
.withShaderDefine("NO_OVERLAY")
|
||||||
|
.withCull(false)
|
||||||
|
// From 26.3 the snippet leaves the color target to each pipeline.
|
||||||
|
//? if >=26.3
|
||||||
|
//.withColorTargetState(ColorTargetState.DEFAULT)
|
||||||
|
.build());
|
||||||
|
private static final Map<Identifier, RenderType> UNLIT_CUTOUT = new HashMap<>();
|
||||||
|
*///?}
|
||||||
|
|
||||||
|
private static RenderType entityAlpha(Identifier texture) {
|
||||||
|
//? if >=26.1 {
|
||||||
|
/*return UNLIT_CUTOUT.computeIfAbsent(texture, id -> RenderType.create("saturn_unlit_cutout",
|
||||||
|
RenderSetup.builder(UNLIT_CUTOUT_PIPELINE).withTexture("Sampler0", id).createRenderSetup()));
|
||||||
|
*///?} else if >=1.21.11 {
|
||||||
|
return RenderTypes.dragonExplosionAlpha(texture);
|
||||||
|
//?} else
|
||||||
|
//return RenderType.dragonExplosionAlpha(texture);
|
||||||
|
}
|
||||||
|
}
|
||||||
+12
-13
@@ -2,32 +2,31 @@ package org.saturnclient.impl.features.entity;
|
|||||||
|
|
||||||
import org.saturnclient.common.feature.EntityFeature;
|
import org.saturnclient.common.feature.EntityFeature;
|
||||||
|
|
||||||
import net.minecraft.client.MinecraftClient;
|
import net.minecraft.client.Minecraft;
|
||||||
import net.minecraft.entity.Entity;
|
import net.minecraft.world.entity.Entity;
|
||||||
import net.minecraft.util.hit.EntityHitResult;
|
import net.minecraft.world.phys.EntityHitResult;
|
||||||
import net.minecraft.util.hit.HitResult;
|
import net.minecraft.world.phys.HitResult;
|
||||||
import net.minecraft.entity.LivingEntity;
|
import net.minecraft.world.entity.LivingEntity;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fabric implementation of {@link EntityFeature}.
|
* Fabric implementation of {@link EntityFeature}.
|
||||||
*
|
*
|
||||||
* Crosshair targeting is resolved via {@link MinecraftClient#crosshairTarget}.
|
* Crosshair targeting is resolved via {@link MinecraftClient#crosshairTarget}.
|
||||||
* Entity state for nametag rendering is supplied at render time by
|
* Entity state for nametags is built by LivingEntityRendererMixin, which
|
||||||
* {@link org.saturnclient.modules.mixins.LivingEntityRendererMixin} which
|
* writes health into the render state and then constructs a
|
||||||
* writes into the render state; {@link NametagsMixin} then constructs
|
* {@link RenderStateEntityState} from that snapshot for NametagsMod.
|
||||||
* a {@link RenderStateEntityState} from the already-extracted snapshot.
|
|
||||||
*/
|
*/
|
||||||
public class EntityFeatureImpl implements EntityFeature {
|
public class EntityFeatureImpl implements EntityFeature {
|
||||||
|
|
||||||
private final MinecraftClient mc;
|
private final Minecraft mc;
|
||||||
|
|
||||||
public EntityFeatureImpl(MinecraftClient mc) {
|
public EntityFeatureImpl(Minecraft mc) {
|
||||||
this.mc = mc;
|
this.mc = mc;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isTargetingLivingEntity() {
|
public boolean isTargetingLivingEntity() {
|
||||||
HitResult target = mc.crosshairTarget;
|
HitResult target = mc.hitResult;
|
||||||
if (target == null || target.getType() != HitResult.Type.ENTITY)
|
if (target == null || target.getType() != HitResult.Type.ENTITY)
|
||||||
return false;
|
return false;
|
||||||
Entity hit = ((EntityHitResult) target).getEntity();
|
Entity hit = ((EntityHitResult) target).getEntity();
|
||||||
@@ -43,7 +42,7 @@ public class EntityFeatureImpl implements EntityFeature {
|
|||||||
* Minecraft render-state object) as a platform-neutral
|
* Minecraft render-state object) as a platform-neutral
|
||||||
* {@link EntityState} for use by {@link NametagsFeature}.
|
* {@link EntityState} for use by {@link NametagsFeature}.
|
||||||
*
|
*
|
||||||
* This is constructed inside {@link NametagsMixin} where both the
|
* This is constructed in LivingEntityRendererMixin, where both the
|
||||||
* render-state mixin interface and the entity name are available.
|
* render-state mixin interface and the entity name are available.
|
||||||
*/
|
*/
|
||||||
public static final class RenderStateEntityState implements EntityState {
|
public static final class RenderStateEntityState implements EntityState {
|
||||||
+2
-2
@@ -7,8 +7,8 @@ import org.saturnclient.common.feature.EntityFeature;
|
|||||||
* {@link net.minecraft.client.render.entity.state.LivingEntityRenderState}.
|
* {@link net.minecraft.client.render.entity.state.LivingEntityRenderState}.
|
||||||
*
|
*
|
||||||
* Carries the entity's health snapshot and classification into the render
|
* Carries the entity's health snapshot and classification into the render
|
||||||
* pipeline so that {@link NametagsMixin} can read them without touching
|
* render state, so NametagsMod's replacement nametag (applied in
|
||||||
* the live entity again.
|
* LivingEntityRendererMixin) can use them without touching the live entity again.
|
||||||
*/
|
*/
|
||||||
public interface HealthRenderState {
|
public interface HealthRenderState {
|
||||||
|
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
package org.saturnclient.impl.features.mixins.entity;
|
||||||
|
|
||||||
|
import org.saturnclient.impl.features.entity.CameraOverriddenEntity;
|
||||||
|
import org.saturnclient.impl.features.render.ZoomFov;
|
||||||
|
import org.saturnclient.mod.mods.FreelookMod;
|
||||||
|
import org.saturnclient.saturnclient.SaturnClient;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.Shadow;
|
||||||
|
import org.spongepowered.asm.mixin.Unique;
|
||||||
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
|
import org.spongepowered.asm.mixin.injection.Inject;
|
||||||
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||||
|
//? if >=26.1
|
||||||
|
//import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||||
|
|
||||||
|
import net.minecraft.client.player.LocalPlayer;
|
||||||
|
import net.minecraft.client.Camera;
|
||||||
|
import net.minecraft.world.entity.Entity;
|
||||||
|
//? if >=1.21.11 {
|
||||||
|
import net.minecraft.world.level.Level;
|
||||||
|
//?} else
|
||||||
|
//import net.minecraft.world.level.BlockGetter;
|
||||||
|
|
||||||
|
@Mixin(Camera.class)
|
||||||
|
public abstract class CameraMixin {
|
||||||
|
@Unique
|
||||||
|
boolean firstTime = true;
|
||||||
|
|
||||||
|
@Shadow
|
||||||
|
protected abstract void setRotation(float yaw, float pitch);
|
||||||
|
|
||||||
|
// Freelook keeps the camera's rotation after vanilla points it along the entity's view. From 26.1
|
||||||
|
// that happens in alignWithEntity, which takes the camera's own entity.
|
||||||
|
//? if >=26.1 {
|
||||||
|
/*@Shadow
|
||||||
|
public abstract Entity entity();
|
||||||
|
|
||||||
|
@Inject(method = "alignWithEntity", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/Camera;setRotation(FF)V", ordinal = 1, shift = At.Shift.AFTER))
|
||||||
|
public void lockRotation(float tickDelta, CallbackInfo ci) {
|
||||||
|
saturn$lockRotation(entity());
|
||||||
|
}
|
||||||
|
|
||||||
|
// The camera works out the FOV from 26.1 (GameRenderer.getFov before).
|
||||||
|
@Inject(method = "calculateFov", at = @At("RETURN"), cancellable = true)
|
||||||
|
private void onCalculateFov(float tickDelta, CallbackInfoReturnable<Float> cir) {
|
||||||
|
cir.setReturnValue(ZoomFov.apply(cir.getReturnValue()));
|
||||||
|
}
|
||||||
|
*///?} else if >=1.21.11 {
|
||||||
|
@Inject(method = "setup", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/Camera;setRotation(FF)V", ordinal = 1, shift = At.Shift.AFTER))
|
||||||
|
public void lockRotation(Level focusedBlock, Entity cameraEntity, boolean isThirdPerson, boolean isFrontFacing,
|
||||||
|
float tickDelta, CallbackInfo ci) {
|
||||||
|
saturn$lockRotation(cameraEntity);
|
||||||
|
}
|
||||||
|
//?} else {
|
||||||
|
/*@Inject(method = "setup", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/Camera;setRotation(FF)V", ordinal = 1, shift = At.Shift.AFTER))
|
||||||
|
public void lockRotation(BlockGetter focusedBlock, Entity cameraEntity, boolean isThirdPerson, boolean isFrontFacing,
|
||||||
|
float tickDelta, CallbackInfo ci) {
|
||||||
|
saturn$lockRotation(cameraEntity);
|
||||||
|
}
|
||||||
|
*///?}
|
||||||
|
|
||||||
|
@Unique
|
||||||
|
private void saturn$lockRotation(Entity cameraEntity) {
|
||||||
|
if (FreelookMod.isFreeLooking() && cameraEntity instanceof LocalPlayer) {
|
||||||
|
CameraOverriddenEntity cameraOverriddenEntity = (CameraOverriddenEntity) cameraEntity;
|
||||||
|
|
||||||
|
if (firstTime && SaturnClient.client.player != null) {
|
||||||
|
cameraOverriddenEntity.freelook$setCameraPitch(SaturnClient.client.player.getXRot());
|
||||||
|
cameraOverriddenEntity.freelook$setCameraYaw(SaturnClient.client.player.getYRot());
|
||||||
|
firstTime = false;
|
||||||
|
}
|
||||||
|
this.setRotation(cameraOverriddenEntity.freelook$getCameraYaw(),
|
||||||
|
cameraOverriddenEntity.freelook$getCameraPitch());
|
||||||
|
|
||||||
|
}
|
||||||
|
if (!FreelookMod.isFreeLooking() && cameraEntity instanceof LocalPlayer) {
|
||||||
|
firstTime = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+6
-6
@@ -8,9 +8,9 @@ import org.spongepowered.asm.mixin.injection.At;
|
|||||||
import org.spongepowered.asm.mixin.injection.Inject;
|
import org.spongepowered.asm.mixin.injection.Inject;
|
||||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||||
|
|
||||||
import net.minecraft.client.network.ClientPlayerEntity;
|
import net.minecraft.client.player.LocalPlayer;
|
||||||
import net.minecraft.entity.Entity;
|
import net.minecraft.world.entity.Entity;
|
||||||
import net.minecraft.util.math.MathHelper;
|
import net.minecraft.util.Mth;
|
||||||
|
|
||||||
@Mixin(Entity.class)
|
@Mixin(Entity.class)
|
||||||
public class EntityMixin implements CameraOverriddenEntity {
|
public class EntityMixin implements CameraOverriddenEntity {
|
||||||
@@ -20,15 +20,15 @@ public class EntityMixin implements CameraOverriddenEntity {
|
|||||||
@Unique
|
@Unique
|
||||||
private float cameraYaw;
|
private float cameraYaw;
|
||||||
|
|
||||||
@Inject(method = "changeLookDirection", at = @At("HEAD"), cancellable = true)
|
@Inject(method = "turn", at = @At("HEAD"), cancellable = true)
|
||||||
public void changeCameraLookDirection(double xDelta, double yDelta, CallbackInfo ci) {
|
public void changeCameraLookDirection(double xDelta, double yDelta, CallbackInfo ci) {
|
||||||
// noinspection ConstantValue// IntelliJ is incorrect here, this code block is
|
// noinspection ConstantValue// IntelliJ is incorrect here, this code block is
|
||||||
// reachable
|
// reachable
|
||||||
if (FreelookMod.isFreeLooking() && (Object) this instanceof ClientPlayerEntity) {
|
if (FreelookMod.isFreeLooking() && (Object) this instanceof LocalPlayer) {
|
||||||
double pitchDelta = (yDelta * 0.15);
|
double pitchDelta = (yDelta * 0.15);
|
||||||
double yawDelta = (xDelta * 0.15);
|
double yawDelta = (xDelta * 0.15);
|
||||||
|
|
||||||
this.cameraPitch = MathHelper.clamp(this.cameraPitch + (float) pitchDelta, -90.0f, 90.0f);
|
this.cameraPitch = Mth.clamp(this.cameraPitch + (float) pitchDelta, -90.0f, 90.0f);
|
||||||
this.cameraYaw += (float) yawDelta;
|
this.cameraYaw += (float) yawDelta;
|
||||||
|
|
||||||
ci.cancel();
|
ci.cancel();
|
||||||
+5
-2
@@ -1,6 +1,9 @@
|
|||||||
package org.saturnclient.impl.features.mixins.render;
|
package org.saturnclient.impl.features.mixins.render;
|
||||||
|
|
||||||
import net.minecraft.client.render.fog.FogRenderer;
|
//? if >=1.21.6 {
|
||||||
|
import net.minecraft.client.renderer.fog.FogRenderer;
|
||||||
|
//?} else
|
||||||
|
//import net.minecraft.client.renderer.FogRenderer;
|
||||||
|
|
||||||
import org.saturnclient.mod.mods.NoFogMod;
|
import org.saturnclient.mod.mods.NoFogMod;
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
@@ -10,7 +13,7 @@ import org.spongepowered.asm.mixin.injection.ModifyVariable;
|
|||||||
@Mixin(FogRenderer.class)
|
@Mixin(FogRenderer.class)
|
||||||
public class BackgroundRendererMixin {
|
public class BackgroundRendererMixin {
|
||||||
// fog
|
// fog
|
||||||
@ModifyVariable(method = "applyFog", at = @At("HEAD"), ordinal = 0, argsOnly = true)
|
@ModifyVariable(method = "setupFog", at = @At("HEAD"), ordinal = 0, argsOnly = true)
|
||||||
private static float modifyFogStart(float viewDistance) {
|
private static float modifyFogStart(float viewDistance) {
|
||||||
if (NoFogMod.isActive()) {
|
if (NoFogMod.isActive()) {
|
||||||
return viewDistance * 2.0f;
|
return viewDistance * 2.0f;
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
package org.saturnclient.impl.features.mixins.render;
|
||||||
|
|
||||||
|
//? if >=1.21.6
|
||||||
|
import net.minecraft.client.Minecraft;
|
||||||
|
//? if >=1.21.6
|
||||||
|
import net.minecraft.client.Options;
|
||||||
|
import net.minecraft.client.renderer.GameRenderer;
|
||||||
|
|
||||||
|
//? if >=1.21.6
|
||||||
|
import org.saturnclient.impl.ui.SaturnScreenFabric;
|
||||||
|
//? if >=1.21.6
|
||||||
|
import org.saturnclient.impl.ui.Screens;
|
||||||
|
import org.saturnclient.impl.features.render.ZoomFov;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
|
import org.spongepowered.asm.mixin.injection.Inject;
|
||||||
|
//? if >=1.21.6
|
||||||
|
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||||
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Overrides the game's field-of-view when {@link org.saturnclient.mod.mods.ZoomMod} is active.
|
||||||
|
*
|
||||||
|
* The old code checked both {@code Zoom.isZooming} and
|
||||||
|
* {@code Zoom.shouldZoom()} separately. The refactored
|
||||||
|
* {@link org.saturnclient.mod.mods.ZoomMod#shouldZoom()} already ORs both conditions
|
||||||
|
* ({@code enabled.value && isZooming}), so only one check is needed.
|
||||||
|
*
|
||||||
|
* No {@link org.saturnclient.common.module.ModProvider} is required
|
||||||
|
* here because zooming is a pure render-time override with no per-tick
|
||||||
|
* engine queries.
|
||||||
|
*/
|
||||||
|
@Mixin(GameRenderer.class)
|
||||||
|
public class GameRendererMixin {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* After the vanilla FOV has been computed, divide it by the zoom
|
||||||
|
* level to produce the narrowed field-of-view.
|
||||||
|
*/
|
||||||
|
// From 26.1 the camera works out the FOV, so CameraMixin applies the zoom there.
|
||||||
|
//? if <26.1 {
|
||||||
|
@Inject(method = "getFov", at = @At("RETURN"), cancellable = true)
|
||||||
|
private void onGetFov(CallbackInfoReturnable<Float> cir) {
|
||||||
|
cir.setReturnValue(ZoomFov.apply(cir.getReturnValue()));
|
||||||
|
}
|
||||||
|
//?}
|
||||||
|
|
||||||
|
//? if >=1.21.6 {
|
||||||
|
// From 1.21.6 the menu blur's strength is the vanilla setting, read here every frame. While a
|
||||||
|
// Saturn screen is open, it's Saturn's background blur instead, faded in with the screen.
|
||||||
|
// From 26.1 the setting is read while extracting the options' render state.
|
||||||
|
//? if >=26.1
|
||||||
|
//@Redirect(method = "extractOptions", at = @At(value = "INVOKE",
|
||||||
|
//? if <26.1
|
||||||
|
@Redirect(method = "render", at = @At(value = "INVOKE",
|
||||||
|
target = "Lnet/minecraft/client/Options;getMenuBackgroundBlurriness()I"))
|
||||||
|
private int useSaturnBlur(Options options) {
|
||||||
|
if (Screens.current() instanceof SaturnScreenFabric saturnScreen) {
|
||||||
|
return saturnScreen.screen.getBackgroundBlur();
|
||||||
|
}
|
||||||
|
|
||||||
|
return options.getMenuBackgroundBlurriness();
|
||||||
|
}
|
||||||
|
//?}
|
||||||
|
}
|
||||||
+13
-3
@@ -3,7 +3,10 @@ package org.saturnclient.impl.features.mixins.render;
|
|||||||
// Code approach adapted from Gamma Utils: https://github.com/Sjouwer/gamma-utils
|
// Code approach adapted from Gamma Utils: https://github.com/Sjouwer/gamma-utils
|
||||||
|
|
||||||
import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
|
import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
|
||||||
import net.minecraft.client.render.LightmapTextureManager;
|
//? if >=26.1 {
|
||||||
|
/*import net.minecraft.client.renderer.LightmapRenderStateExtractor;
|
||||||
|
*///?} else
|
||||||
|
import net.minecraft.client.renderer.LightTexture;
|
||||||
|
|
||||||
import org.saturnclient.mod.mods.FullbrightMod;
|
import org.saturnclient.mod.mods.FullbrightMod;
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
@@ -18,14 +21,21 @@ import org.spongepowered.asm.mixin.injection.At;
|
|||||||
* it is now {@link FullbrightMod}. The injection target and
|
* it is now {@link FullbrightMod}. The injection target and
|
||||||
* strategy are unchanged.
|
* strategy are unchanged.
|
||||||
*/
|
*/
|
||||||
@Mixin(LightmapTextureManager.class)
|
// From 26.1 the gamma is worked out while extracting the lightmap's render state.
|
||||||
|
//? if >=26.1 {
|
||||||
|
/*@Mixin(LightmapRenderStateExtractor.class)
|
||||||
|
*///?} else
|
||||||
|
@Mixin(LightTexture.class)
|
||||||
public class LightmapTextureManagerMixin {
|
public class LightmapTextureManagerMixin {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If fullbright is active, substitute the feature's brightness
|
* If fullbright is active, substitute the feature's brightness
|
||||||
* value for whatever vanilla calculated.
|
* value for whatever vanilla calculated.
|
||||||
*/
|
*/
|
||||||
@ModifyExpressionValue(method = "update", at = @At(value = "INVOKE", target = "Ljava/lang/Math;max(FF)F", ordinal = 0))
|
//? if >=26.1 {
|
||||||
|
/*@ModifyExpressionValue(method = "extract", at = @At(value = "INVOKE", target = "Ljava/lang/Math;max(FF)F", ordinal = 0))
|
||||||
|
*///?} else
|
||||||
|
@ModifyExpressionValue(method = "updateLightTexture", at = @At(value = "INVOKE", target = "Ljava/lang/Math;max(FF)F", ordinal = 0))
|
||||||
private float modifyGamma(float original) {
|
private float modifyGamma(float original) {
|
||||||
if (FullbrightMod.shouldOverrideBrightness()) {
|
if (FullbrightMod.shouldOverrideBrightness()) {
|
||||||
return FullbrightMod.getBrightnessValue();
|
return FullbrightMod.getBrightnessValue();
|
||||||
+3
-3
@@ -1,6 +1,6 @@
|
|||||||
package org.saturnclient.impl.features.mixins.render;
|
package org.saturnclient.impl.features.mixins.render;
|
||||||
|
|
||||||
import net.minecraft.client.render.entity.state.LivingEntityRenderState;
|
import net.minecraft.client.renderer.entity.state.LivingEntityRenderState;
|
||||||
|
|
||||||
import org.saturnclient.common.feature.EntityFeature;
|
import org.saturnclient.common.feature.EntityFeature;
|
||||||
import org.saturnclient.impl.features.entity.HealthRenderState;
|
import org.saturnclient.impl.features.entity.HealthRenderState;
|
||||||
@@ -18,8 +18,8 @@ import org.spongepowered.asm.mixin.Unique;
|
|||||||
*
|
*
|
||||||
* The stored values are written by
|
* The stored values are written by
|
||||||
* {@link LivingEntityRendererMixin#saturn$captureHealth} each time a
|
* {@link LivingEntityRendererMixin#saturn$captureHealth} each time a
|
||||||
* render state is extracted from a live entity, and read by
|
* render state is extracted from a live entity, and read there right away for
|
||||||
* {@link NametagsMixin} at render time.
|
* NametagsMod's replacement nametag.
|
||||||
*/
|
*/
|
||||||
@Mixin(LivingEntityRenderState.class)
|
@Mixin(LivingEntityRenderState.class)
|
||||||
public class LivingEntityRenderStateMixin implements HealthRenderState {
|
public class LivingEntityRenderStateMixin implements HealthRenderState {
|
||||||
+62
@@ -0,0 +1,62 @@
|
|||||||
|
package org.saturnclient.impl.features.mixins.render;
|
||||||
|
|
||||||
|
import net.minecraft.client.renderer.entity.LivingEntityRenderer;
|
||||||
|
import net.minecraft.client.renderer.entity.state.LivingEntityRenderState;
|
||||||
|
import net.minecraft.world.entity.LivingEntity;
|
||||||
|
import net.minecraft.world.entity.monster.Monster;
|
||||||
|
import net.minecraft.world.entity.AgeableMob;
|
||||||
|
import net.minecraft.world.entity.player.Player;
|
||||||
|
import net.minecraft.network.chat.Component;
|
||||||
|
|
||||||
|
import org.saturnclient.common.feature.EntityFeature;
|
||||||
|
import org.saturnclient.impl.features.entity.EntityFeatureImpl;
|
||||||
|
import org.saturnclient.impl.features.entity.HealthRenderState;
|
||||||
|
import org.saturnclient.mod.mods.NametagsMod;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
|
import org.spongepowered.asm.mixin.injection.Inject;
|
||||||
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Captures each living entity's health and type into the render state during
|
||||||
|
* {@code updateRenderState}, and applies {@link NametagsMod}'s replacement nametag to the state's
|
||||||
|
* display name.
|
||||||
|
*
|
||||||
|
* Replacing the name here, while the render state is built, works the same on every version. Hooking
|
||||||
|
* label rendering instead doesn't: 1.21.9 moved it to the render command queue. Player role icons are
|
||||||
|
* added after this, in PlayerEntityRendererMixin.
|
||||||
|
*/
|
||||||
|
@Mixin(LivingEntityRenderer.class)
|
||||||
|
public abstract class LivingEntityRendererMixin<T extends LivingEntity, S extends LivingEntityRenderState> {
|
||||||
|
|
||||||
|
@Inject(method = "extractRenderState", at = @At("TAIL"))
|
||||||
|
private void saturn$captureHealth(T entity, S state, float tickDelta, CallbackInfo ci) {
|
||||||
|
if (!(state instanceof HealthRenderState hrs))
|
||||||
|
return;
|
||||||
|
|
||||||
|
hrs.saturn$setHealth(entity.getHealth(), entity.getMaxHealth());
|
||||||
|
|
||||||
|
// Classify entity type for the nametag feature's filtering logic
|
||||||
|
EntityFeature.EntityType type;
|
||||||
|
if (entity instanceof Player)
|
||||||
|
type = EntityFeature.EntityType.PLAYER;
|
||||||
|
else if (entity instanceof Monster)
|
||||||
|
type = EntityFeature.EntityType.HOSTILE;
|
||||||
|
else if (entity instanceof AgeableMob)
|
||||||
|
type = EntityFeature.EntityType.PASSIVE;
|
||||||
|
else
|
||||||
|
type = EntityFeature.EntityType.OTHER;
|
||||||
|
|
||||||
|
hrs.saturn$setEntityType(type);
|
||||||
|
|
||||||
|
// Only entities that already show a label get a replacement one, as when this hooked label
|
||||||
|
// rendering.
|
||||||
|
if (NametagsMod.shouldReplaceName() && state.nameTag != null) {
|
||||||
|
String replacement = NametagsMod.getNametagString(
|
||||||
|
new EntityFeatureImpl.RenderStateEntityState(state.nameTag.getString(), hrs));
|
||||||
|
if (replacement != null) {
|
||||||
|
state.nameTag = Component.literal(replacement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+19
-8
@@ -1,12 +1,16 @@
|
|||||||
package org.saturnclient.impl.features.mixins.render;
|
package org.saturnclient.impl.features.mixins.render;
|
||||||
|
|
||||||
import net.minecraft.client.font.TextRenderer;
|
import net.minecraft.client.gui.Font;
|
||||||
import net.minecraft.client.gui.DrawContext;
|
import net.minecraft.client.gui.GuiGraphics;
|
||||||
import net.minecraft.client.gui.hud.InGameHud;
|
//? if >=26.2 {
|
||||||
import net.minecraft.client.render.RenderTickCounter;
|
/*import net.minecraft.client.gui.Hud;
|
||||||
|
*///?} else
|
||||||
|
import net.minecraft.client.gui.Gui;
|
||||||
|
import net.minecraft.client.DeltaTracker;
|
||||||
|
|
||||||
import org.saturnclient.impl.ui.RenderScopeImpl;
|
import org.saturnclient.impl.ui.RenderScopeImpl;
|
||||||
import org.saturnclient.impl.ui.SaturnScreenFabric;
|
import org.saturnclient.impl.ui.SaturnScreenFabric;
|
||||||
|
import org.saturnclient.impl.ui.Screens;
|
||||||
import org.saturnclient.mod.HudMod;
|
import org.saturnclient.mod.HudMod;
|
||||||
import org.saturnclient.mod.Mod;
|
import org.saturnclient.mod.Mod;
|
||||||
import org.saturnclient.mod.ModLayout;
|
import org.saturnclient.mod.ModLayout;
|
||||||
@@ -37,18 +41,25 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
|||||||
* {@link SaturnScreenFabric} / {@link HudEditor} pair; that is
|
* {@link SaturnScreenFabric} / {@link HudEditor} pair; that is
|
||||||
* unchanged infrastructure.
|
* unchanged infrastructure.
|
||||||
*/
|
*/
|
||||||
@Mixin(InGameHud.class)
|
// The HUD is drawn by Hud from 26.2 (by Gui before).
|
||||||
|
//? if >=26.2 {
|
||||||
|
/*@Mixin(Hud.class)
|
||||||
|
*///?} else
|
||||||
|
@Mixin(Gui.class)
|
||||||
public class RenderMixin {
|
public class RenderMixin {
|
||||||
|
|
||||||
|
//? if >=26.1 {
|
||||||
|
/*@Inject(method = "extractChat", at = @At("TAIL"))
|
||||||
|
*///?} else
|
||||||
@Inject(method = "renderChat", at = @At("TAIL"))
|
@Inject(method = "renderChat", at = @At("TAIL"))
|
||||||
public void renderMods(DrawContext context, RenderTickCounter tickCounter, CallbackInfo ci) {
|
public void renderMods(GuiGraphics context, DeltaTracker tickCounter, CallbackInfo ci) {
|
||||||
TextRenderer textRenderer = SaturnClient.client.textRenderer;
|
Font textRenderer = SaturnClient.client.font;
|
||||||
|
|
||||||
// Skip rendering while the HUD editor is open so the editor
|
// Skip rendering while the HUD editor is open so the editor
|
||||||
// can draw its own preview layout without double-drawing.
|
// can draw its own preview layout without double-drawing.
|
||||||
if (textRenderer == null)
|
if (textRenderer == null)
|
||||||
return;
|
return;
|
||||||
if (SaturnClient.client.currentScreen instanceof SaturnScreenFabric f
|
if (Screens.current() instanceof SaturnScreenFabric f
|
||||||
&& f.screen instanceof HudEditor)
|
&& f.screen instanceof HudEditor)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
+6
-6
@@ -1,7 +1,7 @@
|
|||||||
package org.saturnclient.impl.features.mixins.world;
|
package org.saturnclient.impl.features.mixins.world;
|
||||||
|
|
||||||
import net.minecraft.client.network.ClientPlayNetworkHandler;
|
import net.minecraft.client.multiplayer.ClientPacketListener;
|
||||||
import net.minecraft.network.packet.s2c.play.WorldTimeUpdateS2CPacket;
|
import net.minecraft.network.protocol.game.ClientboundSetTimePacket;
|
||||||
|
|
||||||
import org.saturnclient.mod.mods.TpsMod;
|
import org.saturnclient.mod.mods.TpsMod;
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
@@ -20,11 +20,11 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
|||||||
* The only change from the original is renaming the import from
|
* The only change from the original is renaming the import from
|
||||||
* {@code feature.features.Tps} to {@link TpsMod}.
|
* {@code feature.features.Tps} to {@link TpsMod}.
|
||||||
*/
|
*/
|
||||||
@Mixin(ClientPlayNetworkHandler.class)
|
@Mixin(ClientPacketListener.class)
|
||||||
public class TpsMixin {
|
public class TpsMixin {
|
||||||
|
|
||||||
@Inject(method = "onWorldTimeUpdate", at = @At("HEAD"))
|
@Inject(method = "handleSetTime", at = @At("HEAD"))
|
||||||
private void onWorldTimeUpdate(WorldTimeUpdateS2CPacket packet, CallbackInfo ci) {
|
private void onWorldTimeUpdate(ClientboundSetTimePacket packet, CallbackInfo ci) {
|
||||||
TpsMod.onTimePacket(packet.time());
|
TpsMod.onTimePacket(packet.gameTime());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+7
-7
@@ -2,8 +2,8 @@ package org.saturnclient.impl.features.network;
|
|||||||
|
|
||||||
import org.saturnclient.common.feature.NetworkFeature;
|
import org.saturnclient.common.feature.NetworkFeature;
|
||||||
|
|
||||||
import net.minecraft.client.MinecraftClient;
|
import net.minecraft.client.Minecraft;
|
||||||
import net.minecraft.client.network.PlayerListEntry;
|
import net.minecraft.client.multiplayer.PlayerInfo;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fabric implementation of {@link NetworkFeature}.
|
* Fabric implementation of {@link NetworkFeature}.
|
||||||
@@ -14,23 +14,23 @@ import net.minecraft.client.network.PlayerListEntry;
|
|||||||
*/
|
*/
|
||||||
public class NetworkFeatureImpl implements NetworkFeature {
|
public class NetworkFeatureImpl implements NetworkFeature {
|
||||||
|
|
||||||
private final MinecraftClient mc;
|
private final Minecraft mc;
|
||||||
|
|
||||||
public NetworkFeatureImpl(MinecraftClient mc) {
|
public NetworkFeatureImpl(Minecraft mc) {
|
||||||
this.mc = mc;
|
this.mc = mc;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean hasNetwork() {
|
public boolean hasNetwork() {
|
||||||
return mc.getNetworkHandler() != null;
|
return mc.getConnection() != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getPing() {
|
public int getPing() {
|
||||||
if (mc.player == null || mc.getNetworkHandler() == null)
|
if (mc.player == null || mc.getConnection() == null)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
PlayerListEntry entry = mc.getNetworkHandler().getPlayerListEntry(mc.player.getUuid());
|
PlayerInfo entry = mc.getConnection().getPlayerInfo(mc.player.getUUID());
|
||||||
|
|
||||||
return entry != null ? entry.getLatency() : -1;
|
return entry != null ? entry.getLatency() : -1;
|
||||||
}
|
}
|
||||||
+46
-34
@@ -1,13 +1,13 @@
|
|||||||
package org.saturnclient.impl.features.player;
|
package org.saturnclient.impl.features.player;
|
||||||
|
|
||||||
import net.minecraft.client.MinecraftClient;
|
import net.minecraft.client.Minecraft;
|
||||||
import net.minecraft.client.network.ClientPlayerEntity;
|
import net.minecraft.client.player.LocalPlayer;
|
||||||
import net.minecraft.entity.EquipmentSlot;
|
import net.minecraft.world.entity.EquipmentSlot;
|
||||||
import net.minecraft.entity.effect.StatusEffectInstance;
|
import net.minecraft.world.effect.MobEffectInstance;
|
||||||
import net.minecraft.item.ItemStack;
|
import net.minecraft.world.item.ItemStack;
|
||||||
import net.minecraft.item.Items;
|
import net.minecraft.world.item.Items;
|
||||||
import net.minecraft.registry.Registries;
|
import net.minecraft.core.registries.BuiltInRegistries;
|
||||||
import net.minecraft.util.Identifier;
|
import net.minecraft.resources.Identifier;
|
||||||
|
|
||||||
import org.saturnclient.common.feature.PlayerFeature;
|
import org.saturnclient.common.feature.PlayerFeature;
|
||||||
import org.saturnclient.common.ref.game.EffectRef;
|
import org.saturnclient.common.ref.game.EffectRef;
|
||||||
@@ -29,9 +29,9 @@ import java.util.List;
|
|||||||
*/
|
*/
|
||||||
public class PlayerFeatureImpl implements PlayerFeature {
|
public class PlayerFeatureImpl implements PlayerFeature {
|
||||||
|
|
||||||
private final MinecraftClient mc;
|
private final Minecraft mc;
|
||||||
|
|
||||||
public PlayerFeatureImpl(MinecraftClient mc) {
|
public PlayerFeatureImpl(Minecraft mc) {
|
||||||
this.mc = mc;
|
this.mc = mc;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,7 +40,7 @@ public class PlayerFeatureImpl implements PlayerFeature {
|
|||||||
// ---------------------------------------------------------------
|
// ---------------------------------------------------------------
|
||||||
|
|
||||||
/** Returns the local player, or {@code null} if one is not loaded. */
|
/** Returns the local player, or {@code null} if one is not loaded. */
|
||||||
private ClientPlayerEntity player() {
|
private LocalPlayer player() {
|
||||||
return mc.player;
|
return mc.player;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,42 +78,42 @@ public class PlayerFeatureImpl implements PlayerFeature {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isForwardPressed() {
|
public boolean isForwardPressed() {
|
||||||
return mc.options.forwardKey.isPressed();
|
return mc.options.keyUp.isDown();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isBackPressed() {
|
public boolean isBackPressed() {
|
||||||
return mc.options.backKey.isPressed();
|
return mc.options.keyDown.isDown();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isLeftPressed() {
|
public boolean isLeftPressed() {
|
||||||
return mc.options.leftKey.isPressed();
|
return mc.options.keyLeft.isDown();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isRightPressed() {
|
public boolean isRightPressed() {
|
||||||
return mc.options.rightKey.isPressed();
|
return mc.options.keyRight.isDown();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isJumpPressed() {
|
public boolean isJumpPressed() {
|
||||||
return mc.options.jumpKey.isPressed();
|
return mc.options.keyJump.isDown();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isAttackPressed() {
|
public boolean isAttackPressed() {
|
||||||
return mc.options.attackKey.isPressed();
|
return mc.options.keyAttack.isDown();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isUsePressed() {
|
public boolean isUsePressed() {
|
||||||
return mc.options.useKey.isPressed();
|
return mc.options.keyUse.isDown();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isSneaking() {
|
public boolean isSneaking() {
|
||||||
return hasPlayer() && player().isSneaking();
|
return hasPlayer() && player().isShiftKeyDown();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -128,7 +128,7 @@ public class PlayerFeatureImpl implements PlayerFeature {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isOnGround() {
|
public boolean isOnGround() {
|
||||||
return hasPlayer() && player().isOnGround();
|
return hasPlayer() && player().onGround();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -145,7 +145,7 @@ public class PlayerFeatureImpl implements PlayerFeature {
|
|||||||
public Velocity getVelocity() {
|
public Velocity getVelocity() {
|
||||||
if (!hasPlayer())
|
if (!hasPlayer())
|
||||||
return Velocity.ZERO;
|
return Velocity.ZERO;
|
||||||
var v = player().getVelocity();
|
var v = player().getDeltaMovement();
|
||||||
return new Velocity(v.x, v.y, v.z);
|
return new Velocity(v.x, v.y, v.z);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -164,27 +164,39 @@ public class PlayerFeatureImpl implements PlayerFeature {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ItemStackRef getMainHand() {
|
public ItemStackRef getMainHand() {
|
||||||
return wrap(hasPlayer() ? player().getMainHandStack() : ItemStack.EMPTY);
|
return wrap(hasPlayer() ? player().getMainHandItem() : ItemStack.EMPTY);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ItemStackRef getHelmet() {
|
public ItemStackRef getHelmet() {
|
||||||
return wrap(hasPlayer() ? player().getEquippedStack(EquipmentSlot.HEAD) : ItemStack.EMPTY);
|
//? if >=1.21.5 {
|
||||||
|
return wrap(hasPlayer() ? player().getItemBySlot(EquipmentSlot.HEAD) : ItemStack.EMPTY);
|
||||||
|
//?} else
|
||||||
|
//return wrap(hasPlayer() ? player().getInventory().armor.get(3) : ItemStack.EMPTY);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ItemStackRef getChestplate() {
|
public ItemStackRef getChestplate() {
|
||||||
return wrap(hasPlayer() ? player().getEquippedStack(EquipmentSlot.CHEST) : ItemStack.EMPTY);
|
//? if >=1.21.5 {
|
||||||
|
return wrap(hasPlayer() ? player().getItemBySlot(EquipmentSlot.CHEST) : ItemStack.EMPTY);
|
||||||
|
//?} else
|
||||||
|
//return wrap(hasPlayer() ? player().getInventory().armor.get(2) : ItemStack.EMPTY);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ItemStackRef getLeggings() {
|
public ItemStackRef getLeggings() {
|
||||||
return wrap(hasPlayer() ? player().getEquippedStack(EquipmentSlot.LEGS) : ItemStack.EMPTY);
|
//? if >=1.21.5 {
|
||||||
|
return wrap(hasPlayer() ? player().getItemBySlot(EquipmentSlot.LEGS) : ItemStack.EMPTY);
|
||||||
|
//?} else
|
||||||
|
//return wrap(hasPlayer() ? player().getInventory().armor.get(1) : ItemStack.EMPTY);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ItemStackRef getBoots() {
|
public ItemStackRef getBoots() {
|
||||||
return wrap(hasPlayer() ? player().getEquippedStack(EquipmentSlot.FEET) : ItemStack.EMPTY);
|
//? if >=1.21.5 {
|
||||||
|
return wrap(hasPlayer() ? player().getItemBySlot(EquipmentSlot.FEET) : ItemStack.EMPTY);
|
||||||
|
//?} else
|
||||||
|
//return wrap(hasPlayer() ? player().getInventory().armor.get(0) : ItemStack.EMPTY);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------
|
// ---------------------------------------------------------------
|
||||||
@@ -225,9 +237,9 @@ public class PlayerFeatureImpl implements PlayerFeature {
|
|||||||
if (!hasPlayer())
|
if (!hasPlayer())
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
|
|
||||||
var effects = player().getActiveStatusEffects().values();
|
var effects = player().getActiveEffectsMap().values();
|
||||||
List<EffectRef> result = new ArrayList<>(effects.size());
|
List<EffectRef> result = new ArrayList<>(effects.size());
|
||||||
for (StatusEffectInstance instance : effects) {
|
for (MobEffectInstance instance : effects) {
|
||||||
result.add((EffectRef) instance);
|
result.add((EffectRef) instance);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
@@ -236,12 +248,12 @@ public class PlayerFeatureImpl implements PlayerFeature {
|
|||||||
@Override
|
@Override
|
||||||
public List<? extends EffectRef> getDummyEffects() {
|
public List<? extends EffectRef> getDummyEffects() {
|
||||||
return List.of(
|
return List.of(
|
||||||
(EffectRef) new StatusEffectInstance(
|
(EffectRef) new MobEffectInstance(
|
||||||
Registries.STATUS_EFFECT.getEntry(Identifier.ofVanilla("speed")).get(), 12000, 2),
|
BuiltInRegistries.MOB_EFFECT.get(Identifier.withDefaultNamespace("speed")).get(), 12000, 2),
|
||||||
(EffectRef) new StatusEffectInstance(
|
(EffectRef) new MobEffectInstance(
|
||||||
Registries.STATUS_EFFECT.getEntry(Identifier.ofVanilla("strength")).get(), 12000, 2),
|
BuiltInRegistries.MOB_EFFECT.get(Identifier.withDefaultNamespace("strength")).get(), 12000, 2),
|
||||||
(EffectRef) new StatusEffectInstance(
|
(EffectRef) new MobEffectInstance(
|
||||||
Registries.STATUS_EFFECT.getEntry(Identifier.ofVanilla("fire_resistance")).get(), 12000, 2));
|
BuiltInRegistries.MOB_EFFECT.get(Identifier.withDefaultNamespace("fire_resistance")).get(), 12000, 2));
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------
|
// ---------------------------------------------------------------
|
||||||
+10
-10
@@ -2,8 +2,8 @@ package org.saturnclient.impl.features.render;
|
|||||||
|
|
||||||
import org.saturnclient.common.feature.RenderFeature;
|
import org.saturnclient.common.feature.RenderFeature;
|
||||||
|
|
||||||
import net.minecraft.client.MinecraftClient;
|
import net.minecraft.client.Minecraft;
|
||||||
import net.minecraft.client.option.Perspective;
|
import net.minecraft.client.CameraType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fabric implementation of {@link RenderFeature}.
|
* Fabric implementation of {@link RenderFeature}.
|
||||||
@@ -14,9 +14,9 @@ import net.minecraft.client.option.Perspective;
|
|||||||
*/
|
*/
|
||||||
public class RenderFeatureImpl implements RenderFeature {
|
public class RenderFeatureImpl implements RenderFeature {
|
||||||
|
|
||||||
private final MinecraftClient mc;
|
private final Minecraft mc;
|
||||||
|
|
||||||
public RenderFeatureImpl(MinecraftClient mc) {
|
public RenderFeatureImpl(Minecraft mc) {
|
||||||
this.mc = mc;
|
this.mc = mc;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -26,12 +26,12 @@ public class RenderFeatureImpl implements RenderFeature {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getScaledWindowWidth() {
|
public int getScaledWindowWidth() {
|
||||||
return mc.getWindow().getScaledWidth();
|
return mc.getWindow().getGuiScaledWidth();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getScaledWindowHeight() {
|
public int getScaledWindowHeight() {
|
||||||
return mc.getWindow().getScaledHeight();
|
return mc.getWindow().getGuiScaledHeight();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------
|
// ---------------------------------------------------------------
|
||||||
@@ -40,7 +40,7 @@ public class RenderFeatureImpl implements RenderFeature {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getFps() {
|
public int getFps() {
|
||||||
return mc.getCurrentFps();
|
return mc.getFps();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------
|
// ---------------------------------------------------------------
|
||||||
@@ -49,16 +49,16 @@ public class RenderFeatureImpl implements RenderFeature {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isFirstPerson() {
|
public boolean isFirstPerson() {
|
||||||
return mc.options.getPerspective() == Perspective.FIRST_PERSON;
|
return mc.options.getCameraType() == CameraType.FIRST_PERSON;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setFirstPerson() {
|
public void setFirstPerson() {
|
||||||
mc.options.setPerspective(Perspective.FIRST_PERSON);
|
mc.options.setCameraType(CameraType.FIRST_PERSON);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setThirdPersonBack() {
|
public void setThirdPersonBack() {
|
||||||
mc.options.setPerspective(Perspective.THIRD_PERSON_BACK);
|
mc.options.setCameraType(CameraType.THIRD_PERSON_BACK);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package org.saturnclient.impl.features.render;
|
||||||
|
|
||||||
|
import org.saturnclient.mod.mods.FreelookMod;
|
||||||
|
import org.saturnclient.mod.mods.ZoomMod;
|
||||||
|
|
||||||
|
/** The zoom mods' change to the field of view, applied where each version computes it. */
|
||||||
|
public final class ZoomFov {
|
||||||
|
private ZoomFov() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static float apply(float fov) {
|
||||||
|
if (ZoomMod.shouldZoom()) {
|
||||||
|
return fov / ZoomMod.getZoomLevel();
|
||||||
|
} else if (FreelookMod.shouldZoomOut()) {
|
||||||
|
return fov / FreelookMod.getZoomLevel();
|
||||||
|
}
|
||||||
|
return fov;
|
||||||
|
}
|
||||||
|
}
|
||||||
+6
-5
@@ -2,7 +2,7 @@ package org.saturnclient.impl.features.world;
|
|||||||
|
|
||||||
import org.saturnclient.common.feature.WorldFeature;
|
import org.saturnclient.common.feature.WorldFeature;
|
||||||
|
|
||||||
import net.minecraft.client.MinecraftClient;
|
import net.minecraft.client.Minecraft;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fabric implementation of {@link WorldFeature}.
|
* Fabric implementation of {@link WorldFeature}.
|
||||||
@@ -12,15 +12,15 @@ import net.minecraft.client.MinecraftClient;
|
|||||||
*/
|
*/
|
||||||
public class WorldFeatureImpl implements WorldFeature {
|
public class WorldFeatureImpl implements WorldFeature {
|
||||||
|
|
||||||
private final MinecraftClient mc;
|
private final Minecraft mc;
|
||||||
|
|
||||||
public WorldFeatureImpl(MinecraftClient mc) {
|
public WorldFeatureImpl(Minecraft mc) {
|
||||||
this.mc = mc;
|
this.mc = mc;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean hasWorld() {
|
public boolean hasWorld() {
|
||||||
return mc.world != null;
|
return mc.level != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -31,6 +31,7 @@ public class WorldFeatureImpl implements WorldFeature {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public long getWorldAge() {
|
public long getWorldAge() {
|
||||||
return hasWorld() ? mc.world.getTime() : 0L;
|
// World.getTime() was a shortcut for this, and 1.21.11 removed it.
|
||||||
|
return hasWorld() ? mc.level.getLevelData().getGameTime() : 0L;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+1
-1
@@ -4,7 +4,7 @@ import org.saturnclient.common.ref.asset.IdentifierRef;
|
|||||||
import org.spongepowered.asm.mixin.Mixin;
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
import org.spongepowered.asm.mixin.Shadow;
|
import org.spongepowered.asm.mixin.Shadow;
|
||||||
|
|
||||||
import net.minecraft.util.Identifier;
|
import net.minecraft.resources.Identifier;
|
||||||
|
|
||||||
@Mixin(Identifier.class)
|
@Mixin(Identifier.class)
|
||||||
public abstract class IdentifierMixin implements IdentifierRef {
|
public abstract class IdentifierMixin implements IdentifierRef {
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package org.saturnclient.impl.mixins;
|
||||||
|
|
||||||
|
import org.saturnclient.common.ref.game.ItemStackRef;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.Shadow;
|
||||||
|
|
||||||
|
import net.minecraft.world.item.ItemStack;
|
||||||
|
|
||||||
|
@Mixin(ItemStack.class)
|
||||||
|
public abstract class ItemStackMixin implements ItemStackRef {
|
||||||
|
@Shadow
|
||||||
|
public abstract boolean isEmpty();
|
||||||
|
|
||||||
|
@Shadow
|
||||||
|
public abstract int getMaxDamage();
|
||||||
|
|
||||||
|
@Shadow
|
||||||
|
public abstract int getDamageValue();
|
||||||
|
|
||||||
|
// ItemStackRef's methods delegate to ItemStack's, whose names production renames.
|
||||||
|
@Override
|
||||||
|
public boolean isEmptyStack() {
|
||||||
|
return isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getMaxItemDamage() {
|
||||||
|
return getMaxDamage();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemDamage() {
|
||||||
|
return getDamageValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
package org.saturnclient.impl.mixins;
|
||||||
|
|
||||||
|
import com.mojang.blaze3d.vertex.PoseStack;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
//? if <1.21.6 {
|
||||||
|
/*import org.saturnclient.common.ref.render.MatrixStackRef;
|
||||||
|
//? if >=1.21.5
|
||||||
|
import org.joml.Quaternionfc;
|
||||||
|
//? if <1.21.5
|
||||||
|
//import org.joml.Quaternionf;
|
||||||
|
import org.spongepowered.asm.mixin.Shadow;
|
||||||
|
import com.mojang.math.Axis;
|
||||||
|
*///?}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes MatrixStack a MatrixStackRef, for GUI drawing before 1.21.6. From 1.21.6 the GUI uses a 2D
|
||||||
|
* Matrix3x2fStack (Matrix3x2fStackRef) instead, so this mixin is empty but stays registered, keeping
|
||||||
|
* impl.mixins.json the same for every version.
|
||||||
|
*/
|
||||||
|
@Mixin(PoseStack.class)
|
||||||
|
//? if <1.21.6 {
|
||||||
|
/*public abstract class MatrixStackMixin implements MatrixStackRef {
|
||||||
|
@Shadow
|
||||||
|
public abstract void pushPose();
|
||||||
|
|
||||||
|
@Shadow
|
||||||
|
public abstract void popPose();
|
||||||
|
|
||||||
|
@Shadow
|
||||||
|
public abstract void translate(float x, float y, float z);
|
||||||
|
|
||||||
|
@Shadow
|
||||||
|
public abstract void scale(float x, float y, float z);
|
||||||
|
|
||||||
|
@Shadow
|
||||||
|
//? if >=1.21.5
|
||||||
|
public abstract void mulPose(Quaternionfc quaternion);
|
||||||
|
//? if <1.21.5
|
||||||
|
//public abstract void mulPose(Quaternionf quaternion);
|
||||||
|
|
||||||
|
@Shadow
|
||||||
|
//? if >=1.21.5
|
||||||
|
public abstract void rotateAround(Quaternionfc quaternion, float originX, float originY, float originZ);
|
||||||
|
//? if <1.21.5
|
||||||
|
//public abstract void rotateAround(Quaternionf quaternion, float originX, float originY, float originZ);
|
||||||
|
|
||||||
|
// MatrixStackRef's methods delegate to PoseStack's, whose names production renames.
|
||||||
|
@Override
|
||||||
|
public void push() {
|
||||||
|
pushPose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void pop() {
|
||||||
|
popPose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void translate(float x, float y) {
|
||||||
|
translate(x, y, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void scale(float x, float y) {
|
||||||
|
scale(x, y, 1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void rotate(float angle, float originX, float originY, float originZ) {
|
||||||
|
rotateAround(Axis.ZN.rotationDegrees(angle), originX, originY, originZ);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void rotate(float angle) {
|
||||||
|
mulPose(Axis.ZN.rotationDegrees(angle));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*///?} else
|
||||||
|
public abstract class MatrixStackMixin {}
|
||||||
+49
-37
@@ -4,61 +4,61 @@ import java.io.File;
|
|||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
import org.saturnclient.common.ref.asset.IdentifierRef;
|
import org.saturnclient.common.ref.asset.IdentifierRef;
|
||||||
import org.saturnclient.common.ref.game.MinecraftClientRef;
|
import org.saturnclient.common.ref.game.MinecraftClientRef;
|
||||||
import org.saturnclient.common.ref.render.WindowRef;
|
import org.saturnclient.common.ref.render.WindowRef;
|
||||||
import org.saturnclient.impl.ui.SaturnScreenFabric;
|
import org.saturnclient.impl.ui.SaturnScreenFabric;
|
||||||
|
import org.saturnclient.impl.ui.Screens;
|
||||||
import org.saturnclient.ui.SaturnScreen;
|
import org.saturnclient.ui.SaturnScreen;
|
||||||
import org.saturnclient.ui.screens.TitleMenu;
|
import org.saturnclient.ui.screens.TitleMenu;
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
import org.spongepowered.asm.mixin.Shadow;
|
import org.spongepowered.asm.mixin.Shadow;
|
||||||
|
|
||||||
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents;
|
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents;
|
||||||
import net.minecraft.client.MinecraftClient;
|
import net.minecraft.client.Minecraft;
|
||||||
import net.minecraft.client.gui.screen.Screen;
|
import net.minecraft.client.gui.screens.multiplayer.JoinMultiplayerScreen;
|
||||||
import net.minecraft.client.gui.screen.multiplayer.MultiplayerScreen;
|
import net.minecraft.client.gui.screens.options.OptionsScreen;
|
||||||
import net.minecraft.client.gui.screen.option.OptionsScreen;
|
import net.minecraft.client.gui.screens.worldselection.SelectWorldScreen;
|
||||||
import net.minecraft.client.gui.screen.world.SelectWorldScreen;
|
import net.minecraft.client.Options;
|
||||||
import net.minecraft.client.option.GameOptions;
|
import net.minecraft.client.User;
|
||||||
import net.minecraft.client.session.Session;
|
import com.mojang.blaze3d.platform.Window;
|
||||||
import net.minecraft.client.util.Window;
|
import net.minecraft.server.packs.resources.ReloadableResourceManager;
|
||||||
import net.minecraft.resource.ReloadableResourceManagerImpl;
|
import net.minecraft.resources.Identifier;
|
||||||
import net.minecraft.util.Identifier;
|
|
||||||
|
|
||||||
@Mixin(MinecraftClient.class)
|
@Mixin(Minecraft.class)
|
||||||
public abstract class MinecraftClientMixin implements MinecraftClientRef {
|
public abstract class MinecraftClientMixin implements MinecraftClientRef {
|
||||||
@Shadow
|
@Shadow
|
||||||
public File runDirectory;
|
public File gameDirectory;
|
||||||
|
|
||||||
@Shadow
|
@Shadow
|
||||||
private Window window;
|
private Window window;
|
||||||
|
|
||||||
@Shadow
|
@Shadow
|
||||||
private ReloadableResourceManagerImpl resourceManager;
|
private ReloadableResourceManager resourceManager;
|
||||||
|
|
||||||
@Shadow
|
@Shadow
|
||||||
public GameOptions options;
|
public Options options;
|
||||||
|
|
||||||
@Shadow
|
@Shadow
|
||||||
private Session session;
|
private User user;
|
||||||
|
|
||||||
@Shadow
|
@Shadow
|
||||||
public abstract void setScreen(@Nullable Screen screen);
|
public abstract void stop();
|
||||||
|
|
||||||
@Shadow
|
|
||||||
@Override
|
@Override
|
||||||
public abstract void scheduleStop();
|
public void scheduleStop() {
|
||||||
|
stop();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public File getRunDirectory() {
|
public File getRunDirectory() {
|
||||||
return runDirectory;
|
return gameDirectory;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public InputStream getResource(IdentifierRef identifier) {
|
public InputStream getResource(IdentifierRef identifier) {
|
||||||
try {
|
try {
|
||||||
return resourceManager.getResource((Identifier) (Object) identifier).get().getInputStream();
|
return resourceManager.getResource((Identifier) (Object) identifier).get().open();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -71,7 +71,7 @@ public abstract class MinecraftClientMixin implements MinecraftClientRef {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setScreen(SaturnScreen screen) {
|
public void setScreen(SaturnScreen screen) {
|
||||||
this.setScreen(new SaturnScreenFabric(screen));
|
Screens.set(new SaturnScreenFabric(screen));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -80,42 +80,48 @@ public abstract class MinecraftClientMixin implements MinecraftClientRef {
|
|||||||
|
|
||||||
switch (screen) {
|
switch (screen) {
|
||||||
case SelectWorld:
|
case SelectWorld:
|
||||||
this.setScreen(new SelectWorldScreen(exitTarget));
|
Screens.set(new SelectWorldScreen(exitTarget));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Multiplayer:
|
case Multiplayer:
|
||||||
this.setScreen(new MultiplayerScreen(exitTarget));
|
Screens.set(new JoinMultiplayerScreen(exitTarget));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Options:
|
case Options:
|
||||||
this.setScreen(new OptionsScreen(exitTarget, this.options));
|
// Only 26.1 and 26.2 take whether a world is open.
|
||||||
|
//? if >=26.3 {
|
||||||
|
/*Screens.set(new OptionsScreen(exitTarget, this.options));
|
||||||
|
*///?} else if >=26.1 {
|
||||||
|
/*Screens.set(new OptionsScreen(exitTarget, this.options, ((Minecraft) (Object) this).level != null));
|
||||||
|
*///?} else
|
||||||
|
Screens.set(new OptionsScreen(exitTarget, this.options));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getAccessToken() {
|
public String getAccessToken() {
|
||||||
if (session == null) {
|
if (user == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.session.getAccessToken();
|
return this.user.getAccessToken();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public UUID getUuid() {
|
public UUID getUuid() {
|
||||||
if (session == null) {
|
if (user == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return this.session.getUuidOrNull();
|
return this.user.getProfileId();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getUsername() {
|
public String getUsername() {
|
||||||
if (session == null) {
|
if (user == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return this.session.getUsername();
|
return this.user.getName();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -131,32 +137,38 @@ public abstract class MinecraftClientMixin implements MinecraftClientRef {
|
|||||||
@Override
|
@Override
|
||||||
@SuppressWarnings("resource")
|
@SuppressWarnings("resource")
|
||||||
public String getPlayerListEntry(UUID uuid) {
|
public String getPlayerListEntry(UUID uuid) {
|
||||||
var client = (MinecraftClient) (Object) this;
|
var client = (Minecraft) (Object) this;
|
||||||
var handler = client.getNetworkHandler();
|
var handler = client.getConnection();
|
||||||
|
|
||||||
if (handler == null)
|
if (handler == null)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
var entry = handler.getPlayerListEntry(uuid);
|
var entry = handler.getPlayerInfo(uuid);
|
||||||
if (entry == null)
|
if (entry == null)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
|
//? if >=1.21.9 {
|
||||||
return entry.getProfile().name();
|
return entry.getProfile().name();
|
||||||
|
//?} else
|
||||||
|
//return entry.getProfile().getName();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@SuppressWarnings("resource")
|
@SuppressWarnings("resource")
|
||||||
public UUID getPlayerListEntryUUID(String name) {
|
public UUID getPlayerListEntryUUID(String name) {
|
||||||
var client = (MinecraftClient) (Object) this;
|
var client = (Minecraft) (Object) this;
|
||||||
var handler = client.getNetworkHandler();
|
var handler = client.getConnection();
|
||||||
|
|
||||||
if (handler == null)
|
if (handler == null)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
var entry = handler.getPlayerListEntry(name);
|
var entry = handler.getPlayerInfo(name);
|
||||||
if (entry == null)
|
if (entry == null)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
|
//? if >=1.21.9 {
|
||||||
return entry.getProfile().id();
|
return entry.getProfile().id();
|
||||||
|
//?} else
|
||||||
|
//return entry.getProfile().getId();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+2
-2
@@ -1,9 +1,9 @@
|
|||||||
package org.saturnclient.impl.mixins;
|
package org.saturnclient.impl.mixins;
|
||||||
|
|
||||||
import net.minecraft.client.texture.Sprite;
|
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
|
||||||
import org.saturnclient.common.ref.asset.SpriteRef;
|
import org.saturnclient.common.ref.asset.SpriteRef;
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
|
||||||
@Mixin(Sprite.class)
|
@Mixin(TextureAtlasSprite.class)
|
||||||
public abstract class SpriteMixin implements SpriteRef {
|
public abstract class SpriteMixin implements SpriteRef {
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
package org.saturnclient.impl.mixins;
|
||||||
|
|
||||||
|
import org.saturnclient.common.ref.asset.IdentifierRef;
|
||||||
|
import org.saturnclient.common.ref.asset.SpriteRef;
|
||||||
|
import org.saturnclient.common.ref.game.EffectRef;
|
||||||
|
//? if <1.21.9
|
||||||
|
//import org.saturnclient.saturnclient.SaturnClient;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.Shadow;
|
||||||
|
|
||||||
|
//? if >=26.2 {
|
||||||
|
/*import net.minecraft.client.gui.Hud;
|
||||||
|
*///?} else
|
||||||
|
import net.minecraft.client.gui.Gui;
|
||||||
|
import net.minecraft.world.effect.MobEffect;
|
||||||
|
import net.minecraft.world.effect.MobEffectInstance;
|
||||||
|
import net.minecraft.core.Holder;
|
||||||
|
import net.minecraft.resources.Identifier;
|
||||||
|
|
||||||
|
@Mixin(MobEffectInstance.class)
|
||||||
|
public abstract class StatusEffectMixin implements EffectRef {
|
||||||
|
|
||||||
|
@Shadow
|
||||||
|
public abstract boolean showIcon();
|
||||||
|
|
||||||
|
@Shadow
|
||||||
|
public abstract boolean isInfiniteDuration();
|
||||||
|
|
||||||
|
@Shadow
|
||||||
|
public abstract int getDuration();
|
||||||
|
|
||||||
|
@Shadow
|
||||||
|
public abstract Holder<MobEffect> getEffect();
|
||||||
|
|
||||||
|
// EffectRef's methods delegate to MobEffectInstance's, whose names production renames.
|
||||||
|
@Override
|
||||||
|
public boolean shouldShowIcon() {
|
||||||
|
return showIcon();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isInfinite() {
|
||||||
|
return isInfiniteDuration();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SpriteRef getIcon() {
|
||||||
|
// 1.21.9 changed the GUI atlas, so newer versions draw the effect's texture (getIconId) instead.
|
||||||
|
//? if >=1.21.9 {
|
||||||
|
return null;
|
||||||
|
//?} else if >=1.21.6 {
|
||||||
|
//return (SpriteRef) SaturnClient.client.getGuiSprites().getSprite((Gui.getMobEffectSprite(getEffect())));
|
||||||
|
//?} else
|
||||||
|
//return (SpriteRef) SaturnClient.client.getMobEffectTextures().get(getEffect());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Before 1.21.6 there's no InGameHud.getEffectTexture; getIcon's sprite is used there instead.
|
||||||
|
//? if >=1.21.6 {
|
||||||
|
@Override
|
||||||
|
public IdentifierRef getIconId() {
|
||||||
|
//? if >=26.2 {
|
||||||
|
/*Identifier id = Hud.getMobEffectSprite(getEffect());
|
||||||
|
*///?} else
|
||||||
|
Identifier id = Gui.getMobEffectSprite(getEffect());
|
||||||
|
|
||||||
|
return IdentifierRef.of(id.getNamespace(), "textures/" + id.getPath() + ".png");
|
||||||
|
}
|
||||||
|
//?}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getInfiniteText() {
|
||||||
|
return "∞";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getDurationSeconds() {
|
||||||
|
return getDuration() / 20; // ticks → seconds
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package org.saturnclient.impl.mixins;
|
||||||
|
|
||||||
|
import org.saturnclient.common.ref.asset.IdentifierRef;
|
||||||
|
import org.saturnclient.common.ref.render.TextRef;
|
||||||
|
import org.saturnclient.saturnclient.SaturnClient;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
|
||||||
|
import net.minecraft.network.chat.Style;
|
||||||
|
import net.minecraft.network.chat.Component;
|
||||||
|
//? if >=1.21.9
|
||||||
|
import net.minecraft.network.chat.FontDescription.Resource;
|
||||||
|
import net.minecraft.resources.Identifier;
|
||||||
|
|
||||||
|
@Mixin(Component.class)
|
||||||
|
public interface TextMixin extends TextRef {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public default TextRef withFont(IdentifierRef font) {
|
||||||
|
//? if >=1.21.9 {
|
||||||
|
return (TextRef) (Component) (((Component) this).copy().setStyle(Style.EMPTY.withFont(new Resource((Identifier) (Object) font))));
|
||||||
|
//?} else
|
||||||
|
//return (TextRef) (((Component) this).copy().setStyle(Style.EMPTY.withFont((Identifier) (Object) font)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public default int getWidth() {
|
||||||
|
return SaturnClient.client.font.width((Component) this);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package org.saturnclient.impl.mixins;
|
||||||
|
|
||||||
|
import org.saturnclient.common.ref.render.WindowRef;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.Shadow;
|
||||||
|
|
||||||
|
import com.mojang.blaze3d.platform.Window;
|
||||||
|
|
||||||
|
@Mixin(Window.class)
|
||||||
|
public abstract class WindowMixin implements WindowRef {
|
||||||
|
@Shadow
|
||||||
|
public abstract int getScreenWidth();
|
||||||
|
|
||||||
|
@Shadow
|
||||||
|
public abstract int getScreenHeight();
|
||||||
|
|
||||||
|
// The framebuffer's size
|
||||||
|
@Shadow
|
||||||
|
public abstract int getWidth();
|
||||||
|
|
||||||
|
@Shadow
|
||||||
|
public abstract int getHeight();
|
||||||
|
|
||||||
|
// WindowRef's methods delegate to Window's, whose names production renames.
|
||||||
|
@Override
|
||||||
|
public int getWindowWidth() {
|
||||||
|
return getScreenWidth();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getWindowHeight() {
|
||||||
|
return getScreenHeight();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getFramebufferWidth() {
|
||||||
|
return getWidth();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getFramebufferHeight() {
|
||||||
|
return getHeight();
|
||||||
|
}
|
||||||
|
}
|
||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
package org.saturnclient.impl.provider;
|
package org.saturnclient.impl.provider;
|
||||||
|
|
||||||
import net.minecraft.client.MinecraftClient;
|
import net.minecraft.client.Minecraft;
|
||||||
|
|
||||||
import org.saturnclient.common.feature.*;
|
import org.saturnclient.common.feature.*;
|
||||||
import org.saturnclient.common.provider.FeatureProvider;
|
import org.saturnclient.common.provider.FeatureProvider;
|
||||||
@@ -33,7 +33,7 @@ public class FeatureProviderFabric implements FeatureProvider {
|
|||||||
private final RenderFeature render;
|
private final RenderFeature render;
|
||||||
private final NetworkFeature network;
|
private final NetworkFeature network;
|
||||||
|
|
||||||
public FeatureProviderFabric(MinecraftClient mc) {
|
public FeatureProviderFabric(Minecraft mc) {
|
||||||
this.player = new PlayerFeatureImpl(mc);
|
this.player = new PlayerFeatureImpl(mc);
|
||||||
this.world = new WorldFeatureImpl(mc);
|
this.world = new WorldFeatureImpl(mc);
|
||||||
this.entity = new EntityFeatureImpl(mc);
|
this.entity = new EntityFeatureImpl(mc);
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package org.saturnclient.impl.provider;
|
||||||
|
|
||||||
|
// 26.3 uses SDL, not GLFW; Keys converts Saturn's GLFW key codes to its own.
|
||||||
|
//? if >=26.3 {
|
||||||
|
/*import com.mojang.blaze3d.platform.InputConstants;
|
||||||
|
*///?} else
|
||||||
|
import org.lwjgl.glfw.GLFW;
|
||||||
|
import org.saturnclient.common.provider.GLFWProvider;
|
||||||
|
import org.saturnclient.impl.ui.Screens;
|
||||||
|
import org.saturnclient.saturnclient.SaturnClient;
|
||||||
|
|
||||||
|
public class GLFWProviderImpl implements GLFWProvider {
|
||||||
|
@Override
|
||||||
|
public boolean isKeyPressed(int key) {
|
||||||
|
//? if >=26.3 {
|
||||||
|
/*int gameKey = Keys.toGame(key);
|
||||||
|
return gameKey >= 0 && InputConstants.isKeyDown(gameKey) && Screens.current() == null;
|
||||||
|
*///?} else {
|
||||||
|
//? if >=1.21.9 {
|
||||||
|
long window = SaturnClient.client.getWindow().handle();
|
||||||
|
//?} else
|
||||||
|
//long window = SaturnClient.client.getWindow().getWindow();
|
||||||
|
return GLFW.glfwGetKey(window, key) == GLFW.GLFW_PRESS
|
||||||
|
&& Screens.current() == null;
|
||||||
|
//?}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getKeyName(int key) {
|
||||||
|
//? if >=26.3 {
|
||||||
|
/*int gameKey = Keys.toGame(key);
|
||||||
|
return gameKey < 0 ? null : InputConstants.Type.KEYBOARD.getOrCreate(gameKey).getDisplayName().getString();
|
||||||
|
*///?} else
|
||||||
|
return GLFW.glfwGetKeyName(key, GLFW.glfwGetKeyScancode(key));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,189 @@
|
|||||||
|
package org.saturnclient.impl.provider;
|
||||||
|
|
||||||
|
//? if >=26.3 {
|
||||||
|
/*import com.mojang.blaze3d.platform.InputConstants;
|
||||||
|
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
|
||||||
|
import org.saturnclient.common.provider.GLFWProvider;
|
||||||
|
*///?}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saturn's key codes are GLFW's, which the config's keybinds store. From 26.3 Minecraft uses SDL,
|
||||||
|
* whose keys (scancodes), modifiers and mouse buttons are numbered differently, so these convert
|
||||||
|
* between the two there. Before 26.3 Minecraft's numbers are GLFW's and pass through.
|
||||||
|
*/
|
||||||
|
public final class Keys {
|
||||||
|
private Keys() {
|
||||||
|
}
|
||||||
|
|
||||||
|
//? if >=26.3 {
|
||||||
|
/*private static final Int2IntOpenHashMap TO_GAME = new Int2IntOpenHashMap();
|
||||||
|
private static final Int2IntOpenHashMap TO_SATURN = new Int2IntOpenHashMap();
|
||||||
|
|
||||||
|
private static void key(int glfw, int game) {
|
||||||
|
TO_GAME.put(glfw, game);
|
||||||
|
TO_SATURN.put(game, glfw);
|
||||||
|
}
|
||||||
|
|
||||||
|
// GLFW's key codes, and the same keys' constants in 26.3
|
||||||
|
static {
|
||||||
|
TO_GAME.defaultReturnValue(-1);
|
||||||
|
TO_SATURN.defaultReturnValue(-1);
|
||||||
|
key(32, InputConstants.KEY_SPACE);
|
||||||
|
key(39, InputConstants.KEY_APOSTROPHE);
|
||||||
|
key(44, InputConstants.KEY_COMMA);
|
||||||
|
key(45, InputConstants.KEY_MINUS);
|
||||||
|
key(46, InputConstants.KEY_PERIOD);
|
||||||
|
key(47, InputConstants.KEY_SLASH);
|
||||||
|
key(48, InputConstants.KEY_0);
|
||||||
|
key(49, InputConstants.KEY_1);
|
||||||
|
key(50, InputConstants.KEY_2);
|
||||||
|
key(51, InputConstants.KEY_3);
|
||||||
|
key(52, InputConstants.KEY_4);
|
||||||
|
key(53, InputConstants.KEY_5);
|
||||||
|
key(54, InputConstants.KEY_6);
|
||||||
|
key(55, InputConstants.KEY_7);
|
||||||
|
key(56, InputConstants.KEY_8);
|
||||||
|
key(57, InputConstants.KEY_9);
|
||||||
|
key(59, InputConstants.KEY_SEMICOLON);
|
||||||
|
key(61, InputConstants.KEY_EQUALS);
|
||||||
|
key(65, InputConstants.KEY_A);
|
||||||
|
key(66, InputConstants.KEY_B);
|
||||||
|
key(67, InputConstants.KEY_C);
|
||||||
|
key(68, InputConstants.KEY_D);
|
||||||
|
key(69, InputConstants.KEY_E);
|
||||||
|
key(70, InputConstants.KEY_F);
|
||||||
|
key(71, InputConstants.KEY_G);
|
||||||
|
key(72, InputConstants.KEY_H);
|
||||||
|
key(73, InputConstants.KEY_I);
|
||||||
|
key(74, InputConstants.KEY_J);
|
||||||
|
key(75, InputConstants.KEY_K);
|
||||||
|
key(76, InputConstants.KEY_L);
|
||||||
|
key(77, InputConstants.KEY_M);
|
||||||
|
key(78, InputConstants.KEY_N);
|
||||||
|
key(79, InputConstants.KEY_O);
|
||||||
|
key(80, InputConstants.KEY_P);
|
||||||
|
key(81, InputConstants.KEY_Q);
|
||||||
|
key(82, InputConstants.KEY_R);
|
||||||
|
key(83, InputConstants.KEY_S);
|
||||||
|
key(84, InputConstants.KEY_T);
|
||||||
|
key(85, InputConstants.KEY_U);
|
||||||
|
key(86, InputConstants.KEY_V);
|
||||||
|
key(87, InputConstants.KEY_W);
|
||||||
|
key(88, InputConstants.KEY_X);
|
||||||
|
key(89, InputConstants.KEY_Y);
|
||||||
|
key(90, InputConstants.KEY_Z);
|
||||||
|
key(91, InputConstants.KEY_LBRACKET);
|
||||||
|
key(92, InputConstants.KEY_BACKSLASH);
|
||||||
|
key(93, InputConstants.KEY_RBRACKET);
|
||||||
|
key(96, InputConstants.KEY_GRAVE);
|
||||||
|
key(256, InputConstants.KEY_ESCAPE);
|
||||||
|
key(257, InputConstants.KEY_RETURN);
|
||||||
|
key(258, InputConstants.KEY_TAB);
|
||||||
|
key(259, InputConstants.KEY_BACKSPACE);
|
||||||
|
key(260, InputConstants.KEY_INSERT);
|
||||||
|
key(261, InputConstants.KEY_DELETE);
|
||||||
|
key(262, InputConstants.KEY_RIGHT);
|
||||||
|
key(263, InputConstants.KEY_LEFT);
|
||||||
|
key(264, InputConstants.KEY_DOWN);
|
||||||
|
key(265, InputConstants.KEY_UP);
|
||||||
|
key(266, InputConstants.KEY_PAGEUP);
|
||||||
|
key(267, InputConstants.KEY_PAGEDOWN);
|
||||||
|
key(268, InputConstants.KEY_HOME);
|
||||||
|
key(269, InputConstants.KEY_END);
|
||||||
|
key(280, InputConstants.KEY_CAPSLOCK);
|
||||||
|
key(281, InputConstants.KEY_SCROLLLOCK);
|
||||||
|
key(282, InputConstants.KEY_NUMLOCK);
|
||||||
|
key(283, InputConstants.KEY_PRINTSCREEN);
|
||||||
|
key(284, InputConstants.KEY_PAUSE);
|
||||||
|
key(290, InputConstants.KEY_F1);
|
||||||
|
key(291, InputConstants.KEY_F2);
|
||||||
|
key(292, InputConstants.KEY_F3);
|
||||||
|
key(293, InputConstants.KEY_F4);
|
||||||
|
key(294, InputConstants.KEY_F5);
|
||||||
|
key(295, InputConstants.KEY_F6);
|
||||||
|
key(296, InputConstants.KEY_F7);
|
||||||
|
key(297, InputConstants.KEY_F8);
|
||||||
|
key(298, InputConstants.KEY_F9);
|
||||||
|
key(299, InputConstants.KEY_F10);
|
||||||
|
key(300, InputConstants.KEY_F11);
|
||||||
|
key(301, InputConstants.KEY_F12);
|
||||||
|
key(302, InputConstants.KEY_F13);
|
||||||
|
key(303, InputConstants.KEY_F14);
|
||||||
|
key(304, InputConstants.KEY_F15);
|
||||||
|
key(305, InputConstants.KEY_F16);
|
||||||
|
key(306, InputConstants.KEY_F17);
|
||||||
|
key(307, InputConstants.KEY_F18);
|
||||||
|
key(308, InputConstants.KEY_F19);
|
||||||
|
key(309, InputConstants.KEY_F20);
|
||||||
|
key(310, InputConstants.KEY_F21);
|
||||||
|
key(311, InputConstants.KEY_F22);
|
||||||
|
key(312, InputConstants.KEY_F23);
|
||||||
|
key(313, InputConstants.KEY_F24);
|
||||||
|
key(320, InputConstants.KEY_NUMPAD0);
|
||||||
|
key(321, InputConstants.KEY_NUMPAD1);
|
||||||
|
key(322, InputConstants.KEY_NUMPAD2);
|
||||||
|
key(323, InputConstants.KEY_NUMPAD3);
|
||||||
|
key(324, InputConstants.KEY_NUMPAD4);
|
||||||
|
key(325, InputConstants.KEY_NUMPAD5);
|
||||||
|
key(326, InputConstants.KEY_NUMPAD6);
|
||||||
|
key(327, InputConstants.KEY_NUMPAD7);
|
||||||
|
key(328, InputConstants.KEY_NUMPAD8);
|
||||||
|
key(329, InputConstants.KEY_NUMPAD9);
|
||||||
|
key(330, InputConstants.KEY_NUMPADCOMMA);
|
||||||
|
key(332, InputConstants.KEY_MULTIPLY);
|
||||||
|
key(334, InputConstants.KEY_ADD);
|
||||||
|
key(335, InputConstants.KEY_NUMPADENTER);
|
||||||
|
key(336, InputConstants.KEY_NUMPADEQUALS);
|
||||||
|
key(340, InputConstants.KEY_LSHIFT);
|
||||||
|
key(341, InputConstants.KEY_LCONTROL);
|
||||||
|
key(342, InputConstants.KEY_LALT);
|
||||||
|
key(343, InputConstants.KEY_LGUI);
|
||||||
|
key(344, InputConstants.KEY_RSHIFT);
|
||||||
|
key(345, InputConstants.KEY_RCONTROL);
|
||||||
|
key(346, InputConstants.KEY_RALT);
|
||||||
|
key(347, InputConstants.KEY_RGUI);
|
||||||
|
}
|
||||||
|
*///?}
|
||||||
|
|
||||||
|
/** A Saturn (GLFW) key code as the game's, or -1 */
|
||||||
|
public static int toGame(int key) {
|
||||||
|
//? if >=26.3 {
|
||||||
|
/*return TO_GAME.get(key);
|
||||||
|
*///?} else
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** A key code from the game as Saturn's (GLFW), or -1 */
|
||||||
|
public static int toSaturn(int key) {
|
||||||
|
//? if >=26.3 {
|
||||||
|
/*return TO_SATURN.get(key);
|
||||||
|
*///?} else
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The game's modifier flags as GLFW's */
|
||||||
|
public static int modifiersToSaturn(int modifiers) {
|
||||||
|
//? if >=26.3 {
|
||||||
|
/*int glfw = 0;
|
||||||
|
if ((modifiers & InputConstants.MOD_SHIFT) != 0) glfw |= GLFWProvider.GLFW_MOD_SHIFT;
|
||||||
|
if ((modifiers & InputConstants.MOD_CONTROL) != 0) glfw |= GLFWProvider.GLFW_MOD_CONTROL;
|
||||||
|
if ((modifiers & InputConstants.MOD_ALT) != 0) glfw |= GLFWProvider.GLFW_MOD_ALT;
|
||||||
|
if ((modifiers & InputConstants.MOD_SUPER) != 0) glfw |= GLFWProvider.GLFW_MOD_SUPER;
|
||||||
|
if ((modifiers & InputConstants.MOD_CAPS_LOCK) != 0) glfw |= GLFWProvider.GLFW_MOD_CAPS_LOCK;
|
||||||
|
if ((modifiers & InputConstants.MOD_NUM_LOCK) != 0) glfw |= GLFWProvider.GLFW_MOD_NUM_LOCK;
|
||||||
|
return glfw;
|
||||||
|
*///?} else
|
||||||
|
return modifiers;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** A mouse button from the game as GLFW's (left 0, right 1, middle 2, then the side buttons) */
|
||||||
|
public static int mouseButtonToSaturn(int button) {
|
||||||
|
//? if >=26.3 {
|
||||||
|
/*if (button == InputConstants.MOUSE_BUTTON_LEFT) return 0;
|
||||||
|
if (button == InputConstants.MOUSE_BUTTON_RIGHT) return 1;
|
||||||
|
if (button == InputConstants.MOUSE_BUTTON_MIDDLE) return 2;
|
||||||
|
return button - 1;
|
||||||
|
*///?} else
|
||||||
|
return button;
|
||||||
|
}
|
||||||
|
}
|
||||||
+5
-5
@@ -4,22 +4,22 @@ import org.saturnclient.common.provider.RefConstructorProvider;
|
|||||||
import org.saturnclient.common.ref.asset.IdentifierRef;
|
import org.saturnclient.common.ref.asset.IdentifierRef;
|
||||||
import org.saturnclient.common.ref.render.TextRef;
|
import org.saturnclient.common.ref.render.TextRef;
|
||||||
|
|
||||||
import net.minecraft.text.Text;
|
import net.minecraft.network.chat.Component;
|
||||||
import net.minecraft.util.Identifier;
|
import net.minecraft.resources.Identifier;
|
||||||
|
|
||||||
public class RefConstructorImpl implements RefConstructorProvider {
|
public class RefConstructorImpl implements RefConstructorProvider {
|
||||||
@Override
|
@Override
|
||||||
public IdentifierRef identifier(String fullPath) {
|
public IdentifierRef identifier(String fullPath) {
|
||||||
return (IdentifierRef) (Object) Identifier.of(fullPath);
|
return (IdentifierRef) (Object) Identifier.parse(fullPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IdentifierRef identifier(String namespace, String path) {
|
public IdentifierRef identifier(String namespace, String path) {
|
||||||
return (IdentifierRef) (Object) Identifier.of(namespace, path);
|
return (IdentifierRef) (Object) Identifier.fromNamespaceAndPath(namespace, path);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public TextRef text(String text) {
|
public TextRef text(String text) {
|
||||||
return (TextRef) Text.literal(text);
|
return (TextRef) (Component) Component.literal(text);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
package org.saturnclient.impl.provider;
|
||||||
|
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
import net.fabricmc.loader.api.FabricLoader;
|
||||||
|
import net.minecraft.client.Minecraft;
|
||||||
|
import net.minecraft.client.gui.components.toasts.SystemToast;
|
||||||
|
//? if >=1.21.9 {
|
||||||
|
import net.minecraft.world.entity.player.PlayerSkin;
|
||||||
|
//?} else
|
||||||
|
//import net.minecraft.client.resources.PlayerSkin;
|
||||||
|
import net.minecraft.network.chat.Component;
|
||||||
|
import net.minecraft.resources.Identifier;
|
||||||
|
|
||||||
|
import org.saturnclient.common.provider.SaturnProvider;
|
||||||
|
import org.saturnclient.common.ref.asset.IdentifierRef;
|
||||||
|
import org.saturnclient.common.ref.game.MinecraftClientRef;
|
||||||
|
import org.saturnclient.cosmetics.Emotes;
|
||||||
|
import org.saturnclient.impl.cosmetics.utils.IdentifierUtils;
|
||||||
|
import org.saturnclient.saturnclient.SaturnClient;
|
||||||
|
|
||||||
|
public class SaturnProviderImpl implements SaturnProvider {
|
||||||
|
/** Looks up the signed-in player's skin; made once, since the skin provider caches it */
|
||||||
|
private Supplier<Identifier> playerSkin;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MinecraftClientRef getClient() {
|
||||||
|
return (MinecraftClientRef) SaturnClient.client;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void playEmote(UUID fromPlayerUuid, String emoteIdOrNull) {
|
||||||
|
if (fromPlayerUuid == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// An empty or unknown ID stops whatever the player is playing.
|
||||||
|
Emotes.get(emoteIdOrNull == null ? "" : emoteIdOrNull).ifPresentOrElse(
|
||||||
|
emote -> org.saturnclient.emotes.fabric.Emotes.play(fromPlayerUuid, emote.animation()),
|
||||||
|
() -> org.saturnclient.emotes.fabric.Emotes.stop(fromPlayerUuid));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void logInfo(String message) {
|
||||||
|
SaturnClient.LOGGER.info(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void logError(String message) {
|
||||||
|
SaturnClient.LOGGER.error(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void logError(String message, Throwable throwable) {
|
||||||
|
SaturnClient.LOGGER.error(message, throwable);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Shown longer than the vanilla default, since these ask the player to act. */
|
||||||
|
private static final SystemToast.SystemToastId WARNING_TOAST = new SystemToast.SystemToastId(15_000L);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IdentifierRef getPlayerSkin() {
|
||||||
|
if (playerSkin == null) {
|
||||||
|
Minecraft client = Minecraft.getInstance();
|
||||||
|
// 1.21.9 moved SkinTextures and renamed the skin provider's lookup.
|
||||||
|
//? if >=1.21.9 {
|
||||||
|
Supplier<PlayerSkin> textures = client.getSkinManager().createLookup(client.getGameProfile(), false);
|
||||||
|
playerSkin = () -> textures.get().body().texturePath();
|
||||||
|
//?} else {
|
||||||
|
/*Supplier<PlayerSkin> textures = client.getSkinManager().lookupInsecure(client.getGameProfile());
|
||||||
|
playerSkin = () -> textures.get().texture();
|
||||||
|
*///?}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (IdentifierRef) (Object) playerSkin.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getModVersion() {
|
||||||
|
return FabricLoader.getInstance().getModContainer(SaturnClient.MOD_ID)
|
||||||
|
.map(mod -> mod.getMetadata().getVersion().getFriendlyString())
|
||||||
|
.orElse("unknown");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void showWarning(String title, String message) {
|
||||||
|
Minecraft client = Minecraft.getInstance();
|
||||||
|
//? if >=26.2 {
|
||||||
|
/*client.execute(() -> SystemToast.add(client.gui.toastManager(), WARNING_TOAST, Component.literal(title),
|
||||||
|
Component.literal(message)));
|
||||||
|
*///?} else {
|
||||||
|
client.execute(() -> client.getToastManager().addToast(
|
||||||
|
SystemToast.multiline(client, WARNING_TOAST, Component.literal(title), Component.literal(message))));
|
||||||
|
//?}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void registerBufferedImageTexture(IdentifierRef i, BufferedImage bi) {
|
||||||
|
IdentifierUtils.registerBufferedImageTextureFast(i, bi);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,175 @@
|
|||||||
|
package org.saturnclient.impl.ui;
|
||||||
|
|
||||||
|
// Entities in the UI are drawn through the GUI render state from 1.21.6, and straight into vertex
|
||||||
|
// consumers before. This file has one implementation for each side.
|
||||||
|
//? if >=1.21.6 {
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
import org.joml.Quaternionf;
|
||||||
|
import org.joml.Vector3f;
|
||||||
|
import org.saturnclient.saturnclient.SaturnClient;
|
||||||
|
import org.saturnclient.ui.RenderScope;
|
||||||
|
import org.saturnclient.ui.components.SkinPreview.EntityDrawer;
|
||||||
|
|
||||||
|
//? if >=1.21.9 {
|
||||||
|
import net.minecraft.client.renderer.entity.EntityRenderDispatcher;
|
||||||
|
//?} else
|
||||||
|
//import net.minecraft.client.renderer.entity.EntityRenderDispatcher;
|
||||||
|
import net.minecraft.client.renderer.entity.EntityRenderer;
|
||||||
|
import net.minecraft.client.renderer.entity.state.EntityRenderState;
|
||||||
|
import net.minecraft.world.entity.LivingEntity;
|
||||||
|
|
||||||
|
public class EntityDrawerImpl implements EntityDrawer {
|
||||||
|
@Override
|
||||||
|
public boolean render(RenderScope renderScope, int width, int height, float yaw) {
|
||||||
|
LivingEntity entity = SaturnClient.client.player;
|
||||||
|
|
||||||
|
if (entity == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The player is about 1.8 blocks tall; leave some room above and below.
|
||||||
|
drawEntity(renderScope, 0, 0, width, height, (int) (height / 2.2F), 0.0625F, yaw, entity);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draws the entity in the box, turned by yaw degrees (0 faces the viewer) with its head straight.
|
||||||
|
public static void drawEntity(RenderScope renderScope, int x1, int y1, int x2, int y2, int size, float scale,
|
||||||
|
float yaw, LivingEntity entity) {
|
||||||
|
renderScope.enableScissor(x1, y1, x2, y2);
|
||||||
|
Quaternionf quaternionf = (new Quaternionf()).rotateZ((float) Math.PI);
|
||||||
|
Quaternionf quaternionf2 = new Quaternionf();
|
||||||
|
float j = entity.yBodyRot;
|
||||||
|
float k = entity.getYRot();
|
||||||
|
float l = entity.getXRot();
|
||||||
|
float m = entity.yHeadRotO;
|
||||||
|
float n = entity.yHeadRot;
|
||||||
|
entity.yBodyRot = 180.0F + yaw;
|
||||||
|
entity.setYRot(180.0F + yaw);
|
||||||
|
entity.setXRot(0.0F);
|
||||||
|
entity.yHeadRot = entity.getYRot();
|
||||||
|
entity.yHeadRotO = entity.getYRot();
|
||||||
|
float o = entity.getScale();
|
||||||
|
Vector3f vector3f = new Vector3f(0.0F, entity.getBbHeight() / 2.0F + scale * o, 0.0F);
|
||||||
|
float p = (float) size / o;
|
||||||
|
drawEntity(renderScope, x1, y1, x2, y2, p, vector3f, quaternionf, quaternionf2, entity);
|
||||||
|
entity.yBodyRot = j;
|
||||||
|
entity.setYRot(k);
|
||||||
|
entity.setXRot(l);
|
||||||
|
entity.yHeadRotO = m;
|
||||||
|
entity.yHeadRot = n;
|
||||||
|
renderScope.disableScissor();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void drawEntity(RenderScope renderScope, int x1, int y1, int x2, int y2, float scale,
|
||||||
|
Vector3f translation, Quaternionf rotation, @Nullable Quaternionf overrideCameraAngle,
|
||||||
|
LivingEntity entity) {
|
||||||
|
//? if >=1.21.9 {
|
||||||
|
EntityRenderDispatcher entityRenderDispatcher = SaturnClient.client.getEntityRenderDispatcher();
|
||||||
|
//?} else
|
||||||
|
//EntityRenderDispatcher entityRenderDispatcher = SaturnClient.client.getEntityRenderDispatcher();
|
||||||
|
EntityRenderer<? super LivingEntity, ?> entityRenderer = entityRenderDispatcher.getRenderer(entity);
|
||||||
|
EntityRenderState entityRenderState = entityRenderer.createRenderState(entity, 1.0F);
|
||||||
|
//? if <1.21.11
|
||||||
|
//entityRenderState.hitboxesRenderState = null;
|
||||||
|
|
||||||
|
if (renderScope instanceof RenderScopeImpl i)
|
||||||
|
i.addEntity(entityRenderState, scale, translation, rotation, overrideCameraAngle, x1, y1, x2, y2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//?} else {
|
||||||
|
/*import org.jetbrains.annotations.Nullable;
|
||||||
|
import org.joml.Quaternionf;
|
||||||
|
import org.joml.Vector3f;
|
||||||
|
import org.saturnclient.saturnclient.SaturnClient;
|
||||||
|
import org.saturnclient.ui.RenderScope;
|
||||||
|
import org.saturnclient.ui.components.SkinPreview.EntityDrawer;
|
||||||
|
|
||||||
|
import com.mojang.blaze3d.platform.Lighting;
|
||||||
|
import net.minecraft.client.renderer.entity.EntityRenderDispatcher;
|
||||||
|
import net.minecraft.world.entity.LivingEntity;
|
||||||
|
|
||||||
|
public class EntityDrawerImpl implements EntityDrawer {
|
||||||
|
// Draws the entity in the box, turned by yaw degrees (0 faces the viewer) with its head straight.
|
||||||
|
public static void drawEntity(RenderScope renderScope, int x1, int y1, int x2, int y2, int size, float f,
|
||||||
|
float yaw, LivingEntity entity) {
|
||||||
|
float g = (float) (x1 + x2) / 2.0F;
|
||||||
|
float h = (float) (y1 + y2) / 2.0F;
|
||||||
|
renderScope.enableScissor(x1, y1, x2, y2);
|
||||||
|
Quaternionf quaternionf = (new Quaternionf()).rotateZ(3.1415927F);
|
||||||
|
float k = entity.yBodyRot;
|
||||||
|
float l = entity.getYRot();
|
||||||
|
float m = entity.getXRot();
|
||||||
|
//? if >=1.21.5
|
||||||
|
float n = entity.yHeadRotO;
|
||||||
|
//? if <1.21.5
|
||||||
|
//float n = entity.yHeadRotO;
|
||||||
|
float o = entity.yHeadRot;
|
||||||
|
entity.setYRot(180.0F + yaw);
|
||||||
|
entity.yBodyRot = 180.0F + yaw;
|
||||||
|
entity.setXRot(0.0F);
|
||||||
|
entity.yHeadRot = entity.getYRot();
|
||||||
|
//? if >=1.21.5
|
||||||
|
entity.yHeadRotO = entity.getYHeadRot();
|
||||||
|
//? if <1.21.5
|
||||||
|
//entity.yHeadRotO = entity.getYHeadRot();
|
||||||
|
entity.setCustomNameVisible(false);
|
||||||
|
|
||||||
|
float p = entity.getScale();
|
||||||
|
Vector3f vector3f = new Vector3f(0.0F, entity.getBbHeight() / 2.0F + f * p, 0.0F);
|
||||||
|
float q = (float) size / p;
|
||||||
|
drawEntity(renderScope, g, h, q, vector3f, quaternionf, null, entity);
|
||||||
|
|
||||||
|
entity.setCustomNameVisible(true);
|
||||||
|
entity.yBodyRot = k;
|
||||||
|
entity.setYRot(l);
|
||||||
|
entity.setXRot(m);
|
||||||
|
//? if >=1.21.5
|
||||||
|
entity.yHeadRotO = n;
|
||||||
|
//? if <1.21.5
|
||||||
|
//entity.yHeadRotO = n;
|
||||||
|
entity.yHeadRot = o;
|
||||||
|
renderScope.disableScissor();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void drawEntity(RenderScope renderScope, float x, float y, float size, Vector3f vector3f,
|
||||||
|
Quaternionf quaternionf, @Nullable Quaternionf quaternionf2, LivingEntity entity) {
|
||||||
|
if (renderScope instanceof RenderScopeImpl scopeImpl) {
|
||||||
|
scopeImpl.matrices.pushPose();
|
||||||
|
scopeImpl.matrices.translate((double) x, (double) y, 0);
|
||||||
|
scopeImpl.matrices.scale(size, size, -size);
|
||||||
|
scopeImpl.matrices.translate(vector3f.x, vector3f.y, vector3f.z);
|
||||||
|
scopeImpl.matrices.mulPose(quaternionf);
|
||||||
|
scopeImpl.draw();
|
||||||
|
//? if >=1.21.5
|
||||||
|
Lighting.setupForFlatItems();
|
||||||
|
//? if <1.21.5
|
||||||
|
//Lighting.setupForEntityInInventory();
|
||||||
|
EntityRenderDispatcher entityRenderDispatcher = SaturnClient.client.getEntityRenderDispatcher();
|
||||||
|
|
||||||
|
entityRenderDispatcher.setRenderShadow(false);
|
||||||
|
scopeImpl.draw((vertexConsumers) -> {
|
||||||
|
entityRenderDispatcher.render(entity, 0.0, 0.0, 0.0, 1.0F,
|
||||||
|
scopeImpl.matrices, vertexConsumers, 15728880);
|
||||||
|
});
|
||||||
|
|
||||||
|
scopeImpl.draw();
|
||||||
|
entityRenderDispatcher.setRenderShadow(true);
|
||||||
|
scopeImpl.getMatrixStack().pop();
|
||||||
|
Lighting.setupFor3DItems();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean render(RenderScope renderScope, int width, int height, float yaw) {
|
||||||
|
LivingEntity entity = SaturnClient.client.player;
|
||||||
|
if (entity == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The player is about 1.8 blocks tall; leave some room above and below.
|
||||||
|
drawEntity(renderScope, 0, 0, width, height, (int) (height / 2.2F), 0.0625F, yaw, entity);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*///?}
|
||||||
@@ -0,0 +1,459 @@
|
|||||||
|
package org.saturnclient.impl.ui;
|
||||||
|
|
||||||
|
// Glass: a copy of the game behind the screen, blurred, drawn through each panel's rounded rectangle
|
||||||
|
// texture as a mask. There's a copy for each blur strength in use (menus and widgets have their own),
|
||||||
|
// blurred with vanilla's blur at that radius, so it holds the world or panorama without the screen's
|
||||||
|
// own animated background blur.
|
||||||
|
//
|
||||||
|
// The GUI rewrites split this into three implementations:
|
||||||
|
// - 1.21.6+: the GUI is drawn after the screen builds it, so the copy is taken from the main
|
||||||
|
// framebuffer while the screen builds its GUI, and glass is a GUI element with its own pipeline
|
||||||
|
// (shaders/core/glass.fsh). The blur is vanilla's effect rebuilt with the radius set.
|
||||||
|
// - 1.21.5: the screen draws straight into the framebuffer, blurring it first, so the game is saved
|
||||||
|
// before the blur (saveBackground). Glass is a render layer with its own pipeline
|
||||||
|
// (shaders/core/glass_pre_1_21_6.fsh), and the blur takes the radius as it runs.
|
||||||
|
// - 1.21.4: as 1.21.5, with GL framebuffers copied by blitting, and a shader program
|
||||||
|
// (shaders/core/glass.json) instead of a pipeline.
|
||||||
|
//? if >=1.21.6 {
|
||||||
|
import java.io.Reader;
|
||||||
|
import java.util.HashMap;
|
||||||
|
//? if >=26.1
|
||||||
|
//import java.util.LinkedHashSet;
|
||||||
|
import java.util.Map;
|
||||||
|
//? if >=26.1
|
||||||
|
//import java.util.Set;
|
||||||
|
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import com.google.gson.JsonParser;
|
||||||
|
import com.mojang.blaze3d.pipeline.RenderPipeline;
|
||||||
|
import com.mojang.blaze3d.systems.RenderSystem;
|
||||||
|
import com.mojang.blaze3d.textures.FilterMode;
|
||||||
|
import com.mojang.blaze3d.textures.GpuTextureView;
|
||||||
|
import com.mojang.serialization.JsonOps;
|
||||||
|
|
||||||
|
import org.saturnclient.config.Config;
|
||||||
|
import org.saturnclient.saturnclient.SaturnClient;
|
||||||
|
|
||||||
|
import com.mojang.blaze3d.pipeline.RenderTarget;
|
||||||
|
import net.minecraft.client.renderer.PostChainConfig;
|
||||||
|
import net.minecraft.client.renderer.PostChain;
|
||||||
|
import net.minecraft.client.renderer.RenderPipelines;
|
||||||
|
import com.mojang.blaze3d.pipeline.TextureTarget;
|
||||||
|
import net.minecraft.client.renderer.LevelTargetBundle;
|
||||||
|
//? if >=26.2 {
|
||||||
|
/*import com.mojang.blaze3d.GpuFormat;
|
||||||
|
import net.minecraft.client.renderer.BindGroupLayouts;
|
||||||
|
*///?}
|
||||||
|
//? if >=26.1 {
|
||||||
|
/*import net.minecraft.client.renderer.Projection;
|
||||||
|
import net.minecraft.client.renderer.ProjectionMatrixBuffer;
|
||||||
|
*///?} else
|
||||||
|
import net.minecraft.client.renderer.CachedOrthoProjectionMatrixBuffer;
|
||||||
|
import net.minecraft.client.renderer.texture.AbstractTexture;
|
||||||
|
import net.minecraft.client.gui.render.TextureSetup;
|
||||||
|
import com.mojang.blaze3d.resource.CrossFrameResourcePool;
|
||||||
|
import net.minecraft.resources.Identifier;
|
||||||
|
|
||||||
|
public final class GlassRenderer {
|
||||||
|
public static final RenderPipeline PIPELINE = RenderPipeline.builder(RenderPipelines.GUI_TEXTURED_SNIPPET)
|
||||||
|
.withLocation(Identifier.fromNamespaceAndPath("saturnclient", "pipeline/glass"))
|
||||||
|
.withFragmentShader(Identifier.fromNamespaceAndPath("saturnclient", "core/glass"))
|
||||||
|
//? if >=26.2 {
|
||||||
|
/*.withBindGroupLayout(BindGroupLayouts.SAMPLER1)
|
||||||
|
*///?} else
|
||||||
|
.withSampler("Sampler1")
|
||||||
|
// The transforms block ends with LineWidth before 1.21.11 (see glass.fsh).
|
||||||
|
//? if <1.21.11
|
||||||
|
//.withShaderDefine("HAS_LINE_WIDTH")
|
||||||
|
// 26.3 compiles through SPIR-V and moved TextureMat up the transforms block.
|
||||||
|
//? if >=26.3
|
||||||
|
//.withShaderDefine("EXPLICIT_LOCATIONS").withShaderDefine("TEXTURE_MAT_SECOND")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
private static final Identifier BLUR = Identifier.withDefaultNamespace("post_effect/blur.json");
|
||||||
|
//? if >=26.1 {
|
||||||
|
/*private static final Projection PROJECTION = new Projection();
|
||||||
|
private static final ProjectionMatrixBuffer PROJECTION_BUFFER = new ProjectionMatrixBuffer("saturn glass");
|
||||||
|
|
||||||
|
static {
|
||||||
|
// As vanilla sets up its post chains' projection; the chain sizes it when it runs.
|
||||||
|
PROJECTION.setupOrtho(0.1f, 1000f, 1f, 1f, false);
|
||||||
|
}
|
||||||
|
*///?} else
|
||||||
|
private static final CachedOrthoProjectionMatrixBuffer PROJECTION = new CachedOrthoProjectionMatrixBuffer("saturn glass", 0.1f, 1000f, false);
|
||||||
|
// For the blurs' intermediate targets, freed once unused for a few frames
|
||||||
|
private static final CrossFrameResourcePool POOL = new CrossFrameResourcePool(3);
|
||||||
|
|
||||||
|
// By blur strength. A blur that failed to load is kept as null, so it isn't retried every frame.
|
||||||
|
private static final Map<Integer, PostChain> BLURS = new HashMap<>();
|
||||||
|
private static final Map<Integer, TextureTarget> BACKDROPS = new HashMap<>();
|
||||||
|
|
||||||
|
private GlassRenderer() {
|
||||||
|
}
|
||||||
|
|
||||||
|
// From 26.1 the GUI is built before the frame's world is drawn, so the main framebuffer still
|
||||||
|
// holds the last frame, glass included. The copies wait until the GUI renderer is about to draw
|
||||||
|
// (GuiRendererMixin), when it holds this frame's world and panorama.
|
||||||
|
//? if >=26.1 {
|
||||||
|
/*private static final Set<Integer> PENDING = new LinkedHashSet<>();
|
||||||
|
*///?}
|
||||||
|
|
||||||
|
private static RenderTarget mainTarget() {
|
||||||
|
//? if >=26.2 {
|
||||||
|
/*return SaturnClient.client.gameRenderer.mainRenderTarget();
|
||||||
|
*///?} else
|
||||||
|
return SaturnClient.client.getMainRenderTarget();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copies the main framebuffer and blurs the copy by blur. Call once a frame for each blur, while
|
||||||
|
// the screen builds its GUI, before drawing glass with it.
|
||||||
|
public static void captureBackdrop(int blur) {
|
||||||
|
RenderTarget main = mainTarget();
|
||||||
|
int width = main.width;
|
||||||
|
int height = main.height;
|
||||||
|
|
||||||
|
TextureTarget backdrop = BACKDROPS.get(blur);
|
||||||
|
if (backdrop == null) {
|
||||||
|
// From 26.3 the depth format picks the depth buffer (none here); 26.2 takes a flag.
|
||||||
|
//? if >=26.3 {
|
||||||
|
/*backdrop = new TextureTarget("Saturn glass " + blur, width, height, GpuFormat.RGBA8_UNORM, null);
|
||||||
|
*///?} else if >=26.2 {
|
||||||
|
/*backdrop = new TextureTarget("Saturn glass " + blur, width, height, false, GpuFormat.RGBA8_UNORM);
|
||||||
|
*///?} else
|
||||||
|
backdrop = new TextureTarget("Saturn glass " + blur, width, height, false);
|
||||||
|
BACKDROPS.put(blur, backdrop);
|
||||||
|
} else if (backdrop.width != width || backdrop.height != height) {
|
||||||
|
backdrop.resize(width, height);
|
||||||
|
}
|
||||||
|
//? if <1.21.11
|
||||||
|
//backdrop.getColorTexture().setTextureFilter(FilterMode.LINEAR, false);
|
||||||
|
|
||||||
|
//? if >=26.1 {
|
||||||
|
/*PENDING.add(blur);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copies and blurs the backdrops the GUI asked for this frame. GuiRendererMixin calls it once the
|
||||||
|
// world and the panorama are drawn, before the GUI.
|
||||||
|
public static void blurPendingBackdrops() {
|
||||||
|
RenderTarget main = mainTarget();
|
||||||
|
for (int blur : PENDING) {
|
||||||
|
TextureTarget backdrop = BACKDROPS.get(blur);
|
||||||
|
// The GUI already refers to the backdrop's texture, so after a resize it keeps the last
|
||||||
|
// frame's until the next one.
|
||||||
|
if (backdrop != null && backdrop.width == main.width && backdrop.height == main.height) {
|
||||||
|
blurBackdrop(main, backdrop, blur);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PENDING.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void blurBackdrop(RenderTarget main, TextureTarget backdrop, int blur) {
|
||||||
|
int width = main.width;
|
||||||
|
int height = main.height;
|
||||||
|
*///?}
|
||||||
|
RenderSystem.getDevice().createCommandEncoder().copyTextureToTexture(
|
||||||
|
main.getColorTexture(), backdrop.getColorTexture(), 0, 0, 0, 0, 0, width, height);
|
||||||
|
|
||||||
|
PostChain processor = getBlur(blur);
|
||||||
|
if (processor != null) {
|
||||||
|
processor.process(backdrop, POOL);
|
||||||
|
}
|
||||||
|
POOL.endFrame();
|
||||||
|
release(blur);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The mask, then the copy blurred by blur, as the glass pipeline samples them
|
||||||
|
public static TextureSetup textures(AbstractTexture mask, int blur) {
|
||||||
|
GpuTextureView backdrop = BACKDROPS.get(blur).getColorTextureView();
|
||||||
|
//? if >=1.21.11
|
||||||
|
return TextureSetup.doubleTexture(mask.getTextureView(), mask.getSampler(), backdrop, RenderSystem.getSamplerCache().getClampToEdge(FilterMode.LINEAR));
|
||||||
|
//? if <1.21.11
|
||||||
|
//return TextureSetup.doubleTexture(mask.getTextureView(), backdrop);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Frees the copies and blurs of strengths that are no longer set, besides kept
|
||||||
|
private static void release(int kept) {
|
||||||
|
BACKDROPS.entrySet().removeIf(entry -> {
|
||||||
|
boolean unused = entry.getKey() != kept && !isSet(entry.getKey());
|
||||||
|
// The GUI already refers to this frame's pending backdrops.
|
||||||
|
//? if >=26.1
|
||||||
|
//unused &= !PENDING.contains(entry.getKey());
|
||||||
|
if (unused) {
|
||||||
|
entry.getValue().destroyBuffers();
|
||||||
|
}
|
||||||
|
return unused;
|
||||||
|
});
|
||||||
|
BLURS.entrySet().removeIf(entry -> {
|
||||||
|
boolean unused = entry.getKey() != kept && !isSet(entry.getKey());
|
||||||
|
// The GUI already refers to this frame's pending backdrops.
|
||||||
|
//? if >=26.1
|
||||||
|
//unused &= !PENDING.contains(entry.getKey());
|
||||||
|
if (unused && entry.getValue() != null) {
|
||||||
|
entry.getValue().close();
|
||||||
|
}
|
||||||
|
return unused;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isSet(int blur) {
|
||||||
|
return blur == Config.menuBlur.value || blur == Config.widgetBlur.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static PostChain getBlur(int blur) {
|
||||||
|
if (!BLURS.containsKey(blur)) {
|
||||||
|
BLURS.put(blur, loadBlur(blur));
|
||||||
|
}
|
||||||
|
return BLURS.get(blur);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vanilla's blur, with its radius set to blur. Vanilla's own leaves it at 0, which makes the shader
|
||||||
|
// read the menu blur setting (Saturn's animated background blur while a Saturn screen is open).
|
||||||
|
private static PostChain loadBlur(int blur) {
|
||||||
|
try (Reader reader = SaturnClient.client.getResourceManager().getResourceOrThrow(BLUR).openAsReader()) {
|
||||||
|
JsonElement json = JsonParser.parseReader(reader);
|
||||||
|
setRadius(json, blur);
|
||||||
|
|
||||||
|
PostChainConfig pipeline = PostChainConfig.CODEC.parse(JsonOps.INSTANCE, json).getOrThrow();
|
||||||
|
return PostChain.load(pipeline, SaturnClient.client.getTextureManager(),
|
||||||
|
//? if >=26.1 {
|
||||||
|
/*LevelTargetBundle.MAIN_TARGETS, Identifier.fromNamespaceAndPath("saturnclient", "glass_blur_" + blur), PROJECTION, PROJECTION_BUFFER);
|
||||||
|
*///?} else
|
||||||
|
LevelTargetBundle.MAIN_TARGETS, Identifier.fromNamespaceAndPath("saturnclient", "glass_blur_" + blur), PROJECTION);
|
||||||
|
} catch (Exception e) {
|
||||||
|
SaturnClient.LOGGER.error("Couldn't load the glass blur", e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sets every "Radius" uniform in the effect
|
||||||
|
private static void setRadius(JsonElement json, int radius) {
|
||||||
|
if (json.isJsonArray()) {
|
||||||
|
json.getAsJsonArray().forEach(element -> setRadius(element, radius));
|
||||||
|
} else if (json.isJsonObject()) {
|
||||||
|
JsonObject object = json.getAsJsonObject();
|
||||||
|
if (object.has("name") && object.get("name").getAsString().equals("Radius")) {
|
||||||
|
object.addProperty("value", (float) radius);
|
||||||
|
}
|
||||||
|
object.entrySet().forEach(entry -> setRadius(entry.getValue(), radius));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//?} else if >=1.21.5 {
|
||||||
|
/*import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import com.mojang.blaze3d.pipeline.RenderPipeline;
|
||||||
|
import com.mojang.blaze3d.systems.RenderSystem;
|
||||||
|
import com.mojang.blaze3d.textures.FilterMode;
|
||||||
|
|
||||||
|
import org.saturnclient.config.Config;
|
||||||
|
import org.saturnclient.saturnclient.SaturnClient;
|
||||||
|
|
||||||
|
import com.mojang.blaze3d.pipeline.RenderTarget;
|
||||||
|
import net.minecraft.client.renderer.PostChain;
|
||||||
|
import net.minecraft.client.renderer.RenderPipelines;
|
||||||
|
import com.mojang.blaze3d.pipeline.TextureTarget;
|
||||||
|
import net.minecraft.client.renderer.LevelTargetBundle;
|
||||||
|
import net.minecraft.client.renderer.RenderType;
|
||||||
|
import net.minecraft.client.renderer.RenderStateShard;
|
||||||
|
import com.mojang.blaze3d.resource.CrossFrameResourcePool;
|
||||||
|
import net.minecraft.resources.Identifier;
|
||||||
|
|
||||||
|
public final class GlassRenderer {
|
||||||
|
private static final RenderPipeline PIPELINE = RenderPipeline.builder(RenderPipelines.GUI_TEXTURED_SNIPPET)
|
||||||
|
.withLocation(Identifier.fromNamespaceAndPath("saturnclient", "pipeline/glass"))
|
||||||
|
.withFragmentShader(Identifier.fromNamespaceAndPath("saturnclient", "core/glass_pre_1_21_6"))
|
||||||
|
.withSampler("Sampler1")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
private static final Identifier BLUR = Identifier.withDefaultNamespace("blur");
|
||||||
|
// For the blur's intermediate target, freed once unused for a few frames
|
||||||
|
private static final CrossFrameResourcePool POOL = new CrossFrameResourcePool(3);
|
||||||
|
|
||||||
|
private static final Map<Integer, TextureTarget> BACKDROPS = new HashMap<>();
|
||||||
|
// By mask and blur
|
||||||
|
private static final Map<String, RenderType> LAYERS = new HashMap<>();
|
||||||
|
// The game behind the screen before the screen blurs it, which glass is blurred from
|
||||||
|
private static TextureTarget background;
|
||||||
|
|
||||||
|
private GlassRenderer() {
|
||||||
|
}
|
||||||
|
|
||||||
|
// Saves the main framebuffer for glass. Call each frame before the screen blurs its background.
|
||||||
|
public static void saveBackground() {
|
||||||
|
background = copy(SaturnClient.client.getMainRenderTarget(), background, "Saturn glass background");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copies the saved background and blurs the copy by blur. Call once a frame for each blur, before
|
||||||
|
// drawing glass with it.
|
||||||
|
public static void captureBackdrop(int blur) {
|
||||||
|
RenderTarget from = background != null ? background : SaturnClient.client.getMainRenderTarget();
|
||||||
|
TextureTarget backdrop = copy(from, BACKDROPS.get(blur), "Saturn glass " + blur);
|
||||||
|
BACKDROPS.put(blur, backdrop);
|
||||||
|
|
||||||
|
PostChain processor = SaturnClient.client.getShaderManager().getPostChain(BLUR,
|
||||||
|
LevelTargetBundle.MAIN_TARGETS);
|
||||||
|
if (processor != null) {
|
||||||
|
processor.process(backdrop, POOL, pass -> pass.setUniform("Radius", (float) blur));
|
||||||
|
}
|
||||||
|
POOL.endFrame();
|
||||||
|
release(blur);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draws glass through mask with the copy blurred by blur
|
||||||
|
public static RenderType getLayer(Identifier mask, int blur) {
|
||||||
|
return LAYERS.computeIfAbsent(mask + " " + blur, key -> RenderType.create("saturn_glass", 786432, PIPELINE,
|
||||||
|
RenderType.CompositeState.builder()
|
||||||
|
.setTextureState(new RenderStateShard.EmptyTextureStateShard(() -> {
|
||||||
|
RenderSystem.setShaderTexture(0,
|
||||||
|
SaturnClient.client.getTextureManager().getTexture(mask).getTexture());
|
||||||
|
RenderSystem.setShaderTexture(1, BACKDROPS.get(blur).getColorTexture());
|
||||||
|
}, () -> {
|
||||||
|
}))
|
||||||
|
.createCompositeState(false)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Frees the copies of strengths that are no longer set, besides kept
|
||||||
|
private static void release(int kept) {
|
||||||
|
BACKDROPS.entrySet().removeIf(entry -> {
|
||||||
|
boolean unused = entry.getKey() != kept && entry.getKey() != Config.menuBlur.value
|
||||||
|
&& entry.getKey() != Config.widgetBlur.value;
|
||||||
|
if (unused) {
|
||||||
|
entry.getValue().destroyBuffers();
|
||||||
|
}
|
||||||
|
return unused;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copies from into to, made or resized to match
|
||||||
|
private static TextureTarget copy(RenderTarget from, TextureTarget to, String name) {
|
||||||
|
int width = from.width;
|
||||||
|
int height = from.height;
|
||||||
|
|
||||||
|
if (to == null) {
|
||||||
|
to = new TextureTarget(name, width, height, false);
|
||||||
|
} else if (to.width != width || to.height != height) {
|
||||||
|
to.resize(width, height);
|
||||||
|
}
|
||||||
|
to.getColorTexture().setTextureFilter(FilterMode.LINEAR, false);
|
||||||
|
|
||||||
|
RenderSystem.getDevice().createCommandEncoder().copyTextureToTexture(
|
||||||
|
from.getColorTexture(), to.getColorTexture(), 0, 0, 0, 0, 0, width, height);
|
||||||
|
return to;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*///?} else {
|
||||||
|
/*import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.lwjgl.opengl.GL11;
|
||||||
|
import org.lwjgl.opengl.GL30;
|
||||||
|
|
||||||
|
import com.mojang.blaze3d.platform.GlStateManager;
|
||||||
|
import com.mojang.blaze3d.systems.RenderSystem;
|
||||||
|
|
||||||
|
import org.saturnclient.config.Config;
|
||||||
|
import org.saturnclient.saturnclient.SaturnClient;
|
||||||
|
|
||||||
|
import net.minecraft.client.renderer.ShaderDefines;
|
||||||
|
import com.mojang.blaze3d.pipeline.RenderTarget;
|
||||||
|
import net.minecraft.client.renderer.PostChain;
|
||||||
|
import net.minecraft.client.renderer.ShaderProgram;
|
||||||
|
import com.mojang.blaze3d.pipeline.TextureTarget;
|
||||||
|
import net.minecraft.client.renderer.LevelTargetBundle;
|
||||||
|
import net.minecraft.client.renderer.RenderType;
|
||||||
|
import net.minecraft.client.renderer.RenderStateShard;
|
||||||
|
import com.mojang.blaze3d.vertex.VertexFormat;
|
||||||
|
import com.mojang.blaze3d.vertex.DefaultVertexFormat;
|
||||||
|
import com.mojang.blaze3d.resource.CrossFrameResourcePool;
|
||||||
|
import net.minecraft.resources.Identifier;
|
||||||
|
|
||||||
|
public final class GlassRenderer {
|
||||||
|
private static final ShaderProgram PROGRAM = new ShaderProgram(Identifier.fromNamespaceAndPath("saturnclient", "core/glass"),
|
||||||
|
DefaultVertexFormat.POSITION_TEX_COLOR, ShaderDefines.EMPTY);
|
||||||
|
|
||||||
|
private static final Identifier BLUR = Identifier.withDefaultNamespace("blur");
|
||||||
|
// For the blur's intermediate target, freed once unused for a few frames
|
||||||
|
private static final CrossFrameResourcePool POOL = new CrossFrameResourcePool(3);
|
||||||
|
|
||||||
|
private static final Map<Integer, TextureTarget> BACKDROPS = new HashMap<>();
|
||||||
|
// By mask and blur
|
||||||
|
private static final Map<String, RenderType> LAYERS = new HashMap<>();
|
||||||
|
// The game behind the screen before the screen blurs it, which glass is blurred from
|
||||||
|
private static TextureTarget background;
|
||||||
|
|
||||||
|
private GlassRenderer() {
|
||||||
|
}
|
||||||
|
|
||||||
|
// Saves the main framebuffer for glass. Call each frame before the screen blurs its background.
|
||||||
|
public static void saveBackground() {
|
||||||
|
background = copy(SaturnClient.client.getMainRenderTarget(), background);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copies the saved background and blurs the copy by blur. Call once a frame for each blur, before
|
||||||
|
// drawing glass with it.
|
||||||
|
public static void captureBackdrop(int blur) {
|
||||||
|
RenderTarget from = background != null ? background : SaturnClient.client.getMainRenderTarget();
|
||||||
|
TextureTarget backdrop = copy(from, BACKDROPS.get(blur));
|
||||||
|
BACKDROPS.put(blur, backdrop);
|
||||||
|
|
||||||
|
PostChain processor = SaturnClient.client.getShaderManager().getPostChain(BLUR,
|
||||||
|
LevelTargetBundle.MAIN_TARGETS);
|
||||||
|
if (processor != null) {
|
||||||
|
processor.setUniform("Radius", blur);
|
||||||
|
processor.process(backdrop, POOL);
|
||||||
|
}
|
||||||
|
POOL.endFrame();
|
||||||
|
release(blur);
|
||||||
|
SaturnClient.client.getMainRenderTarget().bindWrite(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draws glass through mask with the copy blurred by blur
|
||||||
|
public static RenderType getLayer(Identifier mask, int blur) {
|
||||||
|
return LAYERS.computeIfAbsent(mask + " " + blur, key -> RenderType.create("saturn_glass",
|
||||||
|
DefaultVertexFormat.POSITION_TEX_COLOR, VertexFormat.Mode.QUADS, 786432,
|
||||||
|
RenderType.CompositeState.builder()
|
||||||
|
.setTextureState(new RenderStateShard.EmptyTextureStateShard(() -> {
|
||||||
|
RenderSystem.setShaderTexture(0, mask);
|
||||||
|
RenderSystem.setShaderTexture(1, BACKDROPS.get(blur).getColorTextureId());
|
||||||
|
}, () -> {
|
||||||
|
}))
|
||||||
|
.setShaderState(new RenderStateShard.ShaderStateShard(PROGRAM))
|
||||||
|
.setTransparencyState(RenderStateShard.TRANSLUCENT_TRANSPARENCY)
|
||||||
|
.createCompositeState(false)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Frees the copies of strengths that are no longer set, besides kept
|
||||||
|
private static void release(int kept) {
|
||||||
|
BACKDROPS.entrySet().removeIf(entry -> {
|
||||||
|
boolean unused = entry.getKey() != kept && entry.getKey() != Config.menuBlur.value
|
||||||
|
&& entry.getKey() != Config.widgetBlur.value;
|
||||||
|
if (unused) {
|
||||||
|
entry.getValue().destroyBuffers();
|
||||||
|
}
|
||||||
|
return unused;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copies from into to, made or resized to match
|
||||||
|
private static TextureTarget copy(RenderTarget from, TextureTarget to) {
|
||||||
|
int width = from.width;
|
||||||
|
int height = from.height;
|
||||||
|
|
||||||
|
if (to == null) {
|
||||||
|
to = new TextureTarget(width, height, false);
|
||||||
|
} else if (to.width != width || to.height != height) {
|
||||||
|
to.resize(width, height);
|
||||||
|
}
|
||||||
|
to.setFilterMode(GL11.GL_LINEAR);
|
||||||
|
|
||||||
|
GlStateManager._glBindFramebuffer(GL30.GL_READ_FRAMEBUFFER, from.frameBufferId);
|
||||||
|
GlStateManager._glBindFramebuffer(GL30.GL_DRAW_FRAMEBUFFER, to.frameBufferId);
|
||||||
|
GlStateManager._glBlitFrameBuffer(0, 0, width, height, 0, 0, width, height, GL11.GL_COLOR_BUFFER_BIT,
|
||||||
|
GL11.GL_NEAREST);
|
||||||
|
SaturnClient.client.getMainRenderTarget().bindWrite(false);
|
||||||
|
return to;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*///?}
|
||||||
@@ -0,0 +1,842 @@
|
|||||||
|
package org.saturnclient.impl.ui;
|
||||||
|
|
||||||
|
// 1.21.6 rewrote GUI rendering: render states and a 2D matrix stack (Matrix3x2fStack) instead of
|
||||||
|
// drawing into vertex consumers with a MatrixStack. This file has one implementation for each side.
|
||||||
|
//? if >=1.21.6 {
|
||||||
|
import java.util.ArrayDeque;
|
||||||
|
import java.util.Deque;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
import org.joml.Matrix3x2f;
|
||||||
|
import org.joml.Matrix3x2fStack;
|
||||||
|
import org.joml.Quaternionf;
|
||||||
|
import org.joml.Vector3f;
|
||||||
|
|
||||||
|
import com.mojang.blaze3d.textures.GpuTextureView;
|
||||||
|
|
||||||
|
import org.saturnclient.common.ref.asset.IdentifierRef;
|
||||||
|
import org.saturnclient.common.ref.asset.SpriteRef;
|
||||||
|
import org.saturnclient.common.ref.game.ItemStackRef;
|
||||||
|
import org.saturnclient.common.ref.render.MatrixStackRef;
|
||||||
|
import org.saturnclient.impl.ref.Matrix3x2fStackRef;
|
||||||
|
import org.saturnclient.saturnclient.SaturnClient;
|
||||||
|
import org.saturnclient.saturnclient.mixin.DrawContextAccessor;
|
||||||
|
import org.saturnclient.ui.RenderScope;
|
||||||
|
import org.saturnclient.ui.resources.Fonts;
|
||||||
|
import org.saturnclient.ui.resources.SvgTexture;
|
||||||
|
|
||||||
|
import net.minecraft.client.renderer.RenderPipelines;
|
||||||
|
import net.minecraft.client.gui.GuiGraphics;
|
||||||
|
import net.minecraft.client.gui.navigation.ScreenRectangle;
|
||||||
|
import net.minecraft.client.gui.render.state.ColoredRectangleRenderState;
|
||||||
|
import net.minecraft.client.gui.render.state.GuiRenderState;
|
||||||
|
import net.minecraft.client.gui.render.state.GuiItemRenderState;
|
||||||
|
import net.minecraft.client.gui.render.state.GuiTextRenderState;
|
||||||
|
import net.minecraft.client.gui.render.state.BlitRenderState;
|
||||||
|
import net.minecraft.client.gui.render.state.pip.GuiEntityRenderState;
|
||||||
|
import net.minecraft.client.renderer.entity.state.EntityRenderState;
|
||||||
|
//? if >=1.21.7 {
|
||||||
|
import net.minecraft.client.renderer.item.TrackingItemStackRenderState;
|
||||||
|
//?} else
|
||||||
|
//import net.minecraft.client.renderer.item.ItemStackRenderState;
|
||||||
|
import net.minecraft.client.renderer.texture.AbstractTexture;
|
||||||
|
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
|
||||||
|
import net.minecraft.client.gui.render.TextureSetup;
|
||||||
|
import net.minecraft.world.entity.LivingEntity;
|
||||||
|
import net.minecraft.world.item.ItemDisplayContext;
|
||||||
|
import net.minecraft.world.item.ItemStack;
|
||||||
|
import net.minecraft.network.chat.Component;
|
||||||
|
import net.minecraft.resources.Identifier;
|
||||||
|
import net.minecraft.ReportedException;
|
||||||
|
import net.minecraft.CrashReport;
|
||||||
|
import net.minecraft.CrashReportCategory;
|
||||||
|
import net.minecraft.world.level.Level;
|
||||||
|
|
||||||
|
public class RenderScopeImpl implements RenderScope {
|
||||||
|
|
||||||
|
public final Matrix3x2fStackRef matrices;
|
||||||
|
public final GuiRenderState state;
|
||||||
|
|
||||||
|
private ScissorStack scissorStack;
|
||||||
|
private int opacity = 0xFF << 24;
|
||||||
|
// The glass blurs made for this scope's frame
|
||||||
|
private final Set<Integer> capturedBlurs = new HashSet<>();
|
||||||
|
|
||||||
|
public RenderScopeImpl(GuiGraphics context) {
|
||||||
|
this(context.pose(), ((DrawContextAccessor) (Object) context).getState());
|
||||||
|
}
|
||||||
|
|
||||||
|
public RenderScopeImpl(Matrix3x2fStack matrices, GuiRenderState state) {
|
||||||
|
this.matrices = new Matrix3x2fStackRef(matrices);
|
||||||
|
this.state = state;
|
||||||
|
this.scissorStack = new ScissorStack();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MatrixStackRef getMatrixStack() {
|
||||||
|
return matrices;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setOpacity(float alpha) {
|
||||||
|
this.opacity = ((int) (alpha * 255)) << 24;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getColor(int color) {
|
||||||
|
int originalAlpha = (color >>> 24) & 0xFF;
|
||||||
|
int newAlpha = (opacity >>> 24) & 0xFF;
|
||||||
|
int mixedAlpha = (originalAlpha * newAlpha) / 255;
|
||||||
|
return (mixedAlpha << 24) | (color & 0x00FFFFFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void fill(int x1, int y1, int x2, int y2, int color) {
|
||||||
|
this.state.submitGuiElement(
|
||||||
|
new ColoredRectangleRenderState(
|
||||||
|
RenderPipelines.GUI,
|
||||||
|
TextureSetup.noTexture(),
|
||||||
|
new Matrix3x2f(this.matrices.stack),
|
||||||
|
x1, y1, x2, y2, color, color,
|
||||||
|
this.scissorStack.peekLast()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void drawRectangle(int x, int y, int width, int height, int color) {
|
||||||
|
if (color == 0)
|
||||||
|
return;
|
||||||
|
color = getColor(color);
|
||||||
|
|
||||||
|
int x1 = x;
|
||||||
|
int x2 = x + width;
|
||||||
|
int y1 = y;
|
||||||
|
int y2 = y + height;
|
||||||
|
|
||||||
|
if (x2 < x1) {
|
||||||
|
int tmp = x1;
|
||||||
|
x1 = x2;
|
||||||
|
x2 = tmp;
|
||||||
|
}
|
||||||
|
if (y2 < y1) {
|
||||||
|
int tmp = y1;
|
||||||
|
y1 = y2;
|
||||||
|
y2 = tmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.fill(x1, y1, x2, y2, color);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void drawText(String text, int x, int y, int font, int color) {
|
||||||
|
drawText(1.0f, text, x, y, font, color);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void drawText(float scale, String text, int x, int y, int font, int color) {
|
||||||
|
if (color == 0)
|
||||||
|
return;
|
||||||
|
if (font == 0)
|
||||||
|
scale *= 2;
|
||||||
|
|
||||||
|
int lineIndex = 0;
|
||||||
|
for (String line : text.split("\n")) {
|
||||||
|
int finalColor = getColor(color);
|
||||||
|
|
||||||
|
matrices.push();
|
||||||
|
matrices.translate(x, y + (lineIndex * Fonts.getHeight()));
|
||||||
|
matrices.scale(scale, scale);
|
||||||
|
|
||||||
|
this.state.submitText(
|
||||||
|
new GuiTextRenderState(
|
||||||
|
SaturnClient.client.font,
|
||||||
|
((Component) Fonts.setFont(line, font)).getVisualOrderText(),
|
||||||
|
new Matrix3x2f(this.matrices.stack),
|
||||||
|
0, font == 0 ? 1 : 8,
|
||||||
|
//? if >=1.21.11 {
|
||||||
|
finalColor, 0, false, false, this.scissorStack.peekLast()));
|
||||||
|
//?} else
|
||||||
|
//finalColor, 0, false, this.scissorStack.peekLast()));
|
||||||
|
|
||||||
|
matrices.pop();
|
||||||
|
|
||||||
|
lineIndex++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getScaledWindowWidth() {
|
||||||
|
return SaturnClient.client.getWindow().getGuiScaledWidth();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getScaledWindowHeight() {
|
||||||
|
return SaturnClient.client.getWindow().getGuiScaledHeight();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Texture rendering
|
||||||
|
@Override
|
||||||
|
public void drawTexture(IdentifierRef sprite, int x, int y, float u, float v, int width, int height) {
|
||||||
|
drawTexture(sprite, x, y, u, v, width, height, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void drawTexture(IdentifierRef sprite, int x, int y, float u, float v, int width, int height, int color) {
|
||||||
|
drawTexture(sprite, x, y, u, v, width, height, width, height, width, height, color);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void drawTexture(IdentifierRef sprite, int x, int y, float u, float v, int width, int height,
|
||||||
|
int regionWidth, int regionHeight, int textureWidth, int textureHeight) {
|
||||||
|
drawTexture(sprite, x, y, u, v, width, height, regionWidth, regionHeight, textureWidth, textureHeight, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void drawTexture(IdentifierRef sprite, int x, int y, float u, float v, int width, int height,
|
||||||
|
int regionWidth, int regionHeight, int textureWidth, int textureHeight, int color) {
|
||||||
|
drawTexturedQuad((Identifier) (Object) sprite, x, x + width, y, y + height,
|
||||||
|
(u + 0f) / textureWidth,
|
||||||
|
(u + regionWidth) / textureWidth,
|
||||||
|
(v + 0f) / textureHeight,
|
||||||
|
(v + regionHeight) / textureHeight,
|
||||||
|
color);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void drawTexturedQuad(Identifier sprite, int x1, int x2, int y1, int y2,
|
||||||
|
float u1, float u2, float v1, float v2, int color) {
|
||||||
|
if (color == 0)
|
||||||
|
return;
|
||||||
|
color = getColor(color);
|
||||||
|
|
||||||
|
if (sprite.getPath().endsWith(".svg")) {
|
||||||
|
sprite = (Identifier) (Object) SvgTexture.getSvg(
|
||||||
|
(IdentifierRef) (Object) sprite,
|
||||||
|
(x2 - x1) * 2, (y2 - y1) * 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
GpuTextureView gpuTextureView = SaturnClient.client.getTextureManager()
|
||||||
|
.getTexture(sprite).getTextureView();
|
||||||
|
|
||||||
|
//? if >=1.21.11
|
||||||
|
AbstractTexture abstractTexture = SaturnClient.client.getTextureManager().getTexture(sprite);
|
||||||
|
|
||||||
|
this.state.submitGuiElement(new BlitRenderState(
|
||||||
|
RenderPipelines.GUI_TEXTURED,
|
||||||
|
//? if >=1.21.11 {
|
||||||
|
TextureSetup.singleTexture(gpuTextureView, abstractTexture.getSampler()),
|
||||||
|
//?} else
|
||||||
|
//TextureSetup.singleTexture(gpuTextureView),
|
||||||
|
new Matrix3x2f(this.matrices.stack),
|
||||||
|
x1, y1, x2, y2, u1, u2, v1, v2, color,
|
||||||
|
this.scissorStack.peekLast()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void drawGlass(IdentifierRef mask, int x, int y, int width, int height, int blur) {
|
||||||
|
int color = getColor(0xFFFFFFFF);
|
||||||
|
if (mask == null || color >>> 24 == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (capturedBlurs.add(blur)) {
|
||||||
|
GlassRenderer.captureBackdrop(blur);
|
||||||
|
}
|
||||||
|
|
||||||
|
AbstractTexture maskTexture = SaturnClient.client.getTextureManager().getTexture((Identifier) (Object) mask);
|
||||||
|
this.state.submitGuiElement(new BlitRenderState(
|
||||||
|
GlassRenderer.PIPELINE,
|
||||||
|
GlassRenderer.textures(maskTexture, blur),
|
||||||
|
new Matrix3x2f(this.matrices.stack),
|
||||||
|
x, y, x + width, y + height, 0, 1, 0, 1, color,
|
||||||
|
this.scissorStack.peekLast()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scissor management
|
||||||
|
@Override
|
||||||
|
public void enableScissor(int x1, int y1, int x2, int y2) {
|
||||||
|
ScreenRectangle rect = new ScreenRectangle(x1, y1, x2 - x1, y2 - y1).transformAxisAligned(this.matrices.stack);
|
||||||
|
scissorStack.push(rect);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void disableScissor() {
|
||||||
|
scissorStack.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean scissorContains(int x, int y) {
|
||||||
|
return scissorStack.contains(x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ScissorStack {
|
||||||
|
private final Deque<ScreenRectangle> stack = new ArrayDeque<>();
|
||||||
|
|
||||||
|
public ScreenRectangle push(ScreenRectangle rect) {
|
||||||
|
ScreenRectangle last = stack.peekLast();
|
||||||
|
if (last != null) {
|
||||||
|
ScreenRectangle intersection = Objects.requireNonNullElse(rect.intersection(last), ScreenRectangle.empty());
|
||||||
|
stack.addLast(intersection);
|
||||||
|
return intersection;
|
||||||
|
} else {
|
||||||
|
stack.addLast(rect);
|
||||||
|
return rect;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public ScreenRectangle pop() {
|
||||||
|
if (stack.isEmpty())
|
||||||
|
throw new IllegalStateException("Scissor stack underflow");
|
||||||
|
stack.removeLast();
|
||||||
|
return stack.peekLast();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public ScreenRectangle peekLast() {
|
||||||
|
return stack.peekLast();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean contains(int x, int y) {
|
||||||
|
return stack.isEmpty() || stack.peekLast().containsPoint(x, y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Item rendering
|
||||||
|
@Override
|
||||||
|
public void drawItem(ItemStackRef stack, int x, int y) {
|
||||||
|
drawItem(null, SaturnClient.client.level, stack, x, y, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void drawItem(@Nullable LivingEntity entity, @Nullable Level world, ItemStackRef stack, int x, int y,
|
||||||
|
int seed) {
|
||||||
|
drawItem(entity, world, (ItemStack) (Object) stack, x, y, seed, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void drawItem(@Nullable LivingEntity entity, @Nullable Level world, ItemStack stack, int x, int y, int seed,
|
||||||
|
int z) {
|
||||||
|
if (!stack.isEmpty()) {
|
||||||
|
//? if >=1.21.7 {
|
||||||
|
TrackingItemStackRenderState keyedItemRenderState = new TrackingItemStackRenderState();
|
||||||
|
//?} else
|
||||||
|
//ItemStackRenderState keyedItemRenderState = new ItemStackRenderState();
|
||||||
|
SaturnClient.client.getItemModelResolver().updateForTopItem(keyedItemRenderState, stack,
|
||||||
|
ItemDisplayContext.GUI, world,
|
||||||
|
entity, seed);
|
||||||
|
|
||||||
|
try {
|
||||||
|
//? if >=26.1 {
|
||||||
|
/*this.state.submitItem(new GuiItemRenderState(
|
||||||
|
*///?} else
|
||||||
|
this.state.submitItem(new GuiItemRenderState(stack.getItem().getName().toString(),
|
||||||
|
new Matrix3x2f(this.matrices.stack), keyedItemRenderState, x, y,
|
||||||
|
this.scissorStack.peekLast()));
|
||||||
|
} catch (Throwable throwable) {
|
||||||
|
CrashReport crashReport = CrashReport.forThrowable(throwable, "Rendering item");
|
||||||
|
CrashReportCategory crashReportSection = crashReport.addCategory("Item being rendered");
|
||||||
|
crashReportSection.setDetail("Item Type", () -> String.valueOf(stack.getItem()));
|
||||||
|
crashReportSection.setDetail("Item Components", () -> String.valueOf(stack.getComponents()));
|
||||||
|
crashReportSection.setDetail("Item Foil", () -> String.valueOf(stack.hasFoil()));
|
||||||
|
throw new ReportedException(crashReport);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void drawSpriteStretched(SpriteRef sprite, int x, int y, int width, int height) {
|
||||||
|
drawSpriteStretched(sprite, x, y, width, height, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void drawSpriteStretched(SpriteRef sprite, int x, int y, int width, int height, int color) {
|
||||||
|
TextureAtlasSprite mcSprite = (TextureAtlasSprite) sprite;
|
||||||
|
if (color == 0 || width == 0 || height == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
drawTexturedQuad(mcSprite.atlasLocation(), x, x + width, y, y + height,
|
||||||
|
mcSprite.getU0(), mcSprite.getU1(), mcSprite.getV0(), mcSprite.getV1(), color);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void draw() {
|
||||||
|
// No-op for now
|
||||||
|
}
|
||||||
|
|
||||||
|
public void drawBorder(int x, int y, int width, int height, int color) {
|
||||||
|
fill(x, y, x + width, y + 1, color);
|
||||||
|
fill(x, y + height - 1, x + width, y + height, color);
|
||||||
|
fill(x, y + 1, x + 1, y + height - 1, color);
|
||||||
|
fill(x + width - 1, y + 1, x + width, y + height - 1, color);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addEntity(EntityRenderState entityState, float scale, Vector3f translation, Quaternionf rotation,
|
||||||
|
@Nullable Quaternionf overrideCameraAngle, int x1, int y1, int x2, int y2) {
|
||||||
|
|
||||||
|
Matrix3x2f mat = this.matrices.stack;
|
||||||
|
float offsetX = mat.m20;
|
||||||
|
float offsetY = mat.m21;
|
||||||
|
float scaleX = mat.m00; // current matrix X scale
|
||||||
|
float scaleY = mat.m11; // current matrix Y scale
|
||||||
|
|
||||||
|
int adjustedX1 = (int) (x1 * scaleX + offsetX);
|
||||||
|
int adjustedY1 = (int) (y1 * scaleY + offsetY);
|
||||||
|
int adjustedX2 = (int) (x2 * scaleX + offsetX);
|
||||||
|
int adjustedY2 = (int) (y2 * scaleY + offsetY);
|
||||||
|
|
||||||
|
float adjustedScale = scale * scaleX; // apply matrix scale to entity scale too
|
||||||
|
|
||||||
|
this.state.submitPicturesInPictureState(new GuiEntityRenderState(entityState, translation, rotation,
|
||||||
|
overrideCameraAngle, adjustedX1, adjustedY1, adjustedX2, adjustedY2, adjustedScale,
|
||||||
|
this.scissorStack.peekLast()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//?} else {
|
||||||
|
/*import java.util.ArrayDeque;
|
||||||
|
import java.util.Deque;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
import org.joml.Matrix4f;
|
||||||
|
|
||||||
|
import com.mojang.blaze3d.systems.RenderSystem;
|
||||||
|
|
||||||
|
import org.saturnclient.common.ref.asset.IdentifierRef;
|
||||||
|
import org.saturnclient.common.ref.asset.SpriteRef;
|
||||||
|
import org.saturnclient.common.ref.game.ItemStackRef;
|
||||||
|
import org.saturnclient.common.ref.render.MatrixStackRef;
|
||||||
|
import org.saturnclient.saturnclient.SaturnClient;
|
||||||
|
import org.saturnclient.saturnclient.mixin.DrawContextAccessor;
|
||||||
|
import org.saturnclient.ui.RenderScope;
|
||||||
|
import org.saturnclient.ui.resources.Fonts;
|
||||||
|
import org.saturnclient.ui.resources.SvgTexture;
|
||||||
|
|
||||||
|
import net.minecraft.client.gui.Font;
|
||||||
|
import net.minecraft.client.gui.Font.DisplayMode;
|
||||||
|
import net.minecraft.client.gui.GuiGraphics;
|
||||||
|
import net.minecraft.client.gui.navigation.ScreenRectangle;
|
||||||
|
import com.mojang.blaze3d.platform.Lighting;
|
||||||
|
import net.minecraft.client.renderer.texture.OverlayTexture;
|
||||||
|
import net.minecraft.client.renderer.RenderType;
|
||||||
|
import com.mojang.blaze3d.vertex.VertexConsumer;
|
||||||
|
import net.minecraft.client.renderer.MultiBufferSource;
|
||||||
|
import net.minecraft.client.renderer.item.ItemStackRenderState;
|
||||||
|
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
|
||||||
|
import com.mojang.blaze3d.platform.Window;
|
||||||
|
import com.mojang.blaze3d.vertex.PoseStack;
|
||||||
|
import net.minecraft.world.entity.LivingEntity;
|
||||||
|
//? if >=1.21.5
|
||||||
|
import net.minecraft.world.item.ItemDisplayContext;
|
||||||
|
//? if <1.21.5
|
||||||
|
//import net.minecraft.world.item.ItemDisplayContext;
|
||||||
|
import net.minecraft.world.item.ItemStack;
|
||||||
|
|
||||||
|
import net.minecraft.network.chat.Component;
|
||||||
|
import net.minecraft.resources.Identifier;
|
||||||
|
import net.minecraft.ReportedException;
|
||||||
|
import net.minecraft.CrashReport;
|
||||||
|
import net.minecraft.CrashReportCategory;
|
||||||
|
import net.minecraft.world.level.Level;
|
||||||
|
|
||||||
|
public class RenderScopeImpl implements RenderScope {
|
||||||
|
// ----------------------------
|
||||||
|
// Fields
|
||||||
|
// ----------------------------
|
||||||
|
public PoseStack matrices;
|
||||||
|
public MultiBufferSource.BufferSource vertexConsumers;
|
||||||
|
private ScissorStack scissorStack = new ScissorStack();
|
||||||
|
private int opacity = 255 << 24;
|
||||||
|
// The glass blurs made for this scope's frame
|
||||||
|
private final Set<Integer> capturedBlurs = new HashSet<>();
|
||||||
|
private final ItemStackRenderState itemRenderState;
|
||||||
|
|
||||||
|
// ----------------------------
|
||||||
|
// Constructors
|
||||||
|
// ----------------------------
|
||||||
|
public RenderScopeImpl(GuiGraphics context) {
|
||||||
|
this(context.pose(), ((DrawContextAccessor) context).getVertexConsumers());
|
||||||
|
}
|
||||||
|
|
||||||
|
public RenderScopeImpl(PoseStack matrices, MultiBufferSource.BufferSource vertexConsumers) {
|
||||||
|
this.matrices = matrices;
|
||||||
|
this.vertexConsumers = vertexConsumers;
|
||||||
|
this.scissorStack = new ScissorStack();
|
||||||
|
this.itemRenderState = new ItemStackRenderState();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------
|
||||||
|
// Matrix & Transform
|
||||||
|
// ----------------------------
|
||||||
|
@Override
|
||||||
|
public MatrixStackRef getMatrixStack() {
|
||||||
|
return (MatrixStackRef) matrices;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------
|
||||||
|
// Color & Opacity
|
||||||
|
// ----------------------------
|
||||||
|
@Override
|
||||||
|
public void setOpacity(float alpha) {
|
||||||
|
this.opacity = (int) (alpha * 255) << 24;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getColor(int color) {
|
||||||
|
int originalAlpha = (color >>> 24) & 0xFF;
|
||||||
|
int newAlpha = (opacity >>> 24) & 0xFF;
|
||||||
|
int mixedAlpha = (originalAlpha * newAlpha) / 255;
|
||||||
|
return (mixedAlpha << 24) | (color & 0x00FFFFFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------
|
||||||
|
// Draw / Window Info
|
||||||
|
// ----------------------------
|
||||||
|
@Override
|
||||||
|
public void draw() {
|
||||||
|
this.vertexConsumers.endBatch();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getScaledWindowWidth() {
|
||||||
|
return SaturnClient.client.getWindow().getGuiScaledWidth();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getScaledWindowHeight() {
|
||||||
|
return SaturnClient.client.getWindow().getGuiScaledHeight();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void draw(Consumer<MultiBufferSource> drawer) {
|
||||||
|
drawer.accept(this.vertexConsumers);
|
||||||
|
this.vertexConsumers.endBatch();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------
|
||||||
|
// Shapes
|
||||||
|
// ----------------------------
|
||||||
|
@Override
|
||||||
|
public void drawRectangle(int x, int y, int width, int height, int color) {
|
||||||
|
if (color == 0)
|
||||||
|
return;
|
||||||
|
color = getColor(color);
|
||||||
|
|
||||||
|
Matrix4f matrix4f = this.matrices.last().pose();
|
||||||
|
|
||||||
|
int x1 = x;
|
||||||
|
int x2 = x + width;
|
||||||
|
int y1 = y;
|
||||||
|
int y2 = y + height;
|
||||||
|
|
||||||
|
if (x1 < x2) {
|
||||||
|
int tmp = x1;
|
||||||
|
x1 = x2;
|
||||||
|
x2 = tmp;
|
||||||
|
}
|
||||||
|
if (y1 < y2) {
|
||||||
|
int tmp = y1;
|
||||||
|
y1 = y2;
|
||||||
|
y2 = tmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
VertexConsumer vertexConsumer = this.vertexConsumers.getBuffer(RenderType.gui());
|
||||||
|
vertexConsumer.addVertex(matrix4f, x1, y1, 0).setColor(color);
|
||||||
|
vertexConsumer.addVertex(matrix4f, x1, y2, 0).setColor(color);
|
||||||
|
vertexConsumer.addVertex(matrix4f, x2, y2, 0).setColor(color);
|
||||||
|
vertexConsumer.addVertex(matrix4f, x2, y1, 0).setColor(color);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void fill(int x1, int y1, int x2, int y2, int color) {
|
||||||
|
fill(x1, y1, x2, y2, 0, color);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void fill(int x1, int y1, int x2, int y2, int z, int color) {
|
||||||
|
fill(RenderType.gui(), x1, y1, x2, y2, z, color);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void fill(RenderType layer, int x1, int y1, int x2, int y2, int color) {
|
||||||
|
fill(layer, x1, y1, x2, y2, 0, color);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void fill(RenderType layer, int x1, int y1, int x2, int y2, int z, int color) {
|
||||||
|
Matrix4f matrix4f = this.matrices.last().pose();
|
||||||
|
if (x1 < x2) {
|
||||||
|
int tmp = x1;
|
||||||
|
x1 = x2;
|
||||||
|
x2 = tmp;
|
||||||
|
}
|
||||||
|
if (y1 < y2) {
|
||||||
|
int tmp = y1;
|
||||||
|
y1 = y2;
|
||||||
|
y2 = tmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
VertexConsumer vertexConsumer = this.vertexConsumers.getBuffer(layer);
|
||||||
|
vertexConsumer.addVertex(matrix4f, x1, y1, z).setColor(color);
|
||||||
|
vertexConsumer.addVertex(matrix4f, x1, y2, z).setColor(color);
|
||||||
|
vertexConsumer.addVertex(matrix4f, x2, y2, z).setColor(color);
|
||||||
|
vertexConsumer.addVertex(matrix4f, x2, y1, z).setColor(color);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void drawBorder(int x, int y, int width, int height, int color) {
|
||||||
|
fill(x, y, x + width, y + 1, color);
|
||||||
|
fill(x, y + height - 1, x + width, y + height, color);
|
||||||
|
fill(x, y + 1, x + 1, y + height - 1, color);
|
||||||
|
fill(x + width - 1, y + 1, x + width, y + height - 1, color);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------
|
||||||
|
// Text
|
||||||
|
// ----------------------------
|
||||||
|
@Override
|
||||||
|
public void drawText(String text, int x, int y, int font, int color) {
|
||||||
|
drawText(1.0f, text, x, y, font, color);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void drawText(float scale, String text, int x, int y, int font, int color) {
|
||||||
|
if (color == 0)
|
||||||
|
return;
|
||||||
|
if (font == 0)
|
||||||
|
scale *= 2;
|
||||||
|
|
||||||
|
int i = 0;
|
||||||
|
for (String line : text.split("\n")) {
|
||||||
|
color = getColor(color);
|
||||||
|
matrices.pushPose();
|
||||||
|
matrices.translate(x, y + i * Fonts.getHeight(), 0);
|
||||||
|
matrices.scale(scale, scale, 1.0f);
|
||||||
|
Font textRenderer = SaturnClient.client.font;
|
||||||
|
textRenderer.drawInBatch((Component) Fonts.setFont(line, font), 0, font == 0 ? 1 : 7, color, false,
|
||||||
|
matrices.last().pose(), vertexConsumers, DisplayMode.NORMAL, 0, 15728880);
|
||||||
|
matrices.popPose();
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------
|
||||||
|
// Textures & Sprites
|
||||||
|
// ----------------------------
|
||||||
|
@Override
|
||||||
|
public void drawTexture(IdentifierRef sprite, int x, int y, float u, float v, int width, int height) {
|
||||||
|
drawTexture(sprite, x, y, u, v, width, height, width, height, width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void drawTexture(IdentifierRef sprite, int x, int y, float u, float v, int width, int height, int color) {
|
||||||
|
drawTexture(sprite, x, y, u, v, width, height, width, height, width, height, color);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void drawTexture(IdentifierRef sprite, int x, int y, float u, float v, int width, int height,
|
||||||
|
int regionWidth, int regionHeight, int textureWidth, int textureHeight) {
|
||||||
|
drawTexture(sprite, x, y, u, v, width, height, regionWidth, regionHeight, textureWidth, textureHeight, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void drawTexture(IdentifierRef sprite, int x, int y, float u, float v, int width, int height,
|
||||||
|
int regionWidth, int regionHeight, int textureWidth, int textureHeight, int color) {
|
||||||
|
drawTexturedQuad((Identifier) (Object) sprite, x, x + width, y, y + height,
|
||||||
|
(u + 0.0f) / textureWidth, (u + regionWidth) / textureWidth,
|
||||||
|
(v + 0.0f) / textureHeight, (v + regionHeight) / textureHeight, color);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void drawSpriteStretched(SpriteRef sprite, int x, int y, int width, int height) {
|
||||||
|
drawSpriteStretched(sprite, x, y, width, height, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void drawSpriteStretched(SpriteRef saturnSprite, int x, int y, int width, int height, int color) {
|
||||||
|
TextureAtlasSprite sprite = (TextureAtlasSprite) saturnSprite;
|
||||||
|
if (color == 0)
|
||||||
|
return;
|
||||||
|
if (width != 0 && height != 0) {
|
||||||
|
drawTexturedQuad(sprite.atlasLocation(), x, x + width, y, y + height,
|
||||||
|
sprite.getU0(), sprite.getU1(), sprite.getV0(), sprite.getV1(), color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void drawTexturedQuad(Identifier sprite, int x1, int x2, int y1, int y2,
|
||||||
|
float u1, float u2, float v1, float v2, int color) {
|
||||||
|
if (sprite.toString().endsWith(".svg")) {
|
||||||
|
sprite = (Identifier) (Object) SvgTexture.getSvg((IdentifierRef) (Object) sprite, (x2 - x1) * 2,
|
||||||
|
(y2 - y1) * 2);
|
||||||
|
}
|
||||||
|
if (color == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
x1 *= 4;
|
||||||
|
x2 *= 4;
|
||||||
|
y1 *= 4;
|
||||||
|
y2 *= 4;
|
||||||
|
color = getColor(color);
|
||||||
|
|
||||||
|
matrices.pushPose();
|
||||||
|
matrices.scale(0.25f, 0.25f, 1.0f);
|
||||||
|
//? if <1.21.5
|
||||||
|
//RenderSystem.setShaderTexture(0, sprite);
|
||||||
|
RenderType renderLayer = RenderType.guiTextured(sprite);
|
||||||
|
Matrix4f matrix4f = matrices.last().pose();
|
||||||
|
VertexConsumer vertexConsumer = vertexConsumers.getBuffer(renderLayer);
|
||||||
|
vertexConsumer.addVertex(matrix4f, x1, y1, 0).setUv(u1, v1).setColor(color);
|
||||||
|
vertexConsumer.addVertex(matrix4f, x1, y2, 0).setUv(u1, v2).setColor(color);
|
||||||
|
vertexConsumer.addVertex(matrix4f, x2, y2, 0).setUv(u2, v2).setColor(color);
|
||||||
|
vertexConsumer.addVertex(matrix4f, x2, y1, 0).setUv(u2, v1).setColor(color);
|
||||||
|
matrices.popPose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void drawGlass(IdentifierRef mask, int x, int y, int width, int height, int blur) {
|
||||||
|
int color = getColor(0xFFFFFFFF);
|
||||||
|
if (mask == null || color >>> 24 == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (capturedBlurs.add(blur)) {
|
||||||
|
GlassRenderer.captureBackdrop(blur);
|
||||||
|
}
|
||||||
|
|
||||||
|
// In quarter units, as drawTexturedQuad draws
|
||||||
|
int x1 = x * 4;
|
||||||
|
int x2 = (x + width) * 4;
|
||||||
|
int y1 = y * 4;
|
||||||
|
int y2 = (y + height) * 4;
|
||||||
|
|
||||||
|
matrices.pushPose();
|
||||||
|
matrices.scale(0.25f, 0.25f, 1.0f);
|
||||||
|
Matrix4f matrix4f = matrices.last().pose();
|
||||||
|
VertexConsumer vertexConsumer = vertexConsumers.getBuffer(GlassRenderer.getLayer((Identifier) (Object) mask, blur));
|
||||||
|
vertexConsumer.addVertex(matrix4f, x1, y1, 0).setUv(0, 0).setColor(color);
|
||||||
|
vertexConsumer.addVertex(matrix4f, x1, y2, 0).setUv(0, 1).setColor(color);
|
||||||
|
vertexConsumer.addVertex(matrix4f, x2, y2, 0).setUv(1, 1).setColor(color);
|
||||||
|
vertexConsumer.addVertex(matrix4f, x2, y1, 0).setUv(1, 0).setColor(color);
|
||||||
|
matrices.popPose();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------
|
||||||
|
// Items
|
||||||
|
// ----------------------------
|
||||||
|
@Override
|
||||||
|
public void drawItem(ItemStackRef item, int x, int y) {
|
||||||
|
drawItem(SaturnClient.client.player, SaturnClient.client.level, item, x, y, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void drawItem(@Nullable LivingEntity entity, @Nullable Level world, ItemStackRef stack, int x, int y,
|
||||||
|
int seed) {
|
||||||
|
drawItem(entity, world, (ItemStack) (Object) stack, x, y, seed, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void drawItem(@Nullable LivingEntity entity, @Nullable Level world, ItemStack stack, int x, int y, int seed,
|
||||||
|
int z) {
|
||||||
|
if (stack.isEmpty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
//? if >=1.21.5
|
||||||
|
SaturnClient.client.getItemModelResolver().appendItemLayers(itemRenderState, stack, ItemDisplayContext.GUI, world, entity, seed);
|
||||||
|
//? if >=1.21.5
|
||||||
|
SaturnClient.client.getItemModelResolver().updateForTopItem(this.itemRenderState, stack, ItemDisplayContext.GUI, world, entity, seed);
|
||||||
|
//? if <1.21.5
|
||||||
|
//SaturnClient.client.getItemModelResolver().updateForTopItem(itemRenderState, stack, ItemDisplayContext.GUI, false, world, entity, seed);
|
||||||
|
|
||||||
|
this.matrices.pushPose();
|
||||||
|
//? if >=1.21.5
|
||||||
|
this.matrices.translate((float) (x + 8), (float) (y + 8), (float) (150 + z));
|
||||||
|
//? if <1.21.5
|
||||||
|
//this.matrices.translate(x + 8, y + 8, 150 + (itemRenderState.isGui3d() ? z : 0));
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.matrices.scale(16.0F, -16.0F, 16.0F);
|
||||||
|
boolean bl = !this.itemRenderState.usesBlockLight();
|
||||||
|
if (bl) {
|
||||||
|
this.draw();
|
||||||
|
Lighting.setupForFlatItems();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.itemRenderState.render(this.matrices, this.vertexConsumers, 15728880, OverlayTexture.NO_OVERLAY);
|
||||||
|
this.draw();
|
||||||
|
if (bl) {
|
||||||
|
Lighting.setupFor3DItems();
|
||||||
|
}
|
||||||
|
} catch (Throwable throwable) {
|
||||||
|
CrashReport crashReport = CrashReport.forThrowable(throwable, "Rendering item");
|
||||||
|
CrashReportCategory crashReportSection = crashReport.addCategory("Item being rendered");
|
||||||
|
crashReportSection.setDetail("Item Type", () -> String.valueOf(stack.getItem()));
|
||||||
|
crashReportSection.setDetail("Item Components", () -> String.valueOf(stack.getComponents()));
|
||||||
|
crashReportSection.setDetail("Item Foil", () -> String.valueOf(stack.hasFoil()));
|
||||||
|
throw new ReportedException(crashReport);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.matrices.popPose();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------
|
||||||
|
// Scissor / Clipping
|
||||||
|
// ----------------------------
|
||||||
|
@Override
|
||||||
|
public void enableScissor(int x1, int y1, int x2, int y2) {
|
||||||
|
ScreenRectangle screenRect = new ScreenRectangle(x1, y1, x2 - x1, y2 - y1)
|
||||||
|
.transformAxisAligned(matrices.last().pose());
|
||||||
|
setScissor(scissorStack.push(screenRect));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void disableScissor() {
|
||||||
|
setScissor(scissorStack.pop());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean scissorContains(int x, int y) {
|
||||||
|
return scissorStack.containsPoint(x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setScissor(@Nullable ScreenRectangle rect) {
|
||||||
|
draw();
|
||||||
|
if (rect != null) {
|
||||||
|
Window window = SaturnClient.client.getWindow();
|
||||||
|
int framebufferHeight = window.getHeight();
|
||||||
|
double scaleFactor = window.getGuiScale();
|
||||||
|
RenderSystem.enableScissor(
|
||||||
|
(int) (rect.left() * scaleFactor),
|
||||||
|
(int) (framebufferHeight - rect.bottom() * scaleFactor),
|
||||||
|
Math.max(0, (int) (rect.width() * scaleFactor)),
|
||||||
|
Math.max(0, (int) (rect.height() * scaleFactor)));
|
||||||
|
} else {
|
||||||
|
RenderSystem.disableScissor();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class ScissorStack {
|
||||||
|
private final Deque<ScreenRectangle> stack = new ArrayDeque<>();
|
||||||
|
|
||||||
|
public ScreenRectangle push(ScreenRectangle p_281812_) {
|
||||||
|
ScreenRectangle screenrectangle = this.stack.peekLast();
|
||||||
|
if (screenrectangle != null) {
|
||||||
|
ScreenRectangle screenrectangle1 = Objects.requireNonNullElse(p_281812_.intersection(screenrectangle),
|
||||||
|
ScreenRectangle.empty());
|
||||||
|
this.stack.addLast(screenrectangle1);
|
||||||
|
return screenrectangle1;
|
||||||
|
} else {
|
||||||
|
this.stack.addLast(p_281812_);
|
||||||
|
return p_281812_;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public ScreenRectangle pop() {
|
||||||
|
if (this.stack.isEmpty()) {
|
||||||
|
throw new IllegalStateException("Scissor stack underflow");
|
||||||
|
} else {
|
||||||
|
this.stack.removeLast();
|
||||||
|
return this.stack.peekLast();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean containsPoint(int p_329411_, int p_333404_) {
|
||||||
|
return this.stack.isEmpty() ? true : this.stack.peek().containsPoint(p_329411_, p_333404_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*///?}
|
||||||
@@ -0,0 +1,297 @@
|
|||||||
|
package org.saturnclient.impl.ui;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.time.Instant;
|
||||||
|
|
||||||
|
import org.saturnclient.config.Config;
|
||||||
|
import org.saturnclient.impl.provider.Keys;
|
||||||
|
import org.saturnclient.ui.RenderScope;
|
||||||
|
import org.saturnclient.ui.SaturnScreen;
|
||||||
|
import org.saturnclient.ui.SaturnScreen.ScreenProvider;
|
||||||
|
|
||||||
|
import net.minecraft.client.Minecraft;
|
||||||
|
//? if >=1.21.9
|
||||||
|
import net.minecraft.client.input.MouseButtonEvent;
|
||||||
|
import net.minecraft.client.renderer.CubeMap;
|
||||||
|
import net.minecraft.client.gui.GuiGraphics;
|
||||||
|
//? if >=26.1 {
|
||||||
|
/*import net.minecraft.client.renderer.Panorama;
|
||||||
|
*///?} else
|
||||||
|
import net.minecraft.client.renderer.PanoramaRenderer;
|
||||||
|
import net.minecraft.client.gui.screens.Screen;
|
||||||
|
//? if >=1.21.6 {
|
||||||
|
import net.minecraft.client.renderer.texture.CubeMapTexture;
|
||||||
|
//?} else {
|
||||||
|
/*import net.minecraft.client.renderer.texture.SimpleTexture;
|
||||||
|
import net.minecraft.client.renderer.texture.TextureManager;
|
||||||
|
*///?}
|
||||||
|
//? if >=1.21.9
|
||||||
|
import net.minecraft.client.input.KeyEvent;
|
||||||
|
//? if <1.21.6 {
|
||||||
|
/*import net.minecraft.client.renderer.PostChain;
|
||||||
|
import net.minecraft.client.renderer.LevelTargetBundle;
|
||||||
|
import com.mojang.blaze3d.resource.CrossFrameResourcePool;
|
||||||
|
*///?}
|
||||||
|
import net.minecraft.network.chat.Component;
|
||||||
|
import net.minecraft.resources.Identifier;
|
||||||
|
|
||||||
|
public class SaturnScreenFabric extends Screen implements ScreenProvider {
|
||||||
|
protected static final Identifier PANORAMA = Identifier.fromNamespaceAndPath("saturnclient",
|
||||||
|
"textures/gui/title/background/panorama");
|
||||||
|
public static final CubeMap PANORAMA_RENDERER = new CubeMap(PANORAMA);
|
||||||
|
|
||||||
|
// From 26.1 vanilla draws the panorama from its own cube map; GuiRendererMixin swaps in Saturn's.
|
||||||
|
//? if >=26.1 {
|
||||||
|
/*public static final Panorama ROTATING_PANORAMA_RENDERER = new Panorama();
|
||||||
|
*///?} else {
|
||||||
|
public static final PanoramaRenderer ROTATING_PANORAMA_RENDERER;
|
||||||
|
|
||||||
|
static {
|
||||||
|
ROTATING_PANORAMA_RENDERER = new PanoramaRenderer(PANORAMA_RENDERER);
|
||||||
|
}
|
||||||
|
//?}
|
||||||
|
|
||||||
|
//? if <1.21.6
|
||||||
|
//private final CrossFrameResourcePool pool = new CrossFrameResourcePool(3);
|
||||||
|
public final SaturnScreen screen;
|
||||||
|
|
||||||
|
/** Whether the panorama's textures are loaded; it isn't drawn before (e.g. under the splash). */
|
||||||
|
private static volatile boolean panoramaReady = false;
|
||||||
|
|
||||||
|
public static void preload(Minecraft client) {
|
||||||
|
//? if >=1.21.6 {
|
||||||
|
// From 1.21.6 CubeMapRenderer draws from one cube map texture made of panorama_0..5.png.
|
||||||
|
// CubeMapRenderer.registerTextures only stores it for the next resource reload, which has
|
||||||
|
// already happened by now, so register it through the overload that loads it right away.
|
||||||
|
client.getTextureManager().registerAndLoad(PANORAMA, new CubeMapTexture(PANORAMA));
|
||||||
|
//?} else {
|
||||||
|
/*TextureManager textureManager = client.getTextureManager();
|
||||||
|
for (int i = 0; i < 6; i++) {
|
||||||
|
String var10001 = PANORAMA.getPath();
|
||||||
|
Identifier tex = PANORAMA.withPath(var10001 + "_" + i + ".png");
|
||||||
|
SimpleTexture resourceTexture = new SimpleTexture(tex);
|
||||||
|
textureManager.registerAndLoad(tex, resourceTexture);
|
||||||
|
}
|
||||||
|
*///?}
|
||||||
|
panoramaReady = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SaturnScreenFabric(SaturnScreen screen) {
|
||||||
|
super(Component.literal(screen.title));
|
||||||
|
screen.provider = this;
|
||||||
|
this.screen = screen;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void init() {
|
||||||
|
screen.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
//? if <1.21.6
|
||||||
|
//@SuppressWarnings("deprecation")
|
||||||
|
@Override
|
||||||
|
//? if >=26.1 {
|
||||||
|
/*public void extractRenderState(GuiGraphics context, int mouseX, int mouseY, float delta) {
|
||||||
|
*///?} else
|
||||||
|
public void render(GuiGraphics context, int mouseX, int mouseY, float delta) {
|
||||||
|
if (screen.start == null) {
|
||||||
|
screen.start = Instant.now();
|
||||||
|
}
|
||||||
|
long elapsed = Duration.between(screen.start, Instant.now()).toMillis();
|
||||||
|
|
||||||
|
screen.updateScale();
|
||||||
|
|
||||||
|
mouseX = (int) (mouseX / screen.scale);
|
||||||
|
mouseY = (int) (mouseY / screen.scale);
|
||||||
|
|
||||||
|
// From 1.21.6 the background, blur included, is drawn in renderBackground.
|
||||||
|
//? if <1.21.6 {
|
||||||
|
/*if (isShowingPanorama()) {
|
||||||
|
ROTATING_PANORAMA_RENDERER.render(context, this.width, this.height, screen.backgroundOpacity, delta);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Glass is blurred from the game as it is before this screen blurs it.
|
||||||
|
if (Config.usesGlass()) {
|
||||||
|
context.flush();
|
||||||
|
GlassRenderer.saveBackground();
|
||||||
|
}
|
||||||
|
|
||||||
|
float radius = screen.getBackgroundBlur();
|
||||||
|
PostChain postEffectProcessor = radius < 1 ? null : this.minecraft.getShaderManager().getPostChain(
|
||||||
|
Identifier.withDefaultNamespace("blur"),
|
||||||
|
LevelTargetBundle.MAIN_TARGETS);
|
||||||
|
|
||||||
|
if (postEffectProcessor != null) {
|
||||||
|
//? if >=1.21.5
|
||||||
|
postEffectProcessor.process(this.minecraft.getMainRenderTarget(), this.pool, pass -> pass.setUniform("Radius", radius));
|
||||||
|
//? if <1.21.5
|
||||||
|
//postEffectProcessor.setUniform("Radius", radius);
|
||||||
|
//? if <1.21.5
|
||||||
|
//postEffectProcessor.process(this.minecraft.getMainRenderTarget(), this.pool);
|
||||||
|
}
|
||||||
|
*///?}
|
||||||
|
|
||||||
|
RenderScope renderScope = new RenderScopeImpl(context);
|
||||||
|
|
||||||
|
screen.render(renderScope, mouseX, mouseY, delta, elapsed);
|
||||||
|
}
|
||||||
|
|
||||||
|
//? if >=1.21.6 {
|
||||||
|
// Vanilla's background would cover Saturn's panorama, and blurs by the vanilla setting. The
|
||||||
|
// strength of this blur comes from GameRendererMixin, which gives Saturn's setting instead.
|
||||||
|
@Override
|
||||||
|
//? if >=26.1 {
|
||||||
|
/*public void extractBackground(GuiGraphics context, int mouseX, int mouseY, float delta) {
|
||||||
|
*///?} else
|
||||||
|
public void renderBackground(GuiGraphics context, int mouseX, int mouseY, float delta) {
|
||||||
|
// The panorama renderer has no opacity (and takes no delta) from 1.21.6, so it's drawn fully
|
||||||
|
// opaque.
|
||||||
|
if (isShowingPanorama()) {
|
||||||
|
//? if >=26.2 {
|
||||||
|
/*ROTATING_PANORAMA_RENDERER.extractRenderState(context, this.width, this.height);
|
||||||
|
*///?} else if >=26.1 {
|
||||||
|
/*ROTATING_PANORAMA_RENDERER.extractRenderState(context, this.width, this.height, true);
|
||||||
|
*///?} else
|
||||||
|
ROTATING_PANORAMA_RENDERER.render(context, this.width, this.height, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (screen.getBackgroundBlur() >= 1) {
|
||||||
|
context.blurBeforeThisStratum();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (minecraft.level != null) {
|
||||||
|
//? if >=26.1 {
|
||||||
|
/*extractMenuBackground(context);
|
||||||
|
*///?} else
|
||||||
|
renderMenuBackground(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//?}
|
||||||
|
|
||||||
|
/** Whether Saturn's panorama is behind the screen: outside a world, once it's loaded. */
|
||||||
|
private boolean isShowingPanorama() {
|
||||||
|
return panoramaReady && minecraft.level == null && minecraft.getCurrentServer() == null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1.21.9 moved mouse and key events to Click and KeyInput.
|
||||||
|
//? if >=1.21.9 {
|
||||||
|
@Override
|
||||||
|
public boolean mouseClicked(MouseButtonEvent click, boolean doubled) {
|
||||||
|
if (screen.mouseClicked(click.x(), click.y(), Keys.mouseButtonToSaturn(click.button()))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.mouseClicked(click, doubled);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean mouseDragged(MouseButtonEvent click, double offsetX, double offsetY) {
|
||||||
|
// offsetX and offsetY are the drag deltas, as mouseDragged's deltaX and deltaY were before 1.21.9.
|
||||||
|
if (screen.mouseDragged(click.x(), click.y(), Keys.mouseButtonToSaturn(click.button()), offsetX, offsetY)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.mouseDragged(click, offsetX, offsetY);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean mouseReleased(MouseButtonEvent click) {
|
||||||
|
if (screen.mouseReleased(click.x(), click.y(), Keys.mouseButtonToSaturn(click.button()))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.mouseReleased(click);
|
||||||
|
}
|
||||||
|
//?} else {
|
||||||
|
/*@Override
|
||||||
|
public boolean mouseClicked(double mouseX, double mouseY, int button) {
|
||||||
|
if (screen.mouseClicked(mouseX, mouseY, button)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return super.mouseClicked(mouseX, mouseY, button);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY) {
|
||||||
|
if (screen.mouseDragged(mouseX, mouseY, button, deltaX, deltaY)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return super.mouseDragged(mouseX, mouseY, button, deltaX, deltaY);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean mouseReleased(double mouseX, double mouseY, int button) {
|
||||||
|
if (screen.mouseReleased(mouseX, mouseY, button)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return super.mouseReleased(mouseX, mouseY, button);
|
||||||
|
}
|
||||||
|
*///?}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean mouseScrolled(double mouseX, double mouseY, double horizontalAmount, double verticalAmount) {
|
||||||
|
if (screen.mouseScrolled(mouseX, mouseY, horizontalAmount, verticalAmount)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return super.mouseScrolled(mouseX, mouseY, horizontalAmount, verticalAmount);
|
||||||
|
}
|
||||||
|
|
||||||
|
//? if >=1.21.9 {
|
||||||
|
@Override
|
||||||
|
public boolean keyPressed(KeyEvent input) {
|
||||||
|
// 26.3's key() is SDL's scancode; Saturn's key codes are GLFW's (see Keys).
|
||||||
|
//? if >=26.3 {
|
||||||
|
/*if (screen.keyPressed(Keys.toSaturn(input.key()), input.key(), Keys.modifiersToSaturn(input.modifiers()))) {
|
||||||
|
*///?} else
|
||||||
|
if (screen.keyPressed(input.input(), input.scancode(), input.modifiers())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.keyPressed(input);
|
||||||
|
}
|
||||||
|
//?} else {
|
||||||
|
/*@Override
|
||||||
|
public boolean keyPressed(int keyCode, int scanCode, int modifiers) {
|
||||||
|
if (screen.keyPressed(keyCode, scanCode, modifiers)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return super.keyPressed(keyCode, scanCode, modifiers);
|
||||||
|
}
|
||||||
|
*///?}
|
||||||
|
|
||||||
|
//? if >=1.21.11 {
|
||||||
|
@Override
|
||||||
|
public void resize(int width, int height) {
|
||||||
|
screen.resize(width, height);
|
||||||
|
super.resize(width, height);
|
||||||
|
}
|
||||||
|
//?} else {
|
||||||
|
/*@Override
|
||||||
|
public void resize(Minecraft client, int width, int height) {
|
||||||
|
screen.resize(width, height);
|
||||||
|
super.resize(client, width, height);
|
||||||
|
}
|
||||||
|
*///?}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onClose() {
|
||||||
|
this.screen.onClose();
|
||||||
|
super.onClose();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ScreenProvider's close, separate from the vanilla onClose above.
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
onClose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getWidth() {
|
||||||
|
return width;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getHeight() {
|
||||||
|
return height;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package org.saturnclient.impl.ui;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import net.minecraft.client.Minecraft;
|
||||||
|
import net.minecraft.client.gui.screens.Screen;
|
||||||
|
|
||||||
|
/** The open screen and overlay, which Minecraft holds before 26.2 and its Gui from 26.2. */
|
||||||
|
public final class Screens {
|
||||||
|
private Screens() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static @Nullable Screen current() {
|
||||||
|
//? if >=26.2 {
|
||||||
|
/*return Minecraft.getInstance().gui.screen();
|
||||||
|
*///?} else
|
||||||
|
return Minecraft.getInstance().screen;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void set(@Nullable Screen screen) {
|
||||||
|
//? if >=26.2 {
|
||||||
|
/*Minecraft.getInstance().gui.setScreen(screen);
|
||||||
|
*///?} else
|
||||||
|
Minecraft.getInstance().setScreen(screen);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void clearOverlay() {
|
||||||
|
//? if >=26.2 {
|
||||||
|
/*Minecraft.getInstance().gui.setOverlay(null);
|
||||||
|
*///?} else
|
||||||
|
Minecraft.getInstance().setOverlay(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
+3
-3
@@ -3,7 +3,7 @@ package org.saturnclient.saturnclient;
|
|||||||
import net.fabricmc.api.ModInitializer;
|
import net.fabricmc.api.ModInitializer;
|
||||||
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents;
|
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents;
|
||||||
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
|
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
|
||||||
import net.minecraft.client.MinecraftClient;
|
import net.minecraft.client.Minecraft;
|
||||||
|
|
||||||
import org.saturnclient.client.ServiceClient;
|
import org.saturnclient.client.ServiceClient;
|
||||||
import org.saturnclient.cosmetics.Emotes;
|
import org.saturnclient.cosmetics.Emotes;
|
||||||
@@ -29,12 +29,12 @@ public class SaturnClient implements ModInitializer {
|
|||||||
|
|
||||||
public static final String MOD_ID = "saturnclient";
|
public static final String MOD_ID = "saturnclient";
|
||||||
public static final Logger LOGGER = LoggerFactory.getLogger(MOD_ID);
|
public static final Logger LOGGER = LoggerFactory.getLogger(MOD_ID);
|
||||||
public static MinecraftClient client;
|
public static Minecraft client;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onInitialize() {
|
public void onInitialize() {
|
||||||
LOGGER.info("Initializing " + MOD_ID);
|
LOGGER.info("Initializing " + MOD_ID);
|
||||||
client = MinecraftClient.getInstance();
|
client = Minecraft.getInstance();
|
||||||
|
|
||||||
Providers.saturn = new SaturnProviderImpl();
|
Providers.saturn = new SaturnProviderImpl();
|
||||||
Providers.refConstructor = new RefConstructorImpl();
|
Providers.refConstructor = new RefConstructorImpl();
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package org.saturnclient.saturnclient;
|
||||||
|
|
||||||
|
//? if >=1.21.11
|
||||||
|
import org.jspecify.annotations.NonNull;
|
||||||
|
//? if >=1.21.8 {
|
||||||
|
import org.saturnclient.client.player.SaturnPlayer;
|
||||||
|
|
||||||
|
import net.fabricmc.fabric.api.client.rendering.v1.RenderStateDataKey;
|
||||||
|
//?}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The key PlayerEntityRendererMixin stores each player's SaturnPlayer under in their render state.
|
||||||
|
* Fabric API's render state data only exists from 1.21.8, so older versions look players up by name.
|
||||||
|
*/
|
||||||
|
public class SaturnRenderState {
|
||||||
|
//? if >=1.21.11 {
|
||||||
|
@NonNull
|
||||||
|
public static RenderStateDataKey<SaturnPlayer> saturnDataKey = RenderStateDataKey
|
||||||
|
.create(() -> "saturn-client-player");
|
||||||
|
//?} else if >=1.21.8 {
|
||||||
|
/*public static RenderStateDataKey<SaturnPlayer> saturnDataKey = RenderStateDataKey
|
||||||
|
.create(() -> "saturn-client-player");
|
||||||
|
*///?}
|
||||||
|
}
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
package org.saturnclient.saturnclient.event;
|
||||||
|
|
||||||
|
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
|
||||||
|
//? if >=26.1 {
|
||||||
|
/*import net.fabricmc.fabric.api.client.keymapping.v1.KeyMappingHelper;
|
||||||
|
*///?} else
|
||||||
|
import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper;
|
||||||
|
import net.minecraft.client.KeyMapping;
|
||||||
|
import com.mojang.blaze3d.platform.InputConstants;
|
||||||
|
//? if >=1.21.9
|
||||||
|
import net.minecraft.resources.Identifier;
|
||||||
|
|
||||||
|
import org.saturnclient.config.Config;
|
||||||
|
import org.saturnclient.saturnclient.SaturnClient;
|
||||||
|
import org.saturnclient.common.provider.Providers;
|
||||||
|
import org.saturnclient.client.ServiceClient;
|
||||||
|
import org.saturnclient.emotes.fabric.Emotes;
|
||||||
|
import org.saturnclient.impl.ui.SaturnScreenFabric;
|
||||||
|
import org.saturnclient.impl.ui.Screens;
|
||||||
|
import org.saturnclient.ui.screens.EmoteWheel;
|
||||||
|
import org.saturnclient.ui.screens.ShiftMenu;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles keyboard input events for the Saturn Client.
|
||||||
|
* Manages keybindings and their associated actions.
|
||||||
|
*/
|
||||||
|
public class KeyInputHandler {
|
||||||
|
//? if >=1.21.9 {
|
||||||
|
private static final Identifier KEY_CATEGORY = Identifier.fromNamespaceAndPath("saturnclient", "key.category.saturnclient");
|
||||||
|
//?} else
|
||||||
|
//private static final String KEY_CATEGORY = "key.category.saturnclient";
|
||||||
|
private static final String MAIN_MENU_KEY_ID = "key.saturnclient.main_menu";
|
||||||
|
private static final int DEFAULT_MENU_KEY = InputConstants.KEY_RSHIFT;
|
||||||
|
private static KeyMapping mainMenuKeyBinding;
|
||||||
|
|
||||||
|
public static void register() {
|
||||||
|
//? if >=26.1 {
|
||||||
|
/*mainMenuKeyBinding = KeyMappingHelper.registerKeyMapping(
|
||||||
|
new KeyMapping(
|
||||||
|
MAIN_MENU_KEY_ID,
|
||||||
|
DEFAULT_MENU_KEY,
|
||||||
|
KeyMapping.Category.register(KEY_CATEGORY)));
|
||||||
|
*///?} else if >=1.21.9 {
|
||||||
|
mainMenuKeyBinding = KeyBindingHelper.registerKeyBinding(
|
||||||
|
new KeyMapping(
|
||||||
|
MAIN_MENU_KEY_ID,
|
||||||
|
DEFAULT_MENU_KEY,
|
||||||
|
KeyMapping.Category.register(KEY_CATEGORY)));
|
||||||
|
//?} else {
|
||||||
|
/*mainMenuKeyBinding = KeyBindingHelper.registerKeyBinding(new KeyMapping(
|
||||||
|
MAIN_MENU_KEY_ID,
|
||||||
|
InputConstants.Type.KEYSYM,
|
||||||
|
DEFAULT_MENU_KEY,
|
||||||
|
KEY_CATEGORY));
|
||||||
|
*///?}
|
||||||
|
|
||||||
|
// Register the event handler for the main menu key
|
||||||
|
ClientTickEvents.END_CLIENT_TICK.register(client -> {
|
||||||
|
if (mainMenuKeyBinding.consumeClick()) {
|
||||||
|
Providers.saturn.getClient().setScreen(new ShiftMenu());
|
||||||
|
}
|
||||||
|
|
||||||
|
// The emote wheel key opens the wheel, and closes it again while it's open. Closing it
|
||||||
|
// from the screen's own key handling would reopen it on the next tick, since the key is
|
||||||
|
// still down and no screen is open by then.
|
||||||
|
if (Config.openEmoteWheel.wasKeyPressed()
|
||||||
|
//? if >=26.3 {
|
||||||
|
/*&& !InputConstants.isKeyDown(InputConstants.KEY_F3)) {
|
||||||
|
*///?} else if >=1.21.9 {
|
||||||
|
&& !InputConstants.isKeyDown(client.getWindow(), InputConstants.KEY_F3)) {
|
||||||
|
//?} else
|
||||||
|
//&& !InputConstants.isKeyDown(client.getWindow().getWindow(), InputConstants.KEY_F3)) {
|
||||||
|
if (Screens.current() == null) {
|
||||||
|
Providers.saturn.getClient().setScreen(new EmoteWheel());
|
||||||
|
} else if (Screens.current() instanceof SaturnScreenFabric saturnScreen
|
||||||
|
&& saturnScreen.screen instanceof EmoteWheel) {
|
||||||
|
saturnScreen.onClose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sneaking cancels your own emote, for everyone.
|
||||||
|
if (SaturnClient.client.player != null && SaturnClient.client.player.isShiftKeyDown()
|
||||||
|
&& Emotes.playing(SaturnClient.client.player.getUUID()).isPresent()) {
|
||||||
|
Emotes.stop(SaturnClient.client.player.getUUID());
|
||||||
|
ServiceClient.emote("");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package org.saturnclient.saturnclient.mixin;
|
||||||
|
|
||||||
|
import net.minecraft.client.gui.GuiGraphics;
|
||||||
|
//? if >=1.21.6 {
|
||||||
|
import net.minecraft.client.gui.render.state.GuiRenderState;
|
||||||
|
//?} else
|
||||||
|
//import net.minecraft.client.renderer.MultiBufferSource;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||||
|
|
||||||
|
@Mixin(GuiGraphics.class)
|
||||||
|
public interface DrawContextAccessor {
|
||||||
|
//? if >=1.21.6 {
|
||||||
|
@Accessor("guiRenderState")
|
||||||
|
GuiRenderState getState();
|
||||||
|
//?} else {
|
||||||
|
/*@Accessor("bufferSource")
|
||||||
|
MultiBufferSource.BufferSource getVertexConsumers();
|
||||||
|
*///?}
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
package org.saturnclient.saturnclient.mixin;
|
||||||
|
|
||||||
|
//? if >=26.1 {
|
||||||
|
/*import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||||
|
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
|
||||||
|
import net.minecraft.client.gui.render.GuiRenderer;
|
||||||
|
import net.minecraft.client.renderer.CubeMap;
|
||||||
|
import org.saturnclient.impl.ui.GlassRenderer;
|
||||||
|
import org.saturnclient.impl.ui.SaturnScreenFabric;
|
||||||
|
import org.saturnclient.impl.ui.Screens;
|
||||||
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
|
import org.spongepowered.asm.mixin.injection.Inject;
|
||||||
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||||
|
*///?} else
|
||||||
|
import net.minecraft.client.Minecraft;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* From 26.1 GuiRenderer draws the menu panorama from its own cube map, which is vanilla's. While a
|
||||||
|
* Saturn screen is open, this draws Saturn's panorama instead. It also blurs the glass backdrops
|
||||||
|
* once the panorama is drawn. Before 26.1 SaturnScreenFabric draws
|
||||||
|
* the panorama itself, so this mixin is empty there, and it targets Minecraft because GuiRenderer
|
||||||
|
* doesn't exist before 1.21.6. It stays registered, keeping saturnclient.mixins.json the same for
|
||||||
|
* every version.
|
||||||
|
*/
|
||||||
|
//? if >=26.1 {
|
||||||
|
/*@Mixin(GuiRenderer.class)
|
||||||
|
public class GuiRendererMixin {
|
||||||
|
@WrapOperation(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/CubeMap;render(FF)V"))
|
||||||
|
private void saturn$drawSaturnPanorama(CubeMap cubeMap, float xRot, float yRot, Operation<Void> original) {
|
||||||
|
boolean saturnScreen = Screens.current() instanceof SaturnScreenFabric;
|
||||||
|
original.call(saturnScreen ? SaturnScreenFabric.PANORAMA_RENDERER : cubeMap, xRot, yRot);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The glass backdrops are copied here, with the frame's world and panorama drawn and the GUI not
|
||||||
|
// yet: the GUI is built before the frame is drawn (see GlassRenderer).
|
||||||
|
@Inject(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/render/GuiRenderer;prepare()V"))
|
||||||
|
private void saturn$blurGlassBackdrops(CallbackInfo ci) {
|
||||||
|
GlassRenderer.blurPendingBackdrops();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*///?} else {
|
||||||
|
@Mixin(Minecraft.class)
|
||||||
|
public class GuiRendererMixin {
|
||||||
|
}
|
||||||
|
//?}
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
package org.saturnclient.saturnclient.mixin;
|
||||||
|
|
||||||
|
//? if >=1.21.9 {
|
||||||
|
import net.minecraft.client.entity.ClientAvatarEntity;
|
||||||
|
import net.minecraft.world.entity.Avatar;
|
||||||
|
//?} else
|
||||||
|
//import net.minecraft.client.player.AbstractClientPlayer;
|
||||||
|
import net.minecraft.client.renderer.entity.EntityRendererProvider;
|
||||||
|
import net.minecraft.client.renderer.entity.LivingEntityRenderer;
|
||||||
|
import net.minecraft.client.renderer.entity.player.AvatarRenderer;
|
||||||
|
//? if >=1.21.11 {
|
||||||
|
import net.minecraft.client.model.player.PlayerModel;
|
||||||
|
//?} else
|
||||||
|
//import net.minecraft.client.model.PlayerModel;
|
||||||
|
import net.minecraft.client.renderer.entity.state.AvatarRenderState;
|
||||||
|
import net.minecraft.world.entity.Entity;
|
||||||
|
import net.minecraft.world.entity.player.Player;
|
||||||
|
import net.minecraft.network.chat.MutableComponent;
|
||||||
|
import net.minecraft.network.chat.Component;
|
||||||
|
import net.minecraft.ChatFormatting;
|
||||||
|
|
||||||
|
import org.saturnclient.client.player.Roles;
|
||||||
|
import org.saturnclient.client.player.SaturnPlayer;
|
||||||
|
import org.saturnclient.impl.cosmetics.CloakFeatureRenderer;
|
||||||
|
import org.saturnclient.impl.cosmetics.HatFeatureRenderer;
|
||||||
|
import org.saturnclient.saturnclient.SaturnRenderState;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
|
import org.spongepowered.asm.mixin.injection.Inject;
|
||||||
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||||
|
|
||||||
|
@Mixin(AvatarRenderer.class)
|
||||||
|
public abstract class PlayerEntityRendererMixin
|
||||||
|
extends LivingEntityRenderer<Player, AvatarRenderState, PlayerModel> {
|
||||||
|
|
||||||
|
protected PlayerEntityRendererMixin() {
|
||||||
|
super(null, null, 0.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Inject(method = "<init>", at = @At("TAIL"))
|
||||||
|
private void addCustomFeatureRenderer(EntityRendererProvider.Context ctx, boolean slim, CallbackInfo ci) {
|
||||||
|
this.addLayer(new HatFeatureRenderer(this));
|
||||||
|
this.addLayer(new CloakFeatureRenderer(this, ctx.getEquipmentAssets()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adds the role icon to the player's name, and stores the SaturnPlayer in the render state for the
|
||||||
|
// cloak and hat (Fabric API only has render state data from 1.21.8).
|
||||||
|
//? if >=1.21.9 {
|
||||||
|
@Inject(method = "extractRenderState", at = @At("TAIL"))
|
||||||
|
public <A extends Avatar & ClientAvatarEntity> void updateRenderState(
|
||||||
|
A entity, AvatarRenderState state, float f, CallbackInfo ci) {
|
||||||
|
saturn$applyPlayer(entity, state);
|
||||||
|
}
|
||||||
|
//?} else {
|
||||||
|
/*@Inject(method = "extractRenderState", at = @At("TAIL"))
|
||||||
|
public void updateRenderState(
|
||||||
|
AbstractClientPlayer entity, AvatarRenderState state, float f, CallbackInfo ci) {
|
||||||
|
saturn$applyPlayer(entity, state);
|
||||||
|
}
|
||||||
|
*///?}
|
||||||
|
|
||||||
|
private void saturn$applyPlayer(Entity entity, AvatarRenderState state) {
|
||||||
|
if (entity == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SaturnPlayer player = SaturnPlayer.get(entity.getUUID());
|
||||||
|
|
||||||
|
//? if >=1.21.8
|
||||||
|
state.setData(SaturnRenderState.saturnDataKey, player);
|
||||||
|
|
||||||
|
if (player != null && state.nameTag != null) {
|
||||||
|
MutableComponent iconText = Component.literal(Roles.getSaturnIndicator())
|
||||||
|
.withStyle(style -> style.withColor(Roles.getIconColor(player.uuid)));
|
||||||
|
|
||||||
|
state.nameTag = Component.empty()
|
||||||
|
.append(iconText.copy())
|
||||||
|
.append(Component.empty().withStyle(ChatFormatting.RESET))
|
||||||
|
.append(state.nameTag.copy());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+11
-5
@@ -7,18 +7,24 @@ import org.spongepowered.asm.mixin.Mixin;
|
|||||||
import org.spongepowered.asm.mixin.injection.At;
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
import org.spongepowered.asm.mixin.injection.ModifyVariable;
|
import org.spongepowered.asm.mixin.injection.ModifyVariable;
|
||||||
|
|
||||||
import net.minecraft.client.MinecraftClient;
|
import net.minecraft.client.Minecraft;
|
||||||
import net.minecraft.client.gui.screen.Screen;
|
//? if >=26.2
|
||||||
import net.minecraft.client.gui.screen.TitleScreen;
|
//import net.minecraft.client.gui.Gui;
|
||||||
|
import net.minecraft.client.gui.screens.Screen;
|
||||||
|
import net.minecraft.client.gui.screens.TitleScreen;
|
||||||
|
|
||||||
@Mixin(MinecraftClient.class)
|
// Screens are opened through Gui from 26.2 (through Minecraft before).
|
||||||
|
//? if >=26.2 {
|
||||||
|
/*@Mixin(Gui.class)
|
||||||
|
*///?} else
|
||||||
|
@Mixin(Minecraft.class)
|
||||||
public class SetScreenMixin {
|
public class SetScreenMixin {
|
||||||
@ModifyVariable(method = "setScreen", at = @At("HEAD"), argsOnly = true)
|
@ModifyVariable(method = "setScreen", at = @At("HEAD"), argsOnly = true)
|
||||||
private Screen replaceTitleScreen(Screen screen) {
|
private Screen replaceTitleScreen(Screen screen) {
|
||||||
if (Config.saturnTitleScreen.value) {
|
if (Config.saturnTitleScreen.value) {
|
||||||
if (screen instanceof TitleScreen)
|
if (screen instanceof TitleScreen)
|
||||||
return new SaturnScreenFabric(new TitleMenu());
|
return new SaturnScreenFabric(new TitleMenu());
|
||||||
if (screen == null && ((MinecraftClient) (Object) this).world == null)
|
if (screen == null && Minecraft.getInstance().level == null)
|
||||||
return new SaturnScreenFabric(new TitleMenu());
|
return new SaturnScreenFabric(new TitleMenu());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,186 @@
|
|||||||
|
package org.saturnclient.saturnclient.mixin;
|
||||||
|
|
||||||
|
import org.saturnclient.impl.ui.Screens;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import org.saturnclient.config.Theme;
|
||||||
|
import org.saturnclient.impl.ui.RenderScopeImpl;
|
||||||
|
import org.saturnclient.ui.RenderScope;
|
||||||
|
import org.saturnclient.ui.resources.Textures;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.Overwrite;
|
||||||
|
import org.spongepowered.asm.mixin.Shadow;
|
||||||
|
import org.spongepowered.asm.mixin.Final;
|
||||||
|
|
||||||
|
import net.minecraft.client.Minecraft;
|
||||||
|
import net.minecraft.client.gui.GuiGraphics;
|
||||||
|
import net.minecraft.client.gui.screens.LoadingOverlay;
|
||||||
|
//? if >=1.21.11
|
||||||
|
import com.mojang.blaze3d.platform.Window;
|
||||||
|
import net.minecraft.server.packs.resources.ReloadInstance;
|
||||||
|
//? if >=1.21.11 {
|
||||||
|
import net.minecraft.util.Util;
|
||||||
|
//?} else
|
||||||
|
//import net.minecraft.Util;
|
||||||
|
import net.minecraft.util.Mth;
|
||||||
|
|
||||||
|
@Mixin(LoadingOverlay.class)
|
||||||
|
public abstract class SplashOverlayMixin {
|
||||||
|
|
||||||
|
@Shadow
|
||||||
|
private Minecraft minecraft;
|
||||||
|
@Shadow
|
||||||
|
private ReloadInstance reload;
|
||||||
|
|
||||||
|
@Shadow
|
||||||
|
private long fadeOutStart;
|
||||||
|
|
||||||
|
@Shadow
|
||||||
|
private long fadeInStart;
|
||||||
|
|
||||||
|
@Shadow
|
||||||
|
@Final
|
||||||
|
private boolean fadeIn;
|
||||||
|
|
||||||
|
@Shadow
|
||||||
|
@Final
|
||||||
|
private Consumer<Optional<Throwable>> onFinish;
|
||||||
|
|
||||||
|
@Shadow
|
||||||
|
private float currentProgress;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author SaturnClient
|
||||||
|
* @reason Replace vanilla splash rendering completely but keep exit logic
|
||||||
|
*/
|
||||||
|
@Overwrite
|
||||||
|
//? if >=26.1 {
|
||||||
|
/*public void extractRenderState(GuiGraphics context, int mouseX, int mouseY, float delta) {
|
||||||
|
*///?} else
|
||||||
|
public void render(GuiGraphics context, int mouseX, int mouseY, float delta) {
|
||||||
|
int screenWidth = context.guiWidth();
|
||||||
|
int screenHeight = context.guiHeight();
|
||||||
|
long currentTime = Util.getMillis();
|
||||||
|
|
||||||
|
// Initialize reload start time if not set
|
||||||
|
if (fadeInStart == -1L) {
|
||||||
|
fadeInStart = currentTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute fade-out progress after reload completes
|
||||||
|
float fadeOutProgress = fadeOutStart > -1L
|
||||||
|
? (float) (currentTime - fadeOutStart) / 1000f
|
||||||
|
: -1f;
|
||||||
|
|
||||||
|
int alpha = fadeOutProgress >= 0f
|
||||||
|
? Mth.ceil((1.0f - Mth.clamp(fadeOutProgress - 1f, 0f, 1f)) * 255f)
|
||||||
|
: 255;
|
||||||
|
|
||||||
|
// Compute fade-in progress while reloading
|
||||||
|
float reloadProgress = fadeInStart > -1L
|
||||||
|
? (float) (currentTime - fadeInStart) / 500f
|
||||||
|
: -1f;
|
||||||
|
|
||||||
|
// Get render scope for better UI
|
||||||
|
RenderScope scope = new RenderScopeImpl(context);
|
||||||
|
|
||||||
|
// Draw background overlay
|
||||||
|
if (fadeOutProgress >= 1.0F) {
|
||||||
|
renderCurrentScreen(context, mouseX, mouseY, delta);
|
||||||
|
int overlayAlpha = computeOverlayAlpha(fadeOutProgress - 1f);
|
||||||
|
scope.drawTexture(Textures.SPLASH, 0, 0, 0, 0, screenWidth, screenHeight,
|
||||||
|
Theme.withAlpha(overlayAlpha, 0xFFFFFFFF));
|
||||||
|
} else if (fadeIn) {
|
||||||
|
if (reloadProgress < 1.0F) {
|
||||||
|
renderCurrentScreen(context, mouseX, mouseY, delta);
|
||||||
|
}
|
||||||
|
scope.drawTexture(Textures.SPLASH, 0, 0, 0, 0, screenWidth, screenHeight,
|
||||||
|
Theme.withAlpha(alpha, 0xFFFFFFFF));
|
||||||
|
} else {
|
||||||
|
scope.drawTexture(Textures.SPLASH, 0, 0, 0, 0, screenWidth, screenHeight,
|
||||||
|
Theme.withAlpha(alpha, 0xFFFFFFFF));
|
||||||
|
}
|
||||||
|
|
||||||
|
float u = this.reload.getActualProgress();
|
||||||
|
this.currentProgress = Mth.clamp(this.currentProgress * 0.95F + u * 0.050000012F, 0.0F, 1.0F);
|
||||||
|
|
||||||
|
// Draw central logo
|
||||||
|
drawLogo(scope, screenWidth, screenHeight, alpha);
|
||||||
|
renderProgressBar(scope, context, screenWidth, screenHeight, alpha);
|
||||||
|
|
||||||
|
// Remove overlay after fade-out completes
|
||||||
|
if (fadeOutProgress >= 2.0F) {
|
||||||
|
Screens.clearOverlay();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle reload completion and exceptions
|
||||||
|
handleReloadCompletion(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void renderCurrentScreen(GuiGraphics context, int mouseX, int mouseY, float delta) {
|
||||||
|
if (Screens.current() != null) {
|
||||||
|
//? if >=26.1 {
|
||||||
|
/*Screens.current().extractRenderState(context, mouseX, mouseY, delta);
|
||||||
|
*///?} else
|
||||||
|
Screens.current().render(context, mouseX, mouseY, delta);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Draw the main logo centered */
|
||||||
|
private void drawLogo(RenderScope scope, int screenWidth, int screenHeight, int alpha) {
|
||||||
|
scope.drawTexture(Textures.LOGO, (screenWidth - 60) / 2, ((screenHeight - 60) / 2) - 20, 0, 0, 60, 60,
|
||||||
|
Theme.withAlpha(alpha, 0xFFFFFFFF));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void renderProgressBar(RenderScope scope, GuiGraphics context, int screenWidth, int screenHeight,
|
||||||
|
int alpha) {
|
||||||
|
int progressY = ((screenHeight - 5) / 2) + 25;
|
||||||
|
//? if >=26.1 {
|
||||||
|
/*context.outline((screenWidth - 120) / 2, progressY, 120, 5, Theme.withAlpha(alpha, 0xFFFFFFFF));
|
||||||
|
*///?} else if >=1.21.11 {
|
||||||
|
context.renderOutline((screenWidth - 120) / 2, progressY, 120, 5, Theme.withAlpha(alpha, 0xFFFFFFFF));
|
||||||
|
//?} else if >=1.21.9 {
|
||||||
|
/*context.submitOutline((screenWidth - 120) / 2, progressY, 120, 5, Theme.withAlpha(alpha, 0xFFFFFFFF));
|
||||||
|
*///?} else
|
||||||
|
//context.renderOutline((screenWidth - 120) / 2, progressY, 120, 5, Theme.withAlpha(alpha, 0xFFFFFFFF));
|
||||||
|
|
||||||
|
int progressWidth = Math.min((int) (currentProgress * 120), 120);
|
||||||
|
int progressX = (screenWidth - 120) / 2;
|
||||||
|
|
||||||
|
context.fill(progressX, progressY, progressX + progressWidth, progressY + 5,
|
||||||
|
Theme.withAlpha(alpha, 0xFFFFFFFF));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Compute overlay alpha from fade progress */
|
||||||
|
private int computeOverlayAlpha(float progress) {
|
||||||
|
return Mth.ceil((1.0F - Mth.clamp(progress, 0.0F, 1.0F)) * 255.0F);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Handle reload completion logic */
|
||||||
|
private void handleReloadCompletion(GuiGraphics context) {
|
||||||
|
float reloadProgress = fadeInStart > -1L ? (float) (Util.getMillis() - fadeInStart) / 500f
|
||||||
|
: -1f;
|
||||||
|
|
||||||
|
if (fadeOutStart == -1L && reload.isDone() && (!fadeIn || reloadProgress >= 2.0F)) {
|
||||||
|
try {
|
||||||
|
reload.checkExceptions();
|
||||||
|
onFinish.accept(Optional.empty());
|
||||||
|
} catch (Throwable ex) {
|
||||||
|
onFinish.accept(Optional.of(ex));
|
||||||
|
}
|
||||||
|
|
||||||
|
fadeOutStart = Util.getMillis();
|
||||||
|
|
||||||
|
//? if >=1.21.11
|
||||||
|
Window window = minecraft.getWindow();
|
||||||
|
if (Screens.current() != null) {
|
||||||
|
//? if >=1.21.11 {
|
||||||
|
Screens.current().init(window.getGuiScaledWidth(), window.getGuiScaledHeight());
|
||||||
|
//?} else
|
||||||
|
//Screens.current().init(minecraft, context.guiWidth(), context.guiHeight());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+17
-12
@@ -1,8 +1,8 @@
|
|||||||
package org.saturnclient.saturnclient.mixin;
|
package org.saturnclient.saturnclient.mixin;
|
||||||
|
|
||||||
import net.minecraft.client.network.PlayerListEntry;
|
import net.minecraft.client.multiplayer.PlayerInfo;
|
||||||
import net.minecraft.scoreboard.Team;
|
import net.minecraft.world.scores.PlayerTeam;
|
||||||
import net.minecraft.text.Text;
|
import net.minecraft.network.chat.Component;
|
||||||
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
@@ -15,7 +15,7 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
|||||||
|
|
||||||
import com.mojang.authlib.GameProfile;
|
import com.mojang.authlib.GameProfile;
|
||||||
|
|
||||||
@Mixin(PlayerListEntry.class)
|
@Mixin(PlayerInfo.class)
|
||||||
public abstract class TabListMixin {
|
public abstract class TabListMixin {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -23,27 +23,32 @@ public abstract class TabListMixin {
|
|||||||
* Saturn Client, keeping any name, rank prefix or team formatting the server
|
* Saturn Client, keeping any name, rank prefix or team formatting the server
|
||||||
* set.
|
* set.
|
||||||
*/
|
*/
|
||||||
@Inject(method = "getDisplayName", at = @At("RETURN"), cancellable = true)
|
@Inject(method = "getTabListDisplayName", at = @At("RETURN"), cancellable = true)
|
||||||
private void saturn$addSaturnIcon(CallbackInfoReturnable<Text> cir) {
|
private void saturn$addSaturnIcon(CallbackInfoReturnable<Component> cir) {
|
||||||
PlayerListEntry entry = (PlayerListEntry) (Object) this;
|
PlayerInfo entry = (PlayerInfo) (Object) this;
|
||||||
GameProfile profile = entry.getProfile();
|
GameProfile profile = entry.getProfile();
|
||||||
|
//? if >=1.21.9 {
|
||||||
String name = profile.name();
|
String name = profile.name();
|
||||||
UUID uuid = profile.id();
|
UUID uuid = profile.id();
|
||||||
|
//?} else {
|
||||||
|
/*String name = profile.getName();
|
||||||
|
UUID uuid = profile.getId();
|
||||||
|
*///?}
|
||||||
|
|
||||||
if (SaturnPlayer.get(name, uuid) == null) {
|
if (SaturnPlayer.get(name, uuid) == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Text displayName = cir.getReturnValue();
|
Component displayName = cir.getReturnValue();
|
||||||
|
|
||||||
// With no server-set name, vanilla shows the team-decorated profile name.
|
// With no server-set name, vanilla shows the team-decorated profile name.
|
||||||
if (displayName == null) {
|
if (displayName == null) {
|
||||||
displayName = Team.decorateName(entry.getScoreboardTeam(), Text.literal(name));
|
displayName = PlayerTeam.formatNameForTeam(entry.getTeam(), Component.literal(name));
|
||||||
}
|
}
|
||||||
|
|
||||||
cir.setReturnValue(Text.empty()
|
cir.setReturnValue(Component.empty()
|
||||||
.append(Text.literal(Roles.getSaturnIndicator())
|
.append(Component.literal(Roles.getSaturnIndicator())
|
||||||
.styled(style -> style.withColor(Roles.getIconColor(uuid))))
|
.withStyle(style -> style.withColor(Roles.getIconColor(uuid))))
|
||||||
.append(displayName));
|
.append(displayName));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+3
-3
@@ -12,14 +12,14 @@
|
|||||||
package org.saturnclient.saturnclient.mixin;
|
package org.saturnclient.saturnclient.mixin;
|
||||||
|
|
||||||
import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
|
import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
|
||||||
import net.minecraft.client.render.entity.LivingEntityRenderer;
|
import net.minecraft.client.renderer.entity.LivingEntityRenderer;
|
||||||
import net.minecraft.entity.Entity;
|
import net.minecraft.world.entity.Entity;
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
import org.spongepowered.asm.mixin.injection.At;
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
|
|
||||||
@Mixin(LivingEntityRenderer.class)
|
@Mixin(LivingEntityRenderer.class)
|
||||||
public class WhoAmI {
|
public class WhoAmI {
|
||||||
@ModifyExpressionValue(method = "hasLabel(Lnet/minecraft/entity/LivingEntity;D)Z", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/MinecraftClient;getCameraEntity()Lnet/minecraft/entity/Entity;"))
|
@ModifyExpressionValue(method = "shouldShowName(Lnet/minecraft/world/entity/LivingEntity;D)Z", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/Minecraft;getCameraEntity()Lnet/minecraft/world/entity/Entity;"))
|
||||||
public Entity obfuscateCameraEntity(Entity original) {
|
public Entity obfuscateCameraEntity(Entity original) {
|
||||||
// hasLabel checks if the rendered entity is equal to the camera entity (aka our
|
// hasLabel checks if the rendered entity is equal to the camera entity (aka our
|
||||||
// player),
|
// player),
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
#version 330
|
||||||
|
// 26.3 compiles shaders through SPIR-V, which needs every input and output's location.
|
||||||
|
#ifdef EXPLICIT_LOCATIONS
|
||||||
|
#extension GL_ARB_separate_shader_objects : require
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Saturn's glass: the blurred game behind the screen, cut to a panel's shape. For 1.21.6 and
|
||||||
|
// later; glass_pre_1_21_6.fsh (with glass.json, the program 1.21.4 loads) is for older versions.
|
||||||
|
|
||||||
|
// Vanilla's transforms, laid out as the version has them: 1.21.6 to 1.21.10 end with LineWidth, which
|
||||||
|
// 1.21.11 dropped (GlassRenderer defines HAS_LINE_WIDTH there), and 26.3 moved TextureMat up
|
||||||
|
// (TEXTURE_MAT_SECOND). Not imported: the include only exists from 1.21.6, and 1.21.4 and 1.21.5 still
|
||||||
|
// read every shader's imports while loading resources.
|
||||||
|
layout(std140) uniform DynamicTransforms {
|
||||||
|
mat4 ModelViewMat;
|
||||||
|
#ifdef TEXTURE_MAT_SECOND
|
||||||
|
mat4 TextureMat;
|
||||||
|
#endif
|
||||||
|
vec4 ColorModulator;
|
||||||
|
vec3 ModelOffset;
|
||||||
|
#ifndef TEXTURE_MAT_SECOND
|
||||||
|
mat4 TextureMat;
|
||||||
|
#endif
|
||||||
|
#ifdef HAS_LINE_WIDTH
|
||||||
|
float LineWidth;
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
// The panel's rounded rectangle, the same texture its background is drawn with
|
||||||
|
uniform sampler2D Sampler0;
|
||||||
|
// The game behind the screen, blurred, the size of the framebuffer
|
||||||
|
uniform sampler2D Sampler1;
|
||||||
|
|
||||||
|
#ifdef EXPLICIT_LOCATIONS
|
||||||
|
layout(location = 0) in vec2 texCoord0;
|
||||||
|
layout(location = 1) in vec4 vertexColor;
|
||||||
|
|
||||||
|
layout(location = 0) out vec4 fragColor;
|
||||||
|
#else
|
||||||
|
in vec2 texCoord0;
|
||||||
|
in vec4 vertexColor;
|
||||||
|
|
||||||
|
out vec4 fragColor;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
float mask = texture(Sampler0, texCoord0).a * vertexColor.a;
|
||||||
|
if (mask == 0.0) {
|
||||||
|
discard;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The blurred copy lines up with the framebuffer being drawn to.
|
||||||
|
vec2 screenUv = gl_FragCoord.xy / vec2(textureSize(Sampler1, 0));
|
||||||
|
fragColor = vec4(texture(Sampler1, screenUv).rgb, mask) * ColorModulator;
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"vertex": "minecraft:core/position_tex_color",
|
||||||
|
"fragment": "saturnclient:core/glass_pre_1_21_6",
|
||||||
|
"samplers": [
|
||||||
|
{ "name": "Sampler0" },
|
||||||
|
{ "name": "Sampler1" }
|
||||||
|
],
|
||||||
|
"uniforms": [
|
||||||
|
{ "name": "ModelViewMat", "type": "matrix4x4", "count": 16, "values": [ 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0 ] },
|
||||||
|
{ "name": "ProjMat", "type": "matrix4x4", "count": 16, "values": [ 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0 ] },
|
||||||
|
{ "name": "ColorModulator", "type": "float", "count": 4, "values": [ 1.0, 1.0, 1.0, 1.0 ] }
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
#version 150
|
||||||
|
|
||||||
|
// Saturn's glass (see glass.fsh), for 1.21.4 and 1.21.5, which set uniforms one by one instead of in
|
||||||
|
// blocks.
|
||||||
|
|
||||||
|
uniform vec4 ColorModulator;
|
||||||
|
|
||||||
|
// The panel's rounded rectangle, the same texture its background is drawn with
|
||||||
|
uniform sampler2D Sampler0;
|
||||||
|
// The game behind the screen, blurred, the size of the framebuffer
|
||||||
|
uniform sampler2D Sampler1;
|
||||||
|
|
||||||
|
in vec2 texCoord0;
|
||||||
|
in vec4 vertexColor;
|
||||||
|
|
||||||
|
out vec4 fragColor;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
float mask = texture(Sampler0, texCoord0).a * vertexColor.a;
|
||||||
|
if (mask == 0.0) {
|
||||||
|
discard;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The blurred copy lines up with the framebuffer being drawn to.
|
||||||
|
vec2 screenUv = gl_FragCoord.xy / vec2(textureSize(Sampler1, 0));
|
||||||
|
fragColor = vec4(texture(Sampler1, screenUv).rgb, mask) * ColorModulator;
|
||||||
|
}
|
||||||
+1
-1
@@ -22,7 +22,7 @@
|
|||||||
],
|
],
|
||||||
"depends": {
|
"depends": {
|
||||||
"fabricloader": ">=${loader_version}",
|
"fabricloader": ">=${loader_version}",
|
||||||
"fabric": "*",
|
"fabric-api": "*",
|
||||||
"minecraft": "${minecraft_version}"
|
"minecraft": "${minecraft_version}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+1
-1
@@ -2,7 +2,7 @@
|
|||||||
"required": true,
|
"required": true,
|
||||||
"minVersion": "0.8",
|
"minVersion": "0.8",
|
||||||
"package": "org.saturnclient.impl.features.mixins",
|
"package": "org.saturnclient.impl.features.mixins",
|
||||||
"compatibilityLevel": "JAVA_21",
|
"compatibilityLevel": "${java}",
|
||||||
"client": [
|
"client": [
|
||||||
"render.GameRendererMixin",
|
"render.GameRendererMixin",
|
||||||
"render.LightmapTextureManagerMixin",
|
"render.LightmapTextureManagerMixin",
|
||||||
+1
-1
@@ -2,7 +2,7 @@
|
|||||||
"required": true,
|
"required": true,
|
||||||
"minVersion": "0.8",
|
"minVersion": "0.8",
|
||||||
"package": "org.saturnclient.impl.mixins",
|
"package": "org.saturnclient.impl.mixins",
|
||||||
"compatibilityLevel": "JAVA_21",
|
"compatibilityLevel": "${java}",
|
||||||
"client": [
|
"client": [
|
||||||
"IdentifierMixin",
|
"IdentifierMixin",
|
||||||
"ItemStackMixin",
|
"ItemStackMixin",
|
||||||
+3
-2
@@ -2,14 +2,15 @@
|
|||||||
"required": true,
|
"required": true,
|
||||||
"minVersion": "0.8",
|
"minVersion": "0.8",
|
||||||
"package": "org.saturnclient.saturnclient.mixin",
|
"package": "org.saturnclient.saturnclient.mixin",
|
||||||
"compatibilityLevel": "JAVA_21",
|
"compatibilityLevel": "${java}",
|
||||||
"client": [
|
"client": [
|
||||||
"TabListMixin",
|
"TabListMixin",
|
||||||
"PlayerEntityRendererMixin",
|
"PlayerEntityRendererMixin",
|
||||||
"WhoAmI",
|
"WhoAmI",
|
||||||
"DrawContextAccessor",
|
"DrawContextAccessor",
|
||||||
"SplashOverlayMixin",
|
"SplashOverlayMixin",
|
||||||
"SetScreenMixin"
|
"SetScreenMixin",
|
||||||
|
"GuiRendererMixin"
|
||||||
],
|
],
|
||||||
"injectors": {
|
"injectors": {
|
||||||
"defaultRequire": 1
|
"defaultRequire": 1
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
plugins {
|
||||||
|
id("dev.kikugie.stonecutter")
|
||||||
|
}
|
||||||
|
|
||||||
|
// The version whose code is uncommented in src/. Switch with "Set active project to <v>", and run
|
||||||
|
// "Reset active project" before committing.
|
||||||
|
stonecutter active "1.21.11"
|
||||||
|
|
||||||
|
// src/ uses 1.21.11's Mojang names (the active version). These names changed in the versions given,
|
||||||
|
// so Stonecutter swaps in each version's name when it processes it.
|
||||||
|
stonecutter parameters {
|
||||||
|
for ((since, old, new) in listOf(
|
||||||
|
Triple("1.21.11", "ResourceLocation", "Identifier"),
|
||||||
|
Triple("1.21.9", "PlayerRenderer", "AvatarRenderer"),
|
||||||
|
Triple("1.21.9", "PlayerRenderState", "AvatarRenderState"),
|
||||||
|
Triple("26.1", "GuiGraphics", "GuiGraphicsExtractor"),
|
||||||
|
Triple("26.1", "net.minecraft.client.gui.render.state", "net.minecraft.client.renderer.state.gui"),
|
||||||
|
// GuiRenderState's methods
|
||||||
|
Triple("26.1", "submitGuiElement", "addGuiElement"),
|
||||||
|
Triple("26.1", "submitText", "addText"),
|
||||||
|
Triple("26.1", "submitItem", "addItem"),
|
||||||
|
Triple("26.1", "submitPicturesInPictureState", "addPicturesInPictureState"),
|
||||||
|
// 26.3 moves Blaze3D's API to renderpearl
|
||||||
|
Triple("26.3", "com.mojang.blaze3d.GpuFormat", "com.mojang.renderpearl.api.GpuFormat"),
|
||||||
|
Triple("26.3", "com.mojang.blaze3d.pipeline.RenderPipeline", "com.mojang.renderpearl.api.pipeline.RenderPipeline"),
|
||||||
|
Triple("26.3", "com.mojang.blaze3d.textures", "com.mojang.renderpearl.api.textures"),
|
||||||
|
)) {
|
||||||
|
replacements.regex(eval(current.version, ">=$since")) {
|
||||||
|
replace("\\b${Regex.escape(old)}\\b", new, "\\b${Regex.escape(new)}\\b", old)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
subprojects {
|
||||||
|
apply(plugin = "java")
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
maven("https://maven.fabricmc.net/")
|
||||||
|
maven("https://git.selimaj.dev/api/packages/selimaj-dev/maven")
|
||||||
|
maven("https://git.selimaj.dev/api/packages/saturnclientmc/maven")
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
"implementation"("de.javagl:obj:0.4.0")
|
||||||
|
"implementation"("com.formdev:svgSalamander:1.1.4")
|
||||||
|
"implementation"("com.fasterxml.jackson.core:jackson-databind:2.17.0")
|
||||||
|
"implementation"("com.fasterxml.jackson.core:jackson-annotations:2.17.0")
|
||||||
|
"implementation"("com.fasterxml.jackson.core:jackson-core:2.17.0")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Every Minecraft version builds on common and ships its resources.
|
||||||
|
configure(subprojects.filter { it.name != "common" }) {
|
||||||
|
dependencies {
|
||||||
|
"implementation"(project(":common"))
|
||||||
|
}
|
||||||
|
|
||||||
|
extensions.configure<SourceSetContainer> {
|
||||||
|
named("main") {
|
||||||
|
resources.srcDir(project(":common").file("src/main/resources"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// remapJar loads a whole Minecraft classpath and remaps a ~50 MB jar. Eight at once don't fit in the
|
||||||
|
// daemon's heap (org.gradle.jvmargs) and the build stalls in garbage collection, so only two run
|
||||||
|
// together. Everything else still builds in parallel.
|
||||||
|
abstract class RemapLimit : BuildService<BuildServiceParameters.None>
|
||||||
|
|
||||||
|
val remapLimit = gradle.sharedServices.registerIfAbsent("remapLimit", RemapLimit::class) {
|
||||||
|
maxParallelUsages = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
subprojects {
|
||||||
|
tasks.matching { it.name == "remapJar" }.configureEach {
|
||||||
|
usesService(remapLimit)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.register("buildAll") {
|
||||||
|
group = "build"
|
||||||
|
description = "Builds every Minecraft version's mod jar and collects them in build/allJars"
|
||||||
|
|
||||||
|
// The mod jar is remapJar's output for 1.21.x, and jar's for 26.1+, which isn't obfuscated.
|
||||||
|
val modJars = subprojects.filter { it.name != "common" }.flatMap { project ->
|
||||||
|
val unobfuscated = project.name.substringBefore('.').toInt() >= 26
|
||||||
|
val task = if (unobfuscated) "jar" else "remapJar"
|
||||||
|
project.tasks.matching { it.name == task }
|
||||||
|
}
|
||||||
|
dependsOn(modJars)
|
||||||
|
|
||||||
|
doLast {
|
||||||
|
val outputDir = layout.buildDirectory.dir("allJars").get().asFile
|
||||||
|
outputDir.mkdirs()
|
||||||
|
modJars.map { (it as AbstractArchiveTask).archiveFile.get().asFile }
|
||||||
|
.filter { it.exists() && !it.name.contains("sources") }
|
||||||
|
.forEach { it.copyTo(outputDir.resolve(it.name), overwrite = true) }
|
||||||
|
println("All mod jars collected in: $outputDir")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,109 +0,0 @@
|
|||||||
plugins {
|
|
||||||
id 'net.fabricmc.fabric-loom-remap' version "${loom_version}"
|
|
||||||
id 'maven-publish'
|
|
||||||
}
|
|
||||||
|
|
||||||
version = "${project.mod_version}+${project.minecraft_version}"
|
|
||||||
group = project.maven_group
|
|
||||||
|
|
||||||
base {
|
|
||||||
archivesName = project.archives_base_name
|
|
||||||
}
|
|
||||||
|
|
||||||
repositories {
|
|
||||||
// maven { name = "KoSmX Maven"; url = 'https://maven.kosmx.dev/' }
|
|
||||||
maven { name = "Selimaj"; url = 'https://git.selimaj.dev/api/packages/selimaj-dev/maven' }
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
// To change the versions see the gradle.properties file
|
|
||||||
minecraft "com.mojang:minecraft:${project.minecraft_version}"
|
|
||||||
mappings "net.fabricmc:yarn:${project.yarn_mappings}:v2"
|
|
||||||
modImplementation "net.fabricmc:fabric-loader:${project.loader_version}"
|
|
||||||
|
|
||||||
// Fabric API. This is technically optional, but you probably want it anyway.
|
|
||||||
modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_api_version}"
|
|
||||||
|
|
||||||
// Player Animation Library
|
|
||||||
// include modImplementation(
|
|
||||||
// "dev.kosmx.player-anim:player-animation-lib-fabric:${project.player_anim}"
|
|
||||||
// )
|
|
||||||
|
|
||||||
implementation project(":common")
|
|
||||||
|
|
||||||
include implementation("dev.selimaj:session-java:0.2.0")
|
|
||||||
include implementation("de.javagl:obj:0.4.0")
|
|
||||||
include implementation("com.formdev:svgSalamander:1.1.4")
|
|
||||||
include implementation("com.fasterxml.jackson.core:jackson-databind:2.17.0")
|
|
||||||
include implementation("com.fasterxml.jackson.core:jackson-annotations:2.17.0")
|
|
||||||
include implementation("com.fasterxml.jackson.core:jackson-core:2.17.0")
|
|
||||||
|
|
||||||
// Optional: BendyLib
|
|
||||||
// include modRuntimeOnly(
|
|
||||||
// "io.github.kosmx.bendy-lib:bendy-lib-fabric:${project.bendylib_version}"
|
|
||||||
// )
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* ================================================================
|
|
||||||
* Resource Processing
|
|
||||||
* ================================================================
|
|
||||||
* Injects version values into fabric.mod.json.
|
|
||||||
*/
|
|
||||||
processResources {
|
|
||||||
inputs.property "version", project.version
|
|
||||||
inputs.property "minecraft_version", project.minecraft_version
|
|
||||||
inputs.property "loader_version", project.loader_version
|
|
||||||
filteringCharset "UTF-8"
|
|
||||||
|
|
||||||
filesMatching("fabric.mod.json") {
|
|
||||||
expand(
|
|
||||||
"version": project.version,
|
|
||||||
"minecraft_version": project.minecraft_version,
|
|
||||||
"loader_version": project.loader_version
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tasks.withType(JavaCompile).configureEach {
|
|
||||||
it.options.release = 21
|
|
||||||
}
|
|
||||||
|
|
||||||
java {
|
|
||||||
// Loom will automatically attach sourcesJar to a RemapSourcesJar task and to the "build" task
|
|
||||||
// if it is present.
|
|
||||||
// If you remove this line, sources will not be generated.
|
|
||||||
withSourcesJar()
|
|
||||||
|
|
||||||
sourceCompatibility = JavaVersion.VERSION_21
|
|
||||||
targetCompatibility = JavaVersion.VERSION_21
|
|
||||||
}
|
|
||||||
|
|
||||||
jar {
|
|
||||||
inputs.property "archivesName", project.base.archivesName
|
|
||||||
|
|
||||||
from("LICENSE") {
|
|
||||||
rename { "${it}_${inputs.properties.archivesName}"}
|
|
||||||
}
|
|
||||||
|
|
||||||
from project(":common").sourceSets.main.output
|
|
||||||
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
|
||||||
}
|
|
||||||
|
|
||||||
// configure the maven publication
|
|
||||||
publishing {
|
|
||||||
publications {
|
|
||||||
create("mavenJava", MavenPublication) {
|
|
||||||
artifactId = project.archives_base_name
|
|
||||||
from components.java
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// See https://docs.gradle.org/current/userguide/publishing_maven.html for information on how to set up publishing.
|
|
||||||
repositories {
|
|
||||||
// Add repositories to publish to here.
|
|
||||||
// Notice: This block does NOT have the same function as the block in the top level.
|
|
||||||
// The repositories here will be used for publishing your artifact, not for
|
|
||||||
// retrieving dependencies.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -8,14 +8,10 @@ org.gradle.configuration-cache=false
|
|||||||
# Fabric Properties
|
# Fabric Properties
|
||||||
# check these on https://fabricmc.net/develop
|
# check these on https://fabricmc.net/develop
|
||||||
minecraft_version=1.21.10
|
minecraft_version=1.21.10
|
||||||
yarn_mappings=1.21.10+build.3
|
loader_version=0.19.5
|
||||||
loader_version=0.19.0
|
|
||||||
loom_version=1.15-SNAPSHOT
|
|
||||||
|
|
||||||
fabric_api_version=0.138.4+1.21.10
|
fabric_api_version=0.138.4+1.21.10
|
||||||
|
|
||||||
# Mod Properties
|
# Mod Properties
|
||||||
maven_group=org.saturnclient
|
maven_group=org.saturnclient
|
||||||
archives_base_name=saturn-client
|
archives_base_name=saturn-client
|
||||||
|
|
||||||
# player_anim = 2.0.2+1.21.5
|
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
pluginManagement {
|
|
||||||
repositories {
|
|
||||||
maven {
|
|
||||||
name = 'Fabric'
|
|
||||||
url = 'https://maven.fabricmc.net/'
|
|
||||||
}
|
|
||||||
mavenCentral()
|
|
||||||
gradlePluginPortal()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-462
@@ -1,462 +0,0 @@
|
|||||||
package org.saturnclient.impl.cosmetics;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
|
|
||||||
import org.joml.Matrix3f;
|
|
||||||
import org.joml.Matrix4f;
|
|
||||||
import org.joml.Vector3f;
|
|
||||||
import org.joml.Vector4f;
|
|
||||||
import org.saturnclient.client.player.SaturnPlayer;
|
|
||||||
import org.saturnclient.common.ref.asset.IdentifierRef;
|
|
||||||
import org.saturnclient.config.Config;
|
|
||||||
import org.saturnclient.cosmetics.Cloaks;
|
|
||||||
import org.saturnclient.impl.cosmetics.utils.ShaderUtils;
|
|
||||||
import org.saturnclient.saturnclient.SaturnRenderState;
|
|
||||||
|
|
||||||
import net.minecraft.client.render.OverlayTexture;
|
|
||||||
import net.minecraft.client.render.VertexConsumer;
|
|
||||||
import net.minecraft.client.render.command.OrderedRenderCommandQueue;
|
|
||||||
import net.minecraft.client.render.entity.equipment.EquipmentModel;
|
|
||||||
import net.minecraft.client.render.entity.equipment.EquipmentModelLoader;
|
|
||||||
import net.minecraft.client.render.entity.equipment.EquipmentModel.LayerType;
|
|
||||||
import net.minecraft.client.render.entity.feature.FeatureRenderer;
|
|
||||||
import net.minecraft.client.render.entity.feature.FeatureRendererContext;
|
|
||||||
import net.minecraft.client.render.entity.model.PlayerEntityModel;
|
|
||||||
import net.minecraft.client.render.entity.state.PlayerEntityRenderState;
|
|
||||||
import net.minecraft.client.util.math.MatrixStack;
|
|
||||||
import net.minecraft.component.DataComponentTypes;
|
|
||||||
import net.minecraft.component.type.EquippableComponent;
|
|
||||||
import net.minecraft.item.ItemStack;
|
|
||||||
import net.minecraft.item.equipment.EquipmentAsset;
|
|
||||||
import net.minecraft.registry.RegistryKey;
|
|
||||||
import net.minecraft.util.math.MathHelper;
|
|
||||||
import net.minecraft.util.math.RotationAxis;
|
|
||||||
import net.minecraft.util.math.Vec3d;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The cloak renderer
|
|
||||||
* - **Efficient**: uses a image only consisting of the cloak texture, 16x8
|
|
||||||
* whitespace.
|
|
||||||
* - **Curving**: Curves when player velocity increases.
|
|
||||||
* - **Wavey**: Makes the cloak feel like wind, when jump sprinting.
|
|
||||||
*/
|
|
||||||
public class CloakFeatureRenderer extends FeatureRenderer<PlayerEntityRenderState, PlayerEntityModel> {
|
|
||||||
private final EquipmentModelLoader equipmentModelLoader;
|
|
||||||
private static final int PARTS = 24;
|
|
||||||
private final float[] segmentValues = new float[PARTS];
|
|
||||||
private static final int H_PARTS = 6;
|
|
||||||
private float horizontalCurve = 0.0f;
|
|
||||||
|
|
||||||
private static final float TEX_W = 176f;
|
|
||||||
private static final float TEX_H = 138f;
|
|
||||||
|
|
||||||
private static final class PxRect {
|
|
||||||
final int x, y, w, h;
|
|
||||||
|
|
||||||
PxRect(int x, int y, int w, int h) {
|
|
||||||
this.x = x;
|
|
||||||
this.y = y;
|
|
||||||
this.w = w;
|
|
||||||
this.h = h;
|
|
||||||
}
|
|
||||||
|
|
||||||
float u1() {
|
|
||||||
return x / TEX_W;
|
|
||||||
}
|
|
||||||
|
|
||||||
float v1() {
|
|
||||||
return y / TEX_H;
|
|
||||||
}
|
|
||||||
|
|
||||||
float u2() {
|
|
||||||
return (x + w) / TEX_W;
|
|
||||||
}
|
|
||||||
|
|
||||||
float v2() {
|
|
||||||
return (y + h) / TEX_H;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final PxRect FRONT_RECT = new PxRect(8, 8, 80, 128);
|
|
||||||
private static final PxRect BACK_RECT = new PxRect(96, 8, 80, 128);
|
|
||||||
private static final PxRect LEFT_EDGE_RECT = new PxRect(0, 8, 8, 128);
|
|
||||||
private static final PxRect RIGHT_EDGE_RECT = new PxRect(88, 8, 8, 128);
|
|
||||||
private static final PxRect TOP_RECT = new PxRect(8, 0, 80, 8);
|
|
||||||
private static final PxRect BOTTOM_RECT = new PxRect(88, 0, 80, 8);
|
|
||||||
|
|
||||||
public CloakFeatureRenderer(FeatureRendererContext<PlayerEntityRenderState, PlayerEntityModel> context,
|
|
||||||
EquipmentModelLoader equipmentModelLoader) {
|
|
||||||
super(context);
|
|
||||||
this.equipmentModelLoader = equipmentModelLoader;
|
|
||||||
Arrays.fill(segmentValues, 0.0f);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean hasCustomModelForLayer(ItemStack stack, EquipmentModel.LayerType layerType) {
|
|
||||||
EquippableComponent equippableComponent = (EquippableComponent) stack
|
|
||||||
.get(DataComponentTypes.EQUIPPABLE);
|
|
||||||
if (equippableComponent != null && !equippableComponent.assetId().isEmpty()) {
|
|
||||||
EquipmentModel equipmentModel = this.equipmentModelLoader
|
|
||||||
.get((RegistryKey<EquipmentAsset>) equippableComponent.assetId().get());
|
|
||||||
return !equipmentModel.getLayers(layerType).isEmpty();
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void vertex(VertexConsumer vc, Matrix4f posMat, Matrix3f normalMat,
|
|
||||||
Vec3d pos, int color,
|
|
||||||
float u, float v, int overlay, int light,
|
|
||||||
float nx, float ny, float nz) {
|
|
||||||
|
|
||||||
Vector4f p = new Vector4f((float) pos.x, (float) pos.y, (float) pos.z, 1.0f);
|
|
||||||
p.mul(posMat);
|
|
||||||
|
|
||||||
Vector3f n = new Vector3f(nx, ny, nz);
|
|
||||||
n.mul(normalMat);
|
|
||||||
|
|
||||||
vc.vertex(
|
|
||||||
p.x(), p.y(), p.z(),
|
|
||||||
color,
|
|
||||||
u, v,
|
|
||||||
overlay, light,
|
|
||||||
n.x(), n.y(), n.z());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void renderCapeQuad(VertexConsumer vertexConsumer, MatrixStack.Entry entry,
|
|
||||||
Vec3d bottomLeft, Vec3d bottomRight, Vec3d topRight, Vec3d topLeft,
|
|
||||||
float u1, float v1, float u2, float v2, int light, int overlay,
|
|
||||||
float normalX, float normalY, float normalZ, boolean flipWinding) {
|
|
||||||
// Extract matrix (you must transform positions manually now)
|
|
||||||
Matrix4f pos = entry.getPositionMatrix();
|
|
||||||
Matrix3f normalMat = entry.getNormalMatrix();
|
|
||||||
|
|
||||||
int color = 0xFFFFFFFF; // white RGBA
|
|
||||||
|
|
||||||
if (!flipWinding) {
|
|
||||||
// CCW
|
|
||||||
vertex(vertexConsumer, pos, normalMat, bottomLeft, color, u1, v2, overlay, light, normalX, normalY,
|
|
||||||
normalZ);
|
|
||||||
vertex(vertexConsumer, pos, normalMat, bottomRight, color, u2, v2, overlay, light, normalX, normalY,
|
|
||||||
normalZ);
|
|
||||||
vertex(vertexConsumer, pos, normalMat, topRight, color, u2, v1, overlay, light, normalX, normalY, normalZ);
|
|
||||||
vertex(vertexConsumer, pos, normalMat, topLeft, color, u1, v1, overlay, light, normalX, normalY, normalZ);
|
|
||||||
} else {
|
|
||||||
// CW
|
|
||||||
vertex(vertexConsumer, pos, normalMat, topLeft, color, u1, v1, overlay, light, normalX, normalY, normalZ);
|
|
||||||
vertex(vertexConsumer, pos, normalMat, topRight, color, u2, v1, overlay, light, normalX, normalY, normalZ);
|
|
||||||
vertex(vertexConsumer, pos, normalMat, bottomRight, color, u2, v2, overlay, light, normalX, normalY,
|
|
||||||
normalZ);
|
|
||||||
vertex(vertexConsumer, pos, normalMat, bottomLeft, color, u1, v2, overlay, light, normalX, normalY,
|
|
||||||
normalZ);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void renderCape(MatrixStack.Entry entry, VertexConsumer vertexConsumer,
|
|
||||||
PlayerEntityRenderState playerState, int light, int overlay) {
|
|
||||||
|
|
||||||
float capeWidth = 10.0f / 16.0f;
|
|
||||||
float capeHeight = 16.0f / 16.0f;
|
|
||||||
float capeDepth = 1.0f / 16.0f;
|
|
||||||
|
|
||||||
final float frontU1 = FRONT_RECT.u1();
|
|
||||||
final float frontU2 = FRONT_RECT.u2();
|
|
||||||
final float frontV1 = FRONT_RECT.v1();
|
|
||||||
final float frontV2 = FRONT_RECT.v2();
|
|
||||||
final float frontPartV = (frontV2 - frontV1) / PARTS;
|
|
||||||
|
|
||||||
final float backU1 = BACK_RECT.u1();
|
|
||||||
final float backU2 = BACK_RECT.u2();
|
|
||||||
final float backV1 = BACK_RECT.v1();
|
|
||||||
final float backV2 = BACK_RECT.v2();
|
|
||||||
final float backPartV = (backV2 - backV1) / PARTS;
|
|
||||||
|
|
||||||
final float leftU1 = LEFT_EDGE_RECT.u1();
|
|
||||||
final float leftU2 = LEFT_EDGE_RECT.u2();
|
|
||||||
final float leftV1 = LEFT_EDGE_RECT.v1();
|
|
||||||
final float leftV2 = LEFT_EDGE_RECT.v2();
|
|
||||||
final float leftPartV = (leftV2 - leftV1) / PARTS;
|
|
||||||
|
|
||||||
final float rightU1 = RIGHT_EDGE_RECT.u1();
|
|
||||||
final float rightU2 = RIGHT_EDGE_RECT.u2();
|
|
||||||
final float rightV1 = RIGHT_EDGE_RECT.v1();
|
|
||||||
final float rightV2 = RIGHT_EDGE_RECT.v2();
|
|
||||||
final float rightPartV = (rightV2 - rightV1) / PARTS;
|
|
||||||
|
|
||||||
final float topU1 = TOP_RECT.u1();
|
|
||||||
final float topU2 = TOP_RECT.u2();
|
|
||||||
final float topV1 = TOP_RECT.v1();
|
|
||||||
final float topV2 = TOP_RECT.v2();
|
|
||||||
|
|
||||||
final float bottomU1 = BOTTOM_RECT.u1();
|
|
||||||
final float bottomU2 = BOTTOM_RECT.u2();
|
|
||||||
final float bottomV1 = BOTTOM_RECT.v1();
|
|
||||||
final float bottomV2 = BOTTOM_RECT.v2();
|
|
||||||
|
|
||||||
// =============================
|
|
||||||
// 1. Spine
|
|
||||||
// =============================
|
|
||||||
|
|
||||||
float[] spineY = new float[PARTS + 1];
|
|
||||||
float[] spineZ = new float[PARTS + 1];
|
|
||||||
|
|
||||||
float partHeight = capeHeight / PARTS;
|
|
||||||
|
|
||||||
for (int i = 0; i < PARTS; i++) {
|
|
||||||
float angle = (2.0f - segmentValues[i]) * ((float) Math.PI / 2f);
|
|
||||||
float dirY = -(float) Math.cos(angle);
|
|
||||||
float dirZ = (float) Math.sin(angle);
|
|
||||||
|
|
||||||
spineY[i + 1] = spineY[i] + dirY * partHeight;
|
|
||||||
spineZ[i + 1] = spineZ[i] + dirZ * partHeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
// =============================
|
|
||||||
// 2. Grid
|
|
||||||
// =============================
|
|
||||||
|
|
||||||
Vec3d[][] inner = new Vec3d[H_PARTS + 1][PARTS + 1];
|
|
||||||
Vec3d[][] outer = new Vec3d[H_PARTS + 1][PARTS + 1];
|
|
||||||
|
|
||||||
for (int x = 0; x <= H_PARTS; x++) {
|
|
||||||
|
|
||||||
float t = (float) x / H_PARTS;
|
|
||||||
float xPos = -capeWidth / 2.0f + capeWidth * t;
|
|
||||||
|
|
||||||
float centered = t - 0.5f;
|
|
||||||
float curve = (centered * centered) * 0.3f;
|
|
||||||
float zCurve = (curve * horizontalCurve) / PARTS;
|
|
||||||
|
|
||||||
for (int i = 0; i <= PARTS; i++) {
|
|
||||||
float y = spineY[i];
|
|
||||||
float z = spineZ[i];
|
|
||||||
|
|
||||||
int idx = Math.min(i, PARTS - 1);
|
|
||||||
// angle: 0 is horizontal (y-flat), PI/2 is vertical (z-flat)
|
|
||||||
float angle = (2.0f - segmentValues[idx]) * ((float) Math.PI / 2f);
|
|
||||||
|
|
||||||
float cosA = (float) Math.cos(angle);
|
|
||||||
float sinA = (float) Math.sin(angle);
|
|
||||||
|
|
||||||
// Calculate how much the horizontal curve shifts the vertex
|
|
||||||
float currentCurve = zCurve * i;
|
|
||||||
|
|
||||||
// Rotate the curve offset so it aligns with the cape's tilt
|
|
||||||
float curveY = sinA * currentCurve;
|
|
||||||
float curveZ = cosA * currentCurve;
|
|
||||||
|
|
||||||
float thickY = sinA * capeDepth;
|
|
||||||
float thickZ = cosA * capeDepth;
|
|
||||||
|
|
||||||
inner[x][i] = new Vec3d(xPos, y - curveY, z - curveZ);
|
|
||||||
outer[x][i] = new Vec3d(xPos, (y - curveY) - thickY, (z - curveZ) - thickZ);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// =============================
|
|
||||||
// 3. Front + Back
|
|
||||||
// =============================
|
|
||||||
|
|
||||||
for (int x = 0; x < H_PARTS; x++) {
|
|
||||||
|
|
||||||
float uStart = frontU1 + (frontU2 - frontU1) * (1.0f - ((float) x / H_PARTS));
|
|
||||||
float uEnd = frontU1 + (frontU2 - frontU1) * (1.0f - ((float) (x + 1) / H_PARTS));
|
|
||||||
|
|
||||||
float buStart = backU1 + (backU2 - backU1) * ((float) x / H_PARTS);
|
|
||||||
float buEnd = backU1 + (backU2 - backU1) * ((float) (x + 1) / H_PARTS);
|
|
||||||
|
|
||||||
for (int i = 0; i < PARTS; i++) {
|
|
||||||
|
|
||||||
float vStart = frontV1 + frontPartV * i;
|
|
||||||
float vEnd = frontV1 + frontPartV * (i + 1);
|
|
||||||
|
|
||||||
float bvStart = backV1 + backPartV * i;
|
|
||||||
float bvEnd = backV1 + backPartV * (i + 1);
|
|
||||||
|
|
||||||
Vec3d innerTL = inner[x + 1][i];
|
|
||||||
Vec3d innerTR = inner[x][i];
|
|
||||||
Vec3d innerBL = inner[x + 1][i + 1];
|
|
||||||
Vec3d innerBR = inner[x][i + 1];
|
|
||||||
|
|
||||||
Vec3d outerTL = outer[x + 1][i];
|
|
||||||
Vec3d outerTR = outer[x][i];
|
|
||||||
Vec3d outerBL = outer[x + 1][i + 1];
|
|
||||||
Vec3d outerBR = outer[x][i + 1];
|
|
||||||
|
|
||||||
// FRONT
|
|
||||||
renderCapeQuad(vertexConsumer, entry,
|
|
||||||
outerBR, outerBL, outerTL, outerTR,
|
|
||||||
uStart, vStart, uEnd, vEnd,
|
|
||||||
light, overlay, 0, 0, 1, false);
|
|
||||||
|
|
||||||
// BACK
|
|
||||||
renderCapeQuad(vertexConsumer, entry,
|
|
||||||
innerBL, innerBR, innerTR, innerTL,
|
|
||||||
buEnd, bvStart, buStart, bvEnd,
|
|
||||||
light, overlay, 0, 0, -1, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// =============================
|
|
||||||
// 4. LEFT SIDE (x = H_PARTS)
|
|
||||||
// =============================
|
|
||||||
|
|
||||||
for (int i = 0; i < PARTS; i++) {
|
|
||||||
|
|
||||||
Vec3d innerTop = inner[H_PARTS][i];
|
|
||||||
Vec3d innerBot = inner[H_PARTS][i + 1];
|
|
||||||
Vec3d outerTop = outer[H_PARTS][i];
|
|
||||||
Vec3d outerBot = outer[H_PARTS][i + 1];
|
|
||||||
|
|
||||||
float vStart = leftV1 + leftPartV * i;
|
|
||||||
float vEnd = leftV1 + leftPartV * (i + 1);
|
|
||||||
|
|
||||||
renderCapeQuad(vertexConsumer, entry,
|
|
||||||
innerTop, outerTop, outerBot, innerBot,
|
|
||||||
leftU1, vEnd,
|
|
||||||
leftU2, vStart,
|
|
||||||
light, overlay, -1, 0, 0, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
// =============================
|
|
||||||
// 5. RIGHT SIDE (x = 0)
|
|
||||||
// =============================
|
|
||||||
|
|
||||||
for (int i = 0; i < PARTS; i++) {
|
|
||||||
|
|
||||||
Vec3d innerTop = inner[0][i];
|
|
||||||
Vec3d innerBot = inner[0][i + 1];
|
|
||||||
Vec3d outerTop = outer[0][i];
|
|
||||||
Vec3d outerBot = outer[0][i + 1];
|
|
||||||
|
|
||||||
float vStart = rightV1 + rightPartV * i;
|
|
||||||
float vEnd = rightV1 + rightPartV * (i + 1);
|
|
||||||
|
|
||||||
renderCapeQuad(vertexConsumer, entry,
|
|
||||||
outerTop, innerTop, innerBot, outerBot,
|
|
||||||
rightU1, vEnd,
|
|
||||||
rightU2, vStart,
|
|
||||||
light, overlay, 1, 0, 0, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
// =============================
|
|
||||||
// 6. TOP (i = 0)
|
|
||||||
// =============================
|
|
||||||
|
|
||||||
for (int x = 0; x < H_PARTS; x++) {
|
|
||||||
|
|
||||||
Vec3d outerL = outer[x][0];
|
|
||||||
Vec3d outerR = outer[x + 1][0];
|
|
||||||
Vec3d innerR = inner[x + 1][0];
|
|
||||||
Vec3d innerL = inner[x][0];
|
|
||||||
|
|
||||||
float uStart = topU1 + (topU2 - topU1) * ((float) x / H_PARTS);
|
|
||||||
float uEnd = topU1 + (topU2 - topU1) * ((float) (x + 1) / H_PARTS);
|
|
||||||
|
|
||||||
renderCapeQuad(vertexConsumer, entry,
|
|
||||||
outerL, outerR, innerR, innerL,
|
|
||||||
uStart, topV1, uEnd, topV2,
|
|
||||||
light, overlay, 0, 1, 0, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
// =============================
|
|
||||||
// 7. BOTTOM (i = PARTS)
|
|
||||||
// =============================
|
|
||||||
|
|
||||||
for (int x = 0; x < H_PARTS; x++) {
|
|
||||||
|
|
||||||
Vec3d innerL = inner[x][PARTS];
|
|
||||||
Vec3d innerR = inner[x + 1][PARTS];
|
|
||||||
Vec3d outerR = outer[x + 1][PARTS];
|
|
||||||
Vec3d outerL = outer[x][PARTS];
|
|
||||||
|
|
||||||
float uStart = bottomU1 + (bottomU2 - bottomU1) * ((float) x / H_PARTS);
|
|
||||||
float uEnd = bottomU1 + (bottomU2 - bottomU1) * ((float) (x + 1) / H_PARTS);
|
|
||||||
|
|
||||||
renderCapeQuad(vertexConsumer, entry,
|
|
||||||
innerL, innerR, outerR, outerL,
|
|
||||||
uStart, bottomV1, uEnd, bottomV2,
|
|
||||||
light, overlay, 0, -1, 0, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Shift array left and insert new value at the start
|
|
||||||
private void updateSegmentValues(float value) {
|
|
||||||
// Shift all elements one position to the right (from end to start)
|
|
||||||
for (int i = segmentValues.length - 1; i > 0; i--) {
|
|
||||||
segmentValues[i] = segmentValues[i - 1];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Insert the new value at index 0
|
|
||||||
segmentValues[0] = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
private long lastUpdate = 0;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void render(MatrixStack matrices, OrderedRenderCommandQueue queue, int light, PlayerEntityRenderState state,
|
|
||||||
float limbAngle, float limbDistance) {
|
|
||||||
if (state.invisible || !state.capeVisible
|
|
||||||
|| state.skinTextures.cape() != null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
SaturnPlayer player = state.getData(SaturnRenderState.saturnDataKey);
|
|
||||||
|
|
||||||
if (player == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
IdentifierRef customCape = Cloaks.getCurrentCloakTexture(player.cloak);
|
|
||||||
if (customCape == null
|
|
||||||
|| this.hasCustomModelForLayer(state.equippedChestStack, LayerType.WINGS)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int minBrightness = 7;
|
|
||||||
int blockLight = (light >> 4) & 0xF;
|
|
||||||
int skyLight = (light >> 20) & 0xF;
|
|
||||||
blockLight = Math.max(blockLight, minBrightness);
|
|
||||||
skyLight = Math.max(skyLight, minBrightness);
|
|
||||||
light = (skyLight << 20) | (blockLight << 4);
|
|
||||||
|
|
||||||
matrices.push();
|
|
||||||
|
|
||||||
matrices.translate(0.0f, 0.0f, 0.12f);
|
|
||||||
|
|
||||||
if (this.hasCustomModelForLayer(state.equippedChestStack, LayerType.HUMANOID)) {
|
|
||||||
matrices.translate(0.0F, -0.053125F, 0.06875F);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (state.isInSneakingPose) {
|
|
||||||
matrices.translate(0, 0.16f, 0.0f);
|
|
||||||
matrices.multiply(RotationAxis.POSITIVE_X.rotationDegrees(29.0f));
|
|
||||||
}
|
|
||||||
|
|
||||||
long now = System.currentTimeMillis();
|
|
||||||
if (now - lastUpdate >= 20) {
|
|
||||||
float velX = Math.min(0.8f, state.field_53537 / 108.0f);
|
|
||||||
float rawVelY = state.field_53536;
|
|
||||||
float velY = (rawVelY > 3.4f ? rawVelY : rawVelY < -3.4f ? rawVelY : 0.0f) / 16;
|
|
||||||
|
|
||||||
float value = Math.max(0.0f, Math.min(2.0f, state.isSwimming ? 0.0f : velX + velY));
|
|
||||||
|
|
||||||
if (Config.cloakPhysics.value) {
|
|
||||||
updateSegmentValues(segmentValues[0] + (value - segmentValues[0]) * 0.3f);
|
|
||||||
} else {
|
|
||||||
Arrays.fill(segmentValues, segmentValues[0] + (value - segmentValues[0]) * 0.05f);
|
|
||||||
}
|
|
||||||
lastUpdate = now;
|
|
||||||
}
|
|
||||||
|
|
||||||
final int l = light;
|
|
||||||
|
|
||||||
horizontalCurve = MathHelper.clamp(state.field_53537 / 150.0f, -1.0f, 1.0f);
|
|
||||||
|
|
||||||
queue.submitCustom(matrices, ShaderUtils.getRenderLayer(customCape), (entry, vertexConsumer) -> {
|
|
||||||
renderCape(entry, vertexConsumer, state, l, OverlayTexture.DEFAULT_UV);
|
|
||||||
});
|
|
||||||
|
|
||||||
matrices.pop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-51
@@ -1,51 +0,0 @@
|
|||||||
package org.saturnclient.impl.cosmetics;
|
|
||||||
|
|
||||||
import org.joml.Quaternionf;
|
|
||||||
import org.saturnclient.client.player.SaturnPlayer;
|
|
||||||
import org.saturnclient.impl.cosmetics.obj.MtlLoader;
|
|
||||||
import org.saturnclient.impl.cosmetics.obj.ObjModel;
|
|
||||||
|
|
||||||
import net.minecraft.client.render.OverlayTexture;
|
|
||||||
import net.minecraft.client.render.VertexConsumerProvider;
|
|
||||||
import net.minecraft.client.render.command.OrderedRenderCommandQueue;
|
|
||||||
import net.minecraft.client.render.entity.feature.FeatureRenderer;
|
|
||||||
import net.minecraft.client.render.entity.feature.FeatureRendererContext;
|
|
||||||
import net.minecraft.client.render.entity.model.PlayerEntityModel;
|
|
||||||
import net.minecraft.client.render.entity.state.PlayerEntityRenderState;
|
|
||||||
import net.minecraft.client.util.math.MatrixStack;
|
|
||||||
import net.minecraft.client.model.ModelPart;
|
|
||||||
|
|
||||||
public class HatFeatureRenderer extends FeatureRenderer<PlayerEntityRenderState, PlayerEntityModel> {
|
|
||||||
public HatFeatureRenderer(FeatureRendererContext<PlayerEntityRenderState, PlayerEntityModel> context) {
|
|
||||||
super(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
// @Override
|
|
||||||
public void render(MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light,
|
|
||||||
PlayerEntityRenderState state, float limbAngle, float limbDistance) {
|
|
||||||
if (state.displayName == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
SaturnPlayer player = SaturnPlayer.get(state.displayName.getString());
|
|
||||||
|
|
||||||
if (state.invisible || player == null || player.hat.isEmpty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
matrices.push();
|
|
||||||
|
|
||||||
ModelPart head = this.getContextModel().head;
|
|
||||||
matrices.multiply(new Quaternionf().rotationZYX(head.roll, head.yaw, head.pitch));
|
|
||||||
|
|
||||||
ObjModel.cosmetic("hat", player.hat).render(MtlLoader.cosmetic(player.hat),
|
|
||||||
matrices, vertexConsumers, light,
|
|
||||||
OverlayTexture.DEFAULT_UV);
|
|
||||||
|
|
||||||
matrices.pop();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void render(MatrixStack matrices, OrderedRenderCommandQueue queue, int light, PlayerEntityRenderState state,
|
|
||||||
float limbAngle, float limbDistance) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
package org.saturnclient.impl.cosmetics.obj;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import de.javagl.obj.*;
|
|
||||||
|
|
||||||
import net.minecraft.client.MinecraftClient;
|
|
||||||
import net.minecraft.util.Identifier;
|
|
||||||
|
|
||||||
public class MtlLoader {
|
|
||||||
static Map<Identifier, Map<String, Mtl>> loadedMtls = new HashMap<>();
|
|
||||||
|
|
||||||
static Map<String, Mtl> loadMtl(Identifier mtlId) throws IOException {
|
|
||||||
try (InputStream is = MinecraftClient.getInstance().getResourceManager()
|
|
||||||
.getResource(mtlId).get().getInputStream()) {
|
|
||||||
List<Mtl> mtlList = MtlReader.read(is);
|
|
||||||
Map<String, Mtl> mtlMap = new java.util.LinkedHashMap<>();
|
|
||||||
for (Mtl mtl : mtlList) {
|
|
||||||
mtlMap.put(mtl.getName(), mtl);
|
|
||||||
}
|
|
||||||
return mtlMap;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Map<String, Mtl> of(Identifier mtlId) {
|
|
||||||
if (loadedMtls.containsKey(mtlId))
|
|
||||||
return loadedMtls.get(mtlId);
|
|
||||||
|
|
||||||
try {
|
|
||||||
Map<String, Mtl> mtl = loadMtl(mtlId);
|
|
||||||
loadedMtls.put(mtlId, mtl);
|
|
||||||
|
|
||||||
return mtl;
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Map<String, Mtl> cosmetic(String cosmeticId) {
|
|
||||||
int index = cosmeticId.indexOf('_');
|
|
||||||
|
|
||||||
String mtlId = (index == -1) ? "" : cosmeticId.substring(index + 1);
|
|
||||||
|
|
||||||
if (mtlId.isEmpty())
|
|
||||||
mtlId = "white";
|
|
||||||
|
|
||||||
return of(Identifier.of("saturnclient", "models/cosmetic/materials/" + mtlId + ".mtl"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,112 +0,0 @@
|
|||||||
package org.saturnclient.impl.cosmetics.obj;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import org.saturnclient.common.ref.asset.IdentifierRef;
|
|
||||||
import org.saturnclient.impl.cosmetics.utils.ShaderUtils;
|
|
||||||
|
|
||||||
import de.javagl.obj.*;
|
|
||||||
|
|
||||||
import net.minecraft.client.MinecraftClient;
|
|
||||||
import net.minecraft.client.render.VertexConsumer;
|
|
||||||
import net.minecraft.client.render.VertexConsumerProvider;
|
|
||||||
import net.minecraft.client.util.math.MatrixStack;
|
|
||||||
import net.minecraft.util.Identifier;
|
|
||||||
|
|
||||||
public class ObjRenderer {
|
|
||||||
|
|
||||||
public static Obj loadObj(Identifier objId) throws IOException {
|
|
||||||
try (InputStream is = MinecraftClient.getInstance().getResourceManager()
|
|
||||||
.getResource(objId).get().getInputStream()) {
|
|
||||||
return ObjReader.read(is);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void renderObj(Obj obj, Map<String, Mtl> mtlMap, MatrixStack matrices,
|
|
||||||
VertexConsumerProvider vertexConsumers, int light, int overlay) {
|
|
||||||
MatrixStack.Entry entry = matrices.peek();
|
|
||||||
|
|
||||||
Map<String, Obj> materialGroups = ObjSplitting.splitByMaterialGroups(obj);
|
|
||||||
|
|
||||||
for (Map.Entry<String, Obj> group : materialGroups.entrySet()) {
|
|
||||||
String materialName = group.getKey();
|
|
||||||
Obj groupObj = group.getValue();
|
|
||||||
|
|
||||||
Mtl mtl = mtlMap != null ? mtlMap.get(materialName) : null;
|
|
||||||
|
|
||||||
// Resolve texture identifier using same logic as ObjUnbakedModelModel
|
|
||||||
IdentifierRef texture = IdentifierRef.ofVanilla("textures/misc/white.png");
|
|
||||||
|
|
||||||
if (mtl != null) {
|
|
||||||
String mapKd = mtl.getMapKd();
|
|
||||||
if (mapKd != null) {
|
|
||||||
if (!mapKd.startsWith("#")) {
|
|
||||||
texture = IdentifierRef.of(mapKd);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Diffuse color (Kd)
|
|
||||||
float r = 1f, g = 1f, b = 1f;
|
|
||||||
if (mtl != null) {
|
|
||||||
FloatTuple kd = mtl.getKd();
|
|
||||||
if (kd != null) {
|
|
||||||
r = kd.getX();
|
|
||||||
g = kd.getY();
|
|
||||||
b = kd.getZ();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
VertexConsumer consumer = vertexConsumers.getBuffer(ShaderUtils.getRenderLayer(texture));
|
|
||||||
|
|
||||||
int faces = groupObj.getNumFaces();
|
|
||||||
final float fr = r, fg = g, fb = b;
|
|
||||||
|
|
||||||
for (int f = 0; f < faces; f++) {
|
|
||||||
ObjFace face = groupObj.getFace(f);
|
|
||||||
int verts = face.getNumVertices();
|
|
||||||
|
|
||||||
// Your original winding order logic, untouched
|
|
||||||
for (int i = verts - 1; i >= 0; i--) {
|
|
||||||
writeVertex(groupObj, face, i, consumer, entry, light, overlay, fr, fg, fb);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void writeVertex(Obj obj, ObjFace face, int index, VertexConsumer consumer,
|
|
||||||
MatrixStack.Entry matrix, int light, int overlay,
|
|
||||||
float r, float g, float b) {
|
|
||||||
|
|
||||||
FloatTuple pos = obj.getVertex(face.getVertexIndex(index));
|
|
||||||
float x = pos.getX();
|
|
||||||
float y = pos.getY();
|
|
||||||
float z = pos.getZ();
|
|
||||||
|
|
||||||
float nx = 0f, ny = 1f, nz = 0f;
|
|
||||||
int normalIndex = face.getNormalIndex(index);
|
|
||||||
if (normalIndex >= 0) {
|
|
||||||
FloatTuple n = obj.getNormal(normalIndex);
|
|
||||||
nx = n.getX();
|
|
||||||
ny = n.getY();
|
|
||||||
nz = n.getZ();
|
|
||||||
}
|
|
||||||
|
|
||||||
float u = 0f, v = 0f;
|
|
||||||
int texIndex = face.getTexCoordIndex(index);
|
|
||||||
if (texIndex >= 0) {
|
|
||||||
FloatTuple tc = obj.getTexCoord(texIndex);
|
|
||||||
u = tc.getX();
|
|
||||||
v = 1f - tc.getY(); // Flip V: OBJ bottom-left origin -> Minecraft top-left
|
|
||||||
}
|
|
||||||
|
|
||||||
consumer.vertex(matrix, x, y, z)
|
|
||||||
.color(r, g, b, 1f)
|
|
||||||
.texture(u, v)
|
|
||||||
.overlay(overlay)
|
|
||||||
.light(light)
|
|
||||||
.normal(matrix, nx, ny, nz);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-71
@@ -1,71 +0,0 @@
|
|||||||
package org.saturnclient.impl.cosmetics.utils;
|
|
||||||
|
|
||||||
import org.saturnclient.common.ref.asset.IdentifierRef;
|
|
||||||
import org.saturnclient.saturnclient.SaturnClient;
|
|
||||||
|
|
||||||
import net.minecraft.client.texture.NativeImage;
|
|
||||||
import net.minecraft.client.texture.NativeImageBackedTexture;
|
|
||||||
import net.minecraft.util.Identifier;
|
|
||||||
|
|
||||||
import java.awt.image.BufferedImage;
|
|
||||||
import java.awt.image.DataBufferInt;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
public class IdentifierUtils {
|
|
||||||
|
|
||||||
public static void registerBufferedImageTextureFast(IdentifierRef identifierRef, BufferedImage bufferedImage) {
|
|
||||||
try {
|
|
||||||
Identifier identifier = (Identifier) (Object) identifierRef;
|
|
||||||
|
|
||||||
int width = bufferedImage.getWidth();
|
|
||||||
int height = bufferedImage.getHeight();
|
|
||||||
|
|
||||||
NativeImage nativeImage = new NativeImage(NativeImage.Format.RGBA, width, height, false);
|
|
||||||
|
|
||||||
if (bufferedImage.getType() == BufferedImage.TYPE_INT_ARGB) {
|
|
||||||
int[] pixels = ((DataBufferInt) bufferedImage.getRaster().getDataBuffer()).getData();
|
|
||||||
|
|
||||||
int index = 0;
|
|
||||||
for (int y = 0; y < height; y++) {
|
|
||||||
for (int x = 0; x < width; x++) {
|
|
||||||
nativeImage.setColorArgb(x, y, pixels[index++]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
|
|
||||||
for (int y = 0; y < height; y++) {
|
|
||||||
for (int x = 0; x < width; x++) {
|
|
||||||
nativeImage.setColorArgb(x, y, bufferedImage.getRGB(x, y));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
SaturnClient.client.execute(() -> {
|
|
||||||
NativeImageBackedTexture texture = new NativeImageBackedTexture(
|
|
||||||
() -> identifier.toString(),
|
|
||||||
nativeImage);
|
|
||||||
|
|
||||||
SaturnClient.client.getTextureManager().registerTexture(identifier, texture);
|
|
||||||
});
|
|
||||||
|
|
||||||
} catch (Exception e) {
|
|
||||||
SaturnClient.LOGGER.error("Failed to register texture: {}", identifierRef, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void registerBufferedImageTexturesBulk(Map<IdentifierRef, BufferedImage> textures) {
|
|
||||||
|
|
||||||
long start = System.currentTimeMillis();
|
|
||||||
|
|
||||||
textures.forEach(IdentifierUtils::registerBufferedImageTextureFast);
|
|
||||||
|
|
||||||
long end = System.currentTimeMillis();
|
|
||||||
|
|
||||||
SaturnClient.LOGGER.info(
|
|
||||||
"Registered {} textures in {}ms",
|
|
||||||
textures.size(),
|
|
||||||
(end - start));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
package org.saturnclient.impl.cosmetics.utils;
|
|
||||||
|
|
||||||
import org.saturnclient.common.ref.asset.IdentifierRef;
|
|
||||||
import org.saturnclient.cosmetics.utils.GifUtils;
|
|
||||||
|
|
||||||
import com.mojang.blaze3d.pipeline.RenderPipeline;
|
|
||||||
|
|
||||||
import net.minecraft.client.gl.RenderPipelines;
|
|
||||||
import net.minecraft.client.render.RenderLayer;
|
|
||||||
import net.minecraft.client.render.RenderPhase.Textures;
|
|
||||||
import net.minecraft.client.render.block.entity.EndPortalBlockEntityRenderer;
|
|
||||||
import net.minecraft.util.Identifier;
|
|
||||||
|
|
||||||
public class ShaderUtils {
|
|
||||||
public static final RenderPipeline END_GATEWAY_PIPELINE = RenderPipelines.register(
|
|
||||||
RenderPipeline.builder(new RenderPipeline.Snippet[] { RenderPipelines.RENDERTYPE_END_PORTAL_SNIPPET })
|
|
||||||
.withLocation("pipeline/end_gateway")
|
|
||||||
.withShaderDefine("PORTAL_LAYERS", 16)
|
|
||||||
.withCull(false)
|
|
||||||
.build());
|
|
||||||
|
|
||||||
public static final RenderLayer END_GATEWAY = RenderLayer.of("end_gateway", 1536, false, false,
|
|
||||||
END_GATEWAY_PIPELINE,
|
|
||||||
RenderLayer.MultiPhaseParameters.builder()
|
|
||||||
.texture(Textures.create().add(EndPortalBlockEntityRenderer.SKY_TEXTURE, false)
|
|
||||||
.add(EndPortalBlockEntityRenderer.PORTAL_TEXTURE, false).build())
|
|
||||||
.build(false));
|
|
||||||
|
|
||||||
public static RenderLayer getRenderLayer(IdentifierRef texture) {
|
|
||||||
String path = texture.toString();
|
|
||||||
if (path.endsWith(".gif")) {
|
|
||||||
return RenderLayer.getEntityAlpha((Identifier) (Object) GifUtils.get(texture));
|
|
||||||
} else if (path.endsWith("end.png")) {
|
|
||||||
return END_GATEWAY;
|
|
||||||
} else {
|
|
||||||
return RenderLayer.getEntityAlpha((Identifier) (Object) texture);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-46
@@ -1,46 +0,0 @@
|
|||||||
package org.saturnclient.impl.features.mixins.entity;
|
|
||||||
|
|
||||||
import org.saturnclient.impl.features.entity.CameraOverriddenEntity;
|
|
||||||
import org.saturnclient.mod.mods.FreelookMod;
|
|
||||||
import org.saturnclient.saturnclient.SaturnClient;
|
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
|
||||||
import org.spongepowered.asm.mixin.Shadow;
|
|
||||||
import org.spongepowered.asm.mixin.Unique;
|
|
||||||
import org.spongepowered.asm.mixin.injection.At;
|
|
||||||
import org.spongepowered.asm.mixin.injection.Inject;
|
|
||||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
|
||||||
|
|
||||||
import net.minecraft.client.network.ClientPlayerEntity;
|
|
||||||
import net.minecraft.client.render.Camera;
|
|
||||||
import net.minecraft.entity.Entity;
|
|
||||||
import net.minecraft.world.BlockView;
|
|
||||||
|
|
||||||
@Mixin(Camera.class)
|
|
||||||
public abstract class CameraMixin {
|
|
||||||
@Unique
|
|
||||||
boolean firstTime = true;
|
|
||||||
|
|
||||||
@Shadow
|
|
||||||
protected abstract void setRotation(float yaw, float pitch);
|
|
||||||
|
|
||||||
@Inject(method = "update", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/Camera;setRotation(FF)V", ordinal = 1, shift = At.Shift.AFTER))
|
|
||||||
public void lockRotation(BlockView focusedBlock, Entity cameraEntity, boolean isThirdPerson, boolean isFrontFacing,
|
|
||||||
float tickDelta, CallbackInfo ci) {
|
|
||||||
if (FreelookMod.isFreeLooking() && cameraEntity instanceof ClientPlayerEntity) {
|
|
||||||
CameraOverriddenEntity cameraOverriddenEntity = (CameraOverriddenEntity) cameraEntity;
|
|
||||||
|
|
||||||
if (firstTime && SaturnClient.client.player != null) {
|
|
||||||
cameraOverriddenEntity.freelook$setCameraPitch(SaturnClient.client.player.getPitch());
|
|
||||||
cameraOverriddenEntity.freelook$setCameraYaw(SaturnClient.client.player.getYaw());
|
|
||||||
firstTime = false;
|
|
||||||
}
|
|
||||||
this.setRotation(cameraOverriddenEntity.freelook$getCameraYaw(),
|
|
||||||
cameraOverriddenEntity.freelook$getCameraPitch());
|
|
||||||
|
|
||||||
}
|
|
||||||
if (!FreelookMod.isFreeLooking() && cameraEntity instanceof ClientPlayerEntity) {
|
|
||||||
firstTime = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
-62
@@ -1,62 +0,0 @@
|
|||||||
package org.saturnclient.impl.features.mixins.entity;
|
|
||||||
|
|
||||||
import org.saturnclient.impl.features.entity.CameraOverriddenEntity;
|
|
||||||
import org.saturnclient.mod.mods.FreelookMod;
|
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
|
||||||
import org.spongepowered.asm.mixin.Unique;
|
|
||||||
import org.spongepowered.asm.mixin.injection.At;
|
|
||||||
import org.spongepowered.asm.mixin.injection.Inject;
|
|
||||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
|
||||||
|
|
||||||
import net.minecraft.client.network.ClientPlayerEntity;
|
|
||||||
import net.minecraft.entity.Entity;
|
|
||||||
import net.minecraft.util.math.MathHelper;
|
|
||||||
|
|
||||||
@Mixin(Entity.class)
|
|
||||||
public class EntityMixin implements CameraOverriddenEntity {
|
|
||||||
@Unique
|
|
||||||
private float cameraPitch;
|
|
||||||
|
|
||||||
@Unique
|
|
||||||
private float cameraYaw;
|
|
||||||
|
|
||||||
@Inject(method = "changeLookDirection", at = @At("HEAD"), cancellable = true)
|
|
||||||
public void changeCameraLookDirection(double xDelta, double yDelta, CallbackInfo ci) {
|
|
||||||
// noinspection ConstantValue// IntelliJ is incorrect here, this code block is
|
|
||||||
// reachable
|
|
||||||
if (FreelookMod.isFreeLooking() && (Object) this instanceof ClientPlayerEntity) {
|
|
||||||
double pitchDelta = (yDelta * 0.15);
|
|
||||||
double yawDelta = (xDelta * 0.15);
|
|
||||||
|
|
||||||
this.cameraPitch = MathHelper.clamp(this.cameraPitch + (float) pitchDelta, -90.0f, 90.0f);
|
|
||||||
this.cameraYaw += (float) yawDelta;
|
|
||||||
|
|
||||||
ci.cancel();
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@Unique
|
|
||||||
public float freelook$getCameraPitch() {
|
|
||||||
return this.cameraPitch;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@Unique
|
|
||||||
public float freelook$getCameraYaw() {
|
|
||||||
return this.cameraYaw;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@Unique
|
|
||||||
public void freelook$setCameraPitch(float pitch) {
|
|
||||||
this.cameraPitch = pitch;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@Unique
|
|
||||||
public void freelook$setCameraYaw(float yaw) {
|
|
||||||
this.cameraYaw = yaw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-21
@@ -1,21 +0,0 @@
|
|||||||
package org.saturnclient.impl.features.mixins.render;
|
|
||||||
|
|
||||||
import net.minecraft.client.render.fog.FogRenderer;
|
|
||||||
|
|
||||||
import org.saturnclient.mod.mods.NoFogMod;
|
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
|
||||||
import org.spongepowered.asm.mixin.injection.At;
|
|
||||||
import org.spongepowered.asm.mixin.injection.ModifyVariable;
|
|
||||||
|
|
||||||
@Mixin(FogRenderer.class)
|
|
||||||
public class BackgroundRendererMixin {
|
|
||||||
// fog
|
|
||||||
@ModifyVariable(method = "applyFog", at = @At("HEAD"), ordinal = 0, argsOnly = true)
|
|
||||||
private static float modifyFogStart(float viewDistance) {
|
|
||||||
if (NoFogMod.isActive()) {
|
|
||||||
return viewDistance * 2.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
return viewDistance;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-37
@@ -1,37 +0,0 @@
|
|||||||
package org.saturnclient.impl.features.mixins.render;
|
|
||||||
|
|
||||||
import net.minecraft.client.render.GameRenderer;
|
|
||||||
|
|
||||||
import org.saturnclient.mod.mods.ZoomMod;
|
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
|
||||||
import org.spongepowered.asm.mixin.injection.At;
|
|
||||||
import org.spongepowered.asm.mixin.injection.Inject;
|
|
||||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Overrides the game's field-of-view when {@link ZoomMod} is active.
|
|
||||||
*
|
|
||||||
* The old code checked both {@code Zoom.isZooming} and
|
|
||||||
* {@code Zoom.shouldZoom()} separately. The refactored
|
|
||||||
* {@link ZoomMod#shouldZoom()} already ORs both conditions
|
|
||||||
* ({@code enabled.value && isZooming}), so only one check is needed.
|
|
||||||
*
|
|
||||||
* No {@link org.saturnclient.common.module.ModProvider} is required
|
|
||||||
* here because zooming is a pure render-time override with no per-tick
|
|
||||||
* engine queries.
|
|
||||||
*/
|
|
||||||
@Mixin(GameRenderer.class)
|
|
||||||
public class GameRendererMixin {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* After the vanilla FOV has been computed, divide it by the zoom
|
|
||||||
* level to produce the narrowed field-of-view.
|
|
||||||
*/
|
|
||||||
@Inject(method = "getFov", at = @At("RETURN"), cancellable = true)
|
|
||||||
private void onGetFov(CallbackInfoReturnable<Float> cir) {
|
|
||||||
if (ZoomMod.shouldZoom()) {
|
|
||||||
float zoomedFov = cir.getReturnValue() / ZoomMod.getZoomLevel();
|
|
||||||
cir.setReturnValue(zoomedFov);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-35
@@ -1,35 +0,0 @@
|
|||||||
package org.saturnclient.impl.features.mixins.render;
|
|
||||||
|
|
||||||
// Code approach adapted from Gamma Utils: https://github.com/Sjouwer/gamma-utils
|
|
||||||
|
|
||||||
import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
|
|
||||||
import net.minecraft.client.render.LightmapTextureManager;
|
|
||||||
|
|
||||||
import org.saturnclient.mod.mods.FullbrightMod;
|
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
|
||||||
import org.spongepowered.asm.mixin.injection.At;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Overrides the lightmap's gamma computation when
|
|
||||||
* {@link FullbrightMod} is active, replacing the vanilla
|
|
||||||
* brightness with a user-configured value.
|
|
||||||
*
|
|
||||||
* The old import was {@code org.saturnclient.feature.features.Fullbright};
|
|
||||||
* it is now {@link FullbrightMod}. The injection target and
|
|
||||||
* strategy are unchanged.
|
|
||||||
*/
|
|
||||||
@Mixin(LightmapTextureManager.class)
|
|
||||||
public class LightmapTextureManagerMixin {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If fullbright is active, substitute the feature's brightness
|
|
||||||
* value for whatever vanilla calculated.
|
|
||||||
*/
|
|
||||||
@ModifyExpressionValue(method = "update", at = @At(value = "INVOKE", target = "Ljava/lang/Math;max(FF)F", ordinal = 0))
|
|
||||||
private float modifyGamma(float original) {
|
|
||||||
if (FullbrightMod.shouldOverrideBrightness()) {
|
|
||||||
return FullbrightMod.getBrightnessValue();
|
|
||||||
}
|
|
||||||
return original;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-59
@@ -1,59 +0,0 @@
|
|||||||
package org.saturnclient.impl.features.mixins.render;
|
|
||||||
|
|
||||||
import net.minecraft.client.render.entity.state.LivingEntityRenderState;
|
|
||||||
|
|
||||||
import org.saturnclient.common.feature.EntityFeature;
|
|
||||||
import org.saturnclient.impl.features.entity.HealthRenderState;
|
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
|
||||||
import org.spongepowered.asm.mixin.Unique;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Implements {@link HealthRenderState} on
|
|
||||||
* {@link LivingEntityRenderState}, injecting health and entity-type
|
|
||||||
* storage into every living entity's render-state snapshot.
|
|
||||||
*
|
|
||||||
* Previously imported {@code NametagsInterface.EntityType}; the enum
|
|
||||||
* now lives in {@link EntityFeature.EntityType} which is the single
|
|
||||||
* canonical definition shared by the mixin layer and the feature layer.
|
|
||||||
*
|
|
||||||
* The stored values are written by
|
|
||||||
* {@link LivingEntityRendererMixin#saturn$captureHealth} each time a
|
|
||||||
* render state is extracted from a live entity, and read by
|
|
||||||
* {@link NametagsMixin} at render time.
|
|
||||||
*/
|
|
||||||
@Mixin(LivingEntityRenderState.class)
|
|
||||||
public class LivingEntityRenderStateMixin implements HealthRenderState {
|
|
||||||
|
|
||||||
@Unique
|
|
||||||
private float saturn$health = 0f;
|
|
||||||
@Unique
|
|
||||||
private float saturn$maxHealth = 1f;
|
|
||||||
@Unique
|
|
||||||
private EntityFeature.EntityType saturn$entityType = EntityFeature.EntityType.OTHER;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public float saturn$getHealth() {
|
|
||||||
return saturn$health;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public float saturn$getMaxHealth() {
|
|
||||||
return saturn$maxHealth;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void saturn$setHealth(float health, float maxHealth) {
|
|
||||||
this.saturn$health = health;
|
|
||||||
this.saturn$maxHealth = maxHealth;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public EntityFeature.EntityType saturn$getEntityType() {
|
|
||||||
return saturn$entityType;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void saturn$setEntityType(EntityFeature.EntityType type) {
|
|
||||||
saturn$entityType = type;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-51
@@ -1,51 +0,0 @@
|
|||||||
package org.saturnclient.impl.features.mixins.render;
|
|
||||||
|
|
||||||
import net.minecraft.client.render.entity.LivingEntityRenderer;
|
|
||||||
import net.minecraft.client.render.entity.state.LivingEntityRenderState;
|
|
||||||
import net.minecraft.entity.LivingEntity;
|
|
||||||
import net.minecraft.entity.mob.HostileEntity;
|
|
||||||
import net.minecraft.entity.passive.PassiveEntity;
|
|
||||||
import net.minecraft.entity.player.PlayerEntity;
|
|
||||||
|
|
||||||
import org.saturnclient.common.feature.EntityFeature;
|
|
||||||
import org.saturnclient.impl.features.entity.HealthRenderState;
|
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
|
||||||
import org.spongepowered.asm.mixin.injection.At;
|
|
||||||
import org.spongepowered.asm.mixin.injection.Inject;
|
|
||||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Captures each living entity's health and type into the render state
|
|
||||||
* during {@code updateRenderState}, so that nametag rendering can read
|
|
||||||
* them without touching the live entity again.
|
|
||||||
*
|
|
||||||
* Previously imported {@code NametagsInterface.EntityType}; now uses
|
|
||||||
* {@link EntityFeature.EntityType} — the canonical enum shared across
|
|
||||||
* the whole module system.
|
|
||||||
*
|
|
||||||
* The injection target and strategy are unchanged from the original.
|
|
||||||
*/
|
|
||||||
@Mixin(LivingEntityRenderer.class)
|
|
||||||
public abstract class LivingEntityRendererMixin<T extends LivingEntity, S extends LivingEntityRenderState> {
|
|
||||||
|
|
||||||
@Inject(method = "updateRenderState", at = @At("TAIL"))
|
|
||||||
private void saturn$captureHealth(T entity, S state, float tickDelta, CallbackInfo ci) {
|
|
||||||
if (!(state instanceof HealthRenderState hrs))
|
|
||||||
return;
|
|
||||||
|
|
||||||
hrs.saturn$setHealth(entity.getHealth(), entity.getMaxHealth());
|
|
||||||
|
|
||||||
// Classify entity type for the nametag feature's filtering logic
|
|
||||||
EntityFeature.EntityType type;
|
|
||||||
if (entity instanceof PlayerEntity)
|
|
||||||
type = EntityFeature.EntityType.PLAYER;
|
|
||||||
else if (entity instanceof HostileEntity)
|
|
||||||
type = EntityFeature.EntityType.HOSTILE;
|
|
||||||
else if (entity instanceof PassiveEntity)
|
|
||||||
type = EntityFeature.EntityType.PASSIVE;
|
|
||||||
else
|
|
||||||
type = EntityFeature.EntityType.OTHER;
|
|
||||||
|
|
||||||
hrs.saturn$setEntityType(type);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-82
@@ -1,82 +0,0 @@
|
|||||||
package org.saturnclient.impl.features.mixins.render;
|
|
||||||
|
|
||||||
import net.minecraft.client.font.TextRenderer;
|
|
||||||
import net.minecraft.client.gui.DrawContext;
|
|
||||||
import net.minecraft.client.gui.hud.InGameHud;
|
|
||||||
import net.minecraft.client.render.RenderTickCounter;
|
|
||||||
|
|
||||||
import org.saturnclient.impl.ui.RenderScopeImpl;
|
|
||||||
import org.saturnclient.impl.ui.SaturnScreenFabric;
|
|
||||||
import org.saturnclient.mod.HudMod;
|
|
||||||
import org.saturnclient.mod.Mod;
|
|
||||||
import org.saturnclient.mod.ModLayout;
|
|
||||||
import org.saturnclient.mod.ModManager;
|
|
||||||
import org.saturnclient.saturnclient.SaturnClient;
|
|
||||||
import org.saturnclient.ui.RenderScope;
|
|
||||||
import org.saturnclient.ui.screens.HudEditor;
|
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
|
||||||
import org.spongepowered.asm.mixin.injection.At;
|
|
||||||
import org.spongepowered.asm.mixin.injection.Inject;
|
|
||||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Drives per-frame rendering for all registered features.
|
|
||||||
*
|
|
||||||
* This mixin is intentionally kept thin: it creates a {@link RenderScope},
|
|
||||||
* iterates the enabled feature list from {@link ModManager}, and
|
|
||||||
* dispatches either {@link HudMod#renderHud} (for HUD elements) or
|
|
||||||
* {@link Mod#render} (for world-overlay features like Crosshair).
|
|
||||||
*
|
|
||||||
* No feature-specific logic lives here — all decisions about what to
|
|
||||||
* draw are made inside each feature class.
|
|
||||||
*
|
|
||||||
* Changes from the original:
|
|
||||||
* - Package imports updated to the refactored feature names (no
|
|
||||||
* more {@code feature.features.*}).
|
|
||||||
* - The HUD-editor screen check still uses the existing
|
|
||||||
* {@link SaturnScreenFabric} / {@link HudEditor} pair; that is
|
|
||||||
* unchanged infrastructure.
|
|
||||||
*/
|
|
||||||
@Mixin(InGameHud.class)
|
|
||||||
public class RenderMixin {
|
|
||||||
|
|
||||||
@Inject(method = "renderChat", at = @At("TAIL"))
|
|
||||||
public void renderMods(DrawContext context, RenderTickCounter tickCounter, CallbackInfo ci) {
|
|
||||||
TextRenderer textRenderer = SaturnClient.client.textRenderer;
|
|
||||||
|
|
||||||
// Skip rendering while the HUD editor is open so the editor
|
|
||||||
// can draw its own preview layout without double-drawing.
|
|
||||||
if (textRenderer == null)
|
|
||||||
return;
|
|
||||||
if (SaturnClient.client.currentScreen instanceof SaturnScreenFabric f
|
|
||||||
&& f.screen instanceof HudEditor)
|
|
||||||
return;
|
|
||||||
|
|
||||||
RenderScope scope = new RenderScopeImpl(context);
|
|
||||||
|
|
||||||
for (Mod feature : ModManager.ENABLED_MODS) {
|
|
||||||
if (!feature.isEnabled())
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (feature instanceof HudMod hud) {
|
|
||||||
ModLayout dim = hud.getDimensions();
|
|
||||||
|
|
||||||
scope.getMatrixStack().push();
|
|
||||||
scope.getMatrixStack().translate(dim.x.value, (float) dim.y.value);
|
|
||||||
scope.getMatrixStack().scale(dim.scale.value, dim.scale.value);
|
|
||||||
|
|
||||||
if (dim.renderBackground) {
|
|
||||||
scope.drawRoundedRectangle(0, 0, dim.width, dim.height,
|
|
||||||
dim.radius.value, dim.bgColor.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
hud.renderHud(scope);
|
|
||||||
scope.getMatrixStack().pop();
|
|
||||||
|
|
||||||
} else {
|
|
||||||
// Non-HUD features (e.g. CrosshairFeature) use the plain render hook.
|
|
||||||
feature.render(scope);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-30
@@ -1,30 +0,0 @@
|
|||||||
package org.saturnclient.impl.features.mixins.world;
|
|
||||||
|
|
||||||
import net.minecraft.client.network.ClientPlayNetworkHandler;
|
|
||||||
import net.minecraft.network.packet.s2c.play.WorldTimeUpdateS2CPacket;
|
|
||||||
|
|
||||||
import org.saturnclient.mod.mods.TpsMod;
|
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
|
||||||
import org.spongepowered.asm.mixin.injection.At;
|
|
||||||
import org.spongepowered.asm.mixin.injection.Inject;
|
|
||||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Feeds world-time packets into {@link TpsMod}'s TPS estimator.
|
|
||||||
*
|
|
||||||
* This is the only place in the entire codebase that reads a network
|
|
||||||
* packet and passes data to a feature. The feature's
|
|
||||||
* {@link TpsMod#onTimePacket(long)} method handles all averaging
|
|
||||||
* logic; the mixin is intentionally reduced to a single forwarding call.
|
|
||||||
*
|
|
||||||
* The only change from the original is renaming the import from
|
|
||||||
* {@code feature.features.Tps} to {@link TpsMod}.
|
|
||||||
*/
|
|
||||||
@Mixin(ClientPlayNetworkHandler.class)
|
|
||||||
public class TpsMixin {
|
|
||||||
|
|
||||||
@Inject(method = "onWorldTimeUpdate", at = @At("HEAD"))
|
|
||||||
private void onWorldTimeUpdate(WorldTimeUpdateS2CPacket packet, CallbackInfo ci) {
|
|
||||||
TpsMod.onTimePacket(packet.time());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-37
@@ -1,37 +0,0 @@
|
|||||||
package org.saturnclient.impl.features.network;
|
|
||||||
|
|
||||||
import org.saturnclient.common.feature.NetworkFeature;
|
|
||||||
|
|
||||||
import net.minecraft.client.MinecraftClient;
|
|
||||||
import net.minecraft.client.network.PlayerListEntry;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fabric implementation of {@link NetworkFeature}.
|
|
||||||
*
|
|
||||||
* Connection presence is checked via the network handler.
|
|
||||||
* Ping is read from the player's {@link PlayerListEntry} which the
|
|
||||||
* server populates; returns {@code -1} when unavailable.
|
|
||||||
*/
|
|
||||||
public class NetworkFeatureImpl implements NetworkFeature {
|
|
||||||
|
|
||||||
private final MinecraftClient mc;
|
|
||||||
|
|
||||||
public NetworkFeatureImpl(MinecraftClient mc) {
|
|
||||||
this.mc = mc;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean hasNetwork() {
|
|
||||||
return mc.getNetworkHandler() != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getPing() {
|
|
||||||
if (mc.player == null || mc.getNetworkHandler() == null)
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
PlayerListEntry entry = mc.getNetworkHandler().getPlayerListEntry(mc.player.getUuid());
|
|
||||||
|
|
||||||
return entry != null ? entry.getLatency() : -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-36
@@ -1,36 +0,0 @@
|
|||||||
package org.saturnclient.impl.features.world;
|
|
||||||
|
|
||||||
import org.saturnclient.common.feature.WorldFeature;
|
|
||||||
|
|
||||||
import net.minecraft.client.MinecraftClient;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fabric implementation of {@link WorldFeature}.
|
|
||||||
*
|
|
||||||
* Reads the client world's time directly from {@link MinecraftClient}.
|
|
||||||
* Returns safe defaults when no world is loaded.
|
|
||||||
*/
|
|
||||||
public class WorldFeatureImpl implements WorldFeature {
|
|
||||||
|
|
||||||
private final MinecraftClient mc;
|
|
||||||
|
|
||||||
public WorldFeatureImpl(MinecraftClient mc) {
|
|
||||||
this.mc = mc;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean hasWorld() {
|
|
||||||
return mc.world != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the total world age in ticks, or {@code 0} when no world
|
|
||||||
* is loaded. Used by {@link TpsFeature#onTimePacket} (which is
|
|
||||||
* called from the mixin and stores its own copy) and by
|
|
||||||
* {@link DayCounterFeature} for the day count.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public long getWorldAge() {
|
|
||||||
return hasWorld() ? mc.world.getTime() : 0L;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user