mirror of
https://github.com/mat-1/azalea.git
synced 2025-08-02 14:26:04 +00:00
Compare commits
302 commits
azalea-cli
...
main
Author | SHA1 | Date | |
---|---|---|---|
37b124a2b6 | |||
e7bf124ed5 | |||
9719d00526 | |||
302752860c | |||
45f89b48e4 | |||
6984a2b9e6 | |||
a5c67d2eee | |||
004741b781 | |||
63348dbbea | |||
|
df9d776ff8 | ||
|
ebc2e0c067 | ||
a060b73915 | |||
5564d475a2 | |||
2064450763 | |||
7b8dc189f7 | |||
537ec510f0 | |||
f12589ab80 | |||
08c409d048 | |||
f9e4b65713 | |||
|
af1ef93100 | ||
844697c04c | |||
ffbe7a3e42 | |||
319d144995 | |||
f82cf7fa85 | |||
fd9bf16871 | |||
713dae7110 | |||
8f70f191bb | |||
318d95491c | |||
57bdb88272 | |||
6bbb3ec5eb | |||
1fa1520fea | |||
f64229fcc9 | |||
a06b79a6c1 | |||
1a983beec1 | |||
5e81d85d7e | |||
a2606569bb | |||
89ddd5e85f | |||
df092f25ec | |||
1b348ceeff | |||
067ec06f26 | |||
ab05e7bdae | |||
9b0bd29db4 | |||
2a6ac0764f | |||
e4ead93f19 | |||
086f979a28 | |||
40bcb62a77 | |||
4a4de81961 | |||
45d7371274 | |||
3087b0c996 | |||
338f931c51 | |||
874f051810 | |||
be81877137 | |||
93a96786a8 | |||
123c15a293 | |||
bbfca34133 | |||
f5f50b85e5 | |||
f311ac27d4 | |||
415c0d873e | |||
cc3e64a315 | |||
61443fa481 | |||
1edb9d3448 | |||
f3a5e91a8c | |||
7517a207db | |||
abf995a702 | |||
04dd6dd0a4 | |||
5c0e5b1eb3 | |||
cee64cece3 | |||
c5ddae58a1 | |||
2c5f293210 | |||
5adf67fe04 | |||
0569862a1b | |||
d7cd305059 | |||
8da179b221 | |||
3d121722d7 | |||
99659bd9a3 | |||
1d3a7c969f | |||
d028d7c3e9 | |||
b103e6fdc0 | |||
0a7648ce48 | |||
f27c87b291 | |||
e37524899e | |||
ae4b1e85e6 | |||
a64c650504 | |||
cfdd8e690f | |||
a5e7ff771d | |||
da73b4316d | |||
3d340f585a | |||
bacb5c0199 | |||
acd52756be | |||
4bb317a7e1 | |||
|
9e34a2660d | ||
fbcaa6eca9 | |||
|
b35904a0b5 | ||
6a5a88700c | |||
e1d3b902ba | |||
e9b3128103 | |||
1493c06de5 | |||
5d799be9db | |||
ad4eaa174c | |||
e0d3352a90 | |||
a8e76a0bff | |||
2b7be768f2 | |||
8dff973d26 | |||
4a1fdf0121 | |||
aa0256da10 | |||
e9452032bf | |||
d58a1c4fa0 | |||
685aeff13f | |||
7b442368da | |||
f7c9419045 | |||
af3affb467 | |||
68f657310b | |||
7bbb617bd8 | |||
|
9a40b65bc1 | ||
52e34de95c | |||
50c8e6bc5b | |||
5666918519 | |||
b3f65f9d4b | |||
1d3f659c1d | |||
881055e587 | |||
|
11a74f215e | ||
4a7d21425c | |||
b3af8d73fa | |||
65c9f555b0 | |||
c84b3161ae | |||
adef9bf37b | |||
89bc5ca91e | |||
54062c82fd | |||
0fc0fe41d4 | |||
8012fb90b5 | |||
c7d53d6532 | |||
8045b4eda2 | |||
ae3722d72c | |||
fefc5db09a | |||
b7bc08e352 | |||
|
6c1b144970 | ||
77f9d929b6 | |||
e86087366f | |||
ad45cf5431 | |||
43d7c428e3 | |||
|
3f60bdadac | ||
1989f4ec97 | |||
2aa046c4b5 | |||
6a83a6fa38 | |||
b828bc2b12 | |||
66174fc7d4 | |||
a9820dfd79 | |||
1a0c4e2de9 | |||
fd27ca3bec | |||
960b840536 | |||
e2945b90a9 | |||
ed4d5937a7 | |||
31b143f821 | |||
3112dc3ea0 | |||
1be8d8638f | |||
913d6ac8c5 | |||
7dcc86e266 | |||
adf8a604c4 | |||
efc28db6cf | |||
f250978cdd | |||
5fd57fd630 | |||
e99ae608b6 | |||
1fd0292590 | |||
09d515c8cd | |||
5272db8bb4 | |||
635bed9fcb | |||
097ebb3705 | |||
55ed46d081 | |||
43ebbee94a | |||
d0b459e827 | |||
4e5c551d65 | |||
02de98240f | |||
37cde3ad32 | |||
|
ef357fdf36 | ||
8af265e48b | |||
4c53498f07 | |||
|
b25474ad66 | ||
656162d218 | |||
113222a3a2 | |||
aa2039c868 | |||
5c7332b469 | |||
13d5cbed1f | |||
75efbc83fd | |||
ca2e0b3922 | |||
|
b0bd992adc | ||
a95408cbcc | |||
5f5616145b | |||
d6a4d91347 | |||
|
aeff08f5fa | ||
7981230904 | |||
95bdb362ba | |||
f8140e4188 | |||
713b312167 | |||
249fa55a53 | |||
65fe072151 | |||
7a192acc99 | |||
e9a5df2e87 | |||
42c79043cf | |||
28a17f3ed2 | |||
cf66c4be10 | |||
c9022e8f67 | |||
72d87349fe | |||
7d6cacc79b | |||
cfe6bea25a | |||
9e061be903 | |||
172e0ce079 | |||
b9767424f3 | |||
6a5ab34a2d | |||
2be4f0f2b6 | |||
21acf4c846 | |||
dd557c8f29 | |||
|
e21e1b97bf | ||
f8130c3c92 | |||
34f53baf85 | |||
bdd2fc91e1 | |||
6fdf5fce49 | |||
444993b609 | |||
74b52a1fc1 | |||
27945c8870 | |||
f5f15362f2 | |||
5fdea4c0b7 | |||
24babbdbe5 | |||
|
833f306e8b | ||
63b1036a96 | |||
|
c285fadd34 | ||
022771b71b | |||
228489dded | |||
4fb6b07746 | |||
02f50c2dc5 | |||
6020a2297f | |||
|
858bec2081 | ||
8d110a9f7c | |||
b08d3d55d7 | |||
cdb68dfb70 | |||
479a7b8927 | |||
8d71577da8 | |||
4562967b61 | |||
87c34e1c33 | |||
4aa5010ea2 | |||
5bdb07b314 | |||
6c681adc4d | |||
6d8d937d47 | |||
befcec9b3a | |||
ef25e77e52 | |||
67c053638c | |||
ed00a5b8f1 | |||
fe423416b4 | |||
b6ddde99ea | |||
f2d8d4211b | |||
b0b57aa7d4 | |||
53fca5faf4 | |||
900a4234e5 | |||
aef29daad9 | |||
2dcfbe96c3 | |||
a86d011d4a | |||
5721eaf193 | |||
862dec529b | |||
a1435b3b95 | |||
093c99a071 | |||
608ccb8e54 | |||
a5cb21f039 | |||
e03101a7b7 | |||
31d0ac7568 | |||
55d1ae2014 | |||
|
9e98ab373f | ||
3ce7c7cc50 | |||
|
9ef53fcf2b | ||
|
0d16f01571 | ||
615d8f9d2a | |||
ebaf5128fb | |||
5693191b57 | |||
04036b6e4a | |||
6ccd44e28d | |||
185ed84dbb | |||
3c3952bb0b | |||
33e1a1326a | |||
344834c724 | |||
adb56b7eb2 | |||
3c83e5b24a | |||
e11a902fba | |||
2f1fe5f9f6 | |||
8f0d0d9280 | |||
04eaa5c3d0 | |||
0ee9ed50e3 | |||
d67aa07c13 | |||
a599b5614e | |||
f03e0c2235 | |||
de5a53ce08 | |||
958848e8ed | |||
30cbeecdfe | |||
8772661772 | |||
1609b90a93 | |||
fa132b61fc | |||
2c37ade959 | |||
63fef9d94f | |||
a477a84c6e | |||
3b1fe490b0 | |||
478fe722f5 | |||
641b99c7af | |||
98e5efb577 | |||
831e8c167f | |||
ad30950f85 |
562 changed files with 37387 additions and 17073 deletions
45
.cargo/config_fast_builds → .cargo/config_fast_builds.toml
Executable file → Normal file
45
.cargo/config_fast_builds → .cargo/config_fast_builds.toml
Executable file → Normal 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
0
.github/workflows/check.yml
vendored
Executable file → Normal file
71
.github/workflows/doc.yml
vendored
71
.github/workflows/doc.yml
vendored
|
@ -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
3
.gitignore
vendored
Executable file → Normal 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
0
.gitpod.yml
Executable file → Normal file
93
CHANGELOG.md
Normal file
93
CHANGELOG.md
Normal 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
2153
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
95
Cargo.toml
95
Cargo.toml
|
@ -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
43
README.md
Executable file → Normal 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 :).
|
||||
|
|
|
@ -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
4
azalea-auth/README.md
Executable file → Normal 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
0
azalea-auth/examples/auth.rs
Executable file → Normal file
0
azalea-auth/examples/auth_manual.rs
Executable file → Normal file
0
azalea-auth/examples/auth_manual.rs
Executable file → Normal file
0
azalea-auth/examples/certificates.rs
Executable file → Normal file
0
azalea-auth/examples/certificates.rs
Executable file → Normal file
42
azalea-auth/src/auth.rs
Executable file → Normal file
42
azalea-auth/src/auth.rs
Executable file → Normal 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
23
azalea-auth/src/cache.rs
Executable file → Normal 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)?;
|
||||
|
|
|
@ -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
19
azalea-auth/src/game_profile.rs
Executable file → Normal 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
0
azalea-auth/src/lib.rs
Executable file → Normal file
2
azalea-auth/src/sessionserver.rs
Executable file → Normal file
2
azalea-auth/src/sessionserver.rs
Executable file → Normal 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
|
||||
|
|
|
@ -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
14
azalea-block/README.md
Executable file → Normal 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>() {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
|
|
@ -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
81
azalea-block/azalea-block-macros/src/lib.rs
Executable file → Normal 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
0
azalea-block/azalea-block-macros/src/utils.rs
Executable file → Normal file
8
azalea-block/src/behavior.rs
Executable file → Normal file
8
azalea-block/src/behavior.rs
Executable file → Normal 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
|
||||
}
|
||||
}
|
||||
|
|
178
azalea-block/src/block_state.rs
Normal file
178
azalea-block/src/block_state.rs
Normal 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
|
||||
);
|
||||
}
|
||||
}
|
137
azalea-block/src/fluid_state.rs
Normal file
137
azalea-block/src/fluid_state.rs
Normal 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
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
195
azalea-block/src/lib.rs
Executable file → Normal 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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
0
azalea-brigadier/README.md
Executable file → Normal file
4
azalea-brigadier/src/arguments/argument_type.rs
Executable file → Normal file
4
azalea-brigadier/src/arguments/argument_type.rs
Executable file → Normal 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()
|
||||
|
|
|
@ -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()?))
|
||||
}
|
||||
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -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
0
azalea-brigadier/src/arguments/mod.rs
Executable file → Normal 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
40
azalea-brigadier/src/builder/argument_builder.rs
Executable file → Normal 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)
|
||||
|
|
2
azalea-brigadier/src/builder/literal_argument_builder.rs
Executable file → Normal file
2
azalea-brigadier/src/builder/literal_argument_builder.rs
Executable file → Normal 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
0
azalea-brigadier/src/builder/mod.rs
Executable file → Normal file
61
azalea-brigadier/src/builder/required_argument_builder.rs
Executable file → Normal file
61
azalea-brigadier/src/builder/required_argument_builder.rs
Executable file → Normal 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
227
azalea-brigadier/src/command_dispatcher.rs
Executable file → Normal 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
78
azalea-brigadier/src/context/command_context.rs
Executable file → Normal 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
37
azalea-brigadier/src/context/command_context_builder.rs
Executable file → Normal 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)
|
||||
|
|
167
azalea-brigadier/src/context/context_chain.rs
Normal file
167
azalea-brigadier/src/context/context_chain.rs
Normal 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
2
azalea-brigadier/src/context/mod.rs
Executable file → Normal 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
0
azalea-brigadier/src/context/parsed_argument.rs
Executable file → Normal file
0
azalea-brigadier/src/context/parsed_command_node.rs
Executable file → Normal file
0
azalea-brigadier/src/context/parsed_command_node.rs
Executable file → Normal file
0
azalea-brigadier/src/context/string_range.rs
Executable file → Normal file
0
azalea-brigadier/src/context/string_range.rs
Executable file → Normal file
70
azalea-brigadier/src/exceptions/builtin_exceptions.rs → azalea-brigadier/src/errors/builtin_errors.rs
Executable file → Normal file
70
azalea-brigadier/src/exceptions/builtin_exceptions.rs → azalea-brigadier/src/errors/builtin_errors.rs
Executable file → Normal 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())
|
||||
}
|
||||
}
|
93
azalea-brigadier/src/errors/command_syntax_error.rs
Normal file
93
azalea-brigadier/src/errors/command_syntax_error.rs
Normal 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())
|
||||
}
|
||||
}
|
5
azalea-brigadier/src/errors/mod.rs
Normal file
5
azalea-brigadier/src/errors/mod.rs
Normal file
|
@ -0,0 +1,5 @@
|
|||
mod builtin_errors;
|
||||
mod command_syntax_error;
|
||||
|
||||
pub use builtin_errors::BuiltInError;
|
||||
pub use command_syntax_error::CommandSyntaxError;
|
|
@ -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())
|
||||
}
|
||||
}
|
|
@ -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
3
azalea-brigadier/src/lib.rs
Executable file → Normal 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
4
azalea-brigadier/src/modifier.rs
Executable file → Normal 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
14
azalea-brigadier/src/parse_results.rs
Executable file → Normal 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)
|
||||
|
|
12
azalea-brigadier/src/result_consumer.rs
Normal file
12
azalea-brigadier/src/result_consumer.rs
Normal 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
53
azalea-brigadier/src/string_reader.rs
Executable file → Normal 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
13
azalea-brigadier/src/suggestion/mod.rs
Executable file → Normal 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()
|
||||
|
|
10
azalea-brigadier/src/suggestion/suggestion_provider.rs
Normal file
10
azalea-brigadier/src/suggestion/suggestion_provider.rs
Normal 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
4
azalea-brigadier/src/suggestion/suggestions.rs
Executable file → Normal 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
0
azalea-brigadier/src/suggestion/suggestions_builder.rs
Executable file → Normal file
56
azalea-brigadier/src/tree/mod.rs
Executable file → Normal file
56
azalea-brigadier/src/tree/mod.rs
Executable file → Normal 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
|
||||
}
|
||||
|
|
0
azalea-brigadier/tests/arguments/bool_argument_type_test.rs
Executable file → Normal file
0
azalea-brigadier/tests/arguments/bool_argument_type_test.rs
Executable file → Normal file
0
azalea-brigadier/tests/arguments/double_argument_type_test.rs
Executable file → Normal file
0
azalea-brigadier/tests/arguments/double_argument_type_test.rs
Executable file → Normal file
0
azalea-brigadier/tests/arguments/float_argument_type_test.rs
Executable file → Normal file
0
azalea-brigadier/tests/arguments/float_argument_type_test.rs
Executable file → Normal file
0
azalea-brigadier/tests/arguments/integer_argument_type_test.rs
Executable file → Normal file
0
azalea-brigadier/tests/arguments/integer_argument_type_test.rs
Executable file → Normal file
0
azalea-brigadier/tests/arguments/long_argument_type_test.rs
Executable file → Normal file
0
azalea-brigadier/tests/arguments/long_argument_type_test.rs
Executable file → Normal file
0
azalea-brigadier/tests/arguments/string_argument_type_test.rs
Executable file → Normal file
0
azalea-brigadier/tests/arguments/string_argument_type_test.rs
Executable file → Normal 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
12
azalea-brigadier/tests/builder/argument_builder_test.rs
Executable file → Normal 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
|
||||
|
|
0
azalea-brigadier/tests/builder/literal_argument_builder_test.rs
Executable file → Normal file
0
azalea-brigadier/tests/builder/literal_argument_builder_test.rs
Executable file → Normal file
0
azalea-brigadier/tests/builder/required_argument_builder_test.rs
Executable file → Normal file
0
azalea-brigadier/tests/builder/required_argument_builder_test.rs
Executable file → Normal file
54
azalea-brigadier/tests/command_dispatcher_test.rs
Executable file → Normal file
54
azalea-brigadier/tests/command_dispatcher_test.rs
Executable file → Normal 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));
|
||||
}
|
||||
|
||||
|
|
0
azalea-brigadier/tests/command_dispatcher_usages_test.rs
Executable file → Normal file
0
azalea-brigadier/tests/command_dispatcher_usages_test.rs
Executable file → Normal file
0
azalea-brigadier/tests/command_suggestions_test.rs
Executable file → Normal file
0
azalea-brigadier/tests/command_suggestions_test.rs
Executable file → Normal file
0
azalea-brigadier/tests/context/command_context_test.rs
Executable file → Normal file
0
azalea-brigadier/tests/context/command_context_test.rs
Executable file → Normal file
0
azalea-brigadier/tests/context/parsed_argument_test.rs
Executable file → Normal file
0
azalea-brigadier/tests/context/parsed_argument_test.rs
Executable file → Normal file
0
azalea-brigadier/tests/exceptions/dynamic_command_syntax_exception_type_test.rs
Executable file → Normal file
0
azalea-brigadier/tests/exceptions/dynamic_command_syntax_exception_type_test.rs
Executable file → Normal file
0
azalea-brigadier/tests/exceptions/simple_command_syntax_exception_type_test.rs
Executable file → Normal file
0
azalea-brigadier/tests/exceptions/simple_command_syntax_exception_type_test.rs
Executable file → Normal file
52
azalea-brigadier/tests/string_reader_test.rs
Executable file → Normal file
52
azalea-brigadier/tests/string_reader_test.rs
Executable file → Normal 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
0
azalea-brigadier/tests/suggestion/suggestion_test.rs
Executable file → Normal file
0
azalea-brigadier/tests/suggestion/suggestions_builder_test.rs
Executable file → Normal file
0
azalea-brigadier/tests/suggestion/suggestions_builder_test.rs
Executable file → Normal file
2
azalea-brigadier/tests/suggestion/suggestions_test.rs
Executable file → Normal file
2
azalea-brigadier/tests/suggestion/suggestions_test.rs
Executable file → Normal 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);
|
||||
}
|
||||
|
||||
|
|
0
azalea-brigadier/tests/tree/abstract_command_node_test.rs
Executable file → Normal file
0
azalea-brigadier/tests/tree/abstract_command_node_test.rs
Executable file → Normal file
0
azalea-brigadier/tests/tree/argument_command_node_test.rs
Executable file → Normal file
0
azalea-brigadier/tests/tree/argument_command_node_test.rs
Executable file → Normal file
0
azalea-brigadier/tests/tree/literal_command_node_test.rs
Executable file → Normal file
0
azalea-brigadier/tests/tree/literal_command_node_test.rs
Executable file → Normal file
0
azalea-brigadier/tests/tree/root_command_node_test.rs
Executable file → Normal file
0
azalea-brigadier/tests/tree/root_command_node_test.rs
Executable file → Normal 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
0
azalea-buf/README.md
Executable file → Normal 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
0
azalea-buf/azalea-buf-macros/README.md
Executable file → Normal file
2
azalea-buf/azalea-buf-macros/src/lib.rs
Executable file → Normal file
2
azalea-buf/azalea-buf-macros/src/lib.rs
Executable file → Normal 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 {
|
||||
|
|
|
@ -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()
|
||||
),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
Loading…
Add table
Reference in a new issue