Compare commits

...
618 Commits
Author SHA1 Message Date
selimaj-dev 14c16d8fe0 Merge pull request 'Release workflow: upload through the public URL again; smaller jars' (#14) from release-public-url into master
Release / build (release) Successful in 18m36s
2026-09-25 16:36:40 +00:00
selimaj-devandclaude e3d1e4a442 Point common at the panorama downscale merge commit
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-25 18:36:24 +02:00
selimaj-devandclaude 04605f6855 Upload release jars through the public URL again, with a size guard
gitea:3000 isn't resolvable from act_runner's job containers, which run
on their own Docker network. With the panorama downscaled in common the
jars are ~45 MB, under the Cloudflare tunnel's 100 MB request limit, so
upload through github.server_url again. Fail with a readable error
before uploading if any jar is 100 MB or more.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-25 18:31:14 +02:00
selimaj-dev 451ac986c0 Merge pull request 'Release workflow: upload through Gitea's internal address; add manual trigger' (#13) from release-upload-internal-url into master
Reviewed-on: #13
2026-09-25 15:35:17 +00:00
selimaj-devandclaude 80d478d2c9 Upload release jars through Gitea's internal address
The Cloudflare tunnel in front of git.selimaj.dev rejects the jar
uploads with 413 Payload Too Large. Call the API at http://gitea:3000
instead, look the release up by tag, and add a workflow_dispatch trigger
that builds a given tag and attaches the jars to its existing release.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-25 17:32:52 +02:00
selimaj-dev 79719262a2 Merge pull request 'Release workflow: build every version and attach jars; release 0.1.1-beta' (#12) from release-workflow into master
Release / build (release) Failing after 20m1s
Reviewed-on: #12
2026-09-25 14:39:58 +00:00
selimaj-devandclaude 806c4ab643 Make mod_version the source of truth for releases; bump to 0.1.1-beta
The release workflow no longer overrides mod_version from the tag.
Instead it fails early unless the release tag (optionally prefixed with
"v") matches mod_version in gradle.properties, so released jars always
report the same version as the code the version check sees.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-25 16:34:03 +02:00
selimaj-devandclaude 6ee8b64a38 Add release workflow that builds every version and attaches the jars
On a published release, build all versions with buildAll using the tag
as the mod version, and upload build/allJars/*.jar to the release.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-25 16:33:42 +02:00
selimaj-dev a97e5703c9 Merge pull request 'Mojang session auth and version check; release 0.1.0-beta3' (#11) from mojang-session-auth into master
Reviewed-on: #11
2026-09-25 13:29:55 +00:00
selimaj-devandclaude 39f140dae1 Mojang session auth and version check; release 0.1.0-beta3
- Implement SaturnProvider.getModVersion() (Fabric mod metadata) and
  showWarning() (15 s SystemToast) in every version
- Bump mod_version to 0.1.0-beta3, the first version on the new
  Mojang session-server authentication
- Point common at its merge commit for the new auth flow and version check

Fixes #7

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-25 15:27:27 +02:00
selimaj-devandclaude 632591f8d8 Point common at its master merge commit
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-25 14:49:46 +02:00
selimaj-dev 66e994b5ab Merge pull request 'Reconnect to the Saturn server and time out requests (session-java 0.2.0)' (#10) from reconnect-and-timeouts into master
Reviewed-on: #10
2026-09-25 12:48:27 +00:00
selimaj-devandclaude 1d56094388 Use session-java 0.2.0 from the Gitea registry; reconnect and timeouts
Replace JitPack (which can no longer build session-java since its GitHub
repo is gone) with the Gitea package registry in every build file, move
to dev.selimaj:session-java:0.2.0, and bump common for server reconnect
and request timeouts.

Fixes #4, fixes #5.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-25 14:46:45 +02:00
selimaj-devandclaude 69f572288f Point common at its master merge commit
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-25 14:11:20 +02:00
selimaj-dev 1a5c7f2a2c Merge pull request 'Keep server tab names and team formatting; fix player lookups' (#9) from fix-tablist-and-player-lookups into master
Reviewed-on: #9
2026-09-25 12:01:39 +00:00
selimaj-devandclaude 68734f6ac5 Keep server tab names and team formatting in the tab list
TabListMixin @Overwrote PlayerListEntry.getDisplayName() and always
returned the bare profile name, dropping server-set display names (rank
prefixes, colours) and, by never returning null, vanilla's team
decoration, for every player. Inject at RETURN instead: non-Saturn
players keep the original value, and Saturn players get the icon in
front of the original name, or the team-decorated profile name when the
server set none.

Also bump common for the player-lookup and thread-safety fixes.

Fixes #3. Refs #2, #6.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-25 14:00:39 +02:00
selimaj-dev 333c408d1b Merge pull request 'Bye github!' (#1) from bye-github into master
Reviewed-on: #1
2026-09-24 22:17:53 +00:00
selimaj-dev 3c57328b3c byebye github 2026-09-25 00:13:58 +02:00
selimaj-dev 08178b60fe removed module 2026-09-25 00:12:43 +02:00
selimaj-dev 4342233876 Merge pull request #81 from saturnclientmc/cloak-physics
Cloak physics
2026-04-10 05:27:51 +02:00
selimaj-dev 89cb467016 Improved cloak max x speed 2026-04-10 04:10:57 +02:00
selimaj-dev 9488a8647e Improved cloak physics 1.21.4 2026-04-10 04:07:26 +02:00
selimaj-dev fa1d1f312a Improved cloak physics 1.21.5 2026-04-10 04:07:12 +02:00
selimaj-dev b359224ecd Improved cloak physics 1.21.6 2026-04-10 04:06:33 +02:00
selimaj-dev a909aa880a Improved cloak physics 1.21.7 2026-04-10 04:06:02 +02:00
selimaj-dev 8f8fec0816 Improved cloak physics 1.21.8 2026-04-10 04:04:36 +02:00
selimaj-dev c2d20492c3 Improved cloak physics 1.21.9 2026-04-10 04:02:28 +02:00
selimaj-dev 4a7eade70d Improved cloak physics 1.21.10 2026-04-10 04:02:08 +02:00
selimaj-dev 6466d8d8fd Improved cloak physics 2026-04-10 04:00:58 +02:00
selimaj-dev 7c72da978d Improved physics 2026-04-09 23:06:19 +02:00
selimaj-dev 76b842e6a0 Refactored cloak physics 2026-04-09 22:31:38 +02:00
selimaj-dev 00cefe14f7 Refactoring cloak physics 2026-04-09 22:03:28 +02:00
Kr4ight 2a3034be2c Fix inputs #80 2026-04-09 01:28:04 +02:00
selimaj-dev 910633819a Merge pull request #79 from saturnclientmc/nametags-patch
Nametags patch
2026-04-08 20:45:27 +02:00
selimaj-dev daddd0a005 1.21.8 implementation 2026-04-08 20:42:29 +02:00
selimaj-dev cdd6ae8ec9 1.21.9 nametags 2026-04-08 20:40:47 +02:00
selimaj-dev 65c9454370 Re-added old nametag to unsupported versions 2026-04-08 20:39:03 +02:00
selimaj-dev 8a1e9b49bc Added 1.21.8-9 nametag 2026-04-08 20:34:21 +02:00
selimaj-dev f288f9148c Removed nametag mixins 2026-04-08 20:27:59 +02:00
selimaj-dev e165f6b65c Removed nametag from all mixin files 2026-04-08 20:25:44 +02:00
selimaj-dev 019730c756 Nametags 1.21.10 and color fix 2026-04-08 20:23:58 +02:00
selimaj-dev 7fa951b7fa Merge pull request #77 from saturnclientmc/more-versions
More versions
2026-04-08 20:07:16 +02:00
selimaj-dev b39b19f107 Fixed Cloak Renderer 2026-04-08 20:04:00 +02:00
selimaj-dev 88db7a8383 End shader 2026-04-08 19:58:10 +02:00
selimaj-dev d782bccb5f Hat renderer 2026-04-08 19:50:44 +02:00
selimaj-dev 84947d6b33 Working Player tracking 2026-04-08 19:18:31 +02:00
selimaj-dev f79849d9fb Improving render state tracking 2026-04-08 19:08:24 +02:00
selimaj-dev cb38639e11 1.21.11: Small stuff 2026-04-08 10:08:19 +02:00
selimaj-dev 8f567c83c6 1.21.11: Moved mixin to direct 2026-04-08 09:52:14 +02:00
selimaj-dev 8876abde1c 1.21.11: Fix Camera Mixin 2026-04-08 09:41:20 +02:00
selimaj-dev 2d95bf9043 1.21.11: Render Scope 2026-04-08 09:35:43 +02:00
selimaj-dev 0475d8c43d 1.21.11: Fix text stuff 2026-04-08 09:30:39 +02:00
selimaj-dev 491199cfea 1.21.11: Fix Splash overlay and shader utils 2026-04-08 09:29:15 +02:00
selimaj-dev 5aa40d2e61 1.21.11: Fix CloakFeatureRenderer and gradle.properties 2026-04-08 09:21:21 +02:00
selimaj-dev c5b6a858e1 1.21.11: Fix RenderScope 2026-04-08 09:18:08 +02:00
selimaj-dev e28051a109 1.21.11: Tmp fix WorldFeature 2026-04-08 09:17:01 +02:00
selimaj-dev 1471e53a91 1.21.11: Tmp fix ShaderUtils 2026-04-08 09:15:49 +02:00
selimaj-dev 6ae4f980e4 1.21.11: Fix EntityDrawer 2026-04-08 09:11:07 +02:00
selimaj-dev 6fbacbcae6 1.21.11: Fix temp resize on screen 2026-04-08 09:10:38 +02:00
selimaj-dev e934f24d3e 1.21.11: Fix splash overlay 2026-04-08 09:07:38 +02:00
selimaj-dev 191bc1318e Added 1.21.11 (needs fix) 2026-04-08 09:04:06 +02:00
selimaj-dev 249219fe57 Added 1.21.10 2026-04-08 08:58:56 +02:00
selimaj-dev 5a86d4cb4a 1.21.9: Fix crash 2026-04-08 08:46:52 +02:00
selimaj-dev 75a5d7f52a 1.21.9: Fix cloaks 2026-04-08 08:40:33 +02:00
selimaj-dev c078637d3d 1.21.9: Fix status effect 2026-04-08 08:27:24 +02:00
selimaj-dev 6c41e774ff 1.21.9: Fix entity drawer 2026-04-08 07:58:56 +02:00
selimaj-dev c630a4e41a 1.21.9: Removed nametags implementation 2026-04-08 07:50:18 +02:00
selimaj-dev 33956ea550 Fix: text 2026-04-08 07:16:59 +02:00
selimaj-dev 2e45cc39f7 Merge pull request #75 from saturnclientmc/more-versions
More versions
2026-04-07 21:57:20 +02:00
Kr4ight c8860ab837 Running 1.21.9 #72 2026-04-07 21:53:48 +02:00
Kr4ight 64ab091807 Fix 3 2026-04-07 21:47:14 +02:00
Kr4ight 243275b414 Fix 2 2026-04-07 21:32:22 +02:00
Kr4ight 70d7953b45 Fix 1 2026-04-07 21:27:56 +02:00
Kr4ight 812e7bf593 Added 1.21.9 but with errors #72 2026-04-07 21:08:34 +02:00
Kr4ight e5c2d8bd36 Added 1.21.8 #72 2026-04-07 20:59:31 +02:00
Kr4ight 670961acce Added 1.21.7 #72 2026-04-07 20:41:59 +02:00
selimaj-dev b46410c43b Merge pull request #73 from saturnclientmc/fix-auth-timeout
Fix auth timeout
2026-04-07 20:17:52 +02:00
Kr4ight 2cd7db9ade Fix auth timeout 2026-04-07 20:17:15 +02:00
selimaj-dev 3ba9079101 Updated version 2026-03-30 13:57:59 +02:00
Kr4ight db1d31b1e3 Merge pull request #68 from saturnclientmc/improved-session-handling
Fixed large logs when session is offline #66
2026-03-30 13:56:22 +02:00
selimaj-dev d0e712a3c5 Fixed large logs when session is offline #66 2026-03-30 13:51:49 +02:00
Kr4ight b86a2775a1 Merge pull request #65 from saturnclientmc/improved-build-configs
Improved build configs
2026-03-28 21:13:48 +01:00
selimaj-dev 2d16da9dd5 Shared versioning 2026-03-28 21:11:08 +01:00
selimaj-dev 9c0f9a1434 Expanded heap size just in case 2026-03-28 21:00:53 +01:00
selimaj-dev d58b0c90fd Small improvements with build 2026-03-28 20:56:40 +01:00
selimaj-dev bf4cec4ea5 Build all 2026-03-28 20:54:05 +01:00
Kr4ight 2d75ee4ee8 Merge pull request #62 from saturnclientmc/zoom-and-freelok-animation
Zoom and freelok animations
2026-03-26 23:57:35 +01:00
selimaj-dev 37569d069d Working freelook 2026-03-26 17:59:33 +01:00
selimaj-dev f44647ba52 Working freelook 2026-03-26 16:56:44 +01:00
selimaj-dev 518c1d9d57 Merge pull request #60 from saturnclientmc/refactor-renderscope
Refactor RenderScope and improved support with 1.21.6
2026-03-26 02:28:33 +01:00
selimaj-dev 7caff20255 Fixed EntityDrawer 2026-03-26 02:27:04 +01:00
selimaj-dev 69698c16f7 Fixed EntityDrawer 2026-03-26 02:16:57 +01:00
selimaj-dev 3705afb593 Saturn Screen 1.21.6 small patches 2026-03-25 23:36:00 +01:00
selimaj-dev ebe7200d65 Improved rounded rectangles #59 2026-03-25 23:26:33 +01:00
selimaj-dev b457f070b9 Improved rounded rectangles #59 2026-03-25 22:18:26 +01:00
selimaj-dev 4f44274111 Fixed rounded corners on RenderScope 1.21.6 #59 2026-03-25 21:46:34 +01:00
selimaj-dev 51c6108e35 Fixed soem stuff on RenderScope 1.21.6 #59 2026-03-25 21:20:14 +01:00
selimaj-dev 3a71cd5b02 Fixed MatrixStack leak on RenderScope 1.21.6 #59 2026-03-25 21:04:53 +01:00
selimaj-dev 9424e2ec71 Refactored RenderScope 1.21.6 #59 2026-03-25 19:24:02 +01:00
selimaj-dev d50d19b609 Refactored RenderScope and MatrixStack for 1.21.5 #59 2026-03-25 19:09:36 +01:00
selimaj-dev bf280d945e Improved #59 2026-03-25 19:00:21 +01:00
selimaj-dev 0b469e6a5a Improved RenderScope for 1.21.4 #59 2026-03-25 18:23:31 +01:00
Kr4ight 57da2f1181 Merge pull request #58 from saturnclientmc/buggy-1.21.6
Buggy 1.21.6
2026-03-25 16:11:48 +01:00
selimaj-dev c771764251 Fixed MatrixStackRef 2026-03-25 16:10:41 +01:00
selimaj-dev a004585cba Matrix3x2fStackRef 2026-03-25 15:51:14 +01:00
selimaj-dev 92a1f70eb1 Fixed DrawContextAccessor 2026-03-25 15:43:18 +01:00
selimaj-dev 1d0c582729 SaturnScreen temp fix 2026-03-25 15:40:56 +01:00
selimaj-dev 0f947b5115 Rendering for 1.21.6 2026-03-25 15:39:41 +01:00
selimaj-dev 8852856d45 Status effect mixin and ShaderUtils for 1.21.6 2026-03-25 14:49:56 +01:00
selimaj-dev f7a882b0ab Remvoed all player animator stuff 2026-03-25 14:35:37 +01:00
selimaj-dev 35b127def2 Copy pasted 1.21.6 2026-03-25 14:34:22 +01:00
selimaj-dev 13a5347412 Fix mixins #57 2026-03-24 14:36:24 +01:00
Kr4ight ca235486bb Merge pull request #56 from saturnclientmc/refactor-mod-system
Refactor mod system #55
2026-03-24 13:10:43 +01:00
selimaj-dev 89f29d5d0e Refactor feature / module system for 1.21.5 #55 2026-03-24 13:09:32 +01:00
selimaj-dev 99929c7d64 Refactor feature / module system for 1.21.5 #55 2026-03-24 13:06:50 +01:00
selimaj-dev 8836cc851b Refactor feature / module system for 1.21.4 #55 2026-03-24 13:01:38 +01:00
selimaj-dev 7849e2e04c Refactored feature / module system #55 2026-03-24 12:05:02 +01:00
Kr4ight 0bf5bf030f Merge pull request #54 from saturnclientmc/mod-preview
Mod Preview #52
2026-03-24 11:30:52 +01:00
selimaj-dev 21cc6936f7 Mod Preview #52 2026-03-24 11:27:13 +01:00
selimaj-dev 5f225953c2 Merge pull request #53 from saturnclientmc/refactor-gif
Refactored gif system
2026-03-23 21:48:54 +01:00
selimaj-dev 9fbeac628c Refactoed 1.21.5 to work with new gif api 2026-03-23 21:05:22 +01:00
selimaj-dev df102fff84 Gif loading 2026-03-23 20:41:58 +01:00
selimaj-dev d3cd883782 Gif loading 2026-03-23 20:33:10 +01:00
selimaj-dev 7ce54c8561 Fixed 1.21.5 end cosmetics shader 2026-03-23 19:31:11 +01:00
selimaj-dev faa18c99ab Fixed hat rotation in 1.21.5 2026-03-23 14:33:26 +01:00
Rudi a73b139256 Merge pull request #50 from RudideC/master
update common
2026-03-21 23:23:48 +01:00
RudideC 02066bf380 Merge branch 'master' of https://github.com/RudideC/saturnclient 2026-03-21 23:23:15 +01:00
RudideC 925cd6d062 Update common 2026-03-21 23:19:41 +01:00
selimaj-dev f45aa843e5 Merge pull request #48 from saturnclientmc/improved-cloak-physics
Improved cloak physics
2026-03-21 22:38:15 +01:00
selimaj-dev 9f4b2f015d Common 2026-03-21 22:36:53 +01:00
selimaj-dev 16b3936649 Improved expressiveness in cloak physics 2026-03-21 22:08:08 +01:00
RudideC 8b8104412c Update common 2026-03-21 21:51:59 +01:00
selimaj-dev 2bbabff979 Fixed the same thing for 1.21.5 2026-03-21 20:46:10 +01:00
selimaj-dev 39dc0963ce Fixed saturn player tracking 2026-03-21 20:40:29 +01:00
Rudi c37efa53b3 Merge pull request #47 from RudideC/master
fixed compilation
2026-03-21 17:59:56 +01:00
RudideC fe629b8bf5 finally works
svg lib wasnt included
2026-03-21 17:59:22 +01:00
RudideC a8e15b98dd now it runs at least 2026-03-21 17:46:17 +01:00
RudideC 1a71f055ff 1.21.5 build changes
same as 1.21.4
2026-03-21 17:36:53 +01:00
RudideC fa68fcad50 1.21.4 build changes
not quite working, close though
2026-03-21 17:36:41 +01:00
Rudi 66e58b31c2 Merge pull request #46 from RudideC/master
1.21.5 support [NOT PRODUCTION READY]
2026-03-21 17:08:13 +01:00
RudideC 938429dca7 idk 2026-03-21 17:06:50 +01:00
RudideC b9eb77f9e1 different import 2026-03-21 17:06:44 +01:00
RudideC a9b331044f prev vs last 2026-03-21 17:06:36 +01:00
RudideC db22e99b0f armor change 2026-03-21 17:06:29 +01:00
RudideC 290da45cde mixin change 2026-03-21 17:06:23 +01:00
RudideC 56e8cde0f9 major change here, hope nothing bad happens 2026-03-21 17:06:17 +01:00
RudideC 6df7aacb56 1.21.5 2026-03-21 17:05:20 +01:00
RudideC c2f19ef3cf updated for 1.21.5 2026-03-21 17:05:09 +01:00
RudideC 6ac91f10ab player anim for 1.21.5 2026-03-21 17:04:58 +01:00
RudideC 52930df0a9 copy pasted 1.21.4 haha 2026-03-21 16:04:22 +01:00
Rudi ac9f765cb6 Change submodule URL to use HTTPS
this works
2026-03-21 15:42:52 +01:00
Kr4ight 4962e0e4c3 Merge pull request #44 from saturnclientmc/cosmetics
Cosmetics
2026-03-21 14:47:33 +01:00
selimaj-dev 020636d293 Cosmetic update 2026-03-21 14:37:11 +01:00
selimaj-dev 0f172ccbab Bucket hat 2026-03-21 13:12:26 +01:00
selimaj-dev 635219a556 bucket hat 2026-03-21 13:12:03 +01:00
selimaj-dev 5e53a2e188 End shader 2026-03-21 10:38:14 +01:00
selimaj-dev 39b4b616b0 Albania mark 2026-03-21 01:54:24 +01:00
selimaj-dev 0471a007ce Improved cloak physics 2026-03-21 00:38:41 +01:00
selimaj-dev b60f218e29 Cloak loading 2026-03-20 23:06:07 +01:00
Kr4ight 67fb65dcf5 Merge pull request #42 from saturnclientmc/feature-implementations
Implemented features and refactored Property
2026-03-20 22:02:39 +01:00
selimaj-dev a6f3fd79b3 Nofog 2026-03-20 21:55:38 +01:00
selimaj-dev ab60119266 Refactored effect 2026-03-20 21:36:18 +01:00
selimaj-dev 6a40d6b6f1 Fixed Dummy for status effects 2026-03-20 21:17:34 +01:00
selimaj-dev 10a4868d8f Fixed config stuff 2026-03-20 21:07:50 +01:00
selimaj-dev 01da8b8198 Improved module implementation structure 2026-03-20 19:42:41 +01:00
selimaj-dev 787ffd7bcb Fixed freelook 2026-03-20 19:22:59 +01:00
selimaj-dev 462b6fc462 Merge pull request #39 from saturnclientmc/1.21-1.21.1-minor-support
1.21 1.21.1 minor support
2026-03-09 17:50:17 -07:00
selimaj-dev 97603f84ec Fixed 2026-03-10 01:48:31 +01:00
selimaj-dev fba84ebb9a Fixed 2026-03-10 01:48:12 +01:00
selimaj-dev edfe1d59c1 1.21.1 (crashing) 2026-03-09 12:27:13 +01:00
selimaj-dev 7b95f083e7 Fixed CloakFeatureRenderer 2026-03-09 12:23:14 +01:00
selimaj-dev 33c8945d42 Fixed CloakFeatureRenderer 2026-03-09 12:07:59 +01:00
selimaj-dev 4e029570d0 Fixed not booting and build.gradle 2026-03-09 11:44:27 +01:00
selimaj-dev e5a418d7b2 Fixed saturn mixins 2026-03-09 11:32:43 +01:00
selimaj-dev b38e247bff Fixed KeyInputHandler 2026-03-09 11:26:27 +01:00
selimaj-dev 59248ee520 Temporarily fixed SaturnScreenFabric 2026-03-09 11:25:52 +01:00
selimaj-dev 1ec0b90f9b RenderScopeImpl fix 2026-03-09 11:25:11 +01:00
selimaj-dev 4dca80b6b1 Temporarily fixed mixins 2026-03-09 11:17:56 +01:00
selimaj-dev 2317d981b1 Temporarily fixed cosmetics 2026-03-09 11:13:35 +01:00
selimaj-dev dbd096dfbf Added 1.21 (copied from 1.21.4) 2026-03-09 10:45:57 +01:00
selimaj-dev edef4f4893 fix 2026-03-09 01:37:45 +01:00
selimaj-dev 9712bc0f47 Merge pull request #37 from saturnclientmc/final-architecture
Final architecture
2026-03-08 17:34:50 -07:00
selimaj-dev e0d0571078 Fix 2026-03-09 01:33:29 +01:00
selimaj-dev c467eb3b95 Improved gradle 2026-03-09 00:31:20 +01:00
selimaj-dev 57f6ffa3d4 Fixed protected 2026-03-08 16:17:13 +01:00
selimaj-dev 2df38b2962 Moved implementations into impl 2026-03-08 16:16:06 +01:00
selimaj-dev f947fa5f17 Fixed a few things 2026-03-08 16:11:00 +01:00
selimaj-dev 9a8c9670c6 Fixed a few things 2026-03-08 16:05:53 +01:00
selimaj-dev 301b75c003 Mixins 2026-03-08 16:00:36 +01:00
selimaj-dev ceeb64282f Modules implementation 2026-03-08 15:55:44 +01:00
selimaj-dev d68e47d1cc Removed old module logic 2026-03-08 13:48:06 +01:00
selimaj-dev b04014a87f Refactor mixins 2026-03-08 13:34:52 +01:00
selimaj-dev 8854726916 Refactor mixins 2026-03-08 13:34:45 +01:00
selimaj-dev f182ac7e91 Refactor interface to feature 2026-03-08 12:50:46 +01:00
selimaj-dev 68a366a30b ServiceClient cloak loading #36 2026-03-08 12:07:34 +01:00
selimaj-dev dfb3c6e614 CloakStore and static loading 2026-03-08 11:58:11 +01:00
selimaj-dev 6af6ecefad Cloaks in common 2026-03-08 11:45:19 +01:00
selimaj-dev 8039862142 Moving cloaks to common 2026-03-08 11:29:45 +01:00
selimaj-dev b6f164b43b ElementRenderer fix 2026-03-08 11:18:21 +01:00
selimaj-dev 962d2c0319 Fabric mod json small change for website 2026-03-08 11:11:09 +01:00
selimaj-dev 12b4a4fc5f Improved separation 2026-03-08 11:05:46 +01:00
selimaj-dev b99840a937 Provider 2026-03-08 02:59:03 +01:00
selimaj-dev f9938a51e9 Fixed font 2026-03-08 02:47:31 +01:00
selimaj-dev 2fa04b079d GLFW 2026-03-08 02:40:23 +01:00
selimaj-dev c22a9ff5ae Implemented text interface 2026-03-08 02:30:47 +01:00
selimaj-dev 66024ebfa2 Implemented all of the refrences 2026-03-08 01:53:22 +01:00
selimaj-dev 66e4a12819 Improved implementation of ref 2026-03-08 01:23:26 +01:00
selimaj-dev 216b1707ae Architecture #35 2026-03-07 23:16:48 +01:00
selimaj-dev bdc21920eb Architecture #35 2026-03-07 23:14:02 +01:00
selimaj-dev 5d2a21dd69 Removed minecraft package bc it's unnecessary 2026-03-07 23:01:56 +01:00
selimaj-dev 7f5acb9daf Updated readme 2026-03-07 21:30:04 +01:00
selimaj-dev 7728c6e818 Common submodule 2026-03-07 21:25:16 +01:00
selimaj-dev ea6d2958e7 Removed common to replace it with a git submodule 2026-03-07 21:22:32 +01:00
selimaj-dev 04a4a1b226 Merge pull request #34 from saturnclientmc/rewritten-with-common-system
Rewritten with common system (Multi version support)
2026-03-07 09:38:50 -08:00
selimaj-dev b59c2ba705 Moved hats and emotes to common 2026-03-07 18:19:18 +01:00
selimaj-dev 4ddd650102 Fixed default font 2026-03-07 18:06:51 +01:00
selimaj-dev 5f63f8b37a Cosmetics fade remove of sidebar 2026-03-07 17:54:19 +01:00
selimaj-dev 9110cbe722 Added all of the mods 2026-03-07 17:49:35 +01:00
selimaj-dev c2144dcfc8 Fixed screen events 2026-03-07 17:43:01 +01:00
selimaj-dev 39d03c4ef6 Fixed skinpreview drawer 2026-03-07 17:30:47 +01:00
selimaj-dev d4107df833 Fixed resize 2026-03-07 17:23:18 +01:00
selimaj-dev 196dd08943 Title menu stuff 2026-03-07 17:19:10 +01:00
selimaj-dev 199635be0b Fixed screen rendering 2026-03-07 17:08:10 +01:00
selimaj-dev d82921976b It runs but.. 2026-03-07 17:04:11 +01:00
selimaj-dev 8bfc3bc699 Refactored 2026-03-07 17:01:49 +01:00
selimaj-dev e348b94720 Refactored 2026-03-07 16:57:31 +01:00
selimaj-dev 35df2b2b27 Client stuff 2026-03-07 16:55:29 +01:00
selimaj-dev a577ee7796 testing 2026-03-07 16:37:59 +01:00
selimaj-dev 8d528387db Screens 2026-03-07 16:13:11 +01:00
selimaj-dev 90db3d3915 fixed saturnclient package 2026-03-07 16:04:35 +01:00
selimaj-dev 4d7a79f517 HudEditor moved to common 2026-03-07 16:00:51 +01:00
selimaj-dev 5b8ce8f048 Idk 2026-03-07 15:49:29 +01:00
selimaj-dev 7fd5493995 Saturn Screen implementation 2026-03-07 15:37:37 +01:00
selimaj-dev 7af945486c SaturnScreen 2026-03-07 02:33:57 +01:00
selimaj-dev 5bd0d249f9 SaturnScreen 2026-03-07 01:01:10 +01:00
selimaj-dev 0cd192107d Working skin preview 2026-03-07 00:57:04 +01:00
selimaj-dev 3aa98d458d Implementing components 2026-03-07 00:49:31 +01:00
selimaj-dev eca1be7d81 Refacted modules in saturnclient 2026-03-07 00:40:58 +01:00
selimaj-dev bdc22e87ae ping, speedometer, and status effects 2026-03-07 00:37:55 +01:00
selimaj-dev 9b73e4d1c9 nofog and tps and zoom 2026-03-07 00:29:57 +01:00
selimaj-dev 439dc2653e nametags fullbright and health display with keystrokes 2026-03-07 00:25:11 +01:00
selimaj-dev 9fee71154e fps and freelook 2026-03-06 23:54:25 +01:00
selimaj-dev afedec45ae day counter crosshair and coordinates 2026-03-06 23:44:19 +01:00
selimaj-dev 5544946769 auto Sprint and Clock 2026-03-06 23:37:03 +01:00
selimaj-dev debf9ce96d Module provider 2026-03-06 23:27:26 +01:00
selimaj-dev c93b0e049d Armor display mod 2026-03-06 23:10:58 +01:00
selimaj-dev 6eb7fa4c8b Transitioned mod helpers to common 2026-03-06 12:45:02 +01:00
selimaj-dev a02653c5ab Working UI 2026-03-06 12:39:49 +01:00
selimaj-dev 12bb846e50 Improved structure 2026-03-06 12:17:31 +01:00
selimaj-dev 58a38ad809 Fixed scroll 2026-03-06 11:58:14 +01:00
selimaj-dev b9657d6f45 Working 2026-03-06 11:50:51 +01:00
selimaj-dev da27d820f7 MatrixStack 2026-03-06 11:45:17 +01:00
selimaj-dev e07ec5a54a ElementRenderer 2026-03-06 11:41:14 +01:00
selimaj-dev 27c5bafb6b Client mixin 2026-03-06 11:37:24 +01:00
selimaj-dev 3291dc1e6e Temporarily fized 1.21.4 2026-03-06 11:28:48 +01:00
selimaj-dev 7ada7ea3c8 Adding more implementations and fixes 2026-03-06 11:10:28 +01:00
selimaj-dev fc87506965 Adding more implementations 2026-03-06 10:59:07 +01:00
selimaj-dev 0084457008 Fixing svgTexture 2026-03-06 10:27:51 +01:00
selimaj-dev d874d5a388 Elements 2026-03-06 10:16:43 +01:00
selimaj-dev 53332157e3 Small change 2026-03-06 01:15:47 +01:00
selimaj-dev e70c1423a4 Fonts 2026-03-06 01:10:51 +01:00
selimaj-dev 9dbf431a55 Element and animations 2026-03-06 00:53:11 +01:00
selimaj-dev 0f41b2786c Improved structure and added RenderScope 2026-03-06 00:29:11 +01:00
selimaj-dev 86d588ffc4 New textures 2026-03-06 00:09:02 +01:00
selimaj-dev c19b92b242 modules with new resources 2026-03-06 00:08:41 +01:00
selimaj-dev 8027b627d8 UI with new resources 2026-03-06 00:08:07 +01:00
selimaj-dev 9c34198138 Fixed assets 2026-03-06 00:02:11 +01:00
selimaj-dev 6226604fa5 Saturn Client provider and jackson 2026-03-05 23:57:30 +01:00
selimaj-dev 98d5bf5ce5 Saturn Client provider 2026-03-05 23:51:28 +01:00
selimaj-dev 9bf04639b3 ImageTexture with abstracted Identifier 2026-03-05 23:46:32 +01:00
selimaj-dev e8671640e3 Using updated config 2026-03-05 23:45:03 +01:00
selimaj-dev daf71b0e8c moved resources 2026-03-05 23:38:56 +01:00
selimaj-dev d2b016ddc7 Resources 2026-03-05 23:37:09 +01:00
selimaj-dev 4bb6cf8489 Config theme and animation config 2026-03-05 23:34:42 +01:00
selimaj-dev cd8a41524f Config manager and property 2026-03-05 23:28:54 +01:00
selimaj-dev 68a43d55b9 Fixed circular dependency 2026-03-05 22:02:12 +01:00
selimaj-dev 8d3f283f4c MinecraftProvider structure 2026-03-05 21:47:58 +01:00
selimaj-dev 2cd1e39ec5 Identifier 2026-03-05 19:44:52 +01:00
selimaj-dev 45ceaf8b39 renamed ui2 to ui 2026-03-05 18:56:57 +01:00
selimaj-dev dc17e9aa44 Ignore build directory 2026-03-05 16:40:34 +01:00
selimaj-dev bd9dc51578 Common sub project 2026-03-05 16:35:48 +01:00
selimaj-dev 1b3c23587d Working 1.21.4 saturn client (but old structure still) 2026-03-05 16:14:18 +01:00
selimaj-dev da24cc584b Added saturnclient 1.21.4 code 2026-03-05 16:07:35 +01:00
selimaj-dev 55b1814c1e Removed unnecessary client side 2026-03-05 16:01:11 +01:00
selimaj-dev 98976ea745 Working common 2026-03-05 15:52:52 +01:00
selimaj-dev e9f0a49389 Working build 2026-03-05 15:41:24 +01:00
selimaj-dev e414bc2097 Fixed gradle issue 2026-03-05 15:34:30 +01:00
selimaj-dev 6711753bec gradle wrapper 2026-03-05 15:32:57 +01:00
selimaj-dev 076c3d67f8 mod Template 2026-03-05 15:31:31 +01:00
selimaj-dev 6af269da11 Common and versions 2026-03-05 15:25:56 +01:00
selimaj-dev b98fe211f0 Starting fresh 2026-03-05 15:24:28 +01:00
selimaj-dev c70ac16978 Merge pull request #31 from saturnclientmc/rewrote-3d-cosmetic-rendering
Rewrote 3d cosmetic rendering #18
2026-03-03 02:39:12 -08:00
selimaj-dev 6ad563fd4a Removed old obj library 2026-03-03 11:32:02 +01:00
selimaj-dev 8be1bab1b6 Rotation 2026-03-03 11:28:34 +01:00
selimaj-dev a553360868 Refactored json 2026-03-03 10:00:58 +01:00
selimaj-dev 944bada2a2 Refactored structure 2026-03-03 09:53:12 +01:00
selimaj-dev 8ed855723b Working cosmetic id mtl loader 2026-03-03 04:10:19 +01:00
selimaj-dev 5b9807c5d3 Mtl loader 2026-03-03 04:05:14 +01:00
selimaj-dev d9e5153452 Halo json 2026-03-03 03:54:05 +01:00
selimaj-dev 92b310e5e2 Working json for model 2026-03-03 03:50:56 +01:00
selimaj-dev 02d973e4e4 Hat model caching 2026-03-03 03:40:44 +01:00
selimaj-dev 2f41fc080c Working custom obj hat 2026-03-03 03:06:03 +01:00
selimaj-dev 80684f7e07 Working mtl obj rendering 2026-03-03 02:51:06 +01:00
selimaj-dev e3ad918755 Working obj rendering 2026-03-03 02:37:53 +01:00
selimaj-dev 178fb1a484 Tesiting 3d obj rendering 2026-03-02 15:01:18 +01:00
selimaj-dev 2b0f1dcb48 Cosmetics refactor 2026-03-02 02:59:09 +01:00
selimaj-dev 154fbd774f Cosmetics refactor to main 2026-03-02 02:56:17 +01:00
selimaj-dev a497d2a14f Cloak sneak and swim handling 2026-03-02 02:50:28 +01:00
selimaj-dev 00af8c4cd7 Improved cloak y velocity handling 2026-03-02 02:11:38 +01:00
selimaj-dev 3a922418ca Fixed pivot 2026-03-02 02:00:30 +01:00
selimaj-dev 96d7f5065c Fixed small waves 2026-03-01 22:17:16 +01:00
selimaj-dev fb85702ee9 Fixed UI animation default settings 2026-02-25 23:55:16 +01:00
selimaj-dev 69cad4caab Updated README 2026-02-25 23:47:37 +01:00
selimaj-dev 23e508138b Screenshot and fixed small animation duration 2026-02-25 18:01:47 +01:00
selimaj-dev 593b9f4760 Merge pull request #30 from saturnclientmc/cloak-physics
Cloak physics #14
2026-02-25 08:51:23 -08:00
selimaj-dev 5b44caa358 Falling physics 2026-02-25 17:47:49 +01:00
selimaj-dev 28d71e63a9 Flat cloak 2026-02-25 17:35:50 +01:00
selimaj-dev 84addc7996 Fixed gaps in curve 2026-02-25 17:11:38 +01:00
selimaj-dev 3f8d68619a Working simulation 2026-02-25 16:52:12 +01:00
selimaj-dev d941334fa3 Fixed gap 2026-02-25 16:34:06 +01:00
selimaj-dev 9c36d33cb2 Working ish system 2026-02-25 16:24:45 +01:00
selimaj-dev 6b03a1441a Fixing the rendering anchoring 2026-02-25 16:06:10 +01:00
selimaj-dev f40d3026e6 Fixed reverse rendering and position 2026-02-25 14:41:22 +01:00
selimaj-dev 2ea122c4a9 Working full cape rendering 2026-02-25 14:17:57 +01:00
selimaj-dev dfce271c37 Cape segment specific rendering 2026-02-25 13:52:07 +01:00
Rudi d40724205c Merge pull request #29 from RudideC/master
bugfixes, changes to animation defaults
2026-02-25 11:05:58 +01:00
RudideC 55afd1e436 changed default font
i think this looks better. thoughts?
2026-02-25 11:03:03 +01:00
RudideC eb3e3d61db changed default animation timings
should be snappier now
2026-02-25 11:02:40 +01:00
RudideC 5da0e3c428 added myself as contributor 2026-02-25 11:02:21 +01:00
RudideC f464d10971 fixed libraries not included 2026-02-25 11:02:08 +01:00
RudideC 6e0bc22c17 bugfix, fixed mod not loading
remap = false makes it so that it works when developing, but not when compiled
2026-02-25 11:01:57 +01:00
selimaj-dev bb95999112 Implementing cape curving 2026-02-25 04:34:57 +01:00
selimaj-dev 9c0f92d034 Fixed title screen 2026-02-25 03:47:05 +01:00
selimaj-dev 9d04958ff2 Merge pull request #28 from saturnclientmc/animation-settings
Animation settings
2026-02-24 18:36:53 -08:00
selimaj-dev cfe0d4a97f Emote wheel and cosmetics animations 2026-02-25 02:07:30 +01:00
selimaj-dev 0995484c09 Emote wheel animation config 2026-02-25 01:28:52 +01:00
selimaj-dev ec2254cf74 Implemented curves for all animations 2026-02-25 01:18:00 +01:00
selimaj-dev 71ae403188 Implementing AnimationConfig directly 2026-02-25 00:54:35 +01:00
selimaj-dev 6689919b3b Fixes and implemented animation curves 2026-02-25 00:39:48 +01:00
selimaj-dev 246e49e521 Animation fix 2026-02-25 00:25:13 +01:00
selimaj-dev eb065b20d7 Animation config 2026-02-25 00:18:16 +01:00
Rudi 038c859133 Merge pull request #27 from RudideC/master
0.0.7-beta1
2026-02-24 21:41:47 +01:00
RudideC 434afa7f62 0.0.7-beta1
all code integrated, beta release is ready
2026-02-24 21:38:40 +01:00
RudideC fd492b91a0 animation fine-tuning, easing change
can change between ease-in-out and ease-out (default)
2026-02-24 21:34:56 +01:00
RudideC 48a41af3b0 changed timings
personal preference, i think these should be a setting
2026-02-24 21:27:46 +01:00
RudideC 6da9eb55ba fixed bug, where modmenu wouldn't have scroll 2026-02-24 21:27:23 +01:00
RudideC 22efd8b6e0 moved to svg
yay
2026-02-24 16:23:34 +01:00
Rudi b71d779900 Merge pull request #25 from RudideC/master
Nametags, Ping, Clock, Tps
2026-02-24 10:05:52 +01:00
Rudi 6c3dbd90a0 Merge branch 'master' into master 2026-02-24 10:05:15 +01:00
RudideC 1b16098121 0.0.7-alpha1
will do some testing and then move towards the beta release
2026-02-24 09:41:50 +01:00
RudideC 092fcfb009 new icons
wow
2026-02-24 09:40:06 +01:00
RudideC 008a0927b6 clock mod 2026-02-24 09:37:37 +01:00
RudideC 3c2f75e438 tps mod
missing the image, will add later
2026-02-24 09:21:23 +01:00
RudideC 09c354337d rename 2026-02-24 09:19:17 +01:00
selimaj-dev dc6387cc27 Merge pull request #24 from saturnclientmc/services-and-ui-tweaks
Services and UI tweaks
2026-02-23 12:32:03 -08:00
selimaj-dev bff18a05d1 Changed ip to the actual server 2026-02-23 21:30:50 +01:00
selimaj-dev 6717d7e901 Fixed UUID and cloak 2026-02-23 18:51:28 +01:00
selimaj-dev bbf4b17494 Attempting to fix sendplayer 2026-02-23 17:46:28 +01:00
selimaj-dev 68b4bfc439 Working peer player 2026-02-23 16:29:48 +01:00
RudideC 77f5350496 forgot to add icon 2026-02-23 11:38:57 +01:00
RudideC 3c1914c30a added ping mod
wow
2026-02-23 11:27:34 +01:00
RudideC ae8ec4b4ef fixed modmenu scroll 2026-02-23 11:27:25 +01:00
RudideC bdd421e1da nametag module finished
yay
2026-02-23 10:20:41 +01:00
RudideC 7b480d1b8b unit
wow
2026-02-23 09:52:42 +01:00
selimaj-dev 2a425335f0 Working emote streaming 2026-02-23 09:34:38 +01:00
RudideC 02f8b486f3 obfuscate names, name color 2026-02-23 09:24:27 +01:00
selimaj-dev f0966c86f3 Fixing emote system 2026-02-23 09:20:10 +01:00
RudideC 65c6c2a284 colors, can hide heart emoji 2026-02-23 09:11:06 +01:00
RudideC 9fe05722f9 player, hostile, passive filter 2026-02-23 08:47:02 +01:00
selimaj-dev 61be7c317f Fixed the hat rendering issue 2026-02-23 07:22:44 +01:00
selimaj-dev df35497af2 Trying to find the hat rendering issue 2026-02-23 07:18:54 +01:00
selimaj-dev 8614e1bdd1 Working service system 2026-02-23 06:47:08 +01:00
selimaj-dev c5c38247cc Improved build.gradle 2026-02-23 03:45:26 +01:00
selimaj-dev f9c0d3fe58 Fixed keystrokes 2026-02-23 00:24:14 +01:00
selimaj-dev 44c0b69c4d Fixed keystrokes 2026-02-22 23:59:25 +01:00
selimaj-dev 0cfa7b81ef SVG mod icons 2026-02-22 23:40:39 +01:00
selimaj-dev 9caf47d81b Fixed svg issues 2026-02-22 18:11:58 +01:00
selimaj-dev 3c27d3b377 Working svg renderer 2026-02-22 16:41:37 +01:00
selimaj-dev 66b96102f8 Working svg renderer 2026-02-22 16:41:26 +01:00
selimaj-dev 7fc05df240 Svg image rendering 2026-02-22 14:35:57 +01:00
selimaj-dev d32f124974 Removed todos 2026-02-22 04:27:30 +01:00
selimaj-dev fe4d999643 Merge pull request #23 from saturnclientmc/new-ui-theme-and-animations
New UI theme and animations
2026-02-21 18:48:24 -08:00
selimaj-dev cd84866ee8 Optional staggering 2026-02-22 03:44:22 +01:00
selimaj-dev cd5cac7f69 Shift menu animations 2026-02-22 03:36:39 +01:00
selimaj-dev 377031c0a9 Fixed click bugs 2026-02-22 03:20:16 +01:00
selimaj-dev 85d4a1de38 Animated mods 2026-02-22 02:44:58 +01:00
selimaj-dev 44d7aa3641 Hoo lee sheet 2026-02-22 02:31:56 +01:00
selimaj-dev 53732c5df3 Animation stagger and a few slide fixes 2026-02-22 02:19:04 +01:00
selimaj-dev 265b44187b Animation stagger delay 2026-02-22 01:38:30 +01:00
selimaj-dev f46f9c474e Animation Stagger element 2026-02-22 01:34:54 +01:00
selimaj-dev 049c92785d Fixed bugs and restructured roles 2026-02-22 01:25:22 +01:00
selimaj-dev 37ec729d1d Blurring 2026-02-22 01:11:12 +01:00
selimaj-dev 2747ad366e Fixed title menu freezing 2026-02-22 01:06:33 +01:00
selimaj-dev 7de3d5c9bc Fixed keybinding crashing when invalid 2026-02-22 00:20:12 +01:00
selimaj-dev f952fa7095 Better animation system 2026-02-22 00:12:13 +01:00
selimaj-dev e62da29a49 Sub namespace loading 2026-02-21 23:11:22 +01:00
selimaj-dev d3258edbc7 Cleaner Config 2026-02-21 22:55:47 +01:00
selimaj-dev 2096925a02 Smaller HexInput 2026-02-21 22:42:21 +01:00
selimaj-dev 3db44bab85 Fixed HexInput and stuff 2026-02-21 22:37:56 +01:00
selimaj-dev bba12146ea HexInput 2026-02-21 22:17:59 +01:00
selimaj-dev 4403a31c93 Fix splashoverlay and saturnmodule 2026-02-21 20:59:07 +01:00
selimaj-dev 6ee2fc3b0e Improved theme usability 2026-02-21 20:42:04 +01:00
selimaj-dev 3b0ab53d86 Fixed Theme issue 2026-02-21 20:35:52 +01:00
selimaj-dev 7683e7b91e Removed NamedProperty 2026-02-21 20:18:52 +01:00
selimaj-dev 3ba6202b43 Improved scroll 2026-02-21 20:09:19 +01:00
selimaj-dev a49212ca64 Attempting to fix scroll 2026-02-21 20:02:12 +01:00
selimaj-dev 88fe53b907 Fixed sidebar with abstractions 2026-02-21 19:57:40 +01:00
selimaj-dev 52f80281bd Fixed a few issues 2026-02-21 19:46:40 +01:00
selimaj-dev 2f93822996 Scrollbar 2026-02-21 19:38:19 +01:00
selimaj-dev 8d8cbc8b28 Implemenented theme 2026-02-21 19:29:36 +01:00
selimaj-dev a7c5f0d3df Fixed font stuff in screens 2026-02-21 19:19:54 +01:00
selimaj-dev 60d83530ae Tabmenu text and texture button 2026-02-21 19:18:42 +01:00
selimaj-dev 569aecde4a Scroll 2026-02-21 18:51:58 +01:00
selimaj-dev 9fd79aadd5 Notification 2026-02-21 18:47:19 +01:00
selimaj-dev e031672d72 Clicakble texture 2026-02-21 18:46:03 +01:00
selimaj-dev 289a2cd78a Button 2026-02-21 18:44:24 +01:00
selimaj-dev aab4f02dc0 Sidebar 2026-02-21 18:40:14 +01:00
selimaj-dev 5b37131bfd Saturn module 2026-02-21 18:34:12 +01:00
selimaj-dev 79ceaf67a1 Cosmetic Preview 2026-02-21 18:28:30 +01:00
selimaj-dev b0dbdeafa3 Toggle 2026-02-21 18:25:17 +01:00
selimaj-dev 3bc21a5b30 Keybinding selector and select 2026-02-21 18:19:44 +01:00
selimaj-dev 953182e56a Theme font, and implemented input 2026-02-21 18:13:36 +01:00
selimaj-dev 39cea14183 Theme padding and radius 2026-02-21 18:10:57 +01:00
selimaj-dev 8baf28fcfe Implemented theme 2026-02-21 18:05:26 +01:00
selimaj-dev 619c4bce21 Fixed role 2026-02-20 21:02:00 +01:00
selimaj-dev cc80fc5239 Merge pull request #21 from saturnclientmc/refactored-services-and-splash
Refactored services and splash
2026-02-20 11:56:48 -08:00
selimaj-dev f9039fdf67 Splash Background 2026-02-20 20:53:27 +01:00
selimaj-dev bb93d72f66 Progress bar 2026-02-20 20:12:17 +01:00
selimaj-dev 86be2eb956 Tested auth and fixed title menu 2026-02-20 19:44:43 +01:00
selimaj-dev eb40903372 SetScreen mixin 2026-02-20 18:11:58 +01:00
selimaj-dev 039d2cd032 SetScreen mixin 2026-02-20 17:35:59 +01:00
selimaj-dev 365063e8d5 Custom splash overlay 2026-02-20 16:41:09 +01:00
RudideC b43b076ca0 bugfix
you can now activate hitboxes without opening emotes
2026-02-20 11:10:00 +01:00
RudideC 71a7721c52 entity types 2026-02-20 10:12:19 +01:00
RudideC 2bcad83409 added nametags logo 2026-02-20 09:34:55 +01:00
selimaj-dev 6750abcc11 Service client refactor finalized 2026-02-20 03:47:32 +01:00
selimaj-dev 46d3408be9 TabList 2026-02-20 03:46:51 +01:00
selimaj-dev 8015a23342 Emote service 2026-02-20 03:42:56 +01:00
selimaj-dev 4e6bdce019 Buy cosmetics service 2026-02-20 03:41:17 +01:00
selimaj-dev 68519ac00c UI service auth refactored 2026-02-20 03:38:05 +01:00
selimaj-dev 50da953dc4 Added myself as the owner again 2026-02-20 03:25:33 +01:00
selimaj-dev f5776450f0 Name tag service 2026-02-20 03:24:09 +01:00
selimaj-dev 3b3283c6b0 Hat service 2026-02-20 03:11:08 +01:00
selimaj-dev 3f06efe8f4 Hat feature renderer 2026-02-20 03:09:50 +01:00
selimaj-dev 2d021e886f Cloaks 2026-02-20 03:07:31 +01:00
selimaj-dev 96f201cdb4 Auth 2026-02-20 02:45:42 +01:00
selimaj-dev 57c1d21407 Updated session version 2026-02-20 02:15:15 +01:00
selimaj-dev 5d8068c964 Before switching session versions 2026-02-20 01:44:03 +01:00
selimaj-dev 418c47f483 New structure 2026-02-20 00:31:19 +01:00
selimaj-dev 08c001afbb Session build 2026-02-19 23:21:03 +01:00
RudideC 052a2ac06c fixed small issue 2026-02-19 13:33:54 +01:00
RudideC 38041b01cf nametags module 2026-02-19 13:22:30 +01:00
Rudi fdda25464b Merge pull request #17 from RudideC/master
added github actions
2026-02-18 12:41:34 +01:00
RudideC 6bfdf66cdd push
will this work?
2026-02-18 12:37:03 +01:00
RudideC 82f8d0a550 maybe this will work?? 2026-02-18 12:21:17 +01:00
RudideC 81bfe41e25 no sources file 2026-02-18 12:06:58 +01:00
RudideC db0e627f5c fixed bug, dont upload sources jar 2026-02-18 10:13:04 +01:00
RudideC da970354e8 fix deprecation, simplified 2026-02-18 10:02:29 +01:00
RudideC 045e5cd5da test 2026-02-18 09:36:58 +01:00
RudideC e517377bdd ts 2026-02-18 09:25:09 +01:00
RudideC 520b5221b5 new version 2026-02-18 09:17:34 +01:00
selimaj-dev c567b282a5 Merge pull request #12 from RudideC/master
added health display
2026-02-13 12:03:56 +01:00
RudideC 5f19abebbf added health display
ts tuff
2026-02-13 10:06:13 +01:00
Klesti Selimaj 58eda56f37 Merge pull request #11 from RudideC/master
zoom mod
2026-01-21 21:57:42 +01:00
RudideC 50cab402d4 image 2026-01-21 21:52:28 +01:00
RudideC fa7161d371 zoom module 2026-01-21 21:52:24 +01:00
Klesti Selimaj 252808a467 Merge pull request #10 from RudideC/master
added nofog, daycounter and speedometer
2026-01-20 21:56:36 +01:00
RudideC 31e9a8d56e speedometer module finished
logo included
2026-01-20 15:30:57 +01:00
RudideC ba1b68d8fa speedometer works in vehicles 2026-01-20 14:57:59 +01:00
RudideC dcd82fa58e default is x axis 2026-01-20 14:51:22 +01:00
RudideC 4132a05c8f blocks/tick -> blocks/s 2026-01-20 14:44:29 +01:00
RudideC 47f8dd5e22 testing speedometer 2026-01-20 14:35:49 +01:00
RudideC 9b30ec7016 small change 2026-01-20 14:18:44 +01:00
RudideC d10f229ffa daycounter image 2026-01-19 20:55:07 +01:00
RudideC 061ed674e3 daycounter mod
counts days
2026-01-19 20:54:59 +01:00
RudideC 5b5f781095 nofog mod
you can customise if you want to be able to see through liquids, but only lava and powdered snow work (not water)
2026-01-17 00:24:39 +01:00
Klesti Selimaj 48f7aefbf6 Merge pull request #9 from RudideC/master 2026-01-16 22:12:15 +01:00
RudideC 8f111badf6 oops, forgot 2026-01-16 22:05:35 +01:00
RudideC 687bda68a1 renamed potiondisplay to status effects
this is the name most clients use
2026-01-16 22:04:57 +01:00
RudideC 8e2c986ae4 fullbright
new mod
2026-01-16 22:02:48 +01:00
RudideC faa95e664a faster timeout
(so i don't have to wait minutes before testing)
2026-01-16 22:02:25 +01:00
RudideC c9230da168 faster builds
yes
2026-01-16 22:01:59 +01:00
RudideC 7002fbec0e prevent race condition 2026-01-16 19:41:37 +01:00
RudideC 496d27b861 enabled mod list updates
each mod enable/disable updates the list
2026-01-16 19:41:29 +01:00
RudideC 3c37b80bc2 dont sprint when using item
anticheat compliance
2026-01-16 19:02:51 +01:00
selimaj-dev 920657ddd3 Beta 2 2025-09-08 18:46:29 +02:00
selimaj-dev 32035a2f7d Fixed flat cloak with new optimised textures 2025-09-06 12:07:30 +02:00
Leo Codes c230f4f5ec Merge pull request #7 from saturnclientmc/optimised-cloak
Optimised cloak texture size
2025-09-06 02:24:46 +02:00
selimaj-dev 50d1028e53 Optimised cloak texture size 2025-09-06 02:21:40 +02:00
Leo Codes 780cb9ba9a Merge pull request #6 from saturnclientmc/cloak-edge-cases
Cloak edge cases
2025-09-05 18:05:24 +02:00
selimaj-dev 65df9e9a5e Fixed cloak when crouching #5 2025-09-05 17:49:45 +02:00
selimaj-dev 66f8a13faa Added a wave effect #5 2025-09-05 17:09:46 +02:00
selimaj-dev 1726542c84 Fixed cloak when swimming #5 2025-09-05 16:35:57 +02:00
selimaj-dev 71f6597624 0.0.5 beta 1 and a few changes to the cloak 2025-09-04 20:04:38 +02:00
selimaj-dev 45f8c65549 Bendy cloak option and slight cloak jiggle 2025-09-04 19:54:35 +02:00
selimaj-dev 82bb2859c9 Fixed store purchase 2025-09-04 19:30:25 +02:00
Leo Codes 4d87c4e694 Merge pull request #4 from saturnclientmc/updated-cloak
Optimised, added wavy cloaks and added new cloaks
2025-09-04 18:19:49 +02:00
selimaj-dev 5c7283e513 Wavey cloaks 2025-09-04 17:28:17 +02:00
selimaj-dev ee300b2bb2 Wavey cloaks (no bottom) 2025-09-04 16:35:26 +02:00
selimaj-dev 3b1f54a5b0 Okay getting somewhere 2025-09-04 15:57:36 +02:00
selimaj-dev e39c877b91 All sides except top and bottom 2025-09-04 15:43:40 +02:00
selimaj-dev 4bb4e46487 Testing for curved cloak 2025-09-04 15:15:34 +02:00
selimaj-dev d56bdfea8e Working custom renderer for cloaks 2025-09-04 11:41:57 +02:00
selimaj-dev afca9c01b7 Working custom renderer for cloaks 2025-09-04 03:26:37 +02:00
selimaj-dev 40b0177cf2 Working custom renderer for cloaks 2025-09-03 17:50:57 +02:00
selimaj-dev db27de497a Slightly better light for cloaks 2025-09-03 08:20:18 +02:00
selimaj-dev 53fd88df18 Optimised cloak loading 2025-09-03 08:01:32 +02:00
selimaj-dev 8e477f8852 Main hand on armor status 2025-09-03 05:28:41 +02:00
selimaj-dev 1f93f6cf5a Fixed red bull cloak 2025-09-03 05:11:14 +02:00
selimaj-dev a4c60dd5b0 Fixed cloak store 2025-09-03 05:10:00 +02:00
selimaj-dev 72766a47ac Added cloaks to list 2025-09-03 04:45:40 +02:00
selimaj-dev 4b7238e863 Black hole cloak previews 2025-09-03 04:28:19 +02:00
selimaj-dev 52456f2ca8 Black hole cloaks 2025-09-03 04:26:03 +02:00
selimaj-dev a464635075 Cloak fix 2025-09-03 02:57:41 +02:00
selimaj-dev 8089deb172 Optimisations 2025-09-03 02:51:19 +02:00
selimaj-dev ca9626f340 Testing for cloak 2025-09-03 01:34:22 +02:00
selimaj-dev 0d3a27b5e7 Beta 2 2025-08-22 17:36:44 +02:00
selimaj-dev b6eca22097 Fixed alignment in bmw and amg cloaks 2025-08-21 01:49:58 +02:00
selimaj-dev 4de4605ee8 Red Bull cloak (sick af) 2025-08-21 01:44:45 +02:00
selimaj-dev 8018caf26b Ferrari 2025-08-21 01:22:03 +02:00
selimaj-dev 39266df45e Amg petronas f1 cloak 2025-08-20 18:14:45 +02:00
selimaj-dev 25046b53f3 Amg cloak 2025-08-20 17:26:11 +02:00
selimaj-dev 8e89a850b3 Bmw preview 2025-08-19 20:27:55 +02:00
selimaj-dev ccaa79e89b Bmw cloak 2025-08-19 20:24:23 +02:00
selimaj-dev 30b375aa6f Bmw cloak 2025-08-19 20:16:39 +02:00
selimaj-dev 6d2cad1c7a Bmw cloak 2025-08-19 20:16:32 +02:00
selimaj-dev 720ee3d8bd change version to 0.0.4-beta1 2025-08-18 14:00:40 +02:00
selimaj-dev b4b348a54c change version to 0.0.2-beta1 2025-08-18 14:00:23 +02:00
selimaj-dev a3be72c443 Loading in theme manager with refactor 2025-08-18 11:58:14 +02:00
selimaj-dev 1c553d14bf Fixed theme manager bug 2025-08-18 09:25:38 +02:00
selimaj-dev cd967a907c Changed emote icons 2025-08-17 20:34:40 +02:00
selimaj-dev ba04c45b75 readme change 2025-08-17 15:26:45 +02:00
selimaj-dev ecb07ce730 readme change 2025-08-17 15:25:41 +02:00
selimaj-dev 6cd10240fc Merge branch 'refactored-clientcom' 2025-08-17 15:23:24 +02:00
selimaj-dev d162db3464 Fixed keypress and hud rendering 2025-08-17 15:22:00 +02:00
Leo Codes 7e3b6cf7f8 Merge pull request #3 from saturnclientmc/refactored-clientcom
Refactored clientcom
2025-08-16 10:39:06 -04:00
selimaj-dev 6fb38e4f9b Updated ip 2025-08-16 16:33:19 +02:00
selimaj-dev 997fbbe9a9 Working emote system 2025-08-16 16:29:48 +02:00
selimaj-dev 01a8ea15a0 Better cosmetic update system 2025-08-16 16:05:44 +02:00
selimaj-dev 08cee2172c Removed the reload system 2025-08-16 14:17:55 +02:00
selimaj-dev 2c64eed329 new version 2025-07-07 01:01:08 +02:00
selimaj-dev 66d7d7eab9 Disabled online emotes 2025-07-07 00:54:56 +02:00
selimaj-dev 21b1dcc997 saturn client branding 2025-07-05 05:20:34 +02:00
selimaj-dev c2470378e1 saturn client branding 2025-07-05 05:20:27 +02:00
selimaj-dev f57e773a44 fixed hat still visible when invisible 2025-07-05 04:02:30 +02:00
selimaj-dev 6c0575a03f Notifications 2025-06-23 17:03:18 -05:00
selimaj-dev 9f93ea923e Cosmetic preview slight improvement 2025-06-23 17:16:28 +02:00
selimaj-dev ecf5987434 sidebar gap 2025-06-23 17:04:03 +02:00
selimaj-dev 89a5b2437e fully functional store 2025-06-23 16:49:09 +02:00
selimaj-dev c8d28133a0 Crimson Mark 2025-06-23 07:12:54 +02:00
selimaj-dev 0980e3a5d4 semi working Store 2025-06-22 16:56:18 -05:00
selimaj-dev 420ba82b44 more emotes 2025-06-22 11:24:38 -05:00
selimaj-dev 2f3c125bba Keystrokes mouse and space options 2025-06-19 15:13:29 -05:00
selimaj-dev 9e64b2332e added keystrokes 2025-06-18 13:47:50 -05:00
selimaj-dev 089cf4778c freelook 2025-06-18 08:51:14 -05:00
selimaj-dev b9984c35a2 coord display methods 2025-06-18 05:10:15 -05:00
selimaj-dev b035381ba7 removed unnecessary resources 2025-06-18 04:30:17 -05:00
selimaj-dev 87b326ce9f coords 2025-06-18 04:26:39 -05:00
selimaj-dev 786f9cf6f7 small change 2025-06-18 00:29:57 +02:00
selimaj-dev 464d0b502b NONE key selection when backspace 2025-06-17 13:13:56 -05:00
selimaj-dev e38f4480a3 config save bug fix 2025-06-17 11:54:50 -05:00
selimaj-dev d178f1504b config for keybidnings 2025-06-17 11:42:19 -05:00
selimaj-dev 5b77ea7363 Small property refactor 2025-06-17 10:19:49 -05:00
selimaj-dev 78097b1d8f prep for module mixins 2025-06-17 09:54:03 -05:00
selimaj-dev 51040900cf potion display 2025-06-16 16:12:40 -05:00
selimaj-dev f06e2a0aaa updated icon 2025-06-16 16:06:19 -05:00
selimaj-dev 70fcd64f25 potion display (needs dummy and proper dimensions) 2025-06-16 11:49:56 -05:00
selimaj-dev b2c7e9f03e default transparent hud mod background 2025-06-16 07:31:56 -05:00
selimaj-dev 08155bd49a fixed armor display 2025-06-16 07:31:14 -05:00
selimaj-dev 67a5b219f5 better font management 2025-06-16 07:06:15 -05:00
selimaj-dev 6c608409e0 Added emote cancel (shift) 2025-06-15 09:53:50 -05:00
selimaj-dev 4c72f8e39f online emotes (todo: make the emotes cancel across the players) 2025-06-15 01:32:28 +02:00
selimaj-dev 3d9d01a28f fps display 2025-06-14 14:09:27 -05:00
selimaj-dev 7e39532d1d almost fully working emotes 2025-06-14 13:52:29 -05:00
selimaj-dev 76c20d8733 emotes (real) 2025-06-14 06:14:25 +02:00
selimaj-dev a1c68609d9 working emote test 2025-06-14 04:21:34 +02:00
selimaj-dev d49afbb981 updated mercedes flow 2025-05-18 23:03:16 +02:00
selimaj-dev fd0bf75750 beta2 2025-05-17 15:31:22 +02:00
selimaj-dev edbfd0306a added mod detials 2025-05-17 01:56:26 +02:00
selimaj-dev 324ffe0c8b some ui stuff 2025-05-17 01:24:27 +02:00
selimaj-dev 21daf473b1 added armor display 2025-05-16 07:34:40 -05:00
Leo Codes d1030bb235 Merge pull request #2 from saturnclientmc/refactor
Refactor
2025-05-13 02:06:52 -05:00
selimaj-dev 05fcd3032b default colors changed 2025-05-13 00:03:09 +02:00
selimaj-dev 47cafad92b cosmetic skin fix? 2025-05-12 23:40:34 +02:00
selimaj-dev 744698fd71 bye bye old ui 2025-05-12 23:07:06 +02:00
selimaj-dev 86b638afca few changes to finish today 2025-05-12 15:04:08 -05:00
selimaj-dev a016fd72c6 added the settings menu for ui 2 2025-05-12 14:39:03 -05:00
selimaj-dev 8e8737afcd added a toggle 2025-05-12 11:19:26 -05:00
selimaj-dev 6eb3fc8092 color picker 2025-05-12 10:28:37 -05:00
selimaj-dev f9baa0fe5f added FloatInput 2025-05-12 07:18:32 -05:00
selimaj-dev a6e762b0d8 Keypress implementation on ui2 2025-05-12 07:03:32 -05:00
selimaj-dev d72b152a26 ok 2025-05-11 05:00:18 -05:00
selimaj-dev 2a202c4160 added cosmetic menus 2025-05-11 04:36:14 -05:00
selimaj-dev 930d002b81 this time it should be good (aside from colors) 2025-05-11 02:53:31 -05:00
selimaj-dev 35440180ea made the mod actually good 2025-05-09 01:54:17 +02:00
selimaj-dev 7fd0565f7f better mod card design 2025-05-08 09:54:15 -05:00
selimaj-dev 1421d94647 a lot of stuff idk 2025-05-07 12:36:23 -05:00
selimaj-dev fbb1460872 slighly better font 2025-05-07 12:20:43 -05:00
selimaj-dev e7ca5c0297 slighly better font 2025-05-07 11:32:16 -05:00
selimaj-dev 3a54739410 slighly better font 2025-05-07 10:07:57 -05:00
selimaj-dev 1c80803994 title screen change 2025-05-07 12:41:01 +02:00
selimaj-dev 191e9eba4c column fix 2025-05-07 03:05:16 +02:00
selimaj-dev 3d654685d0 small ui changes 2025-05-07 00:58:38 +02:00
selimaj-dev d9bd2c60c9 logo update 2025-05-07 00:26:45 +02:00
selimaj-dev 984132991d Better module display 2025-05-06 23:46:26 +02:00
selimaj-dev 7d649efa22 Themified mods and state merging 2025-05-06 20:48:23 +02:00
selimaj-dev 1ffadc9b45 font 2025-05-06 08:00:21 -05:00
selimaj-dev 9a7b93ccbe mod ui 2025-05-06 08:00:09 -05:00
selimaj-dev d37d4e49f9 New mod ui 2025-05-06 07:23:07 -05:00
selimaj-dev b491f246c8 Dupe fix 2025-05-06 06:31:50 -05:00
selimaj-dev cfe518380a Theming 2025-05-06 06:27:42 -05:00
selimaj-dev fe2521bee2 height fix on scroll 2025-05-06 05:02:11 -05:00
selimaj-dev ae4d9786b2 functional scrolling and functional mod menu 2025-05-06 05:37:06 +02:00
selimaj-dev 0f4af22346 scrolling 2025-05-06 03:18:56 +02:00
selimaj-dev 5a8ae263e9 scissor stack 2025-05-06 01:51:32 +02:00
selimaj-dev 2458906e3b Merge branch 'refactor' of github.com:saturnclientmc/saturnclient into refactor 2025-05-05 11:21:04 -05:00
selimaj-dev 42611d4aed added scrolling (need to add scissor) 2025-05-05 11:20:17 -05:00
selimaj-dev e5cc68ecad updated horns model 2025-05-04 00:58:52 +02:00
612 changed files with 29889 additions and 10455 deletions
-12
View File
@@ -1,12 +0,0 @@
<factorypath>
<factorypathentry kind="EXTJAR" id="/Users/klestiselimaj/.gradle/caches/modules-2/files-2.1/io.github.llamalad7/mixinextras-fabric/0.4.1/8d1a9e96afb990367fa1f904d17580d164da72e3/mixinextras-fabric-0.4.1.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="EXTJAR" id="/Users/klestiselimaj/.gradle/caches/modules-2/files-2.1/org.ow2.asm/asm-analysis/9.7.1/f97a3b319f0ed6a8cd944dc79060d3912a28985f/asm-analysis-9.7.1.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="EXTJAR" id="/Users/klestiselimaj/.gradle/caches/modules-2/files-2.1/net.fabricmc/fabric-mixin-compile-extensions/0.6.0/6fda0c85c419f0a259da017a1424f27507eb30f0/fabric-mixin-compile-extensions-0.6.0.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="EXTJAR" id="/Users/klestiselimaj/.gradle/caches/modules-2/files-2.1/net.fabricmc/mapping-io/0.2.1/2a1aeedce104191bbb9e39e26abfbfeaf9b0590b/mapping-io-0.2.1.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="EXTJAR" id="/Users/klestiselimaj/.gradle/caches/modules-2/files-2.1/org.ow2.asm/asm-tree/9.7.1/3a53139787663b139de76b627fca0084ab60d32c/asm-tree-9.7.1.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="EXTJAR" id="/Users/klestiselimaj/.gradle/caches/fabric-loom/1.21.4/net.fabricmc.yarn.1_21_4.1.21.4+build.7-v2/mappings.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="EXTJAR" id="/Users/klestiselimaj/.gradle/caches/modules-2/files-2.1/net.fabricmc/sponge-mixin/0.15.4+mixin.0.8.7/6a12aacc794f1078458433116e9ed42c1cc98096/sponge-mixin-0.15.4+mixin.0.8.7.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="EXTJAR" id="/Users/klestiselimaj/.gradle/caches/modules-2/files-2.1/org.ow2.asm/asm/9.7.1/f0ed132a49244b042cd0e15702ab9f2ce3cc8436/asm-9.7.1.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="EXTJAR" id="/Users/klestiselimaj/.gradle/caches/modules-2/files-2.1/org.ow2.asm/asm-util/9.7.1/9e23359b598ec6b74b23e53110dd5c577adf2243/asm-util-9.7.1.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="EXTJAR" id="/Users/klestiselimaj/.gradle/caches/modules-2/files-2.1/org.ow2.asm/asm-commons/9.7.1/406c6a2225cfe1819f102a161e54cc16a5c24f75/asm-commons-9.7.1.jar" enabled="true" runInBatchMode="false"/>
</factorypath>
+97
View File
@@ -0,0 +1,97 @@
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"
- name: Set up Java 21
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: 21
cache: gradle
- name: Build all versions
run: |
chmod +x gradlew
# buildAll remaps every versions/* project in parallel into build/allJars.
./gradlew buildAll --no-daemon --stacktrace
- 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
+3
View File
@@ -0,0 +1,3 @@
[submodule "common"]
path = common
url = https://git.selimaj.dev/saturnclientmc/common.git
+1 -1
View File
@@ -1,6 +1,6 @@
# 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 reasons.
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.
| File | License |
| --------------------------------------------------------------------------------------- | ----------------------------------------------------------- |
+60 -8
View File
@@ -1,18 +1,70 @@
<p align="center">
<img src="src/main/resources/assets/saturnclient/icon.png" alt="OSUI" width="150px"/>
<img src="https://raw.github.com/saturnclientmc/common/master/src/main/resources/assets/saturnclient/textures/logo/logo.png" alt="Saturn Client Logo" width="150px"/>
</p>
<h1 align="center">Saturn Client</h1>
<p align="center">
<b>Saturn Client is a free and open source Minecraft client</b>
<b>A free and open-source Minecraft client.</b><br/>
Built with performance, customization, and community in mind.
</p>
# Credits
<p align="center">
<img src="screenshot.png" alt="Saturn Client Screenshot"/>
</p>
- Cloak rendering: `IIpho3nix` & `Dhyan Blum`
- Who Am I: `FokshaWasTaken`
- Panton font (ui): `Ivan Petrov` & `Fontfabric`
- Panorama: https://www.planetminecraft.com/texture-pack/minecraft-cavesand-cliffs-part-ii-main-menu-panorama/
---
If any of the code or assets are in your name and you want to remove them, please submit an issue, we are still in alpha to have any artists so we would also appreciate if you could help us make some cosmetics
## ✨ About
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.
Saturn is currently trying to support multiple versions of minecraft, so please report any issues you are experiencing.
---
## 🚀 Features
- Free cosmetics that don't require real money
- Clean and modern UI with animations
- Ongoing performance improvements
- Open-source and community-driven
---
## 🤝 Contributing
We welcome contributions of all kinds:
- Code improvements
- Bug fixes
- UI enhancements
- Cosmetic designs
- Suggestions and feedback
If you're an artist or designer interested in helping with cosmetics or visual assets, we’d love to collaborate.
---
## 🙌 Credits
- **Cloak Rendering:** `IIpho3nix` & Saturn
- **Who Am I:** `FokshaWasTaken`
- **Panorama:**
https://www.planetminecraft.com/texture-pack/minecraft-cavesand-cliffs-part-ii-main-menu-panorama/
If any code or assets belong to you and you would like them removed, please open an issue.
We aim to properly credit all contributors and respect original creators of their respective art.
---
## 📜 License
Saturn Client is free and open-source using **Apache-2.0**
---
<p align="center">
Made with ❤️ by the Saturn Client team
</p>
+3
View File
@@ -0,0 +1,3 @@
# Version guide
This guide will help you to add or edit minecraft versions.
+55 -71
View File
@@ -1,80 +1,64 @@
plugins {
id 'fabric-loom' version '1.9-SNAPSHOT'
id 'maven-publish'
}
subprojects {
apply plugin: "java"
version = project.mod_version
group = project.maven_group
repositories {
maven { url "https://maven.fabricmc.net/" }
maven { url "https://git.selimaj.dev/api/packages/selimaj-dev/maven" }
mavenCentral()
}
repositories {}
dependencies {
minecraft "com.mojang:minecraft:${project.minecraft_version}"
// Switch back to Yarn
mappings "net.fabricmc:yarn:${project.yarn_mappings}:v2"
modImplementation "net.fabricmc:fabric-loader:${project.loader_version}"
modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}"
modApi "dev.felnull:special-model-loader:1.4.0+pre.1"
include "dev.felnull:special-model-loader:1.4.0+pre.1"
}
repositories {
maven { url "https://maven.parchmentmc.org" }
maven {
name = "FelNull Maven"
url = "https://maven.felnull.dev"
}
}
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
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'
}
}
def targetJavaVersion = 21
tasks.withType(JavaCompile).configureEach {
it.options.encoding = "UTF-8"
if (targetJavaVersion >= 10 || JavaVersion.current().isJava10Compatible()) {
it.options.release.set(targetJavaVersion)
}
}
configure(subprojects.findAll { it.name != "common" }) {
dependencies {
implementation project(":common")
java {
def javaVersion = JavaVersion.toVersion(targetJavaVersion)
if (JavaVersion.current() < javaVersion) {
toolchain.languageVersion = JavaLanguageVersion.of(targetJavaVersion)
}
archivesBaseName = project.archives_base_name
withSourcesJar()
}
jar {
from("LICENSE") {
rename { "${it}_${project.archivesBaseName}"}
}
from("NOTICE") {
rename { "${it}_${project.archivesBaseName}"}
}
}
publishing {
publications {
mavenJava(MavenPublication) {
from components.java
sourceSets {
main {
resources {
srcDir project(":common").file("src/main/resources")
}
}
}
}
}
repositories {}
}
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"
}
}
Submodule
+1
Submodule common added at 5df42e2b7c
File diff suppressed because one or more lines are too long
+5 -15
View File
@@ -1,17 +1,7 @@
# Done to increase the memory available to gradle.
org.gradle.jvmargs=-Xmx1G
org.gradle.jvmargs=-Xmx3G
org.gradle.parallel=true
# Fabric Properties
# check these on https://modmuss50.me/fabric.html
minecraft_version=1.21.4
yarn_mappings=1.21.4+build.7
loader_version=0.16.10
# IntelliJ IDEA is not yet fully compatible with configuration cache, see: https://github.com/FabricMC/fabric-loom/issues/1349
org.gradle.configuration-cache=false
# Mod Properties
mod_version = 0.0.1-alpha2
maven_group = me.iipho3nix
archives_base_name = saturnclient
# Dependencies
# check this on https://modmuss50.me/fabric.html
fabric_version=0.114.0+1.21.4
mod_version=0.1.1-beta
Binary file not shown.
+1 -1
View File
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.0-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
Vendored
+2 -5
View File
@@ -1,7 +1,7 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
# Copyright © 2015 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -114,7 +114,6 @@ case "$( uname )" in #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
@@ -172,7 +171,6 @@ fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
@@ -212,8 +210,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
"$@"
# Stop when "xargs" is not available.
Vendored
+1 -2
View File
@@ -70,11 +70,10 @@ goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
:end
@rem End local scope for the variables with windows NT shell
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

+16 -4
View File
@@ -1,9 +1,21 @@
pluginManagement {
repositories {
maven {
name = 'Fabric'
url = 'https://maven.fabricmc.net/'
}
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
}
@@ -1,11 +0,0 @@
package org.saturnclient.modules;
import net.minecraft.client.gui.DrawContext;
public interface HudMod {
public ModDimensions getDimensions();
public void renderDummy(DrawContext context);
public void render(DrawContext context);
}
@@ -1,64 +0,0 @@
package org.saturnclient.modules;
import org.saturnclient.saturnclient.config.ConfigManager;
import org.saturnclient.saturnclient.config.Property;
public class ModDimensions {
public Property<Integer> x = new Property<>(0);
public Property<Integer> y = new Property<>(0);
public Property<Float> scale = new Property<>(1.0f);
public int width = 0;
public int height = 0;
private final ConfigManager dimensionsConfig;
public ModDimensions(ConfigManager configManager) {
dimensionsConfig = new ConfigManager(configManager, "Dimensions");
dimensionsConfig.property("x", this.x);
dimensionsConfig.property("y", this.y);
dimensionsConfig.property("scale", this.scale);
}
public ModDimensions(ConfigManager configManager, int x, int y) {
dimensionsConfig = new ConfigManager(configManager, "Dimensions");
this.x.value = x;
this.y.value = y;
dimensionsConfig.property("x", this.x);
dimensionsConfig.property("y", this.y);
dimensionsConfig.property("scale", this.scale);
}
public ModDimensions(ConfigManager configManager, int x, int y, int width, int height) {
dimensionsConfig = new ConfigManager(configManager, "Dimensions");
this.x.value = x;
this.y.value = y;
this.width = width;
this.height = height;
dimensionsConfig.property("x", this.x);
dimensionsConfig.property("y", this.y);
dimensionsConfig.property("scale", this.scale);
}
public ModDimensions(ConfigManager configManager, int x, int y, float scale) {
dimensionsConfig = new ConfigManager(configManager, "Dimensions");
this.x.value = x;
this.y.value = y;
this.scale.value = scale;
dimensionsConfig.property("x", this.x);
dimensionsConfig.property("y", this.y);
dimensionsConfig.property("scale", this.scale);
}
public ModDimensions(ConfigManager configManager, int x, int y, int width, int height, float scale) {
dimensionsConfig = new ConfigManager(configManager, "Dimensions");
this.x.value = x;
this.y.value = y;
this.scale.value = scale;
this.width = width;
this.height = height;
dimensionsConfig.property("x", this.x);
dimensionsConfig.property("y", this.y);
dimensionsConfig.property("scale", this.scale);
}
}
@@ -1,58 +0,0 @@
package org.saturnclient.modules;
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
import net.fabricmc.fabric.api.client.rendering.v1.HudRenderCallback;
import net.minecraft.client.font.TextRenderer;
import net.minecraft.client.util.math.MatrixStack;
import org.saturnclient.modules.mods.*;
import org.saturnclient.saturnclient.SaturnClient;
import org.saturnclient.saturnclient.menus.HudEditor;
import org.saturnclient.saturnclient.mixin.DrawContextAccessor;
import org.saturnclient.ui2.RenderScope;
public class ModManager {
public static Module[] MODS = {
new Crosshair(),
new AutoSprint(),
};
public static void init() {
ClientTickEvents.END_CLIENT_TICK.register(client -> {
for (Module m : MODS) {
if (m.isEnabled()) {
m.tick();
}
}
});
HudRenderCallback.EVENT.register((context, _o) -> {
TextRenderer textRenderer = SaturnClient.client.textRenderer;
if (textRenderer != null &&
!(SaturnClient.client.currentScreen instanceof HudEditor)) {
RenderScope renderScope = new RenderScope(context.getMatrices(),
((DrawContextAccessor) context).getVertexConsumers());
for (Module m : MODS) {
if (m instanceof HudMod && m.isEnabled()) {
ModDimensions dim = ((HudMod) m).getDimensions();
MatrixStack matrices = context.getMatrices();
matrices.push();
matrices.translate(dim.x.value, (float) dim.y.value, 0);
matrices.scale(dim.scale.value, dim.scale.value, 1.0f);
((HudMod) m).render(context);
matrices.pop();
} else {
m.render(renderScope);
}
}
}
});
}
}
@@ -1,51 +0,0 @@
package org.saturnclient.modules;
import org.saturnclient.saturnclient.config.ConfigManager;
import org.saturnclient.saturnclient.config.NamedProperty;
import org.saturnclient.ui.Textures;
import org.saturnclient.ui2.RenderScope;
import net.minecraft.util.Identifier;
public abstract class Module {
private final String name;
private final String namespace;
private final ConfigManager configManager;
public Module(String name, String namespace, NamedProperty<?>... props) {
this.name = name;
this.namespace = namespace;
configManager = new ConfigManager(name);
for (NamedProperty<?> prop : props) {
configManager.property(prop.name, prop.prop);
}
}
public Module(ConfigManager config, String name, String namespace) {
this.name = name;
this.namespace = namespace;
configManager = config;
}
public void render(RenderScope scope) {}
public void tick() {}
public abstract boolean isEnabled();
public abstract void setEnabled(boolean e);
public final String getName() {
return name;
}
public Identifier getIconTexture() {
return Textures.getModIcon(namespace);
}
public ConfigManager getConfig() {
return configManager;
}
}
@@ -1,36 +0,0 @@
package org.saturnclient.modules.mods;
import java.util.Objects;
import org.saturnclient.modules.Module;
import org.saturnclient.saturnclient.SaturnClient;
import org.saturnclient.saturnclient.config.Property;
public class AutoSprint extends Module {
private static Property<Boolean> enabled = new Property<>(true);
public AutoSprint() {
super("Auto Sprint", "sprint", enabled.named("Enabled"));
}
@Override
public void tick() {
if (SaturnClient.client.player == null || SaturnClient.client.getNetworkHandler() == null) {
return;
}
if (SaturnClient.client.options.forwardKey.isPressed() && !SaturnClient.client.options.backKey.isPressed() && !SaturnClient.client.player.isSneaking() &&
!SaturnClient.client.player.horizontalCollision) {
Objects.requireNonNull(SaturnClient.client.player).setSprinting(true);
}
}
@Override
public boolean isEnabled() {
return enabled.value;
}
@Override
public void setEnabled(boolean e) {
enabled.value = e;
}
}
@@ -1,44 +0,0 @@
package org.saturnclient.modules.mods;
import org.saturnclient.modules.Module;
import org.saturnclient.saturnclient.SaturnClient;
import org.saturnclient.saturnclient.config.Property;
import org.saturnclient.ui2.RenderScope;
import org.saturnclient.ui2.resources.Textures;
import net.minecraft.client.render.RenderLayer;
public class Crosshair extends Module {
public static Property<Boolean> enabled = new Property<>(false);
public static Property<Boolean> range_indicator = new Property<>(false);
public Crosshair() {
super("Crosshair", "crosshair", enabled.named("Enabled"), range_indicator.named("Range Indicator"));
}
@Override
public void render(RenderScope scope) {
if (enabled.value
&& range_indicator.value
&& SaturnClient.client.targetedEntity != null
&& SaturnClient.client.targetedEntity.isAlive()) {
int scaledWidth = 15;
int scaledHeight = 15;
scope.setRenderLayer(RenderLayer::getCrosshair);
scope.drawTexture(Textures.CROSSHAIR_RANGE,
(scope.getScaledWindowWidth() - scaledWidth) / 2,
(scope.getScaledWindowHeight() - scaledHeight) / 2, 0.0F, 0.0F, scaledWidth, scaledHeight);
}
}
@Override
public boolean isEnabled() {
return enabled.value;
}
@Override
public void setEnabled(boolean e) {
enabled.value = e;
}
}
@@ -1,61 +0,0 @@
package org.saturnclient.saturnclient;
import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents;
import net.fabricmc.fabric.api.client.screen.v1.ScreenEvents;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gui.screen.TitleScreen;
import org.saturnclient.modules.ModManager;
import org.saturnclient.saturnclient.auth.Auth;
import org.saturnclient.saturnclient.config.ConfigManager;
import org.saturnclient.saturnclient.config.ThemeManager;
import org.saturnclient.saturnclient.cosmetics.Hats;
import org.saturnclient.saturnclient.cosmetics.cloaks.Cloaks;
import org.saturnclient.saturnclient.event.KeyInputHandler;
import org.saturnclient.ui2.screens.TitleMenu;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import dev.felnull.specialmodelloader.api.event.SpecialModelLoaderEvents;
/**
* Main class for the Saturn Client mod.
*/
public class SaturnClient implements ModInitializer {
public static final String MOD_ID = "saturnclient";
public static final Logger LOGGER = LoggerFactory.getLogger(MOD_ID);
public static MinecraftClient client;
@Override
public void onInitialize() {
LOGGER.info("Initializing " + MOD_ID);
client = MinecraftClient.getInstance();
ModManager.init();
SaturnClientConfig.init();
ThemeManager.load();
ConfigManager.load();
ClientLifecycleEvents.CLIENT_STOPPING.register(_o -> ConfigManager.save());
ScreenEvents.BEFORE_INIT.register((client, screen, scaledWidth, scaledHeight) -> {
if (SaturnClientConfig.saturnTitleScreen.value && screen instanceof TitleScreen
&& !(screen instanceof TitleMenu)) {
client.setScreen(new TitleMenu());
}
});
SpecialModelLoaderEvents.LOAD_SCOPE.register(() -> {
return (resourceManager, location) -> MOD_ID.equals(location.getNamespace());
});
KeyInputHandler.register();
if (Auth.authenticate()) {
Cloaks.initialize();
Hats.initialize();
LOGGER.info(MOD_ID + " initialization complete");
} else {
LOGGER.error("Failed to authenticate with the server");
}
}
}
@@ -1,80 +0,0 @@
package org.saturnclient.saturnclient;
import org.saturnclient.saturnclient.config.Property;
import org.saturnclient.saturnclient.config.ConfigManager;
import org.saturnclient.ui.SaturnAnimation;
import org.saturnclient.ui.SaturnUi;
import org.saturnclient.ui.Textures;
import org.saturnclient.ui.animations.FadeIn;
import net.minecraft.util.Formatting;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.ColorHelper;
public class SaturnClientConfig {
public static ConfigManager config;
public static Property<Integer> color =
new Property<>(
ColorHelper.getArgb(255, 251, 60, 79),
Property.PropertyType.HEX);
public static Property<Integer> normal =
new Property<>(
ColorHelper.getArgb(255, 182, 182, 182),
Property.PropertyType.HEX);
public static Property<Boolean> realisticLogo =
new Property<>(false);
public static Property<Boolean> saturnTitleScreen =
new Property<>(true);
public static int WHITE = ColorHelper.getArgb(255, 255, 255, 255);
public static int getWhite(float alpha) {
return ((int) (alpha * 255) << 24) | (normal.value & 0x00FFFFFF);
}
public static int getColor(boolean hovering, float alpha) {
return SaturnUi.getAlpha(hovering ? color.value : normal.value, alpha);
}
public static SaturnAnimation[] getAnimations() {
return new SaturnAnimation[] { new FadeIn(1) };
}
public static Identifier getLogo() {
return realisticLogo.value ? Textures.REALISTIC_LOGO : Textures.LOGO;
}
public static String getSaturnIndicator() {
return realisticLogo.value ? "" : "";
}
/*
* Gets the icon color of a specific individual, here are the different colors
*
* - Owner: Dark Red
* - Admin: Red
* - Partners: Gold
* - Contributor: Aqua
* - Other/player: White
*/
public static Formatting getIconColor(String uuid) {
if (uuid.equals("d362a04228bd49e1a807ae74dbe8aba9")) {
return Formatting.DARK_RED; // Owner
}
return Formatting.WHITE;
}
public static void init() {
config = new ConfigManager("Saturn Client");
config.property("Primary color", color);
config.property("Default color", normal);
config.property("Realistic logo", realisticLogo);
config.property("Saturn client title screen", saturnTitleScreen);
}
}
@@ -1,197 +0,0 @@
package org.saturnclient.saturnclient.auth;
import java.util.HashMap;
import java.util.Map;
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents;
import org.saturnclient.saturnclient.cosmetics.Hats;
import org.saturnclient.saturnclient.cosmetics.cloaks.Cloaks;
import org.saturnclient.saturnclient.SaturnClient;
public class Auth {
public static Map<String, SaturnPlayer> players = new HashMap<>();
public static Map<String, String> playerNames = new HashMap<>();
public static String uuid;
private static Thread listenerThread;
private static Thread pingThread;
private static volatile boolean running = false;
@SuppressWarnings("resource")
public static boolean authenticate() {
// Register shutdown hook ONCE
ClientLifecycleEvents.CLIENT_STOPPING.register(_o -> close());
try {
if (SaturnClient.client.getSession() == null) {
SaturnClient.LOGGER.error("No active Minecraft session found");
return false;
}
String accessToken = SaturnClient.client.getSession().getAccessToken();
SaturnClient.LOGGER.info("Authenticating");
Network.init();
Network.write(accessToken);
String response = Network.read();
SaturnParser parser = new SaturnParser(response);
uuid = parser.getString("uuid");
String cloak = parser.getString("cloak");
String hat = parser.getString("hat");
for (String availableCloak : parser.getArray("cloaks")) {
Cloaks.availableCloaks.add(availableCloak);
}
for (String availableHat : parser.getArray("hats")) {
Hats.availableHats.add(availableHat);
}
players.put(uuid, new SaturnPlayer(cloak, hat));
Cloaks.loadCloak(uuid);
playerNames.put(SaturnClient.client.getSession().getUsername(), uuid);
afterAuth();
startListenerThread();
startPingThread();
return true;
} catch (Exception e) {
SaturnClient.LOGGER.error("Authentication failed", e);
return false;
}
}
private static void startListenerThread() {
running = true;
listenerThread = new Thread(() -> {
try {
while (running) {
String message = Network.read();
if (message == null) {
SaturnClient.LOGGER.error("Connection closed");
running = false;
break;
}
SaturnClient.LOGGER.info("Received: " + message);
SaturnParser parser = new SaturnParser(message);
switch (parser.method) {
case "player":
String uuid = parser.getString("uuid");
if (parser.getBool("saturn")) {
String cloak = parser.getString("cloak");
String hat = parser.getString("hat");
players.put(uuid, new SaturnPlayer(cloak, hat));
Cloaks.loadCloak(uuid);
} else {
playerNames.entrySet().removeIf(entry -> entry.getValue().equals(uuid));
}
break;
default:
if (parser.error != null) {
SaturnClient.LOGGER.error("Error from the server: " + parser.error);
}
break;
}
}
} catch (Exception e) {
SaturnClient.LOGGER.error("Error reading from server", e);
} finally {
close();
}
});
listenerThread.setDaemon(true);
listenerThread.start();
}
private static void startPingThread() {
pingThread = new Thread(() -> {
try {
while (running && !Thread.currentThread().isInterrupted()) {
try {
Thread.sleep(25000); // 25 seconds
if (running) {
Network.write("ping");
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // Restore interrupt status
break; // Exit the loop on interruption
}
}
} catch (Exception e) {
if (!(e instanceof InterruptedException)) {
SaturnClient.LOGGER.error("Error in ping thread", e);
}
}
});
pingThread.setDaemon(true);
pingThread.start();
}
public static void close() {
running = false;
if (pingThread != null) {
pingThread.interrupt();
}
if (listenerThread != null) {
listenerThread.interrupt();
}
try {
close();
SaturnClient.LOGGER.info("SaturnApi connection closed.");
} catch (Exception e) {
SaturnClient.LOGGER.error("Failed to close resources", e);
}
}
public static void afterAuth() {
PlayerTracker.initialize();
}
public static void setCloak(String cloakName) {
try {
Network.write("set_cloak@cloak=" + cloakName);
} catch (Exception e) {
SaturnClient.LOGGER.error("Request failed", e);
}
}
public static void setHat(String hatName) {
try {
Network.write("set_hat@hat=" + hatName);
} catch (Exception e) {
SaturnClient.LOGGER.error("Request failed", e);
}
}
public static void player(String name, String uuid) {
try {
playerNames.put(name, uuid.replaceAll("-", ""));
Network.write("player@uuid=" + uuid.replaceAll("-", ""));
} catch (Exception e) {
SaturnClient.LOGGER.error("Request failed", e);
}
}
public static void sendReload() {
for (Map.Entry<String, String> player : playerNames.entrySet()) {
if (players.containsKey(player.getValue()) && !player.getValue().equals(uuid)) {
SaturnClient.client.player.networkHandler
.sendChatCommand("msg " + player.getKey() + " "
+ "$SATURN_RELOAD if you are seeing this as a player, please report this to https://github.com/saturnclientmc/saturnclient/issues");
;
}
}
}
}
@@ -1,98 +0,0 @@
package org.saturnclient.saturnclient.auth;
import java.io.*;
import java.net.*;
import java.security.*;
import java.util.Base64;
import javax.crypto.*;
import javax.crypto.spec.*;
import java.security.interfaces.*;
import java.security.spec.X509EncodedKeySpec;
public class Network {
static Socket socket;
static PrintWriter out;
static BufferedReader in;
static byte[] key = new byte[32];
public static class AES {
public static byte[] encryptAES256(String message, byte[] key) throws Exception {
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
byte[] encryptedMessage = cipher.doFinal(message.getBytes());
return encryptedMessage;
}
public static String decryptAES256(byte[] encryptedMessage, byte[] key) throws Exception {
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec);
byte[] decryptedMessage = cipher.doFinal(encryptedMessage);
return new String(decryptedMessage);
}
}
public static void init() throws Exception {
// Generate AES key
new SecureRandom().nextBytes(key);
socket = new Socket("77.247.92.168", 8080);
// out = new DataOutputStream(socket.getOutputStream());
out = new PrintWriter(socket.getOutputStream(), true);
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
StringBuilder publicKeyBuilder = new StringBuilder();
String line;
while (!(line = in.readLine()).contains("-----END PUBLIC KEY-----")) {
publicKeyBuilder.append(line).append("\n");
}
publicKeyBuilder.append(line);
String publicKeyPem = publicKeyBuilder.toString();
String publicKeyBase64 = publicKeyPem.replace("-----BEGIN PUBLIC KEY-----", "")
.replace("-----END PUBLIC KEY-----", "").replace("\n", "").replace("\r", "");
byte[] publicKeyBytes = Base64.getDecoder().decode(publicKeyBase64);
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKeyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
RSAPublicKey publicKey = (RSAPublicKey) keyFactory.generatePublic(keySpec);
Cipher rsaCipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
rsaCipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] encryptedKey = rsaCipher.doFinal(key);
out.println(Base64.getEncoder().encodeToString(encryptedKey));
}
public static void write(String message) {
try {
out.println(Base64.getEncoder().encodeToString(AES.encryptAES256(message, key)));
} catch (Exception e) {
throw new RuntimeException("Failed to write to server: " + e.getMessage());
}
}
public static String read() {
try {
String msg = in.readLine();
if (msg != null) {
return AES.decryptAES256(Base64.getDecoder().decode(msg), key);
} else {
return "";
}
} catch (Exception e) {
return "";
}
}
public static void close() {
try {
socket.close();
out.close();
in.close();
} catch (Exception e) {
}
}
}
@@ -1,52 +0,0 @@
package org.saturnclient.saturnclient.auth;
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents;
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
import org.saturnclient.saturnclient.SaturnClient;
import net.minecraft.client.network.PlayerListEntry;
import net.minecraft.client.MinecraftClient;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import com.mojang.authlib.GameProfile;
public class PlayerTracker {
private static Set<UUID> trackedPlayers = new HashSet<>();
public static void initialize() {
// When client joins a server, reset tracked players
ClientPlayConnectionEvents.JOIN.register((handler, sender, client) -> {
trackedPlayers.clear();
client.execute(() -> {
checkForNewPlayers(SaturnClient.client);
});
});
// Check for new players every tick
ClientTickEvents.END_CLIENT_TICK.register(client -> checkForNewPlayers(client));
}
private static void checkForNewPlayers(MinecraftClient client) {
if (client.getNetworkHandler() == null)
return;
for (PlayerListEntry player : client.getNetworkHandler().getPlayerList()) {
UUID playerUUID = player.getProfile().getId();
if (!trackedPlayers.contains(playerUUID)) {
// A new player has joined
onPlayerJoin(client, player);
trackedPlayers.add(playerUUID);
}
}
}
private static void onPlayerJoin(MinecraftClient client, PlayerListEntry player) {
GameProfile profile = player.getProfile();
String uuid = profile.getId().toString();
if (uuid != Auth.uuid)
Auth.player(profile.getName(), uuid);
}
}
@@ -1,66 +0,0 @@
package org.saturnclient.saturnclient.auth;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
public class SaturnParser {
public String method = "";
public String error;
private Map<String, String> params = new HashMap<>();
public SaturnParser(String input) throws IOException {
String[] parts = input.split("@");
if (parts.length == 0) {
throw new IOException("Invalid input string");
}
if (parts[0].startsWith("!")) {
error = input;
}
method = parts[0];
for (int i = 1; i < parts.length; i++) {
String[] keyValue = parts[i].split("=");
if (keyValue.length == 2) {
params.put(keyValue[0].trim(), keyValue[1].trim());
} else if (keyValue.length == 1) {
params.put(keyValue[0].trim(), "");
} else {
throw new IOException("Invalid parameter format: " + parts[i]);
}
}
}
public String getString(String i) throws IOException {
String val = params.get(i);
if (val != null) {
return val;
} else {
throw new IOException("Parameter not found " + i);
}
}
public boolean getBool(String i) throws IOException {
String val = params.get(i);
if (val != null) {
return val.equals("true");
} else {
throw new IOException("Parameter not found " + i);
}
}
public String[] getArray(String i) throws IOException {
String val = params.get(i);
if (val != null) {
if (val.isEmpty()) {
return new String[] {};
}
return val.split("\\$");
} else {
throw new IOException("Parameter not found " + i);
}
}
}
@@ -1,11 +0,0 @@
package org.saturnclient.saturnclient.auth;
public class SaturnPlayer {
public String cloak = "";
public String hat = "";
public SaturnPlayer(String cloak, String hat) {
this.cloak = cloak;
this.hat = hat;
}
}
@@ -1,205 +0,0 @@
package org.saturnclient.saturnclient.config;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.JsonPrimitive;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import org.saturnclient.saturnclient.SaturnClient;
public class ConfigManager {
private static File configFile = new File(
SaturnClient.client.runDirectory,
"saturn.json");
private static Map<String, Map<String, Property<?>>> properties = new HashMap<>();
private Map<String, Property<?>> currentMap;
public ConfigManager(String namespace) {
currentMap = new LinkedHashMap<>();
properties.put(namespace, currentMap);
System.out.println("Created namespace: " + namespace);
}
public ConfigManager(ConfigManager config, String namespace) {
currentMap = new LinkedHashMap<>();
Property<Map<String, Property<?>>> namespaceProperty = new Property<>(currentMap,
Property.PropertyType.NAMESPACE);
config.property(namespace, namespaceProperty);
// Update the parent's state
config.currentMap.put(namespace, namespaceProperty);
System.out.println("Created sub-namespace: " + namespace);
}
// Generic method to store any type of property
public <T> Property<T> property(String name, Property<T> value) {
System.out.println("Adding property: " + name);
currentMap.put(name, value);
return value;
}
public static void load() {
try {
SaturnClient.LOGGER.info("Starting to load config...");
if (!configFile.exists()) {
configFile.createNewFile();
Files.write(configFile.toPath(), "{}".getBytes());
}
JsonObject jsonObject = JsonParser.parseString(
new String(Files.readAllBytes(configFile.toPath()))).getAsJsonObject();
for (String namespace : properties.keySet()) {
SaturnClient.LOGGER.info("Loading namespace: " + namespace);
JsonElement configElement = jsonObject.get(namespace);
if (configElement == null)
continue;
JsonObject config = jsonObject.get(namespace).getAsJsonObject();
if (config == null) {
continue;
}
Map<String, Property<?>> propertyMap = properties.get(namespace);
loadProperties(config, propertyMap);
}
} catch (IOException e) {
SaturnClient.LOGGER.error("Error reading the config file", e);
}
}
@SuppressWarnings("unchecked")
private static void loadProperties(JsonObject config, Map<String, Property<?>> propertyMap) {
SaturnClient.LOGGER.info("Loading properties: " + propertyMap);
for (String propertyName : propertyMap.keySet()) {
JsonElement c = config.get(propertyName);
if (c == null) {
continue;
}
Property<?> p = propertyMap.get(propertyName);
if (p.matchesJson(c)) {
if (p.getType() == Property.PropertyType.NAMESPACE) {
// Handle nested namespace
JsonObject nestedConfig = c.getAsJsonObject();
Map<String, Property<?>> nestedProperties = p.getNamespaceValue();
loadProperties(nestedConfig, nestedProperties);
} else {
// Handle regular properties
switch (p.getType()) {
case BOOLEAN:
((Property<Boolean>) p).value = c.getAsBoolean();
break;
case INTEGER:
((Property<Integer>) p).value = c.getAsInt();
break;
case FLOAT:
((Property<Float>) p).value = c.getAsFloat();
break;
case STRING:
((Property<String>) p).value = c.getAsString();
break;
case HEX:
((Property<Integer>) p).value = c.getAsInt();
break;
default:
break;
}
}
} else {
SaturnClient.LOGGER.warn(
"Property does not match JSON: " + propertyName);
}
}
}
public static void save() {
try {
SaturnClient.LOGGER.info("Starting to save config...");
JsonObject jsonObject = new JsonObject();
// Iterate through all namespaces and their properties
for (String namespace : properties.keySet()) {
JsonObject namespaceConfig = new JsonObject();
Map<String, Property<?>> propertyMap = properties.get(namespace);
// Save properties for this namespace
saveProperties(namespaceConfig, propertyMap);
// Add the namespace config to the main JSON object
jsonObject.add(namespace, namespaceConfig);
}
// Format the json so it can easily be edited
Gson gson = new GsonBuilder().setPrettyPrinting().create();
String formattedJson = gson.toJson(jsonObject);
// Write the JSON object to the config file
Files.write(configFile.toPath(), formattedJson.getBytes());
SaturnClient.LOGGER.info("Config saved successfully.");
} catch (IOException e) {
SaturnClient.LOGGER.error("Error saving the config file", e);
}
}
@SuppressWarnings("unchecked")
private static void saveProperties(JsonObject config, Map<String, Property<?>> propertyMap) {
// Iterate through each property in the namespace
for (String propertyName : propertyMap.keySet()) {
Property<?> property = propertyMap.get(propertyName);
JsonElement propertyValue = null;
if (property.getType() == Property.PropertyType.NAMESPACE) {
// Handle nested namespace
JsonObject nestedConfig = new JsonObject();
Map<String, Property<?>> nestedProperties = (Map<String, Property<?>>) property.value;
saveProperties(nestedConfig, nestedProperties);
propertyValue = nestedConfig;
} else {
// Convert the property value based on its type
switch (property.getType()) {
case BOOLEAN:
propertyValue = new JsonPrimitive((Boolean) property.value);
break;
case INTEGER:
propertyValue = new JsonPrimitive((Integer) property.value);
break;
case FLOAT:
propertyValue = new JsonPrimitive((Float) property.value);
break;
case STRING:
propertyValue = new JsonPrimitive((String) property.value);
break;
case HEX:
propertyValue = new JsonPrimitive((Integer) property.value);
break;
default:
SaturnClient.LOGGER.warn(
"Unknown property type for: " + propertyName);
break;
}
}
if (propertyValue != null) {
config.add(propertyName, propertyValue);
}
}
}
public Map<String, Property<?>> getProperties() {
return currentMap;
}
}
@@ -1,11 +0,0 @@
package org.saturnclient.saturnclient.config;
public class NamedProperty<T> {
public String name;
public Property<T> prop;
public NamedProperty(String name, Property<T> value) {
this.name = name;
this.prop = value;
}
}
@@ -1,157 +0,0 @@
package org.saturnclient.saturnclient.config;
import java.util.Map;
import com.google.gson.JsonElement;
import com.google.gson.JsonPrimitive;
public class Property<T> {
public enum PropertyType {
BOOLEAN,
INTEGER,
FLOAT,
STRING,
HEX,
NAMESPACE
}
public T value;
public boolean isReset = false;
private T defaultValue;
private PropertyType type;
public Property(T value) {
this.value = value;
this.defaultValue = value;
if (value instanceof Boolean) {
type = PropertyType.BOOLEAN;
} else if (value instanceof Integer) {
type = PropertyType.INTEGER;
} else if (value instanceof Float) {
type = PropertyType.FLOAT;
} else if (value instanceof String) {
type = PropertyType.STRING;
} else if (valueIsNamespace(value)) {
type = PropertyType.NAMESPACE;
}
}
public Property(T value, PropertyType type) {
this.value = value;
this.defaultValue = value;
this.type = type;
}
public boolean getBool() {
if (value instanceof Boolean) {
return (boolean) value;
}
throw new IllegalStateException("Property does not contain a boolean");
}
public int getInt() {
if (value instanceof Integer) {
return (int) value;
}
throw new IllegalStateException("Property does not contain an int");
}
public float getFloat() {
if (value instanceof Float) {
return (float) value;
}
throw new IllegalStateException("Property does not contain a float");
}
public String getString() {
if (value instanceof String) {
return (String) value;
}
throw new IllegalStateException("Property does not contain a string");
}
public void reset() {
value = defaultValue;
isReset = true;
}
public int getHexString() {
if (value instanceof String) {
String str = (String) value;
if (str.startsWith("0x") || str.startsWith("0X")) {
try {
return Integer.parseInt(str.substring(2), 16);
} catch (NumberFormatException e) {
throw new IllegalStateException(
"Invalid hexadecimal integer format");
}
}
}
throw new IllegalStateException(
"Property does not contain a valid hex integer");
}
public PropertyType getType() {
return type;
}
@SuppressWarnings("unchecked")
public Map<String, Property<?>> getNamespaceValue() {
if (type == PropertyType.NAMESPACE && value instanceof Map) {
return (Map<String, Property<?>>) value;
}
throw new IllegalStateException("Property is not a namespace");
}
public boolean matchesJson(JsonElement element) {
if (element.isJsonPrimitive()) {
JsonPrimitive primitive = element.getAsJsonPrimitive();
if (value instanceof Boolean && primitive.isBoolean())
return true;
if (value instanceof Integer && primitive.isNumber())
return true;
if (value instanceof Float && primitive.isNumber())
return true;
if (value instanceof String && primitive.isString())
return true;
// Check if JSON string represents a valid hex integer
if (value instanceof String && primitive.isString()) {
String str = primitive.getAsString();
return str.matches("0x[0-9A-Fa-f]+");
}
} else if (element.isJsonObject()) {
return isNamespace(value);
}
return false;
}
@Override
public String toString() {
return String.valueOf(value);
}
public boolean isNamespace(Object obj) {
return type == PropertyType.NAMESPACE && value instanceof Map;
}
public static boolean valueIsNamespace(Object obj) {
return obj instanceof Map;
}
@SuppressWarnings("unchecked")
public void setValue(Object value) {
this.value = (T) value;
}
@SuppressWarnings("unchecked")
public void setValue(Object value, Object defaultValue) {
this.value = (T) value;
this.defaultValue = (T) defaultValue;
}
public NamedProperty<T> named(String name) {
return new NamedProperty<>(name, this);
}
}
@@ -1,224 +0,0 @@
package org.saturnclient.saturnclient.config;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import org.saturnclient.saturnclient.SaturnClient;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.JsonPrimitive;
public class ThemeManager {
private static File themeFile = new File(
SaturnClient.client.runDirectory,
"saturn.theme.json");
private static Map<String, Map<String, Property<?>>> properties = new HashMap<>();
// private Map<String, Map<String, Property<?>>> states;
private Map<String, Property<?>> currentStyling;
private String state;
private String[] states;
private String namespace;
public ThemeManager(String namespace, String... states) {
// this.states = new LinkedHashMap<>();
this.namespace = namespace;
this.states = states;
currentStyling = new LinkedHashMap<>();
properties.put(namespace, new LinkedHashMap<>());
for (String s : states) {
properties.put(namespace+"@"+s, new LinkedHashMap<>());
}
}
public void setState(String state) {
this.state = state;
if (this.state != null) {
for (Map.Entry<String, Property<?>> tProp : properties.get(namespace+"@"+state).entrySet()) {
currentStyling.get(tProp.getKey()).setValue(tProp.getValue().value);
}
} else {
for (Map.Entry<String, Property<?>> tProp : properties.get(namespace).entrySet()) {
currentStyling.get(tProp.getKey()).setValue(tProp.getValue().value);
}
}
}
// Generic method to store any type of property
public <T> Property<T> property(String name, Property<T> prop) {
for (String state : states) {
properties.get(namespace+"@"+state).put(name, new Property<T>(prop.value));
}
properties.get(namespace).put(name, new Property<T>(prop.value));
currentStyling.put(name, prop);
return prop;
}
public <T> void propertyStateDefault(String state, String name, T value) {
properties.get(namespace+"@"+state).get(name).setValue(value, value); // sets the current and default value
}
public static void load() {
try {
SaturnClient.LOGGER.info("Starting to load theme...");
if (!themeFile.exists()) {
themeFile.createNewFile();
Files.write(themeFile.toPath(), "{}".getBytes());
}
JsonObject jsonObject = JsonParser.parseString(
new String(Files.readAllBytes(themeFile.toPath()))).getAsJsonObject();
for (String namespace : properties.keySet()) {
JsonElement themeElement = jsonObject.get(namespace);
if (themeElement == null)
continue;
JsonObject theme = jsonObject.get(namespace).getAsJsonObject();
if (theme == null) {
continue;
}
Map<String, Property<?>> propertyMap = properties.get(namespace);
loadProperties(theme, propertyMap);
}
} catch (IOException e) {
SaturnClient.LOGGER.error("Error reading the theme file", e);
}
}
@SuppressWarnings("unchecked")
private static void loadProperties(JsonObject theme, Map<String, Property<?>> propertyMap) {
for (String propertyName : propertyMap.keySet()) {
JsonElement c = theme.get(propertyName);
if (c == null) {
continue;
}
Property<?> p = propertyMap.get(propertyName);
if (p.matchesJson(c)) {
if (p.getType() == Property.PropertyType.NAMESPACE) {
// Handle nested namespace
JsonObject nestedTheme = c.getAsJsonObject();
Map<String, Property<?>> nestedProperties = p.getNamespaceValue();
loadProperties(nestedTheme, nestedProperties);
} else {
// Handle regular properties
switch (p.getType()) {
case BOOLEAN:
((Property<Boolean>) p).value = c.getAsBoolean();
break;
case INTEGER:
((Property<Integer>) p).value = c.getAsInt();
break;
case FLOAT:
((Property<Float>) p).value = c.getAsFloat();
break;
case STRING:
((Property<String>) p).value = c.getAsString();
break;
case HEX:
((Property<Integer>) p).value = c.getAsInt();
break;
default:
break;
}
}
} else {
SaturnClient.LOGGER.warn(
"Property does not match JSON: " + propertyName);
}
}
}
public static void save() {
try {
SaturnClient.LOGGER.info("Starting to save theme...");
JsonObject jsonObject = new JsonObject();
// Iterate through all namespaces and their properties
for (String namespace : properties.keySet()) {
JsonObject namespaceTheme = new JsonObject();
Map<String, Property<?>> propertyMap = properties.get(namespace);
// Save properties for this namespace
saveProperties(namespaceTheme, propertyMap);
// Add the namespace theme to the main JSON object
jsonObject.add(namespace, namespaceTheme);
}
// Format the json so it can easily be edited
Gson gson = new GsonBuilder().setPrettyPrinting().create();
String formattedJson = gson.toJson(jsonObject);
// Write the JSON object to the theme file
Files.write(themeFile.toPath(), formattedJson.getBytes());
SaturnClient.LOGGER.info("Theme saved successfully.");
} catch (IOException e) {
SaturnClient.LOGGER.error("Error saving the theme file", e);
}
}
@SuppressWarnings("unchecked")
private static void saveProperties(JsonObject theme, Map<String, Property<?>> propertyMap) {
// Iterate through each property in the namespace
for (String propertyName : propertyMap.keySet()) {
Property<?> property = propertyMap.get(propertyName);
JsonElement propertyValue = null;
if (property.getType() == Property.PropertyType.NAMESPACE) {
// Handle nested namespace
JsonObject nestedTheme = new JsonObject();
Map<String, Property<?>> nestedProperties = (Map<String, Property<?>>) property.value;
saveProperties(nestedTheme, nestedProperties);
propertyValue = nestedTheme;
} else {
// Convert the property value based on its type
switch (property.getType()) {
case BOOLEAN:
propertyValue = new JsonPrimitive((Boolean) property.value);
break;
case INTEGER:
propertyValue = new JsonPrimitive((Integer) property.value);
break;
case FLOAT:
propertyValue = new JsonPrimitive((Float) property.value);
break;
case STRING:
propertyValue = new JsonPrimitive((String) property.value);
break;
case HEX:
propertyValue = new JsonPrimitive((Integer) property.value);
break;
default:
SaturnClient.LOGGER.warn(
"Unknown property type for: " + propertyName);
break;
}
}
if (propertyValue != null) {
theme.add(propertyName, propertyValue);
}
}
}
}
@@ -1,84 +0,0 @@
package org.saturnclient.saturnclient.cosmetics;
import org.saturnclient.saturnclient.auth.Auth;
import org.saturnclient.saturnclient.cosmetics.cloaks.Cloaks;
import net.minecraft.client.render.OverlayTexture;
import net.minecraft.client.render.RenderLayer;
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.BipedEntityModel;
import net.minecraft.client.render.entity.model.EntityModelLayers;
import net.minecraft.client.render.entity.model.LoadedEntityModels;
import net.minecraft.client.render.entity.model.PlayerCapeModel;
import net.minecraft.client.render.entity.model.PlayerEntityModel;
import net.minecraft.client.render.entity.state.PlayerEntityRenderState;
import net.minecraft.client.util.SkinTextures;
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.Identifier;
public class CloakFeatureRenderer extends FeatureRenderer<PlayerEntityRenderState, PlayerEntityModel> {
private final BipedEntityModel<PlayerEntityRenderState> model;
private final EquipmentModelLoader equipmentModelLoader;
public CloakFeatureRenderer(FeatureRendererContext<PlayerEntityRenderState, PlayerEntityModel> context,
LoadedEntityModels modelLoader, EquipmentModelLoader equipmentModelLoader) {
super(context);
this.model = new PlayerCapeModel<PlayerEntityRenderState>(
modelLoader.getModelPart(EntityModelLayers.PLAYER_CAPE));
this.equipmentModelLoader = equipmentModelLoader;
}
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;
}
}
public void render(MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, int i,
PlayerEntityRenderState playerEntityRenderState, float f, float g) {
if (!playerEntityRenderState.invisible && playerEntityRenderState.capeVisible) {
SkinTextures skinTextures = playerEntityRenderState.skinTextures;
if (skinTextures.capeTexture() == null) {
String name = playerEntityRenderState.name;
String uuid = Auth.playerNames.get(name);
if (uuid == null || !Auth.players.containsKey(uuid)) {
return;
}
Identifier customCape = Cloaks.getCurrentCloakTexture(uuid);
if (customCape != null
&& !this.hasCustomModelForLayer(playerEntityRenderState.equippedChestStack, LayerType.WINGS)) {
matrixStack.push();
if (this.hasCustomModelForLayer(playerEntityRenderState.equippedChestStack, LayerType.HUMANOID)) {
matrixStack.translate(0.0F, -0.053125F, 0.06875F);
}
VertexConsumer vertexConsumer = vertexConsumerProvider
.getBuffer(RenderLayer.getEntityAlpha(customCape));
((PlayerEntityModel) this.getContextModel()).copyTransforms(this.model);
this.model.setAngles(playerEntityRenderState);
this.model.render(matrixStack, vertexConsumer, 0xF000F0, OverlayTexture.DEFAULT_UV);
matrixStack.pop();
}
}
}
}
}
@@ -1,66 +0,0 @@
package org.saturnclient.saturnclient.cosmetics;
import org.saturnclient.saturnclient.auth.SaturnPlayer;
import org.saturnclient.saturnclient.auth.Auth;
import org.saturnclient.saturnclient.SaturnClient;
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.render.item.ItemRenderer;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.component.DataComponentTypes;
import net.minecraft.component.type.EquippableComponent;
import net.minecraft.entity.EquipmentSlot;
import net.minecraft.item.ItemStack;
import net.minecraft.item.Items;
import net.minecraft.item.ModelTransformationMode;
import net.minecraft.util.Identifier;
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) {
ItemStack headItem = state.equippedHeadStack;
String uuid = Auth.playerNames.get(state.name);
if (headItem.isEmpty() && uuid != null) {
SaturnPlayer player = Auth.players.get(uuid);
if (player == null || player.hat == null || player.hat.isEmpty()) {
return;
}
matrices.push();
this.getContextModel().head.rotate(matrices);
matrices.multiply(net.minecraft.util.math.RotationAxis.POSITIVE_X.rotationDegrees(180.0f));
matrices.multiply(net.minecraft.util.math.RotationAxis.POSITIVE_Y.rotationDegrees(180.0f));
ItemStack customHat = new ItemStack(Items.STICK);
customHat.set(DataComponentTypes.ITEM_MODEL,
Identifier.of("saturnclient:hat_" + player.hat));
customHat.set(DataComponentTypes.EQUIPPABLE,
EquippableComponent.builder(EquipmentSlot.HEAD).build());
ItemRenderer itemRenderer = SaturnClient.client.getItemRenderer();
itemRenderer.renderItem(
customHat,
ModelTransformationMode.THIRD_PERSON_RIGHT_HAND,
light,
OverlayTexture.DEFAULT_UV,
matrices,
vertexConsumers,
null,
0);
matrices.pop();
}
}
}
@@ -1,29 +0,0 @@
package org.saturnclient.saturnclient.cosmetics;
import java.util.ArrayList;
import java.util.List;
import org.saturnclient.saturnclient.auth.SaturnPlayer;
import org.saturnclient.saturnclient.auth.Auth;
public class Hats {
public static List<String> availableHats = new ArrayList<>();
public static void initialize() {
availableHats.add(0, "");
}
public static void setHat(String uuid, String hatName) {
setHatSilent(uuid, hatName);
Auth.setHat(hatName);
Auth.sendReload();
}
public static void setHatSilent(String uuid, String hatName) {
SaturnPlayer player = Auth.players.get(uuid);
if (player == null) {
Auth.players.put(uuid, new SaturnPlayer("", hatName));
} else {
player.hat = hatName;
}
}
}
@@ -1,196 +0,0 @@
package org.saturnclient.saturnclient.cosmetics.cloaks;
import net.minecraft.util.Identifier;
import org.saturnclient.saturnclient.auth.Auth;
import org.saturnclient.saturnclient.cosmetics.cloaks.utils.AnimatedCloakData;
import org.saturnclient.saturnclient.cosmetics.cloaks.utils.IdentifierUtils;
import org.saturnclient.saturnclient.auth.SaturnPlayer;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import org.saturnclient.saturnclient.SaturnClient;
/**
* Manages the cloak system for Saturn Client.
* Handles loading and caching of player cloaks.
* Originally created by IIpho3nix and modified for Saturn Client by leo.
*/
public class Cloaks {
private static final String[] ANIMATED_CLOAKS = { "glitch" };
private static final String CLOAKS_RESOURCE_PATH = "assets/saturnclient/textures/cloaks/";
public static final List<String> availableCloaks = new ArrayList<>();
public static Identifier cloakCacheIdentifier = null;
public static final Map<String, List<AnimatedCloakData>> animatedCloaks = new ConcurrentHashMap<>();
private static final Map<String, Long> lastFrameTime = new ConcurrentHashMap<>();
/**
* Initializes the cloak system.
* Loads cloak textures from resources.
*/
public static void initialize() {
availableCloaks.add(0, "");
}
/**
* Handles loading and caching of a new cloak texture.
*
* @param cloakName Name of the cloak file to load
*/
public static void setCloak(String uuid, String cloakName) {
Auth.setCloak(cloakName);
setCloakSilent(uuid, cloakName);
Auth.sendReload();
SaturnClient.LOGGER.info("Cloak set to " + cloakName);
}
/**
* Handles loading and caching of a new cloak texture, without connecting to the
* server.
*
* @param cloakName Name of the cloak file to load
*/
public static void setCloakSilent(String uuid, String cloakName) {
SaturnPlayer player = Auth.players.get(uuid);
if (player == null) {
Auth.players.put(uuid, new SaturnPlayer(cloakName, null));
} else {
player.cloak = cloakName;
}
loadCloak(uuid);
}
/**
* Handles loading and caching of a new cloak texture, without connecting to the
* server.
*
* @param cloakName Name of the cloak file to load
*/
public static void loadCloak(String uuid) {
SaturnPlayer player = Auth.players.get(uuid);
if (player != null && player.cloak != null) {
if (!player.cloak.isEmpty()) {
if (Arrays.asList(ANIMATED_CLOAKS).contains(player.cloak)) {
// Load animated cloak data in background thread
new Thread(() -> {
try {
String fileName = player.cloak + ".gif";
String resourcePath = CLOAKS_RESOURCE_PATH + fileName;
InputStream inputStream = Cloaks.class.getClassLoader().getResourceAsStream(resourcePath);
if (inputStream != null) {
byte[] data = inputStream.readAllBytes();
GifDecoder.GifImage gif = GifDecoder.read(data);
List<BufferedImage> frames = new ArrayList<>();
List<Integer> delays = new ArrayList<>();
int frameCount = gif.getFrameCount();
for (int i = 0; i < frameCount; i++) {
frames.add(gif.getFrame(i));
delays.add(gif.getDelay(i) * 10);
}
// Register textures on main thread
SaturnClient.client.execute(() -> {
try {
List<AnimatedCloakData> animatedFrames = new ArrayList<>();
for (int i = 0; i < frameCount; i++) {
String frameId = fileName.replace(".gif", "") + "_frame_" + i;
Identifier frameIdentifier = Identifier.of(SaturnClient.MOD_ID, "cloaks_" + frameId);
IdentifierUtils.registerBufferedImageTexture(frameIdentifier, frames.get(i));
animatedFrames.add(new AnimatedCloakData(frameIdentifier, delays.get(i)));
}
animatedCloaks.put(uuid, animatedFrames);
lastFrameTime.put(uuid, System.currentTimeMillis());
SaturnClient.LOGGER.info("Loaded " + frames.size() + " frames for animated cloak: " + fileName);
} catch (Exception e) {
SaturnClient.LOGGER.error("Failed to register animated cloak textures: " + fileName, e);
}
});
}
} catch (IOException e) {
SaturnClient.LOGGER.error("Failed to load animated cloak from resources: " + player.cloak + ".gif", e);
}
}).start();
} else {
SaturnClient.client.execute(() -> loadStaticCloak(player.cloak + ".png"));
}
}
}
}
/**
* Loads and processes a static cloak from a PNG file in resources.
*
* @param fileName Name of the PNG file to load
*/
private static void loadStaticCloak(String fileName) {
try {
String resourcePath = CLOAKS_RESOURCE_PATH + fileName;
InputStream inputStream = Cloaks.class.getClassLoader().getResourceAsStream(resourcePath);
if (inputStream != null) {
BufferedImage image = ImageIO.read(inputStream);
// Add debug logging for static cloak
SaturnClient.LOGGER.info("Static cloak dimensions: " + image.getWidth() + "x" + image.getHeight());
String safeFileName = fileName.toLowerCase(Locale.ROOT)
.replace(' ', '_')
.replaceAll("[^a-z0-9/._-]", "");
cloakCacheIdentifier = Identifier.of(SaturnClient.MOD_ID, "cloaks_" + safeFileName);
IdentifierUtils.registerBufferedImageTexture(cloakCacheIdentifier, image);
}
} catch (IOException e) {
SaturnClient.LOGGER.error("Failed to load static cloak from resources: " + fileName, e);
}
}
public static Identifier getCurrentCloakTexture(String uuid) {
if (!Auth.players.containsKey(uuid)) {
return null;
}
String cloakName = Auth.players.get(uuid).cloak;
if (cloakName.isEmpty()) {
return null;
}
if (Arrays.asList(ANIMATED_CLOAKS).contains(cloakName)) {
List<AnimatedCloakData> frames = animatedCloaks.get(uuid);
if (frames == null || frames.isEmpty()) {
return null;
}
long currentTime = System.currentTimeMillis();
long lastTime = lastFrameTime.getOrDefault(uuid, currentTime);
int currentFrame = 0;
long elapsedTime = currentTime - lastTime;
long totalTime = 0;
for (int i = 0; i < frames.size(); i++) {
totalTime += frames.get(i).getDelayMs();
if (elapsedTime < totalTime) {
currentFrame = i;
break;
}
}
if (elapsedTime >= totalTime) {
lastFrameTime.put(uuid, currentTime);
currentFrame = 0;
}
return frames.get(currentFrame).getTextureId();
} else {
return Identifier.of(SaturnClient.MOD_ID, "textures/cloaks/" + cloakName + ".png");
}
}
}
@@ -1,674 +0,0 @@
package org.saturnclient.saturnclient.cosmetics.cloaks;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import static java.lang.System.arraycopy;
/*
* Copyright 2014 Dhyan Blum
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* <p>
* A decoder capable of processing a GIF data stream to render the graphics
* contained in it. This implementation follows the official
* <A HREF="http://www.w3.org/Graphics/GIF/spec-gif89a.txt">GIF
* specification</A>.
* </p>
*
* <p>
* Example usage:
* </p>
*
* <p>
*
* <pre>
* final GifImage gifImage = GifDecoder.read(int[] data);
* final int width = gifImage.getWidth();
* final int height = gifImage.getHeight();
* final int frameCount = gifImage.getFrameCount();
* for (int i = 0; i < frameCount; i++) {
* final BufferedImage image = gifImage.getFrame(i);
* final int delay = gif.getDelay(i);
* }
* </pre>
*
* </p>
*
* @author Dhyan Blum
* @version 1.09 November 2017
*/
public final class GifDecoder {
static final class BitReader {
private int nextBitToRead;
private int numberOfBitsToRead;
private int bitMask; // Used to kill unwanted higher bits
private byte[] bytes; // Data array
// To avoid costly bounds checks, 'in' needs 2 more 0-bytes at the end
private void init(final byte[] bytes) {
this.bytes = bytes;
nextBitToRead = 0;
}
private int read() {
// Byte indices: (bitPos / 8), (bitPos / 8) + 1, (bitPos / 8) + 2
int byteIndex = nextBitToRead >>> 3; // Byte = bit / 8
int bitsToShiftRight = nextBitToRead & 7; // & 7 is the same as MODULO 8
int byte0, byte1, byte2;
byte0 = bytes[byteIndex++] & 0xFF; // & 0xFF gives us the unsigned values
byte1 = bytes[byteIndex++] & 0xFF;
byte2 = bytes[byteIndex] & 0xFF;
// Glue the bytes together, don't do more shifting than necessary
int buffer = ((byte2 << 8 | byte1) << 8 | byte0) >>> bitsToShiftRight;
nextBitToRead += numberOfBitsToRead;
return buffer & bitMask; // Kill the unwanted higher bits
}
private void setNumberOfBitsToRead(final int numberOfBitsToRead) {
this.numberOfBitsToRead = numberOfBitsToRead;
bitMask = (1 << numberOfBitsToRead) - 1;
}
}
static final class CodeTable {
private final int[][] table; // Maps codes to lists of colors
private int initTableSize; // Number of colors +2 for CLEAR + EOI
private int initCodeSize; // Initial code size
private int initCodeLimit; // First code limit
private int codeSize; // Current code size, maximum is 12 bits
private int nextCode; // Next available code for a new entry
private int nextCodeLimit; // Increase codeSize when nextCode == limit
private BitReader bitReader; // Notify when code sizes increases
public CodeTable() {
table = new int[4096][1];
}
private int add(final int[] indices) {
if (nextCode < 4096) {
if (nextCode == nextCodeLimit && codeSize < 12) {
codeSize++; // Max code size is 12
bitReader.setNumberOfBitsToRead(codeSize);
nextCodeLimit = (1 << codeSize) - 1; // 2^codeSize - 1
}
table[nextCode++] = indices;
}
return codeSize;
}
private int clear() {
codeSize = initCodeSize;
bitReader.setNumberOfBitsToRead(codeSize);
nextCodeLimit = initCodeLimit;
nextCode = initTableSize; // Don't recreate table, reset pointer
return codeSize;
}
private void init(final GifFrame fr, final int[] activeColTbl, final BitReader br) {
this.bitReader = br;
final int numColors = activeColTbl.length;
initCodeSize = fr.firstCodeSize;
initCodeLimit = (1 << initCodeSize) - 1; // 2^initCodeSize - 1
initTableSize = fr.endOfInfoCode + 1;
nextCode = initTableSize;
for (int c = numColors - 1; c >= 0; c--) {
table[c][0] = activeColTbl[c]; // Translated color
} // A gap may follow with no colors assigned if numCols < CLEAR
table[fr.clearCode] = new int[] { fr.clearCode }; // CLEAR
table[fr.endOfInfoCode] = new int[] { fr.endOfInfoCode }; // EOI
// Locate transparent color in code table and set to 0
if (fr.transpColFlag && fr.transpColIndex < numColors) {
table[fr.transpColIndex][0] = 0;
}
}
}
final class GifFrame {
// Graphic control extension (optional)
// Disposal: 0=NO_ACTION, 1=NO_DISPOSAL, 2=RESTORE_BG, 3=RESTORE_PREV
private int disposalMethod; // 0-3 as above, 4-7 undefined
private boolean transpColFlag; // 1 Bit
private int delay; // Unsigned, LSByte first, n * 1/100 * s
private int transpColIndex; // 1 Byte
// Image descriptor
private int x; // Position on the canvas from the left
private int y; // Position on the canvas from the top
private int w; // May be smaller than the base image
private int h; // May be smaller than the base image
private int wh; // width * height
private boolean hasLocColTbl; // Has local color table? 1 Bit
private boolean interlaceFlag; // Is an interlace image? 1 Bit
@SuppressWarnings("unused")
private boolean sortFlag; // True if local colors are sorted, 1 Bit
private int sizeOfLocColTbl; // Size of the local color table, 3 Bits
private int[] localColTbl; // Local color table (optional)
// Image data
private int firstCodeSize; // LZW minimum code size + 1 for CLEAR & EOI
private int clearCode;
private int endOfInfoCode;
private byte[] data; // Holds LZW encoded data
private BufferedImage img; // Full drawn image, not just the frame area
}
public final class GifImage {
public String header; // Bytes 0-5, GIF87a or GIF89a
private int w; // Unsigned 16 Bit, the least significant byte first
private int h; // Unsigned 16 Bit, the least significant byte first
private int wh; // Image width * image height
public boolean hasGlobColTbl; // 1 Bit
public int colorResolution; // 3 Bits
public boolean sortFlag; // True if global colors are sorted, 1 Bit
public int sizeOfGlobColTbl; // 2^(val(3 Bits) + 1), see spec
public int bgColIndex; // Background color index, 1 Byte
public int pxAspectRatio; // Pixel aspect ratio, 1 Byte
public int[] globalColTbl; // Global color table
private final List<GifFrame> frames = new ArrayList<GifFrame>(64);
public String appId = ""; // 8 Bytes at in[i+3], usually "NETSCAPE"
public String appAuthCode = ""; // 3 Bytes at in[i+11], usually "2.0"
public int repetitions = 0; // 0: infinite loop, N: number of loops
private BufferedImage img = null; // Currently, drawn frame
private final BitReader bits = new BitReader();
private final CodeTable codes = new CodeTable();
private Graphics2D g;
private int[] decode(final GifFrame fr, final int[] activeColTbl) {
codes.init(fr, activeColTbl, bits);
bits.init(fr.data); // Incoming codes
final int clearCode = fr.clearCode, endCode = fr.endOfInfoCode;
final int[] out = new int[wh]; // Target image pixel array
final int[][] tbl = codes.table; // Code table
int outPos = 0; // Next pixel position in the output image array
codes.clear(); // Init code table
bits.read(); // Skip leading clear code
int code = bits.read(); // Read first code
int[] pixels = tbl[code]; // Output pixel for first code
arraycopy(pixels, 0, out, outPos, pixels.length);
outPos += pixels.length;
try {
while (true) {
final int prevCode = code;
code = bits.read(); // Get next code in stream
if (code == clearCode) { // After a CLEAR table, there is
codes.clear(); // no previous code, we need to read
code = bits.read(); // a new one
pixels = tbl[code]; // Output pixels
arraycopy(pixels, 0, out, outPos, pixels.length);
outPos += pixels.length;
continue; // Back to the loop with a valid previous code
} else if (code == endCode) {
break;
}
final int[] prevVals = tbl[prevCode];
final int[] prevValsAndK = new int[prevVals.length + 1];
arraycopy(prevVals, 0, prevValsAndK, 0, prevVals.length);
if (code < codes.nextCode) { // Code table contains code
pixels = tbl[code]; // Output pixels
arraycopy(pixels, 0, out, outPos, pixels.length);
outPos += pixels.length;
prevValsAndK[prevVals.length] = tbl[code][0]; // K
} else {
prevValsAndK[prevVals.length] = prevVals[0]; // K
arraycopy(prevValsAndK, 0, out, outPos, prevValsAndK.length);
outPos += prevValsAndK.length;
}
codes.add(prevValsAndK); // Previous indices + K
}
} catch (final ArrayIndexOutOfBoundsException ignored) {
}
return out;
}
private int[] deinterlace(final int[] src, final GifFrame fr) {
final int w = fr.w, h = fr.h, wh = fr.wh;
final int[] dest = new int[src.length];
// Interlaced images are organized in 4 sets of pixel lines
final int set2Y = (h + 7) >>> 3; // Line no. = ceil(h/8.0)
final int set3Y = set2Y + ((h + 3) >>> 3); // ceil(h-4/8.0)
final int set4Y = set3Y + ((h + 1) >>> 2); // ceil(h-2/4.0)
// Sets' start indices in source array
final int set2 = w * set2Y, set3 = w * set3Y, set4 = w * set4Y;
// Line skips in destination array
final int w2 = w << 1, w4 = w2 << 1, w8 = w4 << 1;
// Group 1 contains every 8th line starting from 0
int from = 0, to = 0;
for (; from < set2; from += w, to += w8) {
arraycopy(src, from, dest, to, w);
} // Group 2 contains every 8th line starting from 4
for (to = w4; from < set3; from += w, to += w8) {
arraycopy(src, from, dest, to, w);
} // Group 3 contains every 4th line starting from 2
for (to = w2; from < set4; from += w, to += w4) {
arraycopy(src, from, dest, to, w);
} // Group 4 contains every 2nd line starting from 1 (biggest group)
for (to = w; from < wh; from += w, to += w2) {
arraycopy(src, from, dest, to, w);
}
return dest; // All pixel lines have now been rearranged
}
private void drawFrame(final GifFrame fr) {
// Determine the color table that will be active for this frame
final int[] activeColTbl = fr.hasLocColTbl ? fr.localColTbl : globalColTbl;
// Get pixels from data stream
int[] pixels = decode(fr, activeColTbl);
if (fr.interlaceFlag) {
pixels = deinterlace(pixels, fr); // Rearrange pixel lines
}
// Create image of type 2=ARGB for frame area
final BufferedImage frame = new BufferedImage(fr.w, fr.h, 2);
arraycopy(pixels, 0, ((DataBufferInt) frame.getRaster().getDataBuffer()).getData(), 0, fr.wh);
// Draw frame area on top of working image
g.drawImage(frame, fr.x, fr.y, null);
// Visualize frame boundaries during testing
// if (DEBUG_MODE) {
// if (prev != null) {
// g.setColor(Color.RED); // Previous frame color
// g.drawRect(prev.x, prev.y, prev.w - 1, prev.h - 1);
// }
// g.setColor(Color.GREEN); // New frame color
// g.drawRect(fr.x, fr.y, fr.w - 1, fr.h - 1);
// }
// Keep a copy of the previous frame's pixels in case we need to restore the
// frame
int[] prevPx = new int[wh];
arraycopy(((DataBufferInt) img.getRaster().getDataBuffer()).getData(), 0, prevPx, 0, wh);
// Create another copy for the end user to not expose internal state
fr.img = new BufferedImage(w, h, 2); // 2 = ARGB
arraycopy(prevPx, 0, ((DataBufferInt) fr.img.getRaster().getDataBuffer()).getData(), 0, wh);
// Handle disposal of current frame
if (fr.disposalMethod == 2) {
// Restore to background color (clear frame area only)
g.clearRect(fr.x, fr.y, fr.w, fr.h);
} else if (fr.disposalMethod == 3) {
// Restore previous frame
arraycopy(prevPx, 0, ((DataBufferInt) img.getRaster().getDataBuffer()).getData(), 0, wh);
}
}
/**
* Returns the background color of the first frame in this GIF image. If
* the frame has a local color table, the returned color will be from
* that table. If not, the color will be from the global color table.
* Returns 0 if there is neither a local nor a global color table.
*
* @return 32 bit ARGB color in the form 0xAARRGGBB
*/
public final int getBackgroundColor() {
final GifFrame frame = frames.get(0);
if (frame.hasLocColTbl) {
return frame.localColTbl[bgColIndex];
} else if (hasGlobColTbl) {
return globalColTbl[bgColIndex];
}
return 0;
}
/**
* If not 0, the delay specifies how many hundredths (1/100) of a second
* to wait before displaying the frame <i>after</i> the current frame.
*
* @param index Index of the current frame, 0 to N-1
* @return Delay as number of hundredths (1/100) of a second
*/
public final int getDelay(final int index) {
return frames.get(index).delay;
}
/**
* @param index Index of the frame to return as image, starting from 0.
* For incremental calls such as [0, 1, 2, ...] the method's
* run time is O(1) as only one frame is drawn per call. For
* random access calls such as [7, 12, ...] the run time is
* O(N+1) with N being the number of previous frames that
* need to be drawn before N+1 can be drawn on top. Once a
* frame has been drawn it is being cached and the run time
* is more or less O(0) to retrieve it from the list.
* @return A BufferedImage for the specified frame.
*/
public BufferedImage getFrame(final int index) {
if (img == null) { // Init
img = new BufferedImage(w, h, 2); // 2 = ARGB
g = img.createGraphics();
g.setBackground(new Color(0, true)); // Transparent color
}
GifFrame fr = frames.get(index);
if (fr.img == null) {
// Draw all frames until and including the requested frame
for (int i = 0; i <= index; i++) {
fr = frames.get(i);
if (fr.img == null) {
drawFrame(fr);
}
}
}
return fr.img;
}
/**
* @return The number of frames contained in this GIF image
*/
public final int getFrameCount() {
return frames.size();
}
/**
* @return The height of the GIF image
*/
public final int getHeight() {
return h;
}
/**
* @return The width of the GIF image
*/
public final int getWidth() {
return w;
}
}
static final boolean DEBUG_MODE = false;
/**
* @param in Raw image data as a byte[] array
* @return A GifImage object exposing the properties of the GIF image.
* @throws IOException If the image violates the GIF specification or is
* truncated.
*/
public static GifImage read(final byte[] in) throws IOException {
final GifDecoder decoder = new GifDecoder();
final GifImage img = decoder.new GifImage();
GifFrame frame = null; // Currently open frame
int pos = readHeader(in, img); // Read header, get next byte position
pos = readLogicalScreenDescriptor(img, in, pos);
if (img.hasGlobColTbl) {
img.globalColTbl = new int[img.sizeOfGlobColTbl];
pos = readColTbl(in, img.globalColTbl, pos);
}
while (pos < in.length) {
final int block = in[pos] & 0xFF;
switch (block) {
case 0x21: // Extension introducer
if (pos + 1 >= in.length) {
throw new IOException("Unexpected end of file.");
}
switch (in[pos + 1] & 0xFF) {
case 0xFE: // Comment extension
pos = readTextExtension(in, pos);
break;
case 0xFF: // Application extension
pos = readAppExt(img, in, pos);
break;
case 0x01: // Plain text extension
frame = null; // End of current frame
pos = readTextExtension(in, pos);
break;
case 0xF9: // Graphic control extension
if (frame == null) {
frame = decoder.new GifFrame();
img.frames.add(frame);
}
pos = readGraphicControlExt(frame, in, pos);
break;
default:
throw new IOException("Unknown extension at " + pos);
}
break;
case 0x2C: // Image descriptor
if (frame == null) {
frame = decoder.new GifFrame();
img.frames.add(frame);
}
pos = readImgDescr(frame, in, pos);
if (frame.hasLocColTbl) {
frame.localColTbl = new int[frame.sizeOfLocColTbl];
pos = readColTbl(in, frame.localColTbl, pos);
}
pos = readImgData(frame, in, pos);
frame = null; // End of current frame
break;
case 0x3B: // GIF Trailer
return img; // Found trailer, finished reading.
default:
// Unknown block. The image is corrupted. Strategies: a) Skip
// and wait for a valid block. Experience: It'll get worse. b)
// Throw exception. c) Return gracefully if we are almost done
// processing. The frames we have so far should be error-free.
final double progress = 1.0 * pos / in.length;
if (progress < 0.9) {
throw new IOException("Unknown block at: " + pos);
}
pos = in.length; // Exit loop
}
}
return img;
}
/**
* @param is Image data as input stream. This method will read from the
* input stream's current position. It will not reset the
* position before reading and won't reset or close the stream
* afterwards. Call these methods before and after calling this
* method as needed.
* @return A GifImage object exposing the properties of the GIF image.
* @throws IOException If an I/O error occurs, the image violates the GIF
* specification or the GIF is truncated.
*/
public static GifImage read(final InputStream is) throws IOException {
final byte[] data = new byte[is.available()];
is.read(data, 0, data.length);
return read(data);
}
/**
* @param img GIF image
* @param in Raw data
* @param i Index of the first byte of the application extension
* @return Index of the first byte after this extension
*/
static int readAppExt(final GifImage img, final byte[] in, int i) {
img.appId = new String(in, i + 3, 8); // should be "NETSCAPE"
img.appAuthCode = new String(in, i + 11, 3); // should be "2.0"
i += 14; // Go to sub-block size, it's value should be 3
final int subBlockSize = in[i] & 0xFF;
// The only app extension widely used is NETSCAPE, it's got 3 data bytes
if (subBlockSize == 3) {
// in[i+1] should have value 01, in[i+5] should be block terminator
img.repetitions = in[i + 2] & 0xFF | in[i + 3] & 0xFF << 8; // Short
return i + 5;
} // Skip unknown application extensions
while ((in[i] & 0xFF) != 0) { // While sub-block size != 0
i += (in[i] & 0xFF) + 1; // Skip to next sub-block
}
return i + 1;
}
/**
* @param in Raw data
* @param colors Pre-initialized target array to store ARGB colors
* @param i Index of the color table's first byte
* @return Index of the first byte after the color table
*/
static int readColTbl(final byte[] in, final int[] colors, int i) {
final int numColors = colors.length;
for (int c = 0; c < numColors; c++) {
final int a = 0xFF; // Alpha 255 (opaque)
final int r = in[i++] & 0xFF; // 1st byte is red
final int g = in[i++] & 0xFF; // 2nd byte is green
final int b = in[i++] & 0xFF; // 3rd byte is blue
colors[c] = ((a << 8 | r) << 8 | g) << 8 | b;
}
return i;
}
/**
* @param fr GIF frame
* @param in Raw data
* @param i Index of the extension introducer
* @return Index of the first byte after this block
*/
static int readGraphicControlExt(final GifFrame fr, final byte[] in, final int i) {
fr.disposalMethod = (in[i + 3] & 0b00011100) >>> 2; // Bits 4-2
fr.transpColFlag = (in[i + 3] & 1) == 1; // Bit 0
fr.delay = in[i + 4] & 0xFF | (in[i + 5] & 0xFF) << 8; // 16 bit LSB
fr.transpColIndex = in[i + 6] & 0xFF; // Byte 6
return i + 8; // Skipped byte 7 (blockTerminator), as it's always 0x00
}
/**
* @param in Raw data
* @param img The GifImage object that is currently read
* @return Index of the first byte after this block
* @throws IOException If the GIF header/trailer is missing, incomplete or
* unknown
*/
static int readHeader(final byte[] in, final GifImage img) throws IOException {
if (in.length < 6) { // Check first 6 bytes
throw new IOException("Image is truncated.");
}
img.header = new String(in, 0, 6);
if (!img.header.equals("GIF87a") && !img.header.equals("GIF89a")) {
throw new IOException("Invalid GIF header.");
}
return 6;
}
/**
* @param fr The GIF frame to whom this image descriptor belongs
* @param in Raw data
* @param i Index of the first byte of this block, i.e. the minCodeSize
* @return Byte index
*/
static int readImgData(final GifFrame fr, final byte[] in, int i) {
final int fileSize = in.length;
final int minCodeSize = in[i++] & 0xFF; // Read code size, go to block
final int clearCode = 1 << minCodeSize; // CLEAR = 2^minCodeSize
fr.firstCodeSize = minCodeSize + 1; // Add 1 bit for CLEAR and EOI
fr.clearCode = clearCode;
fr.endOfInfoCode = clearCode + 1;
final int imgDataSize = readImgDataSize(in, i);
final byte[] imgData = new byte[imgDataSize + 2];
int imgDataPos = 0;
int subBlockSize = in[i] & 0xFF;
while (subBlockSize > 0) { // While block has data
try { // Next line may throw exception if sub-block size is fake
final int nextSubBlockSizePos = i + subBlockSize + 1;
final int nextSubBlockSize = in[nextSubBlockSizePos] & 0xFF;
arraycopy(in, i + 1, imgData, imgDataPos, subBlockSize);
imgDataPos += subBlockSize; // Move output data position
i = nextSubBlockSizePos; // Move to next sub-block size
subBlockSize = nextSubBlockSize;
} catch (final Exception e) {
// Sub-block exceeds file end, only use remaining bytes
subBlockSize = fileSize - i - 1; // Remaining bytes
arraycopy(in, i + 1, imgData, imgDataPos, subBlockSize);
imgDataPos += subBlockSize; // Move output data position
i += subBlockSize + 1; // Move to next sub-block size
break;
}
}
fr.data = imgData; // Holds LZW encoded data
i++; // Skip last sub-block size, should be 0
return i;
}
static int readImgDataSize(final byte[] in, int i) {
final int fileSize = in.length;
int imgDataPos = 0;
int subBlockSize = in[i] & 0xFF;
while (subBlockSize > 0) { // While block has data
try { // Next line may throw exception if sub-block size is fake
final int nextSubBlockSizePos = i + subBlockSize + 1;
final int nextSubBlockSize = in[nextSubBlockSizePos] & 0xFF;
imgDataPos += subBlockSize; // Move output data position
i = nextSubBlockSizePos; // Move to next sub-block size
subBlockSize = nextSubBlockSize;
} catch (final Exception e) {
// Sub-block exceeds file end, only use remaining bytes
subBlockSize = fileSize - i - 1; // Remaining bytes
imgDataPos += subBlockSize; // Move output data position
break;
}
}
return imgDataPos;
}
/**
* @param fr The GIF frame to whom this image descriptor belongs
* @param in Raw data
* @param i Index of the image separator, i.e. the first block byte
* @return Index of the first byte after this block
*/
static int readImgDescr(final GifFrame fr, final byte[] in, int i) {
fr.x = in[++i] & 0xFF | (in[++i] & 0xFF) << 8; // Byte 1-2: left
fr.y = in[++i] & 0xFF | (in[++i] & 0xFF) << 8; // Byte 3-4: top
fr.w = in[++i] & 0xFF | (in[++i] & 0xFF) << 8; // Byte 5-6: width
fr.h = in[++i] & 0xFF | (in[++i] & 0xFF) << 8; // Byte 7-8: height
fr.wh = fr.w * fr.h;
final byte b = in[++i]; // Byte 9 is a packed byte
fr.hasLocColTbl = (b & 0b10000000) >>> 7 == 1; // Bit 7
fr.interlaceFlag = (b & 0b01000000) >>> 6 == 1; // Bit 6
fr.sortFlag = (b & 0b00100000) >>> 5 == 1; // Bit 5
final int colTblSizePower = (b & 7) + 1; // Bits 2-0
fr.sizeOfLocColTbl = 1 << colTblSizePower; // 2^(N+1), As per the spec
return ++i;
}
/**
* @param img GIF image
* @param i Start index of this block.
* @return Index of the first byte after this block.
*/
static int readLogicalScreenDescriptor(final GifImage img, final byte[] in, final int i) {
img.w = in[i] & 0xFF | (in[i + 1] & 0xFF) << 8; // 16 bit, LSB 1st
img.h = in[i + 2] & 0xFF | (in[i + 3] & 0xFF) << 8; // 16 bit
img.wh = img.w * img.h;
final byte b = in[i + 4]; // Byte 4 is a packed byte
img.hasGlobColTbl = (b & 0b10000000) >>> 7 == 1; // Bit 7
final int colResPower = ((b & 0b01110000) >>> 4) + 1; // Bits 6-4
img.colorResolution = 1 << colResPower; // 2^(N+1), As per the spec
img.sortFlag = (b & 0b00001000) >>> 3 == 1; // Bit 3
final int globColTblSizePower = (b & 7) + 1; // Bits 0-2
img.sizeOfGlobColTbl = 1 << globColTblSizePower; // 2^(N+1), see spec
img.bgColIndex = in[i + 5] & 0xFF; // 1 Byte
img.pxAspectRatio = in[i + 6] & 0xFF; // 1 Byte
return i + 7;
}
/**
* @param in Raw data
* @param pos Index of the extension introducer
* @return Index of the first byte after this block
*/
static int readTextExtension(final byte[] in, final int pos) {
int i = pos + 2; // Skip extension introducer and label
int subBlockSize = in[i++] & 0xFF;
while (subBlockSize != 0 && i < in.length) {
i += subBlockSize;
subBlockSize = in[i++] & 0xFF;
}
return i;
}
}
@@ -1,36 +0,0 @@
package org.saturnclient.saturnclient.cosmetics.cloaks.utils;
import net.minecraft.util.Identifier;
/**
* Represents a single frame of an animated cloak.
*/
public class AnimatedCloakData {
private final Identifier textureId;
private final int delayMs;
/**
* Creates a new animated cloak frame.
*
* @param textureId The texture identifier for this frame
* @param delayMs The delay in milliseconds before the next frame
*/
public AnimatedCloakData(Identifier textureId, int delayMs) {
this.textureId = textureId;
this.delayMs = delayMs;
}
/**
* @return The texture identifier for this frame
*/
public Identifier getTextureId() {
return textureId;
}
/**
* @return The delay in milliseconds before the next frame
*/
public int getDelayMs() {
return delayMs;
}
}
@@ -1,28 +0,0 @@
package org.saturnclient.saturnclient.cosmetics.cloaks.utils;
import org.saturnclient.saturnclient.SaturnClient;
import net.minecraft.client.texture.NativeImage;
import net.minecraft.client.texture.NativeImageBackedTexture;
import net.minecraft.util.Identifier;
import org.lwjgl.BufferUtils;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.nio.ByteBuffer;
public class IdentifierUtils {
public static void registerBufferedImageTexture(Identifier i, BufferedImage bi) {
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ImageIO.write(bi, "png", baos);
byte[] bytes = baos.toByteArray();
ByteBuffer bb = BufferUtils.createByteBuffer(bytes.length).put(bytes);
bb.flip();
NativeImageBackedTexture nibt = new NativeImageBackedTexture(NativeImage.read(bb));
SaturnClient.client.getTextureManager().registerTexture(i, nibt);
} catch (Exception e) {
e.printStackTrace();
}
}
}
@@ -1,48 +0,0 @@
package org.saturnclient.saturnclient.cosmetics.cloaks.utils;
import java.util.Random;
/**
* Utility class for generating random strings and numbers.
*/
public final class RandomUtils {
private static final String ALPHANUMERIC = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
private static final String ALPHANUMERIC_LOWERCASE = "0123456789abcdefghijklmnopqrstuvwxyz";
private static final Random RANDOM = new Random();
private RandomUtils() {
// Prevent instantiation
}
/**
* Generates a random string of specified length using given characters.
*/
private static String random(final int length, final char[] chars) {
StringBuilder sb = new StringBuilder(length);
for (int i = 0; i < length; i++) {
sb.append(chars[RANDOM.nextInt(chars.length)]);
}
return sb.toString();
}
/**
* Generates a random string of specified length using alphanumeric characters.
*/
public static String randomString(final int length) {
return random(length, ALPHANUMERIC.toCharArray());
}
/**
* Generates a random lowercase string of specified length.
*/
public static String randomStringLowercase(final int length) {
return random(length, ALPHANUMERIC_LOWERCASE.toCharArray());
}
/**
* Generates a random integer between min and max (inclusive).
*/
public static int randBetween(int min, int max) {
return RANDOM.nextInt(max - min + 1) + min;
}
}
@@ -1,75 +0,0 @@
package org.saturnclient.saturnclient.event;
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper;
import net.minecraft.client.option.KeyBinding;
// import net.minecraft.client.option.Perspective;
import net.minecraft.client.util.InputUtil;
// import org.saturnclient.modules.mods.FreeLook;
import org.saturnclient.ui2.screens.ShiftMenu;
// import org.saturnclient.saturnclient.menus.SaturnMenu;
import org.lwjgl.glfw.GLFW;
/**
* Handles keyboard input events for the Saturn Client.
* Manages keybindings and their associated actions.
*/
public class KeyInputHandler {
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 = GLFW.GLFW_KEY_RIGHT_SHIFT;
private static KeyBinding mainMenuKeyBinding;
// private static final String FREELOOK_KEY_ID = "key.saturnclient.freelook";
// private static final int FREELOOK_KEY = GLFW.GLFW_KEY_M;
// private static KeyBinding freelookKeyBinding;
/**
* Registers all keybindings and their associated event handlers.
* Should be called during mod initialization.
*/
public static void register() {
// Register the main menu key binding
mainMenuKeyBinding = KeyBindingHelper.registerKeyBinding(new KeyBinding(
MAIN_MENU_KEY_ID,
InputUtil.Type.KEYSYM,
DEFAULT_MENU_KEY,
KEY_CATEGORY));
// freelookKeyBinding = KeyBindingHelper.registerKeyBinding(new KeyBinding(
// FREELOOK_KEY_ID,
// InputUtil.Type.KEYSYM,
// FREELOOK_KEY,
// KEY_CATEGORY));
// Register the event handler for the main menu key
ClientTickEvents.END_CLIENT_TICK.register(client -> {
if (mainMenuKeyBinding.wasPressed()) {
client.setScreen(new ShiftMenu());
}
// else if (freelookKeyBinding.wasPressed() && FreeLook.enabled.value && FreeLook.toggle.value) {
// FreeLook.isFreeLooking = !FreeLook.isFreeLooking;
// if (FreeLook.isFreeLooking) {
// FreeLook.lastPerspective = client.options.getPerspective();
// client.options.setPerspective(Perspective.THIRD_PERSON_BACK);
// } else if (FreeLook.lastPerspective != null) {
// client.options.setPerspective(FreeLook.lastPerspective);
// FreeLook.lastPerspective = null;
// }
// } else if (FreeLook.enabled.value && !FreeLook.toggle.value) {
// if (freelookKeyBinding.isPressed()) {
// if (!FreeLook.isFreeLooking) {
// FreeLook.lastPerspective = client.options.getPerspective();
// client.options.setPerspective(Perspective.THIRD_PERSON_BACK);
// FreeLook.isFreeLooking = true;
// }
// } else if (FreeLook.isFreeLooking && FreeLook.lastPerspective != null) {
// client.options.setPerspective(FreeLook.lastPerspective);
// FreeLook.isFreeLooking = false;
// FreeLook.lastPerspective = null;
// }
// }
});
}
}
@@ -1,158 +0,0 @@
package org.saturnclient.saturnclient.menus;
import net.minecraft.text.Text;
import org.saturnclient.saturnclient.SaturnClientConfig;
import org.saturnclient.saturnclient.auth.Auth;
import org.saturnclient.saturnclient.config.Property;
import org.saturnclient.saturnclient.cosmetics.cloaks.Cloaks;
import org.saturnclient.ui.SaturnUi;
import org.saturnclient.ui.Textures;
import org.saturnclient.ui.components.SkinPreview;
import org.saturnclient.ui.widgets.SaturnClickableImage;
import org.saturnclient.ui.widgets.SaturnClickableSprite;
import org.saturnclient.ui.widgets.SaturnImage;
import org.saturnclient.ui.widgets.SaturnInputBox;
import org.saturnclient.ui.widgets.SaturnScroll;
import org.saturnclient.ui.widgets.SaturnSprite;
public class CloaksMenu extends SaturnUi {
private SaturnScroll scroll;
private Property<String> search = new Property<>("");
public CloaksMenu() {
super(Text.of("Cloaks Menu"));
}
@Override
protected void init() {
int rectWidth = 330;
int rectHeight = 228;
int rectX = (width - rectWidth) / 2;
int rectY = (height - rectHeight + 15) / 2;
draw(
new SaturnSprite(Textures.SETTINGS_BG)
.setX(rectX)
.setY(rectY)
.setWidth(rectWidth)
.setHeight(rectHeight)
.setAnimations(SaturnClientConfig.getAnimations()));
int tabsX = (width - 43) / 2;
int tabsY = rectY - 17;
draw(
new SaturnSprite(Textures.RECT)
.setX(tabsX)
.setY(tabsY)
.setWidth(43)
.setHeight(15)
.setAnimations(SaturnClientConfig.getAnimations()));
draw(
new SaturnInputBox(search, rectX + rectWidth - 72, tabsY, 70).onUpdate(() -> {
scroll.clear();
drawItems(rectX, rectY, rectWidth, rectHeight);
})
.setHeight(15)
.setWidth(72)
.setAnimations(SaturnClientConfig.getAnimations()));
int tabSize = 9;
tabsX = (width - tabSize) / 2;
draw(
new SaturnClickableImage(Textures.HAT, () -> {
client.setScreen(new HatsMenu());
})
.setX(tabsX)
.setY(tabsY + 4)
.setWidth(tabSize)
.setHeight(7)
.setAnimations(SaturnClientConfig.getAnimations()));
draw(
new SaturnClickableImage(Textures.CLOAK, () -> {
})
.setSelected(true)
.setX(tabsX - 12)
.setY(tabsY + 3)
.setWidth(tabSize)
.setHeight(tabSize)
.setAnimations(SaturnClientConfig.getAnimations()));
draw(
new SaturnClickableImage(Textures.SEARCH, () -> {
System.out.println("Pressed");
})
.setX(tabsX + 12)
.setY(tabsY + 3)
.setWidth(tabSize)
.setHeight(tabSize)
.setAnimations(SaturnClientConfig.getAnimations()));
scroll = new SaturnScroll();
drawItems(rectX, rectY, rectWidth, rectHeight);
draw(scroll
.setX(rectX + 17)
.setY(rectY + 10)
.setWidth(rectWidth - 17)
.setHeight(rectHeight - 10)
.setAnimations(SaturnClientConfig.getAnimations()));
draw(new SkinPreview()
.setScale(3.0f)
.setX((rectX + rectWidth) - 180)
.setY(rectY + 8)
.setAnimations(SaturnClientConfig.getAnimations()));
super.init();
}
private void drawItems(int rectX, int rectY, int rectWidth, int rectHeight) {
int cloakWidth = 28;
int cloakHeight = 61;
int modX = 0;
int modY = 0;
int col = 0;
String[] searchTokens = search.value.toLowerCase().split("\\ ");
outer: for (String cloak : Cloaks.availableCloaks) {
for (String searchToken : searchTokens) {
if (!cloak.contains(searchToken)) {
continue outer;
}
}
scroll.draw(
new SaturnClickableSprite(Textures.BUTTON_BORDER, () -> {
Cloaks.setCloak(Auth.uuid, cloak);
})
.setX(modX)
.setY(modY)
.setWidth(cloakWidth + 6)
.setHeight(cloakHeight + 6));
scroll.draw(
new SaturnImage(Textures.getCloakPreview(cloak))
.setX(modX + 3)
.setY(modY + 3)
.setWidth(cloakWidth)
.setHeight(cloakHeight));
col++;
if (col < 4) {
modX += cloakWidth + 21;
} else {
modX = 0;
modY += cloakHeight + 20;
col = 0;
}
}
}
}
@@ -1,234 +0,0 @@
package org.saturnclient.saturnclient.menus;
import java.util.Map;
import net.minecraft.text.Text;
import org.saturnclient.saturnclient.SaturnClientConfig;
import org.saturnclient.saturnclient.config.ConfigManager;
import org.saturnclient.saturnclient.config.Property;
import org.saturnclient.ui.SaturnUi;
import org.saturnclient.ui.SaturnWidget;
import org.saturnclient.ui.Textures;
import org.saturnclient.ui.components.SaturnFloat;
import org.saturnclient.ui.components.SaturnHex;
import org.saturnclient.ui.components.SaturnInteger;
import org.saturnclient.ui.components.SaturnString;
import org.saturnclient.ui.components.SaturnToggle;
import org.saturnclient.ui.widgets.SaturnImageButton;
import org.saturnclient.ui.widgets.SaturnScroll;
import org.saturnclient.ui.widgets.SaturnSprite;
import org.saturnclient.ui.widgets.SaturnText;
public class ConfigEditor extends SaturnUi {
private ConfigManager config;
public ConfigEditor(ConfigManager config) {
super(Text.of("Config editor"));
this.config = config;
}
@Override
protected void init() {
int rectWidth = 330;
int rectHeight = 228;
int rectX = (width - rectWidth) / 2;
int rectY = (height - rectHeight + 15) / 2;
draw(
new SaturnSprite(Textures.SETTINGS_BG)
.setX(rectX)
.setY(rectY)
.setWidth(rectWidth)
.setHeight(rectHeight)
.setAnimations(SaturnClientConfig.getAnimations()));
draw(
new SaturnImageButton(Textures.CLOSE, 9, 9, () -> {
this.close();
})
.setX(rectX)
.setY(rectY - 15)
.setWidth(13)
.setHeight(13)
.setAnimations(SaturnClientConfig.getAnimations()));
SaturnScroll configScroll = new SaturnScroll();
int row = 0;
int col = 0;
drawProperties(configScroll, config.getProperties(), rectWidth, row, col);
draw(
configScroll
.setX(rectX + 17)
.setY(rectY + 10)
.setWidth(rectWidth - 17)
.setHeight(rectHeight - 10)
.setAnimations(SaturnClientConfig.getAnimations()));
super.init();
}
@SuppressWarnings("unchecked")
private int drawProperties(SaturnScroll configScroll, Map<String, Property<?>> properties, int rectWidth,
int row, int col) {
int totalRows = row;
for (Map.Entry<String, Property<?>> propEntry : properties.entrySet()) {
Property<?> prop = propEntry.getValue();
String propName = propEntry.getKey();
boolean full = isFull(prop);
if (full && col > 0) {
col = 0;
totalRows++;
}
int modX = ((rectWidth / 2) - 17) * col;
int modY = 14 * totalRows;
switch (prop.getType()) {
case BOOLEAN:
configScroll.draw(
new SaturnToggle((Property<Boolean>) prop)
.setX(modX)
.setY(modY));
configScroll.draw(
new SaturnText(
propName.substring(0, 1).toUpperCase() +
propName.substring(1))
.setX(modX + 18)
.setY(modY)
.setScale(0.8f));
break;
case INTEGER:
configScroll.draw(
new SaturnText(
propName.substring(0, 1).toUpperCase() +
propName.substring(1))
.setX(modX)
.setY(modY)
.setScale(0.8f));
configScroll.draw(
new SaturnInteger(
(Property<Integer>) prop,
(rectWidth / 2) - 17,
modY,
70));
configScroll.draw(resetButton(prop, modX, modY));
break;
case FLOAT:
configScroll.draw(
new SaturnText(
propName.substring(0, 1).toUpperCase() +
propName.substring(1))
.setX(modX)
.setY(modY)
.setScale(0.8f));
configScroll.draw(
new SaturnFloat(
(Property<Float>) prop,
(rectWidth / 2) - 17,
modY,
70));
configScroll.draw(resetButton(prop, modX, modY));
break;
case STRING:
configScroll.draw(
new SaturnText(
propName.substring(0, 1).toUpperCase() +
propName.substring(1))
.setX(modX)
.setY(modY)
.setScale(0.8f));
configScroll.draw(
new SaturnString(
(Property<String>) prop,
(rectWidth / 2) - 17,
modY,
70));
configScroll.draw(resetButton(prop, modX, modY));
break;
case HEX:
configScroll.draw(
new SaturnText(
propName.substring(0, 1).toUpperCase() +
propName.substring(1))
.setX(modX)
.setY(modY)
.setScale(0.8f));
configScroll.draw(
new SaturnHex(
(Property<Integer>) prop,
(rectWidth / 2) - 17,
modY,
70));
configScroll.draw(resetButton(prop, modX, modY));
break;
case NAMESPACE:
configScroll.draw(
new SaturnText(
propName.substring(0, 1).toUpperCase() +
propName.substring(1))
.setX(modX)
.setY(modY)
.setScale(0.8f));
// Recursively draw nested properties and update total rows
Map<String, Property<?>> nestedProperties = (Map<String, Property<?>>) prop.value;
totalRows = drawProperties(configScroll, nestedProperties, rectWidth,
totalRows + 1, 0);
}
if (full) {
totalRows++;
} else {
col++;
if (col > 1) {
col = 0;
totalRows++;
}
}
}
return totalRows;
}
public static boolean isFull(Property<?> prop) {
switch (prop.getType()) {
case BOOLEAN:
return false;
default:
return true;
}
}
@Override
public void close() {
ConfigManager.save();
super.close();
}
public SaturnWidget resetButton(Property<?> prop, int modX, int modY) {
int size = textRenderer.fontHeight - 1;
return new SaturnImageButton(
Textures.RESET,
size,
size,
() -> {
prop.reset();
})
.setX(modX + 209)
.setY(modY)
.setWidth(size + 3)
.setHeight(size + 3);
}
}
@@ -1,133 +0,0 @@
package org.saturnclient.saturnclient.menus;
import net.minecraft.text.Text;
import org.saturnclient.saturnclient.SaturnClientConfig;
import org.saturnclient.saturnclient.auth.Auth;
import org.saturnclient.saturnclient.cosmetics.Hats;
import org.saturnclient.ui.SaturnUi;
import org.saturnclient.ui.Textures;
import org.saturnclient.ui.components.SkinPreview;
import org.saturnclient.ui.widgets.SaturnClickableImage;
import org.saturnclient.ui.widgets.SaturnClickableSprite;
import org.saturnclient.ui.widgets.SaturnImage;
import org.saturnclient.ui.widgets.SaturnScroll;
import org.saturnclient.ui.widgets.SaturnSprite;
public class HatsMenu extends SaturnUi {
public HatsMenu() {
super(Text.of("Hats Menu"));
}
@Override
protected void init() {
int rectWidth = 330;
int rectHeight = 228;
int rectX = (width - rectWidth) / 2;
int rectY = (height - rectHeight + 15) / 2;
draw(
new SaturnSprite(Textures.SETTINGS_BG)
.setX(rectX)
.setY(rectY)
.setWidth(rectWidth)
.setHeight(rectHeight)
.setAnimations(SaturnClientConfig.getAnimations()));
int tabsX = (width - 43) / 2;
int tabsY = rectY - 17;
draw(
new SaturnSprite(Textures.RECT)
.setX(tabsX)
.setY(tabsY)
.setWidth(43)
.setHeight(15)
.setAnimations(SaturnClientConfig.getAnimations()));
int tabSize = 9;
tabsX = (width - tabSize) / 2;
draw(
new SaturnClickableImage(Textures.HAT, () -> {
client.setScreen(new HatsMenu());
})
.setSelected(true)
.setX(tabsX)
.setY(tabsY + 4)
.setWidth(tabSize)
.setHeight(7)
.setAnimations(SaturnClientConfig.getAnimations()));
draw(
new SaturnClickableImage(Textures.CLOAK, () -> {
client.setScreen(new CloaksMenu());
})
.setX(tabsX - 12)
.setY(tabsY + 3)
.setWidth(tabSize)
.setHeight(tabSize)
.setAnimations(SaturnClientConfig.getAnimations()));
draw(
new SaturnClickableImage(Textures.SEARCH, () -> {
System.out.println("Pressed");
})
.setX(tabsX + 12)
.setY(tabsY + 3)
.setWidth(tabSize)
.setHeight(tabSize)
.setAnimations(SaturnClientConfig.getAnimations()));
int hatSize = 28;
int modX = 0;
int modY = 0;
int col = 0;
SaturnScroll modsScroll = new SaturnScroll();
for (String hat : Hats.availableHats) {
modsScroll.draw(
new SaturnClickableSprite(Textures.BUTTON_BORDER, () -> {
Hats.setHat(Auth.uuid, hat);
})
.setX(modX)
.setY(modY)
.setWidth(hatSize + 6)
.setHeight(hatSize + 6));
modsScroll.draw(
new SaturnImage(Textures.getHatPreview(hat))
.setX(modX + 3)
.setY(modY + 3)
.setWidth(hatSize)
.setHeight(hatSize));
col++;
if (col < 4) {
modX += hatSize + 21;
} else {
modX = 0;
modY += hatSize + 20;
col = 0;
}
}
draw(
modsScroll
.setX(rectX + 17)
.setY(rectY + 10)
.setWidth(rectWidth - 17)
.setHeight(rectHeight - 10)
.setAnimations(SaturnClientConfig.getAnimations()));
draw(new SkinPreview()
.setScale(3.0f)
.setX((rectX + rectWidth) - 180)
.setY(rectY + 8)
.setAnimations(SaturnClientConfig.getAnimations()));
super.init();
}
}
@@ -1,136 +0,0 @@
package org.saturnclient.saturnclient.menus;
import net.minecraft.client.gui.DrawContext;
import net.minecraft.client.gui.screen.Screen;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.text.Text;
import org.saturnclient.modules.HudMod;
import org.saturnclient.modules.ModDimensions;
import org.saturnclient.modules.ModManager;
import org.saturnclient.modules.Module;
import org.saturnclient.saturnclient.config.ConfigManager;
import org.saturnclient.ui.SaturnUi;
import java.util.ArrayList;
import java.util.List;
public class HudEditor extends Screen {
private final List<HudMod> hudMods = new ArrayList<>();
private HudMod draggingMod = null;
private boolean resizing = false;
private int offsetX, offsetY;
private static final int RESIZE_MARGIN = 3;
public HudEditor() {
super(Text.literal("HUD Editor"));
for (Module m : ModManager.MODS) {
if (m.isEnabled() && m instanceof HudMod) {
hudMods.add((HudMod) m);
}
}
}
@Override
public boolean mouseClicked(double mouseX, double mouseY, int button) {
if (button == 0) {
for (int m = hudMods.size() - 1; m >= 0; m--) {
HudMod mod = hudMods.get(m);
ModDimensions dim = mod.getDimensions();
// Check if clicking the corner for resizing
if (isCorner(mouseX, mouseY, dim)) {
draggingMod = mod;
resizing = true;
offsetX = (int) (mouseX - (dim.x.value + dim.width * dim.scale.value));
offsetY = (int) (mouseY - (dim.y.value + dim.height * dim.scale.value));
return true;
}
// Check if clicking inside the mod
if (isInside(mouseX, mouseY, dim)) {
draggingMod = mod;
resizing = false;
offsetX = (int) (mouseX - dim.x.value);
offsetY = (int) (mouseY - dim.y.value);
return true;
}
}
}
return super.mouseClicked(mouseX, mouseY, button);
}
@Override
public boolean mouseReleased(double mouseX, double mouseY, int button) {
if (button == 0) {
draggingMod = null;
resizing = false;
}
return super.mouseReleased(mouseX, mouseY, button);
}
@Override
public boolean mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY) {
if (draggingMod != null) {
ModDimensions dim = draggingMod.getDimensions();
if (resizing) {
// Scale based on cursor movement
float newScale = Math.max(0.5f, ((float) (mouseX - dim.x.value) / dim.width));
dim.scale.value = newScale;
} else {
// Move the mod
dim.x.value = (int) mouseX - offsetX;
dim.y.value = (int) mouseY - offsetY;
}
}
return super.mouseDragged(mouseX, mouseY, button, deltaX, deltaY);
}
@Override
public void render(DrawContext context, int mouseX, int mouseY, float delta) {
if (client.world == null && client.getCurrentServerEntry() == null) {
SaturnUi.ROTATING_PANORAMA_RENDERER.render(context, this.width, this.height, 1.0F, delta);
}
for (HudMod mod : hudMods) {
ModDimensions dim = mod.getDimensions();
MatrixStack matrices = context.getMatrices();
matrices.push();
matrices.translate(dim.x.value, dim.y.value, 0);
matrices.scale(dim.scale.value, dim.scale.value, 1.0f);
// Render the mod
mod.renderDummy(context);
matrices.pop();
// Draw border around mod
int borderX = dim.x.value;
int borderY = dim.y.value;
int borderW = (int) (dim.width * dim.scale.value);
int borderH = (int) (dim.height * dim.scale.value);
context.drawBorder(borderX, borderY, borderW, borderH, 0xFFFFFFFF); // White border
}
}
private boolean isInside(double mouseX, double mouseY, ModDimensions dim) {
return mouseX >= dim.x.value && mouseX <= dim.x.value + (dim.width * dim.scale.value) &&
mouseY >= dim.y.value && mouseY <= dim.y.value + (dim.height * dim.scale.value);
}
private boolean isCorner(double mouseX, double mouseY, ModDimensions dim) {
float rightX = dim.x.value + dim.width * dim.scale.value;
float bottomY = dim.y.value + dim.height * dim.scale.value;
return Math.abs(mouseX - rightX) < RESIZE_MARGIN && Math.abs(mouseY - bottomY) < RESIZE_MARGIN;
}
@Override
public void close() {
ConfigManager.save();
super.close();
}
}
@@ -1,147 +0,0 @@
package org.saturnclient.saturnclient.menus;
import net.minecraft.text.Text;
import org.saturnclient.modules.ModManager;
import org.saturnclient.modules.Module;
import org.saturnclient.saturnclient.SaturnClientConfig;
import org.saturnclient.saturnclient.config.ConfigManager;
import org.saturnclient.saturnclient.config.Property;
import org.saturnclient.ui.SaturnUi;
import org.saturnclient.ui.Textures;
import org.saturnclient.ui.components.SaturnModComp;
import org.saturnclient.ui.widgets.SaturnClickableImage;
import org.saturnclient.ui.widgets.SaturnInputBox;
import org.saturnclient.ui.widgets.SaturnScroll;
import org.saturnclient.ui.widgets.SaturnSprite;
public class ModMenu extends SaturnUi {
private SaturnScroll scroll;
private Property<String> search = new Property<>("");
public ModMenu() {
super(Text.literal("Mods"));
}
@Override
protected void init() {
int rectWidth = 330;
int rectHeight = 228;
int rectX = (width - rectWidth) / 2;
int rectY = (height - rectHeight + 15) / 2;
draw(
new SaturnSprite(Textures.SETTINGS_BG)
.setX(rectX)
.setY(rectY)
.setWidth(rectWidth)
.setHeight(rectHeight)
.setAnimations(SaturnClientConfig.getAnimations()));
int tabsX = (width - 43) / 2;
int tabsY = rectY - 17;
draw(
new SaturnSprite(Textures.RECT)
.setX(tabsX)
.setY(tabsY)
.setWidth(43)
.setHeight(15)
.setAnimations(SaturnClientConfig.getAnimations()));
draw(
new SaturnInputBox(search, rectX + rectWidth - 72, tabsY, 70).onUpdate(() -> {
scroll.clear();
drawItems(rectX, rectY, rectWidth, rectHeight);
})
.setHeight(15)
.setWidth(72)
.setAnimations(SaturnClientConfig.getAnimations()));
int tabSize = 9;
tabsX = (width - tabSize) / 2;
draw(
new SaturnClickableImage(Textures.SETTINGS, () -> {
client.setScreen(new SaturnConfigEditor());
})
.setX(tabsX)
.setY(tabsY + 3)
.setWidth(tabSize)
.setHeight(tabSize)
.setAnimations(SaturnClientConfig.getAnimations()));
draw(
new SaturnClickableImage(Textures.MODS_TAB, () -> {
})
.setSelected(true)
.setX(tabsX - 12)
.setY(tabsY + 3)
.setWidth(tabSize)
.setHeight(tabSize)
.setAnimations(SaturnClientConfig.getAnimations()));
draw(
new SaturnClickableImage(Textures.SEARCH, () -> {
System.out.println("Pressed");
})
.setX(tabsX + 12)
.setY(tabsY + 3)
.setWidth(tabSize)
.setHeight(tabSize)
.setAnimations(SaturnClientConfig.getAnimations()));
scroll = new SaturnScroll();
drawItems(rectX, rectY, rectWidth, rectHeight);
draw(scroll
.setX(rectX + 17)
.setY(rectY + 10)
.setWidth(rectWidth - 17)
.setHeight(rectHeight - 20)
.setAnimations(SaturnClientConfig.getAnimations()));
super.init();
}
@Override
public void close() {
ConfigManager.save();
super.close();
}
private void drawItems(int rectX, int rectY, int rectWidth, int rectHeight) {
int modX = 0;
int modY = 0;
int col = 0;
String[] searchTokens = search.value.toLowerCase().split("\\ ");
outer: for (Module mod : ModManager.MODS) {
String modName = mod.getName().toLowerCase();
for (String searchToken : searchTokens) {
if (!modName.contains(searchToken)) {
continue outer;
}
}
scroll.draw(
new SaturnModComp(mod)
.setX(modX)
.setY(modY)
.setWidth(86)
.setHeight(20));
col++;
if (col < 3) {
modX += 86 + 18;
} else {
modX = 0;
modY += 20 + 20;
col = 0;
}
}
}
}
@@ -1,64 +0,0 @@
package org.saturnclient.saturnclient.menus;
import org.saturnclient.saturnclient.SaturnClientConfig;
import org.saturnclient.ui.Textures;
import org.saturnclient.ui.widgets.SaturnClickableImage;
import org.saturnclient.ui.widgets.SaturnSprite;
public class SaturnConfigEditor extends ConfigEditor {
public SaturnConfigEditor() {
super(SaturnClientConfig.config);
}
@Override
protected void init() {
int rectHeight = 228;
int rectY = (height - rectHeight + 15) / 2;
int tabsX = (width - 43) / 2;
int tabsY = rectY - 17;
draw(
new SaturnSprite(Textures.RECT)
.setX(tabsX)
.setY(tabsY)
.setWidth(43)
.setHeight(15)
.setAnimations(SaturnClientConfig.getAnimations()));
int tabSize = 9;
tabsX = (width - tabSize) / 2;
draw(
new SaturnClickableImage(Textures.SETTINGS, () -> {
})
.setSelected(true)
.setX(tabsX)
.setY(tabsY + 3)
.setWidth(tabSize)
.setHeight(tabSize)
.setAnimations(SaturnClientConfig.getAnimations()));
draw(
new SaturnClickableImage(Textures.MODS_TAB, () -> {
client.setScreen(new ModMenu());
})
.setX(tabsX - 12)
.setY(tabsY + 3)
.setWidth(tabSize)
.setHeight(tabSize)
.setAnimations(SaturnClientConfig.getAnimations()));
draw(
new SaturnClickableImage(Textures.SEARCH, () -> {
System.out.println("Pressed");
})
.setX(tabsX + 12)
.setY(tabsY + 3)
.setWidth(tabSize)
.setHeight(tabSize)
.setAnimations(SaturnClientConfig.getAnimations()));
super.init();
}
}
@@ -1,13 +0,0 @@
package org.saturnclient.saturnclient.menus;
import org.saturnclient.ui2.SaturnScreen;
public class SaturnMenu extends SaturnScreen {
public SaturnMenu() {
super("Saturn Client");
}
@Override
public void ui() {
}
}
@@ -1,78 +0,0 @@
package org.saturnclient.saturnclient.menus;
import org.saturnclient.saturnclient.SaturnClientConfig;
import org.saturnclient.ui.SaturnUi;
import org.saturnclient.ui.Textures;
import org.saturnclient.ui.animations.FadeIn;
import org.saturnclient.ui.animations.Slide;
import org.saturnclient.ui.widgets.SaturnButton;
import org.saturnclient.ui.widgets.SaturnImage;
import org.saturnclient.ui2.screens.ShiftMenu;
import net.minecraft.client.gui.screen.multiplayer.MultiplayerScreen;
import net.minecraft.client.gui.screen.option.OptionsScreen;
import net.minecraft.client.gui.screen.world.SelectWorldScreen;
import net.minecraft.text.Text;
public class SaturnTitleScreen extends SaturnUi {
public SaturnTitleScreen() {
super(Text.literal("Title Screen"));
}
@Override
protected void init() {
blur = false;
int logoSize = 49;
int bX = (width - 150) / 2;
int bY = height / 2 - logoSize - 27;
draw(
new SaturnImage(SaturnClientConfig.getLogo())
.setX((width - logoSize) / 2)
.setY(bY)
.setWidth(logoSize)
.setHeight(logoSize)
.setAnimations(new Slide(3, 14)));
bY += 44;
draw(
new SaturnImage(Textures.LOGO_TEXT)
.setX((width - logoSize) / 2)
.setY(bY)
.setWidth(logoSize)
.setHeight(8)
.setAnimations(new FadeIn(3)));
bY += 15;
draw(new SaturnButton("SINGLEPLAYER", () -> {
client.setScreen(new SelectWorldScreen(new SaturnTitleScreen()));
}).setBold(true).setWidth(210).setHeight(26).setX(bX).setY(bY).setScale(0.69f));
bY += 20;
draw(new SaturnButton("MULTIPLAYER", () -> {
client.setScreen(new MultiplayerScreen(new SaturnTitleScreen()));
}).setBold(true).setWidth(210).setHeight(26).setX(bX).setY(bY).setScale(0.69f));
bY += 20;
draw(new SaturnButton("OPTIONS", () -> {
client.setScreen(new OptionsScreen(new SaturnTitleScreen(), this.client.options));
}).setBold(true).setWidth(103).setHeight(26).setX(bX).setY(bY).setScale(0.69f));
draw(new SaturnButton("QUIT", () -> {
client.scheduleStop();
}).setBold(true).setWidth(103).setHeight(26).setX(bX + 74).setY(bY).setScale(0.69f));
bY += 20;
draw(new SaturnButton("SATURN OPTIONS", () -> {
client.setScreen(new ShiftMenu());
}).setBold(true).setWidth(210).setHeight(26).setX(bX).setY(bY).setScale(0.69f));
super.init();
}
}
@@ -1,33 +0,0 @@
package org.saturnclient.saturnclient.mixin;
import java.util.Map;
import org.jetbrains.annotations.Nullable;
import org.saturnclient.saturnclient.auth.Auth;
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;
import net.minecraft.client.gui.hud.ChatHud;
import net.minecraft.client.gui.hud.MessageIndicator;
import net.minecraft.network.message.MessageSignatureData;
import net.minecraft.text.Text;
@Mixin(ChatHud.class)
public class SaturnReload {
@Inject(method = "addMessage(Lnet/minecraft/text/Text;Lnet/minecraft/network/message/MessageSignatureData;Lnet/minecraft/client/gui/hud/MessageIndicator;)V", at = @At("HEAD"), cancellable = true)
public void onAddMessage(Text message, @Nullable MessageSignatureData signatureData,
@Nullable MessageIndicator indicator, CallbackInfo ci) {
String msg = message.toString();
if (msg.contains("$SATURN_RELOAD")) {
for (Map.Entry<String, String> player : Auth.playerNames.entrySet()) {
String name = player.getKey();
if (msg.contains(name)) {
Auth.player(name, player.getValue());
}
ci.cancel();
}
}
}
}
@@ -1,42 +0,0 @@
package org.saturnclient.saturnclient.mixin;
import net.minecraft.client.network.PlayerListEntry;
import net.minecraft.text.MutableText;
import net.minecraft.text.Text;
import net.minecraft.util.Formatting;
import org.jetbrains.annotations.Nullable;
import org.saturnclient.saturnclient.SaturnClientConfig;
import org.saturnclient.saturnclient.auth.Auth;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Overwrite;
import org.spongepowered.asm.mixin.Shadow;
import com.mojang.authlib.GameProfile;
@Mixin(PlayerListEntry.class)
public abstract class TabListMixin {
@Shadow
private GameProfile profile;
/**
* @author HexLeo
* @reason Adds the Saturn Client icon to the player's name if they are online
* with Saturn Client
*/
@Overwrite
@Nullable
public Text getDisplayName() {
GameProfile profile = ((PlayerListEntry) (Object) this).getProfile();
String name = profile.getName();
String uuid = profile.getId().toString().replaceAll("-", "");
MutableText iconText = Text.literal(SaturnClientConfig.getSaturnIndicator())
.styled(style -> style.withColor(SaturnClientConfig.getIconColor(uuid)));
Text nameText = Text.literal(name).styled(style -> style.withColor(Formatting.WHITE));
return Auth.players.containsKey(uuid) ? iconText.append(nameText) : Text.literal(name);
}
}
@@ -1,13 +0,0 @@
package org.saturnclient.ui;
public class SaturnAnimation {
public int delay = 0;
public int lastTick = 0;
public void init(SaturnWidget widget) {}
public boolean run(SaturnWidget widget, int tick) {
return true;
}
}
@@ -1,322 +0,0 @@
package org.saturnclient.ui;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gl.PostEffectProcessor;
import net.minecraft.client.gui.CubeMapRenderer;
import net.minecraft.client.gui.DrawContext;
import net.minecraft.client.gui.RotatingCubeMapRenderer;
import net.minecraft.client.gui.screen.Screen;
import net.minecraft.client.render.DefaultFramebufferSet;
import net.minecraft.client.render.RenderLayer;
import net.minecraft.client.util.Pool;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.text.MutableText;
import net.minecraft.text.Style;
import net.minecraft.text.Text;
import net.minecraft.util.Identifier;
import org.lwjgl.glfw.GLFW;
public class SaturnUi extends Screen {
private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
private final Pool pool = new Pool(3);
private float f = 0.0f;
public boolean blur = true;
public boolean titleBackground = false;
public List<SaturnWidget> widgets = new ArrayList<>();
private int tick = 0;
protected static final CubeMapRenderer PANORAMA_RENDERER = new CubeMapRenderer(
Identifier.of("saturnclient", "textures/gui/title/background/panorama"));
public static final RotatingCubeMapRenderer ROTATING_PANORAMA_RENDERER;
static {
ROTATING_PANORAMA_RENDERER = new RotatingCubeMapRenderer(PANORAMA_RENDERER);
}
public static MutableText text(String text) {
return Text.literal(text).setStyle(
Style.EMPTY.withFont(Identifier.of("saturnclient:panton")));
}
public static MutableText textBold(String text) {
return Text.literal(text).setStyle(
Style.EMPTY.withFont(Identifier.of("saturnclient:panton_bold")));
}
public static void drawHighResTexture(DrawContext context, Identifier t, int x, int y, int w, int h) {
MatrixStack matricies = context.getMatrices();
matricies.push();
matricies.translate(x, y, 0);
matricies.scale(.25f, .25f, 1.0f);
context.drawTexture(RenderLayer::getGuiTextured, t, 0, 0, 0, 0,
w * 4, h * 4, w * 4, h * 4);
matricies.pop();
}
public static void drawHighResTexture(DrawContext context, Identifier t, int x, int y, int w, int h, int c) {
MatrixStack matricies = context.getMatrices();
matricies.push();
matricies.translate(x, y, 0);
matricies.scale(.25f, .25f, 1.0f);
context.drawTexture(RenderLayer::getGuiTextured, t, 0, 0, 0, 0,
w * 4, h * 4, w * 4, h * 4, c);
matricies.pop();
}
public static void drawHighResGuiTexture(DrawContext context, Identifier t, int x, int y, int w, int h, int c) {
MatrixStack matricies = context.getMatrices();
matricies.push();
matricies.translate(x, y, 0);
matricies.scale(.25f, .25f, 1.0f);
context.drawGuiTexture(
RenderLayer::getGuiTextured,
t,
0,
0,
w * 4,
h * 4,
c);
matricies.pop();
}
public static int getAlpha(int color, float alpha) {
return ((int) (alpha * 255) << 24) |
(color & 0x00FFFFFF);
}
public SaturnUi(Text title) {
super(title);
}
@Override
protected void init() {
scheduler.scheduleAtFixedRate(
this::saturnTick,
0,
7,
TimeUnit.MILLISECONDS);
super.init();
}
public void draw(SaturnWidget widget) {
synchronized (widgets) {
widget.init();
widgets.add(widget);
}
}
public void refresh() {
synchronized (widgets) {
widgets.clear();
f = 0.0f;
tick = 0;
init();
}
}
@SuppressWarnings("deprecation")
@Override
public void render(
DrawContext context,
int mouseX,
int mouseY,
float delta) {
if (client.world == null && client.getCurrentServerEntry() == null) {
ROTATING_PANORAMA_RENDERER.render(context, this.width, this.height, 1.0F, delta);
}
// Blur
if (!(f < 1.0F) && blur) {
PostEffectProcessor postEffectProcessor = this.client.getShaderLoader().loadPostEffect(
Identifier.ofVanilla("blur"),
DefaultFramebufferSet.MAIN_ONLY);
if (postEffectProcessor != null) {
postEffectProcessor.setUniforms("Radius", f);
postEffectProcessor.render(this.client.getFramebuffer(), this.pool);
}
}
for (SaturnWidget widget : new ArrayList<>(widgets)) {
if (!widget.visible)
continue;
double adjustedMouseX = mouseX - widget.x;
double adjustedMouseY = mouseY - widget.y;
boolean isMouseInside = adjustedMouseX >= 0 &&
adjustedMouseX <= (widget.width * widget.scale) &&
adjustedMouseY >= 0 &&
adjustedMouseY <= (widget.height * widget.scale);
MatrixStack matrices = context.getMatrices();
matrices.push();
matrices.translate(widget.x, widget.y, 0);
matrices.scale(widget.scale, widget.scale, 1.0f);
widget.render(
context,
isMouseInside,
(int) (adjustedMouseX / widget.scale),
(int) (adjustedMouseY / widget.scale));
matrices.pop();
}
}
@Override
public boolean mouseClicked(double mouseX, double mouseY, int button) {
if (button == 0) {
for (SaturnWidget widget : new ArrayList<>(widgets)) {
widget.focused = false;
if (!widget.visible)
continue;
double adjustedMouseX = mouseX - widget.x;
double adjustedMouseY = mouseY - widget.y;
boolean isMouseInside = adjustedMouseX >= 0 &&
adjustedMouseX <= (widget.width * widget.scale) &&
adjustedMouseY >= 0 &&
adjustedMouseY <= (widget.height * widget.scale);
if (isMouseInside) {
widget.focused = true;
widget.click(
(int) (adjustedMouseX / widget.scale),
(int) (adjustedMouseY / widget.scale));
}
}
}
return false;
}
@Override
public boolean keyPressed(int keyCode, int scanCode, int modifiers) {
for (SaturnWidget widget : new ArrayList<>(widgets)) {
if (!widget.visible && !widget.focused)
continue;
widget.keyPressed(keyCode, scanCode, modifiers);
char typedChar = getCharFromKey(keyCode, modifiers);
if (typedChar != '\0') {
widget.charTyped(typedChar);
}
}
return super.keyPressed(keyCode, scanCode, modifiers);
}
@Override
public boolean mouseScrolled(
double mouseX,
double mouseY,
double horizontalAmount,
double verticalAmount) {
for (SaturnWidget widget : new ArrayList<>(widgets)) {
double adjustedMouseX = mouseX - widget.x;
double adjustedMouseY = mouseY - widget.y;
boolean isMouseInside = adjustedMouseX >= 0 &&
adjustedMouseX <= (widget.width * widget.scale) &&
adjustedMouseY >= 0 &&
adjustedMouseY <= (widget.height * widget.scale);
if (isMouseInside) {
widget.mouseScrolled(
(int) (adjustedMouseX / widget.scale),
(int) (adjustedMouseY / widget.scale),
horizontalAmount,
verticalAmount);
}
}
return super.mouseScrolled(
mouseX,
mouseY,
horizontalAmount,
verticalAmount);
}
private char getCharFromKey(int keyCode, int modifiers) {
boolean shift = (modifiers & GLFW.GLFW_MOD_SHIFT) != 0;
// A-Z
if (keyCode >= GLFW.GLFW_KEY_A && keyCode <= GLFW.GLFW_KEY_Z) {
return (char) (shift
? 'A' + (keyCode - GLFW.GLFW_KEY_A)
: 'a' + (keyCode - GLFW.GLFW_KEY_A));
}
// 0-9
if (keyCode >= GLFW.GLFW_KEY_0 && keyCode <= GLFW.GLFW_KEY_9) {
return shift
? ")!@#$%^&*(".charAt(keyCode - GLFW.GLFW_KEY_0)
: (char) ('0' + (keyCode - GLFW.GLFW_KEY_0));
}
// Special characters
return switch (keyCode) {
case GLFW.GLFW_KEY_SPACE -> ' ';
case GLFW.GLFW_KEY_PERIOD -> shift ? '>' : '.';
case GLFW.GLFW_KEY_COMMA -> shift ? '<' : ',';
case GLFW.GLFW_KEY_MINUS -> shift ? '_' : '-';
case GLFW.GLFW_KEY_EQUAL -> shift ? '+' : '=';
case GLFW.GLFW_KEY_SEMICOLON -> shift ? ':' : ';';
case GLFW.GLFW_KEY_APOSTROPHE -> shift ? '"' : '\'';
case GLFW.GLFW_KEY_SLASH -> shift ? '?' : '/';
case GLFW.GLFW_KEY_BACKSLASH -> shift ? '|' : '\\';
case GLFW.GLFW_KEY_LEFT_BRACKET -> shift ? '{' : '[';
case GLFW.GLFW_KEY_RIGHT_BRACKET -> shift ? '}' : ']';
case GLFW.GLFW_KEY_GRAVE_ACCENT -> shift ? '~' : '`';
default -> '\0';
};
}
public void saturnTick() {
synchronized (scheduler) {
if (f < 15.0f) {
f += 0.25f;
}
}
synchronized (widgets) {
for (SaturnWidget widget : widgets) {
if (widget.visible && widget.animations != null) {
for (SaturnAnimation animation : widget.animations) {
if (animation != null &&
tick - animation.lastTick == animation.delay) {
if (animation.run(widget, tick)) {
animation = null;
} else {
animation.lastTick = tick;
}
}
}
}
widget.tick();
}
tick++;
}
}
@Override
public void resize(MinecraftClient client, int width, int height) {
super.resize(client, width, height);
refresh();
}
}
@@ -1,88 +0,0 @@
package org.saturnclient.ui;
import net.minecraft.client.gui.DrawContext;
public class SaturnWidget {
public int x = 0;
public int y = 0;
public int width = 0;
public int height = 0;
public float alpha = 1.0f;
public float scale = 1.0f;
public SaturnAnimation[] animations = null;
public boolean visible = true;
public boolean focused = false;
public void init() {
}
public void render(
DrawContext context,
boolean hovering,
int mouseX,
int mouseY) {
}
public void click(int mouseX, int mouseY) {
}
public void keyPressed(int keyCode, int scanCode, int modifiers) {
}
public void charTyped(char typedChar) {
}
public void tick() {
}
public void mouseScrolled(
int mouseX,
int mouseY,
double horizontalAmount,
double verticalAmount) {
}
public SaturnWidget setX(int x) {
this.x = x;
return this;
}
public SaturnWidget setY(int y) {
this.y = y;
return this;
}
public SaturnWidget setWidth(int width) {
this.width = width;
return this;
}
public SaturnWidget setHeight(int height) {
this.height = height;
return this;
}
public SaturnWidget setAlpha(float alpha) {
this.alpha = alpha;
return this;
}
public SaturnWidget setAnimations(SaturnAnimation... animations) {
this.animations = animations;
for (SaturnAnimation anim : this.animations) {
anim.init(this);
}
return this;
}
public SaturnWidget setVisible(boolean visible) {
this.visible = visible;
return this;
}
public SaturnWidget setScale(float scale) {
this.scale = scale;
return this;
}
}
@@ -1,44 +0,0 @@
package org.saturnclient.ui;
import net.minecraft.util.Identifier;
public class Textures {
public static final Identifier COSMETICS = Identifier.of("saturnclient", "textures/gui/components/cosmetics.png");
public static final Identifier HUD_ICON = Identifier.of("saturnclient", "textures/gui/components/hud.png");
public static final Identifier SEARCH = Identifier.of("saturnclient", "textures/gui/components/search.png");
public static final Identifier CLOSE = Identifier.of("saturnclient", "textures/gui/components/close.png");
public static final Identifier MODS_TAB = Identifier.of("saturnclient", "textures/gui/components/mods_tab.png");
public static final Identifier SETTINGS = Identifier.of("saturnclient", "textures/gui/components/settings.png");
public static final Identifier CLOAK = Identifier.of("saturnclient", "textures/gui/components/cloak.png");
public static final Identifier HAT = Identifier.of("saturnclient", "textures/gui/components/hat.png");
public static final Identifier RESET = Identifier.of("saturnclient", "textures/gui/components/reset.png");
public static final Identifier TOGGLE_INDICATOR = Identifier.of("saturnclient",
"textures/gui/components/toggle_indicator.png");
public static final Identifier TOGGLE_BG = Identifier.of("saturnclient", "textures/gui/components/toggle_bg.png");
public static final Identifier LOGO = Identifier.of("saturnclient", "textures/logo/logo.png");
public static final Identifier REALISTIC_LOGO = Identifier.of("saturnclient", "textures/logo/realistic.png");
public static final Identifier LOGO_TEXT = Identifier.of("saturnclient", "textures/logo/text.png");
// Widgets
public static final Identifier BUTTON_BORDER = Identifier.of("saturnclient", "button_border");
public static final Identifier BUTTON = Identifier.of("saturnclient", "button");
public static final Identifier BUTTON_OP = Identifier.of("saturnclient", "button_op");
public static final Identifier SETTINGS_BG = Identifier.of("saturnclient", "settings_bg");
public static final Identifier RECT = Identifier.of("saturnclient", "rect");
public static final Identifier RECT_BORDER = Identifier.of("saturnclient", "rect_border");
public static final Identifier MOD = Identifier.of("saturnclient", "textures/gui/components/mod.png");
public static final Identifier MOD_BG = Identifier.of("saturnclient", "textures/gui/components/mod_bg.png");
public static Identifier getCloakPreview(String cloak) {
return Identifier.of("saturnclient", "textures/gui/cloak/" + (cloak.isEmpty() ? "none" : cloak) + ".png");
}
public static Identifier getHatPreview(String hat) {
return Identifier.of("saturnclient", "textures/gui/hat/" + (hat.isEmpty() ? "none" : hat) + ".png");
}
public static Identifier getModIcon(String modId) {
return Identifier.of("saturnclient", "textures/gui/mod/" + modId + ".png");
}
}
@@ -1,20 +0,0 @@
package org.saturnclient.ui.animations;
import org.saturnclient.ui.SaturnAnimation;
import org.saturnclient.ui.SaturnWidget;
public class FadeIn extends SaturnAnimation {
public FadeIn(int delay) {
this.delay = delay;
}
public void init(SaturnWidget widget) {
widget.alpha = 0.0f;
}
public boolean run(SaturnWidget widget, int tick) {
widget.alpha += 0.05f;
return widget.alpha > 0.9f;
}
}
@@ -1,24 +0,0 @@
package org.saturnclient.ui.animations;
import org.saturnclient.ui.SaturnAnimation;
import org.saturnclient.ui.SaturnWidget;
public class Slide extends SaturnAnimation {
private int offset;
public Slide(int delay, int offset) {
this.delay = delay;
this.offset = offset;
}
public void init(SaturnWidget widget) {
widget.y += offset;
}
public boolean run(SaturnWidget widget, int tick) {
widget.y--;
offset--;
return offset == 0;
}
}
@@ -1,179 +0,0 @@
package org.saturnclient.ui.components;
import org.saturnclient.saturnclient.SaturnClient;
import net.minecraft.client.font.TextRenderer;
import net.minecraft.client.gui.DrawContext;
import org.lwjgl.glfw.GLFW;
import org.saturnclient.saturnclient.SaturnClientConfig;
import org.saturnclient.saturnclient.config.ConfigManager;
import org.saturnclient.saturnclient.config.Property;
import org.saturnclient.ui.SaturnUi;
import org.saturnclient.ui.SaturnWidget;
import org.saturnclient.ui.Textures;
public class SaturnFloat extends SaturnWidget {
String text = "";
public Property<Float> prop;
public int cursorPosition = 0;
private TextRenderer textRenderer = SaturnClient.client.textRenderer;
public SaturnFloat(Property<Float> prop, int x, int y, int width) {
this.x = x;
this.y = y;
this.width = width;
this.prop = prop;
this.text = String.valueOf(prop.value);
this.height = textRenderer.fontHeight + 4;
this.scale = 0.8f;
}
@Override
public void click(int mouseX, int mouseY) {
int scrollOffset = getScrollOffset();
String visibleText = text.substring(scrollOffset);
int newCursorPos = scrollOffset;
int textWidth = 0;
for (int i = 0; i < visibleText.length(); i++) {
textWidth = textRenderer.getWidth(visibleText.substring(0, i + 1));
if (textWidth > mouseX) {
break;
}
newCursorPos++;
}
cursorPosition = newCursorPos;
}
@Override
public void charTyped(char chr) {
if ((Character.isDigit(chr) || chr == '.') && text.length() < 10) {
if (text.equals("0") && cursorPosition == 1) {
text = "" + chr;
} else {
text = text.substring(0, cursorPosition) +
chr +
text.substring(cursorPosition);
prop.value = text.isEmpty() ? 0 : Float.parseFloat(text);
ConfigManager.save();
cursorPosition++;
}
}
}
@Override
public void keyPressed(int keyCode, int scanCode, int modifiers) {
if (keyCode == GLFW.GLFW_KEY_BACKSPACE) {
if (cursorPosition > 0) {
text = text.substring(0, cursorPosition - 1) +
text.substring(cursorPosition);
cursorPosition--;
if (text.length() == 0) {
text = "0";
cursorPosition = 1;
}
prop.value = Float.parseFloat(text);
ConfigManager.save();
}
} else if (keyCode == GLFW.GLFW_KEY_LEFT) {
if (cursorPosition > 0) {
cursorPosition--;
}
} else if (keyCode == GLFW.GLFW_KEY_RIGHT) {
if (cursorPosition < text.length()) {
cursorPosition++;
}
}
}
@Override
public void render(
DrawContext context,
boolean hovering,
int mouseX,
int mouseY) {
if (prop.isReset) {
this.text = String.valueOf(prop.value);
this.cursorPosition = 0;
prop.isReset = false;
}
SaturnUi.drawHighResGuiTexture(
context,
Textures.BUTTON_BORDER,
0,
0,
this.width,
this.height,
focused
? SaturnClientConfig.color.value
: SaturnClientConfig.getWhite(this.alpha));
int scrollOffset = getScrollOffset();
String visibleText = getVisibleText(scrollOffset);
int textColor = focused ? 0xFFFFFF : 0xAAAAAA;
context.drawText(
textRenderer,
SaturnUi.text(visibleText),
2,
2,
textColor,
false);
if (focused) {
int cursorX = 2 +
textRenderer.getWidth(
SaturnUi.text(
visibleText.substring(0, cursorPosition - scrollOffset)));
context.fill(
cursorX,
2,
cursorX + 1,
textRenderer.fontHeight + 4,
0xFFFFFFFF);
}
}
public void clearText() {
text = "0";
cursorPosition = 1;
prop.value = 0.0f;
}
private int getScrollOffset() {
if (text.isEmpty()) {
return 0;
}
int offset = 0;
int maxWidth = width - 4; // Available width for text
while (offset < cursorPosition) { // Ensure cursor remains visible
String visibleText = text.substring(offset, cursorPosition);
if (textRenderer.getWidth(SaturnUi.text(visibleText)) > maxWidth) {
offset++;
} else {
break;
}
}
return offset;
}
private String getVisibleText(int scrollOffset) {
String visibleText = text.substring(scrollOffset);
// Ensure text does not overflow past the available width
for (int i = 1; i <= visibleText.length(); i++) {
if (textRenderer.getWidth(
SaturnUi.text(visibleText.substring(0, i))) > width - 4) {
return visibleText.substring(0, i - 1); // Trim overflow
}
}
return visibleText;
}
}
@@ -1,140 +0,0 @@
package org.saturnclient.ui.components;
import org.saturnclient.saturnclient.SaturnClient;
import net.minecraft.client.font.TextRenderer;
import net.minecraft.client.gui.DrawContext;
import org.lwjgl.glfw.GLFW;
import org.saturnclient.saturnclient.SaturnClientConfig;
import org.saturnclient.saturnclient.config.ConfigManager;
import org.saturnclient.saturnclient.config.Property;
import org.saturnclient.ui.SaturnUi;
import org.saturnclient.ui.SaturnWidget;
import org.saturnclient.ui.Textures;
public class SaturnHex extends SaturnWidget {
private String text = "#"; // Always starts with '#'
public Property<Integer> prop;
public int cursorPosition = 1; // After '#'
private final TextRenderer textRenderer = SaturnClient.client.textRenderer;
public SaturnHex(Property<Integer> prop, int x, int y, int width) {
this.x = x;
this.y = y;
this.width = width;
this.prop = prop;
this.text = intToHex(prop.value);
this.height = textRenderer.fontHeight + 4;
this.scale = 0.8f;
}
@Override
public void click(int mouseX, int mouseY) {
cursorPosition = Math.min(7, text.length()); // Keep cursor inside hex length
}
@Override
public void charTyped(char chr) {
if (isHexChar(chr) && text.length() < 7) { // Limit to 6 hex digits + '#'
text = text.substring(0, cursorPosition) +
chr +
text.substring(cursorPosition);
cursorPosition++;
if (text.length() == 7) { // Only update value when full hex is entered
prop.value = hexToInt(text);
ConfigManager.save();
}
}
}
@Override
public void keyPressed(int keyCode, int scanCode, int modifiers) {
if (keyCode == GLFW.GLFW_KEY_BACKSPACE && cursorPosition > 1) { // Prevent deleting '#'
text = text.substring(0, cursorPosition - 1) +
text.substring(cursorPosition);
cursorPosition--;
} else if (keyCode == GLFW.GLFW_KEY_LEFT && cursorPosition > 1) {
cursorPosition--;
} else if (keyCode == GLFW.GLFW_KEY_RIGHT && cursorPosition < text.length()) {
cursorPosition++;
}
if (text.length() == 1) { // Reset if empty (keep '#')
text = "#";
cursorPosition = 1;
}
if (text.length() == 7) { // Update only when valid hex
prop.value = hexToInt(text);
ConfigManager.save();
}
}
@Override
public void render(
DrawContext context,
boolean hovering,
int mouseX,
int mouseY) {
if (prop.isReset) {
this.text = intToHex(prop.value);
this.cursorPosition = 0;
prop.isReset = false;
}
SaturnUi.drawHighResGuiTexture(
context,
Textures.BUTTON_BORDER,
0,
0,
this.width,
this.height,
focused
? SaturnClientConfig.color.value
: SaturnClientConfig.getWhite(this.alpha));
int textColor = focused ? 0xFFFFFF : 0xAAAAAA;
context.drawText(
textRenderer,
SaturnUi.text(text),
2,
2,
textColor,
false);
if (focused) {
int cursorX = 2 +
textRenderer.getWidth(
SaturnUi.text(text.substring(0, cursorPosition)));
context.fill(
cursorX,
2,
cursorX + 1,
textRenderer.fontHeight + 4,
0xFFFFFFFF);
}
}
public void clearText() {
text = "#";
cursorPosition = 1;
prop.value = 0xFFFFFFFF; // Default white
}
private boolean isHexChar(char chr) {
return ((chr >= '0' && chr <= '9') ||
(chr >= 'A' && chr <= 'F') ||
(chr >= 'a' && chr <= 'f'));
}
public static int hexToInt(String hex) {
hex = hex.replace("#", "");
int rgb = Integer.parseInt(hex, 16);
return (0xFF << 24) | rgb;
}
public static String intToHex(int color) {
int rgb = color & 0xFFFFFF;
return String.format("#%06X", rgb).toLowerCase();
}
}
@@ -1,177 +0,0 @@
package org.saturnclient.ui.components;
import net.minecraft.client.gui.DrawContext;
import org.lwjgl.glfw.GLFW;
import org.saturnclient.saturnclient.SaturnClient;
import org.saturnclient.saturnclient.SaturnClientConfig;
import org.saturnclient.saturnclient.config.ConfigManager;
import org.saturnclient.saturnclient.config.Property;
import org.saturnclient.ui.SaturnUi;
import org.saturnclient.ui.SaturnWidget;
import org.saturnclient.ui.Textures;
public class SaturnInteger extends SaturnWidget {
String text = "";
public Property<Integer> prop;
public int cursorPosition = 0;
public SaturnInteger(Property<Integer> prop, int x, int y, int width) {
this.x = x;
this.y = y;
this.width = width;
this.prop = prop;
this.text = String.valueOf(prop.value);
this.height = SaturnClient.client.textRenderer.fontHeight + 4;
this.scale = 0.8f;
}
@Override
public void click(int mouseX, int mouseY) {
int scrollOffset = getScrollOffset();
String visibleText = text.substring(scrollOffset);
int newCursorPos = scrollOffset;
int textWidth = 0;
for (int i = 0; i < visibleText.length(); i++) {
textWidth = SaturnClient.client.textRenderer.getWidth(visibleText.substring(0, i + 1));
if (textWidth > mouseX) {
break;
}
newCursorPos++;
}
cursorPosition = newCursorPos;
}
@Override
public void charTyped(char chr) {
if (Character.isDigit(chr) && text.length() < 10) {
if (text.equals("0") && cursorPosition == 1) {
text = "" + chr;
} else {
text = text.substring(0, cursorPosition) +
chr +
text.substring(cursorPosition);
prop.value = text.isEmpty() ? 0 : Integer.parseInt(text);
ConfigManager.save();
cursorPosition++;
}
}
}
@Override
public void keyPressed(int keyCode, int scanCode, int modifiers) {
if (keyCode == GLFW.GLFW_KEY_BACKSPACE) {
if (cursorPosition > 0) {
text = text.substring(0, cursorPosition - 1) +
text.substring(cursorPosition);
cursorPosition--;
if (text.length() == 0) {
text = "0";
cursorPosition = 1;
}
prop.value = Integer.parseInt(text);
ConfigManager.save();
}
} else if (keyCode == GLFW.GLFW_KEY_LEFT) {
if (cursorPosition > 0) {
cursorPosition--;
}
} else if (keyCode == GLFW.GLFW_KEY_RIGHT) {
if (cursorPosition < text.length()) {
cursorPosition++;
}
}
}
@Override
public void render(
DrawContext context,
boolean hovering,
int mouseX,
int mouseY) {
if (prop.isReset) {
this.text = String.valueOf(prop.value);
this.cursorPosition = 0;
prop.isReset = false;
}
SaturnUi.drawHighResGuiTexture(
context,
Textures.BUTTON_BORDER,
0,
0,
this.width,
this.height,
focused
? SaturnClientConfig.color.value
: SaturnClientConfig.getWhite(this.alpha));
int scrollOffset = getScrollOffset();
String visibleText = getVisibleText(scrollOffset);
int textColor = focused ? 0xFFFFFF : 0xAAAAAA;
context.drawText(
SaturnClient.client.textRenderer,
SaturnUi.text(visibleText),
2,
2,
textColor,
false);
if (focused) {
int cursorX = 2 +
SaturnClient.client.textRenderer.getWidth(
SaturnUi.text(
visibleText.substring(0, cursorPosition - scrollOffset)));
context.fill(
cursorX,
2,
cursorX + 1,
SaturnClient.client.textRenderer.fontHeight + 4,
0xFFFFFFFF);
}
}
public void clearText() {
text = "0";
cursorPosition = 1;
prop.value = 0;
}
private int getScrollOffset() {
if (text.isEmpty()) {
return 0;
}
int offset = 0;
int maxWidth = width - 4; // Available width for text
while (offset < cursorPosition) { // Ensure cursor remains visible
String visibleText = text.substring(offset, cursorPosition);
if (SaturnClient.client.textRenderer.getWidth(SaturnUi.text(visibleText)) > maxWidth) {
offset++;
} else {
break;
}
}
return offset;
}
private String getVisibleText(int scrollOffset) {
String visibleText = text.substring(scrollOffset);
// Ensure text does not overflow past the available width
for (int i = 1; i <= visibleText.length(); i++) {
if (SaturnClient.client.textRenderer.getWidth(
SaturnUi.text(visibleText.substring(0, i))) > width - 4) {
return visibleText.substring(0, i - 1); // Trim overflow
}
}
return visibleText;
}
}
@@ -1,106 +0,0 @@
package org.saturnclient.ui.components;
import net.minecraft.client.gui.DrawContext;
import org.saturnclient.modules.Module;
import org.saturnclient.saturnclient.SaturnClient;
import org.saturnclient.saturnclient.SaturnClientConfig;
import org.saturnclient.saturnclient.menus.ConfigEditor;
import org.saturnclient.ui.SaturnUi;
import org.saturnclient.ui.SaturnWidget;
import org.saturnclient.ui.Textures;
public class SaturnModComp extends SaturnWidget {
Module mod;
public SaturnModComp(Module mod) {
this.mod = mod;
}
@Override
public void render(
DrawContext context,
boolean hovering,
int mouseX,
int mouseY) {
boolean enabled = mod.isEnabled();
boolean hovering_settings = settingsHovering(mouseX, mouseY);
int color = !hovering_settings && (enabled || hovering)
? SaturnClientConfig.color.value
: SaturnClientConfig.getWhite(alpha);
int size = 12;
if (enabled) {
SaturnUi.drawHighResTexture(
context,
Textures.MOD_BG,
0,
0,
86,
20,
SaturnClientConfig.color.value);
}
SaturnUi.drawHighResTexture(
context,
Textures.MOD,
0,
0,
86,
20,
color);
SaturnUi.drawHighResTexture(
context,
mod.getIconTexture(),
4,
4,
size,
size,
color);
context.drawText(
SaturnClient.client.textRenderer,
SaturnUi.text(mod.getName()),
20,
(21 - SaturnClient.client.textRenderer.fontHeight) / 2,
color,
false);
SaturnUi.drawHighResTexture(
context,
Textures.SETTINGS,
77,
11,
7,
7,
hovering_settings
? SaturnClientConfig.color.value
: SaturnClientConfig.getWhite(alpha));
}
@Override
public void click(int mouseX, int mouseY) {
if (settingsHovering(mouseX, mouseY)) {
SaturnClient.client
.setScreen(new ConfigEditor(mod.getConfig()));
} else {
mod.setEnabled(!mod.isEnabled());
}
}
public boolean settingsHovering(int mouseX, int mouseY) {
int sX = 77;
int sY = 11;
int size = 7;
return (sX < mouseX &&
sX + size > mouseX &&
sY < mouseY &&
sY + size > mouseY);
}
}
@@ -1,155 +0,0 @@
package org.saturnclient.ui.components;
import org.saturnclient.saturnclient.SaturnClient;
import net.minecraft.client.font.TextRenderer;
import net.minecraft.client.gui.DrawContext;
import org.lwjgl.glfw.GLFW;
import org.saturnclient.saturnclient.SaturnClientConfig;
import org.saturnclient.saturnclient.config.Property;
import org.saturnclient.ui.SaturnUi;
import org.saturnclient.ui.SaturnWidget;
import org.saturnclient.ui.Textures;
public class SaturnString extends SaturnWidget {
public Property<String> prop;
public int cursorPosition = 0;
private TextRenderer textRenderer = SaturnClient.client.textRenderer;
public SaturnString(Property<String> prop, int x, int y, int width) {
this.x = x;
this.y = y;
this.width = width;
this.prop = prop;
this.height = textRenderer.fontHeight + 4;
this.scale = 0.8f;
}
@Override
public void click(int mouseX, int mouseY) {
int scrollOffset = getScrollOffset();
String visibleText = prop.value.substring(scrollOffset);
int newCursorPos = scrollOffset;
int textWidth = 0;
for (int i = 0; i < visibleText.length(); i++) {
textWidth = textRenderer.getWidth(visibleText.substring(0, i + 1));
if (textWidth > mouseX) {
break;
}
newCursorPos++;
}
cursorPosition = newCursorPos;
}
@Override
public void charTyped(char chr) {
prop.value = prop.value.substring(0, cursorPosition) +
chr +
prop.value.substring(cursorPosition);
cursorPosition++;
}
@Override
public void keyPressed(int keyCode, int scanCode, int modifiers) {
if (keyCode == GLFW.GLFW_KEY_BACKSPACE) {
if (cursorPosition > 0) {
prop.value = prop.value.substring(0, cursorPosition - 1) +
prop.value.substring(cursorPosition);
cursorPosition--;
}
} else if (keyCode == GLFW.GLFW_KEY_LEFT) {
if (cursorPosition > 0) {
cursorPosition--;
}
} else if (keyCode == GLFW.GLFW_KEY_RIGHT) {
if (cursorPosition < prop.value.length()) {
cursorPosition++;
}
}
}
@Override
public void render(
DrawContext context,
boolean hovering,
int mouseX,
int mouseY) {
SaturnUi.drawHighResGuiTexture(
context,
Textures.BUTTON_BORDER,
0,
0,
this.width,
this.height,
focused
? SaturnClientConfig.color.value
: SaturnClientConfig.getWhite(this.alpha));
int scrollOffset = getScrollOffset();
String visibleText = getVisibleText(scrollOffset);
int textColor = focused ? 0xFFFFFF : 0xAAAAAA;
context.drawText(
textRenderer,
SaturnUi.text(visibleText),
2,
2,
textColor,
false);
if (focused) {
int cursorX = 2 +
textRenderer.getWidth(
SaturnUi.text(
visibleText.substring(0, cursorPosition - scrollOffset)));
context.fill(
cursorX,
2,
cursorX + 1,
textRenderer.fontHeight + 3,
0xFFFFFFFF);
}
}
public void clearText() {
prop.value = "";
cursorPosition = 1;
}
private int getScrollOffset() {
if (prop.value.isEmpty()) {
return 0;
}
int offset = 0;
int maxWidth = width - 4; // Available width for prop.value
while (offset < cursorPosition) { // Ensure cursor remains visible
String visibleText = prop.value.substring(offset, cursorPosition);
if (textRenderer.getWidth(SaturnUi.text(visibleText)) > maxWidth) {
offset++;
} else {
break;
}
}
return offset;
}
private String getVisibleText(int scrollOffset) {
String visibleText = prop.value.substring(scrollOffset);
// Ensure prop.value does not overflow past the available width
for (int i = 1; i <= visibleText.length(); i++) {
if (textRenderer.getWidth(
SaturnUi.text(visibleText.substring(0, i))) > width - 4) {
return visibleText.substring(0, i - 1); // Trim overflow
}
}
return visibleText;
}
}
@@ -1,66 +0,0 @@
package org.saturnclient.ui.components;
import net.minecraft.client.gui.DrawContext;
import net.minecraft.client.render.RenderLayer;
import org.saturnclient.saturnclient.SaturnClientConfig;
import org.saturnclient.saturnclient.config.ConfigManager;
import org.saturnclient.saturnclient.config.Property;
import org.saturnclient.ui.SaturnWidget;
import org.saturnclient.ui.Textures;
public class SaturnToggle extends SaturnWidget {
Property<Boolean> prop;
public SaturnToggle(Property<Boolean> prop) {
this.prop = prop;
width = 16;
height = 8;
}
@Override
public void render(
DrawContext context,
boolean hovering,
int mouseX,
int mouseY) {
int color = prop.value
? SaturnClientConfig.color.value
: SaturnClientConfig.getWhite(alpha);
int w = 13;
int h = 6;
int s = 8;
context.drawTexture(
RenderLayer::getGuiTextured,
Textures.TOGGLE_BG,
0,
1,
0,
0,
w,
h,
w,
h,
color);
context.drawTexture(
RenderLayer::getGuiTextured,
Textures.TOGGLE_INDICATOR,
prop.value ? 5 : 0,
0,
0,
0,
s,
s,
s,
s,
color);
}
@Override
public void click(int mouseX, int mouseY) {
prop.value = !prop.value;
ConfigManager.save();
}
}
@@ -1,79 +0,0 @@
package org.saturnclient.ui.components;
import org.jetbrains.annotations.Nullable;
import org.joml.Quaternionf;
import org.joml.Vector3f;
import org.saturnclient.ui.SaturnWidget;
import org.saturnclient.saturnclient.SaturnClient;
import net.minecraft.client.gui.DrawContext;
import net.minecraft.client.render.DiffuseLighting;
import net.minecraft.client.render.entity.EntityRenderDispatcher;
import net.minecraft.entity.LivingEntity;
// If you think this code is bad, it's because it's written by Mojang
public class SkinPreview extends SaturnWidget {
public static void drawEntity(DrawContext context, int x1, int y1, int x2, int y2, int size, float f, float mouseX,
float mouseY, LivingEntity entity, float angle, boolean negativeAngle) {
float g = (float) (x1 + x2) / 2.0F;
float h = (float) (y1 + y2) / 2.0F;
context.enableScissor(0, 6, x2, y2);
float i = (float) Math.atan((double) ((g - mouseX) / 40.0F));
float j = (float) Math.atan((double) ((h - mouseY) / 40.0F));
Quaternionf quaternionf = (new Quaternionf()).rotateZ(3.1415927F);
float k = entity.bodyYaw;
float l = entity.getYaw();
float m = entity.getPitch();
float n = entity.prevHeadYaw;
float o = entity.headYaw;
entity.setYaw(angle);
entity.bodyYaw = angle;
entity.setPitch(-j * 20.0F);
entity.headYaw = negativeAngle ? -i * 40.0F : i * 40.0F;
entity.prevHeadYaw = entity.getHeadYaw();
entity.setCustomNameVisible(false);
float p = entity.getScale();
Vector3f vector3f = new Vector3f(0.0F, entity.getHeight() / 2.0F + f * p, 0.0F);
float q = (float) size / p;
drawEntity(context, g, h, q, vector3f, quaternionf, null, entity);
entity.setCustomNameVisible(true);
entity.bodyYaw = k;
entity.setYaw(l);
entity.setPitch(m);
entity.prevHeadYaw = n;
entity.headYaw = o;
context.disableScissor();
}
public static void drawEntity(DrawContext context, float x, float y, float size, Vector3f vector3f,
Quaternionf quaternionf, @Nullable Quaternionf quaternionf2, LivingEntity entity) {
context.getMatrices().push();
context.getMatrices().translate((double) x, (double) y, 50.0);
context.getMatrices().scale(size, size, -size);
context.getMatrices().translate(vector3f.x, vector3f.y, vector3f.z);
context.getMatrices().multiply(quaternionf);
context.draw();
DiffuseLighting.method_34742();
EntityRenderDispatcher entityRenderDispatcher = SaturnClient.client.getEntityRenderDispatcher();
entityRenderDispatcher.setRenderShadows(false);
context.draw((vertexConsumers) -> {
entityRenderDispatcher.render(entity, 0.0, 0.0, 0.0, 1.0F, context.getMatrices(), vertexConsumers,
15728880);
});
context.draw();
entityRenderDispatcher.setRenderShadows(true);
context.getMatrices().pop();
DiffuseLighting.enableGuiDepthLighting();
}
@Override
public void render(DrawContext context, boolean hovering, int mouseX, int mouseY) {
LivingEntity entity = SaturnClient.client.player;
if (entity != null) {
drawEntity(context, 0, 0, 75, 78, 30, 0.0625F, mouseX, mouseY, entity, -30.0f, true);
}
}
}
@@ -1,106 +0,0 @@
package org.saturnclient.ui.widgets;
import com.mojang.blaze3d.systems.RenderSystem;
import java.util.function.Consumer;
import org.saturnclient.saturnclient.SaturnClient;
import net.minecraft.client.gui.DrawContext;
import net.minecraft.text.Text;
import org.saturnclient.saturnclient.SaturnClientConfig;
import org.saturnclient.ui.SaturnUi;
import org.saturnclient.ui.SaturnWidget;
import org.saturnclient.ui.Textures;
import net.minecraft.client.MinecraftClient;
public class SaturnButton extends SaturnWidget {
public boolean active = true;
public String text;
public Consumer<SaturnButton> onPress;
public boolean background = true;
public boolean bold = false;
private float hoverAlpha = 0.0f;
public SaturnButton(String text, Consumer<SaturnButton> onPress) {
this.text = text;
this.onPress = onPress;
}
public SaturnButton(String text, Runnable onPress) {
this(text, b -> {
onPress.run();
});
}
@Override
public void render(
DrawContext context,
boolean hovering,
int mouseX,
int mouseY) {
MinecraftClient minecraftClient = SaturnClient.client;
if (background) {
RenderSystem.setShaderTexture(0, Textures.BUTTON);
SaturnUi.drawHighResGuiTexture(
context,
Textures.BUTTON,
0,
0,
this.width,
this.height,
SaturnClientConfig.getColor(hovering, alpha));
if (hovering) {
if (hoverAlpha == 0.0f)
hoverAlpha = 0.1f;
SaturnUi.drawHighResGuiTexture(
context,
Textures.BUTTON_BORDER,
0,
0,
this.width,
this.height,
SaturnClientConfig.getColor(hovering, alpha));
} else {
hoverAlpha = 0.0f;
}
}
if (alpha != 0.0f) {
Text t = bold ? SaturnUi.textBold(text) : SaturnUi.text(text);
context.drawText(
minecraftClient.textRenderer,
t,
((this.width -
minecraftClient.textRenderer.getWidth(t))
/
2),
((this.height - minecraftClient.textRenderer.fontHeight + 1) /
2),
SaturnClientConfig.getColor(hovering, alpha),
false);
}
}
@Override
public void click(int mouseX, int mouseY) {
this.onPress.accept(this);
}
@Override
public void tick() {
if (hoverAlpha > 0.0f && hoverAlpha < 0.9f)
hoverAlpha += 0.03f;
else if (hoverAlpha > 0.9f && hoverAlpha < 1.0f)
hoverAlpha = 1.0f;
}
public SaturnWidget setBold(boolean bold) {
this.bold = bold;
return this;
}
}
@@ -1,68 +0,0 @@
package org.saturnclient.ui.widgets;
import java.util.function.Consumer;
import net.minecraft.client.gui.DrawContext;
import net.minecraft.util.Identifier;
import org.saturnclient.saturnclient.SaturnClientConfig;
import org.saturnclient.ui.SaturnUi;
import org.saturnclient.ui.SaturnWidget;
public class SaturnClickableImage extends SaturnWidget {
public Identifier texture;
public boolean selected = false;
public Consumer<SaturnClickableImage> onPress;
private float hoverAlpha = 0.0f;
public SaturnClickableImage(
Identifier texture,
Consumer<SaturnClickableImage> onPress) {
this.texture = texture;
this.onPress = onPress;
}
public SaturnClickableImage(Identifier texture, Runnable onPress) {
this(texture, _o -> {
onPress.run();
});
}
@Override
public void render(
DrawContext context,
boolean hovering,
int mouseX,
int mouseY) {
if (hovering) {
if (hoverAlpha == 0.0f)
hoverAlpha = 0.1f;
}
SaturnUi.drawHighResTexture(
context,
texture,
0,
0,
width,
height,
SaturnClientConfig.getColor(hovering || selected, alpha));
}
@Override
public void click(int mouseX, int mouseY) {
this.onPress.accept(this);
}
public SaturnClickableImage setSelected(boolean selected) {
this.selected = selected;
return this;
}
@Override
public void tick() {
if (hoverAlpha > 0.0f && hoverAlpha < 0.9f)
hoverAlpha += 0.03f;
else if (hoverAlpha > 0.9f && hoverAlpha < 1.0f)
hoverAlpha = 1.0f;
}
}
@@ -1,54 +0,0 @@
package org.saturnclient.ui.widgets;
import java.util.function.Consumer;
import net.minecraft.client.gui.DrawContext;
import net.minecraft.util.Identifier;
import org.saturnclient.saturnclient.SaturnClientConfig;
import org.saturnclient.ui.SaturnUi;
import org.saturnclient.ui.SaturnWidget;
public class SaturnClickableSprite extends SaturnWidget {
public Identifier sprite;
public boolean selected = false;
public Consumer<SaturnClickableSprite> onPress;
public SaturnClickableSprite(
Identifier sprite,
Consumer<SaturnClickableSprite> onPress) {
this.sprite = sprite;
this.onPress = onPress;
}
public SaturnClickableSprite(Identifier sprite, Runnable onPress) {
this(sprite, _o -> {
onPress.run();
});
}
@Override
public void render(
DrawContext context,
boolean hovering,
int mouseX,
int mouseY) {
SaturnUi.drawHighResGuiTexture(
context,
sprite,
0,
0,
this.width,
this.height,
SaturnClientConfig.getColor(hovering, alpha));
}
@Override
public void click(int mouseX, int mouseY) {
this.onPress.accept(this);
}
public SaturnClickableSprite setSelected(boolean selected) {
this.selected = selected;
return this;
}
}
@@ -1,33 +0,0 @@
package org.saturnclient.ui.widgets;
import net.minecraft.client.gui.DrawContext;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.ColorHelper;
import org.saturnclient.ui.SaturnUi;
import org.saturnclient.ui.SaturnWidget;
public class SaturnImage extends SaturnWidget {
private final Identifier sprite;
public SaturnImage(Identifier sprite) {
this.sprite = sprite;
}
@Override
public void render(
DrawContext context,
boolean hovering,
int mouseX,
int mouseY) {
SaturnUi.drawHighResTexture(
context,
sprite,
0,
0,
width,
height,
ColorHelper.getWhite(alpha));
}
}
@@ -1,101 +0,0 @@
package org.saturnclient.ui.widgets;
import java.util.function.Consumer;
import net.minecraft.client.gui.DrawContext;
import net.minecraft.client.render.RenderLayer;
import net.minecraft.util.Identifier;
import org.saturnclient.saturnclient.SaturnClientConfig;
import org.saturnclient.ui.SaturnUi;
import org.saturnclient.ui.SaturnWidget;
import org.saturnclient.ui.Textures;
import com.mojang.blaze3d.systems.RenderSystem;
public class SaturnImageButton extends SaturnWidget {
public Identifier sprite;
public int imageWidth;
public int imageHeight;
public Consumer<SaturnImageButton> onPress;
private float hoverAlpha = 0.0f;
public SaturnImageButton(
Identifier sprite,
int imageWidth,
int imageHeight,
Consumer<SaturnImageButton> onPress) {
this.sprite = sprite;
this.imageWidth = imageWidth;
this.imageHeight = imageHeight;
this.onPress = onPress;
}
public SaturnImageButton(
Identifier sprite,
int imageWidth,
int imageHeight,
Runnable onPress) {
this(sprite, imageWidth, imageHeight, _o -> {
onPress.run();
});
}
@Override
public void render(
DrawContext context,
boolean hovering,
int mouseX,
int mouseY) {
if (hovering) {
if (hoverAlpha == 0.0f)
hoverAlpha = 0.1f;
}
RenderSystem.setShaderTexture(0, Textures.BUTTON);
SaturnUi.drawHighResGuiTexture(
context,
Textures.BUTTON,
0,
0,
this.width,
this.height,
SaturnClientConfig.getColor(hovering, alpha));
if (hovering) {
SaturnUi.drawHighResGuiTexture(
context,
Textures.BUTTON_BORDER,
0,
0,
this.width,
this.height,
SaturnClientConfig.getColor(hovering, alpha));
}
context.drawTexture(
RenderLayer::getGuiTextured,
sprite,
((width - imageWidth) / 2),
((height - imageHeight) / 2),
0,
0,
imageWidth,
imageHeight,
imageWidth,
imageHeight,
SaturnClientConfig.getColor(hovering, alpha));
}
@Override
public void click(int mouseX, int mouseY) {
this.onPress.accept(this);
}
@Override
public void tick() {
if (hoverAlpha > 0.0f && hoverAlpha < 0.9f)
hoverAlpha += 0.03f;
else if (hoverAlpha > 0.9f && hoverAlpha < 1.0f)
hoverAlpha = 1.0f;
}
}
@@ -1,230 +0,0 @@
package org.saturnclient.ui.widgets;
import org.saturnclient.ui.SaturnWidget;
import org.saturnclient.saturnclient.SaturnClient;
import net.minecraft.client.font.TextRenderer;
import net.minecraft.client.gui.DrawContext;
import net.minecraft.client.util.math.MatrixStack;
import org.lwjgl.glfw.GLFW;
import org.saturnclient.saturnclient.SaturnClientConfig;
import org.saturnclient.saturnclient.config.Property;
import org.saturnclient.ui.SaturnUi;
import org.saturnclient.ui.Textures;
public class SaturnInputBox extends SaturnWidget {
private TextRenderer textRenderer = SaturnClient.client.textRenderer;
private int tick = 0;
private boolean cursorVisible = true;
public Property<String> prop;
public int cursorPosition = 0;
public Runnable onUpdate;
public SaturnInputBox(Property<String> prop, int x, int y, int width) {
this.x = x;
this.y = y;
this.width = width;
this.prop = prop;
this.height = textRenderer.fontHeight + 4;
this.scale = 1.0f;
}
@Override
public void click(int mouseX, int mouseY) {
int scrollOffset = getScrollOffset();
String visibleText = prop.value.substring(scrollOffset);
int newCursorPos = scrollOffset;
int textWidth = 0;
for (int i = 0; i < visibleText.length(); i++) {
textWidth = textRenderer.getWidth(visibleText.substring(0, i + 1));
if (textWidth > mouseX) {
break;
}
newCursorPos++;
}
cursorPosition = newCursorPos;
}
@Override
public void charTyped(char chr) {
prop.value = prop.value.substring(0, cursorPosition) +
chr +
prop.value.substring(cursorPosition);
cursorPosition++;
onUpdate.run();
}
@Override
public void keyPressed(int keyCode, int scanCode, int modifiers) {
if (keyCode == GLFW.GLFW_KEY_BACKSPACE) {
if (cursorPosition > 0) {
prop.value = prop.value.substring(0, cursorPosition - 1) +
prop.value.substring(cursorPosition);
cursorPosition--;
}
onUpdate.run();
} else if (keyCode == GLFW.GLFW_KEY_LEFT) {
if (cursorPosition > 0) {
cursorPosition--;
}
} else if (keyCode == GLFW.GLFW_KEY_RIGHT) {
if (cursorPosition < prop.value.length()) {
cursorPosition++;
}
}
}
@Override
public void render(
DrawContext context,
boolean hovering,
int mouseX,
int mouseY) {
SaturnUi.drawHighResGuiTexture(
context,
Textures.BUTTON,
0,
0,
this.width,
this.height,
focused
? SaturnClientConfig.color.value
: SaturnClientConfig.getWhite(this.alpha));
if (hovering || focused) {
SaturnUi.drawHighResGuiTexture(
context,
Textures.BUTTON_BORDER,
0,
0,
this.width,
this.height,
hovering
? SaturnClientConfig.color.value
: SaturnClientConfig.getWhite(this.alpha));
}
MatrixStack matrices = context.getMatrices();
matrices.push();
if (prop.value.isEmpty() && !focused) {
SaturnUi.drawHighResTexture(
context,
Textures.SEARCH,
3,
3,
9,
9,
hovering
? SaturnClientConfig.color.value
: SaturnClientConfig.getWhite(this.alpha));
matrices.scale(0.9f, 0.9f, 1.0f);
context.drawText(
textRenderer,
SaturnUi.text("Search"),
14,
textRenderer.fontHeight / 2 + 1,
hovering
? SaturnClientConfig.color.value
: SaturnClientConfig.getWhite(this.alpha),
false);
}
matrices.pop();
matrices.push();
matrices.scale(0.9f, 0.9f, 1.0f);
int scrollOffset = getScrollOffset();
String visibleText = getVisibleText(scrollOffset);
int textColor = focused ? 0xFFFFFF : 0xAAAAAA;
context.drawText(
textRenderer,
SaturnUi.text(visibleText),
3,
textRenderer.fontHeight / 2,
textColor,
false);
if (focused && cursorVisible) {
int cursorX = 2 +
textRenderer.getWidth(
SaturnUi.text(
visibleText.substring(0, cursorPosition - scrollOffset)));
matrices.translate(cursorX, 0, 0);
context.fill(
1,
2,
2,
textRenderer.fontHeight + 3,
0xFFFFFFFF);
}
matrices.pop();
}
public void clearText() {
prop.value = "";
cursorPosition = 1;
}
private int getScrollOffset() {
if (prop.value.isEmpty()) {
return 0;
}
int offset = 0;
int maxWidth = width - 4; // Available width for prop.value
while (offset < cursorPosition) { // Ensure cursor remains visible
String visibleText = prop.value.substring(offset, cursorPosition);
if (textRenderer.getWidth(SaturnUi.text(visibleText)) > maxWidth) {
offset++;
} else {
break;
}
}
return offset;
}
private String getVisibleText(int scrollOffset) {
String visibleText = prop.value.substring(scrollOffset);
// Ensure prop.value does not overflow past the available width
for (int i = 1; i <= visibleText.length(); i++) {
if (textRenderer.getWidth(
SaturnUi.text(visibleText.substring(0, i))) > width - 4) {
return visibleText.substring(0, i - 1); // Trim overflow
}
}
return visibleText;
}
public SaturnInputBox onUpdate(Runnable onUpdate) {
this.onUpdate = onUpdate;
return this;
}
@Override
public void tick() {
if (focused) {
if (tick < 45 || tick < 60 && cursorVisible) {
tick++;
} else {
tick = 0;
cursorVisible = !cursorVisible;
}
}
}
}
@@ -1,150 +0,0 @@
package org.saturnclient.ui.widgets;
import java.util.ArrayList;
import java.util.List;
import net.minecraft.client.gui.DrawContext;
import net.minecraft.client.util.math.MatrixStack;
import org.saturnclient.ui.SaturnWidget;
public class SaturnScroll extends SaturnWidget {
private int scroll = 0;
private int maxScroll = 0;
private List<SaturnWidget> children = new ArrayList<>();
@Override
public void mouseScrolled(
int mouseX,
int mouseY,
double horizontalAmount,
double verticalAmount
) {
verticalAmount *= 2;
if (scroll - verticalAmount <= 0) {
scroll = 0;
} else if (scroll - verticalAmount >= maxScroll) {
scroll = maxScroll;
} else {
scroll -= verticalAmount;
}
}
@Override
public void render(
DrawContext context,
boolean hovering,
int mouseX,
int mouseY
) {
context.enableScissor(0, 0, width, height);
for (SaturnWidget widget : children) {
if (!widget.visible) continue;
double adjustedMouseX = (mouseX - widget.x);
double adjustedMouseY = (mouseY - widget.y + scroll);
boolean isMouseInside =
adjustedMouseX >= 0 &&
adjustedMouseX <= (widget.width * widget.scale) &&
adjustedMouseY >= 0 &&
adjustedMouseY <= (widget.height * widget.scale);
if (
widget.y - scroll < height && widget.y + widget.height > scroll
) {
MatrixStack matrices = context.getMatrices();
matrices.push();
matrices.translate(widget.x, widget.y - scroll, 0);
matrices.scale(widget.scale, widget.scale, 1.0f);
widget.render(
context,
isMouseInside,
(int) (adjustedMouseX / widget.scale),
(int) (adjustedMouseY / widget.scale)
);
matrices.pop();
}
}
context.disableScissor();
}
@Override
public void init() {
maxScroll = 0;
for (SaturnWidget child : children) {
maxScroll = Math.max(maxScroll, child.y + child.height - height);
}
}
public void draw(SaturnWidget widget) {
synchronized (children) {
widget.init();
children.add(widget);
}
}
public boolean isScrollable() {
for (SaturnWidget widget : children) {
if (widget.y + widget.height > height) {
return true;
}
}
return false;
}
@Override
public void keyPressed(int keyCode, int scanCode, int modifiers) {
for (SaturnWidget widget : children) {
if (!widget.visible || !widget.focused) {
continue;
}
widget.keyPressed(keyCode, scanCode, modifiers);
break;
}
}
@Override
public void charTyped(char typedChar) {
for (SaturnWidget widget : children) {
if (!widget.visible || !widget.focused) {
continue;
}
widget.charTyped(typedChar);
break;
}
}
@Override
public void click(int mouseX, int mouseY) {
for (SaturnWidget widget : children) {
widget.focused = false;
if (!widget.visible) {
continue;
}
double adjustedMouseX = (mouseX - widget.x);
double adjustedMouseY = (mouseY - (widget.y - scroll));
boolean isMouseInside =
adjustedMouseX >= 0 &&
adjustedMouseX <= (widget.width * widget.scale) &&
adjustedMouseY >= 0 &&
adjustedMouseY <= (widget.height * widget.scale);
if (isMouseInside) {
widget.focused = true;
widget.click(
(int) (adjustedMouseX / widget.scale),
(int) (adjustedMouseY / widget.scale)
);
}
}
}
public void clear() {
children.clear();
}
}
@@ -1,32 +0,0 @@
package org.saturnclient.ui.widgets;
import net.minecraft.client.gui.DrawContext;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.ColorHelper;
import org.saturnclient.ui.SaturnUi;
import org.saturnclient.ui.SaturnWidget;
public class SaturnSprite extends SaturnWidget {
private final Identifier sprite;
public SaturnSprite(Identifier sprite) {
this.sprite = sprite;
}
@Override
public void render(
DrawContext context,
boolean hovering,
int mouseX,
int mouseY) {
SaturnUi.drawHighResGuiTexture(
context,
sprite,
0,
0,
this.width,
this.height,
ColorHelper.getWhite(this.alpha));
}
}
@@ -1,38 +0,0 @@
package org.saturnclient.ui.widgets;
import net.minecraft.client.gui.DrawContext;
import org.saturnclient.saturnclient.SaturnClient;
import org.saturnclient.saturnclient.SaturnClientConfig;
import org.saturnclient.ui.SaturnUi;
import org.saturnclient.ui.SaturnWidget;
public class SaturnText extends SaturnWidget {
private String text;
public SaturnText(String text) {
this.text = text;
this.width = SaturnClient.client.textRenderer.getWidth(SaturnUi.text(text));
this.height = SaturnClient.client.textRenderer.fontHeight;
}
public SaturnText(String text, int color) {
this(text);
}
@Override
public void render(
DrawContext context,
boolean hovering,
int mouseX,
int mouseY) {
context.drawText(
SaturnClient.client.textRenderer,
SaturnUi.text(text),
0,
0,
SaturnClientConfig.getColor(hovering, alpha),
false);
}
}
@@ -1,73 +0,0 @@
package org.saturnclient.ui2;
import java.util.function.Function;
import org.saturnclient.ui2.anim.Animation;
import org.saturnclient.ui2.anim.Curve;
public class Element {
public int x;
public int y;
public int width;
public int height;
public float opacity = 1.0f;
public boolean focused;
public Animation animation;
public float scale = 1.0f;
public Function<Double, Double> curve = Curve::easeInOutCubic;
public void render(RenderScope renderScope, RenderContext ctx) {
}
public void click(int mouseX, int mouseY) {
}
public final Element dimensions(int width, int height) {
this.width = width;
this.height = height;
return this;
}
public final Element position(int x, int y) {
this.x = x;
this.y = y;
return this;
}
public final Element centerHorizontal(int w, int h, int offsetX, int offsetY) {
this.x = (w - (int) (width * scale)) / 2 + offsetX;
this.y = offsetY;
return this;
}
public final Element center(int w, int h) {
this.centerOffset(w, h, 0, 0);
return this;
}
public final Element centerOffset(int w, int h, int offsetX, int offsetY) {
this.x = (w - (int) (width * scale)) / 2 + offsetX;
this.y = (h - (int) (height * scale)) / 2 + offsetY;
return this;
}
public final Element animation(Animation animation) {
this.animation = animation;
return this;
}
public final Element animation(Animation animation, Function<Double, Double> curve) {
this.animation = animation;
this.curve = curve;
return this;
}
public Element scale(float scale) {
this.scale = scale;
return this;
}
}
@@ -1,22 +0,0 @@
package org.saturnclient.ui2;
public class RenderContext {
public int mouseX, mouseY;
public int elementX, elementY;
public int elementWidth, elementHeight;
public float elementScale;
public RenderContext(int mouseX, int mouseY, Element e) {
this.mouseX = mouseX;
this.mouseY = mouseY;
this.elementX = e.x;
this.elementY = e.y;
this.elementWidth = e.width;
this.elementHeight = e.height;
this.elementScale = e.scale;
}
public boolean isHovering() {
return Utils.isHovering(mouseX - elementX, mouseY - elementY, elementWidth, elementHeight, elementScale);
}
}
@@ -1,195 +0,0 @@
package org.saturnclient.ui2;
import java.util.function.Function;
import org.joml.Matrix4f;
import org.saturnclient.ui2.resources.Fonts;
import com.mojang.blaze3d.systems.RenderSystem;
import org.saturnclient.saturnclient.SaturnClient;
import net.minecraft.client.font.TextRenderer;
import net.minecraft.client.font.TextRenderer.TextLayerType;
import net.minecraft.client.render.RenderLayer;
import net.minecraft.client.render.VertexConsumer;
import net.minecraft.client.render.VertexConsumerProvider;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.RotationAxis;
public class RenderScope {
public MatrixStack matrices;
public VertexConsumerProvider.Immediate vertexConsumers;
private Function<Identifier, RenderLayer> renderLayers;
private int opacity = 255 << 24;
public RenderScope(MatrixStack matrices, VertexConsumerProvider.Immediate vertexConsumers) {
this.matrices = matrices;
this.vertexConsumers = vertexConsumers;
}
public void setOpacity(float alpha) {
this.opacity = (int) (alpha * 255) << 24;
}
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);
}
public void setRenderLayer(Function<Identifier, RenderLayer> renderLayers) {
this.renderLayers = renderLayers;
}
public void drawRect(int x, int y, int width, int height, int color) {
color = getColor(color);
Matrix4f matrix4f = this.matrices.peek().getPositionMatrix();
int x1 = x;
int x2 = x + width;
int y1 = y;
int y2 = y + height;
int i;
if (x1 < x2) {
i = x1;
x1 = x2;
x2 = i;
}
if (y1 < y2) {
i = y1;
y1 = y2;
y2 = i;
}
VertexConsumer vertexConsumer = this.vertexConsumers.getBuffer(RenderLayer.getGui());
vertexConsumer.vertex(matrix4f, (float) x1, (float) y1, 0).color(color);
vertexConsumer.vertex(matrix4f, (float) x1, (float) y2, 0).color(color);
vertexConsumer.vertex(matrix4f, (float) x2, (float) y2, 0).color(color);
vertexConsumer.vertex(matrix4f, (float) x2, (float) y1, 0).color(color);
}
public void drawText(String text, int x, int y, boolean bold, int color) {
color = getColor(color);
Identifier font = bold ? Fonts.PANTON_BOLD : Fonts.PANTON;
matrices.push();
matrices.translate(x, y, 0);
matrices.scale(2.0f, 2.0f, 1.0f);
TextRenderer textRenderer = SaturnClient.client.textRenderer;
textRenderer.draw(Fonts.setFont(text, font), 0, 0, color, false, this.matrices.peek().getPositionMatrix(),
this.vertexConsumers, TextLayerType.NORMAL, 0, 15728880);
matrices.pop();
}
public void drawRoundedCorner(int width, int height, int radius, int color) {
int w = width * 20;
int h = height * 20;
for (int y = 0; y < h; y++) {
int startX = 0;
if (y < radius) {
double dy = radius - y - 0.5;
double dx = Math.sqrt(Math.max(0, radius * radius - dy * dy));
startX = radius - (int) dx;
}
this.drawRect(startX, y, w - startX, 1, color);
}
}
private void drawRoundedSide(int cornerWidth, int cornerHeight, int radius, int color) {
// Top
this.matrices.push();
this.matrices.scale(0.05f, 0.05f, 1.0f);
this.drawRoundedCorner(cornerWidth, cornerHeight, radius * 10, color);
this.matrices.pop();
// Bottom
this.matrices.push();
this.matrices.translate(cornerWidth, cornerHeight * 2, 0);
this.matrices.multiply(RotationAxis.NEGATIVE_Z.rotationDegrees(90));
this.matrices.translate(0, -cornerWidth, 0);
this.matrices.scale(0.05f, 0.05f, 1.0f);
this.drawRoundedCorner(cornerHeight, cornerWidth, radius * 10, color);
this.matrices.pop();
}
public void drawRoundedRectangle(int x, int y, int width, int height, int radius, int color) {
radius = Math.min(radius, Math.min(width, height));
int cornerWidth = width / 2;
int cornerHeight = height / 2;
this.matrices.push();
this.matrices.translate(x, y, 0);
this.drawRoundedSide(cornerWidth, cornerHeight, radius, color);
this.matrices.translate(cornerWidth * 2, cornerHeight * 2, 0);
this.matrices.multiply(RotationAxis.NEGATIVE_Z.rotationDegrees(180));
this.matrices.translate(0, 0, 0);
this.drawRoundedSide(cornerWidth, cornerHeight, radius, color);
this.matrices.pop();
}
public int getScaledWindowWidth() {
return SaturnClient.client.getWindow().getScaledWidth();
}
public int getScaledWindowHeight() {
return SaturnClient.client.getWindow().getScaledHeight();
}
public void drawTexture(Identifier sprite, int x, int y, float u, float v, int width, int height, int color) {
this.drawTexture(sprite, x, y, u, v, width, height, width, height, width, height, color);
}
public void drawTexture(Identifier sprite, int x, int y, float u, float v, int width, int height) {
this.drawTexture(sprite, x, y, u, v, width, height, width, height, width, height);
}
public void drawTexture(Identifier sprite, int x, int y, float u, float v, int width, int height, int regionWith, int regionHeight, int textureWidth, int textureHeight) {
this.drawTexture(sprite, x, y, u, v, width, height, regionWith, regionHeight, textureWidth, textureHeight, -1);
}
public void drawTexture(Identifier sprite, int x, int y, float u, float v, int width, int height, int regionWidth, int regionHeight, int textureWidth, int textureHeight, int color) {
this.drawTexturedQuad(sprite, x, x + width, y, y + height, (u + 0.0F) / (float)textureWidth, (u + (float)regionWidth) / (float)textureWidth, (v + 0.0F) / (float)textureHeight, (v + (float)regionHeight) / (float)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) {
x1 *= 4;
x2 *= 4;
y1 *= 4;
y2 *= 4;
color = getColor(color);
matrices.push();
matrices.scale(0.25f, 0.25f, 1.0f);
RenderSystem.setShaderTexture(0, sprite);
RenderLayer renderLayer = (RenderLayer)renderLayers.apply(sprite);
Matrix4f matrix4f = this.matrices.peek().getPositionMatrix();
VertexConsumer vertexConsumer = this.vertexConsumers.getBuffer(renderLayer);
vertexConsumer.vertex(matrix4f, (float)x1, (float)y1, 0.0F).texture(u1, v1).color(color);
vertexConsumer.vertex(matrix4f, (float)x1, (float)y2, 0.0F).texture(u1, v2).color(color);
vertexConsumer.vertex(matrix4f, (float)x2, (float)y2, 0.0F).texture(u2, v2).color(color);
vertexConsumer.vertex(matrix4f, (float)x2, (float)y1, 0.0F).texture(u2, v1).color(color);
matrices.pop();
}
}
@@ -1,128 +0,0 @@
package org.saturnclient.ui2;
import java.util.ArrayList;
import java.util.List;
import org.saturnclient.saturnclient.config.ThemeManager;
import org.saturnclient.saturnclient.mixin.DrawContextAccessor;
import org.saturnclient.ui2.anim.Animation;
import net.minecraft.client.gl.PostEffectProcessor;
import net.minecraft.client.gui.CubeMapRenderer;
import net.minecraft.client.gui.DrawContext;
import net.minecraft.client.gui.RotatingCubeMapRenderer;
import net.minecraft.client.gui.screen.Screen;
import net.minecraft.client.render.DefaultFramebufferSet;
import net.minecraft.client.util.Pool;
import net.minecraft.text.Text;
import net.minecraft.util.Identifier;
public abstract class SaturnScreen extends Screen {
private final Pool pool = new Pool(3);
protected List<Element> elements = new ArrayList<>();
public int blurDuration = 700;
public float blurProgress = 0.0f;
public int backgroundBlur = 10;
protected static final CubeMapRenderer PANORAMA_RENDERER = new CubeMapRenderer(
Identifier.of("saturnclient", "textures/gui/title/background/panorama"));
public static final RotatingCubeMapRenderer ROTATING_PANORAMA_RENDERER;
static {
ROTATING_PANORAMA_RENDERER = new RotatingCubeMapRenderer(PANORAMA_RENDERER);
}
public SaturnScreen(String title) {
super(Text.literal(title));
}
public void draw(Element element) {
synchronized (elements) {
elements.add(element);
}
if (element.animation != null) {
element.animation.init(element);
Animation.execute((Float progress) -> {
element.animation.tick(progress, element);
}, element.animation.duration);
}
}
@Override
protected void init() {
width *= 2;
height *= 2;
ui(); // abstraction to render the saturn ui and also render extra stuff here
ThemeManager.load();
Animation.execute((Float progress) -> {
blurProgress = progress;
}, blurDuration);
}
public abstract void ui();
@SuppressWarnings("deprecation")
@Override
public void render(DrawContext context, int mouseX, int mouseY, float delta) {
mouseX *= 2;
mouseY *= 2;
if (client.world == null && client.getCurrentServerEntry() == null) {
ROTATING_PANORAMA_RENDERER.render(context, this.width, this.height, 1.0F, delta);
}
PostEffectProcessor postEffectProcessor = this.client.getShaderLoader().loadPostEffect(
Identifier.ofVanilla("blur"),
DefaultFramebufferSet.MAIN_ONLY);
if (postEffectProcessor != null) {
postEffectProcessor.setUniforms("Radius", backgroundBlur * blurProgress);
postEffectProcessor.render(this.client.getFramebuffer(), this.pool);
}
// We are using a Abstracted RenderScope because older minecraft versions don't
// use DrawContext
RenderScope renderScope = new RenderScope(context.getMatrices(),
((DrawContextAccessor) context).getVertexConsumers());
renderScope.matrices.push();
renderScope.matrices.scale(0.5f, 0.5f, 0.5f);
for (Element element : elements) {
renderScope.matrices.push();
renderScope.setOpacity(element.opacity);
renderScope.matrices.translate(element.x, element.y, 0);
renderScope.matrices.scale(element.scale, element.scale, 1.0f);
element.render(renderScope, new RenderContext(mouseX, mouseY, element));
renderScope.matrices.pop();
renderScope.setRenderLayer(null);
}
renderScope.matrices.pop();
}
@Override
public boolean mouseClicked(double mouseX, double mouseY, int button) {
mouseX *= 2;
mouseY *= 2;
if (button == 0) {
for (Element element : new ArrayList<>(elements)) {
element.focused = false;
int adjustedMouseX = (int) mouseX - element.x;
int adjustedMouseY = (int) mouseY - element.y;
if (Utils.isHovering(adjustedMouseX, adjustedMouseY, element.width, element.height, element.scale)) {
element.focused = true;
element.click(adjustedMouseX, adjustedMouseY);
}
}
}
return false;
}
}
@@ -1,10 +0,0 @@
package org.saturnclient.ui2;
public class Utils {
public static boolean isHovering(int mouseX, int mouseY, int elementWidth, int elementHeight, float elementScale) {
return mouseX >= 0 &&
mouseX <= (elementWidth * elementScale) &&
mouseY >= 0 &&
mouseY <= (elementHeight * elementScale);
}
}
@@ -1,53 +0,0 @@
package org.saturnclient.ui2.anim;
import java.util.function.Consumer;
import java.util.function.Function;
import org.saturnclient.ui2.Element;
public abstract class Animation {
public int duration;
Animation(int duration) {
this.duration = duration;
}
public abstract void tick(float progress, Element element);
public abstract void init(Element element);
public static void execute(Consumer<Float> tick, int duration) {
new Thread(() -> {
long startTime = System.currentTimeMillis();
Function<Double, Double> curveFunction = Curve::easeInOutCubic;
float progress = 0.0f;
double incremental = (1000 - duration) / 10000.0;
System.out.println("Starting animation...");
for (int i = 0; i <= 100; i++) {
float newProgress = (float) (double) curveFunction.apply(i / 100.0);
while (Math.abs(newProgress - progress) > 1e-6) {
if (progress < newProgress) {
progress += incremental;
if (progress > newProgress) progress = newProgress;
} else {
progress -= incremental;
if (progress < newProgress) progress = newProgress;
}
tick.accept(newProgress);
try {
Thread.sleep(duration / 100);
} catch (Exception e) {
e.printStackTrace();
}
}
}
System.out.println("Animation complete. " + (System.currentTimeMillis() - startTime) + "MS");
}).start();
}
}
@@ -1,26 +0,0 @@
package org.saturnclient.ui2.anim;
public class Curve {
public static double easeOutCubic(double x) {
return 1 - Math.pow(1 - x, 3);
}
public static double easeInOutCubic(double x) {
return (x < 0.5 ? 4 * x * x * x : 1 - Math.pow(-2 * x + 2, 3) / 2);
}
public static double easeInOutCubicReverse(double x) {
return -(x < 0.5 ? 4 * x * x * x : 1 - Math.pow(-2 * x + 2, 3) / 2);
}
public static double easeInOutBack(double x) {
double c1 = 1.70158;
double c2 = c1 * 1.525;
if (x < 0.5) {
return (Math.pow(2 * x, 2) * ((c2 + 1) * 2 * x - c2)) / 2;
} else {
return (Math.pow(2 * x - 2, 2) * ((c2 + 1) * (2 * x - 2) + c2) + 2) / 2;
}
}
}
@@ -1,22 +0,0 @@
package org.saturnclient.ui2.anim;
import org.saturnclient.ui2.Element;
public class Fade extends Animation {
float targetOpacity;
public Fade(int duration) {
super(duration);
}
@Override
public void tick(float progress, Element element) {
element.opacity = targetOpacity * progress;
}
@Override
public void init(Element element) {
targetOpacity = element.opacity;
element.opacity = 0;
}
}
@@ -1,38 +0,0 @@
package org.saturnclient.ui2.anim;
import org.saturnclient.ui2.Element;
public class SlideY extends Animation {
int offset = 20;
int maxOffset = 20;
int elementY;
public SlideY(int duration) {
super(duration);
}
public SlideY(int duration, int offset) {
super(duration);
this.offset = offset;
this.maxOffset = offset;
}
public SlideY(int duration, int offset, int maxOffset) {
super(duration);
this.offset = offset;
this.maxOffset = offset;
}
@Override
public void tick(float progress, Element element) {
element.y = elementY + (int) (offset * progress);
}
@Override
public void init(Element element) {
element.y -= offset;
elementY = element.y;
}
}
@@ -1,52 +0,0 @@
package org.saturnclient.ui2.elements;
import org.saturnclient.saturnclient.config.Property;
import org.saturnclient.saturnclient.config.ThemeManager;
import org.saturnclient.ui2.Element;
import org.saturnclient.ui2.RenderContext;
import org.saturnclient.ui2.RenderScope;
import org.saturnclient.ui2.resources.Fonts;
import net.minecraft.util.Identifier;
public class Button extends Element {
private static ThemeManager theme = new ThemeManager("Button", "hovering");
private static Property<Integer> bgColor = theme.property("bg-color", new Property<Integer>(0xFF000000));
private static Property<Integer> fgColor = theme.property("fg-color", new Property<Integer>(0xFFFFFFFF));
private static Property<Integer> cornerRadius = theme.property("corner-radius", new Property<Integer>(10));
private static Property<Boolean> bold = theme.property("fg-bold", new Property<Boolean>(false));
static {
theme.propertyStateDefault("hovering", "fg-color", 0xFFFF0000);
}
private String text;
private Identifier font;
private Runnable onClick;
public Button(String text, Runnable onClick) {
this.text = text;
this.font = Fonts.getFont(bold.value);
this.onClick = onClick;
this.dimensions(Fonts.getWidth(text, font) + 50, Fonts.getHeight() + 20);
}
@Override
public void render(RenderScope renderScope, RenderContext ctx) {
if (ctx.isHovering()) {
theme.setState("hovering");
} else {
theme.setState(null);
}
renderScope.drawRoundedRectangle(0, 0, width, height, cornerRadius.value, bgColor.value);
renderScope.drawText(text, (width - Fonts.getWidth(text, font)) / 2, (height - Fonts.getHeight()) / 2, bold.value, fgColor.value);
}
@Override
public void click(int mouseX, int mouseY) {
if (onClick != null) {
onClick.run();
}
}
}
@@ -1,42 +0,0 @@
package org.saturnclient.ui2.elements;
import org.saturnclient.saturnclient.config.Property;
import org.saturnclient.saturnclient.config.ThemeManager;
import org.saturnclient.ui2.Element;
import org.saturnclient.ui2.RenderContext;
import org.saturnclient.ui2.RenderScope;
import net.minecraft.client.render.RenderLayer;
import net.minecraft.util.Identifier;
public class ClickableTexture extends Element {
private static ThemeManager theme = new ThemeManager("ClickableTexture", "hovering");
private static Property<Integer> fgColor = theme.property("fg-color", new Property<Integer>(0xFFFFFFFF));
Identifier sprite;
private Runnable onClick;
public ClickableTexture(Identifier sprite, Runnable onClick) {
this.sprite = sprite;
this.onClick = onClick;
}
@Override
public void render(RenderScope renderScope, RenderContext ctx) {
if (ctx.isHovering()) {
theme.setState("hovering");
} else {
theme.setState(null);
}
renderScope.setRenderLayer(RenderLayer::getGuiTextured);
renderScope.drawTexture(sprite, 0, 0, 0, 0, width, height, fgColor.value);
}
@Override
public void click(int mouseX, int mouseY) {
if (onClick != null) {
onClick.run();
}
}
}
@@ -1,22 +0,0 @@
package org.saturnclient.ui2.elements;
import org.saturnclient.ui2.Element;
import org.saturnclient.ui2.RenderContext;
import org.saturnclient.ui2.RenderScope;
import net.minecraft.client.render.RenderLayer;
import net.minecraft.util.Identifier;
public class ImageTexture extends Element {
Identifier sprite;
public ImageTexture(Identifier sprite) {
this.sprite = sprite;
}
@Override
public void render(RenderScope renderScope, RenderContext ctx) {
renderScope.setRenderLayer(RenderLayer::getGuiTextured);
renderScope.drawTexture(sprite, 0, 0, 0, 0, width, height);
}
}
@@ -1,23 +0,0 @@
package org.saturnclient.ui2.elements;
import org.saturnclient.saturnclient.config.Property;
import org.saturnclient.saturnclient.config.ThemeManager;
import org.saturnclient.ui2.Element;
import org.saturnclient.ui2.RenderContext;
import org.saturnclient.ui2.RenderScope;
public class Text extends Element {
private static ThemeManager theme = new ThemeManager("Text");
private static Property<Integer> fgColor = theme.property("fg-color", new Property<Integer>(0xFFFFFFFF));
private String text;
public Text(String text) {
this.text = text;
}
@Override
public void render(RenderScope renderScope, RenderContext ctx) {
renderScope.drawText(this.text, this.x, this.y, false, fgColor.value);
}
}
@@ -1,56 +0,0 @@
package org.saturnclient.ui2.elements;
import org.saturnclient.saturnclient.config.Property;
import org.saturnclient.saturnclient.config.ThemeManager;
import org.saturnclient.ui2.Element;
import org.saturnclient.ui2.RenderContext;
import org.saturnclient.ui2.RenderScope;
import net.minecraft.client.render.RenderLayer;
import net.minecraft.util.Identifier;
public class TextureButton extends Element {
private static ThemeManager theme = new ThemeManager("TextureButton", "hovering");
private static Property<Integer> bgColor = theme.property("bg-color", new Property<Integer>(0xFF000000));
private static Property<Integer> fgColor = theme.property("fg-color", new Property<Integer>(0xFFFFFFFF));
private static Property<Integer> padding = theme.property("padding", new Property<Integer>(14));
private static Property<Integer> cornerRadius = theme.property("corner-radius", new Property<Integer>(10));
static {
theme.propertyStateDefault("hovering", "fg-color", 0xFFFF0000);
}
Identifier sprite;
private Runnable onClick;
public TextureButton(Identifier sprite, Runnable onClick) {
this.sprite = sprite;
this.onClick = onClick;
}
@Override
public void render(RenderScope renderScope, RenderContext ctx) {
if (ctx.isHovering()) {
theme.setState("hovering");
} else {
theme.setState(null);
}
renderScope.drawRoundedRectangle(0, 0, width, height, cornerRadius.value, bgColor.value);
renderScope.setRenderLayer(RenderLayer::getGuiTextured);
int texWidth = width - padding.value;
int texHeight = height - padding.value;
renderScope.drawTexture(sprite, padding.value / 2, padding.value / 2, 0, 0, texWidth, texHeight, fgColor.value);
}
@Override
public void click(int mouseX, int mouseY) {
if (onClick != null) {
onClick.run();
}
}
}
@@ -1,42 +0,0 @@
package org.saturnclient.ui2.resources;
import org.saturnclient.saturnclient.SaturnClient;
import net.minecraft.text.Style;
import net.minecraft.text.Text;
import net.minecraft.util.Identifier;
public class Fonts {
public static final Identifier PANTON = Identifier.of("saturnclient", "panton");
public static final Identifier PANTON_BOLD = Identifier.of("saturnclient", "panton_bold");
public static Identifier getFont(boolean bold) {
if (bold) {
return PANTON_BOLD;
} else {
return PANTON;
}
}
public static Text setFont(String text, Identifier font) {
return Text.literal(text).setStyle(
Style.EMPTY.withFont(font));
}
// public static int getWidth(String text, Identifier font) {
// return SaturnClient.client.textRenderer.getWidth(Text.literal(text).setStyle(
// Style.EMPTY.withFont(font)));
// }
// public static int getHeight() {
// return SaturnClient.client.textRenderer.fontHeight;
// }
public static int getWidth(String text, Identifier font) {
return SaturnClient.client.textRenderer.getWidth(Text.literal(text).setStyle(
Style.EMPTY.withFont(font))) * 2;
}
public static int getHeight() {
return SaturnClient.client.textRenderer.fontHeight * 2;
}
}
@@ -1,7 +0,0 @@
package org.saturnclient.ui2.resources;
import net.minecraft.util.Identifier;
public class Textures {
public static final Identifier CROSSHAIR_RANGE = Identifier.of("saturnclient", "textures/gui/hud/crosshair_range.png");
}
@@ -1,41 +0,0 @@
package org.saturnclient.ui2.screens;
import org.saturnclient.saturnclient.SaturnClient;
import org.saturnclient.saturnclient.SaturnClientConfig;
import org.saturnclient.saturnclient.menus.CloaksMenu;
import org.saturnclient.saturnclient.menus.HudEditor;
import org.saturnclient.saturnclient.menus.ModMenu;
import org.saturnclient.ui.Textures;
import org.saturnclient.ui2.Element;
import org.saturnclient.ui2.SaturnScreen;
import org.saturnclient.ui2.elements.Button;
import org.saturnclient.ui2.elements.ImageTexture;
import org.saturnclient.ui2.elements.TextureButton;
import org.saturnclient.ui2.anim.Fade;
import org.saturnclient.ui2.anim.SlideY;
public class ShiftMenu extends SaturnScreen {
public ShiftMenu() {
super("Shift Menu");
}
@Override
public void ui() {
draw(new ImageTexture(Textures.LOGO_TEXT).dimensions(98, 16).centerOffset(width, height, 0, -36).animation(new Fade(700)));
draw(new ImageTexture(SaturnClientConfig.getLogo()).dimensions(98, 98).centerOffset(width, height, 0, -80).animation(new SlideY(700, -40)));
Element button = new Button("Settings", () -> {
SaturnClient.client.setScreen(new ModMenu());
}).center(width, height);
draw(button);
draw(new TextureButton(Textures.COSMETICS, () -> {
client.setScreen(new CloaksMenu());
}).dimensions(button.height, button.height).centerOffset(width, height, (button.width / 2) + (button.height / 2) + 4, 0));
draw(new TextureButton(Textures.HUD_ICON, () -> {
client.setScreen(new HudEditor());
}).dimensions(button.height, button.height).centerOffset(width, height, -((button.width / 2) + (button.height / 2) + 4), 0));
}
}
@@ -1,63 +0,0 @@
package org.saturnclient.ui2.screens;
import org.saturnclient.saturnclient.SaturnClient;
import org.saturnclient.saturnclient.SaturnClientConfig;
import org.saturnclient.saturnclient.menus.CloaksMenu;
import org.saturnclient.saturnclient.menus.HudEditor;
import org.saturnclient.saturnclient.menus.ModMenu;
import org.saturnclient.ui.Textures;
import org.saturnclient.ui2.SaturnScreen;
import org.saturnclient.ui2.anim.Fade;
import org.saturnclient.ui2.anim.SlideY;
import org.saturnclient.ui2.elements.Button;
import org.saturnclient.ui2.elements.ImageTexture;
import org.saturnclient.ui2.elements.TextureButton;
import org.saturnclient.ui2.resources.Fonts;
import net.minecraft.client.gui.screen.multiplayer.MultiplayerScreen;
import net.minecraft.client.gui.screen.option.OptionsScreen;
import net.minecraft.client.gui.screen.world.SelectWorldScreen;
public class TitleMenu extends SaturnScreen {
public TitleMenu() {
super("Tile Menu");
}
@Override
public void ui() {
backgroundBlur = 0;
draw(new ImageTexture(Textures.LOGO_TEXT).dimensions(98, 16).centerOffset(width, height, 0, -40).animation(new Fade(700)));
draw(new ImageTexture(SaturnClientConfig.getLogo()).dimensions(98, 98).centerOffset(width, height, 0, -82).animation(new SlideY(700, -40)));
draw(new Button("SINGLEPLAYER", () -> {
client.setScreen(new SelectWorldScreen(new TitleMenu()));
}).scale(0.69f).dimensions(420, 52).centerOffset(width, height, 0, 0));
draw(new Button("MULTIPLAYER", () -> {
client.setScreen(new MultiplayerScreen(new TitleMenu()));
}).scale(0.69f).dimensions(420, 52).centerOffset(width, height, 0, 40));
draw(new Button("OPTIONS", () -> {
client.setScreen(new OptionsScreen(new TitleMenu(), SaturnClient.client.options));
}).scale(0.69f).dimensions(206, 52).centerOffset(width, height, -73, 80));
draw(new Button("QUIT", () -> {
client.scheduleStop();
}).scale(0.69f).dimensions(206, 52).centerOffset(width, height, 73, 80));
int s = Fonts.getHeight() + 20;
draw(new TextureButton(Textures.HUD_ICON, () -> {
client.setScreen(new HudEditor());
}).dimensions(s, s).centerHorizontal(width, height, -(s + 2), 8).animation(new SlideY(700, s + 8)));
draw(new TextureButton(Textures.SETTINGS, () -> {
SaturnClient.client.setScreen(new ModMenu());
}).dimensions(s, s).centerHorizontal(width, height, 0, 8).animation(new SlideY(700, s + 8)));
draw(new TextureButton(Textures.CLOAK, () -> {
client.setScreen(new CloaksMenu());
}).dimensions(s, s).centerHorizontal(width, height, s + 2, 8).animation(new SlideY(700, s + 8)));
}
}
@@ -1,12 +0,0 @@
{
"providers": [
{
"type": "reference",
"id": "saturnclient:panton"
},
{
"type": "reference",
"id": "saturnclient:panton_bold"
}
]
}
@@ -1,18 +0,0 @@
{
"providers": [
{
"type": "bitmap",
"file": "minecraft:font/custom/saturn.png",
"ascent": 7,
"height": 8,
"chars": ["\uE002"]
},
{
"type": "bitmap",
"file": "minecraft:font/custom/saturn_realistic.png",
"ascent": 7,
"height": 8,
"chars": ["\uE003"]
}
]
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

@@ -1,3 +0,0 @@
This font was designed by Ivan Petrov and published by Fontfabric.
The panton font is the regular version of: https://www.dafont.com/panton.font
@@ -1,12 +0,0 @@
{
"providers": [
{
"type": "ttf",
"file": "saturnclient:panton.ttf",
"shift": [0, 0],
"size": 9,
"oversample": 5.0,
"skip_missing": true
}
]
}

Some files were not shown because too many files have changed in this diff Show More