Compare commits

...

64 Commits

Author SHA1 Message Date
LambdAurora
f62819d9a4 LambdaControls v1.6.0: Fix several bugs and upgrade settings screen. 2021-03-18 13:28:38 +01:00
Yao Chung Hu
978b94176c Update Mexican Spanish Translation (#49)
* Update Mexican Spanish Translation

* Remove old translation key, add missing translation key

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

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

Note: The translation of "Show controls ring" is not final, since ring menu feature is not completed, I am not sure its feature, so current translation is "Show extra keys (select) menu".
2020-09-02 00:00:27 +08:00
LambdAurora
deccb758ea Fix compilation crash. 2020-09-01 14:30:40 +02:00
LambdAurora
b7c60f8651 🔖 LambdaControls v1.5.0: Update to Minecraft 1.16.2. 2020-09-01 14:29:06 +02:00
LambdAurora
1ee75bc4e5 Fix issues, improve REI compat and horizontal reach-around, WIP on ring. 2020-08-25 16:06:38 +02:00
LambdAurora
1f0ddab36b Fix lot of bugs and update to 1.16.2. 2020-08-19 17:58:50 +02:00
LambdAurora
8a919934e2 Merge pull request #24 from joaoh1/1.16
👽 Fix compatibility with Ok Zoomer 4.0.0 betas
2020-08-14 17:47:08 +02:00
joaoh1
0178f5e684 👽 Fix compatibility with Ok Zoomer 4.0.0 betas 2020-08-14 12:40:45 -03:00
LambdAurora
c495e2ae0c Merge pull request #22 from FlashyReese/1.16
Add es_mx localization
2020-08-03 11:23:02 +02:00
Yao Chung Hu
3ecce09775 fix: use of words 2020-08-02 18:59:59 -05:00
Yao Chung Hu
e94d2eb240 new: added es_mx localization 2020-08-02 18:54:35 -05:00
LambdAurora
7571a2aa1b Merge pull request #21 from joaoh1/1.16
🚸 Hide Ok Zoomer's extra keybinds when disabled
2020-07-31 21:40:44 +02:00
joaoh1
d09a225518 🚸 Hide Ok Zoomer's extra keybinds when disabled 2020-07-31 15:55:43 -03:00
LambdAurora
4290d79bd0 Merge pull request #18 from Hambaka/1.16
🌐 Add Simplified Chinese localization.
2020-07-20 21:09:30 +02:00
Hambaka
f9f0a7a18d 🌐 Add Simplified Chinese localization. 2020-07-21 02:49:01 +08:00
LambdAurora
f889fc6367 Fix chording problem. 2020-07-20 19:22:18 +02:00
LambdAurora
7dcbda7e57 Update CHANGELOG. 2020-07-19 00:11:43 +02:00
LambdAurora
bd065f7b5b 🔖 LambdaControls v1.4.1: Fix crash with REI. 2020-07-19 00:10:22 +02:00
LambdAurora
2785d634dc Fix jar builds. 2020-07-18 18:55:44 +02:00
LambdAurora
257f01ec19 🔖 LambdaControls v1.4.0: Analog movements, 1.16.2 compat, better API, etc. 2020-07-18 18:41:46 +02:00
LambdAurora
127c44a046 Update CHANGELOG. 2020-07-01 01:06:48 +02:00
LambdAurora
272ef6b1dd Fix bad mixin. 2020-07-01 00:59:26 +02:00
LambdAurora
155278130f Add analog movements and WIP action ring. 2020-06-30 19:19:29 +02:00
LambdAurora
3f221feb26 Add mouse4 as back in GUIs. 2020-06-30 00:28:27 +02:00
LambdAurora
7cfd8c9b77 Update mappings. 2020-06-29 17:03:04 +02:00
LambdAurora
2c53575d17 🔖 LambdaControls v1.3.2: Fix things, add vertical reacharound and internal API. 2020-06-29 16:06:15 +02:00
LambdAurora
24f7054eff 🔖 LambdaControls v1.3.1: Fix broken inventory interactions and virtual mouse bug. 2020-06-28 22:22:19 +02:00
LambdAurora
4669e446dc 🔖 LambdaControls v1.3.0: Update to Minecraft 1.16.1. 2020-06-27 19:05:20 +02:00
LambdAurora
ff672f05a1 🔖 LambdaControls v1.2.0: Fast block placement, virtual mouse, etc. 2020-06-27 18:40:35 +02:00
LambdAurora
756e7d102d Improve rotation algorithm. 2020-06-27 18:21:46 +02:00
LambdAurora
e676a37c7f Update to 1.16. 2020-06-27 16:08:10 +02:00
LambdAurora
45dd94fd34 🐛 Fix some little bugs. 2020-02-18 19:20:32 +01:00
LambdAurora
f8cc1fcc20 🚧 More WIP on REI as hooking into it is quite difficult. 2020-02-18 18:09:50 +01:00
LambdAurora
d56126c680 🚧 WIP on REI compatiblity. 2020-02-18 10:43:27 +01:00
LambdAurora
5a2fad4445 🚧 WIP on REI compatiblity. 2020-02-18 10:01:39 +01:00
LambdAurora
0eadc054b6 Add proper fast block placing. 2020-02-17 22:52:59 +01:00
LambdAurora
07a296603a 🚧 WIP on fast block placing. 2020-02-17 12:05:06 +01:00
LambdAurora
8e082404f9 Add always more features. 2020-02-16 23:20:21 +01:00
LambdAurora
8efddb24b0 🎨 White outline for front block placing. 2020-02-16 10:55:21 +01:00
LambdAurora
581757aaee Add new recipe book controls and front block outline. 2020-02-16 00:46:03 +01:00
LambdAurora
40dc8d424c 🎨 Update README. 2020-02-15 00:20:18 +01:00
LambdAurora
230a9f6424 🎨 Update README. 2020-02-15 00:19:11 +01:00
LambdAurora
ba01df036b 🎨 Update README. 2020-02-15 00:02:02 +01:00
LambdAurora
ff90d55351 Improve HUD, add autojump option. 2020-02-14 19:30:54 +01:00
LambdAurora
efc2d6284d 🎨 Quick code cleaning. 2020-02-13 11:54:17 +01:00
LambdAurora
9e2c4720e9 🚧 WIP on better button tip rendering. 2020-02-13 11:51:29 +01:00
LambdAurora
8063116820 Some refactor and add missing behaviors in some GUIs. 2020-02-13 00:28:58 +01:00
LambdAurora
0050b0216c 🔖 LambdaControls v1.1.1: Add more precision in the fly drifting tooltips. 2020-02-05 15:39:19 +01:00
LambdAurora
e13569b71d Add precision to some tooltips and prepare for the fast/accurate block placement feature. 2020-02-05 13:28:23 +01:00
132 changed files with 7577 additions and 5608 deletions

4
.gitignore vendored
View File

@@ -1,11 +1,13 @@
#
# LambdAurora's ignore file
#
# v0.12
# v0.13
# JetBrains
.idea/
*.iml
*.ipr
*.iws
## Intellij IDEA
out/
## CLion

View File

@@ -45,3 +45,68 @@ This update also has a backport 1.14.4 version ([#9](https://github.com/LambdAur
- Added reset settings button.
- HUD side affects button indicators now.
- Added support for Advancements tabs.
### v1.1.1
## v1.2.0-1.3.0
- Improved rotation algorithm ([#11](https://github.com/LambdAurora/LambdaControls/issues/11)).
- Added virtual mouse.
- Added outline on front block placing.
- Added fast block placement ([#8](https://github.com/LambdAurora/LambdaControls/issues/8)).
- Added [REI](https://www.curseforge.com/minecraft/mc-mods/roughly-enough-items) compatibility.
- Improved HUD.
- Added recipe book control.
- And more!
- v1.3.0 specific: Updated to Minecraft 1.16.1
### v1.3.1
- Fixed broken inventory interactions ([#13](https://github.com/LambdAurora/LambdaControls/issues/13))
- Fixed virtual mouse preventing continuous attack (thus making breaking blocks impossible).
- Added support for [ModUpdater](https://gitea.thebrokenrail.com/TheBrokenRail/ModUpdater) hopefully.
- Updated [SpruceUI](https://github.com/LambdAurora/SpruceUI) to 1.5.2.
### v1.3.2
- Added vertical reacharound.
- Added more API for compatibility handlers.
- Improved reacharound API.
- Improved [REI](https://www.curseforge.com/minecraft/mc-mods/roughly-enough-items) compatibility.
## v1.4.0
- Added analog movements ([#10](https://github.com/LambdAurora/LambdaControls/issues/10)).
- Improved Ok Zoomer compability.
- Updated [SpruceUI](https://github.com/LambdAurora/SpruceUI) to 1.5.8 to ensure 1.16.2 compability.
- Internal changes:
- Added analog input value to button bindings.
- Replace lot of strings with Texts.
- Improved block outline rendering injection.
- Shadow library jars instead of Jar-in-Jar.
- Fixed crash in inventory ([#16](https://github.com/LambdAurora/LambdaControls/issues/16))
- WIP:
- Started to work on action ring.
- Will allow for better compability with other mods.
- Might be interesting for keyboard users too.
### v1.4.1
- Fixed crash with [REI](https://www.curseforge.com/minecraft/mc-mods/roughly-enough-items).
## v1.5.0
- Added mappings string editor screen.
- Added Simplified Chinese translations ([#18](https://github.com/LambdAurora/LambdaControls/pull/18)).
- Added Mexican Spanish translations ([#22](https://github.com/LambdAurora/LambdaControls/pull/22)).
- Added Xbox 360 button skin and overhauled Xbox button skin.
- Added debug option.
- Respect toggle setting in Accessibility screen.
- Tweaked rotation speeds.
- Updated to Minecraft 1.16.2.
- Updated [SpruceUI](https://github.com/LambdAurora/SpruceUI) to 1.6.4.
- Overhauled REI compatibility.
- Improved horizontal reach-around.
- Fixed crashes with Ok Zoomer.
- Fixed crashes with key unbinding.
- More WIP on keybind ring.

6
HEADER Normal file
View File

@@ -0,0 +1,6 @@
Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
This file is part of LambdaControls.
Licensed under the MIT license. For more information,
see the LICENSE file.

View File

@@ -1,6 +1,6 @@
MIT License
Copyright © 2020 LambdAurora <aurora42lambda@gmail.com>
Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@@ -3,39 +3,53 @@
![Java 8](https://img.shields.io/badge/language-Java%208-9B599A.svg?style=flat-square)
[![GitHub license](https://img.shields.io/github/license/LambdAurora/LambdaControls?style=flat-square)](https://raw.githubusercontent.com/LambdAurora/LambdaControls/master/LICENSE)
![Environment: Client](https://img.shields.io/badge/environment-client-1976d2?style=flat-square)
![Mod loader: Fabric](https://img.shields.io/badge/modloader-Fabric-1976d2?style=flat-square&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAACXBIWXMAAAsTAAALEwEAmpwYAAAFHGlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxNDIgNzkuMTYwOTI0LCAyMDE3LzA3LzEzLTAxOjA2OjM5ICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6cGhvdG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOCAoV2luZG93cykiIHhtcDpDcmVhdGVEYXRlPSIyMDE4LTEyLTE2VDE2OjU0OjE3LTA4OjAwIiB4bXA6TW9kaWZ5RGF0ZT0iMjAxOS0wNy0yOFQyMToxNzo0OC0wNzowMCIgeG1wOk1ldGFkYXRhRGF0ZT0iMjAxOS0wNy0yOFQyMToxNzo0OC0wNzowMCIgZGM6Zm9ybWF0PSJpbWFnZS9wbmciIHBob3Rvc2hvcDpDb2xvck1vZGU9IjMiIHBob3Rvc2hvcDpJQ0NQcm9maWxlPSJzUkdCIElFQzYxOTY2LTIuMSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDowZWRiMWMyYy1mZjhjLWU0NDEtOTMxZi00OTVkNGYxNGM3NjAiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MGVkYjFjMmMtZmY4Yy1lNDQxLTkzMWYtNDk1ZDRmMTRjNzYwIiB4bXBNTTpPcmlnaW5hbERvY3VtZW50SUQ9InhtcC5kaWQ6MGVkYjFjMmMtZmY4Yy1lNDQxLTkzMWYtNDk1ZDRmMTRjNzYwIj4gPHhtcE1NOkhpc3Rvcnk+IDxyZGY6U2VxPiA8cmRmOmxpIHN0RXZ0OmFjdGlvbj0iY3JlYXRlZCIgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDowZWRiMWMyYy1mZjhjLWU0NDEtOTMxZi00OTVkNGYxNGM3NjAiIHN0RXZ0OndoZW49IjIwMTgtMTItMTZUMTY6NTQ6MTctMDg6MDAiIHN0RXZ0OnNvZnR3YXJlQWdlbnQ9IkFkb2JlIFBob3Rvc2hvcCBDQyAyMDE4IChXaW5kb3dzKSIvPiA8L3JkZjpTZXE+IDwveG1wTU06SGlzdG9yeT4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz4/HiGMAAAAtUlEQVRYw+XXrQqAMBQF4D2P2eBL+QIG8RnEJFaNBjEum+0+zMQLtwwv+wV3ZzhhMDgfJ0wUSinxZUQWgKos1JP/AbD4OneIDyQPwCFniA+EJ4CaXm4TxAXCC0BNHgLhAdAnx9hC8PwGSRtAFVMQjF7cNTWED8B1cgwW20yfJgAvrssAsZ1cB3g/xckAxr6FmCDU5N6f488BrpCQ4rQBJkiMYh4ACmLzwOQF0CExinkCsvw7vgGikl+OotaKRwAAAABJRU5ErkJggg==)
[![Mod loader: Fabric]][fabric]
![Version](https://img.shields.io/github/v/tag/LambdAurora/LambdaControls?label=version&style=flat-square)
[![CurseForge](https://cf.way2muchnoise.eu/title/354231.svg)](https://www.curseforge.com/minecraft/mc-mods/lambdacontrols)
[![CurseForge](http://cf.way2muchnoise.eu/title/354231.svg)](https://www.curseforge.com/minecraft/mc-mods/lambdacontrols)
A Fabric Minecraft mod which adds better controls like controller support.
A Fabric Minecraft mod which adds better controls, reach-around and controller support.
## What's this mod?
This mod adds better controls, reach-around features, etc.
Haven't you dreamed to travel in your modded Minecraft world with your controller? Yes? Then this mod is made for you!
This mod adds a controller support (and an experimental touchscreen support).
This mod also adds controller support.
## ✅ Features:
- Controller support
- Touchscreen support (very experimental and buggy).
- Keyboard controls to look around.
- Toggleable on screen button indicator (like in Bedrock Edition).
- Many Bedrock Edition features:
- Controller support
- Touchscreen support (very experimental and buggy).
- Keyboard controls to look around.
- Toggleable on screen button indicator (like in Bedrock Edition).
- Vertical reach-around.
- Many Bedrock Edition features:
- Toggleable fly drifting
- Front block placing (be careful with this one)
- New controls settings!
- Many options in config to change to your liking.
- Many controllers supported and in a simply way your own controller mappings.
- New controls settings!
- Many options in config to change to your liking.
- Many controllers supported and in a simply way your own controller mappings.
- An easy API for developers to add their own button bindings.
## 🎮 Supported Controllers:
- Dualshock controllers
- Xbox controllers
- Switch Pro controllers
- Joycons
- And many more!
- Dualshock controllers
- Xbox controllers
- Switch Pro controllers
- Joycons
- And many more!
## Screenshots
![controller_controls]
![controller_options]
## Build
Just do `./gradlew :fabric:build` and everything should build just fine!
Just do `./gradlew shadowRemapJar` and everything should build just fine!
[controller_controls]: images/controller_controls.png
[controller_options]: images/controller_options.png
[fabric]: https://fabricmc.net
[Mod loader: Fabric]: https://img.shields.io/badge/modloader-Fabric-1976d2?style=flat-square&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAACXBIWXMAAAsTAAALEwEAmpwYAAAFHGlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxNDIgNzkuMTYwOTI0LCAyMDE3LzA3LzEzLTAxOjA2OjM5ICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6cGhvdG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOCAoV2luZG93cykiIHhtcDpDcmVhdGVEYXRlPSIyMDE4LTEyLTE2VDE2OjU0OjE3LTA4OjAwIiB4bXA6TW9kaWZ5RGF0ZT0iMjAxOS0wNy0yOFQyMToxNzo0OC0wNzowMCIgeG1wOk1ldGFkYXRhRGF0ZT0iMjAxOS0wNy0yOFQyMToxNzo0OC0wNzowMCIgZGM6Zm9ybWF0PSJpbWFnZS9wbmciIHBob3Rvc2hvcDpDb2xvck1vZGU9IjMiIHBob3Rvc2hvcDpJQ0NQcm9maWxlPSJzUkdCIElFQzYxOTY2LTIuMSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDowZWRiMWMyYy1mZjhjLWU0NDEtOTMxZi00OTVkNGYxNGM3NjAiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MGVkYjFjMmMtZmY4Yy1lNDQxLTkzMWYtNDk1ZDRmMTRjNzYwIiB4bXBNTTpPcmlnaW5hbERvY3VtZW50SUQ9InhtcC5kaWQ6MGVkYjFjMmMtZmY4Yy1lNDQxLTkzMWYtNDk1ZDRmMTRjNzYwIj4gPHhtcE1NOkhpc3Rvcnk+IDxyZGY6U2VxPiA8cmRmOmxpIHN0RXZ0OmFjdGlvbj0iY3JlYXRlZCIgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDowZWRiMWMyYy1mZjhjLWU0NDEtOTMxZi00OTVkNGYxNGM3NjAiIHN0RXZ0OndoZW49IjIwMTgtMTItMTZUMTY6NTQ6MTctMDg6MDAiIHN0RXZ0OnNvZnR3YXJlQWdlbnQ9IkFkb2JlIFBob3Rvc2hvcCBDQyAyMDE4IChXaW5kb3dzKSIvPiA8L3JkZjpTZXE+IDwveG1wTU06SGlzdG9yeT4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz4/HiGMAAAAtUlEQVRYw+XXrQqAMBQF4D2P2eBL+QIG8RnEJFaNBjEum+0+zMQLtwwv+wV3ZzhhMDgfJ0wUSinxZUQWgKos1JP/AbD4OneIDyQPwCFniA+EJ4CaXm4TxAXCC0BNHgLhAdAnx9hC8PwGSRtAFVMQjF7cNTWED8B1cgwW20yfJgAvrssAsZ1cB3g/xckAxr6FmCDU5N6f488BrpCQ4rQBJkiMYh4ACmLzwOQF0CExinkCsvw7vgGikl+OotaKRwAAAABJRU5ErkJggg==

View File

@@ -1,51 +1,170 @@
buildscript {
dependencies {
constraints {
["asm", "asm-util", "asm-tree", "asm-analysis"].each {
classpath("org.ow2.asm:$it") {
version { require("9.1") }
because("Fabric's TinyRemapper requires ASM 9")
}
}
}
}
}
plugins {
id 'java-library'
id 'maven-publish'
id 'fabric-loom' version '0.6-SNAPSHOT'
id 'java-library'
id 'maven-publish'
id 'com.github.johnrengelman.shadow' version '6.1.0'
id 'org.cadixdev.licenser' version '0.5.0'
}
allprojects {
group = project.maven_group
version = project.mod_version
import net.fabricmc.loom.task.RemapJarTask
repositories {
mavenLocal()
mavenCentral()
maven { url = 'https://aperlambda.github.io/maven' }
}
group = project.maven_group
version = "${project.mod_version}+${getMCVersionString()}"
archivesBaseName = project.archives_base_name + "-fabric"
dependencies {
}
// This field defines the Java version your mod target.
// The Minecraft launcher currently installs Java 8 for users, so your mod probably wants to target Java 8 too.
def targetJavaVersion = 8
tasks.withType(JavaCompile) {
options.encoding = "UTF-8"
}
// Loom will automatically attach sourcesJar to a RemapSourcesJar task and to the "build" task
// if it is present.
// If you remove this task, sources will not be generated.
task sourcesJar(type: Jar, dependsOn: classes) {
classifier = "sources"
from sourceSets.main.allSource
}
def getMCVersionString() {
if (project.minecraft_version.matches("\\d\\dw\\d\\d[a-z]")) {
return project.minecraft_version
}
int lastDot = project.minecraft_version.lastIndexOf('.')
return project.minecraft_version.substring(0, lastDot)
}
minecraft {
}
repositories {
mavenLocal()
mavenCentral()
maven { url = 'https://aperlambda.github.io/maven' }
maven {
name = 'CottonMC'
url = 'http://server.bbkr.space:8081/artifactory/libs-snapshot'
}
maven {
name = 'Terraformers'
url = 'https://maven.terraformersmc.com/releases'
}
maven { url "https://maven.shedaniel.me/" }
maven { url = "https://jitpack.io" }
}
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 "com.terraformersmc:modmenu:${project.modmenu_version}"
modImplementation "com.github.lambdaurora:spruceui:${project.spruceui_version}"
include "com.github.lambdaurora:spruceui:${project.spruceui_version}"
// Compatibility mods
modImplementation("com.github.joaoh1:okzoomer:e13183c59b") {
exclude group: 'me.shedaniel.cloth'
exclude group: 'io.github.prospector'
}
modImplementation("me.shedaniel:RoughlyEnoughItems:5.10.184")
shadow "com.electronwill.night-config:core:3.6.3"
shadow "com.electronwill.night-config:toml:3.6.3"
}
java {
sourceCompatibility = JavaVersion.toVersion(targetJavaVersion)
targetCompatibility = JavaVersion.toVersion(targetJavaVersion)
withSourcesJar()
}
tasks.withType(JavaCompile).configureEach {
it.options.encoding = "UTF-8"
if (JavaVersion.current().isJava9Compatible()) {
if (JavaVersion.current().isJava9Compatible()) {
it.options.release = targetJavaVersion
}
}
}
processResources {
inputs.property "version", project.version
filesMatching("fabric.mod.json") {
expand "version": project.version
}
}
jar {
from("LICENSE") {
rename { "${it}_${project.archivesBaseName}" }
}
}
license {
header file('HEADER')
include '**/*.java'
}
shadowJar {
dependsOn jar
configurations = [project.configurations.shadow]
archiveClassifier.set('dev')
exclude 'META-INF/maven/**'
exclude 'com/google/**'
exclude 'javax/**'
exclude 'org/**'
relocate 'com.electronwill.nightconfig', 'dev.lambdaurora.lambdacontrols.shadow.nightconfig'
}
task shadowRemapJar(type: RemapJarTask) {
dependsOn shadowJar
input = file("${project.buildDir}/libs/$archivesBaseName-$version-dev.jar")
archiveName = "${archivesBaseName}-${version}.jar"
addNestedDependencies = true
}
/*
// configure the maven publication
publishing {
publications {
mavenJava(MavenPublication) {
// add all the jars that should be included when publishing to maven
artifact(remapJar) {
builtBy remapJar
}
artifact(sourcesJar) {
builtBy remapSourcesJar
}
}
}
repositories {
mavenLocal()
maven {
name = "GithubPackages"
url = uri("https://maven.pkg.github.com/LambdAurora/LambdaControls")
credentials {
username = project.findProperty("gpr.user") ?: System.getenv("USERNAME")
password = project.findProperty("gpr.key") ?: System.getenv("TOKEN")
}
}
}
// select the repositories you want to publish to
repositories {
mavenLocal()
}
}*/
publications {
mavenJava(MavenPublication) {
// add all the jars that should be included when publishing to maven
artifact(remapJar) {
builtBy remapJar
}
artifact(sourcesJar) {
builtBy remapSourcesJar
}
}
}
}

View File

@@ -1,17 +0,0 @@
plugins {
id 'java-library'
}
archivesBaseName = project.archives_base_name + "-core"
dependencies {
api "org.jetbrains:annotations:17.0.0"
api "org.aperlambda:lambdajcommon:1.8.0"
api "com.electronwill.night-config:core:3.5.3"
api "com.electronwill.night-config:toml:3.5.3"
}
java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}

View File

@@ -1,21 +0,0 @@
plugins {
id 'java-library'
}
archivesBaseName = project.archives_base_name + "-elytra"
repositories {
maven { url = 'https://hub.spigotmc.org/nexus/content/groups/public/' }
maven { url = 'https://mcelytra.github.io/maven/' }
maven { url = 'https://libraries.minecraft.net/' }
}
dependencies {
api project(":core")
implementation "org.mcelytra:elytra-core:1.0.0-SNAPSHOT"
}
java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}

View File

@@ -1,53 +0,0 @@
/*
* Copyright © 2020 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of LambdaControls.
*
* Licensed under the MIT license. For more information,
* see the LICENSE file.
*/
package me.lambdaurora.lambdacontrols.event;
import me.lambdaurora.lambdacontrols.ControlsMode;
import me.lambdaurora.lambdacontrols.LambdaControlsConstants;
import org.aperlambda.lambdacommon.Identifier;
import org.jetbrains.annotations.NotNull;
import org.mcelytra.core.entity.EntityPlayer;
import org.mcelytra.core.event.HandlerList;
import org.mcelytra.core.event.player.PlayerEvent;
/**
* Represents an event which is fired when a player change their controls mode.
*
* @author LambdAurora
* @version 1.1.0
* @since 1.1.0
*/
public class PlayerControlsModeEvent extends PlayerEvent
{
private static final HandlerList HANDLERS = new HandlerList();
private final ControlsMode controlsMode;
public PlayerControlsModeEvent(@NotNull EntityPlayer player, @NotNull ControlsMode controlsMode)
{
super(new Identifier(LambdaControlsConstants.NAMESPACE, "player_controls_mode"), player, true);
this.controlsMode = controlsMode;
}
/**
* Returns the controls mode of the player.
*
* @return The player's controls mode.
*/
public ControlsMode getControlsMode()
{
return this.controlsMode;
}
@Override
public @NotNull HandlerList getHandlers()
{
return HANDLERS;
}
}

View File

@@ -1,93 +0,0 @@
plugins {
id 'fabric-loom' version '0.2.6-SNAPSHOT'
id 'java-library'
id 'maven-publish'
}
archivesBaseName = project.archives_base_name + "-fabric"
minecraft {
}
repositories {
maven {
name = 'CottonMC'
url = 'http://server.bbkr.space:8081/artifactory/libs-snapshot'
}
repositories {
maven { url = "https://jitpack.io" }
}
// OkZoomer
ivy {
url 'https://github.com/joaoh1/OkZoomer/releases/download/'
patternLayout {
artifact '[revision]/[module]-[revision].[ext]'
}
metadataSources() {
artifact()
}
}
}
configurations {
shadow
}
dependencies {
//to change the versions see the gradle.properties file
minecraft "com.mojang:minecraft:${project.minecraft_version}"
mappings "net.fabricmc:yarn:${project.yarn_mappings}"
modCompile "net.fabricmc:fabric-loader:${project.loader_version}"
// Fabric API. This is technically optional, but you probably want it anyway.
modApi "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}"
modCompile "io.github.prospector:modmenu:1.8.5+build.23"
modCompile "com.github.lambdaurora:spruceui:${project.spruceui_version}"
include "com.github.lambdaurora:spruceui:${project.spruceui_version}"
// Compatibility mods
modCompile "io.github.joaoh1:okzoomer:2.1.0-beta.2"
api project(":core")
shadow project(":core")
include "org.jetbrains:annotations:17.0.0"
include("org.aperlambda:lambdajcommon:1.8.0") {
exclude group: 'com.google.code.gson'
exclude group: 'com.google.guava'
}
include "com.electronwill.night-config:core:3.5.3"
include "com.electronwill.night-config:toml:3.5.3"
}
processResources {
inputs.property "version", project.version
from(sourceSets.main.resources.srcDirs) {
include "fabric.mod.json"
expand "version": project.version
}
from(sourceSets.main.resources.srcDirs) {
exclude "fabric.mod.json"
}
}
java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
jar {
from "../LICENSE"
from {
configurations.shadow.filter {
it.getName().contains("lambdacontrols")
}.collect {
it.isDirectory() ? it : zipTree(it)
}
}
}
build.dependsOn(":core:build")

View File

@@ -1,326 +0,0 @@
/*
* Copyright © 2020 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of LambdaControls.
*
* Licensed under the MIT license. For more information,
* see the LICENSE file.
*/
package me.lambdaurora.lambdacontrols.client;
import com.mojang.blaze3d.platform.GlStateManager;
import me.lambdaurora.lambdacontrols.ControlsMode;
import me.lambdaurora.lambdacontrols.LambdaControls;
import me.lambdaurora.lambdacontrols.LambdaControlsConstants;
import me.lambdaurora.lambdacontrols.LambdaControlsFeature;
import me.lambdaurora.lambdacontrols.client.compat.LambdaControlsCompat;
import me.lambdaurora.lambdacontrols.client.controller.ButtonBinding;
import me.lambdaurora.lambdacontrols.client.controller.Controller;
import me.lambdaurora.lambdacontrols.client.gui.LambdaControlsHud;
import me.lambdaurora.lambdacontrols.client.gui.TouchscreenOverlay;
import me.lambdaurora.spruceui.event.OpenScreenCallback;
import me.lambdaurora.spruceui.hud.HudManager;
import net.fabricmc.api.ClientModInitializer;
import net.fabricmc.fabric.api.client.keybinding.FabricKeyBinding;
import net.fabricmc.fabric.api.client.keybinding.KeyBindingRegistry;
import net.fabricmc.fabric.api.event.client.ClientTickCallback;
import net.fabricmc.fabric.api.network.ClientSidePacketRegistry;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.font.TextRenderer;
import net.minecraft.client.gui.DrawableHelper;
import net.minecraft.client.resource.language.I18n;
import net.minecraft.client.toast.SystemToast;
import net.minecraft.client.util.InputUtil;
import net.minecraft.text.LiteralText;
import net.minecraft.text.TranslatableText;
import net.minecraft.util.Identifier;
import org.aperlambda.lambdacommon.utils.Pair;
import org.jetbrains.annotations.NotNull;
import org.lwjgl.glfw.GLFW;
/**
* Represents the LambdaControls client mod.
*
* @author LambdAurora
* @version 1.1.0
* @since 1.1.0
*/
public class LambdaControlsClient extends LambdaControls implements ClientModInitializer
{
private static LambdaControlsClient INSTANCE;
public static final FabricKeyBinding BINDING_LOOK_UP = FabricKeyBinding.Builder.create(new Identifier(LambdaControlsConstants.NAMESPACE, "look_up"),
InputUtil.Type.KEYSYM, GLFW.GLFW_KEY_KP_8, "key.categories.movement").build();
public static final FabricKeyBinding BINDING_LOOK_RIGHT = FabricKeyBinding.Builder.create(new Identifier(LambdaControlsConstants.NAMESPACE, "look_right"),
InputUtil.Type.KEYSYM, GLFW.GLFW_KEY_KP_6, "key.categories.movement").build();
public static final FabricKeyBinding BINDING_LOOK_DOWN = FabricKeyBinding.Builder.create(new Identifier(LambdaControlsConstants.NAMESPACE, "look_down"),
InputUtil.Type.KEYSYM, GLFW.GLFW_KEY_KP_2, "key.categories.movement").build();
public static final FabricKeyBinding BINDING_LOOK_LEFT = FabricKeyBinding.Builder.create(new Identifier(LambdaControlsConstants.NAMESPACE, "look_left"),
InputUtil.Type.KEYSYM, GLFW.GLFW_KEY_KP_4, "key.categories.movement").build();
public static final Identifier CONTROLLER_BUTTONS = new Identifier(LambdaControlsConstants.NAMESPACE, "textures/gui/controller_buttons.png");
public static final Identifier CONTROLLER_AXIS = new Identifier(LambdaControlsConstants.NAMESPACE, "textures/gui/controller_axis.png");
public final LambdaControlsConfig config = new LambdaControlsConfig(this);
public final LambdaInput input = new LambdaInput(this);
private LambdaControlsHud hud;
private ControlsMode previousControlsMode;
@Override
public void onInitializeClient()
{
INSTANCE = this;
KeyBindingRegistry.INSTANCE.register(BINDING_LOOK_UP);
KeyBindingRegistry.INSTANCE.register(BINDING_LOOK_RIGHT);
KeyBindingRegistry.INSTANCE.register(BINDING_LOOK_DOWN);
KeyBindingRegistry.INSTANCE.register(BINDING_LOOK_LEFT);
ClientSidePacketRegistry.INSTANCE.register(CONTROLS_MODE_CHANNEL, (context, attachedData) -> context.getTaskQueue()
.execute(() -> ClientSidePacketRegistry.INSTANCE.sendToServer(CONTROLS_MODE_CHANNEL, this.makeControlsModeBuffer(this.config.getControlsMode()))));
ClientSidePacketRegistry.INSTANCE.register(FEATURE_CHANNEL, (context, attachedData) -> {
String name = attachedData.readString(64);
boolean allowed = attachedData.readBoolean();
LambdaControlsFeature.fromName(name).ifPresent(feature -> context.getTaskQueue().execute(() -> feature.setAllowed(allowed)));
});
ClientTickCallback.EVENT.register(this::onTick);
OpenScreenCallback.EVENT.register((client, screen) -> {
if (screen == null && this.config.getControlsMode() == ControlsMode.TOUCHSCREEN) {
screen = new TouchscreenOverlay(this);
screen.init(client, client.getWindow().getScaledWidth(), client.getWindow().getScaledHeight());
client.skipGameRender = false;
client.currentScreen = screen;
} else if (screen != null) {
this.input.onScreenOpen(client, client.getWindow().getWidth(), client.getWindow().getHeight());
}
});
HudManager.register(this.hud = new LambdaControlsHud(this));
}
/**
* This method is called when Minecraft is initializing.
*/
public void onMcInit(@NotNull MinecraftClient client)
{
ButtonBinding.init(client.options);
this.config.load();
this.hud.setVisible(this.config.isHudEnabled());
Controller.updateMappings();
GLFW.glfwSetJoystickCallback((jid, event) -> {
if (event == GLFW.GLFW_CONNECTED) {
Controller controller = Controller.byId(jid);
client.getToastManager().add(new SystemToast(SystemToast.Type.TUTORIAL_HINT, new TranslatableText("lambdacontrols.controller.connected", jid),
new LiteralText(controller.getName())));
} else if (event == GLFW.GLFW_DISCONNECTED) {
client.getToastManager().add(new SystemToast(SystemToast.Type.TUTORIAL_HINT, new TranslatableText("lambdacontrols.controller.disconnected", jid),
null));
}
this.switchControlsMode();
});
LambdaControlsCompat.init(this);
}
/**
* This method is called every Minecraft tick.
*
* @param client The client instance.
*/
public void onTick(@NotNull MinecraftClient client)
{
this.input.onTick(client);
if (this.config.getControlsMode() == ControlsMode.CONTROLLER && (client.isWindowFocused() || this.config.hasUnfocusedInput()))
this.input.onControllerTick(client);
}
public void onRender(MinecraftClient client)
{
this.input.onRender(client);
}
/**
* Called when leaving a server.
*/
public void onLeave()
{
LambdaControlsFeature.resetAllAllowed();
}
/**
* Switches the controls mode if the auto switch is enabled.
*/
public void switchControlsMode()
{
if (this.config.hasAutoSwitchMode()) {
if (this.config.getController().isGamepad()) {
this.previousControlsMode = this.config.getControlsMode();
this.config.setControlsMode(ControlsMode.CONTROLLER);
} else {
if (this.previousControlsMode == null) {
this.previousControlsMode = ControlsMode.DEFAULT;
}
this.config.setControlsMode(this.previousControlsMode);
}
}
}
/**
* Sets whether the HUD is enabled or not.
*
* @param enabled True if the HUD is enabled, else false.
*/
public void setHudEnabled(boolean enabled)
{
this.config.setHudEnabled(enabled);
this.hud.setVisible(enabled);
}
/**
* Gets the LambdaControls client instance.
*
* @return The LambdaControls client instance.
*/
public static LambdaControlsClient get()
{
return INSTANCE;
}
public static Pair<Integer, Integer> drawButton(int x, int y, @NotNull ButtonBinding button, @NotNull MinecraftClient client)
{
return drawButton(x, y, button.getButton(), client);
}
public static Pair<Integer, Integer> drawButton(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];
Pair<Integer, Integer> size = drawButton(currentX, y, btn, client);
if (size.key > height)
height = size.key;
length += size.key;
if (i + 1 < buttons.length) {
length += 2;
currentX = x + length;
}
}
return Pair.of(length, height);
}
@SuppressWarnings("deprecated")
public static Pair<Integer, Integer> drawButton(int x, int y, int button, @NotNull MinecraftClient client)
{
boolean second = false;
if (button == -1)
return Pair.of(0, 0);
else if (button >= 500) {
button -= 1000;
second = true;
}
int controllerType = get().config.getControllerType().getId();
boolean axis = false;
int buttonOffset = button * 15;
switch (button) {
case GLFW.GLFW_GAMEPAD_BUTTON_LEFT_BUMPER:
buttonOffset = 7 * 15;
break;
case GLFW.GLFW_GAMEPAD_BUTTON_RIGHT_BUMPER:
buttonOffset = 8 * 15;
break;
case GLFW.GLFW_GAMEPAD_BUTTON_BACK:
buttonOffset = 4 * 15;
break;
case GLFW.GLFW_GAMEPAD_BUTTON_START:
buttonOffset = 6 * 15;
break;
case GLFW.GLFW_GAMEPAD_BUTTON_GUIDE:
buttonOffset = 5 * 15;
break;
case GLFW.GLFW_GAMEPAD_BUTTON_LEFT_THUMB:
buttonOffset = 15 * 15;
break;
case GLFW.GLFW_GAMEPAD_BUTTON_RIGHT_THUMB:
buttonOffset = 16 * 15;
break;
case GLFW.GLFW_GAMEPAD_AXIS_LEFT_X + 100:
buttonOffset = 0;
axis = true;
break;
case GLFW.GLFW_GAMEPAD_AXIS_LEFT_Y + 100:
buttonOffset = 18;
axis = true;
break;
case GLFW.GLFW_GAMEPAD_AXIS_RIGHT_X + 100:
buttonOffset = 2 * 18;
axis = true;
break;
case GLFW.GLFW_GAMEPAD_AXIS_RIGHT_Y + 100:
buttonOffset = 3 * 18;
axis = true;
break;
case GLFW.GLFW_GAMEPAD_AXIS_LEFT_X + 200:
buttonOffset = 4 * 18;
axis = true;
break;
case GLFW.GLFW_GAMEPAD_AXIS_LEFT_Y + 200:
buttonOffset = 5 * 18;
axis = true;
break;
case GLFW.GLFW_GAMEPAD_AXIS_RIGHT_X + 200:
buttonOffset = 6 * 18;
axis = true;
break;
case GLFW.GLFW_GAMEPAD_AXIS_RIGHT_Y + 200:
buttonOffset = 7 * 18;
axis = true;
break;
case GLFW.GLFW_GAMEPAD_AXIS_LEFT_TRIGGER + 100:
case GLFW.GLFW_GAMEPAD_AXIS_LEFT_TRIGGER + 200:
buttonOffset = 9 * 15;
break;
case GLFW.GLFW_GAMEPAD_AXIS_RIGHT_TRIGGER + 100:
case GLFW.GLFW_GAMEPAD_AXIS_RIGHT_TRIGGER + 200:
buttonOffset = 10 * 15;
break;
}
client.getTextureManager().bindTexture(axis ? LambdaControlsClient.CONTROLLER_AXIS : LambdaControlsClient.CONTROLLER_BUTTONS);
GlStateManager.disableDepthTest();
GlStateManager.color4f(1.0F, second ? 0.0F : 1.0F, 1.0F, 1.0F);
DrawableHelper.blit(x, y, (float) buttonOffset, (float) (controllerType * (axis ? 18 : 15)), axis ? 18 : 15, axis ? 18 : 15, 256, 256);
GlStateManager.enableDepthTest();
return axis ? Pair.of(18, 18) : Pair.of(15, 15);
}
public static int drawButtonTip(int x, int y, @NotNull ButtonBinding button, boolean display, @NotNull MinecraftClient client)
{
return drawButtonTip(x, y, button.getButton(), button.getTranslationKey(), display, client);
}
public static int drawButtonTip(int x, int y, int[] button, @NotNull String action, boolean display, @NotNull MinecraftClient client)
{
if (display) {
int buttonWidth = drawButton(x, y, button, client).key;
String translatedAction = I18n.translate(action);
int textY = (15 - client.textRenderer.fontHeight) / 2;
client.textRenderer.drawWithShadow(translatedAction, (float) (x + buttonWidth + 5), (float) (y + textY), 14737632);
return getButtonTipWidth(translatedAction, client.textRenderer);
}
return -10;
}
private static int getButtonTipWidth(@NotNull String action, @NotNull TextRenderer textRenderer)
{
return 15 + 5 + textRenderer.getStringWidth(action);
}
}

View File

@@ -1,615 +0,0 @@
/*
* Copyright © 2020 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of LambdaControls.
*
* Licensed under the MIT license. For more information,
* see the LICENSE file.
*/
package me.lambdaurora.lambdacontrols.client;
import com.electronwill.nightconfig.core.file.FileConfig;
import me.lambdaurora.lambdacontrols.ControlsMode;
import me.lambdaurora.lambdacontrols.LambdaControlsFeature;
import me.lambdaurora.lambdacontrols.client.controller.ButtonBinding;
import me.lambdaurora.lambdacontrols.client.controller.Controller;
import me.lambdaurora.lambdacontrols.client.controller.InputManager;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.lwjgl.glfw.GLFW;
import java.util.Arrays;
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import static org.lwjgl.glfw.GLFW.GLFW_GAMEPAD_AXIS_LEFT_X;
import static org.lwjgl.glfw.GLFW.GLFW_GAMEPAD_AXIS_LEFT_Y;
/**
* Represents LambdaControls configuration.
*/
public class LambdaControlsConfig
{
// General
private static final ControlsMode DEFAULT_CONTROLS_MODE = ControlsMode.DEFAULT;
private static final boolean DEFAULT_AUTO_SWITCH_MODE = false;
// HUD
private static final boolean DEFAULT_HUD_ENABLE = true;
private static final HudSide DEFAULT_HUD_SIDE = HudSide.LEFT;
// Gameplay
private static final boolean DEFAULT_FRONT_BLOCK_PLACING = false;
private static final boolean DEFAULT_FLY_DRIFTING = false;
private static final boolean DEFAULT_FLY_VERTICAL_DRIFTING = true;
// Controller
private static final ControllerType DEFAULT_CONTROLLER_TYPE = ControllerType.DEFAULT;
private static final double DEFAULT_DEAD_ZONE = 0.25;
private static final double DEFAULT_ROTATION_SPEED = 40.0;
private static final double DEFAULT_MOUSE_SPEED = 25.0;
private static final boolean DEFAULT_UNFOCUSED_INPUT = false;
private static final Pattern BUTTON_BINDING_PATTERN = Pattern.compile("(-?\\d+)\\+?");
protected final FileConfig config = FileConfig.builder("config/lambdacontrols.toml").concurrent().defaultResource("/config.toml").build();
private final LambdaControlsClient mod;
private ControlsMode controlsMode;
private ControllerType controllerType;
// HUD settings.
private boolean hudEnable;
private HudSide hudSide;
// Controller settings
private double deadZone;
private double rotationSpeed;
private double mouseSpeed;
private boolean unfocusedInput;
public LambdaControlsConfig(@NotNull LambdaControlsClient mod)
{
this.mod = mod;
}
/**
* Loads the configuration
*/
public void load()
{
this.config.load();
this.checkAndFix();
this.mod.log("Configuration loaded.");
this.controlsMode = ControlsMode.byId(this.config.getOrElse("controls", DEFAULT_CONTROLS_MODE.getName())).orElse(DEFAULT_CONTROLS_MODE);
// HUD settings.
this.hudEnable = this.config.getOrElse("hud.enable", DEFAULT_HUD_ENABLE);
this.hudSide = HudSide.byId(this.config.getOrElse("hud.side", DEFAULT_HUD_SIDE.getName())).orElse(DEFAULT_HUD_SIDE);
// Gameplay
LambdaControlsFeature.FRONT_BLOCK_PLACING.setEnabled(this.config.getOrElse("gameplay.front_block_placing", DEFAULT_FRONT_BLOCK_PLACING));
// Controller settings.
this.controllerType = ControllerType.byId(this.config.getOrElse("controller.type", DEFAULT_CONTROLLER_TYPE.getName())).orElse(DEFAULT_CONTROLLER_TYPE);
this.deadZone = this.config.getOrElse("controller.dead_zone", DEFAULT_DEAD_ZONE);
this.rotationSpeed = this.config.getOrElse("controller.rotation_speed", DEFAULT_ROTATION_SPEED);
this.mouseSpeed = this.config.getOrElse("controller.mouse_speed", DEFAULT_MOUSE_SPEED);
this.unfocusedInput = this.config.getOrElse("controller.unfocused_input", DEFAULT_UNFOCUSED_INPUT);
// Controller controls.
InputManager.loadButtonBindings(this);
}
/**
* Saves the configuration.
*/
public void save()
{
this.config.set("controller.dead_zone", this.deadZone);
this.config.set("controller.rotation_speed", this.rotationSpeed);
this.config.set("controller.mouse_speed", this.mouseSpeed);
this.config.set("controller.unfocused_input", this.unfocusedInput);
this.config.save();
this.mod.log("Configuration saved.");
}
public void checkAndFix()
{
InputManager.streamBindings().forEach(binding -> {
String path = "controller.controls." + binding.getName();
Object raw = this.config.getRaw(path);
if (raw instanceof Number) {
this.mod.warn("Invalid data at \"" + path + "\", fixing...");
this.config.set(path, String.valueOf(raw));
}
});
}
/**
* Resets the configuration to default values.
*/
public void reset()
{
// General
this.setControlsMode(DEFAULT_CONTROLS_MODE);
this.setAutoSwitchMode(DEFAULT_AUTO_SWITCH_MODE);
// HUD
this.setHudEnabled(DEFAULT_HUD_ENABLE);
this.setHudSide(DEFAULT_HUD_SIDE);
// Gameplay
this.setFrontBlockPlacing(DEFAULT_FRONT_BLOCK_PLACING);
this.setFlyDrifting(DEFAULT_FLY_DRIFTING);
this.setFlyVerticalDrifting(DEFAULT_FLY_VERTICAL_DRIFTING);
// Controller
this.setControllerType(DEFAULT_CONTROLLER_TYPE);
this.setDeadZone(DEFAULT_DEAD_ZONE);
this.setRotationSpeed(DEFAULT_ROTATION_SPEED);
this.setMouseSpeed(DEFAULT_MOUSE_SPEED);
this.setUnfocusedInput(DEFAULT_UNFOCUSED_INPUT);
// Collect prevents concurrent modification.
InputManager.streamBindings().collect(Collectors.toList()).forEach(binding -> this.setButtonBinding(binding, binding.getDefaultButton()));
}
/**
* Gets the controls mode from the configuration.
*
* @return The controls mode.
*/
public @NotNull ControlsMode getControlsMode()
{
return this.controlsMode;
}
/**
* Sets the controls mode in the configuration.
*
* @param controlsMode The controls mode.
*/
public void setControlsMode(@NotNull ControlsMode controlsMode)
{
this.controlsMode = controlsMode;
this.config.set("controls", controlsMode.getName());
}
/**
* Returns whether the auto switch mode is enabled or not.
*
* @return True if the auto switch mode is enabled, else false.
*/
public boolean hasAutoSwitchMode()
{
return this.config.getOrElse("auto_switch_mode", DEFAULT_AUTO_SWITCH_MODE);
}
/**
* Sets whether the auto switch mode is enabled or not.
*
* @param autoSwitchMode True if the auto switch mode is enabled, else false.
*/
public void setAutoSwitchMode(boolean autoSwitchMode)
{
this.config.set("auto_switch_mode", autoSwitchMode);
}
/*
HUD settings
*/
/**
* Returns whether the HUD is enabled.
*
* @return True if the HUD is enabled, else false.
*/
public boolean isHudEnabled()
{
return this.hudEnable;
}
/**
* Sets whether the HUD is enabled.
*
* @param enable True if the HUD is enabled, else false.
*/
public void setHudEnabled(boolean enable)
{
this.hudEnable = enable;
this.config.set("hud.enable", this.hudEnable);
}
/**
* Gets the HUD side from the configuration.
*
* @return The HUD side.
*/
public @NotNull HudSide getHudSide()
{
return this.hudSide;
}
/**
* Sets the HUD side in the configuration.
*
* @param hudSide The HUD side.
*/
public void setHudSide(@NotNull HudSide hudSide)
{
this.hudSide = hudSide;
this.config.set("hud.side", hudSide.getName());
}
/*
Gameplay settings
*/
/**
* Returns whether front block placing is enabled or not.
*
* @return True if front block placing is enabled, else false.
*/
public boolean hasFrontBlockPlacing()
{
return LambdaControlsFeature.FRONT_BLOCK_PLACING.isEnabled();
}
/**
* Sets whether front block placing is enabled or not.
*
* @param enable True if front block placing is enabled, else false.
*/
public void setFrontBlockPlacing(boolean enable)
{
LambdaControlsFeature.FRONT_BLOCK_PLACING.setEnabled(enable);
this.config.set("gameplay.front_block_placing", enable);
}
/**
* Returns whether fly drifting is enabled or not.
*
* @return True if fly drifting is enabled, else false.
*/
public boolean hasFlyDrifting()
{
return this.config.getOrElse("gameplay.fly.drifting", DEFAULT_FLY_DRIFTING);
}
/**
* Sets whether fly drifting is enabled or not.
*
* @param flyDrifting True if fly drifting is enabled, else false.
*/
public void setFlyDrifting(boolean flyDrifting)
{
this.config.set("gameplay.fly.drifting", flyDrifting);
}
/**
* Returns whether vertical fly drifting is enabled or not.
*
* @return True if vertical fly drifting is enabled, else false.
*/
public boolean hasFlyVerticalDrifting()
{
return this.config.getOrElse("gameplay.fly.vertical_drifting", DEFAULT_FLY_VERTICAL_DRIFTING);
}
/**
* Sets whether vertical fly drifting is enabled or not.
*
* @param flyDrifting True if vertical fly drifting is enabled, else false.
*/
public void setFlyVerticalDrifting(boolean flyDrifting)
{
this.config.set("gameplay.fly.vertical_drifting", flyDrifting);
}
/*
Controller settings
*/
/**
* Gets the used controller.
*
* @return The used controller.
*/
public @NotNull Controller getController()
{
Object raw = this.config.getRaw("controller.id");
if (raw instanceof Number) {
return Controller.byId((Integer) raw);
} else if (raw instanceof String) {
return Controller.byGuid((String) raw).orElse(Controller.byId(GLFW.GLFW_JOYSTICK_1));
}
return Controller.byId(GLFW.GLFW_JOYSTICK_1);
}
/**
* Sets the used controller.
*
* @param controller The used controller.
*/
public void setController(@NotNull Controller controller)
{
this.config.set("controller.id", controller.getId());
}
/**
* Gets the second controller (for Joy-Con supports).
*
* @return The second controller.
*/
public @NotNull Optional<Controller> getSecondController()
{
Object raw = this.config.getRaw("controller.id2");
if (raw instanceof Number) {
if ((int) raw == -1)
return Optional.empty();
return Optional.of(Controller.byId((Integer) raw));
} 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 void setSecondController(@Nullable Controller controller)
{
this.config.set("controller.id2", controller == null ? -1 : controller.getId());
}
/**
* Gets the controller's type.
*
* @return The controller's type.
*/
public @NotNull ControllerType getControllerType()
{
return this.controllerType;
}
/**
* Sets the controller's type.
*
* @param controllerType The controller's type.
*/
public void setControllerType(@NotNull ControllerType controllerType)
{
this.controllerType = controllerType;
this.config.set("controller.type", controllerType.getName());
}
/**
* Gets the controller's dead zone from the configuration.
*
* @return The controller's dead zone value.
*/
public double getDeadZone()
{
return this.deadZone;
}
/**
* Sets the controller's dead zone in the configuration.
*
* @param deadZone The new controller's dead zone value.
*/
public void setDeadZone(double deadZone)
{
this.deadZone = deadZone;
}
/**
* Gets the controller's rotation speed.
*
* @return The rotation speed.
*/
public double getRotationSpeed()
{
return this.rotationSpeed;
}
/**
* Sets the controller's rotation speed.
*
* @param rotationSpeed The rotation speed.
*/
public void setRotationSpeed(double rotationSpeed)
{
this.rotationSpeed = rotationSpeed;
}
/**
* Gets the controller's mouse speed.
*
* @return The mouse speed.
*/
public double getMouseSpeed()
{
return this.mouseSpeed;
}
/**
* Sets the controller's mouse speed.
*
* @param mouseSpeed The mouse speed.
*/
public void setMouseSpeed(double mouseSpeed)
{
this.mouseSpeed = mouseSpeed;
}
/**
* Returns whether the right X axis is inverted or not.
*
* @return True if the right X axis is inverted, else false.
*/
public boolean doesInvertRightXAxis()
{
return this.config.getOrElse("controller.invert_right_x_axis", false);
}
/**
* Sets whether the right X axis is inverted or not.
*
* @param invert True if the right X axis is inverted, else false.
*/
public void setInvertRightXAxis(boolean invert)
{
this.config.set("controller.invert_right_x_axis", invert);
}
/**
* Returns whether the right Y axis is inverted or not.
*
* @return True if the right Y axis is inverted, else false.
*/
public boolean doesInvertRightYAxis()
{
return this.config.getOrElse("controller.invert_right_y_axis", false);
}
/**
* Sets whether the right Y axis is inverted or not.
*
* @param invert True if the right Y axis is inverted, else false.
*/
public void setInvertRightYAxis(boolean invert)
{
this.config.set("controller.invert_right_y_axis", invert);
}
/**
* Returns whether unfocused controller input is allowed or not.
*
* @return True if unfocused controller input is allowed, else false.
*/
public boolean hasUnfocusedInput()
{
return this.unfocusedInput;
}
/**
* Sets whether unfocused controller input is allowed or not.
*
* @param unfocusedInput True if unfocused controller input is allowed, else false.
*/
public void setUnfocusedInput(boolean unfocusedInput)
{
this.unfocusedInput = unfocusedInput;
}
/**
* Gets the right X axis sign.
*
* @return The right X axis sign.
*/
public double getRightXAxisSign()
{
return this.doesInvertRightXAxis() ? -1.0 : 1.0;
}
/**
* Gets the right Y axis sign.
*
* @return The right Y axis sign.
*/
public double getRightYAxisSign()
{
return this.doesInvertRightYAxis() ? -1.0 : 1.0;
}
/**
* Loads the button binding from configuration.
*
* @param button The button binding.
*/
public void loadButtonBinding(@NotNull ButtonBinding button)
{
button.setButton(button.getDefaultButton());
String code = this.config.getOrElse("controller.controls." + button.getName(), button.getButtonCode());
Matcher matcher = BUTTON_BINDING_PATTERN.matcher(code);
try {
int[] buttons = new int[1];
int count = 0;
while (matcher.find()) {
count++;
if (count > buttons.length)
buttons = Arrays.copyOf(buttons, count);
String current;
if (!this.checkValidity(button, code, current = matcher.group(1)))
return;
buttons[count - 1] = Integer.parseInt(current);
}
if (count == 0) {
this.mod.warn("Malformed config value \"" + code + "\" for binding \"" + button.getName() + "\".");
this.setButtonBinding(button, new int[]{-1});
}
button.setButton(buttons);
} catch (Exception e) {
this.mod.warn("Malformed config value \"" + code + "\" for binding \"" + button.getName() + "\".");
this.config.set("controller.controls." + button.getName(), button.getButtonCode());
}
}
private boolean checkValidity(@NotNull ButtonBinding binding, @NotNull String input, String group)
{
if (group == null) {
this.mod.warn("Malformed config value \"" + input + "\" for binding \"" + binding.getName() + "\".");
this.config.set("controller.controls." + binding.getName(), binding.getButtonCode());
return false;
}
return true;
}
/**
* Sets the button binding in configuration.
*
* @param binding The button binding.
* @param button The button.
*/
public void setButtonBinding(@NotNull ButtonBinding binding, int[] button)
{
binding.setButton(button);
this.config.set("controller.controls." + binding.getName(), binding.getButtonCode());
}
public boolean isBackButton(int btn, boolean isBtn, int state)
{
if (!isBtn && state == 0)
return false;
return ButtonBinding.axisAsButton(GLFW_GAMEPAD_AXIS_LEFT_Y, false) == ButtonBinding.axisAsButton(btn, state == 1);
}
public boolean isForwardButton(int btn, boolean isBtn, int state)
{
if (!isBtn && state == 0)
return false;
return ButtonBinding.axisAsButton(GLFW_GAMEPAD_AXIS_LEFT_Y, true) == ButtonBinding.axisAsButton(btn, state == 1);
}
public boolean isLeftButton(int btn, boolean isBtn, int state)
{
if (!isBtn && state == 0)
return false;
return ButtonBinding.axisAsButton(GLFW_GAMEPAD_AXIS_LEFT_X, false) == ButtonBinding.axisAsButton(btn, state == 1);
}
public boolean isRightButton(int btn, boolean isBtn, int state)
{
if (!isBtn && state == 0)
return false;
return ButtonBinding.axisAsButton(GLFW_GAMEPAD_AXIS_LEFT_X, true) == ButtonBinding.axisAsButton(btn, state == 1);
}
/**
* Returns whether the specified axis is an axis used for movements.
*
* @param axis The axis index.
* @return True if the axis is used for movements, else false.
*/
public boolean isMovementAxis(int axis)
{
return axis == GLFW_GAMEPAD_AXIS_LEFT_Y || axis == GLFW_GAMEPAD_AXIS_LEFT_X;
}
}

View File

@@ -1,40 +0,0 @@
/*
* Copyright © 2020 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of LambdaControls.
*
* Licensed under the MIT license. For more information,
* see the LICENSE file.
*/
package me.lambdaurora.lambdacontrols.client;
import io.github.prospector.modmenu.api.ModMenuApi;
import me.lambdaurora.lambdacontrols.LambdaControlsConstants;
import me.lambdaurora.lambdacontrols.client.gui.LambdaControlsSettingsScreen;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gui.screen.Screen;
import java.util.function.Function;
/**
* Represents the API implementation of ModMenu for LambdaControls.
*
* @author LambdAurora
* @version 1.1.0
* @since 1.1.0
*/
public class LambdaControlsModMenu implements ModMenuApi
{
@Override
public String getModId()
{
return LambdaControlsConstants.NAMESPACE;
}
@Override
public Function<Screen, ? extends Screen> getConfigScreenFactory()
{
return screen -> new LambdaControlsSettingsScreen(screen, MinecraftClient.getInstance().options, false);
}
}

View File

@@ -1,642 +0,0 @@
/*
* Copyright © 2020 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of LambdaControls.
*
* Licensed under the MIT license. For more information,
* see the LICENSE file.
*/
package me.lambdaurora.lambdacontrols.client;
import me.lambdaurora.lambdacontrols.client.controller.ButtonBinding;
import me.lambdaurora.lambdacontrols.client.controller.Controller;
import me.lambdaurora.lambdacontrols.client.controller.InputManager;
import me.lambdaurora.lambdacontrols.client.gui.ControllerControlsScreen;
import me.lambdaurora.lambdacontrols.client.gui.TouchscreenOverlay;
import me.lambdaurora.lambdacontrols.client.mixin.CreativeInventoryScreenAccessor;
import me.lambdaurora.lambdacontrols.client.mixin.EntryListWidgetAccessor;
import me.lambdaurora.lambdacontrols.client.util.ContainerScreenAccessor;
import me.lambdaurora.spruceui.SpruceLabelWidget;
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.AdvancementsScreen;
import net.minecraft.client.gui.screen.ingame.ContainerScreen;
import net.minecraft.client.gui.screen.ingame.CreativeInventoryScreen;
import net.minecraft.client.gui.screen.multiplayer.MultiplayerScreen;
import net.minecraft.client.gui.screen.multiplayer.MultiplayerServerListWidget;
import net.minecraft.client.gui.screen.world.WorldListWidget;
import net.minecraft.client.gui.widget.AbstractPressableButtonWidget;
import net.minecraft.client.gui.widget.AlwaysSelectedEntryListWidget;
import net.minecraft.client.gui.widget.SliderWidget;
import net.minecraft.container.Slot;
import net.minecraft.container.SlotActionType;
import net.minecraft.util.math.MathHelper;
import org.aperlambda.lambdacommon.utils.Pair;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.lwjgl.glfw.GLFW;
import org.lwjgl.glfw.GLFWGamepadState;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import static me.lambdaurora.lambdacontrols.client.controller.ButtonBinding.axisAsButton;
import static me.lambdaurora.lambdacontrols.client.controller.InputManager.INPUT_MANAGER;
import static org.lwjgl.glfw.GLFW.*;
/**
* Represents the LambdaControls' input handler.
*
* @author LambdAurora
* @version 1.1.0
* @since 1.0.0
*/
public class LambdaInput
{
private static final Map<Integer, Integer> BUTTON_COOLDOWNS = new HashMap<>();
private final LambdaControlsConfig config;
// Cooldowns
private int actionGuiCooldown = 0;
private int ignoreNextA = 0;
// Sneak state.
private double prevTargetYaw = 0.0;
private double prevTargetPitch = 0.0;
private double targetYaw = 0.0;
private double targetPitch = 0.0;
private float prevXAxis = 0.F;
private float prevYAxis = 0.F;
private int targetMouseX = 0;
private int targetMouseY = 0;
private float mouseSpeedX = 0.F;
private float mouseSpeedY = 0.F;
public LambdaInput(@NotNull LambdaControlsClient mod)
{
this.config = mod.config;
}
/**
* This method is called every Minecraft tick.
*
* @param client The client instance.
*/
public void onTick(@NotNull MinecraftClient client)
{
this.prevTargetYaw = this.targetYaw;
this.prevTargetPitch = this.targetPitch;
// Handles the key bindings.
if (LambdaControlsClient.BINDING_LOOK_UP.isPressed()) {
this.handleLook(client, GLFW_GAMEPAD_AXIS_RIGHT_Y, 0.8F, 2);
} else if (LambdaControlsClient.BINDING_LOOK_DOWN.isPressed()) {
this.handleLook(client, GLFW_GAMEPAD_AXIS_RIGHT_Y, 0.8F, 1);
}
if (LambdaControlsClient.BINDING_LOOK_LEFT.isPressed()) {
this.handleLook(client, GLFW_GAMEPAD_AXIS_RIGHT_X, 0.8F, 2);
} else if (LambdaControlsClient.BINDING_LOOK_RIGHT.isPressed()) {
this.handleLook(client, GLFW_GAMEPAD_AXIS_RIGHT_X, 0.8F, 1);
}
INPUT_MANAGER.tick(client);
}
/**
* This method is called every Minecraft tick for controller input update.
*
* @param client The client instance.
*/
public void onControllerTick(@NotNull MinecraftClient client)
{
BUTTON_COOLDOWNS.entrySet().stream().filter(entry -> entry.getValue() > 0).forEach(entry -> BUTTON_COOLDOWNS.put(entry.getKey(), entry.getValue() - 1));
// Decreases the cooldown for GUI actions.
if (this.actionGuiCooldown > 0)
--this.actionGuiCooldown;
InputManager.updateStates();
Controller controller = this.config.getController();
if (controller.isConnected()) {
GLFWGamepadState state = controller.getState();
this.fetchButtonInput(client, state, false);
this.fetchAxeInput(client, state, false);
}
this.config.getSecondController().filter(Controller::isConnected)
.ifPresent(joycon -> {
GLFWGamepadState state = joycon.getState();
this.fetchButtonInput(client, state, true);
this.fetchAxeInput(client, state, true);
});
boolean allowInput = true;
if (client.currentScreen instanceof ControllerControlsScreen && ((ControllerControlsScreen) client.currentScreen).focusedBinding != null)
allowInput = false;
if (allowInput)
InputManager.updateBindings(client);
if (this.ignoreNextA > 0)
this.ignoreNextA--;
if (client.currentScreen instanceof ControllerControlsScreen && InputManager.STATES.entrySet().parallelStream().map(Map.Entry::getValue).allMatch(ButtonState::isUnpressed)) {
ControllerControlsScreen screen = (ControllerControlsScreen) client.currentScreen;
if (screen.focusedBinding != null && !screen.waiting) {
int[] buttons = new int[screen.currentButtons.size()];
for (int i = 0; i < screen.currentButtons.size(); i++)
buttons[i] = screen.currentButtons.get(i);
screen.focusedBinding.setButton(buttons);
screen.focusedBinding = null;
}
}
}
/**
* This method is called before the screen is rendered.
*
* @param client The client instance.
* @param screen The screen to render.
*/
public void onPreRenderScreen(@NotNull MinecraftClient client, @NotNull Screen screen)
{
if (!isScreenInteractive(screen)) {
INPUT_MANAGER.updateMousePosition(client);
}
}
/**
* This method is called when Minecraft renders.
*
* @param client The client instance.
*/
public void onRender(@NotNull MinecraftClient client)
{
if ((client.currentScreen == null || client.currentScreen instanceof TouchscreenOverlay) &&
(this.prevTargetYaw != this.targetYaw || this.prevTargetPitch != this.targetPitch)) {
float deltaYaw = (float) ((this.targetYaw - client.player.prevYaw) * client.getTickDelta());
float deltaPitch = (float) ((this.targetPitch - client.player.prevPitch) * client.getTickDelta());
float rotationYaw = client.player.prevYaw + deltaYaw;
float rotationPitch = client.player.prevPitch + deltaPitch;
client.player.yaw = rotationYaw;
client.player.pitch = MathHelper.clamp(rotationPitch, -90.F, 90.F);
if (client.player.isRiding()) {
client.player.getVehicle().copyPositionAndRotation(client.player);
}
client.getTutorialManager().onUpdateMouse(deltaPitch, deltaYaw);
}
}
/**
* This method is called when a Screen is opened.
*
* @param client The client instance.
* @param windowWidth The window width.
* @param windowHeight The window height.
*/
public void onScreenOpen(@NotNull MinecraftClient client, int windowWidth, int windowHeight)
{
if (client.currentScreen == null) {
this.mouseSpeedX = this.mouseSpeedY = 0.0F;
INPUT_MANAGER.resetMousePosition(windowWidth, windowHeight);
}
}
private void fetchButtonInput(@NotNull MinecraftClient client, @NotNull GLFWGamepadState gamepadState, boolean leftJoycon)
{
ByteBuffer buffer = gamepadState.buttons();
for (int i = 0; i < buffer.limit(); i++) {
int btn = leftJoycon ? ButtonBinding.controller2Button(i) : i;
boolean btnState = buffer.get() == (byte) 1;
ButtonState state = ButtonState.NONE;
ButtonState previousState = InputManager.STATES.getOrDefault(btn, ButtonState.NONE);
if (btnState != previousState.isPressed()) {
state = btnState ? ButtonState.PRESS : ButtonState.RELEASE;
this.handleButton(client, btn, btnState ? 0 : 1, btnState);
if (btnState)
BUTTON_COOLDOWNS.put(btn, 5);
} else if (btnState) {
state = ButtonState.REPEAT;
if (BUTTON_COOLDOWNS.getOrDefault(btn, 0) == 0) {
BUTTON_COOLDOWNS.put(btn, 5);
this.handleButton(client, btn, 2, true);
}
}
InputManager.STATES.put(btn, state);
}
}
private void fetchAxeInput(@NotNull MinecraftClient client, @NotNull GLFWGamepadState gamepadState, boolean leftJoycon)
{
FloatBuffer buffer = gamepadState.axes();
for (int i = 0; i < buffer.limit(); i++) {
int axis = leftJoycon ? ButtonBinding.controller2Button(i) : i;
float value = buffer.get();
float absValue = Math.abs(value);
if (i == GLFW.GLFW_GAMEPAD_AXIS_LEFT_Y)
value *= -1.0F;
int state = value > this.config.getDeadZone() ? 1 : (value < -this.config.getDeadZone() ? 2 : 0);
this.handleAxe(client, axis, value, absValue, state);
}
}
private void handleButton(@NotNull MinecraftClient client, int button, int action, boolean state)
{
if (client.currentScreen instanceof ControllerControlsScreen) {
ControllerControlsScreen screen = (ControllerControlsScreen) client.currentScreen;
if (screen.focusedBinding != null) {
if (action == 0 && !screen.currentButtons.contains(button)) {
screen.currentButtons.add(button);
int[] buttons = new int[screen.currentButtons.size()];
for (int i = 0; i < screen.currentButtons.size(); i++)
buttons[i] = screen.currentButtons.get(i);
screen.focusedBinding.setButton(buttons);
screen.waiting = false;
}
return;
}
}
if (action == 0 || action == 2) {
if (client.currentScreen != null && isScreenInteractive(client.currentScreen)
&& (button == GLFW.GLFW_GAMEPAD_BUTTON_DPAD_UP || button == GLFW.GLFW_GAMEPAD_BUTTON_DPAD_DOWN
|| button == GLFW.GLFW_GAMEPAD_BUTTON_DPAD_LEFT || button == GLFW.GLFW_GAMEPAD_BUTTON_DPAD_RIGHT)) {
if (this.actionGuiCooldown == 0) {
if (button == GLFW.GLFW_GAMEPAD_BUTTON_DPAD_UP) {
this.changeFocus(client.currentScreen, false);
} else if (button == GLFW.GLFW_GAMEPAD_BUTTON_DPAD_DOWN) {
this.changeFocus(client.currentScreen, true);
} else if (button == GLFW.GLFW_GAMEPAD_BUTTON_DPAD_LEFT) {
this.handleLeftRight(client.currentScreen, false);
} else {
this.handleLeftRight(client.currentScreen, true);
}
}
return;
}
}
if (action == 1) {
if (button == GLFW.GLFW_GAMEPAD_BUTTON_A && client.currentScreen != null) {
if (this.actionGuiCooldown == 0) {
Element focused = client.currentScreen.getFocused();
if (focused != null && isScreenInteractive(client.currentScreen)) {
if (this.handleAButton(client.currentScreen, focused)) {
this.actionGuiCooldown = 5; // Prevent to press too quickly the focused element, so we have to skip 5 ticks.
return;
}
}
}
}
if (client.currentScreen instanceof ContainerScreen && client.interactionManager != null && client.player != null) {
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();
Slot slot = ((ContainerScreenAccessor) client.currentScreen).lambdacontrols_getSlotAt(x, y);
SlotActionType slotAction = SlotActionType.PICKUP;
if (button == GLFW.GLFW_GAMEPAD_BUTTON_A && slot != null) {
if (client.currentScreen instanceof CreativeInventoryScreen) {
if (((CreativeInventoryScreenAccessor) client.currentScreen).lambdacontrols_isCreativeInventorySlot(slot))
slotAction = SlotActionType.CLONE;
}
client.interactionManager.clickSlot(((ContainerScreen) client.currentScreen).getContainer().syncId, slot.id, GLFW.GLFW_MOUSE_BUTTON_1, slotAction, client.player);
this.actionGuiCooldown = 5;
return;
} else if (button == GLFW.GLFW_GAMEPAD_BUTTON_B) {
client.player.closeContainer();
return;
} else if (button == GLFW.GLFW_GAMEPAD_BUTTON_X && slot != null) {
client.interactionManager.clickSlot(((ContainerScreen) client.currentScreen).getContainer().syncId, slot.id, GLFW.GLFW_MOUSE_BUTTON_2, SlotActionType.PICKUP, client.player);
return;
} else if (button == GLFW.GLFW_GAMEPAD_BUTTON_Y && slot != null) {
client.interactionManager.clickSlot(((ContainerScreen) client.currentScreen).getContainer().syncId, slot.id, GLFW.GLFW_MOUSE_BUTTON_1, SlotActionType.QUICK_MOVE, client.player);
return;
}
} else if (button == GLFW.GLFW_GAMEPAD_BUTTON_B) {
if (client.currentScreen != null) {
client.currentScreen.onClose();
return;
}
}
}
if (button == GLFW.GLFW_GAMEPAD_BUTTON_A && client.currentScreen != null && !isScreenInteractive(client.currentScreen) && this.actionGuiCooldown == 0 && this.ignoreNextA == 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 (action == 0) {
client.currentScreen.mouseClicked(mouseX, mouseY, GLFW.GLFW_MOUSE_BUTTON_1);
} else if (action == 1) {
client.currentScreen.mouseReleased(mouseX, mouseY, GLFW.GLFW_MOUSE_BUTTON_1);
}
this.actionGuiCooldown = 5;
}
}
private void handleAxe(@NotNull MinecraftClient client, int axis, float value, float absValue, int state)
{
int asButtonState = value > 0.5F ? 1 : (value < -0.5F ? 2 : 0);
if (axis == GLFW_GAMEPAD_AXIS_LEFT_TRIGGER || axis == GLFW_GAMEPAD_AXIS_RIGHT_TRIGGER || axis == ButtonBinding.controller2Button(GLFW.GLFW_GAMEPAD_AXIS_LEFT_TRIGGER) ||
axis == ButtonBinding.controller2Button(GLFW.GLFW_GAMEPAD_AXIS_RIGHT_TRIGGER))
if (asButtonState == 2)
asButtonState = 0;
{
boolean currentPlusState = asButtonState == 1;
boolean currentMinusState = asButtonState == 2;
ButtonState previousPlusState = InputManager.STATES.getOrDefault(axisAsButton(axis, true), ButtonState.NONE);
ButtonState previousMinusState = InputManager.STATES.getOrDefault(axisAsButton(axis, false), ButtonState.NONE);
if (currentPlusState != previousPlusState.isPressed()) {
InputManager.STATES.put(axisAsButton(axis, true), currentPlusState ? ButtonState.PRESS : ButtonState.RELEASE);
if (currentPlusState)
BUTTON_COOLDOWNS.put(axisAsButton(axis, true), 5);
} else if (currentPlusState) {
InputManager.STATES.put(axisAsButton(axis, true), ButtonState.REPEAT);
if (BUTTON_COOLDOWNS.getOrDefault(axisAsButton(axis, true), 0) == 0) {
BUTTON_COOLDOWNS.put(axisAsButton(axis, true), 5);
}
}
if (currentMinusState != previousMinusState.isPressed()) {
InputManager.STATES.put(axisAsButton(axis, false), currentMinusState ? ButtonState.PRESS : ButtonState.RELEASE);
if (currentMinusState)
BUTTON_COOLDOWNS.put(axisAsButton(axis, false), 5);
} else if (currentMinusState) {
InputManager.STATES.put(axisAsButton(axis, false), ButtonState.REPEAT);
if (BUTTON_COOLDOWNS.getOrDefault(axisAsButton(axis, false), 0) == 0) {
BUTTON_COOLDOWNS.put(axisAsButton(axis, false), 5);
}
}
}
if (client.currentScreen instanceof ControllerControlsScreen) {
ControllerControlsScreen screen = (ControllerControlsScreen) client.currentScreen;
if (screen.focusedBinding != null) {
if (asButtonState != 0 && !screen.currentButtons.contains(axisAsButton(axis, asButtonState == 1))) {
screen.currentButtons.add(axisAsButton(axis, asButtonState == 1));
int[] buttons = new int[screen.currentButtons.size()];
for (int i = 0; i < screen.currentButtons.size(); i++)
buttons[i] = screen.currentButtons.get(i);
screen.focusedBinding.setButton(buttons);
screen.waiting = false;
}
return;
}
}
double deadZone = this.config.getDeadZone();
if (client.currentScreen == null) {
// Handles the look direction.
this.handleLook(client, axis, (float) (absValue / (1.0 - this.config.getDeadZone())), state);
} else {
boolean allowMouseControl = true;
if (this.actionGuiCooldown == 0 && this.config.isMovementAxis(axis) && isScreenInteractive(client.currentScreen)) {
if (this.config.isForwardButton(axis, false, asButtonState)) {
allowMouseControl = this.changeFocus(client.currentScreen, false);
} else if (this.config.isBackButton(axis, false, asButtonState)) {
allowMouseControl = this.changeFocus(client.currentScreen, true);
} else if (this.config.isLeftButton(axis, false, asButtonState)) {
allowMouseControl = this.handleLeftRight(client.currentScreen, false);
} else if (this.config.isRightButton(axis, false, asButtonState)) {
allowMouseControl = this.handleLeftRight(client.currentScreen, true);
}
}
float movementX = 0.0F;
float movementY = 0.0F;
if (this.config.isBackButton(axis, false, (value > 0 ? 1 : 2))) {
movementY = absValue;
} else if (this.config.isForwardButton(axis, false, (value > 0 ? 1 : 2))) {
movementY = -absValue;
} else if (this.config.isLeftButton(axis, false, (value > 0 ? 1 : 2))) {
movementX = -absValue;
} else if (this.config.isRightButton(axis, false, (value > 0 ? 1 : 2))) {
movementX = absValue;
}
if (client.currentScreen != null && allowMouseControl) {
boolean moving = Math.abs(movementY) >= deadZone || Math.abs(movementX) >= deadZone;
if (moving) {
/*
Updates the target mouse position when the initial movement stick movement is detected.
It prevents the cursor to jump to the old target mouse position if the user moves the cursor with the mouse.
*/
if (Math.abs(prevXAxis) < deadZone && Math.abs(prevYAxis) < deadZone) {
INPUT_MANAGER.resetMouseTarget(client);
}
if (Math.abs(movementX) >= deadZone)
this.mouseSpeedX = movementX;
else
this.mouseSpeedX = 0.F;
if (Math.abs(movementY) >= deadZone)
this.mouseSpeedY = movementY;
else
this.mouseSpeedY = 0.F;
} else {
this.mouseSpeedX = 0.F;
this.mouseSpeedY = 0.F;
}
if (Math.abs(this.mouseSpeedX) >= .05F || Math.abs(this.mouseSpeedY) >= .05F) {
InputManager.queueMoveMousePosition(this.mouseSpeedX * this.config.getMouseSpeed(), this.mouseSpeedY * this.config.getMouseSpeed());
}
this.moveMouseToClosestSlot(client, client.currentScreen);
}
this.prevXAxis = movementX;
this.prevYAxis = movementY;
}
}
private boolean handleAButton(@NotNull Screen screen, @NotNull Element focused)
{
if (focused instanceof AbstractPressableButtonWidget) {
AbstractPressableButtonWidget widget = (AbstractPressableButtonWidget) focused;
widget.playDownSound(MinecraftClient.getInstance().getSoundManager());
widget.onPress();
return true;
} else if (focused instanceof SpruceLabelWidget) {
((SpruceLabelWidget) focused).onPress();
return true;
} else if (focused instanceof WorldListWidget) {
WorldListWidget list = (WorldListWidget) focused;
list.method_20159().ifPresent(WorldListWidget.Entry::play);
return true;
} else if (focused instanceof MultiplayerServerListWidget) {
MultiplayerServerListWidget list = (MultiplayerServerListWidget) focused;
MultiplayerServerListWidget.Entry entry = list.getSelected();
if (entry instanceof MultiplayerServerListWidget.LanServerEntry || entry instanceof MultiplayerServerListWidget.ServerEntry) {
((MultiplayerScreen) screen).select(entry);
((MultiplayerScreen) screen).connect();
}
} else if (focused instanceof ParentElement) {
Element childFocused = ((ParentElement) focused).getFocused();
if (childFocused != null)
return this.handleAButton(screen, childFocused);
}
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)
{
Element focused = screen.getFocused();
if (focused != null)
if (this.handleRightLeftElement(focused, right))
return this.changeFocus(screen, right);
return true;
}
private boolean handleRightLeftElement(@NotNull Element element, boolean right)
{
if (element instanceof SliderWidget) {
SliderWidget slider = (SliderWidget) element;
slider.keyPressed(right ? 262 : 263, 0, 0);
this.actionGuiCooldown = 2; // Prevent to press too quickly the focused element, so we have to skip 5 ticks.
return false;
} else if (element instanceof AlwaysSelectedEntryListWidget) {
((EntryListWidgetAccessor) element).lambdacontrols_moveSelection(right ? 1 : -1);
return false;
} else if (element instanceof ParentElement) {
ParentElement entryList = (ParentElement) element;
Element focused = entryList.getFocused();
if (focused == null)
return true;
return this.handleRightLeftElement(focused, right);
}
return true;
}
/**
* Handles the look direction input.
*
* @param client The client isntance.
* @param axis The axis to change.
* @param value The value of the look.
* @param state The state.
*/
public void handleLook(@NotNull MinecraftClient client, int axis, float value, int state)
{
// Handles the look direction.
if (client.player != null) {
double powValue = Math.pow(value, 4.0);
if (axis == GLFW_GAMEPAD_AXIS_RIGHT_Y) {
if (state == 2) {
this.targetPitch = client.player.pitch - this.config.getRightYAxisSign() * (this.config.getRotationSpeed() * powValue) * 0.33D;
this.targetPitch = MathHelper.clamp(this.targetPitch, -90.0D, 90.0D);
} else if (state == 1) {
this.targetPitch = client.player.pitch + this.config.getRightYAxisSign() * (this.config.getRotationSpeed() * powValue) * 0.33D;
this.targetPitch = MathHelper.clamp(this.targetPitch, -90.0D, 90.0D);
}
}
if (axis == GLFW_GAMEPAD_AXIS_RIGHT_X) {
if (state == 2) {
this.targetYaw = client.player.yaw - this.config.getRightXAxisSign() * (this.config.getRotationSpeed() * powValue) * 0.33D;
} else if (state == 1) {
this.targetYaw = client.player.yaw + this.config.getRightXAxisSign() * (this.config.getRotationSpeed() * powValue) * 0.33D;
}
}
}
}
private boolean changeFocus(@NotNull Screen screen, boolean down)
{
if (!screen.changeFocus(down)) {
if (screen.changeFocus(down)) {
this.actionGuiCooldown = 5;
return false;
}
return true;
} else {
this.actionGuiCooldown = 5;
return false;
}
}
private static boolean isScreenInteractive(@NotNull Screen screen)
{
return !(screen instanceof AdvancementsScreen || screen instanceof ContainerScreen);
}
// Inspired from https://github.com/MrCrayfish/Controllable/blob/1.14.X/src/main/java/com/mrcrayfish/controllable/client/ControllerInput.java#L686.
private void moveMouseToClosestSlot(@NotNull MinecraftClient client, @Nullable Screen screen)
{
// Makes the mouse attracted to slots. This helps with selecting items when using a controller.
if (screen instanceof ContainerScreen) {
ContainerScreen inventoryScreen = (ContainerScreen) screen;
ContainerScreenAccessor accessor = (ContainerScreenAccessor) inventoryScreen;
int guiLeft = accessor.lambdacontrols_getX();
int guiTop = accessor.lambdacontrols_getY();
int mouseX = (int) (targetMouseX * (double) client.getWindow().getScaledWidth() / (double) client.getWindow().getWidth());
int mouseY = (int) (targetMouseY * (double) client.getWindow().getScaledHeight() / (double) client.getWindow().getHeight());
// Finds the closest slot in the GUI within 14 pixels.
Optional<Pair<Slot, Double>> closestSlot = inventoryScreen.getContainer().slots.parallelStream()
.map(slot -> {
int x = guiLeft + slot.xPosition + 8;
int y = guiTop + slot.yPosition + 8;
// Distance between the slot and the cursor.
double distance = Math.sqrt(Math.pow(x - mouseX, 2) + Math.pow(y - mouseY, 2));
return Pair.of(slot, distance);
}).filter(entry -> entry.value <= 14.0)
.min(Comparator.comparingDouble(p -> p.value));
if (closestSlot.isPresent()) {
Slot slot = closestSlot.get().key;
if (slot.hasStack() || !client.player.inventory.getMainHandStack().isEmpty()) {
int slotCenterXScaled = guiLeft + slot.xPosition + 8;
int slotCenterYScaled = guiTop + slot.yPosition + 8;
int slotCenterX = (int) (slotCenterXScaled / ((double) client.getWindow().getScaledWidth() / (double) client.getWindow().getWidth()));
int slotCenterY = (int) (slotCenterYScaled / ((double) client.getWindow().getScaledHeight() / (double) client.getWindow().getHeight()));
double deltaX = slotCenterX - targetMouseX;
double deltaY = slotCenterY - targetMouseY;
if (mouseX != slotCenterXScaled || mouseY != slotCenterYScaled) {
this.targetMouseX += deltaX * 0.75;
this.targetMouseY += deltaY * 0.75;
} else {
this.mouseSpeedX *= 0.3F;
this.mouseSpeedY *= 0.3F;
}
this.mouseSpeedX *= .75F;
this.mouseSpeedY *= .75F;
} else {
this.mouseSpeedX *= .1F;
this.mouseSpeedY *= .1F;
}
} else {
this.mouseSpeedX *= .3F;
this.mouseSpeedY *= .3F;
}
} else {
this.mouseSpeedX = 0.F;
this.mouseSpeedY = 0.F;
}
}
}

View File

@@ -1,30 +0,0 @@
/*
* Copyright © 2020 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of LambdaControls.
*
* Licensed under the MIT license. For more information,
* see the LICENSE file.
*/
package me.lambdaurora.lambdacontrols.client.compat;
import me.lambdaurora.lambdacontrols.client.LambdaControlsClient;
import org.jetbrains.annotations.NotNull;
/**
* Represents a compatibility handler for a mod.
*
* @author LambdAurora
* @version 1.1.0
* @since 1.1.0
*/
public interface CompatHandler
{
/**
* Handles compatibility of a mod.
*
* @param mod This mod instance.
*/
void handle(@NotNull LambdaControlsClient mod);
}

View File

@@ -1,40 +0,0 @@
/*
* Copyright © 2020 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of LambdaControls.
*
* Licensed under the MIT license. For more information,
* see the LICENSE file.
*/
package me.lambdaurora.lambdacontrols.client.compat;
import me.lambdaurora.lambdacontrols.client.LambdaControlsClient;
import me.lambdaurora.lambdacontrols.client.controller.InputManager;
import net.fabricmc.loader.api.FabricLoader;
import org.aperlambda.lambdacommon.utils.LambdaReflection;
import org.jetbrains.annotations.NotNull;
/**
* Represents a compatibility handler.
*
* @author LambdAurora
* @version 1.1.0
* @since 1.1.0
*/
public class LambdaControlsCompat
{
/**
* Initializes compatibility with other mods if needed.
*
* @param mod The mod instance.
*/
public static void init(@NotNull LambdaControlsClient mod)
{
if (FabricLoader.getInstance().isModLoaded("okzoomer") && LambdaReflection.doesClassExist(OkZoomerCompat.OKZOOMER_CLASS_PATH)) {
mod.log("Adding okzoomer compatibility...");
new OkZoomerCompat().handle(mod);
}
InputManager.loadButtonBindings(mod.config);
}
}

View File

@@ -1,40 +0,0 @@
/*
* Copyright © 2020 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of LambdaControls.
*
* Licensed under the MIT license. For more information,
* see the LICENSE file.
*/
package me.lambdaurora.lambdacontrols.client.compat;
import io.github.joaoh1.okzoomer.OkZoomer;
import me.lambdaurora.lambdacontrols.client.LambdaControlsClient;
import me.lambdaurora.lambdacontrols.client.controller.ButtonBinding;
import org.jetbrains.annotations.NotNull;
import org.lwjgl.glfw.GLFW;
/**
* Represents a compatibility handler for OkZoomer.
*
* @author LambdAurora
* @version 1.1.0
* @since 1.1.0
*/
public class OkZoomerCompat implements CompatHandler
{
public static final String OKZOOMER_CLASS_PATH = "io.github.joaoh1.okzoomer.OkZoomer";
@Override
public void handle(@NotNull LambdaControlsClient mod)
{
new ButtonBinding.Builder("zoom")
.buttons(GLFW.GLFW_GAMEPAD_BUTTON_DPAD_UP, GLFW.GLFW_GAMEPAD_BUTTON_X)
.onlyInGame()
.cooldown(true)
.category(ButtonBinding.MISC_CATEGORY)
.linkKeybind(OkZoomer.zoomKeyBinding)
.register();
}
}

View File

@@ -1,41 +0,0 @@
/*
* Copyright © 2020 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of LambdaControls.
*
* Licensed under the MIT license. For more information,
* see the LICENSE file.
*/
package me.lambdaurora.lambdacontrols.client.controller;
import me.lambdaurora.lambdacontrols.client.ButtonState;
import me.lambdaurora.lambdacontrols.client.util.KeyBindingAccessor;
import net.minecraft.client.MinecraftClient;
import org.jetbrains.annotations.NotNull;
/**
* Represents a press action callback.
*
* @author LambdAurora
* @version 1.1.0
* @since 1.0.0
*/
@FunctionalInterface
public interface PressAction
{
PressAction DEFAULT_ACTION = (client, button, action) -> {
if (action == ButtonState.REPEAT || client.currentScreen != null)
return false;
button.asKeyBinding().ifPresent(binding -> ((KeyBindingAccessor) binding).lambdacontrols_handlePressState(button.isButtonDown()));
return true;
};
/**
* Handles when there is a press action.
*
* @param client The client instance.
* @param action The action done.
*/
boolean press(@NotNull MinecraftClient client, @NotNull ButtonBinding button, @NotNull ButtonState action);
}

View File

@@ -1,59 +0,0 @@
/*
* Copyright © 2020 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of LambdaControls.
*
* Licensed under the MIT license. For more information,
* see the LICENSE file.
*/
package me.lambdaurora.lambdacontrols.client.gui;
import me.lambdaurora.lambdacontrols.client.LambdaControlsClient;
import me.lambdaurora.lambdacontrols.client.controller.ButtonBinding;
import me.lambdaurora.spruceui.AbstractIconButtonWidget;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.resource.language.I18n;
import org.aperlambda.lambdacommon.utils.Pair;
import org.jetbrains.annotations.NotNull;
/**
* Represents a controller button widget.
*/
public class ControllerButtonWidget extends AbstractIconButtonWidget
{
private ButtonBinding binding;
private int iconWidth;
public ControllerButtonWidget(int x, int y, int width, @NotNull ButtonBinding binding, @NotNull PressAction action)
{
super(x, y, width, 20, ButtonBinding.getLocalizedButtonName(binding.getButton()[0]), action);
this.binding = binding;
}
public void update()
{
int length = binding.getButton().length;
this.setMessage(this.binding.isNotBound() ? I18n.translate("lambdacontrols.not_bound") :
(length > 0 ? ButtonBinding.getLocalizedButtonName(binding.getButton()[0]) : "<>"));
}
@Override
public String getMessage()
{
if (this.binding.getButton().length > 1)
return "";
return super.getMessage();
}
@Override
protected int renderIcon(int mouseX, int mouseY, float delta, int x, int y)
{
if (this.binding.getButton().length > 1) {
x += (this.width / 2 - this.iconWidth / 2) - 4;
}
Pair<Integer, Integer> size = LambdaControlsClient.drawButton(x, y, this.binding, MinecraftClient.getInstance());
this.iconWidth = size.key;
return size.value;
}
}

View File

@@ -1,81 +0,0 @@
/*
* Copyright © 2020 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of LambdaControls.
*
* Licensed under the MIT license. For more information,
* see the LICENSE file.
*/
package me.lambdaurora.lambdacontrols.client.gui;
import me.lambdaurora.lambdacontrols.client.LambdaControlsClient;
import me.lambdaurora.lambdacontrols.client.controller.ButtonBinding;
import me.lambdaurora.lambdacontrols.client.controller.InputManager;
import me.lambdaurora.spruceui.SpruceButtonWidget;
import net.minecraft.client.gui.screen.Screen;
import net.minecraft.client.gui.screen.options.ControlsOptionsScreen;
import net.minecraft.client.gui.widget.ButtonWidget;
import net.minecraft.client.resource.language.I18n;
import net.minecraft.text.TranslatableText;
import org.aperlambda.lambdacommon.utils.function.Predicates;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
/**
* Represents the controls screen.
*/
public class ControllerControlsScreen extends Screen
{
private final Screen parent;
final LambdaControlsClient mod;
private final boolean hideSettings;
private ControlsListWidget bindingsListWidget;
private ButtonWidget resetButton;
public ButtonBinding focusedBinding;
public boolean waiting = false;
public List<Integer> currentButtons = new ArrayList<>();
public ControllerControlsScreen(@NotNull Screen parent, boolean hideSettings)
{
super(new TranslatableText("lambdacontrols.menu.title.controller_controls"));
this.parent = parent;
this.mod = LambdaControlsClient.get();
this.hideSettings = hideSettings;
}
@Override
public void removed()
{
this.mod.config.save();
super.removed();
}
@Override
protected void init()
{
this.addButton(new SpruceButtonWidget(this.width / 2 - 155, 18, this.hideSettings ? 310 : 150, 20, I18n.translate("lambdacontrols.menu.keyboard_controls"),
btn -> this.minecraft.openScreen(new ControlsOptionsScreen(this, this.minecraft.options))));
if (!this.hideSettings)
this.addButton(new SpruceButtonWidget(this.width / 2 - 155 + 160, 18, 150, 20, I18n.translate("menu.options"),
btn -> this.minecraft.openScreen(new LambdaControlsSettingsScreen(this, this.minecraft.options, true))));
this.bindingsListWidget = new ControlsListWidget(this, this.minecraft);
this.children.add(this.bindingsListWidget);
this.resetButton = this.addButton(new ButtonWidget(this.width / 2 - 155, this.height - 29, 150, 20, I18n.translate("controls.resetAll"),
btn -> InputManager.streamBindings().forEach(binding -> this.mod.config.setButtonBinding(binding, binding.getDefaultButton()))));
this.addButton(new ButtonWidget(this.width / 2 - 155 + 160, this.height - 29, 150, 20, I18n.translate("gui.done"),
btn -> this.minecraft.openScreen(this.parent)));
}
@Override
public void render(int mouseX, int mouseY, float delta)
{
this.renderBackground();
this.bindingsListWidget.render(mouseX, mouseY, delta);
this.drawCenteredString(this.font, this.title.asFormattedString(), this.width / 2, 8, 16777215);
this.resetButton.active = InputManager.streamBindings().anyMatch(Predicates.not(ButtonBinding::isDefault));
super.render(mouseX, mouseY, delta);
}
}

View File

@@ -1,205 +0,0 @@
/*
* Copyright © 2020 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of LambdaControls.
*
* Licensed under the MIT license. For more information,
* see the LICENSE file.
*/
package me.lambdaurora.lambdacontrols.client.gui;
import me.lambdaurora.lambdacontrols.client.controller.ButtonBinding;
import me.lambdaurora.lambdacontrols.client.controller.ButtonCategory;
import me.lambdaurora.lambdacontrols.client.controller.InputManager;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.font.TextRenderer;
import net.minecraft.client.gui.Element;
import net.minecraft.client.gui.widget.ButtonWidget;
import net.minecraft.client.gui.widget.ElementListWidget;
import net.minecraft.client.resource.language.I18n;
import net.minecraft.util.Formatting;
import org.jetbrains.annotations.NotNull;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
/**
* Represents a control list widget.
*/
public class ControlsListWidget extends ElementListWidget<ControlsListWidget.Entry>
{
private static final int[] UNBOUND = new int[0];
private final ControllerControlsScreen gui;
private int field_2733;
public ControlsListWidget(@NotNull ControllerControlsScreen gui, @NotNull MinecraftClient client)
{
super(client, gui.width + 45, gui.height, 43, gui.height - 32, 24);
this.gui = gui;
InputManager.streamCategories()
.sorted(Comparator.comparingInt(ButtonCategory::getPriority))
.forEach(category -> {
this.addEntry(new CategoryEntry(category));
category.getBindings().forEach(binding -> {
int i = client.textRenderer.getStringWidth(I18n.translate(binding.getTranslationKey()));
if (i > this.field_2733) {
this.field_2733 = i;
}
this.addEntry(new ControlsListWidget.ButtonBindingEntry(binding));
});
});
}
@Override
protected int getScrollbarPosition()
{
return super.getScrollbarPosition() + 15;
}
@Override
public int getRowWidth()
{
return super.getRowWidth() + 32;
}
public class ButtonBindingEntry extends Entry
{
private final ButtonBinding binding;
private final String bindingName;
private final ControllerButtonWidget editButton;
private final ButtonWidget resetButton;
private final ButtonWidget unboundButton;
ButtonBindingEntry(@NotNull ButtonBinding binding)
{
this.binding = binding;
this.bindingName = I18n.translate(this.binding.getTranslationKey());
this.editButton = new ControllerButtonWidget(0, 0, 110, this.binding, btn -> {
gui.focusedBinding = binding;
gui.currentButtons.clear();
gui.waiting = true;
})
{
protected String getNarrationMessage()
{
return binding.isNotBound() ? I18n.translate("narrator.controls.unbound", bindingName) : I18n.translate("narrator.controls.bound", bindingName, super.getNarrationMessage());
}
};
this.resetButton = new ButtonWidget(0, 0, 50, 20, I18n.translate("controls.reset"),
btn -> gui.mod.config.setButtonBinding(binding, binding.getDefaultButton()))
{
protected String getNarrationMessage()
{
return I18n.translate("narrator.controls.reset", bindingName);
}
};
this.unboundButton = new ButtonWidget(0, 0, 50, 20, I18n.translate("lambdacontrols.menu.unbound"),
btn -> {
gui.mod.config.setButtonBinding(binding, UNBOUND);
gui.focusedBinding = null;
})
{
protected String getNarrationMessage()
{
return I18n.translate("lambdacontrols.narrator.unbound", bindingName);
}
};
}
@Override
public List<? extends Element> children()
{
return Collections.unmodifiableList(Arrays.asList(this.editButton, this.resetButton));
}
@Override
public void render(int index, int y, int x, int width, int height, int mouseX, int mouseY, boolean hovering, float delta)
{
boolean focused = gui.focusedBinding == this.binding;
TextRenderer textRenderer = ControlsListWidget.this.minecraft.textRenderer;
String bindingName = this.bindingName;
float var10002 = (float) (x + 70 - ControlsListWidget.this.field_2733);
int var10003 = y + height / 2;
textRenderer.draw(bindingName, var10002, (float) (var10003 - 9 / 2), 16777215);
this.resetButton.x = this.unboundButton.x = x + 190;
this.resetButton.y = this.unboundButton.y = y;
this.resetButton.active = !this.binding.isDefault();
if (focused)
this.unboundButton.render(mouseX, mouseY, delta);
else
this.resetButton.render(mouseX, mouseY, delta);
this.editButton.x = x + 75;
this.editButton.y = y;
this.editButton.update();
if (focused) {
this.editButton.setMessage(Formatting.WHITE + "> " + Formatting.YELLOW + this.editButton.getMessage() + Formatting.WHITE + " <");
} else if (!this.binding.isNotBound() && InputManager.hasDuplicatedBindings(this.binding)) {
this.editButton.setMessage(Formatting.RED + this.editButton.getMessage());
} else if (this.binding.isNotBound()) {
this.editButton.setMessage(Formatting.GOLD + this.editButton.getMessage());
}
this.editButton.render(mouseX, mouseY, delta);
}
public boolean mouseClicked(double mouseX, double mouseY, int button)
{
boolean focused = gui.focusedBinding == this.binding;
if (this.editButton.mouseClicked(mouseX, mouseY, button))
return true;
else
return focused ? this.unboundButton.mouseClicked(mouseX, mouseY, button) : this.resetButton.mouseClicked(mouseX, mouseY, button);
}
public boolean mouseReleased(double mouseX, double mouseY, int button)
{
return this.editButton.mouseReleased(mouseX, mouseY, button) || this.resetButton.mouseReleased(mouseX, mouseY, button)
|| this.unboundButton.mouseReleased(mouseX, mouseY, button);
}
}
public class CategoryEntry extends Entry
{
private final String name;
private final int nameWidth;
public CategoryEntry(@NotNull ButtonCategory category)
{
this.name = category.getTranslatedName();
this.nameWidth = ControlsListWidget.this.minecraft.textRenderer.getStringWidth(this.name);
}
@Override
public void render(int index, int y, int x, int width, int height, int mouseX, int mouseY, boolean hovering, float delta)
{
ControlsListWidget.this.minecraft.textRenderer.draw(this.name, (float) (ControlsListWidget.this.minecraft.currentScreen.width / 2 - this.nameWidth / 2),
(float) ((y + height) - 9 - 1), 16777215);
}
@Override
public boolean changeFocus(boolean bl)
{
return false;
}
@Override
public List<? extends Element> children()
{
return Collections.emptyList();
}
}
@Environment(EnvType.CLIENT)
public abstract static class Entry extends ElementListWidget.Entry<Entry>
{
}
}

View File

@@ -1,81 +0,0 @@
/*
* Copyright © 2020 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of LambdaControls.
*
* Licensed under the MIT license. For more information,
* see the LICENSE file.
*/
package me.lambdaurora.lambdacontrols.client.gui;
import me.lambdaurora.lambdacontrols.ControlsMode;
import me.lambdaurora.lambdacontrols.LambdaControlsConstants;
import me.lambdaurora.lambdacontrols.client.HudSide;
import me.lambdaurora.lambdacontrols.client.LambdaControlsClient;
import me.lambdaurora.lambdacontrols.client.controller.ButtonBinding;
import me.lambdaurora.spruceui.hud.Hud;
import net.minecraft.client.MinecraftClient;
import net.minecraft.util.Identifier;
import net.minecraft.util.hit.HitResult;
import org.jetbrains.annotations.NotNull;
/**
* Represents the LambdaControls HUD.
*
* @author LambdAurora
* @version 1.1.0
* @since 1.0.0
*/
public class LambdaControlsHud extends Hud
{
private final LambdaControlsClient mod;
private MinecraftClient client;
private int widthBottom = 0;
private int widthTop = 0;
public LambdaControlsHud(@NotNull LambdaControlsClient mod)
{
super(new Identifier(LambdaControlsConstants.NAMESPACE, "hud/button_indicator"));
this.mod = mod;
}
@Override
public void init(@NotNull MinecraftClient client, int screenWidth, int screenHeight)
{
super.init(client, screenWidth, screenHeight);
this.client = client;
}
/**
* Renders the LambdaControls' HUD.
*/
public void render(float tickDelta)
{
if (this.mod.config.getControlsMode() == ControlsMode.CONTROLLER && this.client.currentScreen == null) {
int x = this.mod.config.getHudSide() == HudSide.LEFT ? 10 : client.getWindow().getScaledWidth() - 10 - this.widthBottom, y = bottom(10);
x += (this.widthBottom = this.drawButtonTip(x, y, ButtonBinding.INVENTORY, true) + 10);
this.widthBottom += this.drawButtonTip(x, y, ButtonBinding.SWAP_HANDS, true);
x = this.mod.config.getHudSide() == HudSide.LEFT ? 10 : client.getWindow().getScaledWidth() - 10 - this.widthTop;
x += (this.widthTop = this.drawButtonTip(x, (y -= 20), ButtonBinding.DROP_ITEM, !this.client.player.getMainHandStack().isEmpty()) + 10);
this.widthTop += this.drawButtonTip(x, y, ButtonBinding.ATTACK.getButton(),
this.client.crosshairTarget.getType() == HitResult.Type.BLOCK ? "lambdacontrols.action.hit" : ButtonBinding.ATTACK.getTranslationKey(),
this.client.crosshairTarget.getType() != HitResult.Type.MISS);
}
}
private int bottom(int y)
{
return this.client.getWindow().getScaledHeight() - y - 15;
}
private int drawButtonTip(int x, int y, @NotNull ButtonBinding button, boolean display)
{
return LambdaControlsClient.drawButtonTip(x, y, button, display, this.client);
}
private int drawButtonTip(int x, int y, int[] button, @NotNull String action, boolean display)
{
return LambdaControlsClient.drawButtonTip(x, y, button, action, display, this.client);
}
}

View File

@@ -1,265 +0,0 @@
/*
* Copyright © 2020 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of LambdaControls.
*
* Licensed under the MIT license. For more information,
* see the LICENSE file.
*/
package me.lambdaurora.lambdacontrols.client.gui;
import me.lambdaurora.lambdacontrols.ControlsMode;
import me.lambdaurora.lambdacontrols.LambdaControls;
import me.lambdaurora.lambdacontrols.client.LambdaControlsClient;
import me.lambdaurora.lambdacontrols.client.controller.Controller;
import me.lambdaurora.spruceui.SpruceButtonWidget;
import me.lambdaurora.spruceui.SpruceLabelWidget;
import me.lambdaurora.spruceui.Tooltip;
import me.lambdaurora.spruceui.option.*;
import net.fabricmc.fabric.api.network.ClientSidePacketRegistry;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gui.screen.Screen;
import net.minecraft.client.gui.screen.options.ControlsOptionsScreen;
import net.minecraft.client.gui.widget.ButtonListWidget;
import net.minecraft.client.gui.widget.ButtonWidget;
import net.minecraft.client.options.GameOptions;
import net.minecraft.client.options.Option;
import net.minecraft.client.resource.language.I18n;
import net.minecraft.text.TranslatableText;
import net.minecraft.util.Formatting;
import net.minecraft.util.Util;
import org.jetbrains.annotations.NotNull;
import org.lwjgl.glfw.GLFW;
/**
* Represents the LambdaControls settings screen.
*/
public class LambdaControlsSettingsScreen extends Screen
{
public static final String GAMEPAD_TOOL_URL = "http://generalarcade.com/gamepadtool/";
final LambdaControlsClient mod;
private final Screen parent;
private final boolean hideControls;
// General options
private final Option autoSwitchModeOption;
private final Option rotationSpeedOption;
private final Option mouseSpeedOption;
private final Option resetOption;
// Gameplay options
private final Option frontBlockPlacingOption;
private final Option flyDriftingOption;
private final Option flyVerticalDriftingOption;
// Controller options
private final Option controllerOption;
private final Option secondControllerOption;
private final Option controllerTypeOption;
private final Option deadZoneOption;
private final Option invertsRightXAxis;
private final Option invertsRightYAxis;
private final Option unfocusedInputOption;
// Hud options
private final Option hudEnableOption;
private final Option hudSideOption;
private final String controllerMappingsUrlText = I18n.translate("lambdacontrols.controller.mappings.2", Formatting.GOLD.toString(), GAMEPAD_TOOL_URL, Formatting.RESET.toString());
private ButtonListWidget list;
private SpruceLabelWidget gamepadToolUrlLabel;
public LambdaControlsSettingsScreen(Screen parent, @NotNull GameOptions options, boolean hideControls)
{
super(new TranslatableText("lambdacontrols.title.settings"));
this.mod = LambdaControlsClient.get();
this.parent = parent;
this.hideControls = hideControls;
// General options
this.autoSwitchModeOption = new SpruceBooleanOption("lambdacontrols.menu.auto_switch_mode", this.mod.config::hasAutoSwitchMode,
this.mod.config::setAutoSwitchMode, new TranslatableText("lambdacontrols.tooltip.auto_switch_mode"), true);
this.rotationSpeedOption = new SpruceDoubleOption("lambdacontrols.menu.rotation_speed", 0.0, 150.0, 0.5F, this.mod.config::getRotationSpeed,
newValue -> {
synchronized (this.mod.config) {
this.mod.config.setRotationSpeed(newValue);
}
}, option -> option.getDisplayPrefix() + option.get(),
new TranslatableText("lambdacontrols.tooltip.rotation_speed"));
this.mouseSpeedOption = new SpruceDoubleOption("lambdacontrols.menu.mouse_speed", 0.0, 150.0, 0.5F, this.mod.config::getMouseSpeed,
newValue -> {
synchronized (this.mod.config) {
this.mod.config.setMouseSpeed(newValue);
}
}, option -> option.getDisplayPrefix() + option.get(),
new TranslatableText("lambdacontrols.tooltip.mouse_speed"));
this.resetOption = new SpruceResetOption(btn -> {
this.mod.config.reset();
MinecraftClient client = MinecraftClient.getInstance();
this.init(client, client.getWindow().getScaledWidth(), client.getWindow().getScaledHeight());
});
// Gameplay options
this.frontBlockPlacingOption = new SpruceBooleanOption("lambdacontrols.menu.front_block_placing", this.mod.config::hasFrontBlockPlacing,
this.mod.config::setFrontBlockPlacing, new TranslatableText("lambdacontrols.tooltip.front_block_placing"), true);
this.flyDriftingOption = new SpruceBooleanOption("lambdacontrols.menu.fly_drifting", this.mod.config::hasFlyDrifting,
this.mod.config::setFlyDrifting, new TranslatableText("lambdacontrols.tooltip.fly_drifting"), true);
this.flyVerticalDriftingOption = new SpruceBooleanOption("lambdacontrols.menu.fly_drifting_vertical", this.mod.config::hasFlyVerticalDrifting,
this.mod.config::setFlyVerticalDrifting, new TranslatableText("lambdacontrols.tooltip.fly_drifting_vertical"), true);
// Controller options
this.controllerOption = new SpruceCyclingOption("lambdacontrols.menu.controller", amount -> {
int id = this.mod.config.getController().getId();
id += amount;
if (id > GLFW.GLFW_JOYSTICK_LAST)
id = GLFW.GLFW_JOYSTICK_1;
this.mod.config.setController(Controller.byId(id));
}, option -> {
String controllerName = this.mod.config.getController().getName();
if (!this.mod.config.getController().isConnected())
return option.getDisplayPrefix() + Formatting.RED + controllerName;
else if (!this.mod.config.getController().isGamepad())
return option.getDisplayPrefix() + Formatting.GOLD + controllerName;
else
return option.getDisplayPrefix() + controllerName;
}, null);
this.secondControllerOption = new SpruceCyclingOption("lambdacontrols.menu.controller2",
amount -> {
int id = this.mod.config.getSecondController().map(Controller::getId).orElse(-1);
id += amount;
if (id > GLFW.GLFW_JOYSTICK_LAST)
id = -1;
this.mod.config.setSecondController(id == -1 ? null : Controller.byId(id));
}, option -> this.mod.config.getSecondController().map(controller -> {
String controllerName = controller.getName();
if (!controller.isConnected())
return option.getDisplayPrefix() + Formatting.RED + controllerName;
else if (!controller.isGamepad())
return option.getDisplayPrefix() + Formatting.GOLD + controllerName;
else
return option.getDisplayPrefix() + controllerName;
}).orElse(option.getDisplayPrefix() + Formatting.RED + I18n.translate("options.off")),
new TranslatableText("lambdacontrols.tooltip.controller2"));
this.controllerTypeOption = new SpruceCyclingOption("lambdacontrols.menu.controller_type",
amount -> this.mod.config.setControllerType(this.mod.config.getControllerType().next()),
option -> option.getDisplayPrefix() + this.mod.config.getControllerType().getTranslatedName(),
new TranslatableText("lambdacontrols.tooltip.controller_type"));
this.deadZoneOption = new SpruceDoubleOption("lambdacontrols.menu.dead_zone", 0.05, 1.0, 0.05F, this.mod.config::getDeadZone,
newValue -> {
synchronized (this.mod.config) {
this.mod.config.setDeadZone(newValue);
}
}, option -> {
String value = String.valueOf(option.get());
return option.getDisplayPrefix() + value.substring(0, Math.min(value.length(), 5));
}, new TranslatableText("lambdacontrols.tooltip.dead_zone"));
this.invertsRightXAxis = new SpruceBooleanOption("lambdacontrols.menu.invert_right_x_axis", this.mod.config::doesInvertRightXAxis,
newValue -> {
synchronized (this.mod.config) {
this.mod.config.setInvertRightXAxis(newValue);
}
}, null, true);
this.invertsRightYAxis = new SpruceBooleanOption("lambdacontrols.menu.invert_right_y_axis", this.mod.config::doesInvertRightYAxis,
newValue -> {
synchronized (this.mod.config) {
this.mod.config.setInvertRightYAxis(newValue);
}
}, null, true);
this.unfocusedInputOption = new SpruceBooleanOption("lambdacontrols.menu.unfocused_input", this.mod.config::hasUnfocusedInput,
this.mod.config::setUnfocusedInput, new TranslatableText("lambdacontrols.tooltip.unfocused_input"), true);
// HUD options
this.hudEnableOption = new SpruceBooleanOption("lambdacontrols.menu.hud_enable", this.mod.config::isHudEnabled,
this.mod::setHudEnabled, new TranslatableText("lambdacontrols.tooltip.hud_enable"), true);
this.hudSideOption = new SpruceCyclingOption("lambdacontrols.menu.hud_side",
amount -> this.mod.config.setHudSide(this.mod.config.getHudSide().next()),
option -> option.getDisplayPrefix() + this.mod.config.getHudSide().getTranslatedName(),
new TranslatableText("lambdacontrols.tooltip.hud_side"));
}
@Override
public void removed()
{
this.mod.config.save();
super.removed();
}
@Override
public void onClose()
{
this.mod.config.save();
super.onClose();
}
private int getTextHeight()
{
return (5 + this.font.fontHeight) * 3 + 5;
}
@Override
protected void init()
{
super.init();
int buttonHeight = 20;
SpruceButtonWidget controlsModeBtn = new SpruceButtonWidget(this.width / 2 - 155, 18, this.hideControls ? 310 : 150, buttonHeight,
I18n.translate("lambdacontrols.menu.controls_mode") + ": " + I18n.translate(this.mod.config.getControlsMode().getTranslationKey()),
btn -> {
ControlsMode next = this.mod.config.getControlsMode().next();
btn.setMessage(I18n.translate("lambdacontrols.menu.controls_mode") + ": " + I18n.translate(next.getTranslationKey()));
this.mod.config.setControlsMode(next);
this.mod.config.save();
if (this.minecraft.player != null) {
ClientSidePacketRegistry.INSTANCE.sendToServer(LambdaControls.CONTROLS_MODE_CHANNEL, this.mod.makeControlsModeBuffer(next));
}
});
controlsModeBtn.setTooltip(new TranslatableText("lambdacontrols.tooltip.controls_mode"));
this.addButton(controlsModeBtn);
if (!this.hideControls)
this.addButton(new ButtonWidget(this.width / 2 - 155 + 160, 18, 150, buttonHeight, I18n.translate("options.controls"),
btn -> {
if (this.mod.config.getControlsMode() == ControlsMode.CONTROLLER)
this.minecraft.openScreen(new ControllerControlsScreen(this, true));
else
this.minecraft.openScreen(new ControlsOptionsScreen(this, this.minecraft.options));
}));
this.list = new ButtonListWidget(this.minecraft, this.width, this.height, 43, this.height - 29 - this.getTextHeight(), 25);
// General options
this.list.addSingleOptionEntry(new SpruceSeparatorOption("lambdacontrols.menu.title.general", true, null));
this.list.addOptionEntry(this.rotationSpeedOption, this.mouseSpeedOption);
this.list.addSingleOptionEntry(this.autoSwitchModeOption);
// Gameplay options
this.list.addSingleOptionEntry(new SpruceSeparatorOption("lambdacontrols.menu.title.gameplay", true, null));
this.list.addSingleOptionEntry(this.frontBlockPlacingOption);
this.list.addSingleOptionEntry(this.flyDriftingOption);
this.list.addSingleOptionEntry(this.flyVerticalDriftingOption);
// Controller options
this.list.addSingleOptionEntry(new SpruceSeparatorOption("lambdacontrols.menu.title.controller", true, null));
this.list.addSingleOptionEntry(this.controllerOption);
this.list.addSingleOptionEntry(this.secondControllerOption);
this.list.addOptionEntry(this.controllerTypeOption, this.deadZoneOption);
this.list.addOptionEntry(this.invertsRightXAxis, this.invertsRightYAxis);
this.list.addSingleOptionEntry(this.unfocusedInputOption);
this.list.addSingleOptionEntry(new ReloadControllerMappingsOption());
// HUD options
this.list.addSingleOptionEntry(new SpruceSeparatorOption("lambdacontrols.menu.title.hud", true, null));
this.list.addOptionEntry(this.hudEnableOption, this.hudSideOption);
this.children.add(this.list);
this.gamepadToolUrlLabel = new SpruceLabelWidget(this.width / 2, this.height - 29 - (5 + this.font.fontHeight) * 2, this.controllerMappingsUrlText, this.width,
label -> Util.getOperatingSystem().open(GAMEPAD_TOOL_URL), true);
this.gamepadToolUrlLabel.setTooltip(new TranslatableText("chat.link.open"));
this.children.add(this.gamepadToolUrlLabel);
this.addButton(this.resetOption.createButton(this.minecraft.options, this.width / 2 - 155, this.height - 29, 150));
this.addButton(new ButtonWidget(this.width / 2 - 155 + 160, this.height - 29, 150, buttonHeight, I18n.translate("gui.done"),
(buttonWidget) -> this.minecraft.openScreen(this.parent)));
}
@Override
public void render(int mouseX, int mouseY, float delta)
{
this.renderBackground();
this.list.render(mouseX, mouseY, delta);
super.render(mouseX, mouseY, delta);
this.drawCenteredString(this.font, I18n.translate("lambdacontrols.menu.title"), this.width / 2, 8, 16777215);
this.drawCenteredString(this.font, I18n.translate("lambdacontrols.controller.mappings.1", Formatting.GREEN.toString(), Formatting.RESET.toString()), this.width / 2, this.height - 29 - (5 + this.font.fontHeight) * 3, 10526880);
this.gamepadToolUrlLabel.render(mouseX, mouseY, delta);
this.drawCenteredString(this.font, I18n.translate("lambdacontrols.controller.mappings.3", Formatting.GREEN.toString(), Formatting.RESET.toString()), this.width / 2, this.height - 29 - (5 + this.font.fontHeight), 10526880);
Tooltip.renderAll();
}
}

View File

@@ -1,55 +0,0 @@
/*
* Copyright © 2020 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of LambdaControls.
*
* Licensed under the MIT license. For more information,
* see the LICENSE file.
*/
package me.lambdaurora.lambdacontrols.client.gui;
import me.lambdaurora.lambdacontrols.client.controller.Controller;
import me.lambdaurora.spruceui.SpruceButtonWidget;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gui.widget.AbstractButtonWidget;
import net.minecraft.client.options.GameOptions;
import net.minecraft.client.options.Option;
import net.minecraft.client.resource.language.I18n;
import net.minecraft.client.toast.SystemToast;
import net.minecraft.text.TranslatableText;
import org.aperlambda.lambdacommon.utils.Nameable;
import org.jetbrains.annotations.NotNull;
/**
* Represents the option to reload the controller mappings.
*/
public class ReloadControllerMappingsOption extends Option implements Nameable
{
private static final String KEY = "lambdacontrols.menu.reload_controller_mappings";
public ReloadControllerMappingsOption()
{
super(KEY);
}
@Override
public AbstractButtonWidget createButton(GameOptions options, int x, int y, int width)
{
SpruceButtonWidget button = new SpruceButtonWidget(x, y, width, 20, this.getName(), btn -> {
MinecraftClient client = MinecraftClient.getInstance();
Controller.updateMappings();
if (client.currentScreen != null)
client.currentScreen.init(client, client.getWindow().getScaledWidth(), client.getWindow().getScaledHeight());
client.getToastManager().add(new SystemToast(SystemToast.Type.TUTORIAL_HINT, new TranslatableText("lambdacontrols.controller.mappings.updated"), null));
});
button.setTooltip(new TranslatableText("lambdacontrols.tooltip.reload_controller_mappings"));
return button;
}
@Override
public @NotNull String getName()
{
return I18n.translate(KEY);
}
}

View File

@@ -1,32 +0,0 @@
/*
* Copyright © 2020 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of LambdaControls.
*
* Licensed under the MIT license. For more information,
* see the LICENSE file.
*/
package me.lambdaurora.lambdacontrols.client.mixin;
import me.lambdaurora.lambdacontrols.LambdaControls;
import me.lambdaurora.lambdacontrols.client.LambdaControlsClient;
import net.fabricmc.fabric.api.network.ClientSidePacketRegistry;
import net.minecraft.client.network.ClientPlayNetworkHandler;
import net.minecraft.client.network.packet.GameJoinS2CPacket;
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(ClientPlayNetworkHandler.class)
public class ClientPlayNetworkHandlerMixin
{
@Inject(method = "onGameJoin", at = @At(value = "TAIL"))
private void lambdacontrols_onConnect(GameJoinS2CPacket packet, CallbackInfo ci)
{
ClientSidePacketRegistry.INSTANCE.sendToServer(LambdaControls.HELLO_CHANNEL, LambdaControls.get().makeHello(LambdaControlsClient.get().config.getControlsMode()));
ClientSidePacketRegistry.INSTANCE.sendToServer(LambdaControls.CONTROLS_MODE_CHANNEL,
LambdaControls.get().makeControlsModeBuffer(LambdaControlsClient.get().config.getControlsMode()));
}
}

View File

@@ -1,62 +0,0 @@
/*
* Copyright © 2020 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of LambdaControls.
*
* Licensed under the MIT license. For more information,
* see the LICENSE file.
*/
package me.lambdaurora.lambdacontrols.client.mixin;
import me.lambdaurora.lambdacontrols.ControlsMode;
import me.lambdaurora.lambdacontrols.client.LambdaControlsClient;
import me.lambdaurora.lambdacontrols.client.util.ContainerScreenAccessor;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gui.screen.ingame.ContainerScreen;
import net.minecraft.container.Slot;
import org.lwjgl.glfw.GLFW;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Invoker;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
/**
* Represents the mixin for the class ContainerScreen.
*/
@Mixin(ContainerScreen.class)
public abstract class ContainerScreenMixin implements ContainerScreenAccessor
{
protected int x;
protected int y;
@Override
public int lambdacontrols_getX()
{
return this.x;
}
@Override
public int lambdacontrols_getY()
{
return this.y;
}
@Invoker("getSlotAt")
public abstract Slot lambdacontrols_getSlotAt(double posX, double posY);
@Inject(method = "render", at = @At("RETURN"))
public void render(int mouseX, int mouseY, float delta, CallbackInfo ci)
{
if (LambdaControlsClient.get().config.getControlsMode() == ControlsMode.CONTROLLER) {
MinecraftClient client = MinecraftClient.getInstance();
int x = 10, y = client.getWindow().getScaledHeight() - 10 - 15;
x += LambdaControlsClient.drawButtonTip(x, y, new int[]{GLFW.GLFW_GAMEPAD_BUTTON_A}, "lambdacontrols.action.pickup_all", true, client) + 10;
x += LambdaControlsClient.drawButtonTip(x, y, new int[]{GLFW.GLFW_GAMEPAD_BUTTON_B}, "lambdacontrols.action.exit", true, client) + 10;
x += LambdaControlsClient.drawButtonTip(x, y, new int[]{GLFW.GLFW_GAMEPAD_BUTTON_X}, "lambdacontrols.action.pickup", true, client) + 10;
LambdaControlsClient.drawButtonTip(x, y, new int[]{GLFW.GLFW_GAMEPAD_BUTTON_Y}, "lambdacontrols.action.quick_move", true, client);
}
}
}

View File

@@ -1,121 +0,0 @@
/*
* Copyright © 2020 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of LambdaControls.
*
* Licensed under the MIT license. For more information,
* see the LICENSE file.
*/
package me.lambdaurora.lambdacontrols.client.mixin;
import me.lambdaurora.lambdacontrols.LambdaControlsFeature;
import me.lambdaurora.lambdacontrols.client.LambdaControlsClient;
import net.minecraft.block.BlockState;
import net.minecraft.block.FluidBlock;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gui.screen.Screen;
import net.minecraft.client.network.ClientPlayerEntity;
import net.minecraft.client.network.ClientPlayerInteractionManager;
import net.minecraft.client.render.GameRenderer;
import net.minecraft.client.world.ClientWorld;
import net.minecraft.item.BlockItem;
import net.minecraft.item.ItemStack;
import net.minecraft.util.ActionResult;
import net.minecraft.util.Hand;
import net.minecraft.util.hit.BlockHitResult;
import net.minecraft.util.hit.HitResult;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.util.math.MathHelper;
import org.jetbrains.annotations.Nullable;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
@Mixin(MinecraftClient.class)
public abstract class MinecraftClientMixin
{
@Shadow
@Nullable
public HitResult crosshairTarget;
@Shadow
@Nullable
public ClientPlayerEntity player;
@Shadow
@Nullable
public ClientPlayerInteractionManager interactionManager;
@Shadow
@Nullable
public ClientWorld world;
@Shadow
@Final
public GameRenderer gameRenderer;
@Inject(method = "<init>", at = @At("RETURN"))
private void lambdacontrols_onInit(CallbackInfo ci)
{
LambdaControlsClient.get().onMcInit((MinecraftClient) (Object) this);
}
@Inject(method = "render", at = @At("HEAD"))
private void lambdacontrols_onRender(boolean fullRender, CallbackInfo ci)
{
LambdaControlsClient.get().onRender((MinecraftClient) (Object) (this));
}
@Inject(method = "disconnect(Lnet/minecraft/client/gui/screen/Screen;)V", at = @At("RETURN"))
private void lambdacontrols_onLeave(@Nullable Screen screen, CallbackInfo ci)
{
LambdaControlsClient.get().onLeave();
}
@Inject(method = "doItemUse()V", at = @At(value = "INVOKE", target = "Lnet/minecraft/util/hit/HitResult;getType()Lnet/minecraft/util/hit/HitResult$Type;"), locals = LocalCapture.CAPTURE_FAILEXCEPTION, cancellable = true)
private void lambdacontrols_onItemUse(CallbackInfo ci, Hand[] hands, int handCount, int handIndex, Hand hand, ItemStack stackInHand)
{
if (!stackInHand.isEmpty() && this.player.pitch > 35.0F && LambdaControlsFeature.FRONT_BLOCK_PLACING.isAvailable()) {
if (this.crosshairTarget != null && this.crosshairTarget.getType() == HitResult.Type.MISS && this.player.onGround) {
if (!stackInHand.isEmpty() && stackInHand.getItem() instanceof BlockItem) {
BlockPos playerPos = this.player.getBlockPos().down();
BlockPos targetPos = new BlockPos(this.crosshairTarget.getPos()).subtract(playerPos);
BlockPos vector = new BlockPos(MathHelper.clamp(targetPos.getX(), -1, 1), 0, MathHelper.clamp(targetPos.getZ(), -1, 1));
BlockPos blockPos = playerPos.add(vector);
Direction direction = player.getHorizontalFacing();
BlockState adjacentBlockState = this.world.getBlockState(blockPos.offset(direction.getOpposite()));
if (adjacentBlockState.isAir() || adjacentBlockState.getBlock() instanceof FluidBlock || (vector.getX() == 0 && vector.getZ() == 0)) {
return;
}
BlockHitResult hitResult = new BlockHitResult(this.crosshairTarget.getPos(), direction.getOpposite(), blockPos, false);
int previousStackCount = stackInHand.getCount();
ActionResult result = this.interactionManager.interactBlock(this.player, this.world, hand, hitResult);
if (result.isAccepted()) {
if (result.shouldSwingHand()) {
this.player.swingHand(hand);
if (!stackInHand.isEmpty() && (stackInHand.getCount() != previousStackCount || this.interactionManager.hasCreativeInventory())) {
this.gameRenderer.firstPersonRenderer.resetEquipProgress(hand);
}
}
ci.cancel();
}
if (result == ActionResult.FAIL) {
ci.cancel();
}
}
}
}
}
}

View File

@@ -1,46 +0,0 @@
/*
* Copyright © 2020 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of LambdaControls.
*
* Licensed under the MIT license. For more information,
* see the LICENSE file.
*/
package me.lambdaurora.lambdacontrols.client.mixin;
import me.lambdaurora.lambdacontrols.ControlsMode;
import me.lambdaurora.lambdacontrols.client.LambdaControlsClient;
import me.lambdaurora.lambdacontrols.client.util.MouseAccessor;
import net.minecraft.client.Mouse;
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;
/**
* Adds extra access to the mouse.
*/
@Mixin(Mouse.class)
public abstract class MouseMixin implements MouseAccessor
{
@Shadow
protected abstract void onCursorPos(long window, double x, double y);
@Shadow
protected abstract void onMouseButton(long window, int button, int action, int mods);
@Inject(method = "lockCursor", at = @At("HEAD"), cancellable = true)
private void lambdacontrols_onMouseLocked(CallbackInfo ci)
{
if (LambdaControlsClient.get().config.getControlsMode() == ControlsMode.TOUCHSCREEN)
ci.cancel();
}
@Override
public void lambdacontrols_onCursorPos(long window, double x, double y)
{
this.onCursorPos(window, x, y);
}
}

View File

@@ -1,45 +0,0 @@
/*
* Copyright © 2020 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of LambdaControls.
*
* Licensed under the MIT license. For more information,
* see the LICENSE file.
*/
package me.lambdaurora.lambdacontrols.client.mixin;
import me.lambdaurora.lambdacontrols.ControlsMode;
import me.lambdaurora.lambdacontrols.client.LambdaControlsClient;
import me.lambdaurora.lambdacontrols.client.gui.ControllerControlsScreen;
import net.minecraft.client.gui.screen.Screen;
import net.minecraft.client.gui.screen.SettingsScreen;
import net.minecraft.client.gui.widget.AbstractButtonWidget;
import net.minecraft.client.gui.widget.ButtonWidget;
import net.minecraft.text.Text;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
/**
* Injects the new controls settings button.
*/
@Mixin(SettingsScreen.class)
public class SettingsScreenMixin extends Screen
{
protected SettingsScreenMixin(Text title)
{
super(title);
}
@Redirect(method = "init", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screen/SettingsScreen;addButton(Lnet/minecraft/client/gui/widget/AbstractButtonWidget;)Lnet/minecraft/client/gui/widget/AbstractButtonWidget;", ordinal = 7))
private AbstractButtonWidget lambdacontrols_onInit(SettingsScreen screen, AbstractButtonWidget btn)
{
if (LambdaControlsClient.get().config.getControlsMode() == ControlsMode.CONTROLLER) {
return this.addButton(new ButtonWidget(btn.x, btn.y, btn.getWidth(), ((AbstractButtonWidgetAccessor) btn).lambdacontrols_getHeight(), btn.getMessage(),
b -> this.minecraft.openScreen(new ControllerControlsScreen(this, false))));
} else {
return this.addButton(btn);
}
}
}

View File

@@ -1,41 +0,0 @@
/*
* Copyright © 2020 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of LambdaControls.
*
* Licensed under the MIT license. For more information,
* see the LICENSE file.
*/
package me.lambdaurora.lambdacontrols.client.util;
import net.minecraft.container.Slot;
/**
* Represents an accessor to AbstractContainerScreen.
*/
public interface ContainerScreenAccessor
{
/**
* Gets the left coordinate of the GUI.
*
* @return The left coordinate of the GUI.
*/
int lambdacontrols_getX();
/**
* Gets the top coordinate of the GUI.
*
* @return The top coordinate of the GUI.
*/
int lambdacontrols_getY();
/**
* Gets the slot at position.
*
* @param pos_x The X position to check.
* @param pos_y The Y position to check.
* @return The slot at the specified position.
*/
Slot lambdacontrols_getSlotAt(double pos_x, double pos_y);
}

View File

@@ -1,28 +0,0 @@
/*
* Copyright © 2020 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of LambdaControls.
*
* Licensed under the MIT license. For more information,
* see the LICENSE file.
*/
package me.lambdaurora.lambdacontrols.client.util;
/**
* Represents a Minecraft keybinding with extra access.
*/
public interface KeyBindingAccessor
{
boolean lambdacontrols_press();
boolean lambdacontrols_unpress();
default boolean lambdacontrols_handlePressState(boolean pressed)
{
if (pressed)
return this.lambdacontrols_press();
else
return this.lambdacontrols_unpress();
}
}

View File

@@ -1,18 +0,0 @@
/*
* Copyright © 2020 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of LambdaControls.
*
* Licensed under the MIT license. For more information,
* see the LICENSE file.
*/
package me.lambdaurora.lambdacontrols.client.util;
/**
* Represents mouse's extra access.
*/
public interface MouseAccessor
{
void lambdacontrols_onCursorPos(long window, double x, double y);
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

View File

@@ -2,18 +2,18 @@
org.gradle.jvmargs=-Xmx1G
# Fabric Properties
# check these on https://fabricmc.net/use
minecraft_version=1.15.2
yarn_mappings=1.15.2+build.9:v2
loader_version=0.7.6+build.180
# check these on https://fabricmc.net/use
minecraft_version=1.16.5
yarn_mappings=1.16.5+build.5
loader_version=0.11.3
# Mod Properties
mod_version = 1.1.0
maven_group = me.lambdaurora
archives_base_name = lambdacontrols
mod_version = 1.6.0
maven_group = dev.lambdaurora.lambdacontrols
archives_base_name = lambdacontrols
# Dependencies
# currently not on the main fabric site, check on the maven: https://maven.fabricmc.net/net/fabricmc/fabric-api/fabric-api
fabric_version=0.4.29+build.290-1.15
spruceui_version=1.3.4
# currently not on the main fabric site, check on the maven: https://maven.fabricmc.net/net/fabricmc/fabric-api/fabric-api
fabric_version=0.32.0+1.16
spruceui_version=2.1.4-1.16
modmenu_version=1.16.8

Binary file not shown.

View File

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

35
gradlew vendored
View File

@@ -82,6 +82,7 @@ esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
@@ -125,10 +126,11 @@ if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
@@ -154,19 +156,19 @@ if $cygwin ; then
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
i=`expr $i + 1`
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
@@ -175,14 +177,9 @@ save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=$(save "$@")
APP_ARGS=`save "$@"`
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
fi
exec "$JAVACMD" "$@"

189
gradlew.bat vendored
View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

View File

@@ -1,6 +1,5 @@
pluginManagement {
repositories {
jcenter()
maven {
name = 'Fabric'
url = 'https://maven.fabricmc.net/'
@@ -10,4 +9,3 @@ pluginManagement {
}
rootProject.name = 'lambdacontrols'
include 'core', 'fabric', 'spigot'

View File

@@ -1,50 +0,0 @@
plugins {
id 'java-library'
}
archivesBaseName = project.archives_base_name + "-spigot"
repositories {
maven { url = 'https://hub.spigotmc.org/nexus/content/groups/public/' }
maven { url = 'https://libraries.minecraft.net/' }
}
configurations {
include
}
dependencies {
api project(":core")
include(project(":core")) {
exclude group: 'com.google.code.gson'
exclude group: 'com.google.guava'
}
api 'org.spigotmc:spigot-api:1.15.1-R0.1-SNAPSHOT'
api 'io.netty:netty-all:4.1.28.Final'
}
processResources {
inputs.property "version", project.version
from(sourceSets.main.resources.srcDirs) {
include "plugin.yml"
expand 'version': project.version.toString().replace("#", "")
}
}
jar {
from '../LICENSE'
dependsOn configurations.include
from {
(configurations.include).collect {
it.isDirectory() ? it : zipTree(it)
}
}
}
java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}

View File

@@ -1,41 +0,0 @@
/*
* Copyright © 2020 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of LambdaControls.
*
* Licensed under the MIT license. For more information,
* see the LICENSE file.
*/
package me.lambdaurora.lambdacontrols;
import com.electronwill.nightconfig.core.file.FileConfig;
import org.jetbrains.annotations.NotNull;
import java.io.File;
/**
* Represents the LambdaControls Spigot configuration.
*/
public class LambdaControlsConfig
{
private static final boolean DEFAULT_FRONT_BLOCK_PLACING = true;
protected final FileConfig config = FileConfig.builder("config/lambdacontrols.toml").concurrent().defaultResource("/server_config.toml").build();
private final LambdaControlsSpigot plugin;
public LambdaControlsConfig(@NotNull LambdaControlsSpigot plugin)
{
this.plugin = plugin;
}
public void load()
{
File configDir = new File("config/");
if (!configDir.exists())
configDir.mkdirs();
this.config.load();
this.plugin.log("Configuration loaded.");
LambdaControlsFeature.FRONT_BLOCK_PLACING.setAllowed(this.config.getOrElse("gameplay.front_block_placing", DEFAULT_FRONT_BLOCK_PLACING));
}
}

View File

@@ -1,148 +0,0 @@
/*
* Copyright © 2020 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of LambdaControls.
*
* Licensed under the MIT license. For more information,
* see the LICENSE file.
*/
package me.lambdaurora.lambdacontrols;
import io.netty.buffer.Unpooled;
import me.lambdaurora.lambdacontrols.event.PlayerChangeControlsModeEvent;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.plugin.messaging.PluginMessageListener;
import org.jetbrains.annotations.NotNull;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import static me.lambdaurora.lambdacontrols.LambdaControlsConstants.*;
/**
* Represents the LambdaControls spigot plugin which provides extra features for servers.
*
* @author LambdAurora
* @version 1.1.0
* @since 1.1.0
*/
public class LambdaControlsSpigot extends JavaPlugin implements PluginMessageListener, Listener
{
private static final Map<Player, ControlsMode> PLAYERS_CONTROLS_MODE = new HashMap<>();
public final LambdaControlsConfig config = new LambdaControlsConfig(this);
@Override
public void onEnable()
{
super.onEnable();
this.config.load();
// Note that Spigot has a bullshit channel size restriction as Minecraft SUPPORTS UP TO 32767 AS CHANNEL SIZE.
// Please stop using that bad server software, move over Sponge or idk other things. REALLY.
this.getServer().getMessenger().registerIncomingPluginChannel(this, CONTROLS_MODE_CHANNEL.toString(), this);
this.getServer().getMessenger().registerOutgoingPluginChannel(this, CONTROLS_MODE_CHANNEL.toString());
this.getServer().getMessenger().registerOutgoingPluginChannel(this, FEATURE_CHANNEL.toString());
this.getServer().getMessenger().registerIncomingPluginChannel(this, HELLO_CHANNEL.toString(), this);
this.getServer().getPluginManager().registerEvents(this, this);
this.getServer().getOnlinePlayers().forEach(player -> {
PLAYERS_CONTROLS_MODE.put(player, ControlsMode.DEFAULT);
this.requestPlayerControlsMode(player);
this.updatePlayerFeature(player, LambdaControlsFeature.FRONT_BLOCK_PLACING);
});
}
@Override
public void onDisable()
{
super.onDisable();
this.getServer().getMessenger().unregisterIncomingPluginChannel(this, CONTROLS_MODE_CHANNEL.toString());
this.getServer().getMessenger().unregisterOutgoingPluginChannel(this, CONTROLS_MODE_CHANNEL.toString());
this.getServer().getMessenger().unregisterOutgoingPluginChannel(this, FEATURE_CHANNEL.toString());
this.getServer().getMessenger().unregisterIncomingPluginChannel(this, HELLO_CHANNEL.toString());
PLAYERS_CONTROLS_MODE.clear();
}
public void requestPlayerControlsMode(@NotNull Player player)
{
player.sendPluginMessage(this, CONTROLS_MODE_CHANNEL.toString(), new byte[0]);
}
public void updatePlayerFeature(@NotNull Player player, @NotNull LambdaControlsFeature feature)
{
Objects.requireNonNull(player);
Objects.requireNonNull(feature);
player.sendPluginMessage(this, FEATURE_CHANNEL.toString(), this.makeFeatureMessage(feature));
}
/**
* Prints a message to the terminal.
*
* @param info The message to print.
*/
public void log(String info)
{
this.getLogger().info(info);
}
@Override
public void onPluginMessageReceived(@NotNull String channel, @NotNull Player player, @NotNull byte[] message)
{
if (channel.equals(HELLO_CHANNEL.toString())) {
NettyPacketBuffer buffer = new NettyPacketBuffer(Unpooled.copiedBuffer(message));
String version = buffer.readString(16);
ControlsMode.byId(buffer.readString(32)).ifPresent(controlsMode -> {
PLAYERS_CONTROLS_MODE.put(player, controlsMode);
PlayerChangeControlsModeEvent event = new PlayerChangeControlsModeEvent(player, controlsMode);
this.getServer().getPluginManager().callEvent(event);
});
this.updatePlayerFeature(player, LambdaControlsFeature.FRONT_BLOCK_PLACING);
} else if (channel.equals(CONTROLS_MODE_CHANNEL.toString())) {
NettyPacketBuffer buffer = new NettyPacketBuffer(Unpooled.copiedBuffer(message));
ControlsMode.byId(buffer.readString(32)).ifPresent(controlsMode -> {
PLAYERS_CONTROLS_MODE.put(player, controlsMode);
PlayerChangeControlsModeEvent event = new PlayerChangeControlsModeEvent(player, controlsMode);
this.getServer().getPluginManager().callEvent(event);
});
}
}
/**
* Returns a packet byte buffer made for the lambdacontrols:feature plugin message.
*
* @param feature The feature data to send.
* @return The packet byte buffer.
*/
public byte[] makeFeatureMessage(@NotNull LambdaControlsFeature feature)
{
Objects.requireNonNull(feature, "Feature cannot be null.");
NettyPacketBuffer buffer = new NettyPacketBuffer(Unpooled.buffer());
buffer.writeString(feature.getName());
buffer.writeBoolean(feature.isAllowed());
return buffer.array();
}
@EventHandler
public void onPlayerJoin(@NotNull PlayerJoinEvent event)
{
PLAYERS_CONTROLS_MODE.put(event.getPlayer(), ControlsMode.DEFAULT);
}
@EventHandler
public void onPlayerLeave(@NotNull PlayerQuitEvent event)
{
PLAYERS_CONTROLS_MODE.remove(event.getPlayer());
}
}

View File

@@ -1,102 +0,0 @@
package me.lambdaurora.lambdacontrols;
import io.netty.buffer.ByteBuf;
import io.netty.handler.codec.DecoderException;
import io.netty.handler.codec.EncoderException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
public class NettyPacketBuffer extends PacketBuffer
{
public NettyPacketBuffer(ByteBuf byteBuf)
{
super(byteBuf);
}
@Override
public int readVarint()
{
int var1 = 0;
int var2 = 0;
byte var3;
do {
var3 = this.readByte();
var1 |= (var3 & 127) << var2++ * 7;
if (var2 > 5)
throw new RuntimeException("VarInt too big");
} while ((var3 & 128) == 128);
return var1;
}
@Override
public void writeVarint(int input)
{
while ((input & -128) != 0) {
this.writeByte(input & 127 | 128);
input >>>= 7;
}
this.writeByte(input);
}
@Override
public String readString(int maxLength)
{
int var2 = this.readVarint();
if (var2 > maxLength * 4)
throw new DecoderException("The received encoded string buffer length is longer than maximum allowed (" + var2 + " > " + maxLength * 4 + ")");
else if (var2 < 0)
throw new DecoderException("The received encoded string buffer length is less than zero! Weird string!");
else {
String var3 = this.readCharSequence(var2, StandardCharsets.UTF_8).toString();
if (var3.length() > maxLength)
throw new DecoderException("The received string length is longer than maximum allowed (" + var2 + " > " + maxLength + ")");
else
return var3;
}
}
@Override
public void writeString(String string)
{
byte[] var2 = string.getBytes(Charset.forName("UTF-8"));
if (var2.length > 32767) {
throw new EncoderException("String too big (was " + string.length() + " data encoded, max " + 32767 + ")");
} else {
this.writeVarint(var2.length);
this.writeCharSequence(string, StandardCharsets.UTF_8);
}
}
@Override
public boolean equals(Object o)
{
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
NettyPacketBuffer buffer = (NettyPacketBuffer) o;
return byteBuf.equals(buffer.byteBuf);
}
@Override
public int compareTo(ByteBuf buffer)
{
return this.byteBuf.compareTo(buffer);
}
@Override
public String toString()
{
return this.byteBuf.toString();
}
}

View File

@@ -1,65 +0,0 @@
/*
* Copyright © 2020 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of LambdaControls.
*
* Licensed under the MIT license. For more information,
* see the LICENSE file.
*/
package me.lambdaurora.lambdacontrols.event;
import me.lambdaurora.lambdacontrols.ControlsMode;
import org.bukkit.entity.Player;
import org.bukkit.event.HandlerList;
import org.bukkit.event.player.PlayerEvent;
import org.jetbrains.annotations.NotNull;
/**
* Represents an event which is fired when a player change their controls mode.
*
* @author LambdAurora
* @version 1.1.0
* @since 1.1.0
*/
public class PlayerChangeControlsModeEvent extends PlayerEvent
{
private static final HandlerList HANDLERS = new HandlerList();
private final ControlsMode controlsMode;
public PlayerChangeControlsModeEvent(@NotNull Player who, @NotNull ControlsMode controlsMode)
{
super(who);
this.controlsMode = controlsMode;
}
/**
* Returns the controls mode of the player.
*
* @return The player's controls mode.
*/
public ControlsMode getControlsMode()
{
return this.controlsMode;
}
@Override
public String toString()
{
return "PlayerChangeControlsModeEvent{" +
"player=" + this.player +
", controls_mode=" + this.controlsMode +
'}';
}
@Override
public @NotNull HandlerList getHandlers()
{
return HANDLERS;
}
public static @NotNull HandlerList getHandlerList()
{
return HANDLERS;
}
}

View File

@@ -1,6 +0,0 @@
name: LambdaControls
version: ${version}
description: A quick Spigot plugin for LambdaControls which allow server admins to disable some features.
main: me.lambdaurora.lambdacontrols.LambdaControlsSpigot
api-version: 1.13

View File

@@ -1,6 +0,0 @@
# LambdaControls server configuration.
# Gameplay settings
[gameplay]
# Allows front block placing like in Bedrock Edition.
front_block_placing = true

View File

@@ -1,5 +1,5 @@
/*
* Copyright © 2020 LambdAurora <aurora42lambda@gmail.com>
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of LambdaControls.
*
@@ -7,7 +7,7 @@
* see the LICENSE file.
*/
package me.lambdaurora.lambdacontrols;
package dev.lambdaurora.lambdacontrols;
import org.aperlambda.lambdacommon.utils.Nameable;
import org.jetbrains.annotations.NotNull;
@@ -22,8 +22,7 @@ import java.util.Optional;
* @version 1.1.0
* @since 1.0.0
*/
public enum ControlsMode implements Nameable
{
public enum ControlsMode implements Nameable {
DEFAULT,
CONTROLLER,
TOUCHSCREEN;
@@ -33,8 +32,7 @@ public enum ControlsMode implements Nameable
*
* @return The next available controls mode.
*/
public ControlsMode next()
{
public ControlsMode next() {
ControlsMode[] v = values();
if (v.length == this.ordinal() + 1)
return v[0];
@@ -47,14 +45,12 @@ public enum ControlsMode implements Nameable
* @return The translated key of this controls mode.
* @since 1.1.0
*/
public String getTranslationKey()
{
public String getTranslationKey() {
return "lambdacontrols.controls_mode." + this.getName();
}
@Override
public @NotNull String getName()
{
public @NotNull String getName() {
return this.name().toLowerCase();
}
@@ -64,8 +60,7 @@ public enum ControlsMode implements Nameable
* @param id The identifier of the controls mode.
* @return The controls mode if found, else empty.
*/
public static Optional<ControlsMode> byId(@NotNull String id)
{
public static Optional<ControlsMode> byId(@NotNull String id) {
return Arrays.stream(values()).filter(mode -> mode.getName().equalsIgnoreCase(id)).findFirst();
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright © 2020 LambdAurora <aurora42lambda@gmail.com>
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of LambdaControls.
*
@@ -7,16 +7,17 @@
* see the LICENSE file.
*/
package me.lambdaurora.lambdacontrols;
package dev.lambdaurora.lambdacontrols;
import dev.lambdaurora.lambdacontrols.event.PlayerChangeControlsModeCallback;
import io.netty.buffer.Unpooled;
import me.lambdaurora.lambdacontrols.event.PlayerChangeControlsModeCallback;
import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.network.ServerSidePacketRegistry;
import net.fabricmc.loader.api.FabricLoader;
import net.fabricmc.loader.api.ModContainer;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.text.TranslatableText;
import net.minecraft.util.Identifier;
import net.minecraft.util.PacketByteBuf;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.NotNull;
@@ -28,32 +29,32 @@ import java.util.Optional;
* Represents the LambdaControls mod.
*
* @author LambdAurora
* @version 1.1.0
* @version 1.6.0
* @since 1.0.0
*/
public class LambdaControls implements ModInitializer
{
private static LambdaControls INSTANCE;
public static final Identifier CONTROLS_MODE_CHANNEL = new Identifier(LambdaControlsConstants.CONTROLS_MODE_CHANNEL.toString());
public static final Identifier FEATURE_CHANNEL = new Identifier(LambdaControlsConstants.FEATURE_CHANNEL.toString());
public static final Identifier HELLO_CHANNEL = new Identifier(LambdaControlsConstants.HELLO_CHANNEL.toString());
public class LambdaControls implements ModInitializer {
private static LambdaControls INSTANCE;
public static final Identifier CONTROLS_MODE_CHANNEL = new Identifier(LambdaControlsConstants.CONTROLS_MODE_CHANNEL.toString());
public static final Identifier FEATURE_CHANNEL = new Identifier(LambdaControlsConstants.FEATURE_CHANNEL.toString());
public static final Identifier HELLO_CHANNEL = new Identifier(LambdaControlsConstants.HELLO_CHANNEL.toString());
public static final TranslatableText NOT_BOUND_TEXT = new TranslatableText("lambdacontrols.not_bound");
public final Logger logger = LogManager.getLogger("LambdaControls");
@Override
public void onInitialize()
{
public void onInitialize() {
INSTANCE = this;
this.log("Initializing LambdaControls...");
ServerSidePacketRegistry.INSTANCE.register(HELLO_CHANNEL,
(context, attachedData) -> {
String version = attachedData.readString(16);
String version = attachedData.readString(32);
ControlsMode.byId(attachedData.readString(32))
.ifPresent(controlsMode -> context.getTaskQueue()
.execute(() -> PlayerChangeControlsModeCallback.EVENT.invoker().apply(context.getPlayer(), controlsMode)));
context.getTaskQueue().execute(() ->
ServerSidePacketRegistry.INSTANCE.sendToPlayer(context.getPlayer(), FEATURE_CHANNEL, this.makeFeatureBuffer(LambdaControlsFeature.FRONT_BLOCK_PLACING)));
ServerSidePacketRegistry.INSTANCE.sendToPlayer(context.getPlayer(), FEATURE_CHANNEL, this.makeFeatureBuffer(LambdaControlsFeature.HORIZONTAL_REACHAROUND)));
});
ServerSidePacketRegistry.INSTANCE.register(CONTROLS_MODE_CHANNEL,
(context, attachedData) -> ControlsMode.byId(attachedData.readString(32))
@@ -66,8 +67,7 @@ public class LambdaControls implements ModInitializer
*
* @param info The message to print.
*/
public void log(String info)
{
public void log(String info) {
this.logger.info("[LambdaControls] " + info);
}
@@ -76,8 +76,7 @@ public class LambdaControls implements ModInitializer
*
* @param warning The warning to print.
*/
public void warn(String warning)
{
public void warn(String warning) {
this.logger.info("[LambdaControls] " + warning);
}
@@ -87,8 +86,7 @@ public class LambdaControls implements ModInitializer
* @param controlsMode The controls mode to send.
* @return The packet byte buffer.
*/
public PacketByteBuf makeControlsModeBuffer(@NotNull ControlsMode controlsMode)
{
public PacketByteBuf makeControlsModeBuffer(@NotNull ControlsMode controlsMode) {
Objects.requireNonNull(controlsMode, "Controls mode cannot be null.");
return new PacketByteBuf(Unpooled.buffer()).writeString(controlsMode.getName(), 32);
}
@@ -99,22 +97,20 @@ public class LambdaControls implements ModInitializer
* @param feature The feature data to send.
* @return The packet byte buffer.
*/
public PacketByteBuf makeFeatureBuffer(@NotNull LambdaControlsFeature feature)
{
public PacketByteBuf makeFeatureBuffer(@NotNull LambdaControlsFeature feature) {
Objects.requireNonNull(feature, "Feature cannot be null.");
PacketByteBuf buffer = new PacketByteBuf(Unpooled.buffer()).writeString(feature.getName(), 64);
buffer.writeBoolean(feature.isAllowed());
return buffer;
}
public PacketByteBuf makeHello(@NotNull ControlsMode controlsMode)
{
public PacketByteBuf makeHello(@NotNull ControlsMode controlsMode) {
String version = "";
Optional<ModContainer> container;
if ((container = FabricLoader.getInstance().getModContainer(LambdaControlsConstants.NAMESPACE)).isPresent()) {
version = container.get().getMetadata().getVersion().getFriendlyString();
}
return new PacketByteBuf(Unpooled.buffer()).writeString(version, 16).writeString(controlsMode.getName(), 32);
return new PacketByteBuf(Unpooled.buffer()).writeString(version, 32).writeString(controlsMode.getName(), 32);
}
/**
@@ -122,8 +118,7 @@ public class LambdaControls implements ModInitializer
*
* @return The LambdaControls instance.
*/
public static LambdaControls get()
{
public static LambdaControls get() {
return INSTANCE;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright © 2020 LambdAurora <aurora42lambda@gmail.com>
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of LambdaControls.
*
@@ -7,7 +7,7 @@
* see the LICENSE file.
*/
package me.lambdaurora.lambdacontrols;
package dev.lambdaurora.lambdacontrols;
import org.aperlambda.lambdacommon.Identifier;
@@ -18,10 +18,9 @@ import org.aperlambda.lambdacommon.Identifier;
* @version 1.1.0
* @since 1.1.0
*/
public class LambdaControlsConstants
{
public static final String NAMESPACE = "lambdacontrols";
public class LambdaControlsConstants {
public static final String NAMESPACE = "lambdacontrols";
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(NAMESPACE, "hello");
public static final Identifier FEATURE_CHANNEL = new Identifier(NAMESPACE, "feature");
public static final Identifier HELLO_CHANNEL = new Identifier(NAMESPACE, "hello");
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright © 2020 LambdAurora <aurora42lambda@gmail.com>
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of LambdaControls.
*
@@ -7,7 +7,7 @@
* see the LICENSE file.
*/
package me.lambdaurora.lambdacontrols;
package dev.lambdaurora.lambdacontrols;
import org.aperlambda.lambdacommon.utils.Nameable;
import org.jetbrains.annotations.NotNull;
@@ -21,38 +21,36 @@ import java.util.Optional;
* Represents a feature.
*
* @author LambdAurora
* @version 1.1.0
* @version 1.5.0
* @since 1.1.0
*/
public class LambdaControlsFeature implements Nameable
{
private static final List<LambdaControlsFeature> FEATURES = new ArrayList<>();
public static final LambdaControlsFeature FRONT_BLOCK_PLACING = new LambdaControlsFeature("front_block_placing", true, false);
public class LambdaControlsFeature implements Nameable {
private static final List<LambdaControlsFeature> FEATURES = new ArrayList<>();
public static final LambdaControlsFeature FAST_BLOCK_PLACING = new LambdaControlsFeature("fast_block_placing", true, true);
public static final LambdaControlsFeature HORIZONTAL_REACHAROUND = new LambdaControlsFeature("horizontal_reacharound", true, false);
public static final LambdaControlsFeature VERTICAL_REACHAROUND = new LambdaControlsFeature("vertical_reacharound", true, false);
private final String key;
private final String key;
private final boolean defaultAllowed;
private boolean allowed;
private boolean allowed;
private final boolean defaultEnabled;
private boolean enabled;
private boolean enabled;
public LambdaControlsFeature(@NotNull String key, boolean allowed, boolean enabled)
{
public LambdaControlsFeature(@NotNull String key, boolean allowed, boolean enabled) {
Objects.requireNonNull(key, "Feature key cannot be null.");
this.key = key;
this.setAllowed(this.defaultAllowed = allowed);
this.setEnabled(this.defaultEnabled = enabled);
}
public LambdaControlsFeature(@NotNull String key)
{
public LambdaControlsFeature(@NotNull String key) {
this(key, false, false);
}
/**
* Allows the feature.
*/
public void allow()
{
public void allow() {
this.setAllowed(true);
}
@@ -61,8 +59,7 @@ public class LambdaControlsFeature implements Nameable
*
* @return True if this feature is allowed, else false.
*/
public boolean isAllowed()
{
public boolean isAllowed() {
return this.allowed;
}
@@ -71,16 +68,14 @@ public class LambdaControlsFeature implements Nameable
*
* @param allowed True if this feature is allowed, else false.
*/
public void setAllowed(boolean allowed)
{
public void setAllowed(boolean allowed) {
this.allowed = allowed;
}
/**
* Resets allowed state to default.
*/
public void resetAllowed()
{
public void resetAllowed() {
this.setAllowed(this.defaultAllowed);
}
@@ -89,8 +84,7 @@ public class LambdaControlsFeature implements Nameable
*
* @return True if this feature is enabled, else false.
*/
public boolean isEnabled()
{
public boolean isEnabled() {
return this.enabled;
}
@@ -99,8 +93,7 @@ public class LambdaControlsFeature implements Nameable
*
* @param enabled True if this feature is enabled, else false.
*/
public void setEnabled(boolean enabled)
{
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
@@ -111,28 +104,24 @@ public class LambdaControlsFeature implements Nameable
* @see #isAllowed()
* @see #isEnabled()
*/
public boolean isAvailable()
{
public boolean isAvailable() {
return this.isAllowed() && this.isEnabled();
}
/**
* Resets the feature to its default values.
*/
public void reset()
{
public void reset() {
this.resetAllowed();
this.setEnabled(this.defaultEnabled);
}
@Override
public @NotNull String getName()
{
public @NotNull String getName() {
return this.key;
}
public static @NotNull Optional<LambdaControlsFeature> fromName(@NotNull String key)
{
public static @NotNull Optional<LambdaControlsFeature> fromName(@NotNull String key) {
Objects.requireNonNull(key, "Cannot find features with a null name.");
return FEATURES.parallelStream().filter(feature -> feature.getName().equals(key)).findFirst();
}
@@ -140,20 +129,20 @@ public class LambdaControlsFeature implements Nameable
/**
* Resets all features to their default values.
*/
public static void resetAll()
{
public static void resetAll() {
FEATURES.parallelStream().forEach(LambdaControlsFeature::reset);
}
/**
* Resets all features to allow state.
*/
public static void resetAllAllowed()
{
public static void resetAllAllowed() {
FEATURES.parallelStream().forEach(LambdaControlsFeature::resetAllowed);
}
static {
FEATURES.add(FRONT_BLOCK_PLACING);
FEATURES.add(FAST_BLOCK_PLACING);
FEATURES.add(HORIZONTAL_REACHAROUND);
FEATURES.add(VERTICAL_REACHAROUND);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright © 2020 LambdAurora <aurora42lambda@gmail.com>
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of LambdaControls.
*
@@ -7,7 +7,7 @@
* see the LICENSE file.
*/
package me.lambdaurora.lambdacontrols.client;
package dev.lambdaurora.lambdacontrols.client;
/**
* Represents a button state.
@@ -16,8 +16,7 @@ package me.lambdaurora.lambdacontrols.client;
* @version 1.1.0
* @since 1.1.0
*/
public enum ButtonState
{
public enum ButtonState {
NONE(0),
PRESS(1),
RELEASE(2),
@@ -25,8 +24,7 @@ public enum ButtonState
public final int id;
ButtonState(int id)
{
ButtonState(int id) {
this.id = id;
}
@@ -35,8 +33,7 @@ public enum ButtonState
*
* @return True if this state is a pressed state, else false.
*/
public boolean isPressed()
{
public boolean isPressed() {
return this == PRESS || this == REPEAT;
}
@@ -45,8 +42,7 @@ public enum ButtonState
*
* @return True if this state is an unpressed state, else false.
*/
public boolean isUnpressed()
{
public boolean isUnpressed() {
return this == RELEASE || this == NONE;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright © 2020 LambdAurora <aurora42lambda@gmail.com>
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of LambdaControls.
*
@@ -7,9 +7,11 @@
* see the LICENSE file.
*/
package me.lambdaurora.lambdacontrols.client;
package dev.lambdaurora.lambdacontrols.client;
import net.minecraft.client.resource.language.I18n;
import net.minecraft.text.LiteralText;
import net.minecraft.text.Text;
import net.minecraft.text.TranslatableText;
import org.aperlambda.lambdacommon.utils.Nameable;
import org.jetbrains.annotations.NotNull;
@@ -20,23 +22,29 @@ import java.util.Optional;
* Represents a controller type.
*
* @author LambdAurora
* @version 1.0.0
* @version 1.4.3
* @since 1.0.0
*/
public enum ControllerType implements Nameable
{
public enum ControllerType implements Nameable {
DEFAULT(0),
DUALSHOCK(1),
SWITCH(2),
XBOX(3),
STEAM(4),
OUYA(5);
XBOX_360(3, new LiteralText("Xbox 360")),
XBOX(4),
STEAM(5),
OUYA(6);
private final int id;
private final Text text;
ControllerType(int id)
{
ControllerType(int id) {
this.id = id;
this.text = new TranslatableText("lambdacontrols.controller_type." + this.getName());
}
ControllerType(int id, @NotNull Text text) {
this.id = id;
this.text = text;
}
/**
@@ -44,8 +52,7 @@ public enum ControllerType implements Nameable
*
* @return The controller type's identifier.
*/
public int getId()
{
public int getId() {
return this.id;
}
@@ -54,8 +61,7 @@ public enum ControllerType implements Nameable
*
* @return The next available controller type.
*/
public ControllerType next()
{
public @NotNull ControllerType next() {
ControllerType[] v = values();
if (v.length == this.ordinal() + 1)
return v[0];
@@ -63,18 +69,16 @@ public enum ControllerType implements Nameable
}
/**
* Gets the translated name of this controller type.
* Gets the translated text of this controller type.
*
* @return The translated name of this controller type.
* @return The translated text of this controller type.
*/
public String getTranslatedName()
{
return I18n.translate("lambdacontrols.controller_type." + this.getName());
public @NotNull Text getTranslatedText() {
return this.text;
}
@Override
public @NotNull String getName()
{
public @NotNull String getName() {
return this.name().toLowerCase();
}
@@ -84,8 +88,7 @@ public enum ControllerType implements Nameable
* @param id The identifier of the controller type.
* @return The controller type if found, else empty.
*/
public static Optional<ControllerType> byId(@NotNull String id)
{
public static @NotNull Optional<ControllerType> byId(@NotNull String id) {
return Arrays.stream(values()).filter(mode -> mode.getName().equalsIgnoreCase(id)).findFirst();
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright © 2020 LambdAurora <aurora42lambda@gmail.com>
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of LambdaControls.
*
@@ -7,9 +7,10 @@
* see the LICENSE file.
*/
package me.lambdaurora.lambdacontrols.client;
package dev.lambdaurora.lambdacontrols.client;
import net.minecraft.client.resource.language.I18n;
import net.minecraft.text.Text;
import net.minecraft.text.TranslatableText;
import org.aperlambda.lambdacommon.utils.Nameable;
import org.jetbrains.annotations.NotNull;
@@ -20,21 +21,25 @@ import java.util.Optional;
* Represents the hud side which is the side where the movements buttons are.
*
* @author LambdAurora
* @version 1.0.0
* @version 1.4.0
* @since 1.0.0
*/
public enum HudSide implements Nameable
{
public enum HudSide implements Nameable {
LEFT,
RIGHT;
private final Text text;
HudSide() {
this.text = new TranslatableText(this.getTranslationKey());
}
/**
* Returns the next side available.
*
* @return The next available side.
*/
public HudSide next()
{
public @NotNull HudSide next() {
HudSide[] v = values();
if (v.length == this.ordinal() + 1)
return v[0];
@@ -42,18 +47,25 @@ public enum HudSide implements Nameable
}
/**
* Gets the translated name of this hud side.
* Returns the translation key of this hud side.
*
* @return The translated name of this hud side.
* @return The translation key of this hude side.
*/
public String getTranslatedName()
{
return I18n.translate("lambdacontrols.hud_side." + this.getName());
public @NotNull String getTranslationKey() {
return "lambdacontrols.hud_side." + this.getName();
}
/**
* Gets the translated text of this hud side.
*
* @return The translated text of this hud side.
*/
public @NotNull Text getTranslatedText() {
return this.text;
}
@Override
public @NotNull String getName()
{
public @NotNull String getName() {
return this.name().toLowerCase();
}
@@ -63,8 +75,7 @@ public enum HudSide implements Nameable
* @param id The identifier of the hud side.
* @return The hud side if found, else empty.
*/
public static Optional<HudSide> byId(@NotNull String id)
{
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,202 @@
/*
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of LambdaControls.
*
* Licensed under the MIT license. For more information,
* see the LICENSE file.
*/
package dev.lambdaurora.lambdacontrols.client;
import dev.lambdaurora.lambdacontrols.ControlsMode;
import dev.lambdaurora.lambdacontrols.LambdaControls;
import dev.lambdaurora.lambdacontrols.LambdaControlsConstants;
import dev.lambdaurora.lambdacontrols.LambdaControlsFeature;
import dev.lambdaurora.lambdacontrols.client.compat.LambdaControlsCompat;
import dev.lambdaurora.lambdacontrols.client.controller.ButtonBinding;
import dev.lambdaurora.lambdacontrols.client.controller.Controller;
import dev.lambdaurora.lambdacontrols.client.controller.InputManager;
import dev.lambdaurora.lambdacontrols.client.gui.LambdaControlsHud;
import dev.lambdaurora.lambdacontrols.client.gui.TouchscreenOverlay;
import dev.lambdaurora.lambdacontrols.client.ring.KeyBindingRingAction;
import dev.lambdaurora.lambdacontrols.client.ring.LambdaRing;
import me.lambdaurora.spruceui.event.OpenScreenCallback;
import me.lambdaurora.spruceui.hud.HudManager;
import net.fabricmc.api.ClientModInitializer;
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper;
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents;
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.network.ClientPlayNetworkHandler;
import net.minecraft.client.options.KeyBinding;
import net.minecraft.client.toast.SystemToast;
import net.minecraft.client.util.InputUtil;
import net.minecraft.text.LiteralText;
import net.minecraft.text.TranslatableText;
import net.minecraft.util.Identifier;
import org.jetbrains.annotations.NotNull;
import org.lwjgl.glfw.GLFW;
import java.io.File;
/**
* Represents the LambdaControls client mod.
*
* @author LambdAurora
* @version 1.6.0
* @since 1.1.0
*/
public class LambdaControlsClient extends LambdaControls implements ClientModInitializer {
private static LambdaControlsClient INSTANCE;
public static final KeyBinding BINDING_LOOK_UP = InputManager.makeKeyBinding(new Identifier(LambdaControlsConstants.NAMESPACE, "look_up"),
InputUtil.Type.KEYSYM, GLFW.GLFW_KEY_KP_8, "key.categories.movement");
public static final KeyBinding BINDING_LOOK_RIGHT = InputManager.makeKeyBinding(new Identifier(LambdaControlsConstants.NAMESPACE, "look_right"),
InputUtil.Type.KEYSYM, GLFW.GLFW_KEY_KP_6, "key.categories.movement");
public static final KeyBinding BINDING_LOOK_DOWN = InputManager.makeKeyBinding(new Identifier(LambdaControlsConstants.NAMESPACE, "look_down"),
InputUtil.Type.KEYSYM, GLFW.GLFW_KEY_KP_2, "key.categories.movement");
public static final KeyBinding BINDING_LOOK_LEFT = InputManager.makeKeyBinding(new Identifier(LambdaControlsConstants.NAMESPACE, "look_left"),
InputUtil.Type.KEYSYM, GLFW.GLFW_KEY_KP_4, "key.categories.movement");
/*public static final KeyBinding BINDING_RING = InputManager.makeKeyBinding(new Identifier(LambdaControlsConstants.NAMESPACE, "ring"),
InputUtil.Type.MOUSE, GLFW.GLFW_MOUSE_BUTTON_5, "key.categories.misc");*/
public static final Identifier CONTROLLER_BUTTONS = new Identifier(LambdaControlsConstants.NAMESPACE, "textures/gui/controller_buttons.png");
public static final Identifier CONTROLLER_AXIS = new Identifier(LambdaControlsConstants.NAMESPACE, "textures/gui/controller_axis.png");
public static final Identifier CURSOR_TEXTURE = new Identifier(LambdaControlsConstants.NAMESPACE, "textures/gui/cursor.png");
public final static File MAPPINGS_FILE = new File("config/gamecontrollerdb.txt");
public final LambdaControlsConfig config = new LambdaControlsConfig(this);
public final LambdaInput input = new LambdaInput(this);
public final LambdaRing ring = new LambdaRing(this);
public final LambdaReacharound reacharound = new LambdaReacharound();
private LambdaControlsHud hud;
private ControlsMode previousControlsMode;
@Override
public void onInitializeClient() {
INSTANCE = this;
KeyBindingHelper.registerKeyBinding(BINDING_LOOK_UP);
KeyBindingHelper.registerKeyBinding(BINDING_LOOK_RIGHT);
KeyBindingHelper.registerKeyBinding(BINDING_LOOK_DOWN);
KeyBindingHelper.registerKeyBinding(BINDING_LOOK_LEFT);
//KeyBindingHelper.registerKeyBinding(BINDING_RING);
this.ring.registerAction("keybinding", KeyBindingRingAction.FACTORY);
ClientPlayNetworking.registerGlobalReceiver(CONTROLS_MODE_CHANNEL, (client, handler, buf, responseSender) -> {
responseSender.sendPacket(CONTROLS_MODE_CHANNEL, this.makeControlsModeBuffer(this.config.getControlsMode()));
});
ClientPlayNetworking.registerGlobalReceiver(FEATURE_CHANNEL, (client, handler, buf, responseSender) -> {
String name = buf.readString(64);
boolean allowed = buf.readBoolean();
LambdaControlsFeature.fromName(name).ifPresent(feature -> client.execute(() -> feature.setAllowed(allowed)));
});
ClientPlayConnectionEvents.JOIN.register((handler, sender, client) -> {
sender.sendPacket(HELLO_CHANNEL, this.makeHello(this.config.getControlsMode()));
sender.sendPacket(CONTROLS_MODE_CHANNEL, this.makeControlsModeBuffer(this.config.getControlsMode()));
});
ClientPlayConnectionEvents.DISCONNECT.register(this::onLeave);
ClientTickEvents.START_CLIENT_TICK.register(this.reacharound::tick);
ClientTickEvents.END_CLIENT_TICK.register(this::onTick);
OpenScreenCallback.EVENT.register((client, screen) -> {
if (screen == null && this.config.getControlsMode() == ControlsMode.TOUCHSCREEN) {
screen = new TouchscreenOverlay(this);
screen.init(client, client.getWindow().getScaledWidth(), client.getWindow().getScaledHeight());
client.skipGameRender = false;
client.currentScreen = screen;
} else if (screen != null) {
this.input.onScreenOpen(client, client.getWindow().getWidth(), client.getWindow().getHeight());
}
});
HudManager.register(this.hud = new LambdaControlsHud(this));
}
/**
* This method is called when Minecraft is initializing.
*/
public void onMcInit(@NotNull MinecraftClient client) {
ButtonBinding.init(client.options);
this.config.load();
this.hud.setVisible(this.config.isHudEnabled());
Controller.updateMappings();
GLFW.glfwSetJoystickCallback((jid, event) -> {
if (event == GLFW.GLFW_CONNECTED) {
Controller controller = Controller.byId(jid);
client.getToastManager().add(new SystemToast(SystemToast.Type.TUTORIAL_HINT, new TranslatableText("lambdacontrols.controller.connected", jid),
new LiteralText(controller.getName())));
} else if (event == GLFW.GLFW_DISCONNECTED) {
client.getToastManager().add(new SystemToast(SystemToast.Type.TUTORIAL_HINT, new TranslatableText("lambdacontrols.controller.disconnected", jid),
null));
}
this.switchControlsMode();
});
LambdaControlsCompat.init(this);
}
/**
* This method is called every Minecraft tick.
*
* @param client the client instance
*/
public void onTick(@NotNull MinecraftClient client) {
this.input.tick(client);
if (this.config.getControlsMode() == ControlsMode.CONTROLLER && (client.isWindowFocused() || this.config.hasUnfocusedInput()))
this.input.tickController(client);
/*if (BINDING_RING.wasPressed()) {
client.openScreen(new RingScreen());
}*/
}
public void onRender(MinecraftClient client) {
this.input.onRender(client.getTickDelta(), client);
}
/**
* Called when leaving a server.
*/
public void onLeave(ClientPlayNetworkHandler handler, MinecraftClient client) {
LambdaControlsFeature.resetAllAllowed();
}
/**
* Switches the controls mode if the auto switch is enabled.
*/
public void switchControlsMode() {
if (this.config.hasAutoSwitchMode()) {
if (this.config.getController().isGamepad()) {
this.previousControlsMode = this.config.getControlsMode();
this.config.setControlsMode(ControlsMode.CONTROLLER);
} else {
if (this.previousControlsMode == null) {
this.previousControlsMode = ControlsMode.DEFAULT;
}
this.config.setControlsMode(this.previousControlsMode);
}
}
}
/**
* Sets whether the HUD is enabled or not.
*
* @param enabled True if the HUD is enabled, else false.
*/
public void setHudEnabled(boolean enabled) {
this.config.setHudEnabled(enabled);
this.hud.setVisible(enabled);
}
/**
* Gets the LambdaControls client instance.
*
* @return The LambdaControls client instance.
*/
public static LambdaControlsClient get() {
return INSTANCE;
}
}

View File

@@ -0,0 +1,839 @@
/*
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of LambdaControls.
*
* Licensed under the MIT license. For more information,
* see the LICENSE file.
*/
package dev.lambdaurora.lambdacontrols.client;
import com.electronwill.nightconfig.core.file.FileConfig;
import dev.lambdaurora.lambdacontrols.ControlsMode;
import dev.lambdaurora.lambdacontrols.LambdaControlsFeature;
import dev.lambdaurora.lambdacontrols.client.controller.ButtonBinding;
import dev.lambdaurora.lambdacontrols.client.controller.Controller;
import dev.lambdaurora.lambdacontrols.client.controller.InputManager;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.lwjgl.glfw.GLFW;
import java.util.Arrays;
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import static org.lwjgl.glfw.GLFW.GLFW_GAMEPAD_AXIS_LEFT_X;
import static org.lwjgl.glfw.GLFW.GLFW_GAMEPAD_AXIS_LEFT_Y;
/**
* Represents LambdaControls configuration.
*/
public class LambdaControlsConfig {
// General
private static final ControlsMode DEFAULT_CONTROLS_MODE = ControlsMode.DEFAULT;
private static final boolean DEFAULT_AUTO_SWITCH_MODE = false;
private static final boolean DEFAULT_DEBUG = false;
// HUD
private static final boolean DEFAULT_HUD_ENABLE = true;
private static final HudSide DEFAULT_HUD_SIDE = HudSide.LEFT;
// Gameplay
private static final boolean DEFAULT_ANALOG_MOVEMENT = true;
private static final boolean DEFAULT_FAST_BLOCK_INTERACTION = true;
private static final boolean DEFAULT_FLY_DRIFTING = false;
private static final boolean DEFAULT_FLY_VERTICAL_DRIFTING = true;
private static final boolean DEFAULT_HORIZONTAL_REACHAROUND = false;
private static final boolean DEFAULT_VERTICAL_REACHAROUND = false;
private static final boolean DEFAULT_REACHAROUND_OUTLINE = true;
private static final int[] DEFAULT_REACHAROUND_OUTLINE_COLOR = new int[]{255, 255, 255, 102};
// Controller
private static final ControllerType DEFAULT_CONTROLLER_TYPE = ControllerType.DEFAULT;
private static final double DEFAULT_DEAD_ZONE = 0.25;
private static final double DEFAULT_MAX_VALUE = 1;
private static final double DEFAULT_ROTATION_SPEED = 40.0;
private static final double DEFAULT_MOUSE_SPEED = 25.0;
private static final boolean DEFAULT_UNFOCUSED_INPUT = false;
private static final boolean DEFAULT_VIRTUAL_MOUSE = false;
private static final VirtualMouseSkin DEFAULT_VIRTUAL_MOUSE_SKIN = VirtualMouseSkin.DEFAULT_LIGHT;
private static final Pattern BUTTON_BINDING_PATTERN = Pattern.compile("(-?\\d+)\\+?");
protected final FileConfig config = FileConfig.builder("config/lambdacontrols.toml").concurrent().defaultResource("/config.toml").build();
private final LambdaControlsClient mod;
private ControlsMode controlsMode;
private ControllerType controllerType;
// Gameplay.
private boolean analogMovement;
private boolean shouldRenderReacharoundOutline;
private int[] reacharoundOutlineColor;
// Controller settings
private double rightDeadZone;
private double leftDeadZone;
private double[] maxAnalogValues = new double[]{DEFAULT_MAX_VALUE, DEFAULT_MAX_VALUE, DEFAULT_MAX_VALUE, DEFAULT_MAX_VALUE};
private double rotationSpeed;
private double mouseSpeed;
private boolean unfocusedInput;
private boolean virtualMouse;
private VirtualMouseSkin virtualMouseSkin;
// HUD settings.
private boolean hudEnable;
private HudSide hudSide;
public LambdaControlsConfig(@NotNull LambdaControlsClient mod) {
this.mod = mod;
}
/**
* Loads the configuration
*/
public void load() {
this.config.load();
this.checkAndFix();
this.mod.log("Configuration loaded.");
this.controlsMode = ControlsMode.byId(this.config.getOrElse("controls", DEFAULT_CONTROLS_MODE.getName())).orElse(DEFAULT_CONTROLS_MODE);
// HUD settings.
this.hudEnable = this.config.getOrElse("hud.enable", DEFAULT_HUD_ENABLE);
this.hudSide = HudSide.byId(this.config.getOrElse("hud.side", DEFAULT_HUD_SIDE.getName())).orElse(DEFAULT_HUD_SIDE);
// Gameplay
this.analogMovement = this.config.getOrElse("gameplay.analog_movement", DEFAULT_ANALOG_MOVEMENT);
LambdaControlsFeature.FAST_BLOCK_PLACING.setEnabled(this.config.getOrElse("gameplay.fast_block_placing", DEFAULT_FAST_BLOCK_INTERACTION));
LambdaControlsFeature.HORIZONTAL_REACHAROUND.setEnabled(this.config.getOrElse("gameplay.reacharound.horizontal", DEFAULT_HORIZONTAL_REACHAROUND));
LambdaControlsFeature.VERTICAL_REACHAROUND.setEnabled(this.config.getOrElse("gameplay.reacharound.vertical", DEFAULT_VERTICAL_REACHAROUND));
this.shouldRenderReacharoundOutline = this.config.getOrElse("gameplay.reacharound.outline", DEFAULT_REACHAROUND_OUTLINE);
this.reacharoundOutlineColor = this.config.getOptional("gameplay.reacharound.outline_color")
.map(hex -> parseColor((String) hex))
.orElse(DEFAULT_REACHAROUND_OUTLINE_COLOR);
// Controller settings.
this.controllerType = ControllerType.byId(this.config.getOrElse("controller.type", DEFAULT_CONTROLLER_TYPE.getName())).orElse(DEFAULT_CONTROLLER_TYPE);
this.rightDeadZone = this.config.getOrElse("controller.right_dead_zone", DEFAULT_DEAD_ZONE);
this.leftDeadZone = this.config.getOrElse("controller.left_dead_zone", DEFAULT_DEAD_ZONE);
this.rotationSpeed = this.config.getOrElse("controller.rotation_speed", DEFAULT_ROTATION_SPEED);
this.mouseSpeed = this.config.getOrElse("controller.mouse_speed", DEFAULT_MOUSE_SPEED);
this.unfocusedInput = this.config.getOrElse("controller.unfocused_input", DEFAULT_UNFOCUSED_INPUT);
this.virtualMouse = this.config.getOrElse("controller.virtual_mouse", DEFAULT_VIRTUAL_MOUSE);
this.virtualMouseSkin = VirtualMouseSkin.byId(this.config.getOrElse("controller.virtual_mouse_skin", DEFAULT_VIRTUAL_MOUSE_SKIN.getName())).orElse(DEFAULT_VIRTUAL_MOUSE_SKIN);
for (int i = 0; i < this.maxAnalogValues.length; i++) {
this.maxAnalogValues[i] = this.config.getOrElse("controller.max_value_" + i, DEFAULT_MAX_VALUE);
}
// Controller controls.
InputManager.loadButtonBindings(this);
this.mod.ring.load(this.config);
}
/**
* Saves the configuration.
*/
public void save() {
this.config.set("controller.right_dead_zone", this.rightDeadZone);
this.config.set("controller.left_dead_zone", this.leftDeadZone);
this.config.set("controller.rotation_speed", this.rotationSpeed);
this.config.set("controller.mouse_speed", this.mouseSpeed);
this.config.set("controller.unfocused_input", this.unfocusedInput);
this.config.set("controller.virtual_mouse", this.virtualMouse);
for (int i = 0; i < this.maxAnalogValues.length; i++) {
this.config.set("controller.max_value_" + i, this.maxAnalogValues[i]);
}
this.config.save();
this.mod.log("Configuration saved.");
}
public void checkAndFix() {
InputManager.streamBindings().forEach(binding -> {
String path = "controller.controls." + binding.getName();
Object raw = this.config.getRaw(path);
if (raw instanceof Number) {
this.mod.warn("Invalid data at \"" + path + "\", fixing...");
this.config.set(path, String.valueOf(raw));
}
});
if (this.config.contains("gameplay.front_block_placing.enabled")) {
this.setFrontBlockPlacing(this.config.getOrElse("gameplay.front_block_placing.enabled", DEFAULT_HORIZONTAL_REACHAROUND));
this.config.remove("gameplay.front_block_placing.enabled");
}
if (this.config.contains("gameplay.front_block_placing.outline")) {
this.setRenderReacharoundOutline(this.config.getOrElse("gameplay.front_block_placing.outline", DEFAULT_REACHAROUND_OUTLINE));
this.config.remove("gameplay.front_block_placing.outline");
}
if (this.config.contains("gameplay.front_block_placing.outline_color")) {
this.config.getOptional("gameplay.front_block_placing.outline_color").ifPresent(color -> this.config.set("gameplay.reacharound.outline_color", color));
this.config.remove("gameplay.front_block_placing.outline_color");
}
this.renamed("controller.dead_zone", "controller.right_dead_zone");
this.renamed("controller.controls.tab_left", "controller.controls.tab_back");
this.renamed("controller.controls.tab_right", "controller.controls.tab_next");
}
private void renamed(String oldPath, String newPath) {
if (!this.config.contains(oldPath))
return;
Object raw = this.config.getRaw(oldPath);
this.config.remove(oldPath);
this.config.set(newPath, raw);
}
/**
* Resets the configuration to default values.
*/
public void reset() {
// General
this.setControlsMode(DEFAULT_CONTROLS_MODE);
this.setAutoSwitchMode(DEFAULT_AUTO_SWITCH_MODE);
this.setDebug(DEFAULT_DEBUG);
// Gameplay
this.setAnalogMovement(DEFAULT_ANALOG_MOVEMENT);
this.setFastBlockPlacing(DEFAULT_FAST_BLOCK_INTERACTION);
this.setFlyDrifting(DEFAULT_FLY_DRIFTING);
this.setFlyVerticalDrifting(DEFAULT_FLY_VERTICAL_DRIFTING);
this.setFrontBlockPlacing(DEFAULT_HORIZONTAL_REACHAROUND);
this.setVerticalReacharound(DEFAULT_VERTICAL_REACHAROUND);
this.setRenderReacharoundOutline(DEFAULT_REACHAROUND_OUTLINE);
// Controller
this.setControllerType(DEFAULT_CONTROLLER_TYPE);
this.setRightDeadZone(DEFAULT_DEAD_ZONE);
this.setLeftDeadZone(DEFAULT_DEAD_ZONE);
this.setRotationSpeed(DEFAULT_ROTATION_SPEED);
this.setMouseSpeed(DEFAULT_MOUSE_SPEED);
this.setUnfocusedInput(DEFAULT_UNFOCUSED_INPUT);
this.setVirtualMouse(DEFAULT_VIRTUAL_MOUSE);
this.setVirtualMouseSkin(DEFAULT_VIRTUAL_MOUSE_SKIN);
Arrays.fill(this.maxAnalogValues, DEFAULT_MAX_VALUE);
// HUD
this.setHudEnabled(DEFAULT_HUD_ENABLE);
this.setHudSide(DEFAULT_HUD_SIDE);
// Collect prevents concurrent modification.
InputManager.streamBindings().collect(Collectors.toList()).forEach(binding -> this.setButtonBinding(binding, binding.getDefaultButton()));
}
/**
* Gets the controls mode from the configuration.
*
* @return The controls mode.
*/
public @NotNull ControlsMode getControlsMode() {
return this.controlsMode;
}
/**
* Sets the controls mode in the configuration.
*
* @param controlsMode The controls mode.
*/
public void setControlsMode(@NotNull ControlsMode controlsMode) {
this.controlsMode = controlsMode;
this.config.set("controls", controlsMode.getName());
}
/**
* Returns whether the auto switch mode is enabled or not.
*
* @return True if the auto switch mode is enabled, else false.
*/
public boolean hasAutoSwitchMode() {
return this.config.getOrElse("auto_switch_mode", DEFAULT_AUTO_SWITCH_MODE);
}
/**
* Sets whether the auto switch mode is enabled or not.
*
* @param autoSwitchMode True if the auto switch mode is enabled, else false.
*/
public void setAutoSwitchMode(boolean autoSwitchMode) {
this.config.set("auto_switch_mode", autoSwitchMode);
}
/**
* Returns whether the mod has debug enabled or not.
*
* @return True if debug is enabled, else false.
*/
public boolean hasDebug() {
return this.config.getOrElse("debug", DEFAULT_DEBUG);
}
/**
* Sets whether the mod has debug enabled or not.
*
* @param debug True if debug is enabled, else false.
*/
protected void setDebug(boolean debug) {
this.config.set("debug", debug);
}
/*
HUD settings
*/
/**
* Returns whether the HUD is enabled.
*
* @return True if the HUD is enabled, else false.
*/
public boolean isHudEnabled() {
return this.hudEnable;
}
/**
* Sets whether the HUD is enabled.
*
* @param enable True if the HUD is enabled, else false.
*/
public void setHudEnabled(boolean enable) {
this.hudEnable = enable;
this.config.set("hud.enable", this.hudEnable);
}
/**
* Gets the HUD side from the configuration.
*
* @return The HUD side.
*/
public @NotNull HudSide getHudSide() {
return this.hudSide;
}
/**
* Sets the HUD side in the configuration.
*
* @param hudSide The HUD side.
*/
public void setHudSide(@NotNull HudSide hudSide) {
this.hudSide = hudSide;
this.config.set("hud.side", hudSide.getName());
}
/*
Gameplay settings
*/
/**
* Gets whether analog movement is enabled.
*
* @return {@code true} if analog movement is enabled, else {@code false}
*/
public boolean hasAnalogMovement() {
return this.analogMovement;
}
/**
* Sets whether analog movement is enabled.
*
* @param analogMovement {@code true} if analog movement is enabled, else {@code false}
*/
public void setAnalogMovement(boolean analogMovement) {
this.config.set("gameplay.analog_movement", this.analogMovement = analogMovement);
}
/**
* Gets whether fast block placing is enabled or not.
*
* @return True if fast block placing is enabled, else false.
*/
public boolean hasFastBlockPlacing() {
return LambdaControlsFeature.FAST_BLOCK_PLACING.isEnabled();
}
/**
* Sets whether fast block placing is enabled or not.
*
* @param enable True if fast block placing is enabled, else false.
*/
public void setFastBlockPlacing(boolean enable) {
LambdaControlsFeature.FAST_BLOCK_PLACING.setEnabled(enable);
this.config.set("gameplay.fast_block_placing", enable);
}
/**
* Returns whether fly drifting is enabled or not.
*
* @return True if fly drifting is enabled, else false.
*/
public boolean hasFlyDrifting() {
return this.config.getOrElse("gameplay.fly.drifting", DEFAULT_FLY_DRIFTING);
}
/**
* Sets whether fly drifting is enabled or not.
*
* @param flyDrifting True if fly drifting is enabled, else false.
*/
public void setFlyDrifting(boolean flyDrifting) {
this.config.set("gameplay.fly.drifting", flyDrifting);
}
/**
* Returns whether vertical fly drifting is enabled or not.
*
* @return True if vertical fly drifting is enabled, else false.
*/
public boolean hasFlyVerticalDrifting() {
return this.config.getOrElse("gameplay.fly.vertical_drifting", DEFAULT_FLY_VERTICAL_DRIFTING);
}
/**
* Sets whether vertical fly drifting is enabled or not.
*
* @param flyDrifting True if vertical fly drifting is enabled, else false.
*/
public void setFlyVerticalDrifting(boolean flyDrifting) {
this.config.set("gameplay.fly.vertical_drifting", flyDrifting);
}
/**
* Returns whether front block placing is enabled or not.
*
* @return True if front block placing is enabled, else false.
*/
public boolean hasFrontBlockPlacing() {
return LambdaControlsFeature.HORIZONTAL_REACHAROUND.isEnabled();
}
/**
* Sets whether front block placing is enabled or not.
*
* @param enable True if front block placing is enabled, else false.
*/
public void setFrontBlockPlacing(boolean enable) {
LambdaControlsFeature.HORIZONTAL_REACHAROUND.setEnabled(enable);
this.config.set("gameplay.reacharound.horizontal", enable);
}
/**
* Returns whether vertical reacharound is enabled or not.
*
* @return True if vertical reacharound is enabled, else false.
*/
public boolean hasVerticalReacharound() {
return LambdaControlsFeature.VERTICAL_REACHAROUND.isEnabled();
}
/**
* Sets whether vertical reacharound is enabled or not.
*
* @param enable True if vertical reacharound is enabled, else false.
*/
public void setVerticalReacharound(boolean enable) {
LambdaControlsFeature.VERTICAL_REACHAROUND.setEnabled(enable);
this.config.set("gameplay.reacharound.vertical", enable);
}
/**
* Returns whether front block placing outline is enabled or not.
*
* @return True if front block placing outline is enabled, else false.
*/
public boolean shouldRenderReacharoundOutline() {
return this.shouldRenderReacharoundOutline;
}
/**
* Sets whether front block placing outline is enabled or not.
*
* @param render True if front block placing outline is enabled, else false.
*/
public void setRenderReacharoundOutline(boolean render) {
this.config.set("gameplay.reacharound.outline", this.shouldRenderReacharoundOutline = render);
}
/**
* Returns the front block placing outline color as an integer array.
* <p>
* The integer array has 4 elements: red, green, blue and alpha.
*
* @return The color as a RGBA integer array.
*/
public int[] getReacharoundOutlineColor() {
return this.reacharoundOutlineColor;
}
/*
Controller settings
*/
/**
* Gets the used controller.
*
* @return the controller
*/
public Controller getController() {
Object raw = this.config.getRaw("controller.id");
if (raw instanceof Number) {
return Controller.byId((Integer) raw);
} else if (raw instanceof String) {
return Controller.byGuid((String) raw).orElse(Controller.byId(GLFW.GLFW_JOYSTICK_1));
}
return Controller.byId(GLFW.GLFW_JOYSTICK_1);
}
/**
* Sets the used controller.
*
* @param controller the controller
*/
public void setController(Controller controller) {
this.config.set("controller.id", controller.getId());
}
/**
* Gets the second controller (for Joy-Con supports).
*
* @return the second controller
*/
public Optional<Controller> getSecondController() {
Object raw = this.config.getRaw("controller.id2");
if (raw instanceof Number) {
if ((int) raw == -1)
return Optional.empty();
return Optional.of(Controller.byId((Integer) raw));
} 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 void setSecondController(@Nullable Controller controller) {
this.config.set("controller.id2", controller == null ? -1 : controller.getId());
}
/**
* Gets the controller's type.
*
* @return The controller's type.
*/
public @NotNull ControllerType getControllerType() {
return this.controllerType;
}
/**
* Sets the controller's type.
*
* @param controllerType The controller's type.
*/
public void setControllerType(@NotNull ControllerType controllerType) {
this.controllerType = controllerType;
this.config.set("controller.type", controllerType.getName());
}
/**
* Gets the controller's right dead zone from the configuration.
*
* @return the controller's right dead zone value
*/
public double getRightDeadZone() {
return this.rightDeadZone;
}
/**
* Sets the controller's right dead zone in the configuration.
*
* @param deadZone the controller's right dead zone value
*/
public void setRightDeadZone(double deadZone) {
this.rightDeadZone = deadZone;
}
/**
* Gets the controller's left dead zone from the configuration.
*
* @return the controller's left dead zone value
*/
public double getLeftDeadZone() {
return this.leftDeadZone;
}
/**
* Sets the controller's left dead zone in the configuration.
*
* @param deadZone the controller's left dead zone value
*/
public void setLeftDeadZone(double deadZone) {
this.leftDeadZone = deadZone;
}
/**
* Gets the controller's rotation speed.
*
* @return The rotation speed.
*/
public double getRotationSpeed() {
return this.rotationSpeed;
}
/**
* Sets the controller's rotation speed.
*
* @param rotationSpeed The rotation speed.
*/
public void setRotationSpeed(double rotationSpeed) {
this.rotationSpeed = rotationSpeed;
}
/**
* Gets the controller's mouse speed.
*
* @return The mouse speed.
*/
public double getMouseSpeed() {
return this.mouseSpeed;
}
/**
* Sets the controller's mouse speed.
*
* @param mouseSpeed The mouse speed.
*/
public void setMouseSpeed(double mouseSpeed) {
this.mouseSpeed = mouseSpeed;
}
/**
* Returns whether the right X axis is inverted or not.
*
* @return True if the right X axis is inverted, else false.
*/
public boolean doesInvertRightXAxis() {
return this.config.getOrElse("controller.invert_right_x_axis", false);
}
/**
* Sets whether the right X axis is inverted or not.
*
* @param invert True if the right X axis is inverted, else false.
*/
public void setInvertRightXAxis(boolean invert) {
this.config.set("controller.invert_right_x_axis", invert);
}
/**
* Returns whether the right Y axis is inverted or not.
*
* @return True if the right Y axis is inverted, else false.
*/
public boolean doesInvertRightYAxis() {
return this.config.getOrElse("controller.invert_right_y_axis", false);
}
/**
* Sets whether the right Y axis is inverted or not.
*
* @param invert True if the right Y axis is inverted, else false.
*/
public void setInvertRightYAxis(boolean invert) {
this.config.set("controller.invert_right_y_axis", invert);
}
/**
* Returns whether unfocused controller input is allowed or not.
*
* @return True if unfocused controller input is allowed, else false.
*/
public boolean hasUnfocusedInput() {
return this.unfocusedInput;
}
/**
* Sets whether unfocused controller input is allowed or not.
*
* @param unfocusedInput True if unfocused controller input is allowed, else false.
*/
public void setUnfocusedInput(boolean unfocusedInput) {
this.unfocusedInput = unfocusedInput;
}
/**
* Returns whether the mouse is virtual or not.
*
* @return True if the mouse is virtual, else false.
*/
public boolean hasVirtualMouse() {
return this.virtualMouse;
}
/**
* Sets whether the mouse is virtual or not.
*
* @param virtualMouse True if the mouse is virtual, else false.
*/
public void setVirtualMouse(boolean virtualMouse) {
this.virtualMouse = virtualMouse;
}
/**
* Gets the virtual mouse skin.
*
* @return The virtual mouse skin.
*/
public VirtualMouseSkin getVirtualMouseSkin() {
return this.virtualMouseSkin;
}
/**
* Sets the virtual mouse skin.
*
* @param skin The virtual mouse skin.
*/
public void setVirtualMouseSkin(VirtualMouseSkin skin) {
this.virtualMouseSkin = skin;
this.config.set("controller.virtual_mouse_skin", skin.getName());
}
/**
* Gets the right X axis sign.
*
* @return The right X axis sign.
*/
public double getRightXAxisSign() {
return this.doesInvertRightXAxis() ? -1.0 : 1.0;
}
/**
* Gets the right Y axis sign.
*
* @return The right Y axis sign.
*/
public double getRightYAxisSign() {
return this.doesInvertRightYAxis() ? -1.0 : 1.0;
}
public double getAxisMaxValue(int axis) {
if (axis >= this.maxAnalogValues.length)
return DEFAULT_MAX_VALUE;
return this.maxAnalogValues[axis];
}
public void setAxisMaxValue(int axis, double value) {
if (axis < this.maxAnalogValues.length)
this.maxAnalogValues[axis] = value;
}
/**
* Loads the button binding from configuration.
*
* @param button The button binding.
*/
public void loadButtonBinding(@NotNull ButtonBinding button) {
button.setButton(button.getDefaultButton());
String code = this.config.getOrElse("controller.controls." + button.getName(), button.getButtonCode());
Matcher matcher = BUTTON_BINDING_PATTERN.matcher(code);
try {
int[] buttons = new int[1];
int count = 0;
while (matcher.find()) {
count++;
if (count > buttons.length)
buttons = Arrays.copyOf(buttons, count);
String current;
if (!this.checkValidity(button, code, current = matcher.group(1)))
return;
buttons[count - 1] = Integer.parseInt(current);
}
if (count == 0) {
this.mod.warn("Malformed config value \"" + code + "\" for binding \"" + button.getName() + "\".");
this.setButtonBinding(button, new int[]{-1});
}
button.setButton(buttons);
} catch (Exception e) {
this.mod.warn("Malformed config value \"" + code + "\" for binding \"" + button.getName() + "\".");
this.config.set("controller.controls." + button.getName(), button.getButtonCode());
}
}
private boolean checkValidity(@NotNull ButtonBinding binding, @NotNull String input, String group) {
if (group == null) {
this.mod.warn("Malformed config value \"" + input + "\" for binding \"" + binding.getName() + "\".");
this.config.set("controller.controls." + binding.getName(), binding.getButtonCode());
return false;
}
return true;
}
/**
* Sets the button binding in configuration.
*
* @param binding The button binding.
* @param button The button.
*/
public void setButtonBinding(@NotNull ButtonBinding binding, int[] button) {
binding.setButton(button);
this.config.set("controller.controls." + binding.getName(), binding.getButtonCode());
}
public boolean isBackButton(int btn, boolean isBtn, int state) {
if (!isBtn && state == 0)
return false;
return ButtonBinding.axisAsButton(GLFW_GAMEPAD_AXIS_LEFT_Y, false) == ButtonBinding.axisAsButton(btn, state == 1);
}
public boolean isForwardButton(int btn, boolean isBtn, int state) {
if (!isBtn && state == 0)
return false;
return ButtonBinding.axisAsButton(GLFW_GAMEPAD_AXIS_LEFT_Y, true) == ButtonBinding.axisAsButton(btn, state == 1);
}
public boolean isLeftButton(int btn, boolean isBtn, int state) {
if (!isBtn && state == 0)
return false;
return ButtonBinding.axisAsButton(GLFW_GAMEPAD_AXIS_LEFT_X, false) == ButtonBinding.axisAsButton(btn, state == 1);
}
public boolean isRightButton(int btn, boolean isBtn, int state) {
if (!isBtn && state == 0)
return false;
return ButtonBinding.axisAsButton(GLFW_GAMEPAD_AXIS_LEFT_X, true) == ButtonBinding.axisAsButton(btn, state == 1);
}
/**
* Returns whether the specified axis is an axis used for movements.
*
* @param axis The axis index.
* @return True if the axis is used for movements, else false.
*/
public boolean isMovementAxis(int axis) {
return axis == GLFW_GAMEPAD_AXIS_LEFT_Y || axis == GLFW_GAMEPAD_AXIS_LEFT_X;
}
/**
* Parses a color from a hexadecimal color string.
*
* @param hex The hexadecimal color.
* @return The color instance, null if invalid.
*/
private static int[] parseColor(String hex) {
hex = hex.replace("#", "");
switch (hex.length()) {
case 6:
return new int[]{
Integer.valueOf(hex.substring(0, 2), 16),
Integer.valueOf(hex.substring(2, 4), 16),
Integer.valueOf(hex.substring(4, 6), 16),
255
};
case 8:
return new int[]{
Integer.valueOf(hex.substring(0, 2), 16),
Integer.valueOf(hex.substring(2, 4), 16),
Integer.valueOf(hex.substring(4, 6), 16),
Integer.valueOf(hex.substring(6, 8), 16)
};
}
return null;
}
}

View File

@@ -0,0 +1,28 @@
/*
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of LambdaControls.
*
* Licensed under the MIT license. For more information,
* see the LICENSE file.
*/
package dev.lambdaurora.lambdacontrols.client;
import dev.lambdaurora.lambdacontrols.client.gui.LambdaControlsSettingsScreen;
import io.github.prospector.modmenu.api.ConfigScreenFactory;
import io.github.prospector.modmenu.api.ModMenuApi;
/**
* Represents the API implementation of ModMenu for LambdaControls.
*
* @author LambdAurora
* @version 1.3.0
* @since 1.1.0
*/
public class LambdaControlsModMenu implements ModMenuApi {
@Override
public ConfigScreenFactory<?> getModConfigScreenFactory() {
return parent -> new LambdaControlsSettingsScreen(parent, false);
}
}

View File

@@ -0,0 +1,786 @@
/*
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of LambdaControls.
*
* Licensed under the MIT license. For more information,
* see the LICENSE file.
*/
package dev.lambdaurora.lambdacontrols.client;
import com.google.common.collect.ImmutableSet;
import dev.lambdaurora.lambdacontrols.client.compat.LambdaControlsCompat;
import dev.lambdaurora.lambdacontrols.client.controller.ButtonBinding;
import dev.lambdaurora.lambdacontrols.client.controller.Controller;
import dev.lambdaurora.lambdacontrols.client.controller.InputManager;
import dev.lambdaurora.lambdacontrols.client.gui.TouchscreenOverlay;
import dev.lambdaurora.lambdacontrols.client.gui.widget.ControllerControlsWidget;
import dev.lambdaurora.lambdacontrols.client.mixin.AdvancementsScreenAccessor;
import dev.lambdaurora.lambdacontrols.client.mixin.CreativeInventoryScreenAccessor;
import dev.lambdaurora.lambdacontrols.client.mixin.EntryListWidgetAccessor;
import dev.lambdaurora.lambdacontrols.client.util.HandledScreenAccessor;
import dev.lambdaurora.lambdacontrols.client.util.MouseAccessor;
import me.lambdaurora.spruceui.navigation.NavigationDirection;
import me.lambdaurora.spruceui.screen.SpruceScreen;
import me.lambdaurora.spruceui.widget.AbstractSprucePressableButtonWidget;
import me.lambdaurora.spruceui.widget.SpruceElement;
import me.lambdaurora.spruceui.widget.SpruceLabelWidget;
import me.lambdaurora.spruceui.widget.container.SpruceParentWidget;
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.MultiplayerScreen;
import net.minecraft.client.gui.screen.multiplayer.MultiplayerServerListWidget;
import net.minecraft.client.gui.screen.pack.PackScreen;
import net.minecraft.client.gui.screen.world.WorldListWidget;
import net.minecraft.client.gui.widget.AbstractPressableButtonWidget;
import net.minecraft.client.gui.widget.AlwaysSelectedEntryListWidget;
import net.minecraft.client.gui.widget.EntryListWidget;
import net.minecraft.client.gui.widget.SliderWidget;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.screen.slot.Slot;
import net.minecraft.screen.slot.SlotActionType;
import net.minecraft.text.TranslatableText;
import net.minecraft.util.math.MathHelper;
import org.aperlambda.lambdacommon.utils.Pair;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.lwjgl.glfw.GLFW;
import org.lwjgl.glfw.GLFWGamepadState;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import static dev.lambdaurora.lambdacontrols.client.controller.ButtonBinding.axisAsButton;
import static dev.lambdaurora.lambdacontrols.client.controller.InputManager.INPUT_MANAGER;
import static org.lwjgl.glfw.GLFW.*;
/**
* Represents the LambdaControls' input handler.
*
* @author LambdAurora
* @version 1.6.0
* @since 1.0.0
*/
public class LambdaInput {
private static final Map<Integer, Integer> BUTTON_COOLDOWNS = new HashMap<>();
private final LambdaControlsConfig config;
// Cooldowns
private int actionGuiCooldown = 0;
private boolean ignoreNextARelease = false;
private double targetYaw = 0.0;
private double targetPitch = 0.0;
private float prevXAxis = 0.f;
private float prevYAxis = 0.f;
private int targetMouseX = 0;
private int targetMouseY = 0;
private float mouseSpeedX = 0.f;
private float mouseSpeedY = 0.f;
private int inventoryInteractionCooldown = 0;
private ControllerControlsWidget controlsInput = null;
public LambdaInput(@NotNull LambdaControlsClient mod) {
this.config = mod.config;
}
/**
* This method is called every Minecraft tick.
*
* @param client The client instance.
*/
public void tick(@NotNull MinecraftClient client) {
this.targetYaw = 0.F;
this.targetPitch = 0.F;
// Handles the key bindings.
if (LambdaControlsClient.BINDING_LOOK_UP.isPressed()) {
this.handleLook(client, GLFW_GAMEPAD_AXIS_RIGHT_Y, 0.8F, 2);
} else if (LambdaControlsClient.BINDING_LOOK_DOWN.isPressed()) {
this.handleLook(client, GLFW_GAMEPAD_AXIS_RIGHT_Y, 0.8F, 1);
}
if (LambdaControlsClient.BINDING_LOOK_LEFT.isPressed()) {
this.handleLook(client, GLFW_GAMEPAD_AXIS_RIGHT_X, 0.8F, 2);
} else if (LambdaControlsClient.BINDING_LOOK_RIGHT.isPressed()) {
this.handleLook(client, GLFW_GAMEPAD_AXIS_RIGHT_X, 0.8F, 1);
}
INPUT_MANAGER.tick(client);
}
/**
* This method is called every Minecraft tick for controller input update.
*
* @param client The client instance.
*/
public void tickController(@NotNull MinecraftClient client) {
BUTTON_COOLDOWNS.entrySet().stream().filter(entry -> entry.getValue() > 0).forEach(entry -> BUTTON_COOLDOWNS.put(entry.getKey(), entry.getValue() - 1));
// Decreases the cooldown for GUI actions.
if (this.actionGuiCooldown > 0)
--this.actionGuiCooldown;
InputManager.updateStates();
Controller controller = this.config.getController();
if (controller.isConnected()) {
GLFWGamepadState state = controller.getState();
this.fetchButtonInput(client, state, false);
this.fetchAxeInput(client, state, false);
}
this.config.getSecondController().filter(Controller::isConnected)
.ifPresent(joycon -> {
GLFWGamepadState state = joycon.getState();
this.fetchButtonInput(client, state, true);
this.fetchAxeInput(client, state, true);
});
boolean allowInput = true;
if (this.controlsInput != null && this.controlsInput.focusedBinding != null)
allowInput = false;
if (allowInput)
InputManager.updateBindings(client);
if (this.controlsInput != null
&& InputManager.STATES.entrySet().parallelStream().map(Map.Entry::getValue).allMatch(ButtonState::isUnpressed)) {
if (this.controlsInput.focusedBinding != null && !this.controlsInput.waiting) {
int[] buttons = new int[this.controlsInput.currentButtons.size()];
for (int i = 0; i < this.controlsInput.currentButtons.size(); i++)
buttons[i] = this.controlsInput.currentButtons.get(i);
this.controlsInput.focusedBinding.setButton(buttons);
this.controlsInput.focusedBinding = null;
this.controlsInput = null;
}
}
if (this.inventoryInteractionCooldown > 0)
this.inventoryInteractionCooldown--;
}
/**
* This method is called before the screen is rendered.
*
* @param client The client instance.
* @param screen The screen to render.
*/
public void onPreRenderScreen(@NotNull MinecraftClient client, @NotNull Screen screen) {
if (!isScreenInteractive(screen)) {
INPUT_MANAGER.updateMousePosition(client);
}
}
/**
* This method is called when Minecraft renders.
*
* @param client The client instance.
*/
public void onRender(float tickDelta, @NotNull MinecraftClient client) {
if (!(client.currentScreen == null || client.currentScreen instanceof TouchscreenOverlay))
return;
PlayerEntity player = client.player;
if (player == null)
return;
if (this.targetYaw != 0.f || this.targetPitch != 0.f) {
float rotationYaw = (float) (player.prevYaw + (this.targetYaw / 0.10) * tickDelta);
float rotationPitch = (float) (player.prevPitch + (this.targetPitch / 0.10) * tickDelta);
client.player.yaw = rotationYaw;
client.player.pitch = MathHelper.clamp(rotationPitch, -90.f, 90.f);
if (client.player.isRiding()) {
client.player.getVehicle().onPassengerLookAround(client.player);
}
client.getTutorialManager().onUpdateMouse(this.targetPitch, this.targetYaw);
}
}
/**
* This method is called when a Screen is opened.
*
* @param client The client instance.
* @param windowWidth The window width.
* @param windowHeight The window height.
*/
public void onScreenOpen(@NotNull MinecraftClient client, int windowWidth, int windowHeight) {
if (client.currentScreen == null) {
this.mouseSpeedX = this.mouseSpeedY = 0.0F;
INPUT_MANAGER.resetMousePosition(windowWidth, windowHeight);
} else if (isScreenInteractive(client.currentScreen) && this.config.hasVirtualMouse()) {
((MouseAccessor) client.mouse).lambdacontrols$onCursorPos(client.getWindow().getHandle(), 0, 0);
INPUT_MANAGER.resetMouseTarget(client);
}
this.inventoryInteractionCooldown = 5;
}
public void beginControlsInput(ControllerControlsWidget widget) {
this.controlsInput = widget;
if (widget != null) {
this.controlsInput.currentButtons.clear();
this.controlsInput.waiting = true;
}
}
private void fetchButtonInput(@NotNull MinecraftClient client, @NotNull GLFWGamepadState gamepadState, boolean leftJoycon) {
ByteBuffer buffer = gamepadState.buttons();
for (int i = 0; i < buffer.limit(); i++) {
int btn = leftJoycon ? ButtonBinding.controller2Button(i) : i;
boolean btnState = buffer.get() == (byte) 1;
ButtonState state = ButtonState.NONE;
ButtonState previousState = InputManager.STATES.getOrDefault(btn, ButtonState.NONE);
if (btnState != previousState.isPressed()) {
state = btnState ? ButtonState.PRESS : ButtonState.RELEASE;
this.handleButton(client, btn, btnState ? 0 : 1, btnState);
if (btnState)
BUTTON_COOLDOWNS.put(btn, 5);
} else if (btnState) {
state = ButtonState.REPEAT;
if (BUTTON_COOLDOWNS.getOrDefault(btn, 0) == 0) {
BUTTON_COOLDOWNS.put(btn, 5);
this.handleButton(client, btn, 2, true);
}
}
InputManager.STATES.put(btn, state);
}
}
private void fetchAxeInput(@NotNull MinecraftClient client, @NotNull GLFWGamepadState gamepadState, boolean leftJoycon) {
FloatBuffer buffer = gamepadState.axes();
for (int i = 0; i < buffer.limit(); i++) {
int axis = leftJoycon ? ButtonBinding.controller2Button(i) : i;
float value = buffer.get();
float absValue = Math.abs(value);
if (i == GLFW.GLFW_GAMEPAD_AXIS_LEFT_Y)
value *= -1.0F;
int state = value > this.config.getRightDeadZone() ? 1 : (value < -this.config.getRightDeadZone() ? 2 : 0);
this.handleAxe(client, axis, value, absValue, state);
}
}
private void handleButton(@NotNull MinecraftClient client, int button, int action, boolean state) {
if (this.controlsInput != null && this.controlsInput.focusedBinding != null) {
if (action == 0 && !this.controlsInput.currentButtons.contains(button)) {
this.controlsInput.currentButtons.add(button);
int[] buttons = new int[this.controlsInput.currentButtons.size()];
for (int i = 0; i < this.controlsInput.currentButtons.size(); i++)
buttons[i] = this.controlsInput.currentButtons.get(i);
this.controlsInput.focusedBinding.setButton(buttons);
this.controlsInput.waiting = false;
}
return;
}
if (action == 0 || action == 2) {
if (client.currentScreen != null && isScreenInteractive(client.currentScreen)
&& (button == GLFW.GLFW_GAMEPAD_BUTTON_DPAD_UP || button == GLFW.GLFW_GAMEPAD_BUTTON_DPAD_DOWN
|| button == GLFW.GLFW_GAMEPAD_BUTTON_DPAD_LEFT || button == GLFW.GLFW_GAMEPAD_BUTTON_DPAD_RIGHT)) {
if (this.actionGuiCooldown == 0) {
if (button == GLFW.GLFW_GAMEPAD_BUTTON_DPAD_UP) {
this.changeFocus(client.currentScreen, NavigationDirection.UP);
} else if (button == GLFW.GLFW_GAMEPAD_BUTTON_DPAD_DOWN) {
this.changeFocus(client.currentScreen, NavigationDirection.DOWN);
} else if (button == GLFW.GLFW_GAMEPAD_BUTTON_DPAD_LEFT) {
this.handleLeftRight(client.currentScreen, false);
} else {
this.handleLeftRight(client.currentScreen, true);
}
}
return;
}
}
if (action == 1) {
if (button == GLFW.GLFW_GAMEPAD_BUTTON_A && client.currentScreen != null) {
if (this.actionGuiCooldown == 0) {
Element focused = client.currentScreen.getFocused();
if (focused != null && isScreenInteractive(client.currentScreen)) {
if (this.handleAButton(client.currentScreen, focused)) {
this.actionGuiCooldown = 5; // Prevent to press too quickly the focused element, so we have to skip 5 ticks.
return;
}
}
}
}
if (this.handleInventory(client, button)) {
this.ignoreNextARelease = true;
return;
}
if (button == GLFW.GLFW_GAMEPAD_BUTTON_B) {
if (client.currentScreen != null) {
if (!LambdaControlsCompat.handleMenuBack(client, client.currentScreen))
if (!this.tryGoBack(client.currentScreen))
client.currentScreen.onClose();
return;
}
}
}
if (button == GLFW.GLFW_GAMEPAD_BUTTON_A && client.currentScreen != null && !isScreenInteractive(client.currentScreen) && this.actionGuiCooldown == 0) {
if (!this.ignoreNextARelease) {
double mouseX = client.mouse.getX() * (double) client.getWindow().getScaledWidth() / (double) client.getWindow().getWidth();
double mouseY = client.mouse.getY() * (double) client.getWindow().getScaledHeight() / (double) client.getWindow().getHeight();
if (action == 0) {
Screen.wrapScreenError(() -> client.currentScreen.mouseClicked(mouseX, mouseY, GLFW.GLFW_MOUSE_BUTTON_1),
"mouseClicked event handler", client.currentScreen.getClass().getCanonicalName());
} else if (action == 1) {
Screen.wrapScreenError(() -> client.currentScreen.mouseReleased(mouseX, mouseY, GLFW.GLFW_MOUSE_BUTTON_1),
"mouseReleased event handler", client.currentScreen.getClass().getCanonicalName());
}
this.actionGuiCooldown = 5;
} else {
this.ignoreNextARelease = false;
}
}
}
/**
* Handles inventory interaction.
*
* @param client The client instance.
* @param button The button pressed.
* @return True if an inventory interaction was done.
*/
private boolean handleInventory(@NotNull MinecraftClient client, int button) {
if (!(client.currentScreen instanceof HandledScreen))
return false;
if (client.interactionManager == null || client.player == null)
return false;
if (this.inventoryInteractionCooldown > 0)
return true;
if (button == GLFW.GLFW_GAMEPAD_BUTTON_B) {
client.player.closeHandledScreen();
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();
HandledScreen screen = (HandledScreen) client.currentScreen;
HandledScreenAccessor accessor = (HandledScreenAccessor) screen;
Slot slot = ((HandledScreenAccessor) client.currentScreen).lambdacontrols$getSlotAt(x, y);
int slotId;
if (slot == null) {
if (client.player.inventory.getCursorStack().isEmpty())
return false;
slotId = accessor.lambdacontrols$isClickOutsideBounds(x, y, accessor.getX(), accessor.getY(), GLFW_MOUSE_BUTTON_1) ? -999 : -1;
} else {
slotId = slot.id;
}
SlotActionType actionType = SlotActionType.PICKUP;
int clickData = GLFW.GLFW_MOUSE_BUTTON_1;
switch (button) {
case GLFW_GAMEPAD_BUTTON_A:
if (screen instanceof CreativeInventoryScreen)
if (((CreativeInventoryScreenAccessor) screen).lambdacontrols$isCreativeInventorySlot(slot))
actionType = SlotActionType.CLONE;
if (slot != null && LambdaControlsCompat.streamCompatHandlers().anyMatch(handler -> handler.isCreativeSlot(screen, slot)))
actionType = SlotActionType.CLONE;
break;
case GLFW.GLFW_GAMEPAD_BUTTON_X:
clickData = GLFW_MOUSE_BUTTON_2;
break;
case GLFW.GLFW_GAMEPAD_BUTTON_Y:
actionType = SlotActionType.QUICK_MOVE;
break;
default:
return false;
}
accessor.lambdacontrols$onMouseClick(slot, slotId, clickData, actionType);
return true;
}
/**
* Tries to go back.
*
* @param screen The current screen.
* @return True if successful, else false.
*/
public boolean tryGoBack(@NotNull Screen screen) {
ImmutableSet<String> set = ImmutableSet.of("gui.back", "gui.done", "gui.cancel", "gui.toTitle", "gui.toMenu");
return screen.children().stream().filter(element -> element instanceof AbstractPressableButtonWidget)
.map(element -> (AbstractPressableButtonWidget) element)
.filter(element -> element.getMessage() instanceof TranslatableText)
.anyMatch(element -> {
if (set.stream().anyMatch(key -> key.equals(((TranslatableText) element.getMessage()).getKey()))) {
element.onPress();
return true;
}
return false;
});
}
private double getDeadZoneValue(int axis) {
return (axis == GLFW_GAMEPAD_AXIS_LEFT_X || axis == GLFW_GAMEPAD_AXIS_LEFT_Y) ? this.config.getLeftDeadZone()
: this.config.getRightDeadZone();
}
private void handleAxe(@NotNull MinecraftClient client, int axis, float value, float absValue, int state) {
int asButtonState = value > .5f ? 1 : (value < -.5f ? 2 : 0);
if (axis == GLFW_GAMEPAD_AXIS_LEFT_TRIGGER || axis == GLFW_GAMEPAD_AXIS_RIGHT_TRIGGER
|| axis == ButtonBinding.controller2Button(GLFW.GLFW_GAMEPAD_AXIS_LEFT_TRIGGER)
|| axis == ButtonBinding.controller2Button(GLFW.GLFW_GAMEPAD_AXIS_RIGHT_TRIGGER))
if (asButtonState == 2)
asButtonState = 0;
{
boolean currentPlusState = asButtonState == 1;
boolean currentMinusState = asButtonState == 2;
ButtonState previousPlusState = InputManager.STATES.getOrDefault(axisAsButton(axis, true), ButtonState.NONE);
ButtonState previousMinusState = InputManager.STATES.getOrDefault(axisAsButton(axis, false), ButtonState.NONE);
if (currentPlusState != previousPlusState.isPressed()) {
InputManager.STATES.put(axisAsButton(axis, true), currentPlusState ? ButtonState.PRESS : ButtonState.RELEASE);
if (currentPlusState)
BUTTON_COOLDOWNS.put(axisAsButton(axis, true), 5);
} else if (currentPlusState) {
InputManager.STATES.put(axisAsButton(axis, true), ButtonState.REPEAT);
if (BUTTON_COOLDOWNS.getOrDefault(axisAsButton(axis, true), 0) == 0) {
BUTTON_COOLDOWNS.put(axisAsButton(axis, true), 5);
}
}
if (currentMinusState != previousMinusState.isPressed()) {
InputManager.STATES.put(axisAsButton(axis, false), currentMinusState ? ButtonState.PRESS : ButtonState.RELEASE);
if (currentMinusState)
BUTTON_COOLDOWNS.put(axisAsButton(axis, false), 5);
} else if (currentMinusState) {
InputManager.STATES.put(axisAsButton(axis, false), ButtonState.REPEAT);
if (BUTTON_COOLDOWNS.getOrDefault(axisAsButton(axis, false), 0) == 0) {
BUTTON_COOLDOWNS.put(axisAsButton(axis, false), 5);
}
}
double deadZone = this.getDeadZoneValue(axis);
float axisValue = absValue < deadZone ? 0.f : (float) (absValue - deadZone);
axisValue /= (1.0 - deadZone);
axisValue = (float) Math.min(axisValue / this.config.getAxisMaxValue(axis), 1);
if (currentPlusState)
InputManager.BUTTON_VALUES.put(axisAsButton(axis, true), axisValue);
else
InputManager.BUTTON_VALUES.put(axisAsButton(axis, true), 0.f);
if (currentMinusState)
InputManager.BUTTON_VALUES.put(axisAsButton(axis, false), axisValue);
else
InputManager.BUTTON_VALUES.put(axisAsButton(axis, false), 0.f);
}
double deadZone = this.getDeadZoneValue(axis);
if (this.controlsInput != null && this.controlsInput.focusedBinding != null) {
if (asButtonState != 0 && !this.controlsInput.currentButtons.contains(axisAsButton(axis, asButtonState == 1))) {
this.controlsInput.currentButtons.add(axisAsButton(axis, asButtonState == 1));
int[] buttons = new int[this.controlsInput.currentButtons.size()];
for (int i = 0; i < this.controlsInput.currentButtons.size(); i++)
buttons[i] = this.controlsInput.currentButtons.get(i);
this.controlsInput.focusedBinding.setButton(buttons);
this.controlsInput.waiting = false;
}
return;
} else if (client.currentScreen instanceof CreativeInventoryScreen) {
if (axis == GLFW_GAMEPAD_AXIS_RIGHT_Y) {
CreativeInventoryScreen screen = (CreativeInventoryScreen) client.currentScreen;
CreativeInventoryScreenAccessor accessor = (CreativeInventoryScreenAccessor) screen;
// @TODO allow rebinding to left stick
if (accessor.lambdacontrols$hasScrollbar() && absValue >= deadZone) {
screen.mouseScrolled(0.0, 0.0, -value);
}
return;
}
} else if (client.currentScreen instanceof AdvancementsScreen) {
if (axis == GLFW_GAMEPAD_AXIS_RIGHT_X || axis == GLFW_GAMEPAD_AXIS_RIGHT_Y) {
AdvancementsScreen screen = (AdvancementsScreen) client.currentScreen;
AdvancementsScreenAccessor accessor = (AdvancementsScreenAccessor) screen;
if (absValue >= deadZone) {
AdvancementTab tab = accessor.getSelectedTab();
tab.move(axis == GLFW_GAMEPAD_AXIS_RIGHT_X ? -value * 5.0 : 0.0, axis == GLFW_GAMEPAD_AXIS_RIGHT_Y ? -value * 5.0 : 0.0);
}
return;
}
}
absValue -= deadZone;
absValue /= (1.0 - deadZone);
absValue = (float) MathHelper.clamp(absValue / this.config.getAxisMaxValue(axis), 0.f, 1.f);
if (client.currentScreen == null) {
// Handles the look direction.
this.handleLook(client, axis, absValue, state);
} else {
boolean allowMouseControl = true;
if (this.actionGuiCooldown == 0 && this.config.isMovementAxis(axis) && isScreenInteractive(client.currentScreen)) {
if (this.config.isForwardButton(axis, false, asButtonState)) {
allowMouseControl = this.changeFocus(client.currentScreen, NavigationDirection.UP);
} else if (this.config.isBackButton(axis, false, asButtonState)) {
allowMouseControl = this.changeFocus(client.currentScreen, NavigationDirection.DOWN);
} else if (this.config.isLeftButton(axis, false, asButtonState)) {
allowMouseControl = this.handleLeftRight(client.currentScreen, false);
} else if (this.config.isRightButton(axis, false, asButtonState)) {
allowMouseControl = this.handleLeftRight(client.currentScreen, true);
}
}
float movementX = 0.f;
float movementY = 0.f;
if (this.config.isBackButton(axis, false, (value > 0 ? 1 : 2))) {
movementY = absValue;
} else if (this.config.isForwardButton(axis, false, (value > 0 ? 1 : 2))) {
movementY = -absValue;
} else if (this.config.isLeftButton(axis, false, (value > 0 ? 1 : 2))) {
movementX = -absValue;
} else if (this.config.isRightButton(axis, false, (value > 0 ? 1 : 2))) {
movementX = absValue;
}
if (client.currentScreen != null && allowMouseControl) {
boolean moving = movementY != 0 || movementX != 0;
if (moving) {
/*
Updates the target mouse position when the initial movement stick movement is detected.
It prevents the cursor to jump to the old target mouse position if the user moves the cursor with the mouse.
*/
if (Math.abs(prevXAxis) < deadZone && Math.abs(prevYAxis) < deadZone) {
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 * this.config.getMouseSpeed(),
this.mouseSpeedY * this.config.getMouseSpeed()
);
}
this.moveMouseToClosestSlot(client, client.currentScreen);
}
this.prevXAxis = movementX;
this.prevYAxis = movementY;
}
}
private boolean handleAButton(@NotNull Screen screen, @NotNull Element focused) {
if (focused instanceof AbstractPressableButtonWidget) {
AbstractPressableButtonWidget widget = (AbstractPressableButtonWidget) focused;
widget.playDownSound(MinecraftClient.getInstance().getSoundManager());
widget.onPress();
return true;
} else if (focused instanceof AbstractSprucePressableButtonWidget) {
AbstractSprucePressableButtonWidget widget = (AbstractSprucePressableButtonWidget) focused;
widget.playDownSound();
widget.onPress();
return true;
} else if (focused instanceof SpruceLabelWidget) {
((SpruceLabelWidget) focused).onPress();
return true;
} else if (focused instanceof WorldListWidget) {
WorldListWidget list = (WorldListWidget) focused;
list.getSelectedAsOptional().ifPresent(WorldListWidget.Entry::play);
return true;
} else if (focused instanceof MultiplayerServerListWidget) {
MultiplayerServerListWidget list = (MultiplayerServerListWidget) focused;
MultiplayerServerListWidget.Entry entry = list.getSelected();
if (entry instanceof MultiplayerServerListWidget.LanServerEntry || entry instanceof MultiplayerServerListWidget.ServerEntry) {
((MultiplayerScreen) screen).select(entry);
((MultiplayerScreen) screen).connect();
}
} else if (focused instanceof SpruceParentWidget) {
Element childFocused = ((SpruceParentWidget<?>) focused).getFocused();
if (childFocused != null)
return this.handleAButton(screen, childFocused);
} else if (focused instanceof ParentElement) {
Element childFocused = ((ParentElement) focused).getFocused();
if (childFocused != null)
return this.handleAButton(screen, childFocused);
}
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) screen).onNavigation(right ? NavigationDirection.RIGHT : NavigationDirection.LEFT, false);
this.actionGuiCooldown = 5;
return false;
}
Element focused = screen.getFocused();
if (focused != null)
if (this.handleRightLeftElement(focused, right))
return this.changeFocus(screen, right ? NavigationDirection.RIGHT : NavigationDirection.LEFT);
return true;
}
private boolean handleRightLeftElement(@NotNull Element element, boolean right) {
if (element instanceof SpruceElement) {
if (((SpruceElement) element).requiresCursor())
return true;
return !((SpruceElement) element).onNavigation(right ? NavigationDirection.RIGHT : NavigationDirection.LEFT, false);
}
if (element instanceof SliderWidget) {
SliderWidget slider = (SliderWidget) element;
slider.keyPressed(right ? 262 : 263, 0, 0);
this.actionGuiCooldown = 2; // Prevent to press too quickly the focused element, so we have to skip 5 ticks.
return false;
} else if (element instanceof AlwaysSelectedEntryListWidget) {
((EntryListWidgetAccessor) element).lambdacontrols$moveSelection(right ? EntryListWidget.MoveDirection.UP : EntryListWidget.MoveDirection.DOWN);
return false;
} else if (element instanceof ParentElement) {
ParentElement entryList = (ParentElement) element;
Element focused = entryList.getFocused();
if (focused == null)
return true;
return this.handleRightLeftElement(focused, right);
}
return true;
}
/**
* Handles the look direction input.
*
* @param client The client instance.
* @param axis The axis to change.
* @param value The value of the look.
* @param state The state.
*/
public void handleLook(@NotNull MinecraftClient client, int axis, float value, int state) {
// Handles the look direction.
if (client.player != null) {
double powValue = Math.pow(value, 2.0);
if (axis == GLFW_GAMEPAD_AXIS_RIGHT_Y) {
if (state == 2) {
this.targetPitch = -this.config.getRightYAxisSign() * (this.config.getRotationSpeed() * powValue) * 0.11D;
} else if (state == 1) {
this.targetPitch = this.config.getRightYAxisSign() * (this.config.getRotationSpeed() * powValue) * 0.11D;
}
}
if (axis == GLFW_GAMEPAD_AXIS_RIGHT_X) {
if (state == 2) {
this.targetYaw = -this.config.getRightXAxisSign() * (this.config.getRotationSpeed() * powValue) * 0.11D;
} else if (state == 1) {
this.targetYaw = this.config.getRightXAxisSign() * (this.config.getRotationSpeed() * powValue) * 0.11D;
}
}
}
}
private boolean changeFocus(@NotNull Screen screen, NavigationDirection direction) {
if (screen instanceof SpruceScreen) {
if (((SpruceScreen) screen).onNavigation(direction, false)) {
this.actionGuiCooldown = 5;
}
return false;
}
if (!screen.changeFocus(direction.isLookingForward())) {
if (screen.changeFocus(direction.isLookingForward())) {
this.actionGuiCooldown = 5;
return false;
}
return true;
} else {
this.actionGuiCooldown = 5;
return false;
}
}
public static boolean isScreenInteractive(@NotNull Screen screen) {
return !(screen instanceof AdvancementsScreen || screen instanceof HandledScreen || screen instanceof PackScreen
|| (screen instanceof SpruceScreen && ((SpruceScreen) screen).requiresCursor())
|| LambdaControlsCompat.requireMouseOnScreen(screen));
}
// Inspired from https://github.com/MrCrayfish/Controllable/blob/1.14.X/src/main/java/com/mrcrayfish/controllable/client/ControllerInput.java#L686.
private void moveMouseToClosestSlot(@NotNull MinecraftClient client, @Nullable Screen screen) {
// Makes the mouse attracted to slots. This helps with selecting items when using a controller.
if (screen instanceof HandledScreen) {
HandledScreen inventoryScreen = (HandledScreen) screen;
HandledScreenAccessor accessor = (HandledScreenAccessor) inventoryScreen;
int guiLeft = accessor.getX();
int guiTop = accessor.getY();
int mouseX = (int) (targetMouseX * (double) client.getWindow().getScaledWidth() / (double) client.getWindow().getWidth());
int mouseY = (int) (targetMouseY * (double) client.getWindow().getScaledHeight() / (double) client.getWindow().getHeight());
// Finds the closest slot in the GUI within 14 pixels.
Optional<Pair<Slot, Double>> closestSlot = inventoryScreen.getScreenHandler().slots.parallelStream()
.map(slot -> {
int x = guiLeft + slot.x + 8;
int y = guiTop + slot.y + 8;
// Distance between the slot and the cursor.
double distance = Math.sqrt(Math.pow(x - mouseX, 2) + Math.pow(y - mouseY, 2));
return Pair.of(slot, distance);
}).filter(entry -> entry.value <= 14.0)
.min(Comparator.comparingDouble(p -> p.value));
if (closestSlot.isPresent()) {
Slot slot = closestSlot.get().key;
if (slot.hasStack() || !client.player.inventory.getMainHandStack().isEmpty()) {
int slotCenterXScaled = guiLeft + slot.x + 8;
int slotCenterYScaled = guiTop + slot.y + 8;
int slotCenterX = (int) (slotCenterXScaled / ((double) client.getWindow().getScaledWidth() / (double) client.getWindow().getWidth()));
int slotCenterY = (int) (slotCenterYScaled / ((double) client.getWindow().getScaledHeight() / (double) client.getWindow().getHeight()));
double deltaX = slotCenterX - targetMouseX;
double deltaY = slotCenterY - targetMouseY;
if (mouseX != slotCenterXScaled || mouseY != slotCenterYScaled) {
this.targetMouseX += deltaX * 0.75;
this.targetMouseY += deltaY * 0.75;
} else {
this.mouseSpeedX *= 0.3F;
this.mouseSpeedY *= 0.3F;
}
this.mouseSpeedX *= .75F;
this.mouseSpeedY *= .75F;
} else {
this.mouseSpeedX *= .1F;
this.mouseSpeedY *= .1F;
}
} else {
this.mouseSpeedX *= .3F;
this.mouseSpeedY *= .3F;
}
} else {
this.mouseSpeedX = 0.F;
this.mouseSpeedY = 0.F;
}
}
}

View File

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

View File

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

View File

@@ -0,0 +1,103 @@
/*
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of LambdaControls.
*
* Licensed under the MIT license. For more information,
* see the LICENSE file.
*/
package dev.lambdaurora.lambdacontrols.client.compat;
import dev.lambdaurora.lambdacontrols.client.LambdaControlsClient;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gui.screen.Screen;
import net.minecraft.client.gui.screen.ingame.HandledScreen;
import net.minecraft.screen.slot.Slot;
import net.minecraft.util.hit.BlockHitResult;
import org.aperlambda.lambdacommon.utils.Pair;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Represents a compatibility handler for a mod.
*
* @author LambdAurora
* @version 1.5.0
* @since 1.1.0
*/
public interface CompatHandler {
/**
* Handles compatibility of a mod.
*
* @param mod This mod instance.
*/
void handle(@NotNull LambdaControlsClient mod);
/**
* Returns whether the mouse is required on the specified screen.
*
* @param screen The screen.
* @return True if the mouse is required on the specified screen, else false.
*/
default boolean requireMouseOnScreen(Screen screen) {
return false;
}
/**
* Returns a slot at the specified location if possible.
*
* @param screen The screen.
* @param mouseX The mouse X-coordinate.
* @param mouseY The mouse Y-coordinate.
* @return A slot if present, else null.
* @since 1.5.0
*/
default @Nullable Pair<Integer, Integer> getSlotAt(@NotNull Screen screen, int mouseX, int mouseY) {
return null;
}
/**
* Returns whether the current slot is a creative slot or not.
*
* @param screen The screen.
* @param slot The slot to check.
* @return True if the slot is a creative slot, else false.
*/
default boolean isCreativeSlot(@NotNull HandledScreen screen, @NotNull Slot slot) {
return false;
}
/**
* Returns a custom translation key to make custom attack action strings on the HUD.
*
* @param client The client instance.
* @param placeResult The last place block result.
* @return Null if untouched, else a translation key.
*/
default String getAttackActionAt(@NotNull MinecraftClient client, @Nullable BlockHitResult placeResult) {
return null;
}
/**
* Returns a custom translation key to make custom use action strings on the HUD.
*
* @param client The client instance.
* @param placeResult The last place block result.
* @return Null if untouched, else a translation key.
*/
default String getUseActionAt(@NotNull MinecraftClient client, @Nullable BlockHitResult placeResult) {
return null;
}
/**
* Handles the menu back button.
*
* @param client The client instance.
* @param screen The screen.
* @return True if the handle was fired and succeed, else false.
*/
default boolean handleMenuBack(@NotNull MinecraftClient client, @NotNull Screen screen) {
return false;
}
}

View File

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

View File

@@ -0,0 +1,161 @@
/*
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of LambdaControls.
*
* Licensed under the MIT license. For more information,
* see the LICENSE file.
*/
package dev.lambdaurora.lambdacontrols.client.compat;
import dev.lambdaurora.lambdacontrols.client.LambdaControlsClient;
import dev.lambdaurora.lambdacontrols.client.controller.InputManager;
import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gui.screen.Screen;
import net.minecraft.util.hit.BlockHitResult;
import org.aperlambda.lambdacommon.utils.LambdaReflection;
import org.aperlambda.lambdacommon.utils.Pair;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
/**
* Represents a compatibility handler.
*
* @author LambdAurora
* @version 1.5.0
* @since 1.1.0
*/
public class LambdaControlsCompat {
private static final List<CompatHandler> HANDLERS = new ArrayList<>();
/**
* Initializes compatibility with other mods if needed.
*
* @param mod The mod instance.
*/
public static void init(@NotNull LambdaControlsClient mod) {
if (FabricLoader.getInstance().isModLoaded("okzoomer")) {
mod.log("Adding okzoomer compatibility...");
HANDLERS.add(new OkZoomerCompat());
}
if (isReiPresent()) {
mod.log("Adding REI compatiblity...");
HANDLERS.add(new ReiCompat());
}
if (FabricLoader.getInstance().isModLoaded("hardcorequesting") && LambdaReflection.doesClassExist(HQMCompat.GUI_BASE_CLASS_PATH)) {
mod.log("Adding HQM compatibility...");
HANDLERS.add(new HQMCompat());
}
HANDLERS.forEach(handler -> handler.handle(mod));
InputManager.loadButtonBindings(mod.config);
}
/**
* Registers a new compatibility handler.
*
* @param handler The compatibility handler to register.
*/
public static void registerCompatHandler(@NotNull CompatHandler handler) {
HANDLERS.add(handler);
}
/**
* Streams through compatibility handlers.
*
* @return A stream of compatibility handlers.
*/
public static Stream<CompatHandler> streamCompatHandlers() {
return HANDLERS.stream();
}
/**
* Returns whether the mouse is required on the specified screen.
*
* @param screen The screen.
* @return True if the mouse is requried on the specified screen, else false.
*/
public static boolean requireMouseOnScreen(Screen screen) {
return HANDLERS.stream().anyMatch(handler -> handler.requireMouseOnScreen(screen));
}
/**
* Returns a slot at the specified location if possible.
*
* @param screen The screen.
* @param mouseX The mouse X-coordinate.
* @param mouseY The mouse Y-coordinate.
* @return A slot if present, else null.
*/
public static @Nullable Pair<Integer, Integer> getSlotAt(@NotNull Screen screen, int mouseX, int mouseY) {
for (CompatHandler handler : HANDLERS) {
Pair<Integer, Integer> slot = handler.getSlotAt(screen, mouseX, mouseY);
if (slot != null)
return slot;
}
return null;
}
/**
* Returns a custom translation key to make custom attack action strings on the HUD.
*
* @param client The client instance.
* @param placeResult The last place block result.
* @return Null if untouched, else a translation key.
*/
public static String getAttackActionAt(@NotNull MinecraftClient client, @Nullable BlockHitResult placeResult) {
for (CompatHandler handler : HANDLERS) {
String action = handler.getAttackActionAt(client, placeResult);
if (action != null) {
return action;
}
}
return null;
}
/**
* Returns a custom translation key to make custom use action strings on the HUD.
*
* @param client The client instance.
* @param placeResult The last place block result.
* @return Null if untouched, else a translation key.
*/
public static String getUseActionAt(@NotNull MinecraftClient client, @Nullable BlockHitResult placeResult) {
for (CompatHandler handler : HANDLERS) {
String action = handler.getUseActionAt(client, placeResult);
if (action != null) {
return action;
}
}
return null;
}
/**
* Handles the menu back button.
*
* @param client The client instance.
* @param screen The screen.
* @return True if the handle was fired and succeed, else false.
*/
public static boolean handleMenuBack(@NotNull MinecraftClient client, @NotNull Screen screen) {
for (CompatHandler handler : HANDLERS) {
if (handler.handleMenuBack(client, screen))
return true;
}
return false;
}
/**
* Returns whether Roughly Enough Items is present.
*
* @return True if Roughly Enough Items is present, else false.
*/
public static boolean isReiPresent() {
return FabricLoader.getInstance().isModLoaded("roughlyenoughitems");
}
}

View File

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

View File

@@ -0,0 +1,59 @@
/*
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of LambdaControls.
*
* Licensed under the MIT license. For more information,
* see the LICENSE file.
*/
package dev.lambdaurora.lambdacontrols.client.compat;
import dev.lambdaurora.lambdacontrols.client.LambdaControlsClient;
import dev.lambdaurora.lambdacontrols.client.controller.ButtonBinding;
import io.github.joaoh1.okzoomer.client.keybinds.ZoomKeybinds;
import org.jetbrains.annotations.NotNull;
import org.lwjgl.glfw.GLFW;
/**
* Represents a compatibility handler for OkZoomer.
*
* @author LambdAurora
* @version 1.4.3
* @since 1.1.0
*/
public class OkZoomerCompat implements CompatHandler {
@Override
public void handle(@NotNull LambdaControlsClient mod) {
new ButtonBinding.Builder("zoom")
.buttons(GLFW.GLFW_GAMEPAD_BUTTON_DPAD_UP, GLFW.GLFW_GAMEPAD_BUTTON_X)
.onlyInGame()
.cooldown(true)
.category(ButtonBinding.MISC_CATEGORY)
.linkKeybind(ZoomKeybinds.zoomKey)
.register();
if (ZoomKeybinds.areExtraKeybindsEnabled()) {
new ButtonBinding.Builder("zoom_in")
.buttons(GLFW.GLFW_GAMEPAD_BUTTON_DPAD_UP, ButtonBinding.axisAsButton(GLFW.GLFW_GAMEPAD_AXIS_RIGHT_TRIGGER, true))
.onlyInGame()
.cooldown(true)
.category(ButtonBinding.MISC_CATEGORY)
.linkKeybind(ZoomKeybinds.increaseZoomKey)
.register();
new ButtonBinding.Builder("zoom_out")
.buttons(GLFW.GLFW_GAMEPAD_BUTTON_DPAD_UP, ButtonBinding.axisAsButton(GLFW.GLFW_GAMEPAD_AXIS_LEFT_TRIGGER, true))
.onlyInGame()
.cooldown(true)
.category(ButtonBinding.MISC_CATEGORY)
.linkKeybind(ZoomKeybinds.decreaseZoomKey)
.register();
new ButtonBinding.Builder("zoom_reset")
.onlyInGame()
.cooldown(true)
.category(ButtonBinding.MISC_CATEGORY)
.linkKeybind(ZoomKeybinds.resetZoomKey)
.register();
}
}
}

View File

@@ -0,0 +1,360 @@
/*
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of LambdaControls.
*
* Licensed under the MIT license. For more information,
* see the LICENSE file.
*/
package dev.lambdaurora.lambdacontrols.client.compat;
import dev.lambdaurora.lambdacontrols.client.ButtonState;
import dev.lambdaurora.lambdacontrols.client.LambdaControlsClient;
import dev.lambdaurora.lambdacontrols.client.compat.mixin.EntryListWidgetAccessor;
import dev.lambdaurora.lambdacontrols.client.compat.mixin.EntryWidgetAccessor;
import dev.lambdaurora.lambdacontrols.client.compat.mixin.RecipeViewingScreenAccessor;
import dev.lambdaurora.lambdacontrols.client.compat.mixin.VillagerRecipeViewingScreenAccessor;
import dev.lambdaurora.lambdacontrols.client.controller.ButtonBinding;
import dev.lambdaurora.lambdacontrols.client.controller.InputHandlers;
import dev.lambdaurora.lambdacontrols.client.controller.PressAction;
import me.shedaniel.rei.api.*;
import me.shedaniel.rei.gui.ContainerScreenOverlay;
import me.shedaniel.rei.gui.PreRecipeViewingScreen;
import me.shedaniel.rei.gui.RecipeViewingScreen;
import me.shedaniel.rei.gui.VillagerRecipeViewingScreen;
import me.shedaniel.rei.gui.widget.EntryListEntryWidget;
import me.shedaniel.rei.gui.widget.EntryListWidget;
import me.shedaniel.rei.gui.widget.EntryWidget;
import me.shedaniel.rei.gui.widget.WidgetWithBounds;
import me.shedaniel.rei.impl.ScreenHelper;
import me.shedaniel.rei.impl.widgets.ButtonWidget;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gui.Element;
import net.minecraft.client.gui.screen.Screen;
import net.minecraft.util.Identifier;
import org.aperlambda.lambdacommon.utils.LambdaReflection;
import org.aperlambda.lambdacommon.utils.Pair;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.Optional;
import static org.lwjgl.glfw.GLFW.*;
/**
* Represents a compatibility handler for REI.
*
* @author LambdAurora
* @version 1.5.0
* @since 1.2.0
*/
public class ReiCompat implements CompatHandler {
private static final Pair<Integer, Integer> INVALID_SLOT = new Pair<>(-1, -1);
private static EntryListWidget ENTRY_LIST_WIDGET;
@Override
public void handle(@NotNull LambdaControlsClient mod) {
ButtonBinding.builder(new Identifier("rei", "category_back"))
.buttons(GLFW_GAMEPAD_BUTTON_LEFT_BUMPER)
.filter((client, binding) -> isViewingScreen(client.currentScreen))
.action(handleTab(false))
.cooldown(true)
.register();
ButtonBinding.builder(new Identifier("rei", "category_next"))
.buttons(GLFW_GAMEPAD_BUTTON_RIGHT_BUMPER)
.filter((client, binding) -> isViewingScreen(client.currentScreen))
.action(handleTab(true))
.cooldown(true)
.register();
ButtonBinding.builder(new Identifier("rei", "page_back"))
.buttons(ButtonBinding.axisAsButton(GLFW_GAMEPAD_AXIS_RIGHT_X, false))
.filter((client, binding) -> InputHandlers.inInventory(client, binding) || isViewingScreen(client.currentScreen))
.action(handlePage(false))
.cooldown(true)
.register();
ButtonBinding.builder(new Identifier("rei", "page_next"))
.buttons(ButtonBinding.axisAsButton(GLFW_GAMEPAD_AXIS_RIGHT_X, true))
.filter((client, binding) -> InputHandlers.inInventory(client, binding) || isViewingScreen(client.currentScreen))
.action(handlePage(true))
.cooldown(true)
.register();
ButtonBinding.builder(new Identifier("rei", "recipe_back"))
.buttons(GLFW_GAMEPAD_BUTTON_DPAD_UP)
.filter((client, binding) -> isViewingScreen(client.currentScreen))
.action(handleRecipe(false))
.cooldown(true)
.register();
ButtonBinding.builder(new Identifier("rei", "recipe_next"))
.buttons(GLFW_GAMEPAD_BUTTON_DPAD_DOWN)
.filter((client, binding) -> isViewingScreen(client.currentScreen))
.action(handleRecipe(true))
.cooldown(true)
.register();
// For some reasons this is broken.
ButtonBinding.builder(new Identifier("rei", "show_usage"))
.buttons(GLFW_GAMEPAD_BUTTON_RIGHT_THUMB)
.filter((client, binding) -> InputHandlers.inInventory(client, binding) || isViewingScreen(client.currentScreen))
.action(handleShowRecipeUsage(true))
.cooldown(true)
.register();
ButtonBinding.builder(new Identifier("rei", "show_recipe"))
.buttons(GLFW_GAMEPAD_BUTTON_LEFT_THUMB)
.filter((client, binding) -> InputHandlers.inInventory(client, binding) || isViewingScreen(client.currentScreen))
.action(handleShowRecipeUsage(false))
.cooldown(true)
.register();
}
@Override
public boolean requireMouseOnScreen(Screen screen) {
return isViewingScreen(screen) || screen instanceof PreRecipeViewingScreen;
}
@Override
public @Nullable Pair<Integer, Integer> getSlotAt(@NotNull Screen screen, int mouseX, int mouseY) {
Optional<ContainerScreenOverlay> overlay = ScreenHelper.getOptionalOverlay();
if (overlay.isPresent() && overlay.get().isInside(mouseX, mouseY)) {
EntryListWidget widget = getEntryListWidget();
if (widget == null)
return null;
Pair<Integer, Integer> slot = this.getSlotAt(widget, mouseX, mouseY, false);
if (slot != null && slot != INVALID_SLOT)
return slot;
} else if (isViewingScreen(screen)) {
for (Element element : screen.children()) {
Pair<Integer, Integer> slot = this.getSlotAt(element, mouseX, mouseY, true);
if (slot != null && slot != INVALID_SLOT)
return slot;
}
}
return null;
}
private @Nullable Pair<Integer, Integer> getSlotAt(@NotNull Element element, int mouseX, int mouseY, boolean allowEmpty) {
if (element instanceof EntryWidget) {
EntryWidget entry = (EntryWidget) element;
if (entry.containsMouse(mouseX, mouseY)) {
if (!allowEmpty && entry.entries().isEmpty())
return INVALID_SLOT;
return Pair.of(entry.getBounds().getX() + 1, entry.getBounds().getY() + 1);
}
} else if (element instanceof EntryListWidget) {
List<EntryListEntryWidget> entries = ((EntryListWidgetAccessor) element).getEntries();
for (EntryListEntryWidget entry : entries) {
Pair<Integer, Integer> slot = this.getSlotAt(entry, mouseX, mouseY, allowEmpty);
if (slot != null && slot != INVALID_SLOT)
return slot;
}
} else if (!(element instanceof ButtonWidget) && element instanceof WidgetWithBounds) {
for (Element child : ((WidgetWithBounds) element).children()) {
Pair<Integer, Integer> slot = this.getSlotAt(child, mouseX, mouseY, allowEmpty);
if (slot != null && slot != INVALID_SLOT)
return slot;
}
}
return null;
}
private static boolean isViewingScreen(Screen screen) {
return screen instanceof RecipeViewingScreen || screen instanceof VillagerRecipeViewingScreen;
}
@Override
public boolean handleMenuBack(@NotNull MinecraftClient client, @NotNull Screen screen) {
if (!isViewingScreen(screen))
return false;
MinecraftClient.getInstance().openScreen(REIHelper.getInstance().getPreviousContainerScreen());
ScreenHelper.getLastOverlay().init();
return true;
}
private static EntryListWidget getEntryListWidget() {
if (ENTRY_LIST_WIDGET == null) {
ENTRY_LIST_WIDGET = LambdaReflection.getFirstFieldOfType(ContainerScreenOverlay.class, EntryListWidget.class)
.map(field -> (EntryListWidget) LambdaReflection.getFieldValue(null, field))
.orElse(null);
}
return ENTRY_LIST_WIDGET;
}
private static @Nullable EntryStack getCurrentStack(@NotNull MinecraftClient client) {
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();
if (isViewingScreen(client.currentScreen)) {
for (Element element : client.currentScreen.children()) {
EntryStack stack = getCurrentStack(element, x, y);
if (stack != null)
return stack;
}
}
Optional<ContainerScreenOverlay> overlay = ScreenHelper.getOptionalOverlay();
if (!overlay.isPresent())
return RecipeHelper.getInstance().getScreenFocusedStack(client.currentScreen);
EntryListWidget widget = getEntryListWidget();
if (widget == null)
return RecipeHelper.getInstance().getScreenFocusedStack(client.currentScreen);
return getCurrentStack(widget, x, y);
}
private static @Nullable EntryStack getCurrentStack(@NotNull Element element, double mouseX, double mouseY) {
if (element instanceof EntryWidget) {
EntryWidget entry = (EntryWidget) element;
if (entry.containsMouse(mouseX, mouseY))
return ((EntryWidgetAccessor) entry).lambdacontrols_getCurrentEntry();
} else if (element instanceof EntryListWidget) {
List<EntryListEntryWidget> entries = ((EntryListWidgetAccessor) element).getEntries();
for (EntryListEntryWidget entry : entries) {
if (entry.containsMouse(mouseX, mouseY)) {
return ((EntryWidgetAccessor) entry).lambdacontrols_getCurrentEntry();
}
}
} else if (!(element instanceof ButtonWidget) && element instanceof WidgetWithBounds) {
for (Element child : ((WidgetWithBounds) element).children()) {
EntryStack stack = getCurrentStack(child, mouseX, mouseY);
if (stack != null)
return stack;
}
}
return null;
}
private static PressAction handleShowRecipeUsage(boolean usage) {
return (client, button, value, action) -> {
if (action.isUnpressed())
return false;
EntryStack stack = RecipeHelper.getInstance().getScreenFocusedStack(client.currentScreen);
if (stack == null) {
stack = getCurrentStack(client);
}
if (stack != null && !stack.isEmpty()) {
stack = stack.copy();
if (usage) {
return ClientHelper.getInstance().openView(ClientHelper.ViewSearchBuilder.builder().addUsagesFor(stack).setInputNotice(stack).fillPreferredOpenedCategory());
} else {
return ClientHelper.getInstance().openView(ClientHelper.ViewSearchBuilder.builder().addRecipesFor(stack).setOutputNotice(stack).fillPreferredOpenedCategory());
}
}
return false;
};
}
private static PressAction handlePage(boolean next) {
return (client, button, value, action) -> {
if (action == ButtonState.RELEASE)
return false;
Optional<ContainerScreenOverlay> overlay = ScreenHelper.getOptionalOverlay();
if (!overlay.isPresent())
return false;
EntryListWidget widget = getEntryListWidget();
if (widget == null)
return false;
if (next)
widget.nextPage();
else
widget.previousPage();
widget.updateEntriesPosition();
return true;
};
}
/**
* Returns the handler for category tabs buttons.
*
* @param next True if the action is to switch to the next tab.
* @return The handler.
*/
private static PressAction handleTab(boolean next) {
return (client, button, value, action) -> {
if (action != ButtonState.RELEASE)
return false;
if (client.currentScreen instanceof RecipeViewingScreen) {
RecipeViewingScreenAccessor screen = (RecipeViewingScreenAccessor) client.currentScreen;
if (next)
screen.getCategoryNext().onClick();
else
screen.getCategoryBack().onClick();
return true;
} else if (client.currentScreen instanceof VillagerRecipeViewingScreen) {
VillagerRecipeViewingScreenAccessor screen = (VillagerRecipeViewingScreenAccessor) client.currentScreen;
List<RecipeCategory<?>> categories = screen.getCategories();
int currentTab = screen.getSelectedCategoryIndex();
screen.setSelectedCategoryIndex(getNextIndex(currentTab, categories.size(), next));
screen.setSelectedRecipeIndex(0);
screen.lambdacontrols_init();
return true;
}
return false;
};
}
private static PressAction handleRecipe(boolean next) {
return (client, button, value, action) -> {
if (action.isUnpressed())
return false;
if (client.currentScreen instanceof RecipeViewingScreen) {
RecipeViewingScreenAccessor screen = (RecipeViewingScreenAccessor) client.currentScreen;
if (next)
screen.getRecipeNext().onClick();
else
screen.getRecipeBack().onClick();
return true;
} else if (client.currentScreen instanceof VillagerRecipeViewingScreen) {
VillagerRecipeViewingScreenAccessor screen = (VillagerRecipeViewingScreenAccessor) client.currentScreen;
List<RecipeCategory<?>> categories = screen.getCategories();
int currentTab = screen.getSelectedCategoryIndex();
List<RecipeDisplay> recipes = screen.getCategoryMap().get(categories.get(currentTab));
if (recipes.size() == 0)
return true;
int currentRecipe = screen.getSelectedRecipeIndex();
int nextRecipe = getNextIndex(currentRecipe, recipes.size(), next);
if (nextRecipe == 0) {
screen.getScrolling().scrollTo(0.0, true);
} else if (nextRecipe == recipes.size() - 1) {
screen.getScrolling().scrollTo(screen.getScrolling().getMaxScroll(), true);
} else {
double scrollAmount = screen.getScrolling().getMaxScroll() / (float) recipes.size();
screen.getScrolling().offset(next ? scrollAmount : -scrollAmount, true);
}
screen.setSelectedRecipeIndex(nextRecipe);
screen.lambdacontrols_init();
return true;
}
return false;
};
}
private static int getNextIndex(int currentIndex, int size, boolean next) {
int nextIndex = currentIndex + (next ? 1 : -1);
if (nextIndex < 0)
nextIndex = size - 1;
else if (nextIndex >= size)
nextIndex = 0;
return nextIndex;
}
}

View File

@@ -0,0 +1,30 @@
/*
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of LambdaControls.
*
* Licensed under the MIT license. For more information,
* see the LICENSE file.
*/
package dev.lambdaurora.lambdacontrols.client.compat.mixin;
import me.shedaniel.rei.gui.widget.EntryListEntryWidget;
import me.shedaniel.rei.gui.widget.EntryListWidget;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
import java.util.List;
/**
* Represents an accessor to REI's EntryListWidget.
*
* @author LambdAurora
* @version 1.5.0
* @since 1.5.0
*/
@Mixin(value = EntryListWidget.class, remap = false)
public interface EntryListWidgetAccessor {
@Accessor(value = "entries")
List<EntryListEntryWidget> getEntries();
}

View File

@@ -0,0 +1,28 @@
/*
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of LambdaControls.
*
* Licensed under the MIT license. For more information,
* see the LICENSE file.
*/
package dev.lambdaurora.lambdacontrols.client.compat.mixin;
import me.shedaniel.rei.api.EntryStack;
import me.shedaniel.rei.gui.widget.EntryWidget;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Invoker;
/**
* Represents an accessor to REI's EntryWidget.
*
* @author LambdAurora
* @version 1.5.0
* @since 1.5.0
*/
@Mixin(value = EntryWidget.class, remap = false)
public interface EntryWidgetAccessor {
@Invoker("getCurrentEntry")
EntryStack lambdacontrols_getCurrentEntry();
}

View File

@@ -0,0 +1,37 @@
/*
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of LambdaControls.
*
* Licensed under the MIT license. For more information,
* see the LICENSE file.
*/
package dev.lambdaurora.lambdacontrols.client.compat.mixin;
import me.shedaniel.rei.api.widgets.Button;
import me.shedaniel.rei.gui.RecipeViewingScreen;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
/**
* Represents an accessor to REI's RecipeViewingScreen.
*
* @author LambdAurora
* @version 1.5.0
* @since 1.2.0
*/
@Mixin(value = RecipeViewingScreen.class, remap = false)
public interface RecipeViewingScreenAccessor {
@Accessor("categoryBack")
Button getCategoryBack();
@Accessor("categoryNext")
Button getCategoryNext();
@Accessor("recipeBack")
Button getRecipeBack();
@Accessor("recipeNext")
Button getRecipeNext();
}

View File

@@ -0,0 +1,55 @@
/*
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of LambdaControls.
*
* Licensed under the MIT license. For more information,
* see the LICENSE file.
*/
package dev.lambdaurora.lambdacontrols.client.compat.mixin;
import me.shedaniel.clothconfig2.api.ScrollingContainer;
import me.shedaniel.rei.api.RecipeCategory;
import me.shedaniel.rei.api.RecipeDisplay;
import me.shedaniel.rei.gui.VillagerRecipeViewingScreen;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
import org.spongepowered.asm.mixin.gen.Invoker;
import java.util.List;
import java.util.Map;
/**
* Represents an accessor to REI's VillagerRecipeViewingScreen.
*
* @author LambdAurora
* @version 1.5.0
* @since 1.2.0
*/
@Mixin(VillagerRecipeViewingScreen.class)
public interface VillagerRecipeViewingScreenAccessor {
@Accessor(value = "categoryMap", remap = false)
Map<RecipeCategory<?>, List<RecipeDisplay>> getCategoryMap();
@Accessor(value = "categories", remap = false)
List<RecipeCategory<?>> getCategories();
@Accessor(value = "selectedCategoryIndex", remap = false)
int getSelectedCategoryIndex();
@Accessor(value = "selectedCategoryIndex", remap = false)
void setSelectedCategoryIndex(int selectedCategoryIndex);
@Accessor(value = "selectedRecipeIndex", remap = false)
int getSelectedRecipeIndex();
@Accessor(value = "selectedRecipeIndex", remap = false)
void setSelectedRecipeIndex(int selectedRecipeIndex);
@Accessor(value = "scrolling", remap = false)
ScrollingContainer getScrolling();
@Invoker("init")
void lambdacontrols_init();
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright © 2020 LambdAurora <aurora42lambda@gmail.com>
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of LambdaControls.
*
@@ -7,13 +7,14 @@
* see the LICENSE file.
*/
package me.lambdaurora.lambdacontrols.client.controller;
package dev.lambdaurora.lambdacontrols.client.controller;
import me.lambdaurora.lambdacontrols.client.ButtonState;
import dev.lambdaurora.lambdacontrols.client.ButtonState;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.options.GameOptions;
import net.minecraft.client.options.KeyBinding;
import net.minecraft.client.resource.language.I18n;
import net.minecraft.text.Text;
import net.minecraft.text.TranslatableText;
import org.aperlambda.lambdacommon.Identifier;
import org.aperlambda.lambdacommon.utils.Nameable;
import org.aperlambda.lambdacommon.utils.function.PairPredicate;
@@ -24,83 +25,85 @@ import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.stream.Collectors;
import static me.lambdaurora.lambdacontrols.client.controller.InputManager.registerDefaultCategory;
import static org.lwjgl.glfw.GLFW.*;
/**
* Represents a button binding.
*
* @author LambdAurora
* @version 1.1.0
* @version 1.5.0
* @since 1.0.0
*/
public class ButtonBinding implements Nameable
{
public class ButtonBinding implements Nameable {
public static final ButtonCategory MOVEMENT_CATEGORY;
public static final ButtonCategory GAMEPLAY_CATEGORY;
public static final ButtonCategory INVENTORY_CATEGORY;
public static final ButtonCategory MULTIPLAYER_CATEGORY;
public static final ButtonCategory MISC_CATEGORY;
public static final ButtonBinding ATTACK = new Builder("attack").buttons(axisAsButton(GLFW_GAMEPAD_AXIS_RIGHT_TRIGGER, true)).onlyInGame().register();
public static final ButtonBinding BACK = new Builder("back").buttons(axisAsButton(GLFW_GAMEPAD_AXIS_LEFT_Y, false)).onlyInGame().register();
public static final ButtonBinding CHAT = new Builder("chat").buttons(GLFW_GAMEPAD_BUTTON_DPAD_RIGHT).onlyInGame().cooldown(true).register();
public static final ButtonBinding DROP_ITEM = new Builder("drop_item").buttons(GLFW_GAMEPAD_BUTTON_B).onlyInGame().cooldown(true).register();
public static final ButtonBinding FORWARD = new Builder("forward").buttons(axisAsButton(GLFW_GAMEPAD_AXIS_LEFT_Y, true)).onlyInGame().register();
public static final ButtonBinding HOTBAR_LEFT = new Builder("hotbar_left").buttons(GLFW_GAMEPAD_BUTTON_LEFT_BUMPER)
.action(InputHandlers.handleHotbar(false)).onlyInGame().cooldown(true).register();
public static final ButtonBinding HOTBAR_RIGHT = new Builder("hotbar_right").buttons(GLFW_GAMEPAD_BUTTON_RIGHT_BUMPER)
.action(InputHandlers.handleHotbar(true)).onlyInGame().cooldown(true).register();
public static final ButtonBinding INVENTORY = new Builder("inventory").buttons(GLFW_GAMEPAD_BUTTON_Y).onlyInGame().cooldown(true).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)).onlyInGame().register();
public static final ButtonBinding PAUSE_GAME = new Builder("pause_game").buttons(GLFW_GAMEPAD_BUTTON_START).action(InputHandlers::handlePauseGame).cooldown(true).register();
public static final ButtonBinding PICK_BLOCK = new Builder("pick_block").buttons(GLFW_GAMEPAD_BUTTON_DPAD_LEFT).onlyInGame().cooldown(true).register();
public static final ButtonBinding PLAYER_LIST = new Builder("player_list").buttons(GLFW_GAMEPAD_BUTTON_BACK).onlyInGame().register();
public static final ButtonBinding RIGHT = new Builder("right").buttons(axisAsButton(GLFW_GAMEPAD_AXIS_LEFT_X, true)).register();
public static final ButtonBinding SCREENSHOT = new Builder("screenshot").buttons(GLFW_GAMEPAD_BUTTON_DPAD_UP, GLFW_GAMEPAD_BUTTON_A)
.action(InputHandlers::handleScreenshot).cooldown(true).register();
public static final ButtonBinding SLOT_DOWN = new Builder("slot_down").buttons(GLFW_GAMEPAD_BUTTON_DPAD_DOWN)
.action(InputHandlers.handleInventorySlotPad(1)).onlyInInventory().cooldown(true).register();
public static final ButtonBinding SLOT_LEFT = new Builder("slot_left").buttons(GLFW_GAMEPAD_BUTTON_DPAD_LEFT)
.action(InputHandlers.handleInventorySlotPad(3)).onlyInInventory().cooldown(true).register();
public static final ButtonBinding SLOT_RIGHT = new Builder("slot_right").buttons(GLFW_GAMEPAD_BUTTON_DPAD_RIGHT)
.action(InputHandlers.handleInventorySlotPad(2)).onlyInInventory().cooldown(true).register();
public static final ButtonBinding SLOT_UP = new Builder("slot_up").buttons(GLFW_GAMEPAD_BUTTON_DPAD_UP)
.action(InputHandlers.handleInventorySlotPad(0)).onlyInInventory().cooldown(true).register();
public static final ButtonBinding SMOOTH_CAMERA = new Builder("toggle_smooth_camera").cooldown(true).register();
public static final ButtonBinding SNEAK = new Builder("sneak").buttons(GLFW_GAMEPAD_BUTTON_RIGHT_THUMB)
.actions(PressAction.DEFAULT_ACTION, InputHandlers::handleToggleSneak).onlyInGame().cooldown(true).register();
public static final ButtonBinding SPRINT = new Builder("sprint").buttons(GLFW_GAMEPAD_BUTTON_LEFT_THUMB).register();
public static final ButtonBinding SWAP_HANDS = new Builder("swap_hands").buttons(GLFW_GAMEPAD_BUTTON_X).cooldown(true).register();
public static final ButtonBinding TAB_LEFT = new Builder("tab_left").buttons(GLFW_GAMEPAD_BUTTON_LEFT_BUMPER)
.action(InputHandlers.handleHotbar(false)).filter(Predicates.or(InputHandlers::inInventory, InputHandlers::inAdvancements)).cooldown(true).register();
public static final ButtonBinding TAB_RIGHT = new Builder("tab_right").buttons(GLFW_GAMEPAD_BUTTON_RIGHT_BUMPER)
.action(InputHandlers.handleHotbar(true)).filter(Predicates.or(InputHandlers::inInventory, InputHandlers::inAdvancements)).cooldown(true).register();
public static final ButtonBinding TOGGLE_PERSPECTIVE = new Builder("toggle_perspective").buttons(GLFW_GAMEPAD_BUTTON_DPAD_UP, GLFW_GAMEPAD_BUTTON_Y).cooldown(true).register();
public static final ButtonBinding USE = new Builder("use").buttons(axisAsButton(GLFW_GAMEPAD_AXIS_LEFT_TRIGGER, true)).register();
public static final ButtonBinding ATTACK = new Builder("attack").buttons(axisAsButton(GLFW_GAMEPAD_AXIS_RIGHT_TRIGGER, true)).onlyInGame().register();
public static final ButtonBinding BACK = new Builder("back").buttons(axisAsButton(GLFW_GAMEPAD_AXIS_LEFT_Y, false))
.action(MovementHandler.HANDLER).onlyInGame().register();
public static final ButtonBinding CHAT = new Builder("chat").buttons(GLFW_GAMEPAD_BUTTON_DPAD_RIGHT).onlyInGame().cooldown().register();
public static final ButtonBinding DROP_ITEM = new Builder("drop_item").buttons(GLFW_GAMEPAD_BUTTON_B).onlyInGame().cooldown().register();
public static final ButtonBinding FORWARD = new Builder("forward").buttons(axisAsButton(GLFW_GAMEPAD_AXIS_LEFT_Y, true))
.action(MovementHandler.HANDLER).onlyInGame().register();
public static final ButtonBinding HOTBAR_LEFT = new Builder("hotbar_left").buttons(GLFW_GAMEPAD_BUTTON_LEFT_BUMPER)
.action(InputHandlers.handleHotbar(false)).onlyInGame().cooldown().register();
public static final ButtonBinding HOTBAR_RIGHT = new Builder("hotbar_right").buttons(GLFW_GAMEPAD_BUTTON_RIGHT_BUMPER)
.action(InputHandlers.handleHotbar(true)).onlyInGame().cooldown().register();
public static final ButtonBinding INVENTORY = new Builder("inventory").buttons(GLFW_GAMEPAD_BUTTON_Y).onlyInGame().cooldown().register();
public static final ButtonBinding JUMP = new Builder("jump").buttons(GLFW_GAMEPAD_BUTTON_A).onlyInGame().register();
public static final ButtonBinding LEFT = new Builder("left").buttons(axisAsButton(GLFW_GAMEPAD_AXIS_LEFT_X, false))
.action(MovementHandler.HANDLER).onlyInGame().register();
public static final ButtonBinding PAUSE_GAME = new Builder("pause_game").buttons(GLFW_GAMEPAD_BUTTON_START).action(InputHandlers::handlePauseGame).cooldown().register();
public static final ButtonBinding PICK_BLOCK = new Builder("pick_block").buttons(GLFW_GAMEPAD_BUTTON_DPAD_LEFT).onlyInGame().cooldown().register();
public static final ButtonBinding PLAYER_LIST = new Builder("player_list").buttons(GLFW_GAMEPAD_BUTTON_BACK).onlyInGame().register();
public static final ButtonBinding RIGHT = new Builder("right").buttons(axisAsButton(GLFW_GAMEPAD_AXIS_LEFT_X, true))
.action(MovementHandler.HANDLER).onlyInGame().register();
public static final ButtonBinding SCREENSHOT = new Builder("screenshot").buttons(GLFW_GAMEPAD_BUTTON_DPAD_UP, GLFW_GAMEPAD_BUTTON_A)
.action(InputHandlers::handleScreenshot).cooldown().register();
public static final ButtonBinding SLOT_DOWN = new Builder("slot_down").buttons(GLFW_GAMEPAD_BUTTON_DPAD_DOWN)
.action(InputHandlers.handleInventorySlotPad(1)).onlyInInventory().cooldown().register();
public static final ButtonBinding SLOT_LEFT = new Builder("slot_left").buttons(GLFW_GAMEPAD_BUTTON_DPAD_LEFT)
.action(InputHandlers.handleInventorySlotPad(3)).onlyInInventory().cooldown().register();
public static final ButtonBinding SLOT_RIGHT = new Builder("slot_right").buttons(GLFW_GAMEPAD_BUTTON_DPAD_RIGHT)
.action(InputHandlers.handleInventorySlotPad(2)).onlyInInventory().cooldown().register();
public static final ButtonBinding SLOT_UP = new Builder("slot_up").buttons(GLFW_GAMEPAD_BUTTON_DPAD_UP)
.action(InputHandlers.handleInventorySlotPad(0)).onlyInInventory().cooldown().register();
public static final ButtonBinding 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 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)).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)).cooldown().register();
public static final ButtonBinding TOGGLE_PERSPECTIVE = new Builder("toggle_perspective").buttons(GLFW_GAMEPAD_BUTTON_DPAD_UP, GLFW_GAMEPAD_BUTTON_Y).cooldown().register();
public static final ButtonBinding USE = new Builder("use").buttons(axisAsButton(GLFW_GAMEPAD_AXIS_LEFT_TRIGGER, true)).register();
private int[] button;
private int[] defaultButton;
private String key;
private KeyBinding mcKeyBinding = null;
private int[] button;
private final int[] defaultButton;
private final String key;
private final Text text;
private KeyBinding mcKeyBinding = null;
protected PairPredicate<MinecraftClient, ButtonBinding> filter;
private List<PressAction> actions = new ArrayList<>(Collections.singletonList(PressAction.DEFAULT_ACTION));
private boolean hasCooldown;
private int cooldown = 0;
private List<PressAction> actions = new ArrayList<>(Collections.singletonList(PressAction.DEFAULT_ACTION));
private boolean hasCooldown;
private int cooldown = 0;
boolean pressed = false;
public ButtonBinding(@NotNull String key, int[] defaultButton, @NotNull List<PressAction> actions, PairPredicate<MinecraftClient, ButtonBinding> filter, boolean hasCooldown)
{
public ButtonBinding(@NotNull String key, int[] defaultButton, @NotNull List<PressAction> actions, PairPredicate<MinecraftClient, ButtonBinding> filter, boolean hasCooldown) {
this.setButton(this.defaultButton = defaultButton);
this.key = key;
this.text = new TranslatableText(this.key);
this.filter = filter;
this.actions.addAll(actions);
this.hasCooldown = hasCooldown;
}
public ButtonBinding(@NotNull String key, int[] defaultButton, boolean hasCooldown)
{
public ButtonBinding(@NotNull String key, int[] defaultButton, boolean hasCooldown) {
this(key, defaultButton, Collections.emptyList(), Predicates.pairAlwaysTrue(), hasCooldown);
}
@@ -109,8 +112,7 @@ public class ButtonBinding implements Nameable
*
* @return The bound button.
*/
public int[] getButton()
{
public int[] getButton() {
return this.button;
}
@@ -119,8 +121,7 @@ public class ButtonBinding implements Nameable
*
* @param button The bound button.
*/
public void setButton(int[] button)
{
public void setButton(int[] button) {
this.button = button;
if (InputManager.hasBinding(this))
@@ -133,8 +134,7 @@ public class ButtonBinding implements Nameable
* @param button The button to check.
* @return True if the bound button is the specified button, else false.
*/
public boolean isButton(int[] button)
{
public boolean isButton(int[] button) {
return InputManager.areButtonsEquivalent(button, this.button);
}
@@ -143,8 +143,7 @@ public class ButtonBinding implements Nameable
*
* @return True if the button is down, else false.
*/
public boolean isButtonDown()
{
public boolean isButtonDown() {
return this.pressed;
}
@@ -153,8 +152,7 @@ public class ButtonBinding implements Nameable
*
* @return True if this button binding is bound, else false.
*/
public boolean isNotBound()
{
public boolean isNotBound() {
return this.button.length == 0 || this.button[0] == -1;
}
@@ -163,8 +161,7 @@ public class ButtonBinding implements Nameable
*
* @return The default button.
*/
public int[] getDefaultButton()
{
public int[] getDefaultButton() {
return this.defaultButton;
}
@@ -173,8 +170,7 @@ public class ButtonBinding implements Nameable
*
* @return True if the assigned button is the default button, else false.
*/
public boolean isDefault()
{
public boolean isDefault() {
return this.button.length == this.defaultButton.length && InputManager.areButtonsEquivalent(this.button, this.defaultButton);
}
@@ -184,8 +180,7 @@ public class ButtonBinding implements Nameable
* @return The button code.
*/
public @NotNull
String getButtonCode()
{
String getButtonCode() {
return Arrays.stream(this.button)
.mapToObj(btn -> Integer.valueOf(btn).toString())
.collect(Collectors.joining("+"));
@@ -196,8 +191,7 @@ public class ButtonBinding implements Nameable
*
* @param keyBinding The optional key binding.
*/
public void setKeyBinding(@Nullable KeyBinding keyBinding)
{
public void setKeyBinding(@Nullable KeyBinding keyBinding) {
this.mcKeyBinding = keyBinding;
}
@@ -207,16 +201,14 @@ public class ButtonBinding implements Nameable
* @param client The client instance.
* @return True if the button binding is available, else false.
*/
public boolean isAvailable(@NotNull MinecraftClient client)
{
public boolean isAvailable(@NotNull MinecraftClient client) {
return this.filter.test(client, this);
}
/**
* Updates the button binding cooldown.
*/
public void update()
{
public void update() {
if (this.hasCooldown && this.cooldown > 0)
this.cooldown--;
}
@@ -225,25 +217,22 @@ public class ButtonBinding implements Nameable
* Handles the button binding.
*
* @param client The client instance.
* @param state The state.
* @param state The state.
*/
public void handle(@NotNull MinecraftClient client, @NotNull ButtonState state)
{
public void handle(@NotNull MinecraftClient client, float value, @NotNull ButtonState state) {
if (state == ButtonState.REPEAT && this.hasCooldown && this.cooldown != 0)
return;
if (this.hasCooldown && state.isPressed()) {
this.cooldown = 5;
}
for (int i = this.actions.size() - 1; i >= 0; i--) {
if (this.actions.get(i).press(client, this, state))
if (this.actions.get(i).press(client, this, value, state))
break;
}
}
@Override
public @NotNull String getName()
{
public @NotNull String getName() {
return this.key;
}
@@ -252,48 +241,63 @@ public class ButtonBinding implements Nameable
*
* @return The translation key.
*/
public @NotNull
String getTranslationKey()
{
public @NotNull String getTranslationKey() {
return "lambdacontrols.action." + this.getName();
}
public @NotNull Text getText() {
return this.text;
}
/**
* Returns the key binding equivalent of this button binding.
*
* @return The key binding equivalent.
*/
public @NotNull
Optional<KeyBinding> asKeyBinding()
{
public @NotNull Optional<KeyBinding> asKeyBinding() {
return Optional.ofNullable(this.mcKeyBinding);
}
@Override
public String toString() {
return "ButtonBinding{id=\"" + this.key + "\","
+ "hasCooldown=" + this.hasCooldown
+ "}";
}
/**
* Returns the specified axis as a button.
*
* @param axis The axis.
* @param axis The axis.
* @param positive True if the axis part is positive, else false.
* @return The axis as a button.
*/
public static int axisAsButton(int axis, boolean positive)
{
public static int axisAsButton(int axis, boolean positive) {
return positive ? 100 + axis : 200 + axis;
}
/**
* Returns whether the specified button is an axis or not.
*
* @param button The button.
* @return True if the button is an axis, else false.
*/
public static boolean isAxis(int button) {
button %= 500;
return button >= 100;
}
/**
* Returns the second Joycon's specified button code.
*
* @param button The raw button code.
* @return The second Joycon's button code.
*/
public static int controller2Button(int button)
{
public static int controller2Button(int button) {
return 500 + button;
}
public static void init(@NotNull GameOptions options)
{
public static void init(@NotNull GameOptions options) {
ATTACK.mcKeyBinding = options.keyAttack;
BACK.mcKeyBinding = options.keyBack;
CHAT.mcKeyBinding = options.keyChat;
@@ -320,69 +324,67 @@ public class ButtonBinding implements Nameable
* @param button The button.
* @return The localized name of the button.
*/
public static @NotNull
String getLocalizedButtonName(int button)
{
public static @NotNull Text getLocalizedButtonName(int button) {
switch (button % 500) {
case -1:
return I18n.translate("key.keyboard.unknown");
return new TranslatableText("key.keyboard.unknown");
case GLFW_GAMEPAD_BUTTON_A:
return I18n.translate("lambdacontrols.button.a");
return new TranslatableText("lambdacontrols.button.a");
case GLFW_GAMEPAD_BUTTON_B:
return I18n.translate("lambdacontrols.button.b");
return new TranslatableText("lambdacontrols.button.b");
case GLFW_GAMEPAD_BUTTON_X:
return I18n.translate("lambdacontrols.button.x");
return new TranslatableText("lambdacontrols.button.x");
case GLFW_GAMEPAD_BUTTON_Y:
return I18n.translate("lambdacontrols.button.y");
return new TranslatableText("lambdacontrols.button.y");
case GLFW_GAMEPAD_BUTTON_LEFT_BUMPER:
return I18n.translate("lambdacontrols.button.left_bumper");
return new TranslatableText("lambdacontrols.button.left_bumper");
case GLFW_GAMEPAD_BUTTON_RIGHT_BUMPER:
return I18n.translate("lambdacontrols.button.right_bumper");
return new TranslatableText("lambdacontrols.button.right_bumper");
case GLFW_GAMEPAD_BUTTON_BACK:
return I18n.translate("lambdacontrols.button.back");
return new TranslatableText("lambdacontrols.button.back");
case GLFW_GAMEPAD_BUTTON_START:
return I18n.translate("lambdacontrols.button.start");
return new TranslatableText("lambdacontrols.button.start");
case GLFW_GAMEPAD_BUTTON_GUIDE:
return I18n.translate("lambdacontrols.button.guide");
return new TranslatableText("lambdacontrols.button.guide");
case GLFW_GAMEPAD_BUTTON_LEFT_THUMB:
return I18n.translate("lambdacontrols.button.left_thumb");
return new TranslatableText("lambdacontrols.button.left_thumb");
case GLFW_GAMEPAD_BUTTON_RIGHT_THUMB:
return I18n.translate("lambdacontrols.button.right_thumb");
return new TranslatableText("lambdacontrols.button.right_thumb");
case GLFW_GAMEPAD_BUTTON_DPAD_UP:
return I18n.translate("lambdacontrols.button.dpad_up");
return new TranslatableText("lambdacontrols.button.dpad_up");
case GLFW_GAMEPAD_BUTTON_DPAD_RIGHT:
return I18n.translate("lambdacontrols.button.dpad_right");
return new TranslatableText("lambdacontrols.button.dpad_right");
case GLFW_GAMEPAD_BUTTON_DPAD_DOWN:
return I18n.translate("lambdacontrols.button.dpad_down");
return new TranslatableText("lambdacontrols.button.dpad_down");
case GLFW_GAMEPAD_BUTTON_DPAD_LEFT:
return I18n.translate("lambdacontrols.button.dpad_left");
return new TranslatableText("lambdacontrols.button.dpad_left");
case 100:
return I18n.translate("lambdacontrols.axis.left_x+");
return new TranslatableText("lambdacontrols.axis.left_x+");
case 101:
return I18n.translate("lambdacontrols.axis.left_y+");
return new TranslatableText("lambdacontrols.axis.left_y+");
case 102:
return I18n.translate("lambdacontrols.axis.right_x+");
return new TranslatableText("lambdacontrols.axis.right_x+");
case 103:
return I18n.translate("lambdacontrols.axis.right_y+");
return new TranslatableText("lambdacontrols.axis.right_y+");
case 104:
return I18n.translate("lambdacontrols.axis.left_trigger");
return new TranslatableText("lambdacontrols.axis.left_trigger");
case 105:
return I18n.translate("lambdacontrols.axis.right_trigger");
return new TranslatableText("lambdacontrols.axis.right_trigger");
case 200:
return I18n.translate("lambdacontrols.axis.left_x-");
return new TranslatableText("lambdacontrols.axis.left_x-");
case 201:
return I18n.translate("lambdacontrols.axis.left_y-");
return new TranslatableText("lambdacontrols.axis.left_y-");
case 202:
return I18n.translate("lambdacontrols.axis.right_x-");
return new TranslatableText("lambdacontrols.axis.right_x-");
case 203:
return I18n.translate("lambdacontrols.axis.right_y-");
return new TranslatableText("lambdacontrols.axis.right_y-");
default:
return I18n.translate("lambdacontrols.button.unknown", button);
return new TranslatableText("lambdacontrols.button.unknown", button);
}
}
static {
MOVEMENT_CATEGORY = registerDefaultCategory("key.categories.movement", category -> category.registerAllBindings(
MOVEMENT_CATEGORY = InputManager.registerDefaultCategory("key.categories.movement", category -> category.registerAllBindings(
ButtonBinding.FORWARD,
ButtonBinding.BACK,
ButtonBinding.LEFT,
@@ -390,62 +392,81 @@ public class ButtonBinding implements Nameable
ButtonBinding.JUMP,
ButtonBinding.SNEAK,
ButtonBinding.SPRINT));
GAMEPLAY_CATEGORY = registerDefaultCategory("key.categories.gameplay", category -> category.registerAllBindings(
GAMEPLAY_CATEGORY = InputManager.registerDefaultCategory("key.categories.gameplay", category -> category.registerAllBindings(
ButtonBinding.ATTACK,
ButtonBinding.PICK_BLOCK,
ButtonBinding.USE
));
INVENTORY_CATEGORY = registerDefaultCategory("key.categories.inventory", category -> category.registerAllBindings(
INVENTORY_CATEGORY = InputManager.registerDefaultCategory("key.categories.inventory", category -> category.registerAllBindings(
ButtonBinding.DROP_ITEM,
ButtonBinding.HOTBAR_LEFT,
ButtonBinding.HOTBAR_RIGHT,
ButtonBinding.INVENTORY,
ButtonBinding.SWAP_HANDS
));
MULTIPLAYER_CATEGORY = registerDefaultCategory("key.categories.multiplayer",
MULTIPLAYER_CATEGORY = InputManager.registerDefaultCategory("key.categories.multiplayer",
category -> category.registerAllBindings(ButtonBinding.CHAT, ButtonBinding.PLAYER_LIST));
MISC_CATEGORY = registerDefaultCategory("key.categories.misc", category -> category.registerAllBindings(
MISC_CATEGORY = InputManager.registerDefaultCategory("key.categories.misc", category -> category.registerAllBindings(
ButtonBinding.SCREENSHOT,
//SMOOTH_CAMERA,
ButtonBinding.TOGGLE_PERSPECTIVE
));
}
/**
* Returns a builder instance.
*
* @param identifier The identifier of the button binding.
* @return The builder instance
* @since 1.5.0
*/
public static Builder builder(@NotNull Identifier identifier) {
return new Builder(identifier);
}
/**
* Returns a builder instance.
*
* @param identifier The identifier of the button binding.
* @return The builder instance.
* @since 1.5.0
*/
public static Builder builder(@NotNull net.minecraft.util.Identifier identifier) {
return new Builder(identifier);
}
/**
* Represents a quick {@link ButtonBinding} builder.
*
* @author LambdAurora
* @version 1.1.0
* @version 1.5.0
* @since 1.1.0
*/
public static class Builder
{
private final String key;
private int[] buttons = new int[0];
private List<PressAction> actions = new ArrayList<>();
private PairPredicate<MinecraftClient, ButtonBinding> filter = Predicates.pairAlwaysTrue();
private boolean cooldown = false;
private ButtonCategory category = null;
private KeyBinding mcBinding = null;
public static class Builder {
private final String key;
private int[] buttons = new int[0];
private List<PressAction> actions = new ArrayList<>();
private PairPredicate<MinecraftClient, ButtonBinding> filter = Predicates.pairAlwaysTrue();
private boolean cooldown = false;
private ButtonCategory category = null;
private KeyBinding mcBinding = null;
/**
* This constructor shouldn't be used for other mods.
*
* @param key The key with format {@code "<namespace>.<name>"}.
*/
public Builder(@NotNull String key)
{
public Builder(@NotNull String key) {
this.key = key;
this.unbound();
}
public Builder(@NotNull Identifier identifier)
{
public Builder(@NotNull Identifier identifier) {
this(identifier.getNamespace() + "." + identifier.getName());
}
public Builder(@NotNull net.minecraft.util.Identifier identifier)
{
public Builder(@NotNull net.minecraft.util.Identifier identifier) {
this(new Identifier(identifier.toString()));
}
@@ -455,8 +476,7 @@ public class ButtonBinding implements Nameable
* @param buttons The default buttons.
* @return The builder instance.
*/
public Builder buttons(int... buttons)
{
public Builder buttons(int... buttons) {
this.buttons = buttons;
return this;
}
@@ -466,8 +486,7 @@ public class ButtonBinding implements Nameable
*
* @return The builder instance.
*/
public Builder unbound()
{
public Builder unbound() {
return this.buttons(-1);
}
@@ -477,8 +496,7 @@ public class ButtonBinding implements Nameable
* @param actions The actions to add.
* @return The builder instance.
*/
public Builder actions(@NotNull PressAction... actions)
{
public Builder actions(@NotNull PressAction... actions) {
this.actions.addAll(Arrays.asList(actions));
return this;
}
@@ -489,8 +507,7 @@ public class ButtonBinding implements Nameable
* @param action The action to add.
* @return The builder instance.
*/
public Builder action(@NotNull PressAction action)
{
public Builder action(@NotNull PressAction action) {
this.actions.add(action);
return this;
}
@@ -501,8 +518,7 @@ public class ButtonBinding implements Nameable
* @param filter The filter.
* @return The builder instance.
*/
public Builder filter(@NotNull PairPredicate<MinecraftClient, ButtonBinding> filter)
{
public Builder filter(@NotNull PairPredicate<MinecraftClient, ButtonBinding> filter) {
this.filter = filter;
return this;
}
@@ -514,8 +530,7 @@ public class ButtonBinding implements Nameable
* @see #filter(PairPredicate)
* @see InputHandlers#inGame(MinecraftClient, ButtonBinding)
*/
public Builder onlyInGame()
{
public Builder onlyInGame() {
return this.filter(InputHandlers::inGame);
}
@@ -526,8 +541,7 @@ public class ButtonBinding implements Nameable
* @see #filter(PairPredicate)
* @see InputHandlers#inInventory(MinecraftClient, ButtonBinding)
*/
public Builder onlyInInventory()
{
public Builder onlyInInventory() {
return this.filter(InputHandlers::inInventory);
}
@@ -537,20 +551,28 @@ public class ButtonBinding implements Nameable
* @param cooldown True if the {@link ButtonBinding} has a cooldown, else false.
* @return The builder instance.
*/
public Builder cooldown(boolean cooldown)
{
public Builder cooldown(boolean cooldown) {
this.cooldown = cooldown;
return this;
}
/**
* Puts a cooldown on the {@link ButtonBinding}.
*
* @return The builder instance.
* @since 1.5.0
*/
public Builder cooldown() {
return this.cooldown(true);
}
/**
* Sets the category of the {@link ButtonBinding}.
*
* @param category The category.
* @return The builder instance.
*/
public Builder category(@Nullable ButtonCategory category)
{
public Builder category(@Nullable ButtonCategory category) {
this.category = category;
return this;
}
@@ -561,8 +583,7 @@ public class ButtonBinding implements Nameable
* @param binding The keybinding to link.
* @return The builder instance.
*/
public Builder linkKeybind(@Nullable KeyBinding binding)
{
public Builder linkKeybind(@Nullable KeyBinding binding) {
this.mcBinding = binding;
return this;
}
@@ -572,8 +593,7 @@ public class ButtonBinding implements Nameable
*
* @return The built {@link ButtonBinding}.
*/
public ButtonBinding build()
{
public ButtonBinding build() {
ButtonBinding binding = new ButtonBinding(this.key, this.buttons, this.actions, this.filter, this.cooldown);
if (this.category != null)
this.category.registerBinding(binding);
@@ -588,8 +608,7 @@ public class ButtonBinding implements Nameable
* @return The built {@link ButtonBinding}.
* @see #build()
*/
public ButtonBinding register()
{
public ButtonBinding register() {
return InputManager.registerBinding(this.build());
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright © 2020 LambdAurora <aurora42lambda@gmail.com>
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of LambdaControls.
*
@@ -7,7 +7,7 @@
* see the LICENSE file.
*/
package me.lambdaurora.lambdacontrols.client.controller;
package dev.lambdaurora.lambdacontrols.client.controller;
import net.minecraft.client.resource.language.I18n;
import org.aperlambda.lambdacommon.Identifier;
@@ -26,37 +26,31 @@ import java.util.List;
* @version 1.1.0
* @since 1.1.0
*/
public class ButtonCategory implements Identifiable
{
public class ButtonCategory implements Identifiable {
private final List<ButtonBinding> bindings = new ArrayList<>();
private final Identifier id;
private int priority;
private final Identifier id;
private int priority;
public ButtonCategory(@NotNull Identifier id, int priority)
{
public ButtonCategory(@NotNull Identifier id, int priority) {
this.id = id;
this.priority = priority;
}
public ButtonCategory(@NotNull Identifier id)
{
public ButtonCategory(@NotNull Identifier id) {
this(id, 100);
}
public void registerBinding(@NotNull ButtonBinding binding)
{
public void registerBinding(@NotNull ButtonBinding binding) {
if (this.bindings.contains(binding))
throw new IllegalStateException("Cannot register twice a button binding in the same category.");
this.bindings.add(binding);
}
public void registerAllBindings(@NotNull ButtonBinding... bindings)
{
public void registerAllBindings(@NotNull ButtonBinding... bindings) {
this.registerAllBindings(Arrays.asList(bindings));
}
public void registerAllBindings(@NotNull List<ButtonBinding> bindings)
{
public void registerAllBindings(@NotNull List<ButtonBinding> bindings) {
bindings.forEach(this::registerBinding);
}
@@ -65,8 +59,7 @@ public class ButtonCategory implements Identifiable
*
* @return The bindings assigned to this category.
*/
public @NotNull List<ButtonBinding> getBindings()
{
public @NotNull List<ButtonBinding> getBindings() {
return Collections.unmodifiableList(this.bindings);
}
@@ -77,8 +70,7 @@ public class ButtonCategory implements Identifiable
*
* @return The translated name.
*/
public @NotNull String getTranslatedName()
{
public @NotNull String getTranslatedName() {
if (this.id.getNamespace().equals("minecraft"))
return I18n.translate(this.id.getName());
else
@@ -91,14 +83,12 @@ public class ButtonCategory implements Identifiable
*
* @return The priority of this category.
*/
public int getPriority()
{
public int getPriority() {
return this.priority;
}
@Override
public @NotNull Identifier getIdentifier()
{
public @NotNull Identifier getIdentifier() {
return this.id;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright © 2020 LambdAurora <aurora42lambda@gmail.com>
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of LambdaControls.
*
@@ -7,15 +7,22 @@
* see the LICENSE file.
*/
package me.lambdaurora.lambdacontrols.client.controller;
package dev.lambdaurora.lambdacontrols.client.controller;
import me.lambdaurora.lambdacontrols.client.LambdaControlsClient;
import dev.lambdaurora.lambdacontrols.LambdaControls;
import dev.lambdaurora.lambdacontrols.client.LambdaControlsClient;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.toast.SystemToast;
import net.minecraft.text.LiteralText;
import net.minecraft.text.TranslatableText;
import org.aperlambda.lambdacommon.utils.Nameable;
import org.jetbrains.annotations.NotNull;
import org.lwjgl.PointerBuffer;
import org.lwjgl.glfw.GLFW;
import org.lwjgl.glfw.GLFWGamepadState;
import org.lwjgl.system.MemoryStack;
import org.lwjgl.system.MemoryUtil;
import java.io.File;
import java.io.IOException;
import java.nio.Buffer;
import java.nio.ByteBuffer;
@@ -34,16 +41,14 @@ import static org.lwjgl.BufferUtils.createByteBuffer;
* Represents a controller.
*
* @author LambdAurora
* @version 1.0.0
* @version 1.4.3
* @since 1.0.0
*/
public class Controller implements Nameable
{
public class Controller implements Nameable {
private static final Map<Integer, Controller> CONTROLLERS = new HashMap<>();
private final int id;
private final int id;
public Controller(int id)
{
public Controller(int id) {
this.id = id;
}
@@ -52,8 +57,7 @@ public class Controller implements Nameable
*
* @return The identifier of this controller.
*/
public int getId()
{
public int getId() {
return this.id;
}
@@ -62,8 +66,7 @@ public class Controller implements Nameable
*
* @return The controller's GUID.
*/
public String getGuid()
{
public String getGuid() {
String guid = GLFW.glfwGetJoystickGUID(this.id);
return guid == null ? "" : guid;
}
@@ -73,8 +76,7 @@ public class Controller implements Nameable
*
* @return True if this controller is connected, else false.
*/
public boolean isConnected()
{
public boolean isConnected() {
return GLFW.glfwJoystickPresent(this.id);
}
@@ -83,8 +85,7 @@ public class Controller implements Nameable
*
* @return True if this controller is a gamepad, else false.
*/
public boolean isGamepad()
{
public boolean isGamepad() {
return this.isConnected() && GLFW.glfwJoystickIsGamepad(this.id);
}
@@ -94,8 +95,7 @@ public class Controller implements Nameable
* @return The controller's name.
*/
@Override
public @NotNull String getName()
{
public @NotNull String getName() {
String name = this.isGamepad() ? GLFW.glfwGetGamepadName(this.id) : GLFW.glfwGetJoystickName(this.id);
return name == null ? String.valueOf(this.getId()) : name;
}
@@ -105,16 +105,14 @@ public class Controller implements Nameable
*
* @return The state of the controller input.
*/
public GLFWGamepadState getState()
{
public GLFWGamepadState getState() {
GLFWGamepadState state = GLFWGamepadState.create();
if (this.isGamepad())
GLFW.glfwGetGamepadState(this.id, state);
return state;
}
public static @NotNull Controller byId(int id)
{
public static @NotNull Controller byId(int id) {
if (id > GLFW.GLFW_JOYSTICK_LAST) {
LambdaControlsClient.get().log("Controller '" + id + "' doesn't exist.");
id = GLFW.GLFW_JOYSTICK_LAST;
@@ -129,8 +127,7 @@ public class Controller implements Nameable
}
}
public static @NotNull Optional<Controller> byGuid(@NotNull String guid)
{
public static @NotNull Optional<Controller> byGuid(@NotNull String guid) {
return CONTROLLERS.values().stream().filter(Controller::isConnected)
.filter(controller -> controller.getGuid().equals(guid))
.max(Comparator.comparingInt(Controller::getId));
@@ -139,13 +136,12 @@ public class Controller implements Nameable
/**
* Reads the specified resource and returns the raw data as a ByteBuffer.
*
* @param resource The resource to read.
* @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, int bufferSize) throws IOException {
ByteBuffer buffer = null;
Path path = Paths.get(resource);
@@ -164,17 +160,49 @@ public class Controller implements Nameable
/**
* Updates the controller mappings.
*/
public static void updateMappings()
{
public static void updateMappings() {
try {
File mappingsFile = new File("config/gamecontrollerdb.txt");
if (!mappingsFile.exists())
if (!LambdaControlsClient.MAPPINGS_FILE.exists())
return;
LambdaControlsClient.get().log("Updating controller mappings...");
ByteBuffer buffer = ioResourceToBuffer(mappingsFile.getPath(), 1024);
ByteBuffer buffer = ioResourceToBuffer(LambdaControlsClient.MAPPINGS_FILE.getPath(), 1024);
GLFW.glfwUpdateGamepadMappings(buffer);
} catch (IOException e) {
e.printStackTrace();
}
MemoryStack memoryStack = MemoryStack.stackPush();
try {
PointerBuffer pointerBuffer = memoryStack.mallocPointer(1);
int i = GLFW.glfwGetError(pointerBuffer);
if (i != 0) {
long l = pointerBuffer.get();
String string = l == 0L ? "" : MemoryUtil.memUTF8(l);
MinecraftClient client = MinecraftClient.getInstance();
if (client != null) {
client.getToastManager().add(SystemToast.create(client, SystemToast.Type.TUTORIAL_HINT,
new TranslatableText("lambdacontrols.controller.mappings.error"), new LiteralText(string)));
}
}
} catch (Throwable e) {
/* Ignored :concern: */
} finally {
memoryStack.close();
}
if (LambdaControlsClient.get().config.hasDebug()) {
for (int i = GLFW.GLFW_JOYSTICK_1; i <= GLFW.GLFW_JOYSTICK_16; i++) {
Controller controller = byId(i);
if (!controller.isConnected())
continue;
LambdaControls.get().log(String.format("Controller #%d name: \"%s\"\n GUID: %s\n Gamepad: %s",
controller.id,
controller.getName(),
controller.getGuid(),
controller.isGamepad()));
}
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright © 2020 LambdAurora <aurora42lambda@gmail.com>
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of LambdaControls.
*
@@ -7,21 +7,24 @@
* see the LICENSE file.
*/
package me.lambdaurora.lambdacontrols.client.controller;
package dev.lambdaurora.lambdacontrols.client.controller;
import me.lambdaurora.lambdacontrols.client.ButtonState;
import me.lambdaurora.lambdacontrols.client.mixin.AdvancementsScreenAccessor;
import me.lambdaurora.lambdacontrols.client.mixin.CreativeInventoryScreenAccessor;
import me.lambdaurora.lambdacontrols.client.util.ContainerScreenAccessor;
import me.lambdaurora.lambdacontrols.client.util.KeyBindingAccessor;
import dev.lambdaurora.lambdacontrols.client.ButtonState;
import dev.lambdaurora.lambdacontrols.client.LambdaInput;
import dev.lambdaurora.lambdacontrols.client.mixin.AdvancementsScreenAccessor;
import dev.lambdaurora.lambdacontrols.client.mixin.CreativeInventoryScreenAccessor;
import dev.lambdaurora.lambdacontrols.client.mixin.RecipeBookWidgetAccessor;
import dev.lambdaurora.lambdacontrols.client.util.HandledScreenAccessor;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gui.screen.advancement.AdvancementTab;
import net.minecraft.client.gui.screen.advancement.AdvancementsScreen;
import net.minecraft.client.gui.screen.ingame.ContainerScreen;
import net.minecraft.client.gui.screen.ingame.CreativeInventoryScreen;
import net.minecraft.client.gui.screen.ingame.HandledScreen;
import net.minecraft.client.gui.screen.ingame.InventoryScreen;
import net.minecraft.client.gui.screen.recipebook.RecipeGroupButtonWidget;
import net.minecraft.client.util.ScreenshotUtils;
import net.minecraft.container.Slot;
import net.minecraft.item.ItemGroup;
import net.minecraft.screen.slot.Slot;
import org.aperlambda.lambdacommon.utils.Pair;
import org.jetbrains.annotations.NotNull;
@@ -35,66 +38,81 @@ import java.util.stream.Collectors;
* Represents some input handlers.
*
* @author LambdAurora
* @version 1.1.0
* @version 1.4.3
* @since 1.1.0
*/
public class InputHandlers
{
private InputHandlers()
{
public class InputHandlers {
private InputHandlers() {
}
public static PressAction handleHotbar(boolean right)
{
return (client, button, action) -> {
public static PressAction handleHotbar(boolean next) {
return (client, button, value, action) -> {
if (action == ButtonState.RELEASE)
return false;
// When ingame
if (client.currentScreen == null && client.player != null) {
if (right)
if (next)
client.player.inventory.selectedSlot = client.player.inventory.selectedSlot == 8 ? 0 : client.player.inventory.selectedSlot + 1;
else
client.player.inventory.selectedSlot = client.player.inventory.selectedSlot == 0 ? 8 : client.player.inventory.selectedSlot - 1;
return true;
} else if (client.currentScreen instanceof CreativeInventoryScreen) {
CreativeInventoryScreenAccessor inventory = (CreativeInventoryScreenAccessor) client.currentScreen;
int currentSelectedTab = inventory.lambdacontrols_getSelectedTab();
int nextTab = currentSelectedTab + (right ? 1 : -1);
int currentTab = inventory.getSelectedTab();
int nextTab = currentTab + (next ? 1 : -1);
if (nextTab < 0)
nextTab = ItemGroup.GROUPS.length - 1;
else if (nextTab >= ItemGroup.GROUPS.length)
nextTab = 0;
inventory.lambdacontrols_setSelectedTab(ItemGroup.GROUPS[nextTab]);
inventory.lambdacontrols$setSelectedTab(ItemGroup.GROUPS[nextTab]);
return true;
} else if (client.currentScreen instanceof InventoryScreen) {
RecipeBookWidgetAccessor recipeBook = (RecipeBookWidgetAccessor) ((InventoryScreen) client.currentScreen).getRecipeBookWidget();
List<RecipeGroupButtonWidget> tabs = recipeBook.getTabButtons();
RecipeGroupButtonWidget currentTab = recipeBook.getCurrentTab();
if (currentTab == null)
return false;
int nextTab = tabs.indexOf(currentTab) + (next ? 1 : -1);
if (nextTab < 0)
nextTab = tabs.size() - 1;
else if (nextTab >= tabs.size())
nextTab = 0;
currentTab.setToggled(false);
recipeBook.setCurrentTab(currentTab = tabs.get(nextTab));
currentTab.setToggled(true);
recipeBook.lambdacontrols$refreshResults(true);
return true;
} else if (client.currentScreen instanceof AdvancementsScreen) {
AdvancementsScreenAccessor screen = (AdvancementsScreenAccessor) client.currentScreen;
List<AdvancementTab> tabs = screen.lambdacontrols_getTabs().values().stream().distinct().collect(Collectors.toList());
AdvancementTab tab = screen.lambdacontrols_getSelectedTab();
List<AdvancementTab> tabs = screen.getTabs().values().stream().distinct().collect(Collectors.toList());
AdvancementTab 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 + (right ? 1 : -1);
int nextTab = i + (next ? 1 : -1);
if (nextTab < 0)
nextTab = tabs.size() - 1;
else if (nextTab >= tabs.size())
nextTab = 0;
screen.lambdacontrols_getAdvancementManager().selectTab(tabs.get(nextTab).getRoot(), true);
screen.getAdvancementManager().selectTab(tabs.get(nextTab).getRoot(), true);
break;
}
}
return true;
}
return false;
};
}
public static boolean handlePauseGame(@NotNull MinecraftClient client, @NotNull ButtonBinding binding, @NotNull ButtonState action)
{
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.openPauseMenu(false);
else if (client.currentScreen instanceof ContainerScreen && client.player != null) // If the current screen is a container then close it.
client.player.closeContainer();
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.onClose();
}
@@ -104,56 +122,57 @@ public class InputHandlers
/**
* Handles the screenshot action.
*
* @param client The client instance.
* @param client The client instance.
* @param binding The binding which fired the action.
* @param action The action done on the binding.
* @param action The action done on the binding.
* @return True if handled, else false.
*/
public static boolean handleScreenshot(@NotNull MinecraftClient client, @NotNull ButtonBinding binding, @NotNull ButtonState action)
{
if (action == ButtonState.PRESS)
public static boolean handleScreenshot(@NotNull MinecraftClient client, @NotNull ButtonBinding binding, float value, @NotNull ButtonState action) {
if (action == ButtonState.RELEASE)
ScreenshotUtils.saveScreenshot(client.runDirectory, client.getWindow().getFramebufferWidth(), client.getWindow().getFramebufferHeight(), client.getFramebuffer(),
text -> client.execute(() -> client.inGameHud.getChatHud().addMessage(text)));
return true;
}
public static boolean handleToggleSneak(@NotNull MinecraftClient client, @NotNull ButtonBinding button, @NotNull ButtonState action)
{
if (client.player != null && !client.player.abilities.flying) {
button.asKeyBinding().filter(binding -> action == ButtonState.PRESS).ifPresent(binding -> ((KeyBindingAccessor) binding).lambdacontrols_handlePressState(!binding.isPressed()));
return true;
}
return false;
public static boolean handleToggleSneak(@NotNull MinecraftClient client, @NotNull ButtonBinding button, float value, @NotNull ButtonState action) {
button.asKeyBinding().ifPresent(binding -> {
boolean sneakToggled = client.options.sneakToggled;
if (client.player.abilities.flying && sneakToggled)
client.options.sneakToggled = false;
binding.setPressed(button.pressed);
if (client.player.abilities.flying && sneakToggled)
client.options.sneakToggled = true;
});
return true;
}
public static PressAction handleInventorySlotPad(int direction)
{
return (client, binding, action) -> {
if (!(client.currentScreen instanceof ContainerScreen && action != ButtonState.RELEASE))
public static PressAction handleInventorySlotPad(int direction) {
return (client, binding, value, action) -> {
if (!(client.currentScreen instanceof HandledScreen && action != ButtonState.RELEASE))
return false;
ContainerScreen inventory = (ContainerScreen) client.currentScreen;
ContainerScreenAccessor accessor = (ContainerScreenAccessor) inventory;
int guiLeft = accessor.lambdacontrols_getX();
int guiTop = accessor.lambdacontrols_getY();
HandledScreen inventory = (HandledScreen) client.currentScreen;
HandledScreenAccessor accessor = (HandledScreenAccessor) inventory;
int guiLeft = accessor.getX();
int guiTop = accessor.getY();
double mouseX = client.mouse.getX() * (double) client.getWindow().getScaledWidth() / (double) client.getWindow().getWidth();
double mouseY = client.mouse.getY() * (double) client.getWindow().getScaledHeight() / (double) client.getWindow().getHeight();
// Finds the hovered slot.
Slot mouseSlot = accessor.lambdacontrols_getSlotAt(mouseX, mouseY);
Slot mouseSlot = accessor.lambdacontrols$getSlotAt(mouseX, mouseY);
// Finds the closest slot in the GUI within 14 pixels.
Optional<Slot> closestSlot = inventory.getContainer().slots.parallelStream()
Optional<Slot> closestSlot = inventory.getScreenHandler().slots.parallelStream()
.filter(Predicate.isEqual(mouseSlot).negate())
.map(slot -> {
int posX = guiLeft + slot.xPosition + 8;
int posY = guiTop + slot.yPosition + 8;
int posX = guiLeft + slot.x + 8;
int posY = guiTop + slot.y + 8;
int otherPosX = (int) mouseX;
int otherPosY = (int) mouseY;
if (mouseSlot != null) {
otherPosX = guiLeft + mouseSlot.xPosition + 8;
otherPosY = guiTop + mouseSlot.yPosition + 8;
otherPosX = guiLeft + mouseSlot.x + 8;
otherPosY = guiTop + mouseSlot.y + 8;
}
// Distance between the slot and the cursor.
@@ -161,13 +180,13 @@ public class InputHandlers
return Pair.of(slot, distance);
}).filter(entry -> {
Slot slot = entry.key;
int posX = guiLeft + slot.xPosition + 8;
int posY = guiTop + slot.yPosition + 8;
int posX = guiLeft + slot.x + 8;
int posY = guiTop + slot.y + 8;
int otherPosX = (int) mouseX;
int otherPosY = (int) mouseY;
if (mouseSlot != null) {
otherPosX = guiLeft + mouseSlot.xPosition + 8;
otherPosY = guiTop + mouseSlot.yPosition + 8;
otherPosX = guiLeft + mouseSlot.x + 8;
otherPosY = guiTop + mouseSlot.y + 8;
}
if (direction == 0)
return posY < otherPosY;
@@ -185,8 +204,8 @@ public class InputHandlers
if (closestSlot.isPresent()) {
Slot slot = closestSlot.get();
int x = guiLeft + slot.xPosition + 8;
int y = guiTop + slot.yPosition + 8;
int x = guiLeft + slot.x + 8;
int y = guiTop + slot.y + 8;
InputManager.queueMousePosition(x * (double) client.getWindow().getWidth() / (double) client.getWindow().getScaledWidth(),
y * (double) client.getWindow().getHeight() / (double) client.getWindow().getScaledHeight());
return true;
@@ -198,48 +217,57 @@ public class InputHandlers
/**
* Returns always true to the filter.
*
* @param client The client instance.
* @param client The client instance.
* @param binding The affected binding.
* @return True.
*/
public static boolean always(@NotNull MinecraftClient client, @NotNull ButtonBinding binding)
{
public static boolean always(@NotNull MinecraftClient client, @NotNull ButtonBinding binding) {
return true;
}
/**
* Returns whether the client is in game or not.
*
* @param client The client instance.
* @param client The client instance.
* @param binding The affected binding.
* @return True if the client is in game, else false.
*/
public static boolean inGame(@NotNull MinecraftClient client, @NotNull ButtonBinding binding)
{
public static boolean inGame(@NotNull MinecraftClient client, @NotNull ButtonBinding binding) {
return client.currentScreen == null;
}
/**
* Returns whether the client is in a non-interactive screen (which means require mouse input) or not.
*
* @param client The client instance.
* @param binding The affected binding.
* @return True if the client is in a non-interactive screen, else false.
*/
public static boolean inNonInteractiveScreens(@NotNull MinecraftClient client, @NotNull ButtonBinding binding) {
if (client.currentScreen == null)
return false;
return !LambdaInput.isScreenInteractive(client.currentScreen);
}
/**
* Returns whether the client is in an inventory or not.
*
* @param client The client instance.
* @param client The client instance.
* @param binding The affected binding.
* @return True if the client is in an inventory, else false.
*/
public static boolean inInventory(@NotNull MinecraftClient client, @NotNull ButtonBinding binding)
{
return client.currentScreen instanceof ContainerScreen;
public static boolean inInventory(@NotNull MinecraftClient client, @NotNull ButtonBinding binding) {
return client.currentScreen instanceof HandledScreen;
}
/**
* Returns whether the client is in the advancements screen or not.
*
* @param client The client instance.
* @param client The client instance.
* @param binding The affected binding.
* @return True if the client is in the advancements screen, else false.
*/
public static boolean inAdvancements(@NotNull MinecraftClient client, @NotNull ButtonBinding binding)
{
public static boolean inAdvancements(@NotNull MinecraftClient client, @NotNull ButtonBinding binding) {
return client.currentScreen instanceof AdvancementsScreen;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright © 2020 LambdAurora <aurora42lambda@gmail.com>
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of LambdaControls.
*
@@ -7,16 +7,19 @@
* see the LICENSE file.
*/
package me.lambdaurora.lambdacontrols.client.controller;
package dev.lambdaurora.lambdacontrols.client.controller;
import me.lambdaurora.lambdacontrols.ControlsMode;
import me.lambdaurora.lambdacontrols.client.ButtonState;
import me.lambdaurora.lambdacontrols.client.LambdaControlsClient;
import me.lambdaurora.lambdacontrols.client.LambdaControlsConfig;
import me.lambdaurora.lambdacontrols.client.util.MouseAccessor;
import dev.lambdaurora.lambdacontrols.ControlsMode;
import dev.lambdaurora.lambdacontrols.client.ButtonState;
import dev.lambdaurora.lambdacontrols.client.LambdaControlsClient;
import dev.lambdaurora.lambdacontrols.client.LambdaControlsConfig;
import dev.lambdaurora.lambdacontrols.client.util.MouseAccessor;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.options.KeyBinding;
import net.minecraft.client.util.InputUtil;
import net.minecraft.util.math.MathHelper;
import org.aperlambda.lambdacommon.Identifier;
import org.aperlambda.lambdacommon.utils.Pair;
import org.aperlambda.lambdacommon.utils.function.PairPredicate;
import org.jetbrains.annotations.NotNull;
import org.lwjgl.glfw.GLFW;
@@ -30,33 +33,30 @@ import java.util.stream.Stream;
* Represents an input manager for controllers.
*
* @author LambdAurora
* @version 1.1.0
* @version 1.4.0
* @since 1.1.0
*/
public class InputManager
{
public static final InputManager INPUT_MANAGER = new InputManager();
private static final List<ButtonBinding> BINDINGS = new ArrayList<>();
private static final List<ButtonCategory> CATEGORIES = new ArrayList<>();
public static final Map<Integer, ButtonState> STATES = new HashMap<>();
private int prevTargetMouseX = 0;
private int prevTargetMouseY = 0;
private int targetMouseX = 0;
private int targetMouseY = 0;
public class InputManager {
public static final InputManager INPUT_MANAGER = new InputManager();
private static final List<ButtonBinding> BINDINGS = new ArrayList<>();
private static final List<ButtonCategory> CATEGORIES = new ArrayList<>();
public static final Map<Integer, ButtonState> STATES = new HashMap<>();
public static final Map<Integer, Float> BUTTON_VALUES = new HashMap<>();
private int prevTargetMouseX = 0;
private int prevTargetMouseY = 0;
private int targetMouseX = 0;
private int targetMouseY = 0;
protected InputManager()
{
protected InputManager() {
}
public void tick(@NotNull MinecraftClient client)
{
public void tick(@NotNull MinecraftClient client) {
if (LambdaControlsClient.get().config.getControlsMode() == ControlsMode.CONTROLLER) {
this.controllerTick(client);
}
}
public void controllerTick(@NotNull MinecraftClient client)
{
public void controllerTick(@NotNull MinecraftClient client) {
this.prevTargetMouseX = this.targetMouseX;
this.prevTargetMouseY = this.targetMouseY;
}
@@ -66,31 +66,29 @@ public class InputManager
*
* @param client The client instance.
*/
public void updateMousePosition(@NotNull MinecraftClient client)
{
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;
GLFW.glfwSetCursorPos(client.getWindow().getHandle(), mouseX, mouseY);
((MouseAccessor) client.mouse).lambdacontrols_onCursorPos(client.getWindow().getHandle(), mouseX, mouseY);
if (!LambdaControlsClient.get().config.hasVirtualMouse())
GLFW.glfwSetCursorPos(client.getWindow().getHandle(), mouseX, mouseY);
((MouseAccessor) client.mouse).lambdacontrols$onCursorPos(client.getWindow().getHandle(), mouseX, mouseY);
}
}
/**
* Resets the mouse position.
*
* @param windowWidth The window width.
* @param windowWidth The window width.
* @param windowHeight The window height.
*/
public void resetMousePosition(int windowWidth, int windowHeight)
{
public void resetMousePosition(int windowWidth, int windowHeight) {
this.targetMouseX = this.prevTargetMouseX = (int) (windowWidth / 2.F);
this.targetMouseY = this.prevTargetMouseY = (int) (windowHeight / 2.F);
}
public void resetMouseTarget(@NotNull MinecraftClient client)
{
public void resetMouseTarget(@NotNull MinecraftClient client) {
double mouseX = client.mouse.getX();
double mouseY = client.mouse.getY();
this.prevTargetMouseX = this.targetMouseX = (int) mouseX;
@@ -103,8 +101,7 @@ public class InputManager
* @param binding The binding to check.
* @return True if the binding is registered, else false.
*/
public static boolean hasBinding(@NotNull ButtonBinding binding)
{
public static boolean hasBinding(@NotNull ButtonBinding binding) {
return BINDINGS.contains(binding);
}
@@ -114,8 +111,7 @@ public class InputManager
* @param name The name of the binding to check.
* @return True if the binding is registered, else false.
*/
public static boolean hasBinding(@NotNull String name)
{
public static boolean hasBinding(@NotNull String name) {
return BINDINGS.parallelStream().map(ButtonBinding::getName).anyMatch(binding -> binding.equalsIgnoreCase(name));
}
@@ -125,8 +121,7 @@ public class InputManager
* @param identifier The identifier of the binding to check.
* @return True if the binding is registered, else false.
*/
public static boolean hasBinding(@NotNull Identifier identifier)
{
public static boolean hasBinding(@NotNull Identifier identifier) {
return hasBinding(identifier.getNamespace() + "." + identifier.getName());
}
@@ -136,39 +131,33 @@ public class InputManager
* @param binding The binding to register.
* @return The registered binding.
*/
public static @NotNull ButtonBinding registerBinding(@NotNull ButtonBinding binding)
{
public static @NotNull ButtonBinding registerBinding(@NotNull ButtonBinding binding) {
if (hasBinding(binding))
throw new IllegalStateException("Cannot register twice a button binding in the registry.");
BINDINGS.add(binding);
return binding;
}
public static @NotNull ButtonBinding registerBinding(@NotNull Identifier id, int[] defaultButton, @NotNull List<PressAction> actions, @NotNull PairPredicate<MinecraftClient, ButtonBinding> filter, boolean hasCooldown)
{
public static @NotNull ButtonBinding registerBinding(@NotNull Identifier id, int[] defaultButton, @NotNull List<PressAction> actions, @NotNull PairPredicate<MinecraftClient, ButtonBinding> filter, boolean hasCooldown) {
return registerBinding(new ButtonBinding(id.getNamespace() + "." + id.getName(), defaultButton, actions, filter, hasCooldown));
}
public static @NotNull ButtonBinding registerBinding(@NotNull Identifier id, int[] defaultButton, boolean hasCooldown)
{
public static @NotNull ButtonBinding registerBinding(@NotNull Identifier id, int[] defaultButton, boolean hasCooldown) {
return registerBinding(id, defaultButton, Collections.emptyList(), InputHandlers::always, hasCooldown);
}
public static @NotNull ButtonBinding registerBinding(@NotNull net.minecraft.util.Identifier id, int[] defaultButton, @NotNull List<PressAction> actions, @NotNull PairPredicate<MinecraftClient, ButtonBinding> filter, boolean hasCooldown)
{
public static @NotNull ButtonBinding registerBinding(@NotNull net.minecraft.util.Identifier id, int[] defaultButton, @NotNull List<PressAction> actions, @NotNull PairPredicate<MinecraftClient, ButtonBinding> filter, boolean hasCooldown) {
return registerBinding(new Identifier(id.getNamespace(), id.getPath()), defaultButton, actions, filter, hasCooldown);
}
public static @NotNull ButtonBinding registerBinding(@NotNull net.minecraft.util.Identifier id, int[] defaultButton, boolean hasCooldown)
{
public static @NotNull ButtonBinding registerBinding(@NotNull net.minecraft.util.Identifier id, int[] defaultButton, boolean hasCooldown) {
return registerBinding(id, defaultButton, Collections.emptyList(), InputHandlers::always, hasCooldown);
}
/**
* Sorts bindings to get bindings with the higher button counts first.
*/
public static void sortBindings()
{
public static void sortBindings() {
synchronized (BINDINGS) {
List<ButtonBinding> sorted = BINDINGS.stream().sorted(Collections.reverseOrder(Comparator.comparingInt(binding -> binding.getButton().length)))
.collect(Collectors.toList());
@@ -183,24 +172,20 @@ public class InputManager
* @param category The category to register.
* @return The registered category.
*/
public static ButtonCategory registerCategory(@NotNull ButtonCategory category)
{
public static ButtonCategory registerCategory(@NotNull ButtonCategory category) {
CATEGORIES.add(category);
return category;
}
public static ButtonCategory registerCategory(@NotNull Identifier identifier, int priority)
{
public static ButtonCategory registerCategory(@NotNull Identifier identifier, int priority) {
return registerCategory(new ButtonCategory(identifier, priority));
}
public static ButtonCategory registerCategory(@NotNull Identifier identifier)
{
public static ButtonCategory registerCategory(@NotNull Identifier identifier) {
return registerCategory(new ButtonCategory(identifier));
}
protected static ButtonCategory registerDefaultCategory(@NotNull String key, @NotNull Consumer<ButtonCategory> keyAdder)
{
protected static ButtonCategory registerDefaultCategory(@NotNull String key, @NotNull Consumer<ButtonCategory> keyAdder) {
ButtonCategory category = registerCategory(new Identifier("minecraft", key), CATEGORIES.size());
keyAdder.accept(category);
return category;
@@ -211,8 +196,7 @@ public class InputManager
*
* @param config The configuration instance.
*/
public static void loadButtonBindings(@NotNull LambdaControlsConfig config)
{
public static void loadButtonBindings(@NotNull LambdaControlsConfig config) {
List<ButtonBinding> queue = new ArrayList<>(BINDINGS);
queue.forEach(config::loadButtonBinding);
}
@@ -223,8 +207,7 @@ public class InputManager
* @param binding The binding.
* @return The current state of the binding.
*/
public static @NotNull ButtonState getBindingState(@NotNull ButtonBinding binding)
{
public static @NotNull ButtonState getBindingState(@NotNull ButtonBinding binding) {
ButtonState state = ButtonState.REPEAT;
for (int btn : binding.getButton()) {
ButtonState btnState = InputManager.STATES.getOrDefault(btn, ButtonState.NONE);
@@ -241,14 +224,29 @@ public class InputManager
return state;
}
public static float getBindingValue(@NotNull ButtonBinding binding, @NotNull ButtonState state) {
if (state.isUnpressed())
return 0.f;
float value = 0.f;
for (int btn : binding.getButton()) {
if (ButtonBinding.isAxis(btn)) {
value = BUTTON_VALUES.getOrDefault(btn, 1.f);
break;
} else {
value = 1.f;
}
}
return value;
}
/**
* Returns whether the button has duplicated bindings.
*
* @param button The button to check.
* @return True if the button has duplicated bindings, else false.
*/
public static boolean hasDuplicatedBindings(int[] button)
{
public static boolean hasDuplicatedBindings(int[] button) {
return BINDINGS.parallelStream().filter(binding -> areButtonsEquivalent(binding.getButton(), button)).count() > 1;
}
@@ -258,8 +256,7 @@ public class InputManager
* @param binding The binding to check.
* @return True if the button has duplicated bindings, else false.
*/
public static boolean hasDuplicatedBindings(ButtonBinding binding)
{
public static boolean hasDuplicatedBindings(ButtonBinding binding) {
return BINDINGS.parallelStream().filter(other -> areButtonsEquivalent(other.getButton(), binding.getButton()) && other.filter.equals(binding.filter)).count() > 1;
}
@@ -270,8 +267,7 @@ public class InputManager
* @param buttons2 Second set of buttons.
* @return True if the two sets of buttons are equivalent, else false.
*/
public static boolean areButtonsEquivalent(int[] buttons1, int[] buttons2)
{
public static boolean areButtonsEquivalent(int[] buttons1, int[] buttons2) {
if (buttons1.length != buttons2.length)
return false;
int count = 0;
@@ -290,19 +286,17 @@ public class InputManager
* Returns whether the button set contains the specified button or not.
*
* @param buttons The button set.
* @param button The button to check.
* @param button The button to check.
* @return True if the button set contains the specified button, else false.
*/
public static boolean containsButton(int[] buttons, int button)
{
public static boolean containsButton(int[] buttons, int button) {
return Arrays.stream(buttons).anyMatch(btn -> btn == button);
}
/**
* Updates the button states.
*/
public static void updateStates()
{
public static void updateStates() {
STATES.forEach((btn, state) -> {
if (state == ButtonState.PRESS)
STATES.put(btn, ButtonState.REPEAT);
@@ -311,10 +305,9 @@ public class InputManager
});
}
public static void updateBindings(@NotNull MinecraftClient client)
{
public static void updateBindings(@NotNull MinecraftClient client) {
List<Integer> skipButtons = new ArrayList<>();
Map<ButtonBinding, ButtonState> states = new HashMap<>();
Map<ButtonBinding, Pair<ButtonState, Float>> states = new HashMap<>();
for (ButtonBinding binding : BINDINGS) {
ButtonState state = binding.isAvailable(client) ? getBindingState(binding) : ButtonState.NONE;
if (skipButtons.stream().anyMatch(btn -> containsButton(binding.getButton(), btn))) {
@@ -323,38 +316,70 @@ public class InputManager
else
state = ButtonState.NONE;
}
if (state == ButtonState.RELEASE && !binding.pressed) {
state = ButtonState.NONE;
}
binding.pressed = state.isPressed();
binding.update();
if (binding.pressed)
Arrays.stream(binding.getButton()).forEach(skipButtons::add);
states.put(binding, state);
float value = getBindingValue(binding, state);
states.put(binding, Pair.of(state, value));
}
states.forEach((binding, state) -> {
if (state != ButtonState.NONE) {
binding.handle(client, state);
if (state.key != ButtonState.NONE) {
binding.handle(client, state.value, state.key);
}
});
}
public static void queueMousePosition(double x, double y)
{
public static void queueMousePosition(double x, double y) {
INPUT_MANAGER.targetMouseX = (int) MathHelper.clamp(x, 0, MinecraftClient.getInstance().getWindow().getWidth());
INPUT_MANAGER.targetMouseY = (int) MathHelper.clamp(y, 0, MinecraftClient.getInstance().getWindow().getHeight());
}
public static void queueMoveMousePosition(double x, double y)
{
public static void queueMoveMousePosition(double x, double y) {
queueMousePosition(INPUT_MANAGER.targetMouseX + x, INPUT_MANAGER.targetMouseY + y);
}
public static @NotNull Stream<ButtonBinding> streamBindings()
{
public static @NotNull Stream<ButtonBinding> streamBindings() {
return BINDINGS.stream();
}
public static @NotNull Stream<ButtonCategory> streamCategories()
{
public static @NotNull Stream<ButtonCategory> streamCategories() {
return CATEGORIES.stream();
}
/**
* Returns a new key binding instance.
*
* @param id The identifier of the key binding.
* @param type The type.
* @param code The code.
* @param category The category of the key binding.
* @return The key binding.
* @see #makeKeyBinding(Identifier, InputUtil.Type, int, String)
*/
public static @NotNull KeyBinding makeKeyBinding(@NotNull net.minecraft.util.Identifier id, InputUtil.Type type, int code, @NotNull String category) {
return makeKeyBinding(new Identifier(id.getNamespace(), id.getPath()), type, code, category);
}
/**
* Returns a new key binding instance.
*
* @param id The identifier of the key binding.
* @param type The type.
* @param code The code.
* @param category The category of the key binding.
* @return The key binding.
* @see #makeKeyBinding(net.minecraft.util.Identifier, InputUtil.Type, int, String)
*/
public static @NotNull KeyBinding makeKeyBinding(@NotNull Identifier id, InputUtil.Type type, int code, @NotNull String category) {
return new KeyBinding(String.format("key.%s.%s", id.getNamespace(), id.getName()), type, code, category);
}
}

View File

@@ -0,0 +1,97 @@
/*
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of LambdaControls.
*
* Licensed under the MIT license. For more information,
* see the LICENSE file.
*/
package dev.lambdaurora.lambdacontrols.client.controller;
import dev.lambdaurora.lambdacontrols.client.ButtonState;
import dev.lambdaurora.lambdacontrols.client.LambdaControlsClient;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.network.ClientPlayerEntity;
import org.jetbrains.annotations.NotNull;
/**
* Represents the movement handler.
*
* @author LambdAurora
* @version 1.6.0
* @since 1.4.0
*/
public final class MovementHandler implements PressAction {
public static final MovementHandler HANDLER = new MovementHandler();
private boolean shouldOverrideMovement = false;
private boolean pressingForward = false;
private boolean pressingBack = false;
private boolean pressingLeft = false;
private boolean pressingRight = false;
private float movementForward = 0.f;
private float movementSideways = 0.f;
private MovementHandler() {
}
/**
* Applies movement input of this handler to the player's input.
*
* @param player The client player.
*/
public void applyMovement(@NotNull ClientPlayerEntity player) {
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;
this.shouldOverrideMovement = false;
}
@Override
public boolean press(@NotNull MinecraftClient client, @NotNull ButtonBinding button, float value, @NotNull ButtonState action) {
if (client.currentScreen != null || client.player == null)
return this.shouldOverrideMovement = false;
int direction = 0;
if (button == ButtonBinding.FORWARD || button == ButtonBinding.LEFT)
direction = 1;
else if (button == ButtonBinding.BACK || button == ButtonBinding.RIGHT)
direction = -1;
if (action.isUnpressed())
direction = 0;
this.shouldOverrideMovement = direction != 0;
if (LambdaControlsClient.get().config.hasAnalogMovement()) {
value = (float) Math.pow(value, 2);
} else value = 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

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

View File

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

View File

@@ -0,0 +1,280 @@
/*
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of LambdaControls.
*
* Licensed under the MIT license. For more information,
* see the LICENSE file.
*/
package dev.lambdaurora.lambdacontrols.client.gui;
import com.mojang.blaze3d.systems.RenderSystem;
import dev.lambdaurora.lambdacontrols.client.LambdaControlsClient;
import dev.lambdaurora.lambdacontrols.client.LambdaInput;
import dev.lambdaurora.lambdacontrols.client.compat.LambdaControlsCompat;
import dev.lambdaurora.lambdacontrols.client.controller.ButtonBinding;
import dev.lambdaurora.lambdacontrols.client.util.HandledScreenAccessor;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.font.TextRenderer;
import net.minecraft.client.gui.DrawableHelper;
import net.minecraft.client.gui.screen.ingame.HandledScreen;
import net.minecraft.client.resource.language.I18n;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.screen.slot.Slot;
import org.aperlambda.lambdacommon.utils.Pair;
import org.jetbrains.annotations.NotNull;
import org.lwjgl.glfw.GLFW;
/**
* Represents the LambdaControls renderer.
*
* @author LambdAurora
* @version 1.5.0
* @since 1.2.0
*/
public class LambdaControlsRenderer {
public static final int ICON_SIZE = 20;
private static final int BUTTON_SIZE = 15;
private static final int AXIS_SIZE = 18;
public static int getButtonSize(int button) {
switch (button) {
case -1:
return 0;
case GLFW.GLFW_GAMEPAD_AXIS_LEFT_X + 100:
case GLFW.GLFW_GAMEPAD_AXIS_LEFT_X + 200:
case GLFW.GLFW_GAMEPAD_AXIS_LEFT_Y + 100:
case GLFW.GLFW_GAMEPAD_AXIS_LEFT_Y + 200:
case GLFW.GLFW_GAMEPAD_AXIS_RIGHT_X + 100:
case GLFW.GLFW_GAMEPAD_AXIS_RIGHT_X + 200:
case GLFW.GLFW_GAMEPAD_AXIS_RIGHT_Y + 100:
case GLFW.GLFW_GAMEPAD_AXIS_RIGHT_Y + 200:
return AXIS_SIZE;
default:
return BUTTON_SIZE;
}
}
/**
* Gets the binding icon width.
*
* @param binding The binding.
* @return The width.
*/
public static int getBindingIconWidth(@NotNull ButtonBinding binding) {
return getBindingIconWidth(binding.getButton());
}
/**
* Gets the binding icon width.
*
* @param buttons The buttons.
* @return The width.
*/
public static int getBindingIconWidth(int[] buttons) {
int width = 0;
for (int i = 0; i < buttons.length; i++) {
width += ICON_SIZE;
if (i + 1 < buttons.length) {
width += 2;
}
}
return width;
}
public static Pair<Integer, Integer> drawButton(MatrixStack matrices, int x, int y, @NotNull ButtonBinding button, @NotNull MinecraftClient client) {
return drawButton(matrices, x, y, button.getButton(), client);
}
public static Pair<Integer, Integer> drawButton(MatrixStack matrices, int x, int y, int[] buttons, @NotNull MinecraftClient client) {
int height = 0;
int length = 0;
int currentX = x;
for (int i = 0; i < buttons.length; i++) {
int btn = buttons[i];
int size = drawButton(matrices, currentX, y, btn, client);
if (size > height)
height = size;
length += size;
if (i + 1 < buttons.length) {
length += 2;
currentX = x + length;
}
}
return Pair.of(length, height);
}
@SuppressWarnings("deprecated")
public static int drawButton(MatrixStack matrices, int x, int y, int button, @NotNull MinecraftClient client) {
boolean second = false;
if (button == -1)
return 0;
else if (button >= 500) {
button -= 1000;
second = true;
}
int controllerType = LambdaControlsClient.get().config.getControllerType().getId();
boolean axis = false;
int buttonOffset = button * 15;
switch (button) {
case GLFW.GLFW_GAMEPAD_BUTTON_LEFT_BUMPER:
buttonOffset = 7 * 15;
break;
case GLFW.GLFW_GAMEPAD_BUTTON_RIGHT_BUMPER:
buttonOffset = 8 * 15;
break;
case GLFW.GLFW_GAMEPAD_BUTTON_BACK:
buttonOffset = 4 * 15;
break;
case GLFW.GLFW_GAMEPAD_BUTTON_START:
buttonOffset = 6 * 15;
break;
case GLFW.GLFW_GAMEPAD_BUTTON_GUIDE:
buttonOffset = 5 * 15;
break;
case GLFW.GLFW_GAMEPAD_BUTTON_LEFT_THUMB:
buttonOffset = 15 * 15;
break;
case GLFW.GLFW_GAMEPAD_BUTTON_RIGHT_THUMB:
buttonOffset = 16 * 15;
break;
case GLFW.GLFW_GAMEPAD_AXIS_LEFT_X + 100:
buttonOffset = 0;
axis = true;
break;
case GLFW.GLFW_GAMEPAD_AXIS_LEFT_Y + 100:
buttonOffset = 18;
axis = true;
break;
case GLFW.GLFW_GAMEPAD_AXIS_RIGHT_X + 100:
buttonOffset = 2 * 18;
axis = true;
break;
case GLFW.GLFW_GAMEPAD_AXIS_RIGHT_Y + 100:
buttonOffset = 3 * 18;
axis = true;
break;
case GLFW.GLFW_GAMEPAD_AXIS_LEFT_X + 200:
buttonOffset = 4 * 18;
axis = true;
break;
case GLFW.GLFW_GAMEPAD_AXIS_LEFT_Y + 200:
buttonOffset = 5 * 18;
axis = true;
break;
case GLFW.GLFW_GAMEPAD_AXIS_RIGHT_X + 200:
buttonOffset = 6 * 18;
axis = true;
break;
case GLFW.GLFW_GAMEPAD_AXIS_RIGHT_Y + 200:
buttonOffset = 7 * 18;
axis = true;
break;
case GLFW.GLFW_GAMEPAD_AXIS_LEFT_TRIGGER + 100:
case GLFW.GLFW_GAMEPAD_AXIS_LEFT_TRIGGER + 200:
buttonOffset = 9 * 15;
break;
case GLFW.GLFW_GAMEPAD_AXIS_RIGHT_TRIGGER + 100:
case GLFW.GLFW_GAMEPAD_AXIS_RIGHT_TRIGGER + 200:
buttonOffset = 10 * 15;
break;
}
client.getTextureManager().bindTexture(axis ? LambdaControlsClient.CONTROLLER_AXIS : LambdaControlsClient.CONTROLLER_BUTTONS);
RenderSystem.disableDepthTest();
int assetSize = axis ? AXIS_SIZE : BUTTON_SIZE;
RenderSystem.color4f(1.0F, second ? 0.0F : 1.0F, 1.0F, 1.0F);
DrawableHelper.drawTexture(matrices, x + (ICON_SIZE / 2 - assetSize / 2), y + (ICON_SIZE / 2 - assetSize / 2),
(float) buttonOffset, (float) (controllerType * (axis ? AXIS_SIZE : BUTTON_SIZE)),
assetSize, assetSize,
256, 256);
RenderSystem.enableDepthTest();
return ICON_SIZE;
}
public static int drawButtonTip(MatrixStack matrices, int x, int y, @NotNull ButtonBinding button, boolean display, @NotNull MinecraftClient client) {
return drawButtonTip(matrices, x, y, button.getButton(), button.getTranslationKey(), display, client);
}
public static int drawButtonTip(MatrixStack matrices, int x, int y, int[] button, @NotNull String action, boolean display, @NotNull MinecraftClient client) {
if (display) {
int buttonWidth = drawButton(matrices, x, y, button, client).key;
String translatedAction = I18n.translate(action);
int textY = (LambdaControlsRenderer.ICON_SIZE / 2 - client.textRenderer.fontHeight / 2) + 1;
return client.textRenderer.drawWithShadow(matrices, translatedAction, (float) (x + buttonWidth + 2), (float) (y + textY), 14737632);
}
return -10;
}
private static int getButtonTipWidth(@NotNull String action, @NotNull TextRenderer textRenderer) {
return 15 + 5 + textRenderer.getWidth(action);
}
public static void renderVirtualCursor(@NotNull MatrixStack matrices, @NotNull MinecraftClient client) {
if (!LambdaControlsClient.get().config.hasVirtualMouse() || (client.currentScreen == null || LambdaInput.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 HandledScreen) {
HandledScreenAccessor inventoryScreen = (HandledScreenAccessor) client.currentScreen;
int guiLeft = inventoryScreen.getX();
int guiTop = inventoryScreen.getY();
Slot slot = inventoryScreen.lambdacontrols$getSlotAt(mouseX, mouseY);
if (slot != null) {
mouseX = guiLeft + slot.x;
mouseY = guiTop + slot.y;
hoverSlot = true;
}
}
if (!hoverSlot) {
Pair<Integer, Integer> slot = LambdaControlsCompat.getSlotAt(client.currentScreen, mouseX, mouseY);
if (slot != null) {
mouseX = slot.getFirst();
mouseY = slot.getSecond();
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) {
client.getTextureManager().bindTexture(LambdaControlsClient.CURSOR_TEXTURE);
RenderSystem.disableDepthTest();
RenderSystem.color4f(1.0F, 1.0F, 1.0F, 1.0F);
DrawableHelper.drawTexture(matrices, x, y, hoverSlot ? 16.F : 0.F, LambdaControlsClient.get().config.getVirtualMouseSkin().ordinal() * 16.F, 16, 16, 32, 64);
RenderSystem.enableDepthTest();
}
}

View File

@@ -0,0 +1,396 @@
/*
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of LambdaControls.
*
* Licensed under the MIT license. For more information,
* see the LICENSE file.
*/
package dev.lambdaurora.lambdacontrols.client.gui;
import dev.lambdaurora.lambdacontrols.ControlsMode;
import dev.lambdaurora.lambdacontrols.LambdaControls;
import dev.lambdaurora.lambdacontrols.client.LambdaControlsClient;
import dev.lambdaurora.lambdacontrols.client.LambdaControlsConfig;
import dev.lambdaurora.lambdacontrols.client.controller.Controller;
import dev.lambdaurora.lambdacontrols.client.gui.widget.ControllerControlsWidget;
import me.lambdaurora.spruceui.Position;
import me.lambdaurora.spruceui.SpruceTexts;
import me.lambdaurora.spruceui.option.*;
import me.lambdaurora.spruceui.screen.SpruceScreen;
import me.lambdaurora.spruceui.widget.AbstractSpruceWidget;
import me.lambdaurora.spruceui.widget.SpruceLabelWidget;
import me.lambdaurora.spruceui.widget.container.SpruceContainerWidget;
import me.lambdaurora.spruceui.widget.container.SpruceOptionListWidget;
import me.lambdaurora.spruceui.widget.container.tabbed.SpruceTabbedWidget;
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gui.screen.Screen;
import net.minecraft.client.gui.widget.ButtonWidget;
import net.minecraft.client.resource.language.I18n;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.text.LiteralText;
import net.minecraft.text.MutableText;
import net.minecraft.text.Text;
import net.minecraft.text.TranslatableText;
import net.minecraft.util.Formatting;
import net.minecraft.util.Util;
import org.lwjgl.glfw.GLFW;
/**
* Represents the LambdaControls settings screen.
*/
public class LambdaControlsSettingsScreen extends SpruceScreen {
private static final Text SDL2_GAMEPAD_TOOL = new LiteralText("SDL2 Gamepad Tool").formatted(Formatting.GREEN);
public static final String GAMEPAD_TOOL_URL = "https://generalarcade.com/gamepadtool/";
final LambdaControlsClient mod = LambdaControlsClient.get();
private final LambdaControlsConfig config = this.mod.config;
private final Screen parent;
// General options
private final SpruceOption inputModeOption;
private final SpruceOption autoSwitchModeOption;
private final SpruceOption rotationSpeedOption;
private final SpruceOption mouseSpeedOption;
private final SpruceOption virtualMouseOption;
private final SpruceOption resetOption;
// Gameplay options
private final SpruceOption analogMovementOption;
private final SpruceOption autoJumpOption;
private final SpruceOption fastBlockPlacingOption;
private final SpruceOption frontBlockPlacingOption;
private final SpruceOption verticalReacharoundOption;
private final SpruceOption flyDriftingOption;
private final SpruceOption flyVerticalDriftingOption;
// Appearance options
private final SpruceOption controllerTypeOption;
private final SpruceOption virtualMouseSkinOption;
private final SpruceOption hudEnableOption;
private final SpruceOption hudSideOption;
// Controller options
private final SpruceOption controllerOption =
new SpruceCyclingOption("lambdacontrols.menu.controller",
amount -> {
int id = this.config.getController().getId();
id += amount;
if (id > GLFW.GLFW_JOYSTICK_LAST)
id = GLFW.GLFW_JOYSTICK_1;
id = searchNextAvailableController(id, false);
this.config.setController(Controller.byId(id));
},
option -> {
Controller controller = this.config.getController();
String controllerName = controller.getName();
if (!controller.isConnected())
return option.getDisplayText(new LiteralText(controllerName).formatted(Formatting.RED));
else if (!controller.isGamepad())
return option.getDisplayText(new LiteralText(controllerName).formatted(Formatting.GOLD));
else
return option.getDisplayText(new LiteralText(controllerName));
}, null);
private final SpruceOption secondControllerOption = new SpruceCyclingOption("lambdacontrols.menu.controller2",
amount -> {
int id = this.config.getSecondController().map(Controller::getId).orElse(-1);
id += amount;
if (id > GLFW.GLFW_JOYSTICK_LAST)
id = -1;
id = searchNextAvailableController(id, true);
this.config.setSecondController(id == -1 ? null : Controller.byId(id));
},
option -> this.config.getSecondController().map(controller -> {
String controllerName = controller.getName();
if (!controller.isConnected())
return option.getDisplayText(new LiteralText(controllerName).formatted(Formatting.RED));
else if (!controller.isGamepad())
return option.getDisplayText(new LiteralText(controllerName).formatted(Formatting.GOLD));
else
return option.getDisplayText(new LiteralText(controllerName));
}).orElse(option.getDisplayText(SpruceTexts.OPTIONS_OFF.shallowCopy().formatted(Formatting.RED))),
new TranslatableText("lambdacontrols.tooltip.controller2"));
private final SpruceOption unfocusedInputOption;
private final SpruceOption invertsRightXAxis;
private final SpruceOption invertsRightYAxis;
private final SpruceOption rightDeadZoneOption;
private final SpruceOption leftDeadZoneOption;
private final SpruceOption[] maxAnalogValueOptions = new SpruceOption[]{
maxAnalogValueOption(this.config, "lambdacontrols.menu.max_left_x_value", GLFW.GLFW_GAMEPAD_AXIS_LEFT_X),
maxAnalogValueOption(this.config, "lambdacontrols.menu.max_left_y_value", GLFW.GLFW_GAMEPAD_AXIS_LEFT_Y),
maxAnalogValueOption(this.config, "lambdacontrols.menu.max_right_x_value", GLFW.GLFW_GAMEPAD_AXIS_RIGHT_X),
maxAnalogValueOption(this.config, "lambdacontrols.menu.max_right_y_value", GLFW.GLFW_GAMEPAD_AXIS_RIGHT_Y)
};
private static SpruceOption maxAnalogValueOption(LambdaControlsConfig config, String key, int axis) {
return new SpruceDoubleOption(key, .25f, 1.f, 0.05f,
() -> config.getAxisMaxValue(axis),
newValue -> config.setAxisMaxValue(axis, newValue),
option -> option.getDisplayText(new LiteralText(String.format("%.2f", option.get()))),
new TranslatableText(key.replace("menu", "tooltip"))
);
}
private final MutableText controllerMappingsUrlText = new LiteralText("(")
.append(new LiteralText(GAMEPAD_TOOL_URL).formatted(Formatting.GOLD))
.append("),");
private static int searchNextAvailableController(int newId, boolean allowNone) {
if ((allowNone && newId == -1) || newId == 0) return newId;
boolean connected = Controller.byId(newId).isConnected();
if (!connected) {
newId++;
}
if (newId > GLFW.GLFW_JOYSTICK_LAST)
newId = allowNone ? -1 : GLFW.GLFW_JOYSTICK_1;
return connected ? newId : searchNextAvailableController(newId, allowNone);
}
public LambdaControlsSettingsScreen(Screen parent, boolean hideControls) {
super(new TranslatableText("lambdacontrols.title.settings"));
this.parent = parent;
// General options
this.inputModeOption = new SpruceCyclingOption("lambdacontrols.menu.controls_mode",
amount -> {
ControlsMode next = this.config.getControlsMode().next();
this.config.setControlsMode(next);
this.config.save();
if (this.client.player != null) {
ClientPlayNetworking.getSender().sendPacket(LambdaControls.CONTROLS_MODE_CHANNEL, this.mod.makeControlsModeBuffer(next));
}
}, option -> option.getDisplayText(new TranslatableText(this.config.getControlsMode().getTranslationKey())),
new TranslatableText("lambdacontrols.tooltip.controls_mode"));
this.autoSwitchModeOption = new SpruceToggleBooleanOption("lambdacontrols.menu.auto_switch_mode", this.config::hasAutoSwitchMode,
this.config::setAutoSwitchMode, new TranslatableText("lambdacontrols.tooltip.auto_switch_mode"));
this.rotationSpeedOption = new SpruceDoubleOption("lambdacontrols.menu.rotation_speed", 0.0, 100.0, .5f,
this.config::getRotationSpeed,
newValue -> {
synchronized (this.config) {
this.config.setRotationSpeed(newValue);
}
}, option -> option.getDisplayText(new LiteralText(String.valueOf(option.get()))),
new TranslatableText("lambdacontrols.tooltip.rotation_speed"));
this.mouseSpeedOption = new SpruceDoubleOption("lambdacontrols.menu.mouse_speed", 0.0, 150.0, .5f,
this.config::getMouseSpeed,
newValue -> {
synchronized (this.config) {
this.config.setMouseSpeed(newValue);
}
}, option -> option.getDisplayText(new LiteralText(String.valueOf(option.get()))),
new TranslatableText("lambdacontrols.tooltip.mouse_speed"));
this.resetOption = SpruceSimpleActionOption.reset(btn -> {
this.config.reset();
MinecraftClient client = MinecraftClient.getInstance();
this.init(client, client.getWindow().getScaledWidth(), client.getWindow().getScaledHeight());
});
// Gameplay options
this.analogMovementOption = new SpruceToggleBooleanOption("lambdacontrols.menu.analog_movement",
this.config::hasAnalogMovement, this.config::setAnalogMovement,
new TranslatableText("lambdacontrols.tooltip.analog_movement"));
this.autoJumpOption = new SpruceToggleBooleanOption("options.autoJump",
() -> this.client.options.autoJump,
newValue -> this.client.options.autoJump = newValue,
null);
this.fastBlockPlacingOption = new SpruceToggleBooleanOption("lambdacontrols.menu.fast_block_placing", this.config::hasFastBlockPlacing,
this.config::setFastBlockPlacing, new TranslatableText("lambdacontrols.tooltip.fast_block_placing"));
this.frontBlockPlacingOption = new SpruceToggleBooleanOption("lambdacontrols.menu.reacharound.horizontal", this.config::hasFrontBlockPlacing,
this.config::setFrontBlockPlacing, new TranslatableText("lambdacontrols.tooltip.reacharound.horizontal"));
this.verticalReacharoundOption = new SpruceToggleBooleanOption("lambdacontrols.menu.reacharound.vertical", this.config::hasVerticalReacharound,
this.config::setVerticalReacharound, new TranslatableText("lambdacontrols.tooltip.reacharound.vertical"));
this.flyDriftingOption = new SpruceToggleBooleanOption("lambdacontrols.menu.fly_drifting", this.config::hasFlyDrifting,
this.config::setFlyDrifting, new TranslatableText("lambdacontrols.tooltip.fly_drifting"));
this.flyVerticalDriftingOption = new SpruceToggleBooleanOption("lambdacontrols.menu.fly_drifting_vertical", this.config::hasFlyVerticalDrifting,
this.config::setFlyVerticalDrifting, new TranslatableText("lambdacontrols.tooltip.fly_drifting_vertical"));
// Appearance options
this.controllerTypeOption = new SpruceCyclingOption("lambdacontrols.menu.controller_type",
amount -> this.config.setControllerType(this.config.getControllerType().next()),
option -> option.getDisplayText(this.config.getControllerType().getTranslatedText()),
new TranslatableText("lambdacontrols.tooltip.controller_type"));
this.virtualMouseSkinOption = new SpruceCyclingOption("lambdacontrols.menu.virtual_mouse.skin",
amount -> this.config.setVirtualMouseSkin(this.config.getVirtualMouseSkin().next()),
option -> option.getDisplayText(this.config.getVirtualMouseSkin().getTranslatedText()),
null);
this.hudEnableOption = new SpruceToggleBooleanOption("lambdacontrols.menu.hud_enable", this.config::isHudEnabled,
this.mod::setHudEnabled, new TranslatableText("lambdacontrols.tooltip.hud_enable"));
this.hudSideOption = new SpruceCyclingOption("lambdacontrols.menu.hud_side",
amount -> this.config.setHudSide(this.config.getHudSide().next()),
option -> option.getDisplayText(this.config.getHudSide().getTranslatedText()),
new TranslatableText("lambdacontrols.tooltip.hud_side"));
// Controller options
this.rightDeadZoneOption = new SpruceDoubleOption("lambdacontrols.menu.right_dead_zone", 0.05, 1.0, .05f,
this.config::getRightDeadZone,
newValue -> {
synchronized (this.config) {
this.config.setRightDeadZone(newValue);
}
}, option -> {
String value = String.valueOf(option.get());
return option.getDisplayText(new LiteralText(value.substring(0, Math.min(value.length(), 5))));
}, new TranslatableText("lambdacontrols.tooltip.right_dead_zone"));
this.leftDeadZoneOption = new SpruceDoubleOption("lambdacontrols.menu.left_dead_zone", 0.05, 1.0, .05f,
this.config::getLeftDeadZone,
newValue -> {
synchronized (this.config) {
this.config.setLeftDeadZone(newValue);
}
}, option -> {
String value = String.valueOf(option.get());
return option.getDisplayText(new LiteralText(value.substring(0, Math.min(value.length(), 5))));
}, new TranslatableText("lambdacontrols.tooltip.left_dead_zone"));
this.invertsRightXAxis = new SpruceToggleBooleanOption("lambdacontrols.menu.invert_right_x_axis", this.config::doesInvertRightXAxis,
newValue -> {
synchronized (this.config) {
this.config.setInvertRightXAxis(newValue);
}
}, null);
this.invertsRightYAxis = new SpruceToggleBooleanOption("lambdacontrols.menu.invert_right_y_axis", this.config::doesInvertRightYAxis,
newValue -> {
synchronized (this.config) {
this.config.setInvertRightYAxis(newValue);
}
}, null);
this.unfocusedInputOption = new SpruceToggleBooleanOption("lambdacontrols.menu.unfocused_input", this.config::hasUnfocusedInput,
this.config::setUnfocusedInput, new TranslatableText("lambdacontrols.tooltip.unfocused_input"));
this.virtualMouseOption = new SpruceToggleBooleanOption("lambdacontrols.menu.virtual_mouse", this.config::hasVirtualMouse,
this.config::setVirtualMouse, new TranslatableText("lambdacontrols.tooltip.virtual_mouse"));
}
@Override
public void removed() {
this.config.save();
super.removed();
}
@Override
public void onClose() {
this.config.save();
super.onClose();
}
private int getTextHeight() {
return (5 + this.textRenderer.fontHeight) * 3 + 5;
}
@Override
protected void init() {
super.init();
this.buildTabs();
this.addChild(this.resetOption.createWidget(Position.of(this.width / 2 - 155, this.height - 29), 150));
this.addButton(new ButtonWidget(this.width / 2 - 155 + 160, this.height - 29, 150, 20, SpruceTexts.GUI_DONE,
btn -> this.client.openScreen(this.parent)));
}
public void buildTabs() {
SpruceTabbedWidget tabs = new SpruceTabbedWidget(Position.of(0, 24), this.width, this.height - 32 - 24,
null,
Math.max(116, this.width / 8), 0);
this.addChild(tabs);
tabs.addSeparatorEntry(new TranslatableText("lambdacontrols.menu.separator.general"));
tabs.addTabEntry(new TranslatableText("lambdacontrols.menu.title.general"), null,
this::buildGeneralTab);
tabs.addTabEntry(new TranslatableText("lambdacontrols.menu.title.gameplay"), null,
this::buildGameplayTab);
tabs.addTabEntry(new TranslatableText("lambdacontrols.menu.title.visual"), null,
this::buildVisualTab);
tabs.addSeparatorEntry(new TranslatableText("options.controls"));
tabs.addTabEntry(new TranslatableText("lambdacontrols.menu.title.controller_controls"), null,
this::buildControllerControlsTab);
tabs.addSeparatorEntry(new TranslatableText("lambdacontrols.menu.separator.controller"));
tabs.addTabEntry(new TranslatableText("lambdacontrols.menu.title.controller"), null,
this::buildControllerTab);
tabs.addTabEntry(new TranslatableText("lambdacontrols.menu.title.mappings.string"), null,
this::buildMappingsStringEditorTab);
}
public SpruceOptionListWidget buildGeneralTab(int width, int height) {
SpruceOptionListWidget list = new SpruceOptionListWidget(Position.origin(), width, height);
list.addSingleOptionEntry(this.inputModeOption);
list.addSingleOptionEntry(this.autoSwitchModeOption);
list.addSingleOptionEntry(this.rotationSpeedOption);
list.addSingleOptionEntry(this.mouseSpeedOption);
list.addSingleOptionEntry(this.virtualMouseOption);
return list;
}
public SpruceOptionListWidget buildGameplayTab(int width, int height) {
SpruceOptionListWidget list = new SpruceOptionListWidget(Position.origin(), width, height);
list.addSingleOptionEntry(this.analogMovementOption);
list.addSingleOptionEntry(this.fastBlockPlacingOption);
list.addSingleOptionEntry(this.frontBlockPlacingOption);
list.addSingleOptionEntry(this.verticalReacharoundOption);
list.addSingleOptionEntry(this.flyDriftingOption);
list.addSingleOptionEntry(this.flyVerticalDriftingOption);
list.addSingleOptionEntry(this.autoJumpOption);
return list;
}
public SpruceOptionListWidget buildVisualTab(int width, int height) {
SpruceOptionListWidget list = new SpruceOptionListWidget(Position.origin(), width, height);
list.addSingleOptionEntry(this.controllerTypeOption);
list.addSingleOptionEntry(this.virtualMouseSkinOption);
list.addSingleOptionEntry(new SpruceSeparatorOption("lambdacontrols.menu.title.hud", true, null));
list.addSingleOptionEntry(this.hudEnableOption);
list.addSingleOptionEntry(this.hudSideOption);
return list;
}
public ControllerControlsWidget buildControllerControlsTab(int width, int height) {
return new ControllerControlsWidget(Position.origin(), width, height);
}
public AbstractSpruceWidget buildControllerTab(int width, int height) {
SpruceContainerWidget root = new SpruceContainerWidget(Position.origin(), width, height);
SpruceLabelWidget aboutMappings1 = new SpruceLabelWidget(Position.of(0, 2),
new TranslatableText("lambdacontrols.controller.mappings.1", SDL2_GAMEPAD_TOOL),
width, true);
SpruceLabelWidget gamepadToolUrlLabel = new SpruceLabelWidget(Position.of(0, aboutMappings1.getHeight() + 4),
this.controllerMappingsUrlText, width,
label -> Util.getOperatingSystem().open(GAMEPAD_TOOL_URL), true);
gamepadToolUrlLabel.setTooltip(new TranslatableText("chat.link.open"));
SpruceLabelWidget aboutMappings3 = new SpruceLabelWidget(Position.of(0,
aboutMappings1.getHeight() + gamepadToolUrlLabel.getHeight() + 6),
new TranslatableText("lambdacontrols.controller.mappings.3", Formatting.GREEN.toString(), Formatting.RESET.toString()),
width, true);
int listHeight = height - 8 - aboutMappings1.getHeight() - aboutMappings3.getHeight() - gamepadToolUrlLabel.getHeight();
SpruceContainerWidget labels = new SpruceContainerWidget(Position.of(0,
listHeight),
width, height - listHeight);
labels.addChild(aboutMappings1);
labels.addChild(gamepadToolUrlLabel);
labels.addChild(aboutMappings3);
SpruceOptionListWidget list = new SpruceOptionListWidget(Position.origin(), width, listHeight);
list.addSingleOptionEntry(this.controllerOption);
list.addSingleOptionEntry(this.secondControllerOption);
list.addSingleOptionEntry(this.unfocusedInputOption);
list.addOptionEntry(this.invertsRightXAxis, this.invertsRightYAxis);
list.addSingleOptionEntry(this.rightDeadZoneOption);
list.addSingleOptionEntry(this.leftDeadZoneOption);
for (SpruceOption option : this.maxAnalogValueOptions) {
list.addSingleOptionEntry(option);
}
root.addChild(list);
root.addChild(labels);
return root;
}
public SpruceContainerWidget buildMappingsStringEditorTab(int width, int height) {
return new MappingsStringInputWidget(Position.origin(), width, height);
}
@Override
public void renderTitle(MatrixStack matrices, int mouseX, int mouseY, float delta) {
drawCenteredString(matrices, this.textRenderer, I18n.translate("lambdacontrols.menu.title"), this.width / 2, 8, 16777215);
}
}

View File

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

View File

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

View File

@@ -0,0 +1,55 @@
/*
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of LambdaControls.
*
* Licensed under the MIT license. For more information,
* see the LICENSE file.
*/
package dev.lambdaurora.lambdacontrols.client.gui;
import dev.lambdaurora.lambdacontrols.client.LambdaControlsClient;
import dev.lambdaurora.lambdacontrols.client.ring.RingPage;
import net.minecraft.client.gui.screen.Screen;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.text.TranslatableText;
/**
* Represents the controls ring screen.
*
* @author LambdAurora
* @version 1.4.3
* @since 1.4.3
*/
public class RingScreen extends Screen {
protected final LambdaControlsClient mod;
public RingScreen() {
super(new TranslatableText("lambdacontrols.menu.title.ring"));
this.mod = LambdaControlsClient.get();
}
@Override
public boolean isPauseScreen() {
return false;
}
@Override
public void render(MatrixStack matrices, int mouseX, int mouseY, float delta) {
super.render(matrices, mouseX, mouseY, delta);
RingPage page = this.mod.ring.getCurrentPage();
page.render(matrices, this.textRenderer, this.width, this.height, mouseX, mouseY, delta);
}
@Override
public boolean mouseReleased(double mouseX, double mouseY, int button) {
/*if (LambdaControlsClient.BINDING_RING.matchesMouse(button)) {
this.onClose();
return true;
}*/
return false;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright © 2020 LambdAurora <aurora42lambda@gmail.com>
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of LambdaControls.
*
@@ -7,36 +7,21 @@
* see the LICENSE file.
*/
package me.lambdaurora.lambdacontrols.client.gui;
package dev.lambdaurora.lambdacontrols.client.gui;
import me.lambdaurora.lambdacontrols.client.HudSide;
import me.lambdaurora.lambdacontrols.client.LambdaControlsClient;
import me.lambdaurora.lambdacontrols.client.util.KeyBindingAccessor;
import me.lambdaurora.spruceui.SpruceTexturedButtonWidget;
import net.minecraft.client.gui.screen.ChatScreen;
import net.minecraft.client.gui.screen.GameMenuScreen;
import dev.lambdaurora.lambdacontrols.client.LambdaControlsClient;
import net.minecraft.client.gui.screen.Screen;
import net.minecraft.client.gui.screen.ingame.InventoryScreen;
import net.minecraft.client.gui.widget.ButtonWidget;
import net.minecraft.client.gui.widget.TexturedButtonWidget;
import net.minecraft.server.network.packet.PlayerActionC2SPacket;
import net.minecraft.text.LiteralText;
import net.minecraft.util.Arm;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import org.jetbrains.annotations.NotNull;
import org.lwjgl.glfw.GLFW;
import static org.lwjgl.glfw.GLFW.GLFW_GAMEPAD_AXIS_RIGHT_X;
import static org.lwjgl.glfw.GLFW.GLFW_GAMEPAD_AXIS_RIGHT_Y;
/**
* Represents the touchscreen overlay
*/
public class TouchscreenOverlay extends Screen
{
public static final Identifier WIDGETS_LOCATION = new Identifier("lambdacontrols", "textures/gui/widgets.png");
public class TouchscreenOverlay extends Screen {
public TouchscreenOverlay(@NotNull LambdaControlsClient mod) {
super(new LiteralText("Touchscreen overlay"));
}
/*public static final Identifier WIDGETS_LOCATION = new Identifier("lambdacontrols", "textures/gui/widgets.png");
private LambdaControlsClient mod;
private SpruceTexturedButtonWidget jumpButton;
private SpruceTexturedButtonWidget flyButton;
@@ -70,14 +55,14 @@ public class TouchscreenOverlay extends Screen
private void pauseGame(boolean bl)
{
if (this.minecraft == null)
if (this.client == null)
return;
boolean bl2 = this.minecraft.isIntegratedServerRunning() && !this.minecraft.getServer().isRemote();
boolean bl2 = this.client.isIntegratedServerRunning() && !this.client.getServer().isRemote();
if (bl2) {
this.minecraft.openScreen(new GameMenuScreen(!bl));
this.minecraft.getSoundManager().pauseAll();
this.client.openScreen(new GameMenuScreen(!bl));
this.client.getSoundManager().pauseAll();
} else {
this.minecraft.openScreen(new GameMenuScreen(true));
this.client.openScreen(new GameMenuScreen(true));
}
}
@@ -85,7 +70,7 @@ public class TouchscreenOverlay extends Screen
* Updates the forward button ticks cooldown.
*
* @param state The button state.
*/
*
private void updateForwardButtonsState(boolean state)
{
if (state)
@@ -96,12 +81,12 @@ public class TouchscreenOverlay extends Screen
/**
* Updates the jump buttons.
*/
*
private void updateJumpButtons()
{
if (this.minecraft == null)
if (this.client == null)
return;
if (this.minecraft.player.abilities.allowFlying && this.minecraft.player.abilities.flying) {
if (this.client.player.abilities.allowFlying && this.client.player.abilities.flying) {
boolean oldStateFly = this.flyButton.visible;
this.jumpButton.visible = false;
this.flyButton.visible = true;
@@ -125,10 +110,10 @@ public class TouchscreenOverlay extends Screen
*
* @param btn The pressed button.
* @param state The state of the jump button.
*/
*
private void handleJump(ButtonWidget btn, boolean state)
{
((KeyBindingAccessor) this.minecraft.options.keyJump).lambdacontrols_handlePressState(state);
((KeyBindingAccessor) this.client.options.keyJump).lambdacontrols_handlePressState(state);
}
@Override
@@ -149,27 +134,27 @@ public class TouchscreenOverlay extends Screen
protected void init()
{
super.init();
int scaledWidth = this.minecraft.getWindow().getScaledWidth();
int scaledHeight = this.minecraft.getWindow().getScaledHeight();
int scaledWidth = this.client.getWindow().getScaledWidth();
int scaledHeight = this.client.getWindow().getScaledHeight();
this.addButton(new TexturedButtonWidget(scaledWidth / 2 - 20, 0, 20, 20, 0, 106, 20, ButtonWidget.WIDGETS_LOCATION, 256, 256,
btn -> this.minecraft.openScreen(new ChatScreen("")), ""));
btn -> this.client.openScreen(new ChatScreen("")), LiteralText.EMPTY));
this.addButton(new TexturedButtonWidget(scaledWidth / 2, 0, 20, 20, 0, 0, 20, WIDGETS_LOCATION, 256, 256,
btn -> this.pauseGame(false)));
// Inventory buttons.
int inventoryButtonX = scaledWidth / 2;
int inventoryButtonY = scaledHeight - 16 - 5;
if (this.minecraft.options.mainArm == Arm.LEFT) {
if (this.client.options.mainArm == Arm.LEFT) {
inventoryButtonX = inventoryButtonX - 91 - 24;
} else {
inventoryButtonX = inventoryButtonX + 91 + 4;
}
this.addButton(new TexturedButtonWidget(inventoryButtonX, inventoryButtonY, 20, 20, 20, 0, 20, WIDGETS_LOCATION, 256, 256,
btn -> {
if (this.minecraft.interactionManager.hasRidingInventory()) {
this.minecraft.player.openRidingInventory();
if (this.client.interactionManager.hasRidingInventory()) {
this.client.player.openRidingInventory();
} else {
this.minecraft.getTutorialManager().onInventoryOpened();
this.minecraft.openScreen(new InventoryScreen(this.minecraft.player));
this.client.getTutorialManager().onInventoryOpened();
this.client.openScreen(new InventoryScreen(this.client.player));
}
}));
int jumpButtonX, swapHandsX, sneakButtonX;
@@ -187,31 +172,31 @@ public class TouchscreenOverlay extends Screen
this.addButton(new SpruceTexturedButtonWidget(swapHandsX, sneakButtonY, 20, 20, 0, 160, 20, WIDGETS_LOCATION,
(btn, state) -> {
if (state) {
if (!this.minecraft.player.isSpectator()) {
this.minecraft.getNetworkHandler().sendPacket(new PlayerActionC2SPacket(PlayerActionC2SPacket.Action.SWAP_HELD_ITEMS, BlockPos.ORIGIN, Direction.DOWN));
if (!this.client.player.isSpectator()) {
this.client.getNetworkHandler().sendPacket(new PlayerActionC2SPacket(PlayerActionC2SPacket.Action.SWAP_ITEM_WITH_OFFHAND, BlockPos.ORIGIN, Direction.DOWN));
}
}
}));
// Drop
this.addButton(new SpruceTexturedButtonWidget(swapHandsX, sneakButtonY + 5 + 20, 20, 20, 20, 160, 20, WIDGETS_LOCATION,
(btn, state) -> ((KeyBindingAccessor) this.minecraft.options.keyDrop).lambdacontrols_handlePressState(state)));
(btn, state) -> ((KeyBindingAccessor) this.client.options.keyDrop).lambdacontrols_handlePressState(state)));
// Jump keys
this.addButton(this.jumpButton = new SpruceTexturedButtonWidget(jumpButtonX, sneakButtonY, 20, 20, 0, 40, 20, WIDGETS_LOCATION,
this::handleJump));
this.addButton(this.flyButton = new SpruceTexturedButtonWidget(jumpButtonX, sneakButtonY, 20, 20, 20, 40, 20, WIDGETS_LOCATION,
(btn, state) -> {
if (this.flyButtonEnableTicks == 0) this.minecraft.player.abilities.flying = false;
if (this.flyButtonEnableTicks == 0) this.client.player.abilities.flying = false;
}));
this.addButton(this.flyUpButton = new SpruceTexturedButtonWidget(jumpButtonX, sneakButtonY - 5 - 20, 20, 20, 40, 40, 20, WIDGETS_LOCATION,
this::handleJump));
this.addButton(this.flyDownButton = new SpruceTexturedButtonWidget(jumpButtonX, sneakButtonY + 20 + 5, 20, 20, 60, 40, 20, WIDGETS_LOCATION,
(btn, state) -> ((KeyBindingAccessor) this.minecraft.options.keySneak).lambdacontrols_handlePressState(state)));
(btn, state) -> ((KeyBindingAccessor) this.client.options.keySneak).lambdacontrols_handlePressState(state)));
this.updateJumpButtons();
// Movements keys
this.addButton((this.startSneakButton = new SpruceTexturedButtonWidget(sneakButtonX, sneakButtonY, 20, 20, 0, 120, 20, WIDGETS_LOCATION,
(btn, state) -> {
if (state) {
((KeyBindingAccessor) this.minecraft.options.keySneak).lambdacontrols_handlePressState(true);
((KeyBindingAccessor) this.client.options.keySneak).lambdacontrols_handlePressState(true);
this.startSneakButton.visible = false;
this.endSneakButton.visible = true;
}
@@ -219,7 +204,7 @@ public class TouchscreenOverlay extends Screen
this.addButton((this.endSneakButton = new SpruceTexturedButtonWidget(sneakButtonX, sneakButtonY, 20, 20, 20, 120, 20, WIDGETS_LOCATION,
(btn, state) -> {
if (state) {
((KeyBindingAccessor) this.minecraft.options.keySneak).lambdacontrols_handlePressState(false);
((KeyBindingAccessor) this.client.options.keySneak).lambdacontrols_handlePressState(false);
this.endSneakButton.visible = false;
this.startSneakButton.visible = true;
}
@@ -227,49 +212,49 @@ public class TouchscreenOverlay extends Screen
this.endSneakButton.visible = false;
this.addButton(this.forwardLeftButton = new SpruceTexturedButtonWidget(sneakButtonX - 20 - 5, sneakButtonY - 5 - 20, 20, 20, 80, 80, 20, WIDGETS_LOCATION,
(btn, state) -> {
((KeyBindingAccessor) this.minecraft.options.keyForward).lambdacontrols_handlePressState(state);
((KeyBindingAccessor) this.minecraft.options.keyLeft).lambdacontrols_handlePressState(state);
((KeyBindingAccessor) this.client.options.keyForward).lambdacontrols_handlePressState(state);
((KeyBindingAccessor) this.client.options.keyLeft).lambdacontrols_handlePressState(state);
this.updateForwardButtonsState(state);
}));
this.forwardLeftButton.visible = false;
this.addButton(new SpruceTexturedButtonWidget(sneakButtonX, sneakButtonY - 5 - 20, 20, 20, 0, 80, 20, WIDGETS_LOCATION,
(btn, state) -> {
((KeyBindingAccessor) this.minecraft.options.keyForward).lambdacontrols_handlePressState(state);
((KeyBindingAccessor) this.client.options.keyForward).lambdacontrols_handlePressState(state);
this.updateForwardButtonsState(state);
this.forwardLeftButton.visible = true;
this.forwardRightButton.visible = true;
}));
this.addButton(this.forwardRightButton = new SpruceTexturedButtonWidget(sneakButtonX + 20 + 5, sneakButtonY - 5 - 20, 20, 20, 100, 80, 20, WIDGETS_LOCATION,
(btn, state) -> {
((KeyBindingAccessor) this.minecraft.options.keyForward).lambdacontrols_handlePressState(state);
((KeyBindingAccessor) this.minecraft.options.keyRight).lambdacontrols_handlePressState(state);
((KeyBindingAccessor) this.client.options.keyForward).lambdacontrols_handlePressState(state);
((KeyBindingAccessor) this.client.options.keyRight).lambdacontrols_handlePressState(state);
this.updateForwardButtonsState(state);
}));
this.forwardRightButton.visible = true;
this.addButton(new SpruceTexturedButtonWidget(sneakButtonX + 20 + 5, sneakButtonY, 20, 20, 20, 80, 20, WIDGETS_LOCATION,
(btn, state) -> ((KeyBindingAccessor) this.minecraft.options.keyRight).lambdacontrols_handlePressState(state)));
(btn, state) -> ((KeyBindingAccessor) this.client.options.keyRight).lambdacontrols_handlePressState(state)));
this.addButton(new SpruceTexturedButtonWidget(sneakButtonX, sneakButtonY + 20 + 5, 20, 20, 40, 80, 20, WIDGETS_LOCATION,
(btn, state) -> ((KeyBindingAccessor) this.minecraft.options.keyBack).lambdacontrols_handlePressState(state)));
(btn, state) -> ((KeyBindingAccessor) this.client.options.keyBack).lambdacontrols_handlePressState(state)));
this.addButton(new SpruceTexturedButtonWidget(sneakButtonX - 20 - 5, sneakButtonY, 20, 20, 60, 80, 20, WIDGETS_LOCATION,
(btn, state) -> ((KeyBindingAccessor) this.minecraft.options.keyLeft).lambdacontrols_handlePressState(state)));
}
(btn, state) -> ((KeyBindingAccessor) this.client.options.keyLeft).lambdacontrols_handlePressState(state)));
@Override
public void render(int mouseX, int mouseY, float delta)
{
super.render(mouseX, mouseY, delta);
this.buttons.forEach(button -> {
if (button instanceof SpruceTexturedButtonWidget) {
((SpruceTexturedButtonWidget) button).setSilent(true);
}
});
}
@Override
public boolean mouseClicked(double mouseX, double mouseY, int button)
{
if (mouseY >= (double) (this.height - 22) && this.minecraft != null && this.minecraft.player != null) {
if (mouseY >= (double) (this.height - 22) && this.client != null && this.client.player != null) {
int centerX = this.width / 2;
if (mouseX >= (double) (centerX - 90) && mouseX <= (double) (centerX + 90)) {
for (int slot = 0; slot < 9; ++slot) {
int slotX = centerX - 90 + slot * 20 + 2;
if (mouseX >= (double) slotX && mouseX <= (double) (slotX + 20)) {
this.minecraft.player.inventory.selectedSlot = slot;
this.client.player.inventory.selectedSlot = slot;
return true;
}
}
@@ -281,17 +266,17 @@ public class TouchscreenOverlay extends Screen
@Override
public boolean mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY)
{
if (button == GLFW.GLFW_MOUSE_BUTTON_1 && this.minecraft != null) {
if (button == GLFW.GLFW_MOUSE_BUTTON_1 && this.client != null) {
if (deltaY > 0.01)
this.mod.input.handleLook(this.minecraft, GLFW_GAMEPAD_AXIS_RIGHT_Y, (float) Math.abs(deltaY / 5.0), 2);
this.mod.input.handleLook(this.client, GLFW_GAMEPAD_AXIS_RIGHT_Y, (float) Math.abs(deltaY / 5.0), 2);
else if (deltaY < 0.01)
this.mod.input.handleLook(this.minecraft, GLFW_GAMEPAD_AXIS_RIGHT_Y, (float) Math.abs(deltaY / 5.0), 1);
this.mod.input.handleLook(this.client, GLFW_GAMEPAD_AXIS_RIGHT_Y, (float) Math.abs(deltaY / 5.0), 1);
if (deltaX > 0.01)
this.mod.input.handleLook(this.minecraft, GLFW_GAMEPAD_AXIS_RIGHT_X, (float) Math.abs(deltaX / 5.0), 2);
this.mod.input.handleLook(this.client, GLFW_GAMEPAD_AXIS_RIGHT_X, (float) Math.abs(deltaX / 5.0), 2);
else if (deltaX < 0.01)
this.mod.input.handleLook(this.minecraft, GLFW_GAMEPAD_AXIS_RIGHT_X, (float) Math.abs(deltaX / 5.0), 1);
this.mod.input.handleLook(this.client, GLFW_GAMEPAD_AXIS_RIGHT_X, (float) Math.abs(deltaX / 5.0), 1);
}
return super.mouseDragged(mouseX, mouseY, button, deltaX, deltaY);
}
}*/
}

View File

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

View File

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

View File

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

View File

@@ -1,5 +1,5 @@
/*
* Copyright © 2020 LambdAurora <aurora42lambda@gmail.com>
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of LambdaControls.
*
@@ -7,15 +7,14 @@
* see the LICENSE file.
*/
package me.lambdaurora.lambdacontrols.client.mixin;
package dev.lambdaurora.lambdacontrols.client.mixin;
import net.minecraft.client.gui.widget.AbstractButtonWidget;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
@Mixin(AbstractButtonWidget.class)
public interface AbstractButtonWidgetAccessor
{
public interface AbstractButtonWidgetAccessor {
@Accessor("height")
int lambdacontrols_getHeight();
int getHeight();
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright © 2020 LambdAurora <aurora42lambda@gmail.com>
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of LambdaControls.
*
@@ -7,7 +7,7 @@
* see the LICENSE file.
*/
package me.lambdaurora.lambdacontrols.client.mixin;
package dev.lambdaurora.lambdacontrols.client.mixin;
import net.minecraft.advancement.Advancement;
import net.minecraft.client.gui.screen.advancement.AdvancementTab;
@@ -22,14 +22,13 @@ import java.util.Map;
* Represents an accessor of {@link AdvancementsScreen}.
*/
@Mixin(AdvancementsScreen.class)
public interface AdvancementsScreenAccessor
{
public interface AdvancementsScreenAccessor {
@Accessor("advancementHandler")
ClientAdvancementManager lambdacontrols_getAdvancementManager();
ClientAdvancementManager getAdvancementManager();
@Accessor("tabs")
Map<Advancement, AdvancementTab> lambdacontrols_getTabs();
Map<Advancement, AdvancementTab> getTabs();
@Accessor("selectedTab")
AdvancementTab lambdacontrols_getSelectedTab();
AdvancementTab getSelectedTab();
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright © 2020 LambdAurora <aurora42lambda@gmail.com>
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of LambdaControls.
*
@@ -7,10 +7,11 @@
* see the LICENSE file.
*/
package me.lambdaurora.lambdacontrols.client.mixin;
package dev.lambdaurora.lambdacontrols.client.mixin;
import com.mojang.authlib.GameProfile;
import me.lambdaurora.lambdacontrols.client.LambdaControlsClient;
import dev.lambdaurora.lambdacontrols.client.LambdaControlsClient;
import dev.lambdaurora.lambdacontrols.client.controller.MovementHandler;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.input.Input;
import net.minecraft.client.network.AbstractClientPlayerEntity;
@@ -29,9 +30,8 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
* Injects the anti fly drifting feature.
*/
@Mixin(ClientPlayerEntity.class)
public abstract class ClientPlayerEntityMixin extends AbstractClientPlayerEntity
{
private boolean lambdacontrols_driftingPrevented = false;
public abstract class ClientPlayerEntityMixin extends AbstractClientPlayerEntity {
private boolean lambdacontrols$driftingPrevented = false;
@Shadow
protected abstract boolean hasMovementInput();
@@ -46,32 +46,34 @@ public abstract class ClientPlayerEntityMixin extends AbstractClientPlayerEntity
@Shadow
protected abstract boolean isCamera();
public ClientPlayerEntityMixin(ClientWorld world, GameProfile profile)
{
public ClientPlayerEntityMixin(ClientWorld world, GameProfile profile) {
super(world, profile);
}
@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 lambdacontrols_move(MovementType type, Vec3d movement, CallbackInfo ci)
{
public void onMove(MovementType type, Vec3d movement, CallbackInfo ci) {
LambdaControlsClient mod = LambdaControlsClient.get();
if (type == MovementType.SELF) {
if (this.abilities.flying && (!mod.config.hasFlyDrifting() || !mod.config.hasFlyVerticalDrifting())) {
if (!this.hasMovementInput()) {
if (!this.lambdacontrols_driftingPrevented) {
if (!this.lambdacontrols$driftingPrevented) {
if (!mod.config.hasFlyDrifting())
this.setVelocity(this.getVelocity().multiply(0, 1.0, 0));
}
this.lambdacontrols_driftingPrevented = true;
this.lambdacontrols$driftingPrevented = true;
} else
this.lambdacontrols_driftingPrevented = false;
this.lambdacontrols$driftingPrevented = false;
}
}
}
@Inject(method = "tickMovement", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/input/Input;tick(Z)V", shift = At.Shift.AFTER))
public void onInputUpdate(CallbackInfo ci) {
MovementHandler.HANDLER.applyMovement((ClientPlayerEntity) (Object) this);
}
@Inject(method = "tickMovement", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/network/ClientPlayerEntity;isCamera()Z"))
public void lambdacontrols_tickMovement(CallbackInfo ci)
{
public void onTickMovement(CallbackInfo ci) {
if (this.abilities.flying && this.isCamera()) {
if (LambdaControlsClient.get().config.hasFlyVerticalDrifting())
return;

View File

@@ -1,5 +1,5 @@
/*
* Copyright © 2020 LambdAurora <aurora42lambda@gmail.com>
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of LambdaControls.
*
@@ -7,18 +7,17 @@
* see the LICENSE file.
*/
package me.lambdaurora.lambdacontrols.client.mixin;
package dev.lambdaurora.lambdacontrols.client.mixin;
import me.lambdaurora.lambdacontrols.client.gui.ControllerControlsScreen;
import me.lambdaurora.lambdacontrols.client.gui.LambdaControlsSettingsScreen;
import dev.lambdaurora.lambdacontrols.client.gui.LambdaControlsSettingsScreen;
import net.minecraft.client.gui.screen.Screen;
import net.minecraft.client.gui.screen.options.ControlsOptionsScreen;
import net.minecraft.client.gui.screen.options.GameOptionsScreen;
import net.minecraft.client.gui.widget.AbstractButtonWidget;
import net.minecraft.client.gui.widget.ButtonWidget;
import net.minecraft.client.options.GameOptions;
import net.minecraft.client.resource.language.I18n;
import net.minecraft.text.Text;
import net.minecraft.text.TranslatableText;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
@@ -27,20 +26,17 @@ import org.spongepowered.asm.mixin.injection.Redirect;
* Injects the new controls settings button.
*/
@Mixin(ControlsOptionsScreen.class)
public class ControlsOptionsScreenMixin extends GameOptionsScreen
{
public ControlsOptionsScreenMixin(Screen parent, GameOptions gameOptions, Text text)
{
public class ControlsOptionsScreenMixin extends GameOptionsScreen {
public ControlsOptionsScreenMixin(Screen parent, GameOptions gameOptions, Text text) {
super(parent, gameOptions, text);
}
@Redirect(method = "init", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screen/options/ControlsOptionsScreen;addButton(Lnet/minecraft/client/gui/widget/AbstractButtonWidget;)Lnet/minecraft/client/gui/widget/AbstractButtonWidget;", ordinal = 1))
private AbstractButtonWidget on_init(ControlsOptionsScreen screen, AbstractButtonWidget btn)
{
if (this.parent instanceof ControllerControlsScreen)
private AbstractButtonWidget onInit(ControlsOptionsScreen screen, AbstractButtonWidget btn) {
/*if (this.parent instanceof ControllerControlsWidget)
return this.addButton(btn);
else
return this.addButton(new ButtonWidget(btn.x, btn.y, btn.getWidth(), ((AbstractButtonWidgetAccessor) btn).lambdacontrols_getHeight(), I18n.translate("menu.options"),
b -> this.minecraft.openScreen(new LambdaControlsSettingsScreen(this, this.gameOptions, true))));
else*/
return this.addButton(new ButtonWidget(btn.x, btn.y, btn.getWidth(), ((AbstractButtonWidgetAccessor) btn).getHeight(), new TranslatableText("menu.options"),
b -> this.client.openScreen(new LambdaControlsSettingsScreen(this, true))));
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright © 2020 LambdAurora <aurora42lambda@gmail.com>
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of LambdaControls.
*
@@ -7,11 +7,11 @@
* see the LICENSE file.
*/
package me.lambdaurora.lambdacontrols.client.mixin;
package dev.lambdaurora.lambdacontrols.client.mixin;
import net.minecraft.client.gui.screen.ingame.CreativeInventoryScreen;
import net.minecraft.container.Slot;
import net.minecraft.item.ItemGroup;
import net.minecraft.screen.slot.Slot;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.spongepowered.asm.mixin.Mixin;
@@ -22,15 +22,14 @@ import org.spongepowered.asm.mixin.gen.Invoker;
* Represents an accessor to CreativeInventoryScreen.
*/
@Mixin(CreativeInventoryScreen.class)
public interface CreativeInventoryScreenAccessor
{
public interface CreativeInventoryScreenAccessor {
/**
* Gets the selected tab.
*
* @return The selected tab index.
*/
@Accessor("selectedTab")
int lambdacontrols_getSelectedTab();
int getSelectedTab();
/**
* Sets the selected tab.
@@ -38,7 +37,7 @@ public interface CreativeInventoryScreenAccessor
* @param group The tab's item group.
*/
@Invoker("setSelectedTab")
void lambdacontrols_setSelectedTab(@NotNull ItemGroup group);
void lambdacontrols$setSelectedTab(@NotNull ItemGroup group);
/**
* Returns whether the slot belongs to the creative inventory or not.
@@ -47,5 +46,13 @@ public interface CreativeInventoryScreenAccessor
* @return True if the slot is from the creative inventory, else false.
*/
@Invoker("isCreativeInventorySlot")
boolean lambdacontrols_isCreativeInventorySlot(@Nullable Slot slot);
boolean lambdacontrols$isCreativeInventorySlot(@Nullable Slot slot);
/**
* Returns whether the current tab has a scrollbar or not.
*
* @return True if the current tab has a scrollbar, else false.
*/
@Invoker("hasScrollbar")
boolean lambdacontrols$hasScrollbar();
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright © 2020 LambdAurora <aurora42lambda@gmail.com>
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of LambdaControls.
*
@@ -7,15 +7,14 @@
* see the LICENSE file.
*/
package me.lambdaurora.lambdacontrols.client.mixin;
package dev.lambdaurora.lambdacontrols.client.mixin;
import net.minecraft.client.gui.widget.EntryListWidget;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Invoker;
@Mixin(EntryListWidget.class)
public interface EntryListWidgetAccessor
{
public interface EntryListWidgetAccessor {
@Invoker("moveSelection")
void lambdacontrols_moveSelection(int amount);
void lambdacontrols$moveSelection(EntryListWidget.MoveDirection direction);
}

View File

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

View File

@@ -1,5 +1,5 @@
/*
* Copyright © 2020 LambdAurora <aurora42lambda@gmail.com>
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of LambdaControls.
*
@@ -7,10 +7,10 @@
* see the LICENSE file.
*/
package me.lambdaurora.lambdacontrols.client.mixin;
package dev.lambdaurora.lambdacontrols.client.mixin;
import me.lambdaurora.lambdacontrols.ControlsMode;
import me.lambdaurora.lambdacontrols.client.LambdaControlsClient;
import dev.lambdaurora.lambdacontrols.ControlsMode;
import dev.lambdaurora.lambdacontrols.client.LambdaControlsClient;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.render.GameRenderer;
import org.spongepowered.asm.mixin.Final;
@@ -21,15 +21,13 @@ import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(GameRenderer.class)
public class GameRendererMixin
{
public class GameRendererMixin {
@Shadow
@Final
private MinecraftClient client;
@Inject(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/Mouse;getX()D"))
private void onRender(float tickDelta, long startTime, boolean fullRender, CallbackInfo ci)
{
private void onRender(float tickDelta, long startTime, boolean fullRender, CallbackInfo ci) {
if (this.client.currentScreen != null && LambdaControlsClient.get().config.getControlsMode() == ControlsMode.CONTROLLER)
LambdaControlsClient.get().input.onPreRenderScreen(this.client, this.client.currentScreen);
}

View File

@@ -0,0 +1,67 @@
/*
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
*
* This file is part of LambdaControls.
*
* Licensed under the MIT license. For more information,
* see the LICENSE file.
*/
package dev.lambdaurora.lambdacontrols.client.mixin;
import dev.lambdaurora.lambdacontrols.ControlsMode;
import dev.lambdaurora.lambdacontrols.client.LambdaControlsClient;
import dev.lambdaurora.lambdacontrols.client.compat.LambdaControlsCompat;
import dev.lambdaurora.lambdacontrols.client.gui.LambdaControlsRenderer;
import dev.lambdaurora.lambdacontrols.client.util.HandledScreenAccessor;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gui.screen.ingame.HandledScreen;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.screen.slot.Slot;
import net.minecraft.screen.slot.SlotActionType;
import org.jetbrains.annotations.Nullable;
import org.lwjgl.glfw.GLFW;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
import org.spongepowered.asm.mixin.gen.Invoker;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
/**
* Represents the mixin for the class ContainerScreen.
*/
@Mixin(HandledScreen.class)
public abstract class HandledScreenMixin implements HandledScreenAccessor {
@Accessor("x")
public abstract int getX();
@Accessor("y")
public abstract int getY();
@Invoker("getSlotAt")
public abstract Slot lambdacontrols$getSlotAt(double posX, double posY);
@Invoker("isClickOutsideBounds")
public abstract boolean lambdacontrols$isClickOutsideBounds(double mouseX, double mouseY, int x, int y, int button);
@Invoker("onMouseClick")
public abstract void lambdacontrols$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 (LambdaControlsClient.get().config.getControlsMode() == ControlsMode.CONTROLLER) {
MinecraftClient client = MinecraftClient.getInstance();
int x = 2, y = client.getWindow().getScaledHeight() - 2 - LambdaControlsRenderer.ICON_SIZE;
x = LambdaControlsRenderer.drawButtonTip(matrices, x, y, new int[]{GLFW.GLFW_GAMEPAD_BUTTON_A}, "lambdacontrols.action.pickup_all", true, client) + 2;
x = LambdaControlsRenderer.drawButtonTip(matrices, x, y, new int[]{GLFW.GLFW_GAMEPAD_BUTTON_B}, "lambdacontrols.action.exit", true, client) + 2;
if (LambdaControlsCompat.isReiPresent()) {
x = 2;
y -= 24;
}
x = LambdaControlsRenderer.drawButtonTip(matrices, x, y, new int[]{GLFW.GLFW_GAMEPAD_BUTTON_X}, "lambdacontrols.action.pickup", true, client) + 2;
LambdaControlsRenderer.drawButtonTip(matrices, x, y, new int[]{GLFW.GLFW_GAMEPAD_BUTTON_Y}, "lambdacontrols.action.quick_move", true, client);
}
}
}

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