Compare commits
185
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a4a95e06ec | ||
|
|
05d72170e9 | ||
|
|
cf50897082 | ||
|
|
6ca2cfa0ae | ||
|
|
7e67b023fc | ||
|
|
9ff12d0fd4 | ||
|
|
51db629980 | ||
|
|
c3183767a3 | ||
|
|
4a84c16523 | ||
|
|
e7c8a579cd | ||
|
|
e257a8d436 | ||
|
|
fe85ed6a7c | ||
|
|
ed749bfc72 | ||
|
|
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 | ||
|
|
14c16d8fe0 | ||
|
|
e3d1e4a442 | ||
|
|
04605f6855 | ||
|
|
451ac986c0 | ||
|
|
80d478d2c9 | ||
|
|
79719262a2 | ||
|
|
806c4ab643 | ||
|
|
6ee8b64a38 | ||
|
|
a97e5703c9 | ||
|
|
39f140dae1 | ||
|
|
632591f8d8 | ||
|
|
66e994b5ab | ||
|
|
1d56094388 | ||
|
|
69f572288f | ||
|
|
1a5c7f2a2c | ||
|
|
68734f6ac5 | ||
|
|
333c408d1b | ||
|
|
3c57328b3c | ||
|
|
08178b60fe | ||
|
|
4342233876 | ||
|
|
89cb467016 | ||
|
|
9488a8647e | ||
|
|
fa1d1f312a | ||
|
|
b359224ecd | ||
|
|
a909aa880a | ||
|
|
8f8fec0816 | ||
|
|
c2d20492c3 | ||
|
|
4a7eade70d | ||
|
|
6466d8d8fd | ||
|
|
7c72da978d | ||
|
|
76b842e6a0 | ||
|
|
00cefe14f7 | ||
|
|
2a3034be2c | ||
|
|
910633819a | ||
|
|
daddd0a005 | ||
|
|
cdd6ae8ec9 | ||
|
|
65c9454370 | ||
|
|
8a1e9b49bc | ||
|
|
f288f9148c | ||
|
|
e165f6b65c | ||
|
|
019730c756 | ||
|
|
7fa951b7fa | ||
|
|
b39b19f107 | ||
|
|
88db7a8383 | ||
|
|
d782bccb5f | ||
|
|
84947d6b33 | ||
|
|
f79849d9fb | ||
|
|
cb38639e11 | ||
|
|
8f567c83c6 | ||
|
|
8876abde1c | ||
|
|
2d95bf9043 | ||
|
|
0475d8c43d | ||
|
|
491199cfea | ||
|
|
5aa40d2e61 | ||
|
|
c5b6a858e1 | ||
|
|
e28051a109 | ||
|
|
1471e53a91 | ||
|
|
6ae4f980e4 | ||
|
|
6fbacbcae6 | ||
|
|
e934f24d3e | ||
|
|
191bc1318e | ||
|
|
249219fe57 | ||
|
|
5a86d4cb4a | ||
|
|
75a5d7f52a | ||
|
|
c078637d3d | ||
|
|
6c41e774ff | ||
|
|
c630a4e41a | ||
|
|
33956ea550 | ||
|
|
2e45cc39f7 | ||
|
|
c8860ab837 | ||
|
|
64ab091807 | ||
|
|
243275b414 | ||
|
|
70d7953b45 | ||
|
|
812e7bf593 | ||
|
|
e5c2d8bd36 | ||
|
|
670961acce | ||
|
|
b46410c43b | ||
|
|
2cd7db9ade |
@@ -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
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
name: Release
|
||||||
|
|
||||||
|
# Publishing a release builds every Minecraft version and attaches the mod jars
|
||||||
|
# to it. mod_version in gradle.properties is the source of truth: the release
|
||||||
|
# tag must match it (a leading "v" is allowed), so bump mod_version in a PR
|
||||||
|
# before tagging the release.
|
||||||
|
#
|
||||||
|
# To (re)attach jars to an existing release, run this workflow manually with
|
||||||
|
# the release's tag.
|
||||||
|
on:
|
||||||
|
release:
|
||||||
|
types: [published]
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
tag:
|
||||||
|
description: Tag of the existing release to build and attach jars to
|
||||||
|
required: true
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
env:
|
||||||
|
TAG: ${{ github.event.release.tag_name || inputs.tag }}
|
||||||
|
# The public address. Uploads go through the Cloudflare tunnel, which caps
|
||||||
|
# request bodies at 100 MB, so each mod jar must stay under that.
|
||||||
|
GITEA_API: ${{ github.server_url }}/api/v1
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
ref: ${{ env.TAG }}
|
||||||
|
submodules: true
|
||||||
|
|
||||||
|
- name: Check tag matches mod_version
|
||||||
|
run: |
|
||||||
|
MOD_VERSION="$(sed -n 's/^mod_version=//p' gradle.properties | tr -d '[:space:]')"
|
||||||
|
if [ "${TAG#v}" != "$MOD_VERSION" ]; then
|
||||||
|
echo "::error::Release tag '$TAG' doesn't match mod_version '$MOD_VERSION' in gradle.properties. Bump mod_version to '${TAG#v}' on the release branch, or tag the release '$MOD_VERSION'." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "Building Saturn Client $MOD_VERSION"
|
||||||
|
|
||||||
|
# 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: Build all versions
|
||||||
|
run: |
|
||||||
|
chmod +x gradlew
|
||||||
|
# buildAll remaps every Minecraft version (two at a time, to stay within the
|
||||||
|
# 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
|
||||||
|
env:
|
||||||
|
TOKEN: ${{ secrets.GITEA_TOKEN }}
|
||||||
|
REPO: ${{ github.repository }}
|
||||||
|
run: |
|
||||||
|
shopt -s nullglob
|
||||||
|
jars=(build/allJars/*.jar)
|
||||||
|
if [ ${#jars[@]} -eq 0 ]; then
|
||||||
|
echo "No jars found in build/allJars" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Cloudflare in front of the server rejects uploads of 100 MB or more.
|
||||||
|
for jar in "${jars[@]}"; do
|
||||||
|
size=$(stat -c%s "$jar")
|
||||||
|
if [ "$size" -ge 100000000 ]; then
|
||||||
|
echo "::error::$(basename "$jar") is $((size / 1000000)) MB; uploads must be under 100 MB. Check what's taking space with: unzip -l \"$jar\" | sort -n" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# Look the release up by tag so manual runs work too.
|
||||||
|
RELEASE_ID="$(curl --fail-with-body -sS -H "Authorization: token $TOKEN" \
|
||||||
|
"$GITEA_API/repos/$REPO/releases/tags/$TAG" \
|
||||||
|
| grep -o '"id":[0-9]*' | head -n1 | cut -d: -f2)"
|
||||||
|
if [ -z "$RELEASE_ID" ]; then
|
||||||
|
echo "No release found for tag $TAG" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
for jar in "${jars[@]}"; do
|
||||||
|
echo "Uploading $(basename "$jar") to release $TAG ($RELEASE_ID)"
|
||||||
|
curl --fail-with-body -sS -X POST \
|
||||||
|
-H "Authorization: token $TOKEN" \
|
||||||
|
-F "attachment=@$jar" \
|
||||||
|
"$GITEA_API/repos/$REPO/releases/$RELEASE_ID/assets"
|
||||||
|
echo
|
||||||
|
done
|
||||||
+1
-1
@@ -1,3 +1,3 @@
|
|||||||
[submodule "common"]
|
[submodule "common"]
|
||||||
path = common
|
path = common
|
||||||
url = https://github.com/saturnclientmc/common.git
|
url = https://git.selimaj.dev/saturnclientmc/common.git
|
||||||
|
|||||||
+14
-5
@@ -1,8 +1,17 @@
|
|||||||
# Licenses
|
# Licenses
|
||||||
|
|
||||||
This project is under [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0), but a lot of code taken from other open source projects that are under other licenses that need to be mentioned due to legal or ethical reasons.
|
Saturn Client is source-available and open to contributions, but all rights are reserved. Some code is taken from other open source projects, which remain under their own licenses; they are mentioned here due to legal or ethical reasons.
|
||||||
|
|
||||||
| File | License |
|
| File | License |
|
||||||
| --------------------------------------------------------------------------------------- | ----------------------------------------------------------- |
|
| ---------------------------------------------------------------------------- | --------------------------------------- |
|
||||||
| [WhoAmI.java](src/main/java/org/saturnclient/saturnclient/mixin/WhoAmI.java) | [MPL-2.0](https://mozilla.org/MPL/2.0/) |
|
| [WhoAmI.java](src/main/java/org/saturnclient/saturnclient/mixin/WhoAmI.java) | [MPL-2.0](https://mozilla.org/MPL/2.0/) |
|
||||||
| [Cloaks.java](src/main/java/org/saturnclient/saturnclient/cosmetics/cloaks/Cloaks.java) | [GNU-GPL-3.0](https://www.gnu.org/licenses/gpl-3.0.en.html) |
|
| [GifDecoder.java](common/src/main/java/org/saturnclient/cosmetics/utils/GifDecoder.java) (Copyright 2014 Dhyan Blum) | [Apache-2.0](licenses/Apache-2.0.txt) |
|
||||||
|
| Inter font (`inter.ttf`, `inter_bold.ttf`), Copyright (c) 2016 The Inter Project Authors | [SIL OFL 1.1](licenses/OFL-1.1-Inter.txt) |
|
||||||
|
|
||||||
|
The license texts above are also bundled in the mod jar under `licenses/`.
|
||||||
|
|
||||||
|
## Credits for assets and ideas
|
||||||
|
|
||||||
|
- **Emotes:** modified versions of community emotes made for [Emotecraft](https://emotecraft.org), adapted to Saturn's emote library. Each emote's original author is kept in its `author` field.
|
||||||
|
- **Black hole cloaks:** variations of NASA imagery from Wikimedia Commons.
|
||||||
|
- **Fullbright:** the approach is adapted from [Gamma Utils](https://github.com/Sjouwer/gamma-utils) (LGPL-3.0). Only the idea was used; no code was copied.
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
<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>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<b>A free and open-source Minecraft client.</b><br/>
|
<b>A free, source-available Minecraft client.</b><br/>
|
||||||
Built with performance, customization, and community in mind.
|
Built with performance, customization, and community in mind.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
@@ -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, source-available 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
|
- Source-available and open to community contributions
|
||||||
|
|
||||||
---
|
## 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,29 @@ 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:**
|
- **Emotes:** modified versions of community [Emotecraft](https://emotecraft.org) emotes, credited to their authors
|
||||||
https://www.planetminecraft.com/texture-pack/minecraft-cavesand-cliffs-part-ii-main-menu-panorama/
|
- **Black hole cloaks:** variations of NASA imagery from Wikimedia Commons
|
||||||
|
- **Menu panorama:** captured on the [Mountain Village!!](https://www.planetminecraft.com/project/mountain-village-5031630/) map with the [Complementary Reimagined](https://modrinth.com/shader/complementary-reimagined) shader
|
||||||
|
- **Cape creation tool:** [ko-fi.com/s/ce84afe094](https://ko-fi.com/s/ce84afe094)
|
||||||
|
|
||||||
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
|
Copyright © Saturn Client team. All rights reserved.
|
||||||
|
|
||||||
Saturn Client is free and open-source using **Apache-2.0**
|
The source code is publicly available to read, and contributions are welcome: you are free to fork the repository and open issues and pull requests to contribute. Beyond that, this software may not be copied, modified, or redistributed without explicit written permission from the copyright holders. By submitting a contribution, you agree that it becomes part of Saturn Client under these same terms. Third-party components keep their own licenses; see [NOTICES.md](NOTICES.md).
|
||||||
|
|
||||||
---
|
**Saturn Client is not an official Minecraft product. It is not approved by or associated with Mojang or Microsoft.**
|
||||||
|
|
||||||
<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://jitpack.io" }
|
|
||||||
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,82 @@
|
|||||||
|
// 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 {
|
||||||
|
from(rootProject.file("licenses")) {
|
||||||
|
into("licenses")
|
||||||
|
}
|
||||||
|
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: f07750cdcb...19bf0bcc81
@@ -0,0 +1,5 @@
|
|||||||
|
Name: Saturn Client
|
||||||
|
Projection: Unset
|
||||||
|
Rotation-Mode: Focal Point
|
||||||
|
Camera-Positon: [-42.93, 30.42, 37.82]
|
||||||
|
Focal-Point: [0, 8, 0]
|
||||||
+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.0-beta2
|
mod_version=0.1.6-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
|
|
||||||
|
|||||||
@@ -0,0 +1,92 @@
|
|||||||
|
Copyright (c) 2016 The Inter Project Authors (https://github.com/rsms/inter)
|
||||||
|
|
||||||
|
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||||
|
This license is copied below, and is also available with a FAQ at:
|
||||||
|
http://scripts.sil.org/OFL
|
||||||
|
|
||||||
|
-----------------------------------------------------------
|
||||||
|
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||||
|
-----------------------------------------------------------
|
||||||
|
|
||||||
|
PREAMBLE
|
||||||
|
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||||
|
development of collaborative font projects, to support the font creation
|
||||||
|
efforts of academic and linguistic communities, and to provide a free and
|
||||||
|
open framework in which fonts may be shared and improved in partnership
|
||||||
|
with others.
|
||||||
|
|
||||||
|
The OFL allows the licensed fonts to be used, studied, modified and
|
||||||
|
redistributed freely as long as they are not sold by themselves. The
|
||||||
|
fonts, including any derivative works, can be bundled, embedded,
|
||||||
|
redistributed and/or sold with any software provided that any reserved
|
||||||
|
names are not used by derivative works. The fonts and derivatives,
|
||||||
|
however, cannot be released under any other type of license. The
|
||||||
|
requirement for fonts to remain under this license does not apply
|
||||||
|
to any document created using the fonts or their derivatives.
|
||||||
|
|
||||||
|
DEFINITIONS
|
||||||
|
"Font Software" refers to the set of files released by the Copyright
|
||||||
|
Holder(s) under this license and clearly marked as such. This may
|
||||||
|
include source files, build scripts and documentation.
|
||||||
|
|
||||||
|
"Reserved Font Name" refers to any names specified as such after the
|
||||||
|
copyright statement(s).
|
||||||
|
|
||||||
|
"Original Version" refers to the collection of Font Software components as
|
||||||
|
distributed by the Copyright Holder(s).
|
||||||
|
|
||||||
|
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||||
|
or substituting -- in part or in whole -- any of the components of the
|
||||||
|
Original Version, by changing formats or by porting the Font Software to a
|
||||||
|
new environment.
|
||||||
|
|
||||||
|
"Author" refers to any designer, engineer, programmer, technical
|
||||||
|
writer or other person who contributed to the Font Software.
|
||||||
|
|
||||||
|
PERMISSION AND CONDITIONS
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||||
|
redistribute, and sell modified and unmodified copies of the Font
|
||||||
|
Software, subject to the following conditions:
|
||||||
|
|
||||||
|
1) Neither the Font Software nor any of its individual components,
|
||||||
|
in Original or Modified Versions, may be sold by itself.
|
||||||
|
|
||||||
|
2) Original or Modified Versions of the Font Software may be bundled,
|
||||||
|
redistributed and/or sold with any software, provided that each copy
|
||||||
|
contains the above copyright notice and this license. These can be
|
||||||
|
included either as stand-alone text files, human-readable headers or
|
||||||
|
in the appropriate machine-readable metadata fields within text or
|
||||||
|
binary files as long as those fields can be easily viewed by the user.
|
||||||
|
|
||||||
|
3) No Modified Version of the Font Software may use the Reserved Font
|
||||||
|
Name(s) unless explicit written permission is granted by the corresponding
|
||||||
|
Copyright Holder. This restriction only applies to the primary font name as
|
||||||
|
presented to the users.
|
||||||
|
|
||||||
|
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||||
|
Software shall not be used to promote, endorse or advertise any
|
||||||
|
Modified Version, except to acknowledge the contribution(s) of the
|
||||||
|
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||||
|
permission.
|
||||||
|
|
||||||
|
5) The Font Software, modified or unmodified, in part or in whole,
|
||||||
|
must be distributed entirely under this license, and must not be
|
||||||
|
distributed under any other license. The requirement for fonts to
|
||||||
|
remain under this license does not apply to any document created
|
||||||
|
using the Font Software.
|
||||||
|
|
||||||
|
TERMINATION
|
||||||
|
This license becomes null and void if any of the above conditions are
|
||||||
|
not met.
|
||||||
|
|
||||||
|
DISCLAIMER
|
||||||
|
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||||
|
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||||
|
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||||
|
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||||
|
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||||
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"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,548 @@
|
|||||||
|
package org.saturnclient.impl.cosmetics;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.WeakHashMap;
|
||||||
|
|
||||||
|
import org.joml.Matrix3f;
|
||||||
|
import org.joml.Matrix4f;
|
||||||
|
import org.joml.Quaternionf;
|
||||||
|
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.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.saturnclient.SaturnRenderState;
|
||||||
|
|
||||||
|
import net.minecraft.client.model.geom.ModelPart;
|
||||||
|
import net.minecraft.client.renderer.texture.OverlayTexture;
|
||||||
|
import com.mojang.blaze3d.vertex.VertexConsumer;
|
||||||
|
//? if >=1.21.9 {
|
||||||
|
import net.minecraft.client.renderer.SubmitNodeCollector;
|
||||||
|
//?} else
|
||||||
|
//import net.minecraft.client.renderer.MultiBufferSource;
|
||||||
|
import net.minecraft.client.resources.model.EquipmentClientInfo;
|
||||||
|
import net.minecraft.client.resources.model.EquipmentAssetManager;
|
||||||
|
import net.minecraft.client.resources.model.EquipmentClientInfo.LayerType;
|
||||||
|
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;
|
||||||
|
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
|
||||||
|
* - **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 RenderLayer<AvatarRenderState, PlayerModel> {
|
||||||
|
private final EquipmentAssetManager 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(RenderLayerParent<AvatarRenderState, PlayerModel> context,
|
||||||
|
EquipmentAssetManager equipmentModelLoader) {
|
||||||
|
super(context);
|
||||||
|
this.equipmentModelLoader = equipmentModelLoader;
|
||||||
|
Arrays.fill(segmentValues, 0.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean hasCustomModelForLayer(ItemStack stack, EquipmentClientInfo.LayerType layerType) {
|
||||||
|
Equippable equippableComponent = (Equippable) stack
|
||||||
|
.get(DataComponents.EQUIPPABLE);
|
||||||
|
if (equippableComponent != null && !equippableComponent.assetId().isEmpty()) {
|
||||||
|
EquipmentClientInfo equipmentModel = this.equipmentModelLoader
|
||||||
|
.get((ResourceKey<EquipmentAsset>) equippableComponent.assetId().get());
|
||||||
|
return !equipmentModel.getLayers(layerType).isEmpty();
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void vertex(VertexConsumer vc, Matrix4f posMat, Matrix3f normalMat,
|
||||||
|
net.minecraft.world.phys.Vec3 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.addVertex(
|
||||||
|
p.x(), p.y(), p.z(),
|
||||||
|
color,
|
||||||
|
u, v,
|
||||||
|
overlay, light,
|
||||||
|
n.x(), n.y(), n.z());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void renderCapeQuad(VertexConsumer vertexConsumer, PoseStack.Pose entry,
|
||||||
|
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 normalX, float normalY, float normalZ, boolean flipWinding) {
|
||||||
|
// Extract matrix (you must transform positions manually now)
|
||||||
|
Matrix4f pos = entry.pose();
|
||||||
|
Matrix3f normalMat = entry.normal();
|
||||||
|
|
||||||
|
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(PoseStack.Pose entry, VertexConsumer vertexConsumer,
|
||||||
|
AvatarRenderState 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
|
||||||
|
// =============================
|
||||||
|
|
||||||
|
net.minecraft.world.phys.Vec3[][] inner = new net.minecraft.world.phys.Vec3[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++) {
|
||||||
|
|
||||||
|
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 net.minecraft.world.phys.Vec3(xPos, y - curveY, z - curveZ);
|
||||||
|
outer[x][i] = new net.minecraft.world.phys.Vec3(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);
|
||||||
|
|
||||||
|
net.minecraft.world.phys.Vec3 innerTL = inner[x + 1][i];
|
||||||
|
net.minecraft.world.phys.Vec3 innerTR = inner[x][i];
|
||||||
|
net.minecraft.world.phys.Vec3 innerBL = inner[x + 1][i + 1];
|
||||||
|
net.minecraft.world.phys.Vec3 innerBR = inner[x][i + 1];
|
||||||
|
|
||||||
|
net.minecraft.world.phys.Vec3 outerTL = outer[x + 1][i];
|
||||||
|
net.minecraft.world.phys.Vec3 outerTR = outer[x][i];
|
||||||
|
net.minecraft.world.phys.Vec3 outerBL = outer[x + 1][i + 1];
|
||||||
|
net.minecraft.world.phys.Vec3 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++) {
|
||||||
|
|
||||||
|
net.minecraft.world.phys.Vec3 innerTop = inner[H_PARTS][i];
|
||||||
|
net.minecraft.world.phys.Vec3 innerBot = inner[H_PARTS][i + 1];
|
||||||
|
net.minecraft.world.phys.Vec3 outerTop = outer[H_PARTS][i];
|
||||||
|
net.minecraft.world.phys.Vec3 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++) {
|
||||||
|
|
||||||
|
net.minecraft.world.phys.Vec3 innerTop = inner[0][i];
|
||||||
|
net.minecraft.world.phys.Vec3 innerBot = inner[0][i + 1];
|
||||||
|
net.minecraft.world.phys.Vec3 outerTop = outer[0][i];
|
||||||
|
net.minecraft.world.phys.Vec3 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++) {
|
||||||
|
|
||||||
|
net.minecraft.world.phys.Vec3 outerL = outer[x][0];
|
||||||
|
net.minecraft.world.phys.Vec3 outerR = outer[x + 1][0];
|
||||||
|
net.minecraft.world.phys.Vec3 innerR = inner[x + 1][0];
|
||||||
|
net.minecraft.world.phys.Vec3 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++) {
|
||||||
|
|
||||||
|
net.minecraft.world.phys.Vec3 innerL = inner[x][PARTS];
|
||||||
|
net.minecraft.world.phys.Vec3 innerR = inner[x + 1][PARTS];
|
||||||
|
net.minecraft.world.phys.Vec3 outerR = outer[x + 1][PARTS];
|
||||||
|
net.minecraft.world.phys.Vec3 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 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
|
||||||
|
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
|
||||||
|
//? 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 (state.isInvisible || !state.showCape
|
||||||
|
//? if >=1.21.9 {
|
||||||
|
|| state.skin.cape() != null) {
|
||||||
|
//?} else
|
||||||
|
//|| state.skin.capeTexture() != null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//? if >=1.21.8 {
|
||||||
|
SaturnPlayer player = state.getData(SaturnRenderState.saturnDataKey);
|
||||||
|
//?} else
|
||||||
|
//SaturnPlayer player = SaturnPlayer.get(state.name);
|
||||||
|
|
||||||
|
if (player == null || playingTorsoBend(player)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
IdentifierRef customCape = Cloaks.getCurrentCloakTexture(player.cloak);
|
||||||
|
if (customCape == null
|
||||||
|
|| this.hasCustomModelForLayer(state.chestEquipment, 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.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);
|
||||||
|
|
||||||
|
if (this.hasCustomModelForLayer(state.chestEquipment, LayerType.HUMANOID)) {
|
||||||
|
matrices.translate(0.0F, -0.053125F, 0.06875F);
|
||||||
|
}
|
||||||
|
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
if (now - lastUpdate >= 20) {
|
||||||
|
float velX = Math.min(0.8f, state.capeLean / 108.0f);
|
||||||
|
float rawVelY = state.capeFlap;
|
||||||
|
float velY = (rawVelY > 3.4f ? rawVelY : rawVelY < -3.4f ? rawVelY : 0.0f) / 16;
|
||||||
|
|
||||||
|
float value = Math.max(0.0f, Math.min(2.0f, state.isVisuallySwimming ? 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 = Mth.clamp(state.capeLean / 150.0f, -1.0f, 1.0f);
|
||||||
|
|
||||||
|
//? if >=1.21.9 {
|
||||||
|
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.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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,131 @@
|
|||||||
|
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.Minecraft;
|
||||||
|
//? if >=1.21.11 {
|
||||||
|
import net.minecraft.client.renderer.rendertype.RenderType;
|
||||||
|
//?} else
|
||||||
|
//import net.minecraft.client.renderer.RenderType;
|
||||||
|
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 static Obj loadObj(Identifier objId) throws IOException {
|
||||||
|
try (InputStream is = Minecraft.getInstance().getResourceManager()
|
||||||
|
.getResource(objId).get().open()) {
|
||||||
|
return ObjReader.read(is);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void renderObj(Obj obj,
|
||||||
|
Map<String, Mtl> mtlMap,
|
||||||
|
PoseStack matrices,
|
||||||
|
//? if >=1.21.9 {
|
||||||
|
SubmitNodeCollector output,
|
||||||
|
//?} else
|
||||||
|
//MultiBufferSource output,
|
||||||
|
int light,
|
||||||
|
int overlay) {
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
IdentifierRef texture = IdentifierRef.ofVanilla("textures/misc/white.png");
|
||||||
|
|
||||||
|
if (mtl != null) {
|
||||||
|
String mapKd = mtl.getMapKd();
|
||||||
|
if (mapKd != null && !mapKd.startsWith("#")) {
|
||||||
|
texture = IdentifierRef.of(mapKd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderType layer = ShaderUtils.getRenderLayer(texture);
|
||||||
|
|
||||||
|
final float fr = r, fg = g, fb = b;
|
||||||
|
final Obj finalObj = groupObj;
|
||||||
|
|
||||||
|
//? 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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++) {
|
||||||
|
ObjFace face = obj.getFace(f);
|
||||||
|
int verts = face.getNumVertices();
|
||||||
|
|
||||||
|
for (int i = verts - 1; i >= 0; i--) {
|
||||||
|
writeVertex(obj, face, i, consumer, entry, light, overlay, r, g, b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void writeVertex(Obj obj, ObjFace face, int index, VertexConsumer consumer,
|
||||||
|
PoseStack.Pose 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.addVertex(matrix, x, y, z)
|
||||||
|
.setColor(r, g, b, 1f)
|
||||||
|
.setUv(u, v)
|
||||||
|
.setOverlay(overlay)
|
||||||
|
.setLight(light)
|
||||||
|
.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
-4
@@ -1,10 +1,9 @@
|
|||||||
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;
|
||||||
import org.saturnclient.impl.features.mixins.misc.NametagsMixin;
|
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
import org.spongepowered.asm.mixin.Unique;
|
import org.spongepowered.asm.mixin.Unique;
|
||||||
|
|
||||||
@@ -19,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 {}
|
||||||
+51
-39
@@ -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;
|
||||||
|
|
||||||
return entry.getProfile().getName();
|
//? if >=1.21.9 {
|
||||||
|
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;
|
||||||
|
|
||||||
return entry.getProfile().getId();
|
//? if >=1.21.9 {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
+4
-12
@@ -3,12 +3,10 @@ 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;
|
||||||
import org.saturnclient.cosmetics.Hats;
|
|
||||||
import org.saturnclient.cosmetics.Cloaks;
|
|
||||||
import org.saturnclient.common.provider.Providers;
|
import org.saturnclient.common.provider.Providers;
|
||||||
import org.saturnclient.impl.provider.GLFWProviderImpl;
|
import org.saturnclient.impl.provider.GLFWProviderImpl;
|
||||||
import org.saturnclient.impl.provider.FeatureProviderFabric;
|
import org.saturnclient.impl.provider.FeatureProviderFabric;
|
||||||
@@ -31,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();
|
||||||
@@ -61,12 +59,6 @@ public class SaturnClient implements ModInitializer {
|
|||||||
|
|
||||||
KeyInputHandler.register();
|
KeyInputHandler.register();
|
||||||
Emotes.initialize();
|
Emotes.initialize();
|
||||||
if (ServiceClient.authenticate()) {
|
ServiceClient.initialize();
|
||||||
Cloaks.initialize();
|
|
||||||
Hats.initialize();
|
|
||||||
LOGGER.info(MOD_ID + " initialization complete");
|
|
||||||
} else {
|
|
||||||
LOGGER.error("Failed to authenticate with the server");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
package org.saturnclient.saturnclient.mixin;
|
||||||
|
|
||||||
|
import net.minecraft.client.multiplayer.PlayerInfo;
|
||||||
|
import net.minecraft.world.scores.PlayerTeam;
|
||||||
|
import net.minecraft.network.chat.Component;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import org.saturnclient.client.player.Roles;
|
||||||
|
import org.saturnclient.client.player.SaturnPlayer;
|
||||||
|
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;
|
||||||
|
|
||||||
|
import com.mojang.authlib.GameProfile;
|
||||||
|
|
||||||
|
@Mixin(PlayerInfo.class)
|
||||||
|
public abstract class TabListMixin {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the Saturn Client icon to the player's name if they are online with
|
||||||
|
* Saturn Client, keeping any name, rank prefix or team formatting the server
|
||||||
|
* set.
|
||||||
|
*/
|
||||||
|
@Inject(method = "getTabListDisplayName", at = @At("RETURN"), cancellable = true)
|
||||||
|
private void saturn$addSaturnIcon(CallbackInfoReturnable<Component> cir) {
|
||||||
|
PlayerInfo entry = (PlayerInfo) (Object) this;
|
||||||
|
GameProfile profile = entry.getProfile();
|
||||||
|
//? if >=1.21.9 {
|
||||||
|
String name = profile.name();
|
||||||
|
UUID uuid = profile.id();
|
||||||
|
//?} else {
|
||||||
|
/*String name = profile.getName();
|
||||||
|
UUID uuid = profile.getId();
|
||||||
|
*///?}
|
||||||
|
|
||||||
|
if (SaturnPlayer.get(name, uuid) == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Component displayName = cir.getReturnValue();
|
||||||
|
|
||||||
|
// With no server-set name, vanilla shows the team-decorated profile name.
|
||||||
|
if (displayName == null) {
|
||||||
|
displayName = PlayerTeam.formatNameForTeam(entry.getTeam(), Component.literal(name));
|
||||||
|
}
|
||||||
|
|
||||||
|
cir.setReturnValue(Component.empty()
|
||||||
|
.append(Component.literal(Roles.getSaturnIndicator())
|
||||||
|
.withStyle(style -> style.withColor(Roles.getIconColor(uuid))))
|
||||||
|
.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;
|
||||||
|
}
|
||||||
+3
-3
@@ -3,13 +3,13 @@
|
|||||||
"id": "saturnclient",
|
"id": "saturnclient",
|
||||||
"version": "${version}",
|
"version": "${version}",
|
||||||
"name": "saturnclient",
|
"name": "saturnclient",
|
||||||
"description": "The ultimate open source client",
|
"description": "The ultimate source-available Minecraft client",
|
||||||
"authors": ["Saturn", "ORUS"],
|
"authors": ["Saturn", "ORUS"],
|
||||||
"contact": {
|
"contact": {
|
||||||
"website": "https://selimaj.dev",
|
"website": "https://selimaj.dev",
|
||||||
"sources": "https://github.com/saturnclientmc/saturnclient"
|
"sources": "https://github.com/saturnclientmc/saturnclient"
|
||||||
},
|
},
|
||||||
"license": "Apache-2.0",
|
"license": "All Rights Reserved",
|
||||||
"icon": "assets/saturnclient/icon.png",
|
"icon": "assets/saturnclient/icon.png",
|
||||||
"environment": "client",
|
"environment": "client",
|
||||||
"entrypoints": {
|
"entrypoints": {
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
],
|
],
|
||||||
"depends": {
|
"depends": {
|
||||||
"fabricloader": ">=${loader_version}",
|
"fabricloader": ">=${loader_version}",
|
||||||
"fabric": "*",
|
"fabric-api": "*",
|
||||||
"minecraft": "${minecraft_version}"
|
"minecraft": "${minecraft_version}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+1
-2
@@ -2,14 +2,13 @@
|
|||||||
"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",
|
||||||
"render.LivingEntityRendererMixin",
|
"render.LivingEntityRendererMixin",
|
||||||
"render.LivingEntityRenderStateMixin",
|
"render.LivingEntityRenderStateMixin",
|
||||||
"render.BackgroundRendererMixin",
|
"render.BackgroundRendererMixin",
|
||||||
"misc.NametagsMixin",
|
|
||||||
"render.RenderMixin",
|
"render.RenderMixin",
|
||||||
"world.TpsMixin",
|
"world.TpsMixin",
|
||||||
"entity.CameraMixin",
|
"entity.CameraMixin",
|
||||||
+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
-3
@@ -2,15 +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",
|
||||||
"NameTagMixin",
|
|
||||||
"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")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
# Done to increase the memory available to gradle.
|
||||||
|
org.gradle.jvmargs=-Xmx1G
|
||||||
|
org.gradle.parallel=true
|
||||||
|
|
||||||
|
# IntelliJ IDEA is not yet fully compatible with configuration cache, see: https://github.com/FabricMC/fabric-loom/issues/1349
|
||||||
|
org.gradle.configuration-cache=false
|
||||||
|
|
||||||
|
# Fabric Properties
|
||||||
|
# check these on https://fabricmc.net/develop
|
||||||
|
minecraft_version=1.21.10
|
||||||
|
loader_version=0.19.5
|
||||||
|
|
||||||
|
fabric_api_version=0.138.4+1.21.10
|
||||||
|
|
||||||
|
# Mod Properties
|
||||||
|
maven_group=org.saturnclient
|
||||||
|
archives_base_name=saturn-client
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
# Done to increase the memory available to gradle.
|
||||||
|
org.gradle.jvmargs=-Xmx1G
|
||||||
|
org.gradle.parallel=true
|
||||||
|
|
||||||
|
# IntelliJ IDEA is not yet fully compatible with configuration cache, see: https://github.com/FabricMC/fabric-loom/issues/1349
|
||||||
|
org.gradle.configuration-cache=false
|
||||||
|
|
||||||
|
# Fabric Properties
|
||||||
|
# check these on https://fabricmc.net/develop
|
||||||
|
minecraft_version=1.21.11
|
||||||
|
loader_version=0.19.5
|
||||||
|
|
||||||
|
fabric_api_version=0.141.3+1.21.11
|
||||||
|
|
||||||
|
# Mod Properties
|
||||||
|
maven_group=org.saturnclient
|
||||||
|
archives_base_name=saturn-client
|
||||||
@@ -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 = "JitPack"; url = 'https://jitpack.io' }
|
|
||||||
}
|
|
||||||
|
|
||||||
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("com.github.selimaj-dev:session-java:0.1.6")
|
|
||||||
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,9 +8,7 @@ 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.4
|
minecraft_version=1.21.4
|
||||||
yarn_mappings=1.21.4+build.8
|
loader_version=0.19.5
|
||||||
loader_version=0.18.4
|
|
||||||
loom_version=1.15-SNAPSHOT
|
|
||||||
|
|
||||||
# Mod Properties
|
# Mod Properties
|
||||||
maven_group=org.saturnclient
|
maven_group=org.saturnclient
|
||||||
@@ -18,5 +16,3 @@ archives_base_name=saturn-client
|
|||||||
|
|
||||||
# Dependencies
|
# Dependencies
|
||||||
fabric_api_version=0.119.4+1.21.4
|
fabric_api_version=0.119.4+1.21.4
|
||||||
|
|
||||||
player_anim = 2.0.3+1.21.4
|
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
pluginManagement {
|
|
||||||
repositories {
|
|
||||||
maven {
|
|
||||||
name = 'Fabric'
|
|
||||||
url = 'https://maven.fabricmc.net/'
|
|
||||||
}
|
|
||||||
mavenCentral()
|
|
||||||
gradlePluginPortal()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-335
@@ -1,335 +0,0 @@
|
|||||||
package org.saturnclient.impl.cosmetics;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
|
|
||||||
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 net.minecraft.client.render.OverlayTexture;
|
|
||||||
import net.minecraft.client.render.VertexConsumer;
|
|
||||||
import net.minecraft.client.render.VertexConsumerProvider;
|
|
||||||
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.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 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 renderCapeQuad(VertexConsumer vertexConsumer, MatrixStack matrixStack,
|
|
||||||
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) {
|
|
||||||
MatrixStack.Entry entry = matrixStack.peek();
|
|
||||||
|
|
||||||
if (!flipWinding) {
|
|
||||||
// CCW
|
|
||||||
vertexConsumer.vertex(entry.getPositionMatrix(), (float) bottomLeft.x, (float) bottomLeft.y,
|
|
||||||
(float) bottomLeft.z)
|
|
||||||
.color(255, 255, 255, 255).texture(u1, v2).overlay(overlay).light(light)
|
|
||||||
.normal(normalX, normalY, normalZ);
|
|
||||||
vertexConsumer.vertex(entry.getPositionMatrix(), (float) bottomRight.x, (float) bottomRight.y,
|
|
||||||
(float) bottomRight.z)
|
|
||||||
.color(255, 255, 255, 255).texture(u2, v2).overlay(overlay).light(light)
|
|
||||||
.normal(normalX, normalY, normalZ);
|
|
||||||
vertexConsumer.vertex(entry.getPositionMatrix(), (float) topRight.x, (float) topRight.y,
|
|
||||||
(float) topRight.z)
|
|
||||||
.color(255, 255, 255, 255).texture(u2, v1).overlay(overlay).light(light)
|
|
||||||
.normal(normalX, normalY, normalZ);
|
|
||||||
vertexConsumer.vertex(entry.getPositionMatrix(), (float) topLeft.x, (float) topLeft.y,
|
|
||||||
(float) topLeft.z)
|
|
||||||
.color(255, 255, 255, 255).texture(u1, v1).overlay(overlay).light(light)
|
|
||||||
.normal(normalX, normalY, normalZ);
|
|
||||||
} else {
|
|
||||||
// CW
|
|
||||||
vertexConsumer.vertex(entry.getPositionMatrix(), (float) topLeft.x, (float) topLeft.y,
|
|
||||||
(float) topLeft.z)
|
|
||||||
.color(255, 255, 255, 255).texture(u1, v1).overlay(overlay).light(light)
|
|
||||||
.normal(normalX, normalY, normalZ);
|
|
||||||
vertexConsumer.vertex(entry.getPositionMatrix(), (float) topRight.x, (float) topRight.y,
|
|
||||||
(float) topRight.z)
|
|
||||||
.color(255, 255, 255, 255).texture(u2, v1).overlay(overlay).light(light)
|
|
||||||
.normal(normalX, normalY, normalZ);
|
|
||||||
vertexConsumer.vertex(entry.getPositionMatrix(), (float) bottomRight.x, (float) bottomRight.y,
|
|
||||||
(float) bottomRight.z)
|
|
||||||
.color(255, 255, 255, 255).texture(u2, v2).overlay(overlay).light(light)
|
|
||||||
.normal(normalX, normalY, normalZ);
|
|
||||||
vertexConsumer.vertex(entry.getPositionMatrix(), (float) bottomLeft.x, (float) bottomLeft.y,
|
|
||||||
(float) bottomLeft.z)
|
|
||||||
.color(255, 255, 255, 255).texture(u1, v2).overlay(overlay).light(light)
|
|
||||||
.normal(normalX, normalY, normalZ);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void renderCape(MatrixStack matrixStack, 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;
|
|
||||||
float capePartHeight = capeHeight / PARTS;
|
|
||||||
|
|
||||||
float x1 = -capeWidth / 2.0f;
|
|
||||||
float x2 = capeWidth / 2.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();
|
|
||||||
|
|
||||||
float curY = 0.0f;
|
|
||||||
float curZ = 0.0f;
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
// 1. Thickness at the START of this segment (Must match PREVIOUS segment's end)
|
|
||||||
float prevAngle = (i > 0) ? (2.0f - segmentValues[i - 1]) * ((float) Math.PI / 2f) : angle;
|
|
||||||
float startAvgAngle = (angle + prevAngle) / 2.0f;
|
|
||||||
float thickYStart = (float) Math.sin(startAvgAngle) * capeDepth;
|
|
||||||
float thickZStart = (float) Math.cos(startAvgAngle) * capeDepth;
|
|
||||||
|
|
||||||
// 2. Thickness at the END of this segment (Must match NEXT segment's start)
|
|
||||||
float nextAngle = (i < PARTS - 1) ? (2.0f - segmentValues[i + 1]) * ((float) Math.PI / 2f) : angle;
|
|
||||||
float endAvgAngle = (angle + nextAngle) / 2.0f;
|
|
||||||
float thickYEnd = (float) Math.sin(endAvgAngle) * capeDepth;
|
|
||||||
float thickZEnd = (float) Math.cos(endAvgAngle) * capeDepth;
|
|
||||||
|
|
||||||
float nextY = curY + dirY * capePartHeight;
|
|
||||||
float nextZ = curZ + dirZ * capePartHeight;
|
|
||||||
|
|
||||||
// Inner Vertices (The spine of the cape)
|
|
||||||
Vec3d innerTopLeft = new Vec3d(x2, curY, curZ);
|
|
||||||
Vec3d innerTopRight = new Vec3d(x1, curY, curZ);
|
|
||||||
Vec3d innerBotLeft = new Vec3d(x2, nextY, nextZ);
|
|
||||||
Vec3d innerBotRight = new Vec3d(x1, nextY, nextZ);
|
|
||||||
|
|
||||||
// Outer Vertices (Offset by the specific joint thickness)
|
|
||||||
Vec3d outerTopLeft = new Vec3d(x2, curY - thickYStart, curZ - thickZStart);
|
|
||||||
Vec3d outerTopRight = new Vec3d(x1, curY - thickYStart, curZ - thickZStart);
|
|
||||||
Vec3d outerBotLeft = new Vec3d(x2, nextY - thickYEnd, nextZ - thickZEnd);
|
|
||||||
Vec3d outerBotRight = new Vec3d(x1, nextY - thickYEnd, nextZ - thickZEnd);
|
|
||||||
|
|
||||||
// FRONT (facing camera)
|
|
||||||
this.renderCapeQuad(vertexConsumer, matrixStack,
|
|
||||||
outerBotRight, outerBotLeft, outerTopLeft, outerTopRight,
|
|
||||||
frontU2, frontV1 + (frontPartV * i), frontU1, frontV1 + (frontPartV * (i + 1)),
|
|
||||||
light, overlay, 0, 0, 1, false);
|
|
||||||
|
|
||||||
// BACK (Facing player)
|
|
||||||
this.renderCapeQuad(vertexConsumer, matrixStack,
|
|
||||||
innerBotLeft, innerBotRight, innerTopRight, innerTopLeft,
|
|
||||||
backU2, backV1 + (backPartV * i), backU1, backV1 + (backPartV * (i + 1)),
|
|
||||||
light, overlay, 0, 0, -1, false);
|
|
||||||
|
|
||||||
// LEFT EDGE
|
|
||||||
this.renderCapeQuad(vertexConsumer, matrixStack,
|
|
||||||
innerBotLeft, outerBotLeft, outerTopLeft, innerTopLeft,
|
|
||||||
leftU1, leftV1 + (leftPartV * i), leftU2, leftV1 + (leftPartV * (i + 1)),
|
|
||||||
light, overlay, 1, 0, 0, false);
|
|
||||||
|
|
||||||
// RIGHT EDGE
|
|
||||||
this.renderCapeQuad(vertexConsumer, matrixStack,
|
|
||||||
outerBotRight, innerBotRight, innerTopRight, outerTopRight,
|
|
||||||
rightU1, rightV1 + (rightPartV * i), rightU2, rightV1 + (rightPartV * (i + 1)),
|
|
||||||
light, overlay, -1, 0, 0, false);
|
|
||||||
|
|
||||||
// TOP FACE (Only on first segment)
|
|
||||||
if (i == 0) {
|
|
||||||
this.renderCapeQuad(vertexConsumer, matrixStack,
|
|
||||||
outerTopLeft, outerTopRight, innerTopRight, innerTopLeft,
|
|
||||||
topU1, topV1, topU2, topV2, light, overlay, 0, 1, 0, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
// BOTTOM FACE (Only on last segment)
|
|
||||||
if (i == PARTS - 1) {
|
|
||||||
this.renderCapeQuad(vertexConsumer, matrixStack,
|
|
||||||
innerBotLeft, innerBotRight, outerBotRight, outerBotLeft,
|
|
||||||
bottomU1, bottomV1, bottomU2, bottomV2, light, overlay, 0, -1, 0, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
curY = nextY;
|
|
||||||
curZ = nextZ;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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;
|
|
||||||
|
|
||||||
public void render(MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, int light,
|
|
||||||
PlayerEntityRenderState playerEntityRenderState, float f, float g) {
|
|
||||||
if (playerEntityRenderState.invisible || !playerEntityRenderState.capeVisible
|
|
||||||
|| playerEntityRenderState.skinTextures.capeTexture() != null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
SaturnPlayer player = SaturnPlayer.get(playerEntityRenderState.name);
|
|
||||||
|
|
||||||
if (player == null || player.cloak.isEmpty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
IdentifierRef customCape = Cloaks.getCurrentCloakTexture(player.cloak);
|
|
||||||
if (customCape == null
|
|
||||||
|| this.hasCustomModelForLayer(playerEntityRenderState.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);
|
|
||||||
|
|
||||||
VertexConsumer vertexConsumer = vertexConsumerProvider
|
|
||||||
.getBuffer(ShaderUtils.getRenderLayer(customCape));
|
|
||||||
|
|
||||||
matrixStack.push();
|
|
||||||
|
|
||||||
matrixStack.translate(0.0f, 0.0f, 0.12f);
|
|
||||||
|
|
||||||
if (this.hasCustomModelForLayer(playerEntityRenderState.equippedChestStack, LayerType.HUMANOID)) {
|
|
||||||
matrixStack.translate(0.0F, -0.053125F, 0.06875F);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (playerEntityRenderState.isInSneakingPose) {
|
|
||||||
matrixStack.translate(0, 0.16f, 0.0f);
|
|
||||||
matrixStack.multiply(RotationAxis.POSITIVE_X.rotationDegrees(29.0f));
|
|
||||||
}
|
|
||||||
|
|
||||||
long now = System.currentTimeMillis();
|
|
||||||
if (now - lastUpdate >= 20) {
|
|
||||||
float velX = Math.min(0.6f, playerEntityRenderState.field_53537 / 108.0f);
|
|
||||||
float rawVelY = playerEntityRenderState.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, playerEntityRenderState.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;
|
|
||||||
}
|
|
||||||
|
|
||||||
renderCape(matrixStack, vertexConsumer, playerEntityRenderState, light, OverlayTexture.DEFAULT_UV);
|
|
||||||
|
|
||||||
matrixStack.pop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
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 net.minecraft.client.render.OverlayTexture;
|
|
||||||
import net.minecraft.client.render.VertexConsumerProvider;
|
|
||||||
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;
|
|
||||||
|
|
||||||
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) {
|
|
||||||
SaturnPlayer player = SaturnPlayer.get(state.name);
|
|
||||||
|
|
||||||
if (state.invisible || player == null || player.hat.isEmpty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
matrices.push();
|
|
||||||
|
|
||||||
this.getContextModel().head.rotate(matrices);
|
|
||||||
|
|
||||||
ObjModel.cosmetic("hat", player.hat).render(MtlLoader.cosmetic(player.hat),
|
|
||||||
matrices, vertexConsumers, light,
|
|
||||||
OverlayTexture.DEFAULT_UV);
|
|
||||||
|
|
||||||
matrices.pop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-68
@@ -1,68 +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(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,41 +0,0 @@
|
|||||||
package org.saturnclient.impl.cosmetics.utils;
|
|
||||||
|
|
||||||
import org.saturnclient.common.ref.asset.IdentifierRef;
|
|
||||||
import org.saturnclient.cosmetics.utils.GifUtils;
|
|
||||||
|
|
||||||
import net.minecraft.client.render.RenderLayer;
|
|
||||||
import net.minecraft.client.render.RenderPhase;
|
|
||||||
import net.minecraft.client.render.VertexFormat.DrawMode;
|
|
||||||
import net.minecraft.client.render.VertexFormats;
|
|
||||||
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 RenderLayer END_GATEWAY = RenderLayer.of(
|
|
||||||
"end_gateway",
|
|
||||||
VertexFormats.POSITION,
|
|
||||||
DrawMode.QUADS,
|
|
||||||
1536,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
RenderLayer.MultiPhaseParameters.builder()
|
|
||||||
.program(RenderLayer.END_GATEWAY_PROGRAM)
|
|
||||||
.texture(Textures.create()
|
|
||||||
.add(EndPortalBlockEntityRenderer.SKY_TEXTURE, false, false)
|
|
||||||
.add(EndPortalBlockEntityRenderer.PORTAL_TEXTURE, false, false)
|
|
||||||
.build())
|
|
||||||
.cull(RenderPhase.DISABLE_CULLING)
|
|
||||||
.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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-79
@@ -1,79 +0,0 @@
|
|||||||
package org.saturnclient.impl.features.entity;
|
|
||||||
|
|
||||||
import org.saturnclient.common.feature.EntityFeature;
|
|
||||||
|
|
||||||
import net.minecraft.client.MinecraftClient;
|
|
||||||
import net.minecraft.entity.Entity;
|
|
||||||
import net.minecraft.util.hit.EntityHitResult;
|
|
||||||
import net.minecraft.util.hit.HitResult;
|
|
||||||
import net.minecraft.entity.LivingEntity;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fabric implementation of {@link EntityFeature}.
|
|
||||||
*
|
|
||||||
* Crosshair targeting is resolved via {@link MinecraftClient#crosshairTarget}.
|
|
||||||
* Entity state for nametag rendering is supplied at render time by
|
|
||||||
* {@link org.saturnclient.modules.mixins.LivingEntityRendererMixin} which
|
|
||||||
* writes into the render state; {@link NametagsMixin} then constructs
|
|
||||||
* a {@link RenderStateEntityState} from the already-extracted snapshot.
|
|
||||||
*/
|
|
||||||
public class EntityFeatureImpl implements EntityFeature {
|
|
||||||
|
|
||||||
private final MinecraftClient mc;
|
|
||||||
|
|
||||||
public EntityFeatureImpl(MinecraftClient mc) {
|
|
||||||
this.mc = mc;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isTargetingLivingEntity() {
|
|
||||||
HitResult target = mc.crosshairTarget;
|
|
||||||
if (target == null || target.getType() != HitResult.Type.ENTITY)
|
|
||||||
return false;
|
|
||||||
Entity hit = ((EntityHitResult) target).getEntity();
|
|
||||||
return hit instanceof LivingEntity;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------
|
|
||||||
// Render-state adapter
|
|
||||||
// ---------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Wraps a {@link HealthRenderState} (already injected into the
|
|
||||||
* Minecraft render-state object) as a platform-neutral
|
|
||||||
* {@link EntityState} for use by {@link NametagsFeature}.
|
|
||||||
*
|
|
||||||
* This is constructed inside {@link NametagsMixin} where both the
|
|
||||||
* render-state mixin interface and the entity name are available.
|
|
||||||
*/
|
|
||||||
public static final class RenderStateEntityState implements EntityState {
|
|
||||||
|
|
||||||
private final String customName;
|
|
||||||
private final HealthRenderState hrs;
|
|
||||||
|
|
||||||
public RenderStateEntityState(String customName, HealthRenderState hrs) {
|
|
||||||
this.customName = customName;
|
|
||||||
this.hrs = hrs;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getCustomName() {
|
|
||||||
return customName;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public float getHealth() {
|
|
||||||
return hrs.saturn$getHealth();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public float getMaxHealth() {
|
|
||||||
return hrs.saturn$getMaxHealth();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public EntityType getEntityType() {
|
|
||||||
return hrs.saturn$getEntityType();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-95
@@ -1,95 +0,0 @@
|
|||||||
package org.saturnclient.impl.features.mixins.misc;
|
|
||||||
|
|
||||||
import net.minecraft.client.render.VertexConsumerProvider;
|
|
||||||
import net.minecraft.client.render.entity.EntityRenderer;
|
|
||||||
import net.minecraft.client.render.entity.state.EntityRenderState;
|
|
||||||
import net.minecraft.client.render.entity.state.LivingEntityRenderState;
|
|
||||||
import net.minecraft.client.util.math.MatrixStack;
|
|
||||||
import net.minecraft.text.Text;
|
|
||||||
|
|
||||||
import org.saturnclient.impl.features.entity.EntityFeatureImpl;
|
|
||||||
import org.saturnclient.impl.features.entity.HealthRenderState;
|
|
||||||
import org.saturnclient.impl.features.mixins.render.LivingEntityRenderStateMixin;
|
|
||||||
import org.saturnclient.mod.mods.NametagsMod;
|
|
||||||
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;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Intercepts nametag rendering and replaces the displayed text with
|
|
||||||
* the string produced by {@link NametagsMod#getNametagString}.
|
|
||||||
*
|
|
||||||
* Key changes from the original:
|
|
||||||
*
|
|
||||||
* <ul>
|
|
||||||
* <li>The old {@code NametagsFabric.EntityStateImpl} bridge class is
|
|
||||||
* replaced by {@link EntityFeatureImpl.RenderStateEntityState},
|
|
||||||
* which wraps the {@link HealthRenderState} already injected into
|
|
||||||
* the render state by {@link LivingEntityRenderStateMixin}.</li>
|
|
||||||
* <li>The feature import changes from {@code Nametags} to
|
|
||||||
* {@link NametagsMod}; the two static methods
|
|
||||||
* ({@code shouldReplaceName} / {@code getNametagString}) are
|
|
||||||
* unchanged in signature.</li>
|
|
||||||
* <li>The entity's display name is extracted from the render state's
|
|
||||||
* {@code nameVisible} / text fields where available, falling back
|
|
||||||
* to the plain text string passed to this method.</li>
|
|
||||||
* </ul>
|
|
||||||
*
|
|
||||||
* The re-entrancy guard ({@code saturn$rendering}) is kept intact to
|
|
||||||
* prevent infinite recursion when we call {@code renderLabelIfPresent}
|
|
||||||
* with the replacement text.
|
|
||||||
*/
|
|
||||||
@Mixin(EntityRenderer.class)
|
|
||||||
public abstract class NametagsMixin {
|
|
||||||
|
|
||||||
/** Guards against the recursive call we make with the replacement text. */
|
|
||||||
@Unique
|
|
||||||
private static final ThreadLocal<Boolean> saturn$rendering = ThreadLocal.withInitial(() -> false);
|
|
||||||
|
|
||||||
@Shadow
|
|
||||||
protected abstract <S extends EntityRenderState> void renderLabelIfPresent(
|
|
||||||
S state, Text text,
|
|
||||||
MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light);
|
|
||||||
|
|
||||||
@Inject(method = "renderLabelIfPresent", at = @At("HEAD"), cancellable = true)
|
|
||||||
private <S extends EntityRenderState> void saturn$replaceNametag(
|
|
||||||
S state, Text text,
|
|
||||||
MatrixStack matrices, VertexConsumerProvider vertexConsumers,
|
|
||||||
int light, CallbackInfo ci) {
|
|
||||||
|
|
||||||
// Skip our own recursive call
|
|
||||||
if (saturn$rendering.get())
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (!NametagsMod.shouldReplaceName())
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Only living entities carry health data
|
|
||||||
if (!(state instanceof LivingEntityRenderState living))
|
|
||||||
return;
|
|
||||||
if (!(living instanceof HealthRenderState hrs))
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Build the platform-neutral EntityState from the render-state snapshot
|
|
||||||
// (health/type were written by LivingEntityRendererMixin at extract time).
|
|
||||||
String customName = text != null ? text.getString() : null;
|
|
||||||
EntityFeatureImpl.RenderStateEntityState entityState = new EntityFeatureImpl.RenderStateEntityState(
|
|
||||||
customName, hrs);
|
|
||||||
|
|
||||||
// Ask the feature for the replacement string
|
|
||||||
String replacement = NametagsMod.getNametagString(entityState);
|
|
||||||
if (replacement == null)
|
|
||||||
return; // feature decided not to replace this entity
|
|
||||||
|
|
||||||
ci.cancel();
|
|
||||||
saturn$rendering.set(true);
|
|
||||||
try {
|
|
||||||
renderLabelIfPresent(state, Text.literal(replacement), matrices, vertexConsumers, light);
|
|
||||||
} finally {
|
|
||||||
saturn$rendering.set(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user