mirror of
https://github.com/Pumpkin-MC/Pumpkin.git
synced 2026-08-30 20:14:23 +00:00
fix: broadcast correct protocol version in status response
This commit is contained in:
@@ -373,7 +373,7 @@ impl From<ReadingError> for PacketDecodeError {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[derive(Serialize, Clone)]
|
||||
pub struct StatusResponse {
|
||||
/// The version on which the server is running. (Optional)
|
||||
pub version: Option<Version>,
|
||||
@@ -386,7 +386,7 @@ pub struct StatusResponse {
|
||||
/// Whether players are forced to use secure chat.
|
||||
pub enforce_secure_chat: bool,
|
||||
}
|
||||
#[derive(Serialize)]
|
||||
#[derive(Serialize, Clone)]
|
||||
pub struct Version {
|
||||
/// The name of the version (e.g. 1.21.4)
|
||||
pub name: String,
|
||||
@@ -394,7 +394,7 @@ pub struct Version {
|
||||
pub protocol: u32,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[derive(Serialize, Clone)]
|
||||
pub struct Players {
|
||||
/// The maximum player count that the server allows.
|
||||
pub max: u32,
|
||||
@@ -405,7 +405,7 @@ pub struct Players {
|
||||
pub sample: Vec<Sample>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[derive(Serialize, Clone)]
|
||||
pub struct Sample {
|
||||
/// The player's name.
|
||||
pub name: String,
|
||||
|
||||
@@ -182,6 +182,10 @@ pub trait RandomImpl {
|
||||
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_bool(&mut self) -> bool;
|
||||
|
||||
@@ -177,7 +177,7 @@ impl CanyonCarver {
|
||||
let vertical_multiplier = 1.0 - (0.5 - current_step / distance).abs() * 2.0;
|
||||
let factor = canyon_config.shape.vertical_radius_default_factor
|
||||
+ 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)]
|
||||
|
||||
@@ -312,6 +312,8 @@ impl CaveCarver {
|
||||
let zd = (world_z as f64 + 0.5 - z) / horizontal_radius;
|
||||
|
||||
if xd * xd + zd * zd < 1.0 {
|
||||
let mut has_grass = false;
|
||||
|
||||
for world_y in (min_y + 1..=max_y).rev() {
|
||||
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.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,
|
||||
z: i32,
|
||||
is_nether: bool,
|
||||
has_grass: &mut bool,
|
||||
) -> bool {
|
||||
let local_y = y - chunk.bottom_y() as i32;
|
||||
let state_id = chunk.get_block_state_raw(x & 15, local_y, z & 15);
|
||||
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 {
|
||||
return false;
|
||||
if block.id == pumpkin_data::Block::GRASS_BLOCK.id
|
||||
|| block.id == pumpkin_data::Block::MYCELIUM.id
|
||||
{
|
||||
*has_grass = true;
|
||||
}
|
||||
|
||||
// Only carve if it's replaceable
|
||||
@@ -373,6 +386,19 @@ impl CaveCarver {
|
||||
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;
|
||||
}
|
||||
false
|
||||
|
||||
@@ -1,21 +1,27 @@
|
||||
use std::sync::atomic::{AtomicBool, AtomicU8, Ordering};
|
||||
use std::sync::{Arc, Weak};
|
||||
|
||||
use pumpkin_data::entity::EntityType;
|
||||
|
||||
use crate::entity::{
|
||||
Entity, NBTStorage,
|
||||
ai::goal::{look_around::RandomLookAroundGoal, look_at_entity::LookAtEntityGoal},
|
||||
ai::goal::{Controls, Goal, GoalFuture},
|
||||
mob::{Mob, MobEntity},
|
||||
};
|
||||
|
||||
pub struct GhastEntity {
|
||||
pub mob_entity: MobEntity,
|
||||
pub is_charging: AtomicBool,
|
||||
pub explosion_power: AtomicU8,
|
||||
}
|
||||
|
||||
impl GhastEntity {
|
||||
pub async fn new(entity: Entity) -> Arc<Self> {
|
||||
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_weak: Weak<dyn Mob> = {
|
||||
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;
|
||||
|
||||
// TODO: GhastFlyGoal, GhastLookGoal, GhastAttackGoal
|
||||
goal_selector.add_goal(
|
||||
6,
|
||||
LookAtEntityGoal::with_default(mob_weak, &EntityType::PLAYER, 8.0),
|
||||
);
|
||||
goal_selector.add_goal(6, Box::new(RandomLookAroundGoal::default()));
|
||||
goal_selector.add_goal(7, Box::new(GhastLookGoal::new(mob_weak.clone())));
|
||||
};
|
||||
|
||||
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 {}
|
||||
@@ -45,6 +55,61 @@ impl Mob for GhastEntity {
|
||||
}
|
||||
|
||||
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,8 +9,13 @@ impl JavaClient {
|
||||
pub async fn handle_status_request(&self, server: &Server) {
|
||||
debug!("Handling status request");
|
||||
let status = server.get_status();
|
||||
self.send_packet_now(&status.lock().await.get_status())
|
||||
.await;
|
||||
self.send_packet_now(
|
||||
&status
|
||||
.lock()
|
||||
.await
|
||||
.get_status_packet(self.version.load().protocol_version()),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
pub async fn handle_ping_request(&self, ping_request: SStatusPingRequest) {
|
||||
|
||||
@@ -2,7 +2,7 @@ use crate::entity::player::Player;
|
||||
use base64::{Engine as _, engine::general_purpose};
|
||||
use core::error;
|
||||
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::{
|
||||
Players, Sample, StatusResponse, Version,
|
||||
codec::var_int::VarInt,
|
||||
@@ -37,9 +37,6 @@ fn load_icon_from_bytes(png_data: &[u8]) -> String {
|
||||
|
||||
pub struct CachedStatus {
|
||||
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)>,
|
||||
}
|
||||
|
||||
@@ -73,18 +70,29 @@ impl CachedStatus {
|
||||
#[must_use]
|
||||
pub fn new(config: &BasicConfiguration) -> Self {
|
||||
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 {
|
||||
status_response,
|
||||
status_response_json,
|
||||
player_samples: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_status(&self) -> CStatusResponse {
|
||||
CStatusResponse::new(self.status_response_json.clone())
|
||||
pub fn get_status_packet(&self, client_protocol: i32) -> CStatusResponse {
|
||||
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> {
|
||||
@@ -102,38 +110,28 @@ impl CachedStatus {
|
||||
let player_id = player.gameprofile.id;
|
||||
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) {
|
||||
self.player_samples.push((player_id, player_name));
|
||||
let sample = self.build_sample_list();
|
||||
|
||||
let status_response = &mut self.status_response;
|
||||
if let Some(players) = &mut status_response.players {
|
||||
if let Some(players) = &mut self.status_response.players {
|
||||
players.online = players.online.saturating_add(1);
|
||||
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) {
|
||||
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) {
|
||||
self.player_samples.retain(|(id, _)| *id != player_id);
|
||||
let sample = self.build_sample_list();
|
||||
|
||||
let status_response = &mut self.status_response;
|
||||
if let Some(players) = &mut status_response.players {
|
||||
if let Some(players) = &mut self.status_response.players {
|
||||
players.online = players.online.saturating_sub(1);
|
||||
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 {
|
||||
version: Some(Version {
|
||||
name: CURRENT_MC_VERSION.to_string(),
|
||||
protocol: CURRENT_MC_VERSION.protocol_version() as u32,
|
||||
name: format!("{LOWEST_SUPPORTED_MC_VERSION}-{CURRENT_MC_VERSION}"),
|
||||
protocol: LOWEST_SUPPORTED_MC_VERSION.protocol_version() as u32,
|
||||
}),
|
||||
players: Some(Players {
|
||||
max: config.max_players,
|
||||
|
||||
Reference in New Issue
Block a user