Compare commits

...
445 Commits
Author SHA1 Message Date
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
362 changed files with 10851 additions and 18546 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>
+3
View File
@@ -0,0 +1,3 @@
[submodule "common"]
path = common
url = https://github.com/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 |
| --------------------------------------------------------------------------------------- | ----------------------------------------------------------- |
+59 -8
View File
@@ -1,19 +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>
![image](https://github.com/user-attachments/assets/b0b23e72-c2eb-49aa-b49c-4d3cc4d60fcc)
<p align="center">
<img src="screenshot.png" alt="Saturn Client Screenshot"/>
</p>
# Credits
---
- Cloak rendering: `IIpho3nix` & `Dhyan Blum`
- Who Am I: `FokshaWasTaken`
- Panorama: https://www.planetminecraft.com/texture-pack/minecraft-cavesand-cliffs-part-ii-main-menu-panorama/
## ✨ About
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
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 -80
View File
@@ -1,89 +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://jitpack.io" }
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"
include modImplementation("dev.kosmx.player-anim:player-animation-lib-fabric:${project.player_anim}")
//You might want bendy-lib. playerAnimator will wrap it.
// include modRuntimeOnly("io.github.kosmx.bendy-lib:bendy-lib-fabric:${project.bendylib_version}")
}
repositories {
maven { url "https://maven.parchmentmc.org" }
maven {
name = "FelNull Maven"
url = "https://maven.felnull.dev"
}
maven {
name "KosmX's maven"
url 'https://maven.kosmx.dev/'
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'
}
}
processResources {
inputs.property "version", project.version
inputs.property "minecraft_version", project.minecraft_version
inputs.property "loader_version", project.loader_version
filteringCharset "UTF-8"
configure(subprojects.findAll { it.name != "common" }) {
dependencies {
implementation project(":common")
filesMatching("fabric.mod.json") {
expand "version": project.version,
"minecraft_version": project.minecraft_version,
"loader_version": project.loader_version
}
}
def targetJavaVersion = 21
tasks.withType(JavaCompile).configureEach {
it.options.encoding = "UTF-8"
if (targetJavaVersion >= 10 || JavaVersion.current().isJava10Compatible()) {
it.options.release.set(targetJavaVersion)
}
}
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 d73895ac7c
File diff suppressed because one or more lines are too long
+5 -11
View File
@@ -1,13 +1,7 @@
# Done to increase the memory available to gradle.
org.gradle.jvmargs=-Xmx1G
org.gradle.jvmargs=-Xmx3G
org.gradle.parallel=true
minecraft_version=1.21.4
yarn_mappings=1.21.4+build.7
loader_version=0.16.10
fabric_version=0.114.0+1.21.4
# 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_version = 0.0.4-beta1
maven_group = org.saturnclient
archives_base_name = saturnclient
player_anim = 2.0.3+1.21.4
mod_version=0.1.0-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 org.saturnclient.ui2.RenderScope;
public interface HudMod {
public ModDimensions getDimensions();
public void renderDummy(RenderScope scope);
public void renderHud(RenderScope scope);
}
@@ -1,54 +0,0 @@
package org.saturnclient.modules;
import java.util.Map;
import org.saturnclient.saturnclient.config.NamedProperty;
import org.saturnclient.saturnclient.config.Property;
public class ModDimensions {
public Property<Integer> x = Property.integer(0);
public Property<Integer> y = Property.integer(0);
public Property<Float> scale = Property.floatProp(1.0f);
public Property<Integer> bgColor = Property.color(0x00000000);
public Property<Integer> fgColor = Property.color(0xFFffffff);
public Property<Integer> radius = Property.integer(0);
public Property<Integer> font = Property.font(0);
public boolean renderBackground = true;
public int width = 0;
public int height = 0;
public NamedProperty<Map<String, Property<?>>> prop() {
return Property.namespace(Map.of(
"X", x,
"Y", y,
"Scale", scale,
"Background Color", bgColor,
"Foreground Color", fgColor,
"Corner Radius", radius,
"Font", font
)).named("In-Game Display");
}
public ModDimensions() {}
public ModDimensions(int width, int height) {
this.width = width;
this.height = height;
}
public ModDimensions(int x, int y, int width, int height) {
this.x.value = x;
this.y.value = y;
this.width = width;
this.height = height;
}
public ModDimensions(int x, int y, int width, int height, float scale) {
this.x.value = x;
this.y.value = y;
this.scale.value = scale;
this.width = width;
this.height = height;
}
}
@@ -1,28 +0,0 @@
package org.saturnclient.modules;
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
import org.saturnclient.modules.mods.*;
public class ModManager {
public static Module[] MODS = {
new Crosshair(),
new AutoSprint(),
new ArmorDisplay(),
new Fps(),
new PotionDisplay(),
new Coordinates(),
new Freelook(),
new Keystrokes()
};
public static void init() {
ClientTickEvents.END_CLIENT_TICK.register(client -> {
for (Module m : MODS) {
if (m.isEnabled()) {
m.tick();
}
}
});
}
}
@@ -1,52 +0,0 @@
package org.saturnclient.modules;
import org.saturnclient.saturnclient.config.ConfigManager;
import org.saturnclient.saturnclient.config.NamedProperty;
import org.saturnclient.ui2.resources.Textures;
import org.saturnclient.ui2.RenderScope;
import net.minecraft.util.Identifier;
public abstract class Module {
private final ModuleDetails details;
private final ConfigManager configManager;
public Module(ModuleDetails details, NamedProperty<?>... props) {
this.details = details;
configManager = new ConfigManager(details.name);
for (NamedProperty<?> prop : props) {
configManager.property(prop.name, prop.prop);
}
}
public void render(RenderScope scope) {}
public void tick() {}
public abstract boolean isEnabled();
public abstract void setEnabled(boolean e);
public final String getName() {
return details.name;
}
public Identifier getIconTexture() {
return Textures.getModIcon(details.namespace);
}
public ConfigManager getConfig() {
return configManager;
}
public String getDescription() {
return details.description;
}
public String[] getTags() {
return details.tags;
}
public String getVersion() {
return details.version;
}
}
@@ -1,39 +0,0 @@
package org.saturnclient.modules;
public class ModuleDetails {
public String name;
public String namespace;
public String description;
public String version;
public String[] tags = {};
public ModuleDetails(String name, String namespace) {
this.name = name;
this.namespace = namespace;
}
public ModuleDetails name(String name) {
this.name = name;
return this;
}
public ModuleDetails namespace(String namespace) {
this.namespace = namespace;
return this;
}
public ModuleDetails description(String description) {
this.description = description;
return this;
}
public ModuleDetails version(String version) {
this.version = version;
return this;
}
public ModuleDetails tags(String... tags) {
this.tags = tags;
return this;
}
}
@@ -1,6 +0,0 @@
# Credits
### Freelook
- https://github.com/Celibistrial/freelook
- https://modrinth.com/mod/freelook
**The other mods are coded by us, if you have a concern or we missed something feel free to submit a issue with the credits tag**
@@ -1,89 +0,0 @@
package org.saturnclient.modules.mods;
import org.saturnclient.modules.HudMod;
import org.saturnclient.modules.ModDimensions;
import org.saturnclient.modules.Module;
import org.saturnclient.modules.ModuleDetails;
import org.saturnclient.saturnclient.SaturnClient;
import org.saturnclient.saturnclient.config.Property;
import org.saturnclient.ui2.RenderScope;
import net.minecraft.entity.EquipmentSlot;
import net.minecraft.item.ItemStack;
import net.minecraft.item.Items;
public class ArmorDisplay extends Module implements HudMod {
private static Property<Boolean> enabled = Property.bool(false);
private static ModDimensions dimensions = new ModDimensions(40, 60);
public ArmorDisplay() {
super(new ModuleDetails("Armor Display", "armor")
.description("Displays armor durability")
.version("v0.1.0")
.tags("Utility"),
enabled.named("Enabled"),
dimensions.prop());
}
public void renderArmor(RenderScope scope, ItemStack helmet, ItemStack chestplate, ItemStack leggings, ItemStack boots) {
int row = 0;
if (!helmet.isEmpty()) {
scope.drawItem(helmet, 0, 0);
renderHealth(scope, row, helmet.getMaxDamage(), helmet.getDamage());
row++;
}
if (!chestplate.isEmpty()) {
scope.drawItem(chestplate, 0, 15 * row);
renderHealth(scope, row, chestplate.getMaxDamage(), chestplate.getDamage());
row++;
}
if (!leggings.isEmpty()) {
scope.drawItem(leggings, 0, 15 * row);
renderHealth(scope, row, leggings.getMaxDamage(), leggings.getDamage());
row++;
}
if (!boots.isEmpty()) {
scope.drawItem(boots, 0, 15 * row);
renderHealth(scope, row, boots.getMaxDamage(), boots.getDamage());
row++;
}
}
public void renderHealth(RenderScope scope, int i, int max_damage, int damage) {
if (max_damage > 0 ) {
scope.drawText(0.5f, "" + (max_damage - damage), 17, (15 * i) + 3, dimensions.font.value, dimensions.fgColor.value);
}
}
@Override
public void renderHud(RenderScope scope) {
renderArmor(scope,
SaturnClient.client.player.getEquippedStack(EquipmentSlot.HEAD),
SaturnClient.client.player.getEquippedStack(EquipmentSlot.CHEST),
SaturnClient.client.player.getEquippedStack(EquipmentSlot.LEGS),
SaturnClient.client.player.getEquippedStack(EquipmentSlot.FEET));
}
@Override
public void renderDummy(RenderScope scope) {
renderArmor(scope, new ItemStack(Items.DIAMOND_HELMET), new ItemStack(Items.DIAMOND_CHESTPLATE), new ItemStack(Items.DIAMOND_LEGGINGS), new ItemStack(Items.DIAMOND_BOOTS));
}
@Override
public ModDimensions getDimensions() {
return dimensions;
}
@Override
public boolean isEnabled() {
return enabled.value;
}
@Override
public void setEnabled(boolean e) {
enabled.value = e;
}
}
@@ -1,42 +0,0 @@
package org.saturnclient.modules.mods;
import java.util.Objects;
import org.saturnclient.modules.Module;
import org.saturnclient.modules.ModuleDetails;
import org.saturnclient.saturnclient.SaturnClient;
import org.saturnclient.saturnclient.config.Property;
public class AutoSprint extends Module {
private static Property<Boolean> enabled = Property.bool(false);
public AutoSprint() {
super(
new ModuleDetails("Auto Sprint", "sprint")
.description("Makes the player always sprint")
.tags("Movement")
.version("v0.1.0"),
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,75 +0,0 @@
package org.saturnclient.modules.mods;
import org.saturnclient.modules.HudMod;
import org.saturnclient.modules.ModDimensions;
import org.saturnclient.modules.Module;
import org.saturnclient.modules.ModuleDetails;
import org.saturnclient.saturnclient.SaturnClient;
import org.saturnclient.saturnclient.config.Property;
import org.saturnclient.ui2.RenderScope;
import org.saturnclient.ui2.resources.Fonts;
import net.minecraft.util.math.Vec3d;
public class Coordinates extends Module implements HudMod {
private static Property<Boolean> enabled = Property.bool(false);
private static Property<Integer> displayMethod = Property.select(0, "Flat", "Flat annotated", "Newline", "Newline annotated");
private static ModDimensions dimensions = new ModDimensions(40, 18);
public Coordinates() {
super(new ModuleDetails("Coordinates", "coords")
.description("Displays your current coordinates")
.version("v0.1.0")
.tags("Utility"),
enabled.named("Enabled"),
displayMethod.named("Display Method"),
dimensions.prop()
);
}
@Override
public void renderDummy(RenderScope scope) {
renderCoords(532, 69, 253, scope);
}
@Override
public void renderHud(RenderScope scope) {
Vec3d pos = SaturnClient.client.player.getPos();
renderCoords((int) pos.x, (int) pos.y, (int) pos.z, scope);
}
public void renderCoords(int x, int y, int z, RenderScope scope) {
String f = "%d %d %d"; // Flat
switch (displayMethod.value) {
case 1: // Flat annotated
f = "X: %d Y: %d Z: %d";
break;
case 2: // Newline
f = "%d\n%d\n%d";
break;
case 3: // Newline annotated
f = "X: %d\nY: %d\nZ: %d";
break;
}
String text = String.format(f, x, y, z);
scope.drawText(text,
0, 0, dimensions.font.value, dimensions.fgColor.value);
dimensions.width = Fonts.getWidth(text, dimensions.font.value);
dimensions.height = 18 * text.split("\n").length;
}
@Override
public boolean isEnabled() {
return enabled.value;
}
@Override
public ModDimensions getDimensions() {
return dimensions;
}
@Override
public void setEnabled(boolean e) {
enabled.value = e;
}
}
@@ -1,48 +0,0 @@
package org.saturnclient.modules.mods;
import org.saturnclient.modules.Module;
import org.saturnclient.modules.ModuleDetails;
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 = Property.bool(false);
public Crosshair() {
super(
new ModuleDetails("Crosshair", "crosshair")
.description("Changes the crosshair if you're aiming at a entity")
.tags("Visuals", "Utility")
.version("v0.1.0"),
enabled.named("Enabled"));
}
@Override
public void render(RenderScope scope) {
if (enabled.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,53 +0,0 @@
package org.saturnclient.modules.mods;
import org.saturnclient.modules.HudMod;
import org.saturnclient.modules.ModDimensions;
import org.saturnclient.modules.Module;
import org.saturnclient.modules.ModuleDetails;
import org.saturnclient.saturnclient.SaturnClient;
import org.saturnclient.saturnclient.config.Property;
import org.saturnclient.ui2.RenderScope;
import org.saturnclient.ui2.resources.Fonts;
public class Fps extends Module implements HudMod {
private static Property<Boolean> enabled = Property.bool(false);
private static ModDimensions dimensions = new ModDimensions(60, Fonts.getHeight());
public Fps() {
super(new ModuleDetails("FPS Display", "fps")
.description("Displays current FPS")
.version("v0.1.0")
.tags("Utility"),
enabled.named("Enabled"),
dimensions.prop());
}
@Override
public void renderHud(RenderScope scope) {
String text = String.valueOf(SaturnClient.client.getCurrentFps()) + " FPS";
scope.drawText(text, 0, 0, dimensions.font.value, dimensions.fgColor.value);
dimensions.width = Fonts.getWidth(text, dimensions.font.value);
}
@Override
public void renderDummy(RenderScope scope) {
String text = "369 FPS";
scope.drawText(text, 0, 0, dimensions.font.value, dimensions.fgColor.value);
dimensions.width = Fonts.getWidth(text, dimensions.font.value);
}
@Override
public boolean isEnabled() {
return enabled.value;
}
@Override
public void setEnabled(boolean e) {
enabled.value = e;
}
@Override
public ModDimensions getDimensions() {
return dimensions;
}
}
@@ -1,71 +0,0 @@
package org.saturnclient.modules.mods;
import org.lwjgl.glfw.GLFW;
import org.saturnclient.modules.Module;
import org.saturnclient.modules.ModuleDetails;
import org.saturnclient.saturnclient.SaturnClient;
import org.saturnclient.saturnclient.config.Property;
import net.minecraft.client.option.Perspective;
public class Freelook extends Module {
private static Property<Boolean> enabled = Property.bool(false);
private static Property<Boolean> toggle = Property.bool(true);
public static Property<Integer> freelookKey = Property.keybinding(GLFW.GLFW_KEY_H);
public static boolean isFreeLooking = false;
private static boolean was1stPerson = false;
public Freelook() {
super(
new ModuleDetails("Freelook", "freelook")
.description("Allows you to look around freely without moving your character")
.tags("Movement", "Camera")
.version("0.1.0"),
enabled.named("Enabled"),
toggle.named("Toggle freelook"),
freelookKey.named("Freelook Keybinding")
);
}
@Override
public void tick() {
if (toggle.value) {
if (freelookKey.wasKeyPressed()) {
if (isFreeLooking) {
stopFreelook();
} else {
startFreelook();
}
}
} else if (freelookKey.isKeyPressed()) {
if (!isFreeLooking) {
startFreelook();
}
} else if (isFreeLooking) {
stopFreelook();
}
}
private void startFreelook() {
isFreeLooking = true;
was1stPerson = SaturnClient.client.options.getPerspective() == Perspective.FIRST_PERSON;
SaturnClient.client.options.setPerspective(Perspective.THIRD_PERSON_BACK);
}
private void stopFreelook() {
if (was1stPerson) {
SaturnClient.client.options.setPerspective(Perspective.FIRST_PERSON);
}
isFreeLooking = false;
}
@Override
public void setEnabled(boolean e) {
enabled.value = e;
}
@Override
public boolean isEnabled() {
return enabled.value;
}
}
@@ -1,139 +0,0 @@
package org.saturnclient.modules.mods;
import org.saturnclient.modules.Module;
import org.saturnclient.modules.HudMod;
import org.saturnclient.modules.ModDimensions;
import org.saturnclient.saturnclient.SaturnClient;
import org.saturnclient.saturnclient.config.Property;
import org.saturnclient.ui2.RenderScope;
import org.saturnclient.modules.ModuleDetails;
public class Keystrokes extends Module implements HudMod {
public static Property<Boolean> enabled = Property.bool(false);
public static Property<Boolean> showMouse = Property.bool(true);
public static Property<Boolean> showSpace = Property.bool(false);
public static ModDimensions dimensions = new ModDimensions(78, 46);
private boolean w = false;
private boolean a = false;
private boolean s = false;
private boolean d = false;
private boolean lmb = false;
private boolean rmb = false;
private boolean space = false;
private static Property<Integer> clickBg = Property.color(0xFFCCCCCC);
private static Property<Integer> clickFg = Property.color(0xFFFFFFFF);
public Keystrokes() {
super(new ModuleDetails("Keystrokes", "keystrokes")
.description("Displays the keystrokes for movement")
.version("v0.1.0")
.tags("Utility"),
enabled.named("Enabled"),
showMouse.named("Show mouse clicks"),
showSpace.named("Show space clicks"),
dimensions.prop(),
clickFg.named("Clicked fg"),
clickBg.named("Clicked bg"));
dimensions.renderBackground = false;
}
@Override
public void tick() {
w = SaturnClient.client.options.forwardKey.isPressed();
a = SaturnClient.client.options.leftKey.isPressed();
s = SaturnClient.client.options.backKey.isPressed();
d = SaturnClient.client.options.rightKey.isPressed();
lmb = SaturnClient.client.options.attackKey.isPressed();
rmb = SaturnClient.client.options.useKey.isPressed();
space = SaturnClient.client.options.jumpKey.isPressed();
}
@Override
public void renderDummy(RenderScope scope) {
dimensions.height = 54; // base height with padding considered
// WASD keys (3px padding between each key)
renderKey(scope, true, 'W', 27, 0); // Top center
renderKey(scope, false, 'A', 0, 27); // Bottom left
renderKey(scope, false, 'S', 27, 27); // Bottom center
renderKey(scope, false, 'D', 54, 27); // Bottom right
if (showMouse.value) {
renderKeyM(scope, true, "LMB", 0, 54); // Below A/S/D
renderKeyM(scope, false, "RMB", 40, 54); // With 3px between
dimensions.height += 27;
}
if (showSpace.value) {
renderKeySpace(scope, false, 0, dimensions.height);
dimensions.height += 22;
}
}
@Override
public void renderHud(RenderScope scope) {
dimensions.height = 54; // base height with padding considered
// WASD keys (3px padding between each key)
renderKey(scope, w, 'W', 27, 0); // Top center
renderKey(scope, a, 'A', 0, 27); // Bottom left
renderKey(scope, s, 'S', 27, 27); // Bottom center
renderKey(scope, d, 'D', 54, 27); // Bottom right
if (showMouse.value) {
renderKeyM(scope, lmb, "LMB", 0, 54); // Below A/S/D
renderKeyM(scope, rmb, "RMB", 40, 54); // With 3px between
dimensions.height += 27;
}
if (showSpace.value) {
renderKeySpace(scope, space, 0, dimensions.height);
dimensions.height += 22;
}
}
// Consistent 3px padding applied below:
private void renderKeySpace(RenderScope scope, boolean isPressed, int x, int y) {
int height = 19;
scope.drawRoundedRectangle(x, y, dimensions.width, height, dimensions.radius.value, isPressed ? clickBg.value : dimensions.bgColor.value);
// A 30-pixel line centered vertically with 3px top padding
int lineY = y + (height - 1) / 2;
scope.drawRect(x + (dimensions.width - 30) / 2, lineY, 30, 1, isPressed ? clickFg.value : dimensions.fgColor.value);
}
private void renderKeyM(RenderScope scope, boolean isPressed, String c, int x, int y) {
int width = 38, height = 24;
scope.drawRoundedRectangle(x, y, width, height, dimensions.radius.value, isPressed ? clickBg.value : dimensions.bgColor.value);
// Apply padding: 3px top and left -> add slight offset to x/y
scope.drawText(0.6f, c, x + 9, y + 7, dimensions.font.value, isPressed ? clickFg.value : dimensions.fgColor.value);
}
private void renderKey(RenderScope scope, boolean isPressed, char c, int x, int y) {
int size = 24;
scope.drawRoundedRectangle(x, y, size, size, dimensions.radius.value, isPressed ? clickBg.value : dimensions.bgColor.value);
// Center the character with 3px top padding
scope.drawText(0.6f, String.valueOf(c), x + 9, y + 7, dimensions.font.value, isPressed ? clickFg.value : dimensions.fgColor.value);
}
@Override
public boolean isEnabled() {
return enabled.value;
}
@Override
public ModDimensions getDimensions() {
return dimensions;
}
@Override
public void setEnabled(boolean e) {
enabled.value = e;
}
}
@@ -1,105 +0,0 @@
package org.saturnclient.modules.mods;
import java.util.Arrays;
import org.saturnclient.modules.HudMod;
import org.saturnclient.modules.ModDimensions;
import org.saturnclient.modules.Module;
import org.saturnclient.modules.ModuleDetails;
import org.saturnclient.saturnclient.SaturnClient;
import org.saturnclient.saturnclient.config.Property;
import org.saturnclient.ui2.RenderScope;
import net.minecraft.client.resource.language.I18n;
import net.minecraft.client.texture.Sprite;
import net.minecraft.client.texture.StatusEffectSpriteManager;
import net.minecraft.entity.effect.StatusEffect;
import net.minecraft.entity.effect.StatusEffectInstance;
import net.minecraft.registry.Registries;
import net.minecraft.registry.entry.RegistryEntry;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.MathHelper;
public class PotionDisplay extends Module implements HudMod {
public static Property<Boolean> enabled = Property.bool(false);
private static ModDimensions dimensions = new ModDimensions(60, 0);
private final StatusEffectInstance[] dummyEffects = {
new StatusEffectInstance(Registries.STATUS_EFFECT.getEntry(Identifier.ofVanilla("speed")).get(), 12000, 2),
new StatusEffectInstance(Registries.STATUS_EFFECT.getEntry(Identifier.ofVanilla("strength")).get(), 12000, 2),
new StatusEffectInstance(Registries.STATUS_EFFECT.getEntry(Identifier.ofVanilla("fire_resistance")).get(), 12000, 2),
};
public PotionDisplay() {
super(new ModuleDetails("Potion Display", "potion")
.description("Displays potion effects")
.version("v0.1.0")
.tags("Utility"),
enabled.named("Enabled"),
dimensions.prop());
}
private void renderEffects(RenderScope scope, Iterable<StatusEffectInstance> effects) {
int row = 0;
StatusEffectSpriteManager statusEffectSpriteManager = SaturnClient.client.getStatusEffectSpriteManager();
for (StatusEffectInstance effect : effects) {
if (effect.shouldShowIcon()) {
RegistryEntry<StatusEffect> registryEntry = effect.getEffectType();
Sprite sprite = statusEffectSpriteManager.getSprite(registryEntry);
scope.drawSpriteStretched(sprite, 0, 18 * row, 16, 16);
scope.drawText(0.5f, getDurationAsString(effect), 18, 18 * row + 3, dimensions.font.value, -1);
row++;
}
}
dimensions.height = 18 * row;
}
@Override
public void renderHud(RenderScope scope) {
renderEffects(scope, SaturnClient.client.player.getStatusEffects());
}
@Override
public void renderDummy(RenderScope scope) {
renderEffects(scope, Arrays.asList(dummyEffects));
}
// This code was from: https://github.com/magicus/statuseffecttimer/blob/master/src/main/java/se/icus/mag/statuseffecttimer/mixin/StatusEffectTimerMixin.java
private String getDurationAsString(StatusEffectInstance statusEffectInstance) {
if (statusEffectInstance.isInfinite()) {
return I18n.translate("effect.duration.infinite");
}
int ticks = MathHelper.floor((float) statusEffectInstance.getDuration());
int totalSeconds = ticks / 20;
if (totalSeconds >= 3600) {
return totalSeconds / 3600 + "h";
} else if (totalSeconds >= 60) {
int minutes = totalSeconds / 60;
int seconds = totalSeconds % 60;
return String.format("%d:%02d", minutes, seconds);
} else {
return String.valueOf(totalSeconds) + "s";
}
}
@Override
public ModDimensions getDimensions() {
return dimensions;
}
@Override
public boolean isEnabled() {
return enabled.value;
}
@Override
public void setEnabled(boolean e) {
enabled.value = e;
}
}
@@ -1,62 +0,0 @@
package org.saturnclient.modules.mods.mixins;
import net.minecraft.client.network.ClientPlayerEntity;
import net.minecraft.entity.Entity;
import net.minecraft.util.math.MathHelper;
import org.saturnclient.modules.mods.Freelook;
import org.saturnclient.modules.mods.utils.CameraOverriddenEntity;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(Entity.class)
public class EntityMixin implements CameraOverriddenEntity {
@Unique
private float cameraPitch;
@Unique
private float cameraYaw;
@Inject(method = "changeLookDirection", at = @At("HEAD"), cancellable = true)
public void changeCameraLookDirection(double xDelta, double yDelta, CallbackInfo ci) {
//noinspection ConstantValue// IntelliJ is incorrect here, this code block is reachable
if (Freelook.isFreeLooking && (Object) this instanceof ClientPlayerEntity) {
double pitchDelta = (yDelta * 0.15);
double yawDelta = (xDelta * 0.15);
this.cameraPitch = MathHelper.clamp(this.cameraPitch + (float) pitchDelta, -90.0f, 90.0f);
this.cameraYaw += (float) yawDelta;
ci.cancel();
}
}
@Override
@Unique
public float freelook$getCameraPitch() {
return this.cameraPitch;
}
@Override
@Unique
public float freelook$getCameraYaw() {
return this.cameraYaw;
}
@Override
@Unique
public void freelook$setCameraPitch(float pitch) {
this.cameraPitch = pitch;
}
@Override
@Unique
public void freelook$setCameraYaw(float yaw) {
this.cameraYaw = yaw;
}
}
@@ -1,58 +0,0 @@
package org.saturnclient.modules.mods.mixins;
import org.saturnclient.modules.HudMod;
import org.saturnclient.modules.ModDimensions;
import org.saturnclient.modules.ModManager;
import org.saturnclient.saturnclient.SaturnClient;
import org.saturnclient.saturnclient.menus.HudEditor;
import org.saturnclient.saturnclient.mixin.DrawContextAccessor;
import org.saturnclient.ui2.RenderScope;
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.font.TextRenderer;
import net.minecraft.client.gui.DrawContext;
import net.minecraft.client.gui.hud.InGameHud;
import net.minecraft.client.render.RenderLayer;
import net.minecraft.client.render.RenderTickCounter;
@Mixin(InGameHud.class)
public class RenderMixin {
@Inject(method = "renderChat", at = @At("TAIL"))
public void renderMods(DrawContext context, RenderTickCounter tickCounter, CallbackInfo ci) {
TextRenderer textRenderer = SaturnClient.client.textRenderer;
if (textRenderer != null &&
!(SaturnClient.client.currentScreen instanceof HudEditor)) {
RenderScope renderScope = new RenderScope(context.getMatrices(),
((DrawContextAccessor) context).getVertexConsumers());
for (org.saturnclient.modules.Module m : ModManager.MODS) {
if (m instanceof HudMod && m.isEnabled()) {
ModDimensions dim = ((HudMod) m).getDimensions();
renderScope.matrices.push();
renderScope.matrices.translate(dim.x.value, (float) dim.y.value, 0);
renderScope.matrices.scale(dim.scale.value, dim.scale.value, 1.0f);
if (dim.renderBackground) {
renderScope.drawRoundedRectangle(0, 0, dim.width, dim.height, dim.radius.value,
dim.bgColor.value);
}
renderScope.setRenderLayer(RenderLayer::getGuiTextured);
((HudMod) m).renderHud(renderScope);
renderScope.matrices.pop();
} else {
m.render(renderScope);
}
}
}
}
}
@@ -1,9 +0,0 @@
package org.saturnclient.modules.mods.utils;
public interface CameraOverriddenEntity {
float freelook$getCameraPitch();
float freelook$getCameraYaw();
void freelook$setCameraPitch(float pitch);
void freelook$setCameraYaw(float yaw);
}
@@ -1,86 +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.util.math.MatrixStack;
import org.saturnclient.modules.ModManager;
import org.saturnclient.saturnclient.auth.Auth;
import org.saturnclient.saturnclient.config.ConfigManager;
import org.saturnclient.saturnclient.cosmetics.Emotes;
import org.saturnclient.saturnclient.cosmetics.Hats;
import org.saturnclient.saturnclient.cosmetics.cloaks.Cloaks;
import org.saturnclient.saturnclient.event.KeyInputHandler;
import org.saturnclient.saturnclient.mixin.DrawContextAccessor;
import org.saturnclient.ui2.RenderScope;
import org.saturnclient.ui2.SaturnScreen;
import org.saturnclient.ui2.elements.ImageTexture;
import org.saturnclient.ui2.resources.Textures;
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();
SaturnClientConfig.init();
ModManager.init();
ClientLifecycleEvents.CLIENT_STOPPING.register(_o -> ConfigManager.save());
SpecialModelLoaderEvents.LOAD_SCOPE.register(() -> {
return (resourceManager, location) -> MOD_ID.equals(location.getNamespace());
});
ScreenEvents.AFTER_INIT.register((client, screen, scaledWidth, scaledHeight) -> {
if (screen != null && !(screen instanceof TitleMenu)) {
if (screen instanceof SaturnScreen) {
((SaturnScreen) screen).draw(new ImageTexture(Textures.LOGO_TEXT_BIG).dimensions(180, 18)
.position(screen.width - 180 - 20, screen.height - 18 - 20));
} else {
ScreenEvents.afterRender(screen).register((screen1, context, mouseX, mouseY, tickDelta) -> {
RenderScope renderScope = new RenderScope(context.getMatrices(),
((DrawContextAccessor) context).getVertexConsumers());
int width = context.getScaledWindowWidth();
int height = context.getScaledWindowHeight();
float scaleX = (float) screen1.width / width;
float scaleY = (float) screen1.height / height;
MatrixStack matrixStack = context.getMatrices();
matrixStack.push();
matrixStack.translate(width - 95, height - 14, 0);
matrixStack.scale(0.5f / scaleX, 0.5f / scaleY, 1.0f);
renderScope.drawTexture(Textures.LOGO_TEXT_BIG, 0, 0, 0, 0, 180, 18);
matrixStack.pop();
});
}
}
});
KeyInputHandler.register();
Emotes.initialize();
if (Auth.authenticate()) {
Cloaks.initialize();
Hats.initialize();
LOGGER.info(MOD_ID + " initialization complete");
} else {
LOGGER.error("Failed to authenticate with the server");
}
}
}
@@ -1,50 +0,0 @@
package org.saturnclient.saturnclient;
import org.saturnclient.saturnclient.config.Property;
import org.lwjgl.glfw.GLFW;
import org.saturnclient.saturnclient.config.ConfigManager;
import org.saturnclient.ui2.resources.Textures;
import net.minecraft.util.Formatting;
import net.minecraft.util.Identifier;
public class SaturnClientConfig {
public static ConfigManager config;
public static Property<Boolean> realisticLogo = Property.bool(false);
public static Property<Boolean> saturnTitleScreen = Property.bool(true);
public static Property<Integer> openEmoteWheel = Property.keybinding(GLFW.GLFW_KEY_B);
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("Realistic logo", realisticLogo);
config.property("Saturn client title screen", saturnTitleScreen);
config.property("Open Emote Wheel", openEmoteWheel);
}
}
@@ -1,292 +0,0 @@
package org.saturnclient.saturnclient.auth;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents;
import net.minecraft.client.network.AbstractClientPlayerEntity;
import net.minecraft.util.Identifier;
import org.saturnclient.saturnclient.cosmetics.Hats;
import org.saturnclient.saturnclient.cosmetics.cloaks.Cloaks;
import org.saturnclient.ui2.SaturnScreen;
import org.saturnclient.ui2.elements.Notification;
import org.saturnclient.ui2.elements.Notification.NotificationKind;
import dev.kosmx.playerAnim.api.layered.AnimationStack;
import dev.kosmx.playerAnim.minecraftApi.PlayerAnimationAccess;
import dev.kosmx.playerAnim.minecraftApi.PlayerAnimationRegistry;
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;
case "successful_transaction":
String cloak = parser.getStringOrNull("cloak");
String hat = parser.getStringOrNull("hat");
if (cloak != null) {
Cloaks.availableCloaks.add(cloak);
}
if (hat != null) {
Hats.availableHats.add(hat);
}
case "update_cloak":
String targetUuid = parser.getStringOrNull("uuid");
String targetCloak = parser.getStringOrNull("cloak");
if (targetUuid != null && targetCloak != null) {
SaturnPlayer player = players.get(targetUuid);
if (player != null) {
player.cloak = targetCloak;
}
}
break;
case "update_hat":
targetUuid = parser.getStringOrNull("uuid");
String targetHat = parser.getStringOrNull("hat");
if (targetUuid != null && targetHat != null) {
SaturnPlayer player = players.get(targetUuid);
if (player != null) {
player.hat = targetHat;
}
}
break;
case "emote":
targetUuid = parser.getStringOrNull("uuid");
String emote = parser.getStringOrNull("name");
for (AbstractClientPlayerEntity player : SaturnClient.client.world.getPlayers()) {
if (player.getUuidAsString().replace("-", "").equals(targetUuid)) {
AnimationStack animationStack = PlayerAnimationAccess.getPlayerAnimLayer(player);
if (emote != null && !emote.isEmpty()) {
if (animationStack.isActive() && animationStack.getPriority() == 1000) {
animationStack.removeLayer(1000);
}
animationStack.addAnimLayer(1000,
PlayerAnimationRegistry
.getAnimation(Identifier.of("saturnclient", emote))
.playAnimation());
} else {
animationStack.removeLayer(1000);
}
}
}
break;
default:
if (parser.error != null) {
SaturnClient.LOGGER.error("Error from the server: " + parser.error);
if (SaturnClient.client.currentScreen instanceof SaturnScreen) {
String[] a = parser.error.replace("!", "").split(": ");
((SaturnScreen) SaturnClient.client.currentScreen)
.draw(new Notification(SaturnClient.client.currentScreen.width,
SaturnClient.client.currentScreen.height, NotificationKind.Error,
a[0], a[1]));
}
}
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 + "@notify=" + stringifyPlayers());
} catch (Exception e) {
SaturnClient.LOGGER.error("Request failed", e);
}
}
public static void setHat(String hatName) {
try {
Network.write("set_hat@hat=" + hatName + "@notify=" + stringifyPlayers());
} catch (Exception e) {
SaturnClient.LOGGER.error("Request failed", e);
}
}
public static void emote(String name) {
try {
Network.write("emote@name=" + name + "@notify=" + stringifyPlayers());
} catch (Exception e) {
SaturnClient.LOGGER.error("Request failed", e);
}
}
public static String stringifyPlayers() {
return players.keySet().stream()
.filter(key -> key != uuid)
.collect(Collectors.joining("$"));
}
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 buyCloak(String name) {
try {
Network.write("buy_cloak@cloak=" + name);
} catch (Exception e) {
SaturnClient.LOGGER.error("Request failed", e);
}
}
public static void buyHat(String name) {
try {
Network.write("buy_hat@hat=" + name);
} catch (Exception e) {
SaturnClient.LOGGER.error("Request failed", e);
}
}
}
@@ -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,71 +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 String getStringOrNull(String i) throws IOException {
String val = params.get(i);
return val;
}
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,226 +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 static JsonObject cachedThemeJson = null;
private Map<String, Property<?>> currentMap;
private String namespace;
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 = Property.namespace(currentMap);
config.property(namespace, namespaceProperty);
// Update the parent's state
config.currentMap.put(namespace, namespaceProperty);
System.out.println("Created sub-namespace: " + namespace);
}
public void sub(String namespace) {
Property<Map<String, Property<?>>> namespaceProperty = Property.namespace(currentMap);
property(namespace, namespaceProperty);
}
// Generic method to store any type of property
public <T> Property<T> property(String name, Property<T> value) {
currentMap.put(name, value);
loadProp(name, value);
return value;
}
@SuppressWarnings("unchecked")
public <T> void loadProp(String name, Property<T> prop) {
if (cachedThemeJson != null) {
JsonElement element = cachedThemeJson.get(namespace);
if (element != null && element.isJsonObject()) {
JsonObject theme = element.getAsJsonObject();
JsonElement value = theme.get(name);
if (value != null && prop.matchesJson(value)) {
switch (prop.getType()) {
case BOOLEAN:
((Property<Boolean>) prop).setValue(value.getAsBoolean());
break;
case INTEGER:
((Property<Integer>) prop).setValue(value.getAsInt());
break;
case FLOAT:
((Property<Float>) prop).setValue(value.getAsFloat());
break;
case STRING:
((Property<String>) prop).setValue(value.getAsString());
break;
case HEX:
((Property<Integer>) prop).setValue(value.getAsInt());
break;
default:
break;
}
}
}
} else {
load();
}
}
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()) {
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) {
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 {
if (p.value instanceof Integer) {
((Property<Integer>) p).value = c.getAsInt();
} else if (p.value instanceof String) {
((Property<String>) p).value = c.getAsString();
} else if (p.value instanceof Float) {
((Property<Float>) p).value = c.getAsFloat();
} else if (p.value instanceof Boolean) {
((Property<Boolean>) p).value = c.getAsBoolean();
} else {
SaturnClient.LOGGER.warn(
"Unknown property type for: " + propertyName);
}
}
} 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;
// Convert the property value based on its type
switch (property.getType()) {
case NAMESPACE:
// Handle nested namespace
JsonObject nestedConfig = new JsonObject();
Map<String, Property<?>> nestedProperties = (Map<String, Property<?>>) property.value;
saveProperties(nestedConfig, nestedProperties);
propertyValue = nestedConfig;
break;
default:
if (property.value instanceof Integer) {
propertyValue = new JsonPrimitive((Integer) property.value);
} else if (property.value instanceof String) {
propertyValue = new JsonPrimitive((String) property.value);
} else if (property.value instanceof Float) {
propertyValue = new JsonPrimitive((Float) property.value);
} else if (property.value instanceof Boolean) {
propertyValue = new JsonPrimitive((Boolean) property.value);
} else {
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,258 +0,0 @@
package org.saturnclient.saturnclient.config;
import java.util.Map;
import org.lwjgl.glfw.GLFW;
import org.saturnclient.saturnclient.SaturnClient;
import com.google.gson.JsonElement;
import com.google.gson.JsonPrimitive;
public class Property<T> {
public enum PropertyType {
BOOLEAN,
INTEGER,
FLOAT,
STRING,
HEX,
NAMESPACE,
SELECT,
KEYBINDING,
}
public T value;
public boolean isReset = false;
private T defaultValue;
private String[] availableValues;
private PropertyType type;
private boolean wasPressedLastTick = false;
private Property(T value, PropertyType type) {
this.value = value;
this.defaultValue = value;
this.type = type;
}
public static <T> Property<T> from(T value) {
if (value instanceof Boolean) {
return new Property<>(value, PropertyType.BOOLEAN);
} else if (value instanceof Integer) {
return new Property<>(value, PropertyType.INTEGER);
} else if (value instanceof Float) {
return new Property<>(value, PropertyType.FLOAT);
} else if (value instanceof String) {
return new Property<>(value, PropertyType.STRING);
} else if (valueIsNamespace(value)) {
return new Property<>(value, PropertyType.NAMESPACE);
} else {
return null;
}
}
public Property<T> copy() {
return new Property<>(this.value, this.type);
}
public static Property<Integer> font(int value) {
return select(value, "Default", "Inter", "Inter bold");
}
public static Property<Integer> select(Integer value, String... availableValues) {
Property<Integer> property = new Property<>(value, PropertyType.SELECT);
property.availableValues = availableValues;
return property;
}
public static Property<Map<String, Property<?>>> namespace(Map<String, Property<?>> value) {
return new Property<>(value, PropertyType.NAMESPACE);
}
public static Property<Boolean> bool(boolean value) {
return new Property<>(value, PropertyType.BOOLEAN);
}
public static Property<Integer> integer(int value) {
return new Property<>(value, PropertyType.INTEGER);
}
public static Property<Float> floatProp(float value) {
return new Property<>(value, PropertyType.FLOAT);
}
public static Property<String> string(String value) {
return new Property<>(value, PropertyType.STRING);
}
public static Property<Integer> color(int value) {
return new Property<>(value, PropertyType.HEX);
}
public static Property<Integer> keybinding(int value) {
return new Property<>(value, PropertyType.KEYBINDING);
}
public void next() {
if ((Integer) value < availableValues.length - 1)
setValue((Integer) value + 1);
else
setValue(0);
}
public void prev() {
if ((Integer) value > 0)
setValue((Integer) value - 1);
else
setValue(availableValues.length - 1);
}
public void setSelection(int selection) {
if (selection >= 0 && selection < availableValues.length && type == PropertyType.SELECT) {
setValue(selection);
}
}
public String getSelection() {
if (type == PropertyType.SELECT) {
return availableValues[(Integer) value];
} else {
return null;
}
}
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;
if (type == PropertyType.HEX && primitive.isString())
return true;
} 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);
}
public boolean isKeyPressed() {
return (Integer) value == -1 ? false
: GLFW.glfwGetKey(SaturnClient.client.getWindow().getHandle(),
(Integer) (Object) value) == GLFW.GLFW_PRESS && SaturnClient.client.currentScreen == null;
}
public boolean wasKeyPressed() {
boolean isPressed = this.isKeyPressed();
wasPressedLastTick = isPressed && !wasPressedLastTick;
return wasPressedLastTick;
}
@SuppressWarnings("unchecked")
public void load(String name, JsonElement element) {
if (element != null && element.isJsonObject()) {
JsonElement value = element.getAsJsonObject().get(name);
if (value != null) {
switch (this.getType()) {
case BOOLEAN:
((Property<Boolean>) this).setValue(value.getAsBoolean());
break;
case INTEGER:
JsonPrimitive primitive = value.getAsJsonPrimitive();
if (primitive.isNumber())
((Property<Integer>) this).setValue(value.getAsInt());
else if (primitive.isString())
((Property<Integer>) this).setValue(parseHexToInt(value.getAsString()));
break;
case FLOAT:
((Property<Float>) this).setValue(value.getAsFloat());
break;
case STRING:
((Property<String>) this).setValue(value.getAsString());
break;
case HEX:
((Property<Integer>) this).setValue(parseHexToInt(value.getAsString()));
break;
default:
break;
}
}
}
}
public static int parseHexToInt(String hex) {
hex = hex.replaceAll("#", "");
if (hex.length() == 6) {
hex = "FF" + hex;
} else if (hex.length() != 8) {
throw new IllegalArgumentException("Hex string must be 6 or 8 characters long");
}
return (int) Long.parseLong(hex, 16);
}
}
@@ -1,93 +0,0 @@
package org.saturnclient.saturnclient.config;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.saturnclient.saturnclient.SaturnClient;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
public class ThemeManager {
private static File themeFile = new File(
SaturnClient.client.runDirectory,
"saturn.theme.json");
private static List<ThemeManager> theme = new ArrayList<>();
private static JsonObject cachedThemeJson = null;
private Map<String, Map<String, Object>> states;
private Map<String, Property<?>> currentStyling;
private String namespace;
public ThemeManager(String namespace, String... states) {
this.namespace = namespace;
this.states = new LinkedHashMap<>();
this.currentStyling = new LinkedHashMap<>();
this.states.put(null, new LinkedHashMap<>());
for (String s : states) {
this.states.put(s, new LinkedHashMap<>());
}
theme.add(this);
}
public void setState(String state) {
for (Map.Entry<String, Object> tProp : states.get(state).entrySet()) {
currentStyling.get(tProp.getKey()).setValue(tProp.getValue());
}
}
public void applyState(String state) {
for (Map.Entry<String, Object> tProp : states.get(state).entrySet()) {
if (!states.get(null).get(tProp.getKey()).equals(tProp.getValue()))
currentStyling.get(tProp.getKey()).setValue(tProp.getValue());
}
}
public <T> Property<T> property(String name, Property<T> prop) {
for (Map.Entry<String, Map<String, Object>> state : this.states.entrySet()) {
Property<?> property = prop.copy();
if (cachedThemeJson == null) {
load();
}
String stateName = state.getKey();
property.load(name, cachedThemeJson.get(stateName != null ? namespace + "@" + stateName : namespace));
state.getValue().put(name, property.value);
}
currentStyling.put(name, prop);
return prop;
}
public <T> void propertyStateDefault(String state, String name, T value) {
Property<?> prop = currentStyling.get(name).copy();
prop.setValue(value, value);
if (cachedThemeJson == null) {
load();
}
prop.load(name, cachedThemeJson.get(state != null ? namespace + "@" + state : namespace));
states.get(state).put(name, prop.value);
}
public static void load() {
try {
SaturnClient.LOGGER.info("Starting to load theme...");
if (!themeFile.exists()) {
themeFile.createNewFile();
Files.write(themeFile.toPath(), "{}".getBytes());
}
cachedThemeJson = JsonParser.parseString(
new String(Files.readAllBytes(themeFile.toPath()))).getAsJsonObject();
SaturnClient.LOGGER.info("Theme loaded");
} catch (IOException e) {
SaturnClient.LOGGER.error("Error reading the theme file", e);
}
}
}
@@ -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,17 +0,0 @@
package org.saturnclient.saturnclient.cosmetics;
import java.util.ArrayList;
import java.util.List;
public class Emotes {
public static List<String> availableEmotes = new ArrayList<>();
public static void initialize() {
availableEmotes.add("tpose");
availableEmotes.add("back_flip");
availableEmotes.add("facepalm");
availableEmotes.add("cry");
availableEmotes.add("giga_chad");
availableEmotes.add("front_flip");
}
}
@@ -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 && !state.invisible) {
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 final String[] ALL_HATS = { "horns_black", "horns_white", "halo", "halo_black" };
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);
}
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,212 +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 {
public static final String[] ALL_CLOAKS = { "glitch", "mercedes_flow", "crimson_mark", };
private static final String[] ANIMATED_CLOAKS = { "glitch", "mercedes_flow" };
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);
SaturnClient.LOGGER.info("Cloak set to " + cloakName);
}
/**
* Sets cloak of the current player
*
* @param cloakName Name of the cloak file to load
*/
public static void setCloak(String cloakName) {
if (availableCloaks.contains(cloakName)) {
setCloak(Auth.uuid, 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,140 +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.saturnclient.mixin.DrawContextAccessor;
import org.saturnclient.ui2.RenderScope;
import org.saturnclient.ui2.SaturnScreen;
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) {
SaturnScreen.ROTATING_PANORAMA_RENDERER.render(context, this.width, this.height, 1.0F, delta);
}
RenderScope scope = new RenderScope(context.getMatrices(), ((DrawContextAccessor) context).getVertexConsumers());
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(scope);
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,22 +0,0 @@
package org.saturnclient.saturnclient.mixin;
import org.saturnclient.saturnclient.SaturnClient;
import org.saturnclient.saturnclient.SaturnClientConfig;
import org.saturnclient.ui2.screens.TitleMenu;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import net.minecraft.client.gui.screen.TitleScreen;
@Mixin(TitleScreen.class)
public class TitleScreenMixin {
@Inject(method = "init", at = @At("HEAD"), cancellable = true)
private void onInit(CallbackInfo ci) {
if (SaturnClientConfig.saturnTitleScreen.value) {
SaturnClient.client.setScreen(new TitleMenu());
ci.cancel();
}
}
}
@@ -1,83 +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 Integer duration = null; // Element duration, will fade out if not null
public void render(RenderScope renderScope, ElementContext ctx) {
}
public void click(int mouseX, int mouseY) {
}
public void scroll(int mouseX, int mouseY, double horizontalAmount, double verticalAmount) {
}
public void keyPressed(int keyCode, int scanCode, int modifiers) {
}
public void charTyped(char typedChar) {
}
public Element dimensions(int width, int height) {
this.width = width;
this.height = height;
return this;
}
public Element position(int x, int y) {
this.x = x;
this.y = y;
return this;
}
public Element centerHorizontal(int w, int h, int offsetX, int offsetY) {
this.x = (w - (int) (width * scale)) / 2 + offsetX;
this.y = offsetY;
return this;
}
public Element center(int w, int h) {
this.centerOffset(w, h, 0, 0);
return this;
}
public 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 Element animation(Animation animation) {
this.animation = animation;
return this;
}
public 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,23 +0,0 @@
package org.saturnclient.ui2;
public class ElementContext {
public int mouseX, mouseY;
public int elementWidth, elementHeight;
public float elementScale;
public ElementContext(int mouseX, int mouseY, Element e) {
this.mouseX = mouseX - e.x;
this.mouseY = mouseY - e.y;
this.elementWidth = e.width;
this.elementHeight = e.height;
this.elementScale = e.scale;
}
public boolean isHovering() {
return Utils.isHovering(mouseX, mouseY, elementWidth, elementHeight, elementScale);
}
public boolean isHovering(int x, int y, int width, int height) {
return Utils.isHovering(mouseX - x, mouseY - y, width, height, elementScale);
}
}
@@ -1,370 +0,0 @@
package org.saturnclient.ui2;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.Function;
import org.jetbrains.annotations.Nullable;
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.gui.ScreenRect;
import net.minecraft.client.render.DiffuseLighting;
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.item.ItemRenderState;
import net.minecraft.client.texture.Sprite;
import net.minecraft.client.util.Window;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.entity.LivingEntity;
import net.minecraft.item.ItemStack;
import net.minecraft.item.ModelTransformationMode;
import net.minecraft.util.Identifier;
import net.minecraft.util.crash.CrashException;
import net.minecraft.util.crash.CrashReport;
import net.minecraft.util.crash.CrashReportSection;
import net.minecraft.util.math.RotationAxis;
import net.minecraft.world.World;
public class RenderScope {
public MatrixStack matrices;
public VertexConsumerProvider.Immediate vertexConsumers;
// private Function<Identifier, RenderLayer> renderLayers;
private ScissorStack scissorStack = new ScissorStack();
private int opacity = 255 << 24;
private final ItemRenderState itemRenderState;
public RenderScope(MatrixStack matrices, VertexConsumerProvider.Immediate vertexConsumers) {
this.matrices = matrices;
this.vertexConsumers = vertexConsumers;
this.scissorStack = new ScissorStack();
this.itemRenderState = new ItemRenderState();
}
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, int font, int color) {
drawText(1.0f, text, x, y, font, color);
}
public void drawText(float scale, String text, int x, int y, int font, int color) {
if (font == 0) {
scale *= 2;
}
int i = 0;
for (String line : text.split("\n")) {
color = getColor(color);
matrices.push();
matrices.translate(x, y + (i * Fonts.getHeight()), 0);
matrices.scale(scale, scale, 1.0f);
TextRenderer textRenderer = SaturnClient.client.textRenderer;
textRenderer.draw(Fonts.setFont(line, Fonts.getFont(font)), 0, font == 0 ? 1: 7, color, false, this.matrices.peek().getPositionMatrix(),
this.vertexConsumers, TextLayerType.NORMAL, 0, 15728880);
matrices.pop();
i++;
}
}
private 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)RenderLayer.getGuiTextured(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();
}
public void enableScissor(int x1, int y1, int x2, int y2) {
ScreenRect screenRect = (new ScreenRect(x1, y1, x2 - x1, y2 - y1)).transform(this.matrices.peek().getPositionMatrix());
this.setScissor(this.scissorStack.push(screenRect));
}
public void disableScissor() {
this.setScissor(this.scissorStack.pop());
}
public boolean scissorContains(int x, int y) {
return this.scissorStack.containsPoint(x, y);
}
private void setScissor(@Nullable ScreenRect rect) {
this.draw();
if (rect != null) {
Window window = SaturnClient.client.getWindow();
int i = window.getFramebufferHeight();
double d = window.getScaleFactor();
double e = (double)rect.getLeft() * d;
double f = (double)i - (double)rect.getBottom() * d;
double g = (double)rect.width() * d;
double h = (double)rect.height() * d;
RenderSystem.enableScissor((int)e, (int)f, Math.max(0, (int)g), Math.max(0, (int)h));
} else {
RenderSystem.disableScissor();
}
}
public void draw() {
this.vertexConsumers.draw();
}
public void draw(Consumer<VertexConsumerProvider> drawer) {
drawer.accept(this.vertexConsumers);
this.vertexConsumers.draw();
}
static class ScissorStack {
private final Deque<ScreenRect> stack = new ArrayDeque<>();
public ScreenRect push(ScreenRect p_281812_) {
ScreenRect screenrectangle = this.stack.peekLast();
if (screenrectangle != null) {
ScreenRect screenrectangle1 = Objects.requireNonNullElse(p_281812_.intersection(screenrectangle), ScreenRect.empty());
this.stack.addLast(screenrectangle1);
return screenrectangle1;
} else {
this.stack.addLast(p_281812_);
return p_281812_;
}
}
@Nullable
public ScreenRect pop() {
if (this.stack.isEmpty()) {
throw new IllegalStateException("Scissor stack underflow");
} else {
this.stack.removeLast();
return this.stack.peekLast();
}
}
public boolean containsPoint(int p_329411_, int p_333404_) {
return this.stack.isEmpty() ? true : this.stack.peek().contains(p_329411_, p_333404_);
}
}
public void drawItem(ItemStack item, int x, int y) {
this.drawItem(SaturnClient.client.player, SaturnClient.client.world, item, x, y, 0);
}
public void drawItem(ItemStack stack, int x, int y, int seed) {
this.drawItem(SaturnClient.client.player, SaturnClient.client.world, stack, x, y, seed);
}
public void drawItem(ItemStack stack, int x, int y, int seed, int z) {
this.drawItem(SaturnClient.client.player, SaturnClient.client.world, stack, x, y, seed, z);
}
public void drawItemWithoutEntity(ItemStack stack, int x, int y) {
this.drawItemWithoutEntity(stack, x, y, 0);
}
public void drawItemWithoutEntity(ItemStack stack, int x, int y, int seed) {
this.drawItem((LivingEntity)null, SaturnClient.client.world, stack, x, y, seed);
}
public void drawItem(LivingEntity entity, ItemStack stack, int x, int y, int seed) {
this.drawItem(entity, entity.getWorld(), stack, x, y, seed);
}
private void drawItem(@Nullable LivingEntity entity, @Nullable World world, ItemStack stack, int x, int y, int seed) {
this.drawItem(entity, world, stack, x, y, seed, 0);
}
private void drawItem(@Nullable LivingEntity entity, @Nullable World world, ItemStack stack, int x, int y, int seed, int z) {
if (!stack.isEmpty()) {
SaturnClient.client.getItemModelManager().update(this.itemRenderState, stack, ModelTransformationMode.GUI, false, world, entity, seed);
this.matrices.push();
this.matrices.translate((float)(x + 8), (float)(y + 8), (float)(150 + (this.itemRenderState.hasDepth() ? z : 0)));
try {
this.matrices.scale(16.0F, -16.0F, 16.0F);
boolean bl = !this.itemRenderState.isSideLit();
if (bl) {
this.draw();
DiffuseLighting.disableGuiDepthLighting();
}
this.itemRenderState.render(this.matrices, this.vertexConsumers, 15728880, OverlayTexture.DEFAULT_UV);
this.draw();
if (bl) {
DiffuseLighting.enableGuiDepthLighting();
}
} catch (Throwable var11) {
CrashReport crashReport = CrashReport.create(var11, "Rendering item");
CrashReportSection crashReportSection = crashReport.addElement("Item being rendered");
crashReportSection.add("Item Type", () -> {
return String.valueOf(stack.getItem());
});
crashReportSection.add("Item Components", () -> {
return String.valueOf(stack.getComponents());
});
crashReportSection.add("Item Foil", () -> {
return String.valueOf(stack.hasGlint());
});
throw new CrashException(crashReport);
}
this.matrices.pop();
}
}
public void drawSpriteStretched(Sprite sprite, int x, int y, int width, int height) {
this.drawSpriteStretched(sprite, x, y, width, height, -1);
}
public void drawSpriteStretched(Sprite sprite, int x, int y, int width, int height, int color) {
if (width != 0 && height != 0) {
this.drawTexturedQuad(sprite.getAtlasId(), x, x + width, y, y + height, sprite.getMinU(), sprite.getMaxU(), sprite.getMinV(), sprite.getMaxV(), color);
}
}
}
@@ -1,161 +0,0 @@
package org.saturnclient.ui2;
import java.util.ArrayList;
import java.util.List;
import org.saturnclient.saturnclient.mixin.DrawContextAccessor;
import org.saturnclient.ui2.anim.Animation;
import org.saturnclient.ui2.anim.SlideXAbsolute;
import org.saturnclient.ui2.components.ElementRenderer;
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.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);
}
if (element.duration != null) {
new Thread(() -> {
try {
Animation fadeOut = new SlideXAbsolute(700, -(width - element.x));
Thread.sleep(element.duration);
fadeOut.init(element);
Animation.executeSync((Float progress) -> {
fadeOut.tick(progress, element);
}, fadeOut.duration);
synchronized (elements) {
elements.remove(element);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
@Override
protected void init() {
elements.clear();
width *= 2;
height *= 2;
ui(); // abstraction to render the saturn ui and also render extra stuff here
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);
ElementRenderer.render(new ArrayList<>(elements), renderScope, mouseX, mouseY);
renderScope.matrices.pop();
}
@Override
public boolean mouseClicked(double mouseX, double mouseY, int button) {
mouseX *= 2;
mouseY *= 2;
ElementRenderer.mouseClicked(elements, mouseX, mouseY, button);
return false;
}
@Override
public boolean mouseScrolled(
double mouseX,
double mouseY,
double horizontalAmount,
double verticalAmount) {
mouseX *= 2;
mouseY *= 2;
ElementRenderer.mouseScrolled(elements, mouseX, mouseY, horizontalAmount, verticalAmount);
return super.mouseScrolled(
mouseX,
mouseY,
horizontalAmount,
verticalAmount);
}
@Override
public void resize(MinecraftClient client, int width, int height) {
elements.clear();
super.resize(client, width, height);
}
@Override
public boolean keyPressed(int keyCode, int scanCode, int modifiers) {
ElementRenderer.keyPressed(elements, keyCode, scanCode, modifiers);
return super.keyPressed(keyCode, scanCode, modifiers);
}
}
@@ -1,55 +0,0 @@
package org.saturnclient.ui2;
import java.util.List;
import org.lwjgl.glfw.GLFW;
import org.saturnclient.saturnclient.SaturnClient;
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);
}
public static 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 static <T> T getOrNull(List<T> list, int index) {
SaturnClient.LOGGER.info("Accessing index {} in list of size {}", index, list.size());
return (index >= 0 && index < list.size()) ? list.get(index) : null;
}
}
@@ -1,75 +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(() -> {
Function<Double, Double> curveFunction = Curve::easeInOutCubic;
float progress = 0.0f;
double incremental = (1000 - duration) / 10000.0;
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();
}
}
}
}).start();
}
public static void executeSync(Consumer<Float> tick, int duration) {
Function<Double, Double> curveFunction = Curve::easeInOutCubic;
float progress = 0.0f;
double incremental = (1000 - duration) / 10000.0;
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();
}
}
}
}
}
@@ -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,22 +0,0 @@
package org.saturnclient.ui2.anim;
import org.saturnclient.ui2.Element;
public class FadeOut extends Animation {
float targetOpacity;
public FadeOut(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 = 1.0f;
}
}
@@ -1,36 +0,0 @@
package org.saturnclient.ui2.anim;
import org.saturnclient.ui2.Element;
public class SlideX extends Animation {
int offset = 20;
int maxOffset = 20;
int elementX;
public SlideX(int duration) {
super(duration);
}
public SlideX(int duration, int offset) {
super(duration);
this.offset = offset;
this.maxOffset = offset;
}
public SlideX(int duration, int offset, int maxOffset) {
super(duration);
this.offset = offset;
this.maxOffset = maxOffset; // ✅ fixed
}
@Override
public void tick(float progress, Element element) {
element.x = elementX + (int) (offset * progress);
}
@Override
public void init(Element element) {
element.x -= offset;
elementX = element.x;
}
}
@@ -1,35 +0,0 @@
package org.saturnclient.ui2.anim;
import org.saturnclient.ui2.Element;
public class SlideXAbsolute extends Animation {
int offset = 20;
int maxOffset = 20;
int elementX;
public SlideXAbsolute(int duration) {
super(duration);
}
public SlideXAbsolute(int duration, int offset) {
super(duration);
this.offset = offset;
this.maxOffset = offset;
}
public SlideXAbsolute(int duration, int offset, int maxOffset) {
super(duration);
this.offset = offset;
this.maxOffset = maxOffset;
}
@Override
public void tick(float progress, Element element) {
element.x = elementX + (int) (offset * progress);
}
@Override
public void init(Element element) {
elementX = element.x;
}
}
@@ -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,60 +0,0 @@
package org.saturnclient.ui2.components;
import org.saturnclient.saturnclient.config.Property;
import org.saturnclient.saturnclient.config.ThemeManager;
import org.saturnclient.ui2.Element;
import org.saturnclient.ui2.ElementContext;
import org.saturnclient.ui2.RenderScope;
import net.minecraft.client.render.RenderLayer;
import net.minecraft.util.Identifier;
public class CosmeticPreview extends Element {
private static ThemeManager theme = new ThemeManager("CosmeticPreview", "selected", "hovering");
private static Property<Integer> bgColor = theme.property("bg-color", Property.color(0x89000000));
private static Property<Integer> fgColor = theme.property("fg-color", Property.color(0xFFFFFFFF));
private static Property<Integer> padding = theme.property("padding", Property.integer(14));
private static Property<Integer> cornerRadius = theme.property("corner-radius", Property.integer(10));
static {}
Identifier sprite;
private Runnable onClick;
private boolean isSelected;
public CosmeticPreview(boolean isSelected, Identifier sprite, Runnable onClick) {
this.sprite = sprite;
this.onClick = onClick;
this.isSelected = isSelected;
this.width = 50;
this.height = 111;
}
@Override
public void render(RenderScope renderScope, ElementContext ctx) {
if (ctx.isHovering()) {
theme.setState("hovering");
} else if (isSelected) {
theme.setState("selected");
}
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);
theme.setState(null);
}
@Override
public void click(int mouseX, int mouseY) {
if (onClick != null) {
onClick.run();
}
}
}
@@ -1,84 +0,0 @@
package org.saturnclient.ui2.components;
import java.util.ArrayList;
import java.util.List;
import org.saturnclient.ui2.Element;
import org.saturnclient.ui2.ElementContext;
import org.saturnclient.ui2.RenderScope;
import org.saturnclient.ui2.Utils;
import org.saturnclient.ui2.anim.Animation;
public class ElementRenderer {
public static void draw(List<Element> elements, 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);
}
}
public static void render(List<Element> elements, RenderScope renderScope, int mouseX, int mouseY) {
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 ElementContext(mouseX, mouseY, element));
renderScope.matrices.pop();
renderScope.setRenderLayer(null);
}
}
public static void mouseClicked(List<Element> elements, double mouseX, double mouseY, int button) {
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 (element.opacity > 0 && Utils.isHovering(adjustedMouseX, adjustedMouseY, element.width, element.height, element.scale)) {
element.focused = true;
element.click((int) (adjustedMouseX * element.scale), (int) (adjustedMouseY * element.scale));
}
}
}
}
public static void mouseScrolled(
List<Element> elements,
double mouseX,
double mouseY,
double horizontalAmount,
double verticalAmount) {
for (Element element : new ArrayList<>(elements)) {
if (Utils.isHovering((int) mouseX - element.x, (int) mouseY - element.y, element.width, element.height, element.scale)) {
element.scroll(
(int) mouseX - element.x,
(int) mouseY - element.y,
horizontalAmount,
verticalAmount);
}
}
}
public static void keyPressed(List<Element> elements, int keyCode, int scanCode, int modifiers) {
for (Element element : new ArrayList<>(elements)) {
if (element.focused && element.opacity > 0) {
element.keyPressed(keyCode, scanCode, modifiers);
char typedChar = Utils.getCharFromKey(keyCode, modifiers);
if (typedChar != '\0') {
element.charTyped(typedChar);
}
}
}
}
}
@@ -1,100 +0,0 @@
package org.saturnclient.ui2.components;
import org.saturnclient.modules.Module;
import org.saturnclient.saturnclient.SaturnClient;
import org.saturnclient.saturnclient.config.Property;
import org.saturnclient.saturnclient.config.ThemeManager;
import org.saturnclient.ui2.resources.Fonts;
import org.saturnclient.ui2.resources.Textures;
import org.saturnclient.ui2.Element;
import org.saturnclient.ui2.ElementContext;
import org.saturnclient.ui2.RenderScope;
import org.saturnclient.ui2.Utils;
import org.saturnclient.ui2.screens.ConfigEditor;
import net.minecraft.client.render.RenderLayer;
public class SaturnModule extends Element {
private static ThemeManager theme = new ThemeManager("Module", "enabled", "hovering");
private static Property<Integer> bgColor = theme.property("bg", Property.color(0xFF1d202d));
private static Property<Integer> fgColor = theme.property("fg", Property.color(0xFFA1A2B8));
private static Property<Integer> iconFg = theme.property("icon-fg", Property.color(0xFFA1A2B8));
private static Property<Integer> iconBg = theme.property("icon-bg", Property.color(0xFF2e3248));
private static Property<Integer> tagBg = theme.property("tag-bg", Property.color(0xFF2e3248));
private static Property<Integer> iconRadius = theme.property("icon-radius", Property.color(10));
private static Property<Integer> font = theme.property("font", Property.font(1));
private static Property<Integer> radius = theme.property("radius", Property.color(10));
static {
theme.propertyStateDefault("enabled", "bg", 0xFF1d202d);
theme.propertyStateDefault("enabled", "icon-bg", 0xFF845eee);
theme.propertyStateDefault("hovering", "fg", 0xFF845eee);
}
private Module mod;
public SaturnModule(Module mod) {
this.mod = mod;
this.width = 160;
this.height = 50;
}
@Override
public void render(RenderScope renderScope, ElementContext ctx) {
int p = 10;
int s = 14;
boolean settingsHover = false;
renderScope.setRenderLayer(RenderLayer::getGuiTextured);
if (mod.isEnabled()) {
theme.setState("enabled");
}
if (ctx.isHovering(width - s - p - 5, height - s - p - 5, s + 10, s + 10)) {
settingsHover = true;
} else if (ctx.isHovering()) {
theme.applyState("hovering");
}
renderScope.drawRoundedRectangle(0, 0, width, height, radius.value, bgColor.value);
int h = height-(p * 2);
renderScope.drawRoundedRectangle(p, p, h, h, iconRadius.value, iconBg.value);
renderScope.drawTexture(mod.getIconTexture(), p + (p / 2), p + (p / 2), 0, 0, h - p, h - p, iconFg.value);
renderScope.drawText(0.6f, mod.getName(), p+h+4, p + 1, font.value, fgColor.value);
renderScope.drawText(0.35f, mod.getVersion(), width - p - (int) (Fonts.getWidth(mod.getVersion(), font.value) * 0.35f), p + 1, font.value, fgColor.value);
int xt = p + h + 4;
for (String tag : mod.getTags()) {
int xtb = (int) (Fonts.getWidth(tag, font.value) * 0.4f);
renderScope.drawRoundedRectangle(xt, p + (h/2), xtb + 6, 12, 5, tagBg.value);
renderScope.drawText(0.4f, tag, xt+2, p + (h/2) + 3, font.value, fgColor.value);
xt += xtb + 9;
}
if (settingsHover) {
theme.setState("hovering");
}
renderScope.drawTexture(Textures.SETTINGS, width - s - p, height - s - p, 0, 0, s, s, fgColor.value);
theme.setState(null);
}
@Override
public void click(int mouseX, int mouseY) {
int p = 10;
int s = 14;
if (Utils.isHovering(mouseX - (width - s - p - 5), mouseY - (height - s - p - 5), s + 10, s + 10, 1.0f)) {
SaturnClient.client.setScreen(new ConfigEditor(mod.getConfig()));
} else {
mod.setEnabled(!mod.isEnabled());
}
}
}
@@ -1,147 +0,0 @@
package org.saturnclient.ui2.components;
import org.saturnclient.saturnclient.SaturnClient;
import org.saturnclient.saturnclient.SaturnClientConfig;
import org.saturnclient.saturnclient.config.Property;
import org.saturnclient.saturnclient.config.ThemeManager;
import org.saturnclient.ui2.resources.Textures;
import org.saturnclient.ui2.Element;
import org.saturnclient.ui2.ElementContext;
import org.saturnclient.ui2.RenderScope;
import org.saturnclient.ui2.Utils;
import org.saturnclient.ui2.screens.ConfigEditor;
import org.saturnclient.ui2.screens.ModMenu;
import org.saturnclient.ui2.screens.cosmetics.CloakMenu;
import org.saturnclient.ui2.screens.store.CloakStore;
import net.minecraft.client.render.RenderLayer;
import net.minecraft.util.Identifier;
public class Sidebar extends Element {
private static ThemeManager theme = new ThemeManager("SideBar", "hovering");
private static Property<Integer> bgColor = theme.property("bg-color", Property.color(0x90000000));
private static Property<Integer> iconFg = theme.property("icon-fg", Property.color(0xFFFFFFFF));
private static Property<Integer> iconBg = theme.property("icon-bg", Property.color(0x00000000));
private static Property<Integer> cornerRadius = theme.property("corner-radius", Property.integer(10));
private static Property<Integer> iconRadius = theme.property("icon-radius", Property.integer(10));
private static Property<Integer> padding = theme.property("padding", Property.integer(10));
static {
theme.propertyStateDefault("hovering", "icon-bg", -7643914);
}
public static class SidebarComponent {
Runnable onClick;
boolean end;
Identifier sprite;
public SidebarComponent(Identifier sprite, Runnable onClick, boolean end) {
this.onClick = onClick;
this.end = end;
this.sprite = sprite;
}
}
Runnable onClose;
SidebarComponent[] components = {
new Sidebar.SidebarComponent(Textures.MODS_TAB, () -> {
SaturnClient.client.setScreen(new ModMenu());
}, false),
new Sidebar.SidebarComponent(Textures.SETTINGS, () -> {
SaturnClient.client.setScreen(new ConfigEditor(SaturnClientConfig.config));
}, false),
new Sidebar.SidebarComponent(Textures.SHIRT, () -> {
SaturnClient.client.setScreen(new CloakMenu());
}, false),
new Sidebar.SidebarComponent(Textures.CLOSE, () -> {
onClose.run();
}, true),
new Sidebar.SidebarComponent(Textures.STORE, () -> {
SaturnClient.client.setScreen(new CloakStore());
}, true),
};
int active = 0;
public Sidebar(int active, Runnable onClose) {
this.width = 30;
this.height = 350;
this.active = active;
this.onClose = onClose;
}
@Override
public void render(RenderScope renderScope, ElementContext ctx) {
int topRow = 0;
int bottomRow = 0;
int gap = 5; // 5px gap between icons
int sp = 30 - padding.value + gap;
int s = 30 - (padding.value * 2);
int s2 = s + padding.value;
renderScope.setRenderLayer(RenderLayer::getGuiTextured);
renderScope.drawRoundedRectangle(0, 0, width, height, cornerRadius.value, bgColor.value);
int i = 0;
for (SidebarComponent component : components) {
if (component.end) {
int y = (height - (sp * bottomRow)) - (s * 2);
if (ctx.isHovering(0, y - (padding.value / 2), s2, s2) || active == i) {
theme.setState("hovering");
} else {
theme.setState(null);
}
renderScope.drawRoundedRectangle(padding.value / 2, y - (padding.value / 2), s + padding.value, s + padding.value, iconRadius.value, iconBg.value);
renderScope.drawTexture(component.sprite, padding.value, y, 0, 0, s, s, iconFg.value);
bottomRow += 1;
} else {
int y = padding.value + (sp * topRow);
if (ctx.isHovering(0, y - (padding.value / 2), s2, s2) || active == i) {
theme.setState("hovering");
} else {
theme.setState(null);
}
renderScope.drawRoundedRectangle(padding.value / 2, y - (padding.value / 2), s + padding.value, s + padding.value, iconRadius.value, iconBg.value);
renderScope.drawTexture(component.sprite, padding.value, y, 0, 0, s, s, iconFg.value);
topRow += 1;
}
i++;
}
}
@Override
public void click(int mouseX, int mouseY) {
int topRow = 0;
int bottomRow = 0;
int gap = 5; // same 5px gap here
int sp = 30 - padding.value + gap;
int s = 30 - (padding.value * 2);
int s2 = s + padding.value;
for (SidebarComponent component : components) {
if (component.end) {
int y = (height - (sp * bottomRow)) - (s * 2);
if (Utils.isHovering(mouseX - (padding.value / 2), mouseY - (y - (padding.value / 2)), s2, s2, scale)) {
component.onClick.run();
break;
}
bottomRow += 1;
} else {
int y = padding.value + (sp * topRow);
if (Utils.isHovering(mouseX - (padding.value / 2), mouseY - (y - (padding.value / 2)), s2, s2, scale)) {
component.onClick.run();
break;
}
topRow += 1;
}
}
}
}
@@ -1,246 +0,0 @@
package org.saturnclient.ui2.components.dialog;
import java.awt.Color;
import org.saturnclient.saturnclient.SaturnClient;
import org.saturnclient.saturnclient.config.Property;
import org.saturnclient.saturnclient.mixin.DrawContextAccessor;
import org.saturnclient.ui2.RenderScope;
import org.saturnclient.ui2.SaturnScreen;
import net.minecraft.client.gui.DrawContext;
public class ColorDialog extends SaturnScreen {
private Property<Integer> prop;
private SaturnScreen parent;
private static final int PICKER_WIDTH = 200;
private static final int PICKER_HEIGHT = 200;
private static final int SLIDER_WIDTH = 200;
private static final int SLIDER_HEIGHT = 10;
private static final int HUE_SLIDER_HEIGHT = 10;
private float hue = 0.0f;
private int selectedX = 0;
private int selectedY = 0;
private float opacity = 1.0f;
private boolean dragging = false;
public ColorDialog(Property<Integer> prop , SaturnScreen parent) {
super("Color Dialog");
this.prop = prop;
this.parent = parent;
int alpha = (prop.value >> 24) & 0xFF;
if (alpha == 0) alpha = 255; // fallback if no alpha specified
this.opacity = alpha / 255.0f;
syncFromColor();
}
@Override
public void ui() {}
@Override
public void close() {
SaturnClient.client.setScreen(parent);
}
@Override
public void render(DrawContext context, int mouseX, int mouseY, float delta) {
super.render(context, mouseX, mouseY, delta);
RenderScope renderScope = new RenderScope(context.getMatrices(),
((DrawContextAccessor) context).getVertexConsumers());
renderScope.matrices.push();
renderScope.matrices.scale(0.5f, 0.5f, 0.5f);
int xStart = (width - PICKER_WIDTH) / 2;
int yStart = (height - PICKER_HEIGHT) / 2;
// Draw the HSV saturation/value square (with current hue)
for (int x = 0; x < PICKER_WIDTH; x++) {
for (int y = 0; y < PICKER_HEIGHT; y++) {
float saturation = x / (float) PICKER_WIDTH;
float value = 1.0f - y / (float) PICKER_HEIGHT;
int color = Color.HSBtoRGB(hue, saturation, value);
context.fill(xStart + x, yStart + y, xStart + x + 1, yStart + y + 1, 0xFF000000 | color);
}
}
int x = (xStart + selectedX) - 4;
int y = (yStart + selectedY) - 4;
context.fill(x, y, x+8, y+8, 0xFF000000 | prop.value);
context.drawBorder(x, y, 8, 8, 0xFFffffff);
renderSlider(context, mouseX, mouseY, delta);
renderHueSlider(context);
renderScope.matrices.pop();
}
public void renderSlider(DrawContext context, int mouseX, int mouseY, float delta) {
int xStart = (width - SLIDER_WIDTH) / 2;
int yStart = (height - SLIDER_HEIGHT - PICKER_HEIGHT) / 2 - 10;
// Draw slider background (left = transparent, right = white)
for (int x = 0; x < SLIDER_WIDTH; x++) {
float localOpacity = x / (float) SLIDER_WIDTH;
int alpha = (int)(localOpacity * 255) & 0xFF;
int color = (alpha << 24) | (prop.value & 0xFFFFFF);
context.fill(xStart + x, yStart, xStart + x + 1, yStart + SLIDER_HEIGHT, color);
}
// Draw current value as a marker (thin black line)
int knobX = xStart + (int)(opacity * SLIDER_WIDTH);
context.fill(knobX - 1, yStart - 2, knobX + 1, yStart + SLIDER_HEIGHT + 2, 0xFF000000);
// Draw opacity text
// context.drawText(textRenderer, String.format("Opacity: %.2f", opacity), xStart, yStart - 15, 0xFFFFFF, false);
}
private void renderHueSlider(DrawContext context) {
int xStart = (width - SLIDER_WIDTH) / 2;
int yStart = (height + PICKER_HEIGHT) / 2 + 10;
for (int x = 0; x < SLIDER_WIDTH; x++) {
float localHue = x / (float) SLIDER_WIDTH;
int color = Color.HSBtoRGB(localHue, 1.0f, 1.0f);
context.fill(xStart + x, yStart, xStart + x + 1, yStart + HUE_SLIDER_HEIGHT, 0xFF000000 | color);
}
int knobX = xStart + (int)(hue * SLIDER_WIDTH);
context.fill(knobX - 1, yStart - 2, knobX + 1, yStart + HUE_SLIDER_HEIGHT + 2, 0xFF000000);
}
@Override
public boolean mouseClicked(double mouseX, double mouseY, int button) {
mouseX *= 2;
mouseY *= 2;
int xStart = (width - PICKER_WIDTH) / 2;
int yStart = (height - PICKER_HEIGHT) / 2;
int relX = (int) mouseX - xStart;
int relY = (int) mouseY - yStart;
if (relX >= 0 && relX < PICKER_WIDTH && relY >= 0 && relY < PICKER_HEIGHT) {
float saturation = relX / (float) PICKER_WIDTH;
float value = 1.0f - relY / (float) PICKER_HEIGHT;
prop.value = Color.HSBtoRGB(hue, saturation, value);
int alpha = (int)(opacity * 255) & 0xFF;
prop.value = (alpha << 24) | (prop.value & 0xFFFFFF);
selectedX = relX;
selectedY = relY;
return true;
} else if (isInSlider(mouseX, mouseY)) {
updateOpacity(mouseX);
dragging = true;
return true;
} else if (isInHueSlider(mouseX, mouseY)) {
updateHue(mouseX);
return true;
}
return super.mouseClicked(mouseX, mouseY, button);
}
@Override
public boolean mouseReleased(double mouseX, double mouseY, int button) {
dragging = false;
return super.mouseReleased(mouseX, mouseY, button);
}
@Override
public boolean mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY) {
mouseX *= 2;
mouseY *= 2;
if (isInHueSlider(mouseX, mouseY)) {
updateHue(mouseX);
return true;
} else if (dragging) {
updateOpacity(mouseX);
return true;
} else {
int xStart = (width - PICKER_WIDTH) / 2;
int yStart = (height - PICKER_HEIGHT) / 2;
int relX = (int) mouseX - xStart;
int relY = (int) mouseY - yStart;
if (relX >= 0 && relX < PICKER_WIDTH && relY >= 0 && relY < PICKER_HEIGHT) {
float saturation = relX / (float) PICKER_WIDTH;
float value = 1.0f - relY / (float) PICKER_HEIGHT;
prop.value = Color.HSBtoRGB(hue, saturation, value);
int alpha = (int)(opacity * 255) & 0xFF;
prop.value = (alpha << 24) | (prop.value & 0xFFFFFF);
selectedX = relX;
selectedY = relY;
return true;
}
}
return super.mouseDragged(mouseX, mouseY, button, deltaX, deltaY);
}
private boolean isInSlider(double mouseX, double mouseY) {
int xStart = (width - SLIDER_WIDTH) / 2;
int yStart = (height - SLIDER_HEIGHT - PICKER_HEIGHT) / 2 - 10;
return mouseX >= xStart && mouseX <= xStart + SLIDER_WIDTH &&
mouseY >= yStart && mouseY <= yStart + SLIDER_HEIGHT;
}
private boolean isInHueSlider(double mouseX, double mouseY) {
int xStart = (width - SLIDER_WIDTH) / 2;
int yStart = (height + PICKER_HEIGHT) / 2 + 10;
return mouseX >= xStart && mouseX <= xStart + SLIDER_WIDTH &&
mouseY >= yStart && mouseY <= yStart + HUE_SLIDER_HEIGHT;
}
private void updateOpacity(double mouseX) {
int xStart = (width - SLIDER_WIDTH) / 2;
float newOpacity = (float)((mouseX - xStart) / SLIDER_WIDTH);
opacity = Math.max(0.0f, Math.min(1.0f, newOpacity));
int alpha = (int)(opacity * 255) & 0xFF;
prop.value = (alpha << 24) | (prop.value & 0xFFFFFF);
}
private void updateHue(double mouseX) {
int xStart = (width - SLIDER_WIDTH) / 2;
float newHue = (float)((mouseX - xStart) / SLIDER_WIDTH);
hue = Math.max(0.0f, Math.min(1.0f, newHue));
float saturation = selectedX / (float) PICKER_WIDTH;
float value = 1.0f - selectedY / (float) PICKER_HEIGHT;
prop.value = Color.HSBtoRGB(hue, saturation, value);
int alpha = (int)(opacity * 255) & 0xFF;
prop.value = (alpha << 24) | (prop.value & 0xFFFFFF);
}
private void syncFromColor() {
int rgb = prop.value & 0xFFFFFF;
float[] hsb = Color.RGBtoHSB((rgb >> 16) & 0xFF, (rgb >> 8) & 0xFF, rgb & 0xFF, null);
this.hue = hsb[0];
float saturation = hsb[1];
float value = hsb[2];
this.selectedX = (int)(saturation * PICKER_WIDTH);
this.selectedY = (int)((1.0f - value) * PICKER_HEIGHT);
// Extract alpha and convert to opacity float
int alpha = (prop.value >> 24) & 0xFF;
if (alpha == 0) alpha = 255;
this.opacity = alpha / 255.0f;
}
}
@@ -1,59 +0,0 @@
package org.saturnclient.ui2.components.inputs;
import org.saturnclient.saturnclient.config.ConfigManager;
import org.saturnclient.saturnclient.config.Property;
import org.saturnclient.ui2.resources.Fonts;
public class FloatInput extends Input {
public Property<Float> prop;
public FloatInput(Property<Float> prop) {
this.width = 120;
this.height = Fonts.getHeight();
this.prop = prop;
this.text = String.valueOf(prop.value);
}
@Override
public void charTyped(char chr) {
if ((Character.isDigit(chr) || chr == '.' || (chr == '-' && cursorPosition == 0)) && text.length() < 10) {
if (text.equals("0") && cursorPosition == 1) {
text = "" + chr;
} else {
text = text.substring(0, cursorPosition) +
chr +
text.substring(cursorPosition);
try {
prop.value = text.isEmpty() ? 0 : Float.parseFloat(text);
cursorPosition++;
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
@Override
public void backspace() {
try {
if (text.length() == 0) {
text = "0";
cursorPosition = 1;
}
prop.value = Float.parseFloat(text);
ConfigManager.save();
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void checkReset() {
if (prop.isReset) {
this.text = String.valueOf(prop.value);
this.cursorPosition = 0;
prop.isReset = false;
}
}
}
@@ -1,32 +0,0 @@
package org.saturnclient.ui2.components.inputs;
import org.saturnclient.saturnclient.SaturnClient;
import org.saturnclient.saturnclient.config.Property;
import org.saturnclient.ui2.Element;
import org.saturnclient.ui2.ElementContext;
import org.saturnclient.ui2.RenderScope;
import org.saturnclient.ui2.SaturnScreen;
import org.saturnclient.ui2.components.dialog.ColorDialog;
public class HexInput extends Element {
public Property<Integer> prop;
public SaturnScreen screen;
public HexInput(Property<Integer> prop, SaturnScreen screen) {
this.width = 58;
this.height = 20;
this.prop = prop;
this.scale = 0.8f;
this.screen = screen;
}
@Override
public void click(int mouseX, int mouseY) {
SaturnClient.client.setScreen(new ColorDialog(prop, screen));
}
@Override
public void render(RenderScope renderScope, ElementContext ctx) {
renderScope.drawRoundedRectangle(0, 0, width, height, 17, prop.value);
}
}
@@ -1,113 +0,0 @@
package org.saturnclient.ui2.components.inputs;
import org.lwjgl.glfw.GLFW;
import org.saturnclient.saturnclient.SaturnClient;
import org.saturnclient.saturnclient.config.Property;
import org.saturnclient.saturnclient.config.ThemeManager;
import org.saturnclient.ui2.Element;
import org.saturnclient.ui2.ElementContext;
import org.saturnclient.ui2.RenderScope;
import org.saturnclient.ui2.resources.Fonts;
public abstract class Input extends Element {
private static ThemeManager theme = new ThemeManager("Input");
private static Property<Integer> font = theme.property("font", Property.font(1));
String text = "";
public int cursorPosition = 0;
public abstract void checkReset();
public abstract void backspace();
public boolean validateBackspace() {
return true;
}
@Override
public void keyPressed(int keyCode, int scanCode, int modifiers) {
if (keyCode == GLFW.GLFW_KEY_BACKSPACE) {
if (cursorPosition > 0 && validateBackspace()) {
text = text.substring(0, cursorPosition - 1) +
text.substring(cursorPosition);
cursorPosition--;
backspace();
}
} 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 click(int mouseX, int mouseY) {
int scrollOffset = getScrollOffset();
String visibleText = text.substring(scrollOffset);
int newCursorPos = scrollOffset;
float textWidth = 0;
for (int i = 0; i < visibleText.length(); i++) {
textWidth = SaturnClient.client.textRenderer.getWidth(visibleText.substring(0, i + 1)) * 0.6f;
if (textWidth > mouseX) {
break;
}
newCursorPos++;
}
cursorPosition = newCursorPos;
}
@Override
public void render(RenderScope renderScope, ElementContext ctx) {
checkReset();
renderScope.drawRoundedRectangle(0, 0, width, height, 10, 0xFF000000);
int scrollOffset = getScrollOffset();
String visibleText = getVisibleText(scrollOffset);
int textColor = focused ? 0xFFFFFF : 0xAAAAAA;
renderScope.drawText(0.6f, visibleText, 4, 4, font.value, textColor);
if (focused) {
int cursorX = 4 +
(int) (Fonts.getWidth(visibleText.substring(0, cursorPosition - scrollOffset), font.value) * 0.6f);
renderScope.drawRect(cursorX, 2, 1, height - 4, 0xFFFFFFFF);
}
}
protected 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 (Fonts.getWidth(visibleText, font.value) > maxWidth) {
offset++;
} else {
break;
}
}
return offset;
}
protected 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 (Fonts.getWidth(visibleText.substring(0, i), font.value) > width - 4) {
return visibleText.substring(0, i - 1); // Trim overflow
}
}
return visibleText;
}
}
@@ -1,59 +0,0 @@
package org.saturnclient.ui2.components.inputs;
import org.saturnclient.saturnclient.config.ConfigManager;
import org.saturnclient.saturnclient.config.Property;
import org.saturnclient.ui2.resources.Fonts;
public class IntInput extends Input {
public Property<Integer> prop;
public IntInput(Property<Integer> prop) {
this.width = 120;
this.height = Fonts.getHeight();
this.prop = prop;
this.text = String.valueOf(prop.value);
}
@Override
public void charTyped(char chr) {
if ((Character.isDigit(chr) || chr == '.' || (chr == '-' && cursorPosition == 0)) && text.length() < 10) {
if (text.equals("0") && cursorPosition == 1) {
text = "" + chr;
} else {
text = text.substring(0, cursorPosition) +
chr +
text.substring(cursorPosition);
try {
prop.value = text.isEmpty() ? 0 : Integer.parseInt(text);
cursorPosition++;
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
@Override
public void backspace() {
try {
if (text.length() == 0) {
text = "0";
cursorPosition = 1;
}
prop.value = Integer.parseInt(text);
ConfigManager.save();
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void checkReset() {
if (prop.isReset) {
this.text = String.valueOf(prop.value);
this.cursorPosition = 0;
prop.isReset = false;
}
}
}
@@ -1,40 +0,0 @@
package org.saturnclient.ui2.components.inputs;
import org.lwjgl.glfw.GLFW;
import org.saturnclient.saturnclient.config.Property;
import org.saturnclient.saturnclient.config.ThemeManager;
import org.saturnclient.ui2.Element;
import org.saturnclient.ui2.ElementContext;
import org.saturnclient.ui2.RenderScope;
import org.saturnclient.ui2.resources.Fonts;
public class KeybindingSelector extends Element {
private static ThemeManager theme = new ThemeManager("Input");
private static Property<Integer> font = theme.property("font", Property.font(1));
Property<Integer> prop;
public KeybindingSelector(Property<Integer> prop) {
this.prop = prop;
this.width = 120;
this.height = Fonts.getHeight();
}
@Override
public void render(RenderScope renderScope, ElementContext ctx) {
int textColor = focused ? 0xFFFFFF : 0xAAAAAA;
renderScope.drawRoundedRectangle(0, 0, width, height, 10, 0xFF000000);
renderScope.drawText(0.6f, prop.value == -1 ? "<NONE>" :
GLFW.glfwGetKeyName(prop.value, GLFW.glfwGetKeyScancode(prop.value)).toUpperCase(),
4, 4, font.value, textColor);
}
@Override
public void keyPressed(int keyCode, int scanCode, int modifiers) {
if (keyCode == GLFW.GLFW_KEY_BACKSPACE) {
prop.value = -1;
} else {
prop.value = keyCode;
}
focused = false;
}
}
@@ -1,49 +0,0 @@
package org.saturnclient.ui2.components.inputs;
import org.saturnclient.saturnclient.config.Property;
import org.saturnclient.saturnclient.config.ThemeManager;
import org.saturnclient.ui2.Element;
import org.saturnclient.ui2.ElementContext;
import org.saturnclient.ui2.RenderScope;
import org.saturnclient.ui2.Utils;
import org.saturnclient.ui2.resources.Fonts;
import net.minecraft.client.render.RenderLayer;
public class Select extends Element {
private static ThemeManager theme = new ThemeManager("Toggle");
private static Property<Integer> fgColor = theme.property("fg-color", Property.color(0xAAAAAA));
private static Property<Integer> arrowColor = theme.property("arrow-color", Property.color(0xff8B5CF6));
private static Property<Integer> font = theme.property("font", Property.font(1));
private Property<Integer> prop;
public Select(Property<Integer> prop) {
this.prop = prop;
this.width = 150;
this.height = 15;
}
@Override
public void render(RenderScope renderScope, ElementContext ctx) {
renderScope.setRenderLayer(RenderLayer::getGuiTextured);
renderScope.drawText(0.8f, "<", 0, 0, font.value, arrowColor.value);
String text = prop.getSelection();
renderScope.drawText(0.6f, text, (int) (width - ((float) Fonts.getWidth(text, font.value) * 0.6f)) / 2, 1, font.value, fgColor.value);
renderScope.drawText(0.8f, ">", width - 10, 0, font.value, arrowColor.value);
theme.setState(null);
}
@Override
public void click(int mouseX, int mouseY) {
if (Utils.isHovering(mouseX - (width - 10), mouseY, 10, height, 1.0f)) {
prop.next();
} else if (Utils.isHovering(mouseX, mouseY, 10, height, 1.0f)) {
prop.prev();
}
}
}
@@ -1,49 +0,0 @@
package org.saturnclient.ui2.components.inputs;
import org.saturnclient.saturnclient.config.Property;
import org.saturnclient.saturnclient.config.ThemeManager;
import org.saturnclient.ui2.Element;
import org.saturnclient.ui2.ElementContext;
import org.saturnclient.ui2.RenderScope;
public class Toggle extends Element {
private static ThemeManager theme = new ThemeManager("Toggle", "hovering", "enabled");
private static Property<Integer> bgColor = theme.property("bg-color", Property.color(0xFF808080));
private static Property<Integer> fgColor = theme.property("fg-color", Property.color(0xFF909090));
private static Property<Integer> cornerRadius = theme.property("corner-radius", Property.integer(30));
static {
theme.propertyStateDefault("enabled", "bg-color", 0xaf8B5CF6);
theme.propertyStateDefault("enabled", "fg-color", 0xff8B5CF6);
}
private Property<Boolean> prop;
public Toggle(Property<Boolean> prop) {
this.prop = prop;
this.width = 60;
this.height = 30;
}
@Override
public void render(RenderScope renderScope, ElementContext ctx) {
theme.setState(null);
if (prop.value) {
theme.setState("enabled");
}
if (ctx.isHovering()) {
theme.applyState("hovering");
}
renderScope.drawRoundedRectangle(0, 0, width, height, cornerRadius.value, bgColor.value);
renderScope.drawRoundedRectangle(prop.value ? 30 : 0, 0, 30, 30, 30, fgColor.value);
}
@Override
public void click(int mouseX, int mouseY) {
prop.value = !prop.value;
}
}
@@ -1,49 +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.ElementContext;
import org.saturnclient.ui2.RenderScope;
import org.saturnclient.ui2.resources.Fonts;
public class Button extends Element {
private static ThemeManager theme = new ThemeManager("Button", "hovering");
private static Property<Integer> bgColor = theme.property("bg-color", Property.color(0xFF000000));
private static Property<Integer> fgColor = theme.property("fg-color", Property.color(0xFFFFFFFF));
private static Property<Integer> cornerRadius = theme.property("corner-radius", Property.integer(10));
private static Property<Integer> font = theme.property("font", Property.font(1));
static {
theme.propertyStateDefault("hovering", "fg-color", 0xFF845eee);
}
private String text;
private Runnable onClick;
public Button(String text, Runnable onClick) {
this.text = text;
this.onClick = onClick;
this.dimensions(Fonts.getWidth(text, font.value) + 50, 38);
}
@Override
public void render(RenderScope renderScope, ElementContext 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);
renderScope.drawText(text, Fonts.centerX(width, text, font.value), Fonts.centerY(height), font.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.ElementContext;
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", Property.color(0xFFFFFFFF));
Identifier sprite;
private Runnable onClick;
public ClickableTexture(Identifier sprite, Runnable onClick) {
this.sprite = sprite;
this.onClick = onClick;
}
@Override
public void render(RenderScope renderScope, ElementContext 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.ElementContext;
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, ElementContext ctx) {
renderScope.setRenderLayer(RenderLayer::getGuiTextured);
renderScope.drawTexture(sprite, 0, 0, 0, 0, width, height);
}
}
@@ -1,60 +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.ElementContext;
import org.saturnclient.ui2.RenderScope;
import org.saturnclient.ui2.anim.SlideX;
import org.saturnclient.ui2.resources.Fonts;
import org.saturnclient.ui2.resources.Textures;
public class Notification extends Element {
private static ThemeManager theme = new ThemeManager("Notification");
private static Property<Integer> bgColor = theme.property("bg-color", Property.color(0xFF000000));
private static Property<Integer> error = theme.property("error", Property.color(0xFFbf212f));
private static Property<Integer> info = theme.property("info", Property.color(0xFF264b96));
private static Property<Integer> success = theme.property("success", Property.color(0xFF27b376));
public static enum NotificationKind {
Error,
Info,
Success,
}
NotificationKind kind;
String text;
String title;
public Notification(int width, int height, NotificationKind kind, String title, String text) {
this.kind = kind;
this.text = text;
this.title = title;
this.width = (int) Math.max(Fonts.getWidth(text, 1) * 0.6f, Fonts.getWidth(title, 1)) + 20;
this.height = 50;
this.x = width - this.width - 15;
this.y = height - this.height - 15;
this.animation = new SlideX(700, -width);
this.duration = 3000;
}
@Override
public void render(RenderScope renderScope, ElementContext ctx) {
renderScope.drawRoundedRectangle(0, 0, width, height, 10, bgColor.value);
switch (kind) {
case Error:
renderScope.drawTexture(Textures.CIRCLE_X, 9, 9, 0, 0, 15, 15, error.value);
break;
case Info:
renderScope.drawTexture(Textures.INFO, 9, 9, 0, 0, 15, 15, info.value);
break;
case Success:
renderScope.drawTexture(Textures.CHECK, 9, 9, 0, 0, 15, 15, success.value);
break;
}
renderScope.drawText(0.7f, title, 32, 10, 1, -1);
renderScope.drawText(0.5f, text, 32, 28, 1, -1);
}
}
@@ -1,89 +0,0 @@
package org.saturnclient.ui2.elements;
import java.util.ArrayList;
import java.util.List;
import org.saturnclient.saturnclient.config.Property;
import org.saturnclient.saturnclient.config.ThemeManager;
import org.saturnclient.ui2.Element;
import org.saturnclient.ui2.ElementContext;
import org.saturnclient.ui2.RenderScope;
import org.saturnclient.ui2.components.ElementRenderer;
public class Scroll extends Element {
private static ThemeManager theme = new ThemeManager("Scroll");
private static Property<Integer> bgColor = theme.property("bg-color", Property.color(0x90000000));
private static Property<Integer> scrollBarColor = theme.property("scrollbar-color", Property.color(-7643914));
private static Property<Integer> scrollBarRadius = theme.property("scrollbar-radius", Property.integer(10));
private static Property<Integer> scrollBarWidth = theme.property("scrollbar-width", Property.integer(5));
private static Property<Integer> scrollBarPadding = theme.property("scrollbar-padding", Property.integer(5));
private static Property<Integer> cornerRadius = theme.property("corner-radius", Property.integer(10));
int padding = 0;
protected List<Element> children = new ArrayList<>();
int scroll = 0;
int maxScroll = 0;
public Scroll(int padding) {
this.padding = padding;
}
@Override
public void render(RenderScope renderScope, ElementContext ctx) {
renderScope.drawRoundedRectangle(0, 0, width, height, cornerRadius.value, bgColor.value);
renderScope.enableScissor(padding, padding, width - padding, height - padding);
renderScope.matrices.push();
renderScope.matrices.translate(padding, (-scroll) + padding, 0);
ElementRenderer.render(children, renderScope, ctx.mouseX - padding, ctx.mouseY - padding + scroll);
renderScope.matrices.pop();
renderScope.disableScissor();
renderScope.drawRoundedRectangle(width - scrollBarWidth.value - scrollBarPadding.value, calculateScrollBarY(), scrollBarWidth.value, calculateScrollBarHeight(), scrollBarRadius.value, scrollBarColor.value);
}
@Override
public void scroll(int mouseX, int mouseY, double horizontalAmount, double verticalAmount) {
scroll -= verticalAmount;
if (scroll < 0) {
scroll = 0;
} else if (scroll > maxScroll) {
scroll = maxScroll;
}
}
@Override
public void click(int mouseX, int mouseY) {
ElementRenderer.mouseClicked(children, mouseX - padding, mouseY - padding + scroll, 0);
}
public void draw(Element element) {
children.add(element);
}
@Override
public Element dimensions(int width, int height) {
for (Element element : children) {
maxScroll = Math.max(maxScroll, element.y + element.height - height);
}
return super.dimensions(width, height);
}
int calculateScrollBarHeight() {
if (maxScroll <= 0) return height - (scrollBarPadding.value * 2); // No scrolling needed
return Math.max(20, (height * height) / (height + maxScroll)) - (scrollBarPadding.value * 2); // At least 20px tall
}
int calculateScrollBarY() {
int scrollBarHeight = calculateScrollBarHeight();
if (maxScroll <= 0) return scrollBarPadding.value;
return (scroll * (height - scrollBarHeight)) / maxScroll + scrollBarPadding.value;
}
@Override
public void keyPressed(int keyCode, int scanCode, int modifiers) {
ElementRenderer.keyPressed(children, keyCode, scanCode, modifiers);
}
}
@@ -1,73 +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.ElementContext;
import org.saturnclient.ui2.RenderScope;
import org.saturnclient.ui2.Utils;
import net.minecraft.util.Identifier;
public class TabMenu extends Element {
private static ThemeManager theme = new ThemeManager("TabMenu", "hovering", "selected");
private static Property<Integer> bgColor = theme.property("bg-color", Property.color(0x90000000));
private static Property<Integer> iconFg = theme.property("icon-fg", Property.color(0xFFFFFFFF));
private static Property<Integer> iconBg = theme.property("icon-bg", Property.color(0x00000000));
private static Property<Integer> cornerRadius = theme.property("corner-radius", Property.integer(10));
static {
theme.propertyStateDefault("hovering", "icon-bg", -7643914);
}
public static class TabMenuComponent {
Runnable onClick;
Identifier sprite;
public TabMenuComponent(Identifier sprite, Runnable onClick) {
this.onClick = onClick;
this.sprite = sprite;
}
}
TabMenuComponent[] components;
public int selectedScreen = 0;
public TabMenu(int active, TabMenuComponent... components) {
this.components = components;
width = 25 * components.length + 6;
height = 30;
this.selectedScreen = active;
}
@Override
public void render(RenderScope renderScope, ElementContext ctx) {
renderScope.drawRoundedRectangle(0, 0, width, height, cornerRadius.value, bgColor.value);
int col = 0;
for (TabMenuComponent component : components) {
int x = 25 * col + 5;
if (ctx.isHovering(x, 5, 20, 20) || selectedScreen == col) {
theme.setState("hovering");
}
renderScope.drawRoundedRectangle(x, 5, 20, 20, cornerRadius.value, iconBg.value);
renderScope.drawTexture(component.sprite, x + 5, 10, 0, 0, 10, 10, iconFg.value);
theme.setState(null);
col++;
}
}
@Override
public void click(int mouseX, int mouseY) {
int col = 0;
for (TabMenuComponent component : components) {
int x = 25 * col + 5;
if (Utils.isHovering(mouseX - x, mouseY - 5, 20, 20, 1.0f)) {
component.onClick.run();
}
col++;
}
}
}
@@ -1,27 +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.ElementContext;
import org.saturnclient.ui2.RenderScope;
import org.saturnclient.ui2.resources.Fonts;
public class Text extends Element {
private static ThemeManager theme = new ThemeManager("Text");
private static Property<Integer> fgColor = theme.property("fg-color", Property.color(0xFFFFFFFF));
public static Property<Integer> font = theme.property("font", Property.font(1));
private String text;
public Text(String text) {
this.text = text;
width = Fonts.getWidth(text, font.value);
height = Fonts.getHeight();
}
@Override
public void render(RenderScope renderScope, ElementContext ctx) {
renderScope.drawText(this.text, 0, 0, font.value, 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.ElementContext;
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", Property.color(0xFF000000));
private static Property<Integer> fgColor = theme.property("fg-color", Property.color(0xFFFFFFFF));
private static Property<Integer> padding = theme.property("padding", Property.integer(14));
private static Property<Integer> cornerRadius = theme.property("corner-radius", Property.integer(10));
static {
theme.propertyStateDefault("hovering", "fg-color", 0xFF845eee);
}
Identifier sprite;
private Runnable onClick;
public TextureButton(Identifier sprite, Runnable onClick) {
this.sprite = sprite;
this.onClick = onClick;
}
@Override
public void render(RenderScope renderScope, ElementContext 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,56 +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 INTER = Identifier.of("saturnclient", "inter");
public static final Identifier INTER_BOLD = Identifier.of("saturnclient", "inter_bold");
public static final Identifier DEFAULT = Identifier.ofVanilla("default");
public static Identifier getFont(int font) {
switch (font) {
case 0:
return DEFAULT;
case 1:
return INTER;
default:
return INTER_BOLD;
}
}
public static Text setFont(String text, Identifier font) {
return Text.literal(text).setStyle(
Style.EMPTY.withFont(font));
}
public static int getWidth(String text, int font) {
int w = 0;
for (String line : text.split("\n")) {
w = Math.max(w, getWidth(line, getFont(font)));
}
if (font == 0) {
return w * 2;
} else {
return w;
}
}
private static int getWidth(String text, Identifier font) {
return SaturnClient.client.textRenderer.getWidth(setFont(text, font));
}
public static int getHeight() {
return 18;
}
public static int centerX(int w, String text, int font) {
return (w - Fonts.getWidth(text, font)) / 2;
}
public static int centerY(int h) {
return (h - 18) / 2;
}
}
@@ -1,58 +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");
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 LEFT = Identifier.of("saturnclient", "textures/gui/components/left.png");
public static final Identifier RIGHT = Identifier.of("saturnclient", "textures/gui/components/right.png");
public static final Identifier STORE = Identifier.of("saturnclient", "textures/gui/components/store.png");
public static final Identifier COINS = Identifier.of("saturnclient", "textures/gui/components/coins.png");
public static final Identifier SHIRT = Identifier.of("saturnclient", "textures/gui/components/shirt.png");
public static final Identifier CIRCLE_X = Identifier.of("saturnclient", "textures/gui/components/circle_x.png");
public static final Identifier INFO = Identifier.of("saturnclient", "textures/gui/components/info.png");
public static final Identifier CHECK = Identifier.of("saturnclient", "textures/gui/components/check.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");
public static final Identifier LOGO_TEXT_BIG = Identifier.of("saturnclient", "textures/logo/text_big.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 getEmotePreview(String emote) {
return Identifier.of("saturnclient", "textures/gui/emote/" + (emote == null ? "none" : emote) + ".png");
}
public static Identifier getModIcon(String modId) {
return Identifier.of("saturnclient", "textures/gui/mod/" + modId + ".png");
}
}
@@ -1,131 +0,0 @@
package org.saturnclient.ui2.screens;
import java.util.Map;
import org.saturnclient.saturnclient.config.ConfigManager;
import org.saturnclient.saturnclient.config.Property;
import org.saturnclient.ui2.SaturnScreen;
import org.saturnclient.ui2.components.Sidebar;
import org.saturnclient.ui2.components.inputs.FloatInput;
import org.saturnclient.ui2.components.inputs.HexInput;
import org.saturnclient.ui2.components.inputs.IntInput;
import org.saturnclient.ui2.components.inputs.KeybindingSelector;
import org.saturnclient.ui2.components.inputs.Select;
import org.saturnclient.ui2.components.inputs.Toggle;
import org.saturnclient.ui2.elements.Scroll;
import org.saturnclient.ui2.elements.Text;
import org.saturnclient.ui2.resources.Fonts;
public class ConfigEditor extends SaturnScreen {
private ConfigManager config;
public ConfigEditor(ConfigManager config) {
super("Config Editor");
this.config = config;
}
@Override
public void ui() {
int p = 10;
int g = 10;
Scroll scroll = new Scroll(p);
drawProperties(scroll, config.getProperties(), 0, 0, 480 + 10 + (g * 2));
int scrollWidth = 480 + 10 + (g * 2) + (p * 2);
draw(scroll.dimensions(scrollWidth, 350).center(width, height));
draw(new Sidebar(1, this::close).centerOffset(width, height, -(scrollWidth / 2 + 20), 0));
}
@SuppressWarnings("unchecked")
private int drawProperties(Scroll configScroll, Map<String, Property<?>> properties, int row, int col, int w) {
for (Map.Entry<String, Property<?>> propEntry : properties.entrySet()) {
Property<?> prop = propEntry.getValue();
String propName = propEntry.getKey();
boolean full = isFull(prop);
// Always reset to col 0 if full-width component needs to be drawn mid-row
if (full && col != 0) {
col = 0;
row++;
}
int modX = (w / 2) * col; // two-column layout
int modY = 25 * row;
if (prop.getType() == Property.PropertyType.NAMESPACE) {
configScroll.draw(
new Text(
propName
).position(Fonts.centerX(520, propName, Text.font.value), modY + 1).scale(0.7f));
} else {
configScroll.draw(
new Text(
propName
).position(modX, modY + 1).scale(0.7f));
}
switch (prop.getType()) {
case BOOLEAN:
configScroll.draw(
new Toggle((Property<Boolean>) prop)
.position(modX + (w / 2) - 40, modY)
.scale(0.5f));
break;
case HEX:
configScroll.draw(new HexInput((Property<Integer>) prop, this).position(w / 2, modY));
break;
case INTEGER:
configScroll.draw(new IntInput((Property<Integer>) prop).position(w / 2, modY));
break;
case FLOAT:
configScroll.draw(new FloatInput((Property<Float>) prop).position(w / 2, modY));
break;
case NAMESPACE:
Map<String, Property<?>> nestedProperties = (Map<String, Property<?>>) prop.value;
row = drawProperties(configScroll, nestedProperties, row + 1, col, w);
break;
case SELECT:
configScroll.draw(new Select((Property<Integer>) prop).position(w / 2, modY));
break;
case KEYBINDING:
configScroll.draw(new KeybindingSelector((Property<Integer>) prop).position(w / 2, modY));
break;
case STRING:
}
if (full) {
row++; // full-width takes a whole row
col = 0;
} else {
col++;
if (col > 1) { // max 2 columns
col = 0;
row++;
}
}
}
return row;
}
public static boolean isFull(Property<?> prop) {
switch (prop.getType()) {
case BOOLEAN:
return false;
default:
return true;
}
}
}
@@ -1,92 +0,0 @@
package org.saturnclient.ui2.screens;
import org.saturnclient.saturnclient.SaturnClient;
import org.saturnclient.saturnclient.SaturnClientConfig;
import org.saturnclient.saturnclient.auth.Auth;
// import org.saturnclient.saturnclient.auth.Auth;
import org.saturnclient.saturnclient.cosmetics.Emotes;
import org.saturnclient.ui2.SaturnScreen;
import org.saturnclient.ui2.Utils;
import org.saturnclient.ui2.anim.Fade;
import org.saturnclient.ui2.anim.SlideY;
import org.saturnclient.ui2.elements.ImageTexture;
import org.saturnclient.ui2.elements.TextureButton;
import org.saturnclient.ui2.resources.Textures;
import dev.kosmx.playerAnim.api.layered.AnimationStack;
import dev.kosmx.playerAnim.minecraftApi.PlayerAnimationAccess;
import dev.kosmx.playerAnim.minecraftApi.PlayerAnimationRegistry;
import net.minecraft.util.Identifier;
public class EmoteWheel extends SaturnScreen {
int page = 1;
public EmoteWheel() {
super("Emote Wheel");
}
@Override
public void ui() {
int row = 0;
int col = 0;
draw(new ImageTexture(Textures.LOGO_TEXT).dimensions(98, 10).centerOffset(width, height, 0, -105)
.animation(new Fade(700)));
draw(new ImageTexture(SaturnClientConfig.getLogo()).dimensions(98, 98).centerOffset(width, height, 0, -149)
.animation(new SlideY(700, -20)));
draw(new TextureButton(Textures.LEFT, () -> {
if (page > 1) {
page--;
elements.clear();
ui();
}
}).dimensions(30, 30).centerOffset(width, height, -20, 45).animation(new Fade(700)));
draw(new TextureButton(Textures.RIGHT, () -> {
if ((8 * page) < Emotes.availableEmotes.size()) {
page++;
elements.clear();
ui();
}
}).dimensions(30, 30).centerOffset(width, height, 20, 45).animation(new Fade(700)));
while (row < 3) {
int idx = (row * 3 + col) * page;
if (row == 1 && col == 1) {
col++;
}
String emote = Utils.getOrNull(Emotes.availableEmotes, idx);
draw(new TextureButton(Textures.getEmotePreview(emote), () -> {
if (emote == null) {
return;
}
AnimationStack animationStack = PlayerAnimationAccess.getPlayerAnimLayer(SaturnClient.client.player);
if (animationStack.isActive() && animationStack.getPriority() == 1000) {
animationStack.removeLayer(1000);
}
animationStack.addAnimLayer(1000,
PlayerAnimationRegistry.getAnimation(Identifier.of("saturnclient",
emote)).playAnimation());
Auth.emote(emote);
close();
}).dimensions(70, 70).centerOffset(width, height, -80 + (col * 80), -35 + (row * 80))
.animation(new Fade(700)));
if (col < 2) {
col++;
} else {
col = 0;
row++;
}
}
}
@Override
public boolean shouldPause() {
return false;
}
}
@@ -1,46 +0,0 @@
package org.saturnclient.ui2.screens;
import org.saturnclient.modules.ModManager;
import org.saturnclient.modules.Module;
import org.saturnclient.ui2.SaturnScreen;
import org.saturnclient.ui2.components.SaturnModule;
import org.saturnclient.ui2.components.Sidebar;
import org.saturnclient.ui2.elements.ImageTexture;
import org.saturnclient.ui2.elements.Scroll;
import org.saturnclient.ui2.resources.Textures;
public class ModMenu extends SaturnScreen {
public ModMenu() {
super("Mod Menu");
}
@Override
public void ui() {
int p = 10;
int g = 10;
Scroll scroll = new Scroll(p);
int row = 0;
int col = 0;
for (Module mod : ModManager.MODS) {
scroll.draw(new SaturnModule(mod).position((160 + g) * col, (50 + g) * row));
if (col == 2) {
col = 0;
row++;
} else {
col++;
}
}
int scrollWidth = 480 + 10 + (g * 2) + (p * 2);
draw(scroll.dimensions(scrollWidth, 350).center(width, height));
draw(new Sidebar(0, this::close).centerOffset(width, height, -(scrollWidth / 2 + 20), 0));
draw(new ImageTexture(Textures.LOGO_TEXT_BIG).dimensions(180, 18).position(width - 180 - 20, height - 18 - 20));
}
}
@@ -1,40 +0,0 @@
package org.saturnclient.ui2.screens;
import org.saturnclient.saturnclient.SaturnClient;
import org.saturnclient.saturnclient.SaturnClientConfig;
import org.saturnclient.saturnclient.menus.HudEditor;
import org.saturnclient.ui2.resources.Textures;
import org.saturnclient.ui2.screens.cosmetics.CloakMenu;
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, 10).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, -20)));
Element button = new Button("Settings", () -> {
SaturnClient.client.setScreen(new ModMenu());
}).center(width, height);
draw(button);
draw(new TextureButton(Textures.COSMETICS, () -> {
client.setScreen(new CloakMenu());
}).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,62 +0,0 @@
package org.saturnclient.ui2.screens;
import org.saturnclient.saturnclient.SaturnClient;
import org.saturnclient.saturnclient.SaturnClientConfig;
import org.saturnclient.saturnclient.menus.HudEditor;
import org.saturnclient.ui2.resources.Textures;
import org.saturnclient.ui2.screens.cosmetics.CloakMenu;
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, 10).centerOffset(width, height, 0, -36).animation(new Fade(700)));
draw(new ImageTexture(SaturnClientConfig.getLogo()).dimensions(98, 98).centerOffset(width, height, 0, -82).animation(new SlideY(700, -20)));
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 CloakMenu());
}).dimensions(s, s).centerHorizontal(width, height, s + 2, 8).animation(new SlideY(700, s + 8)));
}
}
@@ -1,63 +0,0 @@
package org.saturnclient.ui2.screens.cosmetics;
import org.saturnclient.saturnclient.SaturnClient;
import org.saturnclient.saturnclient.auth.Auth;
import org.saturnclient.saturnclient.auth.SaturnPlayer;
import org.saturnclient.saturnclient.cosmetics.cloaks.Cloaks;
import org.saturnclient.ui2.resources.Textures;
import org.saturnclient.ui2.SaturnScreen;
import org.saturnclient.ui2.components.CosmeticPreview;
import org.saturnclient.ui2.components.Sidebar;
import org.saturnclient.ui2.components.SkinPreview;
import org.saturnclient.ui2.elements.Scroll;
import org.saturnclient.ui2.elements.TabMenu;
import org.saturnclient.ui2.elements.TabMenu.TabMenuComponent;
public class CloakMenu extends SaturnScreen {
public CloakMenu() {
super("Cloaks Menu");
}
@Override
public void ui() {
int p = 10;
int g = 10;
Scroll scroll = new Scroll(p);
int row = 0;
int col = 0;
SaturnPlayer player = Auth.players.get(Auth.uuid);
if (player != null) {
for (String cloak : Cloaks.availableCloaks) {
scroll.draw(new CosmeticPreview(cloak == player.cloak, Textures.getCloakPreview(cloak), () -> {
Cloaks.setCloak(cloak);
}).position((50 + g) * col, (111 + g) * row));
if (col == 5) {
col = 0;
row++;
} else {
col++;
}
}
}
int scrollWidth = 480 + 10 + (g * 2) + (p * 2);
draw(scroll.dimensions(scrollWidth, 350).center(width, height));
draw(new SkinPreview(-30.0f, false).scale(3.5f).position(scroll.x + (scrollWidth - 220), scroll.y+40));
draw(new Sidebar(2, this::close).centerOffset(width, height, -(scrollWidth / 2 + 20), 0));
draw(new TabMenu(0,
new TabMenuComponent(Textures.CLOAK, () -> {}),
new TabMenuComponent(Textures.HAT, () -> {
SaturnClient.client.setScreen(new HatMenu());
})
).centerOffset(width, height, 0, -195));
}
}
@@ -1,63 +0,0 @@
package org.saturnclient.ui2.screens.cosmetics;
import org.saturnclient.saturnclient.SaturnClient;
import org.saturnclient.saturnclient.auth.Auth;
import org.saturnclient.saturnclient.auth.SaturnPlayer;
import org.saturnclient.saturnclient.cosmetics.Hats;
import org.saturnclient.ui2.resources.Textures;
import org.saturnclient.ui2.SaturnScreen;
import org.saturnclient.ui2.components.CosmeticPreview;
import org.saturnclient.ui2.components.Sidebar;
import org.saturnclient.ui2.components.SkinPreview;
import org.saturnclient.ui2.elements.Scroll;
import org.saturnclient.ui2.elements.TabMenu;
import org.saturnclient.ui2.elements.TabMenu.TabMenuComponent;
public class HatMenu extends SaturnScreen {
public HatMenu() {
super("Hats Menu");
}
@Override
public void ui() {
int p = 10;
int g = 10;
Scroll scroll = new Scroll(p);
int row = 0;
int col = 0;
SaturnPlayer player = Auth.players.get(Auth.uuid);
if (player != null) {
for (String hat : Hats.availableHats) {
scroll.draw(new CosmeticPreview(hat == player.hat, Textures.getHatPreview(hat), () -> {
Hats.setHat(Auth.uuid, hat);
}).dimensions(50, 50).position((50 + g) * col, (50 + g) * row));
if (col == 5) {
col = 0;
row++;
} else {
col++;
}
}
}
int scrollWidth = 480 + 10 + (g * 2) + (p * 2);
draw(scroll.dimensions(scrollWidth, 350).center(width, height));
draw(new SkinPreview(170f, true).scale(3.5f).position(scroll.x + (scrollWidth - 220), scroll.y+40));
draw(new Sidebar(2, this::close).centerOffset(width, height, -(scrollWidth / 2 + 20), 0));
draw(new TabMenu(1,
new TabMenuComponent(Textures.CLOAK, () -> {
SaturnClient.client.setScreen(new CloakMenu());
}),
new TabMenuComponent(Textures.HAT, () -> {})
).centerOffset(width, height, 0, -195));
}
}
@@ -1,75 +0,0 @@
package org.saturnclient.ui2.screens.store;
import org.saturnclient.saturnclient.SaturnClient;
import org.saturnclient.saturnclient.auth.Auth;
import org.saturnclient.saturnclient.auth.SaturnPlayer;
import org.saturnclient.saturnclient.cosmetics.cloaks.Cloaks;
import org.saturnclient.ui2.SaturnScreen;
import org.saturnclient.ui2.components.CosmeticPreview;
import org.saturnclient.ui2.components.Sidebar;
import org.saturnclient.ui2.elements.ImageTexture;
import org.saturnclient.ui2.elements.Scroll;
import org.saturnclient.ui2.elements.TabMenu;
import org.saturnclient.ui2.elements.TabMenu.TabMenuComponent;
import org.saturnclient.ui2.elements.Text;
import org.saturnclient.ui2.resources.Fonts;
import org.saturnclient.ui2.resources.Textures;
public class CloakStore extends SaturnScreen {
public CloakStore() {
super("Hat Store");
}
@Override
public void ui() {
int p = 10;
int gx = 10;
int gy = 20;
Scroll scroll = new Scroll(p);
int row = 0;
int col = 0;
SaturnPlayer player = Auth.players.get(Auth.uuid);
if (player != null) {
for (String cloak : Cloaks.ALL_CLOAKS) {
if (!Cloaks.availableCloaks.contains(cloak)) {
int x = (50 + gx) * col;
int y = (111 + gy) * row;
scroll.draw(new CosmeticPreview(cloak == player.cloak, Textures.getCloakPreview(cloak), () -> {
Auth.buyCloak(cloak);
SaturnClient.client.setScreen(new CloakStore());
}).position(x, y));
String t = "100";
scroll.draw(new Text(t).position(x + Fonts.centerX(50, t, Text.font.value), y + 113).scale(0.5f));
scroll.draw(new ImageTexture(Textures.COINS).dimensions(16, 16).position(x + Fonts.getWidth(t, Text.font.value) - 1, y + 112).scale(0.5f));
if (col == 8) {
col = 0;
row++;
} else {
col++;
}
}
}
}
int scrollWidth = 480 + 40 + (gx * 2) + (p * 2);
draw(scroll.dimensions(scrollWidth, 350).centerOffset(width, height, 15, 0));
draw(new Sidebar(5, this::close).centerOffset(width, height, -((scrollWidth - 30) / 2 + 20), 0));
draw(new TabMenu(0,
new TabMenuComponent(Textures.CLOAK, () -> {}),
new TabMenuComponent(Textures.HAT, () -> {
SaturnClient.client.setScreen(new HatStore());
})
).centerOffset(width, height, 0, -195));
}
}
@@ -1,75 +0,0 @@
package org.saturnclient.ui2.screens.store;
import org.saturnclient.saturnclient.SaturnClient;
import org.saturnclient.saturnclient.auth.Auth;
import org.saturnclient.saturnclient.auth.SaturnPlayer;
import org.saturnclient.saturnclient.cosmetics.Hats;
import org.saturnclient.ui2.SaturnScreen;
import org.saturnclient.ui2.components.CosmeticPreview;
import org.saturnclient.ui2.components.Sidebar;
import org.saturnclient.ui2.elements.ImageTexture;
import org.saturnclient.ui2.elements.Scroll;
import org.saturnclient.ui2.elements.TabMenu;
import org.saturnclient.ui2.elements.TabMenu.TabMenuComponent;
import org.saturnclient.ui2.elements.Text;
import org.saturnclient.ui2.resources.Fonts;
import org.saturnclient.ui2.resources.Textures;
public class HatStore extends SaturnScreen {
public HatStore() {
super("Hat Store");
}
@Override
public void ui() {
int p = 10;
int gx = 10;
int gy = 20;
Scroll scroll = new Scroll(p);
int row = 0;
int col = 0;
SaturnPlayer player = Auth.players.get(Auth.uuid);
if (player != null) {
for (String hat : Hats.ALL_HATS) {
if (!Hats.availableHats.contains(hat)) {
int x = (50 + gx) * col;
int y = (50 + gy) * row;
scroll.draw(new CosmeticPreview(hat == player.hat, Textures.getHatPreview(hat), () -> {
Auth.buyHat(hat);
SaturnClient.client.setScreen(new HatStore());
}).dimensions(50, 50).position(x, y));
String t = "50";
scroll.draw(new Text(t).position(x + Fonts.centerX(50, t, Text.font.value), y + 53).scale(0.5f));
scroll.draw(new ImageTexture(Textures.COINS).dimensions(16, 16).position(x + Fonts.getWidth(t, Text.font.value) + 4, y + 52).scale(0.5f));
if (col == 8) {
col = 0;
row++;
} else {
col++;
}
}
}
}
int scrollWidth = 480 + 40 + (gx * 2) + (p * 2);
draw(scroll.dimensions(scrollWidth, 350).centerOffset(width, height, 15, 0));
draw(new Sidebar(5, this::close).centerOffset(width, height, -((scrollWidth - 30) / 2 + 20), 0));
draw(new TabMenu(1,
new TabMenuComponent(Textures.CLOAK, () -> {
SaturnClient.client.setScreen(new CloakStore());
}),
new TabMenuComponent(Textures.HAT, () -> {})
).centerOffset(width, height, 0, -195));
}
}
@@ -1,12 +0,0 @@
{
"providers": [
{
"type": "reference",
"id": "saturnclient:inter"
},
{
"type": "reference",
"id": "saturnclient:inter_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

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