mirror of
https://github.com/TeamMidnightDust/MidnightControls.git
synced 2025-12-13 23:25:10 +01:00
Compare commits
68 Commits
1.1.0
...
1.7.1+1.17
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 |
26
.github/workflows/gradlebuild.yml
vendored
Executable file
26
.github/workflows/gradlebuild.yml
vendored
Executable file
@@ -0,0 +1,26 @@
|
|||||||
|
name: Gradle Build
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- '*'
|
||||||
|
|
||||||
|
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 }}
|
||||||
|
LAMBDACONTROLS_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
|
# LambdAurora's ignore file
|
||||||
#
|
#
|
||||||
# v0.12
|
# v0.15
|
||||||
|
|
||||||
# JetBrains
|
# JetBrains
|
||||||
.idea/
|
.idea/
|
||||||
*.iml
|
*.iml
|
||||||
|
*.ipr
|
||||||
|
*.iws
|
||||||
## Intellij IDEA
|
## Intellij IDEA
|
||||||
out/
|
out/
|
||||||
## CLion
|
## CLion
|
||||||
@@ -51,6 +53,8 @@ __pycache__/
|
|||||||
# OS
|
# OS
|
||||||
## Windows
|
## Windows
|
||||||
desktop.ini
|
desktop.ini
|
||||||
|
# MacOS
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
# File types
|
# File types
|
||||||
*.dll
|
*.dll
|
||||||
@@ -59,10 +63,13 @@ desktop.ini
|
|||||||
*.lib
|
*.lib
|
||||||
lib*.a
|
lib*.a
|
||||||
*.png~
|
*.png~
|
||||||
|
*.tar.?z
|
||||||
|
|
||||||
# Common
|
# Common
|
||||||
bin/
|
bin/
|
||||||
build/
|
build/
|
||||||
dist/
|
dist/
|
||||||
|
lib/
|
||||||
|
obj/
|
||||||
run/
|
run/
|
||||||
target/
|
target/
|
||||||
|
|||||||
96
CHANGELOG.md
96
CHANGELOG.md
@@ -1,6 +1,6 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## v1.0.0
|
## 1.0.0
|
||||||
|
|
||||||
:tada: First release! :tada:
|
:tada: First release! :tada:
|
||||||
|
|
||||||
@@ -12,11 +12,11 @@
|
|||||||
- Added key bindings for look around.
|
- Added key bindings for look around.
|
||||||
- And more!
|
- And more!
|
||||||
|
|
||||||
### v1.0.1
|
### 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))
|
- Fixed tutorial toast to look around not affected by camera movement done with a controller. ([#2](https://github.com/LambdAurora/LambdaControls/issues/2))
|
||||||
|
|
||||||
### v1.0.2 (Unofficial)
|
### 1.0.2 (Unofficial)
|
||||||
|
|
||||||
This update was never pushed but was aiming to fix [#4](https://github.com/LambdAurora/LambdaControls/issues/4).
|
This update was never pushed but was aiming to fix [#4](https://github.com/LambdAurora/LambdaControls/issues/4).
|
||||||
|
|
||||||
@@ -24,7 +24,7 @@ This update was never pushed but was aiming to fix [#4](https://github.com/Lambd
|
|||||||
- Fixed broken chat arrow keys.
|
- Fixed broken chat arrow keys.
|
||||||
- Optimized a little bit the button indicator. (need more work)
|
- Optimized a little bit the button indicator. (need more work)
|
||||||
|
|
||||||
## v1.1.0 - Chording update
|
## 1.1.0 - Chording update
|
||||||
|
|
||||||
This update also has a backport 1.14.4 version ([#9](https://github.com/LambdAurora/LambdaControls/issues/9)).
|
This update also has a backport 1.14.4 version ([#9](https://github.com/LambdAurora/LambdaControls/issues/9)).
|
||||||
|
|
||||||
@@ -45,3 +45,91 @@ This update also has a backport 1.14.4 version ([#9](https://github.com/LambdAur
|
|||||||
- Added reset settings button.
|
- Added reset settings button.
|
||||||
- HUD side affects button indicators now.
|
- HUD side affects button indicators now.
|
||||||
- Added support for Advancements tabs.
|
- 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
|
||||||
@@ -26,7 +26,7 @@ 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.
|
[Fabric](https://fabricmc.net/) is the mod loader and the software which allows Gradle to setup the workspace.
|
||||||
|
|
||||||
### Java 8
|
### Java 16
|
||||||
|
|
||||||
Java is the main language used to make LambdaControls alive.
|
Java is the main language used to make LambdaControls alive.
|
||||||
Knowing how to code in Java is necessary if you contribute to the code.
|
Knowing how to code in Java is necessary if you contribute to the code.
|
||||||
@@ -47,7 +47,7 @@ As it is a Minecraft mod you should know a bit how Minecraft works and how moddi
|
|||||||
|
|
||||||
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 LambdaControls, 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?
|
## How can I contribute?
|
||||||
|
|
||||||
@@ -55,17 +55,17 @@ Git commits should be and must be signed.
|
|||||||
|
|
||||||
#### Before submitting a bug report
|
#### 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.
|
- 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?
|
#### 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/LambdAurora/LambdaControls/blob/1.17/.github/ISSUE_TEMPLATE/bug_report.md)
|
||||||
|
|
||||||
### Suggesting enhancements
|
### Suggesting enhancements
|
||||||
|
|
||||||
Enhancement suggestions are tracked as [GitHub issues](https://github.com/LambdAurora/LambdaControls/issues).
|
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.
|
Check out the [feature request](https://github.com/LambdAurora/LambdaControls/blob/1.17/.github/ISSUE_TEMPLATE/feature_request.md) guide.
|
||||||
|
|
||||||
### Do pull requests
|
### Do pull requests
|
||||||
|
|
||||||
@@ -81,21 +81,16 @@ Feel free to pull request!
|
|||||||
### Git commit messages
|
### Git commit messages
|
||||||
|
|
||||||
* Use the imperative mood ("Move cursor to..." not "Moves cursor to...")
|
* 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.
|
* (Not for the message) Don't forget to sign the commit.
|
||||||
|
|
||||||
### Naming convention
|
### 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.
|
`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
|
### Brace placement
|
||||||
|
|
||||||
Every braces should be at the end of the line of function declaration, etc...
|
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.
|
|
||||||
|
|
||||||
### Quick note for users of the Intellij IDEA IDE
|
### Quick note for users of the Intellij IDEA IDE
|
||||||
|
|
||||||
|
|||||||
6
HEADER
Normal file
6
HEADER
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
|
||||||
|
|
||||||
|
This file is part of LambdaControls.
|
||||||
|
|
||||||
|
Licensed under the MIT license. For more information,
|
||||||
|
see the LICENSE file.
|
||||||
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
|||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright © 2020 LambdAurora <aurora42lambda@gmail.com>
|
Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|||||||
54
README.md
54
README.md
@@ -1,41 +1,57 @@
|
|||||||
# LambdaControls
|
# LambdaControls
|
||||||
|
|
||||||

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

|

|
||||||

|
[![Mod loader: Fabric]][fabric] <!-- modrinth_exclude.start -->
|
||||||

|

|
||||||
[](https://www.curseforge.com/minecraft/mc-mods/lambdacontrols)
|
[](https://www.curseforge.com/minecraft/mc-mods/lambdacontrols)
|
||||||
|
<!-- modrinth_exclude.end -->
|
||||||
|
|
||||||
A Fabric Minecraft mod which adds better controls like controller support.
|
A Fabric Minecraft mod which adds better controls, reach-around and controller support.
|
||||||
|
|
||||||
## What's this mod?
|
## 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!
|
Haven't you dreamed to travel in your modded Minecraft world with your controller? Yes? Then this mod is made for you!
|
||||||
|
|
||||||
This mod adds a controller support (and an experimental touchscreen support).
|
This mod also adds controller support.
|
||||||
|
|
||||||
## ✅ Features:
|
## ✅ Features:
|
||||||
|
|
||||||
- Controller support
|
- Controller support
|
||||||
- Touchscreen support (very experimental and buggy).
|
- Touchscreen support (very experimental and buggy).
|
||||||
- Keyboard controls to look around.
|
- Keyboard controls to look around.
|
||||||
- Toggleable on screen button indicator (like in Bedrock Edition).
|
- Toggleable on screen button indicator (like in Bedrock Edition).
|
||||||
- Many Bedrock Edition features:
|
- Vertical reach-around.
|
||||||
|
- Many Bedrock Edition features:
|
||||||
- Toggleable fly drifting
|
- Toggleable fly drifting
|
||||||
- Front block placing (be careful with this one)
|
- Front block placing (be careful with this one)
|
||||||
- New controls settings!
|
- New controls settings!
|
||||||
- Many options in config to change to your liking.
|
- Many options in config to change to your liking.
|
||||||
- Many controllers supported and in a simply way your own controller mappings.
|
- 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:
|
## 🎮 Supported Controllers:
|
||||||
|
|
||||||
- Dualshock controllers
|
- Dualshock controllers
|
||||||
- Xbox controllers
|
- Xbox controllers
|
||||||
- Switch Pro controllers
|
- Switch Pro controllers
|
||||||
- Joycons
|
- Joycons
|
||||||
- And many more!
|
- And many more!
|
||||||
|
|
||||||
|
## Screenshots
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
<!-- modrinth_exclude.start -->
|
||||||
## Build
|
## Build
|
||||||
|
|
||||||
Just do `./gradlew :fabric:build` and everything should build just fine!
|
Just do `./gradlew build` and everything should build just fine!
|
||||||
|
<!-- modrinth_exclude.end -->
|
||||||
|
|
||||||
|
[fabric]: https://fabricmc.net
|
||||||
|
[Mod loader: Fabric]: https://img.shields.io/badge/modloader-Fabric-1976d2?style=flat-square&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAACXBIWXMAAAsTAAALEwEAmpwYAAAFHGlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxNDIgNzkuMTYwOTI0LCAyMDE3LzA3LzEzLTAxOjA2OjM5ICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6cGhvdG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOCAoV2luZG93cykiIHhtcDpDcmVhdGVEYXRlPSIyMDE4LTEyLTE2VDE2OjU0OjE3LTA4OjAwIiB4bXA6TW9kaWZ5RGF0ZT0iMjAxOS0wNy0yOFQyMToxNzo0OC0wNzowMCIgeG1wOk1ldGFkYXRhRGF0ZT0iMjAxOS0wNy0yOFQyMToxNzo0OC0wNzowMCIgZGM6Zm9ybWF0PSJpbWFnZS9wbmciIHBob3Rvc2hvcDpDb2xvck1vZGU9IjMiIHBob3Rvc2hvcDpJQ0NQcm9maWxlPSJzUkdCIElFQzYxOTY2LTIuMSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDowZWRiMWMyYy1mZjhjLWU0NDEtOTMxZi00OTVkNGYxNGM3NjAiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MGVkYjFjMmMtZmY4Yy1lNDQxLTkzMWYtNDk1ZDRmMTRjNzYwIiB4bXBNTTpPcmlnaW5hbERvY3VtZW50SUQ9InhtcC5kaWQ6MGVkYjFjMmMtZmY4Yy1lNDQxLTkzMWYtNDk1ZDRmMTRjNzYwIj4gPHhtcE1NOkhpc3Rvcnk+IDxyZGY6U2VxPiA8cmRmOmxpIHN0RXZ0OmFjdGlvbj0iY3JlYXRlZCIgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDowZWRiMWMyYy1mZjhjLWU0NDEtOTMxZi00OTVkNGYxNGM3NjAiIHN0RXZ0OndoZW49IjIwMTgtMTItMTZUMTY6NTQ6MTctMDg6MDAiIHN0RXZ0OnNvZnR3YXJlQWdlbnQ9IkFkb2JlIFBob3Rvc2hvcCBDQyAyMDE4IChXaW5kb3dzKSIvPiA8L3JkZjpTZXE+IDwveG1wTU06SGlzdG9yeT4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz4/HiGMAAAAtUlEQVRYw+XXrQqAMBQF4D2P2eBL+QIG8RnEJFaNBjEum+0+zMQLtwwv+wV3ZzhhMDgfJ0wUSinxZUQWgKos1JP/AbD4OneIDyQPwCFniA+EJ4CaXm4TxAXCC0BNHgLhAdAnx9hC8PwGSRtAFVMQjF7cNTWED8B1cgwW20yfJgAvrssAsZ1cB3g/xckAxr6FmCDU5N6f488BrpCQ4rQBJkiMYh4ACmLzwOQF0CExinkCsvw7vgGikl+OotaKRwAAAABJRU5ErkJggg==
|
||||||
|
|||||||
258
build.gradle
258
build.gradle
@@ -1,51 +1,265 @@
|
|||||||
plugins {
|
plugins {
|
||||||
|
id 'fabric-loom' version '0.9.+'
|
||||||
id 'java-library'
|
id 'java-library'
|
||||||
id 'maven-publish'
|
id 'maven-publish'
|
||||||
|
id 'com.github.johnrengelman.shadow' version '7.0.0'
|
||||||
|
id 'org.cadixdev.licenser' version '0.6.1'
|
||||||
|
id 'com.modrinth.minotaur' version '1.2.+'
|
||||||
}
|
}
|
||||||
|
|
||||||
allprojects {
|
import net.fabricmc.loom.task.RemapJarTask
|
||||||
group = project.maven_group
|
import com.google.gson.GsonBuilder
|
||||||
version = project.mod_version
|
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
|
||||||
|
|
||||||
repositories {
|
group = project.maven_group
|
||||||
|
version = "${project.mod_version}+${getMCVersionString()}"
|
||||||
|
archivesBaseName = project.archives_base_name
|
||||||
|
|
||||||
|
// This field defines the Java version your mod target.
|
||||||
|
def targetJavaVersion = 16
|
||||||
|
|
||||||
|
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]
|
||||||
|
}
|
||||||
|
|
||||||
|
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, '')
|
||||||
|
readme = readme.replaceAll(linkRegex, '')
|
||||||
|
return readme
|
||||||
|
}
|
||||||
|
|
||||||
|
minecraft {
|
||||||
|
accessWidener file('src/main/resources/lambdacontrols.accesswidener')
|
||||||
|
}
|
||||||
|
|
||||||
|
repositories {
|
||||||
mavenLocal()
|
mavenLocal()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
maven { url = 'https://aperlambda.github.io/maven' }
|
maven { url 'https://aperlambda.github.io/maven' }
|
||||||
|
maven {
|
||||||
|
name 'Gegy'
|
||||||
|
url 'https://maven.gegy.dev'
|
||||||
}
|
}
|
||||||
|
maven {
|
||||||
dependencies {
|
name 'CottonMC'
|
||||||
|
url 'https://server.bbkr.space:8081/artifactory/libs-snapshot'
|
||||||
}
|
}
|
||||||
|
maven {
|
||||||
tasks.withType(JavaCompile) {
|
name 'Terraformers'
|
||||||
options.encoding = "UTF-8"
|
url 'https://maven.terraformersmc.com/releases'
|
||||||
}
|
}
|
||||||
|
maven { url 'https://maven.shedaniel.me/' }
|
||||||
|
}
|
||||||
|
|
||||||
// Loom will automatically attach sourcesJar to a RemapSourcesJar task and to the "build" task
|
configurations {
|
||||||
// if it is present.
|
shadow
|
||||||
// If you remove this task, sources will not be generated.
|
api.extendsFrom shadow
|
||||||
task sourcesJar(type: Jar, dependsOn: classes) {
|
}
|
||||||
classifier = "sources"
|
|
||||||
from sourceSets.main.allSource
|
dependencies {
|
||||||
|
//to change the versions see the gradle.properties file
|
||||||
|
minecraft "com.mojang:minecraft:${project.minecraft_version}"
|
||||||
|
mappings "net.fabricmc:yarn:${project.yarn_mappings}:v2"
|
||||||
|
modImplementation "net.fabricmc:fabric-loader:${project.loader_version}"
|
||||||
|
|
||||||
|
// Fabric API. This is technically optional, but you probably want it anyway.
|
||||||
|
modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}"
|
||||||
|
|
||||||
|
modImplementation "dev.lambdaurora:spruceui:${project.spruceui_version}"
|
||||||
|
include "dev.lambdaurora:spruceui:${project.spruceui_version}"
|
||||||
|
|
||||||
|
modImplementation "com.terraformersmc:modmenu:${project.modmenu_version}"
|
||||||
|
|
||||||
|
// Compatibility mods
|
||||||
|
/*modImplementation("com.github.joaoh1:okzoomer:e13183c59b") {
|
||||||
|
exclude group: 'me.shedaniel.cloth'
|
||||||
|
exclude group: 'io.github.prospector'
|
||||||
|
}*/
|
||||||
|
|
||||||
|
shadow 'com.electronwill.night-config:core:3.6.3'
|
||||||
|
shadow 'com.electronwill.night-config:toml:3.6.3'
|
||||||
|
}
|
||||||
|
|
||||||
|
java {
|
||||||
|
sourceCompatibility = JavaVersion.toVersion(targetJavaVersion)
|
||||||
|
targetCompatibility = JavaVersion.toVersion(targetJavaVersion)
|
||||||
|
|
||||||
|
withSourcesJar()
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.withType(JavaCompile).configureEach {
|
||||||
|
it.options.encoding = 'UTF-8'
|
||||||
|
|
||||||
|
it.options.release.set(targetJavaVersion)
|
||||||
|
}
|
||||||
|
|
||||||
|
processResources {
|
||||||
|
inputs.property 'version', project.version
|
||||||
|
|
||||||
|
filesMatching('fabric.mod.json') {
|
||||||
|
expand 'version': project.version
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
jar {
|
||||||
|
from('LICENSE') {
|
||||||
|
rename { "${it}_${project.archivesBaseName}" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
license {
|
||||||
|
header file('HEADER')
|
||||||
|
include '**/*.java'
|
||||||
|
}
|
||||||
|
|
||||||
|
shadowJar {
|
||||||
|
dependsOn jar
|
||||||
|
configurations = [project.configurations.shadow]
|
||||||
|
archiveClassifier.set('dev')
|
||||||
|
|
||||||
|
exclude 'META-INF/maven/**'
|
||||||
|
exclude 'com/google/**'
|
||||||
|
exclude 'javax/**'
|
||||||
|
exclude 'org/**'
|
||||||
|
relocate 'com.electronwill.nightconfig', 'dev.lambdaurora.lambdacontrols.shadow.nightconfig'
|
||||||
|
}
|
||||||
|
remapJar.dependsOn(shadowJar)
|
||||||
|
|
||||||
|
task shadowRemapJar(type: RemapJarTask) {
|
||||||
|
dependsOn shadowJar
|
||||||
|
|
||||||
|
input.set(file("${project.buildDir}/libs/$archivesBaseName-$version-dev.jar"))
|
||||||
|
archiveFileName = "${archivesBaseName}-${version}.jar"
|
||||||
|
addNestedDependencies.set(true)
|
||||||
|
remapAccessWidener.set(true)
|
||||||
|
}
|
||||||
|
build.dependsOn(shadowRemapJar)
|
||||||
|
|
||||||
|
task publishModrinth(type: TaskModrinthUpload) {
|
||||||
|
dependsOn(build)
|
||||||
|
onlyIf {
|
||||||
|
System.getenv('MODRINTH_TOKEN')
|
||||||
|
}
|
||||||
|
|
||||||
|
token = System.getenv('MODRINTH_TOKEN')
|
||||||
|
projectId = project.modrinth_id
|
||||||
|
versionNumber = version
|
||||||
|
versionName = "LambdaControls ${project.mod_version} (${getMCVersionString()})"
|
||||||
|
uploadFile = shadowRemapJar
|
||||||
|
addGameVersion((String) project.minecraft_version)
|
||||||
|
addLoader('fabric')
|
||||||
|
versionType = isMCVersionNonRelease() ? VersionType.BETA : VersionType.RELEASE
|
||||||
|
|
||||||
|
// Changelog fetching
|
||||||
|
def changelogText = file('CHANGELOG.md').text
|
||||||
|
def regexVersion = ((String) project.mod_version).replaceAll('\\.', /\\./).replaceAll('\\+', '\\+')
|
||||||
|
def changelogRegex = ~"###? ${regexVersion}\\n\\n(( *- .+\\n)+)"
|
||||||
|
def matcher = changelogText =~ changelogRegex
|
||||||
|
matcher.find()
|
||||||
|
|
||||||
|
changelog = matcher.group(1)
|
||||||
|
|
||||||
|
// 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
|
// configure the maven publication
|
||||||
publishing {
|
publishing {
|
||||||
publications {
|
publications {
|
||||||
mavenJava(MavenPublication) {
|
mavenJava(MavenPublication) {
|
||||||
// add all the jars that should be included when publishing to maven
|
artifact(shadowRemapJar) {
|
||||||
artifact(remapJar) {
|
builtBy shadowRemapJar
|
||||||
builtBy remapJar
|
|
||||||
}
|
}
|
||||||
artifact(sourcesJar) {
|
artifact(sourcesJar) {
|
||||||
builtBy remapSourcesJar
|
builtBy remapSourcesJar
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pom {
|
||||||
|
name = 'LambdaControls'
|
||||||
|
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')
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// select the repositories you want to publish to
|
|
||||||
repositories {
|
repositories {
|
||||||
mavenLocal()
|
mavenLocal()
|
||||||
|
maven {
|
||||||
|
name "GithubPackages"
|
||||||
|
url uri("https://maven.pkg.github.com/LambdAurora/LambdaControls")
|
||||||
|
credentials {
|
||||||
|
username = project.findProperty("gpr.user") ?: System.getenv("USERNAME")
|
||||||
|
password = project.findProperty("gpr.key") ?: System.getenv("TOKEN")
|
||||||
}
|
}
|
||||||
}*/
|
}
|
||||||
|
def lambdacontrolsMaven = System.getenv('LAMBDACONTROLS_MAVEN')
|
||||||
|
if (lambdacontrolsMaven) {
|
||||||
|
maven {
|
||||||
|
name 'LambdaControlsMaven'
|
||||||
|
url uri(lambdacontrolsMaven)
|
||||||
|
credentials {
|
||||||
|
username = project.findProperty('gpr.user') ?: System.getenv('MAVEN_USERNAME')
|
||||||
|
password = project.findProperty('gpr.key') ?: System.getenv('MAVEN_PASSWORD')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,17 +0,0 @@
|
|||||||
plugins {
|
|
||||||
id 'java-library'
|
|
||||||
}
|
|
||||||
|
|
||||||
archivesBaseName = project.archives_base_name + "-core"
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
api "org.jetbrains:annotations:17.0.0"
|
|
||||||
api "org.aperlambda:lambdajcommon:1.8.0"
|
|
||||||
api "com.electronwill.night-config:core:3.5.3"
|
|
||||||
api "com.electronwill.night-config:toml:3.5.3"
|
|
||||||
}
|
|
||||||
|
|
||||||
java {
|
|
||||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
|
||||||
targetCompatibility = JavaVersion.VERSION_1_8
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
plugins {
|
|
||||||
id 'java-library'
|
|
||||||
}
|
|
||||||
|
|
||||||
archivesBaseName = project.archives_base_name + "-elytra"
|
|
||||||
|
|
||||||
repositories {
|
|
||||||
maven { url = 'https://hub.spigotmc.org/nexus/content/groups/public/' }
|
|
||||||
maven { url = 'https://mcelytra.github.io/maven/' }
|
|
||||||
maven { url = 'https://libraries.minecraft.net/' }
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
api project(":core")
|
|
||||||
implementation "org.mcelytra:elytra-core:1.0.0-SNAPSHOT"
|
|
||||||
}
|
|
||||||
|
|
||||||
java {
|
|
||||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
|
||||||
targetCompatibility = JavaVersion.VERSION_1_8
|
|
||||||
}
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2020 LambdAurora <aurora42lambda@gmail.com>
|
|
||||||
*
|
|
||||||
* This file is part of LambdaControls.
|
|
||||||
*
|
|
||||||
* Licensed under the MIT license. For more information,
|
|
||||||
* see the LICENSE file.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package me.lambdaurora.lambdacontrols.event;
|
|
||||||
|
|
||||||
import me.lambdaurora.lambdacontrols.ControlsMode;
|
|
||||||
import me.lambdaurora.lambdacontrols.LambdaControlsConstants;
|
|
||||||
import org.aperlambda.lambdacommon.Identifier;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.mcelytra.core.entity.EntityPlayer;
|
|
||||||
import org.mcelytra.core.event.HandlerList;
|
|
||||||
import org.mcelytra.core.event.player.PlayerEvent;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents an event which is fired when a player change their controls mode.
|
|
||||||
*
|
|
||||||
* @author LambdAurora
|
|
||||||
* @version 1.1.0
|
|
||||||
* @since 1.1.0
|
|
||||||
*/
|
|
||||||
public class PlayerControlsModeEvent extends PlayerEvent
|
|
||||||
{
|
|
||||||
private static final HandlerList HANDLERS = new HandlerList();
|
|
||||||
private final ControlsMode controlsMode;
|
|
||||||
|
|
||||||
public PlayerControlsModeEvent(@NotNull EntityPlayer player, @NotNull ControlsMode controlsMode)
|
|
||||||
{
|
|
||||||
super(new Identifier(LambdaControlsConstants.NAMESPACE, "player_controls_mode"), player, true);
|
|
||||||
this.controlsMode = controlsMode;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the controls mode of the player.
|
|
||||||
*
|
|
||||||
* @return The player's controls mode.
|
|
||||||
*/
|
|
||||||
public ControlsMode getControlsMode()
|
|
||||||
{
|
|
||||||
return this.controlsMode;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull HandlerList getHandlers()
|
|
||||||
{
|
|
||||||
return HANDLERS;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,93 +0,0 @@
|
|||||||
plugins {
|
|
||||||
id 'fabric-loom' version '0.2.6-SNAPSHOT'
|
|
||||||
id 'java-library'
|
|
||||||
id 'maven-publish'
|
|
||||||
}
|
|
||||||
|
|
||||||
archivesBaseName = project.archives_base_name + "-fabric"
|
|
||||||
|
|
||||||
minecraft {
|
|
||||||
}
|
|
||||||
|
|
||||||
repositories {
|
|
||||||
maven {
|
|
||||||
name = 'CottonMC'
|
|
||||||
url = 'http://server.bbkr.space:8081/artifactory/libs-snapshot'
|
|
||||||
}
|
|
||||||
repositories {
|
|
||||||
maven { url = "https://jitpack.io" }
|
|
||||||
}
|
|
||||||
// OkZoomer
|
|
||||||
ivy {
|
|
||||||
url 'https://github.com/joaoh1/OkZoomer/releases/download/'
|
|
||||||
patternLayout {
|
|
||||||
artifact '[revision]/[module]-[revision].[ext]'
|
|
||||||
}
|
|
||||||
metadataSources() {
|
|
||||||
artifact()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
configurations {
|
|
||||||
shadow
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
//to change the versions see the gradle.properties file
|
|
||||||
minecraft "com.mojang:minecraft:${project.minecraft_version}"
|
|
||||||
mappings "net.fabricmc:yarn:${project.yarn_mappings}"
|
|
||||||
modCompile "net.fabricmc:fabric-loader:${project.loader_version}"
|
|
||||||
|
|
||||||
// Fabric API. This is technically optional, but you probably want it anyway.
|
|
||||||
modApi "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}"
|
|
||||||
|
|
||||||
modCompile "io.github.prospector:modmenu:1.8.5+build.23"
|
|
||||||
modCompile "com.github.lambdaurora:spruceui:${project.spruceui_version}"
|
|
||||||
include "com.github.lambdaurora:spruceui:${project.spruceui_version}"
|
|
||||||
|
|
||||||
// Compatibility mods
|
|
||||||
modCompile "io.github.joaoh1:okzoomer:2.1.0-beta.2"
|
|
||||||
|
|
||||||
api project(":core")
|
|
||||||
shadow project(":core")
|
|
||||||
include "org.jetbrains:annotations:17.0.0"
|
|
||||||
include("org.aperlambda:lambdajcommon:1.8.0") {
|
|
||||||
exclude group: 'com.google.code.gson'
|
|
||||||
exclude group: 'com.google.guava'
|
|
||||||
}
|
|
||||||
include "com.electronwill.night-config:core:3.5.3"
|
|
||||||
include "com.electronwill.night-config:toml:3.5.3"
|
|
||||||
}
|
|
||||||
|
|
||||||
processResources {
|
|
||||||
inputs.property "version", project.version
|
|
||||||
|
|
||||||
from(sourceSets.main.resources.srcDirs) {
|
|
||||||
include "fabric.mod.json"
|
|
||||||
expand "version": project.version
|
|
||||||
}
|
|
||||||
|
|
||||||
from(sourceSets.main.resources.srcDirs) {
|
|
||||||
exclude "fabric.mod.json"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
java {
|
|
||||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
|
||||||
targetCompatibility = JavaVersion.VERSION_1_8
|
|
||||||
}
|
|
||||||
|
|
||||||
jar {
|
|
||||||
from "../LICENSE"
|
|
||||||
|
|
||||||
from {
|
|
||||||
configurations.shadow.filter {
|
|
||||||
it.getName().contains("lambdacontrols")
|
|
||||||
}.collect {
|
|
||||||
it.isDirectory() ? it : zipTree(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
build.dependsOn(":core:build")
|
|
||||||
@@ -1,129 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2020 LambdAurora <aurora42lambda@gmail.com>
|
|
||||||
*
|
|
||||||
* This file is part of LambdaControls.
|
|
||||||
*
|
|
||||||
* Licensed under the MIT license. For more information,
|
|
||||||
* see the LICENSE file.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package me.lambdaurora.lambdacontrols;
|
|
||||||
|
|
||||||
import io.netty.buffer.Unpooled;
|
|
||||||
import me.lambdaurora.lambdacontrols.event.PlayerChangeControlsModeCallback;
|
|
||||||
import net.fabricmc.api.ModInitializer;
|
|
||||||
import net.fabricmc.fabric.api.network.ServerSidePacketRegistry;
|
|
||||||
import net.fabricmc.loader.api.FabricLoader;
|
|
||||||
import net.fabricmc.loader.api.ModContainer;
|
|
||||||
import net.minecraft.util.Identifier;
|
|
||||||
import net.minecraft.util.PacketByteBuf;
|
|
||||||
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 LambdaControls mod.
|
|
||||||
*
|
|
||||||
* @author LambdAurora
|
|
||||||
* @version 1.1.0
|
|
||||||
* @since 1.0.0
|
|
||||||
*/
|
|
||||||
public class LambdaControls implements ModInitializer
|
|
||||||
{
|
|
||||||
private static LambdaControls INSTANCE;
|
|
||||||
public static final Identifier CONTROLS_MODE_CHANNEL = new Identifier(LambdaControlsConstants.CONTROLS_MODE_CHANNEL.toString());
|
|
||||||
public static final Identifier FEATURE_CHANNEL = new Identifier(LambdaControlsConstants.FEATURE_CHANNEL.toString());
|
|
||||||
public static final Identifier HELLO_CHANNEL = new Identifier(LambdaControlsConstants.HELLO_CHANNEL.toString());
|
|
||||||
|
|
||||||
public final Logger logger = LogManager.getLogger("LambdaControls");
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onInitialize()
|
|
||||||
{
|
|
||||||
INSTANCE = this;
|
|
||||||
this.log("Initializing LambdaControls...");
|
|
||||||
|
|
||||||
ServerSidePacketRegistry.INSTANCE.register(HELLO_CHANNEL,
|
|
||||||
(context, attachedData) -> {
|
|
||||||
String version = attachedData.readString(16);
|
|
||||||
ControlsMode.byId(attachedData.readString(32))
|
|
||||||
.ifPresent(controlsMode -> context.getTaskQueue()
|
|
||||||
.execute(() -> PlayerChangeControlsModeCallback.EVENT.invoker().apply(context.getPlayer(), controlsMode)));
|
|
||||||
context.getTaskQueue().execute(() ->
|
|
||||||
ServerSidePacketRegistry.INSTANCE.sendToPlayer(context.getPlayer(), FEATURE_CHANNEL, this.makeFeatureBuffer(LambdaControlsFeature.FRONT_BLOCK_PLACING)));
|
|
||||||
});
|
|
||||||
ServerSidePacketRegistry.INSTANCE.register(CONTROLS_MODE_CHANNEL,
|
|
||||||
(context, attachedData) -> ControlsMode.byId(attachedData.readString(32))
|
|
||||||
.ifPresent(controlsMode -> context.getTaskQueue()
|
|
||||||
.execute(() -> PlayerChangeControlsModeCallback.EVENT.invoker().apply(context.getPlayer(), controlsMode))));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Prints a message to the terminal.
|
|
||||||
*
|
|
||||||
* @param info The message to print.
|
|
||||||
*/
|
|
||||||
public void log(String info)
|
|
||||||
{
|
|
||||||
this.logger.info("[LambdaControls] " + info);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Prints a warning to the terminal.
|
|
||||||
*
|
|
||||||
* @param warning The warning to print.
|
|
||||||
*/
|
|
||||||
public void warn(String warning)
|
|
||||||
{
|
|
||||||
this.logger.info("[LambdaControls] " + warning);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a packet byte buffer made for the lambdacontrols: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 lambdacontrols:feature plugin message.
|
|
||||||
*
|
|
||||||
* @param feature The feature data to send.
|
|
||||||
* @return The packet byte buffer.
|
|
||||||
*/
|
|
||||||
public PacketByteBuf makeFeatureBuffer(@NotNull LambdaControlsFeature feature)
|
|
||||||
{
|
|
||||||
Objects.requireNonNull(feature, "Feature cannot be null.");
|
|
||||||
PacketByteBuf buffer = new PacketByteBuf(Unpooled.buffer()).writeString(feature.getName(), 64);
|
|
||||||
buffer.writeBoolean(feature.isAllowed());
|
|
||||||
return buffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PacketByteBuf makeHello(@NotNull ControlsMode controlsMode)
|
|
||||||
{
|
|
||||||
String version = "";
|
|
||||||
Optional<ModContainer> container;
|
|
||||||
if ((container = FabricLoader.getInstance().getModContainer(LambdaControlsConstants.NAMESPACE)).isPresent()) {
|
|
||||||
version = container.get().getMetadata().getVersion().getFriendlyString();
|
|
||||||
}
|
|
||||||
return new PacketByteBuf(Unpooled.buffer()).writeString(version, 16).writeString(controlsMode.getName(), 32);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the LambdaControls instance.
|
|
||||||
*
|
|
||||||
* @return The LambdaControls instance.
|
|
||||||
*/
|
|
||||||
public static LambdaControls get()
|
|
||||||
{
|
|
||||||
return INSTANCE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,91 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2020 LambdAurora <aurora42lambda@gmail.com>
|
|
||||||
*
|
|
||||||
* This file is part of LambdaControls.
|
|
||||||
*
|
|
||||||
* Licensed under the MIT license. For more information,
|
|
||||||
* see the LICENSE file.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package me.lambdaurora.lambdacontrols.client;
|
|
||||||
|
|
||||||
import 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.
|
|
||||||
*
|
|
||||||
* @author LambdAurora
|
|
||||||
* @version 1.0.0
|
|
||||||
* @since 1.0.0
|
|
||||||
*/
|
|
||||||
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 getId()
|
|
||||||
{
|
|
||||||
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 getTranslatedName()
|
|
||||||
{
|
|
||||||
return I18n.translate("lambdacontrols.controller_type." + this.getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
@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 Optional<ControllerType> byId(@NotNull String id)
|
|
||||||
{
|
|
||||||
return Arrays.stream(values()).filter(mode -> mode.getName().equalsIgnoreCase(id)).findFirst();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2020 LambdAurora <aurora42lambda@gmail.com>
|
|
||||||
*
|
|
||||||
* This file is part of LambdaControls.
|
|
||||||
*
|
|
||||||
* Licensed under the MIT license. For more information,
|
|
||||||
* see the LICENSE file.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package me.lambdaurora.lambdacontrols.client;
|
|
||||||
|
|
||||||
import 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.
|
|
||||||
*
|
|
||||||
* @author LambdAurora
|
|
||||||
* @version 1.0.0
|
|
||||||
* @since 1.0.0
|
|
||||||
*/
|
|
||||||
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 getTranslatedName()
|
|
||||||
{
|
|
||||||
return I18n.translate("lambdacontrols.hud_side." + this.getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
@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 Optional<HudSide> byId(@NotNull String id)
|
|
||||||
{
|
|
||||||
return Arrays.stream(values()).filter(mode -> mode.getName().equalsIgnoreCase(id)).findFirst();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,326 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2020 LambdAurora <aurora42lambda@gmail.com>
|
|
||||||
*
|
|
||||||
* This file is part of LambdaControls.
|
|
||||||
*
|
|
||||||
* Licensed under the MIT license. For more information,
|
|
||||||
* see the LICENSE file.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package me.lambdaurora.lambdacontrols.client;
|
|
||||||
|
|
||||||
import com.mojang.blaze3d.platform.GlStateManager;
|
|
||||||
import me.lambdaurora.lambdacontrols.ControlsMode;
|
|
||||||
import me.lambdaurora.lambdacontrols.LambdaControls;
|
|
||||||
import me.lambdaurora.lambdacontrols.LambdaControlsConstants;
|
|
||||||
import me.lambdaurora.lambdacontrols.LambdaControlsFeature;
|
|
||||||
import me.lambdaurora.lambdacontrols.client.compat.LambdaControlsCompat;
|
|
||||||
import me.lambdaurora.lambdacontrols.client.controller.ButtonBinding;
|
|
||||||
import me.lambdaurora.lambdacontrols.client.controller.Controller;
|
|
||||||
import me.lambdaurora.lambdacontrols.client.gui.LambdaControlsHud;
|
|
||||||
import me.lambdaurora.lambdacontrols.client.gui.TouchscreenOverlay;
|
|
||||||
import me.lambdaurora.spruceui.event.OpenScreenCallback;
|
|
||||||
import me.lambdaurora.spruceui.hud.HudManager;
|
|
||||||
import net.fabricmc.api.ClientModInitializer;
|
|
||||||
import net.fabricmc.fabric.api.client.keybinding.FabricKeyBinding;
|
|
||||||
import net.fabricmc.fabric.api.client.keybinding.KeyBindingRegistry;
|
|
||||||
import net.fabricmc.fabric.api.event.client.ClientTickCallback;
|
|
||||||
import net.fabricmc.fabric.api.network.ClientSidePacketRegistry;
|
|
||||||
import net.minecraft.client.MinecraftClient;
|
|
||||||
import net.minecraft.client.font.TextRenderer;
|
|
||||||
import net.minecraft.client.gui.DrawableHelper;
|
|
||||||
import net.minecraft.client.resource.language.I18n;
|
|
||||||
import net.minecraft.client.toast.SystemToast;
|
|
||||||
import net.minecraft.client.util.InputUtil;
|
|
||||||
import net.minecraft.text.LiteralText;
|
|
||||||
import net.minecraft.text.TranslatableText;
|
|
||||||
import net.minecraft.util.Identifier;
|
|
||||||
import org.aperlambda.lambdacommon.utils.Pair;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.lwjgl.glfw.GLFW;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents the LambdaControls client mod.
|
|
||||||
*
|
|
||||||
* @author LambdAurora
|
|
||||||
* @version 1.1.0
|
|
||||||
* @since 1.1.0
|
|
||||||
*/
|
|
||||||
public class LambdaControlsClient extends LambdaControls implements ClientModInitializer
|
|
||||||
{
|
|
||||||
private static LambdaControlsClient INSTANCE;
|
|
||||||
public static final FabricKeyBinding BINDING_LOOK_UP = FabricKeyBinding.Builder.create(new Identifier(LambdaControlsConstants.NAMESPACE, "look_up"),
|
|
||||||
InputUtil.Type.KEYSYM, GLFW.GLFW_KEY_KP_8, "key.categories.movement").build();
|
|
||||||
public static final FabricKeyBinding BINDING_LOOK_RIGHT = FabricKeyBinding.Builder.create(new Identifier(LambdaControlsConstants.NAMESPACE, "look_right"),
|
|
||||||
InputUtil.Type.KEYSYM, GLFW.GLFW_KEY_KP_6, "key.categories.movement").build();
|
|
||||||
public static final FabricKeyBinding BINDING_LOOK_DOWN = FabricKeyBinding.Builder.create(new Identifier(LambdaControlsConstants.NAMESPACE, "look_down"),
|
|
||||||
InputUtil.Type.KEYSYM, GLFW.GLFW_KEY_KP_2, "key.categories.movement").build();
|
|
||||||
public static final FabricKeyBinding BINDING_LOOK_LEFT = FabricKeyBinding.Builder.create(new Identifier(LambdaControlsConstants.NAMESPACE, "look_left"),
|
|
||||||
InputUtil.Type.KEYSYM, GLFW.GLFW_KEY_KP_4, "key.categories.movement").build();
|
|
||||||
public static final Identifier CONTROLLER_BUTTONS = new Identifier(LambdaControlsConstants.NAMESPACE, "textures/gui/controller_buttons.png");
|
|
||||||
public static final Identifier CONTROLLER_AXIS = new Identifier(LambdaControlsConstants.NAMESPACE, "textures/gui/controller_axis.png");
|
|
||||||
public final LambdaControlsConfig config = new LambdaControlsConfig(this);
|
|
||||||
public final LambdaInput input = new LambdaInput(this);
|
|
||||||
private LambdaControlsHud hud;
|
|
||||||
private ControlsMode previousControlsMode;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onInitializeClient()
|
|
||||||
{
|
|
||||||
INSTANCE = this;
|
|
||||||
KeyBindingRegistry.INSTANCE.register(BINDING_LOOK_UP);
|
|
||||||
KeyBindingRegistry.INSTANCE.register(BINDING_LOOK_RIGHT);
|
|
||||||
KeyBindingRegistry.INSTANCE.register(BINDING_LOOK_DOWN);
|
|
||||||
KeyBindingRegistry.INSTANCE.register(BINDING_LOOK_LEFT);
|
|
||||||
|
|
||||||
ClientSidePacketRegistry.INSTANCE.register(CONTROLS_MODE_CHANNEL, (context, attachedData) -> context.getTaskQueue()
|
|
||||||
.execute(() -> ClientSidePacketRegistry.INSTANCE.sendToServer(CONTROLS_MODE_CHANNEL, this.makeControlsModeBuffer(this.config.getControlsMode()))));
|
|
||||||
ClientSidePacketRegistry.INSTANCE.register(FEATURE_CHANNEL, (context, attachedData) -> {
|
|
||||||
String name = attachedData.readString(64);
|
|
||||||
boolean allowed = attachedData.readBoolean();
|
|
||||||
LambdaControlsFeature.fromName(name).ifPresent(feature -> context.getTaskQueue().execute(() -> feature.setAllowed(allowed)));
|
|
||||||
});
|
|
||||||
|
|
||||||
ClientTickCallback.EVENT.register(this::onTick);
|
|
||||||
|
|
||||||
OpenScreenCallback.EVENT.register((client, screen) -> {
|
|
||||||
if (screen == null && this.config.getControlsMode() == ControlsMode.TOUCHSCREEN) {
|
|
||||||
screen = new TouchscreenOverlay(this);
|
|
||||||
screen.init(client, client.getWindow().getScaledWidth(), client.getWindow().getScaledHeight());
|
|
||||||
client.skipGameRender = false;
|
|
||||||
client.currentScreen = screen;
|
|
||||||
} else if (screen != null) {
|
|
||||||
this.input.onScreenOpen(client, client.getWindow().getWidth(), client.getWindow().getHeight());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
HudManager.register(this.hud = new LambdaControlsHud(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is called when Minecraft is initializing.
|
|
||||||
*/
|
|
||||||
public void onMcInit(@NotNull MinecraftClient client)
|
|
||||||
{
|
|
||||||
ButtonBinding.init(client.options);
|
|
||||||
this.config.load();
|
|
||||||
this.hud.setVisible(this.config.isHudEnabled());
|
|
||||||
Controller.updateMappings();
|
|
||||||
GLFW.glfwSetJoystickCallback((jid, event) -> {
|
|
||||||
if (event == GLFW.GLFW_CONNECTED) {
|
|
||||||
Controller controller = Controller.byId(jid);
|
|
||||||
client.getToastManager().add(new SystemToast(SystemToast.Type.TUTORIAL_HINT, new TranslatableText("lambdacontrols.controller.connected", jid),
|
|
||||||
new LiteralText(controller.getName())));
|
|
||||||
} else if (event == GLFW.GLFW_DISCONNECTED) {
|
|
||||||
client.getToastManager().add(new SystemToast(SystemToast.Type.TUTORIAL_HINT, new TranslatableText("lambdacontrols.controller.disconnected", jid),
|
|
||||||
null));
|
|
||||||
}
|
|
||||||
|
|
||||||
this.switchControlsMode();
|
|
||||||
});
|
|
||||||
|
|
||||||
LambdaControlsCompat.init(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is called every Minecraft tick.
|
|
||||||
*
|
|
||||||
* @param client The client instance.
|
|
||||||
*/
|
|
||||||
public void onTick(@NotNull MinecraftClient client)
|
|
||||||
{
|
|
||||||
this.input.onTick(client);
|
|
||||||
if (this.config.getControlsMode() == ControlsMode.CONTROLLER && (client.isWindowFocused() || this.config.hasUnfocusedInput()))
|
|
||||||
this.input.onControllerTick(client);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onRender(MinecraftClient client)
|
|
||||||
{
|
|
||||||
this.input.onRender(client);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when leaving a server.
|
|
||||||
*/
|
|
||||||
public void onLeave()
|
|
||||||
{
|
|
||||||
LambdaControlsFeature.resetAllAllowed();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Switches the controls mode if the auto switch is enabled.
|
|
||||||
*/
|
|
||||||
public void switchControlsMode()
|
|
||||||
{
|
|
||||||
if (this.config.hasAutoSwitchMode()) {
|
|
||||||
if (this.config.getController().isGamepad()) {
|
|
||||||
this.previousControlsMode = this.config.getControlsMode();
|
|
||||||
this.config.setControlsMode(ControlsMode.CONTROLLER);
|
|
||||||
} else {
|
|
||||||
if (this.previousControlsMode == null) {
|
|
||||||
this.previousControlsMode = ControlsMode.DEFAULT;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.config.setControlsMode(this.previousControlsMode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets whether the HUD is enabled or not.
|
|
||||||
*
|
|
||||||
* @param enabled True if the HUD is enabled, else false.
|
|
||||||
*/
|
|
||||||
public void setHudEnabled(boolean enabled)
|
|
||||||
{
|
|
||||||
this.config.setHudEnabled(enabled);
|
|
||||||
this.hud.setVisible(enabled);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the LambdaControls client instance.
|
|
||||||
*
|
|
||||||
* @return The LambdaControls client instance.
|
|
||||||
*/
|
|
||||||
public static LambdaControlsClient get()
|
|
||||||
{
|
|
||||||
return INSTANCE;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Pair<Integer, Integer> drawButton(int x, int y, @NotNull ButtonBinding button, @NotNull MinecraftClient client)
|
|
||||||
{
|
|
||||||
return drawButton(x, y, button.getButton(), client);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Pair<Integer, Integer> drawButton(int x, int y, int[] buttons, @NotNull MinecraftClient client)
|
|
||||||
{
|
|
||||||
int height = 0;
|
|
||||||
int length = 0;
|
|
||||||
int currentX = x;
|
|
||||||
for (int i = 0; i < buttons.length; i++) {
|
|
||||||
int btn = buttons[i];
|
|
||||||
Pair<Integer, Integer> size = drawButton(currentX, y, btn, client);
|
|
||||||
if (size.key > height)
|
|
||||||
height = size.key;
|
|
||||||
length += size.key;
|
|
||||||
if (i + 1 < buttons.length) {
|
|
||||||
length += 2;
|
|
||||||
currentX = x + length;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Pair.of(length, height);
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("deprecated")
|
|
||||||
public static Pair<Integer, Integer> drawButton(int x, int y, int button, @NotNull MinecraftClient client)
|
|
||||||
{
|
|
||||||
boolean second = false;
|
|
||||||
if (button == -1)
|
|
||||||
return Pair.of(0, 0);
|
|
||||||
else if (button >= 500) {
|
|
||||||
button -= 1000;
|
|
||||||
second = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
int controllerType = get().config.getControllerType().getId();
|
|
||||||
boolean axis = false;
|
|
||||||
int buttonOffset = button * 15;
|
|
||||||
switch (button) {
|
|
||||||
case GLFW.GLFW_GAMEPAD_BUTTON_LEFT_BUMPER:
|
|
||||||
buttonOffset = 7 * 15;
|
|
||||||
break;
|
|
||||||
case GLFW.GLFW_GAMEPAD_BUTTON_RIGHT_BUMPER:
|
|
||||||
buttonOffset = 8 * 15;
|
|
||||||
break;
|
|
||||||
case GLFW.GLFW_GAMEPAD_BUTTON_BACK:
|
|
||||||
buttonOffset = 4 * 15;
|
|
||||||
break;
|
|
||||||
case GLFW.GLFW_GAMEPAD_BUTTON_START:
|
|
||||||
buttonOffset = 6 * 15;
|
|
||||||
break;
|
|
||||||
case GLFW.GLFW_GAMEPAD_BUTTON_GUIDE:
|
|
||||||
buttonOffset = 5 * 15;
|
|
||||||
break;
|
|
||||||
case GLFW.GLFW_GAMEPAD_BUTTON_LEFT_THUMB:
|
|
||||||
buttonOffset = 15 * 15;
|
|
||||||
break;
|
|
||||||
case GLFW.GLFW_GAMEPAD_BUTTON_RIGHT_THUMB:
|
|
||||||
buttonOffset = 16 * 15;
|
|
||||||
break;
|
|
||||||
case GLFW.GLFW_GAMEPAD_AXIS_LEFT_X + 100:
|
|
||||||
buttonOffset = 0;
|
|
||||||
axis = true;
|
|
||||||
break;
|
|
||||||
case GLFW.GLFW_GAMEPAD_AXIS_LEFT_Y + 100:
|
|
||||||
buttonOffset = 18;
|
|
||||||
axis = true;
|
|
||||||
break;
|
|
||||||
case GLFW.GLFW_GAMEPAD_AXIS_RIGHT_X + 100:
|
|
||||||
buttonOffset = 2 * 18;
|
|
||||||
axis = true;
|
|
||||||
break;
|
|
||||||
case GLFW.GLFW_GAMEPAD_AXIS_RIGHT_Y + 100:
|
|
||||||
buttonOffset = 3 * 18;
|
|
||||||
axis = true;
|
|
||||||
break;
|
|
||||||
case GLFW.GLFW_GAMEPAD_AXIS_LEFT_X + 200:
|
|
||||||
buttonOffset = 4 * 18;
|
|
||||||
axis = true;
|
|
||||||
break;
|
|
||||||
case GLFW.GLFW_GAMEPAD_AXIS_LEFT_Y + 200:
|
|
||||||
buttonOffset = 5 * 18;
|
|
||||||
axis = true;
|
|
||||||
break;
|
|
||||||
case GLFW.GLFW_GAMEPAD_AXIS_RIGHT_X + 200:
|
|
||||||
buttonOffset = 6 * 18;
|
|
||||||
axis = true;
|
|
||||||
break;
|
|
||||||
case GLFW.GLFW_GAMEPAD_AXIS_RIGHT_Y + 200:
|
|
||||||
buttonOffset = 7 * 18;
|
|
||||||
axis = true;
|
|
||||||
break;
|
|
||||||
case GLFW.GLFW_GAMEPAD_AXIS_LEFT_TRIGGER + 100:
|
|
||||||
case GLFW.GLFW_GAMEPAD_AXIS_LEFT_TRIGGER + 200:
|
|
||||||
buttonOffset = 9 * 15;
|
|
||||||
break;
|
|
||||||
case GLFW.GLFW_GAMEPAD_AXIS_RIGHT_TRIGGER + 100:
|
|
||||||
case GLFW.GLFW_GAMEPAD_AXIS_RIGHT_TRIGGER + 200:
|
|
||||||
buttonOffset = 10 * 15;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
client.getTextureManager().bindTexture(axis ? LambdaControlsClient.CONTROLLER_AXIS : LambdaControlsClient.CONTROLLER_BUTTONS);
|
|
||||||
GlStateManager.disableDepthTest();
|
|
||||||
|
|
||||||
GlStateManager.color4f(1.0F, second ? 0.0F : 1.0F, 1.0F, 1.0F);
|
|
||||||
DrawableHelper.blit(x, y, (float) buttonOffset, (float) (controllerType * (axis ? 18 : 15)), axis ? 18 : 15, axis ? 18 : 15, 256, 256);
|
|
||||||
GlStateManager.enableDepthTest();
|
|
||||||
|
|
||||||
return axis ? Pair.of(18, 18) : Pair.of(15, 15);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int drawButtonTip(int x, int y, @NotNull ButtonBinding button, boolean display, @NotNull MinecraftClient client)
|
|
||||||
{
|
|
||||||
return drawButtonTip(x, y, button.getButton(), button.getTranslationKey(), display, client);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int drawButtonTip(int x, int y, int[] button, @NotNull String action, boolean display, @NotNull MinecraftClient client)
|
|
||||||
{
|
|
||||||
if (display) {
|
|
||||||
int buttonWidth = drawButton(x, y, button, client).key;
|
|
||||||
|
|
||||||
String translatedAction = I18n.translate(action);
|
|
||||||
int textY = (15 - client.textRenderer.fontHeight) / 2;
|
|
||||||
client.textRenderer.drawWithShadow(translatedAction, (float) (x + buttonWidth + 5), (float) (y + textY), 14737632);
|
|
||||||
|
|
||||||
return getButtonTipWidth(translatedAction, client.textRenderer);
|
|
||||||
}
|
|
||||||
|
|
||||||
return -10;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int getButtonTipWidth(@NotNull String action, @NotNull TextRenderer textRenderer)
|
|
||||||
{
|
|
||||||
return 15 + 5 + textRenderer.getStringWidth(action);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,615 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2020 LambdAurora <aurora42lambda@gmail.com>
|
|
||||||
*
|
|
||||||
* This file is part of LambdaControls.
|
|
||||||
*
|
|
||||||
* Licensed under the MIT license. For more information,
|
|
||||||
* see the LICENSE file.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package me.lambdaurora.lambdacontrols.client;
|
|
||||||
|
|
||||||
import com.electronwill.nightconfig.core.file.FileConfig;
|
|
||||||
import me.lambdaurora.lambdacontrols.ControlsMode;
|
|
||||||
import me.lambdaurora.lambdacontrols.LambdaControlsFeature;
|
|
||||||
import me.lambdaurora.lambdacontrols.client.controller.ButtonBinding;
|
|
||||||
import me.lambdaurora.lambdacontrols.client.controller.Controller;
|
|
||||||
import me.lambdaurora.lambdacontrols.client.controller.InputManager;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
import org.lwjgl.glfw.GLFW;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.regex.Matcher;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import static org.lwjgl.glfw.GLFW.GLFW_GAMEPAD_AXIS_LEFT_X;
|
|
||||||
import static org.lwjgl.glfw.GLFW.GLFW_GAMEPAD_AXIS_LEFT_Y;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents LambdaControls configuration.
|
|
||||||
*/
|
|
||||||
public class LambdaControlsConfig
|
|
||||||
{
|
|
||||||
// General
|
|
||||||
private static final ControlsMode DEFAULT_CONTROLS_MODE = ControlsMode.DEFAULT;
|
|
||||||
private static final boolean DEFAULT_AUTO_SWITCH_MODE = false;
|
|
||||||
// HUD
|
|
||||||
private static final boolean DEFAULT_HUD_ENABLE = true;
|
|
||||||
private static final HudSide DEFAULT_HUD_SIDE = HudSide.LEFT;
|
|
||||||
// Gameplay
|
|
||||||
private static final boolean DEFAULT_FRONT_BLOCK_PLACING = false;
|
|
||||||
private static final boolean DEFAULT_FLY_DRIFTING = false;
|
|
||||||
private static final boolean DEFAULT_FLY_VERTICAL_DRIFTING = true;
|
|
||||||
// Controller
|
|
||||||
private static final ControllerType DEFAULT_CONTROLLER_TYPE = ControllerType.DEFAULT;
|
|
||||||
private static final double DEFAULT_DEAD_ZONE = 0.25;
|
|
||||||
private static final double DEFAULT_ROTATION_SPEED = 40.0;
|
|
||||||
private static final double DEFAULT_MOUSE_SPEED = 25.0;
|
|
||||||
private static final boolean DEFAULT_UNFOCUSED_INPUT = false;
|
|
||||||
|
|
||||||
private static final Pattern BUTTON_BINDING_PATTERN = Pattern.compile("(-?\\d+)\\+?");
|
|
||||||
|
|
||||||
protected final FileConfig config = FileConfig.builder("config/lambdacontrols.toml").concurrent().defaultResource("/config.toml").build();
|
|
||||||
private final LambdaControlsClient mod;
|
|
||||||
private ControlsMode controlsMode;
|
|
||||||
private ControllerType controllerType;
|
|
||||||
// HUD settings.
|
|
||||||
private boolean hudEnable;
|
|
||||||
private HudSide hudSide;
|
|
||||||
// Controller settings
|
|
||||||
private double deadZone;
|
|
||||||
private double rotationSpeed;
|
|
||||||
private double mouseSpeed;
|
|
||||||
private boolean unfocusedInput;
|
|
||||||
|
|
||||||
public LambdaControlsConfig(@NotNull LambdaControlsClient mod)
|
|
||||||
{
|
|
||||||
this.mod = mod;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Loads the configuration
|
|
||||||
*/
|
|
||||||
public void load()
|
|
||||||
{
|
|
||||||
this.config.load();
|
|
||||||
this.checkAndFix();
|
|
||||||
this.mod.log("Configuration loaded.");
|
|
||||||
this.controlsMode = ControlsMode.byId(this.config.getOrElse("controls", DEFAULT_CONTROLS_MODE.getName())).orElse(DEFAULT_CONTROLS_MODE);
|
|
||||||
// HUD settings.
|
|
||||||
this.hudEnable = this.config.getOrElse("hud.enable", DEFAULT_HUD_ENABLE);
|
|
||||||
this.hudSide = HudSide.byId(this.config.getOrElse("hud.side", DEFAULT_HUD_SIDE.getName())).orElse(DEFAULT_HUD_SIDE);
|
|
||||||
// Gameplay
|
|
||||||
LambdaControlsFeature.FRONT_BLOCK_PLACING.setEnabled(this.config.getOrElse("gameplay.front_block_placing", DEFAULT_FRONT_BLOCK_PLACING));
|
|
||||||
// Controller settings.
|
|
||||||
this.controllerType = ControllerType.byId(this.config.getOrElse("controller.type", DEFAULT_CONTROLLER_TYPE.getName())).orElse(DEFAULT_CONTROLLER_TYPE);
|
|
||||||
this.deadZone = this.config.getOrElse("controller.dead_zone", DEFAULT_DEAD_ZONE);
|
|
||||||
this.rotationSpeed = this.config.getOrElse("controller.rotation_speed", DEFAULT_ROTATION_SPEED);
|
|
||||||
this.mouseSpeed = this.config.getOrElse("controller.mouse_speed", DEFAULT_MOUSE_SPEED);
|
|
||||||
this.unfocusedInput = this.config.getOrElse("controller.unfocused_input", DEFAULT_UNFOCUSED_INPUT);
|
|
||||||
// Controller controls.
|
|
||||||
InputManager.loadButtonBindings(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Saves the configuration.
|
|
||||||
*/
|
|
||||||
public void save()
|
|
||||||
{
|
|
||||||
this.config.set("controller.dead_zone", this.deadZone);
|
|
||||||
this.config.set("controller.rotation_speed", this.rotationSpeed);
|
|
||||||
this.config.set("controller.mouse_speed", this.mouseSpeed);
|
|
||||||
this.config.set("controller.unfocused_input", this.unfocusedInput);
|
|
||||||
this.config.save();
|
|
||||||
this.mod.log("Configuration saved.");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void checkAndFix()
|
|
||||||
{
|
|
||||||
InputManager.streamBindings().forEach(binding -> {
|
|
||||||
String path = "controller.controls." + binding.getName();
|
|
||||||
Object raw = this.config.getRaw(path);
|
|
||||||
if (raw instanceof Number) {
|
|
||||||
this.mod.warn("Invalid data at \"" + path + "\", fixing...");
|
|
||||||
this.config.set(path, String.valueOf(raw));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resets the configuration to default values.
|
|
||||||
*/
|
|
||||||
public void reset()
|
|
||||||
{
|
|
||||||
// General
|
|
||||||
this.setControlsMode(DEFAULT_CONTROLS_MODE);
|
|
||||||
this.setAutoSwitchMode(DEFAULT_AUTO_SWITCH_MODE);
|
|
||||||
// HUD
|
|
||||||
this.setHudEnabled(DEFAULT_HUD_ENABLE);
|
|
||||||
this.setHudSide(DEFAULT_HUD_SIDE);
|
|
||||||
// Gameplay
|
|
||||||
this.setFrontBlockPlacing(DEFAULT_FRONT_BLOCK_PLACING);
|
|
||||||
this.setFlyDrifting(DEFAULT_FLY_DRIFTING);
|
|
||||||
this.setFlyVerticalDrifting(DEFAULT_FLY_VERTICAL_DRIFTING);
|
|
||||||
// Controller
|
|
||||||
this.setControllerType(DEFAULT_CONTROLLER_TYPE);
|
|
||||||
this.setDeadZone(DEFAULT_DEAD_ZONE);
|
|
||||||
this.setRotationSpeed(DEFAULT_ROTATION_SPEED);
|
|
||||||
this.setMouseSpeed(DEFAULT_MOUSE_SPEED);
|
|
||||||
this.setUnfocusedInput(DEFAULT_UNFOCUSED_INPUT);
|
|
||||||
|
|
||||||
// Collect prevents concurrent modification.
|
|
||||||
InputManager.streamBindings().collect(Collectors.toList()).forEach(binding -> this.setButtonBinding(binding, binding.getDefaultButton()));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the controls mode from the configuration.
|
|
||||||
*
|
|
||||||
* @return The controls mode.
|
|
||||||
*/
|
|
||||||
public @NotNull ControlsMode getControlsMode()
|
|
||||||
{
|
|
||||||
return this.controlsMode;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the controls mode in the configuration.
|
|
||||||
*
|
|
||||||
* @param controlsMode The controls mode.
|
|
||||||
*/
|
|
||||||
public void setControlsMode(@NotNull ControlsMode controlsMode)
|
|
||||||
{
|
|
||||||
this.controlsMode = controlsMode;
|
|
||||||
this.config.set("controls", controlsMode.getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns whether the auto switch mode is enabled or not.
|
|
||||||
*
|
|
||||||
* @return True if the auto switch mode is enabled, else false.
|
|
||||||
*/
|
|
||||||
public boolean hasAutoSwitchMode()
|
|
||||||
{
|
|
||||||
return this.config.getOrElse("auto_switch_mode", DEFAULT_AUTO_SWITCH_MODE);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets whether the auto switch mode is enabled or not.
|
|
||||||
*
|
|
||||||
* @param autoSwitchMode True if the auto switch mode is enabled, else false.
|
|
||||||
*/
|
|
||||||
public void setAutoSwitchMode(boolean autoSwitchMode)
|
|
||||||
{
|
|
||||||
this.config.set("auto_switch_mode", autoSwitchMode);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
HUD settings
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns whether the HUD is enabled.
|
|
||||||
*
|
|
||||||
* @return True if the HUD is enabled, else false.
|
|
||||||
*/
|
|
||||||
public boolean isHudEnabled()
|
|
||||||
{
|
|
||||||
return this.hudEnable;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets whether the HUD is enabled.
|
|
||||||
*
|
|
||||||
* @param enable True if the HUD is enabled, else false.
|
|
||||||
*/
|
|
||||||
public void setHudEnabled(boolean enable)
|
|
||||||
{
|
|
||||||
this.hudEnable = enable;
|
|
||||||
this.config.set("hud.enable", this.hudEnable);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the HUD side from the configuration.
|
|
||||||
*
|
|
||||||
* @return The HUD side.
|
|
||||||
*/
|
|
||||||
public @NotNull HudSide getHudSide()
|
|
||||||
{
|
|
||||||
return this.hudSide;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the HUD side in the configuration.
|
|
||||||
*
|
|
||||||
* @param hudSide The HUD side.
|
|
||||||
*/
|
|
||||||
public void setHudSide(@NotNull HudSide hudSide)
|
|
||||||
{
|
|
||||||
this.hudSide = hudSide;
|
|
||||||
this.config.set("hud.side", hudSide.getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Gameplay settings
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns whether front block placing is enabled or not.
|
|
||||||
*
|
|
||||||
* @return True if front block placing is enabled, else false.
|
|
||||||
*/
|
|
||||||
public boolean hasFrontBlockPlacing()
|
|
||||||
{
|
|
||||||
return LambdaControlsFeature.FRONT_BLOCK_PLACING.isEnabled();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets whether front block placing is enabled or not.
|
|
||||||
*
|
|
||||||
* @param enable True if front block placing is enabled, else false.
|
|
||||||
*/
|
|
||||||
public void setFrontBlockPlacing(boolean enable)
|
|
||||||
{
|
|
||||||
LambdaControlsFeature.FRONT_BLOCK_PLACING.setEnabled(enable);
|
|
||||||
this.config.set("gameplay.front_block_placing", enable);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns whether fly drifting is enabled or not.
|
|
||||||
*
|
|
||||||
* @return True if fly drifting is enabled, else false.
|
|
||||||
*/
|
|
||||||
public boolean hasFlyDrifting()
|
|
||||||
{
|
|
||||||
return this.config.getOrElse("gameplay.fly.drifting", DEFAULT_FLY_DRIFTING);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets whether fly drifting is enabled or not.
|
|
||||||
*
|
|
||||||
* @param flyDrifting True if fly drifting is enabled, else false.
|
|
||||||
*/
|
|
||||||
public void setFlyDrifting(boolean flyDrifting)
|
|
||||||
{
|
|
||||||
this.config.set("gameplay.fly.drifting", flyDrifting);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns whether vertical fly drifting is enabled or not.
|
|
||||||
*
|
|
||||||
* @return True if vertical fly drifting is enabled, else false.
|
|
||||||
*/
|
|
||||||
public boolean hasFlyVerticalDrifting()
|
|
||||||
{
|
|
||||||
return this.config.getOrElse("gameplay.fly.vertical_drifting", DEFAULT_FLY_VERTICAL_DRIFTING);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets whether vertical fly drifting is enabled or not.
|
|
||||||
*
|
|
||||||
* @param flyDrifting True if vertical fly drifting is enabled, else false.
|
|
||||||
*/
|
|
||||||
public void setFlyVerticalDrifting(boolean flyDrifting)
|
|
||||||
{
|
|
||||||
this.config.set("gameplay.fly.vertical_drifting", flyDrifting);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Controller settings
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the used controller.
|
|
||||||
*
|
|
||||||
* @return The used controller.
|
|
||||||
*/
|
|
||||||
public @NotNull Controller getController()
|
|
||||||
{
|
|
||||||
Object raw = this.config.getRaw("controller.id");
|
|
||||||
if (raw instanceof Number) {
|
|
||||||
return Controller.byId((Integer) raw);
|
|
||||||
} else if (raw instanceof String) {
|
|
||||||
return Controller.byGuid((String) raw).orElse(Controller.byId(GLFW.GLFW_JOYSTICK_1));
|
|
||||||
}
|
|
||||||
return Controller.byId(GLFW.GLFW_JOYSTICK_1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the used controller.
|
|
||||||
*
|
|
||||||
* @param controller The used controller.
|
|
||||||
*/
|
|
||||||
public void setController(@NotNull Controller controller)
|
|
||||||
{
|
|
||||||
this.config.set("controller.id", controller.getId());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the second controller (for Joy-Con supports).
|
|
||||||
*
|
|
||||||
* @return The second controller.
|
|
||||||
*/
|
|
||||||
public @NotNull Optional<Controller> getSecondController()
|
|
||||||
{
|
|
||||||
Object raw = this.config.getRaw("controller.id2");
|
|
||||||
if (raw instanceof Number) {
|
|
||||||
if ((int) raw == -1)
|
|
||||||
return Optional.empty();
|
|
||||||
return Optional.of(Controller.byId((Integer) raw));
|
|
||||||
} else if (raw instanceof String) {
|
|
||||||
return Optional.of(Controller.byGuid((String) raw).orElse(Controller.byId(GLFW.GLFW_JOYSTICK_1)));
|
|
||||||
}
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the second controller.
|
|
||||||
*
|
|
||||||
* @param controller The second controller.
|
|
||||||
*/
|
|
||||||
public void setSecondController(@Nullable Controller controller)
|
|
||||||
{
|
|
||||||
this.config.set("controller.id2", controller == null ? -1 : controller.getId());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the controller's type.
|
|
||||||
*
|
|
||||||
* @return The controller's type.
|
|
||||||
*/
|
|
||||||
public @NotNull ControllerType getControllerType()
|
|
||||||
{
|
|
||||||
return this.controllerType;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the controller's type.
|
|
||||||
*
|
|
||||||
* @param controllerType The controller's type.
|
|
||||||
*/
|
|
||||||
public void setControllerType(@NotNull ControllerType controllerType)
|
|
||||||
{
|
|
||||||
this.controllerType = controllerType;
|
|
||||||
this.config.set("controller.type", controllerType.getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the controller's dead zone from the configuration.
|
|
||||||
*
|
|
||||||
* @return The controller's dead zone value.
|
|
||||||
*/
|
|
||||||
public double getDeadZone()
|
|
||||||
{
|
|
||||||
return this.deadZone;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the controller's dead zone in the configuration.
|
|
||||||
*
|
|
||||||
* @param deadZone The new controller's dead zone value.
|
|
||||||
*/
|
|
||||||
public void setDeadZone(double deadZone)
|
|
||||||
{
|
|
||||||
this.deadZone = deadZone;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the controller's rotation speed.
|
|
||||||
*
|
|
||||||
* @return The rotation speed.
|
|
||||||
*/
|
|
||||||
public double getRotationSpeed()
|
|
||||||
{
|
|
||||||
return this.rotationSpeed;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the controller's rotation speed.
|
|
||||||
*
|
|
||||||
* @param rotationSpeed The rotation speed.
|
|
||||||
*/
|
|
||||||
public void setRotationSpeed(double rotationSpeed)
|
|
||||||
{
|
|
||||||
this.rotationSpeed = rotationSpeed;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the controller's mouse speed.
|
|
||||||
*
|
|
||||||
* @return The mouse speed.
|
|
||||||
*/
|
|
||||||
public double getMouseSpeed()
|
|
||||||
{
|
|
||||||
return this.mouseSpeed;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the controller's mouse speed.
|
|
||||||
*
|
|
||||||
* @param mouseSpeed The mouse speed.
|
|
||||||
*/
|
|
||||||
public void setMouseSpeed(double mouseSpeed)
|
|
||||||
{
|
|
||||||
this.mouseSpeed = mouseSpeed;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns whether the right X axis is inverted or not.
|
|
||||||
*
|
|
||||||
* @return True if the right X axis is inverted, else false.
|
|
||||||
*/
|
|
||||||
public boolean doesInvertRightXAxis()
|
|
||||||
{
|
|
||||||
return this.config.getOrElse("controller.invert_right_x_axis", false);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets whether the right X axis is inverted or not.
|
|
||||||
*
|
|
||||||
* @param invert True if the right X axis is inverted, else false.
|
|
||||||
*/
|
|
||||||
public void setInvertRightXAxis(boolean invert)
|
|
||||||
{
|
|
||||||
this.config.set("controller.invert_right_x_axis", invert);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns whether the right Y axis is inverted or not.
|
|
||||||
*
|
|
||||||
* @return True if the right Y axis is inverted, else false.
|
|
||||||
*/
|
|
||||||
public boolean doesInvertRightYAxis()
|
|
||||||
{
|
|
||||||
return this.config.getOrElse("controller.invert_right_y_axis", false);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets whether the right Y axis is inverted or not.
|
|
||||||
*
|
|
||||||
* @param invert True if the right Y axis is inverted, else false.
|
|
||||||
*/
|
|
||||||
public void setInvertRightYAxis(boolean invert)
|
|
||||||
{
|
|
||||||
this.config.set("controller.invert_right_y_axis", invert);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns whether unfocused controller input is allowed or not.
|
|
||||||
*
|
|
||||||
* @return True if unfocused controller input is allowed, else false.
|
|
||||||
*/
|
|
||||||
public boolean hasUnfocusedInput()
|
|
||||||
{
|
|
||||||
return this.unfocusedInput;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets whether unfocused controller input is allowed or not.
|
|
||||||
*
|
|
||||||
* @param unfocusedInput True if unfocused controller input is allowed, else false.
|
|
||||||
*/
|
|
||||||
public void setUnfocusedInput(boolean unfocusedInput)
|
|
||||||
{
|
|
||||||
this.unfocusedInput = unfocusedInput;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the right X axis sign.
|
|
||||||
*
|
|
||||||
* @return The right X axis sign.
|
|
||||||
*/
|
|
||||||
public double getRightXAxisSign()
|
|
||||||
{
|
|
||||||
return this.doesInvertRightXAxis() ? -1.0 : 1.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the right Y axis sign.
|
|
||||||
*
|
|
||||||
* @return The right Y axis sign.
|
|
||||||
*/
|
|
||||||
public double getRightYAxisSign()
|
|
||||||
{
|
|
||||||
return this.doesInvertRightYAxis() ? -1.0 : 1.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Loads the button binding from configuration.
|
|
||||||
*
|
|
||||||
* @param button The button binding.
|
|
||||||
*/
|
|
||||||
public void loadButtonBinding(@NotNull ButtonBinding button)
|
|
||||||
{
|
|
||||||
button.setButton(button.getDefaultButton());
|
|
||||||
String code = this.config.getOrElse("controller.controls." + button.getName(), button.getButtonCode());
|
|
||||||
|
|
||||||
Matcher matcher = BUTTON_BINDING_PATTERN.matcher(code);
|
|
||||||
|
|
||||||
try {
|
|
||||||
int[] buttons = new int[1];
|
|
||||||
int count = 0;
|
|
||||||
while (matcher.find()) {
|
|
||||||
count++;
|
|
||||||
if (count > buttons.length)
|
|
||||||
buttons = Arrays.copyOf(buttons, count);
|
|
||||||
String current;
|
|
||||||
if (!this.checkValidity(button, code, current = matcher.group(1)))
|
|
||||||
return;
|
|
||||||
buttons[count - 1] = Integer.parseInt(current);
|
|
||||||
}
|
|
||||||
if (count == 0) {
|
|
||||||
this.mod.warn("Malformed config value \"" + code + "\" for binding \"" + button.getName() + "\".");
|
|
||||||
this.setButtonBinding(button, new int[]{-1});
|
|
||||||
}
|
|
||||||
|
|
||||||
button.setButton(buttons);
|
|
||||||
} catch (Exception e) {
|
|
||||||
this.mod.warn("Malformed config value \"" + code + "\" for binding \"" + button.getName() + "\".");
|
|
||||||
this.config.set("controller.controls." + button.getName(), button.getButtonCode());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean checkValidity(@NotNull ButtonBinding binding, @NotNull String input, String group)
|
|
||||||
{
|
|
||||||
if (group == null) {
|
|
||||||
this.mod.warn("Malformed config value \"" + input + "\" for binding \"" + binding.getName() + "\".");
|
|
||||||
this.config.set("controller.controls." + binding.getName(), binding.getButtonCode());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the button binding in configuration.
|
|
||||||
*
|
|
||||||
* @param binding The button binding.
|
|
||||||
* @param button The button.
|
|
||||||
*/
|
|
||||||
public void setButtonBinding(@NotNull ButtonBinding binding, int[] button)
|
|
||||||
{
|
|
||||||
binding.setButton(button);
|
|
||||||
this.config.set("controller.controls." + binding.getName(), binding.getButtonCode());
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isBackButton(int btn, boolean isBtn, int state)
|
|
||||||
{
|
|
||||||
if (!isBtn && state == 0)
|
|
||||||
return false;
|
|
||||||
return ButtonBinding.axisAsButton(GLFW_GAMEPAD_AXIS_LEFT_Y, false) == ButtonBinding.axisAsButton(btn, state == 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isForwardButton(int btn, boolean isBtn, int state)
|
|
||||||
{
|
|
||||||
if (!isBtn && state == 0)
|
|
||||||
return false;
|
|
||||||
return ButtonBinding.axisAsButton(GLFW_GAMEPAD_AXIS_LEFT_Y, true) == ButtonBinding.axisAsButton(btn, state == 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isLeftButton(int btn, boolean isBtn, int state)
|
|
||||||
{
|
|
||||||
if (!isBtn && state == 0)
|
|
||||||
return false;
|
|
||||||
return ButtonBinding.axisAsButton(GLFW_GAMEPAD_AXIS_LEFT_X, false) == ButtonBinding.axisAsButton(btn, state == 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isRightButton(int btn, boolean isBtn, int state)
|
|
||||||
{
|
|
||||||
if (!isBtn && state == 0)
|
|
||||||
return false;
|
|
||||||
return ButtonBinding.axisAsButton(GLFW_GAMEPAD_AXIS_LEFT_X, true) == ButtonBinding.axisAsButton(btn, state == 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns whether the specified axis is an axis used for movements.
|
|
||||||
*
|
|
||||||
* @param axis The axis index.
|
|
||||||
* @return True if the axis is used for movements, else false.
|
|
||||||
*/
|
|
||||||
public boolean isMovementAxis(int axis)
|
|
||||||
{
|
|
||||||
return axis == GLFW_GAMEPAD_AXIS_LEFT_Y || axis == GLFW_GAMEPAD_AXIS_LEFT_X;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2020 LambdAurora <aurora42lambda@gmail.com>
|
|
||||||
*
|
|
||||||
* This file is part of LambdaControls.
|
|
||||||
*
|
|
||||||
* Licensed under the MIT license. For more information,
|
|
||||||
* see the LICENSE file.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package me.lambdaurora.lambdacontrols.client;
|
|
||||||
|
|
||||||
import io.github.prospector.modmenu.api.ModMenuApi;
|
|
||||||
import me.lambdaurora.lambdacontrols.LambdaControlsConstants;
|
|
||||||
import me.lambdaurora.lambdacontrols.client.gui.LambdaControlsSettingsScreen;
|
|
||||||
import net.minecraft.client.MinecraftClient;
|
|
||||||
import net.minecraft.client.gui.screen.Screen;
|
|
||||||
|
|
||||||
import java.util.function.Function;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents the API implementation of ModMenu for LambdaControls.
|
|
||||||
*
|
|
||||||
* @author LambdAurora
|
|
||||||
* @version 1.1.0
|
|
||||||
* @since 1.1.0
|
|
||||||
*/
|
|
||||||
public class LambdaControlsModMenu implements ModMenuApi
|
|
||||||
{
|
|
||||||
@Override
|
|
||||||
public String getModId()
|
|
||||||
{
|
|
||||||
return LambdaControlsConstants.NAMESPACE;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Function<Screen, ? extends Screen> getConfigScreenFactory()
|
|
||||||
{
|
|
||||||
return screen -> new LambdaControlsSettingsScreen(screen, MinecraftClient.getInstance().options, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,642 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2020 LambdAurora <aurora42lambda@gmail.com>
|
|
||||||
*
|
|
||||||
* This file is part of LambdaControls.
|
|
||||||
*
|
|
||||||
* Licensed under the MIT license. For more information,
|
|
||||||
* see the LICENSE file.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package me.lambdaurora.lambdacontrols.client;
|
|
||||||
|
|
||||||
import me.lambdaurora.lambdacontrols.client.controller.ButtonBinding;
|
|
||||||
import me.lambdaurora.lambdacontrols.client.controller.Controller;
|
|
||||||
import me.lambdaurora.lambdacontrols.client.controller.InputManager;
|
|
||||||
import me.lambdaurora.lambdacontrols.client.gui.ControllerControlsScreen;
|
|
||||||
import me.lambdaurora.lambdacontrols.client.gui.TouchscreenOverlay;
|
|
||||||
import me.lambdaurora.lambdacontrols.client.mixin.CreativeInventoryScreenAccessor;
|
|
||||||
import me.lambdaurora.lambdacontrols.client.mixin.EntryListWidgetAccessor;
|
|
||||||
import me.lambdaurora.lambdacontrols.client.util.ContainerScreenAccessor;
|
|
||||||
import me.lambdaurora.spruceui.SpruceLabelWidget;
|
|
||||||
import net.minecraft.client.MinecraftClient;
|
|
||||||
import net.minecraft.client.gui.Element;
|
|
||||||
import net.minecraft.client.gui.ParentElement;
|
|
||||||
import net.minecraft.client.gui.screen.Screen;
|
|
||||||
import net.minecraft.client.gui.screen.advancement.AdvancementsScreen;
|
|
||||||
import net.minecraft.client.gui.screen.ingame.ContainerScreen;
|
|
||||||
import net.minecraft.client.gui.screen.ingame.CreativeInventoryScreen;
|
|
||||||
import net.minecraft.client.gui.screen.multiplayer.MultiplayerScreen;
|
|
||||||
import net.minecraft.client.gui.screen.multiplayer.MultiplayerServerListWidget;
|
|
||||||
import net.minecraft.client.gui.screen.world.WorldListWidget;
|
|
||||||
import net.minecraft.client.gui.widget.AbstractPressableButtonWidget;
|
|
||||||
import net.minecraft.client.gui.widget.AlwaysSelectedEntryListWidget;
|
|
||||||
import net.minecraft.client.gui.widget.SliderWidget;
|
|
||||||
import net.minecraft.container.Slot;
|
|
||||||
import net.minecraft.container.SlotActionType;
|
|
||||||
import net.minecraft.util.math.MathHelper;
|
|
||||||
import org.aperlambda.lambdacommon.utils.Pair;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
import org.lwjgl.glfw.GLFW;
|
|
||||||
import org.lwjgl.glfw.GLFWGamepadState;
|
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.nio.FloatBuffer;
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
import static me.lambdaurora.lambdacontrols.client.controller.ButtonBinding.axisAsButton;
|
|
||||||
import static me.lambdaurora.lambdacontrols.client.controller.InputManager.INPUT_MANAGER;
|
|
||||||
import static org.lwjgl.glfw.GLFW.*;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents the LambdaControls' input handler.
|
|
||||||
*
|
|
||||||
* @author LambdAurora
|
|
||||||
* @version 1.1.0
|
|
||||||
* @since 1.0.0
|
|
||||||
*/
|
|
||||||
public class LambdaInput
|
|
||||||
{
|
|
||||||
private static final Map<Integer, Integer> BUTTON_COOLDOWNS = new HashMap<>();
|
|
||||||
private final LambdaControlsConfig config;
|
|
||||||
// Cooldowns
|
|
||||||
private int actionGuiCooldown = 0;
|
|
||||||
private int ignoreNextA = 0;
|
|
||||||
// Sneak state.
|
|
||||||
private double prevTargetYaw = 0.0;
|
|
||||||
private double prevTargetPitch = 0.0;
|
|
||||||
private double targetYaw = 0.0;
|
|
||||||
private double targetPitch = 0.0;
|
|
||||||
private float prevXAxis = 0.F;
|
|
||||||
private float prevYAxis = 0.F;
|
|
||||||
private int targetMouseX = 0;
|
|
||||||
private int targetMouseY = 0;
|
|
||||||
private float mouseSpeedX = 0.F;
|
|
||||||
private float mouseSpeedY = 0.F;
|
|
||||||
|
|
||||||
public LambdaInput(@NotNull LambdaControlsClient mod)
|
|
||||||
{
|
|
||||||
this.config = mod.config;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is called every Minecraft tick.
|
|
||||||
*
|
|
||||||
* @param client The client instance.
|
|
||||||
*/
|
|
||||||
public void onTick(@NotNull MinecraftClient client)
|
|
||||||
{
|
|
||||||
this.prevTargetYaw = this.targetYaw;
|
|
||||||
this.prevTargetPitch = this.targetPitch;
|
|
||||||
|
|
||||||
// Handles the key bindings.
|
|
||||||
if (LambdaControlsClient.BINDING_LOOK_UP.isPressed()) {
|
|
||||||
this.handleLook(client, GLFW_GAMEPAD_AXIS_RIGHT_Y, 0.8F, 2);
|
|
||||||
} else if (LambdaControlsClient.BINDING_LOOK_DOWN.isPressed()) {
|
|
||||||
this.handleLook(client, GLFW_GAMEPAD_AXIS_RIGHT_Y, 0.8F, 1);
|
|
||||||
}
|
|
||||||
if (LambdaControlsClient.BINDING_LOOK_LEFT.isPressed()) {
|
|
||||||
this.handleLook(client, GLFW_GAMEPAD_AXIS_RIGHT_X, 0.8F, 2);
|
|
||||||
} else if (LambdaControlsClient.BINDING_LOOK_RIGHT.isPressed()) {
|
|
||||||
this.handleLook(client, GLFW_GAMEPAD_AXIS_RIGHT_X, 0.8F, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
INPUT_MANAGER.tick(client);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is called every Minecraft tick for controller input update.
|
|
||||||
*
|
|
||||||
* @param client The client instance.
|
|
||||||
*/
|
|
||||||
public void onControllerTick(@NotNull MinecraftClient client)
|
|
||||||
{
|
|
||||||
BUTTON_COOLDOWNS.entrySet().stream().filter(entry -> entry.getValue() > 0).forEach(entry -> BUTTON_COOLDOWNS.put(entry.getKey(), entry.getValue() - 1));
|
|
||||||
// Decreases the cooldown for GUI actions.
|
|
||||||
if (this.actionGuiCooldown > 0)
|
|
||||||
--this.actionGuiCooldown;
|
|
||||||
|
|
||||||
InputManager.updateStates();
|
|
||||||
|
|
||||||
Controller controller = this.config.getController();
|
|
||||||
if (controller.isConnected()) {
|
|
||||||
GLFWGamepadState state = controller.getState();
|
|
||||||
this.fetchButtonInput(client, state, false);
|
|
||||||
this.fetchAxeInput(client, state, false);
|
|
||||||
}
|
|
||||||
this.config.getSecondController().filter(Controller::isConnected)
|
|
||||||
.ifPresent(joycon -> {
|
|
||||||
GLFWGamepadState state = joycon.getState();
|
|
||||||
this.fetchButtonInput(client, state, true);
|
|
||||||
this.fetchAxeInput(client, state, true);
|
|
||||||
});
|
|
||||||
|
|
||||||
boolean allowInput = true;
|
|
||||||
|
|
||||||
if (client.currentScreen instanceof ControllerControlsScreen && ((ControllerControlsScreen) client.currentScreen).focusedBinding != null)
|
|
||||||
allowInput = false;
|
|
||||||
|
|
||||||
if (allowInput)
|
|
||||||
InputManager.updateBindings(client);
|
|
||||||
|
|
||||||
if (this.ignoreNextA > 0)
|
|
||||||
this.ignoreNextA--;
|
|
||||||
|
|
||||||
if (client.currentScreen instanceof ControllerControlsScreen && InputManager.STATES.entrySet().parallelStream().map(Map.Entry::getValue).allMatch(ButtonState::isUnpressed)) {
|
|
||||||
ControllerControlsScreen screen = (ControllerControlsScreen) client.currentScreen;
|
|
||||||
if (screen.focusedBinding != null && !screen.waiting) {
|
|
||||||
int[] buttons = new int[screen.currentButtons.size()];
|
|
||||||
for (int i = 0; i < screen.currentButtons.size(); i++)
|
|
||||||
buttons[i] = screen.currentButtons.get(i);
|
|
||||||
screen.focusedBinding.setButton(buttons);
|
|
||||||
screen.focusedBinding = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is called before the screen is rendered.
|
|
||||||
*
|
|
||||||
* @param client The client instance.
|
|
||||||
* @param screen The screen to render.
|
|
||||||
*/
|
|
||||||
public void onPreRenderScreen(@NotNull MinecraftClient client, @NotNull Screen screen)
|
|
||||||
{
|
|
||||||
if (!isScreenInteractive(screen)) {
|
|
||||||
INPUT_MANAGER.updateMousePosition(client);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is called when Minecraft renders.
|
|
||||||
*
|
|
||||||
* @param client The client instance.
|
|
||||||
*/
|
|
||||||
public void onRender(@NotNull MinecraftClient client)
|
|
||||||
{
|
|
||||||
if ((client.currentScreen == null || client.currentScreen instanceof TouchscreenOverlay) &&
|
|
||||||
(this.prevTargetYaw != this.targetYaw || this.prevTargetPitch != this.targetPitch)) {
|
|
||||||
float deltaYaw = (float) ((this.targetYaw - client.player.prevYaw) * client.getTickDelta());
|
|
||||||
float deltaPitch = (float) ((this.targetPitch - client.player.prevPitch) * client.getTickDelta());
|
|
||||||
float rotationYaw = client.player.prevYaw + deltaYaw;
|
|
||||||
float rotationPitch = client.player.prevPitch + deltaPitch;
|
|
||||||
client.player.yaw = rotationYaw;
|
|
||||||
client.player.pitch = MathHelper.clamp(rotationPitch, -90.F, 90.F);
|
|
||||||
if (client.player.isRiding()) {
|
|
||||||
client.player.getVehicle().copyPositionAndRotation(client.player);
|
|
||||||
}
|
|
||||||
client.getTutorialManager().onUpdateMouse(deltaPitch, deltaYaw);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is called when a Screen is opened.
|
|
||||||
*
|
|
||||||
* @param client The client instance.
|
|
||||||
* @param windowWidth The window width.
|
|
||||||
* @param windowHeight The window height.
|
|
||||||
*/
|
|
||||||
public void onScreenOpen(@NotNull MinecraftClient client, int windowWidth, int windowHeight)
|
|
||||||
{
|
|
||||||
if (client.currentScreen == null) {
|
|
||||||
this.mouseSpeedX = this.mouseSpeedY = 0.0F;
|
|
||||||
INPUT_MANAGER.resetMousePosition(windowWidth, windowHeight);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void fetchButtonInput(@NotNull MinecraftClient client, @NotNull GLFWGamepadState gamepadState, boolean leftJoycon)
|
|
||||||
{
|
|
||||||
ByteBuffer buffer = gamepadState.buttons();
|
|
||||||
for (int i = 0; i < buffer.limit(); i++) {
|
|
||||||
int btn = leftJoycon ? ButtonBinding.controller2Button(i) : i;
|
|
||||||
boolean btnState = buffer.get() == (byte) 1;
|
|
||||||
ButtonState state = ButtonState.NONE;
|
|
||||||
ButtonState previousState = InputManager.STATES.getOrDefault(btn, ButtonState.NONE);
|
|
||||||
|
|
||||||
if (btnState != previousState.isPressed()) {
|
|
||||||
state = btnState ? ButtonState.PRESS : ButtonState.RELEASE;
|
|
||||||
this.handleButton(client, btn, btnState ? 0 : 1, btnState);
|
|
||||||
if (btnState)
|
|
||||||
BUTTON_COOLDOWNS.put(btn, 5);
|
|
||||||
} else if (btnState) {
|
|
||||||
state = ButtonState.REPEAT;
|
|
||||||
if (BUTTON_COOLDOWNS.getOrDefault(btn, 0) == 0) {
|
|
||||||
BUTTON_COOLDOWNS.put(btn, 5);
|
|
||||||
this.handleButton(client, btn, 2, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
InputManager.STATES.put(btn, state);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void fetchAxeInput(@NotNull MinecraftClient client, @NotNull GLFWGamepadState gamepadState, boolean leftJoycon)
|
|
||||||
{
|
|
||||||
FloatBuffer buffer = gamepadState.axes();
|
|
||||||
for (int i = 0; i < buffer.limit(); i++) {
|
|
||||||
int axis = leftJoycon ? ButtonBinding.controller2Button(i) : i;
|
|
||||||
float value = buffer.get();
|
|
||||||
float absValue = Math.abs(value);
|
|
||||||
|
|
||||||
if (i == GLFW.GLFW_GAMEPAD_AXIS_LEFT_Y)
|
|
||||||
value *= -1.0F;
|
|
||||||
|
|
||||||
int state = value > this.config.getDeadZone() ? 1 : (value < -this.config.getDeadZone() ? 2 : 0);
|
|
||||||
this.handleAxe(client, axis, value, absValue, state);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handleButton(@NotNull MinecraftClient client, int button, int action, boolean state)
|
|
||||||
{
|
|
||||||
if (client.currentScreen instanceof ControllerControlsScreen) {
|
|
||||||
ControllerControlsScreen screen = (ControllerControlsScreen) client.currentScreen;
|
|
||||||
if (screen.focusedBinding != null) {
|
|
||||||
if (action == 0 && !screen.currentButtons.contains(button)) {
|
|
||||||
screen.currentButtons.add(button);
|
|
||||||
|
|
||||||
int[] buttons = new int[screen.currentButtons.size()];
|
|
||||||
for (int i = 0; i < screen.currentButtons.size(); i++)
|
|
||||||
buttons[i] = screen.currentButtons.get(i);
|
|
||||||
screen.focusedBinding.setButton(buttons);
|
|
||||||
|
|
||||||
screen.waiting = false;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (action == 0 || action == 2) {
|
|
||||||
if (client.currentScreen != null && isScreenInteractive(client.currentScreen)
|
|
||||||
&& (button == GLFW.GLFW_GAMEPAD_BUTTON_DPAD_UP || button == GLFW.GLFW_GAMEPAD_BUTTON_DPAD_DOWN
|
|
||||||
|| button == GLFW.GLFW_GAMEPAD_BUTTON_DPAD_LEFT || button == GLFW.GLFW_GAMEPAD_BUTTON_DPAD_RIGHT)) {
|
|
||||||
if (this.actionGuiCooldown == 0) {
|
|
||||||
if (button == GLFW.GLFW_GAMEPAD_BUTTON_DPAD_UP) {
|
|
||||||
this.changeFocus(client.currentScreen, false);
|
|
||||||
} else if (button == GLFW.GLFW_GAMEPAD_BUTTON_DPAD_DOWN) {
|
|
||||||
this.changeFocus(client.currentScreen, true);
|
|
||||||
} else if (button == GLFW.GLFW_GAMEPAD_BUTTON_DPAD_LEFT) {
|
|
||||||
this.handleLeftRight(client.currentScreen, false);
|
|
||||||
} else {
|
|
||||||
this.handleLeftRight(client.currentScreen, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (action == 1) {
|
|
||||||
if (button == GLFW.GLFW_GAMEPAD_BUTTON_A && client.currentScreen != null) {
|
|
||||||
if (this.actionGuiCooldown == 0) {
|
|
||||||
Element focused = client.currentScreen.getFocused();
|
|
||||||
if (focused != null && isScreenInteractive(client.currentScreen)) {
|
|
||||||
if (this.handleAButton(client.currentScreen, focused)) {
|
|
||||||
this.actionGuiCooldown = 5; // Prevent to press too quickly the focused element, so we have to skip 5 ticks.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (client.currentScreen instanceof ContainerScreen && client.interactionManager != null && client.player != null) {
|
|
||||||
double x = client.mouse.getX() * (double) client.getWindow().getScaledWidth() / (double) client.getWindow().getWidth();
|
|
||||||
double y = client.mouse.getY() * (double) client.getWindow().getScaledHeight() / (double) client.getWindow().getHeight();
|
|
||||||
Slot slot = ((ContainerScreenAccessor) client.currentScreen).lambdacontrols_getSlotAt(x, y);
|
|
||||||
SlotActionType slotAction = SlotActionType.PICKUP;
|
|
||||||
if (button == GLFW.GLFW_GAMEPAD_BUTTON_A && slot != null) {
|
|
||||||
if (client.currentScreen instanceof CreativeInventoryScreen) {
|
|
||||||
if (((CreativeInventoryScreenAccessor) client.currentScreen).lambdacontrols_isCreativeInventorySlot(slot))
|
|
||||||
slotAction = SlotActionType.CLONE;
|
|
||||||
}
|
|
||||||
client.interactionManager.clickSlot(((ContainerScreen) client.currentScreen).getContainer().syncId, slot.id, GLFW.GLFW_MOUSE_BUTTON_1, slotAction, client.player);
|
|
||||||
this.actionGuiCooldown = 5;
|
|
||||||
return;
|
|
||||||
} else if (button == GLFW.GLFW_GAMEPAD_BUTTON_B) {
|
|
||||||
client.player.closeContainer();
|
|
||||||
return;
|
|
||||||
} else if (button == GLFW.GLFW_GAMEPAD_BUTTON_X && slot != null) {
|
|
||||||
client.interactionManager.clickSlot(((ContainerScreen) client.currentScreen).getContainer().syncId, slot.id, GLFW.GLFW_MOUSE_BUTTON_2, SlotActionType.PICKUP, client.player);
|
|
||||||
return;
|
|
||||||
} else if (button == GLFW.GLFW_GAMEPAD_BUTTON_Y && slot != null) {
|
|
||||||
client.interactionManager.clickSlot(((ContainerScreen) client.currentScreen).getContainer().syncId, slot.id, GLFW.GLFW_MOUSE_BUTTON_1, SlotActionType.QUICK_MOVE, client.player);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else if (button == GLFW.GLFW_GAMEPAD_BUTTON_B) {
|
|
||||||
if (client.currentScreen != null) {
|
|
||||||
client.currentScreen.onClose();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (button == GLFW.GLFW_GAMEPAD_BUTTON_A && client.currentScreen != null && !isScreenInteractive(client.currentScreen) && this.actionGuiCooldown == 0 && this.ignoreNextA == 0) {
|
|
||||||
double mouseX = client.mouse.getX() * (double) client.getWindow().getScaledWidth() / (double) client.getWindow().getWidth();
|
|
||||||
double mouseY = client.mouse.getY() * (double) client.getWindow().getScaledHeight() / (double) client.getWindow().getHeight();
|
|
||||||
if (action == 0) {
|
|
||||||
client.currentScreen.mouseClicked(mouseX, mouseY, GLFW.GLFW_MOUSE_BUTTON_1);
|
|
||||||
} else if (action == 1) {
|
|
||||||
client.currentScreen.mouseReleased(mouseX, mouseY, GLFW.GLFW_MOUSE_BUTTON_1);
|
|
||||||
}
|
|
||||||
this.actionGuiCooldown = 5;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handleAxe(@NotNull MinecraftClient client, int axis, float value, float absValue, int state)
|
|
||||||
{
|
|
||||||
int asButtonState = value > 0.5F ? 1 : (value < -0.5F ? 2 : 0);
|
|
||||||
|
|
||||||
if (axis == GLFW_GAMEPAD_AXIS_LEFT_TRIGGER || axis == GLFW_GAMEPAD_AXIS_RIGHT_TRIGGER || axis == ButtonBinding.controller2Button(GLFW.GLFW_GAMEPAD_AXIS_LEFT_TRIGGER) ||
|
|
||||||
axis == ButtonBinding.controller2Button(GLFW.GLFW_GAMEPAD_AXIS_RIGHT_TRIGGER))
|
|
||||||
if (asButtonState == 2)
|
|
||||||
asButtonState = 0;
|
|
||||||
|
|
||||||
{
|
|
||||||
boolean currentPlusState = asButtonState == 1;
|
|
||||||
boolean currentMinusState = asButtonState == 2;
|
|
||||||
ButtonState previousPlusState = InputManager.STATES.getOrDefault(axisAsButton(axis, true), ButtonState.NONE);
|
|
||||||
ButtonState previousMinusState = InputManager.STATES.getOrDefault(axisAsButton(axis, false), ButtonState.NONE);
|
|
||||||
|
|
||||||
if (currentPlusState != previousPlusState.isPressed()) {
|
|
||||||
InputManager.STATES.put(axisAsButton(axis, true), currentPlusState ? ButtonState.PRESS : ButtonState.RELEASE);
|
|
||||||
if (currentPlusState)
|
|
||||||
BUTTON_COOLDOWNS.put(axisAsButton(axis, true), 5);
|
|
||||||
} else if (currentPlusState) {
|
|
||||||
InputManager.STATES.put(axisAsButton(axis, true), ButtonState.REPEAT);
|
|
||||||
if (BUTTON_COOLDOWNS.getOrDefault(axisAsButton(axis, true), 0) == 0) {
|
|
||||||
BUTTON_COOLDOWNS.put(axisAsButton(axis, true), 5);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (currentMinusState != previousMinusState.isPressed()) {
|
|
||||||
InputManager.STATES.put(axisAsButton(axis, false), currentMinusState ? ButtonState.PRESS : ButtonState.RELEASE);
|
|
||||||
if (currentMinusState)
|
|
||||||
BUTTON_COOLDOWNS.put(axisAsButton(axis, false), 5);
|
|
||||||
} else if (currentMinusState) {
|
|
||||||
InputManager.STATES.put(axisAsButton(axis, false), ButtonState.REPEAT);
|
|
||||||
if (BUTTON_COOLDOWNS.getOrDefault(axisAsButton(axis, false), 0) == 0) {
|
|
||||||
BUTTON_COOLDOWNS.put(axisAsButton(axis, false), 5);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (client.currentScreen instanceof ControllerControlsScreen) {
|
|
||||||
ControllerControlsScreen screen = (ControllerControlsScreen) client.currentScreen;
|
|
||||||
if (screen.focusedBinding != null) {
|
|
||||||
if (asButtonState != 0 && !screen.currentButtons.contains(axisAsButton(axis, asButtonState == 1))) {
|
|
||||||
|
|
||||||
screen.currentButtons.add(axisAsButton(axis, asButtonState == 1));
|
|
||||||
|
|
||||||
int[] buttons = new int[screen.currentButtons.size()];
|
|
||||||
for (int i = 0; i < screen.currentButtons.size(); i++)
|
|
||||||
buttons[i] = screen.currentButtons.get(i);
|
|
||||||
screen.focusedBinding.setButton(buttons);
|
|
||||||
|
|
||||||
screen.waiting = false;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
double deadZone = this.config.getDeadZone();
|
|
||||||
if (client.currentScreen == null) {
|
|
||||||
// Handles the look direction.
|
|
||||||
this.handleLook(client, axis, (float) (absValue / (1.0 - this.config.getDeadZone())), state);
|
|
||||||
} else {
|
|
||||||
boolean allowMouseControl = true;
|
|
||||||
|
|
||||||
if (this.actionGuiCooldown == 0 && this.config.isMovementAxis(axis) && isScreenInteractive(client.currentScreen)) {
|
|
||||||
if (this.config.isForwardButton(axis, false, asButtonState)) {
|
|
||||||
allowMouseControl = this.changeFocus(client.currentScreen, false);
|
|
||||||
} else if (this.config.isBackButton(axis, false, asButtonState)) {
|
|
||||||
allowMouseControl = this.changeFocus(client.currentScreen, true);
|
|
||||||
} else if (this.config.isLeftButton(axis, false, asButtonState)) {
|
|
||||||
allowMouseControl = this.handleLeftRight(client.currentScreen, false);
|
|
||||||
} else if (this.config.isRightButton(axis, false, asButtonState)) {
|
|
||||||
allowMouseControl = this.handleLeftRight(client.currentScreen, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
float movementX = 0.0F;
|
|
||||||
float movementY = 0.0F;
|
|
||||||
|
|
||||||
if (this.config.isBackButton(axis, false, (value > 0 ? 1 : 2))) {
|
|
||||||
movementY = absValue;
|
|
||||||
} else if (this.config.isForwardButton(axis, false, (value > 0 ? 1 : 2))) {
|
|
||||||
movementY = -absValue;
|
|
||||||
} else if (this.config.isLeftButton(axis, false, (value > 0 ? 1 : 2))) {
|
|
||||||
movementX = -absValue;
|
|
||||||
} else if (this.config.isRightButton(axis, false, (value > 0 ? 1 : 2))) {
|
|
||||||
movementX = absValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (client.currentScreen != null && allowMouseControl) {
|
|
||||||
boolean moving = Math.abs(movementY) >= deadZone || Math.abs(movementX) >= deadZone;
|
|
||||||
if (moving) {
|
|
||||||
/*
|
|
||||||
Updates the target mouse position when the initial movement stick movement is detected.
|
|
||||||
It prevents the cursor to jump to the old target mouse position if the user moves the cursor with the mouse.
|
|
||||||
*/
|
|
||||||
if (Math.abs(prevXAxis) < deadZone && Math.abs(prevYAxis) < deadZone) {
|
|
||||||
INPUT_MANAGER.resetMouseTarget(client);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Math.abs(movementX) >= deadZone)
|
|
||||||
this.mouseSpeedX = movementX;
|
|
||||||
else
|
|
||||||
this.mouseSpeedX = 0.F;
|
|
||||||
|
|
||||||
if (Math.abs(movementY) >= deadZone)
|
|
||||||
this.mouseSpeedY = movementY;
|
|
||||||
else
|
|
||||||
this.mouseSpeedY = 0.F;
|
|
||||||
} else {
|
|
||||||
this.mouseSpeedX = 0.F;
|
|
||||||
this.mouseSpeedY = 0.F;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Math.abs(this.mouseSpeedX) >= .05F || Math.abs(this.mouseSpeedY) >= .05F) {
|
|
||||||
InputManager.queueMoveMousePosition(this.mouseSpeedX * this.config.getMouseSpeed(), this.mouseSpeedY * this.config.getMouseSpeed());
|
|
||||||
}
|
|
||||||
|
|
||||||
this.moveMouseToClosestSlot(client, client.currentScreen);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.prevXAxis = movementX;
|
|
||||||
this.prevYAxis = movementY;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean handleAButton(@NotNull Screen screen, @NotNull Element focused)
|
|
||||||
{
|
|
||||||
if (focused instanceof AbstractPressableButtonWidget) {
|
|
||||||
AbstractPressableButtonWidget widget = (AbstractPressableButtonWidget) focused;
|
|
||||||
widget.playDownSound(MinecraftClient.getInstance().getSoundManager());
|
|
||||||
widget.onPress();
|
|
||||||
return true;
|
|
||||||
} else if (focused instanceof SpruceLabelWidget) {
|
|
||||||
((SpruceLabelWidget) focused).onPress();
|
|
||||||
return true;
|
|
||||||
} else if (focused instanceof WorldListWidget) {
|
|
||||||
WorldListWidget list = (WorldListWidget) focused;
|
|
||||||
list.method_20159().ifPresent(WorldListWidget.Entry::play);
|
|
||||||
return true;
|
|
||||||
} else if (focused instanceof MultiplayerServerListWidget) {
|
|
||||||
MultiplayerServerListWidget list = (MultiplayerServerListWidget) focused;
|
|
||||||
MultiplayerServerListWidget.Entry entry = list.getSelected();
|
|
||||||
if (entry instanceof MultiplayerServerListWidget.LanServerEntry || entry instanceof MultiplayerServerListWidget.ServerEntry) {
|
|
||||||
((MultiplayerScreen) screen).select(entry);
|
|
||||||
((MultiplayerScreen) screen).connect();
|
|
||||||
}
|
|
||||||
} else if (focused instanceof ParentElement) {
|
|
||||||
Element childFocused = ((ParentElement) focused).getFocused();
|
|
||||||
if (childFocused != null)
|
|
||||||
return this.handleAButton(screen, childFocused);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles the left and right buttons.
|
|
||||||
*
|
|
||||||
* @param screen The current screen.
|
|
||||||
* @param right True if the right button is pressed, else false.
|
|
||||||
*/
|
|
||||||
private boolean handleLeftRight(@NotNull Screen screen, boolean right)
|
|
||||||
{
|
|
||||||
Element focused = screen.getFocused();
|
|
||||||
if (focused != null)
|
|
||||||
if (this.handleRightLeftElement(focused, right))
|
|
||||||
return this.changeFocus(screen, right);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean handleRightLeftElement(@NotNull Element element, boolean right)
|
|
||||||
{
|
|
||||||
if (element instanceof SliderWidget) {
|
|
||||||
SliderWidget slider = (SliderWidget) element;
|
|
||||||
slider.keyPressed(right ? 262 : 263, 0, 0);
|
|
||||||
this.actionGuiCooldown = 2; // Prevent to press too quickly the focused element, so we have to skip 5 ticks.
|
|
||||||
return false;
|
|
||||||
} else if (element instanceof AlwaysSelectedEntryListWidget) {
|
|
||||||
((EntryListWidgetAccessor) element).lambdacontrols_moveSelection(right ? 1 : -1);
|
|
||||||
return false;
|
|
||||||
} else if (element instanceof ParentElement) {
|
|
||||||
ParentElement entryList = (ParentElement) element;
|
|
||||||
Element focused = entryList.getFocused();
|
|
||||||
if (focused == null)
|
|
||||||
return true;
|
|
||||||
return this.handleRightLeftElement(focused, right);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles the look direction input.
|
|
||||||
*
|
|
||||||
* @param client The client isntance.
|
|
||||||
* @param axis The axis to change.
|
|
||||||
* @param value The value of the look.
|
|
||||||
* @param state The state.
|
|
||||||
*/
|
|
||||||
public void handleLook(@NotNull MinecraftClient client, int axis, float value, int state)
|
|
||||||
{
|
|
||||||
// Handles the look direction.
|
|
||||||
if (client.player != null) {
|
|
||||||
double powValue = Math.pow(value, 4.0);
|
|
||||||
if (axis == GLFW_GAMEPAD_AXIS_RIGHT_Y) {
|
|
||||||
if (state == 2) {
|
|
||||||
this.targetPitch = client.player.pitch - this.config.getRightYAxisSign() * (this.config.getRotationSpeed() * powValue) * 0.33D;
|
|
||||||
this.targetPitch = MathHelper.clamp(this.targetPitch, -90.0D, 90.0D);
|
|
||||||
} else if (state == 1) {
|
|
||||||
this.targetPitch = client.player.pitch + this.config.getRightYAxisSign() * (this.config.getRotationSpeed() * powValue) * 0.33D;
|
|
||||||
this.targetPitch = MathHelper.clamp(this.targetPitch, -90.0D, 90.0D);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (axis == GLFW_GAMEPAD_AXIS_RIGHT_X) {
|
|
||||||
if (state == 2) {
|
|
||||||
this.targetYaw = client.player.yaw - this.config.getRightXAxisSign() * (this.config.getRotationSpeed() * powValue) * 0.33D;
|
|
||||||
} else if (state == 1) {
|
|
||||||
this.targetYaw = client.player.yaw + this.config.getRightXAxisSign() * (this.config.getRotationSpeed() * powValue) * 0.33D;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean changeFocus(@NotNull Screen screen, boolean down)
|
|
||||||
{
|
|
||||||
if (!screen.changeFocus(down)) {
|
|
||||||
if (screen.changeFocus(down)) {
|
|
||||||
this.actionGuiCooldown = 5;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
this.actionGuiCooldown = 5;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean isScreenInteractive(@NotNull Screen screen)
|
|
||||||
{
|
|
||||||
return !(screen instanceof AdvancementsScreen || screen instanceof ContainerScreen);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Inspired from https://github.com/MrCrayfish/Controllable/blob/1.14.X/src/main/java/com/mrcrayfish/controllable/client/ControllerInput.java#L686.
|
|
||||||
private void moveMouseToClosestSlot(@NotNull MinecraftClient client, @Nullable Screen screen)
|
|
||||||
{
|
|
||||||
// Makes the mouse attracted to slots. This helps with selecting items when using a controller.
|
|
||||||
if (screen instanceof ContainerScreen) {
|
|
||||||
ContainerScreen inventoryScreen = (ContainerScreen) screen;
|
|
||||||
ContainerScreenAccessor accessor = (ContainerScreenAccessor) inventoryScreen;
|
|
||||||
int guiLeft = accessor.lambdacontrols_getX();
|
|
||||||
int guiTop = accessor.lambdacontrols_getY();
|
|
||||||
int mouseX = (int) (targetMouseX * (double) client.getWindow().getScaledWidth() / (double) client.getWindow().getWidth());
|
|
||||||
int mouseY = (int) (targetMouseY * (double) client.getWindow().getScaledHeight() / (double) client.getWindow().getHeight());
|
|
||||||
|
|
||||||
// Finds the closest slot in the GUI within 14 pixels.
|
|
||||||
Optional<Pair<Slot, Double>> closestSlot = inventoryScreen.getContainer().slots.parallelStream()
|
|
||||||
.map(slot -> {
|
|
||||||
int x = guiLeft + slot.xPosition + 8;
|
|
||||||
int y = guiTop + slot.yPosition + 8;
|
|
||||||
|
|
||||||
// Distance between the slot and the cursor.
|
|
||||||
double distance = Math.sqrt(Math.pow(x - mouseX, 2) + Math.pow(y - mouseY, 2));
|
|
||||||
return Pair.of(slot, distance);
|
|
||||||
}).filter(entry -> entry.value <= 14.0)
|
|
||||||
.min(Comparator.comparingDouble(p -> p.value));
|
|
||||||
|
|
||||||
if (closestSlot.isPresent()) {
|
|
||||||
Slot slot = closestSlot.get().key;
|
|
||||||
if (slot.hasStack() || !client.player.inventory.getMainHandStack().isEmpty()) {
|
|
||||||
int slotCenterXScaled = guiLeft + slot.xPosition + 8;
|
|
||||||
int slotCenterYScaled = guiTop + slot.yPosition + 8;
|
|
||||||
int slotCenterX = (int) (slotCenterXScaled / ((double) client.getWindow().getScaledWidth() / (double) client.getWindow().getWidth()));
|
|
||||||
int slotCenterY = (int) (slotCenterYScaled / ((double) client.getWindow().getScaledHeight() / (double) client.getWindow().getHeight()));
|
|
||||||
double deltaX = slotCenterX - targetMouseX;
|
|
||||||
double deltaY = slotCenterY - targetMouseY;
|
|
||||||
|
|
||||||
if (mouseX != slotCenterXScaled || mouseY != slotCenterYScaled) {
|
|
||||||
this.targetMouseX += deltaX * 0.75;
|
|
||||||
this.targetMouseY += deltaY * 0.75;
|
|
||||||
} else {
|
|
||||||
this.mouseSpeedX *= 0.3F;
|
|
||||||
this.mouseSpeedY *= 0.3F;
|
|
||||||
}
|
|
||||||
this.mouseSpeedX *= .75F;
|
|
||||||
this.mouseSpeedY *= .75F;
|
|
||||||
} else {
|
|
||||||
this.mouseSpeedX *= .1F;
|
|
||||||
this.mouseSpeedY *= .1F;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.mouseSpeedX *= .3F;
|
|
||||||
this.mouseSpeedY *= .3F;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.mouseSpeedX = 0.F;
|
|
||||||
this.mouseSpeedY = 0.F;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2020 LambdAurora <aurora42lambda@gmail.com>
|
|
||||||
*
|
|
||||||
* This file is part of LambdaControls.
|
|
||||||
*
|
|
||||||
* Licensed under the MIT license. For more information,
|
|
||||||
* see the LICENSE file.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package me.lambdaurora.lambdacontrols.client.compat;
|
|
||||||
|
|
||||||
import me.lambdaurora.lambdacontrols.client.LambdaControlsClient;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents a compatibility handler for a mod.
|
|
||||||
*
|
|
||||||
* @author LambdAurora
|
|
||||||
* @version 1.1.0
|
|
||||||
* @since 1.1.0
|
|
||||||
*/
|
|
||||||
public interface CompatHandler
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Handles compatibility of a mod.
|
|
||||||
*
|
|
||||||
* @param mod This mod instance.
|
|
||||||
*/
|
|
||||||
void handle(@NotNull LambdaControlsClient mod);
|
|
||||||
}
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2020 LambdAurora <aurora42lambda@gmail.com>
|
|
||||||
*
|
|
||||||
* This file is part of LambdaControls.
|
|
||||||
*
|
|
||||||
* Licensed under the MIT license. For more information,
|
|
||||||
* see the LICENSE file.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package me.lambdaurora.lambdacontrols.client.compat;
|
|
||||||
|
|
||||||
import me.lambdaurora.lambdacontrols.client.LambdaControlsClient;
|
|
||||||
import me.lambdaurora.lambdacontrols.client.controller.InputManager;
|
|
||||||
import net.fabricmc.loader.api.FabricLoader;
|
|
||||||
import org.aperlambda.lambdacommon.utils.LambdaReflection;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents a compatibility handler.
|
|
||||||
*
|
|
||||||
* @author LambdAurora
|
|
||||||
* @version 1.1.0
|
|
||||||
* @since 1.1.0
|
|
||||||
*/
|
|
||||||
public class LambdaControlsCompat
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Initializes compatibility with other mods if needed.
|
|
||||||
*
|
|
||||||
* @param mod The mod instance.
|
|
||||||
*/
|
|
||||||
public static void init(@NotNull LambdaControlsClient mod)
|
|
||||||
{
|
|
||||||
if (FabricLoader.getInstance().isModLoaded("okzoomer") && LambdaReflection.doesClassExist(OkZoomerCompat.OKZOOMER_CLASS_PATH)) {
|
|
||||||
mod.log("Adding okzoomer compatibility...");
|
|
||||||
new OkZoomerCompat().handle(mod);
|
|
||||||
}
|
|
||||||
InputManager.loadButtonBindings(mod.config);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2020 LambdAurora <aurora42lambda@gmail.com>
|
|
||||||
*
|
|
||||||
* This file is part of LambdaControls.
|
|
||||||
*
|
|
||||||
* Licensed under the MIT license. For more information,
|
|
||||||
* see the LICENSE file.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package me.lambdaurora.lambdacontrols.client.compat;
|
|
||||||
|
|
||||||
import io.github.joaoh1.okzoomer.OkZoomer;
|
|
||||||
import me.lambdaurora.lambdacontrols.client.LambdaControlsClient;
|
|
||||||
import me.lambdaurora.lambdacontrols.client.controller.ButtonBinding;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.lwjgl.glfw.GLFW;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents a compatibility handler for OkZoomer.
|
|
||||||
*
|
|
||||||
* @author LambdAurora
|
|
||||||
* @version 1.1.0
|
|
||||||
* @since 1.1.0
|
|
||||||
*/
|
|
||||||
public class OkZoomerCompat implements CompatHandler
|
|
||||||
{
|
|
||||||
public static final String OKZOOMER_CLASS_PATH = "io.github.joaoh1.okzoomer.OkZoomer";
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void handle(@NotNull LambdaControlsClient mod)
|
|
||||||
{
|
|
||||||
new ButtonBinding.Builder("zoom")
|
|
||||||
.buttons(GLFW.GLFW_GAMEPAD_BUTTON_DPAD_UP, GLFW.GLFW_GAMEPAD_BUTTON_X)
|
|
||||||
.onlyInGame()
|
|
||||||
.cooldown(true)
|
|
||||||
.category(ButtonBinding.MISC_CATEGORY)
|
|
||||||
.linkKeybind(OkZoomer.zoomKeyBinding)
|
|
||||||
.register();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,596 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2020 LambdAurora <aurora42lambda@gmail.com>
|
|
||||||
*
|
|
||||||
* This file is part of LambdaControls.
|
|
||||||
*
|
|
||||||
* Licensed under the MIT license. For more information,
|
|
||||||
* see the LICENSE file.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package me.lambdaurora.lambdacontrols.client.controller;
|
|
||||||
|
|
||||||
import me.lambdaurora.lambdacontrols.client.ButtonState;
|
|
||||||
import net.minecraft.client.MinecraftClient;
|
|
||||||
import net.minecraft.client.options.GameOptions;
|
|
||||||
import net.minecraft.client.options.KeyBinding;
|
|
||||||
import net.minecraft.client.resource.language.I18n;
|
|
||||||
import org.aperlambda.lambdacommon.Identifier;
|
|
||||||
import org.aperlambda.lambdacommon.utils.Nameable;
|
|
||||||
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 me.lambdaurora.lambdacontrols.client.controller.InputManager.registerDefaultCategory;
|
|
||||||
import static org.lwjgl.glfw.GLFW.*;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents a button binding.
|
|
||||||
*
|
|
||||||
* @author LambdAurora
|
|
||||||
* @version 1.1.0
|
|
||||||
* @since 1.0.0
|
|
||||||
*/
|
|
||||||
public class ButtonBinding implements Nameable
|
|
||||||
{
|
|
||||||
public static final ButtonCategory MOVEMENT_CATEGORY;
|
|
||||||
public static final ButtonCategory GAMEPLAY_CATEGORY;
|
|
||||||
public static final ButtonCategory INVENTORY_CATEGORY;
|
|
||||||
public static final ButtonCategory MULTIPLAYER_CATEGORY;
|
|
||||||
public static final ButtonCategory MISC_CATEGORY;
|
|
||||||
|
|
||||||
public static final ButtonBinding ATTACK = new Builder("attack").buttons(axisAsButton(GLFW_GAMEPAD_AXIS_RIGHT_TRIGGER, true)).onlyInGame().register();
|
|
||||||
public static final ButtonBinding BACK = new Builder("back").buttons(axisAsButton(GLFW_GAMEPAD_AXIS_LEFT_Y, false)).onlyInGame().register();
|
|
||||||
public static final ButtonBinding CHAT = new Builder("chat").buttons(GLFW_GAMEPAD_BUTTON_DPAD_RIGHT).onlyInGame().cooldown(true).register();
|
|
||||||
public static final ButtonBinding DROP_ITEM = new Builder("drop_item").buttons(GLFW_GAMEPAD_BUTTON_B).onlyInGame().cooldown(true).register();
|
|
||||||
public static final ButtonBinding FORWARD = new Builder("forward").buttons(axisAsButton(GLFW_GAMEPAD_AXIS_LEFT_Y, true)).onlyInGame().register();
|
|
||||||
public static final ButtonBinding HOTBAR_LEFT = new Builder("hotbar_left").buttons(GLFW_GAMEPAD_BUTTON_LEFT_BUMPER)
|
|
||||||
.action(InputHandlers.handleHotbar(false)).onlyInGame().cooldown(true).register();
|
|
||||||
public static final ButtonBinding HOTBAR_RIGHT = new Builder("hotbar_right").buttons(GLFW_GAMEPAD_BUTTON_RIGHT_BUMPER)
|
|
||||||
.action(InputHandlers.handleHotbar(true)).onlyInGame().cooldown(true).register();
|
|
||||||
public static final ButtonBinding INVENTORY = new Builder("inventory").buttons(GLFW_GAMEPAD_BUTTON_Y).onlyInGame().cooldown(true).register();
|
|
||||||
public static final ButtonBinding JUMP = new Builder("jump").buttons(GLFW_GAMEPAD_BUTTON_A).onlyInGame().register();
|
|
||||||
public static final ButtonBinding LEFT = new Builder("left").buttons(axisAsButton(GLFW_GAMEPAD_AXIS_LEFT_X, false)).onlyInGame().register();
|
|
||||||
public static final ButtonBinding PAUSE_GAME = new Builder("pause_game").buttons(GLFW_GAMEPAD_BUTTON_START).action(InputHandlers::handlePauseGame).cooldown(true).register();
|
|
||||||
public static final ButtonBinding PICK_BLOCK = new Builder("pick_block").buttons(GLFW_GAMEPAD_BUTTON_DPAD_LEFT).onlyInGame().cooldown(true).register();
|
|
||||||
public static final ButtonBinding PLAYER_LIST = new Builder("player_list").buttons(GLFW_GAMEPAD_BUTTON_BACK).onlyInGame().register();
|
|
||||||
public static final ButtonBinding RIGHT = new Builder("right").buttons(axisAsButton(GLFW_GAMEPAD_AXIS_LEFT_X, true)).register();
|
|
||||||
public static final ButtonBinding SCREENSHOT = new Builder("screenshot").buttons(GLFW_GAMEPAD_BUTTON_DPAD_UP, GLFW_GAMEPAD_BUTTON_A)
|
|
||||||
.action(InputHandlers::handleScreenshot).cooldown(true).register();
|
|
||||||
public static final ButtonBinding SLOT_DOWN = new Builder("slot_down").buttons(GLFW_GAMEPAD_BUTTON_DPAD_DOWN)
|
|
||||||
.action(InputHandlers.handleInventorySlotPad(1)).onlyInInventory().cooldown(true).register();
|
|
||||||
public static final ButtonBinding SLOT_LEFT = new Builder("slot_left").buttons(GLFW_GAMEPAD_BUTTON_DPAD_LEFT)
|
|
||||||
.action(InputHandlers.handleInventorySlotPad(3)).onlyInInventory().cooldown(true).register();
|
|
||||||
public static final ButtonBinding SLOT_RIGHT = new Builder("slot_right").buttons(GLFW_GAMEPAD_BUTTON_DPAD_RIGHT)
|
|
||||||
.action(InputHandlers.handleInventorySlotPad(2)).onlyInInventory().cooldown(true).register();
|
|
||||||
public static final ButtonBinding SLOT_UP = new Builder("slot_up").buttons(GLFW_GAMEPAD_BUTTON_DPAD_UP)
|
|
||||||
.action(InputHandlers.handleInventorySlotPad(0)).onlyInInventory().cooldown(true).register();
|
|
||||||
public static final ButtonBinding SMOOTH_CAMERA = new Builder("toggle_smooth_camera").cooldown(true).register();
|
|
||||||
public static final ButtonBinding SNEAK = new Builder("sneak").buttons(GLFW_GAMEPAD_BUTTON_RIGHT_THUMB)
|
|
||||||
.actions(PressAction.DEFAULT_ACTION, InputHandlers::handleToggleSneak).onlyInGame().cooldown(true).register();
|
|
||||||
public static final ButtonBinding SPRINT = new Builder("sprint").buttons(GLFW_GAMEPAD_BUTTON_LEFT_THUMB).register();
|
|
||||||
public static final ButtonBinding SWAP_HANDS = new Builder("swap_hands").buttons(GLFW_GAMEPAD_BUTTON_X).cooldown(true).register();
|
|
||||||
public static final ButtonBinding TAB_LEFT = new Builder("tab_left").buttons(GLFW_GAMEPAD_BUTTON_LEFT_BUMPER)
|
|
||||||
.action(InputHandlers.handleHotbar(false)).filter(Predicates.or(InputHandlers::inInventory, InputHandlers::inAdvancements)).cooldown(true).register();
|
|
||||||
public static final ButtonBinding TAB_RIGHT = new Builder("tab_right").buttons(GLFW_GAMEPAD_BUTTON_RIGHT_BUMPER)
|
|
||||||
.action(InputHandlers.handleHotbar(true)).filter(Predicates.or(InputHandlers::inInventory, InputHandlers::inAdvancements)).cooldown(true).register();
|
|
||||||
public static final ButtonBinding TOGGLE_PERSPECTIVE = new Builder("toggle_perspective").buttons(GLFW_GAMEPAD_BUTTON_DPAD_UP, GLFW_GAMEPAD_BUTTON_Y).cooldown(true).register();
|
|
||||||
public static final ButtonBinding USE = new Builder("use").buttons(axisAsButton(GLFW_GAMEPAD_AXIS_LEFT_TRIGGER, true)).register();
|
|
||||||
|
|
||||||
private int[] button;
|
|
||||||
private int[] defaultButton;
|
|
||||||
private String key;
|
|
||||||
private KeyBinding mcKeyBinding = null;
|
|
||||||
protected PairPredicate<MinecraftClient, ButtonBinding> filter;
|
|
||||||
private List<PressAction> actions = new ArrayList<>(Collections.singletonList(PressAction.DEFAULT_ACTION));
|
|
||||||
private boolean hasCooldown;
|
|
||||||
private int cooldown = 0;
|
|
||||||
boolean pressed = false;
|
|
||||||
|
|
||||||
public ButtonBinding(@NotNull String key, int[] defaultButton, @NotNull List<PressAction> actions, PairPredicate<MinecraftClient, ButtonBinding> filter, boolean hasCooldown)
|
|
||||||
{
|
|
||||||
this.setButton(this.defaultButton = defaultButton);
|
|
||||||
this.key = key;
|
|
||||||
this.filter = filter;
|
|
||||||
this.actions.addAll(actions);
|
|
||||||
this.hasCooldown = hasCooldown;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ButtonBinding(@NotNull 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 @NotNull
|
|
||||||
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, @NotNull ButtonState state)
|
|
||||||
{
|
|
||||||
if (state == ButtonState.REPEAT && this.hasCooldown && this.cooldown != 0)
|
|
||||||
return;
|
|
||||||
if (this.hasCooldown && state.isPressed()) {
|
|
||||||
this.cooldown = 5;
|
|
||||||
|
|
||||||
}
|
|
||||||
for (int i = this.actions.size() - 1; i >= 0; i--) {
|
|
||||||
if (this.actions.get(i).press(client, this, state))
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull String getName()
|
|
||||||
{
|
|
||||||
return this.key;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the translation key of this button binding.
|
|
||||||
*
|
|
||||||
* @return The translation key.
|
|
||||||
*/
|
|
||||||
public @NotNull
|
|
||||||
String getTranslationKey()
|
|
||||||
{
|
|
||||||
return "lambdacontrols.action." + this.getName();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the key binding equivalent of this button binding.
|
|
||||||
*
|
|
||||||
* @return The key binding equivalent.
|
|
||||||
*/
|
|
||||||
public @NotNull
|
|
||||||
Optional<KeyBinding> asKeyBinding()
|
|
||||||
{
|
|
||||||
return Optional.ofNullable(this.mcKeyBinding);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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 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
|
|
||||||
String getLocalizedButtonName(int button)
|
|
||||||
{
|
|
||||||
switch (button % 500) {
|
|
||||||
case -1:
|
|
||||||
return I18n.translate("key.keyboard.unknown");
|
|
||||||
case GLFW_GAMEPAD_BUTTON_A:
|
|
||||||
return I18n.translate("lambdacontrols.button.a");
|
|
||||||
case GLFW_GAMEPAD_BUTTON_B:
|
|
||||||
return I18n.translate("lambdacontrols.button.b");
|
|
||||||
case GLFW_GAMEPAD_BUTTON_X:
|
|
||||||
return I18n.translate("lambdacontrols.button.x");
|
|
||||||
case GLFW_GAMEPAD_BUTTON_Y:
|
|
||||||
return I18n.translate("lambdacontrols.button.y");
|
|
||||||
case GLFW_GAMEPAD_BUTTON_LEFT_BUMPER:
|
|
||||||
return I18n.translate("lambdacontrols.button.left_bumper");
|
|
||||||
case GLFW_GAMEPAD_BUTTON_RIGHT_BUMPER:
|
|
||||||
return I18n.translate("lambdacontrols.button.right_bumper");
|
|
||||||
case GLFW_GAMEPAD_BUTTON_BACK:
|
|
||||||
return I18n.translate("lambdacontrols.button.back");
|
|
||||||
case GLFW_GAMEPAD_BUTTON_START:
|
|
||||||
return I18n.translate("lambdacontrols.button.start");
|
|
||||||
case GLFW_GAMEPAD_BUTTON_GUIDE:
|
|
||||||
return I18n.translate("lambdacontrols.button.guide");
|
|
||||||
case GLFW_GAMEPAD_BUTTON_LEFT_THUMB:
|
|
||||||
return I18n.translate("lambdacontrols.button.left_thumb");
|
|
||||||
case GLFW_GAMEPAD_BUTTON_RIGHT_THUMB:
|
|
||||||
return I18n.translate("lambdacontrols.button.right_thumb");
|
|
||||||
case GLFW_GAMEPAD_BUTTON_DPAD_UP:
|
|
||||||
return I18n.translate("lambdacontrols.button.dpad_up");
|
|
||||||
case GLFW_GAMEPAD_BUTTON_DPAD_RIGHT:
|
|
||||||
return I18n.translate("lambdacontrols.button.dpad_right");
|
|
||||||
case GLFW_GAMEPAD_BUTTON_DPAD_DOWN:
|
|
||||||
return I18n.translate("lambdacontrols.button.dpad_down");
|
|
||||||
case 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static {
|
|
||||||
MOVEMENT_CATEGORY = registerDefaultCategory("key.categories.movement", category -> category.registerAllBindings(
|
|
||||||
ButtonBinding.FORWARD,
|
|
||||||
ButtonBinding.BACK,
|
|
||||||
ButtonBinding.LEFT,
|
|
||||||
ButtonBinding.RIGHT,
|
|
||||||
ButtonBinding.JUMP,
|
|
||||||
ButtonBinding.SNEAK,
|
|
||||||
ButtonBinding.SPRINT));
|
|
||||||
GAMEPLAY_CATEGORY = registerDefaultCategory("key.categories.gameplay", category -> category.registerAllBindings(
|
|
||||||
ButtonBinding.ATTACK,
|
|
||||||
ButtonBinding.PICK_BLOCK,
|
|
||||||
ButtonBinding.USE
|
|
||||||
));
|
|
||||||
INVENTORY_CATEGORY = registerDefaultCategory("key.categories.inventory", category -> category.registerAllBindings(
|
|
||||||
ButtonBinding.DROP_ITEM,
|
|
||||||
ButtonBinding.HOTBAR_LEFT,
|
|
||||||
ButtonBinding.HOTBAR_RIGHT,
|
|
||||||
ButtonBinding.INVENTORY,
|
|
||||||
ButtonBinding.SWAP_HANDS
|
|
||||||
));
|
|
||||||
MULTIPLAYER_CATEGORY = registerDefaultCategory("key.categories.multiplayer",
|
|
||||||
category -> category.registerAllBindings(ButtonBinding.CHAT, ButtonBinding.PLAYER_LIST));
|
|
||||||
MISC_CATEGORY = registerDefaultCategory("key.categories.misc", category -> category.registerAllBindings(
|
|
||||||
ButtonBinding.SCREENSHOT,
|
|
||||||
//SMOOTH_CAMERA,
|
|
||||||
ButtonBinding.TOGGLE_PERSPECTIVE
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents a quick {@link ButtonBinding} builder.
|
|
||||||
*
|
|
||||||
* @author LambdAurora
|
|
||||||
* @version 1.1.0
|
|
||||||
* @since 1.1.0
|
|
||||||
*/
|
|
||||||
public static class Builder
|
|
||||||
{
|
|
||||||
private final String key;
|
|
||||||
private int[] buttons = new int[0];
|
|
||||||
private List<PressAction> actions = new ArrayList<>();
|
|
||||||
private PairPredicate<MinecraftClient, ButtonBinding> filter = Predicates.pairAlwaysTrue();
|
|
||||||
private boolean cooldown = false;
|
|
||||||
private ButtonCategory category = null;
|
|
||||||
private KeyBinding mcBinding = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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.getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder(@NotNull net.minecraft.util.Identifier identifier)
|
|
||||||
{
|
|
||||||
this(new Identifier(identifier.toString()));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Defines the default buttons of the {@link ButtonBinding}.
|
|
||||||
*
|
|
||||||
* @param buttons The default buttons.
|
|
||||||
* @return The builder instance.
|
|
||||||
*/
|
|
||||||
public Builder buttons(int... buttons)
|
|
||||||
{
|
|
||||||
this.buttons = buttons;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the {@link ButtonBinding} to unbound.
|
|
||||||
*
|
|
||||||
* @return The builder instance.
|
|
||||||
*/
|
|
||||||
public Builder unbound()
|
|
||||||
{
|
|
||||||
return this.buttons(-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds the actions to the {@link ButtonBinding}.
|
|
||||||
*
|
|
||||||
* @param actions The actions to add.
|
|
||||||
* @return The builder instance.
|
|
||||||
*/
|
|
||||||
public Builder actions(@NotNull PressAction... actions)
|
|
||||||
{
|
|
||||||
this.actions.addAll(Arrays.asList(actions));
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds an action to the {@link ButtonBinding}.
|
|
||||||
*
|
|
||||||
* @param action The action to add.
|
|
||||||
* @return The builder instance.
|
|
||||||
*/
|
|
||||||
public Builder action(@NotNull PressAction action)
|
|
||||||
{
|
|
||||||
this.actions.add(action);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets a filter for the {@link ButtonBinding}.
|
|
||||||
*
|
|
||||||
* @param filter The filter.
|
|
||||||
* @return The builder instance.
|
|
||||||
*/
|
|
||||||
public Builder filter(@NotNull PairPredicate<MinecraftClient, ButtonBinding> filter)
|
|
||||||
{
|
|
||||||
this.filter = filter;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the filter of {@link ButtonBinding} to only in game.
|
|
||||||
*
|
|
||||||
* @return The builder instance.
|
|
||||||
* @see #filter(PairPredicate)
|
|
||||||
* @see InputHandlers#inGame(MinecraftClient, ButtonBinding)
|
|
||||||
*/
|
|
||||||
public Builder onlyInGame()
|
|
||||||
{
|
|
||||||
return this.filter(InputHandlers::inGame);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the filter of {@link ButtonBinding} to only in inventory.
|
|
||||||
*
|
|
||||||
* @return The builder instance.
|
|
||||||
* @see #filter(PairPredicate)
|
|
||||||
* @see InputHandlers#inInventory(MinecraftClient, ButtonBinding)
|
|
||||||
*/
|
|
||||||
public Builder onlyInInventory()
|
|
||||||
{
|
|
||||||
return this.filter(InputHandlers::inInventory);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets whether the {@link ButtonBinding} has a cooldown or not.
|
|
||||||
*
|
|
||||||
* @param cooldown True if the {@link ButtonBinding} has a cooldown, else false.
|
|
||||||
* @return The builder instance.
|
|
||||||
*/
|
|
||||||
public Builder cooldown(boolean cooldown)
|
|
||||||
{
|
|
||||||
this.cooldown = cooldown;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the 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()
|
|
||||||
{
|
|
||||||
ButtonBinding 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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,180 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2020 LambdAurora <aurora42lambda@gmail.com>
|
|
||||||
*
|
|
||||||
* This file is part of LambdaControls.
|
|
||||||
*
|
|
||||||
* Licensed under the MIT license. For more information,
|
|
||||||
* see the LICENSE file.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package me.lambdaurora.lambdacontrols.client.controller;
|
|
||||||
|
|
||||||
import me.lambdaurora.lambdacontrols.client.LambdaControlsClient;
|
|
||||||
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.
|
|
||||||
*
|
|
||||||
* @author LambdAurora
|
|
||||||
* @version 1.0.0
|
|
||||||
* @since 1.0.0
|
|
||||||
*/
|
|
||||||
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 getId()
|
|
||||||
{
|
|
||||||
return this.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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 @NotNull String getName()
|
|
||||||
{
|
|
||||||
String name = this.isGamepad() ? GLFW.glfwGetGamepadName(this.id) : GLFW.glfwGetJoystickName(this.id);
|
|
||||||
return name == null ? String.valueOf(this.getId()) : name;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the state of the controller.
|
|
||||||
*
|
|
||||||
* @return The state of the controller input.
|
|
||||||
*/
|
|
||||||
public GLFWGamepadState getState()
|
|
||||||
{
|
|
||||||
GLFWGamepadState state = GLFWGamepadState.create();
|
|
||||||
if (this.isGamepad())
|
|
||||||
GLFW.glfwGetGamepadState(this.id, state);
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static @NotNull Controller byId(int id)
|
|
||||||
{
|
|
||||||
if (id > GLFW.GLFW_JOYSTICK_LAST) {
|
|
||||||
LambdaControlsClient.get().log("Controller '" + id + "' doesn't exist.");
|
|
||||||
id = GLFW.GLFW_JOYSTICK_LAST;
|
|
||||||
}
|
|
||||||
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> byGuid(@NotNull String guid)
|
|
||||||
{
|
|
||||||
return CONTROLLERS.values().stream().filter(Controller::isConnected)
|
|
||||||
.filter(controller -> controller.getGuid().equals(guid))
|
|
||||||
.max(Comparator.comparingInt(Controller::getId));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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;
|
|
||||||
|
|
||||||
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 updateMappings()
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
File mappingsFile = new File("config/gamecontrollerdb.txt");
|
|
||||||
if (!mappingsFile.exists())
|
|
||||||
return;
|
|
||||||
LambdaControlsClient.get().log("Updating controller mappings...");
|
|
||||||
ByteBuffer buffer = ioResourceToBuffer(mappingsFile.getPath(), 1024);
|
|
||||||
GLFW.glfwUpdateGamepadMappings(buffer);
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,245 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2020 LambdAurora <aurora42lambda@gmail.com>
|
|
||||||
*
|
|
||||||
* This file is part of LambdaControls.
|
|
||||||
*
|
|
||||||
* Licensed under the MIT license. For more information,
|
|
||||||
* see the LICENSE file.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package me.lambdaurora.lambdacontrols.client.controller;
|
|
||||||
|
|
||||||
import me.lambdaurora.lambdacontrols.client.ButtonState;
|
|
||||||
import me.lambdaurora.lambdacontrols.client.mixin.AdvancementsScreenAccessor;
|
|
||||||
import me.lambdaurora.lambdacontrols.client.mixin.CreativeInventoryScreenAccessor;
|
|
||||||
import me.lambdaurora.lambdacontrols.client.util.ContainerScreenAccessor;
|
|
||||||
import me.lambdaurora.lambdacontrols.client.util.KeyBindingAccessor;
|
|
||||||
import net.minecraft.client.MinecraftClient;
|
|
||||||
import net.minecraft.client.gui.screen.advancement.AdvancementTab;
|
|
||||||
import net.minecraft.client.gui.screen.advancement.AdvancementsScreen;
|
|
||||||
import net.minecraft.client.gui.screen.ingame.ContainerScreen;
|
|
||||||
import net.minecraft.client.gui.screen.ingame.CreativeInventoryScreen;
|
|
||||||
import net.minecraft.client.util.ScreenshotUtils;
|
|
||||||
import net.minecraft.container.Slot;
|
|
||||||
import net.minecraft.item.ItemGroup;
|
|
||||||
import org.aperlambda.lambdacommon.utils.Pair;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.function.Predicate;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents some input handlers.
|
|
||||||
*
|
|
||||||
* @author LambdAurora
|
|
||||||
* @version 1.1.0
|
|
||||||
* @since 1.1.0
|
|
||||||
*/
|
|
||||||
public class InputHandlers
|
|
||||||
{
|
|
||||||
private InputHandlers()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public static PressAction handleHotbar(boolean right)
|
|
||||||
{
|
|
||||||
return (client, button, action) -> {
|
|
||||||
if (action == ButtonState.RELEASE)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// 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;
|
|
||||||
return true;
|
|
||||||
} else if (client.currentScreen instanceof CreativeInventoryScreen) {
|
|
||||||
CreativeInventoryScreenAccessor inventory = (CreativeInventoryScreenAccessor) client.currentScreen;
|
|
||||||
int currentSelectedTab = inventory.lambdacontrols_getSelectedTab();
|
|
||||||
int nextTab = currentSelectedTab + (right ? 1 : -1);
|
|
||||||
if (nextTab < 0)
|
|
||||||
nextTab = ItemGroup.GROUPS.length - 1;
|
|
||||||
else if (nextTab >= ItemGroup.GROUPS.length)
|
|
||||||
nextTab = 0;
|
|
||||||
inventory.lambdacontrols_setSelectedTab(ItemGroup.GROUPS[nextTab]);
|
|
||||||
return true;
|
|
||||||
} else if (client.currentScreen instanceof AdvancementsScreen) {
|
|
||||||
AdvancementsScreenAccessor screen = (AdvancementsScreenAccessor) client.currentScreen;
|
|
||||||
List<AdvancementTab> tabs = screen.lambdacontrols_getTabs().values().stream().distinct().collect(Collectors.toList());
|
|
||||||
AdvancementTab tab = screen.lambdacontrols_getSelectedTab();
|
|
||||||
for (int i = 0; i < tabs.size(); i++) {
|
|
||||||
if (tabs.get(i).equals(tab)) {
|
|
||||||
int nextTab = i + (right ? 1 : -1);
|
|
||||||
if (nextTab < 0)
|
|
||||||
nextTab = tabs.size() - 1;
|
|
||||||
else if (nextTab >= tabs.size())
|
|
||||||
nextTab = 0;
|
|
||||||
screen.lambdacontrols_getAdvancementManager().selectTab(tabs.get(nextTab).getRoot(), true);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean handlePauseGame(@NotNull MinecraftClient client, @NotNull ButtonBinding binding, @NotNull ButtonState action)
|
|
||||||
{
|
|
||||||
if (action == ButtonState.PRESS) {
|
|
||||||
// If in game, then pause the game.
|
|
||||||
if (client.currentScreen == null)
|
|
||||||
client.openPauseMenu(false);
|
|
||||||
else if (client.currentScreen instanceof ContainerScreen && client.player != null) // If the current screen is a container then close it.
|
|
||||||
client.player.closeContainer();
|
|
||||||
else // 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, @NotNull ButtonState action)
|
|
||||||
{
|
|
||||||
if (action == ButtonState.PRESS)
|
|
||||||
ScreenshotUtils.saveScreenshot(client.runDirectory, client.getWindow().getFramebufferWidth(), client.getWindow().getFramebufferHeight(), client.getFramebuffer(),
|
|
||||||
text -> client.execute(() -> client.inGameHud.getChatHud().addMessage(text)));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean handleToggleSneak(@NotNull MinecraftClient client, @NotNull ButtonBinding button, @NotNull ButtonState action)
|
|
||||||
{
|
|
||||||
if (client.player != null && !client.player.abilities.flying) {
|
|
||||||
button.asKeyBinding().filter(binding -> action == ButtonState.PRESS).ifPresent(binding -> ((KeyBindingAccessor) binding).lambdacontrols_handlePressState(!binding.isPressed()));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static PressAction handleInventorySlotPad(int direction)
|
|
||||||
{
|
|
||||||
return (client, binding, action) -> {
|
|
||||||
if (!(client.currentScreen instanceof ContainerScreen && action != ButtonState.RELEASE))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
ContainerScreen inventory = (ContainerScreen) client.currentScreen;
|
|
||||||
ContainerScreenAccessor accessor = (ContainerScreenAccessor) inventory;
|
|
||||||
int guiLeft = accessor.lambdacontrols_getX();
|
|
||||||
int guiTop = accessor.lambdacontrols_getY();
|
|
||||||
double mouseX = client.mouse.getX() * (double) client.getWindow().getScaledWidth() / (double) client.getWindow().getWidth();
|
|
||||||
double mouseY = client.mouse.getY() * (double) client.getWindow().getScaledHeight() / (double) client.getWindow().getHeight();
|
|
||||||
|
|
||||||
// Finds the hovered slot.
|
|
||||||
Slot mouseSlot = accessor.lambdacontrols_getSlotAt(mouseX, mouseY);
|
|
||||||
|
|
||||||
// Finds the closest slot in the GUI within 14 pixels.
|
|
||||||
Optional<Slot> closestSlot = inventory.getContainer().slots.parallelStream()
|
|
||||||
.filter(Predicate.isEqual(mouseSlot).negate())
|
|
||||||
.map(slot -> {
|
|
||||||
int posX = guiLeft + slot.xPosition + 8;
|
|
||||||
int posY = guiTop + slot.yPosition + 8;
|
|
||||||
|
|
||||||
int otherPosX = (int) mouseX;
|
|
||||||
int otherPosY = (int) mouseY;
|
|
||||||
if (mouseSlot != null) {
|
|
||||||
otherPosX = guiLeft + mouseSlot.xPosition + 8;
|
|
||||||
otherPosY = guiTop + mouseSlot.yPosition + 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 -> {
|
|
||||||
Slot slot = entry.key;
|
|
||||||
int posX = guiLeft + slot.xPosition + 8;
|
|
||||||
int posY = guiTop + slot.yPosition + 8;
|
|
||||||
int otherPosX = (int) mouseX;
|
|
||||||
int otherPosY = (int) mouseY;
|
|
||||||
if (mouseSlot != null) {
|
|
||||||
otherPosX = guiLeft + mouseSlot.xPosition + 8;
|
|
||||||
otherPosY = guiTop + mouseSlot.yPosition + 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()) {
|
|
||||||
Slot slot = closestSlot.get();
|
|
||||||
int x = guiLeft + slot.xPosition + 8;
|
|
||||||
int y = guiTop + slot.yPosition + 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 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 ContainerScreen;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2020 LambdAurora <aurora42lambda@gmail.com>
|
|
||||||
*
|
|
||||||
* This file is part of LambdaControls.
|
|
||||||
*
|
|
||||||
* Licensed under the MIT license. For more information,
|
|
||||||
* see the LICENSE file.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package me.lambdaurora.lambdacontrols.client.controller;
|
|
||||||
|
|
||||||
import me.lambdaurora.lambdacontrols.client.ButtonState;
|
|
||||||
import me.lambdaurora.lambdacontrols.client.util.KeyBindingAccessor;
|
|
||||||
import net.minecraft.client.MinecraftClient;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents a press action callback.
|
|
||||||
*
|
|
||||||
* @author LambdAurora
|
|
||||||
* @version 1.1.0
|
|
||||||
* @since 1.0.0
|
|
||||||
*/
|
|
||||||
@FunctionalInterface
|
|
||||||
public interface PressAction
|
|
||||||
{
|
|
||||||
PressAction DEFAULT_ACTION = (client, button, action) -> {
|
|
||||||
if (action == ButtonState.REPEAT || client.currentScreen != null)
|
|
||||||
return false;
|
|
||||||
button.asKeyBinding().ifPresent(binding -> ((KeyBindingAccessor) binding).lambdacontrols_handlePressState(button.isButtonDown()));
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles when there is a press action.
|
|
||||||
*
|
|
||||||
* @param client The client instance.
|
|
||||||
* @param action The action done.
|
|
||||||
*/
|
|
||||||
boolean press(@NotNull MinecraftClient client, @NotNull ButtonBinding button, @NotNull ButtonState action);
|
|
||||||
}
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2020 LambdAurora <aurora42lambda@gmail.com>
|
|
||||||
*
|
|
||||||
* This file is part of LambdaControls.
|
|
||||||
*
|
|
||||||
* Licensed under the MIT license. For more information,
|
|
||||||
* see the LICENSE file.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package me.lambdaurora.lambdacontrols.client.gui;
|
|
||||||
|
|
||||||
import me.lambdaurora.lambdacontrols.client.LambdaControlsClient;
|
|
||||||
import me.lambdaurora.lambdacontrols.client.controller.ButtonBinding;
|
|
||||||
import me.lambdaurora.spruceui.AbstractIconButtonWidget;
|
|
||||||
import net.minecraft.client.MinecraftClient;
|
|
||||||
import net.minecraft.client.resource.language.I18n;
|
|
||||||
import org.aperlambda.lambdacommon.utils.Pair;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents a controller button widget.
|
|
||||||
*/
|
|
||||||
public class ControllerButtonWidget extends AbstractIconButtonWidget
|
|
||||||
{
|
|
||||||
private ButtonBinding binding;
|
|
||||||
private int iconWidth;
|
|
||||||
|
|
||||||
public ControllerButtonWidget(int x, int y, int width, @NotNull ButtonBinding binding, @NotNull PressAction action)
|
|
||||||
{
|
|
||||||
super(x, y, width, 20, ButtonBinding.getLocalizedButtonName(binding.getButton()[0]), action);
|
|
||||||
this.binding = binding;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void update()
|
|
||||||
{
|
|
||||||
int length = binding.getButton().length;
|
|
||||||
this.setMessage(this.binding.isNotBound() ? I18n.translate("lambdacontrols.not_bound") :
|
|
||||||
(length > 0 ? ButtonBinding.getLocalizedButtonName(binding.getButton()[0]) : "<>"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getMessage()
|
|
||||||
{
|
|
||||||
if (this.binding.getButton().length > 1)
|
|
||||||
return "";
|
|
||||||
return super.getMessage();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected int renderIcon(int mouseX, int mouseY, float delta, int x, int y)
|
|
||||||
{
|
|
||||||
if (this.binding.getButton().length > 1) {
|
|
||||||
x += (this.width / 2 - this.iconWidth / 2) - 4;
|
|
||||||
}
|
|
||||||
Pair<Integer, Integer> size = LambdaControlsClient.drawButton(x, y, this.binding, MinecraftClient.getInstance());
|
|
||||||
this.iconWidth = size.key;
|
|
||||||
return size.value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2020 LambdAurora <aurora42lambda@gmail.com>
|
|
||||||
*
|
|
||||||
* This file is part of LambdaControls.
|
|
||||||
*
|
|
||||||
* Licensed under the MIT license. For more information,
|
|
||||||
* see the LICENSE file.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package me.lambdaurora.lambdacontrols.client.gui;
|
|
||||||
|
|
||||||
import me.lambdaurora.lambdacontrols.client.LambdaControlsClient;
|
|
||||||
import me.lambdaurora.lambdacontrols.client.controller.ButtonBinding;
|
|
||||||
import me.lambdaurora.lambdacontrols.client.controller.InputManager;
|
|
||||||
import me.lambdaurora.spruceui.SpruceButtonWidget;
|
|
||||||
import net.minecraft.client.gui.screen.Screen;
|
|
||||||
import net.minecraft.client.gui.screen.options.ControlsOptionsScreen;
|
|
||||||
import net.minecraft.client.gui.widget.ButtonWidget;
|
|
||||||
import net.minecraft.client.resource.language.I18n;
|
|
||||||
import net.minecraft.text.TranslatableText;
|
|
||||||
import org.aperlambda.lambdacommon.utils.function.Predicates;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents the controls screen.
|
|
||||||
*/
|
|
||||||
public class ControllerControlsScreen extends Screen
|
|
||||||
{
|
|
||||||
private final Screen parent;
|
|
||||||
final LambdaControlsClient mod;
|
|
||||||
private final boolean hideSettings;
|
|
||||||
private ControlsListWidget bindingsListWidget;
|
|
||||||
private ButtonWidget resetButton;
|
|
||||||
public ButtonBinding focusedBinding;
|
|
||||||
public boolean waiting = false;
|
|
||||||
public List<Integer> currentButtons = new ArrayList<>();
|
|
||||||
|
|
||||||
public ControllerControlsScreen(@NotNull Screen parent, boolean hideSettings)
|
|
||||||
{
|
|
||||||
super(new TranslatableText("lambdacontrols.menu.title.controller_controls"));
|
|
||||||
this.parent = parent;
|
|
||||||
this.mod = LambdaControlsClient.get();
|
|
||||||
this.hideSettings = hideSettings;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void removed()
|
|
||||||
{
|
|
||||||
this.mod.config.save();
|
|
||||||
super.removed();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void init()
|
|
||||||
{
|
|
||||||
this.addButton(new SpruceButtonWidget(this.width / 2 - 155, 18, this.hideSettings ? 310 : 150, 20, I18n.translate("lambdacontrols.menu.keyboard_controls"),
|
|
||||||
btn -> this.minecraft.openScreen(new ControlsOptionsScreen(this, this.minecraft.options))));
|
|
||||||
if (!this.hideSettings)
|
|
||||||
this.addButton(new SpruceButtonWidget(this.width / 2 - 155 + 160, 18, 150, 20, I18n.translate("menu.options"),
|
|
||||||
btn -> this.minecraft.openScreen(new LambdaControlsSettingsScreen(this, this.minecraft.options, true))));
|
|
||||||
this.bindingsListWidget = new ControlsListWidget(this, this.minecraft);
|
|
||||||
this.children.add(this.bindingsListWidget);
|
|
||||||
this.resetButton = this.addButton(new ButtonWidget(this.width / 2 - 155, this.height - 29, 150, 20, I18n.translate("controls.resetAll"),
|
|
||||||
btn -> InputManager.streamBindings().forEach(binding -> this.mod.config.setButtonBinding(binding, binding.getDefaultButton()))));
|
|
||||||
this.addButton(new ButtonWidget(this.width / 2 - 155 + 160, this.height - 29, 150, 20, I18n.translate("gui.done"),
|
|
||||||
btn -> this.minecraft.openScreen(this.parent)));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void render(int mouseX, int mouseY, float delta)
|
|
||||||
{
|
|
||||||
this.renderBackground();
|
|
||||||
this.bindingsListWidget.render(mouseX, mouseY, delta);
|
|
||||||
this.drawCenteredString(this.font, this.title.asFormattedString(), this.width / 2, 8, 16777215);
|
|
||||||
this.resetButton.active = InputManager.streamBindings().anyMatch(Predicates.not(ButtonBinding::isDefault));
|
|
||||||
super.render(mouseX, mouseY, delta);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,205 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2020 LambdAurora <aurora42lambda@gmail.com>
|
|
||||||
*
|
|
||||||
* This file is part of LambdaControls.
|
|
||||||
*
|
|
||||||
* Licensed under the MIT license. For more information,
|
|
||||||
* see the LICENSE file.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package me.lambdaurora.lambdacontrols.client.gui;
|
|
||||||
|
|
||||||
import me.lambdaurora.lambdacontrols.client.controller.ButtonBinding;
|
|
||||||
import me.lambdaurora.lambdacontrols.client.controller.ButtonCategory;
|
|
||||||
import me.lambdaurora.lambdacontrols.client.controller.InputManager;
|
|
||||||
import net.fabricmc.api.EnvType;
|
|
||||||
import net.fabricmc.api.Environment;
|
|
||||||
import net.minecraft.client.MinecraftClient;
|
|
||||||
import net.minecraft.client.font.TextRenderer;
|
|
||||||
import net.minecraft.client.gui.Element;
|
|
||||||
import net.minecraft.client.gui.widget.ButtonWidget;
|
|
||||||
import net.minecraft.client.gui.widget.ElementListWidget;
|
|
||||||
import net.minecraft.client.resource.language.I18n;
|
|
||||||
import net.minecraft.util.Formatting;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents a control list widget.
|
|
||||||
*/
|
|
||||||
public class ControlsListWidget extends ElementListWidget<ControlsListWidget.Entry>
|
|
||||||
{
|
|
||||||
private static final int[] UNBOUND = new int[0];
|
|
||||||
private final ControllerControlsScreen gui;
|
|
||||||
private int field_2733;
|
|
||||||
|
|
||||||
public ControlsListWidget(@NotNull ControllerControlsScreen gui, @NotNull MinecraftClient client)
|
|
||||||
{
|
|
||||||
super(client, gui.width + 45, gui.height, 43, gui.height - 32, 24);
|
|
||||||
this.gui = gui;
|
|
||||||
|
|
||||||
InputManager.streamCategories()
|
|
||||||
.sorted(Comparator.comparingInt(ButtonCategory::getPriority))
|
|
||||||
.forEach(category -> {
|
|
||||||
this.addEntry(new CategoryEntry(category));
|
|
||||||
|
|
||||||
category.getBindings().forEach(binding -> {
|
|
||||||
int i = client.textRenderer.getStringWidth(I18n.translate(binding.getTranslationKey()));
|
|
||||||
if (i > this.field_2733) {
|
|
||||||
this.field_2733 = i;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.addEntry(new ControlsListWidget.ButtonBindingEntry(binding));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected int getScrollbarPosition()
|
|
||||||
{
|
|
||||||
return super.getScrollbarPosition() + 15;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getRowWidth()
|
|
||||||
{
|
|
||||||
return super.getRowWidth() + 32;
|
|
||||||
}
|
|
||||||
|
|
||||||
public class ButtonBindingEntry extends Entry
|
|
||||||
{
|
|
||||||
private final ButtonBinding binding;
|
|
||||||
private final String bindingName;
|
|
||||||
private final ControllerButtonWidget editButton;
|
|
||||||
private final ButtonWidget resetButton;
|
|
||||||
private final ButtonWidget unboundButton;
|
|
||||||
|
|
||||||
ButtonBindingEntry(@NotNull ButtonBinding binding)
|
|
||||||
{
|
|
||||||
this.binding = binding;
|
|
||||||
this.bindingName = I18n.translate(this.binding.getTranslationKey());
|
|
||||||
this.editButton = new ControllerButtonWidget(0, 0, 110, this.binding, btn -> {
|
|
||||||
gui.focusedBinding = binding;
|
|
||||||
gui.currentButtons.clear();
|
|
||||||
gui.waiting = true;
|
|
||||||
})
|
|
||||||
{
|
|
||||||
protected String getNarrationMessage()
|
|
||||||
{
|
|
||||||
return binding.isNotBound() ? I18n.translate("narrator.controls.unbound", bindingName) : I18n.translate("narrator.controls.bound", bindingName, super.getNarrationMessage());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
this.resetButton = new ButtonWidget(0, 0, 50, 20, I18n.translate("controls.reset"),
|
|
||||||
btn -> gui.mod.config.setButtonBinding(binding, binding.getDefaultButton()))
|
|
||||||
{
|
|
||||||
protected String getNarrationMessage()
|
|
||||||
{
|
|
||||||
return I18n.translate("narrator.controls.reset", bindingName);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
this.unboundButton = new ButtonWidget(0, 0, 50, 20, I18n.translate("lambdacontrols.menu.unbound"),
|
|
||||||
btn -> {
|
|
||||||
gui.mod.config.setButtonBinding(binding, UNBOUND);
|
|
||||||
gui.focusedBinding = null;
|
|
||||||
})
|
|
||||||
{
|
|
||||||
protected String getNarrationMessage()
|
|
||||||
{
|
|
||||||
return I18n.translate("lambdacontrols.narrator.unbound", bindingName);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<? extends Element> children()
|
|
||||||
{
|
|
||||||
return Collections.unmodifiableList(Arrays.asList(this.editButton, this.resetButton));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void render(int index, int y, int x, int width, int height, int mouseX, int mouseY, boolean hovering, float delta)
|
|
||||||
{
|
|
||||||
boolean focused = gui.focusedBinding == this.binding;
|
|
||||||
TextRenderer textRenderer = ControlsListWidget.this.minecraft.textRenderer;
|
|
||||||
String bindingName = this.bindingName;
|
|
||||||
float var10002 = (float) (x + 70 - ControlsListWidget.this.field_2733);
|
|
||||||
int var10003 = y + height / 2;
|
|
||||||
textRenderer.draw(bindingName, var10002, (float) (var10003 - 9 / 2), 16777215);
|
|
||||||
this.resetButton.x = this.unboundButton.x = x + 190;
|
|
||||||
this.resetButton.y = this.unboundButton.y = y;
|
|
||||||
this.resetButton.active = !this.binding.isDefault();
|
|
||||||
if (focused)
|
|
||||||
this.unboundButton.render(mouseX, mouseY, delta);
|
|
||||||
else
|
|
||||||
this.resetButton.render(mouseX, mouseY, delta);
|
|
||||||
this.editButton.x = x + 75;
|
|
||||||
this.editButton.y = y;
|
|
||||||
this.editButton.update();
|
|
||||||
|
|
||||||
if (focused) {
|
|
||||||
this.editButton.setMessage(Formatting.WHITE + "> " + Formatting.YELLOW + this.editButton.getMessage() + Formatting.WHITE + " <");
|
|
||||||
} else if (!this.binding.isNotBound() && InputManager.hasDuplicatedBindings(this.binding)) {
|
|
||||||
this.editButton.setMessage(Formatting.RED + this.editButton.getMessage());
|
|
||||||
} else if (this.binding.isNotBound()) {
|
|
||||||
this.editButton.setMessage(Formatting.GOLD + this.editButton.getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
this.editButton.render(mouseX, mouseY, delta);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean mouseClicked(double mouseX, double mouseY, int button)
|
|
||||||
{
|
|
||||||
boolean focused = gui.focusedBinding == this.binding;
|
|
||||||
if (this.editButton.mouseClicked(mouseX, mouseY, button))
|
|
||||||
return true;
|
|
||||||
else
|
|
||||||
return focused ? this.unboundButton.mouseClicked(mouseX, mouseY, button) : this.resetButton.mouseClicked(mouseX, mouseY, button);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean mouseReleased(double mouseX, double mouseY, int button)
|
|
||||||
{
|
|
||||||
return this.editButton.mouseReleased(mouseX, mouseY, button) || this.resetButton.mouseReleased(mouseX, mouseY, button)
|
|
||||||
|| this.unboundButton.mouseReleased(mouseX, mouseY, button);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class CategoryEntry extends Entry
|
|
||||||
{
|
|
||||||
private final String name;
|
|
||||||
private final int nameWidth;
|
|
||||||
|
|
||||||
public CategoryEntry(@NotNull ButtonCategory category)
|
|
||||||
{
|
|
||||||
this.name = category.getTranslatedName();
|
|
||||||
this.nameWidth = ControlsListWidget.this.minecraft.textRenderer.getStringWidth(this.name);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void render(int index, int y, int x, int width, int height, int mouseX, int mouseY, boolean hovering, float delta)
|
|
||||||
{
|
|
||||||
ControlsListWidget.this.minecraft.textRenderer.draw(this.name, (float) (ControlsListWidget.this.minecraft.currentScreen.width / 2 - this.nameWidth / 2),
|
|
||||||
(float) ((y + height) - 9 - 1), 16777215);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean changeFocus(boolean bl)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<? extends Element> children()
|
|
||||||
{
|
|
||||||
return Collections.emptyList();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Environment(EnvType.CLIENT)
|
|
||||||
public abstract static class Entry extends ElementListWidget.Entry<Entry>
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2020 LambdAurora <aurora42lambda@gmail.com>
|
|
||||||
*
|
|
||||||
* This file is part of LambdaControls.
|
|
||||||
*
|
|
||||||
* Licensed under the MIT license. For more information,
|
|
||||||
* see the LICENSE file.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package me.lambdaurora.lambdacontrols.client.gui;
|
|
||||||
|
|
||||||
import me.lambdaurora.lambdacontrols.ControlsMode;
|
|
||||||
import me.lambdaurora.lambdacontrols.LambdaControlsConstants;
|
|
||||||
import me.lambdaurora.lambdacontrols.client.HudSide;
|
|
||||||
import me.lambdaurora.lambdacontrols.client.LambdaControlsClient;
|
|
||||||
import me.lambdaurora.lambdacontrols.client.controller.ButtonBinding;
|
|
||||||
import me.lambdaurora.spruceui.hud.Hud;
|
|
||||||
import net.minecraft.client.MinecraftClient;
|
|
||||||
import net.minecraft.util.Identifier;
|
|
||||||
import net.minecraft.util.hit.HitResult;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents the LambdaControls HUD.
|
|
||||||
*
|
|
||||||
* @author LambdAurora
|
|
||||||
* @version 1.1.0
|
|
||||||
* @since 1.0.0
|
|
||||||
*/
|
|
||||||
public class LambdaControlsHud extends Hud
|
|
||||||
{
|
|
||||||
private final LambdaControlsClient mod;
|
|
||||||
private MinecraftClient client;
|
|
||||||
private int widthBottom = 0;
|
|
||||||
private int widthTop = 0;
|
|
||||||
|
|
||||||
public LambdaControlsHud(@NotNull LambdaControlsClient mod)
|
|
||||||
{
|
|
||||||
super(new Identifier(LambdaControlsConstants.NAMESPACE, "hud/button_indicator"));
|
|
||||||
this.mod = mod;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void init(@NotNull MinecraftClient client, int screenWidth, int screenHeight)
|
|
||||||
{
|
|
||||||
super.init(client, screenWidth, screenHeight);
|
|
||||||
this.client = client;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Renders the LambdaControls' HUD.
|
|
||||||
*/
|
|
||||||
public void render(float tickDelta)
|
|
||||||
{
|
|
||||||
if (this.mod.config.getControlsMode() == ControlsMode.CONTROLLER && this.client.currentScreen == null) {
|
|
||||||
int x = this.mod.config.getHudSide() == HudSide.LEFT ? 10 : client.getWindow().getScaledWidth() - 10 - this.widthBottom, y = bottom(10);
|
|
||||||
x += (this.widthBottom = this.drawButtonTip(x, y, ButtonBinding.INVENTORY, true) + 10);
|
|
||||||
this.widthBottom += this.drawButtonTip(x, y, ButtonBinding.SWAP_HANDS, true);
|
|
||||||
x = this.mod.config.getHudSide() == HudSide.LEFT ? 10 : client.getWindow().getScaledWidth() - 10 - this.widthTop;
|
|
||||||
x += (this.widthTop = this.drawButtonTip(x, (y -= 20), ButtonBinding.DROP_ITEM, !this.client.player.getMainHandStack().isEmpty()) + 10);
|
|
||||||
this.widthTop += this.drawButtonTip(x, y, ButtonBinding.ATTACK.getButton(),
|
|
||||||
this.client.crosshairTarget.getType() == HitResult.Type.BLOCK ? "lambdacontrols.action.hit" : ButtonBinding.ATTACK.getTranslationKey(),
|
|
||||||
this.client.crosshairTarget.getType() != HitResult.Type.MISS);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private int bottom(int y)
|
|
||||||
{
|
|
||||||
return this.client.getWindow().getScaledHeight() - y - 15;
|
|
||||||
}
|
|
||||||
|
|
||||||
private int drawButtonTip(int x, int y, @NotNull ButtonBinding button, boolean display)
|
|
||||||
{
|
|
||||||
return LambdaControlsClient.drawButtonTip(x, y, button, display, this.client);
|
|
||||||
}
|
|
||||||
|
|
||||||
private int drawButtonTip(int x, int y, int[] button, @NotNull String action, boolean display)
|
|
||||||
{
|
|
||||||
return LambdaControlsClient.drawButtonTip(x, y, button, action, display, this.client);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,265 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2020 LambdAurora <aurora42lambda@gmail.com>
|
|
||||||
*
|
|
||||||
* This file is part of LambdaControls.
|
|
||||||
*
|
|
||||||
* Licensed under the MIT license. For more information,
|
|
||||||
* see the LICENSE file.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package me.lambdaurora.lambdacontrols.client.gui;
|
|
||||||
|
|
||||||
import me.lambdaurora.lambdacontrols.ControlsMode;
|
|
||||||
import me.lambdaurora.lambdacontrols.LambdaControls;
|
|
||||||
import me.lambdaurora.lambdacontrols.client.LambdaControlsClient;
|
|
||||||
import me.lambdaurora.lambdacontrols.client.controller.Controller;
|
|
||||||
import me.lambdaurora.spruceui.SpruceButtonWidget;
|
|
||||||
import me.lambdaurora.spruceui.SpruceLabelWidget;
|
|
||||||
import me.lambdaurora.spruceui.Tooltip;
|
|
||||||
import me.lambdaurora.spruceui.option.*;
|
|
||||||
import net.fabricmc.fabric.api.network.ClientSidePacketRegistry;
|
|
||||||
import net.minecraft.client.MinecraftClient;
|
|
||||||
import net.minecraft.client.gui.screen.Screen;
|
|
||||||
import net.minecraft.client.gui.screen.options.ControlsOptionsScreen;
|
|
||||||
import net.minecraft.client.gui.widget.ButtonListWidget;
|
|
||||||
import net.minecraft.client.gui.widget.ButtonWidget;
|
|
||||||
import net.minecraft.client.options.GameOptions;
|
|
||||||
import net.minecraft.client.options.Option;
|
|
||||||
import net.minecraft.client.resource.language.I18n;
|
|
||||||
import net.minecraft.text.TranslatableText;
|
|
||||||
import net.minecraft.util.Formatting;
|
|
||||||
import net.minecraft.util.Util;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.lwjgl.glfw.GLFW;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents the LambdaControls settings screen.
|
|
||||||
*/
|
|
||||||
public class LambdaControlsSettingsScreen extends Screen
|
|
||||||
{
|
|
||||||
public static final String GAMEPAD_TOOL_URL = "http://generalarcade.com/gamepadtool/";
|
|
||||||
final LambdaControlsClient mod;
|
|
||||||
private final Screen parent;
|
|
||||||
private final boolean hideControls;
|
|
||||||
// General options
|
|
||||||
private final Option autoSwitchModeOption;
|
|
||||||
private final Option rotationSpeedOption;
|
|
||||||
private final Option mouseSpeedOption;
|
|
||||||
private final Option resetOption;
|
|
||||||
// Gameplay options
|
|
||||||
private final Option frontBlockPlacingOption;
|
|
||||||
private final Option flyDriftingOption;
|
|
||||||
private final Option flyVerticalDriftingOption;
|
|
||||||
// Controller options
|
|
||||||
private final Option controllerOption;
|
|
||||||
private final Option secondControllerOption;
|
|
||||||
private final Option controllerTypeOption;
|
|
||||||
private final Option deadZoneOption;
|
|
||||||
private final Option invertsRightXAxis;
|
|
||||||
private final Option invertsRightYAxis;
|
|
||||||
private final Option unfocusedInputOption;
|
|
||||||
// Hud options
|
|
||||||
private final Option hudEnableOption;
|
|
||||||
private final Option hudSideOption;
|
|
||||||
private final String controllerMappingsUrlText = I18n.translate("lambdacontrols.controller.mappings.2", Formatting.GOLD.toString(), GAMEPAD_TOOL_URL, Formatting.RESET.toString());
|
|
||||||
private ButtonListWidget list;
|
|
||||||
private SpruceLabelWidget gamepadToolUrlLabel;
|
|
||||||
|
|
||||||
public LambdaControlsSettingsScreen(Screen parent, @NotNull GameOptions options, boolean hideControls)
|
|
||||||
{
|
|
||||||
super(new TranslatableText("lambdacontrols.title.settings"));
|
|
||||||
this.mod = LambdaControlsClient.get();
|
|
||||||
this.parent = parent;
|
|
||||||
this.hideControls = hideControls;
|
|
||||||
// General options
|
|
||||||
this.autoSwitchModeOption = new SpruceBooleanOption("lambdacontrols.menu.auto_switch_mode", this.mod.config::hasAutoSwitchMode,
|
|
||||||
this.mod.config::setAutoSwitchMode, new TranslatableText("lambdacontrols.tooltip.auto_switch_mode"), true);
|
|
||||||
this.rotationSpeedOption = new SpruceDoubleOption("lambdacontrols.menu.rotation_speed", 0.0, 150.0, 0.5F, this.mod.config::getRotationSpeed,
|
|
||||||
newValue -> {
|
|
||||||
synchronized (this.mod.config) {
|
|
||||||
this.mod.config.setRotationSpeed(newValue);
|
|
||||||
}
|
|
||||||
}, option -> option.getDisplayPrefix() + option.get(),
|
|
||||||
new TranslatableText("lambdacontrols.tooltip.rotation_speed"));
|
|
||||||
this.mouseSpeedOption = new SpruceDoubleOption("lambdacontrols.menu.mouse_speed", 0.0, 150.0, 0.5F, this.mod.config::getMouseSpeed,
|
|
||||||
newValue -> {
|
|
||||||
synchronized (this.mod.config) {
|
|
||||||
this.mod.config.setMouseSpeed(newValue);
|
|
||||||
}
|
|
||||||
}, option -> option.getDisplayPrefix() + option.get(),
|
|
||||||
new TranslatableText("lambdacontrols.tooltip.mouse_speed"));
|
|
||||||
this.resetOption = new SpruceResetOption(btn -> {
|
|
||||||
this.mod.config.reset();
|
|
||||||
MinecraftClient client = MinecraftClient.getInstance();
|
|
||||||
this.init(client, client.getWindow().getScaledWidth(), client.getWindow().getScaledHeight());
|
|
||||||
});
|
|
||||||
// Gameplay options
|
|
||||||
this.frontBlockPlacingOption = new SpruceBooleanOption("lambdacontrols.menu.front_block_placing", this.mod.config::hasFrontBlockPlacing,
|
|
||||||
this.mod.config::setFrontBlockPlacing, new TranslatableText("lambdacontrols.tooltip.front_block_placing"), true);
|
|
||||||
this.flyDriftingOption = new SpruceBooleanOption("lambdacontrols.menu.fly_drifting", this.mod.config::hasFlyDrifting,
|
|
||||||
this.mod.config::setFlyDrifting, new TranslatableText("lambdacontrols.tooltip.fly_drifting"), true);
|
|
||||||
this.flyVerticalDriftingOption = new SpruceBooleanOption("lambdacontrols.menu.fly_drifting_vertical", this.mod.config::hasFlyVerticalDrifting,
|
|
||||||
this.mod.config::setFlyVerticalDrifting, new TranslatableText("lambdacontrols.tooltip.fly_drifting_vertical"), true);
|
|
||||||
// Controller options
|
|
||||||
this.controllerOption = new SpruceCyclingOption("lambdacontrols.menu.controller", amount -> {
|
|
||||||
int id = this.mod.config.getController().getId();
|
|
||||||
id += amount;
|
|
||||||
if (id > GLFW.GLFW_JOYSTICK_LAST)
|
|
||||||
id = GLFW.GLFW_JOYSTICK_1;
|
|
||||||
this.mod.config.setController(Controller.byId(id));
|
|
||||||
}, option -> {
|
|
||||||
String controllerName = this.mod.config.getController().getName();
|
|
||||||
if (!this.mod.config.getController().isConnected())
|
|
||||||
return option.getDisplayPrefix() + Formatting.RED + controllerName;
|
|
||||||
else if (!this.mod.config.getController().isGamepad())
|
|
||||||
return option.getDisplayPrefix() + Formatting.GOLD + controllerName;
|
|
||||||
else
|
|
||||||
return option.getDisplayPrefix() + controllerName;
|
|
||||||
}, null);
|
|
||||||
this.secondControllerOption = new SpruceCyclingOption("lambdacontrols.menu.controller2",
|
|
||||||
amount -> {
|
|
||||||
int id = this.mod.config.getSecondController().map(Controller::getId).orElse(-1);
|
|
||||||
id += amount;
|
|
||||||
if (id > GLFW.GLFW_JOYSTICK_LAST)
|
|
||||||
id = -1;
|
|
||||||
this.mod.config.setSecondController(id == -1 ? null : Controller.byId(id));
|
|
||||||
}, option -> this.mod.config.getSecondController().map(controller -> {
|
|
||||||
String controllerName = controller.getName();
|
|
||||||
if (!controller.isConnected())
|
|
||||||
return option.getDisplayPrefix() + Formatting.RED + controllerName;
|
|
||||||
else if (!controller.isGamepad())
|
|
||||||
return option.getDisplayPrefix() + Formatting.GOLD + controllerName;
|
|
||||||
else
|
|
||||||
return option.getDisplayPrefix() + controllerName;
|
|
||||||
}).orElse(option.getDisplayPrefix() + Formatting.RED + I18n.translate("options.off")),
|
|
||||||
new TranslatableText("lambdacontrols.tooltip.controller2"));
|
|
||||||
this.controllerTypeOption = new SpruceCyclingOption("lambdacontrols.menu.controller_type",
|
|
||||||
amount -> this.mod.config.setControllerType(this.mod.config.getControllerType().next()),
|
|
||||||
option -> option.getDisplayPrefix() + this.mod.config.getControllerType().getTranslatedName(),
|
|
||||||
new TranslatableText("lambdacontrols.tooltip.controller_type"));
|
|
||||||
this.deadZoneOption = new SpruceDoubleOption("lambdacontrols.menu.dead_zone", 0.05, 1.0, 0.05F, this.mod.config::getDeadZone,
|
|
||||||
newValue -> {
|
|
||||||
synchronized (this.mod.config) {
|
|
||||||
this.mod.config.setDeadZone(newValue);
|
|
||||||
}
|
|
||||||
}, option -> {
|
|
||||||
String value = String.valueOf(option.get());
|
|
||||||
return option.getDisplayPrefix() + value.substring(0, Math.min(value.length(), 5));
|
|
||||||
}, new TranslatableText("lambdacontrols.tooltip.dead_zone"));
|
|
||||||
this.invertsRightXAxis = new SpruceBooleanOption("lambdacontrols.menu.invert_right_x_axis", this.mod.config::doesInvertRightXAxis,
|
|
||||||
newValue -> {
|
|
||||||
synchronized (this.mod.config) {
|
|
||||||
this.mod.config.setInvertRightXAxis(newValue);
|
|
||||||
}
|
|
||||||
}, null, true);
|
|
||||||
this.invertsRightYAxis = new SpruceBooleanOption("lambdacontrols.menu.invert_right_y_axis", this.mod.config::doesInvertRightYAxis,
|
|
||||||
newValue -> {
|
|
||||||
synchronized (this.mod.config) {
|
|
||||||
this.mod.config.setInvertRightYAxis(newValue);
|
|
||||||
}
|
|
||||||
}, null, true);
|
|
||||||
this.unfocusedInputOption = new SpruceBooleanOption("lambdacontrols.menu.unfocused_input", this.mod.config::hasUnfocusedInput,
|
|
||||||
this.mod.config::setUnfocusedInput, new TranslatableText("lambdacontrols.tooltip.unfocused_input"), true);
|
|
||||||
// HUD options
|
|
||||||
this.hudEnableOption = new SpruceBooleanOption("lambdacontrols.menu.hud_enable", this.mod.config::isHudEnabled,
|
|
||||||
this.mod::setHudEnabled, new TranslatableText("lambdacontrols.tooltip.hud_enable"), true);
|
|
||||||
this.hudSideOption = new SpruceCyclingOption("lambdacontrols.menu.hud_side",
|
|
||||||
amount -> this.mod.config.setHudSide(this.mod.config.getHudSide().next()),
|
|
||||||
option -> option.getDisplayPrefix() + this.mod.config.getHudSide().getTranslatedName(),
|
|
||||||
new TranslatableText("lambdacontrols.tooltip.hud_side"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void removed()
|
|
||||||
{
|
|
||||||
this.mod.config.save();
|
|
||||||
super.removed();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onClose()
|
|
||||||
{
|
|
||||||
this.mod.config.save();
|
|
||||||
super.onClose();
|
|
||||||
}
|
|
||||||
|
|
||||||
private int getTextHeight()
|
|
||||||
{
|
|
||||||
return (5 + this.font.fontHeight) * 3 + 5;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void init()
|
|
||||||
{
|
|
||||||
super.init();
|
|
||||||
int buttonHeight = 20;
|
|
||||||
SpruceButtonWidget controlsModeBtn = new SpruceButtonWidget(this.width / 2 - 155, 18, this.hideControls ? 310 : 150, buttonHeight,
|
|
||||||
I18n.translate("lambdacontrols.menu.controls_mode") + ": " + I18n.translate(this.mod.config.getControlsMode().getTranslationKey()),
|
|
||||||
btn -> {
|
|
||||||
ControlsMode next = this.mod.config.getControlsMode().next();
|
|
||||||
btn.setMessage(I18n.translate("lambdacontrols.menu.controls_mode") + ": " + I18n.translate(next.getTranslationKey()));
|
|
||||||
this.mod.config.setControlsMode(next);
|
|
||||||
this.mod.config.save();
|
|
||||||
|
|
||||||
if (this.minecraft.player != null) {
|
|
||||||
ClientSidePacketRegistry.INSTANCE.sendToServer(LambdaControls.CONTROLS_MODE_CHANNEL, this.mod.makeControlsModeBuffer(next));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
controlsModeBtn.setTooltip(new TranslatableText("lambdacontrols.tooltip.controls_mode"));
|
|
||||||
this.addButton(controlsModeBtn);
|
|
||||||
if (!this.hideControls)
|
|
||||||
this.addButton(new ButtonWidget(this.width / 2 - 155 + 160, 18, 150, buttonHeight, I18n.translate("options.controls"),
|
|
||||||
btn -> {
|
|
||||||
if (this.mod.config.getControlsMode() == ControlsMode.CONTROLLER)
|
|
||||||
this.minecraft.openScreen(new ControllerControlsScreen(this, true));
|
|
||||||
else
|
|
||||||
this.minecraft.openScreen(new ControlsOptionsScreen(this, this.minecraft.options));
|
|
||||||
}));
|
|
||||||
|
|
||||||
this.list = new ButtonListWidget(this.minecraft, this.width, this.height, 43, this.height - 29 - this.getTextHeight(), 25);
|
|
||||||
// General options
|
|
||||||
this.list.addSingleOptionEntry(new SpruceSeparatorOption("lambdacontrols.menu.title.general", true, null));
|
|
||||||
this.list.addOptionEntry(this.rotationSpeedOption, this.mouseSpeedOption);
|
|
||||||
this.list.addSingleOptionEntry(this.autoSwitchModeOption);
|
|
||||||
// Gameplay options
|
|
||||||
this.list.addSingleOptionEntry(new SpruceSeparatorOption("lambdacontrols.menu.title.gameplay", true, null));
|
|
||||||
this.list.addSingleOptionEntry(this.frontBlockPlacingOption);
|
|
||||||
this.list.addSingleOptionEntry(this.flyDriftingOption);
|
|
||||||
this.list.addSingleOptionEntry(this.flyVerticalDriftingOption);
|
|
||||||
// Controller options
|
|
||||||
this.list.addSingleOptionEntry(new SpruceSeparatorOption("lambdacontrols.menu.title.controller", true, null));
|
|
||||||
this.list.addSingleOptionEntry(this.controllerOption);
|
|
||||||
this.list.addSingleOptionEntry(this.secondControllerOption);
|
|
||||||
this.list.addOptionEntry(this.controllerTypeOption, this.deadZoneOption);
|
|
||||||
this.list.addOptionEntry(this.invertsRightXAxis, this.invertsRightYAxis);
|
|
||||||
this.list.addSingleOptionEntry(this.unfocusedInputOption);
|
|
||||||
this.list.addSingleOptionEntry(new ReloadControllerMappingsOption());
|
|
||||||
// HUD options
|
|
||||||
this.list.addSingleOptionEntry(new SpruceSeparatorOption("lambdacontrols.menu.title.hud", true, null));
|
|
||||||
this.list.addOptionEntry(this.hudEnableOption, this.hudSideOption);
|
|
||||||
this.children.add(this.list);
|
|
||||||
|
|
||||||
this.gamepadToolUrlLabel = new SpruceLabelWidget(this.width / 2, this.height - 29 - (5 + this.font.fontHeight) * 2, this.controllerMappingsUrlText, this.width,
|
|
||||||
label -> Util.getOperatingSystem().open(GAMEPAD_TOOL_URL), true);
|
|
||||||
this.gamepadToolUrlLabel.setTooltip(new TranslatableText("chat.link.open"));
|
|
||||||
this.children.add(this.gamepadToolUrlLabel);
|
|
||||||
|
|
||||||
this.addButton(this.resetOption.createButton(this.minecraft.options, this.width / 2 - 155, this.height - 29, 150));
|
|
||||||
this.addButton(new ButtonWidget(this.width / 2 - 155 + 160, this.height - 29, 150, buttonHeight, I18n.translate("gui.done"),
|
|
||||||
(buttonWidget) -> this.minecraft.openScreen(this.parent)));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void render(int mouseX, int mouseY, float delta)
|
|
||||||
{
|
|
||||||
this.renderBackground();
|
|
||||||
this.list.render(mouseX, mouseY, delta);
|
|
||||||
super.render(mouseX, mouseY, delta);
|
|
||||||
this.drawCenteredString(this.font, I18n.translate("lambdacontrols.menu.title"), this.width / 2, 8, 16777215);
|
|
||||||
this.drawCenteredString(this.font, I18n.translate("lambdacontrols.controller.mappings.1", Formatting.GREEN.toString(), Formatting.RESET.toString()), this.width / 2, this.height - 29 - (5 + this.font.fontHeight) * 3, 10526880);
|
|
||||||
this.gamepadToolUrlLabel.render(mouseX, mouseY, delta);
|
|
||||||
this.drawCenteredString(this.font, I18n.translate("lambdacontrols.controller.mappings.3", Formatting.GREEN.toString(), Formatting.RESET.toString()), this.width / 2, this.height - 29 - (5 + this.font.fontHeight), 10526880);
|
|
||||||
|
|
||||||
Tooltip.renderAll();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2020 LambdAurora <aurora42lambda@gmail.com>
|
|
||||||
*
|
|
||||||
* This file is part of LambdaControls.
|
|
||||||
*
|
|
||||||
* Licensed under the MIT license. For more information,
|
|
||||||
* see the LICENSE file.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package me.lambdaurora.lambdacontrols.client.gui;
|
|
||||||
|
|
||||||
import me.lambdaurora.lambdacontrols.client.controller.Controller;
|
|
||||||
import me.lambdaurora.spruceui.SpruceButtonWidget;
|
|
||||||
import net.minecraft.client.MinecraftClient;
|
|
||||||
import net.minecraft.client.gui.widget.AbstractButtonWidget;
|
|
||||||
import net.minecraft.client.options.GameOptions;
|
|
||||||
import net.minecraft.client.options.Option;
|
|
||||||
import net.minecraft.client.resource.language.I18n;
|
|
||||||
import net.minecraft.client.toast.SystemToast;
|
|
||||||
import net.minecraft.text.TranslatableText;
|
|
||||||
import org.aperlambda.lambdacommon.utils.Nameable;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents the option to reload the controller mappings.
|
|
||||||
*/
|
|
||||||
public class ReloadControllerMappingsOption extends Option implements Nameable
|
|
||||||
{
|
|
||||||
private static final String KEY = "lambdacontrols.menu.reload_controller_mappings";
|
|
||||||
|
|
||||||
public ReloadControllerMappingsOption()
|
|
||||||
{
|
|
||||||
super(KEY);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public AbstractButtonWidget createButton(GameOptions options, int x, int y, int width)
|
|
||||||
{
|
|
||||||
SpruceButtonWidget button = new SpruceButtonWidget(x, y, width, 20, this.getName(), btn -> {
|
|
||||||
MinecraftClient client = MinecraftClient.getInstance();
|
|
||||||
Controller.updateMappings();
|
|
||||||
if (client.currentScreen != null)
|
|
||||||
client.currentScreen.init(client, client.getWindow().getScaledWidth(), client.getWindow().getScaledHeight());
|
|
||||||
client.getToastManager().add(new SystemToast(SystemToast.Type.TUTORIAL_HINT, new TranslatableText("lambdacontrols.controller.mappings.updated"), null));
|
|
||||||
});
|
|
||||||
button.setTooltip(new TranslatableText("lambdacontrols.tooltip.reload_controller_mappings"));
|
|
||||||
return button;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull String getName()
|
|
||||||
{
|
|
||||||
return I18n.translate(KEY);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2020 LambdAurora <aurora42lambda@gmail.com>
|
|
||||||
*
|
|
||||||
* This file is part of LambdaControls.
|
|
||||||
*
|
|
||||||
* Licensed under the MIT license. For more information,
|
|
||||||
* see the LICENSE file.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package me.lambdaurora.lambdacontrols.client.mixin;
|
|
||||||
|
|
||||||
import net.minecraft.client.gui.widget.AbstractButtonWidget;
|
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
|
||||||
import org.spongepowered.asm.mixin.gen.Accessor;
|
|
||||||
|
|
||||||
@Mixin(AbstractButtonWidget.class)
|
|
||||||
public interface AbstractButtonWidgetAccessor
|
|
||||||
{
|
|
||||||
@Accessor("height")
|
|
||||||
int lambdacontrols_getHeight();
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2020 LambdAurora <aurora42lambda@gmail.com>
|
|
||||||
*
|
|
||||||
* This file is part of LambdaControls.
|
|
||||||
*
|
|
||||||
* Licensed under the MIT license. For more information,
|
|
||||||
* see the LICENSE file.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package me.lambdaurora.lambdacontrols.client.mixin;
|
|
||||||
|
|
||||||
import me.lambdaurora.lambdacontrols.LambdaControls;
|
|
||||||
import me.lambdaurora.lambdacontrols.client.LambdaControlsClient;
|
|
||||||
import net.fabricmc.fabric.api.network.ClientSidePacketRegistry;
|
|
||||||
import net.minecraft.client.network.ClientPlayNetworkHandler;
|
|
||||||
import net.minecraft.client.network.packet.GameJoinS2CPacket;
|
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
|
||||||
import org.spongepowered.asm.mixin.injection.At;
|
|
||||||
import org.spongepowered.asm.mixin.injection.Inject;
|
|
||||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
|
||||||
|
|
||||||
@Mixin(ClientPlayNetworkHandler.class)
|
|
||||||
public class ClientPlayNetworkHandlerMixin
|
|
||||||
{
|
|
||||||
@Inject(method = "onGameJoin", at = @At(value = "TAIL"))
|
|
||||||
private void lambdacontrols_onConnect(GameJoinS2CPacket packet, CallbackInfo ci)
|
|
||||||
{
|
|
||||||
ClientSidePacketRegistry.INSTANCE.sendToServer(LambdaControls.HELLO_CHANNEL, LambdaControls.get().makeHello(LambdaControlsClient.get().config.getControlsMode()));
|
|
||||||
ClientSidePacketRegistry.INSTANCE.sendToServer(LambdaControls.CONTROLS_MODE_CHANNEL,
|
|
||||||
LambdaControls.get().makeControlsModeBuffer(LambdaControlsClient.get().config.getControlsMode()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2020 LambdAurora <aurora42lambda@gmail.com>
|
|
||||||
*
|
|
||||||
* This file is part of LambdaControls.
|
|
||||||
*
|
|
||||||
* Licensed under the MIT license. For more information,
|
|
||||||
* see the LICENSE file.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package me.lambdaurora.lambdacontrols.client.mixin;
|
|
||||||
|
|
||||||
import me.lambdaurora.lambdacontrols.ControlsMode;
|
|
||||||
import me.lambdaurora.lambdacontrols.client.LambdaControlsClient;
|
|
||||||
import me.lambdaurora.lambdacontrols.client.util.ContainerScreenAccessor;
|
|
||||||
import net.minecraft.client.MinecraftClient;
|
|
||||||
import net.minecraft.client.gui.screen.ingame.ContainerScreen;
|
|
||||||
import net.minecraft.container.Slot;
|
|
||||||
import org.lwjgl.glfw.GLFW;
|
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
|
||||||
import org.spongepowered.asm.mixin.gen.Invoker;
|
|
||||||
import org.spongepowered.asm.mixin.injection.At;
|
|
||||||
import org.spongepowered.asm.mixin.injection.Inject;
|
|
||||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents the mixin for the class ContainerScreen.
|
|
||||||
*/
|
|
||||||
@Mixin(ContainerScreen.class)
|
|
||||||
public abstract class ContainerScreenMixin implements ContainerScreenAccessor
|
|
||||||
{
|
|
||||||
protected int x;
|
|
||||||
protected int y;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int lambdacontrols_getX()
|
|
||||||
{
|
|
||||||
return this.x;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int lambdacontrols_getY()
|
|
||||||
{
|
|
||||||
return this.y;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Invoker("getSlotAt")
|
|
||||||
public abstract Slot lambdacontrols_getSlotAt(double posX, double posY);
|
|
||||||
|
|
||||||
@Inject(method = "render", at = @At("RETURN"))
|
|
||||||
public void render(int mouseX, int mouseY, float delta, CallbackInfo ci)
|
|
||||||
{
|
|
||||||
if (LambdaControlsClient.get().config.getControlsMode() == ControlsMode.CONTROLLER) {
|
|
||||||
MinecraftClient client = MinecraftClient.getInstance();
|
|
||||||
int x = 10, y = client.getWindow().getScaledHeight() - 10 - 15;
|
|
||||||
|
|
||||||
x += LambdaControlsClient.drawButtonTip(x, y, new int[]{GLFW.GLFW_GAMEPAD_BUTTON_A}, "lambdacontrols.action.pickup_all", true, client) + 10;
|
|
||||||
x += LambdaControlsClient.drawButtonTip(x, y, new int[]{GLFW.GLFW_GAMEPAD_BUTTON_B}, "lambdacontrols.action.exit", true, client) + 10;
|
|
||||||
x += LambdaControlsClient.drawButtonTip(x, y, new int[]{GLFW.GLFW_GAMEPAD_BUTTON_X}, "lambdacontrols.action.pickup", true, client) + 10;
|
|
||||||
LambdaControlsClient.drawButtonTip(x, y, new int[]{GLFW.GLFW_GAMEPAD_BUTTON_Y}, "lambdacontrols.action.quick_move", true, client);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2020 LambdAurora <aurora42lambda@gmail.com>
|
|
||||||
*
|
|
||||||
* This file is part of LambdaControls.
|
|
||||||
*
|
|
||||||
* Licensed under the MIT license. For more information,
|
|
||||||
* see the LICENSE file.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package me.lambdaurora.lambdacontrols.client.mixin;
|
|
||||||
|
|
||||||
import me.lambdaurora.lambdacontrols.client.gui.ControllerControlsScreen;
|
|
||||||
import me.lambdaurora.lambdacontrols.client.gui.LambdaControlsSettingsScreen;
|
|
||||||
import net.minecraft.client.gui.screen.Screen;
|
|
||||||
import net.minecraft.client.gui.screen.options.ControlsOptionsScreen;
|
|
||||||
import net.minecraft.client.gui.screen.options.GameOptionsScreen;
|
|
||||||
import net.minecraft.client.gui.widget.AbstractButtonWidget;
|
|
||||||
import net.minecraft.client.gui.widget.ButtonWidget;
|
|
||||||
import net.minecraft.client.options.GameOptions;
|
|
||||||
import net.minecraft.client.resource.language.I18n;
|
|
||||||
import net.minecraft.text.Text;
|
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
|
||||||
import org.spongepowered.asm.mixin.injection.At;
|
|
||||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Injects the new controls settings button.
|
|
||||||
*/
|
|
||||||
@Mixin(ControlsOptionsScreen.class)
|
|
||||||
public class ControlsOptionsScreenMixin extends GameOptionsScreen
|
|
||||||
{
|
|
||||||
public ControlsOptionsScreenMixin(Screen parent, GameOptions gameOptions, Text text)
|
|
||||||
{
|
|
||||||
super(parent, gameOptions, text);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Redirect(method = "init", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screen/options/ControlsOptionsScreen;addButton(Lnet/minecraft/client/gui/widget/AbstractButtonWidget;)Lnet/minecraft/client/gui/widget/AbstractButtonWidget;", ordinal = 1))
|
|
||||||
private AbstractButtonWidget on_init(ControlsOptionsScreen screen, AbstractButtonWidget btn)
|
|
||||||
{
|
|
||||||
if (this.parent instanceof ControllerControlsScreen)
|
|
||||||
return this.addButton(btn);
|
|
||||||
else
|
|
||||||
return this.addButton(new ButtonWidget(btn.x, btn.y, btn.getWidth(), ((AbstractButtonWidgetAccessor) btn).lambdacontrols_getHeight(), I18n.translate("menu.options"),
|
|
||||||
b -> this.minecraft.openScreen(new LambdaControlsSettingsScreen(this, this.gameOptions, true))));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,121 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2020 LambdAurora <aurora42lambda@gmail.com>
|
|
||||||
*
|
|
||||||
* This file is part of LambdaControls.
|
|
||||||
*
|
|
||||||
* Licensed under the MIT license. For more information,
|
|
||||||
* see the LICENSE file.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package me.lambdaurora.lambdacontrols.client.mixin;
|
|
||||||
|
|
||||||
import me.lambdaurora.lambdacontrols.LambdaControlsFeature;
|
|
||||||
import me.lambdaurora.lambdacontrols.client.LambdaControlsClient;
|
|
||||||
import net.minecraft.block.BlockState;
|
|
||||||
import net.minecraft.block.FluidBlock;
|
|
||||||
import net.minecraft.client.MinecraftClient;
|
|
||||||
import net.minecraft.client.gui.screen.Screen;
|
|
||||||
import net.minecraft.client.network.ClientPlayerEntity;
|
|
||||||
import net.minecraft.client.network.ClientPlayerInteractionManager;
|
|
||||||
import net.minecraft.client.render.GameRenderer;
|
|
||||||
import net.minecraft.client.world.ClientWorld;
|
|
||||||
import net.minecraft.item.BlockItem;
|
|
||||||
import net.minecraft.item.ItemStack;
|
|
||||||
import net.minecraft.util.ActionResult;
|
|
||||||
import net.minecraft.util.Hand;
|
|
||||||
import net.minecraft.util.hit.BlockHitResult;
|
|
||||||
import net.minecraft.util.hit.HitResult;
|
|
||||||
import net.minecraft.util.math.BlockPos;
|
|
||||||
import net.minecraft.util.math.Direction;
|
|
||||||
import net.minecraft.util.math.MathHelper;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
import org.spongepowered.asm.mixin.Final;
|
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
|
||||||
import org.spongepowered.asm.mixin.Shadow;
|
|
||||||
import org.spongepowered.asm.mixin.injection.At;
|
|
||||||
import org.spongepowered.asm.mixin.injection.Inject;
|
|
||||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
|
||||||
import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
|
|
||||||
|
|
||||||
@Mixin(MinecraftClient.class)
|
|
||||||
public abstract class MinecraftClientMixin
|
|
||||||
{
|
|
||||||
@Shadow
|
|
||||||
@Nullable
|
|
||||||
public HitResult crosshairTarget;
|
|
||||||
|
|
||||||
@Shadow
|
|
||||||
@Nullable
|
|
||||||
public ClientPlayerEntity player;
|
|
||||||
|
|
||||||
@Shadow
|
|
||||||
@Nullable
|
|
||||||
public ClientPlayerInteractionManager interactionManager;
|
|
||||||
|
|
||||||
@Shadow
|
|
||||||
@Nullable
|
|
||||||
public ClientWorld world;
|
|
||||||
|
|
||||||
@Shadow
|
|
||||||
@Final
|
|
||||||
public GameRenderer gameRenderer;
|
|
||||||
|
|
||||||
@Inject(method = "<init>", at = @At("RETURN"))
|
|
||||||
private void lambdacontrols_onInit(CallbackInfo ci)
|
|
||||||
{
|
|
||||||
LambdaControlsClient.get().onMcInit((MinecraftClient) (Object) this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Inject(method = "render", at = @At("HEAD"))
|
|
||||||
private void lambdacontrols_onRender(boolean fullRender, CallbackInfo ci)
|
|
||||||
{
|
|
||||||
LambdaControlsClient.get().onRender((MinecraftClient) (Object) (this));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Inject(method = "disconnect(Lnet/minecraft/client/gui/screen/Screen;)V", at = @At("RETURN"))
|
|
||||||
private void lambdacontrols_onLeave(@Nullable Screen screen, CallbackInfo ci)
|
|
||||||
{
|
|
||||||
LambdaControlsClient.get().onLeave();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Inject(method = "doItemUse()V", at = @At(value = "INVOKE", target = "Lnet/minecraft/util/hit/HitResult;getType()Lnet/minecraft/util/hit/HitResult$Type;"), locals = LocalCapture.CAPTURE_FAILEXCEPTION, cancellable = true)
|
|
||||||
private void lambdacontrols_onItemUse(CallbackInfo ci, Hand[] hands, int handCount, int handIndex, Hand hand, ItemStack stackInHand)
|
|
||||||
{
|
|
||||||
if (!stackInHand.isEmpty() && this.player.pitch > 35.0F && LambdaControlsFeature.FRONT_BLOCK_PLACING.isAvailable()) {
|
|
||||||
if (this.crosshairTarget != null && this.crosshairTarget.getType() == HitResult.Type.MISS && this.player.onGround) {
|
|
||||||
if (!stackInHand.isEmpty() && stackInHand.getItem() instanceof BlockItem) {
|
|
||||||
BlockPos playerPos = this.player.getBlockPos().down();
|
|
||||||
BlockPos targetPos = new BlockPos(this.crosshairTarget.getPos()).subtract(playerPos);
|
|
||||||
BlockPos vector = new BlockPos(MathHelper.clamp(targetPos.getX(), -1, 1), 0, MathHelper.clamp(targetPos.getZ(), -1, 1));
|
|
||||||
BlockPos blockPos = playerPos.add(vector);
|
|
||||||
|
|
||||||
Direction direction = player.getHorizontalFacing();
|
|
||||||
|
|
||||||
BlockState adjacentBlockState = this.world.getBlockState(blockPos.offset(direction.getOpposite()));
|
|
||||||
if (adjacentBlockState.isAir() || adjacentBlockState.getBlock() instanceof FluidBlock || (vector.getX() == 0 && vector.getZ() == 0)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
BlockHitResult hitResult = new BlockHitResult(this.crosshairTarget.getPos(), direction.getOpposite(), blockPos, false);
|
|
||||||
|
|
||||||
int previousStackCount = stackInHand.getCount();
|
|
||||||
ActionResult result = this.interactionManager.interactBlock(this.player, this.world, hand, hitResult);
|
|
||||||
if (result.isAccepted()) {
|
|
||||||
if (result.shouldSwingHand()) {
|
|
||||||
this.player.swingHand(hand);
|
|
||||||
if (!stackInHand.isEmpty() && (stackInHand.getCount() != previousStackCount || this.interactionManager.hasCreativeInventory())) {
|
|
||||||
this.gameRenderer.firstPersonRenderer.resetEquipProgress(hand);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ci.cancel();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result == ActionResult.FAIL) {
|
|
||||||
ci.cancel();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2020 LambdAurora <aurora42lambda@gmail.com>
|
|
||||||
*
|
|
||||||
* This file is part of LambdaControls.
|
|
||||||
*
|
|
||||||
* Licensed under the MIT license. For more information,
|
|
||||||
* see the LICENSE file.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package me.lambdaurora.lambdacontrols.client.mixin;
|
|
||||||
|
|
||||||
import me.lambdaurora.lambdacontrols.ControlsMode;
|
|
||||||
import me.lambdaurora.lambdacontrols.client.LambdaControlsClient;
|
|
||||||
import me.lambdaurora.lambdacontrols.client.util.MouseAccessor;
|
|
||||||
import net.minecraft.client.Mouse;
|
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
|
||||||
import org.spongepowered.asm.mixin.Shadow;
|
|
||||||
import org.spongepowered.asm.mixin.injection.At;
|
|
||||||
import org.spongepowered.asm.mixin.injection.Inject;
|
|
||||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds extra access to the mouse.
|
|
||||||
*/
|
|
||||||
@Mixin(Mouse.class)
|
|
||||||
public abstract class MouseMixin implements MouseAccessor
|
|
||||||
{
|
|
||||||
@Shadow
|
|
||||||
protected abstract void onCursorPos(long window, double x, double y);
|
|
||||||
|
|
||||||
@Shadow
|
|
||||||
protected abstract void onMouseButton(long window, int button, int action, int mods);
|
|
||||||
|
|
||||||
@Inject(method = "lockCursor", at = @At("HEAD"), cancellable = true)
|
|
||||||
private void lambdacontrols_onMouseLocked(CallbackInfo ci)
|
|
||||||
{
|
|
||||||
if (LambdaControlsClient.get().config.getControlsMode() == ControlsMode.TOUCHSCREEN)
|
|
||||||
ci.cancel();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void lambdacontrols_onCursorPos(long window, double x, double y)
|
|
||||||
{
|
|
||||||
this.onCursorPos(window, x, y);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2020 LambdAurora <aurora42lambda@gmail.com>
|
|
||||||
*
|
|
||||||
* This file is part of LambdaControls.
|
|
||||||
*
|
|
||||||
* Licensed under the MIT license. For more information,
|
|
||||||
* see the LICENSE file.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package me.lambdaurora.lambdacontrols.client.mixin;
|
|
||||||
|
|
||||||
import me.lambdaurora.lambdacontrols.ControlsMode;
|
|
||||||
import me.lambdaurora.lambdacontrols.client.LambdaControlsClient;
|
|
||||||
import me.lambdaurora.lambdacontrols.client.gui.ControllerControlsScreen;
|
|
||||||
import net.minecraft.client.gui.screen.Screen;
|
|
||||||
import net.minecraft.client.gui.screen.SettingsScreen;
|
|
||||||
import net.minecraft.client.gui.widget.AbstractButtonWidget;
|
|
||||||
import net.minecraft.client.gui.widget.ButtonWidget;
|
|
||||||
import net.minecraft.text.Text;
|
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
|
||||||
import org.spongepowered.asm.mixin.injection.At;
|
|
||||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Injects the new controls settings button.
|
|
||||||
*/
|
|
||||||
@Mixin(SettingsScreen.class)
|
|
||||||
public class SettingsScreenMixin extends Screen
|
|
||||||
{
|
|
||||||
protected SettingsScreenMixin(Text title)
|
|
||||||
{
|
|
||||||
super(title);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Redirect(method = "init", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screen/SettingsScreen;addButton(Lnet/minecraft/client/gui/widget/AbstractButtonWidget;)Lnet/minecraft/client/gui/widget/AbstractButtonWidget;", ordinal = 7))
|
|
||||||
private AbstractButtonWidget lambdacontrols_onInit(SettingsScreen screen, AbstractButtonWidget btn)
|
|
||||||
{
|
|
||||||
if (LambdaControlsClient.get().config.getControlsMode() == ControlsMode.CONTROLLER) {
|
|
||||||
return this.addButton(new ButtonWidget(btn.x, btn.y, btn.getWidth(), ((AbstractButtonWidgetAccessor) btn).lambdacontrols_getHeight(), btn.getMessage(),
|
|
||||||
b -> this.minecraft.openScreen(new ControllerControlsScreen(this, false))));
|
|
||||||
} else {
|
|
||||||
return this.addButton(btn);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2020 LambdAurora <aurora42lambda@gmail.com>
|
|
||||||
*
|
|
||||||
* This file is part of LambdaControls.
|
|
||||||
*
|
|
||||||
* Licensed under the MIT license. For more information,
|
|
||||||
* see the LICENSE file.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package me.lambdaurora.lambdacontrols.client.util;
|
|
||||||
|
|
||||||
import net.minecraft.container.Slot;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents an accessor to AbstractContainerScreen.
|
|
||||||
*/
|
|
||||||
public interface ContainerScreenAccessor
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Gets the left coordinate of the GUI.
|
|
||||||
*
|
|
||||||
* @return The left coordinate of the GUI.
|
|
||||||
*/
|
|
||||||
int lambdacontrols_getX();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the top coordinate of the GUI.
|
|
||||||
*
|
|
||||||
* @return The top coordinate of the GUI.
|
|
||||||
*/
|
|
||||||
int lambdacontrols_getY();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the slot at position.
|
|
||||||
*
|
|
||||||
* @param pos_x The X position to check.
|
|
||||||
* @param pos_y The Y position to check.
|
|
||||||
* @return The slot at the specified position.
|
|
||||||
*/
|
|
||||||
Slot lambdacontrols_getSlotAt(double pos_x, double pos_y);
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2020 LambdAurora <aurora42lambda@gmail.com>
|
|
||||||
*
|
|
||||||
* This file is part of LambdaControls.
|
|
||||||
*
|
|
||||||
* Licensed under the MIT license. For more information,
|
|
||||||
* see the LICENSE file.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package me.lambdaurora.lambdacontrols.client.util;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents a Minecraft keybinding with extra access.
|
|
||||||
*/
|
|
||||||
public interface KeyBindingAccessor
|
|
||||||
{
|
|
||||||
boolean lambdacontrols_press();
|
|
||||||
|
|
||||||
boolean lambdacontrols_unpress();
|
|
||||||
|
|
||||||
default boolean lambdacontrols_handlePressState(boolean pressed)
|
|
||||||
{
|
|
||||||
if (pressed)
|
|
||||||
return this.lambdacontrols_press();
|
|
||||||
else
|
|
||||||
return this.lambdacontrols_unpress();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2020 LambdAurora <aurora42lambda@gmail.com>
|
|
||||||
*
|
|
||||||
* This file is part of LambdaControls.
|
|
||||||
*
|
|
||||||
* Licensed under the MIT license. For more information,
|
|
||||||
* see the LICENSE file.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package me.lambdaurora.lambdacontrols.client.util;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents mouse's extra access.
|
|
||||||
*/
|
|
||||||
public interface MouseAccessor
|
|
||||||
{
|
|
||||||
void lambdacontrols_onCursorPos(long window, double x, double y);
|
|
||||||
}
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 5.8 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 8.6 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 13 KiB |
@@ -1,48 +0,0 @@
|
|||||||
{
|
|
||||||
"schemaVersion": 1,
|
|
||||||
"id": "lambdacontrols",
|
|
||||||
"name": "LambdaControls",
|
|
||||||
"version": "${version}",
|
|
||||||
"description": "Adds better controls: controller and touchscreen support.",
|
|
||||||
"authors": [
|
|
||||||
"LambdAurora"
|
|
||||||
],
|
|
||||||
"contact": {
|
|
||||||
"homepage": "https://www.curseforge.com/minecraft/mc-mods/lambdacontrols",
|
|
||||||
"sources": "https://github.com/LambdAurora/LambdaControls.git",
|
|
||||||
"issues": "https://github.com/LambdAurora/LambdaControls/issues"
|
|
||||||
},
|
|
||||||
"license": "MIT",
|
|
||||||
"icon": "assets/lambdacontrols/icon.png",
|
|
||||||
"environment": "client",
|
|
||||||
"entrypoints": {
|
|
||||||
"main": [
|
|
||||||
"me.lambdaurora.lambdacontrols.LambdaControls"
|
|
||||||
],
|
|
||||||
"client": [
|
|
||||||
"me.lambdaurora.lambdacontrols.client.LambdaControlsClient"
|
|
||||||
],
|
|
||||||
"modmenu": [
|
|
||||||
"me.lambdaurora.lambdacontrols.client.LambdaControlsModMenu"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"mixins": [
|
|
||||||
"lambdacontrols.mixins.json"
|
|
||||||
],
|
|
||||||
"depends": {
|
|
||||||
"fabricloader": ">=0.4.0",
|
|
||||||
"fabric": "*",
|
|
||||||
"minecraft": "1.15.x",
|
|
||||||
"spruceui": ">=1.3.4"
|
|
||||||
},
|
|
||||||
"recommends": {
|
|
||||||
"modmenu": ">=1.8.0+build.16",
|
|
||||||
"okzoomer": ">=1.0.4"
|
|
||||||
},
|
|
||||||
"suggests": {
|
|
||||||
"flamingo": "*"
|
|
||||||
},
|
|
||||||
"custom": {
|
|
||||||
"modmenu:clientsideOnly": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,18 +2,19 @@
|
|||||||
org.gradle.jvmargs=-Xmx1G
|
org.gradle.jvmargs=-Xmx1G
|
||||||
|
|
||||||
# Fabric Properties
|
# Fabric Properties
|
||||||
# check these on https://fabricmc.net/use
|
# check these on https://fabricmc.net/use
|
||||||
minecraft_version=1.15.2
|
minecraft_version=1.17
|
||||||
yarn_mappings=1.15.2+build.9:v2
|
yarn_mappings=1.17+build.13
|
||||||
loader_version=0.7.6+build.180
|
loader_version=0.11.6
|
||||||
|
|
||||||
# Mod Properties
|
# Mod Properties
|
||||||
mod_version = 1.1.0
|
mod_version = 1.7.1
|
||||||
maven_group = me.lambdaurora
|
maven_group = dev.lambdaurora
|
||||||
archives_base_name = lambdacontrols
|
archives_base_name = lambdacontrols
|
||||||
|
modrinth_id=W1D3UXEc
|
||||||
|
|
||||||
# Dependencies
|
# Dependencies
|
||||||
# currently not on the main fabric site, check on the maven: https://maven.fabricmc.net/net/fabricmc/fabric-api/fabric-api
|
# currently not on the main fabric site, check on the maven: https://maven.fabricmc.net/net/fabricmc/fabric-api/fabric-api
|
||||||
fabric_version=0.4.29+build.290-1.15
|
fabric_version=0.36.0+1.17
|
||||||
spruceui_version=1.3.4
|
spruceui_version=3.2.0+1.17
|
||||||
|
modmenu_version=2.0.2
|
||||||
|
|||||||
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
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-7.1-bin.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.5.1-all.zip
|
|
||||||
|
|||||||
35
gradlew
vendored
Normal file → Executable file
35
gradlew
vendored
Normal file → Executable file
@@ -82,6 +82,7 @@ esac
|
|||||||
|
|
||||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||||
|
|
||||||
|
|
||||||
# Determine the Java command to use to start the JVM.
|
# Determine the Java command to use to start the JVM.
|
||||||
if [ -n "$JAVA_HOME" ] ; then
|
if [ -n "$JAVA_HOME" ] ; then
|
||||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||||
@@ -125,10 +126,11 @@ if $darwin; then
|
|||||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# For Cygwin, switch paths to Windows format before running java
|
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||||
if $cygwin ; then
|
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||||
|
|
||||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||||
|
|
||||||
# We build the pattern for arguments to be converted via cygpath
|
# We build the pattern for arguments to be converted via cygpath
|
||||||
@@ -154,19 +156,19 @@ if $cygwin ; then
|
|||||||
else
|
else
|
||||||
eval `echo args$i`="\"$arg\""
|
eval `echo args$i`="\"$arg\""
|
||||||
fi
|
fi
|
||||||
i=$((i+1))
|
i=`expr $i + 1`
|
||||||
done
|
done
|
||||||
case $i in
|
case $i in
|
||||||
(0) set -- ;;
|
0) set -- ;;
|
||||||
(1) set -- "$args0" ;;
|
1) set -- "$args0" ;;
|
||||||
(2) set -- "$args0" "$args1" ;;
|
2) set -- "$args0" "$args1" ;;
|
||||||
(3) set -- "$args0" "$args1" "$args2" ;;
|
3) set -- "$args0" "$args1" "$args2" ;;
|
||||||
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||||
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||||
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||||
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||||
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||||
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||||
esac
|
esac
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -175,14 +177,9 @@ save () {
|
|||||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||||
echo " "
|
echo " "
|
||||||
}
|
}
|
||||||
APP_ARGS=$(save "$@")
|
APP_ARGS=`save "$@"`
|
||||||
|
|
||||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
# 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"
|
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||||
|
|
||||||
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
|
|
||||||
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
|
|
||||||
cd "$(dirname "$0")"
|
|
||||||
fi
|
|
||||||
|
|
||||||
exec "$JAVACMD" "$@"
|
exec "$JAVACMD" "$@"
|
||||||
|
|||||||
25
gradlew.bat
vendored
25
gradlew.bat
vendored
@@ -29,6 +29,9 @@ if "%DIRNAME%" == "" set DIRNAME=.
|
|||||||
set APP_BASE_NAME=%~n0
|
set APP_BASE_NAME=%~n0
|
||||||
set APP_HOME=%DIRNAME%
|
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.
|
@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"
|
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||||
|
|
||||||
@@ -37,7 +40,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome
|
|||||||
|
|
||||||
set JAVA_EXE=java.exe
|
set JAVA_EXE=java.exe
|
||||||
%JAVA_EXE% -version >NUL 2>&1
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
if "%ERRORLEVEL%" == "0" goto init
|
if "%ERRORLEVEL%" == "0" goto execute
|
||||||
|
|
||||||
echo.
|
echo.
|
||||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
@@ -51,7 +54,7 @@ goto fail
|
|||||||
set JAVA_HOME=%JAVA_HOME:"=%
|
set JAVA_HOME=%JAVA_HOME:"=%
|
||||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||||
|
|
||||||
if exist "%JAVA_EXE%" goto init
|
if exist "%JAVA_EXE%" goto execute
|
||||||
|
|
||||||
echo.
|
echo.
|
||||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||||
@@ -61,28 +64,14 @@ echo location of your Java installation.
|
|||||||
|
|
||||||
goto fail
|
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
|
:execute
|
||||||
@rem Setup the command line
|
@rem Setup the command line
|
||||||
|
|
||||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||||
|
|
||||||
|
|
||||||
@rem Execute Gradle
|
@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%
|
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||||
|
|
||||||
:end
|
:end
|
||||||
@rem End local scope for the variables with windows NT shell
|
@rem End local scope for the variables with windows NT shell
|
||||||
|
|||||||
BIN
images/controller_controls.png
Normal file
BIN
images/controller_controls.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 57 KiB |
BIN
images/controller_options.png
Normal file
BIN
images/controller_options.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 63 KiB |
@@ -1,13 +1,11 @@
|
|||||||
pluginManagement {
|
pluginManagement {
|
||||||
repositories {
|
repositories {
|
||||||
jcenter()
|
|
||||||
maven {
|
maven {
|
||||||
name = 'Fabric'
|
name 'Fabric'
|
||||||
url = 'https://maven.fabricmc.net/'
|
url 'https://maven.fabricmc.net/'
|
||||||
}
|
}
|
||||||
gradlePluginPortal()
|
gradlePluginPortal()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
rootProject.name = 'lambdacontrols'
|
rootProject.name = 'lambdacontrols'
|
||||||
include 'core', 'fabric', 'spigot'
|
|
||||||
|
|||||||
@@ -1,50 +0,0 @@
|
|||||||
plugins {
|
|
||||||
id 'java-library'
|
|
||||||
}
|
|
||||||
|
|
||||||
archivesBaseName = project.archives_base_name + "-spigot"
|
|
||||||
|
|
||||||
repositories {
|
|
||||||
maven { url = 'https://hub.spigotmc.org/nexus/content/groups/public/' }
|
|
||||||
maven { url = 'https://libraries.minecraft.net/' }
|
|
||||||
}
|
|
||||||
|
|
||||||
configurations {
|
|
||||||
include
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
api project(":core")
|
|
||||||
include(project(":core")) {
|
|
||||||
exclude group: 'com.google.code.gson'
|
|
||||||
exclude group: 'com.google.guava'
|
|
||||||
}
|
|
||||||
api 'org.spigotmc:spigot-api:1.15.1-R0.1-SNAPSHOT'
|
|
||||||
api 'io.netty:netty-all:4.1.28.Final'
|
|
||||||
}
|
|
||||||
|
|
||||||
processResources {
|
|
||||||
inputs.property "version", project.version
|
|
||||||
|
|
||||||
from(sourceSets.main.resources.srcDirs) {
|
|
||||||
include "plugin.yml"
|
|
||||||
|
|
||||||
expand 'version': project.version.toString().replace("#", "")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
jar {
|
|
||||||
from '../LICENSE'
|
|
||||||
|
|
||||||
dependsOn configurations.include
|
|
||||||
from {
|
|
||||||
(configurations.include).collect {
|
|
||||||
it.isDirectory() ? it : zipTree(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
java {
|
|
||||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
|
||||||
targetCompatibility = JavaVersion.VERSION_1_8
|
|
||||||
}
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2020 LambdAurora <aurora42lambda@gmail.com>
|
|
||||||
*
|
|
||||||
* This file is part of LambdaControls.
|
|
||||||
*
|
|
||||||
* Licensed under the MIT license. For more information,
|
|
||||||
* see the LICENSE file.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package me.lambdaurora.lambdacontrols;
|
|
||||||
|
|
||||||
import com.electronwill.nightconfig.core.file.FileConfig;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents the LambdaControls Spigot configuration.
|
|
||||||
*/
|
|
||||||
public class LambdaControlsConfig
|
|
||||||
{
|
|
||||||
private static final boolean DEFAULT_FRONT_BLOCK_PLACING = true;
|
|
||||||
|
|
||||||
protected final FileConfig config = FileConfig.builder("config/lambdacontrols.toml").concurrent().defaultResource("/server_config.toml").build();
|
|
||||||
private final LambdaControlsSpigot plugin;
|
|
||||||
|
|
||||||
public LambdaControlsConfig(@NotNull LambdaControlsSpigot plugin)
|
|
||||||
{
|
|
||||||
this.plugin = plugin;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void load()
|
|
||||||
{
|
|
||||||
File configDir = new File("config/");
|
|
||||||
if (!configDir.exists())
|
|
||||||
configDir.mkdirs();
|
|
||||||
this.config.load();
|
|
||||||
this.plugin.log("Configuration loaded.");
|
|
||||||
LambdaControlsFeature.FRONT_BLOCK_PLACING.setAllowed(this.config.getOrElse("gameplay.front_block_placing", DEFAULT_FRONT_BLOCK_PLACING));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,148 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2020 LambdAurora <aurora42lambda@gmail.com>
|
|
||||||
*
|
|
||||||
* This file is part of LambdaControls.
|
|
||||||
*
|
|
||||||
* Licensed under the MIT license. For more information,
|
|
||||||
* see the LICENSE file.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package me.lambdaurora.lambdacontrols;
|
|
||||||
|
|
||||||
import io.netty.buffer.Unpooled;
|
|
||||||
import me.lambdaurora.lambdacontrols.event.PlayerChangeControlsModeEvent;
|
|
||||||
import org.bukkit.entity.Player;
|
|
||||||
import org.bukkit.event.EventHandler;
|
|
||||||
import org.bukkit.event.Listener;
|
|
||||||
import org.bukkit.event.player.PlayerJoinEvent;
|
|
||||||
import org.bukkit.event.player.PlayerQuitEvent;
|
|
||||||
import org.bukkit.plugin.java.JavaPlugin;
|
|
||||||
import org.bukkit.plugin.messaging.PluginMessageListener;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
import static me.lambdaurora.lambdacontrols.LambdaControlsConstants.*;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents the LambdaControls spigot plugin which provides extra features for servers.
|
|
||||||
*
|
|
||||||
* @author LambdAurora
|
|
||||||
* @version 1.1.0
|
|
||||||
* @since 1.1.0
|
|
||||||
*/
|
|
||||||
public class LambdaControlsSpigot extends JavaPlugin implements PluginMessageListener, Listener
|
|
||||||
{
|
|
||||||
private static final Map<Player, ControlsMode> PLAYERS_CONTROLS_MODE = new HashMap<>();
|
|
||||||
public final LambdaControlsConfig config = new LambdaControlsConfig(this);
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onEnable()
|
|
||||||
{
|
|
||||||
super.onEnable();
|
|
||||||
|
|
||||||
this.config.load();
|
|
||||||
|
|
||||||
// Note that Spigot has a bullshit channel size restriction as Minecraft SUPPORTS UP TO 32767 AS CHANNEL SIZE.
|
|
||||||
// Please stop using that bad server software, move over Sponge or idk other things. REALLY.
|
|
||||||
this.getServer().getMessenger().registerIncomingPluginChannel(this, CONTROLS_MODE_CHANNEL.toString(), this);
|
|
||||||
this.getServer().getMessenger().registerOutgoingPluginChannel(this, CONTROLS_MODE_CHANNEL.toString());
|
|
||||||
this.getServer().getMessenger().registerOutgoingPluginChannel(this, FEATURE_CHANNEL.toString());
|
|
||||||
this.getServer().getMessenger().registerIncomingPluginChannel(this, HELLO_CHANNEL.toString(), this);
|
|
||||||
this.getServer().getPluginManager().registerEvents(this, this);
|
|
||||||
|
|
||||||
this.getServer().getOnlinePlayers().forEach(player -> {
|
|
||||||
PLAYERS_CONTROLS_MODE.put(player, ControlsMode.DEFAULT);
|
|
||||||
|
|
||||||
this.requestPlayerControlsMode(player);
|
|
||||||
this.updatePlayerFeature(player, LambdaControlsFeature.FRONT_BLOCK_PLACING);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDisable()
|
|
||||||
{
|
|
||||||
super.onDisable();
|
|
||||||
|
|
||||||
this.getServer().getMessenger().unregisterIncomingPluginChannel(this, CONTROLS_MODE_CHANNEL.toString());
|
|
||||||
this.getServer().getMessenger().unregisterOutgoingPluginChannel(this, CONTROLS_MODE_CHANNEL.toString());
|
|
||||||
this.getServer().getMessenger().unregisterOutgoingPluginChannel(this, FEATURE_CHANNEL.toString());
|
|
||||||
this.getServer().getMessenger().unregisterIncomingPluginChannel(this, HELLO_CHANNEL.toString());
|
|
||||||
|
|
||||||
PLAYERS_CONTROLS_MODE.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void requestPlayerControlsMode(@NotNull Player player)
|
|
||||||
{
|
|
||||||
player.sendPluginMessage(this, CONTROLS_MODE_CHANNEL.toString(), new byte[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void updatePlayerFeature(@NotNull Player player, @NotNull LambdaControlsFeature feature)
|
|
||||||
{
|
|
||||||
Objects.requireNonNull(player);
|
|
||||||
Objects.requireNonNull(feature);
|
|
||||||
|
|
||||||
player.sendPluginMessage(this, FEATURE_CHANNEL.toString(), this.makeFeatureMessage(feature));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Prints a message to the terminal.
|
|
||||||
*
|
|
||||||
* @param info The message to print.
|
|
||||||
*/
|
|
||||||
public void log(String info)
|
|
||||||
{
|
|
||||||
this.getLogger().info(info);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPluginMessageReceived(@NotNull String channel, @NotNull Player player, @NotNull byte[] message)
|
|
||||||
{
|
|
||||||
if (channel.equals(HELLO_CHANNEL.toString())) {
|
|
||||||
NettyPacketBuffer buffer = new NettyPacketBuffer(Unpooled.copiedBuffer(message));
|
|
||||||
String version = buffer.readString(16);
|
|
||||||
ControlsMode.byId(buffer.readString(32)).ifPresent(controlsMode -> {
|
|
||||||
PLAYERS_CONTROLS_MODE.put(player, controlsMode);
|
|
||||||
PlayerChangeControlsModeEvent event = new PlayerChangeControlsModeEvent(player, controlsMode);
|
|
||||||
this.getServer().getPluginManager().callEvent(event);
|
|
||||||
});
|
|
||||||
this.updatePlayerFeature(player, LambdaControlsFeature.FRONT_BLOCK_PLACING);
|
|
||||||
} else if (channel.equals(CONTROLS_MODE_CHANNEL.toString())) {
|
|
||||||
NettyPacketBuffer buffer = new NettyPacketBuffer(Unpooled.copiedBuffer(message));
|
|
||||||
ControlsMode.byId(buffer.readString(32)).ifPresent(controlsMode -> {
|
|
||||||
PLAYERS_CONTROLS_MODE.put(player, controlsMode);
|
|
||||||
PlayerChangeControlsModeEvent event = new PlayerChangeControlsModeEvent(player, controlsMode);
|
|
||||||
this.getServer().getPluginManager().callEvent(event);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a packet byte buffer made for the lambdacontrols:feature plugin message.
|
|
||||||
*
|
|
||||||
* @param feature The feature data to send.
|
|
||||||
* @return The packet byte buffer.
|
|
||||||
*/
|
|
||||||
public byte[] makeFeatureMessage(@NotNull LambdaControlsFeature feature)
|
|
||||||
{
|
|
||||||
Objects.requireNonNull(feature, "Feature cannot be null.");
|
|
||||||
NettyPacketBuffer buffer = new NettyPacketBuffer(Unpooled.buffer());
|
|
||||||
buffer.writeString(feature.getName());
|
|
||||||
buffer.writeBoolean(feature.isAllowed());
|
|
||||||
return buffer.array();
|
|
||||||
}
|
|
||||||
|
|
||||||
@EventHandler
|
|
||||||
public void onPlayerJoin(@NotNull PlayerJoinEvent event)
|
|
||||||
{
|
|
||||||
PLAYERS_CONTROLS_MODE.put(event.getPlayer(), ControlsMode.DEFAULT);
|
|
||||||
}
|
|
||||||
|
|
||||||
@EventHandler
|
|
||||||
public void onPlayerLeave(@NotNull PlayerQuitEvent event)
|
|
||||||
{
|
|
||||||
PLAYERS_CONTROLS_MODE.remove(event.getPlayer());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,102 +0,0 @@
|
|||||||
package me.lambdaurora.lambdacontrols;
|
|
||||||
|
|
||||||
import io.netty.buffer.ByteBuf;
|
|
||||||
import io.netty.handler.codec.DecoderException;
|
|
||||||
import io.netty.handler.codec.EncoderException;
|
|
||||||
|
|
||||||
import java.nio.charset.Charset;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
|
|
||||||
public class NettyPacketBuffer extends PacketBuffer
|
|
||||||
{
|
|
||||||
public NettyPacketBuffer(ByteBuf byteBuf)
|
|
||||||
{
|
|
||||||
super(byteBuf);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int readVarint()
|
|
||||||
{
|
|
||||||
int var1 = 0;
|
|
||||||
int var2 = 0;
|
|
||||||
byte var3;
|
|
||||||
|
|
||||||
do {
|
|
||||||
var3 = this.readByte();
|
|
||||||
var1 |= (var3 & 127) << var2++ * 7;
|
|
||||||
|
|
||||||
if (var2 > 5)
|
|
||||||
throw new RuntimeException("VarInt too big");
|
|
||||||
} while ((var3 & 128) == 128);
|
|
||||||
|
|
||||||
return var1;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void writeVarint(int input)
|
|
||||||
{
|
|
||||||
while ((input & -128) != 0) {
|
|
||||||
this.writeByte(input & 127 | 128);
|
|
||||||
input >>>= 7;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.writeByte(input);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String readString(int maxLength)
|
|
||||||
{
|
|
||||||
int var2 = this.readVarint();
|
|
||||||
|
|
||||||
if (var2 > maxLength * 4)
|
|
||||||
throw new DecoderException("The received encoded string buffer length is longer than maximum allowed (" + var2 + " > " + maxLength * 4 + ")");
|
|
||||||
else if (var2 < 0)
|
|
||||||
throw new DecoderException("The received encoded string buffer length is less than zero! Weird string!");
|
|
||||||
else {
|
|
||||||
String var3 = this.readCharSequence(var2, StandardCharsets.UTF_8).toString();
|
|
||||||
|
|
||||||
if (var3.length() > maxLength)
|
|
||||||
throw new DecoderException("The received string length is longer than maximum allowed (" + var2 + " > " + maxLength + ")");
|
|
||||||
else
|
|
||||||
return var3;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void writeString(String string)
|
|
||||||
{
|
|
||||||
byte[] var2 = string.getBytes(Charset.forName("UTF-8"));
|
|
||||||
|
|
||||||
if (var2.length > 32767) {
|
|
||||||
throw new EncoderException("String too big (was " + string.length() + " data encoded, max " + 32767 + ")");
|
|
||||||
} else {
|
|
||||||
this.writeVarint(var2.length);
|
|
||||||
this.writeCharSequence(string, StandardCharsets.UTF_8);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o)
|
|
||||||
{
|
|
||||||
if (this == o)
|
|
||||||
return true;
|
|
||||||
if (o == null || getClass() != o.getClass())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
NettyPacketBuffer buffer = (NettyPacketBuffer) o;
|
|
||||||
|
|
||||||
return byteBuf.equals(buffer.byteBuf);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int compareTo(ByteBuf buffer)
|
|
||||||
{
|
|
||||||
return this.byteBuf.compareTo(buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString()
|
|
||||||
{
|
|
||||||
return this.byteBuf.toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,65 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2020 LambdAurora <aurora42lambda@gmail.com>
|
|
||||||
*
|
|
||||||
* This file is part of LambdaControls.
|
|
||||||
*
|
|
||||||
* Licensed under the MIT license. For more information,
|
|
||||||
* see the LICENSE file.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package me.lambdaurora.lambdacontrols.event;
|
|
||||||
|
|
||||||
import me.lambdaurora.lambdacontrols.ControlsMode;
|
|
||||||
import org.bukkit.entity.Player;
|
|
||||||
import org.bukkit.event.HandlerList;
|
|
||||||
import org.bukkit.event.player.PlayerEvent;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents an event which is fired when a player change their controls mode.
|
|
||||||
*
|
|
||||||
* @author LambdAurora
|
|
||||||
* @version 1.1.0
|
|
||||||
* @since 1.1.0
|
|
||||||
*/
|
|
||||||
public class PlayerChangeControlsModeEvent extends PlayerEvent
|
|
||||||
{
|
|
||||||
private static final HandlerList HANDLERS = new HandlerList();
|
|
||||||
private final ControlsMode controlsMode;
|
|
||||||
|
|
||||||
public PlayerChangeControlsModeEvent(@NotNull Player who, @NotNull ControlsMode controlsMode)
|
|
||||||
{
|
|
||||||
super(who);
|
|
||||||
this.controlsMode = controlsMode;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the controls mode of the player.
|
|
||||||
*
|
|
||||||
* @return The player's controls mode.
|
|
||||||
*/
|
|
||||||
public ControlsMode getControlsMode()
|
|
||||||
{
|
|
||||||
return this.controlsMode;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString()
|
|
||||||
{
|
|
||||||
return "PlayerChangeControlsModeEvent{" +
|
|
||||||
"player=" + this.player +
|
|
||||||
", controls_mode=" + this.controlsMode +
|
|
||||||
'}';
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull HandlerList getHandlers()
|
|
||||||
{
|
|
||||||
return HANDLERS;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static @NotNull HandlerList getHandlerList()
|
|
||||||
{
|
|
||||||
return HANDLERS;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
name: LambdaControls
|
|
||||||
version: ${version}
|
|
||||||
description: A quick Spigot plugin for LambdaControls which allow server admins to disable some features.
|
|
||||||
|
|
||||||
main: me.lambdaurora.lambdacontrols.LambdaControlsSpigot
|
|
||||||
api-version: 1.13
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
# LambdaControls server configuration.
|
|
||||||
|
|
||||||
# Gameplay settings
|
|
||||||
[gameplay]
|
|
||||||
# Allows front block placing like in Bedrock Edition.
|
|
||||||
front_block_placing = true
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright © 2020 LambdAurora <aurora42lambda@gmail.com>
|
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
|
||||||
*
|
*
|
||||||
* This file is part of LambdaControls.
|
* This file is part of LambdaControls.
|
||||||
*
|
*
|
||||||
@@ -7,7 +7,7 @@
|
|||||||
* see the LICENSE file.
|
* see the LICENSE file.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package me.lambdaurora.lambdacontrols;
|
package dev.lambdaurora.lambdacontrols;
|
||||||
|
|
||||||
import org.aperlambda.lambdacommon.utils.Nameable;
|
import org.aperlambda.lambdacommon.utils.Nameable;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
@@ -19,23 +19,20 @@ import java.util.Optional;
|
|||||||
* Represents the controls mode.
|
* Represents the controls mode.
|
||||||
*
|
*
|
||||||
* @author LambdAurora
|
* @author LambdAurora
|
||||||
* @version 1.1.0
|
* @version 1.7.0
|
||||||
* @since 1.0.0
|
* @since 1.0.0
|
||||||
*/
|
*/
|
||||||
public enum ControlsMode implements Nameable
|
public enum ControlsMode implements Nameable {
|
||||||
{
|
|
||||||
DEFAULT,
|
DEFAULT,
|
||||||
CONTROLLER,
|
CONTROLLER;
|
||||||
TOUCHSCREEN;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the next controls mode available.
|
* Returns the next controls mode available.
|
||||||
*
|
*
|
||||||
* @return The next available controls mode.
|
* @return the next available controls mode
|
||||||
*/
|
*/
|
||||||
public ControlsMode next()
|
public ControlsMode next() {
|
||||||
{
|
var v = values();
|
||||||
ControlsMode[] v = values();
|
|
||||||
if (v.length == this.ordinal() + 1)
|
if (v.length == this.ordinal() + 1)
|
||||||
return v[0];
|
return v[0];
|
||||||
return v[this.ordinal() + 1];
|
return v[this.ordinal() + 1];
|
||||||
@@ -44,28 +41,25 @@ public enum ControlsMode implements Nameable
|
|||||||
/**
|
/**
|
||||||
* Gets the translation key of this controls mode.
|
* Gets the translation key of this controls mode.
|
||||||
*
|
*
|
||||||
* @return The translated key of this controls mode.
|
* @return the translated key of this controls mode
|
||||||
* @since 1.1.0
|
* @since 1.1.0
|
||||||
*/
|
*/
|
||||||
public String getTranslationKey()
|
public String getTranslationKey() {
|
||||||
{
|
|
||||||
return "lambdacontrols.controls_mode." + this.getName();
|
return "lambdacontrols.controls_mode." + this.getName();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @NotNull String getName()
|
public @NotNull String getName() {
|
||||||
{
|
|
||||||
return this.name().toLowerCase();
|
return this.name().toLowerCase();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the controls mode from its identifier.
|
* Gets the controls mode from its identifier.
|
||||||
*
|
*
|
||||||
* @param id The identifier of the controls mode.
|
* @param id the identifier of the controls mode
|
||||||
* @return The controls mode if found, else empty.
|
* @return the controls mode if found, else empty
|
||||||
*/
|
*/
|
||||||
public static Optional<ControlsMode> byId(@NotNull String id)
|
public static Optional<ControlsMode> byId(@NotNull String id) {
|
||||||
{
|
|
||||||
return Arrays.stream(values()).filter(mode -> mode.getName().equalsIgnoreCase(id)).findFirst();
|
return Arrays.stream(values()).filter(mode -> mode.getName().equalsIgnoreCase(id)).findFirst();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
129
src/main/java/dev/lambdaurora/lambdacontrols/LambdaControls.java
Normal file
129
src/main/java/dev/lambdaurora/lambdacontrols/LambdaControls.java
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
|
||||||
|
*
|
||||||
|
* This file is part of LambdaControls.
|
||||||
|
*
|
||||||
|
* Licensed under the MIT license. For more information,
|
||||||
|
* see the LICENSE file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package dev.lambdaurora.lambdacontrols;
|
||||||
|
|
||||||
|
import dev.lambdaurora.lambdacontrols.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 LambdaControls mod.
|
||||||
|
*
|
||||||
|
* @author LambdAurora
|
||||||
|
* @version 1.7.0
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public class LambdaControls implements ModInitializer {
|
||||||
|
private static LambdaControls INSTANCE;
|
||||||
|
public static final Identifier CONTROLS_MODE_CHANNEL = new Identifier(LambdaControlsConstants.CONTROLS_MODE_CHANNEL.toString());
|
||||||
|
public static final Identifier FEATURE_CHANNEL = new Identifier(LambdaControlsConstants.FEATURE_CHANNEL.toString());
|
||||||
|
public static final Identifier HELLO_CHANNEL = new Identifier(LambdaControlsConstants.HELLO_CHANNEL.toString());
|
||||||
|
|
||||||
|
public static final TranslatableText NOT_BOUND_TEXT = new TranslatableText("lambdacontrols.not_bound");
|
||||||
|
|
||||||
|
public final Logger logger = LogManager.getLogger("LambdaControls");
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onInitialize() {
|
||||||
|
INSTANCE = this;
|
||||||
|
this.log("Initializing LambdaControls...");
|
||||||
|
|
||||||
|
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(LambdaControlsFeature.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("[LambdaControls] " + info);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prints a warning to the terminal.
|
||||||
|
*
|
||||||
|
* @param warning the warning to print
|
||||||
|
*/
|
||||||
|
public void warn(String warning) {
|
||||||
|
this.logger.info("[LambdaControls] " + warning);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a packet byte buffer made for the lambdacontrols: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 lambdacontrols:feature plugin message.
|
||||||
|
*
|
||||||
|
* @param features the features data to send
|
||||||
|
* @return the packet byte buffer
|
||||||
|
*/
|
||||||
|
public PacketByteBuf makeFeatureBuffer(LambdaControlsFeature... 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(LambdaControlsConstants.NAMESPACE)).isPresent()) {
|
||||||
|
version = container.get().getMetadata().getVersion().getFriendlyString();
|
||||||
|
}
|
||||||
|
return new PacketByteBuf(Unpooled.buffer()).writeString(version, 32).writeString(controlsMode.getName(), 32);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the LambdaControls instance.
|
||||||
|
*
|
||||||
|
* @return the LambdaControls instance
|
||||||
|
*/
|
||||||
|
public static LambdaControls get() {
|
||||||
|
return INSTANCE;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright © 2020 LambdAurora <aurora42lambda@gmail.com>
|
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
|
||||||
*
|
*
|
||||||
* This file is part of LambdaControls.
|
* This file is part of LambdaControls.
|
||||||
*
|
*
|
||||||
@@ -7,7 +7,7 @@
|
|||||||
* see the LICENSE file.
|
* see the LICENSE file.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package me.lambdaurora.lambdacontrols;
|
package dev.lambdaurora.lambdacontrols;
|
||||||
|
|
||||||
import org.aperlambda.lambdacommon.Identifier;
|
import org.aperlambda.lambdacommon.Identifier;
|
||||||
|
|
||||||
@@ -18,8 +18,7 @@ import org.aperlambda.lambdacommon.Identifier;
|
|||||||
* @version 1.1.0
|
* @version 1.1.0
|
||||||
* @since 1.1.0
|
* @since 1.1.0
|
||||||
*/
|
*/
|
||||||
public class LambdaControlsConstants
|
public class LambdaControlsConstants {
|
||||||
{
|
|
||||||
public static final String NAMESPACE = "lambdacontrols";
|
public static final String NAMESPACE = "lambdacontrols";
|
||||||
public static final Identifier CONTROLS_MODE_CHANNEL = new Identifier(NAMESPACE, "controls_mode");
|
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 FEATURE_CHANNEL = new Identifier(NAMESPACE, "feature");
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright © 2020 LambdAurora <aurora42lambda@gmail.com>
|
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
|
||||||
*
|
*
|
||||||
* This file is part of LambdaControls.
|
* This file is part of LambdaControls.
|
||||||
*
|
*
|
||||||
@@ -7,7 +7,7 @@
|
|||||||
* see the LICENSE file.
|
* see the LICENSE file.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package me.lambdaurora.lambdacontrols;
|
package dev.lambdaurora.lambdacontrols;
|
||||||
|
|
||||||
import org.aperlambda.lambdacommon.utils.Nameable;
|
import org.aperlambda.lambdacommon.utils.Nameable;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
@@ -21,13 +21,14 @@ import java.util.Optional;
|
|||||||
* Represents a feature.
|
* Represents a feature.
|
||||||
*
|
*
|
||||||
* @author LambdAurora
|
* @author LambdAurora
|
||||||
* @version 1.1.0
|
* @version 1.5.0
|
||||||
* @since 1.1.0
|
* @since 1.1.0
|
||||||
*/
|
*/
|
||||||
public class LambdaControlsFeature implements Nameable
|
public class LambdaControlsFeature implements Nameable {
|
||||||
{
|
|
||||||
private static final List<LambdaControlsFeature> FEATURES = new ArrayList<>();
|
private static final List<LambdaControlsFeature> FEATURES = new ArrayList<>();
|
||||||
public static final LambdaControlsFeature FRONT_BLOCK_PLACING = new LambdaControlsFeature("front_block_placing", true, false);
|
public static final LambdaControlsFeature FAST_BLOCK_PLACING = new LambdaControlsFeature("fast_block_placing", true, true);
|
||||||
|
public static final LambdaControlsFeature HORIZONTAL_REACHAROUND = new LambdaControlsFeature("horizontal_reacharound", true, false);
|
||||||
|
public static final LambdaControlsFeature VERTICAL_REACHAROUND = new LambdaControlsFeature("vertical_reacharound", true, false);
|
||||||
|
|
||||||
private final String key;
|
private final String key;
|
||||||
private final boolean defaultAllowed;
|
private final boolean defaultAllowed;
|
||||||
@@ -35,104 +36,92 @@ public class LambdaControlsFeature implements Nameable
|
|||||||
private final boolean defaultEnabled;
|
private final boolean defaultEnabled;
|
||||||
private boolean enabled;
|
private boolean enabled;
|
||||||
|
|
||||||
public LambdaControlsFeature(@NotNull String key, boolean allowed, boolean enabled)
|
public LambdaControlsFeature(@NotNull String key, boolean allowed, boolean enabled) {
|
||||||
{
|
|
||||||
Objects.requireNonNull(key, "Feature key cannot be null.");
|
Objects.requireNonNull(key, "Feature key cannot be null.");
|
||||||
this.key = key;
|
this.key = key;
|
||||||
this.setAllowed(this.defaultAllowed = allowed);
|
this.setAllowed(this.defaultAllowed = allowed);
|
||||||
this.setEnabled(this.defaultEnabled = enabled);
|
this.setEnabled(this.defaultEnabled = enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
public LambdaControlsFeature(@NotNull String key)
|
public LambdaControlsFeature(@NotNull String key) {
|
||||||
{
|
|
||||||
this(key, false, false);
|
this(key, false, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Allows the feature.
|
* Allows the feature.
|
||||||
*/
|
*/
|
||||||
public void allow()
|
public void allow() {
|
||||||
{
|
|
||||||
this.setAllowed(true);
|
this.setAllowed(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether this feature is allowed.
|
* Returns whether this feature is allowed.
|
||||||
*
|
*
|
||||||
* @return True if this feature is allowed, else false.
|
* @return {@code true} if this feature is allowed, else {@code false}
|
||||||
*/
|
*/
|
||||||
public boolean isAllowed()
|
public boolean isAllowed() {
|
||||||
{
|
|
||||||
return this.allowed;
|
return this.allowed;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets whether this feature is allowed.
|
* Sets whether this feature is allowed.
|
||||||
*
|
*
|
||||||
* @param allowed True if this feature is allowed, else false.
|
* @param allowed {@code true} if this feature is allowed, else {@code false}
|
||||||
*/
|
*/
|
||||||
public void setAllowed(boolean allowed)
|
public void setAllowed(boolean allowed) {
|
||||||
{
|
|
||||||
this.allowed = allowed;
|
this.allowed = allowed;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resets allowed state to default.
|
* Resets allowed state to default.
|
||||||
*/
|
*/
|
||||||
public void resetAllowed()
|
public void resetAllowed() {
|
||||||
{
|
|
||||||
this.setAllowed(this.defaultAllowed);
|
this.setAllowed(this.defaultAllowed);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether this feature is enabled.
|
* Returns whether this feature is enabled.
|
||||||
*
|
*
|
||||||
* @return True if this feature is enabled, else false.
|
* @return {@code true} if this feature is enabled, else {@code false}
|
||||||
*/
|
*/
|
||||||
public boolean isEnabled()
|
public boolean isEnabled() {
|
||||||
{
|
|
||||||
return this.enabled;
|
return this.enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether this feature is enabled.
|
* Returns whether this feature is enabled.
|
||||||
*
|
*
|
||||||
* @param enabled True if this feature is enabled, else false.
|
* @param enabled {@code true} if this feature is enabled, else {@code false}
|
||||||
*/
|
*/
|
||||||
public void setEnabled(boolean enabled)
|
public void setEnabled(boolean enabled) {
|
||||||
{
|
|
||||||
this.enabled = enabled;
|
this.enabled = enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether this feature is available or not.
|
* Returns whether this feature is available or not.
|
||||||
*
|
*
|
||||||
* @return True if this feature is available, else false.
|
* @return {@code true} if this feature is available, else {@code false}
|
||||||
* @see #isAllowed()
|
* @see #isAllowed()
|
||||||
* @see #isEnabled()
|
* @see #isEnabled()
|
||||||
*/
|
*/
|
||||||
public boolean isAvailable()
|
public boolean isAvailable() {
|
||||||
{
|
|
||||||
return this.isAllowed() && this.isEnabled();
|
return this.isAllowed() && this.isEnabled();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resets the feature to its default values.
|
* Resets the feature to its default values.
|
||||||
*/
|
*/
|
||||||
public void reset()
|
public void reset() {
|
||||||
{
|
|
||||||
this.resetAllowed();
|
this.resetAllowed();
|
||||||
this.setEnabled(this.defaultEnabled);
|
this.setEnabled(this.defaultEnabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @NotNull String getName()
|
public @NotNull String getName() {
|
||||||
{
|
|
||||||
return this.key;
|
return this.key;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static @NotNull Optional<LambdaControlsFeature> fromName(@NotNull String key)
|
public static @NotNull Optional<LambdaControlsFeature> fromName(@NotNull String key) {
|
||||||
{
|
|
||||||
Objects.requireNonNull(key, "Cannot find features with a null name.");
|
Objects.requireNonNull(key, "Cannot find features with a null name.");
|
||||||
return FEATURES.parallelStream().filter(feature -> feature.getName().equals(key)).findFirst();
|
return FEATURES.parallelStream().filter(feature -> feature.getName().equals(key)).findFirst();
|
||||||
}
|
}
|
||||||
@@ -140,20 +129,20 @@ public class LambdaControlsFeature implements Nameable
|
|||||||
/**
|
/**
|
||||||
* Resets all features to their default values.
|
* Resets all features to their default values.
|
||||||
*/
|
*/
|
||||||
public static void resetAll()
|
public static void resetAll() {
|
||||||
{
|
|
||||||
FEATURES.parallelStream().forEach(LambdaControlsFeature::reset);
|
FEATURES.parallelStream().forEach(LambdaControlsFeature::reset);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resets all features to allow state.
|
* Resets all features to allow state.
|
||||||
*/
|
*/
|
||||||
public static void resetAllAllowed()
|
public static void resetAllAllowed() {
|
||||||
{
|
|
||||||
FEATURES.parallelStream().forEach(LambdaControlsFeature::resetAllowed);
|
FEATURES.parallelStream().forEach(LambdaControlsFeature::resetAllowed);
|
||||||
}
|
}
|
||||||
|
|
||||||
static {
|
static {
|
||||||
FEATURES.add(FRONT_BLOCK_PLACING);
|
FEATURES.add(FAST_BLOCK_PLACING);
|
||||||
|
FEATURES.add(HORIZONTAL_REACHAROUND);
|
||||||
|
FEATURES.add(VERTICAL_REACHAROUND);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright © 2020 LambdAurora <aurora42lambda@gmail.com>
|
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
|
||||||
*
|
*
|
||||||
* This file is part of LambdaControls.
|
* This file is part of LambdaControls.
|
||||||
*
|
*
|
||||||
@@ -7,7 +7,7 @@
|
|||||||
* see the LICENSE file.
|
* see the LICENSE file.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package me.lambdaurora.lambdacontrols.client;
|
package dev.lambdaurora.lambdacontrols.client;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a button state.
|
* Represents a button state.
|
||||||
@@ -16,8 +16,7 @@ package me.lambdaurora.lambdacontrols.client;
|
|||||||
* @version 1.1.0
|
* @version 1.1.0
|
||||||
* @since 1.1.0
|
* @since 1.1.0
|
||||||
*/
|
*/
|
||||||
public enum ButtonState
|
public enum ButtonState {
|
||||||
{
|
|
||||||
NONE(0),
|
NONE(0),
|
||||||
PRESS(1),
|
PRESS(1),
|
||||||
RELEASE(2),
|
RELEASE(2),
|
||||||
@@ -25,28 +24,25 @@ public enum ButtonState
|
|||||||
|
|
||||||
public final int id;
|
public final int id;
|
||||||
|
|
||||||
ButtonState(int id)
|
ButtonState(int id) {
|
||||||
{
|
|
||||||
this.id = id;
|
this.id = id;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether this state is a pressed state.
|
* Returns whether this state is a pressed state.
|
||||||
*
|
*
|
||||||
* @return True if this state is a pressed state, else false.
|
* @return true if this state is a pressed state, else false
|
||||||
*/
|
*/
|
||||||
public boolean isPressed()
|
public boolean isPressed() {
|
||||||
{
|
|
||||||
return this == PRESS || this == REPEAT;
|
return this == PRESS || this == REPEAT;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether this state is an unpressed state.
|
* Returns whether this state is an unpressed state.
|
||||||
*
|
*
|
||||||
* @return True if this state is an unpressed state, else false.
|
* @return true if this state is an unpressed state, else false
|
||||||
*/
|
*/
|
||||||
public boolean isUnpressed()
|
public boolean isUnpressed() {
|
||||||
{
|
|
||||||
return this == RELEASE || this == NONE;
|
return this == RELEASE || this == NONE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
|
||||||
|
*
|
||||||
|
* This file is part of LambdaControls.
|
||||||
|
*
|
||||||
|
* Licensed under the MIT license. For more information,
|
||||||
|
* see the LICENSE file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package dev.lambdaurora.lambdacontrols.client;
|
||||||
|
|
||||||
|
import net.minecraft.text.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),
|
||||||
|
DUALSHOCK(1),
|
||||||
|
SWITCH(2),
|
||||||
|
XBOX_360(3, new LiteralText("Xbox 360")),
|
||||||
|
XBOX(4),
|
||||||
|
STEAM(5),
|
||||||
|
OUYA(6);
|
||||||
|
|
||||||
|
private final int id;
|
||||||
|
private final Text text;
|
||||||
|
|
||||||
|
ControllerType(int id) {
|
||||||
|
this.id = id;
|
||||||
|
this.text = new TranslatableText("lambdacontrols.controller_type." + this.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
ControllerType(int id, @NotNull Text text) {
|
||||||
|
this.id = id;
|
||||||
|
this.text = text;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 LambdaControls.
|
||||||
|
*
|
||||||
|
* Licensed under the MIT license. For more information,
|
||||||
|
* see the LICENSE file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package dev.lambdaurora.lambdacontrols.client;
|
||||||
|
|
||||||
|
import net.minecraft.text.Text;
|
||||||
|
import net.minecraft.text.TranslatableText;
|
||||||
|
import org.aperlambda.lambdacommon.utils.Nameable;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the 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 "lambdacontrols.hud_side." + this.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the translated text of this hud side.
|
||||||
|
*
|
||||||
|
* @return the translated text of this hud side
|
||||||
|
*/
|
||||||
|
public @NotNull Text getTranslatedText() {
|
||||||
|
return this.text;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull String getName() {
|
||||||
|
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,203 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
|
||||||
|
*
|
||||||
|
* This file is part of LambdaControls.
|
||||||
|
*
|
||||||
|
* Licensed under the MIT license. For more information,
|
||||||
|
* see the LICENSE file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package dev.lambdaurora.lambdacontrols.client;
|
||||||
|
|
||||||
|
import dev.lambdaurora.lambdacontrols.ControlsMode;
|
||||||
|
import dev.lambdaurora.lambdacontrols.LambdaControls;
|
||||||
|
import dev.lambdaurora.lambdacontrols.LambdaControlsConstants;
|
||||||
|
import dev.lambdaurora.lambdacontrols.LambdaControlsFeature;
|
||||||
|
import dev.lambdaurora.lambdacontrols.client.compat.LambdaControlsCompat;
|
||||||
|
import dev.lambdaurora.lambdacontrols.client.controller.ButtonBinding;
|
||||||
|
import dev.lambdaurora.lambdacontrols.client.controller.Controller;
|
||||||
|
import dev.lambdaurora.lambdacontrols.client.controller.InputManager;
|
||||||
|
import dev.lambdaurora.lambdacontrols.client.gui.LambdaControlsHud;
|
||||||
|
import dev.lambdaurora.lambdacontrols.client.ring.KeyBindingRingAction;
|
||||||
|
import dev.lambdaurora.lambdacontrols.client.ring.LambdaRing;
|
||||||
|
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 LambdaControls client mod.
|
||||||
|
*
|
||||||
|
* @author LambdAurora
|
||||||
|
* @version 1.7.0
|
||||||
|
* @since 1.1.0
|
||||||
|
*/
|
||||||
|
public class LambdaControlsClient extends LambdaControls implements ClientModInitializer {
|
||||||
|
private static LambdaControlsClient INSTANCE;
|
||||||
|
public static final KeyBinding BINDING_LOOK_UP = InputManager.makeKeyBinding(new Identifier(LambdaControlsConstants.NAMESPACE, "look_up"),
|
||||||
|
InputUtil.Type.KEYSYM, GLFW.GLFW_KEY_KP_8, "key.categories.movement");
|
||||||
|
public static final KeyBinding BINDING_LOOK_RIGHT = InputManager.makeKeyBinding(new Identifier(LambdaControlsConstants.NAMESPACE, "look_right"),
|
||||||
|
InputUtil.Type.KEYSYM, GLFW.GLFW_KEY_KP_6, "key.categories.movement");
|
||||||
|
public static final KeyBinding BINDING_LOOK_DOWN = InputManager.makeKeyBinding(new Identifier(LambdaControlsConstants.NAMESPACE, "look_down"),
|
||||||
|
InputUtil.Type.KEYSYM, GLFW.GLFW_KEY_KP_2, "key.categories.movement");
|
||||||
|
public static final KeyBinding BINDING_LOOK_LEFT = InputManager.makeKeyBinding(new Identifier(LambdaControlsConstants.NAMESPACE, "look_left"),
|
||||||
|
InputUtil.Type.KEYSYM, GLFW.GLFW_KEY_KP_4, "key.categories.movement");
|
||||||
|
/*public static final KeyBinding BINDING_RING = InputManager.makeKeyBinding(new Identifier(LambdaControlsConstants.NAMESPACE, "ring"),
|
||||||
|
InputUtil.Type.MOUSE, GLFW.GLFW_MOUSE_BUTTON_5, "key.categories.misc");*/
|
||||||
|
public static final Identifier CONTROLLER_BUTTONS = new Identifier(LambdaControlsConstants.NAMESPACE, "textures/gui/controller_buttons.png");
|
||||||
|
public static final Identifier CONTROLLER_AXIS = new Identifier(LambdaControlsConstants.NAMESPACE, "textures/gui/controller_axis.png");
|
||||||
|
public static final Identifier CURSOR_TEXTURE = new Identifier(LambdaControlsConstants.NAMESPACE, "textures/gui/cursor.png");
|
||||||
|
public final static File MAPPINGS_FILE = new File("config/gamecontrollerdb.txt");
|
||||||
|
public final LambdaControlsConfig config = new LambdaControlsConfig(this);
|
||||||
|
public final LambdaInput input = new LambdaInput(this);
|
||||||
|
public final LambdaRing ring = new LambdaRing(this);
|
||||||
|
public final LambdaReacharound reacharound = new LambdaReacharound();
|
||||||
|
private LambdaControlsHud hud;
|
||||||
|
private ControlsMode previousControlsMode;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onInitializeClient() {
|
||||||
|
INSTANCE = this;
|
||||||
|
KeyBindingHelper.registerKeyBinding(BINDING_LOOK_UP);
|
||||||
|
KeyBindingHelper.registerKeyBinding(BINDING_LOOK_RIGHT);
|
||||||
|
KeyBindingHelper.registerKeyBinding(BINDING_LOOK_DOWN);
|
||||||
|
KeyBindingHelper.registerKeyBinding(BINDING_LOOK_LEFT);
|
||||||
|
//KeyBindingHelper.registerKeyBinding(BINDING_RING);
|
||||||
|
|
||||||
|
this.ring.registerAction("keybinding", KeyBindingRingAction.FACTORY);
|
||||||
|
|
||||||
|
ClientPlayNetworking.registerGlobalReceiver(CONTROLS_MODE_CHANNEL, (client, handler, buf, responseSender) -> {
|
||||||
|
responseSender.sendPacket(CONTROLS_MODE_CHANNEL, this.makeControlsModeBuffer(this.config.getControlsMode()));
|
||||||
|
});
|
||||||
|
ClientPlayNetworking.registerGlobalReceiver(FEATURE_CHANNEL, (client, handler, buf, responseSender) -> {
|
||||||
|
int features = buf.readVarInt();
|
||||||
|
for (int i = 0; i < features; i++) {
|
||||||
|
var name = buf.readString(64);
|
||||||
|
boolean allowed = buf.readBoolean();
|
||||||
|
LambdaControlsFeature.fromName(name).ifPresent(feature -> client.execute(() -> feature.setAllowed(allowed)));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
ClientPlayConnectionEvents.JOIN.register((handler, sender, client) -> {
|
||||||
|
sender.sendPacket(HELLO_CHANNEL, this.makeHello(this.config.getControlsMode()));
|
||||||
|
sender.sendPacket(CONTROLS_MODE_CHANNEL, this.makeControlsModeBuffer(this.config.getControlsMode()));
|
||||||
|
});
|
||||||
|
ClientPlayConnectionEvents.DISCONNECT.register(this::onLeave);
|
||||||
|
|
||||||
|
ClientTickEvents.START_CLIENT_TICK.register(this.reacharound::tick);
|
||||||
|
ClientTickEvents.END_CLIENT_TICK.register(this::onTick);
|
||||||
|
|
||||||
|
/*OpenScreenCallback.EVENT.register((client, screen) -> {
|
||||||
|
if (screen == null && this.config.getControlsMode() == ControlsMode.TOUCHSCREEN) {
|
||||||
|
screen = new TouchscreenOverlay(this);
|
||||||
|
screen.init(client, client.getWindow().getScaledWidth(), client.getWindow().getScaledHeight());
|
||||||
|
client.skipGameRender = false;
|
||||||
|
client.currentScreen = screen;
|
||||||
|
} else if (screen != null) {
|
||||||
|
this.input.onScreenOpen(client, client.getWindow().getWidth(), client.getWindow().getHeight());
|
||||||
|
}
|
||||||
|
});*/
|
||||||
|
|
||||||
|
HudManager.register(this.hud = new LambdaControlsHud(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is called when Minecraft is initializing.
|
||||||
|
*/
|
||||||
|
public void onMcInit(@NotNull MinecraftClient client) {
|
||||||
|
ButtonBinding.init(client.options);
|
||||||
|
this.config.load();
|
||||||
|
this.hud.setVisible(this.config.isHudEnabled());
|
||||||
|
Controller.updateMappings();
|
||||||
|
GLFW.glfwSetJoystickCallback((jid, event) -> {
|
||||||
|
if (event == GLFW.GLFW_CONNECTED) {
|
||||||
|
var controller = Controller.byId(jid);
|
||||||
|
client.getToastManager().add(new SystemToast(SystemToast.Type.TUTORIAL_HINT, new TranslatableText("lambdacontrols.controller.connected", jid),
|
||||||
|
new LiteralText(controller.getName())));
|
||||||
|
} else if (event == GLFW.GLFW_DISCONNECTED) {
|
||||||
|
client.getToastManager().add(new SystemToast(SystemToast.Type.TUTORIAL_HINT, new TranslatableText("lambdacontrols.controller.disconnected", jid),
|
||||||
|
null));
|
||||||
|
}
|
||||||
|
|
||||||
|
this.switchControlsMode();
|
||||||
|
});
|
||||||
|
|
||||||
|
LambdaControlsCompat.init(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is called every Minecraft tick.
|
||||||
|
*
|
||||||
|
* @param client the client instance
|
||||||
|
*/
|
||||||
|
public void onTick(@NotNull MinecraftClient client) {
|
||||||
|
this.input.tick(client);
|
||||||
|
if (this.config.getControlsMode() == ControlsMode.CONTROLLER && (client.isWindowFocused() || this.config.hasUnfocusedInput()))
|
||||||
|
this.input.tickController(client);
|
||||||
|
|
||||||
|
/*if (BINDING_RING.wasPressed()) {
|
||||||
|
client.openScreen(new RingScreen());
|
||||||
|
}*/
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onRender(MinecraftClient client) {
|
||||||
|
this.input.onRender(client.getTickDelta(), client);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when leaving a server.
|
||||||
|
*/
|
||||||
|
public void onLeave(ClientPlayNetworkHandler handler, MinecraftClient client) {
|
||||||
|
LambdaControlsFeature.resetAllAllowed();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Switches the controls mode if the auto switch is enabled.
|
||||||
|
*/
|
||||||
|
public void switchControlsMode() {
|
||||||
|
if (this.config.hasAutoSwitchMode()) {
|
||||||
|
if (this.config.getController().isGamepad()) {
|
||||||
|
this.previousControlsMode = this.config.getControlsMode();
|
||||||
|
this.config.setControlsMode(ControlsMode.CONTROLLER);
|
||||||
|
} else {
|
||||||
|
if (this.previousControlsMode == null) {
|
||||||
|
this.previousControlsMode = ControlsMode.DEFAULT;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.config.setControlsMode(this.previousControlsMode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets whether the HUD is enabled or not.
|
||||||
|
*
|
||||||
|
* @param enabled true if the HUD is enabled, else false
|
||||||
|
*/
|
||||||
|
public void setHudEnabled(boolean enabled) {
|
||||||
|
this.config.setHudEnabled(enabled);
|
||||||
|
this.hud.setVisible(enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the LambdaControls client instance.
|
||||||
|
*
|
||||||
|
* @return the LambdaControls client instance
|
||||||
|
*/
|
||||||
|
public static LambdaControlsClient get() {
|
||||||
|
return INSTANCE;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,836 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
|
||||||
|
*
|
||||||
|
* This file is part of LambdaControls.
|
||||||
|
*
|
||||||
|
* Licensed under the MIT license. For more information,
|
||||||
|
* see the LICENSE file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package dev.lambdaurora.lambdacontrols.client;
|
||||||
|
|
||||||
|
import com.electronwill.nightconfig.core.file.FileConfig;
|
||||||
|
import dev.lambdaurora.lambdacontrols.ControlsMode;
|
||||||
|
import dev.lambdaurora.lambdacontrols.LambdaControlsFeature;
|
||||||
|
import dev.lambdaurora.lambdacontrols.client.controller.ButtonBinding;
|
||||||
|
import dev.lambdaurora.lambdacontrols.client.controller.Controller;
|
||||||
|
import dev.lambdaurora.lambdacontrols.client.controller.InputManager;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
import org.lwjgl.glfw.GLFW;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import static org.lwjgl.glfw.GLFW.GLFW_GAMEPAD_AXIS_LEFT_X;
|
||||||
|
import static org.lwjgl.glfw.GLFW.GLFW_GAMEPAD_AXIS_LEFT_Y;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents LambdaControls configuration.
|
||||||
|
*/
|
||||||
|
public class LambdaControlsConfig {
|
||||||
|
// General
|
||||||
|
private static final ControlsMode DEFAULT_CONTROLS_MODE = ControlsMode.DEFAULT;
|
||||||
|
private static final boolean DEFAULT_AUTO_SWITCH_MODE = false;
|
||||||
|
private static final boolean DEFAULT_DEBUG = false;
|
||||||
|
// HUD
|
||||||
|
private static final boolean DEFAULT_HUD_ENABLE = true;
|
||||||
|
private static final HudSide DEFAULT_HUD_SIDE = HudSide.LEFT;
|
||||||
|
// Gameplay
|
||||||
|
private static final boolean DEFAULT_ANALOG_MOVEMENT = true;
|
||||||
|
private static final boolean DEFAULT_FAST_BLOCK_INTERACTION = true;
|
||||||
|
private static final boolean DEFAULT_FLY_DRIFTING = false;
|
||||||
|
private static final boolean DEFAULT_FLY_VERTICAL_DRIFTING = true;
|
||||||
|
private static final boolean DEFAULT_HORIZONTAL_REACHAROUND = false;
|
||||||
|
private static final boolean DEFAULT_VERTICAL_REACHAROUND = false;
|
||||||
|
private static final boolean DEFAULT_REACHAROUND_OUTLINE = true;
|
||||||
|
private static final int[] DEFAULT_REACHAROUND_OUTLINE_COLOR = new int[]{255, 255, 255, 102};
|
||||||
|
// Controller
|
||||||
|
private static final ControllerType DEFAULT_CONTROLLER_TYPE = ControllerType.DEFAULT;
|
||||||
|
private static final double DEFAULT_DEAD_ZONE = 0.25;
|
||||||
|
private static final double DEFAULT_MAX_VALUE = 1;
|
||||||
|
private static final double DEFAULT_ROTATION_SPEED = 40.0;
|
||||||
|
private static final double DEFAULT_MOUSE_SPEED = 25.0;
|
||||||
|
private static final boolean DEFAULT_UNFOCUSED_INPUT = false;
|
||||||
|
private static final boolean DEFAULT_VIRTUAL_MOUSE = false;
|
||||||
|
private static final VirtualMouseSkin DEFAULT_VIRTUAL_MOUSE_SKIN = VirtualMouseSkin.DEFAULT_LIGHT;
|
||||||
|
|
||||||
|
private static final Pattern BUTTON_BINDING_PATTERN = Pattern.compile("(-?\\d+)\\+?");
|
||||||
|
|
||||||
|
protected final FileConfig config = FileConfig.builder("config/lambdacontrols.toml").concurrent().defaultResource("/config.toml").build();
|
||||||
|
private final LambdaControlsClient mod;
|
||||||
|
private ControlsMode controlsMode;
|
||||||
|
private ControllerType controllerType;
|
||||||
|
// Gameplay.
|
||||||
|
private boolean analogMovement;
|
||||||
|
private boolean shouldRenderReacharoundOutline;
|
||||||
|
private int[] reacharoundOutlineColor;
|
||||||
|
// Controller settings
|
||||||
|
private double rightDeadZone;
|
||||||
|
private double leftDeadZone;
|
||||||
|
private double[] maxAnalogValues = new double[]{DEFAULT_MAX_VALUE, DEFAULT_MAX_VALUE, DEFAULT_MAX_VALUE, DEFAULT_MAX_VALUE};
|
||||||
|
private double rotationSpeed;
|
||||||
|
private double mouseSpeed;
|
||||||
|
private boolean unfocusedInput;
|
||||||
|
private boolean virtualMouse;
|
||||||
|
private VirtualMouseSkin virtualMouseSkin;
|
||||||
|
// HUD settings.
|
||||||
|
private boolean hudEnable;
|
||||||
|
private HudSide hudSide;
|
||||||
|
|
||||||
|
public LambdaControlsConfig(@NotNull LambdaControlsClient mod) {
|
||||||
|
this.mod = mod;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the configuration
|
||||||
|
*/
|
||||||
|
public void load() {
|
||||||
|
this.config.load();
|
||||||
|
this.checkAndFix();
|
||||||
|
this.mod.log("Configuration loaded.");
|
||||||
|
this.controlsMode = ControlsMode.byId(this.config.getOrElse("controls", DEFAULT_CONTROLS_MODE.getName())).orElse(DEFAULT_CONTROLS_MODE);
|
||||||
|
// HUD settings.
|
||||||
|
this.hudEnable = this.config.getOrElse("hud.enable", DEFAULT_HUD_ENABLE);
|
||||||
|
this.hudSide = HudSide.byId(this.config.getOrElse("hud.side", DEFAULT_HUD_SIDE.getName())).orElse(DEFAULT_HUD_SIDE);
|
||||||
|
// Gameplay
|
||||||
|
this.analogMovement = this.config.getOrElse("gameplay.analog_movement", DEFAULT_ANALOG_MOVEMENT);
|
||||||
|
LambdaControlsFeature.FAST_BLOCK_PLACING.setEnabled(this.config.getOrElse("gameplay.fast_block_placing", DEFAULT_FAST_BLOCK_INTERACTION));
|
||||||
|
LambdaControlsFeature.HORIZONTAL_REACHAROUND.setEnabled(this.config.getOrElse("gameplay.reacharound.horizontal", DEFAULT_HORIZONTAL_REACHAROUND));
|
||||||
|
LambdaControlsFeature.VERTICAL_REACHAROUND.setEnabled(this.config.getOrElse("gameplay.reacharound.vertical", DEFAULT_VERTICAL_REACHAROUND));
|
||||||
|
this.shouldRenderReacharoundOutline = this.config.getOrElse("gameplay.reacharound.outline", DEFAULT_REACHAROUND_OUTLINE);
|
||||||
|
this.reacharoundOutlineColor = this.config.getOptional("gameplay.reacharound.outline_color")
|
||||||
|
.map(hex -> parseColor((String) hex))
|
||||||
|
.orElse(DEFAULT_REACHAROUND_OUTLINE_COLOR);
|
||||||
|
// Controller settings.
|
||||||
|
this.controllerType = ControllerType.byId(this.config.getOrElse("controller.type", DEFAULT_CONTROLLER_TYPE.getName())).orElse(DEFAULT_CONTROLLER_TYPE);
|
||||||
|
this.rightDeadZone = this.config.getOrElse("controller.right_dead_zone", DEFAULT_DEAD_ZONE);
|
||||||
|
this.leftDeadZone = this.config.getOrElse("controller.left_dead_zone", DEFAULT_DEAD_ZONE);
|
||||||
|
this.rotationSpeed = this.config.getOrElse("controller.rotation_speed", DEFAULT_ROTATION_SPEED);
|
||||||
|
this.mouseSpeed = this.config.getOrElse("controller.mouse_speed", DEFAULT_MOUSE_SPEED);
|
||||||
|
this.unfocusedInput = this.config.getOrElse("controller.unfocused_input", DEFAULT_UNFOCUSED_INPUT);
|
||||||
|
this.virtualMouse = this.config.getOrElse("controller.virtual_mouse", DEFAULT_VIRTUAL_MOUSE);
|
||||||
|
this.virtualMouseSkin = VirtualMouseSkin.byId(this.config.getOrElse("controller.virtual_mouse_skin", DEFAULT_VIRTUAL_MOUSE_SKIN.getName())).orElse(DEFAULT_VIRTUAL_MOUSE_SKIN);
|
||||||
|
|
||||||
|
for (int i = 0; i < this.maxAnalogValues.length; i++) {
|
||||||
|
this.maxAnalogValues[i] = this.config.getOrElse("controller.max_value_" + i, DEFAULT_MAX_VALUE);
|
||||||
|
}
|
||||||
|
// Controller controls.
|
||||||
|
InputManager.loadButtonBindings(this);
|
||||||
|
|
||||||
|
this.mod.ring.load(this.config);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves the configuration.
|
||||||
|
*/
|
||||||
|
public void save() {
|
||||||
|
this.config.set("controller.right_dead_zone", this.rightDeadZone);
|
||||||
|
this.config.set("controller.left_dead_zone", this.leftDeadZone);
|
||||||
|
this.config.set("controller.rotation_speed", this.rotationSpeed);
|
||||||
|
this.config.set("controller.mouse_speed", this.mouseSpeed);
|
||||||
|
this.config.set("controller.unfocused_input", this.unfocusedInput);
|
||||||
|
this.config.set("controller.virtual_mouse", this.virtualMouse);
|
||||||
|
|
||||||
|
for (int i = 0; i < this.maxAnalogValues.length; i++) {
|
||||||
|
this.config.set("controller.max_value_" + i, this.maxAnalogValues[i]);
|
||||||
|
}
|
||||||
|
this.config.save();
|
||||||
|
this.mod.log("Configuration saved.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void checkAndFix() {
|
||||||
|
InputManager.streamBindings().forEach(binding -> {
|
||||||
|
var path = "controller.controls." + binding.getName();
|
||||||
|
var raw = this.config.getRaw(path);
|
||||||
|
if (raw instanceof Number) {
|
||||||
|
this.mod.warn("Invalid data at \"" + path + "\", fixing...");
|
||||||
|
this.config.set(path, String.valueOf(raw));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (this.config.contains("gameplay.front_block_placing.enabled")) {
|
||||||
|
this.setFrontBlockPlacing(this.config.getOrElse("gameplay.front_block_placing.enabled", DEFAULT_HORIZONTAL_REACHAROUND));
|
||||||
|
this.config.remove("gameplay.front_block_placing.enabled");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.config.contains("gameplay.front_block_placing.outline")) {
|
||||||
|
this.setRenderReacharoundOutline(this.config.getOrElse("gameplay.front_block_placing.outline", DEFAULT_REACHAROUND_OUTLINE));
|
||||||
|
this.config.remove("gameplay.front_block_placing.outline");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.config.contains("gameplay.front_block_placing.outline_color")) {
|
||||||
|
this.config.getOptional("gameplay.front_block_placing.outline_color").ifPresent(color -> this.config.set("gameplay.reacharound.outline_color", color));
|
||||||
|
this.config.remove("gameplay.front_block_placing.outline_color");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.renamed("controller.dead_zone", "controller.right_dead_zone");
|
||||||
|
this.renamed("controller.controls.tab_left", "controller.controls.tab_back");
|
||||||
|
this.renamed("controller.controls.tab_right", "controller.controls.tab_next");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void renamed(String oldPath, String newPath) {
|
||||||
|
if (!this.config.contains(oldPath))
|
||||||
|
return;
|
||||||
|
var raw = this.config.getRaw(oldPath);
|
||||||
|
this.config.remove(oldPath);
|
||||||
|
this.config.set(newPath, raw);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resets the configuration to default values.
|
||||||
|
*/
|
||||||
|
public void reset() {
|
||||||
|
// General
|
||||||
|
this.setControlsMode(DEFAULT_CONTROLS_MODE);
|
||||||
|
this.setAutoSwitchMode(DEFAULT_AUTO_SWITCH_MODE);
|
||||||
|
this.setDebug(DEFAULT_DEBUG);
|
||||||
|
// Gameplay
|
||||||
|
this.setAnalogMovement(DEFAULT_ANALOG_MOVEMENT);
|
||||||
|
this.setFastBlockPlacing(DEFAULT_FAST_BLOCK_INTERACTION);
|
||||||
|
this.setFlyDrifting(DEFAULT_FLY_DRIFTING);
|
||||||
|
this.setFlyVerticalDrifting(DEFAULT_FLY_VERTICAL_DRIFTING);
|
||||||
|
this.setFrontBlockPlacing(DEFAULT_HORIZONTAL_REACHAROUND);
|
||||||
|
this.setVerticalReacharound(DEFAULT_VERTICAL_REACHAROUND);
|
||||||
|
this.setRenderReacharoundOutline(DEFAULT_REACHAROUND_OUTLINE);
|
||||||
|
// Controller
|
||||||
|
this.setControllerType(DEFAULT_CONTROLLER_TYPE);
|
||||||
|
this.setRightDeadZone(DEFAULT_DEAD_ZONE);
|
||||||
|
this.setLeftDeadZone(DEFAULT_DEAD_ZONE);
|
||||||
|
this.setRotationSpeed(DEFAULT_ROTATION_SPEED);
|
||||||
|
this.setMouseSpeed(DEFAULT_MOUSE_SPEED);
|
||||||
|
this.setUnfocusedInput(DEFAULT_UNFOCUSED_INPUT);
|
||||||
|
this.setVirtualMouse(DEFAULT_VIRTUAL_MOUSE);
|
||||||
|
this.setVirtualMouseSkin(DEFAULT_VIRTUAL_MOUSE_SKIN);
|
||||||
|
|
||||||
|
Arrays.fill(this.maxAnalogValues, DEFAULT_MAX_VALUE);
|
||||||
|
// HUD
|
||||||
|
this.setHudEnabled(DEFAULT_HUD_ENABLE);
|
||||||
|
this.setHudSide(DEFAULT_HUD_SIDE);
|
||||||
|
|
||||||
|
// Collect prevents concurrent modification.
|
||||||
|
InputManager.streamBindings().collect(Collectors.toList()).forEach(binding -> this.setButtonBinding(binding, binding.getDefaultButton()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the controls mode from the configuration.
|
||||||
|
*
|
||||||
|
* @return the controls mode
|
||||||
|
*/
|
||||||
|
public @NotNull ControlsMode getControlsMode() {
|
||||||
|
return this.controlsMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the controls mode in the configuration.
|
||||||
|
*
|
||||||
|
* @param controlsMode the controls mode
|
||||||
|
*/
|
||||||
|
public void setControlsMode(@NotNull ControlsMode controlsMode) {
|
||||||
|
this.controlsMode = controlsMode;
|
||||||
|
this.config.set("controls", controlsMode.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether the auto switch mode is enabled or not.
|
||||||
|
*
|
||||||
|
* @return true if the auto switch mode is enabled, else false
|
||||||
|
*/
|
||||||
|
public boolean hasAutoSwitchMode() {
|
||||||
|
return this.config.getOrElse("auto_switch_mode", DEFAULT_AUTO_SWITCH_MODE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets whether the auto switch mode is enabled or not.
|
||||||
|
*
|
||||||
|
* @param autoSwitchMode true if the auto switch mode is enabled, else false
|
||||||
|
*/
|
||||||
|
public void setAutoSwitchMode(boolean autoSwitchMode) {
|
||||||
|
this.config.set("auto_switch_mode", autoSwitchMode);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether the mod has debug enabled or not.
|
||||||
|
*
|
||||||
|
* @return true if debug is enabled, else false
|
||||||
|
*/
|
||||||
|
public boolean hasDebug() {
|
||||||
|
return this.config.getOrElse("debug", DEFAULT_DEBUG);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets whether the mod has debug enabled or not.
|
||||||
|
*
|
||||||
|
* @param debug true if debug is enabled, else false
|
||||||
|
*/
|
||||||
|
protected void setDebug(boolean debug) {
|
||||||
|
this.config.set("debug", debug);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
HUD settings
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether the HUD is enabled.
|
||||||
|
*
|
||||||
|
* @return true if the HUD is enabled, else false
|
||||||
|
*/
|
||||||
|
public boolean isHudEnabled() {
|
||||||
|
return this.hudEnable;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets whether the HUD is enabled.
|
||||||
|
*
|
||||||
|
* @param enable true if the HUD is enabled, else false
|
||||||
|
*/
|
||||||
|
public void setHudEnabled(boolean enable) {
|
||||||
|
this.hudEnable = enable;
|
||||||
|
this.config.set("hud.enable", this.hudEnable);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the HUD side from the configuration.
|
||||||
|
*
|
||||||
|
* @return the HUD side
|
||||||
|
*/
|
||||||
|
public @NotNull HudSide getHudSide() {
|
||||||
|
return this.hudSide;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the HUD side in the configuration.
|
||||||
|
*
|
||||||
|
* @param hudSide the HUD side
|
||||||
|
*/
|
||||||
|
public void setHudSide(@NotNull HudSide hudSide) {
|
||||||
|
this.hudSide = hudSide;
|
||||||
|
this.config.set("hud.side", hudSide.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Gameplay settings
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets whether analog movement is enabled.
|
||||||
|
*
|
||||||
|
* @return {@code true} if analog movement is enabled, else {@code false}
|
||||||
|
*/
|
||||||
|
public boolean hasAnalogMovement() {
|
||||||
|
return this.analogMovement;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets whether analog movement is enabled.
|
||||||
|
*
|
||||||
|
* @param analogMovement {@code true} if analog movement is enabled, else {@code false}
|
||||||
|
*/
|
||||||
|
public void setAnalogMovement(boolean analogMovement) {
|
||||||
|
this.config.set("gameplay.analog_movement", this.analogMovement = analogMovement);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets whether fast block placing is enabled or not.
|
||||||
|
*
|
||||||
|
* @return true if fast block placing is enabled, else false
|
||||||
|
*/
|
||||||
|
public boolean hasFastBlockPlacing() {
|
||||||
|
return LambdaControlsFeature.FAST_BLOCK_PLACING.isEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets whether fast block placing is enabled or not.
|
||||||
|
*
|
||||||
|
* @param enable true if fast block placing is enabled, else false
|
||||||
|
*/
|
||||||
|
public void setFastBlockPlacing(boolean enable) {
|
||||||
|
LambdaControlsFeature.FAST_BLOCK_PLACING.setEnabled(enable);
|
||||||
|
this.config.set("gameplay.fast_block_placing", enable);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether fly drifting is enabled or not.
|
||||||
|
*
|
||||||
|
* @return true if fly drifting is enabled, else false
|
||||||
|
*/
|
||||||
|
public boolean hasFlyDrifting() {
|
||||||
|
return this.config.getOrElse("gameplay.fly.drifting", DEFAULT_FLY_DRIFTING);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets whether fly drifting is enabled or not.
|
||||||
|
*
|
||||||
|
* @param flyDrifting true if fly drifting is enabled, else false
|
||||||
|
*/
|
||||||
|
public void setFlyDrifting(boolean flyDrifting) {
|
||||||
|
this.config.set("gameplay.fly.drifting", flyDrifting);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether vertical fly drifting is enabled or not.
|
||||||
|
*
|
||||||
|
* @return true if vertical fly drifting is enabled, else false
|
||||||
|
*/
|
||||||
|
public boolean hasFlyVerticalDrifting() {
|
||||||
|
return this.config.getOrElse("gameplay.fly.vertical_drifting", DEFAULT_FLY_VERTICAL_DRIFTING);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets whether vertical fly drifting is enabled or not.
|
||||||
|
*
|
||||||
|
* @param flyDrifting true if vertical fly drifting is enabled, else false
|
||||||
|
*/
|
||||||
|
public void setFlyVerticalDrifting(boolean flyDrifting) {
|
||||||
|
this.config.set("gameplay.fly.vertical_drifting", flyDrifting);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether front block placing is enabled or not.
|
||||||
|
*
|
||||||
|
* @return true if front block placing is enabled, else false
|
||||||
|
*/
|
||||||
|
public boolean hasFrontBlockPlacing() {
|
||||||
|
return LambdaControlsFeature.HORIZONTAL_REACHAROUND.isEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets whether front block placing is enabled or not.
|
||||||
|
*
|
||||||
|
* @param enable true if front block placing is enabled, else false
|
||||||
|
*/
|
||||||
|
public void setFrontBlockPlacing(boolean enable) {
|
||||||
|
LambdaControlsFeature.HORIZONTAL_REACHAROUND.setEnabled(enable);
|
||||||
|
this.config.set("gameplay.reacharound.horizontal", enable);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether vertical reacharound is enabled or not.
|
||||||
|
*
|
||||||
|
* @return true if vertical reacharound is enabled, else false
|
||||||
|
*/
|
||||||
|
public boolean hasVerticalReacharound() {
|
||||||
|
return LambdaControlsFeature.VERTICAL_REACHAROUND.isEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets whether vertical reacharound is enabled or not.
|
||||||
|
*
|
||||||
|
* @param enable true if vertical reacharound is enabled, else false
|
||||||
|
*/
|
||||||
|
public void setVerticalReacharound(boolean enable) {
|
||||||
|
LambdaControlsFeature.VERTICAL_REACHAROUND.setEnabled(enable);
|
||||||
|
this.config.set("gameplay.reacharound.vertical", enable);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether front block placing outline is enabled or not.
|
||||||
|
*
|
||||||
|
* @return true if front block placing outline is enabled, else false
|
||||||
|
*/
|
||||||
|
public boolean shouldRenderReacharoundOutline() {
|
||||||
|
return this.shouldRenderReacharoundOutline;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets whether front block placing outline is enabled or not.
|
||||||
|
*
|
||||||
|
* @param render true if front block placing outline is enabled, else false
|
||||||
|
*/
|
||||||
|
public void setRenderReacharoundOutline(boolean render) {
|
||||||
|
this.config.set("gameplay.reacharound.outline", this.shouldRenderReacharoundOutline = render);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the front block placing outline color as an integer array.
|
||||||
|
* <p>
|
||||||
|
* The integer array has 4 elements: red, green, blue and alpha.
|
||||||
|
*
|
||||||
|
* @return the color as a RGBA integer array
|
||||||
|
*/
|
||||||
|
public int[] getReacharoundOutlineColor() {
|
||||||
|
return this.reacharoundOutlineColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Controller settings
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the used controller.
|
||||||
|
*
|
||||||
|
* @return the controller
|
||||||
|
*/
|
||||||
|
public Controller getController() {
|
||||||
|
var raw = this.config.getRaw("controller.id");
|
||||||
|
if (raw instanceof Number) {
|
||||||
|
return Controller.byId((Integer) raw);
|
||||||
|
} else if (raw instanceof String) {
|
||||||
|
return Controller.byGuid((String) raw).orElse(Controller.byId(GLFW.GLFW_JOYSTICK_1));
|
||||||
|
}
|
||||||
|
return Controller.byId(GLFW.GLFW_JOYSTICK_1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the used controller.
|
||||||
|
*
|
||||||
|
* @param controller the controller
|
||||||
|
*/
|
||||||
|
public void setController(Controller controller) {
|
||||||
|
this.config.set("controller.id", controller.id());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the second controller (for Joy-Con supports).
|
||||||
|
*
|
||||||
|
* @return the second controller
|
||||||
|
*/
|
||||||
|
public Optional<Controller> getSecondController() {
|
||||||
|
var raw = this.config.getRaw("controller.id2");
|
||||||
|
if (raw instanceof Number) {
|
||||||
|
if ((int) raw == -1)
|
||||||
|
return Optional.empty();
|
||||||
|
return Optional.of(Controller.byId((Integer) raw));
|
||||||
|
} else if (raw instanceof String) {
|
||||||
|
return Optional.of(Controller.byGuid((String) raw).orElse(Controller.byId(GLFW.GLFW_JOYSTICK_1)));
|
||||||
|
}
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the second controller.
|
||||||
|
*
|
||||||
|
* @param controller the second controller
|
||||||
|
*/
|
||||||
|
public void setSecondController(@Nullable Controller controller) {
|
||||||
|
this.config.set("controller.id2", controller == null ? -1 : controller.id());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the controller's type.
|
||||||
|
*
|
||||||
|
* @return the controller's type
|
||||||
|
*/
|
||||||
|
public @NotNull ControllerType getControllerType() {
|
||||||
|
return this.controllerType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the controller's type.
|
||||||
|
*
|
||||||
|
* @param controllerType the controller's type
|
||||||
|
*/
|
||||||
|
public void setControllerType(@NotNull ControllerType controllerType) {
|
||||||
|
this.controllerType = controllerType;
|
||||||
|
this.config.set("controller.type", controllerType.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the controller's right dead zone from the configuration.
|
||||||
|
*
|
||||||
|
* @return the controller's right dead zone value
|
||||||
|
*/
|
||||||
|
public double getRightDeadZone() {
|
||||||
|
return this.rightDeadZone;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the controller's right dead zone in the configuration.
|
||||||
|
*
|
||||||
|
* @param deadZone the controller's right dead zone value
|
||||||
|
*/
|
||||||
|
public void setRightDeadZone(double deadZone) {
|
||||||
|
this.rightDeadZone = deadZone;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the controller's left dead zone from the configuration.
|
||||||
|
*
|
||||||
|
* @return the controller's left dead zone value
|
||||||
|
*/
|
||||||
|
public double getLeftDeadZone() {
|
||||||
|
return this.leftDeadZone;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the controller's left dead zone in the configuration.
|
||||||
|
*
|
||||||
|
* @param deadZone the controller's left dead zone value
|
||||||
|
*/
|
||||||
|
public void setLeftDeadZone(double deadZone) {
|
||||||
|
this.leftDeadZone = deadZone;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the controller's rotation speed.
|
||||||
|
*
|
||||||
|
* @return the rotation speed
|
||||||
|
*/
|
||||||
|
public double getRotationSpeed() {
|
||||||
|
return this.rotationSpeed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the controller's rotation speed.
|
||||||
|
*
|
||||||
|
* @param rotationSpeed the rotation speed
|
||||||
|
*/
|
||||||
|
public void setRotationSpeed(double rotationSpeed) {
|
||||||
|
this.rotationSpeed = rotationSpeed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the controller's mouse speed.
|
||||||
|
*
|
||||||
|
* @return the mouse speed
|
||||||
|
*/
|
||||||
|
public double getMouseSpeed() {
|
||||||
|
return this.mouseSpeed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the controller's mouse speed.
|
||||||
|
*
|
||||||
|
* @param mouseSpeed the mouse speed
|
||||||
|
*/
|
||||||
|
public void setMouseSpeed(double mouseSpeed) {
|
||||||
|
this.mouseSpeed = mouseSpeed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether the right X axis is inverted or not.
|
||||||
|
*
|
||||||
|
* @return true if the right X axis is inverted, else false
|
||||||
|
*/
|
||||||
|
public boolean doesInvertRightXAxis() {
|
||||||
|
return this.config.getOrElse("controller.invert_right_x_axis", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets whether the right X axis is inverted or not.
|
||||||
|
*
|
||||||
|
* @param invert true if the right X axis is inverted, else false
|
||||||
|
*/
|
||||||
|
public void setInvertRightXAxis(boolean invert) {
|
||||||
|
this.config.set("controller.invert_right_x_axis", invert);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether the right Y axis is inverted or not.
|
||||||
|
*
|
||||||
|
* @return true if the right Y axis is inverted, else false
|
||||||
|
*/
|
||||||
|
public boolean doesInvertRightYAxis() {
|
||||||
|
return this.config.getOrElse("controller.invert_right_y_axis", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets whether the right Y axis is inverted or not.
|
||||||
|
*
|
||||||
|
* @param invert true if the right Y axis is inverted, else false
|
||||||
|
*/
|
||||||
|
public void setInvertRightYAxis(boolean invert) {
|
||||||
|
this.config.set("controller.invert_right_y_axis", invert);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether unfocused controller input is allowed or not.
|
||||||
|
*
|
||||||
|
* @return true if unfocused controller input is allowed, else false
|
||||||
|
*/
|
||||||
|
public boolean hasUnfocusedInput() {
|
||||||
|
return this.unfocusedInput;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets whether unfocused controller input is allowed or not.
|
||||||
|
*
|
||||||
|
* @param unfocusedInput true if unfocused controller input is allowed, else false
|
||||||
|
*/
|
||||||
|
public void setUnfocusedInput(boolean unfocusedInput) {
|
||||||
|
this.unfocusedInput = unfocusedInput;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether the mouse is virtual or not.
|
||||||
|
*
|
||||||
|
* @return true if the mouse is virtual, else false
|
||||||
|
*/
|
||||||
|
public boolean hasVirtualMouse() {
|
||||||
|
return this.virtualMouse;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets whether the mouse is virtual or not.
|
||||||
|
*
|
||||||
|
* @param virtualMouse true if the mouse is virtual, else false
|
||||||
|
*/
|
||||||
|
public void setVirtualMouse(boolean virtualMouse) {
|
||||||
|
this.virtualMouse = virtualMouse;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the virtual mouse skin.
|
||||||
|
*
|
||||||
|
* @return the virtual mouse skin
|
||||||
|
*/
|
||||||
|
public VirtualMouseSkin getVirtualMouseSkin() {
|
||||||
|
return this.virtualMouseSkin;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the virtual mouse skin.
|
||||||
|
*
|
||||||
|
* @param skin the virtual mouse skin
|
||||||
|
*/
|
||||||
|
public void setVirtualMouseSkin(VirtualMouseSkin skin) {
|
||||||
|
this.virtualMouseSkin = skin;
|
||||||
|
this.config.set("controller.virtual_mouse_skin", skin.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the right X axis sign.
|
||||||
|
*
|
||||||
|
* @return the right X axis sign
|
||||||
|
*/
|
||||||
|
public double getRightXAxisSign() {
|
||||||
|
return this.doesInvertRightXAxis() ? -1.0 : 1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the right Y axis sign.
|
||||||
|
*
|
||||||
|
* @return the right Y axis sign
|
||||||
|
*/
|
||||||
|
public double getRightYAxisSign() {
|
||||||
|
return this.doesInvertRightYAxis() ? -1.0 : 1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getAxisMaxValue(int axis) {
|
||||||
|
if (axis >= this.maxAnalogValues.length)
|
||||||
|
return DEFAULT_MAX_VALUE;
|
||||||
|
return this.maxAnalogValues[axis];
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAxisMaxValue(int axis, double value) {
|
||||||
|
if (axis < this.maxAnalogValues.length)
|
||||||
|
this.maxAnalogValues[axis] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the button binding from configuration.
|
||||||
|
*
|
||||||
|
* @param button the button binding
|
||||||
|
*/
|
||||||
|
public void loadButtonBinding(@NotNull ButtonBinding button) {
|
||||||
|
button.setButton(button.getDefaultButton());
|
||||||
|
var code = this.config.getOrElse("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 (!this.checkValidity(button, code, current = matcher.group(1)))
|
||||||
|
return;
|
||||||
|
buttons[count - 1] = Integer.parseInt(current);
|
||||||
|
}
|
||||||
|
if (count == 0) {
|
||||||
|
this.mod.warn("Malformed config value \"" + code + "\" for binding \"" + button.getName() + "\".");
|
||||||
|
this.setButtonBinding(button, new int[]{-1});
|
||||||
|
}
|
||||||
|
|
||||||
|
button.setButton(buttons);
|
||||||
|
} catch (Exception e) {
|
||||||
|
this.mod.warn("Malformed config value \"" + code + "\" for binding \"" + button.getName() + "\".");
|
||||||
|
this.config.set("controller.controls." + button.getName(), button.getButtonCode());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean checkValidity(@NotNull ButtonBinding binding, @NotNull String input, String group) {
|
||||||
|
if (group == null) {
|
||||||
|
this.mod.warn("Malformed config value \"" + input + "\" for binding \"" + binding.getName() + "\".");
|
||||||
|
this.config.set("controller.controls." + binding.getName(), binding.getButtonCode());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the button binding in configuration.
|
||||||
|
*
|
||||||
|
* @param binding the button binding
|
||||||
|
* @param button the button
|
||||||
|
*/
|
||||||
|
public void setButtonBinding(@NotNull ButtonBinding binding, int[] button) {
|
||||||
|
binding.setButton(button);
|
||||||
|
this.config.set("controller.controls." + binding.getName(), binding.getButtonCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isBackButton(int btn, boolean isBtn, int state) {
|
||||||
|
if (!isBtn && state == 0)
|
||||||
|
return false;
|
||||||
|
return ButtonBinding.axisAsButton(GLFW_GAMEPAD_AXIS_LEFT_Y, false) == ButtonBinding.axisAsButton(btn, state == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isForwardButton(int btn, boolean isBtn, int state) {
|
||||||
|
if (!isBtn && state == 0)
|
||||||
|
return false;
|
||||||
|
return ButtonBinding.axisAsButton(GLFW_GAMEPAD_AXIS_LEFT_Y, true) == ButtonBinding.axisAsButton(btn, state == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isLeftButton(int btn, boolean isBtn, int state) {
|
||||||
|
if (!isBtn && state == 0)
|
||||||
|
return false;
|
||||||
|
return ButtonBinding.axisAsButton(GLFW_GAMEPAD_AXIS_LEFT_X, false) == ButtonBinding.axisAsButton(btn, state == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isRightButton(int btn, boolean isBtn, int state) {
|
||||||
|
if (!isBtn && state == 0)
|
||||||
|
return false;
|
||||||
|
return ButtonBinding.axisAsButton(GLFW_GAMEPAD_AXIS_LEFT_X, true) == ButtonBinding.axisAsButton(btn, state == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether the specified axis is an axis used for movements.
|
||||||
|
*
|
||||||
|
* @param axis the axis index
|
||||||
|
* @return true if the axis is used for movements, else false
|
||||||
|
*/
|
||||||
|
public boolean isMovementAxis(int axis) {
|
||||||
|
return axis == GLFW_GAMEPAD_AXIS_LEFT_Y || axis == GLFW_GAMEPAD_AXIS_LEFT_X;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses a color from a hexadecimal color string.
|
||||||
|
*
|
||||||
|
* @param hex the hexadecimal color
|
||||||
|
* @return the color instance, null if invalid
|
||||||
|
*/
|
||||||
|
private static int[] parseColor(String hex) {
|
||||||
|
hex = hex.replace("#", "");
|
||||||
|
return switch (hex.length()) {
|
||||||
|
case 6 -> new int[]{
|
||||||
|
Integer.valueOf(hex.substring(0, 2), 16),
|
||||||
|
Integer.valueOf(hex.substring(2, 4), 16),
|
||||||
|
Integer.valueOf(hex.substring(4, 6), 16),
|
||||||
|
255
|
||||||
|
};
|
||||||
|
case 8 -> new int[]{
|
||||||
|
Integer.valueOf(hex.substring(0, 2), 16),
|
||||||
|
Integer.valueOf(hex.substring(2, 4), 16),
|
||||||
|
Integer.valueOf(hex.substring(4, 6), 16),
|
||||||
|
Integer.valueOf(hex.substring(6, 8), 16)
|
||||||
|
};
|
||||||
|
default -> null;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
|
||||||
|
*
|
||||||
|
* This file is part of LambdaControls.
|
||||||
|
*
|
||||||
|
* Licensed under the MIT license. For more information,
|
||||||
|
* see the LICENSE file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package dev.lambdaurora.lambdacontrols.client;
|
||||||
|
|
||||||
|
import com.terraformersmc.modmenu.api.ConfigScreenFactory;
|
||||||
|
import com.terraformersmc.modmenu.api.ModMenuApi;
|
||||||
|
import dev.lambdaurora.lambdacontrols.client.gui.LambdaControlsSettingsScreen;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the API implementation of ModMenu for LambdaControls.
|
||||||
|
*
|
||||||
|
* @author LambdAurora
|
||||||
|
* @version 1.7.0
|
||||||
|
* @since 1.1.0
|
||||||
|
*/
|
||||||
|
public class LambdaControlsModMenu implements ModMenuApi {
|
||||||
|
@Override
|
||||||
|
public ConfigScreenFactory<?> getModConfigScreenFactory() {
|
||||||
|
return parent -> new LambdaControlsSettingsScreen(parent, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,771 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
|
||||||
|
*
|
||||||
|
* This file is part of LambdaControls.
|
||||||
|
*
|
||||||
|
* Licensed under the MIT license. For more information,
|
||||||
|
* see the LICENSE file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package dev.lambdaurora.lambdacontrols.client;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableSet;
|
||||||
|
import dev.lambdaurora.lambdacontrols.client.compat.LambdaControlsCompat;
|
||||||
|
import dev.lambdaurora.lambdacontrols.client.controller.ButtonBinding;
|
||||||
|
import dev.lambdaurora.lambdacontrols.client.controller.Controller;
|
||||||
|
import dev.lambdaurora.lambdacontrols.client.controller.InputManager;
|
||||||
|
import dev.lambdaurora.lambdacontrols.client.gui.TouchscreenOverlay;
|
||||||
|
import dev.lambdaurora.lambdacontrols.client.gui.widget.ControllerControlsWidget;
|
||||||
|
import dev.lambdaurora.lambdacontrols.client.mixin.AdvancementsScreenAccessor;
|
||||||
|
import dev.lambdaurora.lambdacontrols.client.mixin.CreativeInventoryScreenAccessor;
|
||||||
|
import dev.lambdaurora.lambdacontrols.client.mixin.EntryListWidgetAccessor;
|
||||||
|
import dev.lambdaurora.lambdacontrols.client.util.HandledScreenAccessor;
|
||||||
|
import dev.lambdaurora.lambdacontrols.client.util.MouseAccessor;
|
||||||
|
import 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 dev.lambdaurora.lambdacontrols.client.controller.ButtonBinding.axisAsButton;
|
||||||
|
import static dev.lambdaurora.lambdacontrols.client.controller.InputManager.INPUT_MANAGER;
|
||||||
|
import static org.lwjgl.glfw.GLFW.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the LambdaControls' input handler.
|
||||||
|
*
|
||||||
|
* @author LambdAurora
|
||||||
|
* @version 1.7.0
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public class LambdaInput {
|
||||||
|
private static final Map<Integer, Integer> BUTTON_COOLDOWNS = new HashMap<>();
|
||||||
|
private final LambdaControlsConfig config;
|
||||||
|
// Cooldowns
|
||||||
|
private int actionGuiCooldown = 0;
|
||||||
|
private boolean ignoreNextARelease = false;
|
||||||
|
private double targetYaw = 0.0;
|
||||||
|
private double targetPitch = 0.0;
|
||||||
|
private float prevXAxis = 0.f;
|
||||||
|
private float prevYAxis = 0.f;
|
||||||
|
private int targetMouseX = 0;
|
||||||
|
private int targetMouseY = 0;
|
||||||
|
private float mouseSpeedX = 0.f;
|
||||||
|
private float mouseSpeedY = 0.f;
|
||||||
|
private int inventoryInteractionCooldown = 0;
|
||||||
|
|
||||||
|
private ControllerControlsWidget controlsInput = null;
|
||||||
|
|
||||||
|
public LambdaInput(@NotNull LambdaControlsClient mod) {
|
||||||
|
this.config = mod.config;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is called every Minecraft tick.
|
||||||
|
*
|
||||||
|
* @param client the client instance
|
||||||
|
*/
|
||||||
|
public void tick(@NotNull MinecraftClient client) {
|
||||||
|
this.targetYaw = 0.F;
|
||||||
|
this.targetPitch = 0.F;
|
||||||
|
|
||||||
|
// Handles the key bindings.
|
||||||
|
if (LambdaControlsClient.BINDING_LOOK_UP.isPressed()) {
|
||||||
|
this.handleLook(client, GLFW_GAMEPAD_AXIS_RIGHT_Y, 0.8F, 2);
|
||||||
|
} else if (LambdaControlsClient.BINDING_LOOK_DOWN.isPressed()) {
|
||||||
|
this.handleLook(client, GLFW_GAMEPAD_AXIS_RIGHT_Y, 0.8F, 1);
|
||||||
|
}
|
||||||
|
if (LambdaControlsClient.BINDING_LOOK_LEFT.isPressed()) {
|
||||||
|
this.handleLook(client, GLFW_GAMEPAD_AXIS_RIGHT_X, 0.8F, 2);
|
||||||
|
} else if (LambdaControlsClient.BINDING_LOOK_RIGHT.isPressed()) {
|
||||||
|
this.handleLook(client, GLFW_GAMEPAD_AXIS_RIGHT_X, 0.8F, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
INPUT_MANAGER.tick(client);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is called every Minecraft tick for controller input update.
|
||||||
|
*
|
||||||
|
* @param client the client instance
|
||||||
|
*/
|
||||||
|
public void tickController(@NotNull MinecraftClient client) {
|
||||||
|
BUTTON_COOLDOWNS.entrySet().stream().filter(entry -> entry.getValue() > 0)
|
||||||
|
.forEach(entry -> BUTTON_COOLDOWNS.put(entry.getKey(), entry.getValue() - 1));
|
||||||
|
// Decreases the cooldown for GUI actions.
|
||||||
|
if (this.actionGuiCooldown > 0)
|
||||||
|
--this.actionGuiCooldown;
|
||||||
|
|
||||||
|
InputManager.updateStates();
|
||||||
|
|
||||||
|
var controller = this.config.getController();
|
||||||
|
if (controller.isConnected()) {
|
||||||
|
var state = controller.getState();
|
||||||
|
this.fetchButtonInput(client, state, false);
|
||||||
|
this.fetchAxeInput(client, state, false);
|
||||||
|
}
|
||||||
|
this.config.getSecondController().filter(Controller::isConnected)
|
||||||
|
.ifPresent(joycon -> {
|
||||||
|
GLFWGamepadState state = joycon.getState();
|
||||||
|
this.fetchButtonInput(client, state, true);
|
||||||
|
this.fetchAxeInput(client, state, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
boolean allowInput = true;
|
||||||
|
|
||||||
|
if (this.controlsInput != null && this.controlsInput.focusedBinding != null)
|
||||||
|
allowInput = false;
|
||||||
|
|
||||||
|
if (allowInput)
|
||||||
|
InputManager.updateBindings(client);
|
||||||
|
|
||||||
|
if (this.controlsInput != null
|
||||||
|
&& InputManager.STATES.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)) {
|
||||||
|
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;
|
||||||
|
INPUT_MANAGER.resetMousePosition(windowWidth, windowHeight);
|
||||||
|
} else if (isScreenInteractive(client.currentScreen) && this.config.hasVirtualMouse()) {
|
||||||
|
((MouseAccessor) client.mouse).lambdacontrols$onCursorPos(client.getWindow().getHandle(), 0, 0);
|
||||||
|
INPUT_MANAGER.resetMouseTarget(client);
|
||||||
|
}
|
||||||
|
this.inventoryInteractionCooldown = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void beginControlsInput(ControllerControlsWidget widget) {
|
||||||
|
this.controlsInput = widget;
|
||||||
|
if (widget != null) {
|
||||||
|
this.controlsInput.currentButtons.clear();
|
||||||
|
this.controlsInput.waiting = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void fetchButtonInput(@NotNull MinecraftClient client, @NotNull GLFWGamepadState gamepadState, boolean leftJoycon) {
|
||||||
|
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 > this.config.getRightDeadZone() ? 1 : (value < -this.config.getRightDeadZone() ? 2 : 0);
|
||||||
|
this.handleAxe(client, axis, value, absValue, state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleButton(@NotNull MinecraftClient client, int button, int action, boolean state) {
|
||||||
|
if (this.controlsInput != null && this.controlsInput.focusedBinding != null) {
|
||||||
|
if (action == 0 && !this.controlsInput.currentButtons.contains(button)) {
|
||||||
|
this.controlsInput.currentButtons.add(button);
|
||||||
|
|
||||||
|
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 (!LambdaControlsCompat.handleMenuBack(client, client.currentScreen))
|
||||||
|
if (!this.tryGoBack(client.currentScreen))
|
||||||
|
client.currentScreen.onClose();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (button == GLFW.GLFW_GAMEPAD_BUTTON_A && client.currentScreen != null && !isScreenInteractive(client.currentScreen)
|
||||||
|
&& this.actionGuiCooldown == 0) {
|
||||||
|
if (!this.ignoreNextARelease) {
|
||||||
|
double mouseX = client.mouse.getX() * (double) client.getWindow().getScaledWidth() / (double) client.getWindow().getWidth();
|
||||||
|
double mouseY = client.mouse.getY() * (double) client.getWindow().getScaledHeight() / (double) client.getWindow().getHeight();
|
||||||
|
if (action == 0) {
|
||||||
|
Screen.wrapScreenError(() -> client.currentScreen.mouseClicked(mouseX, mouseY, GLFW.GLFW_MOUSE_BUTTON_1),
|
||||||
|
"mouseClicked event handler", client.currentScreen.getClass().getCanonicalName());
|
||||||
|
} else if (action == 1) {
|
||||||
|
Screen.wrapScreenError(() -> client.currentScreen.mouseReleased(mouseX, mouseY, GLFW.GLFW_MOUSE_BUTTON_1),
|
||||||
|
"mouseReleased event handler", client.currentScreen.getClass().getCanonicalName());
|
||||||
|
}
|
||||||
|
this.actionGuiCooldown = 5;
|
||||||
|
} else {
|
||||||
|
this.ignoreNextARelease = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles inventory interaction.
|
||||||
|
*
|
||||||
|
* @param client the client instance
|
||||||
|
* @param button the button pressed
|
||||||
|
* @return {@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).lambdacontrols$getSlotAt(x, y);
|
||||||
|
|
||||||
|
int slotId;
|
||||||
|
if (slot == null) {
|
||||||
|
if (client.player.currentScreenHandler.getCursorStack().isEmpty())
|
||||||
|
return false;
|
||||||
|
slotId = accessor.lambdacontrols$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).lambdacontrols$isCreativeInventorySlot(slot))
|
||||||
|
actionType = SlotActionType.CLONE;
|
||||||
|
if (slot != null && LambdaControlsCompat.streamCompatHandlers().anyMatch(handler -> handler.isCreativeSlot(screen, slot)))
|
||||||
|
actionType = SlotActionType.CLONE;
|
||||||
|
break;
|
||||||
|
case GLFW.GLFW_GAMEPAD_BUTTON_X:
|
||||||
|
clickData = GLFW_MOUSE_BUTTON_2;
|
||||||
|
break;
|
||||||
|
case GLFW.GLFW_GAMEPAD_BUTTON_Y:
|
||||||
|
actionType = SlotActionType.QUICK_MOVE;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
accessor.lambdacontrols$onMouseClick(slot, slotId, clickData, actionType);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tries to go back.
|
||||||
|
*
|
||||||
|
* @param screen the current screen
|
||||||
|
* @return true if successful, else false
|
||||||
|
*/
|
||||||
|
public boolean tryGoBack(@NotNull Screen screen) {
|
||||||
|
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) ? this.config.getLeftDeadZone()
|
||||||
|
: this.config.getRightDeadZone();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleAxe(@NotNull MinecraftClient client, int axis, float value, float absValue, int state) {
|
||||||
|
int asButtonState = value > .5f ? 1 : (value < -.5f ? 2 : 0);
|
||||||
|
|
||||||
|
if (axis == GLFW_GAMEPAD_AXIS_LEFT_TRIGGER || axis == GLFW_GAMEPAD_AXIS_RIGHT_TRIGGER
|
||||||
|
|| axis == ButtonBinding.controller2Button(GLFW.GLFW_GAMEPAD_AXIS_LEFT_TRIGGER)
|
||||||
|
|| axis == ButtonBinding.controller2Button(GLFW.GLFW_GAMEPAD_AXIS_RIGHT_TRIGGER))
|
||||||
|
if (asButtonState == 2)
|
||||||
|
asButtonState = 0;
|
||||||
|
|
||||||
|
{
|
||||||
|
boolean currentPlusState = asButtonState == 1;
|
||||||
|
boolean currentMinusState = asButtonState == 2;
|
||||||
|
var previousPlusState = InputManager.STATES.getOrDefault(axisAsButton(axis, true), ButtonState.NONE);
|
||||||
|
var previousMinusState = InputManager.STATES.getOrDefault(axisAsButton(axis, false), ButtonState.NONE);
|
||||||
|
|
||||||
|
if (currentPlusState != previousPlusState.isPressed()) {
|
||||||
|
InputManager.STATES.put(axisAsButton(axis, true), currentPlusState ? ButtonState.PRESS : ButtonState.RELEASE);
|
||||||
|
if (currentPlusState)
|
||||||
|
BUTTON_COOLDOWNS.put(axisAsButton(axis, true), 5);
|
||||||
|
} else if (currentPlusState) {
|
||||||
|
InputManager.STATES.put(axisAsButton(axis, true), ButtonState.REPEAT);
|
||||||
|
if (BUTTON_COOLDOWNS.getOrDefault(axisAsButton(axis, true), 0) == 0) {
|
||||||
|
BUTTON_COOLDOWNS.put(axisAsButton(axis, true), 5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentMinusState != previousMinusState.isPressed()) {
|
||||||
|
InputManager.STATES.put(axisAsButton(axis, false), currentMinusState ? ButtonState.PRESS : ButtonState.RELEASE);
|
||||||
|
if (currentMinusState)
|
||||||
|
BUTTON_COOLDOWNS.put(axisAsButton(axis, false), 5);
|
||||||
|
} else if (currentMinusState) {
|
||||||
|
InputManager.STATES.put(axisAsButton(axis, false), ButtonState.REPEAT);
|
||||||
|
if (BUTTON_COOLDOWNS.getOrDefault(axisAsButton(axis, false), 0) == 0) {
|
||||||
|
BUTTON_COOLDOWNS.put(axisAsButton(axis, false), 5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
double deadZone = this.getDeadZoneValue(axis);
|
||||||
|
float axisValue = absValue < deadZone ? 0.f : (float) (absValue - deadZone);
|
||||||
|
axisValue /= (1.0 - deadZone);
|
||||||
|
|
||||||
|
axisValue = (float) Math.min(axisValue / this.config.getAxisMaxValue(axis), 1);
|
||||||
|
if (currentPlusState)
|
||||||
|
InputManager.BUTTON_VALUES.put(axisAsButton(axis, true), axisValue);
|
||||||
|
else
|
||||||
|
InputManager.BUTTON_VALUES.put(axisAsButton(axis, true), 0.f);
|
||||||
|
if (currentMinusState)
|
||||||
|
InputManager.BUTTON_VALUES.put(axisAsButton(axis, false), axisValue);
|
||||||
|
else
|
||||||
|
InputManager.BUTTON_VALUES.put(axisAsButton(axis, false), 0.f);
|
||||||
|
}
|
||||||
|
|
||||||
|
double deadZone = this.getDeadZoneValue(axis);
|
||||||
|
|
||||||
|
if (this.controlsInput != null && this.controlsInput.focusedBinding != null) {
|
||||||
|
if (asButtonState != 0 && !this.controlsInput.currentButtons.contains(axisAsButton(axis, asButtonState == 1))) {
|
||||||
|
|
||||||
|
this.controlsInput.currentButtons.add(axisAsButton(axis, asButtonState == 1));
|
||||||
|
|
||||||
|
int[] buttons = new int[this.controlsInput.currentButtons.size()];
|
||||||
|
for (int i = 0; i < this.controlsInput.currentButtons.size(); i++)
|
||||||
|
buttons[i] = this.controlsInput.currentButtons.get(i);
|
||||||
|
this.controlsInput.focusedBinding.setButton(buttons);
|
||||||
|
|
||||||
|
this.controlsInput.waiting = false;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
} else if (client.currentScreen instanceof CreativeInventoryScreen creativeInventoryScreen) {
|
||||||
|
if (axis == GLFW_GAMEPAD_AXIS_RIGHT_Y) {
|
||||||
|
var accessor = (CreativeInventoryScreenAccessor) creativeInventoryScreen;
|
||||||
|
// @TODO allow rebinding to left stick
|
||||||
|
if (accessor.lambdacontrols$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 / this.config.getAxisMaxValue(axis), 0.f, 1.f);
|
||||||
|
if (client.currentScreen == null) {
|
||||||
|
// Handles the look direction.
|
||||||
|
this.handleLook(client, axis, absValue, state);
|
||||||
|
} else {
|
||||||
|
boolean allowMouseControl = true;
|
||||||
|
|
||||||
|
if (this.actionGuiCooldown == 0 && this.config.isMovementAxis(axis) && isScreenInteractive(client.currentScreen)) {
|
||||||
|
if (this.config.isForwardButton(axis, false, asButtonState)) {
|
||||||
|
allowMouseControl = this.changeFocus(client.currentScreen, NavigationDirection.UP);
|
||||||
|
} else if (this.config.isBackButton(axis, false, asButtonState)) {
|
||||||
|
allowMouseControl = this.changeFocus(client.currentScreen, NavigationDirection.DOWN);
|
||||||
|
} else if (this.config.isLeftButton(axis, false, asButtonState)) {
|
||||||
|
allowMouseControl = this.handleLeftRight(client.currentScreen, false);
|
||||||
|
} else if (this.config.isRightButton(axis, false, asButtonState)) {
|
||||||
|
allowMouseControl = this.handleLeftRight(client.currentScreen, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
float movementX = 0.f;
|
||||||
|
float movementY = 0.f;
|
||||||
|
|
||||||
|
if (this.config.isBackButton(axis, false, (value > 0 ? 1 : 2))) {
|
||||||
|
movementY = absValue;
|
||||||
|
} else if (this.config.isForwardButton(axis, false, (value > 0 ? 1 : 2))) {
|
||||||
|
movementY = -absValue;
|
||||||
|
} else if (this.config.isLeftButton(axis, false, (value > 0 ? 1 : 2))) {
|
||||||
|
movementX = -absValue;
|
||||||
|
} else if (this.config.isRightButton(axis, false, (value > 0 ? 1 : 2))) {
|
||||||
|
movementX = absValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (client.currentScreen != null && allowMouseControl) {
|
||||||
|
boolean moving = movementY != 0 || movementX != 0;
|
||||||
|
if (moving) {
|
||||||
|
/*
|
||||||
|
Updates the target mouse position when the initial movement stick movement is detected.
|
||||||
|
It prevents the cursor to jump to the old target mouse position if the user moves the cursor with the mouse.
|
||||||
|
*/
|
||||||
|
if (Math.abs(prevXAxis) < deadZone && Math.abs(prevYAxis) < deadZone) {
|
||||||
|
INPUT_MANAGER.resetMouseTarget(client);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.mouseSpeedX = movementX;
|
||||||
|
this.mouseSpeedY = movementY;
|
||||||
|
} else {
|
||||||
|
this.mouseSpeedX = 0.f;
|
||||||
|
this.mouseSpeedY = 0.f;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Math.abs(this.mouseSpeedX) >= .05f || Math.abs(this.mouseSpeedY) >= .05f) {
|
||||||
|
InputManager.queueMoveMousePosition(
|
||||||
|
this.mouseSpeedX * this.config.getMouseSpeed(),
|
||||||
|
this.mouseSpeedY * this.config.getMouseSpeed()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.moveMouseToClosestSlot(client, client.currentScreen);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.prevXAxis = movementX;
|
||||||
|
this.prevYAxis = movementY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean handleAButton(@NotNull Screen screen, @NotNull Element focused) {
|
||||||
|
if (focused instanceof 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).lambdacontrols$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 = -this.config.getRightYAxisSign() * (this.config.getRotationSpeed() * powValue) * 0.11D;
|
||||||
|
} else if (state == 1) {
|
||||||
|
this.targetPitch = this.config.getRightYAxisSign() * (this.config.getRotationSpeed() * powValue) * 0.11D;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (axis == GLFW_GAMEPAD_AXIS_RIGHT_X) {
|
||||||
|
if (state == 2) {
|
||||||
|
this.targetYaw = -this.config.getRightXAxisSign() * (this.config.getRotationSpeed() * powValue) * 0.11D;
|
||||||
|
} else if (state == 1) {
|
||||||
|
this.targetYaw = this.config.getRightXAxisSign() * (this.config.getRotationSpeed() * powValue) * 0.11D;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean changeFocus(@NotNull Screen screen, NavigationDirection direction) {
|
||||||
|
if (screen instanceof SpruceScreen 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())
|
||||||
|
|| LambdaControlsCompat.requireMouseOnScreen(screen));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inspired from https://github.com/MrCrayfish/Controllable/blob/1.14.X/src/main/java/com/mrcrayfish/controllable/client/ControllerInput.java#L686.
|
||||||
|
private void moveMouseToClosestSlot(@NotNull MinecraftClient client, @Nullable Screen screen) {
|
||||||
|
// Makes the mouse attracted to slots. This helps with selecting items when using a controller.
|
||||||
|
if (screen instanceof HandledScreen 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 LambdaControls.
|
||||||
|
*
|
||||||
|
* Licensed under the MIT license. For more information,
|
||||||
|
* see the LICENSE file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package dev.lambdaurora.lambdacontrols.client;
|
||||||
|
|
||||||
|
import dev.lambdaurora.lambdacontrols.LambdaControlsFeature;
|
||||||
|
import net.minecraft.block.Block;
|
||||||
|
import net.minecraft.block.BlockState;
|
||||||
|
import net.minecraft.block.FluidBlock;
|
||||||
|
import net.minecraft.block.SlabBlock;
|
||||||
|
import net.minecraft.client.MinecraftClient;
|
||||||
|
import net.minecraft.item.BlockItem;
|
||||||
|
import net.minecraft.item.ItemStack;
|
||||||
|
import net.minecraft.util.hit.BlockHitResult;
|
||||||
|
import net.minecraft.util.hit.HitResult;
|
||||||
|
import net.minecraft.util.math.BlockPos;
|
||||||
|
import net.minecraft.util.math.Direction;
|
||||||
|
import net.minecraft.util.math.MathHelper;
|
||||||
|
import net.minecraft.util.math.Vec3d;
|
||||||
|
import net.minecraft.world.RaycastContext;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the reach-around API of LambdaControls.
|
||||||
|
*
|
||||||
|
* @version 1.7.0
|
||||||
|
* @since 1.3.2
|
||||||
|
*/
|
||||||
|
public class LambdaReacharound {
|
||||||
|
private BlockHitResult lastReacharoundResult = null;
|
||||||
|
private boolean lastReacharoundVertical = false;
|
||||||
|
private boolean onSlab = false;
|
||||||
|
|
||||||
|
public void tick(@NotNull MinecraftClient client) {
|
||||||
|
this.lastReacharoundResult = this.tryVerticalReachAround(client);
|
||||||
|
if (this.lastReacharoundResult == null) {
|
||||||
|
this.lastReacharoundResult = this.tryHorizontalReachAround(client);
|
||||||
|
this.lastReacharoundVertical = false;
|
||||||
|
} else this.lastReacharoundVertical = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the last reach around result.
|
||||||
|
*
|
||||||
|
* @return the last reach around result
|
||||||
|
*/
|
||||||
|
public @Nullable BlockHitResult getLastReacharoundResult() {
|
||||||
|
return this.lastReacharoundResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether the last reach around is vertical.
|
||||||
|
*
|
||||||
|
* @return {@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 LambdaControlsFeature.HORIZONTAL_REACHAROUND.isAvailable() || LambdaControlsFeature.VERTICAL_REACHAROUND.isAvailable();
|
||||||
|
}
|
||||||
|
|
||||||
|
private float getPlayerRange(@NotNull MinecraftClient client) {
|
||||||
|
return client.interactionManager != null ? client.interactionManager.getReachDistance() : 0.f;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a nullable block hit result if vertical reach-around is possible.
|
||||||
|
*
|
||||||
|
* @param client the client instance
|
||||||
|
* @return a block hit result if vertical reach-around is possible, else {@code null}
|
||||||
|
*/
|
||||||
|
public @Nullable BlockHitResult tryVerticalReachAround(@NotNull MinecraftClient client) {
|
||||||
|
if (!LambdaControlsFeature.VERTICAL_REACHAROUND.isAvailable())
|
||||||
|
return null;
|
||||||
|
if (client.player == null || client.world == null || client.crosshairTarget == null || client.crosshairTarget.getType() != HitResult.Type.MISS
|
||||||
|
|| !client.player.isOnGround() || client.player.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 (!LambdaControlsFeature.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 LambdaControls.
|
||||||
|
*
|
||||||
|
* Licensed under the MIT license. For more information,
|
||||||
|
* see the LICENSE file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package dev.lambdaurora.lambdacontrols.client;
|
||||||
|
|
||||||
|
import net.minecraft.text.Text;
|
||||||
|
import net.minecraft.text.TranslatableText;
|
||||||
|
import org.aperlambda.lambdacommon.utils.Nameable;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the virtual mouse skins.
|
||||||
|
*
|
||||||
|
* @version 1.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 "lambdacontrols.virtual_mouse.skin." + this.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the translated text of this virtual mouse skin.
|
||||||
|
*
|
||||||
|
* @return the translated text of this virtual mouse skin
|
||||||
|
*/
|
||||||
|
public @NotNull Text getTranslatedText() {
|
||||||
|
return this.text;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull String getName() {
|
||||||
|
return this.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the virtual mouse skin from its identifier.
|
||||||
|
*
|
||||||
|
* @param id the identifier of the virtual mouse skin
|
||||||
|
* @return the virtual mouse skin if found, else empty
|
||||||
|
*/
|
||||||
|
public static @NotNull Optional<VirtualMouseSkin> byId(@NotNull String id) {
|
||||||
|
return Arrays.stream(values()).filter(mode -> mode.getName().equalsIgnoreCase(id)).findFirst();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
|
||||||
|
*
|
||||||
|
* This file is part of LambdaControls.
|
||||||
|
*
|
||||||
|
* Licensed under the MIT license. For more information,
|
||||||
|
* see the LICENSE file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package dev.lambdaurora.lambdacontrols.client.compat;
|
||||||
|
|
||||||
|
import dev.lambdaurora.lambdacontrols.client.LambdaControlsClient;
|
||||||
|
import net.minecraft.client.MinecraftClient;
|
||||||
|
import net.minecraft.client.gui.screen.Screen;
|
||||||
|
import net.minecraft.client.gui.screen.ingame.HandledScreen;
|
||||||
|
import net.minecraft.screen.slot.Slot;
|
||||||
|
import net.minecraft.util.hit.BlockHitResult;
|
||||||
|
import org.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 LambdaControlsClient mod);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether the mouse is required on the specified screen.
|
||||||
|
*
|
||||||
|
* @param screen the screen
|
||||||
|
* @return true if the mouse is required on the specified screen, else false
|
||||||
|
*/
|
||||||
|
default boolean requireMouseOnScreen(Screen screen) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a slot at the specified location if possible.
|
||||||
|
*
|
||||||
|
* @param screen the screen
|
||||||
|
* @param mouseX the mouse X-coordinate
|
||||||
|
* @param mouseY the mouse Y-coordinate
|
||||||
|
* @return a slot if present, else null
|
||||||
|
* @since 1.5.0
|
||||||
|
*/
|
||||||
|
default @Nullable 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,41 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
|
||||||
|
*
|
||||||
|
* This file is part of LambdaControls.
|
||||||
|
*
|
||||||
|
* Licensed under the MIT license. For more information,
|
||||||
|
* see the LICENSE file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package dev.lambdaurora.lambdacontrols.client.compat;
|
||||||
|
|
||||||
|
import dev.lambdaurora.lambdacontrols.client.LambdaControlsClient;
|
||||||
|
import net.minecraft.client.gui.screen.Screen;
|
||||||
|
import org.aperlambda.lambdacommon.utils.LambdaReflection;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents HQM compatibility handler.
|
||||||
|
* <p>
|
||||||
|
* This is bad.
|
||||||
|
*
|
||||||
|
* @author LambdAurora
|
||||||
|
* @version 1.3.2
|
||||||
|
* @since 1.3.2
|
||||||
|
*/
|
||||||
|
public class HQMCompat implements CompatHandler {
|
||||||
|
public static final String GUI_BASE_CLASS_PATH = "hardcorequesting.client.interfaces.GuiBase";
|
||||||
|
private Optional<Class<?>> guiBaseClass;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handle(@NotNull LambdaControlsClient mod) {
|
||||||
|
this.guiBaseClass = LambdaReflection.getClass(GUI_BASE_CLASS_PATH);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean requireMouseOnScreen(Screen screen) {
|
||||||
|
return this.guiBaseClass.map(clazz -> clazz.isInstance(screen)).orElse(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,160 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
|
||||||
|
*
|
||||||
|
* This file is part of LambdaControls.
|
||||||
|
*
|
||||||
|
* Licensed under the MIT license. For more information,
|
||||||
|
* see the LICENSE file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package dev.lambdaurora.lambdacontrols.client.compat;
|
||||||
|
|
||||||
|
import dev.lambdaurora.lambdacontrols.client.LambdaControlsClient;
|
||||||
|
import dev.lambdaurora.lambdacontrols.client.controller.InputManager;
|
||||||
|
import net.fabricmc.loader.api.FabricLoader;
|
||||||
|
import net.minecraft.client.MinecraftClient;
|
||||||
|
import net.minecraft.client.gui.screen.Screen;
|
||||||
|
import net.minecraft.util.hit.BlockHitResult;
|
||||||
|
import org.aperlambda.lambdacommon.utils.LambdaReflection;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a compatibility handler.
|
||||||
|
*
|
||||||
|
* @author LambdAurora
|
||||||
|
* @version 1.5.0
|
||||||
|
* @since 1.1.0
|
||||||
|
*/
|
||||||
|
public class LambdaControlsCompat {
|
||||||
|
private static final List<CompatHandler> HANDLERS = new ArrayList<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes compatibility with other mods if needed.
|
||||||
|
*
|
||||||
|
* @param mod the mod instance
|
||||||
|
*/
|
||||||
|
public static void init(@NotNull LambdaControlsClient mod) {
|
||||||
|
/*if (FabricLoader.getInstance().isModLoaded("okzoomer")) {
|
||||||
|
mod.log("Adding okzoomer compatibility...");
|
||||||
|
HANDLERS.add(new OkZoomerCompat());
|
||||||
|
}
|
||||||
|
if (isReiPresent()) {
|
||||||
|
mod.log("Adding REI compatiblity...");
|
||||||
|
HANDLERS.add(new ReiCompat());
|
||||||
|
}*/
|
||||||
|
if (FabricLoader.getInstance().isModLoaded("hardcorequesting") && LambdaReflection.doesClassExist(HQMCompat.GUI_BASE_CLASS_PATH)) {
|
||||||
|
mod.log("Adding HQM compatibility...");
|
||||||
|
HANDLERS.add(new HQMCompat());
|
||||||
|
}
|
||||||
|
HANDLERS.forEach(handler -> handler.handle(mod));
|
||||||
|
InputManager.loadButtonBindings(mod.config);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a new compatibility handler.
|
||||||
|
*
|
||||||
|
* @param handler the compatibility handler to register
|
||||||
|
*/
|
||||||
|
public static void registerCompatHandler(@NotNull CompatHandler handler) {
|
||||||
|
HANDLERS.add(handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Streams through compatibility handlers.
|
||||||
|
*
|
||||||
|
* @return a stream of compatibility handlers
|
||||||
|
*/
|
||||||
|
public static Stream<CompatHandler> streamCompatHandlers() {
|
||||||
|
return HANDLERS.stream();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether the mouse is required on the specified screen.
|
||||||
|
*
|
||||||
|
* @param screen the screen
|
||||||
|
* @return true if the mouse is requried on the specified screen, else false
|
||||||
|
*/
|
||||||
|
public static boolean requireMouseOnScreen(Screen screen) {
|
||||||
|
return HANDLERS.stream().anyMatch(handler -> handler.requireMouseOnScreen(screen));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a slot at the specified location if possible.
|
||||||
|
*
|
||||||
|
* @param screen the screen
|
||||||
|
* @param mouseX the mouse X-coordinate
|
||||||
|
* @param mouseY the mouse Y-coordinate
|
||||||
|
* @return a slot if present, else null
|
||||||
|
*/
|
||||||
|
public static @Nullable 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 LambdaControls.
|
||||||
|
*
|
||||||
|
* Licensed under the MIT license. For more information,
|
||||||
|
* see the LICENSE file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package dev.lambdaurora.lambdacontrols.client.compat;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.objectweb.asm.tree.ClassNode;
|
||||||
|
import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin;
|
||||||
|
import org.spongepowered.asm.mixin.extensibility.IMixinInfo;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This plugin is only present for the conditional mixins.
|
||||||
|
*
|
||||||
|
* @author LambdAurora
|
||||||
|
* @version 1.5.0
|
||||||
|
* @since 1.2.0
|
||||||
|
*/
|
||||||
|
public class LambdaControlsMixinPlugin implements IMixinConfigPlugin {
|
||||||
|
private final HashMap<String, Boolean> conditionalMixins = new HashMap<>();
|
||||||
|
|
||||||
|
public LambdaControlsMixinPlugin() {
|
||||||
|
this.putConditionalMixin("EntryListWidgetAccessor", LambdaControlsCompat.isReiPresent());
|
||||||
|
this.putConditionalMixin("EntryWidgetAccessor", LambdaControlsCompat.isReiPresent());
|
||||||
|
this.putConditionalMixin("RecipeViewingScreenAccessor", LambdaControlsCompat.isReiPresent());
|
||||||
|
this.putConditionalMixin("VillagerRecipeViewingScreenAccessor", LambdaControlsCompat.isReiPresent());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void putConditionalMixin(@NotNull String path, boolean condition) {
|
||||||
|
this.conditionalMixins.put("me.lambdaurora.lambdacontrols.client.compat.mixin." + path, condition);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLoad(String mixinPackage) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getRefMapperConfig() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean shouldApplyMixin(String targetClassName, String mixinClassName) {
|
||||||
|
return this.conditionalMixins.getOrDefault(mixinClassName, Boolean.TRUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void acceptTargets(Set<String> myTargets, Set<String> otherTargets) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> getMixins() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void preApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void postApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
|
||||||
|
*
|
||||||
|
* This file is part of LambdaControls.
|
||||||
|
*
|
||||||
|
* Licensed under the MIT license. For more information,
|
||||||
|
* see the LICENSE file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package dev.lambdaurora.lambdacontrols.client.compat;
|
||||||
|
|
||||||
|
import dev.lambdaurora.lambdacontrols.client.LambdaControlsClient;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a compatibility handler for OkZoomer.
|
||||||
|
*
|
||||||
|
* @author LambdAurora
|
||||||
|
* @version 1.4.3
|
||||||
|
* @since 1.1.0
|
||||||
|
*/
|
||||||
|
public class OkZoomerCompat implements CompatHandler {
|
||||||
|
@Override
|
||||||
|
public void handle(@NotNull LambdaControlsClient mod) {
|
||||||
|
/*new ButtonBinding.Builder("zoom")
|
||||||
|
.buttons(GLFW.GLFW_GAMEPAD_BUTTON_DPAD_UP, GLFW.GLFW_GAMEPAD_BUTTON_X)
|
||||||
|
.onlyInGame()
|
||||||
|
.cooldown(true)
|
||||||
|
.category(ButtonBinding.MISC_CATEGORY)
|
||||||
|
.linkKeybind(ZoomKeybinds.zoomKey)
|
||||||
|
.register();
|
||||||
|
|
||||||
|
if (ZoomKeybinds.areExtraKeybindsEnabled()) {
|
||||||
|
new ButtonBinding.Builder("zoom_in")
|
||||||
|
.buttons(GLFW.GLFW_GAMEPAD_BUTTON_DPAD_UP, ButtonBinding.axisAsButton(GLFW.GLFW_GAMEPAD_AXIS_RIGHT_TRIGGER, true))
|
||||||
|
.onlyInGame()
|
||||||
|
.cooldown(true)
|
||||||
|
.category(ButtonBinding.MISC_CATEGORY)
|
||||||
|
.linkKeybind(ZoomKeybinds.increaseZoomKey)
|
||||||
|
.register();
|
||||||
|
new ButtonBinding.Builder("zoom_out")
|
||||||
|
.buttons(GLFW.GLFW_GAMEPAD_BUTTON_DPAD_UP, ButtonBinding.axisAsButton(GLFW.GLFW_GAMEPAD_AXIS_LEFT_TRIGGER, true))
|
||||||
|
.onlyInGame()
|
||||||
|
.cooldown(true)
|
||||||
|
.category(ButtonBinding.MISC_CATEGORY)
|
||||||
|
.linkKeybind(ZoomKeybinds.decreaseZoomKey)
|
||||||
|
.register();
|
||||||
|
new ButtonBinding.Builder("zoom_reset")
|
||||||
|
.onlyInGame()
|
||||||
|
.cooldown(true)
|
||||||
|
.category(ButtonBinding.MISC_CATEGORY)
|
||||||
|
.linkKeybind(ZoomKeybinds.resetZoomKey)
|
||||||
|
.register();
|
||||||
|
}*/
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,335 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
|
||||||
|
*
|
||||||
|
* This file is part of LambdaControls.
|
||||||
|
*
|
||||||
|
* Licensed under the MIT license. For more information,
|
||||||
|
* see the LICENSE file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package dev.lambdaurora.lambdacontrols.client.compat;
|
||||||
|
|
||||||
|
import dev.lambdaurora.lambdacontrols.client.ButtonState;
|
||||||
|
import dev.lambdaurora.lambdacontrols.client.LambdaControlsClient;
|
||||||
|
import dev.lambdaurora.lambdacontrols.client.controller.ButtonBinding;
|
||||||
|
import dev.lambdaurora.lambdacontrols.client.controller.InputHandlers;
|
||||||
|
import dev.lambdaurora.lambdacontrols.client.controller.PressAction;
|
||||||
|
import net.minecraft.client.MinecraftClient;
|
||||||
|
import net.minecraft.client.gui.screen.Screen;
|
||||||
|
import net.minecraft.util.Identifier;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
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 LambdaControlsClient mod) {
|
||||||
|
ButtonBinding.builder(new Identifier("rei", "category_back"))
|
||||||
|
.buttons(GLFW_GAMEPAD_BUTTON_LEFT_BUMPER)
|
||||||
|
.filter((client, binding) -> isViewingScreen(client.currentScreen))
|
||||||
|
.action(handleTab(false))
|
||||||
|
.cooldown(true)
|
||||||
|
.register();
|
||||||
|
ButtonBinding.builder(new Identifier("rei", "category_next"))
|
||||||
|
.buttons(GLFW_GAMEPAD_BUTTON_RIGHT_BUMPER)
|
||||||
|
.filter((client, binding) -> isViewingScreen(client.currentScreen))
|
||||||
|
.action(handleTab(true))
|
||||||
|
.cooldown(true)
|
||||||
|
.register();
|
||||||
|
|
||||||
|
ButtonBinding.builder(new Identifier("rei", "page_back"))
|
||||||
|
.buttons(ButtonBinding.axisAsButton(GLFW_GAMEPAD_AXIS_RIGHT_X, false))
|
||||||
|
.filter((client, binding) -> InputHandlers.inInventory(client, binding) || isViewingScreen(client.currentScreen))
|
||||||
|
.action(handlePage(false))
|
||||||
|
.cooldown(true)
|
||||||
|
.register();
|
||||||
|
ButtonBinding.builder(new Identifier("rei", "page_next"))
|
||||||
|
.buttons(ButtonBinding.axisAsButton(GLFW_GAMEPAD_AXIS_RIGHT_X, true))
|
||||||
|
.filter((client, binding) -> InputHandlers.inInventory(client, binding) || isViewingScreen(client.currentScreen))
|
||||||
|
.action(handlePage(true))
|
||||||
|
.cooldown(true)
|
||||||
|
.register();
|
||||||
|
|
||||||
|
ButtonBinding.builder(new Identifier("rei", "recipe_back"))
|
||||||
|
.buttons(GLFW_GAMEPAD_BUTTON_DPAD_UP)
|
||||||
|
.filter((client, binding) -> isViewingScreen(client.currentScreen))
|
||||||
|
.action(handleRecipe(false))
|
||||||
|
.cooldown(true)
|
||||||
|
.register();
|
||||||
|
ButtonBinding.builder(new Identifier("rei", "recipe_next"))
|
||||||
|
.buttons(GLFW_GAMEPAD_BUTTON_DPAD_DOWN)
|
||||||
|
.filter((client, binding) -> isViewingScreen(client.currentScreen))
|
||||||
|
.action(handleRecipe(true))
|
||||||
|
.cooldown(true)
|
||||||
|
.register();
|
||||||
|
|
||||||
|
// For some reasons this is broken.
|
||||||
|
ButtonBinding.builder(new Identifier("rei", "show_usage"))
|
||||||
|
.buttons(GLFW_GAMEPAD_BUTTON_RIGHT_THUMB)
|
||||||
|
.filter((client, binding) -> InputHandlers.inInventory(client, binding) || isViewingScreen(client.currentScreen))
|
||||||
|
.action(handleShowRecipeUsage(true))
|
||||||
|
.cooldown(true)
|
||||||
|
.register();
|
||||||
|
|
||||||
|
ButtonBinding.builder(new Identifier("rei", "show_recipe"))
|
||||||
|
.buttons(GLFW_GAMEPAD_BUTTON_LEFT_THUMB)
|
||||||
|
.filter((client, binding) -> InputHandlers.inInventory(client, binding) || isViewingScreen(client.currentScreen))
|
||||||
|
.action(handleShowRecipeUsage(false))
|
||||||
|
.cooldown(true)
|
||||||
|
.register();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean requireMouseOnScreen(Screen screen) {
|
||||||
|
return isViewingScreen(screen) /*|| screen instanceof PreRecipeViewingScreen*/;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable SlotPos getSlotAt(@NotNull Screen screen, int mouseX, int mouseY) {
|
||||||
|
/*var overlay = ScreenHelper.getOptionalOverlay();
|
||||||
|
if (overlay.isPresent() && overlay.get().isInside(mouseX, mouseY)) {
|
||||||
|
var widget = getEntryListWidget();
|
||||||
|
if (widget == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var slot = this.getSlotAt(widget, mouseX, mouseY, false);
|
||||||
|
if (slot != null && slot != INVALID_SLOT)
|
||||||
|
return slot;
|
||||||
|
} else if (isViewingScreen(screen)) {
|
||||||
|
for (var element : screen.children()) {
|
||||||
|
var slot = this.getSlotAt(element, mouseX, mouseY, true);
|
||||||
|
if (slot != null && slot != INVALID_SLOT)
|
||||||
|
return slot;
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*private @Nullable SlotPos getSlotAt(@NotNull Element element, int mouseX, int mouseY, boolean allowEmpty) {
|
||||||
|
if (element instanceof EntryWidget entry) {
|
||||||
|
if (entry.containsMouse(mouseX, mouseY)) {
|
||||||
|
if (!allowEmpty && entry.entries().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 true;
|
||||||
|
//return screen instanceof DefaultDisplayViewingScreen || screen instanceof CompositeDisplayViewingScreen;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean handleMenuBack(@NotNull MinecraftClient client, @NotNull Screen screen) {
|
||||||
|
if (!isViewingScreen(screen))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
/*MinecraftClient.getInstance().openScreen(REIRuntimeImpl.getInstance().getPreviousContainerScreen());
|
||||||
|
ScreenHelper.getLastOverlay().init();*/
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*private static EntryListWidget getEntryListWidget() {
|
||||||
|
if (ENTRY_LIST_WIDGET == null) {
|
||||||
|
ENTRY_LIST_WIDGET = LambdaReflection.getFirstFieldOfType(ContainerScreenOverlay.class, EntryListWidget.class)
|
||||||
|
.map(field -> (EntryListWidget) LambdaReflection.getFieldValue(null, field))
|
||||||
|
.orElse(null);
|
||||||
|
}
|
||||||
|
return ENTRY_LIST_WIDGET;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static @Nullable EntryStack getCurrentStack(@NotNull MinecraftClient client) {
|
||||||
|
double x = client.mouse.getX() * (double) client.getWindow().getScaledWidth() / (double) client.getWindow().getWidth();
|
||||||
|
double y = client.mouse.getY() * (double) client.getWindow().getScaledHeight() / (double) client.getWindow().getHeight();
|
||||||
|
|
||||||
|
if (isViewingScreen(client.currentScreen)) {
|
||||||
|
for (var element : client.currentScreen.children()) {
|
||||||
|
var stack = getCurrentStack(element, x, y);
|
||||||
|
if (stack != null)
|
||||||
|
return stack;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var overlay = ScreenHelper.getOptionalOverlay();
|
||||||
|
if (!overlay.isPresent())
|
||||||
|
return RecipeHelper.getInstance().getScreenFocusedStack(client.currentScreen);
|
||||||
|
var widget = getEntryListWidget();
|
||||||
|
if (widget == null)
|
||||||
|
return RecipeHelper.getInstance().getScreenFocusedStack(client.currentScreen);
|
||||||
|
|
||||||
|
return getCurrentStack(widget, x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static @Nullable EntryStack getCurrentStack(@NotNull Element element, double mouseX, double mouseY) {
|
||||||
|
if (element instanceof EntryWidget entry) {
|
||||||
|
if (entry.containsMouse(mouseX, mouseY))
|
||||||
|
return ((EntryWidgetAccessor) entry).lambdacontrols_getCurrentEntry();
|
||||||
|
} else if (element instanceof EntryListWidget) {
|
||||||
|
var 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 widgetWithBounds) {
|
||||||
|
for (var child : widgetWithBounds.children()) {
|
||||||
|
var 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;
|
||||||
|
|
||||||
|
var 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 DefaultDisplayViewingScreen) {
|
||||||
|
RecipeViewingScreenAccessor screen = (RecipeViewingScreenAccessor) client.currentScreen;
|
||||||
|
if (next)
|
||||||
|
screen.getCategoryNext().onClick();
|
||||||
|
else
|
||||||
|
screen.getCategoryBack().onClick();
|
||||||
|
return true;
|
||||||
|
} else if (client.currentScreen instanceof CompositeDisplayViewingScreen) {
|
||||||
|
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 RecipeViewingScreenAccessor screen) {
|
||||||
|
if (next)
|
||||||
|
screen.getRecipeNext().onClick();
|
||||||
|
else
|
||||||
|
screen.getRecipeBack().onClick();
|
||||||
|
return true;
|
||||||
|
} else if (client.currentScreen instanceof VillagerRecipeViewingScreenAccessor screen) {
|
||||||
|
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,23 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
|
||||||
|
*
|
||||||
|
* This file is part of LambdaControls.
|
||||||
|
*
|
||||||
|
* Licensed under the MIT license. For more information,
|
||||||
|
* see the LICENSE file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package dev.lambdaurora.lambdacontrols.client.compat.mixin;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents an accessor to REI's EntryListWidget.
|
||||||
|
*
|
||||||
|
* @author LambdAurora
|
||||||
|
* @version 1.5.0
|
||||||
|
* @since 1.5.0
|
||||||
|
*/
|
||||||
|
//@Mixin(value = EntryListWidget.class, remap = false)
|
||||||
|
public interface EntryListWidgetAccessor {
|
||||||
|
/*@Accessor(value = "entries")
|
||||||
|
List<EntryListEntryWidget> getEntries();*/
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
|
||||||
|
*
|
||||||
|
* This file is part of LambdaControls.
|
||||||
|
*
|
||||||
|
* Licensed under the MIT license. For more information,
|
||||||
|
* see the LICENSE file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package dev.lambdaurora.lambdacontrols.client.compat.mixin;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents an accessor to REI's EntryWidget.
|
||||||
|
*
|
||||||
|
* @author LambdAurora
|
||||||
|
* @version 1.5.0
|
||||||
|
* @since 1.5.0
|
||||||
|
*/
|
||||||
|
//@Mixin(value = EntryWidget.class, remap = false)
|
||||||
|
public interface EntryWidgetAccessor {
|
||||||
|
/*@Invoker("getCurrentEntry")
|
||||||
|
EntryStack lambdacontrols_getCurrentEntry();*/
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
|
||||||
|
*
|
||||||
|
* This file is part of LambdaControls.
|
||||||
|
*
|
||||||
|
* Licensed under the MIT license. For more information,
|
||||||
|
* see the LICENSE file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package dev.lambdaurora.lambdacontrols.client.compat.mixin;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 LambdaControls.
|
||||||
|
*
|
||||||
|
* Licensed under the MIT license. For more information,
|
||||||
|
* see the LICENSE file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package dev.lambdaurora.lambdacontrols.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 lambdacontrols_init();*/
|
||||||
|
}
|
||||||
@@ -0,0 +1,587 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
|
||||||
|
*
|
||||||
|
* This file is part of LambdaControls.
|
||||||
|
*
|
||||||
|
* Licensed under the MIT license. For more information,
|
||||||
|
* see the LICENSE file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package dev.lambdaurora.lambdacontrols.client.controller;
|
||||||
|
|
||||||
|
import dev.lambdaurora.lambdacontrols.client.ButtonState;
|
||||||
|
import 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 org.aperlambda.lambdacommon.Identifier;
|
||||||
|
import org.aperlambda.lambdacommon.utils.Nameable;
|
||||||
|
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 implements Nameable {
|
||||||
|
public static final ButtonCategory MOVEMENT_CATEGORY;
|
||||||
|
public static final ButtonCategory GAMEPLAY_CATEGORY;
|
||||||
|
public static final ButtonCategory INVENTORY_CATEGORY;
|
||||||
|
public static final ButtonCategory MULTIPLAYER_CATEGORY;
|
||||||
|
public static final ButtonCategory MISC_CATEGORY;
|
||||||
|
|
||||||
|
public static final ButtonBinding ATTACK = new Builder("attack").buttons(axisAsButton(GLFW_GAMEPAD_AXIS_RIGHT_TRIGGER, true)).onlyInGame().register();
|
||||||
|
public static final ButtonBinding BACK = new Builder("back").buttons(axisAsButton(GLFW_GAMEPAD_AXIS_LEFT_Y, false))
|
||||||
|
.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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull String getName() {
|
||||||
|
return this.key;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the translation key of this button binding.
|
||||||
|
*
|
||||||
|
* @return the translation key
|
||||||
|
*/
|
||||||
|
public @NotNull String getTranslationKey() {
|
||||||
|
return "lambdacontrols.action." + this.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
public @NotNull Text getText() {
|
||||||
|
return this.text;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the key binding equivalent of this button binding.
|
||||||
|
*
|
||||||
|
* @return the key binding equivalent
|
||||||
|
*/
|
||||||
|
public @NotNull Optional<KeyBinding> asKeyBinding() {
|
||||||
|
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("lambdacontrols.button.a");
|
||||||
|
case GLFW_GAMEPAD_BUTTON_B -> new TranslatableText("lambdacontrols.button.b");
|
||||||
|
case GLFW_GAMEPAD_BUTTON_X -> new TranslatableText("lambdacontrols.button.x");
|
||||||
|
case GLFW_GAMEPAD_BUTTON_Y -> new TranslatableText("lambdacontrols.button.y");
|
||||||
|
case GLFW_GAMEPAD_BUTTON_LEFT_BUMPER -> new TranslatableText("lambdacontrols.button.left_bumper");
|
||||||
|
case GLFW_GAMEPAD_BUTTON_RIGHT_BUMPER -> new TranslatableText("lambdacontrols.button.right_bumper");
|
||||||
|
case GLFW_GAMEPAD_BUTTON_BACK -> new TranslatableText("lambdacontrols.button.back");
|
||||||
|
case GLFW_GAMEPAD_BUTTON_START -> new TranslatableText("lambdacontrols.button.start");
|
||||||
|
case GLFW_GAMEPAD_BUTTON_GUIDE -> new TranslatableText("lambdacontrols.button.guide");
|
||||||
|
case GLFW_GAMEPAD_BUTTON_LEFT_THUMB -> new TranslatableText("lambdacontrols.button.left_thumb");
|
||||||
|
case GLFW_GAMEPAD_BUTTON_RIGHT_THUMB -> new TranslatableText("lambdacontrols.button.right_thumb");
|
||||||
|
case GLFW_GAMEPAD_BUTTON_DPAD_UP -> new TranslatableText("lambdacontrols.button.dpad_up");
|
||||||
|
case GLFW_GAMEPAD_BUTTON_DPAD_RIGHT -> new TranslatableText("lambdacontrols.button.dpad_right");
|
||||||
|
case GLFW_GAMEPAD_BUTTON_DPAD_DOWN -> new TranslatableText("lambdacontrols.button.dpad_down");
|
||||||
|
case GLFW_GAMEPAD_BUTTON_DPAD_LEFT -> new TranslatableText("lambdacontrols.button.dpad_left");
|
||||||
|
case 100 -> new TranslatableText("lambdacontrols.axis.left_x+");
|
||||||
|
case 101 -> new TranslatableText("lambdacontrols.axis.left_y+");
|
||||||
|
case 102 -> new TranslatableText("lambdacontrols.axis.right_x+");
|
||||||
|
case 103 -> new TranslatableText("lambdacontrols.axis.right_y+");
|
||||||
|
case 104 -> new TranslatableText("lambdacontrols.axis.left_trigger");
|
||||||
|
case 105 -> new TranslatableText("lambdacontrols.axis.right_trigger");
|
||||||
|
case 200 -> new TranslatableText("lambdacontrols.axis.left_x-");
|
||||||
|
case 201 -> new TranslatableText("lambdacontrols.axis.left_y-");
|
||||||
|
case 202 -> new TranslatableText("lambdacontrols.axis.right_x-");
|
||||||
|
case 203 -> new TranslatableText("lambdacontrols.axis.right_y-");
|
||||||
|
default -> new TranslatableText("lambdacontrols.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 instanc
|
||||||
|
* @since 1.5.0
|
||||||
|
*/
|
||||||
|
public static Builder builder(@NotNull Identifier identifier) {
|
||||||
|
return new Builder(identifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a builder instance.
|
||||||
|
*
|
||||||
|
* @param identifier the identifier of the button binding
|
||||||
|
* @return the builder instance
|
||||||
|
* @since 1.5.0
|
||||||
|
*/
|
||||||
|
public static Builder builder(@NotNull net.minecraft.util.Identifier identifier) {
|
||||||
|
return new Builder(identifier);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a quick {@link ButtonBinding} builder.
|
||||||
|
*
|
||||||
|
* @author LambdAurora
|
||||||
|
* @version 1.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.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder(@NotNull net.minecraft.util.Identifier identifier) {
|
||||||
|
this(new Identifier(identifier.toString()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright © 2020 LambdAurora <aurora42lambda@gmail.com>
|
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
|
||||||
*
|
*
|
||||||
* This file is part of LambdaControls.
|
* This file is part of LambdaControls.
|
||||||
*
|
*
|
||||||
@@ -7,7 +7,7 @@
|
|||||||
* see the LICENSE file.
|
* see the LICENSE file.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package me.lambdaurora.lambdacontrols.client.controller;
|
package dev.lambdaurora.lambdacontrols.client.controller;
|
||||||
|
|
||||||
import net.minecraft.client.resource.language.I18n;
|
import net.minecraft.client.resource.language.I18n;
|
||||||
import org.aperlambda.lambdacommon.Identifier;
|
import org.aperlambda.lambdacommon.Identifier;
|
||||||
@@ -26,47 +26,40 @@ import java.util.List;
|
|||||||
* @version 1.1.0
|
* @version 1.1.0
|
||||||
* @since 1.1.0
|
* @since 1.1.0
|
||||||
*/
|
*/
|
||||||
public class ButtonCategory implements Identifiable
|
public class ButtonCategory implements Identifiable {
|
||||||
{
|
|
||||||
private final List<ButtonBinding> bindings = new ArrayList<>();
|
private final List<ButtonBinding> bindings = new ArrayList<>();
|
||||||
private final Identifier id;
|
private final Identifier id;
|
||||||
private int priority;
|
private final int priority;
|
||||||
|
|
||||||
public ButtonCategory(@NotNull Identifier id, int priority)
|
public ButtonCategory(@NotNull Identifier id, int priority) {
|
||||||
{
|
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.priority = priority;
|
this.priority = priority;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ButtonCategory(@NotNull Identifier id)
|
public ButtonCategory(@NotNull Identifier id) {
|
||||||
{
|
|
||||||
this(id, 100);
|
this(id, 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void registerBinding(@NotNull ButtonBinding binding)
|
public void registerBinding(@NotNull ButtonBinding binding) {
|
||||||
{
|
|
||||||
if (this.bindings.contains(binding))
|
if (this.bindings.contains(binding))
|
||||||
throw new IllegalStateException("Cannot register twice a button binding in the same category.");
|
throw new IllegalStateException("Cannot register twice a button binding in the same category.");
|
||||||
this.bindings.add(binding);
|
this.bindings.add(binding);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void registerAllBindings(@NotNull ButtonBinding... bindings)
|
public void registerAllBindings(@NotNull ButtonBinding... bindings) {
|
||||||
{
|
|
||||||
this.registerAllBindings(Arrays.asList(bindings));
|
this.registerAllBindings(Arrays.asList(bindings));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void registerAllBindings(@NotNull List<ButtonBinding> bindings)
|
public void registerAllBindings(@NotNull List<ButtonBinding> bindings) {
|
||||||
{
|
|
||||||
bindings.forEach(this::registerBinding);
|
bindings.forEach(this::registerBinding);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the bindings assigned to this category.
|
* Gets the bindings assigned to this category.
|
||||||
*
|
*
|
||||||
* @return The bindings assigned to this category.
|
* @return the bindings assigned to this category
|
||||||
*/
|
*/
|
||||||
public @NotNull List<ButtonBinding> getBindings()
|
public @NotNull List<ButtonBinding> getBindings() {
|
||||||
{
|
|
||||||
return Collections.unmodifiableList(this.bindings);
|
return Collections.unmodifiableList(this.bindings);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,10 +68,9 @@ public class ButtonCategory implements Identifiable
|
|||||||
* <p>
|
* <p>
|
||||||
* The translation key should be `modid.identifier_name`.
|
* The translation key should be `modid.identifier_name`.
|
||||||
*
|
*
|
||||||
* @return The translated name.
|
* @return the translated name
|
||||||
*/
|
*/
|
||||||
public @NotNull String getTranslatedName()
|
public @NotNull String getTranslatedName() {
|
||||||
{
|
|
||||||
if (this.id.getNamespace().equals("minecraft"))
|
if (this.id.getNamespace().equals("minecraft"))
|
||||||
return I18n.translate(this.id.getName());
|
return I18n.translate(this.id.getName());
|
||||||
else
|
else
|
||||||
@@ -89,16 +81,14 @@ public class ButtonCategory implements Identifiable
|
|||||||
* Gets the priority display of this category.
|
* Gets the priority display of this category.
|
||||||
* It will defines in which order the categories will display on the controls screen.
|
* It will defines in which order the categories will display on the controls screen.
|
||||||
*
|
*
|
||||||
* @return The priority of this category.
|
* @return the priority of this category
|
||||||
*/
|
*/
|
||||||
public int getPriority()
|
public int getPriority() {
|
||||||
{
|
|
||||||
return this.priority;
|
return this.priority;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @NotNull Identifier getIdentifier()
|
public @NotNull Identifier getIdentifier() {
|
||||||
{
|
|
||||||
return this.id;
|
return this.id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,187 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
|
||||||
|
*
|
||||||
|
* This file is part of LambdaControls.
|
||||||
|
*
|
||||||
|
* Licensed under the MIT license. For more information,
|
||||||
|
* see the LICENSE file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package dev.lambdaurora.lambdacontrols.client.controller;
|
||||||
|
|
||||||
|
import dev.lambdaurora.lambdacontrols.LambdaControls;
|
||||||
|
import dev.lambdaurora.lambdacontrols.client.LambdaControlsClient;
|
||||||
|
import net.minecraft.client.MinecraftClient;
|
||||||
|
import net.minecraft.client.toast.SystemToast;
|
||||||
|
import net.minecraft.text.LiteralText;
|
||||||
|
import net.minecraft.text.TranslatableText;
|
||||||
|
import org.aperlambda.lambdacommon.utils.Nameable;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.lwjgl.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) {
|
||||||
|
LambdaControlsClient.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 (!LambdaControlsClient.MAPPINGS_FILE.exists())
|
||||||
|
return;
|
||||||
|
LambdaControlsClient.get().log("Updating controller mappings...");
|
||||||
|
var buffer = ioResourceToBuffer(LambdaControlsClient.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("lambdacontrols.controller.mappings.error"), new LiteralText(string)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Throwable e) {
|
||||||
|
/* Ignored :concern: */
|
||||||
|
}
|
||||||
|
|
||||||
|
if (LambdaControlsClient.get().config.hasDebug()) {
|
||||||
|
for (int i = GLFW.GLFW_JOYSTICK_1; i <= GLFW.GLFW_JOYSTICK_16; i++) {
|
||||||
|
var controller = byId(i);
|
||||||
|
|
||||||
|
if (!controller.isConnected())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
LambdaControls.get().log(String.format("Controller #%d name: \"%s\"\n GUID: %s\n Gamepad: %s",
|
||||||
|
controller.id,
|
||||||
|
controller.getName(),
|
||||||
|
controller.getGuid(),
|
||||||
|
controller.isGamepad()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,266 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
|
||||||
|
*
|
||||||
|
* This file is part of LambdaControls.
|
||||||
|
*
|
||||||
|
* Licensed under the MIT license. For more information,
|
||||||
|
* see the LICENSE file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package dev.lambdaurora.lambdacontrols.client.controller;
|
||||||
|
|
||||||
|
import dev.lambdaurora.lambdacontrols.client.ButtonState;
|
||||||
|
import dev.lambdaurora.lambdacontrols.client.LambdaInput;
|
||||||
|
import dev.lambdaurora.lambdacontrols.client.mixin.AdvancementsScreenAccessor;
|
||||||
|
import dev.lambdaurora.lambdacontrols.client.mixin.CreativeInventoryScreenAccessor;
|
||||||
|
import dev.lambdaurora.lambdacontrols.client.mixin.RecipeBookWidgetAccessor;
|
||||||
|
import dev.lambdaurora.lambdacontrols.client.util.HandledScreenAccessor;
|
||||||
|
import net.minecraft.client.MinecraftClient;
|
||||||
|
import net.minecraft.client.gui.screen.advancement.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.lambdacontrols$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.lambdacontrols$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.getWindow().getFramebufferWidth(), client.getWindow().getFramebufferHeight(), client.getFramebuffer(),
|
||||||
|
text -> client.execute(() -> client.inGameHud.getChatHud().addMessage(text)));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean handleToggleSneak(@NotNull MinecraftClient client, @NotNull ButtonBinding button, 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.lambdacontrols$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 !LambdaInput.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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright © 2020 LambdAurora <aurora42lambda@gmail.com>
|
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
|
||||||
*
|
*
|
||||||
* This file is part of LambdaControls.
|
* This file is part of LambdaControls.
|
||||||
*
|
*
|
||||||
@@ -7,14 +7,18 @@
|
|||||||
* see the LICENSE file.
|
* see the LICENSE file.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package me.lambdaurora.lambdacontrols.client.controller;
|
package dev.lambdaurora.lambdacontrols.client.controller;
|
||||||
|
|
||||||
import me.lambdaurora.lambdacontrols.ControlsMode;
|
import dev.lambdaurora.lambdacontrols.ControlsMode;
|
||||||
import me.lambdaurora.lambdacontrols.client.ButtonState;
|
import dev.lambdaurora.lambdacontrols.client.ButtonState;
|
||||||
import me.lambdaurora.lambdacontrols.client.LambdaControlsClient;
|
import dev.lambdaurora.lambdacontrols.client.LambdaControlsClient;
|
||||||
import me.lambdaurora.lambdacontrols.client.LambdaControlsConfig;
|
import dev.lambdaurora.lambdacontrols.client.LambdaControlsConfig;
|
||||||
import me.lambdaurora.lambdacontrols.client.util.MouseAccessor;
|
import dev.lambdaurora.lambdacontrols.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.MinecraftClient;
|
||||||
|
import net.minecraft.client.option.KeyBinding;
|
||||||
|
import net.minecraft.client.util.InputUtil;
|
||||||
import net.minecraft.util.math.MathHelper;
|
import net.minecraft.util.math.MathHelper;
|
||||||
import org.aperlambda.lambdacommon.Identifier;
|
import org.aperlambda.lambdacommon.Identifier;
|
||||||
import org.aperlambda.lambdacommon.utils.function.PairPredicate;
|
import org.aperlambda.lambdacommon.utils.function.PairPredicate;
|
||||||
@@ -30,33 +34,30 @@ import java.util.stream.Stream;
|
|||||||
* Represents an input manager for controllers.
|
* Represents an input manager for controllers.
|
||||||
*
|
*
|
||||||
* @author LambdAurora
|
* @author LambdAurora
|
||||||
* @version 1.1.0
|
* @version 1.7.0
|
||||||
* @since 1.1.0
|
* @since 1.1.0
|
||||||
*/
|
*/
|
||||||
public class InputManager
|
public class InputManager {
|
||||||
{
|
|
||||||
public static final InputManager INPUT_MANAGER = new InputManager();
|
public static final InputManager INPUT_MANAGER = new InputManager();
|
||||||
private static final List<ButtonBinding> BINDINGS = new ArrayList<>();
|
private static final List<ButtonBinding> BINDINGS = new ArrayList<>();
|
||||||
private static final List<ButtonCategory> CATEGORIES = new ArrayList<>();
|
private static final List<ButtonCategory> CATEGORIES = new ArrayList<>();
|
||||||
public static final Map<Integer, ButtonState> STATES = new HashMap<>();
|
public static final Int2ObjectMap<ButtonState> STATES = new Int2ObjectOpenHashMap<>();
|
||||||
|
public static final Int2FloatMap BUTTON_VALUES = new Int2FloatOpenHashMap();
|
||||||
private int prevTargetMouseX = 0;
|
private int prevTargetMouseX = 0;
|
||||||
private int prevTargetMouseY = 0;
|
private int prevTargetMouseY = 0;
|
||||||
private int targetMouseX = 0;
|
private int targetMouseX = 0;
|
||||||
private int targetMouseY = 0;
|
private int targetMouseY = 0;
|
||||||
|
|
||||||
protected InputManager()
|
protected InputManager() {
|
||||||
{
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void tick(@NotNull MinecraftClient client)
|
public void tick(@NotNull MinecraftClient client) {
|
||||||
{
|
|
||||||
if (LambdaControlsClient.get().config.getControlsMode() == ControlsMode.CONTROLLER) {
|
if (LambdaControlsClient.get().config.getControlsMode() == ControlsMode.CONTROLLER) {
|
||||||
this.controllerTick(client);
|
this.controllerTick(client);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void controllerTick(@NotNull MinecraftClient client)
|
public void controllerTick(@NotNull MinecraftClient client) {
|
||||||
{
|
|
||||||
this.prevTargetMouseX = this.targetMouseX;
|
this.prevTargetMouseX = this.targetMouseX;
|
||||||
this.prevTargetMouseY = this.targetMouseY;
|
this.prevTargetMouseY = this.targetMouseY;
|
||||||
}
|
}
|
||||||
@@ -64,33 +65,31 @@ public class InputManager
|
|||||||
/**
|
/**
|
||||||
* Updates the mouse position. Should only be called on pre render of a screen.
|
* Updates the mouse position. Should only be called on pre render of a screen.
|
||||||
*
|
*
|
||||||
* @param client The client instance.
|
* @param client the client instance
|
||||||
*/
|
*/
|
||||||
public void updateMousePosition(@NotNull MinecraftClient client)
|
public void updateMousePosition(@NotNull MinecraftClient client) {
|
||||||
{
|
|
||||||
Objects.requireNonNull(client, "Client instance cannot be null.");
|
Objects.requireNonNull(client, "Client instance cannot be null.");
|
||||||
if (this.prevTargetMouseX != this.targetMouseX || this.prevTargetMouseY != this.targetMouseY) {
|
if (this.prevTargetMouseX != this.targetMouseX || this.prevTargetMouseY != this.targetMouseY) {
|
||||||
double mouseX = this.prevTargetMouseX + (this.targetMouseX - this.prevTargetMouseX) * client.getTickDelta() + 0.5;
|
double mouseX = this.prevTargetMouseX + (this.targetMouseX - this.prevTargetMouseX) * client.getTickDelta() + 0.5;
|
||||||
double mouseY = this.prevTargetMouseY + (this.targetMouseY - this.prevTargetMouseY) * client.getTickDelta() + 0.5;
|
double mouseY = this.prevTargetMouseY + (this.targetMouseY - this.prevTargetMouseY) * client.getTickDelta() + 0.5;
|
||||||
|
if (!LambdaControlsClient.get().config.hasVirtualMouse())
|
||||||
GLFW.glfwSetCursorPos(client.getWindow().getHandle(), mouseX, mouseY);
|
GLFW.glfwSetCursorPos(client.getWindow().getHandle(), mouseX, mouseY);
|
||||||
((MouseAccessor) client.mouse).lambdacontrols_onCursorPos(client.getWindow().getHandle(), mouseX, mouseY);
|
((MouseAccessor) client.mouse).lambdacontrols$onCursorPos(client.getWindow().getHandle(), mouseX, mouseY);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resets the mouse position.
|
* Resets the mouse position.
|
||||||
*
|
*
|
||||||
* @param windowWidth The window width.
|
* @param windowWidth the window width
|
||||||
* @param windowHeight The window height.
|
* @param windowHeight the window height
|
||||||
*/
|
*/
|
||||||
public void resetMousePosition(int windowWidth, int windowHeight)
|
public void resetMousePosition(int windowWidth, int windowHeight) {
|
||||||
{
|
|
||||||
this.targetMouseX = this.prevTargetMouseX = (int) (windowWidth / 2.F);
|
this.targetMouseX = this.prevTargetMouseX = (int) (windowWidth / 2.F);
|
||||||
this.targetMouseY = this.prevTargetMouseY = (int) (windowHeight / 2.F);
|
this.targetMouseY = this.prevTargetMouseY = (int) (windowHeight / 2.F);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void resetMouseTarget(@NotNull MinecraftClient client)
|
public void resetMouseTarget(@NotNull MinecraftClient client) {
|
||||||
{
|
|
||||||
double mouseX = client.mouse.getX();
|
double mouseX = client.mouse.getX();
|
||||||
double mouseY = client.mouse.getY();
|
double mouseY = client.mouse.getY();
|
||||||
this.prevTargetMouseX = this.targetMouseX = (int) mouseX;
|
this.prevTargetMouseX = this.targetMouseX = (int) mouseX;
|
||||||
@@ -100,77 +99,69 @@ public class InputManager
|
|||||||
/**
|
/**
|
||||||
* Returns whether the specified binding is registered or not.
|
* Returns whether the specified binding is registered or not.
|
||||||
*
|
*
|
||||||
* @param binding The binding to check.
|
* @param binding the binding to check
|
||||||
* @return True if the binding is registered, else false.
|
* @return true if the binding is registered, else false
|
||||||
*/
|
*/
|
||||||
public static boolean hasBinding(@NotNull ButtonBinding binding)
|
public static boolean hasBinding(@NotNull ButtonBinding binding) {
|
||||||
{
|
|
||||||
return BINDINGS.contains(binding);
|
return BINDINGS.contains(binding);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether the specified binding is registered or not.
|
* Returns whether the specified binding is registered or not.
|
||||||
*
|
*
|
||||||
* @param name The name of the binding to check.
|
* @param name the name of the binding to check
|
||||||
* @return True if the binding is registered, else false.
|
* @return true if the binding is registered, else false
|
||||||
*/
|
*/
|
||||||
public static boolean hasBinding(@NotNull String name)
|
public static boolean hasBinding(@NotNull String name) {
|
||||||
{
|
|
||||||
return BINDINGS.parallelStream().map(ButtonBinding::getName).anyMatch(binding -> binding.equalsIgnoreCase(name));
|
return BINDINGS.parallelStream().map(ButtonBinding::getName).anyMatch(binding -> binding.equalsIgnoreCase(name));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether the specified binding is registered or not.
|
* Returns whether the specified binding is registered or not.
|
||||||
*
|
*
|
||||||
* @param identifier The identifier of the binding to check.
|
* @param identifier the identifier of the binding to check
|
||||||
* @return True if the binding is registered, else false.
|
* @return true if the binding is registered, else false
|
||||||
*/
|
*/
|
||||||
public static boolean hasBinding(@NotNull Identifier identifier)
|
public static boolean hasBinding(@NotNull Identifier identifier) {
|
||||||
{
|
|
||||||
return hasBinding(identifier.getNamespace() + "." + identifier.getName());
|
return hasBinding(identifier.getNamespace() + "." + identifier.getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Registers a button binding.
|
* Registers a button binding.
|
||||||
*
|
*
|
||||||
* @param binding The binding to register.
|
* @param binding the binding to register
|
||||||
* @return The registered binding.
|
* @return the registered binding
|
||||||
*/
|
*/
|
||||||
public static @NotNull ButtonBinding registerBinding(@NotNull ButtonBinding binding)
|
public static @NotNull ButtonBinding registerBinding(@NotNull ButtonBinding binding) {
|
||||||
{
|
|
||||||
if (hasBinding(binding))
|
if (hasBinding(binding))
|
||||||
throw new IllegalStateException("Cannot register twice a button binding in the registry.");
|
throw new IllegalStateException("Cannot register twice a button binding in the registry.");
|
||||||
BINDINGS.add(binding);
|
BINDINGS.add(binding);
|
||||||
return binding;
|
return binding;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static @NotNull ButtonBinding registerBinding(@NotNull Identifier id, int[] defaultButton, @NotNull List<PressAction> actions, @NotNull PairPredicate<MinecraftClient, ButtonBinding> filter, boolean hasCooldown)
|
public static @NotNull ButtonBinding registerBinding(@NotNull Identifier id, int[] defaultButton, @NotNull List<PressAction> actions, @NotNull PairPredicate<MinecraftClient, ButtonBinding> filter, boolean hasCooldown) {
|
||||||
{
|
|
||||||
return registerBinding(new ButtonBinding(id.getNamespace() + "." + id.getName(), defaultButton, actions, filter, hasCooldown));
|
return registerBinding(new ButtonBinding(id.getNamespace() + "." + id.getName(), defaultButton, actions, filter, hasCooldown));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static @NotNull ButtonBinding registerBinding(@NotNull Identifier id, int[] defaultButton, boolean hasCooldown)
|
public static @NotNull ButtonBinding registerBinding(@NotNull Identifier id, int[] defaultButton, boolean hasCooldown) {
|
||||||
{
|
|
||||||
return registerBinding(id, defaultButton, Collections.emptyList(), InputHandlers::always, hasCooldown);
|
return registerBinding(id, defaultButton, Collections.emptyList(), InputHandlers::always, hasCooldown);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static @NotNull ButtonBinding registerBinding(@NotNull net.minecraft.util.Identifier id, int[] defaultButton, @NotNull List<PressAction> actions, @NotNull PairPredicate<MinecraftClient, ButtonBinding> filter, boolean hasCooldown)
|
public static @NotNull ButtonBinding registerBinding(@NotNull net.minecraft.util.Identifier id, int[] defaultButton, @NotNull List<PressAction> actions, @NotNull PairPredicate<MinecraftClient, ButtonBinding> filter, boolean hasCooldown) {
|
||||||
{
|
|
||||||
return registerBinding(new Identifier(id.getNamespace(), id.getPath()), defaultButton, actions, filter, hasCooldown);
|
return registerBinding(new Identifier(id.getNamespace(), id.getPath()), defaultButton, actions, filter, hasCooldown);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static @NotNull ButtonBinding registerBinding(@NotNull net.minecraft.util.Identifier id, int[] defaultButton, boolean hasCooldown)
|
public static @NotNull ButtonBinding registerBinding(@NotNull net.minecraft.util.Identifier id, int[] defaultButton, boolean hasCooldown) {
|
||||||
{
|
|
||||||
return registerBinding(id, defaultButton, Collections.emptyList(), InputHandlers::always, hasCooldown);
|
return registerBinding(id, defaultButton, Collections.emptyList(), InputHandlers::always, hasCooldown);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sorts bindings to get bindings with the higher button counts first.
|
* Sorts bindings to get bindings with the higher button counts first.
|
||||||
*/
|
*/
|
||||||
public static void sortBindings()
|
public static void sortBindings() {
|
||||||
{
|
|
||||||
synchronized (BINDINGS) {
|
synchronized (BINDINGS) {
|
||||||
List<ButtonBinding> sorted = BINDINGS.stream().sorted(Collections.reverseOrder(Comparator.comparingInt(binding -> binding.getButton().length)))
|
var sorted = BINDINGS.stream()
|
||||||
|
.sorted(Collections.reverseOrder(Comparator.comparingInt(binding -> binding.getButton().length)))
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
BINDINGS.clear();
|
BINDINGS.clear();
|
||||||
BINDINGS.addAll(sorted);
|
BINDINGS.addAll(sorted);
|
||||||
@@ -180,28 +171,24 @@ public class InputManager
|
|||||||
/**
|
/**
|
||||||
* Registers a category of button bindings.
|
* Registers a category of button bindings.
|
||||||
*
|
*
|
||||||
* @param category The category to register.
|
* @param category the category to register
|
||||||
* @return The registered category.
|
* @return the registered category
|
||||||
*/
|
*/
|
||||||
public static ButtonCategory registerCategory(@NotNull ButtonCategory category)
|
public static ButtonCategory registerCategory(@NotNull ButtonCategory category) {
|
||||||
{
|
|
||||||
CATEGORIES.add(category);
|
CATEGORIES.add(category);
|
||||||
return category;
|
return category;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ButtonCategory registerCategory(@NotNull Identifier identifier, int priority)
|
public static ButtonCategory registerCategory(@NotNull Identifier identifier, int priority) {
|
||||||
{
|
|
||||||
return registerCategory(new ButtonCategory(identifier, priority));
|
return registerCategory(new ButtonCategory(identifier, priority));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ButtonCategory registerCategory(@NotNull Identifier identifier)
|
public static ButtonCategory registerCategory(@NotNull Identifier identifier) {
|
||||||
{
|
|
||||||
return registerCategory(new ButtonCategory(identifier));
|
return registerCategory(new ButtonCategory(identifier));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static ButtonCategory registerDefaultCategory(@NotNull String key, @NotNull Consumer<ButtonCategory> keyAdder)
|
protected static ButtonCategory registerDefaultCategory(@NotNull String key, @NotNull Consumer<ButtonCategory> keyAdder) {
|
||||||
{
|
var category = registerCategory(new Identifier("minecraft", key), CATEGORIES.size());
|
||||||
ButtonCategory category = registerCategory(new Identifier("minecraft", key), CATEGORIES.size());
|
|
||||||
keyAdder.accept(category);
|
keyAdder.accept(category);
|
||||||
return category;
|
return category;
|
||||||
}
|
}
|
||||||
@@ -209,25 +196,23 @@ public class InputManager
|
|||||||
/**
|
/**
|
||||||
* Loads the button bindings from configuration.
|
* Loads the button bindings from configuration.
|
||||||
*
|
*
|
||||||
* @param config The configuration instance.
|
* @param config the configuration instance
|
||||||
*/
|
*/
|
||||||
public static void loadButtonBindings(@NotNull LambdaControlsConfig config)
|
public static void loadButtonBindings(@NotNull LambdaControlsConfig config) {
|
||||||
{
|
var queue = new ArrayList<>(BINDINGS);
|
||||||
List<ButtonBinding> queue = new ArrayList<>(BINDINGS);
|
|
||||||
queue.forEach(config::loadButtonBinding);
|
queue.forEach(config::loadButtonBinding);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the binding state.
|
* Returns the binding state.
|
||||||
*
|
*
|
||||||
* @param binding The binding.
|
* @param binding the binding
|
||||||
* @return The current state of the binding.
|
* @return the current state of the binding
|
||||||
*/
|
*/
|
||||||
public static @NotNull ButtonState getBindingState(@NotNull ButtonBinding binding)
|
public static @NotNull ButtonState getBindingState(@NotNull ButtonBinding binding) {
|
||||||
{
|
var state = ButtonState.REPEAT;
|
||||||
ButtonState state = ButtonState.REPEAT;
|
|
||||||
for (int btn : binding.getButton()) {
|
for (int btn : binding.getButton()) {
|
||||||
ButtonState btnState = InputManager.STATES.getOrDefault(btn, ButtonState.NONE);
|
var btnState = InputManager.STATES.getOrDefault(btn, ButtonState.NONE);
|
||||||
if (btnState == ButtonState.PRESS)
|
if (btnState == ButtonState.PRESS)
|
||||||
state = ButtonState.PRESS;
|
state = ButtonState.PRESS;
|
||||||
else if (btnState == ButtonState.RELEASE) {
|
else if (btnState == ButtonState.RELEASE) {
|
||||||
@@ -241,37 +226,50 @@ public class InputManager
|
|||||||
return state;
|
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.
|
* Returns whether the button has duplicated bindings.
|
||||||
*
|
*
|
||||||
* @param button The button to check.
|
* @param button the button to check
|
||||||
* @return True if the button has duplicated bindings, else false.
|
* @return true if the button has duplicated bindings, else false
|
||||||
*/
|
*/
|
||||||
public static boolean hasDuplicatedBindings(int[] button)
|
public static boolean hasDuplicatedBindings(int[] button) {
|
||||||
{
|
|
||||||
return BINDINGS.parallelStream().filter(binding -> areButtonsEquivalent(binding.getButton(), button)).count() > 1;
|
return BINDINGS.parallelStream().filter(binding -> areButtonsEquivalent(binding.getButton(), button)).count() > 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether the button has duplicated bindings.
|
* Returns whether the button has duplicated bindings.
|
||||||
*
|
*
|
||||||
* @param binding The binding to check.
|
* @param binding the binding to check
|
||||||
* @return True if the button has duplicated bindings, else false.
|
* @return true if the button has duplicated bindings, else false
|
||||||
*/
|
*/
|
||||||
public static boolean hasDuplicatedBindings(ButtonBinding binding)
|
public static boolean hasDuplicatedBindings(ButtonBinding binding) {
|
||||||
{
|
|
||||||
return BINDINGS.parallelStream().filter(other -> areButtonsEquivalent(other.getButton(), binding.getButton()) && other.filter.equals(binding.filter)).count() > 1;
|
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.
|
* Returns whether the specified buttons are equivalent or not.
|
||||||
*
|
*
|
||||||
* @param buttons1 First set of buttons.
|
* @param buttons1 first set of buttons
|
||||||
* @param buttons2 Second set of buttons.
|
* @param buttons2 second set of buttons
|
||||||
* @return True if the two sets of buttons are equivalent, else false.
|
* @return true if the two sets of buttons are equivalent, else false
|
||||||
*/
|
*/
|
||||||
public static boolean areButtonsEquivalent(int[] buttons1, int[] buttons2)
|
public static boolean areButtonsEquivalent(int[] buttons1, int[] buttons2) {
|
||||||
{
|
|
||||||
if (buttons1.length != buttons2.length)
|
if (buttons1.length != buttons2.length)
|
||||||
return false;
|
return false;
|
||||||
int count = 0;
|
int count = 0;
|
||||||
@@ -289,72 +287,103 @@ public class InputManager
|
|||||||
/**
|
/**
|
||||||
* Returns whether the button set contains the specified button or not.
|
* Returns whether the button set contains the specified button or not.
|
||||||
*
|
*
|
||||||
* @param buttons The button set.
|
* @param buttons the button set
|
||||||
* @param button The button to check.
|
* @param button the button to check
|
||||||
* @return True if the button set contains the specified button, else false.
|
* @return true if the button set contains the specified button, else false
|
||||||
*/
|
*/
|
||||||
public static boolean containsButton(int[] buttons, int button)
|
public static boolean containsButton(int[] buttons, int button) {
|
||||||
{
|
|
||||||
return Arrays.stream(buttons).anyMatch(btn -> btn == button);
|
return Arrays.stream(buttons).anyMatch(btn -> btn == button);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the button states.
|
* Updates the button states.
|
||||||
*/
|
*/
|
||||||
public static void updateStates()
|
public static void updateStates() {
|
||||||
{
|
for (var entry : STATES.int2ObjectEntrySet()) {
|
||||||
STATES.forEach((btn, state) -> {
|
if (entry.getValue() == ButtonState.PRESS)
|
||||||
if (state == ButtonState.PRESS)
|
STATES.put(entry.getIntKey(), ButtonState.REPEAT);
|
||||||
STATES.put(btn, ButtonState.REPEAT);
|
else if (entry.getValue() == ButtonState.RELEASE)
|
||||||
else if (state == ButtonState.RELEASE)
|
STATES.put(entry.getIntKey(), ButtonState.NONE);
|
||||||
STATES.put(btn, ButtonState.NONE);
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void updateBindings(@NotNull MinecraftClient client)
|
public static void updateBindings(@NotNull MinecraftClient client) {
|
||||||
{
|
var skipButtons = new IntArrayList();
|
||||||
List<Integer> skipButtons = new ArrayList<>();
|
record ButtonStateValue(ButtonState state, float value) {
|
||||||
Map<ButtonBinding, ButtonState> states = new HashMap<>();
|
}
|
||||||
for (ButtonBinding binding : BINDINGS) {
|
var states = new Object2ObjectOpenHashMap<ButtonBinding, ButtonStateValue>();
|
||||||
ButtonState state = binding.isAvailable(client) ? getBindingState(binding) : ButtonState.NONE;
|
for (var binding : BINDINGS) {
|
||||||
|
var state = binding.isAvailable(client) ? getBindingState(binding) : ButtonState.NONE;
|
||||||
if (skipButtons.stream().anyMatch(btn -> containsButton(binding.getButton(), btn))) {
|
if (skipButtons.stream().anyMatch(btn -> containsButton(binding.getButton(), btn))) {
|
||||||
if (binding.pressed)
|
if (binding.pressed)
|
||||||
state = ButtonState.RELEASE;
|
state = ButtonState.RELEASE;
|
||||||
else
|
else
|
||||||
state = ButtonState.NONE;
|
state = ButtonState.NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (state == ButtonState.RELEASE && !binding.pressed) {
|
||||||
|
state = ButtonState.NONE;
|
||||||
|
}
|
||||||
|
|
||||||
binding.pressed = state.isPressed();
|
binding.pressed = state.isPressed();
|
||||||
binding.update();
|
binding.update();
|
||||||
if (binding.pressed)
|
if (binding.pressed)
|
||||||
Arrays.stream(binding.getButton()).forEach(skipButtons::add);
|
Arrays.stream(binding.getButton()).forEach(skipButtons::add);
|
||||||
states.put(binding, state);
|
|
||||||
|
float value = getBindingValue(binding, state);
|
||||||
|
|
||||||
|
states.put(binding, new ButtonStateValue(state, value));
|
||||||
}
|
}
|
||||||
|
|
||||||
states.forEach((binding, state) -> {
|
states.forEach((binding, state) -> {
|
||||||
if (state != ButtonState.NONE) {
|
if (state.state() != ButtonState.NONE) {
|
||||||
binding.handle(client, state);
|
binding.handle(client, state.value(), state.state());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void queueMousePosition(double x, double y)
|
public static void queueMousePosition(double x, double y) {
|
||||||
{
|
|
||||||
INPUT_MANAGER.targetMouseX = (int) MathHelper.clamp(x, 0, MinecraftClient.getInstance().getWindow().getWidth());
|
INPUT_MANAGER.targetMouseX = (int) MathHelper.clamp(x, 0, MinecraftClient.getInstance().getWindow().getWidth());
|
||||||
INPUT_MANAGER.targetMouseY = (int) MathHelper.clamp(y, 0, MinecraftClient.getInstance().getWindow().getHeight());
|
INPUT_MANAGER.targetMouseY = (int) MathHelper.clamp(y, 0, MinecraftClient.getInstance().getWindow().getHeight());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void queueMoveMousePosition(double x, double y)
|
public static void queueMoveMousePosition(double x, double y) {
|
||||||
{
|
|
||||||
queueMousePosition(INPUT_MANAGER.targetMouseX + x, INPUT_MANAGER.targetMouseY + y);
|
queueMousePosition(INPUT_MANAGER.targetMouseX + x, INPUT_MANAGER.targetMouseY + y);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static @NotNull Stream<ButtonBinding> streamBindings()
|
public static @NotNull Stream<ButtonBinding> streamBindings() {
|
||||||
{
|
|
||||||
return BINDINGS.stream();
|
return BINDINGS.stream();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static @NotNull Stream<ButtonCategory> streamCategories()
|
public static @NotNull Stream<ButtonCategory> streamCategories() {
|
||||||
{
|
|
||||||
return CATEGORIES.stream();
|
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,97 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
|
||||||
|
*
|
||||||
|
* This file is part of LambdaControls.
|
||||||
|
*
|
||||||
|
* Licensed under the MIT license. For more information,
|
||||||
|
* see the LICENSE file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package dev.lambdaurora.lambdacontrols.client.controller;
|
||||||
|
|
||||||
|
import dev.lambdaurora.lambdacontrols.client.ButtonState;
|
||||||
|
import dev.lambdaurora.lambdacontrols.client.LambdaControlsClient;
|
||||||
|
import net.minecraft.client.MinecraftClient;
|
||||||
|
import net.minecraft.client.network.ClientPlayerEntity;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the movement handler.
|
||||||
|
*
|
||||||
|
* @author LambdAurora
|
||||||
|
* @version 1.6.0
|
||||||
|
* @since 1.4.0
|
||||||
|
*/
|
||||||
|
public final class MovementHandler implements PressAction {
|
||||||
|
public static final MovementHandler HANDLER = new MovementHandler();
|
||||||
|
private boolean shouldOverrideMovement = false;
|
||||||
|
private boolean pressingForward = false;
|
||||||
|
private boolean pressingBack = false;
|
||||||
|
private boolean pressingLeft = false;
|
||||||
|
private boolean pressingRight = false;
|
||||||
|
private float movementForward = 0.f;
|
||||||
|
private float movementSideways = 0.f;
|
||||||
|
|
||||||
|
private MovementHandler() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies movement input of this handler to the player's input.
|
||||||
|
*
|
||||||
|
* @param player The client player.
|
||||||
|
*/
|
||||||
|
public void applyMovement(@NotNull ClientPlayerEntity player) {
|
||||||
|
if (!this.shouldOverrideMovement)
|
||||||
|
return;
|
||||||
|
player.input.pressingForward = this.pressingForward;
|
||||||
|
player.input.pressingBack = this.pressingBack;
|
||||||
|
player.input.pressingLeft = this.pressingLeft;
|
||||||
|
player.input.pressingRight = this.pressingRight;
|
||||||
|
player.input.movementForward = this.movementForward;
|
||||||
|
player.input.movementSideways = this.movementSideways;
|
||||||
|
this.shouldOverrideMovement = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean press(@NotNull MinecraftClient client, @NotNull ButtonBinding button, float value, @NotNull ButtonState action) {
|
||||||
|
if (client.currentScreen != null || client.player == null)
|
||||||
|
return this.shouldOverrideMovement = false;
|
||||||
|
|
||||||
|
int direction = 0;
|
||||||
|
if (button == ButtonBinding.FORWARD || button == ButtonBinding.LEFT)
|
||||||
|
direction = 1;
|
||||||
|
else if (button == ButtonBinding.BACK || button == ButtonBinding.RIGHT)
|
||||||
|
direction = -1;
|
||||||
|
|
||||||
|
if (action.isUnpressed())
|
||||||
|
direction = 0;
|
||||||
|
|
||||||
|
this.shouldOverrideMovement = direction != 0;
|
||||||
|
|
||||||
|
if (LambdaControlsClient.get().config.hasAnalogMovement()) {
|
||||||
|
value = (float) Math.pow(value, 2);
|
||||||
|
} else value = 1.f;
|
||||||
|
|
||||||
|
if (button == ButtonBinding.FORWARD || button == ButtonBinding.BACK) {
|
||||||
|
// Handle forward movement.
|
||||||
|
this.pressingForward = direction > 0;
|
||||||
|
this.pressingBack = direction < 0;
|
||||||
|
this.movementForward = direction * value;
|
||||||
|
|
||||||
|
// Slowing down if sneaking.
|
||||||
|
if (client.player.input.sneaking)
|
||||||
|
this.movementForward *= 0.3D;
|
||||||
|
} else {
|
||||||
|
// Handle sideways movement.
|
||||||
|
this.pressingLeft = direction > 0;
|
||||||
|
this.pressingRight = direction < 0;
|
||||||
|
this.movementSideways = direction * value;
|
||||||
|
|
||||||
|
// Slowing down if sneaking.
|
||||||
|
if (client.player.input.sneaking)
|
||||||
|
this.movementSideways *= 0.3D;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.shouldOverrideMovement;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
|
||||||
|
*
|
||||||
|
* This file is part of LambdaControls.
|
||||||
|
*
|
||||||
|
* Licensed under the MIT license. For more information,
|
||||||
|
* see the LICENSE file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package dev.lambdaurora.lambdacontrols.client.controller;
|
||||||
|
|
||||||
|
import dev.lambdaurora.lambdacontrols.client.ButtonState;
|
||||||
|
import dev.lambdaurora.lambdacontrols.client.util.KeyBindingAccessor;
|
||||||
|
import net.minecraft.client.MinecraftClient;
|
||||||
|
import net.minecraft.client.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).lambdacontrols$handlePressState(button.isButtonDown());
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles when there is a press action.
|
||||||
|
*
|
||||||
|
* @param client the client instance
|
||||||
|
* @param action the action done
|
||||||
|
*/
|
||||||
|
boolean press(@NotNull MinecraftClient client, @NotNull ButtonBinding button, float value, @NotNull ButtonState action);
|
||||||
|
}
|
||||||
@@ -0,0 +1,276 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
|
||||||
|
*
|
||||||
|
* This file is part of LambdaControls.
|
||||||
|
*
|
||||||
|
* Licensed under the MIT license. For more information,
|
||||||
|
* see the LICENSE file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package dev.lambdaurora.lambdacontrols.client.gui;
|
||||||
|
|
||||||
|
import dev.lambdaurora.lambdacontrols.ControlsMode;
|
||||||
|
import dev.lambdaurora.lambdacontrols.LambdaControlsConstants;
|
||||||
|
import dev.lambdaurora.lambdacontrols.client.HudSide;
|
||||||
|
import dev.lambdaurora.lambdacontrols.client.LambdaControlsClient;
|
||||||
|
import dev.lambdaurora.lambdacontrols.client.compat.LambdaControlsCompat;
|
||||||
|
import dev.lambdaurora.lambdacontrols.client.controller.ButtonBinding;
|
||||||
|
import 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 LambdaControls HUD.
|
||||||
|
*
|
||||||
|
* @author LambdAurora
|
||||||
|
* @version 1.7.0
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public class LambdaControlsHud extends Hud {
|
||||||
|
private final LambdaControlsClient mod;
|
||||||
|
private MinecraftClient client;
|
||||||
|
private int attackWidth = 0;
|
||||||
|
private int attackButtonWidth = 0;
|
||||||
|
private int dropItemWidth = 0;
|
||||||
|
private int dropItemButtonWidth = 0;
|
||||||
|
private int inventoryWidth = 0;
|
||||||
|
private int inventoryButtonWidth = 0;
|
||||||
|
private int swapHandsWidth = 0;
|
||||||
|
private int swapHandsButtonWidth = 0;
|
||||||
|
private int useWidth = 0;
|
||||||
|
private int useButtonWidth = 0;
|
||||||
|
private BlockHitResult placeHitResult;
|
||||||
|
private String attackAction = "";
|
||||||
|
private String placeAction = "";
|
||||||
|
private int ticksDisplayedCrosshair = 0;
|
||||||
|
|
||||||
|
public LambdaControlsHud(@NotNull LambdaControlsClient mod) {
|
||||||
|
super(new Identifier(LambdaControlsConstants.NAMESPACE, "hud/button_indicator"));
|
||||||
|
this.mod = mod;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(@NotNull MinecraftClient client, int screenWidth, int screenHeight) {
|
||||||
|
super.init(client, screenWidth, screenHeight);
|
||||||
|
this.client = client;
|
||||||
|
this.inventoryWidth = this.width(ButtonBinding.INVENTORY);
|
||||||
|
this.inventoryButtonWidth = LambdaControlsRenderer.getBindingIconWidth(ButtonBinding.INVENTORY);
|
||||||
|
this.swapHandsWidth = this.width(ButtonBinding.SWAP_HANDS);
|
||||||
|
this.swapHandsButtonWidth = LambdaControlsRenderer.getBindingIconWidth(ButtonBinding.SWAP_HANDS);
|
||||||
|
this.dropItemWidth = this.width(ButtonBinding.DROP_ITEM);
|
||||||
|
this.dropItemButtonWidth = LambdaControlsRenderer.getBindingIconWidth(ButtonBinding.DROP_ITEM);
|
||||||
|
this.attackButtonWidth = LambdaControlsRenderer.getBindingIconWidth(ButtonBinding.ATTACK);
|
||||||
|
this.useButtonWidth = LambdaControlsRenderer.getBindingIconWidth(ButtonBinding.USE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders the LambdaControls' HUD.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void render(MatrixStack matrices, float tickDelta) {
|
||||||
|
if (this.mod.config.getControlsMode() == ControlsMode.CONTROLLER && this.client.currentScreen == null) {
|
||||||
|
int y = bottom(2);
|
||||||
|
this.renderFirstIcons(matrices, this.mod.config.getHudSide() == HudSide.LEFT ? 2 : client.getWindow().getScaledWidth() - 2, y);
|
||||||
|
this.renderSecondIcons(matrices, this.mod.config.getHudSide() == HudSide.RIGHT ? 2 : client.getWindow().getScaledWidth() - 2, y);
|
||||||
|
this.renderFirstSection(matrices, this.mod.config.getHudSide() == HudSide.LEFT ? 2 : client.getWindow().getScaledWidth() - 2, y);
|
||||||
|
this.renderSecondSection(matrices, this.mod.config.getHudSide() == HudSide.RIGHT ? 2 : client.getWindow().getScaledWidth() - 2, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.mod.reacharound.isLastReacharoundVertical()) {
|
||||||
|
// Render crosshair indicator.
|
||||||
|
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 = this.mod.config.getHudSide() == HudSide.LEFT ? x : x - this.inventoryButtonWidth;
|
||||||
|
this.drawButton(matrices, currentX, y, ButtonBinding.INVENTORY, true);
|
||||||
|
this.drawButton(matrices, currentX += (this.mod.config.getHudSide() == HudSide.LEFT ? offset : -offset), y, ButtonBinding.SWAP_HANDS, true);
|
||||||
|
offset = 2 + this.swapHandsWidth + this.dropItemButtonWidth + 4;
|
||||||
|
if (this.client.options.showSubtitles && this.mod.config.getHudSide() == HudSide.RIGHT) {
|
||||||
|
currentX += -offset;
|
||||||
|
} else {
|
||||||
|
currentX = this.mod.config.getHudSide() == HudSide.LEFT ? x : x - this.dropItemButtonWidth;
|
||||||
|
y -= 24;
|
||||||
|
}
|
||||||
|
this.drawButton(matrices, currentX, y, ButtonBinding.DROP_ITEM, !this.client.player.getMainHandStack().isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void renderSecondIcons(MatrixStack matrices, int x, int y) {
|
||||||
|
int offset;
|
||||||
|
int currentX = x;
|
||||||
|
if (!this.placeAction.isEmpty()) {
|
||||||
|
if (this.mod.config.getHudSide() == HudSide.LEFT)
|
||||||
|
currentX -= this.useButtonWidth;
|
||||||
|
this.drawButton(matrices, currentX, y, ButtonBinding.USE, true);
|
||||||
|
offset = 2 + this.useWidth + 4;
|
||||||
|
if (this.client.options.showSubtitles && this.mod.config.getHudSide() == HudSide.LEFT) {
|
||||||
|
currentX -= offset;
|
||||||
|
} else {
|
||||||
|
currentX = x;
|
||||||
|
y -= 24;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.mod.config.getHudSide() == HudSide.LEFT)
|
||||||
|
currentX -= this.attackButtonWidth;
|
||||||
|
|
||||||
|
this.drawButton(matrices, currentX, y, ButtonBinding.ATTACK, this.attackWidth != 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void renderFirstSection(MatrixStack matrices, int x, int y) {
|
||||||
|
int currentX = this.mod.config.getHudSide() == HudSide.LEFT ? x + this.inventoryButtonWidth + 2 : x - this.inventoryButtonWidth - 2 - this.inventoryWidth;
|
||||||
|
this.drawTip(matrices, currentX, y, ButtonBinding.INVENTORY, true);
|
||||||
|
currentX += this.mod.config.getHudSide() == HudSide.LEFT ? this.inventoryWidth + 4 + this.swapHandsButtonWidth + 2
|
||||||
|
: -this.swapHandsWidth - 2 - this.swapHandsButtonWidth - 4;
|
||||||
|
this.drawTip(matrices, currentX, y, ButtonBinding.SWAP_HANDS, true);
|
||||||
|
if (this.client.options.showSubtitles && this.mod.config.getHudSide() == HudSide.RIGHT) {
|
||||||
|
currentX += -this.dropItemWidth - 2 - this.dropItemButtonWidth - 4;
|
||||||
|
} else {
|
||||||
|
y -= 24;
|
||||||
|
currentX = this.mod.config.getHudSide() == HudSide.LEFT ? x + this.dropItemButtonWidth + 2 : x - this.dropItemButtonWidth - 2 - this.dropItemWidth;
|
||||||
|
}
|
||||||
|
this.drawTip(matrices, currentX, y, ButtonBinding.DROP_ITEM, !this.client.player.getMainHandStack().isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void renderSecondSection(MatrixStack matrices, int x, int y) {
|
||||||
|
int currentX = x;
|
||||||
|
|
||||||
|
if (!this.placeAction.isEmpty()) {
|
||||||
|
currentX += this.mod.config.getHudSide() == HudSide.RIGHT ? this.useButtonWidth + 2 : -this.useButtonWidth - 2 - this.useWidth;
|
||||||
|
|
||||||
|
this.drawTip(matrices, currentX, y, this.placeAction, true);
|
||||||
|
|
||||||
|
if (this.client.options.showSubtitles && this.mod.config.getHudSide() == HudSide.LEFT) {
|
||||||
|
currentX -= 4;
|
||||||
|
} else {
|
||||||
|
currentX = x;
|
||||||
|
y -= 24;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
currentX += this.mod.config.getHudSide() == HudSide.RIGHT ? this.attackButtonWidth + 2 : -this.attackButtonWidth - 2 - this.attackWidth;
|
||||||
|
|
||||||
|
this.drawTip(matrices, currentX, y, this.attackAction, this.attackWidth != 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void tick() {
|
||||||
|
super.tick();
|
||||||
|
if (this.mod.config.getControlsMode() == ControlsMode.CONTROLLER) {
|
||||||
|
if (this.client.crosshairTarget == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
String placeAction;
|
||||||
|
|
||||||
|
// Update "Use" tip status.
|
||||||
|
if (this.client.crosshairTarget.getType() == HitResult.Type.MISS) {
|
||||||
|
this.placeHitResult = this.mod.reacharound.getLastReacharoundResult();
|
||||||
|
this.attackAction = "";
|
||||||
|
this.attackWidth = 0;
|
||||||
|
} else {
|
||||||
|
if (this.client.crosshairTarget.getType() == HitResult.Type.BLOCK)
|
||||||
|
this.placeHitResult = (BlockHitResult) this.client.crosshairTarget;
|
||||||
|
else
|
||||||
|
this.placeHitResult = null;
|
||||||
|
|
||||||
|
this.attackAction = this.client.crosshairTarget.getType() == HitResult.Type.BLOCK ? "lambdacontrols.action.hit" : ButtonBinding.ATTACK.getTranslationKey();
|
||||||
|
this.attackWidth = this.width(attackAction);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.mod.reacharound.isLastReacharoundVertical()) {
|
||||||
|
if (this.ticksDisplayedCrosshair < 5)
|
||||||
|
this.ticksDisplayedCrosshair++;
|
||||||
|
} else {
|
||||||
|
this.ticksDisplayedCrosshair = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
var customAttackAction = LambdaControlsCompat.getAttackActionAt(this.client, this.placeHitResult);
|
||||||
|
if (customAttackAction != null) {
|
||||||
|
this.attackAction = customAttackAction;
|
||||||
|
this.attackWidth = this.width(customAttackAction);
|
||||||
|
}
|
||||||
|
|
||||||
|
ItemStack stack = null;
|
||||||
|
if (this.client.player != null) {
|
||||||
|
stack = this.client.player.getMainHandStack();
|
||||||
|
if (stack == null || stack.isEmpty())
|
||||||
|
stack = this.client.player.getOffHandStack();
|
||||||
|
}
|
||||||
|
if (stack == null || stack.isEmpty()) {
|
||||||
|
placeAction = "";
|
||||||
|
} else {
|
||||||
|
if (this.placeHitResult != null && stack.getItem() instanceof BlockItem) {
|
||||||
|
placeAction = "lambdacontrols.action.place";
|
||||||
|
} else {
|
||||||
|
placeAction = ButtonBinding.USE.getTranslationKey();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var customUseAction = LambdaControlsCompat.getUseActionAt(this.client, this.placeHitResult);
|
||||||
|
if (customUseAction != null)
|
||||||
|
placeAction = customUseAction;
|
||||||
|
|
||||||
|
this.placeAction = placeAction;
|
||||||
|
|
||||||
|
// Cache the "Use" tip width.
|
||||||
|
if (this.placeAction.isEmpty())
|
||||||
|
this.useWidth = 0;
|
||||||
|
else
|
||||||
|
this.useWidth = this.width(this.placeAction);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasTicks() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int bottom(int y) {
|
||||||
|
return this.client.getWindow().getScaledHeight() - y - LambdaControlsRenderer.ICON_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int width(@NotNull ButtonBinding binding) {
|
||||||
|
return this.width(binding.getTranslationKey());
|
||||||
|
}
|
||||||
|
|
||||||
|
private int width(@Nullable String text) {
|
||||||
|
if (text == null || text.isEmpty())
|
||||||
|
return 0;
|
||||||
|
return this.client.textRenderer.getWidth(I18n.translate(text));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void drawButton(MatrixStack matrices, int x, int y, @NotNull ButtonBinding button, boolean display) {
|
||||||
|
if (display)
|
||||||
|
LambdaControlsRenderer.drawButton(matrices, x, y, button, this.client);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void drawTip(MatrixStack matrices, int x, int y, @NotNull ButtonBinding button, boolean display) {
|
||||||
|
this.drawTip(matrices, x, y, button.getTranslationKey(), display);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void drawTip(MatrixStack matrices, int x, int y, @NotNull String action, boolean display) {
|
||||||
|
if (!display)
|
||||||
|
return;
|
||||||
|
var translatedAction = I18n.translate(action);
|
||||||
|
int textY = (LambdaControlsRenderer.ICON_SIZE / 2 - this.client.textRenderer.fontHeight / 2) + 1;
|
||||||
|
this.client.textRenderer.draw(matrices, translatedAction, (float) x, (float) (y + textY), 14737632);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,255 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
|
||||||
|
*
|
||||||
|
* This file is part of LambdaControls.
|
||||||
|
*
|
||||||
|
* Licensed under the MIT license. For more information,
|
||||||
|
* see the LICENSE file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package dev.lambdaurora.lambdacontrols.client.gui;
|
||||||
|
|
||||||
|
import com.mojang.blaze3d.systems.RenderSystem;
|
||||||
|
import dev.lambdaurora.lambdacontrols.client.LambdaControlsClient;
|
||||||
|
import dev.lambdaurora.lambdacontrols.client.LambdaInput;
|
||||||
|
import dev.lambdaurora.lambdacontrols.client.compat.LambdaControlsCompat;
|
||||||
|
import dev.lambdaurora.lambdacontrols.client.controller.ButtonBinding;
|
||||||
|
import dev.lambdaurora.lambdacontrols.client.util.HandledScreenAccessor;
|
||||||
|
import net.minecraft.client.MinecraftClient;
|
||||||
|
import net.minecraft.client.font.TextRenderer;
|
||||||
|
import net.minecraft.client.gui.DrawableHelper;
|
||||||
|
import net.minecraft.client.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 LambdaControls renderer.
|
||||||
|
*
|
||||||
|
* @author LambdAurora
|
||||||
|
* @version 1.7.0
|
||||||
|
* @since 1.2.0
|
||||||
|
*/
|
||||||
|
public class LambdaControlsRenderer {
|
||||||
|
public static final int ICON_SIZE = 20;
|
||||||
|
private static final int BUTTON_SIZE = 15;
|
||||||
|
private static final int AXIS_SIZE = 18;
|
||||||
|
|
||||||
|
public static int getButtonSize(int button) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("deprecated")
|
||||||
|
public static int drawButton(MatrixStack matrices, int x, int y, int button, @NotNull MinecraftClient client) {
|
||||||
|
boolean second = false;
|
||||||
|
if (button == -1)
|
||||||
|
return 0;
|
||||||
|
else if (button >= 500) {
|
||||||
|
button -= 1000;
|
||||||
|
second = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int controllerType = LambdaControlsClient.get().config.getControllerType().getId();
|
||||||
|
boolean axis = false;
|
||||||
|
int buttonOffset = button * 15;
|
||||||
|
switch (button) {
|
||||||
|
case GLFW.GLFW_GAMEPAD_BUTTON_LEFT_BUMPER -> buttonOffset = 7 * 15;
|
||||||
|
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 ? LambdaControlsClient.CONTROLLER_AXIS : LambdaControlsClient.CONTROLLER_BUTTONS);
|
||||||
|
RenderSystem.disableDepthTest();
|
||||||
|
|
||||||
|
int assetSize = axis ? 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 * (axis ? AXIS_SIZE : BUTTON_SIZE)),
|
||||||
|
assetSize, assetSize,
|
||||||
|
256, 256);
|
||||||
|
RenderSystem.enableDepthTest();
|
||||||
|
|
||||||
|
return ICON_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int drawButtonTip(MatrixStack matrices, int x, int y, @NotNull ButtonBinding button, boolean display, @NotNull MinecraftClient client) {
|
||||||
|
return drawButtonTip(matrices, x, y, button.getButton(), button.getTranslationKey(), display, client);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int drawButtonTip(MatrixStack matrices, int x, int y, int[] button, @NotNull String action, boolean display, @NotNull MinecraftClient client) {
|
||||||
|
if (display) {
|
||||||
|
int buttonWidth = drawButton(matrices, x, y, button, client).length();
|
||||||
|
|
||||||
|
var translatedAction = I18n.translate(action);
|
||||||
|
int textY = (LambdaControlsRenderer.ICON_SIZE / 2 - client.textRenderer.fontHeight / 2) + 1;
|
||||||
|
|
||||||
|
return client.textRenderer.drawWithShadow(matrices, translatedAction, (float) (x + buttonWidth + 2), (float) (y + textY), 14737632);
|
||||||
|
}
|
||||||
|
|
||||||
|
return -10;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int getButtonTipWidth(@NotNull String action, @NotNull TextRenderer textRenderer) {
|
||||||
|
return 15 + 5 + textRenderer.getWidth(action);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void renderVirtualCursor(@NotNull MatrixStack matrices, @NotNull MinecraftClient client) {
|
||||||
|
if (!LambdaControlsClient.get().config.hasVirtualMouse() || (client.currentScreen == null
|
||||||
|
|| LambdaInput.isScreenInteractive(client.currentScreen)))
|
||||||
|
return;
|
||||||
|
|
||||||
|
int mouseX = (int) (client.mouse.getX() * (double) client.getWindow().getScaledWidth() / (double) client.getWindow().getWidth());
|
||||||
|
int mouseY = (int) (client.mouse.getY() * (double) client.getWindow().getScaledHeight() / (double) client.getWindow().getHeight());
|
||||||
|
|
||||||
|
boolean hoverSlot = false;
|
||||||
|
|
||||||
|
if (client.currentScreen instanceof HandledScreenAccessor inventoryScreen) {
|
||||||
|
int guiLeft = inventoryScreen.getX();
|
||||||
|
int guiTop = inventoryScreen.getY();
|
||||||
|
|
||||||
|
Slot slot = inventoryScreen.lambdacontrols$getSlotAt(mouseX, mouseY);
|
||||||
|
|
||||||
|
if (slot != null) {
|
||||||
|
mouseX = guiLeft + slot.x;
|
||||||
|
mouseY = guiTop + slot.y;
|
||||||
|
hoverSlot = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hoverSlot) {
|
||||||
|
var slot = LambdaControlsCompat.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, LambdaControlsClient.CURSOR_TEXTURE);
|
||||||
|
DrawableHelper.drawTexture(matrices, x, y,
|
||||||
|
hoverSlot ? 16.f : 0.f, LambdaControlsClient.get().config.getVirtualMouseSkin().ordinal() * 16.f,
|
||||||
|
16, 16, 32, 64);
|
||||||
|
RenderSystem.enableDepthTest();
|
||||||
|
}
|
||||||
|
|
||||||
|
public record ButtonSize(int length, int height) {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,395 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
|
||||||
|
*
|
||||||
|
* This file is part of LambdaControls.
|
||||||
|
*
|
||||||
|
* Licensed under the MIT license. For more information,
|
||||||
|
* see the LICENSE file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package dev.lambdaurora.lambdacontrols.client.gui;
|
||||||
|
|
||||||
|
import dev.lambdaurora.lambdacontrols.LambdaControls;
|
||||||
|
import dev.lambdaurora.lambdacontrols.client.LambdaControlsClient;
|
||||||
|
import dev.lambdaurora.lambdacontrols.client.LambdaControlsConfig;
|
||||||
|
import dev.lambdaurora.lambdacontrols.client.controller.Controller;
|
||||||
|
import dev.lambdaurora.lambdacontrols.client.gui.widget.ControllerControlsWidget;
|
||||||
|
import 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.resource.language.I18n;
|
||||||
|
import net.minecraft.client.util.math.MatrixStack;
|
||||||
|
import net.minecraft.text.LiteralText;
|
||||||
|
import net.minecraft.text.MutableText;
|
||||||
|
import net.minecraft.text.Text;
|
||||||
|
import net.minecraft.text.TranslatableText;
|
||||||
|
import net.minecraft.util.Formatting;
|
||||||
|
import net.minecraft.util.Util;
|
||||||
|
import org.lwjgl.glfw.GLFW;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the LambdaControls settings screen.
|
||||||
|
*/
|
||||||
|
public class LambdaControlsSettingsScreen extends SpruceScreen {
|
||||||
|
private static final Text SDL2_GAMEPAD_TOOL = new LiteralText("SDL2 Gamepad Tool").formatted(Formatting.GREEN);
|
||||||
|
public static final String GAMEPAD_TOOL_URL = "https://generalarcade.com/gamepadtool/";
|
||||||
|
final LambdaControlsClient mod = LambdaControlsClient.get();
|
||||||
|
private final LambdaControlsConfig config = this.mod.config;
|
||||||
|
private final Screen parent;
|
||||||
|
// General options
|
||||||
|
private final SpruceOption inputModeOption;
|
||||||
|
private final SpruceOption autoSwitchModeOption;
|
||||||
|
private final SpruceOption rotationSpeedOption;
|
||||||
|
private final SpruceOption mouseSpeedOption;
|
||||||
|
private final SpruceOption virtualMouseOption;
|
||||||
|
private final SpruceOption resetOption;
|
||||||
|
// Gameplay options
|
||||||
|
private final SpruceOption analogMovementOption;
|
||||||
|
private final SpruceOption autoJumpOption;
|
||||||
|
private final SpruceOption fastBlockPlacingOption;
|
||||||
|
private final SpruceOption frontBlockPlacingOption;
|
||||||
|
private final SpruceOption verticalReacharoundOption;
|
||||||
|
private final SpruceOption flyDriftingOption;
|
||||||
|
private final SpruceOption flyVerticalDriftingOption;
|
||||||
|
// Appearance options
|
||||||
|
private final SpruceOption controllerTypeOption;
|
||||||
|
private final SpruceOption virtualMouseSkinOption;
|
||||||
|
private final SpruceOption hudEnableOption;
|
||||||
|
private final SpruceOption hudSideOption;
|
||||||
|
// Controller options
|
||||||
|
private final SpruceOption controllerOption =
|
||||||
|
new SpruceCyclingOption("lambdacontrols.menu.controller",
|
||||||
|
amount -> {
|
||||||
|
int id = this.config.getController().id();
|
||||||
|
id += amount;
|
||||||
|
if (id > GLFW.GLFW_JOYSTICK_LAST)
|
||||||
|
id = GLFW.GLFW_JOYSTICK_1;
|
||||||
|
id = searchNextAvailableController(id, false);
|
||||||
|
this.config.setController(Controller.byId(id));
|
||||||
|
},
|
||||||
|
option -> {
|
||||||
|
var controller = this.config.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("lambdacontrols.menu.controller2",
|
||||||
|
amount -> {
|
||||||
|
int id = this.config.getSecondController().map(Controller::id).orElse(-1);
|
||||||
|
id += amount;
|
||||||
|
if (id > GLFW.GLFW_JOYSTICK_LAST)
|
||||||
|
id = -1;
|
||||||
|
id = searchNextAvailableController(id, true);
|
||||||
|
this.config.setSecondController(id == -1 ? null : Controller.byId(id));
|
||||||
|
},
|
||||||
|
option -> this.config.getSecondController().map(controller -> {
|
||||||
|
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("lambdacontrols.tooltip.controller2"));
|
||||||
|
private final SpruceOption unfocusedInputOption;
|
||||||
|
private final SpruceOption invertsRightXAxis;
|
||||||
|
private final SpruceOption invertsRightYAxis;
|
||||||
|
private final SpruceOption rightDeadZoneOption;
|
||||||
|
private final SpruceOption leftDeadZoneOption;
|
||||||
|
private final SpruceOption[] maxAnalogValueOptions = new SpruceOption[]{
|
||||||
|
maxAnalogValueOption(this.config, "lambdacontrols.menu.max_left_x_value", GLFW.GLFW_GAMEPAD_AXIS_LEFT_X),
|
||||||
|
maxAnalogValueOption(this.config, "lambdacontrols.menu.max_left_y_value", GLFW.GLFW_GAMEPAD_AXIS_LEFT_Y),
|
||||||
|
maxAnalogValueOption(this.config, "lambdacontrols.menu.max_right_x_value", GLFW.GLFW_GAMEPAD_AXIS_RIGHT_X),
|
||||||
|
maxAnalogValueOption(this.config, "lambdacontrols.menu.max_right_y_value", GLFW.GLFW_GAMEPAD_AXIS_RIGHT_Y)
|
||||||
|
};
|
||||||
|
|
||||||
|
private static SpruceOption maxAnalogValueOption(LambdaControlsConfig config, String key, int axis) {
|
||||||
|
return new SpruceDoubleOption(key, .25f, 1.f, 0.05f,
|
||||||
|
() -> config.getAxisMaxValue(axis),
|
||||||
|
newValue -> config.setAxisMaxValue(axis, newValue),
|
||||||
|
option -> option.getDisplayText(new LiteralText(String.format("%.2f", option.get()))),
|
||||||
|
new TranslatableText(key.replace("menu", "tooltip"))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private final MutableText controllerMappingsUrlText = new LiteralText("(")
|
||||||
|
.append(new LiteralText(GAMEPAD_TOOL_URL).formatted(Formatting.GOLD))
|
||||||
|
.append("),");
|
||||||
|
|
||||||
|
private static int searchNextAvailableController(int newId, boolean allowNone) {
|
||||||
|
if ((allowNone && newId == -1) || newId == 0) return newId;
|
||||||
|
|
||||||
|
boolean connected = Controller.byId(newId).isConnected();
|
||||||
|
if (!connected) {
|
||||||
|
newId++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newId > GLFW.GLFW_JOYSTICK_LAST)
|
||||||
|
newId = allowNone ? -1 : GLFW.GLFW_JOYSTICK_1;
|
||||||
|
|
||||||
|
return connected ? newId : searchNextAvailableController(newId, allowNone);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LambdaControlsSettingsScreen(Screen parent, boolean hideControls) {
|
||||||
|
super(new TranslatableText("lambdacontrols.title.settings"));
|
||||||
|
this.parent = parent;
|
||||||
|
// General options
|
||||||
|
this.inputModeOption = new SpruceCyclingOption("lambdacontrols.menu.controls_mode",
|
||||||
|
amount -> {
|
||||||
|
var next = this.config.getControlsMode().next();
|
||||||
|
this.config.setControlsMode(next);
|
||||||
|
this.config.save();
|
||||||
|
|
||||||
|
if (this.client.player != null) {
|
||||||
|
ClientPlayNetworking.getSender().sendPacket(LambdaControls.CONTROLS_MODE_CHANNEL, this.mod.makeControlsModeBuffer(next));
|
||||||
|
}
|
||||||
|
}, option -> option.getDisplayText(new TranslatableText(this.config.getControlsMode().getTranslationKey())),
|
||||||
|
new TranslatableText("lambdacontrols.tooltip.controls_mode"));
|
||||||
|
this.autoSwitchModeOption = new SpruceToggleBooleanOption("lambdacontrols.menu.auto_switch_mode", this.config::hasAutoSwitchMode,
|
||||||
|
this.config::setAutoSwitchMode, new TranslatableText("lambdacontrols.tooltip.auto_switch_mode"));
|
||||||
|
this.rotationSpeedOption = new SpruceDoubleOption("lambdacontrols.menu.rotation_speed", 0.0, 100.0, .5f,
|
||||||
|
this.config::getRotationSpeed,
|
||||||
|
newValue -> {
|
||||||
|
synchronized (this.config) {
|
||||||
|
this.config.setRotationSpeed(newValue);
|
||||||
|
}
|
||||||
|
}, option -> option.getDisplayText(new LiteralText(String.valueOf(option.get()))),
|
||||||
|
new TranslatableText("lambdacontrols.tooltip.rotation_speed"));
|
||||||
|
this.mouseSpeedOption = new SpruceDoubleOption("lambdacontrols.menu.mouse_speed", 0.0, 150.0, .5f,
|
||||||
|
this.config::getMouseSpeed,
|
||||||
|
newValue -> {
|
||||||
|
synchronized (this.config) {
|
||||||
|
this.config.setMouseSpeed(newValue);
|
||||||
|
}
|
||||||
|
}, option -> option.getDisplayText(new LiteralText(String.valueOf(option.get()))),
|
||||||
|
new TranslatableText("lambdacontrols.tooltip.mouse_speed"));
|
||||||
|
this.resetOption = SpruceSimpleActionOption.reset(btn -> {
|
||||||
|
this.config.reset();
|
||||||
|
var client = MinecraftClient.getInstance();
|
||||||
|
this.init(client, client.getWindow().getScaledWidth(), client.getWindow().getScaledHeight());
|
||||||
|
});
|
||||||
|
// Gameplay options
|
||||||
|
this.analogMovementOption = new SpruceToggleBooleanOption("lambdacontrols.menu.analog_movement",
|
||||||
|
this.config::hasAnalogMovement, this.config::setAnalogMovement,
|
||||||
|
new TranslatableText("lambdacontrols.tooltip.analog_movement"));
|
||||||
|
this.autoJumpOption = new SpruceToggleBooleanOption("options.autoJump",
|
||||||
|
() -> this.client.options.autoJump,
|
||||||
|
newValue -> this.client.options.autoJump = newValue,
|
||||||
|
null);
|
||||||
|
this.fastBlockPlacingOption = new SpruceToggleBooleanOption("lambdacontrols.menu.fast_block_placing", this.config::hasFastBlockPlacing,
|
||||||
|
this.config::setFastBlockPlacing, new TranslatableText("lambdacontrols.tooltip.fast_block_placing"));
|
||||||
|
this.frontBlockPlacingOption = new SpruceToggleBooleanOption("lambdacontrols.menu.reacharound.horizontal", this.config::hasFrontBlockPlacing,
|
||||||
|
this.config::setFrontBlockPlacing, new TranslatableText("lambdacontrols.tooltip.reacharound.horizontal"));
|
||||||
|
this.verticalReacharoundOption = new SpruceToggleBooleanOption("lambdacontrols.menu.reacharound.vertical", this.config::hasVerticalReacharound,
|
||||||
|
this.config::setVerticalReacharound, new TranslatableText("lambdacontrols.tooltip.reacharound.vertical"));
|
||||||
|
this.flyDriftingOption = new SpruceToggleBooleanOption("lambdacontrols.menu.fly_drifting", this.config::hasFlyDrifting,
|
||||||
|
this.config::setFlyDrifting, new TranslatableText("lambdacontrols.tooltip.fly_drifting"));
|
||||||
|
this.flyVerticalDriftingOption = new SpruceToggleBooleanOption("lambdacontrols.menu.fly_drifting_vertical", this.config::hasFlyVerticalDrifting,
|
||||||
|
this.config::setFlyVerticalDrifting, new TranslatableText("lambdacontrols.tooltip.fly_drifting_vertical"));
|
||||||
|
// Appearance options
|
||||||
|
this.controllerTypeOption = new SpruceCyclingOption("lambdacontrols.menu.controller_type",
|
||||||
|
amount -> this.config.setControllerType(this.config.getControllerType().next()),
|
||||||
|
option -> option.getDisplayText(this.config.getControllerType().getTranslatedText()),
|
||||||
|
new TranslatableText("lambdacontrols.tooltip.controller_type"));
|
||||||
|
this.virtualMouseSkinOption = new SpruceCyclingOption("lambdacontrols.menu.virtual_mouse.skin",
|
||||||
|
amount -> this.config.setVirtualMouseSkin(this.config.getVirtualMouseSkin().next()),
|
||||||
|
option -> option.getDisplayText(this.config.getVirtualMouseSkin().getTranslatedText()),
|
||||||
|
null);
|
||||||
|
this.hudEnableOption = new SpruceToggleBooleanOption("lambdacontrols.menu.hud_enable", this.config::isHudEnabled,
|
||||||
|
this.mod::setHudEnabled, new TranslatableText("lambdacontrols.tooltip.hud_enable"));
|
||||||
|
this.hudSideOption = new SpruceCyclingOption("lambdacontrols.menu.hud_side",
|
||||||
|
amount -> this.config.setHudSide(this.config.getHudSide().next()),
|
||||||
|
option -> option.getDisplayText(this.config.getHudSide().getTranslatedText()),
|
||||||
|
new TranslatableText("lambdacontrols.tooltip.hud_side"));
|
||||||
|
// Controller options
|
||||||
|
this.rightDeadZoneOption = new SpruceDoubleOption("lambdacontrols.menu.right_dead_zone", 0.05, 1.0, .05f,
|
||||||
|
this.config::getRightDeadZone,
|
||||||
|
newValue -> {
|
||||||
|
synchronized (this.config) {
|
||||||
|
this.config.setRightDeadZone(newValue);
|
||||||
|
}
|
||||||
|
}, option -> {
|
||||||
|
var value = String.valueOf(option.get());
|
||||||
|
return option.getDisplayText(new LiteralText(value.substring(0, Math.min(value.length(), 5))));
|
||||||
|
}, new TranslatableText("lambdacontrols.tooltip.right_dead_zone"));
|
||||||
|
this.leftDeadZoneOption = new SpruceDoubleOption("lambdacontrols.menu.left_dead_zone", 0.05, 1.0, .05f,
|
||||||
|
this.config::getLeftDeadZone,
|
||||||
|
newValue -> {
|
||||||
|
synchronized (this.config) {
|
||||||
|
this.config.setLeftDeadZone(newValue);
|
||||||
|
}
|
||||||
|
}, option -> {
|
||||||
|
var value = String.valueOf(option.get());
|
||||||
|
return option.getDisplayText(new LiteralText(value.substring(0, Math.min(value.length(), 5))));
|
||||||
|
}, new TranslatableText("lambdacontrols.tooltip.left_dead_zone"));
|
||||||
|
this.invertsRightXAxis = new SpruceToggleBooleanOption("lambdacontrols.menu.invert_right_x_axis", this.config::doesInvertRightXAxis,
|
||||||
|
newValue -> {
|
||||||
|
synchronized (this.config) {
|
||||||
|
this.config.setInvertRightXAxis(newValue);
|
||||||
|
}
|
||||||
|
}, null);
|
||||||
|
this.invertsRightYAxis = new SpruceToggleBooleanOption("lambdacontrols.menu.invert_right_y_axis", this.config::doesInvertRightYAxis,
|
||||||
|
newValue -> {
|
||||||
|
synchronized (this.config) {
|
||||||
|
this.config.setInvertRightYAxis(newValue);
|
||||||
|
}
|
||||||
|
}, null);
|
||||||
|
this.unfocusedInputOption = new SpruceToggleBooleanOption("lambdacontrols.menu.unfocused_input", this.config::hasUnfocusedInput,
|
||||||
|
this.config::setUnfocusedInput, new TranslatableText("lambdacontrols.tooltip.unfocused_input"));
|
||||||
|
this.virtualMouseOption = new SpruceToggleBooleanOption("lambdacontrols.menu.virtual_mouse", this.config::hasVirtualMouse,
|
||||||
|
this.config::setVirtualMouse, new TranslatableText("lambdacontrols.tooltip.virtual_mouse"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removed() {
|
||||||
|
this.config.save();
|
||||||
|
super.removed();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onClose() {
|
||||||
|
this.config.save();
|
||||||
|
super.onClose();
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getTextHeight() {
|
||||||
|
return (5 + this.textRenderer.fontHeight) * 3 + 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void init() {
|
||||||
|
super.init();
|
||||||
|
|
||||||
|
this.buildTabs();
|
||||||
|
|
||||||
|
this.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.openScreen(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);
|
||||||
|
this.addDrawableChild(tabs);
|
||||||
|
|
||||||
|
tabs.addSeparatorEntry(new TranslatableText("lambdacontrols.menu.separator.general"));
|
||||||
|
tabs.addTabEntry(new TranslatableText("lambdacontrols.menu.title.general"), null,
|
||||||
|
this::buildGeneralTab);
|
||||||
|
tabs.addTabEntry(new TranslatableText("lambdacontrols.menu.title.gameplay"), null,
|
||||||
|
this::buildGameplayTab);
|
||||||
|
tabs.addTabEntry(new TranslatableText("lambdacontrols.menu.title.visual"), null,
|
||||||
|
this::buildVisualTab);
|
||||||
|
|
||||||
|
tabs.addSeparatorEntry(new TranslatableText("options.controls"));
|
||||||
|
tabs.addTabEntry(new TranslatableText("lambdacontrols.menu.title.controller_controls"), null,
|
||||||
|
this::buildControllerControlsTab);
|
||||||
|
|
||||||
|
tabs.addSeparatorEntry(new TranslatableText("lambdacontrols.menu.separator.controller"));
|
||||||
|
tabs.addTabEntry(new TranslatableText("lambdacontrols.menu.title.controller"), null,
|
||||||
|
this::buildControllerTab);
|
||||||
|
tabs.addTabEntry(new TranslatableText("lambdacontrols.menu.title.mappings.string"), null,
|
||||||
|
this::buildMappingsStringEditorTab);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SpruceOptionListWidget buildGeneralTab(int width, int height) {
|
||||||
|
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("lambdacontrols.menu.title.hud", true, null));
|
||||||
|
list.addSingleOptionEntry(this.hudEnableOption);
|
||||||
|
list.addSingleOptionEntry(this.hudSideOption);
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ControllerControlsWidget buildControllerControlsTab(int width, int height) {
|
||||||
|
return new ControllerControlsWidget(Position.origin(), width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AbstractSpruceWidget buildControllerTab(int width, int height) {
|
||||||
|
var root = new SpruceContainerWidget(Position.origin(), width, height);
|
||||||
|
|
||||||
|
var aboutMappings1 = new SpruceLabelWidget(Position.of(0, 2),
|
||||||
|
new TranslatableText("lambdacontrols.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("lambdacontrols.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("lambdacontrols.menu.title"), this.width / 2, 8, 16777215);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
|
||||||
|
*
|
||||||
|
* This file is part of LambdaControls.
|
||||||
|
*
|
||||||
|
* Licensed under the MIT license. For more information,
|
||||||
|
* see the LICENSE file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package dev.lambdaurora.lambdacontrols.client.gui;
|
||||||
|
|
||||||
|
import dev.lambdaurora.lambdacontrols.client.LambdaControlsClient;
|
||||||
|
import dev.lambdaurora.lambdacontrols.client.controller.Controller;
|
||||||
|
import 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("lambdacontrols.menu.title.mappings.string"));
|
||||||
|
|
||||||
|
this.reloadMappingsOption = ReloadControllerMappingsOption.newOption(btn -> {
|
||||||
|
this.writeMappings();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removed() {
|
||||||
|
this.writeMappings();
|
||||||
|
Controller.updateMappings();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onClose() {
|
||||||
|
this.removed();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void writeMappings() {
|
||||||
|
if (this.textArea != null) {
|
||||||
|
this.mappings = this.textArea.getText();
|
||||||
|
try {
|
||||||
|
var fw = new FileWriter(LambdaControlsClient.MAPPINGS_FILE, false);
|
||||||
|
fw.write(this.mappings);
|
||||||
|
fw.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
if (this.client != null)
|
||||||
|
this.client.getToastManager().add(SystemToast.create(this.client, SystemToast.Type.TUTORIAL_HINT,
|
||||||
|
new TranslatableText("lambdacontrols.controller.mappings.error.write"), LiteralText.EMPTY));
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void init() {
|
||||||
|
if (this.textArea != null) {
|
||||||
|
this.mappings = this.textArea.getText();
|
||||||
|
}
|
||||||
|
|
||||||
|
var mappings = "";
|
||||||
|
|
||||||
|
if (this.mappings != null)
|
||||||
|
mappings = this.mappings;
|
||||||
|
else if (LambdaControlsClient.MAPPINGS_FILE.exists()) {
|
||||||
|
try {
|
||||||
|
mappings = String.join("\n", Files.readAllLines(LambdaControlsClient.MAPPINGS_FILE.toPath()));
|
||||||
|
this.mappings = mappings;
|
||||||
|
} catch (IOException e) {
|
||||||
|
/* Ignored */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int textFieldWidth = (int) (this.width * (5.0 / 6.0));
|
||||||
|
this.textArea = new SpruceTextAreaWidget(Position.of(this, this.width / 2 - textFieldWidth / 2, 0), textFieldWidth, this.height - 50, new LiteralText(mappings));
|
||||||
|
this.textArea.setText(mappings);
|
||||||
|
// Display as many lines as possible
|
||||||
|
this.textArea.setDisplayedLines(this.textArea.getInnerHeight() / this.client.textRenderer.fontHeight);
|
||||||
|
this.addChild(this.textArea);
|
||||||
|
|
||||||
|
this.addChild(this.reloadMappingsOption.createWidget(Position.of(this.width / 2 - 155, this.height - 29), 310));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*public void renderTitle(MatrixStack matrices, int mouseX, int mouseY, float delta) {
|
||||||
|
drawCenteredText(matrices, this.textRenderer, this.title, this.width / 2, 8, 16777215);
|
||||||
|
}*/
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
|
||||||
|
*
|
||||||
|
* This file is part of LambdaControls.
|
||||||
|
*
|
||||||
|
* Licensed under the MIT license. For more information,
|
||||||
|
* see the LICENSE file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package dev.lambdaurora.lambdacontrols.client.gui;
|
||||||
|
|
||||||
|
import dev.lambdaurora.lambdacontrols.client.controller.Controller;
|
||||||
|
import 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 = "lambdacontrols.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 LambdaControlsSettingsScreen)
|
||||||
|
client.currentScreen.init(client, client.getWindow().getScaledWidth(), client.getWindow().getScaledHeight());
|
||||||
|
client.getToastManager().add(SystemToast.create(client, SystemToast.Type.TUTORIAL_HINT,
|
||||||
|
new TranslatableText("lambdacontrols.controller.mappings.updated"), LiteralText.EMPTY));
|
||||||
|
}, new TranslatableText("lambdacontrols.tooltip.reload_controller_mappings"));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2021 LambdAurora <aurora42lambda@gmail.com>
|
||||||
|
*
|
||||||
|
* This file is part of LambdaControls.
|
||||||
|
*
|
||||||
|
* Licensed under the MIT license. For more information,
|
||||||
|
* see the LICENSE file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package dev.lambdaurora.lambdacontrols.client.gui;
|
||||||
|
|
||||||
|
import dev.lambdaurora.lambdacontrols.client.LambdaControlsClient;
|
||||||
|
import dev.lambdaurora.lambdacontrols.client.ring.RingPage;
|
||||||
|
import net.minecraft.client.gui.screen.Screen;
|
||||||
|
import net.minecraft.client.util.math.MatrixStack;
|
||||||
|
import net.minecraft.text.TranslatableText;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the controls ring screen.
|
||||||
|
*
|
||||||
|
* @author LambdAurora
|
||||||
|
* @version 1.4.3
|
||||||
|
* @since 1.4.3
|
||||||
|
*/
|
||||||
|
public class RingScreen extends Screen {
|
||||||
|
protected final LambdaControlsClient mod;
|
||||||
|
|
||||||
|
public RingScreen() {
|
||||||
|
super(new TranslatableText("lambdacontrols.menu.title.ring"));
|
||||||
|
this.mod = LambdaControlsClient.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isPauseScreen() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void render(MatrixStack matrices, int mouseX, int mouseY, float delta) {
|
||||||
|
super.render(matrices, mouseX, mouseY, delta);
|
||||||
|
|
||||||
|
RingPage page = this.mod.ring.getCurrentPage();
|
||||||
|
|
||||||
|
page.render(matrices, this.textRenderer, this.width, this.height, mouseX, mouseY, delta);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean mouseReleased(double mouseX, double mouseY, int button) {
|
||||||
|
/*if (LambdaControlsClient.BINDING_RING.matchesMouse(button)) {
|
||||||
|
this.onClose();
|
||||||
|
return true;
|
||||||
|
}*/
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user