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:
Alvsch
2024-11-23 19:00:34 +01:00
committed by GitHub
parent 402f3951f5
commit c3c4aff912
8 changed files with 194 additions and 139 deletions

View File

@@ -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

View File

@@ -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 {

View File

@@ -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 => {

View File

@@ -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 {

View File

@@ -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

View File

@@ -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

View File

@@ -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(),

View File

@@ -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)
}