1use std::borrow::Cow;
4use std::collections::BTreeSet;
5
6use bevy_ecs::prelude::*;
7use bevy_ecs::query::QueryData;
8use derive_more::{Deref, DerefMut};
9use valence_entity::EntityLayerId;
10use valence_protocol::packets::play::{GameJoinS2c, PlayerRespawnS2c, PlayerSpawnPositionS2c};
11use valence_protocol::{BlockPos, GameMode, GlobalPos, Ident, VarInt, WritePacket};
12use valence_registry::tags::TagsRegistry;
13use valence_registry::{BiomeRegistry, RegistryCodec};
14
15use crate::client::{Client, ViewDistance, VisibleChunkLayer};
16use crate::layer::ChunkLayer;
17
18#[derive(Component, Clone, PartialEq, Eq, Default, Debug)]
21pub struct DeathLocation(pub Option<(Ident<String>, BlockPos)>);
22
23#[derive(Component, Copy, Clone, PartialEq, Eq, Default, Debug, Deref, DerefMut)]
24pub struct IsHardcore(pub bool);
25
26#[derive(Component, Copy, Clone, PartialEq, Eq, Default, Debug, Deref, DerefMut)]
28pub struct HashedSeed(pub u64);
29
30#[derive(Component, Copy, Clone, PartialEq, Eq, Default, Debug, Deref, DerefMut)]
31pub struct ReducedDebugInfo(pub bool);
32
33#[derive(Component, Copy, Clone, PartialEq, Eq, Debug, Deref, DerefMut)]
34pub struct HasRespawnScreen(pub bool);
35
36#[derive(Component, Copy, Clone, PartialEq, Eq, Default, Debug, Deref, DerefMut)]
38pub struct IsDebug(pub bool);
39
40#[derive(Component, Copy, Clone, PartialEq, Eq, Default, Debug, Deref, DerefMut)]
42pub struct IsFlat(pub bool);
43
44#[derive(Component, Copy, Clone, PartialEq, Eq, Default, Debug, Deref, DerefMut)]
45pub struct PortalCooldown(pub i32);
46
47#[derive(Component, Copy, Clone, PartialEq, Eq, Default, Debug, Deref, DerefMut)]
49pub struct PrevGameMode(pub Option<GameMode>);
50
51impl Default for HasRespawnScreen {
52 fn default() -> Self {
53 Self(true)
54 }
55}
56
57#[derive(Component, Copy, Clone, PartialEq, Default, Debug)]
60pub struct RespawnPosition {
61 pub pos: BlockPos,
64 pub yaw: f32,
66}
67
68#[derive(QueryData)]
71#[query_data(mutable)]
72pub struct ClientSpawnQuery {
73 pub is_hardcore: &'static mut IsHardcore,
74 pub game_mode: &'static mut GameMode,
75 pub prev_game_mode: &'static mut PrevGameMode,
76 pub hashed_seed: &'static mut HashedSeed,
77 pub view_distance: &'static mut ViewDistance,
78 pub reduced_debug_info: &'static mut ReducedDebugInfo,
79 pub has_respawn_screen: &'static mut HasRespawnScreen,
80 pub is_debug: &'static mut IsDebug,
81 pub is_flat: &'static mut IsFlat,
82 pub death_loc: &'static mut DeathLocation,
83 pub portal_cooldown: &'static mut PortalCooldown,
84}
85
86pub(super) fn initial_join(
87 codec: Res<RegistryCodec>,
88 tags: Res<TagsRegistry>,
89 mut clients: Query<(&mut Client, &VisibleChunkLayer, ClientSpawnQueryReadOnly), Added<Client>>,
90 chunk_layers: Query<&ChunkLayer>,
91) {
92 for (mut client, visible_chunk_layer, spawn) in &mut clients {
93 let Ok(chunk_layer) = chunk_layers.get(visible_chunk_layer.0) else {
94 continue;
95 };
96
97 let dimension_names: BTreeSet<Ident<Cow<str>>> = codec
98 .registry(BiomeRegistry::KEY)
99 .iter()
100 .map(|value| value.name.as_str_ident().into())
101 .collect();
102
103 let dimension_name: Ident<Cow<str>> = chunk_layer.dimension_type_name().into();
104
105 let last_death_location = spawn.death_loc.0.as_ref().map(|(id, pos)| GlobalPos {
106 dimension_name: id.as_str_ident().into(),
107 position: *pos,
108 });
109
110 _ = client.enc.prepend_packet(&GameJoinS2c {
113 entity_id: 0, is_hardcore: spawn.is_hardcore.0,
115 game_mode: *spawn.game_mode,
116 previous_game_mode: spawn.prev_game_mode.0.into(),
117 dimension_names: Cow::Owned(dimension_names),
118 registry_codec: Cow::Borrowed(codec.cached_codec()),
119 dimension_type_name: dimension_name.clone(),
120 dimension_name,
121 hashed_seed: spawn.hashed_seed.0 as i64,
122 max_players: VarInt(0), view_distance: VarInt(i32::from(spawn.view_distance.get())),
124 simulation_distance: VarInt(16), reduced_debug_info: spawn.reduced_debug_info.0,
126 enable_respawn_screen: spawn.has_respawn_screen.0,
127 is_debug: spawn.is_debug.0,
128 is_flat: spawn.is_flat.0,
129 last_death_location,
130 portal_cooldown: VarInt(spawn.portal_cooldown.0),
131 });
132
133 client.write_packet_bytes(tags.sync_tags_packet());
134
135 }
142}
143
144pub(super) fn respawn(
145 mut clients: Query<
146 (
147 &mut Client,
148 &EntityLayerId,
149 &DeathLocation,
150 &HashedSeed,
151 &GameMode,
152 &PrevGameMode,
153 &IsDebug,
154 &IsFlat,
155 ),
156 Changed<VisibleChunkLayer>,
157 >,
158 chunk_layers: Query<&ChunkLayer>,
159) {
160 for (mut client, loc, death_loc, hashed_seed, game_mode, prev_game_mode, is_debug, is_flat) in
161 &mut clients
162 {
163 if client.is_added() {
164 continue;
166 }
167
168 let Ok(chunk_layer) = chunk_layers.get(loc.0) else {
169 continue;
170 };
171
172 let dimension_name = chunk_layer.dimension_type_name();
173
174 let last_death_location = death_loc.0.as_ref().map(|(id, pos)| GlobalPos {
175 dimension_name: id.as_str_ident().into(),
176 position: *pos,
177 });
178
179 client.write_packet(&PlayerRespawnS2c {
180 dimension_type_name: dimension_name.into(),
181 dimension_name: dimension_name.into(),
182 hashed_seed: hashed_seed.0,
183 game_mode: *game_mode,
184 previous_game_mode: prev_game_mode.0.into(),
185 is_debug: is_debug.0,
186 is_flat: is_flat.0,
187 copy_metadata: true,
188 last_death_location,
189 portal_cooldown: VarInt(0), });
191 }
192}
193
194pub(super) fn update_respawn_position(
199 mut clients: Query<(&mut Client, &RespawnPosition), Changed<RespawnPosition>>,
200) {
201 for (mut client, respawn_pos) in &mut clients {
202 client.write_packet(&PlayerSpawnPositionS2c {
203 position: respawn_pos.pos,
204 angle: respawn_pos.yaw,
205 });
206 }
207}