Compare commits

...

268 Commits

Author SHA1 Message Date
Leijurv
4bea9dfb42 started on simultaneous dijkstra scaffolder, saving progress here for now 2023-04-10 00:14:13 -07:00
Leijurv
f039a9cff8 belongs at bottom 2023-04-07 22:12:57 -07:00
Leijurv
06d18c4500 shouldnt have been separated 2023-04-07 20:45:08 -07:00
Leijurv
cc6228dced better test 2023-04-07 20:44:04 -07:00
Leijurv
0ed1c31106 more test 2023-04-07 20:37:32 -07:00
Leijurv
8c118401f3 blip staircases work 2023-04-07 17:47:29 -07:00
Leijurv
3e17352e99 navigablesurface now relies on full playerphysics and somehow it's FASTER??? 2023-04-06 23:43:41 -07:00
Leijurv
d594b15e40 abstract away the specific attachment from the impl of a player physics tracking navigable surface 2023-04-06 22:08:54 -07:00
Leijurv
c48d47ff2c no longer required 2023-04-06 21:25:53 -07:00
Leijurv
361c769650 more testing 2023-04-05 23:32:40 -07:00
Leijurv
294e847f19 refactors 2023-04-05 00:22:15 -07:00
Leijurv
c9bfcb417e compare with/without random splays before/during connection tests 2023-04-04 01:44:43 -07:00
Leijurv
1fc3155e9c more explanation 2023-04-02 23:19:38 -07:00
Leijurv
a9d6aaebf3 misc 2023-04-02 15:12:07 -07:00
Leijurv
b2ffa7de1b improved playerphysicstest actually caught a real bug 2023-03-27 23:17:12 +09:00
Leijurv
5ad90997ff player physics with bidirectional movement with tests, intended for use in navigable surface (because euler tour forests require an undirected graph) 2023-03-27 09:14:30 +09:00
Leijurv
b3afa98958 got a little carried away testing navigable surface 2023-03-23 21:43:37 -07:00
Leijurv
d2a5199383 unzobrist test just to make sure 2023-03-23 21:19:49 -07:00
Leijurv
d32973cae4 equals and hashcode for navigable surface tree attachment 2023-03-21 00:42:09 -07:00
Leijurv
f5dc6a034b refactor conngraphtest since i wrote part of it 2023-03-21 00:37:51 -07:00
Leijurv
06590f487b refactor and integrate redblacknode repo 2023-03-21 00:36:43 -07:00
Leijurv
3549a55f1e Merge remote-tracking branch 'rbn/trimmed' into builder-2 2023-03-21 00:26:43 -07:00
Leijurv
b8608c98e7 deleting these files to make way for merge of unrelated git history 2023-03-21 00:26:11 -07:00
Leijurv
e15cf933aa don't need tree list 2023-03-21 00:25:23 -07:00
Leijurv
8812f3027b trimmed for merge 2023-03-21 00:22:45 -07:00
Leijurv
69b36eea4b only run once 2023-03-21 00:11:27 -07:00
Leijurv
af320bd052 refactor and integrate 2023-03-21 00:11:04 -07:00
Leijurv
317f07283e integrate betterblockpos 2023-03-21 00:00:53 -07:00
Leijurv
bce0d47f95 tweak for older version of fastutil 2023-03-20 23:58:25 -07:00
Leijurv
b8d4c72a92 Merge remote-tracking branch 'dyncon/trimmed' into builder-2 2023-03-20 23:55:09 -07:00
Leijurv
0f5ee85e19 trim for merge 2023-03-20 23:54:02 -07:00
Leijurv
e7f54ab81d switch from integer to custom tree attachment 2023-03-20 23:51:19 -07:00
Leijurv
6fea22dc9f this is incredibly cool and it does figure out the staircase pattern like i hoped 2023-03-16 00:42:23 -07:00
Leijurv
2cb46e0540 explanatory readme 2023-03-16 00:18:38 -07:00
Leijurv
79113cf9a0 Merge branch 'long-map' into baritone-testing 2023-03-15 22:41:46 -07:00
Leijurv
fe808a5f83 test 2023-03-15 22:41:42 -07:00
Leijurv
200e68a1b9 import from baritone builder-2 2023-03-15 22:35:40 -07:00
Leijurv
59be6b4606 dead link 2023-03-15 14:56:13 -07:00
Leijurv
236d171d15 just in case anyone is curious, check in my partial impl from last year of euler tour forests over splay nodes (it was too intimidating to fully reimpl on top of a red-black or avl tree) 2023-03-15 14:54:13 -07:00
Leijurv
7ee6b40815 and i guess this should also use long instead of connvertex 2023-03-15 14:12:14 -07:00
Leijurv
49b620a7cf just checking if a long map could be faster, doesnt seem to matter (yet?) 2023-03-15 13:59:57 -07:00
Leijurv
52f795c3ae ram usage of empty hashmap buckets doesn't affect my use case 2023-03-15 13:43:39 -07:00
Leijurv
c177d6c708 need more heap 2023-03-15 13:39:27 -07:00
Leijurv
9870cbddc3 supposedly need to bump junit 2023-03-15 13:33:22 -07:00
Leijurv
8ee36bcd46 reformat all code 2023-03-15 13:30:23 -07:00
Leijurv
2af6dec3df switch from maven to gradle 2023-03-15 13:26:44 -07:00
Leijurv
802c81d766 bring in red black node 2023-03-15 13:23:39 -07:00
Leijurv
3a8c6712b1 satisfied with benchmark 2023-03-15 13:16:14 -07:00
Leijurv
f378578ada misc intellij 2023-03-15 13:16:08 -07:00
Leijurv
92447c0b5c dijkstra tweaks 2023-03-14 18:13:21 -07:00
Leijurv
023bebee66 more tests and sanity checks around collapsing components 2023-03-08 16:17:18 -08:00
Leijurv
cadf7a06d0 refactor per state with scaffolding into place order dependency graph 2023-03-08 13:49:20 -08:00
Leijurv
19c9238ea2 comments and thoughts about component merge order 2023-03-08 00:59:00 -08:00
Leijurv
19eda1bfe5 scaffolder output 2023-03-07 00:52:39 -08:00
Leijurv
1ffadf0242 refactor out scaffolder strategy 2023-03-07 00:37:53 -08:00
Leijurv
5d0904b46e refactor dependencygraphscaffoldingoverlay to be only mutable inside scaffolder (this was bugging me) 2023-03-04 22:00:18 -08:00
William Jacobs
3eda7a57e8 Added assessment of ancestor pointers to optimization_ideas.txt
This adds my assessment of the performance of the ancestor pointers optimization in practice to optimization_ideas.txt.
2022-08-02 19:27:45 -04:00
William Jacobs
2031ed4bc3 Added ancestor pointers to optimization_ideas.txt
This adds the idea of using ancestor pointers to optimization_ideas.txt.
2022-07-19 12:12:57 -04:00
Leijurv
a448d97ec9 dont remember, misc changes 2021-11-01 17:41:29 -07:00
Leijurv
1a59427957 refactor out sneak position 2021-09-03 13:47:00 -07:00
Leijurv
217f6ecf28 refactor out zobrist world state cache 2021-09-03 13:29:29 -07:00
Leijurv
0fd91edb97 column system, sneaking nodes, connected up raytrace system, misc refactors 2021-09-01 16:22:59 -07:00
Leijurv
35c2d8ba24 refactors and begin on sneak nodes 2021-08-31 17:40:01 -07:00
Leijurv
b26d10e26e jump physics 2021-08-27 23:56:46 -07:00
Leijurv
9da3e09062 snow sucks 2021-08-27 19:01:31 -07:00
Leijurv
9fce9ef5e3 precompute scaffolding variants to remove branch in dependency graph lookup 2021-08-27 16:13:29 -07:00
Leijurv
e5773da108 that's what the mask is 2021-08-24 22:21:41 -07:00
Leijurv
554c0de188 misc progress hooking everything up into solver engine harness 2021-08-24 20:26:38 -07:00
Leijurv
bea31fe77d Merge branch 'master' into builder-2 2021-08-24 15:16:30 -07:00
Leijurv
bcd22dc91c debug 2021-08-24 10:51:38 -07:00
Leijurv
33082428f3 some progress and reworking of greedy solver 2021-08-24 00:17:55 -07:00
Leijurv
0b4e5f753a tweaked physics and begun core pathfinder loop 2021-08-20 16:27:36 -07:00
Leijurv
5aa0dfc98c started on greedy solver, using concept from the zobrist testing 2021-08-19 00:16:52 -07:00
Leijurv
164d0e07cd bounds refactor and start on solver engine interface 2021-08-18 21:50:41 -07:00
Leijurv
b608a8a1e0 misc 2021-08-17 16:04:23 -07:00
Leijurv
13d0e2a5bf Merge pull request #2879 from scorbett123/fixProguard
Fix proguard from being broken.
2021-07-18 23:29:56 -07:00
scorbett123
c3085ae34f Fix proguard
Signed-off-by: scorbett123 <50634068+scorbett123@users.noreply.github.com>
2021-07-18 18:37:20 +01:00
Leijurv
ce2ec8047e Merge pull request #2825 from Zephreo/parkour-place
ParkourPlace for shorter parkour jumps
2021-07-16 20:50:23 -07:00
Harry
335cb7016e fix verifiedMaxJump
- remove redundant assignment by changing some values
- testing Co-Authors

Co-Authored-By: Leijurv <leijurv@gmail.com>
2021-07-17 13:28:12 +10:00
Harry
7a7b050615 weird verifiedMaxJump 2021-07-17 12:34:58 +10:00
Leijurv
6ff7ae800a Merge pull request #2833 from ZacSharp/layerHeight
 Add layerHeight setting
2021-07-16 18:47:15 -07:00
Harry
2159773723 requested changes
- tabs changed to 4 spaces
- fixed checking if block place is behind player
2021-07-01 10:09:06 +10:00
ZacSharp
ab3a015d61 Add layerHeight setting 2021-06-24 23:33:48 +02:00
Harry
3142387e45 Fix collision early return
not tested yet
- fixes early returns
2021-06-23 06:25:10 +10:00
Harry
051325e10f allow smaller parkour place jumps
allows 2 and 1 block parkour place jumps
2021-06-21 10:58:11 +10:00
Harry
199d5d57d5 Update .gitignore 2021-06-21 09:49:18 +10:00
Leijurv
b96795c517 Merge pull request #2803 from scorbett123/farmInventory
Make farm use inventory when allowInventory is enabled.
2021-06-14 12:32:53 -10:00
scorbett123
64ba96daf2 Do edit suggested by leijurv 2021-06-14 20:44:32 +01:00
Leijurv
14b17d86e5 Merge pull request #2801 from scorbett123/master
Add github actions.
2021-06-13 10:21:51 -10:00
Leijurv
90d5f27026 Merge pull request #2804 from ZacSharp/selCopyPaste
 Add a clipboard to #sel
2021-06-13 10:21:10 -10:00
Leijurv
16386b8ddc Merge pull request #2796 from mankool0/itemSaverThreshold
Added itemSaverThreshold setting
2021-06-13 10:17:28 -10:00
Leijurv
cbef21b9ad Merge pull request #2800 from scorbett123/patch-3
Remove broken hit count badge
2021-06-13 10:16:51 -10:00
ZacSharp
cec44e3668 Add a clipboard to #sel 2021-06-13 00:21:52 +02:00
scorbett123
f09f1e7f85 Make farm use inventory when allowInventory is enabled. 2021-06-12 18:18:56 +01:00
scorbett123
95910bc6cb Cleanup - travis.org is now deprecated and can no longer be used. 2021-06-12 14:13:39 +01:00
scorbett123
cc488ccff0 Make actions run tests as well. 2021-06-12 14:09:50 +01:00
scorbett123
37e129bfb9 Create run_tests.yml 2021-06-12 14:08:04 +01:00
scorbett123
6adc923bcb Revert "update mapping.txt location"
This reverts commit 065470ce

Github doesn't seem to support ../
2021-06-12 14:07:35 +01:00
scorbett123
065470cecb update mapping.txt location 2021-06-12 14:02:31 +01:00
scorbett123
260989bda6 Update build java version for github actions. 2021-06-12 13:52:56 +01:00
scorbett123
64b6e0fd56 Remove broken hit count badge 2021-06-12 13:49:59 +01:00
scorbett123
8f45718b75 Try to create github actions
An attempt at github actions.
2021-06-12 11:27:27 +01:00
scorbett123
e66bb7a14b Make proguard output the mapping. 2021-06-12 11:14:59 +01:00
mankool
8bfe32eeef Added itemSaverThreshold 2021-06-09 10:34:45 -07:00
Leijurv
e85c1bbc0d Merge pull request #2736 from ZacSharp/exploreForBlocksForMining
Make MineProcess respect exploreForBlocks
2021-05-28 13:27:13 -07:00
ZacSharp
f99bf0d000 Make legitMode always explore 2021-05-28 22:18:33 +02:00
Leijurv
db24a2251f Merge pull request #2771 from ZacSharp/notificationCallback
More log callbacks
2021-05-26 11:32:53 -07:00
Leijurv
fcb3747690 Merge pull request #2776 from thecakeisalie25/master
Add "RenderGoalAsBox" Setting
2021-05-26 11:30:51 -07:00
Tabbs
dcc27a7447 oops forgot the javadoc 2021-05-25 14:52:16 -05:00
Tabbs
bea39bc613 Merge branch 'master' of https://github.com/thecakeisalie25/baritone 2021-05-25 14:44:59 -05:00
Tabbs
546493ba45 do our best to stop beacon animating
The game seems to animate the beacons anyway, so it does shake a little bit.
2021-05-25 14:44:54 -05:00
Tabbs
dda4928693 change name to renderGoalAnimated 2021-05-25 14:43:32 -05:00
Larson
7d144dd076 fix decimal
Co-authored-by: scorbett123 <50634068+scorbett123@users.noreply.github.com>
2021-05-25 14:33:13 -05:00
Tabbs
73e0700ea3 fix weird rendering issue 2021-05-25 12:41:36 -05:00
Tabbs
3fc36cf798 fix y to 1 if animation is disabled 2021-05-25 12:34:17 -05:00
Leijurv
02f5d4efbe misc bounds benchmark, property extractor, scaffolder 2021-05-24 17:16:25 -07:00
Leijurv
a9b7b91a3c misc tweaks, forgot to commit last week 2021-05-24 12:52:34 -07:00
ZacSharp
dbc43b445b Replace unhandled exception with a helpful message 2021-05-24 03:38:14 +02:00
ZacSharp
638fcd393a Don't try to tab complete hidden settings 2021-05-24 03:20:31 +02:00
ZacSharp
7b5f419713 Treat the new callbacks like the old one 2021-05-24 03:14:33 +02:00
Leijurv
d501359857 progress on properties and entity oriented blocks 2021-05-18 00:16:30 -07:00
Leijurv
d9591e089d basic impl of place options 2021-05-17 02:12:51 -07:00
Leijurv
4848e901c9 progress on reachability cache and raytracer tweaks 2021-05-17 01:53:16 -07:00
Leijurv
65a650c1a6 readme 2021-05-15 22:30:51 -07:00
Leijurv
ca2e7c958a remove zoomy branchy 2021-05-15 21:41:47 -07:00
Leijurv
c97b089c61 rework placeagainstdata 2021-05-13 00:37:40 -07:00
Leijurv
5170b34962 blip 2021-05-12 01:10:25 -07:00
Leijurv
0a8b6e9427 many more blocks, physics attempt 2021-05-11 23:27:52 -07:00
ZacSharp
253fbad3db Make MineProcess respect exploreForBlocks 2021-05-10 22:37:35 +02:00
Leijurv
efb0afcd6c switch to builder pattern for block state cached data 2021-05-10 02:18:59 -07:00
Leijurv
5c85d0cfd1 fixed up raytracer and placement option 2021-05-10 00:23:10 -07:00
ZacSharp
522de3d4b7 Add callback settings for toast pop-ups and desktop notifications 2021-05-10 00:31:37 +02:00
ZacSharp
dc9ae67657 Move NotificationHelper to api and give Helper some methods for notifications 2021-05-10 00:26:33 +02:00
Leijurv
fdf899eabf progress on raytracer 2021-04-21 11:42:56 -07:00
Leijurv
fccac8ed74 Merge pull request #2664 from ZacSharp/buildSelectedSchematic
Add setting to only build selected part of schematica schematic
2021-04-20 23:27:57 -07:00
Leijurv
24c895084e Merge pull request #2647 from scorbett123/patch-2
switch from forge mods to just other mods.
2021-04-20 23:15:37 -07:00
Leijurv
16fbfdecb5 Merge pull request #2645 from millennIumAMbiguity/patch-1
Added missing information to USAGE.md
2021-04-20 23:15:25 -07:00
Leijurv
b79e281074 Merge pull request #2635 from ZacSharp/autoTool
Invert disableAutoTool to autoTool
2021-04-20 23:15:10 -07:00
Leijurv
93ac380fd6 Merge pull request #2634 from ZacSharp/patch-2
fix typo
2021-04-20 23:13:57 -07:00
Leijurv
e62f480836 started on scaffolding 2021-04-16 20:43:24 -07:00
ZacSharp
46066d4cac Misc
* Rename schematicaOnlyBuildSelection to buildOnlySelection
* Make it useable with #build as well
* remove debugging code I forgot
2021-04-14 23:36:16 +02:00
Leijurv
6ef6651ed6 more optim 2021-04-13 19:08:44 -07:00
Leijurv
21aaab1b9e optims 2021-04-13 18:03:33 -07:00
Leijurv
8450675401 saving progress here, incremental scaffolding works and is fast 2021-04-13 16:08:30 -07:00
ZacSharp
c13cf4f29c Add setting to only build selected part of schematica schematic 2021-04-13 23:09:54 +02:00
ZacSharp
a4f06a9e1e Update javadoc as well 2021-04-10 23:56:30 +02:00
scorbett123
cbc5fc8d08 switch from forge mods to just other mods. 2021-04-08 18:36:46 +01:00
millennIumAMbiguity
0a27c0b6e4 Added missing information to USAGE.md
Added missing information about the `farm` command.
2021-04-07 18:12:47 +02:00
ZacSharp
f348a20035 invert disableAutoTool to autoTool 2021-04-04 03:34:10 +02:00
ZacSharp
bdb98b487f fix typo 2021-04-04 03:17:35 +02:00
Leijurv
2cb6402189 Update README.md 2021-04-03 16:56:26 -07:00
Leijurv
861bb7a00a v1.2.15 2021-04-03 16:50:01 -07:00
Leijurv
43d5458a25 fix weird behavior where allowBreak false broke #goto 2021-04-03 16:41:45 -07:00
Leijurv
96fd72e5b0 testing 2021-04-03 15:49:30 -07:00
Leijurv
fe491e4a7d Merge pull request #1809 from Moondarker/master
Added "install" target for instant maven-ready releases
2021-04-03 13:40:56 -07:00
Leijurv
c1794dd0ca reduce useless links 2021-03-25 10:41:16 -10:00
Leijurv
8f7d8b1cba Merge pull request #2611 from Flurrrr/master
Center badges and other stuff
2021-03-25 09:29:08 -10:00
flurr
8ebb8a04fa Center badges and other stuff
-center the badges
-re-arrange them to be more organized
-update the Aristois/Impact badges to the correct versions and fix broken link
2021-03-25 11:51:18 -07:00
Leijurv
ef6435a02a whatever i don't care 2021-03-23 22:44:20 -10:00
Leijurv
5cbe0a8230 Update README.md 2021-03-23 22:44:03 -10:00
Leijurv
dcef6b556c Update README.md 2021-03-23 22:42:39 -10:00
Leijurv
c07a636db2 Update README.md 2021-03-23 22:41:59 -10:00
Leijurv
a6be95fe33 Merge pull request #2606 from scorbett123/patch-1
fix typo
2021-03-23 10:19:11 -10:00
scorbett123
14149aa6b1 fix typo 2021-03-23 19:35:11 +00:00
Leijurv
ae901219fb Merge pull request #2526 from ZacSharp/buildSkipBlocks
Settings to alter block treatment while builing
2021-03-23 08:09:44 -10:00
Leijurv
6d9600d132 Update README.md 2021-03-21 13:59:00 -07:00
Leijurv
ccc55562cd Update README.md 2021-03-21 13:52:27 -07:00
ZacSharp
67d9ae881f don't allocate tons of empty lists 2021-03-09 12:31:02 +01:00
ZacSharp
61e0525ee8 Don't copy block state properties every time 2021-03-09 12:26:47 +01:00
ZacSharp
363519db02 treat all airs as air 2021-03-09 11:53:43 +01:00
Leijurv
4bc179bd2a Merge pull request #2462 from scorbett123/ModifiedImprovement
Improve #modified command to include original setting value.
2021-02-19 20:14:35 -08:00
Leijurv
76debda76e Merge pull request #1978 from ZacSharp/master
 Add improved ETA and a command to access it
2021-02-19 20:13:47 -08:00
Leijurv
b4eabe19cb Merge pull request #2482 from ZacSharp/schematicCacheClearing
Clear caches of schematics when moving them
2021-02-19 16:06:28 -08:00
ZacSharp
971b75860f Call reset() on static schematics as well 2021-02-19 23:57:33 +01:00
ZacSharp
5a926bf169 formatting 2021-02-19 23:52:18 +01:00
Leijurv
d34f37c563 Merge pull request #2476 from biscuut/patch-1
Fixed incorrect block pos world coordinates
2021-02-19 10:54:47 -08:00
Leijurv
6035a01019 Merge pull request #2481 from ZacSharp/buildRepeatCrash
Don't crash by buildRepeating too often
2021-02-19 10:49:06 -08:00
ZacSharp
c880f71dc8 Clear caches of schematics when moving them 2021-02-17 01:59:23 +01:00
ZacSharp
13422ef7ce lag > crash 2021-02-17 01:19:16 +01:00
Biscuit
84e2efd42c Fixed incorrect block pos world coordinates
Fix block pos being incorrect when converting chunk coordinates to world coordinates because of order of operations.
2021-02-15 14:21:03 -05:00
ZacSharp
113d26937d remove unused imports 2021-02-11 01:08:33 +01:00
ZacSharp
34606415d7 Don't mark skipped blocks as invalid just because they are far away 2021-02-10 23:45:12 +01:00
scorbett123
6b9c1f0a22 Improve #modified command to include original setting value.
Signed-off-by: scorbett123 <50634068+scorbett123@users.noreply.github.com>
2021-02-10 16:00:44 +00:00
ZacSharp
132cc0e131 make range initialization more readable 2021-02-10 10:25:17 +01:00
ZacSharp
5926369a56 formatting and comments 2021-02-10 10:15:14 +01:00
ZacSharp
ff068d374f Don't box doubles 2021-02-10 00:29:45 +01:00
Leijurv
4132d6ff61 Merge pull request #2138 from scorbett123/swordSaver
add useSwordToMine setting.
2021-02-08 16:54:37 -08:00
scorbett123
1f10199e64 Change use sword to mine to default to true.
Signed-off-by: scorbett123 <50634068+scorbett123@users.noreply.github.com>
2021-02-08 08:34:31 +00:00
Leijurv
e97704b37f Merge pull request #2139 from scorbett123/notifyOnDeath
notify users that their death position has been saved.
2021-02-07 14:32:43 -08:00
scorbett123
4ae81a3b0b Merge branch 'master' into swordSaver 2021-02-07 12:19:18 +00:00
Moondarker
f4ec9a8ec1 Merge pull request #2 from cabaletta/master
Fetching latest updates
2021-02-07 06:39:00 +03:00
Leijurv
711037b619 rearrange for consistency 2021-02-06 13:36:24 -08:00
Leijurv
b32c9f1724 Merge pull request #2107 from scorbett123/toolsSaver
add itemSaver setting.
2021-02-06 13:33:01 -08:00
Leijurv
be132c531e Update SETUP.md 2021-02-05 22:53:44 -08:00
Leijurv
fb8826caf4 Update README.md 2021-02-05 22:42:03 -08:00
Leijurv
adff60b118 Update README.md 2021-02-05 22:41:52 -08:00
Leijurv
8f863bf19b empty 2021-02-05 20:08:46 -08:00
ZacSharp
fd61207709 Fix heuristic(no args) returning wrong values 2021-01-28 01:40:33 +01:00
ZacSharp
8c1a9f460d Simple blockstate preservation for buildSubstitutes 2021-01-18 12:06:50 +01:00
ZacSharp
f6d4a315c7 Remove debug log 2021-01-18 11:58:42 +01:00
ZacSharp
fc1a2a6112 Add buildSubstitutes setting to builder 2021-01-17 02:45:40 +01:00
ZacSharp
13547781d2 add buildValidSubstitutes setting to builder 2021-01-17 00:53:50 +01:00
ZacSharp
d375d1abc9 Add setting parser for mappings 2021-01-17 00:16:34 +01:00
ZacSharp
5e4f31f39e proper place/break costs for skipped blocks 2021-01-16 02:05:59 +01:00
ZacSharp
dba186347c remove likely useless code breaking buildSkipBlocks 2021-01-13 22:45:59 +01:00
ZacSharp
7988274d61 add buildSkipBlocks setting 2021-01-12 23:59:11 +01:00
Sam Corbett
b9eeab06a1 change useSwordToMine to default to false.
Signed-off-by: Sam Corbett <sam@corbettchocolates.com>
2020-11-02 07:47:36 +00:00
scorbett123
c0f7d5ab44 fix error made while copying and pasting.
Signed-off-by: scorbett123 <sam@corbettchocolates.com>
2020-11-01 17:30:53 +00:00
scorbett123
60f81fb89d notify users that their death position has been saved.
Signed-off-by: scorbett123 <sam@corbettchocolates.com>
2020-11-01 17:18:31 +00:00
scorbett123
49bc96dc7e add useSwordToMine setting.
Signed-off-by: scorbett123 <sam@corbettchocolates.com>
2020-11-01 16:12:38 +00:00
Sam Corbett
9922382581 don't fall back onto the first slot if it has a low durability item in the slot.
Signed-off-by: Sam Corbett <sam@corbettchocolates.com>
2020-10-28 08:44:40 +00:00
scorbett123
4aa52d2f2e add itemSaver setting.
Signed-off-by: scorbett123 <sam@corbettchocolates.com>
2020-10-24 18:03:29 +01:00
ZacSharp
9393192036 Slight change to heuristic(no args) 2020-10-16 01:30:04 +02:00
btrekkie
5cfefbb9ec Merge pull request #2 from btrekkie/dependabot/maven/junit-junit-4.13.1
Bump junit from 4.12 to 4.13.1
2020-10-15 19:20:39 -04:00
btrekkie
1c8ccd4646 Merge pull request #1 from btrekkie/dependabot/maven/junit-junit-4.13.1
Bump junit from 4.12 to 4.13.1
2020-10-15 19:17:39 -04:00
ZacSharp
3cdbc4cb83 👌formatting in of comments 2020-10-15 21:48:17 +02:00
ZacSharp
0c7741120a 👌formatting 2020-10-15 00:05:54 +02:00
ZacSharp
e529438c7e Don't use Abs on Sqrt 2020-10-14 21:14:17 +02:00
dependabot[bot]
6f69316966 Bump junit from 4.12 to 4.13.1
Bumps [junit](https://github.com/junit-team/junit4) from 4.12 to 4.13.1.
- [Release notes](https://github.com/junit-team/junit4/releases)
- [Changelog](https://github.com/junit-team/junit4/blob/main/doc/ReleaseNotes4.12.md)
- [Commits](https://github.com/junit-team/junit4/compare/r4.12...r4.13.1)

Signed-off-by: dependabot[bot] <support@github.com>
2020-10-13 12:15:30 +00:00
dependabot[bot]
5e77ef97e4 Bump junit from 4.12 to 4.13.1
Bumps [junit](https://github.com/junit-team/junit4) from 4.12 to 4.13.1.
- [Release notes](https://github.com/junit-team/junit4/releases)
- [Changelog](https://github.com/junit-team/junit4/blob/main/doc/ReleaseNotes4.12.md)
- [Commits](https://github.com/junit-team/junit4/compare/r4.12...r4.13.1)

Signed-off-by: dependabot[bot] <support@github.com>
2020-10-13 12:15:26 +00:00
ZacSharp
aebfbba20e Also override heuristic(no args) when overriding isInGoal() 2020-09-20 22:39:48 +02:00
ZacSharp
85cc86346c Actually use Y and Z bounds for Y and Z 2020-09-20 22:20:24 +02:00
ZacSharp
b20e095683 add heuristic(no args) to GoalNear and GoalRunAway
not really a good solution but better than nothing
2020-09-20 00:29:31 +02:00
ZacSharp
b4d7f05165 🐛 fix two NPEs in estimatedTickToGoal
apparently `ctx.playerFeet()` and `startPosition` can be `null` before pathing the first time
2020-09-19 21:46:07 +02:00
ZacSharp
46a12754e9 🐛Reset ETA and return 0 if we are already there
not doing this caused a continuously increasing ETA when standing inside a `GoalNear` from `FollowProcess`
2020-09-19 00:30:25 +02:00
ZacSharp
411b2a0acc 🔨move ETA reset to it's own function 2020-09-18 23:58:06 +02:00
ZacSharp
7255ccbdaa add heuristic(no args) to GoalComposite
GoalNear and GoalRunAway are still missing it
2020-09-06 16:42:05 +02:00
ZacSharp
45dc8b949d add a method to get the heuristic at the goal
this alows the ETA to work with goals not ending with a heuristic of 0

GoalComposite, GoalRunAway and GoalNear are still missing
2020-09-05 22:32:38 +02:00
ZacSharp
10e3a5afc4 negative ETAs are actually impossible now 2020-09-05 22:13:05 +02:00
ZacSharp
303aa79ffb Update ETA formula, assuming heuritic at goal is 0 2020-09-04 23:56:01 +02:00
ZacSharp
35f3be9296 get rid of negative ETAs the lazy way 2020-09-02 22:59:13 +02:00
ZacSharp
695954bdb0 no eta after 0 ticks or with division by 0 2020-08-28 23:37:23 +02:00
ZacSharp
d29b3ee893 Fix copy/paste mistake in ETACommand.getLongDesc() 2020-08-28 01:19:06 +02:00
ZacSharp
d9cecb35cb rename Eta to ETA 2020-08-28 00:56:18 +02:00
ZacSharp
71dd6c6333 👌formatting 2020-08-27 01:16:17 +02:00
ZacSharp
56f13d314a Add eta command 2020-08-26 23:53:02 +02:00
ZacSharp
b628d67961 Add ETA for full path 2020-08-26 23:52:44 +02:00
William Jacobs
0fed0d4371 Removed outdated pointer to documentation
This removes the suggestion to check the source code for the documentation from README.md. Now that there is a website with the documentation, there is no need to look through the source code.
2020-06-26 10:51:11 -04:00
Moondarker
8fe05c9119 fixup! Allow multi-artifact building for Jitpack and local Maven repos 2020-06-20 23:16:20 +03:00
Moondarker
e8e6a9bc5c Allow multi-artifact building for Jitpack and local Maven repos 2020-06-20 23:11:16 +03:00
William Jacobs
1069dc9700 Increased minimum Java version and updated documentation
This increases the minimum Java version to 7.0, because Maven doesn't like Java 6.0, and it makes assorted improvements to the documentation.
2020-05-30 10:36:45 -04:00
William Jacobs
515a28dcfd Added reference to documentation webpage
This changes README.md to refer to the webpage containing the documentation for the project.
2020-05-30 10:33:13 -04:00
William Jacobs
426838f33e Added optimization_ideas.txt
This adds optimization_ideas.txt, a text file containing my thoughts on how to make ConnGraph faster.
2019-03-23 20:06:47 -04:00
William Jacobs
458cdd3ddd Fixed size calculation in optimizeForestEdges()
This fixes the code in ConnGraph.optimizeForestEdges() that computes the resulting size after combining two Euler tour trees into one. Technically, the old behavior was still correct, but only by accident; this change is still kind of a bug fix.
2019-03-18 13:21:26 -04:00
William Jacobs
05d678a189 Split removeEdge and optimize() into smaller methods
This moves a bit of functionality from ConnGraph.removeEdge and ConnGraph.optimize() into new methods, in order to improve readability.
2019-03-16 14:42:36 -04:00
William Jacobs
454a2b8ae7 Minor readability improvements
This makes various minor improvements to readability and implementation.
2019-03-16 14:17:40 -04:00
William Jacobs
1536209eb3 Optimization: don't push forest edges if there are no non-forest edges
This optimizes the process of searching for a replacement edge to refrain from pushing forest edges down when a tree does not have any same-level non-forest edges. If a tree doesn't have any such edges, then there definitely isn't a replacement edge at that level, so we can avoid doing extra work.

Interestingly, with this optimization, ConnGraph reduces to a single Euler tour forest if the graph is a forest, with addEdge and removeEdge taking O(log N) (non-amortized) time with high probability.
2019-03-06 22:24:50 -05:00
William Jacobs
a60eac5b6c Initial commit
This adds the initial contents of the repository.
2019-03-06 16:46:45 -05:00
btrekkie
7d95d5f991 Initial commit 2019-03-06 16:36:08 -05:00
William Jacobs
8cf1eb9230 Converted to Maven project
This changes RedBlackNode into a Maven project, by adding pom.xml and changing the directory structure. This should make it easier for other projects to include RedBlackNode as a dependency.
2019-03-06 15:20:01 -05:00
William Jacobs
148e9247d3 Clarified main documentation
This changes README.md and the comment for RedBlackNode to more clearly explain what the project is all about. It emphasizes the fact that RedBlackNode provides public access to the tree's structure. It changes the usage example in README.md from a short RedBlackNode subclass highlighting how easy augmentation is to a medium-length pair of tree and node classes that show how to use insertion, removal, and augmentation.

This change also makes minor improvements to comments for RedBlackNode methods.
2019-03-04 17:01:46 -05:00
Bill Jacobs
6d6b968fd6 Simplified if statements
This simplifies this:

if (a) {
    c;
}
if (b) {
    c;
}

to this:

if (a || b) {
    c;
}
2017-05-13 18:19:03 -05:00
Bill Jacobs
3c75a5e39e Moved TreeList.addAll test to TreeListTest.testAddAll
This moves some code that tests TreeList.addAll from TreeListTest.testAdd to TreeListTest.testAddAll
2016-07-22 17:13:19 -07:00
Bill Jacobs
9534c4ae06 Fixed RedBlackNode.concatenate on two one-node trees
This fixes RedBlackNode.concatenate to work on two one-node trees.  The check for determining which tree had the greater red-black height was incorrect in that case.
2016-07-22 17:03:36 -07:00
Bill Jacobs
5e23bd9c81 Changed SubArrayMinTest to use Integer.bitCount
This changes SubArrayMinTest to use the library method Integer.bitCount rather than a hand-rolled bit counting implementation.
2016-06-24 14:46:37 -07:00
Bill Jacobs
91b5ae633a Fixed SubArrayMin to check children
This fixes SubArrayMin to check the appropriate children of the endpoint nodes, in addition to the children of their ancestors.
2016-06-17 21:54:19 -07:00
Bill Jacobs
8c98d5cc42 Changed createTree to set root's parent to null
This changes createTree to set the "parent" field of the root node to null.
2016-06-06 14:55:58 -07:00
Bill Jacobs
cd4424b94a Fixed calls to augment() in fixSiblingDeletion()
This fixes fixSiblingDeletion() to call augment() in certain cases where augment() returns false.
2016-06-02 20:40:35 -07:00
Bill Jacobs
deb397ed1e Changed fixInsertion to always augment parent
This changes fixInsertion to always augment the node's parent, even if the initial call to augment() returns false, assuming "augment" is true.  When we insert a node, we are supposed to ignore its initial state; thus, we ignore the return value of augment().
2016-05-31 23:07:52 -07:00
Bill Jacobs
4e1fe67095 Updated RedBlackNode.jar to clear links after removing node
This updates RedBlackNode.jar to include the changes in dd0fd1959c.
2016-05-28 17:35:32 -07:00
Bill Jacobs
dd0fd1959c Clear links after removing node
This changes the remove methods to set the parent and child links to be null, so that we're more likely to encounter an exception if we attempt to access the node.
2016-05-28 10:56:08 -07:00
Bill Jacobs
a1dba8a58c Added test for "lca"
This adds SubArrayMinTest, which tests RedBlackNode.lca.
2016-05-26 14:47:27 -07:00
Bill Jacobs
f355e1ed2b Added LCA
This adds a RedBlackNode method for computing the lowest common ancestor of two nodes.
2016-05-26 12:22:32 -07:00
Bill Jacobs
82c1fbdc7b Made fixInsertion return the new root
This changes RedBlackNode.fixInsertion to return the root of the resulting tree.
2016-05-26 10:37:25 -07:00
Bill Jacobs
f7f14ff852 Added RedBlackNode.jar
This adds RedBlackNode.jar for using RedBlackNode in binary form.
2016-05-23 14:00:40 -07:00
Bill Jacobs
754e11ade7 Escaped "<" and ">"
This escapes the "<" and ">" characters in README.md.
2016-05-23 13:57:28 -07:00
Bill Jacobs
389f8ea4e2 Initial commit
This adds the initial contents of the repository.
2016-05-23 13:51:21 -07:00
btrekkie
d3e6f9ae02 Initial commit 2016-05-23 13:43:59 -07:00
145 changed files with 16192 additions and 203 deletions

View File

@@ -11,7 +11,7 @@ Operating system:
Java version:
Minecraft version:
Baritone version:
Forge mods (if used):
Other mods (if used):
## Exception, error or logs
Please find your `latest.log` or `debug.log` in this folder and attach it to the issue

39
.github/workflows/gradle_build.yml vendored Normal file
View File

@@ -0,0 +1,39 @@
# This workflow will build a Java project with Gradle
# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle
name: Java CI with Gradle
on:
push:
pull_request:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up JDK 8
uses: actions/setup-java@v2
with:
java-version: '8'
distribution: 'adopt'
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Build with Gradle
run: ./gradlew build
- name: Archive Artifacts
uses: actions/upload-artifact@v2
with:
name: Artifacts
path: dist/
- name: Archive mapping.txt
uses: actions/upload-artifact@v2
with:
name: Mappings
path: build/tmp/proguard/mapping.txt

26
.github/workflows/run_tests.yml vendored Normal file
View File

@@ -0,0 +1,26 @@
name: Tests
on:
push:
pull_request:
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up JDK 9
uses: actions/setup-java@v2
with:
java-version: '8'
distribution: 'adopt'
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Executing tests
run: ./gradlew test

6
.gitignore vendored
View File

@@ -18,6 +18,12 @@ classes/
*.iws
/logs/
# Eclipse Files
.classpath
.project
.settings/
baritone_Client.launch
# Copyright Files
!/.idea/copyright/Baritone.xml
!/.idea/copyright/profiles_settings.xml

View File

@@ -1,28 +0,0 @@
language: java
sudo: required
services:
- docker
install:
- travis_retry docker build -t cabaletta/baritone .
script:
- docker run --name baritone cabaletta/baritone ./gradlew javadoc
- docker cp baritone:/code/dist dist
- ls dist
- cat dist/checksums.txt
deploy:
provider: releases
api_key:
secure: YOuiXoJNpB4bW89TQoY2IGXg0tqOKls55YMXsSPU6Mx8WzRu8CjjO/A8KA9nGfNrKM+NucjiKr/h53O2Dp2uyy0i0SLvav/G0MaBMeB1NlPRwFopi6tVPNaoZsvr8NW4BIURhspckYLpOTYWnfmOkIv8q7AxrjUZWPKDlq0dte20UxEqUE6msHJ7U9XlKo/4fX40kvWMfwGI2hTyAtL0cRT1QPsd+uW3OQjAPcQj+jKaWld46V8pBK8g9Qde9mo8HC9NBv97zw1bBF1EFkynW569kElHvaS2Opl2QLGaf66guDbpnqDpGHMhQrDdxsZHJ4RksyITn+8A9UArmbkU35BxKqBeQqOWxod2+M0axdLh1pvX43Q1t9n7RiZBf7GvV8vkXL5Sjf8v6Y4LqkJGhvQkTUwpH+0knwrE761DMCtBC34AiWG70D4u7msmhurkflr9kmRHSj/3lyJ1Q2lkt8L+FOAlQBVs64vXTsfgc6Yge7N0O3UD5hCkrDNoz3BzhNBdCkbdxdKCGip71UZgUNkPy9o3ui8jATNj9ypx3+U8ovqP0XWlJqUZmyeXyNGW9NrLeCkRLTlLnZ/dv6OPONa1oAu4TwF1w5A+TGRFZcZjH/PnZKZDQ1OYQOR6drLKRYdr2unvuf5KUKUGqZ7aYtLGhP0rBvGWddRV7DSmX/s=
all_branches: true
file_glob: true
file:
- dist/*
skip_cleanup: true
on:
tags: true
repo: cabaletta/baritone

View File

@@ -1,49 +1,63 @@
# Baritone
[![HitCount](http://hits.dwyl.com/cabaletta/baritone.svg)](http://hits.dwyl.com/cabaletta/baritone/)
[![GitHub All Releases](https://img.shields.io/github/downloads/cabaletta/baritone/total.svg)](https://github.com/cabaletta/baritone/releases/)
<p align="center">
<a href="https://github.com/cabaletta/baritone/releases/"><img src="https://img.shields.io/github/downloads/cabaletta/baritone/total.svg" alt="GitHub All Releases"/></a>
</p>
[![Build Status](https://travis-ci.com/cabaletta/baritone.svg?branch=master)](https://travis-ci.com/cabaletta/baritone/)
[![Release](https://img.shields.io/github/release/cabaletta/baritone.svg)](https://github.com/cabaletta/baritone/releases/)
[![License](https://img.shields.io/badge/license-LGPL--3.0%20with%20anime%20exception-green.svg)](LICENSE)
[![Codacy Badge](https://api.codacy.com/project/badge/Grade/a73d037823b64a5faf597a18d71e3400)](https://www.codacy.com/app/leijurv/baritone?utm_source=github.com&amp;utm_medium=referral&amp;utm_content=cabaletta/baritone&amp;utm_campaign=Badge_Grade)
[![Minecraft](https://img.shields.io/badge/MC-1.12.2-brightgreen.svg)](https://github.com/cabaletta/baritone/tree/master/)
[![Minecraft](https://img.shields.io/badge/MC-1.13.2-brightgreen.svg)](https://github.com/cabaletta/baritone/tree/1.13.2/)
[![Minecraft](https://img.shields.io/badge/MC-1.14.4-brightgreen.svg)](https://github.com/cabaletta/baritone/tree/1.14.4/)
[![Minecraft](https://img.shields.io/badge/MC-1.15.2-brightgreen.svg)](https://github.com/cabaletta/baritone/tree/1.15.2/)
[![Minecraft](https://img.shields.io/badge/MC-1.16.4-brightgreen.svg)](https://github.com/cabaletta/baritone/tree/1.16.4/)
[![Code of Conduct](https://img.shields.io/badge/%E2%9D%A4-code%20of%20conduct-blue.svg?style=flat)](https://github.com/cabaletta/baritone/blob/master/CODE_OF_CONDUCT.md)
[![Known Vulnerabilities](https://snyk.io/test/github/cabaletta/baritone/badge.svg?targetFile=build.gradle)](https://snyk.io/test/github/cabaletta/baritone?targetFile=build.gradle)
[![Contributions welcome](https://img.shields.io/badge/contributions-welcome-brightgreen.svg?style=flat)](https://github.com/cabaletta/baritone/issues/)
[![Issues](https://img.shields.io/github/issues/cabaletta/baritone.svg)](https://github.com/cabaletta/baritone/issues/)
[![GitHub issues-closed](https://img.shields.io/github/issues-closed/cabaletta/baritone.svg)](https://github.com/cabaletta/baritone/issues?q=is%3Aissue+is%3Aclosed)
[![Pull Requests](https://img.shields.io/github/issues-pr/cabaletta/baritone.svg)](https://github.com/cabaletta/baritone/pulls/)
![Code size](https://img.shields.io/github/languages/code-size/cabaletta/baritone.svg)
![GitHub repo size](https://img.shields.io/github/repo-size/cabaletta/baritone.svg)
![Lines of Code](https://tokei.rs/b1/github/cabaletta/baritone?category=code)
[![GitHub contributors](https://img.shields.io/github/contributors/cabaletta/baritone.svg)](https://github.com/cabaletta/baritone/graphs/contributors/)
[![GitHub commits](https://img.shields.io/github/commits-since/cabaletta/baritone/v1.0.0.svg)](https://github.com/cabaletta/baritone/commit/)
[![Impact integration](https://img.shields.io/badge/Impact%20integration-v1.2.14%20/%20v1.3.8%20/%20v1.4.6%20/%20v1.5.3-brightgreen.svg)](https://impactclient.net/)
[![KAMI Blue integration](https://img.shields.io/badge/KAMI%20Blue%20integration-v1.2.14--master-green)](https://github.com/kami-blue/client)
[![ForgeHax integration](https://img.shields.io/badge/ForgeHax%20%22integration%22-scuffed-yellow.svg)](https://github.com/fr1kin/ForgeHax/)
[![Aristois add-on integration](https://img.shields.io/badge/Aristois%20add--on%20integration-v1.3.4%20/%20v1.4.1-green.svg)](https://gitlab.com/emc-mods-indrit/baritone_api)
[![rootNET integration](https://img.shields.io/badge/rootNET%20integration-v1.2.14-green.svg)](https://rootnet.dev/)
[![Future integration](https://img.shields.io/badge/Future%20integration-v1.2.12%20%2F%20v1.3.6%20%2F%20v1.4.4-red)](https://futureclient.net/)
[![RusherHack integration](https://img.shields.io/badge/RusherHack%20integration-v1.2.14-green)](https://rusherhack.org/)
[![forthebadge](https://forthebadge.com/images/badges/built-with-swag.svg)](http://forthebadge.com/)
[![forthebadge](https://forthebadge.com/images/badges/mom-made-pizza-rolls.svg)](http://forthebadge.com/)
<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"/>
</p>
A Minecraft pathfinder bot.
<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&amp;utm_medium=referral&amp;utm_content=cabaletta/baritone&amp;utm_campaign=Badge_Grade"><img src="https://api.codacy.com/project/badge/Grade/a73d037823b64a5faf597a18d71e3400" 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>
<a href="https://github.com/cabaletta/baritone/issues/"><img src="https://img.shields.io/github/issues/cabaletta/baritone.svg" alt="Issues"/></a>
<a href="https://github.com/cabaletta/baritone/issues?q=is%3Aissue+is%3Aclosed"><img src="https://img.shields.io/github/issues-closed/cabaletta/baritone.svg" alt="GitHub issues-closed"/></a>
<a href="https://github.com/cabaletta/baritone/pulls/"><img src="https://img.shields.io/github/issues-pr/cabaletta/baritone.svg" alt="Pull Requests"/></a>
<a href="https://github.com/cabaletta/baritone/graphs/contributors/"><img src="https://img.shields.io/github/contributors/cabaletta/baritone.svg" alt="GitHub contributors"/></a>
<a href="https://github.com/cabaletta/baritone/commit/"><img src="https://img.shields.io/github/commits-since/cabaletta/baritone/v1.0.0.svg" alt="GitHub commits"/></a>
<img src="https://img.shields.io/github/languages/code-size/cabaletta/baritone.svg" alt="Code size"/>
<img src="https://img.shields.io/github/repo-size/cabaletta/baritone.svg" alt="GitHub repo size"/>
<img src="https://tokei.rs/b1/github/cabaletta/baritone?category=code" alt="Lines of Code"/>
</p>
<p align="center">
<a href="https://impactclient.net/"><img src="https://img.shields.io/badge/Impact%20integration-v1.2.14%20/%20v1.3.8%20/%20v1.4.6%20/%20v1.5.3%20/%20v1.6.3-brightgreen.svg" alt="Impact integration"/></a>
<a href="https://github.com/kami-blue/client"><img src="https://img.shields.io/badge/KAMI%20Blue%20integration-v1.2.14--master-green" alt="KAMI Blue integration"/></a>
<a href="https://github.com/fr1kin/ForgeHax/"><img src="https://img.shields.io/badge/ForgeHax%20%22integration%22-scuffed-yellow.svg" alt="ForgeHax integration"/></a>
<a href="https://aristois.net/"><img src="https://img.shields.io/badge/Aristois%20add--on%20integration-v1.6.3-green.svg" alt="Aristois add-on integration"/></a>
<a href="https://rootnet.dev/"><img src="https://img.shields.io/badge/rootNET%20integration-v1.2.14-green.svg" alt="rootNET integration"/></a>
<a href="https://futureclient.net/"><img src="https://img.shields.io/badge/Future%20integration-v1.2.12%20%2F%20v1.3.6%20%2F%20v1.4.4-red" alt="Future integration"/></a>
<a href="https://rusherhack.org/"><img src="https://img.shields.io/badge/RusherHack%20integration-v1.2.14-green" alt="RusherHack integration"/></a>
</p>
<p align="center">
<a href="http://forthebadge.com/"><img src="https://forthebadge.com/images/badges/built-with-swag.svg" alt="forthebadge"/></a>
<a href="http://forthebadge.com/"><img src="https://forthebadge.com/images/badges/mom-made-pizza-rolls.svg" alt="forthebadge"/></a>
</p>
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)
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.14/baritone-api-forge-1.2.14.jar)**. Otherwise, see [Installation & setup](SETUP.md). Once Baritone is installed, look [here](USAGE.md) for instructions on how to use it.
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.4, [click here](https://www.youtube.com/watch?v=_4eVJ9Qz2J8) and see description. If you need Forge 1.16.4, look [here](https://github.com/cabaletta/baritone/releases/tag/v1.6.2) 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)**.
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.4. 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 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).
Have committed at least once a day from Aug 1, 2018, to Aug 1, 2019.

View File

@@ -2,7 +2,7 @@
The easiest way to install Baritone is to install [Impact](https://impactclient.net/), which comes with Baritone.
You can also use a custom version json for Minecraft, with the [1.14.4](https://www.dropbox.com/s/rkml3hjokd3qv0m/1.14.4-Baritone.zip?dl=1) version or the [1.15.2](https://www.dropbox.com/s/8rx6f0kts9hvd4f/1.15.2-Baritone.zip?dl=1) version
You can also use a custom version json for Minecraft, with the [1.14.4](https://www.dropbox.com/s/rkml3hjokd3qv0m/1.14.4-Baritone.zip?dl=1) version or the [1.15.2](https://www.dropbox.com/s/8rx6f0kts9hvd4f/1.15.2-Baritone.zip?dl=1) version or the [1.16.5](https://www.dropbox.com/s/i6f292o2i7o9acp/1.16.5-Baritone.zip?dl=1) version.
Once Baritone is installed, look [here](USAGE.md) for instructions on how to use it.
@@ -11,9 +11,9 @@ 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.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)
Any official release will be GPG signed by leijurv (44A3EA646EADAC6A) and ZeroMemes (73A788379A197567). Please verify that the hash of the file you download is in `checksums.txt` and that `checksums_signed.asc` is a valid signature by those two public keys of `checksums.txt`.
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`.
The build is fully deterministic and reproducible, and you can verify Travis did it properly by running `docker build --no-cache -t cabaletta/baritone .` yourself and comparing the shasum. This works identically on Travis, Mac, and Linux (if you have docker on Windows, I'd be grateful if you could let me know if it works there too).
@@ -32,11 +32,6 @@ If another one of your Forge mods has a Baritone integration, you want `baritone
- **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.
- **Unoptimized**: Nothing is obfuscated. This shouldn't be used ever in production.
## More Info
To replace out Impact 4.5's Baritone build with a customized one, build Baritone as above then copy & **rename** `dist/baritone-api-$VERSION$.jar` into `minecraft/libraries/cabaletta/baritone-api/1.2/baritone-api-1.2.jar`, replacing the jar that was previously there. You also need to edit `minecraft/versions/1.12.2-Impact_4.5/1.12.2-Impact_4.5.json`, find the line `"name": "cabaletta:baritone-api:1.2"`, remove the comma from the end, and **entirely remove the NEXT line** (starts with `"url"`). **Restart your launcher** then load as normal.
You can verify whether or not it worked by running `.b version` in chat (only valid in Impact). It should print out the version that you downloaded. Note: The version that comes with 4.5 is `v1.2.3`.
## Build it yourself
- Clone or download Baritone

View File

@@ -47,7 +47,7 @@ Some common examples:
- `build` to build a schematic. `build blah.schematic` will load `schematics/blah.schematic` and build it with the origin being your player feet. `build blah.schematic x y z` to set the origin. Any of those can be relative to your player (`~ 69 ~-420` would build at x=player x, y=69, z=player z-420).
- `schematica` to build the schematic that is currently open in schematica
- `tunnel` to dig and make a tunnel, 1x2. It will only deviate from the straight line if necessary such as to avoid lava. For a dumber tunnel that is really just cleararea, you can `tunnel 3 2 100`, to clear an area 3 high, 2 wide, and 100 deep.
- `farm` to automatically harvest, replant, or bone meal crops
- `farm` to automatically harvest, replant, or bone meal crops. Use `farm <range>` or `farm <range> <waypoint>` to limit the max distance from the starting point or a waypoint.
- `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.

View File

@@ -16,7 +16,7 @@
*/
group 'baritone'
version '1.2.14'
version '1.2.15'
buildscript {
repositories {
@@ -42,6 +42,7 @@ import baritone.gradle.task.CreateDistTask
import baritone.gradle.task.ProguardTask
apply plugin: 'java'
apply plugin: 'maven'
apply plugin: 'net.minecraftforge.gradle.tweaker-client'
apply plugin: 'org.spongepowered.mixin'
@@ -144,3 +145,25 @@ task proguard(type: ProguardTask) {
task createDist(type: CreateDistTask, dependsOn: proguard)
build.finalizedBy(createDist)
install {
def jarApiName = String.format("%s-api-%s", rootProject.name, version.toString())
def jarApiForgeName = String.format("%s-api-forge-%s", rootProject.name, version.toString())
def jarSAName = String.format("%s-standalone-%s", rootProject.name, version.toString())
def jarSAForgeName = String.format("%s-standalone-forge-%s", rootProject.name, version.toString())
artifacts {
archives file("$buildDir/libs/"+jarApiName+".jar")
archives file("$buildDir/libs/"+jarApiForgeName+".jar")
archives file("$buildDir/libs/"+jarSAName+".jar")
archives file("$buildDir/libs/"+jarSAForgeName+".jar")
}
repositories.mavenInstaller {
addFilter('api') { artifact, file -> artifact.name == "baritone-api" }
addFilter('api-forge') { artifact, file -> artifact.name == "baritone-api-forge" }
addFilter('standalone') { artifact, file -> artifact.name == "baritone-standalone" }
addFilter('standalone-forge') { artifact, file -> artifact.name == "baritone-standalone-forge" }
}
}
install.dependsOn(build)

View File

@@ -81,7 +81,7 @@
-libraryjars 'tempLibraries/netty-all-4.1.9.Final.jar'
-libraryjars 'tempLibraries/oshi-core-1.1.jar'
-libraryjars 'tempLibraries/patchy-1.1.jar'
-libraryjars 'tempLibraries/patchy-1.2.jar'
-libraryjars 'tempLibraries/platform-3.4.0.jar'
-libraryjars 'tempLibraries/realms-1.10.22.jar'
-libraryjars 'tempLibraries/soundsystem-20120107.jar'
@@ -381,3 +381,5 @@
public java.lang.String substring(int);
public java.lang.String substring(int,int);
}
-printmapping mapping.txt

View File

@@ -17,8 +17,10 @@
package baritone.api;
import baritone.api.utils.NotificationHelper;
import baritone.api.utils.SettingsUtil;
import baritone.api.utils.TypeUtils;
import baritone.api.utils.gui.BaritoneToast;
import net.minecraft.block.Block;
import net.minecraft.client.Minecraft;
import net.minecraft.init.Blocks;
@@ -33,6 +35,7 @@ import java.lang.reflect.Type;
import java.util.List;
import java.util.*;
import java.util.function.Consumer;
import java.util.function.BiConsumer;
/**
* Baritone's settings. Settings apply to all Baritone instances.
@@ -70,9 +73,9 @@ public final class Settings {
public final Setting<Boolean> assumeExternalAutoTool = new Setting<>(false);
/**
* If this setting is on, no auto tool will occur at all, not at calculation time nor execution time
* Automatically select the best available tool
*/
public final Setting<Boolean> disableAutoTool = new Setting<>(false);
public final Setting<Boolean> autoTool = new Setting<>(true);
/**
* It doesn't actually take twenty ticks to place a block, this cost is so high
@@ -206,6 +209,29 @@ public final class Settings {
)));
/**
* A list of blocks to be treated as correct.
* <p>
* If a schematic asks for any block on this list at a certain position, it will be treated as correct, regardless of what it currently is.
*/
public final Setting<List<Block>> buildSkipBlocks = new Setting<>(new ArrayList<>(Arrays.asList(
)));
/**
* 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
*/
public final Setting<Map<Block, List<Block>>> buildValidSubstitutes = new Setting<>(new HashMap<>());
/**
* 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
*/
public final Setting<Map<Block, List<Block>>> buildSubstitutes = new Setting<>(new HashMap<>());
/**
* A list of blocks to become air
* <p>
@@ -572,6 +598,12 @@ public final class Settings {
*/
public final Setting<Boolean> renderGoal = new Setting<>(true);
/**
* Render the goal as a sick animated thingy instead of just a box
* (also controls animation of GoalXZ if {@link #renderGoalXZBeacon} is enabled)
*/
public final Setting<Boolean> renderGoalAnimated = new Setting<>(true);
/**
* Render selection boxes
*/
@@ -641,7 +673,7 @@ public final class Settings {
/**
* When GetToBlockProcess or MineProcess fails to calculate a path, instead of just giving up, mark the closest instance
* of that block as "unreachable" and go towards the next closest. GetToBlock expands this seaarch to the whole "vein"; MineProcess does not.
* of that block as "unreachable" and go towards the next closest. GetToBlock expands this search to the whole "vein"; MineProcess does not.
* This is because MineProcess finds individual impossible blocks (like one block in a vein that has gravel on top then lava, so it can't break)
* Whereas GetToBlock should blacklist the whole "vein" if it can't get to any of them.
*/
@@ -696,6 +728,16 @@ public final class Settings {
*/
public final Setting<Boolean> censorRanCommands = new Setting<>(false);
/**
* Stop using tools just before they are going to break.
*/
public final Setting<Boolean> itemSaver = new Setting<>(false);
/**
* Durability to leave on the tool when using itemSaver
*/
public final Setting<Integer> itemSaverThreshold = new Setting<>(10);
/**
* Always prefer silk touch tools over regular tools. This will not sacrifice speed, but it will always prefer silk
* touch tools over other tools of the same speed. This includes always choosing ANY silk touch tool over your hand.
@@ -756,7 +798,7 @@ public final class Settings {
public final Setting<Integer> allowOnlyExposedOresDistance = new Setting<>(1);
/**
* When GetToBlock doesn't know any locations for the desired block, explore randomly instead of giving up.
* When GetToBlock or non-legit Mine doesn't know any locations for the desired block, explore randomly instead of giving up.
*/
public final Setting<Boolean> exploreForBlocks = new Setting<>(true);
@@ -808,6 +850,11 @@ public final class Settings {
*/
public final Setting<Boolean> layerOrder = new Setting<>(false);
/**
* How high should the individual layers be?
*/
public final Setting<Integer> layerHeight = new Setting<>(1);
/**
* Start building the schematic at a specific layer.
* Can help on larger builds when schematic wants to break things its already built
@@ -819,6 +866,11 @@ public final class Settings {
*/
public final Setting<Boolean> skipFailedLayers = new Setting<>(false);
/**
* Only build the selected part of schematics
*/
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
*/
@@ -829,6 +881,13 @@ public final class Settings {
*/
public final Setting<Integer> buildRepeatCount = new Setting<>(-1);
/**
* Don't notify schematics that they are moved.
* e.g. replacing will replace the same spots for every repetition
* Mainly for backward compatibility.
*/
public final Setting<Boolean> buildRepeatSneaky = new Setting<>(true);
/**
* Allow standing above a block while mining it, in BuilderProcess
* <p>
@@ -937,6 +996,7 @@ public final class Settings {
* Disallow MineBehavior from using X-Ray to see where the ores are. Turn this option on to force it to mine "legit"
* where it will only mine an ore once it can actually see it, so it won't do or know anything that a normal player
* couldn't. If you don't want it to look like you're X-Raying, turn this on
* This will always explore, regardless of exploreForBlocks
*/
public final Setting<Boolean> legitMine = new Setting<>(false);
@@ -1023,6 +1083,20 @@ public final class Settings {
*/
public final Setting<Consumer<ITextComponent>> logger = new Setting<>(Minecraft.getMinecraft().ingameGUI.getChatGUI()::printChatMessage);
/**
* The function that is called when Baritone will send a desktop notification. This function can be added to
* via {@link Consumer#andThen(Consumer)} or it can completely be overriden via setting
* {@link Setting#value};
*/
public final Setting<BiConsumer<String, Boolean>> notifier = new Setting<>(NotificationHelper::notify);
/**
* The function that is called when Baritone will show a toast. This function can be added to
* via {@link Consumer#andThen(Consumer)} or it can completely be overriden via setting
* {@link Setting#value};
*/
public final Setting<BiConsumer<ITextComponent, ITextComponent>> toaster = new Setting<>(BaritoneToast::addOrUpdate);
/**
* The size of the box that is rendered when the current goal is a GoalYLevel
*/
@@ -1113,6 +1187,11 @@ public final class Settings {
*/
public final Setting<Boolean> renderSelectionCorners = new Setting<>(true);
/**
* Use sword to mine.
*/
public final Setting<Boolean> useSwordToMine = new Setting<>(true);
/**
* Desktop notifications
*/

View File

@@ -58,6 +58,15 @@ public interface IPathingBehavior extends IBehavior {
return Optional.of(current.getPath().ticksRemainingFrom(start));
}
/**
* Returns the estimated remaining ticks to the current goal.
* Given that the return type is an optional, {@link Optional#empty()}
* will be returned in the case that there is no current goal.
*
* @return The estimated remaining ticks to the current goal.
*/
Optional<Double> estimatedTicksToGoal();
/**
* @return The current pathing goal
*/

View File

@@ -253,8 +253,8 @@ public class TabCompleteHelper {
public TabCompleteHelper addSettings() {
return append(
BaritoneAPI.getSettings().allSettings.stream()
.filter(s -> !SettingsUtil.javaOnlySetting(s))
.map(Settings.Setting::getName)
.filter(s -> !s.equalsIgnoreCase("logger"))
.sorted(String.CASE_INSENSITIVE_ORDER)
);
}

View File

@@ -54,4 +54,18 @@ public interface Goal {
default double heuristic(BlockPos pos) {
return heuristic(pos.getX(), pos.getY(), pos.getZ());
}
/**
* Returns the heuristic at the goal.
* i.e. {@code heuristic() == heuristic(x,y,z)}
* when {@code isInGoal(x,y,z) == true}
* This is needed by {@code PathingBehavior#estimatedTicksToGoal} because
* some Goals actually do not have a heuristic of 0 when that condition is met
*
* @return The estimate number of ticks to satisfy the goal when the goal
* is already satisfied
*/
default double heuristic() {
return 0;
}
}

View File

@@ -57,6 +57,16 @@ public class GoalComposite implements Goal {
return min;
}
@Override
public double heuristic() {
double min = Double.MAX_VALUE;
for (Goal g : goals) {
// just take the highest value that is guaranteed to be inside the goal
min = Math.min(min, g.heuristic());
}
return min;
}
@Override
public String toString() {
return "GoalComposite" + Arrays.toString(goals);

View File

@@ -45,6 +45,11 @@ public class GoalInverted implements Goal {
return -origin.heuristic(x, y, z);
}
@Override
public double heuristic() {
return Double.NEGATIVE_INFINITY;
}
@Override
public String toString() {
return String.format("GoalInverted{%s}", origin.toString());

View File

@@ -19,6 +19,8 @@ package baritone.api.pathing.goals;
import baritone.api.utils.SettingsUtil;
import baritone.api.utils.interfaces.IGoalRenderPos;
import it.unimi.dsi.fastutil.doubles.DoubleOpenHashSet;
import it.unimi.dsi.fastutil.doubles.DoubleIterator;
import net.minecraft.util.math.BlockPos;
public class GoalNear implements Goal, IGoalRenderPos {
@@ -51,6 +53,34 @@ public class GoalNear implements Goal, IGoalRenderPos {
return GoalBlock.calculate(xDiff, yDiff, zDiff);
}
@Override
public double heuristic() {// TODO less hacky solution
int range = (int) Math.ceil(Math.sqrt(rangeSq));
DoubleOpenHashSet maybeAlwaysInside = new DoubleOpenHashSet(); // see pull request #1978
double minOutside = Double.POSITIVE_INFINITY;
for (int dx = -range; dx <= range; dx++) {
for (int dy = -range; dy <= range; dy++) {
for (int dz = -range; dz <= range; dz++) {
double h = heuristic(x + dx, y + dy, z + dz);
if (h < minOutside && isInGoal(x + dx, y + dy, z + dz)) {
maybeAlwaysInside.add(h);
} else {
minOutside = Math.min(minOutside, h);
}
}
}
}
double maxInside = Double.NEGATIVE_INFINITY;
DoubleIterator it = maybeAlwaysInside.iterator();
while (it.hasNext()) {
double inside = it.nextDouble();
if (inside < minOutside) {
maxInside = Math.max(maxInside, inside);
}
}
return maxInside;
}
@Override
public BlockPos getGoalPos() {
return new BlockPos(x, y, z);

View File

@@ -18,6 +18,8 @@
package baritone.api.pathing.goals;
import baritone.api.utils.SettingsUtil;
import it.unimi.dsi.fastutil.doubles.DoubleOpenHashSet;
import it.unimi.dsi.fastutil.doubles.DoubleIterator;
import net.minecraft.util.math.BlockPos;
import java.util.Arrays;
@@ -65,7 +67,7 @@ public class GoalRunAway implements Goal {
}
@Override
public double heuristic(int x, int y, int z) {//mostly copied from GoalBlock
public double heuristic(int x, int y, int z) {// mostly copied from GoalBlock
double min = Double.MAX_VALUE;
for (BlockPos p : from) {
double h = GoalXZ.calculate(p.getX() - x, p.getZ() - z);
@@ -80,6 +82,48 @@ public class GoalRunAway implements Goal {
return min;
}
@Override
public double heuristic() {// TODO less hacky solution
int distance = (int) Math.ceil(Math.sqrt(distanceSq));
int minX = Integer.MAX_VALUE;
int minY = Integer.MAX_VALUE;
int minZ = Integer.MAX_VALUE;
int maxX = Integer.MIN_VALUE;
int maxY = Integer.MIN_VALUE;
int maxZ = Integer.MIN_VALUE;
for (BlockPos p : from) {
minX = Math.min(minX, p.getX() - distance);
minY = Math.min(minY, p.getY() - distance);
minZ = Math.min(minZ, p.getZ() - distance);
maxX = Math.max(minX, p.getX() + distance);
maxY = Math.max(minY, p.getY() + distance);
maxZ = Math.max(minZ, p.getZ() + distance);
}
DoubleOpenHashSet maybeAlwaysInside = new DoubleOpenHashSet(); // see pull request #1978
double minOutside = Double.POSITIVE_INFINITY;
for (int x = minX; x <= maxX; x++) {
for (int y = minY; y <= maxY; y++) {
for (int z = minZ; z <= maxZ; z++) {
double h = heuristic(x, y, z);
if (h < minOutside && isInGoal(x, y, z)) {
maybeAlwaysInside.add(h);
} else {
minOutside = Math.min(minOutside, h);
}
}
}
}
double maxInside = Double.NEGATIVE_INFINITY;
DoubleIterator it = maybeAlwaysInside.iterator();
while (it.hasNext()) {
double inside = it.nextDouble();
if (inside < minOutside) {
maxInside = Math.max(maxInside, inside);
}
}
return maxInside;
}
@Override
public String toString() {
if (maintainY != null) {

View File

@@ -64,6 +64,11 @@ public class GoalStrictDirection implements Goal {
return heuristic;
}
@Override
public double heuristic() {
return Double.NEGATIVE_INFINITY;
}
@Override
public String toString() {
return String.format(

View File

@@ -71,4 +71,11 @@ public class CompositeSchematic extends AbstractSchematic {
}
return entry.schematic.desiredState(x - entry.x, y - entry.y, z - entry.z, current, approxPlaceable);
}
@Override
public void reset() {
for (CompositeSchematicEntry entry : schematicArr) {
entry.schematic.reset();
}
}
}

View File

@@ -19,7 +19,6 @@ package baritone.api.schematic;
import baritone.api.utils.BlockOptionalMeta;
import net.minecraft.block.state.IBlockState;
import net.minecraft.init.Blocks;
import java.util.List;
@@ -44,8 +43,6 @@ public class FillSchematic extends AbstractSchematic {
public IBlockState desiredState(int x, int y, int z, IBlockState current, List<IBlockState> approxPlaceable) {
if (bom.matches(current)) {
return current;
} else if (current.getBlock() != Blocks.AIR) {
return Blocks.AIR.getDefaultState();
}
for (IBlockState placeable : approxPlaceable) {
if (bom.matches(placeable)) {

View File

@@ -73,6 +73,11 @@ public interface ISchematic {
*/
IBlockState desiredState(int x, int y, int z, IBlockState current, List<IBlockState> approxPlaceable);
/**
* Resets possible caches to avoid wrong behavior when moving the schematic around
*/
default void reset() {}
/**
* @return The width (X axis length) of this schematic
*/

View File

@@ -31,6 +31,18 @@ public class ReplaceSchematic extends MaskSchematic {
this.cache = new Boolean[widthX()][heightY()][lengthZ()];
}
@Override
public void reset() {
// it's final, can't use this.cache = new Boolean[widthX()][heightY()][lengthZ()]
for (int x = 0; x < cache.length; x++) {
for (int y = 0; y < cache[0].length; y++) {
for (int z = 0; z < cache[0][0].length; z++) {
cache[x][y][z] = null;
}
}
}
}
@Override
protected boolean partOfMask(int x, int y, int z, IBlockState currentState) {
if (cache[x][y][z] == null) {

View File

@@ -0,0 +1,89 @@
/*
* 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.schematic;
import net.minecraft.block.Block;
import net.minecraft.block.BlockAir;
import net.minecraft.block.properties.IProperty;
import net.minecraft.block.state.IBlockState;
import net.minecraft.init.Blocks;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class SubstituteSchematic extends AbstractSchematic {
private final ISchematic schematic;
private final Map<Block, List<Block>> substitutions;
private final Map<IBlockState, Map<Block, IBlockState>> blockStateCache = new HashMap<>();
public SubstituteSchematic(ISchematic schematic, Map<Block,List<Block>> substitutions) {
super(schematic.widthX(), schematic.heightY(), schematic.lengthZ());
this.schematic = schematic;
this.substitutions = substitutions;
}
@Override
public boolean inSchematic(int x, int y, int z, IBlockState currentState) {
return schematic.inSchematic(x, y, z, currentState);
}
@Override
public IBlockState desiredState(int x, int y, int z, IBlockState current, List<IBlockState> approxPlaceable) {
IBlockState desired = schematic.desiredState(x, y, z, current, approxPlaceable);
Block desiredBlock = desired.getBlock();
if (!substitutions.containsKey(desiredBlock)) {
return desired;
}
List<Block> substitutes = substitutions.get(desiredBlock);
if (substitutes.contains(current.getBlock()) && !(current.getBlock() instanceof BlockAir)) {// don't preserve air, it's almost always there and almost never wanted
return withBlock(desired, current.getBlock());
}
for (Block substitute : substitutes) {
if (substitute instanceof BlockAir) {
return current.getBlock() instanceof BlockAir ? current : Blocks.AIR.getDefaultState(); // can always "place" air
}
for (IBlockState placeable : approxPlaceable) {
if (substitute.equals(placeable.getBlock())) {
return withBlock(desired, placeable.getBlock());
}
}
}
return substitutes.get(0).getDefaultState();
}
private IBlockState withBlock(IBlockState state, Block block) {
if (blockStateCache.containsKey(state) && blockStateCache.get(state).containsKey(block)) {
return blockStateCache.get(state).get(block);
}
Collection<IProperty<?>> properties = state.getPropertyKeys();
IBlockState newState = block.getDefaultState();
for (IProperty<?> property : properties) {
try {
newState = copySingleProp(state, newState, property);
} catch (IllegalArgumentException e) { //property does not exist for target block
}
}
blockStateCache.computeIfAbsent(state, s -> new HashMap<Block,IBlockState>()).put(block, newState);
return newState;
}
private <T extends Comparable<T>> IBlockState copySingleProp(IBlockState fromState, IBlockState toState, IProperty<T> prop) {
return toState.withProperty(prop, fromState.getValue(prop));
}
}

View File

@@ -17,6 +17,7 @@
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;
@@ -79,25 +80,98 @@ 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) {
// 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;
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);
}
@Override
@@ -206,10 +280,10 @@ public final class BetterBlockPos extends BlockPos {
@Nonnull
public String toString() {
return String.format(
"BetterBlockPos{x=%s,y=%s,z=%s}",
SettingsUtil.maybeCensor(x),
SettingsUtil.maybeCensor(y),
SettingsUtil.maybeCensor(z)
"BetterBlockPos{x=%d,y=%d,z=%d}",
x,
y,
z
);
}
}

View File

@@ -18,7 +18,6 @@
package baritone.api.utils;
import baritone.api.BaritoneAPI;
import baritone.api.utils.gui.BaritoneToast;
import net.minecraft.client.Minecraft;
import net.minecraft.util.text.ITextComponent;
import net.minecraft.util.text.TextComponentString;
@@ -71,7 +70,7 @@ public interface Helper {
* @param message The message to display in the popup
*/
default void logToast(ITextComponent title, ITextComponent message) {
mc.addScheduledTask(() -> BaritoneToast.addOrUpdate(mc.getToastGui(), title, message, BaritoneAPI.getSettings().toastTimer.value));
mc.addScheduledTask(() -> BaritoneAPI.getSettings().toaster.value.accept(title, message));
}
/**
@@ -93,6 +92,48 @@ public interface Helper {
logToast(Helper.getPrefix(), new TextComponentString(message));
}
/**
* Send a message as a desktop notification
*
* @param message The message to display in the notification
*/
default void logNotification(String message) {
logNotification(message, false);
}
/**
* Send a message as a desktop notification
*
* @param message The message to display in the notification
* @param error Whether to log as an error
*/
default void logNotification(String message, boolean error) {
if (BaritoneAPI.getSettings().desktopNotifications.value) {
logNotificationDirect(message, error);
}
}
/**
* Send a message as a desktop notification regardless of desktopNotifications
* (should only be used for critically important messages)
*
* @param message The message to display in the notification
*/
default void logNotificationDirect(String message) {
logNotificationDirect(message, false);
}
/**
* Send a message as a desktop notification regardless of desktopNotifications
* (should only be used for critically important messages)
*
* @param message The message to display in the notification
* @param error Whether to log as an error
*/
default void logNotificationDirect(String message, boolean error) {
mc.addScheduledTask(() -> BaritoneAPI.getSettings().notifier.value.accept(message, error));
}
/**
* Send a message to chat only if chatDebug is on
*

View File

@@ -15,7 +15,7 @@
* along with Baritone. If not, see <https://www.gnu.org/licenses/>.
*/
package baritone.utils;
package baritone.api.utils;
import org.apache.commons.lang3.SystemUtils;

View File

@@ -35,6 +35,7 @@ import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.Function;
@@ -49,6 +50,7 @@ public class SettingsUtil {
private static final Path SETTINGS_PATH = getMinecraft().gameDir.toPath().resolve("baritone").resolve("settings.txt");
private static final Pattern SETTING_PATTERN = Pattern.compile("^(?<setting>[^ ]+) +(?<value>.+)"); // key and value split by the first space
private static final String[] JAVA_ONLY_SETTINGS = {"logger", "notifier", "toaster"};
private static boolean isComment(String line) {
return line.startsWith("#") || line.startsWith("//");
@@ -110,7 +112,7 @@ public class SettingsUtil {
System.out.println("NULL SETTING?" + setting.getName());
continue;
}
if (setting.getName().equals("logger")) {
if (javaOnlySetting(setting)) {
continue; // NO
}
if (setting.value == setting.defaultValue) {
@@ -164,13 +166,28 @@ public class SettingsUtil {
}
public static String settingToString(Settings.Setting setting) throws IllegalStateException {
if (setting.getName().equals("logger")) {
return "logger";
if (javaOnlySetting(setting)) {
return setting.getName();
}
return setting.getName() + " " + settingValueToString(setting);
}
/**
* This should always be the same as whether the setting can be parsed from or serialized to a string
*
* @param the setting
* @return true if the setting can not be set or read by the user
*/
public static boolean javaOnlySetting(Settings.Setting setting) {
for (String name : JAVA_ONLY_SETTINGS) { // no JAVA_ONLY_SETTINGS.contains(...) because that would be case sensitive
if (setting.getName().equalsIgnoreCase(name)) {
return true;
}
}
return false;
}
public static void parseAndApply(Settings settings, String settingName, String settingValue) throws IllegalStateException, NumberFormatException {
Settings.Setting setting = settings.byLowerName.get(settingName);
if (setting == null) {
@@ -261,6 +278,36 @@ public class SettingsUtil {
public boolean accepts(Type type) {
return List.class.isAssignableFrom(TypeUtils.resolveBaseClass(type));
}
},
MAPPING() {
@Override
public Object parse(ParserContext context, String raw) {
Type keyType = ((ParameterizedType) context.getSetting().getType()).getActualTypeArguments()[0];
Type valueType = ((ParameterizedType) context.getSetting().getType()).getActualTypeArguments()[1];
Parser keyParser = Parser.getParser(keyType);
Parser valueParser = Parser.getParser(valueType);
return Stream.of(raw.split(",(?=[^,]*->)"))
.map(s -> s.split("->"))
.collect(Collectors.toMap(s -> keyParser.parse(context, s[0]), s -> valueParser.parse(context, s[1])));
}
@Override
public String toString(ParserContext context, Object value) {
Type keyType = ((ParameterizedType) context.getSetting().getType()).getActualTypeArguments()[0];
Type valueType = ((ParameterizedType) context.getSetting().getType()).getActualTypeArguments()[1];
Parser keyParser = Parser.getParser(keyType);
Parser valueParser = Parser.getParser(valueType);
return ((Map<?,?>) value).entrySet().stream()
.map(o -> keyParser.toString(context, o.getKey()) + "->" + valueParser.toString(context, o.getValue()))
.collect(Collectors.joining(","));
}
@Override
public boolean accepts(Type type) {
return Map.class.isAssignableFrom(TypeUtils.resolveBaseClass(type));
}
};
private final Class<?> cla$$;

View File

@@ -71,4 +71,8 @@ public class BaritoneToast implements IToast {
baritonetoast.setDisplayedText(title, subtitle);
}
}
public static void addOrUpdate(ITextComponent title, ITextComponent subtitle) {
addOrUpdate(net.minecraft.client.Minecraft.getMinecraft().getToastGui(), title, subtitle, baritone.api.BaritoneAPI.getSettings().toastTimer.value);
}
}

View File

@@ -24,6 +24,7 @@ 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;
@@ -120,6 +121,15 @@ 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

View File

@@ -18,6 +18,7 @@
package baritone.behavior;
import baritone.Baritone;
import baritone.api.BaritoneAPI;
import baritone.api.event.events.TickEvent;
import baritone.utils.ToolSet;
import net.minecraft.block.Block;
@@ -112,6 +113,9 @@ public final class InventoryBehavior extends Behavior {
if (stack.isEmpty()) {
continue;
}
if (Baritone.settings().itemSaver.value && (stack.getItemDamage() + Baritone.settings().itemSaverThreshold.value) >= stack.getMaxDamage() && stack.getMaxDamage() > 1) {
continue;
}
if (cla$$.isInstance(stack.getItem())) {
double speed = ToolSet.calculateSpeedVsBlock(stack, against.getDefaultState()); // takes into account enchants
if (speed > bestSpeed) {
@@ -149,6 +153,10 @@ public final class InventoryBehavior extends Behavior {
}
public boolean throwaway(boolean select, Predicate<? super ItemStack> desired) {
return throwaway(select, desired, Baritone.settings().allowInventory.value);
}
public boolean throwaway(boolean select, Predicate<? super ItemStack> desired, boolean allowInventory) {
EntityPlayerSP p = ctx.player();
NonNullList<ItemStack> inv = p.inventory.mainInventory;
for (int i = 0; i < 9; i++) {
@@ -181,6 +189,19 @@ public final class InventoryBehavior extends Behavior {
}
}
}
if (allowInventory) {
for (int i = 9; i < 36; i++) {
if (desired.test(inv.get(i))) {
swapWithHotBar(i, 7);
if (select) {
p.inventory.currentItem = 7;
}
return true;
}
}
}
return false;
}
}

View File

@@ -25,6 +25,7 @@ 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;
@@ -40,13 +41,20 @@ 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 :^)
*
@@ -166,7 +174,26 @@ public final class MemoryBehavior extends Behavior {
@Override
public void onPlayerDeath() {
baritone.getWorldProvider().getCurrentWorld().getWaypoints().addWaypoint(new Waypoint("death", Waypoint.Tag.DEATH, ctx.playerFeet()));
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);
}

View File

@@ -52,6 +52,10 @@ public final class PathingBehavior extends Behavior implements IPathingBehavior,
private Goal goal;
private CalculationContext context;
/*eta*/
private int ticksElapsedSoFar;
private BetterBlockPos startPosition;
private boolean safeToCancel;
private boolean pauseRequestedLastTick;
private boolean unpausedLastTick;
@@ -98,6 +102,7 @@ public final class PathingBehavior extends Behavior implements IPathingBehavior,
expectedSegmentStart = pathStart();
baritone.getPathingControlManager().preTick();
tickPath();
ticksElapsedSoFar++;
dispatchEvents();
}
@@ -372,6 +377,40 @@ public final class PathingBehavior extends Behavior implements IPathingBehavior,
return context;
}
public Optional<Double> estimatedTicksToGoal() {
BetterBlockPos currentPos = ctx.playerFeet();
if (goal == null || currentPos == null || startPosition == null) {
return Optional.empty();
}
if (goal.isInGoal(ctx.playerFeet())) {
resetEstimatedTicksToGoal();
return Optional.of(0.0);
}
if (ticksElapsedSoFar == 0) {
return Optional.empty();
}
double current = goal.heuristic(currentPos.x, currentPos.y, currentPos.z);
double start = goal.heuristic(startPosition.x, startPosition.y, startPosition.z);
if (current == start) {// can't check above because current and start can be equal even if currentPos and startPosition are not
return Optional.empty();
}
double eta = Math.abs(current - goal.heuristic()) * ticksElapsedSoFar / Math.abs(start - current);
return Optional.of(eta);
}
private void resetEstimatedTicksToGoal() {
resetEstimatedTicksToGoal(expectedSegmentStart);
}
private void resetEstimatedTicksToGoal(BlockPos start) {
resetEstimatedTicksToGoal(new BetterBlockPos(start));
}
private void resetEstimatedTicksToGoal(BetterBlockPos start) {
ticksElapsedSoFar = 0;
startPosition = start;
}
/**
* See issue #209
*
@@ -468,6 +507,7 @@ public final class PathingBehavior extends Behavior implements IPathingBehavior,
if (executor.get().getPath().positions().contains(expectedSegmentStart)) {
queuePathEvent(PathEvent.CALC_FINISHED_NOW_EXECUTING);
current = executor.get();
resetEstimatedTicksToGoal(start);
} else {
logDebug("Warning: discarding orphan path segment with incorrect start");
}

View File

@@ -0,0 +1,56 @@
/*
* 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();
}
}
}

View File

@@ -0,0 +1,38 @@
/*
* 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;
import java.util.Collections;
import java.util.List;
public class BlockData {
private final BlockStateCachedData[] PER_STATE;
public BlockData(IBlockStateDataProvider provider) {
PER_STATE = provider.allNullable();
}
public BlockStateCachedData get(int state) {
return PER_STATE[state];
}
public List<BlockStateCachedData> getAllStates() {
return Collections.unmodifiableList(Arrays.asList(PER_STATE));
}
}

View File

@@ -0,0 +1,85 @@
/*
* 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);
}
}

View File

@@ -0,0 +1,326 @@
/*
* 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();
}
}

View File

@@ -0,0 +1,236 @@
/*
* 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;
}
}

View File

@@ -0,0 +1,98 @@
/*
* 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();
}
}
}
}

View File

@@ -0,0 +1,120 @@
/*
* 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
}
}

View File

@@ -0,0 +1,94 @@
/*
* 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.OptionalInt;
public class CountingSurface extends NavigableSurface {
public CountingSurface(int x, int y, int z) {
super(x, y, z, Attachment::new, $ -> new Attachment());
}
private static class Attachment {
public final int surfaceSize;
public Attachment(Object a, Object b) {
this((Attachment) a, (Attachment) b);
}
public Attachment(Attachment a, Attachment b) {
this.surfaceSize = a.surfaceSize + b.surfaceSize;
}
public Attachment() {
this.surfaceSize = 1;
}
@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));
}
}

View File

@@ -0,0 +1,165 @@
/*
* 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);
}
}

View File

@@ -0,0 +1,126 @@
/*
* 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;
}
}

View File

@@ -0,0 +1,499 @@
/*
* 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();
}
}
}
}
}
}

View File

@@ -0,0 +1,138 @@
/*
* 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;
}
}
}

View File

@@ -0,0 +1,940 @@
/*
* 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();
}
}

View File

@@ -0,0 +1,94 @@
/*
* 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;
}
}

View File

@@ -0,0 +1,88 @@
/*
* 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?)
}

View File

@@ -0,0 +1,298 @@
/*
* 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);
}
}

View File

@@ -0,0 +1,27 @@
/*
* 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 GreedySolverEngine implements ISolverEngine {
INSTANCE;
@Override
public SolverEngineOutput solve(SolverEngineInput in) {
return new GreedySolver(in).search();
}
}

View File

@@ -0,0 +1,22 @@
/*
* 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 Half {
TOP, BOTTOM, EITHER
}

View File

@@ -0,0 +1,44 @@
/*
* 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;
}
}

View File

@@ -0,0 +1,21 @@
/*
* 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 INavigableSurface {
}

View File

@@ -0,0 +1,33 @@
/*
* 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.LongList;
public interface IReachabilityProvider {
LongList candidates(long playerEyeVoxel);
static IReachabilityProvider get(DependencyGraphScaffoldingOverlay overlay, PlayerReachSphere sphere) {
try {
return new ReachabilityCache(overlay, sphere);
} catch (ReachabilityCache.SchematicIsTooDenseForThisToMakeSenseException ex) {
return new ReachabilityLive(overlay, sphere);
}
}
}

View File

@@ -0,0 +1,27 @@
/*
* 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);
}

View File

@@ -0,0 +1,23 @@
/*
* 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 ISolverEngine {
SolverEngineOutput solve(SolverEngineInput in);
}

View File

@@ -0,0 +1,346 @@
/*
* 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);
}
}

View File

@@ -0,0 +1,126 @@
/*
* 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.Augmentation;
import baritone.builder.utils.com.github.btrekkie.connectivity.ConnGraph;
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, Augmentation 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());
}
}

View File

@@ -0,0 +1,66 @@
/*
* 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;
}
}

View File

@@ -0,0 +1,110 @@
/*
* 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;
}
}

View File

@@ -0,0 +1,70 @@
/*
* 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];
}
}

View File

@@ -0,0 +1,151 @@
/*
* 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;
}
}

View File

@@ -0,0 +1,58 @@
/*
* 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();
}
}
}
}
}

View File

@@ -0,0 +1,102 @@
/*
* 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;
}
}

View File

@@ -0,0 +1,391 @@
/*
* 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
);
}
}

View File

@@ -0,0 +1,77 @@
/*
* 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();
}
}
}
}

View File

@@ -0,0 +1,24 @@
/*
* 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
}

View File

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

View File

@@ -0,0 +1,396 @@
/*
* 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);
}
}

View File

@@ -0,0 +1,59 @@
/*
* 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 {
}
}

View File

@@ -0,0 +1,45 @@
/*
* 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;
}
}

View File

@@ -0,0 +1,211 @@
/*
* 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;
}
}
}

View File

@@ -0,0 +1,184 @@
/*
* 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;
}
}
}

View File

@@ -0,0 +1,74 @@
/*
* 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;
}
}
}

View File

@@ -0,0 +1,58 @@
/*
* 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;
}
}

View File

@@ -0,0 +1,139 @@
/*
* 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;
}
}

View File

@@ -0,0 +1,141 @@
/*
* 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;
}
}
}

View File

@@ -0,0 +1,66 @@
/*
* 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();
}
}
}

View File

@@ -0,0 +1,233 @@
/*
* 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;
}
}

View File

@@ -0,0 +1,225 @@
/*
* 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;
}
}
}

View File

@@ -0,0 +1,39 @@
/*
* 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 class Vec2d {
public final double x;
public final double z;
public Vec2d(double x, double z) {
this.x = x;
this.z = z;
}
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);
}

View File

@@ -0,0 +1,89 @@
/*
* 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 + "}";
}
}

View File

@@ -0,0 +1,123 @@
/*
* 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);
}
}
}

View File

@@ -0,0 +1,52 @@
/*
* 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;
}
}

View File

@@ -0,0 +1,307 @@
/*
* 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()];
}
}

View File

@@ -0,0 +1,112 @@
/*
* 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());
}
}
}

View File

@@ -0,0 +1,50 @@
/*
* 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);
}
}
}

View File

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

View File

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

View File

@@ -0,0 +1,44 @@
/*
* 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);
}

View File

@@ -0,0 +1,70 @@
/*
* 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;
}
}

View File

@@ -0,0 +1,52 @@
/*
* 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;
}
}

View File

@@ -0,0 +1,35 @@
/*
* 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;
}
}

View File

@@ -0,0 +1,125 @@
/*
* 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 combining function for combining user-provided augmentations. augmentationFunc is null if this node is not in
* the highest level.
*/
public final Augmentation augmentationFunc;
/**
* 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 augmentationFunc. 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;
public EulerTourNode(EulerTourVertex vertex, Augmentation augmentationFunc) {
this.vertex = vertex;
this.augmentationFunc = augmentationFunc;
}
/**
* 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();
Object newAugmentation = null;
boolean newHasAugmentation = false;
if (augmentationFunc != null) {
if (left.hasAugmentation) {
newAugmentation = left.augmentation;
newHasAugmentation = true;
}
if (vertex.hasAugmentation && vertex.arbitraryVisit == this) {
if (newHasAugmentation) {
newAugmentation = augmentationFunc.combine(newAugmentation, vertex.augmentation);
} else {
newAugmentation = vertex.augmentation;
newHasAugmentation = true;
}
}
if (right.hasAugmentation) {
if (newHasAugmentation) {
newAugmentation = augmentationFunc.combine(newAugmentation, right.augmentation);
} else {
newAugmentation = right.augmentation;
newHasAugmentation = true;
}
}
}
if (newSize == size && !augmentedFlags && hasAugmentation == newHasAugmentation &&
(newAugmentation != null ? newAugmentation.equals(augmentation) : augmentation == null)) {
return false;
} else {
size = newSize;
augmentation = newAugmentation;
hasAugmentation = newHasAugmentation;
return true;
}
}
}

View File

@@ -0,0 +1,52 @@
/*
* 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;
}

View File

@@ -0,0 +1,30 @@
/*
* 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;
}
}

View File

@@ -0,0 +1,124 @@
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.augmentationFunc 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.)

View File

@@ -0,0 +1,36 @@
/*
* 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);
}
}

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