mirror of
https://github.com/Pumpkin-MC/Pumpkin.git
synced 2026-08-30 20:14:23 +00:00
Consistent registry values and player changes (#324)
* Made registry values consistent * Port to own nbt crate --------- Co-authored-by: Snowiiii <lilalexmed@proton.me>
This commit is contained in:
@@ -8,6 +8,8 @@ pumpkin-protocol = { path = "../pumpkin-protocol" }
|
||||
pumpkin-nbt = { path = "../pumpkin-nbt" }
|
||||
pumpkin-core = { path = "../pumpkin-core" }
|
||||
|
||||
indexmap = { version = "2.6.0", features = ["serde"] }
|
||||
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::{collections::HashMap, sync::LazyLock};
|
||||
use std::sync::LazyLock;
|
||||
|
||||
use banner_pattern::BannerPattern;
|
||||
use biome::Biome;
|
||||
@@ -6,6 +6,7 @@ use chat_type::ChatType;
|
||||
use damage_type::DamageType;
|
||||
use dimension::Dimension;
|
||||
use enchantment::Enchantment;
|
||||
use indexmap::IndexMap;
|
||||
use instrument::Instrument;
|
||||
use jukebox_song::JukeboxSong;
|
||||
use paint::Painting;
|
||||
@@ -47,29 +48,49 @@ pub struct Registry {
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct SyncedRegistry {
|
||||
#[serde(rename = "minecraft:worldgen/biome")]
|
||||
biome: HashMap<String, Biome>,
|
||||
biome: IndexMap<String, Biome>,
|
||||
#[serde(rename = "minecraft:chat_type")]
|
||||
chat_type: HashMap<String, ChatType>,
|
||||
chat_type: IndexMap<String, ChatType>,
|
||||
#[serde(rename = "minecraft:trim_pattern")]
|
||||
trim_pattern: HashMap<String, TrimPattern>,
|
||||
trim_pattern: IndexMap<String, TrimPattern>,
|
||||
#[serde(rename = "minecraft:trim_material")]
|
||||
trim_material: HashMap<String, TrimMaterial>,
|
||||
trim_material: IndexMap<String, TrimMaterial>,
|
||||
#[serde(rename = "minecraft:wolf_variant")]
|
||||
wolf_variant: HashMap<String, WolfVariant>,
|
||||
wolf_variant: IndexMap<String, WolfVariant>,
|
||||
#[serde(rename = "minecraft:painting_variant")]
|
||||
painting_variant: HashMap<String, Painting>,
|
||||
painting_variant: IndexMap<String, Painting>,
|
||||
#[serde(rename = "minecraft:dimension_type")]
|
||||
dimension_type: HashMap<String, Dimension>,
|
||||
dimension_type: IndexMap<String, Dimension>,
|
||||
#[serde(rename = "minecraft:damage_type")]
|
||||
damage_type: HashMap<String, DamageType>,
|
||||
damage_type: IndexMap<String, DamageType>,
|
||||
#[serde(rename = "minecraft:banner_pattern")]
|
||||
banner_pattern: HashMap<String, BannerPattern>,
|
||||
banner_pattern: IndexMap<String, BannerPattern>,
|
||||
#[serde(rename = "minecraft:enchantment")]
|
||||
enchantment: HashMap<String, Enchantment>,
|
||||
enchantment: IndexMap<String, Enchantment>,
|
||||
#[serde(rename = "minecraft:jukebox_song")]
|
||||
jukebox_song: HashMap<String, JukeboxSong>,
|
||||
jukebox_song: IndexMap<String, JukeboxSong>,
|
||||
#[serde(rename = "minecraft:instrument")]
|
||||
instrument: HashMap<String, Instrument>,
|
||||
instrument: IndexMap<String, Instrument>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[repr(u8)]
|
||||
pub enum DimensionType {
|
||||
Overworld,
|
||||
OverworldCaves,
|
||||
TheEnd,
|
||||
TheNether,
|
||||
}
|
||||
|
||||
impl DimensionType {
|
||||
pub fn name(&self) -> &str {
|
||||
match self {
|
||||
Self::Overworld => "minecraft:overworld",
|
||||
Self::OverworldCaves => "minecraft:overworld_caves",
|
||||
Self::TheEnd => "minecraft:the_end",
|
||||
Self::TheNether => "minecraft:the_nether",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Registry {
|
||||
|
||||
@@ -361,7 +361,7 @@ impl Player {
|
||||
Hand::Off => Animation::SwingOffhand,
|
||||
};
|
||||
let id = self.entity_id();
|
||||
let world = &self.living_entity.entity.world;
|
||||
let world = self.world();
|
||||
world
|
||||
.broadcast_packet_except(
|
||||
&[self.gameprofile.id],
|
||||
@@ -449,7 +449,7 @@ impl Player {
|
||||
if self.living_entity.health.load() > 0.0 {
|
||||
return;
|
||||
}
|
||||
self.respawn(false).await;
|
||||
self.world().respawn_player(self, false).await;
|
||||
// TODO: hardcore set spectator
|
||||
}
|
||||
1 => {
|
||||
|
||||
@@ -64,13 +64,13 @@ impl LivingEntity {
|
||||
.await;
|
||||
}
|
||||
|
||||
pub async fn damage(&self, amount: f32) {
|
||||
// TODO add damage_type enum
|
||||
pub async fn damage(&self, amount: f32, damage_type: u8) {
|
||||
self.entity
|
||||
.world
|
||||
.broadcast_packet_all(&CDamageEvent::new(
|
||||
self.entity.entity_id.into(),
|
||||
// TODO add damage_type id
|
||||
0.into(),
|
||||
damage_type.into(),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
@@ -130,7 +130,7 @@ impl LivingEntity {
|
||||
return;
|
||||
}
|
||||
|
||||
self.damage(damage).await;
|
||||
self.damage(damage, 10).await; // Fall
|
||||
} else if y_diff < 0.0 {
|
||||
self.fall_distance.store(0.0);
|
||||
} else {
|
||||
|
||||
@@ -24,14 +24,13 @@ use pumpkin_core::{
|
||||
use pumpkin_entity::{entity_type::EntityType, EntityId};
|
||||
use pumpkin_inventory::player::PlayerInventory;
|
||||
use pumpkin_macros::sound;
|
||||
use pumpkin_protocol::client::play::{CSetEntityMetadata, Metadata};
|
||||
use pumpkin_protocol::server::play::{SClickContainer, SKeepAlive};
|
||||
use pumpkin_protocol::{
|
||||
bytebuf::packet_id::Packet,
|
||||
client::play::{
|
||||
CCombatDeath, CEntityStatus, CGameEvent, CHurtAnimation, CKeepAlive, CPlayDisconnect,
|
||||
CPlayerAbilities, CPlayerInfoUpdate, CRespawn, CSetHealth, CSpawnEntity,
|
||||
CSyncPlayerPosition, CSystemChatMessage, GameEvent, PlayerAction,
|
||||
CPlayerAbilities, CPlayerInfoUpdate, CSetHealth, CSyncPlayerPosition, CSystemChatMessage,
|
||||
GameEvent, PlayerAction,
|
||||
},
|
||||
server::play::{
|
||||
SChatCommand, SChatMessage, SClientCommand, SClientInformationPlay, SClientTickEnd,
|
||||
@@ -53,7 +52,7 @@ use crate::{
|
||||
Client, PlayerConfig,
|
||||
},
|
||||
server::Server,
|
||||
world::{player_chunker, World},
|
||||
world::World,
|
||||
};
|
||||
use crate::{error::PumpkinError, world::player_chunker::get_view_distance};
|
||||
|
||||
@@ -225,7 +224,7 @@ impl Player {
|
||||
/// Removes the Player out of the current World
|
||||
#[allow(unused_variables)]
|
||||
pub async fn remove(&self) {
|
||||
let world = &self.living_entity.entity.world;
|
||||
let world = self.world();
|
||||
// Abort pending chunks here too because we might clean up before chunk tasks are done
|
||||
self.abort_chunks("closed");
|
||||
|
||||
@@ -343,14 +342,14 @@ impl Player {
|
||||
"Removed player id {} ({}) ({} chunks remain cached)",
|
||||
self.gameprofile.name,
|
||||
self.client.id,
|
||||
self.living_entity.entity.world.get_cached_chunk_len()
|
||||
self.world().get_cached_chunk_len()
|
||||
);
|
||||
|
||||
//self.living_entity.entity.world.level.list_cached();
|
||||
//self.world().level.list_cached();
|
||||
}
|
||||
|
||||
pub async fn attack(&self, victim: &Arc<Self>) {
|
||||
let world = &self.living_entity.entity.world;
|
||||
let world = self.world();
|
||||
let victim_entity = &victim.living_entity.entity;
|
||||
let attacker_entity = &self.living_entity.entity;
|
||||
let config = &ADVANCED_CONFIG.pvp;
|
||||
@@ -392,7 +391,10 @@ impl Player {
|
||||
damage *= 1.5;
|
||||
}
|
||||
|
||||
victim.living_entity.damage(damage).await;
|
||||
victim
|
||||
.living_entity
|
||||
.damage(damage, 34) // PlayerAttack
|
||||
.await;
|
||||
|
||||
let mut knockback_strength = 1.0;
|
||||
match attack_type {
|
||||
@@ -473,6 +475,10 @@ impl Player {
|
||||
self.living_entity.entity.entity_id
|
||||
}
|
||||
|
||||
pub const fn world(&self) -> &Arc<World> {
|
||||
&self.living_entity.entity.world
|
||||
}
|
||||
|
||||
/// Updates the current abilities the Player has
|
||||
pub async fn send_abilties_update(&self) {
|
||||
let mut b = 0i8;
|
||||
@@ -520,104 +526,6 @@ impl Player {
|
||||
self.permission_lvl
|
||||
}
|
||||
|
||||
pub async fn respawn(self: &Arc<Self>, alive: bool) {
|
||||
let last_pos = self.living_entity.last_pos.load();
|
||||
let death_location = WorldPosition(Vector3::new(
|
||||
last_pos.x.round() as i32,
|
||||
last_pos.y.round() as i32,
|
||||
last_pos.z.round() as i32,
|
||||
));
|
||||
|
||||
let data_kept = u8::from(alive);
|
||||
|
||||
self.client
|
||||
.send_packet(&CRespawn::new(
|
||||
0.into(),
|
||||
"minecraft:overworld",
|
||||
0, // seed
|
||||
self.gamemode.load() as u8,
|
||||
self.gamemode.load() as i8,
|
||||
false,
|
||||
false,
|
||||
Some(("minecraft:overworld", death_location)),
|
||||
0.into(),
|
||||
0.into(),
|
||||
data_kept,
|
||||
))
|
||||
.await;
|
||||
|
||||
log::debug!("Sending player abilities to {}", self.gameprofile.name);
|
||||
self.send_abilties_update().await;
|
||||
|
||||
self.send_permission_lvl_update().await;
|
||||
|
||||
let world = &self.living_entity.entity.world;
|
||||
|
||||
// teleport
|
||||
let x = 10.0;
|
||||
let z = 10.0;
|
||||
let top = world.get_top_block(Vector2::new(x as i32, z as i32)).await;
|
||||
let position = Vector3::new(x, f64::from(top + 1), z);
|
||||
let yaw = 10.0;
|
||||
let pitch = 10.0;
|
||||
|
||||
log::debug!("Sending player teleport to {}", self.gameprofile.name);
|
||||
self.teleport(position, yaw, pitch).await;
|
||||
|
||||
self.living_entity.last_pos.store(position);
|
||||
|
||||
// TODO: difficulty, exp bar, status effect
|
||||
|
||||
let world = &self.living_entity.entity.world;
|
||||
world
|
||||
.worldborder
|
||||
.lock()
|
||||
.await
|
||||
.init_client(&self.client)
|
||||
.await;
|
||||
|
||||
// TODO: world spawn (compass stuff)
|
||||
|
||||
self.client
|
||||
.send_packet(&CGameEvent::new(GameEvent::StartWaitingChunks, 0.0))
|
||||
.await;
|
||||
|
||||
let entity = &self.living_entity.entity;
|
||||
let entity_id = entity.entity_id;
|
||||
|
||||
let skin_parts = self.config.lock().await.skin_parts;
|
||||
let entity_metadata_packet =
|
||||
CSetEntityMetadata::new(entity_id.into(), Metadata::new(17, VarInt(0), &skin_parts));
|
||||
|
||||
world
|
||||
.broadcast_packet_except(
|
||||
&[self.gameprofile.id],
|
||||
// TODO: add velo
|
||||
&CSpawnEntity::new(
|
||||
entity.entity_id.into(),
|
||||
self.gameprofile.id,
|
||||
(EntityType::Player as i32).into(),
|
||||
position.x,
|
||||
position.y,
|
||||
position.z,
|
||||
pitch,
|
||||
yaw,
|
||||
yaw,
|
||||
0.into(),
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
),
|
||||
)
|
||||
.await;
|
||||
|
||||
player_chunker::player_join(world, self.clone()).await;
|
||||
world.broadcast_packet_all(&entity_metadata_packet).await;
|
||||
// update commands
|
||||
|
||||
self.set_health(20.0, 20, 20.0).await;
|
||||
}
|
||||
|
||||
/// yaw and pitch in degrees
|
||||
pub async fn teleport(&self, position: Vector3<f64>, yaw: f32, pitch: f32) {
|
||||
// this is the ultra special magic code used to create the teleport id
|
||||
|
||||
@@ -301,7 +301,7 @@ async fn main() -> io::Result<()> {
|
||||
{
|
||||
let (player, world) = server.add_player(client).await;
|
||||
world
|
||||
.spawn_player(&BASIC_CONFIG, player.clone(), &server.command_dispatcher)
|
||||
.spawn_player(&BASIC_CONFIG, player.clone(), &server)
|
||||
.await;
|
||||
|
||||
// poll Player
|
||||
|
||||
@@ -7,7 +7,7 @@ use pumpkin_inventory::drag_handler::DragHandler;
|
||||
use pumpkin_inventory::{Container, OpenContainer};
|
||||
use pumpkin_protocol::client::login::CEncryptionRequest;
|
||||
use pumpkin_protocol::{client::config::CPluginMessage, ClientPacket};
|
||||
use pumpkin_registry::Registry;
|
||||
use pumpkin_registry::{DimensionType, Registry};
|
||||
use pumpkin_world::dimension::Dimension;
|
||||
use rand::prelude::SliceRandom;
|
||||
use std::collections::HashMap;
|
||||
@@ -46,6 +46,8 @@ pub struct Server {
|
||||
pub command_dispatcher: Arc<CommandDispatcher<'static>>,
|
||||
/// Manages multiple worlds within the server.
|
||||
pub worlds: Vec<Arc<World>>,
|
||||
// All the dimensions that exists on the server,
|
||||
pub dimensions: Vec<DimensionType>,
|
||||
/// Caches game registries for efficient access.
|
||||
pub cached_registry: Vec<Registry>,
|
||||
/// Tracks open containers used for item interactions.
|
||||
@@ -77,10 +79,13 @@ impl Server {
|
||||
// First register default command, after that plugins can put in their own
|
||||
let command_dispatcher = default_dispatcher();
|
||||
|
||||
let world = World::load(Dimension::OverWorld.into_level(
|
||||
// TODO: load form config
|
||||
"./world".parse().unwrap(),
|
||||
));
|
||||
let world = World::load(
|
||||
Dimension::OverWorld.into_level(
|
||||
// TODO: load form config
|
||||
"./world".parse().unwrap(),
|
||||
),
|
||||
DimensionType::Overworld,
|
||||
);
|
||||
Self {
|
||||
cached_registry: Registry::get_synced(),
|
||||
open_containers: RwLock::new(HashMap::new()),
|
||||
@@ -88,6 +93,12 @@ impl Server {
|
||||
// 0 is invalid
|
||||
entity_id: 2.into(),
|
||||
worlds: vec![Arc::new(world)],
|
||||
dimensions: vec![
|
||||
DimensionType::Overworld,
|
||||
DimensionType::OverworldCaves,
|
||||
DimensionType::TheNether,
|
||||
DimensionType::TheEnd,
|
||||
],
|
||||
command_dispatcher,
|
||||
auth_client,
|
||||
key_store: KeyStore::new(),
|
||||
|
||||
@@ -6,20 +6,22 @@ use std::{
|
||||
pub mod player_chunker;
|
||||
|
||||
use crate::{
|
||||
command::{client_cmd_suggestions, dispatcher::CommandDispatcher},
|
||||
command::client_cmd_suggestions,
|
||||
entity::{
|
||||
player::{ChunkHandleWrapper, Player},
|
||||
Entity,
|
||||
},
|
||||
error::PumpkinError,
|
||||
server::Server,
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use pumpkin_config::BasicConfiguration;
|
||||
use pumpkin_core::math::vector2::Vector2;
|
||||
use pumpkin_core::math::{position::WorldPosition, vector3::Vector3};
|
||||
use pumpkin_core::text::{color::NamedColor, TextComponent};
|
||||
use pumpkin_entity::{entity_type::EntityType, EntityId};
|
||||
use pumpkin_protocol::{
|
||||
client::play::{CBlockUpdate, CSoundEffect, CWorldEvent},
|
||||
client::play::{CBlockUpdate, CRespawn, CSoundEffect, CWorldEvent},
|
||||
SoundCategory,
|
||||
};
|
||||
use pumpkin_protocol::{
|
||||
@@ -29,6 +31,7 @@ use pumpkin_protocol::{
|
||||
},
|
||||
ClientPacket, VarInt,
|
||||
};
|
||||
use pumpkin_registry::DimensionType;
|
||||
use pumpkin_world::chunk::ChunkData;
|
||||
use pumpkin_world::level::Level;
|
||||
use pumpkin_world::{
|
||||
@@ -99,17 +102,20 @@ pub struct World {
|
||||
pub scoreboard: Mutex<Scoreboard>,
|
||||
/// The world's worldborder, defining the playable area and controlling its expansion or contraction.
|
||||
pub worldborder: Mutex<Worldborder>,
|
||||
/// The type of dimension the world is in
|
||||
pub dimension_type: DimensionType,
|
||||
// TODO: entities
|
||||
}
|
||||
|
||||
impl World {
|
||||
#[must_use]
|
||||
pub fn load(level: Level) -> Self {
|
||||
pub fn load(level: Level, dimension_type: DimensionType) -> Self {
|
||||
Self {
|
||||
level: Arc::new(level),
|
||||
current_players: Arc::new(Mutex::new(HashMap::new())),
|
||||
scoreboard: Mutex::new(Scoreboard::new()),
|
||||
worldborder: Mutex::new(Worldborder::new(0.0, 0.0, 29_999_984.0, 0, 0, 0)),
|
||||
dimension_type,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,8 +196,15 @@ impl World {
|
||||
&self,
|
||||
base_config: &BasicConfiguration,
|
||||
player: Arc<Player>,
|
||||
command_dispatcher: &CommandDispatcher<'_>,
|
||||
server: &Server,
|
||||
) {
|
||||
let command_dispatcher = &server.command_dispatcher;
|
||||
let dimensions = &server
|
||||
.dimensions
|
||||
.iter()
|
||||
.map(DimensionType::name)
|
||||
.collect_vec();
|
||||
|
||||
// This code follows the vanilla packet order
|
||||
let entity_id = player.entity_id();
|
||||
let gamemode = player.gamemode.load();
|
||||
@@ -207,15 +220,15 @@ impl World {
|
||||
.send_packet(&CLogin::new(
|
||||
entity_id,
|
||||
base_config.hardcore,
|
||||
&["minecraft:overworld"],
|
||||
dimensions,
|
||||
base_config.max_players.into(),
|
||||
base_config.view_distance.into(), // TODO: view distance
|
||||
base_config.simulation_distance.into(), // TODO: sim view dinstance
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
0.into(),
|
||||
"minecraft:overworld",
|
||||
(self.dimension_type as u8).into(),
|
||||
self.dimension_type.name(),
|
||||
0, // seed
|
||||
gamemode as u8,
|
||||
base_config.default_gamemode as i8,
|
||||
@@ -376,6 +389,106 @@ impl World {
|
||||
player_chunker::player_join(self, player.clone()).await;
|
||||
}
|
||||
|
||||
pub async fn respawn_player(&self, player: &Arc<Player>, alive: bool) {
|
||||
let last_pos = player.living_entity.last_pos.load();
|
||||
let death_dimension = player.world().dimension_type.name();
|
||||
let death_location = WorldPosition(Vector3::new(
|
||||
last_pos.x.round() as i32,
|
||||
last_pos.y.round() as i32,
|
||||
last_pos.z.round() as i32,
|
||||
));
|
||||
|
||||
let data_kept = u8::from(alive);
|
||||
|
||||
// TODO: switch world in player entity to new world
|
||||
|
||||
player
|
||||
.client
|
||||
.send_packet(&CRespawn::new(
|
||||
(self.dimension_type as u8).into(),
|
||||
self.dimension_type.name(),
|
||||
0, // seed
|
||||
player.gamemode.load() as u8,
|
||||
player.gamemode.load() as i8,
|
||||
false,
|
||||
false,
|
||||
Some((death_dimension, death_location)),
|
||||
0.into(),
|
||||
0.into(),
|
||||
data_kept,
|
||||
))
|
||||
.await;
|
||||
|
||||
log::debug!("Sending player abilities to {}", player.gameprofile.name);
|
||||
player.send_abilties_update().await;
|
||||
|
||||
player.send_permission_lvl_update().await;
|
||||
|
||||
// teleport
|
||||
let mut position = Vector3::new(10.0, 120.0, 10.0);
|
||||
let yaw = 10.0;
|
||||
let pitch = 10.0;
|
||||
|
||||
let top = self
|
||||
.get_top_block(Vector2::new(position.x as i32, position.z as i32))
|
||||
.await;
|
||||
position.y = f64::from(top + 1);
|
||||
|
||||
log::debug!("Sending player teleport to {}", player.gameprofile.name);
|
||||
player.teleport(position, yaw, pitch).await;
|
||||
|
||||
player.living_entity.last_pos.store(position);
|
||||
|
||||
// TODO: difficulty, exp bar, status effect
|
||||
|
||||
self.worldborder
|
||||
.lock()
|
||||
.await
|
||||
.init_client(&player.client)
|
||||
.await;
|
||||
|
||||
// TODO: world spawn (compass stuff)
|
||||
|
||||
player
|
||||
.client
|
||||
.send_packet(&CGameEvent::new(GameEvent::StartWaitingChunks, 0.0))
|
||||
.await;
|
||||
|
||||
let entity = &player.living_entity.entity;
|
||||
let entity_id = entity.entity_id;
|
||||
|
||||
let skin_parts = player.config.lock().await.skin_parts;
|
||||
let entity_metadata_packet =
|
||||
CSetEntityMetadata::new(entity_id.into(), Metadata::new(17, VarInt(0), &skin_parts));
|
||||
|
||||
self.broadcast_packet_except(
|
||||
&[player.gameprofile.id],
|
||||
// TODO: add velo
|
||||
&CSpawnEntity::new(
|
||||
entity.entity_id.into(),
|
||||
player.gameprofile.id,
|
||||
(EntityType::Player as i32).into(),
|
||||
position.x,
|
||||
position.y,
|
||||
position.z,
|
||||
pitch,
|
||||
yaw,
|
||||
yaw,
|
||||
0.into(),
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
),
|
||||
)
|
||||
.await;
|
||||
|
||||
player_chunker::player_join(self, player.clone()).await;
|
||||
self.broadcast_packet_all(&entity_metadata_packet).await;
|
||||
// update commands
|
||||
|
||||
player.set_health(20.0, 20, 20.0).await;
|
||||
}
|
||||
|
||||
pub fn mark_chunks_as_not_watched(&self, chunks: &[Vector2<i32>]) -> Vec<Vector2<i32>> {
|
||||
self.level.mark_chunks_as_not_watched(chunks)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user