fix: broadcast correct protocol version in status response
This commit is contained in:
parent
3b2eaf9467
commit
44c162df6d
@ -373,7 +373,7 @@ impl From<ReadingError> for PacketDecodeError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize, Clone)]
|
||||||
pub struct StatusResponse {
|
pub struct StatusResponse {
|
||||||
/// The version on which the server is running. (Optional)
|
/// The version on which the server is running. (Optional)
|
||||||
pub version: Option<Version>,
|
pub version: Option<Version>,
|
||||||
@ -386,7 +386,7 @@ pub struct StatusResponse {
|
|||||||
/// Whether players are forced to use secure chat.
|
/// Whether players are forced to use secure chat.
|
||||||
pub enforce_secure_chat: bool,
|
pub enforce_secure_chat: bool,
|
||||||
}
|
}
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize, Clone)]
|
||||||
pub struct Version {
|
pub struct Version {
|
||||||
/// The name of the version (e.g. 1.21.4)
|
/// The name of the version (e.g. 1.21.4)
|
||||||
pub name: String,
|
pub name: String,
|
||||||
@ -394,7 +394,7 @@ pub struct Version {
|
|||||||
pub protocol: u32,
|
pub protocol: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize, Clone)]
|
||||||
pub struct Players {
|
pub struct Players {
|
||||||
/// The maximum player count that the server allows.
|
/// The maximum player count that the server allows.
|
||||||
pub max: u32,
|
pub max: u32,
|
||||||
@ -405,7 +405,7 @@ pub struct Players {
|
|||||||
pub sample: Vec<Sample>,
|
pub sample: Vec<Sample>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize, Clone)]
|
||||||
pub struct Sample {
|
pub struct Sample {
|
||||||
/// The player's name.
|
/// The player's name.
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
|||||||
@ -182,6 +182,10 @@ pub trait RandomImpl {
|
|||||||
self.next_bounded_i32(max - min + 1) + min
|
self.next_bounded_i32(max - min + 1) + min
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn next_inbetween_f32(&mut self, min: f32, max: f32) -> f32 {
|
||||||
|
self.next_f32() * (max - min) + min
|
||||||
|
}
|
||||||
|
|
||||||
fn next_i64(&mut self) -> i64;
|
fn next_i64(&mut self) -> i64;
|
||||||
|
|
||||||
fn next_bool(&mut self) -> bool;
|
fn next_bool(&mut self) -> bool;
|
||||||
|
|||||||
@ -177,7 +177,7 @@ impl CanyonCarver {
|
|||||||
let vertical_multiplier = 1.0 - (0.5 - current_step / distance).abs() * 2.0;
|
let vertical_multiplier = 1.0 - (0.5 - current_step / distance).abs() * 2.0;
|
||||||
let factor = canyon_config.shape.vertical_radius_default_factor
|
let factor = canyon_config.shape.vertical_radius_default_factor
|
||||||
+ canyon_config.shape.vertical_radius_center_factor * vertical_multiplier;
|
+ canyon_config.shape.vertical_radius_center_factor * vertical_multiplier;
|
||||||
factor as f64 * vertical_radius * (random.next_inbetween_i32(75, 100) as f64 / 100.0)
|
factor as f64 * vertical_radius * random.next_inbetween_f32(0.75, 1.0) as f64
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
|||||||
@ -312,6 +312,8 @@ impl CaveCarver {
|
|||||||
let zd = (world_z as f64 + 0.5 - z) / horizontal_radius;
|
let zd = (world_z as f64 + 0.5 - z) / horizontal_radius;
|
||||||
|
|
||||||
if xd * xd + zd * zd < 1.0 {
|
if xd * xd + zd * zd < 1.0 {
|
||||||
|
let mut has_grass = false;
|
||||||
|
|
||||||
for world_y in (min_y + 1..=max_y).rev() {
|
for world_y in (min_y + 1..=max_y).rev() {
|
||||||
let yd = (world_y as f64 - 0.5 - y) / vertical_radius;
|
let yd = (world_y as f64 - 0.5 - y) / vertical_radius;
|
||||||
|
|
||||||
@ -319,7 +321,15 @@ impl CaveCarver {
|
|||||||
&& !chunk.carving_mask.get(world_x, world_y, world_z)
|
&& !chunk.carving_mask.get(world_x, world_y, world_z)
|
||||||
{
|
{
|
||||||
chunk.carving_mask.set(world_x, world_y, world_z);
|
chunk.carving_mask.set(world_x, world_y, world_z);
|
||||||
self.carve_block(chunk, config, world_x, world_y, world_z, is_nether);
|
self.carve_block(
|
||||||
|
chunk,
|
||||||
|
config,
|
||||||
|
world_x,
|
||||||
|
world_y,
|
||||||
|
world_z,
|
||||||
|
is_nether,
|
||||||
|
&mut has_grass,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -344,13 +354,16 @@ impl CaveCarver {
|
|||||||
y: i32,
|
y: i32,
|
||||||
z: i32,
|
z: i32,
|
||||||
is_nether: bool,
|
is_nether: bool,
|
||||||
|
has_grass: &mut bool,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
let local_y = y - chunk.bottom_y() as i32;
|
let local_y = y - chunk.bottom_y() as i32;
|
||||||
let state_id = chunk.get_block_state_raw(x & 15, local_y, z & 15);
|
let state_id = chunk.get_block_state_raw(x & 15, local_y, z & 15);
|
||||||
let block = pumpkin_data::Block::from_state_id(state_id);
|
let block = pumpkin_data::Block::from_state_id(state_id);
|
||||||
|
|
||||||
if block.id == pumpkin_data::Block::WATER.id || block.id == pumpkin_data::Block::LAVA.id {
|
if block.id == pumpkin_data::Block::GRASS_BLOCK.id
|
||||||
return false;
|
|| block.id == pumpkin_data::Block::MYCELIUM.id
|
||||||
|
{
|
||||||
|
*has_grass = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only carve if it's replaceable
|
// Only carve if it's replaceable
|
||||||
@ -373,6 +386,19 @@ impl CaveCarver {
|
|||||||
chunk.set_block_state(x & 15, local_y, z & 15, air);
|
chunk.set_block_state(x & 15, local_y, z & 15, air);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: fix this
|
||||||
|
// if *has_grass {
|
||||||
|
// let below_state_id = chunk.get_block_state_raw(x & 15, local_y - 1, z & 15);
|
||||||
|
// let below_block = pumpkin_data::Block::from_state_id(below_state_id);
|
||||||
|
|
||||||
|
// if below_block.id == pumpkin_data::Block::DIRT.id {
|
||||||
|
// // TODO: Java uses Biome top material here, defaulting to Grass for now
|
||||||
|
// let top_material =
|
||||||
|
// BlockState::from_id(pumpkin_data::Block::GRASS_BLOCK.default_state.id);
|
||||||
|
// chunk.set_block_state(x & 15, local_y - 1, z & 15, top_material);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
false
|
false
|
||||||
|
|||||||
@ -1,21 +1,27 @@
|
|||||||
|
use std::sync::atomic::{AtomicBool, AtomicU8, Ordering};
|
||||||
use std::sync::{Arc, Weak};
|
use std::sync::{Arc, Weak};
|
||||||
|
|
||||||
use pumpkin_data::entity::EntityType;
|
|
||||||
|
|
||||||
use crate::entity::{
|
use crate::entity::{
|
||||||
Entity, NBTStorage,
|
Entity, NBTStorage,
|
||||||
ai::goal::{look_around::RandomLookAroundGoal, look_at_entity::LookAtEntityGoal},
|
ai::goal::{Controls, Goal, GoalFuture},
|
||||||
mob::{Mob, MobEntity},
|
mob::{Mob, MobEntity},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct GhastEntity {
|
pub struct GhastEntity {
|
||||||
pub mob_entity: MobEntity,
|
pub mob_entity: MobEntity,
|
||||||
|
pub is_charging: AtomicBool,
|
||||||
|
pub explosion_power: AtomicU8,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GhastEntity {
|
impl GhastEntity {
|
||||||
pub async fn new(entity: Entity) -> Arc<Self> {
|
pub async fn new(entity: Entity) -> Arc<Self> {
|
||||||
let mob_entity = MobEntity::new(entity);
|
let mob_entity = MobEntity::new(entity);
|
||||||
let ghast = Self { mob_entity };
|
let ghast = Self {
|
||||||
|
mob_entity,
|
||||||
|
is_charging: AtomicBool::new(false),
|
||||||
|
explosion_power: AtomicU8::new(1),
|
||||||
|
};
|
||||||
|
|
||||||
let mob_arc = Arc::new(ghast);
|
let mob_arc = Arc::new(ghast);
|
||||||
let mob_weak: Weak<dyn Mob> = {
|
let mob_weak: Weak<dyn Mob> = {
|
||||||
let mob_arc: Arc<dyn Mob> = mob_arc.clone();
|
let mob_arc: Arc<dyn Mob> = mob_arc.clone();
|
||||||
@ -25,16 +31,20 @@ impl GhastEntity {
|
|||||||
{
|
{
|
||||||
let mut goal_selector = mob_arc.mob_entity.goals_selector.lock().await;
|
let mut goal_selector = mob_arc.mob_entity.goals_selector.lock().await;
|
||||||
|
|
||||||
// TODO: GhastFlyGoal, GhastLookGoal, GhastAttackGoal
|
goal_selector.add_goal(7, Box::new(GhastLookGoal::new(mob_weak.clone())));
|
||||||
goal_selector.add_goal(
|
|
||||||
6,
|
|
||||||
LookAtEntityGoal::with_default(mob_weak, &EntityType::PLAYER, 8.0),
|
|
||||||
);
|
|
||||||
goal_selector.add_goal(6, Box::new(RandomLookAroundGoal::default()));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
mob_arc
|
mob_arc
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_charging(&self, charging: bool) {
|
||||||
|
// You would also sync this to the client via EntityMetadata here
|
||||||
|
self.is_charging.store(charging, Ordering::Relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_charging(&self) -> bool {
|
||||||
|
self.is_charging.load(Ordering::Relaxed)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NBTStorage for GhastEntity {}
|
impl NBTStorage for GhastEntity {}
|
||||||
@ -45,6 +55,61 @@ impl Mob for GhastEntity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn get_mob_gravity(&self) -> f64 {
|
fn get_mob_gravity(&self) -> f64 {
|
||||||
0.0
|
0.0 // Ghasts fly, no gravity applied in standard travel
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[expect(dead_code)]
|
||||||
|
pub struct GhastLookGoal {
|
||||||
|
goal_control: Controls,
|
||||||
|
mob_weak: Weak<dyn Mob>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GhastLookGoal {
|
||||||
|
#[must_use]
|
||||||
|
pub fn new(mob_weak: Weak<dyn Mob>) -> Self {
|
||||||
|
Self {
|
||||||
|
goal_control: Controls::LOOK,
|
||||||
|
mob_weak,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Goal for GhastLookGoal {
|
||||||
|
fn can_start<'a>(&'a mut self, _mob: &'a dyn Mob) -> GoalFuture<'a, bool> {
|
||||||
|
Box::pin(async { true })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn should_run_every_tick(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tick<'a>(&'a mut self, mob: &'a dyn Mob) -> GoalFuture<'a, ()> {
|
||||||
|
Box::pin(async {
|
||||||
|
let mob_entity = mob.get_mob_entity();
|
||||||
|
let target_opt = mob_entity.target.lock().await.clone();
|
||||||
|
|
||||||
|
if let Some(target) = target_opt {
|
||||||
|
let mob_pos = mob_entity.living_entity.entity.pos.load();
|
||||||
|
let target_pos = target.get_entity().pos.load();
|
||||||
|
|
||||||
|
if mob_pos.squared_distance_to_vec(&target_pos) < 4096.0 {
|
||||||
|
let mut look_control = mob_entity.look_control.lock().await;
|
||||||
|
look_control.look_at(mob, target_pos.x, target_pos.y, target_pos.z);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// If no target, face the movement direction
|
||||||
|
let velocity = mob_entity.living_entity.entity.velocity.load();
|
||||||
|
if velocity.x != 0.0 || velocity.z != 0.0 {
|
||||||
|
let yaw = (-f64::atan2(velocity.x, velocity.z) * (180.0 / std::f64::consts::PI))
|
||||||
|
as f32;
|
||||||
|
mob_entity.living_entity.entity.yaw.store(yaw);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn controls(&self) -> Controls {
|
||||||
|
self.goal_control
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,7 +9,12 @@ impl JavaClient {
|
|||||||
pub async fn handle_status_request(&self, server: &Server) {
|
pub async fn handle_status_request(&self, server: &Server) {
|
||||||
debug!("Handling status request");
|
debug!("Handling status request");
|
||||||
let status = server.get_status();
|
let status = server.get_status();
|
||||||
self.send_packet_now(&status.lock().await.get_status())
|
self.send_packet_now(
|
||||||
|
&status
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.get_status_packet(self.version.load().protocol_version()),
|
||||||
|
)
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -2,7 +2,7 @@ use crate::entity::player::Player;
|
|||||||
use base64::{Engine as _, engine::general_purpose};
|
use base64::{Engine as _, engine::general_purpose};
|
||||||
use core::error;
|
use core::error;
|
||||||
use pumpkin_config::BasicConfiguration;
|
use pumpkin_config::BasicConfiguration;
|
||||||
use pumpkin_data::packet::CURRENT_MC_VERSION;
|
use pumpkin_data::packet::{CURRENT_MC_VERSION, LOWEST_SUPPORTED_MC_VERSION};
|
||||||
use pumpkin_protocol::{
|
use pumpkin_protocol::{
|
||||||
Players, Sample, StatusResponse, Version,
|
Players, Sample, StatusResponse, Version,
|
||||||
codec::var_int::VarInt,
|
codec::var_int::VarInt,
|
||||||
@ -37,9 +37,6 @@ fn load_icon_from_bytes(png_data: &[u8]) -> String {
|
|||||||
|
|
||||||
pub struct CachedStatus {
|
pub struct CachedStatus {
|
||||||
pub status_response: StatusResponse,
|
pub status_response: StatusResponse,
|
||||||
// We cache the json response here so we don't parse it every time someone makes a status request.
|
|
||||||
// Keep in mind that we must parse this again when the StatusResponse changes, which usually happen when a player joins or leaves.
|
|
||||||
status_response_json: String,
|
|
||||||
player_samples: Vec<(Uuid, String)>,
|
player_samples: Vec<(Uuid, String)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -73,18 +70,29 @@ impl CachedStatus {
|
|||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn new(config: &BasicConfiguration) -> Self {
|
pub fn new(config: &BasicConfiguration) -> Self {
|
||||||
let status_response = Self::build_response(config);
|
let status_response = Self::build_response(config);
|
||||||
let status_response_json = serde_json::to_string(&status_response)
|
|
||||||
.expect("Failed to parse status response into JSON");
|
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
status_response,
|
status_response,
|
||||||
status_response_json,
|
|
||||||
player_samples: Vec::new(),
|
player_samples: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_status(&self) -> CStatusResponse {
|
pub fn get_status_packet(&self, client_protocol: i32) -> CStatusResponse {
|
||||||
CStatusResponse::new(self.status_response_json.clone())
|
let mut response = self.status_response.clone();
|
||||||
|
|
||||||
|
let supported_min = LOWEST_SUPPORTED_MC_VERSION.protocol_version();
|
||||||
|
let supported_max = CURRENT_MC_VERSION.protocol_version();
|
||||||
|
|
||||||
|
if client_protocol >= supported_min
|
||||||
|
&& client_protocol <= supported_max
|
||||||
|
&& let Some(version) = &mut response.version
|
||||||
|
{
|
||||||
|
version.protocol = client_protocol as u32;
|
||||||
|
}
|
||||||
|
|
||||||
|
let json = serde_json::to_string(&response).expect("Failed to serialize status response");
|
||||||
|
|
||||||
|
CStatusResponse::new(json)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_sample_list(&self) -> Vec<Sample> {
|
fn build_sample_list(&self) -> Vec<Sample> {
|
||||||
@ -102,38 +110,28 @@ impl CachedStatus {
|
|||||||
let player_id = player.gameprofile.id;
|
let player_id = player.gameprofile.id;
|
||||||
let player_name = player.gameprofile.name.clone();
|
let player_name = player.gameprofile.name.clone();
|
||||||
|
|
||||||
// Only add if player is not already in the list
|
|
||||||
if !self.player_samples.iter().any(|(id, _)| *id == player_id) {
|
if !self.player_samples.iter().any(|(id, _)| *id == player_id) {
|
||||||
self.player_samples.push((player_id, player_name));
|
self.player_samples.push((player_id, player_name));
|
||||||
let sample = self.build_sample_list();
|
let sample = self.build_sample_list();
|
||||||
|
|
||||||
let status_response = &mut self.status_response;
|
if let Some(players) = &mut self.status_response.players {
|
||||||
if let Some(players) = &mut status_response.players {
|
|
||||||
players.online = players.online.saturating_add(1);
|
players.online = players.online.saturating_add(1);
|
||||||
players.sample = sample;
|
players.sample = sample;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.status_response_json = serde_json::to_string(&status_response)
|
|
||||||
.expect("Failed to parse status response into JSON");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn remove_player(&mut self, player: &Player) {
|
pub fn remove_player(&mut self, player: &Player) {
|
||||||
let player_id = player.gameprofile.id;
|
let player_id = player.gameprofile.id;
|
||||||
|
|
||||||
// Only decrement if player was actually in the list
|
|
||||||
if self.player_samples.iter().any(|(id, _)| *id == player_id) {
|
if self.player_samples.iter().any(|(id, _)| *id == player_id) {
|
||||||
self.player_samples.retain(|(id, _)| *id != player_id);
|
self.player_samples.retain(|(id, _)| *id != player_id);
|
||||||
let sample = self.build_sample_list();
|
let sample = self.build_sample_list();
|
||||||
|
|
||||||
let status_response = &mut self.status_response;
|
if let Some(players) = &mut self.status_response.players {
|
||||||
if let Some(players) = &mut status_response.players {
|
|
||||||
players.online = players.online.saturating_sub(1);
|
players.online = players.online.saturating_sub(1);
|
||||||
players.sample = sample;
|
players.sample = sample;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.status_response_json = serde_json::to_string(&status_response)
|
|
||||||
.expect("Failed to parse status response into JSON");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -183,8 +181,8 @@ impl CachedStatus {
|
|||||||
|
|
||||||
StatusResponse {
|
StatusResponse {
|
||||||
version: Some(Version {
|
version: Some(Version {
|
||||||
name: CURRENT_MC_VERSION.to_string(),
|
name: format!("{LOWEST_SUPPORTED_MC_VERSION}-{CURRENT_MC_VERSION}"),
|
||||||
protocol: CURRENT_MC_VERSION.protocol_version() as u32,
|
protocol: LOWEST_SUPPORTED_MC_VERSION.protocol_version() as u32,
|
||||||
}),
|
}),
|
||||||
players: Some(Players {
|
players: Some(Players {
|
||||||
max: config.max_players,
|
max: config.max_players,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user