├── .cargo └── config.toml ├── .devcontainer.json ├── .dockerignore ├── .envrc ├── .github ├── FUNDING.yml ├── chart │ ├── .gitignore │ ├── .python-version │ ├── README.md │ ├── pyproject.toml │ ├── readme_chart.py │ └── uv.lock ├── codecov.yml ├── dependabot.yml └── workflows │ ├── bench.yml │ ├── ci.yml │ ├── conventional.yml │ ├── deploy.yml │ └── publish.yml ├── .gitignore ├── .pre-commit-config.yaml ├── CNAME ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Cargo.lock ├── Cargo.toml ├── Dockerfile ├── GOVERNANCE.md ├── LICENSE ├── README.md ├── clippy.toml ├── crates ├── bvh-region │ ├── Cargo.toml │ ├── README.md │ ├── benches │ │ ├── bvh.rs │ │ ├── side_by_side.rs │ │ └── sort.rs │ ├── src │ │ ├── build.rs │ │ ├── lib.rs │ │ ├── node.rs │ │ ├── plot.rs │ │ ├── query.rs │ │ ├── query │ │ │ ├── closest.rs │ │ │ ├── range.rs │ │ │ └── ray.rs │ │ ├── tests.rs │ │ └── utils.rs │ └── tests │ │ └── simple.rs ├── geometry │ ├── .gitignore │ ├── Cargo.toml │ ├── README.md │ ├── benches │ │ └── general.rs │ ├── build.rs │ └── src │ │ ├── aabb.rs │ │ ├── lib.rs │ │ └── ray.rs ├── hyperion-clap-macros │ ├── .gitignore │ ├── Cargo.toml │ ├── README.md │ └── src │ │ └── lib.rs ├── hyperion-clap │ ├── .gitignore │ ├── Cargo.toml │ ├── README.md │ ├── src │ │ └── lib.rs │ └── tests │ │ └── gamemode.rs ├── hyperion-command │ ├── .gitignore │ ├── Cargo.toml │ ├── README.md │ └── src │ │ ├── component.rs │ │ ├── lib.rs │ │ └── system.rs ├── hyperion-crafting │ ├── .gitignore │ ├── Cargo.toml │ ├── README.md │ └── src │ │ └── lib.rs ├── hyperion-event-macros │ ├── .gitignore │ ├── Cargo.toml │ ├── README.md │ └── src │ │ └── lib.rs ├── hyperion-genmap │ ├── .gitignore │ ├── Cargo.toml │ ├── README.md │ └── src │ │ └── lib.rs ├── hyperion-gui │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── hyperion-inventory │ ├── .gitignore │ ├── Cargo.toml │ ├── README.md │ └── src │ │ └── lib.rs ├── hyperion-item │ ├── .gitignore │ ├── Cargo.toml │ ├── README.md │ └── src │ │ ├── builder.rs │ │ ├── builder │ │ └── book.rs │ │ └── lib.rs ├── hyperion-minecraft-proto │ ├── .gitignore │ ├── Cargo.toml │ ├── README.md │ └── src │ │ └── lib.rs ├── hyperion-nerd-font │ ├── .gitignore │ ├── Cargo.toml │ ├── README.md │ └── src │ │ └── lib.rs ├── hyperion-packet-macros │ ├── .gitignore │ ├── Cargo.toml │ ├── README.md │ └── src │ │ └── lib.rs ├── hyperion-palette │ ├── .gitignore │ ├── Cargo.toml │ ├── README.md │ └── src │ │ ├── encode.rs │ │ ├── indirect.rs │ │ └── lib.rs ├── hyperion-permission │ ├── .gitignore │ ├── Cargo.toml │ ├── README.md │ └── src │ │ ├── lib.rs │ │ └── storage.rs ├── hyperion-proto │ ├── Cargo.toml │ ├── README.md │ └── src │ │ ├── lib.rs │ │ ├── proxy_to_server.rs │ │ ├── server_to_proxy.rs │ │ └── shared.rs ├── hyperion-proxy-module │ ├── Cargo.toml │ ├── README.md │ └── src │ │ └── lib.rs ├── hyperion-proxy │ ├── Cargo.toml │ ├── README.md │ └── src │ │ ├── cache.rs │ │ ├── data.rs │ │ ├── egress.rs │ │ ├── lib.rs │ │ ├── main.rs │ │ ├── player.rs │ │ ├── server_sender.rs │ │ └── util.rs ├── hyperion-rank-tree │ ├── .gitignore │ ├── Cargo.toml │ ├── README.md │ └── src │ │ ├── inventory.rs │ │ └── lib.rs ├── hyperion-respawn │ ├── .gitignore │ ├── Cargo.toml │ ├── README.md │ └── src │ │ └── lib.rs ├── hyperion-scheduled │ ├── .gitignore │ ├── Cargo.toml │ ├── README.md │ └── src │ │ └── lib.rs ├── hyperion-stats │ ├── .gitignore │ ├── Cargo.toml │ ├── README.md │ ├── benches │ │ └── parallel_stats.rs │ └── src │ │ └── lib.rs ├── hyperion-text │ ├── .gitignore │ ├── Cargo.toml │ ├── README.md │ └── src │ │ ├── color.rs │ │ ├── event.rs │ │ ├── font.rs │ │ ├── helper.rs │ │ ├── lib.rs │ │ └── scoreboard.rs ├── hyperion-utils │ ├── .gitignore │ ├── Cargo.toml │ ├── README.md │ └── src │ │ ├── cached_save.rs │ │ ├── lib.rs │ │ └── lifetime.rs ├── hyperion │ ├── Cargo.toml │ ├── README.md │ ├── benches │ │ ├── atomic.rs │ │ └── set.rs │ ├── run │ │ └── config.toml │ ├── src │ │ ├── common │ │ │ ├── config.rs │ │ │ ├── mod.rs │ │ │ ├── runtime.rs │ │ │ └── util │ │ │ │ ├── mod.rs │ │ │ │ ├── mojang.rs │ │ │ │ ├── sendable.rs │ │ │ │ └── tracing_ext.rs │ │ ├── egress │ │ │ ├── metadata.rs │ │ │ ├── mod.rs │ │ │ ├── player_join │ │ │ │ ├── data │ │ │ │ │ └── tags.json │ │ │ │ ├── list.rs │ │ │ │ └── mod.rs │ │ │ ├── stats.rs │ │ │ ├── sync_chunks.rs │ │ │ └── sync_entity_state.rs │ │ ├── ingress │ │ │ ├── data │ │ │ │ └── hyperion.png │ │ │ └── mod.rs │ │ ├── lib.rs │ │ ├── net │ │ │ ├── agnostic.rs │ │ │ ├── agnostic │ │ │ │ ├── chat.rs │ │ │ │ └── sound.rs │ │ │ ├── decoder.rs │ │ │ ├── encoder │ │ │ │ ├── mod.rs │ │ │ │ └── util.rs │ │ │ ├── mod.rs │ │ │ ├── packets.rs │ │ │ └── proxy.rs │ │ ├── simulation │ │ │ ├── animation.rs │ │ │ ├── blocks │ │ │ │ ├── chunk.rs │ │ │ │ ├── chunk │ │ │ │ │ └── packet.rs │ │ │ │ ├── frame.rs │ │ │ │ ├── loader │ │ │ │ │ ├── mod.rs │ │ │ │ │ ├── parse.rs │ │ │ │ │ └── parse │ │ │ │ │ │ └── section.rs │ │ │ │ ├── manager.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── region.rs │ │ │ │ └── shared.rs │ │ │ ├── command.rs │ │ │ ├── data │ │ │ │ └── registries.nbt │ │ │ ├── entity_kind.rs │ │ │ ├── event.rs │ │ │ ├── handlers.rs │ │ │ ├── inventory.rs │ │ │ ├── metadata │ │ │ │ ├── block_display.rs │ │ │ │ ├── display.rs │ │ │ │ ├── entity.rs │ │ │ │ ├── entity │ │ │ │ │ └── flags.rs │ │ │ │ ├── living_entity.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── player.rs │ │ │ │ ├── status.rs │ │ │ │ └── type.rs │ │ │ ├── mod.rs │ │ │ ├── packet.rs │ │ │ ├── skin.rs │ │ │ └── util.rs │ │ ├── spatial │ │ │ └── mod.rs │ │ └── storage │ │ │ ├── bits.rs │ │ │ ├── buf.rs │ │ │ ├── db.rs │ │ │ ├── event │ │ │ ├── mod.rs │ │ │ ├── queue │ │ │ │ ├── event_queue.rs │ │ │ │ ├── mod.rs │ │ │ │ └── raw.rs │ │ │ └── sync.rs │ │ │ ├── mod.rs │ │ │ └── thread_local.rs │ └── tests │ │ ├── collision.rs │ │ ├── entity.rs │ │ └── spatial.rs ├── simd-utils │ ├── .gitignore │ ├── Cargo.toml │ ├── README.md │ ├── proptest-regressions │ │ └── lib.txt │ └── src │ │ ├── lib.rs │ │ └── one_bit_positions.rs └── system-order │ ├── .gitignore │ ├── Cargo.toml │ ├── README.md │ └── src │ └── lib.rs ├── deny.toml ├── docker-compose.yml ├── docs ├── .vitepress │ ├── benchmarks.md │ ├── config.mts │ └── theme │ │ ├── components │ │ └── GithubSnippet.vue │ │ ├── custom.css │ │ └── index.ts ├── architecture │ ├── game-server.md │ ├── introduction.md │ └── proxy.md ├── index.md └── tag │ ├── classes.png │ └── introduction.md ├── events └── tag │ ├── Cargo.toml │ ├── README.md │ ├── build.rs │ └── src │ ├── command.rs │ ├── command │ ├── bow.rs │ ├── chest.rs │ ├── class.rs │ ├── fly.rs │ ├── gui.rs │ ├── raycast.rs │ ├── replace.rs │ ├── shoot.rs │ ├── spawn.rs │ ├── speed.rs │ ├── vanish.rs │ └── xp.rs │ ├── lib.rs │ ├── main.rs │ ├── module.rs │ ├── module │ ├── attack.rs │ ├── block.rs │ ├── bow.rs │ ├── chat.rs │ ├── damage.rs │ ├── level.rs │ ├── regeneration.rs │ ├── spawn.rs │ ├── stats.rs │ └── vanish.rs │ └── skin.rs ├── flake.lock ├── flake.nix ├── justfile ├── libvoidstar.so ├── package.json ├── pnpm-lock.yaml ├── rust-toolchain.toml ├── rustfmt.toml └── tools ├── antithesis-bot ├── Cargo.toml └── src │ ├── bot.rs │ ├── lib.rs │ └── main.rs ├── packet-inspector ├── Cargo.toml ├── README.md ├── build.rs ├── extracted │ └── packets.json └── src │ ├── app.rs │ ├── app │ ├── connection.rs │ ├── filter.rs │ ├── hex_viewer.rs │ ├── packet_list.rs │ └── text_viewer.rs │ ├── lib.rs │ ├── main.rs │ ├── main_cli.rs │ ├── packet_io.rs │ ├── packet_registry.rs │ ├── shared_state.rs │ └── tri_checkbox.rs └── rust-mc-bot ├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md └── src ├── lib.rs ├── main.rs ├── net.rs ├── packet_processors.rs ├── packet_utils.rs └── states ├── login.rs ├── mod.rs ├── play.rs └── status.rs /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | rustflags = ["--cfg", "tokio_unstable", "-Ctarget-cpu=native"] 3 | 4 | [env] 5 | RUST_LOG="debug" -------------------------------------------------------------------------------- /.devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Debian", 3 | "forwardPorts": [ 4 | 25565 5 | ], 6 | "features": { 7 | "ghcr.io/devcontainers/features/rust:1": {} 8 | }, 9 | "customizations": { 10 | "vscode": { 11 | "extensions": [ 12 | "rust-lang.rust-analyzer" 13 | ] 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | # Rust build artifacts 2 | **/target/ 3 | 4 | # Git and development files 5 | .git/ 6 | .github/ 7 | .gitignore 8 | .DS_Store 9 | .idea/ 10 | .vscode/ 11 | .envrc 12 | .pre-commit-config.yaml 13 | .trigger* 14 | 15 | # Docker files 16 | Dockerfile 17 | docker-compose.yml 18 | 19 | # Configuration and environment 20 | .env 21 | deny.toml 22 | clippy.toml 23 | rustfmt.toml 24 | 25 | # Documentation and non-essential files 26 | docs/ 27 | CONTRIBUTING.md 28 | LICENSE 29 | README.md 30 | CNAME 31 | 32 | # Node.js 33 | node_modules/ 34 | pnpm-lock.yaml 35 | 36 | # Nix 37 | flake.nix 38 | flake.lock 39 | result/ 40 | 41 | # Project-specific 42 | run/ 43 | .devcontainer.json 44 | libvoidstar.so -------------------------------------------------------------------------------- /.envrc: -------------------------------------------------------------------------------- 1 | use flake -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: andrewgazelka 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 12 | polar: # Replace with a single Polar username 13 | buy_me_a_coffee: # Replace with a single Buy Me a Coffee username 14 | thanks_dev: # Replace with a single thanks.dev username 15 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 16 | -------------------------------------------------------------------------------- /.github/chart/.gitignore: -------------------------------------------------------------------------------- 1 | performance.png 2 | -------------------------------------------------------------------------------- /.github/chart/.python-version: -------------------------------------------------------------------------------- 1 | 3.12 2 | -------------------------------------------------------------------------------- /.github/chart/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperion-mc/hyperion/ddb109092b40bc381016e327c5b13eb0201e95a3/.github/chart/README.md -------------------------------------------------------------------------------- /.github/chart/pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | dependencies = [ 3 | "matplotlib>=3.9.2", 4 | "numpy>=2.1.2" 5 | ] 6 | description = "Add your description here" 7 | name = "chart" 8 | readme = "README.md" 9 | requires-python = ">=3.12" 10 | version = "0.1.0" 11 | -------------------------------------------------------------------------------- /.github/codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | status: 3 | project: 4 | default: 5 | # basic 6 | target: auto 7 | threshold: 1% 8 | base: auto 9 | # advanced 10 | branches: 11 | - main 12 | if_ci_failed: error 13 | informational: true 14 | only_pulls: false 15 | 16 | patch: 17 | default: 18 | # basic 19 | target: auto 20 | threshold: 1% 21 | base: auto 22 | # advanced 23 | branches: 24 | - main 25 | if_ci_failed: error 26 | informational: true 27 | only_pulls: false 28 | 29 | ignore: 30 | - "**/benches/**/*" # Ignore benchmarks 31 | - "**/*_test.rs" # Ignore test files 32 | - "**/tests/**/*" # Ignore test directories 33 | 34 | github_checks: 35 | annotations: true 36 | 37 | comment: 38 | layout: "diff, flags, files" 39 | behavior: default 40 | require_changes: false 41 | require_base: false 42 | require_head: true 43 | hide_project_coverage: false -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # Please see the documentation for all configuration options: 2 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 3 | 4 | version: 2 5 | updates: 6 | - package-ecosystem: "cargo" 7 | directory: "/" 8 | schedule: 9 | interval: "weekly" 10 | open-pull-requests-limit: 15 11 | groups: 12 | all-cargo-dependencies: 13 | patterns: 14 | - "*" 15 | - package-ecosystem: "gradle" 16 | directory: "/extractor/" 17 | schedule: 18 | interval: "weekly" 19 | open-pull-requests-limit: 15 20 | -------------------------------------------------------------------------------- /.github/workflows/conventional.yml: -------------------------------------------------------------------------------- 1 | name: PR Labeller 2 | 3 | on: 4 | pull_request_target: 5 | branches: [main] 6 | types: [opened, reopened, synchronize, edited, labeled, unlabeled] 7 | 8 | permissions: 9 | contents: read 10 | pull-requests: write 11 | 12 | jobs: 13 | validate_pr_title: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: PR Conventional Commit Validation 17 | uses: ytanikin/pr-conventional-commits@1.4.0 18 | with: 19 | task_types: '["feat","fix","docs","test","ci","refactor","perf","chore","revert"]' 20 | add_label: 'true' 21 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Deploy VitePress site to Pages 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | workflow_dispatch: 7 | 8 | permissions: 9 | contents: read 10 | pages: write 11 | id-token: write 12 | 13 | concurrency: 14 | group: pages 15 | cancel-in-progress: false 16 | 17 | jobs: 18 | build: 19 | runs-on: ubuntu-latest 20 | steps: 21 | - name: Checkout 22 | uses: actions/checkout@v4 23 | with: 24 | fetch-depth: 0 25 | - name: Setup Node 26 | uses: actions/setup-node@v4 27 | with: 28 | node-version: 20 29 | - name: Install pnpm 30 | uses: pnpm/action-setup@v2 31 | with: 32 | version: 9 33 | - name: Get pnpm store directory 34 | shell: bash 35 | run: | 36 | echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV 37 | - name: Setup pnpm cache 38 | uses: actions/cache@v3 39 | with: 40 | path: ${{ env.STORE_PATH }} 41 | key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} 42 | restore-keys: | 43 | ${{ runner.os }}-pnpm-store- 44 | - name: Setup Pages 45 | uses: actions/configure-pages@v4 46 | - name: Install dependencies 47 | run: pnpm install 48 | - name: Build with VitePress 49 | run: pnpm run docs:build 50 | - name: Upload artifact 51 | uses: actions/upload-pages-artifact@v3 52 | with: 53 | path: docs/.vitepress/dist 54 | 55 | deploy: 56 | environment: 57 | name: github-pages 58 | url: ${{ steps.deployment.outputs.page_url }} 59 | needs: build 60 | runs-on: ubuntu-latest 61 | steps: 62 | - name: Deploy to GitHub Pages 63 | id: deployment 64 | uses: actions/deploy-pages@v4 65 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # File Types 2 | *.webm 3 | *.jar 4 | *.svg 5 | *.zip 6 | 7 | /result 8 | 9 | # IDE Configs 10 | /.vscode 11 | .idea 12 | 13 | # Temp or Autogenerated 14 | /run 15 | /target 16 | /profiling 17 | node_modules/ 18 | db/ 19 | docs/.vitepress/cache 20 | crates/run/config.toml 21 | .env 22 | .gradle 23 | .DS_Store 24 | perf.data 25 | 26 | # Misc 27 | /extractor/build 28 | /extractor/out 29 | vendor/ 30 | /extractor/classes 31 | /extractor/run 32 | /extractor/bin 33 | .trigger-release 34 | .trigger-debug 35 | .trigger 36 | valence/ 37 | -------------------------------------------------------------------------------- /CNAME: -------------------------------------------------------------------------------- 1 | hyperion.rs -------------------------------------------------------------------------------- /clippy.toml: -------------------------------------------------------------------------------- 1 | # https://doc.rust-lang.org/nightly/clippy/lint_configuration.html 2 | 3 | cognitive-complexity-threshold = 200 4 | excessive-nesting-threshold = 7 5 | too-many-lines-threshold = 200 6 | -------------------------------------------------------------------------------- /crates/bvh-region/Cargo.toml: -------------------------------------------------------------------------------- 1 | [[bench]] 2 | harness = false 3 | name = "sort" 4 | 5 | #[[bench]] 6 | #harness = false 7 | #name = "bvh" 8 | # 9 | #[[bench]] 10 | #harness = false 11 | #name = "side_by_side" 12 | 13 | [dependencies] 14 | arrayvec = { workspace = true } 15 | derive_more = { workspace = true } 16 | fastrand = { workspace = true } 17 | geometry = { workspace = true } 18 | glam = { workspace = true, features = ["serde"] } 19 | num-traits = { workspace = true } 20 | ordered-float = { workspace = true } 21 | plotters = { workspace = true, features = ["plotters-bitmap", "image"], optional = true } 22 | plotters-bitmap = { workspace = true, optional = true } 23 | proptest = { workspace = true } 24 | rayon = { workspace = true } 25 | tracing = { workspace = true } 26 | 27 | [dev-dependencies] 28 | approx = { workspace = true } 29 | criterion = { workspace = true } 30 | rand = { workspace = true } 31 | #divan = {workspace = true} 32 | #tango-bench = {workspace = true} 33 | 34 | [features] 35 | default = [] 36 | plot = ["dep:plotters", "dep:plotters-bitmap"] 37 | 38 | [lints] 39 | workspace = true 40 | 41 | [package] 42 | authors = ["Andrew Gazelka "] 43 | edition.workspace = true 44 | name = "bvh-region" 45 | publish = false 46 | readme = "README.md" 47 | version.workspace = true 48 | -------------------------------------------------------------------------------- /crates/bvh-region/README.md: -------------------------------------------------------------------------------- 1 | # bvh 2 | 3 | This implements a Bounding Volume Hierarchy (BVH) that is used to accelerate collision detection and other spatial queries. -------------------------------------------------------------------------------- /crates/bvh-region/benches/sort.rs: -------------------------------------------------------------------------------- 1 | use std::hint::black_box; 2 | 3 | use criterion::{Bencher, Criterion, criterion_group, criterion_main}; 4 | use ordered_float::OrderedFloat; 5 | 6 | const LEN: usize = 100; 7 | 8 | fn sort_unwrap(bencher: &mut Bencher<'_>) { 9 | let mut arr = vec![0.0; LEN]; 10 | bencher.iter(|| { 11 | // step 1: random arr of LEN, floats 12 | for elem in &mut arr { 13 | *elem = rand::random::(); 14 | } 15 | arr.sort_unstable_by(|a, b| a.partial_cmp(b).unwrap()); 16 | 17 | black_box(&arr); 18 | }); 19 | } 20 | 21 | fn sort_unwrap_unchecked(bencher: &mut Bencher<'_>) { 22 | let mut arr = vec![0.0; LEN]; 23 | bencher.iter(|| { 24 | // step 1: random arr of LEN, floats 25 | for elem in &mut arr { 26 | *elem = rand::random::(); 27 | } 28 | arr.sort_unstable_by(|a, b| unsafe { a.partial_cmp(b).unwrap_unchecked() }); 29 | 30 | black_box(&arr); 31 | }); 32 | } 33 | 34 | fn sort_unwrap_unchecked_key(bencher: &mut Bencher<'_>) { 35 | let mut arr = vec![0.0; LEN]; 36 | bencher.iter(|| { 37 | // step 1: random arr of len, floats 38 | for elem in &mut arr { 39 | *elem = rand::random::(); 40 | } 41 | arr.sort_unstable_by_key(|a| OrderedFloat(*a)); 42 | 43 | black_box(&arr); 44 | }); 45 | } 46 | 47 | fn criterion_benchmark(c: &mut Criterion) { 48 | c.bench_function("sort_unwrap", sort_unwrap); 49 | c.bench_function("sort_unwrap_unchecked", sort_unwrap_unchecked); 50 | c.bench_function("sort_unwrap_unchecked_key", sort_unwrap_unchecked_key); 51 | } 52 | 53 | criterion_group!(benches, criterion_benchmark); 54 | criterion_main!(benches); 55 | -------------------------------------------------------------------------------- /crates/bvh-region/src/node.rs: -------------------------------------------------------------------------------- 1 | use geometry::aabb::Aabb; 2 | 3 | #[derive(Debug, Copy, Clone, PartialEq)] 4 | pub struct BvhNode { 5 | pub aabb: Aabb, // f32 * 6 = 24 bytes 6 | 7 | // if positive then it is an internal node; if negative then it is a leaf node 8 | pub left: i32, 9 | pub right: i32, 10 | } 11 | 12 | impl BvhNode { 13 | #[allow(dead_code)] 14 | const EMPTY_LEAF: Self = Self { 15 | aabb: Aabb::NULL, 16 | left: -1, 17 | right: 0, 18 | }; 19 | 20 | pub const fn create_leaf(aabb: Aabb, idx_left: usize, len: usize) -> Self { 21 | let left = idx_left as i32; 22 | let right = len as i32; 23 | 24 | let left = -left; 25 | 26 | let left = left - 1; 27 | 28 | Self { aabb, left, right } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /crates/bvh-region/src/query.rs: -------------------------------------------------------------------------------- 1 | mod closest; 2 | mod range; 3 | mod ray; 4 | -------------------------------------------------------------------------------- /crates/bvh-region/src/query/closest.rs: -------------------------------------------------------------------------------- 1 | use std::{cmp::Reverse, collections::BinaryHeap, fmt::Debug}; 2 | 3 | use geometry::aabb::Aabb; 4 | use glam::Vec3; 5 | use num_traits::Zero; 6 | use ordered_float::NotNan; 7 | 8 | use crate::{Bvh, Node, utils::NodeOrd}; 9 | 10 | impl Bvh { 11 | /// Returns the closest element to the target and the distance squared to it. 12 | pub fn get_closest(&self, target: Vec3, get_aabb: impl Fn(&T) -> Aabb) -> Option<(&T, f64)> { 13 | let mut min_dist2 = f64::INFINITY; 14 | let mut min_node = None; 15 | 16 | let on = self.root(); 17 | 18 | let on = match on { 19 | Node::Internal(internal) => internal, 20 | Node::Leaf(leaf) => { 21 | return leaf 22 | .iter() 23 | .map(|elem| { 24 | let aabb = get_aabb(elem); 25 | let dist2 = aabb.dist2(target); 26 | (elem, dist2) 27 | }) 28 | .min_by_key(|(_, dist)| dist.to_bits()); 29 | } 30 | }; 31 | 32 | // let mut stack: SmallVec<&BvhNode, 64> = SmallVec::new(); 33 | let mut heap: BinaryHeap<_> = std::iter::once(on) 34 | .map(|node| { 35 | Reverse(NodeOrd { 36 | node, 37 | by: NotNan::zero(), 38 | }) 39 | }) 40 | .collect(); 41 | 42 | while let Some(on) = heap.pop() { 43 | let on = on.0.node; 44 | let dist2 = on.aabb.dist2(target); 45 | 46 | if dist2 > min_dist2 { 47 | break; 48 | } 49 | 50 | for child in on.children(self) { 51 | match child { 52 | Node::Internal(internal) => { 53 | let dist2 = internal.aabb.dist2(target); 54 | let dist2 = NotNan::new(dist2).unwrap(); 55 | 56 | heap.push(Reverse(NodeOrd { 57 | node: internal, 58 | by: dist2, 59 | })); 60 | } 61 | Node::Leaf(leaf) => { 62 | let Some((elem, dist2)) = leaf 63 | .iter() 64 | .map(|elem| { 65 | let aabb = get_aabb(elem); 66 | let dist2 = aabb.dist2(target); 67 | (elem, dist2) 68 | }) 69 | .min_by_key(|(_, dist)| dist.to_bits()) 70 | else { 71 | continue; 72 | }; 73 | 74 | if dist2 < min_dist2 { 75 | min_dist2 = dist2; 76 | min_node = Some(elem); 77 | } 78 | } 79 | } 80 | } 81 | } 82 | 83 | min_node.map(|elem| (elem, min_dist2)) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /crates/bvh-region/src/utils.rs: -------------------------------------------------------------------------------- 1 | use derive_more::Constructor; 2 | use geometry::aabb::Aabb; 3 | 4 | use crate::node::BvhNode; 5 | 6 | /// get number of threads that is pow of 2 7 | pub fn thread_count_pow2() -> usize { 8 | let max_threads_tentative = rayon::current_num_threads(); 9 | // let max 10 | 11 | // does not make sense to not have a power of two 12 | let mut max_threads = max_threads_tentative.next_power_of_two(); 13 | 14 | if max_threads != max_threads_tentative { 15 | max_threads >>= 1; 16 | } 17 | 18 | max_threads 19 | } 20 | 21 | pub trait GetAabb: Fn(&T) -> Aabb {} 22 | 23 | impl GetAabb for F where F: Fn(&T) -> Aabb {} 24 | 25 | #[derive(Constructor, Copy, Clone, Debug)] 26 | pub struct NodeOrd<'a, T> { 27 | pub node: &'a BvhNode, 28 | pub by: T, 29 | } 30 | 31 | impl PartialEq for NodeOrd<'_, T> { 32 | fn eq(&self, other: &Self) -> bool { 33 | self.by == other.by 34 | } 35 | } 36 | impl PartialOrd for NodeOrd<'_, T> { 37 | fn partial_cmp(&self, other: &Self) -> Option { 38 | self.by.partial_cmp(&other.by) 39 | } 40 | } 41 | 42 | impl Eq for NodeOrd<'_, T> {} 43 | 44 | impl Ord for NodeOrd<'_, T> { 45 | fn cmp(&self, other: &Self) -> std::cmp::Ordering { 46 | self.by.cmp(&other.by) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /crates/geometry/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /crates/geometry/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "geometry" 3 | version.workspace = true 4 | edition.workspace = true 5 | authors = ["Andrew Gazelka "] 6 | readme = "README.md" 7 | publish = false 8 | 9 | [dependencies] 10 | glam = { workspace = true, features = ["serde"] } 11 | serde = { workspace = true, features = ["derive"] } 12 | ordered-float = { workspace = true } 13 | 14 | [dev-dependencies] 15 | approx = { workspace = true } 16 | rand = { workspace = true } 17 | tango-bench = { workspace = true } 18 | 19 | [lints] 20 | workspace = true 21 | 22 | [[bench]] 23 | name = "general" 24 | harness = false 25 | -------------------------------------------------------------------------------- /crates/geometry/README.md: -------------------------------------------------------------------------------- 1 | # aabb -------------------------------------------------------------------------------- /crates/geometry/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | // for tango-bench 3 | println!("cargo:rustc-link-arg-benches=-rdynamic"); 4 | println!("cargo:rerun-if-changed=build.rs"); 5 | } 6 | -------------------------------------------------------------------------------- /crates/geometry/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod aabb; 2 | pub mod ray; 3 | -------------------------------------------------------------------------------- /crates/hyperion-clap-macros/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /crates/hyperion-clap-macros/Cargo.toml: -------------------------------------------------------------------------------- 1 | [dependencies] 2 | quote.workspace = true 3 | syn.workspace = true 4 | 5 | [lib] 6 | proc-macro = true 7 | 8 | [lints] 9 | workspace = true 10 | 11 | [package] 12 | authors = ["Andrew Gazelka "] 13 | edition.workspace = true 14 | name = "hyperion-clap-macros" 15 | publish = false 16 | readme = "README.md" 17 | version.workspace = true 18 | -------------------------------------------------------------------------------- /crates/hyperion-clap-macros/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperion-mc/hyperion/ddb109092b40bc381016e327c5b13eb0201e95a3/crates/hyperion-clap-macros/README.md -------------------------------------------------------------------------------- /crates/hyperion-clap-macros/src/lib.rs: -------------------------------------------------------------------------------- 1 | use proc_macro::TokenStream; 2 | use quote::quote; 3 | use syn::{DeriveInput, Error, Ident, Lit, parse_macro_input}; 4 | 5 | #[proc_macro_derive(CommandPermission, attributes(command_permission))] 6 | pub fn derive_command_permission(input: TokenStream) -> TokenStream { 7 | // Parse the input as a DeriveInput (struct or enum) 8 | let input = parse_macro_input!(input as DeriveInput); 9 | let name = input.ident.clone(); // Clone the Ident to prevent moving 10 | 11 | // Extract the group from the `#[command_permission(group = "Admin")]` attribute 12 | let mut group = None; 13 | for attr in &input.attrs { 14 | if attr.path().is_ident("command_permission") { 15 | if let Err(err) = attr.parse_nested_meta(|meta| { 16 | if meta.path.is_ident("group") { 17 | if let Ok(Lit::Str(lit)) = meta.value()?.parse::() { 18 | group = Some(lit); 19 | } 20 | } 21 | Ok(()) 22 | }) { 23 | return Error::new_spanned(attr, format!("Failed to parse attribute: {err}")) 24 | .to_compile_error() 25 | .into(); 26 | } 27 | } 28 | } 29 | 30 | let group_ident = match group { 31 | Some(g) => Ident::new(&g.value(), g.span()), 32 | None => { 33 | return Error::new_spanned( 34 | input, 35 | "Missing required `#[command_permission(group = \"\")]` attribute.", 36 | ) 37 | .to_compile_error() 38 | .into(); 39 | } 40 | }; 41 | 42 | // Generate the trait implementation 43 | let expanded = quote! { 44 | impl CommandPermission for #name { 45 | fn has_required_permission(user_group: ::hyperion_permission::Group) -> bool { 46 | const REQUIRED_GROUP: ::hyperion_permission::Group = ::hyperion_permission::Group::#group_ident; 47 | 48 | if REQUIRED_GROUP == ::hyperion_permission::Group::Banned { 49 | // When checking for the group "Banned" we don't want to check for higher groups. 50 | return REQUIRED_GROUP == user_group; 51 | } else { 52 | return user_group as u32 >= REQUIRED_GROUP as u32 53 | } 54 | } 55 | } 56 | }; 57 | 58 | TokenStream::from(expanded) 59 | } 60 | -------------------------------------------------------------------------------- /crates/hyperion-clap/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /crates/hyperion-clap/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hyperion-clap" 3 | version.workspace = true 4 | edition.workspace = true 5 | authors = ["Andrew Gazelka "] 6 | readme = "README.md" 7 | publish = false 8 | 9 | [dependencies] 10 | clap ={ workspace = true } 11 | flecs_ecs = { workspace = true } 12 | hyperion = { workspace = true } 13 | hyperion-clap-macros = { workspace = true } 14 | hyperion-command = { workspace = true } 15 | hyperion-permission = { workspace = true } 16 | tracing = { workspace = true } 17 | valence_protocol = { workspace = true } 18 | 19 | [lints] 20 | workspace = true 21 | -------------------------------------------------------------------------------- /crates/hyperion-clap/README.md: -------------------------------------------------------------------------------- 1 | # hyperion-clap -------------------------------------------------------------------------------- /crates/hyperion-clap/tests/gamemode.rs: -------------------------------------------------------------------------------- 1 | use clap::Parser; 2 | 3 | #[derive(Parser, Debug)] 4 | #[command(name = "gamemode")] 5 | #[command(about = "Change the gamemode of a player")] 6 | struct Gamemode { 7 | /// The gamemode to set 8 | #[arg(value_enum)] 9 | mode: hyperion_clap::GameMode, 10 | 11 | /// The player to change the gamemode of 12 | player: Option, 13 | } 14 | -------------------------------------------------------------------------------- /crates/hyperion-command/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /crates/hyperion-command/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hyperion-command" 3 | version.workspace = true 4 | edition.workspace = true 5 | authors = ["Andrew Gazelka "] 6 | readme = "README.md" 7 | publish = false 8 | 9 | [dependencies] 10 | flecs_ecs = { workspace = true } 11 | hyperion = { workspace = true } 12 | hyperion-utils = { workspace = true } 13 | indexmap = { workspace = true } 14 | tracing = { workspace = true } 15 | 16 | [lints] 17 | workspace = true 18 | -------------------------------------------------------------------------------- /crates/hyperion-command/README.md: -------------------------------------------------------------------------------- 1 | # hyperion-command -------------------------------------------------------------------------------- /crates/hyperion-command/src/component.rs: -------------------------------------------------------------------------------- 1 | use flecs_ecs::{ 2 | core::{Entity, EntityView, World}, 3 | macros::Component, 4 | prelude::Module, 5 | }; 6 | use hyperion::{simulation::handlers::PacketSwitchQuery, storage::CommandCompletionRequest}; 7 | use indexmap::IndexMap; 8 | 9 | pub type OnTabComplete = 10 | Box, &CommandCompletionRequest<'_>) + 'static + Send + Sync>; 11 | pub struct CommandHandler { 12 | pub on_execute: fn(input: &str, system: EntityView<'_>, caller: Entity), 13 | pub on_tab_complete: OnTabComplete, 14 | pub has_permissions: fn(world: &World, caller: Entity) -> bool, 15 | } 16 | 17 | #[derive(Component)] 18 | pub struct CommandRegistry { 19 | pub(crate) commands: IndexMap, 20 | } 21 | 22 | impl CommandRegistry { 23 | pub fn register(&mut self, name: impl Into, handler: CommandHandler) { 24 | let name = name.into(); 25 | self.commands.insert(name, handler); 26 | } 27 | 28 | pub fn all(&self) -> impl Iterator { 29 | self.commands.keys().map(String::as_str) 30 | } 31 | 32 | /// Returns an iterator over the names of commands (`&str`) that the given entity (`caller`) 33 | /// has permission to execute. 34 | pub fn get_permitted(&self, world: &World, caller: Entity) -> impl Iterator { 35 | self.commands 36 | .iter() 37 | .filter_map(move |(cmd_name, handler)| { 38 | if (handler.has_permissions)(world, caller) { 39 | Some(cmd_name) 40 | } else { 41 | None 42 | } 43 | }) 44 | .map(String::as_str) 45 | } 46 | } 47 | 48 | #[derive(Component)] 49 | pub struct CommandComponentModule; 50 | 51 | impl Module for CommandComponentModule { 52 | fn module(world: &World) { 53 | world.component::(); 54 | world.set(CommandRegistry { 55 | commands: IndexMap::default(), 56 | }); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /crates/hyperion-command/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(iter_intersperse)] 2 | 3 | use flecs_ecs::{core::World, macros::Component, prelude::Module}; 4 | 5 | mod component; 6 | mod system; 7 | 8 | pub use component::{CommandHandler, CommandRegistry}; 9 | 10 | #[derive(Component)] 11 | pub struct CommandModule; 12 | 13 | impl Module for CommandModule { 14 | fn module(world: &World) { 15 | world.import::(); 16 | world.import::(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /crates/hyperion-crafting/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /crates/hyperion-crafting/Cargo.toml: -------------------------------------------------------------------------------- 1 | [dependencies] 2 | anyhow = { workspace = true } 3 | derive-build = { workspace = true } 4 | flecs_ecs = { workspace = true } 5 | slotmap = { workspace = true } 6 | valence_protocol = { workspace = true } 7 | 8 | [lints] 9 | workspace = true 10 | 11 | [package] 12 | authors = ["Andrew Gazelka "] 13 | edition.workspace = true 14 | name = "hyperion-crafting" 15 | publish = false 16 | readme = "README.md" 17 | version.workspace = true 18 | -------------------------------------------------------------------------------- /crates/hyperion-crafting/README.md: -------------------------------------------------------------------------------- 1 | # hyperion-crafting -------------------------------------------------------------------------------- /crates/hyperion-event-macros/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /crates/hyperion-event-macros/Cargo.toml: -------------------------------------------------------------------------------- 1 | [dependencies] 2 | convert_case.workspace = true 3 | proc-macro2.workspace = true 4 | quote.workspace = true 5 | syn.workspace = true 6 | 7 | [lib] 8 | proc-macro = true 9 | 10 | [lints] 11 | workspace = true 12 | 13 | [package] 14 | authors = ["Andrew Gazelka "] 15 | edition.workspace = true 16 | name = "hyperion-event-macros" 17 | publish = false 18 | readme = "README.md" 19 | version.workspace = true 20 | -------------------------------------------------------------------------------- /crates/hyperion-event-macros/README.md: -------------------------------------------------------------------------------- 1 | # hyperion-event-macros -------------------------------------------------------------------------------- /crates/hyperion-genmap/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /crates/hyperion-genmap/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hyperion-genmap" 3 | version = "0.1.0" 4 | edition = "2021" 5 | authors = ["Andrew Gazelka "] 6 | readme = "README.md" 7 | publish = false 8 | 9 | [dependencies] 10 | flecs_ecs = { workspace = true } 11 | hyperion = { workspace = true } 12 | hyperion-utils = { workspace = true } 13 | 14 | [lints] 15 | workspace = true 16 | -------------------------------------------------------------------------------- /crates/hyperion-genmap/README.md: -------------------------------------------------------------------------------- 1 | # hyperion-genmap -------------------------------------------------------------------------------- /crates/hyperion-genmap/src/lib.rs: -------------------------------------------------------------------------------- 1 | use flecs_ecs::{ 2 | core::{World, WorldGet}, 3 | macros::Component, 4 | prelude::Module, 5 | }; 6 | use hyperion::{runtime::AsyncRuntime, simulation::blocks::Blocks}; 7 | 8 | #[derive(Component)] 9 | pub struct GenMapModule; 10 | 11 | impl Module for GenMapModule { 12 | fn module(world: &World) { 13 | world.import::(); 14 | world.import::(); 15 | 16 | world.get::<&AsyncRuntime>(|runtime| { 17 | const URL: &str = "https://github.com/andrewgazelka/maps/raw/main/GenMap.tar.gz"; 18 | 19 | let f = hyperion_utils::cached_save(world, URL); 20 | 21 | let save = runtime.block_on(f).unwrap_or_else(|e| { 22 | panic!("failed to download map {URL}: {e}"); 23 | }); 24 | 25 | world.set(Blocks::new(world, &save).unwrap()); 26 | }); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /crates/hyperion-gui/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hyperion-gui" 3 | version.workspace = true 4 | edition.workspace = true 5 | 6 | [dependencies] 7 | anyhow = { workspace = true } 8 | flecs_ecs = { workspace = true } 9 | hyperion = { workspace = true } 10 | hyperion-inventory = { workspace = true } 11 | hyperion-utils = { workspace = true } 12 | serde = { version = "1.0", features = ["derive"] } 13 | valence_protocol = { workspace = true } 14 | tracing = { workspace = true } 15 | 16 | [lints] 17 | workspace = true 18 | -------------------------------------------------------------------------------- /crates/hyperion-inventory/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /crates/hyperion-inventory/Cargo.toml: -------------------------------------------------------------------------------- 1 | [dependencies] 2 | hyperion-crafting = {workspace = true} 3 | roaring = {workspace = true} 4 | snafu = {workspace = true} 5 | valence_protocol = {workspace = true} 6 | valence_generated = {workspace = true} 7 | flecs_ecs = {workspace = true} 8 | derive_more = {workspace = true} 9 | tracing = {workspace = true} 10 | 11 | [lints] 12 | workspace = true 13 | 14 | [package] 15 | authors = ["Andrew Gazelka "] 16 | edition.workspace = true 17 | name = "hyperion-inventory" 18 | publish = false 19 | readme = "README.md" 20 | version.workspace = true 21 | -------------------------------------------------------------------------------- /crates/hyperion-inventory/README.md: -------------------------------------------------------------------------------- 1 | # hyperion-inventory -------------------------------------------------------------------------------- /crates/hyperion-item/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /crates/hyperion-item/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hyperion-item" 3 | version.workspace = true 4 | edition.workspace = true 5 | authors = ["Andrew Gazelka "] 6 | readme = "README.md" 7 | publish = false 8 | 9 | [dependencies] 10 | flecs_ecs = { workspace = true } 11 | bytemuck = "1.23.0" 12 | valence_protocol = { workspace = true } 13 | hyperion = { workspace = true } 14 | hyperion-inventory = { workspace = true } 15 | hyperion-utils = { workspace = true } 16 | derive_more = { workspace = true } 17 | 18 | [lints] 19 | workspace = true 20 | -------------------------------------------------------------------------------- /crates/hyperion-item/README.md: -------------------------------------------------------------------------------- 1 | # hyperion-item -------------------------------------------------------------------------------- /crates/hyperion-item/src/builder/book.rs: -------------------------------------------------------------------------------- 1 | use hyperion::{ItemKind, ItemStack}; 2 | use valence_protocol::nbt; 3 | 4 | use crate::builder::ItemBuilder; 5 | 6 | #[derive(Clone, Debug)] 7 | #[must_use] 8 | pub struct BookBuilder { 9 | item: ItemBuilder, 10 | } 11 | 12 | // /give @p minecraft:written_book{author:"AuthorName",title:"BookTitle",pages:['{"text":"Page content"}']} 13 | 14 | impl BookBuilder { 15 | pub fn new(author: impl Into, title: impl Into) -> Self { 16 | let mut item = ItemBuilder::new(ItemKind::WrittenBook); 17 | 18 | let author = author.into(); 19 | let title = title.into(); 20 | 21 | let mut nbt = nbt::Compound::new(); 22 | 23 | nbt.insert("author", nbt::Value::String(author)); 24 | nbt.insert("resolved", nbt::Value::Byte(1)); 25 | nbt.insert("title", nbt::Value::String(title)); 26 | nbt.insert("pages", nbt::Value::List(nbt::List::String(Vec::new()))); 27 | 28 | item.nbt = Some(nbt); 29 | 30 | Self { item } 31 | } 32 | 33 | pub fn add_page(mut self, page: impl Into) -> Self { 34 | let page = page.into(); 35 | let json = format!(r#"{{"text":"{page}"}}"#); 36 | 37 | if let Some(nbt) = &mut self.item.nbt { 38 | if let nbt::Value::List(nbt::List::String(pages)) = nbt.get_mut("pages").unwrap() { 39 | pages.push(json); 40 | } 41 | } 42 | 43 | self 44 | } 45 | 46 | #[must_use] 47 | pub fn build(self) -> ItemStack { 48 | self.item.build() 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /crates/hyperion-item/src/lib.rs: -------------------------------------------------------------------------------- 1 | use derive_more::{Constructor, Deref, DerefMut}; 2 | use flecs_ecs::{ 3 | core::{EntityViewGet, World, WorldGet}, 4 | macros::Component, 5 | prelude::Module, 6 | }; 7 | use hyperion::{ 8 | simulation::{handlers::PacketSwitchQuery, packet::HandlerRegistry}, 9 | storage::{EventFn, InteractEvent}, 10 | }; 11 | use hyperion_utils::LifetimeHandle; 12 | use valence_protocol::nbt; 13 | 14 | pub mod builder; 15 | 16 | #[derive(Component)] 17 | pub struct ItemModule; 18 | 19 | #[derive(Component, Constructor, Deref, DerefMut)] 20 | pub struct Handler { 21 | on_click: EventFn, 22 | } 23 | 24 | impl Module for ItemModule { 25 | fn module(world: &World) { 26 | world.import::(); 27 | world.component::(); 28 | 29 | world.get::<&mut HandlerRegistry>(|registry| { 30 | registry.add_handler(Box::new( 31 | |event: &InteractEvent, 32 | _: &dyn LifetimeHandle<'_>, 33 | query: &mut PacketSwitchQuery<'_>| { 34 | let world = query.world; 35 | let inventory = &mut *query.inventory; 36 | 37 | let stack = &inventory.get_cursor().stack; 38 | 39 | if stack.is_empty() { 40 | return Ok(()); 41 | } 42 | 43 | let Some(nbt) = stack.nbt.as_ref() else { 44 | return Ok(()); 45 | }; 46 | 47 | let Some(handler) = nbt.get("Handler") else { 48 | return Ok(()); 49 | }; 50 | 51 | let nbt::Value::Long(id) = handler else { 52 | return Ok(()); 53 | }; 54 | 55 | let id: u64 = bytemuck::cast(*id); 56 | 57 | let handler = world.entity_from_id(id); 58 | 59 | handler.try_get::<&Handler>(|handler| { 60 | let on_interact = &handler.on_click; 61 | on_interact(query, event); 62 | }); 63 | 64 | Ok(()) 65 | }, 66 | )); 67 | }); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /crates/hyperion-minecraft-proto/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /crates/hyperion-minecraft-proto/Cargo.toml: -------------------------------------------------------------------------------- 1 | [dependencies] 2 | 3 | [lints] 4 | workspace = true 5 | 6 | [package] 7 | authors = ["Andrew Gazelka "] 8 | edition.workspace = true 9 | name = "hyperion-minecraft-proto" 10 | publish = false 11 | readme = "README.md" 12 | version.workspace = true 13 | -------------------------------------------------------------------------------- /crates/hyperion-minecraft-proto/README.md: -------------------------------------------------------------------------------- 1 | # hyperion-minecraft-proto -------------------------------------------------------------------------------- /crates/hyperion-minecraft-proto/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::io::{Cursor, Write}; 2 | 3 | pub enum EncodeError { 4 | Encode(E), 5 | Io(std::io::Error), 6 | } 7 | 8 | pub enum DecodeError { 9 | Decode(E), 10 | Io(std::io::Error), 11 | } 12 | 13 | impl From for EncodeError { 14 | fn from(e: std::io::Error) -> Self { 15 | Self::Io(e) 16 | } 17 | } 18 | 19 | pub trait Encode { 20 | type Error; 21 | 22 | fn encode(&self, w: impl Write) -> Result<(), EncodeError>; 23 | } 24 | 25 | pub trait Decode<'a> { 26 | type Error; 27 | 28 | fn decode(r: Cursor<&'a [u8]>) -> Result> 29 | where 30 | Self: Sized; 31 | } 32 | -------------------------------------------------------------------------------- /crates/hyperion-nerd-font/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /crates/hyperion-nerd-font/Cargo.toml: -------------------------------------------------------------------------------- 1 | [dependencies] 2 | 3 | [lints] 4 | workspace = true 5 | 6 | [package] 7 | authors = ["Andrew Gazelka "] 8 | edition.workspace = true 9 | name = "hyperion-nerd-font" 10 | publish = false 11 | readme = "README.md" 12 | version.workspace = true 13 | -------------------------------------------------------------------------------- /crates/hyperion-nerd-font/README.md: -------------------------------------------------------------------------------- 1 | # hyperion-nerd-font -------------------------------------------------------------------------------- /crates/hyperion-nerd-font/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub const NERD_ROCKET: char = '\u{F14DE}'; 2 | pub const FAIL_ROCKET: char = '\u{ea87}'; 3 | 4 | #[cfg(test)] 5 | mod tests { 6 | use super::*; 7 | 8 | #[test] 9 | #[expect(clippy::print_stdout)] 10 | fn print_chars() { 11 | println!("Rocket: {NERD_ROCKET}"); 12 | println!("Fail: {FAIL_ROCKET}"); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /crates/hyperion-packet-macros/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /crates/hyperion-packet-macros/Cargo.toml: -------------------------------------------------------------------------------- 1 | [dependencies] 2 | proc-macro2.workspace = true 3 | syn.workspace = true 4 | 5 | [lib] 6 | proc-macro = true 7 | 8 | [lints] 9 | workspace = true 10 | 11 | [package] 12 | authors = ["Andrew Gazelka "] 13 | edition.workspace = true 14 | name = "hyperion-packet-macros" 15 | publish = false 16 | readme = "README.md" 17 | version.workspace = true 18 | -------------------------------------------------------------------------------- /crates/hyperion-packet-macros/README.md: -------------------------------------------------------------------------------- 1 | # hyperion-packet-macros 2 | -------------------------------------------------------------------------------- /crates/hyperion-palette/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /crates/hyperion-palette/Cargo.toml: -------------------------------------------------------------------------------- 1 | [dependencies] 2 | anyhow.workspace = true 3 | roaring.workspace = true 4 | valence_protocol.workspace = true 5 | 6 | [lints] 7 | workspace = true 8 | 9 | [package] 10 | authors = ["Andrew Gazelka "] 11 | edition.workspace = true 12 | name = "hyperion-palette" 13 | publish = false 14 | readme = "README.md" 15 | version.workspace = true 16 | -------------------------------------------------------------------------------- /crates/hyperion-palette/README.md: -------------------------------------------------------------------------------- 1 | # hyperion-palette -------------------------------------------------------------------------------- /crates/hyperion-permission/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /crates/hyperion-permission/Cargo.toml: -------------------------------------------------------------------------------- 1 | [dependencies] 2 | anyhow = {workspace = true} 3 | clap = {workspace = true} 4 | flecs_ecs = {workspace = true} 5 | heed = {workspace = true} 6 | hyperion = {workspace = true} 7 | num-derive = {workspace = true} 8 | num-traits = {workspace = true} 9 | tracing = {workspace = true} 10 | uuid = {workspace = true} 11 | 12 | 13 | [lints] 14 | workspace = true 15 | 16 | [package] 17 | authors = ["Andrew Gazelka "] 18 | edition.workspace = true 19 | name = "hyperion-permission" 20 | publish = false 21 | readme = "README.md" 22 | version.workspace = true 23 | -------------------------------------------------------------------------------- /crates/hyperion-permission/README.md: -------------------------------------------------------------------------------- 1 | # hyperion-permission -------------------------------------------------------------------------------- /crates/hyperion-permission/src/lib.rs: -------------------------------------------------------------------------------- 1 | use clap::ValueEnum; 2 | use flecs_ecs::{ 3 | core::{EntityViewGet, QueryBuilderImpl, SystemAPI, TermBuilderImpl, World, WorldGet, id}, 4 | macros::{Component, observer}, 5 | prelude::{Module, flecs}, 6 | }; 7 | use hyperion::{ 8 | net::{Compose, ConnectionId}, 9 | simulation::{Player, Uuid, command::get_command_packet}, 10 | storage::LocalDb, 11 | }; 12 | use num_derive::{FromPrimitive, ToPrimitive}; 13 | 14 | #[derive(Component)] 15 | pub struct PermissionModule; 16 | 17 | mod storage; 18 | 19 | #[derive( 20 | Default, 21 | Component, 22 | FromPrimitive, 23 | ToPrimitive, 24 | Copy, 25 | Clone, 26 | Debug, 27 | PartialEq, 28 | ValueEnum, 29 | Eq 30 | )] 31 | #[repr(C)] 32 | pub enum Group { 33 | Banned, 34 | #[default] 35 | Normal, 36 | Moderator, 37 | Admin, 38 | } 39 | 40 | // todo: 41 | 42 | impl Module for PermissionModule { 43 | fn module(world: &World) { 44 | world.component::(); 45 | world.component::(); 46 | 47 | world.get::<&LocalDb>(|db| { 48 | let storage = storage::PermissionStorage::new(db).unwrap(); 49 | world.set(storage); 50 | }); 51 | 52 | observer!(world, flecs::OnSet, &Uuid, &storage::PermissionStorage($)) 53 | .with(id::()) 54 | .each_entity(|entity, (uuid, permissions)| { 55 | let group = permissions.get(**uuid); 56 | entity.set(group); 57 | }); 58 | 59 | observer!(world, flecs::OnRemove, &Uuid, &Group, &storage::PermissionStorage($)) 60 | .with(id::()) 61 | .each(|(uuid, group, permissions)| { 62 | permissions.set(**uuid, *group).unwrap(); 63 | }); 64 | 65 | observer!(world, flecs::OnSet, &Group).each_iter(|it, row, _group| { 66 | let system = it.system(); 67 | let world = it.world(); 68 | let entity = it.entity(row).expect("row must be in bounds"); 69 | 70 | let root_command = hyperion::simulation::command::get_root_command_entity(); 71 | 72 | let cmd_pkt = get_command_packet(&world, root_command, Some(*entity)); 73 | 74 | entity.get::<&ConnectionId>(|stream| { 75 | world.get::<&Compose>(|compose| { 76 | compose.unicast(&cmd_pkt, *stream, system).unwrap(); 77 | }); 78 | }); 79 | }); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /crates/hyperion-permission/src/storage.rs: -------------------------------------------------------------------------------- 1 | use flecs_ecs::macros::Component; 2 | use heed::{Database, Env, byteorder::NativeEndian, types}; 3 | use hyperion::storage::LocalDb; 4 | use num_traits::{FromPrimitive, ToPrimitive}; 5 | 6 | use crate::Group; 7 | 8 | #[derive(Component)] 9 | pub struct PermissionStorage { 10 | env: Env, 11 | perms: Database, types::U8>, 12 | } 13 | 14 | impl PermissionStorage { 15 | pub fn new(db: &LocalDb) -> anyhow::Result { 16 | // We open the default unnamed database 17 | let perms = { 18 | let mut wtxn = db.write_txn()?; 19 | let db = db.create_database(&mut wtxn, Some("uuid-to-perms"))?; 20 | wtxn.commit()?; 21 | db 22 | }; 23 | 24 | Ok(Self { 25 | env: (**db).clone(), 26 | perms, 27 | }) 28 | } 29 | 30 | pub fn get(&self, uuid: uuid::Uuid) -> Group { 31 | let uuid = uuid.as_u128(); 32 | let rtxn = self.env.read_txn().unwrap(); 33 | let Some(perms) = self.perms.get(&rtxn, &uuid).unwrap() else { 34 | return Group::default(); 35 | }; 36 | 37 | let Some(group) = Group::from_u8(perms) else { 38 | tracing::error!("invalid group {perms:?}"); 39 | return Group::default(); 40 | }; 41 | 42 | group 43 | } 44 | 45 | pub fn set(&self, uuid: uuid::Uuid, group: Group) -> anyhow::Result<()> { 46 | let uuid = uuid.as_u128(); 47 | let mut wtxn = self.env.write_txn()?; 48 | self.perms.put(&mut wtxn, &uuid, &group.to_u8().unwrap())?; 49 | wtxn.commit()?; 50 | Ok(()) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /crates/hyperion-proto/Cargo.toml: -------------------------------------------------------------------------------- 1 | [build-dependencies] 2 | 3 | [dependencies] 4 | rkyv = {workspace = true} 5 | glam = {workspace = true} 6 | 7 | [lints] 8 | workspace = true 9 | 10 | [package] 11 | authors = ["Andrew Gazelka "] 12 | edition.workspace = true 13 | name = "hyperion-proto" 14 | publish = false 15 | readme = "README.md" 16 | version.workspace = true 17 | 18 | [package.metadata.cargo-machete] 19 | ignored = ["prost"] 20 | -------------------------------------------------------------------------------- /crates/hyperion-proto/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperion-mc/hyperion/ddb109092b40bc381016e327c5b13eb0201e95a3/crates/hyperion-proto/README.md -------------------------------------------------------------------------------- /crates/hyperion-proto/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow( 2 | clippy::module_inception, 3 | clippy::module_name_repetitions, 4 | clippy::derive_partial_eq_without_eq, 5 | hidden_glob_reexports 6 | )] 7 | 8 | mod proxy_to_server; 9 | mod server_to_proxy; 10 | mod shared; 11 | 12 | pub use proxy_to_server::*; 13 | pub use server_to_proxy::*; 14 | pub use shared::*; 15 | -------------------------------------------------------------------------------- /crates/hyperion-proto/src/proxy_to_server.rs: -------------------------------------------------------------------------------- 1 | use rkyv::{Archive, Deserialize, Serialize, with::InlineAsBox}; 2 | 3 | #[derive(Archive, Deserialize, Serialize, Clone, PartialEq, Debug)] 4 | pub struct PlayerPackets<'a> { 5 | pub stream: u64, 6 | 7 | #[rkyv(with = InlineAsBox)] 8 | pub data: &'a [u8], 9 | } 10 | 11 | #[derive(Archive, Deserialize, Serialize, Clone, Copy, PartialEq, Debug)] 12 | pub struct PlayerConnect { 13 | pub stream: u64, 14 | } 15 | 16 | #[derive(Archive, Deserialize, Serialize, Clone, Copy, PartialEq, Debug)] 17 | pub struct PlayerDisconnect<'a> { 18 | pub stream: u64, 19 | pub reason: PlayerDisconnectReason<'a>, 20 | } 21 | 22 | #[derive(Archive, Deserialize, Serialize, Clone, Copy, PartialEq, Debug)] 23 | #[non_exhaustive] 24 | pub enum PlayerDisconnectReason<'a> { 25 | /// If cannot receive packets fast enough 26 | CouldNotKeepUp, 27 | LostConnection, 28 | 29 | Other(#[rkyv(with = InlineAsBox)] &'a str), 30 | } 31 | 32 | #[derive(Archive, Deserialize, Serialize, Clone, PartialEq, Debug)] 33 | pub enum ProxyToServerMessage<'a> { 34 | PlayerConnect(PlayerConnect), 35 | PlayerDisconnect(PlayerDisconnect<'a>), 36 | PlayerPackets(PlayerPackets<'a>), 37 | } 38 | -------------------------------------------------------------------------------- /crates/hyperion-proto/src/server_to_proxy.rs: -------------------------------------------------------------------------------- 1 | use rkyv::{Archive, Deserialize, Serialize, with::InlineAsBox}; 2 | 3 | use crate::ChunkPosition; 4 | 5 | #[derive(Archive, Deserialize, Serialize, Clone, PartialEq)] 6 | #[rkyv(derive(Debug))] 7 | pub struct UpdatePlayerChunkPositions { 8 | pub stream: Vec, 9 | pub positions: Vec, 10 | } 11 | 12 | #[derive(Archive, Deserialize, Serialize, Clone, Copy, PartialEq)] 13 | #[rkyv(derive(Debug))] 14 | pub struct SetReceiveBroadcasts { 15 | pub stream: u64, 16 | } 17 | 18 | #[derive(Archive, Deserialize, Serialize, Clone, PartialEq)] 19 | pub struct BroadcastGlobal<'a> { 20 | pub exclude: u64, 21 | pub order: u32, 22 | 23 | #[rkyv(with = InlineAsBox)] 24 | pub data: &'a [u8], 25 | } 26 | 27 | #[derive(Archive, Deserialize, Serialize, Clone, PartialEq)] 28 | pub struct BroadcastLocal<'a> { 29 | pub center: ChunkPosition, 30 | pub exclude: u64, 31 | pub order: u32, 32 | 33 | #[rkyv(with = InlineAsBox)] 34 | pub data: &'a [u8], 35 | } 36 | 37 | #[derive(Archive, Deserialize, Serialize, Clone, PartialEq)] 38 | pub struct Unicast<'a> { 39 | pub stream: u64, 40 | pub order: u32, 41 | 42 | #[rkyv(with = InlineAsBox)] 43 | pub data: &'a [u8], 44 | } 45 | 46 | #[derive(Archive, Deserialize, Serialize, Clone, Copy, PartialEq)] 47 | #[rkyv(derive(Debug))] 48 | pub struct Flush; 49 | 50 | /// The server must be prepared to handle other additional packets with this stream from the proxy after the server 51 | /// sends [`Shutdown`] until the server receives [`crate::PlayerDisconnect`] because proxy to server packets may 52 | /// already be in transit. 53 | #[derive(Archive, Deserialize, Serialize, Clone, Copy, PartialEq, Debug)] 54 | pub struct Shutdown { 55 | pub stream: u64, 56 | } 57 | 58 | #[derive(Archive, Deserialize, Serialize, Clone, PartialEq)] 59 | pub enum ServerToProxyMessage<'a> { 60 | UpdatePlayerChunkPositions(UpdatePlayerChunkPositions), 61 | BroadcastGlobal(BroadcastGlobal<'a>), 62 | BroadcastLocal(BroadcastLocal<'a>), 63 | Unicast(Unicast<'a>), 64 | SetReceiveBroadcasts(SetReceiveBroadcasts), 65 | Flush(Flush), 66 | Shutdown(Shutdown), 67 | } 68 | -------------------------------------------------------------------------------- /crates/hyperion-proto/src/shared.rs: -------------------------------------------------------------------------------- 1 | use glam::I16Vec2; 2 | use rkyv::{Archive, Deserialize, Serialize}; 3 | 4 | #[derive(Archive, Deserialize, Serialize, Clone, Copy, PartialEq, Debug)] 5 | #[rkyv(derive(Debug))] 6 | pub struct ChunkPosition { 7 | pub x: i16, 8 | pub z: i16, 9 | } 10 | 11 | impl ChunkPosition { 12 | #[must_use] 13 | pub const fn new(x: i16, z: i16) -> Self { 14 | Self { x, z } 15 | } 16 | } 17 | 18 | impl From for ChunkPosition { 19 | fn from(value: I16Vec2) -> Self { 20 | Self { 21 | x: value.x, 22 | z: value.y, 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /crates/hyperion-proxy-module/Cargo.toml: -------------------------------------------------------------------------------- 1 | [dependencies] 2 | hyperion-proxy = { workspace = true } 3 | hyperion = { workspace = true } 4 | flecs_ecs = { workspace = true } 5 | tracing = { workspace = true } 6 | tokio = { workspace = true , features = ["full"]} 7 | 8 | [lints] 9 | workspace = true 10 | 11 | [package] 12 | authors = ["Andrew Gazelka "] 13 | edition.workspace = true 14 | name = "hyperion-proxy-module" 15 | publish = false 16 | readme = "README.md" 17 | version.workspace = true 18 | -------------------------------------------------------------------------------- /crates/hyperion-proxy-module/README.md: -------------------------------------------------------------------------------- 1 | todo: write readme 2 | -------------------------------------------------------------------------------- /crates/hyperion-proxy-module/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::net::SocketAddr; 2 | 3 | use flecs_ecs::{core::World, prelude::*}; 4 | use hyperion::runtime::AsyncRuntime; 5 | use tokio::net::TcpListener; 6 | 7 | #[derive(Component)] 8 | pub struct HyperionProxyModule; 9 | 10 | #[derive(Component)] 11 | pub struct ProxyAddress { 12 | pub proxy: String, 13 | pub server: String, 14 | } 15 | 16 | impl Default for ProxyAddress { 17 | fn default() -> Self { 18 | Self { 19 | proxy: "0.0.0.0:25565".to_string(), 20 | server: "127.0.0.1:35565".to_string(), 21 | } 22 | } 23 | } 24 | 25 | impl Module for HyperionProxyModule { 26 | fn module(world: &World) { 27 | world.import::(); 28 | world.component::(); 29 | 30 | proxy_address_observer(world); 31 | } 32 | } 33 | 34 | fn proxy_address_observer(world: &World) { 35 | let mut query = world.observer_named::("proxy_address"); 39 | 40 | #[rustfmt::skip] 41 | query 42 | .term_at(0).singleton() 43 | .term_at(1).filter().singleton(); 44 | 45 | query.each(|(addresses, runtime)| { 46 | let proxy = addresses.proxy.clone(); 47 | let server = addresses.server.clone(); 48 | 49 | runtime.spawn(async move { 50 | let listener = TcpListener::bind(&proxy).await.unwrap(); 51 | tracing::info!("Listening on {proxy}"); 52 | 53 | let server: SocketAddr = tokio::net::lookup_host(&server) 54 | .await 55 | .unwrap() 56 | .next() 57 | .unwrap(); 58 | 59 | hyperion_proxy::run_proxy(listener, server).await.unwrap(); 60 | }); 61 | }); 62 | } 63 | -------------------------------------------------------------------------------- /crates/hyperion-proxy/Cargo.toml: -------------------------------------------------------------------------------- 1 | [dependencies] 2 | colored = { workspace = true } 3 | kanal = { workspace = true } 4 | papaya = { workspace = true } 5 | rkyv = { workspace = true } 6 | rustc-hash = { workspace = true } 7 | tokio = { workspace = true, features = ["full", "tracing"] } 8 | tokio-util = { workspace = true, features = ["full"] } 9 | anyhow = { workspace = true } 10 | bvh = { workspace = true } 11 | bytes = { workspace = true } 12 | clap = { workspace = true } 13 | glam = { workspace = true } 14 | heapless = { workspace = true } 15 | hyperion-proto = { workspace = true } 16 | more-asserts = { workspace = true } 17 | slotmap = { workspace = true } 18 | tracing = { workspace = true } 19 | tracing-subscriber = { workspace = true } 20 | serde = { version = "1.0", features = ["derive"] } 21 | envy = "0.4" 22 | dotenvy = "0.15" 23 | 24 | [lints] 25 | workspace = true 26 | 27 | [package] 28 | authors = ["Andrew Gazelka "] 29 | edition.workspace = true 30 | name = "hyperion-proxy" 31 | publish = false 32 | readme = "README.md" 33 | version.workspace = true 34 | 35 | [target.'cfg(not(target_os = "windows"))'.dependencies] 36 | tikv-jemallocator.workspace = true 37 | -------------------------------------------------------------------------------- /crates/hyperion-proxy/README.md: -------------------------------------------------------------------------------- 1 | todo: write readme 2 | -------------------------------------------------------------------------------- /crates/hyperion-proxy/src/server_sender.rs: -------------------------------------------------------------------------------- 1 | use std::io::IoSlice; 2 | 3 | use rkyv::util::AlignedVec; 4 | use tracing::{Instrument, trace_span, warn}; 5 | 6 | use crate::util::AsyncWriteVectoredExt; 7 | 8 | pub type ServerSender = kanal::AsyncSender; 9 | 10 | // todo: probably makes sense for caller to encode bytes 11 | #[must_use] 12 | pub fn launch_server_writer(mut write: tokio::net::tcp::OwnedWriteHalf) -> ServerSender { 13 | let (tx, rx) = kanal::bounded_async::(32_768); 14 | 15 | tokio::spawn( 16 | async move { 17 | let mut lengths: Vec<[u8; 8]> = Vec::new(); 18 | let mut messages = Vec::new(); 19 | 20 | // todo: remove allocation is there an easy way to do this? 21 | let mut io_slices = Vec::new(); 22 | 23 | while let Ok(message) = rx.recv().await { 24 | let len = message.len() as u64; 25 | 26 | lengths.push(len.to_be_bytes()); 27 | messages.push(message); 28 | 29 | while let Ok(Some(message)) = rx.try_recv() { 30 | let len = message.len() as u64; 31 | lengths.push(len.to_be_bytes()); 32 | messages.push(message); 33 | } 34 | 35 | for (message, length) in messages.iter().zip(lengths.iter()) { 36 | let len = IoSlice::new(length); 37 | let msg = IoSlice::new(message); 38 | 39 | // todo: is there a way around this? 40 | let len = unsafe { core::mem::transmute::, IoSlice<'static>>(len) }; 41 | let msg = unsafe { core::mem::transmute::, IoSlice<'static>>(msg) }; 42 | 43 | io_slices.push(len); 44 | io_slices.push(msg); 45 | } 46 | 47 | if let Err(e) = write.write_vectored_all(&mut io_slices).await { 48 | warn!("failed to write to server: {e}"); 49 | return; 50 | } 51 | 52 | lengths.clear(); 53 | messages.clear(); 54 | io_slices.clear(); 55 | } 56 | } 57 | .instrument(trace_span!("server_writer_loop")), 58 | ); 59 | 60 | tx 61 | } 62 | -------------------------------------------------------------------------------- /crates/hyperion-proxy/src/util.rs: -------------------------------------------------------------------------------- 1 | use std::io::IoSlice; 2 | 3 | use tokio::io::{AsyncWrite, AsyncWriteExt}; 4 | 5 | /// Extension trait for [`AsyncWrite`] to write all data from given IO vectors. 6 | pub trait AsyncWriteVectoredExt: AsyncWrite + Unpin { 7 | /// Writes all data from the given IO vectors to the writer. 8 | fn write_vectored_all( 9 | &mut self, 10 | mut io_vectors: &mut [IoSlice<'_>], 11 | ) -> impl std::future::Future> { 12 | async move { 13 | while !io_vectors.is_empty() { 14 | let bytes_written = self.write_vectored(io_vectors).await?; 15 | if bytes_written == 0 { 16 | return Err(std::io::Error::new( 17 | std::io::ErrorKind::WriteZero, 18 | "failed to write the entire buffer to the writer", 19 | )); 20 | } 21 | IoSlice::advance_slices(&mut io_vectors, bytes_written); 22 | } 23 | Ok(()) 24 | } 25 | } 26 | } 27 | 28 | impl AsyncWriteVectoredExt for T {} 29 | -------------------------------------------------------------------------------- /crates/hyperion-rank-tree/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /crates/hyperion-rank-tree/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hyperion-rank-tree" 3 | version.workspace = true 4 | edition.workspace = true 5 | authors = ["Andrew Gazelka "] 6 | readme = "README.md" 7 | publish = false 8 | 9 | [dependencies] 10 | clap = { workspace = true } 11 | flecs_ecs = { workspace = true } 12 | hyperion = { workspace = true } 13 | hyperion-inventory = { workspace = true } 14 | hyperion-item.workspace = true 15 | tracing = { workspace = true } 16 | valence_protocol = { workspace = true } 17 | 18 | [lints] 19 | workspace = true 20 | -------------------------------------------------------------------------------- /crates/hyperion-rank-tree/README.md: -------------------------------------------------------------------------------- 1 | # hyperion-rank-tree -------------------------------------------------------------------------------- /crates/hyperion-rank-tree/src/lib.rs: -------------------------------------------------------------------------------- 1 | use clap::ValueEnum; 2 | use flecs_ecs::{ 3 | core::{Entity, IdOperations, World, flecs}, 4 | macros::Component, 5 | prelude::Module, 6 | }; 7 | use hyperion::{ 8 | simulation::{Player, handlers::PacketSwitchQuery}, 9 | storage::{EventFn, InteractEvent}, 10 | }; 11 | 12 | pub mod inventory; 13 | 14 | #[derive(Copy, Clone, Debug, ValueEnum, PartialEq, Eq, Component, Default)] 15 | #[repr(C)] 16 | pub enum Class { 17 | /// ![Widget Example](https://i.imgur.com/pW7v0Xn.png) 18 | /// 19 | /// The stick is the starting rank. 20 | #[default] 21 | Stick, // -> [Pickaxe | Sword | Bow ] 22 | 23 | Archer, 24 | Sword, 25 | Miner, 26 | 27 | Excavator, 28 | 29 | Mage, 30 | Knight, 31 | Builder, 32 | } 33 | 34 | #[derive( 35 | Copy, Clone, Debug, PartialEq, Eq, ValueEnum, PartialOrd, Ord, Component, Default 36 | )] 37 | pub enum Team { 38 | #[default] 39 | Blue, 40 | Green, 41 | Red, 42 | Yellow, 43 | } 44 | 45 | #[derive(Component)] 46 | pub struct RankTree; 47 | 48 | #[derive(Component)] 49 | pub struct Handles { 50 | pub speed: Entity, 51 | } 52 | 53 | impl Module for RankTree { 54 | fn module(world: &World) { 55 | world.import::(); 56 | world.component::(); 57 | world.component::(); 58 | world.component::(); 59 | 60 | world 61 | .component::() 62 | .add_trait::<(flecs::With, Team)>(); 63 | 64 | world 65 | .component::() 66 | .add_trait::<(flecs::With, Class)>(); 67 | 68 | let handler: EventFn = Box::new(|query: &mut PacketSwitchQuery<'_>, _| { 69 | let cursor = query.inventory.get_cursor(); 70 | tracing::debug!("clicked {cursor:?}"); 71 | }); 72 | 73 | let speed = world.entity().set(hyperion_item::Handler::new(handler)); 74 | 75 | world.set(Handles { speed: speed.id() }); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /crates/hyperion-respawn/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /crates/hyperion-respawn/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hyperion-respawn" 3 | version = "0.1.0" 4 | edition = "2021" 5 | authors = ["Andrew Gazelka "] 6 | readme = "README.md" 7 | publish = false 8 | 9 | [dependencies] 10 | hyperion = {workspace = true} 11 | hyperion-utils = {workspace = true} 12 | 13 | [lints] 14 | workspace = true 15 | -------------------------------------------------------------------------------- /crates/hyperion-respawn/README.md: -------------------------------------------------------------------------------- 1 | # respawn -------------------------------------------------------------------------------- /crates/hyperion-scheduled/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /crates/hyperion-scheduled/Cargo.toml: -------------------------------------------------------------------------------- 1 | [dependencies] 2 | 3 | [lints] 4 | workspace = true 5 | 6 | [package] 7 | authors = ["Andrew Gazelka "] 8 | edition.workspace = true 9 | name = "hyperion-scheduled" 10 | publish = false 11 | readme = "README.md" 12 | version.workspace = true 13 | -------------------------------------------------------------------------------- /crates/hyperion-scheduled/README.md: -------------------------------------------------------------------------------- 1 | # hyperion-temp-block -------------------------------------------------------------------------------- /crates/hyperion-stats/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /crates/hyperion-stats/Cargo.toml: -------------------------------------------------------------------------------- 1 | [[bench]] 2 | harness = false 3 | name = "parallel_stats" 4 | 5 | [dependencies] 6 | 7 | [dev-dependencies] 8 | rand.workspace = true 9 | approx.workspace = true 10 | divan.workspace = true 11 | 12 | [lints] 13 | workspace = true 14 | 15 | [package] 16 | authors = ["Andrew Gazelka "] 17 | edition.workspace = true 18 | name = "hyperion-stats" 19 | publish = false 20 | readme = "README.md" 21 | version.workspace = true 22 | -------------------------------------------------------------------------------- /crates/hyperion-stats/README.md: -------------------------------------------------------------------------------- 1 | # hyperion-stats 2 | 3 | A high-performance parallel statistics library optimized for real-time anti-cheat systems, particularly in Minecraft servers. Uses SIMD operations to efficiently track multiple statistical metrics simultaneously. 4 | 5 | ## Overview 6 | 7 | This library provides efficient parallel computation of running statistics (mean, variance, min/max) across multiple data streams simultaneously. This is particularly useful for anti-cheat systems that need to track many player metrics in real-time. 8 | 9 | ## Anti-Cheat Applications 10 | 11 | Below are common anti-cheat metrics that can be tracked using parallel statistics: 12 | 13 | | Metric | Description | Statistical Considerations | 14 | |--------|-------------|---------------------------| 15 | | Packets per Second | Track packet frequency per player | - Use sliding window
- Account for network jitter
- Track variance for burst detection | 16 | | Position Delta | Distance between consecutive positions | - Consider tick rate
- Account for teleports
- Track max speed violations | 17 | | Vertical Velocity | Changes in Y-coordinate | - Account for jump mechanics
- Consider block collisions
- Track unusual patterns | 18 | | Click Patterns | Time between clicks | - Track click distribution
- Detect auto-clickers
- Consider CPS limits | 19 | | Rotation Deltas | Changes in pitch/yaw | - Track smooth vs. snap movements
- Detect aimbot patterns
- Consider sensitivity | 20 | | Block Interaction | Time between block breaks/places | - Account for tool efficiency
- Track unusual patterns
- Consider game mechanics | 21 | | Combat Patterns | Hit timing and accuracy | - Track reach distances
- Consider ping/latency
- Detect impossible hits | 22 | | Movement Timing | Time between movement packets | - Account for client tick rate
- Detect timer modifications
- Consider server load | 23 | 24 | ## Future Work & Considerations 25 | 26 | 27 | ### Additional Statistics 28 | - Skewness and kurtosis for better pattern detection 29 | - Exponential moving averages for trend detection 30 | - Correlation between different metrics 31 | - Fourier analysis for periodic pattern detection 32 | - Entropy calculations for randomness assessment 33 | 34 | ### Performance Optimizations 35 | - GPU acceleration for large player counts 36 | - Adaptive sampling rates based on load 37 | - Efficient memory management for long sessions 38 | - Better SIMD utilization 39 | 40 | ### Anti-Cheat Specific Features 41 | - Built-in violation level tracking 42 | - Confidence scoring for detections 43 | - False positive reduction algorithms 44 | - Integration with common game mechanics 45 | - Latency compensation 46 | 47 | ### Challenges to Consider 48 | - Network conditions affecting measurements 49 | - Server performance impact on timing 50 | - Client-side modifications affecting data 51 | - Game mechanic edge cases 52 | - Balance between detection and false positives -------------------------------------------------------------------------------- /crates/hyperion-stats/benches/parallel_stats.rs: -------------------------------------------------------------------------------- 1 | use divan::{Bencher, black_box}; 2 | use hyperion_stats::ParallelStats; 3 | use rand::Rng; 4 | 5 | fn main() { 6 | divan::main(); 7 | } 8 | 9 | fn generate_test_data(width: usize, updates: usize) -> Vec> { 10 | let mut rng = rand::rng(); 11 | (0..updates) 12 | .map(|_| (0..width).map(|_| rng.random::()).collect()) 13 | .collect() 14 | } 15 | 16 | #[divan::bench(args = [ 17 | 4, 8, 16, 32, 64 18 | ])] 19 | fn bench_parallel_stats(bencher: Bencher<'_, '_>, width: usize) { 20 | let updates = 1000; 21 | let test_data = generate_test_data(width, updates); 22 | 23 | bencher.bench(move || { 24 | let mut stats = ParallelStats::new(width); 25 | for values in &test_data { 26 | stats.update(black_box(values)); 27 | } 28 | stats 29 | }); 30 | } 31 | -------------------------------------------------------------------------------- /crates/hyperion-text/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /crates/hyperion-text/Cargo.toml: -------------------------------------------------------------------------------- 1 | [dependencies] 2 | serde.workspace = true 3 | serde_json.workspace = true 4 | thiserror.workspace = true 5 | uuid.workspace = true 6 | valence_protocol.workspace = true 7 | 8 | [lints] 9 | workspace = true 10 | 11 | [package] 12 | authors = ["Andrew Gazelka "] 13 | edition.workspace = true 14 | name = "hyperion-text" 15 | publish = false 16 | readme = "README.md" 17 | version.workspace = true 18 | -------------------------------------------------------------------------------- /crates/hyperion-text/README.md: -------------------------------------------------------------------------------- 1 | # hyperion-text -------------------------------------------------------------------------------- /crates/hyperion-text/src/event.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Cow; 2 | 3 | use serde::{Deserialize, Serialize}; 4 | use uuid::Uuid; 5 | 6 | use crate::Text; 7 | 8 | /// Action to take on click of the text. 9 | #[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)] 10 | #[serde(tag = "action", content = "value", rename_all = "snake_case")] 11 | pub enum ClickEvent<'a> { 12 | /// Opens an URL 13 | OpenUrl(Cow<'a, str>), 14 | /// Only usable by internal servers for security reasons. 15 | OpenFile(Cow<'a, str>), 16 | /// Sends a chat command. Doesn't actually have to be a command, can be a 17 | /// normal chat message. 18 | RunCommand(Cow<'a, str>), 19 | /// Replaces the contents of the chat box with the text, not necessarily a 20 | /// command. 21 | SuggestCommand(Cow<'a, str>), 22 | /// Only usable within written books. Changes the page of the book. Indexing 23 | /// starts at 1. 24 | ChangePage(i32), 25 | /// Copies the given text to clipboard 26 | CopyToClipboard(Cow<'a, str>), 27 | } 28 | 29 | /// Action to take when mouse-hovering on the text. 30 | #[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] 31 | #[serde(tag = "action", content = "contents", rename_all = "snake_case")] 32 | #[expect(clippy::enum_variant_names)] 33 | pub enum HoverEvent<'a> { 34 | /// Displays a tooltip with the given text. 35 | ShowText(Text<'a>), 36 | /// Shows an item. 37 | ShowItem { 38 | /// Resource identifier of the item (ident) 39 | id: Cow<'a, str>, 40 | /// Number of the items in the stack 41 | count: Option, 42 | /// NBT information about the item (sNBT format) 43 | tag: Cow<'a, str>, 44 | }, 45 | /// Shows an entity. 46 | ShowEntity { 47 | /// The entity's UUID 48 | id: Uuid, 49 | /// Resource identifier of the entity 50 | #[serde(rename = "type")] 51 | #[serde(default, skip_serializing_if = "Option::is_none")] 52 | kind: Option>, 53 | /// Optional custom name for the entity 54 | #[serde(default, skip_serializing_if = "Option::is_none")] 55 | name: Option>, 56 | }, 57 | } 58 | -------------------------------------------------------------------------------- /crates/hyperion-text/src/font.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | /// The font of the text. 4 | #[derive(Clone, Copy, PartialEq, Eq, Debug, Serialize, Deserialize)] 5 | pub enum Font { 6 | /// The default font. 7 | #[serde(rename = "minecraft:default")] 8 | Default, 9 | /// Unicode font. 10 | #[serde(rename = "minecraft:uniform")] 11 | Uniform, 12 | /// Enchanting table font. 13 | #[serde(rename = "minecraft:alt")] 14 | Alt, 15 | } 16 | -------------------------------------------------------------------------------- /crates/hyperion-text/src/helper.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Cow; 2 | 3 | use crate::{Text, TextContent}; 4 | 5 | impl<'a> Text<'a> { 6 | /// Creates a new `Text` instance from a string slice. 7 | #[must_use] 8 | pub const fn new(s: &'a str) -> Self { 9 | Text { 10 | content: TextContent::Text { 11 | text: Cow::Borrowed(s), 12 | }, 13 | color: None, 14 | font: None, 15 | bold: None, 16 | italic: None, 17 | underlined: None, 18 | strikethrough: None, 19 | obfuscated: None, 20 | insertion: None, 21 | click_event: None, 22 | hover_event: None, 23 | extra: Vec::new(), 24 | } 25 | } 26 | } 27 | 28 | // Implement From trait for &str to Text conversion 29 | impl<'a> From<&'a str> for Text<'a> { 30 | fn from(s: &'a str) -> Self { 31 | Text::new(s) 32 | } 33 | } 34 | 35 | #[cfg(test)] 36 | mod tests { 37 | use super::*; 38 | 39 | #[test] 40 | fn test_text_creation_and_conversion() { 41 | let text1 = Text::new("Hello, world!"); 42 | let text2: Text<'_> = "Hello, world!".into(); 43 | 44 | assert_eq!(text1, text2); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /crates/hyperion-text/src/scoreboard.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Cow; 2 | 3 | use serde::{Deserialize, Serialize}; 4 | 5 | /// Scoreboard value. 6 | #[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)] 7 | pub struct ScoreboardValueContent<'a> { 8 | /// The name of the score holder whose score should be displayed. This 9 | /// can be a [`selector`] or an explicit name. 10 | /// 11 | /// [`selector`]: https://minecraft.wiki/w/Target_selectors 12 | pub name: Cow<'a, str>, 13 | /// The internal name of the objective to display the player's score in. 14 | pub objective: Cow<'a, str>, 15 | /// If present, this value is displayed regardless of what the score 16 | /// would have been. 17 | #[serde(default, skip_serializing_if = "Option::is_none")] 18 | pub value: Option>, 19 | } 20 | -------------------------------------------------------------------------------- /crates/hyperion-utils/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /crates/hyperion-utils/Cargo.toml: -------------------------------------------------------------------------------- 1 | [dependencies] 2 | flecs_ecs = {workspace = true} 3 | anyhow = {workspace = true} 4 | directories = {workspace = true} 5 | sha2 = {workspace = true} 6 | hex = {workspace = true} 7 | hyperion-packet-macros = {workspace = true} 8 | tracing = {workspace = true} 9 | reqwest = {workspace = true} 10 | flate2 = {workspace = true} 11 | tar = {workspace = true} 12 | tokio-util = {workspace = true} 13 | tokio = {workspace = true} 14 | futures-util = {workspace = true} 15 | valence_protocol = { workspace = true } 16 | 17 | [lints] 18 | workspace = true 19 | 20 | [package] 21 | authors = ["Andrew Gazelka "] 22 | edition.workspace = true 23 | name = "hyperion-utils" 24 | publish = false 25 | readme = "README.md" 26 | version.workspace = true 27 | -------------------------------------------------------------------------------- /crates/hyperion-utils/README.md: -------------------------------------------------------------------------------- 1 | # hyperion-utils -------------------------------------------------------------------------------- /crates/hyperion-utils/src/cached_save.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | use anyhow::Context; 4 | use directories::ProjectDirs; 5 | use flecs_ecs::core::{World, WorldGet}; 6 | use futures_util::stream::StreamExt; 7 | use sha2::Digest; 8 | use tar::Archive; 9 | use tokio_util::io::{StreamReader, SyncIoBridge}; 10 | use tracing::info; 11 | 12 | use crate::AppId; 13 | 14 | pub fn cached_save( 15 | world: &World, 16 | url: U, 17 | ) -> impl Future> + 'static { 18 | let project_dirs = world 19 | .get::<&AppId>( 20 | |AppId { 21 | qualifier, 22 | organization, 23 | application, 24 | }| { ProjectDirs::from(qualifier, organization, application) }, 25 | ) 26 | .expect("failed to get AppId"); 27 | 28 | let cache = project_dirs.cache_dir(); 29 | 30 | let mut hasher = sha2::Sha256::new(); 31 | let url_str = url.as_str().to_string(); 32 | Digest::update(&mut hasher, url_str.as_bytes()); 33 | // Get the final hash result 34 | let url_sha = hasher.finalize(); 35 | let url_sha = hex::encode(url_sha); 36 | 37 | let directory = cache.join(url_sha); 38 | 39 | async move { 40 | if directory.exists() { 41 | info!("using cached NewYork load"); 42 | } else { 43 | // download 44 | let response = reqwest::get(url) 45 | .await 46 | .with_context(|| format!("failed to get {url_str}"))?; 47 | 48 | let byte_stream = response.bytes_stream(); 49 | // Convert the byte stream into an AsyncRead 50 | 51 | let reader = StreamReader::new(byte_stream.map(|result| { 52 | result.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e)) 53 | })); 54 | 55 | let directory = directory.clone(); 56 | let handle = tokio::task::spawn_blocking(move || { 57 | let reader = SyncIoBridge::new(reader); 58 | let reader = std::io::BufReader::new(reader); 59 | let reader = flate2::read::GzDecoder::new(reader); 60 | 61 | // Create the archive in the blocking context 62 | let mut archive = Archive::new(reader); 63 | 64 | archive 65 | .unpack(&directory) 66 | .context("failed to unpack archive")?; 67 | 68 | anyhow::Ok(()) 69 | }); 70 | 71 | handle.await??; 72 | } 73 | 74 | Ok(directory) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /crates/hyperion-utils/src/lib.rs: -------------------------------------------------------------------------------- 1 | use flecs_ecs::{ 2 | core::World, 3 | macros::Component, 4 | prelude::{Entity, Module}, 5 | }; 6 | 7 | mod cached_save; 8 | mod lifetime; 9 | pub use cached_save::cached_save; 10 | pub use lifetime::*; 11 | 12 | pub trait EntityExt { 13 | fn minecraft_id(&self) -> i32; 14 | 15 | fn from_minecraft_id(id: i32) -> Self; 16 | } 17 | 18 | impl EntityExt for Entity { 19 | fn minecraft_id(&self) -> i32 { 20 | let raw = self.0; 21 | // Convert entity id into two u32s 22 | let most_significant = (raw >> 32) as u32; 23 | 24 | #[expect( 25 | clippy::cast_possible_truncation, 26 | reason = "we are getting the least significant bits, we expect truncation" 27 | )] 28 | let least_significant = raw as u32; 29 | 30 | // Ensure most_significant >> 4 does not overlap with least_significant 31 | // and that least_significant AND most_significant is 0 32 | // this is the "thread" space which allows for 2^6 = 64 threads 33 | debug_assert_eq!( 34 | most_significant >> 6, 35 | 0, 36 | "Entity ID is too large for Minecraft" 37 | ); 38 | 39 | debug_assert!( 40 | least_significant < (1 << 26), 41 | "Entity ID is too large for Minecraft (must fit in 2^26)" 42 | ); 43 | 44 | // Combine them into a single i32 45 | let result = (most_significant << 26) | least_significant; 46 | 47 | #[expect( 48 | clippy::cast_possible_wrap, 49 | reason = "we do not care about sign changes, we expect wrap" 50 | )] 51 | let result = result as i32; 52 | 53 | result 54 | } 55 | 56 | fn from_minecraft_id(id: i32) -> Self { 57 | #[expect(clippy::cast_sign_loss, reason = "we do not care about sign changes.")] 58 | let id = id as u32; 59 | 60 | let least_significant = id & ((1 << 26) - 1); 61 | let most_significant = (id >> 26) & 0x3F; 62 | 63 | let raw = (u64::from(most_significant) << 32) | u64::from(least_significant); 64 | Self(raw) 65 | } 66 | } 67 | 68 | /// Represents application identification information used for caching and other system-level operations 69 | #[derive(Component)] 70 | pub struct AppId { 71 | /// The qualifier/category of the application (e.g. "com", "org", "hyperion") 72 | pub qualifier: String, 73 | /// The organization that created the application (e.g. "andrewgazelka") 74 | pub organization: String, 75 | /// The specific application name (e.g. "proof-of-concept") 76 | pub application: String, 77 | } 78 | 79 | #[derive(Component)] 80 | pub struct HyperionUtilsModule; 81 | 82 | impl Module for HyperionUtilsModule { 83 | fn module(world: &World) { 84 | world.component::(); 85 | 86 | world.set(AppId { 87 | qualifier: "github".to_string(), 88 | organization: "hyperion-mc".to_string(), 89 | application: "generic".to_string(), 90 | }); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /crates/hyperion/Cargo.toml: -------------------------------------------------------------------------------- 1 | [[bench]] 2 | harness = false 3 | name = "set" 4 | 5 | [[bench]] 6 | harness = false 7 | name = "atomic" 8 | 9 | [dependencies] 10 | anyhow = { workspace = true } 11 | base64 = { workspace = true } 12 | bitfield-struct = { workspace = true } 13 | bitvec = { workspace = true } 14 | bvh-region = { workspace = true } 15 | bumpalo = { workspace = true } 16 | bytemuck = { workspace = true } 17 | byteorder = { workspace = true } 18 | bytes = { workspace = true } 19 | colored = { workspace = true } 20 | derive_more = { workspace = true } 21 | enumset = { workspace = true } 22 | fastrand = { workspace = true } 23 | flate2 = { workspace = true, features = ["zlib-ng"] } 24 | flecs_ecs = { workspace = true } 25 | geometry = { workspace = true } 26 | glam = { workspace = true, features = ["serde"] } 27 | heapless = { workspace = true } 28 | heed = { workspace = true } 29 | humantime = { workspace = true } 30 | hyperion-crafting = { workspace = true } 31 | hyperion-event-macros = { workspace = true } 32 | hyperion-inventory = { workspace = true } 33 | hyperion-nerd-font = { workspace = true } 34 | hyperion-packet-macros = { workspace = true } 35 | hyperion-palette = { workspace = true } 36 | hyperion-proto = { workspace = true } 37 | hyperion-text = { workspace = true } 38 | hyperion-utils = { workspace = true } 39 | indexmap = { workspace = true } 40 | itertools = { workspace = true } 41 | kanal = { workspace = true } 42 | libc = { workspace = true } 43 | libdeflater = { workspace = true } 44 | memmap2 = { workspace = true } 45 | more-asserts = { workspace = true } 46 | ndarray = { workspace = true } 47 | once_cell = { workspace = true } 48 | ouroboros = { workspace = true } 49 | parking_lot = { workspace = true } 50 | rayon = { workspace = true } 51 | reqwest = { workspace = true } 52 | rkyv = { workspace = true } 53 | roaring = { workspace = true, features = ["simd"] } 54 | rustc-hash = { workspace = true } 55 | serde = { workspace = true, features = ["derive"] } 56 | serde_json = { workspace = true } 57 | sha2 = { workspace = true } 58 | simd-utils = { workspace = true } 59 | system-order = { workspace = true } 60 | thiserror = { workspace = true } 61 | tokio = { workspace = true, features = ["full", "tracing"] } 62 | toml = { workspace = true } 63 | tracing = { workspace = true } 64 | tracing-tracy = { workspace = true } 65 | uuid = { workspace = true } 66 | valence_anvil = { workspace = true } 67 | valence_generated = { workspace = true } 68 | valence_ident = { workspace = true } 69 | valence_nbt = { workspace = true } 70 | valence_protocol = { workspace = true } 71 | valence_registry = { workspace = true } 72 | valence_server = { workspace = true } 73 | valence_text = { workspace = true } 74 | ordered-float = { workspace = true } 75 | dashmap = "6.1.0" 76 | 77 | [dev-dependencies] 78 | approx = { workspace = true } 79 | divan = { workspace = true } 80 | fastrand = { workspace = true } 81 | hyperion-genmap = { workspace = true } 82 | tracing-appender = { workspace = true } 83 | tracing-subscriber = { workspace = true } 84 | 85 | [lints] 86 | workspace = true 87 | 88 | [package] 89 | authors = ["Andrew Gazelka "] 90 | edition.workspace = true 91 | name = "hyperion" 92 | publish = false 93 | readme = "README.md" 94 | version.workspace = true 95 | -------------------------------------------------------------------------------- /crates/hyperion/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperion-mc/hyperion/ddb109092b40bc381016e327c5b13eb0201e95a3/crates/hyperion/README.md -------------------------------------------------------------------------------- /crates/hyperion/benches/set.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | collections::{BTreeSet, HashSet, LinkedList, VecDeque}, 3 | hash::Hash, 4 | hint::black_box, 5 | }; 6 | 7 | use divan::Bencher; 8 | 9 | const LENS: &[usize] = &[1, 2, 4, 8, 16, 32, 64, 128, 256, 512]; 10 | 11 | fn main() { 12 | divan::main(); 13 | } 14 | #[divan::bench( 15 | types = [ 16 | BTreeSet, 17 | HashSet, 18 | Vec, 19 | VecDeque, 20 | ], 21 | args = LENS, 22 | )] 23 | fn from_iter(bencher: Bencher<'_, '_>, len: usize) 24 | where 25 | T: FromIterator + Contains, 26 | { 27 | let max_elem = len * 4; 28 | 29 | let elems: T = (0..len).map(|_| fastrand::usize(..max_elem)).collect(); 30 | bencher.counter(len).bench_local(|| { 31 | let n = fastrand::usize(..max_elem); 32 | black_box(elems.contains_impl(&n)); 33 | }); 34 | } 35 | 36 | trait Contains { 37 | fn contains_impl(&self, t: &T) -> bool; 38 | } 39 | 40 | impl Contains for Vec { 41 | fn contains_impl(&self, t: &T) -> bool { 42 | self.contains(t) 43 | } 44 | } 45 | 46 | impl Contains for VecDeque { 47 | fn contains_impl(&self, t: &T) -> bool { 48 | self.contains(t) 49 | } 50 | } 51 | 52 | impl Contains for BTreeSet { 53 | fn contains_impl(&self, t: &T) -> bool { 54 | self.contains(t) 55 | } 56 | } 57 | 58 | impl Contains for HashSet { 59 | fn contains_impl(&self, t: &T) -> bool { 60 | self.contains(t) 61 | } 62 | } 63 | 64 | impl Contains for LinkedList { 65 | fn contains_impl(&self, t: &T) -> bool { 66 | self.contains(t) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /crates/hyperion/run/config.toml: -------------------------------------------------------------------------------- 1 | border_diameter = 100.0 2 | max_players = 10000 3 | view_distance = 32 4 | simulation_distance = 10 5 | server_desc = "Hyperion Test Server" 6 | 7 | [spawn] 8 | kind = "Chebyshev" 9 | radius = 1000 10 | x = 0 11 | y = 64 12 | z = 0 13 | -------------------------------------------------------------------------------- /crates/hyperion/src/common/config.rs: -------------------------------------------------------------------------------- 1 | //! Configuration for the server. 2 | 3 | use std::{fmt::Debug, fs::File, io::Read, path::Path}; 4 | 5 | use flecs_ecs::macros::Component; 6 | use serde::{Deserialize, Serialize}; 7 | use tracing::{info, instrument, warn}; 8 | 9 | /// The configuration for the server representing a `toml` file. 10 | #[derive(Serialize, Deserialize, Debug, Component)] 11 | pub struct Config { 12 | pub border_diameter: Option, 13 | pub max_players: i32, 14 | pub view_distance: i16, 15 | pub simulation_distance: i32, 16 | pub server_desc: String, 17 | pub spawn: Spawn, 18 | } 19 | 20 | #[derive(Serialize, Deserialize, Debug, Component)] 21 | pub struct Spawn { 22 | pub kind: Radius, 23 | pub radius: i32, 24 | pub x: i32, 25 | pub y: i32, 26 | pub z: i32, 27 | } 28 | 29 | #[derive(Serialize, Deserialize, Debug, Clone, Copy)] 30 | pub enum Radius { 31 | Chebyshev, 32 | Euclidean, 33 | } 34 | 35 | impl Default for Config { 36 | fn default() -> Self { 37 | Self { 38 | border_diameter: Some(100.0), 39 | max_players: 10_000, 40 | view_distance: 32, 41 | simulation_distance: 10, 42 | server_desc: "Hyperion Test Server".to_owned(), 43 | spawn: Spawn::default(), 44 | } 45 | } 46 | } 47 | 48 | impl Default for Spawn { 49 | fn default() -> Self { 50 | Self { 51 | radius: 1000, 52 | kind: Radius::Chebyshev, 53 | x: 0, 54 | y: 64, 55 | z: 0, 56 | } 57 | } 58 | } 59 | 60 | impl Config { 61 | #[instrument] 62 | pub fn load

(path: P) -> anyhow::Result 63 | where 64 | P: AsRef + Debug, 65 | { 66 | info!("loading configuration file"); 67 | 68 | if path.as_ref().exists() { 69 | let mut file = File::open(path)?; 70 | let mut contents = String::default(); 71 | file.read_to_string(&mut contents)?; 72 | let config = toml::from_str::(contents.as_str())?; 73 | return Ok(config); 74 | } 75 | 76 | info!("configuration file not found, using defaults"); 77 | 78 | // make required folders 79 | if let Some(parent) = path.as_ref().parent() { 80 | if let Err(e) = std::fs::create_dir_all(parent) { 81 | // this might happen on a read-only filesystem (i.e., 82 | // when running on a CI, profiling in Instruments, etc.) 83 | warn!( 84 | "failed to create parent directories for {:?}: {}, using defaults", 85 | path.as_ref(), 86 | e 87 | ); 88 | return Ok(Self::default()); 89 | } 90 | } 91 | 92 | // write default config to file 93 | let default_config = Self::default(); 94 | std::fs::write(&path, toml::to_string(&default_config)?.as_bytes())?; 95 | 96 | info!("wrote default configuration to {:?}", path.as_ref()); 97 | 98 | Ok(Self::default()) 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /crates/hyperion/src/common/mod.rs: -------------------------------------------------------------------------------- 1 | //! Defined the [`Global`] struct which is used to store global data which defines a [`crate::HyperionCore`] 2 | use std::{ 3 | sync::{Arc, atomic::AtomicUsize}, 4 | time::Duration, 5 | }; 6 | 7 | use flecs_ecs::macros::Component; 8 | use libdeflater::CompressionLvl; 9 | use valence_protocol::CompressionThreshold; 10 | 11 | pub mod config; 12 | pub mod runtime; 13 | pub mod util; 14 | 15 | /// Shared data that is shared between the ECS framework and the IO thread. 16 | pub struct Shared { 17 | /// The compression level to use for the server. This is how long a packet needs to be before it is compressed. 18 | pub compression_threshold: CompressionThreshold, 19 | 20 | /// The compression level to use for the server. This is the [`libdeflater`] compression level. 21 | pub compression_level: CompressionLvl, 22 | } 23 | 24 | #[derive(Component)] 25 | pub struct Global { 26 | /// The current tick of the game. This is incremented every 50 ms. 27 | pub tick: i64, 28 | 29 | /// The maximum amount of time a player is resistant to being hurt. This is weird as this is 20 in vanilla 30 | /// Minecraft. 31 | /// However, the check to determine if a player can be hurt actually looks at this value divided by 2 32 | pub max_hurt_resistant_time: u16, 33 | 34 | /// Data shared between the IO thread and the ECS framework. 35 | pub shared: Arc, 36 | 37 | /// The amount of time from the last packet a player has sent before the server will kick them. 38 | pub keep_alive_timeout: Duration, 39 | 40 | /// The amount of time the last tick took in milliseconds. 41 | pub ms_last_tick: f32, 42 | 43 | pub player_count: AtomicUsize, 44 | } 45 | 46 | impl Global { 47 | /// Creates a new [`Global`] with the given shared data. 48 | #[must_use] 49 | pub const fn new(shared: Arc) -> Self { 50 | Self { 51 | tick: 0, 52 | max_hurt_resistant_time: 20, // actually kinda like 10 vanilla mc is weird 53 | shared, 54 | keep_alive_timeout: Duration::from_secs(20), 55 | ms_last_tick: 0.0, 56 | player_count: AtomicUsize::new(0), 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /crates/hyperion/src/common/runtime.rs: -------------------------------------------------------------------------------- 1 | //! See [`AsyncRuntime`]. 2 | 3 | use std::sync::Arc; 4 | 5 | use derive_more::{Deref, DerefMut}; 6 | use flecs_ecs::{core::World, macros::Component}; 7 | use kanal::{Receiver, Sender}; 8 | 9 | /// Type alias for world callback functions 10 | pub type WorldCallback = Box; 11 | 12 | /// Wrapper around [`tokio::runtime::Runtime`] 13 | #[derive(Component, Deref, DerefMut, Clone)] 14 | pub struct AsyncRuntime { 15 | #[deref] 16 | #[deref_mut] 17 | runtime: Arc, 18 | callback_sender: Sender, 19 | } 20 | 21 | #[derive(Component)] 22 | pub struct Tasks { 23 | pub(crate) tasks: Receiver, 24 | } 25 | 26 | impl AsyncRuntime { 27 | pub fn schedule( 28 | &self, 29 | future: impl Future + Send + 'static, 30 | handler: fn(T, &World), 31 | ) where 32 | T: Send + 'static, 33 | { 34 | let sender = self.callback_sender.clone(); 35 | 36 | self.spawn(async move { 37 | let result = future.await; 38 | 39 | let to_send = move |world: &World| { 40 | handler(result, world); 41 | }; 42 | 43 | sender.send(Box::new(to_send)).unwrap(); 44 | }); 45 | } 46 | 47 | pub(crate) fn new(sender: Sender) -> Self { 48 | Self { 49 | runtime: Arc::new( 50 | tokio::runtime::Builder::new_multi_thread() 51 | // .worker_threads(2) 52 | .enable_all() 53 | // .thread_stack_size(1024 * 1024) // 1 MiB 54 | .build() 55 | .unwrap(), 56 | ), 57 | callback_sender: sender, 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /crates/hyperion/src/common/util/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod mojang; 2 | 3 | mod sendable; 4 | mod tracing_ext; 5 | 6 | pub use sendable::*; 7 | pub use tracing_ext::*; 8 | -------------------------------------------------------------------------------- /crates/hyperion/src/common/util/sendable.rs: -------------------------------------------------------------------------------- 1 | use flecs_ecs::prelude::*; 2 | 3 | pub struct SendableRef<'a>(pub WorldRef<'a>); 4 | 5 | unsafe impl Send for SendableRef<'_> {} 6 | unsafe impl Sync for SendableRef<'_> {} 7 | 8 | pub struct SendableQuery(pub Query) 9 | where 10 | T: QueryTuple; 11 | 12 | #[expect(clippy::non_send_fields_in_send_ty)] 13 | unsafe impl Send for SendableQuery {} 14 | unsafe impl Sync for SendableQuery {} 15 | -------------------------------------------------------------------------------- /crates/hyperion/src/common/util/tracing_ext.rs: -------------------------------------------------------------------------------- 1 | use flecs_ecs::core::{ComponentId, EntityView, QueryTuple, SystemAPI, builder}; 2 | use tracing::Span; 3 | 4 | pub trait TracingExt<'a, P, T>: SystemAPI<'a, P, T> 5 | where 6 | T: QueryTuple, 7 | P: ComponentId, 8 | { 9 | fn tracing_each_entity( 10 | &mut self, 11 | span: Span, 12 | func: Func, 13 | ) -> >::BuiltType 14 | where 15 | Func: FnMut(EntityView<'_>, T::TupleType<'_>) + 'static, 16 | { 17 | self.run_each_entity( 18 | move |mut iter| { 19 | let _enter = span.enter(); 20 | 21 | while iter.next() { 22 | iter.each(); 23 | } 24 | }, 25 | func, 26 | ) 27 | } 28 | 29 | fn tracing_each( 30 | &mut self, 31 | span: Span, 32 | func: Func, 33 | ) -> >::BuiltType 34 | where 35 | Func: FnMut(T::TupleType<'_>) + 'static, 36 | { 37 | self.run_each( 38 | move |mut iter| { 39 | let _enter = span.enter(); 40 | 41 | while iter.next() { 42 | iter.each(); 43 | } 44 | }, 45 | func, 46 | ) 47 | } 48 | } 49 | 50 | impl<'a, P, T, S> TracingExt<'a, P, T> for S 51 | where 52 | S: SystemAPI<'a, P, T>, 53 | T: QueryTuple, 54 | P: ComponentId, 55 | { 56 | } 57 | -------------------------------------------------------------------------------- /crates/hyperion/src/egress/metadata.rs: -------------------------------------------------------------------------------- 1 | //! Utilities for working with the Entity Metadata packet. 2 | 3 | use ouroboros::self_referencing; 4 | use valence_protocol::{Encode, RawBytes, VarInt, packets::play}; 5 | 6 | #[self_referencing] 7 | pub struct ShowAll { 8 | bytes: Vec, 9 | #[borrows(bytes)] 10 | #[covariant] 11 | pub packet: play::EntityTrackerUpdateS2c<'this>, 12 | } 13 | 14 | // todo: I am not sure what I think about ouroboros ... but it helps allocations. 15 | /// Packet to show all parts of the skin. 16 | #[must_use] 17 | pub fn show_all(id: i32) -> ShowAll { 18 | let entity_id = VarInt(id); 19 | 20 | // https://wiki.vg/Entity_metadata#Entity_Metadata_Format 21 | // https://wiki.vg/Entity_metadata#Player 22 | // 17 = Metadata, type = byte 23 | let mut bytes = Vec::new(); 24 | bytes.push(17_u8); 25 | 26 | #[expect(clippy::unwrap_used, reason = "this should never fail")] 27 | VarInt(0).encode(&mut bytes).unwrap(); 28 | 29 | // all 1s 30 | #[expect(clippy::unwrap_used, reason = "this should never fail")] 31 | u8::MAX.encode(&mut bytes).unwrap(); 32 | 33 | // end with 0xff 34 | bytes.push(0xff); 35 | 36 | ShowAllBuilder { 37 | bytes, 38 | packet_builder: |bytes| play::EntityTrackerUpdateS2c { 39 | entity_id, 40 | tracked_values: RawBytes(bytes), 41 | }, 42 | } 43 | .build() 44 | } 45 | -------------------------------------------------------------------------------- /crates/hyperion/src/egress/stats.rs: -------------------------------------------------------------------------------- 1 | use flecs_ecs::prelude::*; 2 | use tracing::{error, info_span}; 3 | 4 | use crate::{ 5 | net::Compose, 6 | simulation::{PacketState, blocks::Blocks}, 7 | }; 8 | 9 | #[derive(Component)] 10 | pub struct StatsModule; 11 | 12 | impl Module for StatsModule { 13 | fn module(world: &World) { 14 | let players = world.query::<()>().with_enum(PacketState::Play).build(); 15 | 16 | // let last_frame_time_total = 0.0; 17 | 18 | // let system_id = GLOBAL_STATS; 19 | 20 | system!("global_update", world, &mut Compose($)) 21 | .kind(id::()) // ? OnUpdate 22 | .each_iter(move |_, _, compose| { 23 | let global = compose.global_mut(); 24 | 25 | global.tick += 1; 26 | 27 | // let player_count = compose.global().shared.player_count.load(std::sync::atomic::Ordering::Relaxed); 28 | let player_count = players.count(); 29 | 30 | let Ok(player_count) = usize::try_from(player_count) else { 31 | // should never be a negative number. this is just in case. 32 | error!("failed to convert player count to usize. Was {player_count}"); 33 | return; 34 | }; 35 | 36 | *global.player_count.get_mut() = player_count; 37 | }); 38 | 39 | system!( 40 | "load_pending", 41 | world, 42 | &mut Blocks($), 43 | ) 44 | .kind(id::()) 45 | .each_iter(|_iter, _, blocks| { 46 | let span = info_span!("load_pending"); 47 | let _enter = span.enter(); 48 | blocks.load_pending(); 49 | }); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /crates/hyperion/src/ingress/data/hyperion.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperion-mc/hyperion/ddb109092b40bc381016e327c5b13eb0201e95a3/crates/hyperion/src/ingress/data/hyperion.png -------------------------------------------------------------------------------- /crates/hyperion/src/net/agnostic.rs: -------------------------------------------------------------------------------- 1 | //! Agnostic networking primitives. Translates to correct protocol version. 2 | 3 | mod chat; 4 | pub use chat::{Chat, chat}; 5 | 6 | mod sound; 7 | pub use sound::{Sound, SoundBuilder, sound}; 8 | -------------------------------------------------------------------------------- /crates/hyperion/src/net/agnostic/chat.rs: -------------------------------------------------------------------------------- 1 | use std::io::Write; 2 | 3 | use valence_protocol::packets::play; 4 | use valence_text::IntoText; 5 | 6 | use crate::PacketBundle; 7 | 8 | pub struct Chat { 9 | raw: play::GameMessageS2c<'static>, 10 | } 11 | 12 | pub fn chat(chat: impl Into) -> Chat { 13 | let chat = chat.into(); 14 | Chat { 15 | raw: play::GameMessageS2c { 16 | chat: chat.into_cow_text(), 17 | overlay: false, 18 | }, 19 | } 20 | } 21 | 22 | #[macro_export] 23 | macro_rules! chat { 24 | ($($arg:tt)*) => { 25 | $crate::net::agnostic::chat(format!($($arg)*)) 26 | }; 27 | } 28 | 29 | impl PacketBundle for &Chat { 30 | fn encode_including_ids(self, mut w: impl Write) -> anyhow::Result<()> { 31 | self.raw.encode_including_ids(&mut w) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /crates/hyperion/src/net/agnostic/sound.rs: -------------------------------------------------------------------------------- 1 | use std::io::Write; 2 | 3 | use glam::Vec3; 4 | use valence_protocol::{ 5 | packets::play, 6 | sound::{SoundCategory, SoundId}, 7 | }; 8 | 9 | use crate::PacketBundle; 10 | 11 | #[must_use] 12 | pub struct Sound { 13 | raw: play::PlaySoundS2c<'static>, 14 | } 15 | 16 | #[must_use] 17 | pub struct SoundBuilder { 18 | position: Vec3, 19 | pitch: f32, 20 | volume: f32, 21 | seed: Option, 22 | sound: valence_ident::Ident<&'static str>, 23 | } 24 | 25 | impl SoundBuilder { 26 | pub const fn pitch(mut self, pitch: f32) -> Self { 27 | self.pitch = pitch; 28 | self 29 | } 30 | 31 | pub const fn volume(mut self, volume: f32) -> Self { 32 | self.volume = volume; 33 | self 34 | } 35 | 36 | pub const fn seed(mut self, seed: i64) -> Self { 37 | self.seed = Some(seed); 38 | self 39 | } 40 | 41 | pub fn build(self) -> Sound { 42 | Sound { 43 | raw: play::PlaySoundS2c { 44 | id: SoundId::Direct { 45 | id: self.sound.into(), 46 | range: None, 47 | }, 48 | position: (self.position * 8.0).as_ivec3(), 49 | volume: self.volume, 50 | pitch: self.pitch, 51 | seed: self.seed.unwrap_or_else(|| fastrand::i64(..)), 52 | category: SoundCategory::Master, 53 | }, 54 | } 55 | } 56 | } 57 | 58 | impl PacketBundle for &Sound { 59 | fn encode_including_ids(self, mut w: impl Write) -> anyhow::Result<()> { 60 | self.raw.encode_including_ids(&mut w) 61 | } 62 | } 63 | 64 | pub const fn sound(sound: valence_ident::Ident<&'static str>, position: Vec3) -> SoundBuilder { 65 | SoundBuilder { 66 | position, 67 | pitch: 1.0, 68 | volume: 1.0, 69 | seed: None, 70 | sound, 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /crates/hyperion/src/net/packets.rs: -------------------------------------------------------------------------------- 1 | use std::{borrow::Cow, io::Write}; 2 | 3 | use uuid::Uuid; 4 | use valence_protocol::{ 5 | Decode, Encode, ItemStack, Packet, VarInt, 6 | packets::play::{ 7 | boss_bar_s2c::{BossBarColor, BossBarDivision, BossBarFlags}, 8 | entity_equipment_update_s2c::EquipmentEntry, 9 | }, 10 | }; 11 | 12 | #[derive(Clone, PartialEq, Debug, Packet)] 13 | pub struct EntityEquipmentUpdateS2c<'a> { 14 | pub entity_id: VarInt, 15 | pub equipment: Cow<'a, [EquipmentEntry]>, 16 | } 17 | 18 | impl Encode for EntityEquipmentUpdateS2c<'_> { 19 | fn encode(&self, mut w: impl Write) -> anyhow::Result<()> { 20 | self.entity_id.encode(&mut w)?; 21 | 22 | for i in 0..self.equipment.len() { 23 | let slot = self.equipment[i].slot; 24 | if i == self.equipment.len() - 1 { 25 | slot.encode(&mut w)?; 26 | } else { 27 | (slot | -128).encode(&mut w)?; 28 | } 29 | self.equipment[i].item.encode(&mut w)?; 30 | } 31 | 32 | Ok(()) 33 | } 34 | } 35 | 36 | impl<'a> Decode<'a> for EntityEquipmentUpdateS2c<'static> { 37 | fn decode(r: &mut &'a [u8]) -> anyhow::Result { 38 | let entity_id = VarInt::decode(r)?; 39 | 40 | let mut equipment = vec![]; 41 | 42 | loop { 43 | let slot = i8::decode(r)?; 44 | let item = ItemStack::decode(r)?; 45 | equipment.push(EquipmentEntry { 46 | slot: slot & 127, 47 | item, 48 | }); 49 | if slot & -128 == 0 { 50 | break; 51 | } 52 | } 53 | 54 | Ok(Self { 55 | entity_id, 56 | equipment: Cow::Owned(equipment), 57 | }) 58 | } 59 | } 60 | 61 | #[derive(Clone, Debug, Encode, Packet)] 62 | pub struct BossBarS2c<'a> { 63 | pub id: Uuid, 64 | pub action: BossBarAction<'a>, 65 | } 66 | 67 | #[derive(Clone, PartialEq, Debug, Encode)] 68 | pub enum BossBarAction<'a> { 69 | Add { 70 | title: hyperion_text::Text<'a>, 71 | health: f32, 72 | color: BossBarColor, 73 | division: BossBarDivision, 74 | flags: BossBarFlags, 75 | }, 76 | Remove, 77 | UpdateHealth(f32), 78 | UpdateTitle(hyperion_text::Text<'a>), 79 | UpdateStyle(BossBarColor, BossBarDivision), 80 | UpdateFlags(BossBarFlags), 81 | } 82 | -------------------------------------------------------------------------------- /crates/hyperion/src/simulation/animation.rs: -------------------------------------------------------------------------------- 1 | use enumset::{EnumSet, EnumSetType}; 2 | use flecs_ecs::prelude::Component; 3 | use valence_protocol::{VarInt, packets::play::EntityAnimationS2c}; 4 | 5 | #[derive(EnumSetType)] 6 | #[repr(u8)] 7 | pub enum Kind { 8 | SwingMainArm = 0, 9 | UseItem = 1, 10 | LeaveBed = 2, 11 | SwingOffHand = 3, 12 | Critical = 4, 13 | MagicCritical = 5, 14 | } 15 | 16 | #[derive(Component)] 17 | pub struct ActiveAnimation { 18 | kind: EnumSet, 19 | } 20 | 21 | impl ActiveAnimation { 22 | pub const NONE: Self = Self { 23 | kind: EnumSet::empty(), 24 | }; 25 | 26 | pub fn packets( 27 | &mut self, 28 | entity_id: VarInt, 29 | ) -> impl Iterator + use<> { 30 | self.kind.iter().map(move |kind| { 31 | let kind = kind as u8; 32 | EntityAnimationS2c { 33 | entity_id, 34 | animation: kind, 35 | } 36 | }) 37 | } 38 | 39 | pub fn push(&mut self, kind: Kind) { 40 | self.kind.insert(kind); 41 | } 42 | 43 | pub fn clear(&mut self) { 44 | self.kind.clear(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /crates/hyperion/src/simulation/blocks/shared.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::BTreeMap, path::Path}; 2 | 3 | use anyhow::Context; 4 | use tokio::runtime::Runtime; 5 | use valence_protocol::Ident; 6 | use valence_registry::{BiomeRegistry, biome::BiomeId}; 7 | 8 | use super::manager::RegionManager; 9 | 10 | /// Inner state of the [`MinecraftWorld`] component. 11 | pub struct WorldShared { 12 | pub regions: RegionManager, 13 | pub biome_to_id: BTreeMap, BiomeId>, 14 | } 15 | 16 | impl WorldShared { 17 | pub(crate) fn new( 18 | biomes: &BiomeRegistry, 19 | runtime: &Runtime, 20 | path: &Path, 21 | ) -> anyhow::Result { 22 | let regions = RegionManager::new(runtime, path).context("failed to get anvil data")?; 23 | 24 | let biome_to_id = biomes 25 | .iter() 26 | .map(|(id, name, _)| (name.to_string_ident(), id)) 27 | .collect(); 28 | 29 | Ok(Self { 30 | regions, 31 | biome_to_id, 32 | }) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /crates/hyperion/src/simulation/data/registries.nbt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperion-mc/hyperion/ddb109092b40bc381016e327c5b13eb0201e95a3/crates/hyperion/src/simulation/data/registries.nbt -------------------------------------------------------------------------------- /crates/hyperion/src/simulation/entity_kind.rs: -------------------------------------------------------------------------------- 1 | use flecs_ecs::{core::ComponentOrPairId, macros::Component}; 2 | 3 | #[derive(Component, Copy, Clone, Debug, PartialEq, Eq, Hash)] 4 | #[repr(C)] 5 | #[meta] 6 | pub enum EntityKind { 7 | Allay = 0, 8 | AreaEffectCloud = 1, 9 | ArmorStand = 2, 10 | Arrow = 3, 11 | Axolotl = 4, 12 | Bat = 5, 13 | Bee = 6, 14 | Blaze = 7, 15 | BlockDisplay = 8, 16 | Boat = 9, 17 | Camel = 10, 18 | Cat = 11, 19 | CaveSpider = 12, 20 | ChestBoat = 13, 21 | ChestMinecart = 14, 22 | Chicken = 15, 23 | Cod = 16, 24 | CommandBlockMinecart = 17, 25 | Cow = 18, 26 | Creeper = 19, 27 | Dolphin = 20, 28 | Donkey = 21, 29 | DragonFireball = 22, 30 | Drowned = 23, 31 | Egg = 24, 32 | ElderGuardian = 25, 33 | EndCrystal = 26, 34 | EnderDragon = 27, 35 | EnderPearl = 28, 36 | Enderman = 29, 37 | Endermite = 30, 38 | Evoker = 31, 39 | EvokerFangs = 32, 40 | ExperienceBottle = 33, 41 | ExperienceOrb = 34, 42 | EyeOfEnder = 35, 43 | FallingBlock = 36, 44 | FireworkRocket = 37, 45 | Fox = 38, 46 | Frog = 39, 47 | FurnaceMinecart = 40, 48 | Ghast = 41, 49 | Giant = 42, 50 | GlowItemFrame = 43, 51 | GlowSquid = 44, 52 | Goat = 45, 53 | Guardian = 46, 54 | Hoglin = 47, 55 | HopperMinecart = 48, 56 | Horse = 49, 57 | Husk = 50, 58 | Illusioner = 51, 59 | Interaction = 52, 60 | IronGolem = 53, 61 | Item = 54, 62 | ItemDisplay = 55, 63 | ItemFrame = 56, 64 | Fireball = 57, 65 | LeashKnot = 58, 66 | Lightning = 59, 67 | Llama = 60, 68 | LlamaSpit = 61, 69 | MagmaCube = 62, 70 | Marker = 63, 71 | Minecart = 64, 72 | Mooshroom = 65, 73 | Mule = 66, 74 | Ocelot = 67, 75 | Painting = 68, 76 | Panda = 69, 77 | Parrot = 70, 78 | Phantom = 71, 79 | Pig = 72, 80 | Piglin = 73, 81 | PiglinBrute = 74, 82 | Pillager = 75, 83 | PolarBear = 76, 84 | Potion = 77, 85 | Pufferfish = 78, 86 | Rabbit = 79, 87 | Ravager = 80, 88 | Salmon = 81, 89 | Sheep = 82, 90 | Shulker = 83, 91 | ShulkerBullet = 84, 92 | Silverfish = 85, 93 | Skeleton = 86, 94 | SkeletonHorse = 87, 95 | Slime = 88, 96 | SmallFireball = 89, 97 | Sniffer = 90, 98 | SnowGolem = 91, 99 | Snowball = 92, 100 | SpawnerMinecart = 93, 101 | SpectralArrow = 94, 102 | Spider = 95, 103 | Squid = 96, 104 | Stray = 97, 105 | Strider = 98, 106 | Tadpole = 99, 107 | TextDisplay = 100, 108 | Tnt = 101, 109 | TntMinecart = 102, 110 | TraderLlama = 103, 111 | Trident = 104, 112 | TropicalFish = 105, 113 | Turtle = 106, 114 | Vex = 107, 115 | Villager = 108, 116 | Vindicator = 109, 117 | WanderingTrader = 110, 118 | Warden = 111, 119 | Witch = 112, 120 | Wither = 113, 121 | WitherSkeleton = 114, 122 | WitherSkull = 115, 123 | Wolf = 116, 124 | Zoglin = 117, 125 | Zombie = 118, 126 | ZombieHorse = 119, 127 | ZombieVillager = 120, 128 | ZombifiedPiglin = 121, 129 | Player = 122, 130 | FishingBobber = 123, 131 | Gui = 124, 132 | } 133 | -------------------------------------------------------------------------------- /crates/hyperion/src/simulation/metadata/block_display.rs: -------------------------------------------------------------------------------- 1 | use flecs_ecs::prelude::*; 2 | use valence_generated::block::BlockState; 3 | 4 | use super::Metadata; 5 | use crate::define_and_register_components; 6 | 7 | // Example usage: 8 | define_and_register_components! { 9 | 22, DisplayedBlockState -> BlockState, 10 | } 11 | 12 | impl Default for DisplayedBlockState { 13 | fn default() -> Self { 14 | Self::new(BlockState::EMERALD_BLOCK) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /crates/hyperion/src/simulation/metadata/entity.rs: -------------------------------------------------------------------------------- 1 | //! Entity metadata. 2 | //! 3 | //! The base class 4 | //! 5 | //! ```text 6 | //! 0 -> Byte (0) EntityFlags 7 | //! 1 -> VarInt (1) AirTicks(300) 8 | //! 2 -> TextComponent? (6) CustomName("") 9 | //! 3 -> bool (8) CustomNameVisible(false) 10 | //! 4 -> bool (8) Silent(false) 11 | //! 5 -> bool (8) NoGravity(false) 12 | //! 6 -> Pose (21) Pose(STANDING) 13 | //! 7 -> VarInt (1) TicksFrozenInPowderSnow(0) 14 | //! ``` 15 | 16 | use flecs_ecs::prelude::*; 17 | use valence_protocol::{Encode, VarInt}; 18 | 19 | use crate::{define_and_register_components, simulation::Metadata}; 20 | 21 | mod flags; 22 | pub use flags::EntityFlags; 23 | 24 | // Example usage: 25 | define_and_register_components! { 26 | 1, AirSupply -> VarInt, 27 | // 2, CustomName -> Option, 28 | 3, CustomNameVisible -> bool, 29 | 4, Silent -> bool, 30 | 5, NoGravity -> bool, 31 | 7, TicksFrozenInPowderSnow -> VarInt, 32 | } 33 | 34 | impl Default for AirSupply { 35 | fn default() -> Self { 36 | Self::new(VarInt(300)) 37 | } 38 | } 39 | 40 | impl Default for CustomNameVisible { 41 | fn default() -> Self { 42 | Self::new(false) 43 | } 44 | } 45 | 46 | impl Default for Silent { 47 | fn default() -> Self { 48 | Self::new(false) 49 | } 50 | } 51 | 52 | impl Default for NoGravity { 53 | fn default() -> Self { 54 | Self::new(false) 55 | } 56 | } 57 | 58 | impl Default for TicksFrozenInPowderSnow { 59 | fn default() -> Self { 60 | Self::new(VarInt(0)) 61 | } 62 | } 63 | 64 | #[derive(Encode, Clone, Copy, Default, PartialEq, Eq, Debug)] 65 | #[derive(Component)] 66 | #[meta] 67 | #[repr(C)] // ideally this would be u8 68 | pub enum Pose { 69 | #[default] 70 | Standing, 71 | FallFlying, 72 | Sleeping, 73 | Swimming, 74 | SpinAttack, 75 | Sneaking, 76 | LongJumping, 77 | Dying, 78 | Croaking, 79 | UsingTongue, 80 | Sitting, 81 | Roaring, 82 | Sniffing, 83 | Emerging, 84 | Digging, 85 | } 86 | 87 | impl Metadata for Pose { 88 | type Type = Self; 89 | 90 | const INDEX: u8 = 6; 91 | 92 | fn to_type(self) -> Self::Type { 93 | self 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /crates/hyperion/src/simulation/metadata/entity/flags.rs: -------------------------------------------------------------------------------- 1 | use derive_more::Deref; 2 | use flecs_ecs::{core::ComponentOrPairId, macros::Component}; 3 | 4 | use crate::simulation::metadata::Metadata; 5 | 6 | // todo: can be u8 7 | #[derive(Component, PartialEq, Eq, Copy, Clone, Debug, Deref)] 8 | #[meta] 9 | pub struct EntityFlags { 10 | value: u8, 11 | } 12 | 13 | impl Default for EntityFlags { 14 | fn default() -> Self { 15 | Self::new() 16 | } 17 | } 18 | 19 | impl EntityFlags { 20 | pub const CROUCHING: Self = Self { value: 0x02 }; 21 | pub const FLYING_WITH_ELYTRA: Self = Self { value: 0x80 }; 22 | pub const GLOWING: Self = Self { value: 0x40 }; 23 | pub const INVISIBLE: Self = Self { value: 0x20 }; 24 | pub const ON_FIRE: Self = Self { value: 0x01 }; 25 | // 0x04 skipped (previously riding) 26 | pub const SPRINTING: Self = Self { value: 0x08 }; 27 | pub const SWIMMING: Self = Self { value: 0x10 }; 28 | 29 | const fn new() -> Self { 30 | Self { value: 0 } 31 | } 32 | } 33 | 34 | impl std::ops::BitOrAssign for EntityFlags { 35 | fn bitor_assign(&mut self, rhs: Self) { 36 | self.value |= rhs.value; 37 | } 38 | } 39 | 40 | impl std::ops::BitAndAssign for EntityFlags { 41 | fn bitand_assign(&mut self, rhs: Self) { 42 | self.value &= rhs.value; 43 | } 44 | } 45 | 46 | impl std::ops::BitOr for EntityFlags { 47 | type Output = Self; 48 | 49 | fn bitor(self, rhs: Self) -> Self { 50 | Self { 51 | value: self.value | rhs.value, 52 | } 53 | } 54 | } 55 | 56 | impl std::ops::BitAnd for EntityFlags { 57 | type Output = Self; 58 | 59 | fn bitand(self, rhs: Self) -> Self { 60 | Self { 61 | value: self.value & rhs.value, 62 | } 63 | } 64 | } 65 | 66 | impl std::ops::Not for EntityFlags { 67 | type Output = Self; 68 | 69 | fn not(self) -> Self { 70 | Self { value: !self.value } 71 | } 72 | } 73 | 74 | impl Metadata for EntityFlags { 75 | type Type = u8; 76 | 77 | const INDEX: u8 = 0; 78 | 79 | fn to_type(self) -> Self::Type { 80 | self.value 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /crates/hyperion/src/simulation/metadata/player.rs: -------------------------------------------------------------------------------- 1 | // Index Type Meaning Default 2 | // 15 Float (3) Additional Hearts 0.0 3 | // 16 VarInt (1) Score 0 4 | // 17 Byte (0) The Displayed Skin Parts bit mask that is sent in Client Settings 0 5 | // Bit mask Meaning 6 | // 0x01 Cape enabled 7 | // 0x02 Jacket enabled 8 | // 0x04 Left sleeve enabled 9 | // 0x08 Right sleeve enabled 10 | // 0x10 Left pants leg enabled 11 | // 0x20 Right pants leg enabled 12 | // 0x40 Hat enabled 13 | // 0x80 Unused 14 | // 18 Byte (0) Main hand (0 : Left, 1 : Right) 1 15 | // 19 NBT (16) Left shoulder entity data (for occupying parrot) Empty 16 | // 20 NBT (16) Right shoulder entity data (for occupying parrot) Empty 17 | 18 | use flecs_ecs::prelude::*; 19 | use valence_protocol::VarInt; 20 | 21 | use super::Metadata; 22 | use crate::define_and_register_components; 23 | 24 | // Example usage: 25 | define_and_register_components! { 26 | // 15 Float (3) Additional Hearts 0.0 27 | 15, AdditionalHearts -> f32, 28 | 29 | // 16 VarInt (1) Score 0 30 | 16, Score -> VarInt, 31 | 32 | // 17 Byte (0) The Displayed Skin Parts bit mask that is sent in Client Settings 0 33 | 17, DisplayedSkinParts -> u8, 34 | 35 | // 18 Byte (0) Main hand (0 : Left, 1 : Right) 1 36 | 18, MainHand -> u8, 37 | 38 | // 19 NBT (16) Left shoulder entity data (for occupying parrot) Empty 39 | // 19, LeftShoulderEntityData -> Option>, 40 | 41 | // 20 NBT (16) Right shoulder entity data (for occupying parrot) Empty 42 | // 20, RightShoulderEntityData -> Option>, 43 | } 44 | 45 | impl Default for AdditionalHearts { 46 | fn default() -> Self { 47 | Self::new(0.0) 48 | } 49 | } 50 | 51 | impl Default for Score { 52 | fn default() -> Self { 53 | Self::new(VarInt(0)) 54 | } 55 | } 56 | 57 | impl Default for DisplayedSkinParts { 58 | fn default() -> Self { 59 | Self::new(0) 60 | } 61 | } 62 | 63 | impl Default for MainHand { 64 | fn default() -> Self { 65 | Self::new(1) // 1 = Right hand 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /crates/hyperion/src/simulation/metadata/status.rs: -------------------------------------------------------------------------------- 1 | use std::ops::BitOr; 2 | 3 | #[derive(Clone, Copy, PartialEq, Eq)] 4 | pub struct EntityStatus(pub u8); 5 | 6 | impl EntityStatus { 7 | pub const HAS_GLOWING_EFFECT: Self = Self(0x40); 8 | pub const IS_CROUCHING: Self = Self(0x02); 9 | pub const IS_FLYING_WITH_ELYTRA: Self = Self(0x80); 10 | pub const IS_INVISIBLE: Self = Self(0x20); 11 | pub const IS_ON_FIRE: Self = Self(0x01); 12 | pub const IS_SPRINTING: Self = Self(0x08); 13 | pub const IS_SWIMMING: Self = Self(0x10); 14 | 15 | pub const fn has_status(self, status: Self) -> bool { 16 | self.0 & status.0 != 0 17 | } 18 | 19 | pub const fn set_status(&mut self, status: Self) { 20 | self.0 |= status.0; 21 | } 22 | 23 | pub const fn clear_status(&mut self, status: Self) { 24 | self.0 &= !status.0; 25 | } 26 | 27 | pub const fn toggle_status(&mut self, status: Self) { 28 | self.0 ^= status.0; 29 | } 30 | } 31 | 32 | impl BitOr for EntityStatus { 33 | type Output = Self; 34 | 35 | fn bitor(self, rhs: Self) -> Self::Output { 36 | Self(self.0 | rhs.0) 37 | } 38 | } 39 | 40 | impl BitOr for &EntityStatus { 41 | type Output = EntityStatus; 42 | 43 | fn bitor(self, rhs: EntityStatus) -> Self::Output { 44 | EntityStatus(self.0 | rhs.0) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /crates/hyperion/src/simulation/skin.rs: -------------------------------------------------------------------------------- 1 | //! Constructs for obtaining a player's skin. 2 | use anyhow::Context; 3 | use base64::{Engine as _, engine::general_purpose}; 4 | use flecs_ecs::macros::Component; 5 | use rkyv::Archive; 6 | use tracing::info; 7 | 8 | use crate::{storage::SkinHandler, util::mojang::MojangClient}; 9 | 10 | /// A signed player skin. 11 | #[derive( 12 | Debug, 13 | Clone, 14 | Archive, 15 | Component, 16 | rkyv::Deserialize, 17 | rkyv::Serialize, 18 | serde::Serialize, 19 | serde::Deserialize 20 | )] 21 | pub struct PlayerSkin { 22 | /// The textures of the player skin, usually obtained from the [`MojangClient`] as a base64 string. 23 | pub textures: String, 24 | /// The signature of the player skin, usually obtained from the [`MojangClient`] as a base64 string. 25 | pub signature: String, 26 | } 27 | 28 | impl PlayerSkin { 29 | pub const EMPTY: Self = Self { 30 | textures: String::new(), 31 | signature: String::new(), 32 | }; 33 | 34 | /// Creates a new [`PlayerSkin`] 35 | #[must_use] 36 | pub const fn new(textures: String, signature: String) -> Self { 37 | Self { 38 | textures, 39 | signature, 40 | } 41 | } 42 | 43 | /// Gets a skin from a Mojang UUID. 44 | /// 45 | /// # Arguments 46 | /// * `uuid` - A Mojang UUID. 47 | /// 48 | /// # Returns 49 | /// A `PlayerSkin` based on the UUID, or `None` if not found. 50 | pub async fn from_uuid( 51 | uuid: uuid::Uuid, 52 | mojang: &MojangClient, 53 | skins: &SkinHandler, 54 | ) -> anyhow::Result> { 55 | if let Some(skin) = skins.find(uuid)? { 56 | info!("Returning cached skin"); 57 | return Ok(Some(skin)); 58 | } 59 | 60 | info!("player skin cache miss for {uuid}"); 61 | 62 | let json_object = mojang.data_from_uuid(&uuid).await?; 63 | let properties_array = json_object["properties"] 64 | .as_array() 65 | .with_context(|| format!("no properties on {json_object:?}"))?; 66 | for property_object in properties_array { 67 | let name = property_object["name"] 68 | .as_str() 69 | .with_context(|| format!("no name on {property_object:?}"))?; 70 | if name != "textures" { 71 | continue; 72 | } 73 | let textures = property_object["value"] 74 | .as_str() 75 | .with_context(|| format!("no value on {property_object:?}"))?; 76 | let signature = property_object["signature"] 77 | .as_str() 78 | .with_context(|| format!("no signature on {property_object:?}"))?; 79 | 80 | // Validate base64 encoding 81 | general_purpose::STANDARD 82 | .decode(textures) 83 | .context("invalid texture value")?; 84 | general_purpose::STANDARD 85 | .decode(signature) 86 | .context("invalid signature value")?; 87 | 88 | let res = Self { 89 | textures: textures.to_string(), 90 | signature: signature.to_string(), 91 | }; 92 | skins.insert(uuid, &res)?; 93 | return Ok(Some(res)); 94 | } 95 | Ok(None) 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /crates/hyperion/src/storage/buf.rs: -------------------------------------------------------------------------------- 1 | //! Buffer implementations. todo: We might be able to scrap this for [`bytes::Buf`] in the future. 2 | 3 | /// # Safety 4 | /// - `get_contiguous` must return a slice of exactly `len` bytes long. 5 | /// - `advance` must advance the buffer by exactly `len` bytes. 6 | pub unsafe trait Buf { 7 | /// What type we get when we advance. For example, if we have a [`bytes::BytesMut`], we get a [`bytes::BytesMut`]. 8 | type Output; 9 | 10 | /// Get a contiguous slice of memory of length `len`. 11 | /// 12 | /// The returned slice must be exactly `len` bytes long. 13 | fn get_contiguous(&mut self, len: usize) -> &mut [u8]; 14 | 15 | /// Advance the buffer by exactly `len` bytes. 16 | fn advance(&mut self, len: usize) -> Self::Output; 17 | } 18 | 19 | unsafe impl Buf for bytes::BytesMut { 20 | type Output = Self; 21 | 22 | fn get_contiguous(&mut self, len: usize) -> &mut [u8] { 23 | // self.resize(len, 0); 24 | // self 25 | self.reserve(len); 26 | let cap = self.spare_capacity_mut(); 27 | let cap = unsafe { cap.assume_init_mut() }; 28 | cap 29 | } 30 | 31 | fn advance(&mut self, len: usize) -> Self::Output { 32 | unsafe { self.set_len(self.len() + len) }; 33 | self.split_to(len) 34 | } 35 | } 36 | 37 | unsafe impl Buf for Vec { 38 | type Output = (); 39 | 40 | fn get_contiguous(&mut self, len: usize) -> &mut [u8] { 41 | // self.resize(len, 0); 42 | // self 43 | self.reserve(len); 44 | let cap = self.spare_capacity_mut(); 45 | let cap = unsafe { cap.assume_init_mut() }; 46 | cap 47 | } 48 | 49 | fn advance(&mut self, len: usize) -> Self::Output { 50 | unsafe { self.set_len(self.len() + len) }; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /crates/hyperion/src/storage/db.rs: -------------------------------------------------------------------------------- 1 | //! Constructs for connecting and working with a `Heed` database. 2 | 3 | use std::path::Path; 4 | 5 | use byteorder::NativeEndian; 6 | use derive_more::Deref; 7 | use flecs_ecs::macros::Component; 8 | use heed::{Database, Env, EnvOpenOptions, types}; 9 | use uuid::Uuid; 10 | 11 | use crate::simulation::skin::{ArchivedPlayerSkin, PlayerSkin}; 12 | 13 | /// A wrapper around a `Heed` database 14 | #[derive(Component, Debug, Clone, Deref)] 15 | pub struct LocalDb { 16 | env: Env, 17 | } 18 | 19 | impl LocalDb { 20 | /// Creates a new [`LocalDb`] 21 | pub fn new() -> anyhow::Result { 22 | let path = Path::new("db").join("heed.mdb"); 23 | 24 | std::fs::create_dir_all(&path)?; 25 | 26 | let env = unsafe { 27 | EnvOpenOptions::new() 28 | .map_size(10 * 1024 * 1024) // 10MB 29 | .max_dbs(8) // todo: why is this needed/configurable? ideally would be infinite... 30 | .open(&path)? 31 | }; 32 | 33 | Ok(Self { env }) 34 | } 35 | } 36 | 37 | /// A handler for player skin operations 38 | #[derive(Component, Debug, Clone)] 39 | pub struct SkinHandler { 40 | env: Env, 41 | skins: Database, types::Bytes>, 42 | } 43 | 44 | impl SkinHandler { 45 | /// Creates a new [`SkinHandler`] from a given [`LocalDb`]. 46 | pub fn new(db: &LocalDb) -> anyhow::Result { 47 | // We open the default unnamed database 48 | let skins = { 49 | let mut wtxn = db.write_txn()?; 50 | let db = db.create_database(&mut wtxn, Some("uuid-to-skins"))?; 51 | wtxn.commit()?; 52 | db 53 | }; 54 | 55 | Ok(Self { 56 | env: db.env.clone(), 57 | skins, 58 | }) 59 | } 60 | 61 | /// Finds a [`PlayerSkin`] by its UUID. 62 | pub fn find(&self, uuid: Uuid) -> anyhow::Result> { 63 | // We open a read transaction to check if those values are now available 64 | 65 | let uuid = uuid.as_u128(); 66 | 67 | let rtxn = self.env.read_txn()?; 68 | let skin = self.skins.get(&rtxn, &uuid); 69 | 70 | let Some(skin) = skin? else { 71 | return Ok(None); 72 | }; 73 | 74 | let skin = unsafe { rkyv::access_unchecked::(skin) }; 75 | let skin = rkyv::deserialize::<_, rkyv::rancor::Error>(skin).unwrap(); 76 | Ok(Some(skin)) 77 | } 78 | 79 | /// Inserts a [`PlayerSkin`] into the database. 80 | pub fn insert(&self, uuid: Uuid, skin: &PlayerSkin) -> anyhow::Result<()> { 81 | let uuid = uuid.as_u128(); 82 | 83 | let mut wtxn = self.env.write_txn()?; 84 | 85 | let skin = rkyv::to_bytes::(skin).unwrap(); 86 | 87 | self.skins.put(&mut wtxn, &uuid, &skin)?; 88 | wtxn.commit()?; 89 | 90 | Ok(()) 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /crates/hyperion/src/storage/event/mod.rs: -------------------------------------------------------------------------------- 1 | mod queue; 2 | mod sync; 3 | 4 | pub use queue::*; 5 | pub use sync::*; 6 | -------------------------------------------------------------------------------- /crates/hyperion/src/storage/event/queue/event_queue.rs: -------------------------------------------------------------------------------- 1 | use derive_more::{Deref, DerefMut}; 2 | use flecs_ecs::macros::Component; 3 | 4 | use crate::storage::{Event, ThreadLocalVec}; 5 | 6 | #[derive(Component, Deref, DerefMut)] 7 | pub struct EventQueue { 8 | // todo: maybe change to SOA vec 9 | inner: ThreadLocalVec, 10 | } 11 | 12 | impl Default for EventQueue 13 | where 14 | T: Event, 15 | { 16 | fn default() -> Self { 17 | Self { 18 | inner: ThreadLocalVec::default(), 19 | } 20 | } 21 | } 22 | 23 | impl EventQueue { 24 | pub fn drain(&mut self) -> impl Iterator { 25 | self.inner.drain() 26 | } 27 | 28 | pub fn peek(&mut self) -> impl Iterator { 29 | self.inner.iter_mut() 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /crates/hyperion/src/storage/event/queue/mod.rs: -------------------------------------------------------------------------------- 1 | use std::marker::PhantomData; 2 | 3 | use flecs_ecs::{ 4 | core::{ComponentId, ComponentType, DataComponent, Struct, World, WorldGet, flecs}, 5 | macros::Component, 6 | }; 7 | 8 | use crate::simulation::event; 9 | 10 | pub mod event_queue; 11 | pub mod raw; 12 | 13 | pub use event_queue::EventQueue; 14 | use hyperion_event_macros::define_events; 15 | 16 | impl Events { 17 | pub fn push(&self, event: E, world: &World) { 18 | E::input(event, self, world); 19 | } 20 | } 21 | 22 | struct SendSyncPtr(*const T, PhantomData); 23 | 24 | unsafe impl Send for SendSyncPtr {} 25 | unsafe impl Sync for SendSyncPtr {} 26 | 27 | mod sealed { 28 | pub trait Sealed {} 29 | } 30 | 31 | pub trait Event: sealed::Sealed + Send + Sync + 'static { 32 | fn input(elem: Self, events: &Events, world: &World); 33 | } 34 | 35 | fn register_and_pointer>( 36 | world: &World, 37 | elem: T, 38 | ) -> *const T { 39 | world.component::().add_trait::(); 40 | 41 | world.set(elem); 42 | 43 | world.get::<&T>(|x: &T| std::ptr::from_ref::(x)) 44 | } 45 | 46 | // Create the Events struct 47 | define_events! { 48 | event::ItemInteract, 49 | event::SetSkin, 50 | event::AttackEntity, 51 | event::ChatMessage, 52 | event::Command, 53 | event::DestroyBlock, 54 | event::ItemDropEvent, 55 | event::PlaceBlock, 56 | event::PostureUpdate, 57 | event::SwingArm, 58 | event::ToggleDoor, 59 | event::ReleaseUseItem, 60 | event::ClientStatusEvent, 61 | event::ProjectileEntityEvent, 62 | event::ProjectileBlockEvent, 63 | event::ClickSlotEvent, 64 | event::DropItemStackEvent, 65 | event::UpdateSelectedSlotEvent, 66 | event::StartDestroyBlock, 67 | event::HitGroundEvent, 68 | } 69 | -------------------------------------------------------------------------------- /crates/hyperion/src/storage/event/queue/raw.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | any::TypeId, cell::SyncUnsafeCell, mem::MaybeUninit, ptr::NonNull, sync::atomic::AtomicUsize, 3 | }; 4 | 5 | use anyhow::bail; 6 | 7 | /// Denotes a pointer that will become invalid at the end of the tick (it is bump allocated) 8 | #[derive(Debug, Copy, Clone)] 9 | pub struct TypedBumpPtr { 10 | id: TypeId, 11 | // a ptr to a bump allocated event 12 | elem: NonNull<()>, 13 | } 14 | 15 | unsafe impl Send for TypedBumpPtr {} 16 | unsafe impl Sync for TypedBumpPtr {} 17 | 18 | impl TypedBumpPtr { 19 | #[must_use] 20 | pub const fn new(id: TypeId, elem: NonNull<()>) -> Self { 21 | Self { id, elem } 22 | } 23 | 24 | #[must_use] 25 | pub const fn id(&self) -> TypeId { 26 | self.id 27 | } 28 | 29 | #[must_use] 30 | pub const fn elem(&self) -> NonNull<()> { 31 | self.elem 32 | } 33 | } 34 | 35 | /// Think of this as a fixed capacity `Vec` 36 | pub struct RawQueue { 37 | elems: Box<[SyncUnsafeCell>]>, 38 | len: AtomicUsize, 39 | } 40 | 41 | // todo: remove Copy requirement. 42 | impl RawQueue { 43 | #[must_use] 44 | pub fn new(size: usize) -> Self { 45 | let elems = (0..size) 46 | .map(|_| SyncUnsafeCell::new(MaybeUninit::uninit())) 47 | .collect(); 48 | 49 | Self { 50 | elems, 51 | len: AtomicUsize::new(0), 52 | } 53 | } 54 | 55 | pub fn push(&self, elem: T) -> anyhow::Result<()> { 56 | let ptr = self.len.fetch_add(1, std::sync::atomic::Ordering::Relaxed); 57 | 58 | let elems = &*self.elems; 59 | 60 | let Some(ptr) = elems.get(ptr) else { 61 | self.len.fetch_sub(1, std::sync::atomic::Ordering::Relaxed); 62 | bail!("queue is full"); 63 | }; 64 | 65 | let ptr = unsafe { &mut *ptr.get() }; 66 | ptr.write(elem); 67 | 68 | Ok(()) 69 | } 70 | 71 | pub fn iter(&self) -> impl Iterator + '_ { 72 | let len = self.len.load(std::sync::atomic::Ordering::Relaxed); 73 | 74 | (0..len).map(move |i| { 75 | let elem = &self.elems[i]; 76 | let elem = unsafe { &*elem.get() }; 77 | unsafe { elem.assume_init_read() } 78 | }) 79 | } 80 | 81 | pub fn reset(&mut self) { 82 | // we do not need to `Drop` because NonNull does not implement Drop 83 | *self.len.get_mut() = 0; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /crates/hyperion/src/storage/event/sync.rs: -------------------------------------------------------------------------------- 1 | use flecs_ecs::core::Entity; 2 | use hyperion_utils::Lifetime; 3 | use valence_protocol::Hand; 4 | 5 | use crate::simulation::handlers::PacketSwitchQuery; 6 | 7 | pub type EventFn = Box, &T) + 'static + Send + Sync>; 8 | 9 | pub struct CommandCompletionRequest<'a> { 10 | pub query: &'a str, 11 | pub id: i32, 12 | } 13 | 14 | unsafe impl Lifetime for CommandCompletionRequest<'_> { 15 | type WithLifetime<'a> = CommandCompletionRequest<'a>; 16 | } 17 | 18 | pub struct InteractEvent { 19 | pub hand: Hand, 20 | pub sequence: i32, 21 | } 22 | 23 | unsafe impl Lifetime for InteractEvent { 24 | type WithLifetime<'a> = Self; 25 | } 26 | 27 | pub struct PlayerJoinServer { 28 | pub username: String, 29 | pub entity: Entity, 30 | } 31 | -------------------------------------------------------------------------------- /crates/hyperion/src/storage/mod.rs: -------------------------------------------------------------------------------- 1 | mod bits; 2 | mod buf; 3 | mod db; 4 | mod event; 5 | mod thread_local; 6 | 7 | pub use bits::*; 8 | pub use buf::*; 9 | pub use db::*; 10 | pub use event::*; 11 | pub use thread_local::*; 12 | -------------------------------------------------------------------------------- /crates/hyperion/tests/collision.rs: -------------------------------------------------------------------------------- 1 | #![feature(assert_matches)] 2 | #![allow( 3 | clippy::print_stdout, 4 | reason = "the purpose of not having printing to stdout is so that tracing is used properly \ 5 | for the core libraries. These are tests, so it doesn't matter" 6 | )] 7 | 8 | use flecs_ecs::core::{EntityView, EntityViewGet, QueryBuilderImpl, SystemAPI, World, flecs, id}; 9 | use glam::Vec3; 10 | use hyperion::{ 11 | HyperionCore, 12 | simulation::{EntitySize, Owner, Pitch, Position, Velocity, Yaw, entity_kind::EntityKind}, 13 | spatial::{Spatial, SpatialModule}, 14 | }; 15 | 16 | #[test] 17 | #[ignore = "this test takes a SUPER long time to run; unsure why"] 18 | fn test_get_first_collision() { 19 | /// Function to spawn arrows at different angles 20 | fn spawn_arrow(world: &World, position: Vec3, direction: Vec3) -> EntityView<'_> { 21 | tracing::debug!("Spawning arrow at position: {position:?} with direction: {direction:?}"); 22 | world 23 | .entity() 24 | .add_enum(EntityKind::Arrow) 25 | .set(Velocity::new(direction.x, direction.y, direction.z)) 26 | .set(Position::new(position.x, position.y, position.z)) 27 | } 28 | 29 | let world = World::new(); 30 | world.import::(); 31 | world.import::(); 32 | world.import::(); 33 | world.import::(); 34 | 35 | // Make all entities have Spatial component so they are spatially indexed 36 | world 37 | .observer::() 38 | .with_enum_wildcard::() 39 | .each_entity(|entity, ()| { 40 | entity.add(id::()); 41 | }); 42 | 43 | // Create a player entity 44 | let player = world 45 | .entity_named("test_player") 46 | .add_enum(EntityKind::Player) 47 | .set(EntitySize::default()) 48 | .set(Position::new(0.0, 21.0, 0.0)) 49 | .set(Yaw::new(0.0)) 50 | .set(Pitch::new(90.0)); 51 | 52 | // Spawn arrows at different angles 53 | let arrow_velocities = [Vec3::new(0.0, -1.0, 0.0)]; 54 | 55 | let arrows: Vec> = arrow_velocities 56 | .iter() 57 | .map(|velocity| { 58 | spawn_arrow(&world, Vec3::new(0.0, 21.0, 0.0), *velocity).set(Owner::new(*player)) 59 | }) 60 | .collect(); 61 | 62 | // Progress the world to ensure that the index is updated 63 | world.progress(); 64 | 65 | // Get all entities with Position and Velocity components 66 | for arrow in &arrows { 67 | arrow.get::<(&Position, &Velocity)>(|(position, velocity)| { 68 | println!("position: {position:?}"); 69 | println!("velocity: {velocity:?}"); 70 | }); 71 | } 72 | 73 | world.progress(); 74 | 75 | // Get all entities with Position and Velocity components 76 | for arrow in &arrows { 77 | arrow.get::<(&Position, &Velocity)>(|(position, velocity)| { 78 | println!("position: {position:?}"); 79 | println!("velocity: {velocity:?}"); 80 | }); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /crates/hyperion/tests/entity.rs: -------------------------------------------------------------------------------- 1 | #![allow( 2 | clippy::print_stdout, 3 | reason = "the purpose of not having printing to stdout is so that tracing is used properly \ 4 | for the core libraries. These are tests, so it doesn't matter" 5 | )] 6 | 7 | use flecs_ecs::{ 8 | core::{EntityViewGet, World, id}, 9 | macros::Component, 10 | prelude::Module, 11 | }; 12 | use hyperion::{ 13 | simulation::{Owner, Position, Uuid, Velocity, entity_kind::EntityKind}, 14 | spatial::SpatialModule, 15 | }; 16 | 17 | #[derive(Component)] 18 | struct TestModule; 19 | 20 | impl Module for TestModule { 21 | fn module(world: &World) { 22 | world.import::(); 23 | world.import::(); 24 | } 25 | } 26 | 27 | #[test] 28 | #[ignore = "this test takes a SUPER long time to run; unsure why"] 29 | fn arrow() { 30 | let world = World::new(); 31 | world.import::(); 32 | 33 | let arrow = world.entity().add_enum(EntityKind::Arrow); 34 | let owner = world.entity(); 35 | 36 | assert!( 37 | arrow.has(id::()), 38 | "All entities should automatically be given a UUID." 39 | ); 40 | 41 | arrow.get::<&Uuid>(|uuid| { 42 | assert_ne!(uuid.0, uuid::Uuid::nil(), "The UUID should not be nil."); 43 | }); 44 | 45 | arrow 46 | .set(Velocity::new(0.0, 1.0, 0.0)) 47 | .set(Position::new(0.0, 20.0, 0.0)) 48 | .set(Owner::new(*owner)); 49 | 50 | println!("arrow = {arrow:?}"); 51 | 52 | world.progress(); 53 | 54 | arrow.get::<&Position>(|position| { 55 | // since velocity.y is 1.0, the arrow should be at y = 20.0 + (1.0 * drag - gravity) = 20.947525 56 | assert_eq!(*position, Position::new(0.0, 20.947_525, 0.0)); 57 | }); 58 | 59 | world.progress(); 60 | 61 | arrow.get::<&Position>(|position| { 62 | // gravity! drag! this is what was returned from the test but I am unsure if it actually 63 | // what we should be getting 64 | // todo: make a bunch more tests and compare to the vanilla velocity and positions 65 | assert_eq!(*position, Position::new(0.0, 21.842_705, 0.0)); 66 | }); 67 | } 68 | -------------------------------------------------------------------------------- /crates/hyperion/tests/spatial.rs: -------------------------------------------------------------------------------- 1 | #![feature(assert_matches)] 2 | #![allow( 3 | clippy::print_stdout, 4 | reason = "the purpose of not having printing to stdout is so that tracing is used properly \ 5 | for the core libraries. These are tests, so it doesn't matter" 6 | )] 7 | 8 | use std::{assert_matches::assert_matches, collections::HashSet}; 9 | 10 | use approx::assert_relative_eq; 11 | use flecs_ecs::core::{QueryBuilderImpl, SystemAPI, World, WorldGet, flecs, id}; 12 | use geometry::{aabb::Aabb, ray::Ray}; 13 | use glam::Vec3; 14 | use hyperion::{ 15 | HyperionCore, 16 | simulation::{EntitySize, Position, entity_kind::EntityKind}, 17 | spatial, 18 | }; 19 | use spatial::{Spatial, SpatialIndex, SpatialModule}; 20 | 21 | #[test] 22 | fn spatial() { 23 | let world = World::new(); 24 | world.import::(); 25 | world.import::(); 26 | 27 | // Make all entities have Spatial component so they are spatially indexed 28 | world 29 | .observer::() 30 | .with_enum_wildcard::() 31 | .each_entity(|entity, ()| { 32 | entity.add(id::()); 33 | }); 34 | 35 | let zombie = world 36 | .entity_named("test_zombie") 37 | .add_enum(EntityKind::Zombie) 38 | .set(EntitySize::default()) 39 | .set(Position::new(0.0, 0.0, 0.0)); 40 | 41 | let player = world 42 | .entity_named("test_player") 43 | .add_enum(EntityKind::Player) 44 | .set(EntitySize::default()) 45 | .set(Position::new(10.0, 0.0, 0.0)); 46 | 47 | // progress one tick to ensure that the index is updated 48 | world.progress(); 49 | 50 | world.get::<&SpatialIndex>(|spatial| { 51 | let closest = spatial 52 | .closest_to(Vec3::new(1.0, 2.0, 0.0), &world) 53 | .expect("there to be a closest entity"); 54 | assert_eq!(closest, zombie); 55 | 56 | let closest = spatial 57 | .closest_to(Vec3::new(11.0, 2.0, 0.0), &world) 58 | .expect("there to be a closest entity"); 59 | assert_eq!(closest, player); 60 | 61 | let big_aabb = Aabb::new(Vec3::new(0.0, 0.0, 0.0), Vec3::new(100.0, 100.0, 100.0)); 62 | 63 | let collisions: HashSet<_> = spatial.get_collisions(big_aabb, &world).collect(); 64 | assert!( 65 | collisions.contains(&zombie), 66 | "zombie should be in collisions" 67 | ); 68 | assert!( 69 | collisions.contains(&player), 70 | "player should be in collisions" 71 | ); 72 | 73 | let ray = Ray::from_points(Vec3::new(0.0, 0.0, 0.0), Vec3::new(1.0, 1.0, 1.0)); 74 | let (first, distance) = spatial.first_ray_collision(ray, &world).unwrap(); 75 | assert_eq!(first, zombie); 76 | assert_relative_eq!(distance.into_inner(), 0.0); 77 | 78 | let ray = Ray::from_points(Vec3::new(12.0, 0.0, 0.0), Vec3::new(13.0, 1.0, 1.0)); 79 | assert_matches!(spatial.first_ray_collision(ray, &world), None); 80 | }); 81 | } 82 | -------------------------------------------------------------------------------- /crates/simd-utils/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /crates/simd-utils/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "simd-utils" 3 | version.workspace = true 4 | edition.workspace = true 5 | authors = ["Andrew Gazelka "] 6 | readme = "README.md" 7 | publish = false 8 | 9 | [dependencies] 10 | 11 | [dev-dependencies] 12 | proptest = "1.5.0" 13 | aligned-vec = "0.6.4" 14 | 15 | [lints] 16 | workspace = true 17 | -------------------------------------------------------------------------------- /crates/simd-utils/README.md: -------------------------------------------------------------------------------- 1 | # simd-utils -------------------------------------------------------------------------------- /crates/simd-utils/proptest-regressions/lib.txt: -------------------------------------------------------------------------------- 1 | # Seeds for failure cases proptest has generated in the past. It is 2 | # automatically read and these particular cases re-run before any 3 | # novel cases are generated. 4 | # 5 | # It is recommended to check this file in to source control so that 6 | # everyone who runs the test benefits from these saved cases. 7 | cc 5bf89a88a063e4fce4a9a0fa7acdfb22bac51014468b6525b4f09c4d5364de26 # shrinks to current = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] 8 | cc cb6ec7ecdf1576b82fdb0b96a5848958e0f0c770ff70e886beb45eaed74c83a3 # shrinks to current = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] 9 | cc cd3f3b35fcb137d6d75e64730a07b688f609462029f4d77c701d29bf02037486 # shrinks to current = [0, 0, 0, 0, 0, 0, 0, 0, 386, 0, 0, 0, 0, 0, 0, 0, 0] 10 | cc 53eb6c4354595cfc01285c48548bf6aed654ead10a265bf6f214855fbba561b2 # shrinks to current = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] 11 | cc b61e48091cd302dd3112d76307d0031b55d0820463b1ec7506ddd338c196fead # shrinks to current = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] 12 | cc 3fd746d4c176ec0a96d96095c45617318283ed570eac0f9dd6468c7cd59a8093 # shrinks to current = [0, 0, 0, 1585, 0, 0, 61523, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0] 13 | -------------------------------------------------------------------------------- /crates/simd-utils/src/one_bit_positions.rs: -------------------------------------------------------------------------------- 1 | use std::iter::{FusedIterator, TrustedLen}; 2 | 3 | pub struct OneBitPositions { 4 | pub remaining: u64, 5 | } 6 | 7 | impl OneBitPositions { 8 | const fn new(number: u64) -> Self { 9 | Self { remaining: number } 10 | } 11 | } 12 | 13 | impl Iterator for OneBitPositions { 14 | type Item = usize; 15 | 16 | fn next(&mut self) -> Option { 17 | if self.remaining == 0 { 18 | None 19 | } else { 20 | // Get position of lowest set bit 21 | let pos = self.remaining.trailing_zeros(); 22 | // Clear the lowest set bit 23 | self.remaining &= self.remaining - 1; 24 | Some(pos as usize) 25 | } 26 | } 27 | 28 | fn size_hint(&self) -> (usize, Option) { 29 | let len = self.len(); 30 | (len, Some(len)) 31 | } 32 | } 33 | 34 | impl ExactSizeIterator for OneBitPositions { 35 | fn len(&self) -> usize { 36 | self.remaining.count_ones() as usize 37 | } 38 | } 39 | 40 | impl FusedIterator for OneBitPositions {} 41 | 42 | unsafe impl TrustedLen for OneBitPositions {} 43 | 44 | // Extension trait for more ergonomic usage 45 | pub trait OneBitPositionsExt { 46 | fn one_positions(self) -> OneBitPositions; 47 | } 48 | 49 | impl OneBitPositionsExt for u64 { 50 | fn one_positions(self) -> OneBitPositions { 51 | OneBitPositions::new(self) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /crates/system-order/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /crates/system-order/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "system-order" 3 | version.workspace = true 4 | edition.workspace = true 5 | authors = ["Andrew Gazelka "] 6 | readme = "README.md" 7 | publish = false 8 | 9 | [dependencies] 10 | derive_more.workspace = true 11 | flecs_ecs.workspace = true 12 | rustc-hash.workspace = true 13 | 14 | [lints] 15 | workspace = true 16 | -------------------------------------------------------------------------------- /crates/system-order/README.md: -------------------------------------------------------------------------------- 1 | # system-order -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | hyperion-proxy: 3 | image: ghcr.io/hyperion-mc/hyperion/hyperion-proxy:latest 4 | build: 5 | context: . 6 | dockerfile: Dockerfile 7 | target: hyperion-proxy 8 | ports: 9 | - "25565:25565" 10 | command: [] 11 | restart: "no" 12 | environment: 13 | - RUST_LOG=info 14 | - HYPERION_PROXY_PROXY_ADDR=0.0.0.0:25565 15 | - HYPERION_PROXY_SERVER=tag:35565 16 | networks: 17 | - proxy-network 18 | depends_on: 19 | - tag 20 | tag: 21 | image: ghcr.io/hyperion-mc/hyperion/tag:latest 22 | build: 23 | context: . 24 | dockerfile: Dockerfile 25 | target: tag 26 | ports: 27 | - "27750:27750" 28 | expose: 29 | - "35565" 30 | command: [] 31 | restart: "no" 32 | environment: 33 | - RUST_LOG=info 34 | - TAG_IP=0.0.0.0 35 | - TAG_PORT=35565 36 | networks: 37 | - proxy-network 38 | rust-mc-bot: 39 | # image: ghcr.io/hyperion-mc/hyperion/rust-mc-bot:latest 40 | build: 41 | context: . 42 | dockerfile: Dockerfile 43 | target: rust-mc-bot 44 | command: [] 45 | restart: "no" 46 | depends_on: 47 | - hyperion-proxy 48 | environment: 49 | - RUST_LOG=info 50 | - BOT_SERVER=hyperion-proxy:25565 51 | - BOT_BOT_COUNT=500 52 | - BOT_THREADS=2 53 | networks: 54 | - proxy-network 55 | profiles: 56 | - bot 57 | 58 | networks: 59 | proxy-network: 60 | driver: bridge -------------------------------------------------------------------------------- /docs/.vitepress/config.mts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vitepress' 2 | 3 | import { withMermaid } from 'vitepress-plugin-mermaid'; 4 | 5 | import footnote from 'markdown-it-footnote' 6 | 7 | // https://vitepress.dev/reference/site-config 8 | const config = defineConfig({ 9 | title: "Hyperion", 10 | description: "The most advanced Minecraft game engine built in Rust", 11 | markdown: { 12 | math: true, 13 | config: (md) => { 14 | md.use(footnote) 15 | } 16 | }, 17 | themeConfig: { 18 | // https://vitepress.dev/reference/default-theme-config 19 | nav: [ 20 | { text: 'Home', link: '/' }, 21 | { text: 'Architecture', link: '/architecture/introduction' }, 22 | { text: 'Tag', link: '/tag/introduction' }, 23 | ], 24 | 25 | sidebar: [ 26 | { 27 | text: 'Architecture', 28 | items: [ 29 | { text: 'Introduction', link: '/architecture/introduction' }, 30 | { text: 'Game Server', link: '/architecture/game-server' }, 31 | { text: 'Proxy', link: '/architecture/proxy' }, 32 | ] 33 | }, 34 | { 35 | text: 'Tag', 36 | items: [ 37 | { text: '10,000 Player PvP', link: '/tag/introduction' }, 38 | ] 39 | } 40 | ], 41 | socialLinks: [ 42 | { icon: 'github', link: 'https://github.com/hyperion-mc/hyperion' } 43 | ] 44 | } 45 | }) 46 | 47 | 48 | export default withMermaid(config); -------------------------------------------------------------------------------- /docs/.vitepress/theme/components/GithubSnippet.vue: -------------------------------------------------------------------------------- 1 | 33 | 34 | 44 | 45 | -------------------------------------------------------------------------------- /docs/.vitepress/theme/custom.css: -------------------------------------------------------------------------------- 1 | /* .vitepress/theme/custom.css */ 2 | .green-tps { 3 | color: #4ade80; /* You can change this to any green color you prefer */ 4 | } 5 | 6 | :root { 7 | --vp-font-family-mono: 'Fira Code', monospace; 8 | --vp-code-line-height: 1.35; 9 | } 10 | 11 | .medium-zoom-overlay { 12 | z-index: 999; 13 | } 14 | 15 | .medium-zoom-image { 16 | z-index: 1000; 17 | } 18 | 19 | .vp-doc ul { 20 | list-style: disc; 21 | } 22 | 23 | .vp-doc ul ul { 24 | list-style: circle; 25 | } 26 | 27 | .vp-doc ul ul ul { 28 | list-style: square; 29 | } 30 | 31 | .vp-doc ul ul ul ul { 32 | list-style: square; 33 | /* Create open square effect by adding a border and transparent background */ 34 | list-style: none; 35 | } 36 | 37 | .vp-doc ul ul ul ul li::before { 38 | content: "□"; 39 | display: inline-block; 40 | width: 1em; 41 | margin-left: -1em; 42 | } 43 | -------------------------------------------------------------------------------- /docs/.vitepress/theme/index.ts: -------------------------------------------------------------------------------- 1 | // .vitepress/theme/index.js 2 | import DefaultTheme from 'vitepress/theme' 3 | 4 | import GithubSnippet from './components/GithubSnippet.vue' 5 | import './custom.css' 6 | 7 | // import {NolebaseInlineLinkPreviewPlugin} from '@nolebase/vitepress-plugin-inline-link-preview/client'; 8 | import '@fontsource/fira-code' 9 | 10 | import { onMounted } from 'vue'; 11 | 12 | // export default DefaultTheme 13 | 14 | import mediumZoom from 'medium-zoom'; 15 | 16 | export default { 17 | ...DefaultTheme, 18 | enhanceApp({app}) { 19 | app.component('GithubSnippet', GithubSnippet) 20 | }, 21 | setup() { 22 | onMounted(() => { 23 | mediumZoom('[data-zoomable]', { background: 'var(--vp-c-bg)' }); 24 | }); 25 | }, 26 | }; -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | # https://vitepress.dev/reference/default-theme-home-page 3 | layout: home 4 | 5 | hero: 6 | name: "Hyperion" 7 | text: "The most advanced Minecraft game engine built in Rust" 8 | tagline: 170,000 players in one world at 20 TPS 9 | actions: 10 | - theme: brand 11 | text: Architecture 12 | link: /architecture/introduction 13 | - theme: alt 14 | text: 10,000 Player PvP 15 | link: /tag/introduction 16 | 17 | features: 18 | - title: Run massive events with confidence 19 | details: Built in Rust, you can be highly confident your event will not crash from memory leaks or SEGFAULTS. 20 | - title: Vertical and horizontal scalability 21 | details: In our testing I/O is the main bottleneck in massive events. As such, we made it so I/O logic can be offloaded horizontally. The actual core game server is scaled vertically. 22 | - title: Easy debugging profiling 23 | details: All tasks are easily viewable in a tracing UI. All entities are viewable and modifiable from Flecs Explorer. 24 | --- 25 | 26 | -------------------------------------------------------------------------------- /docs/tag/classes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperion-mc/hyperion/ddb109092b40bc381016e327c5b13eb0201e95a3/docs/tag/classes.png -------------------------------------------------------------------------------- /docs/tag/introduction.md: -------------------------------------------------------------------------------- 1 | # 10,000 Player PvP Event 2 | 3 | Our pilot event is a 10,000 player PvP battle to break the Guinness World Record for the largest PvP battle ever. 4 | 5 | We're partnering with [TheMisterEpic](https://www.youtube.com/channel/UCJiFgnnYpwlnadzTzhMnX_Q) to run an initial 6 | proof-of-concept event with around 2k players. Following its success, we'll host the full-scale 10,000 player PvP battle 7 | alongside numerous YouTubers and streamers. 8 | 9 | ## Inspiration 10 | 11 | - [Diep.io Tag](https://diepio.fandom.com/wiki/Tag) 12 | - Hypixel *The Pit* 13 | 14 | ## Teams 15 | 16 | When players join the game, they are assigned to a team. 17 | 18 | - Red team 19 | - Blue team 20 | - Green team 21 | - Yellow team 22 | 23 | Teams are designated by their boot color. 24 | 25 | Their goal is to gather resources and kill all other players on opposing teams to increase their XP points. 26 | 27 | When a player is killed 28 | 29 | - the killed player respawns with $1/3$ of their XP points 30 | - the killer receives $1/2$ of the XP points 31 | - this is to make sure that killing players is not inflationary; $1/3 + 1/2 < 1$. 32 | 33 | ## Classes 34 | 35 | ![classes.png](classes.png){data-zoomable} 36 | 37 | There is an upgrade tree. Everyone starts out as the stick class, which is the most basic class. 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /events/tag/Cargo.toml: -------------------------------------------------------------------------------- 1 | [dependencies] 2 | anyhow = { workspace = true } 3 | clap = { workspace = true } 4 | compact_str = { workspace = true } 5 | derive_more = { workspace = true } 6 | dotenvy = { workspace = true } 7 | envy = "0.4" 8 | fastrand = { workspace = true } 9 | flecs_ecs = { workspace = true } 10 | geometry = { workspace = true } 11 | glam = { workspace = true } 12 | hyperion = { workspace = true } 13 | hyperion-clap = { workspace = true } 14 | hyperion-genmap = { workspace = true } 15 | hyperion-gui = { workspace = true } 16 | hyperion-inventory = { workspace = true } 17 | hyperion-item = { workspace = true } 18 | hyperion-permission = { workspace = true } 19 | hyperion-proxy-module = { workspace = true } 20 | hyperion-rank-tree = { workspace = true } 21 | hyperion-respawn = { workspace = true } 22 | hyperion-scheduled = { workspace = true } 23 | hyperion-text = { workspace = true } 24 | hyperion-utils = { workspace = true } 25 | rayon = { workspace = true } 26 | roaring = { workspace = true } 27 | rustc-hash = { workspace = true } 28 | serde = { version = "1.0", features = ["derive"] } 29 | tracing = { workspace = true } 30 | tracing-subscriber = { workspace = true } 31 | uuid = { workspace = true } 32 | valence_protocol = { workspace = true } 33 | valence_server = { workspace = true } 34 | 35 | [dev-dependencies] 36 | tracing = { workspace = true, features = ["release_max_level_info"] } 37 | 38 | [lints] 39 | workspace = true 40 | 41 | [package] 42 | authors = ["Andrew Gazelka "] 43 | edition.workspace = true 44 | name = "tag" 45 | publish = false 46 | readme = "README.md" 47 | version.workspace = true 48 | 49 | [target.'cfg(not(target_os = "windows"))'.dependencies] 50 | tikv-jemallocator.workspace = true 51 | -------------------------------------------------------------------------------- /events/tag/README.md: -------------------------------------------------------------------------------- 1 | # infection -------------------------------------------------------------------------------- /events/tag/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | // Get target architecture (e.g., x86_64, aarch64, etc.) 3 | let arch = std::env::var("CARGO_CFG_TARGET_ARCH").unwrap_or_else(|_| "unknown".to_string()); 4 | 5 | // Get profile (debug/release) 6 | let profile = if cfg!(debug_assertions) { 7 | "debug" 8 | } else { 9 | "release" 10 | }; 11 | 12 | // Combine them into RUN_MODE 13 | let run_mode = format!("{arch}-{profile}"); 14 | println!("cargo:rustc-env=RUN_MODE={run_mode}"); 15 | } 16 | -------------------------------------------------------------------------------- /events/tag/src/command.rs: -------------------------------------------------------------------------------- 1 | use flecs_ecs::core::World; 2 | use hyperion_clap::{MinecraftCommand, hyperion_command::CommandRegistry}; 3 | 4 | use crate::command::{ 5 | bow::BowCommand, chest::ChestCommand, class::ClassCommand, fly::FlyCommand, gui::GuiCommand, 6 | raycast::RaycastCommand, replace::ReplaceCommand, shoot::ShootCommand, spawn::SpawnCommand, 7 | speed::SpeedCommand, vanish::VanishCommand, xp::XpCommand, 8 | }; 9 | 10 | mod bow; 11 | mod chest; 12 | mod class; 13 | mod fly; 14 | mod gui; 15 | mod raycast; 16 | mod replace; 17 | mod shoot; 18 | mod spawn; 19 | mod speed; 20 | mod vanish; 21 | mod xp; 22 | 23 | pub fn register(registry: &mut CommandRegistry, world: &World) { 24 | BowCommand::register(registry, world); 25 | ClassCommand::register(registry, world); 26 | FlyCommand::register(registry, world); 27 | GuiCommand::register(registry, world); 28 | RaycastCommand::register(registry, world); 29 | ReplaceCommand::register(registry, world); 30 | ShootCommand::register(registry, world); 31 | SpawnCommand::register(registry, world); 32 | SpeedCommand::register(registry, world); 33 | VanishCommand::register(registry, world); 34 | XpCommand::register(registry, world); 35 | ChestCommand::register(registry, world); 36 | } 37 | -------------------------------------------------------------------------------- /events/tag/src/command/bow.rs: -------------------------------------------------------------------------------- 1 | use clap::Parser; 2 | use flecs_ecs::core::{Entity, EntityView, EntityViewGet, WorldProvider}; 3 | use hyperion::{ItemKind, ItemStack}; 4 | use hyperion_clap::{CommandPermission, MinecraftCommand}; 5 | use hyperion_inventory::PlayerInventory; 6 | 7 | #[derive(Parser, CommandPermission, Debug)] 8 | #[command(name = "bow")] 9 | #[command_permission(group = "Normal")] 10 | pub struct BowCommand; 11 | 12 | impl MinecraftCommand for BowCommand { 13 | fn execute(self, system: EntityView<'_>, caller: Entity) { 14 | let world = system.world(); 15 | 16 | caller 17 | .entity_view(world) 18 | .get::<&mut PlayerInventory>(|inventory| { 19 | inventory.try_add_item(ItemStack { 20 | item: ItemKind::Bow, 21 | count: 1, 22 | nbt: None, 23 | }); 24 | 25 | inventory.try_add_item(ItemStack { 26 | item: ItemKind::Arrow, 27 | count: 64, 28 | nbt: None, 29 | }); 30 | }); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /events/tag/src/command/chest.rs: -------------------------------------------------------------------------------- 1 | use clap::Parser; 2 | use flecs_ecs::core::{Builder, Entity, EntityView, QueryAPI, WorldProvider}; 3 | use hyperion::{ 4 | ItemKind, ItemStack, 5 | simulation::{Spawn, entity_kind::EntityKind}, 6 | }; 7 | use hyperion_clap::{CommandPermission, MinecraftCommand}; 8 | use hyperion_gui::Gui; 9 | use hyperion_inventory::{Inventory, ItemSlot}; 10 | use tracing::debug; 11 | use valence_protocol::packets::play::open_screen_s2c::WindowType; 12 | 13 | #[derive(Parser, CommandPermission, Debug)] 14 | #[command(name = "chest")] 15 | #[command_permission(group = "Normal")] 16 | pub struct ChestCommand; 17 | 18 | impl MinecraftCommand for ChestCommand { 19 | fn execute(self, system: EntityView<'_>, caller: Entity) { 20 | let world = system.world(); 21 | 22 | let gui = world.query::<&Gui>().build(); 23 | let mut found = false; 24 | gui.each_iter(|_it, _, gui| { 25 | if gui.id == 28 { 26 | gui.open(system, caller); 27 | found = true; 28 | } 29 | }); 30 | 31 | if !found { 32 | debug!("Creating new GUI"); 33 | let mut gui_inventory = 34 | Inventory::new(27, "Test Chest".to_string(), WindowType::Generic9x3, false); 35 | 36 | let item = ItemStack::new(ItemKind::GoldIngot, 64, None); 37 | 38 | gui_inventory.set(13, item).unwrap(); 39 | gui_inventory 40 | .set(14, ItemStack::new(ItemKind::Diamond, 64, None)) 41 | .unwrap(); 42 | gui_inventory 43 | .set(15, ItemStack::new(ItemKind::IronIngot, 64, None)) 44 | .unwrap(); 45 | gui_inventory 46 | .set(16, ItemStack::new(ItemKind::Coal, 64, None)) 47 | .unwrap(); 48 | gui_inventory 49 | .set(17, ItemStack::new(ItemKind::Emerald, 64, None)) 50 | .unwrap(); 51 | gui_inventory 52 | .set(18, ItemStack::new(ItemKind::GoldIngot, 64, None)) 53 | .unwrap(); 54 | gui_inventory 55 | .set_slot(19, ItemSlot::new(ItemKind::Diamond, 64, None, Some(true))) 56 | .unwrap(); 57 | 58 | let gui = Gui::new(gui_inventory, &world, 28); 59 | 60 | gui.open(system, caller); 61 | // add the gui to the world 62 | world 63 | .entity() 64 | .add_enum(EntityKind::Gui) 65 | .set(gui) 66 | .enqueue(Spawn); 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /events/tag/src/command/class.rs: -------------------------------------------------------------------------------- 1 | use clap::Parser; 2 | use flecs_ecs::core::{Entity, EntityView, EntityViewGet, WorldGet, WorldProvider, id}; 3 | use hyperion::net::{Compose, ConnectionId, agnostic}; 4 | use hyperion_clap::{CommandPermission, MinecraftCommand}; 5 | use hyperion_rank_tree::{Class, Team}; 6 | 7 | #[derive(Parser, CommandPermission, Debug)] 8 | #[command(name = "class")] 9 | #[command_permission(group = "Normal")] 10 | pub struct ClassCommand { 11 | class: Class, 12 | team: Team, 13 | } 14 | impl MinecraftCommand for ClassCommand { 15 | fn execute(self, system: EntityView<'_>, caller: Entity) { 16 | let class_param = self.class; 17 | let team_param = self.team; 18 | 19 | let world = system.world(); 20 | 21 | world.get::<&Compose>(|compose| { 22 | let caller = caller.entity_view(world); 23 | caller.get::<(&ConnectionId, &mut Team, &mut Class)>(|(stream, team, class)| { 24 | if *team == team_param && *class == class_param { 25 | let chat_pkt = agnostic::chat("§cYou’re already using this class!"); 26 | 27 | compose.unicast(&chat_pkt, *stream, system).unwrap(); 28 | return; 29 | } 30 | 31 | if *team != team_param { 32 | *team = team_param; 33 | caller.modified(id::()); 34 | } 35 | 36 | if *class != class_param { 37 | *class = class_param; 38 | caller.modified(id::()); 39 | } 40 | 41 | let msg = format!("Setting rank to {class:?}"); 42 | let chat = agnostic::chat(msg); 43 | compose.unicast(&chat, *stream, system).unwrap(); 44 | }); 45 | }); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /events/tag/src/command/fly.rs: -------------------------------------------------------------------------------- 1 | use clap::Parser; 2 | use flecs_ecs::core::{Entity, EntityView, EntityViewGet, WorldGet, WorldProvider, id}; 3 | use hyperion::{ 4 | net::{Compose, ConnectionId, DataBundle, agnostic}, 5 | simulation::Flight, 6 | }; 7 | use hyperion_clap::{CommandPermission, MinecraftCommand}; 8 | 9 | #[derive(Parser, CommandPermission, Debug)] 10 | #[command(name = "fly")] 11 | #[command_permission(group = "Moderator")] 12 | pub struct FlyCommand; 13 | 14 | impl MinecraftCommand for FlyCommand { 15 | fn execute(self, system: EntityView<'_>, caller: Entity) { 16 | let world = system.world(); 17 | world.get::<&Compose>(|compose| { 18 | caller 19 | .entity_view(world) 20 | .get::<(&mut Flight, &ConnectionId)>(|(flight, stream)| { 21 | flight.allow = !flight.allow; 22 | flight.is_flying = flight.allow && flight.is_flying; 23 | caller.entity_view(world).modified(id::()); 24 | 25 | let allow_flight = flight.allow; 26 | 27 | let chat_packet = if allow_flight { 28 | agnostic::chat("§aFlying enabled") 29 | } else { 30 | agnostic::chat("§cFlying disabled") 31 | }; 32 | 33 | let mut bundle = DataBundle::new(compose, system); 34 | bundle.add_packet(&chat_packet).unwrap(); 35 | 36 | bundle.unicast(*stream).unwrap(); 37 | }); 38 | }); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /events/tag/src/command/gui.rs: -------------------------------------------------------------------------------- 1 | use clap::Parser; 2 | use flecs_ecs::core::{Builder, Entity, EntityView, QueryAPI, WorldProvider}; 3 | use hyperion::{ 4 | ItemKind, ItemStack, 5 | simulation::{Spawn, entity_kind::EntityKind}, 6 | }; 7 | use hyperion_clap::{CommandPermission, MinecraftCommand}; 8 | use hyperion_gui::Gui; 9 | use hyperion_inventory::Inventory; 10 | use tracing::debug; 11 | use valence_protocol::packets::play::{click_slot_c2s::ClickMode, open_screen_s2c::WindowType}; 12 | 13 | #[derive(Parser, CommandPermission, Debug)] 14 | #[command(name = "testgui")] 15 | #[command_permission(group = "Normal")] 16 | pub struct GuiCommand; 17 | 18 | impl MinecraftCommand for GuiCommand { 19 | fn execute(self, system: EntityView<'_>, caller: Entity) { 20 | let world = system.world(); 21 | // get a list of all the guis 22 | let gui = world.query::<&Gui>().build(); 23 | let mut found = false; 24 | gui.each_iter(|_it, _, gui| { 25 | if gui.id == 27 { 26 | gui.open(system, caller); 27 | found = true; 28 | } 29 | }); 30 | if !found { 31 | let mut gui_inventory = 32 | Inventory::new(27, "Test GUI".to_string(), WindowType::Generic9x3, true); 33 | 34 | let item = ItemStack::new(ItemKind::GoldIngot, 1, None); 35 | 36 | gui_inventory.set(13, item).unwrap(); 37 | 38 | let mut gui = Gui::new(gui_inventory, &world, 27); 39 | gui.add_command(13, |player, click_mode| match click_mode { 40 | ClickMode::Click => { 41 | debug!("Player {:?} clicked on slot 13", player); 42 | } 43 | ClickMode::DoubleClick => { 44 | debug!("Player {:?} double clicked on slot 13", player); 45 | } 46 | ClickMode::Drag => { 47 | debug!("Player {:?} dragged on slot 13", player); 48 | } 49 | ClickMode::DropKey => { 50 | debug!("Player {:?} dropped on slot 13", player); 51 | } 52 | ClickMode::Hotbar => { 53 | debug!("Player {:?} hotbar on slot 13", player); 54 | } 55 | ClickMode::ShiftClick => { 56 | debug!("Player {:?} shift clicked on slot 13", player); 57 | } 58 | ClickMode::CreativeMiddleClick => { 59 | debug!("Player {:?} creative middle clicked on slot 13", player); 60 | } 61 | }); 62 | 63 | gui.init(&world); 64 | 65 | gui.open(system, caller); 66 | 67 | world 68 | .entity() 69 | .add_enum(EntityKind::Gui) 70 | .set(gui) 71 | .enqueue(Spawn); 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /events/tag/src/command/raycast.rs: -------------------------------------------------------------------------------- 1 | use clap::Parser; 2 | use flecs_ecs::core::{Entity, EntityView, EntityViewGet, WorldProvider}; 3 | use hyperion::{ 4 | glam::Vec3, 5 | simulation::{Pitch, Position, Yaw, entity_kind::EntityKind}, 6 | spatial::get_first_collision, 7 | }; 8 | use hyperion_clap::{CommandPermission, MinecraftCommand}; 9 | use rayon::iter::Either; 10 | use tracing::debug; 11 | 12 | #[derive(Parser, CommandPermission, Debug)] 13 | #[command(name = "raycast")] 14 | #[command_permission(group = "Admin")] 15 | pub struct RaycastCommand; 16 | 17 | /// Converts Minecraft yaw and pitch angles to a direction vector 18 | /// 19 | /// # Arguments 20 | /// * `yaw` - The yaw angle in degrees (-180 to +180) 21 | /// - -180° or +180°: facing North (negative Z) 22 | /// - -90°: facing East (positive X) 23 | /// - 0°: facing South (positive Z) 24 | /// - +90°: facing West (negative X) 25 | /// * `pitch` - The pitch angle in degrees (-90 to +90) 26 | /// - -90°: looking straight up (positive Y) 27 | /// - 0°: looking horizontal 28 | /// - +90°: looking straight down (negative Y) 29 | /// 30 | /// # Returns 31 | /// A normalized Vec3 representing the look direction 32 | pub fn get_direction_from_rotation(yaw: f32, pitch: f32) -> Vec3 { 33 | // Convert angles from degrees to radians 34 | let yaw_rad = yaw.to_radians(); 35 | let pitch_rad = pitch.to_radians(); 36 | 37 | Vec3::new( 38 | -pitch_rad.cos() * yaw_rad.sin(), // x = -cos(pitch) * sin(yaw) 39 | -pitch_rad.sin(), // y = -sin(pitch) 40 | pitch_rad.cos() * yaw_rad.cos(), // z = cos(pitch) * cos(yaw) 41 | ) 42 | } 43 | 44 | impl MinecraftCommand for RaycastCommand { 45 | fn execute(self, system: EntityView<'_>, caller: Entity) { 46 | const EYE_HEIGHT: f32 = 1.62; 47 | const DISTANCE: f32 = 10.0; 48 | 49 | let world = system.world(); 50 | 51 | let ray = 52 | caller 53 | .entity_view(world) 54 | .get::<(&Position, &Yaw, &Pitch)>(|(position, yaw, pitch)| { 55 | let center = **position; 56 | 57 | let eye = center + Vec3::new(0.0, EYE_HEIGHT, 0.0); 58 | let direction = get_direction_from_rotation(**yaw, **pitch); 59 | 60 | geometry::ray::Ray::new(eye, direction) * DISTANCE 61 | }); 62 | 63 | debug!("ray = {ray:?}"); 64 | 65 | let result = get_first_collision(ray, &world, Some(caller)); 66 | 67 | match result { 68 | Some(Either::Left(entity)) => { 69 | entity 70 | .entity_view(world) 71 | .get::<(&Position, &EntityKind)>(|(position, kind)| { 72 | let position = **position; 73 | debug!("kind: {kind:?}"); 74 | debug!("position: {position:?}"); 75 | }); 76 | } 77 | Some(Either::Right(ray_collision)) => debug!("ray_collision: {ray_collision:?}"), 78 | None => debug!("no collision found"), 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /events/tag/src/command/shoot.rs: -------------------------------------------------------------------------------- 1 | use clap::Parser; 2 | use flecs_ecs::core::{Entity, EntityView, EntityViewGet, WorldProvider}; 3 | use hyperion::{ 4 | glam::Vec3, 5 | simulation::{Pitch, Position, Spawn, Uuid, Velocity, Yaw, entity_kind::EntityKind}, 6 | }; 7 | use hyperion_clap::{CommandPermission, MinecraftCommand}; 8 | use tracing::debug; 9 | 10 | #[derive(Parser, CommandPermission, Debug)] 11 | #[command(name = "shoot")] 12 | #[command_permission(group = "Normal")] 13 | pub struct ShootCommand { 14 | #[arg(help = "Initial velocity of the arrow")] 15 | velocity: f32, 16 | } 17 | 18 | impl MinecraftCommand for ShootCommand { 19 | fn execute(self, system: EntityView<'_>, caller: Entity) { 20 | const EYE_HEIGHT: f32 = 1.62; 21 | const BASE_VELOCITY: f32 = 3.0; // Base velocity multiplier for arrows 22 | 23 | let world = system.world(); 24 | 25 | caller 26 | .entity_view(world) 27 | .get::<(&Position, &Yaw, &Pitch)>(|(pos, yaw, pitch)| { 28 | // Calculate direction vector from player's rotation 29 | let direction = super::raycast::get_direction_from_rotation(**yaw, **pitch); 30 | 31 | // Spawn arrow slightly in front of player to avoid self-collision 32 | let spawn_pos = Vec3::new(pos.x, pos.y + EYE_HEIGHT, pos.z) + direction * 1.0; 33 | 34 | // Calculate velocity with base multiplier 35 | let velocity = direction * (self.velocity * BASE_VELOCITY); 36 | 37 | debug!( 38 | "Arrow velocity: ({}, {}, {})", 39 | velocity.x, velocity.y, velocity.z 40 | ); 41 | 42 | debug!( 43 | "Arrow spawn position: ({}, {}, {})", 44 | spawn_pos.x, spawn_pos.y, spawn_pos.z 45 | ); 46 | 47 | let entity_id = Uuid::new_v4(); 48 | 49 | // Create arrow entity with velocity 50 | world 51 | .entity() 52 | .add_enum(EntityKind::Arrow) 53 | .set(entity_id) 54 | .set(Position::new(spawn_pos.x, spawn_pos.y, spawn_pos.z)) 55 | .set(Velocity::new(velocity.x, velocity.y, velocity.z)) 56 | .set(Yaw::new(**yaw)) 57 | .set(Pitch::new(**pitch)) 58 | .enqueue(Spawn); 59 | }); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /events/tag/src/command/spawn.rs: -------------------------------------------------------------------------------- 1 | use clap::Parser; 2 | use flecs_ecs::{ 3 | core::{Entity, WorldProvider, id}, 4 | prelude::EntityView, 5 | }; 6 | use hyperion::{ 7 | BlockState, 8 | simulation::{ 9 | Pitch, Position, Spawn, Uuid, Velocity, Yaw, 10 | entity_kind::EntityKind, 11 | metadata::{ 12 | block_display::DisplayedBlockState, 13 | display::{Height, Width}, 14 | }, 15 | }, 16 | }; 17 | use hyperion_clap::{CommandPermission, MinecraftCommand}; 18 | 19 | use crate::FollowClosestPlayer; 20 | 21 | #[derive(Parser, CommandPermission, Debug)] 22 | #[command(name = "spawn")] 23 | #[command_permission(group = "Normal")] 24 | pub struct SpawnCommand; 25 | 26 | impl MinecraftCommand for SpawnCommand { 27 | fn execute(self, system: EntityView<'_>, _caller: Entity) { 28 | let world = system.world(); 29 | 30 | world 31 | .entity() 32 | .add_enum(EntityKind::BlockDisplay) 33 | // .set(EntityFlags::ON_FIRE) 34 | .set(Uuid::new_v4()) 35 | .set(Width::new(1.0)) 36 | .set(Height::new(1.0)) 37 | // .set(ViewRange::new(100.0)) 38 | // .add_enum(EntityKind::Zombie) 39 | .set(Position::new(0.0, 22.0, 0.0)) 40 | .set(Pitch::new(0.0)) 41 | .set(Yaw::new(0.0)) 42 | .set(Velocity::new(0.0, 0.0, 0.0)) 43 | .add(id::()) 44 | .set(DisplayedBlockState::new(BlockState::DIRT)) 45 | // .is_a(prefabs.block_display_base) 46 | .enqueue(Spawn); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /events/tag/src/command/speed.rs: -------------------------------------------------------------------------------- 1 | use clap::Parser; 2 | use flecs_ecs::core::{Entity, EntityView, EntityViewGet, WorldGet, WorldProvider}; 3 | use hyperion::{ 4 | net::{Compose, ConnectionId, agnostic}, 5 | simulation::FlyingSpeed, 6 | }; 7 | use hyperion_clap::{CommandPermission, MinecraftCommand}; 8 | 9 | #[derive(Parser, CommandPermission, Debug)] 10 | #[command(name = "speed")] 11 | #[command_permission(group = "Moderator")] 12 | pub struct SpeedCommand { 13 | amount: f32, 14 | } 15 | 16 | impl MinecraftCommand for SpeedCommand { 17 | fn execute(self, system: EntityView<'_>, caller: Entity) { 18 | let world = system.world(); 19 | let msg = format!("Setting speed to {}", self.amount); 20 | let chat = agnostic::chat(msg); 21 | 22 | world.get::<&Compose>(|compose| { 23 | caller.entity_view(world).get::<&ConnectionId>(|stream| { 24 | caller.entity_view(world).set(FlyingSpeed::new(self.amount)); 25 | compose.unicast(&chat, *stream, system).unwrap(); 26 | }); 27 | }); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /events/tag/src/command/vanish.rs: -------------------------------------------------------------------------------- 1 | use clap::Parser; 2 | use flecs_ecs::{ 3 | core::{Entity, EntityView, EntityViewGet, WorldProvider}, 4 | prelude::*, 5 | }; 6 | use hyperion::net::{Compose, ConnectionId}; 7 | use hyperion_clap::{CommandPermission, MinecraftCommand}; 8 | 9 | use crate::module::vanish::Vanished; 10 | 11 | #[derive(Parser, CommandPermission, Debug)] 12 | #[command(name = "vanish")] 13 | #[command_permission(group = "Admin")] 14 | pub struct VanishCommand; 15 | 16 | impl MinecraftCommand for VanishCommand { 17 | fn execute(self, system: EntityView<'_>, caller: Entity) { 18 | let world = system.world(); 19 | 20 | world.get::<&Compose>(|compose| { 21 | caller.entity_view(world).get::<( 22 | Option<&Vanished>, 23 | &ConnectionId, 24 | &hyperion::simulation::Name, 25 | )>(|(vanished, stream, name)| { 26 | let is_vanished = vanished.is_some_and(Vanished::is_vanished); 27 | let caller = caller.entity_view(world); 28 | if is_vanished { 29 | caller.set(Vanished::new(false)); 30 | let packet = hyperion::net::agnostic::chat(format!( 31 | "§7[Admin] §f{name} §7is now visible", 32 | )); 33 | compose.unicast(&packet, *stream, system).unwrap(); 34 | } else { 35 | caller.set(Vanished::new(true)); 36 | let packet = hyperion::net::agnostic::chat(format!( 37 | "§7[Admin] §f{name} §7is now vanished", 38 | )); 39 | compose.unicast(&packet, *stream, system).unwrap(); 40 | } 41 | }); 42 | }); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /events/tag/src/command/xp.rs: -------------------------------------------------------------------------------- 1 | use clap::Parser; 2 | use flecs_ecs::core::{Entity, EntityView, EntityViewGet, WorldProvider, id}; 3 | use hyperion::simulation::Xp; 4 | use hyperion_clap::{CommandPermission, MinecraftCommand}; 5 | 6 | #[derive(Parser, CommandPermission, Debug)] 7 | #[command(name = "xp")] 8 | #[command_permission(group = "Admin")] 9 | pub struct XpCommand { 10 | amount: u16, 11 | } 12 | 13 | impl MinecraftCommand for XpCommand { 14 | fn execute(self, system: EntityView<'_>, caller: Entity) { 15 | let Self { amount } = self; 16 | 17 | let world = system.world(); 18 | let caller = caller.entity_view(world); 19 | 20 | caller.get::<&mut Xp>(|xp| { 21 | xp.amount = amount; 22 | caller.modified(id::()); 23 | }); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /events/tag/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::net::SocketAddr; 2 | 3 | use clap::Parser; 4 | use serde::Deserialize; 5 | use tag::init_game; 6 | use tracing_subscriber::{EnvFilter, Registry, layer::SubscriberExt}; 7 | // use tracing_tracy::TracyLayer; 8 | 9 | #[cfg(not(target_env = "msvc"))] 10 | #[global_allocator] 11 | static GLOBAL: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc; 12 | 13 | /// The arguments to run the server 14 | #[derive(Parser, Deserialize, Debug)] 15 | struct Args { 16 | /// The IP address the server should listen on. Defaults to 0.0.0.0 17 | #[clap(short, long, default_value = "0.0.0.0")] 18 | #[serde(default = "default_ip")] 19 | ip: String, 20 | 21 | /// The port the server should listen on. Defaults to 25565 22 | #[clap(short, long, default_value = "35565")] 23 | #[serde(default = "default_port")] 24 | port: u16, 25 | } 26 | 27 | fn default_ip() -> String { 28 | "0.0.0.0".to_string() 29 | } 30 | 31 | const fn default_port() -> u16 { 32 | 35565 33 | } 34 | 35 | fn setup_logging() { 36 | tracing::subscriber::set_global_default( 37 | Registry::default() 38 | .with(EnvFilter::from_default_env()) 39 | // .with(TracyLayer::default()) 40 | .with( 41 | tracing_subscriber::fmt::layer() 42 | .with_target(false) 43 | .with_thread_ids(false) 44 | .with_file(true) 45 | .with_line_number(true), 46 | ), 47 | ) 48 | .expect("setup tracing subscribers"); 49 | } 50 | 51 | fn main() { 52 | dotenvy::dotenv().ok(); 53 | 54 | setup_logging(); 55 | 56 | // Try to load config from environment variables 57 | let args = match envy::prefixed("TAG_").from_env::() { 58 | Ok(args) => { 59 | tracing::info!("Loaded configuration from environment variables"); 60 | args 61 | } 62 | Err(e) => { 63 | tracing::info!( 64 | "Failed to load from environment: {}, falling back to command line arguments", 65 | e 66 | ); 67 | Args::parse() 68 | } 69 | }; 70 | 71 | let address = format!("{ip}:{port}", ip = args.ip, port = args.port); 72 | let address = address.parse::().unwrap(); 73 | 74 | init_game(address).unwrap(); 75 | } 76 | -------------------------------------------------------------------------------- /events/tag/src/module.rs: -------------------------------------------------------------------------------- 1 | pub mod attack; 2 | pub mod block; 3 | pub mod bow; 4 | pub mod chat; 5 | pub mod damage; 6 | pub mod level; 7 | pub mod regeneration; 8 | pub mod spawn; 9 | pub mod stats; 10 | pub mod vanish; 11 | -------------------------------------------------------------------------------- /events/tag/src/module/damage.rs: -------------------------------------------------------------------------------- 1 | use flecs_ecs::{ 2 | core::{EntityViewGet, QueryBuilderImpl, TermBuilderImpl, World}, 3 | macros::{Component, system}, 4 | prelude::{Module, SystemAPI}, 5 | }; 6 | use hyperion::{ 7 | net::{Compose, ConnectionId, agnostic}, 8 | simulation::{Position, event::HitGroundEvent, metadata::living_entity::Health}, 9 | storage::EventQueue, 10 | }; 11 | use hyperion_utils::EntityExt; 12 | use valence_protocol::{VarInt, packets::play}; 13 | use valence_server::ident; 14 | 15 | #[derive(Component)] 16 | pub struct DamageModule {} 17 | 18 | impl Module for DamageModule { 19 | fn module(world: &World) { 20 | system!("apply natural damages", world, &mut EventQueue($), &Compose($)) 21 | .each_iter(|it, _, (event_queue, compose)| { 22 | let world = it.world(); 23 | let system = it.system(); 24 | 25 | for event in event_queue.drain() { 26 | if event.fall_distance <= 3. { 27 | continue; 28 | } 29 | 30 | let entity = event.client.entity_view(world); 31 | // TODO account for armor/effects and gamemode 32 | let damage = event.fall_distance.floor() - 3.; 33 | 34 | if damage <= 0. { 35 | continue; 36 | } 37 | 38 | entity.get::<(&mut Health, &ConnectionId, &Position)>( 39 | |(health, connection, position)| { 40 | health.damage(damage); 41 | 42 | let pkt_damage_event = play::EntityDamageS2c { 43 | entity_id: VarInt(entity.minecraft_id()), 44 | source_cause_id: VarInt(0), 45 | source_direct_id: VarInt(0), 46 | source_type_id: VarInt(10), // 10 = fall damage 47 | source_pos: Option::None, 48 | }; 49 | 50 | let sound = agnostic::sound( 51 | if event.fall_distance > 7. { 52 | ident!("minecraft:entity.player.big_fall") 53 | } else { 54 | ident!("minecraft:entity.player.small_fall") 55 | }, 56 | **position, 57 | ) 58 | .volume(1.) 59 | .pitch(1.) 60 | .seed(fastrand::i64(..)) 61 | .build(); 62 | 63 | compose 64 | .unicast(&pkt_damage_event, *connection, system) 65 | .unwrap(); 66 | compose 67 | .broadcast_local(&sound, position.to_chunk(), system) 68 | .send() 69 | .unwrap(); 70 | }, 71 | ); 72 | } 73 | }); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /events/tag/src/module/level.rs: -------------------------------------------------------------------------------- 1 | use flecs_ecs::{ 2 | core::{ 3 | ComponentOrPairId, QueryBuilderImpl, SystemAPI, World, WorldProvider, flecs, 4 | term::TermBuilderImpl, 5 | }, 6 | macros::Component, 7 | prelude::Module, 8 | }; 9 | use hyperion::simulation::{Player, Xp}; 10 | use hyperion_inventory::PlayerInventory; 11 | use hyperion_rank_tree::{Class, Team}; 12 | use tracing::debug; 13 | 14 | use crate::MainBlockCount; 15 | 16 | #[derive(Component)] 17 | pub struct LevelModule; 18 | 19 | #[derive(Component, Default, Copy, Clone, Debug)] 20 | #[meta] 21 | pub struct UpgradedTo { 22 | pub value: u8, 23 | } 24 | 25 | impl Module for LevelModule { 26 | #[allow(clippy::excessive_nesting)] 27 | fn module(world: &World) { 28 | world.component::().meta(); 29 | world 30 | .component::() 31 | .add_trait::<(flecs::With, UpgradedTo)>(); // todo: how does this even call Default? (IndraDb) 32 | 33 | world 34 | .observer::() 42 | .term_at(5u32) 43 | .filter() 44 | .each_entity( 45 | |entity, (xp, upgraded_to, rank, team, main_block_count, inventory)| { 46 | debug!("updating level"); 47 | let new_level = xp.get_visual().level; 48 | let world = entity.world(); 49 | let level_diff = new_level - upgraded_to.value; 50 | rank.apply_inventory(*team, inventory, &world, **main_block_count, level_diff); 51 | }, 52 | ); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /events/tag/src/module/regeneration.rs: -------------------------------------------------------------------------------- 1 | use flecs_ecs::{ 2 | core::{ComponentOrPairId, QueryBuilderImpl, TermBuilderImpl, World, flecs}, 3 | macros::{Component, system}, 4 | prelude::Module, 5 | }; 6 | use hyperion::{ 7 | Prev, 8 | net::Compose, 9 | simulation::{Player, metadata::living_entity::Health}, 10 | util::TracingExt, 11 | }; 12 | use tracing::info_span; 13 | 14 | #[derive(Component)] 15 | pub struct RegenerationModule; 16 | 17 | #[derive(Component, Default, Copy, Clone, Debug)] 18 | #[meta] 19 | pub struct LastDamaged { 20 | pub tick: i64, 21 | } 22 | 23 | const MAX_HEALTH: f32 = 20.0; 24 | 25 | impl Module for RegenerationModule { 26 | #[allow(clippy::excessive_nesting)] 27 | fn module(world: &World) { 28 | world.component::().meta(); 29 | 30 | world 31 | .component::() 32 | .add_trait::<(flecs::With, LastDamaged)>(); // todo: how does this even call Default? (IndraDb) 33 | 34 | system!( 35 | "regenerate", 36 | world, 37 | &mut LastDamaged, 38 | &(Prev, Health), 39 | &mut Health, 40 | &Compose($) 41 | ) 42 | .tracing_each( 43 | info_span!("regenerate"), 44 | |(last_damaged, prev_health, health, compose)| { 45 | let current_tick = compose.global().tick; 46 | 47 | if *health < *prev_health { 48 | last_damaged.tick = current_tick; 49 | } 50 | 51 | let ticks_since_damage = current_tick - last_damaged.tick; 52 | 53 | if health.is_dead() { 54 | return; 55 | } 56 | 57 | // Calculate regeneration rate based on time since last damage 58 | let base_regen = 0.01; // Base regeneration per tick 59 | let ramp_factor = 0.0001_f32; // Increase in regeneration per tick 60 | let max_regen = 0.1; // Maximum regeneration per tick 61 | 62 | let regen_rate = ramp_factor 63 | .mul_add(ticks_since_damage as f32, base_regen) 64 | .min(max_regen); 65 | 66 | // Apply regeneration, capped at max health 67 | health.heal(regen_rate); 68 | **health = health.min(MAX_HEALTH); 69 | }, 70 | ); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /events/tag/src/module/stats.rs: -------------------------------------------------------------------------------- 1 | use flecs_ecs::{ 2 | core::{QueryBuilderImpl, SystemAPI, TermBuilderImpl, World}, 3 | macros::{Component, system}, 4 | prelude::Module, 5 | }; 6 | use hyperion::{ 7 | net::Compose, 8 | valence_protocol::{packets::play, text::IntoText}, 9 | }; 10 | use tracing::info_span; 11 | 12 | #[derive(Component)] 13 | pub struct StatsModule; 14 | 15 | impl Module for StatsModule { 16 | #[allow(clippy::excessive_nesting)] 17 | fn module(world: &World) { 18 | let mode = env!("RUN_MODE"); 19 | 20 | let mut tick_times = Vec::with_capacity(20 * 60); // 20 ticks per second, 60 seconds 21 | let mut last_frame_time_total = 0.0; 22 | 23 | system!("stats", world, &Compose($)).each_iter(move |it, _, compose| { 24 | let span = info_span!("stats"); 25 | let _enter = span.enter(); 26 | let system = it.system(); 27 | let world = it.world(); 28 | let player_count = compose 29 | .global() 30 | .player_count 31 | .load(std::sync::atomic::Ordering::Relaxed); 32 | 33 | let info = world.info(); 34 | let current_frame_time_total = info.frame_time_total; 35 | 36 | let ms_per_tick = (current_frame_time_total - last_frame_time_total) * 1000.0; 37 | last_frame_time_total = current_frame_time_total; 38 | 39 | tick_times.push(ms_per_tick); 40 | if tick_times.len() > 20 * 60 { 41 | tick_times.remove(0); 42 | } 43 | 44 | let avg_s05 = tick_times.iter().rev().take(20 * 5).sum::() / (20.0 * 5.0); 45 | let avg_s15 = tick_times.iter().rev().take(20 * 15).sum::() / (20.0 * 15.0); 46 | let avg_s60 = tick_times.iter().sum::() / tick_times.len() as f32; 47 | 48 | let title = format!( 49 | "§b{mode}§r\n§aµ/5s: {avg_s05:.2} ms §r| §eµ/15s: {avg_s15:.2} ms §r| §cµ/1m: \ 50 | {avg_s60:.2} ms" 51 | ); 52 | 53 | let footer = format!("§d§l{player_count} players online"); 54 | 55 | let pkt = play::PlayerListHeaderS2c { 56 | header: title.into_cow_text(), 57 | footer: footer.into_cow_text(), 58 | }; 59 | 60 | compose.broadcast(&pkt, system).send().unwrap(); 61 | }); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /events/tag/src/module/vanish.rs: -------------------------------------------------------------------------------- 1 | use flecs_ecs::{core::World, prelude::*}; 2 | use hyperion::{ 3 | net::{Compose, ConnectionId}, 4 | simulation::{Uuid, metadata::entity::EntityFlags}, 5 | }; 6 | use valence_protocol::packets::play::{self, player_list_s2c::PlayerListActions}; 7 | use valence_server::GameMode; 8 | 9 | #[derive(Component)] 10 | pub struct VanishModule; 11 | 12 | #[derive(Default, Component, Debug)] 13 | pub struct Vanished(pub bool); 14 | 15 | impl Vanished { 16 | #[must_use] 17 | pub const fn new(is_vanished: bool) -> Self { 18 | Self(is_vanished) 19 | } 20 | 21 | #[must_use] 22 | pub const fn is_vanished(&self) -> bool { 23 | self.0 24 | } 25 | } 26 | 27 | impl Module for VanishModule { 28 | fn module(world: &World) { 29 | world.component::(); 30 | 31 | system!( 32 | "vanish_sync", 33 | world, 34 | &Compose($), 35 | &ConnectionId, 36 | &Vanished, 37 | &Uuid, 38 | ) 39 | .kind(id::()) 40 | .each_iter(move |it, row, (compose, _connection_id, vanished, uuid)| { 41 | let entity = it.entity(row).expect("row must be in bounds"); 42 | let system = it.system(); 43 | let world = it.world(); 44 | 45 | if vanished.is_vanished() { 46 | // Remove from player list and make them invisible 47 | let remove_packet = play::PlayerListS2c { 48 | actions: PlayerListActions::new() 49 | .with_update_listed(true) 50 | .with_update_game_mode(true), 51 | entries: vec![play::player_list_s2c::PlayerListEntry { 52 | player_uuid: uuid.0, 53 | listed: false, 54 | game_mode: GameMode::Survival, 55 | ..Default::default() 56 | }] 57 | .into(), 58 | }; 59 | compose.broadcast(&remove_packet, system).send().unwrap(); 60 | 61 | // Set entity flags to make them invisible 62 | let flags = EntityFlags::INVISIBLE; 63 | entity.entity_view(world).set(flags); 64 | } else { 65 | // Add back to player list and make them visible 66 | let add_packet = play::PlayerListS2c { 67 | actions: PlayerListActions::new() 68 | .with_update_listed(true) 69 | .with_update_game_mode(true), 70 | entries: vec![play::player_list_s2c::PlayerListEntry { 71 | player_uuid: uuid.0, 72 | listed: true, 73 | game_mode: GameMode::Survival, 74 | ..Default::default() 75 | }] 76 | .into(), 77 | }; 78 | compose.broadcast(&add_packet, system).send().unwrap(); 79 | 80 | // Clear invisible flag 81 | let flags = EntityFlags::default(); 82 | entity.entity_view(world).set(flags); 83 | } 84 | }); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "nixpkgs": { 4 | "locked": { 5 | "lastModified": 1739866667, 6 | "narHash": "sha256-EO1ygNKZlsAC9avfcwHkKGMsmipUk1Uc0TbrEZpkn64=", 7 | "owner": "NixOS", 8 | "repo": "nixpkgs", 9 | "rev": "73cf49b8ad837ade2de76f87eb53fc85ed5d4680", 10 | "type": "github" 11 | }, 12 | "original": { 13 | "owner": "NixOS", 14 | "ref": "nixos-unstable", 15 | "repo": "nixpkgs", 16 | "type": "github" 17 | } 18 | }, 19 | "root": { 20 | "inputs": { 21 | "nixpkgs": "nixpkgs", 22 | "rust-overlay": "rust-overlay" 23 | } 24 | }, 25 | "rust-overlay": { 26 | "inputs": { 27 | "nixpkgs": [ 28 | "nixpkgs" 29 | ] 30 | }, 31 | "locked": { 32 | "lastModified": 1740191166, 33 | "narHash": "sha256-WqRxO1Afx8jPYG4CKwkvDFWFvDLCwCd4mxb97hFGYPg=", 34 | "owner": "oxalica", 35 | "repo": "rust-overlay", 36 | "rev": "74a3fb71b0cc67376ab9e7c31abcd68c813fc226", 37 | "type": "github" 38 | }, 39 | "original": { 40 | "owner": "oxalica", 41 | "repo": "rust-overlay", 42 | "type": "github" 43 | } 44 | } 45 | }, 46 | "root": "root", 47 | "version": 7 48 | } 49 | -------------------------------------------------------------------------------- /libvoidstar.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperion-mc/hyperion/ddb109092b40bc381016e327c5b13eb0201e95a3/libvoidstar.so -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "docs:dev": "vitepress dev docs", 4 | "docs:build": "vitepress build docs", 5 | "docs:preview": "vitepress preview docs" 6 | }, 7 | "devDependencies": { 8 | "@braintree/sanitize-url": "^7.1.1", 9 | "@nolebase/vitepress-plugin-inline-link-preview": "^2.14.0", 10 | "cytoscape": "^3.31.0", 11 | "cytoscape-cose-bilkent": "^4.1.0", 12 | "dayjs": "^1.11.13", 13 | "debug": "^4.4.0", 14 | "markdown-it-footnote": "^4.0.0", 15 | "markdown-it-mathjax3": "^4.3.2", 16 | "medium-zoom": "^1.1.0", 17 | "mermaid": "^11.4.1", 18 | "vitepress": "^1.6.3", 19 | "vitepress-plugin-mermaid": "^2.0.17" 20 | }, 21 | "dependencies": { 22 | "@fontsource/fira-code": "^5.1.1" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "nightly-2025-02-22" 3 | components = ["rustfmt", "clippy"] 4 | profile = "minimal" 5 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | combine_control_expr = true 2 | comment_width = 100 # https://lkml.org/lkml/2020/5/29/1038 3 | condense_wildcard_suffixes = true 4 | control_brace_style = "AlwaysSameLine" 5 | edition = "2024" 6 | format_code_in_doc_comments = true 7 | format_macro_bodies = true 8 | format_macro_matchers = true 9 | format_strings = true 10 | group_imports = "StdExternalCrate" 11 | imports_granularity = "Crate" 12 | merge_derives = false 13 | newline_style = "Unix" 14 | normalize_comments = true 15 | normalize_doc_attributes = true 16 | overflow_delimited_expr = true 17 | reorder_impl_items = true 18 | reorder_imports = true 19 | unstable_features = true 20 | 21 | # wrap_comments = true 22 | -------------------------------------------------------------------------------- /tools/antithesis-bot/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "antithesis-bot" 3 | version.workspace = true 4 | authors = ["Andrew Gazelka "] 5 | edition.workspace = true 6 | 7 | [dependencies] 8 | antithesis_sdk.workspace = true 9 | bytes = "1.9.0" 10 | dotenvy.workspace = true 11 | envy.workspace = true 12 | eyre.workspace = true 13 | regex = { workspace = true } 14 | serde = { workspace = true, features = ["derive"] } 15 | tokio = { workspace = true, features = ["full"] } 16 | tracing-subscriber.workspace = true 17 | tracing.workspace = true 18 | valence_protocol.workspace = true 19 | 20 | [lints] 21 | workspace = true -------------------------------------------------------------------------------- /tools/antithesis-bot/src/lib.rs: -------------------------------------------------------------------------------- 1 | use antithesis_sdk::serde_json::json; 2 | use serde::Deserialize; 3 | 4 | mod bot; 5 | 6 | #[derive(Deserialize, Debug)] 7 | pub struct LaunchArguments { 8 | address: String, 9 | 10 | #[serde(default = "default_bot_count")] 11 | bot_count: u32, 12 | } 13 | 14 | const fn default_bot_count() -> u32 { 15 | 1 16 | } 17 | 18 | pub async fn start(args: LaunchArguments) -> eyre::Result<()> { 19 | const UNUSUALLY_HIGH_BOT_THRESHOLD: u32 = 1_000; 20 | 21 | antithesis_sdk::antithesis_init(); 22 | 23 | let setup_complete = json!({ 24 | "pls_work": "plssssssss (1)" 25 | }); 26 | 27 | antithesis_sdk::lifecycle::setup_complete(&setup_complete); 28 | 29 | tracing::info!("args = {args:?}"); 30 | 31 | let LaunchArguments { address, bot_count } = args; 32 | 33 | if bot_count > UNUSUALLY_HIGH_BOT_THRESHOLD { 34 | tracing::warn!("bot_count {bot_count} is unusually high. This may cause issues."); 35 | } 36 | 37 | for _ in 0..bot_count { 38 | bot::launch(&address).await?; 39 | } 40 | 41 | Ok(()) 42 | } 43 | -------------------------------------------------------------------------------- /tools/antithesis-bot/src/main.rs: -------------------------------------------------------------------------------- 1 | use antithesis_bot::LaunchArguments; 2 | use tracing::trace; 3 | 4 | #[tokio::main] 5 | async fn main() -> eyre::Result<()> { 6 | tracing_subscriber::fmt::init(); 7 | if let Err(e) = dotenvy::dotenv() { 8 | trace!("Failed to load .env file: {}", e); 9 | } 10 | 11 | // Deserialize environment variables into the struct 12 | let args: LaunchArguments = envy::from_env()?; 13 | 14 | antithesis_bot::start(args).await?; 15 | 16 | Ok(()) 17 | } 18 | -------------------------------------------------------------------------------- /tools/packet-inspector/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "packet-inspector" 3 | description = "A simple Minecraft proxy for inspecting packets." 4 | version.workspace = true 5 | edition.workspace = true 6 | repository.workspace = true 7 | documentation.workspace = true 8 | license.workspace = true 9 | publish = false 10 | 11 | [lints] 12 | workspace = true 13 | 14 | [dependencies] 15 | valence_protocol = { workspace = true, features = ["compression"] } 16 | anyhow.workspace = true 17 | bytes.workspace = true 18 | flate2.workspace = true 19 | flume.workspace = true 20 | tokio = { workspace = true, features = ["full"] } 21 | tracing.workspace = true 22 | time = { workspace = true, features = ["local-offset"] } 23 | egui.workspace = true 24 | eframe = { workspace = true, features = [ 25 | "persistence", 26 | "wgpu", 27 | ] } 28 | egui_dock = { workspace = true, features = ["serde"] } 29 | itertools.workspace = true 30 | syntect = { workspace = true, default-features = false, features = [ 31 | "default-fancy", 32 | ] } 33 | serde = { workspace = true, features = ["derive"] } 34 | 35 | [build-dependencies] 36 | anyhow.workspace = true 37 | syn = { workspace = true, features = ["full"] } 38 | valence_build_utils.workspace = true 39 | quote = { workspace = true, features = ["proc-macro"] } 40 | serde_json = { workspace = true, features = ["raw_value"] } 41 | proc-macro2.workspace = true 42 | serde = { workspace = true, features = ["derive"] } 43 | -------------------------------------------------------------------------------- /tools/packet-inspector/README.md: -------------------------------------------------------------------------------- 1 | # Packet Inspector 2 | 3 | ![packet inspector screenshot](https://raw.githubusercontent.com/valence-rs/valence/main/assets/packet-inspector.png) 4 | 5 | The packet inspector is a Minecraft proxy for viewing the contents of packets as 6 | they are sent/received. It uses Valence's protocol facilities to display packet 7 | contents. This was made for three purposes: 8 | 9 | - Check that packets between Valence and client are matching your expectations. 10 | - Check that packets between vanilla server and client are parsed correctly by 11 | Valence. 12 | - Understand how the protocol works between the vanilla server and client. 13 | 14 | # Usage 15 | 16 | Firstly, we should have a server running that we're going to be 17 | proxying/inspecting. 18 | 19 | ```sh 20 | cargo r -r --example game_of_life 21 | ``` 22 | 23 | Next up, we need to run the proxy server, To launch in a GUI environment, simply run `packet_inspector`. 24 | 25 | ```sh 26 | cargo r -r -p packet_inspector 27 | ``` 28 | 29 | Then click the "Start Listening" button in the top left of the UI. 30 | 31 | The client can now connect to `localhost:25566`. You should see packets streaming in on the GUI. 32 | 33 | ## Quick start with Vanilla Server via Docker 34 | 35 | Start the server 36 | 37 | ```sh 38 | docker run -e EULA=TRUE -e ONLINE_MODE=false -e ANNOUNCE_PLAYER_ACHIEVEMENTS=false -e GENERATE_STRUCTURES=false -e SPAWN_ANIMALS=false -e SPAWN_MONSTERS=false -e SPAWN_NPCS=false -e SPAWN_PROTECTION=0 -e VIEW_DISTANCE=16 -e MODE=creative -e LEVEL_TYPE=flat -e RCON_CMDS_STARTUP="gamerule doWeatherCycle false" -d -p 25565:25565 --name mc itzg/minecraft-server 39 | ``` 40 | 41 | View server logs 42 | 43 | ```sh 44 | docker logs -f mc 45 | ``` 46 | 47 | Server Rcon 48 | 49 | ```sh 50 | docker exec -i mc rcon-cli 51 | ``` 52 | 53 | In a separate terminal, start the packet inspector. 54 | 55 | ```sh 56 | cargo r -r -p packet_inspector --no-default-features --features cli -- 127.0.0.1:25566 127.0.0.1:25565 57 | ``` 58 | 59 | Open Minecraft and connect to `localhost:25566`. 60 | 61 | Clean up 62 | 63 | ``` 64 | docker stop mc 65 | docker rm mc 66 | ``` 67 | -------------------------------------------------------------------------------- /tools/packet-inspector/src/app/connection.rs: -------------------------------------------------------------------------------- 1 | use super::{SharedState, Tab, View}; 2 | use crate::shared_state::Event; 3 | 4 | pub struct Connection; 5 | 6 | impl Tab for Connection { 7 | fn new() -> Self { 8 | Self {} 9 | } 10 | 11 | fn name(&self) -> &'static str { 12 | "Connection" 13 | } 14 | } 15 | 16 | impl View for Connection { 17 | fn ui(&mut self, ui: &mut egui::Ui, state: &mut SharedState) { 18 | ui.label("Listener Address"); 19 | 20 | if state.is_listening { 21 | ui.text_edit_singleline(&mut state.listener_addr.clone()); 22 | ui.label("Server Address"); 23 | ui.text_edit_singleline(&mut state.server_addr.clone()); 24 | 25 | ui.horizontal(|ui| { 26 | if ui.button("Stop Listening").clicked() { 27 | state.send_event(Event::StopListening); 28 | } 29 | ui.checkbox(&mut state.autostart, "Autostart"); 30 | }); 31 | } else { 32 | ui.label("Listener Address"); 33 | ui.text_edit_singleline(&mut state.listener_addr); 34 | ui.label("Server Address"); 35 | ui.text_edit_singleline(&mut state.server_addr); 36 | ui.horizontal(|ui| { 37 | if ui.button("Start Listening").clicked() { 38 | state.send_event(Event::StartListening); 39 | } 40 | ui.checkbox(&mut state.autostart, "Autostart"); 41 | }); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /tools/packet-inspector/src/app/hex_viewer.rs: -------------------------------------------------------------------------------- 1 | use std::io::Read; 2 | 3 | use super::{SharedState, Tab, View}; 4 | 5 | pub struct HexView; 6 | 7 | impl Tab for HexView { 8 | fn new() -> Self { 9 | Self {} 10 | } 11 | 12 | fn name(&self) -> &'static str { 13 | "Hex Viewer" 14 | } 15 | } 16 | 17 | impl View for HexView { 18 | fn ui(&mut self, ui: &mut egui::Ui, state: &mut SharedState) { 19 | let mut buf = [0_u8; 16]; 20 | let mut count = 0; 21 | 22 | let packets = state.packets.read().unwrap(); 23 | let Some(packet_index) = state.selected_packet else { 24 | return; 25 | }; 26 | 27 | let bytes = &packets[packet_index].data.as_ref().unwrap(); 28 | let mut file = &(*bytes).clone()[..]; 29 | 30 | egui::Grid::new("hex_grid") 31 | .spacing([4.0, 1.5]) 32 | .striped(true) 33 | .min_col_width(0.0) 34 | .show(ui, |ui| { 35 | ui.label(" "); 36 | for i in 0..16 { 37 | ui.label(format!("{i:02X}")); 38 | } 39 | ui.end_row(); 40 | loop { 41 | let bytes_read = file.read(&mut buf).unwrap(); 42 | if bytes_read == 0 { 43 | break; 44 | } 45 | 46 | ui.label(format!("{count:08X}")); 47 | let text_color = if ui.style().visuals.dark_mode { 48 | egui::Color32::from_gray(255) 49 | } else { 50 | egui::Color32::from_gray(0) 51 | }; 52 | for b in buf.iter().take(bytes_read) { 53 | ui.colored_label(text_color, format!("{b:02X}")); 54 | } 55 | for _ in 0..16 - bytes_read { 56 | ui.label(" "); 57 | } 58 | ui.label(" "); 59 | for b in buf.iter().take(bytes_read) { 60 | if *b >= 0x20 && *b <= 0x7e { 61 | ui.label(format!("{}", *b as char)); 62 | } else { 63 | ui.label("."); 64 | } 65 | } 66 | 67 | ui.end_row(); 68 | count += bytes_read; 69 | } 70 | }); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /tools/packet-inspector/src/main.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] 2 | #![expect( 3 | clippy::significant_drop_tightening, 4 | reason = "todo: we should double check no significant drop tightening" 5 | )] 6 | 7 | use egui::ViewportBuilder; 8 | 9 | mod tri_checkbox; 10 | 11 | mod app; 12 | mod shared_state; 13 | 14 | #[tokio::main] 15 | async fn main() -> Result<(), Box> { 16 | let native_options = eframe::NativeOptions { 17 | viewport: ViewportBuilder::default().with_inner_size(egui::Vec2::new(1024.0, 768.0)), 18 | ..Default::default() 19 | }; 20 | 21 | eframe::run_native( 22 | "Hyperion Packet Inspector", 23 | native_options, 24 | Box::new(move |cc| { 25 | let gui_app = app::GuiApp::new(cc); 26 | 27 | Ok(Box::new(gui_app)) 28 | }), 29 | )?; 30 | 31 | Ok(()) 32 | } 33 | -------------------------------------------------------------------------------- /tools/packet-inspector/src/main_cli.rs: -------------------------------------------------------------------------------- 1 | use clap::Parser; 2 | use packet_inspector::Packet; 3 | use packet_inspector::Proxy; 4 | use packet_inspector::ProxyLog; 5 | use packet_inspector::DisconnectionReason; 6 | use std::net::SocketAddr; 7 | use tracing::Level; 8 | 9 | #[derive(Parser, Clone, Debug)] 10 | #[clap(author, version, about)] 11 | struct CliArgs { 12 | /// The socket address to listen for connections on. This is the address clients should connect to 13 | listener_addr: SocketAddr, 14 | /// The socket address the proxy will connect to. This is the address of the server 15 | server_addr: SocketAddr, 16 | } 17 | 18 | #[tokio::main] 19 | async fn main() -> anyhow::Result<()> { 20 | tracing_subscriber::fmt() 21 | .with_max_level(Level::TRACE) 22 | .init(); 23 | 24 | let args = CliArgs::parse(); 25 | 26 | let proxy = Proxy::start(args.listener_addr, args.server_addr).await?; 27 | let receiver = proxy.subscribe().await; 28 | 29 | tokio::spawn(async move { 30 | proxy.main_task.await??; 31 | Ok::<(), anyhow::Error>(()) 32 | }); 33 | 34 | // consumer 35 | tokio::spawn(async move { 36 | while let Ok(packet) = receiver.recv_async().await { 37 | log(&packet); 38 | } 39 | }); 40 | 41 | tokio::spawn(async move { 42 | loop { 43 | let next = proxy.logs_rx.recv_async().await?; 44 | match next { 45 | ProxyLog::ClientConnected(addr) => { 46 | tracing::trace!("Accepted a new client {addr}."); 47 | } 48 | ProxyLog::ClientDisconnected(addr, DisconnectionReason::Error(_)) => { 49 | tracing::trace!("Client {addr} disconnected."); 50 | } 51 | ProxyLog::ClientDisconnected(addr, DisconnectionReason::OnlineModeRequired) => { 52 | tracing::error!( 53 | "Client {addr} was disconnected due to a server encryption request. \ 54 | The packet inspector does not support online mode." 55 | ); 56 | } 57 | } 58 | } 59 | 60 | #[allow(unreachable_code)] 61 | Ok::<(), anyhow::Error>(()) 62 | }); 63 | 64 | tokio::signal::ctrl_c().await.unwrap(); 65 | 66 | Ok(()) 67 | } 68 | 69 | fn log(packet: &Packet) { 70 | tracing::debug!( 71 | "{:?} -> [{:?}] 0x{:0>2X} \"{}\" {:?}", 72 | packet.side, 73 | packet.state, 74 | packet.id, 75 | packet.name, 76 | truncated(format!("{:?}", packet.data), 512) 77 | ); 78 | } 79 | 80 | fn truncated(string: String, max_len: usize) -> String { 81 | if string.len() > max_len { 82 | format!("{}...", &string[..max_len]) 83 | } else { 84 | string 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /tools/rust-mc-bot/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | 3 | -------------------------------------------------------------------------------- /tools/rust-mc-bot/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rust-mc-bot" 3 | version.workspace = true 4 | authors = [ 5 | "Eoghanmc22 ", 6 | "Andrew Gazelka ", 7 | ] 8 | edition.workspace = true 9 | 10 | [dependencies] 11 | anyhow.workspace = true 12 | libdeflater.workspace = true 13 | mio.workspace = true 14 | num_cpus.workspace = true 15 | rand.workspace = true 16 | tracing = { workspace = true } 17 | tracing-subscriber = { workspace = true } 18 | serde = { version = "1.0", features = ["derive"] } 19 | envy = "0.4" 20 | dotenvy = "0.15" 21 | 22 | [lints] 23 | workspace = true 24 | -------------------------------------------------------------------------------- /tools/rust-mc-bot/README.md: -------------------------------------------------------------------------------- 1 | ## Disclaimer 2 | 3 | This should **ONLY** be used test your own server. We do not endorse the use of this for any other purposes than testing your own infrastructure. 4 | 5 | Please be aware that attempting to execute this with an external server as a target can be seen as **illegal** as it simulates a layer 7 DoS (denial-of-service) attack, which is against the law in most countries. -------------------------------------------------------------------------------- /tools/rust-mc-bot/src/states/login.rs: -------------------------------------------------------------------------------- 1 | use crate::{Bot, Compression, packet_utils::Buf}; 2 | 3 | // c2s 4 | pub fn write_handshake_packet( 5 | protocol_version: u32, 6 | server_address: &str, 7 | server_port: u16, 8 | next_state: u32, 9 | ) -> Buf { 10 | let mut buf = Buf::with_length((1 + 4 + server_address.len() + 2 + 4) as u32); 11 | buf.write_packet_id(0x00); 12 | 13 | buf.write_var_u32(protocol_version); 14 | buf.write_sized_str(server_address); 15 | buf.write_u16(server_port); 16 | buf.write_var_u32(next_state); 17 | 18 | buf 19 | } 20 | 21 | pub fn write_login_start_packet(username: &str) -> Buf { 22 | let mut buf = Buf::with_length(1 + username.len() as u32); 23 | buf.write_packet_id(0x00); 24 | 25 | buf.write_sized_str(username); 26 | buf.write_bool(false); 27 | 28 | buf 29 | } 30 | 31 | // s2c 32 | 33 | // 0x02 34 | pub fn process_login_success_packet( 35 | buffer: &mut Buf, 36 | bot: &mut Bot, 37 | _compression: &mut Compression, 38 | ) { 39 | let _uuid = buffer.read_u128(); 40 | let _name = buffer.read_sized_string(); 41 | let _properties = buffer.read_var_u32(); 42 | 43 | bot.state = 2; 44 | } 45 | 46 | // 0x03 47 | pub fn process_set_compression_packet( 48 | buf: &mut Buf, 49 | bot: &mut Bot, 50 | _compression: &mut Compression, 51 | ) { 52 | bot.compression_threshold = buf.read_var_u32().0 as i32; 53 | } 54 | -------------------------------------------------------------------------------- /tools/rust-mc-bot/src/states/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod login; 2 | pub mod play; 3 | pub mod status; 4 | -------------------------------------------------------------------------------- /tools/rust-mc-bot/src/states/status.rs: -------------------------------------------------------------------------------- 1 | use crate::{Bot, Compression, packet_utils::Buf}; 2 | 3 | pub fn process_status_response(buffer: &mut Buf, _bot: &mut Bot, _compression: &mut Compression) { 4 | let server_response = buffer.read_sized_string(); 5 | tracing::info!("got response {server_response}"); 6 | } 7 | 8 | pub fn process_pong(buffer: &mut Buf, _bot: &mut Bot, _compression: &mut Compression) { 9 | let payload = buffer.read_sized_string(); 10 | tracing::info!("got pong {payload}"); 11 | } 12 | --------------------------------------------------------------------------------