feat(plugin-api): expose some more entity flags

This commit is contained in:
Alexander Medvedev 2026-04-25 21:18:10 +02:00
parent 7a8f11b984
commit ee11ba746f
14 changed files with 212 additions and 90 deletions

View File

@ -170,6 +170,26 @@ interface player {
is-sprinting: func() -> bool;
/// Sets whether the player is currently sprinting.
set-sprinting: func(sprinting: bool);
/// Returns whether the player is currently swimming.
is-swimming: func() -> bool;
/// Sets whether the player is currently swimming.
set-swimming: func(swimming: bool);
/// Returns whether the player is currently invisible.
is-invisible: func() -> bool;
/// Sets whether the player is currently invisible.
set-invisible: func(invisible: bool);
/// Returns whether the player is currently glowing.
is-glowing: func() -> bool;
/// Sets whether the player is currently glowing.
set-glowing: func(glowing: bool);
/// Returns whether the player is currently flying due to a fall.
is-fall-flying: func() -> bool;
/// Sets whether the player is currently flying due to a fall.
set-fall-flying: func(fall-flying: bool);
/// Returns whether the player is currently on fire. Note: this is only visually like fire, there is no actual logic.
is-on-fire: func() -> bool;
/// Sets whether the player is visually on fire. Note: this is only visually like fire, there is no actual logic.
set-on-fire: func(on-fire: bool);
/// Returns whether the player is currently on the ground.
is-on-ground: func() -> bool;
/// Returns whether the player is currently flying.

View File

@ -124,7 +124,7 @@ impl LevelChannel {
ret
}
pub fn notify(&self) {
let val = self.value.lock().unwrap();
let val = self.value.lock().unwrap_or_else(|e| e.into_inner());
drop(val);
self.notify.notify_one();
}

View File

@ -59,8 +59,12 @@ impl DAG {
if !self.nodes.contains_key(to) || !self.nodes.contains_key(from) {
return;
}
self.nodes.get_mut(to).unwrap().in_degree += 1;
let edge = &mut self.nodes.get_mut(from).unwrap().edge;
*edge = self.edges.insert(Edge::new(to, *edge));
if let Some(node) = self.nodes.get_mut(to) {
node.in_degree += 1;
}
if let Some(node) = self.nodes.get_mut(from) {
let edge = &mut node.edge;
*edge = self.edges.insert(Edge::new(to, *edge));
}
}
}

View File

@ -923,7 +923,7 @@ impl ProtoChunk {
terrain_cache.terrain_builder.place_iceberg(
self,
Biome::from_id(this_biome).unwrap(),
Biome::from_id(this_biome).unwrap_or(&Biome::PLAINS),
x,
z,
surface_estimate,

View File

@ -369,7 +369,8 @@ impl SkyLightPropagator {
}
}
Chunk::Level(c) => {
let mut light_engine = c.light_engine.lock().unwrap();
let mut light_engine =
c.light_engine.lock().unwrap_or_else(|e| e.into_inner());
if section_idx < light_engine.sky_light.len() {
light_engine.sky_light[section_idx]
.set(local_x, local_y, local_z, 15);
@ -406,7 +407,8 @@ impl SkyLightPropagator {
}
}
Chunk::Level(c) => {
let mut light_engine = c.light_engine.lock().unwrap();
let mut light_engine =
c.light_engine.lock().unwrap_or_else(|e| e.into_inner());
if section_idx < light_engine.sky_light.len() {
light_engine.sky_light[section_idx]
.set(local_x, local_y, local_z, light_val);

View File

@ -372,7 +372,7 @@ impl DynamicLightEngine {
return;
}
LightingEngineConfig::Dark => {
self.set_sky_light_level(level, &pos, 0).await.unwrap();
self.set_sky_light_level(level, &pos, 0).await.ok();
return;
}
LightingEngineConfig::Default => {}
@ -484,7 +484,7 @@ impl DynamicLightEngine {
let section_index =
(relative.y - chunk.section.min_y) as usize / BlockPalette::SIZE;
// Bounds check for section index
let mut light_engine = chunk.light_engine.lock().unwrap();
let mut light_engine = chunk.light_engine.lock().unwrap_or_else(|e| e.into_inner());
if section_index >= light_engine.block_light.len() {
return Err("Invalid section index".to_string());
}
@ -511,7 +511,7 @@ impl DynamicLightEngine {
let section_index =
(relative.y - chunk.section.min_y) as usize / BlockPalette::SIZE;
let light_engine = chunk.light_engine.lock().unwrap();
let light_engine = chunk.light_engine.lock().unwrap_or_else(|e| e.into_inner());
// Bounds check for section index (lock the light engine)
if section_index >= light_engine.sky_light.len() {
return 15;
@ -538,7 +538,7 @@ impl DynamicLightEngine {
let section_index =
(relative.y - chunk.section.min_y) as usize / BlockPalette::SIZE;
// Bounds check for section index
let mut light_engine = chunk.light_engine.lock().unwrap();
let mut light_engine = chunk.light_engine.lock().unwrap_or_else(|e| e.into_inner());
if section_index >= light_engine.sky_light.len() {
return Err("Invalid section index".to_string());
}

View File

@ -76,7 +76,7 @@ pub fn set_block_light(cache: &mut Cache, pos: BlockPos, level: u8) {
match &mut cache.chunks[idx] {
Chunk::Level(c) => {
let mut light_engine = c.light_engine.lock().unwrap();
let mut light_engine = c.light_engine.lock().unwrap_or_else(|e| e.into_inner());
if section_y < light_engine.block_light.len() {
light_engine.block_light[section_y].set(x, y, z, level);
c.dirty.store(true, std::sync::atomic::Ordering::Relaxed);
@ -143,7 +143,7 @@ pub fn set_sky_light(cache: &mut Cache, pos: BlockPos, level: u8) {
match &mut cache.chunks[idx] {
Chunk::Level(c) => {
let mut light_engine = c.light_engine.lock().unwrap();
let mut light_engine = c.light_engine.lock().unwrap_or_else(|e| e.into_inner());
if section_y < light_engine.sky_light.len() {
light_engine.sky_light[section_y].set(x, y, z, level);
c.dirty.store(true, std::sync::atomic::Ordering::Relaxed);

View File

@ -394,17 +394,15 @@ impl PoiStorage {
}
fn get_or_load_region(&mut self, rx: i32, rz: i32) -> &mut PoiRegion {
if !self.regions.contains_key(&(rx, rz)) {
let path = self.region_path(rx, rz);
let region = PoiRegion::load(&path).unwrap_or_else(|e| {
let path = self.region_path(rx, rz);
self.regions.entry((rx, rz)).or_insert_with(|| {
PoiRegion::load(&path).unwrap_or_else(|e| {
if path.exists() {
warn!("Failed to load POI region {}: {}", path.display(), e);
}
PoiRegion::new()
});
self.regions.insert((rx, rz), region);
}
self.regions.get_mut(&(rx, rz)).unwrap()
})
})
}
pub fn add(&mut self, pos: BlockPos, poi_type: &str) {

View File

@ -22,12 +22,12 @@ impl<'a, T: std::hash::Hash + Eq> ChunkTickScheduler<&'a T> {
self.offset.store(next_offset, Ordering::SeqCst);
let res = {
let mut queue = self.tick_queue.lock().unwrap();
let mut queue = self.tick_queue.lock().unwrap_or_else(|e| e.into_inner());
std::mem::take(&mut queue[current_offset])
};
if !res.is_empty() {
let mut set = self.queued_ticks.lock().unwrap();
let mut set = self.queued_ticks.lock().unwrap_or_else(|e| e.into_inner());
for next_tick in &res {
set.remove(&(next_tick.position, next_tick.value));
}
@ -36,10 +36,10 @@ impl<'a, T: std::hash::Hash + Eq> ChunkTickScheduler<&'a T> {
}
pub fn schedule_tick(&self, tick: &ScheduledTick<&'a T>, sub_tick_order: u64) {
let mut set = self.queued_ticks.lock().unwrap();
let mut set = self.queued_ticks.lock().unwrap_or_else(|e| e.into_inner());
if set.insert((tick.position, tick.value)) {
let mut queue = self.tick_queue.lock().unwrap();
let mut queue = self.tick_queue.lock().unwrap_or_else(|e| e.into_inner());
let offset = self.offset.load(Ordering::SeqCst);
let index = (offset + tick.delay as usize) % MAX_TICK_DELAY;
@ -53,13 +53,16 @@ impl<'a, T: std::hash::Hash + Eq> ChunkTickScheduler<&'a T> {
}
pub fn is_scheduled(&self, pos: BlockPos, value: &T) -> bool {
self.queued_ticks.lock().unwrap().contains(&(pos, value))
self.queued_ticks
.lock()
.unwrap_or_else(|e| e.into_inner())
.contains(&(pos, value))
}
#[must_use]
pub fn to_vec(&self) -> Vec<ScheduledTick<&'a T>> {
let offset = self.offset.load(Ordering::SeqCst);
let queue = self.tick_queue.lock().unwrap();
let queue = self.tick_queue.lock().unwrap_or_else(|e| e.into_inner());
let mut res = Vec::new();
for i in 0..MAX_TICK_DELAY {
@ -84,7 +87,11 @@ impl<'a, T: std::hash::Hash + Eq + 'static> FromIterator<ScheduledTick<&'a T>>
let (lower, _) = iter.size_hint();
if lower > 0 {
scheduler.queued_ticks.lock().unwrap().reserve(lower);
scheduler
.queued_ticks
.lock()
.unwrap_or_else(|e| e.into_inner())
.reserve(lower);
}
for tick in iter {

View File

@ -413,6 +413,8 @@ pub struct Entity {
pub sneaking: AtomicBool,
/// Indicates whether the entity is sprinting
pub sprinting: AtomicBool,
/// Indicates whether the entity is swimming
pub swimming: AtomicBool,
/// Indicates whether the entity is invisible
pub invisible: AtomicBool,
/// Indicates whether the entity is glowing
@ -568,6 +570,7 @@ impl Entity {
get_section_cord(floor_z),
)),
sneaking: AtomicBool::new(false),
swimming: AtomicBool::new(false),
invisible: AtomicBool::new(false),
glowing: AtomicBool::new(false),
world: ArcSwap::new(world),
@ -2021,6 +2024,13 @@ impl Entity {
self.set_flag(Flag::Sneaking, sneaking).await;
}
pub async fn set_swimming(&self, invisible: bool) {
if self.swimming.load(Ordering::Relaxed) != invisible {
self.swimming.store(invisible, Relaxed);
self.set_flag(Flag::Swimming, invisible).await;
}
}
/// Sets whether the entity is invisible and sends updated metadata.
pub async fn set_invisible(&self, invisible: bool) {
if self.invisible.load(Ordering::Relaxed) != invisible {

View File

@ -241,7 +241,7 @@ impl PumpkinServer {
);
let rcon_server = server.clone();
server.spawn_task(async move {
RCONServer::run(&rcon, rcon_server).await.unwrap();
RCONServer::run(&rcon, rcon_server).await;
});
}
@ -414,7 +414,6 @@ impl PumpkinServer {
}
}
#[expect(clippy::too_many_lines)]
pub async fn unified_listener_task(
&self,
master_client_id_counter: &mut u64,
@ -490,9 +489,7 @@ impl PumpkinServer {
udp_result = resolve_some(self.udp_socket.as_ref(), |sock: &Arc<UdpSocket>| sock.recv_from(&mut udp_buf)) => {
match udp_result {
Ok((len, client_addr)) => {
if len == 0 {
warn!("Received empty UDP packet from {client_addr}");
} else {
if len > 0 {
let id = udp_buf[0];
let is_online = id & 128 != 0;
@ -504,35 +501,23 @@ impl PumpkinServer {
let client = client.clone();
let reader = Cursor::new(udp_buf[..len].to_vec());
let server = self.server.clone();
tasks.spawn(async move {
client.process_packet(&server, reader).await;
});
} else if let Ok(packet) = BedrockClient::is_connection_request(&mut Cursor::new(&udp_buf[4..len])) {
*master_client_id_counter += 1;
let mut platform = BedrockClient::new(self.udp_socket.clone().unwrap(), client_addr, be_clients);
platform.handle_connection_request(packet).await;
platform.start_outgoing_packet_task();
clients_guard.insert(client_addr,
Arc::new(
platform
));
}
} else {
// Please keep the function as simple as possible!
// We dont care about the result, the client just resends the packet
// Since offline packets are very small we dont need to move and clone the data
let _ = BedrockClient::handle_offline_packet(&self.server, id, &mut Cursor::new(&udp_buf[1..len]), client_addr, self.udp_socket.as_ref().unwrap()).await;
} else if let Some(sock) = self.udp_socket.as_ref()
&& let Ok(packet) = BedrockClient::is_connection_request(&mut Cursor::new(&udp_buf[4..len])) {
*master_client_id_counter += 1;
let mut platform = BedrockClient::new(sock.clone(), client_addr, be_clients);
platform.handle_connection_request(packet).await;
platform.start_outgoing_packet_task();
clients_guard.insert(client_addr, Arc::new(platform));
}
} else if let Some(sock) = self.udp_socket.as_ref() {
let _ = BedrockClient::handle_offline_packet(&self.server, id, &mut Cursor::new(&udp_buf[1..len]), client_addr, sock).await;
}
}
}
// Since all packets go over this match statement, there should be not waiting
Err(e) => {
error!("{e}");
}
Err(e) => error!("UDP socket error: {e}"),
}
},

View File

@ -124,7 +124,13 @@ impl BedrockClient {
}
pub fn start_outgoing_packet_task(&mut self) {
let mut packet_receiver = self.outgoing_packet_queue_recv.take().unwrap();
let Some(mut packet_receiver) = self.outgoing_packet_queue_recv.take() else {
error!(
"Outgoing packet receiver was already taken for {}",
self.address
);
return;
};
let close_token = self.close_token.clone();
let writer = self.network_writer.clone();
let addr = self.address;
@ -168,8 +174,10 @@ impl BedrockClient {
if let Some(packet) = packet
&& let Err(error) = self.handle_packet_payload(server, packet).await
{
let _text = format!("Error while reading incoming packet {error}");
error!("Failed to read incoming packet with : {error}");
error!(
"Failed to handle packet payload for {}: {}",
self.address, error
);
self.kick(DisconnectReason::BadPacket, error.to_string())
.await;
}
@ -240,19 +248,23 @@ impl BedrockClient {
socket: &UdpSocket,
) {
let mut data = Vec::new();
let writer = &mut data;
Self::write_raw_packet(packet, writer).unwrap();
if let Err(err) = Self::write_raw_packet(packet, &mut data) {
error!("Failed to write offline packet: {err}");
return;
}
// We dont care if it works, if not the client will try again!
let _ = socket.send_to(&data, addr).await;
}
pub async fn send_game_packet<P: BClientPacket>(&self, packet: &P) {
let mut packet_buf = Vec::new();
self.write_game_packet(packet, &mut packet_buf)
.await
.unwrap();
self.send_framed_packet_data(packet_buf, RakReliability::Unreliable)
.await;
match self.write_game_packet(packet, &mut packet_buf).await {
Ok(()) => {
self.send_framed_packet_data(packet_buf, RakReliability::Unreliable)
.await;
}
Err(err) => error!("Failed to write game packet: {err}"),
}
}
pub async fn write_game_packet_to_set<P: BClientPacket>(
@ -261,9 +273,12 @@ impl BedrockClient {
frame_set: &mut FrameSet,
) {
let mut payload = Vec::new();
self.write_game_packet(packet, &mut payload).await.unwrap();
frame_set.frames.push(Frame::new_unreliable(payload));
match self.write_game_packet(packet, &mut payload).await {
Ok(()) => {
frame_set.frames.push(Frame::new_unreliable(payload));
}
Err(err) => error!("Failed to write game packet to set: {err}"),
}
}
pub async fn send_framed_packet<P: BClientPacket>(
@ -272,8 +287,10 @@ impl BedrockClient {
reliability: RakReliability,
) {
let mut packet_buf = Vec::new();
Self::write_raw_packet(packet, &mut packet_buf).unwrap();
self.send_framed_packet_data(packet_buf, reliability).await;
match Self::write_raw_packet(packet, &mut packet_buf) {
Ok(()) => self.send_framed_packet_data(packet_buf, reliability).await,
Err(err) => error!("Failed to write framed packet: {err}"),
}
}
pub async fn send_framed_packet_data(
@ -341,23 +358,22 @@ impl BedrockClient {
pub async fn send_frame_set(&self, mut frame_set: FrameSet, id: u8) {
frame_set.sequence = u24(self.output_sequence_number.fetch_add(1, Ordering::Relaxed));
let mut frame_set_buf = Vec::new();
frame_set.write_packet_data(&mut frame_set_buf, id).unwrap();
// I dont know if thats the right place to make encryption & decoding
if let Err(err) = frame_set.write_packet_data(&mut frame_set_buf, id) {
error!("Failed to write frame set data: {err}");
return;
}
if let Err(err) = self
.network_writer
.lock()
.await
.write_packet(&frame_set_buf, self.address, &self.socket)
.await
&& !self.is_closed()
{
// It is expected that the packet will fail if we are closed
if !self.is_closed() {
warn!("Failed to send packet to client: {err}");
// We now need to close the connection to the client since the stream is in an
// unknown state
self.close_token.cancel();
}
warn!("Failed to send packet to client {}: {}", self.address, err);
self.close_token.cancel();
}
}
@ -376,9 +392,9 @@ impl BedrockClient {
self.close_token.is_cancelled()
}
pub async fn send_ack(&self, ack: &Ack) {
pub async fn send_ack(&self, ack: &Ack) -> Result<(), Error> {
let mut packet_buf = Vec::new();
ack.write(&mut packet_buf).unwrap();
ack.write(&mut packet_buf)?;
if let Err(err) = self
.network_writer
@ -387,9 +403,11 @@ impl BedrockClient {
.write_packet(&packet_buf, self.address, &self.socket)
.await
{
warn!("Failed to send packet to client: {err}");
warn!("Failed to send ACK to {}: {err}", self.address);
self.close().await;
return Err(err);
}
Ok(())
}
pub async fn handle_packet_payload(
@ -425,7 +443,7 @@ impl BedrockClient {
frame_set: FrameSet,
) -> Result<(), Error> {
// TODO: Send all ACKs in short intervals in batches
self.send_ack(&Ack::new(vec![frame_set.sequence.0])).await;
self.send_ack(&Ack::new(vec![frame_set.sequence.0])).await?;
// TODO
for frame in frame_set.frames {
self.handle_frame(server, frame).await?;
@ -511,9 +529,14 @@ impl BedrockClient {
.await;
}
_ => {
return self
.handle_play_packet(self.player.lock().await.as_ref().unwrap(), server, packet)
.await;
let player_lock = self.player.lock().await;
if let Some(player) = player_lock.as_ref() {
return self.handle_play_packet(player, server, packet).await;
}
debug!(
"Received game packet {} before player was initialized",
packet.id
);
}
}
Ok(())

View File

@ -17,7 +17,7 @@ mod packet;
pub struct RCONServer;
impl RCONServer {
pub async fn run(config: &RCONConfig, server: Arc<Server>) -> Result<(), std::io::Error> {
pub async fn run(config: &RCONConfig, server: Arc<Server>) {
let listener = tokio::net::TcpListener::bind(config.address).await.unwrap();
let password = Arc::new(config.password.clone());
@ -35,10 +35,9 @@ impl RCONServer {
};
// Asynchronously wait for an inbound socket.
let Some(result) = await_new_client().await else {
let Some(Ok((connection, address))) = await_new_client().await else {
break;
};
let (connection, address) = result?;
if config.max_connections != 0 && connections >= config.max_connections {
continue;
@ -53,7 +52,6 @@ impl RCONServer {
debug!("closed RCON connection");
connections -= 1;
}
Ok(())
}
}

View File

@ -685,6 +685,81 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState {
Ok(())
}
async fn is_swimming(&mut self, player: Resource<Player>) -> wasmtime::Result<bool> {
let player = player_from_resource(self, &player)?;
Ok(player.get_entity().swimming.load(Ordering::Relaxed))
}
async fn set_swimming(
&mut self,
player: Resource<Player>,
swimming: bool,
) -> wasmtime::Result<()> {
let player = player_from_resource(self, &player)?;
player.get_entity().set_swimming(swimming).await;
Ok(())
}
async fn is_invisible(&mut self, player: Resource<Player>) -> wasmtime::Result<bool> {
let player = player_from_resource(self, &player)?;
Ok(player.get_entity().invisible.load(Ordering::Relaxed))
}
async fn set_invisible(
&mut self,
player: Resource<Player>,
invisible: bool,
) -> wasmtime::Result<()> {
let player = player_from_resource(self, &player)?;
player.get_entity().set_invisible(invisible).await;
Ok(())
}
async fn is_glowing(&mut self, player: Resource<Player>) -> wasmtime::Result<bool> {
let player = player_from_resource(self, &player)?;
Ok(player.get_entity().glowing.load(Ordering::Relaxed))
}
async fn set_glowing(
&mut self,
player: Resource<Player>,
glowing: bool,
) -> wasmtime::Result<()> {
let player = player_from_resource(self, &player)?;
player.get_entity().set_glowing(glowing).await;
Ok(())
}
async fn is_fall_flying(&mut self, player: Resource<Player>) -> wasmtime::Result<bool> {
let player = player_from_resource(self, &player)?;
Ok(player.get_entity().fall_flying.load(Ordering::Relaxed))
}
async fn set_fall_flying(
&mut self,
player: Resource<Player>,
fall_flying: bool,
) -> wasmtime::Result<()> {
let player = player_from_resource(self, &player)?;
player.get_entity().set_fall_flying(fall_flying).await;
Ok(())
}
async fn is_on_fire(&mut self, player: Resource<Player>) -> wasmtime::Result<bool> {
let player = player_from_resource(self, &player)?;
Ok(player.get_entity().has_visual_fire.load(Ordering::Relaxed))
}
async fn set_on_fire(
&mut self,
player: Resource<Player>,
on_fire: bool,
) -> wasmtime::Result<()> {
let player = player_from_resource(self, &player)?;
player.get_entity().set_on_fire(on_fire).await;
Ok(())
}
async fn is_on_ground(&mut self, player: Resource<Player>) -> wasmtime::Result<bool> {
let player = player_from_resource(self, &player)?;
Ok(player.get_entity().on_ground.load(Ordering::Relaxed))