mirror of
https://github.com/TeamMidnightDust/MidnightControls.git
synced 2025-12-13 07:15:10 +01:00
Compare commits
103 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
24288b3ebd | ||
|
|
0226bfc62f | ||
|
|
28d92a65d4 | ||
|
|
132bdc0cc5 | ||
|
|
bdffef681f | ||
|
|
a49a64ef44 | ||
|
|
0e192b2af4 | ||
|
|
6dd654f2f5 | ||
|
|
eb406a4681 | ||
|
|
d386e3d9a2 | ||
|
|
5fcf4c2b1b | ||
|
|
bfaa4f5d9a | ||
|
|
5d5d808403 | ||
|
|
d08647b3fc | ||
|
|
1039ea5344 | ||
|
|
bb19ea4da5 | ||
|
|
a1773cc0d9 | ||
|
|
e652390583 | ||
|
|
f62819d9a4 | ||
|
|
978b94176c | ||
|
|
c8989cb9ee | ||
|
|
97521e832d | ||
|
|
400b625716 | ||
|
|
f89eebf0e7 | ||
|
|
cc0a9aebe2 | ||
|
|
b8ec934c10 | ||
|
|
ac8fab83a2 | ||
|
|
1489b796a8 | ||
|
|
ab8b78f75a | ||
|
|
c7026ba1fd | ||
|
|
0532913d69 | ||
|
|
597700b2bc | ||
|
|
4edb0952d8 | ||
|
|
178f19dc2c | ||
|
|
129c3d0f3b | ||
|
|
deccb758ea | ||
|
|
b7c60f8651 | ||
|
|
1ee75bc4e5 | ||
|
|
1f0ddab36b | ||
|
|
8a919934e2 | ||
|
|
0178f5e684 | ||
|
|
c495e2ae0c | ||
|
|
3ecce09775 | ||
|
|
e94d2eb240 | ||
|
|
7571a2aa1b | ||
|
|
d09a225518 | ||
|
|
4290d79bd0 | ||
|
|
f9f0a7a18d | ||
|
|
f889fc6367 | ||
|
|
7dcbda7e57 | ||
|
|
bd065f7b5b | ||
|
|
2785d634dc | ||
|
|
257f01ec19 | ||
|
|
127c44a046 | ||
|
|
272ef6b1dd | ||
|
|
155278130f | ||
|
|
3f221feb26 | ||
|
|
7cfd8c9b77 | ||
|
|
2c53575d17 | ||
|
|
24f7054eff | ||
|
|
4669e446dc | ||
|
|
ff672f05a1 | ||
|
|
756e7d102d | ||
|
|
e676a37c7f | ||
|
|
45dd94fd34 | ||
|
|
f8cc1fcc20 | ||
|
|
d56126c680 | ||
|
|
5a2fad4445 | ||
|
|
0eadc054b6 | ||
|
|
07a296603a | ||
|
|
8e082404f9 | ||
|
|
8efddb24b0 | ||
|
|
581757aaee | ||
|
|
40dc8d424c | ||
|
|
230a9f6424 | ||
|
|
ba01df036b | ||
|
|
ff90d55351 | ||
|
|
efc2d6284d | ||
|
|
9e2c4720e9 | ||
|
|
8063116820 | ||
|
|
0050b0216c | ||
|
|
e13569b71d | ||
|
|
47b0cfdad4 | ||
|
|
f56eb9e0ad | ||
|
|
bc712f138b | ||
|
|
66721b00b9 | ||
|
|
e0762b16ee | ||
|
|
7bfff00841 | ||
|
|
a8c969b176 | ||
|
|
f1607fe1bf | ||
|
|
d52a630cb4 | ||
|
|
502e9ff15e | ||
|
|
32e4ad1b7c | ||
|
|
4ae6404ad2 | ||
|
|
185d5e4303 | ||
|
|
0d02e56134 | ||
|
|
738d094fb1 | ||
|
|
7c06afb4a4 | ||
|
|
647e03134f | ||
|
|
1b5a0e470e | ||
|
|
e6da7adf89 | ||
|
|
46d43c6f46 | ||
|
|
9ae1bc2a3b |
23
.github/workflows/gradlebuild.yml
vendored
Executable file
23
.github/workflows/gradlebuild.yml
vendored
Executable file
@@ -0,0 +1,23 @@
|
||||
name: Gradle Build
|
||||
|
||||
on: [ push, pull_request ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up JDK 16
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 16
|
||||
server-id: github # Value of the distributionManagement/repository/id field of the pom.xml
|
||||
settings-path: ${{ github.workspace }} # location for the settings.xml file
|
||||
|
||||
- name: Build with Gradle
|
||||
run: ./gradlew build
|
||||
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: Artifacts
|
||||
path: ./build/libs/
|
||||
40
.github/workflows/gradlepublish.yml
vendored
Executable file
40
.github/workflows/gradlepublish.yml
vendored
Executable file
@@ -0,0 +1,40 @@
|
||||
name: Gradle Package
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '*'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up JDK 16
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 16
|
||||
server-id: github # Value of the distributionManagement/repository/id field of the pom.xml
|
||||
settings-path: ${{ github.workspace }} # location for the settings.xml file
|
||||
|
||||
- name: Build with Gradle
|
||||
run: ./gradlew build
|
||||
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: Artifacts
|
||||
path: ./build/libs/
|
||||
|
||||
# The USERNAME and PASSWORD need to correspond to the credentials environment variables used in
|
||||
# the publishing section of your build.gradle
|
||||
- name: Publish to GitHub Packages and other Mavens
|
||||
run: ./gradlew publish
|
||||
env:
|
||||
BRANCH_NAME: ${{ github.ref }}
|
||||
RUN_COUNT: ${{ github.run_number }}
|
||||
REPO_NAME: ${{ github.repository }}
|
||||
USERNAME: ${{ github.actor }}
|
||||
TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
midnightcontrols_MAVEN: ${{ secrets.MAVEN_URL }}
|
||||
MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }}
|
||||
MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }}
|
||||
23
.github/workflows/modrinth_update.yml
vendored
Executable file
23
.github/workflows/modrinth_update.yml
vendored
Executable file
@@ -0,0 +1,23 @@
|
||||
name: Gradle Build
|
||||
|
||||
on:
|
||||
release:
|
||||
types:
|
||||
- published
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up JDK 16
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 16
|
||||
server-id: github # Value of the distributionManagement/repository/id field of the pom.xml
|
||||
settings-path: ${{ github.workspace }} # location for the settings.xml file
|
||||
|
||||
- name: Build with Gradle
|
||||
env:
|
||||
MODRINTH_TOKEN: ${{ secrets.MODRINTH_TOKEN }}
|
||||
run: ./gradlew publishModrinth
|
||||
9
.gitignore
vendored
9
.gitignore
vendored
@@ -1,11 +1,13 @@
|
||||
#
|
||||
# LambdAurora's ignore file
|
||||
#
|
||||
# v0.12
|
||||
# v0.15
|
||||
|
||||
# JetBrains
|
||||
.idea/
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
## Intellij IDEA
|
||||
out/
|
||||
## CLion
|
||||
@@ -51,6 +53,8 @@ __pycache__/
|
||||
# OS
|
||||
## Windows
|
||||
desktop.ini
|
||||
# MacOS
|
||||
.DS_Store
|
||||
|
||||
# File types
|
||||
*.dll
|
||||
@@ -59,10 +63,13 @@ desktop.ini
|
||||
*.lib
|
||||
lib*.a
|
||||
*.png~
|
||||
*.tar.?z
|
||||
|
||||
# Common
|
||||
bin/
|
||||
build/
|
||||
dist/
|
||||
lib/
|
||||
obj/
|
||||
run/
|
||||
target/
|
||||
|
||||
151
CHANGELOG.md
Normal file
151
CHANGELOG.md
Normal file
@@ -0,0 +1,151 @@
|
||||
# Changelog
|
||||
|
||||
# MidnightControls
|
||||
|
||||
## 0.1.0
|
||||
First beta!
|
||||
Changes from LambdaControls:
|
||||
|
||||
- Support for Steam Deck and Dualsense
|
||||
- Support for L4, L5, R4, R5 buttons
|
||||
- Updated Libraries
|
||||
- New Logo and Name
|
||||
- Lots of Bugfixes
|
||||
- MidnightConfig backend
|
||||
|
||||
|
||||
# LambdaControls
|
||||
|
||||
## 1.0.0
|
||||
|
||||
:tada: First release! :tada:
|
||||
|
||||
- Added controller support.
|
||||
- Added new controls settings GUI.
|
||||
- Added experimental touchscreen support.
|
||||
- Added controller controls GUI.
|
||||
- Added a lot of options.
|
||||
- Added key bindings for look around.
|
||||
- And more!
|
||||
|
||||
### 1.0.1
|
||||
|
||||
- Fixed tutorial toast to look around not affected by camera movement done with a controller. ([#2](https://github.com/LambdAurora/LambdaControls/issues/2))
|
||||
|
||||
### 1.0.2 (Unofficial)
|
||||
|
||||
This update was never pushed but was aiming to fix [#4](https://github.com/LambdAurora/LambdaControls/issues/4).
|
||||
|
||||
- Fixed the toggle sneak button binding.
|
||||
- Fixed broken chat arrow keys.
|
||||
- Optimized a little bit the button indicator. (need more work)
|
||||
|
||||
## 1.1.0 - Chording update
|
||||
|
||||
This update also has a backport 1.14.4 version ([#9](https://github.com/LambdAurora/LambdaControls/issues/9)).
|
||||
|
||||
- Rewrote everything (almost).
|
||||
- Added [networking](https://github.com/LambdAurora/LambdaControls/wiki/LambdaControls-Networking) for some features.
|
||||
- Added second controller support (Joycons supported now hopefully).
|
||||
- Added chording.
|
||||
- Added better developer API
|
||||
- Added hover messages ([#5](https://github.com/LambdAurora/LambdaControls/issues/5)).
|
||||
- Added hotbar button bindings ([#7](https://github.com/LambdAurora/LambdaControls/issues/7)).
|
||||
- Added front block placing feature ([#8](https://github.com/LambdAurora/LambdaControls/issues/8)).
|
||||
- Added no creative fly drifting ([#8](https://github.com/LambdAurora/LambdaControls/issues/8)).
|
||||
- Added option to enable controller focus.
|
||||
- Added [OkZoomer](https://github.com/joaoh1/OkZoomer) compatibility.
|
||||
- Added D-pad movements in inventories.
|
||||
- Increased max speed ranges.
|
||||
- Added [SpruceUI](https://github.com/LambdAurora/SpruceUI) for cleaner custom UI widgets.
|
||||
- Added reset settings button.
|
||||
- HUD side affects button indicators now.
|
||||
- Added support for Advancements tabs.
|
||||
|
||||
### 1.1.1
|
||||
|
||||
## 1.2.0-1.3.0
|
||||
|
||||
- Improved rotation algorithm ([#11](https://github.com/LambdAurora/LambdaControls/issues/11)).
|
||||
- Added virtual mouse.
|
||||
- Added outline on front block placing.
|
||||
- Added fast block placement ([#8](https://github.com/LambdAurora/LambdaControls/issues/8)).
|
||||
- Added [REI](https://www.curseforge.com/minecraft/mc-mods/roughly-enough-items) compatibility.
|
||||
- Improved HUD.
|
||||
- Added recipe book control.
|
||||
- And more!
|
||||
- v1.3.0 specific: Updated to Minecraft 1.16.1
|
||||
|
||||
### 1.3.1
|
||||
|
||||
- Fixed broken inventory interactions ([#13](https://github.com/LambdAurora/LambdaControls/issues/13))
|
||||
- Fixed virtual mouse preventing continuous attack (thus making breaking blocks impossible).
|
||||
- Added support for [ModUpdater](https://gitea.thebrokenrail.com/TheBrokenRail/ModUpdater) hopefully.
|
||||
- Updated [SpruceUI](https://github.com/LambdAurora/SpruceUI) to 1.5.2.
|
||||
|
||||
### 1.3.2
|
||||
|
||||
- Added vertical reacharound.
|
||||
- Added more API for compatibility handlers.
|
||||
- Improved reacharound API.
|
||||
- Improved [REI](https://www.curseforge.com/minecraft/mc-mods/roughly-enough-items) compatibility.
|
||||
|
||||
## 1.4.0
|
||||
|
||||
- Added analog movements ([#10](https://github.com/LambdAurora/LambdaControls/issues/10)).
|
||||
- Improved Ok Zoomer compability.
|
||||
- Updated [SpruceUI](https://github.com/LambdAurora/SpruceUI) to 1.5.8 to ensure 1.16.2 compability.
|
||||
- Internal changes:
|
||||
- Added analog input value to button bindings.
|
||||
- Replace lot of strings with Texts.
|
||||
- Improved block outline rendering injection.
|
||||
- Shadow library jars instead of Jar-in-Jar.
|
||||
- Fixed crash in inventory ([#16](https://github.com/LambdAurora/LambdaControls/issues/16))
|
||||
- WIP:
|
||||
- Started to work on action ring.
|
||||
- Will allow for better compability with other mods.
|
||||
- Might be interesting for keyboard users too.
|
||||
|
||||
### 1.4.1
|
||||
|
||||
- Fixed crash with [REI](https://www.curseforge.com/minecraft/mc-mods/roughly-enough-items).
|
||||
|
||||
## 1.5.0
|
||||
|
||||
- Added mappings string editor screen.
|
||||
- Added Simplified Chinese translations ([#18](https://github.com/LambdAurora/LambdaControls/pull/18)).
|
||||
- Added Mexican Spanish translations ([#22](https://github.com/LambdAurora/LambdaControls/pull/22)).
|
||||
- Added Xbox 360 button skin and overhauled Xbox button skin.
|
||||
- Added debug option.
|
||||
- Respect toggle setting in Accessibility screen.
|
||||
- Tweaked rotation speeds.
|
||||
- Updated to Minecraft 1.16.2.
|
||||
- Updated [SpruceUI] to 1.6.4.
|
||||
- Overhauled REI compatibility.
|
||||
- Improved horizontal reach-around.
|
||||
- Fixed crashes with Ok Zoomer.
|
||||
- Fixed crashes with key unbinding.
|
||||
- More WIP on keybind ring.
|
||||
|
||||
## 1.6.0
|
||||
|
||||
- Reworked entirely the settings screen.
|
||||
- Added independent stick dead zones. ([#32](https://github.com/LambdAurora/LambdaControls/issues/32))
|
||||
- Added max values range. ([#41](https://github.com/LambdAurora/LambdaControls/issues/41))
|
||||
- Updated [SpruceUI] and fix related crashes due to incompatible versions ([#40](https://github.com/LambdAurora/LambdaControls/issues/40), [#48](https://github.com/LambdAurora/LambdaControls/issues/48)).
|
||||
- Fix boat control issues ([#37](https://github.com/LambdAurora/LambdaControls/issues/37)).
|
||||
- Fix incompatibilities with mods using night-config. Now shadowing properly night-config. ([#33](https://github.com/LambdAurora/LambdaControls/issues/33), [#39](https://github.com/LambdAurora/LambdaControls/issues/39))
|
||||
|
||||
## 1.7.0
|
||||
|
||||
- Updated to 1.17.
|
||||
- Small improvements to the codebase thanks to Java 16.
|
||||
- Fix controller bindings not being saved ([#31](https://github.com/LambdAurora/LambdaControls/issues/31), [#55](https://github.com/LambdAurora/LambdaControls/issues/55)).
|
||||
- Dropped entirely Touchscreen Input Mode.
|
||||
- Dropped Roughly Enough Items compatibility.
|
||||
|
||||
### 1.7.1
|
||||
|
||||
- Fix crash at startup.
|
||||
|
||||
[SpruceUI]: https://github.com/LambdAurora/SpruceUI
|
||||
@@ -1,8 +1,8 @@
|
||||
# Contributing to LambdaControls
|
||||
# Contributing to midnightcontrols
|
||||
|
||||
:tada: First of all, thanks for taking time to contribute! :tada:
|
||||
|
||||
The following is a set of guidelines for contributing to LambdaControls.
|
||||
The following is a set of guidelines for contributing to MidnightControls.
|
||||
Feel free to propose changes to this document in a pull request.
|
||||
|
||||
**Table of Contents**
|
||||
@@ -17,8 +17,8 @@ Feel free to propose changes to this document in a pull request.
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
This project and everyone participating in it is governed by the [Code of Conduct](https://github.com/LambdAurora/LambdaControls/blob/master/CODE_OF_CONDUCT.md).
|
||||
By participating, you are expected to uphold this code. Please report unacceptable behavior at [aurora42lambda@gmail.com](mailto:aurora42lambda@gmail.com).
|
||||
This project and everyone participating in it is governed by the [Code of Conduct](https://github.com/LambdAurora/midnightcontrols/blob/master/CODE_OF_CONDUCT.md).
|
||||
By participating, you are expected to uphold this code. Please report unacceptable behavior at [motschen@midnightdust.eu](mailto:motschen@midnightdust.eu).
|
||||
|
||||
## What should I know before I get started?
|
||||
|
||||
@@ -26,9 +26,9 @@ By participating, you are expected to uphold this code. Please report unacceptab
|
||||
|
||||
[Fabric](https://fabricmc.net/) is the mod loader and the software which allows Gradle to setup the workspace.
|
||||
|
||||
### Java 8
|
||||
### Java 17
|
||||
|
||||
Java is the main language used to make LambdaControls alive.
|
||||
Java is the main language used to make MidnightControls alive.
|
||||
Knowing how to code in Java is necessary if you contribute to the code.
|
||||
|
||||
### Minecraft
|
||||
@@ -45,9 +45,9 @@ As it is a Minecraft mod you should know a bit how Minecraft works and how moddi
|
||||
|
||||
### Git
|
||||
|
||||
Git is the control version software we use for LambdaControls, please know how to use it if you consider contributing to the code.
|
||||
Git is the control version software we use for midnightcontrols, please know how to use it if you consider contributing to the code.
|
||||
|
||||
Git commits should be and must be signed.
|
||||
Git commits should be signed.
|
||||
|
||||
## How can I contribute?
|
||||
|
||||
@@ -55,21 +55,21 @@ Git commits should be and must be signed.
|
||||
|
||||
#### Before submitting a bug report
|
||||
|
||||
- Check if you can reproduce it on other platforms, on multiple web browsers.
|
||||
- Check if you can reproduce it on other platforms.
|
||||
- Perform a search to see if the problem has already been reported. If it has **and the issue is still open**, add a comment to the existing issue instead of opening a new one.
|
||||
|
||||
#### How do I submit a bug report?
|
||||
|
||||
Go in the issues tab in GitHub and read the [bug report guide](https://github.com/LambdAurora/LambdaControls/blob/master/.github/ISSUE_TEMPLATE/bug_report.md)
|
||||
Go in the issues tab in GitHub and read the [bug report guide](https://github.com/TeamMidnightDust/MidnightControls/blob/1.18/.github/ISSUE_TEMPLATE/bug_report.md)
|
||||
|
||||
### Suggesting enhancements
|
||||
|
||||
Enhancement suggestions are tracked as [GitHub issues](https://github.com/LambdAurora/LambdaControls/issues).
|
||||
Check out the [feature request](https://github.com/LambdAurora/LambdaControls/blob/master/.github/ISSUE_TEMPLATE/feature_request.md) guide.
|
||||
Enhancement suggestions are tracked as [GitHub issues](https://github.com/TeamMidnightDust/MidnightControls/issues).
|
||||
Check out the [feature request](https://github.com/TeamMidnightDust/MidnightControls/blob/1.17/.github/ISSUE_TEMPLATE/feature_request.md) guide.
|
||||
|
||||
### Do pull requests
|
||||
|
||||
You can help LambdaControls by writing code and submit it with pull requests.
|
||||
You can help midnightcontrols by writing code and submit it with pull requests.
|
||||
|
||||
Pull requests will be accepted if they follow the [styleguide](#styleguides), if they are useful, etc...
|
||||
We can refuse a pull request if the commits are not signed, so don't forget to [sign them](https://help.github.com/en/articles/signing-commits)!
|
||||
@@ -81,21 +81,16 @@ Feel free to pull request!
|
||||
### Git commit messages
|
||||
|
||||
* Use the imperative mood ("Move cursor to..." not "Moves cursor to...")
|
||||
* Consider starting the commit message with an emote, emotes for your commit can be found at the [gitmoji guide](https://gitmoji.carloscuesta.me/).
|
||||
* (Not for the message) Don't forget to sign the commit.
|
||||
|
||||
### Naming convention
|
||||
|
||||
Names in the code should be explicit and always in `snake_case`, `camelCase` will not be allowed.
|
||||
Names in the code should be explicit and always in `camelCase`, `snake_case` will not be allowed.
|
||||
`PascalCase` can be used for class name.
|
||||
|
||||
We chose `snake_case` because it is more accessible for everyone: for people who don't speak English as their native language it is more easy to see the words when they are separated,
|
||||
it also allows the correct use of screen reader on the code with `snake_case` due to the absence of upper case characters.
|
||||
|
||||
### Brace placement
|
||||
|
||||
Every braces should be at the end of the line of function declaration, etc...
|
||||
The only exception is class declarations: braces must be on the next line.
|
||||
Every braces should be at the end of the line of function declaration, etc.
|
||||
|
||||
### Quick note for users of the Intellij IDEA IDE
|
||||
|
||||
|
||||
7
HEADER
Normal file
7
HEADER
Normal file
@@ -0,0 +1,7 @@
|
||||
Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
|
||||
Copyright © 2022 Motschen <motschen@midnightdust.eu>
|
||||
|
||||
This file is part of MidnightControls.
|
||||
|
||||
Licensed under the MIT license. For more information,
|
||||
see the LICENSE file.
|
||||
5
LICENSE
5
LICENSE
@@ -1,6 +1,7 @@
|
||||
MIT License
|
||||
|
||||
Copyright © 2019 LambdAurora <aurora42lambda@gmail.com>
|
||||
Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
|
||||
Copyright © 2022 Motschen <motschen@midnightdust.eu>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -18,4 +19,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
SOFTWARE.
|
||||
|
||||
62
README.md
62
README.md
@@ -1,8 +1,60 @@
|
||||
# LambdaControls
|
||||
# MidnightControls
|
||||
|
||||

|
||||
[](https://raw.githubusercontent.com/LambdAurora/LambdaControls/master/LICENSE)
|
||||
<!-- modrinth_exclude.start -->
|
||||
 <!-- modrinth_exclude.end -->
|
||||
[](https://raw.githubusercontent.com/TeamMidnightDust/MidnightControls/master/LICENSE)
|
||||

|
||||
[![Mod loader: Quilt/Fabric]][quilt] <!-- modrinth_exclude.start -->
|
||||

|
||||
[](https://www.curseforge.com/minecraft/mc-mods/midnightcontrols)
|
||||
<!-- modrinth_exclude.end -->
|
||||
|
||||
A Fabric Minecraft mod which add better controls.
|
||||
It allows the use of a controller or the touchscreen.
|
||||
A Fabric Minecraft mod which adds better controls, reach-around and controller support.
|
||||
Forked from [LambdaControls](https://github.com/LambdAurora/LambdaControls) by the amazing [LambdAurora](https://github.com/LambdAurora), which was sadly discontinued.
|
||||
|
||||
## 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 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).
|
||||
- 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.
|
||||
- An easy API for developers to add their own button bindings.
|
||||
|
||||
## 🎮 Supported Controllers:
|
||||
|
||||
- Dualshock controllers
|
||||
- Dualsense controllers
|
||||
- Xbox controllers
|
||||
- Switch Pro controllers
|
||||
- Joycons
|
||||
- Steam controller and Steam Deck (WIP)
|
||||
- And many more!
|
||||
|
||||
## Screenshots
|
||||
|
||||

|
||||

|
||||
|
||||
<!-- modrinth_exclude.start -->
|
||||
## Build
|
||||
|
||||
Just do `./gradlew build` and everything should build just fine!
|
||||
<!-- modrinth_exclude.end -->
|
||||
|
||||
[quilt]: https://quiltmc.org
|
||||
[Mod loader: Quilt/Fabric]: https://img.shields.io/badge/modloader-Quilt%2FFabric-blueviolet?logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAACXBIWXMAAADGAAAAxgGwdJvFAAAFU0lEQVR4nO1bwW7bOBCdCHtP+wUNxIvhS7yA7kkPOjf9AKPam4/OF2zzB96bblXhH0jPPkS5G1jnIvgib/IFtX/ALoYZemmKtEWRRe3aDzAsURbJGQ6HM4/02Wq1gmNGcNTSnxQA8IdakDK4AOCfJpj3SpiI98r2rAMAbxrWNWFFOMeL1nCJdXQa1gPTbpCbnm34ABIeBThv2hgAfOyVcF+2ZwkAfHGo54kVIRe6NVxiny4d6vpn2g36ugfqFLhwFB6kkUoc67k0XLv0qYKj9wEnBXiqZwEAf/ZKOOuV8BkLWBFesyI8A4C/LOt6AoC39C7HtBvg9Vt65hUnC/BUDzrOf1MGq5S9WkDZnuVle7ZqsBKgw/tO73K0hku8/u64EmhxsoA96MMvxdEroBIKO+CWoshnqqJPYTAGRJ8sql3QO3Op7D19D3z7AV8KeAGArFf+32lWhDwnKNuzuaUCclaE93KBiOU9hMQV+FLAOxz9lPHRR0VkZXs2oBDUNon5ULZnKOgcYwl4FTwna/K+CtRRwIK+dTnCCwkP9I0fkXmh4FcN+6UKqqtHblvtb+18po4TvKaPilsS8qVuYx5havsbJXS1I0ZVAXP1B5jfyzm+hAnN+We1XPluioX03kJtY9oNtG1T+b1aburDxhRAQVMGHw3z9k65F41nktmjUnjjrAj7ZXv27ECIyEKgBd7saBuka7lsPu0GA1MjR0+KVpwgMTlrMkN4YoztlZ/2camTvD0QjcWZF6KxMgcLyKbdAN+HKOb1y6PYH49gEsUwUKw1G48gi2KQZcApkYxH1eldUQBxeKbkRfXEQjDZ21+h2bMiHJDwH2wk1rSX0XWueHZd2yCZ/oVSnilTaA3VAiqjRRahQ0JBjuov3ijfPqAua0kUg67tmyjWCmvsS504wGQRnywjPJ8wtY3xw3827fjMBXziUalL3DcNrIzYRwXcTbvBZ7lgPHoNxKKYky1/+2xsHxXQbw2X/EIoggQHyjC9Yh8VcC6NshDc66jLOHpC5KSAPejDL8U+KuArboTQZgjHeARn+MFnvhvbRyfYaQ2XfNkTVFgUr/mIxlvkJuyjAjCae6BrYQUPW37vhDpT4MnAsCyIgbHBnYbcADJt7/t+deBCiSWsCG8sOv5IgU2FnJh2g+RnBDl1oCpgogqEx1TEURUFokx+tpCYnEzzjg1kh+fq/Ix9OXpG6OjjgI1VIGWcOMhlXh4PPdAz1VTe90rIU8Z/L9JUnALXSK4SjSUzOY+Y1ekyOlzjaamTvf3X8cjunBH1v6OeVtsG1QI66qYEVkoVmyA/O5fYmIHjgSsrskUavAc6q1BLeXXiANMZu0HKwGav7opGv+LticZqHOToLBeZrJRxC97qjOsowCTgZYO9OlNa25haMwgvsFMJPp3gLW1ji8b6dO89fhfYIbzAl23T4adtjyNvD6/mbbs9Xgs1hRcwWoIvCxDb47nQNm5aRDHvoPGcblNYCi+gtQSfU+AdLYfioLXYtHA9eruBhsILVJRwUIGQo/ACG0o4GAV4El5grQSffMA3SqbEnBdb1zeunfYsvAB3jL4UwE92KauA2NnNbLerNKhEqJ6Q+FIAOjpcATAVzjFHoC3qC9OurCW0W9s+6vXpAy4p0hPkSUL3ziNHiY3tqfNdQN4jORgnSEGMLyU8Udbq1QIEdEyRF3hSwlp4vKkwQimrHDuxAR6S4tleFIPzERnhSFXQEtbkD1kbwiMOlhJroISK8IiDpcQsp4NWeMRBc4I1lWAUHnHwpOgOJWwVHvFbsMIGJewUHvFb7QuQYxR/ttgIzbUAgB9KOM3uc+PN7QAAAABJRU5ErkJggg==
|
||||
|
||||
297
build.gradle
297
build.gradle
@@ -1,104 +1,255 @@
|
||||
plugins {
|
||||
id 'fabric-loom' version '0.2.6-SNAPSHOT'
|
||||
id 'maven-publish'
|
||||
id 'fabric-loom' version '0.11-SNAPSHOT'
|
||||
id 'java-library'
|
||||
id 'maven-publish'
|
||||
id 'com.github.johnrengelman.shadow' version '7.0.0'
|
||||
id 'com.modrinth.minotaur' version '1.2.+'
|
||||
}
|
||||
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.google.gson.JsonObject
|
||||
import com.modrinth.minotaur.TaskModrinthUpload
|
||||
import com.modrinth.minotaur.request.VersionType
|
||||
import com.modrinth.minotaur.responses.ResponseError
|
||||
import org.apache.http.client.config.CookieSpecs
|
||||
import org.apache.http.client.config.RequestConfig
|
||||
import org.apache.http.client.entity.EntityBuilder
|
||||
import org.apache.http.client.methods.HttpPatch
|
||||
import org.apache.http.entity.ContentType
|
||||
import org.apache.http.impl.client.HttpClientBuilder
|
||||
import org.apache.http.util.EntityUtils
|
||||
|
||||
archivesBaseName = project.archives_base_name
|
||||
version = project.mod_version
|
||||
group = project.maven_group
|
||||
version = "${project.mod_version}+${getMCVersionString()}"
|
||||
archivesBaseName = project.archives_base_name
|
||||
|
||||
minecraft {
|
||||
// This field defines the Java version your mod target.
|
||||
def targetJavaVersion = 17
|
||||
|
||||
boolean isMCVersionNonRelease() {
|
||||
return project.minecraft_version.matches('^\\d\\dw\\d\\d[a-z]$')
|
||||
|| project.minecraft_version.matches('\\d+\\.\\d+-(pre|rc)(\\d+)')
|
||||
}
|
||||
|
||||
String getMCVersionString() {
|
||||
if (isMCVersionNonRelease()) {
|
||||
return project.minecraft_version
|
||||
}
|
||||
def version = project.minecraft_version.split('\\.')
|
||||
return version[0] + '.' + version[1]
|
||||
}
|
||||
|
||||
boolean pingUrl(String address) {
|
||||
try {
|
||||
def conn = (HttpURLConnection) new URL(address).openConnection()
|
||||
int responseCode = conn.getResponseCode()
|
||||
return (200 <= responseCode && responseCode <= 399)
|
||||
} catch (IOException ignored) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
String parseReadme() {
|
||||
def excludeRegex = /(?m)<!-- modrinth_exclude\.start -->(.|\n)*?<!-- modrinth_exclude\.end -->/
|
||||
def linkRegex = /!\[([A-z_ ]+)]\((images\/[A-z.\/_]+)\)/
|
||||
|
||||
def readme = (String) file('README.md').text
|
||||
readme = readme.replaceAll(excludeRegex, '')
|
||||
return readme
|
||||
}
|
||||
|
||||
|
||||
repositories {
|
||||
mavenLocal()
|
||||
mavenLocal()
|
||||
mavenCentral()
|
||||
maven { url = 'https://aperlambda.github.io/maven' }
|
||||
maven {
|
||||
name = 'CottonMC'
|
||||
url = 'http://server.bbkr.space:8081/artifactory/libs-snapshot'
|
||||
}
|
||||
repositories {
|
||||
maven { url = "https://jitpack.io" }
|
||||
}
|
||||
maven { url 'https://aperlambda.github.io/maven' }
|
||||
maven {
|
||||
name 'Gegy'
|
||||
url 'https://maven.gegy.dev'
|
||||
}
|
||||
maven {
|
||||
name 'CottonMC'
|
||||
url 'https://server.bbkr.space/artifactory/libs-snapshot'
|
||||
}
|
||||
maven { url "https://maven.terraformersmc.com/releases/" }
|
||||
maven { url 'https://maven.kosmx.dev' }
|
||||
maven { url 'https://maven.shedaniel.me/' }
|
||||
maven { url 'https://jitpack.io' }
|
||||
maven { url "https://api.modrinth.com/maven" }
|
||||
maven { url 'https://maven.quiltmc.org/repository/release'}
|
||||
}
|
||||
|
||||
configurations {
|
||||
shadow
|
||||
api.extendsFrom shadow
|
||||
}
|
||||
|
||||
dependencies {
|
||||
//to change the versions see the gradle.properties file
|
||||
minecraft "com.mojang:minecraft:${project.minecraft_version}"
|
||||
mappings "net.fabricmc:yarn:${project.yarn_mappings}"
|
||||
modCompile "net.fabricmc:fabric-loader:${project.loader_version}"
|
||||
//to change the versions see the gradle.properties file
|
||||
minecraft "com.mojang:minecraft:${project.minecraft_version}"
|
||||
mappings "net.fabricmc:yarn:${project.yarn_mappings}:v2"
|
||||
modImplementation "net.fabricmc:fabric-loader:${project.loader_version}"
|
||||
|
||||
// Fabric API. This is technically optional, but you probably want it anyway.
|
||||
modApi "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}"
|
||||
// Fabric API. This is technically optional, but you probably want it anyway.
|
||||
modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}"
|
||||
|
||||
modCompile "io.github.prospector:modmenu:1.8.0+build.16"
|
||||
//modCompile "io.github.cottonmc:cotton-client-commands:0.4.2+1.14.3-SNAPSHOT"
|
||||
modImplementation "dev.lambdaurora:spruceui:${project.spruceui_version}"
|
||||
include "dev.lambdaurora:spruceui:${project.spruceui_version}"
|
||||
api('org.aperlambda:lambdajcommon:1.8.1') {
|
||||
exclude group: 'com.google.code.gson'
|
||||
exclude group: 'com.google.guava'
|
||||
}
|
||||
include 'org.aperlambda:lambdajcommon:1.8.1'
|
||||
|
||||
implementation "org.jetbrains:annotations:17.0.0"
|
||||
implementation "org.aperlambda:lambdajcommon:1.7.2"
|
||||
implementation "com.electronwill.night-config:core:3.5.3"
|
||||
implementation "com.electronwill.night-config:toml:3.5.3"
|
||||
include "org.jetbrains:annotations:17.0.0"
|
||||
include "org.aperlambda:lambdajcommon:1.7.2"
|
||||
include "com.electronwill.night-config:core:3.5.3"
|
||||
include "com.electronwill.night-config:toml:3.5.3"
|
||||
modImplementation "com.terraformersmc:modmenu:${project.modmenu_version}"
|
||||
|
||||
// PSA: Some older mods, compiled on Loom 0.2.1, might have outdated Maven POMs.
|
||||
// You may need to force-disable transitiveness on them.
|
||||
// Compatibility mods
|
||||
modImplementation("maven.modrinth:ok-zoomer:${project.ok_zoomer_version}")
|
||||
modImplementation "io.github.ennuil:LibZoomer:${project.libzoomer_version}"
|
||||
modImplementation "org.quiltmc:quilt-json5:1.0.0"
|
||||
modImplementation("me.shedaniel:RoughlyEnoughItems-runtime-fabric:${project.rei_version}")
|
||||
modImplementation "maven.modrinth:emotecraft:${project.emotecraft_version}"
|
||||
modImplementation "io.github.kosmx:bendy-lib:${project.bendylib_version}"
|
||||
|
||||
modImplementation "maven.modrinth:midnightlib:${project.midnightlib_version}"
|
||||
include "maven.modrinth:midnightlib:${project.midnightlib_version}"
|
||||
|
||||
shadow 'com.electronwill.night-config:core:3.6.3'
|
||||
shadow 'com.electronwill.night-config:toml:3.6.3'
|
||||
}
|
||||
loom {
|
||||
accessWidenerPath = file("src/main/resources/midnightcontrols.accesswidener")
|
||||
}
|
||||
|
||||
java {
|
||||
sourceCompatibility = JavaVersion.toVersion(targetJavaVersion)
|
||||
targetCompatibility = JavaVersion.toVersion(targetJavaVersion)
|
||||
|
||||
withSourcesJar()
|
||||
}
|
||||
|
||||
tasks.withType(JavaCompile).configureEach {
|
||||
it.options.encoding = 'UTF-8'
|
||||
|
||||
it.options.release.set(targetJavaVersion)
|
||||
}
|
||||
|
||||
processResources {
|
||||
inputs.property "version", project.version
|
||||
inputs.property 'version', project.version
|
||||
|
||||
from(sourceSets.main.resources.srcDirs) {
|
||||
include "fabric.mod.json"
|
||||
expand "version": project.version
|
||||
}
|
||||
|
||||
from(sourceSets.main.resources.srcDirs) {
|
||||
exclude "fabric.mod.json"
|
||||
}
|
||||
filesMatching('fabric.mod.json') {
|
||||
expand 'version': project.version
|
||||
}
|
||||
}
|
||||
|
||||
// ensure that the encoding is set to UTF-8, no matter what the system default is
|
||||
// this fixes some edge cases with special characters not displaying correctly
|
||||
// see http://yodaconditions.net/blog/fix-for-java-file-encoding-problems-with-gradle.html
|
||||
tasks.withType(JavaCompile) {
|
||||
options.encoding = "UTF-8"
|
||||
}
|
||||
task publishModrinth(type: TaskModrinthUpload) {
|
||||
dependsOn(build)
|
||||
onlyIf {
|
||||
System.getenv('MODRINTH_TOKEN')
|
||||
}
|
||||
|
||||
// Loom will automatically attach sourcesJar to a RemapSourcesJar task and to the "build" task
|
||||
// if it is present.
|
||||
// If you remove this task, sources will not be generated.
|
||||
task sourcesJar(type: Jar, dependsOn: classes) {
|
||||
classifier = "sources"
|
||||
from sourceSets.main.allSource
|
||||
}
|
||||
token = System.getenv('MODRINTH_TOKEN')
|
||||
projectId = project.modrinth_id
|
||||
versionNumber = version
|
||||
versionName = "midnightcontrols ${project.mod_version} (${getMCVersionString()})"
|
||||
addGameVersion((String) project.minecraft_version)
|
||||
addLoader('fabric')
|
||||
versionType = isMCVersionNonRelease() ? VersionType.BETA : VersionType.RELEASE
|
||||
|
||||
jar {
|
||||
from "LICENSE"
|
||||
// Changelog fetching
|
||||
def changelogText = file('CHANGELOG.md').text
|
||||
def regexVersion = ((String) project.mod_version).replaceAll('\\.', /\\./).replaceAll('\\+', '\\+')
|
||||
def changelogRegex = ~"###? ${regexVersion}\\n\\n(( *- .+\\n)+)"
|
||||
def matcher = changelogText =~ changelogRegex
|
||||
|
||||
if (matcher.find()) {
|
||||
changelog = matcher.group(1)
|
||||
|
||||
def changelogLines = changelogText.split('\n')
|
||||
def linkRefRegex = ~'^\\[([A-z0-9 _\\-/+.]+)]: '
|
||||
for (int i = changelogLines.length - 1; i > 0; i--) {
|
||||
def line = changelogLines[i]
|
||||
if ((line =~ linkRefRegex).find())
|
||||
changelog += '\n' + line
|
||||
else break
|
||||
}
|
||||
}
|
||||
|
||||
// Readme
|
||||
doFirst {
|
||||
final def client = HttpClientBuilder.create().setDefaultRequestConfig(RequestConfig.custom().setCookieSpec(CookieSpecs.IGNORE_COOKIES).build()).build()
|
||||
final def patch = new HttpPatch((String) (apiURL + '/v1/mod/' + projectId))
|
||||
patch.addHeader("Authorization", token)
|
||||
|
||||
var json = new JsonObject()
|
||||
json.addProperty("body", parseReadme())
|
||||
patch.setEntity(EntityBuilder.create()
|
||||
.setText(json.toString())
|
||||
.setContentType(ContentType.APPLICATION_JSON)
|
||||
.build())
|
||||
|
||||
final def response = client.execute(patch)
|
||||
final int status = response.getStatusLine().getStatusCode()
|
||||
|
||||
final def gson = new GsonBuilder().create()
|
||||
if (status == 200) {
|
||||
project.getLogger().lifecycle("Successfully updated readme to ${projectId}.")
|
||||
} else {
|
||||
errorInfo = gson.fromJson(EntityUtils.toString(response.getEntity()), ResponseError.class)
|
||||
project.getLogger().error("Upload failed! Status: ${status} Error: ${errorInfo.getError()} Reason: ${errorInfo.getDescription()}")
|
||||
throw new GradleException("Upload failed! Status: ${status} Reason: ${errorInfo.getDescription()}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// configure the maven publication
|
||||
publishing {
|
||||
publications {
|
||||
mavenJava(MavenPublication) {
|
||||
// add all the jars that should be included when publishing to maven
|
||||
artifact(remapJar) {
|
||||
builtBy remapJar
|
||||
}
|
||||
artifact(sourcesJar) {
|
||||
builtBy remapSourcesJar
|
||||
}
|
||||
}
|
||||
}
|
||||
publications {
|
||||
mavenJava(MavenPublication) {
|
||||
artifact(sourcesJar) {
|
||||
builtBy remapSourcesJar
|
||||
}
|
||||
|
||||
// select the repositories you want to publish to
|
||||
repositories {
|
||||
mavenLocal()
|
||||
}
|
||||
pom {
|
||||
name = 'midnightcontrols'
|
||||
description = 'Adds better controls, and controller support.'
|
||||
}
|
||||
|
||||
pom.withXml {
|
||||
def dependenciesNode = asNode().appendNode('dependencies')
|
||||
|
||||
configurations.shadow.allDependencies.each {
|
||||
def dependencyNode = dependenciesNode.appendNode('dependency')
|
||||
|
||||
dependencyNode.appendNode('groupId', it.group)
|
||||
dependencyNode.appendNode('artifactId', it.name)
|
||||
dependencyNode.appendNode('version', it.version)
|
||||
dependencyNode.appendNode('scope', 'compile')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenLocal()
|
||||
maven {
|
||||
name 'GithubPackages'
|
||||
url uri('https://maven.pkg.github.com/LambdAurora/midnightcontrols')
|
||||
credentials {
|
||||
username = project.findProperty("gpr.user") ?: System.getenv("USERNAME")
|
||||
password = project.findProperty("gpr.key") ?: System.getenv("TOKEN")
|
||||
}
|
||||
}
|
||||
def midnightcontrolsMaven = System.getenv('midnightcontrols_MAVEN')
|
||||
if (midnightcontrolsMaven) {
|
||||
maven {
|
||||
name 'midnightcontrolsMaven'
|
||||
url uri(midnightcontrolsMaven)
|
||||
credentials {
|
||||
username = project.findProperty('gpr.user') ?: System.getenv('MAVEN_USERNAME')
|
||||
password = project.findProperty('gpr.key') ?: System.getenv('MAVEN_PASSWORD')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
303
controller.svg
Normal file
303
controller.svg
Normal file
@@ -0,0 +1,303 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="1024.0px"
|
||||
height="1024.0px"
|
||||
viewBox="0 0 1024.0 1024.0"
|
||||
version="1.1"
|
||||
id="SVGRoot"
|
||||
sodipodi:docname="controller.svg"
|
||||
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04, custom)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<sodipodi:namedview
|
||||
id="namedview893"
|
||||
pagecolor="#505050"
|
||||
bordercolor="#eeeeee"
|
||||
borderopacity="1"
|
||||
inkscape:pageshadow="0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:document-units="px"
|
||||
showgrid="true"
|
||||
inkscape:zoom="0.42961102"
|
||||
inkscape:cx="455.06282"
|
||||
inkscape:cy="790.24975"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1050"
|
||||
inkscape:window-x="1920"
|
||||
inkscape:window-y="30"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="layer1">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid956" />
|
||||
</sodipodi:namedview>
|
||||
<defs
|
||||
id="defs888">
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
id="linearGradient1152">
|
||||
<stop
|
||||
style="stop-color:#ed02a0;stop-opacity:1"
|
||||
offset="0"
|
||||
id="stop1148" />
|
||||
<stop
|
||||
style="stop-color:#8900cb;stop-opacity:1"
|
||||
offset="1"
|
||||
id="stop1150" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient1152"
|
||||
id="linearGradient1154"
|
||||
x1="0"
|
||||
y1="0"
|
||||
x2="1020"
|
||||
y2="1020"
|
||||
gradientUnits="userSpaceOnUse" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient1152"
|
||||
id="linearGradient4289"
|
||||
x1="0"
|
||||
y1="0"
|
||||
x2="1020"
|
||||
y2="1020"
|
||||
gradientUnits="userSpaceOnUse" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient1152"
|
||||
id="linearGradient4663"
|
||||
x1="0"
|
||||
y1="0"
|
||||
x2="1020"
|
||||
y2="1020"
|
||||
gradientUnits="userSpaceOnUse" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient1152"
|
||||
id="linearGradient5165"
|
||||
x1="0"
|
||||
y1="0"
|
||||
x2="1020"
|
||||
y2="1020"
|
||||
gradientUnits="userSpaceOnUse" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient1152"
|
||||
id="linearGradient5507"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="0"
|
||||
y1="0"
|
||||
x2="1020"
|
||||
y2="1020" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient1152"
|
||||
id="linearGradient5817"
|
||||
x1="0"
|
||||
y1="0"
|
||||
x2="1020"
|
||||
y2="1020"
|
||||
gradientUnits="userSpaceOnUse" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient1152"
|
||||
id="linearGradient6161"
|
||||
x1="0"
|
||||
y1="0"
|
||||
x2="1020"
|
||||
y2="1020"
|
||||
gradientUnits="userSpaceOnUse" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient1152"
|
||||
id="linearGradient6535"
|
||||
x1="0"
|
||||
y1="0"
|
||||
x2="1025"
|
||||
y2="1025"
|
||||
gradientUnits="userSpaceOnUse" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient1152"
|
||||
id="linearGradient7579"
|
||||
x1="0"
|
||||
y1="0"
|
||||
x2="1025"
|
||||
y2="1025"
|
||||
gradientUnits="userSpaceOnUse" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient1152"
|
||||
id="linearGradient8115"
|
||||
x1="0"
|
||||
y1="0"
|
||||
x2="1025"
|
||||
y2="1025"
|
||||
gradientUnits="userSpaceOnUse" />
|
||||
</defs>
|
||||
<g
|
||||
inkscape:label="Ebene 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1">
|
||||
<rect
|
||||
style="fill:url(#linearGradient1154);fill-opacity:1"
|
||||
id="rect1033"
|
||||
width="1025"
|
||||
height="1025"
|
||||
x="0"
|
||||
y="0"
|
||||
rx="0"
|
||||
ry="0" />
|
||||
<rect
|
||||
style="fill:#ffffff;fill-opacity:1;stroke-width:1.41813"
|
||||
id="rect2078"
|
||||
width="671.12531"
|
||||
height="330.82504"
|
||||
x="178.42294"
|
||||
y="283.78436"
|
||||
ry="110.27502" />
|
||||
<rect
|
||||
style="fill:#ffffff;fill-opacity:1;stroke-width:1.41852"
|
||||
id="rect2146"
|
||||
width="199.30905"
|
||||
height="504.61328"
|
||||
x="518.59406"
|
||||
y="450.07187"
|
||||
ry="97.030388"
|
||||
transform="matrix(0.97214881,-0.23436445,0.26615211,0.96393104,0,0)" />
|
||||
<rect
|
||||
style="fill:#ffffff;fill-opacity:1;stroke-width:1.41852"
|
||||
id="rect2146-3"
|
||||
width="200.94308"
|
||||
height="502.92538"
|
||||
x="271.98715"
|
||||
y="209.38025"
|
||||
ry="97.050156"
|
||||
transform="matrix(0.97237519,0.23342343,-0.26569554,0.96405699,0,0)" />
|
||||
<rect
|
||||
style="fill:#ffffff;fill-opacity:1;stroke-width:1.41813"
|
||||
id="rect2384"
|
||||
width="87.538078"
|
||||
height="55.137505"
|
||||
x="251.37137"
|
||||
y="270"
|
||||
ry="27.568752" />
|
||||
<rect
|
||||
style="fill:#ffffff;fill-opacity:1;stroke-width:1.41813"
|
||||
id="rect2384-6"
|
||||
width="87.538078"
|
||||
height="55.137505"
|
||||
x="691.97968"
|
||||
y="270"
|
||||
ry="27.568752" />
|
||||
<path
|
||||
style="fill:url(#linearGradient4663);fill-opacity:1"
|
||||
id="path2566"
|
||||
sodipodi:type="arc"
|
||||
sodipodi:cx="302.43524"
|
||||
sodipodi:cy="400.95157"
|
||||
sodipodi:rx="51.063881"
|
||||
sodipodi:ry="48.245316"
|
||||
sodipodi:start="2.7492077"
|
||||
sodipodi:end="2.731929"
|
||||
sodipodi:open="true"
|
||||
sodipodi:arc-type="arc"
|
||||
d="m 255.25223,419.40025 a 51.063881,48.245316 0 0 1 27.4529,-62.94723 51.063881,48.245316 0 0 1 66.74267,25.66543 51.063881,48.245316 0 0 1 -26.87629,63.16896 51.063881,48.245316 0 0 1 -66.97486,-25.11969" />
|
||||
<path
|
||||
style="fill:url(#linearGradient5817);fill-opacity:1"
|
||||
id="path2566-7"
|
||||
sodipodi:type="arc"
|
||||
sodipodi:cx="663.29633"
|
||||
sodipodi:cy="527.23883"
|
||||
sodipodi:rx="51.063881"
|
||||
sodipodi:ry="48.245316"
|
||||
sodipodi:start="2.7492077"
|
||||
sodipodi:end="2.731929"
|
||||
sodipodi:open="true"
|
||||
sodipodi:arc-type="arc"
|
||||
d="m 616.11332,545.68751 a 51.063881,48.245316 0 0 1 27.45289,-62.94723 51.063881,48.245316 0 0 1 66.74267,25.66543 51.063881,48.245316 0 0 1 -26.87628,63.16896 51.063881,48.245316 0 0 1 -66.97487,-25.11969" />
|
||||
<path
|
||||
style="fill:url(#linearGradient5165);fill-opacity:1"
|
||||
id="path2710"
|
||||
sodipodi:type="arc"
|
||||
sodipodi:cx="740.12561"
|
||||
sodipodi:cy="345.81409"
|
||||
sodipodi:rx="21.88452"
|
||||
sodipodi:ry="20.676565"
|
||||
sodipodi:start="2.7492077"
|
||||
sodipodi:end="2.731929"
|
||||
sodipodi:open="true"
|
||||
sodipodi:arc-type="arc"
|
||||
d="m 719.90432,353.72066 a 21.88452,20.676565 0 0 1 11.76553,-26.97738 21.88452,20.676565 0 0 1 28.604,10.99947 21.88452,20.676565 0 0 1 -11.51841,27.07241 21.88452,20.676565 0 0 1 -28.70351,-10.76558" />
|
||||
<path
|
||||
style="fill:url(#linearGradient5507);fill-opacity:1"
|
||||
id="path2710-5"
|
||||
sodipodi:type="arc"
|
||||
sodipodi:cx="785.35364"
|
||||
sodipodi:cy="385.78876"
|
||||
sodipodi:rx="21.88452"
|
||||
sodipodi:ry="20.676565"
|
||||
sodipodi:start="2.7492077"
|
||||
sodipodi:end="2.731929"
|
||||
sodipodi:open="true"
|
||||
sodipodi:arc-type="arc"
|
||||
d="m 765.13235,393.69534 a 21.88452,20.676565 0 0 1 11.76553,-26.97739 21.88452,20.676565 0 0 1 28.604,10.99947 21.88452,20.676565 0 0 1 -11.51841,27.07241 21.88452,20.676565 0 0 1 -28.70351,-10.76558" />
|
||||
<path
|
||||
style="fill:url(#linearGradient6535);fill-opacity:1"
|
||||
id="path2710-3"
|
||||
sodipodi:type="arc"
|
||||
sodipodi:cx="697.81555"
|
||||
sodipodi:cy="385.78876"
|
||||
sodipodi:rx="21.88452"
|
||||
sodipodi:ry="20.676565"
|
||||
sodipodi:start="2.7492077"
|
||||
sodipodi:end="2.731929"
|
||||
sodipodi:open="true"
|
||||
sodipodi:arc-type="arc"
|
||||
d="m 677.59426,393.69534 a 21.88452,20.676565 0 0 1 11.76553,-26.97739 21.88452,20.676565 0 0 1 28.604,10.99947 21.88452,20.676565 0 0 1 -11.51841,27.07241 21.88452,20.676565 0 0 1 -28.70351,-10.76558" />
|
||||
<path
|
||||
style="fill:url(#linearGradient6161);fill-opacity:1"
|
||||
id="path2710-56"
|
||||
sodipodi:type="arc"
|
||||
sodipodi:cx="741.58459"
|
||||
sodipodi:cy="427.14188"
|
||||
sodipodi:rx="21.88452"
|
||||
sodipodi:ry="20.676565"
|
||||
sodipodi:start="2.7492077"
|
||||
sodipodi:end="2.731929"
|
||||
sodipodi:open="true"
|
||||
sodipodi:arc-type="arc"
|
||||
d="m 721.36331,435.04845 a 21.88452,20.676565 0 0 1 11.76552,-26.97738 21.88452,20.676565 0 0 1 28.604,10.99947 21.88452,20.676565 0 0 1 -11.51841,27.07241 21.88452,20.676565 0 0 1 -28.70351,-10.76558" />
|
||||
<rect
|
||||
style="fill:url(#linearGradient8115);fill-opacity:1;stroke-width:1.41813"
|
||||
id="rect2896"
|
||||
width="36.474197"
|
||||
height="110.27502"
|
||||
x="369.54779"
|
||||
y="476.76566"
|
||||
ry="13.784376" />
|
||||
<rect
|
||||
style="fill:url(#linearGradient7579);fill-opacity:1;stroke-width:1.41813"
|
||||
id="rect2898"
|
||||
width="116.71745"
|
||||
height="37.217815"
|
||||
x="327.2377"
|
||||
y="512.60504"
|
||||
ry="13.383304" />
|
||||
<rect
|
||||
style="fill:url(#linearGradient4289);fill-opacity:1;stroke-width:1.41813"
|
||||
id="rect3104"
|
||||
width="233.43489"
|
||||
height="110.27502"
|
||||
x="400.1861"
|
||||
y="338.92191"
|
||||
ry="13.383307" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 9.3 KiB |
@@ -2,17 +2,25 @@
|
||||
org.gradle.jvmargs=-Xmx1G
|
||||
|
||||
# Fabric Properties
|
||||
# check these on https://fabricmc.net/use
|
||||
minecraft_version=1.15.1
|
||||
yarn_mappings=1.15.1+build.3:v2
|
||||
loader_version=0.7.2+build.174
|
||||
# check these on https://fabricmc.net/use
|
||||
minecraft_version=1.18.1
|
||||
yarn_mappings=1.18.1+build.22
|
||||
loader_version=0.13.3
|
||||
|
||||
# Mod Properties
|
||||
mod_version = 1.0.1
|
||||
maven_group = me.lambdaurora
|
||||
archives_base_name = lambdacontrols
|
||||
mod_version = 0.2.0
|
||||
maven_group = eu.midnightdust
|
||||
archives_base_name = midnightcontrols
|
||||
modrinth_id=W1D3UXEc
|
||||
|
||||
# Dependencies
|
||||
# currently not on the main fabric site, check on the maven: https://maven.fabricmc.net/net/fabricmc/fabric-api/fabric-api
|
||||
fabric_version=0.4.25+build.282-1.15
|
||||
|
||||
# currently not on the main fabric site, check on the maven: https://maven.fabricmc.net/net/fabricmc/fabric-api/fabric-api
|
||||
fabric_version=0.46.6+1.18
|
||||
spruceui_version=3.3.2+1.17
|
||||
midnightlib_version=0.4.0
|
||||
modmenu_version=2.0.2
|
||||
emotecraft_version=2.1-MC1.18.1-fabric
|
||||
bendylib_version=2.0.+
|
||||
rei_version=7.4.456
|
||||
ok_zoomer_version=5.0.0-beta.3+1.17.1
|
||||
libzoomer_version = 0.3.0+1.17.1
|
||||
|
||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
3
gradle/wrapper/gradle-wrapper.properties
vendored
3
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,6 +1,5 @@
|
||||
#Mon Oct 28 18:23:18 CET 2019
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.1-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.5.1-all.zip
|
||||
|
||||
270
gradlew
vendored
Normal file → Executable file
270
gradlew
vendored
Normal file → Executable file
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env sh
|
||||
#!/bin/sh
|
||||
|
||||
#
|
||||
# Copyright 2015 the original author or authors.
|
||||
# Copyright © 2015-2021 the original authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
@@ -17,78 +17,113 @@
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
#
|
||||
# Gradle start up script for POSIX generated by Gradle.
|
||||
#
|
||||
# Important for running:
|
||||
#
|
||||
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
||||
# noncompliant, but you have some other compliant shell such as ksh or
|
||||
# bash, then to run this script, type that shell name before the whole
|
||||
# command line, like:
|
||||
#
|
||||
# ksh Gradle
|
||||
#
|
||||
# Busybox and similar reduced shells will NOT work, because this script
|
||||
# requires all of these POSIX shell features:
|
||||
# * functions;
|
||||
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||
# * compound commands having a testable exit status, especially «case»;
|
||||
# * various built-in commands including «command», «set», and «ulimit».
|
||||
#
|
||||
# Important for patching:
|
||||
#
|
||||
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
||||
# by Bash, Ksh, etc; in particular arrays are avoided.
|
||||
#
|
||||
# The "traditional" practice of packing multiple parameters into a
|
||||
# space-separated string is a well documented source of bugs and security
|
||||
# problems, so this is (mostly) avoided, by progressively accumulating
|
||||
# options in "$@", and eventually passing that to Java.
|
||||
#
|
||||
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
||||
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
||||
# see the in-line comments for details.
|
||||
#
|
||||
# There are tweaks for specific operating systems such as AIX, CygWin,
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
fi
|
||||
app_path=$0
|
||||
|
||||
# Need this for daisy-chained symlinks.
|
||||
while
|
||||
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||
[ -h "$app_path" ]
|
||||
do
|
||||
ls=$( ls -ld "$app_path" )
|
||||
link=${ls#*' -> '}
|
||||
case $link in #(
|
||||
/*) app_path=$link ;; #(
|
||||
*) app_path=$APP_HOME$link ;;
|
||||
esac
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
APP_BASE_NAME=${0##*/}
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
MAX_FD=maximum
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
}
|
||||
} >&2
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
} >&2
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
NONSTOP* )
|
||||
nonstop=true
|
||||
;;
|
||||
case "$( uname )" in #(
|
||||
CYGWIN* ) cygwin=true ;; #(
|
||||
Darwin* ) darwin=true ;; #(
|
||||
MSYS* | MINGW* ) msys=true ;; #(
|
||||
NONSTOP* ) nonstop=true ;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
JAVACMD=$JAVA_HOME/jre/sh/java
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
JAVACMD=$JAVA_HOME/bin/java
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
@@ -97,7 +132,7 @@ Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD="java"
|
||||
JAVACMD=java
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
@@ -105,84 +140,95 @@ location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
fi
|
||||
ulimit -n $MAX_FD
|
||||
if [ $? -ne 0 ] ; then
|
||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||
fi
|
||||
else
|
||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# For Darwin, add options to specify how the application appears in the dock
|
||||
if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin, switch paths to Windows format before running java
|
||||
if $cygwin ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
SEP=""
|
||||
for dir in $ROOTDIRSRAW ; do
|
||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||
SEP="|"
|
||||
done
|
||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||
# Add a user-defined pattern to the cygpath arguments
|
||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||
fi
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
i=0
|
||||
for arg in "$@" ; do
|
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=$((i+1))
|
||||
done
|
||||
case $i in
|
||||
(0) set -- ;;
|
||||
(1) set -- "$args0" ;;
|
||||
(2) set -- "$args0" "$args1" ;;
|
||||
(3) set -- "$args0" "$args1" "$args2" ;;
|
||||
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
case $MAX_FD in #(
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
fi
|
||||
|
||||
# Escape application args
|
||||
save () {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
APP_ARGS=$(save "$@")
|
||||
# Collect all arguments for the java command, stacking in reverse order:
|
||||
# * args from the command line
|
||||
# * the main class name
|
||||
# * -classpath
|
||||
# * -D...appname settings
|
||||
# * --module-path (only if needed)
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
||||
|
||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if "$cygwin" || "$msys" ; then
|
||||
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||
|
||||
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
|
||||
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
|
||||
cd "$(dirname "$0")"
|
||||
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
for arg do
|
||||
if
|
||||
case $arg in #(
|
||||
-*) false ;; # don't mess with options #(
|
||||
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
||||
[ -e "$t" ] ;; #(
|
||||
*) false ;;
|
||||
esac
|
||||
then
|
||||
arg=$( cygpath --path --ignore --mixed "$arg" )
|
||||
fi
|
||||
# Roll the args list around exactly as many times as the number of
|
||||
# args, so each arg winds up back in the position where it started, but
|
||||
# possibly modified.
|
||||
#
|
||||
# NB: a `for` loop captures its iteration list before it begins, so
|
||||
# changing the positional parameters here affects neither the number of
|
||||
# iterations, nor the values presented in `arg`.
|
||||
shift # remove old arg
|
||||
set -- "$@" "$arg" # push replacement arg
|
||||
done
|
||||
fi
|
||||
|
||||
# Collect all arguments for the java command;
|
||||
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
||||
# shell script including quotes and variable substitutions, so put them in
|
||||
# double quotes to make sure that they get re-expanded; and
|
||||
# * put everything else in single quotes, so that it's not re-expanded.
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
-classpath "$CLASSPATH" \
|
||||
org.gradle.wrapper.GradleWrapperMain \
|
||||
"$@"
|
||||
|
||||
# Use "xargs" to parse quoted args.
|
||||
#
|
||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||
#
|
||||
# In Bash we could simply go:
|
||||
#
|
||||
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
||||
# set -- "${ARGS[@]}" "$@"
|
||||
#
|
||||
# but POSIX shell has neither arrays nor command substitution, so instead we
|
||||
# post-process each arg (as a line of input to sed) to backslash-escape any
|
||||
# character that might be a shell metacharacter, then use eval to reverse
|
||||
# that process (while maintaining the separation between arguments), and wrap
|
||||
# the whole thing up as a single "set" statement.
|
||||
#
|
||||
# This will of course break if any of these variables contains a newline or
|
||||
# an unmatched quote.
|
||||
#
|
||||
|
||||
eval "set -- $(
|
||||
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
||||
xargs -n1 |
|
||||
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
||||
tr '\n' ' '
|
||||
)" '"$@"'
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
|
||||
189
gradlew.bat
vendored
189
gradlew.bat
vendored
@@ -1,100 +1,89 @@
|
||||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:init
|
||||
@rem Get command-line arguments, handling Windows variants
|
||||
|
||||
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||
|
||||
:win9xME_args
|
||||
@rem Slurp the command line arguments.
|
||||
set CMD_LINE_ARGS=
|
||||
set _SKIP=2
|
||||
|
||||
:win9xME_args_slurp
|
||||
if "x%~1" == "x" goto execute
|
||||
|
||||
set CMD_LINE_ARGS=%*
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
|
||||
BIN
icon.png
BIN
icon.png
Binary file not shown.
|
Before Width: | Height: | Size: 7.8 KiB After Width: | Height: | Size: 66 KiB |
BIN
images/controller_controls.png
Normal file
BIN
images/controller_controls.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 86 KiB |
BIN
images/controller_options.png
Normal file
BIN
images/controller_options.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 89 KiB |
@@ -1,10 +1,11 @@
|
||||
pluginManagement {
|
||||
repositories {
|
||||
jcenter()
|
||||
maven {
|
||||
name = 'Fabric'
|
||||
url = 'https://maven.fabricmc.net/'
|
||||
name 'Fabric'
|
||||
url 'https://maven.fabricmc.net/'
|
||||
}
|
||||
gradlePluginPortal()
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.name = 'midnightcontrols'
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
|
||||
*
|
||||
* This file is part of midnightcontrols.
|
||||
*
|
||||
* Licensed under the MIT license. For more information,
|
||||
* see the LICENSE file.
|
||||
*/
|
||||
|
||||
package eu.midnightdust.midnightcontrols;
|
||||
|
||||
import dev.lambdaurora.spruceui.util.Nameable;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Represents the controls mode.
|
||||
*
|
||||
* @author LambdAurora
|
||||
* @version 1.7.0
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public enum ControlsMode implements Nameable {
|
||||
DEFAULT,
|
||||
CONTROLLER;
|
||||
|
||||
/**
|
||||
* Returns the next controls mode available.
|
||||
*
|
||||
* @return the next available controls mode
|
||||
*/
|
||||
public ControlsMode next() {
|
||||
var v = values();
|
||||
if (v.length == this.ordinal() + 1)
|
||||
return v[0];
|
||||
return v[this.ordinal() + 1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the translation key of this controls mode.
|
||||
*
|
||||
* @return the translated key of this controls mode
|
||||
* @since 1.1.0
|
||||
*/
|
||||
public String getTranslationKey() {
|
||||
return "midnightcontrols.controls_mode." + this.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String getName() {
|
||||
return this.name().toLowerCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the controls mode from its identifier.
|
||||
*
|
||||
* @param id the identifier of the controls mode
|
||||
* @return the controls mode if found, else empty
|
||||
*/
|
||||
public static Optional<ControlsMode> byId(@NotNull String id) {
|
||||
return Arrays.stream(values()).filter(mode -> mode.getName().equalsIgnoreCase(id)).findFirst();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
|
||||
*
|
||||
* This file is part of midnightcontrols.
|
||||
*
|
||||
* Licensed under the MIT license. For more information,
|
||||
* see the LICENSE file.
|
||||
*/
|
||||
|
||||
package eu.midnightdust.midnightcontrols;
|
||||
|
||||
import eu.midnightdust.midnightcontrols.event.PlayerChangeControlsModeCallback;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import net.fabricmc.api.ModInitializer;
|
||||
import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking;
|
||||
import net.fabricmc.loader.api.FabricLoader;
|
||||
import net.fabricmc.loader.api.ModContainer;
|
||||
import net.minecraft.network.PacketByteBuf;
|
||||
import net.minecraft.text.TranslatableText;
|
||||
import net.minecraft.util.Identifier;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Represents the MidnightControls mod.
|
||||
*
|
||||
* @author LambdAurora & Motschen
|
||||
* @version 1.8.0
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public class MidnightControls implements ModInitializer {
|
||||
private static MidnightControls INSTANCE;
|
||||
public static final Identifier CONTROLS_MODE_CHANNEL = new Identifier(MidnightControlsConstants.CONTROLS_MODE_CHANNEL.toString());
|
||||
public static final Identifier FEATURE_CHANNEL = new Identifier(MidnightControlsConstants.FEATURE_CHANNEL.toString());
|
||||
public static final Identifier HELLO_CHANNEL = new Identifier(MidnightControlsConstants.HELLO_CHANNEL.toString());
|
||||
|
||||
public static final TranslatableText NOT_BOUND_TEXT = new TranslatableText("midnightcontrols.not_bound");
|
||||
|
||||
public final Logger logger = LogManager.getLogger("MidnightControls");
|
||||
|
||||
@Override
|
||||
public void onInitialize() {
|
||||
INSTANCE = this;
|
||||
this.log("Initializing MidnightControls...");
|
||||
|
||||
ServerPlayNetworking.registerGlobalReceiver(HELLO_CHANNEL, (server, player, handler, buf, responseSender) -> {
|
||||
String version = buf.readString(32);
|
||||
ControlsMode.byId(buf.readString(32))
|
||||
.ifPresent(controlsMode -> server
|
||||
.execute(() -> PlayerChangeControlsModeCallback.EVENT.invoker().apply(player, controlsMode)));
|
||||
server.execute(() -> {
|
||||
ServerPlayNetworking.send(player, FEATURE_CHANNEL, this.makeFeatureBuffer(MidnightControlsFeature.HORIZONTAL_REACHAROUND));
|
||||
});
|
||||
});
|
||||
ServerPlayNetworking.registerGlobalReceiver(CONTROLS_MODE_CHANNEL,
|
||||
(server, player, handler, buf, responseSender) -> ControlsMode.byId(buf.readString(32))
|
||||
.ifPresent(controlsMode -> server
|
||||
.execute(() -> PlayerChangeControlsModeCallback.EVENT.invoker().apply(player, controlsMode))));
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints a message to the terminal.
|
||||
*
|
||||
* @param info the message to print
|
||||
*/
|
||||
public void log(String info) {
|
||||
this.logger.info("[MidnightControls] " + info);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints a warning to the terminal.
|
||||
*
|
||||
* @param warning the warning to print
|
||||
*/
|
||||
public void warn(String warning) {
|
||||
this.logger.info("[MidnightControls] " + warning);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a packet byte buffer made for the midnightcontrols:controls_mode plugin message.
|
||||
*
|
||||
* @param controlsMode the controls mode to send
|
||||
* @return the packet byte buffer
|
||||
*/
|
||||
public PacketByteBuf makeControlsModeBuffer(@NotNull ControlsMode controlsMode) {
|
||||
Objects.requireNonNull(controlsMode, "Controls mode cannot be null.");
|
||||
return new PacketByteBuf(Unpooled.buffer()).writeString(controlsMode.getName(), 32);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a packet byte buffer made for the midnightcontrols:feature plugin message.
|
||||
*
|
||||
* @param features the features data to send
|
||||
* @return the packet byte buffer
|
||||
*/
|
||||
public PacketByteBuf makeFeatureBuffer(MidnightControlsFeature... features) {
|
||||
if (features.length == 0)
|
||||
throw new IllegalArgumentException("At least one feature must be provided.");
|
||||
var buffer = new PacketByteBuf(Unpooled.buffer());
|
||||
buffer.writeVarInt(features.length);
|
||||
for (var feature : features) {
|
||||
buffer.writeString(feature.getName(), 64);
|
||||
buffer.writeBoolean(feature.isAllowed());
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
public PacketByteBuf makeHello(@NotNull ControlsMode controlsMode) {
|
||||
var version = "";
|
||||
Optional<ModContainer> container;
|
||||
if ((container = FabricLoader.getInstance().getModContainer(MidnightControlsConstants.NAMESPACE)).isPresent()) {
|
||||
version = container.get().getMetadata().getVersion().getFriendlyString();
|
||||
}
|
||||
return new PacketByteBuf(Unpooled.buffer()).writeString(version, 32).writeString(controlsMode.getName(), 32);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the MidnightControls instance.
|
||||
*
|
||||
* @return the MidnightControls instance
|
||||
*/
|
||||
public static MidnightControls get() {
|
||||
return INSTANCE;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
|
||||
*
|
||||
* This file is part of midnightcontrols.
|
||||
*
|
||||
* Licensed under the MIT license. For more information,
|
||||
* see the LICENSE file.
|
||||
*/
|
||||
|
||||
package eu.midnightdust.midnightcontrols;
|
||||
|
||||
|
||||
import net.minecraft.util.Identifier;
|
||||
|
||||
/**
|
||||
* Represents the constants used by MidnightControls.
|
||||
*
|
||||
* @author LambdAurora
|
||||
* @version 1.1.0
|
||||
* @since 1.1.0
|
||||
*/
|
||||
public class MidnightControlsConstants {
|
||||
public static final String NAMESPACE = "midnightcontrols";
|
||||
public static final Identifier CONTROLS_MODE_CHANNEL = new Identifier(NAMESPACE, "controls_mode");
|
||||
public static final Identifier FEATURE_CHANNEL = new Identifier(NAMESPACE, "feature");
|
||||
public static final Identifier HELLO_CHANNEL = new Identifier(NAMESPACE, "hello");
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
/*
|
||||
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
|
||||
*
|
||||
* This file is part of midnightcontrols.
|
||||
*
|
||||
* Licensed under the MIT license. For more information,
|
||||
* see the LICENSE file.
|
||||
*/
|
||||
|
||||
package eu.midnightdust.midnightcontrols;
|
||||
|
||||
import dev.lambdaurora.spruceui.util.Nameable;
|
||||
import eu.midnightdust.midnightcontrols.client.MidnightControlsConfig;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Represents a feature.
|
||||
*
|
||||
* @author LambdAurora
|
||||
* @version 1.5.0
|
||||
* @since 1.1.0
|
||||
*/
|
||||
public class MidnightControlsFeature implements Nameable {
|
||||
private static final List<MidnightControlsFeature> FEATURES = new ArrayList<>();
|
||||
public static final MidnightControlsFeature FAST_BLOCK_PLACING = new MidnightControlsFeature("fast_block_placing", true, MidnightControlsConfig.fastBlockPlacing);
|
||||
public static final MidnightControlsFeature HORIZONTAL_REACHAROUND = new MidnightControlsFeature("horizontal_reacharound", true, MidnightControlsConfig.horizontalReacharound);
|
||||
public static final MidnightControlsFeature VERTICAL_REACHAROUND = new MidnightControlsFeature("vertical_reacharound", true, MidnightControlsConfig.verticalReacharound);
|
||||
|
||||
private final String key;
|
||||
private final boolean defaultAllowed;
|
||||
private boolean allowed;
|
||||
private final boolean defaultEnabled;
|
||||
private boolean enabled;
|
||||
|
||||
public MidnightControlsFeature(@NotNull String key, boolean allowed, boolean enabled) {
|
||||
Objects.requireNonNull(key, "Feature key cannot be null.");
|
||||
this.key = key;
|
||||
this.setAllowed(this.defaultAllowed = allowed);
|
||||
this.setEnabled(this.defaultEnabled = enabled);
|
||||
}
|
||||
|
||||
public MidnightControlsFeature(@NotNull String key) {
|
||||
this(key, false, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows the feature.
|
||||
*/
|
||||
public void allow() {
|
||||
this.setAllowed(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this feature is allowed.
|
||||
*
|
||||
* @return {@code true} if this feature is allowed, else {@code false}
|
||||
*/
|
||||
public boolean isAllowed() {
|
||||
return this.allowed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether this feature is allowed.
|
||||
*
|
||||
* @param allowed {@code true} if this feature is allowed, else {@code false}
|
||||
*/
|
||||
public void setAllowed(boolean allowed) {
|
||||
this.allowed = allowed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets allowed state to default.
|
||||
*/
|
||||
public void resetAllowed() {
|
||||
this.setAllowed(this.defaultAllowed);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this feature is enabled.
|
||||
*
|
||||
* @return {@code true} if this feature is enabled, else {@code false}
|
||||
*/
|
||||
public boolean isEnabled() {
|
||||
return this.enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this feature is enabled.
|
||||
*
|
||||
* @param enabled {@code true} if this feature is enabled, else {@code false}
|
||||
*/
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this feature is available or not.
|
||||
*
|
||||
* @return {@code true} if this feature is available, else {@code false}
|
||||
* @see #isAllowed()
|
||||
* @see #isEnabled()
|
||||
*/
|
||||
public boolean isAvailable() {
|
||||
return this.isAllowed() && this.isEnabled();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the feature to its default values.
|
||||
*/
|
||||
public void reset() {
|
||||
this.resetAllowed();
|
||||
this.setEnabled(this.defaultEnabled);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String getName() {
|
||||
return this.key;
|
||||
}
|
||||
|
||||
public static @NotNull Optional<MidnightControlsFeature> fromName(@NotNull String key) {
|
||||
Objects.requireNonNull(key, "Cannot find features with a null name.");
|
||||
return FEATURES.parallelStream().filter(feature -> feature.getName().equals(key)).findFirst();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets all features to their default values.
|
||||
*/
|
||||
public static void resetAll() {
|
||||
FEATURES.parallelStream().forEach(MidnightControlsFeature::reset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets all features to allow state.
|
||||
*/
|
||||
public static void resetAllAllowed() {
|
||||
FEATURES.parallelStream().forEach(MidnightControlsFeature::resetAllowed);
|
||||
}
|
||||
|
||||
static {
|
||||
FEATURES.add(FAST_BLOCK_PLACING);
|
||||
FEATURES.add(HORIZONTAL_REACHAROUND);
|
||||
FEATURES.add(VERTICAL_REACHAROUND);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
|
||||
*
|
||||
* This file is part of midnightcontrols.
|
||||
*
|
||||
* Licensed under the MIT license. For more information,
|
||||
* see the LICENSE file.
|
||||
*/
|
||||
|
||||
package eu.midnightdust.midnightcontrols.client;
|
||||
|
||||
/**
|
||||
* Represents a button state.
|
||||
*
|
||||
* @author LambdAurora
|
||||
* @version 1.1.0
|
||||
* @since 1.1.0
|
||||
*/
|
||||
public enum ButtonState {
|
||||
NONE(0),
|
||||
PRESS(1),
|
||||
RELEASE(2),
|
||||
REPEAT(3);
|
||||
|
||||
public final int id;
|
||||
|
||||
ButtonState(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this state is a pressed state.
|
||||
*
|
||||
* @return true if this state is a pressed state, else false
|
||||
*/
|
||||
public boolean isPressed() {
|
||||
return this == PRESS || this == REPEAT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this state is an unpressed state.
|
||||
*
|
||||
* @return true if this state is an unpressed state, else false
|
||||
*/
|
||||
public boolean isUnpressed() {
|
||||
return this == RELEASE || this == NONE;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
|
||||
*
|
||||
* This file is part of midnightcontrols.
|
||||
*
|
||||
* Licensed under the MIT license. For more information,
|
||||
* see the LICENSE file.
|
||||
*/
|
||||
|
||||
package eu.midnightdust.midnightcontrols.client;
|
||||
|
||||
import net.minecraft.text.LiteralText;
|
||||
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 a controller type.
|
||||
*
|
||||
* @author LambdAurora
|
||||
* @version 1.4.3
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public enum ControllerType implements Nameable {
|
||||
DEFAULT(0, new LiteralText("Default")),
|
||||
DUALSHOCK(1, new LiteralText("Dualshock")),
|
||||
DUALSENSE(2, new LiteralText("Dualsense")),
|
||||
SWITCH(3, new LiteralText("Switch")),
|
||||
XBOX_360(4, new LiteralText("Xbox 360")),
|
||||
XBOX(5, new LiteralText("Xbox")),
|
||||
STEAM_DECK(6, new LiteralText("Steam Deck")),
|
||||
STEAM_CONTROLLER(7, new LiteralText("Steam Controller")),
|
||||
OUYA(8, new LiteralText("Ouya"));
|
||||
|
||||
private final int id;
|
||||
private final Text text;
|
||||
|
||||
ControllerType(int id) {
|
||||
this.id = id;
|
||||
this.text = new TranslatableText("midnightcontrols.controller_type." + this.getName());
|
||||
}
|
||||
|
||||
ControllerType(int id, @NotNull Text text) {
|
||||
this.id = id;
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the controller type's identifier.
|
||||
*
|
||||
* @return the controller type's identifier
|
||||
*/
|
||||
public int getId() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the next controller type available.
|
||||
*
|
||||
* @return the next available controller type
|
||||
*/
|
||||
public @NotNull ControllerType next() {
|
||||
var v = values();
|
||||
if (v.length == this.ordinal() + 1)
|
||||
return v[0];
|
||||
return v[this.ordinal() + 1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the translated text of this controller type.
|
||||
*
|
||||
* @return the translated text of this controller type
|
||||
*/
|
||||
public @NotNull Text getTranslatedText() {
|
||||
return this.text;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String getName() {
|
||||
return this.name().toLowerCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the controller type from its identifier.
|
||||
*
|
||||
* @param id the identifier of the controller type
|
||||
* @return the controller type if found, else empty
|
||||
*/
|
||||
public static @NotNull Optional<ControllerType> byId(@NotNull String id) {
|
||||
return Arrays.stream(values()).filter(mode -> mode.getName().equalsIgnoreCase(id)).findFirst();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
|
||||
*
|
||||
* This file is part of midnightcontrols.
|
||||
*
|
||||
* Licensed under the MIT license. For more information,
|
||||
* see the LICENSE file.
|
||||
*/
|
||||
|
||||
package eu.midnightdust.midnightcontrols.client;
|
||||
|
||||
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 hud side which is the side where the movements buttons are.
|
||||
*
|
||||
* @author LambdAurora
|
||||
* @version 1.4.0
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public enum HudSide implements Nameable {
|
||||
LEFT,
|
||||
RIGHT;
|
||||
|
||||
private final Text text;
|
||||
|
||||
HudSide() {
|
||||
this.text = new TranslatableText(this.getTranslationKey());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the next side available.
|
||||
*
|
||||
* @return the next available side
|
||||
*/
|
||||
public @NotNull HudSide next() {
|
||||
var v = values();
|
||||
if (v.length == this.ordinal() + 1)
|
||||
return v[0];
|
||||
return v[this.ordinal() + 1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the translation key of this hud side.
|
||||
*
|
||||
* @return the translation key of this hude side
|
||||
*/
|
||||
public @NotNull String getTranslationKey() {
|
||||
return "midnightcontrols.hud_side." + this.getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the translated text of this hud side.
|
||||
*
|
||||
* @return the translated text of this hud side
|
||||
*/
|
||||
public @NotNull Text getTranslatedText() {
|
||||
return this.text;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String getName() {
|
||||
return this.name().toLowerCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the hud side from its identifier.
|
||||
*
|
||||
* @param id the identifier of the hud side
|
||||
* @return the hud side if found, else empty
|
||||
*/
|
||||
public static @NotNull Optional<HudSide> byId(@NotNull String id) {
|
||||
return Arrays.stream(values()).filter(mode -> mode.getName().equalsIgnoreCase(id)).findFirst();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
/*
|
||||
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
|
||||
*
|
||||
* This file is part of midnightcontrols.
|
||||
*
|
||||
* Licensed under the MIT license. For more information,
|
||||
* see the LICENSE file.
|
||||
*/
|
||||
|
||||
package eu.midnightdust.midnightcontrols.client;
|
||||
|
||||
import dev.lambdaurora.spruceui.event.OpenScreenCallback;
|
||||
import eu.midnightdust.midnightcontrols.ControlsMode;
|
||||
import eu.midnightdust.midnightcontrols.MidnightControls;
|
||||
import eu.midnightdust.midnightcontrols.MidnightControlsConstants;
|
||||
import eu.midnightdust.midnightcontrols.MidnightControlsFeature;
|
||||
import eu.midnightdust.midnightcontrols.client.compat.MidnightControlsCompat;
|
||||
import eu.midnightdust.midnightcontrols.client.controller.ButtonBinding;
|
||||
import eu.midnightdust.midnightcontrols.client.controller.Controller;
|
||||
import eu.midnightdust.midnightcontrols.client.controller.InputManager;
|
||||
import eu.midnightdust.midnightcontrols.client.gui.MidnightControlsHud;
|
||||
import eu.midnightdust.midnightcontrols.client.gui.TouchscreenOverlay;
|
||||
import eu.midnightdust.midnightcontrols.client.ring.KeyBindingRingAction;
|
||||
import eu.midnightdust.midnightcontrols.client.ring.MidnightRing;
|
||||
import dev.lambdaurora.spruceui.hud.HudManager;
|
||||
import net.fabricmc.api.ClientModInitializer;
|
||||
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
|
||||
import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper;
|
||||
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents;
|
||||
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking;
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.client.network.ClientPlayNetworkHandler;
|
||||
import net.minecraft.client.option.KeyBinding;
|
||||
import net.minecraft.client.toast.SystemToast;
|
||||
import net.minecraft.client.util.InputUtil;
|
||||
import net.minecraft.text.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 midnightcontrols client mod.
|
||||
*
|
||||
* @author LambdAurora
|
||||
* @version 1.7.0
|
||||
* @since 1.1.0
|
||||
*/
|
||||
public class MidnightControlsClient extends MidnightControls implements ClientModInitializer {
|
||||
private static MidnightControlsClient INSTANCE;
|
||||
public static final KeyBinding BINDING_LOOK_UP = InputManager.makeKeyBinding(new Identifier(MidnightControlsConstants.NAMESPACE, "look_up"),
|
||||
InputUtil.Type.KEYSYM, GLFW.GLFW_KEY_KP_8, "key.categories.movement");
|
||||
public static final KeyBinding BINDING_LOOK_RIGHT = InputManager.makeKeyBinding(new Identifier(MidnightControlsConstants.NAMESPACE, "look_right"),
|
||||
InputUtil.Type.KEYSYM, GLFW.GLFW_KEY_KP_6, "key.categories.movement");
|
||||
public static final KeyBinding BINDING_LOOK_DOWN = InputManager.makeKeyBinding(new Identifier(MidnightControlsConstants.NAMESPACE, "look_down"),
|
||||
InputUtil.Type.KEYSYM, GLFW.GLFW_KEY_KP_2, "key.categories.movement");
|
||||
public static final KeyBinding BINDING_LOOK_LEFT = InputManager.makeKeyBinding(new Identifier(MidnightControlsConstants.NAMESPACE, "look_left"),
|
||||
InputUtil.Type.KEYSYM, GLFW.GLFW_KEY_KP_4, "key.categories.movement");
|
||||
/*public static final KeyBinding BINDING_RING = InputManager.makeKeyBinding(new Identifier(midnightcontrolsConstants.NAMESPACE, "ring"),
|
||||
InputUtil.Type.MOUSE, GLFW.GLFW_MOUSE_BUTTON_5, "key.categories.misc");*/
|
||||
public static final Identifier CONTROLLER_BUTTONS = new Identifier(MidnightControlsConstants.NAMESPACE, "textures/gui/controller_buttons.png");
|
||||
public static final Identifier CONTROLLER_EXPANDED = new Identifier(MidnightControlsConstants.NAMESPACE, "textures/gui/controller_expanded.png");
|
||||
public static final Identifier CONTROLLER_AXIS = new Identifier(MidnightControlsConstants.NAMESPACE, "textures/gui/controller_axis.png");
|
||||
public static final Identifier CURSOR_TEXTURE = new Identifier(MidnightControlsConstants.NAMESPACE, "textures/gui/cursor.png");
|
||||
public final static File MAPPINGS_FILE = new File("config/gamecontrollerdb.txt");
|
||||
public final MidnightInput input = new MidnightInput();
|
||||
public final MidnightRing ring = new MidnightRing(this);
|
||||
public final MidnightReacharound reacharound = new MidnightReacharound();
|
||||
private MidnightControlsHud hud;
|
||||
private ControlsMode previousControlsMode;
|
||||
|
||||
@Override
|
||||
public void onInitializeClient() {
|
||||
INSTANCE = this;
|
||||
KeyBindingHelper.registerKeyBinding(BINDING_LOOK_UP);
|
||||
KeyBindingHelper.registerKeyBinding(BINDING_LOOK_RIGHT);
|
||||
KeyBindingHelper.registerKeyBinding(BINDING_LOOK_DOWN);
|
||||
KeyBindingHelper.registerKeyBinding(BINDING_LOOK_LEFT);
|
||||
//KeyBindingHelper.registerKeyBinding(BINDING_RING);
|
||||
|
||||
this.ring.registerAction("keybinding", KeyBindingRingAction.FACTORY);
|
||||
|
||||
ClientPlayNetworking.registerGlobalReceiver(CONTROLS_MODE_CHANNEL, (client, handler, buf, responseSender) -> {
|
||||
responseSender.sendPacket(CONTROLS_MODE_CHANNEL, this.makeControlsModeBuffer(MidnightControlsConfig.controlsMode));
|
||||
});
|
||||
ClientPlayNetworking.registerGlobalReceiver(FEATURE_CHANNEL, (client, handler, buf, responseSender) -> {
|
||||
int features = buf.readVarInt();
|
||||
for (int i = 0; i < features; i++) {
|
||||
var name = buf.readString(64);
|
||||
boolean allowed = buf.readBoolean();
|
||||
MidnightControlsFeature.fromName(name).ifPresent(feature -> client.execute(() -> feature.setAllowed(allowed)));
|
||||
}
|
||||
});
|
||||
ClientPlayConnectionEvents.JOIN.register((handler, sender, client) -> {
|
||||
sender.sendPacket(HELLO_CHANNEL, this.makeHello(MidnightControlsConfig.controlsMode));
|
||||
sender.sendPacket(CONTROLS_MODE_CHANNEL, this.makeControlsModeBuffer(MidnightControlsConfig.controlsMode));
|
||||
});
|
||||
ClientPlayConnectionEvents.DISCONNECT.register(this::onLeave);
|
||||
|
||||
ClientTickEvents.START_CLIENT_TICK.register(this.reacharound::tick);
|
||||
ClientTickEvents.END_CLIENT_TICK.register(this::onTick);
|
||||
|
||||
OpenScreenCallback.EVENT.register((client, screen) -> {
|
||||
// if (screen == null && MidnightControlsConfig.controlsMode == ControlsMode.TOUCHSCREEN) {
|
||||
// screen = new TouchscreenOverlay(this);
|
||||
// screen.init(client, client.getWindow().getScaledWidth(), client.getWindow().getScaledHeight());
|
||||
// client.skipGameRender = false;
|
||||
// client.currentScreen = screen;
|
||||
// } else if (screen != null) {
|
||||
this.input.onScreenOpen(client, client.getWindow().getWidth(), client.getWindow().getHeight());
|
||||
//}
|
||||
});
|
||||
|
||||
HudManager.register(this.hud = new MidnightControlsHud(this));
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called when Minecraft is initializing.
|
||||
*/
|
||||
public void onMcInit(@NotNull MinecraftClient client) {
|
||||
ButtonBinding.init(client.options);
|
||||
MidnightControlsConfig.load();
|
||||
this.hud.setVisible(MidnightControlsConfig.hudEnable);
|
||||
Controller.updateMappings();
|
||||
GLFW.glfwSetJoystickCallback((jid, event) -> {
|
||||
if (event == GLFW.GLFW_CONNECTED) {
|
||||
var controller = Controller.byId(jid);
|
||||
client.getToastManager().add(new SystemToast(SystemToast.Type.TUTORIAL_HINT, new TranslatableText("midnightcontrols.controller.connected", jid),
|
||||
new LiteralText(controller.getName())));
|
||||
} else if (event == GLFW.GLFW_DISCONNECTED) {
|
||||
client.getToastManager().add(new SystemToast(SystemToast.Type.TUTORIAL_HINT, new TranslatableText("midnightcontrols.controller.disconnected", jid),
|
||||
null));
|
||||
}
|
||||
|
||||
this.switchControlsMode();
|
||||
});
|
||||
|
||||
MidnightControlsCompat.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 (MidnightControlsConfig.controlsMode == ControlsMode.CONTROLLER && (client.isWindowFocused() || MidnightControlsConfig.unfocusedInput))
|
||||
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) {
|
||||
MidnightControlsFeature.resetAllAllowed();
|
||||
}
|
||||
|
||||
/**
|
||||
* Switches the controls mode if the auto switch is enabled.
|
||||
*/
|
||||
public void switchControlsMode() {
|
||||
if (MidnightControlsConfig.autoSwitchMode) {
|
||||
if (MidnightControlsConfig.getController().isGamepad()) {
|
||||
this.previousControlsMode = MidnightControlsConfig.controlsMode;
|
||||
MidnightControlsConfig.controlsMode = ControlsMode.CONTROLLER;
|
||||
} else {
|
||||
if (this.previousControlsMode == null) {
|
||||
this.previousControlsMode = ControlsMode.DEFAULT;
|
||||
}
|
||||
|
||||
MidnightControlsConfig.controlsMode = this.previousControlsMode;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the HUD is enabled or not.
|
||||
*
|
||||
* @param enabled true if the HUD is enabled, else false
|
||||
*/
|
||||
public void setHudEnabled(boolean enabled) {
|
||||
MidnightControlsConfig.hudEnable = enabled;
|
||||
this.hud.setVisible(enabled);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the midnightcontrols client instance.
|
||||
*
|
||||
* @return the midnightcontrols client instance
|
||||
*/
|
||||
public static MidnightControlsClient get() {
|
||||
return INSTANCE;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,258 @@
|
||||
/*
|
||||
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
|
||||
*
|
||||
* This file is part of midnightcontrols.
|
||||
*
|
||||
* Licensed under the MIT license. For more information,
|
||||
* see the LICENSE file.
|
||||
*/
|
||||
|
||||
package eu.midnightdust.midnightcontrols.client;
|
||||
|
||||
import eu.midnightdust.lib.config.MidnightConfig;
|
||||
import eu.midnightdust.midnightcontrols.ControlsMode;
|
||||
import eu.midnightdust.midnightcontrols.client.controller.ButtonBinding;
|
||||
import eu.midnightdust.midnightcontrols.client.controller.Controller;
|
||||
import eu.midnightdust.midnightcontrols.client.controller.InputManager;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.lwjgl.glfw.GLFW;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static org.lwjgl.glfw.GLFW.GLFW_GAMEPAD_AXIS_LEFT_X;
|
||||
import static org.lwjgl.glfw.GLFW.GLFW_GAMEPAD_AXIS_LEFT_Y;
|
||||
|
||||
/**
|
||||
* Represents MidnightControls configuration.
|
||||
*/
|
||||
public class MidnightControlsConfig extends MidnightConfig {
|
||||
// General
|
||||
@Entry public static ControlsMode controlsMode = ControlsMode.DEFAULT;
|
||||
@Entry public static boolean autoSwitchMode = false;
|
||||
@Entry public static boolean debug = false;
|
||||
// HUD
|
||||
@Entry public static boolean hudEnable = true;
|
||||
@Entry public static HudSide hudSide = HudSide.LEFT;
|
||||
// Gameplay
|
||||
@Entry public static boolean analogMovement = true;
|
||||
@Entry public static boolean fastBlockPlacing = true;
|
||||
@Entry public static boolean flyDrifting = false;
|
||||
@Entry public static boolean verticalFlyDrifting = true;
|
||||
@Entry public static boolean horizontalReacharound = false;
|
||||
@Entry public static boolean verticalReacharound = false;
|
||||
@Entry public static boolean shouldRenderReacharoundOutline = true;
|
||||
@Entry public static int[] reacharoundOutlineColor = new int[]{255, 255, 255, 102};
|
||||
// Controller
|
||||
@Entry public static ControllerType controllerType = ControllerType.DEFAULT;
|
||||
@Entry public static double rightDeadZone = 0.25;
|
||||
@Entry public static double leftDeadZone = 0.25;
|
||||
@Entry public static boolean invertRightYAxis = false;
|
||||
@Entry public static boolean invertRightXAxis = false;
|
||||
@Entry public static double DEFAULT_MAX_VALUE = 1;
|
||||
@Entry public static double rotationSpeed = 40.0;
|
||||
@Entry public static double mouseSpeed = 25.0;
|
||||
@Entry public static boolean unfocusedInput = false;
|
||||
@Entry public static boolean virtualMouse = false;
|
||||
@Entry public static VirtualMouseSkin virtualMouseSkin = VirtualMouseSkin.DEFAULT_LIGHT;
|
||||
// @Entry public static List<Pages> ringPages = new ArrayList<String>();
|
||||
// @Entry public static double maxAnalog1 = 1;
|
||||
// @Entry public static double maxAnalog2 = 1;
|
||||
// @Entry public static double maxAnalog3 = 1;
|
||||
// @Entry public static double maxAnalog4 = 1;
|
||||
@Entry public static Object controllerID = 0;
|
||||
@Entry public static Object secondControllerID = -1;
|
||||
@Entry public static Map<String, String> BINDINGS = Map.of();
|
||||
|
||||
private static final Pattern BUTTON_BINDING_PATTERN = Pattern.compile("(-?\\d+)\\+?");
|
||||
// Gameplay.
|
||||
// Controller settings
|
||||
@Entry public static double[] maxAnalogValues = new double[]{DEFAULT_MAX_VALUE, DEFAULT_MAX_VALUE, DEFAULT_MAX_VALUE, DEFAULT_MAX_VALUE};
|
||||
|
||||
/**
|
||||
* Loads the configuration
|
||||
*/
|
||||
public static void load() {
|
||||
MidnightControlsConfig.init("midnightcontrols", MidnightControlsConfig.class);
|
||||
MidnightControlsClient.get().log("Configuration loaded.");
|
||||
// Controller controls.
|
||||
InputManager.loadButtonBindings();
|
||||
//this.mod.ring.load(this.config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the configuration.
|
||||
*/
|
||||
public static void save() {
|
||||
MidnightControlsConfig.write("midnightcontrols");
|
||||
MidnightControlsClient.get().log("Configuration saved.");
|
||||
}
|
||||
/**
|
||||
* Gets the used controller.
|
||||
*
|
||||
* @return the controller
|
||||
*/
|
||||
public static Controller getController() {
|
||||
var raw = MidnightControlsConfig.controllerID;
|
||||
if (raw instanceof Number) {
|
||||
return Controller.byId(((Number) raw).intValue());
|
||||
} 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 static void setController(Controller controller) {
|
||||
MidnightControlsConfig.controllerID = controller.id();
|
||||
MidnightControlsConfig.write("midnightcontrols");
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the second controller (for Joy-Con supports).
|
||||
*
|
||||
* @return the second controller
|
||||
*/
|
||||
public static Optional<Controller> getSecondController() {
|
||||
var raw = MidnightControlsConfig.secondControllerID;
|
||||
if (raw instanceof Number) {
|
||||
if (((Number) raw).intValue() == -1)
|
||||
return Optional.empty();
|
||||
return Optional.of(Controller.byId(((Number) raw).intValue()));
|
||||
} else if (raw instanceof String) {
|
||||
return Optional.of(Controller.byGuid((String) raw).orElse(Controller.byId(GLFW.GLFW_JOYSTICK_1)));
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the second controller.
|
||||
*
|
||||
* @param controller the second controller
|
||||
*/
|
||||
public static void setSecondController(@Nullable Controller controller) {
|
||||
MidnightControlsConfig.secondControllerID = controller == null ? -1 : controller.id();
|
||||
}
|
||||
/**
|
||||
* Gets the right X axis sign.
|
||||
*
|
||||
* @return the right X axis sign
|
||||
*/
|
||||
public static double getRightXAxisSign() {
|
||||
return MidnightControlsConfig.invertRightXAxis ? -1.0 : 1.0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the right Y axis sign.
|
||||
*
|
||||
* @return the right Y axis sign
|
||||
*/
|
||||
public static double getRightYAxisSign() {
|
||||
return MidnightControlsConfig.invertRightYAxis ? -1.0 : 1.0;
|
||||
}
|
||||
|
||||
public static double getAxisMaxValue(int axis) {
|
||||
if (axis >= MidnightControlsConfig.maxAnalogValues.length)
|
||||
return DEFAULT_MAX_VALUE;
|
||||
return MidnightControlsConfig.maxAnalogValues[axis];
|
||||
}
|
||||
|
||||
public static void setAxisMaxValue(int axis, double value) {
|
||||
if (axis < MidnightControlsConfig.maxAnalogValues.length)
|
||||
MidnightControlsConfig.maxAnalogValues[axis] = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the button binding from configuration.
|
||||
*
|
||||
* @param button the button binding
|
||||
*/
|
||||
public static void loadButtonBinding(@NotNull ButtonBinding button) {
|
||||
button.setButton(button.getDefaultButton());
|
||||
var code = MidnightControlsConfig.BINDINGS.getOrDefault("controller.controls." + button.getName(), button.getButtonCode());
|
||||
|
||||
var matcher = BUTTON_BINDING_PATTERN.matcher(code);
|
||||
|
||||
try {
|
||||
var buttons = new int[1];
|
||||
int count = 0;
|
||||
while (matcher.find()) {
|
||||
count++;
|
||||
if (count > buttons.length)
|
||||
buttons = Arrays.copyOf(buttons, count);
|
||||
String current;
|
||||
if (!MidnightControlsConfig.checkValidity(button, code, current = matcher.group(1)))
|
||||
return;
|
||||
buttons[count - 1] = Integer.parseInt(current);
|
||||
}
|
||||
if (count == 0) {
|
||||
MidnightControlsClient.get().warn("Malformed config value \"" + code + "\" for binding \"" + button.getName() + "\".");
|
||||
MidnightControlsConfig.setButtonBinding(button, new int[]{-1});
|
||||
}
|
||||
|
||||
button.setButton(buttons);
|
||||
} catch (Exception e) {
|
||||
MidnightControlsClient.get().warn("Malformed config value \"" + code + "\" for binding \"" + button.getName() + "\".");
|
||||
MidnightControlsConfig.BINDINGS.put("controller.controls." + button.getName(), button.getButtonCode());
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean checkValidity(@NotNull ButtonBinding binding, @NotNull String input, String group) {
|
||||
if (group == null) {
|
||||
MidnightControlsClient.get().warn("Malformed config value \"" + input + "\" for binding \"" + binding.getName() + "\".");
|
||||
MidnightControlsConfig.BINDINGS.put("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 static void setButtonBinding(@NotNull ButtonBinding binding, int[] button) {
|
||||
binding.setButton(button);
|
||||
MidnightControlsConfig.BINDINGS.put("controller.controls." + binding.getName(), binding.getButtonCode());
|
||||
}
|
||||
|
||||
public static boolean isBackButton(int btn, boolean isBtn, int state) {
|
||||
if (!isBtn && state == 0)
|
||||
return false;
|
||||
return ButtonBinding.axisAsButton(GLFW_GAMEPAD_AXIS_LEFT_Y, false) == ButtonBinding.axisAsButton(btn, state == 1);
|
||||
}
|
||||
|
||||
public static boolean isForwardButton(int btn, boolean isBtn, int state) {
|
||||
if (!isBtn && state == 0)
|
||||
return false;
|
||||
return ButtonBinding.axisAsButton(GLFW_GAMEPAD_AXIS_LEFT_Y, true) == ButtonBinding.axisAsButton(btn, state == 1);
|
||||
}
|
||||
|
||||
public static boolean isLeftButton(int btn, boolean isBtn, int state) {
|
||||
if (!isBtn && state == 0)
|
||||
return false;
|
||||
return ButtonBinding.axisAsButton(GLFW_GAMEPAD_AXIS_LEFT_X, false) == ButtonBinding.axisAsButton(btn, state == 1);
|
||||
}
|
||||
|
||||
public static boolean isRightButton(int btn, boolean isBtn, int state) {
|
||||
if (!isBtn && state == 0)
|
||||
return false;
|
||||
return ButtonBinding.axisAsButton(GLFW_GAMEPAD_AXIS_LEFT_X, true) == ButtonBinding.axisAsButton(btn, state == 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the specified axis is an axis used for movements.
|
||||
*
|
||||
* @param axis the axis index
|
||||
* @return true if the axis is used for movements, else false
|
||||
*/
|
||||
public static boolean isMovementAxis(int axis) {
|
||||
return axis == GLFW_GAMEPAD_AXIS_LEFT_Y || axis == GLFW_GAMEPAD_AXIS_LEFT_X;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
|
||||
*
|
||||
* This file is part of midnightcontrols.
|
||||
*
|
||||
* Licensed under the MIT license. For more information,
|
||||
* see the LICENSE file.
|
||||
*/
|
||||
|
||||
package eu.midnightdust.midnightcontrols.client;
|
||||
|
||||
import com.terraformersmc.modmenu.api.ConfigScreenFactory;
|
||||
import com.terraformersmc.modmenu.api.ModMenuApi;
|
||||
import eu.midnightdust.midnightcontrols.client.gui.MidnightControlsSettingsScreen;
|
||||
|
||||
/**
|
||||
* Represents the API implementation of ModMenu for midnightcontrols.
|
||||
*
|
||||
* @author LambdAurora
|
||||
* @version 1.7.0
|
||||
* @since 1.1.0
|
||||
*/
|
||||
public class MidnightControlsModMenu implements ModMenuApi {
|
||||
@Override
|
||||
public ConfigScreenFactory<?> getModConfigScreenFactory() {
|
||||
return parent -> new MidnightControlsSettingsScreen(parent, false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,765 @@
|
||||
/*
|
||||
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
|
||||
*
|
||||
* This file is part of midnightcontrols.
|
||||
*
|
||||
* Licensed under the MIT license. For more information,
|
||||
* see the LICENSE file.
|
||||
*/
|
||||
|
||||
package eu.midnightdust.midnightcontrols.client;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import eu.midnightdust.midnightcontrols.client.compat.MidnightControlsCompat;
|
||||
import eu.midnightdust.midnightcontrols.client.controller.ButtonBinding;
|
||||
import eu.midnightdust.midnightcontrols.client.controller.Controller;
|
||||
import eu.midnightdust.midnightcontrols.client.controller.InputManager;
|
||||
import eu.midnightdust.midnightcontrols.client.gui.TouchscreenOverlay;
|
||||
import eu.midnightdust.midnightcontrols.client.gui.widget.ControllerControlsWidget;
|
||||
import eu.midnightdust.midnightcontrols.client.mixin.AdvancementsScreenAccessor;
|
||||
import eu.midnightdust.midnightcontrols.client.mixin.CreativeInventoryScreenAccessor;
|
||||
import eu.midnightdust.midnightcontrols.client.mixin.EntryListWidgetAccessor;
|
||||
import eu.midnightdust.midnightcontrols.client.util.HandledScreenAccessor;
|
||||
import eu.midnightdust.midnightcontrols.client.util.MouseAccessor;
|
||||
import dev.lambdaurora.spruceui.navigation.NavigationDirection;
|
||||
import dev.lambdaurora.spruceui.screen.SpruceScreen;
|
||||
import dev.lambdaurora.spruceui.widget.AbstractSprucePressableButtonWidget;
|
||||
import dev.lambdaurora.spruceui.widget.SpruceElement;
|
||||
import dev.lambdaurora.spruceui.widget.SpruceLabelWidget;
|
||||
import dev.lambdaurora.spruceui.widget.container.SpruceParentWidget;
|
||||
import net.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.AlwaysSelectedEntryListWidget;
|
||||
import net.minecraft.client.gui.widget.EntryListWidget;
|
||||
import net.minecraft.client.gui.widget.PressableWidget;
|
||||
import net.minecraft.client.gui.widget.SliderWidget;
|
||||
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.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.lwjgl.glfw.GLFW.*;
|
||||
|
||||
/**
|
||||
* Represents the midnightcontrols' input handler.
|
||||
*
|
||||
* @author LambdAurora
|
||||
* @version 1.7.0
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public class MidnightInput {
|
||||
private static final Map<Integer, Integer> BUTTON_COOLDOWNS = new HashMap<>();
|
||||
// Cooldowns
|
||||
public int actionGuiCooldown = 0;
|
||||
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 MidnightInput() {}
|
||||
|
||||
/**
|
||||
* This method is called every Minecraft tick.
|
||||
*
|
||||
* @param client the client instance
|
||||
*/
|
||||
public void tick(@NotNull MinecraftClient client) {
|
||||
this.targetYaw = 0.F;
|
||||
this.targetPitch = 0.F;
|
||||
|
||||
// Handles the key bindings.
|
||||
if (MidnightControlsClient.BINDING_LOOK_UP.isPressed()) {
|
||||
this.handleLook(client, GLFW_GAMEPAD_AXIS_RIGHT_Y, 0.8F, 2);
|
||||
} else if (MidnightControlsClient.BINDING_LOOK_DOWN.isPressed()) {
|
||||
this.handleLook(client, GLFW_GAMEPAD_AXIS_RIGHT_Y, 0.8F, 1);
|
||||
}
|
||||
if (MidnightControlsClient.BINDING_LOOK_LEFT.isPressed()) {
|
||||
this.handleLook(client, GLFW_GAMEPAD_AXIS_RIGHT_X, 0.8F, 2);
|
||||
} else if (MidnightControlsClient.BINDING_LOOK_RIGHT.isPressed()) {
|
||||
this.handleLook(client, GLFW_GAMEPAD_AXIS_RIGHT_X, 0.8F, 1);
|
||||
}
|
||||
|
||||
InputManager.INPUT_MANAGER.tick(client);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called every Minecraft tick for controller input update.
|
||||
*
|
||||
* @param client the client instance
|
||||
*/
|
||||
public void tickController(@NotNull MinecraftClient client) {
|
||||
BUTTON_COOLDOWNS.entrySet().stream().filter(entry -> entry.getValue() > 0)
|
||||
.forEach(entry -> BUTTON_COOLDOWNS.put(entry.getKey(), entry.getValue() - 1));
|
||||
// Decreases the cooldown for GUI actions.
|
||||
if (this.actionGuiCooldown > 0)
|
||||
--this.actionGuiCooldown;
|
||||
|
||||
InputManager.updateStates();
|
||||
|
||||
var controller = MidnightControlsConfig.getController();
|
||||
if (controller.isConnected()) {
|
||||
var state = controller.getState();
|
||||
this.fetchButtonInput(client, state, false);
|
||||
this.fetchAxeInput(client, state, false);
|
||||
}
|
||||
MidnightControlsConfig.getSecondController().filter(Controller::isConnected)
|
||||
.ifPresent(joycon -> {
|
||||
GLFWGamepadState state = joycon.getState();
|
||||
this.fetchButtonInput(client, state, true);
|
||||
this.fetchAxeInput(client, state, true);
|
||||
});
|
||||
|
||||
boolean allowInput = true;
|
||||
|
||||
if (this.controlsInput != null && this.controlsInput.focusedBinding != null)
|
||||
allowInput = false;
|
||||
|
||||
if (allowInput)
|
||||
InputManager.updateBindings(client);
|
||||
|
||||
if (this.controlsInput != null
|
||||
&& InputManager.STATES.int2ObjectEntrySet().parallelStream().map(Map.Entry::getValue).allMatch(ButtonState::isUnpressed)) {
|
||||
if (this.controlsInput.focusedBinding != null && !this.controlsInput.waiting) {
|
||||
int[] buttons = new int[this.controlsInput.currentButtons.size()];
|
||||
for (int i = 0; i < this.controlsInput.currentButtons.size(); i++)
|
||||
buttons[i] = this.controlsInput.currentButtons.get(i);
|
||||
this.controlsInput.finishBindingEdit(buttons);
|
||||
this.controlsInput = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.inventoryInteractionCooldown > 0)
|
||||
this.inventoryInteractionCooldown--;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called before the screen is rendered.
|
||||
*
|
||||
* @param client the client instance
|
||||
* @param screen the screen to render
|
||||
*/
|
||||
public void onPreRenderScreen(@NotNull MinecraftClient client, @NotNull Screen screen) {
|
||||
if (!isScreenInteractive(screen)) {
|
||||
InputManager.INPUT_MANAGER.updateMousePosition(client);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called 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;
|
||||
|
||||
var 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.setYaw(rotationYaw);
|
||||
client.player.setPitch(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;
|
||||
InputManager.INPUT_MANAGER.resetMousePosition(windowWidth, windowHeight);
|
||||
} else if (isScreenInteractive(client.currentScreen) && MidnightControlsConfig.virtualMouse) {
|
||||
((MouseAccessor) client.mouse).midnightcontrols$onCursorPos(client.getWindow().getHandle(), 0, 0);
|
||||
InputManager.INPUT_MANAGER.resetMouseTarget(client);
|
||||
}
|
||||
this.inventoryInteractionCooldown = 5;
|
||||
}
|
||||
|
||||
public void beginControlsInput(ControllerControlsWidget widget) {
|
||||
this.controlsInput = widget;
|
||||
if (widget != null) {
|
||||
this.controlsInput.currentButtons.clear();
|
||||
this.controlsInput.waiting = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void fetchButtonInput(@NotNull MinecraftClient client, @NotNull GLFWGamepadState gamepadState, boolean leftJoycon) {
|
||||
var buffer = gamepadState.buttons();
|
||||
for (int i = 0; i < buffer.limit(); i++) {
|
||||
int btn = leftJoycon ? ButtonBinding.controller2Button(i) : i;
|
||||
boolean btnState = buffer.get() == (byte) 1;
|
||||
var state = ButtonState.NONE;
|
||||
var previousState = InputManager.STATES.getOrDefault(btn, ButtonState.NONE);
|
||||
|
||||
if (btnState != previousState.isPressed()) {
|
||||
state = btnState ? ButtonState.PRESS : ButtonState.RELEASE;
|
||||
this.handleButton(client, btn, btnState ? 0 : 1, btnState);
|
||||
if (btnState)
|
||||
BUTTON_COOLDOWNS.put(btn, 5);
|
||||
} else if (btnState) {
|
||||
state = ButtonState.REPEAT;
|
||||
if (BUTTON_COOLDOWNS.getOrDefault(btn, 0) == 0) {
|
||||
BUTTON_COOLDOWNS.put(btn, 5);
|
||||
this.handleButton(client, btn, 2, true);
|
||||
}
|
||||
}
|
||||
|
||||
InputManager.STATES.put(btn, state);
|
||||
}
|
||||
}
|
||||
|
||||
private void fetchAxeInput(@NotNull MinecraftClient client, @NotNull GLFWGamepadState gamepadState, boolean leftJoycon) {
|
||||
var buffer = gamepadState.axes();
|
||||
for (int i = 0; i < buffer.limit(); i++) {
|
||||
int axis = leftJoycon ? ButtonBinding.controller2Button(i) : i;
|
||||
float value = buffer.get();
|
||||
float absValue = Math.abs(value);
|
||||
|
||||
if (i == GLFW.GLFW_GAMEPAD_AXIS_LEFT_Y)
|
||||
value *= -1.0F;
|
||||
|
||||
int state = value > MidnightControlsConfig.rightDeadZone ? 1 : (value < -MidnightControlsConfig.rightDeadZone ? 2 : 0);
|
||||
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);
|
||||
|
||||
var buttons = new int[this.controlsInput.currentButtons.size()];
|
||||
for (int i = 0; i < this.controlsInput.currentButtons.size(); i++)
|
||||
buttons[i] = this.controlsInput.currentButtons.get(i);
|
||||
this.controlsInput.focusedBinding.setButton(buttons);
|
||||
|
||||
this.controlsInput.waiting = false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (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 this.handleLeftRight(client.currentScreen, button != GLFW.GLFW_GAMEPAD_BUTTON_DPAD_LEFT);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (action == 1) {
|
||||
if (button == GLFW.GLFW_GAMEPAD_BUTTON_A && client.currentScreen != null) {
|
||||
if (this.actionGuiCooldown == 0) {
|
||||
var focused = client.currentScreen.getFocused();
|
||||
if (focused != null && isScreenInteractive(client.currentScreen)) {
|
||||
if (this.handleAButton(client.currentScreen, focused)) {
|
||||
this.actionGuiCooldown = 5; // Prevent to press too quickly the focused element, so we have to skip 5 ticks.
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (this.handleInventory(client, button)) {
|
||||
this.ignoreNextARelease = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (button == GLFW.GLFW_GAMEPAD_BUTTON_B) {
|
||||
if (client.currentScreen != null) {
|
||||
if (!MidnightControlsCompat.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 {@code 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();
|
||||
|
||||
var screen = (HandledScreen) client.currentScreen;
|
||||
var accessor = (HandledScreenAccessor) screen;
|
||||
Slot slot = ((HandledScreenAccessor) client.currentScreen).midnightcontrols$getSlotAt(x, y);
|
||||
|
||||
int slotId;
|
||||
if (slot == null) {
|
||||
if (client.player.currentScreenHandler.getCursorStack().isEmpty())
|
||||
return false;
|
||||
slotId = accessor.midnightcontrols$isClickOutsideBounds(x, y, accessor.getX(), accessor.getY(), GLFW_MOUSE_BUTTON_1) ? -999 : -1;
|
||||
} else {
|
||||
slotId = slot.id;
|
||||
}
|
||||
|
||||
var actionType = SlotActionType.PICKUP;
|
||||
int clickData = GLFW.GLFW_MOUSE_BUTTON_1;
|
||||
switch (button) {
|
||||
case GLFW_GAMEPAD_BUTTON_A:
|
||||
if (screen instanceof CreativeInventoryScreen)
|
||||
if (((CreativeInventoryScreenAccessor) screen).midnightcontrols$isCreativeInventorySlot(slot))
|
||||
actionType = SlotActionType.CLONE;
|
||||
if (slot != null && MidnightControlsCompat.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.midnightcontrols$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) {
|
||||
var set = ImmutableSet.of("gui.back", "gui.done", "gui.cancel", "gui.toTitle", "gui.toMenu");
|
||||
return screen.children().stream().filter(element -> element instanceof PressableWidget)
|
||||
.map(element -> (PressableWidget) element)
|
||||
.filter(element -> element.getMessage() 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) ? MidnightControlsConfig.leftDeadZone
|
||||
: MidnightControlsConfig.rightDeadZone;
|
||||
}
|
||||
|
||||
private void handleAxe(@NotNull MinecraftClient client, int axis, float value, float absValue, int state) {
|
||||
int asButtonState = value > .5f ? 1 : (value < -.5f ? 2 : 0);
|
||||
|
||||
if (axis == GLFW_GAMEPAD_AXIS_LEFT_TRIGGER || axis == GLFW_GAMEPAD_AXIS_RIGHT_TRIGGER
|
||||
|| axis == ButtonBinding.controller2Button(GLFW.GLFW_GAMEPAD_AXIS_LEFT_TRIGGER)
|
||||
|| axis == ButtonBinding.controller2Button(GLFW.GLFW_GAMEPAD_AXIS_RIGHT_TRIGGER))
|
||||
if (asButtonState == 2)
|
||||
asButtonState = 0;
|
||||
|
||||
{
|
||||
boolean currentPlusState = asButtonState == 1;
|
||||
boolean currentMinusState = asButtonState == 2;
|
||||
var previousPlusState = InputManager.STATES.getOrDefault(ButtonBinding.axisAsButton(axis, true), ButtonState.NONE);
|
||||
var previousMinusState = InputManager.STATES.getOrDefault(ButtonBinding.axisAsButton(axis, false), ButtonState.NONE);
|
||||
|
||||
if (currentPlusState != previousPlusState.isPressed()) {
|
||||
InputManager.STATES.put(ButtonBinding.axisAsButton(axis, true), currentPlusState ? ButtonState.PRESS : ButtonState.RELEASE);
|
||||
if (currentPlusState)
|
||||
BUTTON_COOLDOWNS.put(ButtonBinding.axisAsButton(axis, true), 5);
|
||||
} else if (currentPlusState) {
|
||||
InputManager.STATES.put(ButtonBinding.axisAsButton(axis, true), ButtonState.REPEAT);
|
||||
if (BUTTON_COOLDOWNS.getOrDefault(ButtonBinding.axisAsButton(axis, true), 0) == 0) {
|
||||
BUTTON_COOLDOWNS.put(ButtonBinding.axisAsButton(axis, true), 5);
|
||||
}
|
||||
}
|
||||
|
||||
if (currentMinusState != previousMinusState.isPressed()) {
|
||||
InputManager.STATES.put(ButtonBinding.axisAsButton(axis, false), currentMinusState ? ButtonState.PRESS : ButtonState.RELEASE);
|
||||
if (currentMinusState)
|
||||
BUTTON_COOLDOWNS.put(ButtonBinding.axisAsButton(axis, false), 5);
|
||||
} else if (currentMinusState) {
|
||||
InputManager.STATES.put(ButtonBinding.axisAsButton(axis, false), ButtonState.REPEAT);
|
||||
if (BUTTON_COOLDOWNS.getOrDefault(ButtonBinding.axisAsButton(axis, false), 0) == 0) {
|
||||
BUTTON_COOLDOWNS.put(ButtonBinding.axisAsButton(axis, false), 5);
|
||||
}
|
||||
}
|
||||
|
||||
double deadZone = this.getDeadZoneValue(axis);
|
||||
float axisValue = absValue < deadZone ? 0.f : (float) (absValue - deadZone);
|
||||
axisValue /= (1.0 - deadZone);
|
||||
|
||||
axisValue = (float) Math.min(axisValue / MidnightControlsConfig.getAxisMaxValue(axis), 1);
|
||||
if (currentPlusState)
|
||||
InputManager.BUTTON_VALUES.put(ButtonBinding.axisAsButton(axis, true), axisValue);
|
||||
else
|
||||
InputManager.BUTTON_VALUES.put(ButtonBinding.axisAsButton(axis, true), 0.f);
|
||||
if (currentMinusState)
|
||||
InputManager.BUTTON_VALUES.put(ButtonBinding.axisAsButton(axis, false), axisValue);
|
||||
else
|
||||
InputManager.BUTTON_VALUES.put(ButtonBinding.axisAsButton(axis, false), 0.f);
|
||||
}
|
||||
|
||||
double deadZone = this.getDeadZoneValue(axis);
|
||||
|
||||
if (this.controlsInput != null && this.controlsInput.focusedBinding != null) {
|
||||
if (asButtonState != 0 && !this.controlsInput.currentButtons.contains(ButtonBinding.axisAsButton(axis, asButtonState == 1))) {
|
||||
|
||||
this.controlsInput.currentButtons.add(ButtonBinding.axisAsButton(axis, asButtonState == 1));
|
||||
|
||||
int[] buttons = new int[this.controlsInput.currentButtons.size()];
|
||||
for (int i = 0; i < this.controlsInput.currentButtons.size(); i++)
|
||||
buttons[i] = this.controlsInput.currentButtons.get(i);
|
||||
this.controlsInput.focusedBinding.setButton(buttons);
|
||||
|
||||
this.controlsInput.waiting = false;
|
||||
}
|
||||
return;
|
||||
} else if (client.currentScreen instanceof CreativeInventoryScreen creativeInventoryScreen) {
|
||||
if (axis == GLFW_GAMEPAD_AXIS_RIGHT_Y) {
|
||||
var accessor = (CreativeInventoryScreenAccessor) creativeInventoryScreen;
|
||||
// @TODO allow rebinding to left stick
|
||||
if (accessor.midnightcontrols$hasScrollbar() && absValue >= deadZone) {
|
||||
creativeInventoryScreen.mouseScrolled(0.0, 0.0, -value);
|
||||
}
|
||||
return;
|
||||
}
|
||||
} else if (client.currentScreen instanceof AdvancementsScreen advancementsScreen) {
|
||||
if (axis == GLFW_GAMEPAD_AXIS_RIGHT_X || axis == GLFW_GAMEPAD_AXIS_RIGHT_Y) {
|
||||
var accessor = (AdvancementsScreenAccessor) advancementsScreen;
|
||||
if (absValue >= deadZone) {
|
||||
AdvancementTab tab = accessor.getSelectedTab();
|
||||
tab.move(axis == GLFW_GAMEPAD_AXIS_RIGHT_X ? -value * 5.0 : 0.0, axis == GLFW_GAMEPAD_AXIS_RIGHT_Y ? -value * 5.0 : 0.0);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
absValue -= deadZone;
|
||||
absValue /= (1.0 - deadZone);
|
||||
absValue = (float) MathHelper.clamp(absValue / MidnightControlsConfig.getAxisMaxValue(axis), 0.f, 1.f);
|
||||
if (client.currentScreen == null) {
|
||||
// Handles the look direction.
|
||||
this.handleLook(client, axis, absValue, state);
|
||||
} else {
|
||||
boolean allowMouseControl = true;
|
||||
|
||||
if (this.actionGuiCooldown == 0 && MidnightControlsConfig.isMovementAxis(axis) && isScreenInteractive(client.currentScreen)) {
|
||||
if (MidnightControlsConfig.isForwardButton(axis, false, asButtonState)) {
|
||||
allowMouseControl = this.changeFocus(client.currentScreen, NavigationDirection.UP);
|
||||
} else if (MidnightControlsConfig.isBackButton(axis, false, asButtonState)) {
|
||||
allowMouseControl = this.changeFocus(client.currentScreen, NavigationDirection.DOWN);
|
||||
} else if (MidnightControlsConfig.isLeftButton(axis, false, asButtonState)) {
|
||||
allowMouseControl = this.handleLeftRight(client.currentScreen, false);
|
||||
} else if (MidnightControlsConfig.isRightButton(axis, false, asButtonState)) {
|
||||
allowMouseControl = this.handleLeftRight(client.currentScreen, true);
|
||||
}
|
||||
}
|
||||
|
||||
float movementX = 0.f;
|
||||
float movementY = 0.f;
|
||||
|
||||
if (MidnightControlsConfig.isBackButton(axis, false, (value > 0 ? 1 : 2))) {
|
||||
movementY = absValue;
|
||||
} else if (MidnightControlsConfig.isForwardButton(axis, false, (value > 0 ? 1 : 2))) {
|
||||
movementY = -absValue;
|
||||
} else if (MidnightControlsConfig.isLeftButton(axis, false, (value > 0 ? 1 : 2))) {
|
||||
movementX = -absValue;
|
||||
} else if (MidnightControlsConfig.isRightButton(axis, false, (value > 0 ? 1 : 2))) {
|
||||
movementX = absValue;
|
||||
}
|
||||
|
||||
if (client.currentScreen != null && allowMouseControl) {
|
||||
boolean moving = movementY != 0 || movementX != 0;
|
||||
if (moving) {
|
||||
/*
|
||||
Updates the target mouse position when the initial movement stick movement is detected.
|
||||
It prevents the cursor to jump to the old target mouse position if the user moves the cursor with the mouse.
|
||||
*/
|
||||
if (Math.abs(prevXAxis) < deadZone && Math.abs(prevYAxis) < deadZone) {
|
||||
InputManager.INPUT_MANAGER.resetMouseTarget(client);
|
||||
}
|
||||
|
||||
this.mouseSpeedX = movementX;
|
||||
this.mouseSpeedY = movementY;
|
||||
} else {
|
||||
this.mouseSpeedX = 0.f;
|
||||
this.mouseSpeedY = 0.f;
|
||||
}
|
||||
|
||||
if (Math.abs(this.mouseSpeedX) >= .05f || Math.abs(this.mouseSpeedY) >= .05f) {
|
||||
InputManager.queueMoveMousePosition(
|
||||
this.mouseSpeedX * MidnightControlsConfig.mouseSpeed,
|
||||
this.mouseSpeedY * MidnightControlsConfig.mouseSpeed
|
||||
);
|
||||
}
|
||||
|
||||
this.moveMouseToClosestSlot(client, client.currentScreen);
|
||||
}
|
||||
|
||||
this.prevXAxis = movementX;
|
||||
this.prevYAxis = movementY;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean handleAButton(@NotNull Screen screen, @NotNull Element focused) {
|
||||
if (focused instanceof PressableWidget widget) {
|
||||
widget.playDownSound(MinecraftClient.getInstance().getSoundManager());
|
||||
widget.onPress();
|
||||
return true;
|
||||
} else if (focused instanceof AbstractSprucePressableButtonWidget widget) {
|
||||
widget.playDownSound();
|
||||
widget.onPress();
|
||||
return true;
|
||||
} else if (focused instanceof SpruceLabelWidget labelWidget) {
|
||||
labelWidget.onPress();
|
||||
return true;
|
||||
} else if (focused instanceof WorldListWidget list) {
|
||||
list.getSelectedAsOptional().ifPresent(WorldListWidget.Entry::play);
|
||||
return true;
|
||||
} else if (focused instanceof MultiplayerServerListWidget list) {
|
||||
var entry = list.getSelectedOrNull();
|
||||
if (entry instanceof MultiplayerServerListWidget.LanServerEntry || entry instanceof MultiplayerServerListWidget.ServerEntry) {
|
||||
((MultiplayerScreen) screen).select(entry);
|
||||
((MultiplayerScreen) screen).connect();
|
||||
}
|
||||
} else if (focused instanceof SpruceParentWidget) {
|
||||
var childFocused = ((SpruceParentWidget<?>) focused).getFocused();
|
||||
if (childFocused != null)
|
||||
return this.handleAButton(screen, childFocused);
|
||||
} else if (focused instanceof ParentElement widget) {
|
||||
var childFocused = widget.getFocused();
|
||||
if (childFocused != null)
|
||||
return this.handleAButton(screen, childFocused);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the left and right buttons.
|
||||
*
|
||||
* @param screen the current screen
|
||||
* @param right true if the right button is pressed, else false
|
||||
*/
|
||||
private boolean handleLeftRight(@NotNull Screen screen, boolean right) {
|
||||
if (screen instanceof SpruceScreen spruceScreen) {
|
||||
spruceScreen.onNavigation(right ? NavigationDirection.RIGHT : NavigationDirection.LEFT, false);
|
||||
this.actionGuiCooldown = 5;
|
||||
return false;
|
||||
}
|
||||
var focused = screen.getFocused();
|
||||
if (focused != null)
|
||||
if (this.handleRightLeftElement(focused, right))
|
||||
return this.changeFocus(screen, right ? NavigationDirection.RIGHT : NavigationDirection.LEFT);
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean handleRightLeftElement(@NotNull Element element, boolean right) {
|
||||
if (element instanceof SpruceElement spruceElement) {
|
||||
if (spruceElement.requiresCursor())
|
||||
return true;
|
||||
return !spruceElement.onNavigation(right ? NavigationDirection.RIGHT : NavigationDirection.LEFT, false);
|
||||
}
|
||||
if (element instanceof SliderWidget slider) {
|
||||
slider.keyPressed(right ? 262 : 263, 0, 0);
|
||||
this.actionGuiCooldown = 2; // Prevent to press too quickly the focused element, so we have to skip 5 ticks.
|
||||
return false;
|
||||
} else if (element instanceof AlwaysSelectedEntryListWidget) {
|
||||
((EntryListWidgetAccessor) element).midnightcontrols$moveSelection(right ? EntryListWidget.MoveDirection.UP : EntryListWidget.MoveDirection.DOWN);
|
||||
return false;
|
||||
} else if (element instanceof ParentElement entryList) {
|
||||
var focused = entryList.getFocused();
|
||||
if (focused == null)
|
||||
return true;
|
||||
return this.handleRightLeftElement(focused, right);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the look direction input.
|
||||
*
|
||||
* @param client the client instance
|
||||
* @param axis the axis to change
|
||||
* @param value the value of the look
|
||||
* @param state the state
|
||||
*/
|
||||
public void handleLook(@NotNull MinecraftClient client, int axis, float value, int state) {
|
||||
// Handles the look direction.
|
||||
if (client.player != null) {
|
||||
double powValue = Math.pow(value, 2.0);
|
||||
if (axis == GLFW_GAMEPAD_AXIS_RIGHT_Y) {
|
||||
if (state == 2) {
|
||||
this.targetPitch = -MidnightControlsConfig.getRightYAxisSign() * (MidnightControlsConfig.rotationSpeed * powValue) * 0.11D;
|
||||
} else if (state == 1) {
|
||||
this.targetPitch = MidnightControlsConfig.getRightYAxisSign() * (MidnightControlsConfig.rotationSpeed * powValue) * 0.11D;
|
||||
}
|
||||
}
|
||||
if (axis == GLFW_GAMEPAD_AXIS_RIGHT_X) {
|
||||
if (state == 2) {
|
||||
this.targetYaw = -MidnightControlsConfig.getRightXAxisSign() * (MidnightControlsConfig.rotationSpeed * powValue) * 0.11D;
|
||||
} else if (state == 1) {
|
||||
this.targetYaw = MidnightControlsConfig.getRightXAxisSign() * (MidnightControlsConfig.rotationSpeed * powValue) * 0.11D;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean changeFocus(@NotNull Screen screen, NavigationDirection direction) {
|
||||
if (screen instanceof SpruceScreen spruceScreen) {
|
||||
if (spruceScreen.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())
|
||||
|| MidnightControlsCompat.requireMouseOnScreen(screen));
|
||||
}
|
||||
|
||||
// Inspired from https://github.com/MrCrayfish/Controllable/blob/1.14.X/src/main/java/com/mrcrayfish/controllable/client/ControllerInput.java#L686.
|
||||
private void moveMouseToClosestSlot(@NotNull MinecraftClient client, @Nullable Screen screen) {
|
||||
// Makes the mouse attracted to slots. This helps with selecting items when using a controller.
|
||||
if (screen instanceof HandledScreen inventoryScreen) {
|
||||
var accessor = (HandledScreenAccessor) inventoryScreen;
|
||||
int guiLeft = accessor.getX();
|
||||
int guiTop = accessor.getY();
|
||||
int mouseX = (int) (targetMouseX * (double) client.getWindow().getScaledWidth() / (double) client.getWindow().getWidth());
|
||||
int mouseY = (int) (targetMouseY * (double) client.getWindow().getScaledHeight() / (double) client.getWindow().getHeight());
|
||||
|
||||
// Finds the closest slot in the GUI within 14 pixels.
|
||||
Optional<Pair<Slot, Double>> closestSlot = inventoryScreen.getScreenHandler().slots.parallelStream()
|
||||
.map(slot -> {
|
||||
int x = guiLeft + slot.x + 8;
|
||||
int y = guiTop + slot.y + 8;
|
||||
|
||||
// Distance between the slot and the cursor.
|
||||
double distance = Math.sqrt(Math.pow(x - mouseX, 2) + Math.pow(y - mouseY, 2));
|
||||
return Pair.of(slot, distance);
|
||||
}).filter(entry -> entry.value <= 14.0)
|
||||
.min(Comparator.comparingDouble(p -> p.value));
|
||||
|
||||
if (closestSlot.isPresent()) {
|
||||
var slot = closestSlot.get().key;
|
||||
if (slot.hasStack() || !client.player.getInventory().getMainHandStack().isEmpty()) {
|
||||
int slotCenterXScaled = guiLeft + slot.x + 8;
|
||||
int slotCenterYScaled = guiTop + slot.y + 8;
|
||||
int slotCenterX = (int) (slotCenterXScaled / ((double) client.getWindow().getScaledWidth() / (double) client.getWindow().getWidth()));
|
||||
int slotCenterY = (int) (slotCenterYScaled / ((double) client.getWindow().getScaledHeight() / (double) client.getWindow().getHeight()));
|
||||
double deltaX = slotCenterX - targetMouseX;
|
||||
double deltaY = slotCenterY - targetMouseY;
|
||||
|
||||
if (mouseX != slotCenterXScaled || mouseY != slotCenterYScaled) {
|
||||
this.targetMouseX += deltaX * 0.75;
|
||||
this.targetMouseY += deltaY * 0.75;
|
||||
} else {
|
||||
this.mouseSpeedX *= 0.3F;
|
||||
this.mouseSpeedY *= 0.3F;
|
||||
}
|
||||
this.mouseSpeedX *= .75F;
|
||||
this.mouseSpeedY *= .75F;
|
||||
} else {
|
||||
this.mouseSpeedX *= .1F;
|
||||
this.mouseSpeedY *= .1F;
|
||||
}
|
||||
} else {
|
||||
this.mouseSpeedX *= .3F;
|
||||
this.mouseSpeedY *= .3F;
|
||||
}
|
||||
} else {
|
||||
this.mouseSpeedX = 0.F;
|
||||
this.mouseSpeedY = 0.F;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
/*
|
||||
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
|
||||
*
|
||||
* This file is part of midnightcontrols.
|
||||
*
|
||||
* Licensed under the MIT license. For more information,
|
||||
* see the LICENSE file.
|
||||
*/
|
||||
|
||||
package eu.midnightdust.midnightcontrols.client;
|
||||
|
||||
import eu.midnightdust.midnightcontrols.MidnightControlsFeature;
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.block.BlockState;
|
||||
import net.minecraft.block.FluidBlock;
|
||||
import net.minecraft.block.SlabBlock;
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.item.BlockItem;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.util.hit.BlockHitResult;
|
||||
import net.minecraft.util.hit.HitResult;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.util.math.Direction;
|
||||
import net.minecraft.util.math.MathHelper;
|
||||
import net.minecraft.util.math.Vec3d;
|
||||
import net.minecraft.world.RaycastContext;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Represents the reach-around API of midnightcontrols.
|
||||
*
|
||||
* @version 1.7.0
|
||||
* @since 1.3.2
|
||||
*/
|
||||
public class MidnightReacharound {
|
||||
private BlockHitResult lastReacharoundResult = null;
|
||||
private boolean lastReacharoundVertical = false;
|
||||
private boolean onSlab = false;
|
||||
|
||||
public void tick(@NotNull MinecraftClient client) {
|
||||
this.lastReacharoundResult = this.tryVerticalReachAround(client);
|
||||
if (this.lastReacharoundResult == null) {
|
||||
this.lastReacharoundResult = this.tryHorizontalReachAround(client);
|
||||
this.lastReacharoundVertical = false;
|
||||
} else this.lastReacharoundVertical = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the last reach around result.
|
||||
*
|
||||
* @return the last reach around result
|
||||
*/
|
||||
public @Nullable BlockHitResult getLastReacharoundResult() {
|
||||
return this.lastReacharoundResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the last reach around is vertical.
|
||||
*
|
||||
* @return {@code true} if the reach around is vertical
|
||||
*/
|
||||
public boolean isLastReacharoundVertical() {
|
||||
return this.lastReacharoundVertical;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether reacharound is available or not.
|
||||
*
|
||||
* @return {@code true} if reacharound is available, else {@code false}
|
||||
*/
|
||||
public boolean isReacharoundAvailable() {
|
||||
return MidnightControlsFeature.HORIZONTAL_REACHAROUND.isAvailable() || MidnightControlsFeature.VERTICAL_REACHAROUND.isAvailable();
|
||||
}
|
||||
|
||||
private float getPlayerRange(@NotNull MinecraftClient client) {
|
||||
return client.interactionManager != null ? client.interactionManager.getReachDistance() : 0.f;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a nullable block hit result if vertical reach-around is possible.
|
||||
*
|
||||
* @param client the client instance
|
||||
* @return a block hit result if vertical reach-around is possible, else {@code null}
|
||||
*/
|
||||
public @Nullable BlockHitResult tryVerticalReachAround(@NotNull MinecraftClient client) {
|
||||
if (!MidnightControlsFeature.VERTICAL_REACHAROUND.isAvailable())
|
||||
return null;
|
||||
if (client.player == null || client.world == null || client.crosshairTarget == null || client.crosshairTarget.getType() != HitResult.Type.MISS
|
||||
|| !client.player.isOnGround() || client.player.getPitch(0.f) < 80.0F
|
||||
|| client.player.isRiding())
|
||||
return null;
|
||||
|
||||
Vec3d pos = client.player.getCameraPosVec(1.0F);
|
||||
Vec3d rotationVec = client.player.getRotationVec(1.0F);
|
||||
float range = getPlayerRange(client);
|
||||
var rayVec = pos.add(rotationVec.x * range, rotationVec.y * range, rotationVec.z * range).add(0, 0.75, 0);
|
||||
var result = client.world.raycast(new RaycastContext(pos, rayVec, RaycastContext.ShapeType.OUTLINE, RaycastContext.FluidHandling.NONE, client.player));
|
||||
|
||||
if (result.getType() == HitResult.Type.BLOCK) {
|
||||
BlockPos blockPos = result.getBlockPos().down();
|
||||
BlockState state = client.world.getBlockState(blockPos);
|
||||
|
||||
if (client.player.getBlockPos().getY() - blockPos.getY() > 1 && (client.world.isAir(blockPos) || state.getMaterial().isReplaceable())) {
|
||||
return new BlockHitResult(result.getPos(), Direction.DOWN, blockPos, false);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a nullable block hit result if horizontal reach-around is possible.
|
||||
*
|
||||
* @param client the client instance
|
||||
* @return a block hit result if horizontal reach-around is possible
|
||||
*/
|
||||
public @Nullable BlockHitResult tryHorizontalReachAround(@NotNull MinecraftClient client) {
|
||||
if (!MidnightControlsFeature.HORIZONTAL_REACHAROUND.isAvailable())
|
||||
return null;
|
||||
|
||||
if (client.player != null && client.crosshairTarget != null && client.crosshairTarget.getType() == HitResult.Type.MISS
|
||||
&& client.player.isOnGround() && client.player.getPitch(0.f) > 35.f) {
|
||||
if (client.player.isRiding())
|
||||
return null;
|
||||
var playerPos = client.player.getBlockPos().down();
|
||||
if (client.player.getY() - playerPos.getY() - 1.0 >= 0.25) {
|
||||
playerPos = playerPos.up();
|
||||
this.onSlab = true;
|
||||
} else {
|
||||
this.onSlab = false;
|
||||
}
|
||||
var targetPos = new BlockPos(client.crosshairTarget.getPos()).subtract(playerPos);
|
||||
var vector = new BlockPos.Mutable(MathHelper.clamp(targetPos.getX(), -1, 1), 0, MathHelper.clamp(targetPos.getZ(), -1, 1));
|
||||
var blockPos = playerPos.add(vector);
|
||||
|
||||
var direction = client.player.getHorizontalFacing();
|
||||
|
||||
var state = client.world.getBlockState(blockPos);
|
||||
if (!state.isAir())
|
||||
return null;
|
||||
var adjacentBlockState = client.world.getBlockState(blockPos.offset(direction.getOpposite()));
|
||||
if (adjacentBlockState.isAir() || adjacentBlockState.getBlock() instanceof FluidBlock || (vector.getX() == 0 && vector.getZ() == 0)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new BlockHitResult(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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
|
||||
*
|
||||
* This file is part of midnightcontrols.
|
||||
*
|
||||
* Licensed under the MIT license. For more information,
|
||||
* see the LICENSE file.
|
||||
*/
|
||||
|
||||
package eu.midnightdust.midnightcontrols.client;
|
||||
|
||||
import net.minecraft.text.Text;
|
||||
import 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.7.0
|
||||
* @since 1.2.0
|
||||
*/
|
||||
public enum VirtualMouseSkin implements Nameable {
|
||||
DEFAULT_LIGHT("default_light"),
|
||||
DEFAULT_DARK("default_dark"),
|
||||
SECOND_LIGHT("second_light"),
|
||||
SECOND_DARK("second_dark");
|
||||
|
||||
private final String name;
|
||||
private final Text text;
|
||||
|
||||
VirtualMouseSkin(String name) {
|
||||
this.name = name;
|
||||
this.text = new TranslatableText(this.getTranslationKey());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the next virtual mouse skin available.
|
||||
*
|
||||
* @return the next available virtual mouse skin
|
||||
*/
|
||||
public @NotNull VirtualMouseSkin next() {
|
||||
var v = values();
|
||||
if (v.length == this.ordinal() + 1)
|
||||
return v[0];
|
||||
return v[this.ordinal() + 1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the translation key of this virtual mouse skin.
|
||||
*
|
||||
* @return the virtual mouse skin's translation key
|
||||
*/
|
||||
public @NotNull String getTranslationKey() {
|
||||
return "midnightcontrols.virtual_mouse.skin." + this.getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the translated text of this virtual mouse skin.
|
||||
*
|
||||
* @return the translated text of this virtual mouse skin
|
||||
*/
|
||||
public @NotNull Text getTranslatedText() {
|
||||
return this.text;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the virtual mouse skin from its identifier.
|
||||
*
|
||||
* @param id the identifier of the virtual mouse skin
|
||||
* @return the virtual mouse skin if found, else empty
|
||||
*/
|
||||
public static @NotNull Optional<VirtualMouseSkin> byId(@NotNull String id) {
|
||||
return Arrays.stream(values()).filter(mode -> mode.getName().equalsIgnoreCase(id)).findFirst();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
|
||||
*
|
||||
* This file is part of midnightcontrols.
|
||||
*
|
||||
* Licensed under the MIT license. For more information,
|
||||
* see the LICENSE file.
|
||||
*/
|
||||
|
||||
package eu.midnightdust.midnightcontrols.client.compat;
|
||||
|
||||
import eu.midnightdust.midnightcontrols.client.MidnightControlsClient;
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.client.gui.screen.Screen;
|
||||
import net.minecraft.client.gui.screen.ingame.HandledScreen;
|
||||
import net.minecraft.screen.slot.Slot;
|
||||
import net.minecraft.util.hit.BlockHitResult;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Represents a compatibility handler for a mod.
|
||||
*
|
||||
* @author LambdAurora
|
||||
* @version 1.7.0
|
||||
* @since 1.1.0
|
||||
*/
|
||||
public interface CompatHandler {
|
||||
/**
|
||||
* Handles compatibility of a mod.
|
||||
*
|
||||
* @param mod this mod instance
|
||||
*/
|
||||
void handle(@NotNull MidnightControlsClient mod);
|
||||
|
||||
/**
|
||||
* Returns whether the mouse is required on the specified screen.
|
||||
*
|
||||
* @param screen the screen
|
||||
* @return true if the mouse is required on the specified screen, else false
|
||||
*/
|
||||
default boolean requireMouseOnScreen(Screen screen) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a slot at the specified location if possible.
|
||||
*
|
||||
* @param screen the screen
|
||||
* @param mouseX the mouse X-coordinate
|
||||
* @param mouseY the mouse Y-coordinate
|
||||
* @return a slot if present, else null
|
||||
* @since 1.5.0
|
||||
*/
|
||||
default @Nullable CompatHandler.SlotPos getSlotAt(@NotNull Screen screen, int mouseX, int mouseY) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the current slot is a creative slot or not.
|
||||
*
|
||||
* @param screen the screen
|
||||
* @param slot the slot to check
|
||||
* @return true if the slot is a creative slot, else false
|
||||
*/
|
||||
default boolean isCreativeSlot(@NotNull HandledScreen screen, @NotNull Slot slot) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a custom translation key to make custom attack action strings on the HUD.
|
||||
*
|
||||
* @param client the client instance
|
||||
* @param placeResult the last place block result
|
||||
* @return null if untouched, else a translation key
|
||||
*/
|
||||
default String getAttackActionAt(@NotNull MinecraftClient client, @Nullable BlockHitResult placeResult) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a custom translation key to make custom use action strings on the HUD.
|
||||
*
|
||||
* @param client the client instance
|
||||
* @param placeResult the last place block result
|
||||
* @return null if untouched, else a translation key
|
||||
*/
|
||||
default String getUseActionAt(@NotNull MinecraftClient client, @Nullable BlockHitResult placeResult) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the menu back button.
|
||||
*
|
||||
* @param client the client instance
|
||||
* @param screen the screen
|
||||
* @return true if the handle was fired and succeed, else false
|
||||
*/
|
||||
default boolean handleMenuBack(@NotNull MinecraftClient client, @NotNull Screen screen) {
|
||||
return false;
|
||||
}
|
||||
|
||||
record SlotPos(int x, int y) {
|
||||
public static final SlotPos INVALID_SLOT = new SlotPos(-1, -1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
|
||||
*
|
||||
* This file is part of midnightcontrols.
|
||||
*
|
||||
* Licensed under the MIT license. For more information,
|
||||
* see the LICENSE file.
|
||||
*/
|
||||
|
||||
package eu.midnightdust.midnightcontrols.client.compat;
|
||||
|
||||
import eu.midnightdust.midnightcontrols.client.MidnightControlsClient;
|
||||
import eu.midnightdust.midnightcontrols.client.controller.ButtonBinding;
|
||||
import io.github.kosmx.emotes.arch.gui.screen.ingame.FastChosseScreen;
|
||||
import io.github.kosmx.emotes.main.network.ClientEmotePlay;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Represents a compatibility handler for Emotecraft.
|
||||
*
|
||||
* @author Motschen
|
||||
* @version 1.4.3
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public class EmotecraftCompat implements CompatHandler {
|
||||
@Override
|
||||
public void handle(@NotNull MidnightControlsClient mod) {
|
||||
new ButtonBinding.Builder("key.emotecraft.fastchoose")
|
||||
.buttons(16)
|
||||
.onlyInGame()
|
||||
.cooldown(true)
|
||||
.category(ButtonBinding.MISC_CATEGORY)
|
||||
.action((client, button, value, action) -> {
|
||||
client.setScreen(new FastChosseScreen(null));
|
||||
return true;
|
||||
})
|
||||
.register();
|
||||
new ButtonBinding.Builder("key.emotecraft.stop")
|
||||
.buttons(17)
|
||||
.onlyInGame()
|
||||
.cooldown(true)
|
||||
.category(ButtonBinding.MISC_CATEGORY)
|
||||
.action((client, button, value, action) -> {
|
||||
ClientEmotePlay.clientStopLocalEmote();
|
||||
return true;
|
||||
})
|
||||
.register();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
|
||||
*
|
||||
* This file is part of midnightcontrols.
|
||||
*
|
||||
* Licensed under the MIT license. For more information,
|
||||
* see the LICENSE file.
|
||||
*/
|
||||
|
||||
package eu.midnightdust.midnightcontrols.client.compat;
|
||||
|
||||
import eu.midnightdust.midnightcontrols.client.MidnightControlsClient;
|
||||
import net.minecraft.client.gui.screen.Screen;
|
||||
import org.aperlambda.lambdacommon.utils.LambdaReflection;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Represents HQM compatibility handler.
|
||||
* <p>
|
||||
* This is bad.
|
||||
*
|
||||
* @author LambdAurora
|
||||
* @version 1.3.2
|
||||
* @since 1.3.2
|
||||
*/
|
||||
public class HQMCompat implements CompatHandler {
|
||||
public static final String GUI_BASE_CLASS_PATH = "hardcorequesting.client.interfaces.GuiBase";
|
||||
private Optional<Class<?>> guiBaseClass;
|
||||
|
||||
@Override
|
||||
public void handle(@NotNull MidnightControlsClient mod) {
|
||||
this.guiBaseClass = LambdaReflection.getClass(GUI_BASE_CLASS_PATH);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean requireMouseOnScreen(Screen screen) {
|
||||
return this.guiBaseClass.map(clazz -> clazz.isInstance(screen)).orElse(false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
/*
|
||||
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
|
||||
*
|
||||
* This file is part of midnightcontrols.
|
||||
*
|
||||
* Licensed under the MIT license. For more information,
|
||||
* see the LICENSE file.
|
||||
*/
|
||||
|
||||
package eu.midnightdust.midnightcontrols.client.compat;
|
||||
|
||||
import eu.midnightdust.midnightcontrols.client.MidnightControlsClient;
|
||||
import eu.midnightdust.midnightcontrols.client.controller.InputManager;
|
||||
import net.fabricmc.loader.api.FabricLoader;
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.client.gui.screen.Screen;
|
||||
import net.minecraft.util.hit.BlockHitResult;
|
||||
import org.aperlambda.lambdacommon.utils.LambdaReflection;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Represents a compatibility handler.
|
||||
*
|
||||
* @author LambdAurora
|
||||
* @version 1.5.0
|
||||
* @since 1.1.0
|
||||
*/
|
||||
public class MidnightControlsCompat {
|
||||
public static final List<CompatHandler> HANDLERS = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Initializes compatibility with other mods if needed.
|
||||
*
|
||||
* @param mod the mod instance
|
||||
*/
|
||||
public static void init(@NotNull MidnightControlsClient mod) {
|
||||
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());
|
||||
}
|
||||
if (FabricLoader.getInstance().isModLoaded("emotecraft")) {
|
||||
mod.log("Adding Emotecraft compatibility...");
|
||||
HANDLERS.add(new EmotecraftCompat());
|
||||
}
|
||||
HANDLERS.forEach(handler -> handler.handle(mod));
|
||||
InputManager.loadButtonBindings();
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a new compatibility handler.
|
||||
*
|
||||
* @param handler the compatibility handler to register
|
||||
*/
|
||||
public static void registerCompatHandler(@NotNull CompatHandler handler) {
|
||||
HANDLERS.add(handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Streams through compatibility handlers.
|
||||
*
|
||||
* @return a stream of compatibility handlers
|
||||
*/
|
||||
public static Stream<CompatHandler> streamCompatHandlers() {
|
||||
return HANDLERS.stream();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the mouse is required on the specified screen.
|
||||
*
|
||||
* @param screen the screen
|
||||
* @return true if the mouse is requried on the specified screen, else false
|
||||
*/
|
||||
public static boolean requireMouseOnScreen(Screen screen) {
|
||||
return HANDLERS.stream().anyMatch(handler -> handler.requireMouseOnScreen(screen));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a slot at the specified location if possible.
|
||||
*
|
||||
* @param screen the screen
|
||||
* @param mouseX the mouse X-coordinate
|
||||
* @param mouseY the mouse Y-coordinate
|
||||
* @return a slot if present, else null
|
||||
*/
|
||||
public static @Nullable CompatHandler.SlotPos getSlotAt(@NotNull Screen screen, int mouseX, int mouseY) {
|
||||
for (var handler : HANDLERS) {
|
||||
var slot = handler.getSlotAt(screen, mouseX, mouseY);
|
||||
if (slot != null)
|
||||
return slot;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a custom translation key to make custom attack action strings on the HUD.
|
||||
*
|
||||
* @param client the client instance
|
||||
* @param placeResult the last place block result
|
||||
* @return null if untouched, else a translation key
|
||||
*/
|
||||
public static String getAttackActionAt(@NotNull MinecraftClient client, @Nullable BlockHitResult placeResult) {
|
||||
for (CompatHandler handler : HANDLERS) {
|
||||
String action = handler.getAttackActionAt(client, placeResult);
|
||||
if (action != null) {
|
||||
return action;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a custom translation key to make custom use action strings on the HUD.
|
||||
*
|
||||
* @param client the client instance
|
||||
* @param placeResult the last place block result
|
||||
* @return null if untouched, else a translation key
|
||||
*/
|
||||
public static String getUseActionAt(@NotNull MinecraftClient client, @Nullable BlockHitResult placeResult) {
|
||||
for (CompatHandler handler : HANDLERS) {
|
||||
String action = handler.getUseActionAt(client, placeResult);
|
||||
if (action != null) {
|
||||
return action;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the menu back button.
|
||||
*
|
||||
* @param client the client instance
|
||||
* @param screen the screen
|
||||
* @return true if the handle was fired and succeed, else false
|
||||
*/
|
||||
public static boolean handleMenuBack(@NotNull MinecraftClient client, @NotNull Screen screen) {
|
||||
for (CompatHandler handler : HANDLERS) {
|
||||
if (handler.handleMenuBack(client, screen))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether Roughly Enough Items is present.
|
||||
*
|
||||
* @return true if Roughly Enough Items is present, else false
|
||||
*/
|
||||
public static boolean isReiPresent() {
|
||||
return FabricLoader.getInstance().isModLoaded("roughlyenoughitems");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
|
||||
*
|
||||
* This file is part of midnightcontrols.
|
||||
*
|
||||
* Licensed under the MIT license. For more information,
|
||||
* see the LICENSE file.
|
||||
*/
|
||||
|
||||
package eu.midnightdust.midnightcontrols.client.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 MidnightControlsMixinPlugin implements IMixinConfigPlugin {
|
||||
private final HashMap<String, Boolean> conditionalMixins = new HashMap<>();
|
||||
|
||||
public MidnightControlsMixinPlugin() {
|
||||
this.putConditionalMixin("EntryListWidgetAccessor", MidnightControlsCompat.isReiPresent());
|
||||
this.putConditionalMixin("EntryWidgetAccessor", MidnightControlsCompat.isReiPresent());
|
||||
this.putConditionalMixin("RecipeViewingScreenAccessor", MidnightControlsCompat.isReiPresent());
|
||||
this.putConditionalMixin("VillagerRecipeViewingScreenAccessor", MidnightControlsCompat.isReiPresent());
|
||||
}
|
||||
|
||||
private void putConditionalMixin(@NotNull String path, boolean condition) {
|
||||
this.conditionalMixins.put("eu.midnightdust.midnightcontrols.client.compat.mixin." + path, condition);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoad(String mixinPackage) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRefMapperConfig() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldApplyMixin(String targetClassName, String mixinClassName) {
|
||||
return this.conditionalMixins.getOrDefault(mixinClassName, Boolean.TRUE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void acceptTargets(Set<String> myTargets, Set<String> otherTargets) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getMixins() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
|
||||
*
|
||||
* This file is part of midnightcontrols.
|
||||
*
|
||||
* Licensed under the MIT license. For more information,
|
||||
* see the LICENSE file.
|
||||
*/
|
||||
|
||||
package eu.midnightdust.midnightcontrols.client.compat;
|
||||
|
||||
import eu.midnightdust.midnightcontrols.client.MidnightControlsClient;
|
||||
import eu.midnightdust.midnightcontrols.client.controller.ButtonBinding;
|
||||
import io.github.ennuil.okzoomer.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 MidnightControlsClient 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,365 @@
|
||||
/*
|
||||
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
|
||||
*
|
||||
* This file is part of midnightcontrols.
|
||||
*
|
||||
* Licensed under the MIT license. For more information,
|
||||
* see the LICENSE file.
|
||||
*/
|
||||
|
||||
package eu.midnightdust.midnightcontrols.client.compat;
|
||||
|
||||
import eu.midnightdust.midnightcontrols.client.ButtonState;
|
||||
import eu.midnightdust.midnightcontrols.client.MidnightControlsClient;
|
||||
import eu.midnightdust.midnightcontrols.client.compat.mixin.EntryListWidgetAccessor;
|
||||
import eu.midnightdust.midnightcontrols.client.compat.mixin.EntryWidgetAccessor;
|
||||
import eu.midnightdust.midnightcontrols.client.controller.ButtonBinding;
|
||||
import eu.midnightdust.midnightcontrols.client.controller.InputHandlers;
|
||||
import eu.midnightdust.midnightcontrols.client.controller.PressAction;
|
||||
import me.shedaniel.rei.RoughlyEnoughItemsCoreClient;
|
||||
import me.shedaniel.rei.impl.client.ClientHelperImpl;
|
||||
import me.shedaniel.rei.impl.client.REIRuntimeImpl;
|
||||
import me.shedaniel.rei.impl.client.gui.ScreenOverlayImpl;
|
||||
import me.shedaniel.rei.impl.client.gui.screen.CompositeDisplayViewingScreen;
|
||||
import me.shedaniel.rei.impl.client.gui.screen.DefaultDisplayViewingScreen;
|
||||
import me.shedaniel.rei.impl.client.gui.widget.EntryListEntryWidget;
|
||||
import me.shedaniel.rei.impl.client.gui.widget.EntryWidget;
|
||||
import me.shedaniel.rei.impl.client.registry.screen.ScreenRegistryImpl;
|
||||
import me.shedaniel.rei.impl.common.entry.AbstractEntryStack;
|
||||
import net.fabricmc.fabric.api.client.screenhandler.v1.ScreenRegistry;
|
||||
import net.fabricmc.fabric.api.screenhandler.v1.ScreenHandlerRegistry;
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.client.gui.Element;
|
||||
import net.minecraft.client.gui.Selectable;
|
||||
import net.minecraft.client.gui.screen.Screen;
|
||||
import net.minecraft.client.gui.widget.ButtonWidget;
|
||||
import net.minecraft.client.gui.widget.EntryListWidget;
|
||||
import net.minecraft.util.Identifier;
|
||||
import net.minecraft.util.Pair;
|
||||
import org.aperlambda.lambdacommon.utils.LambdaReflection;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import static eu.midnightdust.midnightcontrols.client.compat.CompatHandler.SlotPos.INVALID_SLOT;
|
||||
import static org.lwjgl.glfw.GLFW.*;
|
||||
|
||||
/**
|
||||
* Represents a compatibility handler for REI.
|
||||
*
|
||||
* @author LambdAurora
|
||||
* @version 1.7.0
|
||||
* @since 1.2.0
|
||||
*/
|
||||
public class ReiCompat implements CompatHandler {
|
||||
private static EntryListWidget ENTRY_LIST_WIDGET;
|
||||
|
||||
@Override
|
||||
public void handle(@NotNull MidnightControlsClient 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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable CompatHandler.SlotPos getSlotAt(@NotNull Screen screen, int mouseX, int mouseY) {
|
||||
|
||||
ScreenOverlayImpl overlay = ScreenOverlayImpl.getInstance();
|
||||
if (overlay.isInside(mouseX, mouseY)) {
|
||||
EntryListWidget widget = getEntryListWidget();
|
||||
if (widget == null)
|
||||
return null;
|
||||
|
||||
return this.getSlotAt(widget, mouseX, mouseY, false);
|
||||
} else if (isViewingScreen(screen)) {
|
||||
for (Element element : screen.children()) {
|
||||
var slot = this.getSlotAt(element, mouseX, mouseY, true);
|
||||
if (slot != null)
|
||||
return slot;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private @Nullable CompatHandler.SlotPos 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.getEntries().isEmpty())
|
||||
return INVALID_SLOT;
|
||||
return new SlotPos(entry.getBounds().getX() + 1, entry.getBounds().getY() + 1);
|
||||
}
|
||||
} else if (element instanceof EntryListWidget) {
|
||||
List<EntryListEntryWidget> entries = ((EntryListWidgetAccessor) element).getEntries();
|
||||
for (EntryListEntryWidget entry : entries) {
|
||||
var slot = this.getSlotAt(entry, mouseX, mouseY, allowEmpty);
|
||||
if (slot != null && slot != INVALID_SLOT)
|
||||
return slot;
|
||||
}
|
||||
} else if (!(element instanceof ButtonWidget) && element instanceof WidgetWithBounds widgetWithBounds) {
|
||||
for (var child : widgetWithBounds.children()) {
|
||||
var 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 DefaultDisplayViewingScreen || screen instanceof CompositeDisplayViewingScreen;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handleMenuBack(@NotNull MinecraftClient client, @NotNull Screen screen) {
|
||||
if (!isViewingScreen(screen))
|
||||
return false;
|
||||
|
||||
MinecraftClient.getInstance().setScreen(REIRuntimeImpl.getInstance().getPreviousContainerScreen());
|
||||
REIRuntimeImpl.getInstance().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 AbstractEntryStack 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 = REIRuntimeImpl.getInstance().getOverlay(false,false);
|
||||
if (!overlay.isPresent())
|
||||
return RecipeHelper.getInstance().getScreenFocusedStack(client.currentScreen);
|
||||
EntryListWidget widget = getEntryListWidget();
|
||||
if (widget == null)
|
||||
return ScreenOverlayImpl.getInstance().getInstance().getOverlayMenu()..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;
|
||||
}
|
||||
*/
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
|
||||
*
|
||||
* This file is part of midnightcontrols.
|
||||
*
|
||||
* Licensed under the MIT license. For more information,
|
||||
* see the LICENSE file.
|
||||
*/
|
||||
|
||||
package eu.midnightdust.midnightcontrols.client.compat.mixin;
|
||||
|
||||
import net.minecraft.client.gui.Element;
|
||||
import net.minecraft.client.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 = "children")
|
||||
List<Element> getEntries();
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
|
||||
*
|
||||
* This file is part of midnightcontrols.
|
||||
*
|
||||
* Licensed under the MIT license. For more information,
|
||||
* see the LICENSE file.
|
||||
*/
|
||||
|
||||
package eu.midnightdust.midnightcontrols.client.compat.mixin;
|
||||
|
||||
/**
|
||||
* 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 midnightcontrols_getCurrentEntry();*/
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
|
||||
*
|
||||
* This file is part of midnightcontrols.
|
||||
*
|
||||
* Licensed under the MIT license. For more information,
|
||||
* see the LICENSE file.
|
||||
*/
|
||||
|
||||
package eu.midnightdust.midnightcontrols.client.compat.mixin;
|
||||
|
||||
/**
|
||||
* Represents an accessor to REI's RecipeViewingScreen.
|
||||
*
|
||||
* @author LambdAurora
|
||||
* @version 1.7.0
|
||||
* @since 1.2.0
|
||||
*/
|
||||
//@Mixin(value = DefaultDisplayViewingScreen.class, remap = false)
|
||||
public interface RecipeViewingScreenAccessor {
|
||||
/*@Accessor("categoryBack")
|
||||
Button getCategoryBack();
|
||||
|
||||
@Accessor("categoryNext")
|
||||
Button getCategoryNext();
|
||||
|
||||
@Accessor("recipeBack")
|
||||
Button getRecipeBack();
|
||||
|
||||
@Accessor("recipeNext")
|
||||
Button getRecipeNext();*/
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
|
||||
*
|
||||
* This file is part of midnightcontrols.
|
||||
*
|
||||
* Licensed under the MIT license. For more information,
|
||||
* see the LICENSE file.
|
||||
*/
|
||||
|
||||
package eu.midnightdust.midnightcontrols.client.compat.mixin;
|
||||
|
||||
/**
|
||||
* Represents an accessor to REI's VillagerRecipeViewingScreen.
|
||||
*
|
||||
* @author LambdAurora
|
||||
* @version 1.7.0
|
||||
* @since 1.2.0
|
||||
*/
|
||||
//@Mixin(CompositeDisplayViewingScreen.class)
|
||||
public interface VillagerRecipeViewingScreenAccessor {
|
||||
/*@Accessor(value = "categoryMap", remap = false)
|
||||
Map<DisplayCategory<?>, List<Display>> getCategoryMap();
|
||||
|
||||
@Accessor(value = "categories", remap = false)
|
||||
List<DisplayCategory<?>> 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 midnightcontrols_init();*/
|
||||
}
|
||||
@@ -0,0 +1,574 @@
|
||||
/*
|
||||
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
|
||||
*
|
||||
* This file is part of midnightcontrols.
|
||||
*
|
||||
* Licensed under the MIT license. For more information,
|
||||
* see the LICENSE file.
|
||||
*/
|
||||
|
||||
package eu.midnightdust.midnightcontrols.client.controller;
|
||||
|
||||
import eu.midnightdust.midnightcontrols.client.ButtonState;
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.client.option.GameOptions;
|
||||
import net.minecraft.client.option.KeyBinding;
|
||||
import net.minecraft.text.Text;
|
||||
import net.minecraft.text.TranslatableText;
|
||||
import net.minecraft.util.Identifier;
|
||||
import org.aperlambda.lambdacommon.utils.function.PairPredicate;
|
||||
import org.aperlambda.lambdacommon.utils.function.Predicates;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.lwjgl.glfw.GLFW.*;
|
||||
|
||||
/**
|
||||
* Represents a button binding.
|
||||
*
|
||||
* @author LambdAurora
|
||||
* @version 1.7.0
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public class ButtonBinding {
|
||||
public static final ButtonCategory MOVEMENT_CATEGORY;
|
||||
public static final ButtonCategory GAMEPLAY_CATEGORY;
|
||||
public static final ButtonCategory INVENTORY_CATEGORY;
|
||||
public static final ButtonCategory MULTIPLAYER_CATEGORY;
|
||||
public static final ButtonCategory MISC_CATEGORY;
|
||||
|
||||
public static final ButtonBinding ATTACK = new Builder("attack").buttons(axisAsButton(GLFW_GAMEPAD_AXIS_RIGHT_TRIGGER, true)).onlyInGame().register();
|
||||
public static final ButtonBinding BACK = new Builder("back").buttons(axisAsButton(GLFW_GAMEPAD_AXIS_LEFT_Y, false))
|
||||
.action(MovementHandler.HANDLER).onlyInGame().register();
|
||||
public static final ButtonBinding CHAT = new Builder("chat").buttons(GLFW_GAMEPAD_BUTTON_DPAD_RIGHT).onlyInGame().cooldown().register();
|
||||
public static final ButtonBinding 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 final int[] defaultButton;
|
||||
private final String key;
|
||||
private final Text text;
|
||||
private KeyBinding mcKeyBinding = null;
|
||||
protected PairPredicate<MinecraftClient, ButtonBinding> filter;
|
||||
private final List<PressAction> actions = new ArrayList<>(Collections.singletonList(PressAction.DEFAULT_ACTION));
|
||||
private boolean hasCooldown;
|
||||
private int cooldown = 0;
|
||||
boolean pressed = false;
|
||||
|
||||
public ButtonBinding(String key, int[] defaultButton, List<PressAction> actions, PairPredicate<MinecraftClient, ButtonBinding> filter, boolean hasCooldown) {
|
||||
this.setButton(this.defaultButton = defaultButton);
|
||||
this.key = key;
|
||||
this.text = new TranslatableText(this.key);
|
||||
this.filter = filter;
|
||||
this.actions.addAll(actions);
|
||||
this.hasCooldown = hasCooldown;
|
||||
}
|
||||
|
||||
public ButtonBinding(String key, int[] defaultButton, boolean hasCooldown) {
|
||||
this(key, defaultButton, Collections.emptyList(), Predicates.pairAlwaysTrue(), hasCooldown);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the button bound.
|
||||
*
|
||||
* @return the bound button
|
||||
*/
|
||||
public int[] getButton() {
|
||||
return this.button;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the bound button.
|
||||
*
|
||||
* @param button the bound button
|
||||
*/
|
||||
public void setButton(int[] button) {
|
||||
this.button = button;
|
||||
|
||||
if (InputManager.hasBinding(this))
|
||||
InputManager.sortBindings();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the bound button is the specified button or not.
|
||||
*
|
||||
* @param button the button to check
|
||||
* @return true if the bound button is the specified button, else false
|
||||
*/
|
||||
public boolean isButton(int[] button) {
|
||||
return InputManager.areButtonsEquivalent(button, this.button);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this button is down or not.
|
||||
*
|
||||
* @return true if the button is down, else false
|
||||
*/
|
||||
public boolean isButtonDown() {
|
||||
return this.pressed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this button binding is bound or not.
|
||||
*
|
||||
* @return true if this button binding is bound, else false
|
||||
*/
|
||||
public boolean isNotBound() {
|
||||
return this.button.length == 0 || this.button[0] == -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the default button assigned to this binding.
|
||||
*
|
||||
* @return the default button
|
||||
*/
|
||||
public int[] getDefaultButton() {
|
||||
return this.defaultButton;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the assigned button is the default button.
|
||||
*
|
||||
* @return true if the assigned button is the default button, else false
|
||||
*/
|
||||
public boolean isDefault() {
|
||||
return this.button.length == this.defaultButton.length && InputManager.areButtonsEquivalent(this.button, this.defaultButton);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the button code.
|
||||
*
|
||||
* @return the button code
|
||||
*/
|
||||
public String getButtonCode() {
|
||||
return Arrays.stream(this.button)
|
||||
.mapToObj(btn -> Integer.valueOf(btn).toString())
|
||||
.collect(Collectors.joining("+"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the key binding to emulate with this button binding.
|
||||
*
|
||||
* @param keyBinding the optional key binding
|
||||
*/
|
||||
public void setKeyBinding(@Nullable KeyBinding keyBinding) {
|
||||
this.mcKeyBinding = keyBinding;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the button binding is available in the current context.
|
||||
*
|
||||
* @param client the client instance
|
||||
* @return true if the button binding is available, else false
|
||||
*/
|
||||
public boolean isAvailable(@NotNull MinecraftClient client) {
|
||||
return this.filter.test(client, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the button binding cooldown.
|
||||
*/
|
||||
public void update() {
|
||||
if (this.hasCooldown && this.cooldown > 0)
|
||||
this.cooldown--;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the button binding.
|
||||
*
|
||||
* @param client the client instance
|
||||
* @param state the state
|
||||
*/
|
||||
public void handle(@NotNull MinecraftClient client, float value, @NotNull ButtonState state) {
|
||||
if (state == ButtonState.REPEAT && this.hasCooldown && this.cooldown != 0)
|
||||
return;
|
||||
if (this.hasCooldown && state.isPressed()) {
|
||||
this.cooldown = 5;
|
||||
}
|
||||
for (int i = this.actions.size() - 1; i >= 0; i--) {
|
||||
if (this.actions.get(i).press(client, this, value, state))
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public @NotNull String getName() {
|
||||
return this.key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the translation key of this button binding.
|
||||
*
|
||||
* @return the translation key
|
||||
*/
|
||||
public @NotNull String getTranslationKey() {
|
||||
return "midnightcontrols.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() {
|
||||
return Optional.ofNullable(this.mcKeyBinding);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ButtonBinding{id=\"" + this.key + "\","
|
||||
+ "hasCooldown=" + this.hasCooldown
|
||||
+ "}";
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the specified axis as a button.
|
||||
*
|
||||
* @param axis the axis
|
||||
* @param positive true if the axis part is positive, else false
|
||||
* @return the axis as a button
|
||||
*/
|
||||
public static int axisAsButton(int axis, boolean positive) {
|
||||
return positive ? 100 + axis : 200 + axis;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the specified button is an axis or not.
|
||||
*
|
||||
* @param button the button
|
||||
* @return true if the button is an axis, else false
|
||||
*/
|
||||
public static boolean isAxis(int button) {
|
||||
button %= 500;
|
||||
return button >= 100;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the second Joycon's specified button code.
|
||||
*
|
||||
* @param button the raw button code
|
||||
* @return the second Joycon's button code
|
||||
*/
|
||||
public static int controller2Button(int button) {
|
||||
return 500 + button;
|
||||
}
|
||||
|
||||
public static void init(@NotNull GameOptions options) {
|
||||
ATTACK.mcKeyBinding = options.keyAttack;
|
||||
BACK.mcKeyBinding = options.keyBack;
|
||||
CHAT.mcKeyBinding = options.keyChat;
|
||||
DROP_ITEM.mcKeyBinding = options.keyDrop;
|
||||
FORWARD.mcKeyBinding = options.keyForward;
|
||||
INVENTORY.mcKeyBinding = options.keyInventory;
|
||||
JUMP.mcKeyBinding = options.keyJump;
|
||||
LEFT.mcKeyBinding = options.keyLeft;
|
||||
PICK_BLOCK.mcKeyBinding = options.keyPickItem;
|
||||
PLAYER_LIST.mcKeyBinding = options.keyPlayerList;
|
||||
RIGHT.mcKeyBinding = options.keyRight;
|
||||
SCREENSHOT.mcKeyBinding = options.keyScreenshot;
|
||||
SMOOTH_CAMERA.mcKeyBinding = options.keySmoothCamera;
|
||||
SNEAK.mcKeyBinding = options.keySneak;
|
||||
SPRINT.mcKeyBinding = options.keySprint;
|
||||
SWAP_HANDS.mcKeyBinding = options.keySwapHands;
|
||||
TOGGLE_PERSPECTIVE.mcKeyBinding = options.keyTogglePerspective;
|
||||
USE.mcKeyBinding = options.keyUse;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the localized name of the specified button.
|
||||
*
|
||||
* @param button the button
|
||||
* @return the localized name of the button
|
||||
*/
|
||||
public static @NotNull Text getLocalizedButtonName(int button) {
|
||||
return switch (button % 500) {
|
||||
case -1 -> new TranslatableText("key.keyboard.unknown");
|
||||
case GLFW_GAMEPAD_BUTTON_A -> new TranslatableText("midnightcontrols.button.a");
|
||||
case GLFW_GAMEPAD_BUTTON_B -> new TranslatableText("midnightcontrols.button.b");
|
||||
case GLFW_GAMEPAD_BUTTON_X -> new TranslatableText("midnightcontrols.button.x");
|
||||
case GLFW_GAMEPAD_BUTTON_Y -> new TranslatableText("midnightcontrols.button.y");
|
||||
case GLFW_GAMEPAD_BUTTON_LEFT_BUMPER -> new TranslatableText("midnightcontrols.button.left_bumper");
|
||||
case GLFW_GAMEPAD_BUTTON_RIGHT_BUMPER -> new TranslatableText("midnightcontrols.button.right_bumper");
|
||||
case GLFW_GAMEPAD_BUTTON_BACK -> new TranslatableText("midnightcontrols.button.back");
|
||||
case GLFW_GAMEPAD_BUTTON_START -> new TranslatableText("midnightcontrols.button.start");
|
||||
case GLFW_GAMEPAD_BUTTON_GUIDE -> new TranslatableText("midnightcontrols.button.guide");
|
||||
case GLFW_GAMEPAD_BUTTON_LEFT_THUMB -> new TranslatableText("midnightcontrols.button.left_thumb");
|
||||
case GLFW_GAMEPAD_BUTTON_RIGHT_THUMB -> new TranslatableText("midnightcontrols.button.right_thumb");
|
||||
case GLFW_GAMEPAD_BUTTON_DPAD_UP -> new TranslatableText("midnightcontrols.button.dpad_up");
|
||||
case GLFW_GAMEPAD_BUTTON_DPAD_RIGHT -> new TranslatableText("midnightcontrols.button.dpad_right");
|
||||
case GLFW_GAMEPAD_BUTTON_DPAD_DOWN -> new TranslatableText("midnightcontrols.button.dpad_down");
|
||||
case GLFW_GAMEPAD_BUTTON_DPAD_LEFT -> new TranslatableText("midnightcontrols.button.dpad_left");
|
||||
case 100 -> new TranslatableText("midnightcontrols.axis.left_x+");
|
||||
case 101 -> new TranslatableText("midnightcontrols.axis.left_y+");
|
||||
case 102 -> new TranslatableText("midnightcontrols.axis.right_x+");
|
||||
case 103 -> new TranslatableText("midnightcontrols.axis.right_y+");
|
||||
case 104 -> new TranslatableText("midnightcontrols.axis.left_trigger");
|
||||
case 105 -> new TranslatableText("midnightcontrols.axis.right_trigger");
|
||||
case 200 -> new TranslatableText("midnightcontrols.axis.left_x-");
|
||||
case 201 -> new TranslatableText("midnightcontrols.axis.left_y-");
|
||||
case 202 -> new TranslatableText("midnightcontrols.axis.right_x-");
|
||||
case 203 -> new TranslatableText("midnightcontrols.axis.right_y-");
|
||||
case 15 -> new TranslatableText("midnightcontrols.button.l4");
|
||||
case 16 -> new TranslatableText("midnightcontrols.button.l5");
|
||||
case 17 -> new TranslatableText("midnightcontrols.button.r4");
|
||||
case 18 -> new TranslatableText("midnightcontrols.button.r5");
|
||||
default -> new TranslatableText("midnightcontrols.button.unknown", button);
|
||||
};
|
||||
}
|
||||
|
||||
static {
|
||||
MOVEMENT_CATEGORY = InputManager.registerDefaultCategory("key.categories.movement", category -> category.registerAllBindings(
|
||||
ButtonBinding.FORWARD,
|
||||
ButtonBinding.BACK,
|
||||
ButtonBinding.LEFT,
|
||||
ButtonBinding.RIGHT,
|
||||
ButtonBinding.JUMP,
|
||||
ButtonBinding.SNEAK,
|
||||
ButtonBinding.SPRINT));
|
||||
GAMEPLAY_CATEGORY = InputManager.registerDefaultCategory("key.categories.gameplay", category -> category.registerAllBindings(
|
||||
ButtonBinding.ATTACK,
|
||||
ButtonBinding.PICK_BLOCK,
|
||||
ButtonBinding.USE
|
||||
));
|
||||
INVENTORY_CATEGORY = InputManager.registerDefaultCategory("key.categories.inventory", category -> category.registerAllBindings(
|
||||
ButtonBinding.DROP_ITEM,
|
||||
ButtonBinding.HOTBAR_LEFT,
|
||||
ButtonBinding.HOTBAR_RIGHT,
|
||||
ButtonBinding.INVENTORY,
|
||||
ButtonBinding.SWAP_HANDS
|
||||
));
|
||||
MULTIPLAYER_CATEGORY = InputManager.registerDefaultCategory("key.categories.multiplayer",
|
||||
category -> category.registerAllBindings(ButtonBinding.CHAT, ButtonBinding.PLAYER_LIST));
|
||||
MISC_CATEGORY = InputManager.registerDefaultCategory("key.categories.misc", category -> category.registerAllBindings(
|
||||
ButtonBinding.SCREENSHOT,
|
||||
//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);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a quick {@link ButtonBinding} builder.
|
||||
*
|
||||
* @author LambdAurora
|
||||
* @version 1.5.0
|
||||
* @since 1.1.0
|
||||
*/
|
||||
public static class Builder {
|
||||
private final String key;
|
||||
private int[] buttons = new int[0];
|
||||
private final List<PressAction> actions = new ArrayList<>();
|
||||
private PairPredicate<MinecraftClient, ButtonBinding> filter = Predicates.pairAlwaysTrue();
|
||||
private boolean cooldown = false;
|
||||
private ButtonCategory category = null;
|
||||
private KeyBinding mcBinding = null;
|
||||
|
||||
/**
|
||||
* This constructor shouldn't be used for other mods.
|
||||
*
|
||||
* @param key the key with format {@code "<namespace>.<name>"}
|
||||
*/
|
||||
public Builder(@NotNull String key) {
|
||||
this.key = key;
|
||||
this.unbound();
|
||||
}
|
||||
|
||||
public Builder(@NotNull Identifier identifier) {
|
||||
this(identifier.getNamespace() + "." + identifier.getNamespace());
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines the default buttons of the {@link ButtonBinding}.
|
||||
*
|
||||
* @param buttons the default buttons
|
||||
* @return the builder instance
|
||||
*/
|
||||
public Builder buttons(int... buttons) {
|
||||
this.buttons = buttons;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link ButtonBinding} to unbound.
|
||||
*
|
||||
* @return the builder instance
|
||||
*/
|
||||
public Builder unbound() {
|
||||
return this.buttons(-1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the actions to the {@link ButtonBinding}.
|
||||
*
|
||||
* @param actions the actions to add
|
||||
* @return the builder instance
|
||||
*/
|
||||
public Builder actions(@NotNull PressAction... actions) {
|
||||
this.actions.addAll(Arrays.asList(actions));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an action to the {@link ButtonBinding}.
|
||||
*
|
||||
* @param action the action to add
|
||||
* @return the builder instance
|
||||
*/
|
||||
public Builder action(@NotNull PressAction action) {
|
||||
this.actions.add(action);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a filter for the {@link ButtonBinding}.
|
||||
*
|
||||
* @param filter the filter
|
||||
* @return the builder instance
|
||||
*/
|
||||
public Builder filter(@NotNull PairPredicate<MinecraftClient, ButtonBinding> filter) {
|
||||
this.filter = filter;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the filter of {@link ButtonBinding} to only in game.
|
||||
*
|
||||
* @return the builder instance
|
||||
* @see #filter(PairPredicate)
|
||||
* @see InputHandlers#inGame(MinecraftClient, ButtonBinding)
|
||||
*/
|
||||
public Builder onlyInGame() {
|
||||
return this.filter(InputHandlers::inGame);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the filter of {@link ButtonBinding} to only in inventory.
|
||||
*
|
||||
* @return the builder instance
|
||||
* @see #filter(PairPredicate)
|
||||
* @see InputHandlers#inInventory(MinecraftClient, ButtonBinding)
|
||||
*/
|
||||
public Builder onlyInInventory() {
|
||||
return this.filter(InputHandlers::inInventory);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the {@link ButtonBinding} has a cooldown or not.
|
||||
*
|
||||
* @param cooldown true if the {@link ButtonBinding} has a cooldown, else false
|
||||
* @return the builder instance
|
||||
*/
|
||||
public Builder cooldown(boolean cooldown) {
|
||||
this.cooldown = cooldown;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Puts a cooldown on the {@link ButtonBinding}.
|
||||
*
|
||||
* @return the builder instance
|
||||
* @since 1.5.0
|
||||
*/
|
||||
public Builder cooldown() {
|
||||
return this.cooldown(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the category of the {@link ButtonBinding}.
|
||||
*
|
||||
* @param category the category
|
||||
* @return the builder instance
|
||||
*/
|
||||
public Builder category(@Nullable ButtonCategory category) {
|
||||
this.category = category;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the keybinding linked to the {@link ButtonBinding}.
|
||||
*
|
||||
* @param binding the keybinding to link
|
||||
* @return the builder instance
|
||||
*/
|
||||
public Builder linkKeybind(@Nullable KeyBinding binding) {
|
||||
this.mcBinding = binding;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the {@link ButtonBinding}.
|
||||
*
|
||||
* @return the built {@link ButtonBinding}
|
||||
*/
|
||||
public ButtonBinding build() {
|
||||
var binding = new ButtonBinding(this.key, this.buttons, this.actions, this.filter, this.cooldown);
|
||||
if (this.category != null)
|
||||
this.category.registerBinding(binding);
|
||||
if (this.mcBinding != null)
|
||||
binding.setKeyBinding(this.mcBinding);
|
||||
return binding;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds and registers the {@link ButtonBinding}.
|
||||
*
|
||||
* @return the built {@link ButtonBinding}
|
||||
* @see #build()
|
||||
*/
|
||||
public ButtonBinding register() {
|
||||
return InputManager.registerBinding(this.build());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
|
||||
*
|
||||
* This file is part of midnightcontrols.
|
||||
*
|
||||
* Licensed under the MIT license. For more information,
|
||||
* see the LICENSE file.
|
||||
*/
|
||||
|
||||
package eu.midnightdust.midnightcontrols.client.controller;
|
||||
|
||||
import net.minecraft.client.resource.language.I18n;
|
||||
import org.aperlambda.lambdacommon.Identifier;
|
||||
import org.aperlambda.lambdacommon.utils.Identifiable;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Represents a button binding category
|
||||
*
|
||||
* @author LambdAurora
|
||||
* @version 1.1.0
|
||||
* @since 1.1.0
|
||||
*/
|
||||
public class ButtonCategory implements Identifiable {
|
||||
private final List<ButtonBinding> bindings = new ArrayList<>();
|
||||
private final Identifier id;
|
||||
private final int priority;
|
||||
|
||||
public ButtonCategory(@NotNull Identifier id, int priority) {
|
||||
this.id = id;
|
||||
this.priority = priority;
|
||||
}
|
||||
|
||||
public ButtonCategory(@NotNull Identifier id) {
|
||||
this(id, 100);
|
||||
}
|
||||
|
||||
public void registerBinding(@NotNull ButtonBinding binding) {
|
||||
if (this.bindings.contains(binding))
|
||||
throw new IllegalStateException("Cannot register twice a button binding in the same category.");
|
||||
this.bindings.add(binding);
|
||||
}
|
||||
|
||||
public void registerAllBindings(@NotNull ButtonBinding... bindings) {
|
||||
this.registerAllBindings(Arrays.asList(bindings));
|
||||
}
|
||||
|
||||
public void registerAllBindings(@NotNull List<ButtonBinding> bindings) {
|
||||
bindings.forEach(this::registerBinding);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the bindings assigned to this category.
|
||||
*
|
||||
* @return the bindings assigned to this category
|
||||
*/
|
||||
public @NotNull List<ButtonBinding> getBindings() {
|
||||
return Collections.unmodifiableList(this.bindings);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the translated name of this category.
|
||||
* <p>
|
||||
* The translation key should be `modid.identifier_name`.
|
||||
*
|
||||
* @return the translated name
|
||||
*/
|
||||
public @NotNull String getTranslatedName() {
|
||||
if (this.id.getNamespace().equals("minecraft"))
|
||||
return I18n.translate(this.id.getName());
|
||||
else
|
||||
return I18n.translate(this.id.getNamespace() + "." + this.id.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the priority display of this category.
|
||||
* It will defines in which order the categories will display on the controls screen.
|
||||
*
|
||||
* @return the priority of this category
|
||||
*/
|
||||
public int getPriority() {
|
||||
return this.priority;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Identifier getIdentifier() {
|
||||
return this.id;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
/*
|
||||
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
|
||||
*
|
||||
* This file is part of midnightcontrols.
|
||||
*
|
||||
* Licensed under the MIT license. For more information,
|
||||
* see the LICENSE file.
|
||||
*/
|
||||
|
||||
package eu.midnightdust.midnightcontrols.client.controller;
|
||||
|
||||
import eu.midnightdust.midnightcontrols.MidnightControls;
|
||||
import eu.midnightdust.midnightcontrols.client.MidnightControlsClient;
|
||||
import eu.midnightdust.midnightcontrols.client.MidnightControlsConfig;
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.client.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.glfw.GLFW;
|
||||
import org.lwjgl.glfw.GLFWGamepadState;
|
||||
import org.lwjgl.system.MemoryStack;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.lwjgl.BufferUtils.createByteBuffer;
|
||||
|
||||
/**
|
||||
* Represents a controller.
|
||||
*
|
||||
* @author LambdAurora
|
||||
* @version 1.7.0
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public record Controller(int id) implements Nameable {
|
||||
private static final Map<Integer, Controller> CONTROLLERS = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Gets the controller's globally unique identifier.
|
||||
*
|
||||
* @return the controller's GUID
|
||||
*/
|
||||
public String getGuid() {
|
||||
String guid = GLFW.glfwGetJoystickGUID(this.id);
|
||||
return guid == null ? "" : guid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this controller is connected or not.
|
||||
*
|
||||
* @return true if this controller is connected, else false
|
||||
*/
|
||||
public boolean isConnected() {
|
||||
return GLFW.glfwJoystickPresent(this.id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this controller is a gamepad or not.
|
||||
*
|
||||
* @return true if this controller is a gamepad, else false
|
||||
*/
|
||||
public boolean isGamepad() {
|
||||
return this.isConnected() && GLFW.glfwJoystickIsGamepad(this.id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the name of the controller.
|
||||
*
|
||||
* @return the controller's name
|
||||
*/
|
||||
@Override
|
||||
public String getName() {
|
||||
var name = this.isGamepad() ? GLFW.glfwGetGamepadName(this.id) : GLFW.glfwGetJoystickName(this.id);
|
||||
return name == null ? String.valueOf(this.id()) : name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the state of the controller.
|
||||
*
|
||||
* @return the state of the controller input
|
||||
*/
|
||||
public GLFWGamepadState getState() {
|
||||
var state = GLFWGamepadState.create();
|
||||
if (this.isGamepad())
|
||||
GLFW.glfwGetGamepadState(this.id, state);
|
||||
return state;
|
||||
}
|
||||
|
||||
public static Controller byId(int id) {
|
||||
if (id > GLFW.GLFW_JOYSTICK_LAST) {
|
||||
MidnightControlsClient.get().log("Controller '" + id + "' doesn't exist.");
|
||||
id = GLFW.GLFW_JOYSTICK_LAST;
|
||||
}
|
||||
Controller controller;
|
||||
if (CONTROLLERS.containsKey(id))
|
||||
return CONTROLLERS.get(id);
|
||||
else {
|
||||
controller = new Controller(id);
|
||||
CONTROLLERS.put(id, controller);
|
||||
return controller;
|
||||
}
|
||||
}
|
||||
|
||||
public static Optional<Controller> byGuid(@NotNull String guid) {
|
||||
return CONTROLLERS.values().stream().filter(Controller::isConnected)
|
||||
.filter(controller -> controller.getGuid().equals(guid))
|
||||
.max(Comparator.comparingInt(Controller::id));
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the specified resource and returns the raw data as a ByteBuffer.
|
||||
*
|
||||
* @param resource the resource to read
|
||||
* @param bufferSize the initial buffer size
|
||||
* @return the resource data
|
||||
* @throws IOException If an IO error occurs.
|
||||
*/
|
||||
private static ByteBuffer ioResourceToBuffer(String resource, int bufferSize) throws IOException {
|
||||
ByteBuffer buffer = null;
|
||||
|
||||
var path = Paths.get(resource);
|
||||
if (Files.isReadable(path)) {
|
||||
try (var fc = Files.newByteChannel(path)) {
|
||||
buffer = createByteBuffer((int) fc.size() + 2);
|
||||
while (fc.read(buffer) != -1) ;
|
||||
buffer.put((byte) 0);
|
||||
}
|
||||
}
|
||||
|
||||
buffer.flip(); // Force Java 8 >.<
|
||||
return buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the controller mappings.
|
||||
*/
|
||||
public static void updateMappings() {
|
||||
try {
|
||||
if (!MidnightControlsClient.MAPPINGS_FILE.exists())
|
||||
return;
|
||||
MidnightControlsClient.get().log("Updating controller mappings...");
|
||||
var buffer = ioResourceToBuffer(MidnightControlsClient.MAPPINGS_FILE.getPath(), 1024);
|
||||
GLFW.glfwUpdateGamepadMappings(buffer);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
try (var memoryStack = MemoryStack.stackPush()) {
|
||||
var pointerBuffer = memoryStack.mallocPointer(1);
|
||||
int i = GLFW.glfwGetError(pointerBuffer);
|
||||
if (i != 0) {
|
||||
long l = pointerBuffer.get();
|
||||
var string = l == 0L ? "" : MemoryUtil.memUTF8(l);
|
||||
var client = MinecraftClient.getInstance();
|
||||
if (client != null) {
|
||||
client.getToastManager().add(SystemToast.create(client, SystemToast.Type.TUTORIAL_HINT,
|
||||
new TranslatableText("midnightcontrols.controller.mappings.error"), new LiteralText(string)));
|
||||
}
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
/* Ignored :concern: */
|
||||
}
|
||||
|
||||
if (MidnightControlsConfig.debug) {
|
||||
for (int i = GLFW.GLFW_JOYSTICK_1; i <= GLFW.GLFW_JOYSTICK_16; i++) {
|
||||
var controller = byId(i);
|
||||
|
||||
if (!controller.isConnected())
|
||||
continue;
|
||||
|
||||
MidnightControls.get().log(String.format("Controller #%d name: \"%s\"\n GUID: %s\n Gamepad: %s",
|
||||
controller.id,
|
||||
controller.getName(),
|
||||
controller.getGuid(),
|
||||
controller.isGamepad()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,267 @@
|
||||
/*
|
||||
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
|
||||
*
|
||||
* This file is part of midnightcontrols.
|
||||
*
|
||||
* Licensed under the MIT license. For more information,
|
||||
* see the LICENSE file.
|
||||
*/
|
||||
|
||||
package eu.midnightdust.midnightcontrols.client.controller;
|
||||
|
||||
import eu.midnightdust.midnightcontrols.client.ButtonState;
|
||||
import eu.midnightdust.midnightcontrols.client.MidnightInput;
|
||||
import eu.midnightdust.midnightcontrols.client.mixin.AdvancementsScreenAccessor;
|
||||
import eu.midnightdust.midnightcontrols.client.mixin.CreativeInventoryScreenAccessor;
|
||||
import eu.midnightdust.midnightcontrols.client.mixin.RecipeBookWidgetAccessor;
|
||||
import eu.midnightdust.midnightcontrols.client.util.HandledScreenAccessor;
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.client.gl.Framebuffer;
|
||||
import net.minecraft.client.gui.screen.advancement.AdvancementsScreen;
|
||||
import net.minecraft.client.gui.screen.ingame.HandledScreen;
|
||||
import net.minecraft.client.gui.screen.ingame.InventoryScreen;
|
||||
import net.minecraft.client.util.ScreenshotRecorder;
|
||||
import net.minecraft.item.ItemGroup;
|
||||
import net.minecraft.screen.slot.Slot;
|
||||
import org.aperlambda.lambdacommon.utils.Pair;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Represents some input handlers.
|
||||
*
|
||||
* @author LambdAurora
|
||||
* @version 1.7.0
|
||||
* @since 1.1.0
|
||||
*/
|
||||
public class InputHandlers {
|
||||
private InputHandlers() {
|
||||
}
|
||||
|
||||
public static PressAction handleHotbar(boolean next) {
|
||||
return (client, button, value, action) -> {
|
||||
if (action == ButtonState.RELEASE)
|
||||
return false;
|
||||
|
||||
// When ingame
|
||||
if (client.currentScreen == null && client.player != null) {
|
||||
if (next)
|
||||
client.player.getInventory().selectedSlot = client.player.getInventory().selectedSlot == 8 ? 0 : client.player.getInventory().selectedSlot + 1;
|
||||
else
|
||||
client.player.getInventory().selectedSlot = client.player.getInventory().selectedSlot == 0 ? 8 : client.player.getInventory().selectedSlot - 1;
|
||||
return true;
|
||||
} else if (client.currentScreen instanceof CreativeInventoryScreenAccessor inventory) {
|
||||
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.midnightcontrols$setSelectedTab(ItemGroup.GROUPS[nextTab]);
|
||||
return true;
|
||||
} else if (client.currentScreen instanceof InventoryScreen inventoryScreen) {
|
||||
var recipeBook = (RecipeBookWidgetAccessor) inventoryScreen.getRecipeBookWidget();
|
||||
var tabs = recipeBook.getTabButtons();
|
||||
var 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.midnightcontrols$refreshResults(true);
|
||||
return true;
|
||||
} else if (client.currentScreen instanceof AdvancementsScreenAccessor screen) {
|
||||
var tabs = screen.getTabs().values().stream().distinct().collect(Collectors.toList());
|
||||
var tab = screen.getSelectedTab();
|
||||
if (tab == null)
|
||||
return false;
|
||||
for (int i = 0; i < tabs.size(); i++) {
|
||||
if (tabs.get(i).equals(tab)) {
|
||||
int nextTab = i + (next ? 1 : -1);
|
||||
if (nextTab < 0)
|
||||
nextTab = tabs.size() - 1;
|
||||
else if (nextTab >= tabs.size())
|
||||
nextTab = 0;
|
||||
screen.getAdvancementManager().selectTab(tabs.get(nextTab).getRoot(), true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
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 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();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the screenshot action.
|
||||
*
|
||||
* @param client the client instance
|
||||
* @param binding the binding which fired the action
|
||||
* @param action the action done on the binding
|
||||
* @return true if handled, else false
|
||||
*/
|
||||
public static boolean handleScreenshot(@NotNull MinecraftClient client, @NotNull ButtonBinding binding, float value, @NotNull ButtonState action) {
|
||||
if (action == ButtonState.RELEASE)
|
||||
ScreenshotRecorder.saveScreenshot(client.runDirectory, client.getFramebuffer(),
|
||||
text -> client.execute(() -> client.inGameHud.getChatHud().addMessage(text)));
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean handleToggleSneak(@NotNull MinecraftClient client, @NotNull ButtonBinding button, float value, @NotNull ButtonState action) {
|
||||
button.asKeyBinding().ifPresent(binding -> {
|
||||
boolean sneakToggled = client.options.sneakToggled;
|
||||
if (client.player.getAbilities().flying && sneakToggled)
|
||||
client.options.sneakToggled = false;
|
||||
binding.setPressed(button.pressed);
|
||||
if (client.player.getAbilities().flying && sneakToggled)
|
||||
client.options.sneakToggled = true;
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
public static PressAction handleInventorySlotPad(int direction) {
|
||||
return (client, binding, value, action) -> {
|
||||
if (!(client.currentScreen instanceof HandledScreen inventory && action != ButtonState.RELEASE))
|
||||
return false;
|
||||
|
||||
var accessor = (HandledScreenAccessor) inventory;
|
||||
int guiLeft = accessor.getX();
|
||||
int guiTop = accessor.getY();
|
||||
double mouseX = client.mouse.getX() * (double) client.getWindow().getScaledWidth() / (double) client.getWindow().getWidth();
|
||||
double mouseY = client.mouse.getY() * (double) client.getWindow().getScaledHeight() / (double) client.getWindow().getHeight();
|
||||
|
||||
// Finds the hovered slot.
|
||||
var mouseSlot = accessor.midnightcontrols$getSlotAt(mouseX, mouseY);
|
||||
|
||||
// Finds the closest slot in the GUI within 14 pixels.
|
||||
Optional<Slot> closestSlot = inventory.getScreenHandler().slots.parallelStream()
|
||||
.filter(Predicate.isEqual(mouseSlot).negate())
|
||||
.map(slot -> {
|
||||
int posX = guiLeft + slot.x + 8;
|
||||
int posY = guiTop + slot.y + 8;
|
||||
|
||||
int otherPosX = (int) mouseX;
|
||||
int otherPosY = (int) mouseY;
|
||||
if (mouseSlot != null) {
|
||||
otherPosX = guiLeft + mouseSlot.x + 8;
|
||||
otherPosY = guiTop + mouseSlot.y + 8;
|
||||
}
|
||||
|
||||
// Distance between the slot and the cursor.
|
||||
double distance = Math.sqrt(Math.pow(posX - otherPosX, 2) + Math.pow(posY - otherPosY, 2));
|
||||
return Pair.of(slot, distance);
|
||||
}).filter(entry -> {
|
||||
var slot = entry.key;
|
||||
int posX = guiLeft + slot.x + 8;
|
||||
int posY = guiTop + slot.y + 8;
|
||||
int otherPosX = (int) mouseX;
|
||||
int otherPosY = (int) mouseY;
|
||||
if (mouseSlot != null) {
|
||||
otherPosX = guiLeft + mouseSlot.x + 8;
|
||||
otherPosY = guiTop + mouseSlot.y + 8;
|
||||
}
|
||||
if (direction == 0)
|
||||
return posY < otherPosY;
|
||||
else if (direction == 1)
|
||||
return posY > otherPosY;
|
||||
else if (direction == 2)
|
||||
return posX > otherPosX;
|
||||
else if (direction == 3)
|
||||
return posX < otherPosX;
|
||||
else
|
||||
return false;
|
||||
})
|
||||
.min(Comparator.comparingDouble(p -> p.value))
|
||||
.map(p -> p.key);
|
||||
|
||||
if (closestSlot.isPresent()) {
|
||||
var slot = closestSlot.get();
|
||||
int x = guiLeft + slot.x + 8;
|
||||
int y = guiTop + slot.y + 8;
|
||||
InputManager.queueMousePosition(x * (double) client.getWindow().getWidth() / (double) client.getWindow().getScaledWidth(),
|
||||
y * (double) client.getWindow().getHeight() / (double) client.getWindow().getScaledHeight());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns always true to the filter.
|
||||
*
|
||||
* @param client the client instance
|
||||
* @param binding the affected binding
|
||||
* @return true
|
||||
*/
|
||||
public static boolean always(@NotNull MinecraftClient client, @NotNull ButtonBinding binding) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the client is in game or not.
|
||||
*
|
||||
* @param client the client instance
|
||||
* @param binding the affected binding
|
||||
* @return true if the client is in game, else false
|
||||
*/
|
||||
public static boolean inGame(@NotNull MinecraftClient client, @NotNull ButtonBinding binding) {
|
||||
return client.currentScreen == null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the client is in a non-interactive screen (which means require mouse input) or not.
|
||||
*
|
||||
* @param client the client instance
|
||||
* @param binding the affected binding
|
||||
* @return true if the client is in a non-interactive screen, else false
|
||||
*/
|
||||
public static boolean inNonInteractiveScreens(@NotNull MinecraftClient client, @NotNull ButtonBinding binding) {
|
||||
if (client.currentScreen == null)
|
||||
return false;
|
||||
return !MidnightInput.isScreenInteractive(client.currentScreen);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the client is in an inventory or not.
|
||||
*
|
||||
* @param client the client instance
|
||||
* @param binding the affected binding
|
||||
* @return true if the client is in an inventory, else false
|
||||
*/
|
||||
public static boolean inInventory(@NotNull MinecraftClient client, @NotNull ButtonBinding binding) {
|
||||
return client.currentScreen instanceof HandledScreen;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the client is in the advancements screen or not.
|
||||
*
|
||||
* @param client the client instance
|
||||
* @param binding the affected binding
|
||||
* @return true if the client is in the advancements screen, else false
|
||||
*/
|
||||
public static boolean inAdvancements(@NotNull MinecraftClient client, @NotNull ButtonBinding binding) {
|
||||
return client.currentScreen instanceof AdvancementsScreen;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,385 @@
|
||||
/*
|
||||
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
|
||||
*
|
||||
* This file is part of midnightcontrols.
|
||||
*
|
||||
* Licensed under the MIT license. For more information,
|
||||
* see the LICENSE file.
|
||||
*/
|
||||
|
||||
package eu.midnightdust.midnightcontrols.client.controller;
|
||||
|
||||
import eu.midnightdust.midnightcontrols.ControlsMode;
|
||||
import eu.midnightdust.midnightcontrols.client.ButtonState;
|
||||
import eu.midnightdust.midnightcontrols.client.MidnightControlsConfig;
|
||||
import eu.midnightdust.midnightcontrols.client.util.MouseAccessor;
|
||||
import it.unimi.dsi.fastutil.ints.*;
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.client.option.KeyBinding;
|
||||
import net.minecraft.client.util.InputUtil;
|
||||
import net.minecraft.util.math.MathHelper;
|
||||
import org.aperlambda.lambdacommon.Identifier;
|
||||
import org.aperlambda.lambdacommon.utils.function.PairPredicate;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.lwjgl.glfw.GLFW;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Represents an input manager for controllers.
|
||||
*
|
||||
* @author LambdAurora
|
||||
* @version 1.7.0
|
||||
* @since 1.1.0
|
||||
*/
|
||||
public class InputManager {
|
||||
public static final InputManager INPUT_MANAGER = new InputManager();
|
||||
private static final List<ButtonBinding> BINDINGS = new ArrayList<>();
|
||||
private static final List<ButtonCategory> CATEGORIES = new ArrayList<>();
|
||||
public static final Int2ObjectMap<ButtonState> STATES = new Int2ObjectOpenHashMap<>();
|
||||
public static final Int2FloatMap BUTTON_VALUES = new Int2FloatOpenHashMap();
|
||||
private int prevTargetMouseX = 0;
|
||||
private int prevTargetMouseY = 0;
|
||||
private int targetMouseX = 0;
|
||||
private int targetMouseY = 0;
|
||||
|
||||
protected InputManager() {
|
||||
}
|
||||
|
||||
public void tick(@NotNull MinecraftClient client) {
|
||||
if (MidnightControlsConfig.controlsMode == ControlsMode.CONTROLLER) {
|
||||
this.controllerTick(client);
|
||||
}
|
||||
}
|
||||
|
||||
public void controllerTick(@NotNull MinecraftClient client) {
|
||||
this.prevTargetMouseX = this.targetMouseX;
|
||||
this.prevTargetMouseY = this.targetMouseY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the mouse position. Should only be called on pre render of a screen.
|
||||
*
|
||||
* @param client the client instance
|
||||
*/
|
||||
public void updateMousePosition(@NotNull MinecraftClient client) {
|
||||
Objects.requireNonNull(client, "Client instance cannot be null.");
|
||||
if (this.prevTargetMouseX != this.targetMouseX || this.prevTargetMouseY != this.targetMouseY) {
|
||||
double mouseX = this.prevTargetMouseX + (this.targetMouseX - this.prevTargetMouseX) * client.getTickDelta() + 0.5;
|
||||
double mouseY = this.prevTargetMouseY + (this.targetMouseY - this.prevTargetMouseY) * client.getTickDelta() + 0.5;
|
||||
if (!MidnightControlsConfig.virtualMouse)
|
||||
GLFW.glfwSetCursorPos(client.getWindow().getHandle(), mouseX, mouseY);
|
||||
((MouseAccessor) client.mouse).midnightcontrols$onCursorPos(client.getWindow().getHandle(), mouseX, mouseY);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the mouse position.
|
||||
*
|
||||
* @param windowWidth the window width
|
||||
* @param windowHeight the window height
|
||||
*/
|
||||
public void resetMousePosition(int windowWidth, int windowHeight) {
|
||||
this.targetMouseX = this.prevTargetMouseX = (int) (windowWidth / 2.F);
|
||||
this.targetMouseY = this.prevTargetMouseY = (int) (windowHeight / 2.F);
|
||||
}
|
||||
|
||||
public void resetMouseTarget(@NotNull MinecraftClient client) {
|
||||
double mouseX = client.mouse.getX();
|
||||
double mouseY = client.mouse.getY();
|
||||
this.prevTargetMouseX = this.targetMouseX = (int) mouseX;
|
||||
this.prevTargetMouseY = this.targetMouseY = (int) mouseY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the specified binding is registered or not.
|
||||
*
|
||||
* @param binding the binding to check
|
||||
* @return true if the binding is registered, else false
|
||||
*/
|
||||
public static boolean hasBinding(@NotNull ButtonBinding binding) {
|
||||
return BINDINGS.contains(binding);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the specified binding is registered or not.
|
||||
*
|
||||
* @param name the name of the binding to check
|
||||
* @return true if the binding is registered, else false
|
||||
*/
|
||||
public static boolean hasBinding(@NotNull String name) {
|
||||
return BINDINGS.parallelStream().map(ButtonBinding::getName).anyMatch(binding -> binding.equalsIgnoreCase(name));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the specified binding is registered or not.
|
||||
*
|
||||
* @param identifier the identifier of the binding to check
|
||||
* @return true if the binding is registered, else false
|
||||
*/
|
||||
public static boolean hasBinding(@NotNull Identifier identifier) {
|
||||
return hasBinding(identifier.getNamespace() + "." + identifier.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a button binding.
|
||||
*
|
||||
* @param binding the binding to register
|
||||
* @return the registered binding
|
||||
*/
|
||||
public static @NotNull ButtonBinding registerBinding(@NotNull ButtonBinding binding) {
|
||||
if (hasBinding(binding))
|
||||
throw new IllegalStateException("Cannot register twice a button binding in the registry.");
|
||||
BINDINGS.add(binding);
|
||||
return binding;
|
||||
}
|
||||
|
||||
public static @NotNull ButtonBinding registerBinding(@NotNull Identifier id, int[] defaultButton, @NotNull List<PressAction> actions, @NotNull PairPredicate<MinecraftClient, ButtonBinding> filter, boolean hasCooldown) {
|
||||
return registerBinding(new ButtonBinding(id.getNamespace() + "." + id.getName(), defaultButton, actions, filter, hasCooldown));
|
||||
}
|
||||
|
||||
public static @NotNull ButtonBinding registerBinding(@NotNull Identifier id, int[] defaultButton, boolean hasCooldown) {
|
||||
return registerBinding(id, defaultButton, Collections.emptyList(), InputHandlers::always, hasCooldown);
|
||||
}
|
||||
|
||||
public static @NotNull ButtonBinding registerBinding(@NotNull net.minecraft.util.Identifier id, int[] defaultButton, @NotNull List<PressAction> actions, @NotNull PairPredicate<MinecraftClient, ButtonBinding> filter, boolean hasCooldown) {
|
||||
return registerBinding(new Identifier(id.getNamespace(), id.getPath()), defaultButton, actions, filter, hasCooldown);
|
||||
}
|
||||
|
||||
public static @NotNull ButtonBinding registerBinding(@NotNull net.minecraft.util.Identifier id, int[] defaultButton, boolean hasCooldown) {
|
||||
return registerBinding(id, defaultButton, Collections.emptyList(), InputHandlers::always, hasCooldown);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts bindings to get bindings with the higher button counts first.
|
||||
*/
|
||||
public static void sortBindings() {
|
||||
synchronized (BINDINGS) {
|
||||
var sorted = BINDINGS.stream()
|
||||
.sorted(Collections.reverseOrder(Comparator.comparingInt(binding -> binding.getButton().length))).toList();
|
||||
BINDINGS.clear();
|
||||
BINDINGS.addAll(sorted);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a category of button bindings.
|
||||
*
|
||||
* @param category the category to register
|
||||
* @return the registered category
|
||||
*/
|
||||
public static ButtonCategory registerCategory(@NotNull ButtonCategory category) {
|
||||
CATEGORIES.add(category);
|
||||
return category;
|
||||
}
|
||||
|
||||
public static ButtonCategory registerCategory(@NotNull Identifier identifier, int priority) {
|
||||
return registerCategory(new ButtonCategory(identifier, priority));
|
||||
}
|
||||
|
||||
public static ButtonCategory registerCategory(@NotNull Identifier identifier) {
|
||||
return registerCategory(new ButtonCategory(identifier));
|
||||
}
|
||||
|
||||
protected static ButtonCategory registerDefaultCategory(@NotNull String key, @NotNull Consumer<ButtonCategory> keyAdder) {
|
||||
var category = registerCategory(new Identifier("minecraft", key), CATEGORIES.size());
|
||||
keyAdder.accept(category);
|
||||
return category;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the button bindings from configuration.
|
||||
*/
|
||||
public static void loadButtonBindings() {
|
||||
var queue = new ArrayList<>(BINDINGS);
|
||||
queue.forEach(MidnightControlsConfig::loadButtonBinding);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the binding state.
|
||||
*
|
||||
* @param binding the binding
|
||||
* @return the current state of the binding
|
||||
*/
|
||||
public static @NotNull ButtonState getBindingState(@NotNull ButtonBinding binding) {
|
||||
var state = ButtonState.REPEAT;
|
||||
for (int btn : binding.getButton()) {
|
||||
var btnState = InputManager.STATES.getOrDefault(btn, ButtonState.NONE);
|
||||
if (btnState == ButtonState.PRESS)
|
||||
state = ButtonState.PRESS;
|
||||
else if (btnState == ButtonState.RELEASE) {
|
||||
state = ButtonState.RELEASE;
|
||||
break;
|
||||
} else if (btnState == ButtonState.NONE) {
|
||||
state = ButtonState.NONE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
public static float getBindingValue(@NotNull ButtonBinding binding, @NotNull ButtonState state) {
|
||||
if (state.isUnpressed())
|
||||
return 0.f;
|
||||
|
||||
float value = 0.f;
|
||||
for (int btn : binding.getButton()) {
|
||||
if (ButtonBinding.isAxis(btn)) {
|
||||
value = BUTTON_VALUES.getOrDefault(btn, 1.f);
|
||||
break;
|
||||
} else {
|
||||
value = 1.f;
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the button has duplicated bindings.
|
||||
*
|
||||
* @param button the button to check
|
||||
* @return true if the button has duplicated bindings, else false
|
||||
*/
|
||||
public static boolean hasDuplicatedBindings(int[] button) {
|
||||
return BINDINGS.parallelStream().filter(binding -> areButtonsEquivalent(binding.getButton(), button)).count() > 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the button has duplicated bindings.
|
||||
*
|
||||
* @param binding the binding to check
|
||||
* @return true if the button has duplicated bindings, else false
|
||||
*/
|
||||
public static boolean hasDuplicatedBindings(ButtonBinding binding) {
|
||||
return BINDINGS.parallelStream().filter(other -> areButtonsEquivalent(other.getButton(), binding.getButton()) && other.filter.equals(binding.filter)).count() > 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the specified buttons are equivalent or not.
|
||||
*
|
||||
* @param buttons1 first set of buttons
|
||||
* @param buttons2 second set of buttons
|
||||
* @return true if the two sets of buttons are equivalent, else false
|
||||
*/
|
||||
public static boolean areButtonsEquivalent(int[] buttons1, int[] buttons2) {
|
||||
if (buttons1.length != buttons2.length)
|
||||
return false;
|
||||
int count = 0;
|
||||
for (int btn : buttons1) {
|
||||
for (int btn2 : buttons2) {
|
||||
if (btn == btn2) {
|
||||
count++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return count == buttons1.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the button set contains the specified button or not.
|
||||
*
|
||||
* @param buttons the button set
|
||||
* @param button the button to check
|
||||
* @return true if the button set contains the specified button, else false
|
||||
*/
|
||||
public static boolean containsButton(int[] buttons, int button) {
|
||||
return Arrays.stream(buttons).anyMatch(btn -> btn == button);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the button states.
|
||||
*/
|
||||
public static void updateStates() {
|
||||
for (var entry : STATES.int2ObjectEntrySet()) {
|
||||
if (entry.getValue() == ButtonState.PRESS)
|
||||
STATES.put(entry.getIntKey(), ButtonState.REPEAT);
|
||||
else if (entry.getValue() == ButtonState.RELEASE)
|
||||
STATES.put(entry.getIntKey(), ButtonState.NONE);
|
||||
}
|
||||
}
|
||||
|
||||
public static void updateBindings(@NotNull MinecraftClient client) {
|
||||
var skipButtons = new IntArrayList();
|
||||
record ButtonStateValue(ButtonState state, float value) {
|
||||
}
|
||||
var states = new Object2ObjectOpenHashMap<ButtonBinding, ButtonStateValue>();
|
||||
for (var binding : BINDINGS) {
|
||||
var state = binding.isAvailable(client) ? getBindingState(binding) : ButtonState.NONE;
|
||||
if (skipButtons.intStream().anyMatch(btn -> containsButton(binding.getButton(), btn))) {
|
||||
if (binding.pressed)
|
||||
state = ButtonState.RELEASE;
|
||||
else
|
||||
state = ButtonState.NONE;
|
||||
}
|
||||
|
||||
if (state == ButtonState.RELEASE && !binding.pressed) {
|
||||
state = ButtonState.NONE;
|
||||
}
|
||||
|
||||
binding.pressed = state.isPressed();
|
||||
binding.update();
|
||||
if (binding.pressed)
|
||||
Arrays.stream(binding.getButton()).forEach(skipButtons::add);
|
||||
|
||||
float value = getBindingValue(binding, state);
|
||||
|
||||
states.put(binding, new ButtonStateValue(state, value));
|
||||
}
|
||||
|
||||
states.forEach((binding, state) -> {
|
||||
if (state.state() != ButtonState.NONE) {
|
||||
binding.handle(client, state.value(), state.state());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static void queueMousePosition(double x, double y) {
|
||||
INPUT_MANAGER.targetMouseX = (int) MathHelper.clamp(x, 0, MinecraftClient.getInstance().getWindow().getWidth());
|
||||
INPUT_MANAGER.targetMouseY = (int) MathHelper.clamp(y, 0, MinecraftClient.getInstance().getWindow().getHeight());
|
||||
}
|
||||
|
||||
public static void queueMoveMousePosition(double x, double y) {
|
||||
queueMousePosition(INPUT_MANAGER.targetMouseX + x, INPUT_MANAGER.targetMouseY + y);
|
||||
}
|
||||
|
||||
public static @NotNull Stream<ButtonBinding> streamBindings() {
|
||||
return BINDINGS.stream();
|
||||
}
|
||||
|
||||
public static @NotNull Stream<ButtonCategory> streamCategories() {
|
||||
return CATEGORIES.stream();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new key binding instance.
|
||||
*
|
||||
* @param id the identifier of the key binding
|
||||
* @param type the type
|
||||
* @param code the code
|
||||
* @param category the category of the key binding
|
||||
* @return the key binding
|
||||
* @see #makeKeyBinding(Identifier, InputUtil.Type, int, String)
|
||||
*/
|
||||
public static @NotNull KeyBinding makeKeyBinding(@NotNull net.minecraft.util.Identifier id, InputUtil.Type type, int code, @NotNull String category) {
|
||||
return makeKeyBinding(new Identifier(id.getNamespace(), id.getPath()), type, code, category);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new key binding instance.
|
||||
*
|
||||
* @param id the identifier of the key binding
|
||||
* @param type the type
|
||||
* @param code the code
|
||||
* @param category the category of the key binding
|
||||
* @return the key binding
|
||||
* @see #makeKeyBinding(net.minecraft.util.Identifier, InputUtil.Type, int, String)
|
||||
*/
|
||||
public static @NotNull KeyBinding makeKeyBinding(@NotNull Identifier id, InputUtil.Type type, int code, @NotNull String category) {
|
||||
return new KeyBinding(String.format("key.%s.%s", id.getNamespace(), id.getName()), type, code, category);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
|
||||
*
|
||||
* This file is part of midnightcontrols.
|
||||
*
|
||||
* Licensed under the MIT license. For more information,
|
||||
* see the LICENSE file.
|
||||
*/
|
||||
|
||||
package eu.midnightdust.midnightcontrols.client.controller;
|
||||
|
||||
import eu.midnightdust.midnightcontrols.client.ButtonState;
|
||||
import eu.midnightdust.midnightcontrols.client.MidnightControlsClient;
|
||||
import eu.midnightdust.midnightcontrols.client.MidnightControlsConfig;
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.client.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 (MidnightControlsConfig.analogMovement) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
|
||||
*
|
||||
* This file is part of midnightcontrols.
|
||||
*
|
||||
* Licensed under the MIT license. For more information,
|
||||
* see the LICENSE file.
|
||||
*/
|
||||
|
||||
package eu.midnightdust.midnightcontrols.client.controller;
|
||||
|
||||
import eu.midnightdust.midnightcontrols.client.ButtonState;
|
||||
import eu.midnightdust.midnightcontrols.client.util.KeyBindingAccessor;
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.client.option.StickyKeyBinding;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Represents a press action callback.
|
||||
*
|
||||
* @author LambdAurora
|
||||
* @version 1.7.0
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface PressAction {
|
||||
PressAction DEFAULT_ACTION = (client, button, value, action) -> {
|
||||
if (action == ButtonState.REPEAT || client.currentScreen != null)
|
||||
return false;
|
||||
button.asKeyBinding().ifPresent(binding -> {
|
||||
if (binding instanceof StickyKeyBinding)
|
||||
binding.setPressed(button.pressed);
|
||||
else
|
||||
((KeyBindingAccessor) binding).midnightcontrols$handlePressState(button.isButtonDown());
|
||||
});
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles when there is a press action.
|
||||
*
|
||||
* @param client the client instance
|
||||
* @param action the action done
|
||||
*/
|
||||
boolean press(@NotNull MinecraftClient client, @NotNull ButtonBinding button, float value, @NotNull ButtonState action);
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
|
||||
*
|
||||
* This file is part of midnightcontrols.
|
||||
*
|
||||
* Licensed under the MIT license. For more information,
|
||||
* see the LICENSE file.
|
||||
*/
|
||||
|
||||
package eu.midnightdust.midnightcontrols.client.gui;
|
||||
|
||||
import eu.midnightdust.midnightcontrols.client.MidnightControlsClient;
|
||||
import eu.midnightdust.midnightcontrols.client.controller.Controller;
|
||||
import dev.lambdaurora.spruceui.Position;
|
||||
import dev.lambdaurora.spruceui.option.SpruceOption;
|
||||
import dev.lambdaurora.spruceui.widget.container.SpruceContainerWidget;
|
||||
import dev.lambdaurora.spruceui.widget.text.SpruceTextAreaWidget;
|
||||
import net.minecraft.client.toast.SystemToast;
|
||||
import net.minecraft.text.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.7.0
|
||||
* @since 1.4.3
|
||||
*/
|
||||
public class MappingsStringInputWidget extends SpruceContainerWidget {
|
||||
private final SpruceOption reloadMappingsOption;
|
||||
private String mappings;
|
||||
private SpruceTextAreaWidget textArea;
|
||||
|
||||
protected MappingsStringInputWidget(Position position, int width, int height) {
|
||||
super(position, width, height);
|
||||
//super(new TranslatableText("midnightcontrols.menu.title.mappings.string"));
|
||||
|
||||
this.reloadMappingsOption = ReloadControllerMappingsOption.newOption(btn -> {
|
||||
this.writeMappings();
|
||||
});
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
public void removed() {
|
||||
this.writeMappings();
|
||||
Controller.updateMappings();
|
||||
}
|
||||
|
||||
public void onClose() {
|
||||
this.removed();
|
||||
}
|
||||
|
||||
public void writeMappings() {
|
||||
if (this.textArea != null) {
|
||||
this.mappings = this.textArea.getText();
|
||||
try {
|
||||
var fw = new FileWriter(MidnightControlsClient.MAPPINGS_FILE, false);
|
||||
fw.write(this.mappings);
|
||||
fw.close();
|
||||
} catch (IOException e) {
|
||||
if (this.client != null)
|
||||
this.client.getToastManager().add(SystemToast.create(this.client, SystemToast.Type.TUTORIAL_HINT,
|
||||
new TranslatableText("midnightcontrols.controller.mappings.error.write"), LiteralText.EMPTY));
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void init() {
|
||||
if (this.textArea != null) {
|
||||
this.mappings = this.textArea.getText();
|
||||
}
|
||||
|
||||
var mappings = "";
|
||||
|
||||
if (this.mappings != null)
|
||||
mappings = this.mappings;
|
||||
else if (MidnightControlsClient.MAPPINGS_FILE.exists()) {
|
||||
try {
|
||||
mappings = String.join("\n", Files.readAllLines(MidnightControlsClient.MAPPINGS_FILE.toPath()));
|
||||
this.mappings = mappings;
|
||||
} catch (IOException e) {
|
||||
/* Ignored */
|
||||
}
|
||||
}
|
||||
|
||||
int textFieldWidth = (int) (this.width * (5.0 / 6.0));
|
||||
this.textArea = new SpruceTextAreaWidget(Position.of(this, this.width / 2 - textFieldWidth / 2, 0), textFieldWidth, this.height - 50, 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);
|
||||
}*/
|
||||
}
|
||||
@@ -0,0 +1,278 @@
|
||||
/*
|
||||
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
|
||||
*
|
||||
* This file is part of midnightcontrols.
|
||||
*
|
||||
* Licensed under the MIT license. For more information,
|
||||
* see the LICENSE file.
|
||||
*/
|
||||
|
||||
package eu.midnightdust.midnightcontrols.client.gui;
|
||||
|
||||
import eu.midnightdust.midnightcontrols.ControlsMode;
|
||||
import eu.midnightdust.midnightcontrols.MidnightControlsConstants;
|
||||
import eu.midnightdust.midnightcontrols.client.HudSide;
|
||||
import eu.midnightdust.midnightcontrols.client.MidnightControlsClient;
|
||||
import eu.midnightdust.midnightcontrols.client.MidnightControlsConfig;
|
||||
import eu.midnightdust.midnightcontrols.client.MidnightInput;
|
||||
import eu.midnightdust.midnightcontrols.client.compat.MidnightControlsCompat;
|
||||
import eu.midnightdust.midnightcontrols.client.controller.ButtonBinding;
|
||||
import dev.lambdaurora.spruceui.hud.Hud;
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.client.resource.language.I18n;
|
||||
import net.minecraft.client.util.math.MatrixStack;
|
||||
import net.minecraft.item.BlockItem;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.util.Identifier;
|
||||
import net.minecraft.util.hit.BlockHitResult;
|
||||
import net.minecraft.util.hit.HitResult;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Represents the midnightcontrols HUD.
|
||||
*
|
||||
* @author LambdAurora
|
||||
* @version 1.7.0
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public class MidnightControlsHud extends Hud {
|
||||
private final MidnightControlsClient mod;
|
||||
private MinecraftClient client;
|
||||
private int attackWidth = 0;
|
||||
private int attackButtonWidth = 0;
|
||||
private int dropItemWidth = 0;
|
||||
private int dropItemButtonWidth = 0;
|
||||
private int inventoryWidth = 0;
|
||||
private int inventoryButtonWidth = 0;
|
||||
private int swapHandsWidth = 0;
|
||||
private int swapHandsButtonWidth = 0;
|
||||
private int useWidth = 0;
|
||||
private int useButtonWidth = 0;
|
||||
private BlockHitResult placeHitResult;
|
||||
private String attackAction = "";
|
||||
private String placeAction = "";
|
||||
private int ticksDisplayedCrosshair = 0;
|
||||
|
||||
public MidnightControlsHud(@NotNull MidnightControlsClient mod) {
|
||||
super(new Identifier(MidnightControlsConstants.NAMESPACE, "hud/button_indicator"));
|
||||
this.mod = mod;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(@NotNull MinecraftClient client, int screenWidth, int screenHeight) {
|
||||
super.init(client, screenWidth, screenHeight);
|
||||
this.client = client;
|
||||
this.inventoryWidth = this.width(ButtonBinding.INVENTORY);
|
||||
this.inventoryButtonWidth = MidnightControlsRenderer.getBindingIconWidth(ButtonBinding.INVENTORY);
|
||||
this.swapHandsWidth = this.width(ButtonBinding.SWAP_HANDS);
|
||||
this.swapHandsButtonWidth = MidnightControlsRenderer.getBindingIconWidth(ButtonBinding.SWAP_HANDS);
|
||||
this.dropItemWidth = this.width(ButtonBinding.DROP_ITEM);
|
||||
this.dropItemButtonWidth = MidnightControlsRenderer.getBindingIconWidth(ButtonBinding.DROP_ITEM);
|
||||
this.attackButtonWidth = MidnightControlsRenderer.getBindingIconWidth(ButtonBinding.ATTACK);
|
||||
this.useButtonWidth = MidnightControlsRenderer.getBindingIconWidth(ButtonBinding.USE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the midnightcontrols' HUD.
|
||||
*/
|
||||
@Override
|
||||
public void render(MatrixStack matrices, float tickDelta) {
|
||||
if (MidnightControlsConfig.controlsMode == ControlsMode.CONTROLLER && this.client.currentScreen == null && MidnightControlsConfig.getController().isConnected() && MidnightControlsConfig.getController().isGamepad()) {
|
||||
int y = bottom(2);
|
||||
this.renderFirstIcons(matrices, MidnightControlsConfig.hudSide == HudSide.LEFT ? 2 : client.getWindow().getScaledWidth() - 2, y);
|
||||
this.renderSecondIcons(matrices, MidnightControlsConfig.hudSide == HudSide.RIGHT ? 2 : client.getWindow().getScaledWidth() - 2, y);
|
||||
this.renderFirstSection(matrices, MidnightControlsConfig.hudSide == HudSide.LEFT ? 2 : client.getWindow().getScaledWidth() - 2, y);
|
||||
this.renderSecondSection(matrices, MidnightControlsConfig.hudSide == HudSide.RIGHT ? 2 : client.getWindow().getScaledWidth() - 2, y);
|
||||
}
|
||||
|
||||
if (this.mod.reacharound.isLastReacharoundVertical()) {
|
||||
// Render crosshair indicator.
|
||||
var window = this.client.getWindow();
|
||||
var text = "[ ]";
|
||||
|
||||
float scale = Math.min(5, this.ticksDisplayedCrosshair + tickDelta) / 5F;
|
||||
scale *= scale;
|
||||
int opacity = ((int) (255 * scale)) << 24;
|
||||
|
||||
this.client.textRenderer.draw(matrices, text, window.getScaledWidth() / 2.f - this.client.textRenderer.getWidth(text) / 2.f,
|
||||
window.getScaledHeight() / 2.f - 4, 0xCCCCCC | opacity);
|
||||
}
|
||||
}
|
||||
|
||||
public void renderFirstIcons(MatrixStack matrices, int x, int y) {
|
||||
int offset = 2 + this.inventoryWidth + this.inventoryButtonWidth + 4;
|
||||
int currentX = MidnightControlsConfig.hudSide == HudSide.LEFT ? x : x - this.inventoryButtonWidth;
|
||||
this.drawButton(matrices, currentX, y, ButtonBinding.INVENTORY, true);
|
||||
this.drawButton(matrices, currentX += (MidnightControlsConfig.hudSide == HudSide.LEFT ? offset : -offset), y, ButtonBinding.SWAP_HANDS, true);
|
||||
offset = 2 + this.swapHandsWidth + this.dropItemButtonWidth + 4;
|
||||
if (this.client.options.showSubtitles && MidnightControlsConfig.hudSide == HudSide.RIGHT) {
|
||||
currentX += -offset;
|
||||
} else {
|
||||
currentX = MidnightControlsConfig.hudSide == 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 (MidnightControlsConfig.hudSide == HudSide.LEFT)
|
||||
currentX -= this.useButtonWidth;
|
||||
this.drawButton(matrices, currentX, y, ButtonBinding.USE, true);
|
||||
offset = 2 + this.useWidth + 4;
|
||||
if (this.client.options.showSubtitles && MidnightControlsConfig.hudSide == HudSide.LEFT) {
|
||||
currentX -= offset;
|
||||
} else {
|
||||
currentX = x;
|
||||
y -= 24;
|
||||
}
|
||||
}
|
||||
|
||||
if (MidnightControlsConfig.hudSide == 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 = MidnightControlsConfig.hudSide == HudSide.LEFT ? x + this.inventoryButtonWidth + 2 : x - this.inventoryButtonWidth - 2 - this.inventoryWidth;
|
||||
this.drawTip(matrices, currentX, y, ButtonBinding.INVENTORY, true);
|
||||
currentX += MidnightControlsConfig.hudSide == HudSide.LEFT ? this.inventoryWidth + 4 + this.swapHandsButtonWidth + 2
|
||||
: -this.swapHandsWidth - 2 - this.swapHandsButtonWidth - 4;
|
||||
this.drawTip(matrices, currentX, y, ButtonBinding.SWAP_HANDS, true);
|
||||
if (this.client.options.showSubtitles && MidnightControlsConfig.hudSide == HudSide.RIGHT) {
|
||||
currentX += -this.dropItemWidth - 2 - this.dropItemButtonWidth - 4;
|
||||
} else {
|
||||
y -= 24;
|
||||
currentX = MidnightControlsConfig.hudSide == HudSide.LEFT ? x + this.dropItemButtonWidth + 2 : x - this.dropItemButtonWidth - 2 - this.dropItemWidth;
|
||||
}
|
||||
this.drawTip(matrices, currentX, y, ButtonBinding.DROP_ITEM, !this.client.player.getMainHandStack().isEmpty());
|
||||
}
|
||||
|
||||
public void renderSecondSection(MatrixStack matrices, int x, int y) {
|
||||
int currentX = x;
|
||||
|
||||
if (!this.placeAction.isEmpty()) {
|
||||
currentX += MidnightControlsConfig.hudSide == HudSide.RIGHT ? this.useButtonWidth + 2 : -this.useButtonWidth - 2 - this.useWidth;
|
||||
|
||||
this.drawTip(matrices, currentX, y, this.placeAction, true);
|
||||
|
||||
if (this.client.options.showSubtitles && MidnightControlsConfig.hudSide == HudSide.LEFT) {
|
||||
currentX -= 4;
|
||||
} else {
|
||||
currentX = x;
|
||||
y -= 24;
|
||||
}
|
||||
}
|
||||
|
||||
currentX += MidnightControlsConfig.hudSide == 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 (MidnightControlsConfig.controlsMode == ControlsMode.CONTROLLER) {
|
||||
if (this.client.crosshairTarget == null)
|
||||
return;
|
||||
|
||||
String placeAction;
|
||||
|
||||
// Update "Use" tip status.
|
||||
if (this.client.crosshairTarget.getType() == HitResult.Type.MISS) {
|
||||
this.placeHitResult = this.mod.reacharound.getLastReacharoundResult();
|
||||
this.attackAction = "";
|
||||
this.attackWidth = 0;
|
||||
} else {
|
||||
if (this.client.crosshairTarget.getType() == HitResult.Type.BLOCK)
|
||||
this.placeHitResult = (BlockHitResult) this.client.crosshairTarget;
|
||||
else
|
||||
this.placeHitResult = null;
|
||||
|
||||
this.attackAction = this.client.crosshairTarget.getType() == HitResult.Type.BLOCK ? "midnightcontrols.action.hit" : ButtonBinding.ATTACK.getTranslationKey();
|
||||
this.attackWidth = this.width(attackAction);
|
||||
}
|
||||
|
||||
if (this.mod.reacharound.isLastReacharoundVertical()) {
|
||||
if (this.ticksDisplayedCrosshair < 5)
|
||||
this.ticksDisplayedCrosshair++;
|
||||
} else {
|
||||
this.ticksDisplayedCrosshair = 0;
|
||||
}
|
||||
|
||||
var customAttackAction = MidnightControlsCompat.getAttackActionAt(this.client, this.placeHitResult);
|
||||
if (customAttackAction != null) {
|
||||
this.attackAction = customAttackAction;
|
||||
this.attackWidth = this.width(customAttackAction);
|
||||
}
|
||||
|
||||
ItemStack stack = null;
|
||||
if (this.client.player != null) {
|
||||
stack = this.client.player.getMainHandStack();
|
||||
if (stack == null || stack.isEmpty())
|
||||
stack = this.client.player.getOffHandStack();
|
||||
}
|
||||
if (stack == null || stack.isEmpty()) {
|
||||
placeAction = "";
|
||||
} else {
|
||||
if (this.placeHitResult != null && stack.getItem() instanceof BlockItem) {
|
||||
placeAction = "midnightcontrols.action.place";
|
||||
} else {
|
||||
placeAction = ButtonBinding.USE.getTranslationKey();
|
||||
}
|
||||
}
|
||||
|
||||
var customUseAction = MidnightControlsCompat.getUseActionAt(this.client, this.placeHitResult);
|
||||
if (customUseAction != null)
|
||||
placeAction = customUseAction;
|
||||
|
||||
this.placeAction = placeAction;
|
||||
|
||||
// Cache the "Use" tip width.
|
||||
if (this.placeAction.isEmpty())
|
||||
this.useWidth = 0;
|
||||
else
|
||||
this.useWidth = this.width(this.placeAction);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasTicks() {
|
||||
return true;
|
||||
}
|
||||
|
||||
private int bottom(int y) {
|
||||
return this.client.getWindow().getScaledHeight() - y - MidnightControlsRenderer.ICON_SIZE;
|
||||
}
|
||||
|
||||
private int width(@NotNull ButtonBinding binding) {
|
||||
return this.width(binding.getTranslationKey());
|
||||
}
|
||||
|
||||
private int width(@Nullable String text) {
|
||||
if (text == null || text.isEmpty())
|
||||
return 0;
|
||||
return this.client.textRenderer.getWidth(I18n.translate(text));
|
||||
}
|
||||
|
||||
private void drawButton(MatrixStack matrices, int x, int y, @NotNull ButtonBinding button, boolean display) {
|
||||
if (display)
|
||||
MidnightControlsRenderer.drawButton(matrices, x, y, button, this.client);
|
||||
}
|
||||
|
||||
private void drawTip(MatrixStack matrices, int x, int y, @NotNull ButtonBinding button, boolean display) {
|
||||
this.drawTip(matrices, x, y, button.getTranslationKey(), display);
|
||||
}
|
||||
|
||||
private void drawTip(MatrixStack matrices, int x, int y, @NotNull String action, boolean display) {
|
||||
if (!display)
|
||||
return;
|
||||
var translatedAction = I18n.translate(action);
|
||||
int textY = (MidnightControlsRenderer.ICON_SIZE / 2 - this.client.textRenderer.fontHeight / 2) + 1;
|
||||
this.client.textRenderer.draw(matrices, translatedAction, (float) x, (float) (y + textY), 14737632);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,259 @@
|
||||
/*
|
||||
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
|
||||
*
|
||||
* This file is part of midnightcontrols.
|
||||
*
|
||||
* Licensed under the MIT license. For more information,
|
||||
* see the LICENSE file.
|
||||
*/
|
||||
|
||||
package eu.midnightdust.midnightcontrols.client.gui;
|
||||
|
||||
import com.mojang.blaze3d.systems.RenderSystem;
|
||||
import eu.midnightdust.midnightcontrols.client.MidnightControlsClient;
|
||||
import eu.midnightdust.midnightcontrols.client.MidnightControlsConfig;
|
||||
import eu.midnightdust.midnightcontrols.client.MidnightInput;
|
||||
import eu.midnightdust.midnightcontrols.client.compat.MidnightControlsCompat;
|
||||
import eu.midnightdust.midnightcontrols.client.controller.ButtonBinding;
|
||||
import eu.midnightdust.midnightcontrols.client.util.HandledScreenAccessor;
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.client.font.TextRenderer;
|
||||
import net.minecraft.client.gui.DrawableHelper;
|
||||
import net.minecraft.client.resource.language.I18n;
|
||||
import net.minecraft.client.util.math.MatrixStack;
|
||||
import net.minecraft.screen.slot.Slot;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.lwjgl.glfw.GLFW;
|
||||
|
||||
/**
|
||||
* Represents the midnightcontrols renderer.
|
||||
*
|
||||
* @author LambdAurora
|
||||
* @version 1.7.0
|
||||
* @since 1.2.0
|
||||
*/
|
||||
public class MidnightControlsRenderer {
|
||||
public static final int ICON_SIZE = 20;
|
||||
private static final int BUTTON_SIZE = 15;
|
||||
private static final int AXIS_SIZE = 18;
|
||||
|
||||
public static int getButtonSize(int button) {
|
||||
return switch (button) {
|
||||
case -1 -> 0;
|
||||
case GLFW.GLFW_GAMEPAD_AXIS_LEFT_X + 100, GLFW.GLFW_GAMEPAD_AXIS_LEFT_X + 200,
|
||||
GLFW.GLFW_GAMEPAD_AXIS_LEFT_Y + 100, GLFW.GLFW_GAMEPAD_AXIS_LEFT_Y + 200,
|
||||
GLFW.GLFW_GAMEPAD_AXIS_RIGHT_X + 100, GLFW.GLFW_GAMEPAD_AXIS_RIGHT_X + 200,
|
||||
GLFW.GLFW_GAMEPAD_AXIS_RIGHT_Y + 100, GLFW.GLFW_GAMEPAD_AXIS_RIGHT_Y + 200 -> AXIS_SIZE;
|
||||
default -> BUTTON_SIZE;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the binding icon width.
|
||||
*
|
||||
* @param binding the binding
|
||||
* @return the width
|
||||
*/
|
||||
public static int getBindingIconWidth(@NotNull ButtonBinding binding) {
|
||||
return getBindingIconWidth(binding.getButton());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the binding icon width.
|
||||
*
|
||||
* @param buttons the buttons
|
||||
* @return the width
|
||||
*/
|
||||
public static int getBindingIconWidth(int[] buttons) {
|
||||
int width = 0;
|
||||
for (int i = 0; i < buttons.length; i++) {
|
||||
width += ICON_SIZE;
|
||||
if (i + 1 < buttons.length) {
|
||||
width += 2;
|
||||
}
|
||||
}
|
||||
return width;
|
||||
}
|
||||
|
||||
public static ButtonSize drawButton(MatrixStack matrices, int x, int y, @NotNull ButtonBinding button, @NotNull MinecraftClient client) {
|
||||
return drawButton(matrices, x, y, button.getButton(), client);
|
||||
}
|
||||
|
||||
public static ButtonSize drawButton(MatrixStack matrices, int x, int y, int[] buttons, @NotNull MinecraftClient client) {
|
||||
int height = 0;
|
||||
int length = 0;
|
||||
int currentX = x;
|
||||
for (int i = 0; i < buttons.length; i++) {
|
||||
int btn = buttons[i];
|
||||
int size = drawButton(matrices, currentX, y, btn, client);
|
||||
if (size > height)
|
||||
height = size;
|
||||
length += size;
|
||||
if (i + 1 < buttons.length) {
|
||||
length += 2;
|
||||
currentX = x + length;
|
||||
}
|
||||
}
|
||||
return new ButtonSize(length, height);
|
||||
}
|
||||
|
||||
public static int drawButton(MatrixStack matrices, int x, int y, int button, @NotNull MinecraftClient client) {
|
||||
boolean second = false;
|
||||
if (button == -1)
|
||||
return 0;
|
||||
else if (button >= 500) {
|
||||
button -= 1000;
|
||||
second = true;
|
||||
}
|
||||
|
||||
int controllerType = MidnightControlsConfig.controllerType.getId();
|
||||
boolean axis = false;
|
||||
int buttonOffset = button * 15;
|
||||
switch (button) {
|
||||
case 15 -> buttonOffset = 0;
|
||||
case 16 -> buttonOffset = 18;
|
||||
case 17 -> buttonOffset = 36;
|
||||
case 18 -> buttonOffset = 54;
|
||||
case GLFW.GLFW_GAMEPAD_BUTTON_LEFT_BUMPER -> buttonOffset = 7 * 15;
|
||||
case GLFW.GLFW_GAMEPAD_BUTTON_RIGHT_BUMPER -> buttonOffset = 8 * 15;
|
||||
case GLFW.GLFW_GAMEPAD_BUTTON_BACK -> buttonOffset = 4 * 15;
|
||||
case GLFW.GLFW_GAMEPAD_BUTTON_START -> buttonOffset = 6 * 15;
|
||||
case GLFW.GLFW_GAMEPAD_BUTTON_GUIDE -> buttonOffset = 5 * 15;
|
||||
case GLFW.GLFW_GAMEPAD_BUTTON_LEFT_THUMB -> buttonOffset = 15 * 15;
|
||||
case GLFW.GLFW_GAMEPAD_BUTTON_RIGHT_THUMB -> buttonOffset = 16 * 15;
|
||||
case GLFW.GLFW_GAMEPAD_AXIS_LEFT_X + 100 -> {
|
||||
buttonOffset = 0;
|
||||
axis = true;
|
||||
}
|
||||
case GLFW.GLFW_GAMEPAD_AXIS_LEFT_Y + 100 -> {
|
||||
buttonOffset = 18;
|
||||
axis = true;
|
||||
}
|
||||
case GLFW.GLFW_GAMEPAD_AXIS_RIGHT_X + 100 -> {
|
||||
buttonOffset = 2 * 18;
|
||||
axis = true;
|
||||
}
|
||||
case GLFW.GLFW_GAMEPAD_AXIS_RIGHT_Y + 100 -> {
|
||||
buttonOffset = 3 * 18;
|
||||
axis = true;
|
||||
}
|
||||
case GLFW.GLFW_GAMEPAD_AXIS_LEFT_X + 200 -> {
|
||||
buttonOffset = 4 * 18;
|
||||
axis = true;
|
||||
}
|
||||
case GLFW.GLFW_GAMEPAD_AXIS_LEFT_Y + 200 -> {
|
||||
buttonOffset = 5 * 18;
|
||||
axis = true;
|
||||
}
|
||||
case GLFW.GLFW_GAMEPAD_AXIS_RIGHT_X + 200 -> {
|
||||
buttonOffset = 6 * 18;
|
||||
axis = true;
|
||||
}
|
||||
case GLFW.GLFW_GAMEPAD_AXIS_RIGHT_Y + 200 -> {
|
||||
buttonOffset = 7 * 18;
|
||||
axis = true;
|
||||
}
|
||||
case GLFW.GLFW_GAMEPAD_AXIS_LEFT_TRIGGER + 100, GLFW.GLFW_GAMEPAD_AXIS_LEFT_TRIGGER + 200 -> buttonOffset = 9 * 15;
|
||||
case GLFW.GLFW_GAMEPAD_AXIS_RIGHT_TRIGGER + 100, GLFW.GLFW_GAMEPAD_AXIS_RIGHT_TRIGGER + 200 -> buttonOffset = 10 * 15;
|
||||
}
|
||||
|
||||
RenderSystem.setShaderTexture(0, axis ? MidnightControlsClient.CONTROLLER_AXIS : button >= 15 && button <= 19 ? MidnightControlsClient.CONTROLLER_EXPANDED :MidnightControlsClient.CONTROLLER_BUTTONS);
|
||||
RenderSystem.disableDepthTest();
|
||||
|
||||
int assetSize = axis || (button >= 15 && button <= 18) ? AXIS_SIZE : BUTTON_SIZE;
|
||||
|
||||
RenderSystem.setShaderColor(1.f, second ? 0.f : 1.f, 1.f, 1.f);
|
||||
DrawableHelper.drawTexture(matrices, x + (ICON_SIZE / 2 - assetSize / 2), y + (ICON_SIZE / 2 - assetSize / 2),
|
||||
(float) buttonOffset, (float) (controllerType * assetSize),
|
||||
assetSize, assetSize,
|
||||
256, 256);
|
||||
RenderSystem.enableDepthTest();
|
||||
|
||||
return ICON_SIZE;
|
||||
}
|
||||
|
||||
public static int drawButtonTip(MatrixStack matrices, int x, int y, @NotNull ButtonBinding button, boolean display, @NotNull MinecraftClient client) {
|
||||
return drawButtonTip(matrices, x, y, button.getButton(), button.getTranslationKey(), display, client);
|
||||
}
|
||||
|
||||
public static int drawButtonTip(MatrixStack matrices, int x, int y, int[] button, @NotNull String action, boolean display, @NotNull MinecraftClient client) {
|
||||
if (display) {
|
||||
int buttonWidth = drawButton(matrices, x, y, button, client).length();
|
||||
|
||||
var translatedAction = I18n.translate(action);
|
||||
int textY = (MidnightControlsRenderer.ICON_SIZE / 2 - client.textRenderer.fontHeight / 2) + 1;
|
||||
|
||||
return client.textRenderer.drawWithShadow(matrices, translatedAction, (float) (x + buttonWidth + 2), (float) (y + textY), 14737632);
|
||||
}
|
||||
|
||||
return -10;
|
||||
}
|
||||
|
||||
private static int getButtonTipWidth(@NotNull String action, @NotNull TextRenderer textRenderer) {
|
||||
return 15 + 5 + textRenderer.getWidth(action);
|
||||
}
|
||||
|
||||
public static void renderVirtualCursor(@NotNull MatrixStack matrices, @NotNull MinecraftClient client) {
|
||||
if (!MidnightControlsConfig.virtualMouse || (client.currentScreen == null
|
||||
|| MidnightInput.isScreenInteractive(client.currentScreen)))
|
||||
return;
|
||||
|
||||
int mouseX = (int) (client.mouse.getX() * (double) client.getWindow().getScaledWidth() / (double) client.getWindow().getWidth());
|
||||
int mouseY = (int) (client.mouse.getY() * (double) client.getWindow().getScaledHeight() / (double) client.getWindow().getHeight());
|
||||
|
||||
boolean hoverSlot = false;
|
||||
|
||||
if (client.currentScreen instanceof HandledScreenAccessor inventoryScreen) {
|
||||
int guiLeft = inventoryScreen.getX();
|
||||
int guiTop = inventoryScreen.getY();
|
||||
|
||||
Slot slot = inventoryScreen.midnightcontrols$getSlotAt(mouseX, mouseY);
|
||||
|
||||
if (slot != null) {
|
||||
mouseX = guiLeft + slot.x;
|
||||
mouseY = guiTop + slot.y;
|
||||
hoverSlot = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!hoverSlot) {
|
||||
var slot = MidnightControlsCompat.getSlotAt(client.currentScreen, mouseX, mouseY);
|
||||
|
||||
if (slot != null) {
|
||||
mouseX = slot.x();
|
||||
mouseY = slot.y();
|
||||
hoverSlot = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!hoverSlot) {
|
||||
mouseX -= 8;
|
||||
mouseY -= 8;
|
||||
}
|
||||
|
||||
drawCursor(matrices, mouseX, mouseY, hoverSlot, client);
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws the virtual cursor.
|
||||
*
|
||||
* @param matrices the matrix stack
|
||||
* @param x x coordinate
|
||||
* @param y y coordinate
|
||||
* @param hoverSlot true if hovering a slot, else false
|
||||
* @param client the client instance
|
||||
*/
|
||||
public static void drawCursor(@NotNull MatrixStack matrices, int x, int y, boolean hoverSlot, @NotNull MinecraftClient client) {
|
||||
RenderSystem.disableDepthTest();
|
||||
|
||||
RenderSystem.setShaderColor(1.f, 1.f, 1.f, 1.f);
|
||||
RenderSystem.setShaderTexture(0, MidnightControlsClient.CURSOR_TEXTURE);
|
||||
DrawableHelper.drawTexture(matrices, x, y,
|
||||
hoverSlot ? 16.f : 0.f, MidnightControlsConfig.virtualMouseSkin.ordinal() * 16.f,
|
||||
16, 16, 32, 64);
|
||||
RenderSystem.enableDepthTest();
|
||||
}
|
||||
|
||||
public record ButtonSize(int length, int height) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,411 @@
|
||||
/*
|
||||
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
|
||||
*
|
||||
* This file is part of midnightcontrols.
|
||||
*
|
||||
* Licensed under the MIT license. For more information,
|
||||
* see the LICENSE file.
|
||||
*/
|
||||
|
||||
package eu.midnightdust.midnightcontrols.client.gui;
|
||||
|
||||
import com.mojang.blaze3d.systems.RenderSystem;
|
||||
import dev.lambdaurora.spruceui.background.Background;
|
||||
import dev.lambdaurora.spruceui.widget.SpruceWidget;
|
||||
import eu.midnightdust.lib.util.MidnightColorUtil;
|
||||
import eu.midnightdust.midnightcontrols.MidnightControls;
|
||||
import eu.midnightdust.midnightcontrols.client.MidnightControlsClient;
|
||||
import eu.midnightdust.midnightcontrols.client.MidnightControlsConfig;
|
||||
import eu.midnightdust.midnightcontrols.client.controller.Controller;
|
||||
import eu.midnightdust.midnightcontrols.client.gui.widget.ControllerControlsWidget;
|
||||
import dev.lambdaurora.spruceui.Position;
|
||||
import dev.lambdaurora.spruceui.SpruceTexts;
|
||||
import dev.lambdaurora.spruceui.option.*;
|
||||
import dev.lambdaurora.spruceui.screen.SpruceScreen;
|
||||
import dev.lambdaurora.spruceui.widget.AbstractSpruceWidget;
|
||||
import dev.lambdaurora.spruceui.widget.SpruceLabelWidget;
|
||||
import dev.lambdaurora.spruceui.widget.container.SpruceContainerWidget;
|
||||
import dev.lambdaurora.spruceui.widget.container.SpruceOptionListWidget;
|
||||
import dev.lambdaurora.spruceui.widget.container.tabbed.SpruceTabbedWidget;
|
||||
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking;
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.client.gui.screen.Screen;
|
||||
import net.minecraft.client.gui.widget.ButtonWidget;
|
||||
import net.minecraft.client.render.*;
|
||||
import net.minecraft.client.resource.language.I18n;
|
||||
import net.minecraft.client.util.math.MatrixStack;
|
||||
import net.minecraft.text.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 net.minecraft.util.math.Matrix4f;
|
||||
import org.lwjgl.glfw.GLFW;
|
||||
|
||||
import java.awt.*;
|
||||
|
||||
/**
|
||||
* Represents the midnightcontrols settings screen.
|
||||
*/
|
||||
public class MidnightControlsSettingsScreen extends SpruceScreen {
|
||||
private static final Text SDL2_GAMEPAD_TOOL = new LiteralText("SDL2 Gamepad Tool").formatted(Formatting.GREEN);
|
||||
public static final String GAMEPAD_TOOL_URL = "https://generalarcade.com/gamepadtool/";
|
||||
final MidnightControlsClient mod = MidnightControlsClient.get();
|
||||
private final Screen parent;
|
||||
// General options
|
||||
private final SpruceOption inputModeOption;
|
||||
private final SpruceOption autoSwitchModeOption;
|
||||
private final SpruceOption rotationSpeedOption;
|
||||
private final SpruceOption 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("midnightcontrols.menu.controller",
|
||||
amount -> {
|
||||
int id = MidnightControlsConfig.getController().id();
|
||||
id += amount;
|
||||
if (id > GLFW.GLFW_JOYSTICK_LAST)
|
||||
id = GLFW.GLFW_JOYSTICK_1;
|
||||
id = searchNextAvailableController(id, false);
|
||||
MidnightControlsConfig.setController(Controller.byId(id));
|
||||
},
|
||||
option -> {
|
||||
var controller = MidnightControlsConfig.getController();
|
||||
var controllerName = controller.getName();
|
||||
if (!controller.isConnected())
|
||||
return option.getDisplayText(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("midnightcontrols.menu.controller2",
|
||||
amount -> {
|
||||
int id = MidnightControlsConfig.getSecondController().map(Controller::id).orElse(-1);
|
||||
id += amount;
|
||||
if (id > GLFW.GLFW_JOYSTICK_LAST)
|
||||
id = -1;
|
||||
id = searchNextAvailableController(id, true);
|
||||
MidnightControlsConfig.setSecondController(id == -1 ? null : Controller.byId(id));
|
||||
},
|
||||
option -> MidnightControlsConfig.getSecondController().map(controller -> {
|
||||
var controllerName = controller.getName();
|
||||
if (!controller.isConnected())
|
||||
return option.getDisplayText(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("midnightcontrols.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("midnightcontrols.menu.max_left_x_value", GLFW.GLFW_GAMEPAD_AXIS_LEFT_X),
|
||||
maxAnalogValueOption("midnightcontrols.menu.max_left_y_value", GLFW.GLFW_GAMEPAD_AXIS_LEFT_Y),
|
||||
maxAnalogValueOption("midnightcontrols.menu.max_right_x_value", GLFW.GLFW_GAMEPAD_AXIS_RIGHT_X),
|
||||
maxAnalogValueOption("midnightcontrols.menu.max_right_y_value", GLFW.GLFW_GAMEPAD_AXIS_RIGHT_Y)
|
||||
};
|
||||
|
||||
private static SpruceOption maxAnalogValueOption(String key, int axis) {
|
||||
return new SpruceDoubleOption(key, .25f, 1.f, 0.05f,
|
||||
() -> MidnightControlsConfig.getAxisMaxValue(axis),
|
||||
newValue -> MidnightControlsConfig.setAxisMaxValue(axis, newValue),
|
||||
option -> option.getDisplayText(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 MidnightControlsSettingsScreen(Screen parent, boolean hideControls) {
|
||||
super(new TranslatableText("midnightcontrols.title.settings"));
|
||||
this.parent = parent;
|
||||
// General options
|
||||
this.inputModeOption = new SpruceCyclingOption("midnightcontrols.menu.controls_mode",
|
||||
amount -> {
|
||||
var next = MidnightControlsConfig.controlsMode.next();
|
||||
MidnightControlsConfig.controlsMode = next;
|
||||
MidnightControlsConfig.save();
|
||||
|
||||
if (this.client.player != null) {
|
||||
ClientPlayNetworking.getSender().sendPacket(MidnightControls.CONTROLS_MODE_CHANNEL, this.mod.makeControlsModeBuffer(next));
|
||||
}
|
||||
}, option -> option.getDisplayText(new TranslatableText(MidnightControlsConfig.controlsMode.getTranslationKey())),
|
||||
new TranslatableText("midnightcontrols.tooltip.controlsMidnightColorUtil.radialRainbow(1f,1f);_mode"));
|
||||
this.autoSwitchModeOption = new SpruceToggleBooleanOption("midnightcontrols.menu.auto_switch_mode", () -> MidnightControlsConfig.autoSwitchMode,
|
||||
value -> MidnightControlsConfig.autoSwitchMode = value, new TranslatableText("midnightcontrols.tooltip.auto_switch_mode"));
|
||||
this.rotationSpeedOption = new SpruceDoubleOption("midnightcontrols.menu.rotation_speed", 0.0, 100.0, .5f,
|
||||
() -> MidnightControlsConfig.rotationSpeed,
|
||||
value -> MidnightControlsConfig.rotationSpeed = value, option -> option.getDisplayText(new LiteralText(String.valueOf(option.get()))),
|
||||
new TranslatableText("midnightcontrols.tooltip.rotation_speed"));
|
||||
this.mouseSpeedOption = new SpruceDoubleOption("midnightcontrols.menu.mouse_speed", 0.0, 150.0, .5f,
|
||||
() -> MidnightControlsConfig.mouseSpeed,
|
||||
value -> MidnightControlsConfig.mouseSpeed = value, option -> option.getDisplayText(new LiteralText(String.valueOf(option.get()))),
|
||||
new TranslatableText("midnightcontrols.tooltip.mouse_speed"));
|
||||
this.resetOption = SpruceSimpleActionOption.reset(btn -> {
|
||||
// TODO
|
||||
MidnightControlsConfig.init("midnightcontrols", MidnightControlsConfig.class);
|
||||
var client = MinecraftClient.getInstance();
|
||||
this.init(client, client.getWindow().getScaledWidth(), client.getWindow().getScaledHeight());
|
||||
});
|
||||
// Gameplay options
|
||||
this.analogMovementOption = new SpruceToggleBooleanOption("midnightcontrols.menu.analog_movement",
|
||||
() -> MidnightControlsConfig.analogMovement, value -> MidnightControlsConfig.analogMovement = value,
|
||||
new TranslatableText("midnightcontrols.tooltip.analog_movement"));
|
||||
this.autoJumpOption = new SpruceToggleBooleanOption("options.autoJump",
|
||||
() -> this.client.options.autoJump,
|
||||
newValue -> this.client.options.autoJump = newValue,
|
||||
null);
|
||||
this.fastBlockPlacingOption = new SpruceToggleBooleanOption("midnightcontrols.menu.fast_block_placing", () -> MidnightControlsConfig.fastBlockPlacing,
|
||||
value -> MidnightControlsConfig.fastBlockPlacing = value, new TranslatableText("midnightcontrols.tooltip.fast_block_placing"));
|
||||
this.frontBlockPlacingOption = new SpruceToggleBooleanOption("midnightcontrols.menu.reacharound.horizontal", () -> MidnightControlsConfig.horizontalReacharound,
|
||||
value -> MidnightControlsConfig.horizontalReacharound = value, new TranslatableText("midnightcontrols.tooltip.reacharound.horizontal"));
|
||||
this.verticalReacharoundOption = new SpruceToggleBooleanOption("midnightcontrols.menu.reacharound.vertical", () -> MidnightControlsConfig.verticalReacharound,
|
||||
value -> MidnightControlsConfig.verticalReacharound = value, new TranslatableText("midnightcontrols.tooltip.reacharound.vertical"));
|
||||
this.flyDriftingOption = new SpruceToggleBooleanOption("midnightcontrols.menu.fly_drifting", () -> MidnightControlsConfig.flyDrifting,
|
||||
value -> MidnightControlsConfig.flyDrifting = value, new TranslatableText("midnightcontrols.tooltip.fly_drifting"));
|
||||
this.flyVerticalDriftingOption = new SpruceToggleBooleanOption("midnightcontrols.menu.fly_drifting_vertical", () -> MidnightControlsConfig.verticalFlyDrifting,
|
||||
value -> MidnightControlsConfig.verticalFlyDrifting = value, new TranslatableText("midnightcontrols.tooltip.fly_drifting_vertical"));
|
||||
// Appearance options
|
||||
this.controllerTypeOption = new SpruceCyclingOption("midnightcontrols.menu.controller_type",
|
||||
amount -> MidnightControlsConfig.controllerType = MidnightControlsConfig.controllerType.next(),
|
||||
option -> option.getDisplayText(MidnightControlsConfig.controllerType.getTranslatedText()),
|
||||
new TranslatableText("midnightcontrols.tooltip.controller_type"));
|
||||
this.virtualMouseSkinOption = new SpruceCyclingOption("midnightcontrols.menu.virtual_mouse.skin",
|
||||
amount -> MidnightControlsConfig.virtualMouseSkin = MidnightControlsConfig.virtualMouseSkin.next(),
|
||||
option -> option.getDisplayText(MidnightControlsConfig.virtualMouseSkin.getTranslatedText()),
|
||||
null);
|
||||
this.hudEnableOption = new SpruceToggleBooleanOption("midnightcontrols.menu.hud_enable", () -> MidnightControlsConfig.hudEnable,
|
||||
this.mod::setHudEnabled, new TranslatableText("midnightcontrols.tooltip.hud_enable"));
|
||||
this.hudSideOption = new SpruceCyclingOption("midnightcontrols.menu.hud_side",
|
||||
amount -> MidnightControlsConfig.hudSide = MidnightControlsConfig.hudSide.next(),
|
||||
option -> option.getDisplayText(MidnightControlsConfig.hudSide.getTranslatedText()),
|
||||
new TranslatableText("midnightcontrols.tooltip.hud_side"));
|
||||
// Controller options
|
||||
this.rightDeadZoneOption = new SpruceDoubleOption("midnightcontrols.menu.right_dead_zone", 0.05, 1.0, .05f,
|
||||
() -> MidnightControlsConfig.rightDeadZone,
|
||||
value -> MidnightControlsConfig.rightDeadZone = value, option -> {
|
||||
var value = String.valueOf(option.get());
|
||||
return option.getDisplayText(new LiteralText(value.substring(0, Math.min(value.length(), 5))));
|
||||
}, new TranslatableText("midnightcontrols.tooltip.right_dead_zone"));
|
||||
this.leftDeadZoneOption = new SpruceDoubleOption("midnightcontrols.menu.left_dead_zone", 0.05, 1.0, .05f,
|
||||
() -> MidnightControlsConfig.leftDeadZone,
|
||||
value -> MidnightControlsConfig.leftDeadZone = value, option -> {
|
||||
var value = String.valueOf(option.get());
|
||||
return option.getDisplayText(new LiteralText(value.substring(0, Math.min(value.length(), 5))));
|
||||
}, new TranslatableText("midnightcontrols.tooltip.left_dead_zone"));
|
||||
this.invertsRightXAxis = new SpruceToggleBooleanOption("midnightcontrols.menu.invert_right_x_axis", () -> MidnightControlsConfig.invertRightXAxis,
|
||||
value -> MidnightControlsConfig.invertRightXAxis = value, null);
|
||||
this.invertsRightYAxis = new SpruceToggleBooleanOption("midnightcontrols.menu.invert_right_y_axis", () -> MidnightControlsConfig.invertRightYAxis,
|
||||
value -> MidnightControlsConfig.invertRightYAxis = value, null);
|
||||
this.unfocusedInputOption = new SpruceToggleBooleanOption("midnightcontrols.menu.unfocused_input", () -> MidnightControlsConfig.unfocusedInput,
|
||||
value -> MidnightControlsConfig.unfocusedInput = value, new TranslatableText("midnightcontrols.tooltip.unfocused_input"));
|
||||
this.virtualMouseOption = new SpruceToggleBooleanOption("midnightcontrols.menu.virtual_mouse", () -> MidnightControlsConfig.virtualMouse,
|
||||
value -> MidnightControlsConfig.virtualMouse = value, new TranslatableText("midnightcontrols.tooltip.virtual_mouse"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removed() {
|
||||
MidnightControlsConfig.save();
|
||||
super.removed();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClose() {
|
||||
MidnightControlsConfig.save();
|
||||
super.onClose();
|
||||
}
|
||||
|
||||
private int getTextHeight() {
|
||||
return (5 + this.textRenderer.fontHeight) * 3 + 5;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void init() {
|
||||
super.init();
|
||||
|
||||
this.buildTabs();
|
||||
|
||||
this.addDrawableChild(this.resetOption.createWidget(Position.of(this.width / 2 - 155, this.height - 29), 150));
|
||||
this.addDrawableChild(new ButtonWidget(this.width / 2 - 155 + 160, this.height - 29, 150, 20, SpruceTexts.GUI_DONE,
|
||||
btn -> this.client.setScreen(this.parent)));
|
||||
}
|
||||
|
||||
public void buildTabs() {
|
||||
var tabs = new SpruceTabbedWidget(Position.of(0, 24), this.width, this.height - 32 - 24,
|
||||
null,
|
||||
Math.max(116, this.width / 8), 0);
|
||||
tabs.getList().setBackground(new MidnightControlsBackground());
|
||||
this.addDrawableChild(tabs);
|
||||
|
||||
tabs.addSeparatorEntry(new TranslatableText("midnightcontrols.menu.separator.general"));
|
||||
tabs.addTabEntry(new TranslatableText("midnightcontrols.menu.title.general"), null,
|
||||
this::buildGeneralTab);
|
||||
tabs.addTabEntry(new TranslatableText("midnightcontrols.menu.title.gameplay"), null,
|
||||
this::buildGameplayTab);
|
||||
tabs.addTabEntry(new TranslatableText("midnightcontrols.menu.title.visual"), null,
|
||||
this::buildVisualTab);
|
||||
|
||||
tabs.addSeparatorEntry(new TranslatableText("options.controls"));
|
||||
tabs.addTabEntry(new TranslatableText("midnightcontrols.menu.title.controller_controls"), null,
|
||||
this::buildControllerControlsTab);
|
||||
|
||||
tabs.addSeparatorEntry(new TranslatableText("midnightcontrols.menu.separator.controller"));
|
||||
tabs.addTabEntry(new TranslatableText("midnightcontrols.menu.title.controller"), null,
|
||||
this::buildControllerTab);
|
||||
tabs.addTabEntry(new TranslatableText("midnightcontrols.menu.title.mappings.string"), null,
|
||||
this::buildMappingsStringEditorTab);
|
||||
}
|
||||
|
||||
public SpruceOptionListWidget buildGeneralTab(int width, int height) {
|
||||
var 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) {
|
||||
var 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) {
|
||||
var list = new SpruceOptionListWidget(Position.origin(), width, height);
|
||||
list.addSingleOptionEntry(this.controllerTypeOption);
|
||||
list.addSingleOptionEntry(this.virtualMouseSkinOption);
|
||||
list.addSingleOptionEntry(new SpruceSeparatorOption("midnightcontrols.menu.title.hud", true, null));
|
||||
list.addSingleOptionEntry(this.hudEnableOption);
|
||||
list.addSingleOptionEntry(this.hudSideOption);
|
||||
return list;
|
||||
}
|
||||
|
||||
public ControllerControlsWidget buildControllerControlsTab(int width, int height) {
|
||||
return new ControllerControlsWidget(Position.origin(), width, height);
|
||||
}
|
||||
|
||||
public AbstractSpruceWidget buildControllerTab(int width, int height) {
|
||||
var root = new SpruceContainerWidget(Position.origin(), width, height);
|
||||
|
||||
var aboutMappings1 = new SpruceLabelWidget(Position.of(0, 2),
|
||||
new TranslatableText("midnightcontrols.controller.mappings.1", SDL2_GAMEPAD_TOOL),
|
||||
width, true);
|
||||
|
||||
var gamepadToolUrlLabel = new SpruceLabelWidget(Position.of(0, aboutMappings1.getHeight() + 4),
|
||||
this.controllerMappingsUrlText, width,
|
||||
label -> Util.getOperatingSystem().open(GAMEPAD_TOOL_URL), true);
|
||||
gamepadToolUrlLabel.setTooltip(new TranslatableText("chat.link.open"));
|
||||
|
||||
var aboutMappings3 = new SpruceLabelWidget(Position.of(0,
|
||||
aboutMappings1.getHeight() + gamepadToolUrlLabel.getHeight() + 6),
|
||||
new TranslatableText("midnightcontrols.controller.mappings.3", Formatting.GREEN.toString(), Formatting.RESET.toString()),
|
||||
width, true);
|
||||
|
||||
int listHeight = height - 8 - aboutMappings1.getHeight() - aboutMappings3.getHeight() - gamepadToolUrlLabel.getHeight();
|
||||
var labels = new SpruceContainerWidget(Position.of(0,
|
||||
listHeight),
|
||||
width, height - listHeight);
|
||||
labels.addChild(aboutMappings1);
|
||||
labels.addChild(gamepadToolUrlLabel);
|
||||
labels.addChild(aboutMappings3);
|
||||
|
||||
var list = new SpruceOptionListWidget(Position.origin(), width, listHeight);
|
||||
list.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 (var option : this.maxAnalogValueOptions) {
|
||||
list.addSingleOptionEntry(option);
|
||||
}
|
||||
|
||||
root.addChild(list);
|
||||
root.addChild(labels);
|
||||
return root;
|
||||
}
|
||||
|
||||
public SpruceContainerWidget buildMappingsStringEditorTab(int width, int height) {
|
||||
return new MappingsStringInputWidget(Position.origin(), width, height);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void renderTitle(MatrixStack matrices, int mouseX, int mouseY, float delta) {
|
||||
drawCenteredText(matrices, this.textRenderer, I18n.translate("midnightcontrols.menu.title"), this.width / 2, 8, 16777215);
|
||||
}
|
||||
|
||||
public static class MidnightControlsBackground implements Background {
|
||||
@Override
|
||||
public void render(MatrixStack matrixStack, SpruceWidget widget, int vOffset, int mouseX, int mouseY, float delta) {
|
||||
fill(matrixStack, widget.getX(), widget.getY(), widget.getX() + widget.getWidth(), widget.getY() + widget.getHeight(), MidnightColorUtil.hex2Rgb("#000000"));
|
||||
}
|
||||
private void fill(MatrixStack matrixStack, int x2, int y2, int x1, int y1, Color color) {
|
||||
matrixStack.push();
|
||||
|
||||
Matrix4f matrix = matrixStack.peek().getPositionMatrix();
|
||||
float r = (float)(color.getRed()) / 255.0F;
|
||||
float g = (float)(color.getGreen()) / 255.0F;
|
||||
float b = (float)(color.getBlue()) / 255.0F;
|
||||
float t = (float)(160) / 255.0F;
|
||||
BufferBuilder bufferBuilder = Tessellator.getInstance().getBuffer();
|
||||
RenderSystem.enableBlend();
|
||||
RenderSystem.disableTexture();
|
||||
RenderSystem.defaultBlendFunc();
|
||||
RenderSystem.setShader(GameRenderer::getPositionColorShader);
|
||||
bufferBuilder.begin(VertexFormat.DrawMode.QUADS, VertexFormats.POSITION_COLOR);
|
||||
bufferBuilder.vertex(matrix, (float)x1, (float)y2, 0.0F).color(r, g, b, t).next();
|
||||
bufferBuilder.vertex(matrix, (float)x2, (float)y2, 0.0F).color(r, g, b, t).next();
|
||||
bufferBuilder.vertex(matrix, (float)x2, (float)y1, 0.0F).color(r, g, b, t).next();
|
||||
bufferBuilder.vertex(matrix, (float)x1, (float)y1, 0.0F).color(r, g, b, t).next();
|
||||
bufferBuilder.end();
|
||||
BufferRenderer.draw(bufferBuilder);
|
||||
RenderSystem.enableTexture();
|
||||
RenderSystem.disableBlend();
|
||||
matrixStack.pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
|
||||
*
|
||||
* This file is part of midnightcontrols.
|
||||
*
|
||||
* Licensed under the MIT license. For more information,
|
||||
* see the LICENSE file.
|
||||
*/
|
||||
|
||||
package eu.midnightdust.midnightcontrols.client.gui;
|
||||
|
||||
import eu.midnightdust.midnightcontrols.client.controller.Controller;
|
||||
import dev.lambdaurora.spruceui.option.SpruceSimpleActionOption;
|
||||
import dev.lambdaurora.spruceui.widget.SpruceButtonWidget;
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.client.toast.SystemToast;
|
||||
import net.minecraft.text.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 = "midnightcontrols.menu.reload_controller_mappings";
|
||||
|
||||
public static SpruceSimpleActionOption newOption(@Nullable Consumer<SpruceButtonWidget> before) {
|
||||
return SpruceSimpleActionOption.of(KEY, btn -> {
|
||||
var client = MinecraftClient.getInstance();
|
||||
if (before != null)
|
||||
before.accept(btn);
|
||||
Controller.updateMappings();
|
||||
if (client.currentScreen instanceof MidnightControlsSettingsScreen)
|
||||
client.currentScreen.init(client, client.getWindow().getScaledWidth(), client.getWindow().getScaledHeight());
|
||||
client.getToastManager().add(SystemToast.create(client, SystemToast.Type.TUTORIAL_HINT,
|
||||
new TranslatableText("midnightcontrols.controller.mappings.updated"), LiteralText.EMPTY));
|
||||
}, new TranslatableText("midnightcontrols.tooltip.reload_controller_mappings"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
|
||||
*
|
||||
* This file is part of midnightcontrols.
|
||||
*
|
||||
* Licensed under the MIT license. For more information,
|
||||
* see the LICENSE file.
|
||||
*/
|
||||
|
||||
package eu.midnightdust.midnightcontrols.client.gui;
|
||||
|
||||
import eu.midnightdust.midnightcontrols.client.MidnightControlsClient;
|
||||
import eu.midnightdust.midnightcontrols.client.ring.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 MidnightControlsClient mod;
|
||||
|
||||
public RingScreen() {
|
||||
super(new TranslatableText("midnightcontrols.menu.title.ring"));
|
||||
this.mod = MidnightControlsClient.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldPause() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(MatrixStack matrices, int mouseX, int mouseY, float delta) {
|
||||
super.render(matrices, mouseX, mouseY, delta);
|
||||
|
||||
RingPage page = this.mod.ring.getCurrentPage();
|
||||
|
||||
page.render(matrices, this.textRenderer, this.width, this.height, mouseX, mouseY, delta);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean mouseReleased(double mouseX, double mouseY, int button) {
|
||||
/*if (midnightcontrolsClient.BINDING_RING.matchesMouse(button)) {
|
||||
this.onClose();
|
||||
return true;
|
||||
}*/
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,301 @@
|
||||
/*
|
||||
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
|
||||
*
|
||||
* This file is part of midnightcontrols.
|
||||
*
|
||||
* Licensed under the MIT license. For more information,
|
||||
* see the LICENSE file.
|
||||
*/
|
||||
|
||||
package eu.midnightdust.midnightcontrols.client.gui;
|
||||
|
||||
import dev.lambdaurora.spruceui.widget.SpruceTexturedButtonWidget;
|
||||
import eu.midnightdust.midnightcontrols.client.HudSide;
|
||||
import eu.midnightdust.midnightcontrols.client.MidnightControlsClient;
|
||||
import eu.midnightdust.midnightcontrols.client.MidnightControlsConfig;
|
||||
import eu.midnightdust.midnightcontrols.client.util.KeyBindingAccessor;
|
||||
import net.minecraft.client.gui.screen.ChatScreen;
|
||||
import net.minecraft.client.gui.screen.GameMenuScreen;
|
||||
import net.minecraft.client.gui.screen.Screen;
|
||||
import net.minecraft.client.gui.screen.ingame.InventoryScreen;
|
||||
import net.minecraft.client.gui.widget.ButtonWidget;
|
||||
import net.minecraft.client.gui.widget.TexturedButtonWidget;
|
||||
import net.minecraft.client.option.KeyBinding;
|
||||
import net.minecraft.network.packet.c2s.play.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("midnightcontrols", "textures/gui/widgets.png");
|
||||
private MidnightControlsClient mod;
|
||||
private SpruceTexturedButtonWidget jumpButton;
|
||||
private SpruceTexturedButtonWidget flyButton;
|
||||
private SpruceTexturedButtonWidget flyUpButton;
|
||||
private SpruceTexturedButtonWidget flyDownButton;
|
||||
private int flyButtonEnableTicks = 0;
|
||||
private int forwardButtonTick = 0;
|
||||
private SpruceTexturedButtonWidget forwardLeftButton;
|
||||
private SpruceTexturedButtonWidget forwardRightButton;
|
||||
private SpruceTexturedButtonWidget startSneakButton;
|
||||
private SpruceTexturedButtonWidget endSneakButton;
|
||||
|
||||
public TouchscreenOverlay(@NotNull MidnightControlsClient mod)
|
||||
{
|
||||
super(new LiteralText("Touchscreen overlay"));
|
||||
this.mod = mod;
|
||||
this.passEvents = true;
|
||||
}
|
||||
|
||||
// @Override
|
||||
// public boolean shouldPause()
|
||||
// {
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public boolean keyPressed(int keyCode, int scanCode, int modifiers)
|
||||
// {
|
||||
// super.keyPressed(keyCode,scanCode,modifiers);
|
||||
// //return false;
|
||||
// return true;
|
||||
// }
|
||||
//
|
||||
// private void pauseGame(boolean bl)
|
||||
// {
|
||||
// if (this.client == null)
|
||||
// return;
|
||||
// boolean bl2 = this.client.isIntegratedServerRunning() && !this.client.getServer().isRemote();
|
||||
// if (bl2) {
|
||||
// this.client.setScreen(new GameMenuScreen(!bl));
|
||||
// this.client.getSoundManager().pauseAll();
|
||||
// } else {
|
||||
// this.client.setScreen(new GameMenuScreen(true));
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Updates the forward button ticks cooldown.
|
||||
// *
|
||||
// * @param state The button state.
|
||||
// *
|
||||
// */
|
||||
// private void updateForwardButtonsState(boolean state)
|
||||
// {
|
||||
// if (state)
|
||||
// this.forwardButtonTick = -1;
|
||||
// else
|
||||
// this.forwardButtonTick = 20;
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Updates the jump buttons.
|
||||
// */
|
||||
// private void updateJumpButtons()
|
||||
// {
|
||||
// if (this.client == null)
|
||||
// return;
|
||||
// if (!this.client.interactionManager.isFlyingLocked()) {
|
||||
// boolean oldStateFly = this.flyButton.visible;
|
||||
// this.jumpButton.visible = false;
|
||||
// this.flyButton.visible = true;
|
||||
// this.flyUpButton.visible = true;
|
||||
// this.flyDownButton.visible = true;
|
||||
// if (oldStateFly != this.flyButton.visible) {
|
||||
// this.flyButtonEnableTicks = 5;
|
||||
// this.handleJump(null, false);
|
||||
// } else if (this.flyButtonEnableTicks > 0)
|
||||
// this.flyButtonEnableTicks--;
|
||||
// } else {
|
||||
// this.jumpButton.visible = true;
|
||||
// this.flyButton.visible = false;
|
||||
// this.flyUpButton.visible = false;
|
||||
// this.flyDownButton.visible = false;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Handles the jump button.
|
||||
// *
|
||||
// * @param btn The pressed button.
|
||||
// * @param state The state of the jump button.
|
||||
// */
|
||||
// private void handleJump(ButtonWidget btn, boolean state)
|
||||
// {
|
||||
// ((KeyBindingAccessor) this.client.options.keyJump).midnightcontrols$handlePressState(state);
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public void tick()
|
||||
// {
|
||||
// if (this.forwardButtonTick > 0) {
|
||||
// this.forwardButtonTick--;
|
||||
// } else if (this.forwardButtonTick == 0) {
|
||||
// if (this.forwardLeftButton.visible)
|
||||
// this.forwardLeftButton.visible = false;
|
||||
// if (this.forwardRightButton.visible)
|
||||
// this.forwardRightButton.visible = false;
|
||||
// }
|
||||
// this.updateJumpButtons();
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// protected void init()
|
||||
// {
|
||||
// super.init();
|
||||
// int scaledWidth = this.client.getWindow().getScaledWidth();
|
||||
// int scaledHeight = this.client.getWindow().getScaledHeight();
|
||||
// this.addDrawableChild(new TexturedButtonWidget(scaledWidth / 2 - 20, 0, 20, 20, 0, 106, 20, ButtonWidget.WIDGETS_LOCATION, 256, 256,
|
||||
// btn -> this.client.setScreen(new ChatScreen("")), LiteralText.EMPTY));
|
||||
// this.addDrawableChild(new TexturedButtonWidget(scaledWidth / 2, 0, 20, 20, 0, 0, 20, WIDGETS_LOCATION, 256, 256,
|
||||
// btn -> this.pauseGame(false)));
|
||||
// // Inventory buttons.
|
||||
// int inventoryButtonX = scaledWidth / 2;
|
||||
// int inventoryButtonY = scaledHeight - 16 - 5;
|
||||
// if (this.client.options.mainArm == Arm.LEFT) {
|
||||
// inventoryButtonX = inventoryButtonX - 91 - 24;
|
||||
// } else {
|
||||
// inventoryButtonX = inventoryButtonX + 91 + 4;
|
||||
// }
|
||||
// this.addDrawableChild(new TexturedButtonWidget(inventoryButtonX, inventoryButtonY, 20, 20, 20, 0, 20, WIDGETS_LOCATION, 256, 256,
|
||||
// btn -> {
|
||||
// if (this.client.interactionManager.hasRidingInventory()) {
|
||||
// this.client.player.openRidingInventory();
|
||||
// } else {
|
||||
// this.client.getTutorialManager().onInventoryOpened();
|
||||
// this.client.openScreen(new InventoryScreen(this.client.player));
|
||||
// }
|
||||
// }));
|
||||
// int jumpButtonX, swapHandsX, sneakButtonX;
|
||||
// int sneakButtonY = scaledHeight - 10 - 40 - 5;
|
||||
// if (MidnightControlsConfig.hudSide == HudSide.LEFT) {
|
||||
// jumpButtonX = scaledWidth - 20 - 20;
|
||||
// swapHandsX = jumpButtonX - 5 - 40;
|
||||
// sneakButtonX = 10 + 20 + 5;
|
||||
// } else {
|
||||
// jumpButtonX = 20;
|
||||
// swapHandsX = jumpButtonX + 5 + 40;
|
||||
// sneakButtonX = scaledWidth - 10 - 40 - 5;
|
||||
// }
|
||||
// // Swap items hand.
|
||||
// this.addDrawableChild(new SpruceTexturedButtonWidget(swapHandsX, sneakButtonY, 20, 20, 0, 160, 20, WIDGETS_LOCATION,
|
||||
// (btn, state) -> {
|
||||
// if (state) {
|
||||
// if (!this.client.player.isSpectator()) {
|
||||
// this.client.getNetworkHandler().sendPacket(new PlayerActionC2SPacket(PlayerActionC2SPacket.Action.SWAP_ITEM_WITH_OFFHAND, BlockPos.ORIGIN, Direction.DOWN));
|
||||
// }
|
||||
// }
|
||||
// }));
|
||||
// // Drop
|
||||
// this.addDrawableChild(new SpruceTexturedButtonWidget(swapHandsX, sneakButtonY + 5 + 20, 20, 20, 20, 160, 20, WIDGETS_LOCATION,
|
||||
// (btn, state) -> ((KeyBindingAccessor) this.client.options.keyDrop).midnightcontrols$handlePressState(state)));
|
||||
// // Jump keys
|
||||
// this.addDrawableChild(this.jumpButton = new SpruceTexturedButtonWidget(jumpButtonX, sneakButtonY, 20, 20, 0, 40, 20, WIDGETS_LOCATION,
|
||||
// this::handleJump));
|
||||
// this.addDrawableChild(this.flyButton = new SpruceTexturedButtonWidget(jumpButtonX, sneakButtonY, 20, 20, 20, 40, 20, WIDGETS_LOCATION,
|
||||
// (btn, state) -> {
|
||||
// if (this.flyButtonEnableTicks == 0) this.client..abilities.flying = false;
|
||||
// }));
|
||||
// this.addDrawableChild(this.flyUpButton = new SpruceTexturedButtonWidget(jumpButtonX, sneakButtonY - 5 - 20, 20, 20, 40, 40, 20, WIDGETS_LOCATION,
|
||||
// this::handleJump));
|
||||
// this.addDrawableChild(this.flyDownButton = new SpruceTexturedButtonWidget(jumpButtonX, sneakButtonY + 20 + 5, 20, 20, 60, 40, 20, WIDGETS_LOCATION,
|
||||
// (btn, state) -> ((KeyBindingAccessor) this.client.options.keySneak).midnightcontrols$handlePressState(state)));
|
||||
// this.updateJumpButtons();
|
||||
// // Movements keys
|
||||
// this.addDrawableChild((this.startSneakButton = new SpruceTexturedButtonWidget(sneakButtonX, sneakButtonY, 20, 20, 0, 120, 20, WIDGETS_LOCATION,
|
||||
// (btn, state) -> {
|
||||
// if (state) {
|
||||
// ((KeyBindingAccessor) this.client.options.keySneak).midnightcontrols$handlePressState(true);
|
||||
// this.startSneakButton.setVisible(false);
|
||||
// this.endSneakButton.setVisible(true);
|
||||
// }
|
||||
// })));
|
||||
// this.addDrawableChild((this.endSneakButton = new SpruceTexturedButtonWidget(sneakButtonX, sneakButtonY, 20, 20, 20, 120, 20, WIDGETS_LOCATION,
|
||||
// (btn, state) -> {
|
||||
// if (state) {
|
||||
// ((KeyBindingAccessor) this.client.options.keySneak).midnightcontrols$handlePressState(false);
|
||||
// this.endSneakButton.setVisible(false);
|
||||
// this.startSneakButton.setVisible(true);
|
||||
// }
|
||||
// })));
|
||||
// this.endSneakButton.setVisible(false);
|
||||
// this.addDrawableChild(this.forwardLeftButton = new SpruceTexturedButtonWidget(sneakButtonX - 20 - 5, sneakButtonY - 5 - 20, 20, 20, 80, 80, 20, WIDGETS_LOCATION,
|
||||
// (btn, state) -> {
|
||||
// ((KeyBindingAccessor) this.client.options.keyForward).midnightcontrols$handlePressState(state);
|
||||
// ((KeyBindingAccessor) this.client.options.keyLeft).midnightcontrols$handlePressState(state);
|
||||
// this.updateForwardButtonsState(state);
|
||||
// }));
|
||||
// this.forwardLeftButton.setVisible(false);
|
||||
// this.addDrawableChild(new SpruceTexturedButtonWidget(sneakButtonX, sneakButtonY - 5 - 20, 20, 20, 0, 80, 20, WIDGETS_LOCATION,
|
||||
// (btn, state) -> {
|
||||
// ((KeyBindingAccessor) this.client.options.keyForward).midnightcontrols$handlePressState(state);
|
||||
// this.updateForwardButtonsState(state);
|
||||
// this.forwardLeftButton.setVisible(true);
|
||||
// this.forwardRightButton.setVisible(true);
|
||||
// }));
|
||||
// this.addDrawableChild(this.forwardRightButton = new SpruceTexturedButtonWidget(sneakButtonX + 20 + 5, sneakButtonY - 5 - 20, 20, 20, 100, 80, 20, WIDGETS_LOCATION,
|
||||
// (btn, state) -> {
|
||||
// ((KeyBindingAccessor) this.client.options.keyForward).midnightcontrols$handlePressState(state);
|
||||
// ((KeyBindingAccessor) this.client.options.keyRight).midnightcontrols$handlePressState(state);
|
||||
// this.updateForwardButtonsState(state);
|
||||
// }));
|
||||
// this.forwardRightButton.setVisible(true);
|
||||
// this.addDrawableChild(new SpruceTexturedButtonWidget(sneakButtonX + 20 + 5, sneakButtonY, 20, 20, 20, 80, 20, WIDGETS_LOCATION,
|
||||
// (btn, state) -> ((KeyBindingAccessor) this.client.options.keyRight).midnightcontrols$handlePressState(state)));
|
||||
// this.addDrawableChild(new SpruceTexturedButtonWidget(sneakButtonX, sneakButtonY + 20 + 5, 20, 20, 40, 80, 20, WIDGETS_LOCATION,
|
||||
// (btn, state) -> ((KeyBindingAccessor) this.client.options.keyBack).midnightcontrols$handlePressState(state)));
|
||||
// this.addDrawableChild(new SpruceTexturedButtonWidget(sneakButtonX - 20 - 5, sneakButtonY, 20, 20, 60, 80, 20, WIDGETS_LOCATION,
|
||||
// (btn, state) -> ((KeyBindingAccessor) this.client.options.keyLeft).midnightcontrols$handlePressState(state)));
|
||||
//
|
||||
// this.children().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.client != null && this.client.player != null) {
|
||||
// int centerX = this.width / 2;
|
||||
// if (mouseX >= (double) (centerX - 90) && mouseX <= (double) (centerX + 90)) {
|
||||
// for (int slot = 0; slot < 9; ++slot) {
|
||||
// int slotX = centerX - 90 + slot * 20 + 2;
|
||||
// if (mouseX >= (double) slotX && mouseX <= (double) (slotX + 20)) {
|
||||
// this.client.player.getInventory().selectedSlot = slot;
|
||||
// return true;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// return super.mouseClicked(mouseX, mouseY, button);
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public boolean mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY)
|
||||
// {
|
||||
// if (button == GLFW.GLFW_MOUSE_BUTTON_1 && this.client != null) {
|
||||
// if (deltaY > 0.01)
|
||||
// this.mod.input.handleLook(this.client, GLFW_GAMEPAD_AXIS_RIGHT_Y, (float) Math.abs(deltaY / 5.0), 2);
|
||||
// else if (deltaY < 0.01)
|
||||
// this.mod.input.handleLook(this.client, GLFW_GAMEPAD_AXIS_RIGHT_Y, (float) Math.abs(deltaY / 5.0), 1);
|
||||
//
|
||||
// if (deltaX > 0.01)
|
||||
// this.mod.input.handleLook(this.client, GLFW_GAMEPAD_AXIS_RIGHT_X, (float) Math.abs(deltaX / 5.0), 2);
|
||||
// else if (deltaX < 0.01)
|
||||
// this.mod.input.handleLook(this.client, GLFW_GAMEPAD_AXIS_RIGHT_X, (float) Math.abs(deltaX / 5.0), 1);
|
||||
// }
|
||||
// return super.mouseDragged(mouseX, mouseY, button, deltaX, deltaY);
|
||||
// }
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
|
||||
*
|
||||
* This file is part of midnightcontrols.
|
||||
*
|
||||
* Licensed under the MIT license. For more information,
|
||||
* see the LICENSE file.
|
||||
*/
|
||||
|
||||
package eu.midnightdust.midnightcontrols.client.gui.widget;
|
||||
|
||||
import eu.midnightdust.midnightcontrols.client.controller.ButtonBinding;
|
||||
import eu.midnightdust.midnightcontrols.client.gui.MidnightControlsRenderer;
|
||||
import dev.lambdaurora.spruceui.Position;
|
||||
import dev.lambdaurora.spruceui.SpruceTexts;
|
||||
import dev.lambdaurora.spruceui.widget.AbstractSpruceIconButtonWidget;
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.client.util.math.MatrixStack;
|
||||
import net.minecraft.text.LiteralText;
|
||||
import net.minecraft.text.Text;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Represents a controller button widget.
|
||||
*/
|
||||
public class ControllerButtonWidget extends AbstractSpruceIconButtonWidget {
|
||||
private ButtonBinding binding;
|
||||
private int iconWidth;
|
||||
|
||||
public ControllerButtonWidget(Position position, int width, @NotNull ButtonBinding binding, @NotNull PressAction action) {
|
||||
super(position, width, 20, ButtonBinding.getLocalizedButtonName(binding.getButton()[0]), action);
|
||||
this.binding = binding;
|
||||
}
|
||||
|
||||
public void update() {
|
||||
int length = binding.getButton().length;
|
||||
this.setMessage(this.binding.isNotBound() ? SpruceTexts.NOT_BOUND.copy() :
|
||||
(length > 0 ? ButtonBinding.getLocalizedButtonName(binding.getButton()[0]) : 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;
|
||||
}
|
||||
var size = MidnightControlsRenderer.drawButton(matrices, x, this.getY(), this.binding, MinecraftClient.getInstance());
|
||||
this.iconWidth = size.length();
|
||||
return size.height();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
|
||||
*
|
||||
* This file is part of midnightcontrols.
|
||||
*
|
||||
* Licensed under the MIT license. For more information,
|
||||
* see the LICENSE file.
|
||||
*/
|
||||
|
||||
package eu.midnightdust.midnightcontrols.client.gui.widget;
|
||||
|
||||
import eu.midnightdust.midnightcontrols.client.MidnightControlsClient;
|
||||
import eu.midnightdust.midnightcontrols.client.MidnightControlsConfig;
|
||||
import eu.midnightdust.midnightcontrols.client.controller.ButtonBinding;
|
||||
import eu.midnightdust.midnightcontrols.client.controller.InputManager;
|
||||
import dev.lambdaurora.spruceui.Position;
|
||||
import dev.lambdaurora.spruceui.SpruceTexts;
|
||||
import dev.lambdaurora.spruceui.widget.SpruceButtonWidget;
|
||||
import dev.lambdaurora.spruceui.widget.container.SpruceContainerWidget;
|
||||
import net.minecraft.client.gui.screen.option.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 MidnightControlsClient mod;
|
||||
private ControlsListWidget bindingsListWidget;
|
||||
private SpruceButtonWidget resetButton;
|
||||
public ButtonBinding focusedBinding;
|
||||
public boolean waiting = false;
|
||||
public List<Integer> currentButtons = new ArrayList<>();
|
||||
|
||||
public ControllerControlsWidget(Position position, int width, int height) {
|
||||
super(position, width, height);
|
||||
this.mod = MidnightControlsClient.get();
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
protected void init() {
|
||||
this.addChild(new SpruceButtonWidget(Position.of(this, this.width / 2 - 155, 18), 310, 20,
|
||||
new TranslatableText("midnightcontrols.menu.keyboard_controls"),
|
||||
btn -> this.client.setScreen(new ControlsOptionsScreen(null, this.client.options))));
|
||||
this.bindingsListWidget = new ControlsListWidget(Position.of(this, 0, 43), this.width, this.height - 43 - 35, this);
|
||||
this.addChild(this.bindingsListWidget);
|
||||
this.addChild(this.resetButton = new SpruceButtonWidget(Position.of(this, this.width / 2 - 155, this.height - 29), 150, 20,
|
||||
SpruceTexts.CONTROLS_RESET_ALL,
|
||||
btn -> InputManager.streamBindings().collect(Collectors.toSet()).forEach(binding -> MidnightControlsConfig.setButtonBinding(binding, binding.getDefaultButton()))));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void renderWidget(MatrixStack matrices, int mouseX, int mouseY, float delta) {
|
||||
drawCenteredText(matrices, this.client.textRenderer, new TranslatableText("midnightcontrols.menu.title.controller_controls"),
|
||||
this.getX() + this.width / 2, this.getY() + 4, 16777215);
|
||||
this.resetButton.setActive(InputManager.streamBindings().anyMatch(Predicates.not(ButtonBinding::isDefault)));
|
||||
super.renderWidget(matrices, mouseX, mouseY, delta);
|
||||
}
|
||||
|
||||
public void finishBindingEdit(int... buttons) {
|
||||
if (this.focusedBinding == null) return;
|
||||
MidnightControlsConfig.setButtonBinding(this.focusedBinding, buttons);
|
||||
this.focusedBinding = null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,321 @@
|
||||
/*
|
||||
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
|
||||
*
|
||||
* This file is part of midnightcontrols.
|
||||
*
|
||||
* Licensed under the MIT license. For more information,
|
||||
* see the LICENSE file.
|
||||
*/
|
||||
|
||||
package eu.midnightdust.midnightcontrols.client.gui.widget;
|
||||
|
||||
import eu.midnightdust.midnightcontrols.client.MidnightControlsClient;
|
||||
import eu.midnightdust.midnightcontrols.client.MidnightControlsConfig;
|
||||
import eu.midnightdust.midnightcontrols.client.controller.ButtonBinding;
|
||||
import eu.midnightdust.midnightcontrols.client.controller.ButtonCategory;
|
||||
import eu.midnightdust.midnightcontrols.client.controller.InputManager;
|
||||
import dev.lambdaurora.spruceui.Position;
|
||||
import dev.lambdaurora.spruceui.SpruceTexts;
|
||||
import dev.lambdaurora.spruceui.navigation.NavigationDirection;
|
||||
import dev.lambdaurora.spruceui.navigation.NavigationUtils;
|
||||
import dev.lambdaurora.spruceui.widget.SpruceButtonWidget;
|
||||
import dev.lambdaurora.spruceui.widget.SpruceSeparatorWidget;
|
||||
import dev.lambdaurora.spruceui.widget.SpruceWidget;
|
||||
import dev.lambdaurora.spruceui.widget.container.SpruceEntryListWidget;
|
||||
import dev.lambdaurora.spruceui.widget.container.SpruceParentWidget;
|
||||
import net.fabricmc.api.EnvType;
|
||||
import net.fabricmc.api.Environment;
|
||||
import net.minecraft.client.resource.language.I18n;
|
||||
import net.minecraft.client.util.math.MatrixStack;
|
||||
import net.minecraft.text.LiteralText;
|
||||
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.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Represents a control list widget.
|
||||
*/
|
||||
public class ControlsListWidget extends SpruceEntryListWidget<ControlsListWidget.Entry> {
|
||||
private static final int[] UNBOUND = new int[]{-1};
|
||||
private final ControllerControlsWidget gui;
|
||||
protected int lastIndex = 0;
|
||||
private final int maxTextLength;
|
||||
|
||||
public ControlsListWidget(Position position, int width, int height, ControllerControlsWidget gui) {
|
||||
super(position, width, height, 4, ControlsListWidget.Entry.class);
|
||||
this.gui = gui;
|
||||
this.maxTextLength = InputManager.streamBindings().mapToInt(binding -> this.client.textRenderer.getWidth(binding.getText())).max().orElse(0);
|
||||
|
||||
InputManager.streamCategories()
|
||||
.sorted(Comparator.comparingInt(ButtonCategory::getPriority))
|
||||
.forEach(category -> {
|
||||
this.addEntry(new CategoryEntry(this, category));
|
||||
|
||||
category.getBindings().forEach(binding -> {
|
||||
this.addEntry(new ControlsListWidget.ButtonBindingEntry(this, binding));
|
||||
});
|
||||
});
|
||||
|
||||
this.setAllowOutsideHorizontalNavigation(true);
|
||||
}
|
||||
|
||||
private int getRowWidth() {
|
||||
return this.getWidth() - 6 - this.getRowLeft() * 2;
|
||||
}
|
||||
|
||||
public int getRowLeft() {
|
||||
int baseWidth = 220 + 32;
|
||||
return this.getWidth() / 2 - baseWidth / 2 + 72 - this.maxTextLength;
|
||||
}
|
||||
|
||||
public class ButtonBindingEntry extends Entry implements SpruceParentWidget<SpruceWidget> {
|
||||
private final List<SpruceWidget> children = new ArrayList<>();
|
||||
private @Nullable SpruceWidget focused;
|
||||
private final ButtonBinding binding;
|
||||
private final String bindingName;
|
||||
private final ControllerButtonWidget editButton;
|
||||
private final SpruceButtonWidget resetButton;
|
||||
private final SpruceButtonWidget unbindButton;
|
||||
|
||||
ButtonBindingEntry(@NotNull ControlsListWidget parent, @NotNull ButtonBinding binding) {
|
||||
super(parent);
|
||||
this.binding = binding;
|
||||
this.bindingName = I18n.translate(this.binding.getTranslationKey());
|
||||
this.editButton = new ControllerButtonWidget(Position.of(this, parent.getWidth() / 2 - 8, 0), 110, this.binding, btn -> {
|
||||
gui.focusedBinding = binding;
|
||||
MidnightControlsClient.get().input.beginControlsInput(gui);
|
||||
}) {
|
||||
protected Text getNarrationMessage() {
|
||||
return binding.isNotBound() ? 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 -> MidnightControlsConfig.setButtonBinding(binding, binding.getDefaultButton())) {
|
||||
protected Text getNarrationMessage() {
|
||||
return 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 -> {
|
||||
MidnightControlsConfig.setButtonBinding(binding, UNBOUND);
|
||||
gui.focusedBinding = null;
|
||||
MidnightControlsClient.get().input.beginControlsInput(null);
|
||||
}) {
|
||||
protected Text getNarrationMessage() {
|
||||
return new TranslatableText("midnightcontrols.narrator.unbound", bindingName);
|
||||
}
|
||||
};
|
||||
this.children.add(this.unbindButton);
|
||||
|
||||
this.position.setRelativeX(4);
|
||||
this.width -= 10;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SpruceWidget> children() {
|
||||
return this.children;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable SpruceWidget getFocused() {
|
||||
return this.focused;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFocused(@Nullable SpruceWidget focused) {
|
||||
if (this.focused == focused)
|
||||
return;
|
||||
if (this.focused != null)
|
||||
this.focused.setFocused(false);
|
||||
this.focused = focused;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHeight() {
|
||||
return this.children.stream().mapToInt(SpruceWidget::getHeight).reduce(Integer::max).orElse(0) + 4;
|
||||
}
|
||||
|
||||
/* Input */
|
||||
|
||||
@Override
|
||||
protected boolean onMouseClick(double mouseX, double mouseY, int button) {
|
||||
var it = this.children().iterator();
|
||||
|
||||
SpruceWidget element;
|
||||
do {
|
||||
if (!it.hasNext()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
element = it.next();
|
||||
} while (!element.mouseClicked(mouseX, mouseY, button));
|
||||
|
||||
this.setFocused(element);
|
||||
if (button == GLFW.GLFW_MOUSE_BUTTON_1)
|
||||
this.dragging = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean onMouseRelease(double mouseX, double mouseY, int button) {
|
||||
this.dragging = false;
|
||||
return this.hoveredElement(mouseX, mouseY).filter(element -> element.mouseReleased(mouseX, mouseY, button)).isPresent();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean onMouseDrag(double mouseX, double mouseY, int button, double deltaX, double deltaY) {
|
||||
return this.getFocused() != null && this.dragging && button == GLFW.GLFW_MOUSE_BUTTON_1
|
||||
&& this.getFocused().mouseDragged(mouseX, mouseY, button, deltaX, deltaY);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean onKeyPress(int keyCode, int scanCode, int modifiers) {
|
||||
return this.focused != null && this.focused.keyPressed(keyCode, scanCode, modifiers);
|
||||
}
|
||||
|
||||
/* Navigation */
|
||||
|
||||
@Override
|
||||
public void setFocused(boolean focused) {
|
||||
super.setFocused(focused);
|
||||
if (!focused) {
|
||||
this.setFocused(null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onNavigation(@NotNull NavigationDirection direction, boolean tab) {
|
||||
if (this.requiresCursor()) return false;
|
||||
if (!tab && direction.isVertical()) {
|
||||
if (this.isFocused()) {
|
||||
this.setFocused(null);
|
||||
return false;
|
||||
}
|
||||
int lastIndex = this.parent.lastIndex;
|
||||
if (lastIndex >= this.children.size())
|
||||
lastIndex = this.children.size() - 1;
|
||||
if (!this.children.get(lastIndex).onNavigation(direction, tab))
|
||||
return false;
|
||||
this.setFocused(this.children.get(lastIndex));
|
||||
return true;
|
||||
}
|
||||
|
||||
boolean result = NavigationUtils.tryNavigate(direction, tab, this.children, this.focused, this::setFocused, true);
|
||||
if (result) {
|
||||
this.setFocused(true);
|
||||
if (direction.isHorizontal() && this.getFocused() != null) {
|
||||
this.parent.lastIndex = this.children.indexOf(this.getFocused());
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/* Rendering */
|
||||
|
||||
@Override
|
||||
protected void renderWidget(MatrixStack matrices, int mouseX, int mouseY, float delta) {
|
||||
boolean focused = gui.focusedBinding == this.binding;
|
||||
|
||||
var textRenderer = ControlsListWidget.this.client.textRenderer;
|
||||
int height = this.getHeight();
|
||||
//float textX = (float) (this.getX() + 70 - ControlsListWidget.this.maxTextLength);
|
||||
int textY = this.getY() + height / 2;
|
||||
textRenderer.draw(matrices, this.bindingName, this.getX(), (float) (textY - 9 / 2), 16777215);
|
||||
|
||||
this.resetButton.setVisible(!focused);
|
||||
this.unbindButton.setVisible(focused);
|
||||
this.resetButton.setActive(!this.binding.isDefault());
|
||||
|
||||
this.editButton.update();
|
||||
if (focused) {
|
||||
var text = 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)) {
|
||||
var text = this.editButton.getMessage().copy();
|
||||
this.editButton.setMessage(text.formatted(Formatting.RED));
|
||||
} else if (this.binding.isNotBound()) {
|
||||
var text = this.editButton.getMessage().copy();
|
||||
this.editButton.setMessage(text.formatted(Formatting.GOLD));
|
||||
}
|
||||
|
||||
this.children.forEach(widget -> widget.render(matrices, mouseX, mouseY, delta));
|
||||
}
|
||||
}
|
||||
|
||||
public static class CategoryEntry extends Entry {
|
||||
private final SpruceSeparatorWidget separatorWidget;
|
||||
|
||||
protected CategoryEntry(ControlsListWidget parent, ButtonCategory category) {
|
||||
super(parent);
|
||||
this.separatorWidget = new SpruceSeparatorWidget(Position.of(this, 2, 0), this.getWidth() - 4,
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
|
||||
*
|
||||
* This file is part of midnightcontrols.
|
||||
*
|
||||
* Licensed under the MIT license. For more information,
|
||||
* see the LICENSE file.
|
||||
*/
|
||||
|
||||
package eu.midnightdust.midnightcontrols.client.mixin;
|
||||
|
||||
import net.minecraft.advancement.Advancement;
|
||||
import net.minecraft.client.gui.screen.advancement.AdvancementTab;
|
||||
import net.minecraft.client.gui.screen.advancement.AdvancementsScreen;
|
||||
import net.minecraft.client.network.ClientAdvancementManager;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Represents an accessor of {@link AdvancementsScreen}.
|
||||
*/
|
||||
@Mixin(AdvancementsScreen.class)
|
||||
public interface AdvancementsScreenAccessor {
|
||||
@Accessor("advancementHandler")
|
||||
ClientAdvancementManager getAdvancementManager();
|
||||
|
||||
@Accessor("tabs")
|
||||
Map<Advancement, AdvancementTab> getTabs();
|
||||
|
||||
@Accessor("selectedTab")
|
||||
AdvancementTab getSelectedTab();
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
|
||||
*
|
||||
* This file is part of midnightcontrols.
|
||||
*
|
||||
* Licensed under the MIT license. For more information,
|
||||
* see the LICENSE file.
|
||||
*/
|
||||
|
||||
package eu.midnightdust.midnightcontrols.client.mixin;
|
||||
|
||||
import net.minecraft.client.gui.widget.ClickableWidget;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||
|
||||
@Mixin(ClickableWidget.class)
|
||||
public interface ClickableWidgetAccessor {
|
||||
@Accessor("height")
|
||||
int getHeight();
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
|
||||
*
|
||||
* This file is part of midnightcontrols.
|
||||
*
|
||||
* Licensed under the MIT license. For more information,
|
||||
* see the LICENSE file.
|
||||
*/
|
||||
|
||||
package eu.midnightdust.midnightcontrols.client.mixin;
|
||||
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import eu.midnightdust.midnightcontrols.client.MidnightControlsClient;
|
||||
import eu.midnightdust.midnightcontrols.client.MidnightControlsConfig;
|
||||
import eu.midnightdust.midnightcontrols.client.controller.MovementHandler;
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.client.input.Input;
|
||||
import net.minecraft.client.network.AbstractClientPlayerEntity;
|
||||
import net.minecraft.client.network.ClientPlayerEntity;
|
||||
import net.minecraft.client.world.ClientWorld;
|
||||
import net.minecraft.entity.MovementType;
|
||||
import net.minecraft.util.math.Vec3d;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Injects the anti fly drifting feature.
|
||||
*/
|
||||
@Mixin(ClientPlayerEntity.class)
|
||||
public abstract class ClientPlayerEntityMixin extends AbstractClientPlayerEntity {
|
||||
private boolean midnightcontrols$driftingPrevented = false;
|
||||
|
||||
@Shadow
|
||||
protected abstract boolean hasMovementInput();
|
||||
|
||||
@Shadow
|
||||
@Final
|
||||
protected MinecraftClient client;
|
||||
|
||||
@Shadow
|
||||
public Input input;
|
||||
|
||||
@Shadow
|
||||
protected abstract boolean isCamera();
|
||||
|
||||
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 onMove(MovementType type, Vec3d movement, CallbackInfo ci) {
|
||||
var mod = MidnightControlsClient.get();
|
||||
if (type == MovementType.SELF) {
|
||||
if (this.getAbilities().flying && (!MidnightControlsConfig.flyDrifting || !MidnightControlsConfig.verticalFlyDrifting)) {
|
||||
if (!this.hasMovementInput()) {
|
||||
if (!this.midnightcontrols$driftingPrevented) {
|
||||
if (!MidnightControlsConfig.flyDrifting)
|
||||
this.setVelocity(this.getVelocity().multiply(0, 1.0, 0));
|
||||
}
|
||||
this.midnightcontrols$driftingPrevented = true;
|
||||
} else
|
||||
this.midnightcontrols$driftingPrevented = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "tickMovement", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/input/Input;tick(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 onTickMovement(CallbackInfo ci) {
|
||||
if (this.getAbilities().flying && this.isCamera()) {
|
||||
if (MidnightControlsConfig.verticalFlyDrifting)
|
||||
return;
|
||||
int moving = 0;
|
||||
if (this.input.sneaking) {
|
||||
--moving;
|
||||
}
|
||||
|
||||
if (this.input.jumping) {
|
||||
++moving;
|
||||
}
|
||||
|
||||
if (moving == 0) {
|
||||
this.setVelocity(this.getVelocity().multiply(1.0, 0.0, 1.0));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
|
||||
*
|
||||
* This file is part of midnightcontrols.
|
||||
*
|
||||
* Licensed under the MIT license. For more information,
|
||||
* see the LICENSE file.
|
||||
*/
|
||||
|
||||
package eu.midnightdust.midnightcontrols.client.mixin;
|
||||
|
||||
import eu.midnightdust.midnightcontrols.client.gui.MidnightControlsSettingsScreen;
|
||||
import net.minecraft.client.gui.screen.Screen;
|
||||
import net.minecraft.client.gui.screen.option.ControlsOptionsScreen;
|
||||
import net.minecraft.client.gui.screen.option.GameOptionsScreen;
|
||||
import net.minecraft.client.gui.widget.ButtonWidget;
|
||||
import net.minecraft.client.option.GameOptions;
|
||||
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.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
/**
|
||||
* Injects the new controls settings button.
|
||||
*/
|
||||
@Mixin(ControlsOptionsScreen.class)
|
||||
public class ControlsOptionsScreenMixin extends GameOptionsScreen {
|
||||
public ControlsOptionsScreenMixin(Screen parent, GameOptions gameOptions, Text text) {
|
||||
super(parent, gameOptions, text);
|
||||
}
|
||||
@Inject(method = "init", at = @At(value = "INVOKE", ordinal = 4, shift = At.Shift.AFTER, target = "Lnet/minecraft/client/gui/screen/option/ControlsOptionsScreen;addDrawableChild(Lnet/minecraft/client/gui/Element;)Lnet/minecraft/client/gui/Element;"))
|
||||
private void addControllerButton(CallbackInfo ci) {
|
||||
int i = this.width / 2 - 155;
|
||||
int j = i + 160;
|
||||
int k = this.height / 6 - 12 + 48;;
|
||||
this.addDrawableChild(new ButtonWidget(j, k, 150, 20, new TranslatableText("midnightcontrols.menu.title.controller").append("..."), (button) -> {
|
||||
this.client.setScreen(new MidnightControlsSettingsScreen(this, false));
|
||||
}));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
|
||||
*
|
||||
* This file is part of midnightcontrols.
|
||||
*
|
||||
* Licensed under the MIT license. For more information,
|
||||
* see the LICENSE file.
|
||||
*/
|
||||
|
||||
package eu.midnightdust.midnightcontrols.client.mixin;
|
||||
|
||||
import net.minecraft.client.gui.screen.ingame.CreativeInventoryScreen;
|
||||
import net.minecraft.item.ItemGroup;
|
||||
import net.minecraft.screen.slot.Slot;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||
import org.spongepowered.asm.mixin.gen.Invoker;
|
||||
|
||||
/**
|
||||
* Represents an accessor to CreativeInventoryScreen.
|
||||
*/
|
||||
@Mixin(CreativeInventoryScreen.class)
|
||||
public interface CreativeInventoryScreenAccessor {
|
||||
/**
|
||||
* Gets the selected tab.
|
||||
*
|
||||
* @return the selected tab index
|
||||
*/
|
||||
@Accessor("selectedTab")
|
||||
int getSelectedTab();
|
||||
|
||||
/**
|
||||
* Sets the selected tab.
|
||||
*
|
||||
* @param group the tab's item group
|
||||
*/
|
||||
@Invoker("setSelectedTab")
|
||||
void midnightcontrols$setSelectedTab(@NotNull ItemGroup group);
|
||||
|
||||
/**
|
||||
* Returns whether the slot belongs to the creative inventory or not.
|
||||
*
|
||||
* @param slot the slot to check
|
||||
* @return true if the slot is from the creative inventory, else false
|
||||
*/
|
||||
@Invoker("isCreativeInventorySlot")
|
||||
boolean midnightcontrols$isCreativeInventorySlot(@Nullable Slot slot);
|
||||
|
||||
/**
|
||||
* Returns whether the current tab has a scrollbar or not.
|
||||
*
|
||||
* @return true if the current tab has a scrollbar, else false
|
||||
*/
|
||||
@Invoker("hasScrollbar")
|
||||
boolean midnightcontrols$hasScrollbar();
|
||||
}
|
||||
@@ -1,21 +1,20 @@
|
||||
/*
|
||||
* Copyright © 2019 LambdAurora <aurora42lambda@gmail.com>
|
||||
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
|
||||
*
|
||||
* This file is part of LambdaControls.
|
||||
* This file is part of midnightcontrols.
|
||||
*
|
||||
* Licensed under the MIT license. For more information,
|
||||
* see the LICENSE file.
|
||||
*/
|
||||
|
||||
package me.lambdaurora.lambdacontrols.mixin;
|
||||
package eu.midnightdust.midnightcontrols.client.mixin;
|
||||
|
||||
import net.minecraft.client.gui.widget.EntryListWidget;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Invoker;
|
||||
|
||||
@Mixin(EntryListWidget.class)
|
||||
public interface EntryListWidgetAccessor
|
||||
{
|
||||
public interface EntryListWidgetAccessor {
|
||||
@Invoker("moveSelection")
|
||||
void move_selection(int amount);
|
||||
void midnightcontrols$moveSelection(EntryListWidget.MoveDirection direction);
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
|
||||
*
|
||||
* This file is part of midnightcontrols.
|
||||
*
|
||||
* Licensed under the MIT license. For more information,
|
||||
* see the LICENSE file.
|
||||
*/
|
||||
|
||||
package eu.midnightdust.midnightcontrols.client.mixin;
|
||||
|
||||
import net.minecraft.client.option.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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
|
||||
*
|
||||
* This file is part of midnightcontrols.
|
||||
*
|
||||
* Licensed under the MIT license. For more information,
|
||||
* see the LICENSE file.
|
||||
*/
|
||||
|
||||
package eu.midnightdust.midnightcontrols.client.mixin;
|
||||
|
||||
import eu.midnightdust.midnightcontrols.ControlsMode;
|
||||
import eu.midnightdust.midnightcontrols.client.MidnightControlsClient;
|
||||
import eu.midnightdust.midnightcontrols.client.MidnightControlsConfig;
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.client.render.GameRenderer;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
@Mixin(GameRenderer.class)
|
||||
public class GameRendererMixin {
|
||||
@Shadow
|
||||
@Final
|
||||
private MinecraftClient client;
|
||||
|
||||
@Inject(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/Mouse;getX()D"))
|
||||
private void onRender(float tickDelta, long startTime, boolean fullRender, CallbackInfo ci) {
|
||||
if (this.client.currentScreen != null && MidnightControlsConfig.controlsMode == ControlsMode.CONTROLLER)
|
||||
MidnightControlsClient.get().input.onPreRenderScreen(this.client, this.client.currentScreen);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
|
||||
*
|
||||
* This file is part of midnightcontrols.
|
||||
*
|
||||
* Licensed under the MIT license. For more information,
|
||||
* see the LICENSE file.
|
||||
*/
|
||||
|
||||
package eu.midnightdust.midnightcontrols.client.mixin;
|
||||
|
||||
import eu.midnightdust.midnightcontrols.ControlsMode;
|
||||
import eu.midnightdust.midnightcontrols.client.MidnightControlsConfig;
|
||||
import eu.midnightdust.midnightcontrols.client.MidnightInput;
|
||||
import eu.midnightdust.midnightcontrols.client.compat.MidnightControlsCompat;
|
||||
import eu.midnightdust.midnightcontrols.client.gui.MidnightControlsRenderer;
|
||||
import eu.midnightdust.midnightcontrols.client.util.HandledScreenAccessor;
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.client.gui.screen.ingame.HandledScreen;
|
||||
import net.minecraft.client.util.math.MatrixStack;
|
||||
import net.minecraft.screen.slot.Slot;
|
||||
import net.minecraft.screen.slot.SlotActionType;
|
||||
import 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 midnightcontrols$getSlotAt(double posX, double posY);
|
||||
|
||||
@Invoker("isClickOutsideBounds")
|
||||
public abstract boolean midnightcontrols$isClickOutsideBounds(double mouseX, double mouseY, int x, int y, int button);
|
||||
|
||||
|
||||
@Invoker("onMouseClick")
|
||||
public abstract void midnightcontrols$onMouseClick(@Nullable Slot slot, int slotId, int clickData, SlotActionType actionType);
|
||||
|
||||
@Inject(method = "render", at = @At("RETURN"))
|
||||
public void onRender(MatrixStack matrices, int mouseX, int mouseY, float delta, CallbackInfo ci) {
|
||||
if (MidnightControlsConfig.controlsMode == ControlsMode.CONTROLLER) {
|
||||
var client = MinecraftClient.getInstance();
|
||||
int x = 2, y = client.getWindow().getScaledHeight() - 2 - MidnightControlsRenderer.ICON_SIZE;
|
||||
|
||||
x = MidnightControlsRenderer.drawButtonTip(matrices, x, y, new int[]{GLFW.GLFW_GAMEPAD_BUTTON_A}, "midnightcontrols.action.pickup_all", true, client) + 2;
|
||||
x = MidnightControlsRenderer.drawButtonTip(matrices, x, y, new int[]{GLFW.GLFW_GAMEPAD_BUTTON_B}, "midnightcontrols.action.exit", true, client) + 2;
|
||||
if (MidnightControlsCompat.isReiPresent()) {
|
||||
x = 2;
|
||||
y -= 24;
|
||||
}
|
||||
x = MidnightControlsRenderer.drawButtonTip(matrices, x, y, new int[]{GLFW.GLFW_GAMEPAD_BUTTON_X}, "midnightcontrols.action.pickup", true, client) + 2;
|
||||
MidnightControlsRenderer.drawButtonTip(matrices, x, y, new int[]{GLFW.GLFW_GAMEPAD_BUTTON_Y}, "midnightcontrols.action.quick_move", true, client);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
|
||||
*
|
||||
* This file is part of midnightcontrols.
|
||||
*
|
||||
* Licensed under the MIT license. For more information,
|
||||
* see the LICENSE file.
|
||||
*/
|
||||
|
||||
package eu.midnightdust.midnightcontrols.client.mixin;
|
||||
|
||||
import eu.midnightdust.midnightcontrols.client.util.KeyBindingAccessor;
|
||||
import net.minecraft.client.option.KeyBinding;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
|
||||
@Mixin(KeyBinding.class)
|
||||
public class KeyBindingMixin implements KeyBindingAccessor {
|
||||
@Shadow
|
||||
private int timesPressed;
|
||||
|
||||
@Shadow
|
||||
private boolean pressed;
|
||||
|
||||
@Override
|
||||
public boolean midnightcontrols$press() {
|
||||
boolean oldPressed = this.pressed;
|
||||
if (!this.pressed)
|
||||
this.pressed = true;
|
||||
++this.timesPressed;
|
||||
return oldPressed != this.pressed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean midnightcontrols$unpress() {
|
||||
if (this.pressed) {
|
||||
this.pressed = false;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
/*
|
||||
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
|
||||
*
|
||||
* This file is part of midnightcontrols.
|
||||
*
|
||||
* Licensed under the MIT license. For more information,
|
||||
* see the LICENSE file.
|
||||
*/
|
||||
|
||||
package eu.midnightdust.midnightcontrols.client.mixin;
|
||||
|
||||
import eu.midnightdust.midnightcontrols.MidnightControls;
|
||||
import eu.midnightdust.midnightcontrols.MidnightControlsFeature;
|
||||
import eu.midnightdust.midnightcontrols.client.MidnightControlsClient;
|
||||
import eu.midnightdust.midnightcontrols.client.MidnightInput;
|
||||
import eu.midnightdust.midnightcontrols.client.gui.MidnightControlsRenderer;
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.client.gui.screen.Screen;
|
||||
import net.minecraft.client.network.ClientPlayerEntity;
|
||||
import net.minecraft.client.network.ClientPlayerInteractionManager;
|
||||
import net.minecraft.client.render.GameRenderer;
|
||||
import net.minecraft.client.util.math.MatrixStack;
|
||||
import net.minecraft.client.world.ClientWorld;
|
||||
import net.minecraft.item.BlockItem;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.util.ActionResult;
|
||||
import net.minecraft.util.Hand;
|
||||
import net.minecraft.util.hit.BlockHitResult;
|
||||
import net.minecraft.util.hit.HitResult;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.util.math.Direction;
|
||||
import net.minecraft.util.math.Vec3d;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
|
||||
|
||||
@Mixin(MinecraftClient.class)
|
||||
public abstract class MinecraftClientMixin {
|
||||
@Shadow
|
||||
@Nullable
|
||||
public HitResult crosshairTarget;
|
||||
|
||||
@Shadow
|
||||
@Nullable
|
||||
public ClientPlayerEntity player;
|
||||
|
||||
@Shadow
|
||||
@Nullable
|
||||
public ClientPlayerInteractionManager interactionManager;
|
||||
|
||||
@Shadow
|
||||
@Nullable
|
||||
public ClientWorld world;
|
||||
|
||||
@Shadow
|
||||
@Final
|
||||
public GameRenderer gameRenderer;
|
||||
|
||||
@Shadow
|
||||
private int itemUseCooldown;
|
||||
|
||||
private BlockPos midnightcontrols$lastTargetPos;
|
||||
private Vec3d midnightcontrols$lastPos;
|
||||
private Direction midnightcontrols$lastTargetSide;
|
||||
|
||||
@Inject(method = "<init>", at = @At("RETURN"))
|
||||
private void onInit(CallbackInfo ci) {
|
||||
MidnightControlsClient.get().onMcInit((MinecraftClient) (Object) this);
|
||||
}
|
||||
|
||||
@Inject(method = "tick", at = @At("HEAD"))
|
||||
private void onStartTick(CallbackInfo ci) {
|
||||
if (this.player == null)
|
||||
return;
|
||||
|
||||
if (!MidnightControlsFeature.FAST_BLOCK_PLACING.isAvailable())
|
||||
return;
|
||||
if (this.midnightcontrols$lastPos == null)
|
||||
this.midnightcontrols$lastPos = this.player.getPos();
|
||||
|
||||
int cooldown = this.itemUseCooldown;
|
||||
BlockHitResult hitResult;
|
||||
if (this.crosshairTarget != null && this.crosshairTarget.getType() == HitResult.Type.BLOCK && this.player.getAbilities().flying) {
|
||||
hitResult = (BlockHitResult) this.crosshairTarget;
|
||||
var targetPos = hitResult.getBlockPos();
|
||||
var side = hitResult.getSide();
|
||||
|
||||
boolean sidewaysBlockPlacing = this.midnightcontrols$lastTargetPos == null || !targetPos.equals(this.midnightcontrols$lastTargetPos.offset(this.midnightcontrols$lastTargetSide));
|
||||
boolean backwardsBlockPlacing = this.player.input.movementForward < 0.0f && (this.midnightcontrols$lastTargetPos == null || targetPos.equals(this.midnightcontrols$lastTargetPos.offset(this.midnightcontrols$lastTargetSide)));
|
||||
|
||||
if (cooldown > 1
|
||||
&& !targetPos.equals(this.midnightcontrols$lastTargetPos)
|
||||
&& (sidewaysBlockPlacing || backwardsBlockPlacing)) {
|
||||
this.itemUseCooldown = 1;
|
||||
}
|
||||
|
||||
this.midnightcontrols$lastTargetPos = targetPos.toImmutable();
|
||||
this.midnightcontrols$lastTargetSide = side;
|
||||
}
|
||||
// Removed front placing sprinting as way too cheaty.
|
||||
/*else if (this.player.isSprinting()) {
|
||||
hitResult = midnightcontrolsClient.get().reacharound.getLastReacharoundResult();
|
||||
if (hitResult != null) {
|
||||
if (cooldown > 0)
|
||||
this.itemUseCooldown = 0;
|
||||
}
|
||||
}*/
|
||||
this.midnightcontrols$lastPos = this.player.getPos();
|
||||
}
|
||||
|
||||
@Inject(method = "render", at = @At("HEAD"))
|
||||
private void onRender(boolean fullRender, CallbackInfo ci) {
|
||||
MidnightControlsClient.get().onRender((MinecraftClient) (Object) (this));
|
||||
}
|
||||
|
||||
@Inject(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/GameRenderer;render(FJZ)V", shift = At.Shift.AFTER))
|
||||
private void renderVirtualCursor(boolean fullRender, CallbackInfo ci) {
|
||||
MidnightControlsRenderer.renderVirtualCursor(new MatrixStack(), (MinecraftClient) (Object) this);
|
||||
}
|
||||
|
||||
@Inject(method = "doItemUse()V", at = @At(value = "INVOKE", target = "Lnet/minecraft/util/hit/HitResult;getType()Lnet/minecraft/util/hit/HitResult$Type;"), locals = LocalCapture.CAPTURE_FAILEXCEPTION, cancellable = true)
|
||||
private void onItemUse(CallbackInfo ci, Hand[] hands, int handCount, int handIndex, Hand hand, ItemStack stackInHand) {
|
||||
var mod = MidnightControlsClient.get();
|
||||
if (!stackInHand.isEmpty() && this.player.getPitch(0.f) > 35.0F && mod.reacharound.isReacharoundAvailable()) {
|
||||
if (this.crosshairTarget != null && this.crosshairTarget.getType() == HitResult.Type.MISS && this.player.isOnGround()) {
|
||||
if (!stackInHand.isEmpty() && stackInHand.getItem() instanceof BlockItem) {
|
||||
var hitResult = mod.reacharound.getLastReacharoundResult();
|
||||
|
||||
if (hitResult == null)
|
||||
return;
|
||||
|
||||
hitResult = mod.reacharound.withSideForReacharound(hitResult, stackInHand);
|
||||
|
||||
int previousStackCount = stackInHand.getCount();
|
||||
var result = this.interactionManager.interactBlock(this.player, 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
|
||||
*
|
||||
* This file is part of midnightcontrols.
|
||||
*
|
||||
* Licensed under the MIT license. For more information,
|
||||
* see the LICENSE file.
|
||||
*/
|
||||
|
||||
package eu.midnightdust.midnightcontrols.client.mixin;
|
||||
|
||||
import eu.midnightdust.midnightcontrols.ControlsMode;
|
||||
import eu.midnightdust.midnightcontrols.client.MidnightControlsClient;
|
||||
import eu.midnightdust.midnightcontrols.client.MidnightControlsConfig;
|
||||
import eu.midnightdust.midnightcontrols.client.util.MouseAccessor;
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.client.Mouse;
|
||||
import net.minecraft.client.gui.screen.Screen;
|
||||
import org.lwjgl.glfw.GLFW;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.gen.Invoker;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
|
||||
/**
|
||||
* Adds extra access to the mouse.
|
||||
*/
|
||||
@Mixin(Mouse.class)
|
||||
public abstract class MouseMixin implements MouseAccessor {
|
||||
@Shadow
|
||||
@Final
|
||||
private MinecraftClient client;
|
||||
|
||||
@Invoker("onCursorPos")
|
||||
public abstract void midnightcontrols$onCursorPos(long window, double x, double y);
|
||||
|
||||
@Inject(method = "onMouseButton", at = @At(value = "TAIL"))
|
||||
private void onMouseBackButton(long window, int button, int action, int mods, CallbackInfo ci) {
|
||||
if (action == 1 && button == GLFW.GLFW_MOUSE_BUTTON_4 && MinecraftClient.getInstance().currentScreen != null) {
|
||||
if (MidnightControlsClient.get().input.tryGoBack(MinecraftClient.getInstance().currentScreen)) {
|
||||
action = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "isCursorLocked", at = @At("HEAD"), cancellable = true)
|
||||
private void isCursorLocked(CallbackInfoReturnable<Boolean> ci) {
|
||||
if (this.client.currentScreen == null) {
|
||||
if (MidnightControlsConfig.controlsMode == ControlsMode.CONTROLLER && MidnightControlsConfig.virtualMouse) {
|
||||
ci.setReturnValue(true);
|
||||
ci.cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "lockCursor", at = @At("HEAD"), cancellable = true)
|
||||
private void onCursorLocked(CallbackInfo ci) {
|
||||
if (/*config.getControlsMode() == ControlsMode.TOUCHSCREEN
|
||||
||*/ (MidnightControlsConfig.controlsMode == ControlsMode.CONTROLLER && MidnightControlsConfig.virtualMouse))
|
||||
ci.cancel();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
|
||||
*
|
||||
* This file is part of midnightcontrols.
|
||||
*
|
||||
* Licensed under the MIT license. For more information,
|
||||
* see the LICENSE file.
|
||||
*/
|
||||
|
||||
package eu.midnightdust.midnightcontrols.client.mixin;
|
||||
|
||||
import net.minecraft.client.gui.screen.recipebook.RecipeBookWidget;
|
||||
import net.minecraft.client.gui.screen.recipebook.RecipeGroupButtonWidget;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||
import org.spongepowered.asm.mixin.gen.Invoker;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Mixin(RecipeBookWidget.class)
|
||||
public interface RecipeBookWidgetAccessor {
|
||||
@Accessor("tabButtons")
|
||||
List<RecipeGroupButtonWidget> getTabButtons();
|
||||
|
||||
@Accessor("currentTab")
|
||||
RecipeGroupButtonWidget getCurrentTab();
|
||||
|
||||
@Accessor("currentTab")
|
||||
void setCurrentTab(RecipeGroupButtonWidget currentTab);
|
||||
|
||||
@Invoker("refreshResults")
|
||||
void midnightcontrols$refreshResults(boolean resetCurrentPage);
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
|
||||
*
|
||||
* This file is part of midnightcontrols.
|
||||
*
|
||||
* Licensed under the MIT license. For more information,
|
||||
* see the LICENSE file.
|
||||
*/
|
||||
|
||||
package eu.midnightdust.midnightcontrols.client.mixin;
|
||||
|
||||
import eu.midnightdust.midnightcontrols.client.MidnightControlsClient;
|
||||
import eu.midnightdust.midnightcontrols.client.MidnightControlsConfig;
|
||||
import net.minecraft.block.ShapeContext;
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.client.render.*;
|
||||
import net.minecraft.client.util.math.MatrixStack;
|
||||
import net.minecraft.client.world.ClientWorld;
|
||||
import net.minecraft.item.BlockItem;
|
||||
import net.minecraft.item.ItemPlacementContext;
|
||||
import net.minecraft.item.ItemUsageContext;
|
||||
import net.minecraft.util.Hand;
|
||||
import net.minecraft.util.hit.HitResult;
|
||||
import net.minecraft.util.math.Matrix4f;
|
||||
import net.minecraft.util.shape.VoxelShape;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
/**
|
||||
* Represents a mixin to WorldRenderer.
|
||||
* <p>
|
||||
* Handles the rendering of the block outline of the reach-around features.
|
||||
*/
|
||||
@Mixin(WorldRenderer.class)
|
||||
public abstract class WorldRendererMixin {
|
||||
@Shadow
|
||||
@Final
|
||||
private MinecraftClient client;
|
||||
|
||||
@Shadow
|
||||
private ClientWorld world;
|
||||
|
||||
@Shadow
|
||||
@Final
|
||||
private BufferBuilderStorage bufferBuilders;
|
||||
|
||||
@Shadow
|
||||
private static void drawShapeOutline(MatrixStack matrixStack, VertexConsumer vertexConsumer, VoxelShape voxelShape, double d, double e, double f, float g, float h, float i, float j) {
|
||||
}
|
||||
|
||||
@Inject(
|
||||
method = "render",
|
||||
at = @At(
|
||||
value = "FIELD",
|
||||
target = "Lnet/minecraft/client/MinecraftClient;crosshairTarget:Lnet/minecraft/util/hit/HitResult;",
|
||||
ordinal = 1,
|
||||
shift = At.Shift.AFTER
|
||||
)
|
||||
)
|
||||
private void onOutlineRender(MatrixStack matrices, float tickDelta, long limitTime, boolean renderBlockOutline, Camera camera, GameRenderer gameRenderer,
|
||||
LightmapTextureManager lightmapTextureManager, Matrix4f matrix4f, CallbackInfo ci) {
|
||||
if (this.client.crosshairTarget == null || this.client.crosshairTarget.getType() != HitResult.Type.MISS || !MidnightControlsConfig.shouldRenderReacharoundOutline)
|
||||
return;
|
||||
var result = MidnightControlsClient.get().reacharound.getLastReacharoundResult();
|
||||
if (result == null)
|
||||
return;
|
||||
var blockPos = result.getBlockPos();
|
||||
if (this.world.getWorldBorder().contains(blockPos)) {
|
||||
var stack = this.client.player.getStackInHand(Hand.MAIN_HAND);
|
||||
if (stack == null || !(stack.getItem() instanceof BlockItem))
|
||||
return;
|
||||
|
||||
var mod = MidnightControlsClient.get();
|
||||
|
||||
var block = ((BlockItem) stack.getItem()).getBlock();
|
||||
result = mod.reacharound.withSideForReacharound(result, block);
|
||||
var context = new ItemPlacementContext(new ItemUsageContext(this.client.player, Hand.MAIN_HAND, result));
|
||||
|
||||
var placementState = block.getPlacementState(context);
|
||||
if (placementState == null)
|
||||
return;
|
||||
var pos = camera.getPos();
|
||||
|
||||
var outlineShape = placementState.getOutlineShape(this.client.world, blockPos, ShapeContext.of(camera.getFocusedEntity()));
|
||||
int[] color = MidnightControlsConfig.reacharoundOutlineColor;
|
||||
|
||||
var vertexConsumer = this.bufferBuilders.getEntityVertexConsumers().getBuffer(RenderLayer.getLines());
|
||||
drawShapeOutline(matrices, vertexConsumer, outlineShape,
|
||||
(double) blockPos.getX() - pos.getX(), (double) blockPos.getY() - pos.getY(), (double) blockPos.getZ() - pos.getZ(),
|
||||
color[0] / 255.f, color[1] / 255.f, color[2] / 255.f, color[3] / 255.f);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
|
||||
*
|
||||
* This file is part of midnightcontrols.
|
||||
*
|
||||
* Licensed under the MIT license. For more information,
|
||||
* see the LICENSE file.
|
||||
*/
|
||||
|
||||
package eu.midnightdust.midnightcontrols.client.ring;
|
||||
|
||||
import com.electronwill.nightconfig.core.Config;
|
||||
import net.minecraft.client.font.TextRenderer;
|
||||
import net.minecraft.client.util.math.MatrixStack;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class DummyRingAction extends RingAction {
|
||||
public DummyRingAction(@NotNull Config config) {
|
||||
super(config);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String getName() {
|
||||
return "dummy";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAction(@NotNull RingButtonMode mode) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawIcon(@NotNull MatrixStack matrices, @NotNull TextRenderer textRenderer, int x, int y, boolean hovered) {
|
||||
drawCenteredText(matrices, textRenderer, this.getName(), x + 25, y + 25 - textRenderer.fontHeight / 2, 0xffffff);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
|
||||
*
|
||||
* This file is part of midnightcontrols.
|
||||
*
|
||||
* Licensed under the MIT license. For more information,
|
||||
* see the LICENSE file.
|
||||
*/
|
||||
|
||||
package eu.midnightdust.midnightcontrols.client.ring;
|
||||
|
||||
import com.electronwill.nightconfig.core.Config;
|
||||
import eu.midnightdust.midnightcontrols.client.util.KeyBindingAccessor;
|
||||
import net.minecraft.client.font.TextRenderer;
|
||||
import net.minecraft.client.gui.screen.Screen;
|
||||
import net.minecraft.client.option.KeyBinding;
|
||||
import net.minecraft.client.util.math.MatrixStack;
|
||||
import net.minecraft.text.TranslatableText;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class KeyBindingRingAction extends RingAction {
|
||||
public static final Factory FACTORY = new Factory();
|
||||
public final KeyBinding binding;
|
||||
|
||||
public KeyBindingRingAction(@NotNull Config config, @NotNull KeyBinding binding) {
|
||||
super(config);
|
||||
this.binding = binding;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String getName() {
|
||||
return this.binding.getTranslationKey();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAction(@NotNull RingButtonMode mode) {
|
||||
KeyBindingAccessor accessor = (KeyBindingAccessor) this.binding;
|
||||
switch (mode) {
|
||||
case PRESS, HOLD -> accessor.midnightcontrols$handlePressState(this.activated);
|
||||
case TOGGLE -> {
|
||||
accessor.midnightcontrols$handlePressState(!this.binding.isPressed());
|
||||
this.activated = !this.binding.isPressed();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawIcon(@NotNull MatrixStack matrices, @NotNull TextRenderer textRenderer, int x, int y, boolean hovered) {
|
||||
drawCenteredText(matrices, textRenderer, new TranslatableText(this.getName()), x + 25, y + 25 - textRenderer.fontHeight / 2, 0xffffff);
|
||||
}
|
||||
|
||||
protected static class Factory implements RingAction.Factory {
|
||||
@Override
|
||||
public @NotNull Supplier<RingAction> newFromGui(@NotNull Screen screen) {
|
||||
return () -> null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable RingAction parse(@NotNull Config config) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
|
||||
*
|
||||
* This file is part of midnightcontrols.
|
||||
*
|
||||
* Licensed under the MIT license. For more information,
|
||||
* see the LICENSE file.
|
||||
*/
|
||||
|
||||
package eu.midnightdust.midnightcontrols.client.ring;
|
||||
|
||||
import com.electronwill.nightconfig.core.Config;
|
||||
import eu.midnightdust.midnightcontrols.client.MidnightControlsClient;
|
||||
import eu.midnightdust.midnightcontrols.client.MidnightControlsConfig;
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Represents a key binding ring.
|
||||
*
|
||||
* @author LambdAurora
|
||||
* @version 1.7.0
|
||||
* @since 1.4.0
|
||||
*/
|
||||
public final class MidnightRing {
|
||||
public static final int ELEMENT_SIZE = 50;
|
||||
|
||||
private final Map<String, RingAction.Factory> actionFactories = new Object2ObjectOpenHashMap<>();
|
||||
private final List<RingPage> pages = new ArrayList<>(Collections.singletonList(RingPage.DEFAULT));
|
||||
private final MidnightControlsClient mod;
|
||||
private int currentPage = 0;
|
||||
|
||||
public MidnightRing(@NotNull MidnightControlsClient mod) {
|
||||
this.mod = mod;
|
||||
}
|
||||
|
||||
public void registerAction(@NotNull String name, @NotNull RingAction.Factory factory) {
|
||||
if (this.actionFactories.containsKey(name)) {
|
||||
this.mod.warn("Tried to register twice a ring action: \"" + name + "\".");
|
||||
return;
|
||||
}
|
||||
this.actionFactories.put(name, factory);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the ring from configuration.
|
||||
*/
|
||||
public void load() {
|
||||
// List<Config> configPages = MidnightControlsConfig.ringPages;
|
||||
// if (configPages != null) {
|
||||
// this.pages.clear();
|
||||
// for (var configPage : configPages) {
|
||||
// RingPage.parseRingPage(configPage).ifPresent(this.pages::add);
|
||||
// }
|
||||
// }
|
||||
// if (this.pages.isEmpty()) {
|
||||
// this.pages.add(RingPage.DEFAULT);
|
||||
// }
|
||||
}
|
||||
|
||||
public @NotNull RingPage getCurrentPage() {
|
||||
if (this.currentPage >= this.pages.size())
|
||||
this.currentPage = this.pages.size() - 1;
|
||||
else if (this.currentPage < 0)
|
||||
this.currentPage = 0;
|
||||
return this.pages.get(this.currentPage);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
|
||||
*
|
||||
* This file is part of midnightcontrols.
|
||||
*
|
||||
* Licensed under the MIT license. For more information,
|
||||
* see the LICENSE file.
|
||||
*/
|
||||
|
||||
package eu.midnightdust.midnightcontrols.client.ring;
|
||||
|
||||
import com.electronwill.nightconfig.core.Config;
|
||||
import net.minecraft.client.font.TextRenderer;
|
||||
import net.minecraft.client.gui.DrawableHelper;
|
||||
import net.minecraft.client.gui.screen.Screen;
|
||||
import net.minecraft.client.util.math.MatrixStack;
|
||||
import net.minecraft.text.Text;
|
||||
import net.minecraft.text.TranslatableText;
|
||||
import org.aperlambda.lambdacommon.utils.Nameable;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* Represents a ring action.
|
||||
*
|
||||
* @author LambdAurora
|
||||
* @version 1.5.0
|
||||
* @since 1.4.0
|
||||
*/
|
||||
public abstract class RingAction extends DrawableHelper implements Nameable {
|
||||
protected Config config;
|
||||
protected boolean activated = false;
|
||||
|
||||
public RingAction(@NotNull Config config) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the text name of the ring action.
|
||||
*
|
||||
* @return the text name
|
||||
*/
|
||||
public Text getTextName() {
|
||||
return new TranslatableText(this.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the action is activated or not.
|
||||
*
|
||||
* @return true if the action is activated, else false
|
||||
*/
|
||||
public boolean isActivated() {
|
||||
return this.activated;
|
||||
}
|
||||
|
||||
public void activate(@NotNull RingButtonMode mode) {
|
||||
this.activated = !this.activated;
|
||||
|
||||
this.onAction(mode);
|
||||
}
|
||||
|
||||
public abstract void onAction(@NotNull RingButtonMode mode);
|
||||
|
||||
public void render(@NotNull MatrixStack matrices, @NotNull TextRenderer textRenderer, int x, int y, boolean hovered) {
|
||||
fill(matrices, x, y, x + MidnightRing.ELEMENT_SIZE, y + MidnightRing.ELEMENT_SIZE, hovered ? 0xbb777777 : 0xbb000000);
|
||||
drawIcon(matrices, textRenderer, x, y, hovered);
|
||||
}
|
||||
|
||||
public abstract void drawIcon(@NotNull MatrixStack matrices, @NotNull TextRenderer textRenderer, int x, int y, boolean hovered);
|
||||
|
||||
/**
|
||||
* Represents a factory for {@link RingAction}.
|
||||
*
|
||||
* @version 1.4.3
|
||||
* @since 1.4.3
|
||||
*/
|
||||
public interface Factory {
|
||||
@NotNull Supplier<RingAction> newFromGui(@NotNull Screen screen);
|
||||
|
||||
@Nullable RingAction parse(@NotNull Config config);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
|
||||
*
|
||||
* This file is part of midnightcontrols.
|
||||
*
|
||||
* Licensed under the MIT license. For more information,
|
||||
* see the LICENSE file.
|
||||
*/
|
||||
|
||||
package eu.midnightdust.midnightcontrols.client.ring;
|
||||
|
||||
import net.minecraft.text.Text;
|
||||
import net.minecraft.text.TranslatableText;
|
||||
import org.aperlambda.lambdacommon.utils.Nameable;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Represents the mode of a ring button.
|
||||
*
|
||||
* @author LambdAurora
|
||||
* @version 1.4.0
|
||||
* @since 1.4.0
|
||||
*/
|
||||
public enum RingButtonMode implements Nameable {
|
||||
PRESS("press"),
|
||||
HOLD("hold"),
|
||||
TOGGLE("toggle");
|
||||
|
||||
private final String name;
|
||||
private final Text text;
|
||||
|
||||
RingButtonMode(@NotNull String name) {
|
||||
this.name = name;
|
||||
this.text = new TranslatableText(this.getTranslationKey());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the next ring button mode available.
|
||||
*
|
||||
* @return the next ring button mode
|
||||
*/
|
||||
public @NotNull RingButtonMode next() {
|
||||
var v = values();
|
||||
if (v.length == this.ordinal() + 1)
|
||||
return v[0];
|
||||
return v[this.ordinal() + 1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the translation key of this ring button mode.
|
||||
*
|
||||
* @return the translation key of this ring button mode
|
||||
*/
|
||||
public @NotNull String getTranslationKey() {
|
||||
return "midnightcontrols.ring.button_mode." + this.getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the translated name of this ring button mode.
|
||||
*
|
||||
* @return the translated name of this ring button mode
|
||||
*/
|
||||
public @NotNull Text getTranslatedText() {
|
||||
return this.text;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String getName() {
|
||||
return this.name;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
|
||||
*
|
||||
* This file is part of midnightcontrols.
|
||||
*
|
||||
* Licensed under the MIT license. For more information,
|
||||
* see the LICENSE file.
|
||||
*/
|
||||
|
||||
package eu.midnightdust.midnightcontrols.client.ring;
|
||||
|
||||
import com.electronwill.nightconfig.core.Config;
|
||||
import net.minecraft.client.font.TextRenderer;
|
||||
import net.minecraft.client.gui.DrawableHelper;
|
||||
import net.minecraft.client.util.math.MatrixStack;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Represents a ring page.
|
||||
*
|
||||
* @author LambdAurora
|
||||
* @version 1.5.0
|
||||
* @since 1.4.0
|
||||
*/
|
||||
public class RingPage extends DrawableHelper {
|
||||
public static final RingPage DEFAULT = new RingPage("Default");
|
||||
|
||||
public final String name;
|
||||
private RingAction[] actions = new RingAction[8];
|
||||
|
||||
public RingPage(@NotNull String name) {
|
||||
this.name = name;
|
||||
for (int i = 0; i < 8; i++) {
|
||||
this.actions[i] = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the ring page.
|
||||
*
|
||||
* @param matrices the matrices
|
||||
* @param width the screen width
|
||||
* @param height the screen height
|
||||
* @param mouseX the mouse X-coordinate
|
||||
* @param mouseY the mouse Y-coordinate
|
||||
* @param tickDelta the tick delta
|
||||
*/
|
||||
public void render(@NotNull MatrixStack matrices, @NotNull TextRenderer textRenderer, int width, int height, int mouseX, int mouseY, float tickDelta) {
|
||||
int centerX = width / 2;
|
||||
int centerY = height / 2;
|
||||
|
||||
int offset = MidnightRing.ELEMENT_SIZE + (MidnightRing.ELEMENT_SIZE / 2) + 5;
|
||||
|
||||
int y = centerY - offset;
|
||||
int x = centerX - offset;
|
||||
for (int i = 0; i < 3; i++) {
|
||||
var ringAction = this.actions[i];
|
||||
if (ringAction != null)
|
||||
ringAction.render(matrices, textRenderer, x, y, isHovered(x, y, mouseX, mouseY));
|
||||
x += 55;
|
||||
}
|
||||
y += 55;
|
||||
x = centerX - offset;
|
||||
for (int i = 3; i < 5; i++) {
|
||||
var ringAction = this.actions[i];
|
||||
if (ringAction != null)
|
||||
ringAction.render(matrices, textRenderer, x, y, isHovered(x, y, mouseX, mouseY));
|
||||
x += 55 * 2;
|
||||
}
|
||||
y += 55;
|
||||
x = centerX - offset;
|
||||
for (int i = 5; i < 8; i++) {
|
||||
var ringAction = this.actions[i];
|
||||
if (ringAction != null)
|
||||
ringAction.render(matrices, textRenderer, x, y, isHovered(x, y, mouseX, mouseY));
|
||||
x += 55;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isHovered(int x, int y, int mouseX, int mouseY) {
|
||||
return mouseX >= x && mouseY >= y && mouseX <= x + MidnightRing.ELEMENT_SIZE && mouseY <= y + MidnightRing.ELEMENT_SIZE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to parse a ring page configuration.
|
||||
*
|
||||
* @param config the configuration
|
||||
* @return an optional ring page
|
||||
*/
|
||||
public static @NotNull Optional<RingPage> parseRingPage(@NotNull Config config) {
|
||||
String name = config.get("name");
|
||||
if (name == null)
|
||||
return Optional.empty();
|
||||
|
||||
var page = new RingPage(name);
|
||||
|
||||
List<Config> actionConfigs = config.get("actions");
|
||||
|
||||
|
||||
return Optional.of(page);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
|
||||
*
|
||||
* This file is part of midnightcontrols.
|
||||
*
|
||||
* Licensed under the MIT license. For more information,
|
||||
* see the LICENSE file.
|
||||
*/
|
||||
|
||||
package eu.midnightdust.midnightcontrols.client.util;
|
||||
|
||||
import net.minecraft.screen.slot.Slot;
|
||||
import net.minecraft.screen.slot.SlotActionType;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Represents an accessor to AbstractContainerScreen.
|
||||
*/
|
||||
public interface HandledScreenAccessor {
|
||||
/**
|
||||
* Gets the left coordinate of the GUI.
|
||||
*
|
||||
* @return the left coordinate of the GUI
|
||||
*/
|
||||
int getX();
|
||||
|
||||
/**
|
||||
* Gets the top coordinate of the GUI.
|
||||
*
|
||||
* @return the top coordinate of the GUI
|
||||
*/
|
||||
int getY();
|
||||
|
||||
/**
|
||||
* Gets the slot at position.
|
||||
*
|
||||
* @param posX the X position to check
|
||||
* @param posY the Y position to check
|
||||
* @return the slot at the specified position
|
||||
*/
|
||||
Slot midnightcontrols$getSlotAt(double posX, double posY);
|
||||
|
||||
boolean midnightcontrols$isClickOutsideBounds(double mouseX, double mouseY, int x, int y, int button);
|
||||
|
||||
/**
|
||||
* Handles a mouse click on the specified slot.
|
||||
*
|
||||
* @param slot the slot instance
|
||||
* @param slotId the slot id
|
||||
* @param clickData the click data
|
||||
* @param actionType the action type
|
||||
*/
|
||||
void midnightcontrols$onMouseClick(@Nullable Slot slot, int slotId, int clickData, SlotActionType actionType);
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
|
||||
*
|
||||
* This file is part of midnightcontrols.
|
||||
*
|
||||
* Licensed under the MIT license. For more information,
|
||||
* see the LICENSE file.
|
||||
*/
|
||||
|
||||
package eu.midnightdust.midnightcontrols.client.util;
|
||||
|
||||
/**
|
||||
* Represents a Minecraft keybinding with extra access.
|
||||
*/
|
||||
public interface KeyBindingAccessor {
|
||||
boolean midnightcontrols$press();
|
||||
|
||||
boolean midnightcontrols$unpress();
|
||||
|
||||
default boolean midnightcontrols$handlePressState(boolean pressed) {
|
||||
if (pressed)
|
||||
return this.midnightcontrols$press();
|
||||
else
|
||||
return this.midnightcontrols$unpress();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
|
||||
*
|
||||
* This file is part of midnightcontrols.
|
||||
*
|
||||
* Licensed under the MIT license. For more information,
|
||||
* see the LICENSE file.
|
||||
*/
|
||||
|
||||
package eu.midnightdust.midnightcontrols.client.util;
|
||||
|
||||
/**
|
||||
* Represents mouse's extra access.
|
||||
*/
|
||||
public interface MouseAccessor {
|
||||
void midnightcontrols$onCursorPos(long window, double x, double y);
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
|
||||
*
|
||||
* This file is part of midnightcontrols.
|
||||
*
|
||||
* Licensed under the MIT license. For more information,
|
||||
* see the LICENSE file.
|
||||
*/
|
||||
|
||||
package eu.midnightdust.midnightcontrols.event;
|
||||
|
||||
import eu.midnightdust.midnightcontrols.ControlsMode;
|
||||
import net.fabricmc.fabric.api.event.Event;
|
||||
import net.fabricmc.fabric.api.event.EventFactory;
|
||||
import net.minecraft.entity.player.PlayerEntity;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Represents an event callback which is fired when a player changes the controls mode.
|
||||
*
|
||||
* @author LambdAurora
|
||||
* @version 1.1.0
|
||||
* @since 1.1.0
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface PlayerChangeControlsModeCallback {
|
||||
Event<PlayerChangeControlsModeCallback> EVENT = EventFactory.createArrayBacked(PlayerChangeControlsModeCallback.class, listeners -> (player, controlsMode) -> {
|
||||
for (PlayerChangeControlsModeCallback event : listeners) {
|
||||
event.apply(player, controlsMode);
|
||||
}
|
||||
});
|
||||
|
||||
void apply(@NotNull PlayerEntity player, @NotNull ControlsMode controlsMode);
|
||||
}
|
||||
@@ -1,507 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2019 LambdAurora <aurora42lambda@gmail.com>
|
||||
*
|
||||
* This file is part of LambdaControls.
|
||||
*
|
||||
* Licensed under the MIT license. For more information,
|
||||
* see the LICENSE file.
|
||||
*/
|
||||
|
||||
package me.lambdaurora.lambdacontrols;
|
||||
|
||||
import me.lambdaurora.lambdacontrols.util.KeyBindingAccessor;
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.client.options.GameOptions;
|
||||
import net.minecraft.client.options.KeyBinding;
|
||||
import net.minecraft.client.resource.language.I18n;
|
||||
import net.minecraft.client.util.ScreenshotUtils;
|
||||
import org.aperlambda.lambdacommon.Identifier;
|
||||
import org.aperlambda.lambdacommon.utils.Identifiable;
|
||||
import org.aperlambda.lambdacommon.utils.Nameable;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.lwjgl.glfw.GLFW;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Represents a button binding.
|
||||
*
|
||||
* @author LambdAurora
|
||||
*/
|
||||
public class ButtonBinding implements Nameable
|
||||
{
|
||||
private static final List<ButtonBinding> BINDINGS = new ArrayList<>();
|
||||
private static final List<Category> CATEGORIES = new ArrayList<>();
|
||||
public static final Category MOVEMENT_CATEGORY;
|
||||
public static final Category GAMEPLAY_CATEGORY;
|
||||
public static final Category INVENTORY_CATEGORY;
|
||||
public static final Category MULTIPLAYER_CATEGORY;
|
||||
public static final Category MISC_CATEGORY;
|
||||
public static final ButtonBinding ATTACK = new ButtonBinding("attack", axis_as_button(GLFW.GLFW_GAMEPAD_AXIS_RIGHT_TRIGGER, true));
|
||||
public static final ButtonBinding BACK = new ButtonBinding("back", axis_as_button(GLFW.GLFW_GAMEPAD_AXIS_LEFT_Y, false));
|
||||
public static final ButtonBinding CHAT = new ButtonBinding("chat", GLFW.GLFW_GAMEPAD_BUTTON_DPAD_RIGHT);
|
||||
public static final ButtonBinding DROP_ITEM = new ButtonBinding("drop_item", GLFW.GLFW_GAMEPAD_BUTTON_B);
|
||||
public static final ButtonBinding FORWARD = new ButtonBinding("forward", axis_as_button(GLFW.GLFW_GAMEPAD_AXIS_LEFT_Y, true));
|
||||
public static final ButtonBinding INVENTORY = new ButtonBinding("inventory", GLFW.GLFW_GAMEPAD_BUTTON_Y);
|
||||
public static final ButtonBinding JUMP = new ButtonBinding("jump", GLFW.GLFW_GAMEPAD_BUTTON_A);
|
||||
public static final ButtonBinding LEFT = new ButtonBinding("left", axis_as_button(GLFW.GLFW_GAMEPAD_AXIS_LEFT_X, false));
|
||||
public static final ButtonBinding PAUSE_GAME = new ButtonBinding("pause_game", GLFW.GLFW_GAMEPAD_BUTTON_START);
|
||||
public static final ButtonBinding PICK_BLOCK = new ButtonBinding("pick_block", GLFW.GLFW_GAMEPAD_BUTTON_DPAD_LEFT);
|
||||
public static final ButtonBinding PLAYER_LIST = new ButtonBinding("player_list", GLFW.GLFW_GAMEPAD_BUTTON_BACK);
|
||||
public static final ButtonBinding RIGHT = new ButtonBinding("right", axis_as_button(GLFW.GLFW_GAMEPAD_AXIS_LEFT_X, true));
|
||||
public static final ButtonBinding SCREENSHOT = new ButtonBinding("screenshot", GLFW.GLFW_GAMEPAD_BUTTON_DPAD_DOWN,
|
||||
Collections.singletonList((client, action) -> {
|
||||
if (action == 0)
|
||||
ScreenshotUtils.saveScreenshot(client.runDirectory, client.getWindow().getFramebufferWidth(), client.getWindow().getFramebufferHeight(), client.getFramebuffer(),
|
||||
text -> client.execute(() -> client.inGameHud.getChatHud().addMessage(text)));
|
||||
return true;
|
||||
}));
|
||||
public static final ButtonBinding SMOOTH_CAMERA = new ButtonBinding("toggle_smooth_camera", -1);
|
||||
public static final ButtonBinding SNEAK = new ButtonBinding("sneak", GLFW.GLFW_GAMEPAD_BUTTON_RIGHT_THUMB);
|
||||
public static final ButtonBinding SPRINT = new ButtonBinding("sprint", GLFW.GLFW_GAMEPAD_BUTTON_LEFT_THUMB);
|
||||
public static final ButtonBinding SWAP_HANDS = new ButtonBinding("swap_hands", GLFW.GLFW_GAMEPAD_BUTTON_X);
|
||||
public static final ButtonBinding TOGGLE_PERSPECTIVE = new ButtonBinding("toggle_perspective", GLFW.GLFW_GAMEPAD_BUTTON_DPAD_UP);
|
||||
public static final ButtonBinding USE = new ButtonBinding("use", axis_as_button(GLFW.GLFW_GAMEPAD_AXIS_LEFT_TRIGGER, true));
|
||||
|
||||
private int button;
|
||||
private int default_button;
|
||||
private String key;
|
||||
private KeyBinding minecraft_key_binding = null;
|
||||
private List<PressAction> actions = new ArrayList<>(Collections.singletonList((client, action) -> {
|
||||
this.as_key_binding().ifPresent(key_binding -> ((KeyBindingAccessor) key_binding).handle_press_state(this.is_button_down()));
|
||||
return true;
|
||||
}));
|
||||
private boolean pressed = false;
|
||||
|
||||
protected ButtonBinding(@NotNull String key, int default_button, @NotNull List<PressAction> actions)
|
||||
{
|
||||
this.default_button = this.button = default_button;
|
||||
this.key = key;
|
||||
this.actions.addAll(actions);
|
||||
BINDINGS.add(this);
|
||||
}
|
||||
|
||||
protected ButtonBinding(@NotNull String key, int default_button)
|
||||
{
|
||||
this(key, default_button, Collections.emptyList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the button bound.
|
||||
*
|
||||
* @return The bound button.
|
||||
*/
|
||||
public int get_button()
|
||||
{
|
||||
return this.button;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the bound button.
|
||||
*
|
||||
* @param button The bound button.
|
||||
*/
|
||||
public void set_button(int button)
|
||||
{
|
||||
this.button = button;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the bound button is the specified button or not.
|
||||
*
|
||||
* @param button The button to check.
|
||||
* @return True if the bound button is the specified button, else false.
|
||||
*/
|
||||
public boolean is_button(int button)
|
||||
{
|
||||
return this.button == button;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this button is down or not.
|
||||
*
|
||||
* @return True if the button is down, else false.
|
||||
*/
|
||||
public boolean is_button_down()
|
||||
{
|
||||
return this.pressed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this button binding is bound or not.
|
||||
*
|
||||
* @return True if this button binding is bound, else false.
|
||||
*/
|
||||
public boolean is_not_bound()
|
||||
{
|
||||
return this.button == -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the default button assigned to this binding.
|
||||
*
|
||||
* @return The default button.
|
||||
*/
|
||||
public int get_default_button()
|
||||
{
|
||||
return this.default_button;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the assigned button is the default button.
|
||||
*
|
||||
* @return True if the assigned button is the default button, else false.
|
||||
*/
|
||||
public boolean is_default()
|
||||
{
|
||||
return this.button == this.default_button;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String get_name()
|
||||
{
|
||||
return this.key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the translation key of this button binding.
|
||||
*
|
||||
* @return The translation key.
|
||||
*/
|
||||
public @NotNull String get_translation_key()
|
||||
{
|
||||
return "lambdacontrols.action." + this.get_name();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the key binding equivalent of this button binding.
|
||||
*
|
||||
* @return The key binding equivalent.
|
||||
*/
|
||||
public @NotNull Optional<KeyBinding> as_key_binding()
|
||||
{
|
||||
return Optional.ofNullable(this.minecraft_key_binding);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the specified axis as a button.
|
||||
*
|
||||
* @param axis The axis.
|
||||
* @param positive True if the axis part is positive, else false.
|
||||
* @return The axis as a button.
|
||||
*/
|
||||
public static int axis_as_button(int axis, boolean positive)
|
||||
{
|
||||
return positive ? 100 + axis : 200 + axis;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the second Joycon's specified button code.
|
||||
*
|
||||
* @param button The raw button code.
|
||||
* @return The second Joycon's button code.
|
||||
*/
|
||||
public static int joycon2_button(int button)
|
||||
{
|
||||
return 300 + button;
|
||||
}
|
||||
|
||||
public static void init(@NotNull GameOptions options)
|
||||
{
|
||||
ATTACK.minecraft_key_binding = options.keyAttack;
|
||||
BACK.minecraft_key_binding = options.keyBack;
|
||||
CHAT.minecraft_key_binding = options.keyChat;
|
||||
DROP_ITEM.minecraft_key_binding = options.keyDrop;
|
||||
FORWARD.minecraft_key_binding = options.keyForward;
|
||||
INVENTORY.minecraft_key_binding = options.keyInventory;
|
||||
JUMP.minecraft_key_binding = options.keyJump;
|
||||
LEFT.minecraft_key_binding = options.keyLeft;
|
||||
PICK_BLOCK.minecraft_key_binding = options.keyPickItem;
|
||||
PLAYER_LIST.minecraft_key_binding = options.keyPlayerList;
|
||||
RIGHT.minecraft_key_binding = options.keyRight;
|
||||
SCREENSHOT.minecraft_key_binding = options.keyScreenshot;
|
||||
SMOOTH_CAMERA.minecraft_key_binding = options.keySmoothCamera;
|
||||
SNEAK.minecraft_key_binding = options.keySneak;
|
||||
SPRINT.minecraft_key_binding = options.keySprint;
|
||||
SWAP_HANDS.minecraft_key_binding = options.keySwapHands;
|
||||
TOGGLE_PERSPECTIVE.minecraft_key_binding = options.keyTogglePerspective;
|
||||
USE.minecraft_key_binding = options.keyUse;
|
||||
}
|
||||
|
||||
public static void load_from_config(@NotNull LambdaControlsConfig config)
|
||||
{
|
||||
BINDINGS.forEach(config::load_button_binding);
|
||||
}
|
||||
|
||||
public static void set_button_state(int button, boolean state)
|
||||
{
|
||||
BINDINGS.parallelStream().filter(binding -> Objects.equals(binding.button, button))
|
||||
.forEach(binding -> binding.pressed = state);
|
||||
}
|
||||
|
||||
public static void handle_button(@NotNull MinecraftClient client, int button, int action)
|
||||
{
|
||||
BINDINGS.parallelStream().filter(binding -> binding.button == button)
|
||||
.forEach(binding -> {
|
||||
for (int i = binding.actions.size() - 1; i >= 0; i--) {
|
||||
if (binding.actions.get(i).press(client, action))
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the button has duplicated bindings.
|
||||
*
|
||||
* @param button The button to check.
|
||||
* @return True if the button has duplicated bindings, else false.
|
||||
*/
|
||||
public static boolean has_duplicates(int button)
|
||||
{
|
||||
return BINDINGS.parallelStream().filter(binding -> binding.button == button).count() > 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the localized name of the specified button.
|
||||
*
|
||||
* @param button The button.
|
||||
* @return The localized name of the button.
|
||||
*/
|
||||
public static @NotNull String get_localized_button_name(int button)
|
||||
{
|
||||
switch (button) {
|
||||
case -1:
|
||||
return I18n.translate("key.keyboard.unknown");
|
||||
case GLFW.GLFW_GAMEPAD_BUTTON_A:
|
||||
return I18n.translate("lambdacontrols.button.a");
|
||||
case GLFW.GLFW_GAMEPAD_BUTTON_B:
|
||||
return I18n.translate("lambdacontrols.button.b");
|
||||
case GLFW.GLFW_GAMEPAD_BUTTON_X:
|
||||
return I18n.translate("lambdacontrols.button.x");
|
||||
case GLFW.GLFW_GAMEPAD_BUTTON_Y:
|
||||
return I18n.translate("lambdacontrols.button.y");
|
||||
case GLFW.GLFW_GAMEPAD_BUTTON_LEFT_BUMPER:
|
||||
return I18n.translate("lambdacontrols.button.left_bumper");
|
||||
case GLFW.GLFW_GAMEPAD_BUTTON_RIGHT_BUMPER:
|
||||
return I18n.translate("lambdacontrols.button.right_bumper");
|
||||
case GLFW.GLFW_GAMEPAD_BUTTON_BACK:
|
||||
return I18n.translate("lambdacontrols.button.back");
|
||||
case GLFW.GLFW_GAMEPAD_BUTTON_START:
|
||||
return I18n.translate("lambdacontrols.button.start");
|
||||
case GLFW.GLFW_GAMEPAD_BUTTON_GUIDE:
|
||||
return I18n.translate("lambdacontrols.button.guide");
|
||||
case GLFW.GLFW_GAMEPAD_BUTTON_LEFT_THUMB:
|
||||
return I18n.translate("lambdacontrols.button.left_thumb");
|
||||
case GLFW.GLFW_GAMEPAD_BUTTON_RIGHT_THUMB:
|
||||
return I18n.translate("lambdacontrols.button.right_thumb");
|
||||
case GLFW.GLFW_GAMEPAD_BUTTON_DPAD_UP:
|
||||
return I18n.translate("lambdacontrols.button.dpad_up");
|
||||
case GLFW.GLFW_GAMEPAD_BUTTON_DPAD_RIGHT:
|
||||
return I18n.translate("lambdacontrols.button.dpad_right");
|
||||
case GLFW.GLFW_GAMEPAD_BUTTON_DPAD_DOWN:
|
||||
return I18n.translate("lambdacontrols.button.dpad_down");
|
||||
case GLFW.GLFW_GAMEPAD_BUTTON_DPAD_LEFT:
|
||||
return I18n.translate("lambdacontrols.button.dpad_left");
|
||||
case 100:
|
||||
return I18n.translate("lambdacontrols.axis.left_x+");
|
||||
case 101:
|
||||
return I18n.translate("lambdacontrols.axis.left_y+");
|
||||
case 102:
|
||||
return I18n.translate("lambdacontrols.axis.right_x+");
|
||||
case 103:
|
||||
return I18n.translate("lambdacontrols.axis.right_y+");
|
||||
case 104:
|
||||
return I18n.translate("lambdacontrols.axis.left_trigger");
|
||||
case 105:
|
||||
return I18n.translate("lambdacontrols.axis.right_trigger");
|
||||
case 200:
|
||||
return I18n.translate("lambdacontrols.axis.left_x-");
|
||||
case 201:
|
||||
return I18n.translate("lambdacontrols.axis.left_y-");
|
||||
case 202:
|
||||
return I18n.translate("lambdacontrols.axis.right_x-");
|
||||
case 203:
|
||||
return I18n.translate("lambdacontrols.axis.right_y-");
|
||||
default:
|
||||
return I18n.translate("lambdacontrols.button.unknown", button);
|
||||
}
|
||||
}
|
||||
|
||||
public static @NotNull Stream<ButtonBinding> stream()
|
||||
{
|
||||
return BINDINGS.stream();
|
||||
}
|
||||
|
||||
public static @NotNull Stream<Category> stream_categories()
|
||||
{
|
||||
return CATEGORIES.stream();
|
||||
}
|
||||
|
||||
static {
|
||||
MOVEMENT_CATEGORY = register_default_category("key.categories.movement", category -> category.register_all_bindings(
|
||||
FORWARD,
|
||||
BACK,
|
||||
LEFT,
|
||||
RIGHT,
|
||||
JUMP,
|
||||
SNEAK,
|
||||
SPRINT));
|
||||
GAMEPLAY_CATEGORY = register_default_category("key.categories.gameplay", category -> category.register_all_bindings(
|
||||
ATTACK,
|
||||
PICK_BLOCK,
|
||||
USE
|
||||
));
|
||||
INVENTORY_CATEGORY = register_default_category("key.categories.inventory", category -> category.register_all_bindings(
|
||||
DROP_ITEM,
|
||||
INVENTORY,
|
||||
SWAP_HANDS
|
||||
));
|
||||
MULTIPLAYER_CATEGORY = register_default_category("key.categories.multiplayer",
|
||||
category -> category.register_all_bindings(CHAT, PLAYER_LIST));
|
||||
MISC_CATEGORY = register_default_category("key.categories.misc", category -> category.register_all_bindings(
|
||||
SCREENSHOT,
|
||||
//SMOOTH_CAMERA,
|
||||
TOGGLE_PERSPECTIVE
|
||||
));
|
||||
}
|
||||
|
||||
public static ButtonBinding register(@NotNull Identifier binding_id, int default_button, @NotNull List<PressAction> actions)
|
||||
{
|
||||
return new ButtonBinding(binding_id.get_namespace() + "." + binding_id.get_name(), default_button, actions);
|
||||
}
|
||||
|
||||
public static ButtonBinding register(@NotNull Identifier binding_id, int default_button)
|
||||
{
|
||||
return register(binding_id, default_button, Collections.emptyList());
|
||||
}
|
||||
|
||||
public static ButtonBinding register(@NotNull net.minecraft.util.Identifier binding_id, int default_button, @NotNull List<PressAction> actions)
|
||||
{
|
||||
return register(new Identifier(binding_id.getNamespace(), binding_id.getPath()), default_button, actions);
|
||||
}
|
||||
|
||||
public static ButtonBinding register(@NotNull net.minecraft.util.Identifier binding_id, int default_button)
|
||||
{
|
||||
return register(binding_id, default_button, Collections.emptyList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a category of button bindings.
|
||||
*
|
||||
* @param category The category to register.
|
||||
* @return The registered category.
|
||||
*/
|
||||
public static Category register_category(@NotNull Category category)
|
||||
{
|
||||
CATEGORIES.add(category);
|
||||
return category;
|
||||
}
|
||||
|
||||
public static Category register_category(@NotNull Identifier identifier, int priority)
|
||||
{
|
||||
return register_category(new Category(identifier, priority));
|
||||
}
|
||||
|
||||
public static Category register_category(@NotNull Identifier identifier)
|
||||
{
|
||||
return register_category(new Category(identifier));
|
||||
}
|
||||
|
||||
private static Category register_default_category(@NotNull String key, @NotNull Consumer<Category> key_adder)
|
||||
{
|
||||
Category category = register_category(new Identifier("minecraft", key), CATEGORIES.size());
|
||||
key_adder.accept(category);
|
||||
return category;
|
||||
}
|
||||
|
||||
public static class Category implements Identifiable
|
||||
{
|
||||
private final List<ButtonBinding> bindings = new ArrayList<>();
|
||||
private final Identifier id;
|
||||
private int priority;
|
||||
|
||||
public Category(@NotNull Identifier id, int priority)
|
||||
{
|
||||
this.id = id;
|
||||
this.priority = priority;
|
||||
}
|
||||
|
||||
public Category(@NotNull Identifier id)
|
||||
{
|
||||
this(id, 100);
|
||||
}
|
||||
|
||||
public void register_binding(@NotNull ButtonBinding binding)
|
||||
{
|
||||
if (this.bindings.contains(binding))
|
||||
throw new IllegalStateException("Cannot register twice a button binding in the same category.");
|
||||
this.bindings.add(binding);
|
||||
}
|
||||
|
||||
public void register_all_bindings(@NotNull ButtonBinding... bindings)
|
||||
{
|
||||
this.register_all_bindings(Arrays.asList(bindings));
|
||||
}
|
||||
|
||||
public void register_all_bindings(@NotNull List<ButtonBinding> bindings)
|
||||
{
|
||||
bindings.forEach(this::register_binding);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the bindings assigned to this category.
|
||||
*
|
||||
* @return The bindings assigned to this category.
|
||||
*/
|
||||
public @NotNull List<ButtonBinding> get_bindings()
|
||||
{
|
||||
return Collections.unmodifiableList(this.bindings);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the translated name of this category.
|
||||
* <p>
|
||||
* The translation key should be `modid.identifier_name`.
|
||||
*
|
||||
* @return The translated name.
|
||||
*/
|
||||
public @NotNull String get_translated_name()
|
||||
{
|
||||
if (this.id.get_namespace().equals("minecraft"))
|
||||
return I18n.translate(this.id.get_name());
|
||||
else
|
||||
return I18n.translate(this.id.get_namespace() + "." + this.id.get_name());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the priority display of this category.
|
||||
* It will defines in which order the categories will display on the controls screen.
|
||||
*
|
||||
* @return The priority of this category.
|
||||
*/
|
||||
public int get_priority()
|
||||
{
|
||||
return this.priority;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Identifier get_identifier()
|
||||
{
|
||||
return this.id;
|
||||
}
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public static interface PressAction
|
||||
{
|
||||
/**
|
||||
* Handles when there is a press action on the button.
|
||||
*
|
||||
* @param client The client instance.
|
||||
* @param action The action done.
|
||||
*/
|
||||
boolean press(@NotNull MinecraftClient client, int action);
|
||||
}
|
||||
}
|
||||
@@ -1,175 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2019 LambdAurora <aurora42lambda@gmail.com>
|
||||
*
|
||||
* This file is part of LambdaControls.
|
||||
*
|
||||
* Licensed under the MIT license. For more information,
|
||||
* see the LICENSE file.
|
||||
*/
|
||||
|
||||
package me.lambdaurora.lambdacontrols;
|
||||
|
||||
import org.aperlambda.lambdacommon.utils.Nameable;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.lwjgl.glfw.GLFW;
|
||||
import org.lwjgl.glfw.GLFWGamepadState;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.Buffer;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.SeekableByteChannel;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.lwjgl.BufferUtils.createByteBuffer;
|
||||
|
||||
/**
|
||||
* Represents a controller.
|
||||
*/
|
||||
public class Controller implements Nameable
|
||||
{
|
||||
private static final Map<Integer, Controller> CONTROLLERS = new HashMap<>();
|
||||
private final int id;
|
||||
|
||||
public Controller(int id)
|
||||
{
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the identifier of this controller.
|
||||
*
|
||||
* @return The identifier of this controller.
|
||||
*/
|
||||
public int get_id()
|
||||
{
|
||||
return this.id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the controller's globally unique identifier.
|
||||
*
|
||||
* @return The controller's GUID.
|
||||
*/
|
||||
public String get_guid()
|
||||
{
|
||||
String guid = GLFW.glfwGetJoystickGUID(this.id);
|
||||
return guid == null ? "" : guid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this controller is connected or not.
|
||||
*
|
||||
* @return True if this controller is connected, else false.
|
||||
*/
|
||||
public boolean is_connected()
|
||||
{
|
||||
return GLFW.glfwJoystickPresent(this.id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this controller is a gamepad or not.
|
||||
*
|
||||
* @return True if this controller is a gamepad, else false.
|
||||
*/
|
||||
public boolean is_gamepad()
|
||||
{
|
||||
return this.is_connected() && GLFW.glfwJoystickIsGamepad(this.id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the name of the controller.
|
||||
*
|
||||
* @return The controller's name.
|
||||
*/
|
||||
@Override
|
||||
public @NotNull String get_name()
|
||||
{
|
||||
String name = this.is_gamepad() ? GLFW.glfwGetGamepadName(this.id) : GLFW.glfwGetJoystickName(this.id);
|
||||
return name == null ? String.valueOf(this.get_id()) : name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the state of the controller.
|
||||
*
|
||||
* @return The state of the controller input.
|
||||
*/
|
||||
public GLFWGamepadState get_state()
|
||||
{
|
||||
GLFWGamepadState state = GLFWGamepadState.create();
|
||||
if (this.is_gamepad())
|
||||
GLFW.glfwGetGamepadState(this.id, state);
|
||||
return state;
|
||||
}
|
||||
|
||||
public static @NotNull Controller by_id(int id)
|
||||
{
|
||||
if (id > GLFW.GLFW_JOYSTICK_LAST) {
|
||||
LambdaControls.get().log("Controller '" + id + "' doesn't exist.");
|
||||
id = GLFW.GLFW_JOYSTICK_LAST;
|
||||
}
|
||||
Controller controller;
|
||||
if (CONTROLLERS.containsKey(id))
|
||||
return CONTROLLERS.get(id);
|
||||
else {
|
||||
controller = new Controller(id);
|
||||
CONTROLLERS.put(id, controller);
|
||||
return controller;
|
||||
}
|
||||
}
|
||||
|
||||
public static @NotNull Optional<Controller> by_guid(@NotNull String guid)
|
||||
{
|
||||
return CONTROLLERS.values().stream().filter(Controller::is_connected)
|
||||
.filter(controller -> controller.get_guid().equals(guid))
|
||||
.max(Comparator.comparingInt(Controller::get_id));
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the specified resource and returns the raw data as a ByteBuffer.
|
||||
*
|
||||
* @param resource The resource to read.
|
||||
* @param buffer_size The initial buffer size.
|
||||
* @return The resource data.
|
||||
* @throws IOException If an IO error occurs.
|
||||
*/
|
||||
private static ByteBuffer io_resource_to_buffer(String resource, int buffer_size) throws IOException
|
||||
{
|
||||
ByteBuffer buffer = null;
|
||||
|
||||
Path path = Paths.get(resource);
|
||||
if (Files.isReadable(path)) {
|
||||
try (SeekableByteChannel fc = Files.newByteChannel(path)) {
|
||||
buffer = createByteBuffer((int) fc.size() + 2);
|
||||
while (fc.read(buffer) != -1) ;
|
||||
buffer.put((byte) 0);
|
||||
}
|
||||
}
|
||||
|
||||
((Buffer) buffer).flip(); // Force Java 8 >.<
|
||||
return buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the controller mappings.
|
||||
*/
|
||||
public static void update_mappings()
|
||||
{
|
||||
try {
|
||||
File mappings_file = new File("config/gamecontrollerdb.txt");
|
||||
if (!mappings_file.exists())
|
||||
return;
|
||||
LambdaControls.get().log("Updating controller mappings...");
|
||||
ByteBuffer buffer = io_resource_to_buffer(mappings_file.getPath(), 1024);
|
||||
GLFW.glfwUpdateGamepadMappings(buffer);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2019 LambdAurora <aurora42lambda@gmail.com>
|
||||
*
|
||||
* This file is part of LambdaControls.
|
||||
*
|
||||
* Licensed under the MIT license. For more information,
|
||||
* see the LICENSE file.
|
||||
*/
|
||||
|
||||
package me.lambdaurora.lambdacontrols;
|
||||
|
||||
import net.minecraft.client.resource.language.I18n;
|
||||
import org.aperlambda.lambdacommon.utils.Nameable;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Represents a controller type.
|
||||
*/
|
||||
public enum ControllerType implements Nameable
|
||||
{
|
||||
DEFAULT(0),
|
||||
DUALSHOCK(1),
|
||||
SWITCH(2),
|
||||
XBOX(3),
|
||||
STEAM(4),
|
||||
OUYA(5);
|
||||
|
||||
private final int id;
|
||||
|
||||
ControllerType(int id)
|
||||
{
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the controller type's identifier.
|
||||
*
|
||||
* @return The controller type's identifier.
|
||||
*/
|
||||
public int get_id()
|
||||
{
|
||||
return this.id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the next controller type available.
|
||||
*
|
||||
* @return The next available controller type.
|
||||
*/
|
||||
public ControllerType next()
|
||||
{
|
||||
ControllerType[] v = values();
|
||||
if (v.length == this.ordinal() + 1)
|
||||
return v[0];
|
||||
return v[this.ordinal() + 1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the translated name of this controller type.
|
||||
*
|
||||
* @return The translated name of this controller type.
|
||||
*/
|
||||
public String get_translated_name()
|
||||
{
|
||||
return I18n.translate("lambdacontrols.controller_type." + this.get_name());
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String get_name()
|
||||
{
|
||||
return this.name().toLowerCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the controller type from its identifier.
|
||||
*
|
||||
* @param id The identifier of the controller type.
|
||||
* @return The controller type if found, else empty.
|
||||
*/
|
||||
public static Optional<ControllerType> by_id(@NotNull String id)
|
||||
{
|
||||
return Arrays.stream(values()).filter(mode -> mode.get_name().equalsIgnoreCase(id)).findFirst();
|
||||
}
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2019 LambdAurora <aurora42lambda@gmail.com>
|
||||
*
|
||||
* This file is part of LambdaControls.
|
||||
*
|
||||
* Licensed under the MIT license. For more information,
|
||||
* see the LICENSE file.
|
||||
*/
|
||||
|
||||
package me.lambdaurora.lambdacontrols;
|
||||
|
||||
import net.minecraft.client.resource.language.I18n;
|
||||
import org.aperlambda.lambdacommon.utils.Nameable;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Represents the controls mode.
|
||||
*/
|
||||
public enum ControlsMode implements Nameable
|
||||
{
|
||||
DEFAULT,
|
||||
CONTROLLER,
|
||||
TOUCHSCREEN;
|
||||
|
||||
/**
|
||||
* Returns the next controls mode available.
|
||||
*
|
||||
* @return The next available controls mode.
|
||||
*/
|
||||
public ControlsMode next()
|
||||
{
|
||||
ControlsMode[] v = values();
|
||||
if (v.length == this.ordinal() + 1)
|
||||
return v[0];
|
||||
return v[this.ordinal() + 1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the translated name of this controls mode.
|
||||
*
|
||||
* @return The translated name of this controls mode.
|
||||
*/
|
||||
public String get_translated_name()
|
||||
{
|
||||
return I18n.translate("lambdacontrols.controls_mode." + this.get_name());
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String get_name()
|
||||
{
|
||||
return this.name().toLowerCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the controls mode from its identifier.
|
||||
*
|
||||
* @param id The identifier of the controls mode.
|
||||
* @return The controls mode if found, else empty.
|
||||
*/
|
||||
public static Optional<ControlsMode> by_id(@NotNull String id)
|
||||
{
|
||||
return Arrays.stream(values()).filter(mode -> mode.get_name().equalsIgnoreCase(id)).findFirst();
|
||||
}
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2019 LambdAurora <aurora42lambda@gmail.com>
|
||||
*
|
||||
* This file is part of LambdaControls.
|
||||
*
|
||||
* Licensed under the MIT license. For more information,
|
||||
* see the LICENSE file.
|
||||
*/
|
||||
|
||||
package me.lambdaurora.lambdacontrols;
|
||||
|
||||
import net.minecraft.client.resource.language.I18n;
|
||||
import org.aperlambda.lambdacommon.utils.Nameable;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Represents the hud side which is the side where the movements buttons are.
|
||||
*/
|
||||
public enum HudSide implements Nameable
|
||||
{
|
||||
LEFT,
|
||||
RIGHT;
|
||||
|
||||
/**
|
||||
* Returns the next side available.
|
||||
*
|
||||
* @return The next available side.
|
||||
*/
|
||||
public HudSide next()
|
||||
{
|
||||
HudSide[] v = values();
|
||||
if (v.length == this.ordinal() + 1)
|
||||
return v[0];
|
||||
return v[this.ordinal() + 1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the translated name of this hud side.
|
||||
*
|
||||
* @return The translated name of this hud side.
|
||||
*/
|
||||
public String get_translated_name()
|
||||
{
|
||||
return I18n.translate("lambdacontrols.hud_side." + this.get_name());
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String get_name()
|
||||
{
|
||||
return this.name().toLowerCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the hud side from its identifier.
|
||||
*
|
||||
* @param id The identifier of the hud side.
|
||||
* @return The hud side if found, else empty.
|
||||
*/
|
||||
public static Optional<HudSide> by_id(@NotNull String id)
|
||||
{
|
||||
return Arrays.stream(values()).filter(mode -> mode.get_name().equalsIgnoreCase(id)).findFirst();
|
||||
}
|
||||
}
|
||||
@@ -1,251 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2019 LambdAurora <aurora42lambda@gmail.com>
|
||||
*
|
||||
* This file is part of LambdaControls.
|
||||
*
|
||||
* Licensed under the MIT license. For more information,
|
||||
* see the LICENSE file.
|
||||
*/
|
||||
|
||||
package me.lambdaurora.lambdacontrols;
|
||||
|
||||
import com.mojang.blaze3d.platform.GlStateManager;
|
||||
import net.fabricmc.api.ClientModInitializer;
|
||||
import net.fabricmc.fabric.api.client.keybinding.FabricKeyBinding;
|
||||
import net.fabricmc.fabric.api.client.keybinding.KeyBindingRegistry;
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.client.font.TextRenderer;
|
||||
import net.minecraft.client.gui.DrawableHelper;
|
||||
import net.minecraft.client.resource.language.I18n;
|
||||
import net.minecraft.client.toast.SystemToast;
|
||||
import net.minecraft.client.util.InputUtil;
|
||||
import net.minecraft.text.LiteralText;
|
||||
import net.minecraft.text.TranslatableText;
|
||||
import net.minecraft.util.Identifier;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.lwjgl.glfw.GLFW;
|
||||
|
||||
/**
|
||||
* Represents the LambdaControls mod.
|
||||
*/
|
||||
public class LambdaControls implements ClientModInitializer
|
||||
{
|
||||
private static LambdaControls INSTANCE;
|
||||
public static final FabricKeyBinding BINDING_LOOK_UP = FabricKeyBinding.Builder.create(new Identifier("lambdacontrols", "look_up"),
|
||||
InputUtil.Type.KEYSYM, GLFW.GLFW_KEY_KP_8, "key.categories.movement").build();
|
||||
public static final FabricKeyBinding BINDING_LOOK_RIGHT = FabricKeyBinding.Builder.create(new Identifier("lambdacontrols", "look_right"),
|
||||
InputUtil.Type.KEYSYM, GLFW.GLFW_KEY_KP_6, "key.categories.movement").build();
|
||||
public static final FabricKeyBinding BINDING_LOOK_DOWN = FabricKeyBinding.Builder.create(new Identifier("lambdacontrols", "look_down"),
|
||||
InputUtil.Type.KEYSYM, GLFW.GLFW_KEY_KP_2, "key.categories.movement").build();
|
||||
public static final FabricKeyBinding BINDING_LOOK_LEFT = FabricKeyBinding.Builder.create(new Identifier("lambdacontrols", "look_left"),
|
||||
InputUtil.Type.KEYSYM, GLFW.GLFW_KEY_KP_4, "key.categories.movement").build();
|
||||
public static final Identifier CONTROLLER_BUTTONS = new Identifier("lambdacontrols", "textures/gui/controller_buttons.png");
|
||||
public static final Identifier CONTROLLER_AXIS = new Identifier("lambdacontrols", "textures/gui/controller_axis.png");
|
||||
public final Logger logger = LogManager.getLogger("LambdaControls");
|
||||
public final LambdaControlsConfig config = new LambdaControlsConfig(this);
|
||||
public final LambdaInput input = new LambdaInput(this);
|
||||
private ControlsMode previous_controls_mode;
|
||||
|
||||
@Override
|
||||
public void onInitializeClient()
|
||||
{
|
||||
INSTANCE = this;
|
||||
this.log("Initializing LambdaControls...");
|
||||
this.config.load();
|
||||
|
||||
KeyBindingRegistry.INSTANCE.register(BINDING_LOOK_UP);
|
||||
KeyBindingRegistry.INSTANCE.register(BINDING_LOOK_RIGHT);
|
||||
KeyBindingRegistry.INSTANCE.register(BINDING_LOOK_DOWN);
|
||||
KeyBindingRegistry.INSTANCE.register(BINDING_LOOK_LEFT);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called when Minecraft is initializing.
|
||||
*/
|
||||
public void on_mc_init(@NotNull MinecraftClient client)
|
||||
{
|
||||
Controller.update_mappings();
|
||||
ButtonBinding.init(client.options);
|
||||
GLFW.glfwSetJoystickCallback((jid, event) -> {
|
||||
if (event == GLFW.GLFW_CONNECTED) {
|
||||
Controller controller = Controller.by_id(jid);
|
||||
client.getToastManager().add(new SystemToast(SystemToast.Type.TUTORIAL_HINT, new TranslatableText("lambdacontrols.controller.connected", jid),
|
||||
new LiteralText(controller.get_name())));
|
||||
} else if (event == GLFW.GLFW_DISCONNECTED) {
|
||||
client.getToastManager().add(new SystemToast(SystemToast.Type.TUTORIAL_HINT, new TranslatableText("lambdacontrols.controller.disconnected", jid),
|
||||
null));
|
||||
}
|
||||
|
||||
this.switch_controls_mode();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called every Minecraft tick.
|
||||
*
|
||||
* @param client The client instance.
|
||||
*/
|
||||
public void on_tick(@NotNull MinecraftClient client)
|
||||
{
|
||||
this.input.on_tick(client);
|
||||
if (this.config.get_controls_mode() == ControlsMode.CONTROLLER)
|
||||
this.input.on_controller_tick(client);
|
||||
}
|
||||
|
||||
public void on_render(MinecraftClient client)
|
||||
{
|
||||
this.input.on_render(client);
|
||||
}
|
||||
|
||||
/**
|
||||
* Switches the controls mode if the auto switch is enabled.
|
||||
*/
|
||||
public void switch_controls_mode()
|
||||
{
|
||||
if (this.config.has_auto_switch_mode()) {
|
||||
if (this.config.get_controller().is_gamepad()) {
|
||||
this.previous_controls_mode = this.config.get_controls_mode();
|
||||
this.config.set_controls_mode(ControlsMode.CONTROLLER);
|
||||
} else {
|
||||
if (this.previous_controls_mode == null) {
|
||||
this.previous_controls_mode = ControlsMode.DEFAULT;
|
||||
}
|
||||
|
||||
this.config.set_controls_mode(this.previous_controls_mode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints a message to the terminal.
|
||||
*
|
||||
* @param info The message to print.
|
||||
*/
|
||||
public void log(String info)
|
||||
{
|
||||
this.logger.info("[LambdaControls] " + info);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the LambdaControls instance.
|
||||
*
|
||||
* @return The LambdaControls instance.
|
||||
*/
|
||||
public static LambdaControls get()
|
||||
{
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
public static int draw_button(int x, int y, @NotNull ButtonBinding button, @NotNull MinecraftClient client)
|
||||
{
|
||||
return draw_button(x, y, button.get_button(), client);
|
||||
}
|
||||
|
||||
public static int draw_button(int x, int y, int button, @NotNull MinecraftClient client)
|
||||
{
|
||||
if (button == -1)
|
||||
return 0;
|
||||
|
||||
int controller_type = get().config.get_controller_type().get_id();
|
||||
boolean axis = false;
|
||||
int button_offset = button * 15;
|
||||
switch (button) {
|
||||
case GLFW.GLFW_GAMEPAD_BUTTON_LEFT_BUMPER:
|
||||
button_offset = 7 * 15;
|
||||
break;
|
||||
case GLFW.GLFW_GAMEPAD_BUTTON_RIGHT_BUMPER:
|
||||
button_offset = 8 * 15;
|
||||
break;
|
||||
case GLFW.GLFW_GAMEPAD_BUTTON_BACK:
|
||||
button_offset = 4 * 15;
|
||||
break;
|
||||
case GLFW.GLFW_GAMEPAD_BUTTON_START:
|
||||
button_offset = 6 * 15;
|
||||
break;
|
||||
case GLFW.GLFW_GAMEPAD_BUTTON_GUIDE:
|
||||
button_offset = 5 * 15;
|
||||
break;
|
||||
case GLFW.GLFW_GAMEPAD_BUTTON_LEFT_THUMB:
|
||||
button_offset = 15 * 15;
|
||||
break;
|
||||
case GLFW.GLFW_GAMEPAD_BUTTON_RIGHT_THUMB:
|
||||
button_offset = 16 * 15;
|
||||
break;
|
||||
case GLFW.GLFW_GAMEPAD_AXIS_LEFT_X + 100:
|
||||
button_offset = 0;
|
||||
axis = true;
|
||||
break;
|
||||
case GLFW.GLFW_GAMEPAD_AXIS_LEFT_Y + 100:
|
||||
button_offset = 18;
|
||||
axis = true;
|
||||
break;
|
||||
case GLFW.GLFW_GAMEPAD_AXIS_RIGHT_X + 100:
|
||||
button_offset = 2 * 18;
|
||||
axis = true;
|
||||
break;
|
||||
case GLFW.GLFW_GAMEPAD_AXIS_RIGHT_Y + 100:
|
||||
button_offset = 3 * 18;
|
||||
axis = true;
|
||||
break;
|
||||
case GLFW.GLFW_GAMEPAD_AXIS_LEFT_X + 200:
|
||||
button_offset = 4 * 18;
|
||||
axis = true;
|
||||
break;
|
||||
case GLFW.GLFW_GAMEPAD_AXIS_LEFT_Y + 200:
|
||||
button_offset = 5 * 18;
|
||||
axis = true;
|
||||
break;
|
||||
case GLFW.GLFW_GAMEPAD_AXIS_RIGHT_X + 200:
|
||||
button_offset = 6 * 18;
|
||||
axis = true;
|
||||
break;
|
||||
case GLFW.GLFW_GAMEPAD_AXIS_RIGHT_Y + 200:
|
||||
button_offset = 7 * 18;
|
||||
axis = true;
|
||||
break;
|
||||
case GLFW.GLFW_GAMEPAD_AXIS_LEFT_TRIGGER + 100:
|
||||
case GLFW.GLFW_GAMEPAD_AXIS_LEFT_TRIGGER + 200:
|
||||
button_offset = 9 * 15;
|
||||
break;
|
||||
case GLFW.GLFW_GAMEPAD_AXIS_RIGHT_TRIGGER + 100:
|
||||
case GLFW.GLFW_GAMEPAD_AXIS_RIGHT_TRIGGER + 200:
|
||||
button_offset = 10 * 15;
|
||||
break;
|
||||
}
|
||||
|
||||
client.getTextureManager().bindTexture(axis ? LambdaControls.CONTROLLER_AXIS : LambdaControls.CONTROLLER_BUTTONS);
|
||||
GlStateManager.disableDepthTest();
|
||||
|
||||
GlStateManager.color4f(1.0F, 1.0F, 1.0F, 1.0F);
|
||||
DrawableHelper.blit(x, y, (float) button_offset, (float) (controller_type * (axis ? 18 : 15)), axis ? 18 : 15, axis ? 18 : 15, 256, 256);
|
||||
GlStateManager.enableDepthTest();
|
||||
|
||||
return axis ? 18 : 15;
|
||||
}
|
||||
|
||||
public static int draw_button_tip(int x, int y, @NotNull ButtonBinding button, boolean display, @NotNull MinecraftClient client)
|
||||
{
|
||||
return draw_button_tip(x, y, button.get_button(), button.get_translation_key(), display, client);
|
||||
}
|
||||
|
||||
public static int draw_button_tip(int x, int y, int button, @NotNull String action, boolean display, @NotNull MinecraftClient client)
|
||||
{
|
||||
String translated_action = I18n.translate(action);
|
||||
|
||||
if (display) {
|
||||
int button_width = draw_button(x, y, button, client);
|
||||
|
||||
int text_y = (15 - client.textRenderer.fontHeight) / 2;
|
||||
client.textRenderer.drawWithShadow(translated_action, (float) (x + button_width + 5), (float) (y + text_y), 14737632);
|
||||
}
|
||||
|
||||
return display ? get_button_tip_width(translated_action, client.textRenderer) : -10;
|
||||
}
|
||||
|
||||
private static int get_button_tip_width(@NotNull String action, @NotNull TextRenderer text_renderer)
|
||||
{
|
||||
return 15 + 5 + text_renderer.getStringWidth(action);
|
||||
}
|
||||
}
|
||||
@@ -1,399 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2019 LambdAurora <aurora42lambda@gmail.com>
|
||||
*
|
||||
* This file is part of LambdaControls.
|
||||
*
|
||||
* Licensed under the MIT license. For more information,
|
||||
* see the LICENSE file.
|
||||
*/
|
||||
|
||||
package me.lambdaurora.lambdacontrols;
|
||||
|
||||
import com.electronwill.nightconfig.core.file.FileConfig;
|
||||
import net.minecraft.client.options.KeyBinding;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.lwjgl.glfw.GLFW;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Represents LambdaControls configuration.
|
||||
*/
|
||||
public class LambdaControlsConfig
|
||||
{
|
||||
private final FileConfig config = FileConfig.builder("config/lambdacontrols.toml").concurrent().defaultResource("/config.toml").build();
|
||||
private final Map<String, KeyBinding> keybinding_mappings = new HashMap<>();
|
||||
private final LambdaControls mod;
|
||||
private ControlsMode controls_mode;
|
||||
private ControllerType controller_type;
|
||||
// HUD settings.
|
||||
private boolean hud_enable;
|
||||
private HudSide hud_side;
|
||||
// Controller settings
|
||||
private double dead_zone;
|
||||
private double rotation_speed;
|
||||
private double mouse_speed;
|
||||
|
||||
public LambdaControlsConfig(@NotNull LambdaControls mod)
|
||||
{
|
||||
this.mod = mod;
|
||||
}
|
||||
|
||||
public void load()
|
||||
{
|
||||
this.keybinding_mappings.clear();
|
||||
this.config.load();
|
||||
this.mod.log("Configuration loaded.");
|
||||
this.controls_mode = ControlsMode.by_id(this.config.getOrElse("controls", "default")).orElse(ControlsMode.DEFAULT);
|
||||
// HUD settings.
|
||||
this.hud_enable = this.config.getOrElse("hud.enable", true);
|
||||
this.hud_side = HudSide.by_id(this.config.getOrElse("hud.side", "left")).orElse(HudSide.LEFT);
|
||||
// Controller settings.
|
||||
this.controller_type = ControllerType.by_id(this.config.getOrElse("controller.type", "default")).orElse(ControllerType.DEFAULT);
|
||||
this.dead_zone = this.config.getOrElse("controller.dead_zone", 0.25);
|
||||
this.rotation_speed = this.config.getOrElse("controller.rotation_speed", 40.0);
|
||||
this.mouse_speed = this.config.getOrElse("controller.mouse_speed", 25.0);
|
||||
// Controller controls.
|
||||
ButtonBinding.load_from_config(this);
|
||||
}
|
||||
|
||||
public void save()
|
||||
{
|
||||
this.config.set("controller.dead_zone", this.dead_zone);
|
||||
this.config.set("controller.rotation_speed", this.rotation_speed);
|
||||
this.config.set("controller.mouse_speed", this.mouse_speed);
|
||||
this.config.save();
|
||||
this.mod.log("Configuration saved.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the controls mode from the configuration.
|
||||
*
|
||||
* @return The controls mode.
|
||||
*/
|
||||
public @NotNull ControlsMode get_controls_mode()
|
||||
{
|
||||
return this.controls_mode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the controls mode in the configuration.
|
||||
*
|
||||
* @param controls_mode The controls mode.
|
||||
*/
|
||||
public void set_controls_mode(@NotNull ControlsMode controls_mode)
|
||||
{
|
||||
this.controls_mode = controls_mode;
|
||||
this.config.set("controls", controls_mode.get_name());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the auto switch mode is enabled or not.
|
||||
*
|
||||
* @return True if the auto switch mode is enabled, else false.
|
||||
*/
|
||||
public boolean has_auto_switch_mode()
|
||||
{
|
||||
return this.config.getOrElse("auto_switch_mode", false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the auto switch mode is enabled or not.
|
||||
*
|
||||
* @param auto_switch_mode True if the auto switch mode is enabled, else false.
|
||||
*/
|
||||
public void set_auto_switch_mode(boolean auto_switch_mode)
|
||||
{
|
||||
this.config.set("auto_switch_mode", auto_switch_mode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the HUD is enabled.
|
||||
*
|
||||
* @return True if the HUD is enabled, else false.
|
||||
*/
|
||||
public boolean is_hud_enabled()
|
||||
{
|
||||
return this.hud_enable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the HUD is enabled.
|
||||
*
|
||||
* @param enable True if the HUD is enabled, else false.
|
||||
*/
|
||||
public void set_hud_enabled(boolean enable)
|
||||
{
|
||||
this.hud_enable = enable;
|
||||
this.config.set("hud.enable", this.hud_enable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the HUD side from the configuration.
|
||||
*
|
||||
* @return The HUD side.
|
||||
*/
|
||||
public @NotNull HudSide get_hud_side()
|
||||
{
|
||||
return this.hud_side;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the HUD side in the configuration.
|
||||
*
|
||||
* @param hud_side The HUD side.
|
||||
*/
|
||||
public void set_hud_side(@NotNull HudSide hud_side)
|
||||
{
|
||||
this.hud_side = hud_side;
|
||||
this.config.set("hud.side", hud_side.get_name());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the used controller.
|
||||
*
|
||||
* @return The used controller.
|
||||
*/
|
||||
public @NotNull Controller get_controller()
|
||||
{
|
||||
Object raw = this.config.getRaw("controller.id");
|
||||
if (raw instanceof Number) {
|
||||
return Controller.by_id((Integer) raw);
|
||||
} else if (raw instanceof String) {
|
||||
return Controller.by_guid((String) raw).orElse(Controller.by_id(GLFW.GLFW_JOYSTICK_1));
|
||||
}
|
||||
return Controller.by_id(GLFW.GLFW_JOYSTICK_1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the used controller.
|
||||
*
|
||||
* @param controller The used controller.
|
||||
*/
|
||||
public void set_controller(@NotNull Controller controller)
|
||||
{
|
||||
this.config.set("controller.id", controller.get_id());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the controller's type.
|
||||
*
|
||||
* @return The controller's type.
|
||||
*/
|
||||
public @NotNull ControllerType get_controller_type()
|
||||
{
|
||||
return this.controller_type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the controller's type.
|
||||
*
|
||||
* @param controller_type The controller's type.
|
||||
*/
|
||||
public void set_controller_type(@NotNull ControllerType controller_type)
|
||||
{
|
||||
this.controller_type = controller_type;
|
||||
this.config.set("controller.type", controller_type.get_name());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the controller's dead zone from the configuration.
|
||||
*
|
||||
* @return The controller's dead zone value.
|
||||
*/
|
||||
public double get_dead_zone()
|
||||
{
|
||||
return this.dead_zone;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the controller's dead zone in the configuration.
|
||||
*
|
||||
* @param dead_zone The new controller's dead zone value.
|
||||
*/
|
||||
public void set_dead_zone(double dead_zone)
|
||||
{
|
||||
this.dead_zone = dead_zone;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the controller's rotation speed.
|
||||
*
|
||||
* @return The rotation speed.
|
||||
*/
|
||||
public double get_rotation_speed()
|
||||
{
|
||||
return this.rotation_speed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the controller's rotation speed.
|
||||
*
|
||||
* @param rotation_speed The rotation speed.
|
||||
*/
|
||||
public void set_rotation_speed(double rotation_speed)
|
||||
{
|
||||
this.rotation_speed = rotation_speed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the controller's mouse speed.
|
||||
*
|
||||
* @return The mouse speed.
|
||||
*/
|
||||
public double get_mouse_speed()
|
||||
{
|
||||
return this.mouse_speed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the controller's mouse speed.
|
||||
*
|
||||
* @param mouse_speed The mouse speed.
|
||||
*/
|
||||
public void set_mouse_speed(double mouse_speed)
|
||||
{
|
||||
this.mouse_speed = mouse_speed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the right X axis is inverted or not.
|
||||
*
|
||||
* @return True if the right X axis is inverted, else false.
|
||||
*/
|
||||
public boolean does_invert_right_x_axis()
|
||||
{
|
||||
return this.config.getOrElse("controller.invert_right_x_axis", false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the right X axis is inverted or not.
|
||||
*
|
||||
* @param invert True if the right X axis is inverted, else false.
|
||||
*/
|
||||
public void set_invert_right_x_axis(boolean invert)
|
||||
{
|
||||
this.config.set("controller.invert_right_x_axis", invert);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the right Y axis is inverted or not.
|
||||
*
|
||||
* @return True if the right Y axis is inverted, else false.
|
||||
*/
|
||||
public boolean does_invert_right_y_axis()
|
||||
{
|
||||
return this.config.getOrElse("controller.invert_right_y_axis", false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the right Y axis is inverted or not.
|
||||
*
|
||||
* @param invert True if the right Y axis is inverted, else false.
|
||||
*/
|
||||
public void set_invert_right_y_axis(boolean invert)
|
||||
{
|
||||
this.config.set("controller.invert_right_y_axis", invert);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the right X axis sign.
|
||||
*
|
||||
* @return The right X axis sign.
|
||||
*/
|
||||
public double get_right_x_axis_sign()
|
||||
{
|
||||
return this.does_invert_right_x_axis() ? -1.0 : 1.0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the right Y axis sign.
|
||||
*
|
||||
* @return The right Y axis sign.
|
||||
*/
|
||||
public double get_right_y_axis_sign()
|
||||
{
|
||||
return this.does_invert_right_y_axis() ? -1.0 : 1.0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the keybindings.
|
||||
*
|
||||
* @return The keybindings.
|
||||
*/
|
||||
public @NotNull Map<String, KeyBinding> get_keybindings()
|
||||
{
|
||||
return this.keybinding_mappings;
|
||||
}
|
||||
|
||||
public Optional<KeyBinding> get_keybind(@NotNull String id)
|
||||
{
|
||||
return Optional.ofNullable(this.keybinding_mappings.get(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the button binding from configuration.
|
||||
*
|
||||
* @param button The button binding.
|
||||
*/
|
||||
public void load_button_binding(@NotNull ButtonBinding button)
|
||||
{
|
||||
button.set_button(this.config.getOrElse("controller.controls." + button.get_name(), button.get_button()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the button binding in configuration.
|
||||
*
|
||||
* @param binding The button binding.
|
||||
* @param button The button.
|
||||
*/
|
||||
public void set_button_binding(@NotNull ButtonBinding binding, int button)
|
||||
{
|
||||
binding.set_button(button);
|
||||
this.config.set("controller.controls." + binding.get_name(), button);
|
||||
}
|
||||
|
||||
public boolean is_back_button(int btn, boolean is_btn, int state)
|
||||
{
|
||||
if (!is_btn && state == 0)
|
||||
return false;
|
||||
return ButtonBinding.BACK.is_button(ButtonBinding.axis_as_button(btn, state == 1));
|
||||
}
|
||||
|
||||
public boolean is_forward_button(int btn, boolean is_btn, int state)
|
||||
{
|
||||
if (!is_btn && state == 0)
|
||||
return false;
|
||||
return ButtonBinding.FORWARD.is_button(ButtonBinding.axis_as_button(btn, state == 1));
|
||||
}
|
||||
|
||||
public boolean is_left_button(int btn, boolean is_btn, int state)
|
||||
{
|
||||
if (!is_btn && state == 0)
|
||||
return false;
|
||||
return ButtonBinding.LEFT.is_button(ButtonBinding.axis_as_button(btn, state == 1));
|
||||
}
|
||||
|
||||
public boolean is_right_button(int btn, boolean is_btn, int state)
|
||||
{
|
||||
if (!is_btn && state == 0)
|
||||
return false;
|
||||
return ButtonBinding.RIGHT.is_button(ButtonBinding.axis_as_button(btn, state == 1));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the specified axis is an axis used for movements.
|
||||
*
|
||||
* @param i The axis index.
|
||||
* @return True if the axis is used for movements, else false.
|
||||
*/
|
||||
public boolean is_movement_axis(int i)
|
||||
{
|
||||
return ButtonBinding.FORWARD.is_button(ButtonBinding.axis_as_button(i, true)) || ButtonBinding.FORWARD.is_button(ButtonBinding.axis_as_button(i, false))
|
||||
|| ButtonBinding.BACK.is_button(ButtonBinding.axis_as_button(i, true)) || ButtonBinding.BACK.is_button(ButtonBinding.axis_as_button(i, false))
|
||||
|| ButtonBinding.LEFT.is_button(ButtonBinding.axis_as_button(i, true)) || ButtonBinding.LEFT.is_button(ButtonBinding.axis_as_button(i, false))
|
||||
|| ButtonBinding.RIGHT.is_button(ButtonBinding.axis_as_button(i, true)) || ButtonBinding.RIGHT.is_button(ButtonBinding.axis_as_button(i, false));
|
||||
}
|
||||
}
|
||||
@@ -1,671 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2019 LambdAurora <aurora42lambda@gmail.com>
|
||||
*
|
||||
* This file is part of LambdaControls.
|
||||
*
|
||||
* Licensed under the MIT license. For more information,
|
||||
* see the LICENSE file.
|
||||
*/
|
||||
|
||||
package me.lambdaurora.lambdacontrols;
|
||||
|
||||
import me.lambdaurora.lambdacontrols.gui.LabelWidget;
|
||||
import me.lambdaurora.lambdacontrols.gui.LambdaControlsControlsScreen;
|
||||
import me.lambdaurora.lambdacontrols.gui.TouchscreenOverlay;
|
||||
import me.lambdaurora.lambdacontrols.mixin.EntryListWidgetAccessor;
|
||||
import me.lambdaurora.lambdacontrols.util.AbstractContainerScreenAccessor;
|
||||
import me.lambdaurora.lambdacontrols.util.CreativeInventoryScreenAccessor;
|
||||
import me.lambdaurora.lambdacontrols.util.KeyBindingAccessor;
|
||||
import me.lambdaurora.lambdacontrols.util.MouseAccessor;
|
||||
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.AbstractContainerScreen;
|
||||
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.item.ItemGroup;
|
||||
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.ButtonBinding.*;
|
||||
import static org.lwjgl.glfw.GLFW.GLFW_GAMEPAD_AXIS_RIGHT_X;
|
||||
import static org.lwjgl.glfw.GLFW.GLFW_GAMEPAD_AXIS_RIGHT_Y;
|
||||
|
||||
/**
|
||||
* Represents the LambdaControls' input handler.
|
||||
*/
|
||||
public class LambdaInput
|
||||
{
|
||||
private static final Map<Integer, Boolean> BUTTON_STATES = new HashMap<>();
|
||||
private static final Map<Integer, Integer> BUTTON_COOLDOWNS = new HashMap<>();
|
||||
private static final Map<Integer, Boolean> AXIS_STATES = new HashMap<>();
|
||||
private static final Map<Integer, Integer> AXIS_COOLDOWNS = new HashMap<>();
|
||||
private final LambdaControlsConfig config;
|
||||
private int action_gui_cooldown = 0;
|
||||
private int ignore_next_a = 0;
|
||||
private int last_sneak = 0;
|
||||
private double prev_target_yaw = 0.0;
|
||||
private double prev_target_pitch = 0.0;
|
||||
private double target_yaw = 0.0;
|
||||
private double target_pitch = 0.0;
|
||||
private float prev_x_axis = 0.F;
|
||||
private float prev_y_axis = 0.F;
|
||||
private int prev_target_mouse_x = 0;
|
||||
private int prev_target_mouse_y = 0;
|
||||
private int target_mouse_x = 0;
|
||||
private int target_mouse_y = 0;
|
||||
private float mouse_speed_x = 0.F;
|
||||
private float mouse_speed_y = 0.F;
|
||||
|
||||
public LambdaInput(@NotNull LambdaControls mod)
|
||||
{
|
||||
this.config = mod.config;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called every Minecraft tick.
|
||||
*
|
||||
* @param client The client instance.
|
||||
*/
|
||||
public void on_tick(@NotNull MinecraftClient client)
|
||||
{
|
||||
this.prev_target_yaw = this.target_yaw;
|
||||
this.prev_target_pitch = this.target_pitch;
|
||||
|
||||
// Handles the key bindings.
|
||||
if (LambdaControls.BINDING_LOOK_UP.isPressed()) {
|
||||
this.handle_look(client, GLFW_GAMEPAD_AXIS_RIGHT_Y, 0.8F, 2);
|
||||
} else if (LambdaControls.BINDING_LOOK_DOWN.isPressed()) {
|
||||
this.handle_look(client, GLFW_GAMEPAD_AXIS_RIGHT_Y, 0.8F, 1);
|
||||
}
|
||||
if (LambdaControls.BINDING_LOOK_LEFT.isPressed()) {
|
||||
this.handle_look(client, GLFW_GAMEPAD_AXIS_RIGHT_X, 0.8F, 2);
|
||||
} else if (LambdaControls.BINDING_LOOK_RIGHT.isPressed()) {
|
||||
this.handle_look(client, GLFW_GAMEPAD_AXIS_RIGHT_X, 0.8F, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called every Minecraft tick for controller input update.
|
||||
*
|
||||
* @param client The client instance.
|
||||
*/
|
||||
public void on_controller_tick(@NotNull MinecraftClient client)
|
||||
{
|
||||
BUTTON_COOLDOWNS.entrySet().stream().filter(entry -> entry.getValue() > 0).forEach(entry -> BUTTON_COOLDOWNS.put(entry.getKey(), entry.getValue() - 1));
|
||||
AXIS_COOLDOWNS.entrySet().stream().filter(entry -> entry.getValue() > 0).forEach(entry -> AXIS_COOLDOWNS.put(entry.getKey(), entry.getValue() - 1));
|
||||
// Decreases the last_sneak counter which allows to double press to sneak continuously.
|
||||
if (this.last_sneak > 0)
|
||||
--this.last_sneak;
|
||||
// Decreases the cooldown for GUI actions.
|
||||
if (this.action_gui_cooldown > 0)
|
||||
--this.action_gui_cooldown;
|
||||
this.prev_target_mouse_x = this.target_mouse_x;
|
||||
this.prev_target_mouse_y = this.target_mouse_y;
|
||||
|
||||
Controller controller = this.config.get_controller();
|
||||
if (controller.is_connected()) {
|
||||
GLFWGamepadState state = controller.get_state();
|
||||
this.fetch_button_input(client, state);
|
||||
this.fetch_axe_input(client, state);
|
||||
}
|
||||
|
||||
if (this.ignore_next_a > 0)
|
||||
this.ignore_next_a--;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called before the screen is rendered.
|
||||
*
|
||||
* @param client The client instance.
|
||||
* @param screen The screen to render.
|
||||
*/
|
||||
public void on_pre_render_screen(@NotNull MinecraftClient client, @NotNull Screen screen)
|
||||
{
|
||||
if (!is_screen_interactive(screen)) {
|
||||
if (this.prev_target_mouse_x != this.target_mouse_x || this.prev_target_mouse_y != this.target_mouse_y) {
|
||||
double mouse_x = this.prev_target_mouse_x + (this.target_mouse_x - this.prev_target_mouse_x) * client.getTickDelta() + 0.5;
|
||||
double mouse_y = this.prev_target_mouse_y + (this.target_mouse_y - this.prev_target_mouse_y) * client.getTickDelta() + 0.5;
|
||||
GLFW.glfwSetCursorPos(client.getWindow().getHandle(), mouse_x, mouse_y);
|
||||
((MouseAccessor) client.mouse).on_cursor_pos(client.getWindow().getHandle(), mouse_x, mouse_y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called when Minecraft renders.
|
||||
*
|
||||
* @param client The client instance.
|
||||
*/
|
||||
public void on_render(@NotNull MinecraftClient client)
|
||||
{
|
||||
if ((client.currentScreen == null || client.currentScreen instanceof TouchscreenOverlay) &&
|
||||
(this.prev_target_yaw != this.target_yaw || this.prev_target_pitch != this.target_pitch)) {
|
||||
float delta_yaw = (float) ((this.target_yaw - client.player.prevYaw) * client.getTickDelta());
|
||||
float delta_pitch = (float) ((this.target_pitch - client.player.prevPitch) * client.getTickDelta());
|
||||
float rotation_yaw = client.player.prevYaw + delta_yaw;
|
||||
float rotation_pitch = client.player.prevPitch + delta_pitch;
|
||||
client.player.yaw = rotation_yaw;
|
||||
client.player.pitch = MathHelper.clamp(rotation_pitch, -90.F, 90.F);
|
||||
if (client.player.isRiding()) {
|
||||
client.player.getVehicle().copyPositionAndRotation(client.player);
|
||||
}
|
||||
client.getTutorialManager().onUpdateMouse(delta_pitch, delta_yaw);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called when a Screen is opened.
|
||||
*
|
||||
* @param client The client instance.
|
||||
* @param window_width The window width.
|
||||
* @param window_height The window height.
|
||||
*/
|
||||
public void on_screen_open(@NotNull MinecraftClient client, int window_width, int window_height)
|
||||
{
|
||||
if (client.currentScreen == null) {
|
||||
this.mouse_speed_x = this.mouse_speed_y = 0.0F;
|
||||
this.target_mouse_x = this.prev_target_mouse_x = (int) (window_width / 2.F);
|
||||
this.target_mouse_y = this.prev_target_mouse_y = (int) (window_height / 2.F);
|
||||
}
|
||||
}
|
||||
|
||||
private void fetch_button_input(@NotNull MinecraftClient client, @NotNull GLFWGamepadState gamepad_state)
|
||||
{
|
||||
ByteBuffer buffer = gamepad_state.buttons();
|
||||
for (int btn = 0; btn < buffer.limit(); btn++) {
|
||||
boolean btn_state = buffer.get() == (byte) 1;
|
||||
boolean previous_state = BUTTON_STATES.getOrDefault(btn, false);
|
||||
|
||||
ButtonBinding.set_button_state(btn, btn_state);
|
||||
|
||||
if (btn_state != previous_state) {
|
||||
this.handle_button(client, btn, btn_state ? 0 : 1, btn_state);
|
||||
if (btn_state)
|
||||
BUTTON_COOLDOWNS.put(btn, 5);
|
||||
} else if (btn_state) {
|
||||
if (BUTTON_COOLDOWNS.getOrDefault(btn, 0) == 0) {
|
||||
BUTTON_COOLDOWNS.put(btn, 5);
|
||||
this.handle_button(client, btn, 2, true);
|
||||
}
|
||||
}
|
||||
|
||||
BUTTON_STATES.put(btn, btn_state);
|
||||
}
|
||||
}
|
||||
|
||||
private void fetch_axe_input(@NotNull MinecraftClient client, @NotNull GLFWGamepadState gamepad_state)
|
||||
{
|
||||
FloatBuffer buffer = gamepad_state.axes();
|
||||
for (int i = 0; i < buffer.limit(); i++) {
|
||||
float value = buffer.get();
|
||||
float abs_value = Math.abs(value);
|
||||
|
||||
if (i == GLFW.GLFW_GAMEPAD_AXIS_LEFT_Y)
|
||||
value *= -1.0F;
|
||||
|
||||
ButtonBinding.set_button_state(axis_as_button(i, true), value > 0.5F);
|
||||
ButtonBinding.set_button_state(axis_as_button(i, false), value < -0.5F);
|
||||
|
||||
int state = value > this.config.get_dead_zone() ? 1 : (value < -this.config.get_dead_zone() ? 2 : 0);
|
||||
this.handle_axe(client, i, value, abs_value, state);
|
||||
}
|
||||
}
|
||||
|
||||
private void handle_button(@NotNull MinecraftClient client, int button, int action, boolean state)
|
||||
{
|
||||
if (client.currentScreen instanceof LambdaControlsControlsScreen && action == 0) {
|
||||
LambdaControlsControlsScreen controls_screen = (LambdaControlsControlsScreen) client.currentScreen;
|
||||
if (controls_screen.focused_binding != null) {
|
||||
this.config.set_button_binding(controls_screen.focused_binding, button);
|
||||
controls_screen.focused_binding = null;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (action == 0 || action == 2) {
|
||||
// Handles RB and LB buttons.
|
||||
if (button == GLFW.GLFW_GAMEPAD_BUTTON_LEFT_BUMPER || button == GLFW.GLFW_GAMEPAD_BUTTON_RIGHT_BUMPER) {
|
||||
this.handle_rb_lb(client, button == GLFW.GLFW_GAMEPAD_BUTTON_RIGHT_BUMPER);
|
||||
return;
|
||||
}
|
||||
|
||||
if (client.currentScreen != null && is_screen_interactive(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.action_gui_cooldown == 0) {
|
||||
if (button == GLFW.GLFW_GAMEPAD_BUTTON_DPAD_UP) {
|
||||
this.change_focus(client.currentScreen, false);
|
||||
} else if (button == GLFW.GLFW_GAMEPAD_BUTTON_DPAD_DOWN) {
|
||||
this.change_focus(client.currentScreen, true);
|
||||
} else if (button == GLFW.GLFW_GAMEPAD_BUTTON_DPAD_LEFT) {
|
||||
this.handle_left_right(client.currentScreen, false);
|
||||
} else {
|
||||
this.handle_left_right(client.currentScreen, true);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (action == 0) {
|
||||
// Handles when the player presses the Start button.
|
||||
if (PAUSE_GAME.is_button(button)) {
|
||||
// If in game, then pause the game.
|
||||
if (client.currentScreen == null)
|
||||
client.openPauseMenu(false);
|
||||
else if (client.currentScreen instanceof AbstractContainerScreen) // If the current screen is a container then close it.
|
||||
client.player.closeContainer();
|
||||
else// Else just close the current screen.
|
||||
client.currentScreen.onClose();
|
||||
return;
|
||||
}
|
||||
|
||||
if (button == GLFW.GLFW_GAMEPAD_BUTTON_A && client.currentScreen != null) {
|
||||
if (this.action_gui_cooldown == 0) {
|
||||
Element focused = client.currentScreen.getFocused();
|
||||
if (focused != null && is_screen_interactive(client.currentScreen)) {
|
||||
if (this.handle_a_button(client.currentScreen, focused)) {
|
||||
this.action_gui_cooldown = 5; // Prevent to press too quickly the focused element, so we have to skip 5 ticks.
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (client.currentScreen instanceof AbstractContainerScreen && client.interactionManager != null && client.player != null) {
|
||||
double pos_x = client.mouse.getX() * (double) client.getWindow().getScaledWidth() / (double) client.getWindow().getWidth();
|
||||
double pos_y = client.mouse.getY() * (double) client.getWindow().getScaledHeight() / (double) client.getWindow().getHeight();
|
||||
Slot slot = ((AbstractContainerScreenAccessor) client.currentScreen).get_slot_at(pos_x, pos_y);
|
||||
if (button == GLFW.GLFW_GAMEPAD_BUTTON_A && slot != null) {
|
||||
client.interactionManager.clickSlot(((AbstractContainerScreen) client.currentScreen).getContainer().syncId, slot.id, GLFW.GLFW_MOUSE_BUTTON_1, SlotActionType.PICKUP, client.player);
|
||||
this.action_gui_cooldown = 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(((AbstractContainerScreen) 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(((AbstractContainerScreen) 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;
|
||||
}
|
||||
}
|
||||
|
||||
// Handles sneak button.
|
||||
if (SNEAK.is_button(button) && client.player != null) {
|
||||
this.toggle_sneaking(client);
|
||||
}
|
||||
}
|
||||
|
||||
if (button == GLFW.GLFW_GAMEPAD_BUTTON_A && client.currentScreen != null && !is_screen_interactive(client.currentScreen) && this.action_gui_cooldown == 0 && this.ignore_next_a == 0) {
|
||||
double mouse_x = client.mouse.getX() * (double) client.getWindow().getScaledWidth() / (double) client.getWindow().getWidth();
|
||||
double mouse_y = client.mouse.getY() * (double) client.getWindow().getScaledHeight() / (double) client.getWindow().getHeight();
|
||||
if (action == 0) {
|
||||
client.currentScreen.mouseClicked(mouse_x, mouse_y, GLFW.GLFW_MOUSE_BUTTON_1);
|
||||
} else if (action == 1) {
|
||||
client.currentScreen.mouseReleased(mouse_x, mouse_y, GLFW.GLFW_MOUSE_BUTTON_1);
|
||||
}
|
||||
this.action_gui_cooldown = 5;
|
||||
return;
|
||||
}
|
||||
|
||||
if (client.currentScreen == null && action != 2) {
|
||||
ButtonBinding.handle_button(client, button, action);
|
||||
}
|
||||
}
|
||||
|
||||
private void handle_axe(@NotNull MinecraftClient client, int axis, float value, float abs_value, int state)
|
||||
{
|
||||
int as_button_state = value > 0.5F ? 1 : (value < -0.5F ? 2 : 0);
|
||||
|
||||
if (client.currentScreen instanceof LambdaControlsControlsScreen && as_button_state != 0
|
||||
&& !(as_button_state == 2 && (axis == GLFW.GLFW_GAMEPAD_AXIS_LEFT_TRIGGER || axis == GLFW.GLFW_GAMEPAD_AXIS_RIGHT_TRIGGER))) {
|
||||
LambdaControlsControlsScreen controls_screen = (LambdaControlsControlsScreen) client.currentScreen;
|
||||
if (controls_screen.focused_binding != null) {
|
||||
this.config.set_button_binding(controls_screen.focused_binding, axis_as_button(axis, as_button_state == 1));
|
||||
controls_screen.focused_binding = null;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
double dead_zone = this.config.get_dead_zone();
|
||||
if (client.currentScreen == null) {
|
||||
{
|
||||
int axis_minus = axis + 100;
|
||||
boolean current_plus_state = as_button_state == 1;
|
||||
boolean current_minus_state = as_button_state == 2;
|
||||
boolean previous_plus_state = AXIS_STATES.getOrDefault(axis, false);
|
||||
boolean previous_minus_state = AXIS_STATES.getOrDefault(axis_minus, false);
|
||||
|
||||
if (current_plus_state != previous_plus_state) {
|
||||
ButtonBinding.handle_button(client, ButtonBinding.axis_as_button(axis, true), 0);
|
||||
if (current_plus_state)
|
||||
AXIS_COOLDOWNS.put(axis, 5);
|
||||
} else if (current_plus_state) {
|
||||
if (AXIS_COOLDOWNS.getOrDefault(axis, 0) == 0) {
|
||||
AXIS_COOLDOWNS.put(axis, 5);
|
||||
}
|
||||
}
|
||||
|
||||
if (current_minus_state != previous_minus_state) {
|
||||
ButtonBinding.handle_button(client, ButtonBinding.axis_as_button(axis, false), 0);
|
||||
if (current_minus_state)
|
||||
AXIS_COOLDOWNS.put(axis_minus, 5);
|
||||
} else if (current_minus_state) {
|
||||
if (AXIS_COOLDOWNS.getOrDefault(axis_minus, 0) == 0) {
|
||||
AXIS_COOLDOWNS.put(axis_minus, 5);
|
||||
}
|
||||
}
|
||||
|
||||
AXIS_STATES.put(axis, current_plus_state);
|
||||
AXIS_STATES.put(axis_minus, current_minus_state);
|
||||
}
|
||||
|
||||
// Handles the look direction.
|
||||
this.handle_look(client, axis, abs_value, state);
|
||||
} else {
|
||||
boolean allow_mouse_control = true;
|
||||
|
||||
if (this.action_gui_cooldown == 0 && this.config.is_movement_axis(axis) && is_screen_interactive(client.currentScreen)) {
|
||||
if (this.config.is_forward_button(axis, false, as_button_state)) {
|
||||
allow_mouse_control = this.change_focus(client.currentScreen, false);
|
||||
} else if (this.config.is_back_button(axis, false, as_button_state)) {
|
||||
allow_mouse_control = this.change_focus(client.currentScreen, true);
|
||||
} else if (this.config.is_left_button(axis, false, as_button_state)) {
|
||||
allow_mouse_control = this.handle_left_right(client.currentScreen, false);
|
||||
} else if (this.config.is_right_button(axis, false, as_button_state)) {
|
||||
allow_mouse_control = this.handle_left_right(client.currentScreen, true);
|
||||
}
|
||||
}
|
||||
|
||||
float movement_x = 0.0F;
|
||||
float movement_y = 0.0F;
|
||||
|
||||
if (this.config.is_back_button(axis, false, (value > 0 ? 1 : 2))) {
|
||||
movement_y = abs_value;
|
||||
} else if (this.config.is_forward_button(axis, false, (value > 0 ? 1 : 2))) {
|
||||
movement_y = -abs_value;
|
||||
} else if (this.config.is_left_button(axis, false, (value > 0 ? 1 : 2))) {
|
||||
movement_x = -abs_value;
|
||||
} else if (this.config.is_right_button(axis, false, (value > 0 ? 1 : 2))) {
|
||||
movement_x = abs_value;
|
||||
}
|
||||
|
||||
if (client.currentScreen != null && allow_mouse_control) {
|
||||
boolean moving = Math.abs(movement_y) >= dead_zone || Math.abs(movement_x) >= dead_zone;
|
||||
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(prev_x_axis) < dead_zone && Math.abs(prev_y_axis) < dead_zone) {
|
||||
double mouse_x = client.mouse.getX();
|
||||
double mouse_y = client.mouse.getY();
|
||||
prev_target_mouse_x = target_mouse_x = (int) mouse_x;
|
||||
prev_target_mouse_y = target_mouse_y = (int) mouse_y;
|
||||
}
|
||||
|
||||
if (Math.abs(movement_x) >= dead_zone)
|
||||
this.mouse_speed_x = movement_x;
|
||||
else
|
||||
this.mouse_speed_x = 0.F;
|
||||
|
||||
if (Math.abs(movement_y) >= dead_zone)
|
||||
this.mouse_speed_y = movement_y;
|
||||
else
|
||||
this.mouse_speed_y = 0.F;
|
||||
} else {
|
||||
this.mouse_speed_x = 0.F;
|
||||
this.mouse_speed_y = 0.F;
|
||||
}
|
||||
|
||||
if (Math.abs(this.mouse_speed_x) > .05F || Math.abs(this.mouse_speed_y) > .05F) {
|
||||
this.target_mouse_x += this.mouse_speed_x * this.config.get_mouse_speed();
|
||||
this.target_mouse_x = MathHelper.clamp(this.target_mouse_x, 0, client.getWindow().getWidth());
|
||||
this.target_mouse_y += this.mouse_speed_y * this.config.get_mouse_speed();
|
||||
this.target_mouse_y = MathHelper.clamp(this.target_mouse_y, 0, client.getWindow().getHeight());
|
||||
}
|
||||
|
||||
this.move_mouse_to_closest_slot(client, client.currentScreen);
|
||||
}
|
||||
|
||||
this.prev_x_axis = movement_x;
|
||||
this.prev_y_axis = movement_y;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the press on RB on LB.
|
||||
*
|
||||
* @param client The client's instance.
|
||||
* @param right True if RB is pressed, else false.
|
||||
*/
|
||||
private void handle_rb_lb(@NotNull MinecraftClient client, boolean right)
|
||||
{
|
||||
// When ingame
|
||||
if (client.currentScreen == null && client.player != null) {
|
||||
if (right)
|
||||
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;
|
||||
} else if (client.currentScreen instanceof CreativeInventoryScreen) {
|
||||
CreativeInventoryScreenAccessor creative_inventory = (CreativeInventoryScreenAccessor) client.currentScreen;
|
||||
int current_selected_tab = creative_inventory.get_selected_tab();
|
||||
int next_tab = current_selected_tab + (right ? 1 : -1);
|
||||
if (next_tab < 0)
|
||||
next_tab = ItemGroup.GROUPS.length - 1;
|
||||
else if (next_tab >= ItemGroup.GROUPS.length)
|
||||
next_tab = 0;
|
||||
creative_inventory.set_selected_tab(ItemGroup.GROUPS[next_tab]);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean handle_a_button(@NotNull Screen screen, @NotNull Element focused)
|
||||
{
|
||||
if (focused instanceof AbstractPressableButtonWidget) {
|
||||
AbstractPressableButtonWidget button_widget = (AbstractPressableButtonWidget) focused;
|
||||
button_widget.playDownSound(MinecraftClient.getInstance().getSoundManager());
|
||||
button_widget.onPress();
|
||||
return true;
|
||||
} else if (focused instanceof LabelWidget) {
|
||||
((LabelWidget) focused).on_press();
|
||||
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 child_focused = ((ParentElement) focused).getFocused();
|
||||
if (child_focused != null)
|
||||
return this.handle_a_button(screen, child_focused);
|
||||
}
|
||||
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 handle_left_right(@NotNull Screen screen, boolean right)
|
||||
{
|
||||
Element focused = screen.getFocused();
|
||||
if (focused != null)
|
||||
if (this.handle_right_left_element(focused, right))
|
||||
return this.change_focus(screen, right);
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean handle_right_left_element(@NotNull Element element, boolean right)
|
||||
{
|
||||
if (element instanceof SliderWidget) {
|
||||
SliderWidget slider = (SliderWidget) element;
|
||||
slider.keyPressed(right ? 262 : 263, 0, 0);
|
||||
this.action_gui_cooldown = 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).move_selection(right ? 1 : -1);
|
||||
return false;
|
||||
} else if (element instanceof ParentElement) {
|
||||
ParentElement entry_list = (ParentElement) element;
|
||||
Element focused = entry_list.getFocused();
|
||||
if (focused == null)
|
||||
return true;
|
||||
return this.handle_right_left_element(focused, right);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles whether the player is sneaking.
|
||||
*
|
||||
* @param client The client's instance.
|
||||
*/
|
||||
private void toggle_sneaking(@NotNull MinecraftClient client)
|
||||
{
|
||||
((KeyBindingAccessor) client.options.keySneak).handle_press_state(!client.options.keySneak.isPressed());
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 handle_look(@NotNull MinecraftClient client, int axis, float value, int state)
|
||||
{
|
||||
// Handles the look direction.
|
||||
if (client.player != null) {
|
||||
double pow_value = Math.pow(value, 2.0);
|
||||
if (axis == GLFW_GAMEPAD_AXIS_RIGHT_Y) {
|
||||
if (state == 2) {
|
||||
this.target_pitch = client.player.pitch - this.config.get_right_y_axis_sign() * (this.config.get_rotation_speed() * pow_value) * 0.33D;
|
||||
this.target_pitch = MathHelper.clamp(this.target_pitch, -90.0D, 90.0D);
|
||||
} else if (state == 1) {
|
||||
this.target_pitch = client.player.pitch + this.config.get_right_y_axis_sign() * (this.config.get_rotation_speed() * pow_value) * 0.33D;
|
||||
this.target_pitch = MathHelper.clamp(this.target_pitch, -90.0D, 90.0D);
|
||||
}
|
||||
}
|
||||
if (axis == GLFW_GAMEPAD_AXIS_RIGHT_X) {
|
||||
if (state == 2) {
|
||||
this.target_yaw = client.player.yaw - this.config.get_right_x_axis_sign() * (this.config.get_rotation_speed() * pow_value) * 0.33D;
|
||||
} else if (state == 1) {
|
||||
this.target_yaw = client.player.yaw + this.config.get_right_x_axis_sign() * (this.config.get_rotation_speed() * pow_value) * 0.33D;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean change_focus(@NotNull Screen screen, boolean down)
|
||||
{
|
||||
if (!screen.changeFocus(down)) {
|
||||
if (screen.changeFocus(down)) {
|
||||
this.action_gui_cooldown = 5;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
this.action_gui_cooldown = 5;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean is_screen_interactive(@NotNull Screen screen)
|
||||
{
|
||||
return !(screen instanceof AdvancementsScreen || screen instanceof AbstractContainerScreen);
|
||||
}
|
||||
|
||||
// Inspired from https://github.com/MrCrayfish/Controllable/blob/1.14.X/src/main/java/com/mrcrayfish/controllable/client/ControllerInput.java#L686.
|
||||
private void move_mouse_to_closest_slot(@NotNull MinecraftClient client, @Nullable Screen screen)
|
||||
{
|
||||
// Makes the mouse attracted to slots. This helps with selecting items when using a controller.
|
||||
if (screen instanceof AbstractContainerScreen) {
|
||||
AbstractContainerScreen inventory_screen = (AbstractContainerScreen) screen;
|
||||
AbstractContainerScreenAccessor accessor = (AbstractContainerScreenAccessor) inventory_screen;
|
||||
int gui_left = accessor.get_x();
|
||||
int gui_top = accessor.get_y();
|
||||
int mouse_x = (int) (target_mouse_x * (double) client.getWindow().getScaledWidth() / (double) client.getWindow().getWidth());
|
||||
int mouse_y = (int) (target_mouse_y * (double) client.getWindow().getScaledHeight() / (double) client.getWindow().getHeight());
|
||||
|
||||
// Finds the closest slot in the GUI within 14 pixels.
|
||||
Optional<Pair<Slot, Double>> closest_slot = inventory_screen.getContainer().slotList.parallelStream()
|
||||
.map(slot -> {
|
||||
int pos_x = gui_left + slot.xPosition + 8;
|
||||
int pos_y = gui_top + slot.yPosition + 8;
|
||||
|
||||
// Distance between the slot and the cursor.
|
||||
double distance = Math.sqrt(Math.pow(pos_x - mouse_x, 2) + Math.pow(pos_y - mouse_y, 2));
|
||||
return Pair.of(slot, distance);
|
||||
}).filter(entry -> entry.get_value() <= 14.0)
|
||||
.min(Comparator.comparingDouble(Pair::get_value));
|
||||
|
||||
if (closest_slot.isPresent()) {
|
||||
Slot slot = closest_slot.get().get_key();
|
||||
if (slot.hasStack() || !client.player.inventory.getMainHandStack().isEmpty()) {
|
||||
int slot_center_x_scaled = gui_left + slot.xPosition + 8;
|
||||
int slot_center_y_scaled = gui_top + slot.yPosition + 8;
|
||||
int slot_center_x = (int) (slot_center_x_scaled / ((double) client.getWindow().getScaledWidth() / (double) client.getWindow().getWidth()));
|
||||
int slot_center_y = (int) (slot_center_y_scaled / ((double) client.getWindow().getScaledHeight() / (double) client.getWindow().getHeight()));
|
||||
double delta_x = slot_center_x - target_mouse_x;
|
||||
double delta_y = slot_center_y - target_mouse_y;
|
||||
|
||||
if (mouse_x != slot_center_x_scaled || mouse_y != slot_center_y_scaled) {
|
||||
this.target_mouse_x += delta_x * 0.75;
|
||||
this.target_mouse_y += delta_y * 0.75;
|
||||
} else {
|
||||
this.mouse_speed_x *= 0.3F;
|
||||
this.mouse_speed_y *= 0.3F;
|
||||
}
|
||||
this.mouse_speed_x *= .75F;
|
||||
this.mouse_speed_y *= .75F;
|
||||
} else {
|
||||
this.mouse_speed_x *= .1F;
|
||||
this.mouse_speed_y *= .1F;
|
||||
}
|
||||
} else {
|
||||
this.mouse_speed_x *= .3F;
|
||||
this.mouse_speed_y *= .3F;
|
||||
}
|
||||
} else {
|
||||
this.mouse_speed_x = 0.F;
|
||||
this.mouse_speed_y = 0.F;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2019 LambdAurora <aurora42lambda@gmail.com>
|
||||
*
|
||||
* This file is part of LambdaControls.
|
||||
*
|
||||
* Licensed under the MIT license. For more information,
|
||||
* see the LICENSE file.
|
||||
*/
|
||||
|
||||
package me.lambdaurora.lambdacontrols.gui;
|
||||
|
||||
import com.mojang.blaze3d.platform.GlStateManager;
|
||||
import com.mojang.blaze3d.systems.RenderSystem;
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.client.gui.widget.ButtonWidget;
|
||||
import net.minecraft.util.math.MathHelper;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Represents a button with an icon instead of text.
|
||||
*/
|
||||
public abstract class AbstractIconButtonWidget extends ButtonWidget
|
||||
{
|
||||
private int icon_size = 0;
|
||||
|
||||
public AbstractIconButtonWidget(int x, int y, int width, int height, @NotNull String message, @NotNull PressAction on_press)
|
||||
{
|
||||
super(x, y, width, height, message, on_press);
|
||||
}
|
||||
|
||||
protected abstract int render_icon(int mouse_x, int mouse_y, float delta, int x, int y);
|
||||
|
||||
@Override
|
||||
public void renderButton(int mouse_x, int mouse_y, float delta)
|
||||
{
|
||||
MinecraftClient client = MinecraftClient.getInstance();
|
||||
client.getTextureManager().bindTexture(WIDGETS_LOCATION);
|
||||
RenderSystem.color4f(1.0F, 1.0F, 1.0F, this.alpha);
|
||||
int i = this.getYImage(this.isHovered());
|
||||
RenderSystem.enableBlend();
|
||||
RenderSystem.defaultBlendFunc();
|
||||
RenderSystem.blendFunc(GlStateManager.SrcFactor.SRC_ALPHA, GlStateManager.DstFactor.ONE_MINUS_SRC_ALPHA);
|
||||
this.blit(this.x, this.y, 0, 46 + i * 20, this.width / 2, this.height);
|
||||
this.blit(this.x + this.width / 2, this.y, 200 - this.width / 2, 46 + i * 20, this.width / 2, this.height);
|
||||
this.renderBg(client, mouse_x, mouse_y);
|
||||
|
||||
this.icon_size = this.render_icon(mouse_x, mouse_y, delta, this.x + 4, this.y + (this.height / 2 - this.icon_size / 2));
|
||||
|
||||
if (!this.getMessage().isEmpty()) {
|
||||
int j = this.active ? 16777215 : 10526880;
|
||||
this.drawCenteredString(client.textRenderer, this.getMessage(), this.x + 8 + this.icon_size + (this.width - 8 - this.icon_size - 6) / 2,
|
||||
this.y + (this.height - 8) / 2, j | MathHelper.ceil(this.alpha * 255.0F) << 24);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2019 LambdAurora <aurora42lambda@gmail.com>
|
||||
*
|
||||
* This file is part of LambdaControls.
|
||||
*
|
||||
* Licensed under the MIT license. For more information,
|
||||
* see the LICENSE file.
|
||||
*/
|
||||
|
||||
package me.lambdaurora.lambdacontrols.gui;
|
||||
|
||||
import me.lambdaurora.lambdacontrols.ButtonBinding;
|
||||
import me.lambdaurora.lambdacontrols.LambdaControls;
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Represents a controller button widget.
|
||||
*/
|
||||
public class ControllerButtonWidget extends AbstractIconButtonWidget
|
||||
{
|
||||
private ButtonBinding binding;
|
||||
|
||||
public ControllerButtonWidget(int x, int y, int width, @NotNull ButtonBinding button_binding, @NotNull PressAction on_press)
|
||||
{
|
||||
super(x, y, width, 20, ButtonBinding.get_localized_button_name(button_binding.get_button()), on_press);
|
||||
this.binding = button_binding;
|
||||
}
|
||||
|
||||
public void update()
|
||||
{
|
||||
this.setMessage(ButtonBinding.get_localized_button_name(binding.get_button()));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int render_icon(int mouse_x, int mouse_y, float delta, int x, int y)
|
||||
{
|
||||
return LambdaControls.draw_button(x, y, this.binding, MinecraftClient.getInstance());
|
||||
}
|
||||
}
|
||||
@@ -1,181 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2019 LambdAurora <aurora42lambda@gmail.com>
|
||||
*
|
||||
* This file is part of LambdaControls.
|
||||
*
|
||||
* Licensed under the MIT license. For more information,
|
||||
* see the LICENSE file.
|
||||
*/
|
||||
|
||||
package me.lambdaurora.lambdacontrols.gui;
|
||||
|
||||
import me.lambdaurora.lambdacontrols.ButtonBinding;
|
||||
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 final LambdaControlsControlsScreen gui;
|
||||
private int field_2733;
|
||||
|
||||
public ControlsListWidget(@NotNull LambdaControlsControlsScreen gui, @NotNull MinecraftClient client)
|
||||
{
|
||||
super(client, gui.width + 45, gui.height, 43, gui.height - 32, 24);
|
||||
this.gui = gui;
|
||||
|
||||
ButtonBinding.stream_categories()
|
||||
.sorted(Comparator.comparingInt(ButtonBinding.Category::get_priority))
|
||||
.forEach(category -> {
|
||||
this.addEntry(new CategoryEntry(category));
|
||||
|
||||
category.get_bindings().forEach(binding -> {
|
||||
int i = client.textRenderer.getStringWidth(I18n.translate(binding.get_translation_key()));
|
||||
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 binding_name;
|
||||
private final ControllerButtonWidget edit_button;
|
||||
private final ButtonWidget reset_button;
|
||||
|
||||
ButtonBindingEntry(@NotNull ButtonBinding binding)
|
||||
{
|
||||
this.binding = binding;
|
||||
this.binding_name = I18n.translate(this.binding.get_translation_key());
|
||||
this.edit_button = new ControllerButtonWidget(0, 0, 90, this.binding, btn -> gui.focused_binding = binding)
|
||||
{
|
||||
protected String getNarrationMessage()
|
||||
{
|
||||
return binding.is_not_bound() ? I18n.translate("narrator.controls.unbound", binding_name) : I18n.translate("narrator.controls.bound", binding_name, super.getNarrationMessage());
|
||||
}
|
||||
};
|
||||
this.reset_button = new ButtonWidget(0, 0, 50, 20, I18n.translate("controls.reset"),
|
||||
btn -> gui.mod.config.set_button_binding(binding, binding.get_default_button()))
|
||||
{
|
||||
protected String getNarrationMessage()
|
||||
{
|
||||
return I18n.translate("narrator.controls.reset", binding_name);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<? extends Element> children()
|
||||
{
|
||||
return Collections.unmodifiableList(Arrays.asList(this.edit_button, this.reset_button));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(int index, int y, int x, int width, int height, int mouse_x, int mouse_y, boolean hovering, float delta)
|
||||
{
|
||||
boolean focused = gui.focused_binding == this.binding;
|
||||
TextRenderer text_renderer = ControlsListWidget.this.minecraft.textRenderer;
|
||||
String binding_name = this.binding_name;
|
||||
float var10002 = (float) (x + 70 - ControlsListWidget.this.field_2733);
|
||||
int var10003 = y + height / 2;
|
||||
text_renderer.draw(binding_name, var10002, (float) (var10003 - 9 / 2), 16777215);
|
||||
this.reset_button.x = x + 190;
|
||||
this.reset_button.y = y;
|
||||
this.reset_button.active = !this.binding.is_default();
|
||||
this.reset_button.render(mouse_x, mouse_y, delta);
|
||||
this.edit_button.x = x + 85;
|
||||
this.edit_button.y = y;
|
||||
this.edit_button.update();
|
||||
|
||||
if (focused) {
|
||||
this.edit_button.setMessage(Formatting.WHITE + "> " + Formatting.YELLOW + this.edit_button.getMessage() + Formatting.WHITE + " <");
|
||||
} else if (!this.binding.is_not_bound() && ButtonBinding.has_duplicates(this.binding.get_button())) {
|
||||
this.edit_button.setMessage(Formatting.RED + this.edit_button.getMessage());
|
||||
} else if (this.binding.is_not_bound()) {
|
||||
this.edit_button.setMessage(Formatting.GOLD + edit_button.getMessage());
|
||||
}
|
||||
|
||||
this.edit_button.render(mouse_x, mouse_y, delta);
|
||||
}
|
||||
|
||||
public boolean mouseClicked(double mouseX, double mouseY, int button)
|
||||
{
|
||||
if (this.edit_button.mouseClicked(mouseX, mouseY, button))
|
||||
return true;
|
||||
else
|
||||
return this.reset_button.mouseClicked(mouseX, mouseY, button);
|
||||
}
|
||||
|
||||
public boolean mouseReleased(double mouseX, double mouseY, int button)
|
||||
{
|
||||
return this.edit_button.mouseReleased(mouseX, mouseY, button) || this.reset_button.mouseReleased(mouseX, mouseY, button);
|
||||
}
|
||||
}
|
||||
|
||||
public class CategoryEntry extends Entry
|
||||
{
|
||||
private final String name;
|
||||
private final int name_width;
|
||||
|
||||
public CategoryEntry(@NotNull ButtonBinding.Category category)
|
||||
{
|
||||
this.name = category.get_translated_name();
|
||||
this.name_width = ControlsListWidget.this.minecraft.textRenderer.getStringWidth(this.name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(int index, int y, int x, int width, int height, int mouse_x, int mouse_y, boolean hovering, float delta)
|
||||
{
|
||||
ControlsListWidget.this.minecraft.textRenderer.draw(this.name, (float) (ControlsListWidget.this.minecraft.currentScreen.width / 2 - this.name_width / 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>
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,239 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2019 LambdAurora <aurora42lambda@gmail.com>
|
||||
*
|
||||
* This file is part of LambdaControls.
|
||||
*
|
||||
* Licensed under the MIT license. For more information,
|
||||
* see the LICENSE file.
|
||||
*/
|
||||
|
||||
package me.lambdaurora.lambdacontrols.gui;
|
||||
|
||||
import com.mojang.blaze3d.systems.RenderSystem;
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.client.gui.Drawable;
|
||||
import net.minecraft.client.gui.DrawableHelper;
|
||||
import net.minecraft.client.gui.Element;
|
||||
import net.minecraft.client.render.Tessellator;
|
||||
import net.minecraft.client.render.VertexConsumerProvider;
|
||||
import net.minecraft.client.util.math.Matrix4f;
|
||||
import net.minecraft.client.util.math.MatrixStack;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.lwjgl.glfw.GLFW;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* Represents a label widget.
|
||||
*/
|
||||
// @TODO move this to a GUI library.
|
||||
public class LabelWidget extends DrawableHelper implements Element, Drawable
|
||||
{
|
||||
public static final Consumer<LabelWidget> DEFAULT_ACTION = label -> {
|
||||
};
|
||||
|
||||
private final MinecraftClient client = MinecraftClient.getInstance();
|
||||
private final Consumer<LabelWidget> press_action;
|
||||
private final int x;
|
||||
private final int y;
|
||||
private final int max_width;
|
||||
//private final int max_height;
|
||||
private String text;
|
||||
private String tooltip_text;
|
||||
public boolean visible;
|
||||
private int width;
|
||||
private int height;
|
||||
private boolean centered;
|
||||
protected boolean hovered;
|
||||
protected boolean focused;
|
||||
|
||||
public LabelWidget(int x, int y, @NotNull String text, int max_width, @NotNull Consumer<LabelWidget> press_action, boolean centered)
|
||||
{
|
||||
this.visible = true;
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.max_width = max_width;
|
||||
this.press_action = press_action;
|
||||
this.centered = centered;
|
||||
this.set_text(text);
|
||||
}
|
||||
|
||||
public LabelWidget(int x, int y, @NotNull String text, int max_width, @NotNull Consumer<LabelWidget> press_action)
|
||||
{
|
||||
this(x, y, text, max_width, press_action, false);
|
||||
}
|
||||
|
||||
public LabelWidget(int x, int y, @NotNull String text, int max_width, boolean centered)
|
||||
{
|
||||
this(x, y, text, max_width, DEFAULT_ACTION, centered);
|
||||
}
|
||||
|
||||
public LabelWidget(int x, int y, @NotNull String text, int max_width)
|
||||
{
|
||||
this(x, y, text, max_width, DEFAULT_ACTION);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the text of this label.
|
||||
*
|
||||
* @param text The text to set.
|
||||
*/
|
||||
public void set_text(@NotNull String text)
|
||||
{
|
||||
int width = this.client.textRenderer.getStringWidth(text);
|
||||
while (width > this.max_width) {
|
||||
text = text.substring(0, text.length() - 1);
|
||||
width = this.client.textRenderer.getStringWidth(text);
|
||||
}
|
||||
|
||||
this.text = text;
|
||||
this.width = width;
|
||||
this.height = this.client.textRenderer.fontHeight;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the tooltip text of this label.
|
||||
*
|
||||
* @param text The tooltip text.
|
||||
*/
|
||||
public void set_tooltip_text(String text)
|
||||
{
|
||||
this.tooltip_text = text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the width of this label widget.
|
||||
*
|
||||
* @return The width of this label widget.
|
||||
*/
|
||||
public int get_width()
|
||||
{
|
||||
return this.width;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the height of this label widget.
|
||||
*
|
||||
* @return The height of this label widget.
|
||||
*/
|
||||
public int get_height()
|
||||
{
|
||||
return this.height;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fires the press event on this label widget.
|
||||
*/
|
||||
public void on_press()
|
||||
{
|
||||
this.press_action.accept(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(int mouse_x, int mouse_y, float delta)
|
||||
{
|
||||
if (this.visible) {
|
||||
int x = this.centered ? this.x - this.client.textRenderer.getStringWidth(this.text) / 2 : this.x;
|
||||
this.hovered = mouse_x >= x && mouse_y >= this.y && mouse_x < x + this.width && mouse_y < this.y + this.height;
|
||||
this.drawString(this.client.textRenderer, this.text, x, this.y, 10526880);
|
||||
|
||||
if (this.tooltip_text != null && !this.tooltip_text.isEmpty()) {
|
||||
List<String> wrapped_tooltip_text = this.client.textRenderer.wrapStringToWidthAsList(this.tooltip_text, Math.max(this.width / 2, 200));
|
||||
if (this.hovered)
|
||||
this.render_tooltip(wrapped_tooltip_text, mouse_x, mouse_y);
|
||||
else if (this.focused)
|
||||
this.render_tooltip(wrapped_tooltip_text, this.x - 12, this.y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean mouseClicked(double mouse_x, double mouse_y, int button)
|
||||
{
|
||||
if (this.visible && button == GLFW.GLFW_MOUSE_BUTTON_1) {
|
||||
if (this.hovered) {
|
||||
this.on_press();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean changeFocus(boolean down)
|
||||
{
|
||||
if (this.visible) {
|
||||
this.focused = !this.focused;
|
||||
return this.focused;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void render_tooltip(List<String> text, int x, int y)
|
||||
{
|
||||
if (!text.isEmpty()) {
|
||||
RenderSystem.disableRescaleNormal();
|
||||
RenderSystem.disableDepthTest();
|
||||
int i = 0;
|
||||
|
||||
for (String string : text) {
|
||||
int j = this.client.textRenderer.getStringWidth(string);
|
||||
if (j > i) {
|
||||
i = j;
|
||||
}
|
||||
}
|
||||
|
||||
int k = x + 12;
|
||||
int l = y - 12;
|
||||
int n = 8;
|
||||
if (text.size() > 1) {
|
||||
n += 2 + (text.size() - 1) * 10;
|
||||
}
|
||||
|
||||
if (k + i > this.client.getWindow().getScaledWidth()) {
|
||||
k -= 28 + i;
|
||||
}
|
||||
|
||||
if (l + n + 6 > this.client.getWindow().getScaledHeight()) {
|
||||
l = this.client.getWindow().getScaledHeight() - n - 6;
|
||||
}
|
||||
|
||||
this.setBlitOffset(300);
|
||||
this.client.getItemRenderer().zOffset = 300.0F;
|
||||
this.fillGradient(k - 3, l - 4, k + i + 3, l - 3, -267386864, -267386864);
|
||||
this.fillGradient(k - 3, l + n + 3, k + i + 3, l + n + 4, -267386864, -267386864);
|
||||
this.fillGradient(k - 3, l - 3, k + i + 3, l + n + 3, -267386864, -267386864);
|
||||
this.fillGradient(k - 4, l - 3, k - 3, l + n + 3, -267386864, -267386864);
|
||||
this.fillGradient(k + i + 3, l - 3, k + i + 4, l + n + 3, -267386864, -267386864);
|
||||
this.fillGradient(k - 3, l - 3 + 1, k - 3 + 1, l + n + 3 - 1, 1347420415, 1344798847);
|
||||
this.fillGradient(k + i + 2, l - 3 + 1, k + i + 3, l + n + 3 - 1, 1347420415, 1344798847);
|
||||
this.fillGradient(k - 3, l - 3, k + i + 3, l - 3 + 1, 1347420415, 1347420415);
|
||||
this.fillGradient(k - 3, l + n + 2, k + i + 3, l + n + 3, 1344798847, 1344798847);
|
||||
MatrixStack matrix_stack = new MatrixStack();
|
||||
VertexConsumerProvider.Immediate immediate = VertexConsumerProvider.immediate(Tessellator.getInstance().getBuffer());
|
||||
matrix_stack.translate(0.0D, 0.0D, this.client.getItemRenderer().zOffset);
|
||||
Matrix4f matrix4f = matrix_stack.peek().getModel();
|
||||
|
||||
for (int r = 0; r < text.size(); ++r) {
|
||||
String string2 = text.get(r);
|
||||
if (string2 != null) {
|
||||
this.client.textRenderer.draw(string2, (float) k, (float) l, -1, true, matrix4f, immediate, false, 0, 15728880);
|
||||
}
|
||||
|
||||
if (r == 0) {
|
||||
l += 2;
|
||||
}
|
||||
|
||||
l += 10;
|
||||
}
|
||||
|
||||
immediate.draw();
|
||||
this.setBlitOffset(0);
|
||||
this.client.getItemRenderer().zOffset = 0.0F;
|
||||
RenderSystem.enableDepthTest();
|
||||
RenderSystem.enableRescaleNormal();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,93 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2019 LambdAurora <aurora42lambda@gmail.com>
|
||||
*
|
||||
* This file is part of LambdaControls.
|
||||
*
|
||||
* Licensed under the MIT license. For more information,
|
||||
* see the LICENSE file.
|
||||
*/
|
||||
|
||||
package me.lambdaurora.lambdacontrols.gui;
|
||||
|
||||
import me.lambdaurora.lambdacontrols.ButtonBinding;
|
||||
import me.lambdaurora.lambdacontrols.LambdaControls;
|
||||
import net.minecraft.client.gui.screen.Screen;
|
||||
import net.minecraft.client.gui.widget.ButtonWidget;
|
||||
import net.minecraft.client.options.BooleanOption;
|
||||
import net.minecraft.client.options.Option;
|
||||
import net.minecraft.client.resource.language.I18n;
|
||||
import net.minecraft.text.TranslatableText;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* Represents the controls screen.
|
||||
*/
|
||||
public class LambdaControlsControlsScreen extends Screen
|
||||
{
|
||||
private final LambdaControlsSettingsScreen parent;
|
||||
final LambdaControls mod;
|
||||
private final Option inverts_right_x_axis;
|
||||
private final Option inverts_right_y_axis;
|
||||
private ControlsListWidget bindings_list_widget;
|
||||
private ButtonWidget reset_button;
|
||||
public ButtonBinding focused_binding;
|
||||
|
||||
protected LambdaControlsControlsScreen(@NotNull LambdaControlsSettingsScreen parent)
|
||||
{
|
||||
super(new TranslatableText("lambdacontrols.menu.title.controller_controls"));
|
||||
this.parent = parent;
|
||||
this.mod = parent.mod;
|
||||
this.inverts_right_x_axis = new BooleanOption("lambdacontrols.menu.invert_right_x_axis", game_options -> this.mod.config.does_invert_right_x_axis(),
|
||||
(game_options, new_value) -> {
|
||||
synchronized (this.mod.config) {
|
||||
this.mod.config.set_invert_right_x_axis(new_value);
|
||||
}
|
||||
});
|
||||
this.inverts_right_y_axis = new BooleanOption("lambdacontrols.menu.invert_right_y_axis", game_options -> this.mod.config.does_invert_right_y_axis(),
|
||||
(game_options, new_value) -> {
|
||||
synchronized (this.mod.config) {
|
||||
this.mod.config.set_invert_right_y_axis(new_value);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removed()
|
||||
{
|
||||
this.mod.config.save();
|
||||
super.removed();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void init()
|
||||
{
|
||||
this.addButton(this.inverts_right_x_axis.createButton(this.minecraft.options, this.width / 2 - 155, 18, 150));
|
||||
this.addButton(this.inverts_right_y_axis.createButton(this.minecraft.options, this.width / 2 - 155 + 160, 18, 150));
|
||||
this.bindings_list_widget = new ControlsListWidget(this, this.minecraft);
|
||||
this.children.add(this.bindings_list_widget);
|
||||
this.reset_button = this.addButton(new ButtonWidget(this.width / 2 - 155, this.height - 29, 150, 20, I18n.translate("controls.resetAll"),
|
||||
btn -> ButtonBinding.stream().forEach(binding -> this.mod.config.set_button_binding(binding, binding.get_default_button()))));
|
||||
this.addButton(new ButtonWidget(this.width / 2 - 155 + 160, this.height - 29, 150, 20, I18n.translate("gui.done"),
|
||||
btn -> this.minecraft.openScreen(this.parent)));
|
||||
}
|
||||
|
||||
// Replacement for Predicate#not as it is Java 11.
|
||||
private <T> Predicate<T> not(Predicate<T> target)
|
||||
{
|
||||
Objects.requireNonNull(target);
|
||||
return target.negate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(int mouse_x, int mouse_y, float delta)
|
||||
{
|
||||
this.renderBackground();
|
||||
this.bindings_list_widget.render(mouse_x, mouse_y, delta);
|
||||
this.drawCenteredString(this.font, this.title.asFormattedString(), this.width / 2, 8, 16777215);
|
||||
this.reset_button.active = ButtonBinding.stream().anyMatch(this.not(ButtonBinding::is_default));
|
||||
super.render(mouse_x, mouse_y, delta);
|
||||
}
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2019 LambdAurora <aurora42lambda@gmail.com>
|
||||
*
|
||||
* This file is part of LambdaControls.
|
||||
*
|
||||
* Licensed under the MIT license. For more information,
|
||||
* see the LICENSE file.
|
||||
*/
|
||||
|
||||
package me.lambdaurora.lambdacontrols.gui;
|
||||
|
||||
import me.lambdaurora.lambdacontrols.ButtonBinding;
|
||||
import me.lambdaurora.lambdacontrols.ControlsMode;
|
||||
import me.lambdaurora.lambdacontrols.LambdaControls;
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.client.gui.DrawableHelper;
|
||||
import net.minecraft.util.hit.HitResult;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Represents the LambdaControls HUD.
|
||||
*/
|
||||
public class LambdaControlsHud extends DrawableHelper
|
||||
{
|
||||
private final MinecraftClient client;
|
||||
private final LambdaControls mod;
|
||||
|
||||
public LambdaControlsHud(@NotNull MinecraftClient client, @NotNull LambdaControls mod)
|
||||
{
|
||||
this.client = client;
|
||||
this.mod = mod;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the LambdaControls' HUD.
|
||||
*/
|
||||
public void render()
|
||||
{
|
||||
if (this.mod.config.get_controls_mode() == ControlsMode.CONTROLLER && this.mod.config.is_hud_enabled() && this.client.currentScreen == null && !this.client.options.hudHidden) {
|
||||
int x = 10, y = bottom(10);
|
||||
x += this.draw_button_tip(x, y, ButtonBinding.INVENTORY, true) + 10;
|
||||
this.draw_button_tip(x, y, ButtonBinding.SWAP_HANDS, true);
|
||||
x = 10;
|
||||
x += this.draw_button_tip(x, (y -= 20), ButtonBinding.DROP_ITEM, !this.client.player.getMainHandStack().isEmpty()) + 10;
|
||||
this.draw_button_tip(x, y, ButtonBinding.ATTACK.get_button(),
|
||||
this.client.crosshairTarget.getType() == HitResult.Type.BLOCK ? "lambdacontrols.action.hit" : ButtonBinding.ATTACK.get_translation_key(),
|
||||
this.client.crosshairTarget.getType() != HitResult.Type.MISS);
|
||||
}
|
||||
}
|
||||
|
||||
private int bottom(int y)
|
||||
{
|
||||
return this.client.getWindow().getScaledHeight() - y - 15;
|
||||
}
|
||||
|
||||
private int draw_button_tip(int x, int y, @NotNull ButtonBinding button, boolean display)
|
||||
{
|
||||
return LambdaControls.draw_button_tip(x, y, button, display, this.client);
|
||||
}
|
||||
|
||||
private int draw_button_tip(int x, int y, int button, @NotNull String action, boolean display)
|
||||
{
|
||||
return LambdaControls.draw_button_tip(x, y, button, action, display, this.client);
|
||||
}
|
||||
}
|
||||
@@ -1,170 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2019 LambdAurora <aurora42lambda@gmail.com>
|
||||
*
|
||||
* This file is part of LambdaControls.
|
||||
*
|
||||
* Licensed under the MIT license. For more information,
|
||||
* see the LICENSE file.
|
||||
*/
|
||||
|
||||
package me.lambdaurora.lambdacontrols.gui;
|
||||
|
||||
import me.lambdaurora.lambdacontrols.Controller;
|
||||
import me.lambdaurora.lambdacontrols.ControlsMode;
|
||||
import me.lambdaurora.lambdacontrols.LambdaControls;
|
||||
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.*;
|
||||
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 LambdaControls mod;
|
||||
private final Screen parent;
|
||||
private final GameOptions options;
|
||||
private final Option auto_switch_mode_option;
|
||||
private final Option controller_option;
|
||||
private final Option controller_type_option;
|
||||
private final Option dead_zone_option;
|
||||
private final Option hud_enable_option;
|
||||
private final Option hud_side_option;
|
||||
private final Option mouse_speed_option;
|
||||
private final Option rotation_speed_option;
|
||||
private final String controller_mappings_url_text = I18n.translate("lambdacontrols.controller.mappings.2", Formatting.GOLD.toString(), GAMEPAD_TOOL_URL, Formatting.RESET.toString());
|
||||
private ButtonListWidget list;
|
||||
private LabelWidget gamepad_tool_url_label;
|
||||
|
||||
public LambdaControlsSettingsScreen(Screen parent, @NotNull GameOptions options)
|
||||
{
|
||||
super(new TranslatableText("lambdacontrols.title.settings"));
|
||||
this.mod = LambdaControls.get();
|
||||
this.parent = parent;
|
||||
this.options = options;
|
||||
this.auto_switch_mode_option = new BooleanOption("lambdacontrols.menu.auto_switch_mode", game_options -> this.mod.config.has_auto_switch_mode(),
|
||||
(game_options, new_value) -> this.mod.config.set_auto_switch_mode(new_value));
|
||||
this.controller_option = new CyclingOption("lambdacontrols.menu.controller", (game_options, amount) -> {
|
||||
int current_id = this.mod.config.get_controller().get_id();
|
||||
current_id += amount;
|
||||
if (current_id > GLFW.GLFW_JOYSTICK_LAST)
|
||||
current_id = GLFW.GLFW_JOYSTICK_1;
|
||||
this.mod.config.set_controller(Controller.by_id(current_id));
|
||||
}, (game_options, option) -> {
|
||||
String controller_name = this.mod.config.get_controller().get_name();
|
||||
if (!this.mod.config.get_controller().is_connected())
|
||||
return option.getDisplayPrefix() + Formatting.RED + controller_name;
|
||||
else if (!this.mod.config.get_controller().is_gamepad())
|
||||
return option.getDisplayPrefix() + Formatting.GOLD + controller_name;
|
||||
else
|
||||
return option.getDisplayPrefix() + controller_name;
|
||||
});
|
||||
this.controller_type_option = new CyclingOption("lambdacontrols.menu.controller_type",
|
||||
(game_options, amount) -> this.mod.config.set_controller_type(this.mod.config.get_controller_type().next()),
|
||||
(game_options, option) -> option.getDisplayPrefix() + this.mod.config.get_controller_type().get_translated_name());
|
||||
this.hud_enable_option = new BooleanOption("lambdacontrols.menu.hud_enable", (game_options) -> this.mod.config.is_hud_enabled(),
|
||||
(game_options, new_value) -> this.mod.config.set_hud_enabled(new_value));
|
||||
this.hud_side_option = new CyclingOption("lambdacontrols.menu.hud_side",
|
||||
(game_options, amount) -> this.mod.config.set_hud_side(this.mod.config.get_hud_side().next()),
|
||||
(game_options, option) -> option.getDisplayPrefix() + this.mod.config.get_hud_side().get_translated_name());
|
||||
this.dead_zone_option = new DoubleOption("lambdacontrols.menu.dead_zone", 0.05, 1.0, 0.05F, game_options -> this.mod.config.get_dead_zone(),
|
||||
(game_options, new_value) -> {
|
||||
synchronized (this.mod.config) {
|
||||
this.mod.config.set_dead_zone(new_value);
|
||||
}
|
||||
}, (game_options, option) -> {
|
||||
String value = String.valueOf(option.get(options));
|
||||
return option.getDisplayPrefix() + value.substring(0, Math.min(value.length(), 5));
|
||||
});
|
||||
this.rotation_speed_option = new DoubleOption("lambdacontrols.menu.rotation_speed", 0.0, 50.0, 0.5F, game_options -> this.mod.config.get_rotation_speed(),
|
||||
(game_options, new_value) -> {
|
||||
synchronized (this.mod.config) {
|
||||
this.mod.config.set_rotation_speed(new_value);
|
||||
}
|
||||
}, (game_options, option) -> option.getDisplayPrefix() + option.get(options));
|
||||
this.mouse_speed_option = new DoubleOption("lambdacontrols.menu.mouse_speed", 0.0, 50.0, 0.5F, game_options -> this.mod.config.get_mouse_speed(),
|
||||
(game_options, new_value) -> {
|
||||
synchronized (this.mod.config) {
|
||||
this.mod.config.set_mouse_speed(new_value);
|
||||
}
|
||||
}, (game_options, option) -> option.getDisplayPrefix() + option.get(options));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removed()
|
||||
{
|
||||
this.mod.config.save();
|
||||
super.removed();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClose()
|
||||
{
|
||||
this.mod.config.save();
|
||||
super.onClose();
|
||||
}
|
||||
|
||||
private int get_text_height()
|
||||
{
|
||||
return (5 + this.font.fontHeight) * 3 + 5;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void init()
|
||||
{
|
||||
super.init();
|
||||
int button_height = 20;
|
||||
this.addButton(new ButtonWidget(this.width / 2 - 155, 18, 150, button_height, I18n.translate("lambdacontrols.menu.controls_mode") + ": " + this.mod.config.get_controls_mode().get_translated_name(),
|
||||
btn -> {
|
||||
ControlsMode next = this.mod.config.get_controls_mode().next();
|
||||
btn.setMessage(I18n.translate("lambdacontrols.menu.controls_mode") + ": " + next.get_translated_name());
|
||||
this.mod.config.set_controls_mode(next);
|
||||
this.mod.config.save();
|
||||
}));
|
||||
this.addButton(new ButtonWidget(this.width / 2 - 155 + 160, 18, 150, button_height, I18n.translate("options.controls"),
|
||||
btn -> {
|
||||
if (this.mod.config.get_controls_mode() == ControlsMode.CONTROLLER)
|
||||
this.minecraft.openScreen(new LambdaControlsControlsScreen(this));
|
||||
else
|
||||
this.minecraft.openScreen(new ControlsOptionsScreen(this, this.options));
|
||||
}));
|
||||
|
||||
this.list = new ButtonListWidget(this.minecraft, this.width, this.height, 43, this.height - 29 - this.get_text_height(), 25);
|
||||
this.list.addSingleOptionEntry(this.controller_option);
|
||||
this.list.addOptionEntry(this.controller_type_option, this.dead_zone_option);
|
||||
this.list.addOptionEntry(this.hud_enable_option, this.hud_side_option);
|
||||
this.list.addOptionEntry(this.rotation_speed_option, this.mouse_speed_option);
|
||||
this.list.addSingleOptionEntry(this.auto_switch_mode_option);
|
||||
this.list.addSingleOptionEntry(new ReloadControllerMappingsOption());
|
||||
this.children.add(this.list);
|
||||
|
||||
this.gamepad_tool_url_label = new LabelWidget(this.width / 2, this.height - 29 - (5 + this.font.fontHeight) * 2, this.controller_mappings_url_text, this.width,
|
||||
label -> Util.getOperatingSystem().open(GAMEPAD_TOOL_URL), true);
|
||||
this.gamepad_tool_url_label.set_tooltip_text(I18n.translate("chat.link.open"));
|
||||
this.children.add(this.gamepad_tool_url_label);
|
||||
|
||||
this.addButton(new ButtonWidget(this.width / 2 - 155, this.height - 29, 300, button_height, I18n.translate("gui.done"),
|
||||
(buttonWidget) -> this.minecraft.openScreen(this.parent)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(int mouse_x, int mouse_y, float delta)
|
||||
{
|
||||
this.renderBackground();
|
||||
this.list.render(mouse_x, mouse_y, delta);
|
||||
super.render(mouse_x, mouse_y, 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.gamepad_tool_url_label.render(mouse_x, mouse_y, 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);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user