forked from EXTERNAL/bareiron
Compare commits
59 Commits
Author | SHA1 | Date | |
---|---|---|---|
7e23cabfce | |||
![]() |
d02608c69d | ||
7e7a98bd41 | |||
90a7a8b48e | |||
9fb3e870bf | |||
![]() |
d272e63dd7 | ||
![]() |
182a180c2e | ||
![]() |
450bef9e6c | ||
![]() |
516a00f122 | ||
![]() |
a631de77b5 | ||
![]() |
3bde692976 | ||
![]() |
b23e19ecd4 | ||
![]() |
ba86dfd927 | ||
![]() |
244c98552f | ||
![]() |
e88bf47d5b | ||
![]() |
c5e8408052 | ||
![]() |
e5dfe53d14 | ||
![]() |
ff89519340 | ||
![]() |
200106bb46 | ||
![]() |
6a65b8acba | ||
![]() |
26f068bc09 | ||
![]() |
f6333429eb | ||
![]() |
a34134918a | ||
![]() |
e7e9d307e6 | ||
![]() |
925b841e95 | ||
![]() |
1f9aa50573 | ||
![]() |
39f5c69bc3 | ||
![]() |
e81e75a6d7 | ||
![]() |
61eb38a83d | ||
![]() |
bdb4e4b72c | ||
![]() |
e3589a02f0 | ||
![]() |
bad337a032 | ||
![]() |
6f260383cd | ||
![]() |
7e80e12b26 | ||
![]() |
969f7d3974 | ||
![]() |
2a9e443a8d | ||
![]() |
8d75d0a75f | ||
![]() |
70b3b048db | ||
![]() |
0f1cadd341 | ||
![]() |
efac4c125d | ||
![]() |
fddef798e3 | ||
![]() |
808bbb26a2 | ||
![]() |
ba03d92ad0 | ||
![]() |
dabb202c13 | ||
![]() |
eef1020ad8 | ||
![]() |
81865cb7ac | ||
![]() |
7206dd1b11 | ||
![]() |
3d02dc02bf | ||
![]() |
5b8360708c | ||
![]() |
5a100bcd23 | ||
![]() |
46a808152a | ||
![]() |
ba11d121cf | ||
![]() |
a83acbda67 | ||
![]() |
e4c0c6b6e9 | ||
![]() |
55da6c7d4c | ||
![]() |
5c571df947 | ||
![]() |
125203980b | ||
![]() |
362f9e925b | ||
![]() |
fdb6f6dd15 |
2
.clang-format-ignore
Normal file
2
.clang-format-ignore
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
src/
|
||||||
|
include/
|
40
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
40
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
---
|
||||||
|
name: Bug report
|
||||||
|
about: Report a gameplay bug. Compilation issues do not count.
|
||||||
|
title: 'Bug report: '
|
||||||
|
labels: bug
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Describe the bug**
|
||||||
|
A clear and concise description of what the bug is.
|
||||||
|
|
||||||
|
**To Reproduce**
|
||||||
|
Write clear step-by-step instructions on how to reproduce the bug from a new world:
|
||||||
|
1.
|
||||||
|
2.
|
||||||
|
3.
|
||||||
|
|
||||||
|
**Expected behavior**
|
||||||
|
A clear and concise description of what you expected to happen, compared to what actually happened.
|
||||||
|
|
||||||
|
**Screenshots**
|
||||||
|
If applicable, add screenshots or videos to help explain your problem.
|
||||||
|
|
||||||
|
**Minecraft client details:**
|
||||||
|
- Version:
|
||||||
|
- Mods:
|
||||||
|
|
||||||
|
**Platform (if desktop):**
|
||||||
|
- Operating System:
|
||||||
|
- CPU:
|
||||||
|
|
||||||
|
**Platform (if ESP variant):**
|
||||||
|
- Chip:
|
||||||
|
- Development board:
|
||||||
|
- Power supply:
|
||||||
|
- Internet properties:
|
||||||
|
|
||||||
|
**Additional context**
|
||||||
|
Add any other context about the problem here.
|
16
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
16
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
---
|
||||||
|
name: Feature request
|
||||||
|
about: Suggest a gameplay feature or enhancement. Development tooling does not count.
|
||||||
|
title: 'Feature request:'
|
||||||
|
labels: feature request
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Describe the feature:**
|
||||||
|
A clear and concise description of the feature you wish to see implemented.
|
||||||
|
|
||||||
|
**Explain why this feature is necessary:**
|
||||||
|
A clear and concise explanation of why this feature is important. Consider what compromises are made to memory/performance and weigh your arguments against those.
|
||||||
|
|
||||||
|
**Are you planning on implementing this yourself? If so, explain the approach you intend to take:**
|
16
.github/ISSUE_TEMPLATE/optimization-idea.md
vendored
Normal file
16
.github/ISSUE_TEMPLATE/optimization-idea.md
vendored
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
---
|
||||||
|
name: Optimization idea
|
||||||
|
about: Suggest a potential memory or performance optimization.
|
||||||
|
title: 'Optimization: '
|
||||||
|
labels: optimization
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Describe the optimization:**
|
||||||
|
A clear and concise description of the optimization you wish to implement or see implemented.
|
||||||
|
|
||||||
|
**Explain why this feature is necessary:**
|
||||||
|
A clear and concise explanation of the effects of this optimization, and why it is important. Please remember that this project's priorities are **memory first, performance second**. If your suggestion requires compromising memory usage for performance, make a case for why the tradeoff is worth it.
|
||||||
|
|
||||||
|
**Are you planning on implementing this yourself? If so, explain the approach you intend to take:**
|
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@@ -54,8 +54,8 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
tag_name: latest
|
tag_name: latest
|
||||||
name: Latest build
|
name: Latest build
|
||||||
|
body: "This is a rolling release of bareiron, compiled from the latest commit on the main branch.\n\nThe executable (`bareiron.exe`) is a Cosmopolitan-built polyglot, and should run on Windows and Linux (and Mac?), despite the file extension."
|
||||||
files: bareiron.exe
|
files: bareiron.exe
|
||||||
overwrite: true
|
|
||||||
prerelease: true
|
prerelease: true
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
6
.gitignore
vendored
6
.gitignore
vendored
@@ -1,6 +1,12 @@
|
|||||||
|
a.out
|
||||||
|
*.exe
|
||||||
|
*.o
|
||||||
|
*.a
|
||||||
|
core
|
||||||
.vscode
|
.vscode
|
||||||
notchian
|
notchian
|
||||||
bareiron
|
bareiron
|
||||||
|
bareiron.exe
|
||||||
src/registries.c
|
src/registries.c
|
||||||
include/registries.h
|
include/registries.h
|
||||||
*.bin
|
*.bin
|
||||||
|
39
README.md
39
README.md
@@ -3,14 +3,38 @@ Minimalist Minecraft server for memory-restrictive embedded systems.
|
|||||||
|
|
||||||
The goal of this project is to enable hosting Minecraft servers on very weak devices, such as the ESP32. The project's priorities are, in order: **memory usage**, **performance**, and **features**. Because of this, compliance with vanilla Minecraft is not guaranteed, nor is it a goal of the project.
|
The goal of this project is to enable hosting Minecraft servers on very weak devices, such as the ESP32. The project's priorities are, in order: **memory usage**, **performance**, and **features**. Because of this, compliance with vanilla Minecraft is not guaranteed, nor is it a goal of the project.
|
||||||
|
|
||||||
## Quick start
|
- Minecraft version: `1.21.8`
|
||||||
Before compiling, you'll need to dump registry data from a vanilla Minecraft server. Create a folder called `notchian` here, and put a Minecraft server JAR in it. Then, follow [this guide](https://minecraft.wiki/w/Minecraft_Wiki:Projects/wiki.vg_merge/Data_Generators) to dump all of the registries. Finally, run `build_registries.js` with `node`, `bun`, or `deno`.
|
- Protocol version: `772`
|
||||||
|
|
||||||
- To target Linux, install `gcc` and run `build.sh`
|
> [!WARNING]
|
||||||
- To target an ESP variant, set up a PlatformIO project and clone this repository on top of it.
|
> Currently, only the vanilla client is officially supported. Issues have been reported when using Fabric or similar.
|
||||||
|
|
||||||
|
## Quick start
|
||||||
|
For PC x86_64 platforms, grab the [latest build binary](https://github.com/p2r3/bareiron/releases/download/latest/bareiron.exe) and run it. The file is a [Cosmopolitan polyglot](https://github.com/jart/cosmopolitan), which means it'll run on Windows, Linux, and possibly Mac, despite the file extension. Note that the server's default settings cannot be reconfigured without compiling from source.
|
||||||
|
|
||||||
|
For microcontrollers, see the section on **compilation** below.
|
||||||
|
|
||||||
|
## Compilation
|
||||||
|
Before compiling, you'll need to dump registry data from a vanilla Minecraft server. On Linux, this can be done automatically using the `extract_registries.sh` script. Otherwise, the manual process is as follows: create a folder called `notchian` here, and put a Minecraft server JAR in it. Then, follow [this guide](https://minecraft.wiki/w/Minecraft_Wiki:Projects/wiki.vg_merge/Data_Generators) to dump all of the registries (use the _second_ command with the `--all` flag). Finally, run `build_registries.js` with either [bun](https://bun.sh/), [node](https://nodejs.org/en/download), or [deno](https://docs.deno.com/runtime/getting_started/installation/).
|
||||||
|
|
||||||
|
- To compile on Linux, install `gcc` and run `./build.sh`.
|
||||||
|
- For compiling on Windows, there are a few options:
|
||||||
|
- To compile a native Windows binary: install [MSYS2](https://www.msys2.org/) and open the "MSYS2 MINGW64" shell. From there, run `pacman -Sy mingw-w64-x86_64-gcc`, navigate to this project's directory, and run `./build.sh`.
|
||||||
|
- To compile a native 32-bit binary (compatible with Windows 95/98, but why would you ever want that), use the same steps above, except with `pacman -Sy mingw-w64-cross-gcc` and `./build.sh --9x`.
|
||||||
|
- To compile a MSYS2-linked binary: install [MSYS2](https://www.msys2.org/), and open the "MSYS2 MSYS" shell. From there, install `gcc` (run `pacman -Sy gcc`), navigate to this project's directory and run `./build.sh`.
|
||||||
|
- To compile and run a Linux binary from Windows: install WSL, and from there install `gcc` and run `./build.sh` in this project's directory.
|
||||||
|
- To target an ESP variant, set up a PlatformIO project (select the ESP-IDF framework, **not Arduino**) and clone this repository on top of it. See **Configuration** below for further steps. For better performance, consider changing the clock speed and enabling compiler optimizations. If you don't know how to do this, there are plenty of resources online.
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
Most user-friendly configuration options are available in `include/globals.h`, including WiFi credentials for embedded setups. Of course, many more things can be configured by editing the source code.
|
Configuring the server requires compiling it from its source code as described in the section above.
|
||||||
|
|
||||||
|
Most user-friendly configuration options are available in `include/globals.h`, including WiFi credentials for embedded setups. Some other details, like the MOTD or starting time of day, can be found in `src/globals.c`. For everything else, you'll have to dig through the code.
|
||||||
|
|
||||||
|
Here's a summary of some of the more important yet less trivial options for those who plan to use this on a real microcontroller with real players:
|
||||||
|
|
||||||
|
- Depending on the player count, the performance of the MCU, and the bandwidth of your network, player position broadcasting could potentially throttle your connection. If you find this to be the case, try commenting out `BROADCAST_ALL_MOVEMENT` and `SCALE_MOVEMENT_UPDATES_TO_PLAYER_COUNT`. This will tie movement to the tickrate. If this change makes movement too choppy, you can decrease `TIME_BETWEEN_TICKS` at the cost of more compute.
|
||||||
|
- If you experience crashes or instability related to chests or water, those features can be disabled with `ALLOW_CHESTS` and `DO_FLUID_FLOW`, respectively.
|
||||||
|
- If you find frequent repeated chunk generation to choke the server, increasing `VISITED_HISTORY` might help. There isn't _that_ much of a memory footprint for this - increasing it to `64` for example would only take up 240 extra bytes per allocated player.
|
||||||
|
|
||||||
## Non-volatile storage (optional)
|
## Non-volatile storage (optional)
|
||||||
This section applies to those who target ESP variants and wish to persist world data after a shutdown. *This is not necessary on PC platforms*, as world and player data is written to `world.bin` by default.
|
This section applies to those who target ESP variants and wish to persist world data after a shutdown. *This is not necessary on PC platforms*, as world and player data is written to `world.bin` by default.
|
||||||
@@ -22,5 +46,8 @@ If using an SD card module or other virtual file system, you'll have to implemen
|
|||||||
Alternatively, if you can't set up a file system, you can dump and upload world data over TCP. This can be enabled by uncommenting `DEV_ENABLE_BEEF_DUMPS` in `globals.h`. *Note: this system implements no security or authentication.* With this option enabled, anyone with access to the server can upload arbitrary world data.
|
Alternatively, if you can't set up a file system, you can dump and upload world data over TCP. This can be enabled by uncommenting `DEV_ENABLE_BEEF_DUMPS` in `globals.h`. *Note: this system implements no security or authentication.* With this option enabled, anyone with access to the server can upload arbitrary world data.
|
||||||
|
|
||||||
## Contribution
|
## Contribution
|
||||||
- Create issues and discuss with the maintainer(s) before making pull requests.
|
- Create issues and discuss with the maintainer(s) before making pull requests. Even for small changes.
|
||||||
- Follow the existing code style. Ensure that your changes fit in with the surrounding code, even if you disagree with the style. Pull requests with inconsistent style will be nitpicked.
|
- Follow the existing code style. Ensure that your changes fit in with the surrounding code, even if you disagree with the style. Pull requests with inconsistent style will be nitpicked.
|
||||||
|
- Test your code before creating a pull request or requesting a review, regardless of how "simple" your change is. It's a basic form of respect towards the maintainer and reviewer.
|
||||||
|
- Development tooling and compilation improvements _are not welcome,_ unless you've worked with the codebase long enough to have noticed practical shortcomings in that area. Adding a single compiler flag is not a meaningful first contribution.
|
||||||
|
- For information on the Minecraft server protocol, [refer to the wiki](https://minecraft.wiki/w/Java_Edition_protocol/Packets). For everything else, use a [search engine](https://google.com).
|
48
build.sh
48
build.sh
@@ -1,3 +1,45 @@
|
|||||||
rm bareiron
|
#!/usr/bin/env bash
|
||||||
gcc src/*.c -O3 -Iinclude -o bareiron
|
|
||||||
./bareiron
|
# Check for registries before attempting to compile, prevents confusion
|
||||||
|
if [ ! -f "include/registries.h" ]; then
|
||||||
|
echo "Error: 'include/registries.h' is missing."
|
||||||
|
echo "Please follow the 'Compilation' section of the README to generate it."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Figure out executable suffix (for MSYS compilation)
|
||||||
|
case "$OSTYPE" in
|
||||||
|
msys*|cygwin*|win32*) exe=".exe" ;;
|
||||||
|
*) exe="" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# mingw64-specific linker options
|
||||||
|
windows_linker=""
|
||||||
|
unameOut="$(uname -s)"
|
||||||
|
case "$unameOut" in
|
||||||
|
MINGW64_NT*)
|
||||||
|
windows_linker="-static -lws2_32 -pthread"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# Default compiler
|
||||||
|
compiler="gcc"
|
||||||
|
|
||||||
|
# Handle arguments for windows 9x build
|
||||||
|
for arg in "$@"; do
|
||||||
|
case $arg in
|
||||||
|
--9x)
|
||||||
|
if [[ "$unameOut" == MINGW64_NT* ]]; then
|
||||||
|
compiler="/opt/bin/i686-w64-mingw32-gcc"
|
||||||
|
windows_linker="$windows_linker -Wl,--subsystem,console:4"
|
||||||
|
else
|
||||||
|
echo "Error: Compiling for Windows 9x is only supported when running under the MinGW64 shell."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
rm -f "bareiron$exe"
|
||||||
|
$compiler src/*.c -O3 -Iinclude -o "bareiron$exe" $windows_linker
|
||||||
|
"./bareiron$exe"
|
||||||
|
1
compile_flags.txt
Normal file
1
compile_flags.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
-Iinclude
|
73
extract_registries.sh
Executable file
73
extract_registries.sh
Executable file
@@ -0,0 +1,73 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
REQUIRED_MAJOR=21
|
||||||
|
SERVER_JAR="${SERVER_JAR:-server.jar}"
|
||||||
|
NOTCHIAN_DIR="notchian"
|
||||||
|
JS_RUNTIME=""
|
||||||
|
|
||||||
|
get_java_version() {
|
||||||
|
java -version 2>&1 | awk -F[\".] '/version/ {print $2}'
|
||||||
|
}
|
||||||
|
|
||||||
|
check_java() {
|
||||||
|
if ! command -v java >/dev/null 2>&1; then
|
||||||
|
echo "Java not found in PATH."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
local major
|
||||||
|
major="$(get_java_version)"
|
||||||
|
|
||||||
|
if (( major < REQUIRED_MAJOR )); then
|
||||||
|
echo "Java $REQUIRED_MAJOR or newer required, but found Java $major."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
prepare_notchian_dir() {
|
||||||
|
if [[ ! -d "$NOTCHIAN_DIR" ]]; then
|
||||||
|
echo "Creating $NOTCHIAN_DIR directory..."
|
||||||
|
mkdir -p "$NOTCHIAN_DIR"
|
||||||
|
fi
|
||||||
|
cd "$NOTCHIAN_DIR"
|
||||||
|
}
|
||||||
|
|
||||||
|
dump_registries() {
|
||||||
|
if [[ ! -f "$SERVER_JAR" ]]; then
|
||||||
|
echo "No server.jar found (looked for $SERVER_JAR)."
|
||||||
|
echo "Please download the server.jar from https://www.minecraft.net/en-us/download/server"
|
||||||
|
echo "and place it in the notchian directory."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
java -DbundlerMainClass="net.minecraft.data.Main" -jar "$SERVER_JAR" --all
|
||||||
|
}
|
||||||
|
|
||||||
|
detect_js_runtime() {
|
||||||
|
if command -v node >/dev/null 2>&1; then
|
||||||
|
JS_RUNTIME="node"
|
||||||
|
elif command -v bun >/dev/null 2>&1; then
|
||||||
|
JS_RUNTIME="bun"
|
||||||
|
elif command -v deno >/dev/null 2>&1; then
|
||||||
|
JS_RUNTIME="deno run"
|
||||||
|
else
|
||||||
|
echo "No JavaScript runtime found (Node.js, Bun, or Deno)."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
run_js_script() {
|
||||||
|
local script="$1"
|
||||||
|
if [[ -z "$JS_RUNTIME" ]]; then
|
||||||
|
detect_js_runtime
|
||||||
|
fi
|
||||||
|
echo "Running $script with $JS_RUNTIME..."
|
||||||
|
$JS_RUNTIME "$script"
|
||||||
|
}
|
||||||
|
|
||||||
|
check_java
|
||||||
|
prepare_notchian_dir
|
||||||
|
dump_registries
|
||||||
|
run_js_script "../build_registries.js"
|
||||||
|
echo "Registry dump and processing complete."
|
@@ -34,20 +34,12 @@
|
|||||||
// Max render distance, determines how many chunks to send
|
// Max render distance, determines how many chunks to send
|
||||||
#define VIEW_DISTANCE 2
|
#define VIEW_DISTANCE 2
|
||||||
|
|
||||||
// Time between server ticks in microseconds (default = 1s)
|
// Time between server ticks in microseconds (default = 0.05s)
|
||||||
#define TIME_BETWEEN_TICKS 1000000
|
#define TIME_BETWEEN_TICKS 50000
|
||||||
|
|
||||||
// Calculated from TIME_BETWEEN_TICKS
|
// Calculated from TIME_BETWEEN_TICKS
|
||||||
#define TICKS_PER_SECOND ((float)1000000 / TIME_BETWEEN_TICKS)
|
#define TICKS_PER_SECOND ((float)1000000 / TIME_BETWEEN_TICKS)
|
||||||
|
|
||||||
// Initial world generation seed, will be hashed on startup
|
|
||||||
// Used in generating terrain and biomes
|
|
||||||
#define INITIAL_WORLD_SEED 0xA103DE6C
|
|
||||||
|
|
||||||
// Initial general RNG seed, will be hashed on startup
|
|
||||||
// Used in random game events like item drops and mob behavior
|
|
||||||
#define INITIAL_RNG_SEED 0xE2B9419
|
|
||||||
|
|
||||||
// Size of each bilinearly interpolated area ("minichunk")
|
// Size of each bilinearly interpolated area ("minichunk")
|
||||||
// For best performance, CHUNK_SIZE should be a power of 2
|
// For best performance, CHUNK_SIZE should be a power of 2
|
||||||
#define CHUNK_SIZE 8
|
#define CHUNK_SIZE 8
|
||||||
@@ -74,6 +66,10 @@
|
|||||||
// Determines the fixed amount of memory allocated to blocks
|
// Determines the fixed amount of memory allocated to blocks
|
||||||
#define MAX_BLOCK_CHANGES 20000
|
#define MAX_BLOCK_CHANGES 20000
|
||||||
|
|
||||||
|
// How many "deferred" (happen at a later time than triggered) block updates to store.
|
||||||
|
// Determines the fixed amount of memory allocated to that list
|
||||||
|
#define MAX_DEFERRED_BLOCK_UPDATES 64
|
||||||
|
|
||||||
// If defined, writes and reads world data to/from disk (or flash).
|
// If defined, writes and reads world data to/from disk (or flash).
|
||||||
// This is a synchronous operation, and can cause performance issues if
|
// This is a synchronous operation, and can cause performance issues if
|
||||||
// frequent random disk access is slow. Data is still stored in and
|
// frequent random disk access is slow. Data is still stored in and
|
||||||
@@ -102,9 +98,26 @@
|
|||||||
// clients from Keep Alive packets.
|
// clients from Keep Alive packets.
|
||||||
#define NETWORK_TIMEOUT_TIME 15000000
|
#define NETWORK_TIMEOUT_TIME 15000000
|
||||||
|
|
||||||
|
// Size of the receive buffer for incoming string data
|
||||||
|
#define MAX_RECV_BUF_LEN 256
|
||||||
|
|
||||||
|
// If defined, sends the server brand to clients. Doesn't do much, but will
|
||||||
|
// show up in the top-left of the F3/debug menu, in the Minecraft client.
|
||||||
|
// You can change the brand string in the "brand" variable in src/globals.c
|
||||||
|
#define SEND_BRAND
|
||||||
|
|
||||||
|
// If defined, rebroadcasts ALL incoming movement updates, disconnecting
|
||||||
|
// movement from the server's tickrate. This makes movement much smoother
|
||||||
|
// on very low tickrates, at the cost of potential network instability when
|
||||||
|
// hosting more than just a couple of players. When disabling this on low
|
||||||
|
// tickrates, consider disabling SCALE_MOVEMENT_UPDATES_TO_PLAYER_COUNT too.
|
||||||
|
#define BROADCAST_ALL_MOVEMENT
|
||||||
|
|
||||||
// If defined, scales the frequency at which player movement updates are
|
// If defined, scales the frequency at which player movement updates are
|
||||||
// broadcast based on the amount of players, reducing overhead for higher
|
// broadcast based on the amount of players, reducing overhead for higher
|
||||||
// player counts. For very many players, makes movement look jittery.
|
// player counts. For very many players, makes movement look jittery.
|
||||||
|
// It is not recommended to use this if BROADCAST_ALL_MOVEMENT is disabled
|
||||||
|
// on low tickrates, as that might drastically decrease the update rate.
|
||||||
#define SCALE_MOVEMENT_UPDATES_TO_PLAYER_COUNT
|
#define SCALE_MOVEMENT_UPDATES_TO_PLAYER_COUNT
|
||||||
|
|
||||||
// If defined, calculates fluid flow when blocks are updated near fluids
|
// If defined, calculates fluid flow when blocks are updated near fluids
|
||||||
@@ -128,6 +141,9 @@
|
|||||||
// every time a block is broken.
|
// every time a block is broken.
|
||||||
#define ENABLE_PICKUP_ANIMATION
|
#define ENABLE_PICKUP_ANIMATION
|
||||||
|
|
||||||
|
// If defined, players are able to receive damage from nearby cacti.
|
||||||
|
#define ENABLE_CACTUS_DAMAGE
|
||||||
|
|
||||||
// If defined, logs unrecognized packet IDs
|
// If defined, logs unrecognized packet IDs
|
||||||
// #define DEV_LOG_UNKNOWN_PACKETS
|
// #define DEV_LOG_UNKNOWN_PACKETS
|
||||||
|
|
||||||
@@ -135,7 +151,7 @@
|
|||||||
#define DEV_LOG_LENGTH_DISCREPANCY
|
#define DEV_LOG_LENGTH_DISCREPANCY
|
||||||
|
|
||||||
// If defined, log chunk generation events
|
// If defined, log chunk generation events
|
||||||
#define DEV_LOG_CHUNK_GENERATION
|
// #define DEV_LOG_CHUNK_GENERATION
|
||||||
|
|
||||||
// If defined, allows dumping world data by sending 0xBEEF (big-endian),
|
// If defined, allows dumping world data by sending 0xBEEF (big-endian),
|
||||||
// and uploading world data by sending 0xFEED, followed by the data buffer.
|
// and uploading world data by sending 0xFEED, followed by the data buffer.
|
||||||
@@ -150,7 +166,7 @@
|
|||||||
#define STATE_PLAY 5
|
#define STATE_PLAY 5
|
||||||
|
|
||||||
extern ssize_t recv_count;
|
extern ssize_t recv_count;
|
||||||
extern uint8_t recv_buffer[256];
|
extern uint8_t recv_buffer[MAX_RECV_BUF_LEN];
|
||||||
|
|
||||||
extern uint32_t world_seed;
|
extern uint32_t world_seed;
|
||||||
extern uint32_t rng_seed;
|
extern uint32_t rng_seed;
|
||||||
@@ -161,17 +177,40 @@ extern uint32_t server_ticks;
|
|||||||
extern char motd[];
|
extern char motd[];
|
||||||
extern uint8_t motd_len;
|
extern uint8_t motd_len;
|
||||||
|
|
||||||
extern uint16_t client_count;
|
#ifdef SEND_BRAND
|
||||||
|
extern char brand[];
|
||||||
|
extern uint8_t brand_len;
|
||||||
|
#endif
|
||||||
|
|
||||||
#pragma pack(push, 1)
|
extern uint16_t client_count;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
short x;
|
short x;
|
||||||
uint8_t y;
|
|
||||||
short z;
|
short z;
|
||||||
|
uint8_t y;
|
||||||
uint8_t block;
|
uint8_t block;
|
||||||
} BlockChange;
|
} BlockChange;
|
||||||
|
|
||||||
|
#define UPDATE_BASIC (1 << 0)
|
||||||
|
// the sand at this position will be moved down immediately when this is processed
|
||||||
|
#define UPDATE_FALL_SAND (1 << 1)
|
||||||
|
#define UPDATE_FLOW_WATER (1 << 2)
|
||||||
|
// the sand below this block will fall soon
|
||||||
|
#define UPDATE_CHECK_SAND_FALL (1 << 3)
|
||||||
|
#define UPDATE_CHECK_WATER (1 << 4)
|
||||||
|
|
||||||
|
#define UPDATE_NOW (UPDATE_BASIC | UPDATE_CHECK_SAND_FALL | UPDATE_CHECK_WATER)
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
short update_kind;
|
||||||
|
short x;
|
||||||
|
short z;
|
||||||
|
uint8_t y;
|
||||||
|
uint8_t await_ticks;
|
||||||
|
} DeferredBlockUpdate;
|
||||||
|
|
||||||
|
#pragma pack(push, 1)
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
uint8_t uuid[16];
|
uint8_t uuid[16];
|
||||||
char name[16];
|
char name[16];
|
||||||
@@ -207,6 +246,7 @@ typedef struct {
|
|||||||
// 0x08 - sprinting
|
// 0x08 - sprinting
|
||||||
// 0x10 - eating, makes flagval_16 act as eating timer
|
// 0x10 - eating, makes flagval_16 act as eating timer
|
||||||
// 0x20 - client loading, uses flagval_16 as fallback timer
|
// 0x20 - client loading, uses flagval_16 as fallback timer
|
||||||
|
// 0x40 - movement update cooldown
|
||||||
uint8_t flags;
|
uint8_t flags;
|
||||||
} PlayerData;
|
} PlayerData;
|
||||||
|
|
||||||
@@ -218,15 +258,33 @@ typedef struct {
|
|||||||
uint8_t y;
|
uint8_t y;
|
||||||
short z;
|
short z;
|
||||||
// Lower 5 bits: health
|
// Lower 5 bits: health
|
||||||
// Upper 3 bits: reserved (?)
|
// Middle 1 bit: sheep sheared, unused for other mobs
|
||||||
|
// Upper 2 bits: panic timer
|
||||||
uint8_t data;
|
uint8_t data;
|
||||||
} MobData;
|
} MobData;
|
||||||
|
|
||||||
#pragma pack(pop)
|
#pragma pack(pop)
|
||||||
|
|
||||||
|
union EntityDataValue {
|
||||||
|
uint8_t byte;
|
||||||
|
int pose;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint8_t index;
|
||||||
|
// 0 - Byte
|
||||||
|
// 21 - Pose
|
||||||
|
int type;
|
||||||
|
union EntityDataValue value;
|
||||||
|
} EntityData;
|
||||||
|
|
||||||
extern BlockChange block_changes[MAX_BLOCK_CHANGES];
|
extern BlockChange block_changes[MAX_BLOCK_CHANGES];
|
||||||
extern int block_changes_count;
|
extern int block_changes_count;
|
||||||
|
|
||||||
|
extern DeferredBlockUpdate deferred_block_updates[MAX_DEFERRED_BLOCK_UPDATES];
|
||||||
|
extern int deferred_block_updates_count;
|
||||||
|
extern uint8_t is_processing_deferred_block_updates;
|
||||||
|
|
||||||
extern PlayerData player_data[MAX_PLAYERS];
|
extern PlayerData player_data[MAX_PLAYERS];
|
||||||
extern int player_data_count;
|
extern int player_data_count;
|
||||||
|
|
||||||
|
@@ -14,6 +14,7 @@ int cs_setPlayerPosition (int client_fd, double *x, double *y, double *z, uint8_
|
|||||||
int cs_setPlayerRotation (int client_fd, float *yaw, float *pitch, uint8_t *on_ground);
|
int cs_setPlayerRotation (int client_fd, float *yaw, float *pitch, uint8_t *on_ground);
|
||||||
int cs_setPlayerMovementFlags (int client_fd, uint8_t *on_ground);
|
int cs_setPlayerMovementFlags (int client_fd, uint8_t *on_ground);
|
||||||
int cs_setHeldItem (int client_fd);
|
int cs_setHeldItem (int client_fd);
|
||||||
|
int cs_swingArm (int client_fd);
|
||||||
int cs_clickContainer (int client_fd);
|
int cs_clickContainer (int client_fd);
|
||||||
int cs_closeContainer (int client_fd);
|
int cs_closeContainer (int client_fd);
|
||||||
int cs_clientStatus (int client_fd);
|
int cs_clientStatus (int client_fd);
|
||||||
@@ -21,11 +22,13 @@ int cs_chat (int client_fd);
|
|||||||
int cs_interact (int client_fd);
|
int cs_interact (int client_fd);
|
||||||
int cs_playerInput (int client_fd);
|
int cs_playerInput (int client_fd);
|
||||||
int cs_playerCommand (int client_fd);
|
int cs_playerCommand (int client_fd);
|
||||||
|
int cs_playerLoaded (int client_fd);
|
||||||
|
|
||||||
// Clientbound packets
|
// Clientbound packets
|
||||||
int sc_statusResponse (int client_fd);
|
int sc_statusResponse (int client_fd);
|
||||||
int sc_loginSuccess (int client_fd, uint8_t *uuid, char *name);
|
int sc_loginSuccess (int client_fd, uint8_t *uuid, char *name);
|
||||||
int sc_knownPacks (int client_fd);
|
int sc_knownPacks (int client_fd);
|
||||||
|
int sc_sendPluginMessage (int client_fd, const char *channel, const uint8_t *data, size_t data_len);
|
||||||
int sc_finishConfiguration (int client_fd);
|
int sc_finishConfiguration (int client_fd);
|
||||||
int sc_loginPlay (int client_fd);
|
int sc_loginPlay (int client_fd);
|
||||||
int sc_synchronizePlayerPosition (int client_fd, double x, double y, double z, float yaw, float pitch);
|
int sc_synchronizePlayerPosition (int client_fd, double x, double y, double z, float yaw, float pitch);
|
||||||
@@ -45,6 +48,8 @@ int sc_acknowledgeBlockChange (int client_fd, int sequence);
|
|||||||
int sc_playerInfoUpdateAddPlayer (int client_fd, PlayerData player);
|
int sc_playerInfoUpdateAddPlayer (int client_fd, PlayerData player);
|
||||||
int sc_spawnEntity (int client_fd, int id, uint8_t *uuid, int type, double x, double y, double z, uint8_t yaw, uint8_t pitch);
|
int sc_spawnEntity (int client_fd, int id, uint8_t *uuid, int type, double x, double y, double z, uint8_t yaw, uint8_t pitch);
|
||||||
int sc_spawnEntityPlayer (int client_fd, PlayerData player);
|
int sc_spawnEntityPlayer (int client_fd, PlayerData player);
|
||||||
|
int sc_setEntityMetadata (int client_fd, int id, EntityData *metadata, size_t length);
|
||||||
|
int sc_entityAnimation (int client_fd, int id, uint8_t animation);
|
||||||
int sc_teleportEntity (int client_fd, int id, double x, double y, double z, float yaw, float pitch);
|
int sc_teleportEntity (int client_fd, int id, double x, double y, double z, float yaw, float pitch);
|
||||||
int sc_setHeadRotation (int client_fd, int id, uint8_t yaw);
|
int sc_setHeadRotation (int client_fd, int id, uint8_t yaw);
|
||||||
int sc_updateEntityRotation (int client_fd, int id, uint8_t yaw, uint8_t pitch);
|
int sc_updateEntityRotation (int client_fd, int id, uint8_t yaw, uint8_t pitch);
|
||||||
|
@@ -14,11 +14,16 @@ int getClientIndex (int client_fd);
|
|||||||
void resetPlayerData (PlayerData *player);
|
void resetPlayerData (PlayerData *player);
|
||||||
int reservePlayerData (int client_fd, uint8_t *uuid, char* name);
|
int reservePlayerData (int client_fd, uint8_t *uuid, char* name);
|
||||||
int getPlayerData (int client_fd, PlayerData **output);
|
int getPlayerData (int client_fd, PlayerData **output);
|
||||||
|
PlayerData *getPlayerByName (int start_offset, int end_offset, uint8_t *buffer);
|
||||||
void handlePlayerDisconnect (int client_fd);
|
void handlePlayerDisconnect (int client_fd);
|
||||||
|
void handlePlayerJoin (PlayerData* player);
|
||||||
void disconnectClient (int *client_fd, int cause);
|
void disconnectClient (int *client_fd, int cause);
|
||||||
int givePlayerItem (PlayerData *player, uint16_t item, uint8_t count);
|
int givePlayerItem (PlayerData *player, uint16_t item, uint8_t count);
|
||||||
void spawnPlayer (PlayerData *player);
|
void spawnPlayer (PlayerData *player);
|
||||||
|
|
||||||
|
void broadcastPlayerMetadata (PlayerData *player);
|
||||||
|
void broadcastMobMetadata (int client_fd, int entity_id);
|
||||||
|
|
||||||
uint8_t serverSlotToClientSlot (int window_id, uint8_t slot);
|
uint8_t serverSlotToClientSlot (int window_id, uint8_t slot);
|
||||||
uint8_t clientSlotToServerSlot (int window_id, uint8_t slot);
|
uint8_t clientSlotToServerSlot (int window_id, uint8_t slot);
|
||||||
|
|
||||||
@@ -27,8 +32,11 @@ uint8_t makeBlockChange (short x, uint8_t y, short z, uint8_t block);
|
|||||||
|
|
||||||
uint8_t isInstantlyMined (PlayerData *player, uint8_t block);
|
uint8_t isInstantlyMined (PlayerData *player, uint8_t block);
|
||||||
uint8_t isColumnBlock (uint8_t block);
|
uint8_t isColumnBlock (uint8_t block);
|
||||||
|
uint8_t isFallingBlock (uint8_t block);
|
||||||
uint8_t isPassableBlock (uint8_t block);
|
uint8_t isPassableBlock (uint8_t block);
|
||||||
|
uint8_t isPassableSpawnBlock (uint8_t block);
|
||||||
uint8_t isReplaceableBlock (uint8_t block);
|
uint8_t isReplaceableBlock (uint8_t block);
|
||||||
|
uint8_t getFluid (uint8_t block);
|
||||||
uint32_t isCompostItem (uint16_t item);
|
uint32_t isCompostItem (uint16_t item);
|
||||||
uint8_t getItemStackSize (uint16_t item);
|
uint8_t getItemStackSize (uint16_t item);
|
||||||
|
|
||||||
@@ -37,12 +45,21 @@ void bumpToolDurability (PlayerData *player);
|
|||||||
void handlePlayerAction (PlayerData *player, int action, short x, short y, short z);
|
void handlePlayerAction (PlayerData *player, int action, short x, short y, short z);
|
||||||
void handlePlayerUseItem (PlayerData *player, short x, short y, short z, uint8_t face);
|
void handlePlayerUseItem (PlayerData *player, short x, short y, short z, uint8_t face);
|
||||||
|
|
||||||
void checkFluidUpdate (short x, uint8_t y, short z, uint8_t block);
|
void processBlockUpdate (short x, uint8_t y, short z, uint8_t block, short update_kind);
|
||||||
|
void updateXZNeighbors (short x, uint8_t y, short z, short update_kind);
|
||||||
|
void updateXYZNeighbors (short x, uint8_t y, short z, short update_kind);
|
||||||
|
void deferBlockUpdate (short x, uint8_t y, short z, uint8_t await_ticks, short update_kind);
|
||||||
|
|
||||||
void spawnMob (uint8_t type, short x, uint8_t y, short z, uint8_t health);
|
void spawnMob (uint8_t type, short x, uint8_t y, short z, uint8_t health);
|
||||||
|
void interactEntity (int entity_id, int interactor_id);
|
||||||
void hurtEntity (int entity_id, int attacker_id, uint8_t damage_type, uint8_t damage);
|
void hurtEntity (int entity_id, int attacker_id, uint8_t damage_type, uint8_t damage);
|
||||||
void handleServerTick (int64_t time_since_last_tick);
|
void handleServerTick (int64_t time_since_last_tick);
|
||||||
|
|
||||||
void broadcastChestUpdate (int origin_fd, uint8_t *storage_ptr, uint16_t item, uint8_t count, uint8_t slot);
|
void broadcastChestUpdate (int origin_fd, uint8_t *storage_ptr, uint16_t item, uint8_t count, uint8_t slot);
|
||||||
|
|
||||||
|
ssize_t writeEntityData (int client_fd, EntityData *data);
|
||||||
|
|
||||||
|
int sizeEntityData (EntityData *data);
|
||||||
|
int sizeEntityMetadata (EntityData *metadata, size_t length);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@@ -8,11 +8,13 @@
|
|||||||
void writeBlockChangesToDisk (int from, int to);
|
void writeBlockChangesToDisk (int from, int to);
|
||||||
void writeChestChangesToDisk (uint8_t *storage_ptr, uint8_t slot);
|
void writeChestChangesToDisk (uint8_t *storage_ptr, uint8_t slot);
|
||||||
void writePlayerDataToDisk ();
|
void writePlayerDataToDisk ();
|
||||||
|
void writeDataToDiskOnInterval ();
|
||||||
#else
|
#else
|
||||||
// Define no-op placeholders for when disk syncing isn't enabled
|
// Define no-op placeholders for when disk syncing isn't enabled
|
||||||
#define writeBlockChangesToDisk(a, b)
|
#define writeBlockChangesToDisk(a, b)
|
||||||
#define writeChestChangesToDisk(a, b)
|
#define writeChestChangesToDisk(a, b)
|
||||||
#define writePlayerDataToDisk()
|
#define writePlayerDataToDisk()
|
||||||
|
#define writeDataToDiskOnInterval()
|
||||||
#define initSerializer() 0
|
#define initSerializer() 0
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@@ -5,10 +5,10 @@
|
|||||||
|
|
||||||
#include "globals.h"
|
#include "globals.h"
|
||||||
|
|
||||||
inline int mod_abs (int a, int b) {
|
static inline int mod_abs (int a, int b) {
|
||||||
return ((a % b) + b) % b;
|
return ((a % b) + b) % b;
|
||||||
}
|
}
|
||||||
inline int div_floor (int a, int b) {
|
static inline int div_floor (int a, int b) {
|
||||||
return a % b < 0 ? (a - b) / b : a / b;
|
return a % b < 0 ? (a - b) / b : a / b;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,7 +32,9 @@ int64_t readInt64 (int client_fd);
|
|||||||
float readFloat (int client_fd);
|
float readFloat (int client_fd);
|
||||||
double readDouble (int client_fd);
|
double readDouble (int client_fd);
|
||||||
|
|
||||||
|
ssize_t readLengthPrefixedData (int client_fd);
|
||||||
void readString (int client_fd);
|
void readString (int client_fd);
|
||||||
|
void readStringN (int client_fd, uint32_t max_length);
|
||||||
|
|
||||||
uint32_t fast_rand ();
|
uint32_t fast_rand ();
|
||||||
uint64_t splitmix64 (uint64_t state);
|
uint64_t splitmix64 (uint64_t state);
|
||||||
|
@@ -1,6 +1,11 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <arpa/inet.h>
|
#ifdef _WIN32
|
||||||
|
#include <winsock2.h>
|
||||||
|
#include <ws2tcpip.h>
|
||||||
|
#else
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
#endif
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
#include "globals.h"
|
#include "globals.h"
|
||||||
@@ -24,10 +29,10 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
ssize_t recv_count;
|
ssize_t recv_count;
|
||||||
uint8_t recv_buffer[256] = {0};
|
uint8_t recv_buffer[MAX_RECV_BUF_LEN] = {0};
|
||||||
|
|
||||||
uint32_t world_seed = INITIAL_WORLD_SEED;
|
uint32_t world_seed;
|
||||||
uint32_t rng_seed = INITIAL_RNG_SEED;
|
uint32_t rng_seed;
|
||||||
|
|
||||||
uint16_t world_time = 0;
|
uint16_t world_time = 0;
|
||||||
uint32_t server_ticks = 0;
|
uint32_t server_ticks = 0;
|
||||||
@@ -35,11 +40,20 @@ uint32_t server_ticks = 0;
|
|||||||
char motd[] = { "A bareiron server" };
|
char motd[] = { "A bareiron server" };
|
||||||
uint8_t motd_len = sizeof(motd) - 1;
|
uint8_t motd_len = sizeof(motd) - 1;
|
||||||
|
|
||||||
|
#ifdef SEND_BRAND
|
||||||
|
char brand[] = { "bareiron" };
|
||||||
|
uint8_t brand_len = sizeof(brand) - 1;
|
||||||
|
#endif
|
||||||
|
|
||||||
uint16_t client_count;
|
uint16_t client_count;
|
||||||
|
|
||||||
BlockChange block_changes[MAX_BLOCK_CHANGES];
|
BlockChange block_changes[MAX_BLOCK_CHANGES];
|
||||||
int block_changes_count = 0;
|
int block_changes_count = 0;
|
||||||
|
|
||||||
|
DeferredBlockUpdate deferred_block_updates[MAX_DEFERRED_BLOCK_UPDATES];
|
||||||
|
int deferred_block_updates_count = 0;
|
||||||
|
uint8_t is_processing_deferred_block_updates = 0;
|
||||||
|
|
||||||
PlayerData player_data[MAX_PLAYERS];
|
PlayerData player_data[MAX_PLAYERS];
|
||||||
int player_data_count = 0;
|
int player_data_count = 0;
|
||||||
|
|
||||||
|
382
src/main.c
382
src/main.c
@@ -19,9 +19,16 @@
|
|||||||
#include "lwip/netdb.h"
|
#include "lwip/netdb.h"
|
||||||
#else
|
#else
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
#include <sys/socket.h>
|
#ifdef _WIN32
|
||||||
#include <netinet/in.h>
|
#include <winsock2.h>
|
||||||
#include <arpa/inet.h>
|
#include <ws2tcpip.h>
|
||||||
|
#else
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <netinet/in.h>
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
#include <sys/select.h>
|
||||||
|
#include <sys/random.h>
|
||||||
|
#endif
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
#endif
|
#endif
|
||||||
@@ -85,6 +92,10 @@ void handlePacket (int client_fd, int length, int packet_id, int state) {
|
|||||||
if (cs_clientInformation(client_fd)) break;
|
if (cs_clientInformation(client_fd)) break;
|
||||||
if (sc_knownPacks(client_fd)) break;
|
if (sc_knownPacks(client_fd)) break;
|
||||||
if (sc_registries(client_fd)) break;
|
if (sc_registries(client_fd)) break;
|
||||||
|
|
||||||
|
#ifdef SEND_BRAND
|
||||||
|
if (sc_sendPluginMessage(client_fd, "minecraft:brand", (uint8_t *)brand, brand_len)) break;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -122,34 +133,32 @@ void handlePacket (int client_fd, int length, int packet_id, int state) {
|
|||||||
// Send full client spawn sequence
|
// Send full client spawn sequence
|
||||||
spawnPlayer(player);
|
spawnPlayer(player);
|
||||||
|
|
||||||
// Prepare join message for broadcast
|
// Register all existing players and spawn their entities
|
||||||
uint8_t player_name_len = strlen(player->name);
|
|
||||||
strcpy((char *)recv_buffer, player->name);
|
|
||||||
strcpy((char *)recv_buffer + player_name_len, " joined the game");
|
|
||||||
|
|
||||||
// Register all existing players and spawn their entities, and broadcast
|
|
||||||
// information about the joining player to all existing players.
|
|
||||||
for (int i = 0; i < MAX_PLAYERS; i ++) {
|
for (int i = 0; i < MAX_PLAYERS; i ++) {
|
||||||
if (player_data[i].client_fd == -1) continue;
|
if (player_data[i].client_fd == -1) continue;
|
||||||
if (player_data[i].flags & 0x20 && player_data[i].client_fd != client_fd) continue;
|
// Note that this will also filter out the joining player
|
||||||
|
if (player_data[i].flags & 0x20) continue;
|
||||||
sc_playerInfoUpdateAddPlayer(client_fd, player_data[i]);
|
sc_playerInfoUpdateAddPlayer(client_fd, player_data[i]);
|
||||||
sc_systemChat(player_data[i].client_fd, (char *)recv_buffer, 16 + player_name_len);
|
|
||||||
if (player_data[i].client_fd == client_fd) continue;
|
|
||||||
sc_playerInfoUpdateAddPlayer(player_data[i].client_fd, *player);
|
|
||||||
sc_spawnEntityPlayer(client_fd, player_data[i]);
|
sc_spawnEntityPlayer(client_fd, player_data[i]);
|
||||||
sc_spawnEntityPlayer(player_data[i].client_fd, *player);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send information about all other entities (mobs)
|
// Send information about all other entities (mobs):
|
||||||
// For more info on the arguments, see the spawnMob function
|
// Use a random number for the first half of the UUID
|
||||||
|
uint8_t uuid[16];
|
||||||
|
uint32_t r = fast_rand();
|
||||||
|
memcpy(uuid, &r, 4);
|
||||||
|
// Send allocated living mobs, use ID for second half of UUID
|
||||||
for (int i = 0; i < MAX_MOBS; i ++) {
|
for (int i = 0; i < MAX_MOBS; i ++) {
|
||||||
if (mob_data[i].type == 0) continue;
|
if (mob_data[i].type == 0) continue;
|
||||||
if ((mob_data[i].data & 31) == 0) continue;
|
if ((mob_data[i].data & 31) == 0) continue;
|
||||||
|
memcpy(uuid + 4, &i, 4);
|
||||||
|
// For more info on the arguments here, see the spawnMob function
|
||||||
sc_spawnEntity(
|
sc_spawnEntity(
|
||||||
client_fd, -2 - i, recv_buffer,
|
client_fd, -2 - i, uuid,
|
||||||
mob_data[i].type, mob_data[i].x, mob_data[i].y, mob_data[i].z,
|
mob_data[i].type, mob_data[i].x, mob_data[i].y, mob_data[i].z,
|
||||||
0, 0
|
0, 0
|
||||||
);
|
);
|
||||||
|
broadcastMobMetadata(client_fd, -2 - i);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -212,16 +221,18 @@ void handlePacket (int client_fd, int length, int packet_id, int state) {
|
|||||||
PlayerData *player;
|
PlayerData *player;
|
||||||
if (getPlayerData(client_fd, &player)) break;
|
if (getPlayerData(client_fd, &player)) break;
|
||||||
|
|
||||||
|
uint8_t block_feet = getBlockAt(player->x, player->y, player->z);
|
||||||
|
uint8_t swimming = block_feet >= B_water && block_feet < B_water + 8;
|
||||||
|
|
||||||
// Handle fall damage
|
// Handle fall damage
|
||||||
if (on_ground) {
|
if (on_ground) {
|
||||||
int16_t damage = player->grounded_y - player->y - 3;
|
int16_t damage = player->grounded_y - player->y - 3;
|
||||||
if (damage > 0) {
|
if (damage > 0 && (GAMEMODE == 0 || GAMEMODE == 2) && !swimming) {
|
||||||
uint8_t block_feet = getBlockAt(player->x, player->y, player->z);
|
hurtEntity(client_fd, -1, D_fall, damage);
|
||||||
if (block_feet < B_water || block_feet > B_water + 7) {
|
|
||||||
hurtEntity(client_fd, -1, D_fall, damage);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
player->grounded_y = player->y;
|
player->grounded_y = player->y;
|
||||||
|
} else if (swimming) {
|
||||||
|
player->grounded_y = player->y;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't continue if all we got were flags
|
// Don't continue if all we got were flags
|
||||||
@@ -233,17 +244,31 @@ void handlePacket (int client_fd, int length, int packet_id, int state) {
|
|||||||
player->pitch = pitch / 90.0f * 127.0f;
|
player->pitch = pitch / 90.0f * 127.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Broadcast player position to other players
|
// Whether to broadcast player position to other players
|
||||||
|
uint8_t should_broadcast = true;
|
||||||
|
|
||||||
|
#ifndef BROADCAST_ALL_MOVEMENT
|
||||||
|
// If applicable, tie movement updates to the tickrate by using
|
||||||
|
// a flag that gets reset on every tick. It might sound better
|
||||||
|
// to just make the tick handler broadcast position updates, but
|
||||||
|
// then we lose precision. While position is stored using integers,
|
||||||
|
// here the client gives us doubles and floats directly.
|
||||||
|
should_broadcast = !(player->flags & 0x40);
|
||||||
|
if (should_broadcast) player->flags |= 0x40;
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef SCALE_MOVEMENT_UPDATES_TO_PLAYER_COUNT
|
#ifdef SCALE_MOVEMENT_UPDATES_TO_PLAYER_COUNT
|
||||||
// If applicable, broadcast only every client_count-th movement update
|
// If applicable, broadcast only every client_count-th movement update
|
||||||
uint8_t should_broadcast = false;
|
if (++player->packets_since_update < client_count) {
|
||||||
if (++player->packets_since_update >= client_count) {
|
should_broadcast = false;
|
||||||
should_broadcast = true;
|
} else {
|
||||||
|
// Note that this does not explicitly set should_broadcast to true
|
||||||
|
// This allows the above BROADCAST_ALL_MOVEMENT check to compound
|
||||||
|
// Whether that's ever favorable is up for debate
|
||||||
player->packets_since_update = 0;
|
player->packets_since_update = 0;
|
||||||
}
|
}
|
||||||
#else
|
|
||||||
#define should_broadcast (client_count > 0)
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (should_broadcast) {
|
if (should_broadcast) {
|
||||||
// If the packet had no rotation data, calculate it from player data
|
// If the packet had no rotation data, calculate it from player data
|
||||||
if (packet_id == 0x1D) {
|
if (packet_id == 0x1D) {
|
||||||
@@ -341,10 +366,10 @@ void handlePacket (int client_fd, int length, int packet_id, int state) {
|
|||||||
uint8_t b_mid = getBlockAt(mob_x, mob_y, mob_z);
|
uint8_t b_mid = getBlockAt(mob_x, mob_y, mob_z);
|
||||||
uint8_t b_top = getBlockAt(mob_x, mob_y + 1, mob_z);
|
uint8_t b_top = getBlockAt(mob_x, mob_y + 1, mob_z);
|
||||||
while (mob_y < 255) {
|
while (mob_y < 255) {
|
||||||
if ( // Solid block below, non-solid at feet and above
|
if ( // Solid block below, non-solid(spawnable) at feet and above
|
||||||
!isPassableBlock(b_low) &&
|
!isPassableBlock(b_low) &&
|
||||||
isPassableBlock(b_mid) &&
|
isPassableSpawnBlock(b_mid) &&
|
||||||
isPassableBlock(b_top)
|
isPassableSpawnBlock(b_top)
|
||||||
) break;
|
) break;
|
||||||
b_low = b_mid;
|
b_low = b_mid;
|
||||||
b_mid = b_top;
|
b_mid = b_top;
|
||||||
@@ -413,17 +438,17 @@ void handlePacket (int client_fd, int length, int packet_id, int state) {
|
|||||||
if (state == STATE_PLAY) cs_playerInput(client_fd);
|
if (state == STATE_PLAY) cs_playerInput(client_fd);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 0x2B: // Player Loaded
|
case 0x2B:
|
||||||
PlayerData *player;
|
if (state == STATE_PLAY) cs_playerLoaded(client_fd);
|
||||||
if (getPlayerData(client_fd, &player)) break;
|
|
||||||
// Clear "client loading" flag and fallback timer
|
|
||||||
player->flags &= ~0x20;
|
|
||||||
player->flagval_16 = 0;
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 0x34:
|
case 0x34:
|
||||||
if (state == STATE_PLAY) cs_setHeldItem(client_fd);
|
if (state == STATE_PLAY) cs_setHeldItem(client_fd);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 0x3C:
|
||||||
|
if (state == STATE_PLAY) cs_swingArm(client_fd);
|
||||||
|
break;
|
||||||
|
|
||||||
case 0x28:
|
case 0x28:
|
||||||
if (state == STATE_PLAY) cs_playerAction(client_fd);
|
if (state == STATE_PLAY) cs_playerAction(client_fd);
|
||||||
@@ -474,16 +499,24 @@ void handlePacket (int client_fd, int length, int packet_id, int state) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int main () {
|
int main () {
|
||||||
|
#ifdef _WIN32 //initialize windows socket
|
||||||
|
WSADATA wsa;
|
||||||
|
if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0) {
|
||||||
|
fprintf(stderr, "WSAStartup failed\n");
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
// Hash the seeds to ensure they're random enough
|
if (0) { // TODO: manual seed
|
||||||
world_seed = splitmix64(world_seed);
|
world_seed = splitmix64(world_seed);
|
||||||
printf("World seed (hashed): ");
|
} else {
|
||||||
|
getrandom(&world_seed, 4, GRND_RANDOM);
|
||||||
|
}
|
||||||
|
printf("World seed: ");
|
||||||
for (int i = 3; i >= 0; i --) printf("%X", (unsigned int)((world_seed >> (8 * i)) & 255));
|
for (int i = 3; i >= 0; i --) printf("%X", (unsigned int)((world_seed >> (8 * i)) & 255));
|
||||||
|
printf("\n");
|
||||||
|
|
||||||
rng_seed = splitmix64(rng_seed);
|
getrandom(&rng_seed, 4, GRND_RANDOM);
|
||||||
printf("\nRNG seed (hashed): ");
|
|
||||||
for (int i = 3; i >= 0; i --) printf("%X", (unsigned int)((rng_seed >> (8 * i)) & 255));
|
|
||||||
printf("\n\n");
|
|
||||||
|
|
||||||
// Initialize block changes entries as unallocated
|
// Initialize block changes entries as unallocated
|
||||||
for (int i = 0; i < MAX_BLOCK_CHANGES; i ++) {
|
for (int i = 0; i < MAX_BLOCK_CHANGES; i ++) {
|
||||||
@@ -511,8 +544,12 @@ int main () {
|
|||||||
perror("socket failed");
|
perror("socket failed");
|
||||||
exit(EXIT_FAILURE);
|
exit(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
|
#ifdef _WIN32
|
||||||
|
if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR,
|
||||||
|
(const char*)&opt, sizeof(opt)) < 0) {
|
||||||
|
#else
|
||||||
if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) {
|
if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) {
|
||||||
|
#endif
|
||||||
perror("socket options failed");
|
perror("socket options failed");
|
||||||
exit(EXIT_FAILURE);
|
exit(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
@@ -538,125 +575,190 @@ int main () {
|
|||||||
|
|
||||||
// Make the socket non-blocking
|
// Make the socket non-blocking
|
||||||
// This is necessary to not starve the idle task during slow connections
|
// This is necessary to not starve the idle task during slow connections
|
||||||
|
#ifdef _WIN32
|
||||||
|
u_long mode = 1; // 1 = non-blocking
|
||||||
|
if (ioctlsocket(server_fd, FIONBIO, &mode) != 0) {
|
||||||
|
fprintf(stderr, "Failed to set non-blocking mode\n");
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
#else
|
||||||
int flags = fcntl(server_fd, F_GETFL, 0);
|
int flags = fcntl(server_fd, F_GETFL, 0);
|
||||||
fcntl(server_fd, F_SETFL, flags | O_NONBLOCK);
|
fcntl(server_fd, F_SETFL, flags | O_NONBLOCK);
|
||||||
|
#endif
|
||||||
|
|
||||||
// Track time of last server tick (in microseconds)
|
// Track time of last server tick (in microseconds)
|
||||||
int64_t last_tick_time = get_program_time();
|
int64_t last_tick_time = get_program_time();
|
||||||
|
|
||||||
/**
|
// Main server loop: use select() to sleep until I/O or next tick
|
||||||
* Cycles through all connected clients, handling one packet at a time
|
|
||||||
* from each player. With every iteration, attempts to accept a new
|
|
||||||
* client connection.
|
|
||||||
*/
|
|
||||||
while (true) {
|
while (true) {
|
||||||
// Check if it's time to yield to the idle task
|
fd_set read_fds;
|
||||||
task_yield();
|
FD_ZERO(&read_fds);
|
||||||
|
FD_SET(server_fd, &read_fds);
|
||||||
|
|
||||||
// Attempt to accept a new connection
|
if (deferred_block_updates_count == MAX_DEFERRED_BLOCK_UPDATES) {
|
||||||
for (int i = 0; i < MAX_PLAYERS; i ++) {
|
printf("WARNING: Deferred block update queue maxed out\n");
|
||||||
if (clients[i] != -1) continue;
|
|
||||||
clients[i] = accept(server_fd, (struct sockaddr *)&client_addr, &addr_len);
|
|
||||||
// If the accept was successful, make the client non-blocking too
|
|
||||||
if (clients[i] != -1) {
|
|
||||||
printf("New client, fd: %d\n", clients[i]);
|
|
||||||
int flags = fcntl(clients[i], F_GETFL, 0);
|
|
||||||
fcntl(clients[i], F_SETFL, flags | O_NONBLOCK);
|
|
||||||
client_count ++;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Look for valid connected clients
|
// Attempt to accept a new connection
|
||||||
client_index ++;
|
int max_fd = server_fd;
|
||||||
if (client_index == MAX_PLAYERS) client_index = 0;
|
for (int i = 0; i < MAX_PLAYERS; i ++) {
|
||||||
if (clients[client_index] == -1) continue;
|
if (clients[i] != -1) {
|
||||||
|
FD_SET(clients[i], &read_fds);
|
||||||
|
if (clients[i] > max_fd) max_fd = clients[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Handle periodic events (server ticks)
|
// Compute timeout until next server tick
|
||||||
int64_t time_since_last_tick = get_program_time() - last_tick_time;
|
struct timeval timeout;
|
||||||
if (time_since_last_tick > TIME_BETWEEN_TICKS) {
|
int64_t now = get_program_time();
|
||||||
handleServerTick(time_since_last_tick);
|
int64_t elapsed = now - last_tick_time;
|
||||||
|
int64_t to_next = TIME_BETWEEN_TICKS - elapsed;
|
||||||
|
if (to_next < 0) to_next = 0;
|
||||||
|
timeout.tv_sec = (time_t)(to_next / 1000000);
|
||||||
|
timeout.tv_usec = (suseconds_t)(to_next % 1000000);
|
||||||
|
|
||||||
|
int activity;
|
||||||
|
#ifdef _WIN32
|
||||||
|
activity = select(0, &read_fds, NULL, NULL, &timeout);
|
||||||
|
if (activity == SOCKET_ERROR) {
|
||||||
|
int err = WSAGetLastError();
|
||||||
|
if (err == WSAEINTR) continue;
|
||||||
|
if (err == WSAETIMEDOUT) activity = 0;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
activity = select(max_fd + 1, &read_fds, NULL, NULL, &timeout);
|
||||||
|
if (activity < 0) {
|
||||||
|
if (errno == EINTR) continue; // interrupted, retry
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
now = get_program_time();
|
||||||
|
elapsed = now - last_tick_time;
|
||||||
|
if (elapsed >= TIME_BETWEEN_TICKS) {
|
||||||
|
handleServerTick(elapsed);
|
||||||
last_tick_time = get_program_time();
|
last_tick_time = get_program_time();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle this individual client
|
if (activity <= 0) {
|
||||||
int client_fd = clients[client_index];
|
// timeout or error already handled; continue loop
|
||||||
|
|
||||||
// Check if at least 2 bytes are available for reading
|
|
||||||
recv_count = recv(client_fd, &recv_buffer, 2, MSG_PEEK);
|
|
||||||
if (recv_count < 2) {
|
|
||||||
if (recv_count == 0 || (recv_count < 0 && errno != EAGAIN && errno != EWOULDBLOCK)) {
|
|
||||||
disconnectClient(&clients[client_index], 1);
|
|
||||||
}
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle 0xBEEF and 0xFEED packets for dumping/uploading world data
|
if (FD_ISSET(server_fd, &read_fds)) {
|
||||||
#ifdef DEV_ENABLE_BEEF_DUMPS
|
while (true) {
|
||||||
// Received BEEF packet, dump world data and disconnect
|
int slot = -1;
|
||||||
if (recv_buffer[0] == 0xBE && recv_buffer[1] == 0xEF && getClientState(client_fd) == STATE_NONE) {
|
for (int i = 0; i < MAX_PLAYERS; i ++) {
|
||||||
// Send block changes and player data back to back
|
if (clients[i] == -1) { slot = i; break; }
|
||||||
// The client is expected to know (or calculate) the size of these buffers
|
}
|
||||||
send_all(client_fd, block_changes, sizeof(block_changes));
|
if (slot == -1) {
|
||||||
send_all(client_fd, player_data, sizeof(player_data));
|
int tmp = accept(server_fd, (struct sockaddr *)&client_addr, &addr_len);
|
||||||
// Flush the socket and receive everything left on the wire
|
if (tmp == -1) break;
|
||||||
shutdown(client_fd, SHUT_WR);
|
#ifdef _WIN32
|
||||||
recv_all(client_fd, recv_buffer, sizeof(recv_buffer), false);
|
closesocket(tmp);
|
||||||
// Kick the client
|
#else
|
||||||
disconnectClient(&clients[client_index], 6);
|
close(tmp);
|
||||||
continue;
|
#endif
|
||||||
}
|
break;
|
||||||
// Received FEED packet, load world data from socket and disconnect
|
}
|
||||||
if (recv_buffer[0] == 0xFE && recv_buffer[1] == 0xED && getClientState(client_fd) == STATE_NONE) {
|
|
||||||
// Consume 0xFEED bytes (previous read was just a peek)
|
clients[slot] = accept(server_fd, (struct sockaddr *)&client_addr, &addr_len);
|
||||||
recv_all(client_fd, recv_buffer, 2, false);
|
if (clients[slot] == -1) break; // no more pending
|
||||||
// Write full buffers straight into memory
|
printf("New client, fd: %d\n", clients[slot]);
|
||||||
recv_all(client_fd, block_changes, sizeof(block_changes), false);
|
#ifdef _WIN32
|
||||||
recv_all(client_fd, player_data, sizeof(player_data), false);
|
u_long mode = 1;
|
||||||
// Recover block_changes_count
|
ioctlsocket(clients[slot], FIONBIO, &mode);
|
||||||
for (int i = 0; i < MAX_BLOCK_CHANGES; i ++) {
|
#else
|
||||||
if (block_changes[i].block == 0xFF) continue;
|
int flags = fcntl(clients[slot], F_GETFL, 0);
|
||||||
if (block_changes[i].block == B_chest) i += 14;
|
fcntl(clients[slot], F_SETFL, flags | O_NONBLOCK);
|
||||||
if (i >= block_changes_count) block_changes_count = i + 1;
|
#endif
|
||||||
|
client_count ++;
|
||||||
}
|
}
|
||||||
// Update data on disk
|
|
||||||
writeBlockChangesToDisk(0, block_changes_count);
|
|
||||||
writePlayerDataToDisk();
|
|
||||||
// Kick the client
|
|
||||||
disconnectClient(&clients[client_index], 7);
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < MAX_PLAYERS; i ++) {
|
||||||
|
int client_fd = clients[i];
|
||||||
|
if (client_fd == -1) continue;
|
||||||
|
if (!FD_ISSET(client_fd, &read_fds)) continue;
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
recv_count = recv(client_fd, recv_buffer, 2, MSG_PEEK);
|
||||||
|
if (recv_count == 0) {
|
||||||
|
disconnectClient(&clients[i], 1);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (recv_count == SOCKET_ERROR) {
|
||||||
|
int err = WSAGetLastError();
|
||||||
|
if (err == WSAEWOULDBLOCK) {
|
||||||
|
continue; // no data yet, keep client alive
|
||||||
|
} else {
|
||||||
|
disconnectClient(&clients[i], 1);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
recv_count = recv(client_fd, &recv_buffer, 2, MSG_PEEK);
|
||||||
|
if (recv_count < 2) {
|
||||||
|
if (recv_count == 0 || (recv_count < 0 && errno != EAGAIN && errno != EWOULDBLOCK)) {
|
||||||
|
disconnectClient(&clients[i], 1);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Read packet length
|
#ifdef DEV_ENABLE_BEEF_DUMPS
|
||||||
int length = readVarInt(client_fd);
|
if (recv_buffer[0] == 0xBE && recv_buffer[1] == 0xEF && getClientState(client_fd) == STATE_NONE) {
|
||||||
if (length == VARNUM_ERROR) {
|
send_all(client_fd, block_changes, sizeof(block_changes));
|
||||||
disconnectClient(&clients[client_index], 2);
|
send_all(client_fd, player_data, sizeof(player_data));
|
||||||
continue;
|
shutdown(client_fd, SHUT_WR);
|
||||||
}
|
recv_all(client_fd, recv_buffer, sizeof(recv_buffer), false);
|
||||||
// Read packet ID
|
disconnectClient(&clients[i], 6);
|
||||||
int packet_id = readVarInt(client_fd);
|
continue;
|
||||||
if (packet_id == VARNUM_ERROR) {
|
}
|
||||||
disconnectClient(&clients[client_index], 3);
|
if (recv_buffer[0] == 0xFE && recv_buffer[1] == 0xED && getClientState(client_fd) == STATE_NONE) {
|
||||||
continue;
|
recv_all(client_fd, recv_buffer, 2, false);
|
||||||
}
|
recv_all(client_fd, block_changes, sizeof(block_changes), false);
|
||||||
// Get client connection state
|
recv_all(client_fd, player_data, sizeof(player_data), false);
|
||||||
int state = getClientState(client_fd);
|
for (int j = 0; j < MAX_BLOCK_CHANGES; j ++) {
|
||||||
// Disconnect on legacy server list ping
|
if (block_changes[j].block == 0xFF) continue;
|
||||||
if (state == STATE_NONE && length == 254 && packet_id == 122) {
|
if (block_changes[j].block == B_chest) j += 14;
|
||||||
disconnectClient(&clients[client_index], 5);
|
if (j >= block_changes_count) block_changes_count = j + 1;
|
||||||
continue;
|
}
|
||||||
}
|
writeBlockChangesToDisk(0, block_changes_count);
|
||||||
// Handle packet data
|
writePlayerDataToDisk();
|
||||||
handlePacket(client_fd, length - sizeVarInt(packet_id), packet_id, state);
|
disconnectClient(&clients[i], 7);
|
||||||
if (recv_count == 0 || (recv_count == -1 && errno != EAGAIN && errno != EWOULDBLOCK)) {
|
continue;
|
||||||
disconnectClient(&clients[client_index], 4);
|
}
|
||||||
continue;
|
#endif
|
||||||
}
|
|
||||||
|
|
||||||
|
int length = readVarInt(client_fd);
|
||||||
|
if (length == VARNUM_ERROR) {
|
||||||
|
disconnectClient(&clients[i], 2);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
int packet_id = readVarInt(client_fd);
|
||||||
|
if (packet_id == VARNUM_ERROR) {
|
||||||
|
disconnectClient(&clients[i], 3);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
int state = getClientState(client_fd);
|
||||||
|
if (state == STATE_NONE && length == 254 && packet_id == 122) {
|
||||||
|
disconnectClient(&clients[i], 5);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
handlePacket(client_fd, length - sizeVarInt(packet_id), packet_id, state);
|
||||||
|
if (recv_count == 0 || (recv_count == -1 && errno != EAGAIN && errno != EWOULDBLOCK)) {
|
||||||
|
disconnectClient(&clients[i], 4);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
close(server_fd);
|
close(server_fd);
|
||||||
|
|
||||||
|
#ifdef _WIN32 //cleanup windows socket
|
||||||
|
WSACleanup();
|
||||||
|
#endif
|
||||||
|
|
||||||
printf("Server closed.\n");
|
printf("Server closed.\n");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
232
src/packets.c
232
src/packets.c
@@ -6,7 +6,12 @@
|
|||||||
#include "lwip/netdb.h"
|
#include "lwip/netdb.h"
|
||||||
#include "esp_task_wdt.h"
|
#include "esp_task_wdt.h"
|
||||||
#else
|
#else
|
||||||
#include <arpa/inet.h>
|
#ifdef _WIN32
|
||||||
|
#include <winsock2.h>
|
||||||
|
#include <ws2tcpip.h>
|
||||||
|
#else
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
#endif
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -63,7 +68,8 @@ int cs_loginStart (int client_fd, uint8_t *uuid, char *name) {
|
|||||||
|
|
||||||
readString(client_fd);
|
readString(client_fd);
|
||||||
if (recv_count == -1) return 1;
|
if (recv_count == -1) return 1;
|
||||||
memcpy(name, recv_buffer, strlen((char *)recv_buffer) + 1);
|
strncpy(name, (char *)recv_buffer, 16 - 1);
|
||||||
|
name[16 - 1] = '\0';
|
||||||
printf(" Player name: %s\n", name);
|
printf(" Player name: %s\n", name);
|
||||||
recv_count = recv_all(client_fd, recv_buffer, 16, false);
|
recv_count = recv_all(client_fd, recv_buffer, 16, false);
|
||||||
if (recv_count == -1) return 1;
|
if (recv_count == -1) return 1;
|
||||||
@@ -156,6 +162,23 @@ int cs_pluginMessage (int client_fd) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// S->C Clientbound Plugin Message
|
||||||
|
int sc_sendPluginMessage (int client_fd, const char *channel, const uint8_t *data, size_t data_len) {
|
||||||
|
printf("Sending Plugin Message\n\n");
|
||||||
|
int channel_len = (int)strlen(channel);
|
||||||
|
|
||||||
|
writeVarInt(client_fd, 1 + sizeVarInt(channel_len) + channel_len + sizeVarInt(data_len) + data_len);
|
||||||
|
writeByte(client_fd, 0x01);
|
||||||
|
|
||||||
|
writeVarInt(client_fd, channel_len);
|
||||||
|
send_all(client_fd, channel, channel_len);
|
||||||
|
|
||||||
|
writeVarInt(client_fd, data_len);
|
||||||
|
send_all(client_fd, data, data_len);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
// S->C Finish Configuration
|
// S->C Finish Configuration
|
||||||
int sc_finishConfiguration (int client_fd) {
|
int sc_finishConfiguration (int client_fd) {
|
||||||
writeVarInt(client_fd, 1);
|
writeVarInt(client_fd, 1);
|
||||||
@@ -635,10 +658,8 @@ int cs_clickContainer (int client_fd) {
|
|||||||
count = (uint8_t)readVarInt(client_fd);
|
count = (uint8_t)readVarInt(client_fd);
|
||||||
|
|
||||||
// ignore components
|
// ignore components
|
||||||
tmp = readVarInt(client_fd);
|
readLengthPrefixedData(client_fd);
|
||||||
recv_all(client_fd, recv_buffer, tmp, false);
|
readLengthPrefixedData(client_fd);
|
||||||
tmp = readVarInt(client_fd);
|
|
||||||
recv_all(client_fd, recv_buffer, tmp, false);
|
|
||||||
|
|
||||||
if (count > 0 && apply_changes) {
|
if (count > 0 && apply_changes) {
|
||||||
*p_item = item;
|
*p_item = item;
|
||||||
@@ -668,10 +689,8 @@ int cs_clickContainer (int client_fd) {
|
|||||||
player->flagval_16 = readVarInt(client_fd);
|
player->flagval_16 = readVarInt(client_fd);
|
||||||
player->flagval_8 = readVarInt(client_fd);
|
player->flagval_8 = readVarInt(client_fd);
|
||||||
// ignore components
|
// ignore components
|
||||||
tmp = readVarInt(client_fd);
|
readLengthPrefixedData(client_fd);
|
||||||
recv_all(client_fd, recv_buffer, tmp, false);
|
readLengthPrefixedData(client_fd);
|
||||||
tmp = readVarInt(client_fd);
|
|
||||||
recv_all(client_fd, recv_buffer, tmp, false);
|
|
||||||
} else {
|
} else {
|
||||||
player->flagval_16 = 0;
|
player->flagval_16 = 0;
|
||||||
player->flagval_8 = 0;
|
player->flagval_8 = 0;
|
||||||
@@ -741,6 +760,47 @@ int cs_setPlayerMovementFlags (int client_fd, uint8_t *on_ground) {
|
|||||||
|
|
||||||
*on_ground = readByte(client_fd) & 0x01;
|
*on_ground = readByte(client_fd) & 0x01;
|
||||||
|
|
||||||
|
PlayerData *player;
|
||||||
|
if (!getPlayerData(client_fd, &player))
|
||||||
|
broadcastPlayerMetadata(player);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// C->S Swing Arm (serverbound)
|
||||||
|
int cs_swingArm (int client_fd) {
|
||||||
|
|
||||||
|
PlayerData *player;
|
||||||
|
if (getPlayerData(client_fd, &player)) return 1;
|
||||||
|
|
||||||
|
uint8_t hand = readVarInt(client_fd);
|
||||||
|
|
||||||
|
uint8_t animation = 255;
|
||||||
|
switch (hand) {
|
||||||
|
case 0: {
|
||||||
|
animation = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 1: {
|
||||||
|
animation = 2;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (animation == 255)
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
// Forward animation to all connected players
|
||||||
|
for (int i = 0; i < MAX_PLAYERS; i ++) {
|
||||||
|
PlayerData* other_player = &player_data[i];
|
||||||
|
|
||||||
|
if (other_player->client_fd == -1) continue;
|
||||||
|
if (other_player->client_fd == player->client_fd) continue;
|
||||||
|
if (other_player->flags & 0x20) continue;
|
||||||
|
|
||||||
|
sc_entityAnimation(other_player->client_fd, player->client_fd, animation);
|
||||||
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -850,6 +910,26 @@ int sc_spawnEntity (
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// S->C Set Entity Metadata
|
||||||
|
int sc_setEntityMetadata (int client_fd, int id, EntityData *metadata, size_t length) {
|
||||||
|
int entity_metadata_size = sizeEntityMetadata(metadata, length);
|
||||||
|
if (entity_metadata_size == -1) return 1;
|
||||||
|
|
||||||
|
writeVarInt(client_fd, 2 + sizeVarInt(id) + entity_metadata_size);
|
||||||
|
writeByte(client_fd, 0x5C);
|
||||||
|
|
||||||
|
writeVarInt(client_fd, id); // Entity ID
|
||||||
|
|
||||||
|
for (size_t i = 0; i < length; i ++) {
|
||||||
|
EntityData *data = &metadata[i];
|
||||||
|
writeEntityData(client_fd, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
writeByte(client_fd, 0xFF); // End
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
// S->C Spawn Entity (from PlayerData)
|
// S->C Spawn Entity (from PlayerData)
|
||||||
int sc_spawnEntityPlayer (int client_fd, PlayerData player) {
|
int sc_spawnEntityPlayer (int client_fd, PlayerData player) {
|
||||||
return sc_spawnEntity(
|
return sc_spawnEntity(
|
||||||
@@ -862,6 +942,17 @@ int sc_spawnEntityPlayer (int client_fd, PlayerData player) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// S->C Entity Animation
|
||||||
|
int sc_entityAnimation (int client_fd, int id, uint8_t animation) {
|
||||||
|
writeVarInt(client_fd, 2 + sizeVarInt(id));
|
||||||
|
writeByte(client_fd, 0x02);
|
||||||
|
|
||||||
|
writeVarInt(client_fd, id); // Entity ID
|
||||||
|
writeByte(client_fd, animation); // Animation
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
// S->C Teleport Entity
|
// S->C Teleport Entity
|
||||||
int sc_teleportEntity (
|
int sc_teleportEntity (
|
||||||
int client_fd, int id,
|
int client_fd, int id,
|
||||||
@@ -1022,7 +1113,8 @@ int sc_systemChat (int client_fd, char* message, uint16_t len) {
|
|||||||
// C->S Chat Message
|
// C->S Chat Message
|
||||||
int cs_chat (int client_fd) {
|
int cs_chat (int client_fd) {
|
||||||
|
|
||||||
readString(client_fd);
|
// To be safe, cap messages to 32 bytes before the buffer length
|
||||||
|
readStringN(client_fd, 224);
|
||||||
|
|
||||||
PlayerData *player;
|
PlayerData *player;
|
||||||
if (getPlayerData(client_fd, &player)) return 1;
|
if (getPlayerData(client_fd, &player)) return 1;
|
||||||
@@ -1030,35 +1122,95 @@ int cs_chat (int client_fd) {
|
|||||||
size_t message_len = strlen((char *)recv_buffer);
|
size_t message_len = strlen((char *)recv_buffer);
|
||||||
uint8_t name_len = strlen(player->name);
|
uint8_t name_len = strlen(player->name);
|
||||||
|
|
||||||
// To be safe, cap messages to 32 bytes before the buffer length
|
if (recv_buffer[0] != '!') { // Standard chat message
|
||||||
if (message_len > 224) {
|
|
||||||
recv_buffer[224] = '\0';
|
// Shift message contents forward to make space for player name tag
|
||||||
message_len = 224;
|
memmove(recv_buffer + name_len + 3, recv_buffer, message_len + 1);
|
||||||
|
// Copy player name to index 1
|
||||||
|
memcpy(recv_buffer + 1, player->name, name_len);
|
||||||
|
// Surround player name with brackets and a space
|
||||||
|
recv_buffer[0] = '<';
|
||||||
|
recv_buffer[name_len + 1] = '>';
|
||||||
|
recv_buffer[name_len + 2] = ' ';
|
||||||
|
|
||||||
|
// Forward message to all connected players
|
||||||
|
for (int i = 0; i < MAX_PLAYERS; i ++) {
|
||||||
|
if (player_data[i].client_fd == -1) continue;
|
||||||
|
if (player_data[i].flags & 0x20) continue;
|
||||||
|
sc_systemChat(player_data[i].client_fd, (char *)recv_buffer, message_len + name_len + 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
goto cleanup;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shift message contents forward to make space for player name tag
|
// Handle chat commands
|
||||||
memmove(recv_buffer + name_len + 3, recv_buffer, message_len + 1);
|
|
||||||
// Copy player name to index 1
|
|
||||||
memcpy(recv_buffer + 1, player->name, name_len);
|
|
||||||
// Surround player name with brackets and a space
|
|
||||||
recv_buffer[0] = '<';
|
|
||||||
recv_buffer[name_len + 1] = '>';
|
|
||||||
recv_buffer[name_len + 2] = ' ';
|
|
||||||
|
|
||||||
// Forward message to all connected players
|
if (!strncmp((char *)recv_buffer, "!msg", 4)) {
|
||||||
for (int i = 0; i < MAX_PLAYERS; i ++) {
|
|
||||||
if (player_data[i].client_fd == -1) continue;
|
int target_offset = 5;
|
||||||
if (player_data[i].flags & 0x20) continue;
|
int target_end_offset = 0;
|
||||||
sc_systemChat(player_data[i].client_fd, (char *)recv_buffer, message_len + name_len + 3);
|
int text_offset = 0;
|
||||||
|
|
||||||
|
// Skip spaces after "!msg"
|
||||||
|
while (recv_buffer[target_offset] == ' ') target_offset++;
|
||||||
|
target_end_offset = target_offset;
|
||||||
|
// Extract target name
|
||||||
|
while (recv_buffer[target_end_offset] != ' ' && recv_buffer[target_end_offset] != '\0' && target_end_offset < 21) target_end_offset++;
|
||||||
|
text_offset = target_end_offset;
|
||||||
|
// Skip spaces before message
|
||||||
|
while (recv_buffer[text_offset] == ' ') text_offset++;
|
||||||
|
|
||||||
|
// Send usage guide if arguments are missing
|
||||||
|
if (target_offset == target_end_offset || target_end_offset == text_offset) {
|
||||||
|
sc_systemChat(client_fd, "§7Usage: !msg <player> <message>", 33);
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Query the target player
|
||||||
|
PlayerData *target = getPlayerByName(target_offset, target_end_offset, recv_buffer);
|
||||||
|
if (target == NULL) {
|
||||||
|
sc_systemChat(client_fd, "Player not found", 16);
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format output as a vanilla whisper
|
||||||
|
int name_len = strlen(player->name);
|
||||||
|
int text_len = message_len - text_offset;
|
||||||
|
memmove(recv_buffer + name_len + 24, recv_buffer + text_offset, text_len);
|
||||||
|
snprintf((char *)recv_buffer, sizeof(recv_buffer), "§7§o%s whispers to you:", player->name);
|
||||||
|
recv_buffer[name_len + 23] = ' ';
|
||||||
|
// Send message to target player
|
||||||
|
sc_systemChat(target->client_fd, (char *)recv_buffer, (uint16_t)(name_len + 24 + text_len));
|
||||||
|
|
||||||
|
// Format output for sending player
|
||||||
|
int target_len = target_end_offset - target_offset;
|
||||||
|
memmove(recv_buffer + target_len + 23, recv_buffer + name_len + 24, text_len);
|
||||||
|
snprintf((char *)recv_buffer, sizeof(recv_buffer), "§7§oYou whisper to %s:", target->name);
|
||||||
|
recv_buffer[target_len + 22] = ' ';
|
||||||
|
// Report back to sending player
|
||||||
|
sc_systemChat(client_fd, (char *)recv_buffer, (uint16_t)(target_len + 23 + text_len));
|
||||||
|
|
||||||
|
goto cleanup;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!strncmp((char *)recv_buffer, "!help", 5)) {
|
||||||
|
// Send command guide
|
||||||
|
const char help_msg[] = "§7Commands:\n"
|
||||||
|
" !msg <player> <message> - Send a private message\n"
|
||||||
|
" !help - Show this help message";
|
||||||
|
sc_systemChat(client_fd, (char *)help_msg, (uint16_t)sizeof(help_msg) - 1);
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle fall-through case
|
||||||
|
sc_systemChat(client_fd, "§7Unknown command", 18);
|
||||||
|
|
||||||
|
cleanup:
|
||||||
readUint64(client_fd); // Ignore timestamp
|
readUint64(client_fd); // Ignore timestamp
|
||||||
readUint64(client_fd); // Ignore salt
|
readUint64(client_fd); // Ignore salt
|
||||||
|
|
||||||
// Ignore signature (if any)
|
// Ignore signature (if any)
|
||||||
uint8_t has_signature = readByte(client_fd);
|
uint8_t has_signature = readByte(client_fd);
|
||||||
if (has_signature) recv_all(client_fd, recv_buffer, 256, false);
|
if (has_signature) recv_all(client_fd, recv_buffer, 256, false);
|
||||||
|
|
||||||
readVarInt(client_fd); // Ignore message count
|
readVarInt(client_fd); // Ignore message count
|
||||||
// Ignore acknowledgement bitmask and checksum
|
// Ignore acknowledgement bitmask and checksum
|
||||||
recv_all(client_fd, recv_buffer, 4, false);
|
recv_all(client_fd, recv_buffer, 4, false);
|
||||||
@@ -1084,7 +1236,9 @@ int cs_interact (int client_fd) {
|
|||||||
// Ignore sneaking flag
|
// Ignore sneaking flag
|
||||||
recv_all(client_fd, recv_buffer, 1, false);
|
recv_all(client_fd, recv_buffer, 1, false);
|
||||||
|
|
||||||
if (type == 1) {
|
if (type == 0) { // Interact
|
||||||
|
interactEntity(entity_id, client_fd);
|
||||||
|
} else if (type == 1) { // Attack
|
||||||
hurtEntity(entity_id, client_fd, D_generic, 1);
|
hurtEntity(entity_id, client_fd, D_generic, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1127,6 +1281,8 @@ int cs_playerInput (int client_fd) {
|
|||||||
if (flags & 0x20) player->flags |= 0x04;
|
if (flags & 0x20) player->flags |= 0x04;
|
||||||
else player->flags &= ~0x04;
|
else player->flags &= ~0x04;
|
||||||
|
|
||||||
|
broadcastPlayerMetadata(player);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1144,6 +1300,8 @@ int cs_playerCommand (int client_fd) {
|
|||||||
if (action == 1) player->flags |= 0x08;
|
if (action == 1) player->flags |= 0x08;
|
||||||
else if (action == 2) player->flags &= ~0x08;
|
else if (action == 2) player->flags &= ~0x08;
|
||||||
|
|
||||||
|
broadcastPlayerMetadata(player);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1160,6 +1318,18 @@ int sc_pickupItem (int client_fd, int collected, int collector, uint8_t count) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// C->S Player Loaded
|
||||||
|
int cs_playerLoaded (int client_fd) {
|
||||||
|
|
||||||
|
PlayerData *player;
|
||||||
|
if (getPlayerData(client_fd, &player)) return 1;
|
||||||
|
|
||||||
|
// Redirect handling to player join procedure
|
||||||
|
handlePlayerJoin(player);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
// S->C Registry Data (multiple packets) and Update Tags (configuration, multiple packets)
|
// S->C Registry Data (multiple packets) and Update Tags (configuration, multiple packets)
|
||||||
int sc_registries (int client_fd) {
|
int sc_registries (int client_fd) {
|
||||||
|
|
||||||
|
686
src/procedures.c
686
src/procedures.c
@@ -1,7 +1,11 @@
|
|||||||
|
#include <stdint.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include <winsock2.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#include "globals.h"
|
#include "globals.h"
|
||||||
#include "tools.h"
|
#include "tools.h"
|
||||||
@@ -47,6 +51,7 @@ int getClientIndex (int client_fd) {
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Restores player data to initial state (fresh spawn)
|
||||||
void resetPlayerData (PlayerData *player) {
|
void resetPlayerData (PlayerData *player) {
|
||||||
player->health = 20;
|
player->health = 20;
|
||||||
player->hunger = 20;
|
player->hunger = 20;
|
||||||
@@ -66,16 +71,26 @@ void resetPlayerData (PlayerData *player) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Assigns the given data to a player_data entry
|
||||||
int reservePlayerData (int client_fd, uint8_t *uuid, char *name) {
|
int reservePlayerData (int client_fd, uint8_t *uuid, char *name) {
|
||||||
|
|
||||||
for (int i = 0; i < MAX_PLAYERS; i ++) {
|
for (int i = 0; i < MAX_PLAYERS; i ++) {
|
||||||
|
// Found existing player entry (UUID match)
|
||||||
if (memcmp(player_data[i].uuid, uuid, 16) == 0) {
|
if (memcmp(player_data[i].uuid, uuid, 16) == 0) {
|
||||||
|
// Set network file descriptor and username
|
||||||
player_data[i].client_fd = client_fd;
|
player_data[i].client_fd = client_fd;
|
||||||
|
memcpy(player_data[i].name, name, 16);
|
||||||
|
// Flag player as loading
|
||||||
player_data[i].flags |= 0x20;
|
player_data[i].flags |= 0x20;
|
||||||
player_data[i].flagval_16 = 0;
|
player_data[i].flagval_16 = 0;
|
||||||
memcpy(player_data[i].name, name, 16);
|
// Reset their recently visited chunk list
|
||||||
|
for (int j = 0; j < VISITED_HISTORY; j ++) {
|
||||||
|
player_data[i].visited_x[j] = 32767;
|
||||||
|
player_data[i].visited_z[j] = 32767;
|
||||||
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
// Search for unallocated player slots
|
||||||
uint8_t empty = true;
|
uint8_t empty = true;
|
||||||
for (uint8_t j = 0; j < 16; j ++) {
|
for (uint8_t j = 0; j < 16; j ++) {
|
||||||
if (player_data[i].uuid[j] != 0) {
|
if (player_data[i].uuid[j] != 0) {
|
||||||
@@ -83,6 +98,7 @@ int reservePlayerData (int client_fd, uint8_t *uuid, char *name) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Found free space for a player, initialize default parameters
|
||||||
if (empty) {
|
if (empty) {
|
||||||
if (player_data_count >= MAX_PLAYERS) return 1;
|
if (player_data_count >= MAX_PLAYERS) return 1;
|
||||||
player_data[i].client_fd = client_fd;
|
player_data[i].client_fd = client_fd;
|
||||||
@@ -110,6 +126,22 @@ int getPlayerData (int client_fd, PlayerData **output) {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns the player with the given name, or NULL if not found
|
||||||
|
PlayerData *getPlayerByName (int start_offset, int end_offset, uint8_t *buffer) {
|
||||||
|
for (int i = 0; i < MAX_PLAYERS; i ++) {
|
||||||
|
if (player_data[i].client_fd == -1) continue;
|
||||||
|
int j;
|
||||||
|
for (j = start_offset; j < end_offset && j < 256 && buffer[j] != ' '; j++) {
|
||||||
|
if (player_data[i].name[j - start_offset] != buffer[j]) break;
|
||||||
|
}
|
||||||
|
if ((j == end_offset || buffer[j] == ' ') && j < 256) {
|
||||||
|
return &player_data[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Marks a client as disconnected and cleans up player data
|
// Marks a client as disconnected and cleans up player data
|
||||||
void handlePlayerDisconnect (int client_fd) {
|
void handlePlayerDisconnect (int client_fd) {
|
||||||
// Search for a corresponding player in the player data array
|
// Search for a corresponding player in the player data array
|
||||||
@@ -117,11 +149,6 @@ void handlePlayerDisconnect (int client_fd) {
|
|||||||
if (player_data[i].client_fd != client_fd) continue;
|
if (player_data[i].client_fd != client_fd) continue;
|
||||||
// Mark the player as being offline
|
// Mark the player as being offline
|
||||||
player_data[i].client_fd = -1;
|
player_data[i].client_fd = -1;
|
||||||
// Reset their recently visited chunk list
|
|
||||||
for (int j = 0; j < VISITED_HISTORY; j ++) {
|
|
||||||
player_data[i].visited_x[j] = 32767;
|
|
||||||
player_data[i].visited_z[j] = 32767;
|
|
||||||
}
|
|
||||||
// Prepare leave message for broadcast
|
// Prepare leave message for broadcast
|
||||||
uint8_t player_name_len = strlen(player_data[i].name);
|
uint8_t player_name_len = strlen(player_data[i].name);
|
||||||
strcpy((char *)recv_buffer, player_data[i].name);
|
strcpy((char *)recv_buffer, player_data[i].name);
|
||||||
@@ -146,14 +173,42 @@ void handlePlayerDisconnect (int client_fd) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Marks a client as connected and broadcasts their data to other players
|
||||||
|
void handlePlayerJoin (PlayerData* player) {
|
||||||
|
|
||||||
|
// Prepare join message for broadcast
|
||||||
|
uint8_t player_name_len = strlen(player->name);
|
||||||
|
strcpy((char *)recv_buffer, player->name);
|
||||||
|
strcpy((char *)recv_buffer + player_name_len, " joined the game");
|
||||||
|
|
||||||
|
// Inform other clients (and the joining client) of the player's name and entity
|
||||||
|
for (int i = 0; i < MAX_PLAYERS; i ++) {
|
||||||
|
sc_systemChat(player_data[i].client_fd, (char *)recv_buffer, 16 + player_name_len);
|
||||||
|
sc_playerInfoUpdateAddPlayer(player_data[i].client_fd, *player);
|
||||||
|
if (player_data[i].client_fd != player->client_fd) {
|
||||||
|
sc_spawnEntityPlayer(player_data[i].client_fd, *player);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear "client loading" flag and fallback timer
|
||||||
|
player->flags &= ~0x20;
|
||||||
|
player->flagval_16 = 0;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
void disconnectClient (int *client_fd, int cause) {
|
void disconnectClient (int *client_fd, int cause) {
|
||||||
if (*client_fd == -1) return;
|
if (*client_fd == -1) return;
|
||||||
client_count --;
|
client_count --;
|
||||||
setClientState(*client_fd, STATE_NONE);
|
setClientState(*client_fd, STATE_NONE);
|
||||||
handlePlayerDisconnect(*client_fd);
|
handlePlayerDisconnect(*client_fd);
|
||||||
|
#ifdef _WIN32
|
||||||
|
closesocket(*client_fd);
|
||||||
|
printf("Disconnected client %d, cause: %d, errno: %d\n", *client_fd, cause, WSAGetLastError());
|
||||||
|
#else
|
||||||
close(*client_fd);
|
close(*client_fd);
|
||||||
*client_fd = -1;
|
|
||||||
printf("Disconnected client %d, cause: %d, errno: %d\n\n", *client_fd, cause, errno);
|
printf("Disconnected client %d, cause: %d, errno: %d\n\n", *client_fd, cause, errno);
|
||||||
|
#endif
|
||||||
|
*client_fd = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t serverSlotToClientSlot (int window_id, uint8_t slot) {
|
uint8_t serverSlotToClientSlot (int window_id, uint8_t slot) {
|
||||||
@@ -330,6 +385,87 @@ void spawnPlayer (PlayerData *player) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Broadcasts a player's entity metadata (sneak/sprint state) to other players
|
||||||
|
void broadcastPlayerMetadata (PlayerData *player) {
|
||||||
|
uint8_t sneaking = (player->flags & 0x04) != 0;
|
||||||
|
uint8_t sprinting = (player->flags & 0x08) != 0;
|
||||||
|
|
||||||
|
uint8_t entity_bit_mask = 0;
|
||||||
|
if (sneaking) entity_bit_mask |= 0x02;
|
||||||
|
if (sprinting) entity_bit_mask |= 0x08;
|
||||||
|
|
||||||
|
int pose = 0;
|
||||||
|
if (sneaking) pose = 5;
|
||||||
|
|
||||||
|
EntityData metadata[] = {
|
||||||
|
{
|
||||||
|
0, // Index (Entity Bit Mask)
|
||||||
|
0, // Type (Byte)
|
||||||
|
{ entity_bit_mask }, // Value
|
||||||
|
},
|
||||||
|
{
|
||||||
|
6, // Index (Pose),
|
||||||
|
21, // Type (Pose),
|
||||||
|
{ pose }, // Value (Standing)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
for (int i = 0; i < MAX_PLAYERS; i ++) {
|
||||||
|
PlayerData* other_player = &player_data[i];
|
||||||
|
int client_fd = other_player->client_fd;
|
||||||
|
|
||||||
|
if (client_fd == -1) continue;
|
||||||
|
if (client_fd == player->client_fd) continue;
|
||||||
|
if (other_player->flags & 0x20) continue;
|
||||||
|
|
||||||
|
sc_setEntityMetadata(client_fd, player->client_fd, metadata, 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sends a mob's entity metadata to the given player.
|
||||||
|
// If client_fd is -1, broadcasts to all player
|
||||||
|
void broadcastMobMetadata (int client_fd, int entity_id) {
|
||||||
|
|
||||||
|
MobData *mob = &mob_data[-entity_id - 2];
|
||||||
|
|
||||||
|
EntityData *metadata;
|
||||||
|
size_t length;
|
||||||
|
|
||||||
|
switch (mob->type) {
|
||||||
|
case 106: // Sheep
|
||||||
|
if (!((mob->data >> 5) & 1)) // Don't send metadata if sheep isn't sheared
|
||||||
|
return;
|
||||||
|
|
||||||
|
metadata = malloc(sizeof *metadata);
|
||||||
|
metadata[0] = (EntityData){
|
||||||
|
17, // Index (Sheep Bit Mask),
|
||||||
|
0, // Type (Byte),
|
||||||
|
{ (uint8_t)0x10 }, // Value
|
||||||
|
};
|
||||||
|
length = 1;
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
default: return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (client_fd == -1) {
|
||||||
|
for (int i = 0; i < MAX_PLAYERS; i ++) {
|
||||||
|
PlayerData* player = &player_data[i];
|
||||||
|
client_fd = player->client_fd;
|
||||||
|
|
||||||
|
if (client_fd == -1) continue;
|
||||||
|
if (player->flags & 0x20) continue;
|
||||||
|
|
||||||
|
sc_setEntityMetadata(client_fd, entity_id, metadata, length);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sc_setEntityMetadata(client_fd, entity_id, metadata, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
free(metadata);
|
||||||
|
}
|
||||||
|
|
||||||
uint8_t getBlockChange (short x, uint8_t y, short z) {
|
uint8_t getBlockChange (short x, uint8_t y, short z) {
|
||||||
for (int i = 0; i < block_changes_count; i ++) {
|
for (int i = 0; i < block_changes_count; i ++) {
|
||||||
if (block_changes[i].block == 0xFF) continue;
|
if (block_changes[i].block == 0xFF) continue;
|
||||||
@@ -411,7 +547,21 @@ uint8_t makeBlockChange (short x, uint8_t y, short z, uint8_t block) {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
if (is_base_block) block_changes[i].block = 0xFF;
|
if (is_base_block) block_changes[i].block = 0xFF;
|
||||||
else block_changes[i].block = block;
|
else {
|
||||||
|
#ifdef ALLOW_CHESTS
|
||||||
|
// When placing chests, just unallocate the target block and fall
|
||||||
|
// through to the chest-specific routine below.
|
||||||
|
if (block == B_chest) {
|
||||||
|
block_changes[i].block = 0xFF;
|
||||||
|
if (first_gap > i) first_gap = i;
|
||||||
|
#ifndef DISK_SYNC_BLOCKS_ON_INTERVAL
|
||||||
|
writeBlockChangesToDisk(i, i);
|
||||||
|
#endif
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
block_changes[i].block = block;
|
||||||
|
}
|
||||||
#ifndef DISK_SYNC_BLOCKS_ON_INTERVAL
|
#ifndef DISK_SYNC_BLOCKS_ON_INTERVAL
|
||||||
writeBlockChangesToDisk(i, i);
|
writeBlockChangesToDisk(i, i);
|
||||||
#endif
|
#endif
|
||||||
@@ -430,6 +580,8 @@ uint8_t makeBlockChange (short x, uint8_t y, short z, uint8_t block) {
|
|||||||
// which naturally appends the chest to the end if a gap isn't found.
|
// which naturally appends the chest to the end if a gap isn't found.
|
||||||
int last_real_entry = first_gap - 1;
|
int last_real_entry = first_gap - 1;
|
||||||
for (int i = first_gap; i <= block_changes_count + 15; i ++) {
|
for (int i = first_gap; i <= block_changes_count + 15; i ++) {
|
||||||
|
if (i >= MAX_BLOCK_CHANGES) break; // No more space, trigger failBlockChange
|
||||||
|
|
||||||
if (block_changes[i].block != 0xFF) {
|
if (block_changes[i].block != 0xFF) {
|
||||||
last_real_entry = i;
|
last_real_entry = i;
|
||||||
continue;
|
continue;
|
||||||
@@ -610,6 +762,13 @@ uint8_t isInstantlyMined (PlayerData *player, uint8_t block) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Checks whether the given block can fall down (like sand, anvils, ...)
|
||||||
|
uint8_t isFallingBlock (uint8_t block) {
|
||||||
|
return (
|
||||||
|
block == B_sand
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Checks whether the given block has to have something beneath it
|
// Checks whether the given block has to have something beneath it
|
||||||
uint8_t isColumnBlock (uint8_t block) {
|
uint8_t isColumnBlock (uint8_t block) {
|
||||||
return (
|
return (
|
||||||
@@ -637,6 +796,14 @@ uint8_t isPassableBlock (uint8_t block) {
|
|||||||
block == B_torch
|
block == B_torch
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
// Checks whether the given block is non-solid and spawnable
|
||||||
|
uint8_t isPassableSpawnBlock (uint8_t block) {
|
||||||
|
if (getFluid(block))
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return isPassableBlock(block);
|
||||||
|
}
|
||||||
|
|
||||||
// Checks whether the given block can be replaced by another block
|
// Checks whether the given block can be replaced by another block
|
||||||
uint8_t isReplaceableBlock (uint8_t block) {
|
uint8_t isReplaceableBlock (uint8_t block) {
|
||||||
@@ -877,6 +1044,17 @@ void handleFluidMovement (short x, uint8_t y, short z, uint8_t fluid, uint8_t bl
|
|||||||
// a higher fluid "level" means the fluid has traveled farther
|
// a higher fluid "level" means the fluid has traveled farther
|
||||||
uint8_t level = block - fluid;
|
uint8_t level = block - fluid;
|
||||||
|
|
||||||
|
uint8_t max_level;
|
||||||
|
switch (fluid) {
|
||||||
|
case B_lava:
|
||||||
|
max_level = 3;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case B_water:
|
||||||
|
max_level = 7;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
// Query blocks adjacent to this fluid stream
|
// Query blocks adjacent to this fluid stream
|
||||||
uint8_t adjacent[4] = {
|
uint8_t adjacent[4] = {
|
||||||
getBlockAt(x + 1, y, z),
|
getBlockAt(x + 1, y, z),
|
||||||
@@ -898,10 +1076,7 @@ void handleFluidMovement (short x, uint8_t y, short z, uint8_t fluid, uint8_t bl
|
|||||||
// If not connected, clear this block and recalculate surrounding flow
|
// If not connected, clear this block and recalculate surrounding flow
|
||||||
if (!connected) {
|
if (!connected) {
|
||||||
makeBlockChange(x, y, z, B_air);
|
makeBlockChange(x, y, z, B_air);
|
||||||
checkFluidUpdate(x + 1, y, z, adjacent[0]);
|
updateXYZNeighbors(x, y, z, UPDATE_NOW);
|
||||||
checkFluidUpdate(x - 1, y, z, adjacent[1]);
|
|
||||||
checkFluidUpdate(x, y, z + 1, adjacent[2]);
|
|
||||||
checkFluidUpdate(x, y, z - 1, adjacent[3]);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -910,42 +1085,38 @@ void handleFluidMovement (short x, uint8_t y, short z, uint8_t fluid, uint8_t bl
|
|||||||
uint8_t block_below = getBlockAt(x, y - 1, z);
|
uint8_t block_below = getBlockAt(x, y - 1, z);
|
||||||
if (isReplaceableBlock(block_below)) {
|
if (isReplaceableBlock(block_below)) {
|
||||||
makeBlockChange(x, y - 1, z, fluid);
|
makeBlockChange(x, y - 1, z, fluid);
|
||||||
return handleFluidMovement(x, y - 1, z, fluid, fluid);
|
updateXYZNeighbors(x, y, z, UPDATE_NOW);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stop flowing laterally at the maximum level
|
// Stop flowing laterally at the maximum level
|
||||||
if (level == 3 && fluid == B_lava) return;
|
if (level == max_level) return;
|
||||||
if (level == 7) return;
|
|
||||||
|
|
||||||
// Handle lateral water flow, increasing level by 1
|
// Handle lateral water flow, increasing level by 1
|
||||||
if (isReplaceableFluid(adjacent[0], level, fluid)) {
|
if (isReplaceableFluid(adjacent[0], level, fluid)) {
|
||||||
makeBlockChange(x + 1, y, z, block + 1);
|
makeBlockChange(x + 1, y, z, block + 1);
|
||||||
handleFluidMovement(x + 1, y, z, fluid, block + 1);
|
updateXYZNeighbors(x, y, z, UPDATE_NOW);
|
||||||
}
|
}
|
||||||
if (isReplaceableFluid(adjacent[1], level, fluid)) {
|
if (isReplaceableFluid(adjacent[1], level, fluid)) {
|
||||||
makeBlockChange(x - 1, y, z, block + 1);
|
makeBlockChange(x - 1, y, z, block + 1);
|
||||||
handleFluidMovement(x - 1, y, z, fluid, block + 1);
|
updateXYZNeighbors(x, y, z, UPDATE_NOW);
|
||||||
}
|
}
|
||||||
if (isReplaceableFluid(adjacent[2], level, fluid)) {
|
if (isReplaceableFluid(adjacent[2], level, fluid)) {
|
||||||
makeBlockChange(x, y, z + 1, block + 1);
|
makeBlockChange(x, y, z + 1, block + 1);
|
||||||
handleFluidMovement(x, y, z + 1, fluid, block + 1);
|
updateXYZNeighbors(x, y, z, UPDATE_NOW);
|
||||||
}
|
}
|
||||||
if (isReplaceableFluid(adjacent[3], level, fluid)) {
|
if (isReplaceableFluid(adjacent[3], level, fluid)) {
|
||||||
makeBlockChange(x, y, z - 1, block + 1);
|
makeBlockChange(x, y, z - 1, block + 1);
|
||||||
handleFluidMovement(x, y, z - 1, fluid, block + 1);
|
updateXYZNeighbors(x, y, z, UPDATE_NOW);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void checkFluidUpdate (short x, uint8_t y, short z, uint8_t block) {
|
uint8_t getFluid(uint8_t block) {
|
||||||
|
if (block >= B_water && block < B_water + 8)
|
||||||
uint8_t fluid;
|
return B_water;
|
||||||
if (block >= B_water && block < B_water + 8) fluid = B_water;
|
else if (block >= B_lava && block < B_lava + 4)
|
||||||
else if (block >= B_lava && block < B_lava + 4) fluid = B_lava;
|
return B_lava;
|
||||||
else return;
|
return 0;
|
||||||
|
|
||||||
handleFluidMovement(x, y, z, fluid, block);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef ENABLE_PICKUP_ANIMATION
|
#ifdef ENABLE_PICKUP_ANIMATION
|
||||||
@@ -983,6 +1154,111 @@ void playPickupAnimation (PlayerData *player, uint16_t item, double x, double y,
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// can not be zero!!
|
||||||
|
#define SAND_FALL_T 15
|
||||||
|
#define SAND_FALL_PROPAGATE_T 2
|
||||||
|
|
||||||
|
void processBlockUpdate (short x, uint8_t y, short z, uint8_t block, short update_kind) {
|
||||||
|
if (update_kind & UPDATE_BASIC) {
|
||||||
|
// "normal" block update
|
||||||
|
}
|
||||||
|
|
||||||
|
if (update_kind & UPDATE_CHECK_WATER) {
|
||||||
|
uint8_t fluid = getFluid(block);
|
||||||
|
if (fluid) {
|
||||||
|
uint8_t flow_speed;
|
||||||
|
switch (fluid) {
|
||||||
|
case B_lava:
|
||||||
|
flow_speed = 30;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case B_water:
|
||||||
|
flow_speed = 5;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
deferBlockUpdate(x, y, z, flow_speed, UPDATE_FLOW_WATER);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (update_kind & UPDATE_FLOW_WATER) {
|
||||||
|
uint8_t fluid = getFluid(block);
|
||||||
|
if (fluid) {
|
||||||
|
handleFluidMovement(x, y, z, fluid, block);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (update_kind & UPDATE_CHECK_SAND_FALL) {
|
||||||
|
// we have a separate UPDATE_CHECK_SAND_FALL,
|
||||||
|
// to make chains of falling sand fall as one,
|
||||||
|
// with less delay between each
|
||||||
|
|
||||||
|
if (isFallingBlock(block) && y) {
|
||||||
|
uint8_t below = getBlockAt(x, y - 1, z);
|
||||||
|
if (isReplaceableBlock(below)) {
|
||||||
|
// move the sand down in 15 ticks
|
||||||
|
deferBlockUpdate(x, y, z, SAND_FALL_T - 1, UPDATE_FALL_SAND);
|
||||||
|
|
||||||
|
if (y != 255 && isFallingBlock(getBlockAt(x, y + 1, z))) {
|
||||||
|
// also tell the block above that the sand will be gone soon
|
||||||
|
deferBlockUpdate(x, y + 1, z, SAND_FALL_PROPAGATE_T, UPDATE_CHECK_SAND_FALL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (update_kind & UPDATE_FALL_SAND) {
|
||||||
|
// make sure that is a fallable:tm: block, and we are not in the floor
|
||||||
|
if (isFallingBlock(block) && y) {
|
||||||
|
uint8_t below = getBlockAt(x, y - 1, z);
|
||||||
|
// TODO: what to do if below block breaks sand
|
||||||
|
if (isReplaceableBlock(below)) {
|
||||||
|
// TODO: drop item of below block
|
||||||
|
makeBlockChange(x, y, z, B_air);
|
||||||
|
makeBlockChange(x, y - 1, z, B_air);
|
||||||
|
makeBlockChange(x, y - 1, z, block);
|
||||||
|
// update this block after moved
|
||||||
|
processBlockUpdate (x, y - 1, z, block, UPDATE_NOW);
|
||||||
|
// also update the neighbors
|
||||||
|
updateXZNeighbors(x, y, z, UPDATE_NOW);
|
||||||
|
updateXZNeighbors(x, y - 1, z, UPDATE_NOW);
|
||||||
|
if (y != 255) {
|
||||||
|
processBlockUpdate(x, y + 1, z, getBlockAt(x, y + 1, z), UPDATE_NOW);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateXZNeighbors (short x, uint8_t y, short z, short update_kind) {
|
||||||
|
processBlockUpdate(x - 1, y, z, getBlockAt(x - 1, y, z), update_kind);
|
||||||
|
processBlockUpdate(x + 1, y, z, getBlockAt(x + 1, y, z), update_kind);
|
||||||
|
processBlockUpdate(x, y, z - 1, getBlockAt(x, y, z - 1), update_kind);
|
||||||
|
processBlockUpdate(x, y, z + 1, getBlockAt(x, y, z + 1), update_kind);
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateXYZNeighbors (short x, uint8_t y, short z, short update_kind) {
|
||||||
|
processBlockUpdate(x - 1, y, z, getBlockAt(x - 1, y, z), update_kind);
|
||||||
|
processBlockUpdate(x + 1, y, z, getBlockAt(x + 1, y, z), update_kind);
|
||||||
|
processBlockUpdate(x, y, z - 1, getBlockAt(x, y, z - 1), update_kind);
|
||||||
|
processBlockUpdate(x, y, z + 1, getBlockAt(x, y, z + 1), update_kind);
|
||||||
|
processBlockUpdate(x, y - 1, z, getBlockAt(x, y - 1, z), update_kind);
|
||||||
|
processBlockUpdate(x, y + 1, z, getBlockAt(x, y + 1, z), update_kind);
|
||||||
|
}
|
||||||
|
|
||||||
|
void deferBlockUpdate (short x, uint8_t y, short z, uint8_t await_ticks, short update_kind) {
|
||||||
|
if (deferred_block_updates_count == MAX_DEFERRED_BLOCK_UPDATES) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DeferredBlockUpdate *u = &deferred_block_updates[deferred_block_updates_count ++];
|
||||||
|
u->x = x;
|
||||||
|
u->y = y;
|
||||||
|
u->z = z;
|
||||||
|
u->await_ticks = await_ticks + is_processing_deferred_block_updates;
|
||||||
|
u->update_kind = update_kind;
|
||||||
|
}
|
||||||
|
|
||||||
void handlePlayerAction (PlayerData *player, int action, short x, short y, short z) {
|
void handlePlayerAction (PlayerData *player, int action, short x, short y, short z) {
|
||||||
|
|
||||||
// Re-sync slot when player drops an item
|
// Re-sync slot when player drops an item
|
||||||
@@ -1006,55 +1282,34 @@ void handlePlayerAction (PlayerData *player, int action, short x, short y, short
|
|||||||
// Ignore further actions not pertaining to mining blocks
|
// Ignore further actions not pertaining to mining blocks
|
||||||
if (action != 0 && action != 2) return;
|
if (action != 0 && action != 2) return;
|
||||||
|
|
||||||
|
uint8_t block = getBlockAt(x, y, z);
|
||||||
|
|
||||||
// In creative, only the "start mining" action is sent
|
// In creative, only the "start mining" action is sent
|
||||||
// No additional verification is performed, the block is simply removed
|
// No additional verification is performed, the block is simply removed
|
||||||
if (action == 0 && GAMEMODE == 1) {
|
if (action == 0 && GAMEMODE == 1) {
|
||||||
makeBlockChange(x, y, z, 0);
|
makeBlockChange(x, y, z, B_air);
|
||||||
return;
|
}
|
||||||
|
else {
|
||||||
|
// If this is a "start mining" packet, the block must be instamine
|
||||||
|
if (action == 0 && !isInstantlyMined(player, block)) return;
|
||||||
|
|
||||||
|
// Don't continue if the block change failed
|
||||||
|
if (makeBlockChange(x, y, z, B_air)) return;
|
||||||
|
|
||||||
|
uint16_t held_item = player->inventory_items[player->hotbar];
|
||||||
|
uint16_t item = getMiningResult(held_item, block);
|
||||||
|
bumpToolDurability(player);
|
||||||
|
|
||||||
|
if (item) {
|
||||||
|
#ifdef ENABLE_PICKUP_ANIMATION
|
||||||
|
playPickupAnimation(player, item, x, y, z);
|
||||||
|
#endif
|
||||||
|
givePlayerItem(player, item, 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t block = getBlockAt(x, y, z);
|
// Update nearby blocks
|
||||||
|
updateXYZNeighbors(x, y, z, UPDATE_NOW);
|
||||||
// If this is a "start mining" packet, the block must be instamine
|
|
||||||
if (action == 0 && !isInstantlyMined(player, block)) return;
|
|
||||||
|
|
||||||
// Don't continue if the block change failed
|
|
||||||
if (makeBlockChange(x, y, z, 0)) return;
|
|
||||||
|
|
||||||
uint16_t held_item = player->inventory_items[player->hotbar];
|
|
||||||
uint16_t item = getMiningResult(held_item, block);
|
|
||||||
bumpToolDurability(player);
|
|
||||||
|
|
||||||
if (item) {
|
|
||||||
#ifdef ENABLE_PICKUP_ANIMATION
|
|
||||||
playPickupAnimation(player, item, x, y, z);
|
|
||||||
#endif
|
|
||||||
givePlayerItem(player, item, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update nearby fluids
|
|
||||||
uint8_t block_above = getBlockAt(x, y + 1, z);
|
|
||||||
#ifdef DO_FLUID_FLOW
|
|
||||||
checkFluidUpdate(x, y + 1, z, block_above);
|
|
||||||
checkFluidUpdate(x - 1, y, z, getBlockAt(x - 1, y, z));
|
|
||||||
checkFluidUpdate(x + 1, y, z, getBlockAt(x + 1, y, z));
|
|
||||||
checkFluidUpdate(x, y, z - 1, getBlockAt(x, y, z - 1));
|
|
||||||
checkFluidUpdate(x, y, z + 1, getBlockAt(x, y, z + 1));
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Check if any blocks above this should break, and if so,
|
|
||||||
// iterate upward over all blocks in the column and break them
|
|
||||||
uint8_t y_offset = 1;
|
|
||||||
while (isColumnBlock(block_above)) {
|
|
||||||
// Destroy the next block
|
|
||||||
makeBlockChange(x, y + y_offset, z, 0);
|
|
||||||
// Check for item drops *without a tool*
|
|
||||||
uint16_t item = getMiningResult(0, block_above);
|
|
||||||
if (item) givePlayerItem(player, item, 1);
|
|
||||||
// Select the next block in the column
|
|
||||||
y_offset ++;
|
|
||||||
block_above = getBlockAt(x, y + y_offset, z);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void handlePlayerUseItem (PlayerData *player, short x, short y, short z, uint8_t face) {
|
void handlePlayerUseItem (PlayerData *player, short x, short y, short z, uint8_t face) {
|
||||||
@@ -1151,7 +1406,7 @@ void handlePlayerUseItem (PlayerData *player, short x, short y, short z, uint8_t
|
|||||||
} else if (getItemDefensePoints(*item) != 0) {
|
} else if (getItemDefensePoints(*item) != 0) {
|
||||||
// For some reason, this action is sent twice when looking at a block
|
// For some reason, this action is sent twice when looking at a block
|
||||||
// Ignore the variant that has coordinates
|
// Ignore the variant that has coordinates
|
||||||
if (face == 255) return;
|
if (face != 255) return;
|
||||||
// Swap to held piece of armor
|
// Swap to held piece of armor
|
||||||
uint8_t slot = getArmorItemSlot(*item);
|
uint8_t slot = getArmorItemSlot(*item);
|
||||||
uint16_t prev_item = player->inventory_items[slot];
|
uint16_t prev_item = player->inventory_items[slot];
|
||||||
@@ -1190,8 +1445,7 @@ void handlePlayerUseItem (PlayerData *player, short x, short y, short z, uint8_t
|
|||||||
(y == player->y || y == player->y + 1) &&
|
(y == player->y || y == player->y + 1) &&
|
||||||
z == player->z
|
z == player->z
|
||||||
) &&
|
) &&
|
||||||
isReplaceableBlock(getBlockAt(x, y, z)) &&
|
isReplaceableBlock(getBlockAt(x, y, z))
|
||||||
(!isColumnBlock(block) || getBlockAt(x, y - 1, z) != B_air)
|
|
||||||
) {
|
) {
|
||||||
// Apply server-side block change
|
// Apply server-side block change
|
||||||
if (makeBlockChange(x, y, z, block)) return;
|
if (makeBlockChange(x, y, z, block)) return;
|
||||||
@@ -1199,14 +1453,9 @@ void handlePlayerUseItem (PlayerData *player, short x, short y, short z, uint8_t
|
|||||||
*count -= 1;
|
*count -= 1;
|
||||||
// Clear item id in slot if amount is zero
|
// Clear item id in slot if amount is zero
|
||||||
if (*count == 0) *item = 0;
|
if (*count == 0) *item = 0;
|
||||||
// Calculate fluid flow
|
// Send updates
|
||||||
#ifdef DO_FLUID_FLOW
|
processBlockUpdate(x, y, z, block, UPDATE_NOW);
|
||||||
checkFluidUpdate(x, y + 1, z, getBlockAt(x, y + 1, z));
|
updateXYZNeighbors(x, y, z, UPDATE_NOW);
|
||||||
checkFluidUpdate(x - 1, y, z, getBlockAt(x - 1, y, z));
|
|
||||||
checkFluidUpdate(x + 1, y, z, getBlockAt(x + 1, y, z));
|
|
||||||
checkFluidUpdate(x, y, z - 1, getBlockAt(x, y, z - 1));
|
|
||||||
checkFluidUpdate(x, y, z + 1, getBlockAt(x, y, z + 1));
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sync hotbar contents to player
|
// Sync hotbar contents to player
|
||||||
@@ -1246,11 +1495,57 @@ void spawnMob (uint8_t type, short x, uint8_t y, short z, uint8_t health) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Freshly spawned mobs currently don't need metadata updates.
|
||||||
|
// If this changes, uncomment this line.
|
||||||
|
// broadcastMobMetadata(-1, i);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void interactEntity (int entity_id, int interactor_id) {
|
||||||
|
|
||||||
|
PlayerData *player;
|
||||||
|
if (getPlayerData(interactor_id, &player)) return;
|
||||||
|
|
||||||
|
MobData *mob = &mob_data[-entity_id - 2];
|
||||||
|
|
||||||
|
switch (mob->type) {
|
||||||
|
case 106: // Sheep
|
||||||
|
if (player->inventory_items[player->hotbar] != I_shears)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if ((mob->data >> 5) & 1) // Check if sheep has already been sheared
|
||||||
|
return;
|
||||||
|
|
||||||
|
mob->data |= 1 << 5; // Set sheared to true
|
||||||
|
|
||||||
|
bumpToolDurability(player);
|
||||||
|
|
||||||
|
#ifdef ENABLE_PICKUP_ANIMATION
|
||||||
|
playPickupAnimation(player, I_white_wool, mob->x, mob->y, mob->z);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
uint8_t item_count = 1 + (fast_rand() & 1); // 1-2
|
||||||
|
givePlayerItem(player, I_white_wool, item_count);
|
||||||
|
|
||||||
|
for (int i = 0; i < MAX_PLAYERS; i ++) {
|
||||||
|
PlayerData* player = &player_data[i];
|
||||||
|
int client_fd = player->client_fd;
|
||||||
|
|
||||||
|
if (client_fd == -1) continue;
|
||||||
|
if (player->flags & 0x20) continue;
|
||||||
|
|
||||||
|
sc_entityAnimation(client_fd, interactor_id, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
broadcastMobMetadata(-1, entity_id);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void hurtEntity (int entity_id, int attacker_id, uint8_t damage_type, uint8_t damage) {
|
void hurtEntity (int entity_id, int attacker_id, uint8_t damage_type, uint8_t damage) {
|
||||||
|
|
||||||
if (attacker_id > 0) { // Attacker is a player
|
if (attacker_id > 0) { // Attacker is a player
|
||||||
@@ -1328,10 +1623,14 @@ void hurtEntity (int entity_id, int attacker_id, uint8_t damage_type, uint8_t da
|
|||||||
strcpy((char *)recv_buffer + player_name_len, " was slain by ");
|
strcpy((char *)recv_buffer + player_name_len, " was slain by ");
|
||||||
strcpy((char *)recv_buffer + player_name_len + 14, attacker->name);
|
strcpy((char *)recv_buffer + player_name_len + 14, attacker->name);
|
||||||
recv_buffer[player_name_len + 14 + strlen(attacker->name)] = '\0';
|
recv_buffer[player_name_len + 14 + strlen(attacker->name)] = '\0';
|
||||||
|
} else if (damage_type == D_cactus) {
|
||||||
|
// Killed by being near a cactus
|
||||||
|
strcpy((char *)recv_buffer + player_name_len, " was pricked to death");
|
||||||
|
recv_buffer[player_name_len + 21] = '\0';
|
||||||
} else {
|
} else {
|
||||||
// Unknown death reason
|
// Unknown death reason
|
||||||
strcpy((char *)recv_buffer + player_name_len, " died");
|
strcpy((char *)recv_buffer + player_name_len, " died");
|
||||||
recv_buffer[player_name_len + 4] = '\0';
|
recv_buffer[player_name_len + 5] = '\0';
|
||||||
}
|
}
|
||||||
|
|
||||||
} else player->health -= effective_damage;
|
} else player->health -= effective_damage;
|
||||||
@@ -1347,6 +1646,9 @@ void hurtEntity (int entity_id, int attacker_id, uint8_t damage_type, uint8_t da
|
|||||||
// Don't continue if the mob is already dead
|
// Don't continue if the mob is already dead
|
||||||
if (mob_health == 0) return;
|
if (mob_health == 0) return;
|
||||||
|
|
||||||
|
// Set the mob's panic timer
|
||||||
|
mob->data |= (3 << 6);
|
||||||
|
|
||||||
// Process health change on the server
|
// Process health change on the server
|
||||||
if (mob_health <= damage) {
|
if (mob_health <= damage) {
|
||||||
|
|
||||||
@@ -1405,8 +1707,7 @@ void handleServerTick (int64_t time_since_last_tick) {
|
|||||||
// If 3 seconds (60 vanilla ticks) have passed, assume player has loaded
|
// If 3 seconds (60 vanilla ticks) have passed, assume player has loaded
|
||||||
player->flagval_16 ++;
|
player->flagval_16 ++;
|
||||||
if (player->flagval_16 > (uint16_t)(3 * TICKS_PER_SECOND)) {
|
if (player->flagval_16 > (uint16_t)(3 * TICKS_PER_SECOND)) {
|
||||||
player->flags &= ~0x20;
|
handlePlayerJoin(player);
|
||||||
player->flagval_16 = 0;
|
|
||||||
} else continue;
|
} else continue;
|
||||||
}
|
}
|
||||||
// Reset player attack cooldown
|
// Reset player attack cooldown
|
||||||
@@ -1424,6 +1725,11 @@ void handleServerTick (int64_t time_since_last_tick) {
|
|||||||
player->flagval_16 = 0;
|
player->flagval_16 = 0;
|
||||||
} else player->flagval_16 ++;
|
} else player->flagval_16 ++;
|
||||||
}
|
}
|
||||||
|
// Reset movement update cooldown if not broadcasting every update
|
||||||
|
// Effectively ties player movement updates to the tickrate
|
||||||
|
#ifndef BROADCAST_ALL_MOVEMENT
|
||||||
|
player->flags &= ~0x40;
|
||||||
|
#endif
|
||||||
// Below this, process events that happen once per second
|
// Below this, process events that happen once per second
|
||||||
if (server_ticks % (uint32_t)TICKS_PER_SECOND != 0) continue;
|
if (server_ticks % (uint32_t)TICKS_PER_SECOND != 0) continue;
|
||||||
// Send Keep Alive and Update Time packets
|
// Send Keep Alive and Update Time packets
|
||||||
@@ -1434,6 +1740,15 @@ void handleServerTick (int64_t time_since_last_tick) {
|
|||||||
if (block >= B_lava && block < B_lava + 4) {
|
if (block >= B_lava && block < B_lava + 4) {
|
||||||
hurtEntity(player->client_fd, -1, D_lava, 8);
|
hurtEntity(player->client_fd, -1, D_lava, 8);
|
||||||
}
|
}
|
||||||
|
#ifdef ENABLE_CACTUS_DAMAGE
|
||||||
|
// Tick damage from a cactus block if one is under/inside or around the player.
|
||||||
|
if (block == B_cactus ||
|
||||||
|
getBlockAt(player->x + 1, player->y, player->z) == B_cactus ||
|
||||||
|
getBlockAt(player->x - 1, player->y, player->z) == B_cactus ||
|
||||||
|
getBlockAt(player->x, player->y, player->z + 1) == B_cactus ||
|
||||||
|
getBlockAt(player->x, player->y, player->z - 1) == B_cactus
|
||||||
|
) hurtEntity(player->client_fd, -1, D_cactus, 4);
|
||||||
|
#endif
|
||||||
// Heal from saturation if player is able and has enough food
|
// Heal from saturation if player is able and has enough food
|
||||||
if (player->health >= 20 || player->health == 0) continue;
|
if (player->health >= 20 || player->health == 0) continue;
|
||||||
if (player->hunger < 18) continue;
|
if (player->hunger < 18) continue;
|
||||||
@@ -1447,11 +1762,8 @@ void handleServerTick (int64_t time_since_last_tick) {
|
|||||||
sc_setHealth(player->client_fd, player->health, player->hunger, player->saturation);
|
sc_setHealth(player->client_fd, player->health, player->hunger, player->saturation);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write data to file (if applicable)
|
// Perform regular checks for if it's time to write to disk
|
||||||
writePlayerDataToDisk();
|
writeDataToDiskOnInterval();
|
||||||
#ifdef DISK_SYNC_BLOCKS_ON_INTERVAL
|
|
||||||
writeBlockChangesToDisk(0, block_changes_count);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If the RNG seed ever hits 0, it'll never generate anything
|
* If the RNG seed ever hits 0, it'll never generate anything
|
||||||
@@ -1462,6 +1774,22 @@ void handleServerTick (int64_t time_since_last_tick) {
|
|||||||
*/
|
*/
|
||||||
if (rng_seed == 0) rng_seed = world_seed;
|
if (rng_seed == 0) rng_seed = world_seed;
|
||||||
|
|
||||||
|
// block updates might add more deferred block updates,
|
||||||
|
// so we temporarily make all new block updates add one more tick to the defer tick counter
|
||||||
|
is_processing_deferred_block_updates = 1;
|
||||||
|
int next_update_idx = 0;
|
||||||
|
for (int i = 0; i < deferred_block_updates_count; i ++) {
|
||||||
|
DeferredBlockUpdate *u = &deferred_block_updates[i];
|
||||||
|
if (u->await_ticks) {
|
||||||
|
u->await_ticks --;
|
||||||
|
deferred_block_updates[next_update_idx ++] = *u;
|
||||||
|
} else {
|
||||||
|
processBlockUpdate(u->x, u->y, u->z, getBlockAt(u->x, u->y, u->z), u->update_kind);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
deferred_block_updates_count = next_update_idx;
|
||||||
|
is_processing_deferred_block_updates = 0;
|
||||||
|
|
||||||
// Tick mob behavior
|
// Tick mob behavior
|
||||||
for (int i = 0; i < MAX_MOBS; i ++) {
|
for (int i = 0; i < MAX_MOBS; i ++) {
|
||||||
if (mob_data[i].type == 0) continue;
|
if (mob_data[i].type == 0) continue;
|
||||||
@@ -1490,6 +1818,9 @@ void handleServerTick (int64_t time_since_last_tick) {
|
|||||||
mob_data[i].type == 95 || // Pig
|
mob_data[i].type == 95 || // Pig
|
||||||
mob_data[i].type == 106 // Sheep
|
mob_data[i].type == 106 // Sheep
|
||||||
);
|
);
|
||||||
|
// Mob "panic" timer, set to 3 after being hit
|
||||||
|
// Currently has no effect on hostile mobs
|
||||||
|
uint8_t panic = (mob_data[i].data >> 6) & 3;
|
||||||
|
|
||||||
// Burn hostile mobs if above ground during sunlight
|
// Burn hostile mobs if above ground during sunlight
|
||||||
if (!passive && (world_time < 13000 || world_time > 23460) && mob_data[i].y > 48) {
|
if (!passive && (world_time < 13000 || world_time > 23460) && mob_data[i].y > 48) {
|
||||||
@@ -1499,8 +1830,21 @@ void handleServerTick (int64_t time_since_last_tick) {
|
|||||||
uint32_t r = fast_rand();
|
uint32_t r = fast_rand();
|
||||||
|
|
||||||
if (passive) {
|
if (passive) {
|
||||||
// Update passive mobs once per 4 seconds on average
|
if (panic) {
|
||||||
if (r % (4 * (unsigned int)TICKS_PER_SECOND) != 0) continue;
|
// If panicking, move randomly at up to 4 times per second
|
||||||
|
if (TICKS_PER_SECOND >= 4) {
|
||||||
|
uint32_t ticks_per_panic = (uint32_t)(TICKS_PER_SECOND / 4);
|
||||||
|
if (server_ticks % ticks_per_panic != 0) continue;
|
||||||
|
}
|
||||||
|
// Reset panic state after timer runs out
|
||||||
|
// Each panic timer tick takes one second
|
||||||
|
if (server_ticks % (uint32_t)TICKS_PER_SECOND == 0) {
|
||||||
|
mob_data[i].data -= (1 << 6);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// When not panicking, move idly once per 4 seconds on average
|
||||||
|
if (r % (4 * (unsigned int)TICKS_PER_SECOND) != 0) continue;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Update hostile mobs once per second
|
// Update hostile mobs once per second
|
||||||
if (server_ticks % (uint32_t)TICKS_PER_SECOND != 0) continue;
|
if (server_ticks % (uint32_t)TICKS_PER_SECOND != 0) continue;
|
||||||
@@ -1527,8 +1871,11 @@ void handleServerTick (int64_t time_since_last_tick) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
short new_x = mob_data[i].x, new_z = mob_data[i].z;
|
short old_x = mob_data[i].x, old_z = mob_data[i].z;
|
||||||
uint8_t new_y = mob_data[i].y, yaw = 0;
|
uint8_t old_y = mob_data[i].y;
|
||||||
|
|
||||||
|
short new_x = old_x, new_z = old_z;
|
||||||
|
uint8_t new_y = old_y, yaw = 0;
|
||||||
|
|
||||||
if (passive) { // Passive mob movement handling
|
if (passive) { // Passive mob movement handling
|
||||||
|
|
||||||
@@ -1545,48 +1892,98 @@ void handleServerTick (int64_t time_since_last_tick) {
|
|||||||
} else { // Hostile mob movement handling
|
} else { // Hostile mob movement handling
|
||||||
|
|
||||||
// If we're already next to the player, hurt them and skip movement
|
// If we're already next to the player, hurt them and skip movement
|
||||||
if (closest_dist < 3 && abs(mob_data[i].y - closest_player->y) < 2) {
|
if (closest_dist < 3 && abs(old_y - closest_player->y) < 2) {
|
||||||
hurtEntity(closest_player->client_fd, entity_id, D_generic, 6);
|
hurtEntity(closest_player->client_fd, entity_id, D_generic, 6);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Move towards the closest player on 8 axis
|
// Move towards the closest player on 8 axis
|
||||||
// The condition nesting ensures a correct yaw at 45 degree turns
|
// The condition nesting ensures a correct yaw at 45 degree turns
|
||||||
if (closest_player->x < mob_data[i].x) {
|
if (closest_player->x < old_x) {
|
||||||
new_x -= 1; yaw = 64;
|
new_x -= 1; yaw = 64;
|
||||||
if (closest_player->z < mob_data[i].z) { new_z -= 1; yaw += 32; }
|
if (closest_player->z < old_z) { new_z -= 1; yaw += 32; }
|
||||||
else if (closest_player->z > mob_data[i].z) { new_z += 1; yaw -= 32; }
|
else if (closest_player->z > old_z) { new_z += 1; yaw -= 32; }
|
||||||
}
|
}
|
||||||
else if (closest_player->x > mob_data[i].x) {
|
else if (closest_player->x > old_x) {
|
||||||
new_x += 1; yaw = 192;
|
new_x += 1; yaw = 192;
|
||||||
if (closest_player->z < mob_data[i].z) { new_z -= 1; yaw -= 32; }
|
if (closest_player->z < old_z) { new_z -= 1; yaw -= 32; }
|
||||||
else if (closest_player->z > mob_data[i].z) { new_z += 1; yaw += 32; }
|
else if (closest_player->z > old_z) { new_z += 1; yaw += 32; }
|
||||||
} else {
|
} else {
|
||||||
if (closest_player->z < mob_data[i].z) { new_z -= 1; yaw = 128; }
|
if (closest_player->z < old_z) { new_z -= 1; yaw = 128; }
|
||||||
else if (closest_player->z > mob_data[i].z) { new_z += 1; yaw = 0; }
|
else if (closest_player->z > old_z) { new_z += 1; yaw = 0; }
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Vary the yaw angle to look just a little less robotic
|
// Holds the block that the mob is moving into
|
||||||
yaw += ((r >> 7) & 31) - 16;
|
|
||||||
|
|
||||||
// Check if the blocks we're moving into are passable:
|
|
||||||
// if yes, and the block below is solid, keep the same Y level;
|
|
||||||
// if yes, but the block below isn't solid, drop down one block;
|
|
||||||
// if not, go up by up to one block;
|
|
||||||
// if going up isn't possible, skip this iteration.
|
|
||||||
uint8_t block = getBlockAt(new_x, new_y, new_z);
|
uint8_t block = getBlockAt(new_x, new_y, new_z);
|
||||||
|
// Holds the block above the target block, i.e. the "head" block
|
||||||
uint8_t block_above = getBlockAt(new_x, new_y + 1, new_z);
|
uint8_t block_above = getBlockAt(new_x, new_y + 1, new_z);
|
||||||
if (!isPassableBlock(block) || !isPassableBlock(block_above)) {
|
|
||||||
block = block_above;
|
// Validate movement on X axis
|
||||||
block_above = getBlockAt(new_x, new_y + 2, new_z);
|
if (new_x != old_x && (
|
||||||
if (isPassableBlock(block) && isPassableBlock(block_above)) new_y += 1;
|
!isPassableBlock(getBlockAt(new_x, new_y + 1, old_z)) ||
|
||||||
else continue;
|
(
|
||||||
} else {
|
!isPassableBlock(getBlockAt(new_x, new_y, old_z)) &&
|
||||||
uint8_t block_below = getBlockAt(new_x, new_y - 1, new_z);
|
!isPassableBlock(getBlockAt(new_x, new_y + 2, old_z))
|
||||||
if (isPassableBlock(block_below) && block != B_water) new_y -= 1;
|
)
|
||||||
|
)) {
|
||||||
|
new_x = old_x;
|
||||||
|
block = getBlockAt(old_x, new_y, new_z);
|
||||||
|
block_above = getBlockAt(old_x, new_y + 1, new_z);
|
||||||
}
|
}
|
||||||
|
// Validate movement on Z axis
|
||||||
|
if (new_z != old_z && (
|
||||||
|
!isPassableBlock(getBlockAt(old_x, new_y + 1, new_z)) ||
|
||||||
|
(
|
||||||
|
!isPassableBlock(getBlockAt(old_x, new_y, new_z)) &&
|
||||||
|
!isPassableBlock(getBlockAt(old_x, new_y + 2, new_z))
|
||||||
|
)
|
||||||
|
)) {
|
||||||
|
new_z = old_z;
|
||||||
|
block = getBlockAt(new_x, new_y, old_z);
|
||||||
|
block_above = getBlockAt(new_x, new_y + 1, old_z);
|
||||||
|
}
|
||||||
|
// Validate diagonal movement
|
||||||
|
if (new_x != old_x && new_z != old_z && (
|
||||||
|
!isPassableBlock(block_above) ||
|
||||||
|
(
|
||||||
|
!isPassableBlock(block) &&
|
||||||
|
!isPassableBlock(getBlockAt(new_x, new_y + 2, new_z))
|
||||||
|
)
|
||||||
|
)) {
|
||||||
|
// We know that movement along just one axis is fine thanks to the
|
||||||
|
// checks above, pick one based on proximity.
|
||||||
|
int dist_x = abs(old_x - closest_player->x);
|
||||||
|
int dist_z = abs(old_z - closest_player->z);
|
||||||
|
if (dist_x < dist_z) new_z = old_z;
|
||||||
|
else new_x = old_x;
|
||||||
|
block = getBlockAt(new_x, new_y, new_z);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if we're supposed to climb/drop one block
|
||||||
|
// The checks above already ensure that there's enough space to climb
|
||||||
|
if (!isPassableBlock(block)) new_y += 1;
|
||||||
|
else if (isPassableBlock(getBlockAt(new_x, new_y - 1, new_z))) new_y -= 1;
|
||||||
|
|
||||||
|
// Exit early if all movement was cancelled
|
||||||
|
if (new_x == mob_data[i].x && new_z == old_z && new_y == old_y) continue;
|
||||||
|
|
||||||
|
// Prevent collisions with other mobs
|
||||||
|
uint8_t colliding = false;
|
||||||
|
for (int j = 0; j < MAX_MOBS; j ++) {
|
||||||
|
if (j == i) continue;
|
||||||
|
if (mob_data[j].type == 0) continue;
|
||||||
|
if (
|
||||||
|
mob_data[j].x == new_x &&
|
||||||
|
mob_data[j].z == new_z &&
|
||||||
|
abs((int)mob_data[j].y - (int)new_y) < 2
|
||||||
|
) {
|
||||||
|
colliding = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (colliding) continue;
|
||||||
|
|
||||||
if ( // Hurt mobs that stumble into lava
|
if ( // Hurt mobs that stumble into lava
|
||||||
(block >= B_lava && block < B_lava + 4) ||
|
(block >= B_lava && block < B_lava + 4) ||
|
||||||
@@ -1598,6 +1995,9 @@ void handleServerTick (int64_t time_since_last_tick) {
|
|||||||
mob_data[i].y = new_y;
|
mob_data[i].y = new_y;
|
||||||
mob_data[i].z = new_z;
|
mob_data[i].z = new_z;
|
||||||
|
|
||||||
|
// Vary the yaw angle to look just a little less robotic
|
||||||
|
yaw += ((r >> 7) & 31) - 16;
|
||||||
|
|
||||||
// Broadcast relevant entity movement packets
|
// Broadcast relevant entity movement packets
|
||||||
for (int j = 0; j < MAX_PLAYERS; j ++) {
|
for (int j = 0; j < MAX_PLAYERS; j ++) {
|
||||||
if (player_data[j].client_fd == -1) continue;
|
if (player_data[j].client_fd == -1) continue;
|
||||||
@@ -1633,3 +2033,47 @@ void broadcastChestUpdate (int origin_fd, uint8_t *storage_ptr, uint16_t item, u
|
|||||||
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
ssize_t writeEntityData (int client_fd, EntityData *data) {
|
||||||
|
writeByte(client_fd, data->index);
|
||||||
|
writeVarInt(client_fd, data->type);
|
||||||
|
|
||||||
|
switch (data->type) {
|
||||||
|
case 0: // Byte
|
||||||
|
return writeByte(client_fd, data->value.byte);
|
||||||
|
case 21: // Pose
|
||||||
|
writeVarInt(client_fd, data->value.pose);
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
default: return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the networked size of an EntityData entry
|
||||||
|
int sizeEntityData (EntityData *data) {
|
||||||
|
int value_size;
|
||||||
|
|
||||||
|
switch (data->type) {
|
||||||
|
case 0: // Byte
|
||||||
|
value_size = 1;
|
||||||
|
break;
|
||||||
|
case 21: // Pose
|
||||||
|
value_size = sizeVarInt(data->value.pose);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default: return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1 + sizeVarInt(data->type) + value_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the networked size of an array of EntityData entries
|
||||||
|
int sizeEntityMetadata (EntityData *metadata, size_t length) {
|
||||||
|
int total_size = 0;
|
||||||
|
for (size_t i = 0; i < length; i ++) {
|
||||||
|
int size = sizeEntityData(&metadata[i]);
|
||||||
|
if (size == -1) return -1;
|
||||||
|
total_size += size;
|
||||||
|
}
|
||||||
|
return total_size;
|
||||||
|
}
|
||||||
|
@@ -16,6 +16,8 @@
|
|||||||
|
|
||||||
int64_t last_disk_sync_time = 0;
|
int64_t last_disk_sync_time = 0;
|
||||||
|
|
||||||
|
// TODO: store deferred block updates
|
||||||
|
|
||||||
// Restores world data from disk, or writes world file if it doesn't exist
|
// Restores world data from disk, or writes world file if it doesn't exist
|
||||||
int initSerializer () {
|
int initSerializer () {
|
||||||
|
|
||||||
@@ -45,6 +47,7 @@ int initSerializer () {
|
|||||||
size_t read = fread(block_changes, 1, sizeof(block_changes), file);
|
size_t read = fread(block_changes, 1, sizeof(block_changes), file);
|
||||||
if (read != sizeof(block_changes)) {
|
if (read != sizeof(block_changes)) {
|
||||||
printf("Read %u bytes from \"world.bin\", expected %u (block changes). Aborting.\n", read, sizeof(block_changes));
|
printf("Read %u bytes from \"world.bin\", expected %u (block changes). Aborting.\n", read, sizeof(block_changes));
|
||||||
|
fclose(file);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
// Find the index of the last occupied entry to recover block_changes_count
|
// Find the index of the last occupied entry to recover block_changes_count
|
||||||
@@ -56,6 +59,7 @@ int initSerializer () {
|
|||||||
// Seek past block changes to start reading player data
|
// Seek past block changes to start reading player data
|
||||||
if (fseek(file, sizeof(block_changes), SEEK_SET) != 0) {
|
if (fseek(file, sizeof(block_changes), SEEK_SET) != 0) {
|
||||||
perror("Failed to seek to player data in \"world.bin\". Aborting.");
|
perror("Failed to seek to player data in \"world.bin\". Aborting.");
|
||||||
|
fclose(file);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
// Read player data directly into memory
|
// Read player data directly into memory
|
||||||
@@ -86,6 +90,7 @@ int initSerializer () {
|
|||||||
"Failed to write initial block data to \"world.bin\".\n"
|
"Failed to write initial block data to \"world.bin\".\n"
|
||||||
"Consider checking permissions or disabling SYNC_WORLD_TO_DISK in \"globals.h\"."
|
"Consider checking permissions or disabling SYNC_WORLD_TO_DISK in \"globals.h\"."
|
||||||
);
|
);
|
||||||
|
fclose(file);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
// Seek past written block changes to start writing player data
|
// Seek past written block changes to start writing player data
|
||||||
@@ -94,6 +99,7 @@ int initSerializer () {
|
|||||||
"Failed to seek past block changes in \"world.bin\"."
|
"Failed to seek past block changes in \"world.bin\"."
|
||||||
"Consider checking permissions or disabling SYNC_WORLD_TO_DISK in \"globals.h\"."
|
"Consider checking permissions or disabling SYNC_WORLD_TO_DISK in \"globals.h\"."
|
||||||
);
|
);
|
||||||
|
fclose(file);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
// Write initial player data to disk (should be just nulls?)
|
// Write initial player data to disk (should be just nulls?)
|
||||||
@@ -115,12 +121,6 @@ int initSerializer () {
|
|||||||
// Writes a range of block change entries to disk
|
// Writes a range of block change entries to disk
|
||||||
void writeBlockChangesToDisk (int from, int to) {
|
void writeBlockChangesToDisk (int from, int to) {
|
||||||
|
|
||||||
#ifdef DISK_SYNC_BLOCKS_ON_INTERVAL
|
|
||||||
// Skip this write if enough time hasn't passed since the last one
|
|
||||||
if (get_program_time() - last_disk_sync_time < DISK_SYNC_INTERVAL) return;
|
|
||||||
last_disk_sync_time = get_program_time();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Try to open the file in rw (without overwriting)
|
// Try to open the file in rw (without overwriting)
|
||||||
FILE *file = fopen(FILE_PATH, "r+b");
|
FILE *file = fopen(FILE_PATH, "r+b");
|
||||||
if (!file) {
|
if (!file) {
|
||||||
@@ -149,10 +149,6 @@ void writeBlockChangesToDisk (int from, int to) {
|
|||||||
// Writes all player data to disk
|
// Writes all player data to disk
|
||||||
void writePlayerDataToDisk () {
|
void writePlayerDataToDisk () {
|
||||||
|
|
||||||
// Skip this write if enough time hasn't passed since the last one
|
|
||||||
if (get_program_time() - last_disk_sync_time < DISK_SYNC_INTERVAL) return;
|
|
||||||
last_disk_sync_time = get_program_time();
|
|
||||||
|
|
||||||
// Try to open the file in rw (without overwriting)
|
// Try to open the file in rw (without overwriting)
|
||||||
FILE *file = fopen(FILE_PATH, "r+b");
|
FILE *file = fopen(FILE_PATH, "r+b");
|
||||||
if (!file) {
|
if (!file) {
|
||||||
@@ -176,6 +172,21 @@ void writePlayerDataToDisk () {
|
|||||||
fclose(file);
|
fclose(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Writes data queued for interval writes, but only if enough time has passed
|
||||||
|
void writeDataToDiskOnInterval () {
|
||||||
|
|
||||||
|
// Skip this write if enough time hasn't passed since the last one
|
||||||
|
if (get_program_time() - last_disk_sync_time < DISK_SYNC_INTERVAL) return;
|
||||||
|
last_disk_sync_time = get_program_time();
|
||||||
|
|
||||||
|
// Write full player data and block changes buffers
|
||||||
|
writePlayerDataToDisk();
|
||||||
|
#ifdef DISK_SYNC_BLOCKS_ON_INTERVAL
|
||||||
|
writeBlockChangesToDisk(0, block_changes_count);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef ALLOW_CHESTS
|
#ifdef ALLOW_CHESTS
|
||||||
// Writes a chest slot change to disk
|
// Writes a chest slot change to disk
|
||||||
void writeChestChangesToDisk (uint8_t *storage_ptr, uint8_t slot) {
|
void writeChestChangesToDisk (uint8_t *storage_ptr, uint8_t slot) {
|
||||||
|
73
src/tools.c
73
src/tools.c
@@ -7,8 +7,13 @@
|
|||||||
#include "lwip/netdb.h"
|
#include "lwip/netdb.h"
|
||||||
#include "esp_timer.h"
|
#include "esp_timer.h"
|
||||||
#else
|
#else
|
||||||
#include <sys/socket.h>
|
#ifdef _WIN32
|
||||||
#include <arpa/inet.h>
|
#include <winsock2.h>
|
||||||
|
#include <ws2tcpip.h>
|
||||||
|
#else
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
#endif
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
#ifndef CLOCK_MONOTONIC
|
#ifndef CLOCK_MONOTONIC
|
||||||
@@ -21,14 +26,16 @@
|
|||||||
#include "procedures.h"
|
#include "procedures.h"
|
||||||
#include "tools.h"
|
#include "tools.h"
|
||||||
|
|
||||||
static uint64_t htonll (uint64_t value) {
|
#ifndef htonll
|
||||||
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
static uint64_t htonll (uint64_t value) {
|
||||||
return ((uint64_t)htonl((uint32_t)(value >> 32))) |
|
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
||||||
((uint64_t)htonl((uint32_t)(value & 0xFFFFFFFF)) << 32);
|
return ((uint64_t)htonl((uint32_t)(value >> 32))) |
|
||||||
#else
|
((uint64_t)htonl((uint32_t)(value & 0xFFFFFFFF)) << 32);
|
||||||
return value;
|
#else
|
||||||
|
return value;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
|
||||||
|
|
||||||
// Keep track of the total amount of bytes received with recv_all
|
// Keep track of the total amount of bytes received with recv_all
|
||||||
// Helps notice misread packets and clean up after errors
|
// Helps notice misread packets and clean up after errors
|
||||||
@@ -93,7 +100,11 @@ ssize_t send_all (int client_fd, const void *buf, ssize_t len) {
|
|||||||
|
|
||||||
// Busy-wait (with task yielding) until all data has been sent
|
// Busy-wait (with task yielding) until all data has been sent
|
||||||
while (sent < len) {
|
while (sent < len) {
|
||||||
ssize_t n = send(client_fd, p + sent, len - sent, MSG_NOSIGNAL);
|
#ifdef _WIN32
|
||||||
|
ssize_t n = send(client_fd, p + sent, len - sent, 0);
|
||||||
|
#else
|
||||||
|
ssize_t n = send(client_fd, p + sent, len - sent, MSG_NOSIGNAL);
|
||||||
|
#endif
|
||||||
if (n > 0) { // some data was sent, log it
|
if (n > 0) { // some data was sent, log it
|
||||||
sent += n;
|
sent += n;
|
||||||
last_update_time = get_program_time();
|
last_update_time = get_program_time();
|
||||||
@@ -104,7 +115,12 @@ ssize_t send_all (int client_fd, const void *buf, ssize_t len) {
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
// not yet ready to transmit, try again
|
// not yet ready to transmit, try again
|
||||||
|
#ifdef _WIN32 //handles windows socket timeout
|
||||||
|
int err = WSAGetLastError();
|
||||||
|
if (err == WSAEWOULDBLOCK || err == WSAEINTR) {
|
||||||
|
#else
|
||||||
if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK) {
|
if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK) {
|
||||||
|
#endif
|
||||||
// handle network timeout
|
// handle network timeout
|
||||||
if (get_program_time() - last_update_time > NETWORK_TIMEOUT_TIME) {
|
if (get_program_time() - last_update_time > NETWORK_TIMEOUT_TIME) {
|
||||||
disconnectClient(&client_fd, -2);
|
disconnectClient(&client_fd, -2);
|
||||||
@@ -201,12 +217,45 @@ double readDouble (int client_fd) {
|
|||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Receive length prefixed data with bounds checking
|
||||||
|
ssize_t readLengthPrefixedData (int client_fd) {
|
||||||
|
uint32_t length = readVarInt(client_fd);
|
||||||
|
if (length >= MAX_RECV_BUF_LEN) {
|
||||||
|
printf("ERROR: Received length (%lu) exceeds maximum (%u)\n", length, MAX_RECV_BUF_LEN);
|
||||||
|
disconnectClient(&client_fd, -1);
|
||||||
|
recv_count = 0;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return recv_all(client_fd, recv_buffer, length, false);
|
||||||
|
}
|
||||||
|
|
||||||
// Reads a networked string into recv_buffer
|
// Reads a networked string into recv_buffer
|
||||||
void readString (int client_fd) {
|
void readString (int client_fd) {
|
||||||
uint32_t length = readVarInt(client_fd);
|
recv_count = readLengthPrefixedData(client_fd);
|
||||||
recv_count = recv_all(client_fd, recv_buffer, length, false);
|
|
||||||
recv_buffer[recv_count] = '\0';
|
recv_buffer[recv_count] = '\0';
|
||||||
}
|
}
|
||||||
|
// Reads a networked string of up to N bytes into recv_buffer
|
||||||
|
void readStringN (int client_fd, uint32_t max_length) {
|
||||||
|
// Forward to readString if max length is invalid
|
||||||
|
if (max_length >= MAX_RECV_BUF_LEN) {
|
||||||
|
readString(client_fd);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Attempt to read full string within maximum
|
||||||
|
uint32_t length = readVarInt(client_fd);
|
||||||
|
if (max_length > length) {
|
||||||
|
recv_count = recv_all(client_fd, recv_buffer, length, false);
|
||||||
|
recv_buffer[recv_count] = '\0';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Read string up to maximum, dump the rest
|
||||||
|
recv_count = recv_all(client_fd, recv_buffer, max_length, false);
|
||||||
|
recv_buffer[recv_count] = '\0';
|
||||||
|
uint8_t dummy;
|
||||||
|
for (uint32_t i = max_length; i < length; i ++) {
|
||||||
|
recv_all(client_fd, &dummy, 1, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
uint32_t fast_rand () {
|
uint32_t fast_rand () {
|
||||||
rng_seed ^= rng_seed << 13;
|
rng_seed ^= rng_seed << 13;
|
||||||
|
@@ -1,5 +1,10 @@
|
|||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <arpa/inet.h>
|
#ifdef _WIN32
|
||||||
|
#include <winsock2.h>
|
||||||
|
#include <ws2tcpip.h>
|
||||||
|
#else
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
#endif
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
#include "varnum.h"
|
#include "varnum.h"
|
||||||
|
@@ -172,9 +172,7 @@ uint8_t getHeightAt (int x, int z) {
|
|||||||
|
|
||||||
uint8_t getTerrainAtFromCache (int x, int y, int z, int rx, int rz, ChunkAnchor anchor, ChunkFeature feature, uint8_t height) {
|
uint8_t getTerrainAtFromCache (int x, int y, int z, int rx, int rz, ChunkAnchor anchor, ChunkFeature feature, uint8_t height) {
|
||||||
|
|
||||||
if (y < 64 || y < height) goto skip_feature;
|
if (y >= 64 && y >= height && feature.y != 255) switch (anchor.biome) {
|
||||||
|
|
||||||
switch (anchor.biome) {
|
|
||||||
case W_plains: { // Generate trees in the plains biome
|
case W_plains: { // Generate trees in the plains biome
|
||||||
|
|
||||||
// Don't generate trees underwater
|
// Don't generate trees underwater
|
||||||
@@ -250,7 +248,6 @@ uint8_t getTerrainAtFromCache (int x, int y, int z, int rx, int rz, ChunkAnchor
|
|||||||
default: break;
|
default: break;
|
||||||
}
|
}
|
||||||
|
|
||||||
skip_feature:
|
|
||||||
// Handle surface-level terrain (the very topmost blocks)
|
// Handle surface-level terrain (the very topmost blocks)
|
||||||
if (height >= 63) {
|
if (height >= 63) {
|
||||||
if (y == height) {
|
if (y == height) {
|
||||||
|
Reference in New Issue
Block a user