Merge branch 'master' of https://github.com/Snowiiii/Pumpkin into event-api

Resolve merge conflicts
This commit is contained in:
we sell insurance
2024-09-01 12:31:09 -05:00
61 changed files with 1191 additions and 603 deletions

16
Cargo.lock generated
View File

@@ -1906,6 +1906,7 @@ dependencies = [
"num-bigint",
"num-derive",
"num-traits",
"pumpkin-config",
"pumpkin-core",
"pumpkin-entity",
"pumpkin-inventory",
@@ -1929,6 +1930,16 @@ dependencies = [
"uuid",
]
[[package]]
name = "pumpkin-config"
version = "0.1.0"
dependencies = [
"log",
"pumpkin-core",
"serde",
"toml 0.8.19",
]
[[package]]
name = "pumpkin-core"
version = "0.1.0"
@@ -1936,6 +1947,8 @@ dependencies = [
"colored",
"fastnbt",
"md5",
"num-derive",
"num-traits",
"serde",
"uuid",
]
@@ -1946,6 +1959,7 @@ version = "0.1.0"
dependencies = [
"num-derive",
"num-traits",
"pumpkin-core",
]
[[package]]
@@ -2019,10 +2033,10 @@ dependencies = [
"flate2",
"futures",
"itertools 0.13.0",
"lazy_static",
"log",
"num-derive",
"num-traits",
"pumpkin-core",
"rayon",
"serde",
"serde_json",

View File

@@ -1,6 +1,6 @@
[workspace]
resolver = "2"
members = [ "pumpkin-core", "pumpkin-entity", "pumpkin-inventory", "pumpkin-macros/", "pumpkin-plugin", "pumpkin-protocol/", "pumpkin-registry/", "pumpkin-world", "pumpkin/"]
members = [ "pumpkin-config", "pumpkin-core", "pumpkin-entity", "pumpkin-inventory", "pumpkin-macros/", "pumpkin-plugin", "pumpkin-protocol/", "pumpkin-registry/", "pumpkin-world", "pumpkin/"]
[workspace.package]
version = "0.1.0"

12
pumpkin-config/Cargo.toml Normal file
View File

@@ -0,0 +1,12 @@
[package]
name = "pumpkin-config"
version.workspace = true
edition.workspace = true
[dependencies]
pumpkin-core = { path = "../pumpkin-core" }
serde = "1.0"
toml = "0.8"
log.workspace = true

View File

@@ -1,7 +1,6 @@
use pumpkin_core::ProfileAction;
use serde::{Deserialize, Serialize};
use crate::client::authentication::ProfileAction;
#[derive(Deserialize, Serialize)]
pub struct AuthenticationConfig {
/// Whether to use Mojang authentication.

View File

@@ -0,0 +1,14 @@
use serde::{Deserialize, Serialize};
#[derive(Deserialize, Serialize)]
pub struct CommandsConfig {
/// Are commands from the Console accepted ?
pub use_console: bool,
// TODO: commands...
}
impl Default for CommandsConfig {
fn default() -> Self {
Self { use_console: true }
}
}

View File

@@ -0,0 +1,24 @@
use serde::{Deserialize, Serialize};
#[derive(Deserialize, Serialize)]
// Packet compression
pub struct CompressionConfig {
/// Is compression enabled ?
pub enabled: bool,
/// The compression threshold used when compression is enabled
pub compression_threshold: u32,
/// A value between 0..9
/// 1 = Optimize for the best speed of encoding.
/// 9 = Optimize for the size of data being encoded.
pub compression_level: u32,
}
impl Default for CompressionConfig {
fn default() -> Self {
Self {
enabled: true,
compression_threshold: 256,
compression_level: 4,
}
}
}

176
pumpkin-config/src/lib.rs Normal file
View File

@@ -0,0 +1,176 @@
use log::warn;
use pumpkin_core::{Difficulty, GameMode};
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use std::{
fs,
net::{Ipv4Addr, SocketAddr},
path::Path,
sync::LazyLock,
};
pub mod auth;
pub mod proxy;
pub mod resource_pack;
pub use auth::AuthenticationConfig;
pub use commands::CommandsConfig;
pub use compression::CompressionConfig;
pub use pvp::PVPConfig;
pub use rcon::RCONConfig;
mod commands;
mod compression;
mod pvp;
mod rcon;
use proxy::ProxyConfig;
use resource_pack::ResourcePackConfig;
/// Current Config version of the Base Config
const CURRENT_BASE_VERSION: &str = "1.0.0";
pub static ADVANCED_CONFIG: LazyLock<AdvancedConfiguration> =
LazyLock::new(AdvancedConfiguration::load);
pub static BASIC_CONFIG: LazyLock<BasicConfiguration> = LazyLock::new(BasicConfiguration::load);
/// The idea is that Pumpkin should very customizable.
/// You can Enable or Disable Features depending on your needs.
///
/// This also allows you get some Performance or Resource boosts.
/// Important: The Configuration should match Vanilla by default
#[derive(Deserialize, Serialize, Default)]
pub struct AdvancedConfiguration {
pub proxy: ProxyConfig,
pub authentication: AuthenticationConfig,
pub packet_compression: CompressionConfig,
pub resource_pack: ResourcePackConfig,
pub commands: CommandsConfig,
pub rcon: RCONConfig,
pub pvp: PVPConfig,
}
#[derive(Serialize, Deserialize)]
pub struct BasicConfiguration {
/// A version identifier for the configuration format.
pub config_version: String,
/// The address to bind the server to.
pub server_address: SocketAddr,
/// The seed for world generation.
pub seed: String,
/// The maximum number of players allowed on the server.
pub max_players: u32,
/// The maximum view distance for players.
pub view_distance: u8,
/// The maximum simulated view distance.
pub simulation_distance: u8,
/// The default game difficulty.
pub default_difficulty: Difficulty,
/// Whether the Nether dimension is enabled.
pub allow_nether: bool,
/// Whether the server is in hardcore mode.
pub hardcore: bool,
/// Whether online mode is enabled. Requires valid Minecraft accounts.
pub online_mode: bool,
/// Whether packet encryption is enabled. Required when online mode is enabled.
pub encryption: bool,
/// The server's description displayed on the status screen.
pub motd: String,
/// The default game mode for players.
pub default_gamemode: GameMode,
}
impl Default for BasicConfiguration {
fn default() -> Self {
Self {
config_version: CURRENT_BASE_VERSION.to_string(),
server_address: SocketAddr::new(Ipv4Addr::new(0, 0, 0, 0).into(), 25565),
seed: "".to_string(),
max_players: 100000,
view_distance: 10,
simulation_distance: 10,
default_difficulty: Difficulty::Normal,
allow_nether: true,
hardcore: false,
online_mode: true,
encryption: true,
motd: "A Blazing fast Pumpkin Server!".to_string(),
default_gamemode: GameMode::Survival,
}
}
}
trait LoadConfiguration {
fn load() -> Self
where
Self: Sized + Default + Serialize + DeserializeOwned,
{
let path = Self::get_path();
let config = if path.exists() {
let file_content = fs::read_to_string(path)
.unwrap_or_else(|_| panic!("Couldn't read configuration file at {:?}", path));
toml::from_str(&file_content).unwrap_or_else(|err| {
panic!(
"Couldn't parse config at {:?}. Reason: {}",
path,
err.message()
)
})
} else {
let content = Self::default();
if let Err(err) = fs::write(path, toml::to_string(&content).unwrap()) {
warn!(
"Couldn't write default config to {:?}. Reason: {}",
path, err
);
}
content
};
config.validate();
config
}
fn get_path() -> &'static Path;
fn validate(&self);
}
impl LoadConfiguration for AdvancedConfiguration {
fn get_path() -> &'static Path {
Path::new("features.toml")
}
fn validate(&self) {
self.resource_pack.validate()
}
}
impl LoadConfiguration for BasicConfiguration {
fn get_path() -> &'static Path {
Path::new("configuration.toml")
}
fn validate(&self) {
assert_eq!(
self.config_version, CURRENT_BASE_VERSION,
"Config version does not match used Config version. Please update your config"
);
assert!(self.view_distance >= 2, "View distance must be at least 2");
assert!(
self.view_distance <= 32,
"View distance must be less than 32"
);
if self.online_mode {
assert!(
self.encryption,
"When Online Mode is enabled, Encryption must be enabled"
)
}
}
}

27
pumpkin-config/src/pvp.rs Normal file
View File

@@ -0,0 +1,27 @@
use serde::{Deserialize, Serialize};
#[derive(Deserialize, Serialize)]
pub struct PVPConfig {
/// Is PVP enabled ?
pub enabled: bool,
/// Do we want to have the Red hurt animation & fov bobbing
pub hurt_animation: bool,
/// Should players in creative be protected against PVP
pub protect_creative: bool,
/// Has PVP Knockback?
pub knockback: bool,
/// Should player swing when attacking?
pub swing: bool,
}
impl Default for PVPConfig {
fn default() -> Self {
Self {
enabled: true,
hurt_animation: true,
protect_creative: true,
knockback: true,
swing: true,
}
}
}

View File

@@ -0,0 +1,20 @@
use std::net::{Ipv4Addr, SocketAddr};
use serde::{Deserialize, Serialize};
#[derive(Deserialize, Serialize, Clone)]
pub struct RCONConfig {
pub enabled: bool,
pub address: SocketAddr,
pub password: String,
}
impl Default for RCONConfig {
fn default() -> Self {
Self {
enabled: false,
address: SocketAddr::new(Ipv4Addr::new(0, 0, 0, 0).into(), 25575),
password: "".to_string(),
}
}
}

View File

@@ -9,3 +9,6 @@ fastnbt = { git = "https://github.com/owengage/fastnbt.git" }
uuid.workspace = true
colored = "2"
md5 = "0.7.0"
num-traits = "0.2.19"
num-derive = { version = "0.4.2" }

View File

@@ -0,0 +1,30 @@
use std::str::FromStr;
use num_derive::{FromPrimitive, ToPrimitive};
use serde::{Deserialize, Serialize};
#[derive(Debug, PartialEq, Eq)]
pub struct ParseGameModeError;
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize, FromPrimitive, ToPrimitive)]
pub enum GameMode {
Undefined = -1,
Survival,
Creative,
Adventure,
Spectator,
}
impl FromStr for GameMode {
type Err = ParseGameModeError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"survival" => Ok(Self::Survival),
"creative" => Ok(Self::Creative),
"adventure" => Ok(Self::Adventure),
"spectator" => Ok(Self::Spectator),
_ => Err(ParseGameModeError),
}
}
}

View File

@@ -1,2 +1,23 @@
pub mod gamemode;
pub mod math;
pub mod random;
pub mod text;
pub use gamemode::GameMode;
use serde::{Deserialize, Serialize};
#[derive(PartialEq, Serialize, Deserialize)]
pub enum Difficulty {
Peaceful,
Easy,
Normal,
Hard,
}
#[derive(Deserialize, Serialize, Clone, Debug, PartialEq, Eq)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum ProfileAction {
ForcedNameChange,
UsingBannedSkin,
}

View File

@@ -1,5 +1,4 @@
use pumpkin_protocol::position::WorldPosition;
use pumpkin_world::vector3::Vector3;
use super::{position::WorldPosition, vector3::Vector3};
pub struct BoundingBox {
pub min_x: f64,
@@ -38,6 +37,6 @@ impl BoundingBox {
let d = f64::max(f64::max(self.min_x - pos.x, pos.x - self.max_x), 0.0);
let e = f64::max(f64::max(self.min_y - pos.y, pos.y - self.max_y), 0.0);
let f = f64::max(f64::max(self.min_z - pos.z, pos.z - self.max_z), 0.0);
super::math::squared_magnitude(d, e, f)
super::squared_magnitude(d, e, f)
}
}

View File

@@ -1,3 +1,8 @@
pub mod boundingbox;
pub mod position;
pub mod vector2;
pub mod vector3;
pub fn wrap_degrees(var: f32) -> f32 {
let mut var1 = var % 360.0;
if var1 >= 180.0 {
@@ -14,3 +19,9 @@ pub fn wrap_degrees(var: f32) -> f32 {
pub fn squared_magnitude(a: f64, b: f64, c: f64) -> f64 {
a * a + b * b + c * c
}
/// Converts a world coordinate to the corresponding chunk-section coordinate.
// TODO: This proberbly should place not here
pub fn get_section_cord(coord: i32) -> i32 {
coord >> 4
}

View File

@@ -1,6 +1,8 @@
use pumpkin_world::vector3::Vector3;
use serde::{Deserialize, Serialize};
use super::vector3::Vector3;
/// Aka Block Position
pub struct WorldPosition(pub Vector3<i32>);
impl Serialize for WorldPosition {

View File

@@ -0,0 +1,326 @@
use super::{
gaussian::GaussianGenerator, hash_block_pos, java_string_hash, Random, RandomSplitter,
};
struct LegacyRand {
seed: u64,
internal_next_gaussian: f64,
internal_has_next_gaussian: bool,
}
impl LegacyRand {
fn next_random(&mut self) -> u64 {
let l = self.seed;
let m = l.wrapping_mul(0x5DEECE66D).wrapping_add(11) & 0xFFFFFFFFFFFF;
self.seed = m;
m
}
}
impl GaussianGenerator for LegacyRand {
fn has_next_gaussian(&self) -> bool {
self.internal_has_next_gaussian
}
fn stored_next_gaussian(&self) -> f64 {
self.internal_next_gaussian
}
fn set_has_next_gaussian(&mut self, value: bool) {
self.internal_has_next_gaussian = value;
}
fn set_stored_next_gaussian(&mut self, value: f64) {
self.internal_next_gaussian = value;
}
}
impl Random for LegacyRand {
fn from_seed(seed: u64) -> Self {
LegacyRand {
seed: (seed ^ 0x5DEECE66D) & 0xFFFFFFFFFFFF,
internal_has_next_gaussian: false,
internal_next_gaussian: 0f64,
}
}
fn next(&mut self, bits: u64) -> u64 {
self.next_random() >> (48 - bits)
}
fn split(&mut self) -> Self {
LegacyRand::from_seed(self.next_i64() as u64)
}
fn next_i32(&mut self) -> i32 {
self.next(32) as i32
}
fn next_i64(&mut self) -> i64 {
let i = self.next_i32();
let j = self.next_i32();
((i as i64) << 32).wrapping_add(j as i64)
}
fn next_f32(&mut self) -> f32 {
self.next(24) as f32 * 5.9604645E-8f32
}
fn next_f64(&mut self) -> f64 {
let i = self.next(26);
let j = self.next(27);
let l = (i << 27).wrapping_add(j);
l as f64 * 1.110223E-16f32 as f64
}
fn next_bool(&mut self) -> bool {
self.next(1) != 0
}
fn next_splitter(&mut self) -> impl RandomSplitter {
LegacySplitter::new(self.next_i64() as u64)
}
fn next_gaussian(&mut self) -> f64 {
self.calculate_gaussian()
}
fn next_bounded_i32(&mut self, bound: i32) -> i32 {
if bound & (bound - 1) == 0 {
(bound as u64).wrapping_mul(self.next(31) >> 31) as i32
} else {
loop {
let i = self.next(31) as i32;
let j = i % bound;
if (i - j + (bound - 1)) > 0 {
return j;
}
}
}
}
}
struct LegacySplitter {
seed: u64,
}
impl LegacySplitter {
fn new(seed: u64) -> Self {
LegacySplitter { seed }
}
}
impl RandomSplitter for LegacySplitter {
fn split_u64(&self, seed: u64) -> impl Random {
LegacyRand::from_seed(seed)
}
fn split_string(&self, seed: &str) -> impl Random {
let string_hash = java_string_hash(seed);
LegacyRand::from_seed((string_hash as u64) ^ self.seed)
}
fn split_pos(&self, x: i32, y: i32, z: i32) -> impl Random {
let pos_hash = hash_block_pos(x, y, z);
LegacyRand::from_seed((pos_hash as u64) ^ self.seed)
}
}
#[cfg(test)]
mod test {
use crate::random::{Random, RandomSplitter};
use super::LegacyRand;
#[test]
fn test_next_i32() {
let mut rand = LegacyRand::from_seed(0);
let values = [
-1155484576,
-723955400,
1033096058,
-1690734402,
-1557280266,
1327362106,
-1930858313,
502539523,
-1728529858,
-938301587,
];
for value in values {
assert_eq!(rand.next_i32(), value);
}
}
#[test]
fn test_next_bounded_i32() {
let mut rand = LegacyRand::from_seed(0);
let values = [0, 13, 4, 2, 5, 8, 11, 6, 9, 14];
for value in values {
assert_eq!(rand.next_bounded_i32(0xf), value);
}
}
#[test]
fn test_next_inbetween_i32() {
let mut rand = LegacyRand::from_seed(0);
let values = [1, 5, 2, 12, 12, 6, 12, 10, 4, 3];
for value in values {
assert_eq!(rand.next_inbetween_i32(1, 12), value);
}
}
#[test]
fn test_next_inbetween_exclusive_i32() {
let mut rand = LegacyRand::from_seed(0);
let values = [1, 7, 9, 6, 7, 3, 3, 7, 3, 1];
for value in values {
assert_eq!(rand.next_inbetween_i32_exclusive(1, 12), value);
}
}
#[test]
fn test_next_f64() {
let mut rand = LegacyRand::from_seed(0);
let values = [
0.730967787376657,
0.24053641567148587,
0.6374174253501083,
0.5504370051176339,
0.5975452777972018,
0.3332183994766498,
0.3851891847407185,
0.984841540199809,
0.8791825178724801,
0.9412491794821144,
];
for value in values {
assert_eq!(rand.next_f64(), value);
}
}
#[test]
fn test_next_f32() {
let mut rand = LegacyRand::from_seed(0);
let values: [f32; 10] = [
0.73096776, 0.831441, 0.24053639, 0.6063452, 0.6374174, 0.30905056, 0.550437,
0.1170066, 0.59754527, 0.7815346,
];
for value in values {
assert_eq!(rand.next_f32(), value);
}
}
#[test]
fn test_next_i64() {
let mut rand = LegacyRand::from_seed(0);
let values: [i64; 10] = [
-4962768465676381896,
4437113781045784766,
-6688467811848818630,
-8292973307042192125,
-7423979211207825555,
6146794652083548235,
7105486291024734541,
-279624296851435688,
-2228689144322150137,
-1083761183081836303,
];
for value in values {
assert_eq!(rand.next_i64(), value);
}
}
#[test]
fn test_next_bool() {
let mut rand = LegacyRand::from_seed(0);
let values = [
true, true, false, true, true, false, true, false, true, true,
];
for value in values {
assert_eq!(rand.next_bool(), value);
}
}
#[test]
fn test_next_gaussian() {
let mut rand = LegacyRand::from_seed(0);
let values = [
0.8025330637390305,
-0.9015460884175122,
2.080920790428163,
0.7637707684364894,
0.9845745328825128,
-1.6834122587673428,
-0.027290262907887285,
0.11524570286202315,
-0.39016704137993774,
-0.643388813126449,
];
for value in values {
assert_eq!(rand.next_gaussian(), value);
}
}
#[test]
fn test_next_triangular() {
let mut rand = LegacyRand::from_seed(0);
let values = [
124.52156858525856,
104.34902101162372,
113.2163439160276,
70.01738222704547,
96.89666691951828,
107.30284075808541,
106.16817675813144,
79.11264482608078,
73.96721613927062,
81.72419521080646,
];
for value in values {
assert_eq!(rand.next_triangular(100f64, 50f64), value);
}
}
#[test]
fn test_split() {
let mut original_rand = LegacyRand::from_seed(0);
let mut new_rand = original_rand.split();
{
let splitter = new_rand.next_splitter();
let mut rand1 = splitter.split_string("TEST STRING");
assert_eq!(rand1.next_i32(), -1170413697);
let mut rand2 = splitter.split_u64(10);
assert_eq!(rand2.next_i32(), -1157793070);
let mut rand3 = splitter.split_pos(1, 11, -111);
assert_eq!(rand3.next_i32(), -1213890343);
}
assert_eq!(original_rand.next_i32(), 1033096058);
assert_eq!(new_rand.next_i32(), -888301832);
}
}

View File

@@ -1,6 +1,6 @@
pub mod xoroshiro128;
mod gaussian;
pub mod legacy_rand;
pub mod xoroshiro128;
pub trait Random {
fn from_seed(seed: u64) -> Self;
@@ -35,7 +35,7 @@ pub trait Random {
fn skip(&mut self, count: i32) {
for _ in 0..count {
self.next_i32();
self.next_i64();
}
}
@@ -51,3 +51,82 @@ pub trait RandomSplitter {
fn split_pos(&self, x: i32, y: i32, z: i32) -> impl Random;
}
fn hash_block_pos(x: i32, y: i32, z: i32) -> i64 {
let l = (x.wrapping_mul(3129871) as i64) ^ ((z as i64).wrapping_mul(116129781i64)) ^ (y as i64);
let l = l
.wrapping_mul(l)
.wrapping_mul(42317861i64)
.wrapping_add(l.wrapping_mul(11i64));
l >> 16
}
fn java_string_hash(string: &str) -> u32 {
// All byte values of latin1 align with
// the values of U+0000 - U+00FF making this code
// equivalent to both java hash implementations
let mut result = 0u32;
for char_encoding in string.encode_utf16() {
result = 31u32
.wrapping_mul(result)
.wrapping_add(char_encoding as u32);
}
result
}
#[cfg(test)]
mod tests {
use crate::random::java_string_hash;
use super::hash_block_pos;
#[test]
fn block_position_hash() {
let values: [((i32, i32, i32), i64); 8] = [
((0, 0, 0), 0),
((1, 1, 1), 60311958971344),
((4, 4, 4), 120566413180880),
((25, 25, 25), 111753446486209),
((676, 676, 676), 75210837988243),
((458329, 458329, 458329), -43764888250),
((-387008604, -387008604, -387008604), 8437923733503),
((176771161, 176771161, 176771161), 18421337580760),
];
for ((x, y, z), value) in values {
assert_eq!(hash_block_pos(x, y, z), value);
}
}
#[test]
fn test_java_string_hash() {
let values = [
("", 0),
("1", 49),
("TEST", 2571410),
("TEST1", 79713759),
("TEST0123456789", 506557463),
(
" !\"#$%&'()*+,-./0123456789:\
;<=>?@ABCDEFGHIJKLMNOPQRST\
UVWXYZ[\\]^_`abcdefghijklm\
nopqrstuvwxyz{|}~¡¢£¤¥¦§¨©\
ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄ\
ÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞ\
ßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþ",
(-1992287231i32) as u32,
),
("求同存异", 847053876),
// This might look wierd because hebrew is text is right to left
("אבְּרֵאשִׁ֖ית בָּרָ֣א אֱלֹהִ֑ים אֵ֥ת הַשָּׁמַ֖יִם וְאֵ֥ת הָאָֽרֶץ:", 1372570871),
("संस्कृत-", 1748614838),
];
for (string, value) in values {
assert_eq!(java_string_hash(string), value);
}
}
}

View File

@@ -1,4 +1,4 @@
use super::{gaussian::GaussianGenerator, Random, RandomSplitter};
use super::{gaussian::GaussianGenerator, hash_block_pos, Random, RandomSplitter};
pub struct Xoroshiro {
lo: u64,
@@ -130,12 +130,6 @@ impl Random for Xoroshiro {
fn next_gaussian(&mut self) -> f64 {
self.calculate_gaussian()
}
fn skip(&mut self, count: i32) {
for _ in 0..count {
self.next_random();
}
}
}
pub struct XoroshiroSplitter {
@@ -143,19 +137,9 @@ pub struct XoroshiroSplitter {
hi: u64,
}
fn hash_pos(x: i32, y: i32, z: i32) -> i64 {
let l =
((x.wrapping_mul(3129871)) as i64) ^ ((z as i64).wrapping_mul(116129781i64)) ^ (y as i64);
let l = l
.wrapping_mul(l)
.wrapping_mul(42317861i64)
.wrapping_add(l.wrapping_mul(11i64));
l >> 16
}
impl RandomSplitter for XoroshiroSplitter {
fn split_pos(&self, x: i32, y: i32, z: i32) -> impl Random {
let l = hash_pos(x, y, z) as u64;
let l = hash_block_pos(x, y, z) as u64;
let m = l ^ self.lo;
Xoroshiro::new(m, self.hi)
}
@@ -177,28 +161,10 @@ impl RandomSplitter for XoroshiroSplitter {
mod tests {
use crate::random::{Random, RandomSplitter};
use super::{hash_pos, mix_stafford_13, Xoroshiro};
use super::{mix_stafford_13, Xoroshiro};
// Values checked against results from the equivalent Java source
#[test]
fn block_position_hash() {
let values: [((i32, i32, i32), i64); 8] = [
((0, 0, 0), 0),
((1, 1, 1), 60311958971344),
((4, 4, 4), 120566413180880),
((25, 25, 25), 111753446486209),
((676, 676, 676), 75210837988243),
((458329, 458329, 458329), -43764888250),
((-387008604, -387008604, -387008604), 8437923733503),
((176771161, 176771161, 176771161), 18421337580760),
];
for ((x, y, z), value) in values {
assert_eq!(hash_pos(x, y, z), value);
}
}
#[test]
fn test_mix_stafford_13() {
let values: [(u64, i64); 31] = [

View File

@@ -4,5 +4,7 @@ version.workspace = true
edition.workspace = true
[dependencies]
pumpkin-core = { path = "../pumpkin-core"}
num-traits = "0.2"
num-derive = "0.4"

View File

@@ -1,5 +1,8 @@
use entity_type::EntityType;
use pose::EntityPose;
use pumpkin_core::math::{
get_section_cord, position::WorldPosition, vector2::Vector2, vector3::Vector3,
};
pub mod entity_type;
pub mod pose;
@@ -9,12 +12,10 @@ pub type EntityId = i32;
pub struct Entity {
pub entity_id: EntityId,
pub entity_type: EntityType,
pub x: f64,
pub y: f64,
pub z: f64,
pub lastx: f64,
pub lasty: f64,
pub lastz: f64,
pub pos: Vector3<f64>,
pub block_pos: WorldPosition,
pub chunk_pos: Vector2<i32>,
pub yaw: f32,
pub head_yaw: f32,
pub pitch: f32,
@@ -28,12 +29,9 @@ impl Entity {
Self {
entity_id,
entity_type,
x: 0.0,
y: 0.0,
z: 0.0,
lastx: 0.0,
lasty: 0.0,
lastz: 0.0,
pos: Vector3::new(0.0, 0.0, 0.0),
block_pos: WorldPosition(Vector3::new(0, 0, 0)),
chunk_pos: Vector2::new(0, 0),
yaw: 0.0,
head_yaw: 0.0,
pitch: 0.0,
@@ -41,4 +39,25 @@ impl Entity {
pose: EntityPose::Standing,
}
}
pub fn set_pos(&mut self, x: f64, y: f64, z: f64) {
if self.pos.x != x || self.pos.y != y || self.pos.z != z {
self.pos = Vector3::new(x, y, z);
let i = x.floor() as i32;
let j = y.floor() as i32;
let k = z.floor() as i32;
let block_pos = self.block_pos.0;
if i != block_pos.x || j != block_pos.y || k != block_pos.z {
self.block_pos = WorldPosition(Vector3::new(i, j, k));
if get_section_cord(i) != self.chunk_pos.x
|| get_section_cord(k) != self.chunk_pos.z
{
self.chunk_pos =
Vector2::new(get_section_cord(block_pos.x), get_section_cord(block_pos.z));
}
}
}
}
}

View File

@@ -1,3 +1,4 @@
use bytes::BufMut;
use serde::{
de::{self, DeserializeOwned, SeqAccess, Visitor},
Deserialize, Deserializer, Serialize, Serializer,
@@ -22,19 +23,16 @@ impl Serialize for VarInt {
where
S: Serializer,
{
let mut val = self.0;
let mut buf: Vec<u8> = Vec::new();
for _ in 0..5 {
let mut b: u8 = val as u8 & 0b01111111;
val >>= 7;
if val != 0 {
b |= 0b10000000;
}
buf.push(b);
if val == 0 {
break;
}
let mut value = self.0 as u32;
let mut buf = Vec::new();
while value > 0x7F {
buf.put_u8(value as u8 | 0x80);
value >>= 7;
}
buf.put_u8(value as u8);
serializer.serialize_bytes(&buf)
}
}

View File

@@ -1,7 +1,8 @@
use pumpkin_core::math::position::WorldPosition;
use pumpkin_macros::packet;
use serde::Serialize;
use crate::{position::WorldPosition, VarInt};
use crate::VarInt;
#[derive(Serialize)]
#[packet(0x06)]

View File

@@ -1,7 +1,8 @@
use pumpkin_core::math::position::WorldPosition;
use pumpkin_macros::packet;
use serde::Serialize;
use crate::{position::WorldPosition, VarInt};
use crate::VarInt;
#[derive(Serialize)]
#[packet(0x09)]

View File

@@ -1,7 +1,8 @@
use pumpkin_core::math::position::WorldPosition;
use pumpkin_macros::packet;
use serde::Serialize;
use crate::{position::WorldPosition, VarInt};
use crate::VarInt;
#[derive(Serialize)]
#[packet(0x2B)]

View File

@@ -1,8 +1,7 @@
use pumpkin_core::math::position::WorldPosition;
use pumpkin_macros::packet;
use serde::Serialize;
use crate::position::WorldPosition;
#[derive(Serialize)]
#[packet(0x28)]
pub struct CWorldEvent<'a> {

View File

@@ -8,7 +8,6 @@ pub mod bytebuf;
pub mod client;
pub mod packet_decoder;
pub mod packet_encoder;
pub mod position;
pub mod server;
pub mod slot;
pub mod uuid;

View File

@@ -1,7 +1,8 @@
use num_derive::FromPrimitive;
use pumpkin_core::math::position::WorldPosition;
use pumpkin_macros::packet;
use crate::{position::WorldPosition, VarInt};
use crate::VarInt;
#[derive(serde::Deserialize)]
#[packet(0x24)]

View File

@@ -1,7 +1,8 @@
use pumpkin_core::math::position::WorldPosition;
use pumpkin_macros::packet;
use serde::Deserialize;
use crate::{position::WorldPosition, VarInt};
use crate::VarInt;
#[derive(Deserialize)]
#[packet(0x38)]

View File

@@ -4,7 +4,9 @@ version.workspace = true
edition.workspace = true
[dependencies]
# fastanvil = "0.31"
pumpkin-core = { path = "../pumpkin-core"}
fastnbt = { git = "https://github.com/owengage/fastnbt.git" }
fastsnbt = "0.2"
tokio.workspace = true
@@ -15,7 +17,6 @@ thiserror = "1.0.63"
futures = "0.3.30"
flate2 = "1.0.33"
serde.workspace = true
lazy_static = "1.5.0"
serde_json = "1.0"
static_assertions = "1.1.0"
log.workspace = true

View File

@@ -1,15 +1,13 @@
use std::collections::HashMap;
use std::{collections::HashMap, sync::LazyLock};
use lazy_static::lazy_static;
use serde::Deserialize;
use super::block_id::BlockId;
lazy_static! {
pub static ref BLOCKS: HashMap<String, RegistryBlockType> =
serde_json::from_str(include_str!("../../assets/blocks.json"))
.expect("Could not parse block.json registry.");
}
pub static BLOCKS: LazyLock<HashMap<String, RegistryBlockType>> = LazyLock::new(|| {
serde_json::from_str(include_str!("../../assets/blocks.json"))
.expect("Could not parse block.json registry.")
});
#[derive(Deserialize, Debug, Clone, PartialEq, Eq)]
pub struct RegistryBlockDefinition {

View File

@@ -1,10 +1,10 @@
use crate::vector3::Vector3;
use num_derive::FromPrimitive;
pub mod block_id;
mod block_registry;
pub use block_id::BlockId;
use pumpkin_core::math::vector3::Vector3;
#[derive(FromPrimitive)]
pub enum BlockFace {

View File

@@ -3,13 +3,13 @@ use std::collections::HashMap;
use std::ops::Index;
use fastnbt::LongArray;
use pumpkin_core::math::vector2::Vector2;
use serde::{Deserialize, Serialize};
use crate::{
block::BlockId,
coordinates::{ChunkRelativeBlockCoordinates, Height},
level::{ChunkNotGeneratedError, WorldError},
vector2::Vector2,
WORLD_HEIGHT,
};

View File

@@ -2,9 +2,10 @@ use std::ops::Deref;
use derive_more::derive::{AsMut, AsRef, Display, Into};
use num_traits::{PrimInt, Signed, Unsigned};
use pumpkin_core::math::vector2::Vector2;
use serde::{Deserialize, Serialize};
use crate::{vector2::Vector2, WORLD_LOWEST_Y, WORLD_MAX_Y};
use crate::{WORLD_LOWEST_Y, WORLD_MAX_Y};
#[derive(
Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, AsRef, AsMut, Into, Display,

View File

@@ -0,0 +1,83 @@
use pumpkin_core::math::vector2::Vector2;
#[derive(Debug, PartialEq)]
pub struct Cylindrical {
pub center: Vector2<i32>,
pub view_distance: i32,
}
impl Cylindrical {
pub fn new(center: Vector2<i32>, view_distance: i32) -> Self {
Self {
center,
view_distance,
}
}
#[allow(unused_variables)]
pub fn for_each_changed_chunk(
old_cylindrical: Cylindrical,
new_cylindrical: Cylindrical,
mut newly_included: impl FnMut(Vector2<i32>),
just_removed: impl FnMut(Vector2<i32>),
ignore: bool,
) {
let min_x = old_cylindrical.get_left().min(new_cylindrical.get_left());
let max_x = old_cylindrical.get_right().max(new_cylindrical.get_right());
let min_z = old_cylindrical
.get_bottom()
.min(new_cylindrical.get_bottom());
let max_z = old_cylindrical.get_top().max(new_cylindrical.get_top());
for x in min_x..=max_x {
for z in min_z..=max_z {
// TODO
// let old_is_within = if ignore {
// false
// } else {
// old_cylindrical.is_within_distance(x, z)
// };
// let new_is_within = if ignore {
// true
// } else {
// new_cylindrical.is_within_distance(x, z)
// };
// if old_is_within != new_is_within {
// if new_is_within {
newly_included(Vector2::new(x, z));
// } else {
// dbg!("aa");
// just_removed(Vector2::new(x, z));
// }
// }
}
}
}
fn get_left(&self) -> i32 {
self.center.x - self.view_distance - 1
}
fn get_bottom(&self) -> i32 {
self.center.z - self.view_distance - 1
}
fn get_right(&self) -> i32 {
self.center.x + self.view_distance + 1
}
fn get_top(&self) -> i32 {
self.center.z + self.view_distance + 1
}
#[allow(dead_code)]
fn is_within_distance(&self, x: i32, z: i32) -> bool {
let max_dist_squared = self.view_distance * self.view_distance;
let max_dist = self.view_distance as i64;
let dist_x = (x - self.center.x).abs().max(0) - (1);
let dist_z = (z - self.center.z).abs().max(0) - (1);
let dist_squared = dist_x.pow(2) + (max_dist.min(dist_z as i64) as i32).pow(2);
dist_squared < max_dist_squared
}
}

View File

@@ -1,6 +1,4 @@
use std::collections::HashMap;
use lazy_static::lazy_static;
use std::{collections::HashMap, sync::LazyLock};
pub const ITEM_REGISTRY: &str = "minecraft:item";
@@ -12,10 +10,9 @@ pub struct RegistryElement {
pub entries: HashMap<String, HashMap<String, u32>>,
}
lazy_static! {
pub static ref REGISTRY: HashMap<String, RegistryElement> =
serde_json::from_str(REGISTRY_JSON).expect("Could not parse registry.json registry.");
}
pub static REGISTRY: LazyLock<HashMap<String, RegistryElement>> = LazyLock::new(|| {
serde_json::from_str(REGISTRY_JSON).expect("Could not parse registry.json registry.")
});
pub fn get_protocol_id(category: &str, entry: &str) -> u32 {
*REGISTRY

View File

@@ -1,13 +1,14 @@
use std::collections::HashMap;
use lazy_static::lazy_static;
use crate::global_registry::{self, ITEM_REGISTRY};
use std::{collections::HashMap, sync::LazyLock};
use super::Rarity;
use crate::global_registry::{self, ITEM_REGISTRY};
const ITEMS_JSON: &str = include_str!("../../assets/items.json");
pub static ITEMS: LazyLock<HashMap<String, ItemElement>> = LazyLock::new(|| {
serde_json::from_str(ITEMS_JSON).expect("Could not parse items.json registry.")
});
#[derive(serde::Deserialize, Debug, Clone, PartialEq, Eq)]
pub struct ItemComponents {
// TODO: attribute_modifiers
@@ -27,11 +28,6 @@ pub struct ItemElement {
components: ItemComponents,
}
lazy_static! {
pub static ref ITEMS: HashMap<String, ItemElement> =
serde_json::from_str(ITEMS_JSON).expect("Could not parse items.json registry.");
}
#[allow(dead_code)]
pub fn get_item_element(item_id: &str) -> &ItemComponents {
&ITEMS.get(item_id).expect("Item not found").components

View File

@@ -8,13 +8,13 @@ use std::{
use flate2::{bufread::ZlibDecoder, read::GzDecoder};
use itertools::Itertools;
use pumpkin_core::math::vector2::Vector2;
use rayon::prelude::*;
use thiserror::Error;
use tokio::sync::mpsc;
use crate::{
chunk::ChunkData,
vector2::Vector2,
world_gen::{get_world_gen, Seed, WorldGenerator},
};

View File

@@ -2,13 +2,11 @@ pub mod biome;
pub mod block;
pub mod chunk;
pub mod coordinates;
pub mod cylindrical_chunk_iterator;
pub mod dimension;
pub mod global_registry;
pub mod item;
pub mod level;
pub mod radial_chunk_iterator;
pub mod vector2;
pub mod vector3;
mod world_gen;
pub const WORLD_HEIGHT: usize = 384;

View File

@@ -1,59 +0,0 @@
use crate::vector2::Vector2;
pub struct RadialIterator {
radius: u32,
direction: usize,
current: Vector2<i32>,
step_size: i32,
steps_taken: u32,
steps_in_direction: i32,
}
impl RadialIterator {
pub fn new(radius: u32) -> Self {
RadialIterator {
radius,
direction: 0,
current: Vector2::new(0, 0),
step_size: 1,
steps_taken: 0,
steps_in_direction: 0,
}
}
}
impl Iterator for RadialIterator {
type Item = Vector2<i32>;
fn next(&mut self) -> Option<Self::Item> {
if self.steps_taken >= self.radius * self.radius * 4 {
return None;
}
let result = self.current;
self.steps_in_direction += 1;
// Move in the current direction
match self.direction {
0 => self.current.x += 1, // East
1 => self.current.z += 1, // North
2 => self.current.x -= 1, // West
3 => self.current.z -= 1, // South
_ => {}
}
if self.steps_in_direction >= self.step_size {
self.direction = (self.direction + 1) % 4;
self.steps_in_direction = 0;
// Increase step size after completing two directions
if self.direction == 0 || self.direction == 2 {
self.step_size += 1;
}
}
self.steps_taken += 1;
Some(result)
}
}

View File

@@ -1,10 +1,10 @@
use pumpkin_core::math::vector2::Vector2;
use static_assertions::assert_obj_safe;
use crate::biome::Biome;
use crate::block::BlockId;
use crate::chunk::ChunkData;
use crate::coordinates::{BlockCoordinates, XZBlockCoordinates};
use crate::vector2::Vector2;
use crate::world_gen::Seed;
pub trait GeneratorInit {

View File

@@ -1,7 +1,8 @@
use pumpkin_core::math::vector2::Vector2;
use crate::{
chunk::{ChunkBlocks, ChunkData},
coordinates::{ChunkRelativeBlockCoordinates, ChunkRelativeXZBlockCoordinates},
vector2::Vector2,
WORLD_LOWEST_Y, WORLD_MAX_Y,
};

View File

@@ -10,6 +10,7 @@ default = []
[dependencies]
# pumpkin
pumpkin-core = { path = "../pumpkin-core"}
pumpkin-config = { path = "../pumpkin-config" }
pumpkin-plugin = { path = "../pumpkin-plugin"}
pumpkin-inventory = { path = "../pumpkin-inventory"}
pumpkin-world = { path = "../pumpkin-world"}

View File

@@ -2,13 +2,15 @@ use std::{collections::HashMap, net::IpAddr};
use base64::{engine::general_purpose, Engine};
use num_bigint::BigInt;
use pumpkin_config::{auth::TextureConfig, ADVANCED_CONFIG};
use pumpkin_core::ProfileAction;
use pumpkin_protocol::Property;
use reqwest::{StatusCode, Url};
use serde::{Deserialize, Serialize};
use serde::Deserialize;
use thiserror::Error;
use uuid::Uuid;
use crate::{config::auth_config::TextureConfig, server::Server};
use crate::server::Server;
#[derive(Deserialize, Clone, Debug)]
#[allow(non_snake_case)]
@@ -29,14 +31,6 @@ pub struct Texture {
metadata: Option<HashMap<String, String>>,
}
#[derive(Deserialize, Serialize, Clone, Debug, PartialEq, Eq)]
pub enum ProfileAction {
#[serde(rename = "FORCED_NAME_CHANGE")]
ForcedNameChange,
#[serde(rename = "USING_BANNED_SKIN")]
UsingBannedSkin,
}
#[derive(Deserialize, Clone, Debug)]
pub struct GameProfile {
pub id: Uuid,
@@ -52,13 +46,9 @@ pub async fn authenticate(
ip: &IpAddr,
server: &mut Server,
) -> Result<GameProfile, AuthError> {
assert!(server.advanced_config.authentication.enabled);
assert!(ADVANCED_CONFIG.authentication.enabled);
assert!(server.auth_client.is_some());
let address = if server
.advanced_config
.authentication
.prevent_proxy_connections
{
let address = if ADVANCED_CONFIG.authentication.prevent_proxy_connections {
format!("https://sessionserver.mojang.com/session/minecraft/hasJoined?username={username}&serverId={server_hash}&ip={ip}")
} else {
format!("https://sessionserver.mojang.com/session/minecraft/hasJoined?username={username}&serverId={server_hash}")

View File

@@ -1,4 +1,5 @@
use num_traits::FromPrimitive;
use pumpkin_config::{ADVANCED_CONFIG, BASIC_CONFIG};
use pumpkin_core::text::TextComponent;
use pumpkin_protocol::{
client::{
@@ -81,7 +82,7 @@ impl Client {
properties: vec![],
profile_actions: None,
});
let proxy = &server.advanced_config.proxy;
let proxy = &ADVANCED_CONFIG.proxy;
if proxy.enabled {
if proxy.velocity.enabled {
velocity_login(self)
@@ -96,7 +97,7 @@ impl Client {
"",
public_key_der,
&verify_token,
server.base_config.online_mode, // TODO
BASIC_CONFIG.online_mode, // TODO
);
self.send_packet(&packet);
}
@@ -114,7 +115,7 @@ impl Client {
self.enable_encryption(&shared_secret)
.unwrap_or_else(|e| self.kick(&e.to_string()));
if server.base_config.online_mode {
if BASIC_CONFIG.online_mode {
let hash = Sha1::new()
.chain_update(&shared_secret)
.chain_update(&server.public_key_der)
@@ -132,8 +133,7 @@ impl Client {
Ok(p) => {
// Check if player should join
if let Some(p) = &p.profile_actions {
if !server
.advanced_config
if !ADVANCED_CONFIG
.authentication
.player_profile
.allow_banned_players
@@ -142,8 +142,7 @@ impl Client {
self.kick("Your account can't join");
}
} else {
for allowed in server
.advanced_config
for allowed in ADVANCED_CONFIG
.authentication
.player_profile
.allowed_actions
@@ -162,16 +161,13 @@ impl Client {
}
for ele in self.gameprofile.as_ref().unwrap().properties.clone() {
// todo, use this
unpack_textures(ele, &server.advanced_config.authentication.textures);
unpack_textures(ele, &ADVANCED_CONFIG.authentication.textures);
}
// enable compression
if server.advanced_config.packet_compression.enabled {
let threshold = server
.advanced_config
.packet_compression
.compression_threshold;
let level = server.advanced_config.packet_compression.compression_level;
if ADVANCED_CONFIG.packet_compression.enabled {
let threshold = ADVANCED_CONFIG.packet_compression.compression_threshold;
let level = ADVANCED_CONFIG.packet_compression.compression_level;
self.send_packet(&CSetCompression::new(threshold.into()));
self.set_compression(Some((threshold, level)));
}
@@ -199,7 +195,7 @@ impl Client {
self.connection_state = ConnectionState::Config;
server.send_brand(self);
let resource_config = &server.advanced_config.resource_pack;
let resource_config = &ADVANCED_CONFIG.resource_pack;
if resource_config.enabled {
let prompt_message = if resource_config.prompt_message.is_empty() {
None

View File

@@ -34,6 +34,7 @@ mod client_packet;
mod container;
pub mod player_packet;
#[derive(Clone)]
pub struct PlayerConfig {
pub locale: String, // 16
pub view_distance: i8,

View File

@@ -2,12 +2,17 @@ use std::f32::consts::PI;
use crate::{
commands::{handle_command, CommandSender},
entity::player::{ChatMode, GameMode, Hand, Player},
entity::player::{ChatMode, Hand, Player},
server::Server,
util::math::wrap_degrees,
world::player_chunker,
};
use num_traits::FromPrimitive;
use pumpkin_core::text::TextComponent;
use pumpkin_config::ADVANCED_CONFIG;
use pumpkin_core::{
math::{position::WorldPosition, wrap_degrees},
text::TextComponent,
GameMode,
};
use pumpkin_entity::EntityId;
use pumpkin_inventory::WindowType;
use pumpkin_protocol::server::play::{SCloseContainer, SSetPlayerGround, SUseItem};
@@ -17,7 +22,6 @@ use pumpkin_protocol::{
CHeadRot, CHurtAnimation, CPingResponse, CPlayerChatMessage, CUpdateEntityPos,
CUpdateEntityPosRot, CUpdateEntityRot, CWorldEvent, FilterType,
},
position::WorldPosition,
server::play::{
Action, ActionType, SChatCommand, SChatMessage, SClientInformationPlay, SConfirmTeleport,
SInteract, SPlayPingRequest, SPlayerAction, SPlayerCommand, SPlayerPosition,
@@ -45,9 +49,7 @@ impl Player {
if let Some((id, position)) = self.awaiting_teleport.as_ref() {
if id == &confirm_teleport.teleport_id {
// we should set the pos now to that we requested in the teleport packet, Is may fixed issues when the client sended position packets while being teleported
self.entity.x = position.x;
self.entity.y = position.y;
self.entity.z = position.z;
self.entity.set_pos(position.x, position.y, position.z);
self.awaiting_teleport = None;
} else {
@@ -74,21 +76,24 @@ impl Player {
return;
}
let entity = &mut self.entity;
entity.lastx = entity.x;
entity.lasty = entity.y;
entity.lastz = entity.z;
entity.x = Self::clamp_horizontal(position.x);
entity.y = Self::clamp_vertical(position.feet_y);
entity.z = Self::clamp_horizontal(position.z);
self.lastx = entity.pos.x;
self.lasty = entity.pos.y;
self.lastz = entity.pos.z;
entity.set_pos(
Self::clamp_horizontal(position.x),
Self::clamp_vertical(position.feet_y),
Self::clamp_horizontal(position.z),
);
// TODO: teleport when moving > 8 block
// send new position to all other players
let on_ground = self.on_ground;
let entity_id = entity.entity_id;
let (x, lastx) = (entity.x, entity.lastx);
let (y, lasty) = (entity.y, entity.lasty);
let (z, lastz) = (entity.z, entity.lastz);
let world = self.world.lock().await;
let (x, lastx) = (entity.pos.x, self.lastx);
let (y, lasty) = (entity.pos.y, self.lasty);
let (z, lastz) = (entity.pos.z, self.lastz);
let world = self.world.clone();
let world = world.lock().await;
world.broadcast_packet(
&[&self.client.token],
&CUpdateEntityPos::new(
@@ -99,6 +104,7 @@ impl Player {
on_ground,
),
);
player_chunker::update_position(&world, self).await;
}
pub async fn handle_position_rotation(
@@ -119,25 +125,28 @@ impl Player {
}
let entity = &mut self.entity;
entity.lastx = entity.x;
entity.lasty = entity.y;
entity.lastz = entity.z;
entity.x = Self::clamp_horizontal(position_rotation.x);
entity.y = Self::clamp_vertical(position_rotation.feet_y);
entity.z = Self::clamp_horizontal(position_rotation.z);
self.lastx = entity.pos.x;
self.lasty = entity.pos.y;
self.lastz = entity.pos.z;
entity.set_pos(
Self::clamp_horizontal(position_rotation.x),
Self::clamp_vertical(position_rotation.feet_y),
Self::clamp_horizontal(position_rotation.z),
);
entity.yaw = wrap_degrees(position_rotation.yaw) % 360.0;
entity.pitch = wrap_degrees(position_rotation.pitch).clamp(-90.0, 90.0) % 360.0;
// send new position to all other players
let on_ground = self.on_ground;
let entity_id = entity.entity_id;
let (x, lastx) = (entity.x, entity.lastx);
let (y, lasty) = (entity.y, entity.lasty);
let (z, lastz) = (entity.z, entity.lastz);
let (x, lastx) = (entity.pos.x, self.lastx);
let (y, lasty) = (entity.pos.y, self.lasty);
let (z, lastz) = (entity.pos.z, self.lastz);
let yaw = modulus(entity.yaw * 256.0 / 360.0, 256.0);
let pitch = modulus(entity.pitch * 256.0 / 360.0, 256.0);
// let head_yaw = (entity.head_yaw * 256.0 / 360.0).floor();
let world = self.world.lock().await;
let world = self.world.clone();
let world = world.lock().await;
world.broadcast_packet(
&[&self.client.token],
@@ -155,6 +164,8 @@ impl Player {
&[&self.client.token],
&CHeadRot::new(entity_id.into(), yaw as u8),
);
player_chunker::update_position(&world, self).await;
}
pub async fn handle_rotation(&mut self, _server: &mut Server, rotation: SPlayerRotation) {
@@ -298,7 +309,7 @@ impl Player {
Hand::from_i32(client_information.main_hand.into()),
ChatMode::from_i32(client_information.chat_mode.into()),
) {
self.client.config = Some(PlayerConfig {
self.config = PlayerConfig {
locale: client_information.locale,
view_distance: client_information.view_distance,
chat_mode,
@@ -307,13 +318,13 @@ impl Player {
main_hand,
text_filtering: client_information.text_filtering,
server_listing: client_information.server_listing,
});
};
} else {
self.kick(TextComponent::text("Invalid hand or chat type"))
}
}
pub async fn handle_interact(&mut self, server: &mut Server, interact: SInteract) {
pub async fn handle_interact(&mut self, _: &mut Server, interact: SInteract) {
let sneaking = interact.sneaking;
if self.sneaking != sneaking {
self.set_sneaking(sneaking).await;
@@ -323,7 +334,7 @@ impl Player {
ActionType::Attack => {
let entity_id = interact.entity_id;
// TODO: do validation and stuff
let config = &server.advanced_config.pvp;
let config = &ADVANCED_CONFIG.pvp;
if config.enabled {
let world = self.world.clone();
let world = world.lock().await;

View File

@@ -2,6 +2,7 @@ use std::str::FromStr;
use num_traits::FromPrimitive;
use pumpkin_core::text::TextComponent;
use pumpkin_core::GameMode;
use crate::commands::arg_player::{consume_arg_player, parse_arg_player};
@@ -13,7 +14,6 @@ use crate::commands::tree::{CommandTree, ConsumedArgs, RawArgs};
use crate::commands::tree_builder::{argument, require};
use crate::commands::CommandSender;
use crate::commands::CommandSender::Player;
use crate::entity::player::GameMode;
const NAMES: [&str; 1] = ["gamemode"];

View File

@@ -1,234 +0,0 @@
use std::path::Path;
use auth_config::AuthenticationConfig;
use proxy::ProxyConfig;
use resource_pack::ResourcePackConfig;
use serde::{Deserialize, Serialize};
use crate::{entity::player::GameMode, server::Difficulty};
pub mod auth_config;
pub mod proxy;
pub mod resource_pack;
/// Current Config version of the Base Config
const CURRENT_BASE_VERSION: &str = "1.0.0";
#[derive(Deserialize, Serialize)]
/// The idea is that Pumpkin should very customizable, You can Enable or Disable Features depending on your needs.
/// This also allows you get some Performance or Resource boosts.
/// Important: The Configuration should match Vanilla by default
pub struct AdvancedConfiguration {
pub proxy: ProxyConfig,
pub authentication: AuthenticationConfig,
pub packet_compression: CompressionConfig,
pub resource_pack: ResourcePackConfig,
pub commands: CommandsConfig,
pub rcon: RCONConfig,
pub pvp: PVPConfig,
}
#[derive(Deserialize, Serialize, Clone)]
pub struct RCONConfig {
pub enabled: bool,
pub ip: String,
pub port: u16,
pub password: String,
}
impl Default for RCONConfig {
fn default() -> Self {
Self {
enabled: false,
ip: "0.0.0.0".to_string(),
port: 25575,
password: "".to_string(),
}
}
}
#[derive(Deserialize, Serialize)]
pub struct CommandsConfig {
/// Are commands from the Console accepted ?
pub use_console: bool,
// TODO: commands...
}
impl Default for CommandsConfig {
fn default() -> Self {
Self { use_console: true }
}
}
#[derive(Deserialize, Serialize)]
pub struct PVPConfig {
/// Is PVP enabled ?
pub enabled: bool,
/// Do we want to have the Red hurt animation & fov bobbing
pub hurt_animation: bool,
/// Should players in creative be protected against PVP
pub protect_creative: bool,
/// Has PVP Knockback?
pub knockback: bool,
/// Should player swing when attacking?
pub swing: bool,
}
impl Default for PVPConfig {
fn default() -> Self {
Self {
enabled: true,
hurt_animation: true,
protect_creative: true,
knockback: true,
swing: true,
}
}
}
#[derive(Deserialize, Serialize)]
// Packet compression
pub struct CompressionConfig {
/// Is compression enabled ?
pub enabled: bool,
/// The compression threshold used when compression is enabled
pub compression_threshold: u32,
/// A value between 0..9
/// 1 = Optimize for the best speed of encoding.
/// 9 = Optimize for the size of data being encoded.
pub compression_level: u32,
}
impl Default for CompressionConfig {
fn default() -> Self {
Self {
enabled: true,
compression_threshold: 256,
compression_level: 4,
}
}
}
/// Important: The Configuration should match Vanilla by default
impl Default for AdvancedConfiguration {
fn default() -> Self {
Self {
proxy: ProxyConfig::default(),
authentication: AuthenticationConfig::default(),
commands: CommandsConfig::default(),
packet_compression: CompressionConfig::default(),
resource_pack: ResourcePackConfig::default(),
rcon: RCONConfig::default(),
pvp: PVPConfig::default(),
}
}
}
#[derive(Serialize, Deserialize)]
pub struct BasicConfiguration {
/// A version identifier for the configuration format.
pub config_version: String,
/// The address to bind the server to.
pub server_address: String,
/// The port to listen on.
pub server_port: u16,
/// The seed for world generation.
pub seed: String,
/// The maximum number of players allowed on the server.
pub max_players: u32,
/// The maximum view distance for players.
pub view_distance: u8,
/// The maximum simulated view distance.
pub simulation_distance: u8,
/// The default game difficulty.
pub default_difficulty: Difficulty,
/// Whether the Nether dimension is enabled.
pub allow_nether: bool,
/// Whether the server is in hardcore mode.
pub hardcore: bool,
/// Whether online mode is enabled. Requires valid Minecraft accounts.
pub online_mode: bool,
/// Whether packet encryption is enabled. Required when online mode is enabled.
pub encryption: bool,
/// The server's description displayed on the status screen.
pub motd: String,
/// The default game mode for players.
pub default_gamemode: GameMode,
}
impl Default for BasicConfiguration {
fn default() -> Self {
Self {
config_version: CURRENT_BASE_VERSION.to_string(),
server_address: "0.0.0.0".to_string(),
server_port: 25565,
seed: "".to_string(),
max_players: 100000,
view_distance: 10,
simulation_distance: 10,
default_difficulty: Difficulty::Normal,
allow_nether: true,
hardcore: false,
online_mode: true,
encryption: true,
motd: "A Blazing fast Pumpkin Server!".to_string(),
default_gamemode: GameMode::Survival,
}
}
}
impl AdvancedConfiguration {
pub fn load<P: AsRef<Path>>(path: P) -> AdvancedConfiguration {
if path.as_ref().exists() {
let toml = std::fs::read_to_string(path).expect("Couldn't read configuration");
let config: AdvancedConfiguration =
toml::from_str(toml.as_str()).expect("Couldn't parse features.toml, Probably old config, Replacing with a new one or just delete it");
config.validate();
config
} else {
let config = AdvancedConfiguration::default();
let toml = toml::to_string(&config).expect("Couldn't create toml!");
std::fs::write(path, toml).expect("Couldn't save configuration");
config.validate();
config
}
}
pub fn validate(&self) {
self.resource_pack.validate()
}
}
impl BasicConfiguration {
pub fn load<P: AsRef<Path>>(path: P) -> BasicConfiguration {
if path.as_ref().exists() {
let toml = std::fs::read_to_string(path).expect("Couldn't read configuration");
let config: BasicConfiguration = toml::from_str(toml.as_str()).expect("Couldn't parse configuration.toml, Probably old config, Replacing with a new one or just delete it");
config.validate();
config
} else {
let config = BasicConfiguration::default();
let toml = toml::to_string(&config).expect("Couldn't create toml!");
std::fs::write(path, toml).expect("Couldn't save configuration");
config.validate();
config
}
}
pub fn validate(&self) {
assert_eq!(
self.config_version, CURRENT_BASE_VERSION,
"Config version does not match used Config version. Please update your config"
);
assert!(self.view_distance >= 2, "View distance must be at least 2");
assert!(
self.view_distance <= 32,
"View distance must be less than 32"
);
if self.online_mode {
assert!(
self.encryption,
"When Online Mode is enabled, Encryption must be enabled"
)
}
}
}

View File

@@ -1,8 +1,12 @@
use std::{str::FromStr, sync::Arc};
use std::sync::Arc;
use num_derive::{FromPrimitive, ToPrimitive};
use num_derive::FromPrimitive;
use num_traits::ToPrimitive;
use pumpkin_core::text::TextComponent;
use pumpkin_core::{
math::{boundingbox::BoundingBox, position::WorldPosition, vector3::Vector3},
text::TextComponent,
GameMode,
};
use pumpkin_entity::{entity_type::EntityType, pose::EntityPose, Entity, EntityId};
use pumpkin_inventory::player::PlayerInventory;
use pumpkin_protocol::{
@@ -11,7 +15,6 @@ use pumpkin_protocol::{
CGameEvent, CPlayDisconnect, CPlayerAbilities, CPlayerInfoUpdate, CSetEntityMetadata,
CSyncPlayerPosition, CSystemChatMessage, Metadata, PlayerAction,
},
position::WorldPosition,
server::play::{
SChatCommand, SChatMessage, SClientInformationPlay, SConfirmTeleport, SInteract,
SPlayPingRequest, SPlayerAction, SPlayerCommand, SPlayerPosition, SPlayerPositionRotation,
@@ -20,13 +23,10 @@ use pumpkin_protocol::{
},
ConnectionState, RawPacket, ServerPacket, VarInt,
};
use pumpkin_world::vector3::Vector3;
use serde::{Deserialize, Serialize};
use crate::{
client::{authentication::GameProfile, Client},
client::{authentication::GameProfile, Client, PlayerConfig},
server::Server,
util::boundingbox::BoundingBox,
world::World,
};
@@ -56,6 +56,7 @@ pub struct Player {
pub gameprofile: GameProfile,
pub client: Client,
pub entity: Entity,
pub config: PlayerConfig,
// TODO: Put this into entity
pub world: Arc<tokio::sync::Mutex<World>>,
/// Current gamemode
@@ -68,6 +69,9 @@ pub struct Player {
/// send `send_abilties_update` when changed
pub abilities: PlayerAbilities,
pub lastx: f64,
pub lasty: f64,
pub lastz: f64,
// Client side value, Should be not trusted
pub on_ground: bool,
@@ -83,6 +87,8 @@ pub struct Player {
pub teleport_id_count: i32,
// Current awaiting teleport id and location, None if did not teleport
pub awaiting_teleport: Option<(VarInt, Vector3<f64>)>,
pub watched_section: Vector3<i32>,
}
impl Player {
@@ -104,8 +110,9 @@ impl Player {
}
}
};
let config = client.config.clone().unwrap_or_default();
Self {
config,
gameprofile,
client,
entity: Entity::new(entity_id, EntityType::Player, 1.62),
@@ -124,6 +131,10 @@ impl Player {
teleport_id_count: 0,
abilities: PlayerAbilities::default(),
gamemode,
watched_section: Vector3::new(0, 0, 0),
lastx: 0.0,
lasty: 0.0,
lastz: 0.0,
}
}
@@ -186,11 +197,11 @@ impl Player {
assert!(self.sneaking != sneaking);
self.sneaking = sneaking;
self.set_flag(Self::SNEAKING_FLAG_INDEX, sneaking).await;
if sneaking {
self.set_pose(EntityPose::Crouching).await;
} else {
self.set_pose(EntityPose::Standing).await;
}
// if sneaking {
// self.set_pose(EntityPose::Crouching).await;
// } else {
// self.set_pose(EntityPose::Standing).await;
// }
}
pub async fn set_sprinting(&mut self, sprinting: bool) {
@@ -243,12 +254,10 @@ impl Player {
self.teleport_id_count = 0;
}
let entity = &mut self.entity;
entity.x = x;
entity.y = y;
entity.z = z;
entity.lastx = x;
entity.lasty = y;
entity.lastz = z;
entity.set_pos(x, y, z);
self.lastx = x;
self.lasty = y;
self.lastz = z;
entity.yaw = yaw;
entity.pitch = pitch;
self.awaiting_teleport = Some((self.teleport_id_count.into(), Vector3::new(x, y, z)));
@@ -275,9 +284,9 @@ impl Player {
let d = self.block_interaction_range() + additional_range;
let box_pos = BoundingBox::from_block(pos);
box_pos.squared_magnitude(Vector3 {
x: self.entity.x,
y: self.entity.y + self.entity.standing_eye_height as f64,
z: self.entity.z,
x: self.entity.pos.x,
y: self.entity.pos.y + self.entity.standing_eye_height as f64,
z: self.entity.pos.z,
}) < d * d
}
@@ -442,41 +451,15 @@ impl Player {
}
}
#[derive(FromPrimitive)]
#[derive(FromPrimitive, Clone)]
pub enum Hand {
Main,
Off,
}
#[derive(FromPrimitive)]
#[derive(FromPrimitive, Clone)]
pub enum ChatMode {
Enabled,
CommandsOnly,
Hidden,
}
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize, FromPrimitive, ToPrimitive)]
pub enum GameMode {
Undefined = -1,
Survival,
Creative,
Adventure,
Spectator,
}
#[derive(Debug, PartialEq, Eq)]
pub struct ParseGameModeError;
impl FromStr for GameMode {
type Err = ParseGameModeError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"survival" => Ok(Self::Survival),
"creative" => Ok(Self::Creative),
"adventure" => Ok(Self::Adventure),
"spectator" => Ok(Self::Spectator),
_ => Err(ParseGameModeError),
}
}
}

View File

@@ -6,23 +6,18 @@ compile_error!("Compiling for WASI targets is not supported!");
use mio::net::TcpListener;
use mio::{Events, Interest, Poll, Token};
use std::io::{self};
use client::Client;
use commands::handle_command;
use config::AdvancedConfiguration;
use std::collections::HashMap;
use std::io::{self};
use client::interrupted;
use config::BasicConfiguration;
use client::{interrupted, Client};
use commands::handle_command;
use server::Server;
// Setup some tokens to allow us to identify which event is for which socket.
pub mod client;
pub mod commands;
pub mod config;
pub mod entity;
pub mod proxy;
pub mod rcon;
@@ -34,6 +29,7 @@ fn main() -> io::Result<()> {
use std::sync::{Arc, Mutex};
use entity::player::Player;
use pumpkin_config::{ADVANCED_CONFIG, BASIC_CONFIG};
use pumpkin_core::text::{color::NamedColor, TextComponent};
use rcon::RCONServer;
@@ -64,9 +60,6 @@ fn main() -> io::Result<()> {
use std::time::Instant;
let time = Instant::now();
let basic_config = BasicConfiguration::load("configuration.toml");
let advanced_configuration = AdvancedConfiguration::load("features.toml");
// Create a poll instance.
let mut poll = Poll::new()?;
@@ -74,14 +67,7 @@ fn main() -> io::Result<()> {
let mut events = Events::with_capacity(128);
// Setup the TCP server socket.
let addr = format!(
"{}:{}",
basic_config.server_address, basic_config.server_port
)
.parse()
.unwrap();
let addr = BASIC_CONFIG.server_address;
let mut listener = TcpListener::bind(addr)?;
// Register the server with poll we can receive events for it.
@@ -91,16 +77,13 @@ fn main() -> io::Result<()> {
// Unique token for each incoming connection.
let mut unique_token = Token(SERVER.0 + 1);
let use_console = advanced_configuration.commands.use_console;
let rcon = advanced_configuration.rcon.clone();
let use_console = ADVANCED_CONFIG.commands.use_console;
let rcon = ADVANCED_CONFIG.rcon.clone();
let mut clients: HashMap<Token, Client> = HashMap::new();
let mut players: HashMap<Arc<Token>, Arc<Mutex<Player>>> = HashMap::new();
let server = Arc::new(tokio::sync::Mutex::new(Server::new((
basic_config,
advanced_configuration,
))));
let server = Arc::new(tokio::sync::Mutex::new(Server::new()));
log::info!("Started Server took {}ms", time.elapsed().as_millis());
log::info!("You now can connect to the server, Listening on {}", addr);
@@ -214,7 +197,7 @@ fn main() -> io::Result<()> {
server.add_player(token.clone(), client).await;
players.insert(token, player.clone());
let mut world = world.lock().await;
world.spawn_player(&server.base_config, player).await;
world.spawn_player(&BASIC_CONFIG, player).await;
}
}
}

View File

@@ -2,12 +2,13 @@ use std::net::SocketAddr;
use bytes::{BufMut, BytesMut};
use hmac::{Hmac, Mac};
use pumpkin_config::proxy::VelocityConfig;
use pumpkin_protocol::{
bytebuf::ByteBuffer, client::login::CLoginPluginRequest, server::login::SLoginPluginResponse,
};
use sha2::Sha256;
use crate::{client::Client, config::proxy::VelocityConfig};
use crate::client::Client;
type HmacSha256 = Hmac<Sha256>;

View File

@@ -9,9 +9,10 @@ use mio::{
Events, Interest, Poll, Token,
};
use packet::{Packet, PacketError, PacketType};
use pumpkin_config::RCONConfig;
use thiserror::Error;
use crate::{commands::handle_command, config::RCONConfig, server::Server};
use crate::{commands::handle_command, server::Server};
mod packet;
@@ -35,11 +36,8 @@ impl RCONServer {
server: Arc<tokio::sync::Mutex<Server>>,
) -> Result<Self, io::Error> {
assert!(config.enabled, "RCON is not enabled");
let addr = format!("{}:{}", config.ip, config.port)
.parse()
.expect("Failed to parse RCON address");
let mut poll = Poll::new().unwrap();
let mut listener = TcpListener::bind(addr).unwrap();
let mut listener = TcpListener::bind(config.address).unwrap();
poll.registry()
.register(&mut listener, SERVER, Interest::READABLE)

View File

@@ -11,6 +11,8 @@ use std::{
use base64::{engine::general_purpose, Engine};
use image::GenericImageView;
use mio::Token;
use pumpkin_config::{BasicConfiguration, BASIC_CONFIG};
use pumpkin_core::GameMode;
use pumpkin_entity::EntityId;
use pumpkin_plugin::PluginLoader;
use pumpkin_protocol::{
@@ -21,14 +23,8 @@ use pumpkin_world::dimension::Dimension;
use pumpkin_registry::Registry;
use rsa::{traits::PublicKeyParts, RsaPrivateKey, RsaPublicKey};
use serde::{Deserialize, Serialize};
use crate::{
client::Client,
config::{AdvancedConfiguration, BasicConfiguration},
entity::player::{GameMode, Player},
world::World,
};
use crate::{client::Client, entity::player::Player, world::World};
pub const CURRENT_MC_VERSION: &str = "1.21.1";
@@ -52,16 +48,15 @@ pub struct Server {
pub cached_registry: Vec<Registry>,
entity_id: AtomicI32,
pub base_config: BasicConfiguration,
pub advanced_config: AdvancedConfiguration,
/// Used for Authentication, None is Online mode is disabled
pub auth_client: Option<reqwest::Client>,
}
impl Server {
pub fn new(config: (BasicConfiguration, AdvancedConfiguration)) -> Self {
let status_response = Self::build_response(&config.0);
#[allow(clippy::new_without_default)]
pub fn new() -> Self {
let status_response = Self::build_response(&BASIC_CONFIG);
let status_response_json = serde_json::to_string(&status_response)
.expect("Failed to parse Status response into JSON");
let cached_server_brand = Self::build_brand();
@@ -75,7 +70,7 @@ impl Server {
&private_key.e().to_bytes_be(),
)
.into_boxed_slice();
let auth_client = if config.0.online_mode {
let auth_client = if BASIC_CONFIG.online_mode {
Some(
reqwest::Client::builder()
.timeout(Duration::from_millis(5000))
@@ -106,9 +101,7 @@ impl Server {
status_response,
status_response_json,
public_key_der,
base_config: config.0,
auth_client,
advanced_config: config.1,
}
}
@@ -118,7 +111,7 @@ impl Server {
client: Client,
) -> (Arc<Mutex<Player>>, Arc<tokio::sync::Mutex<World>>) {
let entity_id = self.new_entity_id();
let gamemode = match self.base_config.default_gamemode {
let gamemode = match BASIC_CONFIG.default_gamemode {
GameMode::Undefined => GameMode::Survival,
game_mode => game_mode,
};
@@ -216,11 +209,3 @@ impl Server {
(pub_key, priv_key)
}
}
#[derive(PartialEq, Serialize, Deserialize)]
pub enum Difficulty {
Peaceful,
Easy,
Normal,
Hard,
}

View File

@@ -1,2 +1 @@
pub mod boundingbox;
pub mod math;

View File

@@ -4,22 +4,25 @@ use std::{
sync::{Arc, Mutex, MutexGuard},
};
pub mod player_chunker;
use mio::Token;
use num_traits::ToPrimitive;
use pumpkin_config::BasicConfiguration;
use pumpkin_core::math::vector2::Vector2;
use pumpkin_entity::{entity_type::EntityType, EntityId};
use pumpkin_protocol::{
client::play::{
CCenterChunk, CChunkData, CGameEvent, CLogin, CPlayerAbilities, CPlayerInfoUpdate,
CRemoveEntities, CRemovePlayerInfo, CSetEntityMetadata, CSpawnEntity, Metadata,
PlayerAction,
CChunkData, CGameEvent, CLogin, CPlayerAbilities, CPlayerInfoUpdate, CRemoveEntities,
CRemovePlayerInfo, CSetEntityMetadata, CSpawnEntity, Metadata, PlayerAction,
},
uuid::UUID,
ClientPacket, VarInt,
};
use pumpkin_world::{level::Level, radial_chunk_iterator::RadialIterator};
use pumpkin_world::level::Level;
use tokio::sync::mpsc;
use crate::{config::BasicConfiguration, entity::player::Player};
use crate::{client::Client, entity::player::Player};
pub struct World {
pub level: Arc<Mutex<Level>>,
@@ -89,7 +92,7 @@ impl World {
// TODO: this is for debug purpose, remove later
player
.client
.send_packet(&CPlayerAbilities::new(0x02, 0.1, 0.1));
.send_packet(&CPlayerAbilities::new(0x02, 0.4, 0.1));
// teleport
let x = 10.0;
@@ -190,12 +193,12 @@ impl World {
existing_player.entity_id().into(),
UUID(gameprofile.id),
(EntityType::Player as i32).into(),
entity.x,
entity.y,
entity.z,
entity.pos.x,
entity.pos.y,
entity.pos.z,
entity.yaw,
entity.pitch,
entity.pitch,
entity.head_yaw,
0.into(),
0.0,
0.0,
@@ -213,26 +216,28 @@ impl World {
self.broadcast_packet(&[&player.client.token], &packet)
}
self.spawn_test_chunk(player, base_config.view_distance as u32)
.await;
// Spawn in inital chunks
player_chunker::player_join(self, player).await;
}
async fn spawn_test_chunk(&self, player: &mut Player, distance: u32) {
async fn spawn_world_chunks(
&self,
client: &mut Client,
chunks: Vec<Vector2<i32>>,
distance: i32,
) {
let inst = std::time::Instant::now();
let (sender, mut chunk_receiver) = mpsc::channel(distance as usize);
let chunks: Vec<_> = RadialIterator::new(distance).collect();
let level = self.level.clone();
tokio::spawn(async move {
level.lock().unwrap().fetch_chunks(&chunks, sender);
});
player.client.send_packet(&CCenterChunk {
chunk_x: 0.into(),
chunk_z: 0.into(),
});
while let Some(chunk_data) = chunk_receiver.recv().await {
if client.closed {
return;
}
// dbg!(chunk_pos);
let chunk_data = match chunk_data {
Ok(d) => d,
@@ -251,10 +256,9 @@ impl World {
len / (1024 * 1024)
);
}
player.client.send_packet(&CChunkData(&chunk_data));
client.send_packet(&CChunkData(&chunk_data));
}
let t = inst.elapsed();
dbg!("DONE", t);
dbg!("DONE CHUNKS", inst.elapsed());
}
/// TODO: This definitly should be in world
@@ -282,12 +286,10 @@ impl World {
// todo: put this into the entitiy struct
let id = player.entity_id();
let uuid = player.gameprofile.id;
dbg!("1");
self.broadcast_packet(
&[&player.client.token],
&CRemovePlayerInfo::new(1.into(), &[UUID(uuid)]),
);
dbg!("2");
self.broadcast_packet(&[&player.client.token], &CRemoveEntities::new(&[id.into()]))
}
}

View File

@@ -0,0 +1,102 @@
use pumpkin_config::BASIC_CONFIG;
use pumpkin_core::math::{
get_section_cord, position::WorldPosition, vector2::Vector2, vector3::Vector3,
};
use pumpkin_protocol::client::play::{CCenterChunk, CUnloadChunk};
use pumpkin_world::cylindrical_chunk_iterator::Cylindrical;
use crate::entity::player::Player;
use super::World;
fn get_view_distance(player: &Player) -> i8 {
player
.config
.view_distance
.clamp(2, BASIC_CONFIG.view_distance as i8)
}
pub async fn player_join(world: &World, player: &mut Player) {
let new_watched = chunk_section_from_pos(&player.entity.block_pos);
player.watched_section = new_watched;
let chunk_pos = player.entity.chunk_pos;
player.client.send_packet(&CCenterChunk {
chunk_x: chunk_pos.x.into(),
chunk_z: chunk_pos.z.into(),
});
let view_distance = get_view_distance(player) as i32;
dbg!(view_distance);
let old_cylindrical = Cylindrical::new(
Vector2::new(player.watched_section.x, player.watched_section.z),
view_distance,
);
let new_cylindrical = Cylindrical::new(Vector2::new(chunk_pos.x, chunk_pos.z), view_distance);
let mut loading_chunks = Vec::new();
Cylindrical::for_each_changed_chunk(
old_cylindrical,
new_cylindrical,
|chunk_pos| {
loading_chunks.push(chunk_pos);
},
|chunk_pos| {
player
.client
.send_packet(&CUnloadChunk::new(chunk_pos.x, chunk_pos.z));
},
true,
);
if !loading_chunks.is_empty() {
world
.spawn_world_chunks(&mut player.client, loading_chunks, view_distance)
.await;
}
}
pub async fn update_position(world: &World, player: &mut Player) {
let current_watched = player.watched_section;
let new_watched = chunk_section_from_pos(&player.entity.block_pos);
if current_watched != new_watched {
let chunk_pos = player.entity.chunk_pos;
player.client.send_packet(&CCenterChunk {
chunk_x: chunk_pos.x.into(),
chunk_z: chunk_pos.z.into(),
});
let view_distance = get_view_distance(player) as i32;
let old_cylindrical = Cylindrical::new(
Vector2::new(player.watched_section.x, player.watched_section.z),
view_distance,
);
let new_cylindrical =
Cylindrical::new(Vector2::new(chunk_pos.x, chunk_pos.z), view_distance);
player.watched_section = new_watched;
let mut loading_chunks = Vec::new();
Cylindrical::for_each_changed_chunk(
old_cylindrical,
new_cylindrical,
|chunk_pos| {
loading_chunks.push(chunk_pos);
},
|chunk_pos| {
player
.client
.send_packet(&CUnloadChunk::new(chunk_pos.x, chunk_pos.z));
},
false,
);
if !loading_chunks.is_empty() {
world
.spawn_world_chunks(&mut player.client, loading_chunks, view_distance)
.await;
}
}
}
fn chunk_section_from_pos(block_pos: &WorldPosition) -> Vector3<i32> {
let block_pos = block_pos.0;
Vector3::new(
get_section_cord(block_pos.x),
get_section_cord(block_pos.y),
get_section_cord(block_pos.z),
)
}

9
rust-toolchain.toml Normal file
View File

@@ -0,0 +1,9 @@
[toolchain]
# Anything in the latest stable version of rust is fine to use.
channel = "stable"
targets = [
"x86_64-apple-darwin",
"x86_64-pc-windows-gnu",
"x86_64-pc-windows-msvc",
"x86_64-unknown-linux-gnu",
]