From e6f01e5e4d6cee3881b70f91de97b25aba810c4b Mon Sep 17 00:00:00 2001 From: James P <93358231+dessh@users.noreply.github.com> Date: Sun, 18 Sep 2022 14:32:40 +1200 Subject: [PATCH] Custom resource packs (#68) Implements custom resource pack support. Adds a `Client` method to set resource pack, and a `ClientEvent` for listening to resource pack status updates from clients. --- assets/example_pack.zip | Bin 0 -> 11040 bytes examples/resource_pack.rs | 223 ++++++++++++++++++++++++++++++++++++ src/client.rs | 41 ++++++- src/client/event.rs | 4 +- src/protocol/packets/c2s.rs | 1 + src/protocol/packets/s2c.rs | 10 ++ 6 files changed, 274 insertions(+), 5 deletions(-) create mode 100644 assets/example_pack.zip create mode 100644 examples/resource_pack.rs diff --git a/assets/example_pack.zip b/assets/example_pack.zip new file mode 100644 index 0000000000000000000000000000000000000000..30224ee1b02f1874992249803614fa4972ff06b6 GIT binary patch literal 11040 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 l%%A?(s}O&vq< ShutdownResult { + env_logger::Builder::new() + .filter_module("valence", LevelFilter::Trace) + .parse_default_env() + .init(); + + valence::start_server( + Game { + player_count: AtomicUsize::new(0), + }, + ServerState { + player_list: None, + sheep_id: None, + }, + ) +} + +struct Game { + player_count: AtomicUsize, +} + +struct ServerState { + player_list: Option, + sheep_id: Option, +} + +#[derive(Default)] +struct ClientState { + entity_id: EntityId, +} + +const MAX_PLAYERS: usize = 10; + +const SPAWN_POS: BlockPos = BlockPos::new(0, 100, 0); + +#[async_trait] +impl Config for Game { + type ServerState = ServerState; + type ClientState = ClientState; + type EntityState = (); + type WorldState = (); + type ChunkState = (); + type PlayerListState = (); + + fn max_connections(&self) -> usize { + // We want status pings to be successful even if the server is full. + MAX_PLAYERS + 64 + } + + async fn server_list_ping( + &self, + _server: &SharedServer, + _remote_addr: SocketAddr, + _protocol_version: i32, + ) -> ServerListPing { + ServerListPing::Respond { + online_players: self.player_count.load(Ordering::SeqCst) as i32, + max_players: MAX_PLAYERS as i32, + player_sample: Default::default(), + description: "Hello Valence!".color(Color::AQUA), + favicon_png: Some(include_bytes!("../assets/logo-64x64.png").as_slice().into()), + } + } + + fn init(&self, server: &mut Server) { + let (world_id, world) = server.worlds.insert(DimensionId::default(), ()); + server.state.player_list = Some(server.player_lists.insert(()).0); + + let size = 5; + for z in -size..size { + for x in -size..size { + world.chunks.insert([x, z], UnloadedChunk::default(), ()); + } + } + + let (sheep_id, sheep) = server.entities.insert(EntityKind::Sheep, ()); + server.state.sheep_id = Some(sheep_id); + sheep.set_world(world_id); + sheep.set_position([ + SPAWN_POS.x as f64 + 0.5, + SPAWN_POS.y as f64 + 4.0, + SPAWN_POS.z as f64 + 0.5, + ]); + + if let TrackedData::Sheep(sheep_data) = sheep.data_mut() { + sheep_data.set_custom_name("Hit me".color(Color::GREEN)); + } + + world.chunks.set_block_state(SPAWN_POS, BlockState::BEDROCK); + } + + fn update(&self, server: &mut Server) { + let (world_id, _) = server.worlds.iter_mut().next().expect("missing world"); + + server.clients.retain(|_, client| { + if client.created_this_tick() { + if self + .player_count + .fetch_update(Ordering::SeqCst, Ordering::SeqCst, |count| { + (count < MAX_PLAYERS).then_some(count + 1) + }) + .is_err() + { + client.disconnect("The server is full!".color(Color::RED)); + return false; + } + + match server + .entities + .insert_with_uuid(EntityKind::Player, client.uuid(), ()) + { + Some((id, _)) => client.state.entity_id = id, + None => { + client.disconnect("Conflicting UUID"); + return false; + } + } + + client.spawn(world_id); + client.set_flat(true); + client.set_game_mode(GameMode::Creative); + client.teleport( + [ + SPAWN_POS.x as f64 + 0.5, + SPAWN_POS.y as f64 + 1.0, + SPAWN_POS.z as f64 + 0.5, + ], + 0.0, + 0.0, + ); + client.set_player_list(server.state.player_list.clone()); + + if let Some(id) = &server.state.player_list { + server.player_lists.get_mut(id).insert( + client.uuid(), + client.username(), + client.textures().cloned(), + client.game_mode(), + 0, + None, + ); + } + + set_example_pack(client); + } + + if client.is_disconnected() { + self.player_count.fetch_sub(1, Ordering::SeqCst); + if let Some(id) = &server.state.player_list { + server.player_lists.get_mut(id).remove(client.uuid()); + } + server.entities.remove(client.state.entity_id); + + return false; + } + + let player = server.entities.get_mut(client.state.entity_id).unwrap(); + + while let Some(event) = handle_event_default(client, player) { + match event { + ClientEvent::InteractWithEntity { kind, id, .. } => { + if kind == InteractWithEntityKind::Attack + && Some(id) == server.state.sheep_id + { + set_example_pack(client); + } + } + ClientEvent::ResourcePackStatusChanged(s) => { + let message = match s { + ResourcePackStatus::SuccessfullyLoaded => { + "The resource pack was successfully loaded!".color(Color::GREEN) + } + ResourcePackStatus::Declined => { + "You declined the resource pack :(".color(Color::RED) + } + ResourcePackStatus::FailedDownload => { + "The resource pack download failed.".color(Color::RED) + } + _ => continue, + }; + + client.send_message(message.italic()); + client.send_message( + "Hit the sheep above you to prompt the resource pack again." + .color(Color::GRAY) + .italic(), + ); + } + _ => (), + } + } + + true + }); + } +} + +fn set_example_pack(client: &mut Client) { + client.set_resource_pack( + "https://download843.mediafire.com/jbx81s8p7jig/dve0hxjaqecy7c6/example_pack.zip" + .to_owned(), // TODO: change to the GitHub URL of /assets/example_pack.zip + String::default(), + false, + None, + ); +} diff --git a/src/client.rs b/src/client.rs index bdfbbff..6cf6fde 100644 --- a/src/client.rs +++ b/src/client.rs @@ -30,14 +30,14 @@ use crate::protocol::packets::s2c::play::{ AcknowledgeBlockChange, BiomeRegistry, ChatTypeRegistry, ClearTitles, CustomSoundEffect, DimensionTypeRegistry, DimensionTypeRegistryEntry, DisconnectPlay, EntityAnimationS2c, EntityAttributesProperty, EntityEvent, GameEvent, GameStateChangeReason, KeepAliveS2c, - LoginPlay, PlayerPositionLookFlags, RegistryCodec, RemoveEntities, Respawn, S2cPlayPacket, - SetActionBarText, SetCenterChunk, SetDefaultSpawnPosition, SetEntityMetadata, + LoginPlay, PlayerPositionLookFlags, RegistryCodec, RemoveEntities, ResourcePackS2c, Respawn, + S2cPlayPacket, SetActionBarText, SetCenterChunk, SetDefaultSpawnPosition, SetEntityMetadata, SetEntityVelocity, SetExperience, SetHeadRotation, SetHealth, SetRenderDistance, SetSubtitleText, SetTitleText, SoundCategory, SynchronizePlayerPosition, SystemChatMessage, TeleportEntity, UnloadChunk, UpdateAttributes, UpdateEntityPosition, UpdateEntityPositionAndRotation, UpdateEntityRotation, }; -use crate::protocol::{BoundedInt, ByteAngle, NbtBridge, RawBytes, VarInt}; +use crate::protocol::{BoundedInt, BoundedString, ByteAngle, NbtBridge, RawBytes, VarInt}; use crate::server::{C2sPacketChannels, NewClientData, S2cPlayMessage, SharedServer}; use crate::slab_versioned::{Key, VersionedSlab}; use crate::text::Text; @@ -225,6 +225,7 @@ pub struct Client { /// Should be sent after login packet. msgs_to_send: Vec, bar_to_send: Option, + resource_pack_to_send: Option, attack_speed: f64, movement_speed: f64, bits: ClientBits, @@ -291,6 +292,7 @@ impl Client { dug_blocks: Vec::new(), msgs_to_send: Vec::new(), bar_to_send: None, + resource_pack_to_send: None, attack_speed: 4.0, movement_speed: 0.7, bits: ClientBits::new() @@ -641,6 +643,31 @@ impl Client { self.bits.hardcore() } + /// Requests that the client download and enable a resource pack. + /// + /// # Arguments + /// * `url` - The URL of the resource pack file. + /// * `hash` - The SHA-1 hash of the resource pack file. Any value other + /// than a 40-character hexadecimal string is ignored by the client. + /// * `forced` - Whether a client should be kicked from the server upon + /// declining the pack (this is enforced client-side) + /// * `prompt_message` - A message to be displayed with the resource pack + /// dialog. + pub fn set_resource_pack( + &mut self, + url: impl Into, + hash: impl Into, + forced: bool, + prompt_message: impl Into>, + ) { + self.resource_pack_to_send = Some(ResourcePackS2c { + url: url.into(), + hash: BoundedString(hash.into()), + forced, + prompt_message: prompt_message.into(), + }); + } + /// Gets the client's current settings. pub fn settings(&self) -> Option<&Settings> { self.settings.as_ref() @@ -893,7 +920,9 @@ impl Client { C2sPlayPacket::ChangeRecipeBookSettings(_) => {} C2sPlayPacket::SetSeenRecipe(_) => {} C2sPlayPacket::RenameItem(_) => {} - C2sPlayPacket::ResourcePackC2s(_) => {} + C2sPlayPacket::ResourcePackC2s(p) => self + .events + .push_back(ClientEvent::ResourcePackStatusChanged(p)), C2sPlayPacket::SeenAdvancements(_) => {} C2sPlayPacket::SelectTrade(_) => {} C2sPlayPacket::SetBeaconEffect(_) => {} @@ -1253,6 +1282,10 @@ impl Client { send_packet(&mut self.send, SetActionBarText { text: bar }); } + if let Some(p) = self.resource_pack_to_send.take() { + send_packet(&mut self.send, p); + } + let mut entities_to_unload = Vec::new(); // Update all entities that are visible and unload entities that are no diff --git a/src/client/event.rs b/src/client/event.rs index f4efa83..15421f8 100644 --- a/src/client/event.rs +++ b/src/client/event.rs @@ -8,7 +8,7 @@ use crate::config::Config; use crate::entity::types::Pose; use crate::entity::{Entity, EntityEvent, EntityId, TrackedData}; pub use crate::protocol::packets::c2s::play::{ - BlockFace, ChatMode, DisplayedSkinParts, Hand, MainHand, + BlockFace, ChatMode, DisplayedSkinParts, Hand, MainHand, ResourcePackC2s as ResourcePackStatus, }; pub use crate::protocol::packets::s2c::play::GameMode; use crate::protocol::VarInt; @@ -128,6 +128,7 @@ pub enum ClientEvent { /// Sequence number sequence: VarInt, }, + ResourcePackStatusChanged(ResourcePackStatus), } #[derive(Clone, PartialEq, Debug)] @@ -285,6 +286,7 @@ pub fn handle_event_default( ClientEvent::SteerBoat { .. } => {} ClientEvent::Digging { .. } => {} ClientEvent::InteractWithBlock { .. } => {} + ClientEvent::ResourcePackStatusChanged(_) => {} } entity.set_world(client.world()); diff --git a/src/protocol/packets/c2s.rs b/src/protocol/packets/c2s.rs index b8d2f66..8a004fe 100644 --- a/src/protocol/packets/c2s.rs +++ b/src/protocol/packets/c2s.rs @@ -528,6 +528,7 @@ pub mod play { } def_enum! { + #[derive(Copy, PartialEq, Eq)] ResourcePackC2s: VarInt { SuccessfullyLoaded = 0, Declined = 1, diff --git a/src/protocol/packets/s2c.rs b/src/protocol/packets/s2c.rs index fd4497d..8a26871 100644 --- a/src/protocol/packets/s2c.rs +++ b/src/protocol/packets/s2c.rs @@ -667,6 +667,15 @@ pub mod play { } } + def_struct! { + ResourcePackS2c { + url: String, + hash: BoundedString<0, 40>, + forced: bool, + prompt_message: Option, + } + } + def_struct! { Respawn { dimension_type_name: Ident, @@ -895,6 +904,7 @@ pub mod play { PlayerInfo = 55, SynchronizePlayerPosition = 57, RemoveEntities = 59, + ResourcePackS2c = 61, Respawn = 62, SetHeadRotation = 63, UpdateSectionBlocks = 64,