Compare commits

...

235 Commits

Author SHA1 Message Date
Martin Prokoph
13adff2e26 fix: resourcepacks appearing as outdated 2025-12-11 16:28:57 +01:00
Martin Prokoph
381d715fa8 port: Mounts of Mayham (1.21.11) 2025-12-11 16:27:12 +01:00
Martin Prokoph
d668707300 fix: depend on SpruceUI instead of ObsidianUI 2025-10-30 22:12:45 +01:00
Martin Prokoph
dac5e55ee1 release: v1.11.3-beta.2 2025-10-29 15:16:27 +01:00
Martin Prokoph
6b89dbf8e5 feat: back to SpruceUI!
- SpruceUI is now also available for NeoForge, so I can ditch ObsidianUI and move back to an actively maintained UI library :)
2025-10-03 18:47:27 +02:00
Martin Prokoph
faf24ced17 feat: improve options screen with tooltips & better reset 2025-10-02 17:24:25 +02:00
Martin Prokoph
d7ea484e71 clean: remove double tap to sprint option
Now configurable in vanilla
2025-10-02 17:23:00 +02:00
Martin Prokoph
e24ecdc78b config: reduce default deadzones 2025-10-02 16:24:15 +02:00
Martin Prokoph
66874b7164 feat: fancy input mode icons 2025-10-02 16:22:47 +02:00
Martin Prokoph
238ea583a2 feat: re-order config class 2025-10-02 16:22:22 +02:00
Martin Prokoph
b3b3eb4d55 feat: switch to new MidnightLib-based screen
What used to be the advanced config screen is now the one-and-only default config screen
2025-10-02 16:21:48 +02:00
Martin Prokoph
c2cab16989 fix: controller mouse click simulation in screens 2025-10-02 00:13:40 +02:00
Martin Prokoph
f2372cf406 release: 1.11.3-alpha.2 2025-10-01 17:07:27 +02:00
Martin Prokoph
ffa1bb5a33 fix: get debug hud toggle working correctly 2025-10-01 17:06:20 +02:00
Martin Prokoph
1d9a4287d7 fix: touchscreen interaction finger outline 2025-10-01 16:55:55 +02:00
Martin Prokoph
b3a170f862 fix: server list moving entries instead of cycling 2025-10-01 16:55:09 +02:00
Martin Prokoph
15e1b08e8f fix: attack key not working properly for mobs 2025-10-01 16:54:24 +02:00
Martin Prokoph
7660f6b0ee release: 1.11.3-alpha.1 2025-10-01 14:14:20 +02:00
Martin Prokoph
5c1657aef2 feat: more mouse cursors 🐁 2025-10-01 14:13:47 +02:00
Martin Prokoph
830ac38b02 fix: button category names 2025-10-01 14:12:12 +02:00
Martin Prokoph
4d85879c87 feat: add more functionality to MidnightLib-based config
- The plan is to slowly phase out ObsidianUI, as it is not updated fast enough
2025-10-01 14:11:47 +02:00
Martin Prokoph
60f5142796 fix: correctly register keybind category
This drastically changed in 1.21.9
2025-09-28 13:51:55 +02:00
Martin Prokoph
b8ee08bf81 feat: support more mouse cursor styles 2025-09-28 13:46:09 +02:00
Martin Prokoph
db7f1daa02 port: The Copper Age (1.21.9) 2025-09-27 23:15:20 +02:00
Martin Prokoph
e50128d75d chore: bump version 2025-09-27 21:52:37 +02:00
Martin Prokoph
837ead55fb build: switch to official ObsidianUI version
- Previously, I used my own build to achieve 1.21.6 compatibility
2025-09-27 21:46:26 +02:00
Martin Prokoph
c00d5893e9 Merge pull request #365 from FugLong/fix/macosLaunchCrash
fix: resolve ConcurrentModificationException in InputManager.updateBindings
2025-09-27 21:43:58 +02:00
Elijah Stephenson
af4b40e88a fix: resolve ConcurrentModificationException in InputManager.updateBindings
- Make BINDINGS list thread-safe using Collections.synchronizedList()
- Add synchronized blocks around all BINDINGS access methods
- Prevents crash on macOS clients during launch
- Fixes race condition between main thread and controller input thread
2025-09-26 20:20:51 -05:00
Martin Prokoph
994cd0d155 build: use ObsidianUI from MidnightDust maven 2025-06-21 09:30:11 +02:00
Martin Prokoph
9e4686be32 fix: get book working again with virtualkeyboard 2025-06-21 09:29:37 +02:00
Martin Prokoph
b472503ec4 port: Chase the Skies (1.21.6)
- Needs my ObsidianUI PR
- Virtual keyboard currently does not work in book edit screen
2025-06-17 23:33:43 +02:00
Martin Prokoph
54a43d41c0 chore: bump version 2025-05-19 23:31:20 +02:00
Martin Prokoph
c86df8714c fix: crash on NeoForge 1.21.5 2025-05-19 23:30:06 +02:00
Martin Prokoph
ccad5e1d44 clean: remove GlStateManager calls 2025-05-19 23:14:17 +02:00
Martin Prokoph
58fa3b5bc1 Merge pull request #350 from TeamMidnightDust/feat/virtual-keyboard
feat: virtual keyboard support!
2025-05-19 23:12:39 +02:00
Martin Prokoph
0f407ac245 feat: edit signs using the virtual keyboard! 2025-05-19 22:49:26 +02:00
Martin Prokoph
7375e5ad20 feat: add reset button icon 2025-05-19 20:28:06 +02:00
Martin Prokoph
7b723513ae feat: easy switching between virtual keyboard layouts 2025-05-19 19:18:18 +02:00
Martin Prokoph
0dfd1994dc feat: data-driven virtual keyboard layouts 2025-05-19 16:20:46 +02:00
Martin Prokoph
ecb7cfd888 feat: load virtual keyboard layouts from JSON 2025-05-10 10:38:54 +02:00
Martin Prokoph
18e69c1cb3 Merge pull request #343 from cryy/feat/virtual-keyboard
Feat: Virtual keyboard support
2025-05-08 09:53:58 +02:00
Martin Prokoph
f5be8eb14e Merge branch 'feat/virtual-keyboard' into feat/virtual-keyboard 2025-05-08 09:53:33 +02:00
Martin Prokoph
4a502730bf Merge pull request #338 from muznyo/cz-lang
Add czech language
2025-05-08 09:46:14 +02:00
Martin Prokoph
f263908f5f fix: multiple touch-related bug fixes 2025-05-08 00:08:27 +02:00
Martin Prokoph
28de7312f8 fix: get touchscreen controls working again 2025-05-07 15:08:52 +02:00
cryy
1231c231a9 Fix virtual keyboard config 2025-04-22 23:57:53 +02:00
cryy
24e58027b2 Add obisidianUI text field interop 2025-04-22 23:57:31 +02:00
cryy
33845e111b Fix handler naming 2025-04-22 23:08:48 +02:00
cryy
86622962f8 Fix tooltip entry in en_us locale 2025-04-22 23:05:02 +02:00
cryy
73c5fe1a82 Add virtual keyboard config option 2025-04-22 23:03:07 +02:00
cryy
70d923f959 Implement NeoForge support 2025-04-22 22:52:57 +02:00
cryy
0ef59057af Add missing row width calculation 2025-04-22 20:21:02 +02:00
cryy
9ebd1a9cea Increase displayed lines for newline support 2025-04-22 20:17:25 +02:00
cryy
bb5c6976c0 Replace KeyInfo with String
- KeyInfo was leftover code from toying around with various implementations. String is sufficient in current implementation.
2025-04-22 20:16:08 +02:00
cryy
041eeb29aa Make special key constant private 2025-04-22 19:39:56 +02:00
cryy
662bac3053 Finalize virtual keyboard screen
- Added backspace support
- Added space support
- Added new line support
- Added caps support
- Added symbol support
2025-04-22 19:36:26 +02:00
cryy
50103ce4cf Move cursor to end of text in book 2025-04-20 19:53:26 +02:00
cryy
9e12381471 Implement basic virtual keyboard support
- Listener for clicks inside of text fields and other text-based screens
- Virtual keyboard screen in a QWERTY layout
2025-04-18 19:43:01 +02:00
Ondřej Mužný
945b01a8d6 remove char 2025-03-29 12:02:39 +01:00
Ondřej Mužný
8aa006429d Added czech language 2025-03-29 11:59:47 +01:00
Martin Prokoph
114a72cdde chore: bump version 2025-03-29 00:59:43 +01:00
Martin Prokoph
25e9c9afe3 feat: improve advanced config screen
- Thanks to MidnightLib 1.7.0 :)
2025-03-29 00:58:41 +01:00
Martin Prokoph
d313115388 Merge pull request #327 from Kichura/ge_fun
Gradle 8.13, Change artifact path
2025-03-27 23:33:26 +01:00
Martin Prokoph
e46e855961 Merge branch 'architectury-1.21.5' into ge_fun 2025-03-27 21:22:50 +01:00
Martin Prokoph
38229bc827 port: Spring to Life (1.21.5)
- Still waiting for ObsidianUI to get the settings screen working. Guess I'm gonna have to port that myself again.
2025-03-27 16:10:03 +01:00
Martin Prokoph
f004f0a32d compat: improve/fix Emotecraft compat 2025-03-27 00:00:29 +01:00
Martin Prokoph
c07f3d94dd chore: bump version 2025-03-26 19:54:42 +01:00
Martin Prokoph
6007ef315d fix: controller options button not being clickable 2025-03-26 19:53:31 +01:00
Kichura
1e2dd30e3a Gradle 8.13. 2025-02-25 18:56:00 +01:00
Martin Prokoph
1c26eeed5e fix: catch errors in camera thread
- Solves camera movement breaking entirely when encountering an error
2025-02-15 22:20:29 +01:00
Kichura
f14387b6b4 Gradle 8.12.1. 2025-01-30 02:10:20 +01:00
Kichura
1344a949cd Do not persist credentials. 2025-01-07 22:57:18 +01:00
Kichura
65a0b1cf1f Gradle 8.12, Migrate to setup-gradle. 2025-01-06 23:43:22 +01:00
Kichura
23f7b2e199 Change artifacts path. 2025-01-06 21:16:18 +01:00
Martin Prokoph
e35850c5d5 Bump version 2024-12-11 18:10:57 +01:00
Martin Prokoph
78900ac83e Fix movement while riding boats and other entities 2024-12-11 18:08:40 +01:00
Martin Prokoph
7d791fac89 Port to 1.21.4
- For once, this has been easy :)
2024-12-06 21:11:39 +01:00
Martin Prokoph
bf60595c12 Merge pull request #322 from TeamMidnightDust/architectury-1.21
Sync translations
2024-12-06 21:02:04 +01:00
Martin Prokoph
b96547dafb Merge pull request #319 from FlocosDEV/architectury-1.21
Create pt-br.json
2024-12-06 21:00:56 +01:00
Martin Prokoph
ab869f4f76 First and final 1.21.3 release
- Moving on to 1.21.4
2024-12-06 20:53:14 +01:00
Martin Prokoph
84df412162 Merge pull request #321 from TeamMidnightDust/architectury-1.21
Add @Kichura 's workflow improvements to 1.21.3 branch
2024-12-05 22:10:05 +01:00
Martin Prokoph
afb80fd89c Merge pull request #316 from Kichura/architectury-1.21
Replace Java 16/17 -> 21, Upgrade workflow system
2024-12-04 13:44:12 +01:00
Gabriel
b835a6c4ca Create pt-br.json
Create pt-br.json
2024-11-19 21:36:33 -03:00
Martin Prokoph
3eec0dda4a Update ObsidianUI to my 1.21.3 port 2024-11-18 14:16:44 +01:00
Martin Prokoph
a23e8b8975 Initial port to 1.21.3
- Still waiting for SpruceUI to update, only use the advanced config in the meantime
2024-11-14 21:43:19 +01:00
Martin Prokoph
1aa449b5bd Release 1.10.0 as stable
Identical to rc2 :)
2024-11-12 13:52:46 +01:00
Kichura
35ab81f696 Apply "chmod +x" to gradlew. 2024-10-30 19:44:35 +01:00
Kichura
e08547a641 Ensure fabric requests end-user to use Java 21 instead. 2024-10-29 23:02:21 +01:00
Kichura
c93cc729f4 Upgrade the entire workflow system. 2024-10-29 22:57:47 +01:00
Kichura
1595fffc2c Change Java version reference to 21. 2024-10-29 22:52:02 +01:00
Martin Prokoph
953df7a8c4 Update MidnightLib & Fix various CompatHandlers 2024-10-20 17:21:40 +02:00
Martin Prokoph
a3b8d7cbb5 Various fixes
- Fix #309 (Missing translations)
- Fix controller input always being read
- Improved injection of options button (Closes #305)
2024-10-20 16:59:26 +02:00
Martin Prokoph
929639c081 Fix packets not being optional on Neoforge 2024-10-11 00:02:59 +02:00
Martin Prokoph
abcfd4c98c Update fabric.mod.json 2024-08-30 12:16:29 +02:00
Martin Prokoph
24d169b4c2 First Beta for 1.10.0!
- Separate movement and camera joysticks to fix boat movement (again)
- Fixed crashes with various mods
- Compatibility for Sodium 0.6.0
- Updated MidnightLib to 1.6.1
2024-08-30 12:07:23 +02:00
Martin Prokoph
c0af00f2a3 Cleanup gradle files 2024-07-25 11:10:36 +02:00
Martin Prokoph
333fc7f300 Sensible screen scrolling speeds 2024-07-25 11:07:29 +02:00
Martin Prokoph
dbb6e926e6 Add methods for instantiating storage 2024-07-22 16:20:38 +02:00
Martin Prokoph
78ada44d73 More code improvements 2024-07-22 15:57:43 +02:00
Martin Prokoph
a55e506af0 API hooks for movement and screen pages 2024-07-22 15:23:20 +02:00
Martin Prokoph
540920009f Re-enable camera movement API 2024-07-22 13:35:03 +02:00
Martin Prokoph
044bbfe9ed Use more enums instead of integers 2024-07-22 13:19:09 +02:00
Martin Prokoph
f782e17349 Improve code further 2024-07-22 11:51:57 +02:00
Martin Prokoph
abcbf20eaf Butter-smooth joystick input
- Handle joysticks separately from buttons and triggers
- Joysticks now get updated 1000 times per second
2024-07-21 21:37:46 +02:00
Martin Prokoph
86c96c8a3e Fix mouse back button not being rebindable 2024-07-21 19:32:02 +02:00
Martin Prokoph
310af56162 Improve look keybinds
- Now work regardless of the controller camera mode
- Can now be found in MidnightControls category instead of Minecraft vanilla's
-
2024-07-21 19:25:01 +02:00
Martin Prokoph
cd416cf022 Improve Mouse Emulation & EMI compat
- Closes #294
2024-07-21 18:37:56 +02:00
Martin Prokoph
78df229dd2 Finalize fix for #296 2024-07-21 17:31:39 +02:00
Martin Prokoph
9bcc63fcef Easy copying of controller GUID
- Added a button to copy the current controller GUID (helps with GUID mismatches between MC and mapping programs)
- Added an advanced option to exclude controllers from the GUI by regular expressions (defaults to Touchpad, Keyboard, Pen and Finger)
2024-07-21 17:23:14 +02:00
Martin Prokoph
202ff888be Fix broken Touchscreen behavior 2024-07-18 19:16:36 +02:00
Martin Prokoph
da467619cb Working Neoforge support
Yay!
2024-07-18 14:28:30 +02:00
Martin Prokoph
0d2bd6e4cb Remove non-deprecated references of LambdaJCommon
Sorry Lambda!
This is needed for Neoforge support and to reduce the file size in the future.
2024-07-18 00:27:32 +02:00
Martin Prokoph
241228dcde Further cleanup 2024-07-17 23:43:07 +02:00
Martin Prokoph
45149af859 Improved Cursors
- Cursor position is now float instead of integer, making movement way smoother
- Added a fallback cursor to use in Wayland sessions
2024-07-17 23:23:05 +02:00
Martin Prokoph
bdb3c36518 Reduce code complexity 2024-07-17 20:53:24 +02:00
Martin Prokoph
f82ee2e24a Get Fabric working again, various improvements 2024-07-17 17:05:13 +02:00
Martin Prokoph
27221b62cd Architectury build system & huge code cleanup 2024-07-17 14:26:17 +02:00
Martin Prokoph
9e3b2ae060 Update version number 2024-06-18 19:34:28 +02:00
Martin Prokoph
838fab2c63 Update ObsidianUI & Fix resourcepack warning 2024-06-18 19:33:57 +02:00
Martin Prokoph
2cbe02ebb2 Switch to ObsidianUI
Also remove unnecessary imports.
2024-06-16 18:21:20 +02:00
Martin Prokoph
ba74d98109 Port to 1.21 2024-06-13 20:35:23 +02:00
Martin Prokoph
d3a787090e Always respect minor version in name 2024-04-29 20:55:08 +02:00
Martin Prokoph
00780e13fd Apply mixins conditionally & bump version 2024-04-29 20:51:50 +02:00
Martin Prokoph
a5554ee21d Improve Ranged Combat code 2024-04-29 20:24:42 +02:00
Martin Prokoph
728a2d4603 Merge pull request #272 from kabliz/eye_tracking_range_combat
Improved Range Combat While Eye Tracking
2024-04-29 20:05:21 +02:00
Martin Prokoph
eb54245c21 Merge branch '1.20.5' into eye_tracking_range_combat 2024-04-29 20:05:05 +02:00
Martin Prokoph
e6fcd1469c Port to 1.20.5
Also implemented some nice small fixes :)
2024-04-29 20:03:09 +02:00
Kabliz
8a30b5ee73 Improved Range Combat While Eye Tracking
Adjusted the dynamic camera sensitivity while using an item intended to be used at a distance: the bow, crossbow, trident, and splash potion. The deadzone is now ignored, making slight adjustments to aim a little easier. The deadzone was meant to assist mining and building, but it is a hindrance in ranged combat.
2024-03-17 12:04:24 -07:00
Martin Prokoph
43ffd89a61 Merge pull request #271 from Androser420/1.20.4
Fix Toggle Sprint/Sneak.
2024-03-10 17:07:53 +01:00
Androser420
8a9866f92f Update InputHandlers.java 2024-03-10 00:26:57 +01:00
Androser420
b12413cbcd Update ButtonBinding.java 2024-03-10 00:26:01 +01:00
Martin Prokoph
9f62538197 Replace FAQ with wiki link 2024-02-14 23:42:45 +01:00
Martin Prokoph
ba5143403d Add issue template for mod support 2024-02-14 12:52:16 +01:00
Martin Prokoph
454afa92ec Update issue templates 2024-02-14 12:45:48 +01:00
Martin Prokoph
8e05b658c2 Bump version 2024-02-14 10:48:28 +01:00
Martin Prokoph
cba8a4e1d4 Fix EMI compat 2024-02-14 10:47:52 +01:00
Martin Prokoph
dd0795bc56 Fix virtual mouse 2024-02-14 10:36:27 +01:00
Martin Prokoph
566c26ac17 Bump version 2024-02-03 10:38:54 +01:00
Martin Prokoph
e203e26c3f Fix #255, #257 and likely #258 2024-02-03 10:21:37 +01:00
Martin Prokoph
797d1463a6 Update README.md
Add FAQ regarding orange controller names
2023-12-31 11:11:24 +01:00
Martin Prokoph
3e665a14fd MidnightControls 1.9.2 - Fix #251 2023-12-31 11:00:14 +01:00
Martin Prokoph
2b143c6bf1 Add automatic uploading to gradle script 2023-12-29 12:18:14 +01:00
Martin Prokoph
ab317f3d31 Touch-related fixes 2023-12-28 10:30:43 +01:00
Martin Prokoph
2c11a23914 Add new settings to options screen 2023-12-23 23:15:39 +01:00
Martin Prokoph
6df3e4454b Mixed input & Fix multiple issues
- Update to 1.20.4
- Touch can now be used in controller mode (especially nice on Steam Deck)
- Added option to choose between camera modes (Flat vs. Adaptive, addresses #232)
- Fixed broken button binding (#235)
2023-12-23 23:01:49 +01:00
Motschen
afee0b407d Fix #210 2023-10-03 22:32:03 +02:00
Motschen
d32aefccac Wait for Wynntils keybinding registration
- When Wynntils is present, the keybindings are now loaded only after the keybinding category was created, allowing them to be handled by MidnightControls
2023-10-03 21:37:28 +02:00
Motschen
98797b03ea Get modded keybinds from vanilla instead of fabric
Additional keybinds are now loaded from the vanilla list instead of fabric's modded list
- Better support for mods not using the Fabric Keybinding API (Closes #218)
- Also now supports Forge mods via Sinytra Connector
- Added configurable list to exclude certain keybinds
2023-10-03 18:49:09 +02:00
Motschen
e1d53ee463 Completely rewritten joystick input
- Joystick input is now processed using polar coordinates, resulting in better accuracy and fixing tons of issues (Fixes #135, #138, #186 & #180)
- Camera movement is now way smoother by including the previous stick values in the calculation (Fixes #217 & #167)
- updateMappings() is now called asyncronously (Closes #219)
2023-10-03 17:36:30 +02:00
Motschen
233ae36343 Even more touchscreen improvements
- Fixed #222
Touch-specific improvements
- Interactive items can now be used correctly
- Drop button now works
- Most Keybinds will now work correctly while in touchscreen mode
- Added Touch category to simple options screen
2023-10-02 20:33:12 +02:00
Motschen
eb4f30913a Update pause button texture & version number 2023-09-30 21:23:43 +02:00
Motschen
1186c9c241 Another round of improvements
Mostly related to touch screen support
- Added selection outlines for Break-at-Finger mode
- Breaking blocks/interacting with entities will now just check if the target changed, not the finger position
- Added the ability to create custom touch buttons out of ButtonBindings
- Redesigned top touch buttons
- Added emote touch button when Emotecraft is installed
- Updated SpruceUI
- Removed old mixins
2023-09-30 20:27:03 +02:00
Martin Prokoph
de7cf8a307 Merge pull request #225 from m-jxx1/1.20
Update ko_kr.json
2023-09-30 10:13:49 +02:00
MJ
15510b5997 Update ko_kr.json 2023-09-25 13:28:32 +09:00
Motschen
cb56632ec4 Port to 1.20.2 & Revamp Touchscreen Input
- Port to 1.20.2
- Fixed virtual mouse cursor sometimes being hidden behind objects (closes #221)
- Touchscreen is now actually usable (in theory, I'll have to wait a few weeks for my tablet to arrive to test further)
  - Made it possible to place/break blocks and interact with entities
  - Added a touchscreen mode for interacting with entities and blocks at the position the click was registered at, not just at the crosshair
  - Added a close button to screens without their own back button
- Will be officially released when SpruceUI is updated
2023-09-23 23:13:13 +02:00
Martin Prokoph
9d11d08807 Merge pull request #215 from ImVietnam/patch-1
Add vi_vn.json
2023-09-23 12:18:07 +02:00
Martin Prokoph
54f731c7f0 Merge pull request #209 from notlin4/patch-2
Update Traditional Chinese
2023-09-23 12:17:17 +02:00
Martin Prokoph
3e3ab8743d Merge pull request #208 from kabliz/eyetracking
Eye Tracking Support, No-Hands Minecraft
2023-09-23 12:16:35 +02:00
I_am_Vietnam
ae3d6cc846 Update vi_vn.json 2023-07-04 11:10:33 +07:00
I_am_Vietnam
00c68004e8 Update vi_vn.json 2023-07-03 14:44:21 +07:00
I_am_Vietnam
f0f6a443bf Update vi_vn.json 2023-07-03 14:40:57 +07:00
I_am_Vietnam
d8ff51b3d3 Update vi_vn.json 2023-07-03 12:16:45 +07:00
I_am_Vietnam
0315784a47 Update vi_vn.json 2023-07-03 12:15:56 +07:00
I_am_Vietnam
3a4d23fb91 Update vi_vn.json 2023-07-03 11:36:46 +07:00
I_am_Vietnam
8bce9170a2 Update vi_vn.json 2023-07-03 11:10:15 +07:00
I_am_Vietnam
ec0c334d4a Create vi_vn.json 2023-07-03 11:05:26 +07:00
notlin4
b4209785e0 Update Traditional Chinese 2023-06-17 21:00:49 +08:00
notlin4
fa3d56eb25 Update Traditional Chinese 2023-06-17 20:57:46 +08:00
Kabliz
60bd283c4c Eye Tracking Support, Minecraft Without Hands
Add support for eye tracking hardware, eg the Tobii 5.
Add settings to toggle eye tracking in the settings menu.
Add settings to adjust the deadzone while looking at the crosshair.
Disable mouse raw input while eye tracking is in use, as these two modes are not compatible.
2023-06-11 14:53:36 -07:00
Motschen
889a5d8d58 MidnightControls 1.8.2 - 1.20 & Bug Fixes
- Update to 1.20
- Fix virtual mouse cursor not being visible
- Add option to hide the normal mouse cursor
2023-06-10 14:59:26 +02:00
Martin Prokoph
febf171b88 Merge pull request #195 from notlin4/patch-1
Update Traditional Chinese
2023-06-09 14:27:50 +02:00
notlin4
d133fed2f6 Update zh_tw.json 2023-04-29 21:36:37 +08:00
notlin4
9a61a13fb3 Update zh_tw.json 2023-04-27 18:00:34 +08:00
notlin4
e97223e316 Update zh_tw.json 2023-04-15 16:21:35 +08:00
notlin4
f16f7416ea Update Traditional Chinese 2023-04-15 16:20:26 +08:00
Motschen
c086ecfcef MidnightControls 1.8.1 - Bugfix and Ukrainian Translation
- Fixes #185 (Left joystick not working like a mouse when it's supposed to)
- Includes Ukrainian translation by @forditt (from #170)
2023-03-18 18:53:21 +01:00
Motschen
66cdca30fe MidnightControls 1.8.0 - 1.19.4, New GUI handling, Better reacharound
- Port to 1.19.4
- Drastically improve GUI handling
  - Directions are now respected instead of acting like the Tab key (possible thanks to 1.19.4)
  - The new tabs can be navigated using the shoulder buttons
- Sodium and Iris now have way better controller navigation thanks to isXander's PRs to them
- Improved reacharound thanks to MonstoBusta
- Cleaner advanced options screen
- Better auto-detection of the Steam Deck
2023-03-15 19:14:48 +01:00
Martin Prokoph
6e49667ada Merge pull request #178 from MonstoBusta/1.19.4-Atlas
Partially Rewrite Horizontal Reacharound
2023-03-15 14:21:53 +01:00
Martin Prokoph
d4c3208cee Merge branch '1.19.4' into 1.19.4-Atlas 2023-03-15 14:18:44 +01:00
Motschen
35a4fbee1b Many improvements for new GUI handling 2023-03-09 19:49:21 +01:00
MonstoBusta
2aac2ad4e7 Partially Rewrite Horizontal Reacharound
Replace use of BlockPos with Vec3d to allow double increased clamp on vector instead of integer.
2023-02-27 01:16:24 -06:00
Motschen
4b2919bfca Disable access widener 2023-02-15 11:13:21 +01:00
Motschen
1c999137ba Experimental port to 23w06a
Some stuff is still broken, but I managed to implement directional movement in screens
2023-02-15 11:11:39 +01:00
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
252 changed files with 9981 additions and 4745 deletions

View File

@@ -1,7 +1,7 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
title: 'Bug: TITLE HERE'
labels: bug
assignees: ''
@@ -21,15 +21,17 @@ Steps to reproduce the behavior:
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
If applicable, add screenshots or videos to help explain your problem.
**Desktop (please complete the following information):**
- OS: [e.g. Linux]
- Minecraft [e.g. 1.14.4]
- Fabric [e.g. fabric 0.7.2+build.174]
- Mods [e.g. aurora_keystrokes v1.0.0, modmenu v1.7.15]
- OS: [e.g. Windows / Linux / MacOS]
- Minecraft [e.g. 1.20.4]
- Modloader [e.g. Fabric Loader 0.15.6]
- Fabric/Quilt Libraries [e.g. Fabric Api 0.96.1+1.20.4]
- Mods [e.g. Puzzle v1.6.1, LilTaterReloaded v1.1.15]
- Version [e.g. 1.0.0]
- Branch [e.g. dev]
- Remove this line if you actually completed it
**Additional context**
Add any other context about the problem here.
In case of a crash, please provide the crash log.

View File

@@ -0,0 +1,40 @@
---
name: Controller support
about: Report a problem related to a specific controller
title: 'Controller Issues: CONTROLLER NAME HERE'
labels: controller
assignees: ''
---
**Describe the current state**
A clear and concise description of current state of support for the controller and the issues.
**To Reproduce**
If needed, specify steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots or videos to help explain your problem.
**Desktop (please complete the following information):**
- Have you checked #263 for your controller? [e.g. Yes/No]
- Connection method [e.g. Wired / Bluetooth]
- OS: [e.g. Windows / Linux / MacOS]
- Minecraft [e.g. 1.20.4]
- Modloader [e.g. Fabric Loader 0.15.6]
- Fabric/Quilt Libraries [e.g. Fabric Api 0.96.1+1.20.4]
- Mods [e.g. Puzzle v1.6.1, LilTaterReloaded v1.1.15]
- Version [e.g. 1.0.0]
- Remove this line if you actually completed it
**Additional context**
Add any other context about the problem here.
In case of a crash, please provide the crash log.

View File

@@ -1,7 +1,7 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
title: 'Feature: TITLE HERE'
labels: enhancement
assignees: ''
@@ -17,4 +17,4 @@ A clear and concise description of what you want to happen.
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.
Add any other context or screenshots/videos about the feature request here.

20
.github/ISSUE_TEMPLATE/mod_support.md vendored Normal file
View File

@@ -0,0 +1,20 @@
---
name: Mod Support
about: Compatibility improvements with a specific mod (not for crashes)
title: 'Mod Support: MOD NAME HERE'
labels: mod compatibility
assignees: ''
---
**Is your compatibility request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I've always wanted to [...]
**Describe the way of compatibility you'd imagine**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative mods or workarounds you've considered.
**Additional context**
Add any other context or screenshots/videos about the compatibility request here.

29
.github/workflows/build.yml vendored Normal file
View File

@@ -0,0 +1,29 @@
name: Build with Gradle
on: [ push, pull_request ]
jobs:
build:
runs-on: ubuntu-24.04
steps:
- name: Checkout Repository
uses: actions/checkout@v4
with:
persist-credentials: false
- name: Set up Gradle
uses: gradle/actions/setup-gradle@v4
- name: Set up JDK 21
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: 21
check-latest: true
- name: Build with Gradle
run: ./gradlew build
- name: Upload artifacts to GitHub
uses: actions/upload-artifact@v4
with:
name: MidnightControls-Artifacts
path: |
fabric/build/libs/
neoforge/build/libs/

View File

@@ -1,23 +0,0 @@
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/

View File

@@ -1,23 +0,0 @@
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

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

@@ -1,30 +1,26 @@
name: Gradle Package
name: Publish to Modrinth
on:
push:
tags:
- '*'
release:
types:
- published
jobs:
build:
runs-on: ubuntu-latest
publish:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v2
- name: Set up JDK 16
uses: actions/setup-java@v1
- name: Checkout Repository
uses: actions/checkout@v4
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
persist-credentials: false
- name: Set up Gradle
uses: gradle/actions/setup-gradle@v4
- name: Set up JDK 21
uses: actions/setup-java@v4
with:
name: Artifacts
path: ./build/libs/
distribution: 'temurin'
java-version: 21
check-latest: true
# 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
@@ -38,3 +34,7 @@ jobs:
midnightcontrols_MAVEN: ${{ secrets.MAVEN_URL }}
MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }}
MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }}
- name: Publish to Modrinth
env:
MODRINTH_TOKEN: ${{ secrets.MODRINTH_TOKEN }}
run: ./gradlew publishModrinth

View File

@@ -1,29 +1,29 @@
<div align="center">
<img src=https://github.com/TeamMidnightDust/MidnightControls/blob/1.19/src/main/resources/assets/midnightcontrols/icon.png>
# MidnightControls
<!-- modrinth_exclude.start -->
![Java 16](https://img.shields.io/badge/language-Java%2017-9B599A.svg?style=flat-square) <!-- modrinth_exclude.end -->
[![GitHub license](https://img.shields.io/github/license/TeamMidnightDust/MidnightControls?style=flat-square)](https://raw.githubusercontent.com/TeamMidnightDust/MidnightControls/master/LICENSE)
![Environment: Client](https://img.shields.io/badge/environment-client-1976d2?style=flat-square)
[![Mod loader: Quilt/Fabric]][quilt] <!-- modrinth_exclude.start -->
[![Version](https://img.shields.io/github/v/tag/TeamMidnightDust/MidnightControls?label=version&style=flat-square)](https://github.com/TeamMidnightDust/MidnightControls/releases)
[![CurseForge](http://cf.way2muchnoise.eu/title/621768.svg)](https://www.curseforge.com/minecraft/mc-mods/midnightcontrols)
<!-- modrinth_exclude.end -->
[![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 21](https://img.shields.io/badge/language-Java%2021-9B599A.svg?style=round)](https://www.oracle.com/java/technologies/downloads/#java21)
[![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?
This mod adds better controls, reach-around features, etc.
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!
Haven't you dreamed to travel in your modded Minecraft world with your controller? Yes? Then this mod is made for you!
This mod also adds controller support.
## ✅ Features:
## Features:
- Controller support
- Touchscreen support (very experimental and buggy).
- Touchscreen support
- Keyboard controls to look around.
- Toggleable on screen button indicator (like in Bedrock Edition).
- Vertical reach-around.
@@ -47,14 +47,27 @@ This mod also adds controller support.
## Screenshots
![controller_controls](images/controller_controls.png)
![controller_options](images/controller_options.png)
![Controller_Controls](images/controller_controls.png)
![Controller_Options](images/controller_options.png)
<!-- modrinth_exclude.start -->
## Build
Just do `./gradlew build` and everything should build just fine!
<!-- modrinth_exclude.end -->
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!
## FAQ:
### The controller does not work, and it's name appears in orange, what can I do?
Create a custom mapping as pointed out in the [wiki](https://midnightdust.eu/wiki/midnightcontrols/)
[Quilt]: https://quiltmc.org
[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,259 +1,92 @@
plugins {
id 'fabric-loom' version '0.11-SNAPSHOT'
id 'java-library'
id 'maven-publish'
id 'com.github.johnrengelman.shadow' version '7.0.0'
id 'com.modrinth.minotaur' version '1.2.+'
id "architectury-plugin" version "3.4-SNAPSHOT"
id "dev.architectury.loom" version "1.13-SNAPSHOT" apply false
id "me.shedaniel.unified-publishing" version "0.1.+" apply false
id 'com.github.johnrengelman.shadow' version '8.1.1' apply false
}
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
group = project.maven_group
version = "${project.mod_version}+${getMCVersionString()}"
archivesBaseName = project.archives_base_name
// 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+)')
architectury {
minecraft = rootProject.minecraft_version
}
String getMCVersionString() {
if (isMCVersionNonRelease()) {
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()
mavenCentral()
maven { url 'https://aperlambda.github.io/maven' }
maven {
name 'Gegy'
url 'https://maven.gegy.dev'
url = "https://api.modrinth.com/maven"
}
maven {
name 'CottonMC'
url 'https://server.bbkr.space/artifactory/libs-snapshot'
}
maven { url "https://maven.terraformersmc.com/releases/" }
maven { url 'https://maven.kosmx.dev' }
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}:v2"
modImplementation "net.fabricmc:fabric-loader:${project.loader_version}"
// Fabric API. This is technically optional, but you probably want it anyway.
modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}"
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'
modImplementation "com.terraformersmc:modmenu:${project.modmenu_version}"
// 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}"
modImplementation "org.quiltmc:quilt-json5:1.0.0"
modCompileOnly "maven.modrinth:sodium:${project.sodium_version}"
modImplementation("maven.modrinth:emi:${project.emi_version}")
modImplementation "maven.modrinth:emotecraft:${project.emotecraft_version}"
modImplementation "io.github.kosmx:bendy-lib:${project.bendylib_version}"
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'
}
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
filesMatching('fabric.mod.json') {
expand 'version': project.version
flatDir {
dirs("localMaven")
}
}
task publishModrinth(type: TaskModrinthUpload) {
dependsOn(build)
onlyIf {
System.getenv('MODRINTH_TOKEN')
}
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
// 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
subprojects {
apply plugin: "dev.architectury.loom"
repositories {
maven {
url = "https://api.modrinth.com/maven"
}
maven {
name 'Gegy'
url 'https://maven.gegy.dev'
}
maven { url 'https://jitpack.io' }
flatDir {
dirs("../localMaven")
}
}
// 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()}")
dependencies {
minecraft "com.mojang:minecraft:${rootProject.minecraft_version}"
// The following line declares the mojmap mappings, you may use other mappings as well
//mappings loom.officialMojangMappings()
// The following line declares the yarn mappings you may select this one as well.
mappings loom.layered {
it.mappings("net.fabricmc:yarn:$rootProject.yarn_mappings:v2")
it.mappings("dev.architectury:yarn-mappings-patch-neoforge:$rootProject.yarn_mappings_patch_neoforge_version")
}
}
}
// configure the maven publication
publishing {
publications {
mavenJava(MavenPublication) {
artifact(sourcesJar) {
builtBy remapSourcesJar
}
pom {
name = 'midnightcontrols'
description = 'Adds better controls, and controller support.'
}
allprojects {
apply plugin: "java"
apply plugin: "architectury-plugin"
apply plugin: "maven-publish"
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')
}
}
}
}
archivesBaseName = rootProject.archives_base_name
version = rootProject.mod_version
group = rootProject.maven_group
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')
}
}
maven { url 'https://aperlambda.github.io/maven' }
}
dependencies {
implementation('org.aperlambda:lambdajcommon:1.8.1') {
exclude group: 'com.google.code.gson'
exclude group: 'com.google.guava'
}
}
tasks.withType(JavaCompile) {
options.encoding = "UTF-8"
options.release = 21
}
ext {
releaseChangelog = {
def changes = new StringBuilder()
changes << "## MidnightControls v$project.version for $project.minecraft_version\n[View the changelog](https://www.github.com/TeamMidnightDust/MidnightControls/commits/)"
def proc = "git log --max-count=1 --pretty=format:%s".execute()
proc.in.eachLine { line ->
def processedLine = line.toString()
if (!processedLine.contains("New translations") && !processedLine.contains("Merge") && !processedLine.contains("branch")) {
changes << "\n- ${processedLine.capitalize()}"
}
}
proc.waitFor()
return changes.toString()
}
}
java {
withSourcesJar()
}
}

65
common/build.gradle Normal file
View File

@@ -0,0 +1,65 @@
architectury {
common(rootProject.enabled_platforms.split(","))
}
loom {
accessWidenerPath = file("src/main/resources/midnightcontrols.accesswidener")
}
repositories {
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'}
}
dependencies {
// We depend on fabric loader here to use the fabric @Environment annotations and get the mixin dependencies
// Do NOT use other classes from fabric loader
modImplementation "net.fabricmc:fabric-loader:${rootProject.fabric_loader_version}"
// Using the Fabric version of midnightlib here to create a common config and get useful utilities
// Just make sure NOT to use classes from the .fabric classpath
modCompileOnlyApi "maven.modrinth:midnightlib:${rootProject.midnightlib_version}-fabric"
modCompileOnlyApi "dev.lambdaurora:spruceui:${project.spruceui_version}"
modCompileOnlyApi ("com.terraformersmc:modmenu:${project.modmenu_version}") {
exclude(group: "net.fabricmc.fabric-api")
}
// Compatibility mods
modCompileOnlyApi "io.github.cottonmc:LibGui:${project.libgui_version}"
modCompileOnlyApi "org.quiltmc:quilt-json5:1.0.0"
modImplementation "maven.modrinth:sodium:${project.sodium_version}-fabric"
modCompileOnlyApi "maven.modrinth:emi:${project.emi_version}"
modImplementation "maven.modrinth:emotecraft:${project.emotecraft_version}"
modCompileOnlyApi "io.github.kosmx:bendy-lib:${project.bendylib_version}"
modCompileOnlyApi "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
modCompileOnlyApi("me.shedaniel.cloth:cloth-config-fabric:${project.clothconfig_version}") {
exclude(group: "net.fabricmc.fabric-api")
}
}
publishing {
publications {
mavenCommon(MavenPublication) {
artifactId = rootProject.archives_base_name
from components.java
}
}
// See https://docs.gradle.org/current/userguide/publishing_maven.html for information on how to set up publishing.
repositories {
// Add repositories to publish to here.
}
}

View File

@@ -9,12 +9,17 @@
package eu.midnightdust.midnightcontrols;
import dev.lambdaurora.spruceui.util.Nameable;
import net.minecraft.text.Text;
import net.minecraft.text.object.AtlasTextObjectContents;
import net.minecraft.util.Atlases;
import net.minecraft.util.StringIdentifiable;
import org.jetbrains.annotations.NotNull;
import java.util.Arrays;
import java.util.Optional;
import static eu.midnightdust.midnightcontrols.MidnightControls.id;
/**
* Represents the controls mode.
*
@@ -22,10 +27,15 @@ import java.util.Optional;
* @version 1.7.0
* @since 1.0.0
*/
public enum ControlsMode implements Nameable {
DEFAULT,
CONTROLLER,
TOUCHSCREEN;
public enum ControlsMode implements StringIdentifiable {
DEFAULT("icon/keyboard_mouse"),
CONTROLLER("icon/controller"),
TOUCHSCREEN("icon/touchscreen");
final String emoji;
ControlsMode(String emoji) {
this.emoji = emoji;
}
/**
* Returns the next controls mode available.
@@ -39,6 +49,11 @@ public enum ControlsMode implements Nameable {
return v[this.ordinal() + 1];
}
// @Override
// public Text getText() {
// return Text.object(new AtlasTextObjectContents(Atlases.GUI, id(emoji))).append(" ").append(Text.translatable(getTranslationKey()));
// }
/**
* Gets the translation key of this controls mode.
*
@@ -49,7 +64,7 @@ public enum ControlsMode implements Nameable {
return "midnightcontrols.controls_mode." + this.getName();
}
@Override
public @NotNull String getName() {
return this.name().toLowerCase();
}
@@ -63,4 +78,9 @@ public enum ControlsMode implements Nameable {
public static Optional<ControlsMode> byId(@NotNull String id) {
return Arrays.stream(values()).filter(mode -> mode.getName().equalsIgnoreCase(id)).findFirst();
}
@Override
public String asString() {
return getTranslationKey();
}
}

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;
import eu.midnightdust.lib.util.PlatformFunctions;
import net.minecraft.util.Identifier;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
/**
* Represents the MidnightControls mod.
*
* @author LambdAurora & Motschen
* @version 1.8.0
* @since 1.0.0
*/
public class MidnightControls {
public static boolean isExtrasLoaded;
public static final Logger logger = LogManager.getLogger("MidnightControls");
public static void init() {
isExtrasLoaded = PlatformFunctions.isModLoaded("midnightcontrols-extra");
log("Initializing MidnightControls...");
}
public static Identifier id(String path) {
return Identifier.of(MidnightControlsConstants.NAMESPACE, path);
}
/**
* Prints a message to the terminal.
*
* @param info the message to print
*/
public static void log(String info) {
logger.info("[MidnightControls] {}", info);
}
/**
* Prints a warning to the terminal.
*
* @param warning the warning to print
*/
public static void warn(String warning) {
logger.warn("[MidnightControls] {}", warning);
}
}

View File

@@ -12,6 +12,8 @@ package eu.midnightdust.midnightcontrols;
import net.minecraft.util.Identifier;
import static eu.midnightdust.midnightcontrols.MidnightControls.id;
/**
* Represents the constants used by MidnightControls.
*
@@ -21,7 +23,7 @@ import net.minecraft.util.Identifier;
*/
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");
public static final Identifier CONTROLS_MODE_CHANNEL = id("controls_mode");
public static final Identifier FEATURE_CHANNEL = id("feature");
public static final Identifier HELLO_CHANNEL = id("hello");
}

View File

@@ -9,8 +9,8 @@
package eu.midnightdust.midnightcontrols;
import dev.lambdaurora.spruceui.util.Nameable;
import eu.midnightdust.midnightcontrols.client.MidnightControlsConfig;
import org.aperlambda.lambdacommon.utils.Nameable;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
@@ -44,6 +44,7 @@ public class MidnightControlsFeature implements Nameable {
this.setEnabled(this.defaultEnabled = enabled);
}
@Deprecated
public MidnightControlsFeature(@NotNull String key) {
this(key, false, false);
}
@@ -51,6 +52,7 @@ public class MidnightControlsFeature implements Nameable {
/**
* Allows the feature.
*/
@Deprecated
public void allow() {
this.setAllowed(true);
}

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;
import eu.midnightdust.lib.util.PlatformFunctions;
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.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.touch.gui.TouchscreenOverlay;
import eu.midnightdust.midnightcontrols.client.mixin.KeyBindingIDAccessor;
import eu.midnightdust.midnightcontrols.client.ring.ButtonBindingRingAction;
import eu.midnightdust.midnightcontrols.client.ring.MidnightRing;
import eu.midnightdust.midnightcontrols.client.util.platform.NetworkUtil;
import eu.midnightdust.midnightcontrols.client.virtualkeyboard.MouseClickInterceptor;
import net.minecraft.client.gui.screen.Screen;
import eu.midnightdust.midnightcontrols.client.touch.TouchInput;
import eu.midnightdust.midnightcontrols.client.util.RainbowColor;
import eu.midnightdust.midnightcontrols.packet.ControlsModePayload;
import net.minecraft.client.MinecraftClient;
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.Objects;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.atomic.AtomicReference;
/**
* Represents the midnightcontrols client mod.
*
* @author Motschen, LambdAurora
* @version 1.10.0
* @since 1.1.0
*/
public class MidnightControlsClient extends MidnightControls {
public static boolean lateInitDone = false;
public static final KeyBinding.Category MIDNIGHTCONTROLS_CATEGORY = KeyBinding.Category.create(Identifier.of("midnightcontrols", "keybinds"));
public static final KeyBinding BINDING_LOOK_UP = InputManager.makeKeyBinding(id("look_up"),
InputUtil.Type.KEYSYM, GLFW.GLFW_KEY_KP_8, MIDNIGHTCONTROLS_CATEGORY);
public static final KeyBinding BINDING_LOOK_RIGHT = InputManager.makeKeyBinding(id("look_right"),
InputUtil.Type.KEYSYM, GLFW.GLFW_KEY_KP_6, MIDNIGHTCONTROLS_CATEGORY);
public static final KeyBinding BINDING_LOOK_DOWN = InputManager.makeKeyBinding(id("look_down"),
InputUtil.Type.KEYSYM, GLFW.GLFW_KEY_KP_2, MIDNIGHTCONTROLS_CATEGORY);
public static final KeyBinding BINDING_LOOK_LEFT = InputManager.makeKeyBinding(id("look_left"),
InputUtil.Type.KEYSYM, GLFW.GLFW_KEY_KP_4, MIDNIGHTCONTROLS_CATEGORY);
public static final KeyBinding BINDING_RING = InputManager.makeKeyBinding(id("ring"),
InputUtil.Type.KEYSYM, InputUtil.UNKNOWN_KEY.getCode(), MIDNIGHTCONTROLS_CATEGORY);
public static final Identifier CONTROLLER_BUTTONS = id("textures/gui/controller_buttons.png");
public static final Identifier CONTROLLER_EXPANDED = id("textures/gui/controller_expanded.png");
public static final Identifier CONTROLLER_AXIS = id("textures/gui/controller_axis.png");
public static final File MAPPINGS_FILE = new File("config/gamecontrollercustommappings.txt");
public static MinecraftClient client = MinecraftClient.getInstance();
public static final MidnightInput input = new MidnightInput();
public static final MidnightRing ring = new MidnightRing();
public static final MidnightReacharound reacharound = new MidnightReacharound();
public static final MouseClickInterceptor clickInterceptor = new MouseClickInterceptor();
public static boolean isWayland;
private static MidnightControlsHud hud;
private static ControlsMode previousControlsMode;
public static void initClient() {
client = MinecraftClient.getInstance();
ring.registerAction("buttonbinding", ButtonBindingRingAction.FACTORY);
int delay = 0; // delay for 0 sec.
int period = 1; // repeat every 0.001 sec. (1000 times a second)
Timer timer = new Timer();
timer.scheduleAtFixedRate(new TimerTask() {
public void run() {
try {
if (lateInitDone && client.isRunning()) {
if (MidnightControlsConfig.controlsMode != ControlsMode.DEFAULT && (client.isWindowFocused() || MidnightControlsConfig.unfocusedInput)) {
if (MidnightControlsConfig.controlsMode == ControlsMode.CONTROLLER) input.tickCameraStick();
input.updateCamera();
}
}
} catch (Exception | Error e) {
MidnightControls.logger.error("Exception encountered in camera loop: ",e);
}
}
}, delay, period);
isWayland = GLFW.glfwGetVersionString().contains("Wayland");
}
/**
* This method is called when Minecraft is initializing.
*/
public static void onMcInit(@NotNull MinecraftClient client) {
ButtonBinding.init(client.options);
MidnightControlsConfig.load();
if (MidnightControlsConfig.configVersion < 2) {
MidnightControlsConfig.mouseScreens.remove("me.jellysquid.mods.sodium.client.gui");
MidnightControlsConfig.mouseScreens.remove("net.coderbot.iris.gui");
MidnightControlsConfig.mouseScreens.remove("net.minecraft.class_5375");
MidnightControlsConfig.mouseScreens.remove("net.minecraft.client.gui.screen.pack.PackScreen");
MidnightControlsConfig.configVersion = 2;
MidnightControlsConfig.write(MidnightControlsConstants.NAMESPACE);
}
MidnightControlsHud.isVisible = MidnightControlsConfig.hudEnable;
Controller.updateMappings();
try {
GLFW.glfwSetJoystickCallback((jid, event) -> {
if (event == GLFW.GLFW_CONNECTED) {
var controller = Controller.byId(jid);
client.getToastManager().add(new SystemToast(SystemToast.Type.PERIODIC_NOTIFICATION, Text.translatable("midnightcontrols.controller.connected", jid),
Text.literal(controller.getName())));
} else if (event == GLFW.GLFW_DISCONNECTED) {
client.getToastManager().add(new SystemToast(SystemToast.Type.PERIODIC_NOTIFICATION, Text.translatable("midnightcontrols.controller.disconnected", jid),
null));
}
switchControlsMode();
});
} catch (Exception e) {e.fillInStackTrace();}
MidnightControlsCompat.init();
}
/**
* This method is called to initialize keybindings
*/
public static void initKeybindings() {
if (lateInitDone) return;
if (KeyBindingIDAccessor.getKEYS_BY_ID() == null || KeyBindingIDAccessor.getKEYS_BY_ID().isEmpty()) return;
if (PlatformFunctions.isModLoaded("voxelmap") && !KeyBindingIDAccessor.getKEYS_BY_ID().containsKey("key.minimap.toggleingamewaypoints")) return;
if (PlatformFunctions.isModLoaded("wynntils") && KeyBindingIDAccessor.getKEYS_BY_ID().entrySet().stream().noneMatch(b -> Objects.equals(b.getValue().getCategory(), "Wynntils"))) return;
for (int i = 0; i < KeyBindingIDAccessor.getKEYS_BY_ID().size(); ++i) {
KeyBinding keyBinding = KeyBindingIDAccessor.getKEYS_BY_ID().entrySet().stream().toList().get(i).getValue();
if (MidnightControlsConfig.excludedKeybindings.stream().noneMatch(excluded -> keyBinding.getId().startsWith(excluded))) {
if (!keyBinding.getId().contains(MidnightControlsConstants.NAMESPACE)) {
AtomicReference<ButtonCategory> category = new AtomicReference<>();
InputManager.streamCategories().forEach(buttonCategory -> {
if (buttonCategory.getIdentifier().equals(keyBinding.getCategory().id()))
category.set(buttonCategory);
});
if (category.get() == null) {
category.set(new ButtonCategory(keyBinding.getCategory().id()));
InputManager.registerCategory(category.get());
}
ButtonBinding buttonBinding = new ButtonBinding.Builder(keyBinding.getId()).category(category.get()).linkKeybind(keyBinding).register();
if (MidnightControlsConfig.debug) {
MidnightControls.log(keyBinding.getId());
MidnightControls.log(String.valueOf(buttonBinding));
}
}
}
}
InputManager.loadButtonBindings();
lateInitDone = true;
}
/**
* This method is called every Minecraft tick.
*
* @param client the client instance
*/
public static void onTick(@NotNull MinecraftClient client) {
initKeybindings();
input.tick();
reacharound.tick();
if (MidnightControlsConfig.controlsMode == ControlsMode.CONTROLLER && (client.isWindowFocused() || MidnightControlsConfig.unfocusedInput))
input.tickController();
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();
}
RainbowColor.tick();
TouchInput.tick();
}
/**
* Called when opening a screen.
*/
public static void onScreenOpen(Screen screen) {
client = MinecraftClient.getInstance();
if (screen == null && MidnightControlsConfig.controlsMode == ControlsMode.TOUCHSCREEN) {
screen = new TouchscreenOverlay();
screen.init(client.getWindow().getScaledWidth(), client.getWindow().getScaledHeight());
client.skipGameRender = false;
client.currentScreen = screen;
} else if (screen != null) {
MidnightControlsClient.input.onScreenOpen(client.getWindow().getWidth(), client.getWindow().getHeight());
}
}
/**
* Called when leaving a server.
*/
public static void onLeave() {
MidnightControlsFeature.resetAllAllowed();
}
/**
* Switches the controls mode if the auto switch is enabled.
*/
public static void switchControlsMode() {
if (MidnightControlsConfig.autoSwitchMode) {
if (MidnightControlsConfig.getController().isGamepad()) {
previousControlsMode = MidnightControlsConfig.controlsMode;
MidnightControlsConfig.controlsMode = ControlsMode.CONTROLLER;
} else {
if (previousControlsMode == null) {
previousControlsMode = ControlsMode.DEFAULT;
}
MidnightControlsConfig.controlsMode = previousControlsMode;
}
NetworkUtil.sendPayloadC2S(new ControlsModePayload(MidnightControlsConfig.controlsMode.getName()));
}
}
/**
* Sets whether the HUD is enabled or not.
*
* @param enabled true if the HUD is enabled, else false
*/
public static void setHudEnabled(boolean enabled) {
MidnightControlsConfig.hudEnable = enabled;
MidnightControlsHud.isVisible = enabled;
}
private static final MidnightControlsClient INSTANCE = new MidnightControlsClient();
/**
* Gets the midnightcontrols client instance.
*
* @return the midnightcontrols client instance
*/
@Deprecated
public static MidnightControlsClient get() {
return INSTANCE;
}
}

View File

@@ -0,0 +1,548 @@
/*
* 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 com.mojang.blaze3d.platform.GLX;
import eu.midnightdust.lib.config.EntryInfo;
import eu.midnightdust.lib.config.MidnightConfig;
import eu.midnightdust.lib.config.MidnightConfigListWidget;
import eu.midnightdust.lib.config.MidnightConfigScreen;
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.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.enums.ButtonState;
import eu.midnightdust.midnightcontrols.client.enums.CameraMode;
import eu.midnightdust.midnightcontrols.client.enums.ControllerType;
import eu.midnightdust.midnightcontrols.client.enums.HudSide;
import eu.midnightdust.midnightcontrols.client.enums.VirtualMouseSkin;
import eu.midnightdust.midnightcontrols.client.gui.MidnightControlsSettingsScreen;
import eu.midnightdust.midnightcontrols.client.gui.RingScreen;
import eu.midnightdust.midnightcontrols.client.enums.TouchMode;
import eu.midnightdust.midnightcontrols.client.gui.config.ControllerBindingButton;
import eu.midnightdust.midnightcontrols.client.gui.config.ControllerSelectionButton;
import eu.midnightdust.midnightcontrols.client.gui.config.MappingsStringInputWidget;
import eu.midnightdust.midnightcontrols.client.virtualkeyboard.KeyboardLayoutManager;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gui.screen.ChatScreen;
import net.minecraft.client.gui.screen.advancement.AdvancementsScreen;
import net.minecraft.client.gui.widget.ButtonWidget;
import net.minecraft.client.gui.widget.TextIconButtonWidget;
import net.minecraft.text.Text;
import net.minecraft.util.Identifier;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.lwjgl.glfw.GLFW;
import java.lang.annotation.Annotation;
import java.util.*;
import java.util.regex.Pattern;
import static eu.midnightdust.midnightcontrols.client.MidnightControlsClient.client;
import static org.lwjgl.glfw.GLFW.*;
/**
* Represents MidnightControls configuration.
*/
public class MidnightControlsConfig extends MidnightConfig {
public static final String CONTROLLER = "controller";
public static final String TOUCH = "touch";
public static final String GAMEPLAY = "gameplay";
public static final String SCREENS = "screens";
public static final String VISUAL = "visual";
public static final String MISC = "misc";
public static final String BUTTONS = "buttons";
//public static final String MAPPING = "mapping";
public static boolean isEditing = false;
@Hidden @Entry public static int configVersion = 2;
// Controller
@Entry(category = CONTROLLER, name = "Controller ID") @Hidden public static Object controllerID = 0;
@Entry(category = CONTROLLER, name = "2nd Controller ID") @Hidden public static Object secondControllerID = -1;
@Comment(category = CONTROLLER, centered = true, name="\uD83D\uDD90 Input Mode") public static Comment _input_mode;
@Entry(category = CONTROLLER, name = "midnightcontrols.menu.controls_mode") public static ControlsMode controlsMode = ControlsMode.DEFAULT;
@Entry(category = CONTROLLER, name = "midnightcontrols.menu.auto_switch_mode") public static boolean autoSwitchMode = true;
@Comment(category = CONTROLLER, centered = true, name="\uD83D\uDCF7 Camera Settings") public static Comment _cameraSettings;
@Entry(category = CONTROLLER, name = "midnightcontrols.menu.invert_right_y_axis") public static boolean invertRightYAxis = false;
@Entry(category = CONTROLLER, name = "midnightcontrols.menu.invert_right_x_axis") public static boolean invertRightXAxis = false;
@Entry(category = CONTROLLER, name = "midnightcontrols.menu.rotation_speed", isSlider = true, min = 0, max = 100, precision = 10) public static double rotationSpeed = 35.0; //used for x-axis, name kept for compatibility
@Entry(category = CONTROLLER, name = "midnightcontrols.menu.y_axis_rotation_speed", isSlider = true, min = 0, max = 100, precision = 10) public static double yAxisRotationSpeed = rotationSpeed;
@Entry(category = CONTROLLER, name = "midnightcontrols.menu.camera_mode") public static CameraMode cameraMode = CameraMode.FLAT;
@Comment(category = CONTROLLER, centered = true, name="\uD83D\uDC40 Eye Tracking") public static Comment _eyeTracker;
@Entry(category = CONTROLLER, name = "midnightcontrols.menu.eye_tracker_as_mouse") public static boolean eyeTrackerAsMouse = false;
@Entry(category = CONTROLLER, name = "midnightcontrols.menu.eye_tracker_deadzone", isSlider = true, min = 0, max = 0.4) public static double eyeTrackerDeadzone = 0.05;
@Entry(category = CONTROLLER, name = "midnightcontrols.menu.unfocused_input") public static boolean unfocusedInput = false;
@Comment(category = CONTROLLER, centered = true, name="\uD83D\uDD79 Max Analog Stick Values") public static Comment _maxAnalogValues;
@Entry(category = CONTROLLER, name = "Max analog value: Left X", isSlider = true, min = .25f, max = 1.f) public static double maxAnalogValueLeftX = 1;
@Entry(category = CONTROLLER, name = "Max analog value: Left Y", isSlider = true, min = .25f, max = 1.f) public static double maxAnalogValueLeftY = 1;
@Entry(category = CONTROLLER, name = "Max analog value: Right X", isSlider = true, min = .25f, max = 1.f) public static double maxAnalogValueRightX = 1;
@Entry(category = CONTROLLER, name = "Max analog value: Right Y", isSlider = true, min = .25f, max = 1.f) public static double maxAnalogValueRightY = 1;
@Comment(category = CONTROLLER, centered = true, name="☠ Dead Zones") public static Comment _deadZones;
@Entry(category = CONTROLLER, name = "midnightcontrols.menu.right_dead_zone", isSlider = true, min = 0.05, max = 1) public static double rightDeadZone = 0.15;
@Entry(category = CONTROLLER, name = "midnightcontrols.menu.left_dead_zone", isSlider = true, min = 0.05, max = 1) public static double leftDeadZone = 0.15;
@Entry(category = CONTROLLER, name = "Trigger Dead-Zone", isSlider = true, min = 0.05, max = 1) public static double triggerDeadZone = 0.1;
// Init button binding tab (see #onTabInit())
@Comment(category = BUTTONS) @Condition(requiredModId = "thisModDoesNotExist") public static Comment this_spacer_will_never_be_visible;
@Entry @Hidden public static Map<String, String> BINDING = new HashMap<>();
private static final Pattern BUTTON_BINDING_PATTERN = Pattern.compile("(-?\\d+)\\+?");
// Visual
@Comment(category = VISUAL, centered = true, name="\uD83D\uDDB9 Hud") public static Comment _hud;
@Entry(category = VISUAL, name = "midnightcontrols.menu.hud_enable") public static boolean hudEnable = true;
@Entry(category = VISUAL, name = "midnightcontrols.menu.hud_side") public static HudSide hudSide = HudSide.LEFT;
@Entry(category = VISUAL, name = "midnightcontrols.menu.controller_type") public static ControllerType controllerType = ControllerType.DEFAULT;
@Comment(category = VISUAL, centered = true, name="⊽ Reacharound") public static Comment _reacharoundOutline;
@Condition(requiredModId = "midnightcontrols-extra")
@Entry(category = VISUAL, name = "Reacharound Outline") public static boolean shouldRenderReacharoundOutline = true;
@Condition(requiredModId = "midnightcontrols-extra", requiredOption = "shouldRenderReacharoundOutline")
@Entry(category = VISUAL, name = "Reacharound Outline Color", isColor = true) public static String reacharoundOutlineColorHex = "#ffffff";
@Condition(requiredModId = "midnightcontrols-extra", requiredOption = "shouldRenderReacharoundOutline")
@Entry(category = VISUAL, name = "Reacharound Outline Alpha", isSlider = true, min = 0, max = 255) public static int reacharoundOutlineColorAlpha = 102;
// Gameplay
@Comment(category = GAMEPLAY, centered = true, name="\uD83D\uDECB Comfort") public static Comment _comfort;
@Entry(category = GAMEPLAY, name = "Enable Hints") public static boolean enableHints = true;
@Entry(category = GAMEPLAY, name = "midnightcontrols.menu.analog_movement") public static boolean analogMovement = true;
@Entry(category = GAMEPLAY, name = "midnightcontrols.menu.controller_toggle_sneak") public static boolean controllerToggleSneak = MinecraftClient.getInstance().options.getSneakToggled().getValue();
@Entry(category = GAMEPLAY, name = "midnightcontrols.menu.controller_toggle_sprint") public static boolean controllerToggleSprint = MinecraftClient.getInstance().options.getSprintToggled().getValue();
@Condition(requiredModId = "midnightcontrols-extra")
@Comment(category = GAMEPLAY, centered = true, name="✨ Extras") public static Comment _extras;
@Condition(requiredModId = "midnightcontrols-extra")
@Entry(category = GAMEPLAY, name = "midnightcontrols.menu.fast_block_placing") public static boolean fastBlockPlacing = false; // Disabled by default as this behaviour can be considered cheating on multiplayer servers.
@Condition(requiredModId = "midnightcontrols-extra")
@Entry(category = GAMEPLAY, 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.
@Condition(requiredModId = "midnightcontrols-extra")
@Entry(category = GAMEPLAY, 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.
@Condition(requiredModId = "midnightcontrols-extra")
@Entry(category = GAMEPLAY, name = "midnightcontrols.menu.reacharound.horizontal") public static boolean horizontalReacharound = false; // Disabled by default as this behaviour can be considered cheating on multiplayer servers.
@Condition(requiredModId = "midnightcontrols-extra")
@Entry(category = GAMEPLAY, name = "midnightcontrols.menu.reacharound.vertical") public static boolean verticalReacharound = false; // Disabled by default as this behaviour can be considered cheating on multiplayer servers.
@Condition(requiredModId = "midnightcontrols-extra")
// Screens
@Comment(category = SCREENS, centered = true, name="\uD83D\uDDB1 Mouse Behaviour") public static Comment _mouseBehaviour;
@Entry(category = SCREENS, name = "midnightcontrols.menu.mouse_speed", isSlider = true, min = 0, max = 150, precision = 10) public static double mouseSpeed = 25.0;
@Entry(category = SCREENS, name = "midnightcontrols.menu.joystick_as_mouse") public static boolean joystickAsMouse = false;
@Entry(category = SCREENS, name = "Mouse screens") public static List<String> mouseScreens = Lists.newArrayList("net.minecraft.client.gui.screen.advancement",
"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(category = SCREENS, name = "Arrow screens") public static List<String> arrowScreens = Lists.newArrayList(ChatScreen.class.getCanonicalName());
@Entry(category = SCREENS, name = "WASD screens") public static List<String> wasdScreens = Lists.newArrayList("com.ultreon.devices.core.Laptop");
@Comment(category = SCREENS, centered = true, name="\uD83D\uDC46 Virtual Mouse") public static Comment _virtualMouse;
@Entry(category = SCREENS, name = "midnightcontrols.menu.virtual_mouse") public static boolean virtualMouse = false;
@Condition(requiredOption = "virtualMouse", visibleButLocked = true)
@Entry(category = SCREENS, name = "midnightcontrols.menu.virtual_mouse.skin") public static VirtualMouseSkin virtualMouseSkin = VirtualMouseSkin.DEFAULT_LIGHT;
@Entry(category = SCREENS, name = "midnightcontrols.menu.hide_cursor") public static boolean hideNormalMouse = false;
@Entry(category = SCREENS, name = "midnightcontrols.menu.virtual_keyboard") public static boolean virtualKeyboard = false;
@Comment(category = SCREENS, centered = true, name="\uD83D\uDD27 UI Modifications") public static Comment _uiMods;
@Entry(category = SCREENS, name = "midnightcontrols.menu.move_chat") public static boolean moveChat = false;
@Entry(category = SCREENS, name = "Enable Shortcut in Controls Options") public static boolean shortcutInControls = true;
// Touch
@Comment(category = TOUCH, centered = true, name="\uD83E\uDE84 Behaviour") public static Comment _touchBehaviour;
@Entry(category = TOUCH, name = "midnightcontrols.menu.touch_with_controller") public static boolean touchInControllerMode = false;
@Entry(category = TOUCH, name = "midnightcontrols.menu.touch_speed", isSlider = true, min = 0, max = 150, precision = 10) public static double touchSpeed = 50.0;
@Entry(category = TOUCH, name = "midnightcontrols.menu.invert_touch") public static boolean invertTouch = false;
@Entry(category = TOUCH, name = "midnightcontrols.menu.touch_mode") public static TouchMode touchMode = TouchMode.CROSSHAIR;
@Entry(category = TOUCH, name = "midnightcontrols.menu.touch_break_delay", isSlider = true, min = 50, max = 500) public static int touchBreakDelay = 120;
@Comment(category = TOUCH, centered = true, name="\uD83D\uDCA1 Visuals") public static Comment _visuals;
@Entry(category = TOUCH, name = "midnightcontrols.menu.touch_transparency", isSlider = true, min = 0, max = 100) public static int touchTransparency = 75;
@Entry(category = TOUCH, name = "Touch Outline Color", isColor = true) public static String touchOutlineColorHex = "#ffffff";
@Entry(category = TOUCH, name = "Touch Outline Alpha", isSlider = true, min = 0, max = 255) public static int touchOutlineColorAlpha = 150;
@Comment(category = TOUCH, centered = true, name="\uD83E\uDDEA Advanced") public static Comment _advanced;
@Entry(category = TOUCH, name = "Screens with close button") public static List<String> closeButtonScreens = Lists.newArrayList(ChatScreen.class.getCanonicalName(), AdvancementsScreen.class.getCanonicalName(), RingScreen.class.getCanonicalName());
@Entry(category = TOUCH, name = "Left Touch button bindings") public static List<String> leftTouchBinds = Lists.newArrayList("debug_screen", "screenshot","toggle_perspective");
@Entry(category = TOUCH, name = "Right Touch button bindings") public static List<String> rightTouchBinds = Lists.newArrayList("screenshot","toggle_perspective", "use");
// Miscellaneous
@Entry(category = MISC) @Hidden public static String keyboardLayout = "en_US:qwerty";
@Entry(category = MISC, name = "Debug") public static boolean debug = false;
@Entry(category = MISC, name = "Excluded Keybindings") public static List<String> excludedKeybindings = Lists.newArrayList("key.forward", "key.left", "key.back", "key.right", "key.jump", "key.sneak", "key.sprint", "key.inventory",
"key.swapOffhand", "key.drop", "key.use", "key.attack", "key.chat", "key.playerlist", "key.screenshot", "key.togglePerspective", "key.smoothCamera", "key.fullscreen", "key.saveToolbarActivator", "key.loadToolbarActivator",
"key.pickItem", "key.hotbar.1", "key.hotbar.2", "key.hotbar.3", "key.hotbar.4", "key.hotbar.5", "key.hotbar.6", "key.hotbar.7", "key.hotbar.8", "key.hotbar.9");
@Entry(category = MISC, name = "Ring Bindings (WIP)") @Hidden public static List<String> ringBindings = new ArrayList<>();
@Entry(category = MISC, name = "Ignored Unbound Keys") public static List<String> ignoredUnboundKeys = Lists.newArrayList("inventorytabs.key.next_tab");
@Comment(category = MISC, centered = true, name="☆ Other Options") public static Comment _otherOptions;
@Entry(category = MISC, name = "Trigger button fix") public static boolean triggerFix = true;
@Entry(category = MISC, name = "Excluded Controllers (Name Regex)") public static List<String> excludedControllers = Lists.newArrayList(".*(Keyboard)$", ".*(Touchpad)$", ".*(Pen)$", ".*(Finger)$");
// Init mapping tab (see #onTabInit())
//@Comment(category = MAPPING) @Condition(requiredModId = "thisModDoesNotExist") public static Comment this_spacer_will_never_be_visible_as_well;
@Entry @Hidden public static Map<String, Map<String, String>> controllerBindingProfiles = new HashMap<>();
private static Map<String, String> currentBindingProfile = new HashMap<>();
private static Controller prevController;
public void onTabInit(String tabName, MidnightConfigListWidget list, MidnightConfigScreen screen) {
EntryInfo centeredComment = new EntryInfo(null, "midnightcontrols");
centeredComment.comment = new Comment() {
public Class<? extends Annotation> annotationType() {return null;}
public String category() {return "";}
public String name() {return "";}
public String url() {return "";}
public String requiredMod() {return "";}
@Override
public boolean centered() {
return true;
}
};
if (BUTTONS.equals(tabName)) {
InputManager.streamCategories()
.sorted(Comparator.comparingInt(ButtonCategory::getPriority))
.forEach(category -> {
list.addButton(Lists.newArrayList(), Text.literal(category.getTranslatedName()), centeredComment);
category.getBindings().forEach(binding -> {
ControllerBindingButton.add(binding, list, screen);
});
});
}
if (MISC.equals(tabName)) {
TextIconButtonWidget resetButton = TextIconButtonWidget.builder(Text.translatable("controls.reset"), (button -> {
MidnightControlsConfig.keyboardLayout = "en_US:qwerty";
screen.updateList();
}), true).texture(Identifier.of("midnightlib","icon/reset"), 12, 12).dimension(20, 20).build();
resetButton.setPosition(screen.width - 205 + 150 + 25, 0);
ButtonWidget editButton = ButtonWidget.builder(Text.translatable(KeyboardLayoutManager.getById(MidnightControlsConfig.keyboardLayout).getTranslationKey()),
button -> {
MidnightControlsConfig.keyboardLayout = KeyboardLayoutManager.getNext(KeyboardLayoutManager.getById(MidnightControlsConfig.keyboardLayout)).getId();
resetButton.active = !MidnightControlsConfig.keyboardLayout.equals("en_US:qwerty");
button.setMessage(Text.translatable(KeyboardLayoutManager.getById(MidnightControlsConfig.keyboardLayout).getTranslationKey()));
}).dimensions(screen.width - 185, 0, 150, 20).build();
resetButton.active = !MidnightControlsConfig.keyboardLayout.equals("en_US:qwerty");
list.addButton(List.of(editButton, resetButton), Text.translatable("midnightcontrols.menu.virtual_keyboard_layout"), new EntryInfo(null, screen.modid));
}
if (CONTROLLER.equals(tabName)) {
list.addButton(List.of(), Text.of("\uD83C\uDFAE General"), centeredComment);
ControllerSelectionButton.add(list, screen, false);
ControllerSelectionButton.add(list, screen, true);
ButtonWidget editButton = ButtonWidget.builder(Text.of("OPEN"),
button -> {
client.setScreen(new MidnightControlsSettingsScreen(client.currentScreen, false));
}).dimensions(screen.width - 185, 0, 175, 20).build();
list.addButton(List.of(editButton), Text.of("Legacy Config UI"), new EntryInfo(null, screen.modid));
}
// if (MAPPING.equals(tabName)) {
// MappingsStringInputWidget.add(centeredComment, list, screen);
// }
}
/**
* Loads the configuration
*/
public static void load() {
MidnightControlsConfig.init(MidnightControlsConstants.NAMESPACE, MidnightControlsConfig.class);
MidnightControls.log("Configuration loaded.");
// Controller controls.
InputManager.loadButtonBindings();
}
/**
* Saves the configuration.
*/
public static void save() {
MidnightControlsConfig.write(MidnightControlsConstants.NAMESPACE);
MidnightControls.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(MidnightControlsConstants.NAMESPACE);
}
/**
* 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) {
return switch (axis) {
case GLFW_GAMEPAD_AXIS_LEFT_X -> MidnightControlsConfig.maxAnalogValueLeftX;
case GLFW_GAMEPAD_AXIS_LEFT_Y -> MidnightControlsConfig.maxAnalogValueLeftY;
case GLFW_GAMEPAD_AXIS_RIGHT_X -> MidnightControlsConfig.maxAnalogValueRightX;
default -> MidnightControlsConfig.maxAnalogValueRightY;
};
}
public static void setAxisMaxValue(int axis, double value) {
switch (axis) {
case GLFW_GAMEPAD_AXIS_LEFT_X -> MidnightControlsConfig.maxAnalogValueLeftX = value;
case GLFW_GAMEPAD_AXIS_LEFT_Y -> MidnightControlsConfig.maxAnalogValueLeftY = value;
case GLFW_GAMEPAD_AXIS_RIGHT_X -> MidnightControlsConfig.maxAnalogValueRightX = value;
default -> MidnightControlsConfig.maxAnalogValueRightY = 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) {
MidnightControls.warn("Malformed config value \"" + code + "\" for binding \"" + button.getName() + "\".");
MidnightControlsConfig.setButtonBinding(button, new int[]{-1});
}
button.setButton(buttons);
} catch (Exception e) {
MidnightControls.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) {
MidnightControls.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, ButtonState state) {
if (!isBtn && state == ButtonState.NONE)
return false;
return ButtonBinding.axisAsButton(GLFW_GAMEPAD_AXIS_LEFT_Y, false) == ButtonBinding.axisAsButton(btn, state == ButtonState.PRESS);
}
public static boolean isForwardButton(int btn, boolean isBtn, ButtonState state) {
if (!isBtn && state == ButtonState.NONE)
return false;
return ButtonBinding.axisAsButton(GLFW_GAMEPAD_AXIS_LEFT_Y, true) == ButtonBinding.axisAsButton(btn, state == ButtonState.PRESS);
}
public static boolean isLeftButton(int btn, boolean isBtn, ButtonState state) {
if (!isBtn && state == ButtonState.NONE)
return false;
return ButtonBinding.axisAsButton(GLFW_GAMEPAD_AXIS_LEFT_X, false) == ButtonBinding.axisAsButton(btn, state == ButtonState.PRESS);
}
public static boolean isRightButton(int btn, boolean isBtn, ButtonState state) {
if (!isBtn && state == ButtonState.NONE)
return false;
return ButtonBinding.axisAsButton(GLFW_GAMEPAD_AXIS_LEFT_X, true) == ButtonBinding.axisAsButton(btn, state == ButtonState.PRESS);
}
/**
* 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;
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 = 35.0;
yAxisRotationSpeed = rotationSpeed;
mouseSpeed = 25.0;
unfocusedInput = false;
virtualMouse = false;
virtualMouseSkin = VirtualMouseSkin.DEFAULT_LIGHT;
virtualKeyboard = false;
controllerID = 0;
secondControllerID = -1;
controllerType = ControllerType.DEFAULT;
mouseScreens = Lists.newArrayList("net.minecraft.client.gui.screen.advancement", "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<>();
maxAnalogValueLeftX = 1;
maxAnalogValueLeftY = 1;
maxAnalogValueRightX = 1;
maxAnalogValueRightY = 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 name = getController().getName().toLowerCase();
if (containsAny(name, "xbox 360")) return ControllerType.XBOX_360;
else if (containsAny(name, "xbox") || name.contains("afterglow")) return ControllerType.XBOX;
else if (containsAny(name, "steam") && GLX._getCpuInfo().contains("AMD Custom APU")) return ControllerType.STEAM_DECK;
else if (containsAny(name, "steam")) return ControllerType.STEAM_CONTROLLER;
else if (containsAny(name, "dualsense", "ps5")) return ControllerType.DUALSENSE;
else if (containsAny(name, "dualshock", "ps4", "sony")) return ControllerType.DUALSHOCK;
else if (containsAny(name, "switch", "joy-con", "wii", "nintendo")) return ControllerType.SWITCH;
else if (containsAny(name, "ouya")) return ControllerType.OUYA;
else return ControllerType.DEFAULT;
}
private static boolean containsAny(String controller, String... substring) {
for (String s : substring) if (controller.contains(s)) return true;
return false;
}
public static boolean doMixedInput() {
return touchInControllerMode && controlsMode == ControlsMode.CONTROLLER;
}
}

View File

@@ -0,0 +1,16 @@
package eu.midnightdust.midnightcontrols.client;
import eu.midnightdust.midnightcontrols.client.virtualkeyboard.KeyboardLayoutManager;
import net.minecraft.resource.ResourceManager;
import net.minecraft.resource.SynchronousResourceReloader;
public class MidnightControlsReloadListener implements SynchronousResourceReloader {
public static final MidnightControlsReloadListener INSTANCE = new MidnightControlsReloadListener();
private MidnightControlsReloadListener() {}
@Override
public void reload(ResourceManager manager) {
manager.findResources("keyboard_layouts", path -> path.toString().startsWith("midnightcontrols") && path.toString().endsWith(".json")).forEach(KeyboardLayoutManager::loadLayout);
}
}

View File

@@ -0,0 +1,824 @@
/*
* 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.navigation.NavigationEvent;
import dev.lambdaurora.spruceui.screen.SpruceScreen;
import dev.lambdaurora.spruceui.widget.AbstractSprucePressableButtonWidget;
import dev.lambdaurora.spruceui.widget.AbstractSpruceWidget;
import dev.lambdaurora.spruceui.widget.SpruceElement;
import dev.lambdaurora.spruceui.widget.SpruceLabelWidget;
import dev.lambdaurora.spruceui.widget.container.SpruceEntryListWidget;
import dev.lambdaurora.spruceui.widget.container.SpruceParentWidget;
import eu.midnightdust.lib.util.PlatformFunctions;
import eu.midnightdust.midnightcontrols.client.compat.EmotecraftCompat;
import eu.midnightdust.midnightcontrols.client.compat.LibGuiCompat;
import eu.midnightdust.midnightcontrols.client.compat.MidnightControlsCompat;
import eu.midnightdust.midnightcontrols.client.compat.YACLCompat;
import eu.midnightdust.midnightcontrols.client.gui.config.ControlsInput;
import eu.midnightdust.midnightcontrols.client.mixin.AdvancementsScreenAccessor;
import eu.midnightdust.midnightcontrols.client.mixin.CreativeInventoryScreenAccessor;
import eu.midnightdust.midnightcontrols.client.mixin.KeyboardAccessor;
import eu.midnightdust.midnightcontrols.client.mixin.MouseAccessor;
import eu.midnightdust.midnightcontrols.client.util.InventoryUtil;
import eu.midnightdust.midnightcontrols.client.util.storage.AxisStorage;
import eu.midnightdust.midnightcontrols.client.util.storage.ButtonStorage;
import net.minecraft.client.gui.Click;
import net.minecraft.client.gui.navigation.NavigationDirection;
import net.minecraft.client.gui.screen.option.KeybindsScreen;
import net.minecraft.client.gui.widget.EntryListWidget;
import net.minecraft.client.gui.widget.PressableWidget;
import net.minecraft.client.gui.widget.SliderWidget;
import net.minecraft.client.input.KeyInput;
import net.minecraft.client.input.MouseInput;
import net.minecraft.entity.vehicle.BoatEntity;
import eu.midnightdust.midnightcontrols.MidnightControls;
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.enums.CameraMode;
import eu.midnightdust.midnightcontrols.client.gui.RingScreen;
import eu.midnightdust.midnightcontrols.client.touch.gui.TouchscreenOverlay;
import eu.midnightdust.midnightcontrols.client.ring.RingPage;
import eu.midnightdust.midnightcontrols.client.util.HandledScreenAccessor;
import eu.midnightdust.midnightcontrols.client.util.MathUtil;
import eu.midnightdust.midnightcontrols.client.enums.ButtonState;
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.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.multiplayer.MultiplayerServerListWidget;
import net.minecraft.client.gui.screen.world.WorldListWidget;
import net.minecraft.text.TranslatableTextContent;
import net.minecraft.util.math.MathHelper;
import org.jetbrains.annotations.NotNull;
import org.lwjgl.glfw.GLFW;
import org.lwjgl.glfw.GLFWGamepadState;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import static eu.midnightdust.midnightcontrols.client.MidnightControlsClient.client;
import static org.lwjgl.glfw.GLFW.*;
/**
* Represents the midnightcontrols' input handler.
*
* @author Motschen, LambdAurora
* @version 1.10.0
* @since 1.0.0
*/
public class MidnightInput {
public static final Map<Integer, Integer> BUTTON_COOLDOWNS = new HashMap<>();
public static final KeyInput ENTER_KEY_INPUT = new KeyInput(GLFW_KEY_ENTER, 0, 0);
// Cooldowns
public int actionGuiCooldown = 0;
public int joystickCooldown = 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;
public float mouseSpeedX = 0.f;
public float mouseSpeedY = 0.f;
public int inventoryInteractionCooldown = 0;
public int screenCloseCooldown = 0;
private ControlsInput controlsInput = null;
public MidnightInput() {}
/**
* This method is called every Minecraft tick.
*/
public void tick() {
this.targetYaw = 0.F;
this.targetPitch = 0.F;
// Handles the key bindings.
if (MidnightControlsClient.BINDING_LOOK_UP.isPressed()) {
this.handleFlatLook(AxisStorage.of(GLFW_GAMEPAD_AXIS_RIGHT_Y, -0.8F, 0d));
} else if (MidnightControlsClient.BINDING_LOOK_DOWN.isPressed()) {
this.handleFlatLook(AxisStorage.of(GLFW_GAMEPAD_AXIS_RIGHT_Y, 0.8F, 0d));
}
if (MidnightControlsClient.BINDING_LOOK_LEFT.isPressed()) {
this.handleFlatLook(AxisStorage.of(GLFW_GAMEPAD_AXIS_RIGHT_X, -0.8F, 0d));
} else if (MidnightControlsClient.BINDING_LOOK_RIGHT.isPressed()) {
this.handleFlatLook(AxisStorage.of(GLFW_GAMEPAD_AXIS_RIGHT_X, 0.8F, 0d));
}
InputManager.INPUT_MANAGER.tick();
}
/**
* This method is called every Minecraft tick for controller input update.
*/
public void tickController() {
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;
if (this.joystickCooldown > 0)
--this.joystickCooldown;
InputManager.updateStates();
var controller = MidnightControlsConfig.getController();
if (controller.isConnected()) {
var state = controller.getState();
this.fetchButtonInput(state, false);
this.fetchTriggerInput(state, false);
this.fetchJoystickInput(state, false, false);
}
MidnightControlsConfig.getSecondController().filter(Controller::isConnected)
.ifPresent(joycon -> {
var state = joycon.getState();
this.fetchButtonInput(state, true);
this.fetchTriggerInput(state, true);
this.fetchJoystickInput(state, true, false);
});
boolean allowInput = this.controlsInput == null || this.controlsInput.getFocusedBinding() == null;
if (allowInput)
InputManager.updateBindings();
if (this.controlsInput != null) {
InputManager.STATES.forEach((num, button) -> {
if (button.isPressed()) System.out.println(num);
});
}
if (this.controlsInput != null && InputManager.STATES.int2ObjectEntrySet().parallelStream().map(Map.Entry::getValue).allMatch(ButtonState::isUnpressed)) {
//if (MidnightControlsConfig.debug) MidnightControls.log("Starting MidnightInput Button Edit");
if (this.controlsInput.getFocusedBinding() != null && !this.controlsInput.isWaiting()) {
int[] buttons = new int[this.controlsInput.getCurrentButtons().size()];
for (int i = 0; i < this.controlsInput.getCurrentButtons().size(); i++)
buttons[i] = this.controlsInput.getCurrentButtons().get(i);
this.controlsInput.finishBindingEdit(buttons);
this.controlsInput = null;
}
}
if (this.inventoryInteractionCooldown > 0)
this.inventoryInteractionCooldown--;
}
/**
* This method is called 1000 times a second for smooth camera input
*/
public void tickCameraStick() {
var controller = MidnightControlsConfig.getController();
if (controller.isConnected()) {
this.fetchJoystickInput(controller.getState(), false, true);
}
MidnightControlsConfig.getSecondController().filter(Controller::isConnected)
.ifPresent(joycon -> this.fetchJoystickInput(joycon.getState(), true, true));
}
/**
* This method is called before the screen is rendered.
*
* @param screen the screen to render
*/
public void onPreRenderScreen(@NotNull Screen screen) {
if (!isScreenInteractive(screen)) {
InputManager.INPUT_MANAGER.updateMousePosition(client);
}
}
/**
* This method is called to update the camera
*/
public void updateCamera() {
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.lastYaw + (this.targetYaw * 0.175));
float rotationPitch = (float) (client.player.lastPitch + (this.targetPitch * 0.175));
client.player.lastYaw = rotationYaw;
client.player.lastPitch = 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);
}
}
/**
* This method is called when a Screen is opened.
*
* @param windowWidth the window width
* @param windowHeight the window height
*/
public void onScreenOpen(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(ControlsInput widget) {
this.controlsInput = widget;
if (widget != null) {
this.controlsInput.getCurrentButtons().clear();
this.controlsInput.setWaiting(true);
}
}
private void fetchButtonInput(@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 pressed = buffer.get() == (byte) 1;
var state = ButtonState.NONE;
var previousState = InputManager.STATES.getOrDefault(btn, ButtonState.NONE);
if (pressed != previousState.isPressed()) {
state = pressed ? ButtonState.PRESS : ButtonState.RELEASE;
this.handleButton(ButtonStorage.of(btn, state));
if (pressed)
BUTTON_COOLDOWNS.put(btn, 5);
} else if (pressed) {
state = ButtonState.REPEAT;
if (BUTTON_COOLDOWNS.getOrDefault(btn, 0) == 0) {
BUTTON_COOLDOWNS.put(btn, 5);
this.handleButton(ButtonStorage.of(btn, state));
}
}
InputManager.STATES.put(btn, state);
}
}
final MathUtil.PolarUtil polarUtil = new MathUtil.PolarUtil();
private void fetchJoystickInput(@NotNull GLFWGamepadState gamepadState, boolean leftJoycon, boolean cameraTick) {
var buffer = gamepadState.axes();
polarUtil.calculate(buffer.get(GLFW_GAMEPAD_AXIS_LEFT_X), buffer.get(GLFW_GAMEPAD_AXIS_LEFT_Y), 1, MidnightControlsConfig.leftDeadZone);
float leftX = polarUtil.polarX;
float leftY = polarUtil.polarY;
polarUtil.calculate(buffer.get(GLFW_GAMEPAD_AXIS_RIGHT_X), buffer.get(GLFW_GAMEPAD_AXIS_RIGHT_Y), 1, MidnightControlsConfig.rightDeadZone);
float rightX = polarUtil.polarX;
float rightY = polarUtil.polarY;
boolean isRadialMenu = client.currentScreen instanceof RingScreen || (PlatformFunctions.isModLoaded("emotecraft") && EmotecraftCompat.isEmotecraftScreen(client.currentScreen));
if (!isRadialMenu) {
for (int i = cameraTick ? GLFW_GAMEPAD_AXIS_RIGHT_X : 0; i < (cameraTick ? GLFW_GAMEPAD_AXIS_LEFT_TRIGGER : GLFW_GAMEPAD_AXIS_RIGHT_X); i++) {
int axis = leftJoycon ? ButtonBinding.controller2Button(i) : i;
float value = buffer.get(i);
switch (i) {
case GLFW_GAMEPAD_AXIS_LEFT_X -> {
if (MidnightControlsConfig.analogMovement) value = leftX;
}
case GLFW_GAMEPAD_AXIS_LEFT_Y -> {
if (MidnightControlsConfig.analogMovement) value = leftY;
}
case GLFW_GAMEPAD_AXIS_RIGHT_X -> value = rightX;
case GLFW_GAMEPAD_AXIS_RIGHT_Y -> value = rightY;
}
if (i == GLFW.GLFW_GAMEPAD_AXIS_LEFT_Y)
value *= -1.0F;
this.handleJoystickAxis(AxisStorage.of(axis, value));
}
}
else {
boolean leftStickActive = leftX != 0 || leftY != 0;
handleRadialMenu(leftStickActive ? leftX : rightX, leftStickActive ? leftY : rightY);
}
}
private void fetchTriggerInput(@NotNull GLFWGamepadState gamepadState, boolean leftJoycon) {
var buffer = gamepadState.axes();
for (int i = GLFW_GAMEPAD_AXIS_LEFT_TRIGGER; i <= GLFW_GAMEPAD_AXIS_RIGHT_TRIGGER; i++) {
int axis = leftJoycon ? ButtonBinding.controller2Button(i) : i;
float value = buffer.get(i);
this.handleTriggerAxis(AxisStorage.of(axis, value, MidnightControlsConfig.triggerDeadZone));
}
}
public void handleButton(ButtonStorage storage) {
if (this.controlsInput != null && this.controlsInput.getFocusedBinding() != null) {
if (storage.state == ButtonState.PRESS && !this.controlsInput.getCurrentButtons().contains(storage.button)) {
this.controlsInput.getCurrentButtons().add(storage.button);
var buttons = new int[this.controlsInput.getCurrentButtons().size()];
for (int i = 0; i < this.controlsInput.getCurrentButtons().size(); i++)
buttons[i] = this.controlsInput.getCurrentButtons().get(i);
this.controlsInput.getFocusedBinding().setButton(buttons);
this.controlsInput.update();
this.controlsInput.setWaiting(false);
}
return;
}
if (client.currentScreen != null && storage.state.isPressed() && storage.button == GLFW_GAMEPAD_BUTTON_Y &&
MidnightControlsConfig.arrowScreens.contains(client.currentScreen.getClass().getCanonicalName())) {
pressKeyboardKey(client, GLFW.GLFW_KEY_ENTER);
this.screenCloseCooldown = 5;
}
else if (storage.state.isPressed()) {
if (client.currentScreen != null && storage.isDpad() && this.actionGuiCooldown == 0) {
switch (storage.button) {
case GLFW_GAMEPAD_BUTTON_DPAD_UP -> this.changeFocus(client.currentScreen, NavigationDirection.UP);
case GLFW_GAMEPAD_BUTTON_DPAD_DOWN -> this.changeFocus(client.currentScreen, NavigationDirection.DOWN);
case GLFW_GAMEPAD_BUTTON_DPAD_LEFT -> this.handleLeftRight(client.currentScreen, false);
case GLFW_GAMEPAD_BUTTON_DPAD_RIGHT -> this.handleLeftRight(client.currentScreen, true);
}
if (MidnightControlsConfig.wasdScreens.contains(client.currentScreen.getClass().getCanonicalName())) {
switch (storage.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);
}
}
return;
}
}
else {
if (storage.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; // Set the cooldown to 5 ticks to avoid unintended button presses.
return;
}
}
else if (PlatformFunctions.isModLoaded("libgui")) LibGuiCompat.handlePress(client.currentScreen);
}
}
}
if (storage.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 && client.currentScreen != null) {
var accessor = (MouseAccessor) client.mouse;
accessor.midnightcontrols$onCursorPos(client.getWindow().getHandle(), client.mouse.getX(), client.mouse.getY());
switch (storage.state) {
// Button pressed
case PRESS -> accessor.midnightcontrols$onMouseButton(client.getWindow().getHandle(), new MouseInput(GLFW_MOUSE_BUTTON_LEFT, 0), 1);
case RELEASE -> { // Button released
accessor.midnightcontrols$onMouseButton(client.getWindow().getHandle(), new MouseInput(GLFW_MOUSE_BUTTON_LEFT, 0), 0);
client.currentScreen.setDragging(false);
}
case REPEAT -> client.currentScreen.setDragging(true); // Button held down / dragging
}
this.screenCloseCooldown = 5;
} else {
this.ignoreNextARelease = false;
}
}
else if (storage.button == GLFW.GLFW_GAMEPAD_BUTTON_X && client.currentScreen != null && !isScreenInteractive(client.currentScreen)
&& this.actionGuiCooldown == 0) {
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 (client.currentScreen instanceof HandledScreen<?> handledScreen && ((HandledScreenAccessor) handledScreen).midnightcontrols$getSlotAt(
mouseX, mouseY) != null) return;
if (!this.ignoreNextXRelease && client.currentScreen != null) {
if (storage.state == ButtonState.PRESS) client.currentScreen.mouseClicked(new Click(mouseX, mouseY, new MouseInput(GLFW.GLFW_MOUSE_BUTTON_2, 1)), false);
else if (storage.state == ButtonState.RELEASE) client.currentScreen.mouseReleased(new Click(mouseX, mouseY, new MouseInput(GLFW.GLFW_MOUSE_BUTTON_2, 0)));
this.screenCloseCooldown = 5;
} else {
this.ignoreNextXRelease = false;
}
}
}
private void handleTriggerAxis(AxisStorage storage) {
storage.setupButtonStates();
}
private void handleJoystickAxis(AxisStorage storage) {
storage.setupButtonStates();
this.handleJoystickMovement(storage);
if (this.handleScreenScrolling(client.currentScreen, storage)) return;
storage.absValue = (float) MathHelper.clamp(storage.absValue / MidnightControlsConfig.getAxisMaxValue(storage.axis), 0.f, 1.f);
if (client.currentScreen == null) {
// Handles the look direction.
this.handleLook(storage);
} else {
boolean allowMouseControl = true;
if (this.actionGuiCooldown == 0 && MidnightControlsConfig.isMovementAxis(storage.axis) && isScreenInteractive(client.currentScreen)) {
if (MidnightControlsConfig.isForwardButton(storage.axis, false, storage.buttonState)) {
allowMouseControl = this.changeFocus(client.currentScreen, NavigationDirection.UP);
} else if (MidnightControlsConfig.isBackButton(storage.axis, false, storage.buttonState)) {
allowMouseControl = this.changeFocus(client.currentScreen, NavigationDirection.DOWN);
} else if (MidnightControlsConfig.isLeftButton(storage.axis, false, storage.buttonState)) {
allowMouseControl = this.handleLeftRight(client.currentScreen, false);
} else if (MidnightControlsConfig.isRightButton(storage.axis, false, storage.buttonState)) {
allowMouseControl = this.handleLeftRight(client.currentScreen, true);
}
}
float movementX = 0.f;
float movementY = 0.f;
if (MidnightControlsConfig.isBackButton(storage.axis, false, (storage.value > 0 ? ButtonState.PRESS : ButtonState.RELEASE))) {
movementY = storage.absValue;
} else if (MidnightControlsConfig.isForwardButton(storage.axis, false, (storage.value > 0 ? ButtonState.PRESS : ButtonState.RELEASE))) {
movementY = -storage.absValue;
} else if (MidnightControlsConfig.isLeftButton(storage.axis, false, (storage.value > 0 ? ButtonState.PRESS : ButtonState.RELEASE))) {
movementX = -storage.absValue;
} else if (MidnightControlsConfig.isRightButton(storage.axis, false, (storage.value > 0 ? ButtonState.PRESS : ButtonState.RELEASE))) {
movementX = storage.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) < storage.deadZone && Math.abs(prevYAxis) < storage.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
);
}
InventoryUtil.moveMouseToClosestSlot(client.currentScreen);
}
this.prevXAxis = movementX;
this.prevYAxis = movementY;
}
}
private void handleJoystickMovement(AxisStorage storage) {
float axisValue = storage.absValue;
if (!MidnightControlsConfig.analogMovement || (client.player != null && client.player.getVehicle() instanceof BoatEntity)) {
axisValue = (float) (storage.absValue - storage.deadZone);
axisValue /= (float) (1.0 - storage.deadZone);
axisValue *= (float) storage.deadZone;
}
axisValue = (float) Math.min(axisValue / MidnightControlsConfig.getAxisMaxValue(storage.axis), 1);
if (AxisStorage.isLeftAxis(storage.axis)) MidnightControlsCompat.handleMovement(storage, axisValue);
InputManager.BUTTON_VALUES.put(storage.getButtonId(true), storage.polarity == AxisStorage.Polarity.PLUS ? axisValue : 0.f);
InputManager.BUTTON_VALUES.put(storage.getButtonId(false), storage.polarity == AxisStorage.Polarity.MINUS ? axisValue : 0.f);
storage.absValue = axisValue;
}
private boolean handleScreenScrolling(Screen screen, AxisStorage storage) {
if (screen == null) return false;
// @TODO allow rebinding to left stick
int preferredAxis = true ? GLFW_GAMEPAD_AXIS_RIGHT_Y : GLFW_GAMEPAD_AXIS_LEFT_Y;
if (this.controlsInput != null && this.controlsInput.getFocusedBinding() != null) {
if (storage.buttonState != ButtonState.NONE && !this.controlsInput.getCurrentButtons().contains(storage.getButtonId(storage.buttonState == ButtonState.PRESS))) {
this.controlsInput.getCurrentButtons().add(storage.getButtonId(storage.buttonState == ButtonState.PRESS));
int[] buttons = new int[this.controlsInput.getCurrentButtons().size()];
for (int i = 0; i < this.controlsInput.getCurrentButtons().size(); i++)
buttons[i] = this.controlsInput.getCurrentButtons().get(i);
this.controlsInput.getFocusedBinding().setButton(buttons);
this.controlsInput.update();
this.controlsInput.setWaiting(false);
}
return true;
} else if (storage.absValue >= storage.deadZone) {
if (screen instanceof CreativeInventoryScreen creativeInventoryScreen) {
if (storage.axis == preferredAxis) {
var accessor = (CreativeInventoryScreenAccessor) creativeInventoryScreen;
if (accessor.midnightcontrols$hasScrollbar() && storage.absValue >= storage.deadZone) {
creativeInventoryScreen.mouseScrolled(0.0, 0.0, 0, -(storage.value * 0.0175f));
}
return true;
}
} else if (screen instanceof AdvancementsScreen advancementsScreen) {
if (storage.axis == GLFW_GAMEPAD_AXIS_RIGHT_X || storage.axis == GLFW_GAMEPAD_AXIS_RIGHT_Y) {
var accessor = (AdvancementsScreenAccessor) advancementsScreen;
AdvancementTab tab = accessor.getSelectedTab();
tab.move(storage.axis == GLFW_GAMEPAD_AXIS_RIGHT_X ? -storage.value * 1.0 : 0.0, storage.axis == GLFW_GAMEPAD_AXIS_RIGHT_Y ? -storage.value * 5.0 : 0.0);
return true;
}
} else if (screen != null) {
if (storage.axis == preferredAxis && !handleListWidgetScrolling(screen.children(), storage.value)) {
try {
screen.mouseScrolled(0.0, 0.0, 0, -(storage.value * 0.0175f));
} catch (NullPointerException ignored) {}
} else if (isScreenInteractive(screen)) {
if (joystickCooldown == 0) {
switch (storage.axis) {
case GLFW_GAMEPAD_AXIS_LEFT_Y -> {
this.changeFocus(screen, storage.value > 0 ? NavigationDirection.UP : NavigationDirection.DOWN);
joystickCooldown = 4;
}
case GLFW_GAMEPAD_AXIS_LEFT_X -> {
this.handleLeftRight(screen, storage.value > 0);
joystickCooldown = 4;
}
}
}
return true;
}
}
}
return false;
}
public void handleRadialMenu(float x, float y) {
int index = -1;
float border = 0.3f;
if (x < -border) {
index = 3;
if (y < -border) index = 0;
else if (y > border) index = 5;
} else if (x > border) {
index = 4;
if (y < -border) index = 2;
else if (y > border) index = 7;
} else {
if (y < -border) index = 1;
else if (y > border) index = 6;
}
if (client.currentScreen instanceof RingScreen && index > -1) RingPage.selected = index;
if (PlatformFunctions.isModLoaded("emotecraft") && EmotecraftCompat.isEmotecraftScreen(client.currentScreen)) EmotecraftCompat.handleEmoteSelector(index);
}
public boolean handleListWidgetScrolling(List<? extends Element> children, float value) {
return children.stream().filter(element -> element instanceof SpruceEntryListWidget)
.map(element -> (SpruceEntryListWidget<?>) element)
.filter(AbstractSpruceWidget::isFocusedOrHovered)
.anyMatch(element -> {
element.mouseScrolled(0.0, 0.0, 0, -value);
return true;
}) ||
children.stream().filter(element -> element instanceof EntryListWidget)
.map(element -> (EntryListWidget<?>) element)
.filter(element -> element.getType().isFocused())
.anyMatch(element -> {
element.mouseScrolled(0.0, 0.0, 0, -value / 30);
return true;
});
}
public boolean handleAButton(@NotNull Screen screen, @NotNull Element focused) {
if (focused instanceof PressableWidget widget) {
widget.playDownSound(MinecraftClient.getInstance().getSoundManager());
widget.onPress(ENTER_KEY_INPUT);
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);
entry.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 (PlatformFunctions.isModLoaded("yet-another-config-lib") && YACLCompat.handleAButton(screen, focused)) {
return true;
}
else pressKeyboardKey(screen, GLFW_KEY_ENTER);
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(new NavigationEvent(right ? NavigationDirection.RIGHT : NavigationDirection.LEFT, false, false));
this.actionGuiCooldown = 5;
return false;
}
if (PlatformFunctions.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) {
switch (element) {
case SpruceElement spruceElement -> {
if (spruceElement.requiresCursor())
return true;
return !spruceElement.onNavigation(new NavigationEvent(right ? NavigationDirection.RIGHT : NavigationDirection.LEFT, false, false));
}
case SliderWidget slider -> {
if (slider.active) {
slider.keyPressed(new KeyInput(right ? 262 : 263, 0, 0));
this.actionGuiCooldown = 2; // Prevent to press too quickly the focused element, so we have to skip 5 ticks.
return true;
}
}
// case AlwaysSelectedEntryListWidget<?> alwaysSelectedEntryListWidget -> {
// //TODO ((EntryListWidgetAccessor) element).midnightcontrols$moveSelection(right ? EntryListWidget.MoveDirection.DOWN : EntryListWidget.MoveDirection.UP);
// return false;
// }
case ParentElement entryList -> {
var focused = entryList.getFocused();
if (focused == null)
return true;
return this.handleRightLeftElement(focused, right);
}
default -> {
}
}
return true;
}
private double prevX = 0;
private double prevY = 0;
private double xValue;
private AxisStorage.Polarity xPolarity;
/**
* Handles the look direction input.
*
* @param storage the state of the provided axis
*/
public void handleLook(AxisStorage storage) {
if (client.player == null || !(storage.axis == GLFW_GAMEPAD_AXIS_RIGHT_Y || storage.axis == GLFW_GAMEPAD_AXIS_RIGHT_X)) return;
// Handles the look direction.
if (MidnightControlsConfig.cameraMode == CameraMode.FLAT) handleFlatLook(storage);
else handleAdaptiveLook(storage);
MidnightControlsCompat.handleCamera(this.targetYaw, this.targetPitch);
}
private void handleFlatLook(AxisStorage storage) {
if (storage.polarity != AxisStorage.Polarity.ZERO) {
double rotation = Math.pow(storage.value, 2.0) * 0.11D * storage.polarity.multiplier;
if (storage.axis == GLFW_GAMEPAD_AXIS_RIGHT_Y) this.targetPitch = rotation * MidnightControlsConfig.getRightYAxisSign() * MidnightControlsConfig.yAxisRotationSpeed / 2;
else this.targetYaw = rotation * MidnightControlsConfig.getRightXAxisSign() * MidnightControlsConfig.rotationSpeed / 2;
}
}
private void handleAdaptiveLook(AxisStorage storage) {
if (storage.axis == GLFW_GAMEPAD_AXIS_RIGHT_X) {
xValue = storage.value;
xPolarity = storage.polarity;
}
else {
double yStep = (MidnightControlsConfig.yAxisRotationSpeed / 50) * 0.6000000238418579 + 0.20000000298023224;
double xStep = (MidnightControlsConfig.rotationSpeed / 50) * 0.6000000238418579 + 0.20000000298023224;
float yValue = storage.value;
AxisStorage.Polarity yPolarity = storage.polarity;
double cursorDeltaX = 2 * xValue - this.prevX;
double cursorDeltaY = 2 * yValue - this.prevY;
boolean slowdown = client.options.getPerspective().isFirstPerson() && Objects.requireNonNull(client.player).isUsingSpyglass();
double x = cursorDeltaX * xStep * (slowdown ? xStep : 1);
double y = cursorDeltaY * yStep * (slowdown ? yStep : 1);
double powXValue = Math.pow(x, 2.0);
double powYValue = Math.pow(y, 2.0);
if (xPolarity != AxisStorage.Polarity.ZERO) {
double sign = MidnightControlsConfig.getRightXAxisSign() * MidnightControlsConfig.rotationSpeed;
this.targetYaw = sign * powXValue * 0.11D * xPolarity.multiplier;
}
if (yPolarity != AxisStorage.Polarity.ZERO) {
double sign = MidnightControlsConfig.getRightYAxisSign() * MidnightControlsConfig.yAxisRotationSpeed;
this.targetPitch = sign * powYValue * 0.11D * yPolarity.multiplier;
}
this.prevY = yValue;
this.prevX = xValue;
}
}
public void handleTouchscreenLook(AxisStorage storage) {
if (storage.polarity != AxisStorage.Polarity.ZERO) {
double rotation = storage.value * 0.11D * MidnightControlsConfig.touchSpeed/5;
if (storage.axis == GLFW_GAMEPAD_AXIS_RIGHT_Y) this.targetPitch = rotation;
else this.targetYaw = rotation;
}
}
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(new NavigationEvent(direction, false, false))) {
this.actionGuiCooldown = 5;
}
return true;
}
switch (direction) {
case UP -> pressKeyboardKey(screen, GLFW.GLFW_KEY_UP);
case DOWN -> pressKeyboardKey(screen, GLFW.GLFW_KEY_DOWN);
case LEFT -> pressKeyboardKey(screen, GLFW.GLFW_KEY_LEFT);
case RIGHT -> pressKeyboardKey(screen, GLFW.GLFW_KEY_RIGHT);
}
this.actionGuiCooldown = 5;
return true;
} catch (Exception exception) {MidnightControls.warn("Unknown exception encountered while trying to change focus: "+exception);}
return false;
}
/**
* 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");
if (screen instanceof KeybindsScreen) return false;
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(ENTER_KEY_INPUT);
return true;
}
}
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));
}
public void pressKeyboardKey(MinecraftClient client, int key) {
((KeyboardAccessor) client.keyboard).midnightcontrols$onKey(client.getWindow().getHandle(), 1, new KeyInput(key, 0, 0));
}
public void pressKeyboardKey(Screen screen, int key) {
screen.keyPressed(new KeyInput(key, 0, 0));
}
}

View File

@@ -15,6 +15,7 @@ import net.minecraft.block.BlockState;
import net.minecraft.block.FluidBlock;
import net.minecraft.block.SlabBlock;
import net.minecraft.client.MinecraftClient;
import net.minecraft.entity.attribute.EntityAttributes;
import net.minecraft.item.BlockItem;
import net.minecraft.item.ItemStack;
import net.minecraft.util.hit.BlockHitResult;
@@ -27,6 +28,8 @@ import net.minecraft.world.RaycastContext;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import static eu.midnightdust.midnightcontrols.client.MidnightControlsClient.client;
/**
* Represents the reach-around API of midnightcontrols.
*
@@ -38,10 +41,10 @@ public class MidnightReacharound {
private boolean lastReacharoundVertical = false;
private boolean onSlab = false;
public void tick(@NotNull MinecraftClient client) {
this.lastReacharoundResult = this.tryVerticalReachAround(client);
public void tick() {
this.lastReacharoundResult = this.tryVerticalReachAround();
if (this.lastReacharoundResult == null) {
this.lastReacharoundResult = this.tryHorizontalReachAround(client);
this.lastReacharoundResult = this.tryHorizontalReachAround();
this.lastReacharoundVertical = false;
} else this.lastReacharoundVertical = true;
}
@@ -73,17 +76,16 @@ public class MidnightReacharound {
return MidnightControlsFeature.HORIZONTAL_REACHAROUND.isAvailable() || MidnightControlsFeature.VERTICAL_REACHAROUND.isAvailable();
}
private float getPlayerRange(@NotNull MinecraftClient client) {
return client.interactionManager != null ? client.interactionManager.getReachDistance() : 0.f;
public static float getPlayerRange(@NotNull MinecraftClient client) {
return client.player != null ? Double.valueOf(client.player.getAttributeValue(EntityAttributes.BLOCK_INTERACTION_RANGE)).floatValue() : 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) {
public @Nullable BlockHitResult tryVerticalReachAround() {
if (!MidnightControlsFeature.VERTICAL_REACHAROUND.isAvailable())
return null;
if (client.player == null || client.world == null || client.crosshairTarget == null || client.crosshairTarget.getType() != HitResult.Type.MISS
@@ -101,7 +103,7 @@ public class MidnightReacharound {
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())) {
if (client.player.getBlockPos().getY() - blockPos.getY() > 1 && (client.world.isAir(blockPos) || state.isReplaceable())) {
return new BlockHitResult(result.getPos(), Direction.DOWN, blockPos, false);
}
}
@@ -112,39 +114,46 @@ public class MidnightReacharound {
/**
* 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) {
public @Nullable BlockHitResult tryHorizontalReachAround() {
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.world != null && 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();
// Temporary pos, do not use
Vec3d playerPosi = client.player.getEntityPos();
// Imitates var playerPos = client.player.getBlockPos().down();
Vec3d playerPos = new Vec3d(playerPosi.getX(), playerPosi.getY() - 1.0, playerPosi.getZ());
if (client.player.getY() - playerPos.getY() - 1.0 >= 0.25) {
playerPos = playerPos.up();
// Imitates playerPos = playerPos.up();
playerPos = playerPosi;
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 targetPos = new Vec3d(client.crosshairTarget.getPos().getX(), client.crosshairTarget.getPos().getY(), client.crosshairTarget.getPos().getZ()).subtract(playerPos);
var vector = new Vec3d(MathHelper.clamp(targetPos.getX(), -1, 1), 0, MathHelper.clamp(targetPos.getZ(), -1, 1));
var blockPos = playerPos.add(vector);
// Some functions still need BlockPos, so this is here to let that happen
var blockyPos = BlockPos.ofFloored(blockPos);
var direction = client.player.getHorizontalFacing();
var state = client.world.getBlockState(blockPos);
var state = client.world.getBlockState(blockyPos);
if (!state.isAir())
return null;
var adjacentBlockState = client.world.getBlockState(blockPos.offset(direction.getOpposite()));
var adjacentBlockState = client.world.getBlockState(blockyPos.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 new BlockHitResult(blockPos, direction, blockyPos, false);
}
return null;
}

View File

@@ -0,0 +1,27 @@
/*
* 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 me.juancarloscp52.bedrockify.client.BedrockifyClient;
/**
* Represents HQM compatibility handler.
*
* @author Motschen
* @version 1.7.0
* @since 1.7.0
*/
public class BedrockifyCompat implements CompatHandler {
@Override
public void handle() {
BedrockifyClient.getInstance().settings.disableFlyingMomentum = false;
}
}

View File

@@ -10,6 +10,7 @@
package eu.midnightdust.midnightcontrols.client.compat;
import eu.midnightdust.midnightcontrols.client.MidnightControlsClient;
import eu.midnightdust.midnightcontrols.client.util.storage.AxisStorage;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gui.screen.Screen;
import net.minecraft.client.gui.screen.ingame.HandledScreen;
@@ -26,12 +27,53 @@ import org.jetbrains.annotations.Nullable;
* @since 1.1.0
*/
public interface CompatHandler {
/**
* Handles compatibility of a mod.
*/
default void handle() {
handle(MidnightControlsClient.get());
}
/**
* Handles compatibility of a mod.
*
* @param mod this mod instance
*/
void handle(@NotNull MidnightControlsClient mod);
@Deprecated
default 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) {};
/**
* Handles movement for players as well as vehicles
*
* @param storage the storage containing info about the current axis
* @param adjustedValue the value of the axis, adjusted for max values and non-analogue movement, recommended for player movement
*/
default void handleMovement(@NotNull MinecraftClient client, AxisStorage storage, float adjustedValue) {}
/**
* Handles custom tab behavior
*
* @param forward whether the direction is forward or backward
*/
default boolean handleTabs(Screen screen, boolean forward) {
return false;
};
/**
* Handles custom page behavior
*
* @param forward whether the direction is forward or backward
*/
default boolean handlePages(Screen screen, boolean forward) {
return false;
};
/**
* Returns whether the mouse is required on the specified screen.

View File

@@ -0,0 +1,48 @@
package eu.midnightdust.midnightcontrols.client.compat;
import dev.emi.emi.api.EmiApi;
import dev.emi.emi.config.EmiConfig;
import dev.emi.emi.screen.EmiScreenManager;
import eu.midnightdust.midnightcontrols.client.MidnightControlsClient;
import eu.midnightdust.midnightcontrols.client.MidnightInput;
import eu.midnightdust.midnightcontrols.client.controller.ButtonBinding;
import eu.midnightdust.midnightcontrols.client.controller.ButtonCategory;
import eu.midnightdust.midnightcontrols.client.controller.InputManager;
import org.lwjgl.glfw.GLFW;
import static eu.midnightdust.midnightcontrols.MidnightControls.id;
public class EMICompat implements CompatHandler {
public static boolean handleEmiPages(boolean direction) {
if (isEMIEnabled() && MidnightControlsClient.input.actionGuiCooldown == 0 && EmiScreenManager.getSearchPanel() != null && EmiScreenManager.getSearchPanel().pageLeft != null && EmiScreenManager.getSearchPanel().pageRight != null) {
if (direction) EmiScreenManager.getSearchPanel().pageRight.onPress(MidnightInput.ENTER_KEY_INPUT);
else EmiScreenManager.getSearchPanel().pageLeft.onPress(MidnightInput.ENTER_KEY_INPUT);
MidnightControlsClient.input.actionGuiCooldown = 5;
return true;
}
return false;
}
@Override
public void handle() {
ButtonCategory category = new ButtonCategory(id("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)->handleEmiPages(false)).cooldown()
.filter(((buttonBinding) -> EmiApi.getHandledScreen() != null))
.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)->handleEmiPages(true)).cooldown()
.filter(((buttonBinding) -> EmiApi.getHandledScreen() != null))
.register();
}
public static boolean isEMIEnabled() {
return EmiConfig.enabled;
}
public static boolean isSearchBarCentered() {
return EmiConfig.centerSearchBar;
}
}

View File

@@ -0,0 +1,52 @@
package eu.midnightdust.midnightcontrols.client.compat;
import eu.midnightdust.midnightcontrols.client.controller.InputManager;
import eu.midnightdust.midnightcontrols.client.mixin.MouseAccessor;
import io.github.kosmx.emotes.arch.screen.ingame.FastMenuScreen;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gui.screen.Screen;
import net.minecraft.client.input.MouseInput;
import org.joml.Vector2i;
import org.lwjgl.glfw.GLFW;
public class EmotecraftCompat {
private static final MinecraftClient client = MinecraftClient.getInstance();
public static void openEmotecraftScreen(Screen parent) {
client.setScreen(new FastMenuScreen(parent));
}
public static boolean isEmotecraftScreen(Screen screen) {
return screen instanceof FastMenuScreen;
}
static int prevIndex = -1;
public static void handleEmoteSelector(int index) {
try {
if (client.currentScreen instanceof FastMenuScreen) {
boolean stickReleased = index == -1 && prevIndex != -1;
var pos = calcMousePos(stickReleased ? prevIndex : index);
InputManager.queueMousePosition(pos.x, pos.y);
InputManager.INPUT_MANAGER.updateMousePosition(client);
if (stickReleased) {
((MouseAccessor) client.mouse).midnightcontrols$onMouseButton(client.getWindow().getHandle(), new MouseInput(GLFW.GLFW_MOUSE_BUTTON_LEFT, 0), GLFW.GLFW_PRESS);
prevIndex = -1;
}
else prevIndex = index;
} else prevIndex = -1;
} catch (Exception ignored) {}
}
public static Vector2i calcMousePos(int index) {
int x = client.getWindow().getWidth() / 2;
int y = client.getWindow().getHeight() / 2;
switch (index) {
case 0, 3, 5 -> x -= 275;
case 2, 4, 7 -> x += 275;
}
switch (index) {
case 0, 1, 2 -> y -= 275;
case 5, 6, 7 -> y += 275;
}
return new Vector2i(x, y);
}
}

View File

@@ -9,10 +9,8 @@
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;
@@ -30,7 +28,7 @@ public class HQMCompat implements CompatHandler {
private Optional<Class<?>> guiBaseClass;
@Override
public void handle(@NotNull MidnightControlsClient mod) {
public void handle() {
this.guiBaseClass = LambdaReflection.getClass(GUI_BASE_CLASS_PATH);
}

View File

@@ -0,0 +1,43 @@
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 implements CompatHandler {
@Override
public boolean handleTabs(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.getFirst());
} else {
if (tabIndex > 0) tabManager.onTabClick(tabManager.tabs.get(tabIndex - 1));
else tabManager.onTabClick(tabManager.tabs.getLast());
}
return true;
}
return false;
}
@Override
public boolean handlePages(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);
return true;
}
} else {
if (tabManager.canGoBackAPage()) {
tabManager.setCurrentPage(tabManager.currentPage - 1);
return true;
}
}
}
return false;
}
}

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

@@ -9,9 +9,9 @@
package eu.midnightdust.midnightcontrols.client.compat;
import eu.midnightdust.midnightcontrols.client.MidnightControlsClient;
import eu.midnightdust.lib.util.PlatformFunctions;
import eu.midnightdust.midnightcontrols.client.controller.InputManager;
import net.fabricmc.loader.api.FabricLoader;
import eu.midnightdust.midnightcontrols.client.util.storage.AxisStorage;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gui.screen.Screen;
import net.minecraft.util.hit.BlockHitResult;
@@ -23,37 +23,49 @@ import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
import static eu.midnightdust.midnightcontrols.MidnightControls.log;
import static eu.midnightdust.midnightcontrols.client.MidnightControlsClient.client;
/**
* Represents a compatibility handler.
*
* @author LambdAurora
* @version 1.5.0
* @author LambdAurora, Motschen
* @version 1.10.0
* @since 1.1.0
*/
public class MidnightControlsCompat {
@Deprecated // INTERNAL -> PLEASE USE streamCompatHandlers() INSTEAD
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());
public static void init() {
if (PlatformFunctions.isModLoaded("emi")) {
log("Adding EMI compatibility...");
registerCompatHandler(new EMICompat());
}
if (isEMIPresent()) {
mod.log("Adding EMI compatibility...");
HANDLERS.add(new EMICompat());
if (PlatformFunctions.isModLoaded("hardcorequesting") && LambdaReflection.doesClassExist(HQMCompat.GUI_BASE_CLASS_PATH)) {
log("Adding HQM compatibility...");
registerCompatHandler(new HQMCompat());
}
if (FabricLoader.getInstance().isModLoaded("hardcorequesting") && LambdaReflection.doesClassExist(HQMCompat.GUI_BASE_CLASS_PATH)) {
mod.log("Adding HQM compatibility...");
HANDLERS.add(new HQMCompat());
if (PlatformFunctions.isModLoaded("bedrockify")) {
log("Adding Bedrockify compatibility...");
registerCompatHandler(new BedrockifyCompat());
}
HANDLERS.forEach(handler -> handler.handle(mod));
if (PlatformFunctions.isModLoaded("yet-another-config-lib")) {
log("Adding YACL compatibility...");
registerCompatHandler(new YACLCompat());
}
if (PlatformFunctions.isModLoaded("sodium")) {
log("Adding Sodium compatibility...");
registerCompatHandler(new SodiumCompat());
}
if (PlatformFunctions.isModLoaded("inventorytabs")) {
log("Adding Inventory Tabs compatibility...");
registerCompatHandler(new InventoryTabsCompat());
}
HANDLERS.forEach(CompatHandler::handle);
InputManager.loadButtonBindings();
}
@@ -82,7 +94,25 @@ public class MidnightControlsCompat {
* @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));
return streamCompatHandlers().anyMatch(handler -> handler.requireMouseOnScreen(screen));
}
/**
* Handles custom tabs for modded screens
*
* @param screen the screen
* @return true if the handle was fired and succeed, else false
*/
public static boolean handleTabs(Screen screen, boolean forward) {
return streamCompatHandlers().anyMatch(handler -> handler.handleTabs(screen, forward));
}
/**
* Handles custom pages for modded screens
*
* @param screen the screen
* @return true if the handle was fired and succeed, else false
*/
public static boolean handlePages(Screen screen, boolean forward) {
return streamCompatHandlers().anyMatch(handler -> handler.handlePages(screen, forward));
}
/**
@@ -150,21 +180,22 @@ public class MidnightControlsCompat {
}
return false;
}
/**
* Returns whether Roughly Enough Items is present.
* Handles the camera movement.
*
* @return true if Roughly Enough Items is present, else false
* @param targetYaw the target yaw
* @param targetPitch the target pitch
*/
public static boolean isReiPresent() {
return FabricLoader.getInstance().isModLoaded("roughlyenoughitems");
public static void handleCamera(double targetYaw, double targetPitch) {
MidnightControlsCompat.HANDLERS.forEach(handler -> handler.handleCamera(client, targetYaw, targetPitch));
}
/**
* Returns whether EMI is present.
* Handles movement for players as well as vehicles
*
* @return true if EMI is present, else false
* @param storage the storage containing info about the current axis
* @param adjustedValue the value of the axis, adjusted for max values and non-analogue movement, recommended for player movement
*/
public static boolean isEMIPresent() {
return FabricLoader.getInstance().isModLoaded("emi");
public static void handleMovement(AxisStorage storage, float adjustedValue) {
streamCompatHandlers().forEach(handler -> handler.handleMovement(client, storage, adjustedValue));
}
}

View File

@@ -9,36 +9,27 @@
package eu.midnightdust.midnightcontrols.client.compat;
import net.fabricmc.loader.api.FabricLoader;
import org.jetbrains.annotations.NotNull;
import eu.midnightdust.lib.util.PlatformFunctions;
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
* @author LambdAurora & Motschen
* @version 1.6.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);
}
private String mixinPackage;
@Override
public void onLoad(String mixinPackage) {
this.mixinPackage = mixinPackage + ".";
}
@Override
@@ -48,7 +39,13 @@ public class MidnightControlsMixinPlugin implements IMixinConfigPlugin {
@Override
public boolean shouldApplyMixin(String targetClassName, String mixinClassName) {
return this.conditionalMixins.getOrDefault(mixinClassName, Boolean.TRUE);
final String mixinName = mixinClassName.substring(this.mixinPackage.length());
final String packageName = mixinName.substring(0, mixinName.lastIndexOf('.'));
if (packageName.startsWith("sodium") && !PlatformFunctions.isModLoaded("sodium"))
return false;
return true;
}
@Override

View File

@@ -0,0 +1,20 @@
package eu.midnightdust.midnightcontrols.client.compat;
import eu.midnightdust.midnightcontrols.client.compat.mixin.sodium.SodiumOptionsGUIAccessor;
import net.caffeinemc.mods.sodium.client.gui.SodiumOptionsGUI;
import net.minecraft.client.gui.screen.Screen;
public class SodiumCompat implements CompatHandler {
@Override
public boolean 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));
optionsGUI.setPage(accessor.getPages().get(i));
return true;
}
return false;
}
}

View File

@@ -0,0 +1,58 @@
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 eu.midnightdust.midnightcontrols.client.MidnightInput;
import net.minecraft.client.gui.Element;
import net.minecraft.client.gui.screen.Screen;
import org.lwjgl.glfw.GLFW;
public class YACLCompat implements CompatHandler {
public static boolean handleAButton(Screen screen, Element element) {
if (element instanceof AbstractWidget abstractWidget) {
// imitate enter key press
return abstractWidget.keyPressed(MidnightInput.ENTER_KEY_INPUT);
}
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;
}
@Override
public boolean handleTabs(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

@@ -1,8 +1,8 @@
package eu.midnightdust.midnightcontrols.client.compat.mixin;
package eu.midnightdust.midnightcontrols.client.compat.mixin.sodium;
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 net.caffeinemc.mods.sodium.client.gui.SodiumOptionsGUI;
import net.caffeinemc.mods.sodium.client.gui.options.OptionPage;
import net.caffeinemc.mods.sodium.client.gui.options.control.ControlElement;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;

View File

@@ -9,21 +9,24 @@
package eu.midnightdust.midnightcontrols.client.controller;
import eu.midnightdust.midnightcontrols.client.ButtonState;
import com.google.common.base.Predicates;
import eu.midnightdust.midnightcontrols.client.enums.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.function.Predicate;
import java.util.stream.Collectors;
import static eu.midnightdust.midnightcontrols.client.MidnightControlsClient.client;
import static org.lwjgl.glfw.GLFW.*;
/**
@@ -44,6 +47,15 @@ public class ButtonBinding {
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.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();
@@ -52,6 +64,8 @@ public class ButtonBinding {
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((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();
@@ -62,9 +76,8 @@ public class ButtonBinding {
.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();
.action((client,binding,value,action) -> {if (action == ButtonState.PRESS) client.debugHudEntryList.setF3Enabled(!client.debugHudEntryList.isF3Enabled()); 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)
@@ -73,19 +86,19 @@ public class ButtonBinding {
.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 SMOOTH_CAMERA = new Builder("toggle_smooth_camera").onlyInGame().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).onlyInGame().register();
public static final ButtonBinding SPRINT = new Builder("sprint").buttons(GLFW_GAMEPAD_BUTTON_LEFT_THUMB)
.actions(InputHandlers::handleToggleSprint).onlyInGame().cooldown().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 && client.currentScreen.getClass().toString().contains("sodium"))).cooldown().register();
.action(InputHandlers.handleHotbar(false)).filter(Predicates.or(InputHandlers::inInventory, InputHandlers::inAdvancements).or((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 && client.currentScreen.getClass().toString().contains("sodium"))).cooldown().register();
.action(InputHandlers.handleHotbar(true)).filter(Predicates.or(InputHandlers::inInventory, InputHandlers::inAdvancements).or((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().register();
.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().register();
.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)
@@ -100,13 +113,14 @@ public class ButtonBinding {
private final String key;
private final Text text;
private KeyBinding mcKeyBinding = null;
protected PairPredicate<MinecraftClient, ButtonBinding> filter;
protected Predicate<ButtonBinding> filter;
private final List<PressAction> actions = new ArrayList<>(Collections.singletonList(PressAction.DEFAULT_ACTION));
private boolean hasCooldown;
private final boolean hasCooldown;
private int cooldownLength = 5;
private int cooldown = 0;
boolean pressed = false;
private boolean pressed = false;
public ButtonBinding(String key, int[] defaultButton, List<PressAction> actions, PairPredicate<MinecraftClient, ButtonBinding> filter, boolean hasCooldown) {
public ButtonBinding(String key, int[] defaultButton, List<PressAction> actions, Predicate<ButtonBinding> filter, boolean hasCooldown) {
this.setButton(this.defaultButton = defaultButton);
this.key = key;
this.text = Text.translatable(this.key);
@@ -114,9 +128,21 @@ public class ButtonBinding {
this.actions.addAll(actions);
this.hasCooldown = hasCooldown;
}
public ButtonBinding(String key, int[] defaultButton, List<PressAction> actions, Predicate<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);
this(key, defaultButton, Collections.emptyList(), Predicates.alwaysTrue(), hasCooldown);
}
public ButtonBinding(String key, int[] defaultButton, boolean hasCooldown, int cooldownLength) {
this(key, defaultButton, Collections.emptyList(), Predicates.alwaysTrue(), hasCooldown, cooldownLength);
}
/**
@@ -139,6 +165,14 @@ public class ButtonBinding {
if (InputManager.hasBinding(this))
InputManager.sortBindings();
}
/**
* Sets the button press state.
*
* @param pressed whether the button is pressed
*/
public void setPressed(boolean pressed) {
this.pressed = pressed;
}
/**
* Returns whether the bound button is the specified button or not.
@@ -154,8 +188,18 @@ public class ButtonBinding {
* Returns whether this button is down or not.
*
* @return true if the button is down, else false
* @deprecated Use {@link #isPressed()} instead
*/
@Deprecated
public boolean isButtonDown() {
return isPressed();
}
/**
* Returns whether this button is down or not.
*
* @return true if the button is down, else false
*/
public boolean isPressed() {
return this.pressed;
}
@@ -209,11 +253,10 @@ public class ButtonBinding {
/**
* 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);
public boolean isAvailable() {
return this.filter.test(this);
}
/**
@@ -234,7 +277,7 @@ public class ButtonBinding {
if (state == ButtonState.REPEAT && this.hasCooldown && this.cooldown != 0)
return;
if (this.hasCooldown && state.isPressed()) {
this.cooldown = 5;
this.cooldown = cooldownLength;
}
for (int i = this.actions.size() - 1; i >= 0; i--) {
if (this.actions.get(i).press(client, this, value, state))
@@ -320,7 +363,6 @@ public class ButtonBinding {
PLAYER_LIST.mcKeyBinding = options.playerListKey;
RIGHT.mcKeyBinding = options.rightKey;
SCREENSHOT.mcKeyBinding = options.screenshotKey;
SMOOTH_CAMERA.mcKeyBinding = options.smoothCameraKey;
SNEAK.mcKeyBinding = options.sneakKey;
SPRINT.mcKeyBinding = options.sprintKey;
SWAP_HANDS.mcKeyBinding = options.swapHandsKey;
@@ -371,7 +413,7 @@ public class ButtonBinding {
}
static {
MOVEMENT_CATEGORY = InputManager.registerDefaultCategory("key.categories.movement", category -> category.registerAllBindings(
MOVEMENT_CATEGORY = InputManager.registerDefaultCategory("movement", category -> category.registerAllBindings(
ButtonBinding.FORWARD,
ButtonBinding.BACK,
ButtonBinding.LEFT,
@@ -379,12 +421,13 @@ public class ButtonBinding {
ButtonBinding.JUMP,
ButtonBinding.SNEAK,
ButtonBinding.SPRINT));
GAMEPLAY_CATEGORY = InputManager.registerDefaultCategory("key.categories.gameplay", category -> category.registerAllBindings(
GAMEPLAY_CATEGORY = InputManager.registerDefaultCategory("gameplay", category -> category.registerAllBindings(
ButtonBinding.ATTACK,
ButtonBinding.PICK_BLOCK,
ButtonBinding.USE
));
INVENTORY_CATEGORY = InputManager.registerDefaultCategory("key.categories.inventory", category -> category.registerAllBindings(
INVENTORY_CATEGORY = InputManager.registerDefaultCategory("inventory", category -> category.registerAllBindings(
ButtonBinding.EXIT,
ButtonBinding.DROP_ITEM,
ButtonBinding.HOTBAR_LEFT,
ButtonBinding.HOTBAR_RIGHT,
@@ -402,14 +445,15 @@ public class ButtonBinding {
ButtonBinding.SLOT_LEFT,
ButtonBinding.SLOT_RIGHT
));
MULTIPLAYER_CATEGORY = InputManager.registerDefaultCategory("key.categories.multiplayer",
MULTIPLAYER_CATEGORY = InputManager.registerDefaultCategory("multiplayer",
category -> category.registerAllBindings(ButtonBinding.CHAT, ButtonBinding.PLAYER_LIST));
MISC_CATEGORY = InputManager.registerDefaultCategory("key.categories.misc", category -> category.registerAllBindings(
MISC_CATEGORY = InputManager.registerDefaultCategory("misc", category -> category.registerAllBindings(
ButtonBinding.SCREENSHOT,
ButtonBinding.TOGGLE_PERSPECTIVE,
ButtonBinding.PAUSE_GAME,
//SMOOTH_CAMERA,
ButtonBinding.DEBUG_SCREEN
ButtonBinding.DEBUG_SCREEN,
ButtonBinding.CONTROLS_RING
));
}
@@ -436,8 +480,9 @@ public class ButtonBinding {
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 Predicate<ButtonBinding> filter = Predicates.alwaysTrue();
private boolean cooldown = false;
private int cooldownLength = 5;
private ButtonCategory category = null;
private KeyBinding mcBinding = null;
@@ -454,6 +499,10 @@ public class ButtonBinding {
public Builder(@NotNull Identifier identifier) {
this(identifier.getNamespace() + "." + identifier.getPath());
}
@Deprecated
public Builder(@NotNull org.aperlambda.lambdacommon.Identifier identifier) {
this(identifier.getNamespace() + "." + identifier.getName());
}
/**
* Defines the default buttons of the {@link ButtonBinding}.
@@ -503,7 +552,7 @@ public class ButtonBinding {
* @param filter the filter
* @return the builder instance
*/
public Builder filter(@NotNull PairPredicate<MinecraftClient, ButtonBinding> filter) {
public Builder filter(@NotNull Predicate<ButtonBinding> filter) {
this.filter = filter;
return this;
}
@@ -512,8 +561,8 @@ public class ButtonBinding {
* Sets the filter of {@link ButtonBinding} to only in game.
*
* @return the builder instance
* @see #filter(PairPredicate)
* @see InputHandlers#inGame(MinecraftClient, ButtonBinding)
* @see #filter(Predicate)
* @see InputHandlers#inGame(ButtonBinding)
*/
public Builder onlyInGame() {
return this.filter(InputHandlers::inGame);
@@ -523,8 +572,8 @@ public class ButtonBinding {
* Sets the filter of {@link ButtonBinding} to only in inventory.
*
* @return the builder instance
* @see #filter(PairPredicate)
* @see InputHandlers#inInventory(MinecraftClient, ButtonBinding)
* @see #filter(Predicate)
* @see InputHandlers#inInventory(ButtonBinding)
*/
public Builder onlyInInventory() {
return this.filter(InputHandlers::inInventory);
@@ -540,6 +589,17 @@ public class ButtonBinding {
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}.
@@ -579,7 +639,7 @@ public class ButtonBinding {
* @return the built {@link ButtonBinding}
*/
public ButtonBinding build() {
var binding = new ButtonBinding(this.key, this.buttons, this.actions, this.filter, this.cooldown);
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)

View File

@@ -10,8 +10,7 @@
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 net.minecraft.util.Identifier;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
@@ -26,7 +25,7 @@ import java.util.List;
* @version 1.1.0
* @since 1.1.0
*/
public class ButtonCategory implements Identifiable {
public class ButtonCategory {
private final List<ButtonBinding> bindings = new ArrayList<>();
private final Identifier id;
private final int priority;
@@ -66,15 +65,12 @@ public class ButtonCategory implements Identifiable {
/**
* Gets the translated name of this category.
* <p>
* The translation key should be `modid.identifier_name`.
* The translation key should be `key.category.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());
return I18n.translate("key.category.%s.%s".formatted(id.getNamespace(), id.getPath()));
}
/**
@@ -87,7 +83,6 @@ public class ButtonCategory implements Identifiable {
return this.priority;
}
@Override
public @NotNull Identifier getIdentifier() {
return this.id;
}

View File

@@ -24,7 +24,7 @@ import org.lwjgl.system.MemoryStack;
import org.lwjgl.system.MemoryUtil;
import java.io.*;
import java.net.URL;
import java.net.URI;
import java.nio.ByteBuffer;
import java.nio.file.Files;
import java.nio.file.Paths;
@@ -32,6 +32,7 @@ import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import static org.lwjgl.BufferUtils.createByteBuffer;
@@ -42,7 +43,7 @@ import static org.lwjgl.BufferUtils.createByteBuffer;
* @version 1.7.0
* @since 1.0.0
*/
public record Controller(int id) implements Nameable {
public record Controller(int id) {
private static final Map<Integer, Controller> CONTROLLERS = new HashMap<>();
/**
@@ -78,8 +79,7 @@ public record Controller(int id) implements Nameable {
*
* @return the controller's name
*/
@Override
public String getName() {
public @NotNull String getName() {
var name = this.isGamepad() ? GLFW.glfwGetGamepadName(this.id) : GLFW.glfwGetJoystickName(this.id);
return name == null ? String.valueOf(this.id()) : name;
}
@@ -98,7 +98,7 @@ public record Controller(int id) implements Nameable {
public static Controller byId(int id) {
if (id > GLFW.GLFW_JOYSTICK_LAST) {
MidnightControlsClient.get().log("Controller '" + id + "' doesn't exist.");
MidnightControls.log("Controller '" + id + "' doesn't exist.");
id = GLFW.GLFW_JOYSTICK_LAST;
}
Controller controller;
@@ -121,11 +121,10 @@ public record Controller(int id) implements Nameable {
* 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 {
private static ByteBuffer ioResourceToBuffer(String resource) throws IOException {
ByteBuffer buffer = null;
var path = Paths.get(resource);
@@ -145,27 +144,22 @@ public record Controller(int id) implements Nameable {
* Updates the controller mappings.
*/
public static void updateMappings() {
CompletableFuture.supplyAsync(Controller::updateMappingsSync);
}
private static boolean updateMappingsSync() {
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);
MidnightControls.log("Updating controller mappings...");
Optional<File> databaseFile = getDatabaseFile();
if (databaseFile.isPresent()) {
var database = ioResourceToBuffer(databaseFile.get().getPath());
if (database != null) GLFW.glfwUpdateGamepadMappings(database);
}
if (!MidnightControlsClient.MAPPINGS_FILE.exists())
return;
var buffer = ioResourceToBuffer(MidnightControlsClient.MAPPINGS_FILE.getPath(), 1024);
return false;
var buffer = ioResourceToBuffer(MidnightControlsClient.MAPPINGS_FILE.getPath());
if (buffer != null) GLFW.glfwUpdateGamepadMappings(buffer);
} catch (IOException e) {
e.printStackTrace();
e.fillInStackTrace();
}
try (var memoryStack = MemoryStack.stackPush()) {
@@ -176,10 +170,10 @@ public record Controller(int id) implements Nameable {
var string = l == 0L ? "" : MemoryUtil.memUTF8(l);
var client = MinecraftClient.getInstance();
if (client != null) {
client.getToastManager().add(SystemToast.create(client, SystemToast.Type.TUTORIAL_HINT,
client.getToastManager().add(SystemToast.create(client, SystemToast.Type.PERIODIC_NOTIFICATION,
Text.translatable("midnightcontrols.controller.mappings.error"), Text.literal(string)));
MidnightControls.get().log(I18n.translate("midnightcontrols.controller.mappings.error")+string);
}
MidnightControls.log(I18n.translate("midnightcontrols.controller.mappings.error")+string);
}
} catch (Throwable e) {
/* Ignored :concern: */
@@ -192,12 +186,28 @@ public record Controller(int id) implements Nameable {
if (!controller.isConnected())
continue;
MidnightControls.get().log(String.format("Controller #%d name: \"%s\"\n GUID: %s\n Gamepad: %s",
MidnightControls.log(String.format("Controller #%d name: \"%s\"\n GUID: %s\n Gamepad: %s",
controller.id,
controller.getName(),
controller.getGuid(),
controller.isGamepad()));
}
}
return true;
}
private static Optional<File> getDatabaseFile() {
File databaseFile = new File("config/gamecontrollerdatabase.txt");
try {
BufferedInputStream in = new BufferedInputStream(URI.create("https://raw.githubusercontent.com/gabomdq/SDL_GameControllerDB/master/gamecontrollerdb.txt").toURL().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 e) {return Optional.empty();}
return Optional.of(databaseFile);
}
}

View File

@@ -0,0 +1,317 @@
/*
* 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 com.google.common.collect.Lists;
import eu.midnightdust.midnightcontrols.client.enums.ButtonState;
import eu.midnightdust.midnightcontrols.client.MidnightControlsClient;
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.gui.RingScreen;
import eu.midnightdust.midnightcontrols.client.touch.gui.TouchscreenOverlay;
import eu.midnightdust.midnightcontrols.client.mixin.*;
import eu.midnightdust.midnightcontrols.client.util.HandledScreenAccessor;
import eu.midnightdust.midnightcontrols.client.util.InventoryUtil;
import eu.midnightdust.midnightcontrols.client.util.ToggleSneakSprintUtil;
import eu.midnightdust.midnightcontrols.client.util.platform.ItemGroupUtil;
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.TabNavigationWidget;
import net.minecraft.client.util.ScreenshotRecorder;
import net.minecraft.screen.slot.Slot;
import net.minecraft.screen.slot.SlotActionType;
import net.minecraft.util.math.MathHelper;
import org.jetbrains.annotations.NotNull;
import org.lwjgl.glfw.GLFW;
import java.util.Optional;
import static eu.midnightdust.midnightcontrols.client.MidnightControlsClient.client;
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
*/
public class InputHandlers {
private InputHandlers() {
}
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()) {
var inv = client.player.getInventory();
if (next)
inv.setSelectedSlot(inv.getSelectedSlot() < 8 ? inv.getSelectedSlot() + 1 : inv.getSelectedSlot() - 8);
else
inv.setSelectedSlot(inv.getSelectedSlot() > 0 ? inv.getSelectedSlot() - 1 : inv.getSelectedSlot() + 8);
}
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.ring.cyclePage(next);
} else if (client.currentScreen instanceof CreativeInventoryScreenAccessor inventory) {
inventory.midnightcontrols$setSelectedTab(ItemGroupUtil.cycleTab(next, client));
return true;
} else if (client.currentScreen instanceof RecipeBookScreen<?> recipeBookScreen) {
RecipeBookWidget<?> recipeBook = ((RecipeBookScreenAccessor) recipeBookScreen).getRecipeBook();
var recipeBookAccessor = (RecipeBookWidgetAccessor) recipeBook;
var tabs = recipeBookAccessor.getTabButtons();
var currentTab = recipeBookAccessor.getCurrentTab();
if (currentTab == null || !recipeBook.isOpen()) {
return MidnightControlsCompat.handleTabs(client.currentScreen, next);
}
int nextTab = tabs.indexOf(currentTab) + (next ? 1 : -1);
if (nextTab < 0)
nextTab = tabs.size() - 1;
else if (nextTab >= tabs.size())
nextTab = 0;
currentTab.active = false;
recipeBookAccessor.setCurrentTab(currentTab = tabs.get(nextTab));
currentTab.active = true;
recipeBookScreen.refreshRecipeBook();
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().getAdvancementEntry(), true);
break;
}
}
return true;
} else if (client.currentScreen != null && client.currentScreen.children().stream().anyMatch(e -> e instanceof TabNavigationWidget)) {
return Lists.newCopyOnWriteArrayList(client.currentScreen.children()).stream().anyMatch(e -> {
if (e instanceof TabNavigationWidget tabs) {
TabNavigationWidgetAccessor accessor = (TabNavigationWidgetAccessor) tabs;
int tabIndex = accessor.getTabs().indexOf(accessor.getTabManager().getCurrentTab());
if (next ? tabIndex+1 < accessor.getTabs().size() : tabIndex > 0) {
if (next) tabs.selectTab(tabIndex + 1, true);
else tabs.selectTab(tabIndex - 1, true);
return true;
}
}
return false;
});
} else return MidnightControlsCompat.handleTabs(client.currentScreen, next);
return false;
};
}
public static PressAction handlePage(boolean next) {
return (client, button, value, action) -> {
if (action == ButtonState.RELEASE)
return false;
if (client.currentScreen instanceof CreativeInventoryScreen creativeScreen) {
return ItemGroupUtil.cyclePage(next, creativeScreen);
}
return MidnightControlsCompat.handlePages(client.currentScreen, next);
};
}
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.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.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 = accessor.midnightcontrols$getSlotAt(x, y);
int slotId;
if (slot == null) {
if (button.getName().equals("take_all")) {
return false;
}
slotId = accessor.midnightcontrols$isClickOutsideBounds(x, y, accessor.getX(), accessor.getY()) ? -999 : -1;
} else {
slotId = slot.id;
}
var actionType = SlotActionType.PICKUP;
int clickData = GLFW.GLFW_MOUSE_BUTTON_1;
MidnightControlsClient.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;
}
}
case "take" -> {
clickData = GLFW_MOUSE_BUTTON_2;
}
case "quick_move" -> {
actionType = SlotActionType.QUICK_MOVE;
}
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.openGameMenu(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) {
return ToggleSneakSprintUtil.toggleSneak(button);
}
public static boolean handleToggleSprint(@NotNull MinecraftClient client, @NotNull ButtonBinding button, float value, @NotNull ButtonState action) {
return ToggleSneakSprintUtil.toggleSprint(button);
}
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;
Optional<Slot> closestSlot = InventoryUtil.findClosestSlot(inventory, direction);
if (closestSlot.isPresent()) {
var slot = closestSlot.get();
int x = accessor.getX() + slot.x + 8;
int y = accessor.getY() + 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 binding the affected binding
* @return true
*/
public static boolean always(@NotNull ButtonBinding binding) {
return true;
}
/**
* Returns whether the client is in game or not.
*
* @param binding the affected binding
* @return true if the client is in game, else false
*/
public static boolean inGame(@NotNull ButtonBinding binding) {
return (client.currentScreen == null && MidnightControlsClient.input.screenCloseCooldown <= 0) || client.currentScreen instanceof TouchscreenOverlay || client.currentScreen instanceof RingScreen;
}
/**
* Returns whether the client is in a non-interactive screen (which means require mouse input) or not.
*
* @param binding the affected binding
* @return true if the client is in a non-interactive screen, else false
*/
public static boolean inNonInteractiveScreens(@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 binding the affected binding
* @return true if the client is in an inventory, else false
*/
public static boolean inInventory(@NotNull ButtonBinding binding) {
return client.currentScreen instanceof HandledScreen;
}
/**
* Returns whether the client is in the advancements screen or not.
*
* @param binding the affected binding
* @return true if the client is in the advancements screen, else false
*/
public static boolean inAdvancements(@NotNull ButtonBinding binding) {
return client.currentScreen instanceof AdvancementsScreen;
}
}

View File

@@ -10,25 +10,28 @@
package eu.midnightdust.midnightcontrols.client.controller;
import eu.midnightdust.midnightcontrols.ControlsMode;
import eu.midnightdust.midnightcontrols.client.ButtonState;
import eu.midnightdust.midnightcontrols.client.enums.ButtonState;
import eu.midnightdust.midnightcontrols.client.MidnightControlsConfig;
import eu.midnightdust.midnightcontrols.client.util.MouseAccessor;
import eu.midnightdust.midnightcontrols.client.mixin.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.Identifier;
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.function.Predicate;
import java.util.stream.Stream;
import static eu.midnightdust.midnightcontrols.client.MidnightControlsClient.client;
/**
* Represents an input manager for controllers.
*
@@ -38,7 +41,7 @@ import java.util.stream.Stream;
*/
public class InputManager {
public static final InputManager INPUT_MANAGER = new InputManager();
private static final List<ButtonBinding> BINDINGS = new ArrayList<>();
private static final List<ButtonBinding> BINDINGS = Collections.synchronizedList(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();
@@ -50,17 +53,17 @@ public class InputManager {
protected InputManager() {
}
public void tick(@NotNull MinecraftClient client) {
public void tick() {
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);
this.controllerTick();
}
}
public void controllerTick(@NotNull MinecraftClient client) {
public void controllerTick() {
this.prevTargetMouseX = this.targetMouseX;
this.prevTargetMouseY = this.targetMouseY;
}
@@ -73,8 +76,8 @@ public class InputManager {
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;
double mouseX = this.prevTargetMouseX + (this.targetMouseX - this.prevTargetMouseX) * client.getRenderTickCounter().getTickProgress(true) + 0.5;
double mouseY = this.prevTargetMouseY + (this.targetMouseY - this.prevTargetMouseY) * client.getRenderTickCounter().getTickProgress(true) + 0.5;
if (!MidnightControlsConfig.virtualMouse)
GLFW.glfwSetCursorPos(client.getWindow().getHandle(), mouseX, mouseY);
((MouseAccessor) client.mouse).midnightcontrols$onCursorPos(client.getWindow().getHandle(), mouseX, mouseY);
@@ -106,7 +109,9 @@ public class InputManager {
* @return true if the binding is registered, else false
*/
public static boolean hasBinding(@NotNull ButtonBinding binding) {
return BINDINGS.contains(binding);
synchronized (BINDINGS) {
return BINDINGS.contains(binding);
}
}
/**
@@ -116,7 +121,9 @@ public class InputManager {
* @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));
synchronized (BINDINGS) {
return BINDINGS.parallelStream().map(ButtonBinding::getName).anyMatch(binding -> binding.equalsIgnoreCase(name));
}
}
/**
@@ -126,7 +133,34 @@ public class InputManager {
* @return true if the binding is registered, else false
*/
public static boolean hasBinding(@NotNull Identifier identifier) {
return hasBinding(identifier.getNamespace() + "." + identifier.getName());
return hasBinding(identifier.getNamespace() + "." + identifier.getPath());
}
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) {
synchronized (BINDINGS) {
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<>();
synchronized (BINDINGS) {
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;
}
/**
@@ -136,25 +170,29 @@ public class InputManager {
* @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);
synchronized (BINDINGS) {
if (BINDINGS.contains(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));
@Deprecated
public static @NotNull ButtonBinding registerBinding(@NotNull org.aperlambda.lambdacommon.Identifier id, int[] defaultButton, @NotNull List<PressAction> actions, @NotNull Predicate<ButtonBinding> filter, boolean hasCooldown) {
return registerBinding(Identifier.of(id.getNamespace(), id.getName()), defaultButton, actions, filter, hasCooldown);
}
public static @NotNull ButtonBinding registerBinding(@NotNull Identifier id, int[] defaultButton, boolean hasCooldown) {
@Deprecated
public static @NotNull ButtonBinding registerBinding(@NotNull org.aperlambda.lambdacommon.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 Identifier id, int[] defaultButton, @NotNull List<PressAction> actions, @NotNull Predicate<ButtonBinding> filter, boolean hasCooldown) {
return registerBinding(new ButtonBinding(id.getNamespace() + "." + id.getPath(), defaultButton, actions, filter, hasCooldown));
}
public static @NotNull ButtonBinding registerBinding(@NotNull net.minecraft.util.Identifier id, int[] defaultButton, boolean hasCooldown) {
public static @NotNull ButtonBinding registerBinding(@NotNull Identifier id, int[] defaultButton, boolean hasCooldown) {
return registerBinding(id, defaultButton, Collections.emptyList(), InputHandlers::always, hasCooldown);
}
@@ -180,6 +218,13 @@ public class InputManager {
CATEGORIES.add(category);
return category;
}
public static ButtonCategory registerCategory(@NotNull org.aperlambda.lambdacommon.Identifier identifier, int priority) {
return registerCategory(Identifier.of(identifier.getNamespace(), identifier.getName()), priority);
}
public static ButtonCategory registerCategory(@NotNull org.aperlambda.lambdacommon.Identifier identifier) {
return registerCategory(Identifier.of(identifier.getNamespace(), identifier.getName()));
}
public static ButtonCategory registerCategory(@NotNull Identifier identifier, int priority) {
return registerCategory(new ButtonCategory(identifier, priority));
@@ -190,7 +235,7 @@ public class InputManager {
}
protected static ButtonCategory registerDefaultCategory(@NotNull String key, @NotNull Consumer<ButtonCategory> keyAdder) {
var category = registerCategory(new Identifier("minecraft", key), CATEGORIES.size());
var category = registerCategory(Identifier.of("minecraft", key), CATEGORIES.size());
keyAdder.accept(category);
return category;
}
@@ -249,7 +294,9 @@ public class InputManager {
* @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;
synchronized (BINDINGS) {
return BINDINGS.parallelStream().filter(binding -> areButtonsEquivalent(binding.getButton(), button)).count() > 1;
}
}
/**
@@ -259,7 +306,9 @@ public class InputManager {
* @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;
synchronized (BINDINGS) {
return BINDINGS.parallelStream().filter(other -> areButtonsEquivalent(other.getButton(), binding.getButton()) && other.filter.equals(binding.filter)).count() > 1;
}
}
/**
@@ -307,32 +356,34 @@ public class InputManager {
}
}
public static void updateBindings(@NotNull MinecraftClient client) {
public static void updateBindings() {
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;
synchronized (BINDINGS) {
for (var binding : BINDINGS) {
var state = binding.isAvailable() ? getBindingState(binding) : ButtonState.NONE;
if (skipButtons.intStream().anyMatch(btn -> containsButton(binding.getButton(), btn))) {
if (binding.pressed)
if (binding.isPressed())
state = ButtonState.RELEASE;
else
state = ButtonState.NONE;
}
if (state == ButtonState.RELEASE && !binding.pressed) {
if (state == ButtonState.RELEASE && !binding.isPressed()) {
state = ButtonState.NONE;
}
binding.pressed = state.isPressed();
binding.setPressed(state.isPressed());
binding.update();
if (binding.pressed)
if (binding.isPressed())
Arrays.stream(binding.getButton()).forEach(skipButtons::add);
float value = getBindingValue(binding, state);
states.put(binding, new ButtonStateValue(state, value));
}
}
states.forEach((binding, state) -> {
@@ -352,7 +403,9 @@ public class InputManager {
}
public static @NotNull Stream<ButtonBinding> streamBindings() {
return BINDINGS.stream();
synchronized (BINDINGS) {
return BINDINGS.stream();
}
}
public static @NotNull Stream<ButtonCategory> streamCategories() {
@@ -367,23 +420,9 @@ public class InputManager {
* @param code the code
* @param category the category of the key binding
* @return the key binding
* @see #makeKeyBinding(Identifier, InputUtil.Type, int, String)
* @see #makeKeyBinding(Identifier, InputUtil.Type, int, net.minecraft.client.option.KeyBinding.Category)
*/
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);
public static @NotNull KeyBinding makeKeyBinding(@NotNull Identifier id, InputUtil.Type type, int code, @NotNull KeyBinding.Category category) {
return new KeyBinding(String.format("key.%s.%s", id.getNamespace(), id.getPath()), type, code, category);
}
}

View File

@@ -9,11 +9,16 @@
package eu.midnightdust.midnightcontrols.client.controller;
import eu.midnightdust.midnightcontrols.client.ButtonState;
import eu.midnightdust.midnightcontrols.client.MidnightControlsClient;
import eu.midnightdust.midnightcontrols.client.enums.ButtonState;
import eu.midnightdust.midnightcontrols.client.MidnightControlsConfig;
import eu.midnightdust.midnightcontrols.client.mixin.InputAccessor;
import eu.midnightdust.midnightcontrols.client.util.MathUtil;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.network.ClientPlayerEntity;
import net.minecraft.entity.attribute.EntityAttributes;
import net.minecraft.util.PlayerInput;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.Vec2f;
import org.jetbrains.annotations.NotNull;
/**
@@ -30,8 +35,10 @@ public final class MovementHandler implements PressAction {
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 final MathUtil.PolarUtil polarUtil = new MathUtil.PolarUtil();
private MovementHandler() {
}
@@ -44,12 +51,13 @@ public final class MovementHandler implements PressAction {
public void applyMovement(@NotNull ClientPlayerEntity player) {
if (!this.shouldOverrideMovement)
return;
player.input.pressingForward = this.pressingForward;
player.input.pressingBack = this.pressingBack;
player.input.pressingLeft = this.pressingLeft;
player.input.pressingRight = this.pressingRight;
player.input.movementForward = this.movementForward;
player.input.movementSideways = this.movementSideways;
player.input.playerInput = new PlayerInput(this.pressingForward, this.pressingBack, this.pressingLeft, this.pressingRight,
player.input.playerInput.jump(), player.input.playerInput.sneak(), player.input.playerInput.sprint());
polarUtil.calculate(this.movementSideways, this.movementForward, this.slowdownFactor);
Vec2f inputVector = new Vec2f(polarUtil.polarX, polarUtil.polarY);
((InputAccessor)player.input).setMovementVector(inputVector);
this.shouldOverrideMovement = false;
}
@@ -69,28 +77,26 @@ public final class MovementHandler implements PressAction {
this.shouldOverrideMovement = direction != 0;
if (MidnightControlsConfig.analogMovement) {
value = (float) Math.pow(value, 2);
} else value = 1.f;
if (!MidnightControlsConfig.analogMovement) {
value = 1.f;
}
this.slowdownFactor = client.player.shouldSlowDown() ? (MathHelper.clamp(
0.3F + (float) client.player.getAttributeValue(EntityAttributes.SNEAKING_SPEED),
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;
// Slowing down if sneaking.
if (client.player.input.sneaking)
this.movementForward *= 0.3D;
} else {
// Handle sideways movement.
this.pressingLeft = direction > 0;
this.pressingRight = direction < 0;
this.movementSideways = direction * value;
// Slowing down if sneaking.
if (client.player.input.sneaking)
this.movementSideways *= 0.3D;
}
return this.shouldOverrideMovement;

View File

@@ -9,7 +9,7 @@
package eu.midnightdust.midnightcontrols.client.controller;
import eu.midnightdust.midnightcontrols.client.ButtonState;
import eu.midnightdust.midnightcontrols.client.enums.ButtonState;
import eu.midnightdust.midnightcontrols.client.util.KeyBindingAccessor;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.option.StickyKeyBinding;
@@ -28,10 +28,10 @@ public interface PressAction {
if (action == ButtonState.REPEAT || client.currentScreen != null)
return false;
button.asKeyBinding().ifPresent(binding -> {
if (binding instanceof StickyKeyBinding)
binding.setPressed(button.pressed);
if (binding instanceof StickyKeyBinding && binding != client.options.attackKey) // TODO: Properly fix sticky keys so the attack key doesn't need to be a hardcoded exception
binding.setPressed(button.isPressed());
else
((KeyBindingAccessor) binding).midnightcontrols$handlePressState(button.isButtonDown());
((KeyBindingAccessor) binding).midnightcontrols$handlePressState(button.isPressed());
});
return true;
};

View File

@@ -7,7 +7,7 @@
* see the LICENSE file.
*/
package eu.midnightdust.midnightcontrols.client;
package eu.midnightdust.midnightcontrols.client.enums;
/**
* Represents a button state.

View File

@@ -0,0 +1,17 @@
package eu.midnightdust.midnightcontrols.client.enums;
import net.minecraft.text.Text;
import org.jetbrains.annotations.NotNull;
public enum CameraMode {
FLAT, ADAPTIVE;
public Text getTranslatedText() {
return Text.translatable("midnightcontrols.midnightconfig.enum."+this.getClass().getSimpleName()+"."+this.name());
}
public @NotNull CameraMode next() {
var v = values();
if (v.length == this.ordinal() + 1)
return v[0];
return v[this.ordinal() + 1];
}
}

View File

@@ -7,10 +7,9 @@
* see the LICENSE file.
*/
package eu.midnightdust.midnightcontrols.client;
package eu.midnightdust.midnightcontrols.client.enums;
import net.minecraft.text.Text;
import org.aperlambda.lambdacommon.utils.Nameable;
import org.jetbrains.annotations.NotNull;
import java.util.Arrays;
@@ -23,7 +22,7 @@ import java.util.Optional;
* @version 1.4.3
* @since 1.0.0
*/
public enum ControllerType implements Nameable {
public enum ControllerType {
DEFAULT(0),
DUALSHOCK(1),
DUALSENSE(2),
@@ -32,7 +31,8 @@ public enum ControllerType implements Nameable {
XBOX(5),
STEAM_DECK(6),
STEAM_CONTROLLER(7),
OUYA(8);
OUYA(8),
NUMBERED(9);
private final int id;
private final Text text;
@@ -77,7 +77,6 @@ public enum ControllerType implements Nameable {
return this.text;
}
@Override
public @NotNull String getName() {
return this.name().toLowerCase();
}

View File

@@ -7,7 +7,7 @@
* see the LICENSE file.
*/
package eu.midnightdust.midnightcontrols.client;
package eu.midnightdust.midnightcontrols.client.enums;
import net.minecraft.text.Text;
import org.aperlambda.lambdacommon.utils.Nameable;
@@ -23,7 +23,7 @@ import java.util.Optional;
* @version 1.4.0
* @since 1.0.0
*/
public enum HudSide implements Nameable {
public enum HudSide {
LEFT,
RIGHT;
@@ -63,9 +63,8 @@ public enum HudSide implements Nameable {
return this.text;
}
@Override
public @NotNull String getName() {
return this.name().toLowerCase();
return this.name();
}
/**
@@ -74,6 +73,7 @@ public enum HudSide implements Nameable {
* @param id the identifier of the hud side
* @return the hud side if found, else empty
*/
@Deprecated
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,17 @@
package eu.midnightdust.midnightcontrols.client.enums;
import net.minecraft.text.Text;
import org.jetbrains.annotations.NotNull;
public enum TouchMode {
CROSSHAIR, FINGER_POS;
public Text getTranslatedText() {
return Text.translatable("midnightcontrols.midnightconfig.enum."+this.getClass().getSimpleName()+"."+this.name());
}
public @NotNull TouchMode next() {
var v = values();
if (v.length == this.ordinal() + 1)
return v[0];
return v[this.ordinal() + 1];
}
}

View File

@@ -7,10 +7,9 @@
* see the LICENSE file.
*/
package eu.midnightdust.midnightcontrols.client;
package eu.midnightdust.midnightcontrols.client.enums;
import net.minecraft.text.Text;
import org.aperlambda.lambdacommon.utils.Nameable;
import org.jetbrains.annotations.NotNull;
import java.util.Arrays;
@@ -22,7 +21,7 @@ import java.util.Optional;
* @version 1.7.0
* @since 1.2.0
*/
public enum VirtualMouseSkin implements Nameable {
public enum VirtualMouseSkin {
DEFAULT_LIGHT("default_light"),
DEFAULT_DARK("default_dark"),
SECOND_LIGHT("second_light"),
@@ -66,7 +65,6 @@ public enum VirtualMouseSkin implements Nameable {
return this.text;
}
@Override
public @NotNull String getName() {
return this.name;
}
@@ -77,7 +75,16 @@ public enum VirtualMouseSkin implements Nameable {
* @param id the identifier of the virtual mouse skin
* @return the virtual mouse skin if found, else empty
*/
@Deprecated
public static @NotNull Optional<VirtualMouseSkin> byId(@NotNull String id) {
return Arrays.stream(values()).filter(mode -> mode.getName().equalsIgnoreCase(id)).findFirst();
}
public String getSpritePath() {
return switch (this) {
case DEFAULT_LIGHT -> "cursor/light/default";
case DEFAULT_DARK -> "cursor/dark/default";
case SECOND_LIGHT -> "cursor/light/secondary";
case SECOND_DARK -> "cursor/dark/secondary";
};
}
}

View File

@@ -9,12 +9,16 @@
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.option.SpruceSimpleActionOption;
import dev.lambdaurora.spruceui.render.SpruceGuiGraphics;
import dev.lambdaurora.spruceui.widget.container.SpruceContainerWidget;
import dev.lambdaurora.spruceui.widget.text.SpruceTextAreaWidget;
import eu.midnightdust.midnightcontrols.client.MidnightControlsClient;
import eu.midnightdust.midnightcontrols.client.MidnightControlsConfig;
import eu.midnightdust.midnightcontrols.client.controller.Controller;
import net.minecraft.client.gui.DrawContext;
import net.minecraft.client.toast.SystemToast;
import net.minecraft.text.Text;
@@ -30,17 +34,18 @@ import java.nio.file.Files;
* @since 1.4.3
*/
public class MappingsStringInputWidget extends SpruceContainerWidget {
private final SpruceOption copyGuidOption;
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.copyGuidOption = SpruceSimpleActionOption.of("midnightcontrols.menu.copy_controller_guid", button -> client.keyboard.setClipboard(MidnightControlsConfig.getController().getGuid()));
this.init();
}
@@ -63,9 +68,9 @@ public class MappingsStringInputWidget extends SpruceContainerWidget {
fw.close();
} catch (IOException e) {
if (this.client != null)
this.client.getToastManager().add(SystemToast.create(this.client, SystemToast.Type.TUTORIAL_HINT,
this.client.getToastManager().add(SystemToast.create(this.client, SystemToast.Type.PERIODIC_NOTIFICATION,
Text.translatable("midnightcontrols.controller.mappings.error.write"), Text.empty()));
e.printStackTrace();
e.fillInStackTrace();
}
}
}
@@ -95,10 +100,14 @@ public class MappingsStringInputWidget extends SpruceContainerWidget {
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));
this.addChild(this.reloadMappingsOption.createWidget(Position.of(this.width / 2 - 155, this.height - 29), 257));
this.addChild(this.copyGuidOption.createWidget(Position.of(this.width / 2 + 105, this.height - 29), 65));
}
/*public void renderTitle(MatrixStack matrices, int mouseX, int mouseY, float delta) {
drawCenteredText(matrices, this.textRenderer, this.title, this.width / 2, 8, 16777215);
}*/
@Override
public void renderWidget(SpruceGuiGraphics context, int mouseX, int mouseY, float delta) {
super.renderWidget(context, mouseX, mouseY, delta);
context.vanilla().drawCenteredTextWithShadow(this.client.textRenderer, Text.translatable("midnightcontrols.menu.multiple_mapping_tip"), this.textArea.getX() + this.textArea.getWidth() / 2, this.textArea.getY() + this.textArea.getHeight() - 12, 0xFF888888);
context.vanilla().drawCenteredTextWithShadow(this.client.textRenderer, Text.translatable("midnightcontrols.menu.current_controller_guid", MidnightControlsConfig.getController().getGuid()), this.textArea.getX() + this.textArea.getWidth() / 2, this.height - 21, 0xFFFFFFFF);
}
}

View File

@@ -10,20 +10,18 @@
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.enums.HudSide;
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 dev.lambdaurora.spruceui.hud.Hud;
import net.minecraft.client.render.RenderTickCounter;
import org.joml.Matrix3x2fStack;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gui.DrawContext;
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;
@@ -36,9 +34,8 @@ import org.jetbrains.annotations.Nullable;
* @version 1.7.0
* @since 1.0.0
*/
public class MidnightControlsHud extends Hud {
private final MidnightControlsClient mod;
private MinecraftClient client;
public class MidnightControlsHud {
private MinecraftClient client = MinecraftClient.getInstance();
private int attackWidth = 0;
private int attackButtonWidth = 0;
private int dropItemWidth = 0;
@@ -47,23 +44,22 @@ public class MidnightControlsHud extends Hud {
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 float scale = 1f;
private static boolean isCrammed = false;
public static boolean isVisible = false;
private static final MidnightControlsHud INSTANCE = new MidnightControlsHud();
public MidnightControlsHud(@NotNull MidnightControlsClient mod) {
super(new Identifier(MidnightControlsConstants.NAMESPACE, "hud/button_indicator"));
this.mod = mod;
public static MidnightControlsHud getInstance() {
return INSTANCE;
}
@Override
public void init(@NotNull MinecraftClient client, int screenWidth, int screenHeight) {
super.init(client, screenWidth, screenHeight);
this.client = client;
public void init() {
this.client = MidnightControlsClient.client;
this.inventoryWidth = this.width(ButtonBinding.INVENTORY);
this.inventoryButtonWidth = MidnightControlsRenderer.getBindingIconWidth(ButtonBinding.INVENTORY);
this.swapHandsWidth = this.width(ButtonBinding.SWAP_HANDS);
@@ -72,118 +68,131 @@ public class MidnightControlsHud extends Hud {
this.dropItemButtonWidth = MidnightControlsRenderer.getBindingIconWidth(ButtonBinding.DROP_ITEM);
this.attackButtonWidth = MidnightControlsRenderer.getBindingIconWidth(ButtonBinding.ATTACK);
this.useButtonWidth = MidnightControlsRenderer.getBindingIconWidth(ButtonBinding.USE);
if (client.options.getGuiScale().getValue() >= 4) {
scale = 0.75f;
} else scale = 1f;
}
/**
* Renders the midnightcontrols' HUD.
* Renders the MidnightControls HUD.
*/
@Override
public void render(MatrixStack matrices, float tickDelta) {
if (this.client == null) return;
public void render(DrawContext context, RenderTickCounter tickCounter) {
if (this.client == null || !isVisible) return;
if (MidnightControlsConfig.controlsMode == ControlsMode.CONTROLLER && this.client.currentScreen == null) {
isCrammed = client.getWindow().getScaledWidth() < 520;
int y = bottom(2);
if (scale != 1f) matrices.scale(scale,scale,scale);
this.renderFirstIcons(matrices, MidnightControlsConfig.hudSide == HudSide.LEFT ? 2 : (int) ((client.getWindow().getScaledWidth() - 2) * (1 / scale)), y);
this.renderSecondIcons(matrices, MidnightControlsConfig.hudSide == HudSide.RIGHT ? 2 : (int) ((client.getWindow().getScaledWidth() - 2) * (1 / scale)), y);
this.renderFirstSection(matrices, MidnightControlsConfig.hudSide == HudSide.LEFT ? 2 : (int) ((client.getWindow().getScaledWidth() - 2) * (1 / scale)), y);
this.renderSecondSection(matrices, MidnightControlsConfig.hudSide == HudSide.RIGHT ? 2 : (int) ((client.getWindow().getScaledWidth() - 2) * (1 / scale)), y);
if (scale != 1f) matrices.scale(1,1,1);
Matrix3x2fStack matrices = context.getMatrices();
matrices.pushMatrix();
this.renderFirstIcons(context, MidnightControlsConfig.hudSide == HudSide.LEFT ? 2 : client.getWindow().getScaledWidth() - 2, y);
this.renderSecondIcons(context, MidnightControlsConfig.hudSide == HudSide.RIGHT ? 2 : client.getWindow().getScaledWidth() - 2, y);
this.renderFirstSection(context, MidnightControlsConfig.hudSide == HudSide.LEFT ? 2 : client.getWindow().getScaledWidth() - 2, y);
this.renderSecondSection(context, MidnightControlsConfig.hudSide == HudSide.RIGHT ? 2 : client.getWindow().getScaledWidth() - 2, y);
matrices.popMatrix();
}
if (this.mod.reacharound.isLastReacharoundVertical()) {
if (MidnightControlsClient.reacharound.isLastReacharoundVertical()) {
// Render crosshair indicator.
var window = this.client.getWindow();
var text = "[ ]";
float scale = Math.min(5, this.ticksDisplayedCrosshair + tickDelta) / 5F;
float scale = Math.min(5, this.ticksDisplayedCrosshair + tickCounter.getTickProgress(true)) / 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);
context.drawText(client.textRenderer, text, (int) (window.getScaledWidth() / 2.f - this.client.textRenderer.getWidth(text) / 2.f),
(int) (window.getScaledHeight() / 2.f - 4), 0xFFCCCCCC | opacity, false);
}
}
public void renderFirstIcons(MatrixStack matrices, int x, int y) {
public void renderFirstIcons(DrawContext context, int x, int y) {
int offset = 2 + this.inventoryWidth + this.inventoryButtonWidth + 4;
int currentX = MidnightControlsConfig.hudSide == HudSide.LEFT ? x : x - this.inventoryButtonWidth;
this.drawButton(matrices, currentX, y, ButtonBinding.INVENTORY, true);
this.drawButton(matrices, currentX += (MidnightControlsConfig.hudSide == HudSide.LEFT ? offset : -offset), y, ButtonBinding.SWAP_HANDS, true);
if (!ButtonBinding.INVENTORY.isNotBound()) this.drawButton(context, currentX, y, ButtonBinding.INVENTORY, true);
if (!ButtonBinding.SWAP_HANDS.isNotBound() && !isCrammed && showSwapHandsAction) this.drawButton(context, 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 -= 24;
y -= 20;
}
this.drawButton(matrices, currentX, y, ButtonBinding.DROP_ITEM, !this.client.player.getMainHandStack().isEmpty());
if (!ButtonBinding.DROP_ITEM.isNotBound() && client.player != null)
this.drawButton(context, currentX, y, ButtonBinding.DROP_ITEM, !this.client.player.getMainHandStack().isEmpty());
}
public void renderSecondIcons(MatrixStack matrices, int x, int y) {
public void renderSecondIcons(DrawContext context, int x, int y) {
int offset;
int currentX = x;
if (!this.placeAction.isEmpty()) {
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.USE, true);
this.drawButton(context, 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(context, 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 -= 24;
y -= 20;
}
}
if (MidnightControlsConfig.hudSide == HudSide.LEFT)
currentX -= this.attackButtonWidth;
this.drawButton(matrices, currentX, y, ButtonBinding.ATTACK, this.attackWidth != 0);
if (!ButtonBinding.ATTACK.isNotBound()) this.drawButton(context, currentX, y, ButtonBinding.ATTACK, this.attackWidth != 0);
}
public void renderFirstSection(MatrixStack matrices, int x, int y) {
public void renderFirstSection(DrawContext context, int x, int y) {
int currentX = MidnightControlsConfig.hudSide == HudSide.LEFT ? x + this.inventoryButtonWidth + 2 : x - this.inventoryButtonWidth - 2 - this.inventoryWidth;
this.drawTip(matrices, currentX, y, ButtonBinding.INVENTORY, true);
if (!ButtonBinding.INVENTORY.isNotBound()) this.drawTip(context, currentX, y, ButtonBinding.INVENTORY, true);
currentX += MidnightControlsConfig.hudSide == HudSide.LEFT ? this.inventoryWidth + 4 + this.swapHandsButtonWidth + 2
: -this.swapHandsWidth - 2 - this.swapHandsButtonWidth - 4;
this.drawTip(matrices, currentX, y, ButtonBinding.SWAP_HANDS, true);
if (!ButtonBinding.SWAP_HANDS.isNotBound() && !isCrammed && showSwapHandsAction) this.drawTip(context, 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 -= 24;
y -= 20;
currentX = MidnightControlsConfig.hudSide == HudSide.LEFT ? x + this.dropItemButtonWidth + 2 : x - this.dropItemButtonWidth - 2 - this.dropItemWidth;
}
this.drawTip(matrices, currentX, y, ButtonBinding.DROP_ITEM, !this.client.player.getMainHandStack().isEmpty());
if (!ButtonBinding.DROP_ITEM.isNotBound() && client.player != null) this.drawTip(context, currentX, y, ButtonBinding.DROP_ITEM, !this.client.player.getMainHandStack().isEmpty());
}
public void renderSecondSection(MatrixStack matrices, int x, int y) {
public void renderSecondSection(DrawContext context, 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(context, 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);
this.drawTip(context, currentX, y, this.placeAction, true);
if (this.client.options.getShowSubtitles().getValue() && MidnightControlsConfig.hudSide == HudSide.LEFT) {
currentX -= 4;
} else {
currentX = x;
y -= 24;
y -= 20;
}
}
currentX += MidnightControlsConfig.hudSide == HudSide.RIGHT ? this.attackButtonWidth + 2 : -this.attackButtonWidth - 2 - this.attackWidth;
this.drawTip(matrices, currentX, y, this.attackAction, this.attackWidth != 0);
if (!ButtonBinding.ATTACK.isNotBound()) this.drawTip(context, currentX, y, this.attackAction, this.attackWidth != 0);
}
@Override
public void tick() {
super.tick();
if (this.client == null) return;
if (MidnightControlsConfig.controlsMode == ControlsMode.CONTROLLER) {
if (this.client.crosshairTarget == null)
return;
@@ -191,28 +200,29 @@ public class MidnightControlsHud extends Hud {
String placeAction;
// Update "Use" tip status.
BlockHitResult placeHitResult;
if (this.client.crosshairTarget.getType() == HitResult.Type.MISS) {
this.placeHitResult = this.mod.reacharound.getLastReacharoundResult();
placeHitResult = MidnightControlsClient.reacharound.getLastReacharoundResult();
this.attackAction = "";
this.attackWidth = 0;
} else {
if (this.client.crosshairTarget.getType() == HitResult.Type.BLOCK)
this.placeHitResult = (BlockHitResult) this.client.crosshairTarget;
placeHitResult = (BlockHitResult) this.client.crosshairTarget;
else
this.placeHitResult = null;
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 (MidnightControlsClient.reacharound.isLastReacharoundVertical()) {
if (this.ticksDisplayedCrosshair < 5)
this.ticksDisplayedCrosshair++;
} else {
this.ticksDisplayedCrosshair = 0;
}
var customAttackAction = MidnightControlsCompat.getAttackActionAt(this.client, this.placeHitResult);
var customAttackAction = MidnightControlsCompat.getAttackActionAt(this.client, placeHitResult);
if (customAttackAction != null) {
this.attackAction = customAttackAction;
this.attackWidth = this.width(customAttackAction);
@@ -227,18 +237,19 @@ public class MidnightControlsHud extends Hud {
if (stack == null || stack.isEmpty()) {
placeAction = "";
} else {
if (this.placeHitResult != null && stack.getItem() instanceof BlockItem) {
if (placeHitResult != null && stack.getItem() instanceof BlockItem) {
placeAction = "midnightcontrols.action.place";
} else {
placeAction = ButtonBinding.USE.getTranslationKey();
}
}
var customUseAction = MidnightControlsCompat.getUseActionAt(this.client, this.placeHitResult);
var customUseAction = MidnightControlsCompat.getUseActionAt(this.client, placeHitResult);
if (customUseAction != null)
placeAction = customUseAction;
this.placeAction = placeAction;
this.showSwapHandsAction = this.client.player != null && (!this.client.player.getMainHandStack().isEmpty() || !this.client.player.getOffHandStack().isEmpty());
// Cache the "Use" tip width.
if (this.placeAction.isEmpty())
@@ -248,13 +259,8 @@ public class MidnightControlsHud extends Hud {
}
}
@Override
public boolean hasTicks() {
return true;
}
private int bottom(int y) {
return (int) ((this.client.getWindow().getScaledHeight() * (1/scale)) - y - MidnightControlsRenderer.ICON_SIZE);
return (this.client.getWindow().getScaledHeight() - y - MidnightControlsRenderer.ICON_SIZE);
}
private int width(@NotNull ButtonBinding binding) {
@@ -267,20 +273,20 @@ public class MidnightControlsHud extends Hud {
return this.client.textRenderer.getWidth(I18n.translate(text));
}
private void drawButton(MatrixStack matrices, int x, int y, @NotNull ButtonBinding button, boolean display) {
private void drawButton(DrawContext context, int x, int y, @NotNull ButtonBinding button, boolean display) {
if (display)
MidnightControlsRenderer.drawButton(matrices, x, y, button, this.client);
MidnightControlsRenderer.drawButton(context, 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(DrawContext context, int x, int y, @NotNull ButtonBinding button, boolean display) {
this.drawTip(context, x, y, button.getTranslationKey(), display);
}
private void drawTip(MatrixStack matrices, int x, int y, @NotNull String action, boolean display) {
private void drawTip(DrawContext context, 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);
context.drawText(this.client.textRenderer, translatedAction, x, (y + textY), 0xFFFFFFFF, false);
}
}

View File

@@ -9,21 +9,15 @@
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.enums.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.gl.RenderPipelines;
import net.minecraft.client.gui.DrawContext;
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;
@@ -77,17 +71,17 @@ public class MidnightControlsRenderer {
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(DrawContext context, int x, int y, @NotNull ButtonBinding button, @NotNull MinecraftClient client) {
return drawButton(context, x, y, button.getButton(), client);
}
public static ButtonSize drawButton(MatrixStack matrices, int x, int y, int[] buttons, @NotNull MinecraftClient client) {
public static ButtonSize drawButton(DrawContext context, 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);
int size = drawButton(context, currentX, y, btn, client);
if (size > height)
height = size;
length += size;
@@ -99,7 +93,7 @@ public class MidnightControlsRenderer {
return new ButtonSize(length, height);
}
public static int drawButton(MatrixStack matrices, int x, int y, int button, @NotNull MinecraftClient client) {
public static int drawButton(DrawContext context, int x, int y, int button, @NotNull MinecraftClient client) {
boolean second = false;
if (button == -1)
return 0;
@@ -159,33 +153,31 @@ public class MidnightControlsRenderer {
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),
//RenderSystem.setShaderColor(1.f, second ? 0.f : 1.f, 1.f, 1.f);
context.drawTexture(RenderPipelines.GUI_TEXTURED, axis ? MidnightControlsClient.CONTROLLER_AXIS : button >= 15 && button <= 19 ? MidnightControlsClient.CONTROLLER_EXPANDED :MidnightControlsClient.CONTROLLER_BUTTONS
, 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(DrawContext context, int x, int y, @NotNull ButtonBinding button, boolean display, @NotNull MinecraftClient client) {
return drawButtonTip(context, 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) {
public static int drawButtonTip(DrawContext context, 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();
int buttonWidth = drawButton(context, 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);
context.drawTextWithShadow(client.textRenderer, translatedAction, (x + buttonWidth + 2), (y + textY), 0xFFFFFFFF);
return (x + buttonWidth + 2) + client.textRenderer.getWidth(translatedAction);
}
return -10;
@@ -195,67 +187,6 @@ public class MidnightControlsRenderer {
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

@@ -9,47 +9,46 @@
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.background.Background;
import dev.lambdaurora.spruceui.option.*;
import dev.lambdaurora.spruceui.render.SpruceGuiGraphics;
import dev.lambdaurora.spruceui.screen.SpruceScreen;
import dev.lambdaurora.spruceui.tooltip.TooltipData;
import dev.lambdaurora.spruceui.widget.AbstractSpruceWidget;
import dev.lambdaurora.spruceui.widget.SpruceLabelWidget;
import dev.lambdaurora.spruceui.widget.SpruceWidget;
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 eu.midnightdust.midnightcontrols.MidnightControlsConstants;
import eu.midnightdust.midnightcontrols.client.MidnightControlsClient;
import eu.midnightdust.midnightcontrols.client.util.platform.NetworkUtil;
import eu.midnightdust.midnightcontrols.client.virtualkeyboard.KeyboardLayoutManager;
import net.minecraft.util.math.ColorHelper;
import eu.midnightdust.midnightcontrols.MidnightControls;
import eu.midnightdust.midnightcontrols.client.MidnightControlsConfig;
import eu.midnightdust.midnightcontrols.client.controller.Controller;
import eu.midnightdust.midnightcontrols.client.gui.widget.ControllerControlsWidget;
import eu.midnightdust.midnightcontrols.packet.ControlsModePayload;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gui.DrawContext;
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 net.minecraft.util.math.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;
@@ -57,12 +56,18 @@ public class MidnightControlsSettingsScreen extends SpruceScreen {
private final SpruceOption rotationSpeedOption;
private final SpruceOption yAxisRotationSpeedOption;
private final SpruceOption mouseSpeedOption;
private final SpruceOption joystickAsMouseOption;
private final SpruceOption eyeTrackingAsMouseOption;
private final SpruceOption eyeTrackingDeadzone;
private final SpruceOption virtualMouseOption;
private final SpruceOption virtualKeyboardOption;
private final SpruceOption hideCursorOption;
private final SpruceOption resetOption;
// 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;
@@ -73,6 +78,7 @@ public class MidnightControlsSettingsScreen extends SpruceScreen {
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",
@@ -83,6 +89,7 @@ public class MidnightControlsSettingsScreen extends SpruceScreen {
id = GLFW.GLFW_JOYSTICK_1;
id = searchNextAvailableController(id, false);
MidnightControlsConfig.setController(Controller.byId(id));
if (MidnightControlsConfig.debug) System.out.println(Controller.byId(id).getName() + "'s Controller GUID: " + Controller.byId(id).getGuid());
},
option -> {
var controller = MidnightControlsConfig.getController();
@@ -93,7 +100,7 @@ public class MidnightControlsSettingsScreen extends SpruceScreen {
return option.getDisplayText(Text.literal(controllerName).formatted(Formatting.GOLD));
else
return option.getDisplayText(Text.literal(controllerName));
}, null);
}, TooltipData.EMPTY);
private final SpruceOption secondControllerOption = new SpruceCyclingOption("midnightcontrols.menu.controller2",
amount -> {
int id = MidnightControlsConfig.getSecondController().map(Controller::id).orElse(-1);
@@ -112,10 +119,12 @@ public class MidnightControlsSettingsScreen extends SpruceScreen {
else
return option.getDisplayText(Text.literal(controllerName));
}).orElse(option.getDisplayText(SpruceTexts.OPTIONS_OFF.copyContentOnly().formatted(Formatting.RED))),
Text.translatable("midnightcontrols.tooltip.controller2"));
TooltipData.builder().text(Text.translatable("midnightcontrols.menu.controller2.tooltip")).build());
private final SpruceOption unfocusedInputOption;
private final SpruceOption invertsRightXAxis;
private final SpruceOption invertsRightYAxis;
private final SpruceOption cameraModeOption;
private final SpruceOption toggleControllerProfileOption;
private final SpruceOption rightDeadZoneOption;
private final SpruceOption leftDeadZoneOption;
private final SpruceOption[] maxAnalogValueOptions = new SpruceOption[]{
@@ -124,24 +133,42 @@ public class MidnightControlsSettingsScreen extends SpruceScreen {
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)
};
// Controller options
public final static SpruceOption virtualKeyboardLayoutOption =
new SpruceCyclingOption("midnightcontrols.menu.virtual_keyboard_layout",
amount -> {
MidnightControlsConfig.keyboardLayout = KeyboardLayoutManager.getNext(KeyboardLayoutManager.getById(MidnightControlsConfig.keyboardLayout)).getId();
},
option -> {
return option.getDisplayText(Text.translatable(KeyboardLayoutManager.getById(MidnightControlsConfig.keyboardLayout).getTranslationKey()));
}, TooltipData.EMPTY);
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"))
TooltipData.builder().text(Text.translatable(key.concat(".tooltip"))).build()
);
}
// Touch options
private final SpruceOption touchWithControllerOption;
private final SpruceOption touchSpeedOption;
private final SpruceOption touchBreakDelayOption;
private final SpruceOption invertTouchOption;
private final SpruceOption touchTransparencyOption;
private final SpruceOption touchModeOption;
private final MutableText controllerMappingsUrlText = Text.literal("(")
.append(Text.literal(GAMEPAD_TOOL_URL).formatted(Formatting.GOLD))
.append("),");
private static int searchNextAvailableController(int newId, boolean allowNone) {
public static int searchNextAvailableController(int newId, boolean allowNone) {
if ((allowNone && newId == -1) || newId == 0) return newId;
boolean connected = Controller.byId(newId).isConnected();
Controller candidate = Controller.byId(newId);
boolean connected = candidate.isConnected();
if (MidnightControlsConfig.excludedControllers.stream().anyMatch(exclusion -> candidate.getName().matches(exclusion))) connected = false;
if (!connected) {
newId++;
}
@@ -163,88 +190,140 @@ public class MidnightControlsSettingsScreen extends SpruceScreen {
MidnightControlsConfig.controlsMode = next;
MidnightControlsConfig.save();
if (this.client.player != null) {
ClientPlayNetworking.getSender().sendPacket(MidnightControls.CONTROLS_MODE_CHANNEL, this.mod.makeControlsModeBuffer(next));
if (this.client != null && this.client.player != null) {
NetworkUtil.sendPayloadC2S(new ControlsModePayload(next.getName()));
}
}, option -> option.getDisplayText(Text.translatable(MidnightControlsConfig.controlsMode.getTranslationKey())),
Text.translatable("midnightcontrols.tooltip.controls_mode"));
TooltipData.builder().text(Text.translatable("midnightcontrols.menu.controls_mode.tooltip")).build());
this.autoSwitchModeOption = new SpruceToggleBooleanOption("midnightcontrols.menu.auto_switch_mode", () -> MidnightControlsConfig.autoSwitchMode,
value -> MidnightControlsConfig.autoSwitchMode = value, Text.translatable("midnightcontrols.tooltip.auto_switch_mode"));
value -> MidnightControlsConfig.autoSwitchMode = value, TooltipData.builder().text(Text.translatable("midnightcontrols.menu.auto_switch_mode.tooltip")).build());
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"));
TooltipData.builder().text(Text.translatable("midnightcontrols.menu.rotation_speed.tooltip")).build());
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"));
TooltipData.builder().text(Text.translatable("midnightcontrols.menu.y_axis_rotation_speed.tooltip")).build());
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.mouse_speed"));
TooltipData.builder().text(Text.translatable("midnightcontrols.menu.mouse_speed.tooltip")).build());
this.joystickAsMouseOption = new SpruceToggleBooleanOption("midnightcontrols.menu.joystick_as_mouse",
() -> MidnightControlsConfig.joystickAsMouse, value -> MidnightControlsConfig.joystickAsMouse = value,
TooltipData.builder().text(Text.translatable("midnightcontrols.menu.joystick_as_mouse.tooltip")).build());
this.eyeTrackingAsMouseOption = new SpruceToggleBooleanOption("midnightcontrols.menu.eye_tracker_as_mouse",
() -> MidnightControlsConfig.eyeTrackerAsMouse, value -> MidnightControlsConfig.eyeTrackerAsMouse = value,
TooltipData.builder().text(Text.translatable("midnightcontrols.menu.eye_tracker_as_mouse.tooltip")).build());
this.eyeTrackingDeadzone = new SpruceDoubleInputOption("midnightcontrols.menu.eye_tracker_deadzone",
() -> MidnightControlsConfig.eyeTrackerDeadzone, value -> MidnightControlsConfig.eyeTrackerDeadzone = value,
TooltipData.builder().text(Text.translatable("midnightcontrols.menu.eye_tracker_deadzone.tooltip")).build());
this.resetOption = SpruceSimpleActionOption.reset(btn -> {
// TODO
MidnightControlsConfig.reset();
var client = MinecraftClient.getInstance();
this.init(client, client.getWindow().getScaledWidth(), client.getWindow().getScaledHeight());
this.init(client.getWindow().getScaledWidth(), client.getWindow().getScaledHeight());
});
// 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"));
TooltipData.builder().text(Text.translatable("midnightcontrols.menu.analog_movement.tooltip")).build());
this.autoJumpOption = new SpruceToggleBooleanOption("options.autoJump",
() -> this.client.options.getAutoJump().getValue(),
newValue -> this.client.options.getAutoJump().setValue(newValue),
null);
TooltipData.EMPTY);
this.controllerToggleSneakOption = new SpruceToggleBooleanOption("midnightcontrols.menu.controller_toggle_sneak",
() -> MidnightControlsConfig.controllerToggleSneak, value -> MidnightControlsConfig.controllerToggleSneak = value,
TooltipData.EMPTY);
this.controllerToggleSprintOption = new SpruceToggleBooleanOption("midnightcontrols.menu.controller_toggle_sprint",
() -> MidnightControlsConfig.controllerToggleSprint, value -> MidnightControlsConfig.controllerToggleSprint = value,
TooltipData.EMPTY);
this.fastBlockPlacingOption = new SpruceToggleBooleanOption("midnightcontrols.menu.fast_block_placing", () -> MidnightControlsConfig.fastBlockPlacing,
value -> MidnightControlsConfig.fastBlockPlacing = value, Text.translatable("midnightcontrols.tooltip.fast_block_placing"));
value -> MidnightControlsConfig.fastBlockPlacing = value, TooltipData.builder().text(Text.translatable("midnightcontrols.menu.fast_block_placing.tooltip")).build());
this.frontBlockPlacingOption = new SpruceToggleBooleanOption("midnightcontrols.menu.reacharound.horizontal", () -> MidnightControlsConfig.horizontalReacharound,
value -> MidnightControlsConfig.horizontalReacharound = value, Text.translatable("midnightcontrols.tooltip.reacharound.horizontal"));
value -> MidnightControlsConfig.horizontalReacharound = value, TooltipData.builder().text(Text.translatable("midnightcontrols.menu.reacharound.horizontal.tooltip")).build());
this.verticalReacharoundOption = new SpruceToggleBooleanOption("midnightcontrols.menu.reacharound.vertical", () -> MidnightControlsConfig.verticalReacharound,
value -> MidnightControlsConfig.verticalReacharound = value, Text.translatable("midnightcontrols.tooltip.reacharound.vertical"));
value -> MidnightControlsConfig.verticalReacharound = value, TooltipData.builder().text(Text.translatable("midnightcontrols.menu.reacharound.vertical.tooltip")).build());
this.flyDriftingOption = new SpruceToggleBooleanOption("midnightcontrols.menu.fly_drifting", () -> MidnightControlsConfig.flyDrifting,
value -> MidnightControlsConfig.flyDrifting = value, Text.translatable("midnightcontrols.tooltip.fly_drifting"));
value -> MidnightControlsConfig.flyDrifting = value, TooltipData.builder().text(Text.translatable("midnightcontrols.menu.fly_drifting.tooltip")).build());
this.flyVerticalDriftingOption = new SpruceToggleBooleanOption("midnightcontrols.menu.fly_drifting_vertical", () -> MidnightControlsConfig.verticalFlyDrifting,
value -> MidnightControlsConfig.verticalFlyDrifting = value, Text.translatable("midnightcontrols.tooltip.fly_drifting_vertical"));
value -> MidnightControlsConfig.verticalFlyDrifting = value, TooltipData.builder().text(Text.translatable("midnightcontrols.menu.fly_drifting_vertical.tooltip")).build());
// 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"));
TooltipData.builder().text(Text.translatable("midnightcontrols.menu.controller_type.tooltip")).build());
this.virtualMouseSkinOption = new SpruceCyclingOption("midnightcontrols.menu.virtual_mouse.skin",
amount -> MidnightControlsConfig.virtualMouseSkin = MidnightControlsConfig.virtualMouseSkin.next(),
option -> option.getDisplayText(MidnightControlsConfig.virtualMouseSkin.getTranslatedText()),
null);
TooltipData.EMPTY);
this.hudEnableOption = new SpruceToggleBooleanOption("midnightcontrols.menu.hud_enable", () -> MidnightControlsConfig.hudEnable,
this.mod::setHudEnabled, Text.translatable("midnightcontrols.tooltip.hud_enable"));
MidnightControlsClient::setHudEnabled, TooltipData.builder().text(Text.translatable("midnightcontrols.menu.hud_enable.tooltip")).build());
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"));
TooltipData.builder().text(Text.translatable("midnightcontrols.menu.hud_side.tooltip")).build());
this.moveChatOption = new SpruceToggleBooleanOption("midnightcontrols.menu.move_chat", () -> MidnightControlsConfig.moveChat,
value -> MidnightControlsConfig.moveChat = value, TooltipData.builder().text(Text.translatable("midnightcontrols.menu.move_chat.tooltip")).build());
// 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());
}
}, TooltipData.EMPTY);
this.cameraModeOption = new SpruceCyclingOption("midnightcontrols.menu.camera_mode",
amount -> MidnightControlsConfig.cameraMode = MidnightControlsConfig.cameraMode.next(),
option -> option.getDisplayText(MidnightControlsConfig.cameraMode.getTranslatedText()),
TooltipData.builder().text(Text.translatable("midnightcontrols.menu.camera_mode.tooltip")).build());
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"));
}, TooltipData.builder().text(Text.translatable("midnightcontrols.menu.right_dead_zone.tooltip")).build());
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"));
}, TooltipData.builder().text(Text.translatable("midnightcontrols.menu.left_dead_zone.tooltip")).build());
this.invertsRightXAxis = new SpruceToggleBooleanOption("midnightcontrols.menu.invert_right_x_axis", () -> MidnightControlsConfig.invertRightXAxis,
value -> MidnightControlsConfig.invertRightXAxis = value, null);
value -> MidnightControlsConfig.invertRightXAxis = value, TooltipData.EMPTY);
this.invertsRightYAxis = new SpruceToggleBooleanOption("midnightcontrols.menu.invert_right_y_axis", () -> MidnightControlsConfig.invertRightYAxis,
value -> MidnightControlsConfig.invertRightYAxis = value, null);
value -> MidnightControlsConfig.invertRightYAxis = value, TooltipData.EMPTY);
this.unfocusedInputOption = new SpruceToggleBooleanOption("midnightcontrols.menu.unfocused_input", () -> MidnightControlsConfig.unfocusedInput,
value -> MidnightControlsConfig.unfocusedInput = value, Text.translatable("midnightcontrols.tooltip.unfocused_input"));
value -> MidnightControlsConfig.unfocusedInput = value, TooltipData.builder().text(Text.translatable("midnightcontrols.menu.unfocused_input.tooltip")).build());
this.virtualMouseOption = new SpruceToggleBooleanOption("midnightcontrols.menu.virtual_mouse", () -> MidnightControlsConfig.virtualMouse,
value -> MidnightControlsConfig.virtualMouse = value, Text.translatable("midnightcontrols.tooltip.virtual_mouse"));
value -> MidnightControlsConfig.virtualMouse = value, TooltipData.builder().text(Text.translatable("midnightcontrols.menu.virtual_mouse.tooltip")).build());
this.virtualKeyboardOption = new SpruceToggleBooleanOption("midnightcontrols.menu.virtual_keyboard", () -> MidnightControlsConfig.virtualMouse,
value -> MidnightControlsConfig.virtualKeyboard = value, TooltipData.builder().text(Text.translatable("midnightcontrols.menu.virtual_keyboard.tooltip")).build());
this.hideCursorOption = new SpruceToggleBooleanOption("midnightcontrols.menu.hide_cursor", () -> MidnightControlsConfig.hideNormalMouse,
value -> MidnightControlsConfig.hideNormalMouse = value, TooltipData.builder().text(Text.translatable("midnightcontrols.menu.hide_cursor.tooltip")).build());
// Touch options
this.touchModeOption = new SpruceCyclingOption("midnightcontrols.menu.touch_mode",
amount -> MidnightControlsConfig.touchMode = MidnightControlsConfig.touchMode.next(),
option -> option.getDisplayText(MidnightControlsConfig.touchMode.getTranslatedText()),
TooltipData.builder().text(Text.translatable("midnightcontrols.menu.touch_mode.tooltip")).build());
this.touchWithControllerOption = new SpruceToggleBooleanOption("midnightcontrols.menu.touch_with_controller", () -> MidnightControlsConfig.touchInControllerMode,
value -> MidnightControlsConfig.touchInControllerMode = value, TooltipData.builder().text(Text.translatable("midnightcontrols.menu.touch_with_controller.tooltip")).build());
this.touchSpeedOption = new SpruceDoubleOption("midnightcontrols.menu.touch_speed", 0.0, 150.0, .5f,
() -> MidnightControlsConfig.touchSpeed,
value -> MidnightControlsConfig.touchSpeed = value, option -> option.getDisplayText(Text.literal(String.valueOf(option.get()))),
TooltipData.builder().text(Text.translatable("midnightcontrols.menu.touch_speed.tooltip")).build());
this.touchBreakDelayOption = new SpruceDoubleOption("midnightcontrols.menu.touch_break_delay", 50, 500, 1f,
() -> (double) MidnightControlsConfig.touchBreakDelay,
value -> MidnightControlsConfig.touchBreakDelay = value.intValue(), option -> option.getDisplayText(Text.literal(String.valueOf(option.get()))),
TooltipData.builder().text(Text.translatable("midnightcontrols.menu.touch_break_delay.tooltip")).build());
this.touchTransparencyOption = new SpruceDoubleOption("midnightcontrols.menu.touch_transparency", 0, 100, 1f,
() -> (double) MidnightControlsConfig.touchTransparency,
value -> MidnightControlsConfig.touchTransparency = value.intValue(), option -> option.getDisplayText(Text.literal(String.valueOf(option.get()))),
TooltipData.builder().text(Text.translatable("midnightcontrols.menu.touch_break_delay.tooltip")).build());
this.invertTouchOption = new SpruceToggleBooleanOption("midnightcontrols.menu.invert_touch", () -> MidnightControlsConfig.invertTouch,
value -> MidnightControlsConfig.invertTouch = value, TooltipData.builder().text(Text.translatable("midnightcontrols.menu.invert_touch.tooltip")).build());
}
@Override
@@ -272,8 +351,8 @@ public class MidnightControlsSettingsScreen extends SpruceScreen {
this.buildTabs();
this.addDrawableChild(this.resetOption.createWidget(Position.of(this.width / 2 - 155, this.height - 29), 150));
this.addDrawableChild(new ButtonWidget(this.width / 2 - 155 + 160, this.height - 29, 150, 20, SpruceTexts.GUI_DONE,
btn -> this.client.setScreen(this.parent)));
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() {
@@ -298,6 +377,8 @@ public class MidnightControlsSettingsScreen extends SpruceScreen {
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.touch"), null,
this::buildTouchTab);
tabs.addTabEntry(Text.translatable("midnightcontrols.menu.title.mappings.string"), null,
this::buildMappingsStringEditorTab);
}
@@ -311,6 +392,11 @@ public class MidnightControlsSettingsScreen extends SpruceScreen {
list.addSingleOptionEntry(this.yAxisRotationSpeedOption);
list.addSingleOptionEntry(this.mouseSpeedOption);
list.addSingleOptionEntry(this.virtualMouseOption);
list.addSingleOptionEntry(this.virtualKeyboardOption);
list.addSingleOptionEntry(this.virtualKeyboardLayoutOption);
list.addSingleOptionEntry(this.hideCursorOption);
list.addSingleOptionEntry(this.joystickAsMouseOption);
list.addSingleOptionEntry(this.eyeTrackingAsMouseOption);
return list;
}
@@ -318,7 +404,8 @@ public class MidnightControlsSettingsScreen extends SpruceScreen {
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);
@@ -333,9 +420,10 @@ public class MidnightControlsSettingsScreen extends SpruceScreen {
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(new SpruceSeparatorOption("midnightcontrols.menu.title.hud", true, TooltipData.EMPTY));
list.addSingleOptionEntry(this.hudEnableOption);
list.addSingleOptionEntry(this.hudSideOption);
list.addSingleOptionEntry(this.moveChatOption);
return list;
}
@@ -348,17 +436,17 @@ public class MidnightControlsSettingsScreen extends SpruceScreen {
var aboutMappings1 = new SpruceLabelWidget(Position.of(0, 2),
Text.translatable("midnightcontrols.controller.mappings.1", SDL2_GAMEPAD_TOOL),
width, true);
width);
var gamepadToolUrlLabel = new SpruceLabelWidget(Position.of(0, aboutMappings1.getHeight() + 4),
this.controllerMappingsUrlText, width,
label -> Util.getOperatingSystem().open(GAMEPAD_TOOL_URL), true);
label -> Util.getOperatingSystem().open(GAMEPAD_TOOL_URL));
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);
width);
int listHeight = height - 8 - aboutMappings1.getHeight() - aboutMappings3.getHeight() - gamepadToolUrlLabel.getHeight();
var labels = new SpruceContainerWidget(Position.of(0,
@@ -372,7 +460,9 @@ public class MidnightControlsSettingsScreen extends SpruceScreen {
list.setBackground(new MidnightControlsBackground(130));
list.addSingleOptionEntry(this.controllerOption);
list.addSingleOptionEntry(this.secondControllerOption);
list.addSingleOptionEntry(this.toggleControllerProfileOption);
list.addSingleOptionEntry(this.unfocusedInputOption);
list.addSingleOptionEntry(this.cameraModeOption);
list.addOptionEntry(this.invertsRightXAxis, this.invertsRightYAxis);
list.addSingleOptionEntry(this.rightDeadZoneOption);
list.addSingleOptionEntry(this.leftDeadZoneOption);
@@ -384,14 +474,27 @@ public class MidnightControlsSettingsScreen extends SpruceScreen {
root.addChild(labels);
return root;
}
public SpruceOptionListWidget buildTouchTab(int width, int height) {
var list = new SpruceOptionListWidget(Position.origin(), width, height);
list.setBackground(new MidnightControlsBackground(130));
list.addSingleOptionEntry(this.touchSpeedOption);
list.addSingleOptionEntry(this.touchWithControllerOption);
list.addSingleOptionEntry(this.invertTouchOption);
list.addSingleOptionEntry(new SpruceSeparatorOption("midnightcontrols.menu.title.hud", true, TooltipData.EMPTY));
list.addSingleOptionEntry(this.touchModeOption);
list.addSingleOptionEntry(this.touchBreakDelayOption);
list.addSingleOptionEntry(this.touchTransparencyOption);
return list;
}
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 void render(SpruceGuiGraphics context, int mouseX, int mouseY, float delta) {
super.render(context, mouseX, mouseY, delta);
context.vanilla().drawCenteredTextWithShadow(this.textRenderer, I18n.translate("midnightcontrols.menu.title"), this.width / 2, 8, 0xFFFFFFFF);
}
public static class MidnightControlsBackground implements Background {
@@ -401,31 +504,8 @@ public class MidnightControlsSettingsScreen extends SpruceScreen {
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::getPositionColorShader);
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.drawWithShader(bufferBuilder.end());
RenderSystem.enableTexture();
RenderSystem.disableBlend();
matrixStack.pop();
public void render(SpruceGuiGraphics context, SpruceWidget widget, int vOffset, int mouseX, int mouseY, float delta) {
context.fill(widget.getX(), widget.getY(), widget.getX() + widget.getWidth(), widget.getY() + widget.getHeight(), ColorHelper.getArgb(transparency, 0, 0, 0));
}
}
}

View File

@@ -9,9 +9,10 @@
package eu.midnightdust.midnightcontrols.client.gui;
import eu.midnightdust.midnightcontrols.client.controller.Controller;
import dev.lambdaurora.spruceui.option.SpruceSimpleActionOption;
import dev.lambdaurora.spruceui.tooltip.TooltipData;
import dev.lambdaurora.spruceui.widget.SpruceButtonWidget;
import eu.midnightdust.midnightcontrols.client.controller.Controller;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.toast.SystemToast;
import net.minecraft.text.Text;
@@ -32,9 +33,9 @@ public class ReloadControllerMappingsOption {
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,
client.currentScreen.init(client.getWindow().getScaledWidth(), client.getWindow().getScaledHeight());
client.getToastManager().add(SystemToast.create(client, SystemToast.Type.PERIODIC_NOTIFICATION,
Text.translatable("midnightcontrols.controller.mappings.updated"), Text.empty()));
}, Text.translatable("midnightcontrols.tooltip.reload_controller_mappings"));
}, TooltipData.builder().text(Text.translatable("midnightcontrols.tooltip.reload_controller_mappings")).build());
}
}

View File

@@ -0,0 +1,90 @@
/*
* 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.Click;
import net.minecraft.client.gui.DrawContext;
import net.minecraft.client.gui.screen.Screen;
import net.minecraft.client.gui.widget.ButtonWidget;
import net.minecraft.text.Text;
import static eu.midnightdust.midnightcontrols.client.MidnightControlsClient.ring;
/**
* Represents the controls ring screen.
*
* @author LambdAurora
* @version 1.4.3
* @since 1.4.3
*/
public class RingScreen extends Screen {
public RingScreen() {
super(Text.literal("midnightcontrols.menu.title.ring"));
}
@Override
protected void init() {
super.init();
if (ring.getMaxPages() > 1) {
this.addDrawableChild(ButtonWidget.builder(Text.of(""), button -> ring.cyclePage(false)).dimensions(5, 5, 20, 20).build());
this.addDrawableChild(ButtonWidget.builder(Text.of(""), button -> ring.cyclePage(true)).dimensions(width - 25, 5, 20, 20).build());
}
}
@Override
public boolean shouldPause() {
return false;
}
@Override
public void render(DrawContext context, int mouseX, int mouseY, float delta) {
super.render(context, mouseX, mouseY, delta);
RingPage page = ring.getCurrentPage();
page.render(context, this.textRenderer, this.width, this.height, mouseX, mouseY, delta);
}
@Override
public void close() {
super.close();
assert client != null;
client.currentScreen = null;
RingPage page = 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(Click click) {
if (ring.getCurrentPage().onClick(width, height, (int) click.x(), (int) click.y())) {
this.close();
return true;
}
return false;
}
}

View File

@@ -0,0 +1,159 @@
package eu.midnightdust.midnightcontrols.client.gui.config;
import com.google.common.collect.Lists;
import dev.lambdaurora.spruceui.SpruceTexts;
import eu.midnightdust.lib.config.EntryInfo;
import eu.midnightdust.lib.config.MidnightConfigListWidget;
import eu.midnightdust.lib.config.MidnightConfigScreen;
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 eu.midnightdust.midnightcontrols.client.gui.MidnightControlsRenderer;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.font.DrawnTextConsumer;
import net.minecraft.client.gui.DrawContext;
import net.minecraft.client.gui.tooltip.Tooltip;
import net.minecraft.client.gui.widget.ButtonWidget;
import net.minecraft.client.gui.widget.ClickableWidget;
import net.minecraft.client.gui.widget.TextIconButtonWidget;
import net.minecraft.client.input.AbstractInput;
import net.minecraft.text.MutableText;
import net.minecraft.text.Text;
import net.minecraft.util.Formatting;
import net.minecraft.util.Identifier;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
public class ControllerBindingButton extends ButtonWidget implements ControlsInput {
private static final int[] UNBOUND = new int[]{-1};
static boolean waiting = false;
static List<Integer> currentButtons = new ArrayList<>();
private int iconWidth;
public static void add(ButtonBinding binding, MidnightConfigListWidget list, MidnightConfigScreen screen) {
ControllerBindingButton editButton = new ControllerBindingButton(screen.width - 185 + 22, 0, 128, 20, binding);
TextIconButtonWidget resetButton = TextIconButtonWidget.builder(net.minecraft.text.Text.translatable("controls.reset"), (button -> {
MidnightControlsConfig.setButtonBinding(binding, binding.getDefaultButton());
MidnightControlsClient.input.beginControlsInput(null);
editButton.updateMessage(false);
}), true).texture(Identifier.of("midnightlib","icon/reset"), 12, 12).dimension(20, 20).build();
resetButton.setPosition(screen.width - 205 + 150 + 25, 0);
editButton.resetButton = resetButton;
editButton.updateMessage(false);
EntryInfo info = new EntryInfo(null, screen.modid);
TextIconButtonWidget unbindButton = TextIconButtonWidget.builder(net.minecraft.text.Text.translatable("midnightcontrols.narrator.unbound", binding.getText()), (button -> {
MidnightControlsConfig.setButtonBinding(binding, UNBOUND);
MidnightControlsClient.input.beginControlsInput(null);
editButton.updateMessage(false);
}), true).texture(Identifier.of("midnightcontrols","icon/unbind"), 12, 12).dimension(20, 20).build();
unbindButton.setPosition(screen.width - 205 + 20, 0);
unbindButton.setTooltip(Tooltip.of(SpruceTexts.GUI_UNBIND));
unbindButton.active = !binding.isNotBound();
editButton.unbindButton = unbindButton;
list.addButton(Lists.newArrayList(editButton, resetButton, unbindButton), net.minecraft.text.Text.translatable(binding.getTranslationKey()), info);
}
private final ButtonBinding binding;
private @Nullable ClickableWidget resetButton;
private @Nullable ClickableWidget unbindButton;
public ControllerBindingButton(int x, int y, int width, int height, ButtonBinding binding) {
super(x, y, width, height, binding.getText(), (button) -> {},
(textSupplier) -> binding.isNotBound() ? net.minecraft.text.Text.translatable("narrator.controls.unbound", binding.getTranslationKey()) : net.minecraft.text.Text.translatable("narrator.controls.bound", binding.getTranslationKey(), textSupplier.get()));
this.binding = binding;
updateMessage(false);
}
@Override
public void onPress(AbstractInput input) {
MidnightControlsClient.input.beginControlsInput(this);
this.updateMessage(true);
}
public void updateMessage(boolean focused) {
AtomicBoolean hasConflicts = new AtomicBoolean(false);
MutableText conflictingBindings = net.minecraft.text.Text.empty();
if (focused) this.setMessage(net.minecraft.text.Text.literal("> ").append(getTranslatedButtons().copy().formatted(Formatting.WHITE, Formatting.UNDERLINE)).append(" <").formatted(Formatting.YELLOW));
else {
this.setMessage(getTranslatedButtons());
if (!this.binding.isNotBound()) {
InputManager.streamBindings().forEach(keyBinding -> {
if (keyBinding != this.binding && this.binding.equals(keyBinding)) {
if (hasConflicts.get()) conflictingBindings.append(", ");
hasConflicts.set(true);
conflictingBindings.append(net.minecraft.text.Text.translatable(keyBinding.getTranslationKey()));
}
});
}
}
if (this.resetButton != null) this.resetButton.active = !this.binding.isDefault();
if (this.unbindButton != null) this.unbindButton.active = !binding.isNotBound();
if (hasConflicts.get()) {
this.setMessage(net.minecraft.text.Text.literal("[ ").append(this.getMessage().copy().formatted(Formatting.WHITE)).append(" ]").formatted(Formatting.RED));
this.setTooltip(Tooltip.of(net.minecraft.text.Text.translatable("controls.keybinds.duplicateKeybinds", conflictingBindings)));
} else {
this.setTooltip(null);
}
}
private net.minecraft.text.Text getTranslatedButtons() {
return this.binding.isNotBound() ? SpruceTexts.NOT_BOUND.copy() :
(binding.getButton().length > 0 ? ButtonBinding.getLocalizedButtonName(binding.getButton()[0]) : net.minecraft.text.Text.literal("..."));
}
@Override
public void drawLabel(DrawnTextConsumer consumer) {
if (this.binding.getButton().length < 2) super.drawLabel(consumer);
}
@Override
protected void drawIcon(DrawContext context, int mouseX, int mouseY, float deltaTicks) {
int x = this.getX();
if (this.binding.getButton().length > 1) {
x += (this.width / 2 - iconWidth / 2) - 4;
}
var size = MidnightControlsRenderer.drawButton(context, x, this.getY(), this.binding, MinecraftClient.getInstance());
iconWidth = size.length();
}
@Override
public void finishBindingEdit(int... buttons) {
MidnightControlsConfig.setButtonBinding(binding, buttons);
updateMessage(false);
}
@Override
public void update() {
this.updateMessage(true);
}
@Override
public void setWaiting(boolean value) {
waiting = value;
}
@Override
public boolean isWaiting() {
return waiting;
}
@Override
public List<Integer> getCurrentButtons() {
return currentButtons;
}
@Override
public ButtonBinding getFocusedBinding() {
return this.binding;
}
}

View File

@@ -0,0 +1,64 @@
package eu.midnightdust.midnightcontrols.client.gui.config;
import dev.lambdaurora.spruceui.SpruceTexts;
import eu.midnightdust.lib.config.EntryInfo;
import eu.midnightdust.lib.config.MidnightConfigListWidget;
import eu.midnightdust.lib.config.MidnightConfigScreen;
import eu.midnightdust.midnightcontrols.client.MidnightControlsConfig;
import eu.midnightdust.midnightcontrols.client.controller.Controller;
import net.minecraft.client.gui.tooltip.Tooltip;
import net.minecraft.client.gui.widget.ButtonWidget;
import net.minecraft.client.gui.widget.TextIconButtonWidget;
import net.minecraft.text.Text;
import net.minecraft.util.Formatting;
import net.minecraft.util.Identifier;
import org.lwjgl.glfw.GLFW;
import java.util.List;
import static eu.midnightdust.midnightcontrols.client.gui.MidnightControlsSettingsScreen.searchNextAvailableController;
public class ControllerSelectionButton {
public static void add(MidnightConfigListWidget list, MidnightConfigScreen screen, boolean second) {
TextIconButtonWidget resetButton = TextIconButtonWidget.builder(Text.translatable("controls.reset"), (button -> {
if (second) MidnightControlsConfig.secondControllerID = -1;
else MidnightControlsConfig.controllerID = 0;
screen.updateList();
}), true).texture(Identifier.of("midnightlib","icon/reset"), 12, 12).dimension(20, 20).build();
resetButton.setPosition(screen.width - 205 + 150 + 25, 0);
ButtonWidget editButton = ButtonWidget.builder(getControllerName(second),
button -> {
int id = second ? MidnightControlsConfig.getSecondController().map(Controller::id).orElse(-1) : MidnightControlsConfig.getController().id();
id += 1;
if (id > GLFW.GLFW_JOYSTICK_LAST)
id = GLFW.GLFW_JOYSTICK_1;
id = searchNextAvailableController(id, second);
if (second) {
MidnightControlsConfig.setSecondController(Controller.byId(id));
} else {
MidnightControlsConfig.setController(Controller.byId(id));
}
if (MidnightControlsConfig.debug && id != -1) System.out.println(Controller.byId(id).getName() + "'s Controller GUID: " + Controller.byId(id).getGuid());
resetButton.active = second ? MidnightControlsConfig.getSecondController().isPresent() : false;
button.setMessage(getControllerName(second));
}).dimensions(screen.width - 185, 0, 150, 20).build();
resetButton.active = second ? MidnightControlsConfig.getSecondController().isPresent() : false;
if (second) editButton.setTooltip(Tooltip.of(Text.translatable("midnightcontrols.menu.controller2.tooltip")));
list.addButton(List.of(editButton, resetButton), Text.translatable(second ? "midnightcontrols.menu.controller2" : "midnightcontrols.menu.controller"), new EntryInfo(null, screen.modid));
}
private static Text getControllerName(boolean second) {
if (second && MidnightControlsConfig.getSecondController().isEmpty()) return SpruceTexts.OPTIONS_OFF.copyContentOnly().formatted(Formatting.RED);
var controller = second ? MidnightControlsConfig.getSecondController().get() : MidnightControlsConfig.getController();
var controllerName = controller.getName();
if (!controller.isConnected())
return Text.literal(controllerName).formatted(Formatting.RED);
else if (!controller.isGamepad())
return Text.literal(controllerName).formatted(Formatting.GOLD);
else
return Text.literal(controllerName);
}
}

View File

@@ -0,0 +1,18 @@
package eu.midnightdust.midnightcontrols.client.gui.config;
import eu.midnightdust.midnightcontrols.client.controller.ButtonBinding;
import java.util.List;
public interface ControlsInput {
void setWaiting(boolean value);
boolean isWaiting();
List<Integer> getCurrentButtons();
ButtonBinding getFocusedBinding();
void finishBindingEdit(int[] buttons);
default void update() {};
}

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.gui.config;
import com.google.common.collect.Lists;
import dev.lambdaurora.spruceui.Position;
import dev.lambdaurora.spruceui.widget.text.SpruceTextAreaWidget;
import eu.midnightdust.lib.config.EntryInfo;
import eu.midnightdust.lib.config.MidnightConfigListWidget;
import eu.midnightdust.lib.config.MidnightConfigScreen;
import eu.midnightdust.midnightcontrols.client.MidnightControlsConfig;
import net.minecraft.client.gui.widget.*;
import net.minecraft.text.Text;
import net.minecraft.util.Identifier;
import java.util.List;
import static eu.midnightdust.midnightcontrols.client.MidnightControlsClient.client;
/**
* Represents the controller mappings file editor screen.
*
* @author LambdAurora
* @version 1.7.0
* @since 1.4.3
*/
public class MappingsStringInputWidget {
public static void add(EntryInfo centered, MidnightConfigListWidget list, MidnightConfigScreen screen) {
//SpruceTextAreaWidget editButton = new SpruceTextAreaWidget(Position.of(0, 0), 20, 20, Text.empty());
MultilineTextFieldWidget editButton = new MultilineTextFieldWidget(screen.getTextRenderer(), screen.width / 2 - 128, 0, 256, 60, Text.of("TESTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTT\nnew line!"));
TextIconButtonWidget resetButton = TextIconButtonWidget.builder(Text.translatable("controls.reset"), (button -> {
screen.updateList();
}), true).texture(Identifier.of("midnightlib","icon/reset"), 12, 12).dimension(20, 60).build();
resetButton.setPosition(screen.width / 2 - 128 + 256 + 4, 0);
editButton.setChangedListener(string -> {
resetButton.active = !string.isEmpty();
});
list.addButton(List.of(), Text.translatable("midnightcontrols.menu.title.mappings.string"), centered);
//screen.addDrawableChild(editButton);
list.addButton(Lists.newArrayList(editButton, resetButton), Text.empty(), centered);
list.addButton(List.of(), Text.empty(), centered);
list.addButton(List.of(), Text.empty(), centered);
list.addButton(List.of(), Text.translatable("midnightcontrols.menu.multiple_mapping_tip"), centered);
//list.addButton();
ButtonWidget copyButton = TextIconButtonWidget.builder(Text.of("Copy GUID"), widget -> {
client.keyboard.setClipboard(MidnightControlsConfig.getController().getGuid());
}).dimensions(screen.width - 185, 0, 150, 20).build();
list.addButton(List.of(copyButton), Text.translatable("midnightcontrols.menu.current_controller_guid", MidnightControlsConfig.getController().getGuid()), new EntryInfo(null, screen.modid));
}
}

View File

@@ -0,0 +1,13 @@
package eu.midnightdust.midnightcontrols.client.gui.config;
import net.minecraft.client.font.TextRenderer;
import net.minecraft.client.gui.widget.ScrollableTextWidget;
import net.minecraft.client.gui.widget.TextFieldWidget;
import net.minecraft.text.Text;
public class MultilineTextFieldWidget extends TextFieldWidget {
public MultilineTextFieldWidget(TextRenderer textRenderer, int x, int y, int width, int height, Text text) {
super(textRenderer, x, y, width, height, text);
}
// TODO
}

View File

@@ -0,0 +1,25 @@
package eu.midnightdust.midnightcontrols.client.gui.cursor;
import com.mojang.blaze3d.pipeline.RenderPipeline;
import eu.midnightdust.midnightcontrols.client.mixin.DrawContextAccessor;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gui.DrawContext;
import net.minecraft.client.gui.cursor.Cursor;
import net.minecraft.client.texture.TextureSetup;
import net.minecraft.util.Identifier;
import org.jetbrains.annotations.NotNull;
import org.joml.Matrix3x2f;
import static eu.midnightdust.midnightcontrols.client.MidnightControlsClient.client;
public abstract class CursorRenderer {
public static Cursor currentCursorStyle = Cursor.DEFAULT;
public abstract void renderCursor(@NotNull DrawContext context, @NotNull MinecraftClient client);
public static void drawUnalignedTexturedQuad(RenderPipeline pipeline, Identifier texture, DrawContext context, float x1, float x2, float y1, float y2, float u1, float u2, float v1, float v2) {
DrawContextAccessor accessor = (DrawContextAccessor) context;
var tex = client.getTextureManager().getTexture(texture);
accessor.getState().addSimpleElement(new UnalignedTexturedQuadGuiElementRenderState(pipeline, TextureSetup.of(tex.getGlTextureView(), tex.getSampler()), new Matrix3x2f(context.getMatrices()), x1, y1, x2, y2, u1, u2, v1, v2, 0xffffffff, accessor.getScissorStack().peekLast()));
}
}

View File

@@ -0,0 +1,32 @@
package eu.midnightdust.midnightcontrols.client.gui.cursor;
import com.mojang.blaze3d.pipeline.RenderPipeline;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.minecraft.client.gui.ScreenRect;
import net.minecraft.client.gui.render.state.SimpleGuiElementRenderState;
import net.minecraft.client.render.VertexConsumer;
import net.minecraft.client.texture.TextureSetup;
import org.jetbrains.annotations.Nullable;
import org.joml.Matrix3x2f;
@Environment(EnvType.CLIENT)
public record UnalignedTexturedQuadGuiElementRenderState(RenderPipeline pipeline, TextureSetup textureSetup, Matrix3x2f pose, float x1, float y1, float x2, float y2, float u1, float u2, float v1, float v2, int color, @Nullable ScreenRect scissorArea, @Nullable ScreenRect bounds) implements SimpleGuiElementRenderState {
public UnalignedTexturedQuadGuiElementRenderState(RenderPipeline pipeline, TextureSetup textureSetup, Matrix3x2f pose, float x1, float y1, float x2, float y2, float u1, float u2, float v1, float v2, int color, @Nullable ScreenRect scissorArea) {
this(pipeline, textureSetup, pose, x1, y1, x2, y2, u1, u2, v1, v2, color, scissorArea, createBounds(x1, y1, x2, y2, pose, scissorArea));
}
@Override
public void setupVertices(VertexConsumer vertices) {
vertices.vertex(pose(), x1(), y1()).texture(u1(), v1()).color(color());
vertices.vertex(pose(), x1(), y2()).texture(u1(), v2()).color(color());
vertices.vertex(pose(), x2(), y2()).texture(u2(), v2()).color(color());
vertices.vertex(pose(), x2(), y1()).texture(u2(), v1()).color(color());
}
@Nullable
private static ScreenRect createBounds(float x1, float y1, float x2, float y2, Matrix3x2f pose, @Nullable ScreenRect scissorArea) {
ScreenRect screenRect = (new ScreenRect((int) x1, (int) y1, (int) (x2 - x1), (int) (y2 - y1))).transformEachVertex(pose);
return scissorArea != null ? scissorArea.intersection(screenRect) : screenRect;
}
}

View File

@@ -0,0 +1,67 @@
package eu.midnightdust.midnightcontrols.client.gui.cursor;
import eu.midnightdust.midnightcontrols.client.MidnightControlsConfig;
import eu.midnightdust.midnightcontrols.client.MidnightInput;
import eu.midnightdust.midnightcontrols.client.compat.MidnightControlsCompat;
import eu.midnightdust.midnightcontrols.client.util.HandledScreenAccessor;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gl.RenderPipelines;
import net.minecraft.client.gui.DrawContext;
import net.minecraft.client.texture.Sprite;
import net.minecraft.screen.slot.Slot;
import net.minecraft.util.Atlases;
import org.jetbrains.annotations.NotNull;
import static eu.midnightdust.midnightcontrols.MidnightControls.id;
public class VirtualCursorRenderer extends CursorRenderer {
private static final VirtualCursorRenderer INSTANCE = new VirtualCursorRenderer();
public static VirtualCursorRenderer getInstance() {
return INSTANCE;
}
public void renderCursor(@NotNull DrawContext context, @NotNull MinecraftClient client) {
if (!MidnightControlsConfig.virtualMouse || (client.currentScreen == null
|| MidnightInput.isScreenInteractive(client.currentScreen)))
return;
float mouseX = (float) client.mouse.getX() * client.getWindow().getScaledWidth() / client.getWindow().getWidth();
float mouseY = (float) client.mouse.getY() * client.getWindow().getScaledHeight() / 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 && client.currentScreen != null) {
var slot = MidnightControlsCompat.getSlotAt(client.currentScreen, (int) mouseX, (int) mouseY);
if (slot != null) {
mouseX = slot.x();
mouseY = slot.y();
hoverSlot = true;
}
}
if (!hoverSlot) {
mouseX -= 8;
mouseY -= 8;
}
try {
Sprite sprite = client.getAtlasManager().getAtlasTexture(Atlases.GUI).getSprite(id(MidnightControlsConfig.virtualMouseSkin.getSpritePath() + (hoverSlot ? "_slot" : "")));
drawUnalignedTexturedQuad(RenderPipelines.GUI_TEXTURED, sprite.getAtlasId(), context, mouseX, mouseX + 16, mouseY, mouseY + 16, sprite.getMinU(), sprite.getMaxU(), sprite.getMinV(), sprite.getMaxV());
} catch (IllegalStateException ignored) {}
}
}

View File

@@ -0,0 +1,61 @@
package eu.midnightdust.midnightcontrols.client.gui.cursor;
import eu.midnightdust.midnightcontrols.ControlsMode;
import eu.midnightdust.midnightcontrols.client.MidnightControlsConfig;
import eu.midnightdust.midnightcontrols.client.enums.VirtualMouseSkin;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gl.RenderPipelines;
import net.minecraft.client.gui.DrawContext;
import net.minecraft.client.gui.cursor.StandardCursors;
import net.minecraft.client.texture.Sprite;
import net.minecraft.util.Atlases;
import net.minecraft.util.Identifier;
import org.jetbrains.annotations.NotNull;
import static eu.midnightdust.midnightcontrols.MidnightControls.id;
public class WaylandCursorRenderer extends CursorRenderer {
private static final WaylandCursorRenderer INSTANCE = new WaylandCursorRenderer();
public static final Identifier WAYLAND_CURSOR_ARROW_LIGHT = id("cursor/light/mouse_arrow");
public static final Identifier WAYLAND_CURSOR_ARROW_DARK = id("cursor/dark/mouse_arrow");
public static final Identifier WAYLAND_CURSOR_POINTING_LIGHT = id("cursor/light/mouse_pointing_hand");
public static final Identifier WAYLAND_CURSOR_POINTING_DARK = id("cursor/dark/mouse_pointing_hand");
public static final Identifier WAYLAND_CURSOR_IBEAM_LIGHT = id("cursor/light/mouse_ibeam");
public static final Identifier WAYLAND_CURSOR_IBEAM_DARK = id("cursor/dark/mouse_ibeam");
public static final Identifier WAYLAND_CURSOR_RESIZE_VERTICAL_LIGHT = id("cursor/light/mouse_resize_vertical");
public static final Identifier WAYLAND_CURSOR_REZIZE_VERTICAL_DARK = id("cursor/dark/mouse_resize_vertical");
public static final Identifier WAYLAND_CURSOR_RESIZE_HORIZONTAL_LIGHT = id("cursor/light/mouse_resize_horizontal");
public static final Identifier WAYLAND_CURSOR_REZIZE_HORIZONTAL_DARK = id("cursor/dark/mouse_resize_horizontal");
public static final Identifier WAYLAND_CURSOR_NOT_ALLOWED_LIGHT = id("cursor/light/mouse_not_allowed");
public static final Identifier WAYLAND_CURSOR_NOT_ALLOWED_DARK = id("cursor/dark/mouse_not_allowed");
public static WaylandCursorRenderer getInstance() {
return INSTANCE;
}
public void renderCursor(@NotNull DrawContext context, @NotNull MinecraftClient client) {
if (MidnightControlsConfig.virtualMouse || client.currentScreen == null || MidnightControlsConfig.controlsMode != ControlsMode.CONTROLLER) return;
float mouseX = (float) client.mouse.getX() * client.getWindow().getScaledWidth() / client.getWindow().getWidth();
float mouseY = (float) client.mouse.getY() * client.getWindow().getScaledHeight() / client.getWindow().getHeight();
try {
Sprite sprite = getSprite(client);
drawUnalignedTexturedQuad(RenderPipelines.GUI_TEXTURED, sprite.getAtlasId(), context, mouseX - 2, mouseX + 6, mouseY - 2, mouseY + 6, sprite.getMinU(), sprite.getMaxU(), sprite.getMinV(), sprite.getMaxV());
} catch (IllegalStateException ignored) {}
}
private static Sprite getSprite(@NotNull MinecraftClient client) {
boolean isDark = MidnightControlsConfig.virtualMouseSkin == VirtualMouseSkin.DEFAULT_DARK || MidnightControlsConfig.virtualMouseSkin == VirtualMouseSkin.SECOND_DARK;
Identifier spritePath;
if (CursorRenderer.currentCursorStyle == StandardCursors.POINTING_HAND) spritePath = isDark ? WAYLAND_CURSOR_POINTING_DARK : WAYLAND_CURSOR_POINTING_LIGHT;
else if (CursorRenderer.currentCursorStyle == StandardCursors.IBEAM) spritePath = isDark ? WAYLAND_CURSOR_IBEAM_DARK : WAYLAND_CURSOR_IBEAM_LIGHT;
else if (CursorRenderer.currentCursorStyle == StandardCursors.RESIZE_NS) spritePath = isDark ? WAYLAND_CURSOR_REZIZE_VERTICAL_DARK : WAYLAND_CURSOR_RESIZE_VERTICAL_LIGHT;
else if (CursorRenderer.currentCursorStyle == StandardCursors.RESIZE_EW) spritePath = isDark ? WAYLAND_CURSOR_REZIZE_HORIZONTAL_DARK : WAYLAND_CURSOR_RESIZE_HORIZONTAL_LIGHT;
else if (CursorRenderer.currentCursorStyle == StandardCursors.NOT_ALLOWED) spritePath = isDark ? WAYLAND_CURSOR_NOT_ALLOWED_DARK : WAYLAND_CURSOR_NOT_ALLOWED_LIGHT;
else spritePath = isDark ? WAYLAND_CURSOR_ARROW_DARK : WAYLAND_CURSOR_ARROW_LIGHT;
return client.getAtlasManager().getAtlasTexture(Atlases.GUI).getSprite(spritePath);
}
}

View File

@@ -9,13 +9,14 @@
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.render.SpruceGuiGraphics;
import dev.lambdaurora.spruceui.widget.AbstractSpruceIconButtonWidget;
import eu.midnightdust.midnightcontrols.client.controller.ButtonBinding;
import eu.midnightdust.midnightcontrols.client.gui.MidnightControlsRenderer;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.client.gui.DrawContext;
import net.minecraft.text.Text;
import org.jetbrains.annotations.NotNull;
@@ -45,12 +46,12 @@ public class ControllerButtonWidget extends AbstractSpruceIconButtonWidget {
}
@Override
protected int renderIcon(MatrixStack matrices, int mouseX, int mouseY, float delta) {
protected int renderIcon(SpruceGuiGraphics spruceGuiGraphics, 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());
var size = MidnightControlsRenderer.drawButton(spruceGuiGraphics.vanilla(), x, this.getY(), this.binding, MinecraftClient.getInstance());
this.iconWidth = size.length();
return size.height();
}

View File

@@ -9,17 +9,18 @@
package eu.midnightdust.midnightcontrols.client.gui.widget;
import eu.midnightdust.midnightcontrols.client.MidnightControlsClient;
import dev.lambdaurora.spruceui.Position;
import dev.lambdaurora.spruceui.SpruceTexts;
import dev.lambdaurora.spruceui.render.SpruceGuiGraphics;
import dev.lambdaurora.spruceui.widget.SpruceButtonWidget;
import dev.lambdaurora.spruceui.widget.container.SpruceContainerWidget;
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.config.ControlsInput;
import eu.midnightdust.midnightcontrols.client.gui.MidnightControlsSettingsScreen;
import net.minecraft.client.gui.DrawContext;
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;
@@ -30,17 +31,14 @@ import java.util.stream.Collectors;
/**
* Represents the controls screen.
*/
public class ControllerControlsWidget extends SpruceContainerWidget {
final MidnightControlsClient mod;
private ControlsListWidget bindingsListWidget;
public class ControllerControlsWidget extends SpruceContainerWidget implements ControlsInput {
private SpruceButtonWidget resetButton;
public ButtonBinding focusedBinding;
public boolean waiting = false;
public List<Integer> currentButtons = new ArrayList<>();
ButtonBinding focusedBinding = null;
boolean waiting = false;
List<Integer> currentButtons = new ArrayList<>();
public ControllerControlsWidget(Position position, int width, int height) {
super(position, width, height);
this.mod = MidnightControlsClient.get();
this.init();
}
@@ -49,20 +47,20 @@ public class ControllerControlsWidget extends SpruceContainerWidget {
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);
ControlsListWidget bindingsListWidget = new ControlsListWidget(Position.of(this, 0, 43), this.width, this.height - 43 - 35, this);
bindingsListWidget.setBackground(new MidnightControlsSettingsScreen.MidnightControlsBackground(130));
this.addChild(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);
public void renderWidget(SpruceGuiGraphics context, int mouseX, int mouseY, float delta) {
context.vanilla().drawCenteredTextWithShadow(this.client.textRenderer, Text.translatable("midnightcontrols.menu.title.controller_controls"),
this.getX() + this.width / 2, this.getY() + 4, 0xFFFFFFFF);
this.resetButton.setActive(InputManager.streamBindings().anyMatch(Predicates.not(ButtonBinding::isDefault)));
super.renderWidget(matrices, mouseX, mouseY, delta);
super.renderWidget(context, mouseX, mouseY, delta);
}
public void finishBindingEdit(int... buttons) {
@@ -70,4 +68,24 @@ public class ControllerControlsWidget extends SpruceContainerWidget {
MidnightControlsConfig.setButtonBinding(this.focusedBinding, buttons);
this.focusedBinding = null;
}
@Override
public void setWaiting(boolean value) {
this.waiting = value;
}
@Override
public boolean isWaiting() {
return this.waiting;
}
@Override
public List<Integer> getCurrentButtons() {
return currentButtons;
}
@Override
public ButtonBinding getFocusedBinding() {
return focusedBinding;
}
}

View File

@@ -9,24 +9,33 @@
package eu.midnightdust.midnightcontrols.client.gui.widget;
import dev.lambdaurora.spruceui.Position;
import dev.lambdaurora.spruceui.SpruceTexts;
import dev.lambdaurora.spruceui.navigation.NavigationEvent;
import dev.lambdaurora.spruceui.navigation.NavigationUtils;
import dev.lambdaurora.spruceui.render.SpruceGuiGraphics;
import dev.lambdaurora.spruceui.widget.SpruceButtonWidget;
import dev.lambdaurora.spruceui.widget.SpruceIconButtonWidget;
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 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.minecraft.client.gl.RenderPipelines;
import net.minecraft.client.gui.Click;
import net.minecraft.client.gui.navigation.NavigationAxis;
import net.minecraft.client.gui.navigation.NavigationDirection;
import net.minecraft.client.input.KeyInput;
import net.minecraft.client.input.MouseInput;
import net.minecraft.util.Identifier;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.minecraft.client.gui.DrawContext;
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;
@@ -86,9 +95,9 @@ public class ControlsListWidget extends SpruceEntryListWidget<ControlsListWidget
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 -> {
this.editButton = new ControllerButtonWidget(Position.of(this, parent.getWidth() / 2 - 8, 0), 120, this.binding, btn -> {
gui.focusedBinding = binding;
MidnightControlsClient.get().input.beginControlsInput(gui);
MidnightControlsClient.input.beginControlsInput(gui);
}) {
protected Text getNarrationMessage() {
return binding.isNotBound() ? Text.translatable("narrator.controls.unbound", bindingName)
@@ -96,13 +105,24 @@ public class ControlsListWidget extends SpruceEntryListWidget<ControlsListWidget
}
};
this.children.add(editButton);
this.resetButton = new SpruceButtonWidget(Position.of(this,
this.resetButton = new SpruceIconButtonWidget(Position.of(this,
this.editButton.getPosition().getRelativeX() + this.editButton.getWidth() + 2, 0),
44, 20, Text.translatable("controls.reset"),
37, 20, Text.empty(),
btn -> MidnightControlsConfig.setButtonBinding(binding, binding.getDefaultButton())) {
protected Text getNarrationMessage() {
return Text.translatable("narrator.controls.reset", bindingName);
}
private final Identifier resetTexture = Identifier.of("midnightlib","icon/reset");
@Override
protected int renderIcon(SpruceGuiGraphics context, int mouseX, int mouseY, float delta) {
int size = 12;
int x = this.getX() + this.getWidth() / 2 - size / 2;
int y = this.getY() + this.getHeight() / 2 - size / 2;
context.vanilla().drawGuiTexture(RenderPipelines.GUI_TEXTURED, resetTexture, x, y, size, size);
return 1;
}
};
this.children.add(this.resetButton);
this.unbindButton = new SpruceButtonWidget(Position.of(this,
@@ -111,7 +131,7 @@ public class ControlsListWidget extends SpruceEntryListWidget<ControlsListWidget
btn -> {
MidnightControlsConfig.setButtonBinding(binding, UNBOUND);
gui.focusedBinding = null;
MidnightControlsClient.get().input.beginControlsInput(null);
MidnightControlsClient.input.beginControlsInput(null);
}) {
protected Text getNarrationMessage() {
return Text.translatable("midnightcontrols.narrator.unbound", bindingName);
@@ -150,7 +170,7 @@ public class ControlsListWidget extends SpruceEntryListWidget<ControlsListWidget
/* Input */
@Override
protected boolean onMouseClick(double mouseX, double mouseY, int button) {
protected boolean onMouseClick(Click click, boolean doubleClick) {
var it = this.children().iterator();
SpruceWidget element;
@@ -160,30 +180,30 @@ public class ControlsListWidget extends SpruceEntryListWidget<ControlsListWidget
}
element = it.next();
} while (!element.mouseClicked(mouseX, mouseY, button));
} while (!element.mouseClicked(click, false));
this.setFocused(element);
if (button == GLFW.GLFW_MOUSE_BUTTON_1)
if (click.button() == GLFW.GLFW_MOUSE_BUTTON_1)
this.dragging = true;
return true;
}
@Override
protected boolean onMouseRelease(double mouseX, double mouseY, int button) {
protected boolean onMouseRelease(Click click) {
this.dragging = false;
return this.hoveredElement(mouseX, mouseY).filter(element -> element.mouseReleased(mouseX, mouseY, button)).isPresent();
return this.hoveredElement(click.x(), click.y()).filter(element -> element.mouseReleased(click)).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);
protected boolean onMouseDrag(@NotNull Click click, double deltaX, double deltaY) {
return this.getFocused() != null && this.dragging && click.button() == GLFW.GLFW_MOUSE_BUTTON_1
&& this.getFocused().mouseDragged(click, deltaX, deltaY);
}
@Override
protected boolean onKeyPress(int keyCode, int scanCode, int modifiers) {
return this.focused != null && this.focused.keyPressed(keyCode, scanCode, modifiers);
protected boolean onKeyPress(@NotNull KeyInput input) {
return this.focused != null && this.focused.keyPressed(input);
}
/* Navigation */
@@ -197,9 +217,9 @@ public class ControlsListWidget extends SpruceEntryListWidget<ControlsListWidget
}
@Override
public boolean onNavigation(@NotNull NavigationDirection direction, boolean tab) {
public boolean onNavigation(NavigationEvent event) {
if (this.requiresCursor()) return false;
if (!tab && direction.isVertical()) {
if (!event.tab() && event.direction().getAxis() == NavigationAxis.VERTICAL) {
if (this.isFocused()) {
this.setFocused(null);
return false;
@@ -207,16 +227,16 @@ public class ControlsListWidget extends SpruceEntryListWidget<ControlsListWidget
int lastIndex = this.parent.lastIndex;
if (lastIndex >= this.children.size())
lastIndex = this.children.size() - 1;
if (!this.children.get(lastIndex).onNavigation(direction, tab))
if (!this.children.get(lastIndex).onNavigation(event))
return false;
this.setFocused(this.children.get(lastIndex));
return true;
}
boolean result = NavigationUtils.tryNavigate(direction, tab, this.children, this.focused, this::setFocused, true);
boolean result = NavigationUtils.tryNavigate(event, this.children, this.focused, this::setFocused, true);
if (result) {
this.setFocused(true);
if (direction.isHorizontal() && this.getFocused() != null) {
if (event.direction().getAxis() == NavigationAxis.HORIZONTAL && this.getFocused() != null) {
this.parent.lastIndex = this.children.indexOf(this.getFocused());
}
}
@@ -226,14 +246,14 @@ public class ControlsListWidget extends SpruceEntryListWidget<ControlsListWidget
/* Rendering */
@Override
protected void renderWidget(MatrixStack matrices, int mouseX, int mouseY, float delta) {
protected void renderWidget(SpruceGuiGraphics context, 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);
context.drawText(textRenderer, this.bindingName, this.getX(), (textY - 9 / 2), 0xFFFFFFFF, true);
this.resetButton.setVisible(!focused);
this.unbindButton.setVisible(focused);
@@ -252,7 +272,7 @@ public class ControlsListWidget extends SpruceEntryListWidget<ControlsListWidget
this.editButton.setMessage(text.formatted(Formatting.GOLD));
}
this.children.forEach(widget -> widget.render(matrices, mouseX, mouseY, delta));
this.children.forEach(widget -> widget.render(context, mouseX, mouseY, delta));
}
}
@@ -282,15 +302,15 @@ public class ControlsListWidget extends SpruceEntryListWidget<ControlsListWidget
/* Navigation */
@Override
public boolean onNavigation(@NotNull NavigationDirection direction, boolean tab) {
return this.separatorWidget.onNavigation(direction, tab);
public boolean onNavigation(NavigationEvent event) {
return this.separatorWidget.onNavigation(event);
}
/* Rendering */
@Override
protected void renderWidget(MatrixStack matrices, int mouseX, int mouseY, float delta) {
this.separatorWidget.render(matrices, mouseX, mouseY, delta);
protected void renderWidget(SpruceGuiGraphics context, int mouseX, int mouseY, float delta) {
this.separatorWidget.render(context, mouseX, mouseY, delta);
}
@Override

View File

@@ -0,0 +1,14 @@
package eu.midnightdust.midnightcontrols.client.mixin;
import net.minecraft.block.AbstractBlock;
import net.minecraft.block.BlockState;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.WorldView;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Invoker;
@Mixin(AbstractBlock.class)
public interface AbstractBlockAccessor {
@Invoker("canPlaceAt")
boolean midnightcontrols$canPlaceAt(BlockState state, WorldView world, BlockPos pos);
}

View File

@@ -0,0 +1,34 @@
package eu.midnightdust.midnightcontrols.client.mixin;
import eu.midnightdust.midnightcontrols.client.util.AbstractSignEditScreenAccessor;
import net.minecraft.block.entity.SignBlockEntity;
import net.minecraft.block.entity.SignText;
import net.minecraft.client.gui.screen.ingame.AbstractSignEditScreen;
import net.minecraft.text.Text;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
@Mixin(AbstractSignEditScreen.class)
public class AbstractSignEditScreenMixin implements AbstractSignEditScreenAccessor {
@Shadow @Final private String[] messages;
@Shadow private SignText text;
@Shadow @Final protected SignBlockEntity blockEntity;
@Shadow @Final private boolean front;
@Override
public String[] midnightcontrols$getMessages() {
return messages;
}
@Override
public void midnightcontrols$setMessage(int line, String text) {
this.messages[line] = text;
this.text = this.text.withMessage(line, Text.literal(text));
}
@Override
public void midnightcontrols$writeToBlockEntity() {
this.blockEntity.setText(this.text, this.front);
}
}

View File

@@ -0,0 +1,12 @@
package eu.midnightdust.midnightcontrols.client.mixin;
import net.minecraft.client.gui.screen.ingame.BookEditScreen;
import net.minecraft.client.gui.widget.EditBoxWidget;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
@Mixin(BookEditScreen.class)
public interface BookEditScreenAccessor {
@Accessor("editBox")
EditBoxWidget midnightcontrols$getEditBox();
}

View File

@@ -0,0 +1,12 @@
package eu.midnightdust.midnightcontrols.client.mixin;
import net.minecraft.client.gui.screen.ingame.BookSigningScreen;
import net.minecraft.client.gui.widget.TextFieldWidget;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
@Mixin(BookSigningScreen.class)
public interface BookSigningScreenAccessor {
@Accessor("bookTitleTextField")
TextFieldWidget midnightcontrols$getBookTitleTextField();
}

View File

@@ -0,0 +1,35 @@
package eu.midnightdust.midnightcontrols.client.mixin;
import eu.midnightdust.midnightcontrols.client.MidnightControlsConfig;
import net.minecraft.client.gui.DrawContext;
import net.minecraft.client.gui.screen.ChatScreen;
import net.minecraft.client.gui.screen.Screen;
import net.minecraft.client.gui.widget.TextFieldWidget;
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("HEAD"))
private void midnightcontrols$moveInputFieldBackground(DrawContext context, int mouseX, int mouseY, float delta, CallbackInfo ci) {
if (MidnightControlsConfig.moveChat) context.getMatrices().translate(0f, -this.height + 16);
}
@Inject(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/DrawContext;fill(IIIII)V", shift = At.Shift.AFTER))
private void midnightcontrols$dontMoveOtherStuff(DrawContext context, int mouseX, int mouseY, float delta, CallbackInfo ci) {
if (MidnightControlsConfig.moveChat) context.getMatrices().translate(0f, this.height - 16);
}
}

View File

@@ -17,4 +17,6 @@ import org.spongepowered.asm.mixin.gen.Accessor;
public interface ClickableWidgetAccessor {
@Accessor("height")
int getHeight();
@Accessor("focused")
void setFocused(boolean value);
}

View File

@@ -26,6 +26,7 @@ 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.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.Redirect;
@@ -36,10 +37,10 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
*/
@Mixin(ClientPlayerEntity.class)
public abstract class ClientPlayerEntityMixin extends AbstractClientPlayerEntity {
private boolean midnightcontrols$driftingPrevented = false;
@Unique private boolean midnightcontrols$driftingPrevented = false;
public ClientPlayerEntityMixin(ClientWorld world, GameProfile profile, @Nullable PlayerPublicKey publicKey) {
super(world, profile, publicKey);
public ClientPlayerEntityMixin(ClientWorld world, GameProfile profile) {
super(world, profile);
}
@Shadow
@@ -55,12 +56,9 @@ public abstract class ClientPlayerEntityMixin extends AbstractClientPlayerEntity
@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)) {
@@ -76,7 +74,7 @@ public abstract class ClientPlayerEntityMixin extends AbstractClientPlayerEntity
}
}
@Inject(method = "tickMovement", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/input/Input;tick(ZF)V", shift = At.Shift.AFTER))
@Inject(method = "tickMovement", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/input/Input;tick()V", shift = At.Shift.AFTER))
public void onInputUpdate(CallbackInfo ci) {
MovementHandler.HANDLER.applyMovement((ClientPlayerEntity) (Object) this);
}
@@ -87,11 +85,11 @@ public abstract class ClientPlayerEntityMixin extends AbstractClientPlayerEntity
if (MidnightControlsConfig.verticalFlyDrifting || !MidnightControls.isExtrasLoaded)
return;
int moving = 0;
if (this.input.sneaking) {
if (this.input.playerInput.sneak()) {
--moving;
}
if (this.input.jumping) {
if (this.input.playerInput.jump()) {
++moving;
}

View File

@@ -11,6 +11,7 @@ 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;
@@ -29,8 +30,8 @@ public interface CreativeInventoryScreenAccessor {
* @return the selected tab index
*/
@Accessor("selectedTab")
static int getSelectedTab() {
return 0;
static ItemGroup getSelectedTab() {
return ItemGroups.getDefaultTab();
}
/**
@@ -57,4 +58,10 @@ public interface CreativeInventoryScreenAccessor {
*/
@Invoker("hasScrollbar")
boolean midnightcontrols$hasScrollbar();
/**
* Triggers searching the creative inventory from the current value of the internal {@link net.minecraft.client.gui.widget.TextFieldWidget}
*/
@Invoker("search")
void midnightcontrols$search();
}

View File

@@ -0,0 +1,17 @@
package eu.midnightdust.midnightcontrols.client.mixin;
import eu.midnightdust.midnightcontrols.client.gui.cursor.CursorRenderer;
import net.minecraft.client.gui.cursor.Cursor;
import net.minecraft.client.util.Window;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(Cursor.class)
public abstract class CursorMixin {
@Inject(method = "applyTo", at = @At("TAIL"))
public void midnightcontrols$applyCursorStyle(Window window, CallbackInfo ci) {
CursorRenderer.currentCursorStyle = ((Cursor) (Object) this);
}
}

View File

@@ -0,0 +1,24 @@
/*
* 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.DrawContext;
import net.minecraft.client.gui.render.state.GuiRenderState;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
@Mixin(DrawContext.class)
public interface DrawContextAccessor {
@Accessor("state")
GuiRenderState getState();
@Accessor("scissorStack")
DrawContext.ScissorStack getScissorStack();
}

View File

@@ -0,0 +1,69 @@
/*
* 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.MidnightControlsConfig;
import eu.midnightdust.midnightcontrols.client.gui.MidnightControlsSettingsScreen;
import net.minecraft.client.MinecraftClient;
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.gui.widget.OptionListWidget;
import net.minecraft.client.gui.widget.TextIconButtonWidget;
import net.minecraft.text.Text;
import org.jetbrains.annotations.Nullable;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import static eu.midnightdust.midnightcontrols.MidnightControls.id;
/**
* Injects the new controls settings button.
*/
@Mixin(GameOptionsScreen.class)
public abstract class GameOptionsScreenMixin extends Screen {
@Shadow @Nullable protected OptionListWidget body;
@Unique TextIconButtonWidget midnightcontrols$button = TextIconButtonWidget.builder(Text.translatable("midnightcontrols.menu.title.controller"),
(button -> this.client.setScreen(MidnightControlsConfig.getScreen(this, "midnightcontrols"))), true)
.dimension(20,20).texture(id("icon/controller"), 20, 20).build();
protected GameOptionsScreenMixin(Text title) {
super(title);
}
@Inject(method = "initBody", at = @At("TAIL"))
public void midnightcontrols$addMCButton(CallbackInfo ci) {
if (this.getClass().toString().equals(ControlsOptionsScreen.class.toString())) {
this.midnightcontrols$setButtonPos();
this.addSelectableChild(midnightcontrols$button);
}
}
@Inject(method = "init", at = @At("TAIL"))
public void midnightcontrols$drawMCButton(CallbackInfo ci) {
if (this.getClass().toString().equals(ControlsOptionsScreen.class.toString())) {
this.addDrawableChild(midnightcontrols$button);
}
}
@Inject(method = "refreshWidgetPositions", at = @At("TAIL"))
public void midnightcontrols$onResize(CallbackInfo ci) {
this.midnightcontrols$setButtonPos();
}
@Unique
public void midnightcontrols$setButtonPos() {
if (body != null) {
midnightcontrols$button.setPosition(body.getWidth() / 2 + 158, body.getY() + 4);
}
}
}

View File

@@ -0,0 +1,52 @@
/*
* 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.llamalad7.mixinextras.sugar.Local;
import com.mojang.blaze3d.systems.RenderSystem;
import eu.midnightdust.midnightcontrols.ControlsMode;
import eu.midnightdust.midnightcontrols.client.MidnightControlsClient;
import eu.midnightdust.midnightcontrols.client.MidnightControlsConfig;
import eu.midnightdust.midnightcontrols.client.gui.cursor.VirtualCursorRenderer;
import eu.midnightdust.midnightcontrols.client.gui.cursor.WaylandCursorRenderer;
import eu.midnightdust.midnightcontrols.client.touch.TouchUtils;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gui.DrawContext;
import net.minecraft.client.render.GameRenderer;
import net.minecraft.client.render.RenderTickCounter;
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;
@Mixin(GameRenderer.class)
public abstract class GameRendererMixin {
@Shadow @Final private MinecraftClient client;
@Inject(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/Mouse;getScaledX(Lnet/minecraft/client/util/Window;)D", shift = At.Shift.BEFORE))
private void midnightcontrols$onRender(RenderTickCounter tickCounter, boolean tick, CallbackInfo ci) {
if (this.client.currentScreen != null && MidnightControlsConfig.controlsMode == ControlsMode.CONTROLLER)
MidnightControlsClient.input.onPreRenderScreen(this.client.currentScreen);
}
@Inject(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screen/Screen;renderWithTooltip(Lnet/minecraft/client/gui/DrawContext;IIF)V", shift = At.Shift.AFTER))
private void midnightcontrols$renderVirtualCursor(RenderTickCounter tickCounter, boolean tick, CallbackInfo ci, @Local DrawContext drawContext) {
VirtualCursorRenderer.getInstance().renderCursor(drawContext, client);
if (MidnightControlsClient.isWayland) WaylandCursorRenderer.getInstance().renderCursor(drawContext, client);
}
@Inject(at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/GameRenderer;renderHand(FZLorg/joml/Matrix4f;)V"), method = "renderWorld")
private void midnigtcontrols$captureMatrices(RenderTickCounter tickCounter, CallbackInfo ci, @Local(ordinal = 0) Matrix4f projectionMatrix, @Local(ordinal = 1) Matrix4f worldSpaceMatrix) {
TouchUtils.lastProjMat.set(projectionMatrix);
TouchUtils.lastModMat.set(RenderSystem.getModelViewMatrix());
TouchUtils.lastWorldSpaceMatrix.set(worldSpaceMatrix);
}
}

View File

@@ -9,23 +9,22 @@
package eu.midnightdust.midnightcontrols.client.mixin;
import eu.midnightdust.lib.util.PlatformFunctions;
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.DrawContext;
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;
@@ -37,8 +36,6 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
*/
@Mixin(HandledScreen.class)
public abstract class HandledScreenMixin implements HandledScreenAccessor {
@Unique private static float scale = 1f;
@Accessor("x")
public abstract int getX();
@@ -49,38 +46,34 @@ public abstract class HandledScreenMixin implements HandledScreenAccessor {
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);
public abstract boolean midnightcontrols$isClickOutsideBounds(double mouseX, double mouseY, int left, int top);
@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) {
public void onRender(DrawContext context, int mouseX, int mouseY, float delta, CallbackInfo ci) {
if (MidnightControlsConfig.controlsMode == ControlsMode.CONTROLLER && MidnightControlsConfig.hudEnable) {
var client = MinecraftClient.getInstance();
if (client.options.getGuiScale().getValue() >= 4) {
scale = 0.75f;
} else scale = 1f;
if (scale != 1f) matrices.scale(scale,scale,scale);
int x = 2, y = (int) (client.getWindow().getScaledHeight() * (1 / scale) - 2 - MidnightControlsRenderer.ICON_SIZE);
if (MidnightControlsCompat.isEMIPresent()) {
x += 40 * (1 / scale);
int x = 2, y = client.getWindow().getScaledHeight() - 2 - MidnightControlsRenderer.ICON_SIZE;
if (PlatformFunctions.isModLoaded("emi") && EMICompat.isEMIEnabled()) {
x += 42;
}
x = MidnightControlsRenderer.drawButtonTip(matrices, x, y, new int[]{GLFW.GLFW_GAMEPAD_BUTTON_A}, "midnightcontrols.action.pickup_all", true, client) + 2;
x = MidnightControlsRenderer.drawButtonTip(matrices, x, y, new int[]{GLFW.GLFW_GAMEPAD_BUTTON_B}, "midnightcontrols.action.exit", true, client) + 2;
if (MidnightControlsCompat.isReiPresent()) {
if (!ButtonBinding.TAKE_ALL.isNotBound()) x = MidnightControlsRenderer.drawButtonTip(context, x, y, ButtonBinding.TAKE_ALL,true, client) + 2;
if (!ButtonBinding.EXIT.isNotBound()) x = MidnightControlsRenderer.drawButtonTip(context, x, y, ButtonBinding.EXIT, true, client) + 2;
if (PlatformFunctions.isModLoaded("roughlyenoughitems")) {
x = 2;
y -= 24;
}
if (MidnightControlsCompat.isEMIPresent()) {
x = (int) (client.getWindow().getScaledWidth() * (1 / scale) - 55 - client.textRenderer.getWidth(Text.translatable("midnightcontrols.action.pickup"))
* (1 / scale) - client.textRenderer.getWidth(Text.translatable("midnightcontrols.action.quick_move"))
- MidnightControlsRenderer.getBindingIconWidth(ButtonBinding.TAKE) - MidnightControlsRenderer.getBindingIconWidth(ButtonBinding.QUICK_MOVE));
if (PlatformFunctions.isModLoaded("emi") && EMICompat.isEMIEnabled() && EMICompat.isSearchBarCentered()) {
x = client.getWindow().getScaledWidth() - 4 - client.textRenderer.getWidth(Text.translatable("midnightcontrols.action.pickup"))
- client.textRenderer.getWidth(Text.translatable("midnightcontrols.action.quick_move"))
- 2 * MidnightControlsRenderer.getBindingIconWidth(ButtonBinding.TAKE) - MidnightControlsRenderer.getBindingIconWidth(ButtonBinding.QUICK_MOVE);
y += 2;
}
x = MidnightControlsRenderer.drawButtonTip(matrices, x, y, new int[]{GLFW.GLFW_GAMEPAD_BUTTON_X}, "midnightcontrols.action.pickup", true, client);
MidnightControlsRenderer.drawButtonTip(matrices, x, y, new int[]{GLFW.GLFW_GAMEPAD_BUTTON_Y}, "midnightcontrols.action.quick_move", true, client);
if (scale != 1f) matrices.scale(1,1,1);
if (!ButtonBinding.TAKE.isNotBound()) x = MidnightControlsRenderer.drawButtonTip(context, x, y, ButtonBinding.TAKE, true, client);
if (!ButtonBinding.QUICK_MOVE.isNotBound()) MidnightControlsRenderer.drawButtonTip(context, x, y, ButtonBinding.QUICK_MOVE, true, client);
}
}
}

View File

@@ -0,0 +1,30 @@
package eu.midnightdust.midnightcontrols.client.mixin;
import eu.midnightdust.midnightcontrols.client.gui.MidnightControlsHud;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gui.DrawContext;
import net.minecraft.client.gui.hud.InGameHud;
import net.minecraft.client.render.RenderTickCounter;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(InGameHud.class)
public class InGameHudMixin {
@Inject(method = "<init>", at = @At("TAIL"))
private static void midnightcontrols$initHud(MinecraftClient client, CallbackInfo ci) {
MidnightControlsHud.getInstance().init();
}
@Inject(method = "renderMainHud", at = @At("HEAD"))
public void midnightcontrols$renderHud(DrawContext context, RenderTickCounter tickCounter, CallbackInfo ci) {
MidnightControlsHud.getInstance().render(context, tickCounter);
}
@Inject(method = "tick()V", at = @At("HEAD"))
public void midnightcontrols$tickHud(CallbackInfo ci) {
MidnightControlsHud.getInstance().tick();
}
}

View File

@@ -0,0 +1,12 @@
package eu.midnightdust.midnightcontrols.client.mixin;
import net.minecraft.client.input.Input;
import net.minecraft.util.math.Vec2f;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
@Mixin(Input.class)
public interface InputAccessor {
@Accessor
void setMovementVector(Vec2f input);
}

View File

@@ -0,0 +1,24 @@
package eu.midnightdust.midnightcontrols.client.mixin;
import net.minecraft.client.util.InputUtil;
import org.spongepowered.asm.mixin.Mixin;
import eu.midnightdust.midnightcontrols.client.MidnightControlsConfig;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(InputUtil.class)
public abstract class InputUtilMixin {
/**
* @author kabliz
* @reason This method is static, and there is a terrible UX issue if raw input is turned on at the same time as
* eye tracking. Raw input only tracks literal mice and not other devices, leading to the game appearing to be
* unresponsive and the player not understanding why. This overwrite preserves the user's mouse preferences,
* while not interfering with eye tracking, and the two modes can be switched between during a play session.
*/
@Inject(method = "isRawMouseMotionSupported", at = @At("HEAD"), cancellable = true)
private static void setRawMouseMotionSupported(CallbackInfoReturnable<Boolean> cir) {
if (MidnightControlsConfig.eyeTrackerAsMouse) cir.setReturnValue(false);
}
}

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

@@ -15,7 +15,7 @@ import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
@Mixin(KeyBinding.class)
public class KeyBindingMixin implements KeyBindingAccessor {
public abstract class KeyBindingMixin implements KeyBindingAccessor {
@Shadow
private int timesPressed;

View File

@@ -0,0 +1,12 @@
package eu.midnightdust.midnightcontrols.client.mixin;
import net.minecraft.client.Keyboard;
import net.minecraft.client.input.KeyInput;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Invoker;
@Mixin(Keyboard.class)
public interface KeyboardAccessor {
@Invoker("onKey")
void midnightcontrols$onKey(long window, int action, KeyInput input);
}

View File

@@ -0,0 +1,18 @@
package eu.midnightdust.midnightcontrols.client.mixin;
import eu.midnightdust.midnightcontrols.client.touch.gui.TouchscreenOverlay;
import net.minecraft.client.Keyboard;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gui.screen.Screen;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
@Mixin(Keyboard.class)
public class KeyboardMixin {
@Redirect(method = "onKey", at = @At(value = "FIELD", target = "Lnet/minecraft/client/MinecraftClient;currentScreen:Lnet/minecraft/client/gui/screen/Screen;"))
private Screen midnightcontrols$ignoreTouchOverlay(MinecraftClient instance) {
if (instance.currentScreen instanceof TouchscreenOverlay) return null;
return instance.currentScreen;
}
}

View File

@@ -0,0 +1,170 @@
/*
* 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.llamalad7.mixinextras.sugar.Local;
import eu.midnightdust.midnightcontrols.MidnightControlsFeature;
import eu.midnightdust.midnightcontrols.client.MidnightControlsClient;
import eu.midnightdust.midnightcontrols.client.MidnightControlsConfig;
import eu.midnightdust.midnightcontrols.client.touch.gui.TouchscreenOverlay;
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.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 net.minecraft.util.profiler.Profiler;
import org.jetbrains.annotations.Nullable;
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.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import static eu.midnightdust.midnightcontrols.client.MidnightControlsClient.client;
import static eu.midnightdust.midnightcontrols.client.MidnightControlsClient.reacharound;
@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;
@Shadow public abstract void setScreen(Screen screen);
@Shadow protected int attackCooldown;
@Shadow protected abstract void handleInputEvents();
@Unique private BlockPos midnightcontrols$lastTargetPos;
@Unique private Vec3d midnightcontrols$lastPos;
@Unique private Direction midnightcontrols$lastTargetSide;
@Inject(method = "<init>", at = @At("RETURN"))
private void onInit(CallbackInfo ci) {
MidnightControlsClient.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.getEntityPos();
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.getMovementInput().y < 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.getEntityPos();
}
@Inject(at = @At("TAIL"), method = "setScreen")
private void setScreen(Screen screen, CallbackInfo info) {
if (MidnightControlsConfig.hideNormalMouse){
if (screen != null && !(screen instanceof TouchscreenOverlay)) GLFW.glfwSetInputMode(MinecraftClient.getInstance().getWindow().getHandle(), GLFW.GLFW_CURSOR, GLFW.GLFW_CURSOR_HIDDEN);
else GLFW.glfwSetInputMode(MinecraftClient.getInstance().getWindow().getHandle(), GLFW.GLFW_CURSOR, GLFW.GLFW_CURSOR_DISABLED);
}
MidnightControlsClient.onScreenOpen(screen);
}
@Inject(method = "doItemUse()V", at = @At(value = "INVOKE", target = "Lnet/minecraft/util/hit/HitResult;getType()Lnet/minecraft/util/hit/HitResult$Type;"), cancellable = true)
private void onItemUse(CallbackInfo ci, @Local Hand hand, @Local ItemStack stackInHand) {
if (player != null && !stackInHand.isEmpty() && this.player.getPitch(0.f) > 35.0F && reacharound.isReacharoundAvailable()) {
if (this.crosshairTarget != null && this.crosshairTarget.getType() == HitResult.Type.MISS && this.player.isOnGround()) {
if (!stackInHand.isEmpty() && stackInHand.getItem() instanceof BlockItem) {
var hitResult = reacharound.getLastReacharoundResult();
if (hitResult == null || this.interactionManager == null)
return;
hitResult = 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.player.isInCreativeMode())) {
this.gameRenderer.firstPersonRenderer.resetEquipProgress(hand);
}
//}
ci.cancel();
}
if (result == ActionResult.FAIL) {
ci.cancel();
}
}
}
}
}
// TODO: Replace this with MixinExtras' Expressions once that's officially released
@Inject(method = "tick", at = @At(value = "INVOKE",target = "Lnet/minecraft/client/gui/hud/DebugHud;shouldShowDebugHud()Z"))
private void midnightcontrols$handleKeybindsWithTouchOverlay(CallbackInfo ci, @Local Profiler profiler) {
if (client.currentScreen instanceof TouchscreenOverlay) {
profiler.swap("Keybindings");
this.handleInputEvents();
if (this.attackCooldown > 0) {
--this.attackCooldown;
}
}
}
// Needed, as it will cause item actions not to work in touchscreen mode otherwise with the above method
@Inject(method = "handleInputEvents", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/network/ClientPlayerEntity;isUsingItem()Z"), cancellable = true)
private void midnightcontrols$dontHandleItemAndBlockInteractions(CallbackInfo ci) {
if (client.currentScreen instanceof TouchscreenOverlay) ci.cancel();
}
}

View File

@@ -0,0 +1,14 @@
package eu.midnightdust.midnightcontrols.client.mixin;
import net.minecraft.client.Mouse;
import net.minecraft.client.input.MouseInput;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Invoker;
@Mixin(Mouse.class)
public interface MouseAccessor {
@Invoker("onCursorPos")
void midnightcontrols$onCursorPos(long window, double x, double y);
@Invoker("onMouseButton")
void midnightcontrols$onMouseButton(long window, MouseInput input, int action);
}

View File

@@ -0,0 +1,156 @@
/*
* 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.touch.gui.TouchscreenOverlay;
import eu.midnightdust.midnightcontrols.client.touch.TouchInput;
import eu.midnightdust.midnightcontrols.client.touch.TouchUtils;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.Mouse;
import net.minecraft.client.gui.Click;
import net.minecraft.client.input.MouseInput;
import net.minecraft.client.util.GlfwUtil;
import net.minecraft.item.ItemStack;
import net.minecraft.item.ThrowablePotionItem;
import net.minecraft.item.consume.UseAction;
import net.minecraft.util.math.Smoother;
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.Unique;
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;
import eu.midnightdust.midnightcontrols.client.mouse.EyeTrackerHandler;
import static eu.midnightdust.midnightcontrols.client.MidnightControlsConfig.doMixedInput;
import static org.lwjgl.glfw.GLFW.*;
/**
* Adds extra access to the mouse.
*/
@Mixin(Mouse.class)
public abstract class MouseMixin implements MouseAccessor {
@Shadow @Final private MinecraftClient client;
@Shadow private double y;
@Shadow private double cursorDeltaX;
@Shadow private double cursorDeltaY;
@Shadow private double x;
@Shadow private boolean cursorLocked;
@Shadow private boolean hasResolutionChanged;
@Shadow private double glfwTime;
@Shadow @Final private Smoother cursorXSmoother;
@Shadow @Final private Smoother cursorYSmoother;
@Shadow private boolean leftButtonClicked;
@Inject(method = "onMouseButton", at = @At(value = "HEAD"), cancellable = true)
private void midnightcontrols$onMouseButton(long window, MouseInput input, int action, CallbackInfo ci) {
if (window != this.client.getWindow().getHandle()) return;
if (action == 1 && input.button() == GLFW.GLFW_MOUSE_BUTTON_4 && client.currentScreen != null) {
MidnightControlsClient.input.tryGoBack(client.currentScreen);
}
else if ((client.currentScreen == null && doMixedInput() || client.currentScreen instanceof TouchscreenOverlay) && client.player != null && input.button() == GLFW_MOUSE_BUTTON_1) {
double mouseX = x / client.getWindow().getScaleFactor();
double mouseY = y / client.getWindow().getScaleFactor();
int centerX = client.getWindow().getScaledWidth() / 2;
if (action == 1 && mouseY >= (double) (client.getWindow().getScaledHeight() - 22) && 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)) {
client.player.getInventory().setSelectedSlot(slot);
TouchInput.clickStartTime = -1;
ci.cancel();
return;
}
}
}
if (action == 1) {
TouchInput.clickStartTime = System.currentTimeMillis();
boolean bl = false;
if (client.currentScreen instanceof TouchscreenOverlay overlay) bl = overlay.mouseClicked(new Click(mouseX, mouseY, input), false);
if (!bl) TouchInput.firstHitResult = TouchUtils.getTargetedObject(mouseX, mouseY);
if (client.currentScreen == null) ci.cancel();
}
else if (TouchInput.mouseReleased(mouseX, mouseY, input.button())) ci.cancel();
}
}
@Inject(method = "isCursorLocked", at = @At("HEAD"), cancellable = true)
private void midnightcontrols$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 midnightcontrols$onCursorLocked(CallbackInfo ci) {
if ((MidnightControlsConfig.controlsMode == ControlsMode.CONTROLLER && MidnightControlsConfig.virtualMouse) ||
MidnightControlsConfig.controlsMode == ControlsMode.TOUCHSCREEN || doMixedInput())
ci.cancel();
}
@Inject(method = "updateMouse", at = @At("HEAD"), cancellable = true)
private void midnightcontrols$updateMouse(CallbackInfo ci) {
if (MidnightControlsConfig.eyeTrackerAsMouse && cursorLocked && client.isWindowFocused()) {
// Eye Tracking is only for the camera controlling cursor, we need the normal cursor everywhere else.
if (!client.options.smoothCameraEnabled) {
cursorXSmoother.clear();
cursorYSmoother.clear();
}
EyeTrackerHandler.updateMouseWithEyeTracking(x + cursorDeltaX, y + cursorDeltaY, client,
glfwTime, leftButtonClicked, midnightcontrols$isUsingLongRangedTool(), cursorXSmoother, cursorYSmoother);
glfwTime = GlfwUtil.getTime();
cursorDeltaX = 0.0;
cursorDeltaY = 0.0;
ci.cancel();
}
if (doMixedInput() && client.isWindowFocused()) {
ci.cancel();
}
}
@Unique
private boolean midnightcontrols$isUsingLongRangedTool() {
if (client.player == null) return false;
ItemStack stack = client.player.getActiveItem();
return (leftButtonClicked && (stack.getUseAction() == UseAction.BOW || stack.getUseAction() == UseAction.CROSSBOW ||
stack.getUseAction() == UseAction.SPEAR || stack.getItem() instanceof ThrowablePotionItem));
}
@Inject(method = "lockCursor", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/util/InputUtil;setCursorParameters(Lnet/minecraft/client/util/Window;IDD)V",shift = At.Shift.BEFORE), cancellable = true)
private void midnightcontrols$lockCursor(CallbackInfo ci) {
if ((doMixedInput() || MidnightControlsConfig.eyeTrackerAsMouse)) {
//In eye tracking mode, we cannot have the cursor locked to the center.
GLFW.glfwSetInputMode(client.getWindow().getHandle(), GLFW_CURSOR, GLFW_CURSOR_HIDDEN);
client.setScreen(null);
hasResolutionChanged = true;
ci.cancel();
}
}
}

View File

@@ -0,0 +1,21 @@
/*
* 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.RecipeBookScreen;
import net.minecraft.client.gui.screen.recipebook.RecipeBookWidget;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
@Mixin(RecipeBookScreen.class)
public interface RecipeBookScreenAccessor {
@Accessor("recipeBook")
RecipeBookWidget<?> getRecipeBook();
}

View File

@@ -27,7 +27,4 @@ public interface RecipeBookWidgetAccessor {
@Accessor("currentTab")
void setCurrentTab(RecipeGroupButtonWidget currentTab);
@Invoker("refreshResults")
void midnightcontrols$refreshResults(boolean resetCurrentPage);
}

View File

@@ -0,0 +1,38 @@
package eu.midnightdust.midnightcontrols.client.mixin;
import dev.lambdaurora.spruceui.Position;
import eu.midnightdust.midnightcontrols.ControlsMode;
import eu.midnightdust.midnightcontrols.client.enums.ButtonState;
import eu.midnightdust.midnightcontrols.client.MidnightControlsConfig;
import eu.midnightdust.midnightcontrols.client.controller.ButtonBinding;
import eu.midnightdust.midnightcontrols.client.controller.InputHandlers;
import eu.midnightdust.midnightcontrols.client.touch.gui.SilentTexturedButtonWidget;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gui.Drawable;
import net.minecraft.client.gui.Element;
import net.minecraft.client.gui.Selectable;
import net.minecraft.client.gui.screen.Screen;
import net.minecraft.client.gui.screen.ingame.HandledScreen;
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;
import static eu.midnightdust.midnightcontrols.client.touch.gui.TouchscreenOverlay.WIDGETS_LOCATION;
@Mixin(Screen.class)
public abstract class ScreenMixin {
@Shadow protected abstract <T extends Element & Drawable & Selectable> T addDrawableChild(T drawableElement);
@Shadow public int width;
@Inject(method = "init(II)V", at = @At("TAIL"))
public void midnightcontrols$addCloseButton(int width, int height, CallbackInfo ci) {
if (MidnightControlsConfig.controlsMode == ControlsMode.TOUCHSCREEN && (MidnightControlsConfig.closeButtonScreens.stream().anyMatch(s -> this.getClass().getName().startsWith(s) || ((Object)this) instanceof HandledScreen<?>))) {
this.addDrawableChild(new SilentTexturedButtonWidget(Position.of(this.width - 30, 10), 20, 20, Text.empty(), btn ->
InputHandlers.handleExit().press(MinecraftClient.getInstance(), ButtonBinding.BACK, 0f, ButtonState.PRESS), 20, 160, 20, WIDGETS_LOCATION));
}
}
}

View File

@@ -0,0 +1,16 @@
package eu.midnightdust.midnightcontrols.client.mixin;
import com.google.common.collect.ImmutableList;
import net.minecraft.client.gui.tab.Tab;
import net.minecraft.client.gui.tab.TabManager;
import net.minecraft.client.gui.widget.TabNavigationWidget;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
@Mixin(TabNavigationWidget.class)
public interface TabNavigationWidgetAccessor {
@Accessor
TabManager getTabManager();
@Accessor
ImmutableList<Tab> getTabs();
}

View File

@@ -0,0 +1,120 @@
/*
* 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.ControlsMode;
import eu.midnightdust.midnightcontrols.client.MidnightControlsConfig;
import eu.midnightdust.midnightcontrols.client.touch.TouchInput;
import eu.midnightdust.midnightcontrols.client.enums.TouchMode;
import eu.midnightdust.midnightcontrols.client.util.RainbowColor;
import net.minecraft.block.ShapeContext;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.render.*;
import net.minecraft.client.render.state.WorldRenderState;
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.BlockHitResult;
import net.minecraft.util.hit.HitResult;
import net.minecraft.util.math.ColorHelper;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.awt.*;
import static eu.midnightdust.midnightcontrols.client.MidnightControlsClient.reacharound;
/**
* 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;
@Inject(
method = "renderTargetBlockOutline",
at = @At("HEAD"),
cancellable = true
)
private void onOutlineRender(VertexConsumerProvider.Immediate immediate, MatrixStack matrices, boolean renderBlockOutline, WorldRenderState renderStates, CallbackInfo ci) {
if (((MidnightControlsConfig.controlsMode == ControlsMode.CONTROLLER && MidnightControlsConfig.touchInControllerMode) || MidnightControlsConfig.controlsMode == ControlsMode.TOUCHSCREEN)
&& MidnightControlsConfig.touchMode == TouchMode.FINGER_POS) {
this.midnightcontrols$renderFingerOutline(immediate, matrices, client.gameRenderer.getCamera());
ci.cancel();
}
this.midnightcontrols$renderReacharoundOutline(immediate, matrices, client.gameRenderer.getCamera());
}
@Unique
private void midnightcontrols$renderFingerOutline(VertexConsumerProvider.Immediate immediate, MatrixStack matrices, Camera camera) {
if (TouchInput.firstHitResult == null || TouchInput.firstHitResult.getType() != HitResult.Type.BLOCK)
return;
BlockHitResult result = (BlockHitResult) TouchInput.firstHitResult;
var blockPos = result.getBlockPos();
if (this.world.getWorldBorder().contains(blockPos) && this.client.player != null) {
var outlineShape = this.world.getBlockState(blockPos).getOutlineShape(this.client.world, blockPos, ShapeContext.of(camera.getFocusedEntity()));
Color rgb = MidnightColorUtil.hex2Rgb(MidnightControlsConfig.touchOutlineColorHex);
if (MidnightControlsConfig.touchOutlineColorHex.isEmpty()) rgb = RainbowColor.radialRainbow(1,1);
var pos = camera.getCameraPos();
matrices.push();
var vertexConsumer = immediate.getBuffer(RenderLayers.lines());
VertexRendering.drawOutline(matrices, vertexConsumer, outlineShape, blockPos.getX() - pos.getX(), blockPos.getY() - pos.getY(), blockPos.getZ() - pos.getZ(),
ColorHelper.withAlpha(MidnightControlsConfig.touchOutlineColorAlpha, rgb.getRGB()), 4);
matrices.pop();
}
}
@Unique
private void midnightcontrols$renderReacharoundOutline(VertexConsumerProvider.Immediate immediate, MatrixStack matrices, Camera camera) {
if (this.client.crosshairTarget == null || this.client.crosshairTarget.getType() != HitResult.Type.MISS || !MidnightControlsConfig.shouldRenderReacharoundOutline)
return;
var result = 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 block = ((BlockItem) stack.getItem()).getBlock();
result = 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.getCameraPos();
var outlineShape = placementState.getOutlineShape(this.client.world, blockPos, ShapeContext.of(camera.getFocusedEntity()));
Color rgb = MidnightColorUtil.hex2Rgb(MidnightControlsConfig.reacharoundOutlineColorHex);
if (MidnightControlsConfig.reacharoundOutlineColorHex.isEmpty()) rgb = RainbowColor.radialRainbow(1,1);
matrices.push();
var vertexConsumer = immediate.getBuffer(RenderLayers.lines());
VertexRendering.drawOutline(matrices, vertexConsumer, outlineShape, blockPos.getX() - pos.getX(), blockPos.getY() - pos.getY(), blockPos.getZ() - pos.getZ(),
ColorHelper.withAlpha(MidnightControlsConfig.touchOutlineColorAlpha, rgb.getRGB()), 4);
matrices.pop();
}
}
}

View File

@@ -0,0 +1,72 @@
package eu.midnightdust.midnightcontrols.client.mouse;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.util.GlfwUtil;
import eu.midnightdust.midnightcontrols.client.MidnightControlsConfig;
import net.minecraft.util.math.Smoother;
public class EyeTrackerHandler {
/**
* Based on the updateMouse method in the Mouse.class, this changes the mouse algorithm to suit eye tracking.
* This requires the cursor to not be locked, and the raw input setting to be turned off.
*/
public static void updateMouseWithEyeTracking(double mouseX, double mouseY, MinecraftClient client, double lastMouseUpdateTime, boolean holdingLeftMouseButton, boolean usingLongRangedTool, Smoother smoothX, Smoother smoothY) {
if (client.player == null) return;
/* The player wants objects of interest to be moved under the crosshair that is always center of screen.
* Normal mouse controls operate with the delta values from the direction of mouse movement,
* but in eye tracking we want to use the cursor's actual x,y values (their point of gaze), relative to
* the screen center (where the crosshair is). This new eye tracking delta creates a vector that points
* from the crosshair to the gaze point. As the player keeps their eyes on the object of interest, we pull
* that object into the center until the object is underneath the crosshair.
*/
double deltaTime = GlfwUtil.getTime() - lastMouseUpdateTime;
// The center of screen is the new (0,0)
double centerX = client.getWindow().getWidth() / 2.0;
double centerY = client.getWindow().getHeight() / 2.0;
double gazeRawX = mouseX - centerX;
double gazeRawY = mouseY - centerY;
//This part follows the original mouse.java somewhat closely, with different constants
double feeling = 2.5;
double sensitivity = client.options.getMouseSensitivity().getValue() * feeling;
double spyglass = sensitivity * sensitivity * sensitivity;
double moveScalar = spyglass * 8.0;
double frameScalar;
if(client.options.getPerspective().isFirstPerson() && client.player.isUsingSpyglass()) {
frameScalar = spyglass;
} else {
frameScalar = moveScalar;
}
if(holdingLeftMouseButton && !usingLongRangedTool) {
frameScalar *= 0.5; //Don't move the camera so much while mining. It's annoying.
}
// The longest vector connects the center to the corner of the screen, so that is our maximum magnitude for
// normalization. We use normalized screen size vector for resolution independent control
double magnitudeMax = Math.sqrt(centerX*centerX + centerY*centerY);
double normalizedX = gazeRawX / magnitudeMax;
double normalizedY = gazeRawY / magnitudeMax;
double moveX = normalizedX * frameScalar;
double moveY = normalizedY * frameScalar;
if (client.options.smoothCameraEnabled) {
moveX = smoothX.smooth(moveX, moveScalar*deltaTime);
moveY = smoothY.smooth(moveY, moveScalar*deltaTime);
}
// The player entity's needs their facing rotated.
double invertY = 1.0;
double moveMagnitude = Math.sqrt(normalizedX*normalizedX + normalizedY*normalizedY);
if (client.options.getInvertMouseY().getValue()) {
invertY = -1.0;
}
boolean notInDeadzone = (moveMagnitude > MidnightControlsConfig.eyeTrackerDeadzone) && !usingLongRangedTool;
if (client.player != null && notInDeadzone) {
client.player.changeLookDirection(moveX, moveY * invertY);
client.getTutorialManager().onUpdateMouse(moveX, moveY);
}
}
}

View File

@@ -0,0 +1,69 @@
/*
* 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.google.gson.Gson;
import eu.midnightdust.midnightcontrols.client.enums.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.DrawContext;
import net.minecraft.client.gui.screen.Screen;
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;
}
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 DrawContext context, @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) {
context.drawCenteredTextWithShadow(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 Gson config) {
return null;
}
}
}

View File

@@ -0,0 +1,119 @@
/*
* 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.MidnightControls;
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 int currentPage = 0;
public MidnightRing() {
}
public void registerAction(@NotNull String name, @NotNull RingAction.Factory factory) {
if (this.actionFactories.containsKey(name)) {
MidnightControls.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.floor(bindingIndex / 8f));
page.actions[bindingIndex - 8 * (MathHelper.floor(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.floor(bindingIndex / 8f));
page.actions[bindingIndex - 8 * (MathHelper.floor(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

@@ -9,11 +9,10 @@
package eu.midnightdust.midnightcontrols.client.ring;
import com.electronwill.nightconfig.core.Config;
import com.google.gson.Gson;
import net.minecraft.client.font.TextRenderer;
import net.minecraft.client.gui.DrawableHelper;
import net.minecraft.client.gui.DrawContext;
import net.minecraft.client.gui.screen.Screen;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.text.Text;
import org.aperlambda.lambdacommon.utils.Nameable;
import org.jetbrains.annotations.NotNull;
@@ -28,21 +27,10 @@ import java.util.function.Supplier;
* @version 1.5.0
* @since 1.4.0
*/
public abstract class RingAction extends DrawableHelper implements Nameable {
protected Config config;
public abstract class RingAction {
protected boolean activated = false;
public RingAction(@NotNull Config config) {
this.config = config;
}
/**
* Gets the text name of the ring action.
*
* @return the text name
*/
public Text getTextName() {
return Text.translatable(this.getName());
public RingAction() {
}
/**
@@ -62,12 +50,12 @@ public abstract class RingAction extends DrawableHelper implements Nameable {
public abstract void onAction(@NotNull RingButtonMode mode);
public void render(@NotNull MatrixStack matrices, @NotNull TextRenderer textRenderer, int x, int y, boolean hovered) {
fill(matrices, x, y, x + MidnightRing.ELEMENT_SIZE, y + MidnightRing.ELEMENT_SIZE, hovered ? 0xbb777777 : 0xbb000000);
drawIcon(matrices, textRenderer, x, y, hovered);
public void render(@NotNull DrawContext context, @NotNull TextRenderer textRenderer, int x, int y, boolean hovered, int index) {
context.fill(x, y, x + MidnightRing.ELEMENT_SIZE, y + MidnightRing.ELEMENT_SIZE, hovered || RingPage.selected == index ? 0xbb777777 : 0xbb000000);
drawIcon(context, textRenderer, x, y, hovered);
}
public abstract void drawIcon(@NotNull MatrixStack matrices, @NotNull TextRenderer textRenderer, int x, int y, boolean hovered);
public abstract void drawIcon(@NotNull DrawContext context, @NotNull TextRenderer textRenderer, int x, int y, boolean hovered);
/**
* Represents a factory for {@link RingAction}.
@@ -78,6 +66,6 @@ public abstract class RingAction extends DrawableHelper implements Nameable {
public interface Factory {
@NotNull Supplier<RingAction> newFromGui(@NotNull Screen screen);
@Nullable RingAction parse(@NotNull Config config);
@Nullable RingAction parse(@NotNull Gson config);
}
}

View File

@@ -9,19 +9,11 @@
package eu.midnightdust.midnightcontrols.client.ring;
import com.electronwill.nightconfig.core.Config;
import eu.midnightdust.midnightcontrols.client.MidnightControlsClient;
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 net.minecraft.client.gui.DrawContext;
import org.jetbrains.annotations.NotNull;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
/**
* Represents a ring page.
*
@@ -29,37 +21,34 @@ import java.util.Optional;
* @version 1.5.0
* @since 1.4.0
*/
public class RingPage extends DrawableHelper {
public class RingPage {
public static final RingPage DEFAULT = new RingPage("Default");
public final String name;
private RingAction[] actions = new RingAction[8];
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;
}
this.actions[0] = new DummyRingAction(null);
this.actions[1] = new KeyBindingRingAction(null, MidnightControlsClient.BINDING_LOOK_UP);
this.actions[2] = new KeyBindingRingAction(null, MidnightControlsClient.BINDING_LOOK_LEFT);
this.actions[3] = new KeyBindingRingAction(null, MidnightControlsClient.BINDING_LOOK_RIGHT);
this.actions[4] = new KeyBindingRingAction(null, MidnightControlsClient.BINDING_LOOK_DOWN);
}
/**
* Renders the ring page.
*
* @param matrices the matrices
* @param context the context
* @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) {
public void render(@NotNull DrawContext context, @NotNull TextRenderer textRenderer, int width, int height, int mouseX, int mouseY, float tickDelta) {
int centerX = width / 2;
int centerY = height / 2;
if (MidnightControlsClient.ring.getMaxPages() > 1) context.drawCenteredTextWithShadow(textRenderer, name, centerX, 5, 0xffffff);
int offset = MidnightRing.ELEMENT_SIZE + (MidnightRing.ELEMENT_SIZE / 2) + 5;
@@ -68,29 +57,29 @@ public class RingPage extends DrawableHelper {
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));
x += 55;
ringAction.render(context, textRenderer, x, y, isHovered(x, y, mouseX, mouseY), i);
x += MidnightRing.ELEMENT_SIZE + 5;
}
y += 55;
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));
x += 55 * 2;
ringAction.render(context, textRenderer, x, y, isHovered(x, y, mouseX, mouseY), i);
x += (MidnightRing.ELEMENT_SIZE + 5) * 2;
}
y += 55;
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));
x += 55;
ringAction.render(context, 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;
return mouseX >= x && mouseY >= y && mouseX <= x + MidnightRing.ELEMENT_SIZE && mouseY <= y + MidnightRing.ELEMENT_SIZE && selected < 0;
}
/**
* Renders the ring page.
@@ -114,9 +103,9 @@ public class RingPage extends DrawableHelper {
ringAction.activate(RingButtonMode.PRESS);
return true;
}
x += 55;
x += MidnightRing.ELEMENT_SIZE + 5;
}
y += 55;
y += MidnightRing.ELEMENT_SIZE + 5;
x = centerX - offset;
for (int i = 3; i < 5; i++) {
var ringAction = this.actions[i];
@@ -124,9 +113,9 @@ public class RingPage extends DrawableHelper {
ringAction.activate(RingButtonMode.PRESS);
return true;
}
x += 55 * 2;
x += (MidnightRing.ELEMENT_SIZE + 5) * 2;
}
y += 55;
y += MidnightRing.ELEMENT_SIZE + 5;
x = centerX - offset;
for (int i = 5; i < 8; i++) {
var ringAction = this.actions[i];
@@ -134,27 +123,8 @@ public class RingPage extends DrawableHelper {
ringAction.activate(RingButtonMode.PRESS);
return true;
}
x += 55;
x += MidnightRing.ELEMENT_SIZE + 5;
}
return false;
}
/**
* Tries to parse a ring page configuration.
*
* @param config the configuration
* @return an optional ring page
*/
public static @NotNull Optional<RingPage> parseRingPage(@NotNull Config config) {
String name = config.get("name");
if (name == null)
return Optional.empty();
var page = new RingPage(name);
List<Config> actionConfigs = config.get("actions");
return Optional.of(page);
}
}

View File

@@ -0,0 +1,110 @@
package eu.midnightdust.midnightcontrols.client.touch;
import eu.midnightdust.midnightcontrols.client.MidnightControlsConfig;
import eu.midnightdust.midnightcontrols.client.touch.gui.TouchscreenOverlay;
import net.minecraft.block.BlockState;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gui.Click;
import net.minecraft.client.input.MouseInput;
import net.minecraft.client.particle.BlockDustParticle;
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.EntityHitResult;
import net.minecraft.util.hit.HitResult;
import net.minecraft.util.math.BlockPos;
import static eu.midnightdust.midnightcontrols.client.MidnightControlsConfig.doMixedInput;
public class TouchInput {
private static final MinecraftClient client = MinecraftClient.getInstance();
public static long clickStartTime;
public static HitResult firstHitResult = null;
public static boolean isDragging = false;
public static void tick() {
if ((client.currentScreen == null && doMixedInput()) || client.currentScreen instanceof TouchscreenOverlay) {
double scaleFactor = client.getWindow().getScaleFactor();
if (clickStartTime > 0 && System.currentTimeMillis() - clickStartTime >= MidnightControlsConfig.touchBreakDelay) {
mouseHeldDown(client.mouse.getX() / scaleFactor, client.mouse.getY() / scaleFactor);
}
}
}
public static void mouseHeldDown(double mouseX, double mouseY) {
assert client != null;
assert client.player != null;
assert client.interactionManager != null;
if (client.player.getMainHandStack() != null && TouchUtils.hasInWorldUseAction(client.player.getMainHandStack())) {
client.interactionManager.interactItem(client.player, client.player.getActiveHand());
return;
}
HitResult result = TouchUtils.getTargetedObject(mouseX, mouseY);
if (result == null || firstHitResult == null) {
client.interactionManager.cancelBlockBreaking();
return;
}
if (result instanceof BlockHitResult blockHit && firstHitResult instanceof BlockHitResult firstBlock && blockHit.getBlockPos().equals(firstBlock.getBlockPos())) {
if (MidnightControlsConfig.debug) System.out.println(blockHit.getBlockPos().toString());
if (client.interactionManager.updateBlockBreakingProgress(blockHit.getBlockPos(), blockHit.getSide())) {
//client.particleManager.addBlockBreakingParticles(blockHit.getBlockPos(), blockHit.getSide()); // TODO Re-implement block breaking particles!!!
client.player.swingHand(Hand.MAIN_HAND);
} else client.interactionManager.cancelBlockBreaking();
firstHitResult = TouchUtils.getTargetedObject(mouseX, mouseY);
}
else if (result instanceof EntityHitResult entityHit && firstHitResult instanceof EntityHitResult firstEntity && entityHit.getEntity().getUuid().compareTo(firstEntity.getEntity().getUuid()) == 0) {
if (client.interactionManager.interactEntity(client.player, entityHit.getEntity(), client.player.getActiveHand()) == ActionResult.SUCCESS) {
client.player.swingHand(Hand.MAIN_HAND);
}
firstHitResult = TouchUtils.getTargetedObject(mouseX, mouseY);
}
}
public static boolean mouseReleased(double mouseX, double mouseY, int button) {
isDragging = false;
firstHitResult = null;
if (client.interactionManager != null) client.interactionManager.cancelBlockBreaking();
if ((client.currentScreen == null || !client.currentScreen.mouseReleased(new Click(mouseX, mouseY, new MouseInput(button, 0)))) && System.currentTimeMillis() - clickStartTime < MidnightControlsConfig.touchBreakDelay) {
assert client.player != null;
assert client.world != null;
assert client.interactionManager != null;
clickStartTime = -1;
if (client.player.getMainHandStack() != null && TouchUtils.hasInWorldUseAction(client.player.getMainHandStack())) {
client.interactionManager.stopUsingItem(client.player);
return true;
}
HitResult result = TouchUtils.getTargetedObject(mouseX, mouseY);
if (result == null) return false;
if (result instanceof BlockHitResult blockHit) {
BlockPos blockPos = blockHit.getBlockPos().offset(blockHit.getSide());
BlockState state = client.world.getBlockState(blockPos);
if (client.world.isAir(blockPos) || state.isReplaceable()) {
ItemStack stackInHand = client.player.getMainHandStack();
int previousStackCount = stackInHand.getCount();
var interaction = client.interactionManager.interactBlock(client.player, client.player.getActiveHand(), blockHit);
if (interaction.isAccepted()) {
//if (interaction.shouldSwingHand()) {
client.player.swingHand(client.player.preferredHand);
if (!stackInHand.isEmpty() && (stackInHand.getCount() != previousStackCount || client.player.isInCreativeMode())) {
client.gameRenderer.firstPersonRenderer.resetEquipProgress(client.player.preferredHand);
}
//}
return true;
}
}
}
if (result instanceof EntityHitResult entityHit) {
client.interactionManager.attackEntity(client.player, entityHit.getEntity());
client.player.swingHand(Hand.MAIN_HAND);
return true;
}
}
clickStartTime = -1;
return false;
}
}

View File

@@ -0,0 +1,72 @@
package eu.midnightdust.midnightcontrols.client.touch;
import eu.midnightdust.lib.util.PlatformFunctions;
import eu.midnightdust.midnightcontrols.client.MidnightControlsConfig;
import eu.midnightdust.midnightcontrols.client.enums.TouchMode;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.render.Camera;
import net.minecraft.entity.projectile.ProjectileUtil;
import net.minecraft.item.ItemStack;
import net.minecraft.item.consume.UseAction;
import net.minecraft.util.hit.BlockHitResult;
import net.minecraft.util.hit.EntityHitResult;
import net.minecraft.util.hit.HitResult;
import net.minecraft.util.math.Box;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.RaycastContext;
import org.joml.Matrix4f;
import org.joml.Vector3f;
import org.lwjgl.opengl.GL11;
import static eu.midnightdust.midnightcontrols.client.MidnightReacharound.getPlayerRange;
public class TouchUtils {
private static final MinecraftClient client = MinecraftClient.getInstance();
public static final Matrix4f lastWorldSpaceMatrix = new Matrix4f();
public static final Matrix4f lastProjMat = new Matrix4f();
public static final Matrix4f lastModMat = new Matrix4f();
public static HitResult getTargetedObject(double mouseX, double mouseY) {
if (client.player == null || client.world == null || MidnightControlsConfig.touchMode == TouchMode.CROSSHAIR || PlatformFunctions.isModLoaded("vulkanmod")) {
return client.crosshairTarget;
}
Vec3d near = screenSpaceToWorldSpace(mouseX, mouseY, 0);
Vec3d far = screenSpaceToWorldSpace(mouseX, mouseY, 1);
float playerRange = getPlayerRange(client);
EntityHitResult entityCast = ProjectileUtil.raycast(client.player, near, far, Box.from(client.player.getEntityPos()).expand(playerRange), entity -> (!entity.isSpectator() && entity.isAttackable()), playerRange * playerRange);
if (entityCast != null && entityCast.getType() == HitResult.Type.ENTITY) return entityCast;
BlockHitResult result = client.world.raycast(new RaycastContext(near, far, RaycastContext.ShapeType.OUTLINE, RaycastContext.FluidHandling.NONE, client.player));
if (client.player.getEntityPos().distanceTo(result.getPos()) > playerRange) return null;
return result;
}
/* Taken from https://github.com/0x3C50/Renderer/blob/master/src/main/java/me/x150/renderer/util/RendererUtils.java#L270
* Credits to 0x3C50 */
public static Vec3d screenSpaceToWorldSpace(double x, double y, double d) {
Camera camera = client.getEntityRenderDispatcher().camera;
int displayHeight = client.getWindow().getScaledHeight();
int displayWidth = client.getWindow().getScaledWidth();
int[] viewport = new int[4];
GL11.glGetIntegerv(GL11.GL_VIEWPORT, viewport);
Vector3f target = new Vector3f();
Matrix4f matrixProj = new Matrix4f(lastProjMat);
Matrix4f matrixModel = new Matrix4f(lastModMat);
matrixProj.mul(matrixModel)
.mul(lastWorldSpaceMatrix)
.unproject((float) x / displayWidth * viewport[2],
(float) (displayHeight - y) / displayHeight * viewport[3], (float) d, viewport, target);
return new Vec3d(target.x, target.y, target.z).add(camera.getCameraPos());
}
public static boolean hasInWorldUseAction(ItemStack stack) {
UseAction action = stack.getUseAction();
return action == UseAction.BOW || action == UseAction.BRUSH || action == UseAction.SPEAR;
}
}

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