Compare commits
220 Commits
builder-2-
...
v1.2.16
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6a175379fb | ||
|
|
85c21fa8e3 | ||
|
|
bc8c823045 | ||
|
|
9935da8a6f | ||
|
|
c39b6c3fe7 | ||
|
|
c47a6f92c5 | ||
|
|
778628da30 | ||
|
|
ba0ca0cc65 | ||
|
|
fba3828479 | ||
|
|
c8dd4f26f7 | ||
|
|
16a62625b3 | ||
|
|
de5f6d5fce | ||
|
|
98a87d099b | ||
|
|
a6f3c95684 | ||
|
|
75e39e7ef5 | ||
|
|
77b2b26a7a | ||
|
|
1680eeb80d | ||
|
|
9a9b4a1874 | ||
|
|
145a944860 | ||
|
|
dd127ff3e7 | ||
|
|
b0fb474e1d | ||
|
|
a1a94ec0d1 | ||
|
|
678f8bc77f | ||
|
|
41968546d1 | ||
|
|
f76283ebfe | ||
|
|
e59bf9ab97 | ||
|
|
b2f66241d5 | ||
|
|
cfdbc851a0 | ||
|
|
6ccebdc978 | ||
|
|
df53040339 | ||
|
|
42dc5b14b0 | ||
|
|
e2538abcb1 | ||
|
|
1697293f12 | ||
|
|
d60e1e8a05 | ||
|
|
d157756d94 | ||
|
|
ea1914a248 | ||
|
|
85087ce04a | ||
|
|
e09127eadf | ||
|
|
e1095962a1 | ||
|
|
eabd1150b0 | ||
|
|
93501248cd | ||
|
|
3a945c8c10 | ||
|
|
3cef7a7911 | ||
|
|
1cd2fb5b18 | ||
|
|
ba3ca47f8c | ||
|
|
4699b46744 | ||
|
|
69ffdb7665 | ||
|
|
f9c5386e7a | ||
|
|
55273b5340 | ||
|
|
5cd1c9d15d | ||
|
|
b461b2af2f | ||
|
|
3a5608566e | ||
|
|
fc65f22feb | ||
|
|
f53bfa89a9 | ||
|
|
fdfeeb2ffa | ||
|
|
3e75cc7408 | ||
|
|
d1930e03e1 | ||
|
|
144a534bb3 | ||
|
|
de1256cc80 | ||
|
|
76404c8af6 | ||
|
|
fb814e912d | ||
|
|
43ee86b4fe | ||
|
|
eb697b7a17 | ||
|
|
3da5bbd267 | ||
|
|
c14be17e53 | ||
|
|
45c0b38156 | ||
|
|
025f6235f9 | ||
|
|
4ba3c883e6 | ||
|
|
3a4168a661 | ||
|
|
484b3326c7 | ||
|
|
e99fcbbe36 | ||
|
|
7ba3de57d0 | ||
|
|
5ff274f040 | ||
|
|
293f5db172 | ||
|
|
d928e705b9 | ||
|
|
7a48824ced | ||
|
|
8aa8918124 | ||
|
|
a091c17b83 | ||
|
|
52aa0d9b8a | ||
|
|
3d8eddc4e1 | ||
|
|
295265c261 | ||
|
|
376d6422ec | ||
|
|
113e340474 | ||
|
|
c89d8b69e5 | ||
|
|
70303634f5 | ||
|
|
325aa7201b | ||
|
|
75a3fc699e | ||
|
|
3cd8ce8323 | ||
|
|
240e81a9e0 | ||
|
|
40449400d3 | ||
|
|
9ffe4f2c25 | ||
|
|
af95f77134 | ||
|
|
78f1c45e13 | ||
|
|
cf47573298 | ||
|
|
96ba96ca69 | ||
|
|
da998eb469 | ||
|
|
e75a4b95cc | ||
|
|
344085f4ef | ||
|
|
d37a6a0b2d | ||
|
|
d7fc916d20 | ||
|
|
80a4757242 | ||
|
|
658048ff2d | ||
|
|
0587223da8 | ||
|
|
2d1b81dc20 | ||
|
|
0bd16fb81a | ||
|
|
0c1fec5d1e | ||
|
|
ee16eb7fde | ||
|
|
5c9aeab6b4 | ||
|
|
5c7cae9ab0 | ||
|
|
93fa6cf875 | ||
|
|
5b7bee977b | ||
|
|
8018dac396 | ||
|
|
85ab317c6c | ||
|
|
6b0fb1721b | ||
|
|
fdcdcbb85f | ||
|
|
4e2095d251 | ||
|
|
51275b3a65 | ||
|
|
8d480cefb9 | ||
|
|
3e7f9039c4 | ||
|
|
49828baae3 | ||
|
|
a73e1185dc | ||
|
|
441dceb731 | ||
|
|
57c4f9e103 | ||
|
|
2741fc2683 | ||
|
|
868c023dbd | ||
|
|
e7c357ab7f | ||
|
|
9e1a5008ed | ||
|
|
a6557121a0 | ||
|
|
2dad6262cf | ||
|
|
af1eb58bb8 | ||
|
|
17a2aa42e9 | ||
|
|
231f671400 | ||
|
|
221eba11dd | ||
|
|
ad40a9225e | ||
|
|
cbef05838d | ||
|
|
2cf973809a | ||
|
|
65974e15ba | ||
|
|
dc6b32a154 | ||
|
|
76f593e4bb | ||
|
|
15b991a48e | ||
|
|
702d0790bc | ||
|
|
2953e2c522 | ||
|
|
bf450b7d68 | ||
|
|
4e6b6d97ce | ||
|
|
6aadd00e72 | ||
|
|
e036f5360c | ||
|
|
8c2aae2ddc | ||
|
|
511941c714 | ||
|
|
e58220e2c4 | ||
|
|
02711a73ed | ||
|
|
25d418e96b | ||
|
|
10430f8142 | ||
|
|
8aba97b577 | ||
|
|
8fe5b2369a | ||
|
|
dc6c87b58d | ||
|
|
ef4e19002b | ||
|
|
e6ee5fc6b8 | ||
|
|
b19c935da1 | ||
|
|
0ade37f14f | ||
|
|
91be4c4643 | ||
|
|
d2b62e2d21 | ||
|
|
7052fe6225 | ||
|
|
49357790f1 | ||
|
|
694219e497 | ||
|
|
9b70ace180 | ||
|
|
e0a53144db | ||
|
|
25de332492 | ||
|
|
2d0f10c79f | ||
|
|
74ea803651 | ||
|
|
0908e509a1 | ||
|
|
ad14226c5c | ||
|
|
3fcc032547 | ||
|
|
b52de9b197 | ||
|
|
c2de6e3f3d | ||
|
|
a5638bdba1 | ||
|
|
bb8e4bd737 | ||
|
|
7a96772a9b | ||
|
|
9b07aa17e4 | ||
|
|
90c59d5b8e | ||
|
|
fd3d5b66e9 | ||
|
|
d97346be6c | ||
|
|
9d13bcfe11 | ||
|
|
724ee62246 | ||
|
|
26e8f02e85 | ||
|
|
b3ff3d2466 | ||
|
|
9c3882e91f | ||
|
|
32a7657c4f | ||
|
|
54617bf634 | ||
|
|
61a00b3965 | ||
|
|
0214ffd158 | ||
|
|
1919f7b2a7 | ||
|
|
c2f4e00314 | ||
|
|
3adfa16db8 | ||
|
|
90fcffdf2a | ||
|
|
0ca84a790b | ||
|
|
04802fbb15 | ||
|
|
21534d1c0b | ||
|
|
8579332cdf | ||
|
|
9dc9edbbee | ||
|
|
2980bb0320 | ||
|
|
084798bd1b | ||
|
|
1c27b9b712 | ||
|
|
129ec8472c | ||
|
|
724162f37f | ||
|
|
3244b102ad | ||
|
|
c8966d22ba | ||
|
|
6669d49636 | ||
|
|
3b480dabdb | ||
|
|
3e69281d61 | ||
|
|
1b04386b01 | ||
|
|
1ca736692d | ||
|
|
4dc4795cb7 | ||
|
|
bcc33362b8 | ||
|
|
ba0b5f929f | ||
|
|
89c960c455 | ||
|
|
14178fcd14 | ||
|
|
5e0fe6d897 | ||
|
|
53ef26dabd | ||
|
|
3db5f2a67f | ||
|
|
9d1b979318 |
10
.github/workflows/gradle_build.yml
vendored
10
.github/workflows/gradle_build.yml
vendored
@@ -13,12 +13,12 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- name: Set up JDK 8
|
||||
uses: actions/setup-java@v2
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
java-version: '8'
|
||||
distribution: 'adopt'
|
||||
distribution: 'temurin'
|
||||
|
||||
- name: Grant execute permission for gradlew
|
||||
run: chmod +x gradlew
|
||||
@@ -27,13 +27,13 @@ jobs:
|
||||
run: ./gradlew build
|
||||
|
||||
- name: Archive Artifacts
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: Artifacts
|
||||
path: dist/
|
||||
|
||||
- name: Archive mapping.txt
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: Mappings
|
||||
path: build/tmp/proguard/mapping.txt
|
||||
|
||||
8
.github/workflows/run_tests.yml
vendored
8
.github/workflows/run_tests.yml
vendored
@@ -11,12 +11,12 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up JDK 9
|
||||
uses: actions/setup-java@v2
|
||||
- uses: actions/checkout@v3
|
||||
- name: Set up JDK 8
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
java-version: '8'
|
||||
distribution: 'adopt'
|
||||
distribution: 'temurin'
|
||||
|
||||
- name: Grant execute permission for gradlew
|
||||
run: chmod +x gradlew
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -27,3 +27,6 @@ baritone_Client.launch
|
||||
# Copyright Files
|
||||
!/.idea/copyright/Baritone.xml
|
||||
!/.idea/copyright/profiles_settings.xml
|
||||
|
||||
.vscode/launch.json
|
||||
|
||||
|
||||
28
README.md
28
README.md
@@ -4,18 +4,20 @@
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<img src="https://img.shields.io/badge/MC-1.12.2-brightgreen.svg" alt="Minecraft"/>
|
||||
<img src="https://img.shields.io/badge/MC-1.13.2-brightgreen.svg" alt="Minecraft"/>
|
||||
<img src="https://img.shields.io/badge/MC-1.14.4-brightgreen.svg" alt="Minecraft"/>
|
||||
<img src="https://img.shields.io/badge/MC-1.15.2-brightgreen.svg" alt="Minecraft"/>
|
||||
<img src="https://img.shields.io/badge/MC-1.16.5-brightgreen.svg" alt="Minecraft"/>
|
||||
<a href="https://github.com/cabaletta/baritone/tree/master"><img src="https://img.shields.io/badge/MC-1.12.2-brightgreen.svg" alt="Minecraft"/></a>
|
||||
<a href="https://github.com/cabaletta/baritone/tree/1.13.2"><img src="https://img.shields.io/badge/MC-1.13.2-yellow.svg" alt="Minecraft"/></a>
|
||||
<a href="https://github.com/cabaletta/baritone/tree/1.14.4"><img src="https://img.shields.io/badge/MC-1.14.4-yellow.svg" alt="Minecraft"/></a>
|
||||
<a href="https://github.com/cabaletta/baritone/tree/1.15.2"><img src="https://img.shields.io/badge/MC-1.15.2-yellow.svg" alt="Minecraft"/></a>
|
||||
<a href="https://github.com/cabaletta/baritone/tree/1.16.5"><img src="https://img.shields.io/badge/MC-1.16.5-brightgreen.svg" alt="Minecraft"/></a>
|
||||
<a href="https://github.com/cabaletta/baritone/tree/1.17.1"><img src="https://img.shields.io/badge/MC-1.17.1-brightgreen.svg" alt="Minecraft"/></a>
|
||||
<a href="https://github.com/cabaletta/baritone/tree/1.18.2"><img src="https://img.shields.io/badge/MC-1.18.2-brightgreen.svg" alt="Minecraft"/></a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://travis-ci.com/cabaletta/baritone/"><img src="https://travis-ci.com/cabaletta/baritone.svg?branch=master" alt="Build Status"/></a>
|
||||
<a href="https://github.com/cabaletta/baritone/releases/"><img src="https://img.shields.io/github/release/cabaletta/baritone.svg" alt="Release"/></a>
|
||||
<a href="LICENSE"><img src="https://img.shields.io/badge/license-LGPL--3.0%20with%20anime%20exception-green.svg" alt="License"/></a>
|
||||
<a href="https://www.codacy.com/app/leijurv/baritone?utm_source=github.com&utm_medium=referral&utm_content=cabaletta/baritone&utm_campaign=Badge_Grade"><img src="https://api.codacy.com/project/badge/Grade/a73d037823b64a5faf597a18d71e3400" alt="Codacy Badge"/></a>
|
||||
<a href="https://www.codacy.com/gh/cabaletta/baritone/dashboard?utm_source=github.com&utm_medium=referral&utm_content=cabaletta/baritone&utm_campaign=Badge_Grade"><img src="https://app.codacy.com/project/badge/Grade/cadab857dab049438b6e28b3cfc5570e" alt="Codacy Badge"/></a>
|
||||
<a href="https://github.com/cabaletta/baritone/blob/master/CODE_OF_CONDUCT.md"><img src="https://img.shields.io/badge/%E2%9D%A4-code%20of%20conduct-blue.svg?style=flat" alt="Code of Conduct"/></a>
|
||||
<a href="https://snyk.io/test/github/cabaletta/baritone?targetFile=build.gradle"><img src="https://snyk.io/test/github/cabaletta/baritone/badge.svg?targetFile=build.gradle" alt="Known Vulnerabilities"/></a>
|
||||
<a href="https://github.com/cabaletta/baritone/issues/"><img src="https://img.shields.io/badge/contributions-welcome-brightgreen.svg?style=flat" alt="Contributions welcome"/></a>
|
||||
@@ -48,16 +50,20 @@ A Minecraft pathfinder bot.
|
||||
|
||||
[**Baritone Discord Server**](http://discord.gg/s6fRBAUpmr)
|
||||
|
||||
Baritone is the pathfinding system used in [Impact](https://impactclient.net/) since 4.4. There's a [showcase video](https://youtu.be/CZkLXWo4Fg4) made by @Adovin#0730 on Baritone which I recommend. [Here's](https://www.youtube.com/watch?v=StquF69-_wI) a (very old!) video I made showing off what it can do. [Tutorial playlist](https://www.youtube.com/playlist?list=PLnwnJ1qsS7CoQl9Si-RTluuzCo_4Oulpa)
|
||||
Baritone is the pathfinding system used in [Impact](https://impactclient.net/) since 4.4. There's a [showcase video](https://youtu.be/CZkLXWo4Fg4) made by @Adovin#0730 on Baritone which I recommend. [Here's](https://www.youtube.com/watch?v=StquF69-_wI) a (very old!) video I made showing off what it can do.
|
||||
|
||||
[Tutorial playlist](https://www.youtube.com/playlist?list=PLnwnJ1qsS7CoQl9Si-RTluuzCo_4Oulpa)
|
||||
|
||||
The easiest way to install Baritone is to install [Impact](https://impactclient.net/), which comes with Baritone. The second easiest way (for 1.12.2 only) is to install the v1.2.* `api-forge` jar from [releases](https://github.com/cabaletta/baritone/releases). **For 1.12.2 Forge, just click [here](https://github.com/cabaletta/baritone/releases/download/v1.2.15/baritone-api-forge-1.2.15.jar)**. Otherwise, see [Installation & setup](SETUP.md). Once Baritone is installed, look [here](USAGE.md) for instructions on how to use it.
|
||||
|
||||
For 1.15.2, [click here](https://www.youtube.com/watch?v=j1qKtCZFURM) and see description. If you need Forge 1.15.2, look [here](https://github.com/cabaletta/baritone/releases/tag/v1.5.3), follow the instructions, and get the `api-forge` jar.
|
||||
|
||||
For 1.16.5, [click here](https://www.youtube.com/watch?v=_4eVJ9Qz2J8) and see description. If you need Forge or Fabric 1.16.5, look [here](https://github.com/cabaletta/baritone/releases/tag/v1.6.3) and get the `api-forge` or `api-fabric` jar. **For 1.16.5 Fabric, just click [here](https://github.com/cabaletta/baritone/releases/download/v1.6.3/baritone-api-fabric-1.6.3.jar)**.
|
||||
|
||||
If you need Forge or Fabric 1.17.1, look [here](https://github.com/cabaletta/baritone/releases/tag/v1.7.2) and get the `api-forge` or `api-fabric` jar. **For 1.17.1 Fabric, just click [here](https://github.com/cabaletta/baritone/releases/download/v1.7.2/baritone-api-fabric-1.7.2.jar)**.
|
||||
|
||||
If you need Forge or Fabric 1.18.2, look [here](https://github.com/cabaletta/baritone/releases/tag/v1.8.3) and get the `api-forge` or `api-fabric` jar. **For 1.18.2 Fabric, just click [here](https://github.com/cabaletta/baritone/releases/download/v1.8.3/baritone-api-fabric-1.8.3.jar)**. **For 1.18.2 Forge, just click [here](https://github.com/cabaletta/baritone/releases/download/v1.8.3/baritone-api-forge-1.8.3.jar)**.
|
||||
|
||||
This project is an updated version of [MineBot](https://github.com/leijurv/MineBot/),
|
||||
the original version of the bot for Minecraft 1.8.9, rebuilt for 1.12.2 through 1.16.5. Baritone focuses on reliability and particularly performance (it's over [30x faster](https://github.com/cabaletta/baritone/pull/180#issuecomment-423822928) than MineBot at calculating paths).
|
||||
the original version of the bot for Minecraft 1.8.9, rebuilt for 1.12.2 onwards. Baritone focuses on reliability and particularly performance (it's over [30x faster](https://github.com/cabaletta/baritone/pull/180#issuecomment-423822928) than MineBot at calculating paths).
|
||||
|
||||
Have committed at least once a day from Aug 1, 2018, to Aug 1, 2019.
|
||||
|
||||
@@ -89,7 +95,7 @@ jar.
|
||||
|
||||
Below is an example of basic usage for changing some settings, and then pathing to an X/Z goal.
|
||||
|
||||
```
|
||||
```java
|
||||
BaritoneAPI.getSettings().allowSprint.value = true;
|
||||
BaritoneAPI.getSettings().primaryTimeoutMS.value = 2000L;
|
||||
|
||||
|
||||
21
SETUP.md
21
SETUP.md
@@ -11,7 +11,7 @@ These releases are not always completely up to date with latest features, and ar
|
||||
|
||||
Link to the releases page: [Releases](https://github.com/cabaletta/baritone/releases)
|
||||
|
||||
v1.2.* is for 1.12.2, v1.3.* is for 1.13.2, v1.4.* is for 1.14.4, v1.5.* is for 1.15.2, v1.6.* is for 1.16.2 or 1.16.4 or 1.16.5 (LOL)
|
||||
v1.2.* is for 1.12.2, v1.3.* is for 1.13.2, v1.4.* is for 1.14.4, v1.5.* is for 1.15.2, v1.6.* is for 1.16.5, v1.7.* is for 1.17.1, v1.8.* is for 1.18.1
|
||||
|
||||
Any official release will be GPG signed by leijurv (44A3EA646EADAC6A). Please verify that the hash of the file you download is in `checksums.txt` and that `checksums_signed.asc` is a valid signature by that public keys of `checksums.txt`.
|
||||
|
||||
@@ -22,15 +22,16 @@ The build is fully deterministic and reproducible, and you can verify Travis did
|
||||
|
||||
Building Baritone will result in 5 artifacts created in the ``dist`` directory. These are the same as the artifacts created in the [releases](https://github.com/cabaletta/baritone/releases).
|
||||
|
||||
**The Forge release can simply be added as a Forge mod.**
|
||||
**The Forge and Fabric releases can simply be added as a Forge/Fabric mods.**
|
||||
|
||||
If another one of your Forge mods has a Baritone integration, you want `baritone-api-forge-VERSION.jar`. Otherwise, you want `baritone-standalone-forge-VERSION.jar`
|
||||
|
||||
- **API**: Only the non-api packages are obfuscated. This should be used in environments where other mods would like to use Baritone's features.
|
||||
- **Forge API**: Same as API, but packaged for Forge. This should be used where another mod has a Baritone integration.
|
||||
- **Forge/Fabric API**: Same as API, but packaged for Forge/Fabric. This should be used where another mod has a Baritone integration.
|
||||
- **Standalone**: Everything is obfuscated. This should be used in environments where there are no other mods present that would like to use Baritone's features.
|
||||
- **Forge Standalone**: Same as Standalone, but packaged for Forge. This should be used when Baritone is your only Forge mod, or none of your other Forge mods integrate with Baritone.
|
||||
- **Forge/Fabric Standalone**: Same as Standalone, but packaged for Forge/Fabric. This should be used when Baritone is your only Forge/Fabric mod, or none of your other Forge/Fabric mods integrate with Baritone.
|
||||
- **Unoptimized**: Nothing is obfuscated. This shouldn't be used ever in production.
|
||||
- **Forge/Fabric Unoptimized**: Same as Unoptimized, but packaged for Forge/Fabric.
|
||||
|
||||
## Build it yourself
|
||||
- Clone or download Baritone
|
||||
@@ -42,13 +43,13 @@ If another one of your Forge mods has a Baritone integration, you want `baritone
|
||||
## Command Line
|
||||
On Mac OSX and Linux, use `./gradlew` instead of `gradlew`.
|
||||
|
||||
If you have errors with a package missing please make sure you have setup your environment, and are using Oracle JDK 8.
|
||||
If you have errors with a package missing please make sure you have setup your environment, and are using Oracle JDK 8 for 1.12.2-1.16.5, JDK 16 for 1.17.1, and JDK 17 for 1.18.1.
|
||||
|
||||
To check which java you are using do
|
||||
`java -version` in a command prompt or terminal.
|
||||
If you are using anything above OpenJDK 8, it might not work because the Java distributions above JDK 8 using may not have the needed javax classes.
|
||||
If you are using anything above OpenJDK 8 for 1.12.2-1.16.5, it might not work because the Java distributions above JDK 8 using may not have the needed javax classes.
|
||||
|
||||
Open JDK 8 download: https://openjdk.java.net/install/
|
||||
Open JDK download: https://openjdk.java.net/install/
|
||||
#### macOS guide
|
||||
In order to get JDK 8, Try running the following command:
|
||||
`% /usr/libexec/java_home -V`
|
||||
@@ -84,6 +85,12 @@ For minecraft 1.15.2+, run the following instead to include the Forge jars:
|
||||
$ gradlew build -Pbaritone.forge_build
|
||||
```
|
||||
|
||||
Do this instead for Fabric jars:
|
||||
|
||||
```
|
||||
$ gradlew build -Pbaritone.fabric_build
|
||||
```
|
||||
|
||||
Running Baritone:
|
||||
|
||||
```
|
||||
|
||||
16
USAGE.md
16
USAGE.md
@@ -32,13 +32,13 @@ Watch this [showcase video](https://youtu.be/CZkLXWo4Fg4)!
|
||||
|
||||
To toggle a boolean setting, just say its name in chat (for example saying `allowBreak` toggles whether Baritone will consider breaking blocks). For a numeric setting, say its name then the new value (like `primaryTimeoutMS 250`). It's case insensitive. To reset a setting to its default value, say `acceptableThrowawayItems reset`. To reset all settings, say `reset`. To see all settings that have been modified from their default values, say `modified`.
|
||||
|
||||
Some common examples:
|
||||
Commands in Baritone:
|
||||
- `thisway 1000` then `path` to go in the direction you're facing for a thousand blocks
|
||||
- `goal x y z` or `goal x z` or `goal y`, then `path` to set a goal to a certain coordinate then path to it
|
||||
- `goto x y z` or `goto x z` or `goto y` to go to a certain coordinate (in a single step, starts going immediately)
|
||||
- `goal` to set the goal to your player's feet
|
||||
- `goal clear` to clear the goal
|
||||
- `cancel` or `stop` to stop everything
|
||||
- `cancel` or `stop` to stop everything, `forcecancel` is also an option
|
||||
- `goto portal` or `goto ender_chest` or `goto block_type` to go to a block. (in Impact, `.goto` is an alias for `.b goto` for the most part)
|
||||
- `mine diamond_ore iron_ore` to mine diamond ore or iron ore (turn on the setting `legitMine` to only mine ores that it can actually see. It will explore randomly around y=11 until it finds them.) An amount of blocks can also be specified, for example, `mine 64 diamond_ore`.
|
||||
- `click` to click your destination on the screen. Right click path to on top of the block, left click to path into it (either at foot level or eye level), and left click and drag to select an area (`#help sel` to see what you can do with that selection).
|
||||
@@ -51,11 +51,19 @@ Some common examples:
|
||||
- `axis` to go to an axis or diagonal axis at y=120 (`axisHeight` is a configurable setting, defaults to 120).
|
||||
- `explore x z` to explore the world from the origin of x,z. Leave out x and z to default to player feet. This will continually path towards the closest chunk to the origin that it's never seen before. `explorefilter filter.json` with optional invert can be used to load in a list of chunks to load.
|
||||
- `invert` to invert the current goal and path. This gets as far away from it as possible, instead of as close as possible. For example, do `goal` then `invert` to run as far as possible from where you're standing at the start.
|
||||
- `come` tells Baritone to head towards your camera, useful when freecam doesn't move your player position.
|
||||
- `blacklist` will stop baritone from going to the closest block so it won't attempt to get to it.
|
||||
- `eta` to get information about the estimated time until the next segment and the goal, be aware that the ETA to your goal is really unprecise.
|
||||
- `proc` to view miscellaneous information about the process currently controlling Baritone.
|
||||
- `repack` to re-cache the chunks around you.
|
||||
- `gc` to call `System.gc()` which may free up some memory.
|
||||
- `render` to fix glitched chunk rendering without having to reload all of them.
|
||||
- `reloadall` to reload Baritone's world cache or `saveall` to save Baritone's world cache.
|
||||
- `find` to search through Baritone's cache and attempt to find the location of the block.
|
||||
- `surface` or `top` to tell Baritone to head towards the closest surface-like area, this can be the surface or highest available air space.
|
||||
- `version` to get the version of Baritone you're running
|
||||
- `damn` daniel
|
||||
|
||||
For the rest of the commands, you can take a look at the code [here](https://baritone.leijurv.com/baritone/api/Settings.html).
|
||||
|
||||
All the settings and documentation are <a href="https://github.com/cabaletta/baritone/blob/master/src/api/java/baritone/api/Settings.java">here</a>. If you find HTML easier to read than Javadoc, you can look <a href="https://baritone.leijurv.com/baritone/api/Settings.html#field.detail">here</a>.
|
||||
|
||||
There are about a hundred settings, but here are some fun / interesting / important ones that you might want to look at changing in normal usage of Baritone. The documentation for each can be found at the above links.
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
*/
|
||||
|
||||
group 'baritone'
|
||||
version '1.2.15'
|
||||
version '1.2.16'
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
|
||||
@@ -209,8 +209,8 @@ public class ProguardTask extends BaritoneGradleTask {
|
||||
|
||||
// Setup the template that will be used to derive the API and Standalone configs
|
||||
List<String> template = Files.readAllLines(getTemporaryFile(PROGUARD_CONFIG_DEST));
|
||||
template.add(0, "-injars " + this.artifactPath.toString());
|
||||
template.add(1, "-outjars " + this.getTemporaryFile(PROGUARD_EXPORT_PATH));
|
||||
template.add(0, "-injars '" + this.artifactPath.toString() + "'");
|
||||
template.add(1, "-outjars '" + this.getTemporaryFile(PROGUARD_EXPORT_PATH) + "'");
|
||||
|
||||
// Acquire the RT jar using "java -verbose". This doesn't work on Java 9+
|
||||
Process p = new ProcessBuilder(this.getJavaBinPathForProguard(), "-verbose").start();
|
||||
@@ -405,9 +405,15 @@ public class ProguardTask extends BaritoneGradleTask {
|
||||
Files.delete(this.proguardOut);
|
||||
}
|
||||
|
||||
Path proguardJar = getTemporaryFile(PROGUARD_JAR);
|
||||
// Make paths relative to work directory; fixes spaces in path to config, @"" doesn't work
|
||||
Path workingDirectory = getTemporaryFile("");
|
||||
Path proguardJar = workingDirectory.relativize(getTemporaryFile(PROGUARD_JAR));
|
||||
config = workingDirectory.relativize(config);
|
||||
|
||||
// Honestly, if you still have spaces in your path at this point, you're SOL.
|
||||
|
||||
Process p = new ProcessBuilder("java", "-jar", proguardJar.toString(), "@" + config.toString())
|
||||
.directory(getTemporaryFile("").toFile()) // Set the working directory to the temporary folder]
|
||||
.directory(workingDirectory.toFile()) // Set the working directory to the temporary folder]
|
||||
.start();
|
||||
|
||||
// We can't do output inherit process I/O with gradle for some reason and have it work, so we have to do this
|
||||
|
||||
6
scripts/proguard.pro
vendored
6
scripts/proguard.pro
vendored
@@ -1,5 +1,6 @@
|
||||
-keepattributes Signature
|
||||
-keepattributes *Annotation*
|
||||
-keepattributes InnerClasses
|
||||
|
||||
-optimizationpasses 5
|
||||
-verbose
|
||||
@@ -22,6 +23,7 @@
|
||||
-keep class baritone.api.IBaritoneProvider
|
||||
|
||||
-keep class baritone.api.utils.MyChunkPos { *; } # even in standalone we need to keep this for gson reflect
|
||||
-keepname class baritone.api.utils.BlockOptionalMeta # this name is exposed to the user, so we need to keep it in all builds
|
||||
|
||||
# Keep any class or member annotated with @KeepName so we dont have to put everything in the script
|
||||
-keep,allowobfuscation @interface baritone.KeepName
|
||||
@@ -40,8 +42,10 @@
|
||||
|
||||
#try to keep usage of schematica in separate classes
|
||||
-keep class baritone.utils.schematic.schematica.**
|
||||
-keep class baritone.utils.schematic.litematica.**
|
||||
#proguard doesnt like it when it cant find our fake schematica classes
|
||||
-dontwarn baritone.utils.schematic.schematica.**
|
||||
-dontwarn baritone.utils.schematic.litematica.**
|
||||
|
||||
# copy all necessary libraries into tempLibraries to build
|
||||
|
||||
@@ -81,7 +85,7 @@
|
||||
|
||||
-libraryjars 'tempLibraries/netty-all-4.1.9.Final.jar'
|
||||
-libraryjars 'tempLibraries/oshi-core-1.1.jar'
|
||||
-libraryjars 'tempLibraries/patchy-1.2.jar'
|
||||
-libraryjars 'tempLibraries/patchy-1.3.9.jar'
|
||||
-libraryjars 'tempLibraries/platform-3.4.0.jar'
|
||||
-libraryjars 'tempLibraries/realms-1.10.22.jar'
|
||||
-libraryjars 'tempLibraries/soundsystem-20120107.jar'
|
||||
|
||||
@@ -34,8 +34,8 @@ import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.List;
|
||||
import java.util.*;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* Baritone's settings. Settings apply to all Baritone instances.
|
||||
@@ -49,6 +49,11 @@ public final class Settings {
|
||||
*/
|
||||
public final Setting<Boolean> allowBreak = new Setting<>(true);
|
||||
|
||||
/**
|
||||
* Blocks that baritone will be allowed to break even with allowBreak set to false
|
||||
*/
|
||||
public final Setting<List<Block>> allowBreakAnyway = new Setting<>(new ArrayList<>());
|
||||
|
||||
/**
|
||||
* Allow Baritone to sprint
|
||||
*/
|
||||
@@ -102,6 +107,13 @@ public final class Settings {
|
||||
*/
|
||||
public final Setting<Double> walkOnWaterOnePenalty = new Setting<>(3D);
|
||||
|
||||
/**
|
||||
* Don't allow breaking blocks next to liquids.
|
||||
* <p>
|
||||
* Enable if you have mods adding custom fluid physics.
|
||||
*/
|
||||
public final Setting<Boolean> strictLiquidCheck = new Setting<>(false);
|
||||
|
||||
/**
|
||||
* Allow Baritone to fall arbitrary distances and place a water bucket beneath it.
|
||||
* Reliability: questionable.
|
||||
@@ -111,6 +123,8 @@ public final class Settings {
|
||||
/**
|
||||
* Allow Baritone to assume it can walk on still water just like any other block.
|
||||
* This functionality is assumed to be provided by a separate library that might have imported Baritone.
|
||||
* <p>
|
||||
* Note: This will prevent some usage of the frostwalker enchantment, like pillaring up from water.
|
||||
*/
|
||||
public final Setting<Boolean> assumeWalkOnWater = new Setting<>(false);
|
||||
|
||||
@@ -190,6 +204,13 @@ public final class Settings {
|
||||
/**
|
||||
* Blocks that Baritone is not allowed to break
|
||||
*/
|
||||
public final Setting<List<Block>> blocksToDisallowBreaking = new Setting<>(new ArrayList<>(
|
||||
// Leave Empty by Default
|
||||
));
|
||||
|
||||
/**
|
||||
* blocks that baritone shouldn't break, but can if it needs to.
|
||||
*/
|
||||
public final Setting<List<Block>> blocksToAvoidBreaking = new Setting<>(new ArrayList<>(Arrays.asList( // TODO can this be a HashSet or ImmutableSet?
|
||||
Blocks.CRAFTING_TABLE,
|
||||
Blocks.FURNACE,
|
||||
@@ -200,6 +221,11 @@ public final class Settings {
|
||||
Blocks.WALL_SIGN
|
||||
)));
|
||||
|
||||
/**
|
||||
* this multiplies the break speed, if set above 1 it's "encourage breaking" instead
|
||||
*/
|
||||
public final Setting<Double> avoidBreakingMultiplier = new Setting<>(.1);
|
||||
|
||||
/**
|
||||
* A list of blocks to be treated as if they're air.
|
||||
* <p>
|
||||
@@ -222,6 +248,8 @@ public final class Settings {
|
||||
* A mapping of blocks to blocks treated as correct in their position
|
||||
* <p>
|
||||
* If a schematic asks for a block on this mapping, all blocks on the mapped list will be accepted at that location as well
|
||||
* <p>
|
||||
* Syntax same as <a href="https://baritone.leijurv.com/baritone/api/Settings.html#buildSubstitutes">buildSubstitutes</a>
|
||||
*/
|
||||
public final Setting<Map<Block, List<Block>>> buildValidSubstitutes = new Setting<>(new HashMap<>());
|
||||
|
||||
@@ -229,6 +257,15 @@ public final class Settings {
|
||||
* A mapping of blocks to blocks to be built instead
|
||||
* <p>
|
||||
* If a schematic asks for a block on this mapping, Baritone will place the first placeable block in the mapped list
|
||||
* <p>
|
||||
* Usage Syntax:
|
||||
* <pre>
|
||||
* sourceblockA->blockToSubstituteA1,blockToSubstituteA2,...blockToSubstituteAN,sourceBlockB->blockToSubstituteB1,blockToSubstituteB2,...blockToSubstituteBN,...sourceBlockX->blockToSubstituteX1,blockToSubstituteX2...blockToSubstituteXN
|
||||
* </pre>
|
||||
* Example:
|
||||
* <pre>
|
||||
* stone->cobblestone,andesite,oak_planks->birch_planks,acacia_planks,glass
|
||||
* </pre>
|
||||
*/
|
||||
public final Setting<Map<Block, List<Block>>> buildSubstitutes = new Setting<>(new HashMap<>());
|
||||
|
||||
@@ -246,6 +283,17 @@ public final class Settings {
|
||||
*/
|
||||
public final Setting<Boolean> buildIgnoreExisting = new Setting<>(false);
|
||||
|
||||
/**
|
||||
* If this is true, the builder will ignore directionality of certain blocks like glazed terracotta.
|
||||
*/
|
||||
public final Setting<Boolean> buildIgnoreDirection = new Setting<>(false);
|
||||
|
||||
/**
|
||||
* A list of names of block properties the builder will ignore.
|
||||
*/
|
||||
public final Setting<List<String>> buildIgnoreProperties = new Setting<>(new ArrayList<>(Arrays.asList(
|
||||
)));
|
||||
|
||||
/**
|
||||
* If this setting is true, Baritone will never break a block that is adjacent to an unsupported falling block.
|
||||
* <p>
|
||||
@@ -368,6 +416,9 @@ public final class Settings {
|
||||
*/
|
||||
public final Setting<Double> mobSpawnerAvoidanceCoefficient = new Setting<>(2.0);
|
||||
|
||||
/**
|
||||
* Distance to avoid mob spawners.
|
||||
*/
|
||||
public final Setting<Integer> mobSpawnerAvoidanceRadius = new Setting<>(16);
|
||||
|
||||
/**
|
||||
@@ -377,6 +428,9 @@ public final class Settings {
|
||||
*/
|
||||
public final Setting<Double> mobAvoidanceCoefficient = new Setting<>(1.5);
|
||||
|
||||
/**
|
||||
* Distance to avoid mobs.
|
||||
*/
|
||||
public final Setting<Integer> mobAvoidanceRadius = new Setting<>(8);
|
||||
|
||||
/**
|
||||
@@ -527,6 +581,17 @@ public final class Settings {
|
||||
*/
|
||||
public final Setting<Long> slowPathTimeoutMS = new Setting<>(40000L);
|
||||
|
||||
|
||||
/**
|
||||
* allows baritone to save bed waypoints when interacting with beds
|
||||
*/
|
||||
public final Setting<Boolean> doBedWaypoints = new Setting<>(true);
|
||||
|
||||
/**
|
||||
* allows baritone to save death waypoints
|
||||
*/
|
||||
public final Setting<Boolean> doDeathWaypoints = new Setting<>(true);
|
||||
|
||||
/**
|
||||
* The big one. Download all chunks in simplified 2-bit format and save them for better very-long-distance pathing.
|
||||
*/
|
||||
@@ -543,13 +608,6 @@ public final class Settings {
|
||||
*/
|
||||
public final Setting<Boolean> pruneRegionsFromRAM = new Setting<>(true);
|
||||
|
||||
/**
|
||||
* Remember the contents of containers (chests, echests, furnaces)
|
||||
* <p>
|
||||
* Really buggy since the packet stuff is multithreaded badly thanks to brady
|
||||
*/
|
||||
public final Setting<Boolean> containerMemory = new Setting<>(false);
|
||||
|
||||
/**
|
||||
* Fill in blocks behind you
|
||||
*/
|
||||
@@ -869,7 +927,7 @@ public final class Settings {
|
||||
/**
|
||||
* Only build the selected part of schematics
|
||||
*/
|
||||
public final Setting<Boolean> buildOnlySelection = new Setting<>(false);
|
||||
public final Setting<Boolean> buildOnlySelection = new Setting<>(false);
|
||||
|
||||
/**
|
||||
* How far to move before repeating the build. 0 to disable repeating on a certain axis, 0,0,0 to disable entirely
|
||||
@@ -1081,7 +1139,7 @@ public final class Settings {
|
||||
* via {@link Consumer#andThen(Consumer)} or it can completely be overriden via setting
|
||||
* {@link Setting#value};
|
||||
*/
|
||||
public final Setting<Consumer<ITextComponent>> logger = new Setting<>(Minecraft.getMinecraft().ingameGUI.getChatGUI()::printChatMessage);
|
||||
public final Setting<Consumer<ITextComponent>> logger = new Setting<>(msg -> Minecraft.getMinecraft().ingameGUI.getChatGUI().printChatMessage(msg));
|
||||
|
||||
/**
|
||||
* The function that is called when Baritone will send a desktop notification. This function can be added to
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
/*
|
||||
* This file is part of Baritone.
|
||||
*
|
||||
* Baritone is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Baritone is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Baritone. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package baritone.api.cache;
|
||||
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author Brady
|
||||
* @since 9/23/2018
|
||||
*/
|
||||
public interface IContainerMemory {
|
||||
|
||||
/**
|
||||
* Gets a remembered inventory by its block position.
|
||||
*
|
||||
* @param pos The position of the container block
|
||||
* @return The remembered inventory
|
||||
*/
|
||||
IRememberedInventory getInventoryByPos(BlockPos pos);
|
||||
|
||||
/**
|
||||
* Gets the map of all block positions to their remembered inventories.
|
||||
*
|
||||
* @return Map of block positions to their respective remembered inventories
|
||||
*/
|
||||
Map<BlockPos, IRememberedInventory> getRememberedInventories();
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
/*
|
||||
* This file is part of Baritone.
|
||||
*
|
||||
* Baritone is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Baritone is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Baritone. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package baritone.api.cache;
|
||||
|
||||
import net.minecraft.item.ItemStack;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Brady
|
||||
* @since 9/23/2018
|
||||
*/
|
||||
public interface IRememberedInventory {
|
||||
|
||||
/**
|
||||
* @return The contents of this inventory
|
||||
*/
|
||||
List<ItemStack> getContents();
|
||||
|
||||
/**
|
||||
* @return The number of slots in this inventory
|
||||
*/
|
||||
int getSize();
|
||||
}
|
||||
@@ -37,9 +37,4 @@ public interface IWorldData {
|
||||
*/
|
||||
IWaypointCollection getWaypoints();
|
||||
|
||||
/**
|
||||
* @return The {@link IContainerMemory} instance
|
||||
* @see IContainerMemory
|
||||
*/
|
||||
IContainerMemory getContainerMemory();
|
||||
}
|
||||
|
||||
@@ -26,7 +26,8 @@ import java.util.stream.Stream;
|
||||
|
||||
public enum RelativeCoordinate implements IDatatypePost<Double, Double> {
|
||||
INSTANCE;
|
||||
private static Pattern PATTERN = Pattern.compile("^(~?)([+-]?(?:\\d+(?:\\.\\d*)?|\\.\\d+)([k-k]?)|)$");
|
||||
private static String ScalesAliasRegex = "[kKmM]";
|
||||
private static Pattern PATTERN = Pattern.compile("^(~?)([+-]?(?:\\d+(?:\\.\\d*)?|\\.\\d+)("+ScalesAliasRegex+"?)|)$");
|
||||
|
||||
@Override
|
||||
public Double apply(IDatatypeContext ctx, Double origin) throws CommandException {
|
||||
@@ -41,11 +42,15 @@ public enum RelativeCoordinate implements IDatatypePost<Double, Double> {
|
||||
|
||||
boolean isRelative = !matcher.group(1).isEmpty();
|
||||
|
||||
double offset = matcher.group(2).isEmpty() ? 0 : Double.parseDouble(matcher.group(2).replaceAll("k", ""));
|
||||
|
||||
if (matcher.group(2).contains("k")) {
|
||||
double offset = matcher.group(2).isEmpty() ? 0 : Double.parseDouble(matcher.group(2).replaceAll(ScalesAliasRegex, ""));
|
||||
|
||||
if (matcher.group(2).toLowerCase().contains("k")) {
|
||||
offset *= 1000;
|
||||
}
|
||||
if (matcher.group(2).toLowerCase().contains("m")) {
|
||||
offset *= 1000000;
|
||||
}
|
||||
|
||||
|
||||
if (isRelative) {
|
||||
return origin + offset;
|
||||
|
||||
@@ -84,7 +84,7 @@ public class Registry<V> {
|
||||
* @param entry The entry to unregister.
|
||||
*/
|
||||
public void unregister(V entry) {
|
||||
if (registered(entry)) {
|
||||
if (!registered(entry)) {
|
||||
return;
|
||||
}
|
||||
_entries.remove(entry);
|
||||
|
||||
@@ -87,9 +87,9 @@ public class GoalBlock implements Goal, IGoalRenderPos {
|
||||
public static double calculate(double xDiff, int yDiff, double zDiff) {
|
||||
double heuristic = 0;
|
||||
|
||||
// if yDiff is 1 that means that pos.getY()-this.y==1 which means that we're 1 block below where we should be
|
||||
// therefore going from 0,0,0 to a GoalYLevel of pos.getY()-this.y is accurate
|
||||
heuristic += GoalYLevel.calculate(yDiff, 0);
|
||||
// if yDiff is 1 that means that currentY-goalY==1 which means that we're 1 block above where we should be
|
||||
// therefore going from 0,yDiff,0 to a GoalYLevel of 0 is accurate
|
||||
heuristic += GoalYLevel.calculate(0, yDiff);
|
||||
|
||||
//use the pythagorean and manhattan mixture from GoalXZ
|
||||
heuristic += GoalXZ.calculate(xDiff, zDiff);
|
||||
|
||||
@@ -58,6 +58,8 @@ public interface IBuilderProcess extends IBaritoneProcess {
|
||||
|
||||
void buildOpenSchematic();
|
||||
|
||||
void buildOpenLitematic(int i);
|
||||
|
||||
void pause();
|
||||
|
||||
boolean isPaused();
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
|
||||
package baritone.api.utils;
|
||||
|
||||
import it.unimi.dsi.fastutil.HashCommon;
|
||||
import net.minecraft.util.EnumFacing;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.util.math.MathHelper;
|
||||
@@ -80,98 +79,25 @@ public final class BetterBlockPos extends BlockPos {
|
||||
return longHash(pos.x, pos.y, pos.z);
|
||||
}
|
||||
|
||||
public static final int NUM_X_BITS = 26;
|
||||
public static final int NUM_Z_BITS = NUM_X_BITS;
|
||||
public static final int NUM_Y_BITS = 9; // note: even though Y goes from 0 to 255, that doesn't mean 8 bits will "just work" because the deserializer assumes signed. i could change it for just Y to assume unsigned and leave X and Z as signed, however, we know that in 1.17 they plan to add negative Y. for that reason, the better approach is to give the extra bits to Y and leave it as signed.
|
||||
// also, if 1.17 sticks with the current plan which is -64 to +320, we could have 9 bits for Y and a constant offset of -64 to change it to -128 to +256.
|
||||
// that would result in the packed long representation of any valid coordinate still being a positive integer
|
||||
// i like that property, so i will keep num_y_bits at 9 and plan for an offset in 1.17
|
||||
// it also gives 1 bit of wiggle room in case anything else happens in the future, so we are only using 63 out of 64 bits at the moment
|
||||
public static final int Z_SHIFT = 0;
|
||||
public static final int Y_SHIFT = Z_SHIFT + NUM_Z_BITS + 1; // 1 padding bit to make twos complement not overflow
|
||||
public static final int X_SHIFT = Y_SHIFT + NUM_Y_BITS + 1; // and also here too
|
||||
public static final long X_MASK = (1L << NUM_X_BITS) - 1L; // X doesn't need padding as the overflow carry bit is just discarded, like a normal long (-1) + (1) = 0
|
||||
public static final long Y_MASK = (1L << NUM_Y_BITS) - 1L;
|
||||
public static final long Z_MASK = (1L << NUM_Z_BITS) - 1L;
|
||||
|
||||
public static final long POST_ADDITION_MASK = X_MASK << X_SHIFT | Y_MASK << Y_SHIFT | Z_MASK << Z_SHIFT; // required to "manually inline" toLong(-1, -1, -1) here so that javac inserts proper ldc2_w instructions at usage points instead of getstatic
|
||||
// what's this ^ mask for?
|
||||
// it allows for efficient offsetting and manipulation of a long packed coordinate
|
||||
// if we had three ints, x y z, it would be easy to do "y += 1" or "x -= 1"
|
||||
// but how do you do those things if you have a long with x y and z all stuffed into one primitive?
|
||||
// adding together two long coordinates actually works perfectly if both sides have X, Y, and Z as all positive, no issues at all
|
||||
// but when Y or Z is negative, we run into an issue. consider 8 bits: negative one is 11111111 and one is 00000001
|
||||
// adding them together gives 00000000, zero, **but only because there isn't a 9th bit to carry into**
|
||||
// if we had, instead, 00000000 11111111 + 00000000 00000001 we would rightly get 00000001 00000000 with the 1 being carried into the 9th position there
|
||||
// this is exactly what happens. "toLong(0, 1, 0) + toLong(0, -1, 0)" ends up equaling toLong(1, 0, 0) while we'd rather it equal toLong(0, 0, 0)
|
||||
// so, we simply mask out the unwanted result of the carry by inserting 1 bit of padding space (as added above) between each
|
||||
// it used to be 000XXXXXXXXXXXXXXXXXXXXXXXXXXYYYYYYYYYZZZZZZZZZZZZZZZZZZZZZZZZZZ
|
||||
// and now it is 0XXXXXXXXXXXXXXXXXXXXXXXXXX0YYYYYYYYY0ZZZZZZZZZZZZZZZZZZZZZZZZZZ
|
||||
// we simply place the X Y and Z in slightly different sections of the long, putting a bit of space between each
|
||||
// the mask ^ is 0111111111111111111111111110111111111011111111111111111111111111
|
||||
// using that example of (0,1,0) + (0,-1,0), here's what happens
|
||||
// 0000000000000000000000000000000000001000000000000000000000000000 (this is X=0 Y=1 Z=0)
|
||||
// + 0000000000000000000000000000111111111000000000000000000000000000 (this is X=0 Y=-1 Z=0)
|
||||
// = 0000000000000000000000000001000000000000000000000000000000000000
|
||||
// the unwanted carry bit here ^ is no longer corrupting the least significant bit of X and making it 1!
|
||||
// now it's just turning on the unused padding bit that we don't care about
|
||||
// using the mask and bitwise and, we can easily and branchlessly turn off the padding bits just in case something overflow carried into them!
|
||||
// 0000000000000000000000000001000000000000000000000000000000000000 (the result of the addition from earlier)
|
||||
// & 0111111111111111111111111110111111111011111111111111111111111111 (this is POST_ADDITION_MASK)
|
||||
// = 0000000000000000000000000000000000000000000000000000000000000000
|
||||
// POST_ADDITION_MASK retains the bits that actually form X, Y, and Z, but intentionally turns off the padding bits
|
||||
// so, we can simply do "(toLong(0, 1, 0) + toLong(0, -1, 0)) & POST_ADDITION_MASK" and correctly get toLong(0, 0, 0)
|
||||
// which is incredibly fast and efficient, an add then a bitwise AND against a constant
|
||||
// and it doesn't require us to pull out X, Y, and Z, modify one of them, and put them all back into the long
|
||||
// that's what the point of the mask is
|
||||
|
||||
static {
|
||||
if (POST_ADDITION_MASK != toLong(-1, -1, -1)) {
|
||||
throw new IllegalStateException(POST_ADDITION_MASK + " " + toLong(-1, -1, -1)); // sanity check
|
||||
}
|
||||
}
|
||||
|
||||
public long toLong() {
|
||||
return toLong(this.x, this.y, this.z);
|
||||
}
|
||||
|
||||
public static BetterBlockPos fromLong(long serialized) {
|
||||
return new BetterBlockPos(XfromLong(serialized), YfromLong(serialized), ZfromLong(serialized));
|
||||
}
|
||||
|
||||
public static int XfromLong(long serialized) {
|
||||
return (int) (serialized << (64 - X_SHIFT - NUM_X_BITS) >> (64 - NUM_X_BITS));
|
||||
}
|
||||
|
||||
public static int YfromLong(long serialized) {
|
||||
return (int) (serialized << (64 - Y_SHIFT - NUM_Y_BITS) >> (64 - NUM_Y_BITS));
|
||||
}
|
||||
|
||||
public static int ZfromLong(long serialized) {
|
||||
return (int) (serialized << (64 - Z_SHIFT - NUM_Z_BITS) >> (64 - NUM_Z_BITS));
|
||||
}
|
||||
|
||||
public static long toLong(final int x, final int y, final int z) {
|
||||
return ((long) x & X_MASK) << X_SHIFT | ((long) y & Y_MASK) << Y_SHIFT | ((long) z & Z_MASK) << Z_SHIFT;
|
||||
}
|
||||
|
||||
public static long offsetBy(long pos, int x, int y, int z) {
|
||||
return (pos + toLong(x, y, z)) & BetterBlockPos.POST_ADDITION_MASK;
|
||||
}
|
||||
|
||||
public static final long HASHCODE_MURMUR_MASK = murmur64(-1);
|
||||
public static final long ZOBRIST_MURMUR_MASK = murmur64(-2);
|
||||
|
||||
public static long longHash(int x, int y, int z) {
|
||||
return longHash(toLong(x, y, z));
|
||||
}
|
||||
|
||||
public static long longHash(long packed) {
|
||||
return murmur64(HASHCODE_MURMUR_MASK ^ packed);
|
||||
}
|
||||
|
||||
public static long murmur64(long h) {
|
||||
return HashCommon.murmurHash3(h);
|
||||
// TODO use the same thing as BlockPos.fromLong();
|
||||
// invertibility would be incredibly useful
|
||||
/*
|
||||
* This is the hashcode implementation of Vec3i (the superclass of the class which I shall not name)
|
||||
*
|
||||
* public int hashCode() {
|
||||
* return (this.getY() + this.getZ() * 31) * 31 + this.getX();
|
||||
* }
|
||||
*
|
||||
* That is terrible and has tons of collisions and makes the HashMap terribly inefficient.
|
||||
*
|
||||
* That's why we grab out the X, Y, Z and calculate our own hashcode
|
||||
*/
|
||||
long hash = 3241;
|
||||
hash = 3457689L * hash + x;
|
||||
hash = 8734625L * hash + y;
|
||||
hash = 2873465L * hash + z;
|
||||
return hash;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -280,10 +206,10 @@ public final class BetterBlockPos extends BlockPos {
|
||||
@Nonnull
|
||||
public String toString() {
|
||||
return String.format(
|
||||
"BetterBlockPos{x=%d,y=%d,z=%d}",
|
||||
x,
|
||||
y,
|
||||
z
|
||||
"BetterBlockPos{x=%s,y=%s,z=%s}",
|
||||
SettingsUtil.maybeCensor(x),
|
||||
SettingsUtil.maybeCensor(y),
|
||||
SettingsUtil.maybeCensor(z)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,7 +176,7 @@ public class SettingsUtil {
|
||||
/**
|
||||
* This should always be the same as whether the setting can be parsed from or serialized to a string
|
||||
*
|
||||
* @param the setting
|
||||
* @param setting The Setting
|
||||
* @return true if the setting can not be set or read by the user
|
||||
*/
|
||||
public static boolean javaOnlySetting(Settings.Setting setting) {
|
||||
|
||||
@@ -15,25 +15,21 @@
|
||||
* along with Baritone. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package baritone.builder;
|
||||
package baritone.launch.mixins;
|
||||
|
||||
public class Vec2d {
|
||||
import baritone.utils.accessor.IItemTool;
|
||||
import net.minecraft.item.Item;
|
||||
import net.minecraft.item.ItemTool;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
|
||||
public final double x;
|
||||
public final double z;
|
||||
@Mixin(ItemTool.class)
|
||||
public class MixinItemTool implements IItemTool {
|
||||
@Shadow protected Item.ToolMaterial toolMaterial;
|
||||
|
||||
public Vec2d(double x, double z) {
|
||||
this.x = x;
|
||||
this.z = z;
|
||||
@Override
|
||||
public int getHarvestLevel() {
|
||||
return toolMaterial.getHarvestLevel();
|
||||
}
|
||||
|
||||
public Vec2d plus(double dx, double dz) {
|
||||
return new Vec2d(this.x + dx, this.z + dz);
|
||||
}
|
||||
|
||||
public double[] toArr() {
|
||||
return new double[]{x, z};
|
||||
}
|
||||
|
||||
public static final Vec2d HALVED_CENTER = new Vec2d(0.5d, 0.5d);
|
||||
}
|
||||
@@ -15,13 +15,21 @@
|
||||
* along with Baritone. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package baritone.builder;
|
||||
package baritone.launch.mixins;
|
||||
|
||||
public enum GreedySolverEngine implements ISolverEngine {
|
||||
INSTANCE;
|
||||
import baritone.utils.accessor.INBTTagLongArray;
|
||||
import net.minecraft.nbt.NBTTagLongArray;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||
|
||||
/**
|
||||
* @author rycbar
|
||||
* @since 26.09.2022
|
||||
*/
|
||||
@Mixin(NBTTagLongArray.class)
|
||||
public abstract class MixinNBTTagLongArray implements INBTTagLongArray {
|
||||
|
||||
@Accessor("data")
|
||||
@Override
|
||||
public SolverEngineOutput solve(SolverEngineInput in) {
|
||||
return new GreedySolver(in).search();
|
||||
}
|
||||
}
|
||||
public abstract long[] getLongArray();
|
||||
}
|
||||
@@ -21,7 +21,9 @@
|
||||
"MixinEntityRenderer",
|
||||
"MixinGuiScreen",
|
||||
"MixinItemStack",
|
||||
"MixinItemTool",
|
||||
"MixinMinecraft",
|
||||
"MixinNBTTagLongArray",
|
||||
"MixinNetHandlerPlayClient",
|
||||
"MixinNetworkManager",
|
||||
"MixinPlayerControllerMP",
|
||||
|
||||
@@ -24,7 +24,6 @@ import baritone.api.event.listener.IEventBus;
|
||||
import baritone.api.utils.Helper;
|
||||
import baritone.api.utils.IPlayerContext;
|
||||
import baritone.behavior.*;
|
||||
import baritone.builder.Main;
|
||||
import baritone.cache.WorldProvider;
|
||||
import baritone.command.manager.CommandManager;
|
||||
import baritone.event.GameEventHandler;
|
||||
@@ -69,8 +68,8 @@ public class Baritone implements IBaritone {
|
||||
|
||||
private PathingBehavior pathingBehavior;
|
||||
private LookBehavior lookBehavior;
|
||||
private MemoryBehavior memoryBehavior;
|
||||
private InventoryBehavior inventoryBehavior;
|
||||
private WaypointBehavior waypointBehavior;
|
||||
private InputOverrideHandler inputOverrideHandler;
|
||||
|
||||
private FollowProcess followProcess;
|
||||
@@ -101,9 +100,9 @@ public class Baritone implements IBaritone {
|
||||
// the Behavior constructor calls baritone.registerBehavior(this) so this populates the behaviors arraylist
|
||||
pathingBehavior = new PathingBehavior(this);
|
||||
lookBehavior = new LookBehavior(this);
|
||||
memoryBehavior = new MemoryBehavior(this);
|
||||
inventoryBehavior = new InventoryBehavior(this);
|
||||
inputOverrideHandler = new InputOverrideHandler(this);
|
||||
waypointBehavior = new WaypointBehavior(this);
|
||||
}
|
||||
|
||||
this.pathingControlManager = new PathingControlManager(this);
|
||||
@@ -121,15 +120,6 @@ public class Baritone implements IBaritone {
|
||||
this.worldProvider = new WorldProvider();
|
||||
this.selectionManager = new SelectionManager(this);
|
||||
this.commandManager = new CommandManager(this);
|
||||
|
||||
try {
|
||||
Main.main();
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
} catch (Throwable th) {
|
||||
th.printStackTrace();
|
||||
throw th;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -161,10 +151,6 @@ public class Baritone implements IBaritone {
|
||||
return this.playerContext;
|
||||
}
|
||||
|
||||
public MemoryBehavior getMemoryBehavior() {
|
||||
return this.memoryBehavior;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FollowProcess getFollowProcess() {
|
||||
return this.followProcess;
|
||||
|
||||
@@ -1,319 +0,0 @@
|
||||
/*
|
||||
* This file is part of Baritone.
|
||||
*
|
||||
* Baritone is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Baritone is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Baritone. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package baritone.behavior;
|
||||
|
||||
import baritone.Baritone;
|
||||
import baritone.api.cache.Waypoint;
|
||||
import baritone.api.event.events.BlockInteractEvent;
|
||||
import baritone.api.event.events.PacketEvent;
|
||||
import baritone.api.event.events.PlayerUpdateEvent;
|
||||
import baritone.api.event.events.TickEvent;
|
||||
import baritone.api.event.events.type.EventState;
|
||||
import baritone.api.utils.BetterBlockPos;
|
||||
import baritone.api.utils.Helper;
|
||||
import baritone.cache.ContainerMemory;
|
||||
import baritone.utils.BlockStateInterface;
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.block.BlockBed;
|
||||
import net.minecraft.init.Blocks;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.network.Packet;
|
||||
import net.minecraft.network.play.client.CPacketCloseWindow;
|
||||
import net.minecraft.network.play.client.CPacketPlayerTryUseItemOnBlock;
|
||||
import net.minecraft.network.play.server.SPacketCloseWindow;
|
||||
import net.minecraft.network.play.server.SPacketOpenWindow;
|
||||
import net.minecraft.tileentity.TileEntity;
|
||||
import net.minecraft.tileentity.TileEntityLockable;
|
||||
import net.minecraft.util.EnumFacing;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.util.text.ITextComponent;
|
||||
import net.minecraft.util.text.TextComponentString;
|
||||
import net.minecraft.util.text.TextComponentTranslation;
|
||||
import net.minecraft.util.text.TextFormatting;
|
||||
import net.minecraft.util.text.event.ClickEvent;
|
||||
import net.minecraft.util.text.event.HoverEvent;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
|
||||
import static baritone.api.command.IBaritoneChatControl.FORCE_COMMAND_PREFIX;
|
||||
|
||||
/**
|
||||
* doesn't work for horse inventories :^)
|
||||
*
|
||||
* @author Brady
|
||||
* @since 8/6/2018
|
||||
*/
|
||||
public final class MemoryBehavior extends Behavior {
|
||||
|
||||
private final List<FutureInventory> futureInventories = new ArrayList<>(); // this is per-bot
|
||||
|
||||
private Integer enderChestWindowId; // nae nae
|
||||
|
||||
public MemoryBehavior(Baritone baritone) {
|
||||
super(baritone);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void onTick(TickEvent event) {
|
||||
if (!Baritone.settings().containerMemory.value) {
|
||||
return;
|
||||
}
|
||||
if (event.getType() == TickEvent.Type.OUT) {
|
||||
enderChestWindowId = null;
|
||||
futureInventories.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void onPlayerUpdate(PlayerUpdateEvent event) {
|
||||
if (event.getState() == EventState.PRE) {
|
||||
updateInventory();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void onSendPacket(PacketEvent event) {
|
||||
if (!Baritone.settings().containerMemory.value) {
|
||||
return;
|
||||
}
|
||||
Packet p = event.getPacket();
|
||||
|
||||
if (event.getState() == EventState.PRE) {
|
||||
if (p instanceof CPacketPlayerTryUseItemOnBlock) {
|
||||
CPacketPlayerTryUseItemOnBlock packet = event.cast();
|
||||
|
||||
TileEntity tileEntity = ctx.world().getTileEntity(packet.getPos());
|
||||
// if tileEntity is an ender chest, we don't need to do anything. ender chests are treated the same regardless of what coordinate right clicked
|
||||
|
||||
// Ensure the TileEntity is a container of some sort
|
||||
if (tileEntity instanceof TileEntityLockable) {
|
||||
|
||||
TileEntityLockable lockable = (TileEntityLockable) tileEntity;
|
||||
int size = lockable.getSizeInventory();
|
||||
BetterBlockPos position = BetterBlockPos.from(tileEntity.getPos());
|
||||
BetterBlockPos adj = BetterBlockPos.from(neighboringConnectedBlock(position));
|
||||
System.out.println(position + " " + adj);
|
||||
if (adj != null) {
|
||||
size *= 2; // double chest or double trapped chest
|
||||
if (adj.getX() < position.getX() || adj.getZ() < position.getZ()) {
|
||||
position = adj; // standardize on the lower coordinate, regardless of which side of the large chest we right clicked
|
||||
}
|
||||
}
|
||||
|
||||
this.futureInventories.add(new FutureInventory(System.nanoTime() / 1000000L, size, lockable.getGuiID(), position));
|
||||
}
|
||||
}
|
||||
|
||||
if (p instanceof CPacketCloseWindow) {
|
||||
getCurrent().save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void onReceivePacket(PacketEvent event) {
|
||||
if (!Baritone.settings().containerMemory.value) {
|
||||
return;
|
||||
}
|
||||
Packet p = event.getPacket();
|
||||
|
||||
if (event.getState() == EventState.PRE) {
|
||||
if (p instanceof SPacketOpenWindow) {
|
||||
SPacketOpenWindow packet = event.cast();
|
||||
// Remove any entries that were created over a second ago, this should make up for INSANE latency
|
||||
futureInventories.removeIf(i -> System.nanoTime() / 1000000L - i.time > 1000);
|
||||
|
||||
System.out.println("Received packet " + packet.getGuiId() + " " + packet.getEntityId() + " " + packet.getSlotCount() + " " + packet.getWindowId());
|
||||
System.out.println(packet.getWindowTitle());
|
||||
if (packet.getWindowTitle() instanceof TextComponentTranslation && ((TextComponentTranslation) packet.getWindowTitle()).getKey().equals("container.enderchest")) {
|
||||
// title is not customized (i.e. this isn't just a renamed shulker)
|
||||
enderChestWindowId = packet.getWindowId();
|
||||
return;
|
||||
}
|
||||
futureInventories.stream()
|
||||
.filter(i -> i.type.equals(packet.getGuiId()) && i.slots == packet.getSlotCount())
|
||||
.findFirst().ifPresent(matched -> {
|
||||
// Remove the future inventory
|
||||
futureInventories.remove(matched);
|
||||
|
||||
// Setup the remembered inventory
|
||||
getCurrentContainer().setup(matched.pos, packet.getWindowId(), packet.getSlotCount());
|
||||
});
|
||||
}
|
||||
|
||||
if (p instanceof SPacketCloseWindow) {
|
||||
getCurrent().save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBlockInteract(BlockInteractEvent event) {
|
||||
if (event.getType() == BlockInteractEvent.Type.USE && BlockStateInterface.getBlock(ctx, event.getPos()) instanceof BlockBed) {
|
||||
baritone.getWorldProvider().getCurrentWorld().getWaypoints().addWaypoint(new Waypoint("bed", Waypoint.Tag.BED, BetterBlockPos.from(event.getPos())));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlayerDeath() {
|
||||
Waypoint deathWaypoint = new Waypoint("death", Waypoint.Tag.DEATH, ctx.playerFeet());
|
||||
baritone.getWorldProvider().getCurrentWorld().getWaypoints().addWaypoint(deathWaypoint);
|
||||
ITextComponent component = new TextComponentString("Death position saved.");
|
||||
component.getStyle()
|
||||
.setColor(TextFormatting.WHITE)
|
||||
.setHoverEvent(new HoverEvent(
|
||||
HoverEvent.Action.SHOW_TEXT,
|
||||
new TextComponentString("Click to goto death")
|
||||
))
|
||||
.setClickEvent(new ClickEvent(
|
||||
ClickEvent.Action.RUN_COMMAND,
|
||||
String.format(
|
||||
"%s%s goto %s @ %d",
|
||||
FORCE_COMMAND_PREFIX,
|
||||
"wp",
|
||||
deathWaypoint.getTag().getName(),
|
||||
deathWaypoint.getCreationTimestamp()
|
||||
)
|
||||
));
|
||||
Helper.HELPER.logDirect(component);
|
||||
}
|
||||
|
||||
|
||||
private void updateInventory() {
|
||||
if (!Baritone.settings().containerMemory.value) {
|
||||
return;
|
||||
}
|
||||
int windowId = ctx.player().openContainer.windowId;
|
||||
if (enderChestWindowId != null) {
|
||||
if (windowId == enderChestWindowId) {
|
||||
getCurrent().contents = ctx.player().openContainer.getInventory().subList(0, 27);
|
||||
} else {
|
||||
getCurrent().save();
|
||||
enderChestWindowId = null;
|
||||
}
|
||||
}
|
||||
if (getCurrentContainer() != null) {
|
||||
getCurrentContainer().getInventoryFromWindow(windowId).ifPresent(inventory -> inventory.updateFromOpenWindow(ctx));
|
||||
}
|
||||
}
|
||||
|
||||
private ContainerMemory getCurrentContainer() {
|
||||
if (baritone.getWorldProvider().getCurrentWorld() == null) {
|
||||
return null;
|
||||
}
|
||||
return (ContainerMemory) baritone.getWorldProvider().getCurrentWorld().getContainerMemory();
|
||||
}
|
||||
|
||||
private BlockPos neighboringConnectedBlock(BlockPos in) {
|
||||
BlockStateInterface bsi = baritone.bsi;
|
||||
Block block = bsi.get0(in).getBlock();
|
||||
if (block != Blocks.TRAPPED_CHEST && block != Blocks.CHEST) {
|
||||
return null; // other things that have contents, but can be placed adjacent without combining
|
||||
}
|
||||
for (int i = 0; i < 4; i++) {
|
||||
BlockPos adj = in.offset(EnumFacing.byHorizontalIndex(i));
|
||||
if (bsi.get0(adj).getBlock() == block) {
|
||||
return adj;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* An inventory that we are not yet fully aware of, but are expecting to exist at some point in the future.
|
||||
*/
|
||||
private static final class FutureInventory {
|
||||
|
||||
/**
|
||||
* The time that we initially expected the inventory to be provided, in milliseconds
|
||||
*/
|
||||
private final long time;
|
||||
|
||||
/**
|
||||
* The amount of slots in the inventory
|
||||
*/
|
||||
private final int slots;
|
||||
|
||||
/**
|
||||
* The type of inventory
|
||||
*/
|
||||
private final String type;
|
||||
|
||||
/**
|
||||
* The position of the inventory container
|
||||
*/
|
||||
private final BlockPos pos;
|
||||
|
||||
private FutureInventory(long time, int slots, String type, BlockPos pos) {
|
||||
this.time = time;
|
||||
this.slots = slots;
|
||||
this.type = type;
|
||||
this.pos = pos;
|
||||
// betterblockpos has censoring
|
||||
System.out.println("Future inventory created " + time + " " + slots + " " + type + " " + BetterBlockPos.from(pos));
|
||||
}
|
||||
}
|
||||
|
||||
public Optional<List<ItemStack>> echest() {
|
||||
return Optional.ofNullable(getCurrent().contents).map(Collections::unmodifiableList);
|
||||
}
|
||||
|
||||
public EnderChestMemory getCurrent() {
|
||||
Path path = baritone.getWorldProvider().getCurrentWorld().directory;
|
||||
return EnderChestMemory.getByServerAndPlayer(path.getParent(), ctx.player().getUniqueID());
|
||||
}
|
||||
|
||||
public static class EnderChestMemory {
|
||||
|
||||
private static final Map<Path, EnderChestMemory> memory = new HashMap<>();
|
||||
private final Path enderChest;
|
||||
private List<ItemStack> contents;
|
||||
|
||||
private EnderChestMemory(Path enderChest) {
|
||||
this.enderChest = enderChest;
|
||||
System.out.println("Echest storing in " + enderChest);
|
||||
try {
|
||||
this.contents = ContainerMemory.readItemStacks(Files.readAllBytes(enderChest));
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
System.out.println("CANNOT read echest =( =(");
|
||||
this.contents = null;
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void save() {
|
||||
System.out.println("Saving");
|
||||
if (contents != null) {
|
||||
try {
|
||||
enderChest.getParent().toFile().mkdir();
|
||||
Files.write(enderChest, ContainerMemory.writeItemStacks(contents));
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
System.out.println("CANNOT save echest =( =(");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static synchronized EnderChestMemory getByServerAndPlayer(Path serverStorage, UUID player) {
|
||||
return memory.computeIfAbsent(serverStorage.resolve("echests").resolve(player.toString()), EnderChestMemory::new);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -99,6 +99,7 @@ public final class PathingBehavior extends Behavior implements IPathingBehavior,
|
||||
baritone.getPathingControlManager().cancelEverything();
|
||||
return;
|
||||
}
|
||||
|
||||
expectedSegmentStart = pathStart();
|
||||
baritone.getPathingControlManager().preTick();
|
||||
tickPath();
|
||||
|
||||
92
src/main/java/baritone/behavior/WaypointBehavior.java
Normal file
92
src/main/java/baritone/behavior/WaypointBehavior.java
Normal file
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* This file is part of Baritone.
|
||||
*
|
||||
* Baritone is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Baritone is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Baritone. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package baritone.behavior;
|
||||
|
||||
import baritone.Baritone;
|
||||
import baritone.api.cache.IWaypoint;
|
||||
import baritone.api.cache.Waypoint;
|
||||
import baritone.api.event.events.BlockInteractEvent;
|
||||
import baritone.api.utils.BetterBlockPos;
|
||||
import baritone.api.utils.Helper;
|
||||
import baritone.utils.BlockStateInterface;
|
||||
import net.minecraft.block.BlockBed;
|
||||
import net.minecraft.block.state.IBlockState;
|
||||
import net.minecraft.util.text.ITextComponent;
|
||||
import net.minecraft.util.text.TextComponentString;
|
||||
import net.minecraft.util.text.TextFormatting;
|
||||
import net.minecraft.util.text.event.ClickEvent;
|
||||
import net.minecraft.util.text.event.HoverEvent;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import static baritone.api.command.IBaritoneChatControl.FORCE_COMMAND_PREFIX;
|
||||
|
||||
public class WaypointBehavior extends Behavior {
|
||||
|
||||
|
||||
public WaypointBehavior(Baritone baritone) {
|
||||
super(baritone);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBlockInteract(BlockInteractEvent event) {
|
||||
if (!Baritone.settings().doBedWaypoints.value)
|
||||
return;
|
||||
if (event.getType() == BlockInteractEvent.Type.USE) {
|
||||
BetterBlockPos pos = BetterBlockPos.from(event.getPos());
|
||||
IBlockState state = BlockStateInterface.get(ctx, pos);
|
||||
if (state.getBlock() instanceof BlockBed) {
|
||||
if (state.getValue(BlockBed.PART) == BlockBed.EnumPartType.FOOT) {
|
||||
pos = pos.offset(state.getValue(BlockBed.FACING));
|
||||
}
|
||||
Set<IWaypoint> waypoints = baritone.getWorldProvider().getCurrentWorld().getWaypoints().getByTag(IWaypoint.Tag.BED);
|
||||
boolean exists = waypoints.stream().map(IWaypoint::getLocation).filter(pos::equals).findFirst().isPresent();
|
||||
if (!exists) {
|
||||
baritone.getWorldProvider().getCurrentWorld().getWaypoints().addWaypoint(new Waypoint("bed", Waypoint.Tag.BED, pos));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlayerDeath() {
|
||||
if (!Baritone.settings().doDeathWaypoints.value)
|
||||
return;
|
||||
Waypoint deathWaypoint = new Waypoint("death", Waypoint.Tag.DEATH, ctx.playerFeet());
|
||||
baritone.getWorldProvider().getCurrentWorld().getWaypoints().addWaypoint(deathWaypoint);
|
||||
ITextComponent component = new TextComponentString("Death position saved.");
|
||||
component.getStyle()
|
||||
.setColor(TextFormatting.WHITE)
|
||||
.setHoverEvent(new HoverEvent(
|
||||
HoverEvent.Action.SHOW_TEXT,
|
||||
new TextComponentString("Click to goto death")
|
||||
))
|
||||
.setClickEvent(new ClickEvent(
|
||||
ClickEvent.Action.RUN_COMMAND,
|
||||
String.format(
|
||||
"%s%s goto %s @ %d",
|
||||
FORCE_COMMAND_PREFIX,
|
||||
"wp",
|
||||
deathWaypoint.getTag().getName(),
|
||||
deathWaypoint.getCreationTimestamp()
|
||||
)
|
||||
));
|
||||
Helper.HELPER.logDirect(component);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
/*
|
||||
* This file is part of Baritone.
|
||||
*
|
||||
* Baritone is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Baritone is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Baritone. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package baritone.builder;
|
||||
|
||||
import baritone.api.utils.IPlayerContext;
|
||||
|
||||
/**
|
||||
* 1/16th of a block
|
||||
* <p>
|
||||
* Why do this? Several reasons:
|
||||
* <p>
|
||||
* • No floating point inaccuracy. I got incredibly annoyed with simple stuff like >1 and <1 being randomly wrong. It helps with stuff like slab calculations, 0.5 block steps, etc
|
||||
* <p>
|
||||
* • It's obscenely fast
|
||||
*/
|
||||
public class Blip {
|
||||
|
||||
public static final int FULL_BLOCK = 16;
|
||||
public static final double RATIO = 0.0625;
|
||||
public static final int HALF_BLOCK = 8;
|
||||
public static final int PLAYER_HEIGHT_SLIGHT_UNDERESTIMATE = 28;
|
||||
public static final int PLAYER_HEIGHT_SLIGHT_OVERESTIMATE = 29;
|
||||
public static final int TWO_BLOCKS = 2 * FULL_BLOCK;
|
||||
public static final int FEET_TO_EYE_APPROX = (int) (IPlayerContext.eyeHeight(false) / RATIO);
|
||||
public static final int JUMP = 20; // 1.25
|
||||
public static final int TALLEST_BLOCK = FULL_BLOCK + HALF_BLOCK; // 24, 1.5 blocks tall, the fence / wall has the highest collision box
|
||||
|
||||
public static double playerEyeFromFeetBlips(int feetBlips, boolean sneaking) {
|
||||
return feetBlips * RATIO + IPlayerContext.eyeHeight(sneaking);
|
||||
}
|
||||
|
||||
static {
|
||||
double realPlayerHeight = 1.8;
|
||||
if (PLAYER_HEIGHT_SLIGHT_OVERESTIMATE * RATIO <= realPlayerHeight || PLAYER_HEIGHT_SLIGHT_UNDERESTIMATE * RATIO >= realPlayerHeight || PLAYER_HEIGHT_SLIGHT_OVERESTIMATE != PLAYER_HEIGHT_SLIGHT_UNDERESTIMATE + 1) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
if (FULL_BLOCK * RATIO != 1) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
/*
|
||||
* This file is part of Baritone.
|
||||
*
|
||||
* Baritone is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Baritone is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Baritone. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package baritone.builder;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Information about an IBlockState
|
||||
* <p>
|
||||
* There will be exactly one of these per valid IBlockState in the game
|
||||
*/
|
||||
public final class BlockStateCachedData {
|
||||
public final boolean fullyWalkableTop;
|
||||
private final int collisionHeightBlips;
|
||||
public final boolean isAir;
|
||||
|
||||
public final boolean collidesWithPlayer;
|
||||
|
||||
public final boolean mustSneakWhenPlacingAgainstMe;
|
||||
|
||||
public final List<BlockStatePlacementOption> placeMe; // list because of unknown size with no obvious indexing
|
||||
|
||||
public final PlaceAgainstData[] placeAgainstMe; // array because of fixed size with obvious indexing (no more than one per face, so, index per face)
|
||||
|
||||
public BlockStateCachedData(BlockStateCachedDataBuilder builder) {
|
||||
builder.sanityCheck();
|
||||
this.isAir = builder.isAir();
|
||||
this.fullyWalkableTop = builder.isFullyWalkableTop();
|
||||
this.collidesWithPlayer = builder.isCollidesWithPlayer();
|
||||
if (collidesWithPlayer) {
|
||||
this.collisionHeightBlips = builder.collisionHeightBlips();
|
||||
} else {
|
||||
this.collisionHeightBlips = -1;
|
||||
}
|
||||
|
||||
this.mustSneakWhenPlacingAgainstMe = builder.isMustSneakWhenPlacingAgainstMe();
|
||||
this.placeMe = Collections.unmodifiableList(builder.howCanIBePlaced());
|
||||
|
||||
this.placeAgainstMe = builder.placeAgainstMe();
|
||||
}
|
||||
|
||||
public int collisionHeightBlips() {
|
||||
if (Main.DEBUG && !collidesWithPlayer) { // confirmed and tested: when DEBUG is false, proguard removes this if in the first pass, then inlines the calls in the second pass, making this just as good as a field access in release builds
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
return collisionHeightBlips;
|
||||
}
|
||||
|
||||
public boolean possibleAgainstMe(BlockStatePlacementOption placement) {
|
||||
PlaceAgainstData against = againstMe(placement);
|
||||
return against != null && possible(placement, against);
|
||||
}
|
||||
|
||||
public PlaceAgainstData againstMe(BlockStatePlacementOption placement) {
|
||||
return placeAgainstMe[placement.against.oppositeIndex];
|
||||
}
|
||||
|
||||
public static boolean possible(BlockStatePlacementOption placement, PlaceAgainstData against) {
|
||||
if (placement.against != against.against.opposite()) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
if (placement.against.vertical) {
|
||||
return true;
|
||||
}
|
||||
return
|
||||
(against.presentsAnOptionStrictlyInTheBottomHalfOfTheStandardVoxelPlane() && placement.half != Half.TOP) ||
|
||||
(against.presentsAnOptionStrictlyInTheTopHalfOfTheStandardVoxelPlane() && placement.half != Half.BOTTOM);
|
||||
}
|
||||
}
|
||||
@@ -1,326 +0,0 @@
|
||||
/*
|
||||
* This file is part of Baritone.
|
||||
*
|
||||
* Baritone is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Baritone is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Baritone. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package baritone.builder;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public class BlockStateCachedDataBuilder {
|
||||
|
||||
// should all these be optionals? like maybe Boolean? that start as null? and each has to be set explicitly?
|
||||
private boolean isAir;
|
||||
private boolean canPlaceAgainstMe;
|
||||
private boolean fullyWalkableTop;
|
||||
private boolean collidesWithPlayer;
|
||||
private boolean mustSneakWhenPlacingAgainstMe;
|
||||
private boolean mustBePlacedBottomToTop;
|
||||
/**
|
||||
* Examples:
|
||||
* <p>
|
||||
* Upside down stairs must be placed against TOP
|
||||
* <p>
|
||||
* Bottom slabs must be placed against BOTTOM
|
||||
* <p>
|
||||
* Normal blocks must be placed against EITHER
|
||||
*/
|
||||
private Half mustBePlacedAgainst = Half.EITHER;
|
||||
private Face playerMustBeHorizontalFacingInOrderToPlaceMe;
|
||||
private Integer collisionHeightBlips;
|
||||
private Face canOnlyPlaceAgainst;
|
||||
/**
|
||||
* Blocks that have a collision height lower than their placement bounding box height.
|
||||
* aka snow layers and soul sand.
|
||||
* e.g. soul sand only collides with the player 0.875 high, but the BLOCK is 1.000 high, i.e. you can place against it at height 0.9 even though the player stands at height 0.875
|
||||
*/
|
||||
private boolean fakeLessThanFullHeight;
|
||||
private boolean placementLogicNotImplementedYet;
|
||||
private Face playerMustBeEntityFacingInOrderToPlaceMe;
|
||||
|
||||
public BlockStateCachedDataBuilder() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Really just air. This is a fully open block that won't collide with object mouse over raytrace.
|
||||
*/
|
||||
public BlockStateCachedDataBuilder setAir() {
|
||||
isAir = true;
|
||||
mustBePlacedAgainst = null;
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean isAir() {
|
||||
return isAir;
|
||||
}
|
||||
|
||||
/**
|
||||
* does the top face of this block fully support the player from 0.0,0.0 to 1.0,1.0? true for most normal blocks. false for, for example, fences
|
||||
*/
|
||||
public BlockStateCachedDataBuilder fullyWalkableTop() {
|
||||
fullyWalkableTop = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean isFullyWalkableTop() {
|
||||
return fullyWalkableTop;
|
||||
}
|
||||
|
||||
/**
|
||||
* The highest collision extension of this block possible
|
||||
* <p>
|
||||
* For example, should be 1 for stairs, even though part of the top face is really 0.5
|
||||
* <p>
|
||||
* Should be 1 for top slabs
|
||||
* <p>
|
||||
* Should be 1 for trapdoors because when they're open, they touch the top face of the voxel
|
||||
*/
|
||||
public BlockStateCachedDataBuilder collisionHeight(double y) {
|
||||
for (int h = 0; h <= Blip.TALLEST_BLOCK; h++) { // max height of
|
||||
if (y == h * Blip.RATIO) {
|
||||
collisionHeightBlips = h;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
public Integer collisionHeightBlips() { // e.g. slabs are 0.5, soul sand is 0.875, normal blocks are 1, fences are 1.5
|
||||
return collisionHeightBlips;
|
||||
}
|
||||
|
||||
public BlockStateCachedDataBuilder mustSneakWhenPlacingAgainstMe() {
|
||||
mustSneakWhenPlacingAgainstMe = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean isMustSneakWhenPlacingAgainstMe() {
|
||||
return mustSneakWhenPlacingAgainstMe;
|
||||
}
|
||||
|
||||
public BlockStateCachedDataBuilder canPlaceAgainstMe() {
|
||||
canPlaceAgainstMe = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean isCollidesWithPlayer() {
|
||||
return collidesWithPlayer;
|
||||
}
|
||||
|
||||
public BlockStateCachedDataBuilder collidesWithPlayer(boolean val) {
|
||||
collidesWithPlayer = val;
|
||||
return this;
|
||||
}
|
||||
|
||||
public BlockStateCachedDataBuilder playerMustBeHorizontalFacingInOrderToPlaceMe(Face face) {
|
||||
playerMustBeHorizontalFacingInOrderToPlaceMe = face;
|
||||
return this;
|
||||
}
|
||||
|
||||
public BlockStateCachedDataBuilder playerMustBeEntityFacingInOrderToPlaceMe(Face face) {
|
||||
playerMustBeEntityFacingInOrderToPlaceMe = face;
|
||||
return this;
|
||||
}
|
||||
|
||||
public BlockStateCachedDataBuilder mustBePlacedAgainst(Half half) {
|
||||
if (half == null) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
mustBePlacedAgainst = half;
|
||||
return this;
|
||||
}
|
||||
|
||||
public BlockStateCachedDataBuilder mustBePlacedBottomToTop() {
|
||||
mustBePlacedBottomToTop = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public BlockStateCachedDataBuilder canOnlyPlaceAgainst(Face face) {
|
||||
canOnlyPlaceAgainst = face;
|
||||
return this;
|
||||
}
|
||||
|
||||
public BlockStateCachedDataBuilder placementLogicNotImplementedYet() {
|
||||
placementLogicNotImplementedYet = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public BlockStateCachedDataBuilder fakeLessThanFullHeight() {
|
||||
fakeLessThanFullHeight = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public List<BlockStatePlacementOption> howCanIBePlaced() {
|
||||
if (mustBePlacedAgainst == null || placementLogicNotImplementedYet) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
List<BlockStatePlacementOption> ret = new ArrayList<>();
|
||||
for (Face face : Face.VALUES) {
|
||||
if (Main.STRICT_Y && face == Face.UP) {
|
||||
continue; // TODO don't do this...
|
||||
}
|
||||
if (playerMustBeHorizontalFacingInOrderToPlaceMe == face.opposite()) { // obv, this won't happen if playerMustBeHorizontalFacing is null
|
||||
continue;
|
||||
}
|
||||
if (playerMustBeEntityFacingInOrderToPlaceMe == face) {
|
||||
continue;
|
||||
}
|
||||
if (mustBePlacedBottomToTop && face != Face.DOWN) {
|
||||
continue;
|
||||
}
|
||||
if (canOnlyPlaceAgainst != null && face != canOnlyPlaceAgainst) {
|
||||
continue;
|
||||
}
|
||||
Half overrideHalf = mustBePlacedAgainst;
|
||||
if (face == Face.DOWN) {
|
||||
if (mustBePlacedAgainst == Half.TOP) {
|
||||
continue;
|
||||
} else {
|
||||
overrideHalf = Half.EITHER;
|
||||
}
|
||||
}
|
||||
if (face == Face.UP) {
|
||||
if (mustBePlacedAgainst == Half.BOTTOM) {
|
||||
continue;
|
||||
} else {
|
||||
overrideHalf = Half.EITHER;
|
||||
}
|
||||
}
|
||||
ret.add(BlockStatePlacementOption.get(face, overrideHalf, Optional.ofNullable(playerMustBeHorizontalFacingInOrderToPlaceMe), Optional.ofNullable(playerMustBeEntityFacingInOrderToPlaceMe)));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
public PlaceAgainstData[] placeAgainstMe() {
|
||||
PlaceAgainstData[] data = new PlaceAgainstData[Face.NUM_FACES];
|
||||
if (!canPlaceAgainstMe) {
|
||||
return data;
|
||||
}
|
||||
for (int i = 0; i < Face.NUM_FACES; i++) {
|
||||
data[i] = placeAgainstFace(Face.VALUES[i]);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
protected PlaceAgainstData placeAgainstFace(Face face) {
|
||||
// TODO this makes the stair/slab assumption that the same half is the mustBePlacedAgainst as the faces offered for placement... counterexample is daylight sensor
|
||||
// note that this is actually correct behavior for stairs - you can place against the top half of a upside down stair and against the bottom half of a normal stair
|
||||
if (mustBePlacedAgainst == Half.TOP && face == Face.DOWN) {
|
||||
return null;
|
||||
}
|
||||
if (mustBePlacedAgainst == Half.BOTTOM && face == Face.UP) {
|
||||
return null;
|
||||
}
|
||||
return new PlaceAgainstData(face, face.vertical ? Half.EITHER : mustBePlacedAgainst, mustSneakWhenPlacingAgainstMe);
|
||||
}
|
||||
|
||||
/**
|
||||
* The idea here is that I codify all my assumptions in one place instead of having ad hoc checks absolutely everywhere
|
||||
* <p>
|
||||
* Example: in PlayerPhysics, I made an assumption that a block will never have a collision block taller than 1.5 blocks (e.g. like a fence)
|
||||
* When I wrote the code that assumed that, I also added a check here to make sure every block is like that.
|
||||
* If, in some future update to Minecraft, mojang adds a block that's even taller than a fence, it will be caught here immediately, with a comment saying "playerphysics assumes this is never true"
|
||||
* This way, I'll know immediately, instead of pathing randomly trying to do something impossible with that new block and it being really confusing and annoying.
|
||||
*/
|
||||
public void sanityCheck() {
|
||||
if (isAir()) {
|
||||
if (!howCanIBePlaced().isEmpty()) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
if (isFullyWalkableTop()) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
if (collidesWithPlayer) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
if (mustBePlacedAgainst == null ^ isAir()) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
if (howCanIBePlaced().isEmpty()) {
|
||||
if (mustBePlacedAgainst != null && !placementLogicNotImplementedYet) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
if (playerMustBeHorizontalFacingInOrderToPlaceMe != null || playerMustBeEntityFacingInOrderToPlaceMe != null) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
if (canOnlyPlaceAgainst != null) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
if (isMustSneakWhenPlacingAgainstMe() && mustBePlacedAgainst != Half.EITHER) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
if ((playerMustBeHorizontalFacingInOrderToPlaceMe != null || playerMustBeEntityFacingInOrderToPlaceMe != null) && mustBePlacedAgainst == null) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
if (collisionHeightBlips != null && (collisionHeightBlips > Blip.TALLEST_BLOCK || collisionHeightBlips < 0)) { // playerphysics assumes this is never true
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
if (collidesWithPlayer ^ collisionHeightBlips != null) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
if (fullyWalkableTop && !collidesWithPlayer) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
if (canPlaceAgainstMe && !collidesWithPlayer) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
if (playerMustBeHorizontalFacingInOrderToPlaceMe != null && playerMustBeHorizontalFacingInOrderToPlaceMe.vertical) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
if (Main.STRICT_Y && howCanIBePlaced().stream().anyMatch(opt -> opt.against == Face.UP)) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
PlaceAgainstData[] data = placeAgainstMe();
|
||||
if (data.length != Face.NUM_FACES) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
boolean any = false;
|
||||
for (int i = 0; i < Face.NUM_FACES; i++) {
|
||||
if (data[i] != null) {
|
||||
if (data[i].against != Face.VALUES[i]) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
if (!canPlaceAgainstMe) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
any = true;
|
||||
}
|
||||
}
|
||||
if (canPlaceAgainstMe && !any) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
if (collisionHeightBlips != null && !fakeLessThanFullHeight) {
|
||||
for (PlaceAgainstData d : data) {
|
||||
if (d == null) {
|
||||
continue;
|
||||
}
|
||||
d.streamRelativeToMyself().forEach(hit -> {
|
||||
if (hit.y > collisionHeightBlips * Blip.RATIO) {
|
||||
throw new IllegalStateException(d.against + " " + hit.y + " " + collisionHeightBlips * Blip.RATIO);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static {
|
||||
new BlockStateCachedDataBuilder().sanityCheck();
|
||||
}
|
||||
}
|
||||
@@ -1,236 +0,0 @@
|
||||
/*
|
||||
* This file is part of Baritone.
|
||||
*
|
||||
* Baritone is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Baritone is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Baritone. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package baritone.builder;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* A plane against which this block state can be placed
|
||||
* <p>
|
||||
* For a normal block, this will be a full face of a block. In that case, this class is no more than an EnumFacing
|
||||
* <p>
|
||||
* For a block like a slab or a stair, this will contain the information that the placement must be against the top or bottom half of the face
|
||||
* <p>
|
||||
* For a block like a furnace, this will contain the information that the player must be facing a specific horizontal direction in order to get the desired orientation
|
||||
* <p>
|
||||
* For a block like a piston, dispenser, or observer, this will contain the information that be player must pass a combination of: specific relative eye coordinate, specific relative X Z, and specific horizontal facing
|
||||
*/
|
||||
public class BlockStatePlacementOption {
|
||||
|
||||
/**
|
||||
* e.g. a torch placed down on the ground is placed against the bottom of "the torch bounding box", so this would be DOWN for the torch
|
||||
*/
|
||||
public final Face against;
|
||||
public final Half half;
|
||||
public final Optional<Face> playerMustBeHorizontalFacing; // getHorizontalFacing
|
||||
/**
|
||||
* IMPORTANT this is the RAW getDirectionFromEntityLiving meaning that it is the OPPOSITE of getHorizontalFacing (when in the horizontal plane)
|
||||
*/
|
||||
public final Optional<Face> playerMustBeEntityFacing; // EnumFacing.getDirectionFromEntityLiving, used by piston, dispenser, observer
|
||||
|
||||
private BlockStatePlacementOption(Face against, Half half, Optional<Face> playerMustBeHorizontalFacing, Optional<Face> playerMustBeEntityFacing) {
|
||||
Objects.requireNonNull(against);
|
||||
Objects.requireNonNull(half);
|
||||
this.against = against;
|
||||
this.half = half;
|
||||
this.playerMustBeHorizontalFacing = playerMustBeHorizontalFacing;
|
||||
this.playerMustBeEntityFacing = playerMustBeEntityFacing;
|
||||
validate(against, half, playerMustBeHorizontalFacing, playerMustBeEntityFacing);
|
||||
}
|
||||
|
||||
/**
|
||||
* This value must be greater than the face projections.
|
||||
* <p>
|
||||
* Otherwise certain stair placements would not work. This is verified in the test
|
||||
*/
|
||||
public static final double LOOSE_CENTER_DISTANCE = 0.15;
|
||||
|
||||
public List<Raytracer.Raytrace> computeTraceOptions(PlaceAgainstData placingAgainst, int playerSupportingX, int playerFeetBlips, int playerSupportingZ, PlayerVantage vantage, double blockReachDistance) {
|
||||
if (!BlockStateCachedData.possible(this, placingAgainst)) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
if (Main.DEBUG && placingAgainst.streamRelativeToPlace().noneMatch(hit -> hitOk(half, hit))) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
List<Vec2d> acceptableVantages = new ArrayList<>();
|
||||
Vec2d center = Vec2d.HALVED_CENTER.plus(playerSupportingX, playerSupportingZ);
|
||||
switch (vantage) {
|
||||
case LOOSE_CENTER: {
|
||||
acceptableVantages.add(center.plus(LOOSE_CENTER_DISTANCE, 0));
|
||||
acceptableVantages.add(center.plus(-LOOSE_CENTER_DISTANCE, 0));
|
||||
acceptableVantages.add(center.plus(0, LOOSE_CENTER_DISTANCE));
|
||||
acceptableVantages.add(center.plus(0, -LOOSE_CENTER_DISTANCE));
|
||||
// no break!
|
||||
} // FALLTHROUGH!
|
||||
case STRICT_CENTER: {
|
||||
acceptableVantages.add(center);
|
||||
break;
|
||||
}
|
||||
case SNEAK_BACKPLACE: {
|
||||
if (playerSupportingX != against.x || playerSupportingZ != against.z) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
// in a sneak backplace, there is exactly one location where the player will be
|
||||
acceptableVantages.add(Vec2d.HALVED_CENTER.plus(0.25 * against.x, 0.25 * against.z));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
// direction from placed block to place-against block = this.against
|
||||
long blockPlacedAt = 0;
|
||||
long placeAgainstPos = against.offset(blockPlacedAt);
|
||||
|
||||
return sanityCheckTraces(acceptableVantages
|
||||
.stream()
|
||||
.map(playerEyeXZ -> new Vec3d(playerEyeXZ.x, Blip.playerEyeFromFeetBlips(playerFeetBlips, placingAgainst.mustSneak), playerEyeXZ.z))
|
||||
.flatMap(eye ->
|
||||
placingAgainst.streamRelativeToPlace()
|
||||
.filter(hit -> hitOk(half, hit))
|
||||
.filter(hit -> eye.distSq(hit) < blockReachDistance * blockReachDistance)
|
||||
.filter(hit -> directionOk(eye, hit))
|
||||
.<Supplier<Optional<Raytracer.Raytrace>>>map(hit -> () -> Raytracer.runTrace(eye, placeAgainstPos, against.opposite(), hit))
|
||||
)
|
||||
.collect(Collectors.toList())
|
||||
.parallelStream() // wrap it like this because flatMap forces .sequential() on the interior child stream, defeating the point
|
||||
.map(Supplier::get)
|
||||
.filter(Optional::isPresent)
|
||||
.map(Optional::get)
|
||||
.sorted()
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
public static boolean hitOk(Half half, Vec3d hit) {
|
||||
if (half == Half.EITHER) {
|
||||
return true;
|
||||
} else if (hit.y == 0.1) {
|
||||
return half == Half.BOTTOM;
|
||||
} else if (hit.y == 0.5) {
|
||||
return false; // ambiguous, so force it to pick either down or up
|
||||
} else if (hit.y == 0.9) {
|
||||
return half == Half.TOP;
|
||||
} else {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* In EnumFacing.getDirectionFromEntityLiving, it checks if the player feet is within 2 blocks of the center of the block to be placed.
|
||||
* Normally, this is a nonissue, but a problem arises because we are considering hypothetical placements where the player stands at the exact +0.5,+0.5 center of a block.
|
||||
* In that case, it's possible for our hypothetical to have the player at precisely 2 blocks away, i.e. precisely on the edge of this condition being true or false.
|
||||
* For that reason, we treat those exact cases as "ambiguous". So, if the distance is within this tolerance of 2 (so, 1.99 to 2.01), we treat it as a "could go either way",
|
||||
* because when we really get there in-game, floating point inaccuracy could indeed actually make it go either way.
|
||||
*/
|
||||
private static final double ENTITY_FACING_TOLERANCE = 0.01;
|
||||
|
||||
private boolean directionOk(Vec3d eye, Vec3d hit) {
|
||||
if (playerMustBeHorizontalFacing.isPresent()) {
|
||||
return eye.flatDirectionTo(hit) == playerMustBeHorizontalFacing.get();
|
||||
}
|
||||
if (playerMustBeEntityFacing.isPresent()) { // handle piston, dispenser, dropper, observer
|
||||
if (!hit.inOriginUnitVoxel()) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
Face entFace = playerMustBeEntityFacing.get();
|
||||
// see EnumFacing.getDirectionFromEntityLiving
|
||||
double dx = Math.abs(eye.x - 0.5); // TODO this is changed between 1.12 and 1.19, in 1.19 this should be eye.x-hit.x
|
||||
double dz = Math.abs(eye.z - 0.5);
|
||||
if (dx < 2 - ENTITY_FACING_TOLERANCE && dz < 2 - ENTITY_FACING_TOLERANCE) { // < 1.99
|
||||
if (eye.y < 0) { // eye below placement level = it will be facing down, so this is only okay if we want that
|
||||
return entFace == Face.DOWN;
|
||||
}
|
||||
if (eye.y > 2) { // same for up, if y>2 then it will be facing up
|
||||
return entFace == Face.UP;
|
||||
}
|
||||
} else if (!(dx > 2 + ENTITY_FACING_TOLERANCE || dz > 2 + ENTITY_FACING_TOLERANCE)) { // > 2.01
|
||||
// this is the ambiguous case, because we are neither unambiguously both-within-2 (previous case), nor unambiguously either-above-two (this elseif condition).
|
||||
// UP/DOWN are impossible, but that's caught by flat check
|
||||
if (eye.y < 0 || eye.y > 2) { // this check is okay because player eye height is not an even multiple of blips, therefore there's no way for it to == 0 or == 2, so using > and < is safe
|
||||
return false; // anything that could cause up/down instead of horizontal is also not allowed sadly
|
||||
}
|
||||
} // else we are in unambiguous either-above-two, putting us in simple horizontal mode, so fallthrough to flat condition is correct, yay
|
||||
return eye.flatDirectionTo(hit) == entFace.opposite();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static BlockStatePlacementOption get(Face against, Half half, Optional<Face> playerMustBeHorizontalFacing, Optional<Face> playerMustBeEntityFacing) {
|
||||
BlockStatePlacementOption ret = PLACEMENT_OPTION_SINGLETON_CACHE[against.index][half.ordinal()][Face.OPTS.indexOf(playerMustBeHorizontalFacing)][Face.OPTS.indexOf(playerMustBeEntityFacing)];
|
||||
if (ret == null) {
|
||||
throw new IllegalStateException(against + " " + half + " " + playerMustBeHorizontalFacing + " " + playerMustBeEntityFacing);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static final BlockStatePlacementOption[][][][] PLACEMENT_OPTION_SINGLETON_CACHE;
|
||||
|
||||
static {
|
||||
PLACEMENT_OPTION_SINGLETON_CACHE = new BlockStatePlacementOption[Face.NUM_FACES][Half.values().length][Face.OPTS.size()][Face.OPTS.size()];
|
||||
for (Face against : Face.VALUES) {
|
||||
for (Half half : Half.values()) {
|
||||
for (Optional<Face> horizontalFacing : Face.OPTS) {
|
||||
for (Optional<Face> entityFacing : Face.OPTS) {
|
||||
try {
|
||||
PLACEMENT_OPTION_SINGLETON_CACHE[against.index][half.ordinal()][Face.OPTS.indexOf(horizontalFacing)][Face.OPTS.indexOf(entityFacing)] = new BlockStatePlacementOption(against, half, horizontalFacing, entityFacing);
|
||||
} catch (RuntimeException ex) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void validate(Face against, Half half, Optional<Face> playerMustBeHorizontalFacing, Optional<Face> playerMustBeEntityFacing) {
|
||||
if (playerMustBeEntityFacing.isPresent() && playerMustBeHorizontalFacing.isPresent()) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
if (against.vertical && half != Half.EITHER) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
if (Main.STRICT_Y && against == Face.UP) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
playerMustBeHorizontalFacing.ifPresent(face -> {
|
||||
if (face.vertical) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
if (face == against.opposite()) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
});
|
||||
playerMustBeEntityFacing.ifPresent(face -> {
|
||||
if (half != Half.EITHER) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
if (against == face) { // impossible because EnumFacing inverts the horizontal facing AND because the down and up require the eye to be <0 and >2 respectively
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static List<Raytracer.Raytrace> sanityCheckTraces(List<Raytracer.Raytrace> traces) {
|
||||
if (Main.DEBUG && traces.stream().mapToDouble(Raytracer.Raytrace::centerDistApprox).distinct().count() > 2) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
return traces;
|
||||
}
|
||||
}
|
||||
@@ -1,98 +0,0 @@
|
||||
/*
|
||||
* This file is part of Baritone.
|
||||
*
|
||||
* Baritone is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Baritone is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Baritone. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package baritone.builder;
|
||||
|
||||
import baritone.api.utils.BetterBlockPos;
|
||||
import it.unimi.dsi.fastutil.longs.LongArrayFIFOQueue;
|
||||
import it.unimi.dsi.fastutil.longs.LongIterator;
|
||||
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
|
||||
|
||||
/**
|
||||
* An area.
|
||||
* <p>
|
||||
* More likely than not a cuboid, but who knows :)
|
||||
*/
|
||||
public interface Bounds {
|
||||
|
||||
@FunctionalInterface
|
||||
interface BoundsIntsConsumer {
|
||||
|
||||
void consume(int x, int y, int z);
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
interface BoundsLongConsumer {
|
||||
|
||||
void consume(long pos);
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
interface BoundsIntAndLongConsumer {
|
||||
|
||||
void consume(int x, int y, int z, long pos);
|
||||
}
|
||||
|
||||
void forEach(BoundsIntsConsumer consumer);
|
||||
|
||||
void forEach(BoundsLongConsumer consumer);
|
||||
|
||||
void forEach(BoundsIntAndLongConsumer consumer);
|
||||
|
||||
// there is no "forEach" for the "index" lookup, because it is always going to just be a loop from 0 to bounds.volume()-1
|
||||
|
||||
boolean inRange(int x, int y, int z);
|
||||
|
||||
default boolean inRangePos(long pos) {
|
||||
return inRange(BetterBlockPos.XfromLong(pos), BetterBlockPos.YfromLong(pos), BetterBlockPos.ZfromLong(pos));
|
||||
}
|
||||
|
||||
int volume();
|
||||
|
||||
// this must be implemented EXTREMELY efficiently. no integer division allowed! even a hashmap lookup is borderline.
|
||||
int toIndex(int x, int y, int z); // easy to implement for cuboid, harder for more complicated shapes
|
||||
|
||||
default int toIndex(long pos) {
|
||||
return toIndex(BetterBlockPos.XfromLong(pos), BetterBlockPos.YfromLong(pos), BetterBlockPos.ZfromLong(pos));
|
||||
}
|
||||
|
||||
static void sanityCheckConnectedness(Bounds bounds) {
|
||||
LongOpenHashSet all = new LongOpenHashSet();
|
||||
bounds.forEach(all::add);
|
||||
if (all.size() != bounds.volume()) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
long any = all.iterator().nextLong();
|
||||
LongOpenHashSet reachable = new LongOpenHashSet();
|
||||
LongArrayFIFOQueue queue = new LongArrayFIFOQueue();
|
||||
queue.enqueue(any);
|
||||
while (!queue.isEmpty()) {
|
||||
long pos = queue.dequeueLong();
|
||||
if (bounds.inRangePos(pos) && reachable.add(pos)) {
|
||||
for (Face face : Face.VALUES) {
|
||||
queue.enqueueFirst(face.offset(pos));
|
||||
}
|
||||
}
|
||||
}
|
||||
LongIterator it = all.iterator();
|
||||
while (it.hasNext()) {
|
||||
if (!reachable.contains(it.nextLong())) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,120 +0,0 @@
|
||||
/*
|
||||
* This file is part of Baritone.
|
||||
*
|
||||
* Baritone is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Baritone is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Baritone. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package baritone.builder;
|
||||
|
||||
import baritone.api.utils.BetterBlockPos;
|
||||
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
import static baritone.api.utils.BetterBlockPos.Y_MASK;
|
||||
import static baritone.api.utils.BetterBlockPos.Y_SHIFT;
|
||||
|
||||
/**
|
||||
* A mutable class representing a 1x6x1 column of blocks
|
||||
* <p>
|
||||
* Mutable because allocations are not on the table for the core solver loop
|
||||
*/
|
||||
public class Column {
|
||||
|
||||
public long pos; // TODO this isn't set?
|
||||
//public BlockStateCachedData underUnderneath;
|
||||
public BlockStateCachedData underneath;
|
||||
public BlockStateCachedData feet;
|
||||
public BlockStateCachedData head;
|
||||
public BlockStateCachedData above;
|
||||
public BlockStateCachedData aboveAbove;
|
||||
public PlayerPhysics.VoxelResidency voxelResidency;
|
||||
public Integer feetBlips;
|
||||
|
||||
public void initFrom(long pos, WorldState worldState, SolverEngineInput engineInput) {
|
||||
this.pos = pos;
|
||||
//this.underUnderneath = engineInput.at((pos + DOWN_2) & BetterBlockPos.POST_ADDITION_MASK, worldState);
|
||||
this.underneath = engineInput.at((pos + DOWN_1) & BetterBlockPos.POST_ADDITION_MASK, worldState);
|
||||
this.feet = engineInput.at(pos, worldState);
|
||||
this.head = engineInput.at((pos + UP_1) & BetterBlockPos.POST_ADDITION_MASK, worldState);
|
||||
this.above = engineInput.at((pos + UP_2) & BetterBlockPos.POST_ADDITION_MASK, worldState);
|
||||
this.aboveAbove = engineInput.at((pos + UP_3) & BetterBlockPos.POST_ADDITION_MASK, worldState);
|
||||
init();
|
||||
}
|
||||
|
||||
public void init() {
|
||||
this.voxelResidency = PlayerPhysics.canPlayerStand(underneath, feet);
|
||||
this.feetBlips = boxNullable(PlayerPhysics.determinePlayerRealSupportLevel(underneath, feet, voxelResidency));
|
||||
if (feetBlips != null && !playerCanExistAtFootBlip(feetBlips)) { // TODO is this the correct way to handle head collision?
|
||||
voxelResidency = PlayerPhysics.VoxelResidency.IMPOSSIBLE_WITHOUT_SUFFOCATING; // TODO this is a misuse of this enum value i think
|
||||
feetBlips = null;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean playerCanExistAtFootBlip(int blipWithinFeet) {
|
||||
if (head.collidesWithPlayer) {
|
||||
return false;
|
||||
}
|
||||
if (PlayerPhysics.protrudesIntoThirdBlock(blipWithinFeet) && above.collidesWithPlayer) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean okToSneakIntoHereAtHeight(int blips) {
|
||||
return playerCanExistAtFootBlip(blips) // no collision at head level
|
||||
&& PlayerPhysics.highestCollision(underneath, feet) < blips; // and at foot level, we only collide strictly below where the feet will be
|
||||
}
|
||||
|
||||
public boolean standing() {
|
||||
return feetBlips != null;
|
||||
}
|
||||
|
||||
public static final long DOWN_3 = (Y_MASK - 2) << Y_SHIFT;
|
||||
public static final long DOWN_2 = (Y_MASK - 1) << Y_SHIFT;
|
||||
public static final long DOWN_1 = Y_MASK << Y_SHIFT;
|
||||
public static final long UP_1 = 1L << Y_SHIFT;
|
||||
public static final long UP_2 = 2L << Y_SHIFT;
|
||||
public static final long UP_3 = 3L << Y_SHIFT;
|
||||
|
||||
static {
|
||||
if (DOWN_3 != BetterBlockPos.toLong(0, -3, 0)) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
if (DOWN_2 != BetterBlockPos.toLong(0, -2, 0)) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
if (DOWN_1 != BetterBlockPos.toLong(0, -1, 0)) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
if (UP_1 != BetterBlockPos.toLong(0, 1, 0)) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
if (UP_2 != BetterBlockPos.toLong(0, 2, 0)) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
if (UP_3 != BetterBlockPos.toLong(0, 3, 0)) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
|
||||
private static final Integer[] BLIPS = IntStream.range(-1, Blip.FULL_BLOCK).boxed().toArray(Integer[]::new);
|
||||
|
||||
static {
|
||||
BLIPS[0] = null;
|
||||
}
|
||||
|
||||
private static Integer boxNullable(int blips) {
|
||||
return BLIPS[blips + 1]; // map -1 to [0] which is null
|
||||
}
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
/*
|
||||
* This file is part of Baritone.
|
||||
*
|
||||
* Baritone is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Baritone is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Baritone. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package baritone.builder;
|
||||
|
||||
import baritone.api.utils.BetterBlockPos;
|
||||
import baritone.builder.utils.com.github.btrekkie.connectivity.MutatingAugmentation;
|
||||
|
||||
import java.util.OptionalInt;
|
||||
|
||||
public class CountingSurface extends NavigableSurface {
|
||||
public CountingSurface(int x, int y, int z) {
|
||||
super(x, y, z, new MutatingAugmentation() {
|
||||
@Override
|
||||
public void combine(Object value1, Object value2, Object result) {
|
||||
((Attachment) result).combine((Attachment) value1, (Attachment) value2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object newAugmentation() {
|
||||
return new Attachment();
|
||||
}
|
||||
}, $ -> new Attachment());
|
||||
}
|
||||
|
||||
private static class Attachment {
|
||||
public int surfaceSize;
|
||||
|
||||
public Attachment() {
|
||||
this.surfaceSize = 1;
|
||||
}
|
||||
|
||||
public void combine(Attachment child1, Attachment child2) {
|
||||
surfaceSize = child1.surfaceSize + child2.surfaceSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) { // used as performance optimization in RedBlackNode to avoid augmenting unchanged attachments
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (!(o instanceof Attachment)) {
|
||||
return false;
|
||||
}
|
||||
Attachment that = (Attachment) o;
|
||||
return surfaceSize == that.surfaceSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return surfaceSize;
|
||||
}
|
||||
}
|
||||
|
||||
public OptionalInt surfaceSize(BetterBlockPos pos) { // how big is the navigable surface from here? how many distinct coordinates can i walk to (in the future, the augmentation will probably have a list of those coordinates or something?)
|
||||
Object data = getComponentAugmentation(pos);
|
||||
if (data != null) { // i disagree with the intellij suggestion here i think it makes it worse
|
||||
return OptionalInt.of(((Attachment) data).surfaceSize);
|
||||
} else {
|
||||
return OptionalInt.empty();
|
||||
}
|
||||
}
|
||||
|
||||
public int requireSurfaceSize(int x, int y, int z) {
|
||||
return surfaceSize(new BetterBlockPos(x, y, z)).getAsInt();
|
||||
}
|
||||
|
||||
private void placeOrRemoveBlock(BetterBlockPos where, boolean place) {
|
||||
setBlock(where.toLong(), place ? FakeStates.SOLID : FakeStates.AIR);
|
||||
}
|
||||
|
||||
public void placeBlock(BetterBlockPos where) {
|
||||
placeOrRemoveBlock(where, true);
|
||||
}
|
||||
|
||||
public void placeBlock(int x, int y, int z) {
|
||||
placeBlock(new BetterBlockPos(x, y, z));
|
||||
}
|
||||
|
||||
public void removeBlock(BetterBlockPos where) {
|
||||
placeOrRemoveBlock(where, false);
|
||||
}
|
||||
|
||||
public void removeBlock(int x, int y, int z) {
|
||||
removeBlock(new BetterBlockPos(x, y, z));
|
||||
}
|
||||
}
|
||||
@@ -1,165 +0,0 @@
|
||||
/*
|
||||
* This file is part of Baritone.
|
||||
*
|
||||
* Baritone is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Baritone is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Baritone. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package baritone.builder;
|
||||
|
||||
import baritone.api.utils.BetterBlockPos;
|
||||
|
||||
/**
|
||||
* Bounding box of a cuboid
|
||||
* <p>
|
||||
* Basically just a lot of helper util methods lol
|
||||
*/
|
||||
public class CuboidBounds implements Bounds {
|
||||
|
||||
public final int sizeX;
|
||||
public final int sizeY;
|
||||
public final int sizeZ;
|
||||
private final int sizeXMinusOne;
|
||||
private final int sizeYMinusOne;
|
||||
private final int sizeZMinusOne;
|
||||
public final int size;
|
||||
private final int sizeMinusOne;
|
||||
|
||||
public CuboidBounds(int sizeX, int sizeY, int sizeZ) {
|
||||
this.sizeX = sizeX;
|
||||
this.sizeY = sizeY;
|
||||
this.sizeZ = sizeZ;
|
||||
this.sizeXMinusOne = sizeX - 1;
|
||||
this.sizeYMinusOne = sizeY - 1;
|
||||
this.sizeZMinusOne = sizeZ - 1;
|
||||
this.size = sizeX * sizeY * sizeZ;
|
||||
this.sizeMinusOne = size - 1;
|
||||
if (Main.SLOW_DEBUG) {
|
||||
sanityCheck();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int toIndex(int x, int y, int z) {
|
||||
if (Main.DEBUG && !inRange(x, y, z)) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
return (x * sizeY + y) * sizeZ + z;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean inRange(int x, int y, int z) {
|
||||
return inRangeBranchless(x, y, z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int volume() {
|
||||
return size;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public boolean inRangeBranchy(int x, int y, int z) { // benchmarked: approx 4x slower than branchless
|
||||
return (x >= 0) && (x < sizeX) && (y >= 0) && (y < sizeY) && (z >= 0) && (z < sizeZ);
|
||||
}
|
||||
|
||||
public boolean inRangeBranchless(int x, int y, int z) {
|
||||
return (x | y | z | (sizeXMinusOne - x) | (sizeYMinusOne - y) | (sizeZMinusOne - z)) >= 0;
|
||||
}
|
||||
|
||||
public boolean inRangeBranchless2(int x, int y, int z) {
|
||||
return (x | y | z | ((sizeX - 1) - x) | ((sizeY - 1) - y) | ((sizeZ - 1) - z)) >= 0;
|
||||
}
|
||||
|
||||
public boolean inRangeBranchless3(int x, int y, int z) {
|
||||
return (x | y | z | (sizeX - (x + 1)) | (sizeY - (y + 1)) | (sizeZ - (z + 1))) >= 0;
|
||||
}
|
||||
|
||||
public boolean inRangeBranchless4(int x, int y, int z) {
|
||||
return (x | y | z | ((sizeX - x) - 1) | ((sizeY - y) - 1) | ((sizeZ - z) - 1)) >= 0;
|
||||
}
|
||||
|
||||
public boolean inRangeIndex(int index) {
|
||||
return (index | (sizeMinusOne - index)) >= 0;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void forEach(BoundsIntsConsumer consumer) {
|
||||
int sizeX = this.sizeX, sizeY = this.sizeY, sizeZ = this.sizeZ;
|
||||
for (int x = 0; x < sizeX; x++) {
|
||||
for (int y = 0; y < sizeY; y++) {
|
||||
for (int z = 0; z < sizeZ; z++) {
|
||||
consumer.consume(x, y, z);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void forEach(BoundsLongConsumer consumer) {
|
||||
int sizeX = this.sizeX, sizeY = this.sizeY, sizeZ = this.sizeZ;
|
||||
for (int x = 0; x < sizeX; x++) {
|
||||
for (int y = 0; y < sizeY; y++) {
|
||||
for (int z = 0; z < sizeZ; z++) {
|
||||
consumer.consume(BetterBlockPos.toLong(x, y, z));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void forEach(BoundsIntAndLongConsumer consumer) {
|
||||
int sizeX = this.sizeX, sizeY = this.sizeY, sizeZ = this.sizeZ;
|
||||
for (int x = 0; x < sizeX; x++) {
|
||||
for (int y = 0; y < sizeY; y++) {
|
||||
for (int z = 0; z < sizeZ; z++) {
|
||||
consumer.consume(x, y, z, BetterBlockPos.toLong(x, y, z));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void sanityCheck() {
|
||||
if (sizeY > 256) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
long chk = ((long) sizeX) * ((long) sizeY) * ((long) sizeZ);
|
||||
if (chk != (long) size) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
int index = 0;
|
||||
for (int x = 0; x < sizeX; x++) {
|
||||
for (int y = 0; y < sizeY; y++) {
|
||||
for (int z = 0; z < sizeZ; z++) {
|
||||
if (!inRange(x, y, z)) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
if (toIndex(x, y, z) != index) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
index++;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (index != size) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
if (inRange(-1, 0, 0) || inRange(0, -1, 0) || inRange(0, 0, -1)) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
if (inRange(sizeX, 0, 0) || inRange(0, sizeY, 0) || inRange(0, 0, sizeZ)) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
Bounds.sanityCheckConnectedness(this);
|
||||
}
|
||||
}
|
||||
@@ -1,126 +0,0 @@
|
||||
/*
|
||||
* This file is part of Baritone.
|
||||
*
|
||||
* Baritone is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Baritone is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Baritone. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package baritone.builder;
|
||||
|
||||
import baritone.api.utils.BetterBlockPos;
|
||||
import it.unimi.dsi.fastutil.longs.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Some initial checks on the schematic
|
||||
* <p>
|
||||
* The intent is to provide reasonable error messages, which we can do by catching common cases as early as possible
|
||||
* <p>
|
||||
* So that it's an actual comprehensible error **that tells you where the problem is** instead of just "pathing failed"
|
||||
*/
|
||||
public class DependencyGraphAnalyzer {
|
||||
|
||||
/**
|
||||
* Just a simple check to make sure that everything is placeable.
|
||||
* <p>
|
||||
* Mostly for my own testing because every Minecraft block is placeable, and if the schematic has something weird
|
||||
* and funky, it should be caught earlier anyway.
|
||||
*/
|
||||
public static void prevalidate(PlaceOrderDependencyGraph graph) {
|
||||
List<String> locs = new ArrayList<>();
|
||||
graph.bounds().forEach(pos -> {
|
||||
if (graph.airTreatedAsScaffolding(pos)) {
|
||||
// completely fine to, for example, have an air pocket with non-place-against-able stuff all around it
|
||||
return;
|
||||
}
|
||||
for (Face face : Face.VALUES) {
|
||||
if (graph.incomingEdge(pos, face)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
locs.add(BetterBlockPos.fromLong(pos).toString());
|
||||
});
|
||||
if (!locs.isEmpty()) {
|
||||
throw new IllegalStateException("Unplaceable from any side: " + cuteTrim(locs));
|
||||
}
|
||||
// TODO instead of cuteTrim have a like SpecificBlockPositionsImpossibleException that this throws, and then later, an enclosing function can give the option to reset those locations to air
|
||||
}
|
||||
|
||||
/**
|
||||
* Search from all exterior nodes breadth-first to ensure that, theoretically, everything is reachable.
|
||||
* <p>
|
||||
* This is NOT a sufficient test, because later we are going to ensure that everything is scaffold-placeable which
|
||||
* requires a single root node at the bottom.
|
||||
*/
|
||||
public static void prevalidateExternalToInteriorSearch(PlaceOrderDependencyGraph graph) {
|
||||
LongList edgeBegins = new LongArrayList();
|
||||
graph.bounds().forEach(pos -> {
|
||||
for (Face face : Face.VALUES) {
|
||||
if (graph.incomingEdgePermitExterior(pos, face) && !graph.incomingEdge(pos, face)) {
|
||||
// this block is placeable from the exterior of the schematic!
|
||||
edgeBegins.add(pos); // this will intentionally put the top of the schematic at the front
|
||||
}
|
||||
}
|
||||
});
|
||||
LongSet reachable = searchGraph(edgeBegins, graph::outgoingEdge);
|
||||
List<String> locs = new ArrayList<>();
|
||||
graph.bounds().forEach(pos -> {
|
||||
if (graph.airTreatedAsScaffolding(pos)) {
|
||||
// same as previous validation
|
||||
return;
|
||||
}
|
||||
if (!reachable.contains(pos)) {
|
||||
locs.add(BetterBlockPos.fromLong(pos).toString());
|
||||
}
|
||||
});
|
||||
if (!locs.isEmpty()) {
|
||||
throw new IllegalStateException("Placeable, in theory, but in practice there is no valid path from the exterior to it: " + cuteTrim(locs));
|
||||
}
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
interface EdgeTester {
|
||||
boolean hasEdge(long from, Face dir);
|
||||
}
|
||||
|
||||
public static LongSet searchGraph(LongCollection origin, EdgeTester edgeTester) {
|
||||
LongOpenHashSet reachable = new LongOpenHashSet();
|
||||
LongArrayFIFOQueue queue = new LongArrayFIFOQueue(origin.size());
|
||||
LongIterator it = origin.iterator();
|
||||
while (it.hasNext()) {
|
||||
queue.enqueue(it.nextLong());
|
||||
}
|
||||
while (!queue.isEmpty()) {
|
||||
long pos = queue.dequeueLong();
|
||||
if (reachable.add(pos)) {
|
||||
for (Face face : Face.VALUES) {
|
||||
if (edgeTester.hasEdge(pos, face)) {
|
||||
queue.enqueueFirst(face.offset(pos));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return reachable;
|
||||
}
|
||||
|
||||
private static List<String> cuteTrim(List<String> pos) {
|
||||
if (pos.size() <= 20) {
|
||||
return pos;
|
||||
}
|
||||
pos = pos.subList(0, 20);
|
||||
pos.set(pos.size() - 1, "...");
|
||||
return pos;
|
||||
}
|
||||
}
|
||||
@@ -1,499 +0,0 @@
|
||||
/*
|
||||
* This file is part of Baritone.
|
||||
*
|
||||
* Baritone is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Baritone is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Baritone. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package baritone.builder;
|
||||
|
||||
import baritone.api.utils.BetterBlockPos;
|
||||
import it.unimi.dsi.fastutil.HashCommon;
|
||||
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMaps;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.longs.*;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* A mutable addition of scaffolding blocks to a schematic
|
||||
* <p>
|
||||
* Contains a set of coordinates that are "air" in the schematic, but we are going to put scaffolding throwaway blocks there
|
||||
* <p>
|
||||
* Maintains and incrementally updates a collapsed dependency graph, which is the block placement dependency graph reduced to directed acyclic graph form by way of collapsing all strongly connected components into single nodes
|
||||
* <p>
|
||||
* Helper class, only intended to be used within Scaffolder
|
||||
*/
|
||||
public class DependencyGraphScaffoldingOverlay {
|
||||
|
||||
private final PlaceOrderDependencyGraph delegate;
|
||||
private final LongOpenHashSet scaffoldingAdded;
|
||||
private final CollapsedDependencyGraph collapsedGraph;
|
||||
|
||||
public DependencyGraphScaffoldingOverlay(PlaceOrderDependencyGraph delegate) {
|
||||
this.delegate = delegate;
|
||||
this.scaffoldingAdded = new LongOpenHashSet();
|
||||
this.collapsedGraph = new CollapsedDependencyGraph(TarjansAlgorithm.run(this));
|
||||
//System.out.println("Num components: " + collapsedGraph.components.size());
|
||||
}
|
||||
|
||||
public boolean outgoingEdge(long pos, Face face) {
|
||||
if (overrideOff(pos)) {
|
||||
return false;
|
||||
}
|
||||
if (overrideOff(face.offset(pos))) {
|
||||
return false;
|
||||
}
|
||||
return delegate.outgoingEdge(pos, face);
|
||||
}
|
||||
|
||||
public boolean incomingEdge(long pos, Face face) {
|
||||
if (overrideOff(pos)) {
|
||||
return false;
|
||||
}
|
||||
if (overrideOff(face.offset(pos))) {
|
||||
return false;
|
||||
}
|
||||
return delegate.incomingEdge(pos, face);
|
||||
}
|
||||
|
||||
public boolean hypotheticalScaffoldingIncomingEdge(long pos, Face face) {
|
||||
return delegate.incomingEdge(pos, face);
|
||||
}
|
||||
|
||||
public Bounds bounds() {
|
||||
return delegate.bounds();
|
||||
}
|
||||
|
||||
private boolean overrideOff(long pos) {
|
||||
return bounds().inRangePos(pos) && air(pos);
|
||||
}
|
||||
|
||||
public boolean real(long pos) {
|
||||
return !air(pos);
|
||||
}
|
||||
|
||||
public void forEachReal(Bounds.BoundsLongConsumer consumer) {
|
||||
bounds().forEach(pos -> {
|
||||
if (real(pos)) {
|
||||
consumer.consume(pos);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public boolean air(long pos) {
|
||||
return delegate.airTreatedAsScaffolding(pos) && !scaffoldingAdded.contains(pos);
|
||||
}
|
||||
|
||||
public void enable(long pos) {
|
||||
if (!delegate.airTreatedAsScaffolding(pos)) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
if (!scaffoldingAdded.add(pos)) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
collapsedGraph.incrementalUpdate(pos);
|
||||
if (Main.SLOW_DEBUG) {
|
||||
//System.out.println(collapsedGraph.posToComponent.containsKey(pos) + " " + scaffoldingAdded.contains(pos) + " " + real(pos));
|
||||
recheckEntireCollapsedGraph();
|
||||
}
|
||||
}
|
||||
|
||||
public void recheckEntireCollapsedGraph() {
|
||||
checkEquality(collapsedGraph, new CollapsedDependencyGraph(TarjansAlgorithm.run(this)));
|
||||
//System.out.println("Checked equality");
|
||||
//System.out.println("Num connected components: " + collapsedGraph.components.size());
|
||||
}
|
||||
|
||||
public LongSets.UnmodifiableSet scaffolding() {
|
||||
return (LongSets.UnmodifiableSet) LongSets.unmodifiable(scaffoldingAdded);
|
||||
}
|
||||
|
||||
public BlockStateCachedData data(long pos) {
|
||||
if (Main.DEBUG && !real(pos)) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
return delegate.data(pos);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remember that this returns a collapsed graph that will be updated in-place as positions are enabled. It does not return a copy.
|
||||
*/
|
||||
public CollapsedDependencyGraph getCollapsedGraph() {
|
||||
return collapsedGraph;
|
||||
}
|
||||
|
||||
public class CollapsedDependencyGraph {
|
||||
|
||||
private int nextComponentID;
|
||||
private final Int2ObjectOpenHashMap<CollapsedDependencyGraphComponent> components;
|
||||
private final Long2ObjectOpenHashMap<CollapsedDependencyGraphComponent> posToComponent;
|
||||
|
||||
private CollapsedDependencyGraph(TarjansAlgorithm.TarjansResult partition) {
|
||||
components = new Int2ObjectOpenHashMap<>();
|
||||
for (int i = 0; i < partition.numComponents(); i++) {
|
||||
addComponent();
|
||||
}
|
||||
posToComponent = new Long2ObjectOpenHashMap<>();
|
||||
forEachReal(pos -> {
|
||||
CollapsedDependencyGraphComponent component = components.get(partition.getComponent(pos));
|
||||
component.positions.add(pos);
|
||||
posToComponent.put(pos, component);
|
||||
});
|
||||
forEachReal(pos -> {
|
||||
for (Face face : Face.VALUES) {
|
||||
if (outgoingEdge(pos, face)) {
|
||||
CollapsedDependencyGraphComponent src = posToComponent.get(pos);
|
||||
CollapsedDependencyGraphComponent dst = posToComponent.get(face.offset(pos));
|
||||
if (src != dst) {
|
||||
src.outgoingEdges.add(dst);
|
||||
dst.incomingEdges.add(src);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
if (Main.SLOW_DEBUG) {
|
||||
sanityCheck();
|
||||
}
|
||||
}
|
||||
|
||||
public Int2ObjectMap<CollapsedDependencyGraphComponent> getComponents() {
|
||||
return Int2ObjectMaps.unmodifiable(components);
|
||||
}
|
||||
|
||||
public Long2ObjectMap<CollapsedDependencyGraphComponent> getComponentLocations() {
|
||||
return Long2ObjectMaps.unmodifiable(posToComponent);
|
||||
}
|
||||
|
||||
public OptionalInt lastComponentID() {
|
||||
return nextComponentID == 0 ? OptionalInt.empty() : OptionalInt.of(nextComponentID - 1);
|
||||
}
|
||||
|
||||
private CollapsedDependencyGraphComponent addComponent() {
|
||||
CollapsedDependencyGraphComponent component = new CollapsedDependencyGraphComponent(nextComponentID);
|
||||
components.put(component.id, component);
|
||||
nextComponentID++;
|
||||
return component;
|
||||
}
|
||||
|
||||
private CollapsedDependencyGraphComponent mergeInto(CollapsedDependencyGraphComponent child, CollapsedDependencyGraphComponent parent) {
|
||||
if (child.deleted() || parent.deleted()) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
if (child == parent) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
if (child.positions.size() > parent.positions.size() || (child.positions.size() == parent.positions.size() && child.id < parent.id)) {
|
||||
return mergeInto(parent, child);
|
||||
}
|
||||
if (Main.DEBUG) {
|
||||
//System.out.println("Merging " + child.index + " into " + parent.index);
|
||||
}
|
||||
child.incomingEdges.forEach(intoChild -> {
|
||||
intoChild.outgoingEdges.remove(child);
|
||||
if (intoChild == parent) {
|
||||
return;
|
||||
}
|
||||
intoChild.outgoingEdges.add(parent);
|
||||
parent.incomingEdges.add(intoChild);
|
||||
});
|
||||
child.outgoingEdges.forEach(outOfChild -> {
|
||||
outOfChild.incomingEdges.remove(child);
|
||||
if (outOfChild == parent) {
|
||||
return;
|
||||
}
|
||||
outOfChild.incomingEdges.add(parent);
|
||||
parent.outgoingEdges.add(outOfChild);
|
||||
});
|
||||
parent.positions.addAll(child.positions);
|
||||
LongIterator it = child.positions.iterator();
|
||||
while (it.hasNext()) {
|
||||
long pos = it.nextLong();
|
||||
posToComponent.put(pos, parent);
|
||||
}
|
||||
components.remove(child.id);
|
||||
child.deletedInto = parent;
|
||||
// TODO clear and trim child.positions? maybe unnecessary because nothing should retain a reference to child for longer than a moment
|
||||
return parent;
|
||||
}
|
||||
|
||||
private void incrementalEdgeAddition(long src, long dst) { // TODO put in a param here like "bias" that determines which of the two components gets to eat the other if they are of the same size? could help with the scaffolder if we could guarantee that no new components would be added without cause
|
||||
CollapsedDependencyGraphComponent srcComponent = posToComponent.get(src);
|
||||
CollapsedDependencyGraphComponent dstComponent = posToComponent.get(dst);
|
||||
if (srcComponent == dstComponent) { // already strongly connected
|
||||
return;
|
||||
}
|
||||
if (srcComponent.outgoingEdges.contains(dstComponent)) { // we already know about this edge
|
||||
return;
|
||||
}
|
||||
List<List<CollapsedDependencyGraphComponent>> paths = new ArrayList<>();
|
||||
if (!srcComponent.incomingEdges.isEmpty() && pathExists(dstComponent, srcComponent, paths) > 0) {
|
||||
CollapsedDependencyGraphComponent survivor = srcComponent;
|
||||
for (List<CollapsedDependencyGraphComponent> path : paths) {
|
||||
if (path.get(0) != srcComponent || path.get(path.size() - 1) != dstComponent) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
for (int i = 1; i < path.size(); i++) {
|
||||
if (path.get(i).deleted() || path.get(i) == survivor) { // two different paths to the same goal, only merge the components once, so skip is already survivor or deleted
|
||||
continue;
|
||||
}
|
||||
survivor = mergeInto(survivor, path.get(i));
|
||||
}
|
||||
}
|
||||
// can't run sanityCheck after each mergeInto because it could leave a 2-way connection between components as an intermediary state while collapsing
|
||||
if (Main.SLOW_DEBUG) {
|
||||
sanityCheck();
|
||||
}
|
||||
return;
|
||||
}
|
||||
srcComponent.outgoingEdges.add(dstComponent);
|
||||
dstComponent.incomingEdges.add(srcComponent);
|
||||
}
|
||||
|
||||
private int pathExists(CollapsedDependencyGraphComponent src, CollapsedDependencyGraphComponent dst, List<List<CollapsedDependencyGraphComponent>> paths) {
|
||||
if (src == dst) {
|
||||
paths.add(new ArrayList<>(Collections.singletonList(src)));
|
||||
return 1;
|
||||
}
|
||||
if (Main.DEBUG && dst.incomingEdges.isEmpty()) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
if (Main.STRICT_Y && src.y() > dst.y()) {
|
||||
return 0; // no downward edges in strict_y mode
|
||||
}
|
||||
int numAdded = 0;
|
||||
for (CollapsedDependencyGraphComponent nxt : src.outgoingEdges) {
|
||||
int cnt = pathExists(nxt, dst, paths);
|
||||
if (cnt > 0) {
|
||||
for (int i = 0; i < cnt; i++) {
|
||||
paths.get(paths.size() - 1 - i).add(src);
|
||||
}
|
||||
numAdded += cnt;
|
||||
}
|
||||
}
|
||||
return numAdded;
|
||||
}
|
||||
|
||||
private void incrementalUpdate(long pos) {
|
||||
if (posToComponent.containsKey(pos)) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
CollapsedDependencyGraphComponent component = addComponent();
|
||||
component.positions.add(pos);
|
||||
posToComponent.put(pos, component);
|
||||
if (Main.SLOW_DEBUG) {
|
||||
sanityCheck();
|
||||
}
|
||||
//System.out.println("Incremental " + pos);
|
||||
//System.out.println("Pos core " + posToComponent.get(pos).index);
|
||||
for (Face face : Face.VALUES) {
|
||||
if (outgoingEdge(pos, face)) {
|
||||
//System.out.println("Pos outgoing edge " + face + " goes to " + posToComponent.get(face.offset(pos)).index);
|
||||
incrementalEdgeAddition(pos, face.offset(pos));
|
||||
}
|
||||
if (incomingEdge(pos, face)) {
|
||||
//System.out.println("Pos incoming edge " + face + " comes from " + posToComponent.get(face.offset(pos)).index);
|
||||
incrementalEdgeAddition(face.offset(pos), pos);
|
||||
}
|
||||
}
|
||||
if (Main.SLOW_DEBUG) {
|
||||
sanityCheck();
|
||||
}
|
||||
}
|
||||
|
||||
public class CollapsedDependencyGraphComponent {
|
||||
|
||||
private final int id;
|
||||
private final int hash;
|
||||
private final LongOpenHashSet positions = new LongOpenHashSet();
|
||||
private final Set<CollapsedDependencyGraphComponent> outgoingEdges = new ObjectOpenHashSet<>();
|
||||
private final Set<CollapsedDependencyGraphComponent> incomingEdges = new ObjectOpenHashSet<>();
|
||||
// if i change ^^ that "Set" to "ObjectOpenHashSet" it actually makes the bench about 15% SLOWER?!?!?
|
||||
private int y = -1;
|
||||
private CollapsedDependencyGraphComponent deletedInto;
|
||||
private final Set<CollapsedDependencyGraphComponent> unmodifiableOutgoing = Collections.unmodifiableSet(outgoingEdges);
|
||||
private final Set<CollapsedDependencyGraphComponent> unmodifiableIncoming = Collections.unmodifiableSet(incomingEdges);
|
||||
|
||||
private CollapsedDependencyGraphComponent(int id) {
|
||||
this.id = id;
|
||||
this.hash = HashCommon.murmurHash3(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return hash; // no need to enter native code to get a hashCode, that saves a few nanoseconds
|
||||
}
|
||||
|
||||
private int y() {
|
||||
if (!Main.STRICT_Y || positions.isEmpty()) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
if (y == -1) { // TODO won't work in 1.17+ lol
|
||||
y = BetterBlockPos.YfromLong(positions.iterator().nextLong());
|
||||
if (y == -1) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
return y;
|
||||
}
|
||||
|
||||
public boolean deleted() {
|
||||
return deletedInto != null;
|
||||
}
|
||||
|
||||
public CollapsedDependencyGraphComponent deletedIntoRecursive() { // what cid was this merged into that caused it to be deleted
|
||||
if (!deleted()) {
|
||||
return this;
|
||||
}
|
||||
return deletedInto = deletedInto.deletedIntoRecursive();
|
||||
}
|
||||
|
||||
public LongSet getPositions() {
|
||||
if (deleted()) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
return LongSets.unmodifiable(positions);
|
||||
}
|
||||
|
||||
public Set<CollapsedDependencyGraphComponent> getIncoming() {
|
||||
if (deleted()) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
return unmodifiableIncoming;
|
||||
}
|
||||
|
||||
public Set<CollapsedDependencyGraphComponent> getOutgoing() {
|
||||
if (deleted()) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
return unmodifiableOutgoing;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (!Main.DEBUG) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
return "cid" + id;
|
||||
}
|
||||
}
|
||||
|
||||
private void sanityCheck() {
|
||||
LongOpenHashSet inComponents = new LongOpenHashSet();
|
||||
for (int componentID : components.keySet()) {
|
||||
CollapsedDependencyGraphComponent component = components.get(componentID);
|
||||
if (component.id != componentID) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
if (component.incomingEdges.contains(component) || component.outgoingEdges.contains(component)) {
|
||||
throw new IllegalStateException(component.id + "");
|
||||
}
|
||||
if (component.positions.isEmpty()) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
Integer y = Main.STRICT_Y ? component.y() : null;
|
||||
for (CollapsedDependencyGraphComponent out : component.outgoingEdges) {
|
||||
if (Main.STRICT_Y && out.y() < y) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
if (!out.incomingEdges.contains(component)) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
if (component.incomingEdges.contains(out)) {
|
||||
throw new IllegalStateException(out.id + " is both an incoming AND and outgoing of " + component.id);
|
||||
}
|
||||
}
|
||||
for (CollapsedDependencyGraphComponent in : component.incomingEdges) {
|
||||
if (Main.STRICT_Y && in.y() > y) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
if (!in.outgoingEdges.contains(component)) {
|
||||
throw new IllegalStateException(in.id + " is an incoming edge of " + component.id + " but it doesn't have that as an outgoing edge");
|
||||
}
|
||||
if (component.outgoingEdges.contains(in)) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
LongIterator it = component.positions.iterator();
|
||||
while (it.hasNext()) {
|
||||
long l = it.nextLong();
|
||||
if (posToComponent.get(l) != component) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
if (Main.STRICT_Y && BetterBlockPos.YfromLong(l) != y) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
if (!real(l)) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
inComponents.addAll(component.positions);
|
||||
}
|
||||
if (!inComponents.equals(posToComponent.keySet())) {
|
||||
for (long l : posToComponent.keySet()) {
|
||||
if (!inComponents.contains(l)) {
|
||||
System.out.println(l);
|
||||
System.out.println(posToComponent.get(l).id);
|
||||
System.out.println(posToComponent.get(l).positions.contains(l));
|
||||
System.out.println(posToComponent.get(l).deleted());
|
||||
System.out.println(components.containsValue(posToComponent.get(l)));
|
||||
throw new IllegalStateException(l + " is in posToComponent but not actually in any component");
|
||||
}
|
||||
}
|
||||
throw new IllegalStateException("impossible");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void checkEquality(CollapsedDependencyGraph a, CollapsedDependencyGraph b) {
|
||||
if (a.components.size() != b.components.size()) {
|
||||
throw new IllegalStateException(a.components.size() + " " + b.components.size());
|
||||
}
|
||||
if (a.posToComponent.size() != b.posToComponent.size()) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
if (!a.posToComponent.keySet().equals(b.posToComponent.keySet())) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
a.sanityCheck();
|
||||
b.sanityCheck();
|
||||
Int2IntOpenHashMap aToB = new Int2IntOpenHashMap();
|
||||
for (int key : a.components.keySet()) {
|
||||
aToB.put(key, b.posToComponent.get(a.components.get(key).positions.iterator().nextLong()).id);
|
||||
}
|
||||
for (int i : a.components.keySet()) {
|
||||
int bInd = aToB.get(i);
|
||||
if (!a.components.get(i).positions.equals(b.components.get(bInd).positions)) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
for (List<Set<CollapsedDependencyGraph.CollapsedDependencyGraphComponent>> toCompare : Arrays.asList(
|
||||
Arrays.asList(a.components.get(i).incomingEdges, b.components.get(bInd).incomingEdges),
|
||||
Arrays.asList(a.components.get(i).outgoingEdges, b.components.get(bInd).outgoingEdges)
|
||||
)) {
|
||||
Set<CollapsedDependencyGraph.CollapsedDependencyGraphComponent> aEdges = toCompare.get(0);
|
||||
Set<CollapsedDependencyGraph.CollapsedDependencyGraphComponent> bEdges = toCompare.get(1);
|
||||
if (aEdges.size() != bEdges.size()) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
for (CollapsedDependencyGraph.CollapsedDependencyGraphComponent dst : aEdges) {
|
||||
if (!bEdges.contains(b.components.get(aToB.get(dst.id)))) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,138 +0,0 @@
|
||||
/*
|
||||
* This file is part of Baritone.
|
||||
*
|
||||
* Baritone is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Baritone is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Baritone. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package baritone.builder;
|
||||
|
||||
import baritone.api.utils.BetterBlockPos;
|
||||
import baritone.builder.DependencyGraphScaffoldingOverlay.CollapsedDependencyGraph.CollapsedDependencyGraphComponent;
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.longs.LongArrayList;
|
||||
import it.unimi.dsi.fastutil.longs.LongIterator;
|
||||
import it.unimi.dsi.fastutil.longs.LongList;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.PriorityQueue;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public enum DijkstraScaffolder implements IScaffolderStrategy {
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public LongList scaffoldTo(CollapsedDependencyGraphComponent root, DependencyGraphScaffoldingOverlay overlayGraph) {
|
||||
// TODO what if this root is unreachable, e.g. it's lower in STRICT_Y mode?
|
||||
Set<CollapsedDependencyGraphComponent> exclusiveDescendents = new ObjectOpenHashSet<>();
|
||||
walkAllDescendents(root, exclusiveDescendents);
|
||||
exclusiveDescendents.remove(root);
|
||||
PriorityQueue<ScaffoldingSearchNode> openSet = new PriorityQueue<>(Comparator.comparingInt(node -> node.costSoFar));
|
||||
Long2ObjectOpenHashMap<ScaffoldingSearchNode> nodeMap = new Long2ObjectOpenHashMap<>();
|
||||
LongIterator it = root.getPositions().iterator();
|
||||
while (it.hasNext()) {
|
||||
long l = it.nextLong();
|
||||
nodeMap.put(l, new ScaffoldingSearchNode(l));
|
||||
}
|
||||
System.out.println(root.getPositions().stream().map(BetterBlockPos::fromLong).collect(Collectors.toList()));
|
||||
openSet.addAll(nodeMap.values());
|
||||
while (!openSet.isEmpty()) {
|
||||
ScaffoldingSearchNode node = openSet.poll();
|
||||
CollapsedDependencyGraphComponent tentativeComponent = overlayGraph.getCollapsedGraph().getComponentLocations().get(node.pos);
|
||||
if (tentativeComponent != null) {
|
||||
if (exclusiveDescendents.contains(tentativeComponent)) { // TODO is exclusiveDescendants even valid? returning a route into one of the descendants, if it's on the top of the heap, is valid because it closes a loop and the next dijkstra can start from there? perhaps there's no need to treat descendant interactions differently from any other non-root component? EDIT: maybe it's good to prevent adding useless scaffolding that closes loops for no good reason?
|
||||
// have gone back onto a descendent of this node
|
||||
// sadly this can happen even at the same Y level even in Y_STRICT mode due to orientable blocks forming a loop
|
||||
continue; // TODO does this need to be here? can I expand THROUGH an unrelated component? probably requires testing, this is quite a mind bending possibility. cost should be zero i think?
|
||||
} else {
|
||||
// found a path to a component that isn't a descendent of the root
|
||||
if (tentativeComponent != root) { // but if it IS the root, then we're just on our first loop iteration, we are far from done
|
||||
return reconstructPathTo(node); // all done! found a path to a component unrelated to this one, meaning we have successfully connected this part of the build with scaffolding back to the rest of it
|
||||
// TODO scaffolder strategy should be reworked into a coroutine-like format to decomposes a persistent dijkstra that retains the openset and nodemap between scaffolder component connections. each scaffoldersearchnode would need a persistent progeny (source component) and new combined components would need to be introduced as they're created. then the search can be simultaneous. this would solve the problem of potential incorrect selection of root node, as all possible root nodes are expanded at once
|
||||
}
|
||||
}
|
||||
}
|
||||
for (Face face : Face.VALUES) {
|
||||
if (overlayGraph.hypotheticalScaffoldingIncomingEdge(node.pos, face)) { // we don't have to worry about an incoming edge going into the frontier set because the root component is strongly connected and has no incoming edges from other SCCs, therefore any and all incoming edges will come from hypothetical scaffolding air locations
|
||||
long neighborPos = face.offset(node.pos);
|
||||
int newCost = node.costSoFar + edgeCost(face); // TODO future edge cost should include an added modifier for if neighborPos is in a favorable or unfavorable position e.g. above / under a diagonal depending on if map art or not
|
||||
ScaffoldingSearchNode existingNode = nodeMap.get(neighborPos);
|
||||
if (existingNode != null) {
|
||||
// it's okay if neighbor isn't marked as "air" in the overlay - that's what we want to find - a path to another component
|
||||
// however, we can't consider neighbors within the same component as a solution, clearly
|
||||
// we can accomplish this and kill two birds with one stone by skipping all nodes already in the node map
|
||||
// any position in the initial frontier is clearly in the node map, but also any node that has already been considered
|
||||
// this prevents useless cycling of equivalent paths
|
||||
// this is okay because all paths are equivalent, so there is no possible way to find a better path (because currently it's a fixed value for horizontal / vertical movements)
|
||||
if (existingNode.costSoFar > newCost) { // initialization nodes will have costSoFar = 0 as a base case
|
||||
// note that obviously there is a loopback possibility: search one block north then one block south, you'll run into the same node again. that's fine - "costSoFar < newCost" doesn't mean anything
|
||||
// same for diagonals: one block north then one block down, versus one block down then one block north. that's also fine - "costSoFar == newCost" doesn't mean anything
|
||||
System.out.println(BetterBlockPos.fromLong(node.pos) + " to " + BetterBlockPos.fromLong(neighborPos) + " " + existingNode.costSoFar + " " + newCost + " " + root.getPositions().contains(node.pos) + " " + root.getPositions().contains(neighborPos) + " " + reconstructPathTo(node).stream().map(BetterBlockPos::fromLong).collect(Collectors.toList()) + " " + reconstructPathTo(existingNode).stream().map(BetterBlockPos::fromLong).collect(Collectors.toList()));
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
// TODO if root spans more than 1 y level, then this assumption is not correct because edgeCost is different for a horizontal vs vertical face, meaning that a neighbor can have different cost routes if both sideways and up are part of the root component
|
||||
continue; // nothing to do - we already have an equal-or-better path to this location
|
||||
}
|
||||
ScaffoldingSearchNode newNode = new ScaffoldingSearchNode(neighborPos);
|
||||
newNode.costSoFar = newCost;
|
||||
newNode.prev = node;
|
||||
nodeMap.put(newNode.pos, newNode);
|
||||
openSet.add(newNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void walkAllDescendents(CollapsedDependencyGraphComponent root, Set<CollapsedDependencyGraphComponent> set) {
|
||||
set.add(root);
|
||||
for (CollapsedDependencyGraphComponent component : root.getOutgoing()) {
|
||||
walkAllDescendents(component, set);
|
||||
}
|
||||
}
|
||||
|
||||
private static LongList reconstructPathTo(ScaffoldingSearchNode end) {
|
||||
LongList path = new LongArrayList();
|
||||
while (end != null) {
|
||||
path.add(end.pos);
|
||||
end = end.prev;
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
public static int edgeCost(Face face) {
|
||||
if (Main.STRICT_Y && face == Face.UP) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
// gut feeling: give slight bias to moving horizontally
|
||||
// that will influence it to create horizontal bridges more often than vertical pillars
|
||||
// horizontal bridges are easier to maneuver around and over
|
||||
if (face.y == 0) {
|
||||
return 1;
|
||||
}
|
||||
return 2;
|
||||
}
|
||||
|
||||
private static class ScaffoldingSearchNode {
|
||||
|
||||
private final long pos;
|
||||
private int costSoFar;
|
||||
private ScaffoldingSearchNode prev;
|
||||
|
||||
private ScaffoldingSearchNode(long pos) {
|
||||
this.pos = pos;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,940 +0,0 @@
|
||||
/*
|
||||
* This file is part of Baritone.
|
||||
*
|
||||
* Baritone is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Baritone is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Baritone. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package baritone.builder;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
public class EulerTourForest {
|
||||
|
||||
static long parentWalks;
|
||||
static long parentCalls;
|
||||
// https://web.stanford.edu/class/archive/cs/cs166/cs166.1166/lectures/17/Small17.pdf
|
||||
// https://u.cs.biu.ac.il/~rodittl/p723-holm.pdf
|
||||
// https://web.archive.org/web/20180725100607/https://infoscience.epfl.ch/record/99353/files/HenzingerK99.pdf
|
||||
// https://en.wikipedia.org/wiki/Dynamic_connectivity#The_Level_structure
|
||||
|
||||
public final BSTNode[] loopbacks; // a (v,v) fake edge is created per vertex and maintained at the appropriate location in the tree, to allow fast lookups of where "v" is, without having to rely on the presence or absence of tree edges connected to v
|
||||
|
||||
public EulerTourForest(int n) {
|
||||
this.loopbacks = IntStream.range(0, n).mapToObj(SplayNode::new).toArray(BSTNode[]::new);
|
||||
}
|
||||
|
||||
public TreeEdge link(int vertA, int vertB) {
|
||||
if (connected(vertA, vertB)) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
BSTNode outgoing = new SplayNode(vertA, vertB);
|
||||
BSTNode incoming = new SplayNode(vertB, vertA);
|
||||
BSTNode.barrelRollToLowest(loopbacks[vertA]);
|
||||
BSTNode.barrelRollToLowest(loopbacks[vertB]);
|
||||
BSTNode.concatenate(loopbacks[vertA], outgoing); // (a,a) ... (a,b)
|
||||
BSTNode.concatenate(outgoing, loopbacks[vertB]); // (a,a) ... (a,b) (b,b) ...
|
||||
BSTNode.concatenate(loopbacks[vertB], incoming); // (a,a) ... (a,b) (b,b) ... (b,a)
|
||||
return new TreeEdge(incoming, outgoing);
|
||||
}
|
||||
|
||||
public void cut(TreeEdge edge) {
|
||||
if (edge.owner() != this) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
if (edge.cut) {
|
||||
return;
|
||||
}
|
||||
edge.cut = true;
|
||||
BSTNode outgoing = edge.left;
|
||||
BSTNode incoming = edge.right;
|
||||
if (incoming.src != outgoing.dst || incoming.dst != outgoing.src || outgoing == incoming || incoming.src == incoming.dst) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
if (!connected(incoming.src, incoming.dst)) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
BSTNode.barrelRollToLowest(outgoing);
|
||||
BSTNodePair disconnected = incoming.disconnect(Direction.RIGHT);
|
||||
if (disconnected.left.walkDescendant(Direction.LEFT) != outgoing || disconnected.right.walkDescendant(Direction.LEFT) != incoming) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
if (loopbacks[incoming.src].walkAncestor() != disconnected.left || loopbacks[outgoing.src].walkAncestor() != disconnected.right) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
outgoing.remove();
|
||||
incoming.remove();
|
||||
}
|
||||
|
||||
public boolean connected(int vertA, int vertB) {
|
||||
return loopbacks[vertA].walkAncestor() == loopbacks[vertB].walkAncestor();
|
||||
}
|
||||
|
||||
public int size(int vert) {
|
||||
return loopbacks[vert].walkAncestor().loopbackSize;
|
||||
}
|
||||
|
||||
public int[] walk(int vert) {
|
||||
BSTNode root = loopbacks[vert].walkAncestor();
|
||||
int[] ret = new int[root.loopbackSize];
|
||||
int[] idx = {0};
|
||||
root.walk(node -> {
|
||||
if (node.isLoopback()) {
|
||||
ret[idx[0]++] = node.src;
|
||||
}
|
||||
});
|
||||
return ret;
|
||||
}
|
||||
|
||||
// redblacknode impl deleted here
|
||||
|
||||
public static class SplayNode extends BSTNode {
|
||||
|
||||
private SplayNode() {
|
||||
super();
|
||||
}
|
||||
|
||||
private SplayNode(int same) {
|
||||
super(same);
|
||||
}
|
||||
|
||||
private SplayNode(int src, int dst) {
|
||||
super(src, dst);
|
||||
}
|
||||
|
||||
public void splay() {
|
||||
while (parent != null) {
|
||||
BSTNode grandparent = parent.parent;
|
||||
Direction myDir = whichChildAmI();
|
||||
if (grandparent != null) {
|
||||
Direction parentDir = parent.whichChildAmI();
|
||||
if (myDir == parentDir) {
|
||||
// see "Zig-zig step" of https://en.wikipedia.org/wiki/Splay_tree
|
||||
grandparent.splayRotate(parentDir);
|
||||
parent.splayRotate(myDir);
|
||||
} else {
|
||||
// see "Zig-zag step" of https://en.wikipedia.org/wiki/Splay_tree
|
||||
parent.splayRotate(myDir);
|
||||
grandparent.splayRotate(parentDir);
|
||||
}
|
||||
} else {
|
||||
parent.splayRotate(myDir);
|
||||
if (parent != null) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected BSTNode concatenateRoots(BSTNode right) {
|
||||
if (this.parent != null || right.parent != null || this == right) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
SplayNode newRoot = (SplayNode) right.walkDescendant(Direction.LEFT);
|
||||
newRoot.splay();
|
||||
// newRoot is now the root of a splay tree containing all of right
|
||||
// and, it is the LOWEST value of right, and left is assumed to be less than it, so now we can just attach left as its left child
|
||||
// (since it is the lowest value of right, it cannot possibly have any left child already)
|
||||
if (newRoot.getChild(Direction.LEFT) != null) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
newRoot.setChild(Direction.LEFT, this);
|
||||
newRoot.childUpdated();
|
||||
return newRoot;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected BSTNodePair disconnect(Direction remainOnSide) {
|
||||
splay(); // SIGNIFICANTLY easier to split the tree in half, if we are the root node
|
||||
BSTNode left = this;
|
||||
BSTNode right = this;
|
||||
// simple detach of one side
|
||||
if (remainOnSide == Direction.LEFT) {
|
||||
right = rightChild;
|
||||
rightChild = null;
|
||||
childUpdated();
|
||||
if (right != null) {
|
||||
right.sizeMustBeAccurate();
|
||||
right.parent = null;
|
||||
}
|
||||
} else {
|
||||
left = leftChild;
|
||||
leftChild = null;
|
||||
childUpdated();
|
||||
if (left != null) {
|
||||
left.sizeMustBeAccurate();
|
||||
left.parent = null;
|
||||
}
|
||||
}
|
||||
return new BSTNodePair(left, right);
|
||||
}
|
||||
|
||||
public void remove() {
|
||||
splay();
|
||||
if (leftChild != null) {
|
||||
leftChild.parent = null;
|
||||
}
|
||||
if (rightChild != null) {
|
||||
rightChild.parent = null;
|
||||
}
|
||||
if (leftChild != null && rightChild != null) {
|
||||
leftChild.concatenateRoots(rightChild);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public abstract static class BSTNode {
|
||||
|
||||
protected final int src;
|
||||
protected final int dst;
|
||||
|
||||
protected BSTNode leftChild;
|
||||
protected BSTNode rightChild;
|
||||
protected BSTNode parent;
|
||||
|
||||
protected int loopbackSize;
|
||||
|
||||
private BSTNode() {
|
||||
this(-1);
|
||||
}
|
||||
|
||||
private BSTNode(int same) {
|
||||
this.src = same;
|
||||
this.dst = same;
|
||||
this.loopbackSize = 1;
|
||||
}
|
||||
|
||||
private BSTNode(int src, int dst) {
|
||||
if (src == dst) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
this.src = src;
|
||||
this.dst = dst;
|
||||
this.loopbackSize = 0;
|
||||
}
|
||||
|
||||
protected void splayRotate(Direction dir) {
|
||||
rotateSecret(dir);
|
||||
}
|
||||
|
||||
protected void redBlackRotate(Direction dir) {
|
||||
rotateSecret(dir.opposite()); // i guess they just use different conventions?
|
||||
}
|
||||
|
||||
private void rotateSecret(Direction dir) {
|
||||
// promote my "dir" child to my level, swap myself down to that level
|
||||
|
||||
// see "Zig step" of https://en.wikipedia.org/wiki/Splay_tree
|
||||
BSTNode child = this.getChild(dir);
|
||||
BSTNode replacementChild = child.getChild(dir.opposite()); // stays at the same level, is just rotated to the other side of the tree
|
||||
this.setChild(dir, replacementChild);
|
||||
if (parent == null) {
|
||||
child.parent = null;
|
||||
} else {
|
||||
parent.setChild(whichChildAmI(), child);
|
||||
}
|
||||
child.setChild(dir.opposite(), this); // e.g. my left child now has me as their right child
|
||||
childUpdated();
|
||||
parent.childUpdated();
|
||||
}
|
||||
|
||||
public static BSTNode concatenate(BSTNode left, BSTNode right) {
|
||||
if (left == null) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
if (right == null) {
|
||||
return left;
|
||||
}
|
||||
return left.walkAncestor().concatenateRoots(right.walkAncestor());
|
||||
}
|
||||
|
||||
protected void afterSwap(BSTNode other) {
|
||||
|
||||
}
|
||||
|
||||
protected void swapLocationWith(BSTNode other) {
|
||||
if (other == this) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
if (other.parent == this) {
|
||||
other.swapLocationWith(this);
|
||||
return;
|
||||
}
|
||||
if (parent == other) {
|
||||
// grandpa
|
||||
// other
|
||||
// this otherChild
|
||||
// left right
|
||||
// and we want that to become
|
||||
// grandpa
|
||||
// this
|
||||
// other otherChild
|
||||
// left right
|
||||
Direction dir = whichChildAmI(); // LEFT, in the above example
|
||||
BSTNode otherChild = other.getChild(dir.opposite());
|
||||
BSTNode left = leftChild;
|
||||
BSTNode right = rightChild;
|
||||
if (other.parent == null) { // grandpa
|
||||
parent = null;
|
||||
} else {
|
||||
other.parent.setChild(other.whichChildAmI(), this);
|
||||
}
|
||||
setChild(dir, other);
|
||||
setChild(dir.opposite(), otherChild);
|
||||
other.setChild(Direction.LEFT, left);
|
||||
other.setChild(Direction.RIGHT, right);
|
||||
other.childUpdated();
|
||||
childUpdated();
|
||||
} else {
|
||||
Direction myDir = parent == null ? null : whichChildAmI();
|
||||
Direction otherDir = other.parent == null ? null : other.whichChildAmI();
|
||||
BSTNode tmpLeft = leftChild;
|
||||
BSTNode tmpRight = rightChild;
|
||||
|
||||
BSTNode tmpParent = parent;
|
||||
if (other.parent == null) { // grandpa
|
||||
parent = null;
|
||||
} else {
|
||||
other.parent.setChild(otherDir, this);
|
||||
}
|
||||
if (tmpParent == null) {
|
||||
other.parent = null;
|
||||
} else {
|
||||
tmpParent.setChild(myDir, other);
|
||||
}
|
||||
|
||||
setChild(Direction.LEFT, other.leftChild);
|
||||
setChild(Direction.RIGHT, other.rightChild);
|
||||
other.setChild(Direction.LEFT, tmpLeft);
|
||||
other.setChild(Direction.RIGHT, tmpRight);
|
||||
calcSize();
|
||||
if (parent != null) {
|
||||
parent.bubbleUpSize();
|
||||
}
|
||||
other.calcSize();
|
||||
if (other.parent != null) {
|
||||
other.parent.bubbleUpSize();
|
||||
}
|
||||
}
|
||||
afterSwap(other);
|
||||
}
|
||||
|
||||
protected abstract BSTNode concatenateRoots(BSTNode right);
|
||||
|
||||
public static BSTNode barrelRollToLowest(BSTNode target) {
|
||||
// 1. chop the tree in half, centered at target
|
||||
// 2. reattach them in the opposite order
|
||||
// in other words, "cut the deck but don't riffle" - leave "target" as the first node (NOT necessarily the root node)
|
||||
BSTNodePair pair = target.disconnect(Direction.RIGHT);
|
||||
if ((target instanceof SplayNode && pair.right != target) || pair.right.parent != null) { // splay to root only happens with a splay tree, obviously
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
// target is now the lowest (leftmost) element of pair.right
|
||||
BSTNode ret = BSTNode.concatenate(pair.right, pair.left); // target is now first, and everything else is still in order :D
|
||||
// use concatenate and not concatenateRoots because pair.left could be null
|
||||
if (ret == target && pair.left != null) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
protected abstract BSTNodePair disconnect(Direction remainOnSide); // chops the tree in half, with "this" remaining on the side "remainOnSide"
|
||||
|
||||
public abstract void remove();
|
||||
|
||||
protected void bubbleUpSize() {
|
||||
int ns = calcSize();
|
||||
if (loopbackSize != ns) {
|
||||
loopbackSize = ns;
|
||||
if (parent != null) {
|
||||
parent.bubbleUpSize();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void childUpdated() {
|
||||
loopbackSize = calcSize();
|
||||
}
|
||||
|
||||
protected void sizeMustBeAccurate() {
|
||||
if (loopbackSize != calcSize()) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
|
||||
protected int calcSize() {
|
||||
int size = 0;
|
||||
if (isLoopback()) {
|
||||
size++;
|
||||
}
|
||||
if (rightChild != null) {
|
||||
size += rightChild.loopbackSize;
|
||||
}
|
||||
if (leftChild != null) {
|
||||
size += leftChild.loopbackSize;
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
protected BSTNode getChild(Direction dir) {
|
||||
return dir == Direction.LEFT ? leftChild : rightChild;
|
||||
}
|
||||
|
||||
protected void setChild(Direction dir, BSTNode newChild) {
|
||||
if (newChild == this) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
if (dir == Direction.LEFT) {
|
||||
leftChild = newChild;
|
||||
} else {
|
||||
rightChild = newChild;
|
||||
}
|
||||
if (newChild != null) {
|
||||
newChild.parent = this;
|
||||
}
|
||||
}
|
||||
|
||||
protected Direction whichChildAmI() {
|
||||
if (parent.leftChild == this) {
|
||||
return Direction.LEFT;
|
||||
}
|
||||
if (parent.rightChild == this) {
|
||||
return Direction.RIGHT;
|
||||
}
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
protected BSTNode walkDescendant(Direction side) {
|
||||
BSTNode child = getChild(side);
|
||||
if (child == null) {
|
||||
return this;
|
||||
} else {
|
||||
return child.walkDescendant(side);
|
||||
}
|
||||
}
|
||||
|
||||
protected BSTNode walkAncestor() {
|
||||
BSTNode walk = this;
|
||||
while (walk.parent != null) {
|
||||
parentWalks++;
|
||||
walk = walk.parent;
|
||||
}
|
||||
parentCalls++;
|
||||
return walk;
|
||||
/*if (parent == null) {
|
||||
return this;
|
||||
} else {
|
||||
return parent.walkAncestor();
|
||||
}*/
|
||||
}
|
||||
|
||||
protected void walk(Consumer<BSTNode> consumer) {
|
||||
if (leftChild != null) {
|
||||
leftChild.walk(consumer);
|
||||
}
|
||||
consumer.accept(this);
|
||||
if (rightChild != null) {
|
||||
rightChild.walk(consumer);
|
||||
}
|
||||
}
|
||||
|
||||
protected BSTNode walkNext() {
|
||||
if (rightChild != null) {
|
||||
return rightChild.walkDescendant(Direction.LEFT);
|
||||
}
|
||||
BSTNode itr = this;
|
||||
while (itr.parent != null && itr.whichChildAmI() == Direction.RIGHT) {
|
||||
itr = itr.parent;
|
||||
}
|
||||
return itr.parent;
|
||||
}
|
||||
|
||||
protected boolean isAlone() {
|
||||
return parent == null && leftChild == null && rightChild == null;
|
||||
}
|
||||
|
||||
public boolean isLoopback() {
|
||||
return src == dst;
|
||||
}
|
||||
}
|
||||
|
||||
public enum Direction { // TODO check if proguard converts this to an int
|
||||
LEFT, RIGHT;
|
||||
|
||||
public Direction opposite() {
|
||||
return this == LEFT ? RIGHT : LEFT;
|
||||
}
|
||||
}
|
||||
|
||||
private static class BSTNodePair {
|
||||
|
||||
final BSTNode left;
|
||||
final BSTNode right;
|
||||
|
||||
private BSTNodePair(BSTNode left, BSTNode right) {
|
||||
this.left = left;
|
||||
this.right = right;
|
||||
if ((left != null && left.parent != null) || (right != null && right.parent != null)) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class TreeEdge {
|
||||
|
||||
private boolean cut;
|
||||
final BSTNode left;
|
||||
final BSTNode right;
|
||||
|
||||
private TreeEdge(BSTNode left, BSTNode right) {
|
||||
this.left = left;
|
||||
this.right = right;
|
||||
}
|
||||
|
||||
private EulerTourForest owner() {
|
||||
return EulerTourForest.this;
|
||||
}
|
||||
}
|
||||
|
||||
private static void mustEq(BSTNode a, BSTNode b) {
|
||||
if (a != b) {
|
||||
throw new IllegalStateException(a + " " + b);
|
||||
}
|
||||
}
|
||||
|
||||
public static void sanityCheck2() {
|
||||
for (int i = 0; i < 9; i++) {
|
||||
int mode = i % 3;
|
||||
int SZ = 700;
|
||||
TreeEdge[] up = new TreeEdge[SZ * SZ];
|
||||
TreeEdge[] right = new TreeEdge[SZ * SZ];
|
||||
EulerTourForest forest = new EulerTourForest(SZ * SZ);
|
||||
for (int y = 0; y < SZ; y++) {
|
||||
for (int x = 0; x < SZ; x++) {
|
||||
if (y != SZ - 1) {
|
||||
try {
|
||||
up[x * SZ + y] = forest.link(x * SZ + y, x * SZ + (y + 1));
|
||||
} catch (IllegalStateException ex) {} // ignore if already linked
|
||||
}
|
||||
if (x != SZ - 1) {
|
||||
try {
|
||||
right[x * SZ + y] = forest.link(x * SZ + y, (x + 1) * SZ + y);
|
||||
} catch (IllegalStateException ex) {} // ignore if already linked
|
||||
}
|
||||
}
|
||||
}
|
||||
Random rand = new Random(5021);
|
||||
for (int x = 0; x < SZ; x++) {
|
||||
int y = SZ / 2;
|
||||
forest.cut(up[x * SZ + y]);
|
||||
//System.out.println("Sz " + forest.size(x * SZ + y));
|
||||
}
|
||||
if (mode == 1) {
|
||||
for (int j = 0; j < SZ * SZ * 2; j++) { // *2 for a fair comparison to during connection, since that one splays both sides of each test
|
||||
((EulerTourForest.SplayNode) forest.loopbacks[rand.nextInt(SZ * SZ)]).splay();
|
||||
}
|
||||
}
|
||||
long a = System.currentTimeMillis();
|
||||
parentCalls = 0; // reset metrics
|
||||
parentWalks = 0;
|
||||
for (int checks = 0; checks < SZ * SZ; checks++) {
|
||||
int v1 = rand.nextInt(SZ * SZ);
|
||||
int v2 = rand.nextInt(SZ * SZ);
|
||||
forest.connected(v1, v2);
|
||||
if (mode == 2) {
|
||||
((SplayNode) forest.loopbacks[v1]).splay();
|
||||
((SplayNode) forest.loopbacks[v2]).splay();
|
||||
}
|
||||
}
|
||||
forest.checkForest(false);
|
||||
if (mode == 0) {
|
||||
System.out.println("WITHOUT random accesses");
|
||||
} else if (mode == 1) {
|
||||
System.out.println("WITH pre-connection random accesses");
|
||||
} else {
|
||||
System.out.println("WITH random accesses during connection");
|
||||
}
|
||||
System.out.println("Walk ancestor was called " + parentCalls + " times, and it traversed " + parentWalks + " in total, implying an average height of " + (parentWalks / (float) parentCalls));
|
||||
System.out.println("Time: " + (System.currentTimeMillis() - a));
|
||||
}
|
||||
}
|
||||
|
||||
public static void sanityCheck() {
|
||||
for (Direction dir : Direction.values()) {
|
||||
System.out.println("Testing zig " + dir);
|
||||
// see "Zig step" of https://en.wikipedia.org/wiki/Splay_tree
|
||||
SplayNode p = new SplayNode();
|
||||
SplayNode x = new SplayNode();
|
||||
SplayNode A = new SplayNode();
|
||||
SplayNode B = new SplayNode();
|
||||
SplayNode C = new SplayNode();
|
||||
p.setChild(dir, x);
|
||||
p.setChild(dir.opposite(), C);
|
||||
x.setChild(dir, A);
|
||||
x.setChild(dir.opposite(), B);
|
||||
|
||||
x.splay();
|
||||
|
||||
mustEq(p.parent, x);
|
||||
mustEq(p.getChild(dir), B);
|
||||
mustEq(p.getChild(dir.opposite()), C);
|
||||
mustEq(x.parent, null);
|
||||
mustEq(x.getChild(dir), A);
|
||||
mustEq(x.getChild(dir.opposite()), p);
|
||||
mustEq(A.parent, x);
|
||||
mustEq(A.getChild(dir), null);
|
||||
mustEq(A.getChild(dir.opposite()), null);
|
||||
mustEq(B.parent, p);
|
||||
mustEq(B.getChild(dir), null);
|
||||
mustEq(B.getChild(dir.opposite()), null);
|
||||
mustEq(C.parent, p);
|
||||
mustEq(C.getChild(dir), null);
|
||||
mustEq(C.getChild(dir.opposite()), null);
|
||||
}
|
||||
for (Direction dir : Direction.values()) {
|
||||
System.out.println("Testing zig-zig " + dir);
|
||||
// see "Zig-zig step" of https://en.wikipedia.org/wiki/Splay_tree
|
||||
SplayNode g = new SplayNode();
|
||||
SplayNode p = new SplayNode();
|
||||
SplayNode x = new SplayNode();
|
||||
SplayNode A = new SplayNode();
|
||||
SplayNode B = new SplayNode();
|
||||
SplayNode C = new SplayNode();
|
||||
SplayNode D = new SplayNode();
|
||||
g.setChild(dir, p);
|
||||
g.setChild(dir.opposite(), D);
|
||||
p.setChild(dir, x);
|
||||
p.setChild(dir.opposite(), C);
|
||||
x.setChild(dir, A);
|
||||
x.setChild(dir.opposite(), B);
|
||||
|
||||
x.splay();
|
||||
|
||||
mustEq(g.parent, p);
|
||||
mustEq(g.getChild(dir), C);
|
||||
mustEq(g.getChild(dir.opposite()), D);
|
||||
mustEq(p.parent, x);
|
||||
mustEq(p.getChild(dir), B);
|
||||
mustEq(p.getChild(dir.opposite()), g);
|
||||
mustEq(x.parent, null);
|
||||
mustEq(x.getChild(dir), A);
|
||||
mustEq(x.getChild(dir.opposite()), p);
|
||||
mustEq(A.parent, x);
|
||||
mustEq(A.getChild(dir), null);
|
||||
mustEq(A.getChild(dir.opposite()), null);
|
||||
mustEq(B.parent, p);
|
||||
mustEq(B.getChild(dir), null);
|
||||
mustEq(B.getChild(dir.opposite()), null);
|
||||
mustEq(C.parent, g);
|
||||
mustEq(C.getChild(dir), null);
|
||||
mustEq(C.getChild(dir.opposite()), null);
|
||||
mustEq(D.parent, g);
|
||||
mustEq(D.getChild(dir), null);
|
||||
mustEq(D.getChild(dir.opposite()), null);
|
||||
}
|
||||
for (Direction dir : Direction.values()) {
|
||||
System.out.println("Testing zig-zag " + dir);
|
||||
// see "Zig-zag step" of https://en.wikipedia.org/wiki/Splay_tree
|
||||
SplayNode g = new SplayNode();
|
||||
SplayNode p = new SplayNode();
|
||||
SplayNode x = new SplayNode();
|
||||
SplayNode A = new SplayNode();
|
||||
SplayNode B = new SplayNode();
|
||||
SplayNode C = new SplayNode();
|
||||
SplayNode D = new SplayNode();
|
||||
g.setChild(dir, p);
|
||||
g.setChild(dir.opposite(), D);
|
||||
p.setChild(dir, A);
|
||||
p.setChild(dir.opposite(), x);
|
||||
x.setChild(dir, B);
|
||||
x.setChild(dir.opposite(), C);
|
||||
|
||||
x.splay();
|
||||
|
||||
mustEq(g.parent, x);
|
||||
mustEq(g.getChild(dir), C);
|
||||
mustEq(g.getChild(dir.opposite()), D);
|
||||
mustEq(p.parent, x);
|
||||
mustEq(p.getChild(dir), A);
|
||||
mustEq(p.getChild(dir.opposite()), B);
|
||||
mustEq(x.parent, null);
|
||||
mustEq(x.getChild(dir), p);
|
||||
mustEq(x.getChild(dir.opposite()), g);
|
||||
mustEq(A.parent, p);
|
||||
mustEq(A.getChild(dir), null);
|
||||
mustEq(A.getChild(dir.opposite()), null);
|
||||
mustEq(B.parent, p);
|
||||
mustEq(B.getChild(dir), null);
|
||||
mustEq(B.getChild(dir.opposite()), null);
|
||||
mustEq(C.parent, g);
|
||||
mustEq(C.getChild(dir), null);
|
||||
mustEq(C.getChild(dir.opposite()), null);
|
||||
mustEq(D.parent, g);
|
||||
mustEq(D.getChild(dir), null);
|
||||
mustEq(D.getChild(dir.opposite()), null);
|
||||
}
|
||||
for (Direction GtoP : Direction.values()) {
|
||||
for (Direction PtoX : Direction.values()) {
|
||||
System.out.println("Testing connected swap " + GtoP + " " + PtoX);
|
||||
SplayNode g = new SplayNode();
|
||||
SplayNode p = new SplayNode();
|
||||
SplayNode x = new SplayNode();
|
||||
SplayNode A = new SplayNode();
|
||||
SplayNode B = new SplayNode();
|
||||
SplayNode C = new SplayNode();
|
||||
SplayNode D = new SplayNode();
|
||||
g.setChild(GtoP, p);
|
||||
g.setChild(GtoP.opposite(), D);
|
||||
p.setChild(PtoX, x);
|
||||
p.setChild(PtoX.opposite(), A);
|
||||
x.setChild(Direction.LEFT, B);
|
||||
x.setChild(Direction.RIGHT, C);
|
||||
|
||||
/*x.black = true;
|
||||
p.black = false;*/
|
||||
p.swapLocationWith(x);
|
||||
|
||||
/*if (x.black || !p.black) {
|
||||
throw new IllegalStateException();
|
||||
}*/
|
||||
mustEq(g.parent, null);
|
||||
mustEq(g.getChild(GtoP), x);
|
||||
mustEq(g.getChild(GtoP.opposite()), D);
|
||||
mustEq(p.parent, x);
|
||||
mustEq(p.getChild(Direction.LEFT), B);
|
||||
mustEq(p.getChild(Direction.RIGHT), C);
|
||||
mustEq(x.parent, g);
|
||||
mustEq(x.getChild(PtoX), p);
|
||||
mustEq(x.getChild(PtoX.opposite()), A);
|
||||
mustEq(A.parent, x);
|
||||
mustEq(A.getChild(Direction.LEFT), null);
|
||||
mustEq(A.getChild(Direction.RIGHT), null);
|
||||
mustEq(B.parent, p);
|
||||
mustEq(B.getChild(Direction.LEFT), null);
|
||||
mustEq(B.getChild(Direction.RIGHT), null);
|
||||
mustEq(C.parent, p);
|
||||
mustEq(C.getChild(Direction.LEFT), null);
|
||||
mustEq(C.getChild(Direction.RIGHT), null);
|
||||
mustEq(D.parent, g);
|
||||
mustEq(D.getChild(Direction.LEFT), null);
|
||||
mustEq(D.getChild(Direction.RIGHT), null);
|
||||
}
|
||||
}
|
||||
for (Direction APtoA : Direction.values()) {
|
||||
for (Direction BPtoB : Direction.values()) {
|
||||
System.out.println("Testing disconnected swap " + APtoA + " " + BPtoB);
|
||||
SplayNode ap = new SplayNode();
|
||||
SplayNode apoc = new SplayNode();
|
||||
SplayNode a = new SplayNode();
|
||||
SplayNode alc = new SplayNode();
|
||||
SplayNode arc = new SplayNode();
|
||||
SplayNode bp = new SplayNode();
|
||||
SplayNode bpoc = new SplayNode();
|
||||
SplayNode b = new SplayNode();
|
||||
SplayNode blc = new SplayNode();
|
||||
SplayNode brc = new SplayNode();
|
||||
ap.setChild(APtoA, a);
|
||||
ap.setChild(APtoA.opposite(), apoc);
|
||||
a.setChild(Direction.LEFT, alc);
|
||||
a.setChild(Direction.RIGHT, arc);
|
||||
bp.setChild(BPtoB, b);
|
||||
bp.setChild(BPtoB.opposite(), bpoc);
|
||||
b.setChild(Direction.LEFT, blc);
|
||||
b.setChild(Direction.RIGHT, brc);
|
||||
|
||||
/*a.black = true;
|
||||
b.black = false;*/
|
||||
a.swapLocationWith(b);
|
||||
|
||||
/*if (a.black || !b.black) {
|
||||
throw new IllegalStateException();
|
||||
}*/
|
||||
mustEq(ap.parent, null);
|
||||
mustEq(ap.getChild(APtoA), b);
|
||||
mustEq(ap.getChild(APtoA.opposite()), apoc);
|
||||
mustEq(apoc.parent, ap);
|
||||
mustEq(apoc.getChild(Direction.LEFT), null);
|
||||
mustEq(apoc.getChild(Direction.RIGHT), null);
|
||||
mustEq(a.parent, bp);
|
||||
mustEq(a.getChild(Direction.LEFT), blc);
|
||||
mustEq(a.getChild(Direction.RIGHT), brc);
|
||||
mustEq(alc.parent, b);
|
||||
mustEq(alc.getChild(Direction.LEFT), null);
|
||||
mustEq(alc.getChild(Direction.RIGHT), null);
|
||||
mustEq(arc.parent, b);
|
||||
mustEq(arc.getChild(Direction.LEFT), null);
|
||||
mustEq(arc.getChild(Direction.RIGHT), null);
|
||||
mustEq(bp.parent, null);
|
||||
mustEq(bp.getChild(BPtoB), a);
|
||||
mustEq(bp.getChild(BPtoB.opposite()), bpoc);
|
||||
mustEq(bpoc.parent, bp);
|
||||
mustEq(bpoc.getChild(Direction.LEFT), null);
|
||||
mustEq(bpoc.getChild(Direction.RIGHT), null);
|
||||
mustEq(b.parent, ap);
|
||||
mustEq(b.getChild(Direction.LEFT), alc);
|
||||
mustEq(b.getChild(Direction.RIGHT), arc);
|
||||
mustEq(blc.parent, a);
|
||||
mustEq(blc.getChild(Direction.LEFT), null);
|
||||
mustEq(blc.getChild(Direction.RIGHT), null);
|
||||
mustEq(brc.parent, a);
|
||||
mustEq(brc.getChild(Direction.LEFT), null);
|
||||
mustEq(brc.getChild(Direction.RIGHT), null);
|
||||
}
|
||||
}
|
||||
{
|
||||
Random rand = new Random(5021);
|
||||
List<Supplier<BSTNode>> constructors = Arrays.asList(SplayNode::new/*, SplayNode::new*/);
|
||||
for (int run = 0; run < 10; run++) {
|
||||
int NODES = 10000;
|
||||
Supplier<BSTNode> toUse = constructors.get(run % constructors.size());
|
||||
List<BSTNode> nodes = new ArrayList<>();
|
||||
{
|
||||
BSTNode root = toUse.get();
|
||||
nodes.add(root);
|
||||
for (int i = 1; i < NODES; i++) {
|
||||
nodes.add(toUse.get());
|
||||
root = BSTNode.concatenate(root, nodes.get(i));
|
||||
}
|
||||
}
|
||||
int shuffledBy = 0;
|
||||
for (int ii = 0; ii < 10000; ii++) {
|
||||
if (rand.nextBoolean()) {
|
||||
BSTNode root = nodes.get(rand.nextInt(NODES));
|
||||
if (root instanceof SplayNode) {
|
||||
((SplayNode) root).splay();
|
||||
if (root != nodes.get(rand.nextInt(NODES)).walkAncestor() || root.loopbackSize != NODES) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
} else {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
if (rand.nextBoolean()) {
|
||||
shuffledBy = rand.nextInt(NODES);
|
||||
BSTNode root = BSTNode.barrelRollToLowest(nodes.get(shuffledBy));
|
||||
if (root != nodes.get(rand.nextInt(NODES)).walkAncestor() || root.loopbackSize != NODES) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
if (rand.nextBoolean()) {
|
||||
int pos = rand.nextBoolean() ? (shuffledBy + NODES + rand.nextInt(10) - 5) % NODES : rand.nextInt(NODES);
|
||||
BSTNode remove = nodes.remove(pos);
|
||||
NODES--;
|
||||
remove.remove();
|
||||
if (shuffledBy > pos) {
|
||||
shuffledBy--;
|
||||
}
|
||||
}
|
||||
List<BSTNode> order = new ArrayList<>(NODES);
|
||||
nodes.get(rand.nextInt(NODES)).walkAncestor().walk(order::add);
|
||||
for (int n = 0; n < NODES; n++) {
|
||||
if (order.get(n) != nodes.get((n + shuffledBy) % NODES)) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
order.get(n).sizeMustBeAccurate();
|
||||
if (order.get(n).walkNext() != (n < NODES - 1 ? order.get(n + 1) : null)) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
// slide 22 of https://web.stanford.edu/class/archive/cs/cs166/cs166.1166/lectures/17/Small17.pdf
|
||||
EulerTourForest forest = new EulerTourForest(11);
|
||||
forest.link(0, 1);
|
||||
forest.link(1, 3);
|
||||
forest.link(2, 4);
|
||||
forest.link(1, 2);
|
||||
TreeEdge toCut = forest.link(0, 5);
|
||||
forest.link(5, 6);
|
||||
forest.link(6, 9);
|
||||
forest.link(9, 10);
|
||||
forest.link(9, 8);
|
||||
forest.link(6, 7);
|
||||
BSTNode.barrelRollToLowest(forest.loopbacks[0]);
|
||||
if (!forest.checkForest(true).equals("abdbcecbafgjkjijghgf")) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
forest.cut(toCut);
|
||||
if (!forest.checkForest(true).equals("abdbcecb fgjkjijghg")) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
{
|
||||
// slide 26 of https://web.stanford.edu/class/archive/cs/cs166/cs166.1166/lectures/17/Small17.pdf
|
||||
EulerTourForest forest = new EulerTourForest(11);
|
||||
forest.link(2, 4);
|
||||
TreeEdge toCut = forest.link(2, 1);
|
||||
forest.link(1, 0);
|
||||
forest.link(1, 3);
|
||||
forest.link(2, 6);
|
||||
forest.link(6, 9);
|
||||
forest.link(9, 10);
|
||||
forest.link(9, 8);
|
||||
forest.link(6, 7);
|
||||
forest.link(5, 6);
|
||||
BSTNode.barrelRollToLowest(forest.loopbacks[2]);
|
||||
if (!forest.checkForest(true).equals("cecbabdbcgjkjijghgfg")) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
forest.cut(toCut);
|
||||
if (!forest.checkForest(true).equals("babd cgjkjijghgfgce")) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public String checkForest(boolean verbose) {
|
||||
boolean[] seen = new boolean[loopbacks.length];
|
||||
StringBuilder ret = new StringBuilder();
|
||||
for (int vert = 0; vert < loopbacks.length; vert++) {
|
||||
if (seen[vert]) {
|
||||
continue;
|
||||
}
|
||||
List<BSTNode> order = new ArrayList<>();
|
||||
loopbacks[vert].walkAncestor().walk(order::add);
|
||||
for (int i = 0; i < order.size(); i++) {
|
||||
if (verbose) {
|
||||
System.out.print("(" + (char) ('a' + order.get(i).src) + "," + (char) ('a' + order.get(i).dst) + ") ");
|
||||
}
|
||||
if (order.get(i).dst != order.get((i + 1) % order.size()).src) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
if (order.get(i).isLoopback()) {
|
||||
seen[order.get(i).src] = true;
|
||||
} else {
|
||||
ret.append((char) ('a' + order.get(i).src));
|
||||
}
|
||||
}
|
||||
if (verbose) {
|
||||
System.out.println();
|
||||
}
|
||||
ret.append(" ");
|
||||
if (!seen[vert]) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
if (verbose) {
|
||||
System.out.println(ret);
|
||||
}
|
||||
return ret.toString().trim();
|
||||
}
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
/*
|
||||
* This file is part of Baritone.
|
||||
*
|
||||
* Baritone is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Baritone is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Baritone. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package baritone.builder;
|
||||
|
||||
import baritone.api.utils.BetterBlockPos;
|
||||
import net.minecraft.util.EnumFacing;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* I hate porting things to new versions of Minecraft
|
||||
* <p>
|
||||
* So just like BetterBlockPos, we now have Face
|
||||
*/
|
||||
public enum Face {
|
||||
DOWN, UP, NORTH, SOUTH, WEST, EAST;
|
||||
public final int index = ordinal();
|
||||
public final int oppositeIndex = opposite(index);
|
||||
public final int x = toMC().getXOffset();
|
||||
public final int y = toMC().getYOffset();
|
||||
public final int z = toMC().getZOffset();
|
||||
public final long offset = BetterBlockPos.toLong(x, y, z); // both the previous three lines (avoidably) and this one (unavoidably due to BlockPos superclass of BetterBlockPos) mess up test timing / profiling because it calls the <clinit> of both EnumFacing and BlockPos which does some Log4j bs lol
|
||||
public final int[] vec = new int[]{x, y, z};
|
||||
public final boolean vertical = y != 0;
|
||||
public final int horizontalIndex = x & 1 | (x | z) & 2;
|
||||
public static final int NUM_FACES = 6;
|
||||
public static final Face[] VALUES = new Face[NUM_FACES];
|
||||
public static final Face[] HORIZONTALS = new Face[4];
|
||||
public static final List<Optional<Face>> OPTS;
|
||||
public static final long[] OFFSETS = new long[NUM_FACES];
|
||||
|
||||
static {
|
||||
List<Optional<Face>> lst = new ArrayList<>();
|
||||
for (Face face : values()) {
|
||||
VALUES[face.index] = face;
|
||||
OFFSETS[face.index] = face.offset;
|
||||
lst.add(Optional.of(face));
|
||||
HORIZONTALS[face.horizontalIndex] = face;
|
||||
}
|
||||
lst.add(Optional.empty());
|
||||
OPTS = Collections.unmodifiableList(lst);
|
||||
}
|
||||
|
||||
public final EnumFacing toMC() {
|
||||
return EnumFacing.byIndex(index);
|
||||
}
|
||||
|
||||
public final Face opposite() {
|
||||
return VALUES[oppositeIndex];
|
||||
}
|
||||
|
||||
public final long offset(long pos) {
|
||||
return (pos + offset) & BetterBlockPos.POST_ADDITION_MASK;
|
||||
}
|
||||
|
||||
public static long offset(long pos, int face) {
|
||||
return (pos + OFFSETS[face]) & BetterBlockPos.POST_ADDITION_MASK;
|
||||
}
|
||||
|
||||
public static int opposite(int face) {
|
||||
return face ^ 1;
|
||||
}
|
||||
|
||||
public static int oppositeHorizontal(int horizontalIndex) {
|
||||
return horizontalIndex ^ 2;
|
||||
}
|
||||
|
||||
public static Face between(long from, long to) {
|
||||
for (int i = 0; i < NUM_FACES; i++) {
|
||||
if (offset(from, i) == to) {
|
||||
return VALUES[i];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
/*
|
||||
* This file is part of Baritone.
|
||||
*
|
||||
* Baritone is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Baritone is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Baritone. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package baritone.builder;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
/**
|
||||
* There will be a BlockStateCachedData for every IBlockState in the game, yes.
|
||||
* <p>
|
||||
* But, we need some additional BlockStateCachedDatas. For example, we need one that represents hypothetical scaffolding, and another for air, for properly computing place order dependency graphs. Some other places need a placeholder for out of bounds.
|
||||
* <p>
|
||||
* We could just say that scaffolding is always, like, dirt, or something. But that would be inelegant.
|
||||
* <p>
|
||||
* And beyond the needs at runtime, unit tests shouldn't need to bootstrap and boot up the entire Minecraft game. So, let's have some fake BlockStateCachedData for testing purposes too!
|
||||
*/
|
||||
public class FakeStates {
|
||||
// the three aformentioned placeholders for runtime
|
||||
public static final BlockStateCachedData SCAFFOLDING = new BlockStateCachedData(new BlockStateCachedDataBuilder().collidesWithPlayer(true).fullyWalkableTop().collisionHeight(1).canPlaceAgainstMe());
|
||||
// need a second solid block so that "== FakeStates.SCAFFOLDING" doesn't get tricked
|
||||
public static final BlockStateCachedData SOLID = new BlockStateCachedData(new BlockStateCachedDataBuilder().collidesWithPlayer(true).fullyWalkableTop().collisionHeight(1).canPlaceAgainstMe());
|
||||
public static final BlockStateCachedData AIR = new BlockStateCachedData(new BlockStateCachedDataBuilder().setAir());
|
||||
public static final BlockStateCachedData OUT_OF_BOUNDS = new BlockStateCachedData(new BlockStateCachedDataBuilder().collidesWithPlayer(true).collisionHeight(1));
|
||||
|
||||
// and some for testing
|
||||
public static final BlockStateCachedData[] BY_HEIGHT;
|
||||
|
||||
static {
|
||||
BY_HEIGHT = new BlockStateCachedData[Blip.FULL_BLOCK + 1];
|
||||
for (int height = 1; height <= Blip.FULL_BLOCK; height++) {
|
||||
BY_HEIGHT[height] = new BlockStateCachedData(new BlockStateCachedDataBuilder().collidesWithPlayer(true).fullyWalkableTop().collisionHeight(height * Blip.RATIO));
|
||||
}
|
||||
BY_HEIGHT[0] = AIR;
|
||||
}
|
||||
|
||||
private static List<BlockStateCachedData> PROBABLE_BLOCKS = null;
|
||||
|
||||
private static List<BlockStateCachedData> getProbableBlocks() {
|
||||
if (PROBABLE_BLOCKS == null) {
|
||||
//long a = System.currentTimeMillis();
|
||||
Random rand = new Random(5021);
|
||||
PROBABLE_BLOCKS = IntStream.range(0, 10000).mapToObj($ -> {
|
||||
List<BlockStatePlacementOption> ret = new ArrayList<>(SCAFFOLDING.placeMe);
|
||||
ret.removeIf($$ -> rand.nextInt(10) < 2);
|
||||
BlockStateCachedDataBuilder builder = new BlockStateCachedDataBuilder() {
|
||||
@Override
|
||||
public List<BlockStatePlacementOption> howCanIBePlaced() {
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
if (ret.isEmpty()) {
|
||||
builder.placementLogicNotImplementedYet();
|
||||
}
|
||||
return new BlockStateCachedData(builder
|
||||
.fullyWalkableTop()
|
||||
.collisionHeight(1)
|
||||
.canPlaceAgainstMe()
|
||||
.collidesWithPlayer(true));
|
||||
}).collect(Collectors.toList());
|
||||
//System.out.println("Took " + (System.currentTimeMillis() - a));
|
||||
}
|
||||
return PROBABLE_BLOCKS;
|
||||
}
|
||||
|
||||
public static BlockStateCachedData probablyCanBePlaced(Random rand) {
|
||||
return getProbableBlocks().get(rand.nextInt(getProbableBlocks().size()));
|
||||
}
|
||||
|
||||
// probably more will go here such as for making sure that slabs and stairs work right (like there'll be a fake slab and a fake stair i guess?)
|
||||
}
|
||||
@@ -1,298 +0,0 @@
|
||||
/*
|
||||
* This file is part of Baritone.
|
||||
*
|
||||
* Baritone is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Baritone is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Baritone. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package baritone.builder;
|
||||
|
||||
import baritone.api.utils.BetterBlockPos;
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class GreedySolver {
|
||||
|
||||
private final SolverEngineInput engineInput;
|
||||
private final NodeBinaryHeap heap = new NodeBinaryHeap();
|
||||
private final Long2ObjectOpenHashMap<Node> nodes = new Long2ObjectOpenHashMap<>();
|
||||
private final ZobristWorldStateCache zobristCache;
|
||||
private final long allCompleted;
|
||||
private final Bounds bounds;
|
||||
private Column scratchpadExpandNode1 = new Column();
|
||||
private Column scratchpadExpandNode2 = new Column();
|
||||
private Column scratchpadExpandNode3 = new Column();
|
||||
|
||||
public GreedySolver(SolverEngineInput input) {
|
||||
this.engineInput = input;
|
||||
this.bounds = engineInput.graph.bounds();
|
||||
this.zobristCache = new ZobristWorldStateCache(new WorldState.WorldStateWrappedSubstrate(engineInput));
|
||||
Node root = new Node(engineInput.player, null, 0L, -1L, 0);
|
||||
nodes.put(root.nodeMapKey(), root);
|
||||
heap.insert(root);
|
||||
this.allCompleted = WorldState.predetermineGoalZobrist(engineInput.allToPlaceNow);
|
||||
}
|
||||
|
||||
synchronized SolverEngineOutput search() {
|
||||
while (!heap.isEmpty()) {
|
||||
Node node = heap.removeLowest();
|
||||
if (!node.sneaking() && node.worldStateZobristHash == allCompleted) {
|
||||
return backwards(node);
|
||||
}
|
||||
expandNode(node);
|
||||
}
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
private SolverEngineOutput backwards(Node node) {
|
||||
ArrayDeque<SolvedActionStep> steps = new ArrayDeque<>();
|
||||
while (node.previous != null) {
|
||||
steps.addFirst(step(node, node.previous));
|
||||
node = node.previous;
|
||||
}
|
||||
return new SolverEngineOutput(new ArrayList<>(steps));
|
||||
}
|
||||
|
||||
private SolvedActionStep step(Node next, Node prev) {
|
||||
if (next.worldStateZobristHash == prev.worldStateZobristHash) {
|
||||
return new SolvedActionStep(next.pos());
|
||||
} else {
|
||||
return new SolvedActionStep(next.pos(), WorldState.unzobrist(prev.worldStateZobristHash ^ next.worldStateZobristHash));
|
||||
}
|
||||
}
|
||||
|
||||
private boolean wantToPlaceAt(long blockGoesAt, Node vantage, int blipsWithinVoxel, WorldState worldState) {
|
||||
if (worldState.blockExists(blockGoesAt)) {
|
||||
return false;
|
||||
}
|
||||
long vpos = vantage.pos();
|
||||
int relativeX = BetterBlockPos.XfromLong(vpos) - BetterBlockPos.XfromLong(blockGoesAt);
|
||||
int relativeY = blipsWithinVoxel + Blip.FULL_BLOCK * (BetterBlockPos.YfromLong(vpos) - BetterBlockPos.YfromLong(blockGoesAt));
|
||||
int relativeZ = BetterBlockPos.ZfromLong(vpos) - BetterBlockPos.ZfromLong(blockGoesAt);
|
||||
BlockStateCachedData blockBeingPlaced = engineInput.graph.data(blockGoesAt);
|
||||
for (BlockStatePlacementOption option : blockBeingPlaced.placeMe) {
|
||||
long maybePlaceAgainst = option.against.offset(blockGoesAt);
|
||||
if (!bounds.inRangePos(maybePlaceAgainst)) {
|
||||
continue;
|
||||
}
|
||||
if (!worldState.blockExists(maybePlaceAgainst)) {
|
||||
continue;
|
||||
}
|
||||
BlockStateCachedData placingAgainst = engineInput.graph.data(maybePlaceAgainst);
|
||||
PlaceAgainstData againstData = placingAgainst.againstMe(option);
|
||||
traces:
|
||||
for (Raytracer.Raytrace trace : option.computeTraceOptions(againstData, relativeX, relativeY, relativeZ, PlayerVantage.LOOSE_CENTER, blockReachDistance())) { // TODO or only take the best one
|
||||
for (long l : trace.passedThrough) {
|
||||
if (worldState.blockExists(l)) {
|
||||
continue traces;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void expandNode(Node node) {
|
||||
WorldState worldState = zobristCache.coalesceState(node);
|
||||
long pos = node.pos();
|
||||
Column within = scratchpadExpandNode1;
|
||||
within.initFrom(pos, worldState, engineInput);
|
||||
|
||||
Column supportedBy;
|
||||
boolean sneaking = node.sneaking();
|
||||
if (sneaking) {
|
||||
supportedBy = scratchpadExpandNode3;
|
||||
long supportedFeetVoxel = SneakPosition.sneakDirectionFromPlayerToSupportingBlock(node.sneakingPosition()).offset(pos);
|
||||
supportedBy.initFrom(supportedFeetVoxel, worldState, engineInput);
|
||||
if (Main.DEBUG && !within.okToSneakIntoHereAtHeight(supportedBy.feetBlips)) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
} else {
|
||||
supportedBy = within;
|
||||
}
|
||||
|
||||
int playerFeet = supportedBy.feetBlips;
|
||||
if (Main.DEBUG && !supportedBy.playerCanExistAtFootBlip(playerFeet)) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------------------------------------------
|
||||
// place block beneath or within feet voxel
|
||||
for (int dy = -1; dy <= 0; dy++) {
|
||||
// this is the common case for sneak bridging with full blocks
|
||||
long maybePlaceAt = BetterBlockPos.offsetBy(pos, 0, dy, 0);
|
||||
BlockStateCachedData wouldBePlaced = engineInput.graph.data(maybePlaceAt);
|
||||
int cost = blockPlaceCost();
|
||||
int playerFeetWouldBeAt = playerFeet;
|
||||
/*if (wouldBePlaced.collidesWithPlayer) {
|
||||
int heightRelativeToCurrentVoxel = wouldBePlaced.collisionHeightBlips() + dy * Blip.FULL_BLOCK;
|
||||
if (heightRelativeToCurrentVoxel > playerFeet) {
|
||||
// we would need to jump in order to do this
|
||||
cost += jumpCost();
|
||||
playerFeetWouldBeAt = heightRelativeToCurrentVoxel; // because we'd have to jump, and could only place the block once we had cleared the collision box for it
|
||||
if (!within.playerCanExistAtFootBlip(heightRelativeToCurrentVoxel) || !supportedBy.playerCanExistAtFootBlip(heightRelativeToCurrentVoxel)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (wantToPlaceAt(maybePlaceAt, node, playerFeetWouldBeAt, worldState)) {
|
||||
upsertEdge(node, worldState, pos, null, maybePlaceAt, cost);
|
||||
}*/
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------------------------------------------
|
||||
if (sneaking) {
|
||||
// we can walk back to where we were
|
||||
upsertEdge(node, worldState, supportedBy.pos, null, -1, flatCost());
|
||||
// this will probably rarely be used. i can only imagine rare scenarios such as needing the extra perspective in order to place a block a bit more efficiently. like, this could avoid unnecessary ancillary scaffolding i suppose.
|
||||
// ----
|
||||
// also let's try just letting ourselves fall off the edge of the block
|
||||
int descendBy = PlayerPhysics.playerFalls(pos, worldState, engineInput);
|
||||
if (descendBy != -1) {
|
||||
upsertEdge(node, worldState, BetterBlockPos.offsetBy(pos, 0, -descendBy, 0), null, -1, fallCost(descendBy));
|
||||
}
|
||||
return;
|
||||
}
|
||||
// not sneaking! sneaking returned ^^
|
||||
// -------------------------------------------------------------------------------------------------------------
|
||||
// walk sideways and either stay level, ascend, or descend
|
||||
Column into = scratchpadExpandNode2;
|
||||
for (Face travel : Face.HORIZONTALS) {
|
||||
long newPos = travel.offset(pos);
|
||||
into.initFrom(newPos, worldState, engineInput);
|
||||
PlayerPhysics.Collision collision = PlayerPhysics.playerTravelCollides(within, into);
|
||||
switch (collision) {
|
||||
case BLOCKED: {
|
||||
continue;
|
||||
}
|
||||
case FALL: {
|
||||
upsertEdge(node, worldState, newPos, travel, -1, flatCost()); // sneak off edge of block
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
long realNewPos = BetterBlockPos.offsetBy(newPos, 0, collision.voxelVerticalOffset(), 0);
|
||||
upsertEdge(node, worldState, realNewPos, null, -1, collision.requiresJump() ? jumpCost() : flatCost());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int fallCost(int blocks) {
|
||||
if (blocks < 1) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
private int flatCost() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
private int jumpCost() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
private double blockReachDistance() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
private void upsertEdge(Node node, WorldState worldState, long newPlayerPosition, Face sneakingTowards, long blockPlacement, int edgeCost) {
|
||||
Node neighbor = getNode(newPlayerPosition, sneakingTowards, node, worldState, blockPlacement);
|
||||
if (Main.SLOW_DEBUG && blockPlacement != -1 && !zobristCache.coalesceState(neighbor).blockExists(blockPlacement)) { // only in slow_debug because this force-allocates a WorldState for every neighbor of every node!
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
if (Main.DEBUG && node == neighbor) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
updateNeighbor(node, neighbor, edgeCost);
|
||||
}
|
||||
|
||||
private void updateNeighbor(Node node, Node neighbor, int edgeCost) {
|
||||
int currentCost = neighbor.cost;
|
||||
int offeredCost = node.cost + edgeCost;
|
||||
if (currentCost < offeredCost) {
|
||||
return;
|
||||
}
|
||||
neighbor.previous = node;
|
||||
neighbor.cost = offeredCost;
|
||||
neighbor.combinedCost = offeredCost + neighbor.heuristic;
|
||||
if (Main.DEBUG && neighbor.combinedCost < Integer.MIN_VALUE / 2) { // simple attempt to catch obvious overflow
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
if (neighbor.inHeap()) {
|
||||
heap.update(neighbor);
|
||||
} else {
|
||||
heap.insert(neighbor);
|
||||
}
|
||||
}
|
||||
|
||||
private int calculateHeuristicModifier(WorldState previous, long blockPlacedAt) {
|
||||
if (Main.DEBUG && previous.blockExists(blockPlacedAt)) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
if (true) {
|
||||
throw new UnsupportedOperationException("tune the values first lol");
|
||||
}
|
||||
switch (engineInput.desiredToBePlaced(blockPlacedAt)) {
|
||||
case PART_OF_CURRENT_GOAL:
|
||||
case SCAFFOLDING_OF_CURRENT_GOAL:
|
||||
return -100; // keep kitten on task
|
||||
case PART_OF_FUTURE_GOAL:
|
||||
return -10; // smaller kitten treat for working ahead
|
||||
case SCAFFOLDING_OF_FUTURE_GOAL:
|
||||
return -5; // smallest kitten treat for working ahead on scaffolding
|
||||
case ANCILLARY:
|
||||
return 0; // no kitten treat for placing a random extra block
|
||||
default:
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
|
||||
private int blockPlaceCost() {
|
||||
// maybe like... ten?
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
private Node getNode(long playerPosition, Face sneakingTowards, Node prev, WorldState prevWorld, long blockPlacement) {
|
||||
if (Main.DEBUG && blockPlacement != -1 && prevWorld.blockExists(blockPlacement)) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
long worldStateZobristHash = prev.worldStateZobristHash;
|
||||
if (blockPlacement != -1) {
|
||||
worldStateZobristHash = WorldState.updateZobrist(worldStateZobristHash, blockPlacement);
|
||||
}
|
||||
long code = SneakPosition.encode(playerPosition, sneakingTowards) ^ worldStateZobristHash;
|
||||
Node existing = nodes.get(code);
|
||||
if (existing != null) {
|
||||
return existing;
|
||||
}
|
||||
int newHeuristic = prev.heuristic;
|
||||
if (blockPlacement != -1) {
|
||||
newHeuristic += calculateHeuristicModifier(prevWorld, blockPlacement);
|
||||
}
|
||||
Node node = new Node(playerPosition, null, worldStateZobristHash, blockPlacement, newHeuristic);
|
||||
if (Main.DEBUG && node.nodeMapKey() != code) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
nodes.put(code, node);
|
||||
return node;
|
||||
}
|
||||
|
||||
public BlockStateCachedData at(long pos, WorldState inWorldState) {
|
||||
return engineInput.at(pos, inWorldState);
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
/*
|
||||
* This file is part of Baritone.
|
||||
*
|
||||
* Baritone is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Baritone is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Baritone. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package baritone.builder;
|
||||
|
||||
public interface IBlockStateDataProvider {
|
||||
|
||||
int numStates();
|
||||
|
||||
BlockStateCachedData getNullable(int i);
|
||||
|
||||
default BlockStateCachedData[] allNullable() {
|
||||
BlockStateCachedData[] ret = new BlockStateCachedData[numStates()];
|
||||
RuntimeException ex = null;
|
||||
for (int i = 0; i < ret.length; i++) {
|
||||
try {
|
||||
ret[i] = getNullable(i);
|
||||
} catch (RuntimeException e) {
|
||||
if (ex != null) {
|
||||
ex.printStackTrace(); // printstacktrace all but the one that we throw
|
||||
}
|
||||
ex = e;
|
||||
}
|
||||
}
|
||||
if (ex != null) {
|
||||
throw ex; // throw the last one
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
/*
|
||||
* This file is part of Baritone.
|
||||
*
|
||||
* Baritone is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Baritone is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Baritone. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package baritone.builder;
|
||||
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.longs.LongList;
|
||||
|
||||
public interface IScaffolderStrategy {
|
||||
// TODO passing in DGSO is not ideal because it's mutable
|
||||
// TODO should it instead take in a list of (all) possible roots?
|
||||
LongList scaffoldTo(DependencyGraphScaffoldingOverlay.CollapsedDependencyGraph.CollapsedDependencyGraphComponent root, DependencyGraphScaffoldingOverlay overlayGraph);
|
||||
}
|
||||
@@ -1,346 +0,0 @@
|
||||
/*
|
||||
* This file is part of Baritone.
|
||||
*
|
||||
* Baritone is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Baritone is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Baritone. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package baritone.builder;
|
||||
|
||||
import baritone.api.pathing.movement.ActionCosts;
|
||||
import baritone.api.utils.BetterBlockPos;
|
||||
import baritone.builder.mc.DebugStates;
|
||||
import baritone.builder.mc.VanillaBlockStateDataProvider;
|
||||
import it.unimi.dsi.fastutil.doubles.DoubleArrayList;
|
||||
import it.unimi.dsi.fastutil.longs.LongArrayList;
|
||||
|
||||
import java.util.Random;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class Main {
|
||||
|
||||
public static final boolean DEBUG = true;
|
||||
public static final boolean SLOW_DEBUG = false;
|
||||
public static final boolean VERY_SLOW_DEBUG = false;
|
||||
|
||||
/**
|
||||
* If true, many different parts of the builder switch to a more efficient mode where blocks can only be placed adjacent or upwards, never downwards
|
||||
*/
|
||||
public static final boolean STRICT_Y = true;
|
||||
|
||||
public static final BlockData DATA = new BlockData(new VanillaBlockStateDataProvider());
|
||||
|
||||
public static final Random RAND = new Random(5021);
|
||||
|
||||
public static void main() throws InterruptedException {
|
||||
System.out.println("Those costs are " + (ActionCosts.FALL_N_BLOCKS_COST[2] / 2) + " and " + ActionCosts.JUMP_ONE_BLOCK_COST + " and " + ActionCosts.FALL_N_BLOCKS_COST[1]);
|
||||
for (Face face : Face.VALUES) {
|
||||
System.out.println(face);
|
||||
System.out.println(face.x);
|
||||
System.out.println(face.y);
|
||||
System.out.println(face.z);
|
||||
System.out.println(face.index);
|
||||
System.out.println(face.offset);
|
||||
System.out.println(face.oppositeIndex);
|
||||
System.out.println("Horizontal " + face.horizontalIndex);
|
||||
}
|
||||
{
|
||||
System.out.println("Without");
|
||||
long start = BetterBlockPos.toLong(5021, 69, 420);
|
||||
System.out.println(BetterBlockPos.fromLong(start));
|
||||
start += Face.UP.offset;
|
||||
System.out.println(BetterBlockPos.fromLong(start));
|
||||
start += Face.DOWN.offset;
|
||||
System.out.println(BetterBlockPos.fromLong(start));
|
||||
start += Face.UP.offset;
|
||||
System.out.println(BetterBlockPos.fromLong(start));
|
||||
start += Face.DOWN.offset;
|
||||
System.out.println(BetterBlockPos.fromLong(start));
|
||||
}
|
||||
{
|
||||
System.out.println("With");
|
||||
long start = BetterBlockPos.toLong(5021, 69, 420);
|
||||
System.out.println(BetterBlockPos.fromLong(start));
|
||||
start += Face.UP.offset;
|
||||
start &= BetterBlockPos.POST_ADDITION_MASK;
|
||||
System.out.println(BetterBlockPos.fromLong(start));
|
||||
start += Face.DOWN.offset;
|
||||
start &= BetterBlockPos.POST_ADDITION_MASK;
|
||||
System.out.println(BetterBlockPos.fromLong(start));
|
||||
start += Face.UP.offset;
|
||||
start &= BetterBlockPos.POST_ADDITION_MASK;
|
||||
System.out.println(BetterBlockPos.fromLong(start));
|
||||
start += Face.DOWN.offset;
|
||||
start &= BetterBlockPos.POST_ADDITION_MASK;
|
||||
System.out.println(BetterBlockPos.fromLong(start));
|
||||
}
|
||||
{
|
||||
System.out.println(BetterBlockPos.fromLong(BetterBlockPos.toLong(150, 150, 150)));
|
||||
}
|
||||
for (int i = 0; i < 0; i++) {
|
||||
Stream.of(new Object())
|
||||
.flatMap(ignored -> IntStream.range(0, 100).boxed())
|
||||
.parallel()
|
||||
.forEach(x -> System.out.println(x + ""));
|
||||
IntStream.range(100, 200).boxed()
|
||||
.parallel()
|
||||
.forEach(x -> System.out.println(x + ""));
|
||||
Stream.of(new Object())
|
||||
.flatMap(ignored -> IntStream.range(200, 300).boxed())
|
||||
.collect(Collectors.toList()).parallelStream()
|
||||
.forEach(x -> System.out.println(x + ""));
|
||||
}
|
||||
for (int aaaa = 0; aaaa < 0; aaaa++) {
|
||||
/*Raytracer.raytraceMode++;
|
||||
Raytracer.raytraceMode %= 3;*/
|
||||
Random rand = new Random(5021);
|
||||
DoubleArrayList A = new DoubleArrayList();
|
||||
DoubleArrayList B = new DoubleArrayList();
|
||||
DoubleArrayList C = new DoubleArrayList();
|
||||
DoubleArrayList D = new DoubleArrayList();
|
||||
DoubleArrayList E = new DoubleArrayList();
|
||||
DoubleArrayList F = new DoubleArrayList();
|
||||
LongArrayList G = new LongArrayList();
|
||||
long a = System.currentTimeMillis();
|
||||
for (int trial = 0; trial < 10_000_000; ) {
|
||||
Vec3d playerEye = new Vec3d(rand.nextDouble() * 5 - 2.5, rand.nextDouble() * 5, rand.nextDouble() * 5 - 2.5);
|
||||
long eyeBlock = playerEye.getRoundedToZeroPositionUnsafeDontUse();
|
||||
if (eyeBlock == 0) {
|
||||
// origin, unlucky
|
||||
continue;
|
||||
}
|
||||
Face placeToAgainst = Face.VALUES[rand.nextInt(Face.NUM_FACES)];
|
||||
Face againstToPlace = placeToAgainst.opposite();
|
||||
long placeAgainst = placeToAgainst.offset(0);
|
||||
if (eyeBlock == placeAgainst) {
|
||||
continue;
|
||||
}
|
||||
double[] hitVec = new double[3];
|
||||
for (int i = 0; i < 3; i++) {
|
||||
switch (placeToAgainst.vec[i]) {
|
||||
case -1: {
|
||||
hitVec[i] = 0;
|
||||
break;
|
||||
}
|
||||
case 0: {
|
||||
hitVec[i] = rand.nextDouble();
|
||||
break;
|
||||
}
|
||||
case 1: {
|
||||
hitVec[i] = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
Vec3d hit = new Vec3d(hitVec);
|
||||
Raytracer.runTrace(playerEye, placeAgainst, againstToPlace, hit);
|
||||
A.add(playerEye.x);
|
||||
B.add(playerEye.y);
|
||||
C.add(playerEye.z);
|
||||
D.add(hit.x);
|
||||
E.add(hit.y);
|
||||
F.add(hit.z);
|
||||
G.add(placeAgainst);
|
||||
trial++;
|
||||
}
|
||||
long b = System.currentTimeMillis();
|
||||
System.out.println("Nominal first run with overhead: " + (b - a) + "ms");
|
||||
boolean checkAgainst = true;
|
||||
for (int i = 0; i < 10_000_000; i++) {
|
||||
if (i % 1000 == 0 && checkAgainst) {
|
||||
System.out.println(i);
|
||||
}
|
||||
LongArrayList normal = Raytracer.rayTraceZoomy(A.getDouble(i), B.getDouble(i), C.getDouble(i), D.getDouble(i), E.getDouble(i), F.getDouble(i), G.getLong(i));
|
||||
LongArrayList alternate = Raytracer.rayTraceZoomyAlternate(A.getDouble(i), B.getDouble(i), C.getDouble(i), D.getDouble(i), E.getDouble(i), F.getDouble(i), G.getLong(i));
|
||||
if (!normal.equals(alternate)) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
if (checkAgainst) {
|
||||
LongArrayList superSlow = Raytracer.rayTraceFast(A.getDouble(i), B.getDouble(i), C.getDouble(i), D.getDouble(i), E.getDouble(i), F.getDouble(i));
|
||||
if (!normal.equals(superSlow)) {
|
||||
Raytracer.print(normal);
|
||||
Raytracer.print(superSlow);
|
||||
checkAgainst = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (int it = 0; it < 20; it++) {
|
||||
{
|
||||
Thread.sleep(1000);
|
||||
System.gc();
|
||||
Thread.sleep(1000);
|
||||
long start = System.currentTimeMillis();
|
||||
for (int i = 0; i < 10_000_000; i++) {
|
||||
Raytracer.rayTraceZoomy(A.getDouble(i), B.getDouble(i), C.getDouble(i), D.getDouble(i), E.getDouble(i), F.getDouble(i), G.getLong(i));
|
||||
}
|
||||
long end = System.currentTimeMillis();
|
||||
System.out.println("Normal took " + (end - start) + "ms");
|
||||
}
|
||||
{
|
||||
Thread.sleep(1000);
|
||||
System.gc();
|
||||
Thread.sleep(1000);
|
||||
long start = System.currentTimeMillis();
|
||||
for (int i = 0; i < 10_000_000; i++) {
|
||||
Raytracer.rayTraceZoomyAlternate(A.getDouble(i), B.getDouble(i), C.getDouble(i), D.getDouble(i), E.getDouble(i), F.getDouble(i), G.getLong(i));
|
||||
}
|
||||
long end = System.currentTimeMillis();
|
||||
System.out.println("Alternate took " + (end - start) + "ms");
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
DebugStates.debug();
|
||||
}
|
||||
for (int aaaa = 0; aaaa < 0; aaaa++) {
|
||||
Random rand = new Random(5021);
|
||||
int trials = 10_000_000;
|
||||
int[] X = new int[trials];
|
||||
int[] Y = new int[trials];
|
||||
int[] Z = new int[trials];
|
||||
int sz = 10;
|
||||
CuboidBounds bounds = new CuboidBounds(sz, sz, sz);
|
||||
for (int i = 0; i < trials; i++) {
|
||||
for (int[] toAdd : new int[][]{X, Y, Z}) {
|
||||
toAdd[i] = rand.nextBoolean() ? rand.nextInt(sz) : rand.nextBoolean() ? -1 : sz;
|
||||
}
|
||||
}
|
||||
boolean[] a = new boolean[trials];
|
||||
boolean[] b = new boolean[trials];
|
||||
boolean[] c = new boolean[trials];
|
||||
boolean[] d = new boolean[trials];
|
||||
boolean[] e = new boolean[trials];
|
||||
for (int it = 0; it < 20; it++) {
|
||||
|
||||
{
|
||||
Thread.sleep(1000);
|
||||
System.gc();
|
||||
Thread.sleep(1000);
|
||||
long start = System.currentTimeMillis();
|
||||
for (int i = 0; i < trials; i++) {
|
||||
a[i] = bounds.inRangeBranchy(X[i], Y[i], Z[i]);
|
||||
}
|
||||
long end = System.currentTimeMillis();
|
||||
System.out.println("Branchy took " + (end - start) + "ms");
|
||||
}
|
||||
{
|
||||
Thread.sleep(1000);
|
||||
System.gc();
|
||||
Thread.sleep(1000);
|
||||
long start = System.currentTimeMillis();
|
||||
for (int i = 0; i < trials; i++) {
|
||||
b[i] = bounds.inRangeBranchless(X[i], Y[i], Z[i]);
|
||||
}
|
||||
long end = System.currentTimeMillis();
|
||||
System.out.println("Branchless took " + (end - start) + "ms");
|
||||
}
|
||||
{
|
||||
Thread.sleep(1000);
|
||||
System.gc();
|
||||
Thread.sleep(1000);
|
||||
long start = System.currentTimeMillis();
|
||||
for (int i = 0; i < trials; i++) {
|
||||
c[i] = bounds.inRangeBranchless2(X[i], Y[i], Z[i]);
|
||||
}
|
||||
long end = System.currentTimeMillis();
|
||||
System.out.println("Branchless2 took " + (end - start) + "ms");
|
||||
}
|
||||
{
|
||||
Thread.sleep(1000);
|
||||
System.gc();
|
||||
Thread.sleep(1000);
|
||||
long start = System.currentTimeMillis();
|
||||
for (int i = 0; i < trials; i++) {
|
||||
d[i] = bounds.inRangeBranchless3(X[i], Y[i], Z[i]);
|
||||
}
|
||||
long end = System.currentTimeMillis();
|
||||
System.out.println("Branchless3 took " + (end - start) + "ms");
|
||||
}
|
||||
{
|
||||
Thread.sleep(1000);
|
||||
System.gc();
|
||||
Thread.sleep(1000);
|
||||
long start = System.currentTimeMillis();
|
||||
for (int i = 0; i < trials; i++) {
|
||||
e[i] = bounds.inRangeBranchless4(X[i], Y[i], Z[i]);
|
||||
}
|
||||
long end = System.currentTimeMillis();
|
||||
System.out.println("Branchless4 took " + (end - start) + "ms");
|
||||
}
|
||||
/*
|
||||
Branchless2 took 55ms
|
||||
Branchless3 took 53ms
|
||||
Branchless4 took 47ms
|
||||
Branchy took 137ms
|
||||
Branchless took 35ms
|
||||
Branchless2 took 36ms
|
||||
Branchless3 took 35ms
|
||||
Branchless4 took 41ms
|
||||
Branchy took 118ms
|
||||
Branchless took 33ms
|
||||
Branchless2 took 39ms
|
||||
Branchless3 took 36ms
|
||||
Branchless4 took 42ms
|
||||
Branchy took 125ms
|
||||
Branchless took 41ms
|
||||
Branchless2 took 45ms
|
||||
Branchless3 took 41ms
|
||||
Branchless4 took 45ms
|
||||
Branchy took 123ms
|
||||
Branchless took 38ms
|
||||
Branchless2 took 43ms
|
||||
Branchless3 took 35ms
|
||||
Branchless4 took 43ms
|
||||
Branchy took 117ms
|
||||
Branchless took 37ms
|
||||
Branchless2 took 42ms
|
||||
Branchless3 took 41ms
|
||||
Branchless4 took 45ms
|
||||
Branchy took 123ms
|
||||
Branchless took 35ms
|
||||
Branchless2 took 42ms
|
||||
Branchless3 took 38ms
|
||||
Branchless4 took 46ms
|
||||
Branchy took 126ms
|
||||
Branchless took 34ms
|
||||
Branchless2 took 47ms
|
||||
Branchless3 took 40ms
|
||||
Branchless4 took 47ms
|
||||
Branchy took 124ms
|
||||
*/
|
||||
|
||||
// 3 is better than 2 and 4 because of data dependency
|
||||
// the L1 cache fetch for this.sizeX can happen at the same time as "x+1" (which is an increment of an argument)
|
||||
// in other words: in options 2 and 4, the "+1" or "-1" has a data dependency on the RAM fetch for this.sizeX, but in option 3 alone, the +1 happens upon the argument x, which is likely in a register, meaning it can be pipelined in parallel with the L1 cache fetch for this.sizeX
|
||||
|
||||
}
|
||||
}
|
||||
/*{ // proguard test
|
||||
PlayerPhysics.determinePlayerRealSupport(BlockStateCachedData.get(69), BlockStateCachedData.get(420));
|
||||
PlayerPhysics.determinePlayerRealSupport(BlockStateCachedData.get(420), BlockStateCachedData.get(69));
|
||||
}*/
|
||||
{
|
||||
for (int sneak = 0; sneak < 4; sneak++) {
|
||||
System.out.println("meow");
|
||||
System.out.println(sneak);
|
||||
System.out.println(SneakPosition.encode(0, sneak));
|
||||
System.out.println(SneakPosition.encode(BetterBlockPos.POST_ADDITION_MASK, sneak));
|
||||
System.out.println(SneakPosition.decode(SneakPosition.encode(0, sneak)));
|
||||
System.out.println(SneakPosition.decode(SneakPosition.encode(BetterBlockPos.POST_ADDITION_MASK, sneak)));
|
||||
}
|
||||
}
|
||||
System.exit(0);
|
||||
}
|
||||
}
|
||||
@@ -1,126 +0,0 @@
|
||||
/*
|
||||
* This file is part of Baritone.
|
||||
*
|
||||
* Baritone is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Baritone is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Baritone. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package baritone.builder;
|
||||
|
||||
import baritone.api.utils.BetterBlockPos;
|
||||
import baritone.builder.utils.com.github.btrekkie.connectivity.ConnGraph;
|
||||
import baritone.builder.utils.com.github.btrekkie.connectivity.MutatingAugmentation;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class NavigableSurface {
|
||||
|
||||
private final CuboidBounds bounds;
|
||||
|
||||
private final BlockStateCachedData[] blocks; // TODO switch to xzy ordering so columnFrom is faster
|
||||
|
||||
private final ConnGraph connGraph;
|
||||
|
||||
private final Function<BetterBlockPos, Object> genVertexAugmentation;
|
||||
|
||||
private final Column col1 = new Column();
|
||||
private final Column col2 = new Column();
|
||||
|
||||
public NavigableSurface(int x, int y, int z, MutatingAugmentation augmentation, Function<BetterBlockPos, Object> genVertexAugmentation) {
|
||||
this.bounds = new CuboidBounds(x, y, z);
|
||||
this.blocks = new BlockStateCachedData[bounds.volume()];
|
||||
Arrays.fill(blocks, FakeStates.AIR);
|
||||
this.genVertexAugmentation = genVertexAugmentation;
|
||||
this.connGraph = new ConnGraph(augmentation);
|
||||
|
||||
if (!genVertexAugmentation.apply(new BetterBlockPos(0, 0, 0)).equals(genVertexAugmentation.apply(new BetterBlockPos(0, 0, 0)))) {
|
||||
throw new IllegalStateException("RedBlackNode optimization requires correct impl of .equals on the attachment, to avoid percolating up spurious augmentation non-updates");
|
||||
}
|
||||
}
|
||||
|
||||
private void columnFrom(Column column, long pos) {
|
||||
column.underneath = getBlockOrAir((pos + Column.DOWN_1) & BetterBlockPos.POST_ADDITION_MASK);
|
||||
column.feet = getBlockOrAir(pos);
|
||||
column.head = getBlockOrAir((pos + Column.UP_1) & BetterBlockPos.POST_ADDITION_MASK);
|
||||
column.above = getBlockOrAir((pos + Column.UP_2) & BetterBlockPos.POST_ADDITION_MASK);
|
||||
column.aboveAbove = getBlockOrAir((pos + Column.UP_3) & BetterBlockPos.POST_ADDITION_MASK);
|
||||
column.init();
|
||||
}
|
||||
|
||||
protected void setBlock(long pos, BlockStateCachedData data) {
|
||||
blocks[bounds.toIndex(pos)] = data;
|
||||
for (int dy = -2; dy <= 1; dy++) {
|
||||
long couldHaveChanged = BetterBlockPos.offsetBy(pos, 0, dy, 0);
|
||||
columnFrom(col1, couldHaveChanged);
|
||||
boolean currentlyAllowed = col1.standing();
|
||||
if (currentlyAllowed) {
|
||||
// TODO skip the next line if it already has an augmentation?
|
||||
connGraph.setVertexAugmentation(couldHaveChanged, genVertexAugmentation.apply(BetterBlockPos.fromLong(couldHaveChanged)));
|
||||
|
||||
for (Face dir : Face.HORIZONTALS) {
|
||||
long adj = dir.offset(couldHaveChanged);
|
||||
columnFrom(col2, adj);
|
||||
Integer connDy = PlayerPhysics.bidirectionalPlayerTravel(col1, col2, getBlockOrAir((adj + Column.DOWN_2) & BetterBlockPos.POST_ADDITION_MASK), getBlockOrAir((adj + Column.DOWN_3) & BetterBlockPos.POST_ADDITION_MASK));
|
||||
for (int fakeDy = -2; fakeDy <= 2; fakeDy++) {
|
||||
long neighbor = BetterBlockPos.offsetBy(adj, 0, fakeDy, 0);
|
||||
if (((Integer) fakeDy).equals(connDy)) {
|
||||
connGraph.addEdge(couldHaveChanged, neighbor);
|
||||
} else {
|
||||
connGraph.removeEdge(couldHaveChanged, neighbor);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
connGraph.removeVertexAugmentation(couldHaveChanged);
|
||||
for (Face dir : Face.HORIZONTALS) {
|
||||
long adj = dir.offset(couldHaveChanged);
|
||||
for (int fakeDy = -2; fakeDy <= 2; fakeDy++) {
|
||||
connGraph.removeEdge(couldHaveChanged, BetterBlockPos.offsetBy(adj, 0, fakeDy, 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void setBlock(int x, int y, int z, BlockStateCachedData data) {
|
||||
setBlock(BetterBlockPos.toLong(x, y, z), data);
|
||||
}
|
||||
|
||||
public CuboidBounds bounds() {
|
||||
return bounds;
|
||||
}
|
||||
|
||||
public BlockStateCachedData getBlock(long pos) {
|
||||
return blocks[bounds.toIndex(pos)];
|
||||
}
|
||||
|
||||
public BlockStateCachedData getBlockOrAir(long pos) {
|
||||
if (!bounds.inRangePos(pos)) {
|
||||
return FakeStates.AIR;
|
||||
}
|
||||
return getBlock(pos);
|
||||
}
|
||||
|
||||
public boolean hasBlock(BetterBlockPos pos) {
|
||||
return getBlockOrAir(pos.toLong()).collidesWithPlayer;
|
||||
}
|
||||
|
||||
public boolean connected(BetterBlockPos a, BetterBlockPos b) {
|
||||
return connGraph.connected(a.toLong(), b.toLong());
|
||||
}
|
||||
|
||||
public Object getComponentAugmentation(BetterBlockPos pos) { // maybe should be protected? subclass defines it anyway
|
||||
return connGraph.getComponentAugmentation(pos.toLong());
|
||||
}
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
/*
|
||||
* This file is part of Baritone.
|
||||
*
|
||||
* Baritone is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Baritone is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Baritone. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package baritone.builder;
|
||||
|
||||
import baritone.api.utils.BetterBlockPos;
|
||||
|
||||
public class Node {
|
||||
|
||||
private final long posAndSneak;
|
||||
public final long worldStateZobristHash;
|
||||
|
||||
public final int heuristic;
|
||||
public int cost;
|
||||
public int combinedCost;
|
||||
public Node previous;
|
||||
int heapPosition;
|
||||
|
||||
// boolean unrealizedZobristBlockChange; // no longer needed since presence in the overall GreedySolver zobristWorldStateCache indicates if this is a yet-unrealized branch of the zobrist space
|
||||
long packedUnrealizedCoordinate;
|
||||
// int unrealizedState; // no longer needed now that world state is binarized with scaffolding/build versus air
|
||||
// long unrealizedZobristParentHash; // no longer needed since we can compute it backwards with XOR
|
||||
|
||||
public Node(long pos, Face sneakingTowards, long zobristState, long unrealizedBlockPlacement, int heuristic) {
|
||||
this.posAndSneak = SneakPosition.encode(pos, sneakingTowards);
|
||||
this.heapPosition = -1;
|
||||
this.cost = Integer.MAX_VALUE;
|
||||
this.heuristic = heuristic;
|
||||
this.worldStateZobristHash = zobristState;
|
||||
this.packedUnrealizedCoordinate = unrealizedBlockPlacement;
|
||||
}
|
||||
|
||||
public boolean sneaking() {
|
||||
return SneakPosition.hasSneak(posAndSneak);
|
||||
}
|
||||
|
||||
public long pos() {
|
||||
return posAndSneak & BetterBlockPos.POST_ADDITION_MASK;
|
||||
}
|
||||
|
||||
public long sneakingPosition() {
|
||||
return posAndSneak;
|
||||
}
|
||||
|
||||
public long nodeMapKey() {
|
||||
return posAndSneak ^ worldStateZobristHash;
|
||||
}
|
||||
|
||||
public boolean inHeap() {
|
||||
return heapPosition >= 0;
|
||||
}
|
||||
}
|
||||
@@ -1,110 +0,0 @@
|
||||
/*
|
||||
* This file is part of Baritone.
|
||||
*
|
||||
* Baritone is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Baritone is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Baritone. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package baritone.builder;
|
||||
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public class NodeBinaryHeap {
|
||||
|
||||
private static final int INITIAL_CAPACITY = 1024;
|
||||
|
||||
private Node[] array;
|
||||
|
||||
private int size;
|
||||
|
||||
public NodeBinaryHeap() {
|
||||
this.size = INITIAL_CAPACITY;
|
||||
this.array = new Node[this.size];
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return size;
|
||||
}
|
||||
|
||||
public void insert(Node value) {
|
||||
if (size >= array.length - 1) {
|
||||
array = Arrays.copyOf(array, array.length << 1);
|
||||
}
|
||||
size++;
|
||||
value.heapPosition = size;
|
||||
array[size] = value;
|
||||
update(value);
|
||||
}
|
||||
|
||||
public void update(Node val) {
|
||||
int index = val.heapPosition;
|
||||
int parentInd = index >>> 1;
|
||||
int cost = val.combinedCost;
|
||||
Node parentNode = array[parentInd];
|
||||
while (index > 1 && parentNode.combinedCost > cost) {
|
||||
array[index] = parentNode;
|
||||
array[parentInd] = val;
|
||||
val.heapPosition = parentInd;
|
||||
parentNode.heapPosition = index;
|
||||
index = parentInd;
|
||||
parentInd = index >>> 1;
|
||||
parentNode = array[parentInd];
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return size == 0;
|
||||
}
|
||||
|
||||
public Node removeLowest() {
|
||||
if (size == 0) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
Node result = array[1];
|
||||
Node val = array[size];
|
||||
array[1] = val;
|
||||
val.heapPosition = 1;
|
||||
array[size] = null;
|
||||
size--;
|
||||
result.heapPosition = -1;
|
||||
if (size < 2) {
|
||||
return result;
|
||||
}
|
||||
int index = 1;
|
||||
int smallerChild = 2;
|
||||
int cost = val.combinedCost;
|
||||
do {
|
||||
Node smallerChildNode = array[smallerChild];
|
||||
int smallerChildCost = smallerChildNode.combinedCost;
|
||||
if (smallerChild < size) {
|
||||
Node rightChildNode = array[smallerChild + 1];
|
||||
int rightChildCost = rightChildNode.combinedCost;
|
||||
if (smallerChildCost > rightChildCost) {
|
||||
smallerChild++;
|
||||
smallerChildCost = rightChildCost;
|
||||
smallerChildNode = rightChildNode;
|
||||
}
|
||||
}
|
||||
if (cost <= smallerChildCost) {
|
||||
break;
|
||||
}
|
||||
array[index] = smallerChildNode;
|
||||
array[smallerChild] = val;
|
||||
val.heapPosition = smallerChild;
|
||||
smallerChildNode.heapPosition = index;
|
||||
index = smallerChild;
|
||||
} while ((smallerChild <<= 1) <= size);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
/*
|
||||
* This file is part of Baritone.
|
||||
*
|
||||
* Baritone is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Baritone is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Baritone. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package baritone.builder;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public class PackedBlockStateCuboid {
|
||||
|
||||
public final Bounds bounds;
|
||||
private final BlockStateCachedData[] states;
|
||||
private final BlockStateCachedData[] statesWithScaffolding;
|
||||
|
||||
private PackedBlockStateCuboid(int x, int y, int z) {
|
||||
this.bounds = new CuboidBounds(x, y, z);
|
||||
this.states = new BlockStateCachedData[bounds.volume()];
|
||||
this.statesWithScaffolding = new BlockStateCachedData[bounds.volume()];
|
||||
}
|
||||
|
||||
public PackedBlockStateCuboid(int[][][] blockStates, BlockData data) {
|
||||
this(blockStates.length, blockStates[0].length, blockStates[0][0].length);
|
||||
bounds.forEach((x, y, z) -> states[bounds.toIndex(x, y, z)] = data.get(blockStates[x][y][z]));
|
||||
genScaffoldVariant();
|
||||
}
|
||||
|
||||
public PackedBlockStateCuboid(BlockStateCachedData[][][] blockStates) {
|
||||
this(blockStates.length, blockStates[0].length, blockStates[0][0].length);
|
||||
bounds.forEach((x, y, z) -> states[bounds.toIndex(x, y, z)] = blockStates[x][y][z]);
|
||||
genScaffoldVariant();
|
||||
}
|
||||
|
||||
public static void fillWithAir(BlockStateCachedData[][][] states) {
|
||||
for (BlockStateCachedData[][] layer : states) {
|
||||
for (BlockStateCachedData[] slice : layer) {
|
||||
Arrays.fill(slice, FakeStates.AIR);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void genScaffoldVariant() {
|
||||
for (int i = 0; i < states.length; i++) {
|
||||
if (PlaceOrderDependencyGraph.treatedAsScaffolding(states[i])) {
|
||||
throw new IllegalStateException("including FakeStates.SCAFFOLDING will confuse the place order dependency graph. use an alternate block like FakeStates.SOLID");
|
||||
}
|
||||
statesWithScaffolding[i] = states[i].isAir ? FakeStates.SCAFFOLDING : states[i];
|
||||
}
|
||||
}
|
||||
|
||||
public BlockStateCachedData get(int index) {
|
||||
return states[index];
|
||||
}
|
||||
|
||||
public BlockStateCachedData getScaffoldingVariant(int index) {
|
||||
return statesWithScaffolding[index];
|
||||
}
|
||||
}
|
||||
@@ -1,151 +0,0 @@
|
||||
/*
|
||||
* This file is part of Baritone.
|
||||
*
|
||||
* Baritone is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Baritone is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Baritone. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package baritone.builder;
|
||||
|
||||
import java.util.stream.DoubleStream;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* If you want to place against me, there's some things you gotta know
|
||||
*/
|
||||
public class PlaceAgainstData {
|
||||
|
||||
public final Face against;
|
||||
public final boolean mustSneak; // like if its a crafting table
|
||||
private final Vec3d[] hits;
|
||||
private final boolean top;
|
||||
private final boolean bottom;
|
||||
|
||||
public PlaceAgainstData(Face against, Vec3d[] hits, boolean mustSneak) {
|
||||
this.mustSneak = mustSneak;
|
||||
this.against = against;
|
||||
boolean top = false;
|
||||
boolean bottom = false;
|
||||
for (Vec3d hit : hits) {
|
||||
if (!validatePossibleHit(hit)) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
if (!against.vertical) {
|
||||
if (BlockStatePlacementOption.hitOk(Half.BOTTOM, hit)) {
|
||||
bottom = true;
|
||||
}
|
||||
if (BlockStatePlacementOption.hitOk(Half.TOP, hit)) {
|
||||
top = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
this.top = top;
|
||||
this.bottom = bottom;
|
||||
this.hits = hits;
|
||||
if (!streamRelativeToMyself().allMatch(Vec3d::inOriginUnitVoxel)) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
if (!streamRelativeToPlace().allMatch(Vec3d::inOriginUnitVoxel)) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
if (hits.length == 0) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
|
||||
public PlaceAgainstData(Face against, Half half, boolean mustSneak) {
|
||||
this(against, project(against.opposite(), half), mustSneak);
|
||||
if (against.vertical && half != Half.EITHER) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
|
||||
public Stream<Vec3d> streamRelativeToPlace() {
|
||||
return Stream.of(hits);
|
||||
}
|
||||
|
||||
public Stream<Vec3d> streamRelativeToMyself() {
|
||||
return streamRelativeToPlace().map(v -> v.plus(against.x, against.y, against.z));
|
||||
}
|
||||
|
||||
private boolean validatePossibleHit(Vec3d hit) {
|
||||
double[] h = {hit.x, hit.y, hit.z};
|
||||
for (int i = 0; i < 3; i++) {
|
||||
switch (against.vec[i]) {
|
||||
case -1: {
|
||||
if (h[i] != 1) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 0: {
|
||||
if (h[i] < 0.05 || h[i] > 0.95) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 1: {
|
||||
if (h[i] != 0) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO for playerMustBeHorizontalFacing, do i need something like andThatOptionExtendsTheFullHorizontalSpaceOfTheVoxel()?
|
||||
|
||||
public boolean presentsAnOptionStrictlyInTheTopHalfOfTheStandardVoxelPlane() {
|
||||
if (Main.DEBUG && against.vertical) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
return top;
|
||||
}
|
||||
|
||||
public boolean presentsAnOptionStrictlyInTheBottomHalfOfTheStandardVoxelPlane() {
|
||||
if (Main.DEBUG && against.vertical) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
return bottom;
|
||||
}
|
||||
|
||||
private static final double[] LOCS = {0.1, 0.5, 0.9};
|
||||
|
||||
private static Vec3d[] project(Face ontoFace, Half filterHalf) {
|
||||
return DoubleStream
|
||||
.of(LOCS)
|
||||
.boxed()
|
||||
.flatMap(dx -> DoubleStream.of(LOCS).mapToObj(dz -> new double[]{dx, dz}))
|
||||
.map(faceHit -> project(faceHit, ontoFace))
|
||||
.map(Vec3d::new)
|
||||
.filter(vec -> ontoFace.vertical || BlockStatePlacementOption.hitOk(filterHalf, vec))
|
||||
.toArray(Vec3d[]::new);
|
||||
}
|
||||
|
||||
private static double[] project(double[] faceHit, Face ontoFace) {
|
||||
double[] ret = new double[3];
|
||||
int j = 0;
|
||||
for (int i = 0; i < 3; i++) {
|
||||
if (ontoFace.vec[i] == 0) {
|
||||
ret[i] = faceHit[j++];
|
||||
} else {
|
||||
if (ontoFace.vec[i] == 1) {
|
||||
ret[i] = 1;
|
||||
} // else leave it as zero
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
/*
|
||||
* This file is part of Baritone.
|
||||
*
|
||||
* Baritone is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Baritone is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Baritone. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package baritone.builder;
|
||||
|
||||
import baritone.api.utils.BetterBlockPos;
|
||||
|
||||
public class PlaceOptions {
|
||||
|
||||
double blockReachDistance = 4;
|
||||
DependencyGraphScaffoldingOverlay overlay;
|
||||
IReachabilityProvider provider = IReachabilityProvider.get(overlay, new PlayerReachSphere(blockReachDistance));
|
||||
|
||||
public void whatCouldIDo(int playerX, int playerFeetBlips, int playerZ) {
|
||||
int playerEyeBlips = playerFeetBlips + Blip.FEET_TO_EYE_APPROX;
|
||||
// TODO ugh how tf to deal with sneaking UGH. maybe like if (playerEyeBlips % 16 < 2) { also run all candidates from one voxel lower down because if we snuck our eye would be in there}
|
||||
int voxelY = playerEyeBlips / Blip.FULL_BLOCK;
|
||||
long pos = BetterBlockPos.toLong(playerX, voxelY, playerZ);
|
||||
for (long blockPos : provider.candidates(pos)) {
|
||||
BlockStateCachedData placingAgainst = overlay.data(blockPos);
|
||||
outer:
|
||||
for (Face againstToPlace : Face.VALUES) {
|
||||
Face placeToAgainst = againstToPlace.opposite();
|
||||
if (overlay.outgoingEdge(blockPos, againstToPlace)) {
|
||||
long placingBlockAt = againstToPlace.offset(blockPos);
|
||||
BlockStateCachedData blockBeingPlaced = overlay.data(placingBlockAt);
|
||||
for (BlockStatePlacementOption option : blockBeingPlaced.placeMe) {
|
||||
if (option.against == placeToAgainst) {
|
||||
PlaceAgainstData againstData = placingAgainst.againstMe(option);
|
||||
int relativeX = playerX - BetterBlockPos.XfromLong(placingBlockAt);
|
||||
int relativeY = playerFeetBlips - Blip.FULL_BLOCK * BetterBlockPos.YfromLong(placingBlockAt);
|
||||
int relativeZ = playerZ - BetterBlockPos.ZfromLong(placingBlockAt);
|
||||
for (Raytracer.Raytrace trace : option.computeTraceOptions(againstData, relativeX, relativeY, relativeZ, PlayerVantage.LOOSE_CENTER, blockReachDistance)) {
|
||||
// yay, gold star
|
||||
}
|
||||
continue outer;
|
||||
}
|
||||
}
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,102 +0,0 @@
|
||||
/*
|
||||
* This file is part of Baritone.
|
||||
*
|
||||
* Baritone is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Baritone is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Baritone. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package baritone.builder;
|
||||
|
||||
/**
|
||||
* An immutable graph representing block placement dependency order
|
||||
* <p>
|
||||
* Air blocks are treated as scaffolding!
|
||||
* (the idea is that treating air blocks as air would be boring - nothing can place against them and they can't be placed against anything)
|
||||
* <p>
|
||||
* Edge A --> B means that B can be placed against A
|
||||
*/
|
||||
public class PlaceOrderDependencyGraph {
|
||||
|
||||
private final PackedBlockStateCuboid states;
|
||||
private final byte[] edges;
|
||||
|
||||
public PlaceOrderDependencyGraph(PackedBlockStateCuboid states) {
|
||||
this.states = states;
|
||||
this.edges = new byte[bounds().volume()];
|
||||
|
||||
bounds().forEach(this::compute);
|
||||
}
|
||||
|
||||
private void compute(long pos) {
|
||||
byte val = 0;
|
||||
for (BlockStatePlacementOption option : data(pos).placeMe) {
|
||||
if (Main.STRICT_Y && option.against == Face.UP) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
long againstPos = option.against.offset(pos);
|
||||
BlockStateCachedData against;
|
||||
if (inRange(againstPos)) {
|
||||
against = data(againstPos);
|
||||
} else {
|
||||
against = FakeStates.SCAFFOLDING;
|
||||
}
|
||||
if (against.possibleAgainstMe(option)) {
|
||||
val |= 1 << option.against.index;
|
||||
}
|
||||
}
|
||||
edges[states.bounds.toIndex(pos)] = val;
|
||||
}
|
||||
|
||||
public BlockStateCachedData data(long pos) {
|
||||
return states.getScaffoldingVariant(bounds().toIndex(pos));
|
||||
}
|
||||
|
||||
// example: dirt at 0,0,0 torch at 0,1,0. outgoingEdge(0,0,0,UP) returns true, incomingEdge(0,1,0,DOWN) returns true
|
||||
|
||||
public boolean outgoingEdge(long pos, Face face) {
|
||||
if (!inRange(pos)) {
|
||||
return false;
|
||||
}
|
||||
return incomingEdge(face.offset(pos), face.opposite());
|
||||
}
|
||||
|
||||
public boolean incomingEdge(long pos, Face face) {
|
||||
if (!inRange(face.offset(pos))) {
|
||||
return false;
|
||||
}
|
||||
return incomingEdgePermitExterior(pos, face);
|
||||
}
|
||||
|
||||
public boolean incomingEdgePermitExterior(long pos, Face face) {
|
||||
if (!inRange(pos)) {
|
||||
return false;
|
||||
}
|
||||
return (edges[bounds().toIndex(pos)] & 1 << face.index) != 0;
|
||||
}
|
||||
|
||||
public boolean airTreatedAsScaffolding(long pos) {
|
||||
return treatedAsScaffolding(data(pos));
|
||||
}
|
||||
|
||||
private boolean inRange(long pos) {
|
||||
return bounds().inRangePos(pos);
|
||||
}
|
||||
|
||||
public Bounds bounds() {
|
||||
return states.bounds;
|
||||
}
|
||||
|
||||
public static boolean treatedAsScaffolding(BlockStateCachedData data) {
|
||||
return data == FakeStates.SCAFFOLDING;
|
||||
}
|
||||
}
|
||||
@@ -1,391 +0,0 @@
|
||||
/*
|
||||
* This file is part of Baritone.
|
||||
*
|
||||
* Baritone is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Baritone is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Baritone. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package baritone.builder;
|
||||
|
||||
import baritone.api.utils.BetterBlockPos;
|
||||
|
||||
public class PlayerPhysics {
|
||||
|
||||
public static int determinePlayerRealSupportLevel(BlockStateCachedData underneath, BlockStateCachedData within, VoxelResidency residency) {
|
||||
switch (residency) {
|
||||
case STANDARD_WITHIN_SUPPORT:
|
||||
return within.collisionHeightBlips();
|
||||
case UNDERNEATH_PROTRUDES_AT_OR_ABOVE_FULL_BLOCK:
|
||||
return underneath.collisionHeightBlips() - Blip.FULL_BLOCK;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
public static int determinePlayerRealSupportLevel(BlockStateCachedData underneath, BlockStateCachedData within) {
|
||||
return determinePlayerRealSupportLevel(underneath, within, canPlayerStand(underneath, within));
|
||||
}
|
||||
|
||||
/**
|
||||
* the player Y is within within. i.e. the player Y is greater than or equal within and less than within+1
|
||||
* <p>
|
||||
* underneath is the block underneath that, which we annoyingly also have to check due to fences and other blocks that are taller than a block
|
||||
*/
|
||||
public static VoxelResidency canPlayerStand(BlockStateCachedData underneath, BlockStateCachedData within) {
|
||||
if (within.collidesWithPlayer) {
|
||||
if (underneath.collidesWithPlayer && underneath.collisionHeightBlips() - Blip.FULL_BLOCK > within.collisionHeightBlips()) { // > because imagine something like slab on top of fence, we can walk on the slab even though the fence is equivalent height
|
||||
if (!underneath.fullyWalkableTop) {
|
||||
return VoxelResidency.PREVENTED_BY_UNDERNEATH;
|
||||
}
|
||||
return VoxelResidency.UNDERNEATH_PROTRUDES_AT_OR_ABOVE_FULL_BLOCK; // this could happen if "underneath" is a fence and "within" is a carpet
|
||||
}
|
||||
if (!within.fullyWalkableTop) {
|
||||
return VoxelResidency.PREVENTED_BY_WITHIN;
|
||||
}
|
||||
if (within.collisionHeightBlips() >= Blip.FULL_BLOCK) {
|
||||
return VoxelResidency.IMPOSSIBLE_WITHOUT_SUFFOCATING;
|
||||
}
|
||||
return VoxelResidency.STANDARD_WITHIN_SUPPORT;
|
||||
} else {
|
||||
if (!underneath.collidesWithPlayer) {
|
||||
return VoxelResidency.FLOATING;
|
||||
}
|
||||
if (!underneath.fullyWalkableTop) {
|
||||
return VoxelResidency.PREVENTED_BY_UNDERNEATH;
|
||||
}
|
||||
if (underneath.collisionHeightBlips() < Blip.FULL_BLOCK) { // short circuit only calls collisionHeightBlips when fullyWalkableTop is true, so this is safe
|
||||
return VoxelResidency.FLOATING;
|
||||
}
|
||||
return VoxelResidency.UNDERNEATH_PROTRUDES_AT_OR_ABOVE_FULL_BLOCK;
|
||||
}
|
||||
}
|
||||
|
||||
public enum VoxelResidency {
|
||||
UNDERNEATH_PROTRUDES_AT_OR_ABOVE_FULL_BLOCK, // i fucking hate notch for adding fences to the game. anyway this means that the height is underneath.collisionHeightBlips minus a full block.
|
||||
STANDARD_WITHIN_SUPPORT, // :innocent: emoji, the height is simply the collisionHeightBlips of the within
|
||||
|
||||
IMPOSSIBLE_WITHOUT_SUFFOCATING, // aka: um we are literally underground
|
||||
FLOATING, // aka: um we are literally floating in midair
|
||||
|
||||
PREVENTED_BY_UNDERNEATH, // fences :woozy_face:
|
||||
PREVENTED_BY_WITHIN, // what are you even thinking?
|
||||
}
|
||||
|
||||
public static boolean valid(VoxelResidency res) {
|
||||
// FWIW this is equivalent to "return determinePlayerRealSupportLevel > 0"
|
||||
switch (res) {
|
||||
case UNDERNEATH_PROTRUDES_AT_OR_ABOVE_FULL_BLOCK:
|
||||
case STANDARD_WITHIN_SUPPORT:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static Collision playerTravelCollides(Column within, Column into) {
|
||||
if (Main.DEBUG && within.head.collidesWithPlayer) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
return playerTravelCollides(within.feetBlips,
|
||||
within.above,
|
||||
into.above,
|
||||
into.head,
|
||||
into.feet,
|
||||
into.underneath,
|
||||
within.underneath,
|
||||
within.feet,
|
||||
within.aboveAbove,
|
||||
into.aboveAbove);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Is there a movement (ascend, level, descend) that the player can do AND UNDO?
|
||||
* This is basically the same as playerTravelCollides, but, it's okay to fall, but only as long as you don't fall further than what can be jumped back up
|
||||
*
|
||||
* @return the integer change in voxel Y, or null if there is no such option
|
||||
*/
|
||||
public static Integer bidirectionalPlayerTravel(
|
||||
Column within,
|
||||
Column into,
|
||||
BlockStateCachedData underUnderneathInto,
|
||||
BlockStateCachedData underUnderUnderneathInto
|
||||
) {
|
||||
switch (playerTravelCollides(within, into)) {
|
||||
|
||||
case BLOCKED:
|
||||
return null;
|
||||
|
||||
case JUMP_TO_VOXEL_TWO_UP:
|
||||
return 2;
|
||||
case VOXEL_UP:
|
||||
case JUMP_TO_VOXEL_UP:
|
||||
return 1;
|
||||
case VOXEL_LEVEL:
|
||||
case JUMP_TO_VOXEL_LEVEL:
|
||||
return 0;
|
||||
|
||||
case FALL:
|
||||
if (Main.DEBUG && (canPlayerStand(into.underneath, into.feet) != VoxelResidency.FLOATING || !into.playerCanExistAtFootBlip(within.feetBlips))) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
VoxelResidency downOne = canPlayerStand(underUnderneathInto, into.underneath);
|
||||
switch (downOne) {
|
||||
case PREVENTED_BY_UNDERNEATH:
|
||||
case PREVENTED_BY_WITHIN:
|
||||
return null; // a block we aren't allowed to stand on
|
||||
case UNDERNEATH_PROTRUDES_AT_OR_ABOVE_FULL_BLOCK:
|
||||
case STANDARD_WITHIN_SUPPORT:
|
||||
int fallBy = within.feetBlips + Blip.FULL_BLOCK - determinePlayerRealSupportLevel(underUnderneathInto, into.underneath, downOne);
|
||||
if (fallBy < 1) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
if (fallBy <= Blip.JUMP) {
|
||||
return -1; // we could jump back! yay!
|
||||
}
|
||||
return null; // one-way trip
|
||||
default:
|
||||
throw new IllegalStateException();
|
||||
case FLOATING:
|
||||
break;
|
||||
}
|
||||
// ^ as shown, we are floating in downOne, so let's try downTwo
|
||||
// but first let's make sure it's possible
|
||||
int highestBlipsWithinUnderUnderUnder = Blip.FULL_BLOCK - 1; // 15
|
||||
int highestJumpFromTwoUnder = highestBlipsWithinUnderUnderUnder + Blip.JUMP; // 35
|
||||
int highestJumpIntoThisVoxel = highestJumpFromTwoUnder - Blip.TWO_BLOCKS; // 3
|
||||
if (within.feetBlips > highestJumpIntoThisVoxel) {
|
||||
return null;
|
||||
}
|
||||
VoxelResidency downTwo = canPlayerStand(underUnderUnderneathInto, underUnderneathInto);
|
||||
switch (downTwo) {
|
||||
case UNDERNEATH_PROTRUDES_AT_OR_ABOVE_FULL_BLOCK: // tallest block is 24 blips, which is less than 29, so this case is impossible
|
||||
case PREVENTED_BY_UNDERNEATH:
|
||||
case PREVENTED_BY_WITHIN:
|
||||
case FLOATING:
|
||||
return null;
|
||||
case STANDARD_WITHIN_SUPPORT:
|
||||
int fallBy = within.feetBlips + Blip.TWO_BLOCKS - determinePlayerRealSupportLevel(underUnderUnderneathInto, underUnderneathInto, downTwo);
|
||||
if (fallBy < Blip.FULL_BLOCK + 1) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
if (fallBy <= Blip.JUMP) {
|
||||
return -2; // we could jump back! yay!
|
||||
}
|
||||
return null; // one-way trip
|
||||
default:
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
default:
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* "Can the player walk forwards without needing to break anything?"
|
||||
* <p>
|
||||
* Takes into account things like the automatic +0.5 from walking into a slab.
|
||||
* <p>
|
||||
* Player is standing at X (feet) Y (head) on top of S, intends to walk forwards into this ABCD column
|
||||
* EF
|
||||
* UA
|
||||
* YB
|
||||
* XC
|
||||
* SD
|
||||
*/
|
||||
public static Collision playerTravelCollides(int feetBlips,
|
||||
BlockStateCachedData U,
|
||||
BlockStateCachedData A,
|
||||
BlockStateCachedData B,
|
||||
BlockStateCachedData C,
|
||||
BlockStateCachedData D,
|
||||
BlockStateCachedData S,
|
||||
BlockStateCachedData X,
|
||||
BlockStateCachedData E,
|
||||
BlockStateCachedData F) {
|
||||
if (Main.DEBUG && (feetBlips < 0 || feetBlips >= Blip.FULL_BLOCK)) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
if (Main.DEBUG && (feetBlips != determinePlayerRealSupportLevel(S, X))) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
boolean alreadyWithinU = protrudesIntoThirdBlock(feetBlips);
|
||||
if (Main.DEBUG && (alreadyWithinU && U.collidesWithPlayer)) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
int couldJumpUpTo = feetBlips + Blip.JUMP;
|
||||
int couldStepUpTo = feetBlips + Blip.HALF_BLOCK;
|
||||
if (couldJumpUpTo >= Blip.TWO_BLOCKS && !E.collidesWithPlayer && !F.collidesWithPlayer) {
|
||||
// probably blocked, but maybe could we stand on A?
|
||||
// imagine X is soul sand, A is carpet. that jump is possible
|
||||
int jumpUpTwo = determinePlayerRealSupportLevel(B, A);
|
||||
if (jumpUpTwo >= 0 && jumpUpTwo <= couldJumpUpTo - Blip.TWO_BLOCKS) {
|
||||
if (Main.DEBUG && (!alreadyWithinU || protrudesIntoThirdBlock(jumpUpTwo))) {
|
||||
throw new IllegalStateException(); // numeric impossibilities
|
||||
}
|
||||
return Collision.JUMP_TO_VOXEL_TWO_UP;
|
||||
}
|
||||
}
|
||||
if (alreadyWithinU && A.collidesWithPlayer) { // now that voxel two up, the result within A, is eliminated, we can't proceed if A would block at head level
|
||||
return Collision.BLOCKED; // we are too tall. bonk!
|
||||
}
|
||||
// D cannot prevent us from doing anything because it cant be higher than 1.5. therefore, makes sense to check CB before DC.
|
||||
int voxelUp = determinePlayerRealSupportLevel(C, B);
|
||||
if (voxelUp >= 0) {
|
||||
// fundamentally a step upwards, from X to B instead of X to C
|
||||
// too high?
|
||||
if (A.collidesWithPlayer || U.collidesWithPlayer) {
|
||||
return Collision.BLOCKED;
|
||||
}
|
||||
if (protrudesIntoThirdBlock(voxelUp) && (E.collidesWithPlayer || F.collidesWithPlayer)) {
|
||||
return Collision.BLOCKED;
|
||||
}
|
||||
int heightRelativeToStartVoxel = voxelUp + Blip.FULL_BLOCK;
|
||||
if (heightRelativeToStartVoxel > couldJumpUpTo) {
|
||||
return Collision.BLOCKED;
|
||||
}
|
||||
if (heightRelativeToStartVoxel > couldStepUpTo) {
|
||||
return Collision.JUMP_TO_VOXEL_UP;
|
||||
} // else this is possible!
|
||||
if (Main.DEBUG && (U.collidesWithPlayer || A.collidesWithPlayer || !alreadyWithinU)) {
|
||||
// must already be colliding with U because in order for this non-jump voxel-up step to be even possible, feet must be at least HALF_BLOCK
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
// B can collide with player here, such as if X is soul sand, C is full block, and B is carpet
|
||||
return Collision.VOXEL_UP; // A is already checked, so that's it!
|
||||
}
|
||||
// voxelUp is impossible. pessimistically, this means B is colliding. optimistically, this means B and C are air.
|
||||
if (B.collidesWithPlayer) {
|
||||
// voxel up and voxel two up are both eliminated, so...
|
||||
return Collision.BLOCKED; // we have ruled out stepping on top of B, so now if B is still colliding there is no way forward
|
||||
}
|
||||
int stayLevel = determinePlayerRealSupportLevel(D, C);
|
||||
if (stayLevel >= 0) {
|
||||
// fundamentally staying within the same vertical voxel, X -> C
|
||||
if (protrudesIntoThirdBlock(stayLevel) && !alreadyWithinU) { // step up, combined with our height, protrudes into U and A, AND we didn't already
|
||||
if (U.collidesWithPlayer) { // stayLevel could even be LESS than feet
|
||||
return Collision.BLOCKED;
|
||||
}
|
||||
if (A.collidesWithPlayer) { // already checked (alreadyWithinU && A.collidesWithPlayer) earlier
|
||||
return Collision.BLOCKED;
|
||||
}
|
||||
}
|
||||
if (stayLevel > couldStepUpTo) { // staying within the same voxel means that a jump will always succeed
|
||||
// aka. the jump might headbonk but not in a way that prevents the action. also TODO this is an example of where Collision.INADVISABLE could come in such as if E/F/U/A were like lava or something
|
||||
return Collision.JUMP_TO_VOXEL_LEVEL;
|
||||
}
|
||||
return Collision.VOXEL_LEVEL; // btw it's possible that stayLevel < feetBlips, since a small fall is sorta ignored and treated as a VOXEL_LEVEL
|
||||
} // voxel_level (C within, D underneath) is eliminated, only remaining possibilities are blocked or fall
|
||||
if (C.collidesWithPlayer) {
|
||||
return Collision.BLOCKED;
|
||||
}
|
||||
if (!D.collidesWithPlayer) {
|
||||
return Collision.FALL;
|
||||
}
|
||||
if (Main.DEBUG && D.collisionHeightBlips() >= Blip.FULL_BLOCK && D.fullyWalkableTop) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
if (D.collisionHeightBlips() < Blip.FULL_BLOCK + feetBlips) {
|
||||
return Collision.FALL;
|
||||
} else {
|
||||
return Collision.BLOCKED;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean protrudesIntoThirdBlock(int feet) { // only false for feet between 0 and 3. true for 4 and up
|
||||
return feet > Blip.TWO_BLOCKS - Blip.PLAYER_HEIGHT_SLIGHT_OVERESTIMATE; // > and not >= because the player height is a slight overestimate
|
||||
}
|
||||
|
||||
public enum Collision {
|
||||
// TODO maybe we need another option that is like "you could do it, but you shouldn't". like, "if you hit W, you would walk forward, but you wouldn't like the outcome" such as cactus or lava or something
|
||||
|
||||
BLOCKED, // if you hit W, you would not travel (example: walking into wall)
|
||||
JUMP_TO_VOXEL_LEVEL, // blocked, BUT, if you jumped, you would end up at voxel level. this one is rare, it could only happen if you jump onto a block that is between 0.5 and 1.0 blocks high, such as 7-high snow layers
|
||||
JUMP_TO_VOXEL_UP, // blocked, BUT, if you jumped, you would end up at one voxel higher. this is the common case for jumping.
|
||||
JUMP_TO_VOXEL_TWO_UP, // blocked, BUT, if you jumped, you would end up two voxels higher. this can only happen for weird blocks like jumping out of soul sand and up one
|
||||
VOXEL_UP, // if you hit W, you will end up at a position that's a bit higher, such that you'd determineRealPlayerSupport up by one (example: walking from a partial block to a full block or higher, e.g. half slab to full block, or soul sand to full block, or soul sand to full block+carpet on top)
|
||||
VOXEL_LEVEL, // if you hit W, you will end up at a similar position, such that you'd determineRealPlayerSupport at the same integer grid location (example: walking forward on level ground)
|
||||
FALL; // if you hit W, you will not immediately collide with anything, at all, to the front or to the bottom (example: walking off a cliff)
|
||||
|
||||
public int voxelVerticalOffset() {
|
||||
switch (this) {
|
||||
case VOXEL_LEVEL:
|
||||
case JUMP_TO_VOXEL_LEVEL:
|
||||
return 0;
|
||||
case VOXEL_UP:
|
||||
case JUMP_TO_VOXEL_UP:
|
||||
return 1;
|
||||
case JUMP_TO_VOXEL_TWO_UP:
|
||||
return 2;
|
||||
default:
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean requiresJump() {
|
||||
switch (this) {
|
||||
case VOXEL_LEVEL:
|
||||
case VOXEL_UP:
|
||||
return false;
|
||||
case JUMP_TO_VOXEL_LEVEL:
|
||||
case JUMP_TO_VOXEL_UP:
|
||||
case JUMP_TO_VOXEL_TWO_UP:
|
||||
return true;
|
||||
default:
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static int playerFalls(long newPos, WorldState worldState, SolverEngineInput engineInput) {
|
||||
// this means that there is nothing preventing us from walking forward and falling
|
||||
// iterate downwards to see what we would hit
|
||||
for (int descent = 0; ; descent++) {
|
||||
// NOTE: you cannot do (descent*Face.DOWN.offset)&BetterBlockPos.POST_ADDITION_MASK because Y is serialized into the center of the long. but I suppose you could do it with X. hm maybe Y should be moved to the most significant bits purely to allow this :^)
|
||||
long support = BetterBlockPos.offsetBy(newPos, 0, -descent, 0);
|
||||
long under = Face.DOWN.offset(support);
|
||||
if (Main.DEBUG && !engineInput.bounds.inRangePos(under)) {
|
||||
throw new IllegalStateException(); // should be caught by PREVENTED_BY_UNDERNEATH
|
||||
}
|
||||
VoxelResidency res = canPlayerStand(engineInput.at(under, worldState), engineInput.at(support, worldState));
|
||||
if (Main.DEBUG && descent == 0 && res != VoxelResidency.FLOATING) {
|
||||
throw new IllegalStateException(); // CD shouldn't collide, it should be D and the one beneath... (playerTravelCollides would have returned BLOCKED or VOXEL_LEVEL)
|
||||
}
|
||||
switch (res) {
|
||||
case FLOATING:
|
||||
continue; // as expected
|
||||
case PREVENTED_BY_UNDERNEATH:
|
||||
case PREVENTED_BY_WITHIN:
|
||||
return -1; // no safe landing
|
||||
case UNDERNEATH_PROTRUDES_AT_OR_ABOVE_FULL_BLOCK:
|
||||
case STANDARD_WITHIN_SUPPORT:
|
||||
// found our landing spot
|
||||
return descent;
|
||||
default:
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If the player feet is greater than the return from this function, the player can sneak out at that altitude without colliding with this column
|
||||
* <p>
|
||||
* If there is no collision possible this will return a negative number (which should fit in fine with the above ^ use case)
|
||||
*/
|
||||
public static int highestCollision(BlockStateCachedData underneath, BlockStateCachedData within) {
|
||||
return Math.max(
|
||||
underneath.collidesWithPlayer ? underneath.collisionHeightBlips() - Blip.FULL_BLOCK : -1,
|
||||
within.collidesWithPlayer ? within.collisionHeightBlips() : -1
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
/*
|
||||
* This file is part of Baritone.
|
||||
*
|
||||
* Baritone is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Baritone is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Baritone. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package baritone.builder;
|
||||
|
||||
import baritone.api.utils.BetterBlockPos;
|
||||
import it.unimi.dsi.fastutil.longs.LongArrayList;
|
||||
|
||||
public class PlayerReachSphere {
|
||||
|
||||
public final long[] positions;
|
||||
|
||||
public PlayerReachSphere(double playerReachDistance) {
|
||||
int ceiledPlusOne = 1 + (int) Math.ceil(playerReachDistance);
|
||||
double realSq = playerReachDistance * playerReachDistance;
|
||||
int ceiledSq = (int) Math.ceil(realSq);
|
||||
LongArrayList sphere = new LongArrayList();
|
||||
for (int x = -ceiledPlusOne; x <= ceiledPlusOne; x++) {
|
||||
for (int y = -ceiledPlusOne; y <= ceiledPlusOne; y++) {
|
||||
for (int z = -ceiledPlusOne; z <= ceiledPlusOne; z++) {
|
||||
int d = closestPossibleDist(x, y, z);
|
||||
if (d <= ceiledSq && d < realSq) { // int comparison short circuits before floating point
|
||||
sphere.add(BetterBlockPos.toLong(x, y, z));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
sphere.trim();
|
||||
positions = sphere.elements();
|
||||
}
|
||||
|
||||
public static int closestPossibleDist(int dx, int dy, int dz) { // player eye is within the origin voxel (0,0,0)
|
||||
dx = lower(dx);
|
||||
dy = lower(dy);
|
||||
dz = lower(dz);
|
||||
return dx * dx + dy * dy + dz * dz;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bring closer to 0 by one if not already zero
|
||||
*/
|
||||
private static int lowerBranchy(int v) {
|
||||
if (v > 0) {
|
||||
return v - 1;
|
||||
} else if (v < 0) {
|
||||
return v + 1;
|
||||
} else {
|
||||
return v;
|
||||
}
|
||||
}
|
||||
|
||||
private static int lower(int v) {
|
||||
return v + (v >>> 31) - ((-v) >>> 31);
|
||||
}
|
||||
|
||||
static {
|
||||
for (int i = -10; i <= 10; i++) {
|
||||
if (lowerBranchy(i) != lower(i)) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
/*
|
||||
* This file is part of Baritone.
|
||||
*
|
||||
* Baritone is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Baritone is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Baritone. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package baritone.builder;
|
||||
|
||||
public enum PlayerVantage {
|
||||
LOOSE_CENTER, // allow plus or minus 0.15 in either direction
|
||||
STRICT_CENTER, // player stands at (0.5, 0.5) on the block with no variance allowed
|
||||
SNEAK_BACKPLACE
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
This is where I am working on a rewrite from-scratch of the Baritone schematic builder.
|
||||
|
||||
I will write more documentation eventually, I have some planning documents that I may eventually paste into here and such, I just don't want to commit to writing about something when it's still up for change :)
|
||||
|
||||
The current Baritone BuilderProcess has a number of issues, no one disagrees there. The question is whether to try and fix those issues, or to start from scratch. I believe that starting from scratch is a good idea in this case. The reason is that I want to change the fundamental approach to building schematics. The current approach is basically "do whatever places / breaks I can from wherever I happen to be at the moment" and also "and if I can't work on anything from here, pathfind to a coordinate where I probably will be able to". This suffers from an inherently unfixable problem, which is that **there is no plan**. There are an untold number of infinite loops it can get stuck in that I have just hack patched over. Did you know that whenever the builder places a block, it increments a counter so that for the next five ticks (0.25 seconds) it won't consider breaking any blocks? This was my fix for it just placing and breaking the same block over and over. Did you know that the builder only places blocks at or below foot level purely because otherwise it would stupidly box itself in, because it isn't thinking ahead to "if I cover myself in blocks, I won't be able to walk to the next thing I need to do". And this doesn't even get into the larger scale infinite loops. I patched over the short ones, where it gets itself stuck within seconds, but the long term ones are a different beast. For example, the pathfinder that takes it to the next task can place / break blocks to get there. It has strict penalties for undoing work in the schematic, of course, but sometimes that's the only way forward. And in that scenario, it can cause infinite loops where each cycle lasts 30+ seconds ("nothing to do here, let's pathfind to the other side of the schematic" -> break a block in order to get there -> arrive at the other end -> "whoa i need to go place that block" -> walk back to where it started and place that block -> repeat the cycle).
|
||||
|
||||
So the idea, as you might have guessed from that foreshadowing, is that the new builder will generate a **complete** plan that includes interspersed actions of placing blocks and moving the player. This plan will be coherent and will not suffer from the issues of the current pathfinder in which costs are unable to take into account the previous actions that have changed the world. I'll say more on that later. This does mean that the schematic builder will not use the same path planner as the rest of Baritone.
|
||||
|
||||
Current restrictions that the builder will have, at least initially:
|
||||
* No breaking blocks. If you want to `cleararea` you can just use the old one, it works fine for that specific thing, at least.
|
||||
* For that reason, the schematic area should be clear. Think of this like a 3d printer going from bottom to top. It takes in a schematic, generates a plan of how to build it, and executes it.
|
||||
* Because it generates a full plan, if you think about it, this means that your schematic needs to be plannable. Therefore, if your schematic has something unplaceable in it, it won't even begin to do anything. I'll probably add an option to just skip impossible blocks with a warning. But plan for certain things (e.g. lava) to not be possible.
|
||||
* Ancillary only placed old-baritone-style to assist in movement
|
||||
|
||||
This is less dependent on Minecraft code. I'm too annoyed by things like `IBlockState` -> `BlockState` or `player.motionY()` -> `player.getMotion().y` or `== Blocks.AIR` -> `instanceof BlockAir`. For now, I've put everything that imports from Minecraft code into a subpackage called `mc`. I will probably stick with that. Basically I hate having to change all the stupid mapping names and deal with merge conflicts on those. For that reason I plan to use `int` instead of `IBlockState` everywhere, and basically do like `cachedData[state].isAir` instead of `state.getBlock() instanceof BlockAir` (so like `cachedData` would be a `BlockStateCachedData[]` with one entry per valid `IBlockState`). Then, for different versions of the game, all that would need to change is the code to populate array. This should help with checks like can-walk-on and can-place-against.
|
||||
|
||||
If all the blocks that existed in Minecraft were solid, can-walk-on, can-place-against, this would be trivially easy. (I know - even for schematics of blocks like that, the current builder has problems.) Even for blocks such as glazed terracotta that have orientation, it would be okay.
|
||||
|
||||
The problem arises with all these blocks that people love to use in builds that are really frustrating to work around. I'm referring to slabs, stairs, fences, walls, etc.
|
||||
|
||||
The current pathfinder makes a naive assumption that every movement's starting point is sane. In other words, it assumes that the previous movement will have gotten the player to the starting point stably. So even if the starting point is currently underground, the previous movement will have broken the two blocks that the player is now assumed to be standing in. If the starting point is floating in midair, the previous movement must have gotten a block placed that the player is currently standing on. This allows for incredibly fast long distance pathing. However, that isn't the goal here. In normal Baritone pathfinding, the goal is to get from point A to point B. Placing and breaking blocks along the way is only done as necessary to accomplish the first goal. Whereas in structure building, it's to place and break blocks, to realize a schematic. The player moving around is only done as necessary to accomplish the first goal. So, clearly, the builder pathfinder needs to be smarter than this. In order to generate these plans, it does need to be answer hypotheticals like "by this point in the schematic, if I stood here, could I reach this face of this block to complete this placement?". It also needs to be able to answer questions like "I'm standing at XYZ. But what am I actually standing on? At this point in the construction, is that a scaffolding throwaway block, or is it the half slab that we eventually intend to be placed here?". Therefore, a standard pathfinder, where a node is uniquely identified by its coordinate, will not be sufficient, as that doesn't convey any information about actions taken to the world up to that point. ~~If we want to use a traditional graph search pathfinder,~~ a node must actually be identified by the XYZ of the player and the set of blocks that have been modified so far.
|
||||
|
||||
~~I don't want to go too much further into how a traditional graph search algorithm could work here, as I'm not even certain that's what would work best. I'm considering STAN (an offshoot of graphplan) as well. It might be a hybrid solution, who knows. (e.g. try naive DFS, if that can't find a plan after X milliseconds, then fall back to graphplan).~~
|
||||
|
||||
I'm not sure how to think about performance for this. Obviously there are copes like "pregenerate a plan for a schematic, reuse it". There are also copes like "just throw it in another thread, who cares how long it takes". Because if I dump in a 128x128x128 schematic of all stone (two million blocks), it takes a few seconds to separate the strongly connected components. Is that okay? I think it is. Lol.
|
||||
|
||||
One particularly annoying thing is needing to reimplement portions of Minecraft. For example, there is a bit of code that I can easily call that says "If I looked in this direction right now, and right clicked, what block precisely would it place". Because, well, Minecraft needs to decide what block appears if and when I right click. This works great for current Baritone and I can just call it without needing to worry. For this builder though, I need to do hypotheticals. "If I were standing here on top of this block (that doesn't exist yet), looking at this face of this block (which doesn't exist yet), and right clicked while holding this item (which I don't have), what block would appear?". Minecraft code isn't built like that. There are so many conditions based on Y hit, face hit, player orientation, etc. Just look at the trapdoor place logic to instantly die. So I need to reimplement all the code for "I desire this block state (northward facing upside down acacia stairs). How can I make this coordinate contain that block? (well, you need to be facing north and right click the top face of an adjacent block with such a stair)". That code needs to be written for every block that has custom placement like that. The question of "If block X is at coordinate Y, could I right click against that to place block Z on face D" is incredibly frustratingly not easily answerable. It all depends on external variables like the horizontal orientation of the player, and even the Y coordinate of the player (for some blocks like pistons and dispensers).
|
||||
|
||||
I'm trying my best to minimize floating point calculations as much as possible. The fast case for the graph search should never use floating point. I'm too annoyed by even simple things like `> 1` being randomly wrong (with `0.99999` or `1.000001`). Also, no need to have path costs be floating point. Both path costs and player Y will be fixed point integers. Costs have a to-be-determined denominator, for player Y it's sixteen.
|
||||
|
||||
I'm going to list stupid things about Minecraft that have pissed me off during this:
|
||||
* Every `IBlockState` in the game has an entry in `Block.BLOCK_STATE_IDS`. Except for some variants of tripwire. Nothing else. Just tripwire is the exception. And it isn't even for a good or elegant reason. It's just hardcoded in the thing that constructs the block state registry to skip some of the tripwire states. No idea why.
|
||||
* `Block.BLOCK_STATE_IDS` has double entries. There are many states that map to the same integer ID, when you go back to states it sets certain properties to default. Such as `BlockStairs.SHAPE`.
|
||||
|
||||
Things about old Baritone builder that are stupid and that I will do better:
|
||||
* The thing I mentioned above about left and right clicking
|
||||
* Complete nonunderstanding of orientable blocks. It has no idea that in order to place a stair / a torch it has to go walk around and look at the block from the other side.
|
||||
* Instead of just placing blocks normally, it sneaks before EVERY right click. This makes it look unnatural, stupid, and jittery, for no reason.
|
||||
|
||||
The area shall be combined with the schematic in the following way:
|
||||
* The entire buildable area is considered to be "part of the schematic"
|
||||
* The working space will be that area. The pathfinder's universe ends at the border of that area.
|
||||
* The current status is therefore defineable as the schematic, the set of block positions that are "intended scaffolding", and the set of block positions that have been placed (air is interpreted as scaffolding whether intended or not)
|
||||
* Needed because the scaffolder is not magic, it's possible that the solver needs to place additional scaffolding in order to reach something
|
||||
* There are no further possible options beyond those (want air have air, want scaffolding have air, want air have scaffolding, want block have air, want block have block)
|
||||
* Blocks that are desired to be not-air that are currently a different block are disallowed (for the time being) (because that would require breaking blocks)
|
||||
* Not yet decided if the schematic itself is allowed to have throwaways. Probably should be allowed, it would be indicative of bad design if it weren't allowed.
|
||||
|
||||
# How will it work
|
||||
|
||||
I'm going to boldly write down the current plan. The above part of the readme ^ was all written in 2021 when I was just getting started and didn't really have a well-formed solution to how this mystical schematic building plan would be generated in the first place.
|
||||
|
||||
## The preprocessing
|
||||
|
||||
We load the schematic from disk and probably do some basic checks that it doesn't have any impossible blocks in it.
|
||||
|
||||
First, we create the [place order dependency graph](https://github.com/cabaletta/baritone/blob/a9d6aaebf3f72c7af8e8fe550a1c8cdd36b96fef/src/main/java/baritone/builder/PlaceOrderDependencyGraph.java). This is a simple 3d voxel grid that stores six bits per blockpos in the schematic, representing what blocks have to be placed before this one. For a normal block, in theory all six could be on. (in practice, baritone 2 will probably never place top-to-bottom, so in practice it'll only ever use five of those six). For something like a torch, only one will be on, representing how the torch depends on the block it'll be placed against. Sand will have only the DOWN bit on, because if you tried to place the sand before that block that supports it, it would just fall. In short, this graph represents the dependencies of placing the blocks of the schematic. Each bit that's on means that this block *could* be placed against that face, therefore we assume that once any of those faces are placed, this block can be placed next. This calculation is pretty complicated and involves checking both the block and the one it would hypothetically be placed against. For example, bottom slabs can be placed against upside-down stairs... but only if the stair is facing away from the side you want to place the slab against (yes it does take that into account, see [here](https://github.com/cabaletta/baritone/blob/a9d6aaebf3f72c7af8e8fe550a1c8cdd36b96fef/src/main/java/baritone/builder/mc/BlockStatePropertiesExtractor.java#L48-L69)).
|
||||
|
||||
A [few basic checks](https://github.com/cabaletta/baritone/blob/a9d6aaebf3f72c7af8e8fe550a1c8cdd36b96fef/src/main/java/baritone/builder/DependencyGraphAnalyzer.java) are run against the place order dependency graph, namely that every block can be placed from at least one side, then we check that there is some hypothetical placeable route from the boundary of the schematic to everything in the interior. This catches cases such as shulker boxes placed against each other, where you need one already there to place the other, but that other one also needs the first one (baritone 2 won't support this since it makes the code much easier). Also you might be wondering, "what do you mean placeable route from the boundary? isn't the schematic air? air isn't placeable". The PlaceOrderDependencyGraph treats air as scaffolding, because, it could be. The alternative is just to have no data in the air parts, which is boring. In practice, this is useful for the next step because the scaffolder might need to add scaffolding to the build.
|
||||
|
||||
## Scaffolding
|
||||
|
||||
In Minecraft, blocks have to be placed against other blocks. You can't make one appear way up in the air, you can only make one appear next to an existing block. Therefore, we are going to add a reasonable and hopefully nearly minimal quanity of scaffolding blocks to the schematic so that the entire thing is placeable from a single starting point (which will be at the very bottom, due to how (as mentioned earlier) the dependency graph never points downward, only upward or sideways (this is due to the fact that builds are completed in survival mode and Steve can't fly so he has to walk on top of what he's building)).
|
||||
|
||||
To accomplish this, first the the place order dependency graph is analyzed using [Tarjan's algorithm](https://github.com/cabaletta/baritone/blob/a9d6aaebf3f72c7af8e8fe550a1c8cdd36b96fef/src/main/java/baritone/builder/TarjansAlgorithm.java) to find the strongly connected components, which are collapsed into single nodes within a collapsed dependency graph (which is now a DAG since all the cycles are collapsed), maintained within a [dependency graph scaffolding overlay](https://github.com/cabaletta/baritone/blob/a9d6aaebf3f72c7af8e8fe550a1c8cdd36b96fef/src/main/java/baritone/builder/DependencyGraphScaffoldingOverlay.java). This means the sections of the schematic in which every block can be placed starting from any of them. That's a bit hard to understand but don't worry about it because in practice, most builds will be mostly comprised of normal blocks that can be placed from any direction, so in practice these collapsed components will be just horizontal slices of the build. But, they won't contain air gaps. (example: if your schematic is a simple tower, each y level gets its own slice. if your schematic is two disconnected towers, there will be two slices at each y level, one for each tower). When I say "slice" or "collapsed component" or "strongly connected component" or just "component", that all means the same thing. Basically like Baritone's existing buildInLayers, except the layers are also split sideways at each air gap.
|
||||
|
||||
The point of doing this is that we can very simply and easily tell where scaffolding should go by looking at the collapsed dependency graph. The algorithm is now actually very simple: look at every component that has no incoming edges (called a "root component"). (there will always be at least one since this graph is acyclic) Those are the component(s) that we'd need to "start with". Because we can't (yet) place them starting anywhere else. So, we add scaffolding until there is only one such root component. The actual algorithm for this is not yet fully nailed down. But probably something simple like "pick the root component with the highest Y level then Dijkstra outwards and downwards until you find another component, then add scaffolding along that path" will work fine. Or it might be more complicated (simultaneous Dijkstra from every starting source). Note that this is guaranteed to succeed because of the preprocessing which checked that the build is placeable from the exterior.
|
||||
|
||||
As the scaffolder decides where to place scaffolding, it updates the dependency graph scaffolding overlay. But, rerunning Tarjan's algorithm each time would be very slow (it's `O(blocks)`). Instead, it detects the new edges added to the graph and updates accordingly. If there are any cycles created (aka strongly connected components), they are collapsed in real time as the scaffolding is placed. This is actually pretty fast because under the "no downward placement" assumption, cycles can only really be created on the same y level, and complicated collapsed graphs on the same y level are probably not going to come up much. You'd have to have some borderline malicious schematic full of directional blocks like hoppers or shulkers or logs or something for this to actually be an issue.
|
||||
|
||||
When the scaffolder is done and all the necessary scaffolding is added to the overlay, we now have a beautiful DAG representing how we're going to build the schematic. There is one root node at the bottom of the build with no incoming edges, and outward edges go upwards connecting the entire build. (note: even though there are no cycles, there can still be diamond shapes (A->B A->C B->D C->D), which ends up being [super annoying](https://github.com/cabaletta/baritone/blob/a9d6aaebf3f72c7af8e8fe550a1c8cdd36b96fef/src/main/java/baritone/builder/DependencyGraphScaffoldingOverlay.java#L242-L288)). Each node represents a component aka slice of the build, and every one of those components is a connected set of blocks all at the same y level. The idea is that once we've got any one of the blocks in a component placed, we can get the rest of them placed (this is how they were computed in the first place, from the place order dependency graph).
|
||||
|
||||
## Planning
|
||||
|
||||
The crux of the matter is that the Minecraft player can't fly or teleport in survival mode. If you were in creative mode, this would be no trouble at all, just do it like a 3d printer bottom to top. But alas, we have to walk on top of the build while we're building it.
|
||||
|
||||
Therefore, it's critical to keep track of the area where the player can walk around. We need to be able to frequently and efficiently answer the question "Is it okay to place this block? Or would that get me stuck and unable to walk onwards?"
|
||||
|
||||
The idea here is a data structure that enables [dynamic connectivity](https://en.wikipedia.org/wiki/Dynamic_connectivity), specifically the level structure on top of Euler Tour Forests. This is a data structure that accepts information such as "vertex v and u are now linked by an edge" and "that edge has now been cut" in any order, taking amortized `O((log n)^2)` time. It is able to answer queries of whether v and u are connected by any path also in amortized `O((log n)^2)` time. This is *mind boggling*. Even if you have some incredibly complicated grid with millions of nodes, it can tell you whether a path exists in polylog time. And, even more incredibly, if you remove an edge, the structure updates *also in polylog time* and future queries will reflect that reality (those components are now disconnected if that was the only link, or if there was another link that one is being used now). Here are some links: [wiki](https://en.wikipedia.org/wiki/Dynamic_connectivity#The_Level_structure), [wiki2](https://en.wikipedia.org/wiki/Euler_tour_technique#Euler_tour_trees), [the paper that invented this particular structure (with these deterministic runtimes), see sections 2, 2.1, 3, 3.1, 3.2](https://u.cs.biu.ac.il/~rodittl/p723-holm.pdf).
|
||||
|
||||
The builder is able to treat this mostly as a black box, an API that exposes `Link(u,v)` `Cut(u,v)` and `Is-Connected?(u,v)`. You don't really need to understand how the euler tour forest works. But the basic idea is to maintain a minimum spanning tree of every graph in the forest, maintain an euler tour on each such graph, and store each euler tour in a balanced binary search tree. e.g. if you had nodes A,B,C with connections AB and BC, the euler tour could look like `(a,b) (b,c) (c,b) (b,a)`. It's very important to note that the tour can go in any order. For example you can "cut the deck" and move the first element of the tour to the back, or vice versa, and it's still perfectly valid. The rule is just that the euler tour will go along each edge twice (once in each direction). The way to tell if two nodes have any path between them is by finding which euler tour each is in - if they're both in the same tour, there is a path between them. When linking two nodes U and V that previously had no path between them, the idea is that you grab the euler tour for both, "cut the deck" so that one begins and ends at U, and the other begins and ends at V, then connect them with the new edge. So, like `"previous tour rotated to start and end at U" + [(U,V)] + "the other previous tour rotated to start and end at V" + [(V,U)]`. (these tours were separate, now they're combined). When cutting an edge, it's a bit more complicated because you have to search for whether there's some other edge that can keep these two components connected by another route, but anyway if you don't find one, you just do the opposite (find the `(V,U)` and the `(U,V)` and snip the tour into half at those places, rotating so that one is at the beginning so you get two pieces not three). You could do this with the tour being stored in a simple `ArrayList<Edge>` for sure, but it would have very bad performance, we can do better. Instead, we use a balanced binary tree (specifically a [red-black tree](https://en.wikipedia.org/wiki/Red%E2%80%93black_tree)). It's sortof interesting - we aren't actually storing any data in the tree, the nodes don't actually have any kind of "order" that is being maintained. All the tree is being used for is maintaining the structure (the pointers and the red/black colors). With this structure, operations such as "cut off all elements before this one, and move them to the back" can be done in `O(log n)` time (as well as the simple operations like insert and delete too) (whereas an ArrayList would take `O(n)` for split / concat). There is an additional `O(log n)` term, as we do some trickery with levels - we actually store `log n` copies of this entire data structure, with the maximum size cut in half each time. We need to do that so `Cut` isn't `O(n)`, because `Cut` needs to find an alternate edge if it exists whenever you try and disconnect two components. I'm not going to explain it but take a look at the previous links or [this lecture which covers it](https://web.stanford.edu/class/archive/cs/cs166/cs166.1166/lectures/17/Small17.pdf).
|
||||
|
||||
The one thing you DO need to know is that we can attach arbitrary data to these trees. As a simple example, each node could have attached to it a count of adjacent blocks that we still intend to place but haven't done so yet. Going up the red-black tree, we could code each node to sum the values of its child nodes. Now, we can look at where the player is standing, traverse the red black tree to the root and look at its count, and that'll let us check if there's still work to do in the area we're standing in. This becomes interesting when used as a hypothetical. For example, when we place a block that would cut off the area the player can walk into two pieces with no path between them anymore, we can notice "Hey, one of these has a remaining block count of 0. It's actually fine to cut off that area, because we're done there! We just need to make sure we're standing on the OTHER side when we place that block". This might not sound exciting but it really is because this is done in `O(log n)` time!!
|
||||
|
||||
This solves the "don't paint yourself into the corner" problem.
|
||||
|
||||
Baritone will be able to realize what block placements are "safe" (aka: don't affect the navigable surface (the area that it can walk around in)), and which ones require special conditions (aka: the ones that cut off the surface into two, three, or even four pieces). And the ones that require special conditions will be much easier to reason about (as described in the last paragraph).
|
||||
|
||||
We'll create a plan by having a list of slices that we're currently working on, starting with the root. When a slice is completed, there will be some logic to potentially unlock new slices (its children and maybe also grandchildren (under some conditions) in the collapsed dependency DAG). As blocks are placed, the navigable surface is updated in real time. Perhaps the rules can be relaxed and we can allow slight "working ahead" into slices that are up next, since we can tell when that would block off our future path or not! The navigable surface will be able to answer quickly "can I get next to this block so I can place it?". There will probably be complicated rules about when it is and isn't okay to cut off the walkable area. For example building any kind of pillar absolutely requires cutting it off. For example maybe it needs to look ahead, see what the build will look like once this offshoot tower is completed, and see if it can fall back down to where it was before? Maybe when it tries to place a block but decides no this one is for later it does exponential backoff and moves it further and further back in some priority queue? No idea :D
|
||||
|
||||
|
||||
@@ -1,396 +0,0 @@
|
||||
/*
|
||||
* This file is part of Baritone.
|
||||
*
|
||||
* Baritone is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Baritone is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Baritone. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package baritone.builder;
|
||||
|
||||
import baritone.api.utils.BetterBlockPos;
|
||||
import it.unimi.dsi.fastutil.ints.IntArrayList;
|
||||
import it.unimi.dsi.fastutil.longs.LongArrayList;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Traces rays
|
||||
*/
|
||||
public class Raytracer {
|
||||
|
||||
public static Optional<Raytrace> runTrace(Vec3d playerEye, long againstPos, Face againstFace, Vec3d hit) {
|
||||
// TODO this totally could be cached tho...
|
||||
if (againstFace.offset(againstPos) != 0) {
|
||||
throw new IllegalStateException("sanity check - for now we assume that all placed blocks end up at 0,0,0");
|
||||
}
|
||||
if (toLong(playerEye) == 0) {
|
||||
throw new IllegalStateException("player eye is within the block we want to place, this is maybe possible? idk i suppose you could do this with, like, a torch? still seems weird. idk if this should be allowed");
|
||||
}
|
||||
if (toLong(playerEye) == againstPos) {
|
||||
throw new IllegalStateException("player eye is within the block we want to place against, this is DEFINITELY impossible");
|
||||
}
|
||||
//System.out.println(BetterBlockPos.fromLong(toLong(playerEye)) + " to " + BetterBlockPos.fromLong(toLong(hit)) + " aka " + playerEye + " to " + hit);
|
||||
if (Main.STRICT_Y && floor(playerEye.y) < 0) {
|
||||
throw new IllegalStateException("im lazy and dont want to fix occupancyCountByY");
|
||||
}
|
||||
long hitPos = toLong(hit);
|
||||
if (hitPos != againstPos && hitPos != 0) {
|
||||
throw new IllegalStateException("ambiguous or incorrect hitvec?");
|
||||
}
|
||||
LongArrayList trace = rayTrace(playerEye.x, playerEye.y, playerEye.z, hit.x, hit.y, hit.z, againstPos);
|
||||
if (trace.size() < 2) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
if (trace.getLong(trace.size() - 1) == 0 && trace.getLong(trace.size() - 2) == againstPos) {
|
||||
// placing through the block
|
||||
// for example, we might be standing to the south of the againstPos but we want to place against the north side of it
|
||||
// while we can raytrace to that face, the problem is that our raytrace is hitting some other side of againstPos (maybe south or up) first and then hitting the face we want second
|
||||
return Optional.empty();
|
||||
}
|
||||
return Optional.of(new Raytrace(playerEye, againstPos, againstFace, hit, trace));
|
||||
}
|
||||
|
||||
private static long toLong(Vec3d hit) {
|
||||
return BetterBlockPos.toLong(floor(hit.x), floor(hit.y), floor(hit.z));
|
||||
}
|
||||
|
||||
public static class Raytrace implements Comparable<Raytrace> {
|
||||
|
||||
public final long againstPos;
|
||||
public final Face againstFace;
|
||||
public final long[] passedThrough;
|
||||
|
||||
public final Vec3d playerEye;
|
||||
public final Vec3d hit;
|
||||
|
||||
public final int[] occupancyCounts;
|
||||
|
||||
private Raytrace(Vec3d playerEye, long againstPos, Face againstFace, Vec3d hit, LongArrayList trace) {
|
||||
this.againstFace = againstFace;
|
||||
this.againstPos = againstPos;
|
||||
this.playerEye = playerEye;
|
||||
this.hit = hit;
|
||||
if (trace.getLong(trace.size() - 1) != againstPos) {
|
||||
print(trace);
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
if (trace.getLong(trace.size() - 2) != 0) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
for (int i = 0; i < trace.size() - 1; i++) {
|
||||
if (!adjacent(trace.getLong(i), trace.getLong(i + 1))) {
|
||||
throw new IllegalStateException(BetterBlockPos.fromLong(trace.getLong(i)) + " to " + BetterBlockPos.fromLong(trace.getLong(i + 1)));
|
||||
}
|
||||
}
|
||||
trace.removeLong(trace.size() - 1); // againstPos doesn't ACTUALLY need to be air, so remove it. it was only there for sanity checking and confirming which face we collided with first
|
||||
trace.trim();
|
||||
if (trace.getLong(0) != toLong(playerEye)) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
this.passedThrough = trace.elements();
|
||||
this.occupancyCounts = computeOccupancyCount();
|
||||
}
|
||||
|
||||
private int[] computeOccupancyCount() {
|
||||
long freebieTop = passedThrough[0]; // player eye
|
||||
long freebieBottom = Face.DOWN.offset(freebieTop); // player feet
|
||||
if (Main.STRICT_Y) {
|
||||
IntArrayList list = new IntArrayList();
|
||||
for (int i = passedThrough.length - 1; i >= 0; i--) {
|
||||
long pos = passedThrough[i];
|
||||
int y = BetterBlockPos.YfromLong(pos);
|
||||
if (list.size() == y) { // works because we removed the last trace element (against), which could have negative y
|
||||
list.add(0);
|
||||
}
|
||||
if (list.size() != y + 1) { // this works because we go in reverse order
|
||||
throw new IllegalStateException("nonconsecutive");
|
||||
}
|
||||
if (pos == freebieTop) {
|
||||
// only here for correctness in spirit, technically not needed for comparison since it will exist in all of them
|
||||
continue;
|
||||
}
|
||||
if (pos == freebieBottom) {
|
||||
if (i != 1) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
list.elements()[y]++; // troll face
|
||||
}
|
||||
list.trim();
|
||||
return list.elements();
|
||||
} else {
|
||||
int cnt = 0;
|
||||
for (long pos : passedThrough) {
|
||||
if (pos != freebieBottom && pos != freebieTop) {
|
||||
cnt++;
|
||||
}
|
||||
}
|
||||
return new int[]{cnt};
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(Raytrace o) { // lower is better
|
||||
{ // first, sort by occupancy counts. shortest ray wins. or rather, ray that is shortest on the bottom.
|
||||
if (occupancyCounts.length != o.occupancyCounts.length) {
|
||||
// any two traces with the same src and dst MAY very well have different passedThrough.length
|
||||
// HOWEVER they are guaranteed to have the same occupancyCounts.length, since they start at the same floor(y) and end at the same floor(y)
|
||||
throw new IllegalStateException("comparing raytraces with unequal src/dst");
|
||||
}
|
||||
for (int i = 0; i < occupancyCounts.length; i++) {
|
||||
int cmp = Integer.compare(occupancyCounts[i], o.occupancyCounts[i]);
|
||||
if (cmp != 0) {
|
||||
return cmp;
|
||||
}
|
||||
}
|
||||
}
|
||||
{ // if occupancy counts match, tiebreak with strict center winning over loose center
|
||||
int cmp = Double.compare(centerDistApprox(), o.centerDistApprox());
|
||||
if (cmp != 0) {
|
||||
return cmp;
|
||||
}
|
||||
}
|
||||
{ // if center status matches, finally tiebreak with simple ray length
|
||||
return Double.compare(distSq(), o.distSq());
|
||||
}
|
||||
}
|
||||
|
||||
public double centerDistApprox() {
|
||||
// calculate distance to center of block but intentionally round it off
|
||||
// the intent is for LOOSE_CENTER to always tie with itself, even though floating point inaccuracy would make it unequal if we did a direct Double.compare
|
||||
double dx = playerEye.x - (floor(playerEye.x) + 0.5d);
|
||||
double dz = playerEye.z - (floor(playerEye.z) + 0.5d);
|
||||
double dist = dx * dx + dz * dz;
|
||||
dist = Math.round(dist * 1000000);
|
||||
return dist;
|
||||
}
|
||||
|
||||
public double distSq() {
|
||||
return hit.distSq(playerEye);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If I add 10 to all the numbers I raytrace, then subtract them afterwards, then I can just use (int) instead of the nasty BRANCHING floor.
|
||||
* <p>
|
||||
* No difference raytracing from -2 to -1 as it is to raytrace from 8 to 9. Just add ten!
|
||||
*/
|
||||
private static final int POSITIVITY_OFFSET = 10;
|
||||
|
||||
private static final int NUM_STEPS = 10_000;
|
||||
|
||||
public static int raytraceMode = 2;
|
||||
|
||||
private static LongArrayList rayTrace(double rawStartX, double rawStartY, double rawStartZ, double endX, double endY, double endZ, long againstPos) {
|
||||
LongArrayList slow = raytraceMode == 0 || Main.SLOW_DEBUG ? rayTraceSlow(rawStartX, rawStartY, rawStartZ, endX, endY, endZ) : null;
|
||||
LongArrayList fast = raytraceMode == 1 || Main.SLOW_DEBUG ? rayTraceFast(rawStartX, rawStartY, rawStartZ, endX, endY, endZ) : null;
|
||||
LongArrayList faster = raytraceMode == 2 || Main.SLOW_DEBUG ? rayTraceZoomy(rawStartX, rawStartY, rawStartZ, endX, endY, endZ, againstPos) : null;
|
||||
if (Main.SLOW_DEBUG) {
|
||||
if (fast.equals(slow) && fast.equals(faster)) {
|
||||
} else {
|
||||
System.out.println(rawStartX + " " + rawStartY + " " + rawStartZ + " " + endX + " " + endY + " " + endZ + " " + againstPos);
|
||||
print(slow);
|
||||
print(fast);
|
||||
print(faster);
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
return fast == null ? slow == null ? faster : slow : fast;
|
||||
}
|
||||
|
||||
public static void print(LongArrayList trace) {
|
||||
System.out.println(trace.stream().map(BetterBlockPos::fromLong).collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
public static LongArrayList rayTraceFast(double rawStartX, double rawStartY, double rawStartZ, double endX, double endY, double endZ) {
|
||||
if (willFlipSign(rawStartX) || willFlipSign(rawStartY) || willFlipSign(rawStartZ) || willFlipSign(endX) || willFlipSign(endY) || willFlipSign(endZ)) {
|
||||
throw new IllegalStateException("I suppose this could happen if you set the block reach distance absurdly high? Don't do that.");
|
||||
}
|
||||
double diffX = endX - rawStartX;
|
||||
double diffY = endY - rawStartY;
|
||||
double diffZ = endZ - rawStartZ;
|
||||
if (Math.abs(diffX) < 0.1 && Math.abs(diffY) < 0.1 && Math.abs(diffZ) < 0.1) {
|
||||
// need more checks than before because now the tightest inner do-while does NOT check step against any upper limit at all
|
||||
// therefore, if diff was zero, it would truly get stuck indefinitely, unlike previously where it would bail out at 10010
|
||||
throw new IllegalArgumentException("throwing exception instead of entering infinite inner loop");
|
||||
}
|
||||
double startX = rawStartX + POSITIVITY_OFFSET;
|
||||
double startY = rawStartY + POSITIVITY_OFFSET;
|
||||
double startZ = rawStartZ + POSITIVITY_OFFSET;
|
||||
|
||||
int x = Integer.MIN_VALUE;
|
||||
int y = Integer.MIN_VALUE;
|
||||
int z = Integer.MIN_VALUE;
|
||||
LongArrayList voxelsIntersected = new LongArrayList();
|
||||
int step = 0;
|
||||
double mult = 1.0d / NUM_STEPS;
|
||||
double frac;
|
||||
while (step < NUM_STEPS) {
|
||||
do frac = ++step * mult;
|
||||
while (((x ^ (x = (int) (startX + diffX * frac))) | (y ^ (y = (int) (startY + diffY * frac))) | (z ^ (z = (int) (startZ + diffZ * frac)))) == 0);
|
||||
voxelsIntersected.add(BetterBlockPos.toLong(x - POSITIVITY_OFFSET, y - POSITIVITY_OFFSET, z - POSITIVITY_OFFSET));
|
||||
}
|
||||
if (step > NUM_STEPS + 1) {
|
||||
throw new IllegalStateException("No floating point inaccuracies allowed. Or, at least, no more than 2 parts in 10,000 of wiggle room lol. " + step);
|
||||
}
|
||||
return voxelsIntersected;
|
||||
}
|
||||
|
||||
/**
|
||||
* Here's an alternate implementation of the above that is functionally the same, just simpler (and slower)
|
||||
* <p>
|
||||
* The inner loop branches seven times instead of one, for example.
|
||||
*/
|
||||
private static LongArrayList rayTraceSlow(double startX, double startY, double startZ, double endX, double endY, double endZ) {
|
||||
// i'd love to use strictfp here and on floor, but it could unironically prevent a much needed JIT to native, so I won't, sadly
|
||||
double diffX = endX - startX;
|
||||
double diffY = endY - startY;
|
||||
double diffZ = endZ - startZ;
|
||||
|
||||
int prevX = Integer.MIN_VALUE;
|
||||
int prevY = Integer.MIN_VALUE;
|
||||
int prevZ = Integer.MIN_VALUE;
|
||||
LongArrayList ret = new LongArrayList();
|
||||
int ourLimit = NUM_STEPS + 1;
|
||||
for (int step = 0; step <= ourLimit; step++) { // 1 branch (step <= ourLimit)
|
||||
double frac = step / (double) NUM_STEPS; // go THROUGH the face by a little bit, poke through into the block
|
||||
int x = floor(startX + diffX * frac); // 1 branch in floor
|
||||
int y = floor(startY + diffY * frac); // 1 branch in floor
|
||||
int z = floor(startZ + diffZ * frac); // 1 branch in floor
|
||||
if (x == prevX && y == prevY && z == prevZ) { // 3 branches (due to && short circuiting)
|
||||
continue;
|
||||
}
|
||||
prevX = x;
|
||||
prevY = y;
|
||||
prevZ = z;
|
||||
ret.add(BetterBlockPos.toLong(x, y, z));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static boolean willFlipSign(double pos) {
|
||||
return flippedSign(pos + POSITIVITY_OFFSET);
|
||||
}
|
||||
|
||||
private static boolean flippedSign(double pos) {
|
||||
return pos < 1d;
|
||||
}
|
||||
|
||||
private static boolean adjacent(long a, long b) {
|
||||
if (a == b) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
for (Face face : Face.VALUES) {
|
||||
if (face.offset(a) == b) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static int floor(double value) {
|
||||
int rawCast = (int) value;
|
||||
if (value < (double) rawCast) {
|
||||
// casting rounded up (probably because value < 0)
|
||||
return rawCast - 1;
|
||||
} else {
|
||||
// casting rounded down
|
||||
return rawCast;
|
||||
}
|
||||
}
|
||||
|
||||
public static LongArrayList rayTraceZoomy(double startX, double startY, double startZ, double endX, double endY, double endZ, long againstPos) {
|
||||
if (endX < 0 || endX > 1 || endY < 0 || endY > 1 || endZ < 0 || endZ > 1) {
|
||||
throw new IllegalStateException("won't work");
|
||||
}
|
||||
if (flippedSign(startX += POSITIVITY_OFFSET) | flippedSign(startY += POSITIVITY_OFFSET) | flippedSign(startZ += POSITIVITY_OFFSET) | flippedSign(endX += POSITIVITY_OFFSET) | flippedSign(endY += POSITIVITY_OFFSET) | flippedSign(endZ += POSITIVITY_OFFSET)) {
|
||||
throw new IllegalStateException("I suppose this could happen if you set the block reach distance absurdly high? Don't do that.");
|
||||
}
|
||||
int voxelEndX = BetterBlockPos.XfromLong(againstPos) + POSITIVITY_OFFSET;
|
||||
int voxelEndY = BetterBlockPos.YfromLong(againstPos) + POSITIVITY_OFFSET;
|
||||
int voxelEndZ = BetterBlockPos.ZfromLong(againstPos) + POSITIVITY_OFFSET;
|
||||
|
||||
int voxelInX = (int) startX;
|
||||
int voxelInY = (int) startY;
|
||||
int voxelInZ = (int) startZ;
|
||||
if (startX == (double) voxelInX || startY == (double) voxelInY || startZ == (double) voxelInZ) {
|
||||
throw new IllegalStateException("Integral starting coordinates not supported ever since I removed the -0.0d check");
|
||||
}
|
||||
|
||||
LongArrayList voxelsIntersected = new LongArrayList();
|
||||
int steps = 64; // default is 200
|
||||
while (steps-- >= 0) {
|
||||
long posAsLong = BetterBlockPos.toLong(voxelInX - POSITIVITY_OFFSET, voxelInY - POSITIVITY_OFFSET, voxelInZ - POSITIVITY_OFFSET);
|
||||
voxelsIntersected.add(posAsLong);
|
||||
if (posAsLong == againstPos) {
|
||||
if (voxelsIntersected.size() == 1 || voxelsIntersected.getLong(voxelsIntersected.size() - 2) != 0) {
|
||||
voxelsIntersected.add(0);
|
||||
}
|
||||
return voxelsIntersected;
|
||||
}
|
||||
double nextIntegerX, nextIntegerY, nextIntegerZ;
|
||||
// potentially more based branchless impl?
|
||||
nextIntegerX = voxelInX + ((voxelInX - voxelEndX) >>> 31); // if voxelEnd > voxelIn, then voxelIn-voxelEnd will be negative, meaning the sign bit is 1
|
||||
nextIntegerY = voxelInY + ((voxelInY - voxelEndY) >>> 31); // if we do an unsigned right shift by 31, that sign bit becomes the LSB
|
||||
nextIntegerZ = voxelInZ + ((voxelInZ - voxelEndZ) >>> 31); // therefore, this increments nextInteger iff EndX>inX, otherwise it leaves it alone
|
||||
// remember: don't have to worry about the case when voxelEnd == voxelIn, because nextInteger value wont be used
|
||||
|
||||
double fracIfSkipX = 2; // just has to be strictly greater than 1, might as well just go up to the next int
|
||||
double fracIfSkipY = 2;
|
||||
double fracIfSkipZ = 2;
|
||||
double distanceFromStartToEndX = endX - startX;
|
||||
double distanceFromStartToEndY = endY - startY;
|
||||
double distanceFromStartToEndZ = endZ - startZ;
|
||||
if (voxelEndX != voxelInX) { // reminder to future self: don't "branchlessify" this, it's MUCH slower (pretty obviously, floating point div is much worse than a branch mispredict, but integer increment (like the other two removed branches) are cheap enough to be worth doing either way)
|
||||
fracIfSkipX = (nextIntegerX - startX) / distanceFromStartToEndX;
|
||||
}
|
||||
if (voxelEndY != voxelInY) {
|
||||
fracIfSkipY = (nextIntegerY - startY) / distanceFromStartToEndY;
|
||||
}
|
||||
if (voxelEndZ != voxelInZ) {
|
||||
fracIfSkipZ = (nextIntegerZ - startZ) / distanceFromStartToEndZ;
|
||||
}
|
||||
if (fracIfSkipX < fracIfSkipY && fracIfSkipX < fracIfSkipZ) {
|
||||
// note: voxelEndX == voxelInX is impossible because allowSkip would be set to false in that case, meaning that the elapsed distance would stay at default
|
||||
startX = nextIntegerX;
|
||||
startY += distanceFromStartToEndY * fracIfSkipX;
|
||||
startZ += distanceFromStartToEndZ * fracIfSkipX;
|
||||
voxelInX = ((int) startX) - ((voxelEndX - voxelInX) >>> 31); // tested: faster to paste this 3 times with only one of the subtractions in each
|
||||
voxelInY = ((int) startY);
|
||||
voxelInZ = ((int) startZ);
|
||||
} else if (fracIfSkipY < fracIfSkipZ) {
|
||||
startX += distanceFromStartToEndX * fracIfSkipY;
|
||||
startY = nextIntegerY;
|
||||
startZ += distanceFromStartToEndZ * fracIfSkipY;
|
||||
voxelInX = ((int) startX);
|
||||
voxelInY = ((int) startY) - ((voxelEndY - voxelInY) >>> 31);
|
||||
voxelInZ = ((int) startZ);
|
||||
} else {
|
||||
startX += distanceFromStartToEndX * fracIfSkipZ;
|
||||
startY += distanceFromStartToEndY * fracIfSkipZ;
|
||||
startZ = nextIntegerZ;
|
||||
voxelInX = ((int) startX);
|
||||
voxelInY = ((int) startY);
|
||||
voxelInZ = ((int) startZ) - ((voxelEndZ - voxelInZ) >>> 31);
|
||||
}
|
||||
}
|
||||
print(voxelsIntersected);
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
public static LongArrayList rayTraceZoomyAlternate(double startX, double startY, double startZ, double endX, double endY, double endZ, long againstPos) {
|
||||
return rayTraceZoomy(startX, startY, startZ, endX, endY, endZ, againstPos);
|
||||
}
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
/*
|
||||
* This file is part of Baritone.
|
||||
*
|
||||
* Baritone is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Baritone is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Baritone. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package baritone.builder;
|
||||
|
||||
import baritone.api.utils.BetterBlockPos;
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.longs.LongArrayList;
|
||||
import it.unimi.dsi.fastutil.longs.LongList;
|
||||
|
||||
public class ReachabilityCache implements IReachabilityProvider {
|
||||
|
||||
private final Long2ObjectOpenHashMap<LongArrayList> playerPositionToBlock;
|
||||
|
||||
public ReachabilityCache(DependencyGraphScaffoldingOverlay overlay, PlayerReachSphere sphere) throws SchematicIsTooDenseForThisToMakeSenseException {
|
||||
playerPositionToBlock = new Long2ObjectOpenHashMap<>();
|
||||
int maxReasonableCacheSize = overlay.bounds().volume();
|
||||
int[] cnt = {0};
|
||||
overlay.forEachReal(blockPos -> { // by only iterating through real blocks, this will be a much faster and better option for sparse schematics (e.g. staircased map art)
|
||||
for (long offset : sphere.positions) {
|
||||
long playerEyeVoxel = (blockPos + offset) & BetterBlockPos.POST_ADDITION_MASK;
|
||||
if (overlay.bounds().inRangePos(playerEyeVoxel)) {
|
||||
LongArrayList blocks = playerPositionToBlock.get(playerEyeVoxel);
|
||||
if (blocks == null) {
|
||||
blocks = new LongArrayList();
|
||||
playerPositionToBlock.put(playerEyeVoxel, blocks);
|
||||
}
|
||||
blocks.add(blockPos);
|
||||
if (cnt[0]++ > maxReasonableCacheSize) {
|
||||
throw new SchematicIsTooDenseForThisToMakeSenseException(); // although, of course, it's perfectly possible for this to NOT be a good idea too
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public LongList candidates(long playerEyeVoxel) {
|
||||
return playerPositionToBlock.get(playerEyeVoxel);
|
||||
}
|
||||
|
||||
public static class SchematicIsTooDenseForThisToMakeSenseException extends RuntimeException {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
/*
|
||||
* This file is part of Baritone.
|
||||
*
|
||||
* Baritone is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Baritone is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Baritone. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package baritone.builder;
|
||||
|
||||
import baritone.api.utils.BetterBlockPos;
|
||||
import it.unimi.dsi.fastutil.longs.LongArrayList;
|
||||
import it.unimi.dsi.fastutil.longs.LongList;
|
||||
|
||||
public class ReachabilityLive implements IReachabilityProvider {
|
||||
|
||||
private final DependencyGraphScaffoldingOverlay overlay;
|
||||
private final PlayerReachSphere sphere;
|
||||
|
||||
public ReachabilityLive(DependencyGraphScaffoldingOverlay overlay, PlayerReachSphere sphere) {
|
||||
this.overlay = overlay;
|
||||
this.sphere = sphere;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LongList candidates(long playerEyeVoxel) {
|
||||
LongArrayList ret = new LongArrayList();
|
||||
for (long offset : sphere.positions) {
|
||||
long block = (playerEyeVoxel + offset) & BetterBlockPos.POST_ADDITION_MASK;
|
||||
if (overlay.bounds().inRangePos(block)) {
|
||||
ret.add(block);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
@@ -1,211 +0,0 @@
|
||||
/*
|
||||
* This file is part of Baritone.
|
||||
*
|
||||
* Baritone is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Baritone is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Baritone. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package baritone.builder;
|
||||
|
||||
import baritone.api.utils.BetterBlockPos;
|
||||
import baritone.builder.DependencyGraphScaffoldingOverlay.CollapsedDependencyGraph;
|
||||
import baritone.builder.DependencyGraphScaffoldingOverlay.CollapsedDependencyGraph.CollapsedDependencyGraphComponent;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.longs.LongList;
|
||||
import it.unimi.dsi.fastutil.longs.LongSets;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Given a DependencyGraphScaffoldingOverlay, put in scaffolding blocks until the entire graph is navigable from the root.
|
||||
* <p>
|
||||
* In other words, add scaffolding blocks to the schematic until the entire thing can theoretically be built from one
|
||||
* starting point, just by placing blocks against blocks. So like, anything floating in the air will get a connector down to the
|
||||
* ground (or over to something that's eventually connected to the ground). After this is done, nothing will be left floating in
|
||||
* midair with no connection to the rest of the build.
|
||||
*/
|
||||
public class Scaffolder {
|
||||
|
||||
private final IScaffolderStrategy strategy;
|
||||
private final DependencyGraphScaffoldingOverlay overlayGraph;
|
||||
// NOTE: these next three fields are updated in-place as the overlayGraph is updated :)
|
||||
private final CollapsedDependencyGraph collapsedGraph;
|
||||
private final Int2ObjectMap<CollapsedDependencyGraphComponent> components;
|
||||
private final Long2ObjectMap<CollapsedDependencyGraphComponent> componentLocations;
|
||||
|
||||
private final List<CollapsedDependencyGraphComponent> rootComponents;
|
||||
|
||||
private Scaffolder(PlaceOrderDependencyGraph graph, IScaffolderStrategy strategy) {
|
||||
this.strategy = strategy;
|
||||
this.overlayGraph = new DependencyGraphScaffoldingOverlay(graph);
|
||||
this.collapsedGraph = overlayGraph.getCollapsedGraph();
|
||||
this.components = collapsedGraph.getComponents();
|
||||
this.componentLocations = collapsedGraph.getComponentLocations();
|
||||
|
||||
this.rootComponents = calcRoots();
|
||||
}
|
||||
|
||||
public static Output run(PlaceOrderDependencyGraph graph, IScaffolderStrategy strategy) {
|
||||
Scaffolder scaffolder = new Scaffolder(graph, strategy);
|
||||
while (scaffolder.rootComponents.size() > 1) {
|
||||
scaffolder.loop();
|
||||
}
|
||||
return scaffolder.new Output();
|
||||
}
|
||||
|
||||
private List<CollapsedDependencyGraphComponent> calcRoots() {
|
||||
// since the components form a DAG (because all strongly connected components, and therefore all cycles, have been collapsed)
|
||||
// we can locate all root components by simply finding the ones with no incoming edges
|
||||
return components.values().stream().filter(component -> component.getIncoming().isEmpty()).collect(Collectors.toCollection(ArrayList::new)); // ensure arraylist since we will be mutating the list
|
||||
}
|
||||
|
||||
private void loop() {
|
||||
if (rootComponents.size() <= 1) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
for (CollapsedDependencyGraphComponent root : rootComponents) {
|
||||
// don't remove from rootComponents yet since we aren't sure which way it'll merge (in theory, in practice it'll stop being a root when STRICT_Y is true, since it'll become a descendant, but in theory with STRICT_Y false it could merge on equal footing with another component)
|
||||
if (!root.getIncoming().isEmpty()) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
LongList path = strategy.scaffoldTo(root, overlayGraph);
|
||||
if (path == null) {
|
||||
continue;
|
||||
}
|
||||
if (!root.getPositions().contains(path.get(path.size() - 1))) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
if (root.getPositions().contains(path.get(0))) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
internalEnable(path);
|
||||
return;
|
||||
}
|
||||
throw new IllegalStateException("unconnectable");
|
||||
}
|
||||
|
||||
private void internalEnable(LongList path) {
|
||||
int cid = collapsedGraph.lastComponentID().getAsInt();
|
||||
applyScaffoldingConnection(overlayGraph, path);
|
||||
int newCID = collapsedGraph.lastComponentID().getAsInt();
|
||||
for (int i = cid + 1; i <= newCID; i++) {
|
||||
if (components.get(i) != null && components.get(i).getIncoming().isEmpty()) {
|
||||
rootComponents.add(components.get(i));
|
||||
System.out.println("Adding");
|
||||
}
|
||||
}
|
||||
// why is this valid?
|
||||
// for this to be valid, we need to be confident that no component from cid 0 to old lastcid could have had incomings become empty
|
||||
// consider the case root -> descendant
|
||||
// what if scaffolding created descendant -> root, then they were merged together, but descendant won?
|
||||
// then, descendant would have cid less than last cid, and it wouldn't be added to rootComponents by the previous line perhaps?
|
||||
// but, dijkstra strategy skips merging roots with their descendants intentionally since it's useless to do so
|
||||
rootComponents.removeIf(root -> {
|
||||
if (root.deleted()) {
|
||||
if (!rootComponents.contains(root.deletedIntoRecursive())) {
|
||||
throw new IllegalStateException(); // sanity check the above - if this throws, i suspect it would mean that a root component was merged into one of its descendants by useless scaffolding
|
||||
// if this ends up being unavoidable, then iterating over all deletedIntoRecursive of rootComponents should find all new rootComponents
|
||||
// this is because all new scaffoldings have their own component, so the only way for an old component to have no incomings is if it was merged "the wrong way" with the root, which is easily locatable by deletedIntoRecursive
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (!root.getIncoming().isEmpty()) { // handle the actual root itself that we just connected (hopefully)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
/*rootComponents.clear();
|
||||
rootComponents.addAll(calcRoots());*/
|
||||
if (Main.DEBUG) {
|
||||
if (!new HashSet<>(rootComponents).equals(new HashSet<>(calcRoots()))) { // equal ignoring order
|
||||
// TODO rootComponents should be a Set instead of a List anyway
|
||||
System.out.println(rootComponents);
|
||||
System.out.println(calcRoots());
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void applyScaffoldingConnection(DependencyGraphScaffoldingOverlay overlayGraph, LongList path) {
|
||||
CollapsedDependencyGraph collapsedGraph = overlayGraph.getCollapsedGraph();
|
||||
Long2ObjectMap<CollapsedDependencyGraphComponent> componentLocations = collapsedGraph.getComponentLocations();
|
||||
if (!componentLocations.containsKey(path.getLong(0))) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
if (!componentLocations.containsKey(path.getLong(path.size() - 1))) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
if (componentLocations.get(path.getLong(0)) == componentLocations.get(path.getLong(path.size() - 1))) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
if (!componentLocations.get(path.getLong(path.size() - 1)).getIncoming().isEmpty()) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
// componentLocations.get(path.getLong(path.size() - 1)).getIncoming() can be either empty or nonempty
|
||||
for (int i = 1; i < path.size(); i++) {
|
||||
if (!overlayGraph.hypotheticalScaffoldingIncomingEdge(path.getLong(i), Face.between(path.getLong(i), path.getLong(i - 1)))) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
LongList positions = path.subList(1, path.size() - 1);
|
||||
positions.forEach(pos -> {
|
||||
if (componentLocations.containsKey(pos)) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
});
|
||||
System.out.println("Enabling " + positions.stream().map(BetterBlockPos::fromLong).collect(Collectors.toList()));
|
||||
positions.forEach(overlayGraph::enable); // TODO more performant to enable in reverse order maybe?
|
||||
}
|
||||
|
||||
public class Output {
|
||||
public void enableAncillaryScaffoldingAndRecomputeRoot(LongList positions) {
|
||||
throw new UnsupportedOperationException("mutable components after scaffolding is not worth it");
|
||||
}
|
||||
|
||||
public CollapsedDependencyGraphComponent getRoot() { // TODO this should probably return a new class that is not mutable in-place
|
||||
if (rootComponents.size() != 1) {
|
||||
throw new IllegalStateException(); // this is okay because this can only possibly be called after Scaffolder.run is completed
|
||||
}
|
||||
CollapsedDependencyGraphComponent root = rootComponents.get(0);
|
||||
if (!root.getIncoming().isEmpty() || root.deleted()) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
return root;
|
||||
}
|
||||
|
||||
public boolean real(long pos) {
|
||||
return overlayGraph.real(pos);
|
||||
}
|
||||
|
||||
public void forEachReal(Bounds.BoundsLongConsumer consumer) {
|
||||
overlayGraph.forEachReal(consumer);
|
||||
}
|
||||
|
||||
public LongSets.UnmodifiableSet scaffolding() {
|
||||
return overlayGraph.scaffolding();
|
||||
}
|
||||
|
||||
public boolean air(long pos) {
|
||||
return overlayGraph.air(pos);
|
||||
}
|
||||
|
||||
DependencyGraphScaffoldingOverlay secretInternalForTesting() {
|
||||
return overlayGraph;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,184 +0,0 @@
|
||||
/*
|
||||
* This file is part of Baritone.
|
||||
*
|
||||
* Baritone is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Baritone is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Baritone. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package baritone.builder;
|
||||
|
||||
import baritone.api.utils.BetterBlockPos;
|
||||
import baritone.builder.DependencyGraphScaffoldingOverlay.CollapsedDependencyGraph.CollapsedDependencyGraphComponent;
|
||||
import it.unimi.dsi.fastutil.HashCommon;
|
||||
import it.unimi.dsi.fastutil.longs.LongArrayList;
|
||||
import it.unimi.dsi.fastutil.longs.LongIterator;
|
||||
import it.unimi.dsi.fastutil.longs.LongList;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.PriorityQueue;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.LongStream;
|
||||
|
||||
public class SimultaneousDijkstraScaffolder implements IScaffolderStrategy {
|
||||
@Override
|
||||
public LongList scaffoldTo(CollapsedDependencyGraphComponent $ignored$, DependencyGraphScaffoldingOverlay overlayGraph) {
|
||||
// the use-case that i'm keeping mind from a performance pov is staircased mapart
|
||||
// O(n^2) across all 128x128=16384 blocks would be cringe
|
||||
// so let's have a combined priority queue from all cdg provenances without a reset on every scaffolding placement
|
||||
List<CollapsedDependencyGraphComponent> roots = overlayGraph.getCollapsedGraph().getComponents().values().stream().filter(component -> component.getIncoming().isEmpty()).collect(Collectors.toList());
|
||||
ObjectOpenHashSet<CollapsedDependencyGraphComponent> remainingRoots = new ObjectOpenHashSet<>();
|
||||
remainingRoots.addAll(roots);
|
||||
Object2IntOpenHashMap<CollapsedDependencyGraphComponent> componentToId = new Object2IntOpenHashMap<>();
|
||||
for (int i = 0; i < roots.size(); i++) {
|
||||
componentToId.put(roots.get(i), i);
|
||||
}
|
||||
PriorityQueue<ScaffoldingSearchNode> openSet = new PriorityQueue<>(Comparator.<ScaffoldingSearchNode>comparingInt(node -> node.costSoFar).thenComparingInt(node -> node.key.cdgid));
|
||||
Object2ObjectOpenHashMap<ScaffoldingSearchKey, ScaffoldingSearchNode> nodeMap = new Object2ObjectOpenHashMap<>();
|
||||
for (CollapsedDependencyGraphComponent component : roots) {
|
||||
int cdgid = componentToId.getInt(component);
|
||||
LongIterator it = component.getPositions().iterator();
|
||||
while (it.hasNext()) {
|
||||
long l = it.nextLong();
|
||||
ScaffoldingSearchKey key = new ScaffoldingSearchKey(l, cdgid);
|
||||
nodeMap.put(key, new ScaffoldingSearchNode(key));
|
||||
}
|
||||
}
|
||||
openSet.addAll(nodeMap.values());
|
||||
while (!openSet.isEmpty()) {
|
||||
ScaffoldingSearchNode node = openSet.poll();
|
||||
CollapsedDependencyGraphComponent provenance = roots.get(node.key.cdgid).deletedIntoRecursive();
|
||||
// is the deletedIntoRecursive valid? i think so because the costSoFar is a shared key in the priority queue. sure you could get a suboptimal path from an old subsection of a new cdg, but, it would only be considered after the optimal path. so, no issue? i think?
|
||||
if (!provenance.getIncoming().isEmpty()) {
|
||||
continue; // node originated from a cdg that has been scaffolded making it no longer a root
|
||||
}
|
||||
CollapsedDependencyGraphComponent tentativeComponent = overlayGraph.getCollapsedGraph().getComponentLocations().get(node.key.pos);
|
||||
if (tentativeComponent != provenance) {
|
||||
// TODO eventually figure out the situation with exclusiveDescendents like in the sequential dijkstra scaffolder
|
||||
LongList toActivate = reconstructPathTo(node);
|
||||
// have to do this bs because the scaffolding route can touch a third component even if only one scaffolding block is added
|
||||
long[] allNearby = toActivate.stream().flatMapToLong(pos -> LongStream.of(OFFS_INCL_ZERO).map(off -> (off + pos) & BetterBlockPos.POST_ADDITION_MASK)).toArray();
|
||||
// have to check before applying scaffolding because getComponentLocations will return the new component and not the deleted root if we did it after
|
||||
Set<CollapsedDependencyGraphComponent> toCheck = LongStream.of(allNearby).mapToObj(overlayGraph.getCollapsedGraph().getComponentLocations()::get).collect(Collectors.toCollection(ObjectOpenHashSet::new));
|
||||
Scaffolder.applyScaffoldingConnection(overlayGraph, toActivate);
|
||||
// have to check this again because new scaffolding can make its own collapsed node
|
||||
// for example if there are two individual blocks separated by a knight's move in strict_y mode, meaning there are two new scaffolding blocks added at a certain y, connecting to one block at that y and another at a higher y, then the two new scaffolding can form a larger collapsed node, causing the previous block to be merged into it
|
||||
// in short, it's possible for a new root to be created
|
||||
toCheck.addAll(LongStream.of(allNearby).mapToObj(overlayGraph.getCollapsedGraph().getComponentLocations()::get).collect(Collectors.toSet()));
|
||||
int sz = remainingRoots.size();
|
||||
for (CollapsedDependencyGraphComponent component : toCheck) {
|
||||
if (component.deleted()) {
|
||||
remainingRoots.remove(component);
|
||||
}
|
||||
}
|
||||
if (remainingRoots.size() >= sz) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
for (long pos : toActivate) { // im being lazy, this boxes to Long i think
|
||||
CollapsedDependencyGraphComponent comp = overlayGraph.getCollapsedGraph().getComponentLocations().get(pos);
|
||||
if (comp.getIncoming().isEmpty()) {
|
||||
if (remainingRoots.add(comp)) {
|
||||
int cdgid = roots.size();
|
||||
roots.add(comp);
|
||||
componentToId.put(comp, cdgid);
|
||||
}
|
||||
ScaffoldingSearchNode newNode = new ScaffoldingSearchNode(new ScaffoldingSearchKey(pos, componentToId.getInt(comp)));
|
||||
nodeMap.put(newNode.key, newNode);
|
||||
openSet.add(newNode);
|
||||
}
|
||||
}
|
||||
if (remainingRoots.size() == 1) {
|
||||
return null;
|
||||
}
|
||||
if (remainingRoots.isEmpty()) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
for (Face face : Face.VALUES) {
|
||||
int newCost = node.costSoFar + DijkstraScaffolder.edgeCost(face);
|
||||
if (overlayGraph.hypotheticalScaffoldingIncomingEdge(node.key.pos, face)) {
|
||||
long neighborPos = face.offset(node.key.pos);
|
||||
ScaffoldingSearchKey neighborKey = new ScaffoldingSearchKey(neighborPos, node.key.cdgid);
|
||||
ScaffoldingSearchNode existingNode = nodeMap.get(neighborKey);
|
||||
if (existingNode != null) {
|
||||
if (existingNode.costSoFar > newCost) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
ScaffoldingSearchNode newNode = new ScaffoldingSearchNode(neighborKey);
|
||||
newNode.costSoFar = newCost;
|
||||
newNode.prev = node;
|
||||
nodeMap.put(newNode.key, newNode);
|
||||
openSet.add(newNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
private static class ScaffoldingSearchKey {
|
||||
long pos;
|
||||
int cdgid;
|
||||
|
||||
public ScaffoldingSearchKey(long pos, int cdgid) {
|
||||
this.pos = pos;
|
||||
this.cdgid = cdgid;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
//if (this == o) return true;
|
||||
//if (!(o instanceof ScaffoldingSearchKey)) return false;
|
||||
ScaffoldingSearchKey that = (ScaffoldingSearchKey) o;
|
||||
return pos == that.pos && cdgid == that.cdgid;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return HashCommon.murmurHash3(cdgid) + (int) HashCommon.murmurHash3(pos);
|
||||
}
|
||||
}
|
||||
|
||||
private static class ScaffoldingSearchNode {
|
||||
|
||||
private final ScaffoldingSearchKey key;
|
||||
private int costSoFar;
|
||||
private ScaffoldingSearchNode prev;
|
||||
|
||||
private ScaffoldingSearchNode(ScaffoldingSearchKey key) {
|
||||
this.key = key;
|
||||
}
|
||||
}
|
||||
|
||||
private static LongList reconstructPathTo(ScaffoldingSearchNode end) {
|
||||
LongList path = new LongArrayList();
|
||||
while (end != null) {
|
||||
path.add(end.key.pos);
|
||||
end = end.prev;
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
private static final long[] OFFS_INCL_ZERO = new long[Face.NUM_FACES + 1];
|
||||
|
||||
static {
|
||||
for (int i = 0; i < Face.NUM_FACES; i++) {
|
||||
OFFS_INCL_ZERO[i] = Face.VALUES[i].offset;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
/*
|
||||
* This file is part of Baritone.
|
||||
*
|
||||
* Baritone is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Baritone is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Baritone. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package baritone.builder;
|
||||
|
||||
import baritone.api.utils.BetterBlockPos;
|
||||
|
||||
public class SneakPosition {
|
||||
|
||||
public static long encode(long pos, Face sneakingTowards) {
|
||||
if (Main.DEBUG && sneakingTowards != null && sneakingTowards.vertical) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
return sneakingTowards == null ? pos : encode(pos, sneakingTowards.horizontalIndex);
|
||||
}
|
||||
|
||||
public static long encode(long pos, int sneak) {
|
||||
if (Main.DEBUG && (sneak < 0 || sneak > 3)) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
if (Main.DEBUG && ((pos & BetterBlockPos.POST_ADDITION_MASK) != pos)) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
long ret = pos
|
||||
| (sneak & 0x1L) << 26 // snugly and cozily fit into the two bits left between Y and Z
|
||||
| (sneak & 0x2L) << 35 // and between X and Y
|
||||
| 1L << 63; // and turn on the top bit as a signal
|
||||
if (Main.DEBUG && ((ret & BetterBlockPos.POST_ADDITION_MASK) != pos)) { // ensure that POST_ADDITION_MASK undoes this trickery (see the comments around POST_ADDITION_MASK definition for why/how)
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static int decode(long posAndSneak) {
|
||||
if (Main.DEBUG && !hasSneak(posAndSneak)) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
return (int) (posAndSneak >> 26 & 0x1L | posAndSneak >> 35 & 0x2L);
|
||||
}
|
||||
|
||||
public static boolean hasSneak(long posAndSneak) {
|
||||
return posAndSneak < 0; // checks the MSB like a boss (epically)
|
||||
}
|
||||
|
||||
public static Face sneakDirectionFromPlayerToSupportingBlock(long posAndSneak) {
|
||||
if (hasSneak(posAndSneak)) {
|
||||
return Face.HORIZONTALS[Face.oppositeHorizontal(decode(posAndSneak))];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static Face sneakDirectionFromSupportingBlockToPlayer(long posAndSneak) {
|
||||
if (hasSneak(posAndSneak)) {
|
||||
return Face.HORIZONTALS[decode(posAndSneak)];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
/*
|
||||
* This file is part of Baritone.
|
||||
*
|
||||
* Baritone is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Baritone is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Baritone. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package baritone.builder;
|
||||
|
||||
import baritone.api.utils.BetterBlockPos;
|
||||
|
||||
import java.util.OptionalLong;
|
||||
|
||||
public class SolvedActionStep {
|
||||
|
||||
private final long placePosition;
|
||||
private final long playerEndPosition;
|
||||
private final Face towardsSneakSupport;
|
||||
|
||||
public SolvedActionStep(long posAndSneak) {
|
||||
this(posAndSneak, -1);
|
||||
}
|
||||
|
||||
public SolvedActionStep(long posAndSneak, long blockPlacedAt) {
|
||||
this.playerEndPosition = posAndSneak & BetterBlockPos.POST_ADDITION_MASK;
|
||||
this.placePosition = blockPlacedAt;
|
||||
this.towardsSneakSupport = SneakPosition.sneakDirectionFromPlayerToSupportingBlock(posAndSneak);
|
||||
if (Main.DEBUG && blockPlacedAt < -1) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
|
||||
public OptionalLong placeAt() {
|
||||
return placePosition == -1 ? OptionalLong.empty() : OptionalLong.of(placePosition);
|
||||
}
|
||||
|
||||
public long playerMovesTo() {
|
||||
return playerEndPosition;
|
||||
}
|
||||
|
||||
public Face towardsSupport() {
|
||||
return towardsSneakSupport;
|
||||
}
|
||||
|
||||
public boolean sneaking() {
|
||||
return towardsSneakSupport != null;
|
||||
}
|
||||
}
|
||||
@@ -1,139 +0,0 @@
|
||||
/*
|
||||
* This file is part of Baritone.
|
||||
*
|
||||
* Baritone is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Baritone is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Baritone. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package baritone.builder;
|
||||
|
||||
import baritone.builder.DependencyGraphScaffoldingOverlay.CollapsedDependencyGraph.CollapsedDependencyGraphComponent;
|
||||
import it.unimi.dsi.fastutil.longs.*;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectArrayFIFOQueue;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.OptionalLong;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class SolverEngineHarness {
|
||||
|
||||
private final ISolverEngine engine;
|
||||
private final PackedBlockStateCuboid blocks;
|
||||
private final PlaceOrderDependencyGraph graph;
|
||||
private final Scaffolder.Output scaffolder;
|
||||
|
||||
public SolverEngineHarness(ISolverEngine engine, PackedBlockStateCuboid blocks, IScaffolderStrategy scaffolderStrategy) {
|
||||
this.engine = engine;
|
||||
this.blocks = blocks;
|
||||
this.graph = new PlaceOrderDependencyGraph(blocks);
|
||||
this.scaffolder = Scaffolder.run(graph, scaffolderStrategy);
|
||||
}
|
||||
|
||||
public List<SolvedActionStep> solve(long playerStartPos) {
|
||||
LongOpenHashSet alreadyPlacedSoFar = new LongOpenHashSet();
|
||||
List<SolvedActionStep> steps = new ArrayList<>();
|
||||
while (true) {
|
||||
Set<CollapsedDependencyGraphComponent> frontier = calculateCurrentSolverFrontier(alreadyPlacedSoFar);
|
||||
if (frontier.isEmpty()) {
|
||||
// nothing on the table!
|
||||
break;
|
||||
}
|
||||
List<LongOpenHashSet> goals = expandAndSubtract(frontier, alreadyPlacedSoFar);
|
||||
long playerPos = steps.isEmpty() ? playerStartPos : steps.get(steps.size() - 1).playerMovesTo();
|
||||
SolverEngineInput inp = new SolverEngineInput(graph, scaffolder.scaffolding(), alreadyPlacedSoFar, goals, playerPos);
|
||||
SolverEngineOutput out = engine.solve(inp);
|
||||
if (Main.DEBUG) {
|
||||
out.sanityCheck(inp);
|
||||
}
|
||||
steps.addAll(out.getSteps());
|
||||
LongList ancillaryScaffolding = new LongArrayList();
|
||||
for (SolvedActionStep step : out.getSteps()) {
|
||||
OptionalLong blockPlace = step.placeAt();
|
||||
if (blockPlace.isPresent()) {
|
||||
long pos = blockPlace.getAsLong();
|
||||
if (!alreadyPlacedSoFar.add(pos)) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
if (scaffolder.air(pos)) { // not part of the schematic, nor intended scaffolding
|
||||
ancillaryScaffolding.add(pos); // therefore it must be ancillary scaffolding, some throwaway block we needed to place in order to achieve something else, maybe to get a needed vantage point on some particularly tricky placement
|
||||
}
|
||||
}
|
||||
}
|
||||
scaffolder.enableAncillaryScaffoldingAndRecomputeRoot(ancillaryScaffolding);
|
||||
}
|
||||
if (Main.DEBUG) {
|
||||
scaffolder.forEachReal(pos -> {
|
||||
if (!alreadyPlacedSoFar.contains(pos)) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
});
|
||||
alreadyPlacedSoFar.forEach(pos -> {
|
||||
if (!scaffolder.real(pos)) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
});
|
||||
}
|
||||
return steps;
|
||||
}
|
||||
|
||||
private List<LongOpenHashSet> expandAndSubtract(Set<CollapsedDependencyGraphComponent> frontier, LongSet already) {
|
||||
return frontier.stream()
|
||||
.map(component -> {
|
||||
LongOpenHashSet remainingPositionsInComponent = new LongOpenHashSet(component.getPositions().size());
|
||||
LongIterator it = component.getPositions().iterator();
|
||||
while (it.hasNext()) {
|
||||
long pos = it.nextLong();
|
||||
if (!already.contains(pos)) {
|
||||
remainingPositionsInComponent.add(pos);
|
||||
}
|
||||
}
|
||||
return remainingPositionsInComponent;
|
||||
}).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private Set<CollapsedDependencyGraphComponent> calculateCurrentSolverFrontier(LongSet alreadyPlacedSoFar) {
|
||||
Set<CollapsedDependencyGraphComponent> currentFrontier = new ObjectOpenHashSet<>();
|
||||
Set<CollapsedDependencyGraphComponent> confirmedFullyCompleted = new ObjectOpenHashSet<>();
|
||||
ObjectArrayFIFOQueue<CollapsedDependencyGraphComponent> toExplore = new ObjectArrayFIFOQueue<>();
|
||||
toExplore.enqueue(scaffolder.getRoot());
|
||||
outer:
|
||||
while (!toExplore.isEmpty()) {
|
||||
CollapsedDependencyGraphComponent component = toExplore.dequeue();
|
||||
for (CollapsedDependencyGraphComponent parent : component.getIncoming()) {
|
||||
if (!confirmedFullyCompleted.contains(parent)) {
|
||||
// to be here, one parent must have been fully completed, but it's possible a different parent is not yet fully completed
|
||||
// this is because while the collapsed block placement dependency graph is a directed acyclic graph, it can still have a diamond shape
|
||||
// imagine the dependency is A->B, A->C, C->D, B->E, D->E (so, A is root, it splits in two, then converges at E)
|
||||
// it might explore it in order A, B, C, E (because B, yet canceled as D is not completed yet), D (because C), E (now succeeds since B and D are confirmed)
|
||||
continue outer;
|
||||
}
|
||||
}
|
||||
LongIterator it = component.getPositions().iterator();
|
||||
while (it.hasNext()) {
|
||||
if (!alreadyPlacedSoFar.contains(it.nextLong())) {
|
||||
currentFrontier.add(component);
|
||||
continue outer;
|
||||
}
|
||||
}
|
||||
if (confirmedFullyCompleted.add(component)) {
|
||||
for (CollapsedDependencyGraphComponent child : component.getOutgoing()) {
|
||||
toExplore.enqueue(child); // always reenqueue children because we added ourselves to confirmedFullyCompleted, meaning this time they may well have all parents completed
|
||||
}
|
||||
}
|
||||
}
|
||||
return currentFrontier;
|
||||
}
|
||||
}
|
||||
@@ -1,141 +0,0 @@
|
||||
/*
|
||||
* This file is part of Baritone.
|
||||
*
|
||||
* Baritone is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Baritone is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Baritone. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package baritone.builder;
|
||||
|
||||
import it.unimi.dsi.fastutil.longs.LongIterator;
|
||||
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
|
||||
import it.unimi.dsi.fastutil.longs.LongSet;
|
||||
import it.unimi.dsi.fastutil.longs.LongSets;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
public class SolverEngineInput {
|
||||
|
||||
public final PlaceOrderDependencyGraph graph;
|
||||
public final LongSet intendedScaffolding;
|
||||
public final LongSet alreadyPlaced;
|
||||
public final LongSet allToPlaceNow;
|
||||
private final List<LongOpenHashSet> toPlaceNow;
|
||||
public final Bounds bounds;
|
||||
public final long player;
|
||||
|
||||
/**
|
||||
* @param graph The place dependency graph of the end goal (both schematic and terrain). Where the terrain ends and the schematic begins is immaterial.
|
||||
* @param intendedScaffolding These locations are overridden from air to scaffolding in the dependency graph. Also, bias towards placing scaffolding blocks in these locations (since the scaffolder tells us that'll be helpful to future sections)
|
||||
* @param alreadyPlaced Locations that are currently not-air. Can include entries in intendedScaffolding (indicating placed scaffolding), entries that are non-air in the graph (indicating completed parts of the build), and even entries that are air in the graph and not in intendedScaffolding (indicating incidental scaffolding from previous sections that the scaffolder did not anticipate needing to place)
|
||||
* @param toPlaceNow Locations that are currently top of mind and must be placed. For example, to place the rest of the graph, this would be intendedScaffolding|(graph.allNonAir&~alreadyPlaced)
|
||||
* @param player Last but not least, where is the player standing?
|
||||
*/
|
||||
public SolverEngineInput(PlaceOrderDependencyGraph graph, LongSets.UnmodifiableSet intendedScaffolding, LongOpenHashSet alreadyPlaced, List<LongOpenHashSet> toPlaceNow, long player) {
|
||||
this.graph = graph;
|
||||
this.intendedScaffolding = intendedScaffolding;
|
||||
this.alreadyPlaced = LongSets.unmodifiable(alreadyPlaced);
|
||||
this.toPlaceNow = toPlaceNow;
|
||||
this.player = player;
|
||||
this.allToPlaceNow = combine(toPlaceNow);
|
||||
this.bounds = graph.bounds();
|
||||
if (Main.DEBUG) {
|
||||
sanityCheck();
|
||||
}
|
||||
}
|
||||
|
||||
private void sanityCheck() {
|
||||
if (!bounds.inRangePos(player)) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
for (LongSet toVerify : new LongSet[]{intendedScaffolding, alreadyPlaced}) {
|
||||
for (long pos : toVerify) {
|
||||
if (!bounds.inRangePos(pos)) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
}
|
||||
for (LongSet toPlace : toPlaceNow) {
|
||||
for (long pos : toPlace) {
|
||||
if (alreadyPlaced.contains(pos)) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
if (!bounds.inRangePos(pos)) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
if (intendedScaffolding.contains(pos) ^ graph.airTreatedAsScaffolding(pos)) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public PlacementDesire desiredToBePlaced(long pos) {
|
||||
if (Main.DEBUG && !graph.bounds().inRangePos(pos)) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
if (allToPlaceNow.contains(pos)) {
|
||||
if (Main.DEBUG && (intendedScaffolding.contains(pos) != graph.airTreatedAsScaffolding(pos))) {
|
||||
throw new IllegalStateException("adding this sanity check 2+ years later, hope it's correct");
|
||||
}
|
||||
if (graph.airTreatedAsScaffolding(pos)) {
|
||||
return PlacementDesire.SCAFFOLDING_OF_CURRENT_GOAL;
|
||||
} else {
|
||||
return PlacementDesire.PART_OF_CURRENT_GOAL;
|
||||
}
|
||||
} else {
|
||||
// for positions NOT in allToPlaceNow, intendedScaffolding is not guaranteed to be equivalent to airTreatedAsScaffolding
|
||||
if (graph.airTreatedAsScaffolding(pos)) {
|
||||
if (intendedScaffolding.contains(pos)) {
|
||||
return PlacementDesire.SCAFFOLDING_OF_FUTURE_GOAL;
|
||||
} else {
|
||||
return PlacementDesire.ANCILLARY;
|
||||
}
|
||||
} else {
|
||||
return PlacementDesire.PART_OF_FUTURE_GOAL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum PlacementDesire {
|
||||
PART_OF_CURRENT_GOAL,
|
||||
PART_OF_FUTURE_GOAL,
|
||||
SCAFFOLDING_OF_CURRENT_GOAL,
|
||||
SCAFFOLDING_OF_FUTURE_GOAL,
|
||||
ANCILLARY
|
||||
}
|
||||
|
||||
private static LongOpenHashSet combine(List<LongOpenHashSet> entries) {
|
||||
LongOpenHashSet ret = new LongOpenHashSet(entries.stream().mapToInt(Collection::size).sum());
|
||||
for (LongOpenHashSet set : entries) {
|
||||
LongIterator it = set.iterator();
|
||||
while (it.hasNext()) {
|
||||
ret.add(it.nextLong());
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
public BlockStateCachedData at(long pos, WorldState inWorldState) {
|
||||
if (bounds.inRangePos(pos)) {
|
||||
if (inWorldState.blockExists(pos)) {
|
||||
return graph.data(pos);
|
||||
} else {
|
||||
return FakeStates.AIR;
|
||||
}
|
||||
} else {
|
||||
return FakeStates.OUT_OF_BOUNDS;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
/*
|
||||
* This file is part of Baritone.
|
||||
*
|
||||
* Baritone is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Baritone is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Baritone. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package baritone.builder;
|
||||
|
||||
import baritone.api.utils.BetterBlockPos;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class SolverEngineOutput {
|
||||
|
||||
private final List<SolvedActionStep> steps;
|
||||
|
||||
public SolverEngineOutput(List<SolvedActionStep> steps) {
|
||||
this.steps = steps;
|
||||
}
|
||||
|
||||
public void sanityCheck(SolverEngineInput in) {
|
||||
for (SolvedActionStep step : steps) {
|
||||
step.placeAt().ifPresent(pos -> {
|
||||
if (!in.graph.bounds().inRangePos(pos)) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
});
|
||||
if (!in.graph.bounds().inRangePos(step.playerMovesTo())) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
long prev = in.player;
|
||||
for (SolvedActionStep step : steps) {
|
||||
long curr = step.playerMovesTo();
|
||||
sanityCheckMovement(prev, curr);
|
||||
prev = curr;
|
||||
}
|
||||
if (steps.get(steps.size() - 1).sneaking()) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
|
||||
public List<SolvedActionStep> getSteps() {
|
||||
return Collections.unmodifiableList(steps);
|
||||
}
|
||||
|
||||
private static void sanityCheckMovement(long from, long to) {
|
||||
int dx = BetterBlockPos.XfromLong(from) - BetterBlockPos.XfromLong(to);
|
||||
int dz = BetterBlockPos.ZfromLong(from) - BetterBlockPos.ZfromLong(to);
|
||||
if (Math.abs(dx) + Math.abs(dz) > 1) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,233 +0,0 @@
|
||||
/*
|
||||
* This file is part of Baritone.
|
||||
*
|
||||
* Baritone is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Baritone is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Baritone. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package baritone.builder;
|
||||
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.longs.*;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Collections;
|
||||
|
||||
/**
|
||||
* Tarjans algorithm destructured into a coroutine-like layout with an explicit "call stack" on the heap
|
||||
* <p>
|
||||
* This is needed so as to not stack overflow on huge schematics sadly. My test schematic (128x128x128) creates a
|
||||
* call stack of depth 2 million, which the JVM cannot handle on its own on the stack, but it has no trouble with an
|
||||
* ArrayDeque of 2 million entries on the heap.
|
||||
*/
|
||||
public class TarjansAlgorithm {
|
||||
|
||||
private final DependencyGraphScaffoldingOverlay graph;
|
||||
private final Long2ObjectOpenHashMap<TarjanVertexInfo> infoMap;
|
||||
private ArrayDeque<TarjanVertexInfo> vposStack;
|
||||
private ArrayDeque<TarjanVertexInfo> tarjanCallStack;
|
||||
private int index;
|
||||
private TarjansResult result;
|
||||
|
||||
private TarjansAlgorithm(DependencyGraphScaffoldingOverlay overlayedGraph) {
|
||||
this.graph = overlayedGraph;
|
||||
this.infoMap = new Long2ObjectOpenHashMap<>();
|
||||
this.result = new TarjansResult();
|
||||
this.vposStack = new ArrayDeque<>();
|
||||
this.tarjanCallStack = new ArrayDeque<>();
|
||||
}
|
||||
|
||||
public static TarjansResult run(DependencyGraphScaffoldingOverlay overlayedGraph) {
|
||||
TarjansAlgorithm algo = new TarjansAlgorithm(overlayedGraph);
|
||||
algo.run();
|
||||
if (Main.VERY_SLOW_DEBUG) {
|
||||
sanityCheckResult(overlayedGraph, algo.result);
|
||||
}
|
||||
return algo.result;
|
||||
}
|
||||
|
||||
private void run() {
|
||||
if (Main.DEBUG) {
|
||||
//System.out.println("Tarjan start");
|
||||
}
|
||||
long a = System.currentTimeMillis();
|
||||
graph.forEachReal(pos -> {
|
||||
strongConnect(pos);
|
||||
while (!tarjanCallStack.isEmpty()) {
|
||||
strongConnectPart2(tarjanCallStack.pop());
|
||||
}
|
||||
});
|
||||
if (Main.DEBUG) {
|
||||
//System.out.println("Tarjan end " + (System.currentTimeMillis() - a) + "ms");
|
||||
}
|
||||
}
|
||||
|
||||
private void strongConnect(long vpos) {
|
||||
if (Main.DEBUG && !graph.real(vpos)) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
TarjanVertexInfo info = infoMap.get(vpos);
|
||||
if (info == null) {
|
||||
info = createInfo(vpos);
|
||||
} else {
|
||||
if (info.doneWithMainLoop) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
strongConnectPart2(info);
|
||||
}
|
||||
|
||||
private TarjanVertexInfo createInfo(long vpos) {
|
||||
TarjanVertexInfo info = new TarjanVertexInfo();
|
||||
info.pos = vpos;
|
||||
info.index = index++;
|
||||
info.lowlink = info.index;
|
||||
vposStack.push(info);
|
||||
info.onStack = true;
|
||||
infoMap.put(vpos, info);
|
||||
return info;
|
||||
}
|
||||
|
||||
private void strongConnectPart2(TarjanVertexInfo info) {
|
||||
if (info.doneWithMainLoop) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
long vpos = info.pos;
|
||||
for (int fi = info.facesCompleted; fi < Face.NUM_FACES; fi++) {
|
||||
Face face = Face.VALUES[fi];
|
||||
if (graph.outgoingEdge(vpos, face)) {
|
||||
long wpos = face.offset(vpos);
|
||||
TarjanVertexInfo winfo = infoMap.get(wpos);
|
||||
|
||||
if (winfo == null) {
|
||||
if (info.recursingInto != -1) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
info.recursingInto = wpos;
|
||||
winfo = createInfo(wpos);
|
||||
tarjanCallStack.push(info);
|
||||
tarjanCallStack.push(winfo);
|
||||
return;
|
||||
}
|
||||
if (info.recursingInto == wpos) {
|
||||
info.lowlink = Math.min(info.lowlink, winfo.lowlink);
|
||||
info.recursingInto = -1;
|
||||
} else if (winfo.onStack) {
|
||||
info.lowlink = Math.min(info.lowlink, winfo.index);
|
||||
}
|
||||
}
|
||||
info.facesCompleted = fi + 1;
|
||||
}
|
||||
if (info.recursingInto != -1) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
info.doneWithMainLoop = true;
|
||||
if (info.lowlink == info.index) {
|
||||
result.startComponent();
|
||||
TarjanVertexInfo winfo;
|
||||
do {
|
||||
winfo = vposStack.pop();
|
||||
winfo.onStack = false;
|
||||
result.addNode(winfo.pos);
|
||||
} while (winfo.pos != vpos);
|
||||
}
|
||||
}
|
||||
|
||||
public static class TarjansResult {
|
||||
|
||||
private final Long2IntOpenHashMap posToComponent = new Long2IntOpenHashMap();
|
||||
private int componentID = -1;
|
||||
|
||||
private TarjansResult() {
|
||||
posToComponent.defaultReturnValue(-1);
|
||||
}
|
||||
|
||||
private void startComponent() {
|
||||
componentID++;
|
||||
}
|
||||
|
||||
private void addNode(long nodepos) {
|
||||
if (posToComponent.put(nodepos, componentID) != -1) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
|
||||
public int getComponent(long nodepos) {
|
||||
int result = posToComponent.get(nodepos);
|
||||
if (result == -1) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public int numComponents() {
|
||||
return componentID + 1;
|
||||
}
|
||||
}
|
||||
|
||||
private static class TarjanVertexInfo {
|
||||
|
||||
private long pos;
|
||||
private int index;
|
||||
private int lowlink;
|
||||
private boolean onStack;
|
||||
private boolean doneWithMainLoop;
|
||||
private long recursingInto = -1;
|
||||
private int facesCompleted;
|
||||
}
|
||||
|
||||
public static void sanityCheckResult(DependencyGraphScaffoldingOverlay graph, TarjansResult result) {
|
||||
// this is a much slower (O(n^2) at least instead of O(n)) implementation of finding strongly connected components
|
||||
Int2ObjectOpenHashMap<LongSet> checkedCids = new Int2ObjectOpenHashMap<>();
|
||||
LongSet claimedAlready = new LongOpenHashSet();
|
||||
graph.forEachReal(pos -> {
|
||||
int cid = result.getComponent(pos);
|
||||
LongSet componentShouldBe = checkedCids.get(cid);
|
||||
if (componentShouldBe == null) {
|
||||
componentShouldBe = sanityCheckResultFrom(graph, pos);
|
||||
checkedCids.put(cid, componentShouldBe);
|
||||
LongIterator it = componentShouldBe.iterator();
|
||||
while (it.hasNext()) {
|
||||
if (!claimedAlready.add(it.nextLong())) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!componentShouldBe.contains(pos)) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static LongSet sanityCheckResultFrom(DependencyGraphScaffoldingOverlay graph, long start) {
|
||||
if (graph.air(start)) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
LongList startList = new LongArrayList(Collections.singletonList(start));
|
||||
LongSet reachableForward = DependencyGraphAnalyzer.searchGraph(startList, graph::outgoingEdge);
|
||||
LongSet reachableBackward = DependencyGraphAnalyzer.searchGraph(startList, graph::incomingEdge);
|
||||
// correct iff the intersection of reachableForward and reachableBackward is exactly the component containing start
|
||||
LongSet ret = new LongOpenHashSet();
|
||||
boolean selection = reachableForward.size() < reachableBackward.size();
|
||||
LongSet toIterate = selection ? reachableForward : reachableBackward;
|
||||
LongSet toCheck = selection ? reachableBackward : reachableForward;
|
||||
LongIterator it = toIterate.iterator();
|
||||
while (it.hasNext()) {
|
||||
long pos = it.nextLong();
|
||||
if (toCheck.contains(pos)) {
|
||||
ret.add(pos);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
@@ -1,225 +0,0 @@
|
||||
/*
|
||||
* This file is part of Baritone.
|
||||
*
|
||||
* Baritone is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Baritone is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Baritone. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package baritone.builder;
|
||||
|
||||
import baritone.api.utils.BetterBlockPos;
|
||||
import baritone.utils.BlockStateInterface;
|
||||
import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.block.state.IBlockState;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
/**
|
||||
* Just some testing stuff
|
||||
*/
|
||||
public class Testing {
|
||||
|
||||
private static final int MAX_LEAF_LEVEL = 16;
|
||||
|
||||
public static class AbstractNodeCostSearch {
|
||||
|
||||
long[] KEYS_CACHE = new long[MAX_LEAF_LEVEL];
|
||||
int[] VALS_CACHE = new int[MAX_LEAF_LEVEL];
|
||||
}
|
||||
|
||||
|
||||
public static abstract class BlockStateInterfaceAbstractWrapper {
|
||||
|
||||
protected long zobrist;
|
||||
|
||||
public abstract IBlockState get(long coord);
|
||||
|
||||
public IBlockState get1(int x, int y, int z) {
|
||||
return get(BetterBlockPos.toLong(x, y, z));
|
||||
}
|
||||
|
||||
public BlockStateInterfaceAbstractWrapper with(long packedCoordinate, int state, AbstractNodeCostSearch ref) {
|
||||
return new BlockStateInterfaceLeafDiff(this, packedCoordinate, Block.BLOCK_STATE_IDS.getByValue(state));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class BlockStateInterfaceLeafDiff extends BlockStateInterfaceAbstractWrapper {
|
||||
|
||||
private BlockStateInterfaceAbstractWrapper delegate;
|
||||
private final long coord;
|
||||
private final IBlockState state;
|
||||
|
||||
public BlockStateInterfaceLeafDiff(BlockStateInterfaceAbstractWrapper delegate, long coord, IBlockState state) {
|
||||
this.delegate = delegate;
|
||||
this.state = state;
|
||||
this.coord = coord;
|
||||
zobrist = updateZobristHash(delegate.zobrist, coord, state, delegate.get(coord));
|
||||
}
|
||||
|
||||
public static long updateZobristHash(long oldZobristHash, long coord, IBlockState emplaced, IBlockState old) {
|
||||
if (old != null) {
|
||||
return updateZobristHash(updateZobristHash(oldZobristHash, coord, emplaced, null), coord, old, null);
|
||||
}
|
||||
return oldZobristHash ^ BetterBlockPos.murmur64(BetterBlockPos.longHash(coord) ^ Block.BLOCK_STATE_IDS.get(emplaced));
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBlockState get(long coord) {
|
||||
if (this.coord == coord) {
|
||||
return state;
|
||||
}
|
||||
return delegate.get(coord);
|
||||
}
|
||||
|
||||
public int leafLevels() { // cant cache leaflevel because of how materialize changes it
|
||||
return 1 + (delegate instanceof BlockStateInterfaceLeafDiff ? ((BlockStateInterfaceLeafDiff) delegate).leafLevels() : 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockStateInterfaceAbstractWrapper with(long packedCoordinate, int state, AbstractNodeCostSearch ref) {
|
||||
int level = leafLevels();
|
||||
if (level < MAX_LEAF_LEVEL) {
|
||||
return super.with(packedCoordinate, state, ref);
|
||||
}
|
||||
if (level > MAX_LEAF_LEVEL) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
BlockStateInterfaceLeafDiff parent = this;
|
||||
for (int i = 0; i < MAX_LEAF_LEVEL / 2; i++) {
|
||||
parent = (BlockStateInterfaceLeafDiff) parent.delegate;
|
||||
}
|
||||
parent.delegate = ((BlockStateInterfaceLeafDiff) parent.delegate).materialize(ref);
|
||||
return super.with(packedCoordinate, state, ref);
|
||||
}
|
||||
|
||||
public BlockStateInterfaceMappedDiff materialize(AbstractNodeCostSearch ref) {
|
||||
BlockStateInterfaceLeafDiff parent = this;
|
||||
int startIdx = MAX_LEAF_LEVEL;
|
||||
BlockStateInterfaceMappedDiff ancestor;
|
||||
long[] keys = ref.KEYS_CACHE;
|
||||
int[] vals = ref.VALS_CACHE;
|
||||
while (true) {
|
||||
startIdx--;
|
||||
keys[startIdx] = parent.coord;
|
||||
vals[startIdx] = Block.BLOCK_STATE_IDS.get(parent.state);
|
||||
if (parent.delegate instanceof BlockStateInterfaceLeafDiff) {
|
||||
parent = (BlockStateInterfaceLeafDiff) parent.delegate;
|
||||
} else {
|
||||
if (parent.delegate instanceof BlockStateInterfaceMappedDiff) {
|
||||
ancestor = (BlockStateInterfaceMappedDiff) parent.delegate;
|
||||
} else {
|
||||
ancestor = new BlockStateInterfaceMappedDiff((BlockStateInterfaceWrappedSubstrate) parent.delegate);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
BlockStateInterfaceMappedDiff coalesced = new BlockStateInterfaceMappedDiff(ancestor, keys, vals, startIdx, zobrist);
|
||||
//ref.zobristMap.put(zobrist, coalesced);
|
||||
return coalesced;
|
||||
}
|
||||
}
|
||||
|
||||
public static class BlockStateInterfaceMappedDiff extends BlockStateInterfaceAbstractWrapper {
|
||||
|
||||
private final BlockStateInterfaceWrappedSubstrate delegate;
|
||||
private final Long2ObjectOpenHashMap<Long2IntOpenHashMap> sections;
|
||||
private static final long MASK = BetterBlockPos.toLong(15, 15, 15);
|
||||
|
||||
public BlockStateInterfaceMappedDiff(BlockStateInterfaceWrappedSubstrate substrate) {
|
||||
this.delegate = substrate;
|
||||
this.sections = new Long2ObjectOpenHashMap<>();
|
||||
}
|
||||
|
||||
public BlockStateInterfaceMappedDiff(BlockStateInterfaceMappedDiff delegate, long[] diffKeys, int[] diffVals, int startIdx, long zobrist) {
|
||||
this.sections = new Long2ObjectOpenHashMap<>();
|
||||
this.zobrist = zobrist;
|
||||
this.delegate = delegate.delegate;
|
||||
for (int i = startIdx; i < diffKeys.length; i++) {
|
||||
long bucket = diffKeys[i] & ~MASK;
|
||||
Long2IntOpenHashMap val = sections.get(bucket);
|
||||
if (val == null) {
|
||||
Long2IntOpenHashMap parent = delegate.sections.get(bucket);
|
||||
if (parent == null) {
|
||||
val = new Long2IntOpenHashMap();
|
||||
val.defaultReturnValue(-1);
|
||||
} else {
|
||||
val = parent.clone();
|
||||
}
|
||||
sections.put(bucket, val);
|
||||
}
|
||||
val.put(diffKeys[i] & MASK, diffVals[i]);
|
||||
}
|
||||
Iterator<Long2ObjectMap.Entry<Long2IntOpenHashMap>> it = delegate.sections.long2ObjectEntrySet().fastIterator();
|
||||
while (it.hasNext()) {
|
||||
Long2ObjectMap.Entry<Long2IntOpenHashMap> entry = it.next();
|
||||
if (sections.containsKey(entry.getLongKey())) {
|
||||
continue;
|
||||
}
|
||||
sections.put(entry.getLongKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBlockState get(long coord) {
|
||||
Long2IntOpenHashMap candidateSection = sections.get(coord & ~MASK);
|
||||
if (candidateSection != null) {
|
||||
int val = candidateSection.get(coord & MASK);
|
||||
if (val != -1) {
|
||||
return Block.BLOCK_STATE_IDS.getByValue(val);
|
||||
}
|
||||
}
|
||||
return delegate.get(coord);
|
||||
}
|
||||
}
|
||||
|
||||
public static class BlockStateInterfaceWrappedSubstrate extends BlockStateInterfaceAbstractWrapper {
|
||||
|
||||
private BlockStateInterface delegate;
|
||||
|
||||
@Override
|
||||
public IBlockState get(long coord) {
|
||||
return delegate.get0(BetterBlockPos.XfromLong(coord), BetterBlockPos.YfromLong(coord), BetterBlockPos.ZfromLong(coord));
|
||||
}
|
||||
|
||||
/*@Override
|
||||
public IBlockState getOverridden(long coord) {
|
||||
return null;
|
||||
}*/
|
||||
}
|
||||
|
||||
public static class BlockStateLookupHelper {
|
||||
|
||||
// this falls back from System.identityHashCode to == so it's safe even in the case of a 32-bit collision
|
||||
// but that would never have happened anyway: https://www.wolframalpha.com/input/?i=%28%282%5E32-1%29%2F%282%5E32%29%29%5E2000
|
||||
private static Reference2IntOpenHashMap<IBlockState> states = new Reference2IntOpenHashMap<>();
|
||||
|
||||
static {
|
||||
states.defaultReturnValue(-1); // normal default is 0
|
||||
}
|
||||
|
||||
public static int lookupBlockState(IBlockState state) {
|
||||
int stateMaybe = states.getInt(state);
|
||||
if (stateMaybe >= 0) {
|
||||
return stateMaybe;
|
||||
}
|
||||
int realState = Block.BLOCK_STATE_IDS.get(state); // uses slow REAL hashcode that walks through the Map of properties, gross
|
||||
states.put(state, realState);
|
||||
return realState;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
/*
|
||||
* This file is part of Baritone.
|
||||
*
|
||||
* Baritone is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Baritone is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Baritone. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package baritone.builder;
|
||||
|
||||
import baritone.api.utils.BetterBlockPos;
|
||||
|
||||
public class Vec3d {
|
||||
|
||||
public final double x;
|
||||
public final double y;
|
||||
public final double z;
|
||||
|
||||
public Vec3d(double x, double y, double z) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.z = z;
|
||||
}
|
||||
|
||||
public Vec3d(double[] vec) {
|
||||
this(vec[0], vec[1], vec[2]);
|
||||
if (vec.length != 3) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean inOriginUnitVoxel() {
|
||||
return x >= 0 && x <= 1 && y >= 0 && y <= 1 && z >= 0 && z <= 1;
|
||||
}
|
||||
|
||||
public Vec3d plus(double x, double y, double z) {
|
||||
return new Vec3d(this.x + x, this.y + y, this.z + z);
|
||||
}
|
||||
|
||||
public long getRoundedToZeroPositionUnsafeDontUse() {
|
||||
return BetterBlockPos.toLong((int) x, (int) y, (int) z);
|
||||
}
|
||||
|
||||
public double distSq(Vec3d other) {
|
||||
double dx = x - other.x;
|
||||
double dy = y - other.y;
|
||||
double dz = z - other.z;
|
||||
return dx * dx + dy * dy + dz * dz;
|
||||
}
|
||||
|
||||
public Face flatDirectionTo(Vec3d dst) {
|
||||
return new Vec3d(dst.x - x, dst.y - y, dst.z - z).flatDirection();
|
||||
}
|
||||
|
||||
private static final double AMBIGUITY_TOLERANCE = 0.01;
|
||||
|
||||
public Face flatDirection() {
|
||||
if (Math.abs(Math.abs(x) - Math.abs(z)) < AMBIGUITY_TOLERANCE) {
|
||||
throw new IllegalStateException("ambiguous");
|
||||
}
|
||||
if (Math.abs(x) > Math.abs(z)) {
|
||||
if (x > 0) {
|
||||
return Face.EAST;
|
||||
} else {
|
||||
return Face.WEST;
|
||||
}
|
||||
} else {
|
||||
if (z > 0) {
|
||||
return Face.SOUTH;
|
||||
} else {
|
||||
return Face.NORTH;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Vec3d{" + x + "," + y + "," + z + "}";
|
||||
}
|
||||
}
|
||||
@@ -1,123 +0,0 @@
|
||||
/*
|
||||
* This file is part of Baritone.
|
||||
*
|
||||
* Baritone is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Baritone is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Baritone. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package baritone.builder;
|
||||
|
||||
import baritone.api.utils.BetterBlockPos;
|
||||
import it.unimi.dsi.fastutil.HashCommon;
|
||||
import it.unimi.dsi.fastutil.longs.LongCollection;
|
||||
import it.unimi.dsi.fastutil.longs.LongIterator;
|
||||
|
||||
import java.util.BitSet;
|
||||
|
||||
public abstract class WorldState {
|
||||
|
||||
/**
|
||||
* https://en.wikipedia.org/wiki/Zobrist_hashing
|
||||
*/
|
||||
public final long zobristHash;
|
||||
|
||||
protected WorldState(long zobristHash) {
|
||||
this.zobristHash = zobristHash;
|
||||
}
|
||||
|
||||
public abstract boolean blockExists(long pos);
|
||||
|
||||
public WorldState withChild(long pos) {
|
||||
return new WorldStateLeafDiff(this, pos);
|
||||
}
|
||||
|
||||
public static long updateZobrist(long worldStateZobristHash, long changedPosition) {
|
||||
return zobrist(changedPosition) ^ worldStateZobristHash;
|
||||
}
|
||||
|
||||
public static long predetermineGoalZobrist(LongCollection goal) {
|
||||
LongIterator it = goal.iterator();
|
||||
long ret = 0;
|
||||
while (it.hasNext()) {
|
||||
ret ^= zobrist(it.nextLong());
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static long zobrist(long packed) {
|
||||
return HashCommon.mix(BetterBlockPos.ZOBRIST_MURMUR_MASK ^ packed);
|
||||
}
|
||||
|
||||
public static long unzobrist(long zobrist) {
|
||||
return BetterBlockPos.ZOBRIST_MURMUR_MASK ^ HashCommon.invMix(zobrist);
|
||||
}
|
||||
|
||||
public static class WorldStateWrappedSubstrate extends WorldState {
|
||||
|
||||
private final Bounds bounds;
|
||||
private final BitSet placed; // won't be copied, since alreadyPlaced is going to be **far** larger than toPlaceNow
|
||||
private final int offset;
|
||||
|
||||
public WorldStateWrappedSubstrate(SolverEngineInput inp) {
|
||||
super(0L);
|
||||
this.bounds = inp.graph.bounds();
|
||||
int min;
|
||||
int max;
|
||||
{
|
||||
LongIterator positions = inp.alreadyPlaced.iterator();
|
||||
if (!positions.hasNext()) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
min = bounds.toIndex(positions.nextLong());
|
||||
max = min;
|
||||
while (positions.hasNext()) {
|
||||
int val = bounds.toIndex(positions.nextLong());
|
||||
min = Math.min(min, val);
|
||||
max = Math.max(max, val);
|
||||
}
|
||||
}
|
||||
this.offset = min;
|
||||
this.placed = new BitSet(max - min + 1);
|
||||
LongIterator it = inp.alreadyPlaced.iterator();
|
||||
while (it.hasNext()) {
|
||||
placed.set(bounds.toIndex(it.nextLong()) - offset);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean blockExists(long pos) {
|
||||
int storedAt = bounds.toIndex(pos) - offset;
|
||||
if (storedAt < 0) {
|
||||
return false;
|
||||
}
|
||||
return placed.get(storedAt);
|
||||
}
|
||||
}
|
||||
|
||||
public static class WorldStateLeafDiff extends WorldState {
|
||||
|
||||
private WorldState delegate;
|
||||
private final long pos;
|
||||
|
||||
private WorldStateLeafDiff(WorldState delegate, long pos) {
|
||||
super(updateZobrist(delegate.zobristHash, pos));
|
||||
this.delegate = delegate;
|
||||
this.pos = pos;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean blockExists(long pos) {
|
||||
return this.pos == pos || delegate.blockExists(pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
/*
|
||||
* This file is part of Baritone.
|
||||
*
|
||||
* Baritone is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Baritone is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Baritone. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package baritone.builder;
|
||||
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
||||
|
||||
public class ZobristWorldStateCache {
|
||||
|
||||
private final Long2ObjectOpenHashMap<WorldState> zobristWorldStateCache;
|
||||
|
||||
public ZobristWorldStateCache(WorldState zeroEntry) {
|
||||
this.zobristWorldStateCache = new Long2ObjectOpenHashMap<>();
|
||||
this.zobristWorldStateCache.put(0L, zeroEntry);
|
||||
}
|
||||
|
||||
public WorldState coalesceState(Node node) {
|
||||
WorldState alr = zobristWorldStateCache.get(node.worldStateZobristHash);
|
||||
if (alr != null) {
|
||||
if (Main.DEBUG && alr.zobristHash != node.worldStateZobristHash) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
// don't check packedUnrealizedCoordinate here because it could exist (not -1) if a different route was taken to a zobrist-equivalent node (such as at a different player position) which was then expanded and coalesced
|
||||
return alr;
|
||||
}
|
||||
if (Main.DEBUG && node.packedUnrealizedCoordinate == -1) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
long parent = WorldState.updateZobrist(node.worldStateZobristHash, node.packedUnrealizedCoordinate); // updateZobrist is symmetric because XOR, so the same operation can do child->parent as parent->child
|
||||
WorldState myState = zobristWorldStateCache.get(parent).withChild(node.packedUnrealizedCoordinate);
|
||||
if (Main.DEBUG && myState.zobristHash != node.worldStateZobristHash) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
zobristWorldStateCache.put(node.worldStateZobristHash, myState);
|
||||
node.packedUnrealizedCoordinate = -1;
|
||||
return myState;
|
||||
}
|
||||
}
|
||||
@@ -1,307 +0,0 @@
|
||||
/*
|
||||
* This file is part of Baritone.
|
||||
*
|
||||
* Baritone is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Baritone is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Baritone. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package baritone.builder.mc;
|
||||
|
||||
import baritone.builder.*;
|
||||
import net.minecraft.block.*;
|
||||
import net.minecraft.block.state.IBlockState;
|
||||
import net.minecraft.util.EnumFacing;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* I expect this class to get extremely complicated.
|
||||
* <p>
|
||||
* Thankfully, all of it will be confined to this class, so when there's changes to new versions of Minecraft, e.g. new blocks, there will be only one place to look.
|
||||
*/
|
||||
public class BlockStatePropertiesExtractor {
|
||||
|
||||
public static BlockStateCachedDataBuilder getData(IBlockState state) {
|
||||
Block block = state.getBlock();
|
||||
BlockStateCachedDataBuilder builder = new BlockStateCachedDataBuilder();
|
||||
|
||||
// returns null only if we don't know how to walk on / walk around / place against this block
|
||||
// if we don't know how to place the block, get everything else but add .placementLogicNotImplementedYe
|
||||
|
||||
// special cases
|
||||
{
|
||||
if (block instanceof BlockAir || block instanceof BlockStructureVoid) {
|
||||
return builder.setAir();
|
||||
}
|
||||
if (block instanceof BlockStairs) {
|
||||
boolean rightsideUp = state.getValue(BlockStairs.HALF) == BlockStairs.EnumHalf.BOTTOM; // true if normal stair, false if upside down stair
|
||||
Face facing = fromMC(state.getValue(BlockStairs.FACING));
|
||||
BlockStateCachedDataBuilder stairBuilder = new BlockStateCachedDataBuilder() {
|
||||
@Override
|
||||
protected PlaceAgainstData placeAgainstFace(Face face) {
|
||||
if (face == facing) {
|
||||
// this is "the back" of the stair, which is a full face that you can place against just fine
|
||||
return new PlaceAgainstData(face, Half.EITHER, isMustSneakWhenPlacingAgainstMe());
|
||||
}
|
||||
return super.placeAgainstFace(face);
|
||||
}
|
||||
};
|
||||
if (!rightsideUp) {
|
||||
stairBuilder.fullyWalkableTop();
|
||||
}
|
||||
return stairBuilder.mustBePlacedAgainst(rightsideUp ? Half.BOTTOM : Half.TOP)
|
||||
.collidesWithPlayer(true)
|
||||
.collisionHeight(1)
|
||||
.canPlaceAgainstMe()
|
||||
.playerMustBeHorizontalFacingInOrderToPlaceMe(facing);
|
||||
}
|
||||
if (block instanceof BlockSlab) {
|
||||
if (((BlockSlab) block).isDouble()) {
|
||||
builder.placementLogicNotImplementedYet().collisionHeight(1);
|
||||
} else if (state.getValue(BlockSlab.HALF) == BlockSlab.EnumBlockHalf.BOTTOM) {
|
||||
builder.mustBePlacedAgainst(Half.BOTTOM).collisionHeight(0.5);
|
||||
} else {
|
||||
builder.mustBePlacedAgainst(Half.TOP).collisionHeight(1);
|
||||
}
|
||||
return builder
|
||||
.fullyWalkableTop()
|
||||
.canPlaceAgainstMe()
|
||||
.collidesWithPlayer(true);
|
||||
}
|
||||
if (block instanceof BlockTrapDoor) {
|
||||
boolean bottom = state.getValue(BlockTrapDoor.HALF) == BlockTrapDoor.DoorHalf.BOTTOM;
|
||||
Face facing = fromMC(state.getValue(BlockTrapDoor.FACING));
|
||||
return new BlockStateCachedDataBuilder() {
|
||||
@Override
|
||||
public List<BlockStatePlacementOption> howCanIBePlaced() {
|
||||
List<BlockStatePlacementOption> ret = new ArrayList<>();
|
||||
if (!(Main.STRICT_Y && !bottom)) {
|
||||
ret.add(BlockStatePlacementOption.get(bottom ? Face.DOWN : Face.UP, Half.EITHER, Optional.ofNullable(facing.opposite()), Optional.empty()));
|
||||
}
|
||||
ret.add(BlockStatePlacementOption.get(facing.opposite(), bottom ? Half.BOTTOM : Half.TOP, Optional.empty(), Optional.empty()));
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
.collisionHeight(1) // sometimes it can be 1, and for collision height we err on the side of max
|
||||
.collidesWithPlayer(true); // dont allow walking on top of closed top-half trapdoor because redstone activation is scary and im not gonna predict it
|
||||
}
|
||||
if (block instanceof BlockLog) {
|
||||
BlockLog.EnumAxis axis = state.getValue(BlockLog.LOG_AXIS);
|
||||
BlockStateCachedDataBuilder logBuilder = new BlockStateCachedDataBuilder() {
|
||||
@Override
|
||||
public List<BlockStatePlacementOption> howCanIBePlaced() {
|
||||
List<BlockStatePlacementOption> ret = super.howCanIBePlaced();
|
||||
ret.removeIf(place -> BlockLog.EnumAxis.fromFacingAxis(place.against.toMC().getAxis()) != axis);
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
if (axis == BlockLog.EnumAxis.NONE) {
|
||||
logBuilder.placementLogicNotImplementedYet(); // ugh
|
||||
}
|
||||
return logBuilder
|
||||
.fullyWalkableTop()
|
||||
.collisionHeight(1)
|
||||
.canPlaceAgainstMe()
|
||||
.collidesWithPlayer(true);
|
||||
}
|
||||
if (block instanceof BlockRotatedPillar) { // hay block, bone block
|
||||
// even though blocklog inherits from blockrotatedpillar it uses its own stupid enum, ugh
|
||||
// this is annoying because this is pretty much identical
|
||||
EnumFacing.Axis axis = state.getValue(BlockRotatedPillar.AXIS);
|
||||
BlockStateCachedDataBuilder rotatedPillarBuilder = new BlockStateCachedDataBuilder() {
|
||||
@Override
|
||||
public List<BlockStatePlacementOption> howCanIBePlaced() {
|
||||
List<BlockStatePlacementOption> ret = super.howCanIBePlaced();
|
||||
ret.removeIf(place -> place.against.toMC().getAxis() != axis);
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
return rotatedPillarBuilder
|
||||
.fullyWalkableTop()
|
||||
.collisionHeight(1)
|
||||
.canPlaceAgainstMe()
|
||||
.collidesWithPlayer(true);
|
||||
}
|
||||
if (block instanceof BlockStructure) {
|
||||
return null; // truly an error to encounter this
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// blocks that have to be placed with the player facing a certain way
|
||||
|
||||
// rotated clockwise about the Y axis
|
||||
if (block instanceof BlockAnvil) {
|
||||
builder.playerMustBeHorizontalFacingInOrderToPlaceMe(fromMC(state.getValue(BlockAnvil.FACING).rotateYCCW()));
|
||||
}
|
||||
|
||||
// unchanged
|
||||
if (block instanceof BlockChest
|
||||
// it is not right || block instanceof BlockSkull // TODO is this right?? skull can be any facing?
|
||||
) { // TODO fence gate and lever
|
||||
builder.playerMustBeHorizontalFacingInOrderToPlaceMe(fromMC(state.getValue(BlockHorizontal.FACING)));
|
||||
}
|
||||
|
||||
// opposite
|
||||
if (block instanceof BlockCocoa
|
||||
|| block instanceof BlockEnderChest
|
||||
|| block instanceof BlockEndPortalFrame
|
||||
|| block instanceof BlockFurnace
|
||||
|| block instanceof BlockGlazedTerracotta
|
||||
|| block instanceof BlockPumpkin
|
||||
|| block instanceof BlockRedstoneDiode // both repeater and comparator
|
||||
) {
|
||||
builder.playerMustBeHorizontalFacingInOrderToPlaceMe(fromMC(state.getValue(BlockHorizontal.FACING)).opposite());
|
||||
}
|
||||
}
|
||||
// ladder
|
||||
|
||||
{
|
||||
if (block instanceof BlockContainer || block instanceof BlockWorkbench) {
|
||||
// TODO way more blocks have a right click action, e.g. redstone repeater, daylight sensor
|
||||
builder.mustSneakWhenPlacingAgainstMe();
|
||||
}
|
||||
}
|
||||
|
||||
if (block instanceof BlockCommandBlock
|
||||
|| block instanceof BlockDispenser // dropper extends from dispenser
|
||||
|| block instanceof BlockPistonBase) {
|
||||
builder.playerMustBeEntityFacingInOrderToPlaceMe(fromMC(state.getValue(BlockDirectional.FACING)));
|
||||
}
|
||||
|
||||
if (block instanceof BlockObserver) {
|
||||
builder.playerMustBeEntityFacingInOrderToPlaceMe(fromMC(state.getValue(BlockDirectional.FACING)).opposite());
|
||||
}
|
||||
|
||||
// fully passable blocks
|
||||
{
|
||||
// some ways of determining this list that don't work:
|
||||
// calling isPassable: even though blocks such as carpet and redstone repeaters are marked as passable, they are not really, they do have a height
|
||||
// checking material: Material.PLANTS is not enough because it includes cocoa and chorus, Material.CIRCUITS is not enough because it includes redstone repeaters
|
||||
|
||||
// these are the blocks that you can fully walk through with no collision at all, such as torches and sugar cane
|
||||
if (block instanceof BlockBush // includes crops
|
||||
|| block instanceof BlockReed
|
||||
|| block instanceof BlockTorch
|
||||
//|| block instanceof BlockSign
|
||||
|| block instanceof BlockRedstoneWire
|
||||
|| block instanceof BlockRailBase
|
||||
|| (block instanceof BlockFenceGate && state.getValue(BlockFenceGate.OPEN))
|
||||
|| block instanceof BlockLever
|
||||
|| block instanceof BlockButton
|
||||
|| block instanceof BlockBanner
|
||||
|
||||
// TODO include pressure plate, tripwire, tripwire hook?
|
||||
) {
|
||||
builder.collidesWithPlayer(false);
|
||||
} else {
|
||||
builder.collidesWithPlayer(true);
|
||||
}
|
||||
}
|
||||
|
||||
if (block instanceof BlockFalling) {
|
||||
builder.mustBePlacedBottomToTop();
|
||||
}
|
||||
|
||||
|
||||
// TODO multiblocks like door and bed and double plant
|
||||
|
||||
|
||||
boolean fullyUnderstood = false; // set this flag to true for any state for which we have fully and completely described it
|
||||
|
||||
// getStateForPlacement.against is the against face. placing a torch will have it as UP. placing a bottom slab will have it as UP. placing a top slab will have it as DOWN.
|
||||
|
||||
if (block instanceof BlockFence || (block instanceof BlockFenceGate && !state.getValue(BlockFenceGate.OPEN)) || block instanceof BlockWall) {
|
||||
builder.collisionHeight(1.5);
|
||||
fullyUnderstood = true;
|
||||
}
|
||||
|
||||
if (block instanceof BlockTorch) { // includes redstone torch
|
||||
builder.canOnlyPlaceAgainst(fromMC(state.getValue(BlockTorch.FACING)).opposite());
|
||||
fullyUnderstood = true;
|
||||
}
|
||||
|
||||
if (block instanceof BlockShulkerBox) {
|
||||
builder.canOnlyPlaceAgainst(fromMC(state.getValue(BlockShulkerBox.FACING)).opposite());
|
||||
builder.collisionHeight(1); // TODO should this be 1.5 because sometimes the shulker is open?
|
||||
fullyUnderstood = true;
|
||||
if (state.getValue(BlockShulkerBox.FACING) == EnumFacing.DOWN && Main.STRICT_Y) {
|
||||
// TODO fix downward facing shulker box in STRICT_Y mode
|
||||
fullyUnderstood = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (block instanceof BlockFenceGate // because if we place it we need to think about hypothetically walking through it
|
||||
|| block instanceof BlockLever
|
||||
|| block instanceof BlockButton
|
||||
|| block instanceof BlockBanner) {
|
||||
builder.placementLogicNotImplementedYet();
|
||||
fullyUnderstood = true;
|
||||
}
|
||||
|
||||
if (block instanceof BlockSapling
|
||||
|| block instanceof BlockRedstoneWire
|
||||
|| block instanceof BlockRailBase
|
||||
|| block instanceof BlockFlower
|
||||
|| block instanceof BlockDeadBush
|
||||
|| block instanceof BlockMushroom
|
||||
) {
|
||||
builder.mustBePlacedBottomToTop();
|
||||
fullyUnderstood = true;
|
||||
}
|
||||
|
||||
//isBlockNormalCube=true implies isFullCube=true
|
||||
if (state.isBlockNormalCube() || state.isFullBlock() || block instanceof BlockGlass || block instanceof BlockStainedGlass) {
|
||||
builder.canPlaceAgainstMe();
|
||||
fullyUnderstood = true;
|
||||
}
|
||||
|
||||
if (state.isBlockNormalCube() || block instanceof BlockGlass || block instanceof BlockStainedGlass) {
|
||||
builder.collisionHeight(1);
|
||||
if (!(block instanceof BlockMagma || block instanceof BlockSlime)) {
|
||||
builder.fullyWalkableTop();
|
||||
}
|
||||
fullyUnderstood = true;
|
||||
}
|
||||
|
||||
if (block instanceof BlockSnow) {
|
||||
fullyUnderstood = true;
|
||||
builder.fullyWalkableTop().collisionHeight(0.125 * (state.getValue(BlockSnow.LAYERS) - 1));
|
||||
// funny - if you have snow layers packed 8 high, it only supports the player to a height of 0.875, but it still counts as "isTopSolid" for placing stuff like torches on it
|
||||
}
|
||||
|
||||
if (block instanceof BlockSoulSand) {
|
||||
builder.collisionHeight(0.875).fakeLessThanFullHeight();
|
||||
fullyUnderstood = true;
|
||||
}
|
||||
|
||||
if (block instanceof BlockGrassPath || block instanceof BlockFarmland) {
|
||||
builder.collisionHeight(0.9375);
|
||||
fullyUnderstood = true;
|
||||
}
|
||||
|
||||
|
||||
// TODO fully walkable top and height
|
||||
if (fullyUnderstood) {
|
||||
return builder;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static Face fromMC(EnumFacing facing) {
|
||||
return Face.VALUES[facing.getIndex()];
|
||||
}
|
||||
}
|
||||
@@ -1,112 +0,0 @@
|
||||
/*
|
||||
* This file is part of Baritone.
|
||||
*
|
||||
* Baritone is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Baritone is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Baritone. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package baritone.builder.mc;
|
||||
|
||||
import baritone.builder.BlockData;
|
||||
import baritone.builder.BlockStateCachedData;
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.block.state.IBlockState;
|
||||
import net.minecraft.init.Blocks;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class DebugStates {
|
||||
|
||||
public static void debug() {
|
||||
Map<Block, List<IBlockState>> bts = new HashMap<>();
|
||||
Map<String, List<IBlockState>> byString = new HashMap<>();
|
||||
BlockData data = new BlockData(new VanillaBlockStateDataProvider());
|
||||
for (IBlockState state : Block.BLOCK_STATE_IDS) {
|
||||
//System.out.println(state);
|
||||
bts.computeIfAbsent(state.getBlock(), $ -> new ArrayList<>()).add(state);
|
||||
String str = toString(data.get(Block.BLOCK_STATE_IDS.get(state)));
|
||||
byString.computeIfAbsent(str, $ -> new ArrayList<>()).add(state);
|
||||
}
|
||||
for (String key : (Iterable<String>) byString.keySet().stream().sorted()::iterator) {
|
||||
System.out.println("\n");
|
||||
System.out.println(key);
|
||||
Set<Block> skip = new HashSet<>();
|
||||
List<IBlockState> matches = byString.get(key);
|
||||
for (IBlockState state : matches) {
|
||||
if (skip.contains(state.getBlock())) {
|
||||
continue;
|
||||
}
|
||||
if (matches.containsAll(bts.get(state.getBlock()))) {
|
||||
System.out.println("All " + bts.get(state.getBlock()).size() + " variants of " + state.getBlock());
|
||||
skip.add(state.getBlock());
|
||||
} else {
|
||||
System.out.println(state);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
/*System.out.println(Blocks.OAK_STAIRS.getDefaultState());
|
||||
System.out.println(Block.BLOCK_STATE_IDS.get(Blocks.OAK_STAIRS.getDefaultState()));
|
||||
System.out.println(Block.BLOCK_STATE_IDS.getByValue(Block.BLOCK_STATE_IDS.get(Blocks.OAK_STAIRS.getDefaultState())));*/
|
||||
Set<Block> normal = new HashSet<>();
|
||||
Block.REGISTRY.iterator().forEachRemaining(normal::add);
|
||||
Set<Block> alternate = new HashSet<>();
|
||||
for (IBlockState state : Block.BLOCK_STATE_IDS) {
|
||||
alternate.add(state.getBlock());
|
||||
}
|
||||
if (!alternate.equals(normal)) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
outer:
|
||||
for (Block block : normal) {
|
||||
for (IBlockState state : block.getBlockState().getValidStates()) {
|
||||
if (Block.BLOCK_STATE_IDS.get(state) == -1) {
|
||||
System.out.println(state + " doesn't exist?!");
|
||||
continue;
|
||||
}
|
||||
if (block == Blocks.TRIPWIRE) {
|
||||
System.out.println(state + " does exist");
|
||||
}
|
||||
if (!Block.BLOCK_STATE_IDS.getByValue(Block.BLOCK_STATE_IDS.get(state)).equals(state)) {
|
||||
System.out.println(block + " is weird");
|
||||
continue outer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static String toString(BlockStateCachedData data) {
|
||||
if (data == null) {
|
||||
return "UNKNOWN";
|
||||
}
|
||||
Map<String, String> props = new LinkedHashMap<>();
|
||||
props(data, props);
|
||||
return props.toString();
|
||||
}
|
||||
|
||||
private static void props(BlockStateCachedData data, Map<String, String> props) {
|
||||
if (data.isAir) {
|
||||
props.put("air", "true");
|
||||
return;
|
||||
}
|
||||
props.put("collides", "" + data.collidesWithPlayer);
|
||||
props.put("walkabletop", "" + data.fullyWalkableTop);
|
||||
props.put("placeme", "" + data.placeMe.size());
|
||||
props.put("sneak", "" + data.mustSneakWhenPlacingAgainstMe);
|
||||
props.put("againstme", "" + Stream.of(data.placeAgainstMe).filter(Objects::nonNull).count());
|
||||
if (data.collidesWithPlayer) {
|
||||
props.put("y", "" + data.collisionHeightBlips());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
/*
|
||||
* This file is part of Baritone.
|
||||
*
|
||||
* Baritone is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Baritone is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Baritone. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package baritone.builder.mc;
|
||||
|
||||
import baritone.builder.BlockStateCachedData;
|
||||
import baritone.builder.BlockStateCachedDataBuilder;
|
||||
import baritone.builder.IBlockStateDataProvider;
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.block.state.IBlockState;
|
||||
|
||||
public class VanillaBlockStateDataProvider implements IBlockStateDataProvider {
|
||||
|
||||
@Override
|
||||
public int numStates() {
|
||||
return Block.BLOCK_STATE_IDS.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockStateCachedData getNullable(int i) {
|
||||
IBlockState state = Block.BLOCK_STATE_IDS.getByValue(i);
|
||||
if (state == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
BlockStateCachedDataBuilder builder = BlockStatePropertiesExtractor.getData(state);
|
||||
if (builder == null) {
|
||||
return null;
|
||||
} else {
|
||||
return new BlockStateCachedData(builder);
|
||||
}
|
||||
} catch (Throwable th) {
|
||||
throw new RuntimeException("Exception while extracting " + state + " ID " + i, th);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2019 btrekkie
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -1,15 +0,0 @@
|
||||
Original readme --> https://github.com/btrekkie/dynamic-connectivity/
|
||||
|
||||
I am experimenting with using dynamic connectivity to help with Baritone builder 2
|
||||
|
||||
https://en.wikipedia.org/wiki/Dynamic_connectivity
|
||||
|
||||
This code in this folder was written under the MIT license (see the LICENSE file). This is compatible with LGPL
|
||||
per https://directory.fsf.org/wiki/License:Expat https://www.gnu.org/licenses/license-list.en.html#Expat
|
||||
|
||||
The new license for this modified version (the combined work including my modifications) is still the Baritone license,
|
||||
which is LGPL.
|
||||
|
||||
The git histories of both repos https://github.com/btrekkie/dynamic-connectivity/ and its
|
||||
dependency https://github.com/btrekkie/RedBlackNode/ have been included by using git's "merge unrelated histories"
|
||||
feature, so the git blame is accurate for giving credit, I think.
|
||||
@@ -1,44 +0,0 @@
|
||||
/*
|
||||
* This file was originally written by btrekkie under the MIT license, which is compatible with the LGPL license for this usage within Baritone
|
||||
* https://github.com/btrekkie/dynamic-connectivity/
|
||||
*/
|
||||
|
||||
package baritone.builder.utils.com.github.btrekkie.connectivity;
|
||||
|
||||
/**
|
||||
* A combining function for taking the augmentations associated with a set of ConnVertices and reducing them to a single
|
||||
* result. The function is a binary operation for combining two values into one. For example, given vertices with
|
||||
* augmentations A1, A2, A3, and A4, the combined result may be obtained by computing
|
||||
* combine(combine(combine(A1, A2), A3), A4). In order for an augmentation result to be meaningful, the combining
|
||||
* function must be commutative, meaning combine(x, y) is equivalent to combine(y, x), and associative, meaning
|
||||
* combine(x, combine(y, z)) is equivalent to combine(combine(x, y), z).
|
||||
* <p>
|
||||
* If a ConnGraph represents a game map, then one example of an augmentation would be the amount of gold accessible from
|
||||
* a certain location. Each vertex would be augmented with the amount of gold in that location, and the combining
|
||||
* function would add the two amounts of gold passed in as arguments. Another example would be the strongest monster
|
||||
* that can reach a particular location. Each vertex with at least one monster would be augmented with a pointer to the
|
||||
* strongest monster at that location, and the combining function would return the stronger of the two monsters passed
|
||||
* in as arguments. A third example would be the number of locations accessible from a given vertex. Each vertex would
|
||||
* be augmented with the number 1, and the combining function would add the two numbers of vertices passed in as
|
||||
* arguments.
|
||||
* <p>
|
||||
* ConnGraph treats two augmentation values X and Y as interchangeable if they are equal, as in
|
||||
* X != null ? X.equals(Y) : Y == null. The same holds for two combined augmentation values, and for one combined
|
||||
* augmentation value and one augmentation value.
|
||||
* <p>
|
||||
* See the comments for ConnGraph.
|
||||
*/
|
||||
public interface Augmentation {
|
||||
/**
|
||||
* Returns the result of combining the specified values into one. Each argument is either the augmentation
|
||||
* information associated with a vertex, or the result of a previous call to "combine".
|
||||
* <p>
|
||||
* Note that a value of null is never passed in to indicate the absence of augmentation information. The fact that
|
||||
* ConnGraph.getVertexAugmentation, for example, may return null when there is no associated augmentation might lead
|
||||
* you to believe that a null argument indicates the absence of augmentation information, but again, it does not. A
|
||||
* null argument can only mean that a vertex is explicitly associated with null augmentation information, due to a
|
||||
* prior call to ConnGraph.setVertexAugmentation(vertex, null), or that the "combine" method previously returned
|
||||
* null.
|
||||
*/
|
||||
public Object combine(Object value1, Object value2);
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
package baritone.builder.utils.com.github.btrekkie.connectivity;
|
||||
|
||||
/**
|
||||
* An Augmentation implementation that wraps a MutatingAugmentation. It recycles (or "pools") previously constructed
|
||||
* combined augmentation objects in order to improve performance. This should be passed to the ConnGraph constructor as
|
||||
* an AugmentationReleaseListener so that it can recycle unused objects.
|
||||
*/
|
||||
class AugmentationPool implements Augmentation, AugmentationReleaseListener {
|
||||
/**
|
||||
* The maximum number of unused objects to store in a pool.
|
||||
*/
|
||||
private static final int CAPACITY = 20;
|
||||
|
||||
/**
|
||||
* The MutatingAugmentation we are wrapping.
|
||||
*/
|
||||
private final MutatingAugmentation mutatingAugmentation;
|
||||
|
||||
/**
|
||||
* A pool of unused objects we may reuse. The array has length CAPACITY, but only the first "size" elements contain
|
||||
* reusable objects. The values in the array after the first "size" are unspecified.
|
||||
*/
|
||||
private Object[] pool = new Object[CAPACITY];
|
||||
|
||||
/**
|
||||
* The number of reusable objects in the pool.
|
||||
*/
|
||||
private int size;
|
||||
|
||||
public AugmentationPool(MutatingAugmentation mutatingAugmentation) {
|
||||
this.mutatingAugmentation = mutatingAugmentation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object combine(Object value1, Object value2) {
|
||||
Object result;
|
||||
if (size == 0) {
|
||||
result = mutatingAugmentation.newAugmentation();
|
||||
} else {
|
||||
size--;
|
||||
result = pool[size];
|
||||
}
|
||||
mutatingAugmentation.combine(value1, value2, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void combinedAugmentationReleased(Object obj) {
|
||||
if (size < CAPACITY) {
|
||||
pool[size] = obj;
|
||||
size++;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void vertexAugmentationReleased(Object obj) {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
package baritone.builder.utils.com.github.btrekkie.connectivity;
|
||||
|
||||
/**
|
||||
* Responds to when ownership of an augmentation object is released. If the number of times that
|
||||
* combinedAugmentationReleased and vertexAugmentationReleased were called on an object is equal to the number of times
|
||||
* that Augmentation.combine returned the object plus the number of times the object was passed to
|
||||
* setVertexAugmentation, then ConnGraph no longer has a reference to the object. Such an object may be recycled.
|
||||
* <p>
|
||||
* Note that a graph may have multiple ownership claims to a given augmentation object, meaning the graph needs to
|
||||
* release the object multiple times before it can be recycled. This could happen if Augmentation.combine returned the
|
||||
* same object multiple times or the object was passed to setVertexAugmentation multiple times.
|
||||
* <p>
|
||||
* See ConnGraph(Augmentation, AugmentationReleaseListener).
|
||||
*/
|
||||
public interface AugmentationReleaseListener {
|
||||
/**
|
||||
* Responds to one ownership claim to the specified combined augmentation object (or previous return value of
|
||||
* Augmentation.combine) being released. "obj" is guaranteed not to be null.
|
||||
* <p>
|
||||
* This may be called from any ConnGraph method that mutates the graph, as well as from optimize().
|
||||
*/
|
||||
public void combinedAugmentationReleased(Object obj);
|
||||
|
||||
/**
|
||||
* Responds to one ownership claim to the specified vertex augmentation object (or previous argument to
|
||||
* setVertexAugmentation) being released. "obj" is guaranteed not to be null.
|
||||
* <p>
|
||||
* This may be called from the following ConnGraph methods: setVertexAugmentation, removeVertexAugmentation, and
|
||||
* clear().
|
||||
*/
|
||||
public void vertexAugmentationReleased(Object obj);
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
/*
|
||||
* This file was originally written by btrekkie under the MIT license, which is compatible with the LGPL license for this usage within Baritone
|
||||
* https://github.com/btrekkie/dynamic-connectivity/
|
||||
*/
|
||||
|
||||
package baritone.builder.utils.com.github.btrekkie.connectivity;
|
||||
|
||||
/**
|
||||
* Represents an edge in a ConnGraph, at the level of the edge (i.e. at the lowest level i for which G_i contains the
|
||||
* edge). Every graph edge has exactly one corresponding ConnEdge object, regardless of the number of levels it appears
|
||||
* in. See the comments for the implementation of ConnGraph.
|
||||
* <p>
|
||||
* ConnEdges are stored in the linked lists suggested by EulerTourVertex.graphListHead and
|
||||
* EulerTourVertex.forestListHead. Each ConnEdge is in two linked lists, so care must be taken when traversing the
|
||||
* linked lists. prev1 and next1 are the links for the list starting at vertex1.graphListHead or vertex1.forestListHead,
|
||||
* while prev2 and next2 are the links for vertex2. But the vertex1 and vertex2 fields of a given edge are different
|
||||
* from the vertex1 and vertex2 fields of the linked edges. For example, the edge after next1 is not necessarily
|
||||
* next1.next1. It depends on whether next1.vertex1 is the same as vertex1. If next1.vertex1 == vertex1, then the edge
|
||||
* after next1 is next1.next1, but otherwise, it is next1.next2.
|
||||
*/
|
||||
class ConnEdge {
|
||||
/**
|
||||
* The edge's first endpoint (at the same level as the edge).
|
||||
*/
|
||||
public EulerTourVertex vertex1;
|
||||
|
||||
/**
|
||||
* The edge's second endpoint (at the same level as the edge).
|
||||
*/
|
||||
public EulerTourVertex vertex2;
|
||||
|
||||
/**
|
||||
* The EulerTourEdge object describing the edge's presence in an Euler tour tree, at the same level as the edge, or
|
||||
* null if the edge is not in the Euler tour forest F_i.
|
||||
*/
|
||||
public EulerTourEdge eulerTourEdge;
|
||||
|
||||
/**
|
||||
* The edge preceding this in a linked list of same-level edges adjacent to vertex1, if any. The edge is either part
|
||||
* of a list of non-forest edges starting with vertex1.graphListHead, or part of a list of forest edges starting
|
||||
* with vertex1.forestListHead. Note that this list excludes any edges that also appear in lower levels.
|
||||
*/
|
||||
public ConnEdge prev1;
|
||||
|
||||
/**
|
||||
* The edge succeeding this in a linked list of same-level edges adjacent to vertex1, if any. The edge is either
|
||||
* part of a list of non-forest edges starting with vertex1.graphListHead, or part of a list of forest edges
|
||||
* starting with vertex1.forestListHead. Note that this list excludes any edges that also appear in lower levels.
|
||||
*/
|
||||
public ConnEdge next1;
|
||||
|
||||
/**
|
||||
* The edge preceding this in a linked list of same-level edges adjacent to vertex2, if any. The edge is either part
|
||||
* of a list of non-forest edges starting with vertex2.graphListHead, or part of a list of forest edges starting
|
||||
* with vertex2.forestListHead. Note that this list excludes any edges that also appear in lower levels.
|
||||
*/
|
||||
public ConnEdge prev2;
|
||||
|
||||
/**
|
||||
* The edge succeeding this in a linked list of same-level edges adjacent to vertex2, if any. The edge is either
|
||||
* part of a list of non-forest edges starting with vertex2.graphListHead, or part of a list of forest edges
|
||||
* starting with vertex2.forestListHead. Note that this list excludes any edges that also appear in lower levels.
|
||||
*/
|
||||
public ConnEdge next2;
|
||||
|
||||
public ConnEdge(EulerTourVertex vertex1, EulerTourVertex vertex2) {
|
||||
this.vertex1 = vertex1;
|
||||
this.vertex2 = vertex2;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,52 +0,0 @@
|
||||
/*
|
||||
* This file was originally written by btrekkie under the MIT license, which is compatible with the LGPL license for this usage within Baritone
|
||||
* https://github.com/btrekkie/dynamic-connectivity/
|
||||
*/
|
||||
|
||||
package baritone.builder.utils.com.github.btrekkie.connectivity;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
/**
|
||||
* A vertex in a ConnGraph. See the comments for ConnGraph.
|
||||
*/
|
||||
public class ConnVertex {
|
||||
/**
|
||||
* The thread-local random number generator we use by default to set the "hash" field.
|
||||
*/
|
||||
private static final ThreadLocal<Random> random = new ThreadLocal<Random>() {
|
||||
@Override
|
||||
protected Random initialValue() {
|
||||
return new Random();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* A randomly generated integer to use as the return value of hashCode(). ConnGraph relies on random hash codes for
|
||||
* its performance guarantees.
|
||||
*/
|
||||
private final long hash;
|
||||
|
||||
public ConnVertex() {
|
||||
hash = random.get().nextLong();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new ConnVertex.
|
||||
*
|
||||
* @param random The random number generator to use to produce a random hash code. ConnGraph relies on random hash
|
||||
* codes for its performance guarantees.
|
||||
*/
|
||||
public ConnVertex(Random random) {
|
||||
hash = random.nextLong();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public long getIdentity() {
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
/*
|
||||
* This file was originally written by btrekkie under the MIT license, which is compatible with the LGPL license for this usage within Baritone
|
||||
* https://github.com/btrekkie/dynamic-connectivity/
|
||||
*/
|
||||
|
||||
package baritone.builder.utils.com.github.btrekkie.connectivity;
|
||||
|
||||
/**
|
||||
* The representation of a forest edge in some Euler tour forest F_i at some particular level i. Each forest edge has
|
||||
* one EulerTourEdge object for each level it appears in. See the comments for the implementation of ConnGraph.
|
||||
*/
|
||||
class EulerTourEdge {
|
||||
/**
|
||||
* One of the two visits preceding the edge in the Euler tour, in addition to visit2. (The node is at the same level
|
||||
* as the EulerTourEdge.)
|
||||
*/
|
||||
public final EulerTourNode visit1;
|
||||
|
||||
/**
|
||||
* One of the two visits preceding the edge in the Euler tour, in addition to visit1. (The node is at the same level
|
||||
* as the EulerTourEdge.)
|
||||
*/
|
||||
public final EulerTourNode visit2;
|
||||
|
||||
/**
|
||||
* The representation of this edge in the next-higher level. higherEdge is null if this edge is in the highest
|
||||
* level.
|
||||
*/
|
||||
public EulerTourEdge higherEdge;
|
||||
|
||||
public EulerTourEdge(EulerTourNode visit1, EulerTourNode visit2) {
|
||||
this.visit1 = visit1;
|
||||
this.visit2 = visit2;
|
||||
}
|
||||
}
|
||||
@@ -1,161 +0,0 @@
|
||||
/*
|
||||
* This file was originally written by btrekkie under the MIT license, which is compatible with the LGPL license for this usage within Baritone
|
||||
* https://github.com/btrekkie/dynamic-connectivity/
|
||||
*/
|
||||
|
||||
package baritone.builder.utils.com.github.btrekkie.connectivity;
|
||||
|
||||
import baritone.builder.utils.com.github.btrekkie.red_black_node.RedBlackNode;
|
||||
|
||||
/**
|
||||
* A node in an Euler tour tree for ConnGraph (at some particular level i). See the comments for the implementation of
|
||||
* ConnGraph.
|
||||
*/
|
||||
class EulerTourNode extends RedBlackNode<EulerTourNode> {
|
||||
/**
|
||||
* The dummy leaf node.
|
||||
*/
|
||||
public static final EulerTourNode LEAF = new EulerTourNode(null, null);
|
||||
|
||||
/**
|
||||
* The vertex this node visits.
|
||||
*/
|
||||
public final EulerTourVertex vertex;
|
||||
|
||||
/**
|
||||
* The number of nodes in the subtree rooted at this node.
|
||||
*/
|
||||
public int size;
|
||||
|
||||
/**
|
||||
* Whether the subtree rooted at this node contains a node "node" for which
|
||||
* node.vertex.arbitraryNode == node && node.vertex.graphListHead != null.
|
||||
*/
|
||||
public boolean hasGraphEdge;
|
||||
|
||||
/**
|
||||
* Whether the subtree rooted at this node contains a node "node" for which
|
||||
* node.vertex.arbitraryNode == node && node.vertex.forestListHead != null.
|
||||
*/
|
||||
public boolean hasForestEdge;
|
||||
|
||||
/** The graph this belongs to. "graph" is null instead if this node is not in the highest level. */
|
||||
public final ConnGraph graph;
|
||||
|
||||
/**
|
||||
* The combined augmentation for the subtree rooted at this node. This is the result of combining the augmentation
|
||||
* values node.vertex.augmentation for all nodes "node" in the subtree rooted at this node for which
|
||||
* node.vertex.arbitraryVisit == node, using graph.augmentation. This is null if hasAugmentation is false.
|
||||
*/
|
||||
public Object augmentation;
|
||||
|
||||
/**
|
||||
* Whether the subtree rooted at this node contains at least one augmentation value. This indicates whether there is
|
||||
* some node "node" in the subtree rooted at this node for which node.vertex.hasAugmentation is true and
|
||||
* node.vertex.arbitraryVisit == node.
|
||||
*/
|
||||
public boolean hasAugmentation;
|
||||
|
||||
/**
|
||||
* Whether this node "owns" "augmentation", with respect to graph.augmentationReleaseListener. A node owns a
|
||||
* combined augmentation if the node obtained it by calling Augmentation.combine, as opposed to copying a reference
|
||||
* to vertex.augmentation, left.augmentation, or right.augmentation. This is false if
|
||||
* graph.augmentationReleaseListener or "augmentation" is null.
|
||||
*/
|
||||
public boolean ownsAugmentation;
|
||||
|
||||
public EulerTourNode(EulerTourVertex vertex, ConnGraph graph) {
|
||||
this.vertex = vertex;
|
||||
this.graph = graph;
|
||||
}
|
||||
|
||||
/**
|
||||
* Like augment(), but only updates the augmentation fields hasGraphEdge and hasForestEdge.
|
||||
*/
|
||||
public boolean augmentFlags() {
|
||||
boolean newHasGraphEdge =
|
||||
left.hasGraphEdge || right.hasGraphEdge || (vertex.arbitraryVisit == this && vertex.graphListHead != null);
|
||||
boolean newHasForestEdge =
|
||||
left.hasForestEdge || right.hasForestEdge ||
|
||||
(vertex.arbitraryVisit == this && vertex.forestListHead != null);
|
||||
if (newHasGraphEdge == hasGraphEdge && newHasForestEdge == hasForestEdge) {
|
||||
return false;
|
||||
} else {
|
||||
hasGraphEdge = newHasGraphEdge;
|
||||
hasForestEdge = newHasForestEdge;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean augment() {
|
||||
int newSize = left.size + right.size + 1;
|
||||
boolean augmentedFlags = augmentFlags();
|
||||
if (graph == null || graph.augmentation == null) {
|
||||
if (newSize == size && !augmentedFlags) {
|
||||
return false;
|
||||
} else {
|
||||
size = newSize;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
AugmentationReleaseListener releaseListener = graph.augmentationReleaseListener;
|
||||
Object newAugmentation = null;
|
||||
int valueCount = 0;
|
||||
if (left.hasAugmentation) {
|
||||
newAugmentation = left.augmentation;
|
||||
valueCount = 1;
|
||||
}
|
||||
if (vertex.hasAugmentation && vertex.arbitraryVisit == this) {
|
||||
if (valueCount == 0) {
|
||||
newAugmentation = vertex.augmentation;
|
||||
} else {
|
||||
newAugmentation = graph.augmentation.combine(newAugmentation, vertex.augmentation);
|
||||
}
|
||||
valueCount++;
|
||||
}
|
||||
if (right.hasAugmentation) {
|
||||
if (valueCount == 0) {
|
||||
newAugmentation = right.augmentation;
|
||||
} else {
|
||||
Object tempAugmentation = newAugmentation;
|
||||
newAugmentation = graph.augmentation.combine(newAugmentation, right.augmentation);
|
||||
if (valueCount >= 2 && releaseListener != null && tempAugmentation != null) {
|
||||
releaseListener.combinedAugmentationReleased(tempAugmentation);
|
||||
}
|
||||
}
|
||||
valueCount++;
|
||||
}
|
||||
|
||||
boolean newHasAugmentation = valueCount > 0;
|
||||
boolean newOwnsAugmentation = valueCount >= 2 && releaseListener != null && newAugmentation != null;
|
||||
if (newSize == size && !augmentedFlags && hasAugmentation == newHasAugmentation &&
|
||||
(newAugmentation != null ? newAugmentation.equals(augmentation) : augmentation == null)) {
|
||||
if (newOwnsAugmentation) {
|
||||
releaseListener.combinedAugmentationReleased(newAugmentation);
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
if (ownsAugmentation) {
|
||||
releaseListener.combinedAugmentationReleased(augmentation);
|
||||
}
|
||||
size = newSize;
|
||||
augmentation = newAugmentation;
|
||||
hasAugmentation = newHasAugmentation;
|
||||
ownsAugmentation = newOwnsAugmentation;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeWithoutGettingRoot() {
|
||||
super.removeWithoutGettingRoot();
|
||||
if (ownsAugmentation) {
|
||||
graph.augmentationReleaseListener.combinedAugmentationReleased(augmentation);
|
||||
augmentation = null;
|
||||
hasAugmentation = false;
|
||||
ownsAugmentation = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
/*
|
||||
* This file was originally written by btrekkie under the MIT license, which is compatible with the LGPL license for this usage within Baritone
|
||||
* https://github.com/btrekkie/dynamic-connectivity/
|
||||
*/
|
||||
|
||||
package baritone.builder.utils.com.github.btrekkie.connectivity;
|
||||
|
||||
/**
|
||||
* The representation of a ConnVertex at some particular level i. Each vertex has one EulerTourVertex object for each
|
||||
* level it appears in. Note that different vertices may appear in different numbers of levels, as EulerTourVertex
|
||||
* objects are only created for lower levels as needed. See the comments for the implementation of ConnGraph.
|
||||
*/
|
||||
class EulerTourVertex {
|
||||
/**
|
||||
* The representation of this edge in the next-lower level. lowerVertex is null if this is the lowest-level
|
||||
* representation of this vertex.
|
||||
*/
|
||||
public EulerTourVertex lowerVertex;
|
||||
|
||||
/**
|
||||
* The representation of this edge in the next-higher level. This is null if this vertex is in the highest level.
|
||||
*/
|
||||
public EulerTourVertex higherVertex;
|
||||
|
||||
/**
|
||||
* An arbitrarily selected visit to the vertex in the Euler tour tree that contains it (at the same level as this).
|
||||
*/
|
||||
public EulerTourNode arbitraryVisit;
|
||||
|
||||
/**
|
||||
* The first edge in the linked list of level-i edges that are adjacent to the vertex in G_i, but are not in the
|
||||
* Euler tour forest F_i, where i is the level of the vertex. Note that this list excludes any edges that also
|
||||
* appear in lower levels.
|
||||
*/
|
||||
public ConnEdge graphListHead;
|
||||
|
||||
/**
|
||||
* The first edge in the linked list of level-i edges adjacent to the vertex that are in F_i, where i is the level
|
||||
* of the vertex. Note that this list excludes any edges that also appear in lower levels.
|
||||
*/
|
||||
public ConnEdge forestListHead;
|
||||
|
||||
/**
|
||||
* The augmentation associated with this vertex, if any. This is null instead if higherVertex != null.
|
||||
*/
|
||||
public Object augmentation;
|
||||
|
||||
/**
|
||||
* Whether there is any augmentation associated with this vertex. This is false instead if higherVertex != null.
|
||||
*/
|
||||
public boolean hasAugmentation;
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
package baritone.builder.utils.com.github.btrekkie.connectivity;
|
||||
|
||||
/**
|
||||
* A combining function for taking the augmentations associated with a set of ConnVertices and reducing them to a single
|
||||
* result. MutatingAugmentation has a binary operation "combine" that takes two values, combines them using a function
|
||||
* C, and stores the result in a mutable object. For example:
|
||||
* <p>
|
||||
* class IntWrapper {
|
||||
* public int value;
|
||||
* }
|
||||
* <p>
|
||||
* class Sum implements MutatingAugmentation {
|
||||
* public void combine(Object value1, Object value2, Object result) {
|
||||
* ((IntWrapper)result).value = ((IntWrapper)value1).value + ((IntWrapper)value2).value;
|
||||
* }
|
||||
* <p>
|
||||
* public Object newAugmentation() {
|
||||
* return new IntWrapper();
|
||||
* }
|
||||
* }
|
||||
* <p>
|
||||
* Given vertices with augmentations A1, A2, A3, and A4, the combined result may be obtained by computing
|
||||
* C(C(C(A1, A2), A3), A4). In order for an augmentation result to be meaningful, the combining function must be
|
||||
* commutative, meaning C(x, y) is equivalent to C(y, x), and associative, meaning C(x, C(y, z)) is equivalent to
|
||||
* C(C(x, y), z).
|
||||
* <p>
|
||||
* If a ConnGraph represents a game map, then one example of an augmentation would be the amount of gold accessible from
|
||||
* a certain location. Each vertex would be augmented with the amount of gold in that location, and the combining
|
||||
* function would add the two amounts of gold passed in as arguments. Another example would be the strongest monster
|
||||
* that can reach a particular location. Each vertex with at least one monster would be augmented with a reference to
|
||||
* the strongest monster at that location, and the combining function would take the stronger of the two monsters passed
|
||||
* in as arguments. A third example would be the number of locations accessible from a given vertex. Each vertex would
|
||||
* be augmented with the number 1, and the combining function would add the two numbers of vertices passed in as
|
||||
* arguments.
|
||||
* <p>
|
||||
* ConnGraph treats two augmentation values X and Y as interchangeable if they are equal, as in
|
||||
* X != null ? X.equals(Y) : Y == null. The same holds for two combined augmentation values, and for one combined
|
||||
* augmentation value and one augmentation value.
|
||||
* <p>
|
||||
* See the comments for ConnGraph.
|
||||
*/
|
||||
public interface MutatingAugmentation {
|
||||
/**
|
||||
* Computes the result of combining value1 and value2 into one, and stores it in "result".
|
||||
*/
|
||||
public void combine(Object value1, Object value2, Object result);
|
||||
|
||||
/**
|
||||
* Constructs and returns a new augmentation object that may subsequently be passed to "combine". The initial
|
||||
* contents of the object are ignored.
|
||||
*/
|
||||
public Object newAugmentation();
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
/*
|
||||
* This file was originally written by btrekkie under the MIT license, which is compatible with the LGPL license for this usage within Baritone
|
||||
* https://github.com/btrekkie/dynamic-connectivity/
|
||||
*/
|
||||
|
||||
package baritone.builder.utils.com.github.btrekkie.connectivity;
|
||||
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
||||
|
||||
/**
|
||||
* Describes a ConnVertex, with respect to a particular ConnGraph. There is exactly one VertexInfo object per vertex in
|
||||
* a given graph, regardless of how many levels the vertex is in. See the comments for the implementation of ConnGraph.
|
||||
*/
|
||||
class VertexInfo {
|
||||
/**
|
||||
* The representation of the vertex in the highest level.
|
||||
*/
|
||||
public EulerTourVertex vertex;
|
||||
|
||||
/**
|
||||
* A map from each ConnVertex adjacent to this vertex to the ConnEdge object for the edge connecting it to this
|
||||
* vertex. Lookups take O(1) expected time and O(log N / log log N) time with high probability, because "edges" is a
|
||||
* HashMap, and ConnVertex.hashCode() returns a random integer.
|
||||
*/
|
||||
public Long2ObjectOpenHashMap<ConnEdge> edges = new Long2ObjectOpenHashMap<>(); // TODO in my use case, each vertex will have at most 4 edges (at most one each to north, south, east, west). there's probably a MASSIVE performance improvement to be had by simply switching this to such an array. it might require some bitwise twiddling because the key is a long so we'd have to subtract xyz, but, it would work pretty easily just by copying the Face.horizontalIndex approach (x & 1 | (x | z) & 2)
|
||||
|
||||
public VertexInfo(EulerTourVertex vertex) {
|
||||
this.vertex = vertex;
|
||||
}
|
||||
}
|
||||
@@ -1,124 +0,0 @@
|
||||
Thoughts concerning optimization:
|
||||
|
||||
- I should not optimize until I have access to real-world input samples. Any
|
||||
optimizations are bound to result in increased code complexity, which is not
|
||||
worth it unless I can demonstrate a non-trivial reduction in running time in a
|
||||
real-world setting.
|
||||
- I should profile the program and see if there are any quick wins.
|
||||
- Most obviously, I could implement the optimization of using a B-tree in the
|
||||
top level, and see what effect this has on performance. It would definitely
|
||||
improve the query time, and hopefully it wouldn't affect the update time too
|
||||
much.
|
||||
- If I do implement the B-tree optimization, I should also store a binary search
|
||||
tree representation of the same forest in the top level, for the user-defined
|
||||
augmentation. That way, updates will require O(log N) calls to
|
||||
Augmentation.combine, rather than O(log^2 N / log log N) calls. We have no
|
||||
control over how long Augmentation.combine takes, so we should minimize calls
|
||||
to that method. It will take a bit of care to ensure that fetching the
|
||||
augmentation for a connected component takes O(log N / log log N) time,
|
||||
however.
|
||||
- Alternatively, in each B-tree node, we could store a binary search tree that
|
||||
combines the augmentation information for the items in that node. This might
|
||||
even benefit ConnGraphs that do not have user-supplied augmentation.
|
||||
- Actually, there is a way of speeding up queries by an O(log log N) factor that
|
||||
should be cleaner and easier to implement than a B-tree. We could change each
|
||||
Euler tour node in the top level to store its kth ancestor (e.g. if k = 3,
|
||||
then each node stores a pointer to its great grandparent). Each time we change
|
||||
a node's parent, we have to change the pointers for all of the descendants
|
||||
that are less than k levels below the node - up to 2^k - 1 nodes. Since there
|
||||
are O(log N) parent pointer changes per update, and updates already take
|
||||
O(log^2 N) amortized time, we can afford a k value of up to lg lg N + O(1)
|
||||
(but interestingly, not O(log log N)). However, I think this would be
|
||||
significantly slower than a B-tree in practice.
|
||||
- If I don't implement the B-tree optimization, there are a couple of small
|
||||
optimizations I could try. First, I could move the field EulerTourNode.graph
|
||||
to EulerTourVertex, in order to save a little bit of space. Second, I could
|
||||
create EulerTourNode and EulerTourVertex subclasses specifically for the top
|
||||
level, and offload the fields related to user augmentation to those
|
||||
subclasses. These changes seem inelegant, but it might be worth checking the
|
||||
effect they have on performance.
|
||||
- I looked at the heuristics recommended in
|
||||
http://people.csail.mit.edu/karger/Papers/impconn.pdf (Iyer, et al. (2001): An
|
||||
Experimental Study of Poly-Logarithmic Fully-Dynamic Connectivity Algorithms).
|
||||
The first heuristic is: before pushing down all of the same-level forest
|
||||
edges, which is an expensive operation, sample O(log N) same-level non-forest
|
||||
edges to see if we can get lucky and find a replacement edge without pushing
|
||||
anything. The second heuristic is to refrain from pushing edges in
|
||||
sufficiently small components.
|
||||
|
||||
The first heuristic seems reasonable. However, I don't get the second
|
||||
heuristic. The concept seems to be basically the same as the first - don't
|
||||
push down any edges if we can cheaply find a replacement edge. However, the
|
||||
execution is cruder. Rather than limiting the number of edges to search, which
|
||||
is closely related to the cost of the search, the second heuristic is based on
|
||||
the number of vertices, which is not as closely related.
|
||||
- I have a few ideas for implementing this first heuristic, which could be
|
||||
attempted and their effects on performance measured. The first is that to
|
||||
sample the graph edges in a semi-random order, I could augment each Euler tour
|
||||
node with the sum across all canonical visits to vertices of the number of
|
||||
adjacent same-level graph edges. Then, to obtain a sample, we select each
|
||||
vertex with a probability proportional to this adjacency number. This is
|
||||
fairly straightforward: at each node, we decide to go left, go right, or use
|
||||
the current vertex in proportion to the augmentation.
|
||||
|
||||
This is not exactly random, because after we select a vertex, we choose an
|
||||
arbitrary adjacent edge. However, it seems close enough, and it's not easy to
|
||||
do better.
|
||||
- After sampling an edge, we should remove it from the adjacency list, to avoid
|
||||
repeatedly sampling the same edge. We should then store it in an array of
|
||||
edges we sampled, so that later, we can either re-add the edges to the
|
||||
adjacency lists or push all of them down as necessary.
|
||||
- If the sampling budget (i.e. the number of edges we intend to sample) is at
|
||||
least the number of same-level graph edges, then we should forgo pushing down
|
||||
any edges, regardless of whether we find a replacement edge.
|
||||
- We don't need to sample edges from the smaller of the two post-cut trees. We
|
||||
can sample them from the larger one if it has fewer same-level graph edges.
|
||||
This increases our chances of finding a replacement edge if there is one. As
|
||||
long as we don't push down any forest edges in the larger tree, we're safe.
|
||||
- With an extra augmentation, we can determine whether there is probably a
|
||||
replacement edge. This helps us because if there is probably no replacement
|
||||
edge, then we can save some time by skipping edge sampling entirely. (If the
|
||||
sampling budget is at least the number of same-level graph edges, then we
|
||||
should also refrain from pushing down any edges, as in a previously mentioned
|
||||
optimization.)
|
||||
|
||||
Assign each ConnEdge a random integer ID. Store the XOR of the IDs of all
|
||||
adjacent same-level graph edges in each of the vertices. Augment the Euler
|
||||
tour trees with the XOR of those values for all canonical visits to vertices.
|
||||
The XOR stored in a post-cut tree's root node is equal to the XOR of all of
|
||||
the replacement edges' IDs, because each non-replacement edge is in two
|
||||
adjacency lists and cancels itself out, while each replacement edge is in one
|
||||
adjacency list. Thus, the XOR is 0 if there is no replacement edge, and it is
|
||||
non-zero with probability 1 - 1 / 2^32 if there is at least one replacement
|
||||
edge.
|
||||
- If one of the post-cut trees has a same-level forest edge and the other does
|
||||
not, and the difference in the number of same-level graph edges is not that
|
||||
large, we should favor the one that does not, because it's expensive to push
|
||||
forest edges. Also, there's no need to sample if we pick a tree that has no
|
||||
same-level forest edges.
|
||||
- I wonder if there's a good way to estimate the cost of pushing down a given
|
||||
set of forest edges. For example, is there a strong correlation between the
|
||||
number of forest edges and the cost of pushing them? We could use a larger
|
||||
sampling budget the greater this cost is.
|
||||
- During sampling, it might help to push down edges whose endpoints are
|
||||
connected in the next-lower level, and to not count them against the sampling
|
||||
budget. By paying for some of the edges, we're able to sample more edges, so
|
||||
we're less likely to have to push down the forest edges.
|
||||
|
||||
The downside is that checking whether the endpoints are in the same connected
|
||||
component takes O(log N) time. To mitigate this, we should refrain from
|
||||
attempting to push down edges until necessary. That is, we should spend the
|
||||
entire sampling budget first, then search the edges we sampled for an edge
|
||||
that we can push down. After finding one such edge, we should sample another
|
||||
edge, then search for another edge to push down, etc.
|
||||
- When we are done sampling edges, it might still be beneficial to iterate over
|
||||
the rest of the same-level graph edges in a (semi-)random order. This does
|
||||
increase the amount of time that iteration takes. However, using a random
|
||||
order could be helpful if the sequence of updates has some sort of pattern
|
||||
affecting the location of replacement edges. For example, if there is a
|
||||
tendency to put replacement edges near the end of the Euler tour, or to
|
||||
cluster replacement edges so that they are close to each other in the Euler
|
||||
tour, then random iteration should tend to locate a replacement edge faster
|
||||
than in-order iteration. (If we know from a previously mentioned optimization
|
||||
that there is probably no replacement edge, then we shouldn't bother to
|
||||
iterate over the edges in random order.)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,36 +0,0 @@
|
||||
/*
|
||||
* This file was originally written by btrekkie under the MIT license, which is compatible with the LGPL license for this usage within Baritone
|
||||
* https://github.com/btrekkie/RedBlackNode/
|
||||
*/
|
||||
|
||||
package baritone.builder.utils.com.github.btrekkie.red_black_node;
|
||||
|
||||
/**
|
||||
* Wraps a value using reference equality. In other words, two references are equal only if their values are the same
|
||||
* object instance, as in ==.
|
||||
*
|
||||
* @param <T> The type of value.
|
||||
*/
|
||||
class Reference<T> {
|
||||
/**
|
||||
* The value this wraps.
|
||||
*/
|
||||
private final T value;
|
||||
|
||||
public Reference(T value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public boolean equals(Object obj) {
|
||||
if (!(obj instanceof Reference)) {
|
||||
return false;
|
||||
}
|
||||
Reference<?> reference = (Reference<?>) obj;
|
||||
return value == reference.value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return System.identityHashCode(value);
|
||||
}
|
||||
}
|
||||
@@ -209,7 +209,7 @@ public final class CachedWorld implements ICachedWorld, Helper {
|
||||
private BlockPos guessPosition() {
|
||||
for (IBaritone ibaritone : BaritoneAPI.getProvider().getAllBaritones()) {
|
||||
IWorldData data = ibaritone.getWorldProvider().getCurrentWorld();
|
||||
if (data != null && data.getCachedWorld() == this) {
|
||||
if (data != null && data.getCachedWorld() == this && ibaritone.getPlayerContext().player() != null) {
|
||||
return ibaritone.getPlayerContext().playerFeet();
|
||||
}
|
||||
}
|
||||
|
||||
183
src/main/java/baritone/cache/ContainerMemory.java
vendored
183
src/main/java/baritone/cache/ContainerMemory.java
vendored
@@ -1,183 +0,0 @@
|
||||
/*
|
||||
* This file is part of Baritone.
|
||||
*
|
||||
* Baritone is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Baritone is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Baritone. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package baritone.cache;
|
||||
|
||||
import baritone.Baritone;
|
||||
import baritone.api.cache.IContainerMemory;
|
||||
import baritone.api.cache.IRememberedInventory;
|
||||
import baritone.api.utils.IPlayerContext;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.network.PacketBuffer;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.NoSuchFileException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
|
||||
public class ContainerMemory implements IContainerMemory {
|
||||
|
||||
private final Path saveTo;
|
||||
/**
|
||||
* The current remembered inventories
|
||||
*/
|
||||
private final Map<BlockPos, RememberedInventory> inventories = new HashMap<>();
|
||||
|
||||
|
||||
public ContainerMemory(Path saveTo) {
|
||||
this.saveTo = saveTo;
|
||||
try {
|
||||
read(Files.readAllBytes(saveTo));
|
||||
} catch (NoSuchFileException ignored) {
|
||||
inventories.clear();
|
||||
} catch (Exception ex) {
|
||||
ex.printStackTrace();
|
||||
inventories.clear();
|
||||
}
|
||||
}
|
||||
|
||||
private void read(byte[] bytes) throws IOException {
|
||||
PacketBuffer in = new PacketBuffer(Unpooled.wrappedBuffer(bytes));
|
||||
int chests = in.readInt();
|
||||
for (int i = 0; i < chests; i++) {
|
||||
int x = in.readInt();
|
||||
int y = in.readInt();
|
||||
int z = in.readInt();
|
||||
RememberedInventory rem = new RememberedInventory();
|
||||
rem.items.addAll(readItemStacks(in));
|
||||
rem.size = rem.items.size();
|
||||
rem.windowId = -1;
|
||||
if (rem.items.isEmpty()) {
|
||||
continue; // this only happens if the list has no elements, not if the list has elements that are all empty item stacks
|
||||
}
|
||||
inventories.put(new BlockPos(x, y, z), rem);
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void save() throws IOException {
|
||||
if (!Baritone.settings().containerMemory.value) {
|
||||
return;
|
||||
}
|
||||
ByteBuf buf = Unpooled.buffer(0, Integer.MAX_VALUE);
|
||||
PacketBuffer out = new PacketBuffer(buf);
|
||||
out.writeInt(inventories.size());
|
||||
for (Map.Entry<BlockPos, RememberedInventory> entry : inventories.entrySet()) {
|
||||
out = new PacketBuffer(out.writeInt(entry.getKey().getX()));
|
||||
out = new PacketBuffer(out.writeInt(entry.getKey().getY()));
|
||||
out = new PacketBuffer(out.writeInt(entry.getKey().getZ()));
|
||||
out = writeItemStacks(entry.getValue().getContents(), out);
|
||||
}
|
||||
Files.write(saveTo, out.array());
|
||||
}
|
||||
|
||||
public synchronized void setup(BlockPos pos, int windowId, int slotCount) {
|
||||
RememberedInventory inventory = inventories.computeIfAbsent(pos, x -> new RememberedInventory());
|
||||
inventory.windowId = windowId;
|
||||
inventory.size = slotCount;
|
||||
}
|
||||
|
||||
public synchronized Optional<RememberedInventory> getInventoryFromWindow(int windowId) {
|
||||
return inventories.values().stream().filter(i -> i.windowId == windowId).findFirst();
|
||||
}
|
||||
|
||||
@Override
|
||||
public final synchronized RememberedInventory getInventoryByPos(BlockPos pos) {
|
||||
return inventories.get(pos);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final synchronized Map<BlockPos, IRememberedInventory> getRememberedInventories() {
|
||||
// make a copy since this map is modified from the packet thread
|
||||
return new HashMap<>(inventories);
|
||||
}
|
||||
|
||||
public static List<ItemStack> readItemStacks(byte[] bytes) throws IOException {
|
||||
PacketBuffer in = new PacketBuffer(Unpooled.wrappedBuffer(bytes));
|
||||
return readItemStacks(in);
|
||||
}
|
||||
|
||||
public static List<ItemStack> readItemStacks(PacketBuffer in) throws IOException {
|
||||
int count = in.readInt();
|
||||
List<ItemStack> result = new ArrayList<>();
|
||||
for (int i = 0; i < count; i++) {
|
||||
result.add(in.readItemStack());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static byte[] writeItemStacks(List<ItemStack> write) {
|
||||
ByteBuf buf = Unpooled.buffer(0, Integer.MAX_VALUE);
|
||||
PacketBuffer out = new PacketBuffer(buf);
|
||||
out = writeItemStacks(write, out);
|
||||
return out.array();
|
||||
}
|
||||
|
||||
public static PacketBuffer writeItemStacks(List<ItemStack> write, PacketBuffer out2) {
|
||||
PacketBuffer out = out2; // avoid reassigning an argument LOL
|
||||
out = new PacketBuffer(out.writeInt(write.size()));
|
||||
for (ItemStack stack : write) {
|
||||
out = out.writeItemStack(stack);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* An inventory that we are aware of.
|
||||
* <p>
|
||||
* Associated with a {@link BlockPos} in {@link ContainerMemory#inventories}.
|
||||
*/
|
||||
public static class RememberedInventory implements IRememberedInventory {
|
||||
|
||||
/**
|
||||
* The list of items in the inventory
|
||||
*/
|
||||
private final List<ItemStack> items;
|
||||
|
||||
/**
|
||||
* The last known window ID of the inventory
|
||||
*/
|
||||
private int windowId;
|
||||
|
||||
/**
|
||||
* The size of the inventory
|
||||
*/
|
||||
private int size;
|
||||
|
||||
private RememberedInventory() {
|
||||
this.items = new ArrayList<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public final List<ItemStack> getContents() {
|
||||
return Collections.unmodifiableList(this.items);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final int getSize() {
|
||||
return this.size;
|
||||
}
|
||||
|
||||
public void updateFromOpenWindow(IPlayerContext ctx) {
|
||||
items.clear();
|
||||
items.addAll(ctx.player().openContainer.getInventory().subList(0, size));
|
||||
}
|
||||
}
|
||||
}
|
||||
17
src/main/java/baritone/cache/WorldData.java
vendored
17
src/main/java/baritone/cache/WorldData.java
vendored
@@ -19,7 +19,6 @@ package baritone.cache;
|
||||
|
||||
import baritone.Baritone;
|
||||
import baritone.api.cache.ICachedWorld;
|
||||
import baritone.api.cache.IContainerMemory;
|
||||
import baritone.api.cache.IWaypointCollection;
|
||||
import baritone.api.cache.IWorldData;
|
||||
|
||||
@@ -35,7 +34,6 @@ public class WorldData implements IWorldData {
|
||||
|
||||
public final CachedWorld cache;
|
||||
private final WaypointCollection waypoints;
|
||||
private final ContainerMemory containerMemory;
|
||||
//public final MapData map;
|
||||
public final Path directory;
|
||||
public final int dimension;
|
||||
@@ -44,7 +42,6 @@ public class WorldData implements IWorldData {
|
||||
this.directory = directory;
|
||||
this.cache = new CachedWorld(directory.resolve("cache"), dimension);
|
||||
this.waypoints = new WaypointCollection(directory.resolve("waypoints"));
|
||||
this.containerMemory = new ContainerMemory(directory.resolve("containers"));
|
||||
this.dimension = dimension;
|
||||
}
|
||||
|
||||
@@ -53,15 +50,6 @@ public class WorldData implements IWorldData {
|
||||
System.out.println("Started saving the world in a new thread");
|
||||
cache.save();
|
||||
});
|
||||
Baritone.getExecutor().execute(() -> {
|
||||
System.out.println("Started saving saved containers in a new thread");
|
||||
try {
|
||||
containerMemory.save();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
System.out.println("Failed to save saved containers");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -73,9 +61,4 @@ public class WorldData implements IWorldData {
|
||||
public IWaypointCollection getWaypoints() {
|
||||
return this.waypoints;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IContainerMemory getContainerMemory() {
|
||||
return this.containerMemory;
|
||||
}
|
||||
}
|
||||
|
||||
33
src/main/java/baritone/cache/WorldProvider.java
vendored
33
src/main/java/baritone/cache/WorldProvider.java
vendored
@@ -24,6 +24,7 @@ import baritone.utils.accessor.IAnvilChunkLoader;
|
||||
import baritone.utils.accessor.IChunkProviderServer;
|
||||
import net.minecraft.server.integrated.IntegratedServer;
|
||||
import net.minecraft.world.WorldServer;
|
||||
import net.minecraft.world.World;
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
|
||||
import java.io.File;
|
||||
@@ -44,9 +45,11 @@ public class WorldProvider implements IWorldProvider, Helper {
|
||||
private static final Map<Path, WorldData> worldCache = new HashMap<>(); // this is how the bots have the same cached world
|
||||
|
||||
private WorldData currentWorld;
|
||||
private World mcWorld; // this let's us detect a broken load/unload hook
|
||||
|
||||
@Override
|
||||
public final WorldData getCurrentWorld() {
|
||||
detectAndHandleBrokenLoading();
|
||||
return this.currentWorld;
|
||||
}
|
||||
|
||||
@@ -77,7 +80,16 @@ public class WorldProvider implements IWorldProvider, Helper {
|
||||
directory = new File(directory, "baritone");
|
||||
readme = directory;
|
||||
} else { // Otherwise, the server must be remote...
|
||||
String folderName = mc.getCurrentServerData().serverIP;
|
||||
String folderName;
|
||||
if (mc.getCurrentServerData() != null) {
|
||||
folderName = mc.getCurrentServerData().serverIP;
|
||||
} else {
|
||||
//replaymod causes null currentServerData and false singleplayer.
|
||||
System.out.println("World seems to be a replay. Not loading Baritone cache.");
|
||||
currentWorld = null;
|
||||
mcWorld = mc.world;
|
||||
return;
|
||||
}
|
||||
if (SystemUtils.IS_OS_WINDOWS) {
|
||||
folderName = folderName.replace(":", "_");
|
||||
}
|
||||
@@ -103,11 +115,13 @@ public class WorldProvider implements IWorldProvider, Helper {
|
||||
synchronized (worldCache) {
|
||||
this.currentWorld = worldCache.computeIfAbsent(dir, d -> new WorldData(d, dimension));
|
||||
}
|
||||
this.mcWorld = mc.world;
|
||||
}
|
||||
|
||||
public final void closeWorld() {
|
||||
WorldData world = this.currentWorld;
|
||||
this.currentWorld = null;
|
||||
this.mcWorld = null;
|
||||
if (world == null) {
|
||||
return;
|
||||
}
|
||||
@@ -115,8 +129,25 @@ public class WorldProvider implements IWorldProvider, Helper {
|
||||
}
|
||||
|
||||
public final void ifWorldLoaded(Consumer<WorldData> currentWorldConsumer) {
|
||||
detectAndHandleBrokenLoading();
|
||||
if (this.currentWorld != null) {
|
||||
currentWorldConsumer.accept(this.currentWorld);
|
||||
}
|
||||
}
|
||||
|
||||
private final void detectAndHandleBrokenLoading() {
|
||||
if (this.mcWorld != mc.world) {
|
||||
if (this.currentWorld != null) {
|
||||
System.out.println("mc.world unloaded unnoticed! Unloading Baritone cache now.");
|
||||
closeWorld();
|
||||
}
|
||||
if (mc.world != null) {
|
||||
System.out.println("mc.world loaded unnoticed! Loading Baritone cache now.");
|
||||
initWorld(mc.world.provider.getDimensionType().getId());
|
||||
}
|
||||
} else if (currentWorld == null && mc.world != null && (mc.isSingleplayer() || mc.getCurrentServerData() != null)) {
|
||||
System.out.println("Retrying to load Baritone cache");
|
||||
initWorld(mc.world.provider.getDimensionType().getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,6 +43,7 @@ public final class DefaultCommands {
|
||||
new RepackCommand(baritone),
|
||||
new BuildCommand(baritone),
|
||||
new SchematicaCommand(baritone),
|
||||
new LitematicaCommand(baritone),
|
||||
new ComeCommand(baritone),
|
||||
new AxisCommand(baritone),
|
||||
new ForceCancelCommand(baritone),
|
||||
@@ -51,7 +52,6 @@ public final class DefaultCommands {
|
||||
new TunnelCommand(baritone),
|
||||
new RenderCommand(baritone),
|
||||
new FarmCommand(baritone),
|
||||
new ChestsCommand(baritone),
|
||||
new FollowCommand(baritone),
|
||||
new ExploreFilterCommand(baritone),
|
||||
new ReloadAllCommand(baritone),
|
||||
|
||||
@@ -28,6 +28,7 @@ import baritone.api.command.argument.IArgConsumer;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class ETACommand extends Command {
|
||||
@@ -45,11 +46,17 @@ public class ETACommand extends Command {
|
||||
throw new CommandInvalidStateException("No process in control");
|
||||
}
|
||||
IPathingBehavior pathingBehavior = baritone.getPathingBehavior();
|
||||
|
||||
double ticksRemainingInSegment = pathingBehavior.ticksRemainingInSegment().orElse(Double.NaN);
|
||||
double ticksRemainingInGoal = pathingBehavior.estimatedTicksToGoal().orElse(Double.NaN);
|
||||
|
||||
logDirect(String.format(
|
||||
"Next segment: %.2f\n" +
|
||||
"Goal: %.2f",
|
||||
pathingBehavior.ticksRemainingInSegment().orElse(-1.0),
|
||||
pathingBehavior.estimatedTicksToGoal().orElse(-1.0)
|
||||
"Next segment: %.1fs (%.0f ticks)\n" +
|
||||
"Goal: %.1fs (%.0f ticks)",
|
||||
ticksRemainingInSegment / 20, // we just assume tps is 20, it isn't worth the effort that is needed to calculate it exactly
|
||||
ticksRemainingInSegment,
|
||||
ticksRemainingInGoal / 20,
|
||||
ticksRemainingInGoal
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
@@ -56,6 +56,7 @@ public class ExecutionControlCommands {
|
||||
|
||||
@Override
|
||||
public PathingCommand onTick(boolean calcFailed, boolean isSafeToCancel) {
|
||||
baritone.getInputOverrideHandler().clearAllKeys();
|
||||
return new PathingCommand(null, PathingCommandType.REQUEST_PAUSE);
|
||||
}
|
||||
|
||||
@@ -79,7 +80,7 @@ public class ExecutionControlCommands {
|
||||
}
|
||||
}
|
||||
);
|
||||
pauseCommand = new Command(baritone, "pause", "p") {
|
||||
pauseCommand = new Command(baritone, "pause", "p", "paws") {
|
||||
@Override
|
||||
public void execute(String label, IArgConsumer args) throws CommandException {
|
||||
args.requireMax(0);
|
||||
@@ -112,7 +113,7 @@ public class ExecutionControlCommands {
|
||||
);
|
||||
}
|
||||
};
|
||||
resumeCommand = new Command(baritone, "resume", "r") {
|
||||
resumeCommand = new Command(baritone, "resume", "r", "unpause", "unpaws") {
|
||||
@Override
|
||||
public void execute(String label, IArgConsumer args) throws CommandException {
|
||||
args.requireMax(0);
|
||||
|
||||
@@ -22,14 +22,23 @@ import baritone.api.command.Command;
|
||||
import baritone.api.command.argument.IArgConsumer;
|
||||
import baritone.api.command.datatypes.BlockById;
|
||||
import baritone.api.command.exception.CommandException;
|
||||
import baritone.api.command.helpers.TabCompleteHelper;
|
||||
import baritone.api.utils.BetterBlockPos;
|
||||
import baritone.cache.CachedChunk;
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.util.text.ITextComponent;
|
||||
import net.minecraft.util.text.TextComponentString;
|
||||
import net.minecraft.util.text.TextFormatting;
|
||||
import net.minecraft.util.text.event.ClickEvent;
|
||||
import net.minecraft.util.text.event.HoverEvent;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static baritone.api.command.IBaritoneChatControl.FORCE_COMMAND_PREFIX;
|
||||
|
||||
public class FindCommand extends Command {
|
||||
|
||||
public FindCommand(IBaritone baritone) {
|
||||
@@ -38,12 +47,13 @@ public class FindCommand extends Command {
|
||||
|
||||
@Override
|
||||
public void execute(String label, IArgConsumer args) throws CommandException {
|
||||
args.requireMin(1);
|
||||
List<Block> toFind = new ArrayList<>();
|
||||
while (args.hasAny()) {
|
||||
toFind.add(args.getDatatypeFor(BlockById.INSTANCE));
|
||||
}
|
||||
BetterBlockPos origin = ctx.playerFeet();
|
||||
toFind.stream()
|
||||
ITextComponent[] components = toFind.stream()
|
||||
.flatMap(block ->
|
||||
ctx.worldData().getCachedWorld().getLocationsOf(
|
||||
Block.REGISTRY.getNameForObject(block).getPath(),
|
||||
@@ -54,13 +64,39 @@ public class FindCommand extends Command {
|
||||
).stream()
|
||||
)
|
||||
.map(BetterBlockPos::new)
|
||||
.map(BetterBlockPos::toString)
|
||||
.forEach(this::logDirect);
|
||||
.map(this::positionToComponent)
|
||||
.toArray(ITextComponent[]::new);
|
||||
if (components.length > 0) {
|
||||
Arrays.asList(components).forEach(this::logDirect);
|
||||
} else {
|
||||
logDirect("No positions known, are you sure the blocks are cached?");
|
||||
}
|
||||
}
|
||||
|
||||
private ITextComponent positionToComponent(BetterBlockPos pos) {
|
||||
String positionText = String.format("%s %s %s", pos.x, pos.y, pos.z);
|
||||
String command = String.format("%sgoal %s", FORCE_COMMAND_PREFIX, positionText);
|
||||
ITextComponent baseComponent = new TextComponentString(pos.toString());
|
||||
ITextComponent hoverComponent = new TextComponentString("Click to set goal to this position");
|
||||
baseComponent.getStyle()
|
||||
.setColor(TextFormatting.GRAY)
|
||||
.setInsertion(positionText)
|
||||
.setClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, command))
|
||||
.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, hoverComponent));
|
||||
return baseComponent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<String> tabComplete(String label, IArgConsumer args) {
|
||||
return args.tabCompleteDatatype(BlockById.INSTANCE);
|
||||
public Stream<String> tabComplete(String label, IArgConsumer args) throws CommandException {
|
||||
return new TabCompleteHelper()
|
||||
.append(
|
||||
CachedChunk.BLOCKS_TO_KEEP_TRACK_OF.stream()
|
||||
.map(Block.REGISTRY::getNameForObject)
|
||||
.map(Object::toString)
|
||||
)
|
||||
.filterPrefixNamespaced(args.getString())
|
||||
.sortAlphabetically()
|
||||
.stream();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -71,10 +107,11 @@ public class FindCommand extends Command {
|
||||
@Override
|
||||
public List<String> getLongDesc() {
|
||||
return Arrays.asList(
|
||||
"",
|
||||
"The find command searches through Baritone's cache and attempts to find the location of the block.",
|
||||
"Tab completion will suggest only cached blocks and uncached blocks can not be found.",
|
||||
"",
|
||||
"Usage:",
|
||||
"> "
|
||||
"> find <block> [...] - Try finding the listed blocks"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import baritone.api.command.argument.IArgConsumer;
|
||||
import baritone.api.command.datatypes.EntityClassById;
|
||||
import baritone.api.command.datatypes.IDatatypeFor;
|
||||
import baritone.api.command.datatypes.NearbyPlayer;
|
||||
import baritone.api.command.exception.CommandErrorMessageException;
|
||||
import baritone.api.command.exception.CommandException;
|
||||
import baritone.api.command.helpers.TabCompleteHelper;
|
||||
import net.minecraft.entity.Entity;
|
||||
@@ -60,7 +61,7 @@ public class FollowCommand extends Command {
|
||||
if (gotten instanceof Class) {
|
||||
//noinspection unchecked
|
||||
classes.add((Class<? extends Entity>) gotten);
|
||||
} else {
|
||||
} else if (gotten != null) {
|
||||
entities.add((Entity) gotten);
|
||||
}
|
||||
}
|
||||
@@ -73,12 +74,14 @@ public class FollowCommand extends Command {
|
||||
if (group != null) {
|
||||
logDirect(String.format("Following all %s", group.name().toLowerCase(Locale.US)));
|
||||
} else {
|
||||
logDirect("Following these types of entities:");
|
||||
if (classes.isEmpty()) {
|
||||
if (entities.isEmpty()) throw new NoEntitiesException();
|
||||
logDirect("Following these entities:");
|
||||
entities.stream()
|
||||
.map(Entity::toString)
|
||||
.forEach(this::logDirect);
|
||||
} else {
|
||||
logDirect("Following these types of entities:");
|
||||
classes.stream()
|
||||
.map(EntityList::getKey)
|
||||
.map(Objects::requireNonNull)
|
||||
@@ -155,4 +158,12 @@ public class FollowCommand extends Command {
|
||||
this.datatype = datatype;
|
||||
}
|
||||
}
|
||||
|
||||
public static class NoEntitiesException extends CommandErrorMessageException {
|
||||
|
||||
protected NoEntitiesException() {
|
||||
super("No valid entities in range!");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,47 +18,34 @@
|
||||
package baritone.command.defaults;
|
||||
|
||||
import baritone.api.IBaritone;
|
||||
import baritone.api.cache.IRememberedInventory;
|
||||
import baritone.api.command.Command;
|
||||
import baritone.api.command.argument.IArgConsumer;
|
||||
import baritone.api.command.exception.CommandException;
|
||||
import baritone.api.command.exception.CommandInvalidStateException;
|
||||
import baritone.api.utils.BetterBlockPos;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.util.text.ITextComponent;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class ChestsCommand extends Command {
|
||||
public class LitematicaCommand extends Command {
|
||||
|
||||
public ChestsCommand(IBaritone baritone) {
|
||||
super(baritone, "chests");
|
||||
public LitematicaCommand(IBaritone baritone) {
|
||||
super(baritone, "litematica");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(String label, IArgConsumer args) throws CommandException {
|
||||
args.requireMax(0);
|
||||
Set<Map.Entry<BlockPos, IRememberedInventory>> entries =
|
||||
ctx.worldData().getContainerMemory().getRememberedInventories().entrySet();
|
||||
if (entries.isEmpty()) {
|
||||
throw new CommandInvalidStateException("No remembered inventories");
|
||||
}
|
||||
for (Map.Entry<BlockPos, IRememberedInventory> entry : entries) {
|
||||
// betterblockpos has censoring
|
||||
BetterBlockPos pos = new BetterBlockPos(entry.getKey());
|
||||
IRememberedInventory inv = entry.getValue();
|
||||
logDirect(pos.toString());
|
||||
for (ItemStack item : inv.getContents()) {
|
||||
ITextComponent component = item.getTextComponent();
|
||||
component.appendText(String.format(" x %d", item.getCount()));
|
||||
logDirect(component);
|
||||
int schematic = 0;
|
||||
if (args.hasAny()) {
|
||||
args.requireMax(1);
|
||||
if (args.is(Integer.class)) {
|
||||
schematic = args.getAs(Integer.class) - 1;
|
||||
}
|
||||
}
|
||||
try {
|
||||
baritone.getBuilderProcess().buildOpenLitematic(schematic);
|
||||
} catch (IndexOutOfBoundsException e) {
|
||||
logDirect("Pleas provide a valid index.");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -68,16 +55,17 @@ public class ChestsCommand extends Command {
|
||||
|
||||
@Override
|
||||
public String getShortDesc() {
|
||||
return "Display remembered inventories";
|
||||
return "Builds the loaded schematic";
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getLongDesc() {
|
||||
return Arrays.asList(
|
||||
"The chests command lists remembered inventories, I guess?",
|
||||
"Build a schematic currently open in Litematica.",
|
||||
"",
|
||||
"Usage:",
|
||||
"> chests"
|
||||
"> litematica",
|
||||
"> litematica <#>"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -147,7 +147,8 @@ public class SetCommand extends Command {
|
||||
throw new CommandInvalidTypeException(args.consumed(), "a toggleable setting", "some other setting");
|
||||
}
|
||||
//noinspection unchecked
|
||||
((Settings.Setting<Boolean>) setting).value ^= true;
|
||||
Settings.Setting<Boolean> asBoolSetting = (Settings.Setting<Boolean>) setting;
|
||||
asBoolSetting.value ^= true;
|
||||
logDirect(String.format(
|
||||
"Toggled setting %s to %s",
|
||||
setting.getName(),
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user