Compare commits

..

199 Commits

Author SHA1 Message Date
Motschen
d48952fec9 Fix YACL category switching with bumpers 2023-01-03 18:47:41 +01:00
Martin Prokoph
9deab7228d Merge pull request #144 from isXander/compat/yacl
YACL compatibility.
2023-01-03 17:25:21 +00:00
Martin Prokoph
b4d82a7230 Merge branch '1.19.3' into compat/yacl 2023-01-03 17:23:48 +00:00
Motschen
36e241e438 MidnightControls 1.7.3 - FAPI 0.71 hotfix 2023-01-03 18:18:21 +01:00
Martin Prokoph
bdbee99fe2 Merge pull request #155 from HttpRafa/1.19.3
Fixed error with Fabric API version 0.71.0+1.19.3
2023-01-03 17:02:12 +00:00
Martin Prokoph
439cd0858e Merge pull request #149 from glaav/patch-1
it_it.json (italian translations)
2023-01-03 17:01:08 +00:00
HttpRafa
617648cf57 Fixed error with Fabric API version 0.71.0+1.19.3 2023-01-03 03:11:44 +01:00
glaav
89914a1b6e Italian Translations 2022-12-15 19:51:03 +01:00
isXander
c272fb457f YACL compat (WIP) 2022-12-09 22:50:51 +00:00
Motschen
308b41094f MidnightControls 1.7.2 - Port to 1.19.3, various new options
- Update to 1.19.3 (Tabbing through creative tabs behaves the same as before, that was hard)
- Add an option to move the chat input field to the top of the screen for better input on devices using an on-screen keyboard
- Add an option to make the left joystick behave like a mouse in every screen
- Add a button to more easily access the Advanced Config
2022-12-09 20:02:59 +01:00
Martin Prokoph
bf7209083a Merge pull request #140 from eric122804/patch-1
Create zh_tw
2022-12-09 13:19:55 +00:00
Martin Prokoph
187844db2b Merge pull request #142 from eric122805/patch-1
Create zh_tw
2022-12-09 13:19:19 +00:00
eric122805
24ced76d06 Create zh_tw 2022-12-08 20:30:20 +08:00
eric122804
aa75a58a4d Create zh_tw 2022-12-07 20:30:51 +08:00
Motschen
1f675622f0 Use MidnightLib 22w43a version from Modrinth 2022-10-27 20:51:17 +02:00
Motschen
2d902bbf3c Port to 22w43a 2022-10-27 20:25:26 +02:00
Motschen
9cc5393703 More mod compatibility & Add missing contributor names 2022-10-27 20:00:14 +02:00
Motschen
8d08fdedf4 MidnightControls 1.7.0 - Fixes & Controller Profiles
- Fix diagonal movement speed (PR #113, thanks @dogtopus, closes #42)
- Toggle Sprint and Toggle Sneak can now be enabled separately just for controllers (closes #102)
- Added button binding profile support (closes #107)
- Fix button binding tooltips intersecting with chat on large GUI scales (closes #106)
- Add assets for non-licensed (numbered) PlayStation controllers (closes #110)
- Fix reacharound outline color
- Fix pixel error in legacy icons (closes #84)
- Fix creative flight being slow with Bedrockify installed (just disables the "disableFlyingMomentum" option on launch)
2022-10-16 15:53:03 +02:00
Martin Prokoph
cdafde01f3 Merge pull request #113 from dogtopus/preserve-speed-when-moving-diagonally
Preserve speed when moving diagonally
2022-10-13 15:26:53 +02:00
dogtopus
c07c4200f7 Clamp the speed to 1 so using dpad will not double the speed 2022-10-12 19:17:27 -03:00
dogtopus
c70da21df6 Preserve speed when moving diagonally
The original algorithm squares both X and Y separately, which cause the movement speed to drop by up to half when not moving perfectly along the axes. The new algorithm converts the XY speed vector into a speed and a moving angle vector and only square the speed instead. This prevents the slowdown issue present in the original algorithm.
2022-10-12 18:55:53 -03:00
Motschen
f5d5d93c19 Lowered value at which the HUD is considered as crammed 2022-09-23 21:06:21 +02:00
Motschen
429b4ca607 Add controller profiles, Fix HUD issues, Fix Reacharound Outline Color
Addresses #107 and #106
2022-09-23 20:48:32 +02:00
Motschen
dd173836ff MidnightControls 1.6.0 - Stability, QoL, Cleanup & Compat
- Fixed camera choppiness (especially noticeable on low framerates, closes #38)
- Asset improvements kindly contributed by @spudpiggy
- Button tips now adjust properly to the scaled width of the window (closes #95)
- Make 'Back' binding configurable (closes #93)
- Add config option to disable the button in the controls screen (closes #97)
- Add compatibility with the Inventory Tabs mod (closes #100)
- Improve Emotecraft compatibility
- Add radial menu listing unbound keys (closes #101)
- Fix disabling the HUD not hiding the tips in container screens (closes #104)
- Fix 'Back' button not working correctly
- Fix recipe book not switching tab via shoulder buttons in Crafting Table and Furnace screens
- Support scrolling in Stonecutter
2022-09-21 20:58:27 +02:00
Motschen
203fd6cdb6 First 1.6.0 changes
Way too many changes & fixes to list here, still got some work to do, but this is coming along great already
2022-09-18 20:20:10 +02:00
Martin Prokoph
003f79d405 Merge pull request #98 from TrueHorse/feature/useBuildInScroll
Use build in scrollInHotbar
2022-09-06 17:03:23 +00:00
Martin Prokoph
f48527402a Merge pull request #83 from Madis0/patch-3
Update et_ee.json
2022-09-06 16:47:02 +00:00
Paul Roß
8c4b705e65 use build in scrollInHotbar 2022-09-04 14:17:36 +02:00
Madis Otenurm
1def693ae8 Update et_ee.json 2022-08-03 14:45:03 +03:00
Madis Otenurm
cfd05bb7b6 Update et_ee.json 2022-08-03 14:44:32 +03:00
Motschen
2e861470d8 MidnightControls 1.5.0 - Hints, Legacy Icons, LibGUI
- Add hints when controller is available, but not enabled (Closes #81)
- Add optional Legacy controller icon pack (thanks @Ivanoks)
- Add LibGUI compatibility
- Fix #75 (Swift Sneak enchantment not working, thanks @andrewaramsay)
- Fix crawling being too fast
- Minify assets by @Madis0 and @RaptaG
2022-08-03 12:19:33 +02:00
Motschen
e87ed53485 Push 1.4.2 progress to GitHub (not finished yet) 2022-07-23 13:47:57 +02:00
Martin Prokoph
8847c7f499 Merge pull request #78 from RaptaG/fixed-png-name
Fix wrong name on `controller-buttons.png`
2022-07-22 14:24:10 +00:00
RaptaG
da2773b1e9 Fix wrong name (you're welcome @Madis0) 2022-07-22 14:22:52 +03:00
Martin Prokoph
3650e756c3 Merge pull request #76 from Madis0/compress
Minified images
2022-07-22 09:27:54 +00:00
Madis
6b885beede Minified images 2022-07-20 22:15:41 +03:00
Martin Prokoph
abb3d1de3c Merge pull request #62 from RaptaG/patch-1
Improve README and update the screenshots
2022-07-18 08:41:16 +00:00
Martin Prokoph
a8cdfeeba9 Merge pull request #67 from Ivanoks/1.19
Updated to 1.4.0 format!
2022-07-18 08:40:27 +00:00
Ivan
ed4d310565 Update ru_ru.json 2022-07-09 21:29:11 +06:00
Ivan
ac73c99506 1.4.0 2022-07-09 21:23:11 +06:00
Ivan
d45eeac6dc 1.4.0 new update format. 2022-07-09 20:57:47 +06:00
RaptaG
77ff589544 Actually update screenshots 2022-07-07 16:35:58 +03:00
RaptaG
ce5c0b22db Delete controller_options.png 2022-07-07 13:34:53 +00:00
RaptaG
98af13fd0b Delete 2022-07-07_14.35.12.png 2022-07-07 13:34:11 +00:00
RaptaG
2387444d3a Delete 2022-07-07_14.34.52.png 2022-07-07 13:34:04 +00:00
RaptaG
57860490c8 Updated screenshots 2022-07-07 16:33:35 +03:00
RaptaG
f46aa175a4 Delete controller_controls.png 2022-07-07 13:32:29 +00:00
RaptaG
78390ca02f Modrinth downloads and improve CF downloads 2022-07-07 13:31:57 +00:00
RaptaG
31821b4833 Add the icon 2022-07-07 12:37:01 +00:00
RaptaG
e34ba6025a More improvements 2022-07-07 12:34:57 +00:00
Motschen
9891cd2977 MidnightControls 1.4.1 - Fix tooltips getting displaced
Fixes tooltips in inventories getting displaced when having the GUI scale set to something above 4.
2022-07-06 14:14:25 +02:00
RaptaG
8566e859e3 Improve README 2022-07-06 09:25:51 +00:00
Motschen
735b9e8718 MidnightControls 1.4.0 - Many Improvements and Fixes
- EMI compat! (Switch page keybinds & more)
- Expose more keybindings
- Fix Compatibility with Quilt Creative Extension
- Improve Auto Switch mode (Automatically switch gamepad on connection changes)
- Fix triggers not working on some (slightly broken) controllers
- Fix #57 (Broken stack pickup)
- Fix #56 (Unused language strings)
- Fix #52 (Rebound perspective button blocking actions)
- Fix #46 (Button overlap with Crawl mod)
- Fix #59 (D-Pad can switch EMI buttons when in Inventories)
2022-07-05 21:15:31 +02:00
Martin Prokoph
71ee3d8e0a Merge pull request #55 from Madis0/patch-2
Estonian translation
2022-07-04 18:24:31 +00:00
Martin Prokoph
11f102759c Merge pull request #54 from Madis0/patch-1
Fix missing tooltip
2022-07-04 18:23:47 +00:00
Martin Prokoph
02dd543027 Merge pull request #53 from Ivanoks/1.19
Added Russian Language.
2022-07-04 18:22:39 +00:00
KiskaUWU
c247e51d00 Update ru_ru.json 2022-07-03 00:58:28 +10:00
KiskaUWU
e3052343d3 Update ru_ru.json 2022-07-03 00:57:17 +10:00
KiskaUWU
9e879f9230 Update ru_ru.json 2022-07-03 00:50:56 +10:00
Ivan
bda436d863 Update ru_ru.json
Fix the 128 ",".
2022-07-02 20:42:13 +06:00
KiskaUWU
98d2cdae63 Update ru_ru.json 2022-07-03 00:39:28 +10:00
Ivan
8e0e444d15 Update ru_ru.json
Changed the word format in line 99.
2022-07-02 20:07:07 +06:00
Madis Otenurm
f586647e07 Update et_ee.json 2022-07-02 17:02:47 +03:00
Ivan
73ab8456e7 Update ru_ru.json
Added subtitle about double-tap for run.
+ fix the extra characters in line 165.
2022-07-02 20:00:45 +06:00
Madis Otenurm
1a3d7ce225 Create et_ee.json 2022-07-02 16:42:40 +03:00
Madis Otenurm
965ab57159 Oops 2022-07-02 16:12:47 +03:00
Madis Otenurm
c8dba43b90 Update en_us.json 2022-07-02 12:44:08 +03:00
Ivan
dd8e62c73e Update ru_ru.json 2022-07-02 03:03:52 +06:00
Ivan
b24a158b9b Add files via upload 2022-07-02 02:12:07 +06:00
Motschen
df7c7bbdad MidnightControls 1.3.0 - Fixes and Improvements
- Merge #48 (Split rotation speed in X and Y, thanks to @ronniedude)
- Merge #47 (Korean translations by @gyular)
- Fix #50 (Stack pick-up problems in survival mode)
- Add some compatibility measures for EMI (Will likely be improved further in the future)
- Remove all REI-related code (not working anyway, EMI is better)
- Possibly fix #41 (Random crashes when changing keybinds)
2022-06-30 21:29:59 +02:00
Martin Prokoph
3ddae57eab Merge pull request #47 from gyular/ko_kr
Added Korean Translation
2022-06-30 19:20:55 +00:00
Martin Prokoph
a0701f55f6 Merge pull request #48 from ronniedude/1.19
Split rotationspeed option to allow for separate horizontal and vertical speeds (controller)
2022-06-30 15:03:02 +00:00
ronniedude
b15766dfdb Split rotationspeed option to allow for separate horizontal and vertical speed configurations (controller) 2022-06-29 16:55:15 -04:00
gyular
19ed068da9 Added Korean Translation 2022-06-28 01:53:56 +09:00
Motschen
d2e21f8723 MidnightControls 1.2.2 - Important Bugfix
Fix #45
Fix #44
2022-06-27 14:10:03 +02:00
Motschen
b3f7350de9 MidnightControls 1.2.1 - Fix log spam 2022-06-25 22:21:51 +02:00
Motschen
34408d7a2a MidnightControls 1.2.0 - Touchscreen, Modded Keybinds, Bugfixes
- Added #40 (Modded keybind support)
- Added #20 (Touchscreen support)
- Improved #13 (Sodium screen controller support)
- Attempt to fix #31 & #38 (Jittery input on low FPS)
- Fixed #35 (Front placing being broken)
- Fixed #32 (Option to disable double tap to sprint)
- Fixed #27 (Auto-adapt controller icons)
- Fixed #19 (HUD-scaling on big scales)
- Fixed #36 (Crash on game load)
- Fixed reset option
- Fixed scrolling in trading screens
- Disable features that might be considered as cheats (install MidnightControlsExtra to enable)
2022-06-25 21:23:25 +02:00
Motschen
6e64c7c97d MidnightControls 1.1.0 - 1.19, Inventory Improvements
- Update to 1.19
- Make buttons for Item/Stack Grabbing and Quick Moving configurable
- Add Keybinds for moving between Fabric Creative Inventory Pages
2022-06-09 21:59:26 +02:00
Martin Prokoph
bca73c93cc Merge pull request #29 from akemin-dayo/1.18
OkZoomerCompat, MidnightControlsCompat: Added compatibility with Ok Zoomer 5.0.0-beta.7 (Quilt).
2022-06-09 14:30:39 +00:00
Karen/あけみ
913e79fe5b OkZoomerCompat, MidnightControlsCompat: Added some more comments in the code. 2022-06-08 20:45:50 -07:00
Karen/あけみ
b1334a0455 OkZoomerCompat, MidnightControlsCompat: Added compatibility with Ok Zoomer 5.0.0-beta.7 (Quilt). Tested with Minecraft 1.18.2, Ok Zoomer 5.0.0-beta.7, quilted-fabric-api/QSL 1.1.0-beta.17 (implementing Fabric API 0.55.1 and QFAPI 1.0.0-beta.19), and Quilt Loader 0.17.0-beta.2. And yes, MidnightControls works with Quilt. 2022-06-08 20:40:35 -07:00
Motschen
ce9495c79c Fix vanilla mouseScreens not working in production 2022-05-08 19:42:41 +02:00
Motschen
ff8270d18a MidnightControls 1.0.0 - Bedrock icon resourcepack
- Bring back Lambda-style XBox icons and add optional resourcepack for Bedrock-style icons (Closes #16)
- Make screens on which the controller behaves like a mouse configurable in the advanced config.
2022-05-08 18:46:46 +02:00
Martin Prokoph
3f33549760 Merge pull request #18 from akemin-dayo/1.18
MidnightControlsSettingsScreen: Fix midnightcontrols.tooltip.controls_mode localisation key, which somehow got clobbered in commit SHA1 0226bfc62f.
2022-05-03 20:59:43 +02:00
Karen/あけみ
d034607032 Merge branch '1.18' of https://github.com/TeamMidnightDust/MidnightControls into 1.18
* '1.18' of https://github.com/TeamMidnightDust/MidnightControls:
  Fix crash on resizing options screen
  MidnightControls 0.3.0 - Automatic mapping downloading
2022-05-03 11:54:22 -07:00
Karen/あけみ
e9a11c0ab9 MidnightControlsSettingsScreen: Fix midnightcontrols.tooltip.controls_mode localisation key, which somehow got clobbered in commit SHA1 0226bfc62f. 2022-05-03 11:50:00 -07:00
Motschen
e0d90fc56a Fix crash on resizing options screen 2022-05-03 20:41:24 +02:00
Motschen
cac23d12a1 MidnightControls 0.3.0 - Automatic mapping downloading
- Automatic downloading of mappings from https://github.com/gabomdq/SDL_GameControllerDB
- Change some default settings
- Fix OkZoomer compat by @akemin-dayo
- German translations (by myself)
- Add contributors to fabric.mod.json
- Fix some issues
2022-05-03 20:29:29 +02:00
Martin Prokoph
b982772d31 Merge pull request #17 from akemin-dayo/akemi-ok-zoomer-compat
OkZoomerCompat, MidnightControlsCompat: Major rewrite that now adds compatibility with both 5.0.0-beta Ok Zoomer API versions via Reflection.
2022-05-03 17:23:59 +02:00
Martin Prokoph
e4ca0db2a2 Merge pull request #15 from akemin-dayo/1.18
Added an option to always show the HUD while in Controller Mode, matching the legacy behaviour of LambdaControls. Also changed some default settings to be "safe" in the sense that the features that can be considered "cheating" in multiplayer servers are now disabled by default.
2022-05-03 17:23:45 +02:00
Karen/あけみ
f0cd2a51d1 build.gradle, gradle.properties: Temporarily comment out Ok Zoomer and LibZoomer dependencies, since we don't use them right now as OkZoomerCompat uses Reflection at runtime instead to deal with the current API incompatibility situation. 2022-05-03 07:50:08 -07:00
Karen/あけみ
9d37862144 OkZoomerCompat, MidnightControlsCompat: Major rewrite that now adds compatibility with both 5.0.0-beta Ok Zoomer API versions via Reflection. This fully fixes all current Ok Zoomer-related crashing issues, such as those encountered in #3. Tested with Minecraft 1.18.2 (5.0.0-beta.6) and Minecraft 1.18.1 (5.0.0-beta.3). 2022-05-03 07:43:06 -07:00
Karen/あけみ
c31fb76215 MidnightControlsConfig: Changed some default settings to be "safe" in the sense that the features that can be considered "cheating" in multiplayer servers are now disabled by default. 2022-05-03 05:02:23 -07:00
Karen/あけみ
b6563af2b1 hud_always_show: Add untranslated strings to es_mx, fr_ca, fr_ft, tr_tr, and zh_cn. 2022-05-03 04:54:07 -07:00
Karen/あけみ
1a0030a680 Added an option to always show the HUD while in Controller Mode, matching the legacy behaviour of LambdaControls. This option is enabled by default so that users migrating from LambdaControls will have a consistent experience. 2022-05-03 04:53:45 -07:00
Motschen
78c858b753 Refresh MidnightControlsFeature state on config change 2022-05-03 12:59:08 +02:00
Martin Prokoph
24288b3ebd Update README.md 2022-05-03 10:25:09 +00:00
Motschen
0226bfc62f MidnightControls 0.2.0 - Fixes, improvements, compat
- Fix #4
- Fix #3 (maybe?)
- No longer break OptiFabric
- Better XBox controller icons (#7) by @Ivanoks
- Start of reimplementing REI compat
- Hide HUD when controller is missing
2022-05-03 12:22:20 +02:00
Martin Prokoph
28d92a65d4 Merge pull request #8 from DioEgizio/patch-1
fix links in fmj
2022-04-29 18:28:19 +02:00
DioEgizio
132bdc0cc5 fix links in fmj
fix links in fabric.mod.json
2022-04-21 16:48:43 +02:00
Motschen
bdffef681f Update CHANGELOG.md 2022-03-16 16:40:57 +01:00
Motschen
a49a64ef44 Update README.md 2022-03-12 23:12:17 +01:00
Motschen
0e192b2af4 Merge branch '1.18' of https://github.com/Motschen/LambdaControls into 1.18 2022-03-12 23:10:33 +01:00
Motschen
6dd654f2f5 Update metafiles 2022-03-12 23:10:29 +01:00
Motschen
eb406a4681 Update README.md 2022-03-12 23:02:55 +01:00
Motschen
d386e3d9a2 Update README.md 2022-03-12 23:02:10 +01:00
Motschen
5fcf4c2b1b MidnightControls 0.1.0 (Beta)
Changes from LambdaControls:
- Support for Steam Deck and Dualsense
- Support for L4, L5, R4, R5 buttons
- Updated Libraries
- New Logo and Name
- Lots of Bugfixes
- MidnightConfig backend
2022-03-12 22:33:19 +01:00
Hambaka
bfaa4f5d9a Update Simplified Chinese translation (#74)
Add back the missing Simplified Chinese translation in 1.17 branch from 1.16 branch (don't know why the lang file in 1.17 branch only has 135 lines, it feels like Neubulae's previous pr is disappeared)
Also adjust some translations based on to Neubulae's version.
2021-08-24 17:27:21 +02:00
Ennui Langeweile
5d5d808403 Update and fix Ok Zoomer compatibility (#68) 2021-07-15 01:28:02 +02:00
LambdAurora
d08647b3fc Tweak buildscript. 2021-07-15 01:14:37 +02:00
LambdAurora
1039ea5344 Fix crash at startup. 2021-06-23 15:37:17 +02:00
LambdAurora
bb19ea4da5 Fix buildscript for Modrinth publishing. 2021-06-23 14:32:20 +02:00
LambdAurora
a1773cc0d9 Quick fix. 2021-06-23 14:24:49 +02:00
LambdAurora
e652390583 LambdaControls 1.7.0: update to 1.17. 2021-06-23 14:18:20 +02:00
LambdAurora
f62819d9a4 LambdaControls v1.6.0: Fix several bugs and upgrade settings screen. 2021-03-18 13:28:38 +01:00
Yao Chung Hu
978b94176c Update Mexican Spanish Translation (#49)
* Update Mexican Spanish Translation

* Remove old translation key, add missing translation key

I swear I'm not blind
2021-03-18 13:12:03 +01:00
LambdAurora
c8989cb9ee Add max values to axes, fixes #41. 2021-03-17 23:50:16 +01:00
LambdAurora
97521e832d Add stick-independent dead zones (#32) and update license headers. 2021-03-17 18:49:10 +01:00
LambdAurora
400b625716 Reformat code and update networking. 2021-03-17 00:51:34 +01:00
LambdAurora
f89eebf0e7 More WIP GUI rework! 2021-03-16 22:37:50 +01:00
LambdAurora
cc0a9aebe2 Fix #37, fix some movement bugs, more WIP GUI rework. 2021-03-16 14:51:50 +01:00
LambdAurora
b8ec934c10 Change package. 2021-03-16 00:40:56 +01:00
LambdAurora
ac8fab83a2 More GUI rework! Controller controls tab works now. 2021-03-16 00:31:24 +01:00
LambdAurora
1489b796a8 Fix gradle stupidity. 2021-03-15 21:24:16 +01:00
LambdAurora
ab8b78f75a Modules be gone! 2021-03-15 20:39:40 +01:00
LambdAurora
c7026ba1fd Old stuff be gone. 2021-03-15 17:21:43 +01:00
LambdAurora
0532913d69 WIP broken stuff. 2021-03-15 17:20:16 +01:00
LambdAurora
597700b2bc Merge pull request #34 from egeesin/1.16
Add Turkish localization
2020-11-07 20:08:38 +01:00
Ege
4edb0952d8 Add Turkish translation strings 2020-10-20 03:17:37 +03:00
LambdAurora
178f19dc2c Merge pull request #29 from Hambaka/1.16
Update Simplified Chinese localization.
2020-09-01 19:43:39 +02:00
Hambaka
129c3d0f3b Update Simplified Chinese localization.
Update zh_cn.json

Translate new 5 lines strings.
Adjust some translations. ( followed suggestions in a Chinese Minecraft community project's translation style guide, although most changes in this commit are "add space" )

Note: The translation of "Show controls ring" is not final, since ring menu feature is not completed, I am not sure its feature, so current translation is "Show extra keys (select) menu".
2020-09-02 00:00:27 +08:00
LambdAurora
deccb758ea Fix compilation crash. 2020-09-01 14:30:40 +02:00
LambdAurora
b7c60f8651 🔖 LambdaControls v1.5.0: Update to Minecraft 1.16.2. 2020-09-01 14:29:06 +02:00
LambdAurora
1ee75bc4e5 Fix issues, improve REI compat and horizontal reach-around, WIP on ring. 2020-08-25 16:06:38 +02:00
LambdAurora
1f0ddab36b Fix lot of bugs and update to 1.16.2. 2020-08-19 17:58:50 +02:00
LambdAurora
8a919934e2 Merge pull request #24 from joaoh1/1.16
👽 Fix compatibility with Ok Zoomer 4.0.0 betas
2020-08-14 17:47:08 +02:00
joaoh1
0178f5e684 👽 Fix compatibility with Ok Zoomer 4.0.0 betas 2020-08-14 12:40:45 -03:00
LambdAurora
c495e2ae0c Merge pull request #22 from FlashyReese/1.16
Add es_mx localization
2020-08-03 11:23:02 +02:00
Yao Chung Hu
3ecce09775 fix: use of words 2020-08-02 18:59:59 -05:00
Yao Chung Hu
e94d2eb240 new: added es_mx localization 2020-08-02 18:54:35 -05:00
LambdAurora
7571a2aa1b Merge pull request #21 from joaoh1/1.16
🚸 Hide Ok Zoomer's extra keybinds when disabled
2020-07-31 21:40:44 +02:00
joaoh1
d09a225518 🚸 Hide Ok Zoomer's extra keybinds when disabled 2020-07-31 15:55:43 -03:00
LambdAurora
4290d79bd0 Merge pull request #18 from Hambaka/1.16
🌐 Add Simplified Chinese localization.
2020-07-20 21:09:30 +02:00
Hambaka
f9f0a7a18d 🌐 Add Simplified Chinese localization. 2020-07-21 02:49:01 +08:00
LambdAurora
f889fc6367 Fix chording problem. 2020-07-20 19:22:18 +02:00
LambdAurora
7dcbda7e57 Update CHANGELOG. 2020-07-19 00:11:43 +02:00
LambdAurora
bd065f7b5b 🔖 LambdaControls v1.4.1: Fix crash with REI. 2020-07-19 00:10:22 +02:00
LambdAurora
2785d634dc Fix jar builds. 2020-07-18 18:55:44 +02:00
LambdAurora
257f01ec19 🔖 LambdaControls v1.4.0: Analog movements, 1.16.2 compat, better API, etc. 2020-07-18 18:41:46 +02:00
LambdAurora
127c44a046 Update CHANGELOG. 2020-07-01 01:06:48 +02:00
LambdAurora
272ef6b1dd Fix bad mixin. 2020-07-01 00:59:26 +02:00
LambdAurora
155278130f Add analog movements and WIP action ring. 2020-06-30 19:19:29 +02:00
LambdAurora
3f221feb26 Add mouse4 as back in GUIs. 2020-06-30 00:28:27 +02:00
LambdAurora
7cfd8c9b77 Update mappings. 2020-06-29 17:03:04 +02:00
LambdAurora
2c53575d17 🔖 LambdaControls v1.3.2: Fix things, add vertical reacharound and internal API. 2020-06-29 16:06:15 +02:00
LambdAurora
24f7054eff 🔖 LambdaControls v1.3.1: Fix broken inventory interactions and virtual mouse bug. 2020-06-28 22:22:19 +02:00
LambdAurora
4669e446dc 🔖 LambdaControls v1.3.0: Update to Minecraft 1.16.1. 2020-06-27 19:05:20 +02:00
LambdAurora
ff672f05a1 🔖 LambdaControls v1.2.0: Fast block placement, virtual mouse, etc. 2020-06-27 18:40:35 +02:00
LambdAurora
756e7d102d Improve rotation algorithm. 2020-06-27 18:21:46 +02:00
LambdAurora
e676a37c7f Update to 1.16. 2020-06-27 16:08:10 +02:00
LambdAurora
45dd94fd34 🐛 Fix some little bugs. 2020-02-18 19:20:32 +01:00
LambdAurora
f8cc1fcc20 🚧 More WIP on REI as hooking into it is quite difficult. 2020-02-18 18:09:50 +01:00
LambdAurora
d56126c680 🚧 WIP on REI compatiblity. 2020-02-18 10:43:27 +01:00
LambdAurora
5a2fad4445 🚧 WIP on REI compatiblity. 2020-02-18 10:01:39 +01:00
LambdAurora
0eadc054b6 Add proper fast block placing. 2020-02-17 22:52:59 +01:00
LambdAurora
07a296603a 🚧 WIP on fast block placing. 2020-02-17 12:05:06 +01:00
LambdAurora
8e082404f9 Add always more features. 2020-02-16 23:20:21 +01:00
LambdAurora
8efddb24b0 🎨 White outline for front block placing. 2020-02-16 10:55:21 +01:00
LambdAurora
581757aaee Add new recipe book controls and front block outline. 2020-02-16 00:46:03 +01:00
LambdAurora
40dc8d424c 🎨 Update README. 2020-02-15 00:20:18 +01:00
LambdAurora
230a9f6424 🎨 Update README. 2020-02-15 00:19:11 +01:00
LambdAurora
ba01df036b 🎨 Update README. 2020-02-15 00:02:02 +01:00
LambdAurora
ff90d55351 Improve HUD, add autojump option. 2020-02-14 19:30:54 +01:00
LambdAurora
efc2d6284d 🎨 Quick code cleaning. 2020-02-13 11:54:17 +01:00
LambdAurora
9e2c4720e9 🚧 WIP on better button tip rendering. 2020-02-13 11:51:29 +01:00
LambdAurora
8063116820 Some refactor and add missing behaviors in some GUIs. 2020-02-13 00:28:58 +01:00
LambdAurora
0050b0216c 🔖 LambdaControls v1.1.1: Add more precision in the fly drifting tooltips. 2020-02-05 15:39:19 +01:00
LambdAurora
e13569b71d Add precision to some tooltips and prepare for the fast/accurate block placement feature. 2020-02-05 13:28:23 +01:00
LambdAurora
47b0cfdad4 🔖 LambdaControls v1.1.0: Chording update. 2020-02-01 00:44:13 +01:00
LambdAurora
f56eb9e0ad 🔧 Fix build task dependency. 2020-01-16 11:58:38 +01:00
LambdAurora
bc712f138b Add new unfocused input option, improve front block placing. 2020-01-15 23:57:02 +01:00
LambdAurora
66721b00b9 💬 Increase max speed ranges. 2020-01-15 18:20:10 +01:00
LambdAurora
e0762b16ee ⬆️ Update SpruceUI to fix a crash with Not Enough Crashes. 2020-01-15 17:28:05 +01:00
LambdAurora
7bfff00841 🔧 Fix some dependency management. 2020-01-14 11:33:19 +01:00
LambdAurora
a8c969b176 🔧 Fix SpruceUI dependency for quick builds. 2020-01-13 00:52:47 +01:00
LambdAurora
f1607fe1bf 🔧 Fix ivy pattern layouts for SpruceUI. 2020-01-13 00:32:48 +01:00
LambdAurora
d52a630cb4 🔧 Fix ivy pattern layouts for SpruceUI and OkZoomer. 2020-01-13 00:28:05 +01:00
LambdAurora
502e9ff15e 🎨 Improve fly drifting injection. 2020-01-12 21:45:12 +01:00
LambdAurora
32e4ad1b7c Add fly drifting option. 2020-01-12 21:33:28 +01:00
LambdAurora
4ae6404ad2 🔧 Fix gradle project and update README. 2020-01-12 10:44:18 +01:00
LambdAurora
185d5e4303 🌐 Update localizations. 2020-01-11 21:04:16 +01:00
LambdAurora
0d02e56134 Add front block placing and clean up the code. 2020-01-11 20:02:24 +01:00
LambdAurora
738d094fb1 💥 Lot of breaking changes. 2020-01-04 15:19:02 +01:00
LambdAurora
7c06afb4a4 🐛 Fix config fixer. 2020-01-02 00:43:10 +01:00
LambdAurora
647e03134f Add chording and new button assignment system. 2020-01-02 00:31:00 +01:00
LambdAurora
1b5a0e470e Add working in-game chording, WIP controls screen. 2020-01-01 14:54:51 +01:00
LambdAurora
e6da7adf89 🔥 Remove now unused mixin. 2020-01-01 02:02:48 +01:00
LambdAurora
46d43c6f46 🚧 WIP of chording and next release. 2020-01-01 01:54:14 +01:00
LambdAurora
9ae1bc2a3b 🐛 Fix sneak toggle, arrow keys in chat not working, etc... 2019-12-23 00:01:57 +01:00
167 changed files with 12408 additions and 4670 deletions

23
.github/workflows/gradlebuild.yml vendored Executable file
View File

@@ -0,0 +1,23 @@
name: Gradle Build
on: [ push, pull_request ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up JDK 16
uses: actions/setup-java@v1
with:
java-version: 16
server-id: github # Value of the distributionManagement/repository/id field of the pom.xml
settings-path: ${{ github.workspace }} # location for the settings.xml file
- name: Build with Gradle
run: ./gradlew build
- uses: actions/upload-artifact@v2
with:
name: Artifacts
path: ./build/libs/

40
.github/workflows/gradlepublish.yml vendored Executable file
View File

@@ -0,0 +1,40 @@
name: Gradle Package
on:
push:
tags:
- '*'
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up JDK 16
uses: actions/setup-java@v1
with:
java-version: 16
server-id: github # Value of the distributionManagement/repository/id field of the pom.xml
settings-path: ${{ github.workspace }} # location for the settings.xml file
- name: Build with Gradle
run: ./gradlew build
- uses: actions/upload-artifact@v2
with:
name: Artifacts
path: ./build/libs/
# The USERNAME and PASSWORD need to correspond to the credentials environment variables used in
# the publishing section of your build.gradle
- name: Publish to GitHub Packages and other Mavens
run: ./gradlew publish
env:
BRANCH_NAME: ${{ github.ref }}
RUN_COUNT: ${{ github.run_number }}
REPO_NAME: ${{ github.repository }}
USERNAME: ${{ github.actor }}
TOKEN: ${{ secrets.GITHUB_TOKEN }}
midnightcontrols_MAVEN: ${{ secrets.MAVEN_URL }}
MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }}
MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }}

23
.github/workflows/modrinth_update.yml vendored Executable file
View File

@@ -0,0 +1,23 @@
name: Gradle Build
on:
release:
types:
- published
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up JDK 16
uses: actions/setup-java@v1
with:
java-version: 16
server-id: github # Value of the distributionManagement/repository/id field of the pom.xml
settings-path: ${{ github.workspace }} # location for the settings.xml file
- name: Build with Gradle
env:
MODRINTH_TOKEN: ${{ secrets.MODRINTH_TOKEN }}
run: ./gradlew publishModrinth

9
.gitignore vendored
View File

@@ -1,11 +1,13 @@
#
# LambdAurora's ignore file
#
# v0.12
# v0.15
# JetBrains
.idea/
*.iml
*.ipr
*.iws
## Intellij IDEA
out/
## CLion
@@ -51,6 +53,8 @@ __pycache__/
# OS
## Windows
desktop.ini
# MacOS
.DS_Store
# File types
*.dll
@@ -59,10 +63,13 @@ desktop.ini
*.lib
lib*.a
*.png~
*.tar.?z
# Common
bin/
build/
dist/
lib/
obj/
run/
target/

151
CHANGELOG.md Normal file
View File

@@ -0,0 +1,151 @@
# Changelog
# MidnightControls
## 0.1.0
First beta!
Changes from LambdaControls:
- Support for Steam Deck and Dualsense
- Support for L4, L5, R4, R5 buttons
- Updated Libraries
- New Logo and Name
- Lots of Bugfixes
- MidnightConfig backend
# LambdaControls
## 1.0.0
:tada: First release! :tada:
- Added controller support.
- Added new controls settings GUI.
- Added experimental touchscreen support.
- Added controller controls GUI.
- Added a lot of options.
- Added key bindings for look around.
- And more!
### 1.0.1
- Fixed tutorial toast to look around not affected by camera movement done with a controller. ([#2](https://github.com/LambdAurora/LambdaControls/issues/2))
### 1.0.2 (Unofficial)
This update was never pushed but was aiming to fix [#4](https://github.com/LambdAurora/LambdaControls/issues/4).
- Fixed the toggle sneak button binding.
- Fixed broken chat arrow keys.
- Optimized a little bit the button indicator. (need more work)
## 1.1.0 - Chording update
This update also has a backport 1.14.4 version ([#9](https://github.com/LambdAurora/LambdaControls/issues/9)).
- Rewrote everything (almost).
- Added [networking](https://github.com/LambdAurora/LambdaControls/wiki/LambdaControls-Networking) for some features.
- Added second controller support (Joycons supported now hopefully).
- Added chording.
- Added better developer API
- Added hover messages ([#5](https://github.com/LambdAurora/LambdaControls/issues/5)).
- Added hotbar button bindings ([#7](https://github.com/LambdAurora/LambdaControls/issues/7)).
- Added front block placing feature ([#8](https://github.com/LambdAurora/LambdaControls/issues/8)).
- Added no creative fly drifting ([#8](https://github.com/LambdAurora/LambdaControls/issues/8)).
- Added option to enable controller focus.
- Added [OkZoomer](https://github.com/joaoh1/OkZoomer) compatibility.
- Added D-pad movements in inventories.
- Increased max speed ranges.
- Added [SpruceUI](https://github.com/LambdAurora/SpruceUI) for cleaner custom UI widgets.
- Added reset settings button.
- HUD side affects button indicators now.
- Added support for Advancements tabs.
### 1.1.1
## 1.2.0-1.3.0
- Improved rotation algorithm ([#11](https://github.com/LambdAurora/LambdaControls/issues/11)).
- Added virtual mouse.
- Added outline on front block placing.
- Added fast block placement ([#8](https://github.com/LambdAurora/LambdaControls/issues/8)).
- Added [REI](https://www.curseforge.com/minecraft/mc-mods/roughly-enough-items) compatibility.
- Improved HUD.
- Added recipe book control.
- And more!
- v1.3.0 specific: Updated to Minecraft 1.16.1
### 1.3.1
- Fixed broken inventory interactions ([#13](https://github.com/LambdAurora/LambdaControls/issues/13))
- Fixed virtual mouse preventing continuous attack (thus making breaking blocks impossible).
- Added support for [ModUpdater](https://gitea.thebrokenrail.com/TheBrokenRail/ModUpdater) hopefully.
- Updated [SpruceUI](https://github.com/LambdAurora/SpruceUI) to 1.5.2.
### 1.3.2
- Added vertical reacharound.
- Added more API for compatibility handlers.
- Improved reacharound API.
- Improved [REI](https://www.curseforge.com/minecraft/mc-mods/roughly-enough-items) compatibility.
## 1.4.0
- Added analog movements ([#10](https://github.com/LambdAurora/LambdaControls/issues/10)).
- Improved Ok Zoomer compability.
- Updated [SpruceUI](https://github.com/LambdAurora/SpruceUI) to 1.5.8 to ensure 1.16.2 compability.
- Internal changes:
- Added analog input value to button bindings.
- Replace lot of strings with Texts.
- Improved block outline rendering injection.
- Shadow library jars instead of Jar-in-Jar.
- Fixed crash in inventory ([#16](https://github.com/LambdAurora/LambdaControls/issues/16))
- WIP:
- Started to work on action ring.
- Will allow for better compability with other mods.
- Might be interesting for keyboard users too.
### 1.4.1
- Fixed crash with [REI](https://www.curseforge.com/minecraft/mc-mods/roughly-enough-items).
## 1.5.0
- Added mappings string editor screen.
- Added Simplified Chinese translations ([#18](https://github.com/LambdAurora/LambdaControls/pull/18)).
- Added Mexican Spanish translations ([#22](https://github.com/LambdAurora/LambdaControls/pull/22)).
- Added Xbox 360 button skin and overhauled Xbox button skin.
- Added debug option.
- Respect toggle setting in Accessibility screen.
- Tweaked rotation speeds.
- Updated to Minecraft 1.16.2.
- Updated [SpruceUI] to 1.6.4.
- Overhauled REI compatibility.
- Improved horizontal reach-around.
- Fixed crashes with Ok Zoomer.
- Fixed crashes with key unbinding.
- More WIP on keybind ring.
## 1.6.0
- Reworked entirely the settings screen.
- Added independent stick dead zones. ([#32](https://github.com/LambdAurora/LambdaControls/issues/32))
- Added max values range. ([#41](https://github.com/LambdAurora/LambdaControls/issues/41))
- Updated [SpruceUI] and fix related crashes due to incompatible versions ([#40](https://github.com/LambdAurora/LambdaControls/issues/40), [#48](https://github.com/LambdAurora/LambdaControls/issues/48)).
- Fix boat control issues ([#37](https://github.com/LambdAurora/LambdaControls/issues/37)).
- Fix incompatibilities with mods using night-config. Now shadowing properly night-config. ([#33](https://github.com/LambdAurora/LambdaControls/issues/33), [#39](https://github.com/LambdAurora/LambdaControls/issues/39))
## 1.7.0
- Updated to 1.17.
- Small improvements to the codebase thanks to Java 16.
- Fix controller bindings not being saved ([#31](https://github.com/LambdAurora/LambdaControls/issues/31), [#55](https://github.com/LambdAurora/LambdaControls/issues/55)).
- Dropped entirely Touchscreen Input Mode.
- Dropped Roughly Enough Items compatibility.
### 1.7.1
- Fix crash at startup.
[SpruceUI]: https://github.com/LambdAurora/SpruceUI

View File

@@ -1,8 +1,8 @@
# Contributing to LambdaControls
# Contributing to midnightcontrols
:tada: First of all, thanks for taking time to contribute! :tada:
The following is a set of guidelines for contributing to LambdaControls.
The following is a set of guidelines for contributing to MidnightControls.
Feel free to propose changes to this document in a pull request.
**Table of Contents**
@@ -17,8 +17,8 @@ Feel free to propose changes to this document in a pull request.
## Code of Conduct
This project and everyone participating in it is governed by the [Code of Conduct](https://github.com/LambdAurora/LambdaControls/blob/master/CODE_OF_CONDUCT.md).
By participating, you are expected to uphold this code. Please report unacceptable behavior at [aurora42lambda@gmail.com](mailto:aurora42lambda@gmail.com).
This project and everyone participating in it is governed by the [Code of Conduct](https://github.com/LambdAurora/midnightcontrols/blob/master/CODE_OF_CONDUCT.md).
By participating, you are expected to uphold this code. Please report unacceptable behavior at [motschen@midnightdust.eu](mailto:motschen@midnightdust.eu).
## What should I know before I get started?
@@ -26,9 +26,9 @@ By participating, you are expected to uphold this code. Please report unacceptab
[Fabric](https://fabricmc.net/) is the mod loader and the software which allows Gradle to setup the workspace.
### Java 8
### Java 17
Java is the main language used to make LambdaControls alive.
Java is the main language used to make MidnightControls alive.
Knowing how to code in Java is necessary if you contribute to the code.
### Minecraft
@@ -45,9 +45,9 @@ As it is a Minecraft mod you should know a bit how Minecraft works and how moddi
### Git
Git is the control version software we use for LambdaControls, please know how to use it if you consider contributing to the code.
Git is the control version software we use for midnightcontrols, please know how to use it if you consider contributing to the code.
Git commits should be and must be signed.
Git commits should be signed.
## How can I contribute?
@@ -55,21 +55,21 @@ Git commits should be and must be signed.
#### Before submitting a bug report
- Check if you can reproduce it on other platforms, on multiple web browsers.
- Check if you can reproduce it on other platforms.
- Perform a search to see if the problem has already been reported. If it has **and the issue is still open**, add a comment to the existing issue instead of opening a new one.
#### How do I submit a bug report?
Go in the issues tab in GitHub and read the [bug report guide](https://github.com/LambdAurora/LambdaControls/blob/master/.github/ISSUE_TEMPLATE/bug_report.md)
Go in the issues tab in GitHub and read the [bug report guide](https://github.com/TeamMidnightDust/MidnightControls/blob/1.18/.github/ISSUE_TEMPLATE/bug_report.md)
### Suggesting enhancements
Enhancement suggestions are tracked as [GitHub issues](https://github.com/LambdAurora/LambdaControls/issues).
Check out the [feature request](https://github.com/LambdAurora/LambdaControls/blob/master/.github/ISSUE_TEMPLATE/feature_request.md) guide.
Enhancement suggestions are tracked as [GitHub issues](https://github.com/TeamMidnightDust/MidnightControls/issues).
Check out the [feature request](https://github.com/TeamMidnightDust/MidnightControls/blob/1.17/.github/ISSUE_TEMPLATE/feature_request.md) guide.
### Do pull requests
You can help LambdaControls by writing code and submit it with pull requests.
You can help midnightcontrols by writing code and submit it with pull requests.
Pull requests will be accepted if they follow the [styleguide](#styleguides), if they are useful, etc...
We can refuse a pull request if the commits are not signed, so don't forget to [sign them](https://help.github.com/en/articles/signing-commits)!
@@ -81,21 +81,16 @@ Feel free to pull request!
### Git commit messages
* Use the imperative mood ("Move cursor to..." not "Moves cursor to...")
* Consider starting the commit message with an emote, emotes for your commit can be found at the [gitmoji guide](https://gitmoji.carloscuesta.me/).
* (Not for the message) Don't forget to sign the commit.
### Naming convention
Names in the code should be explicit and always in `snake_case`, `camelCase` will not be allowed.
Names in the code should be explicit and always in `camelCase`, `snake_case` will not be allowed.
`PascalCase` can be used for class name.
We chose `snake_case` because it is more accessible for everyone: for people who don't speak English as their native language it is more easy to see the words when they are separated,
it also allows the correct use of screen reader on the code with `snake_case` due to the absence of upper case characters.
### Brace placement
Every braces should be at the end of the line of function declaration, etc...
The only exception is class declarations: braces must be on the next line.
Every braces should be at the end of the line of function declaration, etc.
### Quick note for users of the Intellij IDEA IDE

7
HEADER Normal file
View File

@@ -0,0 +1,7 @@
Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
Copyright © 2022 Motschen <motschen@midnightdust.eu>
This file is part of MidnightControls.
Licensed under the MIT license. For more information,
see the LICENSE file.

View File

@@ -1,6 +1,7 @@
MIT License
Copyright © 2019 LambdAurora <aurora42lambda@gmail.com>
Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
Copyright © 2022 Motschen <motschen@midnightdust.eu>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -18,4 +19,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
SOFTWARE.

View File

@@ -1,8 +1,69 @@
# LambdaControls
<div align="center">
<img src=https://github.com/TeamMidnightDust/MidnightControls/blob/1.19/src/main/resources/assets/midnightcontrols/icon.png>
![Java 8](https://img.shields.io/badge/language-Java%208-9B599A.svg?style=flat-square)
[![GitHub license](https://img.shields.io/github/license/LambdAurora/LambdaControls?style=flat-square)](https://raw.githubusercontent.com/LambdAurora/LambdaControls/master/LICENSE)
![Environment: Client](https://img.shields.io/badge/environment-client-1976d2?style=flat-square)
# MidnightControls
A Fabric Minecraft mod which add better controls.
It allows the use of a controller or the touchscreen.
[![CurseForge](https://cf.way2muchnoise.eu/full_midnightcontrols_downloads%20on%20CurseForge.svg?badge_style=round)](https://www.curseforge.com/minecraft/mc-mods/midnightcontrols)
[![Modrinth]](https://modrinth.com/mod/midnightcontrols)
[![Version](https://img.shields.io/github/v/tag/TeamMidnightDust/MidnightControls?label=version&style=round)](https://github.com/TeamMidnightDust/MidnightControls/releases)
![Environment: Client](https://img.shields.io/badge/environment-client-1976d2?style=round)
[![Mod loader: Quilt/Fabric]][Quilt]
[![Java 17](https://img.shields.io/badge/language-Java%2017-9B599A.svg?style=round)](https://www.oracle.com/java/technologies/downloads/#java17)
[![GitHub license](https://img.shields.io/github/license/TeamMidnightDust/MidnightControls?style=round)](LICENSE)
A Fabric Minecraft mod which adds better controls, reach-around and controller support.
Forked from [LambdaControls](https://github.com/LambdAurora/LambdaControls) by the amazing [LambdAurora](https://github.com/LambdAurora), which was sadly discontinued.
</div>
## What's this mod?
MidnightControls is a mod which provides controller and touchscreen support in Minecraft: Java Edition.<br />It also includes some Bedrock Edition parity and reach-around features!
## Features:
- Controller support
- Touchscreen support
- Keyboard controls to look around.
- Toggleable on screen button indicator (like in Bedrock Edition).
- Vertical reach-around.
- Many Bedrock Edition features:
- Toggleable fly drifting
- Front block placing (be careful with this one)
- New controls settings!
- Many options in config to change to your liking.
- Many controllers supported and in a simple way your own controller mappings.
- An easy API for developers to add their own button bindings.
## 🎮 Supported Controllers:
- DualShock controllers
- DualSense controllers
- Xbox controllers
- Switch Pro controllers
- Joy-Cons
- Steam controller and Steam Deck (WIP)
- And many more!
## Screenshots
![Controller_Controls](images/controller_controls.png)
![Controller_Options](images/controller_options.png)
## Build
Clone the repo with:
```
git clone https://github.com/TeamMidnightDust/MidnightControls
```
Enter the folder created and run:
```
./gradlew build
```
After this is done, everything should be built just fine!
[Quilt]: https://quiltmc.org
[Mod loader: Quilt/Fabric]: https://img.shields.io/badge/modloader-Quilt%2FFabric-blueviolet?logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAACXBIWXMAAADGAAAAxgGwdJvFAAAFU0lEQVR4nO1bwW7bOBCdCHtP+wUNxIvhS7yA7kkPOjf9AKPam4/OF2zzB96bblXhH0jPPkS5G1jnIvgib/IFtX/ALoYZemmKtEWRRe3aDzAsURbJGQ6HM4/02Wq1gmNGcNTSnxQA8IdakDK4AOCfJpj3SpiI98r2rAMAbxrWNWFFOMeL1nCJdXQa1gPTbpCbnm34ABIeBThv2hgAfOyVcF+2ZwkAfHGo54kVIRe6NVxiny4d6vpn2g36ugfqFLhwFB6kkUoc67k0XLv0qYKj9wEnBXiqZwEAf/ZKOOuV8BkLWBFesyI8A4C/LOt6AoC39C7HtBvg9Vt65hUnC/BUDzrOf1MGq5S9WkDZnuVle7ZqsBKgw/tO73K0hku8/u64EmhxsoA96MMvxdEroBIKO+CWoshnqqJPYTAGRJ8sql3QO3Op7D19D3z7AV8KeAGArFf+32lWhDwnKNuzuaUCclaE93KBiOU9hMQV+FLAOxz9lPHRR0VkZXs2oBDUNon5ULZnKOgcYwl4FTwna/K+CtRRwIK+dTnCCwkP9I0fkXmh4FcN+6UKqqtHblvtb+18po4TvKaPilsS8qVuYx5havsbJXS1I0ZVAXP1B5jfyzm+hAnN+We1XPluioX03kJtY9oNtG1T+b1aburDxhRAQVMGHw3z9k65F41nktmjUnjjrAj7ZXv27ECIyEKgBd7saBuka7lsPu0GA1MjR0+KVpwgMTlrMkN4YoztlZ/2camTvD0QjcWZF6KxMgcLyKbdAN+HKOb1y6PYH49gEsUwUKw1G48gi2KQZcApkYxH1eldUQBxeKbkRfXEQjDZ21+h2bMiHJDwH2wk1rSX0XWueHZd2yCZ/oVSnilTaA3VAiqjRRahQ0JBjuov3ijfPqAua0kUg67tmyjWCmvsS504wGQRnywjPJ8wtY3xw3827fjMBXziUalL3DcNrIzYRwXcTbvBZ7lgPHoNxKKYky1/+2xsHxXQbw2X/EIoggQHyjC9Yh8VcC6NshDc66jLOHpC5KSAPejDL8U+KuArboTQZgjHeARn+MFnvhvbRyfYaQ2XfNkTVFgUr/mIxlvkJuyjAjCae6BrYQUPW37vhDpT4MnAsCyIgbHBnYbcADJt7/t+deBCiSWsCG8sOv5IgU2FnJh2g+RnBDl1oCpgogqEx1TEURUFokx+tpCYnEzzjg1kh+fq/Ix9OXpG6OjjgI1VIGWcOMhlXh4PPdAz1VTe90rIU8Z/L9JUnALXSK4SjSUzOY+Y1ekyOlzjaamTvf3X8cjunBH1v6OeVtsG1QI66qYEVkoVmyA/O5fYmIHjgSsrskUavAc6q1BLeXXiANMZu0HKwGav7opGv+LticZqHOToLBeZrJRxC97qjOsowCTgZYO9OlNa25haMwgvsFMJPp3gLW1ji8b6dO89fhfYIbzAl23T4adtjyNvD6/mbbs9Xgs1hRcwWoIvCxDb47nQNm5aRDHvoPGcblNYCi+gtQSfU+AdLYfioLXYtHA9eruBhsILVJRwUIGQo/ACG0o4GAV4El5grQSffMA3SqbEnBdb1zeunfYsvAB3jL4UwE92KauA2NnNbLerNKhEqJ6Q+FIAOjpcATAVzjFHoC3qC9OurCW0W9s+6vXpAy4p0hPkSUL3ziNHiY3tqfNdQN4jORgnSEGMLyU8Udbq1QIEdEyRF3hSwlp4vKkwQimrHDuxAR6S4tleFIPzERnhSFXQEtbkD1kbwiMOlhJroISK8IiDpcQsp4NWeMRBc4I1lWAUHnHwpOgOJWwVHvFbsMIGJewUHvFb7QuQYxR/ttgIzbUAgB9KOM3uc+PN7QAAAABJRU5ErkJggg==
[Modrinth]: https://img.shields.io/modrinth/dt/bXX9h73M?logoColor=white&logo=data:image/svg%2bxml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgNTEyIDUxNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KICA8cGF0aCBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGNsaXAtcnVsZT0iZXZlbm9kZCIgZD0iTTUwMy4xNiAzMjMuNTZDNTE0LjU1IDI4MS40NyA1MTUuMzIgMjM1LjkxIDUwMy4yIDE5MC43NkM0NjYuNTcgNTQuMjI5OSAzMjYuMDQgLTI2LjgwMDEgMTg5LjMzIDkuNzc5OTFDODMuODEwMSAzOC4wMTk5IDExLjM4OTkgMTI4LjA3IDAuNjg5OTQxIDIzMC40N0g0My45OUM1NC4yOSAxNDcuMzMgMTEzLjc0IDc0LjcyOTggMTk5Ljc1IDUxLjcwOThDMzA2LjA1IDIzLjI1OTggNDE1LjEzIDgwLjY2OTkgNDUzLjE3IDE4MS4zOEw0MTEuMDMgMTkyLjY1QzM5MS42NCAxNDUuOCAzNTIuNTcgMTExLjQ1IDMwNi4zIDk2LjgxOThMMjk4LjU2IDE0MC42NkMzMzUuMDkgMTU0LjEzIDM2NC43MiAxODQuNSAzNzUuNTYgMjI0LjkxQzM5MS4zNiAyODMuOCAzNjEuOTQgMzQ0LjE0IDMwOC41NiAzNjkuMTdMMzIwLjA5IDQxMi4xNkMzOTAuMjUgMzgzLjIxIDQzMi40IDMxMC4zIDQyMi40MyAyMzUuMTRMNDY0LjQxIDIyMy45MUM0NjguOTEgMjUyLjYyIDQ2Ny4zNSAyODEuMTYgNDYwLjU1IDMwOC4wN0w1MDMuMTYgMzIzLjU2WiIgZmlsbD0iIzMwYjI3YiIvPgogIDxwYXRoIGQ9Ik0zMjEuOTkgNTA0LjIyQzE4NS4yNyA1NDAuOCA0NC43NTAxIDQ1OS43NyA4LjExMDExIDMyMy4yNEMzLjg0MDExIDMwNy4zMSAxLjE3IDI5MS4zMyAwIDI3NS40Nkg0My4yN0M0NC4zNiAyODcuMzcgNDYuNDY5OSAyOTkuMzUgNDkuNjc5OSAzMTEuMjlDNTMuMDM5OSAzMjMuOCA1Ny40NSAzMzUuNzUgNjIuNzkgMzQ3LjA3TDEwMS4zOCAzMjMuOTJDOTguMTI5OSAzMTYuNDIgOTUuMzkgMzA4LjYgOTMuMjEgMzAwLjQ3QzY5LjE3IDIxMC44NyAxMjIuNDEgMTE4Ljc3IDIxMi4xMyA5NC43NjAxQzIyOS4xMyA5MC4yMTAxIDI0Ni4yMyA4OC40NDAxIDI2Mi45MyA4OS4xNTAxTDI1NS4xOSAxMzNDMjQ0LjczIDEzMy4wNSAyMzQuMTEgMTM0LjQyIDIyMy41MyAxMzcuMjVDMTU3LjMxIDE1NC45OCAxMTguMDEgMjIyLjk1IDEzNS43NSAyODkuMDlDMTM2Ljg1IDI5My4xNiAxMzguMTMgMjk3LjEzIDEzOS41OSAzMDAuOTlMMTg4Ljk0IDI3MS4zOEwxNzQuMDcgMjMxLjk1TDIyMC42NyAxODQuMDhMMjc5LjU3IDE3MS4zOUwyOTYuNjIgMTkyLjM4TDI2OS40NyAyMTkuODhMMjQ1Ljc5IDIyNy4zM0wyMjguODcgMjQ0LjcyTDIzNy4xNiAyNjcuNzlDMjM3LjE2IDI2Ny43OSAyNTMuOTUgMjg1LjYzIDI1My45OCAyODUuNjRMMjc3LjcgMjc5LjMzTDI5NC41OCAyNjAuNzlMMzMxLjQ0IDI0OS4xMkwzNDIuNDIgMjczLjgyTDMwNC4zOSAzMjAuNDVMMjQwLjY2IDM0MC42M0wyMTIuMDggMzA4LjgxTDE2Mi4yNiAzMzguN0MxODcuOCAzNjcuNzggMjI2LjIgMzgzLjkzIDI2Ni4wMSAzODAuNTZMMjc3LjU0IDQyMy41NUMyMTguMTMgNDMxLjQxIDE2MC4xIDQwNi44MiAxMjQuMDUgMzYxLjY0TDg1LjYzOTkgMzg0LjY4QzEzNi4yNSA0NTEuMTcgMjIzLjg0IDQ4NC4xMSAzMDkuNjEgNDYxLjE2QzM3MS4zNSA0NDQuNjQgNDE5LjQgNDAyLjU2IDQ0NS40MiAzNDkuMzhMNDg4LjA2IDM2NC44OEM0NTcuMTcgNDMxLjE2IDM5OC4yMiA0ODMuODIgMzIxLjk5IDUwNC4yMloiIGZpbGw9IiMzMGIyN2IiLz4KPC9zdmc+

View File

@@ -1,104 +1,269 @@
plugins {
id 'fabric-loom' version '0.2.6-SNAPSHOT'
id 'maven-publish'
id 'fabric-loom' version '1.0-SNAPSHOT'
id 'java-library'
id 'maven-publish'
id 'com.github.johnrengelman.shadow' version '7.0.0'
id 'com.modrinth.minotaur' version '1.2.+'
}
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
import com.google.gson.GsonBuilder
import com.google.gson.JsonObject
import com.modrinth.minotaur.TaskModrinthUpload
import com.modrinth.minotaur.request.VersionType
import com.modrinth.minotaur.responses.ResponseError
import org.apache.http.client.config.CookieSpecs
import org.apache.http.client.config.RequestConfig
import org.apache.http.client.entity.EntityBuilder
import org.apache.http.client.methods.HttpPatch
import org.apache.http.entity.ContentType
import org.apache.http.impl.client.HttpClientBuilder
import org.apache.http.util.EntityUtils
archivesBaseName = project.archives_base_name
version = project.mod_version
group = project.maven_group
version = "${project.mod_version}+${getMCVersionString()}"
archivesBaseName = project.archives_base_name
minecraft {
// This field defines the Java version your mod target.
def targetJavaVersion = 17
boolean isMCVersionNonRelease() {
return project.minecraft_version.matches('^\\d\\dw\\d\\d[a-z]$')
|| project.minecraft_version.matches('\\d+\\.\\d+-(pre|rc)(\\d+)')
}
String getMCVersionString() {
if (isMCVersionNonRelease() || project.minecraft_version == "1.19.3") {
return project.minecraft_version
}
def version = project.minecraft_version.split('\\.')
return version[0] + '.' + version[1]
}
boolean pingUrl(String address) {
try {
def conn = (HttpURLConnection) new URL(address).openConnection()
int responseCode = conn.getResponseCode()
return (200 <= responseCode && responseCode <= 399)
} catch (IOException ignored) {
return false
}
}
String parseReadme() {
def excludeRegex = /(?m)<!-- modrinth_exclude\.start -->(.|\n)*?<!-- modrinth_exclude\.end -->/
def linkRegex = /!\[([A-z_ ]+)]\((images\/[A-z.\/_]+)\)/
def readme = (String) file('README.md').text
readme = readme.replaceAll(excludeRegex, '')
return readme
}
repositories {
mavenLocal()
mavenLocal()
mavenCentral()
maven { url = 'https://aperlambda.github.io/maven' }
maven {
name = 'CottonMC'
url = 'http://server.bbkr.space:8081/artifactory/libs-snapshot'
}
repositories {
maven { url = "https://jitpack.io" }
}
maven { url 'https://aperlambda.github.io/maven' }
maven {
name 'Gegy'
url 'https://maven.gegy.dev'
}
maven {
name = "CottonMC"
url = "https://server.bbkr.space/artifactory/libs-release"
}
maven { url "https://maven.terraformersmc.com/releases/" }
maven { url 'https://maven.kosmx.dev' }
maven { url 'https://maven.isxander.dev/releases' }
maven { url 'https://maven.shedaniel.me/' }
maven { url 'https://jitpack.io' }
maven { url "https://api.modrinth.com/maven" }
maven { url 'https://maven.quiltmc.org/repository/release'}
}
configurations {
shadow
api.extendsFrom shadow
}
dependencies {
//to change the versions see the gradle.properties file
minecraft "com.mojang:minecraft:${project.minecraft_version}"
mappings "net.fabricmc:yarn:${project.yarn_mappings}"
modCompile "net.fabricmc:fabric-loader:${project.loader_version}"
//to change the versions see the gradle.properties file
minecraft "com.mojang:minecraft:${project.minecraft_version}"
mappings "net.fabricmc:yarn:${project.yarn_mappings}:v2"
modImplementation "net.fabricmc:fabric-loader:${project.loader_version}"
// Fabric API. This is technically optional, but you probably want it anyway.
modApi "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}"
// Fabric API. This is technically optional, but you probably want it anyway.
modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}"
modCompile "io.github.prospector:modmenu:1.8.0+build.16"
//modCompile "io.github.cottonmc:cotton-client-commands:0.4.2+1.14.3-SNAPSHOT"
modImplementation "dev.lambdaurora:spruceui:${project.spruceui_version}"
include "dev.lambdaurora:spruceui:${project.spruceui_version}"
api('org.aperlambda:lambdajcommon:1.8.1') {
exclude group: 'com.google.code.gson'
exclude group: 'com.google.guava'
}
include 'org.aperlambda:lambdajcommon:1.8.1'
implementation "org.jetbrains:annotations:17.0.0"
implementation "org.aperlambda:lambdajcommon:1.7.2"
implementation "com.electronwill.night-config:core:3.5.3"
implementation "com.electronwill.night-config:toml:3.5.3"
include "org.jetbrains:annotations:17.0.0"
include "org.aperlambda:lambdajcommon:1.7.2"
include "com.electronwill.night-config:core:3.5.3"
include "com.electronwill.night-config:toml:3.5.3"
modCompileOnlyApi "com.terraformersmc:modmenu:${project.modmenu_version}"
// PSA: Some older mods, compiled on Loom 0.2.1, might have outdated Maven POMs.
// You may need to force-disable transitiveness on them.
// Compatibility mods
// Ok Zoomer and LibZoomer are temporarily disabled for the time being, as we are currently using Reflection at runtime instead in OkZoomerCompat due to there being two major, completely incompatible API versions.
// modImplementation("maven.modrinth:ok-zoomer:${project.ok_zoomer_version}")
// modImplementation "io.github.ennuil:LibZoomer:${project.libzoomer_version}"
modCompileOnlyApi "io.github.cottonmc:LibGui:${project.libgui_version}"
modCompileOnlyApi "org.quiltmc:quilt-json5:1.0.0"
modCompileOnly "maven.modrinth:sodium:${project.sodium_version}"
modImplementation "maven.modrinth:emi:${project.emi_version}"
modCompileOnlyApi "maven.modrinth:emotecraft:${project.emotecraft_version}"
modCompileOnlyApi "io.github.kosmx:bendy-lib:${project.bendylib_version}"
modImplementation "dev.isxander:yet-another-config-lib:${project.yacl_version}"
modCompileOnlyApi "maven.modrinth:inventory-tabs-updated:${project.inventorytabs_version}"
modCompileOnlyApi "maven.modrinth:bedrockify:${project.bedrockify_version}"
// Required for Inventory Tabs
modApi("me.shedaniel.cloth:cloth-config-fabric:${project.clothconfig_version}") {
exclude(group: "net.fabricmc.fabric-api")
}
modImplementation "maven.modrinth:midnightlib:${project.midnightlib_version}"
include "maven.modrinth:midnightlib:${project.midnightlib_version}"
shadow 'com.electronwill.night-config:core:3.6.3'
shadow 'com.electronwill.night-config:toml:3.6.3'
apply plugin: 'java-library'
}
loom {
accessWidenerPath = file("src/main/resources/midnightcontrols.accesswidener")
}
java {
sourceCompatibility = JavaVersion.toVersion(targetJavaVersion)
targetCompatibility = JavaVersion.toVersion(targetJavaVersion)
withSourcesJar()
}
tasks.withType(JavaCompile).configureEach {
it.options.encoding = 'UTF-8'
it.options.release.set(targetJavaVersion)
}
processResources {
inputs.property "version", project.version
inputs.property 'version', project.version
from(sourceSets.main.resources.srcDirs) {
include "fabric.mod.json"
expand "version": project.version
}
from(sourceSets.main.resources.srcDirs) {
exclude "fabric.mod.json"
}
filesMatching('fabric.mod.json') {
expand 'version': project.version
}
}
// ensure that the encoding is set to UTF-8, no matter what the system default is
// this fixes some edge cases with special characters not displaying correctly
// see http://yodaconditions.net/blog/fix-for-java-file-encoding-problems-with-gradle.html
tasks.withType(JavaCompile) {
options.encoding = "UTF-8"
}
task publishModrinth(type: TaskModrinthUpload) {
dependsOn(build)
onlyIf {
System.getenv('MODRINTH_TOKEN')
}
// Loom will automatically attach sourcesJar to a RemapSourcesJar task and to the "build" task
// if it is present.
// If you remove this task, sources will not be generated.
task sourcesJar(type: Jar, dependsOn: classes) {
classifier = "sources"
from sourceSets.main.allSource
}
token = System.getenv('MODRINTH_TOKEN')
projectId = project.modrinth_id
versionNumber = version
versionName = "midnightcontrols ${project.mod_version} (${getMCVersionString()})"
addGameVersion((String) project.minecraft_version)
addLoader('fabric')
versionType = isMCVersionNonRelease() ? VersionType.BETA : VersionType.RELEASE
jar {
from "LICENSE"
// Changelog fetching
def changelogText = file('CHANGELOG.md').text
def regexVersion = ((String) project.mod_version).replaceAll('\\.', /\\./).replaceAll('\\+', '\\+')
def changelogRegex = ~"###? ${regexVersion}\\n\\n(( *- .+\\n)+)"
def matcher = changelogText =~ changelogRegex
if (matcher.find()) {
changelog = matcher.group(1)
def changelogLines = changelogText.split('\n')
def linkRefRegex = ~'^\\[([A-z0-9 _\\-/+.]+)]: '
for (int i = changelogLines.length - 1; i > 0; i--) {
def line = changelogLines[i]
if ((line =~ linkRefRegex).find())
changelog += '\n' + line
else break
}
}
// Readme
doFirst {
final def client = HttpClientBuilder.create().setDefaultRequestConfig(RequestConfig.custom().setCookieSpec(CookieSpecs.IGNORE_COOKIES).build()).build()
final def patch = new HttpPatch((String) (apiURL + '/v1/mod/' + projectId))
patch.addHeader("Authorization", token)
var json = new JsonObject()
json.addProperty("body", parseReadme())
patch.setEntity(EntityBuilder.create()
.setText(json.toString())
.setContentType(ContentType.APPLICATION_JSON)
.build())
final def response = client.execute(patch)
final int status = response.getStatusLine().getStatusCode()
final def gson = new GsonBuilder().create()
if (status == 200) {
project.getLogger().lifecycle("Successfully updated readme to ${projectId}.")
} else {
errorInfo = gson.fromJson(EntityUtils.toString(response.getEntity()), ResponseError.class)
project.getLogger().error("Upload failed! Status: ${status} Error: ${errorInfo.getError()} Reason: ${errorInfo.getDescription()}")
throw new GradleException("Upload failed! Status: ${status} Reason: ${errorInfo.getDescription()}")
}
}
}
// configure the maven publication
publishing {
publications {
mavenJava(MavenPublication) {
// add all the jars that should be included when publishing to maven
artifact(remapJar) {
builtBy remapJar
}
artifact(sourcesJar) {
builtBy remapSourcesJar
}
}
}
publications {
mavenJava(MavenPublication) {
artifact(sourcesJar) {
builtBy remapSourcesJar
}
// select the repositories you want to publish to
repositories {
mavenLocal()
}
pom {
name = 'midnightcontrols'
description = 'Adds better controls, and controller support.'
}
pom.withXml {
def dependenciesNode = asNode().appendNode('dependencies')
configurations.shadow.allDependencies.each {
def dependencyNode = dependenciesNode.appendNode('dependency')
dependencyNode.appendNode('groupId', it.group)
dependencyNode.appendNode('artifactId', it.name)
dependencyNode.appendNode('version', it.version)
dependencyNode.appendNode('scope', 'compile')
}
}
}
}
repositories {
mavenLocal()
maven {
name 'GithubPackages'
url uri('https://maven.pkg.github.com/LambdAurora/midnightcontrols')
credentials {
username = project.findProperty("gpr.user") ?: System.getenv("USERNAME")
password = project.findProperty("gpr.key") ?: System.getenv("TOKEN")
}
}
def midnightcontrolsMaven = System.getenv('midnightcontrols_MAVEN')
if (midnightcontrolsMaven) {
maven {
name 'midnightcontrolsMaven'
url uri(midnightcontrolsMaven)
credentials {
username = project.findProperty('gpr.user') ?: System.getenv('MAVEN_USERNAME')
password = project.findProperty('gpr.key') ?: System.getenv('MAVEN_PASSWORD')
}
}
}
}
}

303
controller.svg Normal file
View File

@@ -0,0 +1,303 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="1024.0px"
height="1024.0px"
viewBox="0 0 1024.0 1024.0"
version="1.1"
id="SVGRoot"
sodipodi:docname="controller.svg"
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04, custom)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview893"
pagecolor="#505050"
bordercolor="#eeeeee"
borderopacity="1"
inkscape:pageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:document-units="px"
showgrid="true"
inkscape:zoom="0.42961102"
inkscape:cx="455.06282"
inkscape:cy="790.24975"
inkscape:window-width="1920"
inkscape:window-height="1050"
inkscape:window-x="1920"
inkscape:window-y="30"
inkscape:window-maximized="1"
inkscape:current-layer="layer1">
<inkscape:grid
type="xygrid"
id="grid956" />
</sodipodi:namedview>
<defs
id="defs888">
<linearGradient
inkscape:collect="always"
id="linearGradient1152">
<stop
style="stop-color:#ed02a0;stop-opacity:1"
offset="0"
id="stop1148" />
<stop
style="stop-color:#8900cb;stop-opacity:1"
offset="1"
id="stop1150" />
</linearGradient>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient1152"
id="linearGradient1154"
x1="0"
y1="0"
x2="1020"
y2="1020"
gradientUnits="userSpaceOnUse" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient1152"
id="linearGradient4289"
x1="0"
y1="0"
x2="1020"
y2="1020"
gradientUnits="userSpaceOnUse" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient1152"
id="linearGradient4663"
x1="0"
y1="0"
x2="1020"
y2="1020"
gradientUnits="userSpaceOnUse" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient1152"
id="linearGradient5165"
x1="0"
y1="0"
x2="1020"
y2="1020"
gradientUnits="userSpaceOnUse" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient1152"
id="linearGradient5507"
gradientUnits="userSpaceOnUse"
x1="0"
y1="0"
x2="1020"
y2="1020" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient1152"
id="linearGradient5817"
x1="0"
y1="0"
x2="1020"
y2="1020"
gradientUnits="userSpaceOnUse" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient1152"
id="linearGradient6161"
x1="0"
y1="0"
x2="1020"
y2="1020"
gradientUnits="userSpaceOnUse" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient1152"
id="linearGradient6535"
x1="0"
y1="0"
x2="1025"
y2="1025"
gradientUnits="userSpaceOnUse" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient1152"
id="linearGradient7579"
x1="0"
y1="0"
x2="1025"
y2="1025"
gradientUnits="userSpaceOnUse" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient1152"
id="linearGradient8115"
x1="0"
y1="0"
x2="1025"
y2="1025"
gradientUnits="userSpaceOnUse" />
</defs>
<g
inkscape:label="Ebene 1"
inkscape:groupmode="layer"
id="layer1">
<rect
style="fill:url(#linearGradient1154);fill-opacity:1"
id="rect1033"
width="1025"
height="1025"
x="0"
y="0"
rx="0"
ry="0" />
<rect
style="fill:#ffffff;fill-opacity:1;stroke-width:1.41813"
id="rect2078"
width="671.12531"
height="330.82504"
x="178.42294"
y="283.78436"
ry="110.27502" />
<rect
style="fill:#ffffff;fill-opacity:1;stroke-width:1.41852"
id="rect2146"
width="199.30905"
height="504.61328"
x="518.59406"
y="450.07187"
ry="97.030388"
transform="matrix(0.97214881,-0.23436445,0.26615211,0.96393104,0,0)" />
<rect
style="fill:#ffffff;fill-opacity:1;stroke-width:1.41852"
id="rect2146-3"
width="200.94308"
height="502.92538"
x="271.98715"
y="209.38025"
ry="97.050156"
transform="matrix(0.97237519,0.23342343,-0.26569554,0.96405699,0,0)" />
<rect
style="fill:#ffffff;fill-opacity:1;stroke-width:1.41813"
id="rect2384"
width="87.538078"
height="55.137505"
x="251.37137"
y="270"
ry="27.568752" />
<rect
style="fill:#ffffff;fill-opacity:1;stroke-width:1.41813"
id="rect2384-6"
width="87.538078"
height="55.137505"
x="691.97968"
y="270"
ry="27.568752" />
<path
style="fill:url(#linearGradient4663);fill-opacity:1"
id="path2566"
sodipodi:type="arc"
sodipodi:cx="302.43524"
sodipodi:cy="400.95157"
sodipodi:rx="51.063881"
sodipodi:ry="48.245316"
sodipodi:start="2.7492077"
sodipodi:end="2.731929"
sodipodi:open="true"
sodipodi:arc-type="arc"
d="m 255.25223,419.40025 a 51.063881,48.245316 0 0 1 27.4529,-62.94723 51.063881,48.245316 0 0 1 66.74267,25.66543 51.063881,48.245316 0 0 1 -26.87629,63.16896 51.063881,48.245316 0 0 1 -66.97486,-25.11969" />
<path
style="fill:url(#linearGradient5817);fill-opacity:1"
id="path2566-7"
sodipodi:type="arc"
sodipodi:cx="663.29633"
sodipodi:cy="527.23883"
sodipodi:rx="51.063881"
sodipodi:ry="48.245316"
sodipodi:start="2.7492077"
sodipodi:end="2.731929"
sodipodi:open="true"
sodipodi:arc-type="arc"
d="m 616.11332,545.68751 a 51.063881,48.245316 0 0 1 27.45289,-62.94723 51.063881,48.245316 0 0 1 66.74267,25.66543 51.063881,48.245316 0 0 1 -26.87628,63.16896 51.063881,48.245316 0 0 1 -66.97487,-25.11969" />
<path
style="fill:url(#linearGradient5165);fill-opacity:1"
id="path2710"
sodipodi:type="arc"
sodipodi:cx="740.12561"
sodipodi:cy="345.81409"
sodipodi:rx="21.88452"
sodipodi:ry="20.676565"
sodipodi:start="2.7492077"
sodipodi:end="2.731929"
sodipodi:open="true"
sodipodi:arc-type="arc"
d="m 719.90432,353.72066 a 21.88452,20.676565 0 0 1 11.76553,-26.97738 21.88452,20.676565 0 0 1 28.604,10.99947 21.88452,20.676565 0 0 1 -11.51841,27.07241 21.88452,20.676565 0 0 1 -28.70351,-10.76558" />
<path
style="fill:url(#linearGradient5507);fill-opacity:1"
id="path2710-5"
sodipodi:type="arc"
sodipodi:cx="785.35364"
sodipodi:cy="385.78876"
sodipodi:rx="21.88452"
sodipodi:ry="20.676565"
sodipodi:start="2.7492077"
sodipodi:end="2.731929"
sodipodi:open="true"
sodipodi:arc-type="arc"
d="m 765.13235,393.69534 a 21.88452,20.676565 0 0 1 11.76553,-26.97739 21.88452,20.676565 0 0 1 28.604,10.99947 21.88452,20.676565 0 0 1 -11.51841,27.07241 21.88452,20.676565 0 0 1 -28.70351,-10.76558" />
<path
style="fill:url(#linearGradient6535);fill-opacity:1"
id="path2710-3"
sodipodi:type="arc"
sodipodi:cx="697.81555"
sodipodi:cy="385.78876"
sodipodi:rx="21.88452"
sodipodi:ry="20.676565"
sodipodi:start="2.7492077"
sodipodi:end="2.731929"
sodipodi:open="true"
sodipodi:arc-type="arc"
d="m 677.59426,393.69534 a 21.88452,20.676565 0 0 1 11.76553,-26.97739 21.88452,20.676565 0 0 1 28.604,10.99947 21.88452,20.676565 0 0 1 -11.51841,27.07241 21.88452,20.676565 0 0 1 -28.70351,-10.76558" />
<path
style="fill:url(#linearGradient6161);fill-opacity:1"
id="path2710-56"
sodipodi:type="arc"
sodipodi:cx="741.58459"
sodipodi:cy="427.14188"
sodipodi:rx="21.88452"
sodipodi:ry="20.676565"
sodipodi:start="2.7492077"
sodipodi:end="2.731929"
sodipodi:open="true"
sodipodi:arc-type="arc"
d="m 721.36331,435.04845 a 21.88452,20.676565 0 0 1 11.76552,-26.97738 21.88452,20.676565 0 0 1 28.604,10.99947 21.88452,20.676565 0 0 1 -11.51841,27.07241 21.88452,20.676565 0 0 1 -28.70351,-10.76558" />
<rect
style="fill:url(#linearGradient8115);fill-opacity:1;stroke-width:1.41813"
id="rect2896"
width="36.474197"
height="110.27502"
x="369.54779"
y="476.76566"
ry="13.784376" />
<rect
style="fill:url(#linearGradient7579);fill-opacity:1;stroke-width:1.41813"
id="rect2898"
width="116.71745"
height="37.217815"
x="327.2377"
y="512.60504"
ry="13.383304" />
<rect
style="fill:url(#linearGradient4289);fill-opacity:1;stroke-width:1.41813"
id="rect3104"
width="233.43489"
height="110.27502"
x="400.1861"
y="338.92191"
ry="13.383307" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 9.3 KiB

269
controller_banner.svg Normal file
View File

@@ -0,0 +1,269 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="1920"
height="1080"
viewBox="0 0 1920 1080"
version="1.1"
id="SVGRoot"
sodipodi:docname="controller_banner.svg"
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04, custom)"
inkscape:export-filename="/home/martin/Bilder/Bilder/MidnightControlsBanner.png"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview893"
pagecolor="#505050"
bordercolor="#eeeeee"
borderopacity="1"
inkscape:pageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:document-units="px"
showgrid="true"
inkscape:zoom="0.40120419"
inkscape:cx="928.45492"
inkscape:cy="816.29258"
inkscape:window-width="1920"
inkscape:window-height="1050"
inkscape:window-x="1920"
inkscape:window-y="30"
inkscape:window-maximized="1"
inkscape:current-layer="layer1">
<inkscape:grid
type="xygrid"
id="grid956" />
</sodipodi:namedview>
<defs
id="defs888">
<linearGradient
inkscape:collect="always"
id="linearGradient2059">
<stop
style="stop-color:#ed02a0;stop-opacity:0.72909278"
offset="0"
id="stop2055" />
<stop
style="stop-color:#8900cb;stop-opacity:0.73234183"
offset="1"
id="stop2057" />
</linearGradient>
<inkscape:path-effect
effect="powerclip"
id="path-effect904"
is_visible="true"
lpeversion="1"
inverse="true"
flatten="false"
hide_clip="false"
message="Füllregel Gerade-Ungerade im &lt;b&gt;Füllung und Kontur&lt;/b&gt;-Dialog verwenden, wenn das Anwenden des Ausschnitts mit nachfolgendem Umwandeln in Pfade kein Ergebnis bringt" />
<inkscape:path-effect
effect="powerclip"
id="path-effect900"
is_visible="true"
lpeversion="1"
inverse="true"
flatten="false"
hide_clip="false"
message="Füllregel Gerade-Ungerade im &lt;b&gt;Füllung und Kontur&lt;/b&gt;-Dialog verwenden, wenn das Anwenden des Ausschnitts mit nachfolgendem Umwandeln in Pfade kein Ergebnis bringt" />
<inkscape:path-effect
effect="powerclip"
id="path-effect896"
is_visible="true"
lpeversion="1"
inverse="true"
flatten="false"
hide_clip="false"
message="Füllregel Gerade-Ungerade im &lt;b&gt;Füllung und Kontur&lt;/b&gt;-Dialog verwenden, wenn das Anwenden des Ausschnitts mit nachfolgendem Umwandeln in Pfade kein Ergebnis bringt" />
<rect
x="500"
y="800"
width="840"
height="220"
id="rect7833" />
<linearGradient
inkscape:collect="always"
id="linearGradient1152">
<stop
style="stop-color:#ed02a0;stop-opacity:1"
offset="0"
id="stop1148" />
<stop
style="stop-color:#8900cb;stop-opacity:1"
offset="1"
id="stop1150" />
</linearGradient>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient2059"
id="linearGradient1154"
x1="0"
y1="0"
x2="1920"
y2="1080"
gradientUnits="userSpaceOnUse" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient1152"
id="linearGradient4289"
x1="-398"
y1="56"
x2="1522"
y2="1136"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(398,-56)" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient1152"
id="linearGradient7579"
x1="-398"
y1="56"
x2="1522"
y2="1136"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(398,-56)" />
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath878">
<path
id="path880"
style="display:none;fill:url(#linearGradient882);stroke-width:1.00028"
d="m 1170.0221,-4.111892 a 21.817197,20.751893 3.393352 0 0 -8.3753,3.68511674 21.817197,20.751893 3.393352 0 0 -4.1775,28.99380526 l 0.2304,0.284584 a 21.817197,20.751893 3.393352 0 0 30.5488,3.770316 21.817197,20.751893 3.393352 0 0 3.9128,-29.0289691 21.817197,20.751893 3.393352 0 0 -22.1392,-7.7048529 z M 858.04762,85.714292 c -7.15183,1.731644 -11.32417,8.932859 -9.35312,16.146398 l 22.2005,81.24794 c 1.97106,7.21354 9.31675,11.62594 16.46858,9.8943 L 1086.714,144.73497 c 7.1518,-1.73163 11.3223,-8.9324 9.3512,-16.14595 l -22.2005,-81.247936 c -1.971,-7.213536 -9.3149,-11.626381 -16.4667,-9.894748 z M 756.02165,124.73884 a 50.906793,48.421082 3.3933293 0 0 -19.54511,8.59585 50.906793,48.421082 3.3933293 0 0 -9.74699,67.65414 l 0.53564,0.66651 a 50.906793,48.421082 3.3933293 0 0 71.28286,8.79621 50.906793,48.421082 3.3933293 0 0 9.13065,-67.73384 50.906793,48.421082 3.3933293 0 0 -51.65705,-17.97887 z M 1139.8366,44.661798 a 21.817197,20.751893 3.393352 0 0 -8.3752,3.685094 21.817197,20.751893 3.393352 0 0 -4.1775,28.993805 l 0.2304,0.284584 a 21.817197,20.751893 3.393352 0 0 30.5487,3.770339 21.817197,20.751893 3.393352 0 0 3.9147,-29.029436 21.817197,20.751893 3.393352 0 0 -22.1411,-7.704386 z m 84.4396,-20.445031 a 21.817197,20.751893 3.393352 0 0 -8.3754,3.685117 21.817197,20.751893 3.393352 0 0 -4.1774,28.993806 l 0.2285,0.285027 a 21.817197,20.751893 3.393352 0 0 30.5488,3.77034 21.817197,20.751893 3.393352 0 0 3.9147,-29.02946 21.817197,20.751893 3.393352 0 0 -22.1392,-7.70483 z M 1193.05,74.672751 a 21.817197,20.751893 3.393352 0 0 -8.3759,3.68321 21.817197,20.751893 3.393352 0 0 -4.1769,28.995709 l 0.2303,0.28458 a 21.817197,20.751893 3.393352 0 0 30.5488,3.77035 21.817197,20.751893 3.393352 0 0 3.9128,-29.029006 21.817197,20.751893 3.393352 0 0 -22.1391,-7.704843 z M 865.52481,226.88685 c -7.36616,1.78355 -11.66248,9.20165 -9.63236,16.63136 l 5.86309,21.45735 -27.90161,6.7557 c -7.15183,1.73165 -11.32229,8.9324 -9.35123,16.14595 l 2.77837,10.1681 c 1.97106,7.21354 9.31487,11.6264 16.46669,9.89476 l 27.90161,-6.75571 6.22966,22.7989 c 2.03012,7.42971 9.59558,11.97577 16.96174,10.19223 l 8.59091,-2.08008 c 7.36616,-1.78354 11.6606,-9.2012 9.63048,-16.63091 l -6.22966,-22.7989 23.68151,-5.73391 c 7.15183,-1.73165 11.32229,-8.93242 9.35123,-16.14595 l -2.77837,-10.16811 c -1.97106,-7.21354 -9.31486,-11.62639 -16.46669,-9.89474 l -23.68151,5.73391 -5.86309,-21.45735 c -2.03012,-7.42971 -9.5937,-11.97623 -16.95987,-10.19269 z m 272.15169,-63.56116 a 50.906793,48.421082 3.3933293 0 0 -19.5432,8.59539 50.906793,48.421082 3.3933293 0 0 -9.747,67.65414 l 0.5356,0.66651 a 50.906793,48.421082 3.3933293 0 0 71.2829,8.79621 50.906793,48.421082 3.3933293 0 0 9.1307,-67.73384 50.906793,48.421082 3.3933293 0 0 -51.659,-17.97841 z" />
<path
id="lpe_path-effect896"
style="fill:url(#linearGradient882);stroke-width:1.00028"
class="powerclip"
d="M 636.0108,56.943474 H 846.95389 V 569.86886 H 636.0108 Z m 534.0113,-61.055366 a 21.817197,20.751893 3.393352 0 0 -8.3753,3.68511674 21.817197,20.751893 3.393352 0 0 -4.1775,28.99380526 l 0.2304,0.284584 a 21.817197,20.751893 3.393352 0 0 30.5488,3.770316 21.817197,20.751893 3.393352 0 0 3.9128,-29.0289691 21.817197,20.751893 3.393352 0 0 -22.1392,-7.7048529 z M 858.04762,85.714292 c -7.15183,1.731644 -11.32417,8.932859 -9.35312,16.146398 l 22.2005,81.24794 c 1.97106,7.21354 9.31675,11.62594 16.46858,9.8943 L 1086.714,144.73497 c 7.1518,-1.73163 11.3223,-8.9324 9.3512,-16.14595 l -22.2005,-81.247936 c -1.971,-7.213536 -9.3149,-11.626381 -16.4667,-9.894748 z M 756.02165,124.73884 a 50.906793,48.421082 3.3933293 0 0 -19.54511,8.59585 50.906793,48.421082 3.3933293 0 0 -9.74699,67.65414 l 0.53564,0.66651 a 50.906793,48.421082 3.3933293 0 0 71.28286,8.79621 50.906793,48.421082 3.3933293 0 0 9.13065,-67.73384 50.906793,48.421082 3.3933293 0 0 -51.65705,-17.97887 z M 1139.8366,44.661798 a 21.817197,20.751893 3.393352 0 0 -8.3752,3.685094 21.817197,20.751893 3.393352 0 0 -4.1775,28.993805 l 0.2304,0.284584 a 21.817197,20.751893 3.393352 0 0 30.5487,3.770339 21.817197,20.751893 3.393352 0 0 3.9147,-29.029436 21.817197,20.751893 3.393352 0 0 -22.1411,-7.704386 z m 84.4396,-20.445031 a 21.817197,20.751893 3.393352 0 0 -8.3754,3.685117 21.817197,20.751893 3.393352 0 0 -4.1774,28.993806 l 0.2285,0.285027 a 21.817197,20.751893 3.393352 0 0 30.5488,3.77034 21.817197,20.751893 3.393352 0 0 3.9147,-29.02946 21.817197,20.751893 3.393352 0 0 -22.1392,-7.70483 z M 1193.05,74.672751 a 21.817197,20.751893 3.393352 0 0 -8.3759,3.68321 21.817197,20.751893 3.393352 0 0 -4.1769,28.995709 l 0.2303,0.28458 a 21.817197,20.751893 3.393352 0 0 30.5488,3.77035 21.817197,20.751893 3.393352 0 0 3.9128,-29.029006 21.817197,20.751893 3.393352 0 0 -22.1391,-7.704843 z M 865.52481,226.88685 c -7.36616,1.78355 -11.66248,9.20165 -9.63236,16.63136 l 5.86309,21.45735 -27.90161,6.7557 c -7.15183,1.73165 -11.32229,8.9324 -9.35123,16.14595 l 2.77837,10.1681 c 1.97106,7.21354 9.31487,11.6264 16.46669,9.89476 l 27.90161,-6.75571 6.22966,22.7989 c 2.03012,7.42971 9.59558,11.97577 16.96174,10.19223 l 8.59091,-2.08008 c 7.36616,-1.78354 11.6606,-9.2012 9.63048,-16.63091 l -6.22966,-22.7989 23.68151,-5.73391 c 7.15183,-1.73165 11.32229,-8.93242 9.35123,-16.14595 l -2.77837,-10.16811 c -1.97106,-7.21354 -9.31486,-11.62639 -16.46669,-9.89474 l -23.68151,5.73391 -5.86309,-21.45735 c -2.03012,-7.42971 -9.5937,-11.97623 -16.95987,-10.19269 z m 272.15169,-63.56116 a 50.906793,48.421082 3.3933293 0 0 -19.5432,8.59539 50.906793,48.421082 3.3933293 0 0 -9.747,67.65414 l 0.5356,0.66651 a 50.906793,48.421082 3.3933293 0 0 71.2829,8.79621 50.906793,48.421082 3.3933293 0 0 9.1307,-67.73384 50.906793,48.421082 3.3933293 0 0 -51.659,-17.97841 z" />
</clipPath>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient1152"
id="linearGradient882"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.96459266,-0.23355313,0.26584317,0.97291549,369.02066,-147.43741)"
x1="-398"
y1="56"
x2="1522"
y2="1136" />
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath884">
<path
id="path886"
style="display:none;fill:url(#linearGradient888);stroke-width:1.00027"
d="m 1026.6374,528.82806 a 20.752793,21.815894 86.926601 0 0 -9.2243,-0.58528 20.752793,21.815894 86.926601 0 0 -18.53105,23.48045 l 0.0557,0.35396 a 20.752793,21.815894 86.926601 0 0 24.81605,17.20206 20.752793,21.815894 86.926601 0 0 18.3173,-23.63171 20.752793,21.815894 86.926601 0 0 -15.4337,-16.81948 z M 707.37883,465.4963 c -7.15078,-1.7386 -14.49832,2.66675 -16.47273,9.87849 l -22.23828,81.2277 c -1.97441,7.21175 2.19438,14.41711 9.34515,16.15571 l 199.32113,48.46175 c 7.15073,1.73859 14.49643,-2.66722 16.47084,-9.87896 l 22.23828,-81.2277 c 1.97441,-7.21174 -2.19254,-14.41666 -9.34327,-16.15524 z M 598.02135,453.23104 a 48.423182,50.903752 86.926623 0 0 -21.5241,-1.36927 48.423182,50.903752 86.926623 0 0 -43.23978,54.7896 l 0.12713,0.82721 a 48.423182,50.903752 86.926623 0 0 57.90649,40.13811 48.423182,50.903752 86.926623 0 0 42.74098,-55.13985 48.423182,50.903752 86.926623 0 0 -36.01072,-39.2458 z m 377.16446,104.5578 a 20.752793,21.815894 86.926601 0 0 -9.22414,-0.58526 20.752793,21.815894 86.926601 0 0 -18.53108,23.48045 l 0.0557,0.35396 a 20.752793,21.815894 86.926601 0 0 24.8159,17.20204 20.752793,21.815894 86.926601 0 0 18.31921,-23.63124 20.752793,21.815894 86.926601 0 0 -15.43563,-16.81995 z m 84.42719,20.52712 a 20.752793,21.815894 86.926601 0 0 -9.2243,-0.58529 20.752793,21.815894 86.926601 0 0 -18.531,23.48045 l 0.054,0.35352 a 20.752793,21.815894 86.926601 0 0 24.8159,17.20204 20.752793,21.815894 86.926601 0 0 18.3193,-23.63122 20.752793,21.815894 86.926601 0 0 -15.4338,-16.8195 z m -53.2258,29.9599 a 20.752793,21.815894 86.926601 0 0 -9.22373,-0.58718 20.752793,21.815894 86.926601 0 0 -18.53161,23.48235 l 0.0557,0.35395 a 20.752793,21.815894 86.926601 0 0 24.81584,17.20205 20.752793,21.815894 86.926601 0 0 18.3174,-23.63169 20.752793,21.815894 86.926601 0 0 -15.4337,-16.81948 z M 641.50831,592.48259 c -7.36508,-1.79069 -14.93245,2.74811 -16.96603,10.17597 l -5.87307,21.452 -27.8975,-6.78283 c -7.15078,-1.7386 -14.49644,2.6672 -16.47085,9.87895 l -2.7831,10.16557 c -1.97441,7.21174 2.1925,14.41666 9.34328,16.15525 l 27.8975,6.78284 -6.24026,22.79322 c -2.03358,7.42786 2.25909,14.85024 9.62417,16.64094 l 8.58964,2.08844 c 7.36508,1.7907 14.93057,-2.74856 16.96415,-10.17642 l 6.24026,-22.79322 23.67803,5.75693 c 7.15078,1.7386 14.49644,-2.66722 16.47085,-9.87896 l 2.7831,-10.16557 c 1.97441,-7.21175 -2.1925,-14.41665 -9.34328,-16.15525 l -23.67803,-5.75693 5.87307,-21.45201 c 2.03358,-7.42785 -2.25721,-14.84978 -9.62229,-16.64048 z m 270.91446,68.20272 a 48.423182,50.903752 86.926623 0 0 -21.52221,-1.3688 48.423182,50.903752 86.926623 0 0 -43.23975,54.78961 l 0.12708,0.8272 a 48.423182,50.903752 86.926623 0 0 57.90652,40.13811 48.423182,50.903752 86.926623 0 0 42.74097,-55.13984 48.423182,50.903752 86.926623 0 0 -36.01261,-39.24628 z" />
<path
id="lpe_path-effect900"
style="fill:url(#linearGradient888);stroke-width:1.00027"
class="powerclip"
d="M 912.35657,483.93158 H 1121.6656 V 998.54486 H 912.35657 Z m 114.28083,44.89648 a 20.752793,21.815894 86.926601 0 0 -9.2243,-0.58528 20.752793,21.815894 86.926601 0 0 -18.53105,23.48045 l 0.0557,0.35396 a 20.752793,21.815894 86.926601 0 0 24.81605,17.20206 20.752793,21.815894 86.926601 0 0 18.3173,-23.63171 20.752793,21.815894 86.926601 0 0 -15.4337,-16.81948 z M 707.37883,465.4963 c -7.15078,-1.7386 -14.49832,2.66675 -16.47273,9.87849 l -22.23828,81.2277 c -1.97441,7.21175 2.19438,14.41711 9.34515,16.15571 l 199.32113,48.46175 c 7.15073,1.73859 14.49643,-2.66722 16.47084,-9.87896 l 22.23828,-81.2277 c 1.97441,-7.21174 -2.19254,-14.41666 -9.34327,-16.15524 z M 598.02135,453.23104 a 48.423182,50.903752 86.926623 0 0 -21.5241,-1.36927 48.423182,50.903752 86.926623 0 0 -43.23978,54.7896 l 0.12713,0.82721 a 48.423182,50.903752 86.926623 0 0 57.90649,40.13811 48.423182,50.903752 86.926623 0 0 42.74098,-55.13985 48.423182,50.903752 86.926623 0 0 -36.01072,-39.2458 z m 377.16446,104.5578 a 20.752793,21.815894 86.926601 0 0 -9.22414,-0.58526 20.752793,21.815894 86.926601 0 0 -18.53108,23.48045 l 0.0557,0.35396 a 20.752793,21.815894 86.926601 0 0 24.8159,17.20204 20.752793,21.815894 86.926601 0 0 18.31921,-23.63124 20.752793,21.815894 86.926601 0 0 -15.43563,-16.81995 z m 84.42719,20.52712 a 20.752793,21.815894 86.926601 0 0 -9.2243,-0.58529 20.752793,21.815894 86.926601 0 0 -18.531,23.48045 l 0.054,0.35352 a 20.752793,21.815894 86.926601 0 0 24.8159,17.20204 20.752793,21.815894 86.926601 0 0 18.3193,-23.63122 20.752793,21.815894 86.926601 0 0 -15.4338,-16.8195 z m -53.2258,29.9599 a 20.752793,21.815894 86.926601 0 0 -9.22373,-0.58718 20.752793,21.815894 86.926601 0 0 -18.53161,23.48235 l 0.0557,0.35395 a 20.752793,21.815894 86.926601 0 0 24.81584,17.20205 20.752793,21.815894 86.926601 0 0 18.3174,-23.63169 20.752793,21.815894 86.926601 0 0 -15.4337,-16.81948 z M 641.50831,592.48259 c -7.36508,-1.79069 -14.93245,2.74811 -16.96603,10.17597 l -5.87307,21.452 -27.8975,-6.78283 c -7.15078,-1.7386 -14.49644,2.6672 -16.47085,9.87895 l -2.7831,10.16557 c -1.97441,7.21174 2.1925,14.41666 9.34328,16.15525 l 27.8975,6.78284 -6.24026,22.79322 c -2.03358,7.42786 2.25909,14.85024 9.62417,16.64094 l 8.58964,2.08844 c 7.36508,1.7907 14.93057,-2.74856 16.96415,-10.17642 l 6.24026,-22.79322 23.67803,5.75693 c 7.15078,1.7386 14.49644,-2.66722 16.47085,-9.87896 l 2.7831,-10.16557 c 1.97441,-7.21175 -2.1925,-14.41665 -9.34328,-16.15525 l -23.67803,-5.75693 5.87307,-21.45201 c 2.03358,-7.42785 -2.25721,-14.84978 -9.62229,-16.64048 z m 270.91446,68.20272 a 48.423182,50.903752 86.926623 0 0 -21.52221,-1.3688 48.423182,50.903752 86.926623 0 0 -43.23975,54.78961 l 0.12708,0.8272 a 48.423182,50.903752 86.926623 0 0 57.90652,40.13811 48.423182,50.903752 86.926623 0 0 42.74097,-55.13984 48.423182,50.903752 86.926623 0 0 -36.01261,-39.24628 z" />
</clipPath>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient1152"
id="linearGradient888"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.96445087,0.23449084,-0.26629564,0.97267307,398.764,38.857662)"
x1="-398"
y1="56"
x2="1522"
y2="1136" />
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath890">
<path
id="path892"
style="display:none;fill:url(#linearGradient894)"
d="m 1138.793,269.14648 a 21.88452,20.676565 0 0 0 -9.1231,1.59766 21.88452,20.676565 0 0 0 -11.7656,26.97656 l 0.1484,0.32813 a 21.88452,20.676565 0 0 0 28.7032,10.76562 21.88452,20.676565 0 0 0 11.5175,-27.07226 21.88452,20.676565 0 0 0 -19.4804,-12.59571 z m -327.22269,13.7754 c -7.41435,0 -13.38476,5.96846 -13.38476,13.38281 v 83.50976 c 0,7.41436 5.97041,13.38282 13.38476,13.38282 h 206.66799 c 7.4143,0 13.3828,-5.96846 13.3828,-13.38282 v -83.50976 c 0,-7.41435 -5.9685,-13.38281 -13.3828,-13.38281 z m -109.57617,13.80664 a 51.063881,48.245316 0 0 0 -21.28906,3.7246 51.063881,48.245316 0 0 0 -27.45313,62.94727 l 0.34375,0.76758 a 51.063881,48.245316 0 0 0 66.97657,25.11914 51.063881,48.245316 0 0 0 26.875,-63.16797 51.063881,48.245316 0 0 0 -45.45313,-29.39062 z m 394.48826,12.39257 a 21.88452,20.676565 0 0 0 -9.123,1.59766 21.88452,20.676565 0 0 0 -11.7656,26.97656 l 0.1484,0.32813 a 21.88452,20.676565 0 0 0 28.7031,10.76562 21.88452,20.676565 0 0 0 11.5195,-27.07226 21.88452,20.676565 0 0 0 -19.4824,-12.59571 z m 87.5391,0 a 21.88452,20.676565 0 0 0 -9.1231,1.59766 21.88452,20.676565 0 0 0 -11.7656,26.97656 l 0.1465,0.32813 a 21.88452,20.676565 0 0 0 28.7031,10.76562 21.88452,20.676565 0 0 0 11.5196,-27.07226 21.88452,20.676565 0 0 0 -19.4805,-12.59571 z m -43.7695,41.35352 a 21.88452,20.676565 0 0 0 -9.1231,1.5957 21.88452,20.676565 0 0 0 -11.7656,26.97852 l 0.1484,0.32812 a 21.88452,20.676565 0 0 0 28.7031,10.76563 21.88452,20.676565 0 0 0 11.5176,-27.07227 21.88452,20.676565 0 0 0 -19.4804,-12.5957 z m -358.91997,70.29101 c -7.63655,10e-6 -13.78515,6.14862 -13.78515,13.78516 v 22.05469 h -28.92579 c -7.41435,0 -13.38281,5.96845 -13.38281,13.38281 v 10.45117 c 0,7.41435 5.96846,13.38282 13.38281,13.38282 h 28.92579 v 23.43359 c 0,7.63654 6.1486,13.78516 13.78515,13.78516 h 8.90625 c 7.63655,0 13.7832,-6.14862 13.7832,-13.78516 v -23.43359 h 24.55079 c 7.41435,0 13.38281,-5.96847 13.38281,-13.38282 v -10.45117 c 0,-7.41436 -5.96846,-13.38281 -13.38281,-13.38281 h -24.55079 v -22.05469 c 0,-7.63654 -6.14665,-13.78516 -13.7832,-13.78516 z m 281.52147,2.25 a 51.063881,48.245316 0 0 0 -21.2871,3.72461 51.063881,48.245316 0 0 0 -27.4531,62.94727 l 0.3437,0.76758 a 51.063881,48.245316 0 0 0 66.9766,25.11914 51.063881,48.245316 0 0 0 26.875,-63.16797 51.063881,48.245316 0 0 0 -45.4551,-29.39063 z" />
<path
id="lpe_path-effect904"
style="fill:url(#linearGradient894)"
class="powerclip"
d="M 571.42297,222.78436 H 1252.5483 V 563.60941 H 571.42297 Z m 567.37003,46.36212 a 21.88452,20.676565 0 0 0 -9.1231,1.59766 21.88452,20.676565 0 0 0 -11.7656,26.97656 l 0.1484,0.32813 a 21.88452,20.676565 0 0 0 28.7032,10.76562 21.88452,20.676565 0 0 0 11.5175,-27.07226 21.88452,20.676565 0 0 0 -19.4804,-12.59571 z m -327.22269,13.7754 c -7.41435,0 -13.38476,5.96846 -13.38476,13.38281 v 83.50976 c 0,7.41436 5.97041,13.38282 13.38476,13.38282 h 206.66799 c 7.4143,0 13.3828,-5.96846 13.3828,-13.38282 v -83.50976 c 0,-7.41435 -5.9685,-13.38281 -13.3828,-13.38281 z m -109.57617,13.80664 a 51.063881,48.245316 0 0 0 -21.28906,3.7246 51.063881,48.245316 0 0 0 -27.45313,62.94727 l 0.34375,0.76758 a 51.063881,48.245316 0 0 0 66.97657,25.11914 51.063881,48.245316 0 0 0 26.875,-63.16797 51.063881,48.245316 0 0 0 -45.45313,-29.39062 z m 394.48826,12.39257 a 21.88452,20.676565 0 0 0 -9.123,1.59766 21.88452,20.676565 0 0 0 -11.7656,26.97656 l 0.1484,0.32813 a 21.88452,20.676565 0 0 0 28.7031,10.76562 21.88452,20.676565 0 0 0 11.5195,-27.07226 21.88452,20.676565 0 0 0 -19.4824,-12.59571 z m 87.5391,0 a 21.88452,20.676565 0 0 0 -9.1231,1.59766 21.88452,20.676565 0 0 0 -11.7656,26.97656 l 0.1465,0.32813 a 21.88452,20.676565 0 0 0 28.7031,10.76562 21.88452,20.676565 0 0 0 11.5196,-27.07226 21.88452,20.676565 0 0 0 -19.4805,-12.59571 z m -43.7695,41.35352 a 21.88452,20.676565 0 0 0 -9.1231,1.5957 21.88452,20.676565 0 0 0 -11.7656,26.97852 l 0.1484,0.32812 a 21.88452,20.676565 0 0 0 28.7031,10.76563 21.88452,20.676565 0 0 0 11.5176,-27.07227 21.88452,20.676565 0 0 0 -19.4804,-12.5957 z m -358.91997,70.29101 c -7.63655,10e-6 -13.78515,6.14862 -13.78515,13.78516 v 22.05469 h -28.92579 c -7.41435,0 -13.38281,5.96845 -13.38281,13.38281 v 10.45117 c 0,7.41435 5.96846,13.38282 13.38281,13.38282 h 28.92579 v 23.43359 c 0,7.63654 6.1486,13.78516 13.78515,13.78516 h 8.90625 c 7.63655,0 13.7832,-6.14862 13.7832,-13.78516 v -23.43359 h 24.55079 c 7.41435,0 13.38281,-5.96847 13.38281,-13.38282 v -10.45117 c 0,-7.41436 -5.96846,-13.38281 -13.38281,-13.38281 h -24.55079 v -22.05469 c 0,-7.63654 -6.14665,-13.78516 -13.7832,-13.78516 z m 281.52147,2.25 a 51.063881,48.245316 0 0 0 -21.2871,3.72461 51.063881,48.245316 0 0 0 -27.4531,62.94727 l 0.3437,0.76758 a 51.063881,48.245316 0 0 0 66.9766,25.11914 51.063881,48.245316 0 0 0 26.875,-63.16797 51.063881,48.245316 0 0 0 -45.4551,-29.39063 z" />
</clipPath>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient1152"
id="linearGradient894"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(398,-56)"
x1="-398"
y1="56"
x2="1522"
y2="1136" />
</defs>
<g
inkscape:label="Ebene 1"
inkscape:groupmode="layer"
id="layer1">
<rect
style="fill:url(#linearGradient1154);fill-opacity:1"
id="rect1033"
width="1920"
height="1080"
x="0"
y="0"
rx="0"
ry="0" />
<path
style="fill:#ffffff;fill-opacity:1;stroke-width:1.41813"
id="rect2078"
width="671.12531"
height="330.82504"
x="576.42297"
y="227.78436"
ry="110.27502"
sodipodi:type="rect"
clip-path="url(#clipPath890)"
inkscape:path-effect="#path-effect904"
d="m 686.69799,227.78436 h 450.57531 c 61.0923,0 110.275,49.18266 110.275,110.27502 v 110.27501 c 0,61.09236 -49.1827,110.27502 -110.275,110.27502 H 686.69799 c -61.09236,0 -110.27502,-49.18266 -110.27502,-110.27502 V 338.05938 c 0,-61.09236 49.18266,-110.27502 110.27502,-110.27502 z" />
<path
style="fill:#ffffff;fill-opacity:1;stroke-width:1.41852"
id="rect2146"
width="199.30905"
height="504.61328"
x="917.35657"
y="488.93158"
ry="97.030388"
transform="matrix(0.97214881,-0.23436445,0.26615211,0.96393104,0,0)"
sodipodi:type="rect"
clip-path="url(#clipPath884)"
inkscape:path-effect="#path-effect900"
d="m 1014.387,488.93158 h 5.2482 c 53.7549,0 97.0304,43.27555 97.0304,97.03039 v 310.5525 c 0,53.75484 -43.2755,97.03039 -97.0304,97.03039 h -5.2482 c -53.75488,0 -97.03043,-43.27555 -97.03043,-97.03039 v -310.5525 c 0,-53.75484 43.27555,-97.03039 97.03043,-97.03039 z" />
<path
style="fill:#ffffff;fill-opacity:1;stroke-width:1.41852"
id="rect2146-3"
width="200.94308"
height="502.92538"
x="641.0108"
y="61.943474"
ry="97.050156"
transform="matrix(0.97237519,0.23342343,-0.26569554,0.96405699,0,0)"
sodipodi:type="rect"
clip-path="url(#clipPath878)"
inkscape:path-effect="#path-effect896"
d="m 738.06096,61.943474 h 6.84277 c 53.76579,0 97.05016,43.284366 97.05016,97.050156 V 467.8187 c 0,53.76579 -43.28437,97.05016 -97.05016,97.05016 h -6.84277 c -53.76579,0 -97.05016,-43.28437 -97.05016,-97.05016 V 158.99363 c 0,-53.76579 43.28437,-97.050156 97.05016,-97.050156 z" />
<rect
style="fill:#ffffff;fill-opacity:1;stroke-width:1.41813"
id="rect2384"
width="87.538078"
height="55.137505"
x="649.37134"
y="214"
ry="27.568752" />
<rect
style="fill:#ffffff;fill-opacity:1;stroke-width:1.41813"
id="rect2384-6"
width="87.538078"
height="55.137505"
x="1089.9797"
y="214"
ry="27.568752" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 24 KiB

View File

@@ -2,17 +2,33 @@
org.gradle.jvmargs=-Xmx1G
# Fabric Properties
# check these on https://fabricmc.net/use
minecraft_version=1.15.1
yarn_mappings=1.15.1+build.3:v2
loader_version=0.7.2+build.174
# check these on https://fabricmc.net/use
minecraft_version=1.19.3
yarn_mappings=1.19.3+build.5
loader_version=0.14.12
# Mod Properties
mod_version = 1.0.1
maven_group = me.lambdaurora
archives_base_name = lambdacontrols
mod_version = 1.7.3
maven_group = eu.midnightdust
archives_base_name = midnightcontrols
modrinth_id=bXX9h73M
# Dependencies
# currently not on the main fabric site, check on the maven: https://maven.fabricmc.net/net/fabricmc/fabric-api/fabric-api
fabric_version=0.4.25+build.282-1.15
# currently not on the main fabric site, check on the maven: https://maven.fabricmc.net/net/fabricmc/fabric-api/fabric-api
fabric_version=0.71.0+1.19.3
sodium_version=mc1.19.2-0.4.4
spruceui_version=4.1.0+1.19.3
midnightlib_version=1.1.0-fabric
modmenu_version=5.0.1
emotecraft_version=2.1.3-SNAPSHOT-build.29-MC1.19-fabric
bendylib_version=2.0.+
emi_version=0.5.0+1.19.3
libgui_version=6.0.0+1.19
inventorytabs_version=inventorytabs-0.9.beta-1.19.x
clothconfig_version=7.0.72
yacl_version=2.2.0
bedrockify_version=1.5+mc1.19.1-2
# Ok Zoomer and LibZoomer are temporarily disabled for the time being, as we are currently using Reflection at runtime instead in OkZoomerCompat due to there being two major, completely incompatible API versions.
# ok_zoomer_version=5.0.0-beta.3+1.17.1
# libzoomer_version=0.3.0+1.17.1

Binary file not shown.

View File

@@ -1,6 +1,5 @@
#Mon Oct 28 18:23:18 CET 2019
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.5.1-all.zip

270
gradlew vendored Normal file → Executable file
View File

@@ -1,7 +1,7 @@
#!/usr/bin/env sh
#!/bin/sh
#
# Copyright 2015 the original author or authors.
# Copyright © 2015-2021 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.
@@ -17,78 +17,113 @@
#
##############################################################################
##
## Gradle start up script for UN*X
##
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
APP_BASE_NAME=${0##*/}
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
MAX_FD=maximum
warn () {
echo "$*"
}
} >&2
die () {
echo
echo "$*"
echo
exit 1
}
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD="$JAVA_HOME/bin/java"
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
@@ -97,7 +132,7 @@ Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
@@ -105,84 +140,95 @@ location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=$(save "$@")
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# 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" )
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"

189
gradlew.bat vendored
View File

@@ -1,100 +1,89 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
: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 %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
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 %*
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

BIN
icon.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.8 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 174 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 184 KiB

View File

@@ -1,10 +1,11 @@
pluginManagement {
repositories {
jcenter()
maven {
name = 'Fabric'
url = 'https://maven.fabricmc.net/'
name 'Fabric'
url 'https://maven.fabricmc.net/'
}
gradlePluginPortal()
}
}
rootProject.name = 'midnightcontrols'

View File

@@ -0,0 +1,66 @@
/*
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of midnightcontrols.
*
* Licensed under the MIT license. For more information,
* see the LICENSE file.
*/
package eu.midnightdust.midnightcontrols;
import dev.lambdaurora.spruceui.util.Nameable;
import org.jetbrains.annotations.NotNull;
import java.util.Arrays;
import java.util.Optional;
/**
* Represents the controls mode.
*
* @author LambdAurora
* @version 1.7.0
* @since 1.0.0
*/
public enum ControlsMode implements Nameable {
DEFAULT,
CONTROLLER,
TOUCHSCREEN;
/**
* Returns the next controls mode available.
*
* @return the next available controls mode
*/
public ControlsMode next() {
var v = values();
if (v.length == this.ordinal() + 1)
return v[0];
return v[this.ordinal() + 1];
}
/**
* Gets the translation key of this controls mode.
*
* @return the translated key of this controls mode
* @since 1.1.0
*/
public String getTranslationKey() {
return "midnightcontrols.controls_mode." + this.getName();
}
@Override
public @NotNull String getName() {
return this.name().toLowerCase();
}
/**
* Gets the controls mode from its identifier.
*
* @param id the identifier of the controls mode
* @return the controls mode if found, else empty
*/
public static Optional<ControlsMode> byId(@NotNull String id) {
return Arrays.stream(values()).filter(mode -> mode.getName().equalsIgnoreCase(id)).findFirst();
}
}

View File

@@ -0,0 +1,129 @@
/*
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of midnightcontrols.
*
* Licensed under the MIT license. For more information,
* see the LICENSE file.
*/
package eu.midnightdust.midnightcontrols;
import eu.midnightdust.midnightcontrols.client.MidnightControlsConfig;
import eu.midnightdust.midnightcontrols.event.PlayerChangeControlsModeCallback;
import io.netty.buffer.Unpooled;
import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking;
import net.fabricmc.loader.api.FabricLoader;
import net.fabricmc.loader.api.ModContainer;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.util.Identifier;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.NotNull;
import java.util.Objects;
import java.util.Optional;
/**
* Represents the MidnightControls mod.
*
* @author LambdAurora & Motschen
* @version 1.8.0
* @since 1.0.0
*/
public class MidnightControls implements ModInitializer {
private static MidnightControls INSTANCE;
public static final Identifier CONTROLS_MODE_CHANNEL = new Identifier(MidnightControlsConstants.CONTROLS_MODE_CHANNEL.toString());
public static final Identifier FEATURE_CHANNEL = new Identifier(MidnightControlsConstants.FEATURE_CHANNEL.toString());
public static final Identifier HELLO_CHANNEL = new Identifier(MidnightControlsConstants.HELLO_CHANNEL.toString());
public static boolean isExtrasLoaded;
public final Logger logger = LogManager.getLogger("MidnightControls");
@Override
public void onInitialize() {
INSTANCE = this;
isExtrasLoaded = FabricLoader.getInstance().isModLoaded("midnightcontrols-extra");
this.log("Initializing MidnightControls...");
ServerPlayNetworking.registerGlobalReceiver(HELLO_CHANNEL, (server, player, handler, buf, responseSender) -> {
String version = buf.readString(32);
ControlsMode.byId(buf.readString(32))
.ifPresent(controlsMode -> server
.execute(() -> PlayerChangeControlsModeCallback.EVENT.invoker().apply(player, controlsMode)));
server.execute(() -> {
ServerPlayNetworking.send(player, FEATURE_CHANNEL, this.makeFeatureBuffer(MidnightControlsFeature.HORIZONTAL_REACHAROUND));
});
});
ServerPlayNetworking.registerGlobalReceiver(CONTROLS_MODE_CHANNEL,
(server, player, handler, buf, responseSender) -> ControlsMode.byId(buf.readString(32))
.ifPresent(controlsMode -> server
.execute(() -> PlayerChangeControlsModeCallback.EVENT.invoker().apply(player, controlsMode))));
}
/**
* Prints a message to the terminal.
*
* @param info the message to print
*/
public void log(String info) {
this.logger.info("[MidnightControls] " + info);
}
/**
* Prints a warning to the terminal.
*
* @param warning the warning to print
*/
public void warn(String warning) {
this.logger.info("[MidnightControls] " + warning);
}
/**
* Returns a packet byte buffer made for the midnightcontrols:controls_mode plugin message.
*
* @param controlsMode the controls mode to send
* @return the packet byte buffer
*/
public PacketByteBuf makeControlsModeBuffer(@NotNull ControlsMode controlsMode) {
Objects.requireNonNull(controlsMode, "Controls mode cannot be null.");
return new PacketByteBuf(Unpooled.buffer()).writeString(controlsMode.getName(), 32);
}
/**
* Returns a packet byte buffer made for the midnightcontrols:feature plugin message.
*
* @param features the features data to send
* @return the packet byte buffer
*/
public PacketByteBuf makeFeatureBuffer(MidnightControlsFeature... features) {
if (features.length == 0)
throw new IllegalArgumentException("At least one feature must be provided.");
var buffer = new PacketByteBuf(Unpooled.buffer());
buffer.writeVarInt(features.length);
for (var feature : features) {
buffer.writeString(feature.getName(), 64);
buffer.writeBoolean(feature.isAllowed());
}
return buffer;
}
public PacketByteBuf makeHello(@NotNull ControlsMode controlsMode) {
var version = "";
Optional<ModContainer> container;
if ((container = FabricLoader.getInstance().getModContainer(MidnightControlsConstants.NAMESPACE)).isPresent()) {
version = container.get().getMetadata().getVersion().getFriendlyString();
}
return new PacketByteBuf(Unpooled.buffer()).writeString(version, 32).writeString(controlsMode.getName(), 32);
}
/**
* Gets the MidnightControls instance.
*
* @return the MidnightControls instance
*/
public static MidnightControls get() {
return INSTANCE;
}
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of midnightcontrols.
*
* Licensed under the MIT license. For more information,
* see the LICENSE file.
*/
package eu.midnightdust.midnightcontrols;
import net.minecraft.util.Identifier;
/**
* Represents the constants used by MidnightControls.
*
* @author LambdAurora
* @version 1.1.0
* @since 1.1.0
*/
public class MidnightControlsConstants {
public static final String NAMESPACE = "midnightcontrols";
public static final Identifier CONTROLS_MODE_CHANNEL = new Identifier(NAMESPACE, "controls_mode");
public static final Identifier FEATURE_CHANNEL = new Identifier(NAMESPACE, "feature");
public static final Identifier HELLO_CHANNEL = new Identifier("lambdacontrols", "hello");
}

View File

@@ -0,0 +1,158 @@
/*
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of midnightcontrols.
*
* Licensed under the MIT license. For more information,
* see the LICENSE file.
*/
package eu.midnightdust.midnightcontrols;
import dev.lambdaurora.spruceui.util.Nameable;
import eu.midnightdust.midnightcontrols.client.MidnightControlsConfig;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
/**
* Represents a feature.
*
* @author LambdAurora
* @version 1.5.0
* @since 1.1.0
*/
public class MidnightControlsFeature implements Nameable {
private static final List<MidnightControlsFeature> FEATURES = new ArrayList<>();
public static final MidnightControlsFeature FAST_BLOCK_PLACING = new MidnightControlsFeature("fast_block_placing", true, MidnightControlsConfig.fastBlockPlacing);
public static final MidnightControlsFeature HORIZONTAL_REACHAROUND = new MidnightControlsFeature("horizontal_reacharound", true, MidnightControlsConfig.horizontalReacharound);
public static final MidnightControlsFeature VERTICAL_REACHAROUND = new MidnightControlsFeature("vertical_reacharound", true, MidnightControlsConfig.verticalReacharound);
private final String key;
private final boolean defaultAllowed;
private boolean allowed;
private final boolean defaultEnabled;
private boolean enabled;
public MidnightControlsFeature(@NotNull String key, boolean allowed, boolean enabled) {
Objects.requireNonNull(key, "Feature key cannot be null.");
this.key = key;
this.setAllowed(this.defaultAllowed = allowed);
this.setEnabled(this.defaultEnabled = enabled);
}
public MidnightControlsFeature(@NotNull String key) {
this(key, false, false);
}
/**
* Allows the feature.
*/
public void allow() {
this.setAllowed(true);
}
/**
* Returns whether this feature is allowed.
*
* @return {@code true} if this feature is allowed, else {@code false}
*/
public boolean isAllowed() {
return MidnightControls.isExtrasLoaded && this.allowed;
}
/**
* Sets whether this feature is allowed.
*
* @param allowed {@code true} if this feature is allowed, else {@code false}
*/
public void setAllowed(boolean allowed) {
this.allowed = allowed;
}
/**
* Resets allowed state to default.
*/
public void resetAllowed() {
this.setAllowed(this.defaultAllowed);
}
/**
* Returns whether this feature is enabled.
*
* @return {@code true} if this feature is enabled, else {@code false}
*/
public boolean isEnabled() {
return this.enabled;
}
/**
* Returns whether this feature is enabled.
*
* @param enabled {@code true} if this feature is enabled, else {@code false}
*/
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
/**
* Refreshes the enabled values from the config.
*/
public static void refreshEnabled() {
MidnightControlsFeature.VERTICAL_REACHAROUND.setEnabled(MidnightControlsConfig.verticalReacharound);
MidnightControlsFeature.FAST_BLOCK_PLACING.setEnabled(MidnightControlsConfig.fastBlockPlacing);
MidnightControlsFeature.HORIZONTAL_REACHAROUND.setEnabled(MidnightControlsConfig.horizontalReacharound);
}
/**
* Returns whether this feature is available or not.
*
* @return {@code true} if this feature is available, else {@code false}
* @see #isAllowed()
* @see #isEnabled()
*/
public boolean isAvailable() {
return this.isAllowed() && this.isEnabled();
}
/**
* Resets the feature to its default values.
*/
public void reset() {
this.resetAllowed();
this.setEnabled(this.defaultEnabled);
}
@Override
public @NotNull String getName() {
return this.key;
}
public static @NotNull Optional<MidnightControlsFeature> fromName(@NotNull String key) {
Objects.requireNonNull(key, "Cannot find features with a null name.");
return FEATURES.parallelStream().filter(feature -> feature.getName().equals(key)).findFirst();
}
/**
* Resets all features to their default values.
*/
public static void resetAll() {
FEATURES.parallelStream().forEach(MidnightControlsFeature::reset);
}
/**
* Resets all features to allow state.
*/
public static void resetAllAllowed() {
FEATURES.parallelStream().forEach(MidnightControlsFeature::resetAllowed);
}
static {
FEATURES.add(FAST_BLOCK_PLACING);
FEATURES.add(HORIZONTAL_REACHAROUND);
FEATURES.add(VERTICAL_REACHAROUND);
}
}

View File

@@ -0,0 +1,48 @@
/*
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of midnightcontrols.
*
* Licensed under the MIT license. For more information,
* see the LICENSE file.
*/
package eu.midnightdust.midnightcontrols.client;
/**
* Represents a button state.
*
* @author LambdAurora
* @version 1.1.0
* @since 1.1.0
*/
public enum ButtonState {
NONE(0),
PRESS(1),
RELEASE(2),
REPEAT(3);
public final int id;
ButtonState(int id) {
this.id = id;
}
/**
* Returns whether this state is a pressed state.
*
* @return true if this state is a pressed state, else false
*/
public boolean isPressed() {
return this == PRESS || this == REPEAT;
}
/**
* Returns whether this state is an unpressed state.
*
* @return true if this state is an unpressed state, else false
*/
public boolean isUnpressed() {
return this == RELEASE || this == NONE;
}
}

View File

@@ -0,0 +1,95 @@
/*
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of midnightcontrols.
*
* Licensed under the MIT license. For more information,
* see the LICENSE file.
*/
package eu.midnightdust.midnightcontrols.client;
import net.minecraft.text.Text;
import org.aperlambda.lambdacommon.utils.Nameable;
import org.jetbrains.annotations.NotNull;
import java.util.Arrays;
import java.util.Optional;
/**
* Represents a controller type.
*
* @author LambdAurora
* @version 1.4.3
* @since 1.0.0
*/
public enum ControllerType implements Nameable {
DEFAULT(0),
DUALSHOCK(1),
DUALSENSE(2),
SWITCH(3),
XBOX_360(4),
XBOX(5),
STEAM_DECK(6),
STEAM_CONTROLLER(7),
OUYA(8),
NUMBERED(9);
private final int id;
private final Text text;
ControllerType(int id) {
this.id = id;
this.text = Text.translatable("midnightcontrols.controller_type." + this.getName());
}
ControllerType(int id, @NotNull Text text) {
this.id = id;
this.text = text;
}
/**
* Returns the controller type's identifier.
*
* @return the controller type's identifier
*/
public int getId() {
return this.id;
}
/**
* Returns the next controller type available.
*
* @return the next available controller type
*/
public @NotNull ControllerType next() {
var v = values();
if (v.length == this.ordinal() + 1)
return v[0];
return v[this.ordinal() + 1];
}
/**
* Gets the translated text of this controller type.
*
* @return the translated text of this controller type
*/
public @NotNull Text getTranslatedText() {
return this.text;
}
@Override
public @NotNull String getName() {
return this.name().toLowerCase();
}
/**
* Gets the controller type from its identifier.
*
* @param id the identifier of the controller type
* @return the controller type if found, else empty
*/
public static @NotNull Optional<ControllerType> byId(@NotNull String id) {
return Arrays.stream(values()).filter(mode -> mode.getName().equalsIgnoreCase(id)).findFirst();
}
}

View File

@@ -0,0 +1,80 @@
/*
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of midnightcontrols.
*
* Licensed under the MIT license. For more information,
* see the LICENSE file.
*/
package eu.midnightdust.midnightcontrols.client;
import net.minecraft.text.Text;
import org.aperlambda.lambdacommon.utils.Nameable;
import org.jetbrains.annotations.NotNull;
import java.util.Arrays;
import java.util.Optional;
/**
* Represents the hud side which is the side where the movements buttons are.
*
* @author LambdAurora
* @version 1.4.0
* @since 1.0.0
*/
public enum HudSide implements Nameable {
LEFT,
RIGHT;
private final Text text;
HudSide() {
this.text = Text.translatable(this.getTranslationKey());
}
/**
* Returns the next side available.
*
* @return the next available side
*/
public @NotNull HudSide next() {
var v = values();
if (v.length == this.ordinal() + 1)
return v[0];
return v[this.ordinal() + 1];
}
/**
* Returns the translation key of this hud side.
*
* @return the translation key of this hude side
*/
public @NotNull String getTranslationKey() {
return "midnightcontrols.hud_side." + this.getName();
}
/**
* Gets the translated text of this hud side.
*
* @return the translated text of this hud side
*/
public @NotNull Text getTranslatedText() {
return this.text;
}
@Override
public @NotNull String getName() {
return this.name().toLowerCase();
}
/**
* Gets the hud side from its identifier.
*
* @param id the identifier of the hud side
* @return the hud side if found, else empty
*/
public static @NotNull Optional<HudSide> byId(@NotNull String id) {
return Arrays.stream(values()).filter(mode -> mode.getName().equalsIgnoreCase(id)).findFirst();
}
}

View File

@@ -0,0 +1,270 @@
/*
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of midnightcontrols.
*
* Licensed under the MIT license. For more information,
* see the LICENSE file.
*/
package eu.midnightdust.midnightcontrols.client;
import dev.lambdaurora.spruceui.event.OpenScreenCallback;
import eu.midnightdust.midnightcontrols.ControlsMode;
import eu.midnightdust.midnightcontrols.MidnightControls;
import eu.midnightdust.midnightcontrols.MidnightControlsConstants;
import eu.midnightdust.midnightcontrols.MidnightControlsFeature;
import eu.midnightdust.midnightcontrols.client.compat.MidnightControlsCompat;
import eu.midnightdust.midnightcontrols.client.compat.VoxelMapCompat;
import eu.midnightdust.midnightcontrols.client.controller.ButtonBinding;
import eu.midnightdust.midnightcontrols.client.controller.ButtonCategory;
import eu.midnightdust.midnightcontrols.client.controller.Controller;
import eu.midnightdust.midnightcontrols.client.controller.InputManager;
import eu.midnightdust.midnightcontrols.client.gui.MidnightControlsHud;
import eu.midnightdust.midnightcontrols.client.gui.RingScreen;
import eu.midnightdust.midnightcontrols.client.gui.TouchscreenOverlay;
import eu.midnightdust.midnightcontrols.client.mixin.KeyBindingIDAccessor;
import eu.midnightdust.midnightcontrols.client.mixin.KeyBindingRegistryImplAccessor;
import eu.midnightdust.midnightcontrols.client.ring.ButtonBindingRingAction;
import eu.midnightdust.midnightcontrols.client.ring.MidnightRing;
import dev.lambdaurora.spruceui.hud.HudManager;
import net.fabricmc.api.ClientModInitializer;
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper;
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents;
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking;
import net.fabricmc.fabric.api.resource.ResourceManagerHelper;
import net.fabricmc.fabric.api.resource.ResourcePackActivationType;
import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.network.ClientPlayNetworkHandler;
import net.minecraft.client.option.KeyBinding;
import net.minecraft.client.toast.SystemToast;
import net.minecraft.client.util.InputUtil;
import net.minecraft.text.Text;
import net.minecraft.util.Identifier;
import org.jetbrains.annotations.NotNull;
import org.lwjgl.glfw.GLFW;
import java.io.File;
import java.util.Timer;
import java.util.TimerTask;
/**
* Represents the midnightcontrols client mod.
*
* @author LambdAurora
* @version 1.7.0
* @since 1.1.0
*/
public class MidnightControlsClient extends MidnightControls implements ClientModInitializer {
public static boolean lateInitDone = false;
public static boolean voxelmapInitDone = false;
private static MidnightControlsClient INSTANCE;
public static final KeyBinding BINDING_LOOK_UP = InputManager.makeKeyBinding(new Identifier(MidnightControlsConstants.NAMESPACE, "look_up"),
InputUtil.Type.KEYSYM, GLFW.GLFW_KEY_KP_8, "key.categories.movement");
public static final KeyBinding BINDING_LOOK_RIGHT = InputManager.makeKeyBinding(new Identifier(MidnightControlsConstants.NAMESPACE, "look_right"),
InputUtil.Type.KEYSYM, GLFW.GLFW_KEY_KP_6, "key.categories.movement");
public static final KeyBinding BINDING_LOOK_DOWN = InputManager.makeKeyBinding(new Identifier(MidnightControlsConstants.NAMESPACE, "look_down"),
InputUtil.Type.KEYSYM, GLFW.GLFW_KEY_KP_2, "key.categories.movement");
public static final KeyBinding BINDING_LOOK_LEFT = InputManager.makeKeyBinding(new Identifier(MidnightControlsConstants.NAMESPACE, "look_left"),
InputUtil.Type.KEYSYM, GLFW.GLFW_KEY_KP_4, "key.categories.movement");
public static final KeyBinding BINDING_RING = InputManager.makeKeyBinding(new Identifier(MidnightControlsConstants.NAMESPACE, "ring"),
InputUtil.Type.KEYSYM, InputUtil.UNKNOWN_KEY.getCode(), "key.categories.misc");
public static final Identifier CONTROLLER_BUTTONS = new Identifier(MidnightControlsConstants.NAMESPACE, "textures/gui/controller_buttons.png");
public static final Identifier CONTROLLER_EXPANDED = new Identifier(MidnightControlsConstants.NAMESPACE, "textures/gui/controller_expanded.png");
public static final Identifier CONTROLLER_AXIS = new Identifier(MidnightControlsConstants.NAMESPACE, "textures/gui/controller_axis.png");
public static final Identifier CURSOR_TEXTURE = new Identifier(MidnightControlsConstants.NAMESPACE, "textures/gui/cursor.png");
public final static File MAPPINGS_FILE = new File("config/gamecontrollercustommappings.txt");
public final MidnightInput input = new MidnightInput();
public final MidnightRing ring = new MidnightRing(this);
public final MidnightReacharound reacharound = new MidnightReacharound();
private MidnightControlsHud hud;
private ControlsMode previousControlsMode;
@Override
public void onInitializeClient() {
INSTANCE = this;
KeyBindingHelper.registerKeyBinding(BINDING_LOOK_UP);
KeyBindingHelper.registerKeyBinding(BINDING_LOOK_RIGHT);
KeyBindingHelper.registerKeyBinding(BINDING_LOOK_DOWN);
KeyBindingHelper.registerKeyBinding(BINDING_LOOK_LEFT);
KeyBindingHelper.registerKeyBinding(BINDING_RING);
this.ring.registerAction("buttonbinding", ButtonBindingRingAction.FACTORY);
ClientPlayNetworking.registerGlobalReceiver(CONTROLS_MODE_CHANNEL, (client, handler, buf, responseSender) ->
responseSender.sendPacket(CONTROLS_MODE_CHANNEL, this.makeControlsModeBuffer(MidnightControlsConfig.controlsMode)));
ClientPlayNetworking.registerGlobalReceiver(FEATURE_CHANNEL, (client, handler, buf, responseSender) -> {
int features = buf.readVarInt();
for (int i = 0; i < features; i++) {
var name = buf.readString(64);
boolean allowed = buf.readBoolean();
MidnightControlsFeature.fromName(name).ifPresent(feature -> client.execute(() -> feature.setAllowed(allowed)));
}
});
ClientPlayConnectionEvents.JOIN.register((handler, sender, client) -> {
sender.sendPacket(HELLO_CHANNEL, this.makeHello(MidnightControlsConfig.controlsMode));
sender.sendPacket(CONTROLS_MODE_CHANNEL, this.makeControlsModeBuffer(MidnightControlsConfig.controlsMode));
});
ClientPlayConnectionEvents.DISCONNECT.register(this::onLeave);
ClientTickEvents.START_CLIENT_TICK.register(this.reacharound::tick);
ClientTickEvents.START_CLIENT_TICK.register(this::onTick);
OpenScreenCallback.EVENT.register((client, screen) -> {
if (screen == null && MidnightControlsConfig.controlsMode == ControlsMode.TOUCHSCREEN) {
screen = new TouchscreenOverlay(this);
screen.init(client, client.getWindow().getScaledWidth(), client.getWindow().getScaledHeight());
client.skipGameRender = false;
client.currentScreen = screen;
} else if (screen != null) {
this.input.onScreenOpen(client, client.getWindow().getWidth(), client.getWindow().getHeight());
}
});
HudManager.register(this.hud = new MidnightControlsHud(this));
FabricLoader.getInstance().getModContainer("midnightcontrols").ifPresent(modContainer -> {
ResourceManagerHelper.registerBuiltinResourcePack(new Identifier("midnightcontrols","bedrock"), modContainer, ResourcePackActivationType.NORMAL);
ResourceManagerHelper.registerBuiltinResourcePack(new Identifier("midnightcontrols","legacy"), modContainer, ResourcePackActivationType.NORMAL);
});
int delay = 0; // delay for 0 sec.
int period = 1; // repeat every 0.001 sec. (100 times a second)
Timer timer = new Timer();
timer.scheduleAtFixedRate(new TimerTask() {
public void run() {
input.updateCamera(MinecraftClient.getInstance());
}
}, delay, period);
}
/**
* This method is called when Minecraft is initializing.
*/
public void onMcInit(@NotNull MinecraftClient client) {
ButtonBinding.init(client.options);
MidnightControlsConfig.load();
this.hud.setVisible(MidnightControlsConfig.hudEnable);
Controller.updateMappings();
GLFW.glfwSetJoystickCallback((jid, event) -> {
if (event == GLFW.GLFW_CONNECTED) {
var controller = Controller.byId(jid);
client.getToastManager().add(new SystemToast(SystemToast.Type.TUTORIAL_HINT, Text.translatable("midnightcontrols.controller.connected", jid),
Text.literal(controller.getName())));
} else if (event == GLFW.GLFW_DISCONNECTED) {
client.getToastManager().add(new SystemToast(SystemToast.Type.TUTORIAL_HINT, Text.translatable("midnightcontrols.controller.disconnected", jid),
null));
}
this.switchControlsMode();
});
MidnightControlsCompat.init(this);
}
ButtonCategory category;
/**
* This method is called to initialize keybindings
*/
public void initKeybindings() {
if (!voxelmapInitDone && FabricLoader.getInstance().isModLoaded("voxelmap") && KeyBindingIDAccessor.getKEYS_BY_ID().containsKey("key.minimap.toggleingamewaypoints")) {
this.log("Adding VoxelMap compatibility...");
new VoxelMapCompat().handle(this);
InputManager.loadButtonBindings();
voxelmapInitDone = true;
}
if (lateInitDone) return;
if (KeyBindingRegistryImplAccessor.getMODDED_KEY_BINDINGS() == null || KeyBindingRegistryImplAccessor.getMODDED_KEY_BINDINGS().isEmpty()) return;
for (int i = 0; i < KeyBindingRegistryImplAccessor.getMODDED_KEY_BINDINGS().size(); ++i) {
KeyBinding keyBinding = KeyBindingRegistryImplAccessor.getMODDED_KEY_BINDINGS().get(i);
if (!keyBinding.getTranslationKey().contains("midnightcontrols") && !keyBinding.getTranslationKey().contains("ok_zoomer") && !keyBinding.getTranslationKey().contains("okzoomer")) {
category = null;
InputManager.streamCategories().forEach(buttonCategory -> {
if (buttonCategory.getIdentifier().equals(new org.aperlambda.lambdacommon.Identifier("minecraft", keyBinding.getCategory())))
category = buttonCategory;
});
if (category == null) {
category = new ButtonCategory(new org.aperlambda.lambdacommon.Identifier("minecraft", keyBinding.getCategory()));
InputManager.registerCategory(category);
}
ButtonBinding buttonBinding = new ButtonBinding.Builder(keyBinding.getTranslationKey()).category(category).linkKeybind(keyBinding).register();
if (MidnightControlsConfig.debug) {
logger.info(keyBinding.getTranslationKey());
logger.info(buttonBinding);
}
}
}
InputManager.loadButtonBindings();
lateInitDone = true;
}
/**
* This method is called every Minecraft tick.
*
* @param client the client instance
*/
public void onTick(@NotNull MinecraftClient client) {
this.initKeybindings();
this.input.tick(client);
if (MidnightControlsConfig.controlsMode == ControlsMode.CONTROLLER && (client.isWindowFocused() || MidnightControlsConfig.unfocusedInput))
this.input.tickController(client);
if (BINDING_RING.wasPressed()) {
ring.loadFromUnbound();
client.setScreen(new RingScreen());
}
if (client.world != null && MidnightControlsConfig.enableHints && !MidnightControlsConfig.autoSwitchMode && MidnightControlsConfig.controlsMode == ControlsMode.DEFAULT && MidnightControlsConfig.getController().isGamepad()) {
client.getToastManager().add(SystemToast.create(client, SystemToast.Type.PERIODIC_NOTIFICATION, Text.translatable("midnightcontrols.controller.tutorial.title"),
Text.translatable("midnightcontrols.controller.tutorial.description", Text.translatable("options.title"), Text.translatable("controls.title"),
Text.translatable("midnightcontrols.menu.title.controller"))));
MidnightControlsConfig.enableHints = false;
MidnightControlsConfig.save();
}
}
public void onRender(MinecraftClient client) {
//this.input.onRender(client);
}
/**
* Called when leaving a server.
*/
public void onLeave(ClientPlayNetworkHandler handler, MinecraftClient client) {
MidnightControlsFeature.resetAllAllowed();
}
/**
* Switches the controls mode if the auto switch is enabled.
*/
public void switchControlsMode() {
if (MidnightControlsConfig.autoSwitchMode) {
if (MidnightControlsConfig.getController().isGamepad()) {
this.previousControlsMode = MidnightControlsConfig.controlsMode;
MidnightControlsConfig.controlsMode = ControlsMode.CONTROLLER;
} else {
if (this.previousControlsMode == null) {
this.previousControlsMode = ControlsMode.DEFAULT;
}
MidnightControlsConfig.controlsMode = this.previousControlsMode;
}
}
}
/**
* Sets whether the HUD is enabled or not.
*
* @param enabled true if the HUD is enabled, else false
*/
public void setHudEnabled(boolean enabled) {
MidnightControlsConfig.hudEnable = enabled;
this.hud.setVisible(enabled);
}
/**
* Gets the midnightcontrols client instance.
*
* @return the midnightcontrols client instance
*/
public static MidnightControlsClient get() {
return INSTANCE;
}
}

View File

@@ -0,0 +1,361 @@
/*
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of midnightcontrols.
*
* Licensed under the MIT license. For more information,
* see the LICENSE file.
*/
package eu.midnightdust.midnightcontrols.client;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import eu.midnightdust.lib.config.MidnightConfig;
import eu.midnightdust.midnightcontrols.ControlsMode;
import eu.midnightdust.midnightcontrols.MidnightControlsFeature;
import eu.midnightdust.midnightcontrols.client.controller.ButtonBinding;
import eu.midnightdust.midnightcontrols.client.controller.Controller;
import eu.midnightdust.midnightcontrols.client.controller.InputManager;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gui.screen.ChatScreen;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.lwjgl.glfw.GLFW;
import java.util.*;
import java.util.regex.Pattern;
import static org.lwjgl.glfw.GLFW.*;
/**
* Represents MidnightControls configuration.
*/
public class MidnightControlsConfig extends MidnightConfig {
public static boolean isEditing = false;
@Hidden @Entry public static int configVersion = 1;
// General
@Entry(name = "midnightcontrols.menu.controls_mode") public static ControlsMode controlsMode = ControlsMode.DEFAULT;
@Entry(name = "midnightcontrols.menu.auto_switch_mode") public static boolean autoSwitchMode = true;
@Entry(name = "Debug") public static boolean debug = false;
// HUD
@Entry(name = "midnightcontrols.menu.hud_enable") public static boolean hudEnable = true;
@Entry(name = "midnightcontrols.menu.hud_side") public static HudSide hudSide = HudSide.LEFT;
@Entry(name = "midnightcontrols.menu.move_chat") public static boolean moveChat = false;
// Gameplay
@Entry(name = "midnightcontrols.menu.analog_movement") public static boolean analogMovement = true;
@Entry(name = "midnightcontrols.menu.double_tap_to_sprint") public static boolean doubleTapToSprint = true;
@Entry(name = "midnightcontrols.menu.controller_toggle_sneak") public static boolean controllerToggleSneak = MinecraftClient.getInstance().options.getSneakToggled().getValue();
@Entry(name = "midnightcontrols.menu.controller_toggle_sprint") public static boolean controllerToggleSprint = MinecraftClient.getInstance().options.getSprintToggled().getValue();
@Entry(name = "midnightcontrols.menu.fast_block_placing") public static boolean fastBlockPlacing = false; // Disabled by default as this behaviour can be considered cheating on multiplayer servers.
@Entry(name = "midnightcontrols.menu.fly_drifting") public static boolean flyDrifting = true; // Enabled by default as disabling this behaviour can be considered cheating on multiplayer servers. It can also conflict with some other mods.
@Entry(name = "midnightcontrols.menu.fly_drifting_vertical") public static boolean verticalFlyDrifting = true; // Enabled by default as disabling this behaviour can be considered cheating on multiplayer servers.
@Entry(name = "midnightcontrols.menu.reacharound.horizontal") public static boolean horizontalReacharound = false; // Disabled by default as this behaviour can be considered cheating on multiplayer servers.
@Entry(name = "midnightcontrols.menu.reacharound.vertical") public static boolean verticalReacharound = false; // Disabled by default as this behaviour can be considered cheating on multiplayer servers.
@Entry(name = "Reacharound Outline") public static boolean shouldRenderReacharoundOutline = true;
@Entry(name = "Reacharound Outline Color (WIP)", isColor = true) public static String reacharoundOutlineColorHex = "#ffffff";
@Entry(name = "Reacharound Outline Alpha", min = 0, max = 255) public static int reacharoundOutlineColorAlpha = 102;
@Entry(name = "midnightcontrols.menu.right_dead_zone") public static double rightDeadZone = 0.25;
@Entry(name = "midnightcontrols.menu.left_dead_zone") public static double leftDeadZone = 0.25;
@Entry(name = "midnightcontrols.menu.invert_right_y_axis") public static boolean invertRightYAxis = false;
@Entry(name = "midnightcontrols.menu.invert_right_x_axis") public static boolean invertRightXAxis = false;
@Entry(name = "midnightcontrols.menu.rotation_speed") public static double rotationSpeed = 40.0; //used for x-axis, name kept for compatability
@Entry(name = "midnightcontrols.menu.y_axis_rotation_speed") public static double yAxisRotationSpeed = rotationSpeed;
@Entry(name = "midnightcontrols.menu.mouse_speed") public static double mouseSpeed = 25.0;
@Entry(name = "midnightcontrols.menu.joystick_as_mouse") public static boolean joystickAsMouse = false;
@Entry(name = "midnightcontrols.menu.unfocused_input") public static boolean unfocusedInput = false;
@Entry(name = "midnightcontrols.menu.virtual_mouse") public static boolean virtualMouse = false;
@Entry(name = "midnightcontrols.menu.virtual_mouse.skin") public static VirtualMouseSkin virtualMouseSkin = VirtualMouseSkin.DEFAULT_LIGHT;
@Entry(name = "Controller ID") public static Object controllerID = 0;
@Entry(name = "2nd Controller ID") public static Object secondControllerID = -1;
@Entry(name = "midnightcontrols.menu.controller_type") public static ControllerType controllerType = ControllerType.DEFAULT;
@Entry(name = "Mouse screens") public static List<String> mouseScreens = Lists.newArrayList("me.jellysquid.mods.sodium.client.gui",
"net.coderbot.iris.gui", "net.minecraft.client.gui.screen.advancement", "net.minecraft.client.gui.screen.pack.PackScreen", "net.minecraft.class_5375",
"net.minecraft.class_457", "net.minecraft.class_408", "net.minecraft.class_3872", "me.flashyreese.mods.reeses_sodium_options.client.gui", "dev.emi.emi.screen",
"hardcorequesting.client.interfaces.GuiQuestBook", "hardcorequesting.client.interfaces.GuiReward", "hardcorequesting.client.interfaces.EditTrackerScreen",
"me.shedaniel.clothconfig2.gui.ClothConfigScreen", "com.mamiyaotaru.voxelmap.gui.GuiWaypoints", "com.mamiyaotaru.voxelmap.gui.GuiPersistentMap");
@Entry(name = "Arrow screens") public static List<String> arrowScreens = Lists.newArrayList(ChatScreen.class.getCanonicalName());
@Entry(name = "WASD screens") public static List<String> wasdScreens = Lists.newArrayList("com.ultreon.devices.core.Laptop");
@Entry(name = "Keybindings") public static Map<String, String> BINDING = new HashMap<>();
private static final Pattern BUTTON_BINDING_PATTERN = Pattern.compile("(-?\\d+)\\+?");
@Entry(name = "Max analog values") public static double[] maxAnalogValues = new double[]{1, 1, 1, 1};
@Entry(name = "Trigger button fix") public static boolean triggerFix = true;
@Entry(name = "Enable Hints") public static boolean enableHints = true;
@Entry(name = "Enable Shortcut in Controls Options") public static boolean shortcutInControls = true;
@Entry(name = "Ring Bindings (WIP)") public static List<String> ringBindings = new ArrayList<>();
@Entry(name = "Ignored Unbound Keys") public static List<String> ignoredUnboundKeys = Lists.newArrayList("inventorytabs.key.next_tab");
@Entry @Hidden public static Map<String, Map<String, String>> controllerBindingProfiles = new HashMap<>();
private static Map<String, String> currentBindingProfile = new HashMap<>();
private static Controller prevController;
/**
* Loads the configuration
*/
public static void load() {
MidnightControlsConfig.init("midnightcontrols", MidnightControlsConfig.class);
MidnightControlsClient.get().log("Configuration loaded.");
// Controller controls.
InputManager.loadButtonBindings();
}
/**
* Saves the configuration.
*/
public static void save() {
MidnightControlsConfig.write("midnightcontrols");
MidnightControlsClient.get().log("Configuration saved.");
MidnightControlsFeature.refreshEnabled();
}
public static void updateBindingsForController(Controller controller) {
if (controller.isConnected() && controller.isGamepad() && controllerBindingProfiles.containsKey(controller.getGuid()))
currentBindingProfile = controllerBindingProfiles.get(controller.getGuid());
else currentBindingProfile = Maps.newHashMap(BINDING);
InputManager.loadButtonBindings();
}
public static Map<String, String> getBindingsForController() {
return currentBindingProfile;
}
/**
* Gets the used controller.
*
* @return the controller
*/
public static Controller getController() {
var raw = MidnightControlsConfig.controllerID;
Controller controller = Controller.byId(GLFW.GLFW_JOYSTICK_1);
if (raw instanceof Number) {
controller = Controller.byId(((Number) raw).intValue());
} else if (raw instanceof String) {
controller = Controller.byGuid((String) raw).orElse(Controller.byId(GLFW.GLFW_JOYSTICK_1));
}
if ((!controller.isConnected() || !controller.isGamepad()) && MidnightControlsConfig.autoSwitchMode && !isEditing) {
for (int i = 0; i < GLFW.GLFW_JOYSTICK_LAST; ++i) {
Controller gamepad = Controller.byId(i);
if (gamepad.isConnected() && gamepad.isGamepad()) {
controller = gamepad;
i = GLFW_JOYSTICK_LAST;
}
}
}
if (controller.isConnected() && controller.isGamepad() && MidnightControlsConfig.autoSwitchMode && !isEditing) MidnightControlsConfig.controlsMode = ControlsMode.CONTROLLER;
if (prevController != controller) updateBindingsForController(controller);
prevController = controller;
return controller;
}
/**
* Sets the used controller.
*
* @param controller the controller
*/
public static void setController(Controller controller) {
MidnightControlsConfig.controllerID = controller.id();
MidnightControlsConfig.write("midnightcontrols");
}
/**
* Gets the second controller (for Joy-Con supports).
*
* @return the second controller
*/
public static Optional<Controller> getSecondController() {
var raw = MidnightControlsConfig.secondControllerID;
if (raw instanceof Number) {
if (((Number) raw).intValue() == -1)
return Optional.empty();
return Optional.of(Controller.byId(((Number) raw).intValue()));
} else if (raw instanceof String) {
return Optional.of(Controller.byGuid((String) raw).orElse(Controller.byId(GLFW.GLFW_JOYSTICK_1)));
}
return Optional.empty();
}
/**
* Sets the second controller.
*
* @param controller the second controller
*/
public static void setSecondController(@Nullable Controller controller) {
MidnightControlsConfig.secondControllerID = controller == null ? -1 : controller.id();
}
/**
* Gets the right X axis sign.
*
* @return the right X axis sign
*/
public static double getRightXAxisSign() {
return MidnightControlsConfig.invertRightXAxis ? -1.0 : 1.0;
}
/**
* Gets the right Y axis sign.
*
* @return the right Y axis sign
*/
public static double getRightYAxisSign() {
return MidnightControlsConfig.invertRightYAxis ? -1.0 : 1.0;
}
public static double getAxisMaxValue(int axis) {
if (axis >= MidnightControlsConfig.maxAnalogValues.length)
return 1;
return MidnightControlsConfig.maxAnalogValues[axis];
}
public static void setAxisMaxValue(int axis, double value) {
if (axis < MidnightControlsConfig.maxAnalogValues.length)
MidnightControlsConfig.maxAnalogValues[axis] = value;
}
/**
* Loads the button binding from configuration.
*
* @param button the button binding
*/
public static void loadButtonBinding(@NotNull ButtonBinding button) {
button.setButton(button.getDefaultButton());
var code = getBindingsForController().getOrDefault("controller.controls." + button.getName(), button.getButtonCode());
var matcher = BUTTON_BINDING_PATTERN.matcher(code);
try {
var buttons = new int[1];
int count = 0;
while (matcher.find()) {
count++;
if (count > buttons.length)
buttons = Arrays.copyOf(buttons, count);
String current;
if (!MidnightControlsConfig.checkValidity(button, code, current = matcher.group(1)))
return;
buttons[count - 1] = Integer.parseInt(current);
}
if (count == 0) {
MidnightControlsClient.get().warn("Malformed config value \"" + code + "\" for binding \"" + button.getName() + "\".");
MidnightControlsConfig.setButtonBinding(button, new int[]{-1});
}
button.setButton(buttons);
} catch (Exception e) {
MidnightControlsClient.get().warn("Malformed config value \"" + code + "\" for binding \"" + button.getName() + "\".");
setButtonBinding(button, button.getButton());
}
}
private static boolean checkValidity(@NotNull ButtonBinding binding, @NotNull String input, String group) {
if (group == null) {
MidnightControlsClient.get().warn("Malformed config value \"" + input + "\" for binding \"" + binding.getName() + "\".");
setButtonBinding(binding, binding.getButton());
return false;
}
return true;
}
/**
* Sets the button binding in configuration.
*
* @param binding the button binding
* @param button the button
*/
public static void setButtonBinding(@NotNull ButtonBinding binding, int[] button) {
binding.setButton(button);
getBindingsForController().put("controller.controls." + binding.getName(), binding.getButtonCode());
if (controllerBindingProfiles.containsKey(getController().getGuid())) controllerBindingProfiles.get(getController().getGuid()).put("controller.controls." + binding.getName(), binding.getButtonCode());
else BINDING.put("controller.controls." + binding.getName(), binding.getButtonCode());
}
public static boolean isBackButton(int btn, boolean isBtn, int state) {
if (!isBtn && state == 0)
return false;
return ButtonBinding.axisAsButton(GLFW_GAMEPAD_AXIS_LEFT_Y, false) == ButtonBinding.axisAsButton(btn, state == 1);
}
public static boolean isForwardButton(int btn, boolean isBtn, int state) {
if (!isBtn && state == 0)
return false;
return ButtonBinding.axisAsButton(GLFW_GAMEPAD_AXIS_LEFT_Y, true) == ButtonBinding.axisAsButton(btn, state == 1);
}
public static boolean isLeftButton(int btn, boolean isBtn, int state) {
if (!isBtn && state == 0)
return false;
return ButtonBinding.axisAsButton(GLFW_GAMEPAD_AXIS_LEFT_X, false) == ButtonBinding.axisAsButton(btn, state == 1);
}
public static boolean isRightButton(int btn, boolean isBtn, int state) {
if (!isBtn && state == 0)
return false;
return ButtonBinding.axisAsButton(GLFW_GAMEPAD_AXIS_LEFT_X, true) == ButtonBinding.axisAsButton(btn, state == 1);
}
/**
* Returns whether the specified axis is an axis used for movements.
*
* @param axis the axis index
* @return true if the axis is used for movements, else false
*/
public static boolean isMovementAxis(int axis) {
return axis == GLFW_GAMEPAD_AXIS_LEFT_Y || axis == GLFW_GAMEPAD_AXIS_LEFT_X;
}
public static void reset() {
controlsMode = ControlsMode.DEFAULT;
autoSwitchMode = true;
debug = false;
hudEnable = true;
hudSide = HudSide.LEFT;
analogMovement = true;
doubleTapToSprint = true;
controllerToggleSneak = MinecraftClient.getInstance().options.getSneakToggled().getValue();
controllerToggleSprint = MinecraftClient.getInstance().options.getSprintToggled().getValue();
fastBlockPlacing = false;
flyDrifting = true;
verticalFlyDrifting = true;
horizontalReacharound = false;
verticalReacharound = false;
shouldRenderReacharoundOutline = true;
reacharoundOutlineColorHex = "#ffffff";
reacharoundOutlineColorAlpha = 102;
rightDeadZone = 0.25;
leftDeadZone = 0.25;
invertRightYAxis = false;
invertRightXAxis = false;
rotationSpeed = 40.0;
yAxisRotationSpeed = rotationSpeed;
mouseSpeed = 25.0;
unfocusedInput = false;
virtualMouse = false;
virtualMouseSkin = VirtualMouseSkin.DEFAULT_LIGHT;
controllerID = 0;
secondControllerID = -1;
controllerType = ControllerType.DEFAULT;
mouseScreens = Lists.newArrayList("me.jellysquid.mods.sodium.client.gui", "net.coderbot.iris.gui", "net.minecraft.client.gui.screen.advancement", "net.minecraft.client.gui.screen.pack.PackScreen", "net.minecraft.class_5375", "net.minecraft.class_457", "net.minecraft.class_408", "net.minecraft.class_3872", "me.flashyreese.mods.reeses_sodium_options.client.gui", "dev.emi.emi.screen", "me.shedaniel.clothconfig2.gui.ClothConfigScreen", "com.mamiyaotaru.voxelmap.gui.GuiWaypoints", "com.mamiyaotaru.voxelmap.gui.GuiPersistentMap");
BINDING = new HashMap<>();
maxAnalogValues = new double[]{1, 1, 1, 1};
triggerFix = true;
enableHints = true;
shortcutInControls = true;
ringBindings = new ArrayList<>();
ignoredUnboundKeys = Lists.newArrayList("inventorytabs.key.next_tab");
controllerBindingProfiles = new HashMap<>();
}
/**
* Gets the controller type from the controller's identifier.
*
* @return the controller name matches a type, else empty
*/
public static @NotNull ControllerType matchControllerToType() {
String controller = getController().getName().toLowerCase();
if (controller.contains("xbox 360")) return ControllerType.XBOX_360;
else if (controller.contains("xbox") || controller.contains("afterglow")) return ControllerType.XBOX;
else if (controller.contains("steam deck")) return ControllerType.STEAM_DECK;
else if (controller.contains("steam")) return ControllerType.STEAM_CONTROLLER;
else if (controller.contains("dualsense") || controller.contains("ps5")) return ControllerType.DUALSENSE;
else if (controller.contains("dualshock") || controller.contains("ps4") || controller.contains("sony")) return ControllerType.DUALSHOCK;
else if (controller.contains("switch") || controller.contains("joy-con") || controller.contains("wii") || controller.contains("nintendo")) return ControllerType.SWITCH;
else if (controller.contains("ouya")) return ControllerType.OUYA;
else return ControllerType.DEFAULT;
}
}

View File

@@ -0,0 +1,28 @@
/*
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of midnightcontrols.
*
* Licensed under the MIT license. For more information,
* see the LICENSE file.
*/
package eu.midnightdust.midnightcontrols.client;
import com.terraformersmc.modmenu.api.ConfigScreenFactory;
import com.terraformersmc.modmenu.api.ModMenuApi;
import eu.midnightdust.midnightcontrols.client.gui.MidnightControlsSettingsScreen;
/**
* Represents the API implementation of ModMenu for midnightcontrols.
*
* @author LambdAurora
* @version 1.7.0
* @since 1.1.0
*/
public class MidnightControlsModMenu implements ModMenuApi {
@Override
public ConfigScreenFactory<?> getModConfigScreenFactory() {
return parent -> new MidnightControlsSettingsScreen(parent, false);
}
}

View File

@@ -0,0 +1,923 @@
/*
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of midnightcontrols.
*
* Licensed under the MIT license. For more information,
* see the LICENSE file.
*/
package eu.midnightdust.midnightcontrols.client;
import com.google.common.collect.ImmutableSet;
import dev.lambdaurora.spruceui.widget.AbstractSpruceWidget;
import dev.lambdaurora.spruceui.widget.container.SpruceEntryListWidget;
import eu.midnightdust.midnightcontrols.MidnightControls;
import eu.midnightdust.midnightcontrols.client.compat.*;
import eu.midnightdust.midnightcontrols.client.controller.ButtonBinding;
import eu.midnightdust.midnightcontrols.client.controller.Controller;
import eu.midnightdust.midnightcontrols.client.controller.InputManager;
import eu.midnightdust.midnightcontrols.client.gui.RingScreen;
import eu.midnightdust.midnightcontrols.client.gui.TouchscreenOverlay;
import eu.midnightdust.midnightcontrols.client.gui.widget.ControllerControlsWidget;
import eu.midnightdust.midnightcontrols.client.mixin.*;
import eu.midnightdust.midnightcontrols.client.ring.RingPage;
import eu.midnightdust.midnightcontrols.client.util.HandledScreenAccessor;
import eu.midnightdust.midnightcontrols.client.util.MouseAccessor;
import dev.lambdaurora.spruceui.navigation.NavigationDirection;
import dev.lambdaurora.spruceui.screen.SpruceScreen;
import dev.lambdaurora.spruceui.widget.AbstractSprucePressableButtonWidget;
import dev.lambdaurora.spruceui.widget.SpruceElement;
import dev.lambdaurora.spruceui.widget.SpruceLabelWidget;
import dev.lambdaurora.spruceui.widget.container.SpruceParentWidget;
import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gui.Element;
import net.minecraft.client.gui.ParentElement;
import net.minecraft.client.gui.screen.Screen;
import net.minecraft.client.gui.screen.TitleScreen;
import net.minecraft.client.gui.screen.advancement.AdvancementTab;
import net.minecraft.client.gui.screen.advancement.AdvancementsScreen;
import net.minecraft.client.gui.screen.ingame.CreativeInventoryScreen;
import net.minecraft.client.gui.screen.ingame.HandledScreen;
import net.minecraft.client.gui.screen.ingame.MerchantScreen;
import net.minecraft.client.gui.screen.ingame.StonecutterScreen;
import net.minecraft.client.gui.screen.multiplayer.MultiplayerScreen;
import net.minecraft.client.gui.screen.multiplayer.MultiplayerServerListWidget;
import net.minecraft.client.gui.screen.world.WorldListWidget;
import net.minecraft.client.gui.widget.*;
import net.minecraft.client.input.Input;
import net.minecraft.client.util.InputUtil;
import net.minecraft.screen.slot.Slot;
import net.minecraft.text.TranslatableTextContent;
import net.minecraft.util.math.MathHelper;
import org.aperlambda.lambdacommon.utils.Pair;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.lwjgl.glfw.GLFW;
import org.lwjgl.glfw.GLFWGamepadState;
import java.util.*;
import static org.lwjgl.glfw.GLFW.*;
/**
* Represents the midnightcontrols' input handler.
*
* @author LambdAurora
* @version 1.7.0
* @since 1.0.0
*/
public class MidnightInput {
private static final Map<Integer, Integer> BUTTON_COOLDOWNS = new HashMap<>();
// Cooldowns
public int actionGuiCooldown = 0;
public boolean ignoreNextARelease = false;
public boolean ignoreNextXRelease = false;
private double targetYaw = 0.0;
private double targetPitch = 0.0;
private float prevXAxis = 0.f;
private float prevYAxis = 0.f;
private int targetMouseX = 0;
private int targetMouseY = 0;
private float mouseSpeedX = 0.f;
private float mouseSpeedY = 0.f;
public int inventoryInteractionCooldown = 0;
public int screenCloseCooldown = 0;
private ControllerControlsWidget controlsInput = null;
public MidnightInput() {}
/**
* This method is called every Minecraft tick.
*
* @param client the client instance
*/
public void tick(@NotNull MinecraftClient client) {
this.targetYaw = 0.F;
this.targetPitch = 0.F;
// Handles the key bindings.
if (MidnightControlsClient.BINDING_LOOK_UP.isPressed()) {
this.handleLook(client, GLFW_GAMEPAD_AXIS_RIGHT_Y, 0.8F, 2);
} else if (MidnightControlsClient.BINDING_LOOK_DOWN.isPressed()) {
this.handleLook(client, GLFW_GAMEPAD_AXIS_RIGHT_Y, 0.8F, 1);
}
if (MidnightControlsClient.BINDING_LOOK_LEFT.isPressed()) {
this.handleLook(client, GLFW_GAMEPAD_AXIS_RIGHT_X, 0.8F, 2);
} else if (MidnightControlsClient.BINDING_LOOK_RIGHT.isPressed()) {
this.handleLook(client, GLFW_GAMEPAD_AXIS_RIGHT_X, 0.8F, 1);
}
InputManager.INPUT_MANAGER.tick(client);
}
/**
* This method is called every Minecraft tick for controller input update.
*
* @param client the client instance
*/
public void tickController(@NotNull MinecraftClient client) {
BUTTON_COOLDOWNS.entrySet().stream().filter(entry -> entry.getValue() > 0)
.forEach(entry -> BUTTON_COOLDOWNS.put(entry.getKey(), entry.getValue() - 1));
// Decreases the cooldown for GUI actions.
if (this.actionGuiCooldown > 0)
--this.actionGuiCooldown;
if (this.screenCloseCooldown > 0)
--this.screenCloseCooldown;
InputManager.updateStates();
var controller = MidnightControlsConfig.getController();
if (controller.isConnected()) {
var state = controller.getState();
this.fetchButtonInput(client, state, false);
this.fetchAxeInput(client, state, false);
}
MidnightControlsConfig.getSecondController().filter(Controller::isConnected)
.ifPresent(joycon -> {
GLFWGamepadState state = joycon.getState();
this.fetchButtonInput(client, state, true);
this.fetchAxeInput(client, state, true);
});
boolean allowInput = this.controlsInput == null || this.controlsInput.focusedBinding == null;
if (allowInput)
InputManager.updateBindings(client);
if (this.controlsInput != null
&& InputManager.STATES.int2ObjectEntrySet().parallelStream().map(Map.Entry::getValue).allMatch(ButtonState::isUnpressed)) {
if (this.controlsInput.focusedBinding != null && !this.controlsInput.waiting) {
int[] buttons = new int[this.controlsInput.currentButtons.size()];
for (int i = 0; i < this.controlsInput.currentButtons.size(); i++)
buttons[i] = this.controlsInput.currentButtons.get(i);
this.controlsInput.finishBindingEdit(buttons);
this.controlsInput = null;
}
}
if (this.inventoryInteractionCooldown > 0)
this.inventoryInteractionCooldown--;
}
/**
* This method is called before the screen is rendered.
*
* @param client the client instance
* @param screen the screen to render
*/
public void onPreRenderScreen(@NotNull MinecraftClient client, @NotNull Screen screen) {
if (!isScreenInteractive(screen)) {
InputManager.INPUT_MANAGER.updateMousePosition(client);
}
}
/**
* This method is called to update the camera
*
* @param client the client instance
*/
public void updateCamera(@NotNull MinecraftClient client) {
if (!(client.currentScreen == null || client.currentScreen instanceof TouchscreenOverlay))
return;
var player = client.player;
if (player == null)
return;
if (this.targetYaw != 0.f || this.targetPitch != 0.f) {
float rotationYaw = (float) (client.player.prevYaw + (this.targetYaw * 0.175));
float rotationPitch = (float) (client.player.prevPitch + (this.targetPitch * 0.175));
client.player.prevYaw = rotationYaw;
client.player.prevPitch = MathHelper.clamp(rotationPitch, -90.f, 90.f);
client.player.setYaw(rotationYaw);
client.player.setPitch(MathHelper.clamp(rotationPitch, -90.f, 90.f));
if (client.player.isRiding() && client.player.getVehicle() != null) {
client.player.getVehicle().onPassengerLookAround(client.player);
}
client.getTutorialManager().onUpdateMouse(this.targetPitch, this.targetYaw);
MidnightControlsCompat.HANDLERS.forEach(handler -> handler.handleCamera(client, targetYaw, targetPitch));
this.onRender(client);
}
}
/**
* This method is deprecated and will be removed in future versions
* It is just kept, because the current version of 'Do a Barrel Roll' mixins into this method
*
* @param client the client instance
*/
@Deprecated
public void onRender(@NotNull MinecraftClient client) {
}
/**
* This method is called when a Screen is opened.
*
* @param client the client instance
* @param windowWidth the window width
* @param windowHeight the window height
*/
public void onScreenOpen(@NotNull MinecraftClient client, int windowWidth, int windowHeight) {
if (client.currentScreen == null) {
this.mouseSpeedX = this.mouseSpeedY = 0.0F;
InputManager.INPUT_MANAGER.resetMousePosition(windowWidth, windowHeight);
} else if (isScreenInteractive(client.currentScreen) && MidnightControlsConfig.virtualMouse) {
((MouseAccessor) client.mouse).midnightcontrols$onCursorPos(client.getWindow().getHandle(), 0, 0);
InputManager.INPUT_MANAGER.resetMouseTarget(client);
}
this.inventoryInteractionCooldown = 5;
}
public void beginControlsInput(ControllerControlsWidget widget) {
this.controlsInput = widget;
if (widget != null) {
this.controlsInput.currentButtons.clear();
this.controlsInput.waiting = true;
}
}
private void fetchButtonInput(@NotNull MinecraftClient client, @NotNull GLFWGamepadState gamepadState, boolean leftJoycon) {
var buffer = gamepadState.buttons();
for (int i = 0; i < buffer.limit(); i++) {
int btn = leftJoycon ? ButtonBinding.controller2Button(i) : i;
boolean btnState = buffer.get() == (byte) 1;
var state = ButtonState.NONE;
var previousState = InputManager.STATES.getOrDefault(btn, ButtonState.NONE);
if (btnState != previousState.isPressed()) {
state = btnState ? ButtonState.PRESS : ButtonState.RELEASE;
this.handleButton(client, btn, btnState ? 0 : 1, btnState);
if (btnState)
BUTTON_COOLDOWNS.put(btn, 5);
} else if (btnState) {
state = ButtonState.REPEAT;
if (BUTTON_COOLDOWNS.getOrDefault(btn, 0) == 0) {
BUTTON_COOLDOWNS.put(btn, 5);
this.handleButton(client, btn, 2, true);
}
}
InputManager.STATES.put(btn, state);
}
}
private void fetchAxeInput(@NotNull MinecraftClient client, @NotNull GLFWGamepadState gamepadState, boolean leftJoycon) {
var buffer = gamepadState.axes();
for (int i = 0; i < buffer.limit(); i++) {
int axis = leftJoycon ? ButtonBinding.controller2Button(i) : i;
float value = buffer.get();
float absValue = Math.abs(value);
if (i == GLFW.GLFW_GAMEPAD_AXIS_LEFT_Y)
value *= -1.0F;
int state = value > MidnightControlsConfig.rightDeadZone ? 1 : (value < -MidnightControlsConfig.rightDeadZone ? 2 : 0);
if (!(client.currentScreen instanceof RingScreen || (MidnightControlsCompat.isEmotecraftPresent() && EmotecraftCompat.isEmotecraftScreen(client.currentScreen)))) this.handleAxe(client, axis, value, absValue, state);
}
if (client.currentScreen instanceof RingScreen || (MidnightControlsCompat.isEmotecraftPresent() && EmotecraftCompat.isEmotecraftScreen(client.currentScreen))) {
float x = Math.abs(buffer.get(GLFW_GAMEPAD_AXIS_LEFT_X)) > MidnightControlsConfig.leftDeadZone ? buffer.get(GLFW_GAMEPAD_AXIS_LEFT_X) : 0;
float y = Math.abs(buffer.get(GLFW_GAMEPAD_AXIS_LEFT_Y)) > MidnightControlsConfig.leftDeadZone ? buffer.get(GLFW_GAMEPAD_AXIS_LEFT_Y) : 0;
if (x == 0 && y == 0) {
x = Math.abs(buffer.get(GLFW_GAMEPAD_AXIS_RIGHT_X)) > MidnightControlsConfig.rightDeadZone ? buffer.get(GLFW_GAMEPAD_AXIS_RIGHT_X) : 0;
y = Math.abs(buffer.get(GLFW_GAMEPAD_AXIS_RIGHT_Y)) > MidnightControlsConfig.rightDeadZone ? buffer.get(GLFW_GAMEPAD_AXIS_RIGHT_Y) : 0;
}
int index = -1;
if (x < 0) {
if (y < 0) index = 0;
if (y == 0) index = 3;
if (y > 0) index = 5;
} else if (x == 0) {
if (y < 0) index = 1;
if (y > 0) index = 6;
} else if (x > 0) {
if (y < 0) index = 2;
if (y == 0) index = 4;
if (y > 0) index = 7;
}
if (client.currentScreen instanceof RingScreen && index > -1) RingPage.selected = index;
if (MidnightControlsCompat.isEmotecraftPresent() && EmotecraftCompat.isEmotecraftScreen(client.currentScreen)) EmotecraftCompat.handleEmoteSelector(index);
}
}
public void handleButton(@NotNull MinecraftClient client, int button, int action, boolean state) {
if (this.controlsInput != null && this.controlsInput.focusedBinding != null) {
if (action == 0 && !this.controlsInput.currentButtons.contains(button)) {
this.controlsInput.currentButtons.add(button);
var buttons = new int[this.controlsInput.currentButtons.size()];
for (int i = 0; i < this.controlsInput.currentButtons.size(); i++)
buttons[i] = this.controlsInput.currentButtons.get(i);
this.controlsInput.focusedBinding.setButton(buttons);
this.controlsInput.waiting = false;
}
return;
}
if (client.currentScreen != null && (action == 0 || action == 2) && button == GLFW_GAMEPAD_BUTTON_Y &&
MidnightControlsConfig.arrowScreens.contains(client.currentScreen.getClass().getCanonicalName())) {
pressKeyboardKey(client, GLFW.GLFW_KEY_ENTER);
this.screenCloseCooldown = 5;
}
else if (action == 0 || action == 2) {
if (client.currentScreen != null
&& (button == GLFW.GLFW_GAMEPAD_BUTTON_DPAD_UP || button == GLFW.GLFW_GAMEPAD_BUTTON_DPAD_DOWN
|| button == GLFW.GLFW_GAMEPAD_BUTTON_DPAD_LEFT || button == GLFW.GLFW_GAMEPAD_BUTTON_DPAD_RIGHT)) {
if (this.actionGuiCooldown == 0) {
if (MidnightControlsConfig.arrowScreens.contains(client.currentScreen.getClass().getCanonicalName())) {
switch (button) {
case GLFW_GAMEPAD_BUTTON_DPAD_UP -> pressKeyboardKey(client, GLFW.GLFW_KEY_UP);
case GLFW_GAMEPAD_BUTTON_DPAD_DOWN -> pressKeyboardKey(client, GLFW.GLFW_KEY_DOWN);
case GLFW_GAMEPAD_BUTTON_DPAD_LEFT -> pressKeyboardKey(client, GLFW.GLFW_KEY_LEFT);
case GLFW_GAMEPAD_BUTTON_DPAD_RIGHT -> pressKeyboardKey(client, GLFW.GLFW_KEY_RIGHT);
}
}
else if (MidnightControlsConfig.wasdScreens.contains(client.currentScreen.getClass().getCanonicalName())) {
switch (button) {
case GLFW_GAMEPAD_BUTTON_DPAD_UP -> pressKeyboardKey(client, GLFW.GLFW_KEY_W);
case GLFW_GAMEPAD_BUTTON_DPAD_DOWN -> pressKeyboardKey(client, GLFW.GLFW_KEY_S);
case GLFW_GAMEPAD_BUTTON_DPAD_LEFT -> pressKeyboardKey(client, GLFW.GLFW_KEY_A);
case GLFW_GAMEPAD_BUTTON_DPAD_RIGHT -> pressKeyboardKey(client, GLFW.GLFW_KEY_D);
}
}
else if (button == GLFW.GLFW_GAMEPAD_BUTTON_DPAD_UP) {
this.changeFocus(client.currentScreen, NavigationDirection.UP);
} else if (button == GLFW.GLFW_GAMEPAD_BUTTON_DPAD_DOWN) {
this.changeFocus(client.currentScreen, NavigationDirection.DOWN);
} else this.handleLeftRight(client.currentScreen, button != GLFW.GLFW_GAMEPAD_BUTTON_DPAD_LEFT);
}
return;
}
}
if (action == 1) {
if (button == GLFW.GLFW_GAMEPAD_BUTTON_A && client.currentScreen != null) {
if (this.actionGuiCooldown == 0) {
var focused = client.currentScreen.getFocused();
if (focused != null && isScreenInteractive(client.currentScreen)) {
if (this.handleAButton(client.currentScreen, focused)) {
this.actionGuiCooldown = 5; // Prevent to press too quickly the focused element, so we have to skip 5 ticks.
return;
}
}
else if (FabricLoader.getInstance().isModLoaded("libgui")) LibGuiCompat.handlePress(client.currentScreen);
}
}
}
if (button == GLFW.GLFW_GAMEPAD_BUTTON_A && client.currentScreen != null && !isScreenInteractive(client.currentScreen)
&& this.actionGuiCooldown == 0) {
if (client.currentScreen instanceof HandledScreen<?> handledScreen && ((HandledScreenAccessor) handledScreen).midnightcontrols$getSlotAt(
client.mouse.getX() * (double) client.getWindow().getScaledWidth() / (double) client.getWindow().getWidth(),
client.mouse.getY() * (double) client.getWindow().getScaledHeight() / (double) client.getWindow().getHeight()) != null) return;
if (!this.ignoreNextARelease) {
double mouseX = client.mouse.getX() * (double) client.getWindow().getScaledWidth() / (double) client.getWindow().getWidth();
double mouseY = client.mouse.getY() * (double) client.getWindow().getScaledHeight() / (double) client.getWindow().getHeight();
if (action == 0) {
Screen.wrapScreenError(() -> {
((MouseAccessor) client.mouse).setLeftButtonClicked(false);
client.currentScreen.mouseClicked(mouseX, mouseY, GLFW.GLFW_MOUSE_BUTTON_1);
},
"mouseClicked event handler", client.currentScreen.getClass().getCanonicalName());
} else if (action == 1) {
Screen.wrapScreenError(() -> {
((MouseAccessor) client.mouse).setLeftButtonClicked(false);
client.currentScreen.mouseReleased(mouseX, mouseY, GLFW.GLFW_MOUSE_BUTTON_1);
},
"mouseReleased event handler", client.currentScreen.getClass().getCanonicalName());
} else if (action == 2) {
Screen.wrapScreenError(() -> {
client.currentScreen.setDragging(true);
((MouseAccessor) client.mouse).setLeftButtonClicked(true);
((MouseAccessor) client.mouse).midnightcontrols$onCursorPos(client.getWindow().getHandle(), client.mouse.getX(), client.mouse.getY());
client.currentScreen.setDragging(false);
},
"mouseClicked event handler", client.currentScreen.getClass().getCanonicalName());
}
this.screenCloseCooldown = 5;
} else {
this.ignoreNextARelease = false;
}
}
else if (button == GLFW.GLFW_GAMEPAD_BUTTON_X && client.currentScreen != null && !isScreenInteractive(client.currentScreen)
&& this.actionGuiCooldown == 0) {
if (client.currentScreen instanceof HandledScreen<?> handledScreen && ((HandledScreenAccessor) handledScreen).midnightcontrols$getSlotAt(
client.mouse.getX() * (double) client.getWindow().getScaledWidth() / (double) client.getWindow().getWidth(),
client.mouse.getY() * (double) client.getWindow().getScaledHeight() / (double) client.getWindow().getHeight()) != null) return;
if (!this.ignoreNextXRelease) {
double mouseX = client.mouse.getX() * (double) client.getWindow().getScaledWidth() / (double) client.getWindow().getWidth();
double mouseY = client.mouse.getY() * (double) client.getWindow().getScaledHeight() / (double) client.getWindow().getHeight();
if (action == 0) {
Screen.wrapScreenError(() -> client.currentScreen.mouseClicked(mouseX, mouseY, GLFW.GLFW_MOUSE_BUTTON_2),
"mouseClicked event handler", client.currentScreen.getClass().getCanonicalName());
} else if (action == 1) {
Screen.wrapScreenError(() -> client.currentScreen.mouseReleased(mouseX, mouseY, GLFW.GLFW_MOUSE_BUTTON_2),
"mouseReleased event handler", client.currentScreen.getClass().getCanonicalName());
}
this.screenCloseCooldown = 5;
} else {
this.ignoreNextXRelease = false;
}
}
}
public void pressKeyboardKey(MinecraftClient client, int key) {
client.keyboard.onKey(client.getWindow().getHandle(), key, 0, 1, 0);
}
/**
/**
* Tries to go back.
*
* @param screen the current screen
* @return true if successful, else false
*/
public boolean tryGoBack(@NotNull Screen screen) {
var set = ImmutableSet.of("gui.back", "gui.done", "gui.cancel", "gui.toTitle", "gui.toMenu");
return screen.children().stream().filter(element -> element instanceof PressableWidget)
.map(element -> (PressableWidget) element)
.filter(element -> element.getMessage() != null && element.getMessage().getContent() != null)
.anyMatch(element -> {
if (element.getMessage().getContent() instanceof TranslatableTextContent translatableText) {
if (set.stream().anyMatch(key -> translatableText.getKey().equals(key))) {
element.onPress();
return true;
}
}
return false;
});
}
private double getDeadZoneValue(int axis) {
return (axis == GLFW_GAMEPAD_AXIS_LEFT_X || axis == GLFW_GAMEPAD_AXIS_LEFT_Y) ? MidnightControlsConfig.leftDeadZone
: MidnightControlsConfig.rightDeadZone;
}
private void handleAxe(@NotNull MinecraftClient client, int axis, float value, float absValue, int state) {
int asButtonState = value > .5f ? 1 : (value < -.5f ? 2 : 0);
if (axis == GLFW_GAMEPAD_AXIS_LEFT_TRIGGER || axis == GLFW_GAMEPAD_AXIS_RIGHT_TRIGGER
|| axis == ButtonBinding.controller2Button(GLFW.GLFW_GAMEPAD_AXIS_LEFT_TRIGGER)
|| axis == ButtonBinding.controller2Button(GLFW.GLFW_GAMEPAD_AXIS_RIGHT_TRIGGER)) {
if (asButtonState == 2) {
asButtonState = 0;
}
else {
// Fixes Triggers not working correctly on some controllers
if (MidnightControlsConfig.triggerFix) {
value = 1.0f;
absValue = 1.0f;
state = 1;
asButtonState = 1;
}
if (MidnightControlsConfig.debug) System.out.println(axis + " "+ value + " " + absValue + " " + state);
}
}
{
boolean currentPlusState = asButtonState == 1;
boolean currentMinusState = asButtonState == 2;
var previousPlusState = InputManager.STATES.getOrDefault(ButtonBinding.axisAsButton(axis, true), ButtonState.NONE);
var previousMinusState = InputManager.STATES.getOrDefault(ButtonBinding.axisAsButton(axis, false), ButtonState.NONE);
if (currentPlusState != previousPlusState.isPressed()) {
InputManager.STATES.put(ButtonBinding.axisAsButton(axis, true), currentPlusState ? ButtonState.PRESS : ButtonState.RELEASE);
if (currentPlusState)
BUTTON_COOLDOWNS.put(ButtonBinding.axisAsButton(axis, true), 5);
} else if (currentPlusState) {
InputManager.STATES.put(ButtonBinding.axisAsButton(axis, true), ButtonState.REPEAT);
if (BUTTON_COOLDOWNS.getOrDefault(ButtonBinding.axisAsButton(axis, true), 0) == 0) {
BUTTON_COOLDOWNS.put(ButtonBinding.axisAsButton(axis, true), 5);
}
}
if (currentMinusState != previousMinusState.isPressed()) {
InputManager.STATES.put(ButtonBinding.axisAsButton(axis, false), currentMinusState ? ButtonState.PRESS : ButtonState.RELEASE);
if (currentMinusState)
BUTTON_COOLDOWNS.put(ButtonBinding.axisAsButton(axis, false), 5);
} else if (currentMinusState) {
InputManager.STATES.put(ButtonBinding.axisAsButton(axis, false), ButtonState.REPEAT);
if (BUTTON_COOLDOWNS.getOrDefault(ButtonBinding.axisAsButton(axis, false), 0) == 0) {
BUTTON_COOLDOWNS.put(ButtonBinding.axisAsButton(axis, false), 5);
}
}
double deadZone = this.getDeadZoneValue(axis);
float axisValue = absValue < deadZone ? 0.f : (float) (absValue - deadZone);
axisValue /= (1.0 - deadZone);
axisValue = (float) Math.min(axisValue / MidnightControlsConfig.getAxisMaxValue(axis), 1);
if (currentPlusState)
InputManager.BUTTON_VALUES.put(ButtonBinding.axisAsButton(axis, true), axisValue);
else
InputManager.BUTTON_VALUES.put(ButtonBinding.axisAsButton(axis, true), 0.f);
if (currentMinusState)
InputManager.BUTTON_VALUES.put(ButtonBinding.axisAsButton(axis, false), axisValue);
else
InputManager.BUTTON_VALUES.put(ButtonBinding.axisAsButton(axis, false), 0.f);
}
double deadZone = this.getDeadZoneValue(axis);
if (this.controlsInput != null && this.controlsInput.focusedBinding != null) {
if (asButtonState != 0 && !this.controlsInput.currentButtons.contains(ButtonBinding.axisAsButton(axis, asButtonState == 1))) {
this.controlsInput.currentButtons.add(ButtonBinding.axisAsButton(axis, asButtonState == 1));
int[] buttons = new int[this.controlsInput.currentButtons.size()];
for (int i = 0; i < this.controlsInput.currentButtons.size(); i++)
buttons[i] = this.controlsInput.currentButtons.get(i);
this.controlsInput.focusedBinding.setButton(buttons);
this.controlsInput.waiting = false;
}
return;
} else if (client.currentScreen instanceof CreativeInventoryScreen creativeInventoryScreen) {
if (axis == GLFW_GAMEPAD_AXIS_RIGHT_Y) {
var accessor = (CreativeInventoryScreenAccessor) creativeInventoryScreen;
// @TODO allow rebinding to left stick
if (accessor.midnightcontrols$hasScrollbar() && absValue >= deadZone) {
creativeInventoryScreen.mouseScrolled(0.0, 0.0, -value);
}
return;
}
} else if (client.currentScreen instanceof MerchantScreen merchantScreen) {
if (axis == GLFW_GAMEPAD_AXIS_RIGHT_Y) {
// @TODO allow rebinding to left stick
if (absValue >= deadZone) {
merchantScreen.mouseScrolled(0.0, 0.0, -(value * 1.5f));
}
return;
}
} else if (client.currentScreen instanceof StonecutterScreen stonecutterScreen) {
if (axis == GLFW_GAMEPAD_AXIS_RIGHT_Y) {
// @TODO allow rebinding to left stick
if (absValue >= deadZone) {
stonecutterScreen.mouseScrolled(0.0, 0.0, -(value * 1.5f));
}
return;
}
} else if (client.currentScreen instanceof AdvancementsScreen advancementsScreen) {
if (axis == GLFW_GAMEPAD_AXIS_RIGHT_X || axis == GLFW_GAMEPAD_AXIS_RIGHT_Y) {
var accessor = (AdvancementsScreenAccessor) advancementsScreen;
if (absValue >= deadZone) {
AdvancementTab tab = accessor.getSelectedTab();
tab.move(axis == GLFW_GAMEPAD_AXIS_RIGHT_X ? -value * 5.0 : 0.0, axis == GLFW_GAMEPAD_AXIS_RIGHT_Y ? -value * 5.0 : 0.0);
}
return;
}
} else if (client.currentScreen != null) {
if (axis == GLFW_GAMEPAD_AXIS_RIGHT_Y && absValue >= deadZone) {
float finalValue = value;
if (client.currentScreen.children().stream().filter(element -> element instanceof SpruceEntryListWidget)
.map(element -> (SpruceEntryListWidget<?>) element)
.filter(AbstractSpruceWidget::isFocusedOrHovered)
.noneMatch(element -> {
element.mouseScrolled(0.0, 0.0, -finalValue);
return true;
})
&&
client.currentScreen.children().stream().filter(element -> element instanceof EntryListWidget)
.map(element -> (EntryListWidget<?>) element)
.filter(element -> element.getType().isFocused())
.noneMatch(element -> {
element.mouseScrolled(0.0, 0.0, -finalValue);
return true;
}))
{
client.currentScreen.mouseScrolled(0.0, 0.0, -(value * 1.5f));
}
return;
}
}
absValue -= deadZone;
absValue /= (1.0 - deadZone);
absValue = (float) MathHelper.clamp(absValue / MidnightControlsConfig.getAxisMaxValue(axis), 0.f, 1.f);
if (client.currentScreen == null) {
// Handles the look direction.
this.handleLook(client, axis, absValue, state);
} else {
boolean allowMouseControl = true;
if (this.actionGuiCooldown == 0 && MidnightControlsConfig.isMovementAxis(axis) && isScreenInteractive(client.currentScreen)) {
if (MidnightControlsConfig.isForwardButton(axis, false, asButtonState)) {
allowMouseControl = this.changeFocus(client.currentScreen, NavigationDirection.UP);
} else if (MidnightControlsConfig.isBackButton(axis, false, asButtonState)) {
allowMouseControl = this.changeFocus(client.currentScreen, NavigationDirection.DOWN);
} else if (MidnightControlsConfig.isLeftButton(axis, false, asButtonState)) {
allowMouseControl = this.handleLeftRight(client.currentScreen, false);
} else if (MidnightControlsConfig.isRightButton(axis, false, asButtonState)) {
allowMouseControl = this.handleLeftRight(client.currentScreen, true);
}
}
float movementX = 0.f;
float movementY = 0.f;
if (MidnightControlsConfig.isBackButton(axis, false, (value > 0 ? 1 : 2))) {
movementY = absValue;
} else if (MidnightControlsConfig.isForwardButton(axis, false, (value > 0 ? 1 : 2))) {
movementY = -absValue;
} else if (MidnightControlsConfig.isLeftButton(axis, false, (value > 0 ? 1 : 2))) {
movementX = -absValue;
} else if (MidnightControlsConfig.isRightButton(axis, false, (value > 0 ? 1 : 2))) {
movementX = absValue;
}
if (client.currentScreen != null && allowMouseControl) {
boolean moving = movementY != 0 || movementX != 0;
if (moving) {
/*
Updates the target mouse position when the initial movement stick movement is detected.
It prevents the cursor to jump to the old target mouse position if the user moves the cursor with the mouse.
*/
if (Math.abs(prevXAxis) < deadZone && Math.abs(prevYAxis) < deadZone) {
InputManager.INPUT_MANAGER.resetMouseTarget(client);
}
this.mouseSpeedX = movementX;
this.mouseSpeedY = movementY;
} else {
this.mouseSpeedX = 0.f;
this.mouseSpeedY = 0.f;
}
if (Math.abs(this.mouseSpeedX) >= .05f || Math.abs(this.mouseSpeedY) >= .05f) {
InputManager.queueMoveMousePosition(
this.mouseSpeedX * MidnightControlsConfig.mouseSpeed,
this.mouseSpeedY * MidnightControlsConfig.mouseSpeed
);
}
this.moveMouseToClosestSlot(client, client.currentScreen);
}
this.prevXAxis = movementX;
this.prevYAxis = movementY;
}
}
public boolean handleAButton(@NotNull Screen screen, @NotNull Element focused) {
if (focused instanceof PressableWidget widget) {
widget.playDownSound(MinecraftClient.getInstance().getSoundManager());
widget.onPress();
return true;
} else if (focused instanceof AbstractSprucePressableButtonWidget widget) {
widget.playDownSound();
widget.onPress();
return true;
} else if (focused instanceof SpruceLabelWidget labelWidget) {
labelWidget.onPress();
return true;
} else if (focused instanceof WorldListWidget list) {
list.getSelectedAsOptional().ifPresent(WorldListWidget.WorldEntry::play);
return true;
} else if (focused instanceof MultiplayerServerListWidget list) {
var entry = list.getSelectedOrNull();
if (entry instanceof MultiplayerServerListWidget.LanServerEntry || entry instanceof MultiplayerServerListWidget.ServerEntry) {
((MultiplayerScreen) screen).select(entry);
((MultiplayerScreen) screen).connect();
}
} else if (focused instanceof SpruceParentWidget) {
var childFocused = ((SpruceParentWidget<?>) focused).getFocused();
if (childFocused != null)
return this.handleAButton(screen, childFocused);
} else if (focused instanceof ParentElement widget) {
var childFocused = widget.getFocused();
if (childFocused != null)
return this.handleAButton(screen, childFocused);
} else if (FabricLoader.getInstance().isModLoaded("yet-another-config-lib") && YACLCompat.handleAButton(screen, focused)) {
return true;
}
return false;
}
/**
* Handles the left and right buttons.
*
* @param screen the current screen
* @param right true if the right button is pressed, else false
*/
private boolean handleLeftRight(@NotNull Screen screen, boolean right) {
if (screen instanceof SpruceScreen spruceScreen) {
spruceScreen.onNavigation(right ? NavigationDirection.RIGHT : NavigationDirection.LEFT, false);
this.actionGuiCooldown = 5;
return false;
}
if (FabricLoader.getInstance().isModLoaded("yet-another-config-lib") && YACLCompat.handleLeftRight(screen, right)) {
this.actionGuiCooldown = 5;
return false;
}
var focused = screen.getFocused();
if (focused != null)
if (this.handleRightLeftElement(focused, right))
return this.changeFocus(screen, right ? NavigationDirection.RIGHT : NavigationDirection.LEFT);
return true;
}
private boolean handleRightLeftElement(@NotNull Element element, boolean right) {
if (element instanceof SpruceElement spruceElement) {
if (spruceElement.requiresCursor())
return true;
return !spruceElement.onNavigation(right ? NavigationDirection.RIGHT : NavigationDirection.LEFT, false);
}
if (element instanceof SliderWidget slider) {
slider.keyPressed(right ? 262 : 263, 0, 0);
this.actionGuiCooldown = 2; // Prevent to press too quickly the focused element, so we have to skip 5 ticks.
return false;
} else if (element instanceof AlwaysSelectedEntryListWidget) {
((EntryListWidgetAccessor) element).midnightcontrols$moveSelection(right ? EntryListWidget.MoveDirection.DOWN : EntryListWidget.MoveDirection.UP);
return false;
} else if (element instanceof ParentElement entryList) {
var focused = entryList.getFocused();
if (focused == null)
return true;
return this.handleRightLeftElement(focused, right);
}
return true;
}
/**
* Handles the look direction input.
*
* @param client the client instance
* @param axis the axis to change
* @param value the value of the look
* @param state the state
*/
public void handleLook(@NotNull MinecraftClient client, int axis, float value, int state) {
// Handles the look direction.
if (client.player != null) {
double powValue = Math.pow(value, 2.0);
if (axis == GLFW_GAMEPAD_AXIS_RIGHT_Y) {
if (state == 2) {
this.targetPitch = -MidnightControlsConfig.getRightYAxisSign() * (MidnightControlsConfig.yAxisRotationSpeed * powValue) * 0.11D;
} else if (state == 1) {
this.targetPitch = MidnightControlsConfig.getRightYAxisSign() * (MidnightControlsConfig.yAxisRotationSpeed * powValue) * 0.11D;
}
}
if (axis == GLFW_GAMEPAD_AXIS_RIGHT_X) {
if (state == 2) {
this.targetYaw = -MidnightControlsConfig.getRightXAxisSign() * (MidnightControlsConfig.rotationSpeed * powValue) * 0.11D;
} else if (state == 1) {
this.targetYaw = MidnightControlsConfig.getRightXAxisSign() * (MidnightControlsConfig.rotationSpeed * powValue) * 0.11D;
}
}
}
}
private boolean changeFocus(@NotNull Screen screen, NavigationDirection direction) {
if (!isScreenInteractive(screen) && !screen.getClass().getCanonicalName().contains("me.jellysquid.mods.sodium.client.gui")) return false;
try {
if (screen instanceof SpruceScreen spruceScreen) {
if (spruceScreen.onNavigation(direction, false)) {
this.actionGuiCooldown = 5;
}
return false;
}
if (FabricLoader.getInstance().isModLoaded("sodium"))
SodiumCompat.handleInput(screen, direction.isLookingForward());
// This still needs some work
// ScreenAccessor accessor = (ScreenAccessor) screen;
// if (accessor.getSelected() != null && accessor.getSelected() instanceof ClickableWidget selected && accessor.getSelectables() != null) {
// //System.out.println(direction);
// if (accessor.getSelectables().size() >= 2) {
// //System.out.println(direction + " 2");
// int xDifference = Integer.MAX_VALUE;
// int yDifference = Integer.MAX_VALUE;
// ClickableWidget newWidget = null;
// for (int i = 0; i < accessor.getSelectables().size(); ++i) {
// if (accessor.getSelectables().get(i) instanceof ClickableWidget candidate) {
// if (!(candidate.x == selected.x && candidate.y == selected.y)) {
// int canXDifference = Math.abs(candidate.x - selected.x);
// int canYDifference = Math.abs(candidate.y - selected.y);
// if (direction.isHorizontal()) {
// if (direction.isLookingForward()) {
// if (candidate.x >= selected.x && canYDifference <= yDifference && canXDifference <= xDifference) {
// System.out.println(direction + " forward horizontal " + candidate);
// newWidget = candidate;
// xDifference = canXDifference;
// yDifference = canYDifference;
// }
// } else {
// if (candidate.x <= selected.x && canYDifference <= yDifference && canXDifference >= xDifference) {
// System.out.println(direction + " backward horizontal " + candidate);
// newWidget = candidate;
// xDifference = canXDifference;
// yDifference = canYDifference;
// }
// }
// } else {
// if (direction.isLookingForward()) {
// if (candidate.y >= selected.y && canYDifference <= yDifference && canXDifference <= xDifference) {
// System.out.println(direction + " forward vertical " + candidate);
// newWidget = candidate;
// xDifference = canXDifference;
// yDifference = canYDifference;
// }
// } else {
// if (candidate.y <= selected.y && canYDifference >= yDifference && canXDifference <= xDifference) {
// System.out.println(direction + " backward vertical " + candidate);
// newWidget = candidate;
// xDifference = canXDifference;
// yDifference = canYDifference;
// }
// }
// }
// }
// }
// }
// if (newWidget != null) {
// //selected.changeFocus(true);
// ((ClickableWidgetAccessor) selected).setFocused(false);
// ((ClickableWidgetAccessor) newWidget).setFocused(true);
// screen.setFocused(newWidget);
// screen.changeFocus(false);
// screen.changeFocus(true);
// this.actionGuiCooldown = 5;
// return true;
// }
//
// }
// }
if (!screen.changeFocus(direction.isLookingForward())) {
if (screen.changeFocus(direction.isLookingForward())) {
this.actionGuiCooldown = 5;
return false;
}
return true;
} else {
this.actionGuiCooldown = 5;
return false;
}
} catch (Exception exception) {MidnightControls.get().warn("Unknown exception encountered while trying to change focus: "+exception);}
return false;
}
public static boolean isScreenInteractive(@NotNull Screen screen) {
return !(screen instanceof HandledScreen || MidnightControlsConfig.joystickAsMouse || MidnightControlsConfig.mouseScreens.stream().anyMatch(a -> screen.getClass().toString().contains(a))
|| (screen instanceof SpruceScreen && ((SpruceScreen) screen).requiresCursor())
|| MidnightControlsCompat.requireMouseOnScreen(screen));
}
// Inspired from https://github.com/MrCrayfish/Controllable/blob/1.14.X/src/main/java/com/mrcrayfish/controllable/client/ControllerInput.java#L686.
private void moveMouseToClosestSlot(@NotNull MinecraftClient client, @Nullable Screen screen) {
// Makes the mouse attracted to slots. This helps with selecting items when using a controller.
if (screen instanceof HandledScreen inventoryScreen) {
var accessor = (HandledScreenAccessor) inventoryScreen;
int guiLeft = accessor.getX();
int guiTop = accessor.getY();
int mouseX = (int) (targetMouseX * (double) client.getWindow().getScaledWidth() / (double) client.getWindow().getWidth());
int mouseY = (int) (targetMouseY * (double) client.getWindow().getScaledHeight() / (double) client.getWindow().getHeight());
// Finds the closest slot in the GUI within 14 pixels.
Optional<Pair<Slot, Double>> closestSlot = inventoryScreen.getScreenHandler().slots.parallelStream()
.map(slot -> {
int x = guiLeft + slot.x + 8;
int y = guiTop + slot.y + 8;
// Distance between the slot and the cursor.
double distance = Math.sqrt(Math.pow(x - mouseX, 2) + Math.pow(y - mouseY, 2));
return Pair.of(slot, distance);
}).filter(entry -> entry.value <= 14.0)
.min(Comparator.comparingDouble(p -> p.value));
if (closestSlot.isPresent()) {
var slot = closestSlot.get().key;
if (slot.hasStack() || !client.player.getInventory().getMainHandStack().isEmpty()) {
int slotCenterXScaled = guiLeft + slot.x + 8;
int slotCenterYScaled = guiTop + slot.y + 8;
int slotCenterX = (int) (slotCenterXScaled / ((double) client.getWindow().getScaledWidth() / (double) client.getWindow().getWidth()));
int slotCenterY = (int) (slotCenterYScaled / ((double) client.getWindow().getScaledHeight() / (double) client.getWindow().getHeight()));
double deltaX = slotCenterX - targetMouseX;
double deltaY = slotCenterY - targetMouseY;
if (mouseX != slotCenterXScaled || mouseY != slotCenterYScaled) {
this.targetMouseX += deltaX * 0.75;
this.targetMouseY += deltaY * 0.75;
} else {
this.mouseSpeedX *= 0.3F;
this.mouseSpeedY *= 0.3F;
}
this.mouseSpeedX *= .75F;
this.mouseSpeedY *= .75F;
} else {
this.mouseSpeedX *= .1F;
this.mouseSpeedY *= .1F;
}
} else {
this.mouseSpeedX *= .3F;
this.mouseSpeedY *= .3F;
}
} else {
this.mouseSpeedX = 0.F;
this.mouseSpeedY = 0.F;
}
}
}

View File

@@ -0,0 +1,165 @@
/*
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of midnightcontrols.
*
* Licensed under the MIT license. For more information,
* see the LICENSE file.
*/
package eu.midnightdust.midnightcontrols.client;
import eu.midnightdust.midnightcontrols.MidnightControlsFeature;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.block.FluidBlock;
import net.minecraft.block.SlabBlock;
import net.minecraft.client.MinecraftClient;
import net.minecraft.item.BlockItem;
import net.minecraft.item.ItemStack;
import net.minecraft.util.hit.BlockHitResult;
import net.minecraft.util.hit.HitResult;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.RaycastContext;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Represents the reach-around API of midnightcontrols.
*
* @version 1.7.0
* @since 1.3.2
*/
public class MidnightReacharound {
private BlockHitResult lastReacharoundResult = null;
private boolean lastReacharoundVertical = false;
private boolean onSlab = false;
public void tick(@NotNull MinecraftClient client) {
this.lastReacharoundResult = this.tryVerticalReachAround(client);
if (this.lastReacharoundResult == null) {
this.lastReacharoundResult = this.tryHorizontalReachAround(client);
this.lastReacharoundVertical = false;
} else this.lastReacharoundVertical = true;
}
/**
* Returns the last reach around result.
*
* @return the last reach around result
*/
public @Nullable BlockHitResult getLastReacharoundResult() {
return this.lastReacharoundResult;
}
/**
* Returns whether the last reach around is vertical.
*
* @return {@code true} if the reach around is vertical
*/
public boolean isLastReacharoundVertical() {
return this.lastReacharoundVertical;
}
/**
* Returns whether reacharound is available or not.
*
* @return {@code true} if reacharound is available, else {@code false}
*/
public boolean isReacharoundAvailable() {
return MidnightControlsFeature.HORIZONTAL_REACHAROUND.isAvailable() || MidnightControlsFeature.VERTICAL_REACHAROUND.isAvailable();
}
private float getPlayerRange(@NotNull MinecraftClient client) {
return client.interactionManager != null ? client.interactionManager.getReachDistance() : 0.f;
}
/**
* Returns a nullable block hit result if vertical reach-around is possible.
*
* @param client the client instance
* @return a block hit result if vertical reach-around is possible, else {@code null}
*/
public @Nullable BlockHitResult tryVerticalReachAround(@NotNull MinecraftClient client) {
if (!MidnightControlsFeature.VERTICAL_REACHAROUND.isAvailable())
return null;
if (client.player == null || client.world == null || client.crosshairTarget == null || client.crosshairTarget.getType() != HitResult.Type.MISS
|| !client.player.isOnGround() || client.player.getPitch(0.f) < 80.0F
|| client.player.isRiding())
return null;
Vec3d pos = client.player.getCameraPosVec(1.0F);
Vec3d rotationVec = client.player.getRotationVec(1.0F);
float range = getPlayerRange(client);
var rayVec = pos.add(rotationVec.x * range, rotationVec.y * range, rotationVec.z * range).add(0, 0.75, 0);
var result = client.world.raycast(new RaycastContext(pos, rayVec, RaycastContext.ShapeType.OUTLINE, RaycastContext.FluidHandling.NONE, client.player));
if (result.getType() == HitResult.Type.BLOCK) {
BlockPos blockPos = result.getBlockPos().down();
BlockState state = client.world.getBlockState(blockPos);
if (client.player.getBlockPos().getY() - blockPos.getY() > 1 && (client.world.isAir(blockPos) || state.getMaterial().isReplaceable())) {
return new BlockHitResult(result.getPos(), Direction.DOWN, blockPos, false);
}
}
return null;
}
/**
* Returns a nullable block hit result if horizontal reach-around is possible.
*
* @param client the client instance
* @return a block hit result if horizontal reach-around is possible
*/
public @Nullable BlockHitResult tryHorizontalReachAround(@NotNull MinecraftClient client) {
if (!MidnightControlsFeature.HORIZONTAL_REACHAROUND.isAvailable())
return null;
if (client.player != null && client.crosshairTarget != null && client.crosshairTarget.getType() == HitResult.Type.MISS
&& client.player.isOnGround() && client.player.getPitch(0.f) > 35.f) {
if (client.player.isRiding())
return null;
var playerPos = client.player.getBlockPos().down();
if (client.player.getY() - playerPos.getY() - 1.0 >= 0.25) {
playerPos = playerPos.up();
this.onSlab = true;
} else {
this.onSlab = false;
}
var targetPos = new BlockPos(client.crosshairTarget.getPos()).subtract(playerPos);
var vector = new BlockPos.Mutable(MathHelper.clamp(targetPos.getX(), -1, 1), 0, MathHelper.clamp(targetPos.getZ(), -1, 1));
var blockPos = playerPos.add(vector);
var direction = client.player.getHorizontalFacing();
var state = client.world.getBlockState(blockPos);
if (!state.isAir())
return null;
var adjacentBlockState = client.world.getBlockState(blockPos.offset(direction.getOpposite()));
if (adjacentBlockState.isAir() || adjacentBlockState.getBlock() instanceof FluidBlock || (vector.getX() == 0 && vector.getZ() == 0)) {
return null;
}
return new BlockHitResult(new Vec3d(blockPos.getX(),blockPos.getY(),blockPos.getZ()), direction, blockPos, false);
}
return null;
}
public @NotNull BlockHitResult withSideForReacharound(@NotNull BlockHitResult result, @Nullable ItemStack stack) {
if (stack == null || stack.isEmpty() || !(stack.getItem() instanceof BlockItem))
return result;
return withSideForReacharound(result, Block.getBlockFromItem(stack.getItem()));
}
public @NotNull BlockHitResult withSideForReacharound(@NotNull BlockHitResult result, @NotNull Block block) {
if (block instanceof SlabBlock) {
if (this.onSlab) result = result.withSide(Direction.UP);
else result = result.withSide(Direction.DOWN);
}
return result;
}
}

View File

@@ -0,0 +1,83 @@
/*
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of midnightcontrols.
*
* Licensed under the MIT license. For more information,
* see the LICENSE file.
*/
package eu.midnightdust.midnightcontrols.client;
import net.minecraft.text.Text;
import org.aperlambda.lambdacommon.utils.Nameable;
import org.jetbrains.annotations.NotNull;
import java.util.Arrays;
import java.util.Optional;
/**
* Represents the virtual mouse skins.
*
* @version 1.7.0
* @since 1.2.0
*/
public enum VirtualMouseSkin implements Nameable {
DEFAULT_LIGHT("default_light"),
DEFAULT_DARK("default_dark"),
SECOND_LIGHT("second_light"),
SECOND_DARK("second_dark");
private final String name;
private final Text text;
VirtualMouseSkin(String name) {
this.name = name;
this.text = Text.translatable(this.getTranslationKey());
}
/**
* Returns the next virtual mouse skin available.
*
* @return the next available virtual mouse skin
*/
public @NotNull VirtualMouseSkin next() {
var v = values();
if (v.length == this.ordinal() + 1)
return v[0];
return v[this.ordinal() + 1];
}
/**
* Returns the translation key of this virtual mouse skin.
*
* @return the virtual mouse skin's translation key
*/
public @NotNull String getTranslationKey() {
return "midnightcontrols.virtual_mouse.skin." + this.getName();
}
/**
* Gets the translated text of this virtual mouse skin.
*
* @return the translated text of this virtual mouse skin
*/
public @NotNull Text getTranslatedText() {
return this.text;
}
@Override
public @NotNull String getName() {
return this.name;
}
/**
* Gets the virtual mouse skin from its identifier.
*
* @param id the identifier of the virtual mouse skin
* @return the virtual mouse skin if found, else empty
*/
public static @NotNull Optional<VirtualMouseSkin> byId(@NotNull String id) {
return Arrays.stream(values()).filter(mode -> mode.getName().equalsIgnoreCase(id)).findFirst();
}
}

View File

@@ -0,0 +1,34 @@
/*
* Copyright © 2022 Motschen <motschen@midnightdust.eu>
*
* This file is part of midnightcontrols.
*
* Licensed under the MIT license. For more information,
* see the LICENSE file.
*/
package eu.midnightdust.midnightcontrols.client.compat;
import eu.midnightdust.midnightcontrols.client.MidnightControlsClient;
import me.juancarloscp52.bedrockify.client.BedrockifyClient;
import me.juancarloscp52.bedrockify.client.BedrockifyClientSettings;
import net.minecraft.client.gui.screen.Screen;
import org.aperlambda.lambdacommon.utils.LambdaReflection;
import org.jetbrains.annotations.NotNull;
import java.util.Optional;
/**
* Represents HQM compatibility handler.
*
* @author Motschen
* @version 1.7.0
* @since 1.7.0
*/
public class BedrockifyCompat implements CompatHandler {
@Override
public void handle(@NotNull MidnightControlsClient mod) {
BedrockifyClient.getInstance().settings.disableFlyingMomentum = false;
}
}

View File

@@ -0,0 +1,113 @@
/*
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of midnightcontrols.
*
* Licensed under the MIT license. For more information,
* see the LICENSE file.
*/
package eu.midnightdust.midnightcontrols.client.compat;
import eu.midnightdust.midnightcontrols.client.MidnightControlsClient;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gui.screen.Screen;
import net.minecraft.client.gui.screen.ingame.HandledScreen;
import net.minecraft.screen.slot.Slot;
import net.minecraft.util.hit.BlockHitResult;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Represents a compatibility handler for a mod.
*
* @author LambdAurora
* @version 1.7.0
* @since 1.1.0
*/
public interface CompatHandler {
/**
* Handles compatibility of a mod.
*
* @param mod this mod instance
*/
void handle(@NotNull MidnightControlsClient mod);
/**
* Handles custom camera updates
*
* @param client the Minecraft client instance
*/
default void handleCamera(@NotNull MinecraftClient client, double targetYaw, double targetPitch) {};
/**
* Returns whether the mouse is required on the specified screen.
*
* @param screen the screen
* @return true if the mouse is required on the specified screen, else false
*/
default boolean requireMouseOnScreen(Screen screen) {
return false;
}
/**
* Returns a slot at the specified location if possible.
*
* @param screen the screen
* @param mouseX the mouse X-coordinate
* @param mouseY the mouse Y-coordinate
* @return a slot if present, else null
* @since 1.5.0
*/
default @Nullable CompatHandler.SlotPos getSlotAt(@NotNull Screen screen, int mouseX, int mouseY) {
return null;
}
/**
* Returns whether the current slot is a creative slot or not.
*
* @param screen the screen
* @param slot the slot to check
* @return true if the slot is a creative slot, else false
*/
default boolean isCreativeSlot(@NotNull HandledScreen screen, @NotNull Slot slot) {
return false;
}
/**
* Returns a custom translation key to make custom attack action strings on the HUD.
*
* @param client the client instance
* @param placeResult the last place block result
* @return null if untouched, else a translation key
*/
default String getAttackActionAt(@NotNull MinecraftClient client, @Nullable BlockHitResult placeResult) {
return null;
}
/**
* Returns a custom translation key to make custom use action strings on the HUD.
*
* @param client the client instance
* @param placeResult the last place block result
* @return null if untouched, else a translation key
*/
default String getUseActionAt(@NotNull MinecraftClient client, @Nullable BlockHitResult placeResult) {
return null;
}
/**
* Handles the menu back button.
*
* @param client the client instance
* @param screen the screen
* @return true if the handle was fired and succeed, else false
*/
default boolean handleMenuBack(@NotNull MinecraftClient client, @NotNull Screen screen) {
return false;
}
record SlotPos(int x, int y) {
public static final SlotPos INVALID_SLOT = new SlotPos(-1, -1);
}
}

View File

@@ -0,0 +1,48 @@
package eu.midnightdust.midnightcontrols.client.compat;
import dev.emi.emi.config.EmiConfig;
import dev.emi.emi.screen.EmiScreen;
import dev.emi.emi.screen.EmiScreenManager;
import eu.midnightdust.midnightcontrols.client.MidnightControlsClient;
import eu.midnightdust.midnightcontrols.client.controller.ButtonBinding;
import eu.midnightdust.midnightcontrols.client.controller.ButtonCategory;
import eu.midnightdust.midnightcontrols.client.controller.InputManager;
import net.minecraft.client.gui.screen.ingame.HandledScreen;
import org.aperlambda.lambdacommon.Identifier;
import org.jetbrains.annotations.NotNull;
import org.lwjgl.glfw.GLFW;
public class EMICompat implements CompatHandler {
public static boolean handleTabs(boolean direction) {
if (isEMIEnabled() && MidnightControlsClient.get().input.actionGuiCooldown == 0 && EmiScreenManager.getSearchPanel() != null && EmiScreenManager.getSearchPanel().pageLeft != null && EmiScreenManager.getSearchPanel().pageRight != null) {
if (direction) EmiScreenManager.getSearchPanel().pageRight.onPress();
else EmiScreenManager.getSearchPanel().pageLeft.onPress();
MidnightControlsClient.get().input.actionGuiCooldown = 5;
return true;
}
return false;
}
@Override
public void handle(@NotNull MidnightControlsClient mod) {
ButtonCategory category = new ButtonCategory(new Identifier("midnightcontrols","category.emi"));
InputManager.registerCategory(category);
new ButtonBinding.Builder("emi_page_left")
.buttons(GLFW.GLFW_GAMEPAD_BUTTON_LEFT_BUMPER, ButtonBinding.axisAsButton(GLFW.GLFW_GAMEPAD_AXIS_LEFT_TRIGGER, true))
.category(category)
.action((client,action,value,buttonState)->handleTabs(false)).cooldown()
.filter(((client, buttonBinding) -> client.currentScreen instanceof HandledScreen<?> || client.currentScreen instanceof EmiScreen))
.register();
new ButtonBinding.Builder("emi_page_right")
.buttons(GLFW.GLFW_GAMEPAD_BUTTON_RIGHT_BUMPER, ButtonBinding.axisAsButton(GLFW.GLFW_GAMEPAD_AXIS_RIGHT_TRIGGER, true))
.category(category)
.action((client,action,value,buttonState)->handleTabs(true)).cooldown()
.filter(((client, buttonBinding) -> client.currentScreen instanceof HandledScreen<?> || client.currentScreen instanceof EmiScreen))
.register();
}
public static boolean isEMIEnabled() {
return EmiConfig.enabled;
}
public static boolean isSearchBarCentered() {
return EmiConfig.centerSearchBar;
}
}

View File

@@ -0,0 +1,29 @@
package eu.midnightdust.midnightcontrols.client.compat;
import eu.midnightdust.midnightcontrols.client.controller.InputManager;
import io.github.kosmx.emotes.arch.gui.screen.ingame.FastChosseScreen;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gui.screen.Screen;
public class EmotecraftCompat {
public static boolean isEmotecraftScreen(Screen screen) {
return screen instanceof FastChosseScreen;
}
public static void handleEmoteSelector(int index) {
MinecraftClient client = MinecraftClient.getInstance();
if (client.currentScreen instanceof FastChosseScreen) {
int x = client.getWindow().getWidth() / 2;
int y = client.getWindow().getHeight() / 2;
if (index == 0) InputManager.queueMousePosition(x-200, y-200);
if (index == 1) InputManager.queueMousePosition(x, y-200);
if (index == 2) InputManager.queueMousePosition(x+200, y-200);
if (index == 3) InputManager.queueMousePosition(x-200, y);
if (index == 4) InputManager.queueMousePosition(x+200, y);
if (index == 5) InputManager.queueMousePosition(x-200, y+200);
if (index == 6) InputManager.queueMousePosition(x, y+200);
if (index == 7) InputManager.queueMousePosition(x+200, y+200);
InputManager.INPUT_MANAGER.updateMousePosition(client);
}
}
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of midnightcontrols.
*
* Licensed under the MIT license. For more information,
* see the LICENSE file.
*/
package eu.midnightdust.midnightcontrols.client.compat;
import eu.midnightdust.midnightcontrols.client.MidnightControlsClient;
import net.minecraft.client.gui.screen.Screen;
import org.aperlambda.lambdacommon.utils.LambdaReflection;
import org.jetbrains.annotations.NotNull;
import java.util.Optional;
/**
* Represents HQM compatibility handler.
* <p>
* This is bad.
*
* @author LambdAurora
* @version 1.3.2
* @since 1.3.2
*/
public class HQMCompat implements CompatHandler {
public static final String GUI_BASE_CLASS_PATH = "hardcorequesting.client.interfaces.GuiBase";
private Optional<Class<?>> guiBaseClass;
@Override
public void handle(@NotNull MidnightControlsClient mod) {
this.guiBaseClass = LambdaReflection.getClass(GUI_BASE_CLASS_PATH);
}
@Override
public boolean requireMouseOnScreen(Screen screen) {
return this.guiBaseClass.map(clazz -> clazz.isInstance(screen)).orElse(false);
}
}

View File

@@ -0,0 +1,33 @@
package eu.midnightdust.midnightcontrols.client.compat;
import com.kqp.inventorytabs.tabs.TabManager;
import net.minecraft.client.gui.screen.Screen;
import net.minecraft.client.gui.screen.ingame.CreativeInventoryScreen;
import net.minecraft.client.gui.screen.ingame.HandledScreen;
public class InventoryTabsCompat {
public static void handleInventoryTabs(Screen screen, boolean next) {
if (screen instanceof HandledScreen<?> && !(screen instanceof CreativeInventoryScreen)) {
TabManager tabManager = TabManager.getInstance();
int tabIndex = tabManager.tabs.indexOf(tabManager.currentTab);
if (next) {
if (tabIndex < tabManager.tabs.size() - 1) tabManager.onTabClick(tabManager.tabs.get(tabIndex + 1));
else tabManager.onTabClick(tabManager.tabs.get(0));
} else {
if (tabIndex > 0) tabManager.onTabClick(tabManager.tabs.get(tabIndex - 1));
else tabManager.onTabClick(tabManager.tabs.get(tabManager.tabs.size() - 1));
}
}
}
public static void handleInventoryPage(Screen screen, boolean next) {
if (screen instanceof HandledScreen<?> && !(screen instanceof CreativeInventoryScreen)) {
TabManager tabManager = TabManager.getInstance();
if (next) {
if (tabManager.canGoForwardAPage()) tabManager.setCurrentPage(tabManager.currentPage + 1);
} else {
if (tabManager.canGoBackAPage()) tabManager.setCurrentPage(tabManager.currentPage - 1);
}
}
}
}

View File

@@ -0,0 +1,25 @@
package eu.midnightdust.midnightcontrols.client.compat;
import io.github.cottonmc.cotton.gui.impl.client.CottonScreenImpl;
import io.github.cottonmc.cotton.gui.widget.WButton;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gui.screen.Screen;
import net.minecraft.client.sound.PositionedSoundInstance;
import net.minecraft.sound.SoundEvents;
import org.jetbrains.annotations.NotNull;
@SuppressWarnings("UnstableApiUsage")
public class LibGuiCompat {
public static boolean handlePress(@NotNull Screen screen) {
if (screen instanceof CottonScreenImpl cottonScreen) {
if (cottonScreen.getDescription() != null && cottonScreen.getDescription().getFocus() != null) {
if (cottonScreen.getDescription().getFocus() instanceof WButton button && button.getOnClick() != null) {
button.getOnClick().run();
MinecraftClient.getInstance().getSoundManager().play(PositionedSoundInstance.master(SoundEvents.UI_BUTTON_CLICK, 1.0F));
return true;
}
}
}
return false;
}
}

View File

@@ -0,0 +1,190 @@
/*
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of midnightcontrols.
*
* Licensed under the MIT license. For more information,
* see the LICENSE file.
*/
package eu.midnightdust.midnightcontrols.client.compat;
import eu.midnightdust.midnightcontrols.client.MidnightControlsClient;
import eu.midnightdust.midnightcontrols.client.controller.InputManager;
import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gui.screen.Screen;
import net.minecraft.util.hit.BlockHitResult;
import org.aperlambda.lambdacommon.utils.LambdaReflection;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
/**
* Represents a compatibility handler.
*
* @author LambdAurora
* @version 1.5.0
* @since 1.1.0
*/
public class MidnightControlsCompat {
public static final List<CompatHandler> HANDLERS = new ArrayList<>();
/**
* Initializes compatibility with other mods if needed.
*
* @param mod the mod instance
*/
public static void init(@NotNull MidnightControlsClient mod) {
// "okzoomer" is the mod ID used by Fabric-compatible versions of Ok Zoomer. (5.0.0-beta.6 and below.)
// "ok_zoomer" is the mod ID used by Quilt-exclusive versions of Ok Zoomer. (5.0.0-beta.7 and above.)
if (FabricLoader.getInstance().isModLoaded("okzoomer") || FabricLoader.getInstance().isModLoaded("ok_zoomer")) {
mod.log("Adding Ok Zoomer compatibility...");
HANDLERS.add(new OkZoomerCompat());
}
if (isEMIPresent()) {
mod.log("Adding EMI compatibility...");
HANDLERS.add(new EMICompat());
}
if (FabricLoader.getInstance().isModLoaded("hardcorequesting") && LambdaReflection.doesClassExist(HQMCompat.GUI_BASE_CLASS_PATH)) {
mod.log("Adding HQM compatibility...");
HANDLERS.add(new HQMCompat());
}
if (FabricLoader.getInstance().isModLoaded("bedrockify")) {
mod.log("Adding Bedrockify compatibility...");
HANDLERS.add(new BedrockifyCompat());
}
HANDLERS.forEach(handler -> handler.handle(mod));
InputManager.loadButtonBindings();
}
/**
* Registers a new compatibility handler.
*
* @param handler the compatibility handler to register
*/
public static void registerCompatHandler(@NotNull CompatHandler handler) {
HANDLERS.add(handler);
}
/**
* Streams through compatibility handlers.
*
* @return a stream of compatibility handlers
*/
public static Stream<CompatHandler> streamCompatHandlers() {
return HANDLERS.stream();
}
/**
* Returns whether the mouse is required on the specified screen.
*
* @param screen the screen
* @return true if the mouse is requried on the specified screen, else false
*/
public static boolean requireMouseOnScreen(Screen screen) {
return HANDLERS.stream().anyMatch(handler -> handler.requireMouseOnScreen(screen));
}
/**
* Returns a slot at the specified location if possible.
*
* @param screen the screen
* @param mouseX the mouse X-coordinate
* @param mouseY the mouse Y-coordinate
* @return a slot if present, else null
*/
public static @Nullable CompatHandler.SlotPos getSlotAt(@NotNull Screen screen, int mouseX, int mouseY) {
for (var handler : HANDLERS) {
var slot = handler.getSlotAt(screen, mouseX, mouseY);
if (slot != null)
return slot;
}
return null;
}
/**
* Returns a custom translation key to make custom attack action strings on the HUD.
*
* @param client the client instance
* @param placeResult the last place block result
* @return null if untouched, else a translation key
*/
public static String getAttackActionAt(@NotNull MinecraftClient client, @Nullable BlockHitResult placeResult) {
for (CompatHandler handler : HANDLERS) {
String action = handler.getAttackActionAt(client, placeResult);
if (action != null) {
return action;
}
}
return null;
}
/**
* Returns a custom translation key to make custom use action strings on the HUD.
*
* @param client the client instance
* @param placeResult the last place block result
* @return null if untouched, else a translation key
*/
public static String getUseActionAt(@NotNull MinecraftClient client, @Nullable BlockHitResult placeResult) {
for (CompatHandler handler : HANDLERS) {
String action = handler.getUseActionAt(client, placeResult);
if (action != null) {
return action;
}
}
return null;
}
/**
* Handles the menu back button.
*
* @param client the client instance
* @param screen the screen
* @return true if the handle was fired and succeed, else false
*/
public static boolean handleMenuBack(@NotNull MinecraftClient client, @NotNull Screen screen) {
for (CompatHandler handler : HANDLERS) {
if (handler.handleMenuBack(client, screen))
return true;
}
return false;
}
/**
* Returns whether Roughly Enough Items is present.
*
* @return true if Roughly Enough Items is present, else false
*/
public static boolean isReiPresent() {
return FabricLoader.getInstance().isModLoaded("roughlyenoughitems");
}
/**
* Returns whether EMI is present.
*
* @return true if EMI is present, else false
*/
public static boolean isEMIPresent() {
return FabricLoader.getInstance().isModLoaded("emi");
}
/**
* Returns whether InventoryTabs is present.
*
* @return true if InventoryTabs is present, else false
*/
public static boolean isInventoryTabsPresent() {
return FabricLoader.getInstance().isModLoaded("inventorytabs");
}
/**
* Returns whether Emotecraft is present.
*
* @return true if Emotecraft is present, else false
*/
public static boolean isEmotecraftPresent() {
return FabricLoader.getInstance().isModLoaded("emotecraft");
}
}

View File

@@ -0,0 +1,70 @@
/*
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of midnightcontrols.
*
* Licensed under the MIT license. For more information,
* see the LICENSE file.
*/
package eu.midnightdust.midnightcontrols.client.compat;
import net.fabricmc.loader.api.FabricLoader;
import org.jetbrains.annotations.NotNull;
import org.objectweb.asm.tree.ClassNode;
import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin;
import org.spongepowered.asm.mixin.extensibility.IMixinInfo;
import java.util.HashMap;
import java.util.List;
import java.util.Set;
/**
* This plugin is only present for the conditional mixins.
*
* @author LambdAurora
* @version 1.5.0
* @since 1.2.0
*/
public class MidnightControlsMixinPlugin implements IMixinConfigPlugin {
private final HashMap<String, Boolean> conditionalMixins = new HashMap<>();
public MidnightControlsMixinPlugin() {
this.putConditionalMixin("SodiumOptionsGUIAccessor", FabricLoader.getInstance().isModLoaded("sodium"));
}
private void putConditionalMixin(@NotNull String path, boolean condition) {
this.conditionalMixins.put("eu.midnightdust.midnightcontrols.client.compat.mixin." + path, condition);
}
@Override
public void onLoad(String mixinPackage) {
}
@Override
public String getRefMapperConfig() {
return null;
}
@Override
public boolean shouldApplyMixin(String targetClassName, String mixinClassName) {
return this.conditionalMixins.getOrDefault(mixinClassName, Boolean.TRUE);
}
@Override
public void acceptTargets(Set<String> myTargets, Set<String> otherTargets) {
}
@Override
public List<String> getMixins() {
return null;
}
@Override
public void preApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) {
}
@Override
public void postApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) {
}
}

View File

@@ -0,0 +1,195 @@
/*
* Copyright © 2021-2022 Karen/あけみ <karen@akemi.ai>, LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of MidnightControls.
*
* Licensed under the MIT license. For more information,
* see the LICENSE file.
*/
package eu.midnightdust.midnightcontrols.client.compat;
import eu.midnightdust.midnightcontrols.client.MidnightControlsClient;
import eu.midnightdust.midnightcontrols.client.controller.ButtonBinding;
import net.minecraft.client.option.KeyBinding;
import org.jetbrains.annotations.NotNull;
import org.aperlambda.lambdacommon.utils.LambdaReflection;
import org.lwjgl.glfw.GLFW;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* Represents a compatibility handler for Ok Zoomer.
*
* @author Karen/あけみ, LambdAurora
* @version 1.4.3
* @since 1.1.0
*/
public class OkZoomerCompat implements CompatHandler {
private boolean didAllReflectionCallsSucceed = false;
private KeyBinding okZoomerZoomKey;
private KeyBinding okZoomerIncreaseZoomKey;
private KeyBinding okZoomerDecreaseZoomKey;
private KeyBinding okZoomerResetZoomKey;
private Method okZoomerAreExtraKeyBindsEnabledMethod;
public OkZoomerCompat() {
// These strings represent the names of the classes, fields, and methods we use from the Ok Zoomer API
String okZoomerZoomKeybindsClassString;
String okZoomerZoomKeyFieldString;
String okZoomerIncreaseZoomKeyFieldString;
String okZoomerDecreaseZoomKeyFieldString;
String okZoomerResetZoomKeyFieldString;
String okZoomerAreExtraKeyBindsEnabledMethodNameString;
// These variables represent the actual objects that we reflect to
Class<?> okZoomerZoomKeybindsClass;
Field okZoomerZoomKeyField;
Field okZoomerIncreaseZoomKeyField;
Field okZoomerDecreaseZoomKeyField;
Field okZoomerResetZoomKeyField;
// First, we need to determine which version of the Ok Zoomer API we're dealing with here.
if (LambdaReflection.doesClassExist("io.github.ennuil.okzoomer.keybinds.ZoomKeybinds")) {
// https://github.com/EnnuiL/OkZoomer/blob/5.0.0-beta.3+1.17.1/src/main/java/io/github/ennuil/okzoomer/keybinds/ZoomKeybinds.java
MidnightControlsClient.get().log("Ok Zoomer version 5.0.0-beta.3 or below detected!");
okZoomerZoomKeybindsClassString = "io.github.ennuil.okzoomer.keybinds.ZoomKeybinds";
okZoomerZoomKeyFieldString = "zoomKey";
okZoomerIncreaseZoomKeyFieldString = "increaseZoomKey";
okZoomerDecreaseZoomKeyFieldString = "decreaseZoomKey";
okZoomerResetZoomKeyFieldString = "resetZoomKey";
okZoomerAreExtraKeyBindsEnabledMethodNameString = "areExtraKeybindsEnabled";
} else if (LambdaReflection.doesClassExist("io.github.ennuil.okzoomer.key_binds.ZoomKeyBinds")) {
// https://github.com/EnnuiL/OkZoomer/blob/5.0.0-beta.6+1.18.2/src/main/java/io/github/ennuil/okzoomer/key_binds/ZoomKeyBinds.java
MidnightControlsClient.get().log("Ok Zoomer version 5.0.0-beta.6, 5.0.0-beta.5, or 5.0.0-beta.4 detected!");
okZoomerZoomKeybindsClassString = "io.github.ennuil.okzoomer.key_binds.ZoomKeyBinds";
okZoomerZoomKeyFieldString = "ZOOM_KEY";
okZoomerIncreaseZoomKeyFieldString = "INCREASE_ZOOM_KEY";
okZoomerDecreaseZoomKeyFieldString = "DECREASE_ZOOM_KEY";
okZoomerResetZoomKeyFieldString = "RESET_ZOOM_KEY";
okZoomerAreExtraKeyBindsEnabledMethodNameString = "areExtraKeyBindsEnabled";
} else if (LambdaReflection.doesClassExist("io.github.ennuil.ok_zoomer.key_binds.ZoomKeyBinds")) {
// https://github.com/EnnuiL/OkZoomer/blob/5.0.0-beta.7+1.18.2/src/main/java/io/github/ennuil/ok_zoomer/key_binds/ZoomKeyBinds.java
MidnightControlsClient.get().log("Ok Zoomer version 5.0.0-beta.7 (Quilt) or above detected!");
okZoomerZoomKeybindsClassString = "io.github.ennuil.ok_zoomer.key_binds.ZoomKeyBinds";
okZoomerZoomKeyFieldString = "ZOOM_KEY";
okZoomerIncreaseZoomKeyFieldString = "INCREASE_ZOOM_KEY";
okZoomerDecreaseZoomKeyFieldString = "DECREASE_ZOOM_KEY";
okZoomerResetZoomKeyFieldString = "RESET_ZOOM_KEY";
okZoomerAreExtraKeyBindsEnabledMethodNameString = "areExtraKeyBindsEnabled";
} else {
// If all of the above checks fail, then the version of the Ok Zoomer API that the user is trying to use is too new.
MidnightControlsClient.get().warn("The version of Ok Zoomer that you are currently using is too new, and is not yet supported by MidnightControls!");
return;
}
// Reflect to the ZoomKeyBinds (>= 5.0.0-beta.4) / ZoomKeybinds (<= 5.0.0-beta.3) class.
try {
okZoomerZoomKeybindsClass = Class.forName(okZoomerZoomKeybindsClassString);
} catch (ClassNotFoundException exception) {
// This theoretically should never happen.
MidnightControlsClient.get().warn("MidnightControls failed to reflect to the Ok Zoomer keybinds class!");
exception.printStackTrace();
return;
}
// Reflect to all of the keybind fields.
try {
okZoomerZoomKeyField = okZoomerZoomKeybindsClass.getField(okZoomerZoomKeyFieldString);
okZoomerIncreaseZoomKeyField = okZoomerZoomKeybindsClass.getField(okZoomerIncreaseZoomKeyFieldString);
okZoomerDecreaseZoomKeyField = okZoomerZoomKeybindsClass.getField(okZoomerDecreaseZoomKeyFieldString);
okZoomerResetZoomKeyField = okZoomerZoomKeybindsClass.getField(okZoomerResetZoomKeyFieldString);
} catch (NoSuchFieldException exception) {
MidnightControlsClient.get().warn("MidnightControls failed to reflect to the Ok Zoomer keybind fields!");
exception.printStackTrace();
return;
}
// Initialise KeyBinding objects
try {
okZoomerZoomKey = (KeyBinding) okZoomerZoomKeyField.get(null);
okZoomerIncreaseZoomKey = (KeyBinding) okZoomerIncreaseZoomKeyField.get(null);
okZoomerDecreaseZoomKey = (KeyBinding) okZoomerDecreaseZoomKeyField.get(null);
okZoomerResetZoomKey = (KeyBinding) okZoomerResetZoomKeyField.get(null);
} catch (IllegalAccessException exception) {
MidnightControlsClient.get().warn("MidnightControls failed to reflect to the Ok Zoomer keybind objects!");
exception.printStackTrace();
return;
}
// Reflect to the areExtraKeyBindsEnabled (>= 5.0.0-beta.4) / areExtraKeybindsEnabled (<= 5.0.0-beta.3) method.
// TODO: Consider replacing this entirely with getExtraKeyBind (>= 5.0.0-beta.4) / getExtraKeybind (<= 5.0.0-beta.3) in the future.
try {
okZoomerAreExtraKeyBindsEnabledMethod = okZoomerZoomKeybindsClass.getDeclaredMethod(okZoomerAreExtraKeyBindsEnabledMethodNameString);
} catch (NoSuchMethodException exception) {
MidnightControlsClient.get().warn("MidnightControls failed to reflect to an Ok Zoomer method (areExtraKeyBindsEnabled / areExtraKeybindsEnabled)!");
exception.printStackTrace();
return;
}
didAllReflectionCallsSucceed = true;
}
@Override
public void handle(@NotNull MidnightControlsClient mod) {
if (didAllReflectionCallsSucceed) {
new ButtonBinding.Builder("zoom")
.buttons(GLFW.GLFW_GAMEPAD_BUTTON_DPAD_UP, GLFW.GLFW_GAMEPAD_BUTTON_X)
.onlyInGame()
.cooldown(true)
.category(ButtonBinding.MISC_CATEGORY)
.linkKeybind(okZoomerZoomKey)
.register();
boolean okZoomerAreExtraKeyBindsEnabled = false;
try {
okZoomerAreExtraKeyBindsEnabled = (boolean) okZoomerAreExtraKeyBindsEnabledMethod.invoke(null);
} catch (IllegalAccessException exception) {
MidnightControlsClient.get().warn("MidnightControls encountered an IllegalAccessException while attempting to invoke a reflected Ok Zoomer method (areExtraKeyBindsEnabled / areExtraKeybindsEnabled)!");
exception.printStackTrace();
} catch (InvocationTargetException exception) {
MidnightControlsClient.get().warn("MidnightControls encountered an InvocationTargetException while attempting to invoke a reflected Ok Zoomer method (areExtraKeyBindsEnabled / areExtraKeybindsEnabled)!");
exception.printStackTrace();
}
if (okZoomerAreExtraKeyBindsEnabled) {
new ButtonBinding.Builder("zoom_in")
.buttons(GLFW.GLFW_GAMEPAD_BUTTON_DPAD_UP, ButtonBinding.axisAsButton(GLFW.GLFW_GAMEPAD_AXIS_RIGHT_TRIGGER, true))
.onlyInGame()
.cooldown(true)
.category(ButtonBinding.MISC_CATEGORY)
.linkKeybind(okZoomerIncreaseZoomKey)
.register();
new ButtonBinding.Builder("zoom_out")
.buttons(GLFW.GLFW_GAMEPAD_BUTTON_DPAD_UP, ButtonBinding.axisAsButton(GLFW.GLFW_GAMEPAD_AXIS_LEFT_TRIGGER, true))
.onlyInGame()
.cooldown(true)
.category(ButtonBinding.MISC_CATEGORY)
.linkKeybind(okZoomerDecreaseZoomKey)
.register();
new ButtonBinding.Builder("zoom_reset")
.onlyInGame()
.cooldown(true)
.category(ButtonBinding.MISC_CATEGORY)
.linkKeybind(okZoomerResetZoomKey)
.register();
}
}
}
}

View File

@@ -0,0 +1,43 @@
package eu.midnightdust.midnightcontrols.client.compat;
import eu.midnightdust.midnightcontrols.MidnightControls;
import eu.midnightdust.midnightcontrols.client.MidnightControlsClient;
import eu.midnightdust.midnightcontrols.client.MidnightControlsConfig;
import eu.midnightdust.midnightcontrols.client.compat.mixin.SodiumOptionsGUIAccessor;
import eu.midnightdust.midnightcontrols.client.controller.InputManager;
import me.jellysquid.mods.sodium.client.gui.SodiumOptionsGUI;
import me.jellysquid.mods.sodium.client.gui.options.control.ControlElement;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gui.screen.Screen;
public class SodiumCompat {
private static final MinecraftClient client = MinecraftClient.getInstance();
public static void handleInput(Screen screen, boolean direction) {
if (screen instanceof SodiumOptionsGUI optionsGUI) {
SodiumOptionsGUIAccessor accessor = (SodiumOptionsGUIAccessor) optionsGUI;
final int max = accessor.getControls().size()-1;
var option = accessor.getControls().stream().filter(ControlElement::isHovered).findFirst().orElse(accessor.getControls().get(0));
int i = accessor.getControls().indexOf(option);
i = (direction ? ((max > i) ? ++i : 0) : (i > 0 ? --i : max));
var dimensions = accessor.getControls().get(i).getDimensions();
int x = (int) (client.getWindow().getScaleFactor() * dimensions.getCenterX());
int y = (int) (client.getWindow().getScaleFactor() * dimensions.getCenterY());
InputManager.queueMousePosition(x,y);
InputManager.INPUT_MANAGER.updateMousePosition(client);
MidnightControlsClient.get().input.actionGuiCooldown = 5;
if (MidnightControlsConfig.debug) MidnightControls.get().log(i+" "+accessor.getControls().size()+" | " + dimensions.getCenterX() + " " + dimensions.getCenterY());
}
}
public static void handleTabs(Screen screen, boolean direction) {
if (screen instanceof SodiumOptionsGUI optionsGUI) {
SodiumOptionsGUIAccessor accessor = (SodiumOptionsGUIAccessor) optionsGUI;
final int max = accessor.getPages().size()-1;
int i = accessor.getPages().indexOf(accessor.getCurrentPage());
i = (direction ? ((max > i) ? ++i : 0) : (i > 0 ? --i : max));
if (MidnightControlsConfig.debug) MidnightControls.get().log(""+i);
optionsGUI.setPage(accessor.getPages().get(i));
}
}
}

View File

@@ -0,0 +1,95 @@
/*
* Copyright © 2022 Motschen <motschen@midnightdust.eu>
*
* This file is part of MidnightControls.
*
* Licensed under the MIT license. For more information,
* see the LICENSE file.
*/
package eu.midnightdust.midnightcontrols.client.compat;
import eu.midnightdust.midnightcontrols.client.MidnightControlsClient;
import eu.midnightdust.midnightcontrols.client.MidnightControlsConfig;
import eu.midnightdust.midnightcontrols.client.controller.ButtonBinding;
import eu.midnightdust.midnightcontrols.client.controller.ButtonCategory;
import eu.midnightdust.midnightcontrols.client.controller.InputManager;
import eu.midnightdust.midnightcontrols.client.mixin.KeyBindingIDAccessor;
import net.minecraft.client.option.KeyBinding;
import org.aperlambda.lambdacommon.Identifier;
import org.jetbrains.annotations.NotNull;
import org.lwjgl.glfw.GLFW;
/**
* Represents a compatibility handler for VoxelMap.
*
* @author Motschen
* @version 1.8.0
* @since 1.8.0
*/
public class VoxelMapCompat implements CompatHandler {
private final KeyBinding voxelMapZoomKey = KeyBindingIDAccessor.getKEYS_BY_ID().getOrDefault("key.minimap.zoom", null);
private final KeyBinding voxelmapFullscreenKey = KeyBindingIDAccessor.getKEYS_BY_ID().getOrDefault("key.minimap.togglefullscreen", null);
private final KeyBinding voxelmapMenuKey = KeyBindingIDAccessor.getKEYS_BY_ID().getOrDefault("key.minimap.voxelmapmenu", null);;
private final KeyBinding voxelmapWaypointMenuKey = KeyBindingIDAccessor.getKEYS_BY_ID().getOrDefault("key.minimap.waypointmenu", null);;
private final KeyBinding voxelmapWaypointKey = KeyBindingIDAccessor.getKEYS_BY_ID().getOrDefault("key.minimap.waypointhotkey", null);
private final KeyBinding voxelmapMobToggleKey = KeyBindingIDAccessor.getKEYS_BY_ID().getOrDefault("key.minimap.togglemobs", null);
private final KeyBinding voxelmapWaypointToggleKey = KeyBindingIDAccessor.getKEYS_BY_ID().getOrDefault("key.minimap.toggleingamewaypoints", null);
private static final ButtonCategory VOXELMAP_CATEGORY = InputManager.registerCategory(new Identifier("minecraft","controls.minimap.title"));
@Override
public void handle(@NotNull MidnightControlsClient mod) {
if (MidnightControlsConfig.debug && KeyBindingIDAccessor.getKEYS_BY_ID() != null) KeyBindingIDAccessor.getKEYS_BY_ID().forEach((a, b) -> System.out.println(a + " - " + b));
new ButtonBinding.Builder("key.minimap.zoom")
.buttons(GLFW.GLFW_GAMEPAD_BUTTON_DPAD_DOWN, GLFW.GLFW_GAMEPAD_BUTTON_X)
.onlyInGame()
.cooldown(true)
.category(VOXELMAP_CATEGORY)
.linkKeybind(voxelMapZoomKey)
.register();
new ButtonBinding.Builder("key.minimap.togglefullscreen")
.buttons(GLFW.GLFW_GAMEPAD_BUTTON_DPAD_DOWN, GLFW.GLFW_GAMEPAD_BUTTON_Y)
.onlyInGame()
.cooldown(true)
.category(VOXELMAP_CATEGORY)
.linkKeybind(voxelmapFullscreenKey)
.register();
new ButtonBinding.Builder("key.minimap.voxelmapmenu")
.buttons(GLFW.GLFW_GAMEPAD_BUTTON_DPAD_DOWN, GLFW.GLFW_GAMEPAD_BUTTON_START)
.onlyInGame()
.cooldown(true)
.category(VOXELMAP_CATEGORY)
.linkKeybind(voxelmapMenuKey)
.register();
new ButtonBinding.Builder("key.minimap.waypointmenu")
.buttons(GLFW.GLFW_GAMEPAD_BUTTON_DPAD_DOWN, GLFW.GLFW_GAMEPAD_BUTTON_GUIDE)
.onlyInGame()
.cooldown(true)
.category(VOXELMAP_CATEGORY)
.linkKeybind(voxelmapWaypointMenuKey)
.register();
new ButtonBinding.Builder("key.minimap.waypointhotkey")
.buttons(GLFW.GLFW_GAMEPAD_BUTTON_DPAD_DOWN, GLFW.GLFW_GAMEPAD_BUTTON_BACK)
.onlyInGame()
.cooldown(true)
.category(VOXELMAP_CATEGORY)
.linkKeybind(voxelmapWaypointKey)
.register();
new ButtonBinding.Builder("key.minimap.togglemobs")
.buttons(GLFW.GLFW_GAMEPAD_BUTTON_DPAD_DOWN, GLFW.GLFW_GAMEPAD_BUTTON_A)
.onlyInGame()
.cooldown(true)
.category(VOXELMAP_CATEGORY)
.linkKeybind(voxelmapMobToggleKey)
.register();
new ButtonBinding.Builder("key.minimap.toggleingamewaypoints")
.buttons(GLFW.GLFW_GAMEPAD_BUTTON_DPAD_DOWN, GLFW.GLFW_GAMEPAD_BUTTON_B)
.onlyInGame()
.cooldown(true)
.category(VOXELMAP_CATEGORY)
.linkKeybind(voxelmapWaypointToggleKey)
.register();
}
}

View File

@@ -0,0 +1,56 @@
package eu.midnightdust.midnightcontrols.client.compat;
import dev.isxander.yacl.gui.AbstractWidget;
import dev.isxander.yacl.gui.OptionListWidget;
import dev.isxander.yacl.gui.YACLScreen;
import dev.isxander.yacl.gui.controllers.ControllerWidget;
import dev.isxander.yacl.gui.controllers.slider.SliderControllerElement;
import net.minecraft.client.gui.Element;
import net.minecraft.client.gui.screen.Screen;
import org.lwjgl.glfw.GLFW;
public class YACLCompat {
public static boolean handleAButton(Screen screen, Element element) {
if (element instanceof AbstractWidget abstractWidget) {
// imitate ender key press
return abstractWidget.keyPressed(GLFW.GLFW_KEY_ENTER, 0, 0);
}
return false;
}
public static boolean handleLeftRight(Screen screen, boolean direction) {
if (screen instanceof YACLScreen yaclScreen) {
SliderControllerElement focusedSlider = yaclScreen.optionList.children().stream()
.filter(OptionListWidget.OptionEntry.class::isInstance)
.map(entry -> ((OptionListWidget.OptionEntry) entry).widget)
.filter(ControllerWidget.class::isInstance)
.map(ControllerWidget.class::cast)
.filter(SliderControllerElement.class::isInstance)
.map(SliderControllerElement.class::cast)
.filter(ControllerWidget::isHovered)
.findFirst()
.orElse(null);
if (focusedSlider == null)
return false;
focusedSlider.incrementValue(direction ? 1 : -1);
return true;
}
return false;
}
public static boolean handleCategories(Screen screen, boolean direction) {
if (screen instanceof YACLScreen yaclScreen) {
int categoryIdx = yaclScreen.getCurrentCategoryIdx();
if (direction) categoryIdx++; else categoryIdx--;
if (categoryIdx < 0) categoryIdx = yaclScreen.config.categories().size() - 1;
if (categoryIdx >= yaclScreen.config.categories().size()) categoryIdx = 0;
yaclScreen.changeCategory(categoryIdx);
return true;
}
return false;
}
}

View File

@@ -0,0 +1,19 @@
package eu.midnightdust.midnightcontrols.client.compat.mixin;
import me.jellysquid.mods.sodium.client.gui.SodiumOptionsGUI;
import me.jellysquid.mods.sodium.client.gui.options.OptionPage;
import me.jellysquid.mods.sodium.client.gui.options.control.ControlElement;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
import java.util.List;
@Mixin(value = SodiumOptionsGUI.class, remap = false)
public interface SodiumOptionsGUIAccessor {
@Accessor
List<ControlElement<?>> getControls();
@Accessor
List<OptionPage> getPages();
@Accessor
OptionPage getCurrentPage();
}

View File

@@ -0,0 +1,639 @@
/*
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of midnightcontrols.
*
* Licensed under the MIT license. For more information,
* see the LICENSE file.
*/
package eu.midnightdust.midnightcontrols.client.controller;
import eu.midnightdust.midnightcontrols.client.ButtonState;
import eu.midnightdust.midnightcontrols.client.MidnightControlsClient;
import eu.midnightdust.midnightcontrols.client.gui.RingScreen;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.option.GameOptions;
import net.minecraft.client.option.KeyBinding;
import net.minecraft.client.resource.language.I18n;
import net.minecraft.text.Text;
import net.minecraft.util.Identifier;
import org.aperlambda.lambdacommon.utils.function.PairPredicate;
import org.aperlambda.lambdacommon.utils.function.Predicates;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.stream.Collectors;
import static org.lwjgl.glfw.GLFW.*;
/**
* Represents a button binding.
*
* @author LambdAurora
* @version 1.7.0
* @since 1.0.0
*/
public class ButtonBinding {
public static final ButtonCategory MOVEMENT_CATEGORY;
public static final ButtonCategory GAMEPLAY_CATEGORY;
public static final ButtonCategory INVENTORY_CATEGORY;
public static final ButtonCategory MULTIPLAYER_CATEGORY;
public static final ButtonCategory MISC_CATEGORY;
public static final ButtonBinding ATTACK = new Builder("attack").buttons(axisAsButton(GLFW_GAMEPAD_AXIS_RIGHT_TRIGGER, true)).onlyInGame().register();
public static final ButtonBinding BACK = new Builder("back").buttons(axisAsButton(GLFW_GAMEPAD_AXIS_LEFT_Y, false))
.action(MovementHandler.HANDLER).onlyInGame().register();
public static final ButtonBinding CHAT = new Builder("chat").buttons(GLFW_GAMEPAD_BUTTON_DPAD_RIGHT).onlyInGame().cooldown().register();
public static final ButtonBinding CONTROLS_RING = new Builder("controls_ring").buttons(GLFW_GAMEPAD_BUTTON_GUIDE).onlyInGame().cooldown()
.action((client, button1, value, action) -> {
if (action.isPressed()) {
MidnightControlsClient.get().ring.loadFromUnbound();
client.setScreen(new RingScreen());
}
if (action.isUnpressed() && client.currentScreen != null) client.currentScreen.close();
return true;
}).register();
public static final ButtonBinding DROP_ITEM = new Builder("drop_item").buttons(GLFW_GAMEPAD_BUTTON_B).onlyInGame().cooldown().register();
public static final ButtonBinding FORWARD = new Builder("forward").buttons(axisAsButton(GLFW_GAMEPAD_AXIS_LEFT_Y, true))
.action(MovementHandler.HANDLER).onlyInGame().register();
public static final ButtonBinding HOTBAR_LEFT = new Builder("hotbar_left").buttons(GLFW_GAMEPAD_BUTTON_LEFT_BUMPER)
.action(InputHandlers.handleHotbar(false)).onlyInGame().cooldown().register();
public static final ButtonBinding HOTBAR_RIGHT = new Builder("hotbar_right").buttons(GLFW_GAMEPAD_BUTTON_RIGHT_BUMPER)
.action(InputHandlers.handleHotbar(true)).onlyInGame().cooldown().register();
public static final ButtonBinding INVENTORY = new Builder("inventory").buttons(GLFW_GAMEPAD_BUTTON_Y).onlyInGame().cooldown().register();
public static final ButtonBinding EXIT = new Builder("exit").buttons(GLFW_GAMEPAD_BUTTON_B).filter((client, buttonBinding) -> client.currentScreen != null && buttonBinding.cooldown == 0 && INVENTORY.cooldown == 0)
.action(InputHandlers.handleExit()).cooldown().register();
public static final ButtonBinding JUMP = new Builder("jump").buttons(GLFW_GAMEPAD_BUTTON_A).onlyInGame().register();
public static final ButtonBinding LEFT = new Builder("left").buttons(axisAsButton(GLFW_GAMEPAD_AXIS_LEFT_X, false))
.action(MovementHandler.HANDLER).onlyInGame().register();
public static final ButtonBinding PAUSE_GAME = new Builder("pause_game").buttons(GLFW_GAMEPAD_BUTTON_START).action(InputHandlers::handlePauseGame).cooldown().register();
public static final ButtonBinding PICK_BLOCK = new Builder("pick_block").buttons(GLFW_GAMEPAD_BUTTON_DPAD_LEFT).onlyInGame().cooldown().register();
public static final ButtonBinding PLAYER_LIST = new Builder("player_list").buttons(GLFW_GAMEPAD_BUTTON_BACK).onlyInGame().register();
public static final ButtonBinding RIGHT = new Builder("right").buttons(axisAsButton(GLFW_GAMEPAD_AXIS_LEFT_X, true))
.action(MovementHandler.HANDLER).onlyInGame().register();
public static final ButtonBinding SCREENSHOT = new Builder("screenshot").buttons(GLFW_GAMEPAD_BUTTON_DPAD_UP, GLFW_GAMEPAD_BUTTON_A)
.action(InputHandlers::handleScreenshot).cooldown().register();
public static final ButtonBinding DEBUG_SCREEN = new Builder("debug_screen").buttons(GLFW_GAMEPAD_BUTTON_DPAD_UP, GLFW_GAMEPAD_BUTTON_B)
.action((client,binding,value,action) -> {if (action == ButtonState.PRESS) client.options.debugEnabled = !client.options.debugEnabled; return true;}).cooldown().register();
public static final ButtonBinding SLOT_DOWN = new Builder("slot_down").buttons(GLFW_GAMEPAD_BUTTON_DPAD_DOWN)
.action(InputHandlers.handleInventorySlotPad(1)).onlyInInventory().cooldown().register();
public static final ButtonBinding SLOT_LEFT = new Builder("slot_left").buttons(GLFW_GAMEPAD_BUTTON_DPAD_LEFT)
.action(InputHandlers.handleInventorySlotPad(3)).onlyInInventory().cooldown().register();
public static final ButtonBinding SLOT_RIGHT = new Builder("slot_right").buttons(GLFW_GAMEPAD_BUTTON_DPAD_RIGHT)
.action(InputHandlers.handleInventorySlotPad(2)).onlyInInventory().cooldown().register();
public static final ButtonBinding SLOT_UP = new Builder("slot_up").buttons(GLFW_GAMEPAD_BUTTON_DPAD_UP)
.action(InputHandlers.handleInventorySlotPad(0)).onlyInInventory().cooldown().register();
public static final ButtonBinding SNEAK = new Builder("sneak").buttons(GLFW_GAMEPAD_BUTTON_RIGHT_THUMB)
.actions(InputHandlers::handleToggleSneak).onlyInGame().cooldown().register();
public static final ButtonBinding SPRINT = new Builder("sprint").buttons(GLFW_GAMEPAD_BUTTON_LEFT_THUMB)
.actions(InputHandlers::handleToggleSprint).onlyInGame().register();
public static final ButtonBinding SWAP_HANDS = new Builder("swap_hands").buttons(GLFW_GAMEPAD_BUTTON_X).onlyInGame().cooldown().register();
public static final ButtonBinding TAB_LEFT = new Builder("tab_back").buttons(GLFW_GAMEPAD_BUTTON_LEFT_BUMPER)
.action(InputHandlers.handleHotbar(false)).filter(Predicates.or(InputHandlers::inInventory, InputHandlers::inAdvancements).or((client, binding) -> client.currentScreen != null)).cooldown().register();
public static final ButtonBinding TAB_RIGHT = new Builder("tab_next").buttons(GLFW_GAMEPAD_BUTTON_RIGHT_BUMPER)
.action(InputHandlers.handleHotbar(true)).filter(Predicates.or(InputHandlers::inInventory, InputHandlers::inAdvancements).or((client, binding) -> client.currentScreen != null)).cooldown().register();
public static final ButtonBinding PAGE_LEFT = new Builder("page_back").buttons(axisAsButton(GLFW_GAMEPAD_AXIS_LEFT_TRIGGER, true))
.action(InputHandlers.handlePage(false)).filter(InputHandlers::inInventory).cooldown(30).register();
public static final ButtonBinding PAGE_RIGHT = new Builder("page_next").buttons(axisAsButton(GLFW_GAMEPAD_AXIS_RIGHT_TRIGGER, true))
.action(InputHandlers.handlePage(true)).filter(InputHandlers::inInventory).cooldown(30).register();
public static final ButtonBinding TAKE = new Builder("take").buttons(GLFW_GAMEPAD_BUTTON_X)
.action(InputHandlers.handleActions()).filter(InputHandlers::inInventory).cooldown().register();
public static final ButtonBinding TAKE_ALL = new Builder("take_all").buttons(GLFW_GAMEPAD_BUTTON_A)
.action(InputHandlers.handleActions()).filter(InputHandlers::inInventory).cooldown().register();
public static final ButtonBinding QUICK_MOVE = new Builder("quick_move").buttons(GLFW_GAMEPAD_BUTTON_Y)
.action(InputHandlers.handleActions()).filter(InputHandlers::inInventory).cooldown().register();
public static final ButtonBinding TOGGLE_PERSPECTIVE = new Builder("toggle_perspective").filter(InputHandlers::inGame).buttons(GLFW_GAMEPAD_BUTTON_DPAD_UP, GLFW_GAMEPAD_BUTTON_Y).cooldown().register();
public static final ButtonBinding USE = new Builder("use").buttons(axisAsButton(GLFW_GAMEPAD_AXIS_LEFT_TRIGGER, true)).register();
private int[] button;
private final int[] defaultButton;
private final String key;
private final Text text;
private KeyBinding mcKeyBinding = null;
protected PairPredicate<MinecraftClient, ButtonBinding> filter;
private final List<PressAction> actions = new ArrayList<>(Collections.singletonList(PressAction.DEFAULT_ACTION));
private boolean hasCooldown;
private int cooldownLength = 5;
private int cooldown = 0;
boolean pressed = false;
public ButtonBinding(String key, int[] defaultButton, List<PressAction> actions, PairPredicate<MinecraftClient, ButtonBinding> filter, boolean hasCooldown) {
this.setButton(this.defaultButton = defaultButton);
this.key = key;
this.text = Text.translatable(this.key);
this.filter = filter;
this.actions.addAll(actions);
this.hasCooldown = hasCooldown;
}
public ButtonBinding(String key, int[] defaultButton, List<PressAction> actions, PairPredicate<MinecraftClient, ButtonBinding> filter, boolean hasCooldown, int cooldownLength) {
this.setButton(this.defaultButton = defaultButton);
this.key = key;
this.text = Text.translatable(this.key);
this.filter = filter;
this.actions.addAll(actions);
this.hasCooldown = hasCooldown;
this.cooldownLength = cooldownLength;
}
public ButtonBinding(String key, int[] defaultButton, boolean hasCooldown) {
this(key, defaultButton, Collections.emptyList(), Predicates.pairAlwaysTrue(), hasCooldown);
}
public ButtonBinding(String key, int[] defaultButton, boolean hasCooldown, int cooldownLength) {
this(key, defaultButton, Collections.emptyList(), Predicates.pairAlwaysTrue(), hasCooldown, cooldownLength);
}
/**
* Returns the button bound.
*
* @return the bound button
*/
public int[] getButton() {
return this.button;
}
/**
* Sets the bound button.
*
* @param button the bound button
*/
public void setButton(int[] button) {
this.button = button;
if (InputManager.hasBinding(this))
InputManager.sortBindings();
}
/**
* Returns whether the bound button is the specified button or not.
*
* @param button the button to check
* @return true if the bound button is the specified button, else false
*/
public boolean isButton(int[] button) {
return InputManager.areButtonsEquivalent(button, this.button);
}
/**
* Returns whether this button is down or not.
*
* @return true if the button is down, else false
*/
public boolean isButtonDown() {
return this.pressed;
}
/**
* Returns whether this button binding is bound or not.
*
* @return true if this button binding is bound, else false
*/
public boolean isNotBound() {
return this.button.length == 0 || this.button[0] == -1;
}
/**
* Gets the default button assigned to this binding.
*
* @return the default button
*/
public int[] getDefaultButton() {
return this.defaultButton;
}
/**
* Returns whether the assigned button is the default button.
*
* @return true if the assigned button is the default button, else false
*/
public boolean isDefault() {
return this.button.length == this.defaultButton.length && InputManager.areButtonsEquivalent(this.button, this.defaultButton);
}
/**
* Returns the button code.
*
* @return the button code
*/
public String getButtonCode() {
return Arrays.stream(this.button)
.mapToObj(btn -> Integer.valueOf(btn).toString())
.collect(Collectors.joining("+"));
}
/**
* Sets the key binding to emulate with this button binding.
*
* @param keyBinding the optional key binding
*/
public void setKeyBinding(@Nullable KeyBinding keyBinding) {
this.mcKeyBinding = keyBinding;
}
/**
* Returns whether the button binding is available in the current context.
*
* @param client the client instance
* @return true if the button binding is available, else false
*/
public boolean isAvailable(@NotNull MinecraftClient client) {
return this.filter.test(client, this);
}
/**
* Updates the button binding cooldown.
*/
public void update() {
if (this.hasCooldown && this.cooldown > 0)
this.cooldown--;
}
/**
* Handles the button binding.
*
* @param client the client instance
* @param state the state
*/
public void handle(@NotNull MinecraftClient client, float value, @NotNull ButtonState state) {
if (state == ButtonState.REPEAT && this.hasCooldown && this.cooldown != 0)
return;
if (this.hasCooldown && state.isPressed()) {
this.cooldown = cooldownLength;
}
for (int i = this.actions.size() - 1; i >= 0; i--) {
if (this.actions.get(i).press(client, this, value, state))
break;
}
}
public @NotNull String getName() {
return this.key;
}
/**
* Returns the translation key of this button binding.
*
* @return the translation key
*/
public @NotNull String getTranslationKey() {
return I18n.hasTranslation("midnightcontrols.action." + this.getName()) ? "midnightcontrols.action." + this.getName() : this.getName();
}
public @NotNull Text getText() {
return this.text;
}
/**
* Returns the key binding equivalent of this button binding.
*
* @return the key binding equivalent
*/
public @NotNull Optional<KeyBinding> asKeyBinding() {
return Optional.ofNullable(this.mcKeyBinding);
}
@Override
public String toString() {
return "ButtonBinding{id=\"" + this.key + "\","
+ "hasCooldown=" + this.hasCooldown
+ "}";
}
/**
* Returns the specified axis as a button.
*
* @param axis the axis
* @param positive true if the axis part is positive, else false
* @return the axis as a button
*/
public static int axisAsButton(int axis, boolean positive) {
return positive ? 100 + axis : 200 + axis;
}
/**
* Returns whether the specified button is an axis or not.
*
* @param button the button
* @return true if the button is an axis, else false
*/
public static boolean isAxis(int button) {
button %= 500;
return button >= 100;
}
/**
* Returns the second Joycon's specified button code.
*
* @param button the raw button code
* @return the second Joycon's button code
*/
public static int controller2Button(int button) {
return 500 + button;
}
public static void init(@NotNull GameOptions options) {
ATTACK.mcKeyBinding = options.attackKey;
BACK.mcKeyBinding = options.backKey;
CHAT.mcKeyBinding = options.chatKey;
DROP_ITEM.mcKeyBinding = options.dropKey;
FORWARD.mcKeyBinding = options.forwardKey;
INVENTORY.mcKeyBinding = options.inventoryKey;
JUMP.mcKeyBinding = options.jumpKey;
LEFT.mcKeyBinding = options.leftKey;
PICK_BLOCK.mcKeyBinding = options.pickItemKey;
PLAYER_LIST.mcKeyBinding = options.playerListKey;
RIGHT.mcKeyBinding = options.rightKey;
SCREENSHOT.mcKeyBinding = options.screenshotKey;
SNEAK.mcKeyBinding = options.sneakKey;
SPRINT.mcKeyBinding = options.sprintKey;
SWAP_HANDS.mcKeyBinding = options.swapHandsKey;
TOGGLE_PERSPECTIVE.mcKeyBinding = options.togglePerspectiveKey;
USE.mcKeyBinding = options.useKey;
}
/**
* Returns the localized name of the specified button.
*
* @param button the button
* @return the localized name of the button
*/
public static @NotNull Text getLocalizedButtonName(int button) {
return switch (button % 500) {
case -1 -> Text.translatable("key.keyboard.unknown");
case GLFW_GAMEPAD_BUTTON_A -> Text.translatable("midnightcontrols.button.a");
case GLFW_GAMEPAD_BUTTON_B -> Text.translatable("midnightcontrols.button.b");
case GLFW_GAMEPAD_BUTTON_X -> Text.translatable("midnightcontrols.button.x");
case GLFW_GAMEPAD_BUTTON_Y -> Text.translatable("midnightcontrols.button.y");
case GLFW_GAMEPAD_BUTTON_LEFT_BUMPER -> Text.translatable("midnightcontrols.button.left_bumper");
case GLFW_GAMEPAD_BUTTON_RIGHT_BUMPER -> Text.translatable("midnightcontrols.button.right_bumper");
case GLFW_GAMEPAD_BUTTON_BACK -> Text.translatable("midnightcontrols.button.back");
case GLFW_GAMEPAD_BUTTON_START -> Text.translatable("midnightcontrols.button.start");
case GLFW_GAMEPAD_BUTTON_GUIDE -> Text.translatable("midnightcontrols.button.guide");
case GLFW_GAMEPAD_BUTTON_LEFT_THUMB -> Text.translatable("midnightcontrols.button.left_thumb");
case GLFW_GAMEPAD_BUTTON_RIGHT_THUMB -> Text.translatable("midnightcontrols.button.right_thumb");
case GLFW_GAMEPAD_BUTTON_DPAD_UP -> Text.translatable("midnightcontrols.button.dpad_up");
case GLFW_GAMEPAD_BUTTON_DPAD_RIGHT -> Text.translatable("midnightcontrols.button.dpad_right");
case GLFW_GAMEPAD_BUTTON_DPAD_DOWN -> Text.translatable("midnightcontrols.button.dpad_down");
case GLFW_GAMEPAD_BUTTON_DPAD_LEFT -> Text.translatable("midnightcontrols.button.dpad_left");
case 100 -> Text.translatable("midnightcontrols.axis.left_x+");
case 101 -> Text.translatable("midnightcontrols.axis.left_y+");
case 102 -> Text.translatable("midnightcontrols.axis.right_x+");
case 103 -> Text.translatable("midnightcontrols.axis.right_y+");
case 104 -> Text.translatable("midnightcontrols.axis.left_trigger");
case 105 -> Text.translatable("midnightcontrols.axis.right_trigger");
case 200 -> Text.translatable("midnightcontrols.axis.left_x-");
case 201 -> Text.translatable("midnightcontrols.axis.left_y-");
case 202 -> Text.translatable("midnightcontrols.axis.right_x-");
case 203 -> Text.translatable("midnightcontrols.axis.right_y-");
case 15 -> Text.translatable("midnightcontrols.button.l4");
case 16 -> Text.translatable("midnightcontrols.button.l5");
case 17 -> Text.translatable("midnightcontrols.button.r4");
case 18 -> Text.translatable("midnightcontrols.button.r5");
default -> Text.translatable("midnightcontrols.button.unknown", button);
};
}
static {
MOVEMENT_CATEGORY = InputManager.registerDefaultCategory("key.categories.movement", category -> category.registerAllBindings(
ButtonBinding.FORWARD,
ButtonBinding.BACK,
ButtonBinding.LEFT,
ButtonBinding.RIGHT,
ButtonBinding.JUMP,
ButtonBinding.SNEAK,
ButtonBinding.SPRINT));
GAMEPLAY_CATEGORY = InputManager.registerDefaultCategory("key.categories.gameplay", category -> category.registerAllBindings(
ButtonBinding.ATTACK,
ButtonBinding.PICK_BLOCK,
ButtonBinding.USE
));
INVENTORY_CATEGORY = InputManager.registerDefaultCategory("key.categories.inventory", category -> category.registerAllBindings(
ButtonBinding.EXIT,
ButtonBinding.DROP_ITEM,
ButtonBinding.HOTBAR_LEFT,
ButtonBinding.HOTBAR_RIGHT,
ButtonBinding.INVENTORY,
ButtonBinding.SWAP_HANDS,
ButtonBinding.TAB_LEFT,
ButtonBinding.TAB_RIGHT,
ButtonBinding.PAGE_LEFT,
ButtonBinding.PAGE_RIGHT,
ButtonBinding.TAKE,
ButtonBinding.TAKE_ALL,
ButtonBinding.QUICK_MOVE,
ButtonBinding.SLOT_UP,
ButtonBinding.SLOT_DOWN,
ButtonBinding.SLOT_LEFT,
ButtonBinding.SLOT_RIGHT
));
MULTIPLAYER_CATEGORY = InputManager.registerDefaultCategory("key.categories.multiplayer",
category -> category.registerAllBindings(ButtonBinding.CHAT, ButtonBinding.PLAYER_LIST));
MISC_CATEGORY = InputManager.registerDefaultCategory("key.categories.misc", category -> category.registerAllBindings(
ButtonBinding.SCREENSHOT,
ButtonBinding.TOGGLE_PERSPECTIVE,
ButtonBinding.PAUSE_GAME,
//SMOOTH_CAMERA,
ButtonBinding.DEBUG_SCREEN,
ButtonBinding.CONTROLS_RING
));
}
/**
* Returns a builder instance.
*
* @param identifier the identifier of the button binding
* @return the builder instance
* @since 1.5.0
*/
public static Builder builder(@NotNull Identifier identifier) {
return new Builder(identifier);
}
/**
* Represents a quick {@link ButtonBinding} builder.
*
* @author LambdAurora
* @version 1.5.0
* @since 1.1.0
*/
public static class Builder {
private final String key;
private int[] buttons = new int[0];
private final List<PressAction> actions = new ArrayList<>();
private PairPredicate<MinecraftClient, ButtonBinding> filter = Predicates.pairAlwaysTrue();
private boolean cooldown = false;
private int cooldownLength = 5;
private ButtonCategory category = null;
private KeyBinding mcBinding = null;
/**
* This constructor shouldn't be used for other mods.
*
* @param key the key with format {@code "<namespace>.<name>"}
*/
public Builder(@NotNull String key) {
this.key = key;
this.unbound();
}
public Builder(@NotNull Identifier identifier) {
this(identifier.getNamespace() + "." + identifier.getPath());
}
/**
* Defines the default buttons of the {@link ButtonBinding}.
*
* @param buttons the default buttons
* @return the builder instance
*/
public Builder buttons(int... buttons) {
this.buttons = buttons;
return this;
}
/**
* Sets the {@link ButtonBinding} to unbound.
*
* @return the builder instance
*/
public Builder unbound() {
return this.buttons(-1);
}
/**
* Adds the actions to the {@link ButtonBinding}.
*
* @param actions the actions to add
* @return the builder instance
*/
public Builder actions(@NotNull PressAction... actions) {
this.actions.addAll(Arrays.asList(actions));
return this;
}
/**
* Adds an action to the {@link ButtonBinding}.
*
* @param action the action to add
* @return the builder instance
*/
public Builder action(@NotNull PressAction action) {
this.actions.add(action);
return this;
}
/**
* Sets a filter for the {@link ButtonBinding}.
*
* @param filter the filter
* @return the builder instance
*/
public Builder filter(@NotNull PairPredicate<MinecraftClient, ButtonBinding> filter) {
this.filter = filter;
return this;
}
/**
* Sets the filter of {@link ButtonBinding} to only in game.
*
* @return the builder instance
* @see #filter(PairPredicate)
* @see InputHandlers#inGame(MinecraftClient, ButtonBinding)
*/
public Builder onlyInGame() {
return this.filter(InputHandlers::inGame);
}
/**
* Sets the filter of {@link ButtonBinding} to only in inventory.
*
* @return the builder instance
* @see #filter(PairPredicate)
* @see InputHandlers#inInventory(MinecraftClient, ButtonBinding)
*/
public Builder onlyInInventory() {
return this.filter(InputHandlers::inInventory);
}
/**
* Sets whether the {@link ButtonBinding} has a cooldown or not.
*
* @param cooldown true if the {@link ButtonBinding} has a cooldown, else false
* @return the builder instance
*/
public Builder cooldown(boolean cooldown) {
this.cooldown = cooldown;
return this;
}
/**
* Sets the cooldown enabled with a custom duration for {@link ButtonBinding}.
*
* @param cooldownLength duration of {@link ButtonBinding} cooldown
* @return the builder instance
*/
public Builder cooldown(int cooldownLength) {
this.cooldownLength = cooldownLength;
this.cooldown = true;
return this;
}
/**
* Puts a cooldown on the {@link ButtonBinding}.
*
* @return the builder instance
* @since 1.5.0
*/
public Builder cooldown() {
return this.cooldown(true);
}
/**
* Sets the category of the {@link ButtonBinding}.
*
* @param category the category
* @return the builder instance
*/
public Builder category(@Nullable ButtonCategory category) {
this.category = category;
return this;
}
/**
* Sets the keybinding linked to the {@link ButtonBinding}.
*
* @param binding the keybinding to link
* @return the builder instance
*/
public Builder linkKeybind(@Nullable KeyBinding binding) {
this.mcBinding = binding;
return this;
}
/**
* Builds the {@link ButtonBinding}.
*
* @return the built {@link ButtonBinding}
*/
public ButtonBinding build() {
var binding = new ButtonBinding(this.key, this.buttons, this.actions, this.filter, this.cooldown, this.cooldownLength);
if (this.category != null)
this.category.registerBinding(binding);
if (this.mcBinding != null)
binding.setKeyBinding(this.mcBinding);
return binding;
}
/**
* Builds and registers the {@link ButtonBinding}.
*
* @return the built {@link ButtonBinding}
* @see #build()
*/
public ButtonBinding register() {
return InputManager.registerBinding(this.build());
}
}
}

View File

@@ -0,0 +1,94 @@
/*
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of midnightcontrols.
*
* Licensed under the MIT license. For more information,
* see the LICENSE file.
*/
package eu.midnightdust.midnightcontrols.client.controller;
import net.minecraft.client.resource.language.I18n;
import org.aperlambda.lambdacommon.Identifier;
import org.aperlambda.lambdacommon.utils.Identifiable;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/**
* Represents a button binding category
*
* @author LambdAurora
* @version 1.1.0
* @since 1.1.0
*/
public class ButtonCategory implements Identifiable {
private final List<ButtonBinding> bindings = new ArrayList<>();
private final Identifier id;
private final int priority;
public ButtonCategory(@NotNull Identifier id, int priority) {
this.id = id;
this.priority = priority;
}
public ButtonCategory(@NotNull Identifier id) {
this(id, 100);
}
public void registerBinding(@NotNull ButtonBinding binding) {
if (this.bindings.contains(binding))
throw new IllegalStateException("Cannot register twice a button binding in the same category.");
this.bindings.add(binding);
}
public void registerAllBindings(@NotNull ButtonBinding... bindings) {
this.registerAllBindings(Arrays.asList(bindings));
}
public void registerAllBindings(@NotNull List<ButtonBinding> bindings) {
bindings.forEach(this::registerBinding);
}
/**
* Gets the bindings assigned to this category.
*
* @return the bindings assigned to this category
*/
public @NotNull List<ButtonBinding> getBindings() {
return Collections.unmodifiableList(this.bindings);
}
/**
* Gets the translated name of this category.
* <p>
* The translation key should be `modid.identifier_name`.
*
* @return the translated name
*/
public @NotNull String getTranslatedName() {
if (this.id.getNamespace().equals("minecraft"))
return I18n.translate(this.id.getName());
else
return I18n.translate(this.id.getNamespace() + "." + this.id.getName());
}
/**
* Gets the priority display of this category.
* It will defines in which order the categories will display on the controls screen.
*
* @return the priority of this category
*/
public int getPriority() {
return this.priority;
}
@Override
public @NotNull Identifier getIdentifier() {
return this.id;
}
}

View File

@@ -0,0 +1,203 @@
/*
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of midnightcontrols.
*
* Licensed under the MIT license. For more information,
* see the LICENSE file.
*/
package eu.midnightdust.midnightcontrols.client.controller;
import eu.midnightdust.midnightcontrols.MidnightControls;
import eu.midnightdust.midnightcontrols.client.MidnightControlsClient;
import eu.midnightdust.midnightcontrols.client.MidnightControlsConfig;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.resource.language.I18n;
import net.minecraft.client.toast.SystemToast;
import net.minecraft.text.Text;
import org.aperlambda.lambdacommon.utils.Nameable;
import org.jetbrains.annotations.NotNull;
import org.lwjgl.glfw.GLFW;
import org.lwjgl.glfw.GLFWGamepadState;
import org.lwjgl.system.MemoryStack;
import org.lwjgl.system.MemoryUtil;
import java.io.*;
import java.net.URL;
import java.nio.ByteBuffer;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import static org.lwjgl.BufferUtils.createByteBuffer;
/**
* Represents a controller.
*
* @author LambdAurora
* @version 1.7.0
* @since 1.0.0
*/
public record Controller(int id) implements Nameable {
private static final Map<Integer, Controller> CONTROLLERS = new HashMap<>();
/**
* Gets the controller's globally unique identifier.
*
* @return the controller's GUID
*/
public String getGuid() {
String guid = GLFW.glfwGetJoystickGUID(this.id);
return guid == null ? "" : guid;
}
/**
* Returns whether this controller is connected or not.
*
* @return true if this controller is connected, else false
*/
public boolean isConnected() {
return GLFW.glfwJoystickPresent(this.id);
}
/**
* Returns whether this controller is a gamepad or not.
*
* @return true if this controller is a gamepad, else false
*/
public boolean isGamepad() {
return this.isConnected() && GLFW.glfwJoystickIsGamepad(this.id);
}
/**
* Gets the name of the controller.
*
* @return the controller's name
*/
@Override
public String getName() {
var name = this.isGamepad() ? GLFW.glfwGetGamepadName(this.id) : GLFW.glfwGetJoystickName(this.id);
return name == null ? String.valueOf(this.id()) : name;
}
/**
* Gets the state of the controller.
*
* @return the state of the controller input
*/
public GLFWGamepadState getState() {
var state = GLFWGamepadState.create();
if (this.isGamepad())
GLFW.glfwGetGamepadState(this.id, state);
return state;
}
public static Controller byId(int id) {
if (id > GLFW.GLFW_JOYSTICK_LAST) {
MidnightControlsClient.get().log("Controller '" + id + "' doesn't exist.");
id = GLFW.GLFW_JOYSTICK_LAST;
}
Controller controller;
if (CONTROLLERS.containsKey(id))
return CONTROLLERS.get(id);
else {
controller = new Controller(id);
CONTROLLERS.put(id, controller);
return controller;
}
}
public static Optional<Controller> byGuid(@NotNull String guid) {
return CONTROLLERS.values().stream().filter(Controller::isConnected)
.filter(controller -> controller.getGuid().equals(guid))
.max(Comparator.comparingInt(Controller::id));
}
/**
* Reads the specified resource and returns the raw data as a ByteBuffer.
*
* @param resource the resource to read
* @param bufferSize the initial buffer size
* @return the resource data
* @throws IOException If an IO error occurs.
*/
private static ByteBuffer ioResourceToBuffer(String resource, int bufferSize) throws IOException {
ByteBuffer buffer = null;
var path = Paths.get(resource);
if (Files.isReadable(path)) {
try (var fc = Files.newByteChannel(path)) {
buffer = createByteBuffer((int) fc.size() + 2);
while (fc.read(buffer) != -1) ;
buffer.put((byte) 0);
}
}
if (buffer != null) buffer.flip(); // Force Java 8 >.<
return buffer;
}
/**
* Updates the controller mappings.
*/
public static void updateMappings() {
try {
MidnightControlsClient.get().log("Updating controller mappings...");
File databaseFile = new File("config/gamecontrollerdatabase.txt");
try {
BufferedInputStream in = new BufferedInputStream(new URL("https://raw.githubusercontent.com/gabomdq/SDL_GameControllerDB/master/gamecontrollerdb.txt").openStream());
BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(databaseFile));
byte[] dataBuffer = new byte[1024];
int bytesRead;
while ((bytesRead = in.read(dataBuffer, 0, 1024)) != -1) {
out.write(dataBuffer, 0, bytesRead);
}
out.close();
} catch (Exception ignored) {/* Just continue when internet connection is not available */}
var database = ioResourceToBuffer(databaseFile.getPath(), 1024);
if (database != null) GLFW.glfwUpdateGamepadMappings(database);
if (!MidnightControlsClient.MAPPINGS_FILE.exists())
return;
var buffer = ioResourceToBuffer(MidnightControlsClient.MAPPINGS_FILE.getPath(), 1024);
if (buffer != null) GLFW.glfwUpdateGamepadMappings(buffer);
} catch (IOException e) {
e.printStackTrace();
}
try (var memoryStack = MemoryStack.stackPush()) {
var pointerBuffer = memoryStack.mallocPointer(1);
int i = GLFW.glfwGetError(pointerBuffer);
if (i != 0) {
long l = pointerBuffer.get();
var string = l == 0L ? "" : MemoryUtil.memUTF8(l);
var client = MinecraftClient.getInstance();
if (client != null) {
client.getToastManager().add(SystemToast.create(client, SystemToast.Type.TUTORIAL_HINT,
Text.translatable("midnightcontrols.controller.mappings.error"), Text.literal(string)));
MidnightControls.get().log(I18n.translate("midnightcontrols.controller.mappings.error")+string);
}
}
} catch (Throwable e) {
/* Ignored :concern: */
}
if (MidnightControlsConfig.debug) {
for (int i = GLFW.GLFW_JOYSTICK_1; i <= GLFW.GLFW_JOYSTICK_16; i++) {
var controller = byId(i);
if (!controller.isConnected())
continue;
MidnightControls.get().log(String.format("Controller #%d name: \"%s\"\n GUID: %s\n Gamepad: %s",
controller.id,
controller.getName(),
controller.getGuid(),
controller.isGamepad()));
}
}
}
}

View File

@@ -0,0 +1,454 @@
/*
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of midnightcontrols.
*
* Licensed under the MIT license. For more information,
* see the LICENSE file.
*/
package eu.midnightdust.midnightcontrols.client.controller;
import eu.midnightdust.midnightcontrols.client.ButtonState;
import eu.midnightdust.midnightcontrols.client.MidnightControlsClient;
import eu.midnightdust.midnightcontrols.client.MidnightControlsConfig;
import eu.midnightdust.midnightcontrols.client.MidnightInput;
import eu.midnightdust.midnightcontrols.client.compat.InventoryTabsCompat;
import eu.midnightdust.midnightcontrols.client.compat.MidnightControlsCompat;
import eu.midnightdust.midnightcontrols.client.compat.SodiumCompat;
import eu.midnightdust.midnightcontrols.client.compat.YACLCompat;
import eu.midnightdust.midnightcontrols.client.gui.RingScreen;
import eu.midnightdust.midnightcontrols.client.mixin.AdvancementsScreenAccessor;
import eu.midnightdust.midnightcontrols.client.mixin.CreativeInventoryScreenAccessor;
import eu.midnightdust.midnightcontrols.client.mixin.RecipeBookWidgetAccessor;
import eu.midnightdust.midnightcontrols.client.util.HandledScreenAccessor;
import eu.midnightdust.midnightcontrols.client.util.MouseAccessor;
import net.fabricmc.fabric.impl.client.itemgroup.CreativeGuiExtensions;
import net.fabricmc.fabric.impl.client.itemgroup.FabricCreativeGuiComponents;
import net.fabricmc.fabric.impl.itemgroup.FabricItemGroup;
import net.fabricmc.fabric.impl.itemgroup.ItemGroupHelper;
import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gui.screen.TitleScreen;
import net.minecraft.client.gui.screen.advancement.AdvancementsScreen;
import net.minecraft.client.gui.screen.ingame.*;
import net.minecraft.client.gui.screen.recipebook.RecipeBookWidget;
import net.minecraft.client.gui.widget.PressableWidget;
import net.minecraft.client.util.ScreenshotRecorder;
import net.minecraft.item.ItemGroup;
import net.minecraft.item.ItemGroups;
import net.minecraft.screen.slot.Slot;
import net.minecraft.screen.slot.SlotActionType;
import net.minecraft.util.math.MathHelper;
import org.aperlambda.lambdacommon.utils.Pair;
import org.jetbrains.annotations.NotNull;
import org.lwjgl.glfw.GLFW;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import java.util.function.Predicate;
import static org.lwjgl.glfw.GLFW.*;
import static org.lwjgl.glfw.GLFW.GLFW_MOUSE_BUTTON_2;
/**
* Represents some input handlers.
*
* @author LambdAurora
* @version 1.7.0
* @since 1.1.0
*/
@SuppressWarnings("UnstableApiUsage")
public class InputHandlers {
private InputHandlers() {
}
private static List<ItemGroup> getVisibleGroups(CreativeInventoryScreen screen) {
return ItemGroupHelper.sortedGroups.stream()
.filter(itemGroup -> {
if (FabricCreativeGuiComponents.COMMON_GROUPS.contains(itemGroup)) return true;
return ((CreativeGuiExtensions)screen).fabric_currentPage() == ((FabricItemGroup)itemGroup).getPage() && itemGroup.shouldDisplay() && (!itemGroup.equals(ItemGroups.OPERATOR) || ItemGroups.operatorEnabled);
}).toList();
}
public static PressAction handleHotbar(boolean next) {
return (client, button, value, action) -> {
if (action == ButtonState.RELEASE)
return false;
// When in-game
if (client.currentScreen == null && client.player != null) {
if (!client.player.isSpectator()) {
if (next)
client.player.getInventory().scrollInHotbar(-1.0);
else
client.player.getInventory().scrollInHotbar(1.0);
}
else {
if (client.inGameHud.getSpectatorHud().isOpen()) {
client.inGameHud.getSpectatorHud().cycleSlot(next ? -1 : 1);
} else {
float g = MathHelper.clamp(client.player.getAbilities().getFlySpeed() + (next ? 1 : -1) * 0.005F, 0.0F, 0.2F);
client.player.getAbilities().setFlySpeed(g);
}
}
return true;
} else if (client.currentScreen instanceof RingScreen) {
MidnightControlsClient.get().ring.cyclePage(next);
} else if (client.currentScreen instanceof CreativeInventoryScreenAccessor inventory) {
ItemGroup currentTab = CreativeInventoryScreenAccessor.getSelectedTab();
int currentColumn = currentTab.getColumn();
ItemGroup.Row currentRow = currentTab.getRow();
ItemGroup newTab = null;
List<ItemGroup> visibleTabs = getVisibleGroups((CreativeInventoryScreen) client.currentScreen);
for (ItemGroup tab : visibleTabs) {
if (tab.getRow().equals(currentRow) && ((newTab == null && ((next && tab.getColumn() > currentColumn) ||
(!next && tab.getColumn() < currentColumn))) || (newTab != null && ((next && tab.getColumn() > currentColumn && tab.getColumn() < newTab.getColumn()) ||
(!next && tab.getColumn() < currentColumn && tab.getColumn() > newTab.getColumn())))))
newTab = tab;
}
if (newTab == null)
for (ItemGroup tab : visibleTabs) {
if ((tab.getRow().compareTo(currentRow)) != 0 && ((next && newTab == null || next && newTab.getColumn() > tab.getColumn()) || (!next && newTab == null) || (!next && newTab.getColumn() < tab.getColumn())))
newTab = tab;
}
if (newTab == null) {
for (ItemGroup tab : visibleTabs) {
if ((next && tab.getRow() == ItemGroup.Row.TOP && tab.getColumn() == 0) ||
!next && tab.getRow() == ItemGroup.Row.BOTTOM && (newTab == null || tab.getColumn() > newTab.getColumn()))
newTab = tab;
}
}
if (newTab == null || newTab.equals(currentTab)) newTab = ItemGroups.getDefaultTab();
inventory.midnightcontrols$setSelectedTab(newTab);
return true;
} else if (client.currentScreen instanceof InventoryScreen || client.currentScreen instanceof CraftingScreen || client.currentScreen instanceof AbstractFurnaceScreen<?>) {
RecipeBookWidget recipeBook;
if (client.currentScreen instanceof InventoryScreen inventoryScreen) recipeBook = inventoryScreen.getRecipeBookWidget();
else if (client.currentScreen instanceof CraftingScreen craftingScreen) recipeBook = craftingScreen.getRecipeBookWidget();
else recipeBook = ((AbstractFurnaceScreen<?>)client.currentScreen).getRecipeBookWidget();
var recipeBookAccessor = (RecipeBookWidgetAccessor) recipeBook;
var tabs = recipeBookAccessor.getTabButtons();
var currentTab = recipeBookAccessor.getCurrentTab();
if (currentTab == null || !recipeBook.isOpen()) {
if (MidnightControlsCompat.isInventoryTabsPresent()) InventoryTabsCompat.handleInventoryTabs(client.currentScreen, next);
return false;
}
int nextTab = tabs.indexOf(currentTab) + (next ? 1 : -1);
if (nextTab < 0)
nextTab = tabs.size() - 1;
else if (nextTab >= tabs.size())
nextTab = 0;
currentTab.setToggled(false);
recipeBookAccessor.setCurrentTab(currentTab = tabs.get(nextTab));
currentTab.setToggled(true);
recipeBookAccessor.midnightcontrols$refreshResults(true);
return true;
} else if (client.currentScreen instanceof AdvancementsScreenAccessor screen) {
var tabs = screen.getTabs().values().stream().distinct().toList();
var tab = screen.getSelectedTab();
if (tab == null)
return false;
for (int i = 0; i < tabs.size(); i++) {
if (tabs.get(i).equals(tab)) {
int nextTab = i + (next ? 1 : -1);
if (nextTab < 0)
nextTab = tabs.size() - 1;
else if (nextTab >= tabs.size())
nextTab = 0;
screen.getAdvancementManager().selectTab(tabs.get(nextTab).getRoot(), true);
break;
}
}
return true;
} else {
if (FabricLoader.getInstance().isModLoaded("sodium"))
SodiumCompat.handleTabs(client.currentScreen, next);
if (FabricLoader.getInstance().isModLoaded("yet-another-config-lib") && YACLCompat.handleCategories(client.currentScreen, next))
return true;
}
if (MidnightControlsCompat.isInventoryTabsPresent()) InventoryTabsCompat.handleInventoryTabs(client.currentScreen, next);
return false;
};
}
public static PressAction handlePage(boolean next) {
return (client, button, value, action) -> {
if (client.currentScreen instanceof CreativeInventoryScreen) {
var screen = (HandledScreenAccessor) client.currentScreen;
try {
if (next) {
return client.currentScreen.children().stream().filter(element -> element instanceof PressableWidget)
.map(element -> (PressableWidget) element)
.filter(element -> element.getMessage() != null && element.getMessage().getContent() != null)
.anyMatch(element -> {
if (element.getMessage().getString().equals(">")) {
element.onPress();
return true;
}
return false;
});
} else {
return client.currentScreen.children().stream().filter(element -> element instanceof PressableWidget)
.map(element -> (PressableWidget) element)
.filter(element -> element.getMessage() != null && element.getMessage().getContent() != null)
.anyMatch(element -> {
if (element.getMessage().getString().equals("<")) {
element.onPress();
return true;
}
return false;
});
}
} catch (Exception ignored) {}
}
if (MidnightControlsCompat.isInventoryTabsPresent()) InventoryTabsCompat.handleInventoryPage(client.currentScreen, next);
return false;
};
}
public static PressAction handleExit() {
return (client, button, value, action) -> {
if (client.currentScreen != null && client.currentScreen.getClass() != TitleScreen.class) {
if (!MidnightControlsCompat.handleMenuBack(client, client.currentScreen))
if (!MidnightControlsClient.get().input.tryGoBack(client.currentScreen))
client.currentScreen.close();
return true;
}
return false;
};
}
public static PressAction handleActions() {
return (client, button, value, action) -> {
if (!(client.currentScreen instanceof HandledScreen<?> screen)) return false;
if (client.interactionManager == null || client.player == null)
return false;
if (MidnightControlsClient.get().input.inventoryInteractionCooldown > 0)
return true;
double x = client.mouse.getX() * (double) client.getWindow().getScaledWidth() / (double) client.getWindow().getWidth();
double y = client.mouse.getY() * (double) client.getWindow().getScaledHeight() / (double) client.getWindow().getHeight();
var accessor = (HandledScreenAccessor) screen;
Slot slot = ((HandledScreenAccessor) screen).midnightcontrols$getSlotAt(x, y);
int slotId;
if (slot == null) {
if (button.getName().equals("take_all")) {
((MouseAccessor) client.mouse).setLeftButtonClicked(true);
return false;
}
slotId = accessor.midnightcontrols$isClickOutsideBounds(x, y, accessor.getX(), accessor.getY(), GLFW_MOUSE_BUTTON_1) ? -999 : -1;
} else {
slotId = slot.id;
}
var actionType = SlotActionType.PICKUP;
int clickData = GLFW.GLFW_MOUSE_BUTTON_1;
MidnightControlsClient.get().input.inventoryInteractionCooldown = 5;
switch (button.getName()) {
case "take_all": {
if (screen instanceof CreativeInventoryScreen) {
if (slot != null && (((CreativeInventoryScreenAccessor) accessor).midnightcontrols$isCreativeInventorySlot(slot) || MidnightControlsCompat.streamCompatHandlers().anyMatch(handler -> handler.isCreativeSlot(screen, slot))))
actionType = SlotActionType.CLONE;
}
break;
}
case "take": {
clickData = GLFW_MOUSE_BUTTON_2;
break;
}
case "quick_move": {
actionType = SlotActionType.QUICK_MOVE;
break;
}
default:
return false;
}
accessor.midnightcontrols$onMouseClick(slot, slotId, clickData, actionType);
return true;
};
}
public static boolean handlePauseGame(@NotNull MinecraftClient client, @NotNull ButtonBinding binding, float value, @NotNull ButtonState action) {
if (action == ButtonState.PRESS) {
// If in game, then pause the game.
if (client.currentScreen == null || client.currentScreen instanceof RingScreen)
client.openPauseMenu(false);
else if (client.currentScreen instanceof HandledScreen && client.player != null) // If the current screen is a container then close it.
client.player.closeHandledScreen();
else // Else just close the current screen.
client.currentScreen.close();
}
return true;
}
/**
* Handles the screenshot action.
*
* @param client the client instance
* @param binding the binding which fired the action
* @param action the action done on the binding
* @return true if handled, else false
*/
public static boolean handleScreenshot(@NotNull MinecraftClient client, @NotNull ButtonBinding binding, float value, @NotNull ButtonState action) {
if (action == ButtonState.RELEASE)
ScreenshotRecorder.saveScreenshot(client.runDirectory, client.getFramebuffer(),
text -> client.execute(() -> client.inGameHud.getChatHud().addMessage(text)));
return true;
}
public static boolean handleToggleSneak(@NotNull MinecraftClient client, @NotNull ButtonBinding button, float value, @NotNull ButtonState action) {
button.asKeyBinding().ifPresent(binding -> {
boolean sneakToggled = client.options.getSneakToggled().getValue();
if (client.player.getAbilities().flying && sneakToggled)
client.options.getSneakToggled().setValue(false);
else if (MidnightControlsConfig.controllerToggleSneak != sneakToggled)
client.options.getSneakToggled().setValue(!sneakToggled);
binding.setPressed(button.pressed);
if (client.player.getAbilities().flying && sneakToggled)
client.options.getSneakToggled().setValue(true);
else if (MidnightControlsConfig.controllerToggleSneak != sneakToggled)
client.options.getSneakToggled().setValue(sneakToggled);
});
return true;
}
public static boolean handleToggleSprint(@NotNull MinecraftClient client, @NotNull ButtonBinding button, float value, @NotNull ButtonState action) {
button.asKeyBinding().ifPresent(binding -> {
boolean sprintToggled = client.options.getSprintToggled().getValue();
if (client.player.getAbilities().flying && sprintToggled)
client.options.getSprintToggled().setValue(false);
else if (MidnightControlsConfig.controllerToggleSneak != sprintToggled)
client.options.getSprintToggled().setValue(!sprintToggled);
binding.setPressed(button.pressed);
if (client.player.getAbilities().flying && sprintToggled)
client.options.getSprintToggled().setValue(true);
else if (MidnightControlsConfig.controllerToggleSneak != sprintToggled)
client.options.getSprintToggled().setValue(sprintToggled);
});
return true;
}
public static PressAction handleInventorySlotPad(int direction) {
return (client, binding, value, action) -> {
if (!(client.currentScreen instanceof HandledScreen inventory && action != ButtonState.RELEASE))
return false;
var accessor = (HandledScreenAccessor) inventory;
int guiLeft = accessor.getX();
int guiTop = accessor.getY();
double mouseX = client.mouse.getX() * (double) client.getWindow().getScaledWidth() / (double) client.getWindow().getWidth();
double mouseY = client.mouse.getY() * (double) client.getWindow().getScaledHeight() / (double) client.getWindow().getHeight();
// Finds the hovered slot.
var mouseSlot = accessor.midnightcontrols$getSlotAt(mouseX, mouseY);
// Finds the closest slot in the GUI within 14 pixels.
Optional<Slot> closestSlot = inventory.getScreenHandler().slots.parallelStream()
.filter(Predicate.isEqual(mouseSlot).negate())
.map(slot -> {
int posX = guiLeft + slot.x + 8;
int posY = guiTop + slot.y + 8;
int otherPosX = (int) mouseX;
int otherPosY = (int) mouseY;
if (mouseSlot != null) {
otherPosX = guiLeft + mouseSlot.x + 8;
otherPosY = guiTop + mouseSlot.y + 8;
}
// Distance between the slot and the cursor.
double distance = Math.sqrt(Math.pow(posX - otherPosX, 2) + Math.pow(posY - otherPosY, 2));
return Pair.of(slot, distance);
}).filter(entry -> {
var slot = entry.key;
int posX = guiLeft + slot.x + 8;
int posY = guiTop + slot.y + 8;
int otherPosX = (int) mouseX;
int otherPosY = (int) mouseY;
if (mouseSlot != null) {
otherPosX = guiLeft + mouseSlot.x + 8;
otherPosY = guiTop + mouseSlot.y + 8;
}
if (direction == 0)
return posY < otherPosY;
else if (direction == 1)
return posY > otherPosY;
else if (direction == 2)
return posX > otherPosX;
else if (direction == 3)
return posX < otherPosX;
else
return false;
})
.min(Comparator.comparingDouble(p -> p.value))
.map(p -> p.key);
if (closestSlot.isPresent()) {
var slot = closestSlot.get();
int x = guiLeft + slot.x + 8;
int y = guiTop + slot.y + 8;
InputManager.queueMousePosition(x * (double) client.getWindow().getWidth() / (double) client.getWindow().getScaledWidth(),
y * (double) client.getWindow().getHeight() / (double) client.getWindow().getScaledHeight());
return true;
}
return false;
};
}
/**
* Returns always true to the filter.
*
* @param client the client instance
* @param binding the affected binding
* @return true
*/
public static boolean always(@NotNull MinecraftClient client, @NotNull ButtonBinding binding) {
return true;
}
/**
* Returns whether the client is in game or not.
*
* @param client the client instance
* @param binding the affected binding
* @return true if the client is in game, else false
*/
public static boolean inGame(@NotNull MinecraftClient client, @NotNull ButtonBinding binding) {
return (client.currentScreen == null && MidnightControlsClient.get().input.screenCloseCooldown <= 0) || client.currentScreen instanceof RingScreen;
}
/**
* Returns whether the client is in a non-interactive screen (which means require mouse input) or not.
*
* @param client the client instance
* @param binding the affected binding
* @return true if the client is in a non-interactive screen, else false
*/
public static boolean inNonInteractiveScreens(@NotNull MinecraftClient client, @NotNull ButtonBinding binding) {
if (client.currentScreen == null)
return false;
return !MidnightInput.isScreenInteractive(client.currentScreen);
}
/**
* Returns whether the client is in an inventory or not.
*
* @param client the client instance
* @param binding the affected binding
* @return true if the client is in an inventory, else false
*/
public static boolean inInventory(@NotNull MinecraftClient client, @NotNull ButtonBinding binding) {
return client.currentScreen instanceof HandledScreen;
}
/**
* Returns whether the client is in the advancements screen or not.
*
* @param client the client instance
* @param binding the affected binding
* @return true if the client is in the advancements screen, else false
*/
public static boolean inAdvancements(@NotNull MinecraftClient client, @NotNull ButtonBinding binding) {
return client.currentScreen instanceof AdvancementsScreen;
}
}

View File

@@ -0,0 +1,413 @@
/*
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of midnightcontrols.
*
* Licensed under the MIT license. For more information,
* see the LICENSE file.
*/
package eu.midnightdust.midnightcontrols.client.controller;
import eu.midnightdust.midnightcontrols.ControlsMode;
import eu.midnightdust.midnightcontrols.client.ButtonState;
import eu.midnightdust.midnightcontrols.client.MidnightControlsConfig;
import eu.midnightdust.midnightcontrols.client.util.MouseAccessor;
import it.unimi.dsi.fastutil.ints.*;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.option.KeyBinding;
import net.minecraft.client.resource.language.I18n;
import net.minecraft.client.util.InputUtil;
import net.minecraft.util.math.MathHelper;
import org.aperlambda.lambdacommon.Identifier;
import org.aperlambda.lambdacommon.utils.function.PairPredicate;
import org.jetbrains.annotations.NotNull;
import org.lwjgl.glfw.GLFW;
import java.util.*;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* Represents an input manager for controllers.
*
* @author LambdAurora
* @version 1.7.0
* @since 1.1.0
*/
public class InputManager {
public static final InputManager INPUT_MANAGER = new InputManager();
private static final List<ButtonBinding> BINDINGS = new ArrayList<>();
private static final List<ButtonCategory> CATEGORIES = new ArrayList<>();
public static final Int2ObjectMap<ButtonState> STATES = new Int2ObjectOpenHashMap<>();
public static final Int2FloatMap BUTTON_VALUES = new Int2FloatOpenHashMap();
public int prevTargetMouseX = 0;
public int prevTargetMouseY = 0;
public int targetMouseX = 0;
public int targetMouseY = 0;
protected InputManager() {
}
public void tick(@NotNull MinecraftClient client) {
if (MidnightControlsConfig.autoSwitchMode && !MidnightControlsConfig.isEditing && MidnightControlsConfig.controlsMode != ControlsMode.TOUCHSCREEN)
if (MidnightControlsConfig.getController().isConnected() && MidnightControlsConfig.getController().isGamepad())
MidnightControlsConfig.controlsMode = ControlsMode.CONTROLLER;
else MidnightControlsConfig.controlsMode = ControlsMode.DEFAULT;
if (MidnightControlsConfig.controlsMode == ControlsMode.CONTROLLER) {
this.controllerTick(client);
}
}
public void controllerTick(@NotNull MinecraftClient client) {
this.prevTargetMouseX = this.targetMouseX;
this.prevTargetMouseY = this.targetMouseY;
}
/**
* Updates the mouse position. Should only be called on pre render of a screen.
*
* @param client the client instance
*/
public void updateMousePosition(@NotNull MinecraftClient client) {
Objects.requireNonNull(client, "Client instance cannot be null.");
if (this.prevTargetMouseX != this.targetMouseX || this.prevTargetMouseY != this.targetMouseY) {
double mouseX = this.prevTargetMouseX + (this.targetMouseX - this.prevTargetMouseX) * client.getTickDelta() + 0.5;
double mouseY = this.prevTargetMouseY + (this.targetMouseY - this.prevTargetMouseY) * client.getTickDelta() + 0.5;
if (!MidnightControlsConfig.virtualMouse)
GLFW.glfwSetCursorPos(client.getWindow().getHandle(), mouseX, mouseY);
((MouseAccessor) client.mouse).midnightcontrols$onCursorPos(client.getWindow().getHandle(), mouseX, mouseY);
}
}
/**
* Resets the mouse position.
*
* @param windowWidth the window width
* @param windowHeight the window height
*/
public void resetMousePosition(int windowWidth, int windowHeight) {
this.targetMouseX = this.prevTargetMouseX = (int) (windowWidth / 2.F);
this.targetMouseY = this.prevTargetMouseY = (int) (windowHeight / 2.F);
}
public void resetMouseTarget(@NotNull MinecraftClient client) {
double mouseX = client.mouse.getX();
double mouseY = client.mouse.getY();
this.prevTargetMouseX = this.targetMouseX = (int) mouseX;
this.prevTargetMouseY = this.targetMouseY = (int) mouseY;
}
/**
* Returns whether the specified binding is registered or not.
*
* @param binding the binding to check
* @return true if the binding is registered, else false
*/
public static boolean hasBinding(@NotNull ButtonBinding binding) {
return BINDINGS.contains(binding);
}
/**
* Returns whether the specified binding is registered or not.
*
* @param name the name of the binding to check
* @return true if the binding is registered, else false
*/
public static boolean hasBinding(@NotNull String name) {
return BINDINGS.parallelStream().map(ButtonBinding::getName).anyMatch(binding -> binding.equalsIgnoreCase(name));
}
/**
* Returns whether the specified binding is registered or not.
*
* @param identifier the identifier of the binding to check
* @return true if the binding is registered, else false
*/
public static boolean hasBinding(@NotNull Identifier identifier) {
return hasBinding(identifier.getNamespace() + "." + identifier.getName());
}
private static ButtonBinding tempBinding;
/**
* Returns the binding matching the given string.
*
* @param name the name of the binding to get
* @return true if the binding is registered, else false
*/
public static ButtonBinding getBinding(@NotNull String name) {
if (BINDINGS.parallelStream().map(ButtonBinding::getName).anyMatch(binding -> binding.equalsIgnoreCase(name)))
BINDINGS.forEach(binding -> {
if (binding.getName().equalsIgnoreCase(name)) InputManager.tempBinding = binding;
});
return tempBinding;
}
private static List<ButtonBinding> unboundBindings;
public static List<ButtonBinding> getUnboundBindings() {
unboundBindings = new ArrayList<>();
BINDINGS.forEach(binding -> {
if (binding.isNotBound() && !MidnightControlsConfig.ignoredUnboundKeys.contains(binding.getTranslationKey())) unboundBindings.add(binding);
});
unboundBindings.sort(Comparator.comparing(s -> I18n.translate(s.getTranslationKey())));
return unboundBindings;
}
/**
* Registers a button binding.
*
* @param binding the binding to register
* @return the registered binding
*/
public static @NotNull ButtonBinding registerBinding(@NotNull ButtonBinding binding) {
if (hasBinding(binding))
throw new IllegalStateException("Cannot register twice a button binding in the registry.");
BINDINGS.add(binding);
return binding;
}
public static @NotNull ButtonBinding registerBinding(@NotNull Identifier id, int[] defaultButton, @NotNull List<PressAction> actions, @NotNull PairPredicate<MinecraftClient, ButtonBinding> filter, boolean hasCooldown) {
return registerBinding(new ButtonBinding(id.getNamespace() + "." + id.getName(), defaultButton, actions, filter, hasCooldown));
}
public static @NotNull ButtonBinding registerBinding(@NotNull Identifier id, int[] defaultButton, boolean hasCooldown) {
return registerBinding(id, defaultButton, Collections.emptyList(), InputHandlers::always, hasCooldown);
}
public static @NotNull ButtonBinding registerBinding(@NotNull net.minecraft.util.Identifier id, int[] defaultButton, @NotNull List<PressAction> actions, @NotNull PairPredicate<MinecraftClient, ButtonBinding> filter, boolean hasCooldown) {
return registerBinding(new Identifier(id.getNamespace(), id.getPath()), defaultButton, actions, filter, hasCooldown);
}
public static @NotNull ButtonBinding registerBinding(@NotNull net.minecraft.util.Identifier id, int[] defaultButton, boolean hasCooldown) {
return registerBinding(id, defaultButton, Collections.emptyList(), InputHandlers::always, hasCooldown);
}
/**
* Sorts bindings to get bindings with the higher button counts first.
*/
public static void sortBindings() {
synchronized (BINDINGS) {
var sorted = BINDINGS.stream()
.sorted(Collections.reverseOrder(Comparator.comparingInt(binding -> binding.getButton().length))).toList();
BINDINGS.clear();
BINDINGS.addAll(sorted);
}
}
/**
* Registers a category of button bindings.
*
* @param category the category to register
* @return the registered category
*/
public static ButtonCategory registerCategory(@NotNull ButtonCategory category) {
CATEGORIES.add(category);
return category;
}
public static ButtonCategory registerCategory(@NotNull Identifier identifier, int priority) {
return registerCategory(new ButtonCategory(identifier, priority));
}
public static ButtonCategory registerCategory(@NotNull Identifier identifier) {
return registerCategory(new ButtonCategory(identifier));
}
protected static ButtonCategory registerDefaultCategory(@NotNull String key, @NotNull Consumer<ButtonCategory> keyAdder) {
var category = registerCategory(new Identifier("minecraft", key), CATEGORIES.size());
keyAdder.accept(category);
return category;
}
/**
* Loads the button bindings from configuration.
*/
public static void loadButtonBindings() {
var queue = new ArrayList<>(BINDINGS);
queue.forEach(MidnightControlsConfig::loadButtonBinding);
}
/**
* Returns the binding state.
*
* @param binding the binding
* @return the current state of the binding
*/
public static @NotNull ButtonState getBindingState(@NotNull ButtonBinding binding) {
var state = ButtonState.REPEAT;
for (int btn : binding.getButton()) {
var btnState = InputManager.STATES.getOrDefault(btn, ButtonState.NONE);
if (btnState == ButtonState.PRESS)
state = ButtonState.PRESS;
else if (btnState == ButtonState.RELEASE) {
state = ButtonState.RELEASE;
break;
} else if (btnState == ButtonState.NONE) {
state = ButtonState.NONE;
break;
}
}
return state;
}
public static float getBindingValue(@NotNull ButtonBinding binding, @NotNull ButtonState state) {
if (state.isUnpressed())
return 0.f;
float value = 0.f;
for (int btn : binding.getButton()) {
if (ButtonBinding.isAxis(btn)) {
value = BUTTON_VALUES.getOrDefault(btn, 1.f);
break;
} else {
value = 1.f;
}
}
return value;
}
/**
* Returns whether the button has duplicated bindings.
*
* @param button the button to check
* @return true if the button has duplicated bindings, else false
*/
public static boolean hasDuplicatedBindings(int[] button) {
return BINDINGS.parallelStream().filter(binding -> areButtonsEquivalent(binding.getButton(), button)).count() > 1;
}
/**
* Returns whether the button has duplicated bindings.
*
* @param binding the binding to check
* @return true if the button has duplicated bindings, else false
*/
public static boolean hasDuplicatedBindings(ButtonBinding binding) {
return BINDINGS.parallelStream().filter(other -> areButtonsEquivalent(other.getButton(), binding.getButton()) && other.filter.equals(binding.filter)).count() > 1;
}
/**
* Returns whether the specified buttons are equivalent or not.
*
* @param buttons1 first set of buttons
* @param buttons2 second set of buttons
* @return true if the two sets of buttons are equivalent, else false
*/
public static boolean areButtonsEquivalent(int[] buttons1, int[] buttons2) {
if (buttons1.length != buttons2.length)
return false;
int count = 0;
for (int btn : buttons1) {
for (int btn2 : buttons2) {
if (btn == btn2) {
count++;
break;
}
}
}
return count == buttons1.length;
}
/**
* Returns whether the button set contains the specified button or not.
*
* @param buttons the button set
* @param button the button to check
* @return true if the button set contains the specified button, else false
*/
public static boolean containsButton(int[] buttons, int button) {
return Arrays.stream(buttons).anyMatch(btn -> btn == button);
}
/**
* Updates the button states.
*/
public static void updateStates() {
for (var entry : STATES.int2ObjectEntrySet()) {
if (entry.getValue() == ButtonState.PRESS)
STATES.put(entry.getIntKey(), ButtonState.REPEAT);
else if (entry.getValue() == ButtonState.RELEASE)
STATES.put(entry.getIntKey(), ButtonState.NONE);
}
}
public static void updateBindings(@NotNull MinecraftClient client) {
var skipButtons = new IntArrayList();
record ButtonStateValue(ButtonState state, float value) {
}
var states = new Object2ObjectOpenHashMap<ButtonBinding, ButtonStateValue>();
for (var binding : BINDINGS) {
var state = binding.isAvailable(client) ? getBindingState(binding) : ButtonState.NONE;
if (skipButtons.intStream().anyMatch(btn -> containsButton(binding.getButton(), btn))) {
if (binding.pressed)
state = ButtonState.RELEASE;
else
state = ButtonState.NONE;
}
if (state == ButtonState.RELEASE && !binding.pressed) {
state = ButtonState.NONE;
}
binding.pressed = state.isPressed();
binding.update();
if (binding.pressed)
Arrays.stream(binding.getButton()).forEach(skipButtons::add);
float value = getBindingValue(binding, state);
states.put(binding, new ButtonStateValue(state, value));
}
states.forEach((binding, state) -> {
if (state.state() != ButtonState.NONE) {
binding.handle(client, state.value(), state.state());
}
});
}
public static void queueMousePosition(double x, double y) {
INPUT_MANAGER.targetMouseX = (int) MathHelper.clamp(x, 0, MinecraftClient.getInstance().getWindow().getWidth());
INPUT_MANAGER.targetMouseY = (int) MathHelper.clamp(y, 0, MinecraftClient.getInstance().getWindow().getHeight());
}
public static void queueMoveMousePosition(double x, double y) {
queueMousePosition(INPUT_MANAGER.targetMouseX + x, INPUT_MANAGER.targetMouseY + y);
}
public static @NotNull Stream<ButtonBinding> streamBindings() {
return BINDINGS.stream();
}
public static @NotNull Stream<ButtonCategory> streamCategories() {
return CATEGORIES.stream();
}
/**
* Returns a new key binding instance.
*
* @param id the identifier of the key binding
* @param type the type
* @param code the code
* @param category the category of the key binding
* @return the key binding
* @see #makeKeyBinding(Identifier, InputUtil.Type, int, String)
*/
public static @NotNull KeyBinding makeKeyBinding(@NotNull net.minecraft.util.Identifier id, InputUtil.Type type, int code, @NotNull String category) {
return makeKeyBinding(new Identifier(id.getNamespace(), id.getPath()), type, code, category);
}
/**
* Returns a new key binding instance.
*
* @param id the identifier of the key binding
* @param type the type
* @param code the code
* @param category the category of the key binding
* @return the key binding
* @see #makeKeyBinding(net.minecraft.util.Identifier, InputUtil.Type, int, String)
*/
public static @NotNull KeyBinding makeKeyBinding(@NotNull Identifier id, InputUtil.Type type, int code, @NotNull String category) {
return new KeyBinding(String.format("key.%s.%s", id.getNamespace(), id.getName()), type, code, category);
}
}

View File

@@ -0,0 +1,115 @@
/*
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of midnightcontrols.
*
* Licensed under the MIT license. For more information,
* see the LICENSE file.
*/
package eu.midnightdust.midnightcontrols.client.controller;
import eu.midnightdust.midnightcontrols.client.ButtonState;
import eu.midnightdust.midnightcontrols.client.MidnightControlsClient;
import eu.midnightdust.midnightcontrols.client.MidnightControlsConfig;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.input.Input;
import net.minecraft.client.network.ClientPlayerEntity;
import net.minecraft.client.tutorial.MovementTutorialStepHandler;
import net.minecraft.client.util.InputUtil;
import net.minecraft.enchantment.EnchantmentHelper;
import net.minecraft.enchantment.Enchantments;
import net.minecraft.enchantment.SoulSpeedEnchantment;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.nbt.NbtList;
import net.minecraft.nbt.scanner.SimpleNbtScanner;
import net.minecraft.util.math.MathHelper;
import org.jetbrains.annotations.NotNull;
/**
* Represents the movement handler.
*
* @author LambdAurora
* @version 1.6.0
* @since 1.4.0
*/
public final class MovementHandler implements PressAction {
public static final MovementHandler HANDLER = new MovementHandler();
private boolean shouldOverrideMovement = false;
private boolean pressingForward = false;
private boolean pressingBack = false;
private boolean pressingLeft = false;
private boolean pressingRight = false;
private float slowdownFactor = 1.f;
private float movementForward = 0.f;
private float movementSideways = 0.f;
private MovementHandler() {
}
/**
* Applies movement input of this handler to the player's input.
*
* @param player The client player.
*/
public void applyMovement(@NotNull ClientPlayerEntity player) {
double movementR, movementTheta;
if (!this.shouldOverrideMovement)
return;
player.input.pressingForward = this.pressingForward;
player.input.pressingBack = this.pressingBack;
player.input.pressingLeft = this.pressingLeft;
player.input.pressingRight = this.pressingRight;
// Do an implicit square here
movementR = this.slowdownFactor * (Math.pow(this.movementSideways, 2) + Math.pow(this.movementForward, 2));
movementR = MathHelper.clamp(movementR, 0.f, 1.f);
movementTheta = Math.atan2(this.movementForward, this.movementSideways);
player.input.movementForward = (float) (movementR * Math.sin(movementTheta));
player.input.movementSideways = (float) (movementR * Math.cos(movementTheta));
this.shouldOverrideMovement = false;
}
@Override
public boolean press(@NotNull MinecraftClient client, @NotNull ButtonBinding button, float value, @NotNull ButtonState action) {
if (client.currentScreen != null || client.player == null)
return this.shouldOverrideMovement = false;
int direction = 0;
if (button == ButtonBinding.FORWARD || button == ButtonBinding.LEFT)
direction = 1;
else if (button == ButtonBinding.BACK || button == ButtonBinding.RIGHT)
direction = -1;
if (action.isUnpressed())
direction = 0;
this.shouldOverrideMovement = direction != 0;
if (!MidnightControlsConfig.analogMovement) {
value = 1.f;
}
this.slowdownFactor = client.player.shouldSlowDown() ? (MathHelper.clamp(
0.3F + EnchantmentHelper.getSwiftSneakSpeedBoost(client.player),
0.0F,
1.0F
)) : 1.f;
if (button == ButtonBinding.FORWARD || button == ButtonBinding.BACK) {
// Handle forward movement.
this.pressingForward = direction > 0;
this.pressingBack = direction < 0;
this.movementForward = direction * value;
} else {
// Handle sideways movement.
this.pressingLeft = direction > 0;
this.pressingRight = direction < 0;
this.movementSideways = direction * value;
}
return this.shouldOverrideMovement;
}
}

View File

@@ -0,0 +1,46 @@
/*
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of midnightcontrols.
*
* Licensed under the MIT license. For more information,
* see the LICENSE file.
*/
package eu.midnightdust.midnightcontrols.client.controller;
import eu.midnightdust.midnightcontrols.client.ButtonState;
import eu.midnightdust.midnightcontrols.client.util.KeyBindingAccessor;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.option.StickyKeyBinding;
import org.jetbrains.annotations.NotNull;
/**
* Represents a press action callback.
*
* @author LambdAurora
* @version 1.7.0
* @since 1.0.0
*/
@FunctionalInterface
public interface PressAction {
PressAction DEFAULT_ACTION = (client, button, value, action) -> {
if (action == ButtonState.REPEAT || client.currentScreen != null)
return false;
button.asKeyBinding().ifPresent(binding -> {
if (binding instanceof StickyKeyBinding)
binding.setPressed(button.pressed);
else
((KeyBindingAccessor) binding).midnightcontrols$handlePressState(button.isButtonDown());
});
return true;
};
/**
* Handles when there is a press action.
*
* @param client the client instance
* @param action the action done
*/
boolean press(@NotNull MinecraftClient client, @NotNull ButtonBinding button, float value, @NotNull ButtonState action);
}

View File

@@ -0,0 +1,104 @@
/*
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of midnightcontrols.
*
* Licensed under the MIT license. For more information,
* see the LICENSE file.
*/
package eu.midnightdust.midnightcontrols.client.gui;
import eu.midnightdust.midnightcontrols.client.MidnightControlsClient;
import eu.midnightdust.midnightcontrols.client.controller.Controller;
import dev.lambdaurora.spruceui.Position;
import dev.lambdaurora.spruceui.option.SpruceOption;
import dev.lambdaurora.spruceui.widget.container.SpruceContainerWidget;
import dev.lambdaurora.spruceui.widget.text.SpruceTextAreaWidget;
import net.minecraft.client.toast.SystemToast;
import net.minecraft.text.Text;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.file.Files;
/**
* Represents the controller mappings file editor screen.
*
* @author LambdAurora
* @version 1.7.0
* @since 1.4.3
*/
public class MappingsStringInputWidget extends SpruceContainerWidget {
private final SpruceOption reloadMappingsOption;
private String mappings;
private SpruceTextAreaWidget textArea;
protected MappingsStringInputWidget(Position position, int width, int height) {
super(position, width, height);
//super(new TranslatableText("midnightcontrols.menu.title.mappings.string"));
this.reloadMappingsOption = ReloadControllerMappingsOption.newOption(btn -> {
this.writeMappings();
});
this.init();
}
public void removed() {
this.writeMappings();
Controller.updateMappings();
}
public void onClose() {
this.removed();
}
public void writeMappings() {
if (this.textArea != null) {
this.mappings = this.textArea.getText();
try {
var fw = new FileWriter(MidnightControlsClient.MAPPINGS_FILE, false);
fw.write(this.mappings);
fw.close();
} catch (IOException e) {
if (this.client != null)
this.client.getToastManager().add(SystemToast.create(this.client, SystemToast.Type.TUTORIAL_HINT,
Text.translatable("midnightcontrols.controller.mappings.error.write"), Text.empty()));
e.printStackTrace();
}
}
}
protected void init() {
if (this.textArea != null) {
this.mappings = this.textArea.getText();
}
var mappings = "";
if (this.mappings != null)
mappings = this.mappings;
else if (MidnightControlsClient.MAPPINGS_FILE.exists()) {
try {
mappings = String.join("\n", Files.readAllLines(MidnightControlsClient.MAPPINGS_FILE.toPath()));
this.mappings = mappings;
} catch (IOException e) {
/* Ignored */
}
}
int textFieldWidth = (int) (this.width * (5.0 / 6.0));
this.textArea = new SpruceTextAreaWidget(Position.of(this, this.width / 2 - textFieldWidth / 2, 0), textFieldWidth, this.height - 50, Text.literal(mappings));
this.textArea.setText(mappings);
// Display as many lines as possible
this.textArea.setDisplayedLines(this.textArea.getInnerHeight() / this.client.textRenderer.fontHeight);
this.addChild(this.textArea);
this.addChild(this.reloadMappingsOption.createWidget(Position.of(this.width / 2 - 155, this.height - 29), 310));
}
/*public void renderTitle(MatrixStack matrices, int mouseX, int mouseY, float delta) {
drawCenteredText(matrices, this.textRenderer, this.title, this.width / 2, 8, 16777215);
}*/
}

View File

@@ -0,0 +1,300 @@
/*
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of midnightcontrols.
*
* Licensed under the MIT license. For more information,
* see the LICENSE file.
*/
package eu.midnightdust.midnightcontrols.client.gui;
import eu.midnightdust.midnightcontrols.ControlsMode;
import eu.midnightdust.midnightcontrols.MidnightControlsConstants;
import eu.midnightdust.midnightcontrols.client.HudSide;
import eu.midnightdust.midnightcontrols.client.MidnightControlsClient;
import eu.midnightdust.midnightcontrols.client.MidnightControlsConfig;
import eu.midnightdust.midnightcontrols.client.compat.MidnightControlsCompat;
import eu.midnightdust.midnightcontrols.client.controller.ButtonBinding;
import dev.lambdaurora.spruceui.hud.Hud;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.resource.language.I18n;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.item.BlockItem;
import net.minecraft.item.ItemStack;
import net.minecraft.util.Identifier;
import net.minecraft.util.hit.BlockHitResult;
import net.minecraft.util.hit.HitResult;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Represents the midnightcontrols HUD.
*
* @author LambdAurora
* @version 1.7.0
* @since 1.0.0
*/
public class MidnightControlsHud extends Hud {
private final MidnightControlsClient mod;
private MinecraftClient client;
private int attackWidth = 0;
private int attackButtonWidth = 0;
private int dropItemWidth = 0;
private int dropItemButtonWidth = 0;
private int inventoryWidth = 0;
private int inventoryButtonWidth = 0;
private int swapHandsWidth = 0;
private int swapHandsButtonWidth = 0;
private boolean showSwapHandsAction = false;
private int useWidth = 0;
private int useButtonWidth = 0;
private BlockHitResult placeHitResult;
private String attackAction = "";
private String placeAction = "";
private int ticksDisplayedCrosshair = 0;
private static boolean isCrammed = false;
public MidnightControlsHud(@NotNull MidnightControlsClient mod) {
super(new Identifier(MidnightControlsConstants.NAMESPACE, "hud/button_indicator"));
this.mod = mod;
}
@Override
public void init(@NotNull MinecraftClient client, int screenWidth, int screenHeight) {
super.init(client, screenWidth, screenHeight);
this.client = client;
this.inventoryWidth = this.width(ButtonBinding.INVENTORY);
this.inventoryButtonWidth = MidnightControlsRenderer.getBindingIconWidth(ButtonBinding.INVENTORY);
this.swapHandsWidth = this.width(ButtonBinding.SWAP_HANDS);
this.swapHandsButtonWidth = MidnightControlsRenderer.getBindingIconWidth(ButtonBinding.SWAP_HANDS);
this.dropItemWidth = this.width(ButtonBinding.DROP_ITEM);
this.dropItemButtonWidth = MidnightControlsRenderer.getBindingIconWidth(ButtonBinding.DROP_ITEM);
this.attackButtonWidth = MidnightControlsRenderer.getBindingIconWidth(ButtonBinding.ATTACK);
this.useButtonWidth = MidnightControlsRenderer.getBindingIconWidth(ButtonBinding.USE);
}
/**
* Renders the MidnightControls HUD.
*/
@Override
public void render(MatrixStack matrices, float tickDelta) {
if (this.client == null) return;
if (MidnightControlsConfig.controlsMode == ControlsMode.CONTROLLER && this.client.currentScreen == null) {
isCrammed = client.getWindow().getScaledWidth() < 520;
int y = bottom(2);
matrices.push();
this.renderFirstIcons(matrices, MidnightControlsConfig.hudSide == HudSide.LEFT ? 2 : client.getWindow().getScaledWidth() - 2, y);
this.renderSecondIcons(matrices, MidnightControlsConfig.hudSide == HudSide.RIGHT ? 2 : client.getWindow().getScaledWidth() - 2, y);
this.renderFirstSection(matrices, MidnightControlsConfig.hudSide == HudSide.LEFT ? 2 : client.getWindow().getScaledWidth() - 2, y);
this.renderSecondSection(matrices, MidnightControlsConfig.hudSide == HudSide.RIGHT ? 2 : client.getWindow().getScaledWidth() - 2, y);
matrices.pop();
}
if (this.mod.reacharound.isLastReacharoundVertical()) {
// Render crosshair indicator.
var window = this.client.getWindow();
var text = "[ ]";
float scale = Math.min(5, this.ticksDisplayedCrosshair + tickDelta) / 5F;
scale *= scale;
int opacity = ((int) (255 * scale)) << 24;
this.client.textRenderer.draw(matrices, text, window.getScaledWidth() / 2.f - this.client.textRenderer.getWidth(text) / 2.f,
window.getScaledHeight() / 2.f - 4, 0xCCCCCC | opacity);
}
}
public void renderFirstIcons(MatrixStack matrices, int x, int y) {
int offset = 2 + this.inventoryWidth + this.inventoryButtonWidth + 4;
int currentX = MidnightControlsConfig.hudSide == HudSide.LEFT ? x : x - this.inventoryButtonWidth;
if (!ButtonBinding.INVENTORY.isNotBound()) this.drawButton(matrices, currentX, y, ButtonBinding.INVENTORY, true);
if (!ButtonBinding.SWAP_HANDS.isNotBound() && !isCrammed && showSwapHandsAction) this.drawButton(matrices, currentX += (MidnightControlsConfig.hudSide == HudSide.LEFT ? offset : -offset), y, ButtonBinding.SWAP_HANDS, true);
offset = 2 + this.swapHandsWidth + this.dropItemButtonWidth + 4;
if (this.client.options.getShowSubtitles().getValue() && MidnightControlsConfig.hudSide == HudSide.RIGHT) {
currentX += -offset;
} else {
currentX = MidnightControlsConfig.hudSide == HudSide.LEFT ? x : x - this.dropItemButtonWidth;
y -= 20;
}
if (!ButtonBinding.DROP_ITEM.isNotBound()) this.drawButton(matrices, currentX, y, ButtonBinding.DROP_ITEM, !this.client.player.getMainHandStack().isEmpty());
}
public void renderSecondIcons(MatrixStack matrices, int x, int y) {
int offset;
int currentX = x;
if (isCrammed && showSwapHandsAction && !this.client.options.getShowSubtitles().getValue() && !ButtonBinding.SWAP_HANDS.isNotBound()) {
if (MidnightControlsConfig.hudSide == HudSide.LEFT)
currentX -= this.useButtonWidth;
this.drawButton(matrices, currentX, y, ButtonBinding.SWAP_HANDS, true);
currentX = x;
y -= 20;
}
if (!this.placeAction.isEmpty() && (!ButtonBinding.USE.isNotBound()) ) {
if (MidnightControlsConfig.hudSide == HudSide.LEFT)
currentX -= this.useButtonWidth;
this.drawButton(matrices, currentX, y, ButtonBinding.USE, true);
offset = 2 + this.useWidth + 4;
if (this.client.options.getShowSubtitles().getValue() && MidnightControlsConfig.hudSide == HudSide.LEFT) {
currentX -= offset;
} else {
currentX = x;
y -= 20;
}
}
if (MidnightControlsConfig.hudSide == HudSide.LEFT)
currentX -= this.attackButtonWidth;
if (!ButtonBinding.ATTACK.isNotBound()) this.drawButton(matrices, currentX, y, ButtonBinding.ATTACK, this.attackWidth != 0);
}
public void renderFirstSection(MatrixStack matrices, int x, int y) {
int currentX = MidnightControlsConfig.hudSide == HudSide.LEFT ? x + this.inventoryButtonWidth + 2 : x - this.inventoryButtonWidth - 2 - this.inventoryWidth;
if (!ButtonBinding.INVENTORY.isNotBound()) this.drawTip(matrices, currentX, y, ButtonBinding.INVENTORY, true);
currentX += MidnightControlsConfig.hudSide == HudSide.LEFT ? this.inventoryWidth + 4 + this.swapHandsButtonWidth + 2
: -this.swapHandsWidth - 2 - this.swapHandsButtonWidth - 4;
if (!ButtonBinding.SWAP_HANDS.isNotBound() && !isCrammed && showSwapHandsAction) this.drawTip(matrices, currentX, y, ButtonBinding.SWAP_HANDS, true);
if (this.client.options.getShowSubtitles().getValue() && MidnightControlsConfig.hudSide == HudSide.RIGHT) {
currentX += -this.dropItemWidth - 2 - this.dropItemButtonWidth - 4;
} else {
y -= 20;
currentX = MidnightControlsConfig.hudSide == HudSide.LEFT ? x + this.dropItemButtonWidth + 2 : x - this.dropItemButtonWidth - 2 - this.dropItemWidth;
}
if (!ButtonBinding.DROP_ITEM.isNotBound()) this.drawTip(matrices, currentX, y, ButtonBinding.DROP_ITEM, !this.client.player.getMainHandStack().isEmpty());
}
public void renderSecondSection(MatrixStack matrices, int x, int y) {
int currentX = x;
if (isCrammed && showSwapHandsAction && !this.client.options.getShowSubtitles().getValue() && !ButtonBinding.SWAP_HANDS.isNotBound()) {
currentX += MidnightControlsConfig.hudSide == HudSide.RIGHT ? this.swapHandsButtonWidth + 2 : -this.swapHandsButtonWidth - 2 - this.swapHandsWidth;
this.drawTip(matrices, currentX, y, ButtonBinding.SWAP_HANDS, true);
currentX = x;
y -= 20;
}
if (!this.placeAction.isEmpty()) {
currentX += MidnightControlsConfig.hudSide == HudSide.RIGHT ? this.useButtonWidth + 2 : -this.useButtonWidth - 2 - this.useWidth;
this.drawTip(matrices, currentX, y, this.placeAction, true);
if (this.client.options.getShowSubtitles().getValue() && MidnightControlsConfig.hudSide == HudSide.LEFT) {
currentX -= 4;
} else {
currentX = x;
y -= 20;
}
}
currentX += MidnightControlsConfig.hudSide == HudSide.RIGHT ? this.attackButtonWidth + 2 : -this.attackButtonWidth - 2 - this.attackWidth;
if (!ButtonBinding.ATTACK.isNotBound()) this.drawTip(matrices, currentX, y, this.attackAction, this.attackWidth != 0);
}
@Override
public void tick() {
super.tick();
if (MidnightControlsConfig.controlsMode == ControlsMode.CONTROLLER) {
if (this.client.crosshairTarget == null)
return;
String placeAction;
// Update "Use" tip status.
if (this.client.crosshairTarget.getType() == HitResult.Type.MISS) {
this.placeHitResult = this.mod.reacharound.getLastReacharoundResult();
this.attackAction = "";
this.attackWidth = 0;
} else {
if (this.client.crosshairTarget.getType() == HitResult.Type.BLOCK)
this.placeHitResult = (BlockHitResult) this.client.crosshairTarget;
else
this.placeHitResult = null;
this.attackAction = this.client.crosshairTarget.getType() == HitResult.Type.BLOCK ? "midnightcontrols.action.hit" : ButtonBinding.ATTACK.getTranslationKey();
this.attackWidth = this.width(attackAction);
}
if (this.mod.reacharound.isLastReacharoundVertical()) {
if (this.ticksDisplayedCrosshair < 5)
this.ticksDisplayedCrosshair++;
} else {
this.ticksDisplayedCrosshair = 0;
}
var customAttackAction = MidnightControlsCompat.getAttackActionAt(this.client, this.placeHitResult);
if (customAttackAction != null) {
this.attackAction = customAttackAction;
this.attackWidth = this.width(customAttackAction);
}
ItemStack stack = null;
if (this.client.player != null) {
stack = this.client.player.getMainHandStack();
if (stack == null || stack.isEmpty())
stack = this.client.player.getOffHandStack();
}
if (stack == null || stack.isEmpty()) {
placeAction = "";
} else {
if (this.placeHitResult != null && stack.getItem() instanceof BlockItem) {
placeAction = "midnightcontrols.action.place";
} else {
placeAction = ButtonBinding.USE.getTranslationKey();
}
}
var customUseAction = MidnightControlsCompat.getUseActionAt(this.client, this.placeHitResult);
if (customUseAction != null)
placeAction = customUseAction;
this.placeAction = placeAction;
this.showSwapHandsAction = !this.client.player.getMainHandStack().isEmpty() || !this.client.player.getOffHandStack().isEmpty();
// Cache the "Use" tip width.
if (this.placeAction.isEmpty())
this.useWidth = 0;
else
this.useWidth = this.width(this.placeAction);
}
}
@Override
public boolean hasTicks() {
return true;
}
private int bottom(int y) {
return (this.client.getWindow().getScaledHeight() - y - MidnightControlsRenderer.ICON_SIZE);
}
private int width(@NotNull ButtonBinding binding) {
return this.width(binding.getTranslationKey());
}
private int width(@Nullable String text) {
if (text == null || text.isEmpty())
return 0;
return this.client.textRenderer.getWidth(I18n.translate(text));
}
private void drawButton(MatrixStack matrices, int x, int y, @NotNull ButtonBinding button, boolean display) {
if (display)
MidnightControlsRenderer.drawButton(matrices, x, y, button, this.client);
}
private void drawTip(MatrixStack matrices, int x, int y, @NotNull ButtonBinding button, boolean display) {
this.drawTip(matrices, x, y, button.getTranslationKey(), display);
}
private void drawTip(MatrixStack matrices, int x, int y, @NotNull String action, boolean display) {
if (!display)
return;
var translatedAction = I18n.translate(action);
int textY = (MidnightControlsRenderer.ICON_SIZE / 2 - this.client.textRenderer.fontHeight / 2) + 1;
this.client.textRenderer.draw(matrices, translatedAction, (float) x, (float) (y + textY), 14737632);
}
}

View File

@@ -0,0 +1,261 @@
/*
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of midnightcontrols.
*
* Licensed under the MIT license. For more information,
* see the LICENSE file.
*/
package eu.midnightdust.midnightcontrols.client.gui;
import com.mojang.blaze3d.systems.RenderSystem;
import eu.midnightdust.midnightcontrols.MidnightControls;
import eu.midnightdust.midnightcontrols.client.ControllerType;
import eu.midnightdust.midnightcontrols.client.MidnightControlsClient;
import eu.midnightdust.midnightcontrols.client.MidnightControlsConfig;
import eu.midnightdust.midnightcontrols.client.MidnightInput;
import eu.midnightdust.midnightcontrols.client.compat.MidnightControlsCompat;
import eu.midnightdust.midnightcontrols.client.controller.ButtonBinding;
import eu.midnightdust.midnightcontrols.client.util.HandledScreenAccessor;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.font.TextRenderer;
import net.minecraft.client.gui.DrawableHelper;
import net.minecraft.client.resource.language.I18n;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.screen.slot.Slot;
import org.jetbrains.annotations.NotNull;
import org.lwjgl.glfw.GLFW;
/**
* Represents the midnightcontrols renderer.
*
* @author LambdAurora
* @version 1.7.0
* @since 1.2.0
*/
public class MidnightControlsRenderer {
public static final int ICON_SIZE = 20;
private static final int BUTTON_SIZE = 15;
private static final int AXIS_SIZE = 18;
public static int getButtonSize(int button) {
return switch (button) {
case -1 -> 0;
case GLFW.GLFW_GAMEPAD_AXIS_LEFT_X + 100, GLFW.GLFW_GAMEPAD_AXIS_LEFT_X + 200,
GLFW.GLFW_GAMEPAD_AXIS_LEFT_Y + 100, GLFW.GLFW_GAMEPAD_AXIS_LEFT_Y + 200,
GLFW.GLFW_GAMEPAD_AXIS_RIGHT_X + 100, GLFW.GLFW_GAMEPAD_AXIS_RIGHT_X + 200,
GLFW.GLFW_GAMEPAD_AXIS_RIGHT_Y + 100, GLFW.GLFW_GAMEPAD_AXIS_RIGHT_Y + 200 -> AXIS_SIZE;
default -> BUTTON_SIZE;
};
}
/**
* Gets the binding icon width.
*
* @param binding the binding
* @return the width
*/
public static int getBindingIconWidth(@NotNull ButtonBinding binding) {
return getBindingIconWidth(binding.getButton());
}
/**
* Gets the binding icon width.
*
* @param buttons the buttons
* @return the width
*/
public static int getBindingIconWidth(int[] buttons) {
int width = 0;
for (int i = 0; i < buttons.length; i++) {
width += ICON_SIZE;
if (i + 1 < buttons.length) {
width += 2;
}
}
return width;
}
public static ButtonSize drawButton(MatrixStack matrices, int x, int y, @NotNull ButtonBinding button, @NotNull MinecraftClient client) {
return drawButton(matrices, x, y, button.getButton(), client);
}
public static ButtonSize drawButton(MatrixStack matrices, int x, int y, int[] buttons, @NotNull MinecraftClient client) {
int height = 0;
int length = 0;
int currentX = x;
for (int i = 0; i < buttons.length; i++) {
int btn = buttons[i];
int size = drawButton(matrices, currentX, y, btn, client);
if (size > height)
height = size;
length += size;
if (i + 1 < buttons.length) {
length += 2;
currentX = x + length;
}
}
return new ButtonSize(length, height);
}
public static int drawButton(MatrixStack matrices, int x, int y, int button, @NotNull MinecraftClient client) {
boolean second = false;
if (button == -1)
return 0;
else if (button >= 500) {
button -= 1000;
second = true;
}
int controllerType = MidnightControlsConfig.controllerType == ControllerType.DEFAULT ? MidnightControlsConfig.matchControllerToType().getId() : MidnightControlsConfig.controllerType.getId();
boolean axis = false;
int buttonOffset = button * 15;
switch (button) {
case 15 -> buttonOffset = 0;
case 16 -> buttonOffset = 18;
case 17 -> buttonOffset = 36;
case 18 -> buttonOffset = 54;
case GLFW.GLFW_GAMEPAD_BUTTON_LEFT_BUMPER -> buttonOffset = 7 * 15;
case GLFW.GLFW_GAMEPAD_BUTTON_RIGHT_BUMPER -> buttonOffset = 8 * 15;
case GLFW.GLFW_GAMEPAD_BUTTON_BACK -> buttonOffset = 4 * 15;
case GLFW.GLFW_GAMEPAD_BUTTON_START -> buttonOffset = 6 * 15;
case GLFW.GLFW_GAMEPAD_BUTTON_GUIDE -> buttonOffset = 5 * 15;
case GLFW.GLFW_GAMEPAD_BUTTON_LEFT_THUMB -> buttonOffset = 15 * 15;
case GLFW.GLFW_GAMEPAD_BUTTON_RIGHT_THUMB -> buttonOffset = 16 * 15;
case GLFW.GLFW_GAMEPAD_AXIS_LEFT_X + 100 -> {
buttonOffset = 0;
axis = true;
}
case GLFW.GLFW_GAMEPAD_AXIS_LEFT_Y + 100 -> {
buttonOffset = 18;
axis = true;
}
case GLFW.GLFW_GAMEPAD_AXIS_RIGHT_X + 100 -> {
buttonOffset = 2 * 18;
axis = true;
}
case GLFW.GLFW_GAMEPAD_AXIS_RIGHT_Y + 100 -> {
buttonOffset = 3 * 18;
axis = true;
}
case GLFW.GLFW_GAMEPAD_AXIS_LEFT_X + 200 -> {
buttonOffset = 4 * 18;
axis = true;
}
case GLFW.GLFW_GAMEPAD_AXIS_LEFT_Y + 200 -> {
buttonOffset = 5 * 18;
axis = true;
}
case GLFW.GLFW_GAMEPAD_AXIS_RIGHT_X + 200 -> {
buttonOffset = 6 * 18;
axis = true;
}
case GLFW.GLFW_GAMEPAD_AXIS_RIGHT_Y + 200 -> {
buttonOffset = 7 * 18;
axis = true;
}
case GLFW.GLFW_GAMEPAD_AXIS_LEFT_TRIGGER + 100, GLFW.GLFW_GAMEPAD_AXIS_LEFT_TRIGGER + 200 -> buttonOffset = 9 * 15;
case GLFW.GLFW_GAMEPAD_AXIS_RIGHT_TRIGGER + 100, GLFW.GLFW_GAMEPAD_AXIS_RIGHT_TRIGGER + 200 -> buttonOffset = 10 * 15;
}
RenderSystem.setShaderTexture(0, axis ? MidnightControlsClient.CONTROLLER_AXIS : button >= 15 && button <= 19 ? MidnightControlsClient.CONTROLLER_EXPANDED :MidnightControlsClient.CONTROLLER_BUTTONS);
RenderSystem.disableDepthTest();
int assetSize = axis || (button >= 15 && button <= 18) ? AXIS_SIZE : BUTTON_SIZE;
RenderSystem.setShaderColor(1.f, second ? 0.f : 1.f, 1.f, 1.f);
DrawableHelper.drawTexture(matrices, x + (ICON_SIZE / 2 - assetSize / 2), y + (ICON_SIZE / 2 - assetSize / 2),
(float) buttonOffset, (float) (controllerType * assetSize),
assetSize, assetSize,
256, 256);
RenderSystem.enableDepthTest();
return ICON_SIZE;
}
public static int drawButtonTip(MatrixStack matrices, int x, int y, @NotNull ButtonBinding button, boolean display, @NotNull MinecraftClient client) {
return drawButtonTip(matrices, x, y, button.getButton(), button.getTranslationKey(), display, client);
}
public static int drawButtonTip(MatrixStack matrices, int x, int y, int[] button, @NotNull String action, boolean display, @NotNull MinecraftClient client) {
if (display) {
int buttonWidth = drawButton(matrices, x, y, button, client).length();
var translatedAction = I18n.translate(action);
int textY = (MidnightControlsRenderer.ICON_SIZE / 2 - client.textRenderer.fontHeight / 2) + 1;
return client.textRenderer.drawWithShadow(matrices, translatedAction, (float) (x + buttonWidth + 2), (float) (y + textY), 14737632);
}
return -10;
}
private static int getButtonTipWidth(@NotNull String action, @NotNull TextRenderer textRenderer) {
return 15 + 5 + textRenderer.getWidth(action);
}
public static void renderVirtualCursor(@NotNull MatrixStack matrices, @NotNull MinecraftClient client) {
if (!MidnightControlsConfig.virtualMouse || (client.currentScreen == null
|| MidnightInput.isScreenInteractive(client.currentScreen)))
return;
int mouseX = (int) (client.mouse.getX() * (double) client.getWindow().getScaledWidth() / (double) client.getWindow().getWidth());
int mouseY = (int) (client.mouse.getY() * (double) client.getWindow().getScaledHeight() / (double) client.getWindow().getHeight());
boolean hoverSlot = false;
if (client.currentScreen instanceof HandledScreenAccessor inventoryScreen) {
int guiLeft = inventoryScreen.getX();
int guiTop = inventoryScreen.getY();
Slot slot = inventoryScreen.midnightcontrols$getSlotAt(mouseX, mouseY);
if (slot != null) {
mouseX = guiLeft + slot.x;
mouseY = guiTop + slot.y;
hoverSlot = true;
}
}
if (!hoverSlot) {
var slot = MidnightControlsCompat.getSlotAt(client.currentScreen, mouseX, mouseY);
if (slot != null) {
mouseX = slot.x();
mouseY = slot.y();
hoverSlot = true;
}
}
if (!hoverSlot) {
mouseX -= 8;
mouseY -= 8;
}
drawCursor(matrices, mouseX, mouseY, hoverSlot, client);
}
/**
* Draws the virtual cursor.
*
* @param matrices the matrix stack
* @param x x coordinate
* @param y y coordinate
* @param hoverSlot true if hovering a slot, else false
* @param client the client instance
*/
public static void drawCursor(@NotNull MatrixStack matrices, int x, int y, boolean hoverSlot, @NotNull MinecraftClient client) {
RenderSystem.disableDepthTest();
RenderSystem.setShaderColor(1.f, 1.f, 1.f, 1.f);
RenderSystem.setShaderTexture(0, MidnightControlsClient.CURSOR_TEXTURE);
DrawableHelper.drawTexture(matrices, x, y,
hoverSlot ? 16.f : 0.f, MidnightControlsConfig.virtualMouseSkin.ordinal() * 16.f,
16, 16, 32, 64);
RenderSystem.enableDepthTest();
}
public record ButtonSize(int length, int height) {
}
}

View File

@@ -0,0 +1,464 @@
/*
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of midnightcontrols.
*
* Licensed under the MIT license. For more information,
* see the LICENSE file.
*/
package eu.midnightdust.midnightcontrols.client.gui;
import com.mojang.blaze3d.systems.RenderSystem;
import dev.lambdaurora.spruceui.background.Background;
import dev.lambdaurora.spruceui.widget.SpruceWidget;
import eu.midnightdust.lib.util.MidnightColorUtil;
import eu.midnightdust.midnightcontrols.MidnightControls;
import eu.midnightdust.midnightcontrols.client.MidnightControlsClient;
import eu.midnightdust.midnightcontrols.client.MidnightControlsConfig;
import eu.midnightdust.midnightcontrols.client.controller.Controller;
import eu.midnightdust.midnightcontrols.client.gui.widget.ControllerControlsWidget;
import dev.lambdaurora.spruceui.Position;
import dev.lambdaurora.spruceui.SpruceTexts;
import dev.lambdaurora.spruceui.option.*;
import dev.lambdaurora.spruceui.screen.SpruceScreen;
import dev.lambdaurora.spruceui.widget.AbstractSpruceWidget;
import dev.lambdaurora.spruceui.widget.SpruceLabelWidget;
import dev.lambdaurora.spruceui.widget.container.SpruceContainerWidget;
import dev.lambdaurora.spruceui.widget.container.SpruceOptionListWidget;
import dev.lambdaurora.spruceui.widget.container.tabbed.SpruceTabbedWidget;
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gui.screen.Screen;
import net.minecraft.client.gui.widget.ButtonWidget;
import net.minecraft.client.render.*;
import net.minecraft.client.resource.language.I18n;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.text.MutableText;
import net.minecraft.text.Text;
import net.minecraft.util.Formatting;
import net.minecraft.util.Util;
import org.joml.Matrix4f;
import org.lwjgl.glfw.GLFW;
import java.awt.*;
/**
* Represents the midnightcontrols settings screen.
*/
public class MidnightControlsSettingsScreen extends SpruceScreen {
private static final Text SDL2_GAMEPAD_TOOL = Text.literal("SDL2 Gamepad Tool").formatted(Formatting.GREEN);
public static final String GAMEPAD_TOOL_URL = "https://generalarcade.com/gamepadtool/";
final MidnightControlsClient mod = MidnightControlsClient.get();
private final Screen parent;
// General options
private final SpruceOption inputModeOption;
private final SpruceOption autoSwitchModeOption;
private final SpruceOption rotationSpeedOption;
private final SpruceOption yAxisRotationSpeedOption;
private final SpruceOption mouseSpeedOption;
private final SpruceOption joystickAsMouseOption;
private final SpruceOption virtualMouseOption;
private final SpruceOption resetOption;
private final SpruceOption advancedConfigOption;
// Gameplay options
private final SpruceOption analogMovementOption;
private final SpruceOption doubleTapToSprintOption;
private final SpruceOption autoJumpOption;
private final SpruceOption controllerToggleSneakOption;
private final SpruceOption controllerToggleSprintOption;
private final SpruceOption fastBlockPlacingOption;
private final SpruceOption frontBlockPlacingOption;
private final SpruceOption verticalReacharoundOption;
private final SpruceOption flyDriftingOption;
private final SpruceOption flyVerticalDriftingOption;
// Appearance options
private final SpruceOption controllerTypeOption;
private final SpruceOption virtualMouseSkinOption;
private final SpruceOption hudEnableOption;
private final SpruceOption hudSideOption;
private final SpruceOption moveChatOption;
// Controller options
private final SpruceOption controllerOption =
new SpruceCyclingOption("midnightcontrols.menu.controller",
amount -> {
int id = MidnightControlsConfig.getController().id();
id += amount;
if (id > GLFW.GLFW_JOYSTICK_LAST)
id = GLFW.GLFW_JOYSTICK_1;
id = searchNextAvailableController(id, false);
MidnightControlsConfig.setController(Controller.byId(id));
},
option -> {
var controller = MidnightControlsConfig.getController();
var controllerName = controller.getName();
if (!controller.isConnected())
return option.getDisplayText(Text.literal(controllerName).formatted(Formatting.RED));
else if (!controller.isGamepad())
return option.getDisplayText(Text.literal(controllerName).formatted(Formatting.GOLD));
else
return option.getDisplayText(Text.literal(controllerName));
}, null);
private final SpruceOption secondControllerOption = new SpruceCyclingOption("midnightcontrols.menu.controller2",
amount -> {
int id = MidnightControlsConfig.getSecondController().map(Controller::id).orElse(-1);
id += amount;
if (id > GLFW.GLFW_JOYSTICK_LAST)
id = -1;
id = searchNextAvailableController(id, true);
MidnightControlsConfig.setSecondController(id == -1 ? null : Controller.byId(id));
},
option -> MidnightControlsConfig.getSecondController().map(controller -> {
var controllerName = controller.getName();
if (!controller.isConnected())
return option.getDisplayText(Text.literal(controllerName).formatted(Formatting.RED));
else if (!controller.isGamepad())
return option.getDisplayText(Text.literal(controllerName).formatted(Formatting.GOLD));
else
return option.getDisplayText(Text.literal(controllerName));
}).orElse(option.getDisplayText(SpruceTexts.OPTIONS_OFF.copyContentOnly().formatted(Formatting.RED))),
Text.translatable("midnightcontrols.tooltip.controller2"));
private final SpruceOption unfocusedInputOption;
private final SpruceOption invertsRightXAxis;
private final SpruceOption invertsRightYAxis;
private final SpruceOption toggleControllerProfileOption;
private final SpruceOption rightDeadZoneOption;
private final SpruceOption leftDeadZoneOption;
private final SpruceOption[] maxAnalogValueOptions = new SpruceOption[]{
maxAnalogValueOption("midnightcontrols.menu.max_left_x_value", GLFW.GLFW_GAMEPAD_AXIS_LEFT_X),
maxAnalogValueOption("midnightcontrols.menu.max_left_y_value", GLFW.GLFW_GAMEPAD_AXIS_LEFT_Y),
maxAnalogValueOption("midnightcontrols.menu.max_right_x_value", GLFW.GLFW_GAMEPAD_AXIS_RIGHT_X),
maxAnalogValueOption("midnightcontrols.menu.max_right_y_value", GLFW.GLFW_GAMEPAD_AXIS_RIGHT_Y)
};
private static SpruceOption maxAnalogValueOption(String key, int axis) {
return new SpruceDoubleOption(key, .25f, 1.f, 0.05f,
() -> MidnightControlsConfig.getAxisMaxValue(axis),
newValue -> MidnightControlsConfig.setAxisMaxValue(axis, newValue),
option -> option.getDisplayText(Text.literal(String.format("%.2f", option.get()))),
Text.translatable(key.replace("menu", "tooltip"))
);
}
private final MutableText controllerMappingsUrlText = Text.literal("(")
.append(Text.literal(GAMEPAD_TOOL_URL).formatted(Formatting.GOLD))
.append("),");
private static int searchNextAvailableController(int newId, boolean allowNone) {
if ((allowNone && newId == -1) || newId == 0) return newId;
boolean connected = Controller.byId(newId).isConnected();
if (!connected) {
newId++;
}
if (newId > GLFW.GLFW_JOYSTICK_LAST)
newId = allowNone ? -1 : GLFW.GLFW_JOYSTICK_1;
return connected ? newId : searchNextAvailableController(newId, allowNone);
}
public MidnightControlsSettingsScreen(Screen parent, boolean hideControls) {
super(Text.translatable("midnightcontrols.title.settings"));
MidnightControlsConfig.isEditing = true;
this.parent = parent;
// General options
this.inputModeOption = new SpruceCyclingOption("midnightcontrols.menu.controls_mode",
amount -> {
var next = MidnightControlsConfig.controlsMode.next();
MidnightControlsConfig.controlsMode = next;
MidnightControlsConfig.save();
if (this.client != null && this.client.player != null) {
ClientPlayNetworking.getSender().sendPacket(MidnightControls.CONTROLS_MODE_CHANNEL, this.mod.makeControlsModeBuffer(next));
}
}, option -> option.getDisplayText(Text.translatable(MidnightControlsConfig.controlsMode.getTranslationKey())),
Text.translatable("midnightcontrols.tooltip.controls_mode"));
this.autoSwitchModeOption = new SpruceToggleBooleanOption("midnightcontrols.menu.auto_switch_mode", () -> MidnightControlsConfig.autoSwitchMode,
value -> MidnightControlsConfig.autoSwitchMode = value, Text.translatable("midnightcontrols.tooltip.auto_switch_mode"));
this.rotationSpeedOption = new SpruceDoubleOption("midnightcontrols.menu.rotation_speed", 0.0, 100.0, .5f,
() -> MidnightControlsConfig.rotationSpeed,
value -> MidnightControlsConfig.rotationSpeed = value, option -> option.getDisplayText(Text.literal(String.valueOf(option.get()))),
Text.translatable("midnightcontrols.tooltip.rotation_speed"));
this.yAxisRotationSpeedOption = new SpruceDoubleOption("midnightcontrols.menu.y_axis_rotation_speed", 0.0, 100.0, .5f,
() -> MidnightControlsConfig.yAxisRotationSpeed,
value -> MidnightControlsConfig.yAxisRotationSpeed = value, option -> option.getDisplayText(Text.literal(String.valueOf(option.get()))),
Text.translatable("midnightcontrols.tooltip.y_axis_rotation_speed"));
this.mouseSpeedOption = new SpruceDoubleOption("midnightcontrols.menu.mouse_speed", 0.0, 150.0, .5f,
() -> MidnightControlsConfig.mouseSpeed,
value -> MidnightControlsConfig.mouseSpeed = value, option -> option.getDisplayText(Text.literal(String.valueOf(option.get()))),
Text.translatable("midnightcontrols.tooltip.joystick_as_mouse"));
this.joystickAsMouseOption = new SpruceToggleBooleanOption("midnightcontrols.menu.joystick_as_mouse",
() -> MidnightControlsConfig.joystickAsMouse, value -> MidnightControlsConfig.joystickAsMouse = value,
Text.translatable("midnightcontrols.tooltip.joystick_as_mouse"));
this.resetOption = SpruceSimpleActionOption.reset(btn -> {
MidnightControlsConfig.reset();
var client = MinecraftClient.getInstance();
this.init(client, client.getWindow().getScaledWidth(), client.getWindow().getScaledHeight());
});
this.advancedConfigOption = SpruceSimpleActionOption.of("midnightcontrols.midnightconfig.title", button -> client.setScreen(MidnightControlsConfig.getScreen(this, "midnightcontrols")));
// Gameplay options
this.analogMovementOption = new SpruceToggleBooleanOption("midnightcontrols.menu.analog_movement",
() -> MidnightControlsConfig.analogMovement, value -> MidnightControlsConfig.analogMovement = value,
Text.translatable("midnightcontrols.tooltip.analog_movement"));
this.doubleTapToSprintOption = new SpruceToggleBooleanOption("midnightcontrols.menu.double_tap_to_sprint",
() -> MidnightControlsConfig.doubleTapToSprint, value -> MidnightControlsConfig.doubleTapToSprint = value,
Text.translatable("midnightcontrols.tooltip.double_tap_to_sprint"));
this.autoJumpOption = new SpruceToggleBooleanOption("options.autoJump",
() -> this.client.options.getAutoJump().getValue(),
newValue -> this.client.options.getAutoJump().setValue(newValue),
null);
this.controllerToggleSneakOption = new SpruceToggleBooleanOption("midnightcontrols.menu.controller_toggle_sneak",
() -> MidnightControlsConfig.controllerToggleSneak, value -> MidnightControlsConfig.controllerToggleSneak = value,
null);
this.controllerToggleSprintOption = new SpruceToggleBooleanOption("midnightcontrols.menu.controller_toggle_sprint",
() -> MidnightControlsConfig.controllerToggleSprint, value -> MidnightControlsConfig.controllerToggleSprint = value,
null);
this.fastBlockPlacingOption = new SpruceToggleBooleanOption("midnightcontrols.menu.fast_block_placing", () -> MidnightControlsConfig.fastBlockPlacing,
value -> MidnightControlsConfig.fastBlockPlacing = value, Text.translatable("midnightcontrols.tooltip.fast_block_placing"));
this.frontBlockPlacingOption = new SpruceToggleBooleanOption("midnightcontrols.menu.reacharound.horizontal", () -> MidnightControlsConfig.horizontalReacharound,
value -> MidnightControlsConfig.horizontalReacharound = value, Text.translatable("midnightcontrols.tooltip.reacharound.horizontal"));
this.verticalReacharoundOption = new SpruceToggleBooleanOption("midnightcontrols.menu.reacharound.vertical", () -> MidnightControlsConfig.verticalReacharound,
value -> MidnightControlsConfig.verticalReacharound = value, Text.translatable("midnightcontrols.tooltip.reacharound.vertical"));
this.flyDriftingOption = new SpruceToggleBooleanOption("midnightcontrols.menu.fly_drifting", () -> MidnightControlsConfig.flyDrifting,
value -> MidnightControlsConfig.flyDrifting = value, Text.translatable("midnightcontrols.tooltip.fly_drifting"));
this.flyVerticalDriftingOption = new SpruceToggleBooleanOption("midnightcontrols.menu.fly_drifting_vertical", () -> MidnightControlsConfig.verticalFlyDrifting,
value -> MidnightControlsConfig.verticalFlyDrifting = value, Text.translatable("midnightcontrols.tooltip.fly_drifting_vertical"));
// Appearance options
this.controllerTypeOption = new SpruceCyclingOption("midnightcontrols.menu.controller_type",
amount -> MidnightControlsConfig.controllerType = MidnightControlsConfig.controllerType.next(),
option -> option.getDisplayText(MidnightControlsConfig.controllerType.getTranslatedText()),
Text.translatable("midnightcontrols.tooltip.controller_type"));
this.virtualMouseSkinOption = new SpruceCyclingOption("midnightcontrols.menu.virtual_mouse.skin",
amount -> MidnightControlsConfig.virtualMouseSkin = MidnightControlsConfig.virtualMouseSkin.next(),
option -> option.getDisplayText(MidnightControlsConfig.virtualMouseSkin.getTranslatedText()),
null);
this.hudEnableOption = new SpruceToggleBooleanOption("midnightcontrols.menu.hud_enable", () -> MidnightControlsConfig.hudEnable,
this.mod::setHudEnabled, Text.translatable("midnightcontrols.tooltip.hud_enable"));
this.hudSideOption = new SpruceCyclingOption("midnightcontrols.menu.hud_side",
amount -> MidnightControlsConfig.hudSide = MidnightControlsConfig.hudSide.next(),
option -> option.getDisplayText(MidnightControlsConfig.hudSide.getTranslatedText()),
Text.translatable("midnightcontrols.tooltip.hud_side"));
this.moveChatOption = new SpruceToggleBooleanOption("midnightcontrols.menu.move_chat", () -> MidnightControlsConfig.moveChat,
value -> MidnightControlsConfig.moveChat = value, Text.translatable("midnightcontrols.tooltip.move_chat"));
// Controller options
this.toggleControllerProfileOption = new SpruceToggleBooleanOption("midnightcontrols.menu.separate_controller_profile", () -> MidnightControlsConfig.controllerBindingProfiles.containsKey(MidnightControlsConfig.getController().getGuid()), value -> {
if (value) {
MidnightControlsConfig.controllerBindingProfiles.put(MidnightControlsConfig.getController().getGuid(), MidnightControlsConfig.getBindingsForController());
MidnightControlsConfig.updateBindingsForController(MidnightControlsConfig.getController());
} else {
MidnightControlsConfig.controllerBindingProfiles.remove(MidnightControlsConfig.getController().getGuid());
MidnightControlsConfig.updateBindingsForController(MidnightControlsConfig.getController());
}
}, Text.translatable(""));
this.rightDeadZoneOption = new SpruceDoubleOption("midnightcontrols.menu.right_dead_zone", 0.05, 1.0, .05f,
() -> MidnightControlsConfig.rightDeadZone,
value -> MidnightControlsConfig.rightDeadZone = value, option -> {
var value = String.valueOf(option.get());
return option.getDisplayText(Text.literal(value.substring(0, Math.min(value.length(), 5))));
}, Text.translatable("midnightcontrols.tooltip.right_dead_zone"));
this.leftDeadZoneOption = new SpruceDoubleOption("midnightcontrols.menu.left_dead_zone", 0.05, 1.0, .05f,
() -> MidnightControlsConfig.leftDeadZone,
value -> MidnightControlsConfig.leftDeadZone = value, option -> {
var value = String.valueOf(option.get());
return option.getDisplayText(Text.literal(value.substring(0, Math.min(value.length(), 5))));
}, Text.translatable("midnightcontrols.tooltip.left_dead_zone"));
this.invertsRightXAxis = new SpruceToggleBooleanOption("midnightcontrols.menu.invert_right_x_axis", () -> MidnightControlsConfig.invertRightXAxis,
value -> MidnightControlsConfig.invertRightXAxis = value, null);
this.invertsRightYAxis = new SpruceToggleBooleanOption("midnightcontrols.menu.invert_right_y_axis", () -> MidnightControlsConfig.invertRightYAxis,
value -> MidnightControlsConfig.invertRightYAxis = value, null);
this.unfocusedInputOption = new SpruceToggleBooleanOption("midnightcontrols.menu.unfocused_input", () -> MidnightControlsConfig.unfocusedInput,
value -> MidnightControlsConfig.unfocusedInput = value, Text.translatable("midnightcontrols.tooltip.unfocused_input"));
this.virtualMouseOption = new SpruceToggleBooleanOption("midnightcontrols.menu.virtual_mouse", () -> MidnightControlsConfig.virtualMouse,
value -> MidnightControlsConfig.virtualMouse = value, Text.translatable("midnightcontrols.tooltip.virtual_mouse"));
}
@Override
public void removed() {
MidnightControlsConfig.isEditing = false;
MidnightControlsConfig.save();
super.removed();
}
@Override
public void close() {
MidnightControlsConfig.isEditing = false;
MidnightControlsConfig.save();
super.close();
}
private int getTextHeight() {
return (5 + this.textRenderer.fontHeight) * 3 + 5;
}
@Override
protected void init() {
super.init();
this.buildTabs();
this.addDrawableChild(this.resetOption.createWidget(Position.of(this.width / 2 - 155, this.height - 29), 150));
this.addDrawableChild(ButtonWidget.builder(SpruceTexts.GUI_DONE, btn -> this.client.setScreen(this.parent))
.dimensions(this.width / 2 - 155 + 160, this.height - 29, 150, 20).build());
}
public void buildTabs() {
var tabs = new SpruceTabbedWidget(Position.of(0, 24), this.width, this.height - 32 - 24,
null,
Math.max(116, this.width / 8), 0);
tabs.getList().setBackground(new MidnightControlsBackground());
this.addDrawableChild(tabs);
tabs.addSeparatorEntry(Text.translatable("midnightcontrols.menu.separator.general"));
tabs.addTabEntry(Text.translatable("midnightcontrols.menu.title.general"), null,
this::buildGeneralTab);
tabs.addTabEntry(Text.translatable("midnightcontrols.menu.title.gameplay"), null,
this::buildGameplayTab);
tabs.addTabEntry(Text.translatable("midnightcontrols.menu.title.visual"), null,
this::buildVisualTab);
tabs.addSeparatorEntry(Text.translatable("options.controls"));
tabs.addTabEntry(Text.translatable("midnightcontrols.menu.title.controller_controls"), null,
this::buildControllerControlsTab);
tabs.addSeparatorEntry(Text.translatable("midnightcontrols.menu.separator.controller"));
tabs.addTabEntry(Text.translatable("midnightcontrols.menu.title.controller"), null,
this::buildControllerTab);
tabs.addTabEntry(Text.translatable("midnightcontrols.menu.title.mappings.string"), null,
this::buildMappingsStringEditorTab);
}
public SpruceOptionListWidget buildGeneralTab(int width, int height) {
var list = new SpruceOptionListWidget(Position.origin(), width, height);
list.setBackground(new MidnightControlsBackground(130));
list.addSingleOptionEntry(this.inputModeOption);
list.addSingleOptionEntry(this.autoSwitchModeOption);
list.addSingleOptionEntry(this.rotationSpeedOption);
list.addSingleOptionEntry(this.yAxisRotationSpeedOption);
list.addSingleOptionEntry(this.mouseSpeedOption);
list.addSingleOptionEntry(this.virtualMouseOption);
list.addSingleOptionEntry(this.joystickAsMouseOption);
list.addSingleOptionEntry(this.advancedConfigOption);
return list;
}
public SpruceOptionListWidget buildGameplayTab(int width, int height) {
var list = new SpruceOptionListWidget(Position.origin(), width, height);
list.setBackground(new MidnightControlsBackground(130));
list.addSingleOptionEntry(this.analogMovementOption);
list.addSingleOptionEntry(this.doubleTapToSprintOption);
list.addSingleOptionEntry(this.controllerToggleSneakOption);
list.addSingleOptionEntry(this.controllerToggleSprintOption);
if (MidnightControls.isExtrasLoaded) list.addSingleOptionEntry(this.fastBlockPlacingOption);
if (MidnightControls.isExtrasLoaded) list.addSingleOptionEntry(this.frontBlockPlacingOption);
if (MidnightControls.isExtrasLoaded) list.addSingleOptionEntry(this.verticalReacharoundOption);
if (MidnightControls.isExtrasLoaded) list.addSingleOptionEntry(this.flyDriftingOption);
if (MidnightControls.isExtrasLoaded) list.addSingleOptionEntry(this.flyVerticalDriftingOption);
list.addSingleOptionEntry(this.autoJumpOption);
return list;
}
public SpruceOptionListWidget buildVisualTab(int width, int height) {
var list = new SpruceOptionListWidget(Position.origin(), width, height);
list.setBackground(new MidnightControlsBackground(130));
list.addSingleOptionEntry(this.controllerTypeOption);
list.addSingleOptionEntry(this.virtualMouseSkinOption);
list.addSingleOptionEntry(new SpruceSeparatorOption("midnightcontrols.menu.title.hud", true, null));
list.addSingleOptionEntry(this.hudEnableOption);
list.addSingleOptionEntry(this.hudSideOption);
list.addSingleOptionEntry(this.moveChatOption);
return list;
}
public ControllerControlsWidget buildControllerControlsTab(int width, int height) {
return new ControllerControlsWidget(Position.origin(), width, height);
}
public AbstractSpruceWidget buildControllerTab(int width, int height) {
var root = new SpruceContainerWidget(Position.origin(), width, height);
var aboutMappings1 = new SpruceLabelWidget(Position.of(0, 2),
Text.translatable("midnightcontrols.controller.mappings.1", SDL2_GAMEPAD_TOOL),
width, true);
var gamepadToolUrlLabel = new SpruceLabelWidget(Position.of(0, aboutMappings1.getHeight() + 4),
this.controllerMappingsUrlText, width,
label -> Util.getOperatingSystem().open(GAMEPAD_TOOL_URL), true);
gamepadToolUrlLabel.setTooltip(Text.translatable("chat.link.open"));
var aboutMappings3 = new SpruceLabelWidget(Position.of(0,
aboutMappings1.getHeight() + gamepadToolUrlLabel.getHeight() + 6),
Text.translatable("midnightcontrols.controller.mappings.3", Formatting.GREEN.toString(), Formatting.RESET.toString()),
width, true);
int listHeight = height - 8 - aboutMappings1.getHeight() - aboutMappings3.getHeight() - gamepadToolUrlLabel.getHeight();
var labels = new SpruceContainerWidget(Position.of(0,
listHeight),
width, height - listHeight);
labels.addChild(aboutMappings1);
labels.addChild(gamepadToolUrlLabel);
labels.addChild(aboutMappings3);
var list = new SpruceOptionListWidget(Position.origin(), width, listHeight);
list.setBackground(new MidnightControlsBackground(130));
list.addSingleOptionEntry(this.controllerOption);
list.addSingleOptionEntry(this.secondControllerOption);
list.addSingleOptionEntry(this.toggleControllerProfileOption);
list.addSingleOptionEntry(this.unfocusedInputOption);
list.addOptionEntry(this.invertsRightXAxis, this.invertsRightYAxis);
list.addSingleOptionEntry(this.rightDeadZoneOption);
list.addSingleOptionEntry(this.leftDeadZoneOption);
for (var option : this.maxAnalogValueOptions) {
list.addSingleOptionEntry(option);
}
root.addChild(list);
root.addChild(labels);
return root;
}
public SpruceContainerWidget buildMappingsStringEditorTab(int width, int height) {
return new MappingsStringInputWidget(Position.origin(), width, height);
}
@Override
public void renderTitle(MatrixStack matrices, int mouseX, int mouseY, float delta) {
drawCenteredText(matrices, this.textRenderer, I18n.translate("midnightcontrols.menu.title"), this.width / 2, 8, 16777215);
}
public static class MidnightControlsBackground implements Background {
private int transparency = 160;
public MidnightControlsBackground() {}
public MidnightControlsBackground(int transparency) {
this.transparency = transparency;
}
@Override
public void render(MatrixStack matrixStack, SpruceWidget widget, int vOffset, int mouseX, int mouseY, float delta) {
fill(matrixStack, widget.getX(), widget.getY(), widget.getX() + widget.getWidth(), widget.getY() + widget.getHeight(), MidnightColorUtil.hex2Rgb("#000000"));
}
private void fill(MatrixStack matrixStack, int x2, int y2, int x1, int y1, Color color) {
matrixStack.push();
Matrix4f matrix = matrixStack.peek().getPositionMatrix();
float r = (float)(color.getRed()) / 255.0F;
float g = (float)(color.getGreen()) / 255.0F;
float b = (float)(color.getBlue()) / 255.0F;
float t = (float)(transparency) / 255.0F;
BufferBuilder bufferBuilder = Tessellator.getInstance().getBuffer();
RenderSystem.enableBlend();
RenderSystem.disableTexture();
RenderSystem.defaultBlendFunc();
RenderSystem.setShader(GameRenderer::getPositionColorProgram);
bufferBuilder.begin(VertexFormat.DrawMode.QUADS, VertexFormats.POSITION_COLOR);
bufferBuilder.vertex(matrix, (float)x1, (float)y2, 0.0F).color(r, g, b, t).next();
bufferBuilder.vertex(matrix, (float)x2, (float)y2, 0.0F).color(r, g, b, t).next();
bufferBuilder.vertex(matrix, (float)x2, (float)y1, 0.0F).color(r, g, b, t).next();
bufferBuilder.vertex(matrix, (float)x1, (float)y1, 0.0F).color(r, g, b, t).next();
BufferRenderer.drawWithGlobalProgram(bufferBuilder.end());
RenderSystem.enableTexture();
RenderSystem.disableBlend();
matrixStack.pop();
}
}
}

View File

@@ -0,0 +1,40 @@
/*
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of midnightcontrols.
*
* Licensed under the MIT license. For more information,
* see the LICENSE file.
*/
package eu.midnightdust.midnightcontrols.client.gui;
import eu.midnightdust.midnightcontrols.client.controller.Controller;
import dev.lambdaurora.spruceui.option.SpruceSimpleActionOption;
import dev.lambdaurora.spruceui.widget.SpruceButtonWidget;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.toast.SystemToast;
import net.minecraft.text.Text;
import org.jetbrains.annotations.Nullable;
import java.util.function.Consumer;
/**
* Represents the option to reload the controller mappings.
*/
public class ReloadControllerMappingsOption {
private static final String KEY = "midnightcontrols.menu.reload_controller_mappings";
public static SpruceSimpleActionOption newOption(@Nullable Consumer<SpruceButtonWidget> before) {
return SpruceSimpleActionOption.of(KEY, btn -> {
var client = MinecraftClient.getInstance();
if (before != null)
before.accept(btn);
Controller.updateMappings();
if (client.currentScreen instanceof MidnightControlsSettingsScreen)
client.currentScreen.init(client, client.getWindow().getScaledWidth(), client.getWindow().getScaledHeight());
client.getToastManager().add(SystemToast.create(client, SystemToast.Type.TUTORIAL_HINT,
Text.translatable("midnightcontrols.controller.mappings.updated"), Text.empty()));
}, Text.translatable("midnightcontrols.tooltip.reload_controller_mappings"));
}
}

View File

@@ -0,0 +1,89 @@
/*
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of midnightcontrols.
*
* Licensed under the MIT license. For more information,
* see the LICENSE file.
*/
package eu.midnightdust.midnightcontrols.client.gui;
import eu.midnightdust.midnightcontrols.client.MidnightControlsClient;
import eu.midnightdust.midnightcontrols.client.ring.RingButtonMode;
import eu.midnightdust.midnightcontrols.client.ring.RingPage;
import net.minecraft.client.gui.screen.Screen;
import net.minecraft.client.gui.widget.ButtonWidget;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.text.Text;
/**
* Represents the controls ring screen.
*
* @author LambdAurora
* @version 1.4.3
* @since 1.4.3
*/
public class RingScreen extends Screen {
protected final MidnightControlsClient mod;
public RingScreen() {
super(Text.literal("midnightcontrols.menu.title.ring"));
this.mod = MidnightControlsClient.get();
}
@Override
protected void init() {
super.init();
if (mod.ring.getMaxPages() > 1) {
this.addDrawableChild(ButtonWidget.builder(Text.of(""), button -> this.mod.ring.cyclePage(false)).dimensions(5, 5, 20, 20).build());
this.addDrawableChild(ButtonWidget.builder(Text.of(""), button -> this.mod.ring.cyclePage(true)).dimensions(width - 25, 5, 20, 20).build());
}
}
@Override
public boolean shouldPause() {
return false;
}
@Override
public void render(MatrixStack matrices, int mouseX, int mouseY, float delta) {
super.render(matrices, mouseX, mouseY, delta);
RingPage page = this.mod.ring.getCurrentPage();
page.render(matrices, this.textRenderer, this.width, this.height, mouseX, mouseY, delta);
}
@Override
public void close() {
super.close();
assert client != null;
client.currentScreen = null;
RingPage page = this.mod.ring.getCurrentPage();
if (RingPage.selected >= 0 && page.actions[RingPage.selected] != null)
page.actions[RingPage.selected].activate(RingButtonMode.PRESS);
RingPage.selected = -1;
this.removed();
}
@Override
public boolean changeFocus(boolean lookForwards) {
if (lookForwards) {
if (RingPage.selected < 7) ++RingPage.selected;
else RingPage.selected = -1;
}
else {
if (RingPage.selected > -1) --RingPage.selected;
else RingPage.selected = 7;
}
return true;
}
@Override
public boolean mouseReleased(double mouseX, double mouseY, int button) {
if (mod.ring.getCurrentPage().onClick(width, height, (int) mouseX, (int) mouseY)) {
this.close();
return true;
}
return false;
}
}

View File

@@ -0,0 +1,295 @@
/*
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of midnightcontrols.
*
* Licensed under the MIT license. For more information,
* see the LICENSE file.
*/
package eu.midnightdust.midnightcontrols.client.gui;
import dev.lambdaurora.spruceui.Position;
import dev.lambdaurora.spruceui.widget.SpruceButtonWidget;
import eu.midnightdust.midnightcontrols.client.HudSide;
import eu.midnightdust.midnightcontrols.client.MidnightControlsClient;
import eu.midnightdust.midnightcontrols.client.MidnightControlsConfig;
import eu.midnightdust.midnightcontrols.client.gui.widget.SilentTexturedButtonWidget;
import eu.midnightdust.midnightcontrols.client.util.KeyBindingAccessor;
import net.minecraft.client.gui.screen.ChatScreen;
import net.minecraft.client.gui.screen.GameMenuScreen;
import net.minecraft.client.gui.screen.Screen;
import net.minecraft.client.gui.screen.ingame.InventoryScreen;
import net.minecraft.client.gui.widget.TexturedButtonWidget;
import net.minecraft.network.packet.c2s.play.PlayerActionC2SPacket;
import net.minecraft.text.Text;
import net.minecraft.util.Arm;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import org.jetbrains.annotations.NotNull;
import org.lwjgl.glfw.GLFW;
import static org.lwjgl.glfw.GLFW.GLFW_GAMEPAD_AXIS_RIGHT_X;
import static org.lwjgl.glfw.GLFW.GLFW_GAMEPAD_AXIS_RIGHT_Y;
/**
* Represents the touchscreen overlay
*/
public class TouchscreenOverlay extends Screen {
public static final Identifier WIDGETS_LOCATION = new Identifier("midnightcontrols", "textures/gui/widgets.png");
private MidnightControlsClient mod;
private SilentTexturedButtonWidget jumpButton;
private SilentTexturedButtonWidget flyButton;
private SilentTexturedButtonWidget flyUpButton;
private SilentTexturedButtonWidget flyDownButton;
private int flyButtonEnableTicks = 0;
private int forwardButtonTick = 0;
private SilentTexturedButtonWidget forwardLeftButton;
private SilentTexturedButtonWidget forwardRightButton;
private SilentTexturedButtonWidget startSneakButton;
private SilentTexturedButtonWidget endSneakButton;
public TouchscreenOverlay(@NotNull MidnightControlsClient mod) {
super(Text.literal("Touchscreen overlay"));
this.mod = mod;
this.passEvents = true;
}
@Override
public boolean shouldPause()
{
return false;
}
@Override
public boolean keyPressed(int keyCode, int scanCode, int modifiers) {
super.keyPressed(keyCode,scanCode,modifiers);
//return false;
return true;
}
private void pauseGame(boolean bl) {
if (this.client == null)
return;
boolean bl2 = this.client.isIntegratedServerRunning() && !this.client.getServer().isRemote();
if (bl2) {
this.client.setScreen(new GameMenuScreen(!bl));
this.client.getSoundManager().pauseAll();
} else {
this.client.setScreen(new GameMenuScreen(true));
}
}
/**
* Updates the forward button ticks cooldown.
*
* @param state The button state.
*
*/
private void updateForwardButtonsState(boolean state) {
if (state)
this.forwardButtonTick = -1;
else
this.forwardButtonTick = 20;
}
/**
* Updates the jump buttons.
*/
private void updateJumpButtons() {
if (this.client == null)
return;
if (!this.client.interactionManager.isFlyingLocked()) {
boolean oldStateFly = this.flyButton.isVisible();
this.jumpButton.setVisible(false);
this.flyButton.setVisible(true);
this.flyUpButton.setVisible(true);
this.flyDownButton.setVisible(true);
if (oldStateFly != this.flyButton.isVisible()) {
this.flyButtonEnableTicks = 5;
this.setJump(false);
} else if (this.flyButtonEnableTicks > 0)
this.flyButtonEnableTicks--;
} else {
this.jumpButton.setVisible(true);
this.flyButton.setVisible(false);
this.flyUpButton.setVisible(false);
this.flyDownButton.setVisible(false);
}
}
/**
* Handles the jump button.
*
* @param btn The pressed button.
*/
private void handleJump(SpruceButtonWidget btn) {
((KeyBindingAccessor) this.client.options.jumpKey).midnightcontrols$handlePressState(btn.isActive());
}
/**
* Handles the jump button.
*
* @param state The state.
*/
private void setJump(boolean state) {
((KeyBindingAccessor) this.client.options.jumpKey).midnightcontrols$handlePressState(state);
}
@Override
public void tick() {
if (this.forwardButtonTick > 0) {
this.forwardButtonTick--;
} else if (this.forwardButtonTick == 0) {
if (this.forwardLeftButton.isVisible())
this.forwardLeftButton.setVisible(false);
if (this.forwardRightButton.isVisible())
this.forwardRightButton.setVisible(false);
}
this.updateJumpButtons();
}
@Override
protected void init() {
super.init();
int scaledWidth = this.client.getWindow().getScaledWidth();
int scaledHeight = this.client.getWindow().getScaledHeight();
this.addDrawableChild(new TexturedButtonWidget(scaledWidth / 2 - 20, 0, 20, 20, 0, 106, 20, new Identifier("textures/gui/widgets.png"), 256, 256,
btn -> this.client.setScreen(new ChatScreen("")), Text.empty()));
this.addDrawableChild(new TexturedButtonWidget(scaledWidth / 2, 0, 20, 20, 0, 0, 20, WIDGETS_LOCATION, 256, 256,
btn -> this.pauseGame(false)));
// Inventory buttons.
int inventoryButtonX = scaledWidth / 2;
int inventoryButtonY = scaledHeight - 16 - 5;
if (this.client.options.getMainArm().getValue() == Arm.LEFT) {
inventoryButtonX = inventoryButtonX - 91 - 24;
} else {
inventoryButtonX = inventoryButtonX + 91 + 4;
}
this.addDrawableChild(new TexturedButtonWidget(inventoryButtonX, inventoryButtonY, 20, 20, 20, 0, 20, WIDGETS_LOCATION, 256, 256,
btn -> {
if (this.client.interactionManager.hasRidingInventory()) {
this.client.player.openRidingInventory();
} else {
this.client.getTutorialManager().onInventoryOpened();
this.client.setScreen(new InventoryScreen(this.client.player));
}
}));
int jumpButtonX, swapHandsX, sneakButtonX;
int sneakButtonY = scaledHeight - 10 - 40 - 5;
if (MidnightControlsConfig.hudSide == HudSide.LEFT) {
jumpButtonX = scaledWidth - 20 - 20;
swapHandsX = jumpButtonX - 5 - 40;
sneakButtonX = 10 + 20 + 5;
} else {
jumpButtonX = 20;
swapHandsX = jumpButtonX + 5 + 40;
sneakButtonX = scaledWidth - 10 - 40 - 5;
}
// Swap items hand.
this.addDrawableChild(new SilentTexturedButtonWidget(Position.of(swapHandsX, sneakButtonY), 20, 20, Text.empty(),
button -> {
if (button.isActive()) {
if (!this.client.player.isSpectator()) {
this.client.getNetworkHandler().sendPacket(new PlayerActionC2SPacket(PlayerActionC2SPacket.Action.SWAP_ITEM_WITH_OFFHAND, BlockPos.ORIGIN, Direction.DOWN));
}
}
},0, 160, 20, WIDGETS_LOCATION));
// Drop
this.addDrawableChild(new SilentTexturedButtonWidget(Position.of(swapHandsX, sneakButtonY + 5 + 20), 20, 20, Text.empty(), btn ->
((KeyBindingAccessor) this.client.options.dropKey).midnightcontrols$handlePressState(btn.isActive()), 0, 160, 20, WIDGETS_LOCATION));
// Jump keys
this.addDrawableChild(this.jumpButton = new SilentTexturedButtonWidget(Position.of(jumpButtonX, sneakButtonY), 20, 20, Text.empty(), this::handleJump, 0, 40, 20, WIDGETS_LOCATION));
this.addDrawableChild(this.flyButton = new SilentTexturedButtonWidget(Position.of(jumpButtonX, sneakButtonY), 20, 20, Text.empty(),btn -> {
if (this.flyButtonEnableTicks == 0) this.client.player.getAbilities().flying = false;
}, 20, 40, 20, WIDGETS_LOCATION)
);
this.addDrawableChild(this.flyUpButton = new SilentTexturedButtonWidget(Position.of(jumpButtonX, sneakButtonY - 5 - 20), 20, 20,Text.empty(),
this::handleJump, 40, 40, 20, WIDGETS_LOCATION
));
this.addDrawableChild(this.flyDownButton = new SilentTexturedButtonWidget(Position.of(jumpButtonX, sneakButtonY + 20 + 5), 20, 20, Text.empty(),
btn -> ((KeyBindingAccessor) this.client.options.sneakKey).midnightcontrols$handlePressState(btn.isActive()), 60, 40, 20, WIDGETS_LOCATION
));
this.updateJumpButtons();
// Movements keys
this.addDrawableChild((this.startSneakButton = new SilentTexturedButtonWidget(Position.of(sneakButtonX, sneakButtonY), 20, 20, Text.empty(), btn -> {
if (btn.isActive()) {
((KeyBindingAccessor) this.client.options.sneakKey).midnightcontrols$handlePressState(true);
this.startSneakButton.setVisible(false);
this.endSneakButton.setVisible(true);
}
}, 0, 120, 20, WIDGETS_LOCATION))
);
this.addDrawableChild((this.endSneakButton = new SilentTexturedButtonWidget(Position.of(sneakButtonX, sneakButtonY), 20, 20, Text.empty(), btn -> {
if (btn.isActive()) {
((KeyBindingAccessor) this.client.options.sneakKey).midnightcontrols$handlePressState(false);
this.endSneakButton.setVisible(false);
this.startSneakButton.setVisible(true);
}
}, 20, 120, 20, WIDGETS_LOCATION)));
this.endSneakButton.setVisible(false);
this.addDrawableChild(this.forwardLeftButton = new SilentTexturedButtonWidget(Position.of(sneakButtonX - 20 - 5, sneakButtonY - 5 - 20), 20, 20, Text.empty(), btn -> {
((KeyBindingAccessor) this.client.options.forwardKey).midnightcontrols$handlePressState(btn.isActive());
((KeyBindingAccessor) this.client.options.leftKey).midnightcontrols$handlePressState(btn.isActive());
this.updateForwardButtonsState(btn.isActive());
}, 80, 80, 20, WIDGETS_LOCATION
));
this.forwardLeftButton.setVisible(false);
this.addDrawableChild(new SilentTexturedButtonWidget(Position.of(sneakButtonX, sneakButtonY - 5 - 20), 20, 20, Text.empty(), btn -> {
((KeyBindingAccessor) this.client.options.forwardKey).midnightcontrols$handlePressState(btn.isActive());
this.updateForwardButtonsState(btn.isActive());
this.forwardLeftButton.setVisible(true);
this.forwardRightButton.setVisible(true);
}, 0, 80, 20, WIDGETS_LOCATION
));
this.addDrawableChild(this.forwardRightButton = new SilentTexturedButtonWidget(Position.of(sneakButtonX + 20 + 5, sneakButtonY - 5 - 20), 20, 20, Text.empty(), btn -> {
((KeyBindingAccessor) this.client.options.forwardKey).midnightcontrols$handlePressState(btn.isActive());
((KeyBindingAccessor) this.client.options.rightKey).midnightcontrols$handlePressState(btn.isActive());
this.updateForwardButtonsState(btn.isActive());
}, 100, 80, 20, WIDGETS_LOCATION
));
this.forwardRightButton.setVisible(true);
this.addDrawableChild(new SilentTexturedButtonWidget(Position.of(sneakButtonX + 20 + 5, sneakButtonY), 20, 20, Text.empty(),
btn -> ((KeyBindingAccessor) this.client.options.rightKey).midnightcontrols$handlePressState(btn.isActive()), 20, 80, 20, WIDGETS_LOCATION
));
this.addDrawableChild(new SilentTexturedButtonWidget(Position.of(sneakButtonX, sneakButtonY + 20 + 5), 20, 20, Text.empty(),
btn -> ((KeyBindingAccessor) this.client.options.backKey).midnightcontrols$handlePressState(btn.isActive()), 40, 80, 20, WIDGETS_LOCATION
));
this.addDrawableChild(new SilentTexturedButtonWidget(Position.of(sneakButtonX - 20 - 5, sneakButtonY), 20, 20, Text.empty(),
btn -> ((KeyBindingAccessor) this.client.options.leftKey).midnightcontrols$handlePressState(btn.isActive()), 60, 80, 20, WIDGETS_LOCATION
));
}
@Override
public boolean mouseClicked(double mouseX, double mouseY, int button) {
if (mouseY >= (double) (this.height - 22) && this.client != null && this.client.player != null) {
int centerX = this.width / 2;
if (mouseX >= (double) (centerX - 90) && mouseX <= (double) (centerX + 90)) {
for (int slot = 0; slot < 9; ++slot) {
int slotX = centerX - 90 + slot * 20 + 2;
if (mouseX >= (double) slotX && mouseX <= (double) (slotX + 20)) {
this.client.player.getInventory().selectedSlot = slot;
return true;
}
}
}
}
return super.mouseClicked(mouseX, mouseY, button);
}
@Override
public boolean mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY) {
if (button == GLFW.GLFW_MOUSE_BUTTON_1 && this.client != null) {
if (deltaY > 0.01)
this.mod.input.handleLook(this.client, GLFW_GAMEPAD_AXIS_RIGHT_Y, (float) Math.abs(deltaY / 5.0), 2);
else if (deltaY < 0.01)
this.mod.input.handleLook(this.client, GLFW_GAMEPAD_AXIS_RIGHT_Y, (float) Math.abs(deltaY / 5.0), 1);
if (deltaX > 0.01)
this.mod.input.handleLook(this.client, GLFW_GAMEPAD_AXIS_RIGHT_X, (float) Math.abs(deltaX / 5.0), 2);
else if (deltaX < 0.01)
this.mod.input.handleLook(this.client, GLFW_GAMEPAD_AXIS_RIGHT_X, (float) Math.abs(deltaX / 5.0), 1);
}
return super.mouseDragged(mouseX, mouseY, button, deltaX, deltaY);
}
}

View File

@@ -0,0 +1,57 @@
/*
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of midnightcontrols.
*
* Licensed under the MIT license. For more information,
* see the LICENSE file.
*/
package eu.midnightdust.midnightcontrols.client.gui.widget;
import eu.midnightdust.midnightcontrols.client.controller.ButtonBinding;
import eu.midnightdust.midnightcontrols.client.gui.MidnightControlsRenderer;
import dev.lambdaurora.spruceui.Position;
import dev.lambdaurora.spruceui.SpruceTexts;
import dev.lambdaurora.spruceui.widget.AbstractSpruceIconButtonWidget;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.text.Text;
import org.jetbrains.annotations.NotNull;
/**
* Represents a controller button widget.
*/
public class ControllerButtonWidget extends AbstractSpruceIconButtonWidget {
private ButtonBinding binding;
private int iconWidth;
public ControllerButtonWidget(Position position, int width, @NotNull ButtonBinding binding, @NotNull PressAction action) {
super(position, width, 20, ButtonBinding.getLocalizedButtonName(binding.getButton()[0]), action);
this.binding = binding;
}
public void update() {
int length = binding.getButton().length;
this.setMessage(this.binding.isNotBound() ? SpruceTexts.NOT_BOUND.copy() :
(length > 0 ? ButtonBinding.getLocalizedButtonName(binding.getButton()[0]) : Text.literal("<>")));
}
@Override
public Text getMessage() {
if (this.binding.getButton().length > 1)
return Text.empty();
return super.getMessage();
}
@Override
protected int renderIcon(MatrixStack matrices, int mouseX, int mouseY, float delta) {
int x = this.getX();
if (this.binding.getButton().length > 1) {
x += (this.width / 2 - this.iconWidth / 2) - 4;
}
var size = MidnightControlsRenderer.drawButton(matrices, x, this.getY(), this.binding, MinecraftClient.getInstance());
this.iconWidth = size.length();
return size.height();
}
}

View File

@@ -0,0 +1,73 @@
/*
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of midnightcontrols.
*
* Licensed under the MIT license. For more information,
* see the LICENSE file.
*/
package eu.midnightdust.midnightcontrols.client.gui.widget;
import eu.midnightdust.midnightcontrols.client.MidnightControlsClient;
import eu.midnightdust.midnightcontrols.client.MidnightControlsConfig;
import eu.midnightdust.midnightcontrols.client.controller.ButtonBinding;
import eu.midnightdust.midnightcontrols.client.controller.InputManager;
import dev.lambdaurora.spruceui.Position;
import dev.lambdaurora.spruceui.SpruceTexts;
import dev.lambdaurora.spruceui.widget.SpruceButtonWidget;
import dev.lambdaurora.spruceui.widget.container.SpruceContainerWidget;
import eu.midnightdust.midnightcontrols.client.gui.MidnightControlsSettingsScreen;
import net.minecraft.client.gui.screen.option.ControlsOptionsScreen;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.text.Text;
import org.aperlambda.lambdacommon.utils.function.Predicates;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
/**
* Represents the controls screen.
*/
public class ControllerControlsWidget extends SpruceContainerWidget {
final MidnightControlsClient mod;
private ControlsListWidget bindingsListWidget;
private SpruceButtonWidget resetButton;
public ButtonBinding focusedBinding;
public boolean waiting = false;
public List<Integer> currentButtons = new ArrayList<>();
public ControllerControlsWidget(Position position, int width, int height) {
super(position, width, height);
this.mod = MidnightControlsClient.get();
this.init();
}
protected void init() {
this.addChild(new SpruceButtonWidget(Position.of(this, this.width / 2 - 155, 18), 310, 20,
Text.translatable("midnightcontrols.menu.keyboard_controls"),
btn -> this.client.setScreen(new ControlsOptionsScreen(null, this.client.options))));
this.bindingsListWidget = new ControlsListWidget(Position.of(this, 0, 43), this.width, this.height - 43 - 35, this);
this.bindingsListWidget.setBackground(new MidnightControlsSettingsScreen.MidnightControlsBackground(130));
this.addChild(this.bindingsListWidget);
this.addChild(this.resetButton = new SpruceButtonWidget(Position.of(this, this.width / 2 - 155, this.height - 29), 150, 20,
SpruceTexts.CONTROLS_RESET_ALL,
btn -> InputManager.streamBindings().collect(Collectors.toSet()).forEach(binding -> MidnightControlsConfig.setButtonBinding(binding, binding.getDefaultButton()))));
}
@Override
public void renderWidget(MatrixStack matrices, int mouseX, int mouseY, float delta) {
drawCenteredText(matrices, this.client.textRenderer, Text.translatable("midnightcontrols.menu.title.controller_controls"),
this.getX() + this.width / 2, this.getY() + 4, 16777215);
this.resetButton.setActive(InputManager.streamBindings().anyMatch(Predicates.not(ButtonBinding::isDefault)));
super.renderWidget(matrices, mouseX, mouseY, delta);
}
public void finishBindingEdit(int... buttons) {
if (this.focusedBinding == null) return;
MidnightControlsConfig.setButtonBinding(this.focusedBinding, buttons);
this.focusedBinding = null;
}
}

View File

@@ -0,0 +1,319 @@
/*
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of midnightcontrols.
*
* Licensed under the MIT license. For more information,
* see the LICENSE file.
*/
package eu.midnightdust.midnightcontrols.client.gui.widget;
import eu.midnightdust.midnightcontrols.client.MidnightControlsClient;
import eu.midnightdust.midnightcontrols.client.MidnightControlsConfig;
import eu.midnightdust.midnightcontrols.client.controller.ButtonBinding;
import eu.midnightdust.midnightcontrols.client.controller.ButtonCategory;
import eu.midnightdust.midnightcontrols.client.controller.InputManager;
import dev.lambdaurora.spruceui.Position;
import dev.lambdaurora.spruceui.SpruceTexts;
import dev.lambdaurora.spruceui.navigation.NavigationDirection;
import dev.lambdaurora.spruceui.navigation.NavigationUtils;
import dev.lambdaurora.spruceui.widget.SpruceButtonWidget;
import dev.lambdaurora.spruceui.widget.SpruceSeparatorWidget;
import dev.lambdaurora.spruceui.widget.SpruceWidget;
import dev.lambdaurora.spruceui.widget.container.SpruceEntryListWidget;
import dev.lambdaurora.spruceui.widget.container.SpruceParentWidget;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.minecraft.client.resource.language.I18n;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.text.Text;
import net.minecraft.util.Formatting;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.lwjgl.glfw.GLFW;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
/**
* Represents a control list widget.
*/
public class ControlsListWidget extends SpruceEntryListWidget<ControlsListWidget.Entry> {
private static final int[] UNBOUND = new int[]{-1};
private final ControllerControlsWidget gui;
protected int lastIndex = 0;
private final int maxTextLength;
public ControlsListWidget(Position position, int width, int height, ControllerControlsWidget gui) {
super(position, width, height, 4, ControlsListWidget.Entry.class);
this.gui = gui;
this.maxTextLength = InputManager.streamBindings().mapToInt(binding -> this.client.textRenderer.getWidth(binding.getText())).max().orElse(0);
InputManager.streamCategories()
.sorted(Comparator.comparingInt(ButtonCategory::getPriority))
.forEach(category -> {
this.addEntry(new CategoryEntry(this, category));
category.getBindings().forEach(binding -> {
this.addEntry(new ControlsListWidget.ButtonBindingEntry(this, binding));
});
});
this.setAllowOutsideHorizontalNavigation(true);
}
private int getRowWidth() {
return this.getWidth() - 6 - this.getRowLeft() * 2;
}
public int getRowLeft() {
int baseWidth = 220 + 32;
return this.getWidth() / 2 - baseWidth / 2 + 72 - this.maxTextLength;
}
public class ButtonBindingEntry extends Entry implements SpruceParentWidget<SpruceWidget> {
private final List<SpruceWidget> children = new ArrayList<>();
private @Nullable SpruceWidget focused;
private final ButtonBinding binding;
private final String bindingName;
private final ControllerButtonWidget editButton;
private final SpruceButtonWidget resetButton;
private final SpruceButtonWidget unbindButton;
ButtonBindingEntry(@NotNull ControlsListWidget parent, @NotNull ButtonBinding binding) {
super(parent);
this.binding = binding;
this.bindingName = I18n.translate(this.binding.getTranslationKey());
this.editButton = new ControllerButtonWidget(Position.of(this, parent.getWidth() / 2 - 8, 0), 110, this.binding, btn -> {
gui.focusedBinding = binding;
MidnightControlsClient.get().input.beginControlsInput(gui);
}) {
protected Text getNarrationMessage() {
return binding.isNotBound() ? Text.translatable("narrator.controls.unbound", bindingName)
: Text.translatable("narrator.controls.bound", bindingName, super.getNarrationMessage());
}
};
this.children.add(editButton);
this.resetButton = new SpruceButtonWidget(Position.of(this,
this.editButton.getPosition().getRelativeX() + this.editButton.getWidth() + 2, 0),
44, 20, Text.translatable("controls.reset"),
btn -> MidnightControlsConfig.setButtonBinding(binding, binding.getDefaultButton())) {
protected Text getNarrationMessage() {
return Text.translatable("narrator.controls.reset", bindingName);
}
};
this.children.add(this.resetButton);
this.unbindButton = new SpruceButtonWidget(Position.of(this,
this.editButton.getPosition().getRelativeX() + this.editButton.getWidth() + 2, 0),
this.resetButton.getWidth(), this.resetButton.getHeight(), SpruceTexts.GUI_UNBIND,
btn -> {
MidnightControlsConfig.setButtonBinding(binding, UNBOUND);
gui.focusedBinding = null;
MidnightControlsClient.get().input.beginControlsInput(null);
}) {
protected Text getNarrationMessage() {
return Text.translatable("midnightcontrols.narrator.unbound", bindingName);
}
};
this.children.add(this.unbindButton);
this.position.setRelativeX(4);
this.width -= 10;
}
@Override
public List<SpruceWidget> children() {
return this.children;
}
@Override
public @Nullable SpruceWidget getFocused() {
return this.focused;
}
@Override
public void setFocused(@Nullable SpruceWidget focused) {
if (this.focused == focused)
return;
if (this.focused != null)
this.focused.setFocused(false);
this.focused = focused;
}
@Override
public int getHeight() {
return this.children.stream().mapToInt(SpruceWidget::getHeight).reduce(Integer::max).orElse(0) + 4;
}
/* Input */
@Override
protected boolean onMouseClick(double mouseX, double mouseY, int button) {
var it = this.children().iterator();
SpruceWidget element;
do {
if (!it.hasNext()) {
return false;
}
element = it.next();
} while (!element.mouseClicked(mouseX, mouseY, button));
this.setFocused(element);
if (button == GLFW.GLFW_MOUSE_BUTTON_1)
this.dragging = true;
return true;
}
@Override
protected boolean onMouseRelease(double mouseX, double mouseY, int button) {
this.dragging = false;
return this.hoveredElement(mouseX, mouseY).filter(element -> element.mouseReleased(mouseX, mouseY, button)).isPresent();
}
@Override
protected boolean onMouseDrag(double mouseX, double mouseY, int button, double deltaX, double deltaY) {
return this.getFocused() != null && this.dragging && button == GLFW.GLFW_MOUSE_BUTTON_1
&& this.getFocused().mouseDragged(mouseX, mouseY, button, deltaX, deltaY);
}
@Override
protected boolean onKeyPress(int keyCode, int scanCode, int modifiers) {
return this.focused != null && this.focused.keyPressed(keyCode, scanCode, modifiers);
}
/* Navigation */
@Override
public void setFocused(boolean focused) {
super.setFocused(focused);
if (!focused) {
this.setFocused(null);
}
}
@Override
public boolean onNavigation(@NotNull NavigationDirection direction, boolean tab) {
if (this.requiresCursor()) return false;
if (!tab && direction.isVertical()) {
if (this.isFocused()) {
this.setFocused(null);
return false;
}
int lastIndex = this.parent.lastIndex;
if (lastIndex >= this.children.size())
lastIndex = this.children.size() - 1;
if (!this.children.get(lastIndex).onNavigation(direction, tab))
return false;
this.setFocused(this.children.get(lastIndex));
return true;
}
boolean result = NavigationUtils.tryNavigate(direction, tab, this.children, this.focused, this::setFocused, true);
if (result) {
this.setFocused(true);
if (direction.isHorizontal() && this.getFocused() != null) {
this.parent.lastIndex = this.children.indexOf(this.getFocused());
}
}
return result;
}
/* Rendering */
@Override
protected void renderWidget(MatrixStack matrices, int mouseX, int mouseY, float delta) {
boolean focused = gui.focusedBinding == this.binding;
var textRenderer = ControlsListWidget.this.client.textRenderer;
int height = this.getHeight();
//float textX = (float) (this.getX() + 70 - ControlsListWidget.this.maxTextLength);
int textY = this.getY() + height / 2;
textRenderer.draw(matrices, this.bindingName, this.getX(), (float) (textY - 9 / 2), 16777215);
this.resetButton.setVisible(!focused);
this.unbindButton.setVisible(focused);
this.resetButton.setActive(!this.binding.isDefault());
this.editButton.update();
if (focused) {
var text = Text.literal("> ").formatted(Formatting.WHITE);
text.append(this.editButton.getMessage().copy().formatted(Formatting.YELLOW));
this.editButton.setMessage(text.append(Text.literal(" <").formatted(Formatting.WHITE)));
} else if (!this.binding.isNotBound() && InputManager.hasDuplicatedBindings(this.binding)) {
var text = this.editButton.getMessage().copy();
this.editButton.setMessage(text.formatted(Formatting.RED));
} else if (this.binding.isNotBound()) {
var text = this.editButton.getMessage().copy();
this.editButton.setMessage(text.formatted(Formatting.GOLD));
}
this.children.forEach(widget -> widget.render(matrices, mouseX, mouseY, delta));
}
}
public static class CategoryEntry extends Entry {
private final SpruceSeparatorWidget separatorWidget;
protected CategoryEntry(ControlsListWidget parent, ButtonCategory category) {
super(parent);
this.separatorWidget = new SpruceSeparatorWidget(Position.of(this, 2, 0), this.getWidth() - 4,
Text.literal(category.getTranslatedName())) {
@Override
public int getWidth() {
return CategoryEntry.this.getWidth() - 4;
}
};
}
public SpruceSeparatorWidget getSeparatorWidget() {
return this.separatorWidget;
}
@Override
public int getHeight() {
return this.separatorWidget.getHeight() + 4;
}
/* Navigation */
@Override
public boolean onNavigation(@NotNull NavigationDirection direction, boolean tab) {
return this.separatorWidget.onNavigation(direction, tab);
}
/* Rendering */
@Override
protected void renderWidget(MatrixStack matrices, int mouseX, int mouseY, float delta) {
this.separatorWidget.render(matrices, mouseX, mouseY, delta);
}
@Override
public String toString() {
return "SpruceTabbedWidget$SeparatorEntry{" +
"position=" + this.getPosition() +
", width=" + this.getWidth() +
", height=" + this.getHeight() +
'}';
}
}
@Environment(EnvType.CLIENT)
public abstract static class Entry extends SpruceEntryListWidget.Entry {
protected final ControlsListWidget parent;
protected Entry(ControlsListWidget parent) {
this.parent = parent;
}
@Override
public int getWidth() {
return this.parent.getInnerWidth();
}
}
}

View File

@@ -0,0 +1,26 @@
package eu.midnightdust.midnightcontrols.client.gui.widget;
import dev.lambdaurora.spruceui.Position;
import dev.lambdaurora.spruceui.widget.SpruceTexturedButtonWidget;
import net.minecraft.text.Text;
import net.minecraft.util.Identifier;
public class SilentTexturedButtonWidget extends SpruceTexturedButtonWidget {
public SilentTexturedButtonWidget(Position position, int width, int height, Text message, PressAction action, int u, int v, int hoveredVOffset, Identifier texture) {
super(position, width, height, message, action, u, v, hoveredVOffset, texture);
}
public SilentTexturedButtonWidget(Position position, int width, int height, Text message, boolean showMessage, PressAction action, int u, int v, int hoveredVOffset, Identifier texture) {
super(position, width, height, message, showMessage, action, u, v, hoveredVOffset, texture);
}
public SilentTexturedButtonWidget(Position position, int width, int height, Text message, PressAction action, int u, int v, int hoveredVOffset, Identifier texture, int textureWidth, int textureHeight) {
super(position, width, height, message, action, u, v, hoveredVOffset, texture, textureWidth, textureHeight);
}
public SilentTexturedButtonWidget(Position position, int width, int height, Text message, boolean showMessage, PressAction action, int u, int v, int hoveredVOffset, Identifier texture, int textureWidth, int textureHeight) {
super(position, width, height, message, showMessage, action, u, v, hoveredVOffset, texture, textureWidth, textureHeight);
}
@Override
public void playDownSound() {}
}

View File

@@ -0,0 +1,34 @@
/*
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of midnightcontrols.
*
* Licensed under the MIT license. For more information,
* see the LICENSE file.
*/
package eu.midnightdust.midnightcontrols.client.mixin;
import net.minecraft.advancement.Advancement;
import net.minecraft.client.gui.screen.advancement.AdvancementTab;
import net.minecraft.client.gui.screen.advancement.AdvancementsScreen;
import net.minecraft.client.network.ClientAdvancementManager;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
import java.util.Map;
/**
* Represents an accessor of {@link AdvancementsScreen}.
*/
@Mixin(AdvancementsScreen.class)
public interface AdvancementsScreenAccessor {
@Accessor("advancementHandler")
ClientAdvancementManager getAdvancementManager();
@Accessor("tabs")
Map<Advancement, AdvancementTab> getTabs();
@Accessor("selectedTab")
AdvancementTab getSelectedTab();
}

View File

@@ -0,0 +1,35 @@
package eu.midnightdust.midnightcontrols.client.mixin;
import eu.midnightdust.midnightcontrols.client.MidnightControlsConfig;
import net.minecraft.client.gui.screen.ChatScreen;
import net.minecraft.client.gui.screen.Screen;
import net.minecraft.client.gui.widget.TextFieldWidget;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.text.Text;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(ChatScreen.class)
public abstract class ChatScreenMixin extends Screen {
@Shadow protected TextFieldWidget chatField;
protected ChatScreenMixin(Text title) {
super(title);
}
@Inject(at = @At("TAIL"), method = "init")
private void midnightcontrols$moveInputField(CallbackInfo ci) {
if (MidnightControlsConfig.moveChat) chatField.setY(4);
}
@Inject(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/widget/TextFieldWidget;setTextFieldFocused(Z)V", shift = At.Shift.AFTER))
private void midnightcontrols$moveInputFieldBackground(MatrixStack matrices, int mouseX, int mouseY, float delta, CallbackInfo ci) {
if (MidnightControlsConfig.moveChat) matrices.translate(0f, -this.height + 16, 0f);
}
@Inject(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/widget/TextFieldWidget;render(Lnet/minecraft/client/util/math/MatrixStack;IIF)V", shift = At.Shift.BEFORE))
private void midnightcontrols$dontMoveOtherStuff(MatrixStack matrices, int mouseX, int mouseY, float delta, CallbackInfo ci) {
if (MidnightControlsConfig.moveChat) matrices.translate(0f, this.height - 16, 0f);
}
}

View File

@@ -0,0 +1,22 @@
/*
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of midnightcontrols.
*
* Licensed under the MIT license. For more information,
* see the LICENSE file.
*/
package eu.midnightdust.midnightcontrols.client.mixin;
import net.minecraft.client.gui.widget.ClickableWidget;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
@Mixin(ClickableWidget.class)
public interface ClickableWidgetAccessor {
@Accessor("height")
int getHeight();
@Accessor("focused")
void setFocused(boolean value);
}

View File

@@ -0,0 +1,103 @@
/*
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of midnightcontrols.
*
* Licensed under the MIT license. For more information,
* see the LICENSE file.
*/
package eu.midnightdust.midnightcontrols.client.mixin;
import com.mojang.authlib.GameProfile;
import eu.midnightdust.midnightcontrols.MidnightControls;
import eu.midnightdust.midnightcontrols.client.MidnightControlsClient;
import eu.midnightdust.midnightcontrols.client.MidnightControlsConfig;
import eu.midnightdust.midnightcontrols.client.controller.MovementHandler;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.input.Input;
import net.minecraft.client.network.AbstractClientPlayerEntity;
import net.minecraft.client.network.ClientPlayerEntity;
import net.minecraft.client.world.ClientWorld;
import net.minecraft.entity.MovementType;
import net.minecraft.network.encryption.PlayerPublicKey;
import net.minecraft.util.math.Vec3d;
import org.jetbrains.annotations.Nullable;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
/**
* Injects the anti fly drifting feature.
*/
@Mixin(ClientPlayerEntity.class)
public abstract class ClientPlayerEntityMixin extends AbstractClientPlayerEntity {
private boolean midnightcontrols$driftingPrevented = false;
public ClientPlayerEntityMixin(ClientWorld world, GameProfile profile) {
super(world, profile);
}
@Shadow
protected abstract boolean hasMovementInput();
@Shadow
@Final
protected MinecraftClient client;
@Shadow
public Input input;
@Shadow
protected abstract boolean isCamera();
@Shadow protected int ticksLeftToDoubleTapSprint;
@Inject(method = "move(Lnet/minecraft/entity/MovementType;Lnet/minecraft/util/math/Vec3d;)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/network/AbstractClientPlayerEntity;move(Lnet/minecraft/entity/MovementType;Lnet/minecraft/util/math/Vec3d;)V"))
public void onMove(MovementType type, Vec3d movement, CallbackInfo ci) {
if (!MidnightControlsConfig.doubleTapToSprint) ticksLeftToDoubleTapSprint = 0;
if (!MidnightControls.isExtrasLoaded) return;
if (type == MovementType.SELF) {
if (this.getAbilities().flying && (!MidnightControlsConfig.flyDrifting || !MidnightControlsConfig.verticalFlyDrifting)) {
if (!this.hasMovementInput()) {
if (!this.midnightcontrols$driftingPrevented) {
if (!MidnightControlsConfig.flyDrifting)
this.setVelocity(this.getVelocity().multiply(0, 1.0, 0));
}
this.midnightcontrols$driftingPrevented = true;
} else
this.midnightcontrols$driftingPrevented = false;
}
}
}
@Inject(method = "tickMovement", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/input/Input;tick(ZF)V", shift = At.Shift.AFTER))
public void onInputUpdate(CallbackInfo ci) {
MovementHandler.HANDLER.applyMovement((ClientPlayerEntity) (Object) this);
}
@Inject(method = "tickMovement", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/network/ClientPlayerEntity;isCamera()Z"))
public void onTickMovement(CallbackInfo ci) {
if (this.getAbilities().flying && this.isCamera()) {
if (MidnightControlsConfig.verticalFlyDrifting || !MidnightControls.isExtrasLoaded)
return;
int moving = 0;
if (this.input.sneaking) {
--moving;
}
if (this.input.jumping) {
++moving;
}
if (moving == 0) {
this.setVelocity(this.getVelocity().multiply(1.0, 0.0, 1.0));
}
}
}
}

View File

@@ -0,0 +1,39 @@
/*
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of midnightcontrols.
*
* Licensed under the MIT license. For more information,
* see the LICENSE file.
*/
package eu.midnightdust.midnightcontrols.client.mixin;
import eu.midnightdust.lib.util.screen.TexturedOverlayButtonWidget;
import eu.midnightdust.midnightcontrols.client.gui.MidnightControlsSettingsScreen;
import net.minecraft.client.gui.screen.Screen;
import net.minecraft.client.gui.screen.option.ControlsOptionsScreen;
import net.minecraft.client.gui.screen.option.GameOptionsScreen;
import net.minecraft.client.option.GameOptions;
import net.minecraft.text.Text;
import net.minecraft.util.Identifier;
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;
/**
* Injects the new controls settings button.
*/
@Mixin(ControlsOptionsScreen.class)
public abstract class ControlsOptionsScreenMixin extends GameOptionsScreen {
public ControlsOptionsScreenMixin(Screen parent, GameOptions gameOptions, Text text) {
super(parent, gameOptions, text);
}
@Inject(method = "init", at = @At(value = "INVOKE", ordinal = 1, shift = At.Shift.AFTER, target = "Lnet/minecraft/client/gui/screen/option/ControlsOptionsScreen;addDrawableChild(Lnet/minecraft/client/gui/Element;)Lnet/minecraft/client/gui/Element;"))
private void addControllerButton(CallbackInfo ci) {
this.addDrawableChild(new TexturedOverlayButtonWidget(this.width / 2 + 158, this.height / 6 - 12, 20, 20,0,0,20, new Identifier("midnightcontrols", "textures/gui/midnightcontrols_button.png"), 32, 64, (button) -> {
this.client.setScreen(new MidnightControlsSettingsScreen(this, false));
}, Text.translatable("midnightcontrols.menu.title.controller")));
}
}

View File

@@ -0,0 +1,61 @@
/*
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of midnightcontrols.
*
* Licensed under the MIT license. For more information,
* see the LICENSE file.
*/
package eu.midnightdust.midnightcontrols.client.mixin;
import net.minecraft.client.gui.screen.ingame.CreativeInventoryScreen;
import net.minecraft.item.ItemGroup;
import net.minecraft.item.ItemGroups;
import net.minecraft.screen.slot.Slot;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
import org.spongepowered.asm.mixin.gen.Invoker;
/**
* Represents an accessor to CreativeInventoryScreen.
*/
@Mixin(CreativeInventoryScreen.class)
public interface CreativeInventoryScreenAccessor {
/**
* Gets the selected tab.
*
* @return the selected tab index
*/
@Accessor("selectedTab")
static ItemGroup getSelectedTab() {
return ItemGroups.getDefaultTab();
}
/**
* Sets the selected tab.
*
* @param group the tab's item group
*/
@Invoker("setSelectedTab")
void midnightcontrols$setSelectedTab(@NotNull ItemGroup group);
/**
* Returns whether the slot belongs to the creative inventory or not.
*
* @param slot the slot to check
* @return true if the slot is from the creative inventory, else false
*/
@Invoker("isCreativeInventorySlot")
boolean midnightcontrols$isCreativeInventorySlot(@Nullable Slot slot);
/**
* Returns whether the current tab has a scrollbar or not.
*
* @return true if the current tab has a scrollbar, else false
*/
@Invoker("hasScrollbar")
boolean midnightcontrols$hasScrollbar();
}

View File

@@ -1,21 +1,20 @@
/*
* Copyright © 2019 LambdAurora <aurora42lambda@gmail.com>
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of LambdaControls.
* This file is part of midnightcontrols.
*
* Licensed under the MIT license. For more information,
* see the LICENSE file.
*/
package me.lambdaurora.lambdacontrols.mixin;
package eu.midnightdust.midnightcontrols.client.mixin;
import net.minecraft.client.gui.widget.EntryListWidget;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Invoker;
@Mixin(EntryListWidget.class)
public interface EntryListWidgetAccessor
{
public interface EntryListWidgetAccessor {
@Invoker("moveSelection")
void move_selection(int amount);
void midnightcontrols$moveSelection(EntryListWidget.MoveDirection direction);
}

View File

@@ -0,0 +1,36 @@
/*
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of midnightcontrols.
*
* Licensed under the MIT license. For more information,
* see the LICENSE file.
*/
package eu.midnightdust.midnightcontrols.client.mixin;
import net.minecraft.client.option.GameOptions;
import net.minecraft.client.option.SimpleOption;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
/**
* Represents a mixin to GameOptions.
* <p>
* Sets the default of the Auto-Jump option to false.
*/
@Mixin(GameOptions.class)
public abstract class GameOptionsMixin {
@Shadow public abstract SimpleOption<Boolean> getAutoJump();
@Inject(method = "load", at = @At("HEAD"))
public void onInit(CallbackInfo ci) {
// Set default value of the Auto-Jump option to false.
getAutoJump().setValue(false);
}
}

View File

@@ -0,0 +1,35 @@
/*
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of midnightcontrols.
*
* Licensed under the MIT license. For more information,
* see the LICENSE file.
*/
package eu.midnightdust.midnightcontrols.client.mixin;
import eu.midnightdust.midnightcontrols.ControlsMode;
import eu.midnightdust.midnightcontrols.client.MidnightControlsClient;
import eu.midnightdust.midnightcontrols.client.MidnightControlsConfig;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.render.GameRenderer;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(GameRenderer.class)
public class GameRendererMixin {
@Shadow
@Final
private MinecraftClient client;
@Inject(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/Mouse;getX()D", shift = At.Shift.BEFORE))
private void onRender(float tickDelta, long startTime, boolean fullRender, CallbackInfo ci) {
if (this.client.currentScreen != null && MidnightControlsConfig.controlsMode == ControlsMode.CONTROLLER)
MidnightControlsClient.get().input.onPreRenderScreen(this.client, this.client.currentScreen);
}
}

View File

@@ -0,0 +1,82 @@
/*
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of midnightcontrols.
*
* Licensed under the MIT license. For more information,
* see the LICENSE file.
*/
package eu.midnightdust.midnightcontrols.client.mixin;
import eu.midnightdust.midnightcontrols.ControlsMode;
import eu.midnightdust.midnightcontrols.client.MidnightControlsConfig;
import eu.midnightdust.midnightcontrols.client.MidnightInput;
import eu.midnightdust.midnightcontrols.client.compat.EMICompat;
import eu.midnightdust.midnightcontrols.client.compat.MidnightControlsCompat;
import eu.midnightdust.midnightcontrols.client.controller.ButtonBinding;
import eu.midnightdust.midnightcontrols.client.gui.MidnightControlsRenderer;
import eu.midnightdust.midnightcontrols.client.util.HandledScreenAccessor;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gui.screen.ingame.HandledScreen;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.screen.slot.Slot;
import net.minecraft.screen.slot.SlotActionType;
import net.minecraft.text.Text;
import org.jetbrains.annotations.Nullable;
import org.lwjgl.glfw.GLFW;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.gen.Accessor;
import org.spongepowered.asm.mixin.gen.Invoker;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
/**
* Represents the mixin for the class ContainerScreen.
*/
@Mixin(HandledScreen.class)
public abstract class HandledScreenMixin implements HandledScreenAccessor {
@Accessor("x")
public abstract int getX();
@Accessor("y")
public abstract int getY();
@Invoker("getSlotAt")
public abstract Slot midnightcontrols$getSlotAt(double posX, double posY);
@Invoker("isClickOutsideBounds")
public abstract boolean midnightcontrols$isClickOutsideBounds(double mouseX, double mouseY, int x, int y, int button);
@Invoker("onMouseClick")
public abstract void midnightcontrols$onMouseClick(@Nullable Slot slot, int slotId, int clickData, SlotActionType actionType);
@Inject(method = "render", at = @At("RETURN"))
public void onRender(MatrixStack matrices, int mouseX, int mouseY, float delta, CallbackInfo ci) {
if (MidnightControlsConfig.controlsMode == ControlsMode.CONTROLLER && MidnightControlsConfig.hudEnable) {
var client = MinecraftClient.getInstance();
matrices.push();
int x = 2, y = client.getWindow().getScaledHeight() - 2 - MidnightControlsRenderer.ICON_SIZE;
if (MidnightControlsCompat.isEMIPresent() && EMICompat.isEMIEnabled()) {
x += 42;
}
if (!ButtonBinding.TAKE_ALL.isNotBound()) x = MidnightControlsRenderer.drawButtonTip(matrices, x, y, ButtonBinding.TAKE_ALL,true, client) + 2;
if (!ButtonBinding.EXIT.isNotBound()) x = MidnightControlsRenderer.drawButtonTip(matrices, x, y, ButtonBinding.EXIT, true, client) + 2;
if (MidnightControlsCompat.isReiPresent()) {
x = 2;
y -= 24;
}
if (MidnightControlsCompat.isEMIPresent() && EMICompat.isEMIEnabled() && EMICompat.isSearchBarCentered()) {
x = client.getWindow().getScaledWidth() - 55 - client.textRenderer.getWidth(Text.translatable("midnightcontrols.action.pickup"))
- client.textRenderer.getWidth(Text.translatable("midnightcontrols.action.quick_move"))
- MidnightControlsRenderer.getBindingIconWidth(ButtonBinding.TAKE) - MidnightControlsRenderer.getBindingIconWidth(ButtonBinding.QUICK_MOVE);
}
if (!ButtonBinding.TAKE.isNotBound()) x = MidnightControlsRenderer.drawButtonTip(matrices, x, y, ButtonBinding.TAKE, true, client);
if (!ButtonBinding.QUICK_MOVE.isNotBound()) MidnightControlsRenderer.drawButtonTip(matrices, x, y, ButtonBinding.QUICK_MOVE, true, client);
matrices.pop();
}
}
}

View File

@@ -0,0 +1,14 @@
package eu.midnightdust.midnightcontrols.client.mixin;
import net.minecraft.client.option.KeyBinding;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
import java.util.Map;
@Mixin(KeyBinding.class)
public interface KeyBindingIDAccessor {
@Accessor @Final
static Map<String, KeyBinding> getKEYS_BY_ID() {return null;};
}

View File

@@ -0,0 +1,42 @@
/*
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of midnightcontrols.
*
* Licensed under the MIT license. For more information,
* see the LICENSE file.
*/
package eu.midnightdust.midnightcontrols.client.mixin;
import eu.midnightdust.midnightcontrols.client.util.KeyBindingAccessor;
import net.minecraft.client.option.KeyBinding;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
@Mixin(KeyBinding.class)
public class KeyBindingMixin implements KeyBindingAccessor {
@Shadow
private int timesPressed;
@Shadow
private boolean pressed;
@Override
public boolean midnightcontrols$press() {
boolean oldPressed = this.pressed;
if (!this.pressed)
this.pressed = true;
++this.timesPressed;
return !oldPressed;
}
@Override
public boolean midnightcontrols$unpress() {
if (this.pressed) {
this.pressed = false;
return true;
}
return false;
}
}

View File

@@ -0,0 +1,19 @@
package eu.midnightdust.midnightcontrols.client.mixin;
import net.fabricmc.fabric.impl.client.keybinding.KeyBindingRegistryImpl;
import net.minecraft.client.option.KeyBinding;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
import java.util.List;
@Mixin(value = KeyBindingRegistryImpl.class, remap = false)
public interface KeyBindingRegistryImplAccessor {
@Accessor @Final
static List<KeyBinding> getMODDED_KEY_BINDINGS() {
return null;
}
}

View File

@@ -0,0 +1,154 @@
/*
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of midnightcontrols.
*
* Licensed under the MIT license. For more information,
* see the LICENSE file.
*/
package eu.midnightdust.midnightcontrols.client.mixin;
import eu.midnightdust.midnightcontrols.MidnightControls;
import eu.midnightdust.midnightcontrols.MidnightControlsFeature;
import eu.midnightdust.midnightcontrols.client.MidnightControlsClient;
import eu.midnightdust.midnightcontrols.client.MidnightInput;
import eu.midnightdust.midnightcontrols.client.gui.MidnightControlsRenderer;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gui.screen.Screen;
import net.minecraft.client.network.ClientPlayerEntity;
import net.minecraft.client.network.ClientPlayerInteractionManager;
import net.minecraft.client.render.GameRenderer;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.client.world.ClientWorld;
import net.minecraft.item.BlockItem;
import net.minecraft.item.ItemStack;
import net.minecraft.util.ActionResult;
import net.minecraft.util.Hand;
import net.minecraft.util.hit.BlockHitResult;
import net.minecraft.util.hit.HitResult;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.util.math.Vec3d;
import org.jetbrains.annotations.Nullable;
import org.objectweb.asm.Opcodes;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
@Mixin(MinecraftClient.class)
public abstract class MinecraftClientMixin {
@Shadow
@Nullable
public HitResult crosshairTarget;
@Shadow
@Nullable
public ClientPlayerEntity player;
@Shadow
@Nullable
public ClientPlayerInteractionManager interactionManager;
@Shadow
@Final
public GameRenderer gameRenderer;
@Shadow
private int itemUseCooldown;
private BlockPos midnightcontrols$lastTargetPos;
private Vec3d midnightcontrols$lastPos;
private Direction midnightcontrols$lastTargetSide;
@Inject(method = "<init>", at = @At("RETURN"))
private void onInit(CallbackInfo ci) {
MidnightControlsClient.get().onMcInit((MinecraftClient) (Object) this);
}
@Inject(method = "tick", at = @At("HEAD"))
private void onStartTick(CallbackInfo ci) {
if (this.player == null)
return;
if (!MidnightControlsFeature.FAST_BLOCK_PLACING.isAvailable())
return;
if (this.midnightcontrols$lastPos == null)
this.midnightcontrols$lastPos = this.player.getPos();
int cooldown = this.itemUseCooldown;
BlockHitResult hitResult;
if (this.crosshairTarget != null && this.crosshairTarget.getType() == HitResult.Type.BLOCK && this.player.getAbilities().flying) {
hitResult = (BlockHitResult) this.crosshairTarget;
var targetPos = hitResult.getBlockPos();
var side = hitResult.getSide();
boolean sidewaysBlockPlacing = this.midnightcontrols$lastTargetPos == null || !targetPos.equals(this.midnightcontrols$lastTargetPos.offset(this.midnightcontrols$lastTargetSide));
boolean backwardsBlockPlacing = this.player.input.movementForward < 0.0f && (this.midnightcontrols$lastTargetPos == null || targetPos.equals(this.midnightcontrols$lastTargetPos.offset(this.midnightcontrols$lastTargetSide)));
if (cooldown > 1
&& !targetPos.equals(this.midnightcontrols$lastTargetPos)
&& (sidewaysBlockPlacing || backwardsBlockPlacing)) {
this.itemUseCooldown = 1;
}
this.midnightcontrols$lastTargetPos = targetPos.toImmutable();
this.midnightcontrols$lastTargetSide = side;
}
// Removed front placing sprinting as way too cheaty.
// else if (this.player.isSprinting()) {
// hitResult = MidnightControlsClient.get().reacharound.getLastReacharoundResult();
// if (hitResult != null) {
// if (cooldown > 0)
// this.itemUseCooldown = 0;
// }
// }
this.midnightcontrols$lastPos = this.player.getPos();
}
@Inject(method = "render", at = @At("HEAD"))
private void onRender(CallbackInfo ci) {
MidnightControlsClient.get().onRender((MinecraftClient) (Object) (this));
}
@Inject(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/GameRenderer;render(FJZ)V", shift = At.Shift.AFTER))
private void renderVirtualCursor(boolean fullRender, CallbackInfo ci) {
MidnightControlsRenderer.renderVirtualCursor(new MatrixStack(), (MinecraftClient) (Object) this);
}
@Inject(method = "doItemUse()V", at = @At(value = "INVOKE", target = "Lnet/minecraft/util/hit/HitResult;getType()Lnet/minecraft/util/hit/HitResult$Type;"), locals = LocalCapture.CAPTURE_FAILEXCEPTION, cancellable = true)
private void onItemUse(CallbackInfo ci, Hand[] hands, int handCount, int handIndex, Hand hand, ItemStack stackInHand) {
var mod = MidnightControlsClient.get();
if (!stackInHand.isEmpty() && this.player.getPitch(0.f) > 35.0F && mod.reacharound.isReacharoundAvailable()) {
if (this.crosshairTarget != null && this.crosshairTarget.getType() == HitResult.Type.MISS && this.player.isOnGround()) {
if (!stackInHand.isEmpty() && stackInHand.getItem() instanceof BlockItem) {
var hitResult = mod.reacharound.getLastReacharoundResult();
if (hitResult == null)
return;
hitResult = mod.reacharound.withSideForReacharound(hitResult, stackInHand);
int previousStackCount = stackInHand.getCount();
var result = this.interactionManager.interactBlock(this.player, hand, hitResult);
if (result.isAccepted()) {
if (result.shouldSwingHand()) {
this.player.swingHand(hand);
if (!stackInHand.isEmpty() && (stackInHand.getCount() != previousStackCount || this.interactionManager.hasCreativeInventory())) {
this.gameRenderer.firstPersonRenderer.resetEquipProgress(hand);
}
}
ci.cancel();
}
if (result == ActionResult.FAIL) {
ci.cancel();
}
}
}
}
}
}

View File

@@ -0,0 +1,70 @@
/*
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of midnightcontrols.
*
* Licensed under the MIT license. For more information,
* see the LICENSE file.
*/
package eu.midnightdust.midnightcontrols.client.mixin;
import eu.midnightdust.midnightcontrols.ControlsMode;
import eu.midnightdust.midnightcontrols.client.MidnightControlsClient;
import eu.midnightdust.midnightcontrols.client.MidnightControlsConfig;
import eu.midnightdust.midnightcontrols.client.util.MouseAccessor;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.Mouse;
import net.minecraft.client.gui.screen.Screen;
import org.lwjgl.glfw.GLFW;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.gen.Accessor;
import org.spongepowered.asm.mixin.gen.Invoker;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
/**
* Adds extra access to the mouse.
*/
@Mixin(Mouse.class)
public abstract class MouseMixin implements MouseAccessor {
@Shadow
@Final
private MinecraftClient client;
@Accessor
public abstract void setLeftButtonClicked(boolean value);
@Invoker("onCursorPos")
public abstract void midnightcontrols$onCursorPos(long window, double x, double y);
@Inject(method = "onMouseButton", at = @At(value = "TAIL"))
private void onMouseBackButton(long window, int button, int action, int mods, CallbackInfo ci) {
if (action == 1 && button == GLFW.GLFW_MOUSE_BUTTON_4 && MinecraftClient.getInstance().currentScreen != null) {
if (MidnightControlsClient.get().input.tryGoBack(MinecraftClient.getInstance().currentScreen)) {
action = 0;
}
}
}
@Inject(method = "isCursorLocked", at = @At("HEAD"), cancellable = true)
private void isCursorLocked(CallbackInfoReturnable<Boolean> ci) {
if (this.client.currentScreen == null) {
if (MidnightControlsConfig.controlsMode == ControlsMode.CONTROLLER && MidnightControlsConfig.virtualMouse) {
ci.setReturnValue(true);
ci.cancel();
}
}
}
@Inject(method = "lockCursor", at = @At("HEAD"), cancellable = true)
private void onCursorLocked(CallbackInfo ci) {
if (/*config.getControlsMode() == ControlsMode.TOUCHSCREEN
||*/ (MidnightControlsConfig.controlsMode == ControlsMode.CONTROLLER && MidnightControlsConfig.virtualMouse))
ci.cancel();
}
}

View File

@@ -0,0 +1,33 @@
/*
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of midnightcontrols.
*
* Licensed under the MIT license. For more information,
* see the LICENSE file.
*/
package eu.midnightdust.midnightcontrols.client.mixin;
import net.minecraft.client.gui.screen.recipebook.RecipeBookWidget;
import net.minecraft.client.gui.screen.recipebook.RecipeGroupButtonWidget;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
import org.spongepowered.asm.mixin.gen.Invoker;
import java.util.List;
@Mixin(RecipeBookWidget.class)
public interface RecipeBookWidgetAccessor {
@Accessor("tabButtons")
List<RecipeGroupButtonWidget> getTabButtons();
@Accessor("currentTab")
RecipeGroupButtonWidget getCurrentTab();
@Accessor("currentTab")
void setCurrentTab(RecipeGroupButtonWidget currentTab);
@Invoker("refreshResults")
void midnightcontrols$refreshResults(boolean resetCurrentPage);
}

View File

@@ -0,0 +1,17 @@
package eu.midnightdust.midnightcontrols.client.mixin;
import net.minecraft.client.gui.Selectable;
import net.minecraft.client.gui.screen.Screen;
import org.jetbrains.annotations.Nullable;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
import java.util.List;
@Mixin(Screen.class)
public interface ScreenAccessor {
@Accessor
List<Selectable> getSelectables();
@Accessor @Nullable
Selectable getSelected();
}

View File

@@ -0,0 +1,102 @@
/*
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of midnightcontrols.
*
* Licensed under the MIT license. For more information,
* see the LICENSE file.
*/
package eu.midnightdust.midnightcontrols.client.mixin;
import eu.midnightdust.lib.util.MidnightColorUtil;
import eu.midnightdust.midnightcontrols.client.MidnightControlsClient;
import eu.midnightdust.midnightcontrols.client.MidnightControlsConfig;
import net.minecraft.block.ShapeContext;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.render.*;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.client.world.ClientWorld;
import net.minecraft.item.BlockItem;
import net.minecraft.item.ItemPlacementContext;
import net.minecraft.item.ItemUsageContext;
import net.minecraft.util.Hand;
import net.minecraft.util.hit.HitResult;
import net.minecraft.util.shape.VoxelShape;
import org.joml.Matrix4f;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.awt.*;
/**
* Represents a mixin to WorldRenderer.
* <p>
* Handles the rendering of the block outline of the reach-around features.
*/
@Mixin(WorldRenderer.class)
public abstract class WorldRendererMixin {
@Shadow
@Final
private MinecraftClient client;
@Shadow
private ClientWorld world;
@Shadow
@Final
private BufferBuilderStorage bufferBuilders;
@Shadow
private static void drawCuboidShapeOutline(MatrixStack matrices, VertexConsumer vertexConsumer, VoxelShape shape, double offsetX, double offsetY, double offsetZ, float red, float green, float blue, float alpha) {
}
@Inject(
method = "render",
at = @At(
value = "FIELD",
target = "Lnet/minecraft/client/MinecraftClient;crosshairTarget:Lnet/minecraft/util/hit/HitResult;",
ordinal = 1,
shift = At.Shift.AFTER
)
)
private void onOutlineRender(MatrixStack matrices, float tickDelta, long limitTime, boolean renderBlockOutline, Camera camera, GameRenderer gameRenderer,
LightmapTextureManager lightmapTextureManager, Matrix4f matrix4f, CallbackInfo ci) {
if (this.client.crosshairTarget == null || this.client.crosshairTarget.getType() != HitResult.Type.MISS || !MidnightControlsConfig.shouldRenderReacharoundOutline)
return;
var result = MidnightControlsClient.get().reacharound.getLastReacharoundResult();
if (result == null)
return;
var blockPos = result.getBlockPos();
if (this.world.getWorldBorder().contains(blockPos) && this.client.player != null) {
var stack = this.client.player.getStackInHand(Hand.MAIN_HAND);
if (stack == null || !(stack.getItem() instanceof BlockItem))
return;
var mod = MidnightControlsClient.get();
var block = ((BlockItem) stack.getItem()).getBlock();
result = mod.reacharound.withSideForReacharound(result, block);
var context = new ItemPlacementContext(new ItemUsageContext(this.client.player, Hand.MAIN_HAND, result));
var placementState = block.getPlacementState(context);
if (placementState == null)
return;
var pos = camera.getPos();
var outlineShape = placementState.getOutlineShape(this.client.world, blockPos, ShapeContext.of(camera.getFocusedEntity()));
Color rgb = MidnightColorUtil.hex2Rgb(MidnightControlsConfig.reacharoundOutlineColorHex);
if (MidnightControlsConfig.reacharoundOutlineColorHex.isEmpty()) rgb = MidnightColorUtil.radialRainbow(1,1);
matrices.push();
var vertexConsumer = this.bufferBuilders.getEntityVertexConsumers().getBuffer(RenderLayer.getLines());
drawCuboidShapeOutline(matrices, vertexConsumer, outlineShape,
(double) blockPos.getX() - pos.getX(), (double) blockPos.getY() - pos.getY(), (double) blockPos.getZ() - pos.getZ(),
rgb.getRed() / 255.f, rgb.getGreen() / 255.f, rgb.getBlue() / 255.f, MidnightControlsConfig.reacharoundOutlineColorAlpha / 255.f);
matrices.pop();
}
}
}

View File

@@ -0,0 +1,72 @@
/*
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of midnightcontrols.
*
* Licensed under the MIT license. For more information,
* see the LICENSE file.
*/
package eu.midnightdust.midnightcontrols.client.ring;
import com.electronwill.nightconfig.core.Config;
import eu.midnightdust.midnightcontrols.client.ButtonState;
import eu.midnightdust.midnightcontrols.client.controller.ButtonBinding;
import eu.midnightdust.midnightcontrols.client.util.KeyBindingAccessor;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.font.TextRenderer;
import net.minecraft.client.gui.screen.Screen;
import net.minecraft.client.option.KeyBinding;
import net.minecraft.client.option.StickyKeyBinding;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.text.OrderedText;
import net.minecraft.text.Text;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.function.Supplier;
public class ButtonBindingRingAction extends RingAction {
public static final Factory FACTORY = new Factory();
public final ButtonBinding binding;
public ButtonBindingRingAction(@NotNull ButtonBinding binding) {
super();
this.binding = binding;
}
@Override
public @NotNull String getName() {
return this.binding.getTranslationKey();
}
@Override
public void onAction(@NotNull RingButtonMode mode) {
binding.handle(MinecraftClient.getInstance(), 1.0f, ButtonState.PRESS);
if (binding.asKeyBinding().isPresent()) {
binding.asKeyBinding().get().setPressed(true);
((KeyBindingAccessor)binding.asKeyBinding().get()).midnightcontrols$press();
}
}
@Override
public void drawIcon(@NotNull MatrixStack matrices, @NotNull TextRenderer textRenderer, int x, int y, boolean hovered) {
List<OrderedText> lines = textRenderer.wrapLines(Text.translatable(this.getName()), MidnightRing.ELEMENT_SIZE);
for (int i = 0; i < lines.size(); ++i) {
drawCenteredTextWithShadow(matrices, textRenderer, lines.get(i), x + MidnightRing.ELEMENT_SIZE / 2, y + MidnightRing.ELEMENT_SIZE / 2 - textRenderer.fontHeight / 2 * (lines.size()-1) - textRenderer.fontHeight / 2 + textRenderer.fontHeight * i, 0xffffff);
}
}
protected static class Factory implements RingAction.Factory {
@Override
public @NotNull Supplier<RingAction> newFromGui(@NotNull Screen screen) {
return () -> null;
}
@Override
public @Nullable RingAction parse(@NotNull Config config) {
return null;
}
}
}

View File

@@ -0,0 +1,121 @@
/*
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of midnightcontrols.
*
* Licensed under the MIT license. For more information,
* see the LICENSE file.
*/
package eu.midnightdust.midnightcontrols.client.ring;
import eu.midnightdust.midnightcontrols.client.MidnightControlsClient;
import eu.midnightdust.midnightcontrols.client.MidnightControlsConfig;
import eu.midnightdust.midnightcontrols.client.controller.ButtonBinding;
import eu.midnightdust.midnightcontrols.client.controller.InputManager;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import net.minecraft.util.math.MathHelper;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
/**
* Represents a key binding ring.
*
* @author LambdAurora
* @version 1.7.0
* @since 1.4.0
*/
public final class MidnightRing {
public static final int ELEMENT_SIZE = 75;
private final Map<String, RingAction.Factory> actionFactories = new Object2ObjectOpenHashMap<>();
private final List<RingPage> pages = new ArrayList<>(Collections.singletonList(RingPage.DEFAULT));
private final MidnightControlsClient mod;
private int currentPage = 0;
public MidnightRing(@NotNull MidnightControlsClient mod) {
this.mod = mod;
}
public void registerAction(@NotNull String name, @NotNull RingAction.Factory factory) {
if (this.actionFactories.containsKey(name)) {
this.mod.warn("Tried to register a ring action twice: \"" + name + "\".");
return;
}
this.actionFactories.put(name, factory);
}
/**
* Loads the ring from configuration.
*/
public void loadFromConfig() {
List<String> configBindings = MidnightControlsConfig.ringBindings;
if (configBindings != null) {
this.pages.clear();
int bindingIndex = 0;
for (int i = 0; i < MathHelper.ceil(configBindings.size() / 8f); ++i) {
this.pages.add(new RingPage(i+1 + " / " + MathHelper.ceil(configBindings.size() / 8f)));
}
for (String binding : configBindings) {
ButtonBinding buttonBinding = InputManager.getBinding(binding);
if (buttonBinding != null) {
RingPage page = this.pages.get(MathHelper.fastFloor(bindingIndex / 8f));
page.actions[bindingIndex - 8 * (MathHelper.fastFloor(bindingIndex / 8f))] = (new ButtonBindingRingAction(buttonBinding));
++bindingIndex;
}
}
}
if (this.pages.isEmpty()) {
this.pages.add(RingPage.DEFAULT);
}
}
/**
* Loads the ring from all unbound keys.
*/
public void loadFromUnbound() {
List<ButtonBinding> unboundBindings = InputManager.getUnboundBindings();
if (unboundBindings != null) {
this.pages.clear();
int bindingIndex = 0;
for (int i = 0; i < MathHelper.ceil(unboundBindings.size() / 8f); ++i) {
this.pages.add(new RingPage(i+1 + " / " + MathHelper.ceil(unboundBindings.size() / 8f)));
}
for (ButtonBinding buttonBinding : unboundBindings) {
if (buttonBinding != null) {
RingPage page = this.pages.get(MathHelper.fastFloor(bindingIndex / 8f));
page.actions[bindingIndex - 8 * (MathHelper.fastFloor(bindingIndex / 8f))] = (new ButtonBindingRingAction(buttonBinding));
++bindingIndex;
}
}
}
if (this.pages.isEmpty()) {
this.pages.add(RingPage.DEFAULT);
}
}
public int getMaxPages() {
return this.pages.size();
}
public @NotNull RingPage getCurrentPage() {
if (this.currentPage >= this.pages.size())
this.currentPage = this.pages.size() - 1;
else if (this.currentPage < 0)
this.currentPage = 0;
return this.pages.get(this.currentPage);
}
public void cyclePage(boolean forwards) {
if (forwards) {
if (currentPage < pages.size()-1) ++currentPage;
else currentPage = 0;
} else {
if (currentPage > 0) --currentPage;
else currentPage = pages.size();
}
}
}

View File

@@ -0,0 +1,81 @@
/*
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of midnightcontrols.
*
* Licensed under the MIT license. For more information,
* see the LICENSE file.
*/
package eu.midnightdust.midnightcontrols.client.ring;
import com.electronwill.nightconfig.core.Config;
import net.minecraft.client.font.TextRenderer;
import net.minecraft.client.gui.DrawableHelper;
import net.minecraft.client.gui.screen.Screen;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.text.Text;
import org.aperlambda.lambdacommon.utils.Nameable;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.function.Supplier;
/**
* Represents a ring action.
*
* @author LambdAurora
* @version 1.5.0
* @since 1.4.0
*/
public abstract class RingAction extends DrawableHelper implements Nameable {
protected boolean activated = false;
public RingAction() {
}
/**
* Gets the text name of the ring action.
*
* @return the text name
*/
public Text getTextName() {
return Text.translatable(this.getName());
}
/**
* Returns whether the action is activated or not.
*
* @return true if the action is activated, else false
*/
public boolean isActivated() {
return this.activated;
}
public void activate(@NotNull RingButtonMode mode) {
this.activated = !this.activated;
this.onAction(mode);
}
public abstract void onAction(@NotNull RingButtonMode mode);
public void render(@NotNull MatrixStack matrices, @NotNull TextRenderer textRenderer, int x, int y, boolean hovered, int index) {
fill(matrices, x, y, x + MidnightRing.ELEMENT_SIZE, y + MidnightRing.ELEMENT_SIZE, hovered || RingPage.selected == index ? 0xbb777777 : 0xbb000000);
drawIcon(matrices, textRenderer, x, y, hovered);
}
public abstract void drawIcon(@NotNull MatrixStack matrices, @NotNull TextRenderer textRenderer, int x, int y, boolean hovered);
/**
* Represents a factory for {@link RingAction}.
*
* @version 1.4.3
* @since 1.4.3
*/
public interface Factory {
@NotNull Supplier<RingAction> newFromGui(@NotNull Screen screen);
@Nullable RingAction parse(@NotNull Config config);
}
}

View File

@@ -0,0 +1,70 @@
/*
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of midnightcontrols.
*
* Licensed under the MIT license. For more information,
* see the LICENSE file.
*/
package eu.midnightdust.midnightcontrols.client.ring;
import net.minecraft.text.Text;
import org.aperlambda.lambdacommon.utils.Nameable;
import org.jetbrains.annotations.NotNull;
/**
* Represents the mode of a ring button.
*
* @author LambdAurora
* @version 1.4.0
* @since 1.4.0
*/
public enum RingButtonMode implements Nameable {
PRESS("press"),
HOLD("hold"),
TOGGLE("toggle");
private final String name;
private final Text text;
RingButtonMode(@NotNull String name) {
this.name = name;
this.text = Text.translatable(this.getTranslationKey());
}
/**
* Returns the next ring button mode available.
*
* @return the next ring button mode
*/
public @NotNull RingButtonMode next() {
var v = values();
if (v.length == this.ordinal() + 1)
return v[0];
return v[this.ordinal() + 1];
}
/**
* Returns the translation key of this ring button mode.
*
* @return the translation key of this ring button mode
*/
public @NotNull String getTranslationKey() {
return "midnightcontrols.ring.button_mode." + this.getName();
}
/**
* Gets the translated name of this ring button mode.
*
* @return the translated name of this ring button mode
*/
public @NotNull Text getTranslatedText() {
return this.text;
}
@Override
public @NotNull String getName() {
return this.name;
}
}

View File

@@ -0,0 +1,140 @@
/*
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of midnightcontrols.
*
* Licensed under the MIT license. For more information,
* see the LICENSE file.
*/
package eu.midnightdust.midnightcontrols.client.ring;
import com.electronwill.nightconfig.core.Config;
import eu.midnightdust.midnightcontrols.client.MidnightControlsClient;
import eu.midnightdust.midnightcontrols.client.controller.ButtonBinding;
import eu.midnightdust.midnightcontrols.client.controller.InputManager;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.font.TextRenderer;
import net.minecraft.client.gui.DrawableHelper;
import net.minecraft.client.gui.screen.ChatScreen;
import net.minecraft.client.util.math.MatrixStack;
import org.jetbrains.annotations.NotNull;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
/**
* Represents a ring page.
*
* @author LambdAurora
* @version 1.5.0
* @since 1.4.0
*/
public class RingPage extends DrawableHelper {
public static final RingPage DEFAULT = new RingPage("Default");
public final String name;
public static int selected = -1;
public RingAction[] actions = new RingAction[8];
public RingPage(@NotNull String name) {
this.name = name;
for (int i = 0; i < 8; i++) {
this.actions[i] = null;
}
}
/**
* Renders the ring page.
*
* @param matrices the matrices
* @param width the screen width
* @param height the screen height
* @param mouseX the mouse X-coordinate
* @param mouseY the mouse Y-coordinate
* @param tickDelta the tick delta
*/
public void render(@NotNull MatrixStack matrices, @NotNull TextRenderer textRenderer, int width, int height, int mouseX, int mouseY, float tickDelta) {
int centerX = width / 2;
int centerY = height / 2;
if (MidnightControlsClient.get().ring.getMaxPages() > 1) drawCenteredText(matrices, textRenderer, name, centerX, 5, 0xffffff);
int offset = MidnightRing.ELEMENT_SIZE + (MidnightRing.ELEMENT_SIZE / 2) + 5;
int y = centerY - offset;
int x = centerX - offset;
for (int i = 0; i < 3; i++) {
var ringAction = this.actions[i];
if (ringAction != null)
ringAction.render(matrices, textRenderer, x, y, isHovered(x, y, mouseX, mouseY), i);
x += MidnightRing.ELEMENT_SIZE + 5;
}
y += MidnightRing.ELEMENT_SIZE + 5;
x = centerX - offset;
for (int i = 3; i < 5; i++) {
var ringAction = this.actions[i];
if (ringAction != null)
ringAction.render(matrices, textRenderer, x, y, isHovered(x, y, mouseX, mouseY), i);
x += (MidnightRing.ELEMENT_SIZE + 5) * 2;
}
y += MidnightRing.ELEMENT_SIZE + 5;
x = centerX - offset;
for (int i = 5; i < 8; i++) {
var ringAction = this.actions[i];
if (ringAction != null)
ringAction.render(matrices, textRenderer, x, y, isHovered(x, y, mouseX, mouseY), i);
x += MidnightRing.ELEMENT_SIZE + 5;
}
}
private static boolean isHovered(int x, int y, int mouseX, int mouseY) {
return mouseX >= x && mouseY >= y && mouseX <= x + MidnightRing.ELEMENT_SIZE && mouseY <= y + MidnightRing.ELEMENT_SIZE && selected < 0;
}
/**
* Renders the ring page.
*
* @param width the screen width
* @param height the screen height
* @param mouseX the mouse X-coordinate
* @param mouseY the mouse Y-coordinate
*/
public boolean onClick(int width, int height, int mouseX, int mouseY) {
int centerX = width / 2;
int centerY = height / 2;
int offset = MidnightRing.ELEMENT_SIZE + (MidnightRing.ELEMENT_SIZE / 2) + 5;
int y = centerY - offset;
int x = centerX - offset;
for (int i = 0; i < 3; i++) {
var ringAction = this.actions[i];
if (ringAction != null && isHovered(x,y,mouseX,mouseY)) {
ringAction.activate(RingButtonMode.PRESS);
return true;
}
x += MidnightRing.ELEMENT_SIZE + 5;
}
y += MidnightRing.ELEMENT_SIZE + 5;
x = centerX - offset;
for (int i = 3; i < 5; i++) {
var ringAction = this.actions[i];
if (ringAction != null && isHovered(x,y,mouseX,mouseY)) {
ringAction.activate(RingButtonMode.PRESS);
return true;
}
x += (MidnightRing.ELEMENT_SIZE + 5) * 2;
}
y += MidnightRing.ELEMENT_SIZE + 5;
x = centerX - offset;
for (int i = 5; i < 8; i++) {
var ringAction = this.actions[i];
if (ringAction != null && isHovered(x,y,mouseX,mouseY)) {
ringAction.activate(RingButtonMode.PRESS);
return true;
}
x += MidnightRing.ELEMENT_SIZE + 5;
}
return false;
}
}

View File

@@ -0,0 +1,54 @@
/*
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of midnightcontrols.
*
* Licensed under the MIT license. For more information,
* see the LICENSE file.
*/
package eu.midnightdust.midnightcontrols.client.util;
import net.minecraft.screen.slot.Slot;
import net.minecraft.screen.slot.SlotActionType;
import org.jetbrains.annotations.Nullable;
/**
* Represents an accessor to AbstractContainerScreen.
*/
public interface HandledScreenAccessor {
/**
* Gets the left coordinate of the GUI.
*
* @return the left coordinate of the GUI
*/
int getX();
/**
* Gets the top coordinate of the GUI.
*
* @return the top coordinate of the GUI
*/
int getY();
/**
* Gets the slot at position.
*
* @param posX the X position to check
* @param posY the Y position to check
* @return the slot at the specified position
*/
Slot midnightcontrols$getSlotAt(double posX, double posY);
boolean midnightcontrols$isClickOutsideBounds(double mouseX, double mouseY, int x, int y, int button);
/**
* Handles a mouse click on the specified slot.
*
* @param slot the slot instance
* @param slotId the slot id
* @param clickData the click data
* @param actionType the action type
*/
void midnightcontrols$onMouseClick(@Nullable Slot slot, int slotId, int clickData, SlotActionType actionType);
}

View File

@@ -0,0 +1,26 @@
/*
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of midnightcontrols.
*
* Licensed under the MIT license. For more information,
* see the LICENSE file.
*/
package eu.midnightdust.midnightcontrols.client.util;
/**
* Represents a Minecraft keybinding with extra access.
*/
public interface KeyBindingAccessor {
boolean midnightcontrols$press();
boolean midnightcontrols$unpress();
default boolean midnightcontrols$handlePressState(boolean pressed) {
if (pressed)
return this.midnightcontrols$press();
else
return this.midnightcontrols$unpress();
}
}

View File

@@ -0,0 +1,18 @@
/*
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of midnightcontrols.
*
* Licensed under the MIT license. For more information,
* see the LICENSE file.
*/
package eu.midnightdust.midnightcontrols.client.util;
/**
* Represents mouse's extra access.
*/
public interface MouseAccessor {
void midnightcontrols$onCursorPos(long window, double x, double y);
void setLeftButtonClicked(boolean value);
}

View File

@@ -0,0 +1,34 @@
/*
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of midnightcontrols.
*
* Licensed under the MIT license. For more information,
* see the LICENSE file.
*/
package eu.midnightdust.midnightcontrols.event;
import eu.midnightdust.midnightcontrols.ControlsMode;
import net.fabricmc.fabric.api.event.Event;
import net.fabricmc.fabric.api.event.EventFactory;
import net.minecraft.entity.player.PlayerEntity;
import org.jetbrains.annotations.NotNull;
/**
* Represents an event callback which is fired when a player changes the controls mode.
*
* @author LambdAurora
* @version 1.1.0
* @since 1.1.0
*/
@FunctionalInterface
public interface PlayerChangeControlsModeCallback {
Event<PlayerChangeControlsModeCallback> EVENT = EventFactory.createArrayBacked(PlayerChangeControlsModeCallback.class, listeners -> (player, controlsMode) -> {
for (PlayerChangeControlsModeCallback event : listeners) {
event.apply(player, controlsMode);
}
});
void apply(@NotNull PlayerEntity player, @NotNull ControlsMode controlsMode);
}

View File

@@ -1,507 +0,0 @@
/*
* Copyright © 2019 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of LambdaControls.
*
* Licensed under the MIT license. For more information,
* see the LICENSE file.
*/
package me.lambdaurora.lambdacontrols;
import me.lambdaurora.lambdacontrols.util.KeyBindingAccessor;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.options.GameOptions;
import net.minecraft.client.options.KeyBinding;
import net.minecraft.client.resource.language.I18n;
import net.minecraft.client.util.ScreenshotUtils;
import org.aperlambda.lambdacommon.Identifier;
import org.aperlambda.lambdacommon.utils.Identifiable;
import org.aperlambda.lambdacommon.utils.Nameable;
import org.jetbrains.annotations.NotNull;
import org.lwjgl.glfw.GLFW;
import java.util.*;
import java.util.function.Consumer;
import java.util.stream.Stream;
/**
* Represents a button binding.
*
* @author LambdAurora
*/
public class ButtonBinding implements Nameable
{
private static final List<ButtonBinding> BINDINGS = new ArrayList<>();
private static final List<Category> CATEGORIES = new ArrayList<>();
public static final Category MOVEMENT_CATEGORY;
public static final Category GAMEPLAY_CATEGORY;
public static final Category INVENTORY_CATEGORY;
public static final Category MULTIPLAYER_CATEGORY;
public static final Category MISC_CATEGORY;
public static final ButtonBinding ATTACK = new ButtonBinding("attack", axis_as_button(GLFW.GLFW_GAMEPAD_AXIS_RIGHT_TRIGGER, true));
public static final ButtonBinding BACK = new ButtonBinding("back", axis_as_button(GLFW.GLFW_GAMEPAD_AXIS_LEFT_Y, false));
public static final ButtonBinding CHAT = new ButtonBinding("chat", GLFW.GLFW_GAMEPAD_BUTTON_DPAD_RIGHT);
public static final ButtonBinding DROP_ITEM = new ButtonBinding("drop_item", GLFW.GLFW_GAMEPAD_BUTTON_B);
public static final ButtonBinding FORWARD = new ButtonBinding("forward", axis_as_button(GLFW.GLFW_GAMEPAD_AXIS_LEFT_Y, true));
public static final ButtonBinding INVENTORY = new ButtonBinding("inventory", GLFW.GLFW_GAMEPAD_BUTTON_Y);
public static final ButtonBinding JUMP = new ButtonBinding("jump", GLFW.GLFW_GAMEPAD_BUTTON_A);
public static final ButtonBinding LEFT = new ButtonBinding("left", axis_as_button(GLFW.GLFW_GAMEPAD_AXIS_LEFT_X, false));
public static final ButtonBinding PAUSE_GAME = new ButtonBinding("pause_game", GLFW.GLFW_GAMEPAD_BUTTON_START);
public static final ButtonBinding PICK_BLOCK = new ButtonBinding("pick_block", GLFW.GLFW_GAMEPAD_BUTTON_DPAD_LEFT);
public static final ButtonBinding PLAYER_LIST = new ButtonBinding("player_list", GLFW.GLFW_GAMEPAD_BUTTON_BACK);
public static final ButtonBinding RIGHT = new ButtonBinding("right", axis_as_button(GLFW.GLFW_GAMEPAD_AXIS_LEFT_X, true));
public static final ButtonBinding SCREENSHOT = new ButtonBinding("screenshot", GLFW.GLFW_GAMEPAD_BUTTON_DPAD_DOWN,
Collections.singletonList((client, action) -> {
if (action == 0)
ScreenshotUtils.saveScreenshot(client.runDirectory, client.getWindow().getFramebufferWidth(), client.getWindow().getFramebufferHeight(), client.getFramebuffer(),
text -> client.execute(() -> client.inGameHud.getChatHud().addMessage(text)));
return true;
}));
public static final ButtonBinding SMOOTH_CAMERA = new ButtonBinding("toggle_smooth_camera", -1);
public static final ButtonBinding SNEAK = new ButtonBinding("sneak", GLFW.GLFW_GAMEPAD_BUTTON_RIGHT_THUMB);
public static final ButtonBinding SPRINT = new ButtonBinding("sprint", GLFW.GLFW_GAMEPAD_BUTTON_LEFT_THUMB);
public static final ButtonBinding SWAP_HANDS = new ButtonBinding("swap_hands", GLFW.GLFW_GAMEPAD_BUTTON_X);
public static final ButtonBinding TOGGLE_PERSPECTIVE = new ButtonBinding("toggle_perspective", GLFW.GLFW_GAMEPAD_BUTTON_DPAD_UP);
public static final ButtonBinding USE = new ButtonBinding("use", axis_as_button(GLFW.GLFW_GAMEPAD_AXIS_LEFT_TRIGGER, true));
private int button;
private int default_button;
private String key;
private KeyBinding minecraft_key_binding = null;
private List<PressAction> actions = new ArrayList<>(Collections.singletonList((client, action) -> {
this.as_key_binding().ifPresent(key_binding -> ((KeyBindingAccessor) key_binding).handle_press_state(this.is_button_down()));
return true;
}));
private boolean pressed = false;
protected ButtonBinding(@NotNull String key, int default_button, @NotNull List<PressAction> actions)
{
this.default_button = this.button = default_button;
this.key = key;
this.actions.addAll(actions);
BINDINGS.add(this);
}
protected ButtonBinding(@NotNull String key, int default_button)
{
this(key, default_button, Collections.emptyList());
}
/**
* Returns the button bound.
*
* @return The bound button.
*/
public int get_button()
{
return this.button;
}
/**
* Sets the bound button.
*
* @param button The bound button.
*/
public void set_button(int button)
{
this.button = button;
}
/**
* Returns whether the bound button is the specified button or not.
*
* @param button The button to check.
* @return True if the bound button is the specified button, else false.
*/
public boolean is_button(int button)
{
return this.button == button;
}
/**
* Returns whether this button is down or not.
*
* @return True if the button is down, else false.
*/
public boolean is_button_down()
{
return this.pressed;
}
/**
* Returns whether this button binding is bound or not.
*
* @return True if this button binding is bound, else false.
*/
public boolean is_not_bound()
{
return this.button == -1;
}
/**
* Gets the default button assigned to this binding.
*
* @return The default button.
*/
public int get_default_button()
{
return this.default_button;
}
/**
* Returns whether the assigned button is the default button.
*
* @return True if the assigned button is the default button, else false.
*/
public boolean is_default()
{
return this.button == this.default_button;
}
@Override
public @NotNull String get_name()
{
return this.key;
}
/**
* Returns the translation key of this button binding.
*
* @return The translation key.
*/
public @NotNull String get_translation_key()
{
return "lambdacontrols.action." + this.get_name();
}
/**
* Returns the key binding equivalent of this button binding.
*
* @return The key binding equivalent.
*/
public @NotNull Optional<KeyBinding> as_key_binding()
{
return Optional.ofNullable(this.minecraft_key_binding);
}
/**
* Returns the specified axis as a button.
*
* @param axis The axis.
* @param positive True if the axis part is positive, else false.
* @return The axis as a button.
*/
public static int axis_as_button(int axis, boolean positive)
{
return positive ? 100 + axis : 200 + axis;
}
/**
* Returns the second Joycon's specified button code.
*
* @param button The raw button code.
* @return The second Joycon's button code.
*/
public static int joycon2_button(int button)
{
return 300 + button;
}
public static void init(@NotNull GameOptions options)
{
ATTACK.minecraft_key_binding = options.keyAttack;
BACK.minecraft_key_binding = options.keyBack;
CHAT.minecraft_key_binding = options.keyChat;
DROP_ITEM.minecraft_key_binding = options.keyDrop;
FORWARD.minecraft_key_binding = options.keyForward;
INVENTORY.minecraft_key_binding = options.keyInventory;
JUMP.minecraft_key_binding = options.keyJump;
LEFT.minecraft_key_binding = options.keyLeft;
PICK_BLOCK.minecraft_key_binding = options.keyPickItem;
PLAYER_LIST.minecraft_key_binding = options.keyPlayerList;
RIGHT.minecraft_key_binding = options.keyRight;
SCREENSHOT.minecraft_key_binding = options.keyScreenshot;
SMOOTH_CAMERA.minecraft_key_binding = options.keySmoothCamera;
SNEAK.minecraft_key_binding = options.keySneak;
SPRINT.minecraft_key_binding = options.keySprint;
SWAP_HANDS.minecraft_key_binding = options.keySwapHands;
TOGGLE_PERSPECTIVE.minecraft_key_binding = options.keyTogglePerspective;
USE.minecraft_key_binding = options.keyUse;
}
public static void load_from_config(@NotNull LambdaControlsConfig config)
{
BINDINGS.forEach(config::load_button_binding);
}
public static void set_button_state(int button, boolean state)
{
BINDINGS.parallelStream().filter(binding -> Objects.equals(binding.button, button))
.forEach(binding -> binding.pressed = state);
}
public static void handle_button(@NotNull MinecraftClient client, int button, int action)
{
BINDINGS.parallelStream().filter(binding -> binding.button == button)
.forEach(binding -> {
for (int i = binding.actions.size() - 1; i >= 0; i--) {
if (binding.actions.get(i).press(client, action))
break;
}
});
}
/**
* Returns whether the button has duplicated bindings.
*
* @param button The button to check.
* @return True if the button has duplicated bindings, else false.
*/
public static boolean has_duplicates(int button)
{
return BINDINGS.parallelStream().filter(binding -> binding.button == button).count() > 1;
}
/**
* Returns the localized name of the specified button.
*
* @param button The button.
* @return The localized name of the button.
*/
public static @NotNull String get_localized_button_name(int button)
{
switch (button) {
case -1:
return I18n.translate("key.keyboard.unknown");
case GLFW.GLFW_GAMEPAD_BUTTON_A:
return I18n.translate("lambdacontrols.button.a");
case GLFW.GLFW_GAMEPAD_BUTTON_B:
return I18n.translate("lambdacontrols.button.b");
case GLFW.GLFW_GAMEPAD_BUTTON_X:
return I18n.translate("lambdacontrols.button.x");
case GLFW.GLFW_GAMEPAD_BUTTON_Y:
return I18n.translate("lambdacontrols.button.y");
case GLFW.GLFW_GAMEPAD_BUTTON_LEFT_BUMPER:
return I18n.translate("lambdacontrols.button.left_bumper");
case GLFW.GLFW_GAMEPAD_BUTTON_RIGHT_BUMPER:
return I18n.translate("lambdacontrols.button.right_bumper");
case GLFW.GLFW_GAMEPAD_BUTTON_BACK:
return I18n.translate("lambdacontrols.button.back");
case GLFW.GLFW_GAMEPAD_BUTTON_START:
return I18n.translate("lambdacontrols.button.start");
case GLFW.GLFW_GAMEPAD_BUTTON_GUIDE:
return I18n.translate("lambdacontrols.button.guide");
case GLFW.GLFW_GAMEPAD_BUTTON_LEFT_THUMB:
return I18n.translate("lambdacontrols.button.left_thumb");
case GLFW.GLFW_GAMEPAD_BUTTON_RIGHT_THUMB:
return I18n.translate("lambdacontrols.button.right_thumb");
case GLFW.GLFW_GAMEPAD_BUTTON_DPAD_UP:
return I18n.translate("lambdacontrols.button.dpad_up");
case GLFW.GLFW_GAMEPAD_BUTTON_DPAD_RIGHT:
return I18n.translate("lambdacontrols.button.dpad_right");
case GLFW.GLFW_GAMEPAD_BUTTON_DPAD_DOWN:
return I18n.translate("lambdacontrols.button.dpad_down");
case GLFW.GLFW_GAMEPAD_BUTTON_DPAD_LEFT:
return I18n.translate("lambdacontrols.button.dpad_left");
case 100:
return I18n.translate("lambdacontrols.axis.left_x+");
case 101:
return I18n.translate("lambdacontrols.axis.left_y+");
case 102:
return I18n.translate("lambdacontrols.axis.right_x+");
case 103:
return I18n.translate("lambdacontrols.axis.right_y+");
case 104:
return I18n.translate("lambdacontrols.axis.left_trigger");
case 105:
return I18n.translate("lambdacontrols.axis.right_trigger");
case 200:
return I18n.translate("lambdacontrols.axis.left_x-");
case 201:
return I18n.translate("lambdacontrols.axis.left_y-");
case 202:
return I18n.translate("lambdacontrols.axis.right_x-");
case 203:
return I18n.translate("lambdacontrols.axis.right_y-");
default:
return I18n.translate("lambdacontrols.button.unknown", button);
}
}
public static @NotNull Stream<ButtonBinding> stream()
{
return BINDINGS.stream();
}
public static @NotNull Stream<Category> stream_categories()
{
return CATEGORIES.stream();
}
static {
MOVEMENT_CATEGORY = register_default_category("key.categories.movement", category -> category.register_all_bindings(
FORWARD,
BACK,
LEFT,
RIGHT,
JUMP,
SNEAK,
SPRINT));
GAMEPLAY_CATEGORY = register_default_category("key.categories.gameplay", category -> category.register_all_bindings(
ATTACK,
PICK_BLOCK,
USE
));
INVENTORY_CATEGORY = register_default_category("key.categories.inventory", category -> category.register_all_bindings(
DROP_ITEM,
INVENTORY,
SWAP_HANDS
));
MULTIPLAYER_CATEGORY = register_default_category("key.categories.multiplayer",
category -> category.register_all_bindings(CHAT, PLAYER_LIST));
MISC_CATEGORY = register_default_category("key.categories.misc", category -> category.register_all_bindings(
SCREENSHOT,
//SMOOTH_CAMERA,
TOGGLE_PERSPECTIVE
));
}
public static ButtonBinding register(@NotNull Identifier binding_id, int default_button, @NotNull List<PressAction> actions)
{
return new ButtonBinding(binding_id.get_namespace() + "." + binding_id.get_name(), default_button, actions);
}
public static ButtonBinding register(@NotNull Identifier binding_id, int default_button)
{
return register(binding_id, default_button, Collections.emptyList());
}
public static ButtonBinding register(@NotNull net.minecraft.util.Identifier binding_id, int default_button, @NotNull List<PressAction> actions)
{
return register(new Identifier(binding_id.getNamespace(), binding_id.getPath()), default_button, actions);
}
public static ButtonBinding register(@NotNull net.minecraft.util.Identifier binding_id, int default_button)
{
return register(binding_id, default_button, Collections.emptyList());
}
/**
* Registers a category of button bindings.
*
* @param category The category to register.
* @return The registered category.
*/
public static Category register_category(@NotNull Category category)
{
CATEGORIES.add(category);
return category;
}
public static Category register_category(@NotNull Identifier identifier, int priority)
{
return register_category(new Category(identifier, priority));
}
public static Category register_category(@NotNull Identifier identifier)
{
return register_category(new Category(identifier));
}
private static Category register_default_category(@NotNull String key, @NotNull Consumer<Category> key_adder)
{
Category category = register_category(new Identifier("minecraft", key), CATEGORIES.size());
key_adder.accept(category);
return category;
}
public static class Category implements Identifiable
{
private final List<ButtonBinding> bindings = new ArrayList<>();
private final Identifier id;
private int priority;
public Category(@NotNull Identifier id, int priority)
{
this.id = id;
this.priority = priority;
}
public Category(@NotNull Identifier id)
{
this(id, 100);
}
public void register_binding(@NotNull ButtonBinding binding)
{
if (this.bindings.contains(binding))
throw new IllegalStateException("Cannot register twice a button binding in the same category.");
this.bindings.add(binding);
}
public void register_all_bindings(@NotNull ButtonBinding... bindings)
{
this.register_all_bindings(Arrays.asList(bindings));
}
public void register_all_bindings(@NotNull List<ButtonBinding> bindings)
{
bindings.forEach(this::register_binding);
}
/**
* Gets the bindings assigned to this category.
*
* @return The bindings assigned to this category.
*/
public @NotNull List<ButtonBinding> get_bindings()
{
return Collections.unmodifiableList(this.bindings);
}
/**
* Gets the translated name of this category.
* <p>
* The translation key should be `modid.identifier_name`.
*
* @return The translated name.
*/
public @NotNull String get_translated_name()
{
if (this.id.get_namespace().equals("minecraft"))
return I18n.translate(this.id.get_name());
else
return I18n.translate(this.id.get_namespace() + "." + this.id.get_name());
}
/**
* Gets the priority display of this category.
* It will defines in which order the categories will display on the controls screen.
*
* @return The priority of this category.
*/
public int get_priority()
{
return this.priority;
}
@Override
public @NotNull Identifier get_identifier()
{
return this.id;
}
}
@FunctionalInterface
public static interface PressAction
{
/**
* Handles when there is a press action on the button.
*
* @param client The client instance.
* @param action The action done.
*/
boolean press(@NotNull MinecraftClient client, int action);
}
}

View File

@@ -1,175 +0,0 @@
/*
* Copyright © 2019 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of LambdaControls.
*
* Licensed under the MIT license. For more information,
* see the LICENSE file.
*/
package me.lambdaurora.lambdacontrols;
import org.aperlambda.lambdacommon.utils.Nameable;
import org.jetbrains.annotations.NotNull;
import org.lwjgl.glfw.GLFW;
import org.lwjgl.glfw.GLFWGamepadState;
import java.io.File;
import java.io.IOException;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.channels.SeekableByteChannel;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import static org.lwjgl.BufferUtils.createByteBuffer;
/**
* Represents a controller.
*/
public class Controller implements Nameable
{
private static final Map<Integer, Controller> CONTROLLERS = new HashMap<>();
private final int id;
public Controller(int id)
{
this.id = id;
}
/**
* Gets the identifier of this controller.
*
* @return The identifier of this controller.
*/
public int get_id()
{
return this.id;
}
/**
* Gets the controller's globally unique identifier.
*
* @return The controller's GUID.
*/
public String get_guid()
{
String guid = GLFW.glfwGetJoystickGUID(this.id);
return guid == null ? "" : guid;
}
/**
* Returns whether this controller is connected or not.
*
* @return True if this controller is connected, else false.
*/
public boolean is_connected()
{
return GLFW.glfwJoystickPresent(this.id);
}
/**
* Returns whether this controller is a gamepad or not.
*
* @return True if this controller is a gamepad, else false.
*/
public boolean is_gamepad()
{
return this.is_connected() && GLFW.glfwJoystickIsGamepad(this.id);
}
/**
* Gets the name of the controller.
*
* @return The controller's name.
*/
@Override
public @NotNull String get_name()
{
String name = this.is_gamepad() ? GLFW.glfwGetGamepadName(this.id) : GLFW.glfwGetJoystickName(this.id);
return name == null ? String.valueOf(this.get_id()) : name;
}
/**
* Gets the state of the controller.
*
* @return The state of the controller input.
*/
public GLFWGamepadState get_state()
{
GLFWGamepadState state = GLFWGamepadState.create();
if (this.is_gamepad())
GLFW.glfwGetGamepadState(this.id, state);
return state;
}
public static @NotNull Controller by_id(int id)
{
if (id > GLFW.GLFW_JOYSTICK_LAST) {
LambdaControls.get().log("Controller '" + id + "' doesn't exist.");
id = GLFW.GLFW_JOYSTICK_LAST;
}
Controller controller;
if (CONTROLLERS.containsKey(id))
return CONTROLLERS.get(id);
else {
controller = new Controller(id);
CONTROLLERS.put(id, controller);
return controller;
}
}
public static @NotNull Optional<Controller> by_guid(@NotNull String guid)
{
return CONTROLLERS.values().stream().filter(Controller::is_connected)
.filter(controller -> controller.get_guid().equals(guid))
.max(Comparator.comparingInt(Controller::get_id));
}
/**
* Reads the specified resource and returns the raw data as a ByteBuffer.
*
* @param resource The resource to read.
* @param buffer_size The initial buffer size.
* @return The resource data.
* @throws IOException If an IO error occurs.
*/
private static ByteBuffer io_resource_to_buffer(String resource, int buffer_size) throws IOException
{
ByteBuffer buffer = null;
Path path = Paths.get(resource);
if (Files.isReadable(path)) {
try (SeekableByteChannel fc = Files.newByteChannel(path)) {
buffer = createByteBuffer((int) fc.size() + 2);
while (fc.read(buffer) != -1) ;
buffer.put((byte) 0);
}
}
((Buffer) buffer).flip(); // Force Java 8 >.<
return buffer;
}
/**
* Updates the controller mappings.
*/
public static void update_mappings()
{
try {
File mappings_file = new File("config/gamecontrollerdb.txt");
if (!mappings_file.exists())
return;
LambdaControls.get().log("Updating controller mappings...");
ByteBuffer buffer = io_resource_to_buffer(mappings_file.getPath(), 1024);
GLFW.glfwUpdateGamepadMappings(buffer);
} catch (IOException e) {
e.printStackTrace();
}
}
}

View File

@@ -1,87 +0,0 @@
/*
* Copyright © 2019 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of LambdaControls.
*
* Licensed under the MIT license. For more information,
* see the LICENSE file.
*/
package me.lambdaurora.lambdacontrols;
import net.minecraft.client.resource.language.I18n;
import org.aperlambda.lambdacommon.utils.Nameable;
import org.jetbrains.annotations.NotNull;
import java.util.Arrays;
import java.util.Optional;
/**
* Represents a controller type.
*/
public enum ControllerType implements Nameable
{
DEFAULT(0),
DUALSHOCK(1),
SWITCH(2),
XBOX(3),
STEAM(4),
OUYA(5);
private final int id;
ControllerType(int id)
{
this.id = id;
}
/**
* Returns the controller type's identifier.
*
* @return The controller type's identifier.
*/
public int get_id()
{
return this.id;
}
/**
* Returns the next controller type available.
*
* @return The next available controller type.
*/
public ControllerType next()
{
ControllerType[] v = values();
if (v.length == this.ordinal() + 1)
return v[0];
return v[this.ordinal() + 1];
}
/**
* Gets the translated name of this controller type.
*
* @return The translated name of this controller type.
*/
public String get_translated_name()
{
return I18n.translate("lambdacontrols.controller_type." + this.get_name());
}
@Override
public @NotNull String get_name()
{
return this.name().toLowerCase();
}
/**
* Gets the controller type from its identifier.
*
* @param id The identifier of the controller type.
* @return The controller type if found, else empty.
*/
public static Optional<ControllerType> by_id(@NotNull String id)
{
return Arrays.stream(values()).filter(mode -> mode.get_name().equalsIgnoreCase(id)).findFirst();
}
}

View File

@@ -1,67 +0,0 @@
/*
* Copyright © 2019 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of LambdaControls.
*
* Licensed under the MIT license. For more information,
* see the LICENSE file.
*/
package me.lambdaurora.lambdacontrols;
import net.minecraft.client.resource.language.I18n;
import org.aperlambda.lambdacommon.utils.Nameable;
import org.jetbrains.annotations.NotNull;
import java.util.Arrays;
import java.util.Optional;
/**
* Represents the controls mode.
*/
public enum ControlsMode implements Nameable
{
DEFAULT,
CONTROLLER,
TOUCHSCREEN;
/**
* Returns the next controls mode available.
*
* @return The next available controls mode.
*/
public ControlsMode next()
{
ControlsMode[] v = values();
if (v.length == this.ordinal() + 1)
return v[0];
return v[this.ordinal() + 1];
}
/**
* Gets the translated name of this controls mode.
*
* @return The translated name of this controls mode.
*/
public String get_translated_name()
{
return I18n.translate("lambdacontrols.controls_mode." + this.get_name());
}
@Override
public @NotNull String get_name()
{
return this.name().toLowerCase();
}
/**
* Gets the controls mode from its identifier.
*
* @param id The identifier of the controls mode.
* @return The controls mode if found, else empty.
*/
public static Optional<ControlsMode> by_id(@NotNull String id)
{
return Arrays.stream(values()).filter(mode -> mode.get_name().equalsIgnoreCase(id)).findFirst();
}
}

View File

@@ -1,66 +0,0 @@
/*
* Copyright © 2019 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of LambdaControls.
*
* Licensed under the MIT license. For more information,
* see the LICENSE file.
*/
package me.lambdaurora.lambdacontrols;
import net.minecraft.client.resource.language.I18n;
import org.aperlambda.lambdacommon.utils.Nameable;
import org.jetbrains.annotations.NotNull;
import java.util.Arrays;
import java.util.Optional;
/**
* Represents the hud side which is the side where the movements buttons are.
*/
public enum HudSide implements Nameable
{
LEFT,
RIGHT;
/**
* Returns the next side available.
*
* @return The next available side.
*/
public HudSide next()
{
HudSide[] v = values();
if (v.length == this.ordinal() + 1)
return v[0];
return v[this.ordinal() + 1];
}
/**
* Gets the translated name of this hud side.
*
* @return The translated name of this hud side.
*/
public String get_translated_name()
{
return I18n.translate("lambdacontrols.hud_side." + this.get_name());
}
@Override
public @NotNull String get_name()
{
return this.name().toLowerCase();
}
/**
* Gets the hud side from its identifier.
*
* @param id The identifier of the hud side.
* @return The hud side if found, else empty.
*/
public static Optional<HudSide> by_id(@NotNull String id)
{
return Arrays.stream(values()).filter(mode -> mode.get_name().equalsIgnoreCase(id)).findFirst();
}
}

View File

@@ -1,251 +0,0 @@
/*
* Copyright © 2019 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of LambdaControls.
*
* Licensed under the MIT license. For more information,
* see the LICENSE file.
*/
package me.lambdaurora.lambdacontrols;
import com.mojang.blaze3d.platform.GlStateManager;
import net.fabricmc.api.ClientModInitializer;
import net.fabricmc.fabric.api.client.keybinding.FabricKeyBinding;
import net.fabricmc.fabric.api.client.keybinding.KeyBindingRegistry;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.font.TextRenderer;
import net.minecraft.client.gui.DrawableHelper;
import net.minecraft.client.resource.language.I18n;
import net.minecraft.client.toast.SystemToast;
import net.minecraft.client.util.InputUtil;
import net.minecraft.text.LiteralText;
import net.minecraft.text.TranslatableText;
import net.minecraft.util.Identifier;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.NotNull;
import org.lwjgl.glfw.GLFW;
/**
* Represents the LambdaControls mod.
*/
public class LambdaControls implements ClientModInitializer
{
private static LambdaControls INSTANCE;
public static final FabricKeyBinding BINDING_LOOK_UP = FabricKeyBinding.Builder.create(new Identifier("lambdacontrols", "look_up"),
InputUtil.Type.KEYSYM, GLFW.GLFW_KEY_KP_8, "key.categories.movement").build();
public static final FabricKeyBinding BINDING_LOOK_RIGHT = FabricKeyBinding.Builder.create(new Identifier("lambdacontrols", "look_right"),
InputUtil.Type.KEYSYM, GLFW.GLFW_KEY_KP_6, "key.categories.movement").build();
public static final FabricKeyBinding BINDING_LOOK_DOWN = FabricKeyBinding.Builder.create(new Identifier("lambdacontrols", "look_down"),
InputUtil.Type.KEYSYM, GLFW.GLFW_KEY_KP_2, "key.categories.movement").build();
public static final FabricKeyBinding BINDING_LOOK_LEFT = FabricKeyBinding.Builder.create(new Identifier("lambdacontrols", "look_left"),
InputUtil.Type.KEYSYM, GLFW.GLFW_KEY_KP_4, "key.categories.movement").build();
public static final Identifier CONTROLLER_BUTTONS = new Identifier("lambdacontrols", "textures/gui/controller_buttons.png");
public static final Identifier CONTROLLER_AXIS = new Identifier("lambdacontrols", "textures/gui/controller_axis.png");
public final Logger logger = LogManager.getLogger("LambdaControls");
public final LambdaControlsConfig config = new LambdaControlsConfig(this);
public final LambdaInput input = new LambdaInput(this);
private ControlsMode previous_controls_mode;
@Override
public void onInitializeClient()
{
INSTANCE = this;
this.log("Initializing LambdaControls...");
this.config.load();
KeyBindingRegistry.INSTANCE.register(BINDING_LOOK_UP);
KeyBindingRegistry.INSTANCE.register(BINDING_LOOK_RIGHT);
KeyBindingRegistry.INSTANCE.register(BINDING_LOOK_DOWN);
KeyBindingRegistry.INSTANCE.register(BINDING_LOOK_LEFT);
}
/**
* This method is called when Minecraft is initializing.
*/
public void on_mc_init(@NotNull MinecraftClient client)
{
Controller.update_mappings();
ButtonBinding.init(client.options);
GLFW.glfwSetJoystickCallback((jid, event) -> {
if (event == GLFW.GLFW_CONNECTED) {
Controller controller = Controller.by_id(jid);
client.getToastManager().add(new SystemToast(SystemToast.Type.TUTORIAL_HINT, new TranslatableText("lambdacontrols.controller.connected", jid),
new LiteralText(controller.get_name())));
} else if (event == GLFW.GLFW_DISCONNECTED) {
client.getToastManager().add(new SystemToast(SystemToast.Type.TUTORIAL_HINT, new TranslatableText("lambdacontrols.controller.disconnected", jid),
null));
}
this.switch_controls_mode();
});
}
/**
* This method is called every Minecraft tick.
*
* @param client The client instance.
*/
public void on_tick(@NotNull MinecraftClient client)
{
this.input.on_tick(client);
if (this.config.get_controls_mode() == ControlsMode.CONTROLLER)
this.input.on_controller_tick(client);
}
public void on_render(MinecraftClient client)
{
this.input.on_render(client);
}
/**
* Switches the controls mode if the auto switch is enabled.
*/
public void switch_controls_mode()
{
if (this.config.has_auto_switch_mode()) {
if (this.config.get_controller().is_gamepad()) {
this.previous_controls_mode = this.config.get_controls_mode();
this.config.set_controls_mode(ControlsMode.CONTROLLER);
} else {
if (this.previous_controls_mode == null) {
this.previous_controls_mode = ControlsMode.DEFAULT;
}
this.config.set_controls_mode(this.previous_controls_mode);
}
}
}
/**
* Prints a message to the terminal.
*
* @param info The message to print.
*/
public void log(String info)
{
this.logger.info("[LambdaControls] " + info);
}
/**
* Gets the LambdaControls instance.
*
* @return The LambdaControls instance.
*/
public static LambdaControls get()
{
return INSTANCE;
}
public static int draw_button(int x, int y, @NotNull ButtonBinding button, @NotNull MinecraftClient client)
{
return draw_button(x, y, button.get_button(), client);
}
public static int draw_button(int x, int y, int button, @NotNull MinecraftClient client)
{
if (button == -1)
return 0;
int controller_type = get().config.get_controller_type().get_id();
boolean axis = false;
int button_offset = button * 15;
switch (button) {
case GLFW.GLFW_GAMEPAD_BUTTON_LEFT_BUMPER:
button_offset = 7 * 15;
break;
case GLFW.GLFW_GAMEPAD_BUTTON_RIGHT_BUMPER:
button_offset = 8 * 15;
break;
case GLFW.GLFW_GAMEPAD_BUTTON_BACK:
button_offset = 4 * 15;
break;
case GLFW.GLFW_GAMEPAD_BUTTON_START:
button_offset = 6 * 15;
break;
case GLFW.GLFW_GAMEPAD_BUTTON_GUIDE:
button_offset = 5 * 15;
break;
case GLFW.GLFW_GAMEPAD_BUTTON_LEFT_THUMB:
button_offset = 15 * 15;
break;
case GLFW.GLFW_GAMEPAD_BUTTON_RIGHT_THUMB:
button_offset = 16 * 15;
break;
case GLFW.GLFW_GAMEPAD_AXIS_LEFT_X + 100:
button_offset = 0;
axis = true;
break;
case GLFW.GLFW_GAMEPAD_AXIS_LEFT_Y + 100:
button_offset = 18;
axis = true;
break;
case GLFW.GLFW_GAMEPAD_AXIS_RIGHT_X + 100:
button_offset = 2 * 18;
axis = true;
break;
case GLFW.GLFW_GAMEPAD_AXIS_RIGHT_Y + 100:
button_offset = 3 * 18;
axis = true;
break;
case GLFW.GLFW_GAMEPAD_AXIS_LEFT_X + 200:
button_offset = 4 * 18;
axis = true;
break;
case GLFW.GLFW_GAMEPAD_AXIS_LEFT_Y + 200:
button_offset = 5 * 18;
axis = true;
break;
case GLFW.GLFW_GAMEPAD_AXIS_RIGHT_X + 200:
button_offset = 6 * 18;
axis = true;
break;
case GLFW.GLFW_GAMEPAD_AXIS_RIGHT_Y + 200:
button_offset = 7 * 18;
axis = true;
break;
case GLFW.GLFW_GAMEPAD_AXIS_LEFT_TRIGGER + 100:
case GLFW.GLFW_GAMEPAD_AXIS_LEFT_TRIGGER + 200:
button_offset = 9 * 15;
break;
case GLFW.GLFW_GAMEPAD_AXIS_RIGHT_TRIGGER + 100:
case GLFW.GLFW_GAMEPAD_AXIS_RIGHT_TRIGGER + 200:
button_offset = 10 * 15;
break;
}
client.getTextureManager().bindTexture(axis ? LambdaControls.CONTROLLER_AXIS : LambdaControls.CONTROLLER_BUTTONS);
GlStateManager.disableDepthTest();
GlStateManager.color4f(1.0F, 1.0F, 1.0F, 1.0F);
DrawableHelper.blit(x, y, (float) button_offset, (float) (controller_type * (axis ? 18 : 15)), axis ? 18 : 15, axis ? 18 : 15, 256, 256);
GlStateManager.enableDepthTest();
return axis ? 18 : 15;
}
public static int draw_button_tip(int x, int y, @NotNull ButtonBinding button, boolean display, @NotNull MinecraftClient client)
{
return draw_button_tip(x, y, button.get_button(), button.get_translation_key(), display, client);
}
public static int draw_button_tip(int x, int y, int button, @NotNull String action, boolean display, @NotNull MinecraftClient client)
{
String translated_action = I18n.translate(action);
if (display) {
int button_width = draw_button(x, y, button, client);
int text_y = (15 - client.textRenderer.fontHeight) / 2;
client.textRenderer.drawWithShadow(translated_action, (float) (x + button_width + 5), (float) (y + text_y), 14737632);
}
return display ? get_button_tip_width(translated_action, client.textRenderer) : -10;
}
private static int get_button_tip_width(@NotNull String action, @NotNull TextRenderer text_renderer)
{
return 15 + 5 + text_renderer.getStringWidth(action);
}
}

View File

@@ -1,399 +0,0 @@
/*
* Copyright © 2019 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of LambdaControls.
*
* Licensed under the MIT license. For more information,
* see the LICENSE file.
*/
package me.lambdaurora.lambdacontrols;
import com.electronwill.nightconfig.core.file.FileConfig;
import net.minecraft.client.options.KeyBinding;
import org.jetbrains.annotations.NotNull;
import org.lwjgl.glfw.GLFW;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
/**
* Represents LambdaControls configuration.
*/
public class LambdaControlsConfig
{
private final FileConfig config = FileConfig.builder("config/lambdacontrols.toml").concurrent().defaultResource("/config.toml").build();
private final Map<String, KeyBinding> keybinding_mappings = new HashMap<>();
private final LambdaControls mod;
private ControlsMode controls_mode;
private ControllerType controller_type;
// HUD settings.
private boolean hud_enable;
private HudSide hud_side;
// Controller settings
private double dead_zone;
private double rotation_speed;
private double mouse_speed;
public LambdaControlsConfig(@NotNull LambdaControls mod)
{
this.mod = mod;
}
public void load()
{
this.keybinding_mappings.clear();
this.config.load();
this.mod.log("Configuration loaded.");
this.controls_mode = ControlsMode.by_id(this.config.getOrElse("controls", "default")).orElse(ControlsMode.DEFAULT);
// HUD settings.
this.hud_enable = this.config.getOrElse("hud.enable", true);
this.hud_side = HudSide.by_id(this.config.getOrElse("hud.side", "left")).orElse(HudSide.LEFT);
// Controller settings.
this.controller_type = ControllerType.by_id(this.config.getOrElse("controller.type", "default")).orElse(ControllerType.DEFAULT);
this.dead_zone = this.config.getOrElse("controller.dead_zone", 0.25);
this.rotation_speed = this.config.getOrElse("controller.rotation_speed", 40.0);
this.mouse_speed = this.config.getOrElse("controller.mouse_speed", 25.0);
// Controller controls.
ButtonBinding.load_from_config(this);
}
public void save()
{
this.config.set("controller.dead_zone", this.dead_zone);
this.config.set("controller.rotation_speed", this.rotation_speed);
this.config.set("controller.mouse_speed", this.mouse_speed);
this.config.save();
this.mod.log("Configuration saved.");
}
/**
* Gets the controls mode from the configuration.
*
* @return The controls mode.
*/
public @NotNull ControlsMode get_controls_mode()
{
return this.controls_mode;
}
/**
* Sets the controls mode in the configuration.
*
* @param controls_mode The controls mode.
*/
public void set_controls_mode(@NotNull ControlsMode controls_mode)
{
this.controls_mode = controls_mode;
this.config.set("controls", controls_mode.get_name());
}
/**
* Returns whether the auto switch mode is enabled or not.
*
* @return True if the auto switch mode is enabled, else false.
*/
public boolean has_auto_switch_mode()
{
return this.config.getOrElse("auto_switch_mode", false);
}
/**
* Sets whether the auto switch mode is enabled or not.
*
* @param auto_switch_mode True if the auto switch mode is enabled, else false.
*/
public void set_auto_switch_mode(boolean auto_switch_mode)
{
this.config.set("auto_switch_mode", auto_switch_mode);
}
/**
* Returns whether the HUD is enabled.
*
* @return True if the HUD is enabled, else false.
*/
public boolean is_hud_enabled()
{
return this.hud_enable;
}
/**
* Sets whether the HUD is enabled.
*
* @param enable True if the HUD is enabled, else false.
*/
public void set_hud_enabled(boolean enable)
{
this.hud_enable = enable;
this.config.set("hud.enable", this.hud_enable);
}
/**
* Gets the HUD side from the configuration.
*
* @return The HUD side.
*/
public @NotNull HudSide get_hud_side()
{
return this.hud_side;
}
/**
* Sets the HUD side in the configuration.
*
* @param hud_side The HUD side.
*/
public void set_hud_side(@NotNull HudSide hud_side)
{
this.hud_side = hud_side;
this.config.set("hud.side", hud_side.get_name());
}
/**
* Gets the used controller.
*
* @return The used controller.
*/
public @NotNull Controller get_controller()
{
Object raw = this.config.getRaw("controller.id");
if (raw instanceof Number) {
return Controller.by_id((Integer) raw);
} else if (raw instanceof String) {
return Controller.by_guid((String) raw).orElse(Controller.by_id(GLFW.GLFW_JOYSTICK_1));
}
return Controller.by_id(GLFW.GLFW_JOYSTICK_1);
}
/**
* Sets the used controller.
*
* @param controller The used controller.
*/
public void set_controller(@NotNull Controller controller)
{
this.config.set("controller.id", controller.get_id());
}
/**
* Gets the controller's type.
*
* @return The controller's type.
*/
public @NotNull ControllerType get_controller_type()
{
return this.controller_type;
}
/**
* Sets the controller's type.
*
* @param controller_type The controller's type.
*/
public void set_controller_type(@NotNull ControllerType controller_type)
{
this.controller_type = controller_type;
this.config.set("controller.type", controller_type.get_name());
}
/**
* Gets the controller's dead zone from the configuration.
*
* @return The controller's dead zone value.
*/
public double get_dead_zone()
{
return this.dead_zone;
}
/**
* Sets the controller's dead zone in the configuration.
*
* @param dead_zone The new controller's dead zone value.
*/
public void set_dead_zone(double dead_zone)
{
this.dead_zone = dead_zone;
}
/**
* Gets the controller's rotation speed.
*
* @return The rotation speed.
*/
public double get_rotation_speed()
{
return this.rotation_speed;
}
/**
* Sets the controller's rotation speed.
*
* @param rotation_speed The rotation speed.
*/
public void set_rotation_speed(double rotation_speed)
{
this.rotation_speed = rotation_speed;
}
/**
* Gets the controller's mouse speed.
*
* @return The mouse speed.
*/
public double get_mouse_speed()
{
return this.mouse_speed;
}
/**
* Sets the controller's mouse speed.
*
* @param mouse_speed The mouse speed.
*/
public void set_mouse_speed(double mouse_speed)
{
this.mouse_speed = mouse_speed;
}
/**
* Returns whether the right X axis is inverted or not.
*
* @return True if the right X axis is inverted, else false.
*/
public boolean does_invert_right_x_axis()
{
return this.config.getOrElse("controller.invert_right_x_axis", false);
}
/**
* Sets whether the right X axis is inverted or not.
*
* @param invert True if the right X axis is inverted, else false.
*/
public void set_invert_right_x_axis(boolean invert)
{
this.config.set("controller.invert_right_x_axis", invert);
}
/**
* Returns whether the right Y axis is inverted or not.
*
* @return True if the right Y axis is inverted, else false.
*/
public boolean does_invert_right_y_axis()
{
return this.config.getOrElse("controller.invert_right_y_axis", false);
}
/**
* Sets whether the right Y axis is inverted or not.
*
* @param invert True if the right Y axis is inverted, else false.
*/
public void set_invert_right_y_axis(boolean invert)
{
this.config.set("controller.invert_right_y_axis", invert);
}
/**
* Gets the right X axis sign.
*
* @return The right X axis sign.
*/
public double get_right_x_axis_sign()
{
return this.does_invert_right_x_axis() ? -1.0 : 1.0;
}
/**
* Gets the right Y axis sign.
*
* @return The right Y axis sign.
*/
public double get_right_y_axis_sign()
{
return this.does_invert_right_y_axis() ? -1.0 : 1.0;
}
/**
* Returns the keybindings.
*
* @return The keybindings.
*/
public @NotNull Map<String, KeyBinding> get_keybindings()
{
return this.keybinding_mappings;
}
public Optional<KeyBinding> get_keybind(@NotNull String id)
{
return Optional.ofNullable(this.keybinding_mappings.get(id));
}
/**
* Loads the button binding from configuration.
*
* @param button The button binding.
*/
public void load_button_binding(@NotNull ButtonBinding button)
{
button.set_button(this.config.getOrElse("controller.controls." + button.get_name(), button.get_button()));
}
/**
* Sets the button binding in configuration.
*
* @param binding The button binding.
* @param button The button.
*/
public void set_button_binding(@NotNull ButtonBinding binding, int button)
{
binding.set_button(button);
this.config.set("controller.controls." + binding.get_name(), button);
}
public boolean is_back_button(int btn, boolean is_btn, int state)
{
if (!is_btn && state == 0)
return false;
return ButtonBinding.BACK.is_button(ButtonBinding.axis_as_button(btn, state == 1));
}
public boolean is_forward_button(int btn, boolean is_btn, int state)
{
if (!is_btn && state == 0)
return false;
return ButtonBinding.FORWARD.is_button(ButtonBinding.axis_as_button(btn, state == 1));
}
public boolean is_left_button(int btn, boolean is_btn, int state)
{
if (!is_btn && state == 0)
return false;
return ButtonBinding.LEFT.is_button(ButtonBinding.axis_as_button(btn, state == 1));
}
public boolean is_right_button(int btn, boolean is_btn, int state)
{
if (!is_btn && state == 0)
return false;
return ButtonBinding.RIGHT.is_button(ButtonBinding.axis_as_button(btn, state == 1));
}
/**
* Returns whether the specified axis is an axis used for movements.
*
* @param i The axis index.
* @return True if the axis is used for movements, else false.
*/
public boolean is_movement_axis(int i)
{
return ButtonBinding.FORWARD.is_button(ButtonBinding.axis_as_button(i, true)) || ButtonBinding.FORWARD.is_button(ButtonBinding.axis_as_button(i, false))
|| ButtonBinding.BACK.is_button(ButtonBinding.axis_as_button(i, true)) || ButtonBinding.BACK.is_button(ButtonBinding.axis_as_button(i, false))
|| ButtonBinding.LEFT.is_button(ButtonBinding.axis_as_button(i, true)) || ButtonBinding.LEFT.is_button(ButtonBinding.axis_as_button(i, false))
|| ButtonBinding.RIGHT.is_button(ButtonBinding.axis_as_button(i, true)) || ButtonBinding.RIGHT.is_button(ButtonBinding.axis_as_button(i, false));
}
}

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