1
2
Fork 0
mirror of https://github.com/mat-1/azalea.git synced 2025-08-02 14:26:04 +00:00

Compare commits

...

302 commits

Author SHA1 Message Date
mat
37b124a2b6 upgrade deps 2025-08-02 08:52:39 +08:00
mat
e7bf124ed5 update changelog 2025-07-24 07:31:23 -03:00
mat
9719d00526 add Client::force_stop_pathfinding 2025-07-24 21:26:49 +11:00
mat
302752860c update movement code for 1.21.5 changes
fixes grim flags
2025-07-24 04:42:52 -05:30
mat
45f89b48e4 1.21.8 2025-07-24 03:54:33 -03:30
mat
6984a2b9e6 remove incorrect optimization attempt in pathfinder 2025-07-24 19:06:01 +12:00
mat
a5c67d2eee add FastFixedBitSet and use it in the pathfinder 2025-07-24 19:06:01 +12:00
mat
004741b781 delete unused module from azalea-chat 2025-07-24 19:06:01 +12:00
mat
63348dbbea clippy: use is_multiple_of 2025-07-24 19:06:01 +12:00
Kumpelinus
df9d776ff8
Add TicksAlive component (#229)
* Add TicksAlive component

* Rename TicksAlive to TicksConnected

* Move component to plugins/tick_counter.rs and add doc comment
2025-07-21 15:28:41 -05:00
mat
ebc2e0c067
1.21.7 (#227)
* 1.21.7-rc1

* 1.21.7

* update Cargo.lock too
2025-06-30 14:14:45 -05:00
mat
a060b73915 fix ClientboundShowDialog in config 2025-06-27 19:43:54 -04:30
mat
5564d475a2 typos 2025-06-27 14:00:26 -09:00
mat
2064450763 outline_shapes patch for pumpkin extractor isn't necessary anymore 2025-06-27 18:56:53 -03:30
mat
7b8dc189f7 let chains are stabilized 2025-06-26 06:48:25 -12:00
mat
537ec510f0 remove some unused system ordering 2025-06-25 20:06:25 -10:00
mat
f12589ab80 fix invalid look directions on teleport 2025-06-25 16:10:15 -13:45
mat
08c409d048 improve packet_order test, add BlockUpdatePlugin, fix packet order for sprinting 2025-06-25 15:14:39 -12:45
mat
f9e4b65713 start adding packet_order test 2025-06-26 10:05:58 +07:00
Yhgd
af1ef93100
fix: parse int lists in TranslatableComponent with field (#225) 2025-06-18 19:25:15 -05:00
mat
844697c04c 1.21.6 fixes 2025-06-18 08:24:34 +07:00
mat
ffbe7a3e42 1.21.6 (#215) 2025-06-17 06:49:07 -12:00
mat
319d144995 take Entity instead of MinecraftEntityId in Client::attack 2025-06-17 09:14:37 +09:30
mat
f82cf7fa85 cleanup 2025-06-16 17:08:09 -06:30
mat
fd9bf16871 implement EntityHitResult 2025-06-16 21:31:04 +00:00
mat
713dae7110 update changelog 2025-06-15 19:52:26 -05:00
mat
8f70f191bb fix unused import warnings when compiling some crates individually 2025-06-16 05:50:06 +05:00
mat
318d95491c Release independent packages
Generated by cargo-workspaces
2025-06-15 19:21:07 -05:00
mat
57bdb88272 use workspace deps everywhere 2025-06-16 01:20:37 +01:00
mat
6bbb3ec5eb Release independent packages
Generated by cargo-workspaces
2025-06-15 19:13:03 -05:00
mat
1fa1520fea put all azalea-* deps in the workspace Cargo.toml 2025-06-16 04:12:22 +04:00
mat
f64229fcc9 add some missing things to the changelog 2025-06-15 22:42:10 +01:00
mat
a06b79a6c1 update deps 2025-06-15 10:09:47 -10:30
mat
1a983beec1 update changelog 2025-06-15 14:45:56 -05:00
mat
5e81d85d7e add note about current_thread to azalea readme 2025-06-14 20:33:22 -10:30
mat
a2606569bb use owned instead of borrowed Vec3 more 2025-06-11 22:58:41 -06:30
mat
89ddd5e85f fix bench 2025-06-11 15:50:45 -12:45
mat
df092f25ec add pathfinder retry_on_no_path and rework GotoEvent constructor 2025-06-11 16:02:08 -09:00
mat
1b348ceeff implement reverting block state predictions on ack 2025-06-11 22:22:26 +00:00
mat
067ec06f26 add BlockPos::distance_to and length 2025-06-11 05:17:06 -13:00
mat
ab05e7bdae add Client::attack_cooldown_remaining_ticks 2025-06-11 02:55:30 -05:30
mat
9b0bd29db4 take BlockPos instead of &BlockPos in all function arguments 2025-06-11 16:55:33 +11:00
mat
2a6ac0764f add mine_with_auto_tool 2025-06-10 19:47:42 -01:00
mat
e4ead93f19 jump if in water while executing pathfinder path 2025-06-09 21:33:21 -03:30
mat
086f979a28 replace Client::get_open_container and view_container_or_inventory with get_inventory 2025-06-09 13:14:20 -11:00
mat
40bcb62a77 update config_fast_builds 2025-06-09 20:33:06 +12:00
mat
4a4de81961 handle relative teleports correctly and fix entity chunk indexing warnings 2025-06-09 17:15:07 +09:00
mat
45d7371274 insert ClientInformation earlier 2025-06-08 17:44:12 -09:00
mat
3087b0c996 add support for panicking on warn/error in simulation tests 2025-06-08 22:46:26 -03:30
mat
338f931c51 wait for block to exist when calling open_container_at 2025-06-05 00:59:14 -04:00
mat
874f051810 remove comment about a part of a test that i decided not to implement 2025-06-04 20:55:57 -07:00
mat
be81877137 fix panic when receiving add_entity and start_configuration in the same update 2025-06-04 21:53:06 -06:00
mat
93a96786a8 handle set_held_slot packet and add more Into BlockStates impls 2025-06-04 10:26:32 +03:30
mat
123c15a293 fix hashset of blockstate into blockstates impl 2025-06-04 12:16:12 +06:00
mat
bbfca34133 copy player part of container_menu to inventory_menu on close 2025-06-03 17:10:34 -13:00
mat
f5f50b85e5 re-enable click prediction and fix related issues 2025-06-04 01:53:24 -03:30
mat
f311ac27d4 send ServerboundPlayerLoaded on join and respawn 2025-06-03 22:01:50 +03:30
mat
415c0d873e fix CubeVoxelShape::find_index 2025-06-02 23:44:49 -09:00
mat
cc3e64a315 fix collisions bugs 2025-06-03 02:41:25 -06:00
mat
61443fa481 fix wrong sequence number being sent 2025-06-02 21:51:08 -09:30
mat
1edb9d3448 add BlockPos::center_bottom 2025-06-02 17:56:16 -08:00
mat
f3a5e91a8c fix issues when pathfinding to non-full blocks and add Client::view_inventory 2025-06-03 09:52:30 +09:30
mat
7517a207db rename the Block trait to BlockTrait to disambiguate with azalea_registry::Block 2025-06-03 06:11:26 +07:00
mat
abf995a702 replace wait_one_tick with wait_ticks and some other api improvements 2025-06-03 03:48:36 +05:00
mat
04dd6dd0a4 fix login_to_dimension_with_same_name test logging error 2025-06-02 13:18:42 -06:30
mat
5c0e5b1eb3 sort entities_by by distance and improve some docs 2025-06-03 07:40:53 +12:00
mat
cee64cece3 actually send Event::ReceiveChunk 2025-06-02 15:55:39 -03:00
mat
c5ddae58a1 improve docs for biome code 2025-06-02 11:51:03 -06:00
mat
2c5f293210 add Event::ReceiveChunk and find_blocks_in_chunk function 2025-06-02 06:55:38 -10:30
mat
5adf67fe04 fix grammar issues in readme 2025-06-01 19:52:51 -12:00
mat
0569862a1b fix issues related to pathfinder mining 2025-06-02 03:14:08 -04:30
mat
d7cd305059 fix VibrationParticle 2025-06-02 10:30:28 +05:00
mat
8da179b221 simplify some join logic so the Entity is returned even on connection error 2025-06-02 04:12:07 -01:00
mat
3d121722d7 several pathfinder fixes 2025-06-02 03:44:24 -01:00
mat
99659bd9a3 add CustomPathfinderState 2025-06-01 21:01:31 -05:45
mat
1d3a7c969f add Client::entities_by and improve some docs 2025-06-01 09:57:54 -12:45
mat
d028d7c3e9 add basic support for getting biome ids in chunks 2025-06-02 07:45:26 +11:00
mat
b103e6fdc0 doc updates 2025-05-31 23:14:59 -06:00
mat
0a7648ce48 add '@generated' to generated code 2025-05-30 18:09:44 -10:00
mat
f27c87b291 more formatting fixes 2025-05-30 19:22:09 -08:45
mat
e37524899e formatting: merge imports 2025-05-30 14:44:48 -13:00
mat
ae4b1e85e6 fix clippy issues and improve formatting everywhere 2025-05-30 19:36:59 -08:00
mat
a64c650504 make fixedbitset require generic const exprs again :3 2025-05-30 12:59:08 -13:45
mat
cfdd8e690f update changelog 2025-05-31 10:47:23 +11:00
mat
a5e7ff771d implement missing brigadier features and cleanup some more 2025-05-30 16:37:40 -07:00
mat
da73b4316d add support for custom suggestions in azalea-brigadier and cleanup a bit 2025-05-28 14:47:18 -09:00
mat
3d340f585a doc fixes 2025-05-23 11:20:57 -13:45
mat
bacb5c0199 patch pathfinder path on cost increase 2025-05-24 01:20:14 +03:00
mat
acd52756be fix Event::Login not firing on reconnect by adding entity id to RemoveOnDisconnectBundle 2025-05-23 12:53:29 -09:00
mat
4bb317a7e1 add line about azalea-auth.json in azalea-auth readme 2025-05-23 19:08:02 -02:00
activepass
9e34a2660d
lowercase named colours (#222) 2025-05-17 01:07:51 -05:00
mat
fbcaa6eca9 rearrange some parts of changelog and mention pr authors 2025-05-10 06:52:26 +03:30
x-osc
b35904a0b5
add distance param for ReachBlockPosGoal (#220)
* add distance param for ReachBlockPosGoal

* add new impl for ReachBlockPosGoal and optimize slightly
2025-05-09 22:12:59 -05:00
mat
6a5a88700c fix offline-mode cert warnings and improve some docs 2025-05-09 15:00:12 -12:00
mat
e1d3b902ba add StartUseItemEvent and improve code related to interactions 2025-05-10 06:22:08 +03:30
mat
e9b3128103 don't send chat signing certs on offline-mode servers 2025-05-09 14:12:51 -10:30
mat
1493c06de5 better docs for disabling plugins 2025-05-07 16:28:03 -05:00
mat
5d799be9db re-export bevy_tasks from azalea_client 2025-05-08 06:09:03 +09:00
mat
ad4eaa174c doc warnings 2025-05-07 15:08:31 -05:45
mat
e0d3352a90 add chat signing 2025-05-08 08:51:34 +12:00
mat
a8e76a0bff impl Display for proxy and trace log JoinOpts 2025-05-07 06:48:59 -11:00
mat
2b7be768f2 reword some things in the changelog 2025-05-06 15:52:16 -13:45
mat
8dff973d26 support legacy hex colors 2025-05-07 14:24:07 +09:00
mat
4a1fdf0121 fix incorrect chat parsing when legacy color codes are mixed in 2025-05-06 22:35:17 -06:30
mat
aa0256da10 upgrade rust version and fix clippy warnings 2025-05-07 11:27:58 +08:00
mat
e9452032bf fix various issues with mining 2025-05-06 14:20:57 -13:00
mat
d58a1c4fa0 delete outdated parts of azalea-protocol readme 2025-05-06 09:09:02 -13:45
mat
685aeff13f fix pathfinder descending onto non-full blocks 2025-05-06 17:51:50 -05:00
mat
7b442368da fix some edge cases when pathfinding on slabs and stairs 2025-05-07 07:09:48 +09:00
mat
f7c9419045 pathfinder can now handle slabs, stairs, and dirt paths 2025-05-07 06:59:22 +10:00
mat
af3affb467 fix chunk errors when joining a world with a same name but different height 2025-05-07 06:00:29 +10:00
mat
68f657310b switch back to stable hickory-resolver 2025-05-06 04:28:23 -13:45
mat
7bbb617bd8 update deps and lock hickory-resolver to a commit 2025-05-03 16:39:56 -06:00
mat
9a40b65bc1
Add AutoReconnectPlugin (#221)
* add AutoReconnectPlugin

* merge main

* start simplifying swarm internals

* fix Swarm::into_iter, handler functions, DisconnectEvent, and add some more docs

* add ClientBuilder/SwarmBuilder::reconnect_after

* fix a doctest

* reword SwarmEvent::Disconnect doc

* better behavior when we try to join twice

* reconnect on ConnectionFailedEvent too

* autoreconnect is less breaking now
2025-05-02 15:55:58 -05:00
mat
52e34de95c typo 2025-05-03 06:52:50 +11:00
mat
50c8e6bc5b the bevy update is a breaking change 2025-05-02 07:52:06 -12:00
mat
5666918519 add changelog 2025-05-03 01:50:30 +06:00
mat
b3f65f9d4b drop dependency on pixlyzer and start using pumpkin extractor 2025-05-01 22:12:00 -08:45
mat
1d3f659c1d make ClientBuilder/SwarmBuilder Send 2025-05-02 02:41:14 +00:00
mat
881055e587 fix InstanceName desync 2025-05-02 01:20:25 +00:00
Kumpelinus
11a74f215e
Implement a to_html method for FormattedText (#208)
* Implement a to_html method for FormattedText

Also fix a small issue with ansi formatting where it duplicated
text.

* cargo fmt

* Make format conversion generic

* cargo lint and fmt

* Fix ascii conversion cleanup

* Implement suggested changes

* format, improve sanitization, add xss test

---------

Co-authored-by: mat <git@matdoes.dev>
2025-05-01 13:26:04 -05:00
mat
4a7d21425c fix some data components 2025-04-25 05:45:55 +03:30
mat
b3af8d73fa update to bevy 0.16 2025-04-25 01:10:03 -01:00
mat
65c9f555b0 unused import 2025-04-24 16:46:46 -09:00
mat
c84b3161ae info log on DisconnectEvent 2025-04-24 17:13:56 -08:00
mat
adef9bf37b always insert Swarm before any systems can start running 2025-04-24 20:55:58 -03:30
mat
89bc5ca91e update RawConnection::state when start_configuration is received 2025-04-24 11:12:47 -12:45
mat
54062c82fd update to git hickory-resolver and use system resolver 2025-04-24 10:03:29 -13:00
mat
0fc0fe41d4 delete some unused packet modules in game state 2025-04-24 18:42:56 -04:00
mat
8012fb90b5 update serialization for set_player_team 2025-04-25 07:24:12 +09:00
mat
c7d53d6532 faster pathfinder WeightedNode::ord 2025-04-22 04:52:02 +03:00
mat
8045b4eda2 add StartJoinServerEvent to allow joining servers exclusively from ecs 2025-04-19 23:51:19 -04:30
mat
ae3722d72c send correct uuid in offline mode 2025-04-19 23:49:11 +05:00
mat
fefc5db09a make azalea::pathfinder::debug public 2025-04-19 21:09:38 +03:00
mat
b7bc08e352 clippy 2025-04-19 23:35:13 +07:00
manen
6c1b144970
remove .unwrap() from wait_until_goto_target_reached (#216)
* fix panics in `wait_until_goto_target_reached`

* replace eprintln with warn

---------

Co-authored-by: mat <git@matdoes.dev>
2025-04-19 11:33:47 -05:00
mat
77f9d929b6 typo in instance_block_action packet 2025-04-18 14:31:18 +08:00
mat
e86087366f box display_name in PlayerInfo to make it smaller 2025-04-18 01:09:49 +02:00
mat
ad45cf5431 allow disabling Event::Packet with a crate feature 2025-04-17 10:15:14 -12:45
mat
43d7c428e3 fix another panic on disconnect and slightly optimize client events loop 2025-04-18 00:32:31 +02:00
mat
3f60bdadac
Move login state to the ECS (#213)
* use packet handlers code for login custom_query

* initial broken implementation for ecs-only login

* fixes

* run Update schedule 60 times per second and delete code related to run_schedule_sender

* fix tests

* fix online-mode

* reply to query packets in a separate system and make it easier for plugins to disable individual replies

* remove unused imports
2025-04-17 16:16:51 -05:00
mat
1989f4ec97 remove unused import 2025-04-17 11:47:24 -09:00
mat
2aa046c4b5 make BlockState::id private 2025-04-17 11:09:14 -09:30
mat
6a83a6fa38 shorten the Plugins bullet point in the readme 2025-04-16 00:01:59 -03:30
mat
b828bc2b12 minor readme fixes 2025-04-15 17:30:56 -10:00
mat
66174fc7d4 clippy and fix broken doc tests 2025-04-16 07:13:09 +04:00
mat
a9820dfd79 make goto async and clean up some examples 2025-04-15 22:04:43 -04:30
mat
1a0c4e2de9 fix wrong MAXIMUM_UNCOMPRESSED_LENGTH value 2025-04-13 16:10:59 +00:00
mat
fd27ca3bec release 0.12.0+mc1.21.5 2025-04-13 05:30:40 +03:00
mat
960b840536 Revert "Release 0.12.0"
This reverts commit e2945b90a9.
2025-04-12 13:33:07 -12:45
mat
e2945b90a9 Release 0.12.0
azalea@0.12.0
azalea-auth@0.12.0
azalea-block@0.12.0
azalea-block-macros@0.12.0
azalea-brigadier@0.12.0
azalea-buf@0.12.0
azalea-buf-macros@0.12.0
azalea-chat@0.12.0
azalea-client@0.12.0
azalea-core@0.12.0
azalea-crypto@0.12.0
azalea-entity@0.12.0
azalea-inventory@0.12.0
azalea-inventory-macros@0.12.0
azalea-language@0.12.0
azalea-physics@0.12.0
azalea-protocol@0.12.0
azalea-protocol-macros@0.12.0
azalea-registry@0.12.0
azalea-registry-macros@0.12.0
azalea-world@0.12.0

Generated by cargo-workspaces
2025-04-12 21:08:44 -05:00
mat
ed4d5937a7 upgrade deps 2025-04-12 21:29:36 -04:30
mat
31b143f821 fix ChickenVariant data component 2025-04-12 13:37:27 -12:00
mat
3112dc3ea0 fix incorrect data components 2025-04-13 02:38:29 +06:00
mat
1be8d8638f optimization: only run update_on_climbable for local entities 2025-04-13 01:45:07 +06:00
mat
913d6ac8c5 clippy 2025-04-04 09:23:28 -13:45
mat
7dcc86e266 remove unwrap in Hello packet handler 2025-04-05 06:37:36 +08:00
mat
adf8a604c4 fix ordering for handle_attack_queued 2025-04-04 13:48:48 -08:45
mat
efc28db6cf remove unnecessary ecs system ordering for handle_outgoing_packets 2025-04-05 01:46:14 +03:30
mat
f250978cdd send attack packets at the end of the tick 2025-04-05 00:50:39 +03:30
mat
5fd57fd630 don't require mut for functions in Client and add some more convenience functions 2025-04-04 16:05:06 -04:30
mat
e99ae608b6 rename ChatPacket::username and uuid to sender and sender_uuid
also adds more convenience functions to Client
2025-04-04 18:24:22 -02:00
mat
1fd0292590 rename ChatPacket::username and uuid to sender and sender_uuid 2025-04-04 06:23:19 -14:00
mat
09d515c8cd breaking change: send azalea::Event::Spawn every time we switch worlds instead of only once ever 2025-04-04 18:09:19 +00:00
mat
5272db8bb4 remove executable bit from files 2025-04-04 15:56:06 +00:00
mat
635bed9fcb fix compilation related to carried_item 2025-03-29 23:39:02 +00:00
mat
097ebb3705 use HashedStack for ServerboundContainerClick::carried_item too 2025-03-29 23:20:26 +00:00
mat
55ed46d081 fix incorrect usage of optional dependency 2025-03-29 18:15:49 -05:00
mat
43ebbee94a update ServerboundContainerClick to use HashedStack from 1.21.5 2025-03-29 23:10:55 +00:00
mat
d0b459e827 add support for unnamed structs to azalea-buf-macros 2025-03-29 22:29:37 +00:00
mat
4e5c551d65 fix entity deindexing happening at the wrong time 2025-03-28 00:54:17 +00:00
mat
02de98240f fix not replying to ping packets in config state 2025-03-27 21:28:13 +00:00
mat
37cde3ad32 fix components, show_in_tooltip was removed 2025-03-26 00:00:25 +00:00
mat
ef357fdf36
1.21.5 (#198)
* 25w02a

* move item_components codegen to a different module

* remove outdated test

* 25w03a

* start updating to 24w09b

* 1.21.5-pre2

* fix broken packets

* 1.21.5-rc2

* merge main

* delete unused acket_handling

* 1.21.5
2025-03-25 11:17:39 -05:00
mat
8af265e48b PongPlugin 2025-03-25 05:16:10 +00:00
mat
4c53498f07 ignore clippy warning on spawn_listener 2025-03-25 04:32:03 +00:00
EightFactorial
b25474ad66
Wait for plugins to load before starting the ECS (#212)
* Wait for plugins to load before starting the ECS

Also runs `App:finish()` after plugins finish loading.

* Forgot to run `App::cleanup()`

Oops haha

* copy a check from bevy and don't busywait

---------

Co-authored-by: mat <git@matdoes.dev>
2025-03-24 23:30:33 -05:00
mat
656162d218 make events channel in Client::start_client optional 2025-03-25 04:06:47 +00:00
mat
113222a3a2 use zlib-rs for compression 2025-03-25 03:54:22 +00:00
mat
aa2039c868 upgrade deps and slightly improve docs 2025-03-20 03:35:59 +00:00
mat
5c7332b469 add Event::Spawn 2025-03-20 03:30:57 +00:00
mat
13d5cbed1f document Position default 2025-03-20 01:49:15 +00:00
mat
75efbc83fd make SendPacketEvent a bevy trigger 2025-03-19 21:35:47 +00:00
mat
ca2e0b3922 entity collisions 2025-03-16 19:01:31 +00:00
mat
b0bd992adc
Fluid physics fixes (#210)
* start fixing code related to fluid physics

* implement force_solid for blocks

* afk pool test
2025-03-16 13:41:17 -05:00
mat
a95408cbcc fix broken doc links 2025-03-16 04:36:42 +00:00
mat
5f5616145b document ClientboundAddEntity::data 2025-03-16 04:34:32 +00:00
mat
d6a4d91347 impl from u32 for minecraftentityid 2025-03-16 04:23:14 +00:00
Gjum
aeff08f5fa
update dead wiki.vg links (#209) 2025-03-13 17:03:42 -05:00
mat
7981230904 mention NO_COLOR=1 in section about logging in docs 2025-03-13 21:34:54 +00:00
mat
95bdb362ba improve Swarm docs and change an indexing warning to a debug log 2025-03-13 21:27:48 +00:00
mat
f8140e4188 use RawConnection to write reply after getting start_configuration 2025-03-13 21:21:59 +00:00
mat
713b312167 correctly accept resource packs while in config state 2025-03-13 21:01:37 +00:00
mat
249fa55a53 fix wrong Potion data component 2025-03-13 20:52:29 +00:00
mat
65fe072151 fix wrong Consumable component implementation and add set_equipment test 2025-03-13 20:46:12 +00:00
mat
7a192acc99 properly remove from the EntityIdIndex component on despawn 2025-03-13 20:13:58 +00:00
mat
e9a5df2e87 remove EntityBundle on start_configuration 2025-03-07 19:17:23 +00:00
mat
42c79043cf don't send game packets when outside of the game state 2025-03-07 19:11:24 +00:00
mat
28a17f3ed2 add bots and plugins to readme 2025-03-06 04:33:30 +00:00
mat
cf66c4be10 fix despawning entities on dimension change 2025-03-06 04:11:19 +00:00
mat
c9022e8f67 fix errors when switching to Game state and add fast_login test 2025-03-02 05:42:15 +00:00
mat
72d87349fe update outdated comment 2025-02-24 04:26:16 +00:00
mat
7d6cacc79b fix send_receivepacketevent running outside of game state 2025-02-24 04:24:52 +00:00
mat
cfe6bea25a fix compile error 2025-02-24 04:15:33 +00:00
mat
9e061be903 fix an error in set_entity_data 2025-02-24 04:14:54 +00:00
mat
172e0ce079 add fallback for adding LocalEntity so tests don't fail 2025-02-24 03:59:57 +00:00
mat
b9767424f3 don't remove LocalEntity from disconnected players, add new debug logs, and make GameProfile clones cheaper 2025-02-24 03:55:29 +00:00
mat
6a5ab34a2d azalea-language now does a binary search instead of a hashmap lookup 2025-02-23 17:39:17 +00:00
mat
2be4f0f2b6 make run_schedule a bounded channel 2025-02-23 17:10:47 +00:00
mat
21acf4c846 try to receive packets all at once before sending run_schedule_sender 2025-02-23 09:05:20 +00:00
mat
dd557c8f29 fix memory leak in simulation tests (lol)
also, change some vecs into boxed slices, and add RelativeEntityUpdate::new
2025-02-23 08:47:17 +00:00
mat
e21e1b97bf
Refactor azalea-client (#205)
* start organizing packet_handling more by moving packet handlers into their own functions

* finish writing all the handler functions for packets

* use macro for generating match statement for packet handler functions

* fix set_entity_data

* update config state to also use handler functions

* organize az-client file structure by moving things into plugins directory

* fix merge issues
2025-02-22 21:45:26 -06:00
mat
f8130c3c92 minor memory usage optimizations 2025-02-23 03:10:21 +00:00
mat
34f53baf85 update to rust edition 2024 2025-02-22 23:01:54 +00:00
mat
bdd2fc91e1 update deps 2025-02-22 22:56:07 +00:00
mat
6fdf5fce49 fix brand and client info so they're only sent when leaving login instead of entering config
closes #206
2025-02-22 22:34:06 +00:00
mat
444993b609 debug log CARGO_PKG_VERSION on swarm start 2025-02-22 21:52:23 +00:00
mat
74b52a1fc1 fix is_trapdoor_useable_as_ladder and add test 2025-02-22 21:09:35 +00:00
mat
27945c8870 despawn entities when switching worlds and some testbot fixes 2025-02-21 22:50:39 +00:00
mat
f5f15362f2 fix some components not being removed from clients and add debugecsleak testbot command 2025-02-21 21:51:26 +00:00
mat
5fdea4c0b7 fix wrong docs for testbot 2025-02-21 20:20:17 +00:00
mat
24babbdbe5 GameTick should only happen after Update 2025-02-21 20:17:47 +00:00
mat
833f306e8b
Fix errors on switching dimensions (#204)
* Fix errors on switching dimensions

* fix other tests

* clippy

* fix log feature in test_simulation

* fix chunks oops
2025-02-21 13:43:56 -06:00
mat
63b1036a96 fix CustomModelData and WrittenBookContent datacomponents 2025-02-21 17:22:00 +00:00
Shayne Hartford
c285fadd34
Enable serde feature in sub-crates (#202)
Add serde derive to MinecraftEntityId
2025-02-17 17:28:37 -06:00
mat
022771b71b update docs for TabList to mention how it works as a resource 2025-02-17 23:09:24 +00:00
mat
228489dded use MinecraftEntityId type instead of u32 in az-protocol 2025-02-16 17:10:04 +00:00
mat
4fb6b07746 upgrade deps 2025-02-14 03:56:36 +00:00
mat
02f50c2dc5 remove stabilized trait_upcasting feature gates 2025-02-14 03:40:51 +00:00
mat
6020a2297f add todo about wrong calculated_bits_per_entry code 2025-02-04 05:29:18 +00:00
Kumpelinus
858bec2081
Fix deadlock when changing server (#200)
* Fix deadlock when changing server

* remove unnecessary clones

---------

Co-authored-by: mat <git@matdoes.dev>
2025-02-03 13:31:32 -06:00
mat
8d110a9f7c cleanup, fix warnings 2025-02-02 21:17:28 +00:00
mat
b08d3d55d7 start implementing data driven registries 2025-02-02 21:15:45 +00:00
mat
cdb68dfb70 update deps 2025-02-02 17:53:04 +00:00
mat
479a7b8927 update actions/upload-pages-artifact and deploy-pages 2025-02-01 21:37:07 +00:00
mat
8d71577da8 fix doc errors 2025-02-01 21:35:11 +00:00
mat
4562967b61 set player metadata on init instead of login 2025-02-01 21:26:37 +00:00
mat
87c34e1c33 add failing test_set_health_before_login test 2025-02-01 20:35:57 +00:00
mat
4aa5010ea2 remove debug log 2025-01-29 06:07:16 +00:00
mat
5bdb07b314 fix PlayerInput to handle diagonal inputs 2025-01-29 06:04:38 +00:00
mat
6c681adc4d change some warns to debugs 2025-01-29 05:45:17 +00:00
mat
6d8d937d47 add support for tickend and playerinput 2025-01-29 05:29:20 +00:00
mat
befcec9b3a fix wrong Profile data component impl 2025-01-29 02:09:54 +00:00
mat
ef25e77e52 add data for EntityEffect particle to fix set_entity_data errors on hypixel 2025-01-29 02:02:39 +00:00
mat
67c053638c fix chunk parsing on hypixel 2025-01-27 19:24:12 +00:00
mat
ed00a5b8f1 don't warn on release mode for unknown PlayerInfoUpdate 2025-01-27 18:14:53 +00:00
mat
fe423416b4 Rename Connection::configuration to config and add some clientbound functions that already existed serverbound
taken from Shay's fork: b0ca6076ed
2025-01-25 22:34:00 +00:00
mat
b6ddde99ea add ProtocolPacket::name function 2025-01-25 22:20:35 +00:00
mat
f2d8d4211b fix name of field in CommonPlayerSpawnInfo 2025-01-21 23:55:44 +00:00
mat
b0b57aa7d4 delete accidentally created debug file 2025-01-21 23:54:11 +00:00
mat
53fca5faf4 fix errors when switching worlds 2025-01-21 23:53:11 +00:00
mat
900a4234e5 fix wrong ClientboundSoundEntity 2025-01-21 23:21:08 +00:00
mat
aef29daad9 remove negative checks for box_shape 2025-01-20 05:49:13 +00:00
mat
2dcfbe96c3 fix wrong ServerLinkKind and serialize hex colors correctly in nbt 2025-01-14 03:18:38 +00:00
mat
a86d011d4a change some warnings to debug logs and improve some entity id index code 2025-01-13 06:11:14 +00:00
mat
5721eaf193 fix ClientboundSound and implement az_registry::Holder 2025-01-13 04:52:40 +00:00
mat
862dec529b update warning in readme to say azalea is 'useable' instead of 'somewhat useable' 2025-01-12 23:01:55 +00:00
mat
a1435b3b95 fix bugs with decoding/encoding DataComponentPatch 2025-01-12 22:43:37 +00:00
mat
093c99a071 add AZALEA_DO_NOT_CUT_OFF_PACKET_LOGS env variable 2025-01-12 21:51:37 +00:00
mat
608ccb8e54 fix panic on bot disconnect 2025-01-12 05:07:53 +00:00
mat
a5cb21f039 codegen entity dimensions 2025-01-11 08:14:19 +00:00
mat
e03101a7b7 dead entities cannot be revived 2025-01-11 04:35:10 +00:00
mat
31d0ac7568 update wrong entity metadata 2025-01-11 04:09:39 +00:00
mat
55d1ae2014 revert doc updates 2025-01-10 23:54:33 +00:00
mat
9e98ab373f
Create CNAME 2025-01-10 17:34:34 -06:00
mat
3ce7c7cc50 tweaks to doc gh action 2025-01-10 23:08:51 +00:00
Aditya Kumar
9ef53fcf2b
Feat: Host Documentation for multiple versions/branch (#188)
* Better Docs

* Made action only to run on `main` or version branch

* Moved README.md to BRANCHES.md

* Redirect to main branch by default

* Moved Branches.md to branches.html
2025-01-10 17:08:23 -06:00
mat
0d16f01571
Fluid physics (#199)
* start implementing fluid physics

* Initial implementation of fluid pushing

* different travel function in water

* bubble columns

* jumping in water

* cleanup

* change ultrawarm to be required

* fix for clippy
2025-01-10 16:45:27 -06:00
mat
615d8f9d2a bump minimum rust version and improve pathfinder docs 2024-12-28 02:10:05 +00:00
mat
ebaf5128fb better pathfinder debug messages 2024-12-28 01:48:25 +00:00
mat
5693191b57 implement fluid_shape 2024-12-27 12:35:25 +00:00
mat
04036b6e4a implement BlockState::outline_shape 2024-12-27 12:35:18 +00:00
mat
6ccd44e28d better astar WeightedNode::cmp 2024-12-27 06:57:08 +00:00
mat
185ed84dbb don't save Movement type while pathfinding as an optimization, recalculate it later in reconstruct_path 2024-12-27 06:27:16 +00:00
mat
3c3952bb0b resolve some todos in az-core 2024-12-27 05:43:35 +00:00
mat
33e1a1326a patch path on timeout instead of recalculating everything 2024-12-26 12:36:41 +00:00
mat
344834c724 better pathfinder timeouts 2024-12-26 08:03:55 +00:00
mat
adb56b7eb2 make a_star function use an IndexMap like the pathfinding crate 2024-12-26 07:42:35 +00:00
mat
3c83e5b24a replace priority_queue crate with std BinaryHeap 2024-12-26 05:52:46 +00:00
mat
e11a902fba patch pathfinder obstructions instead of just truncating the path 2024-12-25 12:09:27 +00:00
mat
2f1fe5f9f6 partial fix for sending Config packets during Game 2024-12-25 09:58:32 +00:00
mat
8f0d0d9280 close tcp connection on bot disconnect and add swarms to testbot cli 2024-12-25 07:27:09 +00:00
mat
04eaa5c3d0 remove dependency on bytes crate for azalea-protocol and fix memory leak 2024-12-25 06:16:10 +00:00
mat
0ee9ed50e3 optimize pathfinder 2024-12-25 03:00:48 +00:00
mat
d67aa07c13 optimize cost_for_breaking_block by making its cache an UnsafeCell instead 2024-12-24 12:41:18 +00:00
mat
a599b5614e make BlockState a u16 and add a BlockStateIntegerRepr type 2024-12-24 09:40:29 +00:00
mat
f03e0c2235 fix parsing Dust particle and treat waterlogged blocks as liquid in pathfinder 2024-12-24 08:48:36 +00:00
mat
de5a53ce08 add additional pathfinder test and fix pathfinder failure 2024-12-24 05:18:33 +00:00
mat
958848e8ed improve some docs and apis related to pathfinder 2024-12-24 04:37:55 +00:00
mat
30cbeecdfe update outdated doc in Swarm and remove unnecessary generics 2024-12-23 13:10:40 +00:00
mat
8772661772 lift requirement on anyhow for handler function 2024-12-23 10:34:44 +00:00
mat
1609b90a93 rename metadata::State to metadata::SnifferState 2024-12-23 09:55:21 +00:00
mat
fa132b61fc add a bit more logging to handshake_proxy 2024-12-23 07:31:51 +00:00
mat
2c37ade959 add a test for 2b2t queue chat message 2024-12-23 02:01:58 +00:00
mat
63fef9d94f fix 'extra' parsing in FormattedText::from_nbt_compound which results in duplicated chat fragments 2024-12-23 02:01:09 +00:00
mat
a477a84c6e exit get_block_state early if below the world 2024-12-23 01:31:17 +00:00
mat
3b1fe490b0 make testbot take cli args 2024-12-23 01:12:27 +00:00
mat
478fe722f5 fix unlinked link in comment 2024-12-21 03:09:40 +00:00
mat
641b99c7af update trust-dns -> hickory-dns 2024-12-21 03:06:56 +00:00
mat
98e5efb577 fix flakey pathfinder tests by moving stop_pathfinding_on_instance_change to be before path_found_listener 2024-12-21 02:57:19 +00:00
mat
831e8c167f fix compiling azalea-registry 2024-12-21 02:40:53 +00:00
mat
ad30950f85 Release independent packages
Generated by cargo-workspaces
2024-12-20 19:22:29 -06:00
562 changed files with 37387 additions and 17073 deletions

View file

@ -1,33 +1,46 @@
# This file was borrowed from Bevy: https://github.com/bevyengine/bevy/blob/main/.cargo/config_fast_builds
# This file is based on Bevy's fast builds config: https://github.com/bevyengine/bevy/blob/main/.cargo/config_fast_builds.toml
# Add the contents of this file to `config.toml` to enable "fast build" configuration. Please read the notes below.
# NOTE: For maximum performance, build using a nightly compiler
# If you are using rust stable, remove the "-Zshare-generics=y" below.
[target.x86_64-unknown-linux-gnu]
linker = "clang"
rustflags = ["-Clink-arg=-fuse-ld=lld", "-Zshare-generics=y"]
rustflags = [
# LLD linker
#
# You may need to install it:
#
# - Ubuntu: `sudo apt-get install lld clang`
# - Fedora: `sudo dnf install lld clang`
# - Arch: `sudo pacman -S lld clang`
"-Clink-arg=-fuse-ld=lld",
# Mold linker
#
# You may need to install it:
#
# - Ubuntu: `sudo apt-get install mold clang`
# - Fedora: `sudo dnf install mold clang`
# - Arch: `sudo pacman -S mold clang`
# "-Clink-arg=-fuse-ld=mold",
# Nightly
"-Zshare-generics=y",
]
# NOTE: you must install [Mach-O LLD Port](https://lld.llvm.org/MachO/index.html) on mac. you can easily do this by installing llvm which includes lld with the "brew" package manager:
# `brew install llvm`
[target.x86_64-apple-darwin]
rustflags = [
"-C",
"link-arg=-fuse-ld=/usr/local/opt/llvm/bin/ld64.lld",
"-Zshare-generics=y",
]
rustflags = ["-Zshare-generics=y"]
[target.aarch64-apple-darwin]
rustflags = [
"-C",
"link-arg=-fuse-ld=/opt/homebrew/opt/llvm/bin/ld64.lld",
"-Zshare-generics=y",
]
rustflags = ["-Zshare-generics=y"]
[target.x86_64-pc-windows-msvc]
linker = "rust-lld.exe"
rustflags = ["-Zshare-generics=n"]
rustflags = [
# This needs to be off if you use dynamic linking on Windows.
"-Zshare-generics=n",
]
# Optional: Uncommenting the following improves compile times, but reduces the amount of debug info to 'line number tables only'
# In most cases the gains are negligible, but if you are on macos and have slow compile times you should see significant gains.

0
.github/workflows/check.yml vendored Executable file → Normal file
View file

View file

@ -1,45 +1,44 @@
name: Doc
on:
push:
branches:
- main
workflow_dispatch:
push:
branches:
- main
workflow_dispatch:
permissions:
contents: write
pages: write
id-token: write
contents: write
pages: write
id-token: write
concurrency:
group: "pages"
cancel-in-progress: true
group: "pages"
cancel-in-progress: true
jobs:
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v3
- uses: actions-rs/toolchain@v1
with:
toolchain: nightly
- run: cargo doc --workspace --no-deps
- uses: "finnp/create-file-action@master"
env:
FILE_NAME: "./target/doc/index.html"
FILE_DATA: '<!DOCTYPE html><html><head><meta http-equiv="refresh" content="0;url=''./azalea''"/></head></html>' # Redirect to default page
- name: Setup Pages
uses: actions/configure-pages@v2
- name: Upload artifact
uses: actions/upload-pages-artifact@v1
with:
path: './target/doc/'
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v1
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v3
- uses: actions-rs/toolchain@v1
with:
toolchain: nightly
- run: cargo doc --workspace --no-deps
- uses: "finnp/create-file-action@master"
env:
FILE_NAME: "./target/doc/index.html"
FILE_DATA: '<!DOCTYPE html><html><head><meta http-equiv="refresh" content="0;url=''./azalea''"/></head></html>' # Redirect to default page
- name: Setup Pages
uses: actions/configure-pages@v2
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: "./target/doc/"
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4

3
.gitignore vendored Executable file → Normal file
View file

@ -17,3 +17,6 @@ perf.data.old
heaptrack.*
rustc-ice-*
# not created by azalea itself, sometimes used for debugging since the docs specifically mentions using azalea.log
azalea.log

0
.gitpod.yml Executable file → Normal file
View file

93
CHANGELOG.md Normal file
View file

@ -0,0 +1,93 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
Due to the complexity of Azalea and the fact that almost every Minecraft version
is breaking anyways, semantic versioning is not followed.
## [Unreleased]
### Added
- `HitResult` now contains the entity that's being looked at.
- A `QueuedServerBlockUpdates` component that keeps track of block updates per `Update`.
- Local clients now have a `TicksConnected` component. (@Kumpelinus)
### Changed
- Update to Minecraft 1.21.8.
- Renamed `azalea_entity::EntityKind` to `EntityKindComponent` to disambiguate with `azalea_registry::EntityKind`.
- Moved functions and types related to hit results from `azalea::interact` to `azalea::interact::pick`.
- `Client::attack` now takes `Entity` instead of `MinecraftEntityId`.
### Fixed
- Fix packet order for loading (`PlayerLoaded`/`MovePlayerPos`) and sprinting (`PlayerInput`/`PlayerCommand`).
- Clients no longer send invalid look directions if the server teleports us with one.
- Movement code was updated with the changes from 1.21.5, so it no longer flags Grim.
- `azalea-chat` now correctly handles arrays of integers in the `with` field. (@qwqawawow)
## [0.13.0+mc1.21.5] - 2025-06-15
### Added
- This changelog. To see changes before this update, look at the git commits.
- azalea and azalea-client now have a `packet-event` feature, which can be disabled for efficiency if you're not using `Event::Packet`.
- `StartJoinServerEvent` can now be used to join servers exclusively from the ECS without a Tokio runtime.
- Add `FormattedText::to_html` and `FormattedText::to_custom_format`. (@Kumpelinus)
- Non-standard legacy hex colors like `§#ff0000` are now supported in azalea-chat.
- Chat signing.
- Add auto-reconnecting which is enabled by default.
- `ClientBuilder` and `SwarmBuilder` are now Send.
- Add `Client::start_use_item`.
- The pathfinder no longer avoids slabs, stairs, and dirt path blocks.
- The pathfinder now immediately recalculates if blocks are placed in its path.
- Bots that use custom pathfinder moves can now keep arbitrary persistent state by using the `CustomPathfinderState` component and `PathfinderCtx::custom_state`.
- The reach distance for the pathfinder `ReachBlockPosGoal` is now configurable. (@x-osc)
- There is now a `retry_on_no_path` option in `GotoEvent` that can be set to false to make the pathfinder give up if no path could be found.
- azalea-brigadier now supports suggestions, command contexts, result consumers, and returning errors with `ArgumentBuilder::executes_result`.
- Proper support for getting biomes at coordinates.
- Add a new `Client::entities_by` which sorts entities that match a criteria by their distance to the client.
- New client event `Event::ReceiveChunk`.
- Several new functions for interacting with inventories (`Client::get_inventory`, `get_held_item`, `ContainerHandleRef::left_click`, `shift_click`, `right_click`, `slots`).
- Add `Client::mine_with_auto_tool`.
- Add `Client::set_selected_hotbar_slot` and `Client::selected_hotbar_slot`.
- Add `Client::attack_cooldown_remaining_ticks` to complement `has_attack_cooldown`.
- Add `BlockPos::length`, `distance_to`, and `center_bottom`.
### Changed
- `Client::goto` is now async and completes when the client reaches its destination. `Client::start_goto` should be used if the old behavior is desired.
- The `BlockState::id` field is now private, use `.id()` instead.
- Update to [Bevy 0.16](https://bevyengine.org/news/bevy-0-16/).
- Rename `InstanceContainer::insert` to `get_or_insert`.
- Replace `BlockInteractEvent` with the more general-purpose `StartUseItemEvent`.
- Replace `wait_one_tick` and `wait_one_update` with `wait_ticks` and `wait_updates`.
- Functions that took `&Vec3` or `&BlockPos` as arguments now only take them as owned types.
- Rename `azalea_block::Block` to `BlockTrait` to disambiguate with `azalea_registry::Block`.
- `GotoEvent` is now non-enhaustive and should instead be constructed by calling its methods.
### Fixed
- Clients now validate incoming packets using the correct `MAXIMUM_UNCOMPRESSED_LENGTH` value.
- Several protocol fixes, including for `ClientboundSetPlayerTeam` and a few data components.
- No more chunk errors when the client joins another world with the same name but different height.
- Update the `InstanceName` component correctly when we receive a respawn or second login packet.
- azalea-chat now handles legacy color codes correctly when parsing from NBT.
- Send the correct UUID to servers in `ClientboundHello` when we're joining in offline-mode.
- Block shapes and some properties were using data from `1.20.3-pre4` due to using an old data generator (Pixlyzer), which has now been replaced with the data generator from [Pumpkin](https://github.com/Pumpkin-MC/Extractor).
- When patching the path, don't replace the move we're currently executing.
- The correct sequence number is now sent when interacting with blocks.
- Mining is now generally more reliable and doesn't flag Grim.
- Ghost blocks are now handled correctly due to implementing `ClientboundBlockChangedAck`.
- Player eye height was wrong due to being calculated from height instead of being a special case (was 1.53, should've been 1.62).
- The player inventory is now correctly updated when we close a container.
- Inventory interactions are now predicted on the client-side again, and the remaining click operations were implemented.
- `Client::open_container_at` now waits up to 10 ticks for the block to exist if you try to click air.
- Wrong physics collision code resulted in `HitResult` sometimes containing the wrong coordinates and `inside` value.
- Fix the client being unresponsive for a few seconds after joining due to not sending `ServerboundPlayerLoaded`.
- Fix panic when a client received `ClientboundAddEntity` and `ClientboundStartConfiguration` at the same time.
- Fix panic due to `ClientInformation` being inserted too late.
- `ClientboundTeleportEntity` did not handle relative teleports correctly.
- Pathfinder now gets stuck in water less by automatically trying to jump if it's in water.

2153
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -22,64 +22,87 @@ resolver = "2"
# --- Workspace Settings ---
[workspace.package]
version = "0.11.0+mc1.21.4"
edition = "2021"
version = "0.13.0+mc1.21.8"
edition = "2024"
license = "MIT"
repository = "https://github.com/azalea-rs/azalea"
# homepage = "https://github.com/azalea-rs/azalea"
[workspace.dependencies]
simdnbt = { version = "0.7", git = "https://github.com/azalea-rs/simdnbt" }
aes = "0.8.4"
anyhow = "1.0.94"
anyhow = "1.0.98"
async-recursion = "1.1.1"
async-trait = "0.1.83"
base64 = "0.22.1"
bevy_app = "0.15.0"
bevy_ecs = { version = "0.15.0", default-features = false }
bevy_log = "0.15.0"
bevy_tasks = "0.15.0"
bevy_time = "0.15.0"
bevy_app = "0.16.1"
bevy_ecs = { version = "0.16.1", default-features = false }
bevy_log = "0.16.1"
bevy_tasks = "0.16.1"
bevy_time = "0.16.1"
byteorder = "1.5.0"
bytes = "1.9.0"
cfb8 = "0.8.1"
chrono = { version = "0.4.39", default-features = false }
criterion = "0.5.1"
derive_more = "1.0.0"
chrono = { version = "0.4.41", default-features = false }
criterion = "0.7.0"
derive_more = "2.0.1"
enum-as-inner = "0.6.1"
env_logger = "0.11.6"
flate2 = "1.0.35"
env_logger = "0.11.8"
flate2 = { version = "1.1.2", features = ["zlib-rs"] }
futures = "0.3.31"
futures-lite = "2.5.0"
log = "0.4.22"
futures-lite = "2.6.0"
md-5 = "0.10.6"
minecraft_folder_path = "0.1.2"
nohash-hasher = "0.2.0"
num-bigint = "0.4.6"
num-traits = "0.2.19"
parking_lot = "0.12.3"
priority-queue = "2.1.1"
proc-macro2 = "1.0.92"
quote = "1.0.37"
rand = "0.8.5"
parking_lot = "0.12.4"
proc-macro2 = "1.0.95"
quote = "1.0.40"
rand = "0.9.2"
regex = "1.11.1"
reqwest = { version = "0.12.9", default-features = false }
rsa = "0.9.7"
reqwest = { version = "0.12.22", default-features = false }
rsa = "0.10.0-rc.3"
rsa_public_encrypt_pkcs1 = "0.4.0"
rustc-hash = "2.1.0"
serde = "1.0.216"
serde_json = "1.0.133"
rustc-hash = "2.1.1"
serde = "1.0.219"
serde_json = "1.0.142"
sha-1 = "0.10.1"
sha2 = "0.10.8"
simdnbt = "0.6"
socks5-impl = "0.6.0"
syn = "2.0.90"
thiserror = "2.0.8"
tokio = "1.42.0"
tokio-util = "0.7.13"
sha2 = "0.11.0-rc.0"
socks5-impl = "0.7.2"
syn = "2.0.104"
thiserror = "2.0.12"
tokio = "1.47.1"
tokio-util = "0.7.15"
tracing = "0.1.41"
tracing-subscriber = "0.3.19"
trust-dns-resolver = { version = "0.23.2", default-features = false }
uuid = "1.11.0"
hickory-resolver = "0.25.2"
uuid = "1.17"
num-format = "0.4.4"
indexmap = "2.10.0"
paste = "1.0.15"
compact_str = "0.9.0"
crc32fast = "1.5.0"
async-compat = "0.2.4"
azalea-block-macros = { path = "azalea-block/azalea-block-macros", version = "0.13.0" }
azalea-block = { path = "azalea-block", version = "0.13.0" }
azalea-auth = { path = "azalea-auth", version = "0.13.0" }
azalea-brigadier = { path = "azalea-brigadier", version = "0.13.0" }
azalea-buf-macros = { path = "azalea-buf/azalea-buf-macros", version = "0.13.0" }
azalea-buf = { path = "azalea-buf", version = "0.13.0" }
azalea-chat = { path = "azalea-chat", version = "0.13.0" }
azalea-client = { path = "azalea-client", version = "0.13.0", default-features = false }
azalea-core = { path = "azalea-core", version = "0.13.0" }
azalea-crypto = { path = "azalea-crypto", version = "0.13.0" }
azalea-entity = { path = "azalea-entity", version = "0.13.0" }
azalea-inventory-macros = { path = "azalea-inventory/azalea-inventory-macros", version = "0.13.0" }
azalea-inventory = { path = "azalea-inventory", version = "0.13.0" }
azalea-language = { path = "azalea-language", version = "0.13.0" }
azalea-physics = { path = "azalea-physics", version = "0.13.0" }
azalea-protocol-macros = { path = "azalea-protocol/azalea-protocol-macros", version = "0.13.0" }
azalea-protocol = { path = "azalea-protocol", version = "0.13.0" }
azalea-registry-macros = { path = "azalea-registry/azalea-registry-macros", version = "0.13.0" }
azalea-registry = { path = "azalea-registry", version = "0.13.0" }
azalea-world = { path = "azalea-world", version = "0.13.0" }
# --- Profile Settings ---

43
README.md Executable file → Normal file
View file

@ -8,23 +8,23 @@ A collection of Rust crates for making Minecraft bots, clients, and tools.
<img src="https://github.com/azalea-rs/azalea/assets/27899617/b98a42df-5cf0-4d1f-ae7c-ecca333e3cab" alt="Azalea" height="200">
</p>
<!-- The line below is automatically read and updated by the migrate script, so don't change it manually. -->
_Currently supported Minecraft version: `1.21.4`._
_Currently supported Minecraft version: `1.21.8`._
> [!WARNING]
> Azalea is still very unfinished, though most crates are in a somewhat useable state
> Azalea is still unfinished, though most crates are in a useable state.
## Features
- [Accurate physics](https://github.com/azalea-rs/azalea/blob/main/azalea-physics/src/lib.rs) (but some features like entity collisions and water physics aren't yet implemented)
- [Accurate physics](https://github.com/azalea-rs/azalea/blob/main/azalea-physics/src/lib.rs) (but some features like entity pushing and elytras aren't implemented yet)
- [Pathfinder](https://azalea.matdoes.dev/azalea/pathfinder/index.html)
- [Swarms](https://azalea.matdoes.dev/azalea/swarm/index.html)
- [Breaking blocks](https://azalea.matdoes.dev/azalea/struct.Client.html#method.mine)
- [Block interactions & building](https://azalea.matdoes.dev/azalea/struct.Client.html#method.block_interact) (this doesn't predict the block interactions/placement on the client yet but it's usually fine)
- [Block interactions & building](https://azalea.matdoes.dev/azalea/struct.Client.html#method.block_interact) (this doesn't predict the block interactions/placement on the client yet, but it's usually fine)
- [Inventories](https://azalea.matdoes.dev/azalea/struct.Client.html#impl-ContainerClientExt-for-Client)
- [Attacking entities](https://azalea.matdoes.dev/azalea/struct.Client.html#method.attack) (but you can't get the entity at the crosshair yet)
- [Attacking entities](https://azalea.matdoes.dev/azalea/struct.Client.html#method.attack)
- [Plugins](#plugins)
## Docs
@ -49,18 +49,23 @@ If you'd like to chat about Azalea, you can join the Matrix space at [#azalea:ma
- Bedrock edition.
- Graphics.
## Branches
## Real-world bots using Azalea
There are several branches in the Azalea repository that target older Minecraft versions.
Most of them are severely outdated compared to the latest version of Azalea.
If you'd like to update them or add more, please open a PR.
Here's an incomplete list of bots built using Azalea, primarily intended as a reference in addition to the existing documentation and examples:
- [1.21.2-1.21.3](https://github.com/azalea-rs/azalea/tree/1.21.3)
- [1.21-1.21.1](https://github.com/azalea-rs/azalea/tree/1.21.1)
- [1.20.5-1.20.6](https://github.com/azalea-rs/azalea/tree/1.20.6)
- [1.20.4](https://github.com/azalea-rs/azalea/tree/1.20.4)
- [1.20.2](https://github.com/azalea-rs/azalea/tree/1.20.2)
- [1.20-1.20.1](https://github.com/azalea-rs/azalea/tree/1.20.1)
- [1.19.4](https://github.com/azalea-rs/azalea/tree/1.19.4)
- [1.19.3](https://github.com/azalea-rs/azalea/tree/1.19.3)
- [1.19.2](https://github.com/azalea-rs/azalea/tree/1.19.2)
- [ShayBox/ShaysBot](https://github.com/ShayBox/ShaysBot) - Pearl stasis bot featuring a Discord bot, an HTTP API, and more.
- [EnderKill98/statis-bot](https://github.com/EnderKill98/stasis-bot) - This bot can automatically detect thrown pearls and later walk there and pull them for you.
- [as1100k/aether](https://github.com/as1100k/aether) - Collection of Minecraft bots and plugins.
- [mat-1/potato-bot-2](https://github.com/mat-1/potato-bot-2) - Hardened Discord chat bridge created for the LiveOverflow SMP.
- [ErrorNoInternet/ErrorNoWatcher](https://github.com/ErrorNoInternet/ErrorNoWatcher) - A Minecraft bot with Lua scripting support.
You can see more projects built with Azalea in the [GitHub dependency graph](https://github.com/azalea-rs/azalea/network/dependents).
## Plugins
Azalea has support for Bevy plugins, which can significantly alter its functionality. Here are some plugins that you may find useful:
- [azalea-rs/azalea-viaversion](https://github.com/azalea-rs/azalea-viaversion) - Multi-version compatibility for your Azalea bots using ViaProxy.
- [azalea-rs/azalea-hax](https://github.com/azalea-rs/azalea-hax) - Anti-knockback.
If you've created your own plugin for Azalea, please create a PR to add it to this list :).

View file

@ -1,26 +1,29 @@
[package]
name = "azalea-auth"
description = "A port of Mojang's Authlib and launcher authentication."
version = { workspace = true }
edition = { workspace = true }
license = { workspace = true }
repository = { workspace = true }
version.workspace = true
edition.workspace = true
license.workspace = true
repository.workspace = true
[dependencies]
azalea-buf = { path = "../azalea-buf", version = "0.11.0+mc1.21.4" }
azalea-crypto = { path = "../azalea-crypto", version = "0.11.0+mc1.21.4" }
base64 = { workspace = true }
azalea-buf.workspace = true
azalea-crypto.workspace = true
base64.workspace = true
chrono = { workspace = true, features = ["serde"] }
md-5 = { workspace = true }
reqwest = { workspace = true, features = ["json", "rustls-tls"] }
rsa = { workspace = true }
md-5.workspace = true
reqwest = { workspace = true, default-features = false, features = [
"json",
"rustls-tls",
] }
rsa.workspace = true
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
thiserror = { workspace = true }
serde_json.workspace = true
thiserror.workspace = true
tokio = { workspace = true, features = ["fs"] }
tracing = { workspace = true }
tracing.workspace = true
uuid = { workspace = true, features = ["serde", "v3"] }
[dev-dependencies]
env_logger = { workspace = true }
env_logger.workspace = true
tokio = { workspace = true, features = ["full"] }

4
azalea-auth/README.md Executable file → Normal file
View file

@ -2,6 +2,8 @@
A port of Mojang's Authlib and launcher authentication.
The default location of Azalea's cache is at `~/.minecraft/azalea-auth.json`. You can delete or modify this file if you'd like to associate an email with a different account.
# Examples
```no_run
@ -24,4 +26,4 @@ async fn main() {
}
```
Thanks to [wiki.vg contributors](https://wiki.vg/Microsoft_Authentication_Scheme), [Overhash](https://gist.github.com/OverHash/a71b32846612ba09d8f79c9d775bfadf), and [prismarine-auth contributors](https://github.com/PrismarineJS/prismarine-auth).
Thanks to [wiki contributors](https://minecraft.wiki/w/Microsoft_authentication), [Overhash](https://gist.github.com/OverHash/a71b32846612ba09d8f79c9d775bfadf), and [prismarine-auth contributors](https://github.com/PrismarineJS/prismarine-auth).

0
azalea-auth/examples/auth.rs Executable file → Normal file
View file

0
azalea-auth/examples/auth_manual.rs Executable file → Normal file
View file

0
azalea-auth/examples/certificates.rs Executable file → Normal file
View file

42
azalea-auth/src/auth.rs Executable file → Normal file
View file

@ -3,13 +3,14 @@
use std::{
collections::HashMap,
path::PathBuf,
time::{Instant, SystemTime, UNIX_EPOCH},
time::{Duration, Instant, SystemTime, UNIX_EPOCH},
};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use serde_json::json;
use thiserror::Error;
use tokio::time::sleep;
use tracing::{error, trace};
use uuid::Uuid;
@ -75,8 +76,9 @@ pub async fn auth(email: &str, opts: AuthOpts<'_>) -> Result<AuthResult, AuthErr
None
};
if cached_account.is_some() && !cached_account.as_ref().unwrap().mca.is_expired() {
let account = cached_account.as_ref().unwrap();
if let Some(account) = &cached_account
&& !account.mca.is_expired()
{
// the minecraft auth data is cached and not expired, so we can just
// use that instead of doing auth all over again :)
@ -129,8 +131,8 @@ pub async fn auth(email: &str, opts: AuthOpts<'_>) -> Result<AuthResult, AuthErr
let profile: ProfileResponse = get_profile(&client, &res.minecraft_access_token).await?;
if let Some(cache_file) = opts.cache_file {
if let Err(e) = cache::set_account_in_cache(
if let Some(cache_file) = opts.cache_file
&& let Err(e) = cache::set_account_in_cache(
&cache_file,
email,
CachedAccount {
@ -142,9 +144,8 @@ pub async fn auth(email: &str, opts: AuthOpts<'_>) -> Result<AuthResult, AuthErr
},
)
.await
{
error!("{}", e);
}
{
error!("{}", e);
}
Ok(AuthResult {
@ -328,7 +329,7 @@ pub async fn get_ms_link_code(
Ok(client
.post("https://login.live.com/oauth20_connect.srf")
.form(&vec![
.form(&[
("scope", scope),
("client_id", client_id),
("response_type", "device_code"),
@ -354,17 +355,17 @@ pub async fn get_ms_auth_token(
CLIENT_ID
};
let login_expires_at = Instant::now() + std::time::Duration::from_secs(res.expires_in);
let login_expires_at = Instant::now() + Duration::from_secs(res.expires_in);
while Instant::now() < login_expires_at {
tokio::time::sleep(std::time::Duration::from_secs(res.interval)).await;
sleep(Duration::from_secs(res.interval)).await;
trace!("Polling to check if user has logged in...");
if let Ok(access_token_response) = client
let res = client
.post(format!(
"https://login.live.com/oauth20_token.srf?client_id={client_id}"
))
.form(&vec![
.form(&[
("client_id", client_id),
("device_code", &res.device_code),
("grant_type", "urn:ietf:params:oauth:grant-type:device_code"),
@ -372,11 +373,11 @@ pub async fn get_ms_auth_token(
.send()
.await?
.json::<AccessTokenResponse>()
.await
{
.await;
if let Ok(access_token_response) = res {
trace!("access_token_response: {:?}", access_token_response);
let expires_at = SystemTime::now()
+ std::time::Duration::from_secs(access_token_response.expires_in);
let expires_at =
SystemTime::now() + Duration::from_secs(access_token_response.expires_in);
return Ok(ExpiringValue {
data: access_token_response,
expires_at: expires_at
@ -428,7 +429,7 @@ pub async fn refresh_ms_auth_token(
let access_token_response_text = client
.post("https://login.live.com/oauth20_token.srf")
.form(&vec![
.form(&[
("scope", scope),
("client_id", client_id),
("grant_type", "refresh_token"),
@ -441,8 +442,7 @@ pub async fn refresh_ms_auth_token(
let access_token_response: AccessTokenResponse =
serde_json::from_str(&access_token_response_text)?;
let expires_at =
SystemTime::now() + std::time::Duration::from_secs(access_token_response.expires_in);
let expires_at = SystemTime::now() + Duration::from_secs(access_token_response.expires_in);
Ok(ExpiringValue {
data: access_token_response,
expires_at: expires_at
@ -558,7 +558,7 @@ async fn auth_with_minecraft(
.await?;
trace!("{:?}", res);
let expires_at = SystemTime::now() + std::time::Duration::from_secs(res.expires_in);
let expires_at = SystemTime::now() + Duration::from_secs(res.expires_in);
Ok(ExpiringValue {
data: res,
// to seconds since epoch

23
azalea-auth/src/cache.rs Executable file → Normal file
View file

@ -1,22 +1,27 @@
//! Cache auth information
use std::path::Path;
use std::time::{SystemTime, UNIX_EPOCH};
use std::{
io,
path::Path,
time::{SystemTime, UNIX_EPOCH},
};
use serde::{Deserialize, Serialize};
use thiserror::Error;
use tokio::fs::File;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::{
fs::{self, File},
io::{AsyncReadExt, AsyncWriteExt},
};
use tracing::{debug, trace};
#[derive(Debug, Error)]
pub enum CacheError {
#[error("Failed to read cache file: {0}")]
Read(std::io::Error),
Read(io::Error),
#[error("Failed to write cache file: {0}")]
Write(std::io::Error),
Write(io::Error),
#[error("Failed to create cache file directory: {0}")]
MkDir(std::io::Error),
MkDir(io::Error),
#[error("Failed to parse cache file: {0}")]
Parse(serde_json::Error),
}
@ -94,7 +99,9 @@ async fn set_entire_cache(cache_file: &Path, cache: Vec<CachedAccount>) -> Resul
"Making cache file parent directory at {}",
cache_file_parent.to_string_lossy()
);
std::fs::create_dir_all(cache_file_parent).map_err(CacheError::MkDir)?;
fs::create_dir_all(cache_file_parent)
.await
.map_err(CacheError::MkDir)?;
}
let mut cache_file = File::create(cache_file).await.map_err(CacheError::Write)?;
let cache = serde_json::to_string_pretty(&cache).map_err(CacheError::Parse)?;

View file

@ -1,6 +1,6 @@
use base64::Engine;
use chrono::{DateTime, Utc};
use rsa::{pkcs8::DecodePrivateKey, RsaPrivateKey};
use rsa::{RsaPrivateKey, pkcs8::DecodePrivateKey};
use serde::Deserialize;
use thiserror::Error;
use tracing::trace;

19
azalea-auth/src/game_profile.rs Executable file → Normal file
View file

@ -1,4 +1,4 @@
use std::collections::HashMap;
use std::{collections::HashMap, sync::Arc};
use azalea_buf::AzBuf;
use serde::{Deserialize, Serialize};
@ -10,7 +10,8 @@ pub struct GameProfile {
pub uuid: Uuid,
/// The username of the player.
pub name: String,
pub properties: HashMap<String, ProfilePropertyValue>,
// this is an arc to make GameProfile cheaper to clone when the properties are big
pub properties: Arc<HashMap<String, ProfilePropertyValue>>,
}
impl GameProfile {
@ -18,7 +19,7 @@ impl GameProfile {
GameProfile {
uuid,
name,
properties: HashMap::new(),
properties: Arc::new(HashMap::new()),
}
}
}
@ -38,7 +39,7 @@ impl From<SerializableGameProfile> for GameProfile {
Self {
uuid: value.id,
name: value.name,
properties,
properties: Arc::new(properties),
}
}
}
@ -59,11 +60,11 @@ pub struct SerializableGameProfile {
impl From<GameProfile> for SerializableGameProfile {
fn from(value: GameProfile) -> Self {
let mut properties = Vec::new();
for (key, value) in value.properties {
for (key, value) in &*value.properties {
properties.push(SerializableProfilePropertyValue {
name: key,
value: value.value,
signature: value.signature,
name: key.clone(),
value: value.value.clone(),
signature: value.signature.clone(),
});
}
Self {
@ -114,7 +115,7 @@ mod tests {
signature: Some("zxcv".to_string()),
},
);
map
map.into()
},
}
);

0
azalea-auth/src/lib.rs Executable file → Normal file
View file

2
azalea-auth/src/sessionserver.rs Executable file → Normal file
View file

@ -159,7 +159,7 @@ pub async fn serverside_auth(
StatusCode::FORBIDDEN => {
return Err(ServerSessionServerError::Unknown(
res.json::<ForbiddenError>().await?.error,
))
));
}
status_code => {
// log the headers

View file

@ -1,12 +1,12 @@
[package]
name = "azalea-block"
description = "Representation of Minecraft block states."
version = { workspace = true }
edition = { workspace = true }
license = { workspace = true }
repository = { workspace = true }
version.workspace = true
edition.workspace = true
license.workspace = true
repository.workspace = true
[dependencies]
azalea-block-macros = { path = "./azalea-block-macros", version = "0.11.0+mc1.21.4" }
azalea-buf = { path = "../azalea-buf", version = "0.11.0+mc1.21.4" }
azalea-registry = { path = "../azalea-registry", version = "0.11.0+mc1.21.4" }
azalea-block-macros.workspace = true
azalea-buf.workspace = true
azalea-registry.workspace = true

14
azalea-block/README.md Executable file → Normal file
View file

@ -23,20 +23,20 @@ let block_state: BlockState = azalea_block::blocks::CobblestoneWall {
let block_state: BlockState = azalea_registry::Block::Jukebox.into();
```
## Block trait
## BlockTrait
The [`Block`] trait represents a type of a block. With the the [`Block`] trait, you can get some extra things like the string block ID and some information about the block's behavior. Also, the structs that implement the trait contain the block attributes as fields so it's more convenient to get them. Note that this is often used as `Box<dyn Block>`.
If for some reason you don't want the `Block` trait, set default-features to false.
The [`BlockTrait`] trait represents a type of a block. With [`BlockTrait`], you can get some extra things like the string block ID and some information about the block's behavior. Also, the structs that implement the trait contain the block attributes as fields so it's more convenient to get them. Note that this is often used as `Box<dyn BlockTrait>`.
If for some reason you don't want `BlockTrait`, set `default-features = false`.
```
# use azalea_block::{Block, BlockState};
# use azalea_block::{BlockTrait, BlockState};
# let block_state = BlockState::from(azalea_registry::Block::Jukebox);
let block = Box::<dyn Block>::from(block_state);
let block = Box::<dyn BlockTrait>::from(block_state);
```
```
# use azalea_block::{Block, BlockState};
# use azalea_block::{BlockTrait, BlockState};
# let block_state: BlockState = azalea_registry::Block::Jukebox.into();
if let Some(jukebox) = Box::<dyn Block>::from(block_state).downcast_ref::<azalea_block::blocks::Jukebox>() {
if let Some(jukebox) = Box::<dyn BlockTrait>::from(block_state).downcast_ref::<azalea_block::blocks::Jukebox>() {
// ...
}
```

View file

@ -1,15 +1,15 @@
[package]
name = "azalea-block-macros"
description = "Proc macros used by azalea-block."
version = { workspace = true }
edition = { workspace = true }
license = { workspace = true }
repository = { workspace = true }
version.workspace = true
edition.workspace = true
license.workspace = true
repository.workspace = true
[lib]
proc-macro = true
[dependencies]
proc-macro2 = { workspace = true }
quote = { workspace = true }
syn = { workspace = true }
proc-macro2.workspace = true
quote.workspace = true
syn.workspace = true

81
azalea-block/azalea-block-macros/src/lib.rs Executable file → Normal file
View file

@ -2,23 +2,24 @@
mod utils;
use std::collections::HashMap;
use std::fmt::Write;
use std::{collections::HashMap, fmt::Write};
use proc_macro::TokenStream;
use proc_macro2::TokenTree;
use quote::quote;
use syn::{
braced,
Expr, Ident, LitStr, Token, braced,
ext::IdentExt,
parenthesized,
parse::{Parse, ParseStream, Result},
parse_macro_input,
punctuated::Punctuated,
token, Expr, Ident, LitStr, Token,
token,
};
use utils::{combinations_of, to_pascal_case};
// must be the same as the type in `azalea-block/src/lib.rs`
type BlockStateIntegerRepr = u16;
enum PropertyType {
/// `Axis { X, Y, Z }`
Enum {
@ -275,7 +276,7 @@ impl Parse for MakeBlockStates {
}
struct PropertyVariantData {
pub block_state_ids: Vec<u32>,
pub block_state_ids: Vec<BlockStateIntegerRepr>,
pub ident: Ident,
pub is_enum: bool,
}
@ -288,7 +289,7 @@ pub fn make_block_states(input: TokenStream) -> TokenStream {
let mut properties_map = HashMap::new();
let mut property_struct_names_to_names = HashMap::new();
let mut state_id: u32 = 0;
let mut state_id: BlockStateIntegerRepr = 0;
for property in &input.property_definitions.properties {
let property_struct_name: Ident;
@ -339,8 +340,8 @@ pub fn make_block_states(input: TokenStream) -> TokenStream {
#property_enum_variants
}
impl From<u32> for #property_struct_name {
fn from(value: u32) -> Self {
impl From<crate::block_state::BlockStateIntegerRepr> for #property_struct_name {
fn from(value: crate::block_state::BlockStateIntegerRepr) -> Self {
match value {
#property_from_number_variants
_ => panic!("Invalid property value: {}", value),
@ -358,8 +359,8 @@ pub fn make_block_states(input: TokenStream) -> TokenStream {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct #property_struct_name(pub bool);
impl From<u32> for #property_struct_name {
fn from(value: u32) -> Self {
impl From<crate::block_state::BlockStateIntegerRepr> for #property_struct_name {
fn from(value: crate::block_state::BlockStateIntegerRepr) -> Self {
match value {
0 => Self(false),
1 => Self(true),
@ -509,13 +510,13 @@ pub fn make_block_states(input: TokenStream) -> TokenStream {
Ident::new(&combination[i].to_string(), proc_macro2::Span::call_site());
// this terrible code just gets the property default as a string
let property_default_as_string = if let TokenTree::Ident(ident) =
property.default.clone().into_iter().last().unwrap()
{
ident.to_string()
} else {
panic!()
};
let property_default_as_string =
match property.default.clone().into_iter().last().unwrap() {
TokenTree::Ident(ident) => ident.to_string(),
_ => {
panic!()
}
};
if property_default_as_string != combination[i] {
is_default = false;
}
@ -550,7 +551,7 @@ pub fn make_block_states(input: TokenStream) -> TokenStream {
from_block_to_state_match_inner.extend(quote! {
#block_struct_name {
#from_block_to_state_combination_match_inner
} => BlockState { id: #state_id },
} => BlockState::new_const(#state_id),
});
if is_default {
@ -563,15 +564,16 @@ pub fn make_block_states(input: TokenStream) -> TokenStream {
let Some(default_state_id) = default_state_id else {
let defaults = properties_with_name
.iter()
.map(|p| {
if let TokenTree::Ident(i) = p.default.clone().into_iter().last().unwrap() {
i.to_string()
} else {
.map(|p| match p.default.clone().into_iter().last().unwrap() {
TokenTree::Ident(i) => i.to_string(),
_ => {
panic!()
}
})
.collect::<Vec<_>>();
panic!("Couldn't get default state id for {block_name_pascal_case}, combinations={block_properties_vec:?}, defaults={defaults:?}")
panic!(
"Couldn't get default state id for {block_name_pascal_case}, combinations={block_properties_vec:?}, defaults={defaults:?}"
)
};
// 7035..=7058 => {
@ -583,7 +585,7 @@ pub fn make_block_states(input: TokenStream) -> TokenStream {
// }
// }
let mut from_state_to_block_inner = quote! {};
let mut division = 1u32;
let mut division: BlockStateIntegerRepr = 1;
for i in (0..properties_with_name.len()).rev() {
let PropertyWithNameAndDefault {
property_type: property_struct_name_ident,
@ -593,7 +595,7 @@ pub fn make_block_states(input: TokenStream) -> TokenStream {
} = &properties_with_name[i];
let property_variants = &block_properties_vec[i];
let property_variants_count = property_variants.len() as u32;
let property_variants_count = property_variants.len() as crate::BlockStateIntegerRepr;
let conversion_code = {
if &property_value_type.to_string() == "bool" {
assert_eq!(property_variants_count, 2);
@ -623,7 +625,7 @@ pub fn make_block_states(input: TokenStream) -> TokenStream {
azalea_registry::Block::#block_name_pascal_case => Box::new(#block_struct_name::default()),
});
from_registry_block_to_blockstate_match.extend(quote! {
azalea_registry::Block::#block_name_pascal_case => BlockState { id: #default_state_id },
azalea_registry::Block::#block_name_pascal_case => BlockState::new_const(#default_state_id),
});
from_registry_block_to_blockstates_match.extend(quote! {
azalea_registry::Block::#block_name_pascal_case => BlockStates::from(#first_state_id..=#last_state_id),
@ -643,7 +645,7 @@ pub fn make_block_states(input: TokenStream) -> TokenStream {
let block_id = block.name.to_string();
let from_block_to_state_match = if block.properties_and_defaults.is_empty() {
quote! { BlockState { id: #first_state_id } }
quote! { BlockState::new_const(#first_state_id) }
} else {
quote! {
match self {
@ -658,7 +660,7 @@ pub fn make_block_states(input: TokenStream) -> TokenStream {
#block_struct_fields
}
impl Block for #block_struct_name {
impl BlockTrait for #block_struct_name {
fn behavior(&self) -> BlockBehavior {
#block_behavior
}
@ -694,11 +696,8 @@ pub fn make_block_states(input: TokenStream) -> TokenStream {
let last_state_id = state_id - 1;
let mut generated = quote! {
impl BlockState {
/// Returns the highest possible state ID.
#[inline]
pub fn max_state() -> u32 {
#last_state_id
}
/// The highest possible block state ID.
pub const MAX_STATE: crate::block_state::BlockStateIntegerRepr = #last_state_id;
/// Get a property from this block state. Will be `None` if the block can't have the property.
///
@ -717,10 +716,10 @@ pub fn make_block_states(input: TokenStream) -> TokenStream {
// ```
// match state_id {
// // this is just an example of how it might look, these state ids are definitely not correct
// 0|3|6 => Some(Self::Axis::X),
// 1|4|7 => Some(Self::Axis::Y),
// 2|5|8 => Some(Self::Axis::Z),
// _ => None
// 0 | 3 | 6 => Some(Self::Axis::X),
// 1 | 4 | 7 => Some(Self::Axis::Y),
// 2 | 5 | 8 => Some(Self::Axis::Z),
// _ => None,
// }
// ```
let mut property_impls = quote! {};
@ -762,7 +761,7 @@ pub fn make_block_states(input: TokenStream) -> TokenStream {
type Value = #value;
fn try_from_block_state(block_state: BlockState) -> Option<Self::Value> {
match block_state.id {
match block_state.id() {
#enum_inner_generated
_ => None
}
@ -786,16 +785,16 @@ pub fn make_block_states(input: TokenStream) -> TokenStream {
#block_structs
impl From<BlockState> for Box<dyn Block> {
impl From<BlockState> for Box<dyn BlockTrait> {
fn from(block_state: BlockState) -> Self {
let b = block_state.id;
let b = block_state.id();
match b {
#from_state_to_block_match
_ => panic!("Invalid block state: {}", b),
}
}
}
impl From<azalea_registry::Block> for Box<dyn Block> {
impl From<azalea_registry::Block> for Box<dyn BlockTrait> {
fn from(block: azalea_registry::Block) -> Self {
match block {
#from_registry_block_to_block_match

0
azalea-block/azalea-block-macros/src/utils.rs Executable file → Normal file
View file

8
azalea-block/src/behavior.rs Executable file → Normal file
View file

@ -4,6 +4,8 @@ pub struct BlockBehavior {
pub destroy_time: f32,
pub explosion_resistance: f32,
pub requires_correct_tool_for_drops: bool,
pub force_solid: Option<bool>,
}
impl Default for BlockBehavior {
@ -14,6 +16,7 @@ impl Default for BlockBehavior {
destroy_time: 0.,
explosion_resistance: 0.,
requires_correct_tool_for_drops: false,
force_solid: None,
}
}
}
@ -52,4 +55,9 @@ impl BlockBehavior {
self.requires_correct_tool_for_drops = true;
self
}
pub fn force_solid(mut self, force_solid: bool) -> Self {
self.force_solid = Some(force_solid);
self
}
}

View file

@ -0,0 +1,178 @@
use std::{
fmt::{self, Debug},
io::{self, Cursor, Write},
};
use azalea_buf::{AzaleaRead, AzaleaReadVar, AzaleaWrite, AzaleaWriteVar, BufReadError};
use crate::BlockTrait;
/// The type that's used internally to represent a block state ID.
///
/// This should be either `u16` or `u32`. If you choose to modify it, you must
/// also change it in `azalea-block-macros/src/lib.rs`.
///
/// This does not affect protocol serialization, it just allows you to make the
/// internal type smaller if you want.
pub type BlockStateIntegerRepr = u16;
/// A representation of a state a block can be in.
///
/// For example, a stone block only has one state but each possible stair
/// rotation is a different state.
///
/// Note that this type is internally either a `u16` or `u32`, depending on
/// [`BlockStateIntegerRepr`].
#[derive(Copy, Clone, PartialEq, Eq, Default, Hash)]
pub struct BlockState {
/// The protocol ID for the block state. IDs may change every
/// version, so you shouldn't hard-code them or store them in databases.
id: BlockStateIntegerRepr,
}
impl BlockState {
/// A shortcut for getting the air block state, since it always has an ID of
/// 0.
pub const AIR: BlockState = BlockState { id: 0 };
/// Create a new BlockState and panic if the block is not a valid state.
///
/// You should probably use [`BlockState::try_from`] instead.
#[inline]
pub(crate) const fn new_const(id: BlockStateIntegerRepr) -> Self {
assert!(Self::is_valid_state(id));
Self { id }
}
/// Whether the block state is possible to exist in vanilla Minecraft.
///
/// It's equivalent to checking that the state ID is not greater than
/// [`Self::MAX_STATE`].
#[inline]
pub const fn is_valid_state(state_id: BlockStateIntegerRepr) -> bool {
state_id <= Self::MAX_STATE
}
/// Returns true if the block is air. This only checks for normal air, not
/// other types like cave air.
#[inline]
pub fn is_air(&self) -> bool {
self == &Self::AIR
}
/// Returns the protocol ID for the block state. IDs may change every
/// version, so you shouldn't hard-code them or store them in databases.
#[inline]
pub const fn id(&self) -> BlockStateIntegerRepr {
self.id
}
}
impl TryFrom<u32> for BlockState {
type Error = ();
/// Safely converts a u32 state id to a block state.
fn try_from(state_id: u32) -> Result<Self, Self::Error> {
let state_id = state_id as BlockStateIntegerRepr;
if Self::is_valid_state(state_id) {
Ok(BlockState { id: state_id })
} else {
Err(())
}
}
}
impl TryFrom<u16> for BlockState {
type Error = ();
/// Safely converts a u16 state id to a block state.
fn try_from(state_id: u16) -> Result<Self, Self::Error> {
let state_id = state_id as BlockStateIntegerRepr;
if Self::is_valid_state(state_id) {
Ok(BlockState { id: state_id })
} else {
Err(())
}
}
}
impl From<BlockState> for u32 {
/// See [`BlockState::id`].
fn from(value: BlockState) -> Self {
value.id as u32
}
}
impl AzaleaRead for BlockState {
fn azalea_read(buf: &mut Cursor<&[u8]>) -> Result<Self, BufReadError> {
let state_id = u32::azalea_read_var(buf)?;
Self::try_from(state_id).map_err(|_| BufReadError::UnexpectedEnumVariant {
id: state_id as i32,
})
}
}
impl AzaleaWrite for BlockState {
fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
u32::azalea_write_var(&(self.id as u32), buf)
}
}
impl Debug for BlockState {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"BlockState(id: {}, {:?})",
self.id,
Box::<dyn BlockTrait>::from(*self)
)
}
}
impl From<BlockState> for azalea_registry::Block {
fn from(value: BlockState) -> Self {
Box::<dyn BlockTrait>::from(value).as_registry_block()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_from_u32() {
assert_eq!(
BlockState::try_from(0 as BlockStateIntegerRepr).unwrap(),
BlockState::AIR
);
assert!(BlockState::try_from(BlockState::MAX_STATE).is_ok());
assert!(BlockState::try_from(BlockState::MAX_STATE + 1).is_err());
}
#[test]
fn test_from_blockstate() {
let block: Box<dyn BlockTrait> = Box::<dyn BlockTrait>::from(BlockState::AIR);
assert_eq!(block.id(), "air");
let block: Box<dyn BlockTrait> =
Box::<dyn BlockTrait>::from(BlockState::from(azalea_registry::Block::FloweringAzalea));
assert_eq!(block.id(), "flowering_azalea");
}
#[test]
fn test_debug_blockstate() {
let formatted = format!(
"{:?}",
BlockState::from(azalea_registry::Block::FloweringAzalea)
);
assert!(formatted.ends_with(", FloweringAzalea)"), "{}", formatted);
let formatted = format!(
"{:?}",
BlockState::from(azalea_registry::Block::BigDripleafStem)
);
assert!(
formatted.ends_with(", BigDripleafStem { facing: North, waterlogged: false })"),
"{}",
formatted
);
}
}

View file

@ -0,0 +1,137 @@
use crate::block_state::{BlockState, BlockStateIntegerRepr};
#[derive(Clone, Debug)]
pub struct FluidState {
pub kind: FluidKind,
/// 0 = empty, 8 = full, 9 = max.
///
/// 9 is meant to be used when there's another fluid block of the same type
/// above it, but it's usually unused by this struct.
///
/// This is different from [`crate::blocks::Water::level`], which is
/// basically the opposite (0 = full, 8 = empty). You can convert between
/// the two representations with [`to_or_from_legacy_fluid_level`].
pub amount: u8,
/// Whether this fluid is at the max level and there's another fluid of the
/// same type above it.
///
/// TODO: this is currently unused (always false), make this actually get
/// set (see FlowingFluid.getFlowing)
pub falling: bool,
}
#[derive(Default, Clone, Copy, Debug, PartialEq, Eq)]
pub enum FluidKind {
#[default]
Empty,
Water,
Lava,
}
impl FluidState {
pub fn new_source_block(kind: FluidKind, falling: bool) -> Self {
Self {
kind,
amount: 8,
falling,
}
}
/// A floating point number in between 0 and 1 representing the height (as a
/// percentage of a full block) of the fluid.
pub fn height(&self) -> f32 {
self.amount as f32 / 9.
}
pub fn is_empty(&self) -> bool {
self.amount == 0
}
pub fn affects_flow(&self, other: &FluidState) -> bool {
other.amount == 0 || self.is_same_kind(other)
}
pub fn is_same_kind(&self, other: &FluidState) -> bool {
(other.kind == self.kind) || (self.amount == 0 && other.amount == 0)
}
}
impl Default for FluidState {
fn default() -> Self {
Self {
kind: FluidKind::Empty,
amount: 0,
falling: false,
}
}
}
impl From<BlockState> for FluidState {
fn from(state: BlockState) -> Self {
// note that 8 here might be treated as 9 in some cases if there's another fluid
// block of the same type above it
if state
.property::<crate::properties::Waterlogged>()
.unwrap_or_default()
{
return Self {
kind: FluidKind::Water,
amount: 8,
falling: false,
};
}
let registry_block = azalea_registry::Block::from(state);
match registry_block {
azalea_registry::Block::Water => {
let level = state
.property::<crate::properties::WaterLevel>()
.expect("water block should always have WaterLevel");
return Self {
kind: FluidKind::Water,
amount: to_or_from_legacy_fluid_level(level as u8),
falling: false,
};
}
azalea_registry::Block::Lava => {
let level = state
.property::<crate::properties::LavaLevel>()
.expect("lava block should always have LavaLevel");
return Self {
kind: FluidKind::Lava,
amount: to_or_from_legacy_fluid_level(level as u8),
falling: false,
};
}
azalea_registry::Block::BubbleColumn => {
return Self::new_source_block(FluidKind::Water, false);
}
_ => {}
}
Self::default()
}
}
/// Sometimes Minecraft represents fluids with 0 being empty and 8 being full,
/// and sometimes it's the opposite. You can use this function to convert
/// in between those two representations.
///
/// You usually don't need to call this yourself, see [`FluidState`].
pub fn to_or_from_legacy_fluid_level(level: u8) -> u8 {
// see FlowingFluid.getLegacyLevel
8_u8.saturating_sub(level)
}
impl From<FluidState> for BlockState {
fn from(state: FluidState) -> Self {
match state.kind {
FluidKind::Empty => BlockState::AIR,
FluidKind::Water => BlockState::from(crate::blocks::Water {
level: crate::properties::WaterLevel::from(state.amount as BlockStateIntegerRepr),
}),
FluidKind::Lava => BlockState::from(crate::blocks::Lava {
level: crate::properties::LavaLevel::from(state.amount as BlockStateIntegerRepr),
}),
}
}
}

556
azalea-block/src/generated.rs Executable file → Normal file

File diff suppressed because it is too large Load diff

195
azalea-block/src/lib.rs Executable file → Normal file
View file

@ -1,22 +1,21 @@
#![doc = include_str!("../README.md")]
#![feature(trait_upcasting)]
mod behavior;
pub mod block_state;
pub mod fluid_state;
mod generated;
mod range;
use core::fmt::Debug;
use std::{
any::Any,
io::{Cursor, Write},
};
use std::any::Any;
use azalea_buf::{AzaleaRead, AzaleaReadVar, AzaleaWrite, AzaleaWriteVar, BufReadError};
pub use behavior::BlockBehavior;
// re-exported for convenience
pub use block_state::BlockState;
pub use generated::{blocks, properties};
pub use range::BlockStates;
pub trait Block: Debug + Any {
pub trait BlockTrait: Debug + Any {
fn behavior(&self) -> BlockBehavior;
/// Get the Minecraft ID for this block. For example `stone` or
/// `grass_block`.
@ -28,8 +27,8 @@ pub trait Block: Debug + Any {
/// `azalea_registry::Block` doesn't contain any state data.
fn as_registry_block(&self) -> azalea_registry::Block;
}
impl dyn Block {
pub fn downcast_ref<T: Block>(&self) -> Option<&T> {
impl dyn BlockTrait {
pub fn downcast_ref<T: BlockTrait>(&self) -> Option<&T> {
(self as &dyn Any).downcast_ref::<T>()
}
}
@ -39,181 +38,3 @@ pub trait Property {
fn try_from_block_state(state: BlockState) -> Option<Self::Value>;
}
/// A representation of a state a block can be in.
///
/// For example, a stone block only has one state but each possible stair
/// rotation is a different state.
#[derive(Copy, Clone, PartialEq, Eq, Default, Hash)]
pub struct BlockState {
/// The protocol ID for the block state. IDs may change every
/// version, so you shouldn't hard-code them or store them in databases.
pub id: u32,
}
impl BlockState {
pub const AIR: BlockState = BlockState { id: 0 };
#[inline]
pub fn is_valid_state(state_id: u32) -> bool {
state_id <= Self::max_state()
}
/// Returns true if the block is air. This only checks for normal air, not
/// other types like cave air.
#[inline]
pub fn is_air(&self) -> bool {
self == &Self::AIR
}
}
impl TryFrom<u32> for BlockState {
type Error = ();
/// Safely converts a state id to a block state.
fn try_from(state_id: u32) -> Result<Self, Self::Error> {
if Self::is_valid_state(state_id) {
Ok(BlockState { id: state_id })
} else {
Err(())
}
}
}
impl AzaleaRead for BlockState {
fn azalea_read(buf: &mut Cursor<&[u8]>) -> Result<Self, BufReadError> {
let state_id = u32::azalea_read_var(buf)?;
Self::try_from(state_id).map_err(|_| BufReadError::UnexpectedEnumVariant {
id: state_id as i32,
})
}
}
impl AzaleaWrite for BlockState {
fn azalea_write(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
u32::azalea_write_var(&self.id, buf)
}
}
impl std::fmt::Debug for BlockState {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"BlockState(id: {}, {:?})",
self.id,
Box::<dyn Block>::from(*self)
)
}
}
#[derive(Clone, Debug)]
pub struct FluidState {
pub fluid: azalea_registry::Fluid,
pub height: u8,
}
impl Default for FluidState {
fn default() -> Self {
Self {
fluid: azalea_registry::Fluid::Empty,
height: 0,
}
}
}
impl From<BlockState> for FluidState {
fn from(state: BlockState) -> Self {
if state
.property::<crate::properties::Waterlogged>()
.unwrap_or_default()
{
Self {
fluid: azalea_registry::Fluid::Water,
height: 15,
}
} else {
let block = Box::<dyn Block>::from(state);
if let Some(water) = block.downcast_ref::<crate::blocks::Water>() {
Self {
fluid: azalea_registry::Fluid::Water,
height: water.level as u8,
}
} else if let Some(lava) = block.downcast_ref::<crate::blocks::Lava>() {
Self {
fluid: azalea_registry::Fluid::Lava,
height: lava.level as u8,
}
} else {
Self {
fluid: azalea_registry::Fluid::Empty,
height: 0,
}
}
}
}
}
impl From<FluidState> for BlockState {
fn from(state: FluidState) -> Self {
match state.fluid {
azalea_registry::Fluid::Empty => BlockState::AIR,
azalea_registry::Fluid::Water | azalea_registry::Fluid::FlowingWater => {
BlockState::from(crate::blocks::Water {
level: crate::properties::WaterLevel::from(state.height as u32),
})
}
azalea_registry::Fluid::Lava | azalea_registry::Fluid::FlowingLava => {
BlockState::from(crate::blocks::Lava {
level: crate::properties::LavaLevel::from(state.height as u32),
})
}
}
}
}
impl From<BlockState> for azalea_registry::Block {
fn from(value: BlockState) -> Self {
Box::<dyn Block>::from(value).as_registry_block()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_from_u32() {
assert_eq!(BlockState::try_from(0).unwrap(), BlockState::AIR);
assert!(BlockState::try_from(BlockState::max_state()).is_ok());
assert!(BlockState::try_from(BlockState::max_state() + 1).is_err());
}
#[test]
fn test_from_blockstate() {
let block: Box<dyn Block> = Box::<dyn Block>::from(BlockState::AIR);
assert_eq!(block.id(), "air");
let block: Box<dyn Block> =
Box::<dyn Block>::from(BlockState::from(azalea_registry::Block::FloweringAzalea));
assert_eq!(block.id(), "flowering_azalea");
}
#[test]
fn test_debug_blockstate() {
let formatted = format!(
"{:?}",
BlockState::from(azalea_registry::Block::FloweringAzalea)
);
assert!(formatted.ends_with(", FloweringAzalea)"), "{}", formatted);
let formatted = format!(
"{:?}",
BlockState::from(azalea_registry::Block::BigDripleafStem)
);
assert!(
formatted.ends_with(", BigDripleafStem { facing: North, waterlogged: false })"),
"{}",
formatted
);
}
}

View file

@ -1,20 +1,22 @@
use std::{
collections::HashSet,
collections::{HashSet, hash_set},
ops::{Add, RangeInclusive},
};
use crate::BlockState;
use azalea_registry::Block;
use crate::{BlockState, block_state::BlockStateIntegerRepr};
#[derive(Debug, Clone)]
pub struct BlockStates {
pub set: HashSet<BlockState>,
}
impl From<RangeInclusive<u32>> for BlockStates {
fn from(range: RangeInclusive<u32>) -> Self {
impl From<RangeInclusive<BlockStateIntegerRepr>> for BlockStates {
fn from(range: RangeInclusive<BlockStateIntegerRepr>) -> Self {
let mut set = HashSet::with_capacity((range.end() - range.start() + 1) as usize);
for id in range {
set.insert(BlockState { id });
set.insert(BlockState::try_from(id).unwrap_or_default());
}
Self { set }
}
@ -22,7 +24,7 @@ impl From<RangeInclusive<u32>> for BlockStates {
impl IntoIterator for BlockStates {
type Item = BlockState;
type IntoIter = std::collections::hash_set::IntoIter<BlockState>;
type IntoIter = hash_set::IntoIter<BlockState>;
fn into_iter(self) -> Self::IntoIter {
self.set.into_iter()
@ -44,3 +46,34 @@ impl Add for BlockStates {
}
}
}
impl From<HashSet<Block>> for BlockStates {
fn from(set: HashSet<Block>) -> Self {
Self::from(&set)
}
}
impl From<&HashSet<Block>> for BlockStates {
fn from(set: &HashSet<Block>) -> Self {
let mut block_states = HashSet::with_capacity(set.len());
for &block in set {
block_states.extend(BlockStates::from(block));
}
Self { set: block_states }
}
}
impl<const N: usize> From<[Block; N]> for BlockStates {
fn from(arr: [Block; N]) -> Self {
Self::from(&arr[..])
}
}
impl From<&[Block]> for BlockStates {
fn from(arr: &[Block]) -> Self {
let mut block_states = HashSet::with_capacity(arr.len());
for &block in arr {
block_states.extend(BlockStates::from(block));
}
Self { set: block_states }
}
}

View file

@ -1,19 +1,19 @@
[package]
name = "azalea-brigadier"
description = "A port of Mojang's Brigadier command parsing and dispatching library."
version = { workspace = true }
edition = { workspace = true }
license = { workspace = true }
repository = { workspace = true }
version.workspace = true
edition.workspace = true
license.workspace = true
repository.workspace = true
[dev-dependencies]
bevy_app = { workspace = true }
bevy_ecs = { workspace = true }
bevy_app.workspace = true
bevy_ecs.workspace = true
[dependencies]
azalea-buf = { path = "../azalea-buf", version = "0.11.0+mc1.21.4", optional = true }
azalea-chat = { path = "../azalea-chat", version = "0.11.0+mc1.21.4", optional = true }
parking_lot = { workspace = true }
azalea-buf = { workspace = true, optional = true }
azalea-chat = { workspace = true, optional = true }
parking_lot.workspace = true
[features]
azalea-buf = ["dep:azalea-buf", "dep:azalea-chat", "azalea-chat/azalea-buf"]

0
azalea-brigadier/README.md Executable file → Normal file
View file

4
azalea-brigadier/src/arguments/argument_type.rs Executable file → Normal file
View file

@ -1,13 +1,13 @@
use std::{any::Any, sync::Arc};
use crate::{
exceptions::CommandSyntaxException,
errors::CommandSyntaxError,
string_reader::StringReader,
suggestion::{Suggestions, SuggestionsBuilder},
};
pub trait ArgumentType {
fn parse(&self, reader: &mut StringReader) -> Result<Arc<dyn Any>, CommandSyntaxException>;
fn parse(&self, reader: &mut StringReader) -> Result<Arc<dyn Any>, CommandSyntaxError>;
fn list_suggestions(&self, _builder: SuggestionsBuilder) -> Suggestions {
Suggestions::default()

View file

@ -3,7 +3,7 @@ use std::{any::Any, sync::Arc};
use super::ArgumentType;
use crate::{
context::CommandContext,
exceptions::CommandSyntaxException,
errors::CommandSyntaxError,
string_reader::StringReader,
suggestion::{Suggestions, SuggestionsBuilder},
};
@ -12,7 +12,7 @@ use crate::{
struct Boolean;
impl ArgumentType for Boolean {
fn parse(&self, reader: &mut StringReader) -> Result<Arc<dyn Any>, CommandSyntaxException> {
fn parse(&self, reader: &mut StringReader) -> Result<Arc<dyn Any>, CommandSyntaxError> {
Ok(Arc::new(reader.read_boolean()?))
}

View file

@ -3,7 +3,7 @@ use std::{any::Any, sync::Arc};
use super::ArgumentType;
use crate::{
context::CommandContext,
exceptions::{BuiltInExceptions, CommandSyntaxException},
errors::{BuiltInError, CommandSyntaxError},
string_reader::StringReader,
};
@ -14,28 +14,28 @@ struct Double {
}
impl ArgumentType for Double {
fn parse(&self, reader: &mut StringReader) -> Result<Arc<dyn Any>, CommandSyntaxException> {
fn parse(&self, reader: &mut StringReader) -> Result<Arc<dyn Any>, CommandSyntaxError> {
let start = reader.cursor;
let result = reader.read_double()?;
if let Some(minimum) = self.minimum {
if result < minimum {
reader.cursor = start;
return Err(BuiltInExceptions::DoubleTooSmall {
found: result,
min: minimum,
}
.create_with_context(reader));
if let Some(minimum) = self.minimum
&& result < minimum
{
reader.cursor = start;
return Err(BuiltInError::DoubleTooSmall {
found: result,
min: minimum,
}
.create_with_context(reader));
}
if let Some(maximum) = self.maximum {
if result > maximum {
reader.cursor = start;
return Err(BuiltInExceptions::DoubleTooBig {
found: result,
max: maximum,
}
.create_with_context(reader));
if let Some(maximum) = self.maximum
&& result > maximum
{
reader.cursor = start;
return Err(BuiltInError::DoubleTooBig {
found: result,
max: maximum,
}
.create_with_context(reader));
}
Ok(Arc::new(result))
}

View file

@ -3,7 +3,7 @@ use std::{any::Any, sync::Arc};
use super::ArgumentType;
use crate::{
context::CommandContext,
exceptions::{BuiltInExceptions, CommandSyntaxException},
errors::{BuiltInError, CommandSyntaxError},
string_reader::StringReader,
};
@ -14,28 +14,28 @@ struct Float {
}
impl ArgumentType for Float {
fn parse(&self, reader: &mut StringReader) -> Result<Arc<dyn Any>, CommandSyntaxException> {
fn parse(&self, reader: &mut StringReader) -> Result<Arc<dyn Any>, CommandSyntaxError> {
let start = reader.cursor;
let result = reader.read_float()?;
if let Some(minimum) = self.minimum {
if result < minimum {
reader.cursor = start;
return Err(BuiltInExceptions::FloatTooSmall {
found: result,
min: minimum,
}
.create_with_context(reader));
if let Some(minimum) = self.minimum
&& result < minimum
{
reader.cursor = start;
return Err(BuiltInError::FloatTooSmall {
found: result,
min: minimum,
}
.create_with_context(reader));
}
if let Some(maximum) = self.maximum {
if result > maximum {
reader.cursor = start;
return Err(BuiltInExceptions::FloatTooBig {
found: result,
max: maximum,
}
.create_with_context(reader));
if let Some(maximum) = self.maximum
&& result > maximum
{
reader.cursor = start;
return Err(BuiltInError::FloatTooBig {
found: result,
max: maximum,
}
.create_with_context(reader));
}
Ok(Arc::new(result))
}

View file

@ -3,7 +3,7 @@ use std::{any::Any, sync::Arc};
use super::ArgumentType;
use crate::{
context::CommandContext,
exceptions::{BuiltInExceptions, CommandSyntaxException},
errors::{BuiltInError, CommandSyntaxError},
string_reader::StringReader,
};
@ -14,28 +14,28 @@ struct Integer {
}
impl ArgumentType for Integer {
fn parse(&self, reader: &mut StringReader) -> Result<Arc<dyn Any>, CommandSyntaxException> {
fn parse(&self, reader: &mut StringReader) -> Result<Arc<dyn Any>, CommandSyntaxError> {
let start = reader.cursor;
let result = reader.read_int()?;
if let Some(minimum) = self.minimum {
if result < minimum {
reader.cursor = start;
return Err(BuiltInExceptions::IntegerTooSmall {
found: result,
min: minimum,
}
.create_with_context(reader));
if let Some(minimum) = self.minimum
&& result < minimum
{
reader.cursor = start;
return Err(BuiltInError::IntegerTooSmall {
found: result,
min: minimum,
}
.create_with_context(reader));
}
if let Some(maximum) = self.maximum {
if result > maximum {
reader.cursor = start;
return Err(BuiltInExceptions::IntegerTooBig {
found: result,
max: maximum,
}
.create_with_context(reader));
if let Some(maximum) = self.maximum
&& result > maximum
{
reader.cursor = start;
return Err(BuiltInError::IntegerTooBig {
found: result,
max: maximum,
}
.create_with_context(reader));
}
Ok(Arc::new(result))
}

View file

@ -3,7 +3,7 @@ use std::{any::Any, sync::Arc};
use super::ArgumentType;
use crate::{
context::CommandContext,
exceptions::{BuiltInExceptions, CommandSyntaxException},
errors::{BuiltInError, CommandSyntaxError},
string_reader::StringReader,
};
@ -14,28 +14,28 @@ struct Long {
}
impl ArgumentType for Long {
fn parse(&self, reader: &mut StringReader) -> Result<Arc<dyn Any>, CommandSyntaxException> {
fn parse(&self, reader: &mut StringReader) -> Result<Arc<dyn Any>, CommandSyntaxError> {
let start = reader.cursor;
let result = reader.read_long()?;
if let Some(minimum) = self.minimum {
if result < minimum {
reader.cursor = start;
return Err(BuiltInExceptions::LongTooSmall {
found: result,
min: minimum,
}
.create_with_context(reader));
if let Some(minimum) = self.minimum
&& result < minimum
{
reader.cursor = start;
return Err(BuiltInError::LongTooSmall {
found: result,
min: minimum,
}
.create_with_context(reader));
}
if let Some(maximum) = self.maximum {
if result > maximum {
reader.cursor = start;
return Err(BuiltInExceptions::LongTooBig {
found: result,
max: maximum,
}
.create_with_context(reader));
if let Some(maximum) = self.maximum
&& result > maximum
{
reader.cursor = start;
return Err(BuiltInError::LongTooBig {
found: result,
max: maximum,
}
.create_with_context(reader));
}
Ok(Arc::new(result))
}

0
azalea-brigadier/src/arguments/mod.rs Executable file → Normal file
View file

View file

@ -1,9 +1,7 @@
use std::{any::Any, sync::Arc};
use super::ArgumentType;
use crate::{
context::CommandContext, exceptions::CommandSyntaxException, string_reader::StringReader,
};
use crate::{context::CommandContext, errors::CommandSyntaxError, string_reader::StringReader};
pub enum StringArgument {
/// Match up until the next space.
@ -16,7 +14,7 @@ pub enum StringArgument {
}
impl ArgumentType for StringArgument {
fn parse(&self, reader: &mut StringReader) -> Result<Arc<dyn Any>, CommandSyntaxException> {
fn parse(&self, reader: &mut StringReader) -> Result<Arc<dyn Any>, CommandSyntaxError> {
let result = match self {
StringArgument::SingleWord => reader.read_unquoted_string().to_string(),
StringArgument::QuotablePhrase => reader.read_string()?,

40
azalea-brigadier/src/builder/argument_builder.rs Executable file → Normal file
View file

@ -1,18 +1,32 @@
use std::{fmt::Debug, sync::Arc};
use std::{
fmt::{self, Debug},
sync::Arc,
};
use parking_lot::RwLock;
use super::{literal_argument_builder::Literal, required_argument_builder::Argument};
use crate::{
context::CommandContext,
errors::CommandSyntaxError,
modifier::RedirectModifier,
tree::{Command, CommandNode},
};
#[derive(Debug, Clone)]
pub enum ArgumentBuilderType {
#[derive(Debug)]
pub enum ArgumentBuilderType<S> {
Literal(Literal),
Argument(Argument),
Argument(Argument<S>),
}
impl<S> Clone for ArgumentBuilderType<S> {
fn clone(&self) -> Self {
match self {
ArgumentBuilderType::Literal(literal) => ArgumentBuilderType::Literal(literal.clone()),
ArgumentBuilderType::Argument(argument) => {
ArgumentBuilderType::Argument(argument.clone())
}
}
}
}
/// A node that hasn't yet been built.
@ -30,7 +44,7 @@ pub struct ArgumentBuilder<S> {
/// A node that isn't yet built.
impl<S> ArgumentBuilder<S> {
pub fn new(value: ArgumentBuilderType) -> Self {
pub fn new(value: ArgumentBuilderType<S>) -> Self {
Self {
arguments: CommandNode {
value,
@ -49,9 +63,7 @@ impl<S> ArgumentBuilder<S> {
/// ```
/// # use azalea_brigadier::prelude::*;
/// # let mut subject = CommandDispatcher::<()>::new();
/// literal("foo").then(
/// literal("bar").executes(|ctx: &CommandContext<()>| 42)
/// )
/// literal("foo").then(literal("bar").executes(|ctx: &CommandContext<()>| 42))
/// # ;
/// ```
pub fn then(self, argument: ArgumentBuilder<S>) -> Self {
@ -79,6 +91,16 @@ impl<S> ArgumentBuilder<S> {
pub fn executes<F>(mut self, f: F) -> Self
where
F: Fn(&CommandContext<S>) -> i32 + Send + Sync + 'static,
{
self.command = Some(Arc::new(move |ctx: &CommandContext<S>| Ok(f(ctx))));
self
}
/// Same as [`Self::executes`] but returns a `Result<i32,
/// CommandSyntaxError>`.
pub fn executes_result<F>(mut self, f: F) -> Self
where
F: Fn(&CommandContext<S>) -> Result<i32, CommandSyntaxError> + Send + Sync + 'static,
{
self.command = Some(Arc::new(f));
self
@ -163,7 +185,7 @@ impl<S> ArgumentBuilder<S> {
}
impl<S> Debug for ArgumentBuilder<S> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ArgumentBuilder")
.field("arguments", &self.arguments)
// .field("command", &self.command)

View file

@ -12,7 +12,7 @@ impl Literal {
}
}
impl From<Literal> for ArgumentBuilderType {
impl<S> From<Literal> for ArgumentBuilderType<S> {
fn from(literal: Literal) -> Self {
Self::Literal(literal)
}

0
azalea-brigadier/src/builder/mod.rs Executable file → Normal file
View file

View file

@ -1,37 +1,52 @@
use std::{any::Any, fmt::Debug, sync::Arc};
use std::{
any::Any,
fmt::{self, Debug},
sync::Arc,
};
use super::argument_builder::{ArgumentBuilder, ArgumentBuilderType};
use crate::{
arguments::ArgumentType,
exceptions::CommandSyntaxException,
context::CommandContext,
errors::CommandSyntaxError,
string_reader::StringReader,
suggestion::{Suggestions, SuggestionsBuilder},
suggestion::{SuggestionProvider, Suggestions, SuggestionsBuilder},
};
/// An argument node type. The `T` type parameter is the type of the argument,
/// which can be anything.
#[derive(Clone)]
pub struct Argument {
pub struct Argument<S> {
pub name: String,
parser: Arc<dyn ArgumentType + Send + Sync>,
custom_suggestions: Option<Arc<dyn SuggestionProvider<S> + Send + Sync>>,
}
impl Argument {
pub fn new(name: &str, parser: Arc<dyn ArgumentType + Send + Sync>) -> Self {
impl<S> Argument<S> {
pub fn new(
name: &str,
parser: Arc<dyn ArgumentType + Send + Sync>,
custom_suggestions: Option<Arc<dyn SuggestionProvider<S> + Send + Sync>>,
) -> Self {
Self {
name: name.to_string(),
parser,
custom_suggestions,
}
}
pub fn parse(&self, reader: &mut StringReader) -> Result<Arc<dyn Any>, CommandSyntaxException> {
pub fn parse(&self, reader: &mut StringReader) -> Result<Arc<dyn Any>, CommandSyntaxError> {
self.parser.parse(reader)
}
pub fn list_suggestions(&self, builder: SuggestionsBuilder) -> Suggestions {
// TODO: custom suggestions
// https://github.com/Mojang/brigadier/blob/master/src/main/java/com/mojang/brigadier/tree/ArgumentCommandNode.java#L71
self.parser.list_suggestions(builder)
pub fn list_suggestions(
&self,
context: CommandContext<S>,
builder: SuggestionsBuilder,
) -> Suggestions {
if let Some(s) = &self.custom_suggestions {
s.get_suggestions(context, builder)
} else {
self.parser.list_suggestions(builder)
}
}
pub fn examples(&self) -> Vec<String> {
@ -39,14 +54,14 @@ impl Argument {
}
}
impl From<Argument> for ArgumentBuilderType {
fn from(argument: Argument) -> Self {
impl<S> From<Argument<S>> for ArgumentBuilderType<S> {
fn from(argument: Argument<S>) -> Self {
Self::Argument(argument)
}
}
impl Debug for Argument {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
impl<S> Debug for Argument<S> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Argument")
.field("name", &self.name)
// .field("parser", &self.parser)
@ -59,5 +74,15 @@ pub fn argument<S>(
name: &str,
parser: impl ArgumentType + Send + Sync + 'static,
) -> ArgumentBuilder<S> {
ArgumentBuilder::new(Argument::new(name, Arc::new(parser)).into())
ArgumentBuilder::new(Argument::new(name, Arc::new(parser), None).into())
}
impl<S> Clone for Argument<S> {
fn clone(&self) -> Self {
Self {
name: self.name.clone(),
parser: self.parser.clone(),
custom_suggestions: self.custom_suggestions.clone(),
}
}
}

227
azalea-brigadier/src/command_dispatcher.rs Executable file → Normal file
View file

@ -1,7 +1,7 @@
use std::{
cmp::Ordering,
collections::{HashMap, HashSet},
mem,
ptr,
rc::Rc,
sync::Arc,
};
@ -10,9 +10,10 @@ use parking_lot::RwLock;
use crate::{
builder::argument_builder::ArgumentBuilder,
context::{CommandContext, CommandContextBuilder},
exceptions::{BuiltInExceptions, CommandSyntaxException},
context::{CommandContextBuilder, ContextChain},
errors::{BuiltInError, CommandSyntaxError},
parse_results::ParseResults,
result_consumer::{DefaultResultConsumer, ResultConsumer},
string_reader::StringReader,
suggestion::{Suggestions, SuggestionsBuilder},
tree::CommandNode,
@ -30,12 +31,14 @@ where
Self: Sync + Send,
{
pub root: Arc<RwLock<CommandNode<S>>>,
consumer: Box<dyn ResultConsumer<S> + Send + Sync>,
}
impl<S> CommandDispatcher<S> {
pub fn new() -> Self {
Self {
root: Arc::new(RwLock::new(CommandNode::default())),
consumer: Box::new(DefaultResultConsumer),
}
}
@ -52,7 +55,7 @@ impl<S> CommandDispatcher<S> {
build
}
pub fn parse(&self, command: StringReader, source: S) -> ParseResults<S> {
pub fn parse(&self, command: StringReader, source: S) -> ParseResults<'_, S> {
let source = Arc::new(source);
let context = CommandContextBuilder::new(self, source, self.root.clone(), command.cursor());
@ -64,10 +67,10 @@ impl<S> CommandDispatcher<S> {
node: &Arc<RwLock<CommandNode<S>>>,
original_reader: &StringReader,
context_so_far: CommandContextBuilder<'a, S>,
) -> Result<ParseResults<'a, S>, CommandSyntaxException> {
) -> Result<ParseResults<'a, S>, CommandSyntaxError> {
let source = context_so_far.source.clone();
#[allow(clippy::mutable_key_type)] // this is fine because we don't mutate the key
let mut errors = HashMap::<Rc<CommandNode<S>>, CommandSyntaxException>::new();
let mut errors = HashMap::<Rc<CommandNode<S>>, CommandSyntaxError>::new();
let mut potentials: Vec<ParseResults<S>> = vec![];
let cursor = original_reader.cursor();
@ -83,7 +86,7 @@ impl<S> CommandDispatcher<S> {
if let Err(ex) = parse_with_context_result {
errors.insert(
Rc::new((*child.read()).clone()),
BuiltInExceptions::DispatcherParseException {
BuiltInError::DispatcherParseException {
message: ex.message(),
}
.create_with_context(&reader),
@ -94,8 +97,7 @@ impl<S> CommandDispatcher<S> {
if reader.can_read() && reader.peek() != ' ' {
errors.insert(
Rc::new((*child.read()).clone()),
BuiltInExceptions::DispatcherExpectedArgumentSeparator
.create_with_context(&reader),
BuiltInError::DispatcherExpectedArgumentSeparator.create_with_context(&reader),
);
reader.cursor = cursor;
continue;
@ -108,23 +110,30 @@ impl<S> CommandDispatcher<S> {
1
}) {
reader.skip();
if let Some(redirect) = &child.read().redirect {
let child_context =
CommandContextBuilder::new(self, source, redirect.clone(), reader.cursor);
let parse = self
.parse_nodes(redirect, &reader, child_context)
.expect("Parsing nodes failed");
context.with_child(Rc::new(parse.context));
return Ok(ParseResults {
context,
reader: parse.reader,
exceptions: parse.exceptions,
});
} else {
let parse = self
.parse_nodes(&child, &reader, context)
.expect("Parsing nodes failed");
potentials.push(parse);
match &child.read().redirect {
Some(redirect) => {
let child_context = CommandContextBuilder::new(
self,
source,
redirect.clone(),
reader.cursor,
);
let parse = self
.parse_nodes(redirect, &reader, child_context)
.expect("Parsing nodes failed");
context.with_child(Rc::new(parse.context));
return Ok(ParseResults {
context,
reader: parse.reader,
exceptions: parse.exceptions,
});
}
_ => {
let parse = self
.parse_nodes(&child, &reader, context)
.expect("Parsing nodes failed");
potentials.push(parse);
}
}
} else {
potentials.push(ParseResults {
@ -172,11 +181,11 @@ impl<S> CommandDispatcher<S> {
&self,
input: impl Into<StringReader>,
source: S,
) -> Result<i32, CommandSyntaxException> {
) -> Result<i32, CommandSyntaxError> {
let input = input.into();
let parse = self.parse(input, source);
Self::execute_parsed(parse)
self.execute_parsed(parse)
}
pub fn add_paths(
@ -215,96 +224,39 @@ impl<S> CommandDispatcher<S> {
pub fn find_node(&self, path: &[&str]) -> Option<Arc<RwLock<CommandNode<S>>>> {
let mut node = self.root.clone();
for name in path {
if let Some(child) = node.clone().read().child(name) {
node = child;
} else {
return None;
}
match node.clone().read().child(name) {
Some(child) => {
node = child;
}
_ => {
return None;
}
};
}
Some(node)
}
/// Executes a given pre-parsed command.
pub fn execute_parsed(parse: ParseResults<S>) -> Result<i32, CommandSyntaxException> {
pub fn execute_parsed(&self, parse: ParseResults<S>) -> Result<i32, CommandSyntaxError> {
if parse.reader.can_read() {
if parse.exceptions.len() == 1 {
return Err(parse.exceptions.values().next().unwrap().clone());
}
if parse.context.range.is_empty() {
return Err(
BuiltInExceptions::DispatcherUnknownCommand.create_with_context(&parse.reader)
);
}
return Err(
BuiltInExceptions::DispatcherUnknownArgument.create_with_context(&parse.reader)
);
return Err(if parse.exceptions.len() == 1 {
parse.exceptions.values().next().unwrap().clone()
} else if parse.context.range.is_empty() {
BuiltInError::DispatcherUnknownCommand.create_with_context(&parse.reader)
} else {
BuiltInError::DispatcherUnknownArgument.create_with_context(&parse.reader)
});
}
let mut result = 0i32;
let mut successful_forks = 0;
let mut forked = false;
let mut found_command = false;
let command = parse.reader.string();
let original = parse.context.build(command);
let mut contexts = vec![original];
let mut next: Vec<CommandContext<S>> = vec![];
let original = Rc::new(parse.context.build(command));
let flat_context = ContextChain::try_flatten(original.clone());
let Some(flat_context) = flat_context else {
self.consumer.on_command_complete(original, false, 0);
return Err(BuiltInError::DispatcherUnknownCommand.create_with_context(&parse.reader));
};
while !contexts.is_empty() {
for context in &contexts {
let child = &context.child;
if let Some(child) = child {
forked |= child.forks;
if child.has_nodes() {
found_command = true;
let modifier = &context.modifier;
if let Some(modifier) = modifier {
let results = modifier(context);
if let Ok(results) = results {
if !results.is_empty() {
next.extend(results.iter().map(|s| child.copy_for(s.clone())));
}
} else {
// TODO
// self.consumer.on_command_complete(context, false, 0);
if !forked {
return Err(results.err().unwrap());
}
}
} else {
next.push(child.copy_for(context.source.clone()));
}
}
} else if let Some(context_command) = &context.command {
found_command = true;
let value = context_command(context);
result += value;
// consumer.on_command_complete(context, true, value);
successful_forks += 1;
// TODO: allow context_command to error and handle those
// errors
}
}
// move next into contexts and clear next
mem::swap(&mut contexts, &mut next);
next.clear();
}
if !found_command {
// consumer.on_command_complete(original, false, 0);
return Err(
BuiltInExceptions::DispatcherUnknownCommand.create_with_context(&parse.reader)
);
}
// TODO: this is not how vanilla does it but it works
Ok(if successful_forks >= 2 {
successful_forks
} else {
result
})
// Ok(if forked { successful_forks } else { result })
flat_context.execute_all(original.source.clone(), self.consumer.as_ref())
}
pub fn get_all_usage(
@ -332,32 +284,35 @@ impl<S> CommandDispatcher<S> {
if node.command.is_some() {
result.push(prefix.to_owned());
}
if let Some(redirect) = &node.redirect {
let redirect = if redirect.data_ptr() == self.root.data_ptr() {
"...".to_string()
} else {
format!("-> {}", redirect.read().usage_text())
};
if prefix.is_empty() {
result.push(format!("{} {redirect}", node.usage_text()));
} else {
result.push(format!("{prefix} {redirect}"));
match &node.redirect {
Some(redirect) => {
let redirect = if ptr::eq(redirect.data_ptr(), self.root.data_ptr()) {
"...".to_string()
} else {
format!("-> {}", redirect.read().usage_text())
};
if prefix.is_empty() {
result.push(format!("{} {redirect}", node.usage_text()));
} else {
result.push(format!("{prefix} {redirect}"));
}
}
} else {
for child in node.children.values() {
let child = child.read();
self.get_all_usage_recursive(
&child,
source,
result,
if prefix.is_empty() {
child.usage_text()
} else {
format!("{prefix} {}", child.usage_text())
}
.as_str(),
restricted,
);
_ => {
for child in node.children.values() {
let child = child.read();
self.get_all_usage_recursive(
&child,
source,
result,
if prefix.is_empty() {
child.usage_text()
} else {
format!("{prefix} {}", child.usage_text())
}
.as_str(),
restricted,
);
}
}
}
}
@ -409,7 +364,7 @@ impl<S> CommandDispatcher<S> {
}
if let Some(redirect) = &node.redirect {
let redirect = if redirect.data_ptr() == self.root.data_ptr() {
let redirect = if ptr::eq(redirect.data_ptr(), self.root.data_ptr()) {
"...".to_string()
} else {
format!("-> {}", redirect.read().usage_text())
@ -449,7 +404,7 @@ impl<S> CommandDispatcher<S> {
Ordering::Equal => {
let usage = child_usage.into_iter().next().unwrap();
let usage = if child_optional {
format!("[{}]", usage)
format!("[{usage}]")
} else {
usage
};

78
azalea-brigadier/src/context/command_context.rs Executable file → Normal file
View file

@ -1,8 +1,14 @@
use std::{any::Any, collections::HashMap, fmt::Debug, rc::Rc, sync::Arc};
use std::{
any::Any,
collections::HashMap,
fmt::{self, Debug},
rc::Rc,
sync::Arc,
};
use parking_lot::RwLock;
use super::{parsed_command_node::ParsedCommandNode, string_range::StringRange, ParsedArgument};
use super::{ParsedArgument, parsed_command_node::ParsedCommandNode, string_range::StringRange};
use crate::{
modifier::RedirectModifier,
tree::{Command, CommandNode},
@ -11,15 +17,15 @@ use crate::{
/// A built `CommandContextBuilder`.
pub struct CommandContext<S> {
pub source: Arc<S>,
pub input: String,
pub arguments: HashMap<String, ParsedArgument>,
pub command: Command<S>,
pub root_node: Arc<RwLock<CommandNode<S>>>,
pub nodes: Vec<ParsedCommandNode<S>>,
pub range: StringRange,
pub child: Option<Rc<CommandContext<S>>>,
pub modifier: Option<Arc<RedirectModifier<S>>>,
pub forks: bool,
pub(super) input: String,
pub(super) arguments: HashMap<String, ParsedArgument>,
pub(super) command: Command<S>,
pub(super) root_node: Arc<RwLock<CommandNode<S>>>,
pub(super) nodes: Vec<ParsedCommandNode<S>>,
pub(super) range: StringRange,
pub(super) child: Option<Rc<CommandContext<S>>>,
pub(super) modifier: Option<Arc<RedirectModifier<S>>>,
pub(super) forks: bool,
}
impl<S> Clone for CommandContext<S> {
@ -40,7 +46,7 @@ impl<S> Clone for CommandContext<S> {
}
impl<S> Debug for CommandContext<S> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("CommandContext")
// .field("source", &self.source)
.field("input", &self.input)
@ -59,8 +65,10 @@ impl<S> Debug for CommandContext<S> {
impl<S> CommandContext<S> {
pub fn copy_for(&self, source: Arc<S>) -> Self {
if Arc::ptr_eq(&source, &self.source) {
// fast path
return self.clone();
}
CommandContext {
source,
input: self.input.clone(),
@ -75,12 +83,52 @@ impl<S> CommandContext<S> {
}
}
pub fn child(&self) -> Option<&CommandContext<S>> {
self.child.as_ref().map(|c| c.as_ref())
}
pub fn last_child(&self) -> &CommandContext<S> {
let mut result = self;
while let Some(child) = result.child() {
result = child;
}
result
}
pub fn command(&self) -> &Command<S> {
&self.command
}
pub fn argument(&self, name: &str) -> Option<&dyn Any> {
let argument = self.arguments.get(name);
argument.map(|a| a.result.as_ref())
}
pub fn redirect_modifier(&self) -> Option<&RedirectModifier<S>> {
self.modifier.as_ref().map(|m| m.as_ref())
}
pub fn range(&self) -> &StringRange {
&self.range
}
pub fn input(&self) -> &str {
&self.input
}
pub fn root_node(&self) -> &Arc<RwLock<CommandNode<S>>> {
&self.root_node
}
pub fn nodes(&self) -> &[ParsedCommandNode<S>] {
&self.nodes
}
pub fn has_nodes(&self) -> bool {
!self.nodes.is_empty()
}
pub fn argument(&self, name: &str) -> Option<Arc<dyn Any>> {
let argument = self.arguments.get(name);
argument.map(|a| a.result.clone())
pub fn is_forked(&self) -> bool {
self.forks
}
}

37
azalea-brigadier/src/context/command_context_builder.rs Executable file → Normal file
View file

@ -1,10 +1,15 @@
use std::{collections::HashMap, fmt::Debug, rc::Rc, sync::Arc};
use std::{
collections::HashMap,
fmt::{self, Debug},
rc::Rc,
sync::Arc,
};
use parking_lot::RwLock;
use super::{
command_context::CommandContext, parsed_command_node::ParsedCommandNode,
string_range::StringRange, suggestion_context::SuggestionContext, ParsedArgument,
ParsedArgument, command_context::CommandContext, parsed_command_node::ParsedCommandNode,
string_range::StringRange, suggestion_context::SuggestionContext,
};
use crate::{
command_dispatcher::CommandDispatcher,
@ -107,18 +112,18 @@ impl<'a, S> CommandContextBuilder<'a, S> {
}
if self.range.end() < cursor {
if let Some(child) = &self.child {
child.find_suggestion_context(cursor)
} else if let Some(last) = self.nodes.last() {
SuggestionContext {
parent: Arc::clone(&last.node),
start_pos: last.range.end() + 1,
}
} else {
SuggestionContext {
parent: Arc::clone(&self.root),
start_pos: self.range.start(),
}
match &self.child {
Some(child) => child.find_suggestion_context(cursor),
_ => match self.nodes.last() {
Some(last) => SuggestionContext {
parent: Arc::clone(&last.node),
start_pos: last.range.end() + 1,
},
_ => SuggestionContext {
parent: Arc::clone(&self.root),
start_pos: self.range.start(),
},
},
}
} else {
let mut prev = &self.root;
@ -140,7 +145,7 @@ impl<'a, S> CommandContextBuilder<'a, S> {
}
impl<S> Debug for CommandContextBuilder<'_, S> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("CommandContextBuilder")
// .field("arguments", &self.arguments)
.field("root", &self.root)

View file

@ -0,0 +1,167 @@
use std::{rc::Rc, sync::Arc};
use super::CommandContext;
use crate::{errors::CommandSyntaxError, result_consumer::ResultConsumer};
pub struct ContextChain<S> {
modifiers: Vec<Rc<CommandContext<S>>>,
executable: Rc<CommandContext<S>>,
next_stage_cache: Option<Rc<ContextChain<S>>>,
}
impl<S> ContextChain<S> {
pub fn new(modifiers: Vec<Rc<CommandContext<S>>>, executable: Rc<CommandContext<S>>) -> Self {
if executable.command.is_none() {
panic!("Last command in chain must be executable");
}
Self {
modifiers,
executable,
next_stage_cache: None,
}
}
pub fn try_flatten(root_context: Rc<CommandContext<S>>) -> Option<Self> {
let mut modifiers = Vec::new();
let mut current = root_context;
loop {
let child = current.child.clone();
let Some(child) = child else {
// Last entry must be executable command
current.command.as_ref()?;
return Some(ContextChain::new(modifiers, current));
};
modifiers.push(current);
current = child;
}
}
pub fn run_modifier(
modifier: Rc<CommandContext<S>>,
source: Arc<S>,
result_consumer: &dyn ResultConsumer<S>,
forked_mode: bool,
) -> Result<Vec<Arc<S>>, CommandSyntaxError> {
let source_modifier = modifier.redirect_modifier();
let Some(source_modifier) = source_modifier else {
return Ok(vec![source]);
};
let context_to_use = Rc::new(modifier.copy_for(source));
let err = match (source_modifier)(&context_to_use) {
Ok(res) => return Ok(res),
Err(e) => e,
};
result_consumer.on_command_complete(context_to_use, false, 0);
if forked_mode {
return Ok(vec![]);
}
Err(err)
}
pub fn run_executable(
&self,
executable: Rc<CommandContext<S>>,
source: Arc<S>,
result_consumer: &dyn ResultConsumer<S>,
forked_mode: bool,
) -> Result<i32, CommandSyntaxError> {
let context_to_use = Rc::new(executable.copy_for(source));
let Some(command) = &executable.command else {
unimplemented!();
};
let err = match (command)(&context_to_use) {
Ok(result) => {
result_consumer.on_command_complete(context_to_use, true, result);
return if forked_mode { Ok(1) } else { Ok(result) };
}
Err(err) => err,
};
result_consumer.on_command_complete(context_to_use, false, 0);
if forked_mode { Ok(0) } else { Err(err) }
}
pub fn execute_all(
&self,
source: Arc<S>,
result_consumer: &dyn ResultConsumer<S>,
) -> Result<i32, CommandSyntaxError> {
if self.modifiers.is_empty() {
return self.run_executable(self.executable.clone(), source, result_consumer, false);
}
let mut forked_mode = false;
let mut current_sources = vec![source];
for modifier in &self.modifiers {
forked_mode |= modifier.is_forked();
let mut next_sources = Vec::new();
for source_to_run in current_sources {
next_sources.extend(Self::run_modifier(
modifier.clone(),
source_to_run.clone(),
result_consumer,
forked_mode,
)?);
}
if next_sources.is_empty() {
return Ok(0);
}
current_sources = next_sources;
}
let mut result = 0;
for execution_source in current_sources {
result += self.run_executable(
self.executable.clone(),
execution_source,
result_consumer,
forked_mode,
)?;
}
Ok(result)
}
pub fn stage(&self) -> Stage {
if self.modifiers.is_empty() {
Stage::Execute
} else {
Stage::Modify
}
}
pub fn top_context(&self) -> Rc<CommandContext<S>> {
self.modifiers
.first()
.cloned()
.unwrap_or_else(|| self.executable.clone())
}
pub fn next_stage(&mut self) -> Option<Rc<ContextChain<S>>> {
let modifier_count = self.modifiers.len();
if modifier_count == 0 {
return None;
}
if self.next_stage_cache.is_none() {
self.next_stage_cache = Some(Rc::new(ContextChain::new(
self.modifiers[1..].to_vec(),
self.executable.clone(),
)));
}
self.next_stage_cache.clone()
}
}
pub enum Stage {
Modify,
Execute,
}

2
azalea-brigadier/src/context/mod.rs Executable file → Normal file
View file

@ -1,5 +1,6 @@
mod command_context;
mod command_context_builder;
mod context_chain;
mod parsed_argument;
mod parsed_command_node;
mod string_range;
@ -7,6 +8,7 @@ pub mod suggestion_context;
pub use command_context::CommandContext;
pub use command_context_builder::CommandContextBuilder;
pub use context_chain::ContextChain;
pub use parsed_argument::ParsedArgument;
pub use parsed_command_node::ParsedCommandNode;
pub use string_range::StringRange;

0
azalea-brigadier/src/context/parsed_argument.rs Executable file → Normal file
View file

0
azalea-brigadier/src/context/parsed_command_node.rs Executable file → Normal file
View file

0
azalea-brigadier/src/context/string_range.rs Executable file → Normal file
View file

View file

@ -1,10 +1,10 @@
use std::fmt;
use super::command_syntax_exception::CommandSyntaxException;
use super::command_syntax_error::CommandSyntaxError;
use crate::string_reader::StringReader;
#[derive(Clone, PartialEq)]
pub enum BuiltInExceptions {
pub enum BuiltInError {
DoubleTooSmall { found: f64, min: f64 },
DoubleTooBig { found: f64, max: f64 },
@ -40,114 +40,114 @@ pub enum BuiltInExceptions {
DispatcherParseException { message: String },
}
impl fmt::Debug for BuiltInExceptions {
impl fmt::Debug for BuiltInError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
BuiltInExceptions::DoubleTooSmall { found, min } => {
BuiltInError::DoubleTooSmall { found, min } => {
write!(f, "Double must not be less than {min}, found {found}")
}
BuiltInExceptions::DoubleTooBig { found, max } => {
BuiltInError::DoubleTooBig { found, max } => {
write!(f, "Double must not be more than {max}, found {found}")
}
BuiltInExceptions::FloatTooSmall { found, min } => {
BuiltInError::FloatTooSmall { found, min } => {
write!(f, "Float must not be less than {min}, found {found}")
}
BuiltInExceptions::FloatTooBig { found, max } => {
BuiltInError::FloatTooBig { found, max } => {
write!(f, "Float must not be more than {max}, found {found}")
}
BuiltInExceptions::IntegerTooSmall { found, min } => {
BuiltInError::IntegerTooSmall { found, min } => {
write!(f, "Integer must not be less than {min}, found {found}")
}
BuiltInExceptions::IntegerTooBig { found, max } => {
BuiltInError::IntegerTooBig { found, max } => {
write!(f, "Integer must not be more than {max}, found {found}")
}
BuiltInExceptions::LongTooSmall { found, min } => {
BuiltInError::LongTooSmall { found, min } => {
write!(f, "Long must not be less than {min}, found {found}")
}
BuiltInExceptions::LongTooBig { found, max } => {
BuiltInError::LongTooBig { found, max } => {
write!(f, "Long must not be more than {max}, found {found}")
}
BuiltInExceptions::LiteralIncorrect { expected } => {
BuiltInError::LiteralIncorrect { expected } => {
write!(f, "Expected literal {expected}")
}
BuiltInExceptions::ReaderExpectedStartOfQuote => {
BuiltInError::ReaderExpectedStartOfQuote => {
write!(f, "Expected quote to start a string")
}
BuiltInExceptions::ReaderExpectedEndOfQuote => {
BuiltInError::ReaderExpectedEndOfQuote => {
write!(f, "Unclosed quoted string")
}
BuiltInExceptions::ReaderInvalidEscape { character } => {
BuiltInError::ReaderInvalidEscape { character } => {
write!(f, "Invalid escape sequence '{character}' in quoted string")
}
BuiltInExceptions::ReaderInvalidBool { value } => {
BuiltInError::ReaderInvalidBool { value } => {
write!(
f,
"Invalid bool, expected true or false but found '{value}'"
)
}
BuiltInExceptions::ReaderInvalidInt { value } => {
BuiltInError::ReaderInvalidInt { value } => {
write!(f, "Invalid Integer '{value}'")
}
BuiltInExceptions::ReaderExpectedInt => {
BuiltInError::ReaderExpectedInt => {
write!(f, "Expected Integer")
}
BuiltInExceptions::ReaderInvalidLong { value } => {
BuiltInError::ReaderInvalidLong { value } => {
write!(f, "Invalid long '{value}'")
}
BuiltInExceptions::ReaderExpectedLong => {
BuiltInError::ReaderExpectedLong => {
write!(f, "Expected long")
}
BuiltInExceptions::ReaderInvalidDouble { value } => {
BuiltInError::ReaderInvalidDouble { value } => {
write!(f, "Invalid double '{value}'")
}
BuiltInExceptions::ReaderExpectedDouble => {
BuiltInError::ReaderExpectedDouble => {
write!(f, "Expected double")
}
BuiltInExceptions::ReaderInvalidFloat { value } => {
BuiltInError::ReaderInvalidFloat { value } => {
write!(f, "Invalid Float '{value}'")
}
BuiltInExceptions::ReaderExpectedFloat => {
BuiltInError::ReaderExpectedFloat => {
write!(f, "Expected Float")
}
BuiltInExceptions::ReaderExpectedBool => {
BuiltInError::ReaderExpectedBool => {
write!(f, "Expected bool")
}
BuiltInExceptions::ReaderExpectedSymbol { symbol } => {
BuiltInError::ReaderExpectedSymbol { symbol } => {
write!(f, "Expected '{symbol}'")
}
BuiltInExceptions::DispatcherUnknownCommand => {
BuiltInError::DispatcherUnknownCommand => {
write!(f, "Unknown command")
}
BuiltInExceptions::DispatcherUnknownArgument => {
BuiltInError::DispatcherUnknownArgument => {
write!(f, "Incorrect argument for command")
}
BuiltInExceptions::DispatcherExpectedArgumentSeparator => {
BuiltInError::DispatcherExpectedArgumentSeparator => {
write!(
f,
"Expected whitespace to end one argument, but found trailing data"
)
}
BuiltInExceptions::DispatcherParseException { message } => {
BuiltInError::DispatcherParseException { message } => {
write!(f, "Could not parse command: {message}")
}
}
}
}
impl BuiltInExceptions {
pub fn create(self) -> CommandSyntaxException {
impl BuiltInError {
pub fn create(self) -> CommandSyntaxError {
let message = format!("{self:?}");
CommandSyntaxException::create(self, message)
CommandSyntaxError::create(self, message)
}
pub fn create_with_context(self, reader: &StringReader) -> CommandSyntaxException {
pub fn create_with_context(self, reader: &StringReader) -> CommandSyntaxError {
let message = format!("{self:?}");
CommandSyntaxException::new(self, message, reader.string(), reader.cursor())
CommandSyntaxError::new(self, message, reader.string(), reader.cursor())
}
}

View file

@ -0,0 +1,93 @@
use std::{
cmp,
fmt::{self, Debug, Write},
};
use super::builtin_errors::BuiltInError;
#[derive(Clone, PartialEq)]
pub struct CommandSyntaxError {
kind: BuiltInError,
message: String,
input: Option<String>,
cursor: Option<usize>,
}
const CONTEXT_AMOUNT: usize = 10;
impl CommandSyntaxError {
pub fn new(kind: BuiltInError, message: String, input: &str, cursor: usize) -> Self {
Self {
kind,
message,
input: Some(input.to_string()),
cursor: Some(cursor),
}
}
pub fn create(kind: BuiltInError, message: String) -> Self {
Self {
kind,
message,
input: None,
cursor: None,
}
}
pub fn message(&self) -> String {
let mut message = self.message.clone();
let context = self.context();
if let Some(context) = context {
write!(
message,
" at position {}: {context}",
self.cursor.unwrap_or(usize::MAX)
)
.unwrap();
}
message
}
pub fn raw_message(&self) -> &String {
&self.message
}
pub fn context(&self) -> Option<String> {
if let Some(input) = &self.input
&& let Some(cursor) = self.cursor
{
let mut builder = String::new();
let cursor = cmp::min(input.len(), cursor);
if cursor > CONTEXT_AMOUNT {
builder.push_str("...");
}
builder.push_str(
&input[(cmp::max(0, cursor as isize - CONTEXT_AMOUNT as isize) as usize)..cursor],
);
builder.push_str("<--[HERE]");
return Some(builder);
}
None
}
pub fn kind(&self) -> &BuiltInError {
&self.kind
}
pub fn input(&self) -> &Option<String> {
&self.input
}
pub fn cursor(&self) -> Option<usize> {
self.cursor
}
}
impl Debug for CommandSyntaxError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.message())
}
}

View file

@ -0,0 +1,5 @@
mod builtin_errors;
mod command_syntax_error;
pub use builtin_errors::BuiltInError;
pub use command_syntax_error::CommandSyntaxError;

View file

@ -1,95 +0,0 @@
use std::{
cmp,
fmt::{self, Write},
};
use super::builtin_exceptions::BuiltInExceptions;
#[derive(Clone, PartialEq)]
pub struct CommandSyntaxException {
pub type_: BuiltInExceptions,
message: String,
input: Option<String>,
cursor: Option<usize>,
}
const CONTEXT_AMOUNT: usize = 10;
impl CommandSyntaxException {
pub fn new(type_: BuiltInExceptions, message: String, input: &str, cursor: usize) -> Self {
Self {
type_,
message,
input: Some(input.to_string()),
cursor: Some(cursor),
}
}
pub fn create(type_: BuiltInExceptions, message: String) -> Self {
Self {
type_,
message,
input: None,
cursor: None,
}
}
pub fn message(&self) -> String {
let mut message = self.message.clone();
let context = self.context();
if let Some(context) = context {
write!(
message,
" at position {}: {}",
self.cursor.unwrap_or(usize::MAX),
context
)
.unwrap();
}
message
}
pub fn raw_message(&self) -> &String {
&self.message
}
pub fn context(&self) -> Option<String> {
if let Some(input) = &self.input {
if let Some(cursor) = self.cursor {
let mut builder = String::new();
let cursor = cmp::min(input.len(), cursor);
if cursor > CONTEXT_AMOUNT {
builder.push_str("...");
}
builder.push_str(
&input
[(cmp::max(0, cursor as isize - CONTEXT_AMOUNT as isize) as usize)..cursor],
);
builder.push_str("<--[HERE]");
return Some(builder);
}
}
None
}
pub fn get_type(&self) -> &BuiltInExceptions {
&self.type_
}
pub fn input(&self) -> &Option<String> {
&self.input
}
pub fn cursor(&self) -> Option<usize> {
self.cursor
}
}
impl fmt::Debug for CommandSyntaxException {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.message())
}
}

View file

@ -1,5 +0,0 @@
mod builtin_exceptions;
mod command_syntax_exception;
pub use builtin_exceptions::BuiltInExceptions;
pub use command_syntax_exception::CommandSyntaxException;

3
azalea-brigadier/src/lib.rs Executable file → Normal file
View file

@ -4,9 +4,10 @@ pub mod arguments;
pub mod builder;
pub mod command_dispatcher;
pub mod context;
pub mod exceptions;
pub mod errors;
pub mod modifier;
pub mod parse_results;
pub mod result_consumer;
pub mod string_reader;
pub mod suggestion;
pub mod tree;

4
azalea-brigadier/src/modifier.rs Executable file → Normal file
View file

@ -1,6 +1,6 @@
use std::sync::Arc;
use crate::{context::CommandContext, exceptions::CommandSyntaxException};
use crate::{context::CommandContext, errors::CommandSyntaxError};
pub type RedirectModifier<S> =
dyn Fn(&CommandContext<S>) -> Result<Vec<Arc<S>>, CommandSyntaxException> + Send + Sync;
dyn Fn(&CommandContext<S>) -> Result<Vec<Arc<S>>, CommandSyntaxError> + Send + Sync;

14
azalea-brigadier/src/parse_results.rs Executable file → Normal file
View file

@ -1,18 +1,22 @@
use std::{collections::HashMap, fmt::Debug, rc::Rc};
use std::{
collections::HashMap,
fmt::{self, Debug},
rc::Rc,
};
use crate::{
context::CommandContextBuilder, exceptions::CommandSyntaxException,
string_reader::StringReader, tree::CommandNode,
context::CommandContextBuilder, errors::CommandSyntaxError, string_reader::StringReader,
tree::CommandNode,
};
pub struct ParseResults<'a, S> {
pub context: CommandContextBuilder<'a, S>,
pub reader: StringReader,
pub exceptions: HashMap<Rc<CommandNode<S>>, CommandSyntaxException>,
pub exceptions: HashMap<Rc<CommandNode<S>>, CommandSyntaxError>,
}
impl<S> Debug for ParseResults<'_, S> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ParseResults")
.field("context", &self.context)
// .field("reader", &self.reader)

View file

@ -0,0 +1,12 @@
use std::rc::Rc;
use crate::context::CommandContext;
pub trait ResultConsumer<S> {
fn on_command_complete(&self, context: Rc<CommandContext<S>>, success: bool, result: i32);
}
pub struct DefaultResultConsumer;
impl<S> ResultConsumer<S> for DefaultResultConsumer {
fn on_command_complete(&self, _context: Rc<CommandContext<S>>, _success: bool, _result: i32) {}
}

53
azalea-brigadier/src/string_reader.rs Executable file → Normal file
View file

@ -1,6 +1,6 @@
use std::str::FromStr;
use crate::exceptions::{BuiltInExceptions, CommandSyntaxException};
use crate::errors::{BuiltInError, CommandSyntaxError};
#[derive(Clone)]
pub struct StringReader {
@ -91,19 +91,19 @@ impl StringReader {
}
}
pub fn read_int(&mut self) -> Result<i32, CommandSyntaxException> {
pub fn read_int(&mut self) -> Result<i32, CommandSyntaxError> {
let start = self.cursor;
while self.can_read() && StringReader::is_allowed_number(self.peek()) {
self.skip();
}
let number = &self.string[start..self.cursor];
if number.is_empty() {
return Err(BuiltInExceptions::ReaderExpectedInt.create_with_context(self));
return Err(BuiltInError::ReaderExpectedInt.create_with_context(self));
}
let result = i32::from_str(number);
if result.is_err() {
self.cursor = start;
return Err(BuiltInExceptions::ReaderInvalidInt {
return Err(BuiltInError::ReaderInvalidInt {
value: number.to_string(),
}
.create_with_context(self));
@ -112,19 +112,19 @@ impl StringReader {
Ok(result.unwrap())
}
pub fn read_long(&mut self) -> Result<i64, CommandSyntaxException> {
pub fn read_long(&mut self) -> Result<i64, CommandSyntaxError> {
let start = self.cursor;
while self.can_read() && StringReader::is_allowed_number(self.peek()) {
self.skip();
}
let number = &self.string[start..self.cursor];
if number.is_empty() {
return Err(BuiltInExceptions::ReaderExpectedLong.create_with_context(self));
return Err(BuiltInError::ReaderExpectedLong.create_with_context(self));
}
let result = i64::from_str(number);
if result.is_err() {
self.cursor = start;
return Err(BuiltInExceptions::ReaderInvalidLong {
return Err(BuiltInError::ReaderInvalidLong {
value: number.to_string(),
}
.create_with_context(self));
@ -133,19 +133,19 @@ impl StringReader {
Ok(result.unwrap())
}
pub fn read_double(&mut self) -> Result<f64, CommandSyntaxException> {
pub fn read_double(&mut self) -> Result<f64, CommandSyntaxError> {
let start = self.cursor;
while self.can_read() && StringReader::is_allowed_number(self.peek()) {
self.skip();
}
let number = &self.string[start..self.cursor];
if number.is_empty() {
return Err(BuiltInExceptions::ReaderExpectedDouble.create_with_context(self));
return Err(BuiltInError::ReaderExpectedDouble.create_with_context(self));
}
let result = f64::from_str(number);
if result.is_err() {
self.cursor = start;
return Err(BuiltInExceptions::ReaderInvalidDouble {
return Err(BuiltInError::ReaderInvalidDouble {
value: number.to_string(),
}
.create_with_context(self));
@ -154,19 +154,19 @@ impl StringReader {
Ok(result.unwrap())
}
pub fn read_float(&mut self) -> Result<f32, CommandSyntaxException> {
pub fn read_float(&mut self) -> Result<f32, CommandSyntaxError> {
let start = self.cursor;
while self.can_read() && StringReader::is_allowed_number(self.peek()) {
self.skip();
}
let number = &self.string[start..self.cursor];
if number.is_empty() {
return Err(BuiltInExceptions::ReaderExpectedFloat.create_with_context(self));
return Err(BuiltInError::ReaderExpectedFloat.create_with_context(self));
}
let result = f32::from_str(number);
if result.is_err() {
self.cursor = start;
return Err(BuiltInExceptions::ReaderInvalidFloat {
return Err(BuiltInError::ReaderInvalidFloat {
value: number.to_string(),
}
.create_with_context(self));
@ -193,22 +193,19 @@ impl StringReader {
&self.string[start..self.cursor]
}
pub fn read_quoted_string(&mut self) -> Result<String, CommandSyntaxException> {
pub fn read_quoted_string(&mut self) -> Result<String, CommandSyntaxError> {
if !self.can_read() {
return Ok(String::new());
}
let next = self.peek();
if !StringReader::is_quoted_string_start(next) {
return Err(BuiltInExceptions::ReaderExpectedStartOfQuote.create_with_context(self));
return Err(BuiltInError::ReaderExpectedStartOfQuote.create_with_context(self));
}
self.skip();
self.read_string_until(next)
}
pub fn read_string_until(
&mut self,
terminator: char,
) -> Result<String, CommandSyntaxException> {
pub fn read_string_until(&mut self, terminator: char) -> Result<String, CommandSyntaxError> {
let mut result = String::new();
let mut escaped = false;
while self.can_read() {
@ -219,7 +216,7 @@ impl StringReader {
escaped = false;
} else {
self.cursor -= 1;
return Err(BuiltInExceptions::ReaderInvalidEscape { character: c }
return Err(BuiltInError::ReaderInvalidEscape { character: c }
.create_with_context(self));
}
} else if c == SYNTAX_ESCAPE {
@ -231,10 +228,10 @@ impl StringReader {
}
}
Err(BuiltInExceptions::ReaderExpectedEndOfQuote.create_with_context(self))
Err(BuiltInError::ReaderExpectedEndOfQuote.create_with_context(self))
}
pub fn read_string(&mut self) -> Result<String, CommandSyntaxException> {
pub fn read_string(&mut self) -> Result<String, CommandSyntaxError> {
if !self.can_read() {
return Ok(String::new());
}
@ -246,11 +243,11 @@ impl StringReader {
Ok(self.read_unquoted_string().to_string())
}
pub fn read_boolean(&mut self) -> Result<bool, CommandSyntaxException> {
pub fn read_boolean(&mut self) -> Result<bool, CommandSyntaxError> {
let start = self.cursor;
let value = self.read_string()?;
if value.is_empty() {
return Err(BuiltInExceptions::ReaderExpectedBool.create_with_context(self));
return Err(BuiltInError::ReaderExpectedBool.create_with_context(self));
}
if value == "true" {
@ -259,15 +256,13 @@ impl StringReader {
Ok(false)
} else {
self.cursor = start;
Err(BuiltInExceptions::ReaderInvalidBool { value }.create_with_context(self))
Err(BuiltInError::ReaderInvalidBool { value }.create_with_context(self))
}
}
pub fn expect(&mut self, c: char) -> Result<(), CommandSyntaxException> {
pub fn expect(&mut self, c: char) -> Result<(), CommandSyntaxError> {
if !self.can_read() || self.peek() != c {
return Err(
BuiltInExceptions::ReaderExpectedSymbol { symbol: c }.create_with_context(self)
);
return Err(BuiltInError::ReaderExpectedSymbol { symbol: c }.create_with_context(self));
}
self.skip();
Ok(())

13
azalea-brigadier/src/suggestion/mod.rs Executable file → Normal file
View file

@ -1,9 +1,11 @@
mod suggestion_provider;
mod suggestions;
mod suggestions_builder;
#[cfg(feature = "azalea-buf")]
use std::io::Write;
use std::io::{self, Write};
use std::{
cmp::Ordering,
fmt::{self, Display},
hash::Hash,
};
@ -12,6 +14,7 @@ use std::{
use azalea_buf::AzaleaWrite;
#[cfg(feature = "azalea-buf")]
use azalea_chat::FormattedText;
pub use suggestion_provider::SuggestionProvider;
pub use suggestions::Suggestions;
pub use suggestions_builder::SuggestionsBuilder;
@ -93,7 +96,7 @@ impl Suggestion {
}
impl SuggestionValue {
pub fn cmp_ignore_case(&self, other: &Self) -> std::cmp::Ordering {
pub fn cmp_ignore_case(&self, other: &Self) -> Ordering {
match (self, other) {
(SuggestionValue::Text(a), SuggestionValue::Text(b)) => {
a.to_lowercase().cmp(&b.to_lowercase())
@ -118,7 +121,7 @@ impl Display for SuggestionValue {
}
impl Ord for SuggestionValue {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
fn cmp(&self, other: &Self) -> Ordering {
match (self, other) {
(SuggestionValue::Text(a), SuggestionValue::Text(b)) => a.cmp(b),
(SuggestionValue::Integer(a), SuggestionValue::Integer(b)) => a.cmp(b),
@ -131,14 +134,14 @@ impl Ord for SuggestionValue {
}
}
impl PartialOrd for SuggestionValue {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
#[cfg(feature = "azalea-buf")]
impl AzaleaWrite for Suggestion {
fn azalea_write(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
self.value.to_string().azalea_write(buf)?;
self.tooltip
.clone()

View file

@ -0,0 +1,10 @@
use super::{Suggestions, SuggestionsBuilder};
use crate::context::CommandContext;
pub trait SuggestionProvider<S> {
fn get_suggestions(
&self,
context: CommandContext<S>,
builder: SuggestionsBuilder,
) -> Suggestions;
}

4
azalea-brigadier/src/suggestion/suggestions.rs Executable file → Normal file
View file

@ -1,5 +1,5 @@
#[cfg(feature = "azalea-buf")]
use std::io::{Cursor, Write};
use std::io::{self, Cursor, Write};
use std::{collections::HashSet, hash::Hash};
#[cfg(feature = "azalea-buf")]
@ -107,7 +107,7 @@ impl AzaleaRead for Suggestions {
#[cfg(feature = "azalea-buf")]
impl AzaleaWrite for Suggestions {
fn azalea_write(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
(self.range.start() as u32).azalea_write_var(buf)?;
(self.range.length() as u32).azalea_write_var(buf)?;
self.suggestions.azalea_write(buf)?;

0
azalea-brigadier/src/suggestion/suggestions_builder.rs Executable file → Normal file
View file

56
azalea-brigadier/src/tree/mod.rs Executable file → Normal file
View file

@ -1,7 +1,7 @@
use std::{
collections::{BTreeMap, HashMap},
fmt::Debug,
hash::Hash,
fmt::{self, Debug},
hash::{Hash, Hasher},
ptr,
sync::Arc,
};
@ -14,18 +14,19 @@ use crate::{
required_argument_builder::Argument,
},
context::{CommandContext, CommandContextBuilder, ParsedArgument, StringRange},
exceptions::{BuiltInExceptions, CommandSyntaxException},
errors::{BuiltInError, CommandSyntaxError},
modifier::RedirectModifier,
string_reader::StringReader,
suggestion::{Suggestions, SuggestionsBuilder},
};
pub type Command<S> = Option<Arc<dyn Fn(&CommandContext<S>) -> i32 + Send + Sync>>;
pub type Command<S> =
Option<Arc<dyn Fn(&CommandContext<S>) -> Result<i32, CommandSyntaxError> + Send + Sync>>;
/// An ArgumentBuilder that has been built.
#[non_exhaustive]
pub struct CommandNode<S> {
pub value: ArgumentBuilderType,
pub value: ArgumentBuilderType<S>,
// this is a BTreeMap because children need to be ordered when getting command suggestions
pub children: BTreeMap<String, Arc<RwLock<CommandNode<S>>>>,
@ -66,7 +67,7 @@ impl<S> CommandNode<S> {
}
/// Gets the argument, or panics. You should use match if you're not certain
/// about the type.
pub fn argument(&self) -> &Argument {
pub fn argument(&self) -> &Argument<S> {
match self.value {
ArgumentBuilderType::Argument(ref argument) => argument,
_ => panic!("CommandNode::argument() called on non-argument node"),
@ -149,7 +150,7 @@ impl<S> CommandNode<S> {
&self,
reader: &mut StringReader,
context_builder: &mut CommandContextBuilder<S>,
) -> Result<(), CommandSyntaxException> {
) -> Result<(), CommandSyntaxError> {
match self.value {
ArgumentBuilderType::Argument(ref argument) => {
let start = reader.cursor();
@ -176,7 +177,7 @@ impl<S> CommandNode<S> {
return Ok(());
}
Err(BuiltInExceptions::LiteralIncorrect {
Err(BuiltInError::LiteralIncorrect {
expected: literal.value.clone(),
}
.create_with_context(reader))
@ -214,9 +215,7 @@ impl<S> CommandNode<S> {
pub fn list_suggestions(
&self,
// context is here because that's how it is in mojang's brigadier, but we haven't
// implemented custom suggestions yet so this is unused rn
_context: CommandContext<S>,
context: CommandContext<S>,
builder: SuggestionsBuilder,
) -> Suggestions {
match &self.value {
@ -231,15 +230,15 @@ impl<S> CommandNode<S> {
Suggestions::default()
}
}
ArgumentBuilderType::Argument(argument) => argument.list_suggestions(builder),
ArgumentBuilderType::Argument(argument) => argument.list_suggestions(context, builder),
}
}
}
impl<S> Debug for CommandNode<S> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("CommandNode")
.field("value", &self.value)
// .field("value", &self.value)
.field("children", &self.children)
.field("command", &self.command.is_some())
// .field("requirement", &self.requirement)
@ -269,7 +268,7 @@ impl<S> Default for CommandNode<S> {
}
impl<S> Hash for CommandNode<S> {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
fn hash<H: Hasher>(&self, state: &mut H) {
// hash the children
for (k, v) in &self.children {
k.hash(state);
@ -292,18 +291,27 @@ impl<S> PartialEq for CommandNode<S> {
}
}
if let Some(selfexecutes) = &self.command {
// idk how to do this better since we can't compare `dyn Fn`s
if let Some(otherexecutes) = &other.command {
#[allow(ambiguous_wide_pointer_comparisons)]
if !Arc::ptr_eq(selfexecutes, otherexecutes) {
match &self.command {
Some(selfexecutes) => {
// idk how to do this better since we can't compare `dyn Fn`s
match &other.command {
Some(otherexecutes) =>
{
#[allow(ambiguous_wide_pointer_comparisons)]
if !Arc::ptr_eq(selfexecutes, otherexecutes) {
return false;
}
}
_ => {
return false;
}
}
}
_ => {
if other.command.is_some() {
return false;
}
} else {
return false;
}
} else if other.command.is_some() {
return false;
}
true
}

View file

View file

View file

View file

View file

View file

View file

@ -1,18 +1,8 @@
use std::sync::Arc;
use std::{mem, ops::Deref, sync::Arc};
use azalea_brigadier::{
arguments::integer_argument_type::integer,
builder::{literal_argument_builder::literal, required_argument_builder::argument},
command_dispatcher::CommandDispatcher,
context::CommandContext,
};
use azalea_brigadier::prelude::*;
use bevy_app::App;
use bevy_ecs::{
component::Component,
query::With,
system::{Query, Resource, RunSystemOnce},
world::{FromWorld, World},
};
use bevy_ecs::{prelude::*, system::RunSystemOnce};
use parking_lot::Mutex;
#[test]
@ -150,8 +140,7 @@ impl DispatchStorage {
///
/// Spawns a number of entities with the [`SpawnedEntity`] component.
fn command_spawn_entity_num(context: &CommandContext<WorldAccessor>) -> i32 {
let num = context.argument("entities").unwrap();
let num = *num.downcast_ref::<i32>().unwrap();
let num = get_integer(context, "entities").unwrap();
for _ in 0..num {
context.source.lock().spawn(SpawnedEntity);
@ -183,7 +172,7 @@ impl WorldAccessor {
/// Swap the internal [`World`] with the given one.
fn swap(&mut self, world: &mut World) {
std::mem::swap(&mut *self.lock(), world);
mem::swap(&mut *self.lock(), world);
}
}
@ -192,7 +181,7 @@ impl WorldAccessor {
struct SpawnedEntity;
/// Implemented for convenience.
impl std::ops::Deref for WorldAccessor {
impl Deref for WorldAccessor {
type Target = Arc<Mutex<World>>;
fn deref(&self) -> &Self::Target {
&self.world

12
azalea-brigadier/tests/builder/argument_builder_test.rs Executable file → Normal file
View file

@ -10,11 +10,13 @@ fn test_arguments() {
let builder = builder.then(argument.clone());
assert_eq!(builder.arguments().children.len(), 1);
let built_argument = Rc::new(argument.build());
assert!(builder
.arguments()
.children
.values()
.any(|e| *e.read() == *built_argument));
assert!(
builder
.arguments()
.children
.values()
.any(|e| *e.read() == *built_argument)
);
}
// @Test

View file

View file

54
azalea-brigadier/tests/command_dispatcher_test.rs Executable file → Normal file
View file

@ -5,7 +5,7 @@ use azalea_brigadier::{
builder::{literal_argument_builder::literal, required_argument_builder::argument},
command_dispatcher::CommandDispatcher,
context::CommandContext,
exceptions::{BuiltInExceptions, CommandSyntaxException},
errors::{BuiltInError, CommandSyntaxError},
string_reader::StringReader,
};
@ -50,10 +50,7 @@ fn execute_unknown_command() {
let execute_result = subject.execute("foo", &CommandSource {});
let err = execute_result.err().unwrap();
match err.type_ {
BuiltInExceptions::DispatcherUnknownCommand => {}
_ => panic!("Unexpected error"),
}
assert_eq!(err.kind(), &BuiltInError::DispatcherUnknownCommand);
assert_eq!(err.cursor().unwrap(), 0);
}
@ -65,10 +62,7 @@ fn execute_impermissible_command() {
let execute_result = subject.execute("foo", &CommandSource {});
let err = execute_result.err().unwrap();
match err.type_ {
BuiltInExceptions::DispatcherUnknownCommand => {}
_ => panic!("Unexpected error"),
}
assert_eq!(err.kind(), &BuiltInError::DispatcherUnknownCommand);
assert_eq!(err.cursor().unwrap(), 0);
}
@ -80,10 +74,7 @@ fn execute_empty_command() {
let execute_result = subject.execute("", &CommandSource {});
let err = execute_result.err().unwrap();
match err.type_ {
BuiltInExceptions::DispatcherUnknownCommand => {}
_ => panic!("Unexpected error"),
}
assert_eq!(err.kind(), &BuiltInError::DispatcherUnknownCommand);
assert_eq!(err.cursor().unwrap(), 0);
}
@ -95,10 +86,7 @@ fn execute_unknown_subcommand() {
let execute_result = subject.execute("foo bar", &CommandSource {});
let err = execute_result.err().unwrap();
match err.type_ {
BuiltInExceptions::DispatcherUnknownArgument => {}
_ => panic!("Unexpected error"),
}
assert_eq!(err.kind(), &BuiltInError::DispatcherUnknownArgument);
assert_eq!(err.cursor().unwrap(), 4);
}
@ -110,10 +98,7 @@ fn execute_incorrect_literal() {
let execute_result = subject.execute("foo baz", &CommandSource {});
let err = execute_result.err().unwrap();
match err.type_ {
BuiltInExceptions::DispatcherUnknownArgument => {}
_ => panic!("Unexpected error"),
}
assert_eq!(err.kind(), &BuiltInError::DispatcherUnknownArgument);
assert_eq!(err.cursor().unwrap(), 4);
}
@ -130,10 +115,7 @@ fn execute_ambiguous_incorrect_argument() {
let execute_result = subject.execute("foo unknown", &CommandSource {});
let err = execute_result.err().unwrap();
match err.type_ {
BuiltInExceptions::DispatcherUnknownArgument => {}
_ => panic!("Unexpected error"),
}
assert_eq!(err.kind(), &BuiltInError::DispatcherUnknownArgument);
assert_eq!(err.cursor().unwrap(), 4);
}
@ -173,7 +155,7 @@ fn parse_incomplete_argument() {
}
#[test]
fn execute_ambiguious_parent_subcommand() {
fn execute_ambiguous_parent_subcommand() {
let mut subject = CommandDispatcher::new();
subject.register(
@ -186,7 +168,7 @@ fn execute_ambiguious_parent_subcommand() {
}
#[test]
fn execute_ambiguious_parent_subcommand_via_redirect() {
fn execute_ambiguous_parent_subcommand_via_redirect() {
let mut subject = CommandDispatcher::new();
let real = subject.register(
@ -245,7 +227,7 @@ fn execute_redirected_multiple_times() {
);
assert_eq!(*child2.unwrap().nodes[0].node.read(), *concrete_node.read());
assert_eq!(CommandDispatcher::execute_parsed(parse).unwrap(), 42);
assert_eq!(subject.execute_parsed(parse).unwrap(), 42);
}
#[test]
@ -255,7 +237,7 @@ fn execute_redirected() {
let source1 = Arc::new(CommandSource {});
let source2 = Arc::new(CommandSource {});
let modifier = move |_: &CommandContext<CommandSource>| -> Result<Vec<Arc<CommandSource>>, CommandSyntaxException> {
let modifier = move |_: &CommandContext<CommandSource>| -> Result<Vec<Arc<CommandSource>>, CommandSyntaxError> {
Ok(vec![source1.clone(), source2.clone()])
};
@ -281,7 +263,7 @@ fn execute_redirected() {
assert_eq!(*parent.nodes[0].node.read(), *concrete_node.read());
assert_eq!(*parent.source, CommandSource {});
assert_eq!(CommandDispatcher::execute_parsed(parse).unwrap(), 2);
assert_eq!(subject.execute_parsed(parse).unwrap(), 2);
}
#[test]
@ -297,10 +279,7 @@ fn execute_orphaned_subcommand() {
let result = subject.execute("foo 5", &CommandSource {});
assert!(result.is_err());
let result = result.unwrap_err();
assert_eq!(
*result.get_type(),
BuiltInExceptions::DispatcherUnknownCommand
);
assert_eq!(*result.kind(), BuiltInError::DispatcherUnknownCommand);
assert_eq!(result.cursor(), Some(5));
}
@ -327,10 +306,7 @@ fn parse_no_space_separator() {
let result = subject.execute("foo$", &CommandSource {});
assert!(result.is_err());
let result = result.unwrap_err();
assert_eq!(
*result.get_type(),
BuiltInExceptions::DispatcherUnknownCommand
);
assert_eq!(*result.kind(), BuiltInError::DispatcherUnknownCommand);
assert_eq!(result.cursor(), Some(0));
}
@ -348,7 +324,7 @@ fn execute_invalid_subcommand() {
assert!(result.is_err());
let result = result.unwrap_err();
// this fails for some reason, i blame mojang
// assert_eq!(*result.get_type(), BuiltInExceptions::ReaderExpectedInt);
// assert_eq!(*result.get_type(), BuiltInError::ReaderExpectedInt);
assert_eq!(result.cursor(), Some(4));
}

View file

0
azalea-brigadier/tests/command_suggestions_test.rs Executable file → Normal file
View file

0
azalea-brigadier/tests/context/command_context_test.rs Executable file → Normal file
View file

0
azalea-brigadier/tests/context/parsed_argument_test.rs Executable file → Normal file
View file

52
azalea-brigadier/tests/string_reader_test.rs Executable file → Normal file
View file

@ -1,4 +1,4 @@
use azalea_brigadier::{exceptions::BuiltInExceptions, string_reader::StringReader};
use azalea_brigadier::{errors::BuiltInError, string_reader::StringReader};
#[test]
fn can_read() {
@ -222,7 +222,7 @@ fn read_quoted_string_no_open() {
let result = reader.read_quoted_string();
assert!(result.is_err());
if let Err(e) = result {
assert_eq!(e.get_type(), &BuiltInExceptions::ReaderExpectedStartOfQuote);
assert_eq!(e.kind(), &BuiltInError::ReaderExpectedStartOfQuote);
assert_eq!(e.cursor(), Some(0));
}
}
@ -233,7 +233,7 @@ fn read_quoted_string_no_close() {
let result = reader.read_quoted_string();
assert!(result.is_err());
if let Err(e) = result {
assert_eq!(e.get_type(), &BuiltInExceptions::ReaderExpectedEndOfQuote);
assert_eq!(e.kind(), &BuiltInError::ReaderExpectedEndOfQuote);
assert_eq!(e.cursor(), Some(12));
}
}
@ -245,8 +245,8 @@ fn read_quoted_string_invalid_escape() {
assert!(result.is_err());
if let Err(e) = result {
assert_eq!(
e.get_type(),
&BuiltInExceptions::ReaderInvalidEscape { character: 'n' }
e.kind(),
&BuiltInError::ReaderInvalidEscape { character: 'n' }
);
assert_eq!(e.cursor(), Some(7));
}
@ -259,8 +259,8 @@ fn read_quoted_string_invalid_quote_escape() {
assert!(result.is_err());
if let Err(e) = result {
assert_eq!(
e.get_type(),
&BuiltInExceptions::ReaderInvalidEscape { character: '"' }
e.kind(),
&BuiltInError::ReaderInvalidEscape { character: '"' }
);
assert_eq!(e.cursor(), Some(7));
}
@ -313,8 +313,8 @@ fn read_int_invalid() {
assert!(result.is_err());
if let Err(e) = result {
assert_eq!(
e.get_type(),
&BuiltInExceptions::ReaderInvalidInt {
e.kind(),
&BuiltInError::ReaderInvalidInt {
value: "12.34".to_string()
}
);
@ -328,7 +328,7 @@ fn read_int_none() {
let result = reader.read_int();
assert!(result.is_err());
if let Err(e) = result {
assert_eq!(e.get_type(), &BuiltInExceptions::ReaderExpectedInt);
assert_eq!(e.kind(), &BuiltInError::ReaderExpectedInt);
assert_eq!(e.cursor(), Some(0));
}
}
@ -372,8 +372,8 @@ fn read_long_invalid() {
assert!(result.is_err());
if let Err(e) = result {
assert_eq!(
e.get_type(),
&BuiltInExceptions::ReaderInvalidLong {
e.kind(),
&BuiltInError::ReaderInvalidLong {
value: "12.34".to_string()
}
);
@ -387,7 +387,7 @@ fn read_long_none() {
let result = reader.read_long();
assert!(result.is_err());
if let Err(e) = result {
assert_eq!(e.get_type(), &BuiltInExceptions::ReaderExpectedLong);
assert_eq!(e.kind(), &BuiltInError::ReaderExpectedLong);
assert_eq!(e.cursor(), Some(0));
}
}
@ -439,8 +439,8 @@ fn read_double_invalid() {
assert!(result.is_err());
if let Err(e) = result {
assert_eq!(
e.get_type(),
&BuiltInExceptions::ReaderInvalidDouble {
e.kind(),
&BuiltInError::ReaderInvalidDouble {
value: "12.34.56".to_string()
}
);
@ -454,7 +454,7 @@ fn read_double_none() {
let result = reader.read_double();
assert!(result.is_err());
if let Err(e) = result {
assert_eq!(e.get_type(), &BuiltInExceptions::ReaderExpectedDouble);
assert_eq!(e.kind(), &BuiltInError::ReaderExpectedDouble);
assert_eq!(e.cursor(), Some(0));
}
}
@ -506,8 +506,8 @@ fn read_float_invalid() {
assert!(result.is_err());
if let Err(e) = result {
assert_eq!(
e.get_type(),
&BuiltInExceptions::ReaderInvalidFloat {
e.kind(),
&BuiltInError::ReaderInvalidFloat {
value: "12.34.56".to_string()
}
);
@ -521,7 +521,7 @@ fn read_float_none() {
let result = reader.read_float();
assert!(result.is_err());
if let Err(e) = result {
assert_eq!(e.get_type(), &BuiltInExceptions::ReaderExpectedFloat);
assert_eq!(e.kind(), &BuiltInError::ReaderExpectedFloat);
assert_eq!(e.cursor(), Some(0));
}
}
@ -556,8 +556,8 @@ fn expect_incorrect() {
assert!(result.is_err());
if let Err(e) = result {
assert_eq!(
e.get_type(),
&BuiltInExceptions::ReaderExpectedSymbol { symbol: 'a' }
e.kind(),
&BuiltInError::ReaderExpectedSymbol { symbol: 'a' }
);
assert_eq!(e.cursor(), Some(0));
}
@ -570,8 +570,8 @@ fn expect_none() {
assert!(result.is_err());
if let Err(e) = result {
assert_eq!(
e.get_type(),
&BuiltInExceptions::ReaderExpectedSymbol { symbol: 'a' }
e.kind(),
&BuiltInError::ReaderExpectedSymbol { symbol: 'a' }
);
assert_eq!(e.cursor(), Some(0));
}
@ -591,8 +591,8 @@ fn read_boolean_incorrect() {
assert!(result.is_err());
if let Err(e) = result {
assert_eq!(
e.get_type(),
&BuiltInExceptions::ReaderInvalidBool {
e.kind(),
&BuiltInError::ReaderInvalidBool {
value: "tuesday".to_string()
}
);
@ -606,7 +606,7 @@ fn read_boolean_none() {
let result = reader.read_boolean();
assert!(result.is_err());
if let Err(e) = result {
assert_eq!(e.get_type(), &BuiltInExceptions::ReaderExpectedBool);
assert_eq!(e.kind(), &BuiltInError::ReaderExpectedBool);
assert_eq!(e.cursor(), Some(0));
}
}

0
azalea-brigadier/tests/suggestion/suggestion_test.rs Executable file → Normal file
View file

View file

2
azalea-brigadier/tests/suggestion/suggestions_test.rs Executable file → Normal file
View file

@ -17,7 +17,7 @@ fn merge_single() {
StringRange::at(5),
vec![Suggestion::new(StringRange::at(5), "ar")],
);
let merged = Suggestions::merge("foo b", &[suggestions.clone()]);
let merged = Suggestions::merge("foo b", std::slice::from_ref(&suggestions));
assert_eq!(merged, suggestions);
}

View file

View file

View file

0
azalea-brigadier/tests/tree/root_command_node_test.rs Executable file → Normal file
View file

View file

@ -1,19 +1,19 @@
[package]
name = "azalea-buf"
description = "Serialize and deserialize buffers from Minecraft."
version = { workspace = true }
edition = { workspace = true }
license = { workspace = true }
repository = { workspace = true }
version.workspace = true
edition.workspace = true
license.workspace = true
repository.workspace = true
[dependencies]
azalea-buf-macros = { path = "./azalea-buf-macros", version = "0.11.0+mc1.21.4" }
byteorder = { workspace = true }
azalea-buf-macros.workspace = true
byteorder.workspace = true
serde_json = { workspace = true, optional = true }
simdnbt = { workspace = true }
thiserror = { workspace = true }
tracing = { workspace = true }
uuid = { workspace = true }
simdnbt.workspace = true
thiserror.workspace = true
tracing.workspace = true
uuid.workspace = true
[features]
serde_json = ["dep:serde_json"]

0
azalea-buf/README.md Executable file → Normal file
View file

View file

@ -1,15 +1,15 @@
[package]
name = "azalea-buf-macros"
description = "#[derive(AzBuf)]"
version = { workspace = true }
edition = { workspace = true }
license = { workspace = true }
repository = { workspace = true }
version.workspace = true
edition.workspace = true
license.workspace = true
repository.workspace = true
[lib]
proc-macro = true
[dependencies]
proc-macro2 = { workspace = true }
quote = { workspace = true }
proc-macro2.workspace = true
quote.workspace = true
syn = { workspace = true, features = ["extra-traits"] }

0
azalea-buf/azalea-buf-macros/README.md Executable file → Normal file
View file

2
azalea-buf/azalea-buf-macros/src/lib.rs Executable file → Normal file
View file

@ -3,7 +3,7 @@ mod write;
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, DeriveInput};
use syn::{DeriveInput, parse_macro_input};
#[proc_macro_derive(AzaleaRead, attributes(var))]
pub fn derive_azalearead(input: TokenStream) -> TokenStream {

View file

@ -1,61 +1,5 @@
use quote::{quote, ToTokens};
use syn::{punctuated::Punctuated, token::Comma, Data, Field, FieldsNamed, Ident};
fn read_named_fields(
named: &Punctuated<Field, Comma>,
) -> (Vec<proc_macro2::TokenStream>, Vec<&Option<Ident>>) {
let read_fields = named
.iter()
.map(|f| {
let field_name = &f.ident;
let field_type = &f.ty;
let is_variable_length = f.attrs.iter().any(|a| a.path().is_ident("var"));
let limit = f
.attrs
.iter()
.find(|a| a.path().is_ident("limit"))
.map(|a| {
a.parse_args::<syn::LitInt>()
.unwrap()
.base10_parse::<usize>()
.unwrap()
});
if is_variable_length && limit.is_some() {
panic!("Fields cannot have both var and limit attributes");
}
// do a different buf.write_* for each field depending on the type
// if it's a string, use buf.write_string
match field_type {
syn::Type::Path(_) | syn::Type::Array(_) => {
if is_variable_length {
quote! {
let #field_name = azalea_buf::AzaleaReadVar::azalea_read_var(buf)?;
}
} else if let Some(limit) = limit {
quote! {
let #field_name = azalea_buf::AzaleaReadLimited::azalea_read_limited(buf, #limit)?;
}
} else {
quote! {
let #field_name = azalea_buf::AzaleaRead::azalea_read(buf)?;
}
}
}
_ => panic!(
"Error reading field {}: {}",
field_name.clone().unwrap(),
field_type.to_token_stream()
),
}
})
.collect::<Vec<_>>();
let read_field_names = named.iter().map(|f| &f.ident).collect::<Vec<_>>();
(read_fields, read_field_names)
}
use quote::{ToTokens, quote};
use syn::{Data, Field, FieldsNamed, Ident, punctuated::Punctuated, token::Comma};
pub fn create_impl_azalearead(ident: &Ident, data: &Data) -> proc_macro2::TokenStream {
match data {
@ -83,8 +27,18 @@ pub fn create_impl_azalearead(ident: &Ident, data: &Data) -> proc_macro2::TokenS
}
}
}
_ => {
panic!("#[derive(AzBuf)] can only be used on structs with named fields")
syn::Fields::Unnamed(fields) => {
let read_fields = read_unnamed_fields(&fields.unnamed);
quote! {
impl azalea_buf::AzaleaRead for #ident {
fn azalea_read(buf: &mut std::io::Cursor<&[u8]>) -> Result<Self, azalea_buf::BufReadError> {
Ok(Self(
#(#read_fields),*
))
}
}
}
}
},
syn::Data::Enum(syn::DataEnum { variants, .. }) => {
@ -202,3 +156,78 @@ pub fn create_impl_azalearead(ident: &Ident, data: &Data) -> proc_macro2::TokenS
_ => panic!("#[derive(AzBuf)] can only be used on structs"),
}
}
fn read_named_fields(
named: &Punctuated<Field, Comma>,
) -> (Vec<proc_macro2::TokenStream>, Vec<&Option<Ident>>) {
let read_fields = named
.iter()
.map(|f| {
let field_name = &f.ident;
let reader_call = get_reader_call(f);
quote! { let #field_name = #reader_call; }
})
.collect::<Vec<_>>();
let read_field_names = named.iter().map(|f| &f.ident).collect::<Vec<_>>();
(read_fields, read_field_names)
}
fn read_unnamed_fields(unnamed: &Punctuated<Field, Comma>) -> Vec<proc_macro2::TokenStream> {
unnamed
.iter()
.map(|f| {
let reader_call = get_reader_call(f);
quote! { #reader_call }
})
.collect::<Vec<_>>()
}
fn get_reader_call(f: &Field) -> proc_macro2::TokenStream {
let is_variable_length = f
.attrs
.iter()
.any(|a: &syn::Attribute| a.path().is_ident("var"));
let limit = f
.attrs
.iter()
.find(|a| a.path().is_ident("limit"))
.map(|a| {
a.parse_args::<syn::LitInt>()
.unwrap()
.base10_parse::<usize>()
.unwrap()
});
if is_variable_length && limit.is_some() {
panic!("Fields cannot have both var and limit attributes");
}
let field_type = &f.ty;
// do a different buf.write_* for each field depending on the type
// if it's a string, use buf.write_string
match field_type {
syn::Type::Path(_) | syn::Type::Array(_) => {
if is_variable_length {
quote! {
azalea_buf::AzaleaReadVar::azalea_read_var(buf)?
}
} else if let Some(limit) = limit {
quote! {
azalea_buf::AzaleaReadLimited::azalea_read_limited(buf, #limit)?
}
} else {
quote! {
azalea_buf::AzaleaRead::azalea_read(buf)?
}
}
}
_ => panic!(
"Error reading field {:?}: {}",
f.ident.clone(),
field_type.to_token_stream()
),
}
}

View file

@ -1,41 +1,6 @@
use proc_macro2::Span;
use quote::{quote, ToTokens};
use syn::{punctuated::Punctuated, token::Comma, Data, Field, FieldsNamed, Ident};
fn write_named_fields(
named: &Punctuated<Field, Comma>,
ident_name: Option<&Ident>,
) -> proc_macro2::TokenStream {
let write_fields = named.iter().map(|f| {
let field_name = &f.ident;
let field_type = &f.ty;
let ident_dot_field = match ident_name {
Some(ident) => quote! { &#ident.#field_name },
None => quote! { #field_name },
};
// do a different buf.write_* for each field depending on the type
// if it's a string, use buf.write_string
match field_type {
syn::Type::Path(_) | syn::Type::Array(_) => {
if f.attrs.iter().any(|attr| attr.path().is_ident("var")) {
quote! {
azalea_buf::AzaleaWriteVar::azalea_write_var(#ident_dot_field, buf)?;
}
} else {
quote! {
azalea_buf::AzaleaWrite::azalea_write(#ident_dot_field, buf)?;
}
}
}
_ => panic!(
"Error writing field {}: {}",
field_name.clone().unwrap(),
field_type.to_token_stream()
),
}
});
quote! { #(#write_fields)* }
}
use quote::{ToTokens, quote};
use syn::{Data, Field, FieldsNamed, Ident, punctuated::Punctuated, token::Comma};
pub fn create_impl_azaleawrite(ident: &Ident, data: &Data) -> proc_macro2::TokenStream {
match data {
@ -62,8 +27,17 @@ pub fn create_impl_azaleawrite(ident: &Ident, data: &Data) -> proc_macro2::Token
}
}
}
_ => {
panic!("#[derive(AzBuf)] can only be used on structs with named fields")
syn::Fields::Unnamed(fields) => {
let write_fields = write_unnamed_fields(&fields.unnamed);
quote! {
impl azalea_buf::AzaleaWrite for #ident {
fn azalea_write(&self, buf: &mut impl std::io::Write) -> Result<(), std::io::Error> {
#write_fields
Ok(())
}
}
}
}
},
syn::Data::Enum(syn::DataEnum { variants, .. }) => {
@ -200,3 +174,56 @@ pub fn create_impl_azaleawrite(ident: &Ident, data: &Data) -> proc_macro2::Token
_ => panic!("#[derive(AzBuf)] can only be used on structs"),
}
}
fn write_named_fields(
named: &Punctuated<Field, Comma>,
ident_name: Option<&Ident>,
) -> proc_macro2::TokenStream {
let write_fields = named.iter().map(|f| {
let field_name = &f.ident;
let ident_dot_field = match ident_name {
Some(ident) => quote! { &#ident.#field_name },
None => quote! { #field_name },
};
make_write_call(f, ident_dot_field)
});
quote! { #(#write_fields)* }
}
fn write_unnamed_fields(named: &Punctuated<Field, Comma>) -> proc_macro2::TokenStream {
let write_fields = named.iter().enumerate().map(|(i, f)| {
let i_literal = syn::Index::from(i);
let ident_dot_field = quote! { &self.#i_literal };
make_write_call(f, ident_dot_field)
});
quote! { #(#write_fields)* }
}
fn make_write_call(
f: &Field,
ident_dot_field: proc_macro2::TokenStream,
) -> proc_macro2::TokenStream {
let field_type = &f.ty;
// do a different buf.write_* for each field depending on the type
// if it's a string, use buf.write_string
match field_type {
syn::Type::Path(_) | syn::Type::Array(_) => {
if f.attrs.iter().any(|attr| attr.path().is_ident("var")) {
quote! {
azalea_buf::AzaleaWriteVar::azalea_write_var(#ident_dot_field, buf)?;
}
} else {
quote! {
azalea_buf::AzaleaWrite::azalea_write(#ident_dot_field, buf)?;
}
}
}
_ => panic!(
"Error writing field {:?}: {}",
f.ident,
field_type.to_token_stream()
),
}
}

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