diff --git a/pumpkin-plugin-wit/v0.1/player.wit b/pumpkin-plugin-wit/v0.1/player.wit index f6355e9f6..397f54bab 100644 --- a/pumpkin-plugin-wit/v0.1/player.wit +++ b/pumpkin-plugin-wit/v0.1/player.wit @@ -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. diff --git a/pumpkin-world/src/chunk_system/channel.rs b/pumpkin-world/src/chunk_system/channel.rs index 6bb17a928..12b212a07 100644 --- a/pumpkin-world/src/chunk_system/channel.rs +++ b/pumpkin-world/src/chunk_system/channel.rs @@ -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(); } diff --git a/pumpkin-world/src/chunk_system/dag.rs b/pumpkin-world/src/chunk_system/dag.rs index c6a3e0674..28528c238 100644 --- a/pumpkin-world/src/chunk_system/dag.rs +++ b/pumpkin-world/src/chunk_system/dag.rs @@ -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)); + } } } diff --git a/pumpkin-world/src/generation/proto_chunk.rs b/pumpkin-world/src/generation/proto_chunk.rs index 8b9d1375b..c8a2076d2 100644 --- a/pumpkin-world/src/generation/proto_chunk.rs +++ b/pumpkin-world/src/generation/proto_chunk.rs @@ -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, diff --git a/pumpkin-world/src/lighting/engine.rs b/pumpkin-world/src/lighting/engine.rs index 8d52fbf8e..0b9cc0144 100644 --- a/pumpkin-world/src/lighting/engine.rs +++ b/pumpkin-world/src/lighting/engine.rs @@ -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); diff --git a/pumpkin-world/src/lighting/runtime.rs b/pumpkin-world/src/lighting/runtime.rs index b53f8e9f5..1b01a6f49 100644 --- a/pumpkin-world/src/lighting/runtime.rs +++ b/pumpkin-world/src/lighting/runtime.rs @@ -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()); } diff --git a/pumpkin-world/src/lighting/storage.rs b/pumpkin-world/src/lighting/storage.rs index 4c62b2eb4..a248f62b5 100644 --- a/pumpkin-world/src/lighting/storage.rs +++ b/pumpkin-world/src/lighting/storage.rs @@ -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); diff --git a/pumpkin-world/src/poi/mod.rs b/pumpkin-world/src/poi/mod.rs index 3e6815e02..ad4fccc58 100644 --- a/pumpkin-world/src/poi/mod.rs +++ b/pumpkin-world/src/poi/mod.rs @@ -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) { diff --git a/pumpkin-world/src/tick/scheduler.rs b/pumpkin-world/src/tick/scheduler.rs index 9863b99fa..1126311c1 100644 --- a/pumpkin-world/src/tick/scheduler.rs +++ b/pumpkin-world/src/tick/scheduler.rs @@ -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> { 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> 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 { diff --git a/pumpkin/src/entity/mod.rs b/pumpkin/src/entity/mod.rs index 4f8d3116e..377d26ff7 100644 --- a/pumpkin/src/entity/mod.rs +++ b/pumpkin/src/entity/mod.rs @@ -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 { diff --git a/pumpkin/src/lib.rs b/pumpkin/src/lib.rs index 9203c2bdf..f1b59981d 100644 --- a/pumpkin/src/lib.rs +++ b/pumpkin/src/lib.rs @@ -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| 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}"), } }, diff --git a/pumpkin/src/net/bedrock/mod.rs b/pumpkin/src/net/bedrock/mod.rs index e0e7a0056..04b9ceae2 100644 --- a/pumpkin/src/net/bedrock/mod.rs +++ b/pumpkin/src/net/bedrock/mod.rs @@ -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(&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( @@ -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( @@ -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(()) diff --git a/pumpkin/src/net/rcon/mod.rs b/pumpkin/src/net/rcon/mod.rs index 4e24f5c7f..bada820c7 100644 --- a/pumpkin/src/net/rcon/mod.rs +++ b/pumpkin/src/net/rcon/mod.rs @@ -17,7 +17,7 @@ mod packet; pub struct RCONServer; impl RCONServer { - pub async fn run(config: &RCONConfig, server: Arc) -> Result<(), std::io::Error> { + pub async fn run(config: &RCONConfig, server: Arc) { 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(()) } } diff --git a/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/player.rs b/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/player.rs index 89a22df8b..1389774d1 100644 --- a/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/player.rs +++ b/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/player.rs @@ -685,6 +685,81 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState { Ok(()) } + async fn is_swimming(&mut self, player: Resource) -> wasmtime::Result { + let player = player_from_resource(self, &player)?; + Ok(player.get_entity().swimming.load(Ordering::Relaxed)) + } + + async fn set_swimming( + &mut self, + player: Resource, + 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) -> wasmtime::Result { + let player = player_from_resource(self, &player)?; + Ok(player.get_entity().invisible.load(Ordering::Relaxed)) + } + + async fn set_invisible( + &mut self, + player: Resource, + 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) -> wasmtime::Result { + let player = player_from_resource(self, &player)?; + Ok(player.get_entity().glowing.load(Ordering::Relaxed)) + } + + async fn set_glowing( + &mut self, + player: Resource, + 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) -> wasmtime::Result { + 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, + 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) -> wasmtime::Result { + 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, + 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) -> wasmtime::Result { let player = player_from_resource(self, &player)?; Ok(player.get_entity().on_ground.load(Ordering::Relaxed))