Compare commits

...

26 Commits

Author SHA1 Message Date
328ed38a1a Fix linter errors 2025-04-27 13:21:46 +01:00
c5c14a3424 Fix just book 2025-03-19 13:39:49 +00:00
dc85971315 Fix extensions breaking if a non extension is in the extensions folder 2025-03-19 00:38:07 +00:00
25be06eb76 Disable rustdoc action 2025-03-18 23:37:19 +00:00
3260862bb4 Ffs 2 2025-03-18 23:33:44 +00:00
cf078b6c28 Ffs 2025-03-18 23:28:23 +00:00
65a44a3254 Oh no 2025-03-18 23:20:15 +00:00
26a679e9fa Update version 2025-03-18 23:17:39 +00:00
f62945a247 Add a book 2025-03-18 23:14:53 +00:00
f257027af1 Fix player allocation (lma0) 2025-03-18 21:14:25 +00:00
7b4f6239ce Add just support 2025-03-17 19:14:13 +00:00
4182db1506 Add config defaults
Anything exposed to the user probably should be robust to shit input
2025-03-17 16:24:54 +00:00
c6976db04c Fix tomorrow lmao 2025-03-17 01:00:58 +00:00
488712b71c Add config files (and refactor parts of RTE) 2025-03-17 00:38:45 +00:00
fb48a3c0ba
Update README.md 2025-03-09 11:06:21 +00:00
61f6cca295 Catastrophically bad bug discovered, string write was writing zeroes instead of spaces (null terminators laugh in the face of god when he farts) 2025-03-09 00:53:56 +00:00
cc3f159927
Update LICENSE.md 2025-03-09 00:48:53 +00:00
e14205bcca Add license, refine some code, remove credits because file was heavily modified 2025-03-09 00:47:29 +00:00
0591726722 The start of unit testing 2025-03-08 20:19:05 +00:00
f624a75ecf Giyaaaaaat 2025-03-08 19:28:51 +00:00
8847811321 Github actions is grilling me rn 2025-03-08 19:08:43 +00:00
a12d7bbdf6 AAAAAAAH 2025-03-08 18:59:13 +00:00
f41c27768f AAAAAH 2025-03-08 18:59:13 +00:00
22eb8328e0 AAAAAH 2025-03-08 18:59:13 +00:00
73b9265374 Grr 2025-03-08 18:59:13 +00:00
6eebe7ab21
Update README.md 2025-03-08 18:42:33 +00:00
40 changed files with 1043 additions and 93 deletions

1
.actrc
View File

@ -1 +0,0 @@
-P ubuntu-latest=ghcr.io/catthehacker/ubuntu:rust-latest

View File

@ -16,13 +16,11 @@ jobs:
uses: actions/checkout@v2
- name: Install nightly toolchain
uses: actions-rs/toolchain@v1
uses: dtolnay/rust-toolchain@stable
with:
profile: minimal
toolchain: nightly
override: true
- uses: Swatinem/rust-cache@v1
- uses: Swatinem/rust-cache@v2
- name: Run cargo check
uses: actions-rs/cargo@v1
@ -41,13 +39,11 @@ jobs:
uses: actions/checkout@v2
- name: Install nightly toolchain
uses: actions-rs/toolchain@v1
uses: dtolnay/rust-toolchain@stable
with:
profile: minimal
toolchain: ${{ matrix.rust }}
override: true
toolchain: nightly
- uses: Swatinem/rust-cache@v1
- uses: Swatinem/rust-cache@v2
- name: Run cargo test
uses: actions-rs/cargo@v1
@ -65,14 +61,12 @@ jobs:
submodules: true
- name: Install nightly toolchain
uses: actions-rs/toolchain@v1
uses: dtolnay/rust-toolchain@stable
with:
profile: minimal
toolchain: nightly
override: true
components: rustfmt, clippy
- uses: Swatinem/rust-cache@v1
- uses: Swatinem/rust-cache@v2
- name: Run cargo fmt
uses: actions-rs/cargo@v1
@ -86,7 +80,6 @@ jobs:
command: clippy
args: -- -D warnings
# This package does not provide docs
# - name: Run rustdoc lints
# uses: actions-rs/cargo@v1
# env:

View File

@ -12,7 +12,7 @@ env:
BIN_NAME: mcrizzledizzle
PROJECT_NAME: mcrizzledizzle
REPO_NAME: illegitimate-egg/mcrizzledizzle
BREW_TAP: jondot/homebrew-tap
# BREW_TAP: jondot/homebrew-tap
jobs:
dist:
@ -24,12 +24,12 @@ jobs:
build: [x86_64-linux, aarch64-linux, x86_64-macos, x86_64-windows] #, x86_64-win-gnu, win32-msvc
include:
- build: x86_64-linux
os: ubuntu-20.04
os: ubuntu-latest
rust: nightly
target: x86_64-unknown-linux-gnu
cross: false
- build: aarch64-linux
os: ubuntu-20.04
os: ubuntu-latest
rust: nightly
target: aarch64-unknown-linux-gnu
cross: true
@ -58,17 +58,15 @@ jobs:
steps:
- name: Checkout sources
uses: actions/checkout@v2
uses: actions/checkout@v4
with:
submodules: true
- name: Install ${{ matrix.rust }} toolchain
uses: actions-rs/toolchain@v1
uses: dtolnay/rust-toolchain@stable
with:
profile: minimal
toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }}
override: true
- name: Run cargo test
uses: actions-rs/cargo@v1
@ -122,7 +120,7 @@ jobs:
with:
submodules: false
- uses: actions/download-artifact@v2
- uses: actions/download-artifact@v4
# with:
# path: dist
# - run: ls -al ./dist
@ -180,14 +178,14 @@ jobs:
run: |
printf "::set-output name=%s::%s\n" tag-name "${GITHUB_REF#refs/tags/}"
- uses: mislav/bump-homebrew-formula-action@v1
with:
formula-path: ${{env.PROJECT_NAME}}.rb
homebrew-tap: ${{ env.BREW_TAP }}
download-url: "https://github.com/${{ env.REPO_NAME }}/releases/download/${{ steps.extract-version.outputs.tag-name }}/${{env.PROJECT_NAME}}-${{ steps.extract-version.outputs.tag-name }}-x86_64-macos.tar.xz"
commit-message: updating formula for ${{ env.PROJECT_NAME }}
env:
COMMITTER_TOKEN: ${{ secrets.COMMITTER_TOKEN }}
# - uses: mislav/bump-homebrew-formula-action@v1
# with:
# formula-path: ${{env.PROJECT_NAME}}.rb
# homebrew-tap: ${{ env.BREW_TAP }}
# download-url: "https://github.com/${{ env.REPO_NAME }}/releases/download/${{ steps.extract-version.outputs.tag-name }}/${{env.PROJECT_NAME}}-${{ steps.extract-version.outputs.tag-name }}-x86_64-macos.tar.xz"
# commit-message: updating formula for ${{ env.PROJECT_NAME }}
# env:
# COMMITTER_TOKEN: ${{ secrets.COMMITTER_TOKEN }}
#
# you can use this initial file in your homebrew-tap if you don't have an initial formula:
# <projectname>.rb
@ -219,4 +217,43 @@ jobs:
# - run: cargo publish --token ${CRATES_TOKEN}
# env:
# CRATES_TOKEN: ${{ secrets.CRATES_TOKEN }}
book:
name: Book
runs-on: ubuntu-latest
permissions:
contents: write # To push a branch
pages: write # To push to a GitHub Pages site
id-token: write # To update the deployment status
steps:
- name: Checkout sources
uses: actions/checkout@v2
- name: Install nightly toolchain
uses: dtolnay/rust-toolchain@stable
with:
toolchain: nightly
- uses: Swatinem/rust-cache@v2
- name: Install mdbook
uses: actions-rs/cargo@v1
with:
command: install
args: mdbook --no-default-features --features search --vers "^0.4" --locked
- name: Build book
run: |
cd book
mdbook build
- name: Setup Pages
uses: actions/configure-pages@v4
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: 'book/book'
- name: Deploy to GitHub pages
id: deployment
uses: actions/deploy-pages@v4

View File

@ -1,2 +0,0 @@
CI Based on:
[Rust CI Release Template](https://github.com/SpectralOps/rust-ci-release-template)

86
Cargo.lock generated
View File

@ -125,6 +125,12 @@ dependencies = [
"powerfmt",
]
[[package]]
name = "equivalent"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
name = "flate2"
version = "1.1.0"
@ -146,6 +152,22 @@ dependencies = [
"wasi",
]
[[package]]
name = "hashbrown"
version = "0.15.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289"
[[package]]
name = "indexmap"
version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3954d50fe15b02142bf25d3b8bdadb634ec3948f103d04ffe3031bc8fe9d7058"
dependencies = [
"equivalent",
"hashbrown",
]
[[package]]
name = "instant"
version = "0.1.13"
@ -181,7 +203,7 @@ checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e"
[[package]]
name = "mcrizzledizzle"
version = "0.1.0"
version = "0.2.0"
dependencies = [
"ctrlc",
"flate2",
@ -189,7 +211,9 @@ dependencies = [
"regex",
"rhai",
"rhai-rand",
"serde",
"simple_logger",
"toml",
]
[[package]]
@ -408,18 +432,18 @@ checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd"
[[package]]
name = "serde"
version = "1.0.218"
version = "1.0.219"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8dfc9d19bdbf6d17e22319da49161d5d0108e4188e8b680aef6299eed22df60"
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.218"
version = "1.0.219"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b"
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
dependencies = [
"proc-macro2",
"quote",
@ -438,6 +462,15 @@ dependencies = [
"serde",
]
[[package]]
name = "serde_spanned"
version = "0.6.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1"
dependencies = [
"serde",
]
[[package]]
name = "simple_logger"
version = "5.0.0"
@ -538,6 +571,40 @@ dependencies = [
"crunchy",
]
[[package]]
name = "toml"
version = "0.8.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148"
dependencies = [
"serde",
"serde_spanned",
"toml_datetime",
"toml_edit",
]
[[package]]
name = "toml_datetime"
version = "0.6.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41"
dependencies = [
"serde",
]
[[package]]
name = "toml_edit"
version = "0.22.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474"
dependencies = [
"indexmap",
"serde",
"serde_spanned",
"toml_datetime",
"winnow",
]
[[package]]
name = "unicode-ident"
version = "1.0.17"
@ -695,6 +762,15 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "winnow"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e97b544156e9bebe1a0ffbc03484fc1ffe3100cbce3ffb17eac35f7cdd7ab36"
dependencies = [
"memchr",
]
[[package]]
name = "zerocopy"
version = "0.7.35"

View File

@ -1,19 +1,19 @@
[package]
name = "mcrizzledizzle"
version = "0.1.0"
version = "0.2.0"
edition = "2021"
[[bin]]
name = "mcrizzledizzle"
path = "src/main.rs"
[dependencies]
ctrlc = "3.4.5"
flate2 = "1.0.30"
log = "0.4.26"
regex = "1.11.1"
rhai-rand = "0.1.6"
[dependencies.simple_logger]
version = "5.0.0"
features = ["threads", "nightly"]
[dependencies.rhai]
version = "1.21.0"
features = ["sync"]
serde = { version = "1.0.219", features = ["derive"] }
toml = "0.8.20"
simple_logger = { version = "5.0.0", features = ["threads", "nightly"] }
rhai = { version = "1.21.0", features = ["sync"] }

195
LICENSE.md Normal file
View File

@ -0,0 +1,195 @@
Apache License
==============
_Version 2.0, January 2004_
_&lt;<http://www.apache.org/licenses/>&gt;_
### Terms and Conditions for use, reproduction, and distribution
#### 1. Definitions
“License” shall mean the terms and conditions for use, reproduction, and
distribution as defined by Sections 1 through 9 of this document.
“Licensor” shall mean the copyright owner or entity authorized by the copyright
owner that is granting the License.
“Legal Entity” shall mean the union of the acting entity and all other entities
that control, are controlled by, or are under common control with that entity.
For the purposes of this definition, “control” means **(i)** the power, direct or
indirect, to cause the direction or management of such entity, whether by
contract or otherwise, or **(ii)** ownership of fifty percent (50%) or more of the
outstanding shares, or **(iii)** beneficial ownership of such entity.
“You” (or “Your”) shall mean an individual or Legal Entity exercising
permissions granted by this License.
“Source” form shall mean the preferred form for making modifications, including
but not limited to software source code, documentation source, and configuration
files.
“Object” form shall mean any form resulting from mechanical transformation or
translation of a Source form, including but not limited to compiled object code,
generated documentation, and conversions to other media types.
“Work” shall mean the work of authorship, whether in Source or Object form, made
available under the License, as indicated by a copyright notice that is included
in or attached to the work (an example is provided in the Appendix below).
“Derivative Works” shall mean any work, whether in Source or Object form, that
is based on (or derived from) the Work and for which the editorial revisions,
annotations, elaborations, or other modifications represent, as a whole, an
original work of authorship. For the purposes of this License, Derivative Works
shall not include works that remain separable from, or merely link (or bind by
name) to the interfaces of, the Work and Derivative Works thereof.
“Contribution” shall mean any work of authorship, including the original version
of the Work and any modifications or additions to that Work or Derivative Works
thereof, that is intentionally submitted to Licensor for inclusion in the Work
by the copyright owner or by an individual or Legal Entity authorized to submit
on behalf of the copyright owner. For the purposes of this definition,
“submitted” means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems, and
issue tracking systems that are managed by, or on behalf of, the Licensor for
the purpose of discussing and improving the Work, but excluding communication
that is conspicuously marked or otherwise designated in writing by the copyright
owner as “Not a Contribution.”
“Contributor” shall mean Licensor and any individual or Legal Entity on behalf
of whom a Contribution has been received by Licensor and subsequently
incorporated within the Work.
#### 2. Grant of Copyright License
Subject to the terms and conditions of this License, each Contributor hereby
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
irrevocable copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the Work and such
Derivative Works in Source or Object form.
#### 3. Grant of Patent License
Subject to the terms and conditions of this License, each Contributor hereby
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
irrevocable (except as stated in this section) patent license to make, have
made, use, offer to sell, sell, import, and otherwise transfer the Work, where
such license applies only to those patent claims licensable by such Contributor
that are necessarily infringed by their Contribution(s) alone or by combination
of their Contribution(s) with the Work to which such Contribution(s) was
submitted. If You institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work or a
Contribution incorporated within the Work constitutes direct or contributory
patent infringement, then any patent licenses granted to You under this License
for that Work shall terminate as of the date such litigation is filed.
#### 4. Redistribution
You may reproduce and distribute copies of the Work or Derivative Works thereof
in any medium, with or without modifications, and in Source or Object form,
provided that You meet the following conditions:
* **(a)** You must give any other recipients of the Work or Derivative Works a copy of
this License; and
* **(b)** You must cause any modified files to carry prominent notices stating that You
changed the files; and
* **(c)** You must retain, in the Source form of any Derivative Works that You distribute,
all copyright, patent, trademark, and attribution notices from the Source form
of the Work, excluding those notices that do not pertain to any part of the
Derivative Works; and
* **(d)** If the Work includes a “NOTICE” text file as part of its distribution, then any
Derivative Works that You distribute must include a readable copy of the
attribution notices contained within such NOTICE file, excluding those notices
that do not pertain to any part of the Derivative Works, in at least one of the
following places: within a NOTICE text file distributed as part of the
Derivative Works; within the Source form or documentation, if provided along
with the Derivative Works; or, within a display generated by the Derivative
Works, if and wherever such third-party notices normally appear. The contents of
the NOTICE file are for informational purposes only and do not modify the
License. You may add Your own attribution notices within Derivative Works that
You distribute, alongside or as an addendum to the NOTICE text from the Work,
provided that such additional attribution notices cannot be construed as
modifying the License.
You may add Your own copyright statement to Your modifications and may provide
additional or different license terms and conditions for use, reproduction, or
distribution of Your modifications, or for any such Derivative Works as a whole,
provided Your use, reproduction, and distribution of the Work otherwise complies
with the conditions stated in this License.
#### 5. Submission of Contributions
Unless You explicitly state otherwise, any Contribution intentionally submitted
for inclusion in the Work by You to the Licensor shall be under the terms and
conditions of this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify the terms of
any separate license agreement you may have executed with Licensor regarding
such Contributions.
#### 6. Trademarks
This License does not grant permission to use the trade names, trademarks,
service marks, or product names of the Licensor, except as required for
reasonable and customary use in describing the origin of the Work and
reproducing the content of the NOTICE file.
#### 7. Disclaimer of Warranty
Unless required by applicable law or agreed to in writing, Licensor provides the
Work (and each Contributor provides its Contributions) on an “AS IS” BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
including, without limitation, any warranties or conditions of TITLE,
NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
solely responsible for determining the appropriateness of using or
redistributing the Work and assume any risks associated with Your exercise of
permissions under this License.
#### 8. Limitation of Liability
In no event and under no legal theory, whether in tort (including negligence),
contract, or otherwise, unless required by applicable law (such as deliberate
and grossly negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special, incidental,
or consequential damages of any character arising as a result of this License or
out of the use or inability to use the Work (including but not limited to
damages for loss of goodwill, work stoppage, computer failure or malfunction, or
any and all other commercial damages or losses), even if such Contributor has
been advised of the possibility of such damages.
#### 9. Accepting Warranty or Additional Liability
While redistributing the Work or Derivative Works thereof, You may choose to
offer, and charge a fee for, acceptance of support, warranty, indemnity, or
other liability obligations and/or rights consistent with this License. However,
in accepting such obligations, You may act only on Your own behalf and on Your
sole responsibility, not on behalf of any other Contributor, and only if You
agree to indemnify, defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason of your
accepting any such warranty or additional liability.
_END OF TERMS AND CONDITIONS_
### APPENDIX: How to apply the Apache License to your work
To apply the Apache License to your work, attach the following boilerplate
notice, with the fields enclosed by brackets `[]` replaced with your own
identifying information. (Don't include the brackets!) The text should be
enclosed in the appropriate comment syntax for the file format. We also
recommend that a file or class name and description of purpose be included on
the same “printed page” as the copyright notice for easier identification within
third-party archives.
Copyright 2025 illegitimate-egg
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -1,3 +1,5 @@
[![CI Build](https://github.com/illegitimate-egg/mcrizzledizzle/actions/workflows/build.yml/badge.svg)](https://github.com/illegitimate-egg/mcrizzledizzle/actions/workflows/build.yml)
McRizzleDrizzle
mæk ˈrrɪzᵊl ˈdrɪzᵊl
@ -5,4 +7,10 @@ mæk ˈrrɪzᵊl ˈdrɪzᵊl
TODO:
- [x] ~~Fix error on disconnect~~
- [x] ~~Command support~~
- [ ] Operators
- [ ] Finish command support (Event Listeners)
- [ ] Finish the readme
- [ ] Fix player id/thread allocator
Backburner:
- [ ] classicube extensions

1
book/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
book

6
book/book.toml Normal file
View File

@ -0,0 +1,6 @@
[book]
authors = ["illegitimate-egg"]
language = "en"
multilingual = false
src = "src"
title = "The Emporium of mcrizzledizzle Configuration and Plugin Development"

5
book/src/README.md Normal file
View File

@ -0,0 +1,5 @@
# Introduction
Mcrizzledizzle is an incredibly shitty yet somehow performant take on the classic Minecraft server.
Here you can find information about writing plugins and configuring the server.

22
book/src/SUMMARY.md Normal file
View File

@ -0,0 +1,22 @@
# Summary
- [Introduction](README.md)
# User Guide
- [Configuration](guide/configuration.md)
- [Installing Extensions](guide/extensions.md)
# Extension Development
- [Extensions](./extensions/README.md)
- [Types](extensions/types/README.md)
- [Metadata](extensions/types/Metadata.md)
- [Version](extensions/types/Version.md)
- [Player](extensions/types/Player.md)
- [PlayersWrapper](extensions/types/PlayersWrapper.md)
- [WorldWrapper](extensions/types/WorldWrapper.md)
- [Context](extensions/types/Context.md)
- [Vec3](extensions/types/Vec3.md)
- [Event](extensions/types/Event.md)
- [Functions](extensions/functions/README.md)
- [Logging](extensions/functions/logging.md)

View File

@ -0,0 +1,25 @@
# Extensions
<div class="warning">
This guide is intended for experienced programmers, it is not a step by step guide. If in doubt check the <a href="https://github.com/illegitimate-egg/mcrizzledizzle/tree/master/rte/extensions">official plugins</a>.
</div>
The extensions interface uses the [rhai](https://rhai.rs/) programming language with some custom functionality to provide a full set of instructions for making extensions.
## Extension Structure
All extensions must provide a `metadata()` and `init(players, world)` function. mcrizzledizzle uses these internally to populate information about the extension as well as register any commands or event listeners that the extension might want to use.
Example:
```rust
fn metadata() {
Metadata("My Awesome Plugin's name!", "My Plugin's (less awesome) author", Version("1.0.0"))
}
fn init(players, world) {
...
}
```
The `Metadata` struct expects a name string, description string and valid `Version()`. The Version should be a valid [semantic version](https://semver.org/) otherwise the plugin will not load correctly.

View File

@ -0,0 +1,3 @@
# Functions
These functions can be called directly from an extension. They aren't tied to any structure.

View File

@ -0,0 +1,27 @@
# Logging
These functions all go directly to the server logs, as you'd expect. They all take a message and handle the rest for you.
## info
Signature:
```rust
fn info(msg: String)
```
## warn
Signature:
```rust
fn warn(msg: String)
```
## error
Signature:
```rust
fn error(msg: String)
```
## debug
Signature:
```rust
fn debug(msg: String)
```

View File

@ -0,0 +1,29 @@
# Context
The context is a struct that you can create to tell the server about what your plugin wants to do. You must create one if you want to register commands or add event listeners.
```rust
struct Context {
commands: HashMap<String, FnPtr>,
event_listener: HashMap<EventType, FnPtr>,
};
```
## Implementations
### register_command
```rust
fn register_command(&mut self, name: String, callback: FnPtr)
```
This is how you can register commands on the server. Your `callback`` likely should be a closure as you almost certainly want to capture information from the environment.
### add_event_listener
```rust
fn add_event_listener(&mut self, event: &str, callback: FnPtr)
```
This is how event listeners are created. `callback`` should probably be a closure because you almost certainly want information from the environment. The currently available event types are:
- `"block_break"` This is fired when a black is broken. This is interruptible.
- `"player_leave"` This is fired when a player leaves the server. This is not interruptible.

View File

@ -0,0 +1,25 @@
# Event
Events are structs that tell the extension about changes in server or player state.
```rust
struct Event {
player: u8,
position: Vec3,
selected_block: u8,
is_cancelled: bool,
}
```
Only certain parts of the struct are used for certain events. Since events are only returned to their corresponding handler, the event type is not provided in the struct. Available event types are:
- `"block_break"` This is fired when a black is broken. This is interruptible.
- `"player_leave"` This is fired when a player leaves the server. This is not interruptible.
## Implementations
### cancel
```rust
fn cancel(&mut self)
```
This sets is_cancelled to true, cancelling interruptible events, like block breaking.

View File

@ -0,0 +1,21 @@
# Metadata
This type tells mcrizzledizzle important imformation about the plugin.
The struct signature looks like this:
```rust
struct Metadata {
name: String,
author: String,
version: Version,
};
```
It should be used inside the metadata function like so:
```rust
fn metadata() {
Metadata("Example Name", "Example Description", Version("1.0.0"))
}
```

View File

@ -0,0 +1,9 @@
# Player
The player struct probably shouldn't be modified by plugins, in any case, here's the struct definition:
```rust
struct Player {
id: u8,
};
```

View File

@ -0,0 +1,36 @@
# PlayersWrapper
The PlayersWrapper struct wraps around the server's Player Data mutex. It provides a friendly interface for extensions.
```rust
struct PlayersWrapper(Arc<Mutex<[Player; 255]>>);
```
You cannot instantiate a PlayersWrapper yourself, it is passed as the first argument of init() and can then be called upon from there.
## Implementations
### send_message
```rust
fn send_message(
self,
player: u8,
message: String,
)
```
This function expects a player id and a message to be passed as arguments. Keep in mind that the length limit for messages is 64 characters.
### send_all
```rust
fn send_all(self, message: String)
```
This function is like `send_message` except it sends the message to all connected players.
### username
```rust
fn username(self, player: u8)
```
This function gets the username of a player from their id.

View File

@ -0,0 +1,3 @@
# Types
This contains a big list of structs and their available functions to call from rhai.

View File

@ -0,0 +1,16 @@
# Vec3
Vec3s are used to store positions of blocks and players.
```rust
struct Vec3 {
pub x: i16,
pub y: i16,
pub z: i16,
}
```
You can create one using the constructor:
```rust
Vec3(x: i64, y: i64, z: i64)
```

View File

@ -0,0 +1,16 @@
# Version
This type is stored internally as a complete semantic version, an easy constructor is provided that converts a valid semver to a set of parts, providing a display function.
Struct signature:
```rust
struct Version {
major: u16,
minor: u16,
patch: u16,
prerelease: String,
build: String,
};
```
Usually it would be used along with [Metadata](./Metadata.md) to setup an extension.

View File

@ -0,0 +1,23 @@
# WorldWrapper
The WorldWrapper struct wraps around the server's World Data mutex. It provides a friendly interface for extensions.
```rust
struct WorldWrapper(Arc<Mutex<World>>);
```
You cannot instantiate a WorldWrapper yourself, it is passed as the second argument of init() and can be called upon from there.
## Implementations
### set_block
```rust
fn set_block(
self,
players_wrapper: PlayersWrapper,
position: Vec3,
block_type: u8,
)
```
This functions sets a block at the desired position. Since it uses the player data internally it requires the PlayersWrapper.

View File

@ -0,0 +1,27 @@
# Configuration
The config file is a regular toml file that is placed in the same directory that the program is run from. There's an example [in the repo](https://github.com/illegitimate-egg/mcrizzledizzle/blob/master/rte/config.toml) where all the available parameters have been set.
The config is split into two parts, one relating to server operations and another relating to world operations.
## [server]
Under server you can set the port, motd and name of the server.
```toml
name = "server of ire"
motd = "Message of the day" # There's a 64 character limit on these so be careful (including colour escapes)
port = 25565 # default mc port
max_players = 255 # 255 is the maximum number
```
## [world]
For the world you can set the path and size (for generation) of the world.
```toml
world = "world.wrld" # This is a custom world format that will not work with other servers
# Generator settings
size_x = 64
size_y = 32
size_z = 64
```

View File

@ -0,0 +1,17 @@
# Installing Extensions
Extensions are distributed as `rhai` files. As with all software that you install on your computer, it's important to make sure that you trust the program that you're running as they are quite powerful.
Extensions go into the auto-generated extension folder. Some official examples can be found in the [extensions](https://github.com/illegitimate-egg/mcrizzledizzle/tree/master/rte/extensions) folder on the repository:
- `fill.rhai` - A worldedit like fill command
- `ping-pong.rhai` - A very basic command, responds when you using /ping
- `utils.rhai` - A set of utilities used for development of the extension interface
To see what commands are available you can use the `/help` command. To see what extensions are installed and their versions you can use the `/extensions` command.
## Other extensions
There are some third-party extensions that are officially endorsed by mcrizzledizzle.
- [williamist/rizzle-extensions](https://github.com/williamistGitHub/rizzle-extensions) - A great (set of) extension(s) written by a contributor

16
justfile Normal file
View File

@ -0,0 +1,16 @@
alias r := run
alias t := test
alias b := book
run:
cd rte; cargo run
test:
cargo test
book:
cd book; mdbook serve
install_book_toolchain:
cargo install mdbook --locked --version 0.4.47
# cargo install --locked --path packages/mdbook-trpl # mdbook repo needed for this one lmao

12
rte/config.toml Normal file
View File

@ -0,0 +1,12 @@
[server]
name = "mcrizzledizzle default"
motd = "For shits and giggles"
port = 25565
max_players = 3
[world]
world = "world.wrld" # Custom world type, not interchangable with other servers
# Generation parameters, when a world is read these are ignored
size_x = 64
size_y = 32
size_z = 64

93
src/config.rs Normal file
View File

@ -0,0 +1,93 @@
use log::warn;
use serde::Deserialize;
use std::{
fs::File,
io::{Read, Write},
};
use crate::error::AppError;
#[derive(Clone, Debug, Default, Deserialize)]
#[serde(default)]
pub struct Config {
pub server: ServerConfig,
pub world: WorldConfig,
}
// Hmm hmm hmm hmm... the great, iconic, symbol of nobility. My sibilantic friend, ServerSonfig.
// Your hour has passed and
#[derive(Clone, Debug, Deserialize)]
#[serde(default)]
pub struct ServerConfig {
pub port: u16,
pub max_players: u8, // Upper limit
pub name: String,
pub motd: String,
}
impl Default for ServerConfig {
fn default() -> Self {
Self {
port: 25565,
max_players: 255,
name: "Default".to_string(),
motd: "Default".to_string(),
}
}
}
#[derive(Clone, Debug, Deserialize)]
#[serde(default)]
pub struct WorldConfig {
pub world: String,
pub size_x: i16,
pub size_y: i16,
pub size_z: i16,
}
impl Default for WorldConfig {
fn default() -> Self {
Self {
world: "world.wrld".to_string(),
size_x: 64,
size_y: 32,
size_z: 64,
}
}
}
impl Config {
pub fn load() -> Result<Self, AppError> {
// Load the config file
let mut config_file = match File::open("config.toml") {
Ok(result) => result,
Err(_) => {
const CONFIG_FILE_DATA: &str = r#"[server]
name = "mcrizzledizzle default"
motd = "For shits and giggles"
port = 25565
max_players = 255
[world]
world = "world.wrld" # Custom world type, not interchangable with other servers
# Generation parameters, when a world is read these are ignored
size_x = 64
size_y = 32
size_z = 64
"#;
warn!("No config file was present! Generating one now.");
let mut config_file = File::create("config.toml")?;
config_file.write_all(CONFIG_FILE_DATA.as_bytes())?;
File::open("config.toml").expect("Failed to create config.toml")
}
};
let mut config_data = String::new();
config_file
.read_to_string(&mut config_data)
.expect("Failed to read config file");
Ok(toml::from_str(&config_data)?)
}
}

View File

@ -13,6 +13,7 @@ pub enum AppError {
TryFromIntError(TryFromIntError),
RhaiError(Box<EvalAltResult>),
MutexPoisoned(String),
DeserializerError(toml::de::Error),
InvalidWorldFile,
// InvalidExtensionVersion,
}
@ -20,13 +21,14 @@ pub enum AppError {
impl fmt::Display for AppError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
AppError::IoError(err) => write!(f, "IO Error: {}", err),
AppError::RegexError(err) => write!(f, "Extension Regex Error: {}", err),
AppError::ParseIntError(err) => write!(f, "Parse int error: {}", err),
AppError::ParseFloatError(err) => write!(f, "Parse float error: {}", err),
AppError::TryFromIntError(err) => write!(f, "Integer conversion error: {}", err),
AppError::RhaiError(err) => write!(f, "Rhai compilation error: {}", err),
AppError::MutexPoisoned(err) => write!(f, "Poisoned mutex: {}", err),
AppError::IoError(err) => write!(f, "IO Error: {err}"),
AppError::RegexError(err) => write!(f, "Extension Regex Error: {err}"),
AppError::ParseIntError(err) => write!(f, "Parse int error: {err}"),
AppError::ParseFloatError(err) => write!(f, "Parse float error: {err}"),
AppError::TryFromIntError(err) => write!(f, "Integer conversion error: {err}"),
AppError::RhaiError(err) => write!(f, "Rhai compilation error: {err}"),
AppError::MutexPoisoned(err) => write!(f, "Poisoned mutex: {err}"),
AppError::DeserializerError(err) => write!(f, "Config Deserializer Failed: {err}"),
AppError::InvalidWorldFile => write!(f, "Invalid world file"),
// AppError::InvalidExtensionVersion => write!(f, "Invalid extension version"),
}
@ -74,3 +76,9 @@ impl<T> From<PoisonError<T>> for AppError {
AppError::MutexPoisoned(err.to_string())
}
}
impl From<toml::de::Error> for AppError {
fn from(err: toml::de::Error) -> Self {
AppError::DeserializerError(err)
}
}

View File

@ -462,7 +462,7 @@ impl Extensions {
let extension_path = extension?.path();
if extension_path.extension() != Some(OsStr::new("rhai")) {
break;
continue;
}
info!("Loading extension {}", extension_path.display());

View File

@ -1,9 +1,13 @@
#![feature(coverage_attribute)]
use log::{error, info};
use simple_logger::SimpleLogger;
use std::io::Write;
use std::net::{SocketAddr, TcpListener};
use std::sync::{Arc, Mutex};
mod command;
mod config;
mod error;
mod extensions;
mod network;
@ -11,10 +15,12 @@ mod player;
mod utils;
mod world;
use config::Config;
use error::AppError;
use extensions::{Extensions, PlayersWrapper, WorldWrapper};
use network::handle_client;
use player::{Player, SpecialPlayers};
use utils::{client_disconnect, server_identification};
use world::World;
fn main() {
@ -27,22 +33,25 @@ fn main() {
}
fn run() -> Result<(), AppError> {
let config = Config::load()?;
let players: [Player; 255] = core::array::from_fn(|_| Player::default());
let players_arc = Arc::new(Mutex::new(players));
let world_instance: World = World::load()?;
let world_instance: World = World::load(&config.world)?;
let world_arc = Arc::new(Mutex::new(world_instance));
let addr = SocketAddr::from(([0, 0, 0, 0], 25565));
let addr = SocketAddr::from(([0, 0, 0, 0], config.server.port));
let listener = TcpListener::bind(addr)?;
let mut thread_number: u8 = 0;
let world_arc_clone_main_thread = Arc::clone(&world_arc);
let world_config_clone = config.clone().world;
ctrlc::set_handler(move || {
println!();
info!("SAVING");
World::save(world_arc_clone_main_thread.clone()).unwrap(); // Fortnite save the world
World::save(&world_config_clone, world_arc_clone_main_thread.clone()).unwrap(); // Fortnite save the world
std::process::exit(0);
})
.expect("Error handling control C, save on exit will not work");
@ -52,20 +61,43 @@ fn run() -> Result<(), AppError> {
WorldWrapper::new(world_arc.clone()),
)?);
info!("Server listening on {}", 25565);
info!("Server listening on {}", config.server.port);
for stream in listener.incoming() {
let players_arc_clone = Arc::clone(&players_arc);
let world_arc_clone = Arc::clone(&world_arc);
let extensions_arc_clone = Arc::clone(&extensions);
handle_client(
stream?,
thread_number,
players_arc_clone,
world_arc_clone,
extensions_arc_clone,
);
thread_number = thread_number.wrapping_add(1);
let mut insertion_attempts: u8 = 0;
while players_arc.lock()?[thread_number as usize].id != SpecialPlayers::SelfPlayer as u8
&& insertion_attempts < config.server.max_players
{
insertion_attempts += 1;
// One is reserved for communications
if thread_number < config.server.max_players - 1 {
thread_number += 1;
} else {
thread_number = 0;
}
}
if insertion_attempts == config.server.max_players {
// Server must be full
// Seems silly that we have to ident to kick clients, but I didn't make the protocol
let mut disconnect_packets: Vec<u8> = Vec::new();
disconnect_packets
.extend_from_slice(&server_identification(config.server.clone(), false));
disconnect_packets
.extend_from_slice(&client_disconnect("Server is full! Try again later"));
stream?.write_all(&disconnect_packets)?;
} else {
handle_client(
config.clone().server,
stream?,
thread_number,
players_arc_clone,
world_arc_clone,
extensions_arc_clone,
);
}
}
Ok(())
}

View File

@ -7,12 +7,14 @@ use std::thread::sleep;
use std::time::Duration;
use crate::command::handle_command;
use crate::config::ServerConfig;
use crate::extensions::{Event, EventType, Extensions};
use crate::player::{Player, PlayerStatus, SpecialPlayers};
use crate::utils::*;
use crate::world::World;
pub fn handle_client(
config: ServerConfig,
mut stream: TcpStream,
client_number: u8,
players_arc_clone: Arc<Mutex<[Player; 255]>>,
@ -73,7 +75,7 @@ pub fn handle_client(
let mut verif_key_formatted = String::new();
use std::fmt::Write;
for &byte in &verif_key {
write!(&mut verif_key_formatted, "{:X}", byte).expect("Piss");
write!(&mut verif_key_formatted, "{byte:X}").expect("Piss");
}
{
let mut players = players_arc_clone.lock().unwrap();
@ -90,7 +92,12 @@ pub fn handle_client(
current_player.pitch = 0;
current_player.operator = true;
let _ = bomb_server_details(&mut stream, current_player, &world_arc_clone);
let _ = bomb_server_details(
config.clone(),
&mut stream,
current_player,
&world_arc_clone,
);
for i in 0..immediate_join.len() {
if immediate_join[i] {
@ -275,7 +282,7 @@ pub fn handle_client(
break;
}
sleep(Duration::from_millis(1)); // 1000 TPS TODO: Delta time
sleep(Duration::from_millis(50)); // 1000 TPS TODO: Delta time
{
let mut players = players_arc_clone.lock().unwrap();
if !players[client_number as usize].outgoing_data.is_empty() {

View File

@ -5,6 +5,7 @@ use std::io::prelude::*;
use std::net::TcpStream;
use std::sync::{Arc, Mutex};
use crate::config::ServerConfig;
use crate::error::AppError;
use crate::Player;
use crate::SpecialPlayers;
@ -12,11 +13,7 @@ use crate::World;
pub fn to_mc_string(text: &str) -> [u8; 64] {
let text_vec: Vec<char> = text.chars().take(64).collect();
let mut balls = [0; 64];
for ref mut ball in balls {
*ball = 0x20;
}
let mut balls = [0x20; 64];
for i in 0..text_vec.len() {
balls[i] = text_vec[i] as u8;
@ -36,16 +33,13 @@ pub fn client_disconnect(text: &str) -> Vec<u8> {
ret_val
}
pub fn server_identification(is_op: bool) -> Vec<u8> {
pub fn server_identification(config: ServerConfig, is_op: bool) -> Vec<u8> {
let mut ret_val: Vec<u8> = vec![];
ret_val.push(0x00);
ret_val.push(0x07);
let server_name = "Erm... what the sigma?";
ret_val.append(&mut to_mc_string(server_name).to_vec());
let server_motd = "Pragmatism not idealism";
ret_val.append(&mut to_mc_string(server_motd).to_vec());
ret_val.append(&mut to_mc_string(&config.name).to_vec());
ret_val.append(&mut to_mc_string(&config.motd).to_vec());
if is_op {
ret_val.push(0x64);
@ -64,6 +58,8 @@ pub fn init_level() -> Vec<u8> {
vec![0x02]
}
// Mutex coverage difficult, this whole function is basic rust literals anyway
#[coverage(off)]
pub fn finalize_level(world_arc_clone: &Arc<Mutex<World>>) -> Result<Vec<u8>, AppError> {
let mut ret_val: Vec<u8> = vec![];
ret_val.push(0x04);
@ -149,6 +145,7 @@ pub fn set_position_and_orientation(
ret_val
}
#[coverage(off)]
pub fn send_level_data(world_arc_clone: &Arc<Mutex<World>>) -> Result<Vec<u8>, AppError> {
let mut ret_val: Vec<u8> = vec![];
let mut world_dat = world_arc_clone.lock()?.data.clone();
@ -215,13 +212,15 @@ pub fn send_level_data(world_arc_clone: &Arc<Mutex<World>>) -> Result<Vec<u8>, A
Ok(ret_val)
}
#[coverage(off)]
pub fn bomb_server_details(
config: ServerConfig,
stream: &mut TcpStream,
current_player: &Player,
world_arc_clone: &Arc<Mutex<World>>,
) -> Result<(), AppError> {
let mut compound_data: Vec<u8> = vec![];
compound_data.append(&mut server_identification(current_player.operator));
compound_data.append(&mut server_identification(config, current_player.operator));
compound_data.append(&mut init_level());
@ -244,3 +243,168 @@ pub fn bomb_server_details(
let _ = stream.write(&compound_data);
Ok(())
}
#[cfg(test)]
mod tests {
use crate::config::WorldConfig;
use super::*;
#[test]
fn test_string_writer() {
assert_eq!(to_mc_string("This is a nice test string that's longer than 64 chars. By having a string of this length it checks if to_mc_string truncates text correctly."), [84, 104, 105, 115, 32, 105, 115, 32, 97, 32, 110, 105, 99, 101, 32, 116, 101, 115, 116, 32, 115, 116, 114, 105, 110, 103, 32, 116, 104, 97, 116, 39, 115, 32, 108, 111, 110, 103, 101, 114, 32, 116, 104, 97, 110, 32, 54, 52, 32, 99, 104, 97, 114, 115, 46, 32, 66, 121, 32, 104, 97, 118, 105, 110]);
}
#[test]
fn test_short_writer() {
for x in i16::MIN..i16::MAX {
assert_eq!(
stream_write_short(x),
[(x.to_le() >> 0x08) as u8, (x.to_le() & 0x00FF) as u8].to_vec() // There is a very
// real argument that I can't counter that says this is how this function should be
// implemented, and also that you can't test a range of data but to that I say...
// yeah ig TODO:
// Make this not a pile of shit
// UPDATE: I have looked at the compiled code, the version that's actually used live is slighly shorter and is faster
);
}
}
#[test]
fn test_client_disconnect() {
assert_eq!(
client_disconnect("test string"),
[
14, 116, 101, 115, 116, 32, 115, 116, 114, 105, 110, 103, 32, 32, 32, 32, 32, 32,
32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
32, 32, 32, 32, 32
]
);
}
#[test]
fn test_server_ident() {
assert_eq!(
server_identification(
ServerConfig {
port: 25565,
max_players: 255,
name: "Shrimp".to_string(),
motd: "Also shrimp".to_string()
},
true
),
[
0, 7, 83, 104, 114, 105, 109, 112, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
32, 32, 32, 32, 65, 108, 115, 111, 32, 115, 104, 114, 105, 109, 112, 32, 32, 32,
32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
32, 32, 32, 32, 32, 32, 32, 32, 100
]
);
assert_eq!(
server_identification(
ServerConfig {
port: 25565,
max_players: 255,
name: "Shrimp".to_string(),
motd: "Also shrimp".to_string()
},
false
),
[
0, 7, 83, 104, 114, 105, 109, 112, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
32, 32, 32, 32, 65, 108, 115, 111, 32, 115, 104, 114, 105, 109, 112, 32, 32, 32,
32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
32, 32, 32, 32, 32, 32, 32, 32, 0
]
);
}
#[test]
fn test_ping() {
assert_eq!(ping(), [1]);
}
#[test]
fn test_init_level() {
assert_eq!(init_level(), [2]);
}
#[test]
fn test_finalize_level() {
assert_eq!(
finalize_level(&Arc::new(Mutex::new(
World::load(&WorldConfig {
world: "testpath.testwrld".to_string(),
size_x: 256,
size_y: 128,
size_z: 256
})
.unwrap()
)))
.unwrap(),
[4, 1, 0, 0, 128, 1, 0]
);
}
#[test]
fn test_spawn_player() {
assert_eq!(
spawn_player(7, "jimmy the 9", 25, 47, 3, 90, 90),
[
7, 7, 106, 105, 109, 109, 121, 32, 116, 104, 101, 32, 57, 32, 32, 32, 32, 32, 32,
32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
32, 32, 32, 32, 32, 3, 32, 5, 224, 0, 96, 90, 90
]
);
}
#[test]
fn test_despawn_player() {
for x in u8::MIN..u8::MAX {
assert_eq!(despawn_player(x), [0x0C, x]);
}
}
#[test]
fn test_send_chat_message() {
assert_eq!(
send_chat_message(12, "Testy toes".to_string(), "Whurrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr".to_string()),
[
13, 12, 84, 101, 115, 116, 121, 32, 116, 111, 101, 115, 58, 32, 87, 104, 117, 114,
114, 114, 114, 114, 114, 114, 114, 114, 114, 114, 114, 114, 114, 114, 114, 114,
114, 114, 114, 114, 114, 114, 114, 114, 114, 114, 114, 114, 114, 114, 114, 114,
114, 114, 114, 114, 114, 114, 114, 114, 114, 114, 114, 114, 114, 114, 114, 114
]
);
}
#[test]
fn test_write_chat_stream() {
assert_eq!(
write_chat_stream("This message is nice and long, like the message before it, it is long because this also tests for truncation".to_string()),
[
13, 255, 84, 104, 105, 115, 32, 109, 101, 115, 115, 97, 103, 101, 32, 105, 115, 32,
110, 105, 99, 101, 32, 97, 110, 100, 32, 108, 111, 110, 103, 44, 32, 108, 105, 107,
101, 32, 116, 104, 101, 32, 109, 101, 115, 115, 97, 103, 101, 32, 98, 101, 102, 111,
114, 101, 32, 105, 116, 44, 32, 105, 116, 32, 105, 115
]
);
}
#[test]
fn test_set_position_and_orientation() {
assert_eq!(
set_position_and_orientation(120, 15, 16, 17, 90, 90),
[8, 120, 0, 15, 0, 16, 0, 17, 90, 90]
);
}
}

View File

@ -4,6 +4,7 @@ use std::sync::{Arc, Mutex};
use log::info;
use crate::config::WorldConfig;
use crate::error::AppError;
#[derive(Debug)]
@ -32,8 +33,8 @@ impl World {
world_dat
}
pub fn load() -> Result<Self, AppError> {
if fs::metadata("world.wrld").is_ok() {
pub fn load(config: &WorldConfig) -> Result<Self, AppError> {
if fs::metadata(&config.world).is_ok() {
let mut world: World = World {
size_x: 0,
size_y: 0,
@ -41,7 +42,7 @@ impl World {
data: Vec::new(),
};
let world_data_raw = fs::read("world.wrld")?;
let world_data_raw = fs::read(&config.world)?;
if world_data_raw.len() < 6 {
return Err(AppError::InvalidWorldFile);
}
@ -61,20 +62,20 @@ impl World {
}
world.data = world_data_raw[6..].to_vec();
info!("Loaded world {}", "world.wrld");
info!("Loaded world {}", &config.world);
Ok(world)
} else {
info!("Creating word {}", "world.wrld");
info!("Creating word {}", &config.world);
Ok(World {
size_x: 64,
size_y: 32,
size_z: 64,
data: World::build(64, 32, 64),
size_x: config.size_x,
size_y: config.size_y,
size_z: config.size_z,
data: World::build(config.size_x, config.size_y, config.size_z),
})
}
}
pub fn save(world_arc_clone: Arc<Mutex<World>>) -> Result<(), AppError> {
pub fn save(config: &WorldConfig, world_arc_clone: Arc<Mutex<World>>) -> Result<(), AppError> {
let mut to_write: Vec<u8> = Vec::new();
{
let mut world_dat = world_arc_clone.lock()?;
@ -88,7 +89,7 @@ impl World {
to_write.append(&mut world_dat.data);
}
let mut file = File::create("world.wrld")?;
let mut file = File::create(&config.world)?;
Ok(file.write_all(&to_write)?)
}
}

Binary file not shown.