1use std::borrow::Cow;
4use std::collections::BTreeSet;
5
6use bevy_ecs::prelude::*;
7use bevy_ecs::query::QueryData;
8use chunkedge_entity::EntityLayerId;
9use chunkedge_protocol::packets::play::game_event_s2c::GameEventKind;
10use chunkedge_protocol::packets::play::respawn_s2c::DataKeptFlags;
11use chunkedge_protocol::packets::play::{
12 GameEventS2c, LoginS2c, RespawnS2c, SetDefaultSpawnPositionS2c,
13};
14use chunkedge_protocol::{BlockPos, GameMode, GlobalPos, Ident, VarInt, WritePacket};
15use chunkedge_registry::tags::TagsRegistry;
16use chunkedge_registry::{DimensionTypeRegistry, RegistryCodec};
17use derive_more::{Deref, DerefMut};
18
19use crate::client::{Client, ViewDistance, VisibleChunkLayer};
20use crate::layer::ChunkLayer;
21
22#[derive(Component, Clone, PartialEq, Eq, Default, Debug)]
25pub struct DeathLocation(pub Option<(Ident<String>, BlockPos)>);
26
27#[derive(Component, Copy, Clone, PartialEq, Eq, Default, Debug, Deref, DerefMut)]
28pub struct IsHardcore(pub bool);
29
30#[derive(Component, Copy, Clone, PartialEq, Eq, Default, Debug, Deref, DerefMut)]
32pub struct HashedSeed(pub u64);
33
34#[derive(Component, Copy, Clone, PartialEq, Eq, Default, Debug, Deref, DerefMut)]
35pub struct ReducedDebugInfo(pub bool);
36
37#[derive(Component, Copy, Clone, PartialEq, Eq, Debug, Deref, DerefMut)]
38pub struct HasRespawnScreen(pub bool);
39
40#[derive(Component, Copy, Clone, PartialEq, Eq, Default, Debug, Deref, DerefMut)]
42pub struct IsDebug(pub bool);
43
44#[derive(Component, Copy, Clone, PartialEq, Eq, Default, Debug, Deref, DerefMut)]
46pub struct IsFlat(pub bool);
47
48#[derive(Component, Copy, Clone, PartialEq, Eq, Default, Debug, Deref, DerefMut)]
49pub struct PortalCooldown(pub i32);
50
51#[derive(Component, Copy, Clone, PartialEq, Eq, Default, Debug, Deref, DerefMut)]
53pub struct PrevGameMode(pub Option<GameMode>);
54
55impl Default for HasRespawnScreen {
56 fn default() -> Self {
57 Self(true)
58 }
59}
60
61#[derive(Component, Copy, Clone, PartialEq, Default, Debug)]
64pub struct RespawnPosition {
65 pub pos: BlockPos,
68 pub yaw: f32,
70}
71
72#[derive(QueryData)]
75#[query_data(mutable)]
76pub struct ClientSpawnQuery {
77 pub is_hardcore: &'static mut IsHardcore,
78 pub game_mode: &'static mut GameMode,
79 pub prev_game_mode: &'static mut PrevGameMode,
80 pub hashed_seed: &'static mut HashedSeed,
81 pub view_distance: &'static mut ViewDistance,
82 pub reduced_debug_info: &'static mut ReducedDebugInfo,
83 pub has_respawn_screen: &'static mut HasRespawnScreen,
84 pub is_debug: &'static mut IsDebug,
85 pub is_flat: &'static mut IsFlat,
86 pub death_loc: &'static mut DeathLocation,
87 pub portal_cooldown: &'static mut PortalCooldown,
88}
89
90pub(super) fn initial_join(
91 codec: Res<RegistryCodec>,
92 tags: Res<TagsRegistry>,
93 mut clients: Query<(&mut Client, &VisibleChunkLayer, ClientSpawnQueryReadOnly), Added<Client>>,
94 chunk_layers: Query<&ChunkLayer>,
95) {
96 for (mut client, visible_chunk_layer, spawn) in &mut clients {
97 let Ok(chunk_layer) = chunk_layers.get(visible_chunk_layer.0) else {
98 continue;
99 };
100
101 let dimension_names: BTreeSet<Ident<Cow<str>>> = codec
102 .registry(DimensionTypeRegistry::KEY)
103 .iter()
104 .map(|value| value.name.as_str_ident().into())
105 .collect();
106
107 let dimension_type = chunk_layer.dimension_type();
108
109 let last_death_location = spawn.death_loc.0.as_ref().map(|(id, pos)| GlobalPos {
110 dimension_name: id.as_str_ident().into(),
111 position: *pos,
112 });
113
114 _ = client.enc.prepend_packet(&LoginS2c {
117 entity_id: 0, is_hardcore: spawn.is_hardcore.0,
119 game_mode: *spawn.game_mode,
120 previous_game_mode: spawn.prev_game_mode.0.into(),
121 dimension_names: Cow::Owned(dimension_names),
122 dimension_name: Ident::new("overworld").unwrap(),
123 hashed_seed: spawn.hashed_seed.0 as i64,
124 max_players: VarInt(0), view_distance: VarInt(i32::from(spawn.view_distance.get())),
126 simulation_distance: VarInt(16), reduced_debug_info: spawn.reduced_debug_info.0,
128 enable_respawn_screen: spawn.has_respawn_screen.0,
129 is_debug: spawn.is_debug.0,
130 is_flat: spawn.is_flat.0,
131 last_death_location,
132 portal_cooldown: VarInt(spawn.portal_cooldown.0),
133 do_limited_crafting: false, dimension_type: VarInt(dimension_type.get_value().into()),
135 enforeces_secure_chat: true,
136 sea_level: VarInt(0),
138 });
139
140 client.write_packet_bytes(tags.sync_tags_packet());
141
142 client.write_packet(&GameEventS2c {
143 kind: GameEventKind::StartWaitingForLevelChunks,
144 value: 0.0,
145 });
146
147 }
154}
155
156pub(super) fn respawn(
157 mut clients: Query<
158 (
159 &mut Client,
160 &EntityLayerId,
161 &DeathLocation,
162 &HashedSeed,
163 &GameMode,
164 &PrevGameMode,
165 &IsDebug,
166 &IsFlat,
167 ),
168 Changed<VisibleChunkLayer>,
169 >,
170 chunk_layers: Query<&ChunkLayer>,
171) {
172 for (mut client, loc, death_loc, hashed_seed, game_mode, prev_game_mode, is_debug, is_flat) in
173 &mut clients
174 {
175 if client.is_added() {
176 continue;
178 }
179
180 let Ok(chunk_layer) = chunk_layers.get(loc.0) else {
181 continue;
182 };
183
184 let dimension_type = chunk_layer.dimension_type();
185
186 let last_death_location = death_loc.0.as_ref().map(|(id, pos)| GlobalPos {
187 dimension_name: id.as_str_ident().into(),
188 position: *pos,
189 });
190
191 client.write_packet(&RespawnS2c {
192 dimension_type: VarInt(dimension_type.get_value().into()),
193 dimension_name: Ident::new("overworld").unwrap(),
194 hashed_seed: hashed_seed.0,
195 game_mode: *game_mode,
196 previous_game_mode: prev_game_mode.0.into(),
197 is_debug: is_debug.0,
198 is_flat: is_flat.0,
199 last_death_location,
200 portal_cooldown: VarInt(0), sea_level: VarInt(0), data_kept: DataKeptFlags::new(),
203 });
204
205 client.write_packet(&GameEventS2c {
206 kind: GameEventKind::StartWaitingForLevelChunks,
207 value: 0.0,
208 });
209 }
210}
211
212pub(super) fn update_respawn_position(
217 mut clients: Query<(&mut Client, &RespawnPosition), Changed<RespawnPosition>>,
218) {
219 for (mut client, respawn_pos) in &mut clients {
220 client.write_packet(&SetDefaultSpawnPositionS2c {
221 position: respawn_pos.pos,
222 angle: respawn_pos.yaw,
223 });
224 }
225}